mirror of
https://github.com/swarm-game/swarm.git
synced 2024-10-05 20:19:09 +03:00
Render command matrix (#1658)
## Demo 1. Run `scripts/play.sh` 2. Load http://localhost:5357/command-matrix.html The rows are sortable by column. ### Also stack build swarm:swarm-docs --fast && stack exec swarm-docs -- cheatsheet --matrix ## Screenshot ![Screenshot from 2024-01-21 21-32-56](https://github.com/swarm-game/swarm/assets/261693/f92f5ac9-8440-4aac-9a4b-9e5edac616f2)
This commit is contained in:
parent
101b17c882
commit
5f53082971
@ -50,6 +50,7 @@ cliParser =
|
|||||||
, Just Recipes <$ switch (long "recipes" <> help "Generate recipes page (uses data from recipes.yaml)")
|
, 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 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 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")
|
, Just Scenario <$ switch (long "scenario" <> help "Generate scenario schema page")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import Swarm.Doc.Schema.Parse
|
|||||||
import Swarm.Doc.Schema.Refined
|
import Swarm.Doc.Schema.Refined
|
||||||
import Swarm.Doc.Schema.SchemaType
|
import Swarm.Doc.Schema.SchemaType
|
||||||
import Swarm.Doc.Util
|
import Swarm.Doc.Util
|
||||||
|
import Swarm.Doc.Wiki.Util
|
||||||
import Swarm.Util (applyWhen, brackets, quote, showT)
|
import Swarm.Util (applyWhen, brackets, quote, showT)
|
||||||
import System.Directory (listDirectory)
|
import System.Directory (listDirectory)
|
||||||
import System.FilePath (splitExtension, (<.>), (</>))
|
import System.FilePath (splitExtension, (<.>), (</>))
|
||||||
@ -112,9 +113,7 @@ recombineExtension (filenameStem, fileExtension) =
|
|||||||
|
|
||||||
genMarkdown :: [SchemaData] -> Either T.Text T.Text
|
genMarkdown :: [SchemaData] -> Either T.Text T.Text
|
||||||
genMarkdown schemaThings =
|
genMarkdown schemaThings =
|
||||||
left renderError $
|
pandocToText pd
|
||||||
runPure $
|
|
||||||
writeMarkdown (def {writerExtensions = extensionsFromList [Ext_pipe_tables]}) pd
|
|
||||||
where
|
where
|
||||||
titleMap = makeTitleMap schemaThings
|
titleMap = makeTitleMap schemaThings
|
||||||
pd =
|
pd =
|
||||||
|
@ -25,6 +25,8 @@ import Data.Text qualified as T
|
|||||||
import Data.Text.IO qualified as T
|
import Data.Text.IO qualified as T
|
||||||
import Swarm.Doc.Schema.Render
|
import Swarm.Doc.Schema.Render
|
||||||
import Swarm.Doc.Util
|
import Swarm.Doc.Util
|
||||||
|
import Swarm.Doc.Wiki.Matrix
|
||||||
|
import Swarm.Doc.Wiki.Util
|
||||||
import Swarm.Game.Display (displayChar)
|
import Swarm.Game.Display (displayChar)
|
||||||
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities)
|
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities)
|
||||||
import Swarm.Game.Entity qualified as E
|
import Swarm.Game.Entity qualified as E
|
||||||
@ -52,7 +54,7 @@ data PageAddress = PageAddress
|
|||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
-- | An enumeration of the kinds of cheat sheets we can produce.
|
-- | 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)
|
deriving (Eq, Show, Enum, Bounded)
|
||||||
|
|
||||||
-- * Functions
|
-- * Functions
|
||||||
@ -62,6 +64,9 @@ makeWikiPage address s = case s of
|
|||||||
Nothing -> error "Not implemented for all Wikis"
|
Nothing -> error "Not implemented for all Wikis"
|
||||||
Just st -> case st of
|
Just st -> case st of
|
||||||
Commands -> T.putStrLn commandsPage
|
Commands -> T.putStrLn commandsPage
|
||||||
|
CommandMatrix -> case pandocToText commandsMatrix of
|
||||||
|
Right x -> T.putStrLn x
|
||||||
|
Left x -> error $ T.unpack x
|
||||||
Capabilities -> simpleErrorHandle $ do
|
Capabilities -> simpleErrorHandle $ do
|
||||||
entities <- loadEntities
|
entities <- loadEntities
|
||||||
sendIO $ T.putStrLn $ capabilityPage address entities
|
sendIO $ T.putStrLn $ capabilityPage address entities
|
||||||
|
40
app/doc/Swarm/Doc/Wiki/Matrix.hs
Normal file
40
app/doc/Swarm/Doc/Wiki/Matrix.hs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
--
|
||||||
|
-- Auto-generation of command attributes matrix.
|
||||||
|
module Swarm.Doc.Wiki.Matrix where
|
||||||
|
|
||||||
|
import Data.List.NonEmpty qualified as NE
|
||||||
|
import Data.Text qualified as T
|
||||||
|
import Swarm.Doc.Command
|
||||||
|
import Text.Pandoc
|
||||||
|
import Text.Pandoc.Builder
|
||||||
|
|
||||||
|
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
|
15
app/doc/Swarm/Doc/Wiki/Util.hs
Normal file
15
app/doc/Swarm/Doc/Wiki/Util.hs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
-- |
|
||||||
|
-- SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
--
|
||||||
|
-- Utilities for generating doc markup
|
||||||
|
module Swarm.Doc.Wiki.Util where
|
||||||
|
|
||||||
|
import Control.Arrow (left)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Text.Pandoc
|
||||||
|
|
||||||
|
pandocToText :: Pandoc -> Either Text Text
|
||||||
|
pandocToText =
|
||||||
|
left renderError
|
||||||
|
. runPure
|
||||||
|
. writeMarkdown (def {writerExtensions = extensionsFromList [Ext_pipe_tables]})
|
76
src/Swarm/Doc/Command.hs
Normal file
76
src/Swarm/Doc/Command.hs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
-- |
|
||||||
|
-- SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
--
|
||||||
|
-- Auto-generation of command attributes matrix.
|
||||||
|
module Swarm.Doc.Command where
|
||||||
|
|
||||||
|
import Data.Aeson (ToJSON)
|
||||||
|
import Data.List.NonEmpty qualified as NE
|
||||||
|
import Data.Set (Set)
|
||||||
|
import Data.Set qualified as Set
|
||||||
|
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 Swarm.Util (listEnums)
|
||||||
|
|
||||||
|
data DerivedAttrs = DerivedAttrs
|
||||||
|
{ hasActorTarget :: Bool
|
||||||
|
, pureComputation :: Bool
|
||||||
|
, modifiesEnvironment :: Bool
|
||||||
|
, modifiesRobot :: Bool
|
||||||
|
, movesRobot :: Bool
|
||||||
|
, returnsValue :: Bool
|
||||||
|
, outputType :: String
|
||||||
|
}
|
||||||
|
deriving (Generic, ToJSON)
|
||||||
|
|
||||||
|
data CommandEntry = CommandEntry
|
||||||
|
{ cmd :: Const
|
||||||
|
, effects :: Set 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
|
||||||
|
|
||||||
|
-- | Uses explicit effects documentation as well as
|
||||||
|
-- type signature information to compute various flags
|
||||||
|
mkEntry :: Const -> CommandEntry
|
||||||
|
mkEntry c =
|
||||||
|
CommandEntry c cmdEffects rawArgs $
|
||||||
|
DerivedAttrs
|
||||||
|
{ hasActorTarget = operatesOnActor inputArgs
|
||||||
|
, pureComputation = Set.null cmdEffects
|
||||||
|
, modifiesEnvironment = Mutation EntityChange `Set.member` cmdEffects
|
||||||
|
, modifiesRobot = not . Set.disjoint cmdEffects . Set.fromList $ map (Mutation . RobotChange) listEnums
|
||||||
|
, movesRobot = Mutation (RobotChange PositionChange) `Set.member` cmdEffects
|
||||||
|
, returnsValue = theOutputType /= TyCmd TyUnit
|
||||||
|
, outputType = show theOutputType
|
||||||
|
}
|
||||||
|
where
|
||||||
|
cmdInfo = constInfo c
|
||||||
|
cmdEffects = effectInfo $ constDoc cmdInfo
|
||||||
|
|
||||||
|
getArgs ((Forall _ t)) = unchainFun t
|
||||||
|
|
||||||
|
rawArgs = getArgs $ inferConst c
|
||||||
|
|
||||||
|
inputArgs = NE.init rawArgs
|
||||||
|
theOutputType = NE.last rawArgs
|
||||||
|
|
||||||
|
operatesOnActor = elem TyActor
|
||||||
|
|
||||||
|
getCatalog :: CommandCatalog
|
||||||
|
getCatalog = CommandCatalog $ map mkEntry commands
|
@ -97,13 +97,15 @@ import Data.Int (Int32)
|
|||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.List.NonEmpty qualified as NonEmpty
|
import Data.List.NonEmpty qualified as NonEmpty
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
|
import Data.Set (Set)
|
||||||
import Data.Set qualified as S
|
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 hiding (filter, length, map)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Data.Tree
|
import Data.Tree
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Swarm.Language.Direction
|
import Swarm.Language.Direction
|
||||||
|
import Swarm.Language.Syntax.CommandMetadata
|
||||||
import Swarm.Language.Types
|
import Swarm.Language.Types
|
||||||
import Swarm.Util qualified as Util
|
import Swarm.Util qualified as Util
|
||||||
import Witch.From (from)
|
import Witch.From (from)
|
||||||
@ -402,15 +404,21 @@ data ConstInfo = ConstInfo
|
|||||||
}
|
}
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
data ConstDoc = ConstDoc {briefDoc :: Text, longDoc :: Text}
|
data ConstDoc = ConstDoc
|
||||||
|
{ effectInfo :: Set CommandEffect
|
||||||
|
-- ^ NOTE: The absence of effects implies a "pure computation".
|
||||||
|
, briefDoc :: Text
|
||||||
|
, longDoc :: Text
|
||||||
|
}
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
instance IsString ConstDoc where
|
|
||||||
fromString = flip ConstDoc "" . T.pack
|
|
||||||
|
|
||||||
data ConstMeta
|
data ConstMeta
|
||||||
= -- | Function with arity of which some are commands
|
= -- | Function with arity of which some are commands
|
||||||
ConstMFunc Int Bool
|
ConstMFunc
|
||||||
|
-- | Arity
|
||||||
|
Int
|
||||||
|
-- | Is a command?
|
||||||
|
Bool
|
||||||
| -- | Unary operator with fixity and associativity.
|
| -- | Unary operator with fixity and associativity.
|
||||||
ConstMUnOp MUnAssoc
|
ConstMUnOp MUnAssoc
|
||||||
| -- | Binary operator with fixity and associativity.
|
| -- | Binary operator with fixity and associativity.
|
||||||
@ -527,88 +535,163 @@ isLong c = case tangibility (constInfo c) of
|
|||||||
-- matching gives us warning if we add more constants.
|
-- matching gives us warning if we add more constants.
|
||||||
constInfo :: Const -> ConstInfo
|
constInfo :: Const -> ConstInfo
|
||||||
constInfo c = case c of
|
constInfo c = case c of
|
||||||
Wait -> command 0 long "Wait for a number of time steps."
|
Wait ->
|
||||||
|
command 0 long $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange BehaviorChange)
|
||||||
|
"Wait for a number of time steps."
|
||||||
Noop ->
|
Noop ->
|
||||||
command 0 Intangible . doc "Do nothing." $
|
command 0 Intangible . doc Set.empty "Do nothing." $
|
||||||
[ "This is different than `Wait` in that it does not take up a time step."
|
[ "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."
|
, "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."
|
, "Usually it is automatically inserted where needed, so you do not have to worry about it."
|
||||||
]
|
]
|
||||||
Selfdestruct ->
|
Selfdestruct ->
|
||||||
command 0 short . doc "Self-destruct a robot." $
|
command 0 short
|
||||||
[ "Useful to not clutter the world."
|
. doc
|
||||||
, "This destroys the robot's inventory, so consider `salvage` as an alternative."
|
(Set.singleton $ Mutation $ RobotChange ExistenceChange)
|
||||||
]
|
"Self-destruct a robot."
|
||||||
Move -> command 0 short "Move forward one step."
|
$ [ "Useful to not clutter the world."
|
||||||
Backup -> command 0 short "Move backward one step."
|
, "This destroys the robot's inventory, so consider `salvage` as an alternative."
|
||||||
|
]
|
||||||
|
Move ->
|
||||||
|
command 0 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange PositionChange)
|
||||||
|
"Move forward one step."
|
||||||
|
Backup ->
|
||||||
|
command 0 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange PositionChange)
|
||||||
|
"Move backward one step."
|
||||||
Path ->
|
Path ->
|
||||||
command 2 short . doc "Obtain shortest path to the destination." $
|
command 2 short
|
||||||
[ "Optionally supply a distance limit as the first argument."
|
. doc
|
||||||
, "Supply either a location (`inL`) or an entity (`inR`) as the second argument."
|
(Set.singleton $ Query $ Sensing EntitySensing)
|
||||||
, "If a path exists, returns the direction to proceed along and the remaining distance."
|
"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 and the remaining distance."
|
||||||
|
]
|
||||||
Push ->
|
Push ->
|
||||||
command 1 short . doc "Push an entity forward one step." $
|
command 1 short
|
||||||
[ "Both entity and robot moves forward one step."
|
. doc
|
||||||
, "Destination must not contain an entity."
|
(Set.fromList [Mutation EntityChange, Mutation $ RobotChange PositionChange])
|
||||||
]
|
"Push an entity forward one step."
|
||||||
|
$ [ "Both entity and robot moves forward one step."
|
||||||
|
, "Destination must not contain an entity."
|
||||||
|
]
|
||||||
Stride ->
|
Stride ->
|
||||||
command 1 short . doc "Move forward multiple steps." $
|
command 1 short
|
||||||
[ T.unwords ["Has a max range of", T.pack $ show maxStrideRange, "units."]
|
. doc
|
||||||
]
|
(Set.singleton $ Mutation $ RobotChange PositionChange)
|
||||||
Turn -> command 1 short "Turn in some direction."
|
"Move forward multiple steps."
|
||||||
Grab -> command 0 short "Grab an item from the current location."
|
$ [ T.unwords ["Has a max range of", T.pack $ show maxStrideRange, "units."]
|
||||||
|
]
|
||||||
|
Turn ->
|
||||||
|
command 1 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange PositionChange)
|
||||||
|
"Turn in some direction."
|
||||||
|
Grab ->
|
||||||
|
command 0 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange])
|
||||||
|
"Grab an item from the current location."
|
||||||
Harvest ->
|
Harvest ->
|
||||||
command 0 short . doc "Harvest an item from the current location." $
|
command 0 short . doc (Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange]) "Harvest an item from the current location." $
|
||||||
[ "Leaves behind a growing seed if the harvested item is growable."
|
[ "Leaves behind a growing seed if the harvested item is growable."
|
||||||
, "Otherwise it works exactly like `grab`."
|
, "Otherwise it works exactly like `grab`."
|
||||||
]
|
]
|
||||||
Ignite ->
|
Ignite ->
|
||||||
command 1 short . doc "Ignite a combustible item in the specified direction." $
|
command 1 short
|
||||||
[ "Combustion persists for a random duration and may spread."
|
. doc
|
||||||
]
|
(Set.singleton $ Mutation EntityChange)
|
||||||
|
"Ignite a combustible item in the specified direction."
|
||||||
|
$ [ "Combustion persists for a random duration and may spread."
|
||||||
|
]
|
||||||
Place ->
|
Place ->
|
||||||
command 1 short . doc "Place an item at the current location." $
|
command 1 short
|
||||||
["The current location has to be empty for this to work."]
|
. doc
|
||||||
|
(Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange])
|
||||||
|
"Place an item at the current location."
|
||||||
|
$ ["The current location has to be empty for this to work."]
|
||||||
Ping ->
|
Ping ->
|
||||||
command 1 short . doc "Obtain the relative location of another robot." $
|
command 1 short
|
||||||
[ "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."
|
. doc
|
||||||
, "The location (x, y) is given relative to one's current orientation:"
|
(Set.singleton $ Query $ Sensing RobotSensing)
|
||||||
, "Positive x value is to the right, negative left. Likewise, positive y value is forward, negative back."
|
"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."
|
||||||
Give -> command 2 short "Give an item to another actor nearby."
|
, "The location (x, y) is given relative to one's current orientation:"
|
||||||
Equip -> command 1 short "Equip a device on oneself."
|
, "Positive x value is to the right, negative left. Likewise, positive y value is forward, negative back."
|
||||||
Unequip -> command 1 short "Unequip an equipped device, returning to inventory."
|
]
|
||||||
Make -> command 1 long "Make an item using a recipe."
|
Give ->
|
||||||
Has -> command 1 Intangible "Sense whether the robot has a given item in its inventory."
|
command 2 short $
|
||||||
Equipped -> command 1 Intangible "Sense whether the robot has a specific device equipped."
|
shortDoc
|
||||||
Count -> command 1 Intangible "Get the count of a given item in a robot's inventory."
|
(Set.singleton $ Mutation $ RobotChange InventoryChange)
|
||||||
|
"Give an item to another actor nearby."
|
||||||
|
Equip ->
|
||||||
|
command 1 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.fromList [Mutation $ RobotChange InventoryChange, Mutation $ RobotChange BehaviorChange])
|
||||||
|
"Equip a device on oneself."
|
||||||
|
Unequip ->
|
||||||
|
command 1 short $
|
||||||
|
shortDoc
|
||||||
|
(Set.fromList [Mutation $ RobotChange InventoryChange, Mutation $ RobotChange BehaviorChange])
|
||||||
|
"Unequip an equipped device, returning to inventory."
|
||||||
|
Make ->
|
||||||
|
command 1 long $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange InventoryChange)
|
||||||
|
"Make an item using a recipe."
|
||||||
|
Has ->
|
||||||
|
command 1 Intangible $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Query $ Sensing RobotSensing)
|
||||||
|
"Sense whether the robot has a given item in its inventory."
|
||||||
|
Equipped ->
|
||||||
|
command 1 Intangible $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Query $ Sensing RobotSensing)
|
||||||
|
"Sense whether the robot has a specific device equipped."
|
||||||
|
Count ->
|
||||||
|
command 1 Intangible $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Query $ Sensing RobotSensing)
|
||||||
|
"Get the count of a given item in a robot's inventory."
|
||||||
Reprogram ->
|
Reprogram ->
|
||||||
command 2 long . doc "Reprogram another robot with a new command." $
|
command 2 long
|
||||||
["The other robot has to be nearby and idle."]
|
. doc
|
||||||
|
(Set.singleton $ Mutation $ RobotChange BehaviorChange)
|
||||||
|
"Reprogram another robot with a new command."
|
||||||
|
$ ["The other robot has to be nearby and idle."]
|
||||||
Drill ->
|
Drill ->
|
||||||
command 1 long . doc "Drill through an entity." $
|
command 1 long
|
||||||
[ "Usually you want to `drill forward` when exploring to clear out obstacles."
|
. doc
|
||||||
, "When you have found a source to drill, you can stand on it and `drill down`."
|
(Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange])
|
||||||
, "See what recipes with drill you have available."
|
"Drill through an entity."
|
||||||
, "The `drill` command may return the name of an entity added to your inventory."
|
$ [ "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 ->
|
Use ->
|
||||||
command 2 long . doc "Use one entity upon another." $
|
command 2 long . doc (Set.singleton $ Mutation EntityChange) "Use one entity upon another." $
|
||||||
[ "Which entities you can `use` with others depends on the available recipes."
|
[ "Which entities you can `use` with others depends on the available recipes."
|
||||||
, "The object being used must be a 'required' entity in a recipe."
|
, "The object being used must be a 'required' entity in a recipe."
|
||||||
]
|
]
|
||||||
Build ->
|
Build ->
|
||||||
command 1 long . doc "Construct a new robot." $
|
command 1 long . doc (Set.singleton $ Mutation $ RobotChange ExistenceChange) "Construct a new robot." $
|
||||||
[ "You can specify a command for the robot to execute."
|
[ "You can specify a command for the robot to execute."
|
||||||
, "If the command requires devices they will be taken from your inventory and "
|
, "If the command requires devices they will be taken from your inventory and "
|
||||||
<> "equipped on the new robot."
|
<> "equipped on the new robot."
|
||||||
]
|
]
|
||||||
Salvage ->
|
Salvage ->
|
||||||
command 0 long . doc "Deconstruct an old robot." $
|
command 0 long . doc (Set.singleton $ Mutation $ RobotChange ExistenceChange) "Deconstruct an old robot." $
|
||||||
["Salvaging a robot will give you its inventory, equipped devices and log."]
|
["Salvaging a robot will give you its inventory, equipped devices and log."]
|
||||||
Say ->
|
Say ->
|
||||||
command 1 short . doc "Emit a message." $
|
command 1 short . doc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "Emit a message." $
|
||||||
[ "The message will be in the robot's log (if it has one) and the global log."
|
[ "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 "
|
, "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."
|
<> "in the messages panel, along with your own messages and logs."
|
||||||
@ -617,208 +700,217 @@ constInfo c = case c of
|
|||||||
, "In creative mode, there is of course no such limitation."
|
, "In creative mode, there is of course no such limitation."
|
||||||
]
|
]
|
||||||
Listen ->
|
Listen ->
|
||||||
command 1 long . doc "Listen for a message from other actors." $
|
command 1 long . doc (Set.singleton $ Query $ Sensing RobotSensing) "Listen for a message from other actors." $
|
||||||
[ "It will take the first message said by the closest actor."
|
[ "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, "
|
, "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."
|
<> "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."
|
, "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 (Set.singleton $ Mutation LogEmission) "Log the string in the robot's logger."
|
||||||
View ->
|
View ->
|
||||||
command 1 short . doc "View the given actor." $
|
command 1 short . doc (Set.singleton $ 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."
|
[ "This will recenter the map on the target robot and allow its inventory and logs to be inspected."
|
||||||
]
|
]
|
||||||
Appear ->
|
Appear ->
|
||||||
command 1 short . doc "Set how the robot is displayed." $
|
command 1 short . doc (Set.singleton $ Mutation Cosmetic) "Set how the robot is displayed." $
|
||||||
[ "You can either specify one character or five (for each direction)."
|
[ "You can either specify one character or five (for each direction)."
|
||||||
, "The default is \"X^>v<\"."
|
, "The default is \"X^>v<\"."
|
||||||
]
|
]
|
||||||
Create ->
|
Create ->
|
||||||
command 1 short . doc "Create an item out of thin air." $
|
command 1 short . doc (Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange]) "Create an item out of thin air." $
|
||||||
["Only available in creative mode."]
|
["Only available in creative mode."]
|
||||||
Halt -> command 1 short "Tell a robot to halt."
|
Halt -> command 1 short $ shortDoc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "Tell a robot to halt."
|
||||||
Time -> command 0 Intangible "Get the current time."
|
Time ->
|
||||||
|
command 0 Intangible $
|
||||||
|
shortDoc
|
||||||
|
(Set.singleton $ Query $ Sensing WorldCondition)
|
||||||
|
"Get the current time."
|
||||||
Scout ->
|
Scout ->
|
||||||
command 1 short . doc "Detect whether a robot is within line-of-sight in a direction." $
|
command 1 short . doc (Set.singleton $ Query $ Sensing RobotSensing) "Detect whether a robot is within line-of-sight in a direction." $
|
||||||
[ "Perception is blocked by 'Opaque' entities."
|
[ "Perception is blocked by 'Opaque' entities."
|
||||||
, T.unwords ["Has a max range of", T.pack $ show maxScoutRange, "units."]
|
, 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
|
||||||
|
(Set.singleton $ Query $ Sensing RobotSensing)
|
||||||
|
"Get the current x and y coordinates."
|
||||||
Waypoint ->
|
Waypoint ->
|
||||||
command 2 Intangible . doc "Get the x, y coordinates of a named waypoint, by index" $
|
command 2 Intangible . doc (Set.singleton $ 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."
|
[ "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))."
|
, "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."
|
, "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."
|
, "A robot can use the count to know whether they have iterated over the full waypoint circuit."
|
||||||
]
|
]
|
||||||
Structure ->
|
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 (Set.singleton $ 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."
|
[ "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))."
|
, "Since structures can have multiple occurrences, returns a tuple of (count, (x, y))."
|
||||||
, "The supplied index will be wrapped automatically, modulo the structure count."
|
, "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."
|
, "A robot can use the count to know whether they have iterated over the full structure list."
|
||||||
]
|
]
|
||||||
Floorplan ->
|
Floorplan ->
|
||||||
command 1 Intangible . doc "Get the dimensions of a structure template" $
|
command 1 Intangible . doc (Set.singleton $ Query APriori) "Get the dimensions of a structure template" $
|
||||||
[ "Returns a tuple of (width, height) for the structure of the requested name."
|
[ "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."
|
, "Yields an error if the supplied string is not the name of a structure."
|
||||||
]
|
]
|
||||||
HasTag ->
|
HasTag ->
|
||||||
command 2 Intangible . doc "Check whether the given entity has the given tag" $
|
command 2 Intangible . doc (Set.singleton $ 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."
|
[ "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."
|
, "Yields an error if the first argument is not a valid entity."
|
||||||
]
|
]
|
||||||
TagMembers ->
|
TagMembers ->
|
||||||
command 2 Intangible . doc "Get the entities labeled by a tag, by alphabetical index" $
|
command 2 Intangible . doc (Set.singleton $ Query APriori) "Get the entities labeled by a tag, by alphabetical index" $
|
||||||
[ "Returns a tuple of (member count, entity)."
|
[ "Returns a tuple of (member count, entity)."
|
||||||
, "The supplied index will be wrapped automatically, modulo the member count."
|
, "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."
|
, "A robot can use the count to know whether they have iterated over the full list."
|
||||||
]
|
]
|
||||||
Detect ->
|
Detect ->
|
||||||
command 2 Intangible . doc "Detect an entity within a rectangle." $
|
command 2 Intangible . doc (Set.singleton $ 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."]
|
["Locate the closest instance of a given entity within the rectangle specified by opposite corners, relative to the current location."]
|
||||||
Resonate ->
|
Resonate ->
|
||||||
command 2 Intangible . doc "Count specific entities within a rectangle." $
|
command 2 Intangible . doc (Set.singleton $ 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."
|
[ "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."
|
, "Counts the entities within the rectangle specified by opposite corners, relative to the current location."
|
||||||
]
|
]
|
||||||
Density ->
|
Density ->
|
||||||
command 1 Intangible . doc "Count all entities within a rectangle." $
|
command 1 Intangible . doc (Set.singleton $ 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."
|
[ "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."
|
, "Counts the entities within the rectangle specified by opposite corners, relative to the current location."
|
||||||
]
|
]
|
||||||
Sniff ->
|
Sniff ->
|
||||||
command 1 short . doc "Determine distance to entity." $
|
command 1 short . doc (Set.singleton $ Query $ Sensing EntitySensing) "Determine distance to entity." $
|
||||||
[ "Measures concentration of airborne particles to infer distance to a certain kind of entity."
|
[ "Measures concentration of airborne particles to infer distance to a certain kind of entity."
|
||||||
, "If none is detected, returns (-1)."
|
, "If none is detected, returns (-1)."
|
||||||
, T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."]
|
, T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."]
|
||||||
]
|
]
|
||||||
Chirp ->
|
Chirp ->
|
||||||
command 1 short . doc "Determine direction to entity." $
|
command 1 short . doc (Set.singleton $ 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."
|
[ "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."
|
, "Returns 'down' if out of range or the direction is indeterminate."
|
||||||
, "Provides absolute directions if \"compass\" equipped, relative directions otherwise."
|
, "Provides absolute directions if \"compass\" equipped, relative directions otherwise."
|
||||||
, T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."]
|
, T.unwords ["Has a max range of", T.pack $ show maxSniffRange, "units."]
|
||||||
]
|
]
|
||||||
Watch ->
|
Watch ->
|
||||||
command 1 short . doc "Interrupt `wait` upon location changes." $
|
command 1 short . doc (Set.singleton $ Query $ Sensing EntitySensing) "Interrupt `wait` upon location changes." $
|
||||||
[ "Place seismic detectors to alert upon entity changes to the specified location."
|
[ "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."
|
, "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."
|
, "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."
|
, "Any change to entities at the monitored locations will cause the robot to wake up before the `wait` timeout."
|
||||||
]
|
]
|
||||||
Surveil ->
|
Surveil ->
|
||||||
command 1 short . doc "Interrupt `wait` upon (remote) location changes." $
|
command 1 short . doc (Set.singleton $ Query $ Sensing EntitySensing) "Interrupt `wait` upon (remote) location changes." $
|
||||||
[ "Like `watch`, but with no restriction on distance."
|
[ "Like `watch`, but with no restriction on distance."
|
||||||
]
|
]
|
||||||
Heading -> command 0 Intangible "Get the current heading."
|
Heading -> command 0 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Get the current heading."
|
||||||
Blocked -> command 0 Intangible "See if the robot can move forward."
|
Blocked -> command 0 Intangible $ shortDoc (Set.singleton $ Query $ Sensing EntitySensing) "See if the robot can move forward."
|
||||||
Scan ->
|
Scan ->
|
||||||
command 1 Intangible . doc "Scan a nearby location for entities." $
|
command 1 Intangible . doc (Set.singleton $ Query $ Sensing EntitySensing) "Scan a nearby location for entities." $
|
||||||
[ "Adds the entity (not actor) to your inventory with count 0 if there is any."
|
[ "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."
|
, "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."
|
Upload -> command 1 short $ shortDoc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "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."
|
Ishere -> command 1 Intangible $ shortDoc (Set.singleton $ Query $ Sensing EntitySensing) "See if a specific entity is in the current location."
|
||||||
Isempty ->
|
Isempty ->
|
||||||
command 0 Intangible . doc "Check if the current location is empty." $
|
command 0 Intangible . doc (Set.singleton $ Query $ Sensing EntitySensing) "Check if the current location is empty." $
|
||||||
[ "Detects whether or not the current location contains an entity."
|
[ "Detects whether or not the current location contains an entity."
|
||||||
, "Does not detect robots or other actors."
|
, "Does not detect robots or other actors."
|
||||||
]
|
]
|
||||||
Self -> function 0 "Get a reference to the current robot."
|
Self -> function 0 $ shortDoc (Set.singleton $ Query APriori) "Get a reference to the current robot."
|
||||||
Parent -> function 0 "Get a reference to the robot's parent."
|
Parent -> function 0 $ shortDoc (Set.singleton $ Query APriori) "Get a reference to the robot's parent."
|
||||||
Base -> function 0 "Get a reference to the base."
|
Base -> function 0 $ shortDoc (Set.singleton $ Query APriori) "Get a reference to the base."
|
||||||
Meet -> command 0 Intangible "Get a reference to a nearby actor, if there is one."
|
Meet -> command 0 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Get a reference to a nearby actor, if there is one."
|
||||||
MeetAll -> command 0 long "Run a command for each nearby actor."
|
MeetAll -> command 0 long $ shortDoc (Set.fromList [Mutation $ RobotChange BehaviorChange, Query $ Sensing RobotSensing]) "Run a command for each nearby actor."
|
||||||
Whoami -> command 0 Intangible "Get the robot's display name."
|
Whoami -> command 0 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Get the robot's display name."
|
||||||
Setname -> command 1 short "Set the robot's display name."
|
Setname -> command 1 short $ shortDoc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "Set the robot's display name."
|
||||||
Random ->
|
Random ->
|
||||||
command 1 Intangible . doc "Get a uniformly random integer." $
|
command 1 Intangible . doc (Set.singleton $ Query PRNG) "Get a uniformly random integer." $
|
||||||
["The random integer will be chosen from the range 0 to n-1, exclusive of the argument."]
|
["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."
|
Run -> command 1 long $ shortDoc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "Run a program loaded from a file."
|
||||||
Return -> command 1 Intangible "Make the value a result in `cmd`."
|
Return -> command 1 Intangible $ shortDoc Set.empty "Make the value a result in `cmd`."
|
||||||
Try -> command 2 Intangible "Execute a command, catching errors."
|
Try -> command 2 Intangible $ shortDoc Set.empty "Execute a command, catching errors."
|
||||||
Undefined -> function 0 "A value of any type, that is evaluated as error."
|
Undefined -> function 0 $ shortDoc Set.empty "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."
|
Fail -> function 1 $ shortDoc Set.empty "A value of any type, that is evaluated as error with message."
|
||||||
If ->
|
If ->
|
||||||
function 3 . doc "If-Then-Else function." $
|
function 3 . doc Set.empty "If-Then-Else function." $
|
||||||
["If the bool predicate is true then evaluate the first expression, otherwise the second."]
|
["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."
|
Inl -> function 1 $ shortDoc Set.empty "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."
|
Inr -> function 1 $ shortDoc Set.empty "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."
|
Case -> function 3 $ shortDoc Set.empty "Evaluate one of the given functions on a value of sum type."
|
||||||
Fst -> function 1 "Get the first value of a pair."
|
Fst -> function 1 $ shortDoc Set.empty "Get the first value of a pair."
|
||||||
Snd -> function 1 "Get the second value of a pair."
|
Snd -> function 1 $ shortDoc Set.empty "Get the second value of a pair."
|
||||||
Force -> function 1 "Force the evaluation of a delayed value."
|
Force -> function 1 $ shortDoc Set.empty "Force the evaluation of a delayed value."
|
||||||
Not -> function 1 "Negate the boolean value."
|
Not -> function 1 $ shortDoc Set.empty "Negate the boolean value."
|
||||||
Neg -> unaryOp "-" 7 P "Negate the given integer value."
|
Neg -> unaryOp "-" 7 P $ shortDoc Set.empty "Negate the given integer value."
|
||||||
Add -> binaryOp "+" 6 L "Add the given integer values."
|
Add -> binaryOp "+" 6 L $ shortDoc Set.empty "Add the given integer values."
|
||||||
And -> binaryOp "&&" 3 R "Logical and (true if both values are true)."
|
And -> binaryOp "&&" 3 R $ shortDoc Set.empty "Logical and (true if both values are true)."
|
||||||
Or -> binaryOp "||" 2 R "Logical or (true if either value is true)."
|
Or -> binaryOp "||" 2 R $ shortDoc Set.empty "Logical or (true if either value is true)."
|
||||||
Sub -> binaryOp "-" 6 L "Subtract the given integer values."
|
Sub -> binaryOp "-" 6 L $ shortDoc Set.empty "Subtract the given integer values."
|
||||||
Mul -> binaryOp "*" 7 L "Multiply the given integer values."
|
Mul -> binaryOp "*" 7 L $ shortDoc Set.empty "Multiply the given integer values."
|
||||||
Div -> binaryOp "/" 7 L "Divide the left integer value by the right one, rounding down."
|
Div -> binaryOp "/" 7 L $ shortDoc Set.empty "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."
|
Exp -> binaryOp "^" 8 R $ shortDoc Set.empty "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."
|
Eq -> binaryOp "==" 4 N $ shortDoc Set.empty "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."
|
Neq -> binaryOp "!=" 4 N $ shortDoc Set.empty "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."
|
Lt -> binaryOp "<" 4 N $ shortDoc Set.empty "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."
|
Gt -> binaryOp ">" 4 N $ shortDoc Set.empty "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."
|
Leq -> binaryOp "<=" 4 N $ shortDoc Set.empty "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."
|
Geq -> binaryOp ">=" 4 N $ shortDoc Set.empty "Check that the left value is greater or equal to the right one."
|
||||||
Format -> function 1 "Turn an arbitrary value into a string."
|
Format -> function 1 $ shortDoc Set.empty "Turn an arbitrary value into a string."
|
||||||
Concat -> binaryOp "++" 6 R "Concatenate the given strings."
|
Concat -> binaryOp "++" 6 R $ shortDoc Set.empty "Concatenate the given strings."
|
||||||
Chars -> function 1 "Counts the number of characters in the text."
|
Chars -> function 1 $ shortDoc Set.empty "Counts the number of characters in the text."
|
||||||
Split ->
|
Split ->
|
||||||
function 2 . doc "Split the text into two at given position." $
|
function 2 . doc Set.empty "Split the text into two at given position." $
|
||||||
[ "To be more specific, the following holds for all `text` values `s1` and `s2`:"
|
[ "To be more specific, the following holds for all `text` values `s1` and `s2`:"
|
||||||
, "`(s1,s2) == split (chars s1) (s1 ++ 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."
|
, "So split can be used to undo concatenation if you know the length of the original string."
|
||||||
]
|
]
|
||||||
CharAt ->
|
CharAt ->
|
||||||
function 2 . doc "Get the character at a given index." $
|
function 2 . doc Set.empty "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`."
|
[ "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."
|
, "Throws an exception if given an out-of-bounds index."
|
||||||
]
|
]
|
||||||
ToChar ->
|
ToChar ->
|
||||||
function 1 . doc "Create a singleton `text` value from the given character code." $
|
function 1 . doc Set.empty "Create a singleton `text` value from the given character code." $
|
||||||
[ "That is, `chars (toChar c) == 1` and `charAt 0 (toChar c) == c`."
|
[ "That is, `chars (toChar c) == 1` and `charAt 0 (toChar c) == c`."
|
||||||
]
|
]
|
||||||
AppF ->
|
AppF ->
|
||||||
binaryOp "$" 0 R . doc "Apply the function on the left to the value on the right." $
|
binaryOp "$" 0 R . doc Set.empty "Apply the function on the left to the value on the right." $
|
||||||
[ "This operator is useful to avoid nesting parentheses."
|
[ "This operator is useful to avoid nesting parentheses."
|
||||||
, "For exaple:"
|
, "For exaple:"
|
||||||
, "`f $ g $ h x = f (g (h x))`"
|
, "`f $ g $ h x = f (g (h x))`"
|
||||||
]
|
]
|
||||||
Swap ->
|
Swap ->
|
||||||
command 1 short . doc "Swap placed entity with one in inventory." $
|
command 1 short . doc (Set.fromList [Mutation EntityChange, Mutation $ RobotChange InventoryChange]) "Swap placed entity with one in inventory." $
|
||||||
[ "This essentially works like atomic grab and place."
|
[ "This essentially works like atomic grab and place."
|
||||||
, "Use this to avoid race conditions where more robots grab, scan or place in one location."
|
, "Use this to avoid race conditions where more robots grab, scan or place in one location."
|
||||||
]
|
]
|
||||||
Atomic ->
|
Atomic ->
|
||||||
command 1 Intangible . doc "Execute a block of commands atomically." $
|
command 1 Intangible . doc (Set.singleton 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@."
|
[ "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 ->
|
Instant ->
|
||||||
command 1 Intangible . doc "Execute a block of commands instantly." $
|
command 1 Intangible . doc (Set.singleton MetaEffect) "Execute a block of commands instantly." $
|
||||||
[ "Like `atomic`, but with no restriction on program size."
|
[ "Like `atomic`, but with no restriction on program size."
|
||||||
]
|
]
|
||||||
Key ->
|
Key ->
|
||||||
function 1 . doc "Create a key value from a text description." $
|
function 1 . doc Set.empty "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'"
|
[ "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'."
|
, "For example, 'M-C-x', 'Down', or 'S-4'."
|
||||||
, "Which key combinations are actually possible to type may vary by keyboard and terminal program."
|
, "Which key combinations are actually possible to type may vary by keyboard and terminal program."
|
||||||
]
|
]
|
||||||
InstallKeyHandler ->
|
InstallKeyHandler ->
|
||||||
command 2 Intangible . doc "Install a keyboard input handler." $
|
command 2 Intangible . doc (Set.singleton $ Mutation $ 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 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."
|
, "The second argument is a function to handle keyboard inputs."
|
||||||
]
|
]
|
||||||
Teleport -> command 2 short "Teleport a robot to the given location."
|
Teleport -> command 2 short $ shortDoc (Set.singleton $ Mutation $ RobotChange PositionChange) "Teleport a robot to the given location."
|
||||||
As -> command 2 Intangible "Hypothetically run a command as if you were another robot."
|
As -> command 2 Intangible $ shortDoc (Set.singleton $ Mutation $ RobotChange BehaviorChange) "Hypothetically run a command as if you were another robot."
|
||||||
RobotNamed -> command 1 Intangible "Find an actor by name."
|
RobotNamed -> command 1 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Find an actor by name."
|
||||||
RobotNumbered -> command 1 Intangible "Find an actor by number."
|
RobotNumbered -> command 1 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Find an actor by number."
|
||||||
Knows -> command 1 Intangible "Check if the robot knows about an entity."
|
Knows -> command 1 Intangible $ shortDoc (Set.singleton $ Query $ Sensing RobotSensing) "Check if the robot knows about an entity."
|
||||||
where
|
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 =
|
unaryOp s p side d =
|
||||||
ConstInfo
|
ConstInfo
|
||||||
{ syntax = s
|
{ syntax = s
|
||||||
|
43
src/swarm-lang/Swarm/Language/Syntax/CommandMetadata.hs
Normal file
43
src/swarm-lang/Swarm/Language/Syntax/CommandMetadata.hs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
-- |
|
||||||
|
-- SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
--
|
||||||
|
-- Command metadata for documentation
|
||||||
|
module Swarm.Language.Syntax.CommandMetadata where
|
||||||
|
|
||||||
|
import Data.Aeson (ToJSON)
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
|
||||||
|
data SensingType
|
||||||
|
= RobotSensing
|
||||||
|
| EntitySensing
|
||||||
|
| WorldCondition
|
||||||
|
deriving (Eq, Ord, Enum, Bounded, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
data QueryType
|
||||||
|
= -- | empirical knowledge
|
||||||
|
Sensing SensingType
|
||||||
|
| -- | The random number generator
|
||||||
|
PRNG
|
||||||
|
| -- | a priori knowledge
|
||||||
|
APriori
|
||||||
|
deriving (Eq, Ord, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
data RobotChangeType
|
||||||
|
= PositionChange
|
||||||
|
| InventoryChange
|
||||||
|
| ExistenceChange
|
||||||
|
| BehaviorChange
|
||||||
|
deriving (Eq, Ord, Enum, Bounded, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
data MutationType
|
||||||
|
= Cosmetic
|
||||||
|
| LogEmission
|
||||||
|
| EntityChange
|
||||||
|
| RobotChange RobotChangeType
|
||||||
|
deriving (Eq, Ord, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
data CommandEffect
|
||||||
|
= Query QueryType
|
||||||
|
| MetaEffect
|
||||||
|
| Mutation MutationType
|
||||||
|
deriving (Eq, Ord, Show, Generic, ToJSON)
|
@ -63,6 +63,7 @@ import Servant
|
|||||||
import Servant.Docs (ToCapture)
|
import Servant.Docs (ToCapture)
|
||||||
import Servant.Docs qualified as SD
|
import Servant.Docs qualified as SD
|
||||||
import Servant.Docs.Internal qualified as SD (renderCurlBasePath)
|
import Servant.Docs.Internal qualified as SD (renderCurlBasePath)
|
||||||
|
import Swarm.Doc.Command
|
||||||
import Swarm.Game.Robot
|
import Swarm.Game.Robot
|
||||||
import Swarm.Game.Scenario.Objective
|
import Swarm.Game.Scenario.Objective
|
||||||
import Swarm.Game.Scenario.Objective.Graph
|
import Swarm.Game.Scenario.Objective.Graph
|
||||||
@ -109,6 +110,7 @@ type SwarmAPI =
|
|||||||
:<|> "code" :> "render" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text
|
:<|> "code" :> "render" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text
|
||||||
:<|> "code" :> "run" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text
|
:<|> "code" :> "run" :> ReqBody '[PlainText] T.Text :> Post '[PlainText] T.Text
|
||||||
:<|> "paths" :> "log" :> Get '[JSON] (RingBuffer CacheLogEntry)
|
:<|> "paths" :> "log" :> Get '[JSON] (RingBuffer CacheLogEntry)
|
||||||
|
:<|> "commands" :> Get '[JSON] CommandCatalog
|
||||||
:<|> "repl" :> "history" :> "full" :> Get '[JSON] [REPLHistItem]
|
:<|> "repl" :> "history" :> "full" :> Get '[JSON] [REPLHistItem]
|
||||||
:<|> "map" :> Capture "size" AreaDimensions :> Get '[JSON] GridResponse
|
:<|> "map" :> Capture "size" AreaDimensions :> Get '[JSON] GridResponse
|
||||||
|
|
||||||
@ -164,6 +166,7 @@ mkApp state events =
|
|||||||
:<|> codeRenderHandler
|
:<|> codeRenderHandler
|
||||||
:<|> codeRunHandler events
|
:<|> codeRunHandler events
|
||||||
:<|> pathsLogHandler state
|
:<|> pathsLogHandler state
|
||||||
|
:<|> cmdMatrixHandler state
|
||||||
:<|> replHandler state
|
:<|> replHandler state
|
||||||
:<|> mapViewHandler state
|
:<|> mapViewHandler state
|
||||||
|
|
||||||
@ -241,6 +244,9 @@ pathsLogHandler appStateRef = do
|
|||||||
appState <- liftIO (readIORef appStateRef)
|
appState <- liftIO (readIORef appStateRef)
|
||||||
pure $ appState ^. gameState . pathCaching . pathCachingLog
|
pure $ appState ^. gameState . pathCaching . pathCachingLog
|
||||||
|
|
||||||
|
cmdMatrixHandler :: ReadableIORef AppState -> Handler CommandCatalog
|
||||||
|
cmdMatrixHandler _ = pure getCatalog
|
||||||
|
|
||||||
replHandler :: ReadableIORef AppState -> Handler [REPLHistItem]
|
replHandler :: ReadableIORef AppState -> Handler [REPLHistItem]
|
||||||
replHandler appStateRef = do
|
replHandler appStateRef = do
|
||||||
appState <- liftIO (readIORef appStateRef)
|
appState <- liftIO (readIORef appStateRef)
|
||||||
|
@ -115,6 +115,7 @@ library swarm-lang
|
|||||||
Swarm.Language.Pretty
|
Swarm.Language.Pretty
|
||||||
Swarm.Language.Requirement
|
Swarm.Language.Requirement
|
||||||
Swarm.Language.Syntax
|
Swarm.Language.Syntax
|
||||||
|
Swarm.Language.Syntax.CommandMetadata
|
||||||
Swarm.Language.Text.Markdown
|
Swarm.Language.Text.Markdown
|
||||||
Swarm.Language.Typecheck
|
Swarm.Language.Typecheck
|
||||||
Swarm.Language.Typecheck.Unify
|
Swarm.Language.Typecheck.Unify
|
||||||
@ -422,7 +423,8 @@ library swarm-util
|
|||||||
|
|
||||||
library
|
library
|
||||||
import: stan-config, common, ghc2021-extensions
|
import: stan-config, common, ghc2021-extensions
|
||||||
exposed-modules: Swarm.Doc.Keyword
|
exposed-modules: Swarm.Doc.Command
|
||||||
|
Swarm.Doc.Keyword
|
||||||
Swarm.Doc.Pedagogy
|
Swarm.Doc.Pedagogy
|
||||||
Swarm.Doc.Util
|
Swarm.Doc.Util
|
||||||
Swarm.TUI.Border
|
Swarm.TUI.Border
|
||||||
@ -566,6 +568,7 @@ library
|
|||||||
, Swarm.Language.Pretty
|
, Swarm.Language.Pretty
|
||||||
, Swarm.Language.Requirement
|
, Swarm.Language.Requirement
|
||||||
, Swarm.Language.Syntax
|
, Swarm.Language.Syntax
|
||||||
|
, Swarm.Language.Syntax.CommandMetadata
|
||||||
, Swarm.Language.Text.Markdown
|
, Swarm.Language.Text.Markdown
|
||||||
, Swarm.Language.Typecheck
|
, Swarm.Language.Typecheck
|
||||||
, Swarm.Language.Typecheck.Unify
|
, Swarm.Language.Typecheck.Unify
|
||||||
@ -675,6 +678,8 @@ executable swarm-docs
|
|||||||
Swarm.Doc.Schema.Render
|
Swarm.Doc.Schema.Render
|
||||||
Swarm.Doc.Schema.SchemaType
|
Swarm.Doc.Schema.SchemaType
|
||||||
Swarm.Doc.Wiki.Cheatsheet
|
Swarm.Doc.Wiki.Cheatsheet
|
||||||
|
Swarm.Doc.Wiki.Matrix
|
||||||
|
Swarm.Doc.Wiki.Util
|
||||||
build-depends: optparse-applicative >= 0.16 && < 0.19,
|
build-depends: optparse-applicative >= 0.16 && < 0.19,
|
||||||
dotgen >= 0.4 && < 0.5,
|
dotgen >= 0.4 && < 0.5,
|
||||||
pandoc >= 3.0 && < 3.2,
|
pandoc >= 3.0 && < 3.2,
|
||||||
|
37
web/command-matrix.html
Normal file
37
web/command-matrix.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Command matrix</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style/tablesort.css"/>
|
||||||
|
<link rel="stylesheet" href="style/command-matrix.css"/>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.1.0/tablesort.min.js"></script>
|
||||||
|
<script src="script/command-matrix.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onload=()=>{
|
||||||
|
const tableElement = document.querySelector("table");
|
||||||
|
doFetch(tableElement);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table id="my-table">
|
||||||
|
<thead>
|
||||||
|
<tr data-sort-method="none">
|
||||||
|
<th style="color: gray">Command</th>
|
||||||
|
<th>Explicit robot target</th>
|
||||||
|
<th>Pure computation</th>
|
||||||
|
<th>Modifies environment</th>
|
||||||
|
<th>Modifies robot</th>
|
||||||
|
<th>Moves robot</th>
|
||||||
|
<th>Returns value</th>
|
||||||
|
<!-- <th>Output type</th> -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="my-table-body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
63
web/script/command-matrix.js
Normal file
63
web/script/command-matrix.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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 fieldVals = [
|
||||||
|
entry.derivedAttrs.hasActorTarget,
|
||||||
|
entry.derivedAttrs.pureComputation,
|
||||||
|
entry.derivedAttrs.modifiesEnvironment,
|
||||||
|
entry.derivedAttrs.modifiesRobot,
|
||||||
|
entry.derivedAttrs.movesRobot,
|
||||||
|
entry.derivedAttrs.returnsValue,
|
||||||
|
]
|
||||||
|
|
||||||
|
const cellVals = [
|
||||||
|
mkLink(entry.cmd, "https://github.com/swarm-game/swarm/wiki/Commands-Cheat-Sheet#" + entry.cmd),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const val of fieldVals) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.className = val ? "trueValue" : "falseValue";
|
||||||
|
span.appendChild(document.createTextNode(val));
|
||||||
|
cellVals.push(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cellVals.push(document.createTextNode(entry.derivedAttrs.outputType));
|
||||||
|
|
||||||
|
for (const val of cellVals) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
34
web/style/command-matrix.css
Normal file
34
web/style/command-matrix.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trueValue {
|
||||||
|
color: #00a000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.falseValue {
|
||||||
|
color: #a00000;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
48
web/style/tablesort.css
Normal file
48
web/style/tablesort.css
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user