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; }
// 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(""); } }
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."); } }
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)); } }
public override string ToString(bool printReadably) { return("<Atom>:" + myAtom.ToString(printReadably)); }
public MalMalError(MalVal exceptionVal) : base(exceptionVal.ToString(true)) { myException = exceptionVal; }
// 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); } }
// Evaluate the ast in the supplied environment. This is a bit of a monster but // has been left as per the guide to simplify TCO in step 5. // 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": // 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); 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.getName(), val); } // Using the populated Let environment, evaluate and return the result form. return(EVAL(astList[2], TempLetEnv)); case "do": // Evaluate all elements of list using eval_ast, retun the final eval'd element. MalList el = (MalList)eval_ast(astList.Rest(), env); return((el.Count() > 0) ? el[el.Count() - 1] : malNil); 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. Eval the 'false' branch, if any. if (astList.Count() == 4) { return(EVAL(astList[3], env)); } else { return(malNil); } } else { // Eval the 'true' branch. return(EVAL(astList[2], env)); } case "fn": // e.g. (def a (fn (i) (* i 2))) if (!(astList[1] is MalList a1f)) { throw new MalEvalError("Expected arg list but got: '" + astList[1].ToString(true) + "'"); } // Note - these functions can create Mal runtime errors - e.g. if undefined symbols are used. MalVal a2f = astList[2]; Env cur_env = env; return(new MalFunc(a2f, env, a1f, args => EVAL(a2f, new Env(cur_env, a1f, args)))); default: // Ast isn't a special form, so it should be a function. // Try to evaluate an ast as a MalFunc. MalList evaledList = (MalList)eval_ast(ast, env); MalVal listFirst = evaledList.First(); if (listFirst is MalFunc func) { // Ast *is* a function, so call it. return(func.Apply(evaledList.Rest())); } else { throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function."); } } } } 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)); } }