refactor: move form validation into a separate module (#1233)

This commit is contained in:
Scott Olsen 2021-06-08 01:39:06 -04:00 committed by GitHub
parent 7c821543bc
commit 4f7905d85b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 654 additions and 242 deletions

View File

@ -29,6 +29,7 @@ library
EvalError,
Eval,
Expand,
Forms,
GenerateConstraints,
Infer,
Info,

View File

@ -10,7 +10,7 @@ import Control.Exception
import Control.Monad.State
import Data.Either (fromRight)
import Data.Foldable (foldlM, foldrM)
import Data.List (foldl', intercalate, isSuffixOf)
import Data.List (foldl', isSuffixOf)
import Data.List.Split (splitOn, splitWhen)
import Data.Maybe (fromJust, fromMaybe, isJust)
import Emit
@ -35,6 +35,7 @@ import TypeError
import Types
import Util
import Prelude hiding (exp, mod)
import Forms
-- TODO: Formalize "lookup order preference" a bit better and move into
-- the Context module.
@ -48,6 +49,8 @@ data Resolver
= ResolveGlobal
| ResolveLocal
type Evaluator = [XObj] -> IO (Context, Either EvalError XObj)
-- Prefer dynamic bindings
evalDynamic :: Resolver -> Context -> XObj -> IO (Context, Either EvalError XObj)
evalDynamic resolver ctx xobj = eval ctx xobj PreferDynamic resolver
@ -181,246 +184,59 @@ eval ctx xobj@(XObj o info ty) preference resolver =
resolveDef (XObj (Lst [XObj DefDynamic _ _, _, value]) _ _) = value
resolveDef (XObj (Lst [XObj LocalDef _ _, _, value]) _ _) = value
resolveDef x = x
eval' form =
case form of
[XObj If _ _, mcond, mtrue, mfalse] -> do
(newCtx, evd) <- eval ctx mcond preference ResolveLocal
case evd of
Right cond ->
case xobjObj cond of
Bol b -> eval newCtx (if b then mtrue else mfalse) preference ResolveLocal
_ ->
pure (throwErr (IfContainsNonBool cond) ctx (xobjInfo cond))
Left e -> pure (newCtx, Left e)
XObj If _ _ : _ ->
pure (throwErr (IfMalformed xobj) ctx (xobjInfo xobj))
[XObj (Defn _) _ _, name, args@(XObj (Arr a) _ _), _] ->
case xobjObj name of
(Sym (SymPath [] _) _) ->
if all isUnqualifiedSym a
then specialCommandDefine ctx xobj
else pure (throwErr (DefnContainsQualifiedArgs args) ctx (xobjInfo xobj))
_ ->
pure (throwErr (DefnIdentifierIsQualified name) ctx (xobjInfo xobj))
[XObj (Defn _) _ _, _, invalidArgs, _] ->
pure (throwErr (defnInvalidArgs [invalidArgs]) ctx (xobjInfo xobj))
(defn@(XObj (Defn _) _ _) : _) ->
pure (throwErr (DefnMalformed xobj) ctx (xobjInfo defn))
[XObj Def _ _, name, _] ->
if isUnqualifiedSym name
then specialCommandDefine ctx xobj
else pure (throwErr (DefIdentifierIsQualified name) ctx (xobjInfo xobj))
[the@(XObj The _ _), t, value] ->
do
(newCtx, evaledValue) <- expandAll (evalDynamic ResolveLocal) ctx value -- TODO: Why expand all here?
pure
( newCtx,
do
okValue <- evaledValue
Right (XObj (Lst [the, t, okValue]) info ty)
)
(XObj The _ _ : _) ->
pure (throwErr (TheMalformed xobj) ctx (xobjInfo xobj))
[XObj Let _ _, XObj (Arr bindings) _ _, body]
| odd (length bindings) ->
pure (throwErr (LetUnevenForms xobj) ctx (xobjInfo xobj))
| not (all isSym (evenIndices bindings)) ->
pure (throwErr (LetMalformedIdentifiers bindings) ctx (xobjInfo xobj))
| otherwise ->
do
let binds = unwrapVar (pairwise bindings) []
ni = Env Map.empty (contextInternalEnv ctx) Nothing Set.empty InternalEnv 0
eitherCtx <- foldrM successiveEval' (Right (replaceInternalEnv ctx ni)) binds
case eitherCtx of
Left err -> pure (ctx, Left err)
Right newCtx -> do
(finalCtx, evaledBody) <- eval newCtx body (PreferLocal (map (\(name, _) -> (SymPath [] name)) binds)) ResolveLocal
let Just e = contextInternalEnv finalCtx
parentEnv = fromMaybe e (envParent e)
pure
( replaceInternalEnv finalCtx parentEnv,
do
okBody <- evaledBody
Right okBody
)
where
unwrapVar [] acc = acc
unwrapVar ((XObj (Sym (SymPath [] x) _) _ _, y) : xs) acc = unwrapVar xs ((x, y) : acc)
unwrapVar _ _ = error "unwrapvar"
successiveEval' (n, x) =
\case
err@(Left _) -> pure err
Right ctx' -> do
-- Bind a reference to the let bind in a recursive
-- environment. This permits recursion in anonymous functions
-- in let binds such as:
-- (let [f (fn [x] (if (= x 1) x (f (dec x))))] (f 10))
let origin = (contextInternalEnv ctx')
recFix = (E.recursive origin (Just "let-rec-env") 0)
Right envWithSelf = E.insertX recFix (SymPath [] n) x
ctx'' = replaceInternalEnv ctx' envWithSelf
(newCtx, res) <- eval ctx'' x preference resolver
case res of
Right okX ->
pure $ Right (fromRight (error "Failed to eval let binding!!") (bindLetDeclaration (newCtx {contextInternalEnv = origin}) n okX))
Left err -> pure $ Left err
[f@(XObj Fn {} _ _), args@(XObj (Arr a) _ _), body] -> do
(newCtx, expanded) <- macroExpand ctx body
pure $
case expanded of
Right b ->
if all isUnqualifiedSym a
then (newCtx, Right (XObj (Closure (XObj (Lst [f, args, b]) info ty) (CCtx newCtx)) info ty))
else (throwErr (FnContainsQualifiedArgs args) ctx (xobjInfo args))
Left err -> (ctx, Left err)
XObj (Closure (XObj (Lst [XObj (Fn _ _) _ _, XObj (Arr params) _ _, body]) _ _) (CCtx c)) _ _ : args ->
case checkArity (pretty xobj) params args of
Left err -> pure (evalError ctx err (xobjInfo xobj))
Right () ->
do
(newCtx, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right okArgs -> do
let newGlobals = (contextGlobalEnv newCtx) <> (contextGlobalEnv c)
newTypes = TypeEnv $ (getTypeEnv (contextTypeEnv newCtx)) <> (getTypeEnv (contextTypeEnv c))
updater = replaceHistory' (contextHistory ctx) . replaceGlobalEnv' newGlobals . replaceTypeEnv' newTypes
(ctx', res) <- apply (updater c) body params okArgs
pure (replaceGlobalEnv newCtx (contextGlobalEnv ctx'), res)
Left err -> pure (newCtx, Left err)
XObj (Lst [XObj Dynamic _ _, sym, XObj (Arr params) _ _, body]) i _ : args ->
case checkArity (getName sym) params args of
Left err ->
pure (evalError ctx err i)
Right () ->
do
(newCtx, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right okArgs -> apply newCtx body params okArgs
Left err -> pure (newCtx, Left err)
XObj (Lst [XObj Macro _ _, sym, XObj (Arr params) _ _, body]) i _ : args ->
case checkArity (getName sym) params args of
Left err -> pure (evalError ctx err i)
Right () -> do
-- Replace info so that the macro which is called gets the source location info of the expansion site.
--let replacedBody = replaceSourceInfoOnXObj (info xobj) body
(ctx', res) <- apply ctx body params args
case res of
Right xobj' -> macroExpand ctx' xobj'
Left _ -> pure (ctx, res)
[XObj (Lst [XObj (Command (NullaryCommandFunction nullary)) _ _, _, _]) _ _] ->
do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) []
case evaledArgs of
Right [] -> nullary c
Right _ -> error "eval nullary"
Left err -> pure (ctx, Left err)
[XObj (Lst [XObj (Command (UnaryCommandFunction unary)) _ _, _, _]) _ _, x] ->
do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x]
case evaledArgs of
Right [x'] -> unary c x'
Right _ -> error "eval unary"
Left err -> pure (ctx, Left err)
[XObj (Lst [XObj (Command (BinaryCommandFunction binary)) _ _, _, _]) _ _, x, y] ->
do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x, y]
case evaledArgs of
Right [x', y'] -> binary c x' y'
Right _ -> error "eval binary"
Left err -> pure (ctx, Left err)
[XObj (Lst [XObj (Command (TernaryCommandFunction ternary)) _ _, _, _]) _ _, x, y, z] ->
do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x, y, z]
case evaledArgs of
Right [x', y', z'] -> ternary c x' y' z'
Right _ -> error "eval ternary"
Left err -> pure (ctx, Left err)
XObj (Lst [XObj (Command (VariadicCommandFunction variadic)) _ _, _, _]) _ _ : args ->
do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right xs -> variadic c xs
Left err -> pure (ctx, Left err)
XObj (Lst [XObj (Command _) _ _, sym, XObj (Arr params) _ _]) i _ : args ->
badArity (getName sym) params args i
[e@(XObj (Lst [XObj (Primitive (NullaryPrimitive nullary)) _ _, _, _]) _ _)] -> nullary e ctx
[e@(XObj (Lst [XObj (Primitive (UnaryPrimitive unary)) _ _, _, _]) _ _), x] -> unary e ctx x
[e@(XObj (Lst [XObj (Primitive (BinaryPrimitive binary)) _ _, _, _]) _ _), x, y] -> binary e ctx x y
[e@(XObj (Lst [XObj (Primitive (TernaryPrimitive ternary)) _ _, _, _]) _ _), x, y, z] -> ternary e ctx x y z
[e@(XObj (Lst [XObj (Primitive (QuaternaryPrimitive quaternary)) _ _, _, _]) _ _), x, y, z, w] -> quaternary e ctx x y z w
e@(XObj (Lst [XObj (Primitive (VariadicPrimitive variadic)) _ _, _, _]) _ _) : args -> variadic e ctx args
XObj (Lst [XObj (Primitive _) _ _, sym, XObj (Arr params) _ _]) i _ : args -> badArity (getName sym) params args i
XObj (Lst (XObj (Defn _) _ _ : _)) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj (Lst (XObj (Interface _ _) _ _ : _)) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj (Lst (XObj (Instantiate _) _ _ : _)) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj (Lst (XObj (Deftemplate _) _ _ : _)) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj (Lst (XObj (External _) _ _ : _)) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj (Match _) _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
XObj Ref _ _ : _ -> pure (ctx, Left (HasStaticCall xobj info))
l@(XObj (Lst _) i t) : args -> do
(newCtx, f) <- eval ctx l preference ResolveLocal
case f of
Right fun -> do
(newCtx', res) <- eval (pushFrame newCtx xobj) (XObj (Lst (fun : args)) i t) preference ResolveLocal
pure (popFrame newCtx', res)
x -> pure (newCtx, x)
x@(XObj (Sym _ _) i _) : args -> do
(newCtx, f) <- eval ctx x preference resolver
case f of
Right fun -> do
(newCtx', res) <- eval (pushFrame ctx xobj) (XObj (Lst (fun : args)) i ty) preference resolver
pure (popFrame newCtx', res)
Left err -> pure (newCtx, Left err)
XObj With _ _ : xobj'@(XObj (Sym path _) _ _) : forms ->
specialCommandWith ctx xobj' path forms
XObj With _ _ : x : _ ->
pure (throwErr (withInvalidArgs [x]) ctx (xobjInfo xobj))
XObj SetBang _ _ : args -> specialCommandSet ctx args
[XObj Do _ _] ->
pure (throwErr DoMissingForms ctx (xobjInfo xobj))
XObj Do _ _ : rest -> foldlM successiveEval' (ctx, dynamicNil) rest
where
successiveEval' (ctx', acc) x =
case acc of
err@(Left _) -> pure (ctx', err)
Right _ -> eval ctx' x preference resolver
[XObj While _ _, cond, body] ->
specialCommandWhile ctx cond body
[] -> pure (ctx, dynamicNil)
_ -> pure (throwErr (UnknownForm xobj) ctx (xobjInfo xobj))
badArity name params args i = case checkArity name params args of
Left err -> pure (evalError ctx err i)
Right () -> error "badarity"
checkArity name params args =
let la = length args
withRest = any ((":rest" ==) . getName) params
lp = length params - (if withRest then 2 else 0)
in if lp == la || (withRest && la >= lp)
then Right ()
else
if la < lp
then
Left
( name
++ " expected "
++ show lp
++ " arguments but received only "
++ show la
++ ".\n\nYoull have to provide "
++ intercalate ", " (map pretty (drop la params))
++ " as well."
)
else
Left
( name
++ " expected "
++ show lp
++ " arguments, but received "
++ show la
++ ".\n\nThe arguments "
++ intercalate ", " (map pretty (drop lp args))
++ " are not needed."
)
case validate form of
Left e -> pure (evalError ctx (format e) (xobjInfo xobj))
Right form' ->
case form' of
(IfPat _ _ _) -> evaluateIf form'
(DefnPat _ _ _) -> specialCommandDefine ctx xobj
(DefPat _ _) -> specialCommandDefine ctx xobj
(ThePat _ _ _) -> evaluateThe form'
(LetPat _ _) -> evaluateLet form'
(FnPat _ _ _) -> evaluateFn form'
(AppPat (ClosurePat _ _ _) _) -> evaluateClosure form'
(AppPat (DynamicFnPat _ _ _) _) -> evaluateDynamicFn form'
(AppPat (MacroPat _ _ _) _) -> evaluateMacro form'
(AppPat (CommandPat _ _ _) _) -> evaluateCommand form'
(AppPat (PrimitivePat _ _ _) _) -> evaluatePrimitive form'
(WithPat (SymPat sym path) forms) -> specialCommandWith ctx sym path forms
(DoPat forms) -> evaluateSideEffects forms
(WhilePat cond body) -> specialCommandWhile ctx cond body
-- This next match is a bit redundant looking at first glance, but
-- it is necessary to prevent hangs on input such as: `((def foo 2)
-- 4)`. Ideally, we could perform only *one* static check (the one
-- we do in eval). But the timing is wrong.
-- The `def` in the example above initially comes into the
-- evaluator as a *Sym*, **not** as a `Def` xobj. So, we need to
-- discriminate on the result of evaluating the symbol to eagerly
-- break the evaluation loop, otherwise we will proceed to evaluate
-- the def form, yielding Unit, and attempt to reevaluate unit
-- indefinitely on subsequent eval loops.
-- Importantly, the loop *is only broken on literal nested lists*.
-- That is, passing a *symbol* that, e.g. resolves to a defn list, won't
-- break our normal loop.
(AppPat (ListPat self ((SymPat x _):xs)) args) ->
do (_, evald) <- eval ctx x preference ResolveGlobal
case evald of
Left err -> pure (evalError ctx (show err) (xobjInfo xobj))
Right x' -> case checkStatic' x' of
Right _ -> evaluateApp ((XObj (Lst (x':xs)) (xobjInfo self) (xobjTy self)):args)
Left er -> pure (evalError ctx (show er) (xobjInfo xobj))
(SetPat iden value) -> specialCommandSet ctx (iden:[value])
(AppPat (SymPat _ _) _) -> evaluateApp form'
[] -> pure (ctx, dynamicNil)
_ -> pure (throwErr (UnknownForm xobj) ctx (xobjInfo xobj))
checkStatic' (XObj Def _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (Defn _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (Interface _ _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (Instantiate _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (Deftemplate _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (External _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj (Match _) _ _) = Left (HasStaticCall xobj info)
checkStatic' (XObj Ref _ _) = Left (HasStaticCall xobj info)
checkStatic' x' = Right x'
successiveEval (ctx', acc) x =
case acc of
Left _ -> pure (ctx', acc)
@ -430,6 +246,178 @@ eval ctx xobj@(XObj o info ty) preference resolver =
Right res -> (newCtx, Right (l ++ [res]))
Left err -> (newCtx, Left err)
evaluateIf :: Evaluator
evaluateIf (IfPat cond true false) = do
(newCtx, evd) <- eval ctx cond preference ResolveLocal
case evd of
Right cond' ->
case xobjObj cond' of
Bol b -> eval newCtx (if b then true else false) preference ResolveLocal
_ ->
pure (throwErr (IfContainsNonBool cond) ctx (xobjInfo cond))
Left e -> pure (newCtx, Left e)
evaluateIf _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateThe :: Evaluator
evaluateThe (ThePat the t value) = do
(newCtx, evaledValue) <- expandAll (evalDynamic ResolveLocal) ctx value -- TODO: Why expand all here?
pure
( newCtx,
do
okValue <- evaledValue
Right (XObj (Lst [the, t, okValue]) info ty)
)
evaluateThe _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateLet :: Evaluator
evaluateLet (LetPat (ArrPat _ bindings) body) = do
let binds = unwrapVar (pairwise bindings) []
ni = Env Map.empty (contextInternalEnv ctx) Nothing Set.empty InternalEnv 0
eitherCtx <- foldrM successiveEval' (Right (replaceInternalEnv ctx ni)) binds
case eitherCtx of
Left err -> pure (ctx, Left err)
Right newCtx -> do
(finalCtx, evaledBody) <- eval newCtx body (PreferLocal (map (\(name, _) -> (SymPath [] name)) binds)) ResolveLocal
let Just e = contextInternalEnv finalCtx
parentEnv = fromMaybe e (envParent e)
pure
( replaceInternalEnv finalCtx parentEnv,
do
okBody <- evaledBody
Right okBody
)
where
unwrapVar [] acc = acc
unwrapVar ((XObj (Sym (SymPath [] x) _) _ _, y) : xs) acc = unwrapVar xs ((x, y) : acc)
unwrapVar _ _ = error "unwrapvar"
successiveEval' (n, x) =
\case
err@(Left _) -> pure err
Right ctx' -> do
-- Bind a reference to the let bind in a recursive
-- environment. This permits recursion in anonymous functions
-- in let binds such as:
-- (let [f (fn [x] (if (= x 1) x (f (dec x))))] (f 10))
let origin = (contextInternalEnv ctx')
recFix = (E.recursive origin (Just "let-rec-env") 0)
Right envWithSelf = E.insertX recFix (SymPath [] n) x
ctx'' = replaceInternalEnv ctx' envWithSelf
(newCtx, res) <- eval ctx'' x preference resolver
case res of
Right okX ->
pure $ Right (fromRight (error "Failed to eval let binding!!") (bindLetDeclaration (newCtx {contextInternalEnv = origin}) n okX))
Left err -> pure $ Left err
evaluateLet _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateFn :: Evaluator
evaluateFn (FnPat self args body) = do
(newCtx, expanded) <- macroExpand ctx body
pure $
case expanded of
Right b ->
(newCtx, Right (XObj (Closure (XObj (Lst [self, args, b]) info ty) (CCtx newCtx)) info ty))
Left err -> (ctx, Left err)
evaluateFn _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateClosure :: Evaluator
evaluateClosure (AppPat (ClosurePat params body c) args) = do
(newCtx, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right okArgs -> do
let newGlobals = (contextGlobalEnv newCtx) <> (contextGlobalEnv c)
newTypes = TypeEnv $ (getTypeEnv (contextTypeEnv newCtx)) <> (getTypeEnv (contextTypeEnv c))
updater = replaceHistory' (contextHistory ctx) . replaceGlobalEnv' newGlobals . replaceTypeEnv' newTypes
(ctx', res) <- apply (updater c) body params okArgs
pure (replaceGlobalEnv newCtx (contextGlobalEnv ctx'), res)
Left err -> pure (newCtx, Left err)
evaluateClosure _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateDynamicFn :: Evaluator
evaluateDynamicFn (AppPat (DynamicFnPat _ params body) args) = do
(newCtx, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right okArgs -> apply newCtx body params okArgs
Left err -> pure (newCtx, Left err)
evaluateDynamicFn _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateMacro :: Evaluator
evaluateMacro (AppPat (MacroPat _ params body) args) = do
(ctx', res) <- apply ctx body params args
case res of
Right xobj' -> macroExpand ctx' xobj'
Left _ -> pure (ctx, res)
evaluateMacro _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateCommand :: Evaluator
evaluateCommand (AppPat (CommandPat (NullaryCommandFunction nullary) _ _) []) =
nullary ctx
evaluateCommand (AppPat (CommandPat (UnaryCommandFunction unary) _ _) [x]) = do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x]
case evaledArgs of
Right args -> let [x'] = take 1 args in unary c x'
Left err -> pure (ctx, Left err)
evaluateCommand (AppPat (CommandPat (BinaryCommandFunction binary) _ _) [x, y]) = do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x, y]
case evaledArgs of
Right args -> let [x', y'] = take 2 args in binary c x' y'
Left err -> pure (ctx, Left err)
evaluateCommand (AppPat (CommandPat (TernaryCommandFunction ternary) _ _) [x, y, z]) = do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) [x, y, z]
case evaledArgs of
Right args' -> let [x', y', z'] = take 3 args' in ternary c x' y' z'
Left err -> pure (ctx, Left err)
evaluateCommand (AppPat (CommandPat (VariadicCommandFunction variadic) _ _) args) = do
(c, evaledArgs) <- foldlM successiveEval (ctx, Right []) args
case evaledArgs of
Right args' -> variadic c args'
Left err -> pure (ctx, Left err)
-- Should be caught during validation
evaluateCommand (AppPat (CommandPat _ _ _) _) =
pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateCommand _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluatePrimitive :: Evaluator
evaluatePrimitive (AppPat p@(PrimitivePat (NullaryPrimitive nullary) _ _) []) =
nullary p ctx
evaluatePrimitive (AppPat p@(PrimitivePat (UnaryPrimitive unary) _ _) [x]) = do
unary p ctx x
evaluatePrimitive (AppPat p@(PrimitivePat (BinaryPrimitive binary) _ _) [x, y]) = do
binary p ctx x y
evaluatePrimitive (AppPat p@(PrimitivePat (TernaryPrimitive ternary) _ _) [x, y, z]) = do
ternary p ctx x y z
evaluatePrimitive (AppPat p@(PrimitivePat (QuaternaryPrimitive quaternary) _ _) [x, y, z, w]) = do
quaternary p ctx x y z w
evaluatePrimitive (AppPat p@(PrimitivePat (VariadicPrimitive variadic) _ _) args) = do
variadic p ctx args
-- Should be caught during validation
evaluatePrimitive (AppPat (PrimitivePat _ _ _) _) =
pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluatePrimitive _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateApp :: Evaluator
evaluateApp (AppPat f' args) =
case f' of
(ListPat l _) -> go l
(SymPat sym _) -> go sym
_ -> pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
where go x =
do (newCtx, f) <- eval ctx x preference ResolveLocal
case f of
Right fun -> do
(newCtx', res) <- eval (pushFrame newCtx xobj) (XObj (Lst (fun : args)) (xobjInfo x) (xobjTy x)) preference ResolveLocal
pure (popFrame newCtx', res)
x' -> pure (newCtx, x')
evaluateApp _ = pure (evalError ctx (format (GenericMalformed xobj)) (xobjInfo xobj))
evaluateSideEffects :: Evaluator
evaluateSideEffects forms = do
foldlM successiveEval' (ctx, dynamicNil) forms
where
successiveEval' (ctx', acc) x =
case acc of
err@(Left _) -> pure (ctx', err)
Right _ -> eval ctx' x preference resolver
macroExpand :: Context -> XObj -> IO (Context, Either EvalError XObj)
macroExpand ctx xobj =
case xobj of

412
src/Forms.hs Normal file
View File

@ -0,0 +1,412 @@
{-# LANGUAGE PatternSynonyms #-}
-- | Module Forms both defines valid forms in Carp and performs validation on
-- unchecked forms (xobjs).
--
-- It defines a number of pattern synonyms for ease of pattern matching.
module Forms (
validate,
format,
Malformed(GenericMalformed),
pattern ArrPat,
pattern ListPat,
pattern SymPat,
pattern UnqualifiedSymPat,
pattern DefPat,
pattern DefnPat,
pattern IfPat,
pattern ThePat,
pattern LetPat,
pattern FnPat,
pattern ClosurePat,
pattern DynamicFnPat,
pattern MacroPat,
pattern CommandPat,
pattern PrimitivePat,
pattern AppPat,
pattern WithPat,
pattern DoPat,
pattern WhilePat,
pattern SetPat,
) where
import Obj
import SymPath
import Util
import Data.List (intercalate)
--------------------------------------------------------------------------------
-- Data
-- Specialized constructors for each built-in language form.
-- | Error type representing a generic malformed expression.
data Malformed = InvalidIdentifier XObj Modifier
| QualifiedIdentifier XObj Modifier
| GenericMalformed XObj
| InvalidArguments XObj Modifier
| InvalidBody XObj Modifier
| InvalidCondition XObj Modifier
| InvalidType XObj Modifier
| InvalidBindings XObj Modifier
| UnevenForms XObj Int Modifier
| InsufficientArguments XObj Int Int [XObj]
| TooManyArguments XObj Int Int [XObj]
| InvalidApplication XObj
| DoMissingForms
instance Show Malformed where
show (QualifiedIdentifier x modifier) =
"I expected an unqualified symbol, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidIdentifier x modifier) =
"I expected a symbol, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidArguments x modifier) =
"`I expected an array of valid arguments, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidBody x modifier) =
"I expected a valid definition body, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidCondition x modifier) =
"I expected a boolean condition, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidType x modifier) =
"I expected the name of a type, but got: " ++ pretty x
++ formatModifier modifier
show (InvalidBindings bindings modifier) =
"Expected an array of name-value binding pairs, but got: " ++ pretty bindings
++ formatModifier modifier
show (UnevenForms forms len modifier) =
"Expected an even number of forms, but got: " ++ pretty forms
++ "of length " ++ show len ++ " " ++ formatModifier modifier
show (InsufficientArguments form lenExpected lenRecieved params) =
let name = case form of
(DynamicFnPat sym _ _) -> getName sym
(MacroPat sym _ _) -> getName sym
(CommandPat _ sym _) -> getName sym
(PrimitivePat _ sym _) -> getName sym
XObj Ref _ _ -> "ref"
_ -> pretty form
in name ++ " expected "
++ show lenExpected
++ " arguments but received only "
++ show lenRecieved
++ ".\n\nYoull have to provide "
++ intercalate ", " (map pretty (drop lenRecieved params))
++ " as well."
show (TooManyArguments form lenExpected lenRecieved args) =
let name = case form of
(DynamicFnPat sym _ _) -> getName sym
(MacroPat sym _ _) -> getName sym
(CommandPat _ sym _) -> getName sym
(PrimitivePat _ sym _) -> getName sym
XObj Ref _ _ -> "ref"
_ -> pretty form
in name ++ " expected "
++ show lenExpected
++ " arguments but received "
++ show lenRecieved
++ ".\n\nThe arguments "
++ intercalate ", " (map pretty (drop lenExpected args))
++ " are not needed."
show (InvalidApplication xobj) =
"Expected a function or macro, but got: " ++ pretty xobj
show (DoMissingForms) =
"Expected one or more forms in a `do` form, but got none."
show (GenericMalformed x) =
"The form: " ++ pretty x ++ " is malformed"
-- | Specific errors for particular types of malformed expressions.
data Modifier = DefnQualifiedSyms XObj
| DefnNonArrayArgs XObj
| DefnNonSymArgs XObj
| IfInvalidCondition XObj
| WhileInvalidCondition XObj
| TheInvalidType XObj
| LetMalformedBinding XObj
| LetUnevenForms XObj
| LetNonArrayBindings XObj
| FnQualifiedSyms XObj
| FnNonArrayArgs XObj
| FnNonSymArgs XObj
| InvalidWith XObj
| None
instance Show Modifier where
show None = ""
show (DefnQualifiedSyms arg) =
"`defn` requires all of its arguments to be unqualified symbols, but the arugment: "
++ pretty arg ++ " is qualified"
show (DefnNonArrayArgs args) =
"`defn` requires an array of arugments, but it got: " ++ pretty args
show (DefnNonSymArgs arg) =
"`defn` requires an array of symbols as arguments, but the argument: "
++ pretty arg ++ " is not a symbol"
show (IfInvalidCondition cond) =
"`if` requires a condition that can be evaluated to a boolean, but it got: "
++ pretty cond ++ " which cannot resolve to a boolean value."
show (WhileInvalidCondition cond) =
"`while` requires a condition that can be evaluated to a boolean, but it got: "
++ pretty cond ++ " which cannot resolve to a boolean value."
show (TheInvalidType t) =
"`the` requires a valid type name, but it got: "
++ pretty t ++ " which is not a valid type name"
show (LetMalformedBinding bind) =
"`let` requires name-value binding pairs, but it got: " ++ pretty bind
++ " as a binding name, which is invalid. Binding names must be symbols"
show (LetUnevenForms arr) =
"`let` requires an even number of name-value bindings forms, but it got: " ++ pretty arr
show (LetNonArrayBindings invalid) =
"`let` requires an array of bindings, but it got: " ++ pretty invalid
show (FnQualifiedSyms arg) =
"`fn` requires all of its arguments to be unqualified symbols, but the arugment: "
++ pretty arg ++ " is qualified"
show (FnNonArrayArgs args) =
"`fn` requires an array of arugments, but it got: " ++ pretty args
show (FnNonSymArgs arg) =
"`fn` requires an array of symbols as arguments, but the argument: "
++ pretty arg ++ " is not a symbol"
show (InvalidWith x) =
"`with` requires a symbol as an arugment, but got: " ++ pretty x
formatModifier :: Modifier -> String
formatModifier None = ""
formatModifier m = "\n - " ++ show m
-- | Format a malformed form error for printing.
format :: Malformed -> String
format e = "[ERROR] " ++ show e
--------------------------------------------------------------------------------
-- Validation functions
-- | Validate a given XObj to ensure it is a well formed expression of the language.
validate :: [XObj] -> Either Malformed [XObj]
validate xs =
case xs of
DefPat _ _ -> validateDef xs
DefnPat _ _ _ -> validateDefn xs
IfPat _ _ _ -> validateIf xs
ThePat _ _ _ -> validateThe xs
LetPat _ _ -> validateLet xs
FnPat _ _ _ -> validateFn xs
WithPat _ _ -> validateWith xs
DoPat _ -> validateDo xs
WhilePat _ _ -> validateWhile xs
-- There are a number of application patterns (the "has static call patterns")
-- that are formally caught at evaluation time.
AppPat (ClosurePat _ _ _) _ -> validateApp xs
AppPat (DynamicFnPat _ _ _) _ -> validateApp xs
AppPat (MacroPat _ _ _) _ -> validateApp xs
AppPat (CommandPat _ _ _) _ -> validateApp xs
AppPat (PrimitivePat _ _ _) _ -> validateApp xs
AppPat (XObj Ref _ _) _ -> validateApp xs
_ -> Right xs
-- TODO: Complete validation of if currently relies on evaluating its condition
-- for truthiness But there is a class of list forms we can rule out purely
-- symbolically, e.g. `def`, etc..
validateIf :: [XObj] -> Either Malformed [XObj]
validateIf x@(IfPat (ListPat _ _) _ _) = Right x -- needs further evaluation
validateIf (IfPat (ArrPat invalid _) _ _) = Left (InvalidCondition invalid (IfInvalidCondition invalid))
validateIf x@(IfPat cond _ _)
| isSym cond = Right x -- needs further evaluation
| isBool cond = Right x
| otherwise = Left (InvalidCondition cond (IfInvalidCondition cond))
validateIf invalid = Left (GenericMalformed (XObj (Lst invalid) Nothing Nothing))
-- | Validation of (while cond body) expressions.
validateWhile :: [XObj] -> Either Malformed [XObj]
validateWhile x@(WhilePat (ListPat _ _) _) = Right x -- needs further evaluation
validateWhile (WhilePat (ArrPat invalid _) _) = Left (InvalidCondition invalid (WhileInvalidCondition invalid))
validateWhile x@(WhilePat cond _)
| isSym cond = Right x -- needs further evaluation
| isBool cond = Right x
| otherwise = Left (InvalidCondition cond (WhileInvalidCondition cond))
validateWhile invalid = Left (GenericMalformed (XObj (Lst invalid) Nothing Nothing))
-- | Validation of (def name value) expressions.
validateDef :: [XObj] -> Either Malformed [XObj]
validateDef x@(DefPat (UnqualifiedSymPat _ _) _) = Right x
validateDef (DefPat (SymPat invalid _) _) = Left (QualifiedIdentifier invalid None)
validateDef (DefPat invalid _) = Left (InvalidIdentifier invalid None)
validateDef def = Left (GenericMalformed (XObj (Lst def) Nothing Nothing))
-- | Validation of (defn name [args] body) expressions.
validateDefn :: [XObj] -> Either Malformed [XObj]
validateDefn x@(DefnPat (UnqualifiedSymPat _ _) (ArrPat arr args) _)
| not (all isSym args) = Left (InvalidArguments arr (DefnNonSymArgs (head (remove isSym args))))
| not (all isUnqualifiedSym args) =
Left (InvalidArguments arr (DefnQualifiedSyms (head (remove isUnqualifiedSym args))))
| otherwise = pure x
validateDefn (DefnPat (UnqualifiedSymPat _ _) invalid _) =
Left (InvalidArguments invalid (DefnNonArrayArgs invalid))
validateDefn (DefnPat (SymPat invalid _) _ _) = Left (QualifiedIdentifier invalid None)
validateDefn (DefnPat invalid _ _) = Left (InvalidIdentifier invalid None)
validateDefn defn = Left (GenericMalformed (XObj (Lst defn) Nothing Nothing))
-- | Validation of (the type body) expressions
validateThe :: [XObj] -> Either Malformed [XObj]
validateThe x@(ThePat _ t _) =
case xobjToTy t of
Nothing -> Left (InvalidType t (TheInvalidType t))
Just _ -> Right x
validateThe the = Left (GenericMalformed (XObj (Lst the) Nothing Nothing))
-- | Validation of (let [bindings] body) expressions.
validateLet :: [XObj] -> Either Malformed [XObj]
validateLet x@(LetPat (ArrPat arr binds) _)
| odd (length binds) =
Left (UnevenForms arr (length binds) (LetUnevenForms arr))
| not (all isSym (evenIndices binds)) =
Left (InvalidBindings arr (LetMalformedBinding (head (remove isSym (evenIndices binds)))))
| otherwise = Right x
validateLet (LetPat invalid _) = Left (InvalidBindings invalid (LetNonArrayBindings invalid))
validateLet lett = Left (GenericMalformed (XObj (Lst lett) Nothing Nothing))
-- | Validation of (fn [args] body) expressions.
validateFn :: [XObj] -> Either Malformed [XObj]
validateFn x@(FnPat _ (ArrPat arr args) _)
| not (all isSym args) = Left (InvalidArguments arr (FnNonSymArgs (head (remove isSym args))))
| not (all isUnqualifiedSym args) =
Left (InvalidArguments arr (FnQualifiedSyms (head (remove isUnqualifiedSym args))))
| otherwise = pure x
validateFn (FnPat _ invalid _) = Left (InvalidArguments invalid (FnNonArrayArgs invalid))
validateFn fn = Left (GenericMalformed (XObj (Lst fn) Nothing Nothing))
-- | Validation of (do body) expressions.
validateDo :: [XObj] -> Either Malformed [XObj]
validateDo x@(DoPat forms) =
case forms of
[] -> Left DoMissingForms
_ -> Right x
validateDo doo = Left (GenericMalformed (XObj (Lst doo) Nothing Nothing))
-- | Validation of (function arguments) function applications.
validateApp :: [XObj] -> Either Malformed [XObj]
-- Special case for Refs
validateApp x@(AppPat f@(XObj Ref _ _) args) =
checkAppArity f [(XObj (Sym (SymPath [] "x") Symbol) Nothing Nothing)] args >> Right x
validateApp x@(AppPat f@(ClosurePat params _ _) args) =
checkAppArity f params args >> Right x
validateApp x@(AppPat f@(DynamicFnPat _ params _) args) =
checkAppArity f params args >> Right x
validateApp x@(AppPat f@(MacroPat _ params _) args) =
checkAppArity f params args >> Right x
validateApp x@(AppPat f@(CommandPat arity _ _) args) =
case arity of
(NullaryCommandFunction _) -> checkAppArity f p args >> Right x
(UnaryCommandFunction _) -> checkAppArity f p args >> Right x
(BinaryCommandFunction _) -> checkAppArity f p args >> Right x
(TernaryCommandFunction _) -> checkAppArity f p args >> Right x
(VariadicCommandFunction _) -> Right x
where p = (\y -> XObj (Sym (SymPath [] y) Symbol) Nothing Nothing) <$> argnames
argnames =
case arity of
NullaryCommandFunction _ -> []
UnaryCommandFunction _ -> ["x"]
BinaryCommandFunction _ -> ["x", "y"]
TernaryCommandFunction _ -> ["x", "y", "z"]
VariadicCommandFunction _ -> []
validateApp x@(AppPat f@(PrimitivePat arity _ _) args) =
case arity of
(NullaryPrimitive _) -> checkAppArity f p args >> Right x
(UnaryPrimitive _) -> checkAppArity f p args >> Right x
(BinaryPrimitive _) -> checkAppArity f p args >> Right x
(TernaryPrimitive _) -> checkAppArity f p args >> Right x
(QuaternaryPrimitive _) -> checkAppArity f p args >> Right x
(VariadicPrimitive _) -> Right x
where p = (\y -> XObj (Sym (SymPath [] y) Symbol) Nothing Nothing) <$> argnames
argnames =
case arity of
NullaryPrimitive _ -> []
UnaryPrimitive _ -> ["x"]
BinaryPrimitive _ -> ["x", "y"]
TernaryPrimitive _ -> ["x", "y", "z"]
QuaternaryPrimitive _ -> ["x", "y", "z", "w"]
VariadicPrimitive _ -> []
validateApp (AppPat invalid _) = Left (InvalidApplication invalid)
validateApp app = Left (GenericMalformed (XObj (Lst app) Nothing Nothing))
-- | Validation of (with module body) expressions
validateWith :: [XObj] -> Either Malformed [XObj]
validateWith x@(WithPat (SymPat _ _) _) = Right x
validateWith (WithPat invalid _) = Left (InvalidIdentifier invalid (InvalidWith invalid))
validateWith with = Left (GenericMalformed (XObj (Lst with) Nothing Nothing))
-- | Checks that the number of arguments passed to a function are correct.
checkAppArity :: XObj -> [XObj] -> [XObj] -> Either Malformed ()
checkAppArity xobj params args =
let la = length args
withRest = any ((":rest" ==) . getName) params
lp = length params - (if withRest then 2 else 0)
in if lp == la || (withRest && la >= lp)
then Right ()
else
if la < lp
then Left (InsufficientArguments xobj lp la params)
else Left (TooManyArguments xobj lp la args)
--------------------------------------------------------------------------------
-- Pattern Synonyms
pattern ArrPat :: XObj -> [XObj] -> XObj
pattern ArrPat self members <- self@(XObj (Arr members) _ _)
pattern ListPat :: XObj -> [XObj] -> XObj
pattern ListPat self members <- self@(XObj (Lst members) _ _)
pattern SymPat :: XObj -> SymPath -> XObj
pattern SymPat self path <- self@(XObj (Sym path _) _ _)
pattern UnqualifiedSymPat :: XObj -> SymPath -> XObj
pattern UnqualifiedSymPat self path <- self@(XObj (Sym path@(SymPath [] _) _) _ _)
pattern DefPat :: XObj -> XObj -> [XObj]
pattern DefPat name value <- [XObj Def _ _, name, value]
pattern DefnPat :: XObj -> XObj -> XObj -> [XObj]
pattern DefnPat name args body <- [XObj (Defn _) _ _, name, args, body]
pattern IfPat :: XObj -> XObj -> XObj -> [XObj]
pattern IfPat cond true false <- [XObj If _ _, cond, true, false]
pattern ThePat :: XObj -> XObj -> XObj -> [XObj]
pattern ThePat self t value <- [self@(XObj The _ _), t, value]
pattern LetPat :: XObj -> XObj -> [XObj]
pattern LetPat bindings body <- [XObj Let _ _, bindings, body]
pattern FnPat :: XObj -> XObj -> XObj -> [XObj]
pattern FnPat self args body <- [self@(XObj (Fn _ _) _ _), args, body]
pattern ClosurePat :: [XObj] -> XObj -> Context -> XObj
pattern ClosurePat params body ctx <- XObj (Closure (XObj (Lst [_, (ArrPat _ params), body]) _ _) (CCtx ctx)) _ _
pattern DynamicFnPat :: XObj -> [XObj] -> XObj -> XObj
pattern DynamicFnPat sym params body <- XObj (Lst [XObj Dynamic _ _, sym, (ArrPat _ params), body]) _ _
pattern MacroPat :: XObj -> [XObj] -> XObj -> XObj
pattern MacroPat sym params body <- XObj (Lst [XObj Macro _ _, sym, (ArrPat _ params), body]) _ _
pattern CommandPat :: CommandFunctionType -> XObj -> [XObj] -> XObj
pattern CommandPat arity sym params <- XObj (Lst [XObj (Command arity) _ _, sym, (ArrPat _ params)]) _ _
pattern PrimitivePat :: PrimitiveFunctionType -> XObj -> [XObj] -> XObj
pattern PrimitivePat arity sym params <- XObj (Lst [XObj (Primitive arity) _ _, sym, (ArrPat _ params)]) _ _
pattern AppPat :: XObj -> [XObj] -> [XObj]
pattern AppPat f args <- (f:args)
pattern WithPat :: XObj -> [XObj] -> [XObj]
pattern WithPat sym forms <- (XObj With _ _: sym: forms)
pattern DoPat :: [XObj] -> [XObj]
pattern DoPat forms <- (XObj Do _ _ : forms)
pattern WhilePat :: XObj -> XObj -> [XObj]
pattern WhilePat cond body <- [XObj While _ _, cond, body]
pattern SetPat :: XObj -> XObj -> [XObj]
pattern SetPat iden value <- [XObj SetBang _ _, iden, value]

View File

@ -1,5 +1,6 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE PatternSynonyms #-}
module Obj where
@ -213,6 +214,10 @@ isLiteral (XObj (Chr _) _ _) = True
isLiteral (XObj (Bol _) _ _) = True
isLiteral _ = False
isBool :: XObj -> Bool
isBool (XObj (Bol _) _ _) = True
isBool _ = False
isExternalFunction :: XObj -> Bool
isExternalFunction (XObj (Lst (XObj (External _) _ _ : _)) _ _) = True
isExternalFunction _ = False
@ -561,7 +566,7 @@ instance Show EvalError where
if null t
then ""
else
"\n\nTraceback:\n"
"\n\nTraceback:\n "
++ unlines (map (\x -> prettyUpTo 60 x ++ showInfo (xobjInfo x)) t)
-- | Get the type of an XObj as a string.
@ -1122,6 +1127,10 @@ trueXObj = XObj (Bol True) Nothing Nothing
falseXObj :: XObj
falseXObj = XObj (Bol False) Nothing Nothing
isList :: XObj -> Bool
isList (XObj (Lst _) _ _) = True
isList _ = False
-- | Applies an XObj transformation over all atomic XObjs in a form, retaining
-- list and array structure.
walk :: (XObj -> XObj) -> XObj -> XObj

View File

@ -1 +1,3 @@
multiarg-ref.carp:2:1 `ref` takes a single argument, but I got `(ref 1 2)`.
multiarg-ref.carp:2:2 [ERROR] ref expected 1 arguments but received 2.
The arguments 2 are not needed.