From 848fcaac757a631d9300cf45d65175b16c2b0ccb Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 3 Oct 2026 14:03:48 -0700 Subject: Chapter 17a: Mutability and Read-Only Views Co-authored-by: Jun Kawasaki --- .../com/seaofnodes/simple/GlobalCodeMotion.java | 53 +-- src/main/java/com/seaofnodes/simple/IterPeeps.java | 25 +- src/main/java/com/seaofnodes/simple/Parser.java | 464 +++++++++++++-------- .../java/com/seaofnodes/simple/node/AddNode.java | 3 +- .../java/com/seaofnodes/simple/node/CFGNode.java | 90 ++-- .../java/com/seaofnodes/simple/node/CProjNode.java | 4 - .../java/com/seaofnodes/simple/node/DivFNode.java | 2 +- .../java/com/seaofnodes/simple/node/IfNode.java | 12 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 51 ++- .../java/com/seaofnodes/simple/node/LoopNode.java | 29 +- .../java/com/seaofnodes/simple/node/MemOpNode.java | 5 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 5 +- src/main/java/com/seaofnodes/simple/node/Node.java | 17 +- .../java/com/seaofnodes/simple/node/NotNode.java | 11 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 4 +- .../com/seaofnodes/simple/node/ReadOnlyNode.java | 26 ++ .../com/seaofnodes/simple/node/RegionNode.java | 68 +-- .../com/seaofnodes/simple/node/ReturnNode.java | 2 - .../java/com/seaofnodes/simple/node/ScopeNode.java | 180 ++++++-- .../java/com/seaofnodes/simple/node/ShlNode.java | 2 +- .../java/com/seaofnodes/simple/node/ShrNode.java | 5 + .../java/com/seaofnodes/simple/node/StartNode.java | 10 +- .../java/com/seaofnodes/simple/node/StopNode.java | 10 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 8 +- .../java/com/seaofnodes/simple/node/SubNode.java | 5 + .../simple/print/SimpleGraphAdapter.java | 2 +- .../simple/print/SimpleGraphObserver.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 22 +- src/main/java/com/seaofnodes/simple/type/Type.java | 32 +- .../java/com/seaofnodes/simple/type/TypeFloat.java | 6 +- .../com/seaofnodes/simple/type/TypeInteger.java | 3 + .../java/com/seaofnodes/simple/type/TypeMem.java | 8 +- .../com/seaofnodes/simple/type/TypeMemPtr.java | 47 ++- .../com/seaofnodes/simple/type/TypeStruct.java | 66 ++- .../java/com/seaofnodes/simple/BrainFuckTest.java | 80 ---- .../java/com/seaofnodes/simple/Chapter01Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 19 +- .../java/com/seaofnodes/simple/Chapter06Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 38 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 18 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 10 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 68 +-- .../java/com/seaofnodes/simple/Chapter11Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 46 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 40 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter14Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 89 ++-- .../java/com/seaofnodes/simple/Chapter16Test.java | 42 +- .../java/com/seaofnodes/simple/Chapter17aTest.java | 107 +++++ .../java/com/seaofnodes/simple/MutabilityTest.java | 57 +++ .../java/com/seaofnodes/simple/SchedulerTest.java | 6 +- src/test/java/com/seaofnodes/simple/TypeTest.java | 14 +- .../com/seaofnodes/simple/evaluator/Evaluator.java | 3 +- .../seaofnodes/simple/fuzzer/ScriptGenerator.java | 14 +- 57 files changed, 1149 insertions(+), 813 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java delete mode 100644 src/test/java/com/seaofnodes/simple/BrainFuckTest.java create mode 100644 src/test/java/com/seaofnodes/simple/Chapter17aTest.java create mode 100644 src/test/java/com/seaofnodes/simple/MutabilityTest.java (limited to 'src') diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index bb109fc9..eba99128 100644 --- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -16,43 +16,6 @@ public abstract class GlobalCodeMotion { schedLate( stop); } - // ------------------------------------------------------------------------ - // Backwards walk on the CFG only, looking for unreachable code - which has - // to be an infinite loop. Insert a bogus never-taken exit to Stop, so the - // loop becomes reachable. Also, set loop nesting depth - public static void fixLoops(StopNode stop) { - // Backwards walk from Stop, looking for unreachable code - BitSet visit = new BitSet(); - HashSet unreach = new HashSet<>(); - unreach.add(Parser.START); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - if( unreach.isEmpty() ) return; - - // Forwards walk from unreachable, looking for loops with no exit test. - visit.clear(); - for( CFGNode cfg : unreach ) - walkInfinite(cfg,visit,stop); - // Set loop depth on remaining graph - unreach.clear(); - visit.clear(); - for( Node ret : stop._inputs ) - ((ReturnNode)ret).walkUnreach(visit,unreach); - assert unreach.isEmpty(); - } - - - // Forwards walk over previously unreachable, looking for loops with no - // exit test. - static private void walkInfinite( CFGNode n, BitSet visit, StopNode stop ) { - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - if( n instanceof LoopNode loop ) - loop.forceExit(stop); - for( Node use : n._outputs ) - if( use instanceof CFGNode cfg ) - walkInfinite(cfg,visit,stop); - } // ------------------------------------------------------------------------ // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses @@ -66,18 +29,16 @@ public abstract class GlobalCodeMotion { // Reverse Post-Order on CFG for( int j=rpo.size()-1; j>=0; j-- ) { CFGNode cfg = rpo.get(j); - cfg.loopDepth(); // Pre-compute loop depth + cfg.loopDepth(); for( Node n : cfg._inputs ) _schedEarly(n,visit); // In dead infinite loops, entire code blocks may be unreachable // from below. Reach down from the CFG to their Phis so their // inputs are scheduled too. - if( cfg instanceof RegionNode ) { - int len = cfg.nOuts(); - for( int i=0; i * The theoretical overall worklist is mindless just grabbing the next thing and - * doing it. If the graph changes, put the neighbors on the worklist. Lather, - * Rinse, Repeat until the worklist runs dry. - * + * doing it. If the graph changes, put the neighbors on the worklist. + *
+ * Lather, Rinse, Repeat until the worklist runs dry. + *

* The main issues we have to deal with: * *

    @@ -28,7 +28,7 @@ import java.util.Random; * is stable and correct between peepholes. In our case `Node.subsume` does * most of the munging, building on our prior stable Node utilities. * - *
  • Changing a Node also changes the graph "neighborhood". The neigbors need to + *
  • Changing a Node also changes the graph "neighborhood". The neighbors need to * be checked to see if THEY can also peephole, and so on. After any peephole * or graph update we put a Nodes uses and defs on the worklist.
  • * @@ -38,7 +38,7 @@ import java.util.Random; * check is linear at each peephole step... so quadratic overall. Its a useful * assert, but one we can disable once the overall algorithm is stable - and * then turn it back on again when some new set of peepholes is misbehaving. - * The code for this is turned on in `IterOptim.iterate` as `assert + * The code for this is turned on in `IterPeeps.iterate` as `assert * progressOnList(stop);` *
*/ @@ -119,9 +119,12 @@ public abstract class IterPeeps { MID_ASSERT = true; int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; Node changed = stop.walk( n -> { - if( WORK.on(n) ) return null; - Node m = n.peepholeOpt(); - if( m==null ) return null; + Node m = n; + if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=5) ) { // Types must be forwards, even if on worklist + if( WORK.on(n) ) return null; + m = n.peepholeOpt(); + if( m==null ) return null; + } System.err.println("BREAK HERE FOR BUG"); return m; }); diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 41a3fcaa..e2df494e 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -4,7 +4,6 @@ import com.seaofnodes.graph.GraphObserver; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import static com.seaofnodes.simple.Utils.TODO; import java.util.*; @@ -99,12 +98,12 @@ public class Parser { public final Stack _xScopes = new Stack<>(); ScopeNode _continueScope; - ScopeNode _breakScope; + ScopeNode _breakScope; // Merge all the while-breaks here // Mapping from a type name to a Type. The string name matches // `type.str()` call. No TypeMemPtrs are in here, because Simple does not // have C-style '*ptr' references. - public final HashMap TYPES; + public static HashMap TYPES; // Mapping from a type name to the constructor for a Type. public final HashMap INITS; @@ -157,7 +156,7 @@ public class Parser { public static Node find(int nid) { return START.find(nid); } private Node ctrl() { return _scope.ctrl(); } - private Node ctrl(Node n) { return _scope.ctrl(n); } + private N ctrl(N n) { return _scope.ctrl(n); } public StopNode parse() { _xScopes.push(_scope); @@ -168,11 +167,11 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program - parseBlock(); + parseBlock(false); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); - _scope.pop(); + _scope.kill(); _xScopes.pop(); for( StructNode init : INITS.values() ) init.unkeep().kill(); @@ -191,9 +190,9 @@ public class Parser { * Does not parse the opening or closing '{}' * @return a {@link Node} or {@code null} */ - private Node parseBlock() { + private Node parseBlock(boolean inCon) { // Enter a new scope - _scope.push(); + _scope.push(inCon); while (!peek('}') && !_lexer.isEOF()) parseStatement(); // Exit scope @@ -212,17 +211,17 @@ public class Parser { private Node parseStatement() { if( false ) return null; else if (matchx("return") ) return parseReturn(); - else if (match ("{") ) return require(parseBlock(),"}"); + else if (match ("{") ) return require(parseBlock(false),"}"); else if (matchx("if") ) return parseIf(); else if (matchx("while") ) return parseWhile(); else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); else if (matchx(";") ) return null; // Empty statement - // declarations of vars with struct type are handled in parseExpressionStatement due - // to ambiguity - else return parseExpressionStatement(); + // Declaration or normal assignment/expression + else return parseDeclarationStatement(); } + /** * Parses a while statement * @@ -231,13 +230,13 @@ public class Parser { * * @return a {@link Node}, never {@code null} */ + private Node parseWhile() { + require("("); var savedContinueScope = _continueScope; var savedBreakScope = _breakScope; - require("("); - // 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 @@ -257,9 +256,11 @@ public class Parser { _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis // Parse predicate - var pred = require(parseExpression(), ")"); + var pred = parseAsgn(); + require(")"); + // IfNode takes current control and predicate - Node ifNode = new IfNode(ctrl(), pred).peephole(); + Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); // Setup projection nodes Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); @@ -270,6 +271,7 @@ public class Parser { // the loop predicate. Note that body Scope is still our current scope. ctrl(ifF); _xScopes.push(_breakScope = _scope.dup()); + _breakScope.addGuards(ifF,pred,true); // Up-cast predicate // No continues yet _continueScope = null; @@ -277,7 +279,9 @@ public class Parser { // Parse the true side, which corresponds to loop body // Our current scope is the body Scope ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate parseStatement(); // Parse loop body + _scope.removeGuards(ifT); // Merge the loop bottom into other continue statements if (_continueScope != null) { @@ -305,7 +309,8 @@ public class Parser { // the scope is the exit scope after the exit test. _xScopes.pop(); _xScopes.push(exit); - return _scope = exit; + _scope = exit; + return _scope; } private ScopeNode jumpTo(ScopeNode toScope) { @@ -322,14 +327,23 @@ public class Parser { return cur; // toScope is either the break scope, or a scope that was created here assert toScope._lexSize.size() <= _breakScope._lexSize.size(); - toScope.ctrl(toScope.mergeScopes(cur)); + toScope.ctrl(toScope.mergeScopes(cur).peephole()); return toScope; } private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); } - private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); } private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); } + 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 = require(jumpTo(_breakScope ),";"); + _breakScope.addGuards(_breakScope.ctrl(), null, false); + return _breakScope; + } /** * Parses a statement @@ -340,9 +354,12 @@ public class Parser { * @return a {@link Node}, never {@code null} */ private Node parseIf() { - require("("); // Parse predicate - var pred = require(parseExpression(), ")").keep(); + require("("); + var pred = require(parseAsgn(), ")"); + + pred.keep(); + // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes @@ -357,20 +374,26 @@ public class Parser { // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection - _scope.upcast(ifT,pred,false); // Up-cast predicate - parseStatement(); // Parse true-side + _scope.addGuards(ifT,pred,false); // Up-cast predicate + parseStatement(); // Parse true-side + _scope.removeGuards(ifT); + ScopeNode tScope = _scope; // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ifF.unkeep(); // fScope already owns the false control - _scope.upcast(ifF,pred,true); // Up-cast predicate - if (matchx("else")) { - parseStatement(); + // Up-cast predicate, even if not else clause, because predicate can + // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` + _scope.addGuards(ifF,pred,true); + boolean doRHS = matchx("else"); + if( doRHS ) parseStatement(); + _scope.removeGuards(ifF); + if( doRHS ) fScope = _scope; - } pred.unkeep(); + // Check for `if(pred) int x=17;` if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) throw error("Cannot define a new name on one arm of an if"); @@ -378,10 +401,12 @@ public class Parser { _scope = tScope; _xScopes.pop(); // Discard pushed from graph display - return ctrl(tScope.mergeScopes(fScope)); + RegionNode r = ctrl(tScope.mergeScopes(fScope)); + Node ret = r; + r.peephole(); + return ret; } - /** * Parses a return statement; "return" already parsed. * The $ctrl edge is killed. @@ -392,7 +417,7 @@ public class Parser { * @return an expression {@link Node}, never {@code null} */ private Node parseReturn() { - var expr = require(parseExpression(), ";"); + var expr = require(parseAsgn(), ";"); Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); ctrl(XCTRL); // Kill control return ret; @@ -400,89 +425,113 @@ public class Parser { - /** Parse: name [=expr] + /** Parse: [name '='] expr */ - private Node parseAsgn(Type t) { - boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name - int old = _lexer._position; - String name = requireId(); - Node expr; - if( peek(';') || peek(',') ) { - // Bare "name" is not allowed - if( t==null ) throw errorSyntax("expression"); - // Does this declaration need an initializer and not getting one? - Type init = t instanceof TypeMemPtr tmp && !tmp._nil - // Must be initialized in the "new" constructor - ? Type.TOP - // Else takes the default - : t.makeInit(); - expr = con(init); - - } else if( !match("=") ) { // Something else - _lexer._position = old; - return null; - } else - expr = parseExpression(); - - // Defining a new variable vs updating an old one - if( !isDecl ) { // Assigning over an existing name - // Lookup - ScopeNode.Var def = _scope.lookup(name); - if( def==null ) - throw error("Undefined name '" + name + "'"); - t = def._type; // Declared field type - } + private Node parseAsgn() { + int old = pos(); + String name = _lexer.matchId(); + // Just a plain expression, no assignment. + // Distinguish `var==expr` from `var=expr` + if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) + { pos(old); return parseExpression(); } + + // Parse assignment expression + Node expr = parseAsgn(); + + // Final variable to update + ScopeNode.Var def = _scope.lookup(name); + if( def==null ) + throw error("Undefined name '" + name + "'"); + + // TOP fields are for late-initialized fields; these have never + // been written to, and this must be the final write. Other writes + // outside the constructor need to check the final bit. + if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.canInit(def) ) + throw error("Cannot reassign final '"+name+"'"); + + // Lift expression, based on type + Node lift = liftExpr(expr, def.type()); + // Update + _scope.update(name,lift); + // Return un-lifted expr + return expr; + } + // Apply declared access permissions; widen floats and narrow integers. + // Early error if types do not match variable. + private Node liftExpr( Node expr, Type t ) { + assert !(expr._type instanceof TypeMemPtr tmp) || !tmp.isFRef(); + // Read-only access is deep; a fixed binding alone does not remove access. + if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) { + expr = peep(new ReadOnlyNode(expr)); + } // Auto-widen int to float - if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) - expr = new ToFloatNode(expr).peephole(); + expr = widenInt( expr, t ); // Auto-narrow wide ints to narrow ints expr = zsMask(expr,t); - // Auto-deepen forward ref types - Type e = expr._type; - if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null ) - e = TYPES.get(tmp._obj._name); // Type is sane - if( !e.isa(t) ) - throw error("Type " + e.str() + " is not of declared type " + t.str()); - - if( isDecl ) { - if( !_scope.define(name,t,false,expr) ) - throw error("Redefining name '" + name + "'"); - } else - _scope.update(name,expr); + if( !expr._type.isa(t) || !expr._type.accessISA(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); return expr; } - /** - * Parses an expression statement or a declaration statement where type is a struct - * - *
-     * type decl [, decl]*;  // Define many names with the same type
-     * asgn;                 // Assign or load a variable
-     * expr;                 // Something else
-     * name;                 // Bare name is an error
+    private Node widenInt( Node expr, Type t ) {
+        return expr._type instanceof TypeInteger && t instanceof TypeFloat
+            ? peep(new ToFloatNode(expr)) : expr;
+    }
 
-     * 
- * @return an expression {@link Node}, never {@code null} + /** + * Parse declaration or expression statement + * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';' + *

+ * exprAsgn = var '=' exprAsgn | expr */ - private Node parseExpressionStatement() { - Node n; + private Node parseDeclarationStatement() { Type t = type(); - if( t != null ) { - // Parse comma-separated declarations - n = parseAsgn(t); - while( match(",") ) - n = parseAsgn(t); - - // Parse "asgn;" which is just "name = expr;" - } else if( ( n = parseAsgn(null)) == null ) { - // Something else - n = parseExpression(); - } + if( t == null ) + return require(parseAsgn(),";"); + + // now parse var['=' asgnexpr] in a loop + Node n = parseDeclaration(t); + while( match(",") ) + n = parseDeclaration(t); return require(n,";"); } + // The binding modifier sits immediately before the name. It never + // changes permissions on the referenced object. + private boolean bindingFinal(Type t) { + if( match("!") ) return false; + if( match("~") ) return true; + return t instanceof TypeMemPtr; + } + + /** Parse final: [!|~]var['=' asgn] + */ + private Node parseDeclaration(Type t) { + assert t!=null; + boolean xfinal = bindingFinal(t); + String name = requireId(); + + // Optional initializing expression follows + Node expr; + if( match("=") ) { + expr = parseAsgn(); + + } else { + expr = con(xfinal && !(t instanceof TypeMemPtr) ? Type.TOP : t.makeInit()); + } + + // Lift expression, based on type + Node lift = liftExpr(expr, t); + + // Define a new name, + if( !_scope.define(name,t,xfinal,lift) ) + throw error("Redefining name '" + name + "'"); + return null; + } + + /** * Parse a struct declaration, and return the following statement. @@ -495,11 +544,12 @@ public class Parser { if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); String typeName = requireId(); Type t = TYPES.get(typeName); - if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) + if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) ) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); // A Block scope parse, and inspect the scope afterward for fields. - _scope.push(); + _scope.push(true); + _xScopes.push(_scope); require("{"); while (!peek('}') && !_lexer.isEOF()) parseStatement(); @@ -512,7 +562,7 @@ public class Parser { for( int i=lexlen; i - * expr : compareExpr + * expr : bitwise * * @return an expression {@link Node}, never {@code null} */ @@ -612,7 +684,7 @@ public class Parser { else if( match("^") ) lhs = new XorNode(lhs,null); else break; lhs.setDef(2,parseComparison()); - lhs = lhs.peephole(); + lhs = peep(lhs); } return lhs; } @@ -640,9 +712,9 @@ public class Parser { else break; // Peepholes can fire, but lhs is already "hooked", kept alive lhs.setDef(idx,parseShift()); - lhs = lhs.widen().peephole(); + lhs = peep(lhs.widen()); if( negate ) // Extra negate for != - lhs = new NotNode(lhs).peephole(); + lhs = peep(new NotNode(lhs)); } return lhs; } @@ -664,7 +736,7 @@ public class Parser { else if( match(">>") ) lhs = new SarNode(lhs,null); else break; lhs.setDef(2,parseAddition()); - lhs = lhs.widen().peephole(); + lhs = peep(lhs.widen()); } return lhs; } @@ -685,7 +757,7 @@ public class Parser { else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); - lhs = lhs.widen().peephole(); + lhs = peep(lhs.widen()); } return lhs; } @@ -706,7 +778,7 @@ public class Parser { else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); - lhs = lhs.widen().peephole(); + lhs = peep(lhs.widen()); } return lhs; } @@ -715,13 +787,13 @@ public class Parser { * Parse a unary minus expression. * *

-     *     unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
+     *     unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
      * 
* @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { - if (match("-")) return new MinusNode(parseUnary()).widen().peephole(); - if (match("!")) return new NotNode(parseUnary()).peephole(); + if( match("-") ) return peep(new MinusNode(parseUnary()).widen()); + if( match("!") ) return peep(new NotNode(parseUnary())); return parsePostfix(parsePrimary()); } @@ -729,23 +801,33 @@ public class Parser { * Parse a primary expression: * *
-     *     primaryExpr : integerLiteral | Identifier | true | false | null | new Type | '(' expression ')'
+     *     primaryExpr : integerLiteral | true | false | null | new Type | '(' expression ')' | Id['++','--']
      * 
* @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return con(1); if( matchx("false") ) return ZERO; if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR); + if( match("(") ) return require(parseAsgn(), ")"); if( matchx("new" ) ) return alloc(); // Expect an identifier now - String name = _lexer.matchId(); - if( name == null) throw errorSyntax("an identifier or expression"); - ScopeNode.Var n = _scope.lookup(name); - if( n!=null ) return _scope.in(n); - throw error("Undefined name '" + name + "'"); + ScopeNode.Var n = requireLookupId("an identifier or expression"); + Node rvalue = _scope.in(n); + if( rvalue._type == Type.BOTTOM ) + throw error("Cannot read uninitialized field '"+n._name+"'"); + return rvalue; + } + + ScopeNode.Var requireLookupId(String msg) { + String id = _lexer.matchId(); + if( id == null || KEYWORDS.contains(id) ) + throw errorSyntax(msg); + ScopeNode.Var n = _scope.lookup(id); + if( n==null ) + throw error("Undefined name '" + id + "'"); + return n; } /** @@ -756,7 +838,7 @@ public class Parser { if( t==null ) throw error("Expected a type"); // Parse ary[ length_expr ] if( match("[") ) { - Node len = parseExpression().keep(); + Node len = parseAsgn(); if( !(len._type instanceof TypeInteger) ) throw error("Cannot allocate an array with length "+len._type); require("]"); @@ -781,15 +863,15 @@ public class Parser { // Push a scope, and pre-assign all struct fields. _scope.push(); for( int i=0; i