// Setup the REPL. public MalRepl() { // Load built-in functions into the initial eval environment. try { myEnv = new Env(null); // Load core functions. foreach (var thing in MalNameSpace) { MalSym mSym = new MalSym(thing.Key); if (myEnv.Find(mSym) == null) { myEnv.Set(mSym, thing.Value); } else { // The namespace itself may already have thrown an error but if not. throw new MalInternalError("Attempt to refine symbol: '" + mSym.ToString(true) + "' with '" + thing.Value.ToString(true) + "'"); } } // Load the special eval core function. We have to do it here to access the REPL env. myEnv.Set(new MalSym("eval"), new MalFunc(args => { return(EVAL(args[0], myEnv)); })); // some convenient test files // "F:\Mal-development\mal-tests\mal-step6-test-01.txt" // "F:\Mal-development\mal-tests\mal-code-01.txt" // (load-file "F:\Mal-development\mal-tests\mal-code-01.txt") // (eval (read-string (slurp "F:\Mal-development\mal-tests\mal-code-01.txt"))) // Add 'core' functions defined using Mal itself. EVAL(READ("(def not (fn (a) (if a false true)))"), myEnv); // TODO -number of brackets is wrong and returns nil. EVAL(READ("(def load-file (fn (f) (eval (read-string (str \"(do \" (slurp f) \"))\")))))"), myEnv); EVAL(READ("(defmacro cond (fn (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"), myEnv); EVAL(READ("(defmacro or (fn (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"), myEnv); } catch (System.TypeInitializationException e) { // Typically happens if there is a duplicate symbol in the namespace list Console.WriteLine("Unrecoverable error: " + e.InnerException.Message); } catch (MalLookupError e) { Console.WriteLine(e.Message); } catch (MalEvalError e) { Console.WriteLine(e.Message); } catch (MalParseError e) { Console.WriteLine(e.Message); } }
public Env(Env outer) { this.outer = outer; }
static Value EVAL(Value arg, Env env) { for (;;) { arg = MACROEXPAND(arg, env); if (arg is List lst) { if (lst.IsEmpty()) { return(arg); } Value val = lst.First(); if (val == Reader.Def) { if (lst.Count() != 3) { throw new MalException(Reader.Def.Name + " - syntax error"); } val = lst.Nth(1); if (val is Symbol sym) { val = EVAL(lst.Nth(2), env); if (val is Closure cls && cls.IsMacro) { cls = cls.Clone(null) as Closure; cls.IsMacro = false; val = cls; } env.Set(sym, val); return(val); } else { throw new MalException(Reader.Def.Name + " - symbol expected"); } } else if (val == Reader.Let) { if (lst.Count() != 3) { throw new MalException(Reader.Let.Name + " - syntax error"); } Env newEnv = new Env(env); Value val_1 = lst.Nth(1); if (val_1 is Sequence syms && syms.Count() % 2 == 0) { int cnt = syms.Count(); for (int i = 0; i < cnt; i += 2) { if (syms.Nth(i) is Symbol sym) { newEnv.Set(sym, EVAL(syms.Nth(i + 1), newEnv)); } else { throw new MalException(Reader.Let.Name + " - bindings - symbol is expected"); } } arg = lst.Nth(2); env = newEnv; continue; } else { throw new MalException(Reader.Let.Name + " - bad bindings"); } } else if (val == Reader.If) { int cnt = lst.Count(); if (cnt != 3 && cnt != 4) { throw new MalException(Reader.If.Name + " - syntax error"); } Value tst = EVAL(lst.Nth(1), env); if (tst == null || tst == Reader.Nil || tst == Reader.False) { if (cnt == 3) { return(Reader.Nil); } arg = lst.Nth(3); continue; } else { arg = lst.Nth(2); continue; } } else if (val == Reader.Do) { if (lst.Count() < 2) { throw new MalException(Reader.Do.Name + " - at least one argument is required"); } int cnt = lst.Count() - 1; for (int i = 1; i < cnt; i++) { EVAL(lst.Nth(i), env); } arg = lst.Nth(cnt); continue; } else if (val == Reader.Fn) { if (lst.Count() != 3) { throw new MalException(Reader.Fn.Name + " - syntax error"); } if (lst.Nth(1) is Sequence seq) { int cnt = seq.Count(); for (int i = 0; i < cnt; i++) { if (!(seq.Nth(i) is Symbol)) { throw new MalException(Reader.Fn.Name + " - symbol is expected for argument"); } } } else { throw new MalException(Reader.Fn.Name + " - expected sequence of arguments"); } if (lst.Nth(2) == null) { throw new MalException(Reader.Fn.Name + " - function body is expected"); } return(new Closure(lst.Nth(1) as Sequence, lst.Nth(2), env)); } else if (val == Reader.Quote) { if (lst.Count() != 2) { throw new MalException(Reader.Quote.Name + " - syntax error"); } return(lst.Nth(1)); } else if (val == Reader.Quasiquote) { if (lst.Count() != 2) { throw new MalException(Reader.Quasiquote.Name + " - syntax error"); } return(QQuote(lst.Nth(1), env)); } else if (val == Reader.Defmacro) { if (lst.Count() != 3) { throw new MalException(Reader.Defmacro.Name + " - syntax error"); } val = lst.Nth(1); if (val is Symbol sym) { val = EVAL(lst.Nth(2), env); if (val is Closure cls) { cls = cls.Clone(null) as Closure; cls.IsMacro = true; env.Set(sym, cls); return(val); } else { throw new MalException(Reader.Defmacro.Name + " - closure is expected"); } } else { throw new MalException(Reader.Defmacro.Name + " - symbol expected"); } } else if (val == Reader.Macroexpand) { if (lst.Count() != 2) { throw new MalException(Reader.Macroexpand.Name + " - syntax error"); } return(MACROEXPAND(lst.Nth(1), env)); } else if (val == Reader.Try) { int cnt = lst.Count(); if (cnt != 2 && cnt != 3) { throw new MalException(Reader.Try.Name + " - syntax error"); } Symbol var = null; Value exp = null; if (cnt == 3) { if (lst.Nth(2) is List clst && clst.Count() == 3) { if (clst.Nth(1) is Symbol sym) { var = sym; } else { throw new MalException(Reader.Catch.Name + " - symbol is expected"); } exp = clst.Nth(2); } else { throw new MalException(Reader.Catch.Name + " - syntax error"); } } Value res; try { res = EVAL(lst.Nth(1), env); } catch (MalException ex) { if (var != null && exp != null) { Env newEnv = new Env(env); if (ex.Val != null) { newEnv.Set(var, ex.Val); } else { newEnv.Set(var, new Str(ex.Message)); } res = EVAL(exp, newEnv); } else { throw ex; } } return(res); }
static int Main(string[] args) { Env env = CreateTopLevelEnv(); List <Value> malArgs = new List <Value>(); for (int i = 1; i < args.Length; i++) { malArgs.Add(new Str(args[i])); } env.Set(Reader.AddSymbol("*ARGV*") as Symbol, new List(malArgs)); Rep("(try* (load-file \".malrc\"))", env); if (args.Length == 0) { return(Repl(env)); } #if NATIVE_LOAD_FILE FileInfo fi = new FileInfo(args[0]); if (fi.Exists) { using (Stream stream = fi.OpenRead()) LoadStream(stream, env); } else { Console.Error.WriteLine("ERROR: unable open file '{0}'", args[0]); return(1); } #else List loaderArgs = new List(new List <Value>() { new Str(args[0]) }); try { var val = env.Get(Reader.Load_file); if (val is Closure cls) { EVAL(cls.Body, cls.CreateEnv(loaderArgs)); } else if (val is Func_Native fn) { fn.Apply(loaderArgs); } else { throw new MalException("unknown function to evaluate file"); } } catch (MalException ex) { Console.Error.WriteLine("ERROR: {0}", ex.Message); return(1); } catch (MalQuitExc) { } #endif return(0); }
// Evaluate the ast in the supplied environment. This is a bit of a monster but // has been left as per the guide to simplify Tail Call Optimisation (TCO) static MalVal EVAL(MalVal InitialAst, Env IntialEnv) { // The ast and env to be EVAL'd. Initially set to those passed to EVAL but // may be updated if there is a TCO loop. MalVal ast = InitialAst; Env env = IntialEnv; // Some eval cases will return out of the function, others keep looping (as per TCO) with // an updated ast / Env. The alternative is to call EVAL recursively, which has simpler // logic but which can blow up the stack. while (true) { if (ast is MalList astList) { if (astList.Count() <= 0) { // Empty list, return unchanged. return(ast); } else { // I used to check astList[0] was a symbol but this dissallows forms whose first el is an anon (fn..) special form. string symbolZero = astList[0] is MalSym ? ((MalSym)astList[0]).getName() : "__<*fn*>__"; switch (symbolZero) { case "def": // Evaluate all elements of list using eval_ast, retun the final eval'd element. // Should be something like (def a b). Set() symbol a to be the result of evaluating b. if (astList.Count() != 3 || !(astList[1] is MalSym symbol)) { throw new MalEvalError("'def' should be followed by a symbol and a value"); } // Must add the result of the EVAL to the environment so can't TCO loop here. MalVal result = EVAL(astList[2], env); env.Set(symbol, result); return(result); case "let": // Let syntax is (let <bindings-list> <result>), e.g. (let (p ( + 2 3) q (+ 2 p)) (+ p q)). // Bindings can refer to earlier bindings in the new let environment or to symbols // in the outer environment. Let defs hide hide same-named symbols in the outer scope. if (astList.Count() != 3) { throw new MalEvalError("'let' should have two arguments (bindings-list and result), but instead had " + (astList.Count() - 1)); } // Extract the first parameter - the bindings list. E.g. (p (+ 2 3) q (+ 2 p)) if (!(astList[1] is MalList bindingsList)) { throw new MalEvalError("'let' should be followed by a non-empty bindings list and a result form"); } if (bindingsList.Count() <= 1 || bindingsList.Count() % 2 != 0) { throw new MalEvalError("'let' bindings list should have an even number of entries"); } // Create a new Env - the scope of the Let form. It's discarded when done. Env TempLetEnv = new Env(env); // Process each pair of entries in the bindings list. for (int i = 0; i < bindingsList.Count(); i += 2) { // The first element should be a 'key' symbol. E.g. 'p'. if (!(bindingsList[i] is MalSym bindingKey)) { throw new MalEvalError("'let' expected symbol but got: '" + bindingsList[i].ToString(true) + "'"); } // The second element (e.g. (+ 2 3)) is the value of the key symbol in Let's environment MalVal val = EVAL(bindingsList[i + 1], TempLetEnv); // Store the new value in the environment. TempLetEnv.Set(bindingKey, val); } // TCO loop instead of EVAL(astList[2], TempLetEnv) ast = astList[2]; env = TempLetEnv; continue; case "do": // Something like (do <form> <form>) MalList formsToDo = astList.Rest(); int formsToDoCount = formsToDo.Count(); if (formsToDoCount == 0) { // Empty (do ) return(malNil); } else { // EVAL all forms in the (do ) body, up to but not including the last one. eval_ast(formsToDo.GetRange(0, formsToDoCount - 1), env); // EVAL the last form in the (do ) body via TCO loop, keeping the same env. ast = formsToDo[formsToDoCount - 1]; // TCO loop. continue; } case "if": // If has the syntax (if <cond> <true-branch> <optional-false-branch>) if (astList.Count() < 3 || astList.Count() > 4) { throw new MalEvalError("'if' should have a condition, true branch and optional false branch"); } // Evaluate the Cond part of the if. MalVal cond = EVAL(astList[1], env); if (cond is MalNil || cond is MalFalse) { // Cond is nil or false. if (astList.Count() == 4) { // Eval the 'false' branch via TCO loop. ast = astList[3]; continue; } else { // No 'false' branch. return(malNil); } } else { // Eval the 'true' branch via TCO loop. ast = astList[2]; continue; } case "fn": // e.g. (def a (fn (i) (* i 2))) or (def b (fn () (prn 1) (prn 2))) if (astList.Count() < 3) { throw new MalEvalError("fn must have an arg list and at least one body form"); } if (!(astList[1] is MalList FnArgList)) { // The 2nd element must be an arg list (possibly empty). throw new MalEvalError("Expected arg list but got: '" + astList[1].ToString(true) + "'"); } MalVal FnBodyExprs; // Unlike the Mal reference, this handles fns that have multiple expr forms. if (astList.Count() == 3) { FnBodyExprs = astList[2]; } else { // Either I've misunderstood something or the Reference doesn't handle some functions correctly. FnBodyExprs = astList.GetRange(2, astList.Count() - 1); } Env cur_env = env; // This is different from C#-Mal but their version doesn't work. return(new MalFunc(FnBodyExprs, env, FnArgList, args => EVAL(FnBodyExprs, new Env(cur_env, FnArgList, args)))); default: // Ast isn't a special form, so try to evaluate it as a function. MalList evaledList = (MalList)eval_ast(ast, env); MalVal listFirst = evaledList.First(); if (listFirst is MalFunc func) { if (func.IsCore) { // Can't TCO a core function so directly apply func. return(func.Apply(evaledList.Rest())); } else { // Core function. // The ast is the one stored by the func. ast = func.ast; // Create a new env using func's env and params as the outer and binds arguments // and the rest of the current ast as the exprs argument. env = new Env(func.env, func.fparams, evaledList.Rest()); // and now 'apply' via the TCO loop. continue; } } else { return(listFirst); } } } } else { // If ast is not a list (e.g. a vector), return the result of calling eval_ast on it. return(eval_ast(ast, env)); } } }
static Value EVAL(Value arg, Env env) { for (;;) { if (arg is List lst) { if (lst.IsEmpty()) { return(arg); } Value val = lst.First(); if (val == Reader.Def) { if (lst.Count() != 3) { throw new MalException(Reader.Def.Name + " - syntax error"); } val = lst.Nth(1); if (val is Symbol sym) { val = EVAL(lst.Nth(2), env); env.Set(sym, val); return(val); } else { throw new MalException(Reader.Def.Name + " - symbol expected"); } } else if (val == Reader.Let) { if (lst.Count() != 3) { throw new MalException(Reader.Let.Name + " - syntax error"); } Env newEnv = new Env(env); Value val_1 = lst.Nth(1); if (val_1 is Sequence syms && syms.Count() % 2 == 0) { int cnt = syms.Count(); for (int i = 0; i < cnt; i += 2) { if (syms.Nth(i) is Symbol sym) { newEnv.Set(sym, EVAL(syms.Nth(i + 1), newEnv)); } else { throw new MalException(Reader.Let.Name + " - bindings - symbol is expected"); } } arg = lst.Nth(2); env = newEnv; continue; } else { throw new MalException(Reader.Let.Name + " - bad bindings"); } } else if (val == Reader.If) { int cnt = lst.Count(); if (cnt != 3 && cnt != 4) { throw new MalException(Reader.If.Name + " - syntax error"); } Value tst = EVAL(lst.Nth(1), env); if (tst == null || tst == Reader.Nil || tst == Reader.False) { if (cnt == 3) { return(Reader.Nil); } arg = lst.Nth(3); continue; } else { arg = lst.Nth(2); continue; } } else if (val == Reader.Do) { if (lst.Count() < 2) { throw new MalException(Reader.Do.Name + " - at least one argument is required"); } int cnt = lst.Count() - 1; for (int i = 1; i < cnt; i++) { EVAL(lst.Nth(i), env); } arg = lst.Nth(cnt); continue; } else if (val == Reader.Fn) { if (lst.Count() != 3) { throw new MalException(Reader.Fn.Name + " - syntax error"); } if (lst.Nth(1) is Sequence seq) { int cnt = seq.Count(); for (int i = 0; i < cnt; i++) { if (!(seq.Nth(i) is Symbol)) { throw new MalException(Reader.Fn.Name + " - symbol is expected for argument"); } } } else { throw new MalException(Reader.Fn.Name + " - expected sequence of arguments"); } if (lst.Nth(2) == null) { throw new MalException(Reader.Fn.Name + " - function body is expected"); } return(new Closure(lst.Nth(1) as Sequence, lst.Nth(2), env)); } else { Value ast = Eval_ast(arg, env); val = (ast as List).First(); if (val is Func_Native fn) { return(fn.Apply((ast as List).Rest())); } else if (val is Closure cls) { arg = cls.Body; env = cls.CreateEnv((ast as List).Rest()); continue; } else { throw new MalException("function is expected"); } } }