// Name changed from the guide to highlight that the outcome is complicated. static JKLVal Quasiquote(JKLVal 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. JKLList qList = new JKLList(); qList.Add(new JKLSym("quote")); qList.Add(ast); //qList.Conj(new JKLSym("quote"), ast); return(qList); } else { JKLSequence astSeq = (JKLSequence)ast; JKLVal 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 JKLSym a0Sym && a0Sym.getName() == "unquote") { // (qq (uq form)) -> form return(astSeq[1]); }
public JKLFunc(JKLVal ast, Env e, JKLSequence fparams, Func <JKLList, JKLVal> fn) { this.ast = ast; this.env = e; this.fparams = fparams; this.fn = fn; IsCore = false; }
public void Set(JKLSym keySymbol, JKLVal value) { // Takes a symbol key and a JKL 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 JKLEvalError("Attempt to redefine '" + keySymbol.ToString(true) + "'"); } }
public JKLVal Reset(JKLVal newVal) { // some interesting error cases here, e.g. (reset a (list a)). if (newVal == this) { throw new JKLEvalError("Can't reset atom '" + this.ToString(true) + "' to itself"); } myAtom = newVal; return(newVal); }
public JKLFunc(JKLVal ast, Env e, JKLSequence fparams, Func <JKLList, JKLVal> fn, bool isMacro, JKLVal meta) { this.ast = ast; this.env = e; this.fparams = fparams; this.fn = fn; this.isMacro = isMacro; this.meta = meta; IsCore = false; }
public bool Contains(JKLVal target) { for (var i = 0; i < MyElements.Count; i++) { if (EQ(MyElements[i], target) == jklTrue) { return(true); } } return(false); }
public JKLAtom(JKLVal atom) { if (atom == null) { throw new JKLInternalError("Attempt to create null Atom"); } if (((object)atom is JKLNil) || ((object)atom is JKLTrue) || ((object)atom is JKLNil)) { throw new JKLEvalError("'" + 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(JKLVal 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(""); } }
// Helper functions for EVAL ----------------------------------------------------------- // Helper function for quasiquotes. Return true if the arg is a non-empty list. static Boolean is_pair(JKLVal ast) { // This name might make sense in the sense of a cons cell / dotted pair. if (ast is JKLList jklList) { if (jklList.Count() > 0) { return(true); } } if (ast is JKLVector jklVec) { if (jklVec.Count() > 0) { return(true); } } return(false); }
// Hashmap equality helper function. // True if every key / value pair in A is also in B, even if the keys are in a different order. // E.g. The following should be true // (= {:a 1 :b 2 :c {:x 10 :y 11}} {:c {:y 11 :x 10} :a 1 :b 2}) // TODO - check what happens when shadow keys exist. private static bool _innerHashMapEQ(JKLHashMap a, JKLHashMap b) { if (a.Count() != b.Count()) { // The hashmaps are not of equal length. return(false); } if (a.Count() % 2 != 0) { // Check that the hashmap has an even number of entries to avoid crashing the // equality check code. throw new JKLEvalError("hashmap does not contain even key/value pairs '" + a.ToString(true) + "'"); // A stronger check would test that the keys are in fact keys but this isn't essential // for the equality check to work and in any case should have already been tested. } // Work through hashmap A, looking for matching key value pairs in hashmap B. for (int i = 0; i < a.Count(); i += 2) { // Get the next key value pair from hashmap A. JKLVal aKey = a.Get(i); JKLVal aVal = a.Get(i + 1); // Check that the key from A is also in B and retrieve its value in B. if (_InnerHashmapGet(b, aKey, "_innerHashMapEQ?", out JKLVal bVal)) { // Are the vals the same? if (!(EQ(aVal, bVal) == jklTrue)) { // ... no, the value from B is different. return(false); } // implicit 'continue' here: - check the remaining values. } else { // The key from A isn't in B. return(false); } } // If here, the two hashmaps have the same key value pairs. return(true); }
// Non-destructively remove a target item from a supplied list. // This has CL-like semantics, do only removes the first target encountered. public JKLSequence Remove(JKLVal target) { // Create a list to which the non-target items will be copied. JKLSequence RemoveSeq; bool removedP = false; // Instantiate a sequence of the same type. if (this is JKLList) { RemoveSeq = new JKLList(); } else if (this is JKLVector) { RemoveSeq = new JKLVector(); } else { throw new JKLEvalError("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) == jklTrue) { // This is the target to remove, so don't copy it. removedP = true; } else { RemoveSeq.Add(MyElements[i]); } } } return(RemoveSeq); }
// Read a JKLSequence, 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 JKLSeqBase read_list(TokenQueue TQ, JKLSeqBase sequence, char start, char end) { // Check that we are in fact at the start of a list. string token = TQ.Next(); if (token[0] != start) { // Parse error - probably internal if the list code is correct. throw new JKLInternalError("Sequence expected '" + start + "' but got: " + token); } // Use read_form to get the list's contents, accumulating them into the list. while (true) { token = TQ.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. TQ.Next(); // And we are done. break; } // Mutually recurse to read the next list element. JKLVal newVal = read_form(TQ); sequence.Add(newVal); } else { // The input has finished but the list hasn't. Try to get more input. TQ.LoadMoreTokens(start, end); } } return(sequence); }
// Read a JKLVal form - which is either an atom or a sequence. static public JKLVal read_form(TokenQueue TQ) { if (TQ.Peek() == null) { // Reader is empty - caused by a comment line in the input. return(null); } else if (TQ.Peek().StartsWith('(')) { // Create a new List and read it's body. return(read_list(TQ, new JKLList(), '(', ')')); } else if (TQ.Peek().StartsWith('[')) { // Create a new Vector and read it's body. return(read_list(TQ, new JKLVector(), '[', ']')); } else if (TQ.Peek().StartsWith('{')) { // Create a new HashMap and read it's body. EVAL checks it has valid key val pairs. return(read_list(TQ, new JKLHashMap(), '{', '}')); } else if (TQ.Peek().StartsWith(')') || TQ.Peek().StartsWith(']') || TQ.Peek().StartsWith('}')) { // A sequence close character that doesn't match a start. // This correctly handles a case like [1 ( 2 ] 3). throw new JKLParseError("Expecting sequence or atom but got '" + TQ.Peek() + "'"); } else if (TQ.Peek().StartsWith('&')) { // Reader macro. We have '&atomName'. Convert this into (deref atomName); string varArgAtom = TQ.Peek(); if (varArgAtom.Length == 1) { // Treat a solo '&' as a varargs symbol, TQ.Next(); return(jklVarArgsChar); } else { throw new JKLParseError("'&' can't start a symbol name: '" + varArgAtom.ToString() + "'"); } } else if (TQ.Peek().StartsWith('@')) { TQ.Next(); // Build a deref form. JKLList derefForm = new JKLList(); derefForm.Add(new JKLSym("deref")); derefForm.Add(read_form(TQ)); return(derefForm); } else if (TQ.Peek().StartsWith('\'')) { // Return a list containing a quote symbol and the quoted form. TQ.Next(); JKLList quoteForm = new JKLList(); quoteForm.Add(new JKLSym("quote")); quoteForm.Add(read_form(TQ)); return(quoteForm); } else if (TQ.Peek().StartsWith('`')) { // Return a list containing a quasiquote symbol and the quasiquoted form. TQ.Next(); JKLList quasiquoteForm = new JKLList(); quasiquoteForm.Add(new JKLSym("quasiquote")); quasiquoteForm.Add(read_form(TQ)); return(quasiquoteForm); } else if (TQ.Peek().StartsWith("~@")) { // Return a list containing a splice-unquote symbol and the next form. // Dammit! I'd missed the '~' here and spent several days wondering why (or ...) didn't work. TQ.Next(); JKLList quasiquoteForm = new JKLList(); quasiquoteForm.Add(new JKLSym("splice-unquote")); quasiquoteForm.Add(read_form(TQ)); return(quasiquoteForm); } else if (TQ.Peek().StartsWith('~')) { // Return a list containing an unquote symbol and the next form. TQ.Next(); JKLList quasiquoteForm = new JKLList(); quasiquoteForm.Add(new JKLSym("unquote")); quasiquoteForm.Add(read_form(TQ)); return(quasiquoteForm); } else if (TQ.Peek().StartsWith('^')) { // Return a new list that contains the symbol "with-meta" and the result of reading the // next next form (2nd argument) (read_form) and the next form (1st argument) in that order TQ.Next(); JKLList withMetaForm = new JKLList(); withMetaForm.Add(new JKLSym("with-meta")); JKLVal firstArg = read_form(TQ); JKLVal secondArg = read_form(TQ); withMetaForm.Add(secondArg); withMetaForm.Add(firstArg); return(withMetaForm); } else { // This isn't a list so parse it as an atom. return(read_token(TQ)); } }
public JKLHostedLispError(JKLVal exceptionVal) : base(exceptionVal.ToString(true)) { myException = exceptionVal; }
public void Add(JKLVal newVal) { MyElements.Add(newVal); }
// Support the built-in '=' function. False until explicitly proven otherwise. // Should be used in preference to == for internal JKLVal equality checks. public static JKLVal EQ(JKLVal a, JKLVal b) { if (a.GetType() != b.GetType()) { // TODO - allow equality comparisons between ints and floats. // TODO - allow equality comparisons between empty lists and vectors. return(jklFalse); } // If here, they are of the same Mal type. Do they have the same value? switch ((object)a) { case JKLSym aSym: if (aSym.getName() == ((JKLSym)b).getName()) { return(jklTrue); } break; case JKLNum aNumk: if (aNumk.Unbox() == ((JKLNum)b).Unbox()) { return(jklTrue); } break; case JKLString aString: if (aString.unbox() == ((JKLString)b).unbox()) { return(jklTrue); } break; case JKLKeyword aKeyWord: if (aKeyWord.unbox() == ((JKLKeyword)b).unbox()) { return(jklTrue); } break; case JKLNil aNil: if (aNil == ((JKLNil)b)) { return(jklTrue); } break; case JKLTrue aTrue: if (aTrue == ((JKLTrue)b)) { return(jklTrue); } break; case JKLFalse aFalse: if (aFalse == ((JKLFalse)b)) { return(jklTrue); } break; case JKLHashMap ahashMap: if (_innerHashMapEQ(ahashMap, (JKLHashMap)b)) { return(jklTrue); } else { return(jklFalse); } case JKLSeqBase aSeq: // Sequences must be the same length, and each element must be the same. JKLSeqBase bSeq = (JKLSeqBase)b; if (aSeq.Count() != bSeq.Count()) { // They are not of equal length. return(jklFalse); } for (var i = 0; i < aSeq.Count(); i++) { // Now check that the elements are equal. if (EQ(aSeq[i], bSeq[i]) == jklFalse) { // At least one of the elements is not equal. return(jklFalse); } } return(jklTrue); case JKLAtom 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 == ((JKLAtom)b)) { return(jklTrue); } break; default: throw new JKLInternalError("Can't yet compare '" + a.GetType() + "' with '" + b.GetType() + "'"); } return(jklFalse); }