From 68fa2115debb89f6c824f6c0f7d5d5c180b17e37 Mon Sep 17 00:00:00 2001 From: George Thomas Date: Tue, 24 Oct 2023 15:54:35 +0100 Subject: [PATCH] feat: Add support for creating simple 2D animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a new ADT, `Picture`, representing 2D images, and a primitive function `animate : Integer → (Integer → Picture) → Animation`, where `Animation` is a new primitive type, currently implemented as a GIF for ease of use by clients. The arguments are a frame count and a description of how to render each frame. This is inspired by the API of pure functional animation libraries such as Haskell's Gloss. Both implementation and student-facing API are really just a proof-of-concept for exploring the use of Primer for effectful programming. Though much of what is added here (`Picture` ADT, new primitives etc.) is likely to remain useful for any future work in this direction. Signed-off-by: George Thomas --- flake.nix | 2 + .../test/outputs/OpenAPI/openapi.json | 18 ++ primer/gen/Primer/Gen/Core/Raw.hs | 1 + primer/gen/Primer/Gen/Core/Typed.hs | 3 +- primer/primer.cabal | 4 + primer/src/Primer/Builtins/Picture.hs | 1 + primer/src/Primer/Core/Meta.hs | 2 + primer/src/Primer/Module.hs | 5 +- primer/src/Primer/Pretty.hs | 1 + primer/src/Primer/Primitives.hs | 188 +++++++++++++++++- primer/src/Primer/Primitives/PrimDef.hs | 1 + primer/src/Primer/Typecheck.hs | 4 +- primer/test/Tests/Action.hs | 6 +- primer/test/Tests/Action/Prog.hs | 14 +- .../Beginner-Editable.fragment | 24 +++ .../M.comprehensive/Expert-Editable.fragment | 168 ++++++++++++++++ .../Intermediate-Editable.fragment | 168 ++++++++++++++++ 17 files changed, 591 insertions(+), 19 deletions(-) create mode 100644 primer/src/Primer/Builtins/Picture.hs diff --git a/flake.nix b/flake.nix index 4da3cb91f..43dc0e957 100644 --- a/flake.nix +++ b/flake.nix @@ -415,6 +415,8 @@ # https://github.com/input-output-hk/haskell.nix/issues/1242 packages.mtl-compat.writeHieFiles = false; packages.bytestring-builder.writeHieFiles = false; + packages.fail.writeHieFiles = false; + packages.diagrams.writeHieFiles = false; } { #TODO This shouldn't be necessary - see the commented-out `build-tool-depends` in primer.cabal. diff --git a/primer-service/test/outputs/OpenAPI/openapi.json b/primer-service/test/outputs/OpenAPI/openapi.json index b852a7a8c..334c1e321 100644 --- a/primer-service/test/outputs/OpenAPI/openapi.json +++ b/primer-service/test/outputs/OpenAPI/openapi.json @@ -628,6 +628,24 @@ "contents" ], "type": "object" + }, + { + "properties": { + "contents": { + "type": "string" + }, + "tag": { + "enum": [ + "PrimAnimation" + ], + "type": "string" + } + }, + "required": [ + "tag", + "contents" + ], + "type": "object" } ] }, diff --git a/primer/gen/Primer/Gen/Core/Raw.hs b/primer/gen/Primer/Gen/Core/Raw.hs index 3137cc075..57027a175 100644 --- a/primer/gen/Primer/Gen/Core/Raw.hs +++ b/primer/gen/Primer/Gen/Core/Raw.hs @@ -165,6 +165,7 @@ genPrimCon = _ = \case PrimChar _ -> () PrimInt _ -> () + PrimAnimation _ -> () genType :: ExprGen Type genType = diff --git a/primer/gen/Primer/Gen/Core/Typed.hs b/primer/gen/Primer/Gen/Core/Typed.hs index 57e75b18c..783e2f486 100644 --- a/primer/gen/Primer/Gen/Core/Typed.hs +++ b/primer/gen/Primer/Gen/Core/Typed.hs @@ -487,7 +487,7 @@ genChk ty = do brs0 <- Gen.list (Range.linear 0 5) $ do p <- pg (p,) . CaseBranch (PatPrim p) [] <$> genChk ty - let brs = nubSortOn ((\case PrimInt n -> Left n; PrimChar c -> Right c) . fst) brs0 + let brs = nubSortOn ((\case PrimInt n -> Left (Left n); PrimChar c -> Left (Right c); PrimAnimation b -> Right b) . fst) brs0 fb <- genChk ty pure $ Case () e (snd <$> brs) (CaseFallback fb) @@ -676,6 +676,7 @@ genPrimCon = catMaybes <$> sequence [whenInScope PrimChar 'a' genChar, whenInSco _ = \case PrimChar _ -> () PrimInt _ -> () + PrimAnimation _ -> () -- We bias the distribution towards a small set, to make it more likely we -- generate name clashes on occasion diff --git a/primer/primer.cabal b/primer/primer.cabal index 06dd2fddd..590871b8d 100644 --- a/primer/primer.cabal +++ b/primer/primer.cabal @@ -23,6 +23,7 @@ library Primer.App.Utils Primer.Builtins Primer.Builtins.DSL + Primer.Builtins.Picture Primer.Core Primer.Core.DSL Primer.Core.Meta @@ -109,8 +110,11 @@ library , aeson >=2.0 && <2.2 , assoc ^>=1.1 , base >=4.12 && <4.19 + , base64-bytestring ^>=1.2.1 , containers >=0.6.0.1 && <0.7.0 , deriving-aeson >=0.2 && <0.3.0 + , diagrams-lib ^>=1.4.6 + , diagrams-rasterific ^>=1.4.2 , exceptions >=0.10.4 && <0.11.0 , extra >=1.7.10 && <1.8.0 , generic-optics >=2.0 && <2.3.0 diff --git a/primer/src/Primer/Builtins/Picture.hs b/primer/src/Primer/Builtins/Picture.hs new file mode 100644 index 000000000..065c2b837 --- /dev/null +++ b/primer/src/Primer/Builtins/Picture.hs @@ -0,0 +1 @@ +module Primer.Builtins.Picture () where diff --git a/primer/src/Primer/Core/Meta.hs b/primer/src/Primer/Core/Meta.hs index ced09b4e4..c2821cca4 100644 --- a/primer/src/Primer/Core/Meta.hs +++ b/primer/src/Primer/Core/Meta.hs @@ -195,6 +195,8 @@ instance HasMetadata (Meta a) where data PrimCon = PrimChar Char | PrimInt Integer + | -- | Contains a base-64 encoding of an animated GIF. + PrimAnimation Text deriving stock (Eq, Show, Read, Data, Generic) deriving (FromJSON, ToJSON) via PrimerJSON PrimCon deriving anyclass (NFData) diff --git a/primer/src/Primer/Module.hs b/primer/src/Primer/Module.hs index ee11fd19a..a8ff3cfcf 100644 --- a/primer/src/Primer/Module.hs +++ b/primer/src/Primer/Module.hs @@ -62,7 +62,7 @@ import Primer.JSON ( ToJSON, ) import Primer.Name (Name) -import Primer.Primitives (allPrimTypeDefs, primDefName, primitiveModuleName) +import Primer.Primitives (allPrimTypeDefs, pictureDef, primDefName, primitiveModuleName, tPicture) import Primer.TypeDef (TypeDef (..), TypeDefMap, forgetTypeDefMetadata, generateTypeDefIDs) data Module = Module @@ -133,10 +133,11 @@ nextModuleID m = primitiveModule :: MonadFresh ID m => m Module primitiveModule = do allPrimTypeDefs' <- traverse (generateTypeDefIDs . TypeDefPrim) allPrimTypeDefs + pictureDef' <- generateTypeDefIDs $ TypeDefAST pictureDef pure Module { moduleName = primitiveModuleName - , moduleTypes = M.mapKeys baseName allPrimTypeDefs' + , moduleTypes = M.mapKeys baseName allPrimTypeDefs' <> M.fromList [(baseName tPicture, pictureDef')] , moduleDefs = M.fromList $ [(primDefName def, DefPrim def) | def <- enumerate] } diff --git a/primer/src/Primer/Pretty.hs b/primer/src/Primer/Pretty.hs index 7ffdd2e32..82be3c693 100644 --- a/primer/src/Primer/Pretty.hs +++ b/primer/src/Primer/Pretty.hs @@ -185,6 +185,7 @@ prettyExpr opts = \case prim = \case PrimChar c -> "Char" <+> pretty @Text (show c) PrimInt n -> "Int" <+> pretty @Text (show n) + PrimAnimation n -> pretty @Text (show n) typeann e t = brac Round Yellow (pE e) <+> col Yellow "::" <> line <> brac Round Yellow (pT t) -- When grouped: " x " diff --git a/primer/src/Primer/Primitives.hs b/primer/src/Primer/Primitives.hs index e12ace782..fc98ce645 100644 --- a/primer/src/Primer/Primitives.hs +++ b/primer/src/Primer/Primitives.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BlockArguments #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE ViewPatterns #-} @@ -6,6 +7,7 @@ module Primer.Primitives ( allPrimTypeDefs, tInt, tChar, + tAnimation, primitive, primitiveGVar, primConName, @@ -14,33 +16,66 @@ module Primer.Primitives ( primFunDef, PrimFunError (..), primitiveModuleName, + pictureDef, + tPicture, + cCircle, + cRect, + cColour, + cRotate, + cTranslate, + cCompoundPicture, ) where -import Foreword +import Foreword hiding (rotate) import Control.Monad.Fresh (MonadFresh) import Data.Aeson (FromJSON (..), ToJSON (..)) +import Data.ByteString.Base64 qualified as B64 import Data.Data (Data) import Data.Map qualified as M +import Diagrams.Backend.Rasterific ( + GifLooping (LoopingForever), + defaultPaletteOptions, + rasterGif, + ) +import Diagrams.Prelude ( + V2 (..), + circle, + deg, + fillColor, + mkP2, + mkSizeSpec, + rect, + rectEnvelope, + rotate, + sRGB24, + translate, + (@@), + ) import Numeric.Natural (Natural) import Primer.Builtins ( + cCons, + cNil, cSucc, cZero, tBool, + tList, tMaybe, tNat, ) import Primer.Builtins.DSL (boolAnn, maybeAnn, nat) import Primer.Core ( Expr, - Expr' (Con, PrimCon), + Expr' (..), GVarName, GlobalName, ID, ModuleName, - PrimCon (PrimChar, PrimInt), + PrimCon (PrimAnimation, PrimChar, PrimInt), + TmVarRef (LocalVarRef), TyConName, Type' (..), + ValConName, mkSimpleModuleName, qualifyName, ) @@ -48,13 +83,14 @@ import Primer.Core.DSL ( ann, char, int, + prim, tcon, ) import Primer.Core.Utils (generateIDs) import Primer.JSON (CustomJSON (..), PrimerJSON) import Primer.Name (Name) import Primer.Primitives.PrimDef (PrimDef (..)) -import Primer.TypeDef (PrimTypeDef (..)) +import Primer.TypeDef (ASTTypeDef (..), PrimTypeDef (..), ValCon (..)) data PrimFunError = -- | We have attempted to apply a primitive function to invalid args. @@ -74,6 +110,7 @@ primConName :: PrimCon -> TyConName primConName = \case PrimChar _ -> tChar PrimInt _ -> tInt + PrimAnimation _ -> tAnimation primitive :: Name -> GlobalName k primitive = qualifyName primitiveModuleName @@ -84,6 +121,9 @@ tChar = primitive "Char" tInt :: TyConName tInt = primitive "Int" +tAnimation :: TyConName +tAnimation = primitive "Animation" + -- | Construct a reference to a primitive definition. primitiveGVar :: PrimDef -> GVarName primitiveGVar = primitive . primDefName @@ -107,6 +147,13 @@ allPrimTypeDefs = , primTypeDefNameHints = ["i", "j", "k", "m", "n"] } ) + , let name = tAnimation + in ( name + , PrimTypeDef + { primTypeDefParameters = [] + , primTypeDefNameHints = [] + } + ) ] where -- This ensures that when we modify the constructors of `PrimCon` (i.e. we add/remove primitive types), @@ -114,6 +161,7 @@ allPrimTypeDefs = _ = \case PrimChar _ -> () PrimInt _ -> () + PrimAnimation _ -> () primDefName :: PrimDef -> Name primDefName = \case @@ -137,6 +185,7 @@ primDefName = \case IntNeq -> "Int.≠" IntToNat -> "Int.toNat" IntFromNat -> "Int.fromNat" + Animate -> "animate" PrimConst -> "const" primDefType :: PrimDef -> Type' () () @@ -164,12 +213,20 @@ primFunTypes = \case IntNeq -> ([c tInt, c tInt], c tBool) IntToNat -> ([c tInt], c tMaybe `a` c tNat) IntFromNat -> ([c tNat], c tInt) + Animate -> + ( + [ c tInt -- Loop time, in seconds. + , c tInt `f` c tPicture -- A function from frame number to output. + ] + , c tAnimation + ) -- Arbitrarily limited to `Int` and `Bool` since we our system doesn't allow polymorphic primitives. -- Note that this primitive is only for testing anyway. PrimConst -> ([c tBool, c tNat], c tBool) where c = TCon () a = TApp () + f = TFun () primFunDef :: PrimDef -> [Expr' () () ()] -> Either PrimFunError (forall m. MonadFresh ID m => m Expr) primFunDef def args = case def of @@ -276,6 +333,49 @@ primFunDef def args = case def of [exprToNat -> Just n] -> Right $ int $ fromIntegral n _ -> err + Animate -> case args of + -- Since we can only translate a `Picture` expression to an image once it is in normal form, + -- this guard will only pass when `picture` has no free variables other than `time`. + [PrimCon () (PrimInt duration), Lam () time picture] + | Just frames <- traverse diagramAtTime [0 .. (duration * 100) `div` frameLength] -> + Right + $ prim + $ PrimAnimation + $ either + -- This case really shouldn't be able to happen, unless `diagrams-rasterific` is broken. + -- In fact, the default behaviour (`animatedGif`) is just to write the error to `stdout`, + -- and we only have to handle this because we need to use the lower-level `rasterGif`, + -- for unrelated reasons (getting the `Bytestring` without dumping it to a file). + mempty + (decodeUtf8 . B64.encode . toS) + $ rasterGif @Double + (mkSizeSpec $ Just . fromInteger <$> V2 width height) + gifLooping + defaultPaletteOptions + $ map + ( (,fromInteger frameLength) + . rectEnvelope + (fromInteger <$> mkP2 (width `div` 2) (height `div` 2)) + (fromInteger <$> V2 width height) + ) + frames + where + -- Note that this simple substitution hack only allows for trivial functions, + -- i.e. those where only substitution is needed for the function body to reach a normal form. + -- Our primitives system doesn't yet support further evaluation here. + diagramAtTime t = exprToDiagram $ substTime (PrimCon () (PrimInt t)) picture + where + substTime a = \case + Var () (LocalVarRef t') | t' == time -> a + Con () c es -> Con () c $ map (substTime a) es + e -> e + -- Values which are hardcoded, for now at least, for the sake of keeping the student-facing API simple. + -- We keep the frame rate and resolution low to avoid serialising huge GIFs. + gifLooping = LoopingForever + frameLength = 10 -- in hundredths of a second, as per the GIF spec + width = 160 + height = 90 + _ -> err PrimConst -> case args of [x, _] -> Right $ generateIDs x `ann` tcon tBool @@ -285,4 +385,84 @@ primFunDef def args = case def of Con _ c [] | c == cZero -> Just 0 Con _ c [x] | c == cSucc -> succ <$> exprToNat x _ -> Nothing + exprToDiagram e = + exprToPicture e <&> fix \f -> \case + Circle r -> + if r == 0 -- `diagrams` crashes with a divide-by-zero if we don't catch this case + then mempty + else circle (fromInteger r) + Rect w h -> rect (fromInteger w) (fromInteger h) + Colour r g b p -> f p & fillColor (sRGB24 (fromInteger r) (fromInteger g) (fromInteger b)) + Rotate a p -> f p & rotate (fromInteger a @@ deg) + Translate x y p -> f p & translate (V2 (fromInteger x) (fromInteger y)) + CompoundPicture ps -> foldMap' f ps err = Left $ PrimFunError def args + +pictureDef :: ASTTypeDef () () +pictureDef = + ASTTypeDef + { astTypeDefParameters = [] + , astTypeDefConstructors = + [ ValCon cCircle [TCon () tInt] + , ValCon cRect [TCon () tInt, TCon () tInt] + , ValCon cColour [TCon () tInt, TCon () tInt, TCon () tInt, TCon () tPicture] + , ValCon cRotate [TCon () tInt, TCon () tPicture] + , ValCon cTranslate [TCon () tInt, TCon () tInt, TCon () tPicture] + , -- Pictures are ordered foreground to background, i.e. those earlier in the list appear on top. + ValCon cCompoundPicture [TApp () (TCon () tList) (TCon () tPicture)] + ] + , astTypeDefNameHints = [] + } + +tPicture :: TyConName +tPicture = primitive "Picture" +cCircle :: ValConName +cCircle = primitive "Circle" +cRect :: ValConName +cRect = primitive "Rectangle" +cColour :: ValConName +cColour = primitive "Colour" +cRotate :: ValConName +cRotate = primitive "Rotate" +cTranslate :: ValConName +cTranslate = primitive "Translate" +cCompoundPicture :: ValConName +cCompoundPicture = primitive "Compound" + +-- | A Haskell model of our built-in `Picture` type. +-- Using this type can make working with pictures more convenient, +-- including by giving us compile-time exhaustiveness checks. +data Picture + = Circle Integer + | Rect Integer Integer + | Colour Integer Integer Integer Picture + | Rotate Integer Picture + | Translate Integer Integer Picture + | CompoundPicture [Picture] + +exprToPicture :: Expr' a b c -> Maybe Picture +exprToPicture = \case + Con _ c [PrimCon _ (PrimInt r)] + | c == cCircle -> + Just $ Circle r + Con _ c [PrimCon _ (PrimInt w), PrimCon _ (PrimInt h)] + | c == cRect -> + Just $ Rect w h + Con _ c [PrimCon _ (PrimInt r), PrimCon _ (PrimInt g), PrimCon _ (PrimInt b), exprToPicture -> Just p] + | c == cColour -> + Just $ Colour r g b p + Con _ c [PrimCon _ (PrimInt a), exprToPicture -> Just p] + | c == cRotate -> + Just $ Rotate a p + Con _ c [PrimCon _ (PrimInt x), PrimCon _ (PrimInt y), exprToPicture -> Just p] + | c == cTranslate -> + Just $ Translate x y p + Con _ c [exprToList -> Just (traverse exprToPicture -> Just ps)] + | c == cCompoundPicture -> + Just $ CompoundPicture ps + _ -> Nothing + where + exprToList = \case + Con _ c [] | c == cNil -> Just [] + Con _ c [x, exprToList -> Just xs] | c == cCons -> Just $ x : xs + _ -> Nothing diff --git a/primer/src/Primer/Primitives/PrimDef.hs b/primer/src/Primer/Primitives/PrimDef.hs index f23bbaf29..1e6644a7c 100644 --- a/primer/src/Primer/Primitives/PrimDef.hs +++ b/primer/src/Primer/Primitives/PrimDef.hs @@ -34,6 +34,7 @@ data PrimDef | IntNeq | IntToNat | IntFromNat + | Animate | -- | Only for testing PrimConst deriving stock (Eq, Show, Read, Enum, Bounded, Data, Generic) diff --git a/primer/src/Primer/Typecheck.hs b/primer/src/Primer/Typecheck.hs index 4c06767ff..441d86f09 100644 --- a/primer/src/Primer/Typecheck.hs +++ b/primer/src/Primer/Typecheck.hs @@ -164,7 +164,7 @@ import Primer.Module ( moduleTypesQualifiedMeta, ) import Primer.Name (Name, NameCounter) -import Primer.Primitives (primConName, tChar, tInt) +import Primer.Primitives (primConName, tAnimation, tChar, tInt) import Primer.Subst (substTy) import Primer.TypeDef ( ASTTypeDef (astTypeDefConstructors, astTypeDefParameters), @@ -744,7 +744,7 @@ check t = \case scrutWrap <- Hole <$> meta' (TCSynthed (TEmptyHole ())) <*> pure (addChkMetaT (TEmptyHole ()) e') pure $ Case caseMeta scrutWrap [] CaseExhaustive Left (TDIPrim tc) -> do - unless (tc == tInt || tc == tChar) $ throwError' $ InternalError $ "Unknown primitive type: " <> show tc + unless (tc == tInt || tc == tChar || tc == tAnimation) $ throwError' $ InternalError $ "Unknown primitive type: " <> show tc let f b = case caseBranchName b of PatCon _ -> Nothing PatPrim pc -> case pc of diff --git a/primer/test/Tests/Action.hs b/primer/test/Tests/Action.hs index 086f260e4..521e1542b 100644 --- a/primer/test/Tests/Action.hs +++ b/primer/test/Tests/Action.hs @@ -205,7 +205,7 @@ unit_6 = ) [Move Child1, Move Child1, Move Child2, ConstructLam Nothing] ( ann - (lam "f" (app (lvar "f") (lam "a27" emptyHole))) + (lam "f" (app (lvar "f") (lam "a42" emptyHole))) (tfun (tfun tEmptyHole tEmptyHole) tEmptyHole) ) @@ -722,7 +722,7 @@ unit_case_on_hole = ( lam "x" $ case_ (ann emptyHole $ tcon tNat) - [branch cZero [] emptyHole, branch cSucc [("a29", Nothing)] emptyHole] -- NB: fragile names here + [branch cZero [] emptyHole, branch cSucc [("a44", Nothing)] emptyHole] -- NB: fragile names here ) (tfun (tcon tNat) (tcon tNat)) ) @@ -898,7 +898,7 @@ unit_rename_case_bind_clash = unit_case_branches :: Assertion unit_case_branches = let e cse = ann cse (tcon tBool) - n = "a24" + n = "a39" e0 = e $ caseFB_ diff --git a/primer/test/Tests/Action/Prog.hs b/primer/test/Tests/Action/Prog.hs index c9e3fa8f4..3037e4391 100644 --- a/primer/test/Tests/Action/Prog.hs +++ b/primer/test/Tests/Action/Prog.hs @@ -1197,7 +1197,7 @@ unit_ParamKindAction_1 = progActionTest ( defaultProgEditableTypeDefs (pure []) ) - [ParamKindAction tT pB [SetCursor 30, ConstructKFun]] + [ParamKindAction tT pB [SetCursor 45, ConstructKFun]] $ expectSuccess $ \_ prog' -> do td <- findTypeDef tT prog' @@ -1211,8 +1211,8 @@ unit_ParamKindAction_2 = progActionTest ( defaultProgEditableTypeDefs (pure []) ) - [ ParamKindAction tT pB [SetCursor 30, ConstructKFun] - , ParamKindAction tT pB [SetCursor 36, ConstructKType] + [ ParamKindAction tT pB [SetCursor 45, ConstructKFun] + , ParamKindAction tT pB [SetCursor 51, ConstructKType] ] $ expectError (@?= ActionError (CustomFailure ConstructKType "can only construct the kind 'Type' in hole")) @@ -1221,8 +1221,8 @@ unit_ParamKindAction_2b = progActionTest ( defaultProgEditableTypeDefs (pure []) ) - [ ParamKindAction tT pB [SetCursor 30, ConstructKFun] - , ParamKindAction tT pB [SetCursor 36, Delete] + [ ParamKindAction tT pB [SetCursor 45, ConstructKFun] + , ParamKindAction tT pB [SetCursor 51, Delete] ] $ expectSuccess $ \_ prog' -> do @@ -1237,7 +1237,7 @@ unit_ParamKindAction_3 = progActionTest ( defaultProgEditableTypeDefs (pure []) ) - [ ParamKindAction tT pA [SetCursor 29, Delete] + [ ParamKindAction tT pA [SetCursor 44, Delete] ] $ expectSuccess $ \_ prog' -> do @@ -1252,7 +1252,7 @@ unit_ParamKindAction_bad_id = progActionTest ( defaultProgEditableTypeDefs (pure []) ) - [ ParamKindAction tT pB [SetCursor 30, ConstructKFun] + [ ParamKindAction tT pB [SetCursor 45, ConstructKFun] , ParamKindAction tT pB [SetCursor 0, ConstructKType] ] $ expectError (@?= ActionError (IDNotFound 0)) diff --git a/primer/test/outputs/available-actions/M.comprehensive/Beginner-Editable.fragment b/primer/test/outputs/available-actions/M.comprehensive/Beginner-Editable.fragment index b03355fcd..b3c809a26 100644 --- a/primer/test/outputs/available-actions/M.comprehensive/Beginner-Editable.fragment +++ b/primer/test/outputs/available-actions/M.comprehensive/Beginner-Editable.fragment @@ -1397,6 +1397,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -1409,6 +1415,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -1681,6 +1693,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -1693,6 +1711,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } diff --git a/primer/test/outputs/available-actions/M.comprehensive/Expert-Editable.fragment b/primer/test/outputs/available-actions/M.comprehensive/Expert-Editable.fragment index 81ac37f9c..4036e344d 100644 --- a/primer/test/outputs/available-actions/M.comprehensive/Expert-Editable.fragment +++ b/primer/test/outputs/available-actions/M.comprehensive/Expert-Editable.fragment @@ -613,6 +613,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -833,6 +869,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -2157,6 +2229,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -2497,6 +2605,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -2739,6 +2883,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -2751,6 +2901,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -3722,6 +3878,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -3734,6 +3896,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } diff --git a/primer/test/outputs/available-actions/M.comprehensive/Intermediate-Editable.fragment b/primer/test/outputs/available-actions/M.comprehensive/Intermediate-Editable.fragment index bd4bf0e0b..00c0efd6d 100644 --- a/primer/test/outputs/available-actions/M.comprehensive/Intermediate-Editable.fragment +++ b/primer/test/outputs/available-actions/M.comprehensive/Intermediate-Editable.fragment @@ -421,6 +421,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -617,6 +653,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -1465,6 +1537,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -1733,6 +1841,42 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Circle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rectangle" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Colour" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Rotate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Translate" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } + , Option + { option = "Compound" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -1876,6 +2020,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -1888,6 +2038,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone } @@ -2160,6 +2316,12 @@ Output ( "Builtins" :| [] ) , matchesType = False } + , Option + { option = "Animation" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } , Option { option = "Char" , context = Just @@ -2172,6 +2334,12 @@ Output ( "Primitives" :| [] ) , matchesType = False } + , Option + { option = "Picture" + , context = Just + ( "Primitives" :| [] ) + , matchesType = False + } ] , free = FreeNone }