// 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]); }
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)); } }
// // General functions // public static bool _equal_Q(MalVal a, MalVal b) { Type ota = a.GetType(), otb = b.GetType(); if (!((ota == otb) || (a is MalList && b is MalList))) { return false; } else { if (a is MalInt) { return ((MalInt)a).getValue() == ((MalInt)b).getValue(); } else if (a is MalSymbol) { return ((MalSymbol)a).getName() == ((MalSymbol)b).getName(); } else if (a is MalString) { return ((MalString)a).getValue() == ((MalString)b).getValue(); } else if (a is MalList) { if (((MalList)a).size() != ((MalList)b).size()) { return false; } for (int i=0; i<((MalList)a).size(); i++) { if (! _equal_Q(((MalList)a)[i], ((MalList)b)[i])) { return false; } } return true; } else { return a == b; } } }
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 MalFunc(MalVal ast, Env e, MalSequence fparams, Func <MalList, MalVal> fn) { this.ast = ast; this.env = e; this.fparams = fparams; this.fn = fn; IsCore = false; }
// 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 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) + "'"); } }
public MalVal Reset(MalVal newVal) { // some interesting error cases here, e.g. (reset a (list a)). if (newVal == this) { throw new MalEvalError("Can't reset atom '" + this.ToString(true) + "' to itself"); } myAtom = newVal; return(newVal); }
public bool Contains(MalVal target) { for (var i = 0; i < MyElements.Count; i++) { if (EQ(MyElements[i], target) == malTrue) { return(true); } } return(false); }
public MalAtom(MalVal atom) { if (atom == null) { throw new MalInternalError("Attempt to create null Atom"); } if (((object)atom is MalNil) || ((object)atom is MalTrue) || ((object)atom is MalNil)) { throw new MalEvalError("'" + atom.ToString(true) + "' cannot be an Atom"); } myAtom = atom; }
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); }
// The converse of read_str - return a string rep of a MAL object. public static string pr_str(MalVal ast, bool printReadably) { // Mal Guide says switch on the ast type, but we use virtuals instead. if (ast != null) { return(ast.ToString(printReadably)); } else { // The MAL guide doesn't do this check but it stops comment lines from crashing. return(""); } }
// The guts of the REPL. It extendsthe C#-Mal reference REPL // to handle (1) multi-line forms and (2) multi-form lines. public void RunREPL() { while (true) { try { // Read standard input. string line = ReadLine.Readline("JKL> "); // Exit if EOF or ctrl-Z. if (line == null) { break; } // Go round again if empty line (user hit return). if (line == "") { continue; } // Tokenise the input and load the tokens into a Reader object. MalReader.Reader rdr = new MalReader.Reader(MalReader.Tokenizer(line)); // Loop until all tokens on the line have been consumed. This handles lines // like (a b) (c d) - where Mal REPL would silently ignore (c d) while (rdr.Peek() != null) { // Parse input to create a Mal abstract syntax tree. The parser // attempts to reload the reader if it runs out of tokens mid form. MalVal ast = READMULTILINE(rdr); // Evaluate the ast and print the result. Console.WriteLine(PRINT(EVAL(ast, myEnv))); } } catch (MalParseError e) { Console.WriteLine(e.Message); } catch (MalEvalError e) { Console.WriteLine(e.Message); } catch (MalInternalError e) { Console.WriteLine(e.Message); } } }
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); } }
// // General functions // public static bool _equal_Q(MalVal a, MalVal b) { Type ota = a.GetType(), otb = b.GetType(); if (!((ota == otb) || (a is MalList && b is MalList))) { return(false); } else { if (a is MalInt) { return(((MalInt)a).getValue() == ((MalInt)b).getValue()); } else if (a is MalSymbol) { return(((MalSymbol)a).getName() == ((MalSymbol)b).getName()); } else if (a is MalString) { return(((MalString)a).getValue() == ((MalString)b).getValue()); } else if (a is MalList) { if (((MalList)a).size() != ((MalList)b).size()) { return(false); } for (int i = 0; i < ((MalList)a).size(); i++) { if (!_equal_Q(((MalList)a)[i], ((MalList)b)[i])) { return(false); } } return(true); } else { return(a == b); } } }
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."); } }
// -------------------- Quoting and macros --------------------------------------------- // Helper function for quasiquotes. Return true if the arg is a non-empty list. static Boolean is_pair(MalVal ast) { if (ast is MalList malList) { if (malList.Count() > 0) { return(true); } } if (ast is MalVector malVec) { if (malVec.Count() > 0) { return(true); } } return(false); }
// // General functions // public static bool _equal_Q(MalVal a, MalVal b) { Type ota = a.GetType(), otb = b.GetType(); if (!((ota == otb) || (a is MalList && b is MalList))) { return false; } else { if (a is MalInt) { return ((MalInt)a).getValue() == ((MalInt)b).getValue(); } else if (a is MalSymbol) { return ((MalSymbol)a).getName() == ((MalSymbol)b).getName(); } else if (a is MalString) { return ((MalString)a).getValue() == ((MalString)b).getValue(); } else if (a is MalList) { if (((MalList)a).size() != ((MalList)b).size()) { return false; } for (int i=0; i<((MalList)a).size(); i++) { if (! _equal_Q(((MalList)a)[i], ((MalList)b)[i])) { return false; } } return true; } else if (a is MalHashMap) { var akeys = ((MalHashMap)a).getValue().Keys; var bkeys = ((MalHashMap)b).getValue().Keys; if (akeys.Count != bkeys.Count) { return false; } foreach (var k in akeys) { if (!_equal_Q(((MalHashMap)a).getValue()[k], ((MalHashMap)b).getValue()[k])) { return false; } } return true; } else { return a == b; } } }
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)); } }
// Helper functions for EVAL ----------------------------------------------------------- // Helper function for quasiquotes. Return true if the arg is a non-empty list. static Boolean is_pair(MalVal ast) { // This name might make sense in the sense of a cons cell / dotted pair. if (ast is MalList malList) { if (malList.Count() > 0) { return(true); } } if (ast is MalVector malVec) { if (malVec.Count() > 0) { return(true); } } return(false); }
// Evaluate the ast in the supplied environment. // NOTE - I've taken off the '*' and '!' chars from the special forms. static MalVal EVAL(MalVal ast, Env env) { if (ast is MalList astList) { if (astList.Count() <= 0) { // Empty list, return unchanged. return(ast); } else { // ast is a non-empty list. [0] should be a special form or a function name. if (!(astList[0] is MalSym sym)) { // Something like ([ 1 2]) perhaps. throw new MalEvalError("Expected function name or special form but got '" + astList[0] + "'"); } switch (sym.getName()) { case "def": return(EvalDef(astList, env)); case "let": return(EvalLet(astList, env)); case "do": return(EvalDo(astList, env)); case "if": return(EvalIf(astList, env)); case "fn": return(EvalFnDef(astList, env)); default: // Ast isn't a special form, so it should be a function. return(EvalMalFunc(ast, env)); } } } 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)); } }
// 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); }
// 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]); }
// Read a MalSequence, checking that it starts and terminates correctly. // Named read_list to follow the ref, but has been genericized to handle vectors as well. static public MalSeqBase read_list(Reader reader, MalSeqBase sequence, char start, char end) { // Check that we are in fact at the start of a list. string token = reader.Next(); if (token[0] != start) { // Parse error - probably internal if the list code is correct. throw new MalInternalError("Sequence expected '" + start + "' but got: " + token); } // Use read_form to get the list's contents, accumulating them into the list. while (true) { token = reader.Peek(); if (token != null) { // We are in the list or at the end. if (token[0] == end) { // Reached valid end of list. Consume the end char. reader.Next(); // And we are done. break; } // Mutually recurse to read the next list element. MalVal newVal = read_form(reader); sequence.Add(newVal); } else { // The input has finished but the list hasn't. Try to get more input. reader.LoadMoreTokens(start, end); } } return(sequence); }
public MalException(string value) :base(value) { this.value = new MalString(value); }
String PRINT(MalVal exp) { return(Printer.pr_str(exp, true)); }
// Support the built-in '=' function. False until explicitly proven otherwise. // Should be used in preference to == for internal MalVal equality checks. public static MalVal EQ(MalVal a, MalVal b) { if (a.GetType() != b.GetType()) { // TODO - allow equality comparisons between ints and floats. return(malFalse); } // If here, they are of the same Mal type. Do they have the same value? switch ((object)a) { case MalSym aSym: if (aSym.getName() == ((MalSym)b).getName()) { return(malTrue); } break; case MalNum aNumk: if (aNumk.Unbox() == ((MalNum)b).Unbox()) { return(malTrue); } break; case MalString aString: if (aString.unbox() == ((MalString)b).unbox()) { return(malTrue); } break; case MalKeyword aKeyWord: if (aKeyWord.unbox() == ((MalKeyword)b).unbox()) { return(malTrue); } break; case MalNil aNil: if (aNil == ((MalNil)b)) { return(malTrue); } break; case MalTrue aTrue: if (aTrue == ((MalTrue)b)) { return(malTrue); } break; case MalFalse aFalse: if (aFalse == ((MalFalse)b)) { return(malTrue); } break; case MalSeqBase aSeq: // Sequences must be the same length, and each element must be the same. MalSeqBase bSeq = (MalSeqBase)b; if (aSeq.Count() != bSeq.Count()) { // They are not of equal length. return(malFalse); } for (var i = 0; i < aSeq.Count(); i++) { // At least one of the elements is not equal. if (EQ(aSeq[i], bSeq[i]) == malFalse) { return(malFalse); } } return(malTrue); case MalAtom aAtom: // The atoms must be the same object. Two atoms containing the same value are not equal. // TODO check whether this should be true if dereferencing them should be used instead. This isn't specified in the tests. if (aAtom == ((MalAtom)b)) { return(malTrue); } break; default: throw new MalInternalError("Can't yet compare '" + a.GetType() + "' with '" + b.GetType() + "'"); } return(malFalse); }
public MalAtom(MalVal value) { this.value = value; }
public MalVal setValue(MalVal value) { return this.value = value; }
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 MalMalError(MalVal exceptionVal) : base(exceptionVal.ToString(true)) { myException = exceptionVal; }
public MalVal setMeta(MalVal m) { meta = m; return this; }
public void Add(MalVal newVal) { MyElements.Add(newVal); }
public MalVal setValue(MalVal value) { return(this.value = value); }
//string Message; public MalException(MalVal value) { this.value = value; }