/// <summary> /// The constructor for TBlock. /// </summary> /// <param name="interpreter">The interpreter that the constructor is being called from.</param> /// <param name="exception">An exception that will be written to if there was an error.</param> /// <param name="elseAllowed"> /// Whether the 'else' keyword is allowed to be used in the top level of the block. Usually only used in when /// creating a block in IF statements. /// </param> public TBlock(Interpreter interpreter, out TException exception, bool elseAllowed) { statements = new List <string>(); elseLocation = -1; this.elseAllowed = elseAllowed; exception = null; currentLine = -1; int blockLevel = 0; while (true) // Keep adding statements until the appropriate terminator (i.e. 'end') is found { string statement = interpreter.GetInput().Trim(); // Split the statement into symbols so we can analyse it to find out where the block begin/ends are bool isComment; Interpreter.Group symbolGroup = interpreter.SplitIntoSymbols(statement, out exception, out isComment); if (exception != null) { statements.Clear(); break; } if (isComment) { continue; } // A bit of Linq to easily count the number of a particular keyword in a group string keywordName = ""; var keywordQuery = from object item in symbolGroup where (item is string) && ((string)item == keywordName) select item; // If there's a begin, increment the block level. If there are too many then return an error keywordName = "begin"; int beginCount = keywordQuery.Count(); if (beginCount > 1) { exception = new TException(interpreter, "Having more than one begin on a line is forbidden"); break; } else if (beginCount == 1) { ++blockLevel; } // If there's an end, decrement the block level keywordName = "end"; int endCount = keywordQuery.Count(); if (endCount > 1) { exception = new TException(interpreter, "Having more than one end on a line is forbidden"); break; } else if (endCount == 1) { if (blockLevel == 0) { break; } else { --blockLevel; } } // Increment the block level if there is an IF statement or WHILE loop where a block of code is to // follow, i.e. there is no statement after the comma after the condition if (symbolGroup.IndexOf("if") >= 0) { string commaStr = symbolGroup[symbolGroup.Count - 1] as string; if ((commaStr != null) && (commaStr == ",")) { ++blockLevel; } } if (symbolGroup.IndexOf("while") >= 0) { string commaStr = symbolGroup[symbolGroup.Count - 1] as string; if ((commaStr != null) && (commaStr == ",")) { ++blockLevel; } } // Increment if there is a multi-line function declaration (let <ident><group> =<nothing>) int equalsIndex; if ((equalsIndex = symbolGroup.IndexOf("=")) == symbolGroup.Count - 1) { if ((equalsIndex > 1) && (symbolGroup[equalsIndex - 1] is Interpreter.Group)) { if (symbolGroup.LastIndexOf("let", equalsIndex - 2) >= 0) { ++blockLevel; } } } bool breakNow = false; // For a double break int elseIndex = -1; while ((elseIndex = symbolGroup.IndexOf("else", elseIndex + 1)) >= 0) { if (elseIndex == 0) // If the else is at the beginning of the line { if (blockLevel == 0) { // If a top level 'else' has not already been used and the use of 'else' is allowed if ((elseLocation < 0) && elseAllowed) { elseLocation = statements.Count; if (elseIndex < symbolGroup.Count - 1) // If there is a statement after the 'else' { /* Convert * <block> * else <statement> * * into * <block> * else * <block> * * and stop populating the block with statments */ statements.Add("else"); statements.Add(statement.Substring(4, statement.Length - 4)); breakNow = true; } } else { // 'else' already used or it is not allowed, error exception = new TException(interpreter, "Unexpected keyword 'else'", elseAllowed ? "else already used" : "else not allowed in this construct"); statements.Clear(); breakNow = true; break; } } else if (elseIndex < symbolGroup.Count - 1) { --blockLevel; } // if a statement follows the 'else' on the same line then decrement the block level } else if (symbolGroup.LastIndexOf("if", elseIndex) >= 0) { /* If there is an IF statement of the form * * if <condition>, <statement> else * <block> * end * * I.e. if the last occurrence of 'if' is before the current 'else' * and the current 'else' is the last symbol of the line * * increment the block level. */ if (elseIndex == symbolGroup.Count - 1) { ++blockLevel; } } } if (breakNow) { break; } statements.Add(statement); } }
/// <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 modulus brackets and replacing the modulus brackets and their contents /// with the absolute values. /// </summary> /// <param name="group">The group to parse.</param> /// <returns>A TException on failure, otherwise null.</returns> TException ParseModulusBracketsOfGroup(Group group) { int index; while ((index = group.IndexOf(TType.MODULUS_CHARACTER.ToString())) >= 0) { TException exception = new TException(this, "Modulus brackets not closed", "another | required"); if (index + 2 >= group.Count) return exception; if (group[index + 2] is string) { if ((string)group[index + 2] != TType.MODULUS_CHARACTER.ToString()) return exception; } else return exception; TType value = TType.Parse(this, group[index + 1]); if (value is TException) return value as TException; TType result = Operations.Math.Modulus(this, value); if (result == null) return new TException(this, "Modulus operation failed", "reason unknown"); if (result is TException) return result as TException; group[index] = result; group.RemoveRange(index + 1, 2); } return null; }
/// <summary> /// Splits a string into symbols and puts them in an Interpreter.Group. /// (This method is probably really inefficient with the way it handles strings...) /// </summary> /// <param name="statement">The string to split into symbols.</param> /// <param name="exception">A value that will be set if an error occurs.</param> /// <returns>The Interpreter.Group that contains the resulting symbols. Returns null on failure.</returns> public Group SplitIntoSymbols(string statement, out TException exception, out bool isCommentLine) { exception = null; statement = statement.Trim(); // Ignore comments and empty strings int commentIndex; if ((commentIndex = statement.IndexOf("//")) >= 0) statement = statement.Remove(commentIndex); if (statement == "") { isCommentLine = true; return null; } isCommentLine = false; // Extract the strings from the statement, storing them in a list List<string> strings = new List<string>(); int index = -1; while ((index = statement.IndexOf(TType.STRING_CHARACTER, index + 1)) >= 0) { // Find the string terminator int index2 = statement.IndexOf(TType.STRING_CHARACTER, index + 1); if (index2 < 0) { exception = new TException(this, "String not closed", "another \" required"); return null; } // Add the substring between the speech marks to the list, and leave the beginning speech mark of the // string in tact so that the strings can be substituded back into the final group string str = statement.Substring(index + 1, (index2 - index) - 1); statement = statement.Remove(index + 1, index2 - index); strings.Add(str); } // Split up the symbols foreach (string s in SYMBOLS_TO_SPLIT_BY) { string newS = " " + s + " "; statement = statement.Replace(s, newS); // 2 character symbols are broken, fix ahead... } // Populate a list with the symbols List<string> symbols = new List<string>(); foreach (string s in statement.Split(' ')) { string str = s.Trim(); if (str != "") symbols.Add(str); } Group superGroup = new Group(null); Group currentGroup = superGroup; int groupDepth = 0, stringId = 0; bool modulusOpen = false; for (int i = 0; i < symbols.Count; ++i) { // Fix for broken 2 character symbols if (i + 1 < symbols.Count) { if (symbols[i + 1] == "=") { if (symbols[i] == "~") { symbols[i] = "~="; symbols.RemoveAt(i + 1); } else if (symbols[i] == ">") { symbols[i] = ">="; symbols.RemoveAt(i + 1); } else if (symbols[i] == "<") { symbols[i] = "<="; symbols.RemoveAt(i + 1); } else if (symbols[i] == "/") { symbols[i] = "/="; symbols.RemoveAt(i + 1); } } } string s = symbols[i]; // If any opening brackets are found, create a new Group to contain the symbols within the brackets if ((s == "(") || ((s == "|") && !modulusOpen)) { Group newGroup; if (s == "|") // If a modulus bracket is found, create a new group between the modulus brackets { // Do this to the modulus brackets: (|(stuff)|) so 'my_func|x|' will work newGroup = new Group(currentGroup); currentGroup = newGroup; ++groupDepth; modulusOpen = true; currentGroup.Add(s); } newGroup = new Group(currentGroup); currentGroup = newGroup; ++groupDepth; } else if ((s == ")") || ((s == "|") && modulusOpen)) { // If any closing brackets are found, finish adding to the current group and start adding to the // parent group if (currentGroup.ParentGroup != null) currentGroup = currentGroup.ParentGroup; --groupDepth; if (s == "|") { modulusOpen = false; currentGroup.Add(s); if (currentGroup.ParentGroup != null) currentGroup = currentGroup.ParentGroup; --groupDepth; } } else if (s == "\"") { currentGroup.Add(new TString(strings[stringId])); ++stringId; } else currentGroup.Add(s); } // Return TExceptions if the brackets were not matched up properly if (groupDepth > 0) { exception = new TException(this, "Too few closing brackets", groupDepth.ToString() + " required"); return null; } if (groupDepth < 0) { exception = new TException(this, "Too many closing brackets", "remove " + (-groupDepth).ToString()); return null; } return superGroup; }
/// <summary> /// The constructor for TBlock. /// </summary> /// <param name="interpreter">The interpreter that the constructor is being called from.</param> /// <param name="exception">An exception that will be written to if there was an error.</param> /// <param name="elseAllowed"> /// Whether the 'else' keyword is allowed to be used in the top level of the block. Usually only used in when /// creating a block in IF statements. /// </param> public TBlock(Interpreter interpreter, out TException exception, bool elseAllowed) { statements = new List<string>(); elseLocation = -1; this.elseAllowed = elseAllowed; exception = null; currentLine = -1; int blockLevel = 0; while (true) // Keep adding statements until the appropriate terminator (i.e. 'end') is found { string statement = interpreter.GetInput().Trim(); // Split the statement into symbols so we can analyse it to find out where the block begin/ends are bool isComment; Interpreter.Group symbolGroup = interpreter.SplitIntoSymbols(statement, out exception, out isComment); if (exception != null) { statements.Clear(); break; } if (isComment) continue; // A bit of Linq to easily count the number of a particular keyword in a group string keywordName = ""; var keywordQuery = from object item in symbolGroup where (item is string) && ((string)item == keywordName) select item; // If there's a begin, increment the block level. If there are too many then return an error keywordName = "begin"; int beginCount = keywordQuery.Count(); if (beginCount > 1) { exception = new TException(interpreter, "Having more than one begin on a line is forbidden"); break; } else if (beginCount == 1) ++blockLevel; // If there's an end, decrement the block level keywordName = "end"; int endCount = keywordQuery.Count(); if (endCount > 1) { exception = new TException(interpreter, "Having more than one end on a line is forbidden"); break; } else if (endCount == 1) { if (blockLevel == 0) break; else --blockLevel; } // Increment the block level if there is an IF statement or WHILE loop where a block of code is to // follow, i.e. there is no statement after the comma after the condition if (symbolGroup.IndexOf("if") >= 0) { string commaStr = symbolGroup[symbolGroup.Count - 1] as string; if ((commaStr != null) && (commaStr == ",")) ++blockLevel; } if (symbolGroup.IndexOf("while") >= 0) { string commaStr = symbolGroup[symbolGroup.Count - 1] as string; if ((commaStr != null) && (commaStr == ",")) ++blockLevel; } // Increment if there is a multi-line function declaration (let <ident><group> =<nothing>) int equalsIndex; if ((equalsIndex = symbolGroup.IndexOf("=")) == symbolGroup.Count - 1) { if ((equalsIndex > 1) && (symbolGroup[equalsIndex - 1] is Interpreter.Group)) { if (symbolGroup.LastIndexOf("let", equalsIndex - 2) >= 0) ++blockLevel; } } bool breakNow = false; // For a double break int elseIndex = -1; while ((elseIndex = symbolGroup.IndexOf("else", elseIndex + 1)) >= 0) { if (elseIndex == 0) // If the else is at the beginning of the line { if (blockLevel == 0) { // If a top level 'else' has not already been used and the use of 'else' is allowed if ((elseLocation < 0) && elseAllowed) { elseLocation = statements.Count; if (elseIndex < symbolGroup.Count - 1) // If there is a statement after the 'else' { /* Convert * <block> * else <statement> * * into * <block> * else * <block> * * and stop populating the block with statments */ statements.Add("else"); statements.Add(statement.Substring(4, statement.Length - 4)); breakNow = true; } } else { // 'else' already used or it is not allowed, error exception = new TException(interpreter, "Unexpected keyword 'else'", elseAllowed ? "else already used" : "else not allowed in this construct"); statements.Clear(); breakNow = true; break; } } else if (elseIndex < symbolGroup.Count - 1) --blockLevel; // if a statement follows the 'else' on the same line then decrement the block level } else if (symbolGroup.LastIndexOf("if", elseIndex) >= 0) { /* If there is an IF statement of the form * * if <condition>, <statement> else * <block> * end * * I.e. if the last occurrence of 'if' is before the current 'else' * and the current 'else' is the last symbol of the line * * increment the block level. */ if (elseIndex == symbolGroup.Count - 1) ++blockLevel; } } if (breakNow) break; statements.Add(statement); } }