/// <summary> /// Parses an if statement. /// </summary> /// <param name="group">The group to parse.</param> /// <returns> /// If a statement or block of code is executed, then the result of that code is returned, otherwise the /// boolean result of the logical expression is returned. A TException is returned when there is an error. /// </returns> TType ParseIfGroup(Group group) { /* BNF for if statement: * <if-statement> ::= 'if' <condition> ',' ( <statements> | <block-no-end> 'else' <statements> ) * <block-no-end> ::= <new-line> (<statement> <new-line>)* * The 'if' is assumed to have already been checked for */ if (group.Count < 2) return new TException(this, "Statement could not be evaluated", "if statement must be given a condition"); int commaIndex = group.IndexOf(","); if (commaIndex < 0) return new TException(this, "if statment invalid", "comma required after condition"); Group conditionGroup = new Group(null); conditionGroup.AddRange(group.GetRange(1, commaIndex - 1)); TType value = ParseGroup(conditionGroup); if (value is TException) return value; TBoolean result = value as TBoolean; if (result == null) { bool success = false; TVariable variable = value as TVariable; if (variable != null) { result = variable.Value as TBoolean; if (result != null) success = true; } if (!success) return new TException(this, "Condition does not evaluate to a boolean value", "yes or no"); } if (group.Count > commaIndex + 1) // If there is no block after the 'if' <condition> ',' { // If there is an else and it's at the end of the line, get a block for the else int elseIndex = group.IndexOf("else", commaIndex + 1); TBlock elseBlock = null; if (elseIndex == group.Count - 1) { TException exception; elseBlock = new TBlock(this, out exception, false); if (exception != null) return exception; } if (result.Value) { // Execute the code between the comma and the end of the statement or the else (if any). // If there isn't anything to execute, then a block is created (near the end of this method) Group statementGroup = new Group(null); if (elseIndex < 0) statementGroup.AddRange(group.GetRange(commaIndex + 1, group.Count - (commaIndex + 1))); else statementGroup.AddRange(group.GetRange(commaIndex + 1, elseIndex - (commaIndex + 1))); if (statementGroup.Count > 0) return ParseGroup(statementGroup); } else if (elseIndex >= 0) { if (elseBlock == null) { Group statementGroup = new Group(null); statementGroup.AddRange(group.GetRange(elseIndex + 1, group.Count - (elseIndex + 1))); if (statementGroup.Count > 0) return ParseGroup(statementGroup); } else { bool exitFromFunction, breakUsed; return elseBlock.Execute(this, out exitFromFunction, out breakUsed); } } else return result; } { // This code is executed if there is a block after the 'if' <condition> ',' instead of a single statement TException exception; TBlock block = new TBlock(this, out exception, true); if (exception != null) return exception; bool exitFromFunction, breakUsed; if (result.Value) return block.Execute(this, out exitFromFunction, out breakUsed, true); if (block.HasElse()) return block.Execute(this, out exitFromFunction, out breakUsed, false); } return result; }
/// <summary> /// Parses an assignment operation. /// </summary> /// <param name="group">The group to parse.</param> /// <returns>The assigned variable or function on success, otherwise a TException.</returns> TType ParseAssignmentGroup(Group group) { /* BNF for assignment: * <assignment> ::= 'let' ( <variable-assignment> | <function-declaration> ) * <var-assignment> ::= <identifier> '=' <expression> * <func-declaration> ::= <identifier> '(' { <parameters> } ')' '=' <statements> * <parameters> ::= <identifier> { ',' <identifier> }* * <statements> ::= <block> | <statement> * <block> ::= <new-line> (<statement> <new-line>)* 'end' * You can probably guess what <identifier>, <statement> and <new-line> are * The 'let' is assumed to have already been checked for */ int equalsIndex = group.IndexOf("="); if (equalsIndex < 0) return new TException(this, "Variable or function could not be assigned a value"); // Could be assigning a dereferenced variable TException exception = ParseReferencesOfGroup(group, equalsIndex); if (exception != null) return exception; if (group.Count == 1) return new TException(this, "Could not assign variable", "no variable name given"); string variableName = group[1] as string; TVariable existingVariable = null; if (variableName == null) { exception = new TException(this, "Could not assign variable", "invalid variable name given"); // Check if an existing variable or function is being assigned Group groupToParse = group[1] as Group; TType value = group[1] as TType; if (groupToParse != null) value = ParseGroup(groupToParse); if (value == null) return exception; TVariable variable = value as TVariable; if (variable != null) existingVariable = variable; else { TFunction function = value as TFunction; if (function != null) variableName = function.Name; else return exception; } } if (group.Count == 2) return new TException(this, "Variable could not be assigned a value"); string assignmentOperator = group[2] as string; if (assignmentOperator == null) // Now we assume that we're dealing with a function declaration { Group paramGroup = group[2] as Group; if (paramGroup == null) // The user probably just wanted to assign a variable but made a typo return new TException(this, "Variable could not be assigned a value", "value to assign to variable must be given"); // Get the identifiers of all the parameters within the brackets, keeping strict watch on comma usage TParameterList paramList = new TParameterList(); bool commaExpected = false; for (int i = 0; i < paramGroup.Count; ++i) { if (commaExpected && (i == paramGroup.Count - 1)) return new TException(this, "Parameters could not be parsed", "last parameter missing"); string paramName = paramGroup[i] as string; if (commaExpected && (paramName != ",")) paramName = null; if (paramName == null) return new TException(this, "Parameters could not be parsed", "invalid parameter name given"); if (!commaExpected) paramList.Add(paramName); commaExpected = !commaExpected; } exception = new TException(this, "Function could not be given a body", "function body must be given"); if (group.Count == 3) return exception; assignmentOperator = group[3] as string; if (assignmentOperator == null) return exception; else if (assignmentOperator != "=") return exception; TFunction function; if (group.Count == 4) // statement is just 'let <i><params> =', so get a block { TBlock block = new TBlock(this, out exception, false); if (exception != null) return exception; function = new TFunction(variableName ?? existingVariable.Identifier, block, paramList.ParameterNames, null); } else // Create a single line function { Group funcBody = new Group(null); funcBody.AddRange(group.GetRange(4, group.Count - 4)); function = new TFunction(variableName ?? existingVariable.Identifier, funcBody.ToString(), paramList.ParameterNames, null); } exception = TFunction.AddFunction(this, function); if (exception != null) return exception; return function; } { // Assume that we're dealing with a variable assigment exception = new TException(this, "Variable could not be assigned a value", "value to assign to variable must be given"); if (assignmentOperator != "=") return exception; if (group.Count == 3) return exception; // Parse the expression on the right hand side of the assigment operator, and if the result is a // TVariable, then get it's value (we don't want to assign the TVariable itself to the new TVariable) Group valueGroup = new Group(null); valueGroup.AddRange(group.GetRange(3, group.Count - 3)); TType value = ParseGroup(valueGroup); if (value is TException) return value; TVariable variable = value as TVariable; if (variable != null) value = variable.Value; // Make sure we don't get any circular references; they cause stack overflows when outputting them variable = existingVariable ?? Stack.FindVariable(variableName); if (value == variable) return new TException(this, "Illegal assignment attempted", "variables cannot reference themselves"); TVariable circularRefCheckVar = value as TVariable; while (circularRefCheckVar != null) { TVariable variableValue = circularRefCheckVar.Value as TVariable; if (variableValue != null) { if (variableValue == variable) return new TException(this, "Illegal assignment attempted", "circular reference detected"); else circularRefCheckVar = variableValue; } else circularRefCheckVar = null; } if (variable == null) // If the variable doesn't exist already, add it to the stack variable dictionary { variable = new TVariable(variableName, value); Stack.AddVariable(variable); } else variable.Value = value; return variable; } }
/// <summary> /// Parses and puts into effect an interpreter directive. /// </summary> /// <param name="group">The group containing the directive.</param> /// <returns>TNil on success, otherwise a TException.</returns> TType ParseDirectiveGroup(Group group) { if (group.Count < 2) return new TException(this, "No directive given"); string directive = group[1] as string; if (directive == null) return new TException(this, "No directive given"); switch (directive) { case "STRICT": // Toggles strict mode. Usage: '#STRICT yes', '#STRICT no' or '#STRICT <condition>' if (group.Count > 2) { Group conditionGroup = new Group(null); conditionGroup.AddRange(group.GetRange(2, group.Count - 2)); TType value = ParseGroup(conditionGroup); if (value is TException) return value; TBoolean result = value as TBoolean; if (result == null) return new TException(this, "Directive 'STRICT' could not be used", "invalid argument; use yes, no or a condition"); Strict = result.Value; if (Strict) System.Console.WriteLine("Interpreter running in strict mode"); else System.Console.WriteLine("Interpreter not running in strict mode"); return TNil.Instance; } else return new TException(this, "Directive 'STRICT' could not be used", "invalid argument (none given); use yes, no or a condition"); } return new TException(this, "Could not use directive", "directive '" + directive + "' not recognised"); }
/// <summary> /// Parses a while loop statement. /// </summary> /// <param name="group">The group to parse.</param> /// <returns>TNil on success, otherwise a TException.</returns> TType ParseWhileGroup(Group group) { /* BNF for while loop: * <while-loop> ::= 'while' <condition> ',' ( <statement> | <block> ) * The 'while' is assumed to have already been checked for */ if (group.Count == 1) return new TException(this, "Statement could not be evaluated", "while loop must be given a condition"); int commaIndex = group.IndexOf(","); if (commaIndex < 0) return new TException(this, "while loop invalid", "comma required after condition"); if (group.Count == 1) return new TException(this, "Statement could not be evaluated", "while loop must be given a condition"); Group conditionGroup = new Group(null); conditionGroup.AddRange(group.GetRange(1, commaIndex - 1)); Group statementGroup = null; TBlock block = null; if (group.Count > commaIndex + 1) // If there is a statement after the comma, use that statement { statementGroup = new Group(null); statementGroup.AddRange(group.GetRange(commaIndex + 1, group.Count - (commaIndex + 1))); } else // Otherwise get a block { TException exception; block = new TBlock(this, out exception, false); if (exception != null) return exception; } TType returnValue = TNil.Instance; while (true) { // Parse the condition, and if it's true run the block or single statement, otherwise return MNil TType value = ParseGroup((Group)conditionGroup.Clone()); if (value is TException) return value; TBoolean result = value as TBoolean; if (result == null) { bool success = false; TVariable variable = value as TVariable; if (variable != null) { result = variable.Value as TBoolean; if (result != null) success = true; } if (!success) return new TException(this, "Condition does not evaluate to a boolean value", "yes or no"); } if (result.Value) { bool breakUsed = false; if (statementGroup != null) returnValue = ParseGroup((Group)statementGroup.Clone()); else { bool exitFromFunction; returnValue = block.Execute(this, out exitFromFunction, out breakUsed); if (exitFromFunction) return returnValue; } if ((returnValue is TException) || breakUsed) return returnValue; } else return returnValue; if (!alive) return TNil.Instance; } }