/// <summary> /// Appends a TType to this TArgumentList. If a TArgumentList is passed, then the values contained in that /// TArgumentList are appended to this TArgumentList. If the values to be appended are TVariables, then the /// value of the TVariable will be appended instead of the TVariable itself (although if TVariable A /// containing TVariable B is passed, then TVariable B will be appended, as opposed to TVariable B's value). /// </summary> /// <param name="argument">The TType to append.</param> public void Add(TType argument) { TArgumentList argList = argument as TArgumentList; if (argList == null) { TVariable variable = argument as TVariable; if (variable == null) { arguments.Add(argument); } else { arguments.Add(variable.Value); } } else { for (int i = 0; i < argList.Count; ++i) { arguments.Add(argList[i]); } } }
/// <summary> /// Calls the TFunction with the specified arguments. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="value"> /// The argument to pass to the function. When passing multiple arguments, use a TArgumentList. /// </param> /// <returns></returns> public TType Call(Interpreter interpreter, TType value) { // If value is already a TArgumentList, then simply copy the reference, otherwise create a new // TArgumentList and add the value to it. This TArgument list is to be passed to the function. TArgumentList argList = value as TArgumentList; if (argList == null) { argList = new TArgumentList(); argList.Add(value); } // If the function takes a fixed number of arguments... if (ArgNames != null) { // Occupy the argument list with the default arguments. If there is no default argument in a place // where an argument should have been given, return a TException. if (argList.Count < ArgNames.Length) { for (int i = argList.Count; i < DefaultArgs.Length; ++i) { if (DefaultArgs[i] == null) { break; } else { argList.Add(DefaultArgs[i]); } } } if (argList.Count != ArgNames.Length) { return(new TException(interpreter, "Incorrect number of arguments for function '" + Name + "'", argList.Count.ToString() + " out of " + ArgNames.Length.ToString() + " given")); } } interpreter.Stack.Push(); // Keep a track of the new stack level so that if a function calls 'exit()', which pops from the stack, // this will be able to be detected and the function call can be terminated properly int stackLevel = interpreter.Stack.Level; // Put the arguments on the current stack 'frame' if (ArgNames == null) { for (int i = 0; i < argList.Count; ++i) { interpreter.Stack.AddVariable(new TVariable("arg" + i.ToString(), argList[i])); } } else { for (int i = 0; i < argList.Count; ++i) { interpreter.Stack.AddVariable(new TVariable(ArgNames[i], argList[i])); } } TType returnValue = null; bool dontPop = false; // Set to true if the stack is popped during the call, i.e. if 'exit()' is called // Call the function if (HardCodedFunction != null) { returnValue = HardCodedFunction.Invoke(interpreter, argList); } else if (Block != null) { bool breakUsed; returnValue = Block.Execute(interpreter, out dontPop, out breakUsed); } else if (CustomFunction != "") { returnValue = interpreter.Interpret(CustomFunction, true); if (interpreter.Stack.Level < stackLevel) { dontPop = true; } } // If returnValue is a TVariable, then return the value of the TVariable (e.g. we want to return 5, as // opposed to the variable X which contains 5) TVariable variable = returnValue as TVariable; if (variable != null) { returnValue = variable.Value; } if (!dontPop) { interpreter.Stack.Pop(); } return(returnValue ?? TNil.Instance); }
/// <summary> /// Adds a variable within the current Toast function's scope. /// </summary> /// <param name="variable">The TVariable to add.</param> public void AddVariable(TVariable variable) { stack.Peek().Add(variable.Identifier, variable); }
/// <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 group, searching for any reference and dereference operators. Operators and operands are /// replaced with the reference/dereferenced value. /// </summary> /// <param name="group">The group to parse.</param> /// <param name="limit">The index of the group to parse up to. Used by the ParseAssignmentGroup method.</param> /// <returns>A TException on failure, otherwise null.</returns> TException ParseReferencesOfGroup(Group group, int limit = -1) { int limitToUse = limit < 0 ? group.Count - 1 : limit; int index; while (true) { // Search from right to left for the first reference or dereference character int refIndex = group.LastIndexOf(TType.REFERENCE_CHARACTER.ToString(), limitToUse), derefIndex = group.LastIndexOf(TType.DEREFERENCE_CHARACTER.ToString(), limitToUse); char character; // Used so we know what operation we're carrying out later if (refIndex < 0) { index = derefIndex; character = TType.DEREFERENCE_CHARACTER; } else if (derefIndex < 0) { index = refIndex; character = TType.REFERENCE_CHARACTER; } else { if (refIndex > derefIndex) { index = refIndex; character = TType.REFERENCE_CHARACTER; } else { index = derefIndex; character = TType.DEREFERENCE_CHARACTER; } } if (index < 0) break; if (index + 1 > group.Count) return new TException(this, "Invalid expression term '" + character + "' at end of statement"); // If the operand is not a variable, return a TException (can't dereference values) TType variable = TType.Parse(this, group[index + 1]); if (variable is TException) return variable as TException; if (!(variable is TVariable)) { if (character == TType.REFERENCE_CHARACTER) return new TException(this, "Attempted creation of reference to value", "expected variable identifier"); else return new TException(this, "Attempted dereference of value", "expected variable identifier"); } if (character == TType.REFERENCE_CHARACTER) group[index] = new TVariable("reference", variable); else { group[index] = ((TVariable)variable).Value; if (!(group[index] is TVariable) && !(group[index] is TFunction)) return new TException(this, "Dereference of value type variable", "expected reference"); } group.RemoveAt(index + 1); limitToUse = limit < 0 ? group.Count - 1 : limit; } return null; }