/// <summary> /// Copies the data from another TFunction into this TFunction. /// </summary> /// <param name="function">The existing TFunction whose data is to be copied into the new TFunction.</param> public void CopyFrom(TFunction function) { Name = function.Name; HardCodedFunction = function.HardCodedFunction; CustomFunction = function.CustomFunction; Block = function.Block; CopyArguments(function.ArgNames, function.DefaultArgs); }
/// <summary> /// A TFunction constructor. /// </summary> /// <param name="name">The name of the new Toast function.</param> /// <param name="function">A string of Toast code to be executed when the Toast function is called.</param> /// <param name="argNames"> /// The argument names to use in the function. Pass null for the function to use a variable number of /// arguments. /// </param> /// <param name="defaultArgs"> /// The default values of arguments, as TTypes. Use null in the array to specify that no default value should /// be used for a particular argument, or pass null to indicate that no arguments should have default values. /// </param> public TFunction(string name, string function, string[] argNames, TType[] defaultArgs) { Name = name; HardCodedFunction = null; CustomFunction = function; Block = null; CopyArguments(argNames, defaultArgs); }
/// <summary> /// A TFunction constructor. /// </summary> /// <param name="name">The name of the new Toast function.</param> /// <param name="function">A TBlock to be executed when the Toast function is called.</param> /// <param name="argNames"> /// The argument names to use in the function. Pass null for the function to use a variable number of /// arguments. /// </param> /// <param name="defaultArgs"> /// The default values of arguments, as TTypes. Use null in the array to specify that no default value should /// be used for a particular argument, or pass null to indicate that no arguments should have default values. /// </param> public TFunction(string name, TBlock block, string[] argNames, TType[] defaultArgs) { Name = name; Block = block; HardCodedFunction = null; CustomFunction = ""; CopyArguments(argNames, defaultArgs); }
/// <summary> /// Executes the code contained inside the TBlock. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="exitFromFunction"> /// A value that will be set to whether 'exit()' was called during the block execution. /// </param> /// <param name="breakUsed">Whethe the 'break' keyword was used in the block.</param> /// <param name="beforeElse">Whether to execute the code before the 'else', if any.</param> /// <returns>A TType value of the result of the last executed statement in the block.</returns> public TType Execute(Interpreter interpreter, out bool exitFromFunction, out bool breakUsed, bool beforeElse = true) { exitFromFunction = breakUsed = false; if (!beforeElse && (elseLocation < 0)) { return(new TException(interpreter, "No else statement found")); } int start, end; // Decide which section of code should be executed if (elseAllowed) { if (beforeElse) { start = 0; end = (elseLocation > 0 ? elseLocation : statements.Count) - 1; } else { start = elseLocation + 1; end = statements.Count - 1; } } else { start = 0; end = statements.Count - 1; } int stackLevel = interpreter.Stack.Level; // Keep track of the previous block (if any). Used when there are blocks within blocks TBlock previousCurrentBlock = interpreter.CurrentBlock; interpreter.CurrentBlock = this; TType returnValue = null, previousReturnValue = null; for (currentLine = start; currentLine <= end; ++currentLine) { previousReturnValue = returnValue; returnValue = interpreter.Interpret(statements[currentLine], true); if (interpreter.Stack.Level < stackLevel) // if 'exit()' was called { exitFromFunction = true; break; } // Need to check for TBreak so that it can be used to break from a loop (if any) if (returnValue is TException) { break; } if (returnValue is TBreak) { returnValue = previousReturnValue ?? TNil.Instance; breakUsed = true; break; } } // Now that the block has finished executing its code, restore control to the outer block interpreter.CurrentBlock = previousCurrentBlock; return(returnValue ?? TNil.Instance); }
/// <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 a 'begin', indicating the start of a block of code. /// </summary> /// <returns>A TBlock on success, otherwise a TException.</returns> TType ParseBegin() { TException exception; TType block = new TBlock(this, out exception, false); return exception ?? block; }
/// <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 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; } }