// Name changed from the guide to highlight that the outcome is complicated. static MalVal Quasiquote(MalVal ast) { if (!is_pair(ast)) { // Case 1. // If is_pair of ast is false: return a new list containing: a symbol named "quote" and ast. MalList qList = new MalList(); qList.Add(new MalSym("quote")); qList.Add(ast); //qList.Conj(new MalSym("quote"), ast); return(qList); } else { MalSequence astSeq = (MalSequence)ast; MalVal a0 = astSeq[0]; // Case 2: if the first element of ast is a symbol named "unquote": return the second element of ast. if (a0 is MalSym a0Sym && a0Sym.getName() == "unquote") { // (qq (uq form)) -> form return(astSeq[1]); }
//------------------ General utilities ------------------------------------------- // Generic error check for core fns. public static void CheckArgCount(MalList args, int expectedCount, string callerName) { if (args.Count() != expectedCount) { throw new MalEvalError("'" + callerName + "' expected " + expectedCount + " arg(s) but got " + args.Count()); } }
static MalVal eval_ast(MalVal ast, Env env) { // TODO - handle malHashMap. // Switch on the type of the ast. switch ((Object)ast) { case MalSym malSym: return(env.Get(malSym)); case MalList malList: // Return a new list that is the result of calling EVAL on each of the members of the list. MalList derivedList = new MalList(); for (int i = 0; i < malList.Count(); i++) { derivedList.Add(EVAL(malList.Get(i), env)); } return(derivedList); case MalVector malVec: // Return a new vector that is the result of calling EVAL on each of the members of the vector. MalVector derivedVec = new MalVector(); for (int i = 0; i < malVec.Count(); i++) { derivedVec.Add(EVAL(malVec.Get(i), env)); } return(derivedVec); case MalHashMap malHashMap: throw new MalInternalError("Can't evaluate a HashMap yet '" + malHashMap.ToString() + "'"); default: // It's not a symbol or a list. return(ast); } }
static MalVal EvalIf(MalList astList, Env env) { // 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. Eval the 'false' branch, if any. if (astList.Count() == 4) { return(EVAL(astList[3], env)); } else { return(new MalNil()); } } else { // Eval the 'true' branch. return(EVAL(astList[2], env)); } }
static protected MalNum ProcessNumbers(MalList args, NumericOp op) { double result = 0; // Initiate the calculation with the first arg. switch (args[0]) { case MalNum num: result = (double)num; // Handle the special case of (- 1). if (args.Count() == 1 && op == NumericOp.Subtract) { double negative = (double)num; return(new MalNum(-negative)); } break; default: // Should have already detected this but just in case. throw new MalInternalError("Non-number while calculating numbers: '" + args[0].ToString(true)); } double divTest = 0; // Now apply the op to the remaining args. for (var i = 1; i < args.Count(); i++) { switch (args[i]) { case MalNum num: switch (op) { case NumericOp.Plus: result += (double)num; break; case NumericOp.Multiply: result *= (double)num; break; case NumericOp.Subtract: result -= (double)num; break; case NumericOp.Divide: divTest = (double)num; if (i > 0 && divTest == 0) { throw new MalEvalError("Can't divide by zero"); } result /= divTest; break; } break; default: throw new MalInternalError("Non-number while calculating numbers: '" + args[i].ToString(true)); } } return(new MalNum(result)); }
public MalFunc(MalVal ast, Env e, MalList fparams, Func <MalList, MalVal> fn) { this.ast = ast; this.env = e; this.fparams = fparams; this.fn = fn; }
public MalFunc(MalVal ast, Mal.env.Env env, MalList fparams, Func <MalList, MalVal> fn) { this.fn = fn; this.ast = ast; this.env = env; this.fparams = fparams; }
public MalHashMap dissoc_BANG(MalList lst) { for (int i = 0; i < lst.size(); i++) { value.Remove(((MalString)lst[i]).getValue()); } return(this); }
public MalHashMap assoc_BANG(MalList lst) { for (int i = 0; i < lst.size(); i += 2) { value[((MalString)lst[i]).getValue()] = lst[i + 1]; } return(this); }
static MalVal EvalDo(MalList astList, Env env) { // Evaluate all elements of list using eval_ast, retun the final eval'd element. // I peeked at the reference to get this right although in my defence I added error checking MalList el = (MalList)eval_ast(astList.Rest(), env); return((el.Count() > 0) ? el[el.Count() - 1] : new MalNil()); }
static MalVal EvalFnDef(MalList astList, Env env) { // return new function closure. The body of that closure does the following // create a new env using Env (closed over from outer scope) as the outer param, the first param (second el of ast from outer scope) as the binds param // and the params to the closure as the exprs parameter. // Call EVAL on the second parameter (third list el of ast from outer scope_) using the new environment // Use the result as the return value of the closure. throw new MalInternalError("'fn' not implemented yet"); }
// EVAL is handled by two functions - EVAL which decides whether or not the ast is a special // form or not and eval_ast which evaluates the remaining symbols, lists, etc. // EVAL is tail-call optimised, as per Mal guide step 5 static MalVal eval_ast(MalVal ast, Env env) { // Switch on the type of the ast. switch ((Object)ast) { case MalSym malSym: return(env.Get(malSym)); case MalList malList: // Return a new list that is the result of calling EVAL on each of the members of the list. MalList derivedList = new MalList(); for (int i = 0; i < malList.Count(); i++) { derivedList.Add(EVAL(malList.Get(i), env)); } return(derivedList); case MalVector malVec: // Return a new vector that is the result of calling EVAL on each of the members of the vector. MalVector derivedVec = new MalVector(); for (int i = 0; i < malVec.Count(); i++) { derivedVec.Add(EVAL(malVec.Get(i), env)); } return(derivedVec); case MalHashMap malHashMap: // Return a new hash-map which consists of key-value pairs where the key is a key from the hash-map // and the value is the result of calling EVAL on the corresponding value. if (malHashMap.Count() % 2 != 0) { throw new MalEvalError("Hashmap requires an even number of elements forming key value pairs '" + malHashMap.ToString(true) + "'"); } MalHashMap derivedHashMap = new MalHashMap(); // Work through successive key value pairs. // Note - C#-Mal creates a dictionary, loads it and then passes the result to a Hashmap c'tor that takes a Dict. for (int i = 0; i < malHashMap.Count(); i += 2) { MalVal key = malHashMap.Get(i); if (key is MalString || key is MalKeyword) { derivedHashMap.Add(key); derivedHashMap.Add(EVAL(malHashMap.Get(i + 1), env)); } else { throw new MalEvalError("Expecting a keyword or string as a HashMap key but got '" + key.ToString(true) + "'"); } } return(derivedHashMap); default: // It's not a symbol or a list. return(ast); } }
public MalList Conj(params MalVal[] mvs) { // TODO. Is this the solution to the macro problem? Console.WriteLine("mvs: " + mvs.Length); MalList newList = new MalList(); for (var i = 0; i < mvs.Length; i++) { newList.Add(mvs[i]); } return(newList); }
static MalVal EvalDef(MalList astList, Env env) { // 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"); } MalVal result = EVAL(astList[2], env); env.Set(symbol.getName(), result); return(result); }
public void ReadForm_ReadsAList() { var input = "(+ 1 1)"; var reader = Reader.ReadStr(input); var result = new MalList(); result.Add(new MalAtom("+")); result.Add(new MalAtom("1")); result.Add(new MalAtom("1")); Reader.ReadForm(reader).Should().BeEquivalentTo(result); }
static MalVal eval_ast(MalVal ast, Dictionary <string, MalFunc> replEnv) { // TODO - handle vectors // Switch on the type of the ast. switch ((Object)ast) { case MalSym malSym: // Lookup the symbol in the environment and return the value or raise an error. string malsymstring = malSym.ToString(true); Console.WriteLine("eval_ast - searching for symbol: '" + malsymstring + "'"); if (replEnv.TryGetValue(malsymstring, out MalFunc func)) { Console.WriteLine("eval_ast - found function '" + func.ToString(true) + "'"); return(func); } else { throw new MalEvalError("Undefined symbol '" + malSym.ToString(true) + "'"); } case MalList malList: MalList derivedList = new MalList(); Console.WriteLine("eval_ast - handling MalList"); for (int i = 0; i < malList.Count(); i++) { derivedList.Add(EVAL(malList.Get(i), replEnv)); } return(derivedList); case MalVector malVec: MalVector derivedVec = new MalVector(); Console.WriteLine("eval_ast - handling MalVector"); // TODO - return a new list that is the result of calling EVAL on each of the members of the list. for (int i = 0; i < malVec.Count(); i++) { derivedVec.Add(EVAL(malVec.Get(i), replEnv)); } return(derivedVec); case MalHashMap malHashMap: throw new MalEvalError("INTERNAL - can't evaluate a HashMap yet '" + malHashMap.ToString() + "'"); default: // It's not a symbol or a list. return(ast); } }
static MalVal EvalLet(MalList astList, Env env) { // 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. // Confirm the structure of the let* form. 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. Env LetEnv = 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 is the value of the key symbol in Let's environment, E.g. (+ 2 3) MalVal val = EVAL(bindingsList[i + 1], LetEnv); // This expression can refer to earlier let bindings. // Now, store the new value in the environment. LetEnv.Set(bindingKey.getName(), val); // Note - this can silently redefine built-in functions e.g. if something like '+' is used. } // Using the populated Let* environment, evaluate and return the result form. // The Let* environment itself is discarded, unshadowing symbols in outer environments. return(EVAL(astList[2], LetEnv)); }
static MalVal EvalMalFunc(MalVal ast, Env env) { // Try to evaluate an ast as a MalFunc. MalList evaledList = (MalList)eval_ast(ast, env); MalVal listFirst = evaledList.First(); MalList listRest = evaledList.Rest(); if (listFirst is MalFunc func) { // Ast *is* a function, so call it. return(func.Apply(listRest)); } else { throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function."); } }
// Return the tail of the list. public MalList Rest() { if (MyElements.Count > 0) { // We return a MalList, not a List<MalVal>. MalList newList = new MalList(); foreach (var element in MyElements.GetRange(1, MyElements.Count - 1)) { newList.Add(element); } return(newList); } else { return(new MalList()); } }
// Analagous to the C# equivalent, shallow copy of a MalList. public MalList GetRange(int first, int last) { if (first > last) { throw new MalInternalError("GetRange - first '" + first + "' must be less than or equal to '" + last + "'"); } MalList newList = new MalList(); for (var i = 0; i < MyElements.Count; i++) { if (i >= first && i <= last) { newList.Add(MyElements[i]); } } return(newList); }
static MalVal EVAL(MalVal ast, Dictionary <string, MalFunc> replEnv) { switch ((Object)ast) { case MalList mList: // Ast is a list. // TODO - should this also do vectors and hashmaps? if (mList.Count() <= 0) { // Empty list, return unchanged. Console.WriteLine("EVAL - empty list: " + Printer.pr_str(mList, true)); return(ast); } else { // ast is a non-empty list, so evaluate it. Console.WriteLine("EVAL - non-empty list: " + Printer.pr_str(mList, true)); // Evaluate the List. MalList evaledList = (MalList)eval_ast(ast, replEnv); MalVal listFirst = evaledList.First(); MalList listRest = evaledList.Rest(); switch ((Object)listFirst) { case MalFunc func: Console.WriteLine("EVAL - List head is: '" + Printer.pr_str(listFirst, true) + "'. Rest elements: "); // Take the first item of the evaluated list and call it as function using the rest of the evaluated list as its arguments return(func.Apply(listRest)); //return null; default: throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function."); } } default: // If ast is not a list (e.g. a vector), return the result of calling eval_ast on it. return(eval_ast(ast, replEnv)); } }
// Non-destructively remove a target item from a supplied list. // This has CL-like semantics, do only removes the first target encountered. public MalSequence Remove(MalVal target) { // Create a list to which the non-target items will be copied. MalSequence RemoveSeq; bool removedP = false; // Instantiate a sequence of the same type. if (this is MalList) { RemoveSeq = new MalList(); } else if (this is MalVector) { RemoveSeq = new MalVector(); } else { throw new MalEvalError("remove expected list or vector but got '" + this.ToString() + "'"); } for (var i = 0; i < MyElements.Count; i++) { if (removedP) { // target already removed so always okay to keep this one. RemoveSeq.Add(MyElements[i]); } else { if (EQ(MyElements[i], target) == malTrue) { // This is the target to remove, so don't copy it. removedP = true; } else { RemoveSeq.Add(MyElements[i]); } } } return(RemoveSeq); }
public void ReadForm_ReadsNestedLists() { var input = "(+ 1 (+ 2 3) 4)"; var reader = Reader.ReadStr(input); var innerList = new MalList(); innerList.Add(new MalAtom("+")); innerList.Add(new MalAtom("2")); innerList.Add(new MalAtom("3")); var outerList = new MalList(); outerList.Add(new MalAtom("+")); outerList.Add(new MalAtom("1")); outerList.Add(innerList); outerList.Add(new MalAtom("4")); Reader.ReadForm(reader).Should().BeEquivalentTo(outerList); }
public Env(Env outer, MalList binds, MalList exprs) { // 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; if (binds.Count() != exprs.Count()) { throw new MalEvalError("Incorrect number of fn arguments?"); } // Bind (set) each element (symbol) to the respective elements of the exprs list for (var i = 0; i < binds.Count(); i++) { if (binds[i] is MalSym symbol) { Set(symbol, exprs[i]); } } }
// Name changed from the guide to highlight that the outcome is complicated. // TODO logic here is broken - see the above for the right if nesting. static MalVal ProcessQuasiquote(MalVal ast) { if (!is_pair(ast)) { // Case 1. // If is_pair of ast is false: return a new list containing: a symbol named "quote" and ast. MalList qList = new MalList(); qList.Add(new MalSym("quote")); qList.Add(ast); return(qList); } else { MalList astList = (MalList)ast; MalVal a0 = astList[0]; // Case 2: if the first element of ast is a symbol named "unquote": return the second element of ast. if (a0 is MalSym a0Sym && a0Sym.getName() == "unquote") { return(astList[1]); }
//------------------ Utilities for Numbers and numeric ops ------------------------------------------- // C#-Mal only allows integers and thus avoids a lot of complexity. I allow ints and floats // although the implementation is more complex. // Also unlike C#-Mal, numeric args can have arg lists of variable length, e.g. (+ 1 2 3). // Decide whether to use int or float arithmetic and check for non-numerics. Applies // float contaigon if an expression has a mix of ints and floats. protected static bool AllIntegers(MalList args) { bool allInts = true; for (var index = 0; index < args.Count(); index++) { switch (args[index]) { case MalInt i: break; case MalFloat f: allInts = false; // Don't return at this point in case there are lurking non-numbers. break; default: throw new MalEvalError("non-number in numeric expression: '" + args[index].ToString(true)); } } return(allInts); }
// Mixing floats and integers introduces loads of special cases. I decided to // move the things that are type-dependent into helper functions to keep the // numeric Builtins themselves cleaner. static protected MalInt ProcessIntegers(MalList args, NumericOp op) { int result = (int)(MalInt)args[0]; int divTest = 0; // Handle the special case of (- 1). if (args.Count() == 1 && op == NumericOp.Subtract) { return(new MalInt(-result)); } for (var i = 1; i < args.Count(); i++) { switch (op) { case NumericOp.Plus: result += (int)(MalInt)args[i]; break; case NumericOp.Multiply: result *= (int)(MalInt)args[i]; break; case NumericOp.Subtract: result -= (int)(MalInt)args[i]; break; case NumericOp.Divide: divTest = (int)(MalInt)args[i]; if (i > 0 && divTest == 0) { throw new MalEvalError("Can't divide by zero"); } result /= divTest; break; } } return(new MalInt(result)); }
// Call the stored func, passing it a list containing args, if any. public MalVal Apply(MalList args) { // Console.Write("applying function built-in = " + myCreatedByFn); return(fn(args)); }
public MalHashMap(MalList lst) { value = new Dictionary<String, MalVal>(); assoc_BANG(lst); }
public MalHashMap assoc_BANG(MalList lst) { for (int i=0; i<lst.size(); i+=2) { value[((MalString)lst[i]).getValue()] = lst[i+1]; } return this; }
public Mal.env.Env genEnv(MalList args) { return(new Mal.env.Env(env, fparams, args)); }
public MalVal apply(MalList args) { return(fn(args)); }
public MalHashMap dissoc_BANG(MalList lst) { for (int i=0; i<lst.size(); i++) { value.Remove(((MalString)lst[i]).getValue()); } return this; }
public MalFunc(MalVal ast, Mal.env.Env env, MalList fparams, Func<MalList, MalVal> fn) { this.fn = fn; this.ast = ast; this.env = env; this.fparams = fparams; }
public MalVal apply(MalList args) { return fn(args); }
public Mal.env.Env genEnv(MalList args) { return new Mal.env.Env(env, fparams, args); }