// 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); }
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 + "'"); }
// 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 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); }