summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/parser/Statements.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/parser/Statements.java')
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Statements.java272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/main/java/com/seaofnodes/simple/parser/Statements.java b/src/main/java/com/seaofnodes/simple/parser/Statements.java
new file mode 100644
index 00000000..4ff4993b
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/parser/Statements.java
@@ -0,0 +1,272 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+
+/** Blocks, statements, and loops; builds their control-flow scopes. */
+final class Statements {
+ private final Parser P;
+
+ // Merge all the while-breaks here
+ ScopeNode _breakScope;
+ // Merge all loop continues here.
+ ScopeNode _continueScope;
+
+ Statements(Parser P) { this.P = P; }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ Node parseStatement() {
+ if( P.matchx("return") ) return parseReturn();
+ else if (P.matchx("if") ) return parseIf();
+ else if (P.matchx("while") ) return parseWhile();
+ else if (P.matchx("for") ) return parseFor();
+ else if (P.matchx("break") ) return parseBreak();
+ else if (P.matchx("continue")) return parseContinue();
+ else if (P.matchx("struct") ) return P.structs.parseStruct();
+ else if (P.structs.inStructDeclaration() && P.matchx("new")) return P.structs.parseConstructorDeclaration();
+ else if (P.matchx(";") ) return P._code.ZERO; // Empty statement
+ // Break ambiguity around leading function types and starting a block
+ else if (P.peek('{') && !P.types.isTypeFun() ) {
+ P.match("{");
+ return P.require(parseBlock(new Kind.Block()),"}");
+ }
+ // Declaration or normal assignment/expression
+ else return P.declarations.parseDeclarationStatement();
+ }
+
+ /**
+ * Parses a block
+ *
+ * <pre>
+ * '{' statements '}'
+ * </pre>
+ * Does not parse the opening or closing '{}'
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseBlock(Kind kind) {
+ // Enter a new scope
+ P._scope.push(kind);
+ Node last = P._code.ZERO;
+ while (!P.peek('}') && !P._lexer.isEOF()) {
+ P._code.add(last);
+ last = parseStatement();
+ }
+ // Exit scope
+ last.keep();
+ P._scope.pop();
+ return last.unkeep();
+ }
+
+ /**
+ * Parses a return statement; "return" already parsed.
+ * The $ctrl edge is killed.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseReturn() {
+ var expr = P.require(P.expressions.parseAssignment(), ";");
+ return P.functions.addReturn(expr);
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseIf() {
+ // Parse predicate
+ P.require("(");
+ var pred = P.require(P.expressions.parseAssignment(), ")");
+ return P.controlflow.conditional(pred,"else");
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ P.require("(");
+ return parseLooping(false);
+ }
+
+ /**
+ * Parses a for statement
+ *
+ * <pre>
+ * for( var x=init; test; incr ) body
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseFor() {
+ // { var x=init,y=init,...;
+ // while( test ) {
+ // body;
+ // next;
+ // }
+ // }
+ P.require("(");
+ P._scope.push(new Kind.Block()); // Scope for the index variables
+ if( !P.match(";") ) // Can be empty init "for(;test;next) body"
+ P.declarations.parseDeclarationStatement(); // Non-empty init
+ Node rez = parseLooping(true);
+ P._scope.pop(); // Exit index variable scope
+ return rez;
+ }
+
+ // Shared by `for` and `while`
+ private Node parseLooping( boolean doFor ) {
+
+ var savedContinueScope = _continueScope;
+ var savedBreakScope = _breakScope;
+
+ // Loop region has two control inputs, the first is the entry
+ // point, and second is back edge that is set after loop is parsed
+ // (see end_loop() call below). Note that the absence of back edge is
+ // used as an indicator to switch off peepholes of the region and
+ // associated phis; see {@code inProgress()}.
+
+ P.ctrl(new LoopNode(P.loc(),null,P.ctrl()).peephole()); // Note we set back edge to null here
+
+ // At loop head, we clone the current Scope (this includes all
+ // names in every nesting level within the Scope).
+ // We create phis eagerly for all the names we find, see dup().
+
+ // Save the current scope as the loop head
+ ScopeNode head = P._scope.keep();
+ // Clone the head Scope to create a new Scope for the body.
+ // Create phis eagerly as part of cloning
+ P._xScopes.push(P._scope = P._scope.dup(true)); // The true argument triggers creating phis
+
+ // Parse predicate
+ var pred = P.peek(';') ? P.con(1) : P.expressions.parseAssignment();
+ P.require( doFor ? ";" : ")" );
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(P.ctrl(), pred.keep()).peephole();
+ // Setup projection nodes
+ CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+
+ // for( ;;next ) body
+ int nextPos = -1, nextEnd = -1;
+ if( doFor ) {
+ // Skip the next expression and parse it later
+ nextPos = P.pos();
+ P._lexer.skipTo('(',')',"for update");
+ nextEnd = P.pos();
+ P.require(")");
+ }
+
+ // Clone the body Scope to create the break/exit Scope which accounts for any
+ // side effects in the predicate. The break/exit Scope will be the final
+ // scope after the loop, and its control input is the False branch of
+ // the loop predicate. Note that body Scope is still our current scope.
+ P.ctrl(ifF);
+ P._xScopes.push(_breakScope = P._scope.dup());
+ _breakScope.addGuards(ifF,pred,true); // Up-cast predicate
+
+ // No continues yet
+ _continueScope = null;
+
+ // Parse the true side, which corresponds to loop body
+ // Our current scope is the body Scope
+ P.ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ P._scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate
+ parseStatement().isKill(); // Parse loop body
+ P._scope.removeGuards(ifT);
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ P._scope.kill();
+ P._scope = _continueScope;
+ }
+
+ // Now append the next code onto the body code
+ if( doFor ) {
+ int old = P.pos(nextPos);
+ if( !P.peek(')') )
+ P.expressions.parseAssignment();
+ if( P.pos() != nextEnd )
+ throw P.errorSyntax( "Unexpected code after expression" );
+ P.pos(old);
+ }
+
+ // The true branch loops back, so whatever is current _scope.ctrl gets
+ // added to head loop as input. endLoop() updates the head scope, and
+ // goes through all the phis that were created earlier. For each phi,
+ // it sets the second input to the corresponding input from the back
+ // edge. If the phi is redundant, it is replaced by its sole input.
+ var exit = _breakScope;
+ head.balanceLoopFRefs(P._scope);
+ exit.balanceLoopFRefs(P._scope);
+ head.endLoop(P._scope, exit);
+ head.unkeep().kill();
+
+ P._xScopes.pop(); // Cleanup
+ P._xScopes.pop(); // Cleanup
+
+ _continueScope = savedContinueScope;
+ _breakScope = savedBreakScope;
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ // During sys parsing, there is no xscope here.
+ if( !P._xScopes.isEmpty() ) {
+ P._xScopes.pop();
+ P._xScopes.push( exit );
+ }
+ P._scope = exit;
+ return P._code.ZERO;
+ }
+ private Node parseBreak () {
+ checkLoopActive();
+ // At the time of the break, and loop-exit conditions are only valid if
+ // they are ALSO valid at the break. It is the intersection of
+ // conditions here, not the union.
+ _breakScope.removeGuards(_breakScope.ctrl());
+ _breakScope = P.require(jumpTo(_breakScope ),";");
+ _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false);
+ return P._code.ZERO;
+ }
+
+ private Node parseContinue() { checkLoopActive(); _continueScope = P.require(jumpTo( _continueScope ),";"); return P._code.ZERO; }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = P._scope.dup();
+ P.ctrl(P._code.XCTRL); // Kill current scope
+ // Prune nested lexical scopes that have depth > than the loop head
+ // We use _breakScope as a proxy for the loop head scope to obtain the depth
+ while( cur.depth() > _breakScope.depth() )
+ cur.pop();
+ // If this is a continue then first time the target is null
+ // So we just use the pruned current scope as the base for the
+ // "continue"
+ if( toScope == null )
+ return cur;
+ // toScope is either the break scope, or a scope that was created here
+ assert toScope.depth() <= _breakScope.depth();
+ toScope.ctrl(toScope.mergeScopes(cur,P.loc()).peephole());
+ return toScope;
+ }
+
+ private void checkLoopActive() { if (_breakScope == null) throw P.error("No active loop for a break or continue"); }
+}