summaryrefslogtreecommitdiff
path: root/chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java
diff options
context:
space:
mode:
Diffstat (limited to 'chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java')
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java2156
1 files changed, 2156 insertions, 0 deletions
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..9cf565ac
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,2156 @@
+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.
+ * <p>
+ * 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()}.
+ * <p>
+ * 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.
+ * <p>
+ * 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<String> 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<ScopeNode> _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<String, Type> TYPES = defaultTypes();
+
+ // Mapping from a type name to the constructor for a Type.
+ public final HashMap<String, StructNode> 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<String, Type> 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 extends Node> 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();
+ stop.peephole();
+ }
+
+ /**
+ * 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 ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, 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;
+ 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);
+
+ 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 = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,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<ids.length; i++ ) {
+ Type t = sig.arg(i);
+ _scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
+ }
+
+ // Parse the body
+ Node last=ZERO;
+ while (!peek('}') && !_lexer.isEOF())
+ last = parseStatement();
+
+ // 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
+ _scope.pop();
+ _fun = oldfun;
+ _breakScope = breakScope;
+ _continueScope = continueScope;
+
+ // Reset all fields to pre-parse days
+ for( int i=0; i<preParse.length; i++ )
+ _scope.setDef(i,preParse[i]==null ? null : preParse[i].unkeep());
+
+ return ret;
+ }
+
+ /**
+ * 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
+ _scope.push(kind);
+ Node last = ZERO;
+ while (!peek('}') && !_lexer.isEOF())
+ last = parseStatement();
+ // Exit scope
+ last.keep();
+ _scope.pop();
+ return last.unkeep();
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @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 (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();
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ 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;
+ // }
+ // }
+ 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
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @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("",lhs._type.meet(rhs._type).glb(false),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.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @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.
+ _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`
+ Node self = _scope.in(_scope.lookup("self")); // Insert self reference
+ 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
+ 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( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() )
+ throw error("Must define forward ref "+tmp._obj._name);
+ // 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));
+ }
+ if( !expr._type.accessISA(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
+ // Auto-widen array to i64 (cast ptr to raw int bits)
+ if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]")));
+ // Auto-widen int to float
+ expr = widenInt( expr, t );
+ // Auto-narrow wide ints to narrow ints. For loads, emit code to force
+ // the loaded value to match the declared sign/zero bits. For stores,
+ // just force the type, acting "as if" the store silently truncates.
+ if( expr._type instanceof TypeFloat && t instanceof TypeInteger )
+ expr = peep(new ToIntegerNode(expr));
+ Type et = expr._type;
+ if( isLoad ) { expr = zsMask(expr,t); et = expr._type; }
+ else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t;
+
+ // Type is sane
+ if( et!=Type.BOTTOM && (!et.shallowISA(t) || !et.accessISA(t)) )
+ expr = peep(new CastNode(t.isConstant() ? t : t.widen(),null,expr));
+ return 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 ';'
+ * <p>
+ * 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)
+ : 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 _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.
+ if( !lift._type.isa(t) )
+ lift = peep(new CastNode(t.widen(),null,lift));
+ // 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 Node parseStruct() {
+ // "struct" already parsed, so expect the struct name next.
+ String old = _nestedType;
+ String typeName = requireId();
+ if( old!=null ) typeName = old+"."+typeName;
+ _nestedType = typeName;
+ // New declared or forward-ref type
+ TypeStruct ts = TYPES.get(typeName) instanceof TypeMemPtr tmp ? tmp._obj : TypeStruct.open(typeName);
+ TYPES.put(typeName,TypeMemPtr.make(ts));
+
+ // A Block scope parse, and inspect the scope afterward for fields.
+ _scope.push(new Kind.Define(TypeMemPtr.make(ts)));
+ require("{");
+ int lexlen = _scope._kinds.last()._lexSize, nvar=lexlen;
+ while (!peek('}') && !_lexer.isEOF()) {
+ parseStatement().isKill();
+ while( nvar < _scope._vars._len ) {
+ Var v = _scope.var(nvar++);
+ if( !v.isFRef() ) {
+ ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && v._init && _scope.in( v._idx )._type != Type.TOP ) );
+ TYPES.put( typeName, TypeMemPtr.make( ts ) );
+ }
+ }
+ }
+
+ // Count struct declarations
+ int varlen = _scope._vars._len;
+ ts = ts.close();
+ TYPES.put(typeName,TypeMemPtr.make(ts));
+
+ // Generate an initializing struct
+ StructNode s = new StructNode(ts);
+ for( int i=lexlen; i<varlen; i++ )
+ if( !_scope.var(i).isFRef() ) // Promote to outer scope, not defined here
+ s.addDef(_scope.in(i)); // Keep the program code to generate instance
+
+ // Save instance default constructor
+ s.setType(s.compute()); // Do not call peephole, in case the entire struct is a big plain constant
+ INITS.put(typeName,s.keep());
+
+ // Having finalized the struct, upgrade all the 'self' pointers
+ for( Node n : s._inputs )
+ if( n._type instanceof TypeFunPtr tfp && _code.link(tfp) instanceof FunNode fun )
+ _code.addAll( fun._outputs );
+
+
+ // Done with struct/block scope
+ _nestedType = old;
+ require("}");
+ require(";");
+ _scope.pop();
+ return ZERO;
+ }
+
+
+ // Parse and return a type or null. Valid types always are followed by an
+ // 'id' which the caller must parse. This lets us distinguish forward ref
+ // types (which ARE valid here) from local vars in an (optional) forward
+ // ref type position.
+
+ // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
+ private Type type() {
+ int old1 = pos();
+ boolean writable = match("!");
+ boolean readonly = !writable && match("~");
+ // Only type with a leading `{` is a function pointer...
+ if( peek('{') ) return typeFunPtr();
+
+ // Otherwise you get a type name
+ String tname = _lexer.matchId();
+ if( tname==null ) return posT(old1);
+
+ // Convert the type name to a type.
+ Type t0 = TYPES.get(tname);
+ // No new types as keywords
+ if( t0 == null && KEYWORDS.contains(tname) )
+ return posT(old1);
+ if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+
+ // Check for subtype.
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = _lexer.matchId();
+ if( sname==null ) { pos(old2); break; }
+ String tsname = tname+"."+sname;
+ t0 = TYPES.get(tsname);
+ if( t0==null ) { pos(old2); t0 = TYPES.get(tname); break; }
+ tname = tsname;
+ }
+
+ // Still no type found? Assume forward reference
+ Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type
+ // A qualifier before the name applies to the struct reference, not
+ // to an enclosing array. Arrays have their own [] / [~] qualifier.
+ if( t1 instanceof TypeMemPtr tmp ) {
+ if( readonly || !writable ) t1 = tmp.makeRO();
+ } else if( writable || readonly )
+ return posT(old1);
+
+ // Nest arrays and '?' as needed
+ Type t2 = t1;
+ while( true ) {
+ if( match("?") ) {
+ if( !(t2 instanceof TypeMemPtr tmp) )
+ throw error("Type "+t0+" cannot be null");
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
+ } else if( match("[~]") ) {
+ t2 = typeAry(t2,true).makeRO();
+ } else if( match("[]") ) {
+ t2 = typeAry(t2,false);
+ } else
+ break;
+ }
+
+ // Check no forward ref
+ if( t0 != null ) return t2;
+ // Check valid forward ref, after parsing all the type extra bits.
+ // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
+ int old2 = pos();
+ if( !match("!") ) match("~");
+ String id = _lexer.matchId();
+ if( !(peek(',') || peek(';') || match("->")) || 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<Type> 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:
+ *
+ * <pre>
+ * expr : bitwise [? expr [: expr]]
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() {
+ Node expr = parseLogical();
+ return match("?") ? parseTrinary(expr,":") : expr;
+ }
+
+ /**
+ * Parse an bitwise expression
+ *
+ * <pre>
+ * bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
+ * </pre>
+ * @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).widen());
+ if( !eq ) lhs = peep(new NotNode(lhs));
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : shiftExpr < shiftExpr <= shiftExpr...
+ * expr : shiftExpr > shiftExpr >= shiftExpr...
+ * </pre>
+ * @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("",TypeInteger.BOOL,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.widen());
+ }
+
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
+ * </pre>
+ * @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.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+ * </pre>
+ * @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.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an multiplicativeExpr expression
+ *
+ * <pre>
+ * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+ * </pre>
+ * @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.widen());
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
+ * </pre>
+ * @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 = n.type() instanceof TypeFloat
+ ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta))))
+ : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
+ _scope.update(n,expr);
+ return expr;
+ }
+ }
+ // Reset, try again
+ pos(old);
+ }
+ if (match("-")) return peep(new MinusNode(parseUnary()).widen());
+ if (match("!")) return peep(new NotNode(parseUnary()));
+ // Else be a primary expression
+ return parsePrimary();
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
+ * new Type | '(' expression ')' | Id['++','--'] |
+ * [static.]* Id
+ * </pre>
+ * @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) ) {
+ Node self = _scope.in(_scope.lookup("self")); // Insert self reference
+ return parsePostfixName(self,id);
+ }
+
+ // Load local value
+ Node rvalue = _scope.in(var);
+ if( 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+"'");
+ 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 ) {
+ 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);
+ 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;
+ }
+
+ // 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.widen()),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());
+
+ // Parse new struct { default_initialization }
+ StructNode s = INITS.get(tmp._obj._name);
+ if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'");
+
+ Field[] fs = s._ts._fields;
+ // if the object is fully initialized, we can skip a block here.
+ // Check for constructor block:
+ boolean hasConstructor = match("{");
+ Ary<Node> init=s._inputs; int idx=0;
+ if( hasConstructor ) {
+ idx = _scope.nIns();
+ // Push a scope, and pre-assign all struct fields.
+ _scope.push(new Kind.Block());
+ Lexer loc = loc();
+ for( int i=0; i<fs.length; i++ )
+ // An initial TOP means the field needs to be initialized. We
+ // store a BOT initially; any partial init will fall to BOT
+ // (merge BOT and the partial) and be obviously only a partial
+ // init. To be initialized the field needs a full clobber.
+ _scope.define(fs[i]._fname, fs[i]._t, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc);
+ // Parse the constructor body
+ require(parseBlock(new Kind.Alloc(tmp)),"}");
+ init = _scope._inputs;
+ }
+ // Check that all fields are initialized
+ for( int i=idx; i<init.size(); i++ )
+ if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
+ throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
+ Node ptr = newStruct(tmp._obj, off(tmp._obj, " len"), idx, init );
+ if( hasConstructor )
+ _scope.pop();
+ return ptr;
+ }
+
+
+ /**
+ * Return a NewNode initialized memory.
+ * @param obj is the declared type, with GLB fields
+ * @param init is a collection of initialized fields
+ */
+ private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) {
+ Field[] fs = obj._fields;
+ if( fs==null )
+ throw error("Unknown struct type '" + obj._name + "'");
+ Node prior = mem().keep();
+ MemMergeNode input = new MemMergeNode((Node)null);
+ for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior);
+ // Allocation zeroes its fields; nonzero initializers become explicit Stores.
+ Node[] ns = new Node[3];
+ ns[0] = ctrl();
+ ns[1] = input.peephole();
+ ns[2] = size;
+ Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep();
+ Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep();
+ Node out = new ProjNode(nnn,1,"$mem").peephole().keep();
+ MemMergeNode after = new MemMergeNode(prior);
+ for( Field f : fs ) if( !f._one ) after.alias(f._alias,out);
+ mem(after.peephole());
+ out.unkeep();
+ nnn.unkeep();
+ prior.unkill();
+ for( int i=0; i<fs.length; i++ ) {
+ Node val = init.get(idx+i);
+ if( !fs[i]._one && val._type != val._type.makeZero() ) {
+ Field f = fs[i];
+ Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole();
+ mem(new MemMergeNode(mem(),f._alias,st).peephole());
+ }
+ }
+ return ptr.unkeep();
+ }
+
+ private static final Ary<Node> ALTMP = new Ary<>(Node.class);
+ private Node newArray(TypeStruct ary, Node len) {
+ ConFldOffNode base = off(ary, "[]");
+ int scale= ary.aryScale();
+ Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale)))));
+ ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero()));
+ return newStruct(ary,size,0,ALTMP);
+ }
+ private Node newString(String s) {
+ TypeMemPtr tmp = typeAry(TypeInteger.U8,true);
+ int lenAlias = tmp._obj.field("#" )._alias;
+ int elemAlias = tmp._obj.field("[]")._alias;
+ TypeConAryB con = TypeConAryB.make(s);
+ Type elem = con.elem();
+ // Make a TMP, not-null (byte)2, singleton (true) (requires text
+ // strings are hash-interned), with a TypeStruct having a constant
+ // array body.
+ TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true);
+ assert str.isConstant();
+ return con(str.makeRO());
+ }
+
+ // Memory is one hidden SSA variable, including across branches and loops.
+ private Node mem() { return _scope.mem(); }
+ private void mem(Node n) { _scope.mem(n); }
+
+ private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
+ if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
+ Node prior = mem().keep();
+ Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init);
+ st.setDef(0,ctrl);
+ st = st.peephole();
+ mem(new MemMergeNode(prior,alias,st).peephole());
+ prior.unkill();
+ }
+
+ /**
+ * Parse postfix expression; this can be a field expression, an array
+ * lookup or a postfix operator like '#'
+ *
+ * <pre>
+ * expr ('.' FIELD)* [ = expr ] // Field reference
+ * expr '#' // Postfix unary read operator
+ * expr ['++' | '--' ] // Postfix unary write operator
+ * expr ('[' expr ']')* = [ = expr ] // Array reference
+ * expr '(' [args,]* ')' // Function call
+ * </pre>
+ */
+ private Node parsePostfix(Node expr) {
+ String name;
+ if( match(".") ) name = requireId();
+ else if( match("#") ) name = "#";
+ else if( match("[") ) name = "[]";
+ else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")"));
+ else return expr; // No postfix
+
+ return parsePostfixName(expr,name);
+ }
+
+ /**
+ * Parse postfix expression; this can be a field expression, an array
+ * lookup or a postfix operator like '#'
+ *
+ * <pre>
+ * expr ['++' | '--' ] // Postfix unary write operator
+ * expr [ [op]= expr ] // Field reference
+ * expr ('[' expr ']')* [ [op]= expr ] // Array reference
+ * </pre>
+ */
+ private Node parsePostfixName(Node expr, String name) {
+
+ if( expr._type==Type.NIL )
+ throw error("Accessing unknown field '" + name + "' from 'null'");
+
+ // Sanity check expr for being a reference
+ if( !(expr._type instanceof TypeMemPtr ptr) )
+ throw error( "Expected "+(name=="#" || name=="[]" ? "array" : "reference")+" but found " + expr._type.str() );
+
+ // Find the field from the Type. Lookup in the base object field names.
+ TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name);
+ if( !ptr._obj.isAry() )
+ ptr = ptr.makeFrom((TypeStruct)ptr._obj.join(ptr2._obj)); // Resolve the shape, retaining access.
+ TypeStruct base = ptr._obj;
+ int fidx = base.find(name);
+ if( fidx == -1 )
+ throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+
+ // Get field type and layout offset from base type and field index fidx
+ Field f = base._fields[fidx]; // Field from field index
+ Type tf = f._t;
+ if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
+ tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
+ if( base.isAry() && tf instanceof TypeConAry con )
+ tf = con.elem();
+
+ expr.keep();
+ // Field offset; fixed for structs, computed for arrays
+ Node off = (name.equals("[]") // If field is an array body
+ // Array index math
+ ? peep(new AddNode(off(base,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale())))))
+ // Struct field offsets are hardwired
+ : off(base,name)).keep();
+
+ // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
+ if( matchOpx('=','=') ) {
+ Node val = parseAsgn().keep();
+ Node lift = liftExpr( val, tf, false );
+
+ store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null);
+ return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
+ }
+
+ // Load field
+ Node load = f._one && INITS.containsKey( base._name )
+ ? INITS.get(base._name).in(base.find(name))
+ : new LoadNode(loc(),name, f._alias, tf, mem(), expr, off);
+ // Arrays include control, as a proxy for a safety range check
+ // Structs don't need this; they only need a NPE check which is
+ // done via the type system.
+ if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl());
+ load = peep(load);
+
+ // Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
+ char ch = _lexer.matchOperAssign();
+ if( ch!=0 ) {
+ Node op = opAssign(ch,load,tf);
+ store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null);
+ load = postfix(ch) ? load.unkeep() : op;
+ // And use the original loaded value as the result
+ return load;
+ //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN
+ } else {
+ off.unkill();
+ // Method call: Loaded a function from a named field, AND the base
+ // field type is final.
+ if( load._type instanceof TypeFunPtr && name!="[]" &&
+ !(load instanceof ExternNode) &&
+ ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final &&
+ // And calling a function
+ match("(") ) {
+ return parsePostfix(require(functionCall(load,expr),")"));
+ }
+
+ // Drop expr, keeping load alive
+ load.keep();
+ expr.unkill();
+ load.unkeep();
+ }
+
+ return parsePostfix(load);
+ }
+
+
+ // zero/sign extend. "i" is limited to either classic unsigned (min==0) or
+ // classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
+ private Node zsMask(Node val, Type t ) {
+ if( val._type instanceof TypeFloat && t instanceof TypeInteger )
+ val = new ToIntegerNode(val).peephole();
+ if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) {
+ if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
+ return val;
+ // Float rounding
+ return peep(new RoundF32Node(val));
+ }
+ if( t0._min==0 ) // Unsigned
+ return peep(new AndNode(null,val,con(t0._max)));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(t0._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO )
+ return val;
+ return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep()));
+ }
+
+ /**
+ * Parse a function body; the caller will parse the surrounding "{}"
+ *
+ * <pre>
+ * { [type arg,]* -> expr }
+ * { expr } // The no-argument function
+ * </pre>
+ */
+ private Node func() {
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ java.util.BitSet finals = new java.util.BitSet();
+
+ // Defined in constructor? Add `self` argument. "static" calls still
+ // add a `self` they just ignore it.
+ if( _scope._kinds.last() instanceof Kind.Define define ) {
+ // Upgrade defined struct to latest fields
+ TypeMemPtr tmp = (TypeMemPtr)TYPES.get(define._tmp._obj._name);
+ ts .push(tmp);
+ ids.push("self");
+ }
+
+ // Parse other arguments
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc(); // First argument location
+ while( true ) {
+ Type t = type(); // Arg type
+ if( t==null ) break;
+ finals.set(ids.size(),bindingFinal(t));
+ String id = requireId();
+ ts .push(t ); // Push type/arg pairs
+ ids.push(id);
+ match(",");
+ }
+ require("->");
+ // Make a concrete function type, with a fidx
+ TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM));
+ ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
+ return new FunPtrNode(ret._fun.sig(),ret).peephole();
+ }
+
+ /**
+ * Parse function call arguments; caller will parse the surrounding "()"
+ * <pre>
+ * ( arg* )
+ * </pre>
+ */
+ private Node functionCall(Node fcn, Node self) {
+ if( fcn._type == Type.NIL )
+ throw error("Calling a null function pointer");
+ if( !(fcn instanceof FRefNode) && !fcn._type.isa(TypeFunPtr.BOT) )
+ throw error("Expected a function but got "+fcn._type.glb(false).str());
+ fcn.keep(); // Keep while parsing args
+
+ Ary<Node> args = new Ary<>( Node.class );
+ args.push(null); // Space for ctrl,mem
+ args.push(null);
+ if( self != null ) // Method call has a self
+ args.push(self);
+ while( !peek(')') ) {
+ Node arg = parseAsgn();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !match(",") ) break;
+ }
+ // Control & memory after parsing args
+ args.set(0,ctrl().keep());
+ args.set(1,_scope.mem().keep());
+ args.push(fcn); // Function pointer
+ // Unkeep them all
+ for( Node arg : args )
+ arg.unkeep();
+ // Dead into the call? Skip all the node gen
+ if( ctrl()._type == Type.XCONTROL ) {
+ for( Node arg : args )
+ if( arg.isUnused() )
+ arg.kill();
+ return con(Type.TOP);
+ }
+
+ // Into the call
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ // Post-call setup
+ CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
+ call.peephole(); // Rerun peeps after CallEnd, allows early inlining
+ // Control from CallEnd
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ // A call may update any alias, so its memory result is the new whole state.
+ _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ // Call result
+ return new ProjNode(cend,2,"#2").peephole();
+ }
+
+ // Just after parsing "type foo = " can parse `"C"`
+ private boolean isExternDecl( ) {
+ int old = pos();
+ String s = parseString();
+ if( "C".equals(s) ) return true;
+ pos(old);
+ return false;
+ }
+ // External linked constant
+ private Node externDecl( String ex, Type t ) {
+ if( t instanceof TypeFunPtr tfp ) // Generic TFP from type parse
+ t = _code.makeFun(tfp); // Get a FIDX, becomes a constant
+ return new ExternNode(t,ex).peephole();
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * floatLiteral: [digits].[digits]?[e [digits]]?
+ * </pre>
+ */
+ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
+ public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
+ public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); }
+ public static ConFldOffNode off( TypeStruct base, String fname ) { return (ConFldOffNode)(new ConFldOffNode(base._name,fname).peephole()); }
+ public Node peep( Node n ) {
+ // Peephole, then improve with lexically scoped guards
+ return _scope.upcastGuard(n.peephole());
+ }
+
+ // Parse a string or null
+ private String parseString() {
+ if( !peek('"') ) return null;
+ _lexer.inc();
+ StringBuilder sb = new StringBuilder();
+ while( !_lexer.isEOF() ) {
+ char c = _lexer.nextChar();
+ if( c=='"' ) return sb.toString();
+ if( c!='\\' ) {
+ sb.append(c);
+ continue;
+ }
+ if( _lexer.isEOF() )
+ throw error("Unclosed string");
+ sb.append(escapedChar("string"));
+ }
+ throw error("Unclosed string");
+ }
+
+ private char escapedChar(String kind) {
+ char esc = _lexer.nextChar();
+ return switch( esc ) {
+ case '0' -> '\u0000';
+ case 'n' -> '\n';
+ case 't' -> '\t';
+ case 'r' -> '\r';
+ case '\\' -> '\\';
+ case '"' -> '"';
+ case '\'' -> '\'';
+ default -> throw error("Unknown "+kind+" escape \\\\"+esc);
+ };
+ }
+
+ // Already parsed "'"
+ private Node parseChar() {
+ if( _lexer.isEOF() ) throw error("Unclosed character");
+ char c = _lexer.nextChar();
+ if( c=='\\' ) {
+ if( _lexer.isEOF() ) throw error("Unclosed character");
+ c = escapedChar("character");
+ }
+ return require(con(TypeInteger.constant(c)),"'");
+ }
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ private boolean match (String syntax) { return _lexer.match (syntax); }
+ // Match must be "exact", not be followed by more id letters
+ private boolean matchx(String syntax) { return _lexer.matchx(syntax); }
+ private boolean matchOp(char c0 ) { return _lexer.matchOp(c0); }
+ private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
+ // Return true and do NOT skip if 'ch' is next
+ private boolean peek(char ch) { return _lexer.peek(ch); }
+ private boolean peekIsId() { return _lexer.peekIsId(); }
+
+ // ch is +/- 1, means oper++ or oper-- means postfix
+ private static boolean postfix(char ch) {
+ return (byte)ch== 1 || (byte)ch== -1;
+ }
+
+ public int pos() { return _lexer._position; }
+ private int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ private Type posT(int pos) { _lexer._position = pos; return null; }
+ // Source code location
+ Lexer loc() { return new Lexer(_lexer); }
+
+
+ // Require and return an identifier
+ private String requireId() {
+ String id = _lexer.matchId();
+ if (id != null && !KEYWORDS.contains(id) ) return id.intern();
+ throw error("Expected an identifier, found '"+id+"'");
+ }
+
+ private String matchId() {
+ int old = pos();
+ String id = _lexer.matchId();
+ if( id==null ) return null;
+ if( !KEYWORDS.contains(id) ) return id;
+ pos(old);
+ return null;
+ }
+
+ // Require an exact match
+ private Parser require(String syntax) { require(null, syntax); return this; }
+ private <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
+ }
+
+ ////////////////////////////////////
+ // Lexer components
+
+ // Lexer provides low level access to the raw file bytes, peeks and matches
+ // short strings, parses numbers, skips comments and whitespace, tracks
+ // line numbers, allows the parse position to be saved and restored, and
+ // serves as a location indicator for errors.
+
+ public static class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Tracks current position in input buffer
+ private int _position = 0;
+
+ //
+ private int _line_number = 1;
+ // Start of current line
+ private int _line_start = 0;
+
+ /**
+ * Record the source text for lexing
+ */
+ public Lexer(String source) {
+ this(source.getBytes());
+ }
+
+ /**
+ * Direct from disk file source
+ */
+ public Lexer(byte[] buf) {
+ _input = buf;
+ }
+
+ /**
+ * Copy a lexer from a lexer
+ */
+ private Lexer(Lexer l) {
+ _input = l._input;
+ _position = l._position;
+ _line_number = l._line_number;
+ _line_start = l._line_start;
+ }
+
+ // Very handy in the debugger, shows the unparsed program
+ @Override
+ public String toString() {
+ return new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ private boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ private char peek() {
+ return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
+ : (char) _input[_position];
+ }
+ // Just crash if misused
+ public byte peek(int off) { return _input[_position+off]; }
+
+ private void inc() {
+ if( _position++ < _input.length && _input[_position-1] == '\n' ) {
+ _line_number++;
+ _line_start = _position;
+ }
+ }
+ // Does not honor LF, so caller must roll back position on a LF
+ private char nextChar() {
+ char ch = peek();
+ inc();
+ return ch;
+ }
+
+ // True if a white space
+ private boolean isWhiteSpace() {
+ return peek() <= ' '; // Includes all the use space, tab, newline, CR
+ }
+
+ /**
+ * Return the next non-white-space character
+ */
+ private void skipWhiteSpace() {
+ while( true ) {
+ if( isWhiteSpace() ) inc();
+ // Skip // to end of line
+ else if( _position+2 < _input.length &&
+ _input[_position ] == '/' &&
+ _input[_position+1] == '/') {
+ inc(); inc();
+ while( !isEOF() && _input[_position] != '\n' )
+ inc();
+ } else if( _position+2 < _input.length &&
+ _input[_position ] == '/' &&
+ _input[_position+1] == '*') {
+ // Skip /*comment*/
+ while( !isEOF() && !(_input[_position-1] == '*' && _input[_position] == '/'))
+ inc();
+ inc();
+ } else break;
+ }
+ }
+
+ // Next non-white-space character, or EOF
+ public char nextXChar() { skipWhiteSpace(); return nextChar(); }
+
+ // Return true, if we find "syntax" after skipping white space; also
+ // then advance the cursor past syntax.
+ // Return false otherwise, and do not advance the cursor.
+ boolean match(String syntax) {
+ assert syntax.indexOf('\n')==-1; // No newlines in match
+ skipWhiteSpace();
+ int len = syntax.length();
+ if (_position + len > _input.length) return false;
+ for (int i = 0; i < len; i++)
+ if ((char) _input[_position + i] != syntax.charAt(i))
+ return false;
+ _position += len;
+ return true;
+ }
+
+ // Match must be exact and not followed by more ID characters.
+ // Prevents identifier "ifxy" from matching an "if" statement.
+ boolean matchx(String syntax) {
+ if( !match(syntax) ) return false;
+ if( !isIdLetter(peek()) ) return true;
+ _position -= syntax.length();
+ return false;
+ }
+ // Match this char, and the next char must be different.
+ // Handles '&&' vs '&'
+ boolean matchOp( char c0 ) {
+ skipWhiteSpace();
+ if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c0 )
+ return false;
+ inc();
+ return true;
+ }
+ // Match these two characters in a row
+ boolean matchOpx(char c0, char c1) {
+ skipWhiteSpace();
+ if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 )
+ return false;
+ inc();
+ return true;
+ }
+
+ private boolean peek(char ch) {
+ skipWhiteSpace();
+ return peek()==ch;
+ }
+
+ boolean peekIsId() {
+ skipWhiteSpace();
+ return isIdStart(peek());
+ }
+
+ // Return an identifier or null
+ String matchId() {
+ return peekIsId() ? parseId() : null;
+ }
+
+ // Used for errors
+ String getAnyNextToken() {
+ if (isEOF()) return "";
+ if (isIdStart(peek())) return parseId();
+ if (isNumber(peek())) return parseNumberString();
+ if (isPunctuation(peek())) return parsePunctuation();
+ return String.valueOf(peek());
+ }
+
+
+ boolean isNumber(char ch) {return Character.isDigit(ch);}
+
+ // Return a constant Type, either TypeInteger or TypeFloat
+ private Type parseNumber() {
+ int old = _position;
+ int len = isLongOrDouble();
+ if( len > 0 ) {
+ if( len > 1 && _input[old]=='0' )
+ throw error("Syntax error: integer values cannot start with '0'",this);
+ long i = Long.parseLong(new String(_input,old,len));
+ return TypeInteger.constant(i);
+ }
+ return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len)));
+ }
+ private String parseNumberString() {
+ int old = _position;
+ int len = Math.abs(isLongOrDouble());
+ return new String(_input,old,len);
+ }
+
+ // Return +len that ends a long
+ // Return -len that ends a double
+ private int isLongOrDouble() {
+ int old = _position;
+ char c;
+ while( Character.isDigit(c=nextChar()) ) ;
+ if( !(c=='e' || c=='.') )
+ return --_position - old;
+ if( peek() == '-' ) nextChar();
+ while( Character.isDigit(c=nextChar()) || c=='e' || c=='.' ) ;
+ return -(--_position - old);
+ }
+
+ // First letter of an identifier
+ private boolean isIdStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ // All characters of an identifier, e.g. "_x123"
+ private boolean isIdLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private String parseId() {
+ int start = _position;
+ while (isIdLetter(nextChar())) ;
+ return new String(_input, start, --_position - start);
+ }
+
+ //
+ private boolean isPunctuation(char ch) {
+ return "=;[]<>()+-/*&|^".indexOf(ch) != -1;
+ }
+
+ private String parsePunctuation() {
+ int start = _position;
+ return new String(_input, start, 1);
+ }
+
+ // Next oper= character, or 0.
+ // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535)
+ // Distinct tags for the multi-character compound operators.
+ static final char SHL = 2, SAR = 3, SHR = 4;
+
+ public char matchOperAssign() {
+ skipWhiteSpace();
+ if( match("<<=" ) ) return SHL;
+ if( match(">>>=") ) return SHR;
+ if( match(">>=" ) ) return SAR;
+ if( _position+2 >= _input.length ) return 0;
+ char ch0 = (char)_input[_position];
+ if( "+-/*&|^".indexOf(ch0) == -1 ) return 0;
+ char ch1 = (char)_input[_position+1];
+ if( ch1 == '=' ) { _position += 2; return ch0; }
+ if( isIdLetter((char)_input[_position+2]) ) return 0;
+ if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; }
+ if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; }
+ return 0;
+ }
+ }
+
+ ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); }
+ private ParseException _errorSyntax(String msg) {
+ return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken());
+ }
+ ParseException error(String msg) { return error(msg,_lexer); }
+ public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
+
+ public static class ParseException extends RuntimeException {
+ public final Lexer _loc;
+ // file:line:charoff err
+ //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage;
+ ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; }
+ }
+
+}