update grammar to remove more junk + upate tests + codegen

This commit is contained in:
ChUrl
2021-02-05 14:08:08 +01:00
parent d1790e526f
commit 384c318c18
11 changed files with 44 additions and 125 deletions

View File

@ -27,7 +27,11 @@ public final class StackSizeAnalyzer {
final StackModel stack = new StackModel(); final StackModel stack = new StackModel();
runStackModel(tree.getRoot().getChildren().get(3).getChildren().get(11), stack); if (tree.getRoot().getChildren().size() > 1) {
// Or else main-method would be empty
runStackModel(tree.getRoot().getChildren().get(1), stack);
}
Logger.logDebug("Found required stack-depth", StackSizeAnalyzer.class); Logger.logDebug("Found required stack-depth", StackSizeAnalyzer.class);
return stack.getMax(); return stack.getMax();
@ -64,7 +68,7 @@ public final class StackSizeAnalyzer {
private static void println(SyntaxTreeNode root, StackModel stack) { private static void println(SyntaxTreeNode root, StackModel stack) {
stack.push(root); // Getstatic stack.push(root); // Getstatic
runStackModel(root.getChildren().get(1).getChildren().get(1), stack); runStackModel(root.getChildren().get(0).getChildren().get(0), stack);
stack.pop(); // Objectref stack.pop(); // Objectref
stack.pop(); // Argument stack.pop(); // Argument

View File

@ -89,7 +89,7 @@ public final class FlowGraphGenerator {
private static FlowGraph initFlowGraph(SyntaxTree tree, Map<String, Integer> varMap, String source) { private static FlowGraph initFlowGraph(SyntaxTree tree, Map<String, Integer> varMap, String source) {
final String bytecodeVersion = "49.0"; final String bytecodeVersion = "49.0";
final String clazz = tree.getRoot().getChildren().get(1).getValue(); final String clazz = tree.getRoot().getChildren().get(0).getValue();
final int stackSize = StackSizeAnalyzer.runStackModel(tree); final int stackSize = StackSizeAnalyzer.runStackModel(tree);
final int localCount = varMap.size() + 1; final int localCount = varMap.size() + 1;
@ -104,8 +104,14 @@ public final class FlowGraphGenerator {
public FlowGraph generateGraph() { public FlowGraph generateGraph() {
Logger.logDebug("Beginning generation of source-graph", FlowGraphGenerator.class); Logger.logDebug("Beginning generation of source-graph", FlowGraphGenerator.class);
if (this.tree.getRoot().getChildren().size() == 1) {
// Empty main-method
return this.graph;
}
// Skip the first 2 identifiers: ClassName, MainArgs // Skip the first 2 identifiers: ClassName, MainArgs
this.generateNode(this.tree.getRoot().getChildren().get(3).getChildren().get(11)); this.generateNode(this.tree.getRoot().getChildren().get(1));
this.graph.purgeEmptyBlocks(); this.graph.purgeEmptyBlocks();
Logger.logDebug("Source-graph generation complete", FlowGraphGenerator.class); Logger.logDebug("Source-graph generation complete", FlowGraphGenerator.class);
@ -176,7 +182,7 @@ public final class FlowGraphGenerator {
this.graph.addLabel("LOOPstart" + currentLabel); this.graph.addLabel("LOOPstart" + currentLabel);
// Condition while ( ... ) { // Condition while ( ... ) {
this.generateNode(root.getChildren().get(0).getChildren().get(1)); this.generateNode(root.getChildren().get(0).getChildren().get(0));
// Jump out of loop if condition is false // Jump out of loop if condition is false
this.graph.addJump("ifeq", "LOOPend" + currentLabel); this.graph.addJump("ifeq", "LOOPend" + currentLabel);
@ -202,7 +208,7 @@ public final class FlowGraphGenerator {
final String inst = switch (type) { final String inst = switch (type) {
case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "istore"; case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "istore";
case "STRING_TYPE" -> "astore"; case "STRING_TYPE" -> "astore";
default -> throw new IllegalStateException("Unexpected value: " + type); default -> throw new CodeGenerationException("Unexpected value: " + type);
}; };
Logger.logInfo("assign(): Node \"" + root.getName() + ": " + root.getValue() + "\" => " + inst, FlowGraphGenerator.class); Logger.logInfo("assign(): Node \"" + root.getName() + ": " + root.getValue() + "\" => " + inst, FlowGraphGenerator.class);
@ -239,7 +245,7 @@ public final class FlowGraphGenerator {
inst = switch (root.getValue()) { inst = switch (root.getValue()) {
case "ADD" -> ""; case "ADD" -> "";
case "SUB" -> "ineg"; case "SUB" -> "ineg";
default -> throw new IllegalStateException("Unexpected value: " + root.getValue()); default -> throw new CodeGenerationException("Unexpected value: " + root.getValue());
}; };
} else if (root.getChildren().size() == 2) { //! Stack - 1 } else if (root.getChildren().size() == 2) { //! Stack - 1
// Binary operator // Binary operator
@ -253,7 +259,7 @@ public final class FlowGraphGenerator {
case "MUL" -> "imul"; case "MUL" -> "imul";
case "DIV" -> "idiv"; case "DIV" -> "idiv";
case "MOD" -> "irem"; // Remainder operator case "MOD" -> "irem"; // Remainder operator
default -> throw new IllegalStateException("Unexpected value: " + root.getValue()); default -> throw new CodeGenerationException("Unexpected value: " + root.getValue());
}; };
} }
@ -276,7 +282,7 @@ public final class FlowGraphGenerator {
if (!"NOT".equals(node.getValue())) { if (!"NOT".equals(node.getValue())) {
// Possibility doesn't exist, would be frontend-error // Possibility doesn't exist, would be frontend-error
throw new IllegalStateException("Unexpected value: " + node.getValue()); throw new CodeGenerationException("Unexpected value: " + node.getValue());
} }
this.generateNode(node.getChildren().get(0)); this.generateNode(node.getChildren().get(0));
@ -298,12 +304,12 @@ public final class FlowGraphGenerator {
final String cmpeq = switch (type) { final String cmpeq = switch (type) {
case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "if_icmpeq"; case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "if_icmpeq";
case "STRING_TYPE" -> "if_accmpeq"; case "STRING_TYPE" -> "if_accmpeq";
default -> throw new IllegalStateException("Unexpected value: " + type); default -> throw new CodeGenerationException("Unexpected value: " + type);
}; };
final String cmpne = switch (type) { final String cmpne = switch (type) {
case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "if_icmpne"; case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "if_icmpne";
case "STRING_TYPE" -> "if_accmpne"; case "STRING_TYPE" -> "if_accmpne";
default -> throw new IllegalStateException("Unexpected value: " + type); default -> throw new CodeGenerationException("Unexpected value: " + type);
}; };
// The comparison operations need to jump // The comparison operations need to jump
@ -316,7 +322,7 @@ public final class FlowGraphGenerator {
case "LESS_EQUAL" -> this.genComparisonInst("if_icmple", "LE", currentLabel); case "LESS_EQUAL" -> this.genComparisonInst("if_icmple", "LE", currentLabel);
case "GREATER" -> this.genComparisonInst("if_icmpgt", "GT", currentLabel); case "GREATER" -> this.genComparisonInst("if_icmpgt", "GT", currentLabel);
case "GREATER_EQUAL" -> this.genComparisonInst("if_icmpge", "GE", currentLabel); case "GREATER_EQUAL" -> this.genComparisonInst("if_icmpge", "GE", currentLabel);
default -> throw new IllegalStateException("Unexpected value: " + node.getValue()); default -> throw new CodeGenerationException("Unexpected value: " + node.getValue());
} }
} }
} }
@ -359,7 +365,7 @@ public final class FlowGraphGenerator {
final String inst = switch (type) { final String inst = switch (type) {
case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "iload"; case "INTEGER_TYPE", "BOOLEAN_TYPE" -> "iload";
case "STRING_TYPE" -> "aload"; case "STRING_TYPE" -> "aload";
default -> throw new IllegalStateException("Unexpected value: " + type); default -> throw new CodeGenerationException("Unexpected value: " + type);
}; };
Logger.logInfo("identifier(): Node \"" + node.getName() + ": " + node.getValue() + "\" => " + inst, FlowGraphGenerator.class); Logger.logInfo("identifier(): Node \"" + node.getName() + ": " + node.getValue() + "\" => " + inst, FlowGraphGenerator.class);
@ -370,12 +376,12 @@ public final class FlowGraphGenerator {
private void printlnNode(SyntaxTreeNode node) { //! Stack + 1 private void printlnNode(SyntaxTreeNode node) { //! Stack + 1
this.graph.addInstruction("getstatic", "java/lang/System/out", "Ljava/io/PrintStream;"); this.graph.addInstruction("getstatic", "java/lang/System/out", "Ljava/io/PrintStream;");
final SyntaxTreeNode expr = node.getChildren().get(1).getChildren().get(1); final SyntaxTreeNode expr = node.getChildren().get(0).getChildren().get(0);
final String type = switch (this.nodeTypeMap.get(expr)) { final String type = switch (this.nodeTypeMap.get(expr)) {
case "BOOLEAN_TYPE" -> "Z"; case "BOOLEAN_TYPE" -> "Z";
case "INTEGER_TYPE" -> "I"; case "INTEGER_TYPE" -> "I";
case "STRING_TYPE" -> "Ljava/lang/String;"; case "STRING_TYPE" -> "Ljava/lang/String;";
default -> throw new IllegalStateException("Unexpected value: " + this.nodeTypeMap.get(expr)); default -> throw new CodeGenerationException("Unexpected value: " + this.nodeTypeMap.get(expr));
}; };
this.generateNode(expr); this.generateNode(expr);

View File

@ -59,7 +59,7 @@ public final class TypeChecker {
} else if ("par_expr".equals(root.getName())) { } else if ("par_expr".equals(root.getName())) {
// Nodetable Eintrag für Klammern // Nodetable Eintrag für Klammern
final SyntaxTreeNode centerChild = root.getChildren().get(1); final SyntaxTreeNode centerChild = root.getChildren().get(0);
nodeTable.put(root, nodeTable.get(centerChild)); nodeTable.put(root, nodeTable.get(centerChild));
} else if ("IDENTIFIER".equals(root.getName())) { } else if ("IDENTIFIER".equals(root.getName())) {

View File

@ -36,7 +36,7 @@ class FlowGraphGeneratorTest {
@BeforeAll @BeforeAll
static void init() throws IOException, URISyntaxException { static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(FlowGraphGeneratorTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
final Grammar grammar = Grammar.fromFile(path); final Grammar grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar); parser = StupsParser.fromGrammar(grammar);
stupsGrammar = grammar; stupsGrammar = grammar;

View File

@ -34,7 +34,7 @@ class LivenessAnalysisTest {
@BeforeAll @BeforeAll
static void init() throws IOException, URISyntaxException { static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(LivenessAnalysisTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
final Grammar grammar = Grammar.fromFile(path); final Grammar grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar); parser = StupsParser.fromGrammar(grammar);
stupsGrammar = grammar; stupsGrammar = grammar;

View File

@ -24,7 +24,7 @@ class LexerGrammarParserTest {
@BeforeAll @BeforeAll
static void init() throws IOException, URISyntaxException { static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(LexerGrammarParserTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
final Grammar grammar = Grammar.fromFile(path); final Grammar grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar); parser = StupsParser.fromGrammar(grammar);
} }

View File

@ -24,7 +24,7 @@ class ParseTreeCleanerTest {
@BeforeAll @BeforeAll
static void init() throws IOException, URISyntaxException { static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(ParseTreeCleanerTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
grammar = Grammar.fromFile(path); grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar); parser = StupsParser.fromGrammar(grammar);
} }
@ -49,7 +49,7 @@ class ParseTreeCleanerTest {
ParseTreeCleaner.deleteChildren(tree, grammar); ParseTreeCleaner.deleteChildren(tree, grammar);
assertThat(before - tree.size()).isEqualTo(3); assertThat(before - tree.size()).isEqualTo(19);
} }
@Test @Test
@ -79,6 +79,6 @@ class ParseTreeCleanerTest {
ParseTreeCleaner.clean(tree, grammar); ParseTreeCleaner.clean(tree, grammar);
assertThat(tree.size()).isEqualTo(28); assertThat(tree.size()).isEqualTo(10);
} }
} }

View File

@ -23,7 +23,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
class TypeCheckerTest { class TypeCheckerTest {
private final Path path = Paths.get(this.getClass().getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); private final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
private final Grammar grammar = Grammar.fromFile(this.path); private final Grammar grammar = Grammar.fromFile(this.path);
private final StupsParser stupsParser = StupsParser.fromGrammar(this.grammar); private final StupsParser stupsParser = StupsParser.fromGrammar(this.grammar);
@ -39,9 +39,8 @@ class TypeCheckerTest {
private SyntaxTree getTree(String expr) { private SyntaxTree getTree(String expr) {
final Lexer lex = new StupsLexer(CharStreams.fromString(exprToProg(expr))); final Lexer lex = new StupsLexer(CharStreams.fromString(exprToProg(expr)));
final SyntaxTree tree = this.stupsParser.parse(lex.getAllTokens(), lex.getVocabulary()); final SyntaxTree tree = this.stupsParser.parse(lex.getAllTokens(), lex.getVocabulary());
final SyntaxTree ast = SyntaxTree.toAbstractSyntaxTree(tree, this.grammar);
return ast; return SyntaxTree.toAbstractSyntaxTree(tree, this.grammar);
} }
@ParameterizedTest @ParameterizedTest

View File

@ -28,7 +28,7 @@ class TypeTableTest {
@BeforeAll @BeforeAll
static void init() throws IOException, URISyntaxException { static void init() throws IOException, URISyntaxException {
final Path path = Paths.get(TypeTableTest.class.getClassLoader().getResource("exampleGrammars/Grammar.grammar").toURI()); final Path path = Paths.get(System.getProperty("user.dir") + "/stups.grammar");
grammar = Grammar.fromFile(path); grammar = Grammar.fromFile(path);
parser = StupsParser.fromGrammar(grammar); parser = StupsParser.fromGrammar(grammar);
} }

View File

@ -1,93 +0,0 @@
// NTERM, TERM are reserved
// Some Grammar-Symbols have to be named this way:
// assignment, declaration (for TypeTable creation)
// IDENTIFIER, NOT, ADD, SUB (for TypeChecking)
// expr, par_expr, assignment (for TypeChecking)
// Nonterminals:
NTERM: val type
NTERM: op unary arith_op logic_op compare_op
NTERM: S class_cnt block_cnt
NTERM: statement stmt print
NTERM: declaration assignment
NTERM: par_expr expr expr_2 expr_f
NTERM: loop cond cond_else single_or_braced
// Token:
TERM: CLASS PUBLIC STATIC
TERM: STRING_TYPE INTEGER_TYPE BOOLEAN_TYPE VOID_TYPE
TERM: WHILE IF ELSE
TERM: PRINTLN
TERM: ADD MUL SUB DIV MOD
TERM: AND OR NOT
TERM: LESS LESS_EQUAL GREATER GREATER_EQUAL EQUAL NOT_EQUAL
TERM: ASSIGN
TERM: L_BRACE R_BRACE L_BRACKET R_BRACKET L_PAREN R_PAREN SEMICOLON COMMA DOT
TERM: INTEGER_LIT STRING_LIT BOOLEAN_LIT
TERM: IDENTIFIER IDENTIFIER_MAIN
// Actions: promote - Information "hochreichen": Ersetzt Node mit Child, wenn es nur ein Child gibt
// - val[promote] :: --val--INTEGER_LIT: 5 => --INTEGER_LIT: 5
// delifempty - Kann Node löschen, wenn dieser keinen Inhalt hat, normalerweise bei eps
// - block_cnt[delifempty] :: --block_cnt => --
// delchild= - Entfernt bestimmte Child-Nodes eines Parents
// - cond[delchild=IF] :: --cond--IF => --cond
// - assignment[delchild=ASSIGN] :: --assignment--ASSIGN => --assignment
// valtoval= - Verschiebt eine Child-Value in die Parent-Value und entfernt das Child
// - assignment[valtoval=IDENTIFIER] :: --assignment--IDENTIFIER: a => --assignment: a
// nametoval= - Verschiebt einen Child-Name in die Parent-Value und entfernt das Child
// - expr_2[nametoval=ADD] :: --expr_2--ADD => --expr_2: ADD
// renameto= - Führt Umbenennung eines Nodes durch
// - expr_2[renameto=expr] :: --expr_2: ADD => --expr: ADD
// General -----------------------------------------------------------------------------------------
val[promote] -> INTEGER_LIT | STRING_LIT | BOOLEAN_LIT | IDENTIFIER
type[promote] -> INTEGER_TYPE | STRING_TYPE | BOOLEAN_TYPE
// OP-List: ADD,SUB,MUL,DIV,MOD,AND,OR,NOT,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,EQUAL,NOT_EQUAL
op[promote] -> arith_op | logic_op | compare_op
unary[promote] -> ADD | SUB | NOT
arith_op[promote] -> ADD | SUB | MUL | DIV | MOD
logic_op[promote] -> AND | OR | NOT
compare_op[promote] -> LESS | LESS_EQUAL | GREATER | GREATER_EQUAL | EQUAL | NOT_EQUAL
// -------------------------------------------------------------------------------------------------
// START -> class IDENTIFIER { class_cnt }
S[promote] -> CLASS IDENTIFIER L_BRACE class_cnt R_BRACE | eps
// class_cnt -> public static void main(String[] args) { block_cnt }
class_cnt[promote delifempty] -> PUBLIC STATIC VOID_TYPE IDENTIFIER_MAIN L_PAREN STRING_TYPE L_BRACKET R_BRACKET IDENTIFIER R_PAREN L_BRACE block_cnt R_BRACE | eps
// block_ccnt -> stuff in {} | list of statements
block_cnt[promote delifempty] -> statement block_cnt | L_BRACE block_cnt R_BRACE | eps
// statement -> stuff ending with ; | loop | condition
statement[promote] -> stmt SEMICOLON | loop | cond
stmt[promote] -> print | declaration | assignment
print -> PRINTLN par_expr
// declaration -> type IDENTIFIER = expr;
declaration[nametoval=INTEGER_TYPE,BOOLEAN_TYPE,STRING_TYPE] -> type assignment
assignment[delchild=ASSIGN valtoval=IDENTIFIER] -> IDENTIFIER ASSIGN expr
// par_expr -> ( expr )
par_expr -> L_PAREN expr R_PAREN
// expr -> expression that returns something | literal
expr[promote nametoval=ADD,SUB,MUL,DIV,MOD,AND,OR,NOT,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,EQUAL,NOT_EQUAL] -> expr_f expr_2
expr_2[promote delifempty renameto=expr] -> op expr_f expr_2 | eps
expr_f[promote renameto=expr] -> unary expr_f | par_expr | val
// Control-Flow
loop[delchild=WHILE] -> WHILE par_expr single_or_braced
cond[delchild=IF] -> IF par_expr single_or_braced cond_else
cond_else[delifempty delchild=ELSE] -> ELSE single_or_braced | eps
single_or_braced -> L_BRACE block_cnt R_BRACE | stmt SEMICOLON

View File

@ -61,25 +61,27 @@ compare_op[promote] -> LESS | LESS_EQUAL | GREATER | GREATER_EQUAL | EQUAL | NOT
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// START -> class IDENTIFIER { class_cnt } // START -> class IDENTIFIER { class_cnt }
S[promote] -> CLASS IDENTIFIER L_BRACE class_cnt R_BRACE | eps S[renameto=CLASS delchild=CLASS,R_BRACE,L_BRACE] -> CLASS IDENTIFIER L_BRACE class_cnt R_BRACE | eps
// class_cnt -> public static void main(String[] args) { block_cnt } // class_cnt -> public static void main(String[] args) { block_cnt }
class_cnt[promote delifempty] -> PUBLIC STATIC VOID_TYPE IDENTIFIER_MAIN L_PAREN STRING_TYPE L_BRACKET R_BRACKET IDENTIFIER R_PAREN L_BRACE block_cnt R_BRACE | eps // remove all the stuff that is irrelevant to this simple case
class_cnt[promote delifempty delchild=L_BRACE,R_BRACE,L_PAREN,R_PAREN,L_BRACKET,R_BRACKET,IDENTIFIER,IDENTIFIER_MAIN,STRING_TYPE,VOID_TYPE,STATIC,PUBLIC] -> PUBLIC STATIC VOID_TYPE IDENTIFIER_MAIN L_PAREN STRING_TYPE L_BRACKET R_BRACKET IDENTIFIER R_PAREN L_BRACE block_cnt R_BRACE | eps
// block_ccnt -> stuff in {} | list of statements // block_ccnt -> stuff in {} | list of statements
block_cnt[promote delifempty] -> statement block_cnt | L_BRACE block_cnt R_BRACE | eps block_cnt[promote delifempty] -> statement block_cnt | L_BRACE block_cnt R_BRACE | eps
// statement -> stuff ending with ; | loop | condition // statement -> stuff ending with ; | loop | condition
statement[promote] -> stmt SEMICOLON | loop | cond statement[promote delchild=SEMICOLON] -> stmt SEMICOLON | loop | cond
stmt[promote] -> print | declaration | assignment stmt[promote] -> print | declaration | assignment
print -> PRINTLN par_expr print[nametoval=PRINTLN] -> PRINTLN par_expr
// declaration -> type IDENTIFIER = expr; // declaration -> type IDENTIFIER = expr;
declaration[nametoval=INTEGER_TYPE,BOOLEAN_TYPE,STRING_TYPE] -> type assignment declaration[nametoval=INTEGER_TYPE,BOOLEAN_TYPE,STRING_TYPE] -> type assignment
assignment[delchild=ASSIGN valtoval=IDENTIFIER] -> IDENTIFIER ASSIGN expr assignment[delchild=ASSIGN valtoval=IDENTIFIER] -> IDENTIFIER ASSIGN expr
// par_expr -> ( expr ) // par_expr -> ( expr )
par_expr -> L_PAREN expr R_PAREN // dont [promote] par_expr to make parenthesis-precedence easier
par_expr[delchild=L_PAREN,R_PAREN] -> L_PAREN expr R_PAREN
// expr -> expression that returns something | literal // expr -> expression that returns something | literal
expr[promote nametoval=ADD,SUB,MUL,DIV,MOD,AND,OR,NOT,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,EQUAL,NOT_EQUAL] -> expr_f expr_2 expr[promote nametoval=ADD,SUB,MUL,DIV,MOD,AND,OR,NOT,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL,EQUAL,NOT_EQUAL] -> expr_f expr_2
@ -87,7 +89,8 @@ expr_2[promote delifempty renameto=expr] -> op expr_f expr_2 | eps
expr_f[promote renameto=expr] -> unary expr_f | par_expr | val expr_f[promote renameto=expr] -> unary expr_f | par_expr | val
// Control-Flow // Control-Flow
// Don't [delifempty] single_or_braced to make it easier to get the expression
loop[delchild=WHILE] -> WHILE par_expr single_or_braced loop[delchild=WHILE] -> WHILE par_expr single_or_braced
cond[delchild=IF] -> IF par_expr single_or_braced cond_else cond[delchild=IF] -> IF par_expr single_or_braced cond_else
cond_else[delifempty delchild=ELSE] -> ELSE single_or_braced | eps cond_else[delifempty delchild=ELSE] -> ELSE single_or_braced | eps
single_or_braced -> L_BRACE block_cnt R_BRACE | stmt SEMICOLON single_or_braced[promote delchild=L_BRACE,R_BRACE] -> L_BRACE block_cnt R_BRACE | stmt SEMICOLON