package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.node.ScopeNode.Kind; import java.util.*; /** * The Parser converts a Simple source program to the Sea of Nodes intermediate * representation directly in one pass. There is no intermediate Abstract * Syntax Tree structure. *

* This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { public static ConstantNode ZERO; // Very common node, cached here public static ConstantNode NIL; // Very common node, cached here public static XCtrlNode XCTRL; // Very common node, cached here // Compile driver public final CodeGen _code; // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private Lexer _lexer; /** * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how * we handle branching. See {@link #parseIf()}. *

* Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds * variable names to Nodes. The top of this stack represents current scope. *

* We keep a list of all ScopeNodes so that we can show them in graphs. * @see #parseIf() * @see #_xScopes */ public ScopeNode _scope; /** * List of keywords disallowed as identifiers */ public static final HashSet KEYWORDS = new HashSet<>(){{ add("bool"); add("break"); add("byte"); add("continue"); add("else"); add("f32"); add("f64"); add("false"); add("flt"); add("i16"); add("i32"); add("i64"); add("i8"); add("if"); add("int"); add("new"); add("null"); add("return"); add("struct"); add("true"); add("u1"); add("u16"); add("u32"); add("u8"); add("while"); }}; /** * We clone ScopeNodes when control flows branch; it is useful to have * a list of all active ScopeNodes for purposes of visualization of the SoN graph */ public final Stack _xScopes = new Stack<>(); ScopeNode _continueScope; ScopeNode _breakScope; // Merge all the while-breaks here FunNode _fun; // Current function being parsed // Mapping from a type name to a Type. The string name matches // `type.str()` call. public static HashMap TYPES = defaultTypes(); // Mapping from a type name to the constructor for a Type. public final HashMap INITS; public Parser(CodeGen code, TypeInteger arg) { _code = code; _scope = new ScopeNode(); _continueScope = _breakScope = null; ZERO = con(TypeInteger.ZERO).keep(); NIL = con(Type.NIL).keep(); XCTRL= new XCtrlNode().peephole().keep(); TYPES = defaultTypes(); INITS = new HashMap<>(); } @Override public String toString() { return _lexer.toString(); } public static HashMap defaultTypes() { return new HashMap<>() {{ put("bool",TypeInteger.U1 ); put("byte",TypeInteger.U8 ); put("f32" ,TypeFloat .F32); put("f64" ,TypeFloat .F64); put("flt" ,TypeFloat .F64); put("i16" ,TypeInteger.I16); put("i32" ,TypeInteger.I32); put("i64" ,TypeInteger.BOT); put("i8" ,TypeInteger.I8 ); put("int" ,TypeInteger.BOT); put("u1" ,TypeInteger.U1 ); put("u16" ,TypeInteger.U16); put("u32" ,TypeInteger.U32); put("u8" ,TypeInteger.U8 ); put("val" ,Type.TOP); // Marker type, indicates type inference put("var" ,Type.BOTTOM); // Marker type, indicates type inference }}; } // Debugging utility to find a Node by index public Node f(int nid) { return _code.f(nid); } private Node ctrl() { return _scope.ctrl(); } private N ctrl(N n) { return _scope.ctrl(n); } public void parse() { // Graph callbacks during scope setup need a source position. _lexer = new Lexer(sys.SYS); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); _scope.mem(con(TypeMem.BOT)); // Parse the sys import while( !_lexer.isEOF() ) { parseStatement(); _lexer.skipWhiteSpace(); } // Reset lexer for program text _lexer = new Lexer(_code._src); _xScopes.push(_scope); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody( _code._main,loc(),"arg"); // Kill an empty default main. Keep only if it was explicitly defined // (programmer asked for a "main") or it has stuff (i.e. beginner // default main). FunNode main = _code.link(_code._main); StopNode stop = _code._stop; // Gather an explicit main FunNode xmain = null; for( Node n : stop._inputs ) if( n instanceof ReturnNode ret && "main".equals(ret.fun()._name) ) if( xmain != null ) throw Utils.TODO(); else xmain = ret.fun(); if( main.ret().expr()._type==Type.TOP && main.uctrl()==null ) { // Kill an empty default main; so it does not attempt to put a // "main" in any final ELF file main.setDef(1,XCTRL); // Delete default start input stop.delDef(stop._inputs.find(main.ret())); if( xmain != null ) _code.setMain(xmain); } else { // We have a non-empty default main. if( xmain != null ) // Found an explicit "main" AND we have a default "main" throw error("Cannot define both an explicit main and a default main"); main._name = "main"; } if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); // Clean up and reset _xScopes.pop(); _scope.kill(); for( StructNode init : INITS.values() ) init.unkeep().kill(); INITS.clear(); for( Ary captures : CAPTURES.values() ) for( Node value : captures ) value.unkill(); CAPTURES.clear(); stop.peephole(); // Helpers are built with init(), so their complete call graph exists // before any inlining. Seed those nodes now along with the parsed body. _code._start.walk(n -> { _code.add(n); return null; }); } /** * Parses a function body, assuming the header is parsed. */ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) { return parseFunctionBody(sig, loc, null, ids); } private FunNode _privateFun; private HashMap _fieldVars; private record Constructor(TypeFunPtr sig, Lexer loc, int body, String[] ids, java.util.BitSet finals) {} private Constructor _constructor; private final HashMap ALLOCS = new HashMap<>(); private final HashMap INITIALIZERS = new HashMap<>(); private final HashMap> CAPTURES = new HashMap<>(); private Ary _captures; private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) { return parseFunctionBody(sig,loc,finals,false,ids); } private ReturnNode parseFunctionBody(TypeFunPtr sig, Lexer loc, java.util.BitSet finals, boolean initializer, String... ids) { // If this is a method, record & restore the existing struct fields, // which are also the struct initializers. The method might update // mutable fields, so when the function parse is done reset back to // pre-function-parse values. Node[] preParse = new Node[_scope.nIns()]; for( int i=0; i<_scope.nIns(); i++ ) preParse[i] = _scope.in(i)==null ? null : _scope.in(i).keep(); // Stack parser state on the local Java stack, and unstack it later FunNode oldfun = _fun; FunNode oldPrivate = _privateFun; HashMap oldFields = _fieldVars; Constructor oldConstructor = _constructor; _fieldVars = new HashMap<>(); _constructor = null; Ary oldCaptures = _captures; _captures = initializer ? new Ary<>(Node.class) : null; ScopeNode breakScope = _breakScope; _breakScope = null; ScopeNode continueScope = _continueScope; _continueScope = null; FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start)); // Once the function header is available, install in linker table - // allowing recursive functions. Linker matches on declared args and // exact fidx, and ignores the return (because the fidx will only match // the exact single function). _code.link(fun); _privateFun = sig.ret() instanceof TypeMem ? fun : null; fun._constructor = _privateFun!=null; Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole(); // Build a multi-exit return point for all function returns RegionNode r = new RegionNode( null,null,null).init(); assert r.inProgress(); PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = (sig.ret() instanceof TypeMem ? new BulkMemPhiNode("#selfMem",r,null) : new PhiNode(ScopeNode.ARG0,r,null)).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); assert ret.inProgress(); _code._stop.addDef(ret); // Pre-call the function from Start, with worse-case arguments. This // represents all the future, yet-to-be-parsed functions calls and // external calls. _scope.push(new Kind.Func()); ctrl(fun); // Scope control from function // Unknown callers supply whole memory; field aliases are discovered lazily. _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i=0; j-- ) if( _scope.var(j)._name.equals(f._fname) ) { v=_scope.var(j); break; } assert v!=null; _fieldVars.put(f._fname,v); if( !f._one ) { Node value=missingInit(defaults.in(i),new java.util.BitSet()) ? con(Type.BOTTOM) : new LoadNode(loc(),f._fname,f._alias,privateMem(),self(),off(shape,f._fname),true).peephole(); _scope.update(v,value); } } } Node last=ZERO; while (!peek('}') && !_lexer.isEOF()) { last = parseStatement(); if( initializer ) fields = initFields(fields); } if( initializer ) { finishFields(); CAPTURES.put(_nestedType,_captures); if( _constructor!=null ) { Constructor ctor=_constructor; int end=pos(ctor.body()); ReturnNode body=parseFunctionBody(ctor.sig(),ctor.loc(),ctor.finals(),ctor.ids()); body.fun().setName(shortName(_nestedType)); require("}"); pos(end); } } if( _privateFun==fun ) last = storeFields(initializer); // Last expression is the return except for the top-level main if( ctrl()._type==Type.CONTROL ) if( fun.sig() != _code._main ) fun.addReturn(ctrl(), _scope.mem(), last); else { fun.setDef(1,XCTRL); // Kill default main _code.addAll(fun._outputs); } // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); r ._inputs.pop(); rmem._inputs.pop(); rrez._inputs.pop(); r._loc = loc(); // Final position assert !r.inProgress(); // Force peeps, which have been avoided due to inProgress ret.setDef(1,rmem.peephole()); ret.setDef(2,rrez.peephole()); ret.setDef(0,r.peephole()); ret = (ReturnNode)ret.peephole(); // Function scope ends if( initializer ) _scope.pop(); _scope.pop(); _fun = oldfun; _privateFun = oldPrivate; _fieldVars = oldFields; _constructor = oldConstructor; _captures = oldCaptures; _breakScope = breakScope; _continueScope = continueScope; // Reset all fields to pre-parse days for( int i=0; i * '{' statements '}' * * Does not parse the opening or closing '{}' * @return a {@link Node} or {@code null} */ private Node parseBlock(Kind kind) { // Enter a new scope _scope.push(kind); Node last = ZERO; while (!peek('}') && !_lexer.isEOF()) last = parseStatement(); // Exit scope last.keep(); _scope.pop(); return last.unkeep(); } /** * Parses a statement * *

     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
     * 
* @return a {@link Node} or {@code null} */ private Node parseStatement() { if( matchx("return") ) return parseReturn(); else if (matchx("if") ) return parseIf(); else if (matchx("while") ) return parseWhile(); else if (matchx("for") ) return parseFor(); else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); else if (_scope.inConstructor() && matchx("new")) return parseConstructorDeclaration(); else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { match("{"); return require(parseBlock(new Kind.Block()),"}"); } // Declaration or normal assignment/expression else return parseDeclarationStatement(); } // In a struct declaration, new names the constructor. Its body still // parses as a function; in expressions, new allocates and calls it. private Node parseConstructorDeclaration() { Lexer loc=loc(); String name=requireId(); String expect=shortName(_nestedType); if( !name.equals(expect) ) throw error("Constructor name '"+name+"' must match type name '"+expect+"'"); require("="); Node ctor=constructorFunc(); TypeFunPtr sig=(TypeFunPtr)ctor._type; Node lift=liftExpr(ctor,sig,true); if( !_scope.define(name,sig,true,lift,loc) ) throw error("Redefining constructor '"+name+"'"); _scope.lookup(name)._init=true; return require(lift,";"); } /** * Parses a while statement * *
     *     while ( expression ) statement
     * 
* @return a {@link Node}, never {@code null} */ private Node parseWhile() { require("("); return parseLooping(false); } /** * Parses a for statement * *
     *     for( var x=init; test; incr ) body
     * 
* @return a {@link Node}, never {@code null} */ private Node parseFor() { // { var x=init,y=init,...; // while( test ) { // body; // next; // } // } require("("); _scope.push(new Kind.Block()); // Scope for the index variables if( !match(";") ) // Can be empty init "for(;test;next) body" parseDeclarationStatement(); // Non-empty init Node rez = parseLooping(true); _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()}. ctrl(new LoopNode(loc(),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 = _scope.keep(); // Clone the head Scope to create a new Scope for the body. // Create phis eagerly as part of cloning _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis // Parse predicate var pred = peek(';') ? con(1) : parseAsgn(); require( doFor ? ";" : ")" ); // IfNode takes current control and predicate 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(); // for( ;;next ) body int nextPos = -1, nextEnd = -1; if( doFor ) { // Skip the next expression and parse it later nextPos = pos(); skipAsgn(); nextEnd = pos(); 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. ctrl(ifF); _xScopes.push(_breakScope = _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 ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate parseStatement().isKill(); // Parse loop body _scope.removeGuards(ifT); // Merge the loop bottom into other continue statements if (_continueScope != null) { _continueScope = jumpTo(_continueScope); _scope.kill(); _scope = _continueScope; } // Now append the next code onto the body code if( doFor ) { int old = pos(nextPos); if( !peek(')') ) parseAsgn(); if( pos() != nextEnd ) throw errorSyntax( "Unexpected code after expression" ); 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.endLoop(_scope, exit); head.unkeep().kill(); _xScopes.pop(); // Cleanup _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( !_xScopes.isEmpty() ) { _xScopes.pop(); _xScopes.push( exit ); } _scope = exit; return ZERO; } private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); ctrl(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,loc()).peephole()); return toScope; } private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return 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 = require(jumpTo(_breakScope ),";"); _breakScope.addGuards(_breakScope.ctrl(), null, false); return ZERO; } // Look for an unbalanced `)`, skipping balanced private void skipAsgn() { int paren=0; while( true ) // Next X char handles skipping complex comments switch( _lexer.nextXChar() ) { case Character.MAX_VALUE: throw Utils.TODO(); case ')': if( --paren<0 ) { posT( pos() - 1 ); return; // Leave the `)` behind } break; case '(': paren ++; break; default: break; } } /** * Parses a statement * *
     *     if ( expression ) statement [else statement]
     * 
* @return a {@link Node}, never {@code null} */ private Node parseIf() { // Parse predicate require("("); var pred = require(parseAsgn(), ")"); return parseTrinary(pred,"else"); } // Parse a conditional expression, merging results. private Node parseTrinary( Node pred, String fside ) { // IfNode takes current control and predicate pred.keep(); Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope fScope.ctrl(ifF); // The pending arm already starts at False _xScopes.push(fScope); // For graph visualization we need all scopes // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.addGuards(ifT,pred,false); // Up-cast predicate // Parse true-side flavor Node lhs = switch( fside ) { case "else" -> parseStatement(); // if( pred ) stmts; case ":" -> parseAsgn(); // pred ? asgn; case "&&" -> parseLogical(); // pred && expr case "||" -> pred; // pred || expr_is_ignored default -> throw Utils.TODO(); }; lhs.keep(); _scope.removeGuards(ifT); // See if a one-sided def was made: "if(pred) int x = 1;" and throw. // See if any forward-refs were made, and copy them to the other side: // "pred ? n*fact(n-1) : 1" fScope.balanceIf(_scope); 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 // 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); // Parse false-side flavor boolean doRHS = false; // RHS is optional for if/else and trinary Node rhs = switch( fside ) { case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); case "&&" -> rhs = pred; case "||" -> rhs = parseLogical(); default -> throw Utils.TODO(); }; rhs.keep(); _scope.removeGuards(ifF); if( doRHS || fside.equals("||") ) fScope = _scope; pred.unkeep(); // Check the trinary widening int/flt if( !fside.equals("else") ) { rhs = widenInt( rhs.unkeep(), lhs._type ).keep(); lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); } _scope = tScope; _xScopes.pop(); // Discard pushed from graph display // See if a one-sided def was made: "if(pred) int x = 1;" and throw. // See if any forward-refs were made, and copy them to the other side: // "pred ? n*fact(n-1) : 1" tScope.balanceIf(fScope); // Merge results RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep())); // Immediately fail e.g. `arg ? 7 : ptr` ParseException err; if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; r.peephole(); return ret; } /** * Parses a return statement; "return" already parsed. * The $ctrl edge is killed. * *
     *     'return' expr ;
     * 
* @return an expression {@link Node}, never {@code null} */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); // Force the lazy whole-memory Phi before closing this exit. if( _privateFun==_fun ) throw error("A constructor cannot return a value"); _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } /** Parse: [name '='] expr */ private Node parseAsgn() { int old = pos(); String name = _lexer.matchId(); int old_pre_equals = pos(); // Just a plain expression, no assignment. // Distinguish `var==expr` from `var=expr` if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) { pos(old); return parseExpression(); } // Find variable to update Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + name + "'"); // If a method var, allow nested blocks and methods, but not fcns or other structs. // Else safety check for accessing fields out of function scope. if( instance(def) ) { pos(old_pre_equals); // setup for `self.name = expr` Var selfVar = _scope.lookup("self"); Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); return parsePostfixName(self,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 && // Inside an allocation, final assign is OK, outside nope. // The alloc() call added the allocation scope !_scope.canInit(def) ) throw error("Cannot reassign final '"+name+"'"); // Parse assignment expression checkCaptureWrite(def); Node expr = parseAsgn(); // Lift expression, based on type Node lift = liftExpr(expr.keep(), def.type(), true); // Update _scope.update(name,lift); // Return un-lifted expr return expr.unkeep(); } // Apply declared access permissions; widen floats and narrow integers. // Early error if types do not match variable. private Node liftExpr(Node expr, Type t, boolean isLoad) { if( t==Type.BOTTOM || t==Type.TOP ) return expr; // Constructor sentinels record missing initialization, not a value // needing conversion. Keep them visible to the completion check. if( expr instanceof ConstantNode && !(expr instanceof FRefNode) && (expr._type==Type.TOP || expr._type==Type.BOTTOM) ) return expr; if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) expr = peep(new ReadOnlyNode(expr)); if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) return expr; return peep(new ConvertNode(t,expr)); } private Node widenInt( Node expr, Type t ) { return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat ? peep(new ToFloatNode(expr)) : expr; } /** * Parse declaration or expression statement * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';' *

* exprAsgn = var '=' exprAsgn | expr */ private Node parseDeclarationStatement() { int old = pos(); Type t = type(); if( peek('.') ) // Ambiguity static vars: "type.var", parse as expression { pos(old); t=null; } 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,";"); } // Widen a reassignable binding without discarding the initializer's // permissions. Lattice glb includes readonly memory and is too broad here. private Type inferredType(Type t) { if( t instanceof TypeMemPtr tmp ) { if( tmp._obj.isAry() ) { Type elem = tmp._obj._fields[1]._t; if( elem instanceof TypeConAry ) elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t; return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable(); } Type base = TYPES.get(tmp._obj._name); TypeMemPtr ptr = (TypeMemPtr)base; return ptr.withAccess(tmp._ro).makeNullable(); } return t.glb(false); } // 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==Type.TOP || t instanceof TypeNil; } /** Parse final: [!|~]var['=' asgn] */ private Node parseDeclaration(Type t) { assert t!=null; // Has var/val instead of a user-declared type boolean inferType = t==Type.TOP || t==Type.BOTTOM; boolean xfinal = bindingFinal(t); Lexer loc = loc(); String name = requireId(); // Optional initializing expression follows boolean fld_final = false; // Fixed field independent of pointee access boolean initialized = match("="); Node expr; if( initialized ) { expr = isExternDecl() ? externDecl(name,t) : _scope.inConstructor() && name.equals(shortName(_nestedType)) ? constructorFunc() : parseAsgn(); // var/val, then type comes from expression if( inferType ) { if( expr._type==Type.NIL ) throw error("a not-null/non-zero expression"); t = expr._type; if( !xfinal ) t = inferredType(t); // Widen if not final } // expr is a constant function if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { if( expr instanceof ExternNode ) t = expr._type; // Upgrade declared type to exact function else if( _code._link(tfp)!=null ) _code.link(tfp).setName(name); // Assign debug name to Simple function } } else { // Need an expression to infer the type. // Also, if not-null then need an initializing expression. // Allowed in a class def, because type/init will happen in the constructor. if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !_scope.inConstructor() ) throw errorSyntax("=expression"); // Initial value for uninitialized struct fields. expr = switch( t ) { // Nullable pointers get a NIL; not-null get a TOP which // signals that they *must* be initialized in the constructor. case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP); case TypeInteger ti -> xfinal ? con(Type.TOP) : ZERO; case TypeFloat tf -> con(xfinal ? Type.TOP : TypeFloat.FZERO); // Bottom signals type inference: they must be initialized in // the constructor and that's when we'll discover the type. case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } }; // Nullable fields are set in the constructor, but remain shallow final. // e.g. final pointer to a r/w array if( t instanceof TypeNil tn && !tn.nullable() && xfinal ) fld_final = true; } // Lift expression, based on type Node lift = liftExpr(expr, t, true); // Lift type to the declaration. This will report as an error later if // we cannot lift the type. // Define a new name if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) throw error("Redefining name '" + name + "'", loc); _scope.lookup(name)._init = initialized; return lift; } /** * Parse a struct declaration, and return the following statement. * Structs cannot be redefined, but can be nested. * * @return zero */ private String _nestedType; private static String shortName(String name) { return name.substring(name.lastIndexOf('.')+1); } private Node parseStruct() { String old = _nestedType; String name = requireId(); if( old!=null ) name=old+"."+name; _nestedType=name; TypeMemPtr ptr = TYPES.get(name) instanceof TypeMemPtr p ? p : TypeMemPtr.make(TypeStruct.open(name)); TYPES.put(name,ptr); require("{"); TypeFunPtr sig = _code.makeFun(TypeFunPtr.make(false,false,new Type[]{ptr,TypeMem.BOT},TypeMem.BOT)); ReturnNode init = parseFunctionBody(sig,loc(),null,true,"self","#selfMem"); init.fun()._name=name+"."; INITIALIZERS.put(name,init); TypeStruct shape = ((TypeMemPtr)TYPES.get(name))._obj; ALLOCS.put(name,makeAllocator(shape,init)); _nestedType=old; require("}"); require(";"); return ZERO; } private int initFields(int index) { TypeStruct shape = ((TypeMemPtr)TYPES.get(_nestedType))._obj; while( index<_scope.nIns() ) { Var v = _scope.var(index++); if( v.isFRef() ) continue; Node value = _scope.in(v); boolean constant = v._final && v._init && value._type.isConstant(); Field field = Field.make(v._name,v.type(),_code.getALIAS(),v._final,constant); shape=shape.add(field); TYPES.put(_nestedType,TypeMemPtr.make(shape)); _fieldVars.put(v._name,v); } return index; } private void finishFields() { TypeStruct shape = ((TypeMemPtr)TYPES.get(_nestedType))._obj.close(); TYPES.put(_nestedType,TypeMemPtr.make(shape)); StructNode constants = new StructNode(shape); int first = _scope._kinds.last()._lexSize; for( int i=first; i<_scope.nIns(); i++ ) if( !_scope.var(i).isFRef() ) constants.addDef(_scope.in(i)); constants.setType(constants.compute()); INITS.put(_nestedType,constants.keep()); } private Node self() { return _scope.in(_scope.lookup("self")); } private Node privateMem() { return _scope.in(_scope.lookup("#selfMem")); } private boolean privateSelf(Node ptr) { if( _privateFun!=_fun || _privateFun==null ) return false; while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1); Node self=self(); while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self=self.in(1); return ptr==self; } private boolean mayBeSelf(Node ptr, java.util.BitSet seen) { if( _privateFun==null || _privateFun!=_fun || seen.get(ptr._nid) ) return false; seen.set(ptr._nid); if( privateSelf(ptr) ) return true; if( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) return mayBeSelf(ptr.in(1),seen); if( ptr instanceof PhiNode && !(ptr instanceof ParmNode) ) for( int i=1; i types = new Ary<>(Type.class); Ary names = new Ary<>(String.class); types.add(TYPES.get(_nestedType)); names.add("self"); types.add(TypeMem.BOT); names.add("#selfMem"); java.util.BitSet finals = new java.util.BitSet(); finals.set(0); // Rebinding self would detach the pointer from its private memory. while( true ) { Type t=type(); if( t==null ) break; finals.set(names.size(),bindingFinal(t)); types.add(t); names.add(requireId()); match(","); } require("->"); TypeFunPtr sig = _code.makeFun(TypeFunPtr.make(false,false,types.asAry(),TypeMem.BOT)); // Bind the signature now, but parse the body after the field list is // closed. Then even a later field has a local SSA slot before branches // and loops are parsed. Strings/comments must not affect brace depth. _constructor=new Constructor(sig,loc(),pos(),names.asAry(),finals); int depth=1; while( depth>0 ) { if( peek('"') ) { parseString(); continue; } if( match("'") ) { parseChar(); continue; } char ch=_lexer.nextXChar(); if( ch==Character.MAX_VALUE ) throw errorSyntax("}"); if( ch=='{' ) depth++; if( ch=='}' ) depth--; } return con(sig); } private CallEndNode rawCall(Node ctrl, Node pub, ReturnNode target, Node... args) { Node[] inputs = new Node[args.length+3]; inputs[0]=ctrl; inputs[1]=pub; System.arraycopy(args,0,inputs,2,args.length); inputs[inputs.length-1]=new FunPtrNode(target.fun().sig(),target).peephole(); // Keep the complete declaration-side caller set before any inlining. CallNode call = new CallNode(loc(),inputs).init(); CallEndNode end = new CallEndNode(call).init(); call.link(target.fun()); return end; } private Node publish(TypeStruct shape, Node ctrl, Node pub, Node priv, Node ptr) { priv.keep(); ptr.keep(); for( Field f : shape._fields ) if( !f._one ) { pub.keep(); Node esc = new EscapeNode(loc(),f,ctrl,pub,priv,ptr).peephole(); Node next = new MemMergeNode(pub,f._alias,esc).peephole(); pub.unkeep(); pub=next; } ptr.unkeep(); priv.unkill(); return pub; } private TypeFunPtr makeAllocator(TypeStruct shape, ReturnNode init) { Field cf=shape.field(shortName(shape._name)); TypeFunPtr ctor=cf!=null && cf._t instanceof TypeFunPtr f ? f : null; int nargs=ctor==null ? 0 : ctor.nargs()-2; Ary captures=CAPTURES.get(shape._name); Type[] types=new Type[nargs+captures.size()]; for( int i=0; i"; fun._constructor=true; _code.link(fun); Node rpc=new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).init(); Node pub=new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).init(); Node[] args=new Node[nargs+2]; for( int i=0; i")) || id==null ) return posT(old1); // Reset lexer to reparse pos(old2); // Reset lexer to reparse // Yes a forward ref, so declare it TYPES.put(tname,((TypeMemPtr)t1).withAccess(false)); // Return the (array, final) type return t2; } // Make an array type of t private TypeMemPtr typeAry( Type t, boolean efinal ) { if( t instanceof TypeMemPtr tmp && tmp.notNull() ) throw error("Arrays of reference types must always be nullable"); String tname = "[]"+t.str(); TypeMemPtr ta = (TypeMemPtr)TYPES.get(tname); if( ta==null ) // Remember final version TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) )); // Reuse aliases, but preserve this occurrence's element permissions. TypeStruct ary = ta._obj; return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,efinal)); } // A function type is `{ type... -> type }` or `{ type }`. private Type typeFunPtr() { int old = pos(); // Record lexer position match("{"); // Skip already-peeked '{' Type t0 = type(); // Either return or first arg if( t0==null ) return posT(old); // Not a function if( match("}") ) // No-arg function { -> type } return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0); Ary ts = new Ary<>(Type.class); ts.push(t0); // First argument while( true ) { if( match("->") ) { // End of arguments, parse return Type ret = type(); if( ret==null || !match("}") ) return posT(old); // Not a function return TypeFunPtr.make(match("?"),false,ts.asAry(),ret); } Type t1 = type(); if( t1==null ) return posT(old); // Not a function ts.push(t1); } } // True if a TypeFunPtr, without advancing parser private boolean isTypeFun() { int old = pos(); if( typeFunPtr()==null ) return false; pos(old); return true; } /** * Parse an expression of the form: * *

     *     expr : bitwise [? expr [: expr]]
     * 
* @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { Node expr = parseLogical(); return match("?") ? parseTrinary(expr,":") : expr; } /** * Parse an bitwise expression * *
     *     bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
     * 
* @return a bitwise expression {@link Node}, never {@code null} */ private Node parseLogical() { Node lhs = parseBitwise(); while (true) { if( false ) ; else if( match("&&") ) lhs = parseTrinary(lhs, "&&"); else if( match("||") ) lhs = parseTrinary(lhs, "||"); else break; } return lhs; } private Node parseBitwise() { Node lhs = parseEquality(); while( true ) { if( false ) ; else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null); else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null); else if( match ("^") ) lhs = new XorNode(loc(),lhs,null); else break; lhs.setDef(2,parseEquality()); lhs = peep(lhs); } return lhs; } /** * Parse an eq/ne expression */ private Node parseEquality() { Node lhs = parseComparison(); while( true ) { boolean eq = false; if( match("==") ) eq = true; else if( !match("!=") ) break; lhs.keep(); Node rhs = parseComparison(); lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs)); if( !eq ) lhs = peep(new NotNode(lhs)); } return lhs; } /** * Parse an expression of the form: * *
     *     expr : shiftExpr < shiftExpr <= shiftExpr...
     *     expr : shiftExpr > shiftExpr >= shiftExpr...
     * 
* @return an comparator expression {@link Node}, never {@code null} */ private Node parseComparison() { Node lhs = parseShift(); int dir = 0; while( true ) { int dir0; if( false ) ; else if( match("<=" ) ) dir0 = -1; else if( match(">=" ) ) dir0 = -2; else if( match("<" ) ) dir0 = 1; else if( match(">" ) ) dir0 = 2; else break; lhs.keep(); // Check and record direction if( dir==0 ) { dir = Math.abs(dir0); Node rhs = parseShift(); lhs = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool } else if( dir == Math.abs(dir0) ) { // e0 < lhs < ??? Node ifNode = new IfNode(ctrl(), lhs).peephole(); Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole(); Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); // False side does nothing ScopeNode fScope = _scope.dup(); fScope.ctrl(ifF); // True side executes next part of range ctrl(ifT); _scope.addGuards(ifT,lhs,false); Node rhs = parseShift(); _scope.removeGuards(ifT); // TODO: I think should remain true as long as possible Node next = makeCompBool(dir0, lhs, rhs).keep(); // Merge result RegionNode r = ctrl(_scope.mergeScopes(fScope, loc())); assert next._type.meet(lhs._type).isa( TypeInteger.BOOL ); lhs = peep(new PhiNode("",r,next.unkeep(),lhs.unkeep())); r.peephole(); } else { throw error("Mixing relational directions in a chained relational test"); } } return lhs; } private Node makeCompBool( int dir0, Node lhs, Node rhs ) { // Convert to a bool if( Math.abs(dir0)==2 ) // Swap direction { Node tmp = lhs; lhs = rhs; rhs = tmp; } lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs)); return peep(lhs); } /** * Parse an additive expression * *
     *     shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
     * 
* @return a shift expression {@link Node}, never {@code null} */ private Node parseShift() { Node lhs = parseAddition(); while( true ) { if( false ) ; else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null); else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null); else if( match(">>") ) lhs = new SarNode(loc(),lhs,null); else break; lhs.setDef(2,parseAddition()); ParseException err; if( (err=lhs.err()) != null ) throw err; lhs = peep(lhs); } return lhs; } /** * Parse an additive expression * *
     *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
     * 
* @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { Node lhs = parseMultiplication(); while( true ) { if( false ) ; else if( match("+") ) lhs = new AddNode(lhs,null); else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); lhs = peep(lhs); } return lhs; } /** * Parse an multiplicativeExpr expression * *
     *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
     * 
* @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { var lhs = parseUnary(); while( true ) { if( false ) ; else if( match("*") ) lhs = new MulNode(lhs,null); else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); lhs = peep(lhs); } return lhs; } /** * Parse a unary minus expression. * *
     *     unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
     * 
* @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { // Pre-dec/pre-inc int old = pos(); if( match("--") || match("++") ) { int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post String name = _lexer.matchId(); if( name!=null ) { Var n = _scope.lookup(name); if( n != null && !(n.type() instanceof TypeMemPtr) ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true); _scope.update(n,expr); return expr; } } // Reset, try again pos(old); } if (match("-")) return peep(new MinusNode(parseUnary())); if (match("!")) return peep(new NotNode(parseUnary())); // Else be a primary expression return parsePrimary(); } /** * Parse a primary expression: * *
     *     primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
     *                   new Type | '(' expression ')' | Id['++','--'] |
     *                   [static.]* Id
     * 
* @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); if( _lexer.peek('"') ) return newString(parseString()); if( matchx("true" ) ) return con(1); if( matchx("false") ) return ZERO; if( matchx("null" ) ) return NIL; if( match ("'" ) ) return parseChar(); if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")")); if( matchx("new" ) ) return parsePostfix(alloc()); if( match ("{" ) ) return parsePostfix(require(func(),"}")); // Parse static variable lookup: `type.fld` in the type namespace // CNC: not a full type parse, just a typename parse int pos = pos(); Type t = type(); if( t!=null ) { if( peek('.') ) // A namespace lookup is not a reference declaration. return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name))); //pos(pos); // TODO: not a `type.fld` //return null; throw Utils.TODO(); } // Expect an identifier now // - if null, make fref, normal var handling // - if final constant, normal var handling // - if method var, scan skipping blocks & methods, must hit correct struct no fcns // - if normal var, scan skipping blocks, allowing last fcn // - NO fcns nested in methods, NO methods nested in fcns String id = _lexer.matchId(); if( id == null || KEYWORDS.contains(id) ) throw errorSyntax("an identifier or expression"); Var var = _scope.lookup(id); if( var==null ) { // If missing, assume a forward reference _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); var = _scope.lookup(id); } // If a method var, allow nested blocks and methods, but not fcns or other structs. // Else safety check for accessing fields out of function scope. if( instance(var) ) { Var selfVar = _scope.lookup("self"); Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); return parsePostfixName(self,id); } // Load local value Node oldValue = _scope.in(var); if( oldValue instanceof ConstantNode && !(oldValue instanceof FRefNode) && oldValue._type==Type.BOTTOM ) throw error("Cannot read uninitialized field '"+id+"'"); Node rvalue = liftExpr(oldValue,var.type(),true); if( rvalue!=oldValue ) _scope.update(var,rvalue); if( rvalue instanceof ConstantNode && rvalue._type == Type.BOTTOM ) if( rvalue instanceof FRefNode ) return parsePostfix(rvalue); else throw error("Cannot read uninitialized field '"+id+"'"); // Check for assign-update, x += e0; char ch = _lexer.matchOperAssign(); if( ch==0 ) // Normal primary, check for postfix updates return parsePostfix(rvalue); // Assign-update direct into Scope if( var._final ) throw error("Cannot reassign final '"+var._name+"'"); checkCaptureWrite(var); Node op = opAssign(ch,rvalue, var.type() ); _scope.update(var,op); return postfix(ch) ? rvalue.unkeep() : op; } // true if instance variable lookup (var is in any number of nested blocks, // then an instance function then inside a struct declaration). private boolean instance(Var var ) { // If a method var, allow nested blocks and methods, but not fcns or other structs Kind kind = _scope.kind(var); if( kind instanceof Kind.Define ) { if( var.isFRef() ) return false; // Promote an unresolved lexical name, not self.name. if( _privateFun==_fun && _privateFun!=null ) return true; for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) { Kind xkind = _scope._kinds.at(i); if( xkind instanceof Kind.Block ) continue; if( xkind instanceof Kind.Func ) { if( _scope._kinds.at(i-1)==kind ) return true; // Instance variable: lookup in a method in a decl } throw Utils.TODO(); } } else { // If a normal var, allow any nested blocks, and a last fcn - // but not functions further out (which involves capture). Node def = _scope.in(var._idx); if( _captures!=null && _scope.kind(var) instanceof Kind.Func && !(var._final && def._type.isConstant()) && !(def instanceof FRefNode) ) { if( !(def instanceof ParmNode parm && parm.fun()==_fun) ) { _captures.add(def.keep()); Type type=def._type; int index=_fun.sig().nargs(); _fun.addParseArg(type); _scope.update(var,new ParmNode(var._name,index+2,type,_fun,con(type)).init()); } return false; } for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) if( _scope._kinds.at(i) instanceof Kind.Func && !(def instanceof FRefNode) && !(var._final && def._type.isConstant()) ) throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); } return false; } private void checkCaptureWrite(Var var) { if( _captures==null ) return; for( int i=_scope.depth()-1; i>=0 && var._idx<_scope._kinds.at(i)._lexSize; i-- ) if( _scope._kinds.at(i) instanceof Kind.Func ) throw error("Cannot assign captured variable '"+var._name+"' in an initializer"); } // Check for assign-update, "x += e0". // Returns "x + e0"; the caller assigns value. // if ch is postfix, then lhs is kept and caller will unkeep and return it. private Node opAssign( char ch, Node lhs, Type t ) { // RHS of the update. lhs.keep(); // Alive across parseAsgn Node rhs = (byte)ch == 1 ? con( 1) : // var++ (byte)ch == -1 ? con(-1) : // var-- parseAsgn(); // var op= rhs if( !postfix(ch) ) lhs.unkeep(); // Allow to die in next peep // 4 cases: // int + int ==>> narrow int // int + flt ==>> error, caller must fail assigning flt into int // flt + int ==>> use float op, wrap toFloat() // flt + flt ==>> use float op Node op = switch(ch) { case 1, (char)-1, '+' -> new AddNode(lhs,rhs); case '-' -> new SubNode(lhs,rhs); case '*' -> new MulNode(lhs,rhs); case '/' -> new DivNode(lhs,rhs); case '&' -> new AndNode(loc(),lhs,rhs); case '|' -> new OrNode(loc(),lhs,rhs); case '^' -> new XorNode(loc(),lhs,rhs); case Lexer.SHL -> new ShlNode(loc(),lhs,rhs); case Lexer.SAR -> new SarNode(loc(),lhs,rhs); case Lexer.SHR -> new ShrNode(loc(),lhs,rhs); default -> throw Utils.TODO(); }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() return liftExpr(peep(op),t,true); } /** Parse an allocation */ private Node alloc() { Type t = type(); if( t==null ) throw error("Expected a type"); // Parse ary[ length_expr ] if( match("[") ) { if( !t.makeZero().isa(t) ) throw error("Cannot allocate a non-nullable, since arrays always zero/null fill"); Node len = parseAsgn(); if( !(len._type instanceof TypeInteger) ) throw error("Cannot allocate an array with length "+len._type); require("]"); TypeMemPtr tmp = typeAry(t,false); return newArray(tmp._obj,len); } if( !(t instanceof TypeMemPtr tmp) ) throw error("Cannot allocate a "+t.str()); TypeStruct shape=tmp._obj; TypeFunPtr sig=ALLOCS.get(shape._name); if( sig==null ) throw error("Unknown struct type '"+shape._name+"'"); if( !peek('{') ) { // Obvious missing defaults can still be diagnosed here. A user // constructor or a merge needs the later publication check. if( shape.field(shortName(shape._name))==null ) { StructNode defaults=INITS.get(shape._name); Type initial=INITIALIZERS.get(shape._name).expr()._type; for( int i=0; i args=new Ary<>(Node.class); if( match("(") ) { while( !peek(')') ) { Node arg=parseAsgn(); checkPublication(arg); args.add(arg.keep()); if( !match(",") ) break; } require(")"); } for( Node capture : CAPTURES.get(shape._name) ) { checkPublication(capture); args.add(capture.keep()); } if( args.size()!=sig.nargs() ) throw error("Wrong number of constructor arguments for '"+shape._name+"'"); for( Node arg : args ) arg.unkeep(); return callWithArgs(fptr,args.asAry()); } require("{"); Node alloc=new NewNode(TypeMemPtr.make(shape),ctrl(),null,off(shape," len")).init(); Node ptr=new ProjNode(alloc,0,shape._name).init().keep(); Node priv=new ProjNode(alloc,1,"#selfMem").init(); Ary captures=CAPTURES.get(shape._name); Node[] args=new Node[captures.size()+2]; args[0]=ptr; args[1]=priv; for( int i=0; i