-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dump a list of tutorials and commands they first introduce (#1186)
May be able to reduce likelihood of #1086 by having a autogenerated table of the commands and the tutorials they are introduced in. This work is related to #1106 in that robot commands should be given distinct markup (backticks). ## Demo scripts/play.sh generate pedagogy **Updated:** See [rendered output](https://gist.github.com/kostmo/d4af3a6814e65fc6d455fd34b41552d3). ## New tests scripts/run-tests.sh --test-arguments '--pattern "Pedagogical soundness"' Note that the test for `crash.yaml` is currently failing because the `Give` command (and some others) are utilized in this tutorial without having been mentioned in the description or in earlier tutorials. Additionally, the "bug" described in [this comment](#1089 (comment)) **should** be caught by these tests (i.e. fail the tests) since the embedded `solution` code in the YAML file utilizes the `give` command. That is, if it currently weren't for [this issue](https://github.com/swarm-game/swarm/pull/1186/files#r1152789153).
- Loading branch information
Showing
16 changed files
with
340 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
-- | | ||
-- SPDX-License-Identifier: BSD-3-Clause | ||
-- | ||
-- Constants used throughout the UI and game | ||
module Swarm.Constant where | ||
|
||
import Data.Text (Text) | ||
|
||
-- * Website constants | ||
|
||
-- By convention, all URL constants include trailing slashes | ||
-- when applicable. | ||
|
||
swarmRepoUrl :: Text | ||
swarmRepoUrl = "https://github.com/swarm-game/swarm/" | ||
|
||
wikiUrl :: Text | ||
wikiUrl = swarmRepoUrl <> "wiki/" | ||
|
||
wikiCheatSheet :: Text | ||
wikiCheatSheet = wikiUrl <> "Commands-Cheat-Sheet" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
-- | | ||
-- SPDX-License-Identifier: BSD-3-Clause | ||
-- | ||
-- Assess pedagogical soundness of the tutorials. | ||
-- | ||
-- Approach: | ||
-- 1. Obtain a list of all of the tutorial scenarios, in order | ||
-- 2. Search their "solution" code for `commands` | ||
-- 3. "fold" over the tutorial list, noting which tutorial was first to introduce each command | ||
module Swarm.Doc.Pedagogy ( | ||
renderTutorialProgression, | ||
generateIntroductionsSequence, | ||
CoverageInfo (..), | ||
TutorialInfo (..), | ||
) where | ||
|
||
import Control.Arrow ((&&&)) | ||
import Control.Lens (universe, view) | ||
import Control.Monad (guard, (<=<)) | ||
import Control.Monad.Except (ExceptT (..), liftIO) | ||
import Data.List (foldl', intercalate, sort, sortOn) | ||
import Data.List.Extra (zipFrom) | ||
import Data.Map (Map) | ||
import Data.Map qualified as M | ||
import Data.Maybe (mapMaybe) | ||
import Data.Set (Set) | ||
import Data.Set qualified as S | ||
import Data.Text (Text) | ||
import Data.Text qualified as T | ||
import Swarm.Constant | ||
import Swarm.Game.Entity (loadEntities) | ||
import Swarm.Game.Scenario (Scenario, scenarioDescription, scenarioName, scenarioObjectives, scenarioSolution) | ||
import Swarm.Game.Scenario.Objective (objectiveGoal) | ||
import Swarm.Game.ScenarioInfo (ScenarioCollection, ScenarioInfoPair, flatten, loadScenariosWithWarnings, scenarioCollectionToList, scenarioPath) | ||
import Swarm.Language.Module (Module (..)) | ||
import Swarm.Language.Pipeline (ProcessedTerm (..)) | ||
import Swarm.Language.Syntax | ||
import Swarm.Language.Types (Polytype) | ||
import Swarm.TUI.Controller (getTutorials) | ||
import Swarm.Util (simpleErrorHandle) | ||
|
||
-- * Constants | ||
|
||
commandsWikiAnchorPrefix :: Text | ||
commandsWikiAnchorPrefix = wikiCheatSheet <> "#" | ||
|
||
-- * Types | ||
|
||
-- | Tutorials augmented by the set of | ||
-- commands that they introduce. | ||
-- Generated by folding over all of the | ||
-- tutorials in sequence. | ||
data CoverageInfo = CoverageInfo | ||
{ tutInfo :: TutorialInfo | ||
, novelSolutionCommands :: Map Const [SrcLoc] | ||
} | ||
|
||
-- | Tutorial scenarios with the set of commands | ||
-- introduced in their solution and descriptions | ||
-- having been extracted | ||
data TutorialInfo = TutorialInfo | ||
{ scenarioPair :: ScenarioInfoPair | ||
, tutIndex :: Int | ||
, solutionCommands :: Map Const [SrcLoc] | ||
, descriptionCommands :: Set Const | ||
} | ||
|
||
-- | A private type used by the fold | ||
data CommandAccum = CommandAccum | ||
{ _encounteredCmds :: Set Const | ||
, tuts :: [CoverageInfo] | ||
} | ||
|
||
-- * Functions | ||
|
||
-- | Extract commands from both goal descriptions and solution code. | ||
extractCommandUsages :: Int -> ScenarioInfoPair -> TutorialInfo | ||
extractCommandUsages idx siPair@(s, _si) = | ||
TutorialInfo siPair idx solnCommands $ getDescCommands s | ||
where | ||
solnCommands = getCommands maybeSoln | ||
maybeSoln = view scenarioSolution s | ||
|
||
-- | Obtain the set of all commands mentioned by | ||
-- name in the tutorial's goal descriptions. | ||
getDescCommands :: Scenario -> Set Const | ||
getDescCommands s = | ||
S.fromList $ mapMaybe (`M.lookup` txtLookups) backtickedWords | ||
where | ||
goalTextParagraphs = concatMap (view objectiveGoal) $ view scenarioObjectives s | ||
allWords = concatMap (T.words . T.toLower) goalTextParagraphs | ||
getBackticked = T.stripPrefix "`" <=< T.stripSuffix "`" | ||
backtickedWords = mapMaybe getBackticked allWords | ||
|
||
commandConsts = filter isConsidered allConst | ||
txtLookups = M.fromList $ map (syntax . constInfo &&& id) commandConsts | ||
|
||
isConsidered :: Const -> Bool | ||
isConsidered c = isUserFunc c && c `S.notMember` ignoredCommands | ||
where | ||
ignoredCommands = S.fromList [Run, Return, Noop, Force] | ||
|
||
-- | Extract the command names from the source code of the solution. | ||
-- | ||
-- NOTE: `noop` gets automatically inserted for an empty `build {}` command | ||
-- at parse time, so we explicitly ignore the `noop` in the case that | ||
-- the player did not write it explicitly in their code. | ||
-- | ||
-- Also, the code from `run` is not parsed transitively yet. | ||
getCommands :: Maybe ProcessedTerm -> Map Const [SrcLoc] | ||
getCommands Nothing = mempty | ||
getCommands (Just (ProcessedTerm (Module stx _) _ _)) = | ||
M.fromListWith (<>) $ mapMaybe isCommand nodelist | ||
where | ||
nodelist :: [Syntax' Polytype] | ||
nodelist = universe stx | ||
isCommand (Syntax' sloc t _) = case t of | ||
TConst c -> guard (isConsidered c) >> Just (c, [sloc]) | ||
_ -> Nothing | ||
|
||
-- | "fold" over the tutorials in sequence to determine which | ||
-- commands are novel to each tutorial's solution. | ||
computeCommandIntroductions :: [(Int, ScenarioInfoPair)] -> [CoverageInfo] | ||
computeCommandIntroductions = | ||
reverse . tuts . foldl' f initial | ||
where | ||
initial = CommandAccum mempty mempty | ||
|
||
f :: CommandAccum -> (Int, ScenarioInfoPair) -> CommandAccum | ||
f (CommandAccum encounteredPreviously xs) (idx, siPair) = | ||
CommandAccum updatedEncountered $ CoverageInfo usages novelCommands : xs | ||
where | ||
usages = extractCommandUsages idx siPair | ||
usedCmdsForTutorial = solutionCommands usages | ||
|
||
updatedEncountered = encounteredPreviously `S.union` M.keysSet usedCmdsForTutorial | ||
novelCommands = M.withoutKeys usedCmdsForTutorial encounteredPreviously | ||
|
||
-- | Extract the tutorials from the complete scenario collection | ||
-- and derive their command coverage info. | ||
generateIntroductionsSequence :: ScenarioCollection -> [CoverageInfo] | ||
generateIntroductionsSequence = | ||
computeCommandIntroductions . zipFrom 0 . getTuts | ||
where | ||
getTuts = | ||
concatMap flatten | ||
. scenarioCollectionToList | ||
. getTutorials | ||
|
||
-- * Rendering functions | ||
|
||
-- | Helper for standalone rendering. | ||
-- For unit tests, can instead access the scenarios via the GameState. | ||
loadScenarioCollection :: IO ScenarioCollection | ||
loadScenarioCollection = simpleErrorHandle $ do | ||
entities <- ExceptT loadEntities | ||
(_, loadedScenarios) <- liftIO $ loadScenariosWithWarnings entities | ||
return loadedScenarios | ||
|
||
renderUsagesMarkdown :: CoverageInfo -> Text | ||
renderUsagesMarkdown (CoverageInfo (TutorialInfo (s, si) idx _sCmds dCmds) novelCmds) = | ||
T.unlines bodySections | ||
where | ||
bodySections = firstLine : otherLines | ||
otherLines = | ||
intercalate | ||
[""] | ||
[ pure . surround "`" . T.pack $ view scenarioPath si | ||
, pure . surround "*" . T.strip $ view scenarioDescription s | ||
, renderSection "Introduced in solution" . renderCmdList $ M.keysSet novelCmds | ||
, renderSection "Referenced in description" $ renderCmdList dCmds | ||
] | ||
surround x y = x <> y <> x | ||
|
||
renderSection title content = | ||
["### " <> title] <> content | ||
|
||
firstLine = | ||
T.unwords | ||
[ "##" | ||
, renderTutorialTitle idx s | ||
] | ||
|
||
renderTutorialTitle :: Show a => a -> Scenario -> Text | ||
renderTutorialTitle idx s = | ||
T.unwords | ||
[ T.pack $ show idx <> ":" | ||
, view scenarioName s | ||
] | ||
|
||
linkifyCommand :: Text -> Text | ||
linkifyCommand c = "[" <> c <> "](" <> commandsWikiAnchorPrefix <> c <> ")" | ||
|
||
renderList :: [Text] -> [Text] | ||
renderList items = | ||
if null items | ||
then pure "(none)" | ||
else map ("* " <>) items | ||
|
||
cmdSetToSortedText :: Set Const -> [Text] | ||
cmdSetToSortedText = sort . map (T.pack . show) . S.toList | ||
|
||
renderCmdList :: Set Const -> [Text] | ||
renderCmdList = renderList . map linkifyCommand . cmdSetToSortedText | ||
|
||
renderTutorialProgression :: IO Text | ||
renderTutorialProgression = | ||
processAndRender <$> loadScenarioCollection | ||
where | ||
processAndRender ss = | ||
T.unlines allLines | ||
where | ||
introSection = | ||
"# Command introductions by tutorial" | ||
: "This document indicates which tutorials introduce various commands and keywords." | ||
: "" | ||
: "All used:" | ||
: renderFullCmdList allUsed | ||
|
||
render (cmd, tut) = | ||
T.unwords | ||
[ linkifyCommand cmd | ||
, "(" <> renderTutorialTitle (tutIndex tut) (fst $ scenarioPair tut) <> ")" | ||
] | ||
renderFullCmdList = renderList . map render . sortOn fst | ||
infos = generateIntroductionsSequence ss | ||
allLines = introSection <> map renderUsagesMarkdown infos | ||
allUsed = concatMap mkTuplesForTutorial infos | ||
|
||
mkTuplesForTutorial tut = | ||
map (\x -> (T.pack $ show x, tutIdxScenario)) $ | ||
M.keys $ | ||
novelSolutionCommands tut | ||
where | ||
tutIdxScenario = tutInfo tut |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.