From f41552993ac2e8e1618b1fd7c6f02e07fa6f0da4 Mon Sep 17 00:00:00 2001 From: Karl Ostmo Date: Wed, 22 Nov 2023 13:22:39 -0800 Subject: [PATCH] Render command matrix --- app/Main.hs | 1 + src/Swarm/Doc/Schema/Render.hs | 4 +- src/Swarm/Doc/Util.hs | 8 + src/Swarm/Doc/Wiki/Cheatsheet.hs | 6 +- src/Swarm/Doc/Wiki/Matrix.hs | 92 +++++ src/Swarm/Language/Syntax.hs | 366 ++++++++++++------- src/Swarm/Language/Syntax/CommandMetadata.hs | 42 +++ src/Swarm/Web.hs | 6 + swarm.cabal | 6 +- web/command-matrix.html | 33 ++ web/favicon.ico | Bin 0 -> 7746 bytes web/image/haskell-logo.png | Bin 0 -> 11664 bytes web/script/command-matrix.js | 47 +++ web/style/command-matrix.css | 26 ++ web/style/tablesort.css | 48 +++ 15 files changed, 541 insertions(+), 144 deletions(-) create mode 100644 src/Swarm/Doc/Wiki/Matrix.hs create mode 100644 src/Swarm/Language/Syntax/CommandMetadata.hs create mode 100644 web/command-matrix.html create mode 100644 web/favicon.ico create mode 100644 web/image/haskell-logo.png create mode 100644 web/script/command-matrix.js create mode 100644 web/style/command-matrix.css create mode 100644 web/style/tablesort.css diff --git a/app/Main.hs b/app/Main.hs index eb47196426..6424f51ce9 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -135,6 +135,7 @@ cliParser = , Just Recipes <$ switch (long "recipes" <> help "Generate recipes page (uses data from recipes.yaml)") , Just Capabilities <$ switch (long "capabilities" <> help "Generate capabilities page (uses entity map)") , Just Commands <$ switch (long "commands" <> help "Generate commands page (uses constInfo, constCaps and inferConst)") + , Just CommandMatrix <$ switch (long "matrix" <> help "Generate commands matrix page") , Just Scenario <$ switch (long "scenario" <> help "Generate scenario schema page") ] seed :: Parser (Maybe Int) diff --git a/src/Swarm/Doc/Schema/Render.hs b/src/Swarm/Doc/Schema/Render.hs index 57d60475ba..58967a9153 100644 --- a/src/Swarm/Doc/Schema/Render.hs +++ b/src/Swarm/Doc/Schema/Render.hs @@ -111,9 +111,7 @@ recombineExtension (filenameStem, fileExtension) = genMarkdown :: [SchemaData] -> Either T.Text T.Text genMarkdown schemaThings = - left renderError $ - runPure $ - writeMarkdown (def {writerExtensions = extensionsFromList [Ext_pipe_tables]}) pd + pandocToText pd where titleMap = makeTitleMap schemaThings pd = diff --git a/src/Swarm/Doc/Util.hs b/src/Swarm/Doc/Util.hs index 60858b1087..3231167a65 100644 --- a/src/Swarm/Doc/Util.hs +++ b/src/Swarm/Doc/Util.hs @@ -6,6 +6,7 @@ -- Utilities for generating doc markup module Swarm.Doc.Util where +import Control.Arrow (left) import Control.Effect.Throw (Has, Throw, throwError) import Control.Lens (view) import Data.Maybe (listToMaybe) @@ -16,6 +17,7 @@ import Swarm.Game.Robot (Robot, instantiateRobot) import Swarm.Game.Scenario (Scenario, scenarioRobots) import Swarm.Language.Syntax (Const (..)) import Swarm.Language.Syntax qualified as Syntax +import Text.Pandoc -- * Text operations @@ -51,3 +53,9 @@ getBaseRobot :: Has (Throw SystemFailure) sig m => Scenario -> m Robot getBaseRobot s = case listToMaybe $ view scenarioRobots s of Just r -> pure $ instantiateRobot 0 r Nothing -> throwError $ CustomFailure "Scenario contains no robots" + +pandocToText :: Pandoc -> Either Text Text +pandocToText = + left renderError + . runPure + . writeMarkdown (def {writerExtensions = extensionsFromList [Ext_pipe_tables]}) diff --git a/src/Swarm/Doc/Wiki/Cheatsheet.hs b/src/Swarm/Doc/Wiki/Cheatsheet.hs index 093ed22f9c..e6dee15cfd 100644 --- a/src/Swarm/Doc/Wiki/Cheatsheet.hs +++ b/src/Swarm/Doc/Wiki/Cheatsheet.hs @@ -25,6 +25,7 @@ import Data.Text qualified as T import Data.Text.IO qualified as T import Swarm.Doc.Schema.Render import Swarm.Doc.Util +import Swarm.Doc.Wiki.Matrix import Swarm.Game.Display (displayChar) import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities) import Swarm.Game.Entity qualified as E @@ -52,7 +53,7 @@ data PageAddress = PageAddress deriving (Eq, Show) -- | An enumeration of the kinds of cheat sheets we can produce. -data SheetType = Entities | Commands | Capabilities | Recipes | Scenario +data SheetType = Entities | Commands | CommandMatrix | Capabilities | Recipes | Scenario deriving (Eq, Show, Enum, Bounded) -- * Functions @@ -62,6 +63,9 @@ makeWikiPage address s = case s of Nothing -> error "Not implemented for all Wikis" Just st -> case st of Commands -> T.putStrLn commandsPage + CommandMatrix -> case pandocToText commandsMatrix of + Right x -> T.putStrLn x + Left x -> error $ T.unpack x Capabilities -> simpleErrorHandle $ do entities <- loadEntities sendIO $ T.putStrLn $ capabilityPage address entities diff --git a/src/Swarm/Doc/Wiki/Matrix.hs b/src/Swarm/Doc/Wiki/Matrix.hs new file mode 100644 index 0000000000..eb952d8a98 --- /dev/null +++ b/src/Swarm/Doc/Wiki/Matrix.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- | +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Auto-generation of command attributes matrix. +module Swarm.Doc.Wiki.Matrix where + +import Data.Aeson (ToJSON) +import Data.List.NonEmpty qualified as NE +import Data.Text qualified as T +import GHC.Generics (Generic) +import Servant.Docs qualified as SD +import Swarm.Doc.Util +import Swarm.Language.Pretty (unchainFun) +import Swarm.Language.Syntax +import Swarm.Language.Syntax.CommandMetadata +import Swarm.Language.Typecheck (inferConst) +import Swarm.Language.Types +import Text.Pandoc +import Text.Pandoc.Builder + +data DerivedAttrs = DerivedAttrs + { hasActorTarget :: Bool + , pureComputation :: Bool + } + deriving (Generic, ToJSON) + +data CommandEntry = CommandEntry + { cmd :: Const + , effects :: CommandEffect + , argTypes :: NE.NonEmpty Type + , derivedAttrs :: DerivedAttrs + } + deriving (Generic, ToJSON) + +newtype CommandCatalog = CommandCatalog + { entries :: [CommandEntry] + } + deriving (Generic, ToJSON) + +instance SD.ToSample CommandCatalog where + toSamples _ = SD.noSamples + +mkEntry :: Const -> CommandEntry +mkEntry c = + CommandEntry c cmdEffects rawArgs $ + DerivedAttrs + (operatesOnActor inputArgs) + (cmdEffects == Computation) + where + cmdInfo = constInfo c + cmdEffects = effectInfo $ constDoc cmdInfo + + getArgs ((Forall _ t)) = unchainFun t + + rawArgs = getArgs $ inferConst c + + inputArgs = NE.init rawArgs + outputType = NE.last rawArgs + + operatesOnActor = elem TyActor + +getCatalog :: CommandCatalog +getCatalog = CommandCatalog $ map mkEntry commands + +commandsMatrix :: Pandoc +commandsMatrix = + setTitle (text "Commands matrix") $ + doc (header 3 (text "Commands matrix")) + <> doc (makePropsTable ["Command", "Effects", "Actor Target", "Type"]) + +makePropsTable :: + [T.Text] -> + Blocks +makePropsTable headingsList = + simpleTable headerRow $ map genPropsRow catalogEntries + where + CommandCatalog catalogEntries = getCatalog + headerRow = map (plain . text) headingsList + +genPropsRow :: CommandEntry -> [Blocks] +genPropsRow e = + [ showCode (cmd e) + , showCode (effects e) + , showCode (hasActorTarget $ derivedAttrs e) + ] + <> NE.toList completeTypeMembers + where + showCode :: Show a => a -> Blocks + showCode = plain . code . T.pack . show + completeTypeMembers = NE.map showCode $ argTypes e diff --git a/src/Swarm/Language/Syntax.hs b/src/Swarm/Language/Syntax.hs index 54f93be00f..0234b6fb38 100644 --- a/src/Swarm/Language/Syntax.hs +++ b/src/Swarm/Language/Syntax.hs @@ -98,12 +98,13 @@ import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NonEmpty import Data.Map.Strict (Map) import Data.Set qualified as S -import Data.String (IsString (fromString)) +import Data.Set qualified as Set import Data.Text hiding (filter, length, map) import Data.Text qualified as T import Data.Tree import GHC.Generics (Generic) import Swarm.Language.Direction +import Swarm.Language.Syntax.CommandMetadata import Swarm.Language.Types import Swarm.Util qualified as Util import Witch.From (from) @@ -402,15 +403,20 @@ data ConstInfo = ConstInfo } deriving (Eq, Ord, Show) -data ConstDoc = ConstDoc {briefDoc :: Text, longDoc :: Text} +data ConstDoc = ConstDoc + { effectInfo :: CommandEffect + , briefDoc :: Text + , longDoc :: Text + } deriving (Eq, Ord, Show) -instance IsString ConstDoc where - fromString = flip ConstDoc "" . T.pack - data ConstMeta = -- | Function with arity of which some are commands - ConstMFunc Int Bool + ConstMFunc + -- | Arity + Int + -- | Is a command? + Bool | -- | Unary operator with fixity and associativity. ConstMUnOp MUnAssoc | -- | Binary operator with fixity and associativity. @@ -527,88 +533,163 @@ isLong c = case tangibility (constInfo c) of -- matching gives us warning if we add more constants. constInfo :: Const -> ConstInfo constInfo c = case c of - Wait -> command 0 long "Wait for a number of time steps." + Wait -> + command 0 long $ + shortDoc + (Mutation $ Set.singleton $ RobotChange BehaviorChange) + "Wait for a number of time steps." Noop -> - command 0 Intangible . doc "Do nothing." $ + command 0 Intangible . doc Computation "Do nothing." $ [ "This is different than `Wait` in that it does not take up a time step." , "It is useful for commands like if, which requires you to provide both branches." , "Usually it is automatically inserted where needed, so you do not have to worry about it." ] Selfdestruct -> - command 0 short . doc "Self-destruct a robot." $ - [ "Useful to not clutter the world." - , "This destroys the robot's inventory, so consider `salvage` as an alternative." - ] - Move -> command 0 short "Move forward one step." - Backup -> command 0 short "Move backward one step." + command 0 short + . doc + (Mutation $ Set.singleton $ RobotChange ExistenceChange) + "Self-destruct a robot." + $ [ "Useful to not clutter the world." + , "This destroys the robot's inventory, so consider `salvage` as an alternative." + ] + Move -> + command 0 short $ + shortDoc + (Mutation $ Set.singleton $ RobotChange PositionChange) + "Move forward one step." + Backup -> + command 0 short $ + shortDoc + (Mutation $ Set.singleton $ RobotChange PositionChange) + "Move backward one step." Path -> - command 2 short . doc "Obtain shortest path to the destination." $ - [ "Optionally supply a distance limit as the first argument." - , "Supply either a location (`inL`) or an entity (`inR`) as the second argument." - , "If a path exists, returns the direction to proceed along." - ] + command 2 short + . doc + (Query $ Sensing EntitySensing) + "Obtain shortest path to the destination." + $ [ "Optionally supply a distance limit as the first argument." + , "Supply either a location (`inL`) or an entity (`inR`) as the second argument." + , "If a path exists, returns the direction to proceed along." + ] Push -> - command 1 short . doc "Push an entity forward one step." $ - [ "Both entity and robot moves forward one step." - , "Destination must not contain an entity." - ] + command 1 short + . doc + (Mutation $ Set.singleton EntityChange) + "Push an entity forward one step." + $ [ "Both entity and robot moves forward one step." + , "Destination must not contain an entity." + ] Stride -> - command 1 short . doc "Move forward multiple steps." $ - [ T.unwords ["Has a max range of", T.pack $ show maxStrideRange, "units."] - ] - Turn -> command 1 short "Turn in some direction." - Grab -> command 0 short "Grab an item from the current location." + command 1 short + . doc + (Mutation $ Set.singleton $ RobotChange PositionChange) + "Move forward multiple steps." + $ [ T.unwords ["Has a max range of", T.pack $ show maxStrideRange, "units."] + ] + Turn -> + command 1 short $ + shortDoc + (Mutation $ Set.singleton $ RobotChange PositionChange) + "Turn in some direction." + Grab -> + command 0 short $ + shortDoc + (Mutation $ Set.fromList [EntityChange, RobotChange InventoryChange]) + "Grab an item from the current location." Harvest -> - command 0 short . doc "Harvest an item from the current location." $ + command 0 short . doc (Mutation $ Set.singleton EntityChange) "Harvest an item from the current location." $ [ "Leaves behind a growing seed if the harvested item is growable." , "Otherwise it works exactly like `grab`." ] Ignite -> - command 1 short . doc "Ignite a combustible item in the specified direction." $ - [ "Combustion persists for a random duration and may spread." - ] + command 1 short + . doc + (Mutation $ Set.singleton EntityChange) + "Ignite a combustible item in the specified direction." + $ [ "Combustion persists for a random duration and may spread." + ] Place -> - command 1 short . doc "Place an item at the current location." $ - ["The current location has to be empty for this to work."] + command 1 short + . doc + (Mutation $ Set.singleton EntityChange) + "Place an item at the current location." + $ ["The current location has to be empty for this to work."] Ping -> - command 1 short . doc "Obtain the relative location of another robot." $ - [ "The other robot must be within transmission range, accounting for antennas installed on either end, and the invoking robot must be oriented in a cardinal direction." - , "The location (x, y) is given relative to one's current orientation:" - , "Positive x value is to the right, negative left. Likewise, positive y value is forward, negative back." - ] - Give -> command 2 short "Give an item to another actor nearby." - Equip -> command 1 short "Equip a device on oneself." - Unequip -> command 1 short "Unequip an equipped device, returning to inventory." - Make -> command 1 long "Make an item using a recipe." - Has -> command 1 Intangible "Sense whether the robot has a given item in its inventory." - Equipped -> command 1 Intangible "Sense whether the robot has a specific device equipped." - Count -> command 1 Intangible "Get the count of a given item in a robot's inventory." + command 1 short + . doc + (Query $ Sensing RobotSensing) + "Obtain the relative location of another robot." + $ [ "The other robot must be within transmission range, accounting for antennas installed on either end, and the invoking robot must be oriented in a cardinal direction." + , "The location (x, y) is given relative to one's current orientation:" + , "Positive x value is to the right, negative left. Likewise, positive y value is forward, negative back." + ] + Give -> + command 2 short $ + shortDoc + (Mutation $ Set.singleton $ RobotChange InventoryChange) + "Give an item to another actor nearby." + Equip -> + command 1 short $ + shortDoc + (Mutation $ Set.fromList [RobotChange InventoryChange, RobotChange BehaviorChange]) + "Equip a device on oneself." + Unequip -> + command 1 short $ + shortDoc + (Mutation $ Set.fromList [RobotChange InventoryChange, RobotChange BehaviorChange]) + "Unequip an equipped device, returning to inventory." + Make -> + command 1 long $ + shortDoc + (Mutation $ Set.singleton $ RobotChange InventoryChange) + "Make an item using a recipe." + Has -> + command 1 Intangible $ + shortDoc + (Query $ Sensing RobotSensing) + "Sense whether the robot has a given item in its inventory." + Equipped -> + command 1 Intangible $ + shortDoc + (Query $ Sensing RobotSensing) + "Sense whether the robot has a specific device equipped." + Count -> + command 1 Intangible $ + shortDoc + (Query $ Sensing RobotSensing) + "Get the count of a given item in a robot's inventory." Reprogram -> - command 2 long . doc "Reprogram another robot with a new command." $ - ["The other robot has to be nearby and idle."] + command 2 long + . doc + (Mutation $ Set.singleton $ RobotChange BehaviorChange) + "Reprogram another robot with a new command." + $ ["The other robot has to be nearby and idle."] Drill -> - command 1 long . doc "Drill through an entity." $ - [ "Usually you want to `drill forward` when exploring to clear out obstacles." - , "When you have found a source to drill, you can stand on it and `drill down`." - , "See what recipes with drill you have available." - , "The `drill` command may return the name of an entity added to your inventory." - ] + command 1 long + . doc + (Mutation $ Set.fromList [EntityChange, RobotChange InventoryChange]) + "Drill through an entity." + $ [ "Usually you want to `drill forward` when exploring to clear out obstacles." + , "When you have found a source to drill, you can stand on it and `drill down`." + , "See what recipes with drill you have available." + , "The `drill` command may return the name of an entity added to your inventory." + ] Use -> - command 2 long . doc "Use one entity upon another." $ + command 2 long . doc (Mutation $ Set.singleton EntityChange) "Use one entity upon another." $ [ "Which entities you can `use` with others depends on the available recipes." , "The object being used must be a 'required' entity in a recipe." ] Build -> - command 1 long . doc "Construct a new robot." $ + command 1 long . doc (Mutation $ Set.singleton $ RobotChange ExistenceChange) "Construct a new robot." $ [ "You can specify a command for the robot to execute." , "If the command requires devices they will be taken from your inventory and " <> "equipped on the new robot." ] Salvage -> - command 0 long . doc "Deconstruct an old robot." $ + command 0 long . doc (Mutation $ Set.singleton $ RobotChange ExistenceChange) "Deconstruct an old robot." $ ["Salvaging a robot will give you its inventory, equipped devices and log."] Say -> - command 1 short . doc "Emit a message." $ + command 1 short . doc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Emit a message." $ [ "The message will be in the robot's log (if it has one) and the global log." , "You can view the message that would be picked by `listen` from the global log " <> "in the messages panel, along with your own messages and logs." @@ -617,208 +698,217 @@ constInfo c = case c of , "In creative mode, there is of course no such limitation." ] Listen -> - command 1 long . doc "Listen for a message from other actors." $ + command 1 long . doc (Query $ Sensing RobotSensing) "Listen for a message from other actors." $ [ "It will take the first message said by the closest actor." , "You do not need to actively listen for the message to be logged though, " <> "that is done automatically once you have a listening device equipped." , "Note that you can see the messages either in your logger device or the message panel." ] - Log -> command 1 short "Log the string in the robot's logger." + Log -> command 1 short $ shortDoc (Query $ Sensing RobotSensing) "Log the string in the robot's logger." View -> - command 1 short . doc "View the given actor." $ + command 1 short . doc (Query $ Sensing RobotSensing) "View the given actor." $ [ "This will recenter the map on the target robot and allow its inventory and logs to be inspected." ] Appear -> - command 1 short . doc "Set how the robot is displayed." $ + command 1 short . doc (Mutation $ Set.singleton Cosmetic) "Set how the robot is displayed." $ [ "You can either specify one character or five (for each direction)." , "The default is \"X^>v<\"." ] Create -> - command 1 short . doc "Create an item out of thin air." $ + command 1 short . doc (Mutation $ Set.fromList [EntityChange, RobotChange InventoryChange]) "Create an item out of thin air." $ ["Only available in creative mode."] - Halt -> command 1 short "Tell a robot to halt." - Time -> command 0 Intangible "Get the current time." + Halt -> command 1 short $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Tell a robot to halt." + Time -> + command 0 Intangible $ + shortDoc + (Query $ Sensing WorldCondition) + "Get the current time." Scout -> - command 1 short . doc "Detect whether a robot is within line-of-sight in a direction." $ + command 1 short . doc (Query $ Sensing RobotSensing) "Detect whether a robot is within line-of-sight in a direction." $ [ "Perception is blocked by 'Opaque' entities." , T.unwords ["Has a max range of", T.pack $ show maxScoutRange, "units."] ] - Whereami -> command 0 Intangible "Get the current x and y coordinates." + Whereami -> + command 0 Intangible $ + shortDoc + (Query $ Sensing RobotSensing) + "Get the current x and y coordinates." Waypoint -> - command 2 Intangible . doc "Get the x, y coordinates of a named waypoint, by index" $ + command 2 Intangible . doc (Query APriori) "Get the x, y coordinates of a named waypoint, by index" $ [ "Return only the waypoints in the same subworld as the calling robot." , "Since waypoint names can have plural multiplicity, returns a tuple of (count, (x, y))." , "The supplied index will be wrapped automatically, modulo the waypoint count." , "A robot can use the count to know whether they have iterated over the full waypoint circuit." ] Structure -> - command 2 Intangible . doc "Get the x, y coordinates of the southwest corner of a constructed structure, by name and index" $ + command 2 Intangible . doc (Query $ Sensing EntitySensing) "Get the x, y coordinates of the southwest corner of a constructed structure, by name and index" $ [ "The outermost type of the return value indicates whether any structure of such name exists." , "Since structures can have multiple occurrences, returns a tuple of (count, (x, y))." , "The supplied index will be wrapped automatically, modulo the structure count." , "A robot can use the count to know whether they have iterated over the full structure list." ] Floorplan -> - command 1 Intangible . doc "Get the dimensions of a structure template" $ + command 1 Intangible . doc (Query APriori) "Get the dimensions of a structure template" $ [ "Returns a tuple of (width, height) for the structure of the requested name." , "Yields an error if the supplied string is not the name of a structure." ] HasTag -> - command 2 Intangible . doc "Check whether the given entity has the given tag" $ + command 2 Intangible . doc (Query APriori) "Check whether the given entity has the given tag" $ [ "Returns true if the first argument is an entity that is labeled by the tag in the second argument." , "Yields an error if the first argument is not a valid entity." ] TagMembers -> - command 2 Intangible . doc "Get the entities labeled by a tag, by alphabetical index" $ + command 2 Intangible . doc (Query APriori) "Get the entities labeled by a tag, by alphabetical index" $ [ "Returns a tuple of (member count, entity)." , "The supplied index will be wrapped automatically, modulo the member count." , "A robot can use the count to know whether they have iterated over the full list." ] Detect -> - command 2 Intangible . doc "Detect an entity within a rectangle." $ + command 2 Intangible . doc (Query $ Sensing EntitySensing) "Detect an entity within a rectangle." $ ["Locate the closest instance of a given entity within the rectangle specified by opposite corners, relative to the current location."] Resonate -> - command 2 Intangible . doc "Count specific entities within a rectangle." $ + command 2 Intangible . doc (Query $ Sensing EntitySensing) "Count specific entities within a rectangle." $ [ "Applies a strong magnetic field over a given area and stimulates the matter within, generating a non-directional radio signal. A receiver tuned to the resonant frequency of the target entity is able to measure its quantity." , "Counts the entities within the rectangle specified by opposite corners, relative to the current location." ] Density -> - command 1 Intangible . doc "Count all entities within a rectangle." $ + command 1 Intangible . doc (Query $ Sensing EntitySensing) "Count all entities within a rectangle." $ [ "Applies a strong magnetic field over a given area and stimulates the matter within, generating a non-directional radio signal. A receiver measured the signal intensity to measure the quantity." , "Counts the entities within the rectangle specified by opposite corners, relative to the current location." ] Sniff -> - command 1 short . doc "Determine distance to entity." $ + command 1 short . doc (Query $ Sensing EntitySensing) "Determine distance to entity." $ [ "Measures concentration of airborne particles to infer distance to a certain kind of entity." , "If none is detected, returns (-1)." , T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."] ] Chirp -> - command 1 short . doc "Determine direction to entity." $ + command 1 short . doc (Query $ Sensing EntitySensing) "Determine direction to entity." $ [ "Uses a directional sonic emitter and microphone tuned to the acoustic signature of a specific entity to determine its direction." , "Returns 'down' if out of range or the direction is indeterminate." , "Provides absolute directions if \"compass\" equipped, relative directions otherwise." , T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."] ] Watch -> - command 1 short . doc "Interrupt `wait` upon location changes." $ + command 1 short . doc (Query $ Sensing EntitySensing) "Interrupt `wait` upon location changes." $ [ "Place seismic detectors to alert upon entity changes to the specified location." , "Supply a direction, as with the `scan` command, to specify a nearby location." , "Can be invoked more than once until the next `wait` command, at which time the only the registered locations that are currently nearby are preserved." , "Any change to entities at the monitored locations will cause the robot to wake up before the `wait` timeout." ] Surveil -> - command 1 short . doc "Interrupt `wait` upon (remote) location changes." $ + command 1 short . doc (Query $ Sensing EntitySensing) "Interrupt `wait` upon (remote) location changes." $ [ "Like `watch`, but with no restriction on distance." ] - Heading -> command 0 Intangible "Get the current heading." - Blocked -> command 0 Intangible "See if the robot can move forward." + Heading -> command 0 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Get the current heading." + Blocked -> command 0 Intangible $ shortDoc (Query $ Sensing EntitySensing) "See if the robot can move forward." Scan -> - command 1 Intangible . doc "Scan a nearby location for entities." $ + command 1 Intangible . doc (Query $ Sensing EntitySensing) "Scan a nearby location for entities." $ [ "Adds the entity (not actor) to your inventory with count 0 if there is any." , "If you can use sum types, you can also inspect the result directly." ] - Upload -> command 1 short "Upload a robot's known entities and log to another robot." - Ishere -> command 1 Intangible "See if a specific entity is in the current location." + Upload -> command 1 short $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Upload a robot's known entities and log to another robot." + Ishere -> command 1 Intangible $ shortDoc (Query $ Sensing EntitySensing) "See if a specific entity is in the current location." Isempty -> - command 0 Intangible . doc "Check if the current location is empty." $ + command 0 Intangible . doc (Query $ Sensing EntitySensing) "Check if the current location is empty." $ [ "Detects whether or not the current location contains an entity." , "Does not detect robots or other actors." ] - Self -> function 0 "Get a reference to the current robot." - Parent -> function 0 "Get a reference to the robot's parent." - Base -> function 0 "Get a reference to the base." - Meet -> command 0 Intangible "Get a reference to a nearby actor, if there is one." - MeetAll -> command 0 long "Run a command for each nearby actor." - Whoami -> command 0 Intangible "Get the robot's display name." - Setname -> command 1 short "Set the robot's display name." + Self -> function 0 $ shortDoc (Query APriori) "Get a reference to the current robot." + Parent -> function 0 $ shortDoc (Query APriori) "Get a reference to the robot's parent." + Base -> function 0 $ shortDoc (Query APriori) "Get a reference to the base." + Meet -> command 0 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Get a reference to a nearby actor, if there is one." + MeetAll -> command 0 long $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Run a command for each nearby actor." + Whoami -> command 0 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Get the robot's display name." + Setname -> command 1 short $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Set the robot's display name." Random -> - command 1 Intangible . doc "Get a uniformly random integer." $ + command 1 Intangible . doc Computation "Get a uniformly random integer." $ ["The random integer will be chosen from the range 0 to n-1, exclusive of the argument."] - Run -> command 1 long "Run a program loaded from a file." - Return -> command 1 Intangible "Make the value a result in `cmd`." - Try -> command 2 Intangible "Execute a command, catching errors." - Undefined -> function 0 "A value of any type, that is evaluated as error." - Fail -> function 1 "A value of any type, that is evaluated as error with message." + Run -> command 1 long $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Run a program loaded from a file." + Return -> command 1 Intangible $ shortDoc Computation "Make the value a result in `cmd`." + Try -> command 2 Intangible $ shortDoc Computation "Execute a command, catching errors." + Undefined -> function 0 $ shortDoc Computation "A value of any type, that is evaluated as error." + Fail -> function 1 $ shortDoc Computation "A value of any type, that is evaluated as error with message." If -> - function 3 . doc "If-Then-Else function." $ + function 3 . doc Computation "If-Then-Else function." $ ["If the bool predicate is true then evaluate the first expression, otherwise the second."] - Inl -> function 1 "Put the value into the left component of a sum type." - Inr -> function 1 "Put the value into the right component of a sum type." - Case -> function 3 "Evaluate one of the given functions on a value of sum type." - Fst -> function 1 "Get the first value of a pair." - Snd -> function 1 "Get the second value of a pair." - Force -> function 1 "Force the evaluation of a delayed value." - Not -> function 1 "Negate the boolean value." - Neg -> unaryOp "-" 7 P "Negate the given integer value." - Add -> binaryOp "+" 6 L "Add the given integer values." - And -> binaryOp "&&" 3 R "Logical and (true if both values are true)." - Or -> binaryOp "||" 2 R "Logical or (true if either value is true)." - Sub -> binaryOp "-" 6 L "Subtract the given integer values." - Mul -> binaryOp "*" 7 L "Multiply the given integer values." - Div -> binaryOp "/" 7 L "Divide the left integer value by the right one, rounding down." - Exp -> binaryOp "^" 8 R "Raise the left integer value to the power of the right one." - Eq -> binaryOp "==" 4 N "Check that the left value is equal to the right one." - Neq -> binaryOp "!=" 4 N "Check that the left value is not equal to the right one." - Lt -> binaryOp "<" 4 N "Check that the left value is lesser than the right one." - Gt -> binaryOp ">" 4 N "Check that the left value is greater than the right one." - Leq -> binaryOp "<=" 4 N "Check that the left value is lesser or equal to the right one." - Geq -> binaryOp ">=" 4 N "Check that the left value is greater or equal to the right one." - Format -> function 1 "Turn an arbitrary value into a string." - Concat -> binaryOp "++" 6 R "Concatenate the given strings." - Chars -> function 1 "Counts the number of characters in the text." + Inl -> function 1 $ shortDoc Computation "Put the value into the left component of a sum type." + Inr -> function 1 $ shortDoc Computation "Put the value into the right component of a sum type." + Case -> function 3 $ shortDoc Computation "Evaluate one of the given functions on a value of sum type." + Fst -> function 1 $ shortDoc Computation "Get the first value of a pair." + Snd -> function 1 $ shortDoc Computation "Get the second value of a pair." + Force -> function 1 $ shortDoc Computation "Force the evaluation of a delayed value." + Not -> function 1 $ shortDoc Computation "Negate the boolean value." + Neg -> unaryOp "-" 7 P $ shortDoc Computation "Negate the given integer value." + Add -> binaryOp "+" 6 L $ shortDoc Computation "Add the given integer values." + And -> binaryOp "&&" 3 R $ shortDoc Computation "Logical and (true if both values are true)." + Or -> binaryOp "||" 2 R $ shortDoc Computation "Logical or (true if either value is true)." + Sub -> binaryOp "-" 6 L $ shortDoc Computation "Subtract the given integer values." + Mul -> binaryOp "*" 7 L $ shortDoc Computation "Multiply the given integer values." + Div -> binaryOp "/" 7 L $ shortDoc Computation "Divide the left integer value by the right one, rounding down." + Exp -> binaryOp "^" 8 R $ shortDoc Computation "Raise the left integer value to the power of the right one." + Eq -> binaryOp "==" 4 N $ shortDoc Computation "Check that the left value is equal to the right one." + Neq -> binaryOp "!=" 4 N $ shortDoc Computation "Check that the left value is not equal to the right one." + Lt -> binaryOp "<" 4 N $ shortDoc Computation "Check that the left value is lesser than the right one." + Gt -> binaryOp ">" 4 N $ shortDoc Computation "Check that the left value is greater than the right one." + Leq -> binaryOp "<=" 4 N $ shortDoc Computation "Check that the left value is lesser or equal to the right one." + Geq -> binaryOp ">=" 4 N $ shortDoc Computation "Check that the left value is greater or equal to the right one." + Format -> function 1 $ shortDoc Computation "Turn an arbitrary value into a string." + Concat -> binaryOp "++" 6 R $ shortDoc Computation "Concatenate the given strings." + Chars -> function 1 $ shortDoc Computation "Counts the number of characters in the text." Split -> - function 2 . doc "Split the text into two at given position." $ + function 2 . doc Computation "Split the text into two at given position." $ [ "To be more specific, the following holds for all `text` values `s1` and `s2`:" , "`(s1,s2) == split (chars s1) (s1 ++ s2)`" , "So split can be used to undo concatenation if you know the length of the original string." ] CharAt -> - function 2 . doc "Get the character at a given index." $ + function 2 . doc Computation "Get the character at a given index." $ [ "Gets the character (as an `int` representing a Unicode codepoint) at a specific index in a `text` value. Valid indices are 0 through `chars t - 1`." , "Throws an exception if given an out-of-bounds index." ] ToChar -> - function 1 . doc "Create a singleton `text` value from the given character code." $ + function 1 . doc Computation "Create a singleton `text` value from the given character code." $ [ "That is, `chars (toChar c) == 1` and `charAt 0 (toChar c) == c`." ] AppF -> - binaryOp "$" 0 R . doc "Apply the function on the left to the value on the right." $ + binaryOp "$" 0 R . doc Computation "Apply the function on the left to the value on the right." $ [ "This operator is useful to avoid nesting parentheses." , "For exaple:" , "`f $ g $ h x = f (g (h x))`" ] Swap -> - command 1 short . doc "Swap placed entity with one in inventory." $ + command 1 short . doc (Mutation $ Set.fromList [EntityChange, RobotChange InventoryChange]) "Swap placed entity with one in inventory." $ [ "This essentially works like atomic grab and place." , "Use this to avoid race conditions where more robots grab, scan or place in one location." ] Atomic -> - command 1 Intangible . doc "Execute a block of commands atomically." $ + command 1 Intangible . doc MetaEffect "Execute a block of commands atomically." $ [ "When executing `atomic c`, a robot will not be interrupted, that is, no other robots will execute any commands while the robot is executing @c@." ] Instant -> - command 1 Intangible . doc "Execute a block of commands instantly." $ + command 1 Intangible . doc MetaEffect "Execute a block of commands instantly." $ [ "Like `atomic`, but with no restriction on program size." ] Key -> - function 1 . doc "Create a key value from a text description." $ + function 1 . doc Computation "Create a key value from a text description." $ [ "The key description can optionally start with modifiers like 'C-', 'M-', 'A-', or 'S-', followed by either a regular key, or a special key name like 'Down' or 'End'" , "For example, 'M-C-x', 'Down', or 'S-4'." , "Which key combinations are actually possible to type may vary by keyboard and terminal program." ] InstallKeyHandler -> - command 2 Intangible . doc "Install a keyboard input handler." $ + command 2 Intangible . doc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Install a keyboard input handler." $ [ "The first argument is a hint line that will be displayed when the input handler is active." , "The second argument is a function to handle keyboard inputs." ] - Teleport -> command 2 short "Teleport a robot to the given location." - As -> command 2 Intangible "Hypothetically run a command as if you were another robot." - RobotNamed -> command 1 Intangible "Find an actor by name." - RobotNumbered -> command 1 Intangible "Find an actor by number." - Knows -> command 1 Intangible "Check if the robot knows about an entity." + Teleport -> command 2 short $ shortDoc (Mutation $ Set.singleton $ RobotChange PositionChange) "Teleport a robot to the given location." + As -> command 2 Intangible $ shortDoc (Mutation $ Set.singleton $ RobotChange BehaviorChange) "Hypothetically run a command as if you were another robot." + RobotNamed -> command 1 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Find an actor by name." + RobotNumbered -> command 1 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Find an actor by number." + Knows -> command 1 Intangible $ shortDoc (Query $ Sensing RobotSensing) "Check if the robot knows about an entity." where - doc b ls = ConstDoc b (T.unlines ls) + doc e b ls = ConstDoc e b (T.unlines ls) + shortDoc e b = ConstDoc e b "" unaryOp s p side d = ConstInfo { syntax = s diff --git a/src/Swarm/Language/Syntax/CommandMetadata.hs b/src/Swarm/Language/Syntax/CommandMetadata.hs new file mode 100644 index 0000000000..bd8abdcf08 --- /dev/null +++ b/src/Swarm/Language/Syntax/CommandMetadata.hs @@ -0,0 +1,42 @@ +-- | +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Command metadata for documentation +module Swarm.Language.Syntax.CommandMetadata where + +import Data.Aeson (ToJSON) +import Data.Set (Set) +import GHC.Generics (Generic) + +data SensingType + = RobotSensing + | EntitySensing + | WorldCondition + deriving (Eq, Ord, Show, Generic, ToJSON) + +data QueryType + = -- | empirical knowledge + Sensing SensingType + | -- | a priori knowledge + APriori + deriving (Eq, Ord, Show, Generic, ToJSON) + +data RobotChangeType + = PositionChange + | InventoryChange + | ExistenceChange + | BehaviorChange + deriving (Eq, Ord, Show, Generic, ToJSON) + +data MutationType + = Cosmetic + | EntityChange + | RobotChange RobotChangeType + deriving (Eq, Ord, Show, Generic, ToJSON) + +data CommandEffect + = Computation + | Query QueryType + | MetaEffect + | Mutation (Set MutationType) + deriving (Eq, Ord, Show, Generic, ToJSON) diff --git a/src/Swarm/Web.hs b/src/Swarm/Web.hs index 3f0a56bbf5..dd3463e528 100644 --- a/src/Swarm/Web.hs +++ b/src/Swarm/Web.hs @@ -63,6 +63,7 @@ import Servant import Servant.Docs (ToCapture) import Servant.Docs qualified as SD import Servant.Docs.Internal qualified as SD (renderCurlBasePath) +import Swarm.Doc.Wiki.Matrix import Swarm.Game.Robot import Swarm.Game.Scenario.Objective import Swarm.Game.Scenario.Objective.Graph @@ -105,6 +106,7 @@ type SwarmAPI = :<|> "code" :> "render" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text :<|> "code" :> "run" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text :<|> "paths" :> "log" :> Get '[JSON] (RingBuffer CacheLogEntry) + :<|> "commands" :> Get '[JSON] CommandCatalog :<|> "repl" :> "history" :> "full" :> Get '[JSON] [REPLHistItem] swarmApi :: Proxy SwarmAPI @@ -159,6 +161,7 @@ mkApp state events = :<|> codeRenderHandler :<|> codeRunHandler events :<|> pathsLogHandler state + :<|> cmdMatrixHandler state :<|> replHandler state robotsHandler :: ReadableIORef AppState -> Handler [Robot] @@ -235,6 +238,9 @@ pathsLogHandler appStateRef = do appState <- liftIO (readIORef appStateRef) pure $ appState ^. gameState . pathCaching . pathCachingLog +cmdMatrixHandler :: ReadableIORef AppState -> Handler CommandCatalog +cmdMatrixHandler _ = pure getCatalog + replHandler :: ReadableIORef AppState -> Handler [REPLHistItem] replHandler appStateRef = do appState <- liftIO (readIORef appStateRef) diff --git a/swarm.cabal b/swarm.cabal index 43c88fe393..4a5aa17a63 100644 --- a/swarm.cabal +++ b/swarm.cabal @@ -110,7 +110,7 @@ library Swarm.Doc.Schema.SchemaType Swarm.Doc.Util Swarm.Doc.Wiki.Cheatsheet - Swarm.Game.Failure + Swarm.Doc.Wiki.Matrix Swarm.Game.Achievement.Attainment Swarm.Game.Achievement.Definitions Swarm.Game.Achievement.Description @@ -121,6 +121,7 @@ library Swarm.Game.Entity.Cosmetic Swarm.Game.Entity.Cosmetic.Assignment Swarm.Game.Exception + Swarm.Game.Failure Swarm.Game.Location Swarm.Game.Recipe Swarm.Game.ResourceLoading @@ -205,6 +206,7 @@ library Swarm.Language.Pretty Swarm.Language.Requirement Swarm.Language.Syntax + Swarm.Language.Syntax.CommandMetadata Swarm.Language.Text.Markdown Swarm.Language.Typecheck Swarm.Language.Typecheck.Unify @@ -212,6 +214,7 @@ library Swarm.Language.Types Swarm.Language.Value Swarm.ReadableIORef + Swarm.TUI.Border Swarm.TUI.View Swarm.TUI.View.Achievement Swarm.TUI.View.Attribute.Attr @@ -222,7 +225,6 @@ library Swarm.TUI.View.Objective Swarm.TUI.View.Structure Swarm.TUI.View.Util - Swarm.TUI.Border Swarm.Game.Scenario.Topography.Area Swarm.TUI.Editor.Controller Swarm.TUI.Editor.Json diff --git a/web/command-matrix.html b/web/command-matrix.html new file mode 100644 index 0000000000..e1fe618d0e --- /dev/null +++ b/web/command-matrix.html @@ -0,0 +1,33 @@ + + + + Command matrix + + + + + + + + + + + + + + + + + + + + + +
CommandActor targetPure computation
+ + \ No newline at end of file diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..dd117f16328807ce2e48507499594fdc4efaf8bd GIT binary patch literal 7746 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 X_KEtyxGM0aHPbOYW8FNgL)iZTFzC&z literal 0 HcmV?d00001 diff --git a/web/script/command-matrix.js b/web/script/command-matrix.js new file mode 100644 index 0000000000..0021efb712 --- /dev/null +++ b/web/script/command-matrix.js @@ -0,0 +1,47 @@ +function mkLink(text, url) { + const anchor = document.createElement("a"); + anchor.href = url + anchor.textContent = text; + return anchor; +} + +function insertTableRows(myTableBody, entries) { + for (const entry of entries) { + const rowItem = document.createElement("tr"); + + const vals = [ + mkLink(entry.cmd, "https://github.com/swarm-game/swarm/wiki/Commands-Cheat-Sheet#" + entry.cmd), + document.createTextNode(entry.derivedAttrs.hasActorTarget), + document.createTextNode(entry.derivedAttrs.pureComputation), + ]; + + for (const val of vals) { + const cellElement = document.createElement("td"); + cellElement.appendChild(val); + rowItem.append(cellElement); + } + + myTableBody.appendChild(rowItem); + } +} + +function doFetch(myTable) { + fetch("commands") + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP error, status = ${response.status}`); + } + return response.json(); + }) + .then((data) => { + const myTableBody = myTable.querySelector("tbody"); + insertTableRows(myTableBody, data.entries); + // Documentation: http://tristen.ca/tablesort/demo/ + new Tablesort(document.getElementById('my-table')); + }) + .catch((error) => { + const p = document.createElement("p"); + p.appendChild(document.createTextNode(`Error: ${error.message}`)); + document.body.insertBefore(p, myTable); + }); +} \ No newline at end of file diff --git a/web/style/command-matrix.css b/web/style/command-matrix.css new file mode 100644 index 0000000000..eb3756eedf --- /dev/null +++ b/web/style/command-matrix.css @@ -0,0 +1,26 @@ +body { + font-family: sans-serif; +} + +a:link { + color: lavender; + text-decoration: none; +} + +/* visited link */ +a:visited { + color: lightblue; + text-decoration: none; +} + +/* mouse over link */ +a:hover { + color: hotpink; + text-decoration: underline; +} + +/* selected link */ +a:active { + color: blue; + text-decoration: underline; +} \ No newline at end of file diff --git a/web/style/tablesort.css b/web/style/tablesort.css new file mode 100644 index 0000000000..8510f7df25 --- /dev/null +++ b/web/style/tablesort.css @@ -0,0 +1,48 @@ +body { + color: white; + background-color: #212121; + } + + table { + margin-left: auto; + margin-right: auto; + } + + th, td { + padding: 0.25em; + } + + th[role=columnheader]:not(.no-sort) { + cursor: pointer; + } + + th[role=columnheader]:not(.no-sort):after { + content: ''; + float: right; + margin-top: 7px; + margin-left: 5px; + border-width: 0 4px 4px; + border-style: solid; + border-color: white transparent; + visibility: hidden; + opacity: 0; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + } + + th[aria-sort=ascending]:not(.no-sort):after { + border-bottom: none; + border-width: 4px 4px 0; + } + + th[aria-sort]:not(.no-sort):after { + visibility: visible; + opacity: 0.4; + } + + th[role=columnheader]:not(.no-sort):hover:after { + visibility: visible; + opacity: 1; + } \ No newline at end of file