// Return the value of a target symbol in this or an outer scope. public MalVal Get(MalSym keySymbol) { Env e = Find(keySymbol); if (e == null) { switch (keySymbol.ToString(true)) { // Some symbols are only valid when used in a particular context. case "unquote": throw new MalEvalError("'unquote' used incorrectly (missing macro?)"); case "quasiquote": throw new MalEvalError("'quasiquote' used incorrectly (missing macro?)"); case "splice-unquote": throw new MalEvalError("'splice-unquote' used incorrectly (missing macro?)"); } //// If here the symbol simply hasn't been defined or is mis-spelt. throw new MalLookupError("Get - Symbol not found '" + keySymbol.ToString(true) + "'"); } else { if (e.data.TryGetValue(keySymbol.getName(), out MalVal value)) { return(value); } else { throw new MalInternalError("Get - Find successful but symbol retrieval failed '" + keySymbol.ToString(true) + "'"); } } }
public void Set(MalSym keySymbol, MalVal value) { // Takes a symbol key and a mal value and adds them to the environment. if (!data.TryAdd(keySymbol.getName(), value)) { // Symbol can shadow an equivalent in an outer scope but cannot be duped. throw new MalEvalError("Attempt to redefine '" + keySymbol.ToString(true) + "'"); } }
// Return the value of a target symbol in this or an outer scope. public MalVal Get(MalSym keySymbol) { Env e = Find(keySymbol); if (e != null && e.data.TryGetValue(keySymbol.getName(), out MalVal value)) { return(value); } throw new MalEvalError("Symbol not found '" + keySymbol.ToString(true) + "'"); }
public Env(Env outer, MalSym binds, MalSym exprs) { // TODO - these binds and exprs might not be malsyms // A key-MalVal dictionary. data = new Dictionary <string, MalVal>(); // The scope we are created in. Null is taken to mean the REPL itself. this.outer = outer; // Bind (set) each element (symbol) to the respective elements of the exprs list throw new MalInternalError("Env can't bind yet"); }
// Search for and return the environment (scope) that contains a target symbol. public Env Find(MalSym keySymbol) { if (data.ContainsKey(keySymbol.getName())) { // Symbol exists in the current scope. return(this); } else if (outer != null) { // Recurse to search for the symbol in an outer scope. return(outer.Find(keySymbol)); } else { // Symbol is not defined. return(null); } }
// Return the value of a target symbol in this or an outer scope. public MalVal Get(MalSym keySymbol) { Env e = Find(keySymbol); if (e != null && e.data.TryGetValue(keySymbol.getName(), out MalVal value)) { return(value); } switch (keySymbol.ToString(true)) { case "quote": throw new MalEvalError("'quote' used where expression expected"); case "quasiquote": throw new MalEvalError("'quasiquote' used where expression expected"); case "splice-unquote": throw new MalEvalError("'splice-unquote' used where expression expected"); default: throw new MalEvalError("Symbol not found '" + keySymbol.ToString(true) + "'"); } }
// 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); // Establish a gensym mechanism EVAL(READ("(def *gensym-counter* (atom 0))"), myEnv); EVAL(READ("(def gensym (fn [] (symbol (str \"G__\" (swap *gensym-counter* (fn [x] (+ 1 x)))))))"), myEnv); 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 (condvar (gensym)) `(let (~condvar ~(first xs)) (if ~condvar ~condvar (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); } }