From 0e1328675c83b1f33102607bbe0fa560eb1bbec0 Mon Sep 17 00:00:00 2001 From: Moony Date: Wed, 2 Aug 2023 16:06:16 -0500 Subject: [PATCH] Toolshed (#4197) * Saving work * Move shit to engine * lord * merg * awa * bql is kill * forgot the fucking bike rack * bql is kill for real * pjb will kill me * aughfhbdj * yo ho here we go on my way to the MINES * a * adgddf * gdsgvfvxshngfgh * b * hfsjhghj * hbfdjjh * tf you mean i have to document it * follow C# standards * Assorted cleanup and documentation pass, minor bugfix in ValueRefParser. * Start porting old commands, remove that pesky prefix in favor of integrating with the shell. * Fix valueref up a bit, improve autocomplete for it. * e * Toolshed type system adventure. * a log * a * a e i o u * awa * fix tests * Arithmetic commands. * a * parse improvements --------- Co-authored-by: moonheart08 --- RELEASE-NOTES.md | 5 +- .../NotoSans/NotoSansMono-Regular.ttf | Bin 0 -> 403040 bytes Resources/Locale/en-US/commands.ftl | 14 +- Resources/Locale/en-US/toolshed-commands.ftl | 163 ++++++++ .../Console/ClientConsoleHost.Completions.cs | 11 +- Robust.Client/Console/ClientConsoleHost.cs | 37 +- Robust.Client/Console/IClientConsoleHost.cs | 2 +- .../CustomControls/DebugConsole.xaml | 2 +- .../DebugConsole.xaml.Completions.cs | 14 +- .../CustomControls/DebugConsole.xaml.cs | 12 +- .../Stylesheets/DefaultStylesheet.cs | 10 + Robust.Server/BaseServer.cs | 2 + Robust.Server/Bql/BqlQueryManager.Parsers.cs | 162 -------- .../Bql/BqlQueryManager.SimpleExecutor.cs | 39 -- Robust.Server/Bql/BqlQueryManager.cs | 44 --- Robust.Server/Bql/BqlQuerySelector.Builtin.cs | 368 ------------------ Robust.Server/Bql/BqlQuerySelector.cs | 63 --- Robust.Server/Bql/BqlQuerySelectorParsed.cs | 19 - Robust.Server/Bql/ForAllCommand.cs | 78 ---- Robust.Server/Bql/IBqlQueryManager.cs | 11 - .../Bql/RegisterBqlQuerySelectorAttribute.cs | 14 - Robust.Server/Console/ConGroupController.cs | 12 +- .../IConGroupControllerImplementation.cs | 3 +- Robust.Server/Console/ServerConsoleHost.cs | 68 +++- Robust.Server/ServerIoC.cs | 2 - .../Toolshed/Commands/Players/ActorCommand.cs | 24 ++ .../Commands/Players/PlayersCommand.cs | 84 ++++ Robust.Shared.Scripting/ILCommand.cs | 35 ++ .../Robust.Shared.Scripting.csproj | 1 + .../ScriptGlobalsShared.cs | 54 ++- Robust.Shared/Console/Commands/HelpCommand.cs | 12 +- Robust.Shared/Console/ConsoleHost.cs | 6 +- Robust.Shared/Console/ConsoleShell.cs | 6 + Robust.Shared/Console/IConsoleCommand.cs | 5 +- Robust.Shared/Console/IConsoleHost.cs | 5 +- Robust.Shared/Console/IConsoleShell.cs | 3 + Robust.Shared/Console/LocalizedCommands.cs | 6 +- .../Network/Messages/MsgConCmdAck.cs | 15 +- .../Network/Messages/MsgConCompletion.cs | 6 + Robust.Shared/Players/ICommonSession.cs | 6 - Robust.Shared/SharedIoC.cs | 2 + Robust.Shared/Toolshed/Attributes.cs | 90 +++++ .../Toolshed/Commands/Debug/FuckCommand.cs | 13 + .../Toolshed/Commands/EcsCompCommand.cs | 18 + .../Toolshed/Commands/Entities/CompCommand.cs | 28 ++ .../Commands/Entities/DeleteCommand.cs | 20 + .../Toolshed/Commands/Entities/DoCommand.cs | 44 +++ .../Toolshed/Commands/Entities/EntCommand.cs | 11 + .../Commands/Entities/EntitiesCommand.cs | 16 + .../Commands/Entities/NamedCommand.cs | 17 + .../Commands/Entities/NearbyCommand.cs | 18 + .../Commands/Entities/PausedCommand.cs | 15 + .../Commands/Entities/PrototypedCommand.cs | 15 + .../Toolshed/Commands/Entities/WithCommand.cs | 16 + .../Toolshed/Commands/Generic/AnyCommand.cs | 11 + .../Toolshed/Commands/Generic/ArrowCommand.cs | 32 ++ .../Toolshed/Commands/Generic/AsCommand.cs | 13 + .../Toolshed/Commands/Generic/CountCommand.cs | 14 + .../Commands/Generic/EmplaceCommand.cs | 157 ++++++++ .../Toolshed/Commands/Generic/FirstCommand.cs | 11 + .../Commands/Generic/IsEmptyCommand.cs | 22 ++ .../Commands/Generic/IsNullCommand.cs | 8 + .../Toolshed/Commands/Generic/MapCommand.cs | 22 ++ .../Commands/Generic/SelectCommand.cs | 38 ++ .../Toolshed/Commands/Generic/SplatCommand.cs | 27 ++ .../Commands/Generic/UniqueCommand.cs | 12 + .../Toolshed/Commands/Generic/ValCommand.cs | 16 + .../Toolshed/Commands/Generic/WhereCommand.cs | 28 ++ .../Commands/Math/ArithmeticCommands.cs | 166 ++++++++ .../Commands/Math/ComparisonCommands.cs | 113 ++++++ .../Commands/Misc/BuildInfoCommand.cs | 27 ++ .../Toolshed/Commands/Misc/CmdCommand.cs | 32 ++ .../Toolshed/Commands/Misc/ExplainCommand.cs | 21 + .../Toolshed/Commands/Misc/HelpCommand.cs | 28 ++ .../Toolshed/Commands/Misc/IoCCommand.cs | 15 + .../Toolshed/Commands/Misc/LocCommand.cs | 17 + .../Toolshed/Commands/Misc/PhysicsCommand.cs | 48 +++ .../Toolshed/Commands/Misc/SearchCommand.cs | 24 ++ .../Commands/Misc/StopwatchCommand.cs | 19 + .../Toolshed/Commands/Misc/TypesCommand.cs | 43 ++ .../Toolshed/Commands/Misc/VarsCommand.cs | 13 + .../Toolshed/Commands/Players/SelfCommand.cs | 25 ++ .../Toolshed/Commands/Types/MethodsCommand.cs | 49 +++ .../Toolshed/Commands/Vfs/CdCommand.cs | 27 ++ .../Toolshed/Commands/Vfs/LsCommand.cs | 31 ++ .../Toolshed/Commands/Vfs/VfsCommand.cs | 29 ++ Robust.Shared/Toolshed/Errors/IConError.cs | 123 ++++++ .../Errors/NotForServerConsoleError.cs | 18 + .../Errors/SessionHasNoEntityError.cs | 18 + .../Errors/UnhandledExceptionError.cs | 29 ++ Robust.Shared/Toolshed/ForwardParser.cs | 191 +++++++++ Robust.Shared/Toolshed/IInvocationContext.cs | 152 ++++++++ .../Toolshed/IPermissionController.cs | 10 + .../Invocation/OldShellInvocationContext.cs | 73 ++++ .../Toolshed/ReflectionExtensions.cs | 294 ++++++++++++++ Robust.Shared/Toolshed/Syntax/Block.cs | 155 ++++++++ Robust.Shared/Toolshed/Syntax/Expression.cs | 187 +++++++++ .../Toolshed/Syntax/ParsedCommand.cs | 329 ++++++++++++++++ Robust.Shared/Toolshed/Syntax/ValueRef.cs | 106 +++++ .../Toolshed/ToolshedCommand.Entities.cs | 113 ++++++ .../Toolshed/ToolshedCommand.Help.cs | 45 +++ .../ToolshedCommand.Implementations.cs | 119 ++++++ Robust.Shared/Toolshed/ToolshedCommand.cs | 207 ++++++++++ .../Toolshed/ToolshedCommandImplementor.cs | 252 ++++++++++++ .../Toolshed/ToolshedManager.Parsing.cs | 148 +++++++ .../Toolshed/ToolshedManager.Permissions.cs | 12 + .../Toolshed/ToolshedManager.PrettyPrint.cs | 67 ++++ .../Toolshed/ToolshedManager.Queries.cs | 188 +++++++++ .../Toolshed/ToolshedManager.Types.cs | 113 ++++++ Robust.Shared/Toolshed/ToolshedManager.cs | 256 ++++++++++++ .../Toolshed/TypeParsers/BlockTypeParser.cs | 80 ++++ .../TypeParsers/ComponentTypeParser.cs | 78 ++++ .../TypeParsers/EntityUidTypeParser.cs | 69 ++++ .../TypeParsers/ExpressionTypeParser.cs | 48 +++ .../Toolshed/TypeParsers/FloatTypeParser.cs | 57 +++ Robust.Shared/Toolshed/TypeParsers/IAsType.cs | 9 + .../Toolshed/TypeParsers/IntTypeParser.cs | 57 +++ .../TypeParsers/PrototypeTypeParser.cs | 75 ++++ .../TypeParsers/QuantityTypeParser.cs | 73 ++++ .../Toolshed/TypeParsers/ResPathTypeParser.cs | 30 ++ .../Toolshed/TypeParsers/StringTypeParser.cs | 101 +++++ .../Toolshed/TypeParsers/TypeParser.cs | 41 ++ .../Toolshed/TypeParsers/TypeTypeParser.cs | 228 +++++++++++ .../TypeParsers/ValueRefTypeParser.cs | 116 ++++++ .../Commands/ViewVariablesBaseCommand.cs | 2 +- 125 files changed, 6280 insertions(+), 873 deletions(-) create mode 100644 Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf create mode 100644 Resources/Locale/en-US/toolshed-commands.ftl delete mode 100644 Robust.Server/Bql/BqlQueryManager.Parsers.cs delete mode 100644 Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs delete mode 100644 Robust.Server/Bql/BqlQueryManager.cs delete mode 100644 Robust.Server/Bql/BqlQuerySelector.Builtin.cs delete mode 100644 Robust.Server/Bql/BqlQuerySelector.cs delete mode 100644 Robust.Server/Bql/BqlQuerySelectorParsed.cs delete mode 100644 Robust.Server/Bql/ForAllCommand.cs delete mode 100644 Robust.Server/Bql/IBqlQueryManager.cs delete mode 100644 Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs create mode 100644 Robust.Server/Toolshed/Commands/Players/ActorCommand.cs create mode 100644 Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs create mode 100644 Robust.Shared.Scripting/ILCommand.cs create mode 100644 Robust.Shared/Toolshed/Attributes.cs create mode 100644 Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/EcsCompCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/PrototypedCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs create mode 100644 Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs create mode 100644 Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs create mode 100644 Robust.Shared/Toolshed/Errors/IConError.cs create mode 100644 Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs create mode 100644 Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs create mode 100644 Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs create mode 100644 Robust.Shared/Toolshed/ForwardParser.cs create mode 100644 Robust.Shared/Toolshed/IInvocationContext.cs create mode 100644 Robust.Shared/Toolshed/IPermissionController.cs create mode 100644 Robust.Shared/Toolshed/Invocation/OldShellInvocationContext.cs create mode 100644 Robust.Shared/Toolshed/ReflectionExtensions.cs create mode 100644 Robust.Shared/Toolshed/Syntax/Block.cs create mode 100644 Robust.Shared/Toolshed/Syntax/Expression.cs create mode 100644 Robust.Shared/Toolshed/Syntax/ParsedCommand.cs create mode 100644 Robust.Shared/Toolshed/Syntax/ValueRef.cs create mode 100644 Robust.Shared/Toolshed/ToolshedCommand.Entities.cs create mode 100644 Robust.Shared/Toolshed/ToolshedCommand.Help.cs create mode 100644 Robust.Shared/Toolshed/ToolshedCommand.Implementations.cs create mode 100644 Robust.Shared/Toolshed/ToolshedCommand.cs create mode 100644 Robust.Shared/Toolshed/ToolshedCommandImplementor.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.Parsing.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.Permissions.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.PrettyPrint.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.Queries.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.Types.cs create mode 100644 Robust.Shared/Toolshed/ToolshedManager.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/BlockTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/ExpressionTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/FloatTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/IAsType.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/IntTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/ResPathTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/TypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs create mode 100644 Robust.Shared/Toolshed/TypeParsers/ValueRefTypeParser.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bfd0c0448..d446d1e43 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,10 @@ END TEMPLATE--> ### New features -*None yet* + +- Toolshed, a tacit shell language, has been introduced. + - Use Robust.Shared.ToolshedManager to invoke commands, with optional input and output. + - Implement IInvocationContext for custom invocation contexts i.e. scripting systems. ### Bugfixes diff --git a/Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf b/Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b7615d8e870e3fcbd17c5300e5744ccc39b396b7 GIT binary patch literal 403040 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
  • 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%0AguIS8PbdteDmC8bYuen^wWZb9I=;W*vzBYC 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/commands.ftl b/Resources/Locale/en-US/commands.ftl index 93524d4c0..3b413408d 100644 --- a/Resources/Locale/en-US/commands.ftl +++ b/Resources/Locale/en-US/commands.ftl @@ -17,15 +17,15 @@ cmd-error-dir-not-found = Could not find directory: {$dir}. cmd-failure-no-attached-entity = There is no entity attached to this shell. ## 'help' command -cmd-help-desc = Display general help or help text for a specific command -cmd-help-help = Usage: help [command name] +cmd-oldhelp-desc = Display general help or help text for a specific command +cmd-oldhelp-help = Usage: help [command name] When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command. -cmd-help-no-args = To display help for a specific command, write 'help '. To list all available commands, write 'list'. To search for commands, use 'list '. -cmd-help-unknown = Unknown command: { $command } -cmd-help-top = { $command } - { $description } -cmd-help-invalid-args = Invalid amount of arguments. -cmd-help-arg-cmdname = [command name] +cmd-oldhelp-no-args = To display help for a specific command, write 'help '. To list all available commands, write 'list'. To search for commands, use 'list '. +cmd-oldhelp-unknown = Unknown command: { $command } +cmd-oldhelp-top = { $command } - { $description } +cmd-oldhelp-invalid-args = Invalid amount of arguments. +cmd-oldhelp-arg-cmdname = [command name] ## 'cvar' command cmd-cvar-desc = Gets or sets a CVar. diff --git a/Resources/Locale/en-US/toolshed-commands.ftl b/Resources/Locale/en-US/toolshed-commands.ftl new file mode 100644 index 000000000..673e40f72 --- /dev/null +++ b/Resources/Locale/en-US/toolshed-commands.ftl @@ -0,0 +1,163 @@ +command-description-tpto = + Teleport the given entities to some target entity. +command-description-player-list = + Returns a list of all player sessions. +command-description-player-self = + Returns the current player session. +command-description-player-imm = + Returns the session associated with the player given as argument. +command-description-player-entity = + Returns the entities of the input sessions. +command-description-self = + Returns the current attached entity. +command-description-physics-velocity = + Returns the velocity of the input entities. +command-description-physics-angular-velocity = + Returns the angular velocity of the input entities. +command-description-buildinfo = + Provides information about the build of the game. +command-description-cmd-list = + Returns a list of all commands, for this side. +command-description-explain = + Explains the given expression, providing command descriptions and signatures. +command-description-search = + Searches through the input for the provided value. +command-description-stopwatch = + Measures the execution time of the given expression. +command-description-types-consumers = + Provides all commands that can consume the given type. +command-description-types-tree = + Debug tool to return all types the command interpreter can downcast the input to. +command-description-types-gettype = + Returns the type of the input. +command-description-types-fullname = + Returns the full name of the input type according to CoreCLR. +command-description-as = + Casts the input to the given type. + Effectively a type hint if you know the type but the interpreter does not. +command-description-count = + Counts the amount of entries in it's input, returning an integer. +command-description-map = + Maps the input over the given block, with the provided expected return type. + This command may be modified to not need an explicit return type in the future. +command-description-select = + Selects N objects or N% of objects from the input. + One can additionally invert this command with not to make it select everything except N objects instead. +command-description-comp = + Returns the given component from the input entities, discarding entities without that component. +command-description-delete = + Deletes the input entities. +command-description-ent = + Returns the provided entity ID. +command-description-entities = + Returns all entities on the server. +command-description-paused = + Filters the input entities by whether or not they are paused. + This command can be inverted with not. +command-description-with = + Filters the input entities by whether or not they have the given component. + This command can be inverted with not. +command-description-fuck = + Throws an exception. +command-description-ecscomp-listty = + Lists every type of component registered. +command-description-cd = + Changes the session's current directory to the given relative or absolute path. +command-description-ls-here = + Lists the contents of the current directory. +command-description-ls-in = + Lists the contents of the given relative or absolute path. +command-description-methods-get = + Returns all methods associated with the input type. +command-description-methods-overrides = + Returns all methods overriden on the input type. +command-description-methods-overridesfrom = + Returns all methods overriden from the given type on the input type. +command-description-cmd-moo = + Asks the important questions. +command-description-cmd-descloc = + Returns the localization string for a command's description. +command-description-cmd-getshim = + Returns a command's execution shim. +command-description-help = + Provides a quick rundown of how to use toolshed. +command-description-ioc-registered = + Returns all the types registered with IoCManager on the current thread (usually the game thread) +command-description-ioc-get = + Gets an instance of an IoC registration. +command-description-loc-tryloc = + Tries to get a localization string, returning null if unable. +command-description-loc-loc = + Gets a localization string, returning the unlocalized string if unable. +command-description-physics-angular_velocity = + Returns the angular velocity of the given entities. +command-description-vars = + Provides a list of all variables set in this session. +command-description-any = + Returns true if there's any values in the input, otherwise false. +command-description-ArrowCommand = + Assigns the input to a variable. +command-description-isempty = + Returns true if the input is empty, otherwise false. +command-description-isnull = + Returns true if the input is null, otherwise false. +command-description-unique = + Filters the input sequence for uniqueness, removing duplicate values. +command-description-where = + Given some input sequence IEnumerable, takes a block of signature T -> bool that decides if each input value should be included in the output sequence. +command-description-do = + Backwards compatibility with BQL, applies the given old commands over the input sequence. +command-description-named = + Filters the input entities by their name, with the regex $selector^. +command-description-prototyped = + Filters the input entities by their prototype. +command-description-nearby = + Creates a new list of all entities nearby the inputs within the given range. +command-description-first = + Returns the first entry of the given enumerable. +command-description-splat = + "Splats" a block, value, or variable, creating N copies of it in a list. +command-description-val = + Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables. +command-description-actor-controlled = + Filters entities by whether or not they're actively controlled. +command-description-actor-session = + Returns the sessions associated with the input entities. +command-description-physics-parent = + Returns the parent(s) of the input entities. +command-description-emplace = + Runs the given block over it's inputs, with the input value placed into the variable $value within the block. + Additionally breaks out $wx, $wy, $proto, $desc, $name, and $paused for entities. + Can also have breakout values for other types, consult the documentation for that type for further info. +command-description-AddCommand = + Performs numeric addition. +command-description-SubtractCommand = + Performs numeric subtraction. +command-description-MultiplyCommand = + Performs numeric multiplication. +command-description-DivideCommand = + Performs numeric division. +command-description-min = + Returns the minimum of two values. +command-description-max = + Returns the maximum of two values. +command-description-BitAndCommand = + Performs bitwise AND. +command-description-BitOrCommand = + Performs bitwise OR. +command-description-BitXorCommand = + Performs bitwise XOR. +command-description-neg = + Negates the input. +command-description-GreaterThanCommand = + Performs a greater-than comparison, x > y. +command-description-LessThanCommand = + Performs a less-than comparison, x < y. +command-description-GreaterThanOrEqualCommand = + Performs a greater-than-or-equal comparison, x >= y. +command-description-LessThanOrEqualCommand = + Performs a less-than-or-equal comparison, x <= y. +command-description-EqualCommand = + Performs an equality comparison, returning true if the inputs are equal. +command-description-NotEqualCommand = + Performs an equality comparison, returning true if the inputs are not equal. diff --git a/Robust.Client/Console/ClientConsoleHost.Completions.cs b/Robust.Client/Console/ClientConsoleHost.Completions.cs index 90b90a146..c1ad7d095 100644 --- a/Robust.Client/Console/ClientConsoleHost.Completions.cs +++ b/Robust.Client/Console/ClientConsoleHost.Completions.cs @@ -14,7 +14,7 @@ internal sealed partial class ClientConsoleHost private int _completionSeq; - public async Task GetCompletions(List args, CancellationToken cancel) + public async Task GetCompletions(List args, string argStr, CancellationToken cancel) { // Last element is the command currently being typed. May be empty. @@ -24,10 +24,10 @@ internal sealed partial class ClientConsoleHost if (delay > 0) await Task.Delay((int)(delay * 1000), cancel); - return await CalcCompletions(args, cancel); + return await CalcCompletions(args, argStr, cancel); } - private Task CalcCompletions(List args, CancellationToken cancel) + private Task CalcCompletions(List args, string argStr, CancellationToken cancel) { if (args.Count == 1) { @@ -44,10 +44,10 @@ internal sealed partial class ClientConsoleHost if (!AvailableCommands.TryGetValue(args[0], out var cmd)) return Task.FromResult(CompletionResult.Empty); - return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask(); + return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], argStr, cancel).AsTask(); } - private Task DoServerCompletions(List args, CancellationToken cancel) + private Task DoServerCompletions(List args, string argStr, CancellationToken cancel) { var tcs = new TaskCompletionSource(); var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel); @@ -62,6 +62,7 @@ internal sealed partial class ClientConsoleHost var msg = new MsgConCompletion { Args = args.ToArray(), + ArgString = argStr, Seq = seq }; diff --git a/Robust.Client/Console/ClientConsoleHost.cs b/Robust.Client/Console/ClientConsoleHost.cs index c863dcc6d..60585000f 100644 --- a/Robust.Client/Console/ClientConsoleHost.cs +++ b/Robust.Client/Console/ClientConsoleHost.cs @@ -10,6 +10,7 @@ using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.IoC; using Robust.Shared.Log; +using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Players; @@ -21,13 +22,13 @@ namespace Robust.Client.Console { public sealed class AddStringArgs : EventArgs { - public string Text { get; } + public FormattedMessage Text { get; } public bool Local { get; } public bool Error { get; } - public AddStringArgs(string text, bool local, bool error) + public AddStringArgs(FormattedMessage text, bool local, bool error) { Text = text; Local = local; @@ -132,10 +133,17 @@ namespace Robust.Client.Console AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message)); } + public override void WriteLine(ICommonSession? session, FormattedMessage msg) + { + AddFormattedLine(msg); + } + /// public override void WriteError(ICommonSession? session, string text) { - OutputText(text, true, true); + var msg = new FormattedMessage(); + msg.AddText(text); + OutputText(msg, true, true); } public bool IsCmdServer(IConsoleCommand cmd) @@ -151,8 +159,13 @@ namespace Robust.Client.Console if (string.IsNullOrWhiteSpace(command)) return; + WriteLine(null, ""); + var msg = new FormattedMessage(); + msg.PushColor(Color.Gold); + msg.AddText("> " + command); + msg.Pop(); // echo the command locally - WriteLine(null, "> " + command); + OutputText(msg, true, false); //Commands are processed locally and then sent to the server to be processed there again. var args = new List(); @@ -205,7 +218,9 @@ namespace Robust.Client.Console /// public override void WriteLine(ICommonSession? session, string text) { - OutputText(text, true, false); + var msg = new FormattedMessage(); + msg.AddText(text); + OutputText(msg, true, false); } /// @@ -214,12 +229,12 @@ namespace Robust.Client.Console // We don't have anything to dispose. } - private void OutputText(string text, bool local, bool error) + private void OutputText(FormattedMessage text, bool local, bool error) { AddString?.Invoke(this, new AddStringArgs(text, local, error)); var level = error ? LogLevel.Warning : LogLevel.Info; - _conLogger.Log(level, text); + _conLogger.Log(level, text.ToString()); } private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs) @@ -229,7 +244,7 @@ namespace Robust.Client.Console private void HandleConCmdAck(MsgConCmdAck msg) { - OutputText("< " + msg.Text, false, msg.Error); + OutputText(msg.Text, false, msg.Error); } private void HandleConCmdReg(MsgConCmdReg msg) @@ -303,13 +318,14 @@ namespace Robust.Client.Console public async ValueTask GetCompletionAsync( IConsoleShell shell, string[] args, + string argStr, CancellationToken cancel) { var host = (ClientConsoleHost)shell.ConsoleHost; var argsList = args.ToList(); argsList.Insert(0, Command); - return await host.DoServerCompletions(argsList, cancel); + return await host.DoServerCompletions(argsList, argStr, cancel); } } @@ -327,10 +343,11 @@ namespace Robust.Client.Console public override async ValueTask GetCompletionAsync( IConsoleShell shell, string[] args, + string argStr, CancellationToken cancel) { var host = (ClientConsoleHost)shell.ConsoleHost; - return await host.DoServerCompletions(args.ToList(), cancel); + return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel); } } } diff --git a/Robust.Client/Console/IClientConsoleHost.cs b/Robust.Client/Console/IClientConsoleHost.cs index aeb62b5ed..ea09fa0be 100644 --- a/Robust.Client/Console/IClientConsoleHost.cs +++ b/Robust.Client/Console/IClientConsoleHost.cs @@ -19,6 +19,6 @@ namespace Robust.Client.Console void AddFormattedLine(FormattedMessage message); - Task GetCompletions(List args, CancellationToken cancel); + Task GetCompletions(List args, string argStr, CancellationToken cancel); } } diff --git a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml index 0053f57af..8dac92d03 100644 --- a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml +++ b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml @@ -1,7 +1,7 @@ - + 0 ? _compFiltered[_compSelected] : default; _compFiltered = FilterCompletions(_compCurResult.Options, curTyping); @@ -168,7 +168,7 @@ public sealed partial class DebugConsole DebugTools.AssertNotNull(_compCurResult); - var (_, _, endRange) = CalcTypingArgs(); + var (_, _, endRange, _) = CalcTypingArgs(); var offset = CommandBar.GetOffsetAtIndex(endRange.start); // Logger.Debug($"Offset: {offset}"); @@ -231,7 +231,7 @@ public sealed partial class DebugConsole } } - private (List args, string curTyping, (int start, int end) lastRange) CalcTypingArgs() + private (List args, string curTyping, (int start, int end) lastRange, string argStr) CalcTypingArgs() { var cursor = CommandBar.CursorPosition; // Don't consider text after the cursor. @@ -252,7 +252,7 @@ public sealed partial class DebugConsole else lastRange = (cursor, cursor); - return (args, args[^1], lastRange); + return (args, args[^1], lastRange, text.ToString()); } private CompletionOption[] FilterCompletions(IEnumerable completions, string curTyping) @@ -270,7 +270,7 @@ public sealed partial class DebugConsole { // Figure out typing word so we know how much to replace. var (completion, _, completionFlags) = _compFiltered[_compSelected]; - var (_, _, lastRange) = CalcTypingArgs(); + var (_, _, lastRange, _) = CalcTypingArgs(); // Replace the full word from the start. // This means that letter casing will match the completion suggestion. diff --git a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs index c729a9ed5..86fb7afb4 100644 --- a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs +++ b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs @@ -23,9 +23,9 @@ namespace Robust.Client.UserInterface.CustomControls /// /// Write a line with a specific color to the console window. /// - void AddLine(string text, Color color); + void AddLine(FormattedMessage text, Color color); - void AddLine(string text); + void AddLine(FormattedMessage text); void AddFormattedLine(FormattedMessage message); @@ -108,7 +108,7 @@ namespace Robust.Client.UserInterface.CustomControls private Color DetermineColor(bool local, bool error) { - return Color.White; + return error ? Color.Red : Color.White; } protected override void FrameUpdate(FrameEventArgs args) @@ -136,16 +136,16 @@ namespace Robust.Client.UserInterface.CustomControls _flushHistoryToDisk(); } - public void AddLine(string text, Color color) + public void AddLine(FormattedMessage text, Color color) { var formatted = new FormattedMessage(3); formatted.PushColor(color); - formatted.AddText(text); + formatted.AddMessage(text); formatted.Pop(); AddFormattedLine(formatted); } - public void AddLine(string text) + public void AddLine(FormattedMessage text) { AddLine(text, Color.White); } diff --git a/Robust.Client/UserInterface/Stylesheets/DefaultStylesheet.cs b/Robust.Client/UserInterface/Stylesheets/DefaultStylesheet.cs index c964fb5bb..d923726a7 100644 --- a/Robust.Client/UserInterface/Stylesheets/DefaultStylesheet.cs +++ b/Robust.Client/UserInterface/Stylesheets/DefaultStylesheet.cs @@ -15,6 +15,9 @@ public sealed class DefaultStylesheet { var notoSansFont = res.GetResource("/EngineFonts/NotoSans/NotoSans-Regular.ttf"); var notoSansFont12 = new VectorFont(notoSansFont, 12); + var notoSansMonoFont = res.GetResource("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf"); + var notoSansMono12 = new VectorFont(notoSansMonoFont, 12); + var theme = userInterfaceManager.CurrentTheme; @@ -38,6 +41,13 @@ public sealed class DefaultStylesheet Stylesheet = new Stylesheet(new StyleRule[] { + /* + * Debug console and other monospace things. + */ + + Element().Class("monospace") + .Prop("font", notoSansMono12), + /* * OS Window defaults */ diff --git a/Robust.Server/BaseServer.cs b/Robust.Server/BaseServer.cs index 5c237decf..070945569 100644 --- a/Robust.Server/BaseServer.cs +++ b/Robust.Server/BaseServer.cs @@ -32,6 +32,7 @@ using Robust.Shared.Profiling; using Robust.Shared.Prototypes; using Robust.Shared.Reflection; using Robust.Shared.Replays; +using Robust.Shared.Toolshed; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Threading; @@ -371,6 +372,7 @@ namespace Robust.Server _prototype.ResolveResults(); _refMan.Initialize(); + IoCManager.Resolve().Initialize(); _consoleHost.Initialize(); _entityManager.Startup(); _mapManager.Startup(); diff --git a/Robust.Server/Bql/BqlQueryManager.Parsers.cs b/Robust.Server/Bql/BqlQueryManager.Parsers.cs deleted file mode 100644 index ae2e0916a..000000000 --- a/Robust.Server/Bql/BqlQueryManager.Parsers.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Pidgin; -using Robust.Shared.GameObjects; -using static Pidgin.Parser; -using static Pidgin.Parser; - -namespace Robust.Server.Bql -{ - public partial class BqlQueryManager - { - private readonly Dictionary> _parsers = new(); - private Parser _allQuerySelectors = default!; - private Parser, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString()); - - private static Parser Word => OneOf(LetterOrDigit, Char('_')).ManyString(); - - private static Parser Objectify(Parser inp) - { - return Parser.Map(x => (object) x!, inp); - } - - private struct SubstitutionData - { - public string Name; - - public SubstitutionData(string name) - { - Name = name; - } - } - - private static Parser Substitution => - Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString())) - .MapWithInput((x, _) => new SubstitutionData(x.ToString())); - - private static Parser Integer => - Try(Int(10)); - - private static Parser SubstitutableInteger => - Objectify(Integer).Or(Objectify(Try(Substitution))); - - private static Parser Float => - Try(Real); - - private static Parser SubstitutableFloat => - Objectify(Float).Or(Objectify(Try(Substitution))); - - private static Parser Percentage => - Try(Real).Before(Char('%')); - - private static Parser SubstitutablePercentage => - Objectify(Percentage).Or(Objectify(Try(Substitution))); - - private static Parser EntityId => - Try(Parser.Map(x => new EntityUid(x), Int(10))); - - private static Parser SubstitutableEntityId => - Objectify(EntityId).Or(Objectify(Try(Substitution))); - - private Parser Component => - Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word)); - - private Parser SubstitutableComponent => - Objectify(Component).Or(Objectify(Try(Substitution))); - - private static Parser QuotedString => - OneOf(Try(Char('"').Then(OneOf(new [] - { - AnyCharExcept("\"") - }).ManyString().Before(Char('"')))), Try(Word)); - - private static Parser SubstitutableString => - Objectify(QuotedString).Or(Objectify(Try(Substitution))); - - // thing to make sure it all compiles. - [UsedImplicitly] - private Parser TypeSystemCheck => - OneOf(new[] - { - Objectify(Integer), - Objectify(Percentage), - Objectify(EntityId), - Objectify(Component), - Objectify(Float), - Objectify(QuotedString) - }); - - private Parser BuildBqlQueryParser(BqlQuerySelector inst) - { - if (inst.Arguments.Length == 0) - { - return Parser.Map(_ => new BqlQuerySelectorParsed(new List(), inst.Token, false), SkipWhitespaces); - } - - List> argsParsers = new(); - - foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i))) - { - List> choices = new(); - if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String) - { - choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument"))); - } - if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component) - { - choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument"))); - } - if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId) - { - choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument"))); - } - if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer) - { - choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument"))); - } - if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage) - { - choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument"))); - } - if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float) - { - choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument"))); - } - - argsParsers.Add(OneOf(choices)); - } - - Parser> finalParser = argsParsers[0].Map(x => new List { x }); - - for (var i = 1; i < argsParsers.Count; i++) - { - finalParser = finalParser.Then(argsParsers[i], (list, o) => - { - list.Add(o); - return list; - }).Labelled("arguments"); - } - - return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser); - } - - private void DoParserSetup() - { - foreach (var inst in _instances) - { - _parsers.Add(inst.GetType(), BuildBqlQueryParser(inst)); - } - - _allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x => - Try(String(x.Token).Before(Char(' '))))).Then(tok => - _parsers[_queriesByToken[tok].GetType()]) - ).Map(pair => - { - pair.b.Inverted = pair.a.HasValue; - return pair.b; - }); - } - } -} diff --git a/Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs b/Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs deleted file mode 100644 index e7f069320..000000000 --- a/Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Pidgin; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Robust.Server.Bql -{ - public partial class BqlQueryManager - { - public (IEnumerable, string) SimpleParseAndExecute(string query) - { - var parsed = SimpleQuery.Parse(query); - if (parsed.Success) - { - var selectors = parsed.Value.Item1.ToArray(); - if (selectors.Length == 0) - { - return (_entityManager.GetEntities(), parsed.Value.Item2); - } - - var entities = _queriesByToken[selectors[0].Token] - .DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, _entityManager); - - foreach (var sel in selectors[1..]) - { - entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, _entityManager); - } - - return (entities, parsed.Value.Item2); - } - else - { - throw new Exception(parsed.Error!.RenderErrorMessage()); - } - } - } -} diff --git a/Robust.Server/Bql/BqlQueryManager.cs b/Robust.Server/Bql/BqlQueryManager.cs deleted file mode 100644 index cc2b79b74..000000000 --- a/Robust.Server/Bql/BqlQueryManager.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Reflection; -using Robust.Shared.Utility; - -namespace Robust.Server.Bql -{ - public sealed partial class BqlQueryManager : IBqlQueryManager - { - [Dependency] private readonly IReflectionManager _reflectionManager = default!; - [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - - private readonly List _instances = new(); - private readonly Dictionary _queriesByToken = new(); - - /// - /// Automatically registers all query selectors with the parser/executor. - /// - public void DoAutoRegistrations() - { - foreach (var type in _reflectionManager.FindTypesWithAttribute()) - { - RegisterClass(type); - } - - DoParserSetup(); - } - - /// - /// Internally registers the given . - /// - /// The selector to register - private void RegisterClass(Type bqlQuerySelector) - { - DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector)); - var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!; - _instances.Add(inst); - _queriesByToken.Add(inst.Token, inst); - } - } -} diff --git a/Robust.Server/Bql/BqlQuerySelector.Builtin.cs b/Robust.Server/Bql/BqlQuerySelector.Builtin.cs deleted file mode 100644 index 1f701ad35..000000000 --- a/Robust.Server/Bql/BqlQuerySelector.Builtin.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; - -namespace Robust.Server.Bql -{ - [RegisterBqlQuerySelector] - public sealed class WithQuerySelector : BqlQuerySelector - { - public override string Token => "with"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var comp = (Type) arguments[0]; - return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted); - } - - public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - if (isInverted) - { - return base.DoInitialSelection(arguments, isInverted, entityManager); - } - - return entityManager.GetAllComponents((Type) arguments[0], includePaused: true) - .Select(x => x.Uid); - } - } - - [RegisterBqlQuerySelector] - public sealed class NamedQuerySelector : BqlQuerySelector - { - public override string Token => "named"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var r = new Regex("^" + (string) arguments[0] + "$"); - return input.Where(e => - { - if (entityManager.TryGetComponent(e, out var metaDataComponent)) - return r.IsMatch(metaDataComponent.EntityName) ^ isInverted; - return isInverted; - }); - } - } - - [RegisterBqlQuerySelector] - public sealed class ParentedToQuerySelector : BqlQuerySelector - { - public override string Token => "parentedto"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var uid = (EntityUid) arguments[0]; - return input.Where(e => (entityManager.TryGetComponent(e, out var transform) && - transform.ParentUid == uid) ^ isInverted); - } - } - - [RegisterBqlQuerySelector] - public sealed class RecursiveParentedToQuerySelector : BqlQuerySelector - { - public override string Token => "rparentedto"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var uid = (EntityUid) arguments[0]; - return input.Where(e => - { - if (!entityManager.TryGetComponent(e, out var transform)) - return isInverted; - while (transform.ParentUid != EntityUid.Invalid) - { - if ((transform.ParentUid == uid) ^ isInverted) - return true; - if (!entityManager.TryGetComponent(transform.ParentUid, out transform)) - return false; - } - - return false; - }); - } - } - - [RegisterBqlQuerySelector] - public sealed class ChildrenQuerySelector : BqlQuerySelector - { - public override string Token => "children"; - - public override QuerySelectorArgument[] Arguments => Array.Empty(); - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - foreach (var uid in input) - { - if (!entityManager.TryGetComponent(uid, out TransformComponent? xform)) continue; - - foreach (var child in xform.ChildEntities) - { - yield return child; - } - } - } - } - - [RegisterBqlQuerySelector] - public sealed class RecursiveChildrenQuerySelector : BqlQuerySelector - { - public override string Token => "rchildren"; - - public override QuerySelectorArgument[] Arguments => Array.Empty(); - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, - bool isInverted, IEntityManager entityManager) - { - IEnumerable toSearch = input; - - while (true) - { - // TODO: Reduce LINQ chaining - var doing = toSearch.Where(entityManager.HasComponent).Select(entityManager.GetComponent).ToArray(); - var search = doing.SelectMany(x => x.ChildEntities); - if (!search.Any()) - break; - toSearch = doing.SelectMany(x => x.ChildEntities).Where(x => x != EntityUid.Invalid); - - foreach (var xform in doing) - { - foreach (var uid in xform.ChildEntities) - { - // This should never happen anyway - if (uid == EntityUid.Invalid) continue; - yield return uid; - } - } - } - } - } - - [RegisterBqlQuerySelector] - public sealed class ParentQuerySelector : BqlQuerySelector - { - public override string Token => "parent"; - - public override QuerySelectorArgument[] Arguments => Array.Empty(); - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - return input.Where(entityManager.HasComponent) - .Distinct(); - } - - public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - return DoSelection( - Query(entityManager.AllEntityQueryEnumerator()), - arguments, - isInverted, entityManager); - - IEnumerable Query(AllEntityQueryEnumerator enumerator) - { - while (enumerator.MoveNext(out var entityUid, out _)) - { - yield return entityUid; - } - } - } - } - - [RegisterBqlQuerySelector] - public sealed class AboveQuerySelector : BqlQuerySelector - { - public override string Token => "above"; - - public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var tileDefinitionManager = IoCManager.Resolve(); - var tileTy = tileDefinitionManager[(string) arguments[0]]; - - var map = IoCManager.Resolve(); - if (tileTy.TileId == 0) - { - return input.Where(e => entityManager.TryGetComponent(e, out var transform) && (transform.GridUid is null) ^ isInverted); - } - else - { - return input.Where(e => - { - if (!entityManager.TryGetComponent(e, out var transform)) return isInverted; - - if (!map.TryGetGrid(transform.GridUid, out var grid)) - return isInverted; - - return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted; - - }); - } - } - } - - [RegisterBqlQuerySelector] - // ReSharper disable once InconsistentNaming the name is correct shut up - public sealed class OnGridQuerySelector : BqlQuerySelector - { - public override string Token => "ongrid"; - - public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - // TODO: Probably easier and significantly faster to just iterate the grid's children. - var grid = new EntityUid((int) arguments[0]); - return input.Where(e => (entityManager.TryGetComponent(e, out var transform) && transform.GridUid == grid) ^ isInverted); - } - } - - [RegisterBqlQuerySelector] - // ReSharper disable once InconsistentNaming the name is correct shut up - public sealed class OnMapQuerySelector : BqlQuerySelector - { - public override string Token => "onmap"; - - public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - // TODO: Just use EntityLookup GetEntitiesInMap - var map = new MapId((int) arguments[0]); - return input.Where(e => (entityManager.TryGetComponent(e, out var transform) && transform.MapID == map) ^ isInverted); - } - } - - [RegisterBqlQuerySelector] - public sealed class PrototypedQuerySelector : BqlQuerySelector - { - public override string Token => "prototyped"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var name = (string) arguments[0]; - return input.Where(e => (entityManager.TryGetComponent(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted); - } - } - - [RegisterBqlQuerySelector] - public sealed class RecursivePrototypedQuerySelector : BqlQuerySelector - { - public override string Token => "rprototyped"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var name = (string) arguments[0]; - return input.Where(e => - { - if (!entityManager.TryGetComponent(e, out var metaData)) - return isInverted; - if ((metaData.EntityPrototype?.ID == name) ^ isInverted) - return true; - - var prototypeManager = IoCManager.Resolve(); - - return metaData.EntityPrototype != null && prototypeManager.EnumerateParents(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted; - }); - } - } - - [RegisterBqlQuerySelector] - public sealed class SelectQuerySelector : BqlQuerySelector - { - public override string Token => "select"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - if (arguments[0] is int) - { - var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray(); - var taken = (int) arguments[0]; - - if (isInverted) - taken = Math.Max(0, inp.Length - taken); - - return inp.Take(taken); - } - - var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray(); - var amount = isInverted - ? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1)) - : (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1)); - return enumerable.Take(amount); - } - } - - [RegisterBqlQuerySelector] - public sealed class NearQuerySelector : BqlQuerySelector - { - public override string Token => "near"; - - public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float }; - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - var radius = (float)(double)arguments[0]; - var entityLookup = entityManager.System(); - var xformQuery = entityManager.GetEntityQuery(); - var distinct = new HashSet(); - - foreach (var uid in input) - { - foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates, - radius)) - { - if (!distinct.Add(near)) continue; - yield return near; - } - } - - //BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular. - } - } - - [RegisterBqlQuerySelector] - // ReSharper disable once InconsistentNaming the name is correct shut up - public sealed class AnchoredQuerySelector : BqlQuerySelector - { - public override string Token => "anchored"; - - public override QuerySelectorArgument[] Arguments => Array.Empty(); - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - return input.Where(e => (entityManager.TryGetComponent(e, out var transform) && transform.Anchored) ^ isInverted); - } - } - - [RegisterBqlQuerySelector] - public sealed class PausedQuerySelector : BqlQuerySelector - { - public override string Token => "paused"; - - public override QuerySelectorArgument[] Arguments => Array.Empty(); - - public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - return input.Where(e => entityManager.GetComponent(e).EntityPaused ^ isInverted); - } - } -} diff --git a/Robust.Server/Bql/BqlQuerySelector.cs b/Robust.Server/Bql/BqlQuerySelector.cs deleted file mode 100644 index 98b6809c0..000000000 --- a/Robust.Server/Bql/BqlQuerySelector.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; - -namespace Robust.Server.Bql -{ - [Flags] - [PublicAPI] - public enum QuerySelectorArgument - { - Integer = 0b00000001, - Float = 0b00000010, - String = 0b00000100, - Percentage = 0b00001000, - Component = 0b00010000, - //SubQuery = 0b00100000, - EntityId = 0b01000000, - } - - [PublicAPI] - public abstract class BqlQuerySelector - { - /// - /// The token name for the given QuerySelector, for example `when`. - /// - public virtual string Token => throw new NotImplementedException(); - - /// - /// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot". - /// - public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException(); - - /// - /// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the - /// input on some criteria like what entities are nearby. - /// - /// Input entity list. - /// Parsed selector arguments. - /// Whether the query is inverted. - /// The entity manager. - /// New list of entities - /// someone is a moron if this happens. - public abstract IEnumerable DoSelection(IEnumerable input, - IReadOnlyList arguments, bool isInverted, IEntityManager entityManager); - - /// - /// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient - /// than just querying every entity. - /// - /// - /// - /// - /// - public virtual IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) - { - return DoSelection(entityManager.GetEntities(), arguments, isInverted, entityManager); - } - - [UsedImplicitly] - protected BqlQuerySelector() {} - } -} diff --git a/Robust.Server/Bql/BqlQuerySelectorParsed.cs b/Robust.Server/Bql/BqlQuerySelectorParsed.cs deleted file mode 100644 index 067d63928..000000000 --- a/Robust.Server/Bql/BqlQuerySelectorParsed.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Robust.Server.Bql -{ - public struct BqlQuerySelectorParsed - { - public List Arguments; - public string Token; - public bool Inverted; - - public BqlQuerySelectorParsed(List arguments, string token, bool inverted) - { - Arguments = arguments; - Token = token; - Inverted = inverted; - } - } -} diff --git a/Robust.Server/Bql/ForAllCommand.cs b/Robust.Server/Bql/ForAllCommand.cs deleted file mode 100644 index cd40db9de..000000000 --- a/Robust.Server/Bql/ForAllCommand.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Globalization; -using System.Linq; -using Robust.Server.Player; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Robust.Server.Bql -{ - public sealed class ForAllCommand : LocalizedCommands - { - [Dependency] private readonly IBqlQueryManager _bql = default!; - [Dependency] private readonly IEntityManager _entities = default!; - - public override string Command => "forall"; - - public override void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (args.Length < 2) - { - shell.WriteLine(Help); - return; - } - - var transformSystem = _entities.System(); - - var (entities, rest) = _bql.SimpleParseAndExecute(argStr[6..]); - - foreach (var ent in entities.ToList()) - { - var cmds = SubstituteEntityDetails(_entities, transformSystem, shell, ent, rest).Split(";"); - foreach (var cmd in cmds) - { - shell.ExecuteCommand(cmd); - } - } - } - - // This will be refactored out soon. - private static string SubstituteEntityDetails( - IEntityManager entMan, - SharedTransformSystem transformSystem, - IConsoleShell shell, - EntityUid ent, - string ruleString) - { - var transform = entMan.GetComponent(ent); - var metadata = entMan.GetComponent(ent); - - var worldPos = transformSystem.GetWorldPosition(transform); - var localPos = transform.LocalPosition; - - // gross, is there a better way to do this? - ruleString = ruleString.Replace("$ID", ent.ToString()); - ruleString = ruleString.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$NAME", metadata.EntityName); - - if (shell.Player is { AttachedEntity: { } pEntity}) - { - var pTransform = entMan.GetComponent(pEntity); - - var pWorldPos = transformSystem.GetWorldPosition(pTransform); - var pLocalPos = pTransform.LocalPosition; - - ruleString = ruleString.Replace("$PID", pEntity.ToString()); - ruleString = ruleString.Replace("$PWX", pWorldPos.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PWY", pWorldPos.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PLX", pLocalPos.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PLY", pLocalPos.Y.ToString(CultureInfo.InvariantCulture)); - } - - return ruleString; - } - } -} diff --git a/Robust.Server/Bql/IBqlQueryManager.cs b/Robust.Server/Bql/IBqlQueryManager.cs deleted file mode 100644 index 0de4c812c..000000000 --- a/Robust.Server/Bql/IBqlQueryManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using Robust.Shared.GameObjects; - -namespace Robust.Server.Bql -{ - public interface IBqlQueryManager - { - public (IEnumerable, string) SimpleParseAndExecute(string query); - void DoAutoRegistrations(); - } -} diff --git a/Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs b/Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs deleted file mode 100644 index 5c6fd068a..000000000 --- a/Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Robust.Server.Bql -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [BaseTypeRequired(typeof(BqlQuerySelector))] - [MeansImplicitUse] - [PublicAPI] - public sealed class RegisterBqlQuerySelectorAttribute : Attribute - { - - } -} diff --git a/Robust.Server/Console/ConGroupController.cs b/Robust.Server/Console/ConGroupController.cs index fbf4f064a..26ba7a970 100644 --- a/Robust.Server/Console/ConGroupController.cs +++ b/Robust.Server/Console/ConGroupController.cs @@ -1,8 +1,12 @@ using Robust.Server.Player; +using Robust.Shared.Map; +using Robust.Shared.Players; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; namespace Robust.Server.Console { - internal sealed class ConGroupController : IConGroupController + internal sealed class ConGroupController : IConGroupController, IPermissionController { public IConGroupControllerImplementation? Implementation { get; set; } @@ -30,5 +34,11 @@ namespace Robust.Server.Console { return Implementation?.CanAdminReloadPrototypes(session) ?? false; } + + public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error) + { + error = null; + return Implementation?.CheckInvokable(command, user, out error) ?? false; + } } } diff --git a/Robust.Server/Console/IConGroupControllerImplementation.cs b/Robust.Server/Console/IConGroupControllerImplementation.cs index 96e9546d5..548f24dff 100644 --- a/Robust.Server/Console/IConGroupControllerImplementation.cs +++ b/Robust.Server/Console/IConGroupControllerImplementation.cs @@ -1,8 +1,9 @@ using Robust.Server.Player; +using Robust.Shared.Toolshed; namespace Robust.Server.Console { - public interface IConGroupControllerImplementation + public interface IConGroupControllerImplementation : IPermissionController { bool CanCommand(IPlayerSession session, string cmdName); bool CanAdminPlace(IPlayerSession session); diff --git a/Robust.Server/Console/ServerConsoleHost.cs b/Robust.Server/Console/ServerConsoleHost.cs index 10ebc3763..8d6d15b62 100644 --- a/Robust.Server/Console/ServerConsoleHost.cs +++ b/Robust.Server/Console/ServerConsoleHost.cs @@ -9,6 +9,8 @@ using Robust.Shared.Log; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Players; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Syntax; using Robust.Shared.Utility; namespace Robust.Server.Console @@ -19,6 +21,7 @@ namespace Robust.Server.Console [Dependency] private readonly IConGroupController _groupController = default!; [Dependency] private readonly IPlayerManager _players = default!; [Dependency] private readonly ISystemConsoleManager _systemConsole = default!; + [Dependency] private readonly ToolshedManager _toolshed = default!; public ServerConsoleHost() : base(isServer: true) {} @@ -45,19 +48,31 @@ namespace Robust.Server.Console /// public override void WriteLine(ICommonSession? session, string text) { + var msg = new FormattedMessage(); + msg.AddText(text); if (session is IPlayerSession playerSession) - OutputText(playerSession, text, false); + OutputText(playerSession, msg, false); else - OutputText(null, text, false); + OutputText(null, msg, false); + } + + public override void WriteLine(ICommonSession? session, FormattedMessage msg) + { + if (session is IPlayerSession playerSession) + OutputText(playerSession, msg, false); + else + OutputText(null, msg, false); } /// public override void WriteError(ICommonSession? session, string text) { + var msg = new FormattedMessage(); + msg.AddText(text); if (session is IPlayerSession playerSession) - OutputText(playerSession, text, true); + OutputText(playerSession, msg, true); else - OutputText(null, text, true); + OutputText(null, msg, true); } public bool IsCmdServer(IConsoleCommand cmd) => true; @@ -70,13 +85,13 @@ namespace Robust.Server.Console var localShell = shell.ConsoleHost.LocalShell; var sudoShell = new SudoShell(this, localShell, shell); ExecuteInShell(sudoShell, argStr["sudo ".Length..]); - }, (shell, args) => + }, (shell, args, argStr) => { var localShell = shell.ConsoleHost.LocalShell; var sudoShell = new SudoShell(this, localShell, shell); #pragma warning disable CA2012 - return CalcCompletions(sudoShell, args); + return CalcCompletions(sudoShell, args, argStr); #pragma warning restore CA2012 }); @@ -117,6 +132,18 @@ namespace Robust.Server.Console AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs); conCmd.Execute(shell, command, cmdArgs); } + else + { + // toolshed time + _toolshed.InvokeCommand(shell, command, null, out var res, out var ctx); + + foreach (var err in ctx.GetErrors()) + { + ctx.WriteLine(err.Describe()); + } + + shell.WriteLine(_toolshed.PrettyPrintType(res)); + } } catch (Exception e) { @@ -162,7 +189,7 @@ namespace Robust.Server.Console ExecuteCommand(session, text); } - private void OutputText(IPlayerSession? session, string text, bool error) + private void OutputText(IPlayerSession? session, FormattedMessage text, bool error) { if (session != null) { @@ -183,10 +210,25 @@ namespace Robust.Server.Console private async void HandleConCompletions(MsgConCompletion message) { var session = _players.GetSessionByChannel(message.MsgChannel); + var shell = new ConsoleShell(this, session, false); - var result = await CalcCompletions(shell, message.Args); + var result = await CalcCompletions(shell, message.Args, message.ArgString); + if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1) + { + var parser = new ForwardParser(message.ArgString, _toolshed); + CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _); + if (completions == null) + { + goto done; + } + var (shedRes, _) = await completions.Value; + shedRes ??= CompletionResult.Empty; + result = new CompletionResult(shedRes.Options.Concat(result.Options).ToArray(), shedRes.Hint ?? result.Hint); + } + + done: var msg = new MsgConCompletionResp { Result = result, @@ -199,7 +241,7 @@ namespace Robust.Server.Console NetManager.ServerSendMessage(msg, message.MsgChannel); } - private ValueTask CalcCompletions(IConsoleShell shell, string[] args) + private ValueTask CalcCompletions(IConsoleShell shell, string[] args, string argStr) { // Logger.Debug(string.Join(", ", args)); @@ -217,7 +259,7 @@ namespace Robust.Server.Console if (!ShellCanExecute(shell, cmdName)) return ValueTask.FromResult(CompletionResult.Empty); - return cmd.GetCompletionAsync(shell, args[1..], default); + return cmd.GetCompletionAsync(shell, args[1..], argStr, default); } private sealed class SudoShell : IConsoleShell @@ -254,6 +296,12 @@ namespace Robust.Server.Console _sudoer.WriteLine(text); } + public void WriteLine(FormattedMessage message) + { + _owner.WriteLine(message); + _sudoer.WriteLine(message); + } + public void WriteError(string text) { _owner.WriteError(text); diff --git a/Robust.Server/ServerIoC.cs b/Robust.Server/ServerIoC.cs index 7517d3e4f..8d7b3225f 100644 --- a/Robust.Server/ServerIoC.cs +++ b/Robust.Server/ServerIoC.cs @@ -1,4 +1,3 @@ -using Robust.Server.Bql; using Robust.Server.Configuration; using Robust.Server.Console; using Robust.Server.DataMetrics; @@ -82,7 +81,6 @@ namespace Robust.Server deps.Register(); deps.Register(); deps.Register(); - deps.Register(); deps.Register(); deps.Register(); deps.Register(); diff --git a/Robust.Server/Toolshed/Commands/Players/ActorCommand.cs b/Robust.Server/Toolshed/Commands/Players/ActorCommand.cs new file mode 100644 index 000000000..abadccded --- /dev/null +++ b/Robust.Server/Toolshed/Commands/Players/ActorCommand.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Toolshed; + +namespace Robust.Server.Toolshed.Commands.Players; + +[ToolshedCommand] +public sealed class ActorCommand : ToolshedCommand +{ + [CommandImplementation("controlled")] + public IEnumerable Controlled([PipedArgument] IEnumerable input) + { + return input.Where(HasComp); + } + + [CommandImplementation("session")] + public IEnumerable Session([PipedArgument] IEnumerable input) + { + return input.Where(HasComp).Select(x => Comp(x).PlayerSession); + } +} diff --git a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs new file mode 100644 index 000000000..a7bcbd3fc --- /dev/null +++ b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Robust.Server.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Network; +using Robust.Shared.Players; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Server.Toolshed.Commands.Players; + +[ToolshedCommand] +public sealed class PlayerCommand : ToolshedCommand +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + + [CommandImplementation("list")] + public IEnumerable Players() + => _playerManager.ServerSessions; + + [CommandImplementation("self")] + public IPlayerSession Self([CommandInvocationContext] IInvocationContext ctx) + { + if (ctx.Session is null) + { + ctx.ReportError(new NotForServerConsoleError()); + } + + return (IPlayerSession)ctx.Session!; + } + + [CommandImplementation("imm")] + public ICommonSession Immediate( + [CommandInvocationContext] IInvocationContext ctx, + [CommandArgument] string username + ) + { + _playerManager.TryGetSessionByUsername(username, out var session); + + if (session is null) + { + if (Guid.TryParse(username, out var guid)) + { + _playerManager.TryGetSessionById(new NetUserId(guid), out session); + } + } + + if (session is null) + { + ctx.ReportError(new NoSuchPlayerError(username)); + } + + return session!; + } + + [CommandImplementation("entity")] + public IEnumerable GetPlayerEntity([PipedArgument] IEnumerable sessions) + { + return sessions.Select(x => x.AttachedEntity).Where(x => x is not null).Cast(); + } + + [CommandImplementation("entity")] + public EntityUid GetPlayerEntity([PipedArgument] IPlayerSession sessions) + { + return sessions.AttachedEntity ?? default; + } +} + +public record struct NoSuchPlayerError(string Username) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared.Scripting/ILCommand.cs b/Robust.Shared.Scripting/ILCommand.cs new file mode 100644 index 000000000..36e9eb70e --- /dev/null +++ b/Robust.Shared.Scripting/ILCommand.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Reflection.Emit; +using ILReader; +using ILReader.Readers; +using Robust.Shared.Toolshed; + +namespace Robust.Shared.Scripting; + +[ToolshedCommand] +public sealed class ILCommand : ToolshedCommand +{ + [CommandImplementation("dumpil")] + public void DumpIL( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] MethodInfo info + ) + { + var reader = GetReader(info); + + foreach (var instruction in reader) + { + if (instruction is null) + break; + + ctx.WriteLine(instruction.ToString()!); + } + } + + private IILReader GetReader(MethodBase method) + { + IILReaderConfiguration cfg = ILReader.Configuration.Resolve(method); + return cfg.GetReader(method); + } +} + diff --git a/Robust.Shared.Scripting/Robust.Shared.Scripting.csproj b/Robust.Shared.Scripting/Robust.Shared.Scripting.csproj index 55f53999c..59c108282 100644 --- a/Robust.Shared.Scripting/Robust.Shared.Scripting.csproj +++ b/Robust.Shared.Scripting/Robust.Shared.Scripting.csproj @@ -7,6 +7,7 @@ + diff --git a/Robust.Shared.Scripting/ScriptGlobalsShared.cs b/Robust.Shared.Scripting/ScriptGlobalsShared.cs index fe8e350f6..50161f857 100644 --- a/Robust.Shared.Scripting/ScriptGlobalsShared.cs +++ b/Robust.Shared.Scripting/ScriptGlobalsShared.cs @@ -10,7 +10,10 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Map.Components; +using Robust.Shared.Players; using Robust.Shared.Prototypes; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; using Robust.Shared.Utility; namespace Robust.Shared.Scripting @@ -19,7 +22,7 @@ namespace Robust.Shared.Scripting [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "CA1822")] - public abstract class ScriptGlobalsShared + public abstract class ScriptGlobalsShared : IInvocationContext { private const BindingFlags DefaultHelpFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public; @@ -30,6 +33,8 @@ namespace Robust.Shared.Scripting [field: Dependency] public IMapManager map { get; } = default!; [field: Dependency] public IDependencyCollection dependencies { get; } = default!; + [field: Dependency] public ToolshedManager shed { get; } = default!; + protected ScriptGlobalsShared(IDependencyCollection dependencies) { dependencies.InjectDependencies(this); @@ -161,6 +166,24 @@ namespace Robust.Shared.Scripting public abstract void write(object toString); public abstract void show(object obj); + public object? tsh(string toolshedCommand) + { + shed.InvokeCommand(this, toolshedCommand, null, out var res); + return res; + } + + public T tsh(string toolshedCommand) + { + shed.InvokeCommand(this, toolshedCommand, null, out var res); + return (T)res!; + } + + public TOut tsh(TIn value, string toolshedCommand) + { + shed.InvokeCommand(this, toolshedCommand, value, out var res); + return (TOut)res!; + } + #region EntityManager proxy methods public T Comp(EntityUid uid) where T : Component => ent.GetComponent(uid); @@ -227,5 +250,34 @@ namespace Robust.Shared.Scripting return ent.EntityQuery(includePaused); } #endregion + + public bool CheckInvokable(CommandSpec command, out IConError? error) + { + error = null; + return true; // Do as I say! + } + + public ICommonSession? Session => null; + + public void WriteLine(string line) + { + write(line); + } + + public void ReportError(IConError err) + { + write(err); + } + + public IEnumerable GetErrors() + { + return Array.Empty(); + } + + public void ClearErrors() + { + } + + public Dictionary Variables { get; } = new(); } } diff --git a/Robust.Shared/Console/Commands/HelpCommand.cs b/Robust.Shared/Console/Commands/HelpCommand.cs index de03cfd5a..8b7b9cd49 100644 --- a/Robust.Shared/Console/Commands/HelpCommand.cs +++ b/Robust.Shared/Console/Commands/HelpCommand.cs @@ -5,31 +5,31 @@ namespace Robust.Shared.Console.Commands; internal sealed class HelpCommand : LocalizedCommands { - public override string Command => "help"; + public override string Command => "oldhelp"; public override void Execute(IConsoleShell shell, string argStr, string[] args) { switch (args.Length) { case 0: - shell.WriteLine(Loc.GetString("cmd-help-no-args")); + shell.WriteLine(Loc.GetString("cmd-oldhelp-no-args")); break; case 1: var commandName = args[0]; if (!shell.ConsoleHost.AvailableCommands.TryGetValue(commandName, out var cmd)) { - shell.WriteError(Loc.GetString("cmd-help-unknown", ("command", commandName))); + shell.WriteError(Loc.GetString("cmd-oldhelp-unknown", ("command", commandName))); return; } - shell.WriteLine(Loc.GetString("cmd-help-top", ("command", cmd.Command), + shell.WriteLine(Loc.GetString("cmd-oldhelp-top", ("command", cmd.Command), ("description", cmd.Description))); shell.WriteLine(cmd.Help); break; default: - shell.WriteError(Loc.GetString("cmd-help-invalid-args")); + shell.WriteError(Loc.GetString("cmd-oldhelp-invalid-args")); break; } } @@ -41,7 +41,7 @@ internal sealed class HelpCommand : LocalizedCommands var host = shell.ConsoleHost; return CompletionResult.FromHintOptions( host.AvailableCommands.Values.OrderBy(c => c.Command).Select(c => new CompletionOption(c.Command, c.Description)).ToArray(), - Loc.GetString("cmd-help-arg-cmdname")); + Loc.GetString("cmd-oldhelp-arg-cmdname")); } return CompletionResult.Empty; diff --git a/Robust.Shared/Console/ConsoleHost.cs b/Robust.Shared/Console/ConsoleHost.cs index e54480eb5..82804610e 100644 --- a/Robust.Shared/Console/ConsoleHost.cs +++ b/Robust.Shared/Console/ConsoleHost.cs @@ -11,6 +11,7 @@ using Robust.Shared.Network; using Robust.Shared.Players; using Robust.Shared.Reflection; using Robust.Shared.Timing; +using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.Console @@ -173,6 +174,8 @@ namespace Robust.Shared.Console //TODO: IConsoleOutput for [e#1225] public abstract void WriteLine(ICommonSession? session, string text); + public abstract void WriteLine(ICommonSession? session, FormattedMessage msg); + public abstract void WriteError(ICommonSession? session, string text); /// @@ -324,10 +327,11 @@ namespace Robust.Shared.Console public ValueTask GetCompletionAsync( IConsoleShell shell, string[] args, + string argStr, CancellationToken cancel) { if (CompletionCallbackAsync != null) - return CompletionCallbackAsync(shell, args); + return CompletionCallbackAsync(shell, args, argStr); if (CompletionCallback != null) return ValueTask.FromResult(CompletionCallback(shell, args)); diff --git a/Robust.Shared/Console/ConsoleShell.cs b/Robust.Shared/Console/ConsoleShell.cs index 5b750df6a..6777fb818 100644 --- a/Robust.Shared/Console/ConsoleShell.cs +++ b/Robust.Shared/Console/ConsoleShell.cs @@ -1,4 +1,5 @@ using Robust.Shared.Players; +using Robust.Shared.Utility; namespace Robust.Shared.Console { @@ -48,6 +49,11 @@ namespace Robust.Shared.Console ConsoleHost.WriteLine(Player, text); } + public void WriteLine(FormattedMessage message) + { + ConsoleHost.WriteLine(Player, message); + } + /// public void WriteError(string text) { diff --git a/Robust.Shared/Console/IConsoleCommand.cs b/Robust.Shared/Console/IConsoleCommand.cs index 50a765074..87e1f8541 100644 --- a/Robust.Shared/Console/IConsoleCommand.cs +++ b/Robust.Shared/Console/IConsoleCommand.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -8,7 +9,7 @@ namespace Robust.Shared.Console /// Basic interface to handle console commands. Any class implementing this will be /// registered with the console system through reflection. /// - [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] + [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors), Obsolete("New commands should utilize RtShellCommand.")] public interface IConsoleCommand { /// @@ -78,7 +79,7 @@ namespace Robust.Shared.Console /// /// If this method is implemented, will not be automatically called. /// - ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel) + ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, string argStr, CancellationToken cancel) { return ValueTask.FromResult(GetCompletion(shell, args)); } diff --git a/Robust.Shared/Console/IConsoleHost.cs b/Robust.Shared/Console/IConsoleHost.cs index 00c495a96..2907b36e3 100644 --- a/Robust.Shared/Console/IConsoleHost.cs +++ b/Robust.Shared/Console/IConsoleHost.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Robust.Shared.Players; +using Robust.Shared.Utility; namespace Robust.Shared.Console { @@ -21,7 +22,7 @@ namespace Robust.Shared.Console /// /// Called to fetch completions for a console command (async). See for details. /// - public delegate ValueTask ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args); + public delegate ValueTask ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args, string argStr); public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args); @@ -249,6 +250,8 @@ namespace Robust.Shared.Console /// Text message to send. void WriteLine(ICommonSession? session, string text); + void WriteLine(ICommonSession? session, FormattedMessage msg); + /// /// Sends a foreground colored text string to the remote session. /// diff --git a/Robust.Shared/Console/IConsoleShell.cs b/Robust.Shared/Console/IConsoleShell.cs index ddcfbb4fb..91d23fa62 100644 --- a/Robust.Shared/Console/IConsoleShell.cs +++ b/Robust.Shared/Console/IConsoleShell.cs @@ -1,4 +1,5 @@ using Robust.Shared.Players; +using Robust.Shared.Utility; namespace Robust.Shared.Console { @@ -54,6 +55,8 @@ namespace Robust.Shared.Console /// Line of text to write. void WriteLine(string text); + void WriteLine(FormattedMessage message); + /// /// Write an error line to the console window. /// diff --git a/Robust.Shared/Console/LocalizedCommands.cs b/Robust.Shared/Console/LocalizedCommands.cs index 3328598a8..67e50673b 100644 --- a/Robust.Shared/Console/LocalizedCommands.cs +++ b/Robust.Shared/Console/LocalizedCommands.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using Robust.Shared.IoC; @@ -5,6 +6,7 @@ using Robust.Shared.Localization; namespace Robust.Shared.Console; +[Obsolete("You should use ToolshedCommand instead.")] public abstract class LocalizedCommands : IConsoleCommand { [Dependency] protected readonly ILocalizationManager LocalizationManager = default!; @@ -23,12 +25,12 @@ public abstract class LocalizedCommands : IConsoleCommand /// public abstract void Execute(IConsoleShell shell, string argStr, string[] args); - + /// public virtual CompletionResult GetCompletion(IConsoleShell shell, string[] args) => CompletionResult.Empty; /// - public virtual ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, + public virtual ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, string argStr, CancellationToken cancel) { return ValueTask.FromResult(GetCompletion(shell, args)); diff --git a/Robust.Shared/Network/Messages/MsgConCmdAck.cs b/Robust.Shared/Network/Messages/MsgConCmdAck.cs index aaa5de52f..8933142a0 100644 --- a/Robust.Shared/Network/Messages/MsgConCmdAck.cs +++ b/Robust.Shared/Network/Messages/MsgConCmdAck.cs @@ -1,5 +1,7 @@ +using System.IO; using Lidgren.Network; using Robust.Shared.Serialization; +using Robust.Shared.Utility; #nullable disable @@ -9,17 +11,24 @@ namespace Robust.Shared.Network.Messages { public override MsgGroups MsgGroup => MsgGroups.String; - public string Text { get; set; } + public FormattedMessage Text { get; set; } public bool Error { get; set; } public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { - Text = buffer.ReadString(); + int length = buffer.ReadVariableInt32(); + using var stream = buffer.ReadAlignedMemory(length); + Text = serializer.Deserialize(stream); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { - buffer.Write(Text); + var stream = new MemoryStream(); + + serializer.Serialize(stream, Text); + + buffer.WriteVariableInt32((int)stream.Length); + buffer.Write(stream.AsSpan()); } } } diff --git a/Robust.Shared/Network/Messages/MsgConCompletion.cs b/Robust.Shared/Network/Messages/MsgConCompletion.cs index a3ba5c805..5092f4e41 100644 --- a/Robust.Shared/Network/Messages/MsgConCompletion.cs +++ b/Robust.Shared/Network/Messages/MsgConCompletion.cs @@ -12,6 +12,8 @@ public sealed class MsgConCompletion : NetMessage public int Seq { get; set; } public string[] Args { get; set; } + public string ArgString { get; set; } + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { Seq = buffer.ReadInt32(); @@ -22,6 +24,8 @@ public sealed class MsgConCompletion : NetMessage { Args[i] = buffer.ReadString(); } + + ArgString = buffer.ReadString(); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) @@ -33,5 +37,7 @@ public sealed class MsgConCompletion : NetMessage { buffer.Write(arg); } + + buffer.Write(ArgString); } } diff --git a/Robust.Shared/Players/ICommonSession.cs b/Robust.Shared/Players/ICommonSession.cs index 98aaa9cef..945e174c0 100644 --- a/Robust.Shared/Players/ICommonSession.cs +++ b/Robust.Shared/Players/ICommonSession.cs @@ -44,11 +44,5 @@ namespace Robust.Shared.Players /// on the Client only the LocalPlayer has a network channel. /// INetChannel ConnectedClient { get; } - - /// - /// Porting convenience for admin commands which use such logic as "at the player's feet", etc: the transform component of the attached entity. - /// - [Obsolete("Query manually from the EntityUid")] - TransformComponent? AttachedEntityTransform => IoCManager.Resolve().GetComponentOrNull(AttachedEntity); } } diff --git a/Robust.Shared/SharedIoC.cs b/Robust.Shared/SharedIoC.cs index 6cf19e546..93449d5d1 100644 --- a/Robust.Shared/SharedIoC.cs +++ b/Robust.Shared/SharedIoC.cs @@ -17,6 +17,7 @@ using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Threading; using Robust.Shared.Timing; +using Robust.Shared.Toolshed; namespace Robust.Shared { @@ -46,6 +47,7 @@ namespace Robust.Shared deps.Register(); deps.Register(); deps.Register(); + deps.Register(); deps.Register(); } } diff --git a/Robust.Shared/Toolshed/Attributes.cs b/Robust.Shared/Toolshed/Attributes.cs new file mode 100644 index 000000000..2c71979c0 --- /dev/null +++ b/Robust.Shared/Toolshed/Attributes.cs @@ -0,0 +1,90 @@ +using System; +using JetBrains.Annotations; + +namespace Robust.Shared.Toolshed; + +/// +/// Used to mark a class so that automatically discovers and registers it. +/// +[AttributeUsage(AttributeTargets.Class)] +[MeansImplicitUse] +public sealed class ToolshedCommandAttribute : Attribute +{ + public string? Name = null; +} + +/// +/// Marks a function in a as being an implementation of that command, so that Toolshed will use it's signature for parsing/etc. +/// +[AttributeUsage(AttributeTargets.Method)] +[MeansImplicitUse] +public sealed class CommandImplementationAttribute : Attribute +{ + public readonly string? SubCommand = null; + + public CommandImplementationAttribute(string? subCommand = null) + { + SubCommand = subCommand; + } +} + +/// +/// Marks an argument in a function in a as being the "piped" argument, the return value of the prior command in the chain. +/// +[AttributeUsage(AttributeTargets.Parameter)] +[MeansImplicitUse] +public sealed class PipedArgumentAttribute : Attribute +{ +} + +/// +/// Marks an argument in a function as being an argument of a . +/// This will make it so the argument will get parsed. +/// +[AttributeUsage(AttributeTargets.Parameter)] +[MeansImplicitUse] +public sealed class CommandArgumentAttribute : Attribute +{ +} + +/// +/// Marks an argument in a function as specifying whether or not this call to a is inverted. +/// +[AttributeUsage(AttributeTargets.Parameter)] +[MeansImplicitUse] +public sealed class CommandInvertedAttribute : Attribute +{ +} + +/// +/// Marks an argument in a function as being where the invocation context should be provided in a . +/// +/// +[AttributeUsage(AttributeTargets.Parameter)] +[MeansImplicitUse] +public sealed class CommandInvocationContextAttribute : Attribute +{ +} + +/// +/// Marks a command implementation as taking the type of the previous command in sequence as a generic argument. +/// +/// +/// If the argument marked with is not T but instead a pattern like IEnumerable<T>, Toolshed will account for this. +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class TakesPipedTypeAsGenericAttribute : Attribute +{ +} + +// Internal because this is just a hack at the moment and should be replaced with proper inference later! +// Overrides type argument parsing to parse a block and then use it's return type as the sole type argument. +internal sealed class MapLikeCommandAttribute : Attribute +{ + public bool TakesPipedType; + + public MapLikeCommandAttribute(bool takesPipedType = true) + { + TakesPipedType = takesPipedType; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs b/Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs new file mode 100644 index 000000000..4c2c70b66 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs @@ -0,0 +1,13 @@ +using System; + +namespace Robust.Shared.Toolshed.Commands.Debug; + +[ToolshedCommand] +internal sealed class FuckCommand : ToolshedCommand +{ + [CommandImplementation] + public object? Fuck([PipedArgument] object? value) + { + throw new Exception("fuck!"); + } +} diff --git a/Robust.Shared/Toolshed/Commands/EcsCompCommand.cs b/Robust.Shared/Toolshed/Commands/EcsCompCommand.cs new file mode 100644 index 000000000..0da75e6fc --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/EcsCompCommand.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Robust.Shared.Toolshed.Commands; + +[ToolshedCommand] +internal sealed class EcsCompCommand : ToolshedCommand +{ + [Dependency] private readonly IComponentFactory _factory = default!; + + [CommandImplementation("listty")] + public IEnumerable ListTy() + { + return _factory.AllRegisteredTypes; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs new file mode 100644 index 000000000..1c979b28e --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Toolshed.TypeParsers; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class CompCommand : ToolshedCommand +{ + public override Type[] TypeParameterParsers => new[] {typeof(ComponentType)}; + + [CommandImplementation] + public IEnumerable CompEnumerable([PipedArgument] IEnumerable input) + where T: IComponent + { + return input.Where(HasComp).Select(Comp); + } + + [CommandImplementation] + public T? CompDirect([PipedArgument] EntityUid input) + where T : IComponent + { + TryComp(input, out T? res); + return res; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs new file mode 100644 index 000000000..886c90571 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class DeleteCommand : ToolshedCommand +{ + [Dependency] private readonly IEntityManager _entity = default!; + + [CommandImplementation] + public void Delete([PipedArgument] IEnumerable entities) + { + foreach (var ent in entities) + { + Del(ent); + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs new file mode 100644 index 000000000..e0f735cc4 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Robust.Shared.GameObjects; +using Robust.Shared.Toolshed.Invocation; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class DoCommand : ToolshedCommand +{ + private SharedTransformSystem? _xformSys; + + [CommandImplementation, TakesPipedTypeAsGeneric] + public void Do( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] IEnumerable input, + [CommandArgument] string command) + { + if (ctx is not OldShellInvocationContext { } reqCtx) + { + throw new NotImplementedException(); + } + + _xformSys ??= GetSys(); + var xformQ = GetEntityQuery(); + var shell = reqCtx.Shell; + foreach (var i in input) + { + var cmdStr = command; + if (i is EntityUid id) + { + var worldPos = _xformSys.GetWorldPosition(id, xformQ); + cmdStr = cmdStr + .Replace("$ID", id.ToString()) + .Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture)) + .Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture)); + } + + cmdStr = cmdStr.Replace("$SELF", i!.ToString() ?? ""); + shell.ExecuteCommand(cmdStr); + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs new file mode 100644 index 000000000..9947ec65d --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class EntCommand : ToolshedCommand +{ + [CommandImplementation] + public EntityUid Ent([CommandArgument] EntityUid ent) => ent; +} + diff --git a/Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs new file mode 100644 index 000000000..24aee9dd8 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class EntitiesCommand : ToolshedCommand +{ + [CommandImplementation] + public IEnumerable Entities() + { + // NOTE: Makes a copy due to the fact chained on commands might modify this list. + return EntityManager.GetEntities().ToHashSet(); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs new file mode 100644 index 000000000..3364b3509 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class NamedCommand : ToolshedCommand +{ + [CommandImplementation] + public IEnumerable Named([PipedArgument] IEnumerable input, [CommandArgument] string regex, [CommandInverted] bool inverted) + { + var compiled = new Regex($"${regex}^"); + return input.Where(x => compiled.IsMatch(EntName(x)) ^ inverted); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs new file mode 100644 index 000000000..8ca996b37 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class NearbyCommand : ToolshedCommand +{ + private EntityLookupSystem? _lookup; + + [CommandImplementation] + public IEnumerable Nearby([PipedArgument] IEnumerable input, [CommandArgument] float range) + { + _lookup ??= GetSys(); + return input.SelectMany(x => _lookup.GetEntitiesInRange(x, range)).Distinct(); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs new file mode 100644 index 000000000..0a6154db5 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class PausedCommand : ToolshedCommand +{ + [CommandImplementation] + public IEnumerable Paused([PipedArgument] IEnumerable entities, [CommandInverted] bool inverted) + { + return entities.Where(x => Comp(x).EntityPaused ^ inverted); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/PrototypedCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/PrototypedCommand.cs new file mode 100644 index 000000000..c41ae45f0 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/PrototypedCommand.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class PrototypedCommand : ToolshedCommand +{ + [CommandImplementation()] + public IEnumerable Prototyped([PipedArgument] IEnumerable input, + [CommandArgument] string prototype) + => input.Where(x => MetaData(x).EntityPrototype?.ID == prototype); + +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs new file mode 100644 index 000000000..61dad190d --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Toolshed.TypeParsers; + +namespace Robust.Shared.Toolshed.Commands.Entities; + +[ToolshedCommand] +internal sealed class WithCommand : ToolshedCommand +{ + [CommandImplementation] + public IEnumerable With([PipedArgument] IEnumerable input, [CommandArgument] ComponentType ty, [CommandInverted] bool inverted) + { + return input.Where(x => EntityManager.HasComponent(x, ty.Ty) ^ inverted); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs new file mode 100644 index 000000000..f038130a6 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class AnyCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Any([PipedArgument] IEnumerable input) => input.Any(); +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs new file mode 100644 index 000000000..dc35ed6de --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic ; + +[ToolshedCommand(Name = "=>")] +internal sealed class ArrowCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Arrow( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T input, + [CommandArgument] ValueRef @ref + ) + { + @ref.Set(ctx, input); + return input; + } + + [CommandImplementation, TakesPipedTypeAsGeneric] + public List Arrow( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] IEnumerable input, + [CommandArgument] ValueRef> @ref + ) + { + var list = input.ToList(); + @ref.Set(ctx, list); + return list; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs new file mode 100644 index 000000000..cb1f75a41 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs @@ -0,0 +1,13 @@ +using System; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class AsCommand : ToolshedCommand +{ + public override Type[] TypeParameterParsers => new[] {typeof(Type)}; + + [CommandImplementation, TakesPipedTypeAsGeneric] + public TOut? As([PipedArgument] TIn value) + => (TOut?)(object?)value; +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs new file mode 100644 index 000000000..fe0e2de6d --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class CountCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public int Count([PipedArgument] IEnumerable enumerable) + { + return enumerable.Count(); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs new file mode 100644 index 000000000..459612e9c --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Robust.Shared.GameObjects; +using Robust.Shared.Players; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand, MapLikeCommand(false)] +internal sealed class EmplaceCommand : ToolshedCommand +{ + [CommandImplementation] + IEnumerable Emplace( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] IEnumerable value, + [CommandArgument] Block block + ) + { + + foreach (var v in value) + { + var emplaceCtx = new EmplaceContext(ctx, v, EntityManager); + yield return block.Invoke(null, emplaceCtx)!; + } + } +} + +internal record struct EmplaceContext(IInvocationContext Inner, T Value, IEntityManager EntityManager) : IInvocationContext +{ + public bool CheckInvokable(CommandSpec command, out IConError? error) + { + return Inner.CheckInvokable(command, out error); + } + + public ICommonSession? Session => Inner.Session; + + public void WriteLine(string line) + { + Inner.WriteLine(line); + } + + public void ReportError(IConError err) + { + Inner.ReportError(err); + } + + public IEnumerable GetErrors() + { + return Inner.GetErrors(); + } + + public void ClearErrors() + { + Inner.ClearErrors(); + } + + public Dictionary Variables => default!; // we never use this. + + public IEnumerable GetVars() + { + // note: this lies. + return Inner.GetVars(); + } + + public object? ReadVar(string name) + { + if (name == "value") + return Value; + + if (Value is IEmplaceBreakout breakout) + { + if (breakout.TryReadVar(name, out var value)) + return value; + } + + // TODO: Emplace behavior should be generalized and not hardcoded. + if (Value is EntityUid id) + { + switch (name) + { + case "wy": + case "wx": + { + var xform = EntityManager.GetComponent(id); + var sys = EntityManager.System(); + var coords = sys.GetWorldPosition(xform); + if (name == "wx") + return coords.X; + else + return coords.Y; + } + case "proto": + case "desc": + case "name": + case "paused": + { + var meta = EntityManager.GetComponent(id); + switch (name) + { + case "proto": + return meta.EntityPrototype?.ID ?? ""; + case "desc": + return meta.EntityDescription; + case "name": + return meta.EntityName; + case "paused": + return meta.EntityPaused; + } + + throw new UnreachableException(); + } + } + } + else if (Value is ICommonSession session) + { + switch (name) + { + case "ent": + { + return session.AttachedEntity!; + } + case "name": + { + return session.Name; + } + case "userid": + { + return session.UserId; + } + } + } + + return Inner.ReadVar(name); + } + + public void WriteVar(string name, object? value) + { + if (name == "value") + return; + + if (Value is IEmplaceBreakout v) + { + if (v.VarsOverriden.Contains(name)) + return; + } + + Inner.WriteVar(name, value); + } +} + +public interface IEmplaceBreakout +{ + public ImmutableHashSet VarsOverriden { get; } + public bool TryReadVar(string name, out object? value); +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs new file mode 100644 index 000000000..d31125aba --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class FirstCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T First([PipedArgument] IEnumerable input) => input.First(); +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs new file mode 100644 index 000000000..79a2b0290 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs @@ -0,0 +1,22 @@ +using System.Collections; +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class IsEmptyCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool IsEmpty([PipedArgument] T? input, [CommandInverted] bool inverted) + { + if (input is null) + return true ^ inverted; // Null is empty for all we care. + + if (input is IEnumerable @enum) + { + return !@enum.Cast().Any() ^ inverted; + } + + return false ^ inverted; // Not a collection, cannot be empty. + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs new file mode 100644 index 000000000..d245d44cc --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs @@ -0,0 +1,8 @@ +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class IsNullCommand : ToolshedCommand +{ + [CommandImplementation] + public bool IsNull([PipedArgument] object? input, [CommandInverted] bool inverted) => input is null ^ inverted; +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs new file mode 100644 index 000000000..44a03f351 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand, MapLikeCommand] +internal sealed class MapCommand : ToolshedCommand +{ + public override Type[] TypeParameterParsers => new[] {typeof(Type)}; + + [CommandImplementation, TakesPipedTypeAsGeneric] + public IEnumerable? Map( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] IEnumerable value, + [CommandArgument] Block block + ) + { + return value.Select(x => block.Invoke(x, ctx)).Where(x => x != null).Cast(); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs new file mode 100644 index 000000000..e79da078d --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.IoC; +using Robust.Shared.Random; +using Robust.Shared.Toolshed.TypeParsers; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class SelectCommand : ToolshedCommand +{ + [Dependency] private readonly IRobustRandom _random = default!; + + [CommandImplementation, TakesPipedTypeAsGeneric] + public IEnumerable Select([PipedArgument] IEnumerable enumerable, [CommandArgument] Quantity quantity, [CommandInverted] bool inverted) + { + var arr = enumerable.ToArray(); + _random.Shuffle(arr); + + if (quantity is {Amount: { } amount}) + { + + var taken = (int) System.Math.Ceiling(amount); + if (inverted) + taken = System.Math.Max(0, arr.Length - taken); + + return arr.Take(taken); + } + else + { + var percent = inverted + ? (int) System.Math.Floor(arr.Length * System.Math.Clamp(1 - (double) quantity.Percentage!, 0, 1)) + : (int) System.Math.Floor(arr.Length * System.Math.Clamp((double) quantity.Percentage!, 0, 1)); + + return arr.Take(percent); + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs new file mode 100644 index 000000000..669f274a5 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class SplatCommand : ToolshedCommand +{ + public override Type[] TypeParameterParsers => new[] {typeof(Type)}; + + [CommandImplementation] + public IEnumerable Splat( + [CommandInvocationContext] IInvocationContext ctx, + [CommandArgument] ValueRef value, + [CommandArgument] ValueRef amountValue) + { + var amount = amountValue.Evaluate(ctx); + for (var i = 0; i < amount; i++) + { + yield return value.Evaluate(ctx)!; + if (ctx.GetErrors().Any()) + yield break; + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs new file mode 100644 index 000000000..18a1fd020 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class UniqueCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public IEnumerable Unique([PipedArgument] IEnumerable input) + => input.Distinct(); +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs new file mode 100644 index 000000000..0a1f7f3bc --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs @@ -0,0 +1,16 @@ +using System; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class ValCommand : ToolshedCommand +{ + public override Type[] TypeParameterParsers => new[] {typeof(Type)}; + + [CommandImplementation] + public T Val( + [CommandInvocationContext] IInvocationContext ctx, + [CommandArgument] ValueRef value + ) => value.Evaluate(ctx)!; +} diff --git a/Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs new file mode 100644 index 000000000..0e752984c --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Generic; + +[ToolshedCommand] +internal sealed class WhereCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public IEnumerable Where( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] IEnumerable input, + [CommandArgument] Block check + ) + { + foreach (var i in input) + { + var res = check.Invoke(i, ctx); + + if (ctx.GetErrors().Any()) + yield break; + + if (res) + yield return i; + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs b/Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs new file mode 100644 index 000000000..4c27e5ec4 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs @@ -0,0 +1,166 @@ +using System.Numerics; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Math; + +[ToolshedCommand(Name = "+")] +public sealed class AddCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IAdditionOperators + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return x; + return x + yVal; + } +} + +[ToolshedCommand(Name = "-")] +public sealed class SubtractCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : ISubtractionOperators + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return x; + return x - yVal; + } +} + +[ToolshedCommand(Name = "*")] +public sealed class MultiplyCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IMultiplyOperators + { + var yVal = y.Evaluate(ctx)!; + return x * yVal; + } +} + +[ToolshedCommand(Name = "/")] +public sealed class DivideCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IDivisionOperators + { + var yVal = y.Evaluate(ctx)!; + return x / yVal; + } +} + +[ToolshedCommand] +public sealed class MinCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IComparisonOperators + { + var yVal = y.Evaluate(ctx)!; + return x > yVal ? yVal : x; + } +} + +[ToolshedCommand] +public sealed class MaxCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IComparisonOperators + { + var yVal = y.Evaluate(ctx)!; + return x > yVal ? x : yVal; + } +} + +[ToolshedCommand(Name = "&")] +public sealed class BitAndCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IBitwiseOperators + { + var yVal = y.Evaluate(ctx)!; + return x & yVal; + } +} + +[ToolshedCommand(Name = "|")] +public sealed class BitOrCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IBitwiseOperators + { + var yVal = y.Evaluate(ctx)!; + return x | yVal; + } +} + +[ToolshedCommand(Name = "^")] +public sealed class BitXorCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IBitwiseOperators + { + var yVal = y.Evaluate(ctx)!; + return x ^ yVal; + } +} + +[ToolshedCommand] +public sealed class NegCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public T Operation( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x + ) + where T : IUnaryNegationOperators + { + return -x; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs b/Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs new file mode 100644 index 000000000..ed0610bdc --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs @@ -0,0 +1,113 @@ +using System; +using System.Numerics; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Math; + +[ToolshedCommand(Name = ">")] +public sealed class GreaterThanCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : INumber + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return x > yVal; + } +} + +[ToolshedCommand(Name = "<")] +public sealed class LessThanCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IComparisonOperators + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return x > yVal; + } +} + +[ToolshedCommand(Name = ">=")] +public sealed class GreaterThanOrEqualCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : INumber + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return x >= yVal; + } +} + +[ToolshedCommand(Name = "<=")] +public sealed class LessThanOrEqualCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IComparisonOperators + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return x <= yVal; + } +} + +[ToolshedCommand(Name = "==")] +public sealed class EqualCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IEquatable + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return x.Equals(yVal); + } +} + +[ToolshedCommand(Name = "!=")] +public sealed class NotEqualCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public bool Comparison( + [CommandInvocationContext] IInvocationContext ctx, + [PipedArgument] T x, + [CommandArgument] ValueRef y + ) + where T : IEquatable + { + var yVal = y.Evaluate(ctx); + if (yVal is null) + return false; + return !x.Equals(yVal); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs new file mode 100644 index 000000000..d31e257c3 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Configuration; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class BuildInfoCommand : ToolshedCommand +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private static readonly string Gold = Color.Gold.ToHex(); + + [CommandImplementation] + public void BuildInfo([CommandInvocationContext] IInvocationContext ctx) + { + var game = _cfg.GetCVar(CVars.BuildForkId); + ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Game:[/color] {game}")); + var buildCommit = _cfg.GetCVar(CVars.BuildHash); + ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Build commit:[/color] {buildCommit}")); + var buildManifest = _cfg.GetCVar(CVars.BuildManifestHash); + ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Manifest hash:[/color] {buildManifest}")); + var engine = _cfg.GetCVar(CVars.BuildEngineVersion); + ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Engine ver:[/color] {engine}")); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs new file mode 100644 index 000000000..44b6363ff --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class CmdCommand : ToolshedCommand +{ + [CommandImplementation("list")] + public IEnumerable List() + => Toolshed.AllCommands(); + + [CommandImplementation("moo")] + public string Moo() + => "Have you mooed today?"; + + [CommandImplementation("descloc")] + public string GetLogStr([PipedArgument] CommandSpec cmd) => cmd.DescLocStr(); + +#if CLIENT_SCRIPTING + [CommandImplementation("getshim")] + public MethodInfo GetShim([CommandArgument] Block block) + { + + // this is gross sue me + var invocable = block.CommandRun.Commands.Last().Item1.Invocable; + return invocable.GetMethodInfo(); + } +#endif +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs new file mode 100644 index 000000000..dd8db5d59 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class ExplainCommand : ToolshedCommand +{ + [CommandImplementation] + public void Explain( + [CommandInvocationContext] IInvocationContext ctx, + [CommandArgument] CommandRun expr + ) + { + foreach (var (cmd, _) in expr.Commands) + { + ctx.WriteLine(cmd.Command.GetHelp(cmd.SubCommand)); + ctx.WriteLine($"{cmd.PipedType?.PrettyName() ?? "[none]"} -> {cmd.ReturnType?.PrettyName() ?? "[none]"}"); + ctx.WriteLine(""); + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs new file mode 100644 index 000000000..28b2c680c --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Maths; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class HelpCommand : ToolshedCommand +{ + private static readonly string Gold = Color.Gold.ToHex(); + private static readonly string Aqua = Color.Aqua.ToHex(); + + [CommandImplementation] + public void Help([CommandInvocationContext] IInvocationContext ctx) + { + ctx.WriteLine($@" + TOOLSHED + /.\\\\\\\\ +/___\\\\\\\\ +|''''|'''''| +| 8 | === | +|_0__|_____|"); + ctx.WriteMarkup($@" +For a list of commands, run [color={Gold}]cmd:list[/color]. +To search for commands, run [color={Gold}]cmd:list search ""[color={Aqua}]query[/color]""[/color]. +For a breakdown of how a string of commands flows, run [color={Gold}]explain [color={Aqua}]commands here[/color][/color]. +For help with old console commands, run [color={Gold}]oldhelp[/color]. +"); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs new file mode 100644 index 000000000..516dd9d9f --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.IoC; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class IoCCommand : ToolshedCommand +{ + [CommandImplementation("registered")] + public IEnumerable Registered() => IoCManager.Instance!.GetRegisteredTypes(); + + [CommandImplementation("get")] + public object? Get([PipedArgument] Type t) => IoCManager.ResolveType(t); +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs new file mode 100644 index 000000000..6aba6c179 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Localization; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class LocCommand : ToolshedCommand +{ + [CommandImplementation("tryloc")] + public string? TryLocalize([PipedArgument] string str) + { + Loc.TryGetString(str, out var loc); + return loc; + } + + [CommandImplementation("loc")] + public string Localize([PipedArgument] string str) => Loc.GetString(str); +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs new file mode 100644 index 000000000..5fcd9713e --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Physics.Components; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class PhysicsCommand : ToolshedCommand +{ + private SharedTransformSystem? _xform = default!; + + [CommandImplementation("velocity")] + public IEnumerable Velocity([PipedArgument] IEnumerable input) + { + var physQuery = GetEntityQuery(); + + foreach (var ent in input) + { + if (!physQuery.TryGetComponent(ent, out var comp)) + continue; + + yield return comp.LinearVelocity.Length(); + } + } + + [CommandImplementation("parent")] + public IEnumerable Parent([PipedArgument] IEnumerable input) + { + _xform ??= GetSys(); + return input.Select(x => Comp(x).ParentUid); + } + + [CommandImplementation("angular_velocity")] + public IEnumerable AngularVelocity([PipedArgument] IEnumerable input) + { + var physQuery = GetEntityQuery(); + + foreach (var ent in input) + { + if (!physQuery.TryGetComponent(ent, out var comp)) + continue; + + yield return comp.AngularVelocity; + } + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs new file mode 100644 index 000000000..c6140ecd1 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class SearchCommand : ToolshedCommand +{ + [CommandImplementation, TakesPipedTypeAsGeneric] + public IEnumerable Search([PipedArgument] IEnumerable input, [CommandArgument] string term) + { + var list = input.Select(x => Toolshed.PrettyPrintType(x)).ToList(); + return list.Where(x => x.Contains(term, StringComparison.InvariantCultureIgnoreCase)).Select(x => + { + var startIdx = x.IndexOf(term, StringComparison.InvariantCultureIgnoreCase); + return ConHelpers.HighlightSpan(x, (startIdx, startIdx + term.Length), Color.Aqua); + }); + } +} + diff --git a/Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs new file mode 100644 index 000000000..00ac1921b --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs @@ -0,0 +1,19 @@ +using Robust.Shared.Maths; +using Robust.Shared.Timing; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class StopwatchCommand : ToolshedCommand +{ + [CommandImplementation] + public object? Stopwatch([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] CommandRun expr) + { + var watch = new Stopwatch(); + watch.Start(); + var result = expr.Invoke(null, ctx); + ctx.WriteMarkup($"Ran expression in [color={Color.Aqua.ToHex()}]{watch.Elapsed:g}[/color]"); + return result; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs new file mode 100644 index 000000000..279336c84 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class TypesCommand : ToolshedCommand +{ + [CommandImplementation("consumers")] + public void Consumers([CommandInvocationContext] IInvocationContext ctx, [PipedArgument] object? input) + { + var t = input is Type ? (Type)input : input!.GetType(); + + ctx.WriteLine($"Valid intakers for {t.PrettyName()}:"); + + foreach (var (command, subCommand) in Toolshed.CommandsTakingType(t)) + { + if (subCommand is null) + ctx.WriteLine($"{command.Name}"); + else + ctx.WriteLine($"{command.Name}:{subCommand}"); + } + } + + [CommandImplementation("tree")] + public IEnumerable Tree([CommandInvocationContext] IInvocationContext ctx, [PipedArgument] object? input) + { + var t = input is Type ? (Type)input : input!.GetType(); + return Toolshed.AllSteppedTypes(t); + } + + [CommandImplementation("gettype")] + public Type GetType([PipedArgument] object? input) + { + return input?.GetType() ?? typeof(void); + } + + [CommandImplementation("fullname")] + public string FullName([PipedArgument] Type input) + { + return input.FullName!; + } +} diff --git a/Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs b/Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs new file mode 100644 index 000000000..f597e2a6e --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs @@ -0,0 +1,13 @@ +using System.Linq; + +namespace Robust.Shared.Toolshed.Commands.Misc; + +[ToolshedCommand] +internal sealed class VarsCommand : ToolshedCommand +{ + [CommandImplementation] + public void Vars([CommandInvocationContext] IInvocationContext ctx) + { + ctx.WriteLine(Toolshed.PrettyPrintType(ctx.GetVars().Select(x => $"{x} = {ctx.ReadVar(x)}"))); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs b/Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs new file mode 100644 index 000000000..ba6823bf0 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Toolshed.Errors; + +namespace Robust.Shared.Toolshed.Commands.Players; + +[ToolshedCommand] +internal sealed class SelfCommand : ToolshedCommand +{ + [CommandImplementation] + public EntityUid Self([CommandInvocationContext] IInvocationContext ctx) + { + if (ctx.Session is null) + { + ctx.ReportError(new NotForServerConsoleError()); + return default!; + } + + if (ctx.Session.AttachedEntity is { } ent) + return ent; + + ctx.ReportError(new SessionHasNoEntityError(ctx.Session)); + return default!; + } +} + diff --git a/Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs b/Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs new file mode 100644 index 000000000..d8286d0c5 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Robust.Shared.Toolshed.Commands.Types; + +#if CLIENT_SCRIPTING +[ToolshedCommand] +internal sealed class MethodsCommand : ToolshedCommand +{ + [CommandImplementation("get")] + public IEnumerable Get([PipedArgument] IEnumerable types) + { + foreach (var ty in types) + { + foreach (var method in ty.GetMethods()) + { + yield return method; + } + } + } + + [CommandImplementation("overrides")] + public IEnumerable Overrides([PipedArgument] IEnumerable types) + { + foreach (var ty in types) + { + foreach (var method in ty.GetMethods()) + { + if (method.DeclaringType != ty) + yield return method; + } + } + } + + [CommandImplementation("overridesfrom")] + public IEnumerable OverridesFrom([PipedArgument] IEnumerable types, [CommandArgument] Type t) + { + foreach (var ty in types) + { + foreach (var method in ty.GetMethods()) + { + if (method.DeclaringType == t) + yield return method; + } + } + } +} +#endif diff --git a/Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs b/Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs new file mode 100644 index 000000000..2081ab908 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Commands.Vfs; + +[ToolshedCommand] +internal sealed class CdCommand : VfsCommand +{ + [CommandImplementation] + public void Cd( + [CommandInvocationContext] IInvocationContext ctx, + [CommandArgument] ResPath path + ) + { + var curPath = CurrentPath(ctx); + + if (path.IsRooted) + { + curPath = path; + } + else + { + curPath /= path; + } + + SetPath(ctx, curPath); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs b/Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs new file mode 100644 index 000000000..2e28b678e --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Commands.Vfs; + +[ToolshedCommand] +internal sealed class LsCommand : VfsCommand +{ + [CommandImplementation("here")] + public IEnumerable LsHere([CommandInvocationContext] IInvocationContext ctx) + { + var curPath = CurrentPath(ctx); + return Resources.ContentGetDirectoryEntries(curPath).Select(x => curPath/x); + } + + [CommandImplementation("in")] + public IEnumerable LsIn([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] ResPath @in) + { + var curPath = CurrentPath(ctx); + if (@in.IsRooted) + { + curPath = @in; + } + else + { + curPath /= @in; + } + return Resources.ContentGetDirectoryEntries(curPath).Select(x => curPath/x); + } +} diff --git a/Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs b/Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs new file mode 100644 index 000000000..640742ac5 --- /dev/null +++ b/Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs @@ -0,0 +1,29 @@ +using System; +using JetBrains.Annotations; +using Robust.Shared.ContentPack; +using Robust.Shared.IoC; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Commands.Vfs; + +/// +/// A simple base class for commands that work with the VFS and would like to manipulate the user's current location within the VFS. +/// +/// +[PublicAPI] +public abstract class VfsCommand : ToolshedCommand +{ + [Dependency] protected readonly IResourceManager Resources = default!; + + /// + /// The name of the variable storing a ResPath? representing the user's current VFS location. + /// + public const string UserVfsLocVariableName = "user_vfs_loc"; + + protected ResPath CurrentPath(IInvocationContext ctx) => ((ResPath?) ctx.ReadVar(UserVfsLocVariableName)) ?? ResPath.Root; + + protected void SetPath(IInvocationContext ctx, ResPath path) + { + ctx.WriteVar(UserVfsLocVariableName, (ResPath?) path.Clean()); + } +} diff --git a/Robust.Shared/Toolshed/Errors/IConError.cs b/Robust.Shared/Toolshed/Errors/IConError.cs new file mode 100644 index 000000000..14e56fab6 --- /dev/null +++ b/Robust.Shared/Toolshed/Errors/IConError.cs @@ -0,0 +1,123 @@ +using System.Diagnostics; +using System.Text; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Errors; + +/// +/// A Toolshed-oriented representation of an error. +/// Contains metadata about where in an executed command it occurred, and supports formatting. +/// +/// > entities runverbas self "yeet" +/// entities runverbas self "yeet" +/// ^^^^^ +/// You must be logged in with a client to use this, the server console isn't workable. +/// +/// +public interface IConError +{ + /// + /// Returns a user friendly description of the error. + /// + /// + /// This calls for the actual description by default. + /// If you fully override this, you should provide your own context provider, as the default implementation includes where in the expression the error occurred. + /// + public FormattedMessage Describe() + { + var msg = new FormattedMessage(); + if (Expression is { } expr && IssueSpan is { } span) + { + msg.AddMessage(ConHelpers.HighlightSpan(expr, span, Color.Red)); + msg.PushNewline(); + msg.AddMessage(ConHelpers.ArrowSpan(span)); + msg.PushNewline(); + } + msg.AddMessage(DescribeInner()); +#if TOOLS + if (Trace is not null) + { + msg.PushNewline(); + msg.AddText(Trace.ToString()); + } +#endif + return msg; + } + + /// + /// Describes the error, called by 's default implementation. + /// + protected FormattedMessage DescribeInner(); + /// + /// The expression this error was raised in or on. + /// + public string? Expression { get; protected set; } + /// + /// Where in the expression this error was raised. + /// + public Vector2i? IssueSpan { get; protected set; } + /// + /// The stack trace for this error if any. + /// + /// + /// This is not present in release builds. + /// + public StackTrace? Trace { get; protected set; } + + /// + /// Attaches additional context to an error, namely where it occurred. + /// + /// Expression the error occured in or on. + /// Where in the expression it occurred. + public void Contextualize(string expression, Vector2i issueSpan) + { + if (Expression is not null && IssueSpan is not null) + return; + +#if TOOLS + Trace = new StackTrace(skipFrames: 1); +#endif + + Expression = expression; + IssueSpan = issueSpan; + } +} + +/// +/// Pile of helpers for console formatting. +/// +public static class ConHelpers +{ + /// + /// Highlights a section of the input a given color. + /// + /// Input text. + /// Span to highlight. + /// Color to use. + /// A formatted message with highlighting applied. + public static FormattedMessage HighlightSpan(string input, Vector2i span, Color color) + { + if (span.X == span.Y) + return new FormattedMessage(); + var msg = FormattedMessage.FromMarkup(input[..span.X]); + msg.PushColor(color); + msg.AddText(input[span.X..span.Y]); + msg.Pop(); + msg.AddText(input[span.Y..]); + return msg; + } + + /// + /// Creates a string with up arrows (^) under the given span. + /// + /// Span to underline. + /// A string of whitespace with (^) under the given span. + public static FormattedMessage ArrowSpan(Vector2i span) + { + var builder = new StringBuilder(); + builder.Append(' ', span.X); + builder.Append('^', span.Y - span.X); + return FormattedMessage.FromMarkup(builder.ToString()); + } +} diff --git a/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs b/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs new file mode 100644 index 000000000..024d015ed --- /dev/null +++ b/Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs @@ -0,0 +1,18 @@ +using System.Diagnostics; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Errors; + +public sealed class NotForServerConsoleError : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup( + "You must be logged in with a client to use this, the server console isn't workable."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs b/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs new file mode 100644 index 000000000..ad6afbd68 --- /dev/null +++ b/Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs @@ -0,0 +1,18 @@ +using System.Diagnostics; +using Robust.Shared.Maths; +using Robust.Shared.Players; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Errors; + +public record struct SessionHasNoEntityError(ICommonSession Session) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"The user {Session.Name} has no attached entity."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs b/Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs new file mode 100644 index 000000000..032004d68 --- /dev/null +++ b/Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics; +using JetBrains.Annotations; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Errors; + +public sealed class UnhandledExceptionError : IConError +{ + [PublicAPI] + public Exception Exception; + + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText(Exception.ToString()); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } + + public UnhandledExceptionError(Exception exception) + { + Exception = exception; + } +} diff --git a/Robust.Shared/Toolshed/ForwardParser.cs b/Robust.Shared/Toolshed/ForwardParser.cs new file mode 100644 index 000000000..6b5a1d119 --- /dev/null +++ b/Robust.Shared/Toolshed/ForwardParser.cs @@ -0,0 +1,191 @@ +using System; +using System.Diagnostics; +using System.Text; +using JetBrains.Annotations; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +public sealed class ForwardParser +{ + [Dependency] public readonly ToolshedManager Toolshed = default!; + + public readonly string Input; + public int MaxIndex { get; private set; } + + public int Index { get; private set; } = 0; + + public ForwardParser(string input, ToolshedManager toolshed) + { + Toolshed = toolshed; + Input = input; + MaxIndex = input.Length - 1; + } + + private ForwardParser(ForwardParser parser, int sliceSize, int? index) + { + IoCManager.InjectDependencies(this); + DebugTools.Assert(sliceSize > 0); + Input = parser.Input; + Index = index ?? parser.Index; + MaxIndex = Math.Min(parser.MaxIndex, Index + sliceSize - 1); + } + + public bool SpanInRange(int length) + { + return MaxIndex >= (Index + length - 1); + } + + public bool EatMatch(char c) + { + if (PeekChar() == c) + { + Index++; + return true; + } + + return false; + } + + public char? PeekChar() + { + if (!SpanInRange(1)) + return null; + + return Input[Index]; + } + + public char? GetChar() + { + if (PeekChar() is { } c) + { + Index++; + return c; + } + + return null; + } + + [PublicAPI] + public void DebugPrint() + { + Logger.DebugS("parser", Input); + MakeDebugPointer(Index); + MakeDebugPointer(MaxIndex, '|'); + } + + private void MakeDebugPointer(int pointAt, char pointer = '^') + { + var builder = new StringBuilder(); + builder.Append(' ', pointAt); + builder.Append(pointer); + Logger.DebugS("parser", builder.ToString()); + } + + private string? MaybeGetWord(bool advanceIndex, Func? test) + { + var startingIndex = Index; + test ??= static c => c != ' '; + + var builder = new StringBuilder(); + + Consume(char.IsWhiteSpace); + + // Walk forward until we run into whitespace + while (PeekChar() is { } c && test(c)) + { + builder.Append(GetChar()); + } + + if (startingIndex == Index) + return null; + + if (!advanceIndex) + Index = startingIndex; + + return builder.ToString(); + } + + public string? PeekWord(Func? test = null) => MaybeGetWord(false, test); + + public string? GetWord(Func? test = null) => MaybeGetWord(true, test); + + public ParserRestorePoint Save() + { + return new ParserRestorePoint(Index); + } + + public void Restore(ParserRestorePoint point) + { + Index = point.Index; + } + + public int Consume(Func control) + { + var amount = 0; + + while (PeekChar() is { } c && control(c)) + { + GetChar(); + amount++; + } + + return amount; + } + + public ForwardParser? SliceBlock(char startDelim, char endDelim) + { + var checkpoint = Save(); + + Consume(char.IsWhiteSpace); + + if (GetChar() != startDelim) + { + Restore(checkpoint); + return null; + } + + var blockStart = Index; + + var stack = 1; + + while (stack > 0) + { + var c = GetChar(); + if (c == startDelim) + stack++; + + if (c == endDelim) + { + if (--stack == 0) + break; + } + + if (c == null) + { + Restore(checkpoint); + return null; + } + } + + return new ForwardParser(this, Index - blockStart, blockStart); + } +} + +public readonly record struct ParserRestorePoint(int Index); + +public record struct OutOfInputError : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup("Ran out of input data when data was expected."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/IInvocationContext.cs b/Robust.Shared/Toolshed/IInvocationContext.cs new file mode 100644 index 000000000..096d824ff --- /dev/null +++ b/Robust.Shared/Toolshed/IInvocationContext.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using Robust.Shared.Players; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +/// +/// A context in which Toolshed commands can be run using . +/// +public interface IInvocationContext +{ + /// + /// Test if the given command spec can be invoked in this context. + /// + /// Command to test. + /// An error to report, if any. + /// Whether or not the given command can be invoked + /// + /// THIS IS A SECURITY BOUNDARY. + /// If you want to avoid players being able to just reboot your server, you should probably implement this! + /// + public bool CheckInvokable(CommandSpec command, out IConError? error); + + /// + /// The session this context is for, if any. + /// + ICommonSession? Session { get; } + + /// + /// Writes a line to this context's output. + /// + /// The text to print. + /// + /// This can be stubbed safely, there's no requirement that the side effects of this function be observable. + /// + public void WriteLine(string line); + + + /// + /// Writes a formatted message to this context's output. + /// + /// The formatted message to print. + /// + /// This can be stubbed safely, there's no requirement that the side effects of this function be observable. + /// + public void WriteLine(FormattedMessage line) + { + // Cut markup for server. + if (Session is null) + { + WriteLine(line.ToString()); + return; + } + + WriteLine(line.ToMarkup()); + } + + /// + /// Writes the given markup to this context's output. + /// + /// The markup to print. + /// + /// This can be stubbed safely, there's no requirement that the side effects of this function be observable. + /// + public void WriteMarkup(string markup) + { + WriteLine(FormattedMessage.FromMarkup(markup)); + } + + /// + /// Writes the given error to the context's output. + /// + /// The error to write out. + /// + /// This can be stubbed safely, there's no requirement that the side effects of this function be observable. + /// + public void WriteError(IConError error) + { + WriteLine(error.Describe()); + } + + /// + /// Reports the given error to the context. + /// + /// Error to report. + /// + /// This may have arbitrary side effects. Usually, it'll push the error to some list you can retrieve with GetErrors(). + /// + public void ReportError(IConError err); + + /// + /// Gets the list of unobserved errors. + /// + /// An enumerable of console errors. + /// + /// This is not required to contain anything, may contain errors you did not ReportError(), and may not contain errors you did ReportError(). + /// + public IEnumerable GetErrors(); + + /// + /// Clears the list of unobserved errors. + /// + /// + /// After calling this, assuming atomicity (no threads), GetErrors() MUST be empty in order for an IInvocationContext to be compliant. + /// + public void ClearErrors(); + + /// + /// The backing variable storage. + /// + /// + /// You don't have to use this at all. + /// + protected Dictionary Variables { get; } + + /// + /// Reads the given variable from the context. + /// + /// The name of the variable. + /// The contents of the variable. + /// + /// This may behave arbitrarily, but it's advised it behave somewhat sanely. + /// + public virtual object? ReadVar(string name) + { + Variables.TryGetValue(name, out var res); + return res; + } + + /// + /// Writes the given variable to the context. + /// + /// The name of the variable. + /// The contents of the variable. + /// + /// Writes may be ignored or manipulated. + /// + public virtual void WriteVar(string name, object? value) + { + Variables[name] = value; + } + + /// + /// Provides a list of all variables that have been written to at some point. + /// + /// List of all variables. + public virtual IEnumerable GetVars() + { + return Variables.Keys; + } +} diff --git a/Robust.Shared/Toolshed/IPermissionController.cs b/Robust.Shared/Toolshed/IPermissionController.cs new file mode 100644 index 000000000..602c00c1e --- /dev/null +++ b/Robust.Shared/Toolshed/IPermissionController.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; +using Robust.Shared.Players; +using Robust.Shared.Toolshed.Errors; + +namespace Robust.Shared.Toolshed; + +public interface IPermissionController +{ + public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error); +} diff --git a/Robust.Shared/Toolshed/Invocation/OldShellInvocationContext.cs b/Robust.Shared/Toolshed/Invocation/OldShellInvocationContext.cs new file mode 100644 index 000000000..e2acfdcbb --- /dev/null +++ b/Robust.Shared/Toolshed/Invocation/OldShellInvocationContext.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Players; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Invocation; + +/// +internal sealed class OldShellInvocationContext : IInvocationContext +{ + [Dependency] private readonly ToolshedManager _toolshed = default!; + private readonly List _errors = new(); + + /// + /// Old system's shell associated with this context + /// + public IConsoleShell Shell; + + public OldShellInvocationContext(IConsoleShell shell) + { + IoCManager.InjectDependencies(this); + Shell = shell; + } + + /// + public bool CheckInvokable(CommandSpec command, out IConError? error) + { + if (_toolshed.ActivePermissionController is { } controller) + return controller.CheckInvokable(command, Session, out error); + + error = null; + return true; + } + + /// + public ICommonSession? Session => Shell.Player; + + /// + public void WriteLine(string line) + { + Shell.WriteLine(line); + } + + /// + public void WriteLine(FormattedMessage line) + { + Shell.WriteLine(line); + } + + /// + public void ReportError(IConError err) + { + _errors.Add(err); + } + + /// + public IEnumerable GetErrors() + { + return _errors; + } + + /// + public void ClearErrors() + { + _errors.Clear(); + } + + /// + public Dictionary Variables { get; } = new(); +} + diff --git a/Robust.Shared/Toolshed/ReflectionExtensions.cs b/Robust.Shared/Toolshed/ReflectionExtensions.cs new file mode 100644 index 000000000..9e2936924 --- /dev/null +++ b/Robust.Shared/Toolshed/ReflectionExtensions.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Robust.Shared.Exceptions; +using Robust.Shared.Log; + +namespace Robust.Shared.Toolshed; + +// TODO: Audit this for sandboxability and expose some of these to content. +internal static class ReflectionExtensions +{ + public static bool CanBeNull(this Type t) + { + return !t.IsValueType || t.IsGenericType(typeof(Nullable<>)); + } + + public static bool CanBeEmpty(this Type t) + { + return t.CanBeNull() || t.IsGenericType(typeof(IEnumerable<>)); + } + + public static bool IsGenericType(this Type t, Type genericType) + { + return t.IsGenericType && t.GetGenericTypeDefinition() == genericType; + } + + public static IEnumerable GetVariants(this Type t, ToolshedManager toolshed) + { + var args = t.GetGenericArguments(); + var generic = t.GetGenericTypeDefinition(); + var genericArgs = generic.GetGenericArguments(); + var variantCount = genericArgs.Count(x => (x.GenericParameterAttributes & GenericParameterAttributes.VarianceMask) != 0); + + if (variantCount > 1) + { + throw new NotImplementedException("I swear to god I am NOT supporting more than one variant type parameter. Absolutely no combinatorial explosions in this house, factorials can go home."); + } + + yield return t; + + if (variantCount < 1) + { + yield break; + } + + var variant = 0; + for (var i = 0; i < args.Length; i++) + { + if ((genericArgs[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) != 0) + { + variant = i; + break; + } + } + + var newArgs = (Type[]) args.Clone(); + + foreach (var type in toolshed.AllSteppedTypes(args[variant], false)) + { + newArgs[variant] = type; + yield return generic.MakeGenericType(newArgs); + } + } + + public static IEnumerable<(string, List)> BySubCommand(this IEnumerable methods) + { + var output = new Dictionary>(); + + foreach (var method in methods) + { + var subCommand = method.GetCustomAttribute()!.SubCommand ?? ""; + if (!output.TryGetValue(subCommand, out var methodList)) + { + methodList = new(); + output[subCommand] = methodList; + } + methodList.Add(method); + } + + return output.Select(x => (x.Key, x.Value)); + } + + public static Type StepDownConstraints(this Type t) + { + if (!t.IsGenericType || t.IsGenericTypeDefinition) + return t; + + var oldArgs = t.GenericTypeArguments; + var newArgs = new Type[oldArgs.Length]; + + for (var i = 0; i < oldArgs.Length; i++) + { + if (oldArgs[i].IsGenericType) + newArgs[i] = oldArgs[i].GetGenericTypeDefinition(); + else + newArgs[i] = oldArgs[i]; + } + + return t.GetGenericTypeDefinition().MakeGenericType(newArgs); + } + + public static string PrettyName(this Type type) + { + var name = type.Name; + + if (type.IsGenericParameter) + return type.ToString(); + + if (type.DeclaringType is not null) + { + name = $"{PrettyName(type.DeclaringType!)}.{type.Name}"; + } + + if (type.GetGenericArguments().Length == 0) + { + return name; + } + + if (!name.Contains('`')) + return name + "<>"; + + var genericArguments = type.GetGenericArguments(); + var exactName = name.Substring(0, name.IndexOf('`', StringComparison.InvariantCulture)); + return exactName + "<" + string.Join(",", genericArguments.Select(PrettyName)) + ">"; + } + + public static ParameterInfo? ConsoleGetPipedArgument(this MethodInfo method) + { + var p = method.GetParameters().Where(x => x.GetCustomAttribute() is not null).ToList(); + return p.FirstOrDefault(); + } + + public static IEnumerable ConsoleGetArguments(this MethodInfo method) + { + return method.GetParameters().Where(x => x.GetCustomAttribute() is not null); + } + + public static Expression CreateEmptyExpr(this Type t) + { + if (!t.CanBeEmpty()) + throw new TypeArgumentException(); + + if (t.IsGenericType(typeof(IEnumerable<>))) + { + var array = Array.CreateInstance(t.GetGenericArguments().First(), 0); + return Expression.Constant(array, t); + } + + if (t.CanBeNull()) + { + if (Nullable.GetUnderlyingType(t) is not null) + return Expression.Constant(t.GetConstructor(BindingFlags.CreateInstance, Array.Empty())!.Invoke(null, null), t); + + return Expression.Constant(null, t); + } + + throw new NotImplementedException(); + } + + // IEnumerable ^ IEnumerable -> EntityUid + public static Type Intersect(this Type left, Type right) + { + if (!left.IsGenericType) + return left; + + if (!right.IsGenericType) + return left; + + return left.GetGenericArguments().First(); + } + + public static void DumpGenericInfo(this Type t) + { + Logger.Debug($"Info for {t.PrettyName()}"); + Logger.Debug( + $"GP {t.IsGenericParameter} | MP {t.IsGenericMethodParameter} | TP {t.IsGenericTypeParameter} | DEF {t.IsGenericTypeDefinition} | TY {t.IsGenericType} | CON {t.IsConstructedGenericType}"); + if (t.IsGenericParameter) + Logger.Debug($"CONSTRAINTS: {string.Join(", ", t.GetGenericParameterConstraints().Select(PrettyName))}"); + if (!t.IsGenericTypeDefinition && IsGenericRelated(t) && t.IsGenericType) + DumpGenericInfo(t.GetGenericTypeDefinition()); + foreach (var p in t.GetGenericArguments()) + { + DumpGenericInfo(p); + } + } + + + + public static bool IsAssignableToGeneric(this Type left, Type right, ToolshedManager toolshed, bool recursiveDescent = true) + { + if (left.IsAssignableTo(right)) + return true; + + if (right.IsInterface) + { + foreach (var i in left.GetInterfaces()) + { + if (left.IsAssignableToGeneric(i, toolshed, recursiveDescent)) + return true; + } + } + + if (left.Constructable() && right.IsGenericParameter) + { + var constraints = right.GetGenericParameterConstraints(); + foreach (var t in constraints) + { + if (!left.IsAssignableToGeneric(t, toolshed, recursiveDescent)) + return false; + } + + return true; + } + + if (left.IsGenericType && right.IsGenericType && left.GenericTypeArguments.Length == right.GenericTypeArguments.Length) + { + var equal = left.GetGenericTypeDefinition() == right.GetGenericTypeDefinition(); + + if (!equal) + goto next; + + var res = true; + foreach (var (leftTy, rightTy) in left.GenericTypeArguments.Zip(right.GenericTypeArguments)) + { + res &= leftTy.IsAssignableToGeneric(rightTy, toolshed, false); + } + + return res; + } + + next: + if (recursiveDescent) + { + foreach (var leftSubTy in toolshed.AllSteppedTypes(left)) + { + if (leftSubTy.IsAssignableToGeneric(right, toolshed, false)) + { + return true; + } + } + } + + return false; + } + + public static bool IsGenericRelated(this Type t) + { + return t.IsGenericParameter | t.IsGenericType | t.IsGenericMethodParameter | t.IsGenericTypeDefinition | t.IsConstructedGenericType | t.IsGenericTypeParameter; + } + + public static bool Constructable(this Type t) + { + if (!IsGenericRelated(t)) + return true; + + if (!t.IsGenericType) + return false; + + var r = true; + + foreach (var arg in t.GetGenericArguments()) + { + r &= Constructable(arg); + } + + return r; + } + + public static PropertyInfo? FindIndexerProperty( + this Type type) + { + var defaultPropertyAttribute = type.GetCustomAttributes().FirstOrDefault(); + + return defaultPropertyAttribute == null + ? null + : type.GetRuntimeProperties() + .FirstOrDefault( + pi => + pi.Name == defaultPropertyAttribute.MemberName + && pi.IsIndexerProperty() + && pi.SetMethod?.GetParameters() is { } parameters + && parameters.Length == 2 + && parameters[0].ParameterType == typeof(string)); + } + + public static bool IsIndexerProperty(this PropertyInfo propertyInfo) + { + var indexParams = propertyInfo.GetIndexParameters(); + return indexParams.Length == 1 + && indexParams[0].ParameterType == typeof(string); + } +} diff --git a/Robust.Shared/Toolshed/Syntax/Block.cs b/Robust.Shared/Toolshed/Syntax/Block.cs new file mode 100644 index 000000000..6577c60f0 --- /dev/null +++ b/Robust.Shared/Toolshed/Syntax/Block.cs @@ -0,0 +1,155 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Syntax; + +public sealed class Block +{ + internal CommandRun CommandRun { get; set; } + + public static bool TryParse( + bool doAutoComplete, + ForwardParser parser, + Type? pipedType, + [NotNullWhen(true)] out Block? block, + out ValueTask<(CompletionResult?, IConError?)>? autoComplete, + out IConError? error + ) + { + parser.Consume(char.IsWhiteSpace); + + var enclosed = parser.EatMatch('{'); + + CommandRun.TryParse(enclosed, doAutoComplete, parser, pipedType, null, !enclosed, out var expr, out autoComplete, out error); + + if (expr is null) + { + block = null; + return false; + } + + if (enclosed && !parser.EatMatch('}')) + { + error = new MissingClosingBrace(); + block = null; + return false; + } + + block = new Block(expr); + return true; + } + + public Block(CommandRun expr) + { + CommandRun = expr; + } + + public object? Invoke(object? input, IInvocationContext ctx) + { + return CommandRun.Invoke(input, ctx); + } +} + +/// +/// Something more akin to actual expressions. +/// +public sealed class Block +{ + internal CommandRun CommandRun { get; set; } + + public static bool TryParse(bool doAutoComplete, ForwardParser parser, Type? pipedType, + [NotNullWhen(true)] out Block? block, out ValueTask<(CompletionResult?, IConError?)>? autoComplete, out IConError? error) + { + parser.Consume(char.IsWhiteSpace); + + var enclosed = parser.EatMatch('{'); + + CommandRun.TryParse(enclosed, doAutoComplete, parser, pipedType, !enclosed, out var expr, out autoComplete, out error); + + if (expr is null) + { + block = null; + return false; + } + + if (enclosed && !parser.EatMatch('}')) + { + error = new MissingClosingBrace(); + block = null; + return false; + } + + block = new Block(expr); + return true; + } + + public Block(CommandRun expr) + { + CommandRun = expr; + } + + public T? Invoke(object? input, IInvocationContext ctx) + { + return CommandRun.Invoke(input, ctx); + } +} + +public sealed class Block +{ + internal CommandRun CommandRun { get; set; } + + public static bool TryParse(bool doAutoComplete, ForwardParser parser, Type? pipedType, + [NotNullWhen(true)] out Block? block, out ValueTask<(CompletionResult?, IConError?)>? autoComplete, out IConError? error) + { + parser.Consume(char.IsWhiteSpace); + + var enclosed = parser.EatMatch('{'); + + CommandRun.TryParse(enclosed, doAutoComplete, parser, !enclosed, out var expr, out autoComplete, out error); + + if (expr is null) + { + block = null; + return false; + } + + if (enclosed && !parser.EatMatch('}')) + { + error = new MissingClosingBrace(); + block = null; + return false; + } + + block = new Block(expr); + return true; + } + + public Block(CommandRun expr) + { + CommandRun = expr; + } + + public TOut? Invoke(object? input, IInvocationContext ctx) + { + return CommandRun.Invoke(input, ctx); + } +} + + +public record struct MissingClosingBrace() : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup("Expected a closing brace."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/Syntax/Expression.cs b/Robust.Shared/Toolshed/Syntax/Expression.cs new file mode 100644 index 000000000..1ce63e566 --- /dev/null +++ b/Robust.Shared/Toolshed/Syntax/Expression.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Syntax; + +/// +/// A "run" of commands. Not a true expression. +/// +public sealed class CommandRun +{ + public readonly List<(ParsedCommand, Vector2i)> Commands; + private readonly string _originalExpr; + + public static bool TryParse( + bool blockMode, + bool doAutocomplete, + ForwardParser parser, + Type? pipedType, + Type? targetOutput, + bool once, + [NotNullWhen(true)] out CommandRun? expr, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete, + out IConError? error + ) + { + autocomplete = null; + error = null; + var cmds = new List<(ParsedCommand, Vector2i)>(); + var start = parser.Index; + var noCommand = false; + parser.Consume(char.IsWhiteSpace); + + while ((!once || cmds.Count < 1) && ParsedCommand.TryParse(doAutocomplete, parser, pipedType, out var cmd, out error, out noCommand, out autocomplete, targetOutput)) + { + var end = parser.Index; + pipedType = cmd.ReturnType; + cmds.Add((cmd, (start, end))); + parser.Consume(char.IsWhiteSpace); + start = parser.Index; + + if (blockMode && parser.PeekChar() == '}') + break; + } + + if (error is OutOfInputError && noCommand) + error = null; + + if (error is not null and not OutOfInputError || error is OutOfInputError && !noCommand || cmds.Count == 0) + { + expr = null; + return false; + } + + if (!(cmds.Last().Item1.ReturnType?.IsAssignableTo(targetOutput) ?? false) && targetOutput is not null) + { + error = new ExpressionOfWrongType(targetOutput, cmds.Last().Item1.ReturnType!, once); + expr = null; + return false; + } + + expr = new CommandRun(cmds, parser.Input); + return true; + } + + public object? Invoke(object? input, IInvocationContext ctx, bool reportErrors = true) + { + var ret = input; + foreach (var (cmd, span) in Commands) + { + ret = cmd.Invoke(ret, ctx); + if (ctx.GetErrors().Any()) + { + // Got an error, we need to report it and break out. + foreach (var err in ctx.GetErrors()) + { + err.Contextualize(_originalExpr, span); + ctx.WriteLine(err.Describe()); + } + + return null; + } + } + + return ret; + } + + + private CommandRun(List<(ParsedCommand, Vector2i)> commands, string originalExpr) + { + Commands = commands; + _originalExpr = originalExpr; + } +} + +public sealed class CommandRun +{ + internal readonly CommandRun InnerCommandRun; + + public static bool TryParse(bool blockMode, bool doAutoComplete, ForwardParser parser, bool once, + [NotNullWhen(true)] out CommandRun? expr, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete, out IConError? error) + { + if (!CommandRun.TryParse(blockMode, doAutoComplete, parser, typeof(TIn), typeof(TOut), once, out var innerExpr, out autocomplete, out error)) + { + expr = null; + return false; + } + + expr = new CommandRun(innerExpr); + return true; + } + + public TOut? Invoke(object? input, IInvocationContext ctx) + { + var res = InnerCommandRun.Invoke(input, ctx); + if (res is null) + return default; + return (TOut?) res; + } + + private CommandRun(CommandRun commandRun) + { + InnerCommandRun = commandRun; + } +} + +public sealed class CommandRun +{ + internal readonly CommandRun _innerCommandRun; + + public static bool TryParse(bool blockMode, bool doAutoComplete, ForwardParser parser, Type? pipedType, bool once, + [NotNullWhen(true)] out CommandRun? expr, out ValueTask<(CompletionResult?, IConError?)>? completion, + out IConError? error) + { + if (!CommandRun.TryParse(blockMode, doAutoComplete, parser, pipedType, typeof(TRes), once, out var innerExpr, out completion, out error)) + { + expr = null; + return false; + } + + expr = new CommandRun(innerExpr); + return true; + } + + public TRes? Invoke(object? input, IInvocationContext ctx) + { + var res = _innerCommandRun.Invoke(input, ctx); + if (res is null) + return default; + return (TRes?) res; + } + + private CommandRun(CommandRun commandRun) + { + _innerCommandRun = commandRun; + } +} + +public record struct ExpressionOfWrongType(Type Expected, Type Got, bool Once) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = FormattedMessage.FromMarkup( + $"Expected an expression of type {Expected.PrettyName()}, but got {Got.PrettyName()}"); + + if (Once) + { + msg.PushNewline(); + msg.AddText("Note: A single command is expected here, if you were trying to chain commands please surround the run with { } to form a block."); + } + + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs b/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs new file mode 100644 index 000000000..c9dc24e8c --- /dev/null +++ b/Robust.Shared/Toolshed/Syntax/ParsedCommand.cs @@ -0,0 +1,329 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Syntax; + +using Invocable = Func; + +public sealed class ParsedCommand +{ + public ToolshedCommand Command { get; } + public Type? ReturnType { get; } + + public Type? PipedType => Bundle.PipedArgumentType; + internal Invocable Invocable { get; } + private CommandArgumentBundle Bundle { get; } + public string? SubCommand { get; } + + public static bool TryParse( + bool doAutoComplete, + ForwardParser parser, + Type? pipedArgumentType, + [NotNullWhen(true)] out ParsedCommand? result, + out IConError? error, + out bool noCommand, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete, + Type? targetType = null + ) + { + noCommand = false; + var checkpoint = parser.Save(); + var bundle = new CommandArgumentBundle() + {Arguments = new(), Inverted = false, PipedArgumentType = pipedArgumentType, TypeArguments = Array.Empty()}; + + autocomplete = null; + if (!TryDigestModifiers(parser, bundle, out error) + || !TryParseCommand(doAutoComplete, parser, bundle, pipedArgumentType, targetType, out var subCommand, out var invocable, out var command, out error, out noCommand, out autocomplete) + || !command.TryGetReturnType(subCommand, pipedArgumentType, bundle.TypeArguments, out var retType) + ) + { + result = null; + parser.Restore(checkpoint); + return false; + } + + + result = new(bundle, invocable, command, retType, subCommand); + return true; + } + + private ParsedCommand(CommandArgumentBundle bundle, Invocable invocable, ToolshedCommand command, Type? returnType, string? subCommand) + { + Invocable = invocable; + Bundle = bundle; + Command = command; + ReturnType = returnType; + SubCommand = subCommand; + } + + private static bool TryDigestModifiers(ForwardParser parser, CommandArgumentBundle bundle, out IConError? error) + { + error = null; + if (parser.PeekWord() == "not") + { + parser.GetWord(); //yum + bundle.Inverted = true; + } + + return true; + } + + private static bool TryParseCommand( + bool makeCompletions, + ForwardParser parser, + CommandArgumentBundle bundle, + Type? pipedType, + Type? targetType, + out string? subCommand, + [NotNullWhen(true)] out Invocable? invocable, + [NotNullWhen(true)] out ToolshedCommand? command, + out IConError? error, + out bool noCommand, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete + ) + { + noCommand = false; + var start = parser.Index; + var cmd = parser.GetWord(c => c is not ':' and not '{' and not '}' && !char.IsWhiteSpace(c)); + subCommand = null; + invocable = null; + command = null; + if (cmd is null) + { + if (parser.PeekChar() is null) + { + noCommand = true; + error = new OutOfInputError(); + error.Contextualize(parser.Input, (parser.Index, parser.Index)); + autocomplete = null; + if (makeCompletions) + { + var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void)); + autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), ""), error)); + } + + return false; + } + else + { + + noCommand = true; + error = new NotValidCommandError(targetType); + error.Contextualize(parser.Input, (start, parser.Index)); + autocomplete = null; + return false; + } + } + + if (!parser.Toolshed.TryGetCommand(cmd, out var cmdImpl)) + { + error = new UnknownCommandError(cmd); + error.Contextualize(parser.Input, (start, parser.Index)); + autocomplete = null; + if (makeCompletions) + { + var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void)); + autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), ""), error)); + } + + return false; + } + + if (cmdImpl.HasSubCommands) + { + error = null; + autocomplete = null; + if (makeCompletions) + { + var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void)).Where(x => x.Cmd.Name == cmd); + autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>(( + CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), ""), error)); + } + + if (parser.GetChar() is not ':') + { + error = new OutOfInputError(); + error.Contextualize(parser.Input, (parser.Index, parser.Index)); + return false; + } + + var subCmdStart = parser.Index; + + if (parser.GetWord() is not { } subcmd) + { + error = new OutOfInputError(); + error.Contextualize(parser.Input, (parser.Index, parser.Index)); + return false; + } + + if (!cmdImpl.Subcommands.Contains(subcmd)) + { + error = new UnknownSubcommandError(cmd, subcmd, cmdImpl); + error.Contextualize(parser.Input, (subCmdStart, parser.Index)); + return false; + } + + subCommand = subcmd; + } + + if (parser.Consume(char.IsWhiteSpace) == 0 && makeCompletions) + { + error = null; + var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void)); + autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), ""), null)); + return false; + } + + var argsStart = parser.Index; + + if (!cmdImpl.TryParseArguments(makeCompletions, parser, pipedType, subCommand, out var args, out var types, out error, out autocomplete)) + { + error?.Contextualize(parser.Input, (argsStart, parser.Index)); + return false; + } + + bundle.TypeArguments = types; + + if (!cmdImpl.TryGetImplementation(bundle.PipedArgumentType, subCommand, types, out var impl)) + { + error = new NoImplementationError(cmd, types, subCommand, bundle.PipedArgumentType); + error.Contextualize(parser.Input, (start, parser.Index)); + autocomplete = null; + return false; + } + + bundle.Arguments = args; + invocable = impl; + command = cmdImpl; + autocomplete = null; + return true; + } + + private bool _passedInvokeTest = false; + + public object? Invoke(object? pipedIn, IInvocationContext ctx) + { + if (!_passedInvokeTest && !ctx.CheckInvokable(new CommandSpec(Command, SubCommand), out var error)) + { + // Could not invoke the command for whatever reason, i.e. permission errors. + if (error is not null) + ctx.ReportError(error); + return null; + } + + // TODO: This optimization might be dangerous if blocks can be passed to other people through vars. + // Or not if it can only be done deliberately, but social engineering is a thing. + _passedInvokeTest = true; + + try + { + return Invocable.Invoke(new CommandInvocationArguments() + {Bundle = Bundle, PipedArgument = pipedIn, Context = ctx}); + } + catch (Exception e) + { + ctx.ReportError(new UnhandledExceptionError(e)); + return null; + } + } +} + +public record struct UnknownCommandError(string Cmd) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"Got unknown command {Cmd}."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct NoImplementationError(string Cmd, Type[] Types, string? SubCommand, Type? PipedType) : IConError +{ + public FormattedMessage DescribeInner() + { + var newCon = IoCManager.Resolve(); + + var msg = FormattedMessage.FromMarkup($"Could not find an implementation for {Cmd} given the input type {PipedType?.PrettyName() ?? "void"}."); + msg.PushNewline(); + + var typeArgs = ""; + + if (Types.Length != 0) + { + typeArgs = "<" + string.Join(",", Types.Select(ReflectionExtensions.PrettyName)) + ">"; + } + + msg.AddText($"Signature: {Cmd}{(SubCommand is not null ? $":{SubCommand}" : "")}{typeArgs} {PipedType?.PrettyName() ?? "void"} -> ???"); + + var piped = PipedType ?? typeof(void); + var cmdImpl = newCon.GetCommand(Cmd); + var accepted = cmdImpl.AcceptedTypes(SubCommand).ToHashSet(); + + foreach (var (command, subCommand) in newCon.CommandsTakingType(piped)) + { + if (!command.TryGetReturnType(subCommand, piped, Array.Empty(), out var retType) || !accepted.Any(x => retType.IsAssignableTo(x))) + continue; + + if (!cmdImpl.TryGetReturnType(SubCommand, retType, Types, out var myRetType)) + continue; + + msg.PushNewline(); + msg.AddText($"The command {command.Name}{(subCommand is not null ? $":{subCommand}" : "")} can convert from {piped.PrettyName()} to {retType.PrettyName()}."); + msg.PushNewline(); + msg.AddText($"With this fix, the new signature will be: {Cmd}{(SubCommand is not null ? $":{SubCommand}" : "")}{typeArgs} {retType?.PrettyName() ?? "void"} -> {myRetType?.PrettyName() ?? "void"}."); + } + + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct UnknownSubcommandError(string Cmd, string SubCmd, ToolshedCommand Command) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText($"The command group {Cmd} doesn't have command {SubCmd}."); + msg.PushNewline(); + msg.AddText($"The valid commands are: {string.Join(", ", Command.Subcommands)}."); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct NotValidCommandError(Type? TargetType) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText("Ran into an invalid command, could not parse."); + if (TargetType is not null && TargetType != typeof(void)) + { + msg.PushNewline(); + msg.AddText($"The parser was trying to obtain a run of type {TargetType.PrettyName()}, make sure your run actually returns that value."); + } + + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/Syntax/ValueRef.cs b/Robust.Shared/Toolshed/Syntax/ValueRef.cs new file mode 100644 index 000000000..b1643dcc7 --- /dev/null +++ b/Robust.Shared/Toolshed/Syntax/ValueRef.cs @@ -0,0 +1,106 @@ +using System; +using System.Diagnostics; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.Syntax; + +public sealed class ValueRef : ValueRef +{ + public ValueRef(ValueRef inner) + { + InnerBlock = inner.InnerBlock; + VarName = inner.VarName; + HasValue = inner.HasValue; + Value = inner.Value; + Expression = inner.Expression; + RefSpan = inner.RefSpan; + } + public ValueRef(string varName) : base(varName) + { + } + + public ValueRef(Block innerBlock) : base(innerBlock) + { + } + + public ValueRef(T value) : base(value) + { + } +} + +[Virtual] +public class ValueRef +{ + internal Block? InnerBlock; + internal string? VarName; + internal bool HasValue = false; + internal T? Value; + internal string? Expression; + internal Vector2i? RefSpan; + + protected ValueRef() + { + } + + public ValueRef(string varName) + { + VarName = varName; + } + + public ValueRef(Block innerBlock) + { + InnerBlock = innerBlock; + } + + public ValueRef(T value) + { + Value = value; + HasValue = true; + } + + public bool LikelyConst => VarName is not null || HasValue; + + + public T? Evaluate(IInvocationContext ctx) + { + if (Value is not null && HasValue) + { + return Value; + } + else if (VarName is not null) + { + return (T?)ctx.ReadVar(VarName); + } + else if (InnerBlock is not null) + { + return InnerBlock.Invoke(null, ctx); + } + else + { + throw new UnreachableException(); + } + } + + public void Set(IInvocationContext ctx, T? value) + { + if (VarName is null) + throw new NotImplementedException(); + + ctx.WriteVar(VarName!, value); + } +} + +public record struct BadVarTypeError(Type Got, Type Expected, string VarName) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup( + $"Got unexpected type {Got.PrettyName()} in {VarName}, expected {Expected.PrettyName()}"); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs b/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs new file mode 100644 index 000000000..1c4afeb46 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedCommand.Entities.cs @@ -0,0 +1,113 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Toolshed; + +public abstract partial class ToolshedCommand +{ + [PublicAPI, IoC.Dependency] + protected readonly IEntityManager EntityManager = default!; + + [PublicAPI, IoC.Dependency] + protected readonly IEntitySystemManager EntitySystemManager = default!; + + /// + /// A shorthand for retrieving for an entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected MetaDataComponent MetaData(EntityUid entity) + => EntityManager.GetComponent(entity); + + /// + /// A shorthand for retrieving for an entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected TransformComponent Transform(EntityUid entity) + => EntityManager.GetComponent(entity); + + /// + /// A shorthand for retrieving an entity's name. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected string EntName(EntityUid entity) + => EntityManager.GetComponent(entity).EntityName; + + /// + /// A shorthand for deleting an entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void Del(EntityUid entityUid) + => EntityManager.DeleteEntity(entityUid); + + /// + /// A shorthand for checking if an entity is deleted or otherwise non-existant. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool Deleted(EntityUid entity) + => EntityManager.Deleted(entity); + + /// + /// A shorthand for retrieving the given component for an entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T Comp(EntityUid entity) + where T: IComponent + => EntityManager.GetComponent(entity); + + /// + /// A shorthand for checking if an entity has the given component. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool HasComp(EntityUid entityUid) + where T: IComponent + => EntityManager.HasComponent(entityUid); + + /// + /// A shorthand for attempting to retrieve the given component for an entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool TryComp(EntityUid? entity, [NotNullWhen(true)] out T? component) + where T: IComponent + => EntityManager.TryGetComponent(entity, out component); + + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool TryComp(EntityUid entity, [NotNullWhen(true)] out T? component) + where T: IComponent + => EntityManager.TryGetComponent(entity, out component); + + /// + /// A shorthand for adding a component to the given entity. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T AddComp(EntityUid entity) + where T : Component, new() + => EntityManager.AddComponent(entity); + + /// + /// A shorthand for ensuring an entity has the given component. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T EnsureComp(EntityUid entity) + where T: Component, new() + => EntityManager.EnsureComponent(entity); + + /// + /// A shorthand for retrieving an entity system. + /// + /// This may be replaced with some form of dependency attribute in the future. + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T GetSys() + where T: EntitySystem + => EntitySystemManager.GetEntitySystem(); + + /// + /// A shorthand for retrieving an entity query. + /// + [PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityQuery GetEntityQuery() + where T : Component + => EntityManager.GetEntityQuery(); +} diff --git a/Robust.Shared/Toolshed/ToolshedCommand.Help.cs b/Robust.Shared/Toolshed/ToolshedCommand.Help.cs new file mode 100644 index 000000000..685202597 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedCommand.Help.cs @@ -0,0 +1,45 @@ +using System.Linq; +using Robust.Shared.Localization; + +namespace Robust.Shared.Toolshed; + +public abstract partial class ToolshedCommand +{ + /// + /// Returns a command's localized description. + /// + public string Description(string? subCommand) + => Loc.GetString(UnlocalizedDescription(subCommand)); + + /// + /// Returns the locale string for a command's description. + /// + public string UnlocalizedDescription(string? subCommand) + { + if (Name.All(char.IsAsciiLetterOrDigit)) + { + return $"command-description-{Name}" + (subCommand is not null ? $"-{subCommand}" : ""); + } + else + { + return $"command-description-{GetType().PrettyName()}" + (subCommand is not null ? $"-{subCommand}" : ""); + } + } + + /// + /// Returns a command's help string. + /// + public string GetHelp(string? subCommand) + { + if (subCommand is null) + return $"{Name}: {Description(null)}"; + else + return $"{Name}:{subCommand}: {Description(subCommand)}"; + } + + /// + public override string ToString() + { + return GetHelp(null); + } +} diff --git a/Robust.Shared/Toolshed/ToolshedCommand.Implementations.cs b/Robust.Shared/Toolshed/ToolshedCommand.Implementations.cs new file mode 100644 index 000000000..7b6d8526d --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedCommand.Implementations.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Robust.Shared.Log; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +public abstract partial class ToolshedCommand +{ + private readonly Dictionary _implementors = new(); + private readonly Dictionary<(CommandDiscriminator, string?), List> _concreteImplementations = new(); + + public bool TryGetReturnType(string? subCommand, Type? pipedType, Type[] typeArguments, + [NotNullWhen(true)] out Type? type) + { + var impls = GetConcreteImplementations(pipedType, typeArguments, subCommand).ToList(); + + if (impls.Count > 0) + { + type = impls.First().ReturnType; + return true; + } + + type = null; + return false; + } + + internal List GetConcreteImplementations(Type? pipedType, Type[] typeArguments, + string? subCommand) + { + var idx = (new CommandDiscriminator(pipedType, typeArguments), subCommand); + if (_concreteImplementations.TryGetValue(idx, + out var impl)) + { + return impl; + } + + impl = GetConcreteImplementationsInternal(pipedType, typeArguments, subCommand); + if (impl.Count == 0 && pipedType is not null && pipedType != typeof(void)) + impl = GetConcreteImplementationsInternal(typeof(IEnumerable<>).MakeGenericType(pipedType), typeArguments, subCommand); + _concreteImplementations[idx] = impl; + return impl; + } + + private List GetConcreteImplementationsInternal(Type? pipedType, Type[] typeArguments, + string? subCommand) + { + var impls = GetGenericImplementations() + .Where(x => + { + if (x.ConsoleGetPipedArgument() is { } param) + { + return pipedType?.IsAssignableToGeneric(param.ParameterType, Toolshed) ?? false; + } + + return pipedType is null; + }) + .OrderByDescending(x => + { + if (x.ConsoleGetPipedArgument() is { } param) + { + return param.ParameterType.IsGenericType; + } + + return false; + }) + .Where(x => x.GetCustomAttribute()?.SubCommand == subCommand) + .Where(x => + { + if (x.IsGenericMethodDefinition) + { + var expectedLen = x.GetGenericArguments().Length; + if (x.HasCustomAttribute()) + expectedLen -= 1; + + return typeArguments.Length == expectedLen; + } + + return typeArguments.Length == 0; + }) + .Select(x => + { + if (x.IsGenericMethodDefinition) + { + if (x.HasCustomAttribute()) + { + var paramT = x.ConsoleGetPipedArgument()!.ParameterType; + var t = pipedType!.Intersect(paramT); + return x.MakeGenericMethod(typeArguments.Append(t).ToArray()); + } + else + return x.MakeGenericMethod(typeArguments); + } + + return x; + }).ToList(); + + return impls; + } + + internal List GetGenericImplementations() + { + var t = GetType(); + + var methods = t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | + BindingFlags.Instance); + + return methods.Where(x => x.HasCustomAttribute()).ToList(); + } + + internal bool TryGetImplementation(Type? pipedType, string? subCommand, Type[] typeArguments, + [NotNullWhen(true)] out Func? impl) + { + return _implementors[subCommand ?? ""].TryGetImplementation(pipedType, typeArguments, out impl); + } +} diff --git a/Robust.Shared/Toolshed/ToolshedCommand.cs b/Robust.Shared/Toolshed/ToolshedCommand.cs new file mode 100644 index 000000000..1878a4df6 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedCommand.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Reflection; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.TypeParsers; + +namespace Robust.Shared.Toolshed; + +/// +/// This class is used for implementing new commands in Toolshed. +/// +/// +/// Toolshed's code generation will automatically handle creating command executor stubs, you don't need to override anything. +/// +/// +/// [ToolshedCommand] +/// public sealed class ExampleCommand : ToolshedCommand +/// { +/// // Toolshed will automatically infer autocompletion information, type information, and parsing. +/// [CommandImplementation] +/// public IEnumerable<EntityUid> Example( +/// [PipedArgument] IEnumerable<EntityUid> input, +/// [CommandArgument] int amount +/// ) +/// { +/// return input.Take(amount); +/// } +/// } +/// +/// +/// +/// +/// +/// +/// +/// +/// +[Reflect(false)] +public abstract partial class ToolshedCommand +{ + [Dependency] protected readonly ToolshedManager Toolshed = default!; + + /// + /// The user-facing name of the command. + /// + /// This is automatically generated based on the type name unless overridden with . + public string Name { get; } + + /// + /// Whether or not this command has subcommands. + /// + public bool HasSubCommands { get; } + + /// + /// The additional type parameters of this command, specifically which parsers to use. + /// + /// Every type specified must either be itself or something implementing where T is Type. + public virtual Type[] TypeParameterParsers => Array.Empty(); + + internal bool HasTypeParameters => TypeParameterParsers.Length != 0; + + /// + /// The list of all subcommands on this command. + /// + public IEnumerable Subcommands => _implementors.Keys.Where(x => x != ""); + + protected ToolshedCommand() + { + var name = GetType().GetCustomAttribute()!.Name; + + if (name is null) + { + var typeName = GetType().Name; + const string commandStr = "Command"; + + if (!typeName.EndsWith(commandStr)) + { + throw new InvalidComponentNameException($"Component {GetType()} must end with the word Component"); + } + + name = typeName[..^commandStr.Length].ToLowerInvariant(); + } + + Name = name; + HasSubCommands = false; + _implementors[""] = + new ToolshedCommandImplementor + { + Owner = this, + SubCommand = null + }; + + var impls = GetGenericImplementations(); + Dictionary> parameters = new(); + + foreach (var impl in impls) + { + var myParams = new SortedDictionary(); + string? subCmd = null; + if (impl.GetCustomAttribute() is {SubCommand: { } x}) + { + subCmd = x; + HasSubCommands = true; + _implementors[x] = + new ToolshedCommandImplementor + { + Owner = this, + SubCommand = x + }; + } + + foreach (var param in impl.GetParameters()) + { + if (param.GetCustomAttribute() is not null) + { + if (parameters.ContainsKey(param.Name!)) + continue; + + myParams.Add(param.Name!, param.ParameterType); + } + } + + if (parameters.TryGetValue(subCmd ?? "", out var existing)) + { + if (!existing.SequenceEqual(existing)) + { + throw new NotImplementedException("All command implementations of a given subcommand must share the same parameters!"); + } + } + else + parameters.Add(subCmd ?? "", myParams); + + } + } + + internal IEnumerable AcceptedTypes(string? subCommand) + { + return GetGenericImplementations() + .Where(x => + x.ConsoleGetPipedArgument() is not null + && x.GetCustomAttribute()?.SubCommand == subCommand) + .Select(x => x.ConsoleGetPipedArgument()!.ParameterType); + } + + internal bool TryParseArguments( + bool doAutocomplete, + ForwardParser parser, + Type? pipedType, + string? subCommand, + [NotNullWhen(true)] out Dictionary? args, + out Type[] resolvedTypeArguments, + out IConError? error, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete + ) + { + return _implementors[subCommand ?? ""].TryParseArguments(doAutocomplete, parser, subCommand, pipedType, out args, out resolvedTypeArguments, out error, out autocomplete); + } +} + +internal sealed class CommandInvocationArguments +{ + public required object? PipedArgument; + public required IInvocationContext Context { get; set; } + public required CommandArgumentBundle Bundle; + public Dictionary Arguments => Bundle.Arguments; + public bool Inverted => Bundle.Inverted; + public Type? PipedArgumentType => Bundle.PipedArgumentType; +} + +internal sealed class CommandArgumentBundle +{ + public required Dictionary Arguments; + public required bool Inverted = false; + public required Type? PipedArgumentType; + public required Type[] TypeArguments; +} + +internal readonly record struct CommandDiscriminator(Type? PipedType, Type[] TypeArguments) : IEquatable +{ + public bool Equals(CommandDiscriminator? other) + { + if (other is not {} value) + return false; + + return value.PipedType == PipedType && value.TypeArguments.SequenceEqual(TypeArguments); + } + + public override int GetHashCode() + { + // poor man's hash do not judge + var h = PipedType?.GetHashCode() ?? (int.MaxValue / 3); + foreach (var arg in TypeArguments) + { + h += h ^ arg.GetHashCode(); + int.RotateLeft(h, 3); + } + + return h; + } +} diff --git a/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs b/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs new file mode 100644 index 000000000..6a720372d --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedCommandImplementor.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; +using Robust.Shared.Toolshed.TypeParsers; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +internal sealed class ToolshedCommandImplementor +{ + [Dependency] private readonly ToolshedManager _toolshedManager = default!; + public required ToolshedCommand Owner; + + public required string? SubCommand; + + public Dictionary> Implementations = new(); + + public ToolshedCommandImplementor() + { + IoCManager.InjectDependencies(this); + } + + /// + /// You who tread upon this dreaded land, what is it that brings you here? + /// For this place is not for you, this land of terror and death. + /// It brings fear to all who tread within, terror to the homes ahead. + /// Begone, foul maintainer, for this place is not for thee. + /// + public bool TryParseArguments( + bool doAutocomplete, + ForwardParser parser, + string? subCommand, + Type? pipedType, + [NotNullWhen(true)] out Dictionary? args, + out Type[] resolvedTypeArguments, + out IConError? error, + out ValueTask<(CompletionResult?, IConError?)>? autocomplete + ) + { + resolvedTypeArguments = new Type[Owner.TypeParameterParsers.Length]; + + var firstStart = parser.Index; + + // HACK: This is for commands like Map until I have a better solution. + if (Owner.GetType().GetCustomAttribute() is {} mapLike) + { + var start = parser.Index; + // We do our own parsing, assuming this is some kind of map-like operation. + var chkpoint = parser.Save(); + DebugTools.AssertNotNull(pipedType); + DebugTools.Assert(pipedType?.IsGenericType ?? false); + DebugTools.Assert(pipedType?.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + if (!Block.TryParse(doAutocomplete, parser, mapLike.TakesPipedType ? pipedType.GetGenericArguments()[0] : null, out var block, out autocomplete, out error)) + { + error?.Contextualize(parser.Input, (start, parser.Index)); + resolvedTypeArguments = Array.Empty(); + args = null; + return false; + } + + resolvedTypeArguments[0] = block.CommandRun.Commands.Last().Item1.ReturnType!; + parser.Restore(chkpoint); + goto mapLikeDone; + } + + for (var i = 0; i < Owner.TypeParameterParsers.Length; i++) + { + var start = parser.Index; + var chkpoint = parser.Save(); + if (!_toolshedManager.TryParse(parser, Owner.TypeParameterParsers[i], out var parsed, out error) || parsed is not { } ty) + { + error?.Contextualize(parser.Input, (start, parser.Index)); + resolvedTypeArguments = Array.Empty(); + args = null; + autocomplete = null; + if (doAutocomplete) + { + parser.Restore(chkpoint); + autocomplete = _toolshedManager.TryAutocomplete(parser, Owner.TypeParameterParsers[i], null); + } + + return false; + } + + Type real; + if (ty is IAsType asTy) + { + real = asTy.AsType(); + } + else if (ty is Type realTy) + { + real = realTy; + } + else + { + throw new NotImplementedException(); + } + + resolvedTypeArguments[i] = real; + } + + mapLikeDone: + var impls = Owner.GetConcreteImplementations(pipedType, resolvedTypeArguments, subCommand); + if (impls.FirstOrDefault() is not { } impl) + { + args = null; + error = new NoImplementationError(Owner.Name, resolvedTypeArguments, subCommand, pipedType); + error.Contextualize(parser.Input, (firstStart, parser.Index)); + autocomplete = null; + return false; + } + + args = new(); + foreach (var argument in impl.ConsoleGetArguments()) + { + var start = parser.Index; + var chkpoint = parser.Save(); + if (!_toolshedManager.TryParse(parser, argument.ParameterType, out var parsed, out error)) + { + error?.Contextualize(parser.Input, (start, parser.Index)); + args = null; + autocomplete = null; + if (doAutocomplete) + { + parser.Restore(chkpoint); + autocomplete = _toolshedManager.TryAutocomplete(parser, argument.ParameterType, null); + } + return false; + } + args[argument.Name!] = parsed; + } + + error = null; + autocomplete = null; + return true; + } + + /// + /// Attempts to generate a callable shim for a command, aka it's implementation, using the given types. + /// + public bool TryGetImplementation(Type? pipedType, Type[] typeArguments, [NotNullWhen(true)] out Func? impl) + { + var discrim = new CommandDiscriminator(pipedType, typeArguments); + + if (Implementations.TryGetValue(discrim, out impl)) + return true; + + if (!Owner.TryGetReturnType(SubCommand, pipedType, typeArguments, out var ty)) + { + impl = null; + return false; + } + + // Okay we need to build a new shim. + + var possibleImpls = Owner.GetConcreteImplementations(pipedType, typeArguments, SubCommand); + + IEnumerable impls; + + if (pipedType is null) + { + impls = possibleImpls.Where(x => + x.ConsoleGetPipedArgument() is {} param && param.ParameterType.CanBeEmpty() + || x.ConsoleGetPipedArgument() is null + || x.GetParameters().Length == 0); + } + else + { + impls = possibleImpls.Where(x => + x.ConsoleGetPipedArgument() is {} param && _toolshedManager.IsTransformableTo(pipedType, param.ParameterType) + || x.IsGenericMethodDefinition); + } + + var implArray = impls.ToArray(); + if (implArray.Length == 0) + { + return false; + } + + var unshimmed = implArray.First(); + + var args = Expression.Parameter(typeof(CommandInvocationArguments)); + + var paramList = new List(); + + foreach (var param in unshimmed.GetParameters()) + { + if (param.GetCustomAttribute() is { } _) + { + if (pipedType is null) + { + paramList.Add(param.ParameterType.CreateEmptyExpr()); + } + else + { + // (ParameterType)(args.PipedArgument) + paramList.Add(_toolshedManager.GetTransformer(pipedType, param.ParameterType, Expression.Field(args, nameof(CommandInvocationArguments.PipedArgument)))); + } + + continue; + } + + if (param.GetCustomAttribute() is { } arg) + { + // (ParameterType)(args.Arguments[param.Name]) + paramList.Add(Expression.Convert( + Expression.MakeIndex( + Expression.Property(args, nameof(CommandInvocationArguments.Arguments)), + typeof(Dictionary).FindIndexerProperty(), + new [] {Expression.Constant(param.Name)}), + param.ParameterType)); + continue; + } + + if (param.GetCustomAttribute() is { } _) + { + // args.Inverted + paramList.Add(Expression.Property(args, nameof(CommandInvocationArguments.Inverted))); + continue; + } + + if (param.GetCustomAttribute() is { } _) + { + // args.Context + paramList.Add(Expression.Property(args, nameof(CommandInvocationArguments.Context))); + continue; + } + + } + + Expression partialShim = Expression.Call(Expression.Constant(Owner), unshimmed, paramList); + + if (unshimmed.ReturnType == typeof(void)) + partialShim = Expression.Block(partialShim, Expression.Constant(null)); + else if (ty is not null && ty.IsValueType) + partialShim = Expression.Convert(partialShim, typeof(object)); // Have to box primitives. + + var lambda = Expression.Lambda>(partialShim, args); + + Implementations[discrim] = lambda.Compile(); + impl = Implementations[discrim]; + return true; + } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs new file mode 100644 index 000000000..733b8f98d --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.Parsing.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.TypeParsers; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +public sealed partial class ToolshedManager +{ + private readonly Dictionary _consoleTypeParsers = new(); + private readonly Dictionary _genericTypeParsers = new(); + + private void InitializeParser() + { + var parsers = _reflection.GetAllChildren(); + + foreach (var parserType in parsers) + { + if (parserType.IsGenericType) + { + var t = parserType.BaseType!.GetGenericArguments().First(); + _genericTypeParsers.Add(t.GetGenericTypeDefinition(), parserType); + _log.Debug($"Setting up {parserType.PrettyName()}, {t.GetGenericTypeDefinition().PrettyName()}"); + } + else + { + var parser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(parserType); + parser.PostInject(); + _log.Debug($"Setting up {parserType.PrettyName()}, {parser.Parses.PrettyName()}"); + _consoleTypeParsers.Add(parser.Parses, parser); + } + } + } + + private ITypeParser? GetParserForType(Type t) + { + if (_consoleTypeParsers.TryGetValue(t, out var parser)) + return parser; + + + if (t.IsConstructedGenericType) + { + if (!_genericTypeParsers.TryGetValue(t.GetGenericTypeDefinition(), out var genParser)) + return null; + + var concreteParser = genParser.MakeGenericType(t.GenericTypeArguments); + + var builtParser = (ITypeParser) _typeFactory.CreateInstanceUnchecked(concreteParser, true); + builtParser.PostInject(); + _consoleTypeParsers.Add(builtParser.Parses, builtParser); + return builtParser; + } + + var baseTy = t.BaseType; + + if (baseTy is not null && baseTy != typeof(object) && baseTy != t.BaseType) + return GetParserForType(t); + + return null; + } + + /// + /// Attempts to parse the given type. + /// + /// The input to parse from. + /// The parsed value, if any. + /// A console error, if any, that can be reported to explain the parsing failure. + /// The type to parse from the input. + /// Success. + public bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? parsed, out IConError? error) + { + return TryParse(parser, typeof(T), out parsed, out error); + } + + /// + /// iunno man it does autocomplete what more do u want + /// + public ValueTask<(CompletionResult?, IConError?)> TryAutocomplete(ForwardParser parser, Type t, string? argName) + { + var impl = GetParserForType(t); + + if (impl is null) + { + return ValueTask.FromResult<(CompletionResult?, IConError?)>((null, new UnparseableValueError(t))); + } + + return impl.TryAutocomplete(parser, argName); + } + + /// + /// Attempts to parse the given type. + /// + /// The input to parse from. + /// The type to parse from the input. + /// The parsed value, if any. + /// A console error, if any, that can be reported to explain the parsing failure. + /// Success. + public bool TryParse(ForwardParser parser, Type t, [NotNullWhen(true)] out object? parsed, out IConError? error) + { + var impl = GetParserForType(t); + + if (impl is null) + { + parsed = null; + error = new UnparseableValueError(t); + return false; + } + + return impl.TryParse(parser, out parsed, out error); + } +} + +/// +/// Error that's given if a type cannot be parsed due to lack of parser. +/// +/// The type being parsed. +public record struct UnparseableValueError(Type T) : IConError +{ + public FormattedMessage DescribeInner() + { + + if (T.Constructable()) + { + var msg = FormattedMessage.FromMarkup( + $"The type {T.PrettyName()} has no parser available and cannot be parsed."); + msg.PushNewline(); + msg.AddText("Please contact a programmer with this error, they'd probably like to see it."); + msg.PushNewline(); + msg.AddMarkup("[bold][color=red]THIS IS A BUG.[/color][/bold]"); + return msg; + } + else + { + return FormattedMessage.FromMarkup($"The type {T.PrettyName()} cannot be parsed, as it cannot be constructed."); + } + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.Permissions.cs b/Robust.Shared/Toolshed/ToolshedManager.Permissions.cs new file mode 100644 index 000000000..26595e434 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.Permissions.cs @@ -0,0 +1,12 @@ +namespace Robust.Shared.Toolshed; + +public sealed partial class ToolshedManager +{ + /// + /// The active permission controller, if any. + /// + /// + /// Invocation contexts can entirely ignore this, though it's bad form to do so if they have a session on hand. + /// + public IPermissionController? ActivePermissionController { get; set; } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.PrettyPrint.cs b/Robust.Shared/Toolshed/ToolshedManager.PrettyPrint.cs new file mode 100644 index 000000000..a16b8391e --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.PrettyPrint.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +public sealed partial class ToolshedManager +{ + /// + /// Pretty prints a value for use in the console. + /// + /// Value to pretty print. + /// The stringified value. + /// + /// This returns markup. + /// + public string PrettyPrintType(object? value) + { + if (value is null) + return "null"; + + if (value is string str) + return str; + + if (value is FormattedMessage msg) + return msg.ToMarkup(); + + if (value is EntityUid uid) + { + return _entity.ToPrettyString(uid); + } + + if (value is Type t) + { + return t.PrettyName(); + } + + if (value.GetType().IsAssignableTo(typeof(IEnumerable))) + { + return string.Join(",\n", ((IEnumerable) value).Select(_entity.ToPrettyString)); + } + + if (value.GetType().IsAssignableTo(typeof(IEnumerable))) + { + return string.Join(",\n", ((IEnumerable) value).Cast().Select(PrettyPrintType)); + } + + if (value.GetType().IsAssignableTo(typeof(IDictionary))) + { + var dict = ((IDictionary) value).GetEnumerator(); + + var kvList = new List(); + + do + { + kvList.Add($"({PrettyPrintType(dict.Key)}, {PrettyPrintType(dict.Value)}"); + } while (dict.MoveNext()); + + return $"Dictionary {{\n{string.Join(",\n", kvList)}\n}}"; + } + + return value.ToString() ?? "[unrepresentable]"; + } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.Queries.cs b/Robust.Shared/Toolshed/ToolshedManager.Queries.cs new file mode 100644 index 000000000..819679da4 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.Queries.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Log; + +namespace Robust.Shared.Toolshed; + +// This is for information about commands that can be queried, i.e. return type possibilities. + +public sealed partial class ToolshedManager +{ + private readonly Dictionary> _commandPipeValueMap = new(); + private readonly Dictionary> _commandReturnValueMap = new(); + + private void InitializeQueries() + { + foreach (var (_, cmd) in _commands) + { + foreach (var (subcommand, methods) in cmd.GetGenericImplementations().BySubCommand()) + { + foreach (var method in methods) + { + var piped = method.ConsoleGetPipedArgument()?.ParameterType; + + if (piped is null) + piped = typeof(void); + + var list = GetTypeImplList(piped); + var invList = GetCommandRetValuesInternal(new CommandSpec(cmd, subcommand)); + list.Add(new CommandSpec(cmd, subcommand == "" ? null : subcommand)); + if (cmd.TryGetReturnType(subcommand, piped, Array.Empty(), out var retType) || method.ReturnType.Constructable()) + { + invList.Add((retType ?? method.ReturnType)); + } + } + } + } + } + + /// + /// Returns all commands that fit the given type constraints. + /// + /// Enumerable of matching command specs. + public IEnumerable CommandsFittingConstraint(Type input, Type output) + { + foreach (var (command, subcommand) in CommandsTakingType(input)) + { + if (command.HasTypeParameters) + continue; // We don't consider these right now. + + var impls = command.GetConcreteImplementations(input, Array.Empty(), subcommand); + + foreach (var impl in impls) + { + if (impl.ReturnType.IsAssignableTo(output)) + yield return new CommandSpec(command, subcommand); + } + } + } + + /// + /// Returns all commands that accept the given type. + /// + /// Type to use in the query. + /// Enumerable of matching command specs. + /// Not currently type constraint aware. + public IEnumerable CommandsTakingType(Type t) + { + var output = new Dictionary<(string, string?), CommandSpec>(); + foreach (var type in AllSteppedTypes(t)) + { + var list = GetTypeImplList(type); + if (type.IsGenericType) + { + list = list.Concat(GetTypeImplList(type.GetGenericTypeDefinition())).ToList(); + } + foreach (var entry in list) + { + output.TryAdd((entry.Cmd.Name, entry.SubCommand), entry); + } + } + + return output.Values; + } + + private Dictionary> _typeCache = new(); + + internal IEnumerable AllSteppedTypes(Type t, bool allowVariants = true) + { + if (_typeCache.TryGetValue(t, out var cache)) + return cache; + cache = new(AllSteppedTypesInner(t, allowVariants)); + _typeCache[t] = cache; + + return cache; + } + + private IEnumerable AllSteppedTypesInner(Type t, bool allowVariants) + { + Type oldT; + do + { + yield return t; + if (t == typeof(void)) + yield break; + + if (t.IsGenericType && allowVariants) + { + foreach (var variant in t.GetVariants(this)) + { + yield return variant; + } + } + + foreach (var @interface in t.GetInterfaces()) + { + foreach (var innerT in AllSteppedTypes(@interface, allowVariants)) + { + yield return innerT; + } + } + + if (t.BaseType is { } baseType) + { + foreach (var innerT in AllSteppedTypes(baseType, allowVariants)) + { + yield return innerT; + } + } + + yield return typeof(IEnumerable<>).MakeGenericType(t); + + oldT = t; + t = t.StepDownConstraints(); + } while (t != oldT); + } + + /// + /// Attempts to return the return values of the given command, if they can be decided. + /// + /// + /// Generics are flat out uncomputable so this doesn't bother. + /// + public IReadOnlySet GetCommandRetValues(CommandSpec command) + => GetCommandRetValuesInternal(command); + + private HashSet GetCommandRetValuesInternal(CommandSpec command) + { + if (!_commandReturnValueMap.TryGetValue(command, out var l)) + { + l = new(); + _commandReturnValueMap[command] = l; + } + + return l; + } + + private List GetTypeImplList(Type t) + { + if (!t.Constructable()) + { + if (t.IsGenericParameter) + { + var constraints = t.GetGenericParameterConstraints(); + + // for now be dumb. + if (constraints.Length > 0 && !constraints.First().IsConstructedGenericType) + return GetTypeImplList(constraints.First()); + return GetTypeImplList(typeof(object)); + } + + t = t.GetGenericTypeDefinition(); + } + + if (t.IsGenericType && !t.IsConstructedGenericType) + { + t = t.GetGenericTypeDefinition(); + } + + if (!_commandPipeValueMap.TryGetValue(t, out var l)) + { + l = new(); + _commandPipeValueMap[t] = l; + } + + return l; + } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.Types.cs b/Robust.Shared/Toolshed/ToolshedManager.Types.cs new file mode 100644 index 000000000..65eed8eb9 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.Types.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Robust.Shared.Toolshed.TypeParsers; + +namespace Robust.Shared.Toolshed; + +public sealed partial class ToolshedManager +{ + internal bool IsTransformableTo(Type left, Type right) + { + if (left.IsAssignableTo(right)) + return true; + + var asType = typeof(IAsType<>).MakeGenericType(right); + + if (left.GetInterfaces().Contains(asType)) + { + return true; + } + + if (right == typeof(object)) + return true; // May need boxed. + + if (right.IsGenericType && right.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + if (right.GenericTypeArguments[0] == left) + return true; + + return false; + } + + return false; + } + + internal Expression GetTransformer(Type from, Type to, Expression input) + { + if (!IsTransformableTo(from, to)) + throw new InvalidCastException(); + + if (from.IsAssignableTo(to)) + return Expression.Convert(input, to); + + var asType = typeof(IAsType<>).MakeGenericType(to); + + if (from.GetInterfaces().Contains(asType)) + { + // Just call astype 4head + return Expression.Convert( + Expression.Call(input, asType.GetMethod(nameof(IAsType.AsType))!), + to + ); + } + + if (to.IsGenericType && to.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + if (to.GenericTypeArguments[0] == from) + { + var tys = new [] {from}; + return Expression.Convert( + Expression.New( + typeof(UnitEnumerable<>).MakeGenericType(tys).GetConstructor(tys)!, + Expression.Convert(input, from) + ), + to + ); + } + } + + return Expression.Convert(input, to); + } +} + +internal sealed record UnitEnumerable(T Value) : IEnumerable +{ + internal record struct UnitEnumerator(T Value) : IEnumerator + { + private bool _taken = false; + + public bool MoveNext() + { + if (_taken) + return false; + _taken = true; + return true; + } + + public void Reset() + { + _taken = false; + } + + public T Current => Value; + + object IEnumerator.Current => Current!; + + public void Dispose() + { + } + } + + public IEnumerator GetEnumerator() + { + return new UnitEnumerator(Value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/Robust.Shared/Toolshed/ToolshedManager.cs b/Robust.Shared/Toolshed/ToolshedManager.cs new file mode 100644 index 000000000..221b4e130 --- /dev/null +++ b/Robust.Shared/Toolshed/ToolshedManager.cs @@ -0,0 +1,256 @@ +#pragma warning restore CS1591 + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Network; +using Robust.Shared.Players; +using Robust.Shared.Reflection; +using Robust.Shared.Timing; +using Robust.Shared.Toolshed.Invocation; +using Robust.Shared.Toolshed.Syntax; +using Robust.Shared.Toolshed.TypeParsers; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed; + +/// +/// The overarching controller for Toolshed, providing invocation, reflection, commands, parsing, and other tools used by the language. +/// External documentation has a more in-depth look. +/// +/// +/// +public sealed partial class ToolshedManager +{ + [Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!; + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly IReflectionManager _reflection = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly INetManager _net = default!; + + private ISawmill _log = default!; + + private readonly Dictionary _commands = new(); + + /// + /// If you're not an engine developer, you probably shouldn't call this. + /// + public void Initialize() + { +#if !CLIENT_SCRIPTING + if (_net.IsClient) + throw new NotImplementedException("Toolshed is not yet ready for client-side use."); +#endif + + _log = _logManager.GetSawmill("toolshed"); + var watch = new Stopwatch(); + watch.Start(); + + var tys = _reflection.FindTypesWithAttribute(); + foreach (var ty in tys) + { + if (!ty.IsAssignableTo(typeof(ToolshedCommand))) + { + _log.Error($"The type {ty.AssemblyQualifiedName} has {nameof(ToolshedCommandAttribute)} without being a child of {nameof(ToolshedCommand)}"); + continue; + } + + var command = (ToolshedCommand)Activator.CreateInstance(ty)!; + IoCManager.InjectDependencies(command); + + _commands.Add(command.Name, command); + } + + InitializeParser(); + InitializeQueries(); + + _log.Info($"Initialized console in {watch.Elapsed}"); + } + + /// + /// Provides every registered command, including subcommands. + /// + /// Enumerable of every command. + public IEnumerable AllCommands() + { + foreach (var (_, cmd) in _commands) + { + if (cmd.HasSubCommands) + { + foreach (var subcommand in cmd.Subcommands) + { + yield return new(cmd, subcommand); + } + } + else + { + yield return new(cmd, null); + } + } + } + + /// + /// Gets a command's object by name. + /// + /// Command to get. + /// A command object. + /// Thrown when there is no command of the given name. + public ToolshedCommand GetCommand(string commandName) => _commands[commandName]; + + /// + /// Attempts to get a command's object by name. + /// + /// Command to get. + /// The command obtained, if any. + /// Success. + public bool TryGetCommand(string commandName, [NotNullWhen(true)] out ToolshedCommand? command) + { + return _commands.TryGetValue(commandName, out command); + } + + private Dictionary _contexts = new(); + + /// + /// Invokes a command as the given user. + /// + /// User to run as. + /// Command to invoke. + /// An input value to use, if any. + /// The resulting value, if any. + /// Invocation success. + /// + /// ToolshedManager toolshed = ...; + /// ICommonSession ctx = ...; + /// // Now run some user provided command and get a result! + /// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result); + /// + /// + /// This will use the same IInvocationContext as the one used by the user for debug console commands. + /// + public bool InvokeCommand(ICommonSession session, string command, object? input, out object? result) + { + if (!_contexts.TryGetValue(session.UserId, out var ctx)) + { + // Can't get a shell here. + result = null; + return false; + } + + ctx.ClearErrors(); + + return InvokeCommand(ctx, command, input, out result); + } + + /// + /// Invokes a command as the given user. + /// + /// User to run as. + /// Command to invoke. + /// An input value to use, if any. + /// The resulting value, if any. + /// Invocation success. + /// + /// ToolshedManager toolshed = ...; + /// IConsoleShell ctx = ...; + /// // Now run some user provided command and get a result! + /// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result); + /// + /// + /// This will use the same IInvocationContext as the one used by the user for debug console commands. + /// + public bool InvokeCommand(IConsoleShell session, string command, object? input, out object? result, out IInvocationContext ctx) + { + var idx = session.Player?.UserId ?? new NetUserId(); + if (!_contexts.TryGetValue(idx, out var ourCtx)) + { + ourCtx = new OldShellInvocationContext(session); + _contexts[idx] = ourCtx; + } + + ourCtx.ClearErrors(); + ctx = ourCtx; + + return InvokeCommand(ctx, command, input, out result); + } + + + /// + /// Invokes a command with the given context. + /// + /// The context to run in. + /// Command to invoke. + /// An input value to use, if any. + /// The resulting value, if any. + /// Invocation success. + /// + /// ToolshedManager toolshed = ...; + /// IInvocationContext ctx = ...; + /// // Now run some user provided command and get a result! + /// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result); + /// + public bool InvokeCommand(IInvocationContext ctx, string command, object? input, out object? result) + { + ctx.ClearErrors(); + + var parser = new ForwardParser(command, this); + if (!CommandRun.TryParse(false, false, parser, input?.GetType(), null, false, out var expr, out _, out var err) || parser.Index < parser.MaxIndex) + { + + if (err is not null) + ctx.ReportError(err); + + result = null; + return false; + } + + result = expr.Invoke(input, ctx); + return true; + } +} + +/// +/// A command specification, containing both the command object and the selected subcommand if any. +/// +/// Command object. +/// Subcommand, if any. +public readonly record struct CommandSpec(ToolshedCommand Cmd, string? SubCommand) : IAsType +{ + /// + public ToolshedCommand AsType() + { + return Cmd; + } + + /// + /// Returns a completion option for this command, for use in autocomplete. + /// + public CompletionOption AsCompletion() + { + return new CompletionOption( + $"{Cmd.Name}{(SubCommand is not null ? ":" + SubCommand : "")}", + Cmd.Description(SubCommand) + ); + } + + /// + /// Returns the full name of the command. + /// + public string FullName() => $"{Cmd.Name}{(SubCommand is not null ? ":" + SubCommand : "")}"; + + /// + /// Returns the localization string for the description of this command. + /// + public string DescLocStr() => Cmd.UnlocalizedDescription(SubCommand); + + /// + public override string ToString() + { + return Cmd.GetHelp(SubCommand); + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/BlockTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/BlockTypeParser.cs new file mode 100644 index 000000000..292a43425 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/BlockTypeParser.cs @@ -0,0 +1,80 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class BlockTypeParser : TypeParser +{ + public BlockTypeParser() + { + } + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var r = Block.TryParse(false, parser, null, out var block, out _, out error); + result = block; + return r; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + Block.TryParse(true, parser, null, out _, out var autocomplete, out _); + if (autocomplete is null) + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((null, null)); + + return autocomplete.Value; + } +} + + +internal sealed class BlockTypeParser : TypeParser> +{ + public BlockTypeParser() + { + } + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var r = Block.TryParse(false, parser, null, out var block, out _, out error); + result = block; + return r; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + Block.TryParse(true, parser, null, out _, out var autocomplete, out _); + if (autocomplete is null) + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((null, null)); + + return autocomplete.Value; + } +} + +internal sealed class BlockTypeParser : TypeParser> +{ + public BlockTypeParser() + { + } + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var r = Block.TryParse(false, parser, null, out var block, out _, out error); + result = block; + return r; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + Block.TryParse(true, parser, null, out _, out var autocomplete, out _); + if (autocomplete is null) + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((null, null)); + + return autocomplete.Value; + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs new file mode 100644 index 000000000..3fe00ed5d --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/ComponentTypeParser.cs @@ -0,0 +1,78 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class ComponentTypeParser : TypeParser +{ + [Dependency] private readonly IComponentFactory _factory = default!; + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var start = parser.Index; + var word = parser.GetWord(); + error = null; + + if (word is null) + { + error = new OutOfInputError(); + result = null; + return false; + } + + if (!_factory.TryGetRegistration(word.ToLower(), out var reg, true)) + { + result = null; + error = new UnknownComponentError(word); + error.Contextualize(parser.Input, (start, parser.Index)); + return false; + } + + result = new ComponentType(reg.Type); + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>( + (CompletionResult.FromOptions(_factory.AllRegisteredTypes.Select(_factory.GetComponentName)), null) + ); + } +} + +public readonly record struct ComponentType(Type Ty) : IAsType +{ + public Type AsType() => Ty; +}; + +public record struct UnknownComponentError(string Component) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = FormattedMessage.FromMarkup( + $"Unknown component {Component}. For a list of all components, try types:components." + ); + if (Component.EndsWith("component", true, CultureInfo.InvariantCulture)) + { + msg.PushNewline(); + msg.AddText($"Do not specify the word `Component` in the argument. Maybe try {Component[..^"component".Length]}?"); + } + + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs new file mode 100644 index 000000000..4c21421fc --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs @@ -0,0 +1,69 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class EntityUidTypeParser : TypeParser +{ + [Dependency] private readonly IEntityManager _entity = default!; + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var start = parser.Index; + var word = parser.GetWord(); + error = null; + + if (!EntityUid.TryParse(word, out var ent)) + { + result = null; + + if (word is not null) + error = new InvalidEntityUid(word); + else + error = new OutOfInputError(); + + error.Contextualize(parser.Input, (start, parser.Index)); + return false; + } + + result = ent; + return true; + } + + public override async ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return (CompletionResult.FromHint(""), null); + } +} + +public record struct InvalidEntityUid(string Value) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"Couldn't parse {Value} as an entity ID. Entity IDs are numeric, optionally starting with a c to indicate client-sided-ness."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct DeadEntity(EntityUid Entity) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"The entity {Entity} does not exist."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/ExpressionTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ExpressionTypeParser.cs new file mode 100644 index 000000000..85b6fff97 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/ExpressionTypeParser.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class ExpressionTypeParser : TypeParser +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var res = CommandRun.TryParse(false, false, parser, null, null, false, out var r, out _, out error); + result = r; + return res; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + CommandRun.TryParse(false, true, parser, null, null, false, out _, out var autocomplete, out _); + if (autocomplete is null) + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((null, null)); + + return autocomplete.Value; + } + +} + +internal sealed class ExpressionTypeParser : TypeParser> +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var res = CommandRun.TryParse(false, false, parser, null, false, out var r, out _, out error); + result = r; + return res; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + CommandRun.TryParse(false, true, parser, null, false, out _, out var autocomplete, out _); + if (autocomplete is null) + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((null, null)); + + return autocomplete.Value; + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/FloatTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/FloatTypeParser.cs new file mode 100644 index 000000000..8a7266560 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/FloatTypeParser.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class FloatTypeParser : TypeParser +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var maybeFloat = parser.GetWord(); + if (!float.TryParse(maybeFloat, out var @float)) + { + if (maybeFloat is null) + { + error = new OutOfInputError(); + } + else + { + error = new InvalidInteger(maybeFloat); + } + + result = null; + return false; + } + + result = @float; + error = null; + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return new ValueTask<(CompletionResult? result, IConError? error)>( + (CompletionResult.FromHint($"any float (decimal number)"), null) + ); + } +} + +public record struct InvalidFloat(string Value) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup( + $"The value {Value} is not a valid floating point number. Only decimal numbers are accepted."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + diff --git a/Robust.Shared/Toolshed/TypeParsers/IAsType.cs b/Robust.Shared/Toolshed/TypeParsers/IAsType.cs new file mode 100644 index 000000000..9442da8fb --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/IAsType.cs @@ -0,0 +1,9 @@ +namespace Robust.Shared.Toolshed.TypeParsers; + +/// +/// Generalized unboxing of a value from a containing structure. +/// +public interface IAsType +{ + public T AsType(); +} diff --git a/Robust.Shared/Toolshed/TypeParsers/IntTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/IntTypeParser.cs new file mode 100644 index 000000000..e9605b864 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/IntTypeParser.cs @@ -0,0 +1,57 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class IntTypeParser : TypeParser +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var maybeInt = parser.GetWord(); + if (!int.TryParse(maybeInt, out var @int)) + { + if (maybeInt is null) + { + error = new OutOfInputError(); + } + else + { + error = new InvalidInteger(maybeInt); + } + + result = null; + return false; + } + + result = @int; + error = null; + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return new ValueTask<(CompletionResult? result, IConError? error)>( + (CompletionResult.FromHint($"integer between {int.MinValue} and {int.MaxValue}"), null) + ); + } +} + +public record struct InvalidInteger(string Value) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup( + $"The value {Value} is not a valid integer. Please input some integer value between {int.MinValue} and {int.MaxValue}."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + diff --git a/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs new file mode 100644 index 000000000..626a2f4a2 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/PrototypeTypeParser.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class PrototypeTypeParser : TypeParser> + where T : class, IPrototype +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var proto = parser.GetWord(char.IsLetterOrDigit); + + if (proto is null || !_prototype.TryIndex(proto, out var resolved)) + { + _prototype.TryGetKindFrom(out var kind); + DebugTools.AssertNotNull(kind); + + error = new NotAValidPrototype(proto ?? "[null]", kind!); + result = null; + return false; + } + + result = new Prototype(resolved); + error = null; + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, string? argName) + { + IEnumerable options; + + // todo: this should be an attribute. + if (typeof(T) != typeof(EntityPrototype)) + options = CompletionHelper.PrototypeIDs(); + else + options = Array.Empty(); + + _prototype.TryGetKindFrom(out var kind); + DebugTools.AssertNotNull(kind); + + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHintOptions(options, $"<{kind} prototype>"), null)); + } +} + +public readonly record struct Prototype(T Value) : IAsType + where T : class, IPrototype +{ + public string AsType() + { + return Value.ID; + } +} + +public record struct NotAValidPrototype(string Proto, string Kind) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup($"{Proto} is not a valid {Kind} prototype"); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs new file mode 100644 index 000000000..73a4fa88a --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/QuantityTypeParser.cs @@ -0,0 +1,73 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class QuantityParser : TypeParser +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var word = parser.GetWord(); + error = null; + + if (word?.TrimEnd('%') is not { } maybeParseable || !float.TryParse(maybeParseable, out var v)) + { + if (word is not null) + error = new InvalidQuantity(word); + else + error = new OutOfInputError(); + + result = null; + return false; + } + + if (v < 0.0) + { + error = new InvalidQuantity(word); + result = null; + return false; + } + + if (word.EndsWith('%')) + { + if (v > 100.0) + { + error = new InvalidQuantity(word); + result = null; + return false; + } + + result = new Quantity(null, (v / 100.0f)); + return true; + } + + result = new Quantity(v, null); + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHint($"{argName ?? "quantity"}"), null)); + } +} + +public readonly record struct Quantity(float? Amount, float? Percentage); + +public record struct InvalidQuantity(string Value) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup( + $"The value {Value} is not a valid quantity. Please input some decimal number, optionally with a % to indicate that it's a percentage."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/ResPathTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ResPathTypeParser.cs new file mode 100644 index 000000000..27c68c40d --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/ResPathTypeParser.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class ResPathTypeParser : StringTypeParser +{ + public override Type Parses => typeof(ResPath); + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var baseResult = base.TryParse(parser, out result, out error); + + if (!baseResult) + return false; + + result = new ResPath((string) result!); + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return base.TryAutocomplete(parser, argName); + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs new file mode 100644 index 000000000..5e7dda7eb --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/StringTypeParser.cs @@ -0,0 +1,101 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + +[Virtual] +internal class StringTypeParser : TypeParser +{ + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + error = null; + parser.Consume(char.IsWhiteSpace); + if (parser.PeekChar() is not '"') + { + if (parser.PeekChar() is null) + { + error = new OutOfInputError(); + result = null; + return false; + } + + error = new StringMustStartWithQuote(); + error.Contextualize(parser.Input, (parser.Index, parser.Index + 1)); + result = null; + return false; + } + + parser.GetChar(); + + var output = new StringBuilder(); + + while (true) + { + while (parser.PeekChar() is not '"' and not '\\' and not null) + { + output.Append(parser.GetChar()); + } + + if (parser.PeekChar() is '"' or null) + { + if (parser.PeekChar() is null) + { + error = new OutOfInputError(); + result = null; + return false; + } + + parser.GetChar(); + break; + } + + parser.GetChar(); // okay it's \ + + switch (parser.GetChar()) + { + case '"': + output.Append('"'); + continue; + case 'n': + output.Append('\n'); + continue; + case '\\': + output.Append('\\'); + continue; + default: + result = null; + // todo: error + return false; + } + } + + parser.Consume(char.IsWhiteSpace); + + result = output.ToString(); + return true; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHint($"\"<{argName ?? "string"}>\""), null)); + } +} + +public record struct StringMustStartWithQuote : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromMarkup("A string must start with a quote."); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs new file mode 100644 index 000000000..184566198 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/TypeParser.cs @@ -0,0 +1,41 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Toolshed.Errors; + +namespace Robust.Shared.Toolshed.TypeParsers; + +[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] +public interface ITypeParser : IPostInjectInit +{ + public Type Parses { get; } + + public bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error); + + public ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName); +} + +[PublicAPI] +public abstract class TypeParser : ITypeParser + where T: notnull +{ + [Dependency] private readonly ILogManager _log = default!; + + protected ISawmill _sawmill = default!; + + public virtual Type Parses => typeof(T); + + public abstract bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error); + public abstract ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName); + + public virtual void PostInject() + { + _sawmill = _log.GetSawmill(GetType().PrettyName()); + } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs new file mode 100644 index 000000000..ab8145165 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/TypeTypeParser.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.ContentPack; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Robust.Shared.Toolshed.TypeParsers; + + +// TODO: This should be able to parse more types, currently it only knows the ones in SimpleTypes. +internal sealed class TypeTypeParser : TypeParser +{ + [Dependency] private readonly IModLoader _modLoader = default!; + + public Dictionary Types = new() + { + {"object", typeof(object)}, + {"int", typeof(int)}, + {"uint", typeof(uint)}, + {"char", typeof(char)}, + {"byte", typeof(byte)}, + {"sbyte", typeof(sbyte)}, + {"short", typeof(short)}, + {"ushort", typeof(ushort)}, + {"long", typeof(ulong)}, + {"ulong", typeof(ulong)}, + {"string", typeof(string)}, + {"bool", typeof(bool)}, + {"nint", typeof(nint)}, + {"nuint", typeof(nuint)}, + {"float", typeof(float)}, + {"double", typeof(double)}, + {nameof(Vector2), typeof(Vector2)}, + {nameof(TimeSpan), typeof(TimeSpan)}, + {nameof(DateTime), typeof(DateTime)}, + {"IEnumerable", typeof(IEnumerable<>)}, + {"List", typeof(List<>)}, + {"HashSet", typeof(HashSet<>)}, + {nameof(Task), typeof(Task<>)}, + {nameof(ValueTask), typeof(ValueTask<>)}, + // C# doesn't let you do `typeof(Dictionary<>)`. Why is a mystery to me. + {"Dictionary", typeof(Dictionary).GetGenericTypeDefinition()}, + }; + + private readonly HashSet _ambiguousTypes = new(); + + public override void PostInject() + { + // SANDBOXING: We assume all `public` types on loaded assemblies are safe to reference. INCLUDING ROBUST.SHARED. + foreach (var mod in _modLoader.LoadedModules.Append(Assembly.GetExecutingAssembly())) + { + foreach (var exported in mod.ExportedTypes) + { + var name = exported.Name; + if (_ambiguousTypes.Contains(name)) + continue; + + if (!Types.TryAdd(name, exported)) + { + Types.Remove(name); + _ambiguousTypes.Add(name); + } + } + } + } + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var firstWord = parser.GetWord(char.IsLetterOrDigit); + if (firstWord is null) + { + error = new OutOfInputError(); + result = null; + return false; + } + + var ty = ParseBase(firstWord); + + if (ty is null) + { + error = new UnknownType(firstWord); + result = null; + return false; + } + + if (ty.IsGenericTypeDefinition) + { + if (!parser.EatMatch('<')) + { + error = new ExpectedGeneric(); + result = null; + return false; + } + + var len = ty.GetGenericArguments().Length; + var args = new Type[ty.GetGenericArguments().Length]; + + for (var i = 0; i < len; i++) + { + if (!TryParse(parser, out var t, out error)) + { + result = null; + return false; + } + + args[i] = (Type) t; + + if (i != (len - 1) && !parser.EatMatch(',')) + { + error = new ExpectedNextType(); + result = null; + return false; + } + } + + if (!parser.EatMatch('>')) + { + error = new ExpectedGeneric(); + result = null; + return false; + } + + ty = ty.MakeGenericType(args); + } + + if (parser.EatMatch('[')) + { + if (!parser.EatMatch(']')) + { + error = new UnknownType(firstWord); + result = null; + return false; + } + + ty = ty.MakeArrayType(); + } + + if (parser.EatMatch('?') && (ty.IsValueType || ty.IsPrimitive)) + { + ty = typeof(Nullable<>).MakeGenericType(ty); + } + + result = ty; + error = null; + return true; + } + + private Type? ParseBase(string word) + { + Types.TryGetValue(word, out var ty); + return ty; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + // TODO: Suggest generics. + var options = Types.Select(x => new CompletionOption(x.Key)); + return ValueTask.FromResult<(CompletionResult? result, IConError? error)>((CompletionResult.FromHintOptions(options, "C# level type"), null)); + } +} + +public record struct ExpectedNextType() : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText($"Expected another type in the generic arguments."); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct ExpectedGeneric() : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText($"Expected a generic type, did you forget the angle brackets?"); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + +public record struct UnknownType(string T) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText($"The type {T} is not known and cannot be used."); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} + + +internal record struct TypeIsSandboxViolation(Type T) : IConError +{ + public FormattedMessage DescribeInner() + { + var msg = new FormattedMessage(); + msg.AddText($"The type {T.PrettyName()} is not permitted under sandbox rules."); + return msg; + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Robust.Shared/Toolshed/TypeParsers/ValueRefTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/ValueRefTypeParser.cs new file mode 100644 index 000000000..1f9202690 --- /dev/null +++ b/Robust.Shared/Toolshed/TypeParsers/ValueRefTypeParser.cs @@ -0,0 +1,116 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Robust.Shared.Console; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Toolshed.Syntax; + +namespace Robust.Shared.Toolshed.TypeParsers; + +internal sealed class ValueRefTypeParser : TypeParser> +{ + [Dependency] private readonly ToolshedManager _toolshed = default!; + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + var res = _toolshed.TryParse>(parser, out var inner, out error); + result = null; + if (res) + result = new ValueRef((ValueRef)inner!); + return res; + } + + public override ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, string? argName) + { + return _toolshed.TryAutocomplete(parser, typeof(ValueRef), argName); + } +} + +internal sealed class VarRefParser : TypeParser> +{ + [Dependency] private readonly ToolshedManager _toolshed = default!; + + public override bool TryParse(ForwardParser parser, [NotNullWhen(true)] out object? result, out IConError? error) + { + error = null; + parser.Consume(char.IsWhiteSpace); + + var chkpoint = parser.Save(); + var success = _toolshed.TryParse(parser, out var value, out error); + + if (error is UnparseableValueError) + error = null; + + if (value is not null && success) + { + result = new ValueRef((T)value); + error = null; + return true; + } + + parser.Restore(chkpoint); + + if (parser.EatMatch('$')) + { + // We're parsing a variable. + if (parser.GetWord(x => char.IsLetterOrDigit(x) || x == '_') is not { } word) + { + error = new OutOfInputError(); + result = null; + return false; + } + + result = new ValueRef(word); + error = null; + return true; + } + else + { + if (Block.TryParse(false, parser, null, out var block, out _, out error)) + { + result = new ValueRef(block); + return true; + } + + result = null; + return false; + } + } + + public override async ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ForwardParser parser, + string? argName) + { + parser.Consume(char.IsWhiteSpace); + + if (parser.EatMatch('$')) + { + return (CompletionResult.FromHint(""), null); + } + else + { + var chkpoint = parser.Save(); + var (res, err) = await _toolshed.TryAutocomplete(parser, typeof(TAuto), null); + parser.Restore(chkpoint); + CompletionOption[] parseOptions = Array.Empty(); + if (err is not UnparseableValueError || res is not null) + { + parseOptions = res?.Options ?? parseOptions; + } + + chkpoint = parser.Save(); + Block.TryParse(true, parser, null, out _, out var result, out _); + if (result is not null) + { + var (blockRes, _) = await result.Value; + var options = blockRes?.Options ?? Array.Empty(); + return (CompletionResult.FromHintOptions(parseOptions.Concat(options).ToArray(), $""), err); + } + parser.Restore(chkpoint); + + return (CompletionResult.FromHint("$"), null); + } + } +} diff --git a/Robust.Shared/ViewVariables/Commands/ViewVariablesBaseCommand.cs b/Robust.Shared/ViewVariables/Commands/ViewVariablesBaseCommand.cs index 5edbcb4eb..0483f515f 100644 --- a/Robust.Shared/ViewVariables/Commands/ViewVariablesBaseCommand.cs +++ b/Robust.Shared/ViewVariables/Commands/ViewVariablesBaseCommand.cs @@ -12,7 +12,7 @@ public abstract class ViewVariablesBaseCommand : LocalizedCommands [Dependency] protected readonly INetManager _netMan = default!; [Dependency] protected readonly IViewVariablesManager _vvm = default!; - public override async ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel) + public override async ValueTask GetCompletionAsync(IConsoleShell shell, string[] args, string argStr, CancellationToken cancel) { if (args.Length is 0 or > 1) return CompletionResult.Empty;