keiro-dsl-0.7.0.0: src/Keiro/Dsl/Expression.hs
{-# OPTIONS_GHC -Werror=incomplete-patterns #-}
-- | The authoritative version-2 aggregate scalar-expression resolver.
--
-- The parser records syntax and exact rows. This module resolves every root,
-- required structural path, literal, and operator once, retaining the evidence
-- needed by generated Keiki lowering. It deliberately has no evaluator: Keiki's
-- structural term tree remains the one production meaning for concrete and
-- symbolic execution.
module Keiro.Dsl.Expression
( ExpressionEnvironment,
expressionEnvironment,
ExpectedScalarType (..),
ScalarRootProvenance (..),
ResolvedScalarProjection (..),
ArithmeticEvidence (..),
ScalarValue (..),
TypedScalarNode (..),
TypedScalarExpr (..),
ExpressionDiagnosticCode (..),
ExpressionDiagnostic (..),
resolveScalarExpr,
resolveGuardExpr,
resolveWriteExpr,
)
where
import Data.List (find)
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NE
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text qualified as T
import Data.Time.Clock (UTCTime)
import Data.Time.Format.ISO8601 (iso8601ParseM)
import Data.TypeID qualified as TypeID
import Keiro.Dsl.AggregateType
import Keiro.Dsl.Grammar
import Keiro.Dsl.TypeGraph
data ExpressionEnvironment = ExpressionEnvironment
{ environmentSpec :: !Spec,
environmentAggregate :: !Aggregate,
environmentTransition :: !Transition,
environmentSymbols :: !AggregateSymbols,
environmentTypeGraph :: !(Maybe TypeGraph)
}
expressionEnvironment :: Spec -> Aggregate -> Transition -> ExpressionEnvironment
expressionEnvironment spec aggregate transition =
ExpressionEnvironment
{ environmentSpec = spec,
environmentAggregate = aggregate,
environmentTransition = transition,
environmentSymbols = aggregateSymbols spec,
environmentTypeGraph = either (const Nothing) Just (resolveTypeGraph spec)
}
data ExpectedScalarType
= InferScalarType
| ExpectScalarType !ResolvedAggregateType
deriving stock (Eq, Show)
data ScalarRootProvenance
= ScalarRegisterRoot !Name !ResolvedAggregateType
| ScalarCommandRoot !Name !ResolvedAggregateType
deriving stock (Eq, Show)
data ResolvedScalarProjection = ResolvedScalarProjection
{ scalarProjectionOwner :: !MappedKey,
scalarProjectionPointer :: !Text,
scalarProjectionFields :: ![Name]
}
deriving stock (Eq, Show)
data ArithmeticEvidence
= ExactIntegerArithmetic
| TotalNaturalArithmetic
deriving stock (Eq, Ord, Show)
data ScalarValue
= ScalarTextValue !Text
| ScalarIntValue !Int
| ScalarIntegerValue !Integer
| ScalarNaturalValue !Integer
| ScalarBoolValue !Bool
| ScalarTimeValue !UTCTime
| ScalarEnumValue !Name !Name
| ScalarIdValue !Name !Text
deriving stock (Eq, Show)
data TypedScalarNode
= TypedLiteral !ScalarValue
| TypedRoot !ScalarRootProvenance
| TypedProject !ScalarRootProvenance !ResolvedScalarProjection
| TypedAdd !ArithmeticEvidence !TypedScalarExpr !TypedScalarExpr
| TypedSubtract !ArithmeticEvidence !TypedScalarExpr !TypedScalarExpr
| TypedMultiply !ArithmeticEvidence !TypedScalarExpr !TypedScalarExpr
| TypedEqual !TypedScalarExpr !TypedScalarExpr
| TypedNotEqual !TypedScalarExpr !TypedScalarExpr
| TypedCompare !CmpOp !TypedScalarExpr !TypedScalarExpr
| TypedAnd !TypedScalarExpr !TypedScalarExpr
| TypedOr !TypedScalarExpr !TypedScalarExpr
deriving stock (Eq, Show)
data TypedScalarExpr = TypedScalarExpr
{ typedScalarType :: !ResolvedAggregateType,
typedScalarLoc :: !Loc,
typedScalarNode :: !TypedScalarNode
}
deriving stock (Eq, Show)
data ExpressionDiagnosticCode
= ScalarRootUnknown
| ScalarRootAmbiguous
| ScalarPathInvalid
| ScalarPathUnsupported
| ScalarLiteralNeedsType
| ScalarLiteralInvalid
| ScalarOperandTypeMismatch
| ScalarOperatorUnsupported
| ScalarBooleanOperandRequired
| ScalarGuardBoolRequired
| ScalarWriteTargetUnknown
| ScalarWriteTypeMismatch
deriving stock (Eq, Ord, Show)
data ExpressionDiagnostic = ExpressionDiagnostic
{ expressionDiagnosticLoc :: !Loc,
expressionDiagnosticCode :: !ExpressionDiagnosticCode,
expressionDiagnosticMessage :: !Text
}
deriving stock (Eq, Show)
resolveScalarExpr :: ExpressionEnvironment -> ExpectedScalarType -> Expr -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveScalarExpr environment expected expression =
checkExpected expected =<< resolve expression expected
where
resolve syntax wanted = case syntax of
EOr left right -> resolveBoolean TypedOr left right
EAnd left right -> resolveBoolean TypedAnd left right
ECmp operator left right -> resolveComparison operator left right
EAdd loc left right -> resolveArithmetic loc TypedAdd left right wanted
ESubtract loc left right -> resolveArithmetic loc TypedSubtract left right wanted
EMultiply loc left right -> resolveArithmetic loc TypedMultiply left right wanted
EPath loc root path -> resolvePath environment loc root path
ELiteral loc literal -> resolveLiteral environment loc wanted literal
EAtom (ABool value) -> pure (literalExpr noLoc AggregateBool (ScalarBoolValue value))
EAtom (AName name) -> resolvePath environment noLoc UnqualifiedRoot [name]
resolveBoolean constructor left right = do
(resolvedLeft, resolvedRight) <- resolveBoth (resolve left (ExpectScalarType AggregateBool)) (resolve right (ExpectScalarType AggregateBool))
pure
TypedScalarExpr
{ typedScalarType = AggregateBool,
typedScalarLoc = expressionLoc left right,
typedScalarNode = constructor resolvedLeft resolvedRight
}
resolveComparison operator left right = do
(resolvedLeft, resolvedRight) <- resolvePair left right
requireSameType resolvedLeft resolvedRight
requireComparisonCapability operator resolvedLeft
pure
TypedScalarExpr
{ typedScalarType = AggregateBool,
typedScalarLoc = expressionLoc left right,
typedScalarNode = case operator of
OpEq -> TypedEqual resolvedLeft resolvedRight
OpNeq -> TypedNotEqual resolvedLeft resolvedRight
OpLt -> TypedCompare OpLt resolvedLeft resolvedRight
OpLe -> TypedCompare OpLe resolvedLeft resolvedRight
OpGt -> TypedCompare OpGt resolvedLeft resolvedRight
OpGe -> TypedCompare OpGe resolvedLeft resolvedRight
}
resolveArithmetic loc constructor left right wanted = do
pair <- case wanted of
ExpectScalarType scalarType
| scalarType `elem` [AggregateInteger, AggregateNatural] ->
resolveBoth (resolve left wanted) (resolve right wanted)
_ -> resolvePair left right
let (resolvedLeft, resolvedRight) = pair
requireSameType resolvedLeft resolvedRight
evidence <- arithmeticEvidence loc (typedScalarType resolvedLeft)
pure
TypedScalarExpr
{ typedScalarType = typedScalarType resolvedLeft,
typedScalarLoc = loc,
typedScalarNode = constructor evidence resolvedLeft resolvedRight
}
resolvePair left right
| contextualLiteral left && not (contextualLiteral right) = do
resolvedRight <- resolve right InferScalarType
resolvedLeft <- resolve left (ExpectScalarType (typedScalarType resolvedRight))
pure (resolvedLeft, resolvedRight)
| contextualLiteral right && not (contextualLiteral left) = do
resolvedLeft <- resolve left InferScalarType
resolvedRight <- resolve right (ExpectScalarType (typedScalarType resolvedLeft))
pure (resolvedLeft, resolvedRight)
| otherwise = case (resolve left InferScalarType, resolve right InferScalarType) of
(Right resolvedLeft, Right resolvedRight) -> Right (resolvedLeft, resolvedRight)
(Left _, Right resolvedRight) -> do
resolvedLeft <- resolve left (ExpectScalarType (typedScalarType resolvedRight))
pure (resolvedLeft, resolvedRight)
(Right resolvedLeft, Left _) -> do
resolvedRight <- resolve right (ExpectScalarType (typedScalarType resolvedLeft))
pure (resolvedLeft, resolvedRight)
(Left leftErrors, Left rightErrors) -> Left (leftErrors <> rightErrors)
resolveBoth left right = case (left, right) of
(Right resolvedLeft, Right resolvedRight) -> Right (resolvedLeft, resolvedRight)
(Left leftErrors, Left rightErrors) -> Left (leftErrors <> rightErrors)
(Left errors, _) -> Left errors
(_, Left errors) -> Left errors
requireSameType left right
| typedScalarType left == typedScalarType right = Right ()
| otherwise =
failure
(typedScalarLoc right)
ScalarOperandTypeMismatch
( "expression operands have different scalar types '"
<> aggregateCanonicalName (typedScalarType left)
<> "' and '"
<> aggregateCanonicalName (typedScalarType right)
<> "'; numeric coercion is not supported"
)
requireComparisonCapability operator operand =
let useSite = case operator of
OpEq -> EqualityGuardUse
OpNeq -> EqualityGuardUse
OpLt -> OrderingGuardUse
OpLe -> OrderingGuardUse
OpGt -> OrderingGuardUse
OpGe -> OrderingGuardUse
in case aggregateCapability useSite (typedScalarType operand) of
SolverVisible -> Right ()
OpaqueOnly -> unsupported useSite
Unsupported -> unsupported useSite
where
unsupported useSite =
failure
(typedScalarLoc operand)
ScalarOperatorUnsupported
( renderUseSite useSite
<> " is unsupported for scalar type '"
<> aggregateCanonicalName (typedScalarType operand)
<> "'"
)
checkExpected InferScalarType resolved = Right resolved
checkExpected (ExpectScalarType wanted) resolved
| wanted == typedScalarType resolved = Right resolved
| wanted == AggregateBool =
failure
(typedScalarLoc resolved)
ScalarBooleanOperandRequired
("Boolean expression requires Bool, found '" <> aggregateCanonicalName (typedScalarType resolved) <> "'")
| otherwise =
failure
(typedScalarLoc resolved)
ScalarOperandTypeMismatch
( "expected scalar type '"
<> aggregateCanonicalName wanted
<> "', found '"
<> aggregateCanonicalName (typedScalarType resolved)
<> "'"
)
resolveGuardExpr :: ExpressionEnvironment -> Expr -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveGuardExpr environment expression =
case resolveScalarExpr environment (ExpectScalarType AggregateBool) expression of
Left diagnostics
| all ((== ScalarBooleanOperandRequired) . expressionDiagnosticCode) (NE.toList diagnostics) ->
Left
( fmap
( \diagnostic ->
diagnostic
{ expressionDiagnosticCode = ScalarGuardBoolRequired,
expressionDiagnosticMessage = "aggregate guard must resolve to Bool; " <> expressionDiagnosticMessage diagnostic
}
)
diagnostics
)
other -> other
resolveWriteExpr :: ExpressionEnvironment -> Name -> Expr -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveWriteExpr environment registerName expression =
case find ((== registerName) . regName) (aggRegs (environmentAggregate environment)) of
Nothing ->
failure
(exprLoc expression)
ScalarWriteTargetUnknown
("write target '" <> registerName <> "' is not an aggregate register")
Just register -> case resolveAggregateType (environmentSymbols environment) (regLoc register) RegisterUse (regType register) of
Left _ -> Right unresolvedSentinel
Right expected -> case resolveScalarExpr environment (ExpectScalarType expected) expression of
Left diagnostics -> Left (fmap writeDiagnostic diagnostics)
Right resolved
| predicateValued resolved ->
failure
(typedScalarLoc resolved)
ScalarOperatorUnsupported
"comparison and Boolean operators are guard predicates and cannot be written as scalar terms"
| otherwise -> Right resolved
where
writeDiagnostic diagnostic
| expressionDiagnosticCode diagnostic == ScalarOperandTypeMismatch =
diagnostic
{ expressionDiagnosticCode = ScalarWriteTypeMismatch,
expressionDiagnosticMessage =
"write to register '"
<> registerName
<> "' has the wrong scalar type; "
<> expressionDiagnosticMessage diagnostic
}
| otherwise = diagnostic
where
-- The aggregate type validator reports the primary type error first. This
-- value is never scaffolded because any error prevents generation.
unresolvedSentinel = TypedScalarExpr AggregateBool noLoc (TypedLiteral (ScalarBoolValue False))
resolvePath :: ExpressionEnvironment -> Loc -> ExprRoot -> [Name] -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolvePath environment loc root path = case path of
[] -> failure loc ScalarRootUnknown "scalar path is empty"
rootName : fields -> do
provenance <- resolveRoot environment loc root rootName
case fields of
[] -> pure (rootExpr loc provenance)
_ -> resolveProjectionPath environment loc provenance fields
resolveRoot :: ExpressionEnvironment -> Loc -> ExprRoot -> Name -> Either (NonEmpty ExpressionDiagnostic) ScalarRootProvenance
resolveRoot environment loc root name = case root of
RegisterRoot -> maybe unknown (Right . registerRoot) register
CommandRoot -> maybe unknown (Right . commandRoot) commandField
UnqualifiedRoot -> case (register, commandField) of
(Just _, Just _) ->
failure
loc
ScalarRootAmbiguous
("unqualified scalar root '" <> name <> "' matches both a register and command field; use reg." <> name <> " or cmd." <> name)
(Just value, Nothing) -> Right (registerRoot value)
(Nothing, Just value) -> Right (commandRoot value)
(Nothing, Nothing) -> unknown
where
aggregate = environmentAggregate environment
transition = environmentTransition environment
symbols = environmentSymbols environment
register = find ((== name) . regName) (aggRegs aggregate)
commandField = do
command <- find ((== tCommand transition) . cmdName) (aggCommands aggregate)
find ((== name) . aggregateFieldName) (cmdFields command)
registerRoot value =
ScalarRegisterRoot name (resolvedOrUnknown (resolveAggregateType symbols (regLoc value) RegisterUse (regType value)))
commandRoot value =
ScalarCommandRoot name (resolvedOrUnknown (inferAggregateFieldType symbols aggregate CommandFieldUse value))
resolvedOrUnknown = either (const (AggregateMapped (MappedKey "<invalid>"))) id
unknown =
failure
loc
ScalarRootUnknown
( "scalar root '"
<> name
<> "' resolves to no register or field of command '"
<> tCommand transition
<> "'; qualify enum values as Type.Constructor"
)
resolveProjectionPath :: ExpressionEnvironment -> Loc -> ScalarRootProvenance -> [Name] -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveProjectionPath environment loc provenance fields = do
owner <- case rootType provenance of
AggregateMapped key -> Right key
other ->
failure
loc
ScalarPathInvalid
("cannot project fields through scalar type '" <> aggregateCanonicalName other <> "'")
graph <- maybe (failure loc ScalarPathUnsupported "mapped structural graph is unavailable") Right (environmentTypeGraph environment)
(resolvedType, wireKeys) <- walk graph owner fields
if scalarLeaf resolvedType
then
pure
TypedScalarExpr
{ typedScalarType = resolvedType,
typedScalarLoc = loc,
typedScalarNode =
TypedProject
provenance
ResolvedScalarProjection
{ scalarProjectionOwner = owner,
scalarProjectionPointer = T.concat ["/" <> escapePointer key | key <- wireKeys],
scalarProjectionFields = fields
}
}
else
failure
loc
ScalarPathUnsupported
("path ends at unsupported non-scalar type '" <> aggregateCanonicalName resolvedType <> "'")
where
walk graph ownerKey remaining = case Map.lookup ownerKey (tgDeclarations graph) of
Just (ResolvedStructural _ (RRecord _ _ recordFields)) -> selectField graph recordFields remaining
Just ResolvedStructural {} -> failure loc ScalarPathUnsupported "scalar paths may cross required structural records only"
Just ResolvedOpaque {} -> failure loc ScalarPathUnsupported "scalar paths cannot cross an opaque mapped declaration"
Nothing -> failure loc ScalarPathInvalid ("unknown mapped path owner '" <> unMappedKey ownerKey <> "'")
selectField _ _ [] = failure loc ScalarPathInvalid "scalar path is empty"
selectField graph recordFields (fieldName : rest) = case find ((== fieldName) . rwfHaskell) recordFields of
Nothing -> failure loc ScalarPathInvalid ("required structural field '" <> fieldName <> "' does not exist")
Just field
| rwfPresence field /= PRequired -> failure loc ScalarPathUnsupported ("field '" <> fieldName <> "' is optional; scalar paths must be total")
| null rest -> (,[rwfKey field]) <$> resolvedLeaf (rwfType field)
| RRef nextOwner <- rwfType field -> do
(leafType, keys) <- walk graph nextOwner rest
pure (leafType, rwfKey field : keys)
| otherwise -> failure loc ScalarPathUnsupported ("field '" <> fieldName <> "' is not a required structural record")
resolvedLeaf = \case
RText -> Right AggregateText
RInt -> Right AggregateInt
RInteger -> Right AggregateInteger
RBool -> Right AggregateBool
RNatural -> Right AggregateNatural
RTime -> Right AggregateTime
RJson -> unsupported "Json"
ROptional {} -> unsupported "Optional"
RList {} -> unsupported "List"
RMap {} -> unsupported "Map"
RRef key -> Right (AggregateMapped key)
unsupported name = failure loc ScalarPathUnsupported (name <> " is not a supported scalar path leaf")
resolveLiteral :: ExpressionEnvironment -> Loc -> ExpectedScalarType -> ScalarLiteral -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveLiteral environment loc expected literal = case literal of
LiteralBool value -> pure (literalExpr loc AggregateBool (ScalarBoolValue value))
LiteralText value -> case expected of
ExpectScalarType AggregateTime -> case iso8601ParseM (T.unpack value) of
Just parsed -> pure (literalExpr loc AggregateTime (ScalarTimeValue parsed))
Nothing -> invalid "Time literal must be a valid ISO-8601 UTC timestamp"
ExpectScalarType AggregateText -> pure (literalExpr loc AggregateText (ScalarTextValue value))
InferScalarType -> pure (literalExpr loc AggregateText (ScalarTextValue value))
ExpectScalarType other -> mismatch other "quoted Text/Time"
LiteralIntegral value -> case expected of
ExpectScalarType AggregateInt
| value >= fromIntegral (minBound :: Int) && value <= fromIntegral (maxBound :: Int) ->
pure (literalExpr loc AggregateInt (ScalarIntValue (fromInteger value)))
| otherwise -> invalid "Int literal is outside the Haskell Int range"
ExpectScalarType AggregateInteger -> pure (literalExpr loc AggregateInteger (ScalarIntegerValue value))
ExpectScalarType AggregateNatural
| value >= 0 -> pure (literalExpr loc AggregateNatural (ScalarNaturalValue value))
| otherwise -> invalid "Natural literal must be non-negative"
InferScalarType -> failure loc ScalarLiteralNeedsType "integral literal needs an expected Int, Integer, or Natural type"
ExpectScalarType other -> mismatch other "integral"
LiteralQualified typeName constructor -> resolveEnumLiteral environment loc expected typeName constructor
LiteralId typeName value -> resolveIdLiteral environment loc expected typeName value
where
invalid message = failure loc ScalarLiteralInvalid message
mismatch other syntax =
failure
loc
ScalarOperandTypeMismatch
(syntax <> " literal cannot inhabit scalar type '" <> aggregateCanonicalName other <> "'")
resolveEnumLiteral :: ExpressionEnvironment -> Loc -> ExpectedScalarType -> Name -> Name -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveEnumLiteral environment loc expected typeName constructor = case find ((== typeName) . enumName) (specEnums (environmentSpec environment)) of
Nothing -> failure loc ScalarLiteralInvalid ("unknown enum literal type '" <> typeName <> "'")
Just declaration
| constructor `notElem` map fst (enumCtors declaration) ->
failure loc ScalarLiteralInvalid ("enum '" <> typeName <> "' has no constructor '" <> constructor <> "'")
| otherwise -> do
resolved <- resolveDeclared typeName
requireExpected resolved
pure (literalExpr loc resolved (ScalarEnumValue typeName constructor))
where
resolveDeclared name = case resolveAggregateType (environmentSymbols environment) loc WholeValueWriteUse (TRef name) of
Left _ -> failure loc ScalarLiteralInvalid ("enum type '" <> name <> "' is not available at this aggregate use")
Right resolved -> Right resolved
requireExpected resolved = case expected of
InferScalarType -> Right ()
ExpectScalarType wanted
| wanted == resolved -> Right ()
| otherwise -> failure loc ScalarOperandTypeMismatch ("enum literal has type '" <> aggregateCanonicalName resolved <> "', expected '" <> aggregateCanonicalName wanted <> "'")
resolveIdLiteral :: ExpressionEnvironment -> Loc -> ExpectedScalarType -> Name -> Text -> Either (NonEmpty ExpressionDiagnostic) TypedScalarExpr
resolveIdLiteral environment loc expected typeName value = case find ((== typeName) . idName) (specIds (environmentSpec environment)) of
Nothing -> failure loc ScalarLiteralInvalid ("unknown ID literal type '" <> typeName <> "'")
Just declaration -> case TypeID.parseText value of
Left parseError -> failure loc ScalarLiteralInvalid ("invalid " <> typeName <> " literal: " <> T.pack (show parseError))
Right parsed
| TypeID.getPrefix parsed /= idPrefix declaration ->
failure loc ScalarLiteralInvalid ("ID literal prefix must be '" <> idPrefix declaration <> "'")
| otherwise -> do
resolved <- case resolveAggregateType (environmentSymbols environment) loc WholeValueWriteUse (TRef typeName) of
Left _ -> failure loc ScalarLiteralInvalid ("ID type '" <> typeName <> "' is not available at this aggregate use")
Right valueType -> Right valueType
case expected of
InferScalarType -> pure ()
ExpectScalarType wanted
| wanted == resolved -> pure ()
| otherwise -> failure loc ScalarOperandTypeMismatch ("ID literal has type '" <> aggregateCanonicalName resolved <> "', expected '" <> aggregateCanonicalName wanted <> "'")
pure (literalExpr loc resolved (ScalarIdValue typeName value))
arithmeticEvidence :: Loc -> ResolvedAggregateType -> Either (NonEmpty ExpressionDiagnostic) ArithmeticEvidence
arithmeticEvidence _ AggregateInteger = Right ExactIntegerArithmetic
arithmeticEvidence _ AggregateNatural = Right TotalNaturalArithmetic
arithmeticEvidence loc scalarType =
failure
loc
ScalarOperatorUnsupported
( "arithmetic is unsupported for scalar type '"
<> aggregateCanonicalName scalarType
<> "'; only exact Integer and total Natural arithmetic are admitted"
)
rootType :: ScalarRootProvenance -> ResolvedAggregateType
rootType (ScalarRegisterRoot _ scalarType) = scalarType
rootType (ScalarCommandRoot _ scalarType) = scalarType
rootExpr :: Loc -> ScalarRootProvenance -> TypedScalarExpr
rootExpr loc provenance = TypedScalarExpr (rootType provenance) loc (TypedRoot provenance)
literalExpr :: Loc -> ResolvedAggregateType -> ScalarValue -> TypedScalarExpr
literalExpr loc scalarType value = TypedScalarExpr scalarType loc (TypedLiteral value)
scalarLeaf :: ResolvedAggregateType -> Bool
scalarLeaf = \case
AggregateText -> True
AggregateInt -> True
AggregateInteger -> True
AggregateBool -> True
AggregateTime -> True
AggregateNatural -> True
AggregateNominal {} -> True
AggregateVertex {} -> False
AggregateMapped {} -> False
predicateValued :: TypedScalarExpr -> Bool
predicateValued expression = case typedScalarNode expression of
TypedEqual {} -> True
TypedNotEqual {} -> True
TypedCompare {} -> True
TypedAnd {} -> True
TypedOr {} -> True
TypedLiteral {} -> False
TypedRoot {} -> False
TypedProject {} -> False
TypedAdd {} -> False
TypedSubtract {} -> False
TypedMultiply {} -> False
contextualLiteral :: Expr -> Bool
contextualLiteral = \case
ELiteral _ LiteralIntegral {} -> True
ELiteral _ LiteralText {} -> True
_ -> False
expressionLoc :: Expr -> Expr -> Loc
expressionLoc left right = case exprLoc left of
Loc 0 -> exprLoc right
loc -> loc
escapePointer :: Text -> Text
escapePointer = T.replace "/" "~1" . T.replace "~" "~0"
renderUseSite :: AggregateUseSite -> Text
renderUseSite = \case
EqualityGuardUse -> "equality"
OrderingGuardUse -> "ordering"
CommandFieldUse -> "command field"
EventFieldUse -> "event field"
RegisterUse -> "register"
WholeValueWriteUse -> "whole-value write"
CodecUse -> "JSON codec"
SnapshotUse -> "snapshot"
HarnessSampleUse -> "harness sample"
HaskellLoweringUse -> "Haskell lowering"
failure :: Loc -> ExpressionDiagnosticCode -> Text -> Either (NonEmpty ExpressionDiagnostic) a
failure loc diagnosticCode diagnosticMessage =
Left
( ExpressionDiagnostic
{ expressionDiagnosticLoc = loc,
expressionDiagnosticCode = diagnosticCode,
expressionDiagnosticMessage = diagnosticMessage
}
:| []
)