private static void ParseOperand(ASTNode node, string lexeme) { bool modifier = false; // An operand may start with an '@' symbol. Pick that // off. if (lexeme[0] == '@') { node.Push(ASTNodeType.QUIET, null, _curLine); lexeme = lexeme.Substring(1, lexeme.Length - 1); modifier = true; } // An operand may end with a '!' symbol. Pick that // off. if (lexeme.EndsWith("!")) { node.Push(ASTNodeType.FORCE, null, _curLine); lexeme = lexeme.Substring(0, lexeme.Length - 1); modifier = true; } if (!modifier) { ParseValue(node, lexeme, ASTNodeType.OPERAND); } else { node.Push(ASTNodeType.OPERAND, lexeme, _curLine); } }
private static void ParseForLoop(ASTNode statement, string[] lexemes) { // There are 4 variants of for loops in steam. The simplest two just // iterate a fixed number of times. The other two iterate // parts of lists. We call those second two FOREACH. // We're intentionally deprecating two of the variants here. // The for X to Y variant, where both X and Y are integers, // is useless. It can be just written as for X. // The for X to Y in LIST variant may have some niche uses, but // is annoying to implement. // The for X loop remains supported as is, while the // for X in LIST form is actually transformed into a foreach // statement. if (lexemes.Length == 1) { // for X var loop = statement.Push(ASTNodeType.FOR, null, _curLine); ParseValue(loop, lexemes[0], ASTNodeType.STRING); } else { throw new SyntaxError(statement, "Invalid for loop"); } }
private static void ParseBinaryExpression(ASTNode node, string[] lexemes) { var expr = node.Push(ASTNodeType.BINARY_EXPRESSION, null, _curLine); int i = 0; // The expressions on either side of the operator can be values // or operands that need to be evaluated. ParseOperand(expr, lexemes[i++]); for (; i < lexemes.Length; i++) { if (IsOperator(lexemes[i])) { break; } ParseValue(expr, lexemes[i], ASTNodeType.STRING); } ParseOperator(expr, lexemes[i++]); ParseOperand(expr, lexemes[i++]); for (; i < lexemes.Length; i++) { if (IsOperator(lexemes[i])) { break; } ParseValue(expr, lexemes[i], ASTNodeType.STRING); } }
private static void ParseLogicalExpression(ASTNode node, string[] lexemes) { // The steam language supports logical operators 'and' and 'or'. // Catch those and split the expression into pieces first. // Fortunately, it does not support parenthesis. var expr = node; bool logical = false; int start = 0; for (int i = start; i < lexemes.Length; i++) { if (lexemes[i] == "and" || lexemes[i] == "or") { if (!logical) { expr = node.Push(ASTNodeType.LOGICAL_EXPRESSION, null, _curLine); logical = true; } ParseExpression(expr, lexemes.Slice(start, i - 1)); start = i + 1; expr.Push(lexemes[i] == "and" ? ASTNodeType.AND : ASTNodeType.OR, null, _curLine); } } ParseExpression(expr, lexemes.Slice(start, lexemes.Length - 1)); }
private static void ParseValue(ASTNode node, string lexeme, ASTNodeType typeDefault) { if (lexeme.StartsWith("0x")) { node.Push(ASTNodeType.SERIAL, lexeme, _curLine); } else if (int.TryParse(lexeme, out _)) { node.Push(ASTNodeType.INTEGER, lexeme, _curLine); } else if (double.TryParse(lexeme, out _)) { node.Push(ASTNodeType.DOUBLE, lexeme, _curLine); } else { node.Push(typeDefault, lexeme, _curLine); } }
private static void ParseCommand(ASTNode node, string lexeme) { // A command may start with an '@' symbol. Pick that // off. if (lexeme[0] == '@') { node.Push(ASTNodeType.QUIET, null, _curLine); lexeme = lexeme.Substring(1, lexeme.Length - 1); } // A command may end with a '!' symbol. Pick that // off. if (lexeme.EndsWith("!")) { node.Push(ASTNodeType.FORCE, null, _curLine); lexeme = lexeme.Substring(0, lexeme.Length - 1); } node.Push(ASTNodeType.COMMAND, lexeme, _curLine); }
private static void ParseOperator(ASTNode node, string lexeme) { switch (lexeme) { case "==": case "=": node.Push(ASTNodeType.EQUAL, null, _curLine); break; case "!=": node.Push(ASTNodeType.NOT_EQUAL, null, _curLine); break; case "<": node.Push(ASTNodeType.LESS_THAN, null, _curLine); break; case "<=": node.Push(ASTNodeType.LESS_THAN_OR_EQUAL, null, _curLine); break; case ">": node.Push(ASTNodeType.GREATER_THAN, null, _curLine); break; case ">=": node.Push(ASTNodeType.GREATER_THAN_OR_EQUAL, null, _curLine); break; default: throw new SyntaxError(node, "Invalid operator in binary expression"); } }
private static void ParseForEachLoop(ASTNode statement, string[] lexemes) { // foreach X in LIST var loop = statement.Push(ASTNodeType.FOREACH, null, _curLine); if (lexemes[1] != "in") { throw new SyntaxError(statement, "Invalid foreach loop"); } // This is the iterator name ParseValue(loop, lexemes[0], ASTNodeType.STRING); loop.Push(ASTNodeType.LIST, lexemes[2], _curLine); }
private static void ParseUnaryExpression(ASTNode node, string[] lexemes) { var expr = node.Push(ASTNodeType.UNARY_EXPRESSION, null, _curLine); int i = 0; if (lexemes[i] == "not") { expr.Push(ASTNodeType.NOT, null, _curLine); i++; } ParseOperand(expr, lexemes[i++]); for (; i < lexemes.Length; i++) { ParseValue(expr, lexemes[i], ASTNodeType.STRING); } }
private static void ParseStatement(ASTNode node, string[] lexemes) { var statement = node.Push(ASTNodeType.STATEMENT, null, _curLine); // Examine the first word on the line switch (lexemes[0]) { // Ignore comments case "#": case "//": return; // Control flow statements are special case "if": { if (lexemes.Length <= 1) { throw new SyntaxError(node, "Script compilation error"); } var t = statement.Push(ASTNodeType.IF, null, _curLine); ParseLogicalExpression(t, lexemes.Slice(1, lexemes.Length - 1)); break; } case "elseif": { if (lexemes.Length <= 1) { throw new SyntaxError(node, "Script compilation error"); } var t = statement.Push(ASTNodeType.ELSEIF, null, _curLine); ParseLogicalExpression(t, lexemes.Slice(1, lexemes.Length - 1)); break; } case "else": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.ELSE, null, _curLine); break; case "endif": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.ENDIF, null, _curLine); break; case "while": { if (lexemes.Length <= 1) { throw new SyntaxError(node, "Script compilation error"); } var t = statement.Push(ASTNodeType.WHILE, null, _curLine); ParseLogicalExpression(t, lexemes.Slice(1, lexemes.Length - 1)); break; } case "endwhile": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.ENDWHILE, null, _curLine); break; case "for": { if (lexemes.Length <= 1) { throw new SyntaxError(node, "Script compilation error"); } ParseForLoop(statement, lexemes.Slice(1, lexemes.Length - 1)); break; } case "endfor": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.ENDFOR, null, _curLine); break; case "break": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.BREAK, null, _curLine); break; case "continue": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.CONTINUE, null, _curLine); break; case "stop": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.STOP, null, _curLine); break; case "replay": case "loop": if (lexemes.Length > 1) { throw new SyntaxError(node, "Script compilation error"); } statement.Push(ASTNodeType.REPLAY, null, _curLine); break; default: // It's a regular statement. ParseCommand(statement, lexemes[0]); foreach (var lexeme in lexemes.Slice(1, lexemes.Length - 1)) { ParseValue(statement, lexeme, ASTNodeType.STRING); } break; } }