package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; 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
* 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 ) {
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
Node expr;
if( match("=") ) {
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); }
};
}
// 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,null,lift));
// Define a new name
if( !_scope.define(name,t,xfinal,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
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;
// A Block scope parse, and inspect the scope afterward for fields.
_scope.push(ScopeNode.Kind.Constructor);
require("{");
while (!peek('}') && !_lexer.isEOF())
parseStatement().isKill();
// Grab the declarations and build fields and a Struct
int lexlen = _scope._lexSize.last();
int varlen = _scope._vars._len;
StructNode s = new StructNode();
Ary
* 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 (matchx(";") ) return null; // Empty statement
// Break ambiguity around leading function types and starting a block
else if (peek('{') && !isTypeFun() ) {
match("{");
return require(parseBlock(ScopeNode.Kind.Block),"}");
}
// Declaration or normal assignment/expression
else return parseDeclarationStatement();
}
/**
* 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(ScopeNode.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._lexSize.size() > _breakScope._lexSize.size() )
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._lexSize.size() <= _breakScope._lexSize.size();
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,true,"else");
}
// Parse a conditional expression, merging results.
private Node parseTrinary( Node pred, boolean stmt, String fside ) {
pred.keep();
// IfNode takes current control and predicate
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
Node lhs = (stmt ? parseStatement() : parseAsgn()).keep(); // Parse true-side
_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);
boolean doRHS = match(fside);
Node rhs = (doRHS
? (stmt ? parseStatement() : parseAsgn())
: con(lhs._type.makeZero())).keep();
_scope.removeGuards(ifF);
if( doRHS )
fScope = _scope;
pred.unkeep();
// Check the trinary widening int/flt
if( !stmt ) {
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),r,lhs.unkeep(),rhs.unkeep()));
// Immediately fail e.g. `arg ? 7 : ptr`
ParseException err;
if( !stmt && (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.
_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();
// Just a plain expression, no assignment.
// Distinguish `var==expr` from `var=expr`
if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') )
{ pos(old); return parseExpression(); }
// Parse assignment expression
Node expr = parseAsgn();
// Final variable to update
Var def = _scope.lookup(name);
if( def==null )
throw error("Undefined name '" + name + "'");
// TOP fields are for late-initialized fields; these have never
// been written to, and this must be the final write. Other writes
// outside the constructor need to check the final bit.
if( _scope.in(def._idx)._type!=Type.TOP && def._final &&
// Inside a constructor, final assign is OK, outside nope
!_scope.canInit(def) )
throw error("Cannot reassign final '"+name+"'");
// 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 && tmp.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));
}
// 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)),con(tmp._obj.aryBase())));
// 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.
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)) )
throw error("Type " + et.str() + " is not of declared type " + t.str());
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 ';'
*
* expr : bitwise [? expr [: expr]]
*
* @return an expression {@link Node}, never {@code null}
*/
private Node parseExpression() {
Node expr = parseBitwise();
return match("?") ? parseTrinary(expr,false,":") : expr;
}
/**
* Parse an bitwise expression
*
*
* bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
*
* @return a bitwise expression {@link Node}, never {@code null}
*/
private Node parseBitwise() {
Node lhs = parseComparison();
while( true ) {
if( false ) ;
else if( match("&") ) lhs = new AndNode(loc(),lhs,null);
else if( match("|") ) lhs = new OrNode(loc(),lhs,null);
else if( match("^") ) lhs = new XorNode(loc(),lhs,null);
else break;
lhs.setDef(2,parseComparison());
lhs = peep(lhs);
}
return lhs;
}
/**
* Parse an expression of the form:
*
*
* expr : additiveExpr op additiveExpr
*
* @return an comparator expression {@link Node}, never {@code null}
*/
private Node parseComparison() {
var lhs = parseShift();
while( true ) {
int idx=0; boolean negate=false;
// Test for any local nodes made, and "keep" lhs during peepholes
if( false ) ;
else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); }
else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; }
else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); }
else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); }
else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); }
else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); }
else break;
// Peepholes can fire, but lhs is already "hooked", kept alive
lhs.setDef(idx,parseShift());
lhs = peep(lhs.widen());
if( negate ) // Extra negate for !=
lhs = peep(new NotNode(lhs));
}
return 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.widen());
}
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.widen());
}
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.widen());
}
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 = 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()));
return parsePostfix(parsePrimary());
}
/**
* Parse a primary expression:
*
*
* primaryExpr : integerLiteral | true | false | null | new Type | '(' expression ')' | Id['++','--']
*
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
if( _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 require(parseAsgn(), ")");
if( matchx("new" ) ) return alloc();
if( match ("{" ) ) return require(func(),"}");
// Parse static variable lookup: `type.fld` in the type namespace
Type t = type();
if( t!=null && match(".") ) {
String fld = requireId();
TypeMemPtr tmp = ((TypeMemPtr)t);
String tname = tmp._obj._name;
StructNode init = INITS.get(tname);
int idx = tmp._obj.find(fld);
if( idx == -1 )
throw error("Accessing unknown STATIC field");
return init.in(idx);
}
// Expect an identifier now
Var n = requireLookupId();
Node rvalue = _scope.in(n);
if( rvalue._type == Type.BOTTOM )
if( rvalue instanceof FRefNode ) return rvalue;
else throw error("Cannot read uninitialized field '"+n._name+"'");
// Check for assign-update, x += e0;
char ch = _lexer.matchOperAssign();
if( ch==0 ) return rvalue;
Node op = opAssign(ch,rvalue,n._final,n.type(),n._name);
_scope.update(n,op);
return postfix(ch) ? rvalue.unkeep() : op;
}
// 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, boolean xfinal, Type t, String name ) {
if( xfinal )
throw error("Cannot reassign final '"+name+"'");
// 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);
}
// Expect an ID here. If not found, assume a forward reference function
Var requireLookupId() {
// Try in the normal variable namespace
String id = _lexer.matchId();
if( id == null || KEYWORDS.contains(id) )
throw errorSyntax("an identifier or expression");
Var n = _scope.lookup(id);
// If missing, assume a forward reference
if( n==null ) {
_scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc());
n = _scope.lookup(id);
} else {
// Lookup worked on an out-of-function value.
if( _scope.outOfFunction(n) ) {
// No closures, so this has to be a final constant (which
// includes forward refs)
Node def = _scope.in(n._idx);
if( !(def instanceof FRefNode) && !(n._final && def._type.isConstant()) )
throw error("Variable '"+n._name+"' is out of function scope and must be a final constant");
}
}
return n;
}
/**
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);
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
* expr ('.' FIELD)* [ = expr ] // Field reference
* expr '#' // Postfix unary read operator
* expr ['++' | '--' ] // Postfix unary write operator
* expr ('[' expr ']')* = [ = expr ] // Array reference
* expr '(' [args,]* ')' // Function call
*
*/
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),")"));
else return expr; // No postfix
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.
TypeStruct base = ptr._obj;
assert !base.isFRef();
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._type;
if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
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(con(base.aryBase()),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale())))))
// Struct field offsets are hardwired
: con(base.offset(fidx))).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
}
Node load = 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,f._final,tf,name);
store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null);
load = postfix(ch) ? load.unkeep() : op;
// And use the original loaded value as the result
} else expr.unkill();
off.unkill();
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 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 "{}"
*
*
* { [type arg,]* -> expr }
* { expr } // The no-argument function
*
*/
private Node func() {
Ary
* ( arg* )
*
*/
private Node functionCall(Node expr) {
if( expr._type == Type.NIL )
throw error("Calling a null function pointer");
if( !(expr instanceof FRefNode) && !expr._type.isa(TypeFunPtr.BOT) )
throw error("Expected a function but got "+expr._type.glb(false).str());
expr.keep(); // Keep while parsing args
Ary
* integerLiteral: [1-9][0-9]* | [0]
* floatLiteral: [digits].[digits]?[e [digits]]?
*
*/
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 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 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