aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2022-10-05 14:28:00 +0300
committerJan Tuomi <jans.tuomi@gmail.com>2022-12-05 14:21:53 +0200
commit78fac664f39dc52818fb99264c258b822df7d5b9 (patch)
tree9b7187e15c4b2690df89e7558c610f20c99e9efd
parent77968899b90cd9afe2ed2668b290b66cb553ced2 (diff)
Refactor Env into LContext
-rw-r--r--app/Main.hs21
-rw-r--r--src/Interpreter.hs191
-rw-r--r--src/Utils.hs17
-rw-r--r--test/Spec.hs18
-rw-r--r--test/TestUtils.hs21
-rw-r--r--todo.md5
6 files changed, 160 insertions, 113 deletions
diff --git a/app/Main.hs b/app/Main.hs
index 45540aa..0aa66d7 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -23,21 +23,21 @@ parseArgs config args =
config { configUseREPL = True } rest
_ -> config
-repl :: Config -> Env -> InputT IO ()
-repl config env = do
+repl :: LState -> InputT IO ()
+repl ls = do
minput <- getInputLine "> "
case minput of
Nothing -> return ()
Just input -> do
- result <- lift $ runL config (runInlineScript "<repl>" env input)
+ result <- lift $ runL ls (runInlineScript "<repl>" input)
case result of
Left (LException mp ex) -> do
outputStrLn $ case mp of
Just p -> p ++ " error: " ++ ex
Nothing -> "error: " ++ ex
- repl config env
- Right (newEnv, _) -> do
- repl config newEnv
+ repl ls
+ Right (_, LState { stateEnv = newEnv }) -> do
+ repl ls { stateEnv = newEnv }
main :: IO ()
main = do
@@ -54,6 +54,7 @@ main = do
}
let config = parseArgs initialConfig args
+ ls = LState { stateConfig = config, stateEnv = builtinEnv }
if (configShowHelp config) then do
putStrLn $ "Usage: " ++ progName ++ " # to open REPL"
@@ -73,15 +74,15 @@ main = do
case (configScriptFileName config) of
Just scriptFileName -> do
- result <- runL config (runScriptFile builtinEnv scriptFileName)
+ result <- runL ls (runScriptFile scriptFileName)
case result of
Left (LException mp ex) -> case mp of
Just p -> putStrLn $ p ++ " error: " ++ ex
Nothing -> putStrLn $ "error: " ++ ex
- Right (evaledEnv, _) -> do
+ Right (_, LState { stateEnv = evaledEnv }) -> do
when (configUseREPL config) $
- runInputT defaultSettings (repl config evaledEnv)
+ runInputT defaultSettings (repl ls { stateEnv = evaledEnv })
Nothing -> do
putStrLn $ "Lang REPL"
putStrLn $ "Use CTRL+D to exit"
- runInputT defaultSettings (repl config builtinEnv)
+ runInputT defaultSettings (repl ls)
diff --git a/src/Interpreter.hs b/src/Interpreter.hs
index 37996f4..e649464 100644
--- a/src/Interpreter.hs
+++ b/src/Interpreter.hs
@@ -1,13 +1,14 @@
{-# LANGUAGE LambdaCase #-}
module Interpreter (
runInlineScript,
- runScriptFile
+ runScriptFile,
+ evaluate
) where
import qualified Data.Map as M
import qualified Data.List as L
import Data.Function ( on )
-import Control.Monad.Reader
+import Control.Monad.State
import Control.Monad.Except
import Utils
-- import Debug.Trace
@@ -26,6 +27,7 @@ _curryCall env (arg:rest) f = do
ASTFunction f' -> f' env arg
other -> throwL (astPos g) $ "cannot call value " ++ show other ++ " as a function"
+-- todo remove Env param, use State monad
curryCall :: Env -> [AST] -> LFunction -> LContext AST
curryCall env [] f = f env (makeNonsenseAST ASTUnit)
curryCall env args f = _curryCall env args f
@@ -53,11 +55,11 @@ foldSymValPairs ((sym, val):rest) body =
replacedBody = traverseAndReplace sym val body
in foldSymValPairs replacedRest replacedBody
-letArgsToSymValPairs :: Depth -> Env -> [AST] -> LContext (String, AST)
-letArgsToSymValPairs d env args =
+letArgsToSymValPairs :: Depth -> [AST] -> LContext (String, AST)
+letArgsToSymValPairs d args =
case args of
[AST { astNode = ASTSymbol symbol' }, value'] -> do
- (_, evaledValue) <- evaluate d env value'
+ evaledValue <- evaluate d value'
return (symbol', evaledValue)
[AST { astNode = ASTSymbol "lazy" }, AST { astNode = ASTSymbol symbol' }, value'] -> do
return (symbol', value')
@@ -72,12 +74,11 @@ defineUserFunction d AST { astNode = ASTSymbol param } exprs = return fn where
letSymValPairs <- letExprs
$> mapM (\case AST { astNode = ASTFunctionCall v } -> return $ drop 1 v
ast -> throwL (astPos ast) $ "unreachable: map letExprs, ast: " ++ show ast)
- .> fmap (mapM $ letArgsToSymValPairs d env) .> join
+ .> fmap (mapM $ letArgsToSymValPairs d) .> join
let body = head $ drop (length exprs - 1) replacedExprs
let newBody = traverseAndReplace param arg body
$> foldSymValPairs letSymValPairs
- (_, ret) <- evaluate d env newBody
- return ret
+ evaluate d newBody
defineUserFunction _ param exprs = throwL (astPos param)
$ "unreachable: defineUserFunction, param: " ++ show param ++ ", exprs: " ++ show exprs
@@ -99,8 +100,8 @@ defineUserFunctionWithLetExprs d (AST { astNode = ASTSymbol param }:rest) exprs
defineUserFunctionWithLetExprs _ (param:_) _ = throwL (astPos $ param)
$ "unreachable: defineUserFunctionWithLetExprs, param: " ++ show param
-evaluateFunctionDef :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateFunctionDef d env asts = do
+evaluateFunctionDef :: Depth -> [AST] -> LContext AST
+evaluateFunctionDef d asts = do
let defAst = head asts
args = tail asts
(params'', exprs) <- case args of
@@ -112,6 +113,9 @@ evaluateFunctionDef d env asts = do
AST { astNode = ASTVector params' } <- assertIsASTVector params''
params <- mapM assertIsASTSymbol params'
+ env <- getEnv
+ let isParamNameShadowing name = M.member name env
+
let shadowingParamM = L.find (astNode .> (\(ASTSymbol sym) -> sym) .> isParamNameShadowing) params
case shadowingParamM of
Just shadowingParam -> throwL (astPos shadowingParam)
@@ -126,14 +130,13 @@ evaluateFunctionDef d env asts = do
Nothing -> return ()
fn <- defineUserFunctionWithLetExprs d params exprs
- return $ (env, defAst { astNode = ASTFunction fn })
+ return $ defAst { astNode = ASTFunction fn }
where
isLetAST AST { astNode = ASTFunctionCall (AST { astNode = ASTSymbol "let!" }:_) } = True
isLetAST _ = False
- isParamNameShadowing name = M.member name env
-evaluateMatch :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateMatch d env asts = do
+evaluateMatch :: Depth -> [AST] -> LContext AST
+evaluateMatch d asts = do
let matchAst = head asts
args = tail asts
(actualExpr, rest) <- case args of
@@ -146,52 +149,55 @@ evaluateMatch d env asts = do
++ "- matching on expr: " ++ show actualExpr ++ "\n"
++ "- arguments: " ++ show rest)
- (_, evaledActual) <- evaluate d env actualExpr
+ evaledActual <- evaluate d actualExpr
ret <- matchPairs (actualExpr, evaledActual) pairs
- return (env, matchAst { astNode = astNode ret })
+ return $ matchAst { astNode = astNode ret }
where
matchPairs :: (AST, AST) -> [(AST, AST)] -> LContext AST
matchPairs (actualExpr, evaledActual) [] = throwL (astPos actualExpr)
$ "matching case not found when matching on expression: " ++ show actualExpr
++ " (actual value: " ++ show evaledActual ++ ")"
matchPairs (actualExpr, evaledActual) ((matcher, branch):restPairs) = do
- (_, evaledMatcher) <- evaluate d env matcher
+ evaledMatcher <- evaluate d matcher
if evaledActual == evaledMatcher
- then do
- (_, ret) <- evaluate d env branch
- return ret
+ then evaluate d branch
else matchPairs (actualExpr, evaledActual) restPairs
-evaluateLet :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateLet d env asts = do
+evaluateLet :: Depth -> [AST] -> LContext AST
+evaluateLet d asts = do
let letAst = head asts
args = tail asts
when (d > 1) $ throwL (astPos letAst) $ "let! can only be called on the top level or in a function definition"
- (symbol, value) <- letArgsToSymValPairs d env args
+ (symbol, value) <- letArgsToSymValPairs d args
+ env <- getEnv
when (M.member symbol env) $ throwL (astPos letAst) $ "symbol already defined: " ++ symbol
let newEnv = M.insert symbol value env
- return $ (newEnv, letAst { astNode = ASTUnit })
+ putEnv newEnv
+ return $ letAst { astNode = ASTUnit }
-evaluateEnv :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateEnv d env asts = do
+evaluateEnv :: Depth -> [AST] -> LContext AST
+evaluateEnv d asts = do
let envAst = head asts
when (d > 1) $ throwL (astPos envAst) $ "env! can only be called on the top level"
+
+ env <- getEnv
let pairs = M.assocs env
let longestKey = L.maximumBy (compare `on` (length . fst)) pairs $> fst
let pad s = s ++ take (length longestKey + 4 - length s) (L.repeat ' ')
let rows = pairs $> map (\(k, v) -> pad k ++ show v)
liftIO $ mapM_ putStrLn rows
- return (env, envAst { astNode = ASTUnit })
+ return $ envAst { astNode = ASTUnit }
-evaluateImport :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateImport d env asts = do
+evaluateImport :: Depth -> [AST] -> LContext AST
+evaluateImport d asts = do
let importAst = head asts
args = tail asts
when (d > 1) $ throwL (astPos importAst) $ "import! can only be called on the top level"
case args of
[AST { astNode = ASTSymbol qualifier }, AST { astNode = ASTString path }] -> do
- (evaledRawEnv, _) <- runScriptFile builtinEnv path
+ builtinState <- getBuiltinState
+ LState { stateEnv = evaledRawEnv } <- lift $ execStateT (runScriptFile path) builtinState
let exportsVecASTM = M.lookup "exports" evaledRawEnv
exportedEnv <- case exportsVecASTM of
Just (AST { astNode = ASTVector exportsVec }) -> do
@@ -204,9 +210,12 @@ evaluateImport d env asts = do
Nothing -> throwL (astPos importAst) $ "no exports vector defined in file: " ++ path
let nameMangled = M.mapKeys (\k -> qualifier ++ ":" ++ k) exportedEnv
- return $ (M.union env nameMangled, importAst { astNode = ASTUnit })
+ env <- getEnv
+ putEnv $ M.union env nameMangled
+ return $ importAst { astNode = ASTUnit }
[AST { astNode = ASTString path }] -> do
- (evaledRawEnv, _) <- runScriptFile builtinEnv path
+ builtinState <- getBuiltinState
+ LState { stateEnv = evaledRawEnv } <- lift $ execStateT (runScriptFile path) builtinState
let exportsVecASTM = M.lookup "exports" evaledRawEnv
exportedEnv <- case exportsVecASTM of
Just (AST { astNode = ASTVector exportsVec }) -> do
@@ -218,69 +227,96 @@ evaluateImport d env asts = do
Just ast -> throwL (astPos ast) $ "exports symbol set to non-symbol value: " ++ show ast
Nothing -> throwL (astPos importAst) $ "no exports vector defined in file: " ++ path
- return $ (M.union env exportedEnv, importAst { astNode = ASTUnit })
+ env <- getEnv
+ putEnv $ M.union env exportedEnv
+ return $ importAst { astNode = ASTUnit }
+
_ -> throwL (astPos importAst) $ "invalid arguments passed to import!: " ++ show args
-evaluateUserFunction :: Depth -> Env -> [AST] -> LContext (Env, AST)
-evaluateUserFunction d env children = do
+evaluateUserFunction :: Depth -> [AST] -> LContext AST
+evaluateUserFunction d children = do
let fnAst = head children
args = tail children
- (_, fnEvaled) <- evaluate d env fnAst
+ fnEvaled <- evaluate d fnAst
AST { astNode = (ASTFunction fn) } <- assertIsASTFunction fnEvaled
- evaledArgs' <- mapM (evaluate d env) args
- let evaledArgs = map snd evaledArgs'
- doubleEvaledArgs' <- mapM (evaluate d env) evaledArgs
- let doubleEvaledArgs = map snd doubleEvaledArgs'
+ evaledArgs <- mapM (evaluate d) args
+ doubleEvaledArgs <- mapM (evaluate d) evaledArgs
+
+ env <- getEnv
result <- curryCall env (reverse doubleEvaledArgs) fn
- -- maybe remove double eval here? can't remember why it was added
- return (env, fnAst { astNode = astNode result })
+ -- todo: maybe remove double eval here? can't remember why it was added
+ return $ fnAst { astNode = astNode result }
-evaluateSymbol :: Env -> AST -> LContext (Env, AST)
-evaluateSymbol env ast@AST { astNode = ASTSymbol sym } = do
+evaluateSymbol :: AST -> LContext AST
+evaluateSymbol ast@AST { astNode = ASTSymbol sym } = do
+ env <- getEnv
let val = M.lookup sym env
case val of
- Just ast' -> return (env, ast')
+ Just ast' -> return ast'
Nothing -> throwL (astPos ast) $ "symbol " ++ sym ++ " not defined in environment"
-evaluateSymbol _ ast = throwL (astPos ast) $ "unreachable: evaluateSymbol, ast: " ++ show ast
+evaluateSymbol ast = throwL (astPos ast) $ "unreachable: evaluateSymbol, ast: " ++ show ast
-evaluate :: Depth -> Env -> AST -> LContext (Env, AST)
-evaluate d env AST { astNode = fnc@(ASTFunctionCall args@(x:_)) } =
- do config <- ask
+evaluate :: Depth -> AST -> LContext AST
+evaluate d AST { astNode = fnc@(ASTFunctionCall args@(x:_)) } =
+ do config <- getConfig
when (configPrintCallStack config) $ liftIO $ putStrLn $ "fn call: " ++ show fnc
case astNode x of
-- remember to add these as reseved keywords in Builtins!
ASTSymbol "\\" ->
- evaluateFunctionDef (d + 1) env args
+ evaluateFunctionDef (d + 1) args
ASTSymbol "match" ->
- evaluateMatch (d + 1) env args
+ evaluateMatch (d + 1) args
ASTSymbol "let!" ->
- evaluateLet (d + 1) env args
+ evaluateLet (d + 1) args
ASTSymbol "env!" ->
- evaluateEnv (d + 1) env args
+ evaluateEnv (d + 1) args
ASTSymbol "import!" ->
- evaluateImport (d + 1) env args
+ evaluateImport (d + 1) args
_ ->
- evaluateUserFunction (d + 1) env args
-evaluate _ env ast@AST { astNode = (ASTSymbol _) } =
- evaluateSymbol env ast
-evaluate d env ast@AST { astNode = (ASTVector vec) } =
- do rets <- mapM (evaluate (d + 1) env) vec
- let vec' = map snd rets
- return $ (env, ast { astNode = ASTVector vec' })
-evaluate _ env other =
- return (env, other)
+ evaluateUserFunction (d + 1) args
+evaluate _ ast@AST { astNode = (ASTSymbol _) } =
+ evaluateSymbol ast
+evaluate d ast@AST { astNode = (ASTVector vec) } =
+ do rets <- mapM (evaluate (d + 1)) vec
+ return $ ast { astNode = ASTVector rets }
+evaluate _ other =
+ return other
-- LIB
-runScriptFile :: Env -> String -> LContext (Env, [AST])
-runScriptFile env fileName = do
+runScriptFile :: String -> LContext [AST]
+runScriptFile fileName = do
src <- liftIO $ readFile fileName
- runInlineScript fileName env src
+ runInlineScript fileName src
+
+getEnv :: LContext Env
+getEnv = do
+ s <- get
+ return $ stateEnv s
-runInlineScript :: String -> Env -> String -> LContext (Env, [AST])
-runInlineScript fileName env src = do
+getConfig :: LContext Config
+getConfig = do
+ s <- get
+ return $ stateConfig s
+
+putEnv :: Env -> LContext ()
+putEnv env = do
+ modify (\s -> s { stateEnv = env })
+
+insertEnv :: String -> AST -> LContext ()
+insertEnv k v = do
+ env <- getEnv
+ putEnv $ M.insert k v env
+
+getBuiltinState :: LContext LState
+getBuiltinState = do
+ config <- getConfig
+ return $ LState { stateConfig = config, stateEnv = builtinEnv }
+
+runInlineScript :: String -> String -> LContext [AST]
+runInlineScript fileName src = do
tokenized <- tokenize fileName src
- config <- ask
+ LState { stateConfig = config } <- get
when (configVerboseMode config) $ liftIO $ putStrLn $ "tokenized:\t\t" ++ show tokenized
parsed <- parse tokenized
@@ -288,16 +324,15 @@ runInlineScript fileName env src = do
let output = "parsed:\t\t\t" ++ (map show parsed $> L.intercalate "\n\t\t\t")
liftIO $ putStrLn output
- (newEnv, evaluated) <- foldEvaluate env parsed
-
+ evaluated <- foldEvaluate parsed
when (configPrintEvaled config) $ do
liftIO $ mapM_ putStrLn (map show evaluated)
- return (newEnv, evaluated)
+ return evaluated
where
- foldEvaluate :: Env -> [AST] -> LContext (Env, [AST])
- foldEvaluate accEnv [] = return (accEnv, [])
- foldEvaluate accEnv (ast:rest) = do
- (newAccEnv, newAst) <- evaluate 0 accEnv ast
- (retEnv, restEvaled) <- foldEvaluate newAccEnv rest
- return $ (retEnv, newAst : restEvaled)
+ foldEvaluate :: [AST] -> LContext [AST]
+ foldEvaluate [] = return []
+ foldEvaluate (ast:rest) = do
+ newAst <- evaluate 0 ast
+ restEvaled <- foldEvaluate rest
+ return $ newAst : restEvaled
diff --git a/src/Utils.hs b/src/Utils.hs
index 1e9afc5..6354a87 100644
--- a/src/Utils.hs
+++ b/src/Utils.hs
@@ -2,7 +2,7 @@
module Utils where
import Control.Monad.Except
-import Control.Monad.Reader
+import Control.Monad.State
import qualified Data.Map as M
import qualified Data.List as L
import qualified Data.Char as C
@@ -22,10 +22,17 @@ data Config = Config {
configUseREPL :: Bool
}
-type LContext a = ReaderT Config (ExceptT LException IO) a
+type Env = M.Map String AST
+
+data LState = LState {
+ stateConfig :: Config,
+ stateEnv :: Env
+}
-runL :: Config -> LContext a -> IO (Either LException a)
-runL config lc = runExceptT $ runReaderT lc config
+type LContext a = StateT LState (ExceptT LException IO) a
+
+runL :: LState -> LContext a -> IO (Either LException (a, LState))
+runL s lc = runExceptT $ (flip runStateT) s lc
data Token = Token {
tokenContent :: String,
@@ -40,8 +47,6 @@ instance (Eq Token) where
instance (Show Token) where
show token = show $ tokenContent token
-type Env = M.Map String AST
-
type LFunction = (Env -> AST -> LContext AST)
data ASTNode
diff --git a/test/Spec.hs b/test/Spec.hs
index 1b712a8..710e528 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -5,32 +5,32 @@ import qualified Data.Map as M
import Builtins
import Tokenizer ( tokenize )
import Parser ( parse )
-import Evaluator ( evaluate )
-import Lib ( runInlineScript )
+import Interpreter ( evaluate, runInlineScript )
import Utils
import TestUtils
tokenizeTests = testGroup "tokenize" [
- do got <- expectSuccessL $ tokenize "<test>" "(+ 1 (- 10 5))"
+ do (got, _) <- expectSuccessL M.empty $ tokenize "<test>" "(+ 1 (- 10 5))"
let expected = ["(", "+", "1", "(", "-", "10", "5", ")", ")"]
assertEqual "" (map tokenContent got) expected
]
parseTests = testGroup "parse" [
- do got <- expectSuccessL $ parse (map makeNonsenseToken ["(", "+", "1", "2", ")"])
+ do (got, _) <- expectSuccessL M.empty $ parse (map makeNonsenseToken ["(", "+", "1", "2", ")"])
let expected = [astFunctionCall
[ astSymbol "+", astInteger 1, astInteger 2 ]]
assertEqual "" got expected
- , do got <- expectErrorL $ parse (map makeNonsenseToken ["(", "+", "1", "2"])
+ , do got <- expectErrorL M.empty $ parse (map makeNonsenseToken ["(", "+", "1", "2"])
let expected = "unbalanced function call"
assertEqual "" got expected
]
evaluateTests = testGroup "evaluate" [
do let env = M.fromList [builtinAdd2] :: Env
- (gotEnv, gotAST) <- expectSuccessL $ evaluate 0 env (astFunctionCall
- [astSymbol "+", astInteger 1, astInteger 2])
+ (gotAST, LState { stateEnv = gotEnv }) <- expectSuccessL env $
+ evaluate 0 (astFunctionCall [astSymbol "+", astInteger 1, astInteger 2])
+
let expectedAST = astInteger 3
assertEqual "" gotAST expectedAST
assertEqual "" (M.keys gotEnv) (M.keys env)
@@ -39,7 +39,9 @@ evaluateTests = testGroup "evaluate" [
e2eTests = testGroup "e2e" [
do let env = M.fromList [builtinSubtract2] :: Env
let script1 = "(let! sub2 (\\[a b] (- a b)))\n(sub2 3 2)"
- (gotEnv, gotASTs) <- expectSuccessL $ runInlineScript "<test>" env script1
+ (gotASTs, LState { stateEnv = gotEnv }) <- expectSuccessL env $
+ runInlineScript "<test>" script1
+
let expectedEnvKeys = ["-", "sub2"]
assertEqual "" (M.keys gotEnv) expectedEnvKeys
assertEqual "" (last gotASTs) (astInteger 1)
diff --git a/test/TestUtils.hs b/test/TestUtils.hs
index e497ece..e505de2 100644
--- a/test/TestUtils.hs
+++ b/test/TestUtils.hs
@@ -9,25 +9,26 @@ testConfig = Config {
configVerboseMode = False,
configShowHelp = False,
configPrintEvaled = False,
- configPrintCallStack = False
+ configPrintCallStack = False,
+ configUseREPL = False
}
-testRunL :: LContext a -> IO (Either LException a)
-testRunL = runL testConfig
+testRunL :: Env -> LContext a -> IO (Either LException (a, LState))
+testRunL env = runL LState { stateConfig = testConfig, stateEnv = env }
-expectSuccessL :: LContext a -> IO a
-expectSuccessL lc =
- do res <- testRunL lc
+expectSuccessL :: Env -> LContext a -> IO (a, LState)
+expectSuccessL env lc =
+ do res <- testRunL env lc
case res of
Left (LException _ err) -> error $ "unexpected error: " ++ err
Right val -> return val
-expectErrorL :: Show a => LContext a -> IO String
-expectErrorL lc =
- do res <- testRunL lc
+expectErrorL :: Show a => Env -> LContext a -> IO String
+expectErrorL env lc =
+ do res <- testRunL env lc
case res of
Left (LException _ err) -> return err
- Right val -> error $ "unexpected success: " ++ show val
+ Right (val, _) -> error $ "unexpected success: " ++ show val
ast :: ASTNode -> AST
ast node = AST { astNode = node }
diff --git a/todo.md b/todo.md
index 9f61e8f..5f6594c 100644
--- a/todo.md
+++ b/todo.md
@@ -2,8 +2,11 @@
In order of priority
+- Add proper module system
+ - Make exports a return value of runXXX functions
+- Add "stack traces" (somehow)
+- Write function let expressions properly using lookups
- Write tests!
-- Add import function with support for qualified imports
- Come up with a name for the language
- Add auto import for standard library (std) and a flag to disable auto import
- Add effects system (see `examples/effects-concept.lisp`)