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); }
public void Evaluate(List <Step> evaluationSteps) { for (int index = 0; index < evaluationSteps.Count; index++) { Step evalStep = evaluationSteps[index]; // .Type() can only be "VAR_DECLARE", "VAR_CHANGE", "FUNC_CALL", "IF_STATEMENT", "WHILE_LOOP" // It could also be "ELSE_STATEMENT", but we should only check for that DIRECTLY after an IF_STATEMENT if (evalStep.Type().Equals("IF_STATEMENT")) { // Evaluate if statement - contains OPERAND1, OPERAND2, COMPARISON, codeBlockContents IfStatement ifState = (IfStatement)evalStep; // Cast as we know it is now an IfStatement obj bool conditionResult = CompareExpressions(ifState.GetOp1(), ifState.GetOp2(), ifState.GetComparator()); bool hasElse = index + 1 < evaluationSteps.Count && evaluationSteps[index + 1].Type().Equals("ELSE_STATEMENT"); // No chance of index out of range error as set to False before reaching it if (conditionResult) // If the 'IfStatement' condition is TRUE { Evaluate(ifState.GetCBContents()); // 'run' the contents of the if statement - this is RECURSIVE if (hasElse) { evaluationSteps.RemoveAt(index + 1); } // If we have an ELSE_STATEMENT after this, we need to remove it as the IF_STATEMENT has triggered (therefore the ELSE will not be triggered). } else if (hasElse) { // If the CONDITION is FALSE and the next Step obj is an ELSE_STATEMENT type ElseStatement elseState = (ElseStatement)evaluationSteps[index + 1]; // Cast to else Evaluate(elseState.GetCBContents()); // 'run' the contents of the else (RECURSION) evaluationSteps.RemoveAt(index + 1); // Remove ELSE_STATEMENT as we have used it and do not want to go over it again. } } else if (evalStep.Type().Equals("WHILE_LOOP")) { WhileLoop whileLoop = (WhileLoop)evalStep; // Similar to if statement evaluation though no need to set a 'condition' variable because that condition may change // Basically just reusing the C# while loop with the template of the Interpreted one while (CompareExpressions(whileLoop.GetOp1(), whileLoop.GetOp2(), whileLoop.GetComparator())) { // While the condition is true, evaluate code inside Evaluate(whileLoop.GetCBContents()); } } else if (evalStep.Type().Equals("VAR_DECLARE")) // Declare a variable in the variableScope { VarDeclare varDecl = (VarDeclare)evalStep; // Cast as we know it's a VarDeclare obj if (variableScope.ContainsKey(varDecl.GetName())) { throw new DeclareError(); } // If scope already has a variable that name, you cannot redeclare it as it already exists. // Potential endpoint if variable exists - entire program will stop (crash). Token varExpr = ResolveExpression(varDecl.Value()); if (!varExpr.Type().Equals(varDecl.GetVarType())) { throw new TypeError(); } // Value of variable does not match type with declared one. e.g 'int x = "Hello";' variableScope.Add(varDecl.GetName(), varExpr); // Type of variable can be found out by the .Type() of the key's Token. // e.g 'int x = 1 + 2;' // if we want to find variable 'x' type, we find variableScope[x].Type() which will return 'number', with variableScope[x].Value() being '3' } else if (evalStep.Type().Equals("VAR_CHANGE")) // Change a pre-existing variable { VarChange varChan = (VarChange)evalStep; // Cast as we know it is a VarChange obj if (!variableScope.ContainsKey(varChan.GetName())) { throw new ReferenceError(); } // If variable is NOT in the variableScope then we cannot change it as it doesn't exist. // Potential endpoint for program crash string varType = variableScope[varChan.GetName()].Type(); Token newValue = ResolveExpression(varChan.Value()); if (!varType.Equals(newValue.Type())) { throw new TypeError(); } // If the new value of the variable is not the right type, then crash. // Potential endpoint // e.g int x = 0; x = "hi"; will cause this error variableScope[varChan.GetName()] = newValue; // Assign new value (Token) } else if (evalStep.Type().Equals("FUNC_CALL")) // Call a function { FuncCall functionCall = (FuncCall)evalStep; // Cast as we know it is a FuncCall obj now if (!functionCall.GetName().Equals("inputstr") && !functionCall.GetName().Equals("inputint")) // If NOT calling 'input' function { CallFunction(functionCall.GetName(), ResolveExpression(functionCall.GetArguments())); // Call function with name and *resolved* list of arguments // Resolve function always outputs a single token which is the result of an expression (list of tokens) being evaluated } else // SPECIAL CASE: Calling inputStr or inputInt functions indicates that the 'argument' is NOT an expression to be resolved, but rather a variable name to store input value in. // This means functionCall.Argumnets() will only have 1 token: { CallFunction(functionCall.GetName(), functionCall.GetArguments()[0]); // Pass in first value in Arguments as there only should be one - the variable to be input to } } else { throw new SyntaxError(); // Unrecognised Step, crash program. } } }