mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-24 03:35:17 +03:00
Added support for custom evaluation to Analyzer. (#375)
This commit is contained in:
parent
bb3964ae6f
commit
880cd39226
@ -141,3 +141,4 @@ tests:
|
|||||||
- path
|
- path
|
||||||
- unordered-containers
|
- unordered-containers
|
||||||
- strong-path
|
- strong-path
|
||||||
|
- split
|
||||||
|
@ -30,9 +30,11 @@
|
|||||||
-- @Page { title = "Home", author = Nothing, content = "Hello world" }@
|
-- @Page { title = "Home", author = Nothing, content = "Hello world" }@
|
||||||
module Wasp.Analyzer.Evaluator.Evaluation
|
module Wasp.Analyzer.Evaluator.Evaluation
|
||||||
( runEvaluation,
|
( runEvaluation,
|
||||||
|
evaluation,
|
||||||
|
evaluation',
|
||||||
module Wasp.Analyzer.Evaluator.Evaluation.Combinators,
|
module Wasp.Analyzer.Evaluator.Evaluation.Combinators,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Wasp.Analyzer.Evaluator.Evaluation.Combinators
|
import Wasp.Analyzer.Evaluator.Evaluation.Combinators
|
||||||
import Wasp.Analyzer.Evaluator.Evaluation.Internal (runEvaluation)
|
import Wasp.Analyzer.Evaluator.Evaluation.Internal (evaluation, evaluation', runEvaluation)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
module Wasp.Analyzer.Evaluator.EvaluationError
|
module Wasp.Analyzer.Evaluator.EvaluationError
|
||||||
( EvaluationError (..),
|
( EvaluationError (..),
|
||||||
EvaluationErrorContext (..),
|
EvaluationErrorContext (..),
|
||||||
|
EvaluationParseError (..),
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Text.Parsec (ParseError)
|
import qualified Text.Parsec
|
||||||
import Wasp.Analyzer.Type (Type)
|
import Wasp.Analyzer.Type (Type)
|
||||||
|
|
||||||
data EvaluationError
|
data EvaluationError
|
||||||
@ -20,9 +21,10 @@ data EvaluationError
|
|||||||
InvalidEnumVariant String String
|
InvalidEnumVariant String String
|
||||||
| -- | "MissingField fieldName"
|
| -- | "MissingField fieldName"
|
||||||
MissingField String
|
MissingField String
|
||||||
| -- | In case when evaluation includes parsing with Parsec and it fails.
|
| -- | In case when evaluation includes parsing and it fails.
|
||||||
ParseError ParseError
|
ParseError EvaluationParseError
|
||||||
| WithContext EvaluationErrorContext EvaluationError
|
| -- | Not an actual error, but a wrapper that provides additional context.
|
||||||
|
WithContext EvaluationErrorContext EvaluationError
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
data EvaluationErrorContext
|
data EvaluationErrorContext
|
||||||
@ -32,3 +34,10 @@ data EvaluationErrorContext
|
|||||||
| -- | ForVariable varName
|
| -- | ForVariable varName
|
||||||
ForVariable String
|
ForVariable String
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
data EvaluationParseError
|
||||||
|
= -- | In case when evaluation includes parsing with Parsec and it fails.
|
||||||
|
EvaluationParseErrorParsec Text.Parsec.ParseError
|
||||||
|
| -- | In case when evaluation does some general parsing and it fails.
|
||||||
|
EvaluationParseError String
|
||||||
|
deriving (Show, Eq)
|
||||||
|
@ -5,7 +5,7 @@ module Wasp.Analyzer.StdTypeDefinitions.Entity () where
|
|||||||
|
|
||||||
import Control.Arrow (left)
|
import Control.Arrow (left)
|
||||||
import qualified Text.Parsec as Parsec
|
import qualified Text.Parsec as Parsec
|
||||||
import qualified Wasp.Analyzer.Evaluator.EvaluationError as EvaluationError
|
import qualified Wasp.Analyzer.Evaluator.EvaluationError as ER
|
||||||
import qualified Wasp.Analyzer.Type as Type
|
import qualified Wasp.Analyzer.Type as Type
|
||||||
import qualified Wasp.Analyzer.TypeChecker.AST as TC.AST
|
import qualified Wasp.Analyzer.TypeChecker.AST as TC.AST
|
||||||
import Wasp.Analyzer.TypeDefinitions (DeclType (..), IsDeclType (..))
|
import Wasp.Analyzer.TypeDefinitions (DeclType (..), IsDeclType (..))
|
||||||
@ -24,6 +24,6 @@ instance IsDeclType Entity where
|
|||||||
|
|
||||||
declEvaluate _ _ expr = case expr of
|
declEvaluate _ _ expr = case expr of
|
||||||
TC.AST.PSL pslString ->
|
TC.AST.PSL pslString ->
|
||||||
left EvaluationError.ParseError $
|
left (ER.ParseError . ER.EvaluationParseErrorParsec) $
|
||||||
makeEntity <$> Parsec.parse Wasp.Psl.Parser.Model.body "" pslString
|
makeEntity <$> Parsec.parse Wasp.Psl.Parser.Model.body "" pslString
|
||||||
_ -> Left $ EvaluationError.ExpectedType (Type.QuoterType "psl") (TC.AST.exprType expr)
|
_ -> Left $ ER.ExpectedType (Type.QuoterType "psl") (TC.AST.exprType expr)
|
||||||
|
@ -19,7 +19,8 @@ module Wasp.Analyzer.TypeDefinitions
|
|||||||
where
|
where
|
||||||
|
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
import Wasp.Analyzer.TypeDefinitions.Class
|
import Wasp.Analyzer.TypeDefinitions.Class.IsDeclType
|
||||||
|
import Wasp.Analyzer.TypeDefinitions.Class.IsEnumType
|
||||||
import Wasp.Analyzer.TypeDefinitions.Internal
|
import Wasp.Analyzer.TypeDefinitions.Internal
|
||||||
|
|
||||||
empty :: TypeDefinitions
|
empty :: TypeDefinitions
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
|
||||||
|
module Wasp.Analyzer.TypeDefinitions.Class.HasCustomEvaluation
|
||||||
|
( HasCustomEvaluation (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.Typeable (Typeable)
|
||||||
|
import Wasp.Analyzer.Evaluator.Evaluation.TypedExpr (TypedExprEvaluation)
|
||||||
|
import Wasp.Analyzer.Type (Type)
|
||||||
|
|
||||||
|
-- | For a Haskell type @a@, provides its Wasp @Type@ (via @waspType@ function)
|
||||||
|
-- | and its @Evaluation@ (via @evaluation@ function) that evaluates from
|
||||||
|
-- | @waspType@ into @a@.
|
||||||
|
-- | This class can be useful when using @makeDeclType@, since @makeDeclType@ checks
|
||||||
|
-- | for types with this class and if they have it, it uses their custom evaluation
|
||||||
|
-- | instead of trying to figure out their evaluation on its own.
|
||||||
|
class (Typeable a) => HasCustomEvaluation a where
|
||||||
|
waspType :: Type
|
||||||
|
evaluation :: TypedExprEvaluation a
|
@ -1,8 +1,7 @@
|
|||||||
{-# LANGUAGE AllowAmbiguousTypes #-}
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
|
||||||
module Wasp.Analyzer.TypeDefinitions.Class
|
module Wasp.Analyzer.TypeDefinitions.Class.IsDeclType
|
||||||
( IsDeclType (..),
|
( IsDeclType (..),
|
||||||
IsEnumType (..),
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -10,52 +9,42 @@ import Data.Typeable (Typeable)
|
|||||||
import Wasp.Analyzer.Evaluator.Bindings (Bindings)
|
import Wasp.Analyzer.Evaluator.Bindings (Bindings)
|
||||||
import Wasp.Analyzer.Evaluator.EvaluationError (EvaluationError)
|
import Wasp.Analyzer.Evaluator.EvaluationError (EvaluationError)
|
||||||
import Wasp.Analyzer.TypeChecker.AST (TypedExpr)
|
import Wasp.Analyzer.TypeChecker.AST (TypedExpr)
|
||||||
import Wasp.Analyzer.TypeDefinitions.Internal (DeclType, EnumType, TypeDefinitions)
|
import Wasp.Analyzer.TypeDefinitions.Internal (DeclType, TypeDefinitions)
|
||||||
import qualified Wasp.AppSpec.Core.Decl as AppSpecDecl
|
import qualified Wasp.AppSpec.Core.Decl as AppSpecDecl
|
||||||
|
|
||||||
-- | Marks Haskell type as a representation of a specific Wasp declaration type.
|
-- | Marks Haskell type as a representation of a specific Wasp declaration type.
|
||||||
-- This is supposed to be used on types from Wasp AST (the IR between Analyzer and Generator)
|
-- This is supposed to be used on types from @AppSpec@ (the main Wasp IR)
|
||||||
-- in order to enrich them with information on how are they to be analyzed
|
-- in order to enrich them with information on how are they to be analyzed
|
||||||
-- (and therefore make them part of the Wasp language).
|
-- (and therefore make them part of the Wasp language).
|
||||||
--
|
--
|
||||||
|
-- To put it very practically, if you make a Haskell type (usually from @AppSpec@)
|
||||||
|
-- an instance of this class, and if you add it to "Wasp.Analyzer.StdTypeDefinitions",
|
||||||
|
-- you are telling Analyzer that there is a new declaration type in Wasp language with
|
||||||
|
-- name, Wasp type and evaluation from that Wasp type into Haskell type as specified
|
||||||
|
-- by this instance, and therefore you just added a new declaration type to the
|
||||||
|
-- Wasp language and Analyzer will include it in the final result it produces.
|
||||||
|
--
|
||||||
-- NOTE: If this Haskell type satisfies certain requirements, the IsDeclType instance for it
|
-- NOTE: If this Haskell type satisfies certain requirements, the IsDeclType instance for it
|
||||||
-- can be automatically derived from its shape by using 'Analyzer.Evaluator.TH.makeDeclType'.
|
-- can be automatically derived from its shape by using
|
||||||
|
-- 'Wasp.Analyzer.TypeDefinitions.TH.makeDeclType'.
|
||||||
class (Typeable a, AppSpecDecl.IsDecl a) => IsDeclType a where
|
class (Typeable a, AppSpecDecl.IsDecl a) => IsDeclType a where
|
||||||
declType :: DeclType
|
declType :: DeclType
|
||||||
|
|
||||||
-- | Evaluates a given Wasp "TypedExpr" to a value of this type, assuming it is of
|
-- | Evaluates a given Wasp "TypedExpr" to a value of type @a@, assuming given
|
||||||
-- declaration type described by (dtBodyType . declType) and (dtName . declType) (otherwise throws an error).
|
-- typed expression is of declaration type described by @dtBodyType . declType@
|
||||||
|
-- and @dtName . declType@ (otherwise throws an error).
|
||||||
--
|
--
|
||||||
-- For @declEvaluate typeDefs bindings declBodyExpr@:
|
-- For @declEvaluate typeDefs bindings declBodyExpr@:
|
||||||
-- - "typeDefs" is the type definitions used in the Analyzer
|
-- - "typeDefs" is the type definitions used in the Analyzer
|
||||||
-- - "bindings" contains the values of all the declarations evaluated so far
|
-- - "bindings" contains the values of all the declarations evaluated so far
|
||||||
-- - "declBodyExpr" is the expression describing declaration body, that should be evaluated by this function
|
-- - "declBodyExpr" is the expression describing declaration body,
|
||||||
|
-- that should be evaluated by this function
|
||||||
--
|
--
|
||||||
-- __Examples__
|
-- __Examples__
|
||||||
--
|
--
|
||||||
-- Imagine that we have Wasp code @test Example 4@, and we have @instance IsDeclType Test@.
|
-- Imagine that we have Wasp code @test Example 4@, and we have @instance IsDeclType Test@.
|
||||||
-- Here, @test@ is declaration type name, @Example@ is declaration name, and @4@ is declaration body.
|
-- Here, @test@ is declaration type name, @Example@ is declaration name,
|
||||||
|
-- and @4@ is declaration body.
|
||||||
-- @declEvaluate@ function would then be called somewhat like:
|
-- @declEvaluate@ function would then be called somewhat like:
|
||||||
-- @declEvaluate @Test typeDefs bindings (NumberLiteral 4)@
|
-- @declEvaluate @Test typeDefs bindings (NumberLiteral 4)@
|
||||||
declEvaluate :: TypeDefinitions -> Bindings -> TypedExpr -> Either EvaluationError a
|
declEvaluate :: TypeDefinitions -> Bindings -> TypedExpr -> Either EvaluationError a
|
||||||
|
|
||||||
-- | Marks Haskell type as a representation of a specific Wasp enum type.
|
|
||||||
-- Analogous to IsDeclType, but for enums.
|
|
||||||
--
|
|
||||||
-- NOTE: If this Haskell type satisfies certain requirements, the IsEnumType instance for it
|
|
||||||
-- can be automatically derived from its shape by using 'Analyzer.Evaluator.TH.makeEnumType'.
|
|
||||||
class Typeable a => IsEnumType a where
|
|
||||||
enumType :: EnumType
|
|
||||||
|
|
||||||
-- | Converts a string to a Haskell value of this type.
|
|
||||||
--
|
|
||||||
-- @mapM_ enumEvaluate (etVariants enumType) == Right ()@ should be true
|
|
||||||
-- for all instances of "IsEnumType".
|
|
||||||
--
|
|
||||||
-- __Examples__
|
|
||||||
--
|
|
||||||
-- >>> data Example = Foo | Bar
|
|
||||||
-- >>> instance IsEnumType Example where {- omitted -}
|
|
||||||
-- >>> enumEvaluate "Foo"
|
|
||||||
-- Foo
|
|
||||||
enumEvaluate :: String -> Either EvaluationError a
|
|
31
waspc/src/Wasp/Analyzer/TypeDefinitions/Class/IsEnumType.hs
Normal file
31
waspc/src/Wasp/Analyzer/TypeDefinitions/Class/IsEnumType.hs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
|
|
||||||
|
module Wasp.Analyzer.TypeDefinitions.Class.IsEnumType
|
||||||
|
( IsEnumType (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.Typeable (Typeable)
|
||||||
|
import Wasp.Analyzer.Evaluator.EvaluationError (EvaluationError)
|
||||||
|
import Wasp.Analyzer.TypeDefinitions.Internal (EnumType)
|
||||||
|
|
||||||
|
-- | Marks Haskell type as a representation of a specific Wasp enum type.
|
||||||
|
-- Analogous to IsDeclType, but for enums.
|
||||||
|
--
|
||||||
|
-- NOTE: If this Haskell type satisfies certain requirements, the IsEnumType instance for it
|
||||||
|
-- can be automatically derived from its shape by using 'Analyzer.Evaluator.TH.makeEnumType'.
|
||||||
|
class Typeable a => IsEnumType a where
|
||||||
|
enumType :: EnumType
|
||||||
|
|
||||||
|
-- | Converts a string to a Haskell value of this type.
|
||||||
|
--
|
||||||
|
-- @mapM_ enumEvaluate (etVariants enumType) == Right ()@ should be true
|
||||||
|
-- for all instances of "IsEnumType".
|
||||||
|
--
|
||||||
|
-- __Examples__
|
||||||
|
--
|
||||||
|
-- >>> data Example = Foo | Bar
|
||||||
|
-- >>> instance IsEnumType Example where {- omitted -}
|
||||||
|
-- >>> enumEvaluate "Foo"
|
||||||
|
-- Foo
|
||||||
|
enumEvaluate :: String -> Either EvaluationError a
|
@ -11,37 +11,89 @@ import Control.Applicative ((<|>))
|
|||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
import Language.Haskell.TH.Syntax (VarBangType)
|
import Language.Haskell.TH.Syntax (VarBangType)
|
||||||
import Wasp.Analyzer.Evaluator.Evaluation
|
import qualified Wasp.Analyzer.Evaluator.Evaluation as E
|
||||||
import qualified Wasp.Analyzer.Type as T
|
import qualified Wasp.Analyzer.Type as T
|
||||||
import Wasp.Analyzer.TypeDefinitions (DeclType (..), EnumType (..), IsDeclType (..), IsEnumType (..))
|
import Wasp.Analyzer.TypeDefinitions (DeclType (..), EnumType (..), IsDeclType (..), IsEnumType (..))
|
||||||
|
import Wasp.Analyzer.TypeDefinitions.Class.HasCustomEvaluation (HasCustomEvaluation)
|
||||||
|
import qualified Wasp.Analyzer.TypeDefinitions.Class.HasCustomEvaluation as HasCustomEvaluation
|
||||||
import Wasp.Analyzer.TypeDefinitions.TH.Common
|
import Wasp.Analyzer.TypeDefinitions.TH.Common
|
||||||
import Wasp.AppSpec.Core.Decl (makeDecl)
|
import Wasp.AppSpec.Core.Decl (makeDecl)
|
||||||
import Wasp.AppSpec.Core.Ref (Ref)
|
import Wasp.AppSpec.Core.Ref (Ref)
|
||||||
import qualified Wasp.AppSpec.ExtImport as AppSpec.ExtImport
|
import qualified Wasp.AppSpec.ExtImport as AppSpec.ExtImport
|
||||||
import qualified Wasp.AppSpec.JSON as AppSpec.JSON
|
import qualified Wasp.AppSpec.JSON as AppSpec.JSON
|
||||||
|
|
||||||
-- | @makeDeclType ''Type@ writes an @IsDeclType@ instance for @Type@. A type
|
-- | @makeDeclType ''T@ generates an @IsDeclType@ instance for Haskell type @T@.
|
||||||
-- error is raised if @Type@ does not fit the criteria described below.
|
-- A type error is raised if @T@ does not fit the criteria described below, which are
|
||||||
|
-- required for @makeDeclType@ to be able to automatically generate @IsDeclType@
|
||||||
|
-- instance for a type @T@.
|
||||||
--
|
--
|
||||||
-- Requirements on @Type@ for this to work:
|
-- Requirements on the shape of type @T@ for this to work:
|
||||||
-- - The type must be an ADT with one constructor.
|
-- - type @T@ must be an ADT with one constructor, which must have
|
||||||
-- - The type must have just one field OR use record syntax (in which case it can have multiple fields).
|
-- just one field OR use record syntax (in which case it can have multiple record fields).
|
||||||
|
-- - type @T@ must have no type parameters.
|
||||||
|
-- Therefore, some valid types based on this would be:
|
||||||
|
-- - `data T = Foo String`
|
||||||
|
-- - `data T = Foo { a :: String, b :: Int }`
|
||||||
|
-- while some invalid types would be:
|
||||||
|
-- - `data T = Foo String | Bar Int`
|
||||||
|
-- - `data T = Foo String Int`
|
||||||
|
-- - `data T a = Foo a`
|
||||||
--
|
--
|
||||||
-- Properties that hold for @Type@ and its generated @IsDeclType@ instance:
|
-- Further, there are requirements/limitations as to which types can @T@ contain in itself
|
||||||
-- - For the rest of the bullet points below, let's say that
|
-- (be it as a type of the record field or as a type of the data constructor field).
|
||||||
-- @let bodyType = dtBodyType (declType \@Type)@.
|
-- They can be:
|
||||||
-- - If @Type@ uses record syntax, then
|
-- - Primitive: @String@, @Integer@, @Double@, @Bool@.
|
||||||
-- - @bodyType@ is a @Dict@
|
-- - @[a]@ where @a@ is an accepted type itself.
|
||||||
-- - If and only if there is a key @x@ in @bodyType@,
|
-- - @Maybe a@ (but only as a type of a record field) where @a@ is an accepted type itself.
|
||||||
-- then @Type@ has a record @x@ with the same type.
|
-- - A few special types from AppSpec:
|
||||||
-- - If a key @x@ is optional, then the record @x@ in @Type@ is a @Maybe@.
|
-- - 'Wasp.AppSpec.ExtImport.ExtImport'
|
||||||
-- - If @Type@ is a simple ADT (not record) with one field, then @bodyType@ maps to the type of that field.
|
-- - 'Wasp.AppSpec.JSON.JSON'
|
||||||
-- - @dtName (declType \@Type) == "type"@ -> the name of declaration type is the same as the name of @Type@
|
-- - @Wasp.AppSpec.Core.Ref a@ where @a@ is an instance of @IsDeclType@.
|
||||||
-- but with the first letter changed to lowercase.
|
-- - A type that implements an @IsEnumType@ instance.
|
||||||
|
-- - A type with just one data constructor that is a record, and whose record field types
|
||||||
|
-- are all accepted types.
|
||||||
|
-- - A type that implements a @HasCustomEvaluation@ instance.
|
||||||
|
-- Place in implementation that is a source of truth for this is 'waspKindOfHaskellType'.
|
||||||
|
--
|
||||||
|
-- If all of the above requirements are met, @IsDeclType@ instance is generated.
|
||||||
|
-- Main parts of that instance are Wasp type ('Wasp.Analyzer.Type.Type')
|
||||||
|
-- of the declaration body (referred to as @WT@ from now on) and the evaluator
|
||||||
|
-- that evalutes from that Wasp type @WT@ into Haskell type @T@.
|
||||||
|
-- We can also describe @WT@ with @let WT = dtBodyType (declType \@T)@.
|
||||||
|
-- For the user of @makeDeclType@, it is important to understand how
|
||||||
|
-- @makeDeclType@ determines @WT@ based on the @T@, since @WT@ then determines
|
||||||
|
-- how will this new declaration type (that we described with @T@) manifest
|
||||||
|
-- in the Wasp language itself.
|
||||||
|
--
|
||||||
|
-- @WT@ is determined based on @T@ in the following manner:
|
||||||
|
-- - If @T@ is a record, then @WT@ is a @Dict@ with the same field keys as @T@ has,
|
||||||
|
-- and field types are determined as described below. recursively in this same manner.
|
||||||
|
-- Only exception is if type of field in record @T@ is @Maybe a@ -> in that case
|
||||||
|
-- type of corresponding field in @Dict@ @WT@ is determined from @a@ but field is
|
||||||
|
-- marked as optional (otherwise it is required).
|
||||||
|
-- - If @T@ is a simple ADT (not record) with just one field, then @WT@ is determined
|
||||||
|
-- directly from the type of that one field.
|
||||||
|
-- - When determining how types of fields (in record or in simple ADT) from @T@ map
|
||||||
|
-- to parts of @WT@, following approach is used:
|
||||||
|
-- - Primitives, list, ExtImport, JSON, enum, decl ref -> all those are pretty
|
||||||
|
-- straight-forwardly mapped onto corresponding types from `Wasp.Analyzer.Type`.
|
||||||
|
-- - Type of record field in @T@ maps to required field in @WT@ @Dict@ of
|
||||||
|
-- type that maps per rules mentioned above/below.
|
||||||
|
-- Only exception is if type of field in record @T@ is @Maybe a@ -> in that case
|
||||||
|
-- type of corresponding field in @Dict@ @WT@ is determined from @a@ but field is
|
||||||
|
-- marked as optional (otherwise it is required).
|
||||||
|
-- - If type of field is a record itself, then it is maped in a same way as
|
||||||
|
-- @T@ would be mapped -> as a @Dict@ with the same fields as the record.
|
||||||
|
-- Therefore this results in nested dictionaries.
|
||||||
|
-- - For types that have an instance of @HasCustomEvaluation@,
|
||||||
|
-- its @waspType@ method is directly used to obtain the Wasp type.
|
||||||
|
-- This allows us to take control over how Wasp type is determined for this specific
|
||||||
|
-- sub-part of @T@.
|
||||||
|
--
|
||||||
|
-- Finally, it is important to mention that name of the declaration type is the same
|
||||||
|
-- as the name of @T@ but with first letter changed to lowercase:
|
||||||
|
-- @dtName (declType \@MyType) == "myType"@
|
||||||
--
|
--
|
||||||
-- One way to summarize the crux of properties above is to say: If given @Type@ is a simple ADT with one field,
|
|
||||||
-- it is translated into a declaration which has body corresponding to that field. If instead @Type@
|
|
||||||
-- is a record, it is translated into a declaration whose body is a dictionary with same fields as the record.
|
|
||||||
--
|
--
|
||||||
-- __Examples__
|
-- __Examples__
|
||||||
--
|
--
|
||||||
@ -110,18 +162,19 @@ makeIsDeclTypeInstanceDefinition _ _ = fail "makeDeclType expects given type to
|
|||||||
recordFieldsToNameTypePairs :: [VarBangType] -> [(Name, Type)]
|
recordFieldsToNameTypePairs :: [VarBangType] -> [(Name, Type)]
|
||||||
recordFieldsToNameTypePairs = map $ \(fieldName, _, fieldType) -> (fieldName, fieldType)
|
recordFieldsToNameTypePairs = map $ \(fieldName, _, fieldType) -> (fieldName, fieldType)
|
||||||
|
|
||||||
-- | Create an "IsDeclType" instance for types that have a single data constructor which has a single value, e.g. @data Type = Type x@.
|
-- | Create an "IsDeclType" instance for types that have a single data constructor which has a single value,
|
||||||
|
-- e.g. @data Type = Type x@.
|
||||||
genIsDeclTypeInstanceDefinitionFromNormalDataConstructor :: Name -> Name -> Type -> Q [DecQ]
|
genIsDeclTypeInstanceDefinitionFromNormalDataConstructor :: Name -> Name -> Type -> Q [DecQ]
|
||||||
genIsDeclTypeInstanceDefinitionFromNormalDataConstructor typeName dataConstructorName dataConstructorType = do
|
genIsDeclTypeInstanceDefinitionFromNormalDataConstructor typeName dataConstructorName dataConstructorType = do
|
||||||
let evaluateE = [|runEvaluation $ $(conE dataConstructorName) <$> $(genEvaluationExprForHaskellType dataConstructorType)|]
|
(bodyTypeE, evaluationE) <- genWaspTypeAndEvaluationForHaskellType dataConstructorType
|
||||||
let bodyTypeE = genWaspTypeFromHaskellType dataConstructorType
|
let evaluateE = [|E.runEvaluation $ $(conE dataConstructorName) <$> $evaluationE|]
|
||||||
pure $ genIsDeclTypeInstanceDefinition typeName dataConstructorName bodyTypeE evaluateE
|
pure $ genIsDeclTypeInstanceDefinition typeName dataConstructorName bodyTypeE evaluateE
|
||||||
|
|
||||||
-- | For decls with record constructors, i.e. @data Fields = Fields { a :: String, b :: String }
|
-- | For decls with record constructors, i.e. @data Fields = Fields { a :: String, b :: String }
|
||||||
genIsDeclTypeInstanceDefinitionFromRecordDataConstructor :: Name -> Name -> [(Name, Type)] -> Q [DecQ]
|
genIsDeclTypeInstanceDefinitionFromRecordDataConstructor :: Name -> Name -> [(Name, Type)] -> Q [DecQ]
|
||||||
genIsDeclTypeInstanceDefinitionFromRecordDataConstructor typeName dataConstructorName fields = do
|
genIsDeclTypeInstanceDefinitionFromRecordDataConstructor typeName dataConstructorName fields = do
|
||||||
(dictTypeE, dictEvaluationE) <- genDictTypeAndEvaluationForRecord dataConstructorName fields
|
(dictTypeE, dictEvaluationE) <- genDictWaspTypeAndEvaluationForRecord dataConstructorName fields
|
||||||
let evaluateE = [|runEvaluation $dictEvaluationE|]
|
let evaluateE = [|E.runEvaluation $dictEvaluationE|]
|
||||||
let bodyTypeE = dictTypeE
|
let bodyTypeE = dictTypeE
|
||||||
pure $ genIsDeclTypeInstanceDefinition typeName dataConstructorName bodyTypeE evaluateE
|
pure $ genIsDeclTypeInstanceDefinition typeName dataConstructorName bodyTypeE evaluateE
|
||||||
|
|
||||||
@ -142,96 +195,61 @@ genIsDeclTypeInstanceDefinition typeName dataConstructorName bodyTypeE evaluateE
|
|||||||
genFunc 'declEvaluate evaluateE
|
genFunc 'declEvaluate evaluateE
|
||||||
]
|
]
|
||||||
|
|
||||||
--------------- Dict ------------------
|
--------------- Kind, Wasp Type and Evaluation of a Haskell type ------------------
|
||||||
|
|
||||||
-- | Given a record data constructor name and fields, return the evaluation that evaluates
|
type WaspTypeExpQ = ExpQ
|
||||||
-- Wasp dictionary into the given record, and also return the type of such Wasp dictionary.
|
|
||||||
-- First member of returned couple is type, second is evaluation.
|
type EvaluationExpQ = ExpQ
|
||||||
genDictTypeAndEvaluationForRecord :: Name -> [(Name, Type)] -> Q (ExpQ, ExpQ)
|
|
||||||
genDictTypeAndEvaluationForRecord dataConName fields = do
|
-- | For a given Haskell type @typ@, generates two TH expressions:
|
||||||
(dictEntryTypesE, dictEvaluationE) <- genDictEntriesTypeAndEvaluationForRecord dataConName fields
|
-- one that is a Wasp @Type@, and another that is @Evaluation@ that evaluates from that Wasp @Type@
|
||||||
|
-- into a given Haskell type @typ@.
|
||||||
|
genWaspTypeAndEvaluationForHaskellType :: Type -> Q (WaspTypeExpQ, EvaluationExpQ)
|
||||||
|
genWaspTypeAndEvaluationForHaskellType typ =
|
||||||
|
waspKindOfHaskellType typ >>= \case
|
||||||
|
KString -> return ([|T.StringType|], [|E.string|])
|
||||||
|
KInteger -> return ([|T.NumberType|], [|E.integer|])
|
||||||
|
KDouble -> return ([|T.NumberType|], [|E.double|])
|
||||||
|
KBool -> return ([|T.BoolType|], [|E.bool|])
|
||||||
|
KList elemHaskellType -> do
|
||||||
|
(elemWaspType, elemEvaluation) <- genWaspTypeAndEvaluationForHaskellType elemHaskellType
|
||||||
|
return ([|T.ListType $(elemWaspType)|], [|E.list $(elemEvaluation)|])
|
||||||
|
KImport -> return ([|T.ExtImportType|], [|E.extImport|])
|
||||||
|
KJSON -> return ([|T.QuoterType "json"|], [|E.json|])
|
||||||
|
KDeclRef t ->
|
||||||
return
|
return
|
||||||
( [|T.DictType $ H.fromList $dictEntryTypesE|],
|
( [|T.DeclType $ dtName $ declType @ $(pure t)|],
|
||||||
[|dict $dictEvaluationE|]
|
[|E.declRef @ $(pure t)|]
|
||||||
|
)
|
||||||
|
KEnum ->
|
||||||
|
return
|
||||||
|
( [|T.EnumType $ etName $ enumType @ $(pure typ)|],
|
||||||
|
[|E.enum @ $(pure typ)|]
|
||||||
|
)
|
||||||
|
KRecord dataConName fields -> genDictWaspTypeAndEvaluationForRecord dataConName fields
|
||||||
|
KCustomEvaluation ->
|
||||||
|
return
|
||||||
|
( [|HasCustomEvaluation.waspType @ $(pure typ)|],
|
||||||
|
[|HasCustomEvaluation.evaluation @ $(pure typ)|]
|
||||||
)
|
)
|
||||||
|
|
||||||
genDictEntriesTypeAndEvaluationForRecord :: Name -> [(Name, Type)] -> Q (ExpQ, ExpQ)
|
|
||||||
genDictEntriesTypeAndEvaluationForRecord dataConstructorName fields =
|
|
||||||
go $ reverse fields -- Reversing enables us to apply evaluations in right order.
|
|
||||||
where
|
|
||||||
go [] = pure (listE [], [|pure|] `appE` conE dataConstructorName)
|
|
||||||
go ((fieldName, fieldType) : restOfFields) = do
|
|
||||||
(restDictType, restEvaluation) <- go restOfFields
|
|
||||||
let thisDictTypeE =
|
|
||||||
[|
|
|
||||||
($(nameToStringLiteralExpr fieldName), $(genDictEntryTypeFromHaskellType fieldType)) :
|
|
||||||
$restDictType
|
|
||||||
|]
|
|
||||||
let thisEvaluationE = [|$restEvaluation <*> $(genDictEntryEvaluationForRecordField fieldName fieldType)|]
|
|
||||||
pure (thisDictTypeE, thisEvaluationE)
|
|
||||||
|
|
||||||
-- | Write a @DictEntryType@ that corresponds to a given a Haskell type.
|
|
||||||
genDictEntryTypeFromHaskellType :: Type -> ExpQ
|
|
||||||
genDictEntryTypeFromHaskellType typ =
|
|
||||||
waspKindOfType typ >>= \case
|
|
||||||
KOptional elemType -> [|T.DictOptional $(genWaspTypeFromHaskellType elemType)|]
|
|
||||||
_ -> [|T.DictRequired $(genWaspTypeFromHaskellType typ)|]
|
|
||||||
|
|
||||||
-- | "genDictEvaluationE fieldName typ" writes a "DictEvaluation" for a haskell record field
|
|
||||||
-- named "fieldName" with a value "typ".
|
|
||||||
genDictEntryEvaluationForRecordField :: Name -> Type -> ExpQ
|
|
||||||
genDictEntryEvaluationForRecordField fieldName fieldType =
|
|
||||||
waspKindOfType fieldType >>= \case
|
|
||||||
KOptional elemType -> [|maybeField $(nameToStringLiteralExpr fieldName) $(genEvaluationExprForHaskellType elemType)|]
|
|
||||||
_ -> [|field $(nameToStringLiteralExpr fieldName) $(genEvaluationExprForHaskellType fieldType)|]
|
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
--------------- Types ------------------
|
|
||||||
|
|
||||||
-- | Haskell type -> Wasp type.
|
|
||||||
genWaspTypeFromHaskellType :: Type -> ExpQ
|
|
||||||
genWaspTypeFromHaskellType typ =
|
|
||||||
waspKindOfType typ >>= \case
|
|
||||||
KString -> [|T.StringType|]
|
|
||||||
KInteger -> [|T.NumberType|]
|
|
||||||
KDouble -> [|T.NumberType|]
|
|
||||||
KBool -> [|T.BoolType|]
|
|
||||||
KList elemType -> [|T.ListType $(genWaspTypeFromHaskellType elemType)|]
|
|
||||||
KImport -> [|T.ExtImportType|]
|
|
||||||
KJSON -> [|T.QuoterType "json"|]
|
|
||||||
KDeclRef t -> [|T.DeclType $ dtName $ declType @ $(pure t)|]
|
|
||||||
KEnum -> [|T.EnumType $ etName $ enumType @ $(pure typ)|]
|
|
||||||
KOptional _ -> fail "Maybe is only allowed in record fields"
|
KOptional _ -> fail "Maybe is only allowed in record fields"
|
||||||
KRecord dataConName fields ->
|
|
||||||
fst =<< genDictTypeAndEvaluationForRecord dataConName fields
|
|
||||||
|
|
||||||
-- | Generates an expression that is @Evaluation@ that evaluates to a given Haskell type.
|
|
||||||
genEvaluationExprForHaskellType :: Type -> ExpQ
|
|
||||||
genEvaluationExprForHaskellType typ =
|
|
||||||
waspKindOfType typ >>= \case
|
|
||||||
KString -> [|string|]
|
|
||||||
KInteger -> [|integer|]
|
|
||||||
KDouble -> [|double|]
|
|
||||||
KBool -> [|bool|]
|
|
||||||
KList elemType -> [|list $(genEvaluationExprForHaskellType elemType)|]
|
|
||||||
KImport -> [|extImport|]
|
|
||||||
KJSON -> [|json|]
|
|
||||||
KDeclRef t -> [|declRef @ $(pure t)|]
|
|
||||||
KEnum -> [|enum @ $(pure typ)|]
|
|
||||||
KOptional _ -> fail "Maybe is only allowed in record fields"
|
|
||||||
KRecord dataConName fields ->
|
|
||||||
snd =<< genDictTypeAndEvaluationForRecord dataConName fields
|
|
||||||
|
|
||||||
-- | Find the "WaspKind" of a Haskell type.
|
-- | Find the "WaspKind" of a Haskell type.
|
||||||
waspKindOfType :: Type -> Q WaspKind
|
-- Wasp Kind is really just an intermediate representation that captures
|
||||||
waspKindOfType typ = do
|
-- information we will need to later determine Wasp type and Evaluation for
|
||||||
|
-- this Haskell type, which will be used to evaluate type-checked AST.
|
||||||
|
waspKindOfHaskellType :: Type -> Q WaspKind
|
||||||
|
waspKindOfHaskellType typ = do
|
||||||
maybeDeclRefKind <- tryCastingToDeclRefKind typ
|
maybeDeclRefKind <- tryCastingToDeclRefKind typ
|
||||||
maybeEnumKind <- tryCastingToEnumKind typ
|
maybeEnumKind <- tryCastingToEnumKind typ
|
||||||
|
maybeCustomEvaluationKind <- tryCastingToCustomEvaluationKind typ
|
||||||
maybeRecordKind <- tryCastingToRecordKind typ
|
maybeRecordKind <- tryCastingToRecordKind typ
|
||||||
maybe (fail $ "No translation to wasp type for type " ++ show typ) return $
|
maybe (fail $ "No translation to wasp type for type " ++ show typ) return $
|
||||||
maybeDeclRefKind
|
maybeDeclRefKind
|
||||||
<|> maybeEnumKind
|
<|> maybeEnumKind
|
||||||
|
-- NOTE: It is important that @maybeCustomEvaluationKind@ is before @maybeRecordKind@,
|
||||||
|
-- since having a custom evaluation should override typical record evalution, if type is a record.
|
||||||
|
<|> maybeCustomEvaluationKind
|
||||||
<|> maybeRecordKind
|
<|> maybeRecordKind
|
||||||
<|> case typ of
|
<|> case typ of
|
||||||
ConT name
|
ConT name
|
||||||
@ -265,6 +283,11 @@ waspKindOfType typ = do
|
|||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
tryCastingToRecordKind _ = return Nothing
|
tryCastingToRecordKind _ = return Nothing
|
||||||
|
|
||||||
|
tryCastingToCustomEvaluationKind :: Type -> Q (Maybe WaspKind)
|
||||||
|
tryCastingToCustomEvaluationKind t = do
|
||||||
|
hasInstance <- isInstance ''HasCustomEvaluation [t]
|
||||||
|
return $ if hasInstance then Just KCustomEvaluation else Nothing
|
||||||
|
|
||||||
-- | An intermediate mapping between Haskell types and Wasp types, we use it internally
|
-- | An intermediate mapping between Haskell types and Wasp types, we use it internally
|
||||||
-- in this module when generating @Types@, @Evaluation@, @DictEntryTypes@, and @DictEvaluation@
|
-- in this module when generating @Types@, @Evaluation@, @DictEntryTypes@, and @DictEvaluation@
|
||||||
-- so that we have easier time when figuring out what we are dealing with.
|
-- so that we have easier time when figuring out what we are dealing with.
|
||||||
@ -284,5 +307,48 @@ data WaspKind
|
|||||||
| -- | Type that has a single data constructor that is a record.
|
| -- | Type that has a single data constructor that is a record.
|
||||||
-- KRecord <record constructor name> <fields:(identifier, type)>
|
-- KRecord <record constructor name> <fields:(identifier, type)>
|
||||||
KRecord Name [(Name, Type)]
|
KRecord Name [(Name, Type)]
|
||||||
|
| KCustomEvaluation
|
||||||
|
|
||||||
---------------------------------------
|
---------- Kind, Wasp Type and Evaluation of a Haskell Record as a Wasp Dict -------------
|
||||||
|
|
||||||
|
-- | Given a record data constructor name and fields, return the evaluation that evaluates
|
||||||
|
-- Wasp dictionary into the given record, and also return the type of such Wasp dictionary.
|
||||||
|
-- First member of returned couple is type, second is evaluation.
|
||||||
|
genDictWaspTypeAndEvaluationForRecord :: Name -> [(Name, Type)] -> Q (WaspTypeExpQ, EvaluationExpQ)
|
||||||
|
genDictWaspTypeAndEvaluationForRecord dataConName fields = do
|
||||||
|
(dictEntryTypesE, dictEvaluationE) <- genDictEntriesWaspTypeAndEvaluationForRecord dataConName fields
|
||||||
|
return
|
||||||
|
( [|T.DictType $ H.fromList $dictEntryTypesE|],
|
||||||
|
[|E.dict $dictEvaluationE|]
|
||||||
|
)
|
||||||
|
|
||||||
|
genDictEntriesWaspTypeAndEvaluationForRecord :: Name -> [(Name, Type)] -> Q (WaspTypeExpQ, EvaluationExpQ)
|
||||||
|
genDictEntriesWaspTypeAndEvaluationForRecord dataConstructorName fields =
|
||||||
|
go $ reverse fields -- Reversing enables us to apply evaluations in right order.
|
||||||
|
where
|
||||||
|
go [] = pure (listE [], [|pure|] `appE` conE dataConstructorName)
|
||||||
|
go ((fieldName, fieldType) : restOfFields) = do
|
||||||
|
(fieldWaspType, fieldEvaluation) <- genDictEntryWaspTypeAndEvaluationForRecordField fieldName fieldType
|
||||||
|
(restWaspType, restEvaluation) <- go restOfFields
|
||||||
|
pure
|
||||||
|
( [|($(nameToStringLiteralExpr fieldName), $fieldWaspType) : $restWaspType|],
|
||||||
|
[|$restEvaluation <*> $fieldEvaluation|]
|
||||||
|
)
|
||||||
|
|
||||||
|
-- | For a given Haskell record field (name and type), generate Wasp Type and
|
||||||
|
-- Evaluation from that Wasp Type into given record field.
|
||||||
|
genDictEntryWaspTypeAndEvaluationForRecordField :: Name -> Type -> Q (WaspTypeExpQ, EvaluationExpQ)
|
||||||
|
genDictEntryWaspTypeAndEvaluationForRecordField fieldName fieldType =
|
||||||
|
waspKindOfHaskellType fieldType >>= \case
|
||||||
|
KOptional elemType -> do
|
||||||
|
(waspType, evaluation) <- genWaspTypeAndEvaluationForHaskellType elemType
|
||||||
|
return
|
||||||
|
( [|T.DictOptional $(waspType)|],
|
||||||
|
[|E.maybeField $(nameToStringLiteralExpr fieldName) $(evaluation)|]
|
||||||
|
)
|
||||||
|
_ -> do
|
||||||
|
(waspType, evaluation) <- genWaspTypeAndEvaluationForHaskellType fieldType
|
||||||
|
return
|
||||||
|
( [|T.DictRequired $(waspType)|],
|
||||||
|
[|E.field $(nameToStringLiteralExpr fieldName) $(evaluation)|]
|
||||||
|
)
|
||||||
|
@ -18,18 +18,6 @@ data App = App
|
|||||||
|
|
||||||
-- | NOTE: This is new. Before, `dependencies` was a standalone declaration and it was a {=json json=},
|
-- | NOTE: This is new. Before, `dependencies` was a standalone declaration and it was a {=json json=},
|
||||||
-- while now it is a [{ name :: String, version :: String }].
|
-- while now it is a [{ name :: String, version :: String }].
|
||||||
-- TODO: We might want to look into making this nicer in the future
|
|
||||||
-- -> maybe change how it is represented in the wasp lang, e.g. make it just a list of strings,
|
|
||||||
-- while here it would still be what it is or it would be parsed even more.
|
|
||||||
-- We could make this work by supporting manual definition of how Dependency evaluates / what is its type.
|
|
||||||
-- So, similar like we have IsDeclType instance for declarations, we could have similar abstraction for
|
|
||||||
-- sub-types like these, and we would inject it into Analyzer and it would be defined manually.
|
|
||||||
-- A way this might work: in Evaluator, we would check when the type matches the one we have this custom logic for,
|
|
||||||
-- probably in the function that figures out the Kind of the type, and then we would based on that use
|
|
||||||
-- evaluator provided for that type. This means we can provide this custom sort of logic per specific type from
|
|
||||||
-- AppSpec, but not for type + context in which it is. However, that should be enough, I don't think that is a problem.
|
|
||||||
-- If knowledge of context is important then it is best to just manually define the parent type's evaluation.
|
|
||||||
-- TODO: Make a github issue for this (TODO above)?
|
|
||||||
dependencies :: Maybe [Dependency]
|
dependencies :: Maybe [Dependency]
|
||||||
}
|
}
|
||||||
deriving (Show, Eq, Data)
|
deriving (Show, Eq, Data)
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module Analyzer.EvaluatorTest where
|
module Analyzer.EvaluatorTest where
|
||||||
|
|
||||||
import Data.Data (Data)
|
import Data.Data (Data)
|
||||||
|
import Data.List.Split (splitOn)
|
||||||
import Test.Tasty.Hspec
|
import Test.Tasty.Hspec
|
||||||
|
import Text.Read (readMaybe)
|
||||||
import Wasp.Analyzer.Evaluator
|
import Wasp.Analyzer.Evaluator
|
||||||
|
import qualified Wasp.Analyzer.Evaluator.Evaluation as E
|
||||||
|
import qualified Wasp.Analyzer.Evaluator.EvaluationError as EvaluationError
|
||||||
import Wasp.Analyzer.Parser (parse)
|
import Wasp.Analyzer.Parser (parse)
|
||||||
|
import qualified Wasp.Analyzer.Type as T
|
||||||
import Wasp.Analyzer.TypeChecker (typeCheck)
|
import Wasp.Analyzer.TypeChecker (typeCheck)
|
||||||
|
import qualified Wasp.Analyzer.TypeChecker.AST as TypedAST
|
||||||
import qualified Wasp.Analyzer.TypeDefinitions as TD
|
import qualified Wasp.Analyzer.TypeDefinitions as TD
|
||||||
|
import Wasp.Analyzer.TypeDefinitions.Class.HasCustomEvaluation (HasCustomEvaluation (..))
|
||||||
import Wasp.Analyzer.TypeDefinitions.TH
|
import Wasp.Analyzer.TypeDefinitions.TH
|
||||||
import Wasp.AppSpec.Core.Decl (IsDecl)
|
import Wasp.AppSpec.Core.Decl (IsDecl)
|
||||||
import Wasp.AppSpec.Core.Ref (Ref (..))
|
import Wasp.AppSpec.Core.Ref (Ref (..))
|
||||||
@ -21,18 +29,24 @@ fromRight :: Show a => Either a b -> b
|
|||||||
fromRight (Right x) = x
|
fromRight (Right x) = x
|
||||||
fromRight (Left e) = error $ show e
|
fromRight (Left e) = error $ show e
|
||||||
|
|
||||||
|
------- Simple -------
|
||||||
|
|
||||||
newtype Simple = Simple String deriving (Eq, Show, Data)
|
newtype Simple = Simple String deriving (Eq, Show, Data)
|
||||||
|
|
||||||
instance IsDecl Simple
|
instance IsDecl Simple
|
||||||
|
|
||||||
makeDeclType ''Simple
|
makeDeclType ''Simple
|
||||||
|
|
||||||
|
------- Fields -------
|
||||||
|
|
||||||
data Fields = Fields {a :: String, b :: Maybe Double} deriving (Eq, Show, Data)
|
data Fields = Fields {a :: String, b :: Maybe Double} deriving (Eq, Show, Data)
|
||||||
|
|
||||||
instance IsDecl Fields
|
instance IsDecl Fields
|
||||||
|
|
||||||
makeDeclType ''Fields
|
makeDeclType ''Fields
|
||||||
|
|
||||||
|
------ Business ------
|
||||||
|
|
||||||
data Person = Person {name :: String, age :: Integer} deriving (Eq, Show, Data)
|
data Person = Person {name :: String, age :: Integer} deriving (Eq, Show, Data)
|
||||||
|
|
||||||
instance IsDecl Person
|
instance IsDecl Person
|
||||||
@ -43,12 +57,6 @@ data BusinessType = Manufacturer | Seller | Store deriving (Eq, Show, Data)
|
|||||||
|
|
||||||
makeEnumType ''BusinessType
|
makeEnumType ''BusinessType
|
||||||
|
|
||||||
data Special = Special {imps :: [ExtImport], json :: JSON} deriving (Eq, Show)
|
|
||||||
|
|
||||||
instance IsDecl Special
|
|
||||||
|
|
||||||
makeDeclType ''Special
|
|
||||||
|
|
||||||
data Business = Business
|
data Business = Business
|
||||||
{ employees :: [Ref Person],
|
{ employees :: [Ref Person],
|
||||||
worth :: Double,
|
worth :: Double,
|
||||||
@ -61,6 +69,53 @@ instance IsDecl Business
|
|||||||
|
|
||||||
makeDeclType ''Business
|
makeDeclType ''Business
|
||||||
|
|
||||||
|
-------- Special --------
|
||||||
|
|
||||||
|
data Special = Special {imps :: [ExtImport], json :: JSON} deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance IsDecl Special
|
||||||
|
|
||||||
|
makeDeclType ''Special
|
||||||
|
|
||||||
|
------ HasCustomEvaluation ------
|
||||||
|
|
||||||
|
data SemanticVersion = SemanticVersion Int Int Int
|
||||||
|
deriving (Eq, Show, Data)
|
||||||
|
|
||||||
|
instance HasCustomEvaluation SemanticVersion where
|
||||||
|
waspType = T.StringType
|
||||||
|
evaluation = E.evaluation' $ \case
|
||||||
|
TypedAST.StringLiteral str -> case splitOn "." str of
|
||||||
|
[major, minor, patch] ->
|
||||||
|
maybe
|
||||||
|
( Left $
|
||||||
|
EvaluationError.ParseError $
|
||||||
|
EvaluationError.EvaluationParseError
|
||||||
|
"Failed parsing semantic version -> some part is not int"
|
||||||
|
)
|
||||||
|
pure
|
||||||
|
$ do
|
||||||
|
majorInt <- readMaybe @Int major
|
||||||
|
minorInt <- readMaybe @Int minor
|
||||||
|
patchInt <- readMaybe @Int patch
|
||||||
|
return $ SemanticVersion majorInt minorInt patchInt
|
||||||
|
_ ->
|
||||||
|
Left $
|
||||||
|
EvaluationError.ParseError $
|
||||||
|
EvaluationError.EvaluationParseError $
|
||||||
|
"Failed parsing semantic version -> it doesn't have 3 comma separated parts."
|
||||||
|
expr -> Left $ EvaluationError.ExpectedType T.StringType (TypedAST.exprType expr)
|
||||||
|
|
||||||
|
data Custom = Custom
|
||||||
|
{version :: SemanticVersion}
|
||||||
|
deriving (Eq, Show, Data)
|
||||||
|
|
||||||
|
instance IsDecl Custom
|
||||||
|
|
||||||
|
makeDeclType ''Custom
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
eval :: TD.TypeDefinitions -> [String] -> Either EvaluationError [Decl]
|
eval :: TD.TypeDefinitions -> [String] -> Either EvaluationError [Decl]
|
||||||
eval typeDefs source = evaluate typeDefs $ fromRight $ typeCheck typeDefs $ fromRight $ parse $ unlines source
|
eval typeDefs source = evaluate typeDefs $ fromRight $ typeCheck typeDefs $ fromRight $ parse $ unlines source
|
||||||
|
|
||||||
@ -112,6 +167,7 @@ spec_Evaluator = do
|
|||||||
" json: {=json \"key\": 1 json=}",
|
" json: {=json \"key\": 1 json=}",
|
||||||
"}"
|
"}"
|
||||||
]
|
]
|
||||||
|
|
||||||
fmap takeDecls (eval typeDefs source)
|
fmap takeDecls (eval typeDefs source)
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ ( "Test",
|
[ ( "Test",
|
||||||
@ -120,3 +176,9 @@ spec_Evaluator = do
|
|||||||
(JSON " \"key\": 1 ")
|
(JSON " \"key\": 1 ")
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
it "Evaluates a declaration with a field that has custom evaluation" $ do
|
||||||
|
let typeDefs = TD.addDeclType @Custom $ TD.empty
|
||||||
|
let decls = eval typeDefs ["custom Test { version: \"1.2.3\" }"]
|
||||||
|
fmap takeDecls decls
|
||||||
|
`shouldBe` Right [("Test", Custom {version = SemanticVersion 1 2 3})]
|
||||||
|
Loading…
Reference in New Issue
Block a user