Esempio n. 1
0
        // 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]);
                }
Esempio n. 2
0
 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;
 }
Esempio n. 3
0
 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) + "'");
     }
 }
Esempio n. 4
0
 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);
 }
Esempio n. 5
0
 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;
 }
Esempio n. 6
0
 public bool Contains(JKLVal target)
 {
     for (var i = 0; i < MyElements.Count; i++)
     {
         if (EQ(MyElements[i], target) == jklTrue)
         {
             return(true);
         }
     }
     return(false);
 }
Esempio n. 7
0
 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;
 }
Esempio n. 8
0
 // 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("");
     }
 }
Esempio n. 9
0
        // 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);
        }
Esempio n. 10
0
            // 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);
            }
Esempio n. 11
0
            // 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);
            }
Esempio n. 12
0
        // 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);
        }
Esempio n. 13
0
        // 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));
            }
        }
Esempio n. 14
0
 public JKLHostedLispError(JKLVal exceptionVal) : base(exceptionVal.ToString(true))
 {
     myException = exceptionVal;
 }
Esempio n. 15
0
 public void Add(JKLVal newVal)
 {
     MyElements.Add(newVal);
 }
Esempio n. 16
0
            // 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);
            }