keiro-dsl-0.8.0.0: src/Keiro/Dsl/Parser/Mapped.hs
{-# LANGUAGE ImportQualifiedPost #-}
-- | Consumer-owned mapped and nominal type syntax.
module Keiro.Dsl.Parser.Mapped
( pMappedTopItem,
pMappedTypeExpr,
pUsingNominalBinding,
)
where
import Data.Maybe (mapMaybe)
import Data.Text (Text)
import Keiro.Dsl.Frontend.Internal (FrontendContext, frontendSupportsFeature)
import Keiro.Dsl.Grammar
import Keiro.Dsl.LanguageVersion
import Keiro.Dsl.Parser.Core
import Keiro.Dsl.Syntax (SurfaceTopItem (..))
import Text.Megaparsec
-- Consumer-owned mapped and nominal types
--------------------------------------------------------------------------------
data MappedKind = MappedRecord | MappedEnum | MappedUnion
data MappedClause
= MCHaskell HaskellSource
| MCBinding Text
| MCBindingVersion Text
| MCCanonical Text
| MCFixtures Text
| MCInitial Text
| MCCodec Text
| MCCodecVersion Text
| MCShape MappedShape
pMappedTopItem :: FrontendContext -> P SurfaceTopItem
pMappedTopItem context = do
loc <- getLoc
keyword "mapped"
choice
[ if frontendSupportsFeature context NominalBindingSyntax
then SurfaceNominalScalar <$> pNominalScalarAfterMapped loc
else do
marker <- withOwnedSpan (try (keyword "nominal"))
requireLanguageFeatureAt context NominalBindingSyntax (spanOf marker)
fail "unreachable enabled nominal syntax in predecessor grammar",
SurfaceMapped <$> pMappedStructural context loc,
SurfaceMapped <$> pMappedOpaque loc
]
pMappedStructural :: FrontendContext -> Loc -> P MappedDecl
pMappedStructural context loc = do
keyword "structural"
kind <-
choice
[ MappedRecord <$ keyword "record",
MappedEnum <$ keyword "enum",
MappedUnion <$ keyword "union"
]
name <- ident
clauses <- braces (many (pStructuralClause context kind))
hs <- oneClause "haskell" (\case MCHaskell value -> Just value; _ -> Nothing) clauses
binding <- oneClause "binding" (\case MCBinding value -> Just value; _ -> Nothing) clauses
bindingVersion <- oneClause "binding-version" (\case MCBindingVersion value -> Just value; _ -> Nothing) clauses
canonical <- oneClause "canonical-type" (\case MCCanonical value -> Just value; _ -> Nothing) clauses
fixtures <- oneClause "fixtures" (\case MCFixtures value -> Just value; _ -> Nothing) clauses
initial <- oneClause "initial" (\case MCInitial value -> Just value; _ -> Nothing) clauses
shape <- requiredClause "wire" (\case MCShape value -> Just value; _ -> Nothing) clauses
pure
MappedStructural
{ msName = name,
msHaskell = hs,
msBinding = binding,
msBindingVersion = bindingVersion,
msCanonical = canonical,
msFixtures = fixtures,
msInitial = initial,
msShape = shape,
msLoc = loc
}
pMappedOpaque :: Loc -> P MappedDecl
pMappedOpaque loc = do
keyword "opaque"
name <- ident
clauses <- braces (many pOpaqueClause)
hs <- oneClause "haskell" (\case MCHaskell value -> Just value; _ -> Nothing) clauses
codec <- oneClause "codec" (\case MCCodec value -> Just value; _ -> Nothing) clauses
version <- oneClause "version" (\case MCCodecVersion value -> Just value; _ -> Nothing) clauses
fixtures <- oneClause "fixtures" (\case MCFixtures value -> Just value; _ -> Nothing) clauses
initial <- oneClause "initial" (\case MCInitial value -> Just value; _ -> Nothing) clauses
pure
MappedOpaque
{ moName = name,
moHaskell = hs,
moCodecId = codec,
moCodecVersion = version,
moFixtures = fixtures,
moInitial = initial,
moLoc = loc
}
pNominalScalarAfterMapped :: Loc -> P NominalScalarDecl
pNominalScalarAfterMapped loc = do
keyword "nominal"
name <- ident
_ <- symbol ":"
representation <- ident
binding <- pNominalBindingBlock loc
pure
NominalScalarDecl
{ nominalScalarName = name,
nominalScalarRepresentation = representation,
nominalScalarBinding = binding,
nominalScalarLoc = loc
}
pUsingNominalBinding :: P NominalBindingDecl
pUsingNominalBinding = do
keyword "using"
loc <- getLoc
pNominalBindingBlock loc
pNominalBindingBlock :: Loc -> P NominalBindingDecl
pNominalBindingBlock loc = do
clauses <- braces (many pNominalClause)
hs <- oneClause "haskell" (\case MCHaskell value -> Just value; _ -> Nothing) clauses
binding <- oneClause "binding" (\case MCBinding value -> Just value; _ -> Nothing) clauses
bindingVersion <- oneClause "binding-version" (\case MCBindingVersion value -> Just value; _ -> Nothing) clauses
canonical <- oneClause "canonical-type" (\case MCCanonical value -> Just value; _ -> Nothing) clauses
fixtures <- oneClause "fixtures" (\case MCFixtures value -> Just value; _ -> Nothing) clauses
initial <- oneClause "initial" (\case MCInitial value -> Just value; _ -> Nothing) clauses
pure
NominalBindingDecl
{ nominalHaskell = hs,
nominalBinding = binding,
nominalBindingVersion = bindingVersion,
nominalCanonicalType = canonical,
nominalFixtures = fixtures,
nominalInitial = initial,
nominalLoc = loc
}
pNominalClause :: P MappedClause
pNominalClause =
choice
[ MCHaskell <$> pHaskellSource,
MCBindingVersion <$> pQuotedFact "binding-version",
MCBinding <$> pQuotedFact "binding",
MCCanonical <$> pQuotedFact "canonical-type",
MCFixtures <$> pQuotedFact "fixtures",
MCInitial <$> pQuotedFact "initial"
]
pStructuralClause :: FrontendContext -> MappedKind -> P MappedClause
pStructuralClause context kind =
choice
[ MCHaskell <$> pHaskellSource,
MCBindingVersion <$> pQuotedFact "binding-version",
MCBinding <$> pQuotedFact "binding",
MCCanonical <$> pQuotedFact "canonical-type",
MCFixtures <$> pQuotedFact "fixtures",
MCInitial <$> pQuotedFact "initial",
MCShape <$> pMappedShape context kind
]
pOpaqueClause :: P MappedClause
pOpaqueClause =
choice
[ MCHaskell <$> pHaskellSource,
MCCodec <$> pQuotedFact "codec",
MCCodecVersion <$> pQuotedFact "version",
MCFixtures <$> pQuotedFact "fixtures",
MCInitial <$> pQuotedFact "initial"
]
pHaskellSource :: P HaskellSource
pHaskellSource = do
keyword "haskell"
keyword "package"
_ <- symbol "="
packageName <- wireWord
keyword "module"
_ <- symbol "="
moduleName <- pModulePrefix
keyword "type"
_ <- symbol "="
typeName <- ident
pure HaskellSource {hsPackage = packageName, hsModule = moduleName, hsType = typeName}
pQuotedFact :: Text -> P Text
pQuotedFact factName = keyword factName *> symbol "=" *> stringLit
pMappedShape :: FrontendContext -> MappedKind -> P MappedShape
pMappedShape context kind = do
keyword "wire"
case kind of
MappedRecord -> do
keyword "object"
keyword "constructor"
_ <- symbol "="
constructor <- ident
unknownFields <- pUnknownFieldsFact
fields <- braces (many (pWireField context))
pure (ShapeRecord constructor unknownFields fields)
MappedEnum -> do
keyword "string"
ShapeEnum <$> braces (many pWireEnum)
MappedUnion -> do
keyword "tagged-object"
keyword "tag"
_ <- symbol "="
tagField <- stringLit
keyword "contents"
_ <- symbol "="
contentsField <- stringLit
unknownFields <- pUnknownFieldsFact
arms <- braces (many (pWireArm context))
pure (ShapeUnion (TaggedObject tagField contentsField unknownFields) arms)
pUnknownFieldsFact :: P UnknownFields
pUnknownFieldsFact = do
keyword "unknown-fields"
_ <- symbol "="
choice [RejectUnknown <$ keyword "reject", IgnoreUnknown <$ keyword "ignore"]
pWireField :: FrontendContext -> P WireField
pWireField context = do
loc <- getLoc
haskellName <- ident
keyword "as"
wireKey <- stringLit
_ <- symbol ":"
fieldType <- pMappedTypeExpr context
presence <- choice [PRequired <$ keyword "required", POptional <$ keyword "optional"]
onMissing <- optional (keyword "on-missing" *> symbol "=" *> pOnMissing)
pure
WireField
{ wfHaskell = haskellName,
wfKey = wireKey,
wfType = fieldType,
wfPresence = presence,
wfOnMissing = onMissing,
wfLoc = loc
}
pWireEnum :: P WireEnum
pWireEnum = do
loc <- getLoc
constructor <- ident
keyword "as"
wireTag <- stringLit
pure WireEnum {weCtor = constructor, weTag = wireTag, weLoc = loc}
pWireArm :: FrontendContext -> P WireArm
pWireArm context = do
loc <- getLoc
constructor <- ident
keyword "as"
wireTag <- stringLit
payload <- optional (symbol ":" *> pMappedTypeExpr context)
pure WireArm {waCtor = constructor, waTag = wireTag, waPayload = payload, waLoc = loc}
pMappedTypeExpr :: FrontendContext -> P TypeExpr
pMappedTypeExpr context =
choice
[ TOptional <$> (keyword "Optional" *> pTypeArgument),
TList <$> (keyword "List" *> pTypeArgument),
TMap <$> (keyword "Map" *> pTypeArgument),
TText <$ keyword "Text",
TInt <$ keyword "Int",
TInteger <$ languageFeatureKeyword context IntegerScalarSyntax "Integer",
TBool <$ keyword "Bool",
TNatural <$ keyword "Natural",
TTime <$ (keyword "Time" <|> keyword "UTCTime"),
TJson <$ keyword "Json",
TRef <$> ident
]
where
pTypeArgument = parens (pMappedTypeExpr context) <|> pTypeAtom
pTypeAtom =
choice
[ TText <$ keyword "Text",
TInt <$ keyword "Int",
TInteger <$ languageFeatureKeyword context IntegerScalarSyntax "Integer",
TBool <$ keyword "Bool",
TNatural <$ keyword "Natural",
TTime <$ (keyword "Time" <|> keyword "UTCTime"),
TJson <$ keyword "Json",
TRef <$> ident
]
languageFeatureKeyword :: FrontendContext -> LanguageFeature -> Text -> P ()
languageFeatureKeyword context feature spelling = do
marker <- withOwnedSpan (keyword spelling)
requireLanguageFeatureAt context feature (spanOf marker)
pOnMissing :: P OnMissing
pOnMissing =
choice
[ OmNull <$ keyword "null",
OmEmptyList <$ (symbol "[" *> symbol "]"),
OmEmptyMap <$ (symbol "{" *> symbol "}"),
OmBool True <$ keyword "true",
OmBool False <$ keyword "false",
OmText <$> stringLit,
OmInt <$> integerLiteral,
OmCtor <$> ident
]
oneClause :: String -> (MappedClause -> Maybe a) -> [MappedClause] -> P (Maybe a)
oneClause clauseName select clauses =
case mapMaybe select clauses of
[] -> pure Nothing
[value] -> pure (Just value)
_ -> fail ("duplicate " <> clauseName <> " clause in mapped declaration")
requiredClause :: String -> (MappedClause -> Maybe a) -> [MappedClause] -> P a
requiredClause clauseName select clauses = do
found <- oneClause clauseName select clauses
maybe (fail ("missing " <> clauseName <> " clause in mapped structural declaration")) pure found
--------------------------------------------------------------------------------