summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/parser/Statements.java
blob: 4ff4993b04838cc118139026146510113cfa48d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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"); }
}