//------------------ General utilities ------------------------------------------- // Generic error check for core fns. public static void CheckArgCount(JKLList args, int expectedCount, string callerName) { if (args.Count() != expectedCount) { throw new JKLEvalError("'" + callerName + "' expected " + expectedCount + " arg(s) but got " + args.Count()); } }
// 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]); }
// Analagous to the C# equivalent, shallow copy of a JKLList. public JKLList GetRange(int first, int last) { if (first > last) { throw new JKLInternalError("GetRange - first '" + first + "' must be less than or equal to '" + last + "'"); } JKLList newList = new JKLList(); for (var i = 0; i < MyElements.Count; i++) { if (i >= first && i <= last) { newList.Add(MyElements[i]); } } return(newList); }
// Return the tail of the list. public JKLList Rest() { if (MyElements.Count > 0) { // We return a JKLList, not a List<JKLVal>. JKLList newList = new JKLList(); foreach (var element in MyElements.GetRange(1, MyElements.Count - 1)) { newList.Add(element); } return(newList); } else { return(new JKLList()); } }
// 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 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)); } }
// Call the stored func, passing it a list containing args, if any. public JKLVal Apply(JKLList args) { // Console.Write("applying function built-in = " + myCreatedByFn); return(fn(args)); }