glean-0.1.0.0: glean/hs/Glean/Schema/Resolve.hs
{-
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
-}
{-# LANGUAGE DeriveGeneric #-}
module Glean.Schema.Resolve
( parseAndResolveSchema
, parseAndResolveSchemaCached
, SchemaParserCache
, resolveType
, resolveSchema
, runResolve
, resolveQuery
, resolveSchemaRefs
) where
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Except
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import Data.Char
import Data.Graph
import Data.Foldable
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import Data.HashSet (HashSet)
import Data.List
import Data.Maybe
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as Text
import Compat.Prettyprinter hiding (group)
import TextShow
import Glean.Angle.Hash
import Glean.Angle.Parser
import Glean.Angle.Types
import Glean.Schema.Types
import Glean.Schema.Util
import Glean.Schema.Evolve (validateResolvedEvolutions)
type SchemaParserCache = HashMap Hash SourceSchemas
-- ---------------------------------------------------------------------------
-- High-level schema parsing / resolution APIs
--
-- | Useful packaging of 'parseSchema' and 'resolveSchema'. Note that
-- parsing and resolution of a schema is a pure function.
--
parseAndResolveSchema
:: ByteString
-> Either String (SourceSchemas, ResolvedSchemas)
parseAndResolveSchema str =
case parseSchema str of
Left str -> Left str
Right ss -> case resolveSchema ss of
Left txt -> Left (Text.unpack txt)
Right r -> Right (ss, r)
--
-- | Like parseAndResolveSchema but uses a cache to avoid repeatedly
-- parsing the same schema fragments.
--
-- We split the input string at "schema" declarations and hash the
-- content of each one to use as the cache key. There are a couple of
-- caveats here:
--
-- * we lose the benefit of the FILE annotations that tell us the
-- original source file name for error messages. Therefore don't
-- use this parsing method when you want error messages; use
-- the uncached parseAndResolveSchema instead.
--
-- * "schema" should appear in column 0. But if it doesn't, the worst
-- that can happen is we miss out on some caching. Since "schema" is
-- a keyword, it can't appear inside a schema or anything silly like
-- that.
--
parseAndResolveSchemaCached
:: SchemaParserCache
-> ByteString
-> Either String (SourceSchemas, ResolvedSchemas, SchemaParserCache)
parseAndResolveSchemaCached cache str = do
let
(ver, rest) = stripAngleVersion str
cutSchemas b =
(h <> "\n") : -- put the "\n" back on the end
if ByteString.null t
then []
else cutSchemas (ByteString.drop 1 t) -- drop the "\n"
where (h,t) = ByteString.breakSubstring "\nschema" b
schemaFragments =
[ (hashByteString frag, frag)
| frag <- cutSchemas rest ]
parsed <- forM schemaFragments $ \(key, str) ->
case HashMap.lookup key cache of
Nothing -> case parseSchemaWithVersion ver str of
Left err -> Left err
Right ss -> return (key, ss)
Just cached -> return (key, cached)
let
merged = SourceSchemas
{ srcAngleVersion = ver
, srcSchemas = schemas
, srcEvolves = evolves }
where
schemas = concatMap (srcSchemas . snd) parsed
evolves = concatMap (srcEvolves . snd) parsed
newCache = HashMap.union cache (HashMap.fromList parsed)
case resolveSchema merged of
Left txt -> Left (Text.unpack txt)
Right r -> Right (merged, r, newCache)
--
-- | Turn 'SourceSchemas' into a 'ResolvedSchemas' by resolving all the
-- references and checking for validity.
--
resolveSchema :: SourceSchemas -> Either Text ResolvedSchemas
resolveSchema schemas = runExcept $ do
checkAngleVersion (srcAngleVersion schemas)
SourceSchemas{..} <- resolveSchemaRefs schemas
let
-- dependency analysis: we want to process schemas in dependency
-- order, and detect cycles in evolves declarations.
sccs = stronglyConnComp
[ (schema, schemaName schema, out schema)
| schema <- srcSchemas ]
out s = schemaDependencies s <> evolvesOf (schemaName s)
schemaDependencies SourceSchema{..} =
schemaInherits ++ [ name | SourceImport name _ <- schemaDecls ]
evolves = HashMap.fromListWith (++)
[ (new, [old])
| SourceEvolves _ new old <- srcEvolves
]
evolvesOf name = HashMap.lookupDefault [] name evolves
resolveSchemas env [] = return env
resolveSchemas env (AcyclicSCC one : rest) = do
let schemaEvolves = HashMap.lookupDefault [] (schemaName one) evolves
resolved <- resolveOneSchema env srcAngleVersion
schemaEvolves one
resolveSchemas (HashMap.insert (schemaName one) resolved env) rest
resolveSchemas _ (CyclicSCC some : _) = throwError $
"cycle in schema definitions between: " <>
Text.intercalate ", " (map (showRef . schemaName) some)
-- Resolve all the references in each individual schema
finalEnv <- resolveSchemas HashMap.empty sccs
let
resolved =
[ schema
| AcyclicSCC one <- sccs
, Just schema <- [HashMap.lookup (schemaName one) finalEnv ] ]
allSchemas =
[ schema
| schema@ResolvedSchema{..} <- HashMap.elems finalEnv
, resolvedSchemaName == "all"
]
when (srcAngleVersion >= AngleVersion 6) $
liftEither $ validateResolvedEvolutions resolved
return ResolvedSchemas
{ schemasHighestVersion =
if null allSchemas
then Nothing
else Just (maximum $ map resolvedSchemaVersion allSchemas)
, schemasResolved = resolved
}
resolveSchemaRefs :: SourceSchemas -> Except Text SourceSchemas
resolveSchemaRefs SourceSchemas{..} = do
let
unqualMap
| srcAngleVersion >= AngleVersion 10 =
HashMap.fromListWith (<>)
[ (SourceRef name Nothing, HashSet.singleton ref)
| SourceSchema{..} <- srcSchemas
, let ref@(SourceRef name _) = schemaName
]
| otherwise = HashMap.empty
refMap = unqualMap <> HashMap.fromList
[ (schemaName, HashSet.singleton schemaName)
| SourceSchema{..} <- srcSchemas
]
schemaByName ref =
case HashMap.lookup ref refMap of
Nothing -> unknown
Just s -> case HashSet.toList s of
[one] -> return one
names -> throwError $
"ambiguous schema " <> showRef ref <>
", could be one of " <> Text.intercalate ", " (map showRef names)
where
unknown = throwError $ "unknown schema: " <> showRef ref
schemaByNameWithNamespace parentSchema ref =
let message e = e <> " within schema: " <> showRef parentSchema
in withExcept message (schemaByName ref)
resolveDecl parentSchema (SourceImport r s) =
SourceImport <$> schemaByNameWithNamespace parentSchema r <*> pure s
resolveDecl _ decl = return decl
resolveEvolve (SourceEvolves l n o) =
SourceEvolves l <$> schemaByName n <*> schemaByName o
resolveSchema SourceSchema{..} = do
inherits <- mapM (schemaByNameWithNamespace schemaName) schemaInherits
decls <- mapM (resolveDecl schemaName) schemaDecls
return SourceSchema
{ schemaName = schemaName
, schemaInherits = inherits
, schemaDecls = decls
, schemaSrcSpan = schemaSrcSpan
}
schemas <- traverse resolveSchema srcSchemas
evolves <- traverse resolveEvolve srcEvolves
return SourceSchemas
{ srcAngleVersion = srcAngleVersion
, srcSchemas = schemas
, srcEvolves = evolves
}
type Environment = HashMap SourceRef ResolvedSchemaRef
resolveOneSchema
:: Environment
-> AngleVersion
-> [SourceRef]
-> SourceSchema
-> Except Text ResolvedSchemaRef
resolveOneSchema env angleVersion evolves SourceSchema{..} =
let inSchema e = throwError $ "In " <> showRef schemaName <> ":\n " <> e in
flip catchError inSchema $ do
let
SourceRef namespace maybeVer = schemaName
schemaByName name = case HashMap.lookup name env of
Nothing -> throwError $ "unknown schema: " <> showRef name
Just one -> return one
checkNameSpace namespace
-- Version of this schema
version <- case maybeVer of
Nothing -> throwError $ "missing version: " <> showRef schemaName
Just v -> return v
-- All the schemas we're inheriting from
inherits <- traverse schemaByName schemaInherits
-- All the schemas we imported
imports <- traverse schemaByName [ name | SourceImport name _ <- schemaDecls ]
let
qualify :: Name -> Name
qualify x = namespace <> "." <> x
qualifyNameEnv :: NameEnv t -> NameEnv t
qualifyNameEnv env = HashMap.fromList
[ (SourceRef (qualify name) ver, target)
| (SourceRef name ver, target) <- HashMap.toList env ]
localPreds =
[ let
SourceRef name explicitVersion = predicateDefRef p
thisVersion = fromMaybe version explicitVersion
qname = qualify name
in
(name, PredicateRef qname thisVersion, p)
| SourcePredicate p <- schemaDecls
]
localTypes =
[ let
SourceRef name explicitVersion = typeDefRef p
thisVersion = fromMaybe version explicitVersion
qname = qualify name
in
(name, TypeRef qname thisVersion, p)
| SourceType p <- schemaDecls
]
-- Check for multiple definitions of the same name/version.
-- Multiple definitions of the same name is OK: an unqualified
-- reference will be rejected as ambiguous, but can be resolved by
-- using an explicit version.
let
numRefs = HashMap.fromListWith (+) $
[ ((predicateRef_name, predicateRef_version), 1::Int)
| (_, PredicateRef{..}, _) <- localPreds ] ++
[ ((typeRef_name, typeRef_version), 1)
| (_, TypeRef{..}, _) <- localTypes]
forM_ (HashMap.toList numRefs) $ \((name,ver), num) -> do
when (num > 1) $ throwError $
"multiple definitions for: " <> name <> "." <> showt ver
-- Build the scope: a mapping from unversioned names to RefTarget
let
-- inherited definitions are in scope unqualified and qualified
-- (but unqualified local names override unqualified inherited names)
qualInheritedScope :: NameEnv RefResolved
qualInheritedScope =
unionNameEnvs $ map resolvedSchemaQualScope inherits
unqualInheritedScope :: NameEnv RefResolved
unqualInheritedScope =
unionNameEnvs $ map resolvedSchemaUnqualScope inherits
-- imported names are in scope qualified only
importedScope :: NameEnv RefResolved
importedScope = unionNameEnvs $ map resolvedSchemaQualScope imports
unionNameEnvs :: [NameEnv RefResolved] -> NameEnv RefResolved
unionNameEnvs = foldl' (HashMap.unionWith Set.union) HashMap.empty
nameEntries name ver target =
[ (SourceRef name Nothing, set),
(SourceRef name (Just ver), set) ]
where set = Set.singleton target
-- local definitions are in scope unqualified and qualified
unqualLocalScope :: NameEnv RefResolved
unqualLocalScope = HashMap.fromListWith Set.union unqualLocalEntities
qualLocalScope :: NameEnv RefResolved
qualLocalScope = qualifyNameEnv unqualLocalScope
unqualLocalEntities =
-- P and P.1 for each local predicate
[ entry
| (name, r, _) <- localPreds
, entry <- nameEntries name (predicateRef_version r) (RefPred r)
] ++
-- T and T.1 for each local type
[ entry
| (name, r, _) <- localTypes
, entry <- nameEntries name (typeRef_version r) (RefType r)
]
scope =
HashMap.union unqualLocalScope $
HashMap.union unqualInheritedScope $
HashMap.unionWith Set.union qualLocalScope $
HashMap.unionWith Set.union qualInheritedScope
importedScope
-- Check for inheriting multiple versions of a predicate/types
--
-- As a special case, if the schema is called "all" then allow name
-- clashes in the inheritance set. This is to facilitate our "all.N"
-- convention for declaring the "top-level" schema which will
-- inevitably have name clashes, but we don't care about what it
-- exports.
unless (namespace == "all") $
forM_ (HashMap.toList unqualInheritedScope) $ \(ref, targets) -> do
case Set.elems targets of
(_:_:_) -> throwError $
"inherited schemas give multiple definitions for: " <> showRef ref
_ -> return ()
-- resolve type definitions
types <- forM localTypes $
\(name, ref, TypeDef{..}) -> do
checkName name
type' <- runResolve angleVersion scope (resolveType typeDefType)
return (name, TypeDef
{ typeDefRef = ref
, typeDefType = type'
, typeDefSrcSpan = typeDefSrcSpan })
-- resolve predicate definitions
predicates <- forM localPreds $
\(name, ref, PredicateDef{..}) -> do
checkName name
runResolve angleVersion scope $ do
key <- resolveType predicateDefKeyType
value <- resolveType predicateDefValueType
return (name, PredicateDef
{ predicateDefRef = ref
, predicateDefKeyType = key
, predicateDefValueType = value
, predicateDefDeriving = NoDeriving
, predicateDefSrcSpan = predicateDefSrcSpan })
let
-- scope of predicates that we can specify queries for. Namely
-- locally-defined predicates and inherited predicates.
predScope = resolveRef $
HashMap.union unqualLocalScope $
HashMap.union unqualInheritedScope $
HashMap.unionWith Set.union qualLocalScope
qualInheritedScope
-- resolve queries
localDeriving <-
forM [ (derivingDefRef,derive)
| SourceDeriving derive@DerivingDef{..} <- schemaDecls ] $
\(ref, DerivingDef{..}) -> do
ty <- lookupResultToExcept ref $ predScope ref
ref <- case ty of
RefPred ref -> return ref
_ -> throwError $ showRef ref <> " is not a predicate"
resolvedDerInfo <- runResolve angleVersion scope $
resolveDeriving derivingDefDeriveInfo
return (ref, DerivingDef ref resolvedDerInfo derivingDefSrcSpan)
let
localTypeNames = HashSet.fromList $
map (typeRef_name . typeDefRef . snd) types
localTypes = HashMap.fromList
[ (typeDefRef def, def) | (_, def) <- types ]
-- The types we re-export from this schema are all the types that
-- are inherited but not shadowed by a locally-defined type.
reExportedTypes =
HashMap.fromList
[ (typeDefRef def, def)
| schema <- inherits
, (ref, def) <- HashMap.toList $
resolvedSchemaTypes schema <>
resolvedSchemaReExportedTypes schema
, not (typeRef_name ref `HashSet.member` localTypeNames)
]
localPredicateNames = HashSet.fromList $
[ predicateRef_name (predicateDefRef def) | (_, def) <- predicates ]
localPredicates = HashMap.fromList
[ (predicateDefRef def, def) | (_, def) <- predicates ]
-- Similarly for predicates.
reExportedPredicates =
HashMap.fromList
[ (predicateDefRef def, def)
| schema <- inherits
, (ref, def) <- HashMap.toList $
resolvedSchemaPredicates schema <>
resolvedSchemaReExportedPredicates schema
, not (predicateRef_name ref `HashSet.member` localPredicateNames)
]
exportedUnqualScope = HashMap.union unqualLocalScope unqualInheritedScope
-- Note: local names override inherited names
-- Qualified exported names. The "all" schema is special: we don't
-- re-export things qualified with "all.", only with the original
-- schema name.
exportedQualScope
| namespace == "all" = qualInheritedScope
| otherwise =
HashMap.union
(qualifyNameEnv exportedUnqualScope)
qualInheritedScope -- NB. local overrides inherited
schemaEvolves <- traverse schemaByName evolves
return ResolvedSchema
{ resolvedSchemaName = namespace
, resolvedSchemaVersion = version
, resolvedSchemaAngleVersion = angleVersion
, resolvedSchemaTypes = localTypes
, resolvedSchemaReExportedTypes = reExportedTypes
, resolvedSchemaPredicates = localPredicates
, resolvedSchemaReExportedPredicates = reExportedPredicates
, resolvedSchemaUnqualScope = exportedUnqualScope
, resolvedSchemaQualScope = exportedQualScope
, resolvedSchemaDeriving = HashMap.fromList localDeriving
, resolvedSchemaEvolves = Set.fromList (schemaRef <$> schemaEvolves)
}
resolveType :: (ShowRef t, ShowRef p) =>
SourceType -> Resolve p t (Type_ SrcSpan p t)
resolveType typ = go typ
where
go typ = case typ of
ByteTy -> return ByteTy
NatTy -> return NatTy
StringTy -> return StringTy
ArrayTy ty -> ArrayTy <$> go ty
RecordTy fields -> do checkFields fields; RecordTy <$> mapM goField fields
SumTy fields -> do checkFields fields; SumTy <$> mapM goField fields
SetTy ty -> SetTy <$> go ty
PredicateTy s ref -> goRef ref s
NamedTy s ref -> goRef ref s -- shouldn't happen, but handle it anyway
MaybeTy ty -> MaybeTy <$> go ty
EnumeratedTy names -> lift $ do
mapM_ checkName names
return (EnumeratedTy names)
BooleanTy -> return BooleanTy
_ -> error "resolveType"
goRef ref s = do
scope <- getScope
target <- lift $ lookupResultToExcept ref $
resolveRef scope ref
case target of
RefType ref -> return (NamedTy s ref)
RefPred ref -> return (PredicateTy s ref)
checkFields fields = do
sequence_
[ throwError $ "duplicate field: " <> x
| x:_:_ <- group $ sort $ map fieldDefName fields ]
lift $ do
mapM_ (checkName . fieldDefName) fields
mapM_ (checkFieldName . fieldDefName) fields
goField (FieldDef name ty) = FieldDef name <$> go ty
lookupResultToExcept
:: (ShowRef t, ShowRef p)
=> SourceRef
-> LookupResult (RefTarget p t)
-> Except Text (RefTarget p t)
lookupResultToExcept ref res =
either throwError return (lookupResultToEither ref res)
checkAngleVersion :: AngleVersion -> Except Text ()
checkAngleVersion v =
unless (v >= latestSupportedAngleVersion) $
let ver = Text.pack $ show $ pretty v in
throwError $ "Angle version " <> ver <> " is not supported"
checkFieldName :: Name -> Except Text ()
checkFieldName n =
when (isUpper (Text.head n)) $
throwError $ "field names must begin with a lowercase letter: " <> n
checkName :: Name -> Except Text ()
checkName n = do
when (n `HashSet.member` reservedWords) $
throwError $ n <> " is a reserved word, it cannot be used"
checkNameSpace :: Name -> Except Text ()
checkNameSpace n = mapM_ checkName (splitDot n)
reservedWords :: HashSet Text
reservedWords = HashSet.fromList [
-- Thrift reserved words, seems like a good idea to avoid these
"binary", "bool", "byte", "const", "cpp_include",
"double", "enum", "exception", "extends", "false", "float",
"hash_map", "hash_set", "hs_include", "i16", "i32", "i64", "include",
"list", "map", "namespace", "oneway", "optional", "required", "senum",
"service", "set", "stream", "string", "struct", "throws", "true",
"typedef", "union", "view", "void",
-- keywords that the fbthrift compiler rejects (see
-- thrift/compiler/parse/thrift.ll)
"abstract",
"and",
"args",
"as",
"assert",
"auto",
"break",
"case",
"char",
"class",
"continue",
"declare",
"def",
"default",
"del",
"do",
"elif",
"else",
"elseif",
"except",
"exec",
"extern",
"finally",
"for",
"foreach",
"function",
"global",
"goto",
"if",
"implements",
"import",
"in",
"int",
"inline",
"instanceof",
"interface",
"is",
"lambda",
"long",
"native",
"new",
"not",
"or",
"pass",
"public",
"print",
"private",
"protected",
"raise",
"register",
"return",
"short",
"signed",
"sizeof",
"static",
"switch",
"synchronized",
"template",
"this",
"throw",
"transient",
"try",
"unsigned",
"var",
"virtual",
"volatile",
"while",
"with",
"yield",
"Object",
"Client",
"IFace",
"Processor",
-- Other words to avoid, because they lead to Thrift-generated code that
-- does not compile for one or more languages.
"None", "operator", {- TODO: "Enum" -}
-- Reserved for temporary predicates
"_tmp_"
]
-- -----------------------------------------------------------------------------
-- Resolving queries
runResolve
:: AngleVersion
-> NameEnv (RefTarget p t)
-> Resolve p t a
-> Except Text a
runResolve ver scope act = runReaderT act (ver,scope)
type Resolve p t a =
ReaderT (AngleVersion, NameEnv (RefTarget p t)) (Except Text) a
getScope :: Resolve p t (NameEnv (RefTarget p t))
getScope = asks snd
resolveDeriving
:: (ShowRef t, ShowRef p)
=> SourceDerivingInfo' SrcSpan
-> Resolve p t (DerivingInfo (Query_ p t))
resolveDeriving NoDeriving = return NoDeriving
resolveDeriving (Derive when query) = Derive when <$> resolveQuery query
resolveQuery
:: (ShowRef t, ShowRef p)
=> SourceQuery
-> Resolve p t (Query_ p t)
resolveQuery (SourceQuery head stmts ord) =
SourceQuery
<$> mapM resolvePat head
<*> mapM resolveStatement stmts
<*> pure ord
resolvePat
:: (ShowRef t, ShowRef p)
=> SourcePat
-> Resolve p t (SourcePat_ SrcSpan SrcSpan p t)
resolvePat pat = case pat of
Nat s i -> return (Nat s i)
String s t -> return (String s t)
StringPrefix s t -> return (StringPrefix s t)
ByteArray s b -> return (ByteArray s b)
Array s pats -> Array s <$> mapM resolvePat pats
ArrayPrefix s pats -> ArrayPrefix s <$> mapM resolvePat pats
Tuple s pats -> Tuple s <$> mapM resolvePat pats
All s qs -> All s <$> resolvePat qs
Elements s pat -> Elements s <$> resolvePat pat
Wildcard s -> return (Wildcard s)
TypeSignature s pat ty ->
TypeSignature s
<$> resolvePat pat
<*> resolveType ty
Variable s n -> return (Variable s n)
FactId s Nothing id -> return (FactId s Nothing id)
FactId s (Just pred) id -> do
res <- resolveTypeOrPred s pred
case res of
RefPred ref ->
return (TypeSignature s (FactId s Nothing id) (PredicateTy s ref))
_other -> prettyErrorIn pat $ "not a predicate: " <> pretty pred
OrPattern s l r -> OrPattern s <$> resolvePat l <*> resolvePat r
Negation s pat -> Negation s <$> resolvePat pat
Never s -> return (Never s)
IfPattern s cond then_ else_ ->
IfPattern s
<$> resolvePat cond
<*> resolvePat then_
<*> resolvePat else_
ElementsOfArray s pat -> ElementsOfArray s <$> resolvePat pat
KeyValue s k v -> KeyValue s <$> resolvePat k <*> resolvePat v
NestedQuery s q -> NestedQuery s <$> resolveQuery q
Struct s fields -> Struct s <$> mapM resolveField fields
where resolveField (Field n pat) = Field n <$> resolvePat pat
App s (Variable svar txt) args
| Just primOp <- HashMap.lookup txt primitives -> do
Prim s primOp <$> mapM resolvePat args
| otherwise ->
case args of
[arg] -> do
arg' <- resolvePat arg
let (range, txt') = if
| Just rem <- Text.stripSuffix "#new" txt -> (SeekOnStacked, rem)
| Just rem <- Text.stripSuffix "#old" txt -> (SeekOnBase, rem)
| otherwise -> (SeekOnAllFacts, txt)
res <- resolveTypeOrPred svar txt'
case res of
RefPred ref -> return (Clause s svar ref arg' range)
RefType ref
| SeekOnAllFacts <- range ->
return (TypeSignature s arg' (NamedTy svar ref))
| otherwise ->
prettyErrorIn pat "cannot use #new or #old with types"
-- The syntax "T pat" for "pat : T" is something we might
-- consider deprecating later. For now it's just desugared
-- here.
_ -> prettyErrorIn pat "unexpected extra argument(s)"
App s (StringPrefix a b) [pat] -> do
pat' <- resolvePat pat
return (App s (StringPrefix a b) [pat'])
App _ f _ ->
prettyErrorIn pat $ "invalid pattern: cannot apply " <>
describe f <> " to arguments"
FieldSelect s pat field q ->
(\p -> FieldSelect s p field q)
<$> resolvePat pat
Deref s pat -> Deref s <$> resolvePat pat
Enum s n -> return (Enum s n)
Clause{} -> internal
Prim{} -> internal
where
internal = throwError $ "internal: unexpected: " <>
Text.pack (show (pretty pat))
resolveTypeOrPred
:: (ShowRef p, ShowRef t)
=> SrcSpan
-> Name
-> Resolve p t (RefTarget p t)
resolveTypeOrPred span txt = do
scope <- getScope
let ref = parseRef txt
case lookupResultToEither ref $ resolveRef scope ref of
Left s -> prettyErrorAt span (pretty s)
Right r -> return r
prettyError :: Doc ann -> Resolve p t a
prettyError = throwError . Text.pack . show
prettyErrorIn :: IsSrcSpan s => SourcePat' s st -> Doc ann -> Resolve p t a
prettyErrorIn pat doc = prettyErrorAt (sourcePatSpan pat) doc
prettyErrorAt :: IsSrcSpan span => span -> Doc ann -> Resolve p t a
prettyErrorAt span doc = prettyError $ vcat
[ pretty span
, doc
]
resolveStatement
:: (ShowRef t, ShowRef p)
=> SourceStatement
-> Resolve p t (Statement_ p t)
resolveStatement (SourceStatement l r) =
SourceStatement <$> resolvePat l <*> resolvePat r
primitives :: HashMap Text PrimOp
primitives = HashMap.fromList
[ ("prim.toLower", PrimOpToLower)
, ("prim.length", PrimOpLength)
, ("prim.relToAbsByteSpans", PrimOpRelToAbsByteSpans)
, ("prim.unpackByteSpans", PrimOpUnpackByteSpans)
, ("prim.zip", PrimOpZip)
, ("prim.concat", PrimOpConcat)
, ("prim.reverse", PrimOpReverse)
, ("prim.size", PrimOpSize)
, ("prim.gtNat", PrimOpGtNat)
, ("prim.geNat", PrimOpGeNat)
, ("prim.ltNat", PrimOpLtNat)
, ("prim.leNat", PrimOpLeNat)
, ("prim.neNat", PrimOpNeNat)
, ("prim.addNat", PrimOpAddNat)
, ("prim.neExpr", PrimOpNeExpr)
]