Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kostmo committed Sep 26, 2023
1 parent fc228b5 commit 5f27081
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 219 deletions.
65 changes: 37 additions & 28 deletions data/scenarios/doc-fragments/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ table.
| `orientationMap` | `fromList []` | [orientation-map](#orientation-map "Link to object properties") | |
| `priority` | `1.0` | `number` | When multiple entities and robots occupy the same cell, the one with the highest priority is drawn. By default, entities have priority `1`, and robots have priority `10`. |

### Recipe

Recipe describes a process that takes some inputs and produces some
outputs, which robots can access using `make` and `drill`.

| Key | Default? | Type | Description |
|------------|----------|-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `in` | | [inventory](#inventory "Link to object properties") | A list of ingredients consumed by the recipe. Each ingredient is a tuple consisting of an integer and an entity name, indicating the number of copies of the given entity that are needed. |
| `out` | | [inventory](#inventory "Link to object properties") | A list of outputs produced by the recipe. It is a list of `[count, entity name]` tuples just like `in`. |
| `required` | `[]` | [inventory](#inventory "Link to object properties") | A list of catalysts required by the recipe. They are neither consumed nor produced, but must be present in order for the recipe to be carried out. It is a list of \[count, entity name\] tuples just like in and out. |
| `time` | `1.0` | `number` | The number of ticks the recipe takes to perform. For recipes which take more than 1 tick, the robot will wait for a number of ticks until the recipe is complete. For example, this is used for many drilling recipes. |
| `weight` | `1.0` | `number` | Whenever there are multiple recipes that match the relevant criteria, one of them will be chosen at random, with probability proportional to their weights. For example, suppose there are two recipes that both output a widget, one with weight `1` and the other with weight `9`. When a robot executes `make "widget"`, the first recipe will be chosen 10% of the time, and the second recipe 90% of the time. |

### Inventory

A list of `[count, entity name]` pairs, specifying the number of each
entity.

List of [entity-count](#entity-count "Link to object properties")

### Entity count

One row in an inventory list

| Index | Default? | Type | Description |
|-------|----------|----------|-------------|
| `0` | | `number` | Quantity |
| `1` | | `string` | Entity name |

### World

Description of the world in the Swarm game
Expand Down Expand Up @@ -225,27 +254,6 @@ Explicit waypoint definition
| `loc` | | [planar-loc](#planar-location "Link to object properties") | |
| `name` | | `string` | Waypoint name |

### Recipe

Recipe describes a process that takes some inputs and produces some
outputs, which robots can access using `make` and `drill`.

| Key | Default? | Type | Description |
|------------|----------|-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `in` | | [inventory](#inventory "Link to object properties") | A list of ingredients consumed by the recipe. Each ingredient is a tuple consisting of an integer and an entity name, indicating the number of copies of the given entity that are needed. |
| `out` | | [inventory](#inventory "Link to object properties") | A list of outputs produced by the recipe. It is a list of `[count, entity name]` tuples just like `in`. |
| `required` | `[]` | [inventory](#inventory "Link to object properties") | A list of catalysts required by the recipe. They are neither consumed nor produced, but must be present in order for the recipe to be carried out. It is a list of \[count, entity name\] tuples just like in and out. |
| `time` | `1.0` | `number` | The number of ticks the recipe takes to perform. For recipes which take more than 1 tick, the robot will wait for a number of ticks until the recipe is complete. For example, this is used for many drilling recipes. |
| `weight` | `1.0` | `number` | Whenever there are multiple recipes that match the relevant criteria, one of them will be chosen at random, with probability proportional to their weights. For example, suppose there are two recipes that both output a widget, one with weight `1` and the other with weight `9`. When a robot executes `make "widget"`, the first recipe will be chosen 10% of the time, and the second recipe 90% of the time. |

### Inventory

A list of `[count, entity name]` pairs, specifying the number of each
entity.

| Key | Default? | Type | Description |
|-----|----------|------|-------------|

### Objective

Scenario goals and their prerequisites. The top-level objectives field
Expand Down Expand Up @@ -287,8 +295,10 @@ Structure placement

x and y coordinates of a location in a particular world

| Key | Default? | Type | Description |
|-----|----------|------|-------------|
| Index | Default? | Type | Description |
|-------|----------|----------|--------------|
| `0` | | `number` | X coordinate |
| `1` | | `number` | Y coordinate |

### Portal

Expand All @@ -314,15 +324,14 @@ Properties of a portal's exit

Prerequisite conditions for an objective.

| Key | Default? | Type | Description |
|-----|----------|------|-------------|

### Numeric range

Min/max range of a value

| Key | Default? | Type | Description |
|-----|----------|------|-------------|
| Index | Default? | Type | Description |
|-------|----------|----------|-------------|
| `0` | | `number` | minimum |
| `1` | | `number` | maximum |

### Structure orientation

Expand Down
17 changes: 17 additions & 0 deletions data/schema/entity-count.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/entity-count.json",
"title": "Entity count",
"description": "One row in an inventory list",
"type": "array",
"items": [
{
"name": "Quantity",
"type": "number"
},
{
"name": "Entity name",
"type": "string"
}
]
}
12 changes: 1 addition & 11 deletions data/schema/inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
"description": "A list of `[count, entity name]` pairs, specifying the number of each entity.",
"type": "array",
"items": {
"type": "array",
"items": [
{
"title": "Entity count",
"type": "number"
},
{
"title": "Entity name",
"type": "string"
}
]
"$ref": "entity-count.json"
}
}
13 changes: 1 addition & 12 deletions src/Swarm/Doc/Gen.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import Data.Text.IO qualified as T
import Data.Tuple (swap)
import Swarm.Doc.Pedagogy
import Swarm.Doc.Schema.Render
import Swarm.Doc.Util
import Swarm.Game.Display (displayChar)
import Swarm.Game.Entity (Entity, EntityMap (entitiesByName), entityDisplay, entityName, loadEntities)
import Swarm.Game.Entity qualified as E
Expand Down Expand Up @@ -221,12 +222,6 @@ generateSpecialKeyNames =
-- GENERATE TABLES: COMMANDS, ENTITIES AND CAPABILITIES TO MARKDOWN TABLE
-- ----------------------------------------------------------------------------

wrap :: Char -> Text -> Text
wrap c = T.cons c . flip T.snoc c

codeQuote :: Text -> Text
codeQuote = wrap '`'

escapeTable :: Text -> Text
escapeTable = T.concatMap (\c -> if c == '|' then T.snoc "\\" c else T.singleton c)

Expand All @@ -241,12 +236,6 @@ listToRow mw xs = wrap '|' . T.intercalate "|" $ zipWith format mw xs
maxWidths :: [[Text]] -> [Int]
maxWidths = map (maximum . map T.length) . transpose

addLink :: Text -> Text -> Text
addLink l t = T.concat ["[", t, "](", l, ")"]

tshow :: (Show a) => a -> Text
tshow = T.pack . show

-- ---------
-- COMMANDS
-- ---------
Expand Down
11 changes: 5 additions & 6 deletions src/Swarm/Doc/Schema/Arrangement.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
--
-- Graph-based heuristics for arranging the
-- order of sections in the schema docs
module Swarm.Doc.Schema.Arrangement (mkSchemaGraph) where
module Swarm.Doc.Schema.Arrangement (sortAndPruneSchemas) where

import Data.Graph
import Data.Set qualified as Set
import Swarm.Doc.Schema.Parse
import Swarm.Doc.Schema.Refined
import Swarm.Doc.Schema.SchemaType

-- | Sort the schemas in topological order.
--
Expand All @@ -17,18 +18,16 @@ import Swarm.Doc.Schema.Refined
-- (i.e. exclude @entities.json@ and @recipes.json@,
-- which are used independently to validate @entities.yaml@
-- and @recipes.yaml@).
mkSchemaGraph ::
sortAndPruneSchemas ::
SchemaIdReference ->
[SchemaData] ->
[SchemaData]
mkSchemaGraph rootSchemaKey schemas =
sortAndPruneSchemas rootSchemaKey schemas =
reverse . flattenSCCs . stronglyConnComp $ reachableEdges
where
rawEdgeList = map getNodeEdgesEntry schemas
(graph, _nodeFromVertex, vertexFromKey) = graphFromEdges rawEdgeList

scenarioVertex = vertexFromKey rootSchemaKey
reachableVertices = Set.fromList $ maybe [] (reachable graph) scenarioVertex
reachableVertices = Set.fromList $ maybe [] (reachable graph) $ vertexFromKey rootSchemaKey

reachableEdges = filter f rawEdgeList
f (_, k, _) = maybe False (`Set.member` reachableVertices) . vertexFromKey $ k
Expand Down
123 changes: 12 additions & 111 deletions src/Swarm/Doc/Schema/Parse.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ module Swarm.Doc.Schema.Parse where

import Control.Applicative ((<|>))
import Data.Aeson
import Data.List.Extra (replace)
import Data.Map (Map)
import Data.Map qualified as M
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics (Generic)
import Swarm.Doc.Schema.Refined
import System.FilePath (takeBaseName)
import Swarm.Doc.Schema.SchemaType
import Swarm.Doc.Schema.Superset
import Text.Pandoc

-- | Includes everything needed to
Expand All @@ -32,48 +27,17 @@ data SchemaData = SchemaData
, markdownFooters :: [Pandoc]
}

schemaJsonOptions :: Options
schemaJsonOptions =
defaultOptions
{ fieldLabelModifier = replace "S" "$" . tail -- drops leading underscore
}

data ItemDescription
= ItemList [SwarmSchema]
| ItemType SwarmSchema
data Members
= ObjectProperties (Map Text SwarmSchema)
| ListMembers (ItemDescription SwarmSchema)
| SimpleType (SingleOrList Text) -- TODO: Currently unused
deriving (Eq, Ord, Show)

instance FromJSON ItemDescription where
parseJSON x =
ItemType <$> parseJSON x
<|> ItemList <$> parseJSON x

data SchemaRaw = SchemaRaw
{ _description :: Maybe Text
, _default :: Maybe Value
, _title :: Maybe Text
, _type :: Maybe (SingleOrList Text)
, _properties :: Maybe (Map Text SwarmSchema)
, _items :: Maybe ItemDescription
, _examples :: Maybe [Value]
, _Sref :: Maybe Text
, _oneOf :: Maybe [SchemaRaw]
, _footers :: Maybe [FilePath]
}
deriving (Eq, Ord, Show, Generic)

instance FromJSON SchemaRaw where
parseJSON = genericParseJSON schemaJsonOptions

-- | A subset of all JSON schemas, conforming to internal Swarm conventions.
--
-- TODO: Conveniently, this extra layer of processing
-- is able to enforce that all "object" definitions in the schema
-- contain the @additionalProperties: false@ property.
data ToplevelSchema = ToplevelSchema
{ title :: Text
, description :: Pandoc
, content :: SwarmSchema
, members :: Maybe Members
, footerPaths :: [FilePath]
}
deriving (Eq, Ord, Show)
Expand All @@ -86,71 +50,8 @@ instance FromJSON ToplevelSchema where
theTitle <- maybe (fail "Schema requires a title") return $ _title rawSchema
theDescription <- maybe (fail "Schema requires a description") return $ objectDescription swarmSchema
let theFooters = fromMaybe [] $ _footers rawSchema
return $ ToplevelSchema theTitle theDescription swarmSchema theFooters

-- TODO use this to represent mutual-exclusivity
-- between objects and arrays
data Members
= ObjectProperties (Map Text SwarmSchema)
| ListMembers [SwarmSchema]

data SwarmSchema = SwarmSchema
{ schemaType :: SchemaType
, defaultValue :: Maybe Value
, objectDescription :: Maybe Pandoc
, properties :: Maybe (Map Text SwarmSchema)
, examples :: [Value]
}
deriving (Eq, Ord, Show)

instance FromJSON SwarmSchema where
parseJSON x = do
rawSchema :: rawSchema <- parseJSON x
toSwarmSchema rawSchema

getMarkdown :: MonadFail m => Text -> m Pandoc
getMarkdown desc = case runPure (readMarkdown def desc) of
Right doc -> return doc
Left err -> fail $ T.unpack $ renderError err

extractSchemaType :: SchemaRaw -> Maybe SchemaType
extractSchemaType rawSchema =
mkReference <$> _Sref rawSchema
<|> getTypeFromItems
<|> Simple <$> _type rawSchema
<|> Alternatives . mapMaybe extractSchemaType <$> _oneOf rawSchema
where
mkReference = Reference . SchemaIdReference . T.pack . takeBaseName . T.unpack

getTypeFromItems :: Maybe SchemaType
getTypeFromItems = do
itemsThing <- _items rawSchema
case itemsThing of
ItemList _ -> Nothing
ItemType x -> Just $ ListOf $ schemaType x

toSwarmSchema :: MonadFail m => SchemaRaw -> m SwarmSchema
toSwarmSchema rawSchema = do
theType <- maybe (fail "Unspecified sub-schema type") return maybeType
markdownDescription <- mapM getMarkdown $ _description rawSchema
return
SwarmSchema
{ schemaType = theType
, defaultValue = _default rawSchema
, objectDescription = markdownDescription
, examples = fromMaybe [] $ _examples rawSchema
, properties = _properties rawSchema
}
where
maybeType = extractSchemaType rawSchema

-- * Utilities

-- | Recursively extract references to other schemas
extractReferences :: SwarmSchema -> Set SchemaIdReference
extractReferences s = thisRefList <> otherRefLists
where
thisRefList = Set.fromList . getSchemaReferences $ schemaType s

otherSchemas = maybe [] M.elems (properties s)
otherRefLists = Set.unions $ map extractReferences otherSchemas
let maybeMembers =
ObjectProperties <$> properties swarmSchema
<|> ListMembers <$> itemsDescription swarmSchema
return $ ToplevelSchema theTitle theDescription swarmSchema maybeMembers theFooters
Loading

0 comments on commit 5f27081

Please sign in to comment.