diff --git a/data/entities.yaml b/data/entities.yaml index 23615f0ef..f8a164280 100644 --- a/data/entities.yaml +++ b/data/entities.yaml @@ -330,6 +330,22 @@ ignition: 0.1 duration: [20, 40] product: ash +- name: parsley + display: + attr: plant + char: 'p' + description: + - A fast-growing plant with a pungent aroma, often found growing near rocks. + - | + When equipped as a device, parsley enables the `read` command, which can be used to turn text into values: + ``` + read : Text -> a + ``` + If the given `Text`{=type} represents a value of the expected type, the value will be returned; otherwise, an exception is thrown. For example, `(read "3" : Int) == 3`, but `read "hello" : Int` crashes. + - Note that `read`, unlike `format`, is unable to deal with function, delay, or command types. + properties: [pickable, growable] + capabilities: [read] + growth: [10, 100] - name: linotype display: attr: silver diff --git a/data/worlds/classic.world b/data/worlds/classic.world index 9af5931d1..149555933 100644 --- a/data/worlds/classic.world +++ b/data/worlds/classic.world @@ -75,6 +75,7 @@ overlay [ {stone} , mask (hash % 10 == 0) {stone, rock} , mask (hash % 100 == 0) {stone, lodestone} + , mask (hash % 300 == 0) {grass, parsley} ] ) , mask (big && soft && natural) diff --git a/editors/emacs/swarm-mode.el b/editors/emacs/swarm-mode.el index e74c12bd9..60120084d 100644 --- a/editors/emacs/swarm-mode.el +++ b/editors/emacs/swarm-mode.el @@ -161,6 +161,7 @@ "fail" "not" "format" + "read" "chars" "split" "charat" diff --git a/editors/vim/swarm.vim b/editors/vim/swarm.vim index 83952ae45..ab06c8bbe 100644 --- a/editors/vim/swarm.vim +++ b/editors/vim/swarm.vim @@ -1,5 +1,5 @@ syn keyword Keyword def tydef rec end let in require -syn keyword Builtins self parent base if inl inr case fst snd force undefined fail not format chars split charat tochar key +syn keyword Builtins self parent base if inl inr case fst snd force undefined fail not format read chars split charat tochar key syn keyword Command noop wait selfdestruct move backup volume path push stride turn grab harvest sow ignite place ping give equip unequip make has equipped count drill use build salvage reprogram say listen log view appear create halt time scout whereami locateme waypoint structure floorplan hastag tagmembers detect resonate density sniff chirp watch surveil heading blocked scan upload ishere isempty meet meetall whoami setname random run return try swap atomic instant installkeyhandler teleport warp as robotnamed robotnumbered knows syn keyword Direction east north west south down forward left back right syn match Type "\<[A-Z][a-zA-Z_]*\>" diff --git a/editors/vscode/syntaxes/swarm.tmLanguage.yaml b/editors/vscode/syntaxes/swarm.tmLanguage.yaml index 9414ed2ea..ecee58ad9 100644 --- a/editors/vscode/syntaxes/swarm.tmLanguage.yaml +++ b/editors/vscode/syntaxes/swarm.tmLanguage.yaml @@ -46,7 +46,7 @@ repository: keyword: name: keyword.other match: >- - \b(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|chars|split|charat|tochar|key|noop|wait|selfdestruct|move|backup|volume|path|push|stride|turn|grab|harvest|sow|ignite|place|ping|give|equip|unequip|make|has|equipped|count|drill|use|build|salvage|reprogram|say|listen|log|view|appear|create|halt|time|scout|whereami|locateme|waypoint|structure|floorplan|hastag|tagmembers|detect|resonate|density|sniff|chirp|watch|surveil|heading|blocked|scan|upload|ishere|isempty|meet|meetall|whoami|setname|random|run|return|try|swap|atomic|instant|installkeyhandler|teleport|warp|as|robotnamed|robotnumbered|knows)\b + \b(self|parent|base|if|inl|inr|case|fst|snd|force|undefined|fail|not|format|read|chars|split|charat|tochar|key|noop|wait|selfdestruct|move|backup|volume|path|push|stride|turn|grab|harvest|sow|ignite|place|ping|give|equip|unequip|make|has|equipped|count|drill|use|build|salvage|reprogram|say|listen|log|view|appear|create|halt|time|scout|whereami|locateme|waypoint|structure|floorplan|hastag|tagmembers|detect|resonate|density|sniff|chirp|watch|surveil|heading|blocked|scan|upload|ishere|isempty|meet|meetall|whoami|setname|random|run|return|try|swap|atomic|instant|installkeyhandler|teleport|warp|as|robotnamed|robotnumbered|knows)\b require: name: keyword.control.require match: \b(require)\b diff --git a/src/swarm-engine/Swarm/Game/Step.hs b/src/swarm-engine/Swarm/Game/Step.hs index 73f458659..6ac85ce2c 100644 --- a/src/swarm-engine/Swarm/Game/Step.hs +++ b/src/swarm-engine/Swarm/Game/Step.hs @@ -561,6 +561,7 @@ stepCESK cesk = case cesk of In (TInt n) _ s k -> return $ Out (VInt n) s k In (TText str) _ s k -> return $ Out (VText str) s k In (TBool b) _ s k -> return $ Out (VBool b) s k + In (TType ty) _ s k -> return $ Out (VType ty) s k -- There should not be any antiquoted variables left at this point. In (TAntiText v) _ s k -> return $ Up (Fatal (T.append "Antiquoted variable found at runtime: $str:" v)) s k diff --git a/src/swarm-engine/Swarm/Game/Step/Arithmetic.hs b/src/swarm-engine/Swarm/Game/Step/Arithmetic.hs index 08ced6c3a..5676ee5b0 100644 --- a/src/swarm-engine/Swarm/Game/Step/Arithmetic.hs +++ b/src/swarm-engine/Swarm/Game/Step/Arithmetic.hs @@ -77,6 +77,7 @@ compareValues v1 = case v1 of VSuspend {} -> incomparable v1 VExc {} -> incomparable v1 VBlackhole {} -> incomparable v1 + VType {} -> incomparable v1 -- | Values with different types were compared; this should not be -- possible since the type system should catch it. diff --git a/src/swarm-engine/Swarm/Game/Step/Const.hs b/src/swarm-engine/Swarm/Game/Step/Const.hs index b1ed7d9a1..e7bf82c7e 100644 --- a/src/swarm-engine/Swarm/Game/Step/Const.hs +++ b/src/swarm-engine/Swarm/Game/Step/Const.hs @@ -87,6 +87,7 @@ import Swarm.Game.Universe import Swarm.Game.Value import Swarm.Language.Capability import Swarm.Language.Key (parseKeyComboFull) +import Swarm.Language.Parser.Value (readValue) import Swarm.Language.Pipeline import Swarm.Language.Requirements qualified as R import Swarm.Language.Syntax @@ -1208,6 +1209,11 @@ execConst runChildProg c vs s k = do Format -> case vs of [v] -> return $ mkReturn $ prettyValue v _ -> badConst + Read -> case vs of + [VType ty, VText txt] -> case readValue ty txt of + Nothing -> raise Read ["Could not read", showT txt, "at type", prettyText ty] + Just v -> return (mkReturn v) + _ -> badConst Chars -> case vs of [VText t] -> return $ mkReturn $ T.length t _ -> badConst diff --git a/src/swarm-engine/Swarm/Game/Value.hs b/src/swarm-engine/Swarm/Game/Value.hs index 1beac280e..a050e622f 100644 --- a/src/swarm-engine/Swarm/Game/Value.hs +++ b/src/swarm-engine/Swarm/Game/Value.hs @@ -38,6 +38,9 @@ pattern VRect x1 y1 x2 y2 = VPair (VPair (VInt x1) (VInt y1)) (VPair (VInt x2) ( class Valuable a where asValue :: a -> Value +instance Valuable Value where + asValue = id + instance Valuable Int32 where asValue = VInt . fromIntegral diff --git a/src/swarm-lang/Swarm/Language/Elaborate.hs b/src/swarm-lang/Swarm/Language/Elaborate.hs index d07b24140..c63d2f2cc 100644 --- a/src/swarm-lang/Swarm/Language/Elaborate.hs +++ b/src/swarm-lang/Swarm/Language/Elaborate.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ViewPatterns #-} -- | -- SPDX-License-Identifier: BSD-3-Clause @@ -8,6 +9,7 @@ module Swarm.Language.Elaborate where import Control.Lens (transform, (^.)) import Swarm.Language.Syntax +import Swarm.Language.Types -- | Perform some elaboration / rewriting on a fully type-annotated -- term. This currently performs such operations as rewriting @if@ @@ -23,7 +25,10 @@ elaborate :: TSyntax -> TSyntax elaborate = transform rewrite where rewrite :: TSyntax -> TSyntax - rewrite (Syntax' l t cs ty) = Syntax' l (rewriteTerm t) cs ty + rewrite = \case + syn@(Syntax' l (TConst Read) cs pty@(ptBody -> TyText :->: outTy)) -> + Syntax' l (SApp syn (Syntax' NoLoc (TType outTy) mempty (mkTrivPoly TyUnit))) cs pty + Syntax' l t cs ty -> Syntax' l (rewriteTerm t) cs ty rewriteTerm :: TTerm -> TTerm rewriteTerm = \case diff --git a/src/swarm-lang/Swarm/Language/LSP/Hover.hs b/src/swarm-lang/Swarm/Language/LSP/Hover.hs index 92d14470f..c57c5d25c 100644 --- a/src/swarm-lang/Swarm/Language/LSP/Hover.hs +++ b/src/swarm-lang/Swarm/Language/LSP/Hover.hs @@ -130,6 +130,7 @@ narrowToPosition s0@(Syntax' _ t _ ty) pos = fromMaybe s0 $ case t of TVar {} -> Nothing TRequire {} -> Nothing TRequireDevice {} -> Nothing + TType {} -> Nothing -- these should not show up in surface language TRef {} -> Nothing TRobot {} -> Nothing @@ -203,6 +204,7 @@ explain trm = case trm ^. sTerm of SRcd {} -> literal "A record literal." SProj {} -> literal "A record projection." STydef {} -> literal "A type synonym definition." + TType {} -> literal "A type literal." -- type ascription SAnnotate lhs typeAnn -> Node diff --git a/src/swarm-lang/Swarm/Language/Parser/Record.hs b/src/swarm-lang/Swarm/Language/Parser/Record.hs index 181fa3896..cfc6511f2 100644 --- a/src/swarm-lang/Swarm/Language/Parser/Record.hs +++ b/src/swarm-lang/Swarm/Language/Parser/Record.hs @@ -16,8 +16,8 @@ import Swarm.Language.Parser.Lex (symbol, tmVar) import Swarm.Util (failT, findDup, squote) import Text.Megaparsec (sepBy) --- | Parse something using record syntax of the form @{x1 v1, x2 v2, --- ...}@. The same parser is used both in parsing record types and +-- | Parse something using record syntax of the form @x1 v1, x2 v2, +-- ...@. The same parser is used both in parsing record types and -- record values, so it is factored out into its own module. -- -- The @Parser a@ argument is the parser to use for the RHS of each diff --git a/src/swarm-lang/Swarm/Language/Parser/Value.hs b/src/swarm-lang/Swarm/Language/Parser/Value.hs new file mode 100644 index 000000000..f9be8aa7c --- /dev/null +++ b/src/swarm-lang/Swarm/Language/Parser/Value.hs @@ -0,0 +1,71 @@ +-- | +-- SPDX-License-Identifier: BSD-3-Clause +-- +-- Parse values of the Swarm language, indexed by type, by running the +-- full swarm-lang parser and then checking that the result is a value +-- of the proper type. +module Swarm.Language.Parser.Value (readValue) where + +import Control.Lens ((^.)) +import Data.Either.Extra (eitherToMaybe) +import Data.Text (Text) +import Swarm.Language.Context qualified as Ctx +import Swarm.Language.Key (parseKeyComboFull) +import Swarm.Language.Parser (readNonemptyTerm) +import Swarm.Language.Syntax +import Swarm.Language.Typecheck (checkTop) +import Swarm.Language.Types (Type) +import Swarm.Language.Value +import Text.Megaparsec qualified as MP + +readValue :: Type -> Text -> Maybe Value +readValue ty txt = do + s <- eitherToMaybe $ readNonemptyTerm txt + _ <- eitherToMaybe $ checkTop Ctx.empty Ctx.empty Ctx.empty s ty + toValue $ s ^. sTerm + +toValue :: Term -> Maybe Value +toValue = \case + TUnit -> Just VUnit + TDir d -> Just $ VDir d + TInt n -> Just $ VInt n + TText t -> Just $ VText t + TBool b -> Just $ VBool b + TApp (TConst c) t2 -> case c of + Neg -> toValue t2 >>= negateInt + Inl -> VInj False <$> toValue t2 + Inr -> VInj True <$> toValue t2 + Key -> do + VText k <- toValue t2 + VKey <$> eitherToMaybe (MP.runParser parseKeyComboFull "" k) + _ -> Nothing + TPair t1 t2 -> VPair <$> toValue t1 <*> toValue t2 + TRcd m -> VRcd <$> traverse (>>= toValue) m + -- List the other cases explicitly, instead of a catch-all, so that + -- we will get a warning if we ever add new constructors in the + -- future + TConst {} -> Nothing + TAntiInt {} -> Nothing + TAntiText {} -> Nothing + TRequireDevice {} -> Nothing + TRequire {} -> Nothing + TRequirements {} -> Nothing + TVar {} -> Nothing + TLam {} -> Nothing + TApp {} -> Nothing + TLet {} -> Nothing + TTydef {} -> Nothing + TBind {} -> Nothing + TDelay {} -> Nothing + TProj {} -> Nothing + TAnnotate {} -> Nothing + TSuspend {} -> Nothing + +-- TODO(#2232): in order to get `read` to work for delay, function, +-- and/or command types, we will need to handle a few more of the +-- above cases, e.g. TConst, TLam, TApp, TLet, TBind, TDelay. + +negateInt :: Value -> Maybe Value +negateInt = \case + VInt n -> Just (VInt (-n)) + _ -> Nothing diff --git a/src/swarm-lang/Swarm/Language/Syntax/AST.hs b/src/swarm-lang/Swarm/Language/Syntax/AST.hs index 753fcaafa..8497a3fe0 100644 --- a/src/swarm-lang/Swarm/Language/Syntax/AST.hs +++ b/src/swarm-lang/Swarm/Language/Syntax/AST.hs @@ -142,6 +142,8 @@ data Term' ty | -- | Run the given command, then suspend and wait for a new REPL -- input. SSuspend (Syntax' ty) + | -- | A type literal. + TType Type deriving ( Eq , Show diff --git a/src/swarm-lang/Swarm/Language/Syntax/Constants.hs b/src/swarm-lang/Swarm/Language/Syntax/Constants.hs index e858bd95d..6d15f7cc9 100644 --- a/src/swarm-lang/Swarm/Language/Syntax/Constants.hs +++ b/src/swarm-lang/Swarm/Language/Syntax/Constants.hs @@ -271,6 +271,8 @@ data Const -- | Turn an arbitrary value into a string Format + | -- | Try to turn a string into a value + Read | -- | Concatenate string values Concat | -- | Count number of characters. @@ -813,6 +815,7 @@ constInfo c = case c of Leq -> binaryOp "<=" 4 N $ shortDoc Set.empty "Check that the left value is lesser 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 $ shortDoc Set.empty "Turn an arbitrary value into a string." + Read -> function 2 $ shortDoc Set.empty "Try to read a string into a value of the expected type." Concat -> binaryOp "++" 6 R $ shortDoc Set.empty "Concatenate the given strings." Chars -> function 1 $ shortDoc Set.empty "Counts the number of characters in the text." Split -> diff --git a/src/swarm-lang/Swarm/Language/Syntax/Pretty.hs b/src/swarm-lang/Swarm/Language/Syntax/Pretty.hs index b48f11af1..50d2a0fdc 100644 --- a/src/swarm-lang/Swarm/Language/Syntax/Pretty.hs +++ b/src/swarm-lang/Swarm/Language/Syntax/Pretty.hs @@ -136,6 +136,7 @@ instance PrettyPrec (Term' ty) where SSuspend t -> pparens (p > 10) $ "suspend" <+> prettyPrec 11 t + TType ty -> prettyPrec p ty prettyDefinition :: Doc ann -> Var -> Maybe (Poly q Type) -> Syntax' ty -> Doc ann prettyDefinition defName x mty t1 = diff --git a/src/swarm-lang/Swarm/Language/Syntax/Util.hs b/src/swarm-lang/Swarm/Language/Syntax/Util.hs index bbd1c1b7a..d21b17179 100644 --- a/src/swarm-lang/Swarm/Language/Syntax/Util.hs +++ b/src/swarm-lang/Swarm/Language/Syntax/Util.hs @@ -145,6 +145,7 @@ freeVarsS f = go S.empty SProj s1 x -> rewrap $ SProj <$> go bound s1 <*> pure x SAnnotate s1 pty -> rewrap $ SAnnotate <$> go bound s1 <*> pure pty SSuspend s1 -> rewrap $ SSuspend <$> go bound s1 + TType {} -> pure s where rewrap s' = Syntax' l <$> s' <*> pure ty <*> pure cmts diff --git a/src/swarm-lang/Swarm/Language/Typecheck.hs b/src/swarm-lang/Swarm/Language/Typecheck.hs index e732e518b..49c7bda68 100644 --- a/src/swarm-lang/Swarm/Language/Typecheck.hs +++ b/src/swarm-lang/Swarm/Language/Typecheck.hs @@ -39,6 +39,7 @@ module Swarm.Language.Typecheck ( -- * Type inference inferTop, + checkTop, infer, inferConst, check, @@ -811,6 +812,10 @@ decomposeProdTy = decomposeTyConApp2 TCProd inferTop :: TCtx -> ReqCtx -> TDCtx -> Syntax -> Either ContextualTypeErr TSyntax inferTop ctx reqCtx tdCtx = runTC ctx reqCtx tdCtx Ctx.empty . infer +-- | Top level type checking function. +checkTop :: TCtx -> ReqCtx -> TDCtx -> Syntax -> Type -> Either ContextualTypeErr TSyntax +checkTop ctx reqCtx tdCtx t ty = runTC ctx reqCtx tdCtx Ctx.empty $ check t (toU ty) + -- | Infer the type of a term, returning a type-annotated term. -- -- The only cases explicitly handled in 'infer' are those where @@ -1094,6 +1099,7 @@ inferConst c = run . runReader @TVCtx Ctx.empty . quantify $ case c of Div -> arithBinT Exp -> arithBinT Format -> [tyQ| a -> Text |] + Read -> [tyQ| Text -> a |] Concat -> [tyQ| Text -> Text -> Text |] Chars -> [tyQ| Text -> Int |] Split -> [tyQ| Int -> Text -> (Text * Text) |] @@ -1387,6 +1393,7 @@ analyzeAtomic locals (Syntax l t) = case t of TRequire {} -> return 0 SRequirements {} -> return 0 STydef {} -> return 0 + TType {} -> return 0 -- Constants. TConst c -- Nested 'atomic' is not allowed. diff --git a/src/swarm-lang/Swarm/Language/Types.hs b/src/swarm-lang/Swarm/Language/Types.hs index 3ef4e05ae..2d7f8d14d 100644 --- a/src/swarm-lang/Swarm/Language/Types.hs +++ b/src/swarm-lang/Swarm/Language/Types.hs @@ -112,6 +112,7 @@ module Swarm.Language.Types ( tydefArity, substTydef, expandTydef, + expandTydefs, elimTydef, TDCtx, @@ -125,10 +126,11 @@ module Swarm.Language.Types ( import Control.Algebra (Has, run) import Control.Carrier.Reader (runReader) import Control.Effect.Reader (Reader, ask) -import Control.Lens (makeLenses, view) +import Control.Lens (Plated (..), makeLenses, rewriteM, view) import Control.Monad.Free import Data.Aeson (FromJSON (..), FromJSON1 (..), ToJSON (..), ToJSON1 (..), genericLiftParseJSON, genericLiftToJSON, genericParseJSON, genericToJSON) import Data.Data (Data) +import Data.Data.Lens (uniplate) import Data.Eq.Deriving (deriveEq1) import Data.Fix import Data.Foldable (fold) @@ -299,6 +301,9 @@ instance FromJSON1 TypeF where -- with 'Type' as if it were defined in a directly recursive way. type Type = Fix TypeF +instance Plated Type where + plate = uniplate + newtype IntVar = IntVar Int deriving (Show, Data, Eq, Ord, Generic, Hashable) @@ -818,6 +823,16 @@ expandTydef userTyCon tys = do tydefInfo = fromMaybe (error errMsg) mtydefInfo return $ substTydef tydefInfo tys +-- | Expand *all* applications of user-defined type constructors +-- everywhere in a type. +expandTydefs :: forall sig m. (Has (Reader TDCtx) sig m) => Type -> m Type +expandTydefs = rewriteM expand + where + expand :: Type -> m (Maybe Type) + expand = \case + TyUser u tys -> Just <$> expandTydef u tys + _ -> pure Nothing + -- | Given the definition of a type alias, substitute the given -- arguments into its body and return the resulting type. substTydef :: forall t. Typical t => TydefInfo -> [t] -> t diff --git a/src/swarm-lang/Swarm/Language/Value.hs b/src/swarm-lang/Swarm/Language/Value.hs index 802146cc5..2bd67f11b 100644 --- a/src/swarm-lang/Swarm/Language/Value.hs +++ b/src/swarm-lang/Swarm/Language/Value.hs @@ -44,7 +44,7 @@ import Swarm.Language.Requirements.Type (ReqCtx, Requirements) import Swarm.Language.Syntax import Swarm.Language.Syntax.Direction import Swarm.Language.Typed -import Swarm.Language.Types (Polytype, TCtx, TDCtx, TydefInfo) +import Swarm.Language.Types (Polytype, TCtx, TDCtx, TydefInfo, Type) import Swarm.Pretty (prettyText) -- | A /value/ is a term that cannot (or does not) take any more @@ -119,6 +119,9 @@ data Value where -- .) VBlackhole :: Value + -- | A special value used to represent runtime type information + -- passed to ad-hoc polymorphic functions. + VType :: Type -> Value deriving (Eq, Show, Generic, Hashable) -- | A value context is a mapping from variable names to their runtime @@ -241,3 +244,4 @@ valueToTerm = \case VSuspend t _ -> TSuspend t VExc -> TConst Undefined VBlackhole -> TConst Undefined + VType _ -> TConst Undefined diff --git a/swarm.cabal b/swarm.cabal index ad8e25b49..d40116b41 100644 --- a/swarm.cabal +++ b/swarm.cabal @@ -460,6 +460,7 @@ library swarm-lang Swarm.Language.Parser.Term Swarm.Language.Parser.Type Swarm.Language.Parser.Util + Swarm.Language.Parser.Value Swarm.Language.Pipeline Swarm.Language.Pipeline.QQ Swarm.Language.Requirements diff --git a/test/unit/TestEval.hs b/test/unit/TestEval.hs index 37cc7ee61..34164a7fd 100644 --- a/test/unit/TestEval.hs +++ b/test/unit/TestEval.hs @@ -12,7 +12,11 @@ import Data.Char (ord) import Data.Map qualified as M import Data.Text (Text) import Data.Text qualified as T +import Graphics.Vty.Input.Events qualified as V import Swarm.Game.State +import Swarm.Game.Value (Valuable (..)) +import Swarm.Language.Key +import Swarm.Language.Syntax.Direction import Swarm.Language.Value import Test.Tasty import Test.Tasty.HUnit @@ -268,6 +272,118 @@ testEval g = `evaluatesToP` VInt i ) ] + , testGroup + "read" + [ testCase + "read Unit" + ("read \"()\" : Unit" `evaluatesToV` ()) + , testCase + "read Unit with spaces" + ("read \" () \" : Unit" `evaluatesToV` ()) + , testCase + "no read Unit" + ("read \"xyz\" : Unit" `throwsError` ("Could not read" `T.isInfixOf`)) + , testCase + "read Int" + ("read \"32\" : Int" `evaluatesToV` (32 :: Integer)) + , testCase + "read negative Int" + ("read \"-32\" : Int" `evaluatesToV` (-32 :: Integer)) + , testCase + "read Int with spaces" + ("read \" - 32 \" : Int" `evaluatesToV` (-32 :: Integer)) + , testCase + "no read Int" + ("read \"32.0\" : Int" `throwsError` ("Could not read" `T.isInfixOf`)) + , testCase + "read false" + ("read \"false\" : Bool" `evaluatesToV` False) + , testCase + "read true" + ("read \"true\" : Bool" `evaluatesToV` True) + , testCase + "read forward" + ( "read \"forward\" : Dir" + `evaluatesTo` VDir (DRelative (DPlanar DForward)) + ) + , testCase + "read east" + ("read \"east\" : Dir" `evaluatesTo` VDir (DAbsolute DEast)) + , testCase + "read down" + ("read \"down\" : Dir" `evaluatesTo` VDir (DRelative DDown)) + , testCase + "read text" + ("read \"\\\"hi\\\"\" : Text" `evaluatesToV` ("hi" :: Text)) + , testCase + "read sum inl" + ( "read \"inl 3\" : (Int + Bool)" + `evaluatesToV` Left @Integer @Bool 3 + ) + , testCase + "read sum inr" + ( "read \"inr true\" : (Int + Bool)" + `evaluatesToV` Right @Integer True + ) + , testCase + "read nested sum" + ( "read \"inl (inr true)\" : ((Int + Bool) + Unit)" + `evaluatesToV` Left @_ @() (Right @Integer True) + ) + , testCase + "read pair" + ( "read \"(3, true)\" : Int * Bool" + `evaluatesToV` (3 :: Integer, True) + ) + , testCase + "read pair with non-atomic value" + ( "read \"(3, inr true)\" : Int * (Unit + Bool)" + `evaluatesToV` (3 :: Integer, Right @() True) + ) + , testCase + "read nested pair" + ( "read \"(3, true, ())\" : Int * Bool * Unit" + `evaluatesToV` (3 :: Integer, (True, ())) + ) + , testCase + "read left-nested pair" + ( "read \"((3, true), ())\" : ((Int * Bool) * Unit)" + `evaluatesToV` ((3 :: Integer, True), ()) + ) + , testCase + "read empty record" + ("read \"[]\" : []" `evaluatesTo` VRcd M.empty) + , testCase + "read singleton record" + ( "read \"[x = 2]\" : [x : Int]" + `evaluatesTo` VRcd (M.singleton "x" (VInt 2)) + ) + , testCase + "read doubleton record" + ( "read \"[x = 2, y = inr ()]\" : [x : Int, y : Bool + Unit]" + `evaluatesTo` (VRcd . M.fromList $ [("x", VInt 2), ("y", VInj True VUnit)]) + ) + , testCase + "read permuted doubleton record" + ( "read \"[y = inr (), x = 2]\" : [x : Int, y : Bool + Unit]" + `evaluatesTo` (VRcd . M.fromList $ [("x", VInt 2), ("y", VInj True VUnit)]) + ) + , testCase + "no read record with repeated fields" + ( "read \"[x = 2, x = 3]\" : [x : Int]" + `throwsError` ("Could not read" `T.isInfixOf`) + ) + , testCase + "read key" + ( "read \"key \\\"M-C-F5\\\"\" : Key" + `evaluatesTo` VKey (mkKeyCombo [V.MCtrl, V.MMeta] (V.KFun 5)) + ) + , testCase + "read recursive list" + ( "read \"inr (3, inr (5, inl ()))\" : rec l. Unit + (Int * l)" + `evaluatesToV` [3 :: Integer, 5] + ) + ] , testGroup "records - #1093" [ testCase @@ -365,6 +481,9 @@ testEval g = result <- evaluate tm assertEqual "" (Right val) (fst <$> result) + evaluatesToV :: Valuable v => Text -> v -> Assertion + evaluatesToV tm val = tm `evaluatesTo` asValue val + evaluatesToP :: Text -> Value -> Property evaluatesToP tm val = ioProperty $ do result <- evaluate tm diff --git a/weeder.toml b/weeder.toml index 21c1c1750..177979db9 100644 --- a/weeder.toml +++ b/weeder.toml @@ -40,6 +40,7 @@ roots = [ "^Swarm.Language.Syntax.Pattern.UTerm$", "^Swarm.Language.Syntax.Util.asTree$", "^Swarm.Language.Syntax.Util.mapFreeS$", + "^Swarm.Language.Types.expandTydefs$", "^Swarm.Util.isSuccessOr$", "^Swarm.Util.replaceLast$", "^Swarm.Util.reflow$",