Example #1
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);
        }
Example #2
0
        static public JKLVal read_token(TokenQueue TQ)
        {
            // "If you have a problem and you think regex is the answer, now you have two problems!"
            // Unlike the referenceC#-Mal, read_token handles floats and badly-formed symbols.
            // In the Mal Guide this is called read_atom but I renamed it to avoid confusion
            // with Mal Atoms.
            string tokenToRead = TQ.Next();

            if (tokenToRead.Length <= 0)
            {
                // TODO  - this may stop comments being handled correctly.
                throw new JKLInternalError("Reader has returned empty string");
            }
            switch (tokenToRead[0])
            {
            case '+':
                if (tokenToRead.Length == 1)
                {
                    // Token is a solo '+', not the beginning of a number.
                    return(new JKLSym(tokenToRead));
                }
                // Skip the sign and extract a positive number;
                return(ParseNumber(tokenToRead.Substring(1), true));

            case '-':
                if (tokenToRead.Length == 1)
                {
                    // Token is a solo '-', not the beginning of a number.
                    return(new JKLSym(tokenToRead));
                }
                // Skip the sign and extract a negative number;
                return(ParseNumber(tokenToRead.Substring(1), false));

            case '.':
                // An initial '.' is only allowed at the start of a number, as in '.2'.
                return(ParseNumber(tokenToRead, true));

            case '\"':
                if (tokenToRead.EndsWith("\""))
                {
                    // Get rid of the quotes before storing the string. Seems right although
                    // I haven't confirmed by checking the reference version.
                    char[] charsToTrim = { '"' };
                    tokenToRead = tokenToRead.Trim(charsToTrim);
                    return(new JKLString(tokenToRead));
                }
                // TODO - never reaches this point. The reader regex seems to throw away '"' chars if there is only 1.
                throw new JKLParseError("String '" + tokenToRead + "' lacks a closing thingy");

            case ':':
                // Handle a keyword
                if (tokenToRead.Length == 1)
                {
                    // Can't have a solo colon.
                    throw new JKLParseError("':' must be followed by a keyword");
                }
                return(new JKLKeyword(tokenToRead));

            default:
                if (IsDigit(tokenToRead[0].ToString()))
                {
                    // Token seems to be an unsigned number.
                    return(ParseNumber(tokenToRead, true));
                }
                else if (tokenToRead == "nil")
                {
                    return(jklNil);
                }
                else if (tokenToRead == "true")
                {
                    return(jklTrue);
                }
                else if (tokenToRead == "false")
                {
                    return(jklFalse);
                }
                else
                {
                    // If here it is 'just' a symbol.
                    return(new JKLSym(tokenToRead));
                }
            }
            throw new JKLInternalError("Can't process '" + tokenToRead + "'");
        }
Example #3
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));
            }
        }
Example #4
0
        public List <Step> ParseTokens() // Due to the size of this method I am not using IEnumerable 'yield return' as it is hard to track nested return statements.
        {
            List <Step> EvaluationSteps = new List <Step>();

            while (tokQueue.More())                  // While more tokens in queue (returns bool)
            {
                Token nextTok = tokQueue.MoveNext(); // pop next out of TokenQueue

                if (nextTok.Type().Equals("identifier"))
                // All statements in our language begin with identifiers.
                // We do not know what we have at this point, so let's check the identifier to see which tokens should follow after.
                {
                    if (Syntax.IsType(nextTok.Value()))
                    // If it is a var type, e.g "int", "string" - if it is, this is a variable declaration ("int x = 0;")
                    {
                        /*
                         * EXPECTED PATTERN: varType varName = expr;
                         * e.g int x = 2 + y*10;
                         * e.g string testing = "Hello World!";
                         */

                        VarDeclare varDeclare = CaptureVarDeclare(nextTok.Value()); // Call method with argument storing the type of var being declared, e.g 'string'

                        EvaluationSteps.Add(varDeclare);                            // Add Event object to the overall list of 'Steps' for the Evaluator module
                    }
                    else if (nextTok.Value().ToLower().Equals("if"))
                    // Start of an if statement
                    {
                        /*
                         * EXPECTED PATTERN: if(operands) { codeblock }
                         * e.g if (x > 0) {
                         *      output(x);
                         *      x = 0;
                         *     }
                         */

                        IfStatement ifState = CaptureIfStatement(); // Capture all useful information of the following if statements

                        // We COULD have an else statement, so let's check the next token
                        // First check there are still MORE tokens to check to avoid out of range errors
                        // Then check it's an IDENTIFIER ('else')
                        if (tokQueue.More() && tokQueue.Next().Type().Equals("identifier") && tokQueue.Next().Value().Equals("else"))
                        {
                            // If next token is 'else' and an identifier
                            ElseStatement elseState = CaptureElseStatement();
                            EvaluationSteps.Add(ifState);
                            EvaluationSteps.Add(elseState);
                            // Add if state then else directly after (ordered list!)
                        }
                        else
                        {
                            EvaluationSteps.Add(ifState);  // if no 'else' statement exists just add the if statement
                        }
                    }

                    else if (nextTok.Value().ToLower().Equals("while"))
                    {
                        IfStatement template  = CaptureIfStatement(); // Trick the program to think it's capturing an if statement
                        WhileLoop   whileLoop = new WhileLoop(template.GetCBContents(), template.GetOp1(), template.GetOp2(), template.GetComparator());
                        // Reuse code from the if statement because while & if follow the exact same structure:
                        // while (condition) { codeblock }
                        // if (condition) { codeblock }
                        // We just captured an if statement 'template' then used the information it collected to create a while loop instead

                        EvaluationSteps.Add(whileLoop);
                    }


                    else if (GrammarTokenCheck(tokQueue.Next(), "("))
                    // This condition will also return true if it finds an if/while statement, so it is AFTER the check for those.
                    // As we're using else if, if the program didn't recognise a 'while' or 'if' statement, we will reach this check
                    // We can GUARANTEE now that this must be a function call as 'if(){}' and 'while(){}' have been ruled out
                    {
                        /*
                         * EXPECTED PATTERN: funcName(expr); // Can take any expression!
                         * e.g output("Testing");
                         * e.g output(1 + 23);
                         * e.g output(x);
                         */

                        tokQueue.MoveNext(); // Skip the '(' token
                        // Remember, nextTok still holds the value of the token before '('
                        // This is the name of our function ('funcName')

                        FuncCall funcCall = CaptureFunctionCall(nextTok.Value()); // Pass the function name, e.g 'output'
                        EvaluationSteps.Add(funcCall);
                    }
                    else if (GrammarTokenCheck(tokQueue.Next(), "=")) // .Next() is PEEK not POP.
                    // Check if the token AFTER this one is "="
                    {
                        /*
                         * EXPECTED PATTERN: varName = expr;
                         * e.g x = 2 + y*10;
                         * e.g testing = "Hello World!";
                         */

                        tokQueue.MoveNext(); // Skip the '=' token
                        // Remember, nextTok still holds the value of the token before the '='
                        // This is the name of our variable to change ('varName')

                        VarChange varChan = CaptureVarChange(nextTok.Value());
                        EvaluationSteps.Add(varChan);
                    }
                    else
                    {
                        throw new SyntaxError();
                    }
                    // If there is a rogue 'else' statement it will be caught in this
                    // Else statements are not 'looked' for on there own, they are only recognised when an if statement is found
                }
                else
                {
                    throw new SyntaxError();    // Statement doesn't begin with identifier - throw error.
                }
            }

            return(EvaluationSteps);
        }