package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.node.ScopeNode.Kind;
import static com.seaofnodes.simple.util.Utils.TODO;
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 {
// Compile driver
public final CodeGen _code;
// The Lexer. Thin wrapper over a byte[] buffer with a cursor.
private Lexer _lexer;
// Class prefix string; TODO: use something short: "%"
public static final String CLZ = "class:";
private static final String clzPrefix = CLZ;
public static String addClzPrefix( String x ) {
assert !x.startsWith(clzPrefix);
return (clzPrefix+x).intern();
}
public static boolean startsClzPrefix( String s ) {
return s.startsWith(clzPrefix);
}
public static boolean isPublicClassName( String name ) {
if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length());
return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
}
// Source file for compilation
public CompUnit _ref;
// Current classname being parsed
private String _nestedType;
/**
* Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
* there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
* we handle branching. See {@link #parseIf()}.
*
* Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds
* variable names to Nodes. The top of this stack represents current scope.
*
* We keep a list of all ScopeNodes so that we can show them in graphs.
* @see #parseIf()
* @see #_xScopes
*/
public ScopeNode _scope;
/**
* List of keywords disallowed as identifiers
*/
public static final HashSet KEYWORDS = new HashSet<>(){{
add("bool");
add("break");
add("byte");
add("continue");
add("else");
add("f32");
add("f64");
add("false");
add("flt");
add("i16");
add("i32");
add("i64");
add("i8");
add("if");
add("int");
add("new");
add("null");
add("return");
add("struct");
add("true");
add("u1");
add("u16");
add("u32");
add("u8");
add("while");
}};
/**
* We clone ScopeNodes when control flows branch; it is useful to have
* a list of all active ScopeNodes for purposes of visualization of the SoN graph
*/
public final Stack _xScopes = new Stack<>();
ScopeNode _breakScope; // Merge all the while-breaks here
ScopeNode _continueScope; // Merge all the while-continues here
ScopeNode _returnScope; // Merge all the function exits here
Ary _frefs; // Forward refs created during this parse
TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
// Constructor exit types survive scope teardown until all fields are known.
// A null entry denotes a constructor with no returning path.
private ArrayList> _ctorExits;
// Mapping from a type name to a Type. The string name matches
// `type.str()` call.
public static final HashMap TYPES = new HashMap<>();
public static final HashMap INIT_TYPES =
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
}};
public Parser(CodeGen code ) {
_code = code;
}
@Override
public String toString() { return _lexer==null ? "" : _lexer.toString(); }
// Debugging utility to find a Node by index
public Node f(int nid) { return _code.f(nid); }
// Read and set CTRL
private Node ctrl() { return _scope.ctrl(); }
private N ctrl(N n) { return _scope.ctrl(n); }
// Read and set Memory
private Node mem() { return _scope.mem(1); }
private void mem(Node mem) { _scope.mem(mem); }
// Structural label for precise memory Phis. The '$' prefix cannot be
// referenced in Simple source.
public static String memName(int alias) { return ("$"+alias).intern(); }
// Ordinary parsing carries one whole-memory value. Alias partitioning is
// graph optimization, not parser state.
private void storeMem( Node st, int alias, Node prior ) {
mem(alias==1 ? st : mergeAlias(prior,alias,st));
}
// A semantic graph partition representing the complete memory after a
// precise alias update. ScopeNode treats this as one opaque value.
private Node mergeAlias(Node bulk, int alias, Node precise) {
assert bulk != null : "Missing bulk memory for alias #"+alias;
MemMergeNode merge = new MemMergeNode(null,bulk);
merge.alias(alias,precise);
return peep(merge);
}
public Ary parse( CompUnit ref ) {
assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
assert _frefs == null;
_lexer = new Lexer(ref._src);
_frefs = new Ary<>(FRefNode.class);
// Starting Scope has control, memory, initial arguments
_scope = new ScopeNode();
_scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
_scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer);
// Track active scopes for Graph display
_xScopes.push(_scope);
ctrl(_code.XCTRL);
mem(con(TypeMem.BOT));
// File-level struct declaration
_ref = ref;
_nestedType = ref._cname;
String typeName = addClzPrefix(ref._cname);
ReturnNode clzret = parseStruct( true, typeName );
if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
// All still-unresolved forward refs
// should immediately resolve to a file-based name which is a Simple
// class type.
Ary frefs = new Ary<>(FRefNode.class);
for( FRefNode fref : _frefs ) {
if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name);
fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true);
frefs.push(fref);
}
// The class being published
ref._clz = (TypeStruct)TYPES.get(typeName);
// Clean up and reset
_xScopes.pop();
_scope.kill();
_scope = null;
_frefs = null;
return frefs;
}
/**
* 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) {
// Record & restore the existing scope vars. The function parse might
// update fields, so when the parse is done reset back to the pre-parse
// state.
Node[] preParse = _scope.save();
// Parse function body normally
ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids);
// Reset all fields to pre-parse days
_scope.restore(preParse);
return ret;
}
// Parse a function body
// Stack the {break,continue,return} scopes and a new lexical scope.
// Build a FunNode and parameters, extending scope by parm names.
// Parse the function body
// - while( not end-of-function )
// - - parseStatement
// - - If isInit, also incrementally extend the self-type.
// If isInit
// - upgrade and close the self-type
// - Gather fields and store values
// - returns self, not last
// Unstack the {break,continue,return} scopes.
private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
return _parseFunctionBody(funName, sig, loc, null, ids);
}
private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) {
boolean isInit = FunNode.isInit(funName);
boolean isCtor = isInit || isExplicitConstructorName(funName);
String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
// Stack parser state on the local Java stack, and unstack it later
_scope.push(new Kind.Func(funName));
ScopeNode breakScope = _breakScope; _breakScope = null;
ScopeNode continueScope = _continueScope; _continueScope = null;
ScopeNode returnScope = _returnScope; _returnScope = null;
int oldUID = _code.UID(); // Used to approximate function size
FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._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,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
// 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.ctrl(fun); // Scope control from function
// Private mem alias tracking per function
Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole();
Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole();
mem(privMem);
// All args, "as-if" called externally
for( int i=0; i and , local vars are really struct fields and
// get exposed as discovered. This allows late local functions to
// use early declared fields.
if( isInit )
nvar = updateSelfAsFieldsDiscovered( typeName, nvar );
}
ParmNode self = fun.parm(2);
// Last expression is the return
if( ctrl()._type==Type.CONTROL ) {
// Constructors get special return types
if( fun.isClz() ) {
assert last!=null;
} else if( isCtor ) {
last = fun.parm(3);
}
addReturn(last);
// exit path is now dead (nothing live after a "return")
}
if( isCtor )
upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit );
// Build a return from the _returnScope.
// Can be no returns for never-exit functions
Node rctl = _returnScope==null ? _code.XCTRL : _returnScope.ctrl().peephole();
Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem();
Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole();
ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun));
fun.setRet(ret);
// Approximate function size, for inlining heuristics
fun._approxUIDs = _code.UID() - oldUID;
for( Node cend : ret.outs() )
if( cend instanceof CallEndNode )
throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return
// Export (and keep-alive) functions until prove dead
_ref.addFun(_code,fun);
// Unstack parser state
if( _returnScope != null ) _returnScope.kill();
_returnScope = returnScope;
_continueScope = continueScope;
_breakScope = breakScope;
_scope.pop();
return ret;
}
private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) {
int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/;
// Load self type in and out of the TYPES hashtable.
TypeStruct tself = (TypeStruct)TYPES.get(typeName);
// Start looking for newly declared vars at the lexical scope start,
// skipping two ("self" and "selfmem") and fields seen already.
while( lex + nvar < _scope.nIns() ) {
Var v = _scope.var(lex + nvar++);
// Field with sharper type
Field old = tself.field(v._name);
Field fld = v._extern!=0
? Field.make(v._name,v.type(),v._extern,false,true)
: old == null
? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final)
: old.makeFrom(v.type());
tself = tself.addOrUpdate(fld);
}
TYPES.put(typeName,tself);
return nvar;
}
void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
int lex = _scope.klast()._lexSize;
assert _scope.var(lex)._name=="self";
TypeStruct tself = (TypeStruct)TYPES.get(typeName);
int base = lex+2; // Skip ctrl, mem
if( upgrade ) {
// Default / defines the struct and can sharpen fields.
// A named constructor only initializes the already-closed struct.
for( int i=0; i receives the
// broad allocation pointer, while private memory tracks initialized fields.
self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself));
FunNode fun = self.fun();
fun.setSig(fun.sig().makeFrom(self._type,0));
if( !isClz ) // returns upgraded private memory
smem._declaredType = smem._type = TypeMem.makePrivate(tself);
}
if( !upgrade ) {
IdentityHashMap exit = null;
if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) {
exit = new IdentityHashMap<>();
int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2;
for( int i=first; i updates the private memory that got
// passed in - is treated like a normal argument and not like memory.
Node mmm = isClz ? _returnScope.mem(1) : smem;
// Store constructor results into fields
for( int i=0; i return memory.
_returnScope.mem(mmm);
//_code.add(mmm);
}
// Validate each user constructor, or an implicit empty one, before the
// declaration scope goes away. Later fields use their declaration defaults.
private static boolean initialized(Type t) {
return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
}
private void checkInitFields(TypeStruct ts, int base) {
if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return;
boolean implicit = _ctorExits.isEmpty();
if( implicit ) _ctorExits.add(new IdentityHashMap<>());
for( IdentityHashMap exit : _ctorExits ) {
if( exit==null ) continue;
for( Field fld : ts._fields ) {
int idx = fieldVarIdx(base,fld._fname);
if( idx == -1 ) continue;
Var v = _scope.var(idx);
if( !v._uninit || v._fref ) continue;
Type t = exit.getOrDefault(v,_returnScope.in(idx)._type);
if( !initialized(t) )
throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
(implicit ? "' needs to be set in a constructor" :
"' is only partially set in the constructor"),v._loc);
}
}
}
private int fieldVarIdx( int base, String name ) {
for( int i=base; i<_scope.nIns(); i++ )
if( _scope.var(i)._name == name )
return i;
return -1;
}
// Named constructors update field Vars from the enclosing initializer
// scope. Constructor arguments and locals begin at limit and must not
// shadow the field state gathered into private memory.
private int fieldVarIdxBefore( int limit, String name ) {
for( int i=limit-1; i>=0; i-- )
if( _scope.var(i)._name == name )
return i;
return -1;
}
private boolean isConstructorDecl(String name) {
TypeStruct self = _scope.constructorSelf();
return self != null && name == initFieldName(self._name);
}
private boolean inExplicitConstructor() {
Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
return isExplicitConstructorName(func._name);
}
private boolean isExplicitConstructorName(String funName) {
TypeStruct self = _scope.constructorSelf();
return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
}
private static TypeStruct constructorRecv(TypeStruct self) {
Field[] fs = new Field[self._fields.length];
for( int i=0; i
* '{' statements '}'
*
* Does not parse the opening or closing '{}'
* @return a {@link Node} or {@code null}
*/
private Node parseBlock(Kind kind) {
// Enter a new scope
_scope.push(kind);
Node last = _code.ZERO;
while (!peek('}') && !_lexer.isEOF()) {
_code.add(last);
last = parseStatement();
}
// Exit scope
last.keep();
_scope.pop();
return last.unkeep();
}
/**
* Parses a statement
*
*
* returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
*
* @return a {@link Node} or {@code null}
*/
private Node parseStatement() {
if( matchx("return") ) return parseReturn();
else if (matchx("if") ) return parseIf();
else if (matchx("while") ) return parseWhile();
else if (matchx("for") ) return parseFor();
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration();
else if (matchx(";") ) return _code.ZERO; // 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();
}
private boolean inStructDeclaration() {
return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name);
}
// Constructor declaration: new TypeName = { args -> body };
private Node parseConstructorDeclaration() {
TypeStruct self = _scope.constructorSelf();
assert self != null;
String name = requireId();
String expect = initFieldName(self._name);
if( name != expect )
throw error("Constructor name '" + name + "' must match type name '" + expect + "'");
require("=");
Node ctor = constructorFunc(name);
TypeFunPtr sig = (TypeFunPtr)ctor._type;
String clzName = addClzPrefix(self._name);
TypeStruct clz = (TypeStruct)TYPES.get(clzName);
Field old = clz==null ? null : clz.field("");
if( old != null )
throw error("Redefining constructor '" + name + "'");
Field fld = Field.make("",sig,_code.alias(_ref._cname),true);
TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld));
return require(ctor,";");
}
/**
* Parses a while statement
*
*
* while ( expression ) statement
*
* @return a {@link Node}, never {@code null}
*/
private Node parseWhile() {
require("(");
return parseLooping(false);
}
/**
* Parses a for statement
*
*
* for( var x=init; test; incr ) body
*
* @return a {@link Node}, never {@code null}
*/
private Node parseFor() {
// { var x=init,y=init,...;
// while( test ) {
// body;
// next;
// }
// }
require("(");
_scope.push(new Kind.Block()); // Scope for the index variables
if( !match(";") ) // Can be empty init "for(;test;next) body"
parseDeclarationStatement(); // Non-empty init
Node rez = parseLooping(true);
_scope.pop(); // Exit index variable scope
return rez;
}
// Shared by `for` and `while`
private Node parseLooping( boolean doFor ) {
var savedContinueScope = _continueScope;
var savedBreakScope = _breakScope;
// Loop region has two control inputs, the first is the entry
// point, and second is back edge that is set after loop is parsed
// (see end_loop() call below). Note that the absence of back edge is
// used as an indicator to switch off peepholes of the region and
// associated phis; see {@code inProgress()}.
ctrl(new LoopNode(loc(),null,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
CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
// for( ;;next ) body
int nextPos = -1, nextEnd = -1;
if( doFor ) {
// Skip the next expression and parse it later
nextPos = 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.balanceLoopFRefs(_scope);
exit.balanceLoopFRefs(_scope);
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 _code.ZERO;
}
private ScopeNode jumpTo(ScopeNode toScope) {
ScopeNode cur = _scope.dup();
ctrl(_code.XCTRL); // Kill current scope
// Prune nested lexical scopes that have depth > than the loop head
// We use _breakScope as a proxy for the loop head scope to obtain the depth
while( cur.depth() > _breakScope.depth() )
cur.pop();
// If this is a continue then first time the target is null
// So we just use the pruned current scope as the base for the
// "continue"
if( toScope == null )
return cur;
// toScope is either the break scope, or a scope that was created here
assert toScope.depth() <= _breakScope.depth();
toScope.ctrl(toScope.mergeScopes(cur,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 _code.ZERO; }
private Node parseBreak () {
checkLoopActive();
// At the time of the break, and loop-exit conditions are only valid if
// they are ALSO valid at the break. It is the intersection of
// conditions here, not the union.
_breakScope.removeGuards(_breakScope.ctrl());
_breakScope = require(jumpTo(_breakScope ),";");
_breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false);
return _code.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 TODO();
case ')':
if( --paren<0 ) {
posT( pos() - 1 );
return; // Leave the `)` behind
}
break;
case '(': paren ++; break;
default: break;
}
}
/**
* Parses a statement
*
*
* if ( expression ) statement [else statement]
*
* @return a {@link Node}, never {@code null}
*/
private Node parseIf() {
// Parse predicate
require("(");
var pred = require(parseAsgn(), ")");
return parseTrinary(pred,"else");
}
// Parse a conditional expression, merging results.
private Node parseTrinary( Node pred, String fside ) {
// IfNode takes current control and predicate
pred.keep();
Node ifNode = new IfNode(ctrl(), pred).peephole();
// Setup projection nodes
CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
CFGNode 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 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 "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored
case "||" -> parseLogical(); // pred || expr
default -> throw TODO();
};
rhs.keep();
_scope.removeGuards(ifF);
// The RHS of `||` is parsed on the false side, so retain its updated
// control, memory and locals just as an explicit else arm does.
if( doRHS || fside.equals("||") )
fScope = _scope;
pred.unkeep();
// Check the trinary widening int/flt
if( !fside.equals("else") ) {
rhs = widenInt( rhs.unkeep(), lhs._type ).keep();
lhs = widenInt( lhs.unkeep(), rhs._type ).keep();
}
_scope = tScope;
_xScopes.pop(); // Discard pushed from graph display
// See if a one-sided def was made: "if(pred) int x = 1;" and throw.
// See if any forward-refs were made, and copy them to the other side:
// "pred ? n*fact(n-1) : 1"
tScope.balanceIf(fScope);
// Merge results
RegionNode r = ctrl(tScope.mergeScopes(fScope,loc()));
Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep()));
// Immediately fail e.g. `arg ? 7 : ptr`
ParseException err;
if( !fside.equals("else") && (err=ret.err()) !=null ) throw err;
ret.keep();
r.peephole();
return ret.unkeep();
}
/**
* 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(), ";");
return addReturn(expr);
}
private Node addReturn(Node expr) {
expr.keep();
// Need default memory, since it can be lazy, need to force
// a non-lazy Phi
mem(); // Force a pending lazy bulk-memory Phi
// For and - all fields in scope will be live-on-exit
// and stored into `self` and need to have lazy-phi's inserted.
int lexN = _scope.enclosingFuncOrDecl();
Kind.Func k = (Kind.Func)_scope._kinds.at(lexN);
if( FunNode.isInit(k._name) ) {
int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns();
for( int i=k._lexSize; i and - ALL FIELDS IN LAST SCOPE are live
// and need to have lazy-phi's inserted.
// And fields might not match.... will need matching Var/define with default values
int rlen = _returnScope.nIns()-1;
RegionNode r = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep());
_returnScope._merge(_scope, r, rlen);
Node oldExpr = _returnScope.in(rlen);
_returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole());
_code.add(r);
_returnScope.ctrl(r.unkeep());
}
ctrl(_code.XCTRL); // Kill control
return expr.unkeep();
}
/** 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(); }
// Find variable to update
Var def = _scope.lookup(name);
if( def==null )
def = defineOpenConstructorField(name);
if( def==null )
throw error("Undefined name '" + name + "'");
if( def._extern!=0 ) // Parse the C binding as a memory lvalue
{ pos(old); return parseExpression(); }
// 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.inConstructor() && def._idx >= _scope.klast()._lexSize) &&
!inExplicitConstructor() )
throw error("Cannot reassign final '"+name+"'");
// Parse assignment expression
Node expr = parseAsgn();
// Lift expression, based on type
Type decl = def.type(); // Declared type
Node lift = convertExpr(expr.keep(), decl).keep();
// Update
_scope.update(name,lift);
// Check for storing into an instance field instead of a local var
int kx = _scope.kindx(def); // Declaration scope
int fx = _scope.enclosingFunction(); // Enclosing function scope
Kind kk = _scope._kinds.at(kx);
Kind fk = _scope._kinds.at(fx);
// Access instance field from 'self'
if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) &&
fk instanceof Kind.Func method && !inExplicitConstructor() ) {
// Named field offset on an unknown struct type
TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type();
TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name);
Field fld = sobj.field(name);
int alias = fld._alias;
Node ptr = _scope.in(method._lexSize);
Node off = fldoff(ptr,name);
Node prior = mem().keep();
Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep();
storeMem(st,alias,prior);
st.unkeep();
prior.unkeep();
}
lift.unkill();
// Return un-lifted expr
return expr.unkeep();
}
// Widen ints to floats; narrow wide int types.
// Early error if types do not match variable.
private Node convertExpr( Node expr, Type t ) {
if( !expr._type.accessISA(t) )
throw error("Type "+expr._type.str()+" is not of declared type "+t.str());
if( t instanceof TypeMemPtr tmp && tmp.isFinal() )
expr = peep(new ReadOnlyNode(expr));
// A FunPtr carries the semantic edge to its function's Return. Do not
// hide that edge behind a Convert which can fold to a plain constant.
if( expr instanceof FunPtrNode && t instanceof TypeFunPtr )
return expr;
// Auto-widen array to i64 (cast ptr to raw int bits)
if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]")));
// Widen ints to floats, narrow wide types
return peep(new ConvertNode(t,expr));
}
// Inferred `var` declarations describe writable slots, not the exact
// value used to initialize them. Explicitly typed locals retain the
// user's nullability and numeric-width guarantees; `val` retains the
// initializer's precise type.
private static Type mutableInferredType(Type t) {
return switch( t ) {
case TypeInteger ti -> TypeInteger.BOT;
case TypeFloat tf -> TypeFloat.F64;
case TypeMemPtr tmp -> tmp.makeVar();
case TypeFunPtr tfp -> tfp;
case TypeConAry> a -> a;
case TypePtr ptr -> TypePtr.PTR;
case TypeScalar ts -> TypeScalar.BOT;
default -> t;
};
}
// Seed an inferred return merge with the declared numeric family instead
// of a transient constant/range. Pointer nullability remains part of the
// inferred function result and is combined by the later Phi.
private static Type inferredReturnType(Type t) {
return switch( t ) {
case TypeInteger ti -> TypeInteger.BOT;
case TypeFloat tf -> TypeFloat.F64;
default -> t;
};
}
private Node widenInt( Node expr, Type t ) {
return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
? peep(new ToFloatNode(expr)) : expr;
}
/**
* Parse declaration or expression statement
* declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';'
*
* exprAsgn = var '=' exprAsgn | expr
*/
private Node parseDeclarationStatement() {
int old = pos();
Type t = type();
if( peek('.') ) // Ambiguity static vars: "type.var", parse as expression
{ pos(old); t=null; }
if( t == null )
return require(parseAsgn(),";");
// now parse var['=' asgnexpr] in a loop
Node n = parseDeclaration(t);
while( match(",") )
n = parseDeclaration(t);
return require(n,";");
}
// 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; // Field is final, but not deeply final
boolean uninit = false; // Required non-null constructor field
Node expr;
if( match("=") ) {
if( isExternDecl() ) {
if( !(t instanceof TypeFunPtr) ) {
Node ptr = externData(name,t);
// The lexical binding is a fixed address; its contents are mutable.
if( !_scope.define(name,t,true,ptr,loc) )
throw error("Redefining name '"+name+"'",loc);
_scope.lookup(name)._extern = _code.externAlias(name);
return _code.ZERO;
}
expr = externDecl(name,t);
t = expr._type; // Upgrade declared type to the exact extern decl type
} else if( isConstructorDecl(name) ) {
expr = constructorFunc(name);
} else {
expr = 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 = mutableInferredType(t);
}
// expr is a constant function
if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
t = decl.makeFrom(tfp.fidx());
FunNode fun = _code.link(tfp);
if( fun != null )
fun.setName(name); // Assign debug name to Simple function
}
} else {
// Since no initializer, not-final but might need an initializer
// 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 ) {
// Required pointers retain their declared shape, but include
// null until every constructor path assigns them.
case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL));
case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO;
case TypeFloat tf -> con(xfinal ? Type.BOTTOM : 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); }
};
// Every non-null pointer without a default is constructor-required.
// Without `!` it is also shallow-final; mutability and definite
// initialization are independent properties.
if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) {
uninit = true;
if( xfinal ) fld_final = true;
}
}
// Lift expression, based on type
// Do not let an authoritative Convert hide the nullable
// not-yet-initialized state from constructor-local type flow.
Node lift = uninit ? expr : convertExpr(expr, t);
// Define a new name
if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
_scope.lookup(name)._uninit = uninit;
return lift;
}
/**
* Parse a struct declaration, and return the following statement.
* Structs cannot be redefined, but can be nested.
*
* @return zero
*/
private Node parseStruct( ) {
// keyword "struct" already parsed, so expect the struct name next.
String typeName = requireId();
require("{");
// create type class:NESTED.typeName; no fields.
String old = _nestedType;
String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern();
_nestedType = fullName; // Recursive structs start with this as their basename
ReturnNode ret = parseStruct( false, fullName );
_nestedType = old;
// Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init"
// Function does malloc internally.
// Future self: means subtyping has to deal with "who does the malloc"
String clzName = addClzPrefix(fullName);
TypeFunPtr sig = ret.fun().sig();
TypeStruct ts = (TypeStruct)TYPES.get(clzName);
String initName = initFieldName(fullName);
Field oldFld = ts==null ? null : ts.field(initName);
Field fld = oldFld==null
? Field.make(initName,sig,_code.alias(_ref._cname),true)
: oldFld.makeFrom(sig);
Field ctorFld = ts==null ? null : ts.field("");
ts = ctorFld==null
? TypeStruct.make(clzName,false,fld)
: TypeStruct.make(clzName,false,fld,ctorFld);
TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
ts = ts.add(Field.make("",alloc,_code.alias(_ref._cname),true));
TYPES.put(clzName,ts);
// Insert a field in the containing class with the nested class type.
// This is basically sugar for "val CLZNAME = "
// and is a nicer version of updateSelfAsFieldsDiscovered.
_scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc());
require("}");
return require(_code.ZERO,";");
}
// Build the declaration-side allocation helper. Allocation sites only
// call this function; all layout, initialization and publication choices
// are made here, where the complete struct and constructor are known.
private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
Type[] atypes = new Type[nargs];
for( int i=0; i").intern();
int oldUID = _code.UID();
FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole();
_code.link(fun);
Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,
new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole();
Node[] parms = new Node[nargs];
for( int i=0; i();
// Record & restore global state set during parsing the code
Node oldCtrl= _scope.ctrl().keep();
Node oldMem = _scope.mem ().keep();
// Make the future clazz/instance struct.
TypeStruct tself = (TypeStruct)TYPES.get(typeName);
if( tself==null )
TYPES.put(typeName, tself = TypeStruct.make(typeName,true));
else {
assert tself._open;
TYPES.put(typeName,tself = tself.defined());
}
// Class object keeps a runtime flag to signal the has run.
if( isClz )
TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false)));
// Signature of the default constructor
TypeFunPtr sig = structInitSig(isClz,tself);
// Function and argument names of default constructor
String fname = (typeName + (isClz ? "." : ".")).intern();
String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"};
// Struct decls look like function bodies. Parse function body normally.
ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids);
_ctorExits = oldExits;
// Unwind global state.
ctrl(oldCtrl.unkeep());
mem (oldMem .unkeep());
return ret;
}
// signature: { self arg/selfMem -> BOT/selfMem }
// Self-memory is the rare *private* (never aliased) memory for the new object.
private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) {
// Private uninitialized memory from new object, no escaped values.
TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null);
Type targ = isClz ? TypeInteger.BOT : privMem;
// Return for private memory has to name all escaped fields, which are not known yet.
// Escape ALL fields, and sharpen later.
Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself);
Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ };
// FIDX for FREFs is in the global namespace, needs the correct clz name.
String clzBare = tself._name;
int order = 3; // 3 is always the order, 2nd in the file just behind the
if( isClz ) {
assert startsClzPrefix( clzBare );
clzBare = clzBare.substring(clzPrefix.length()).intern();
order = 2; // 2 is always the order, first in file and skipping reserved #1
}
return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order));
}
// A class type carries the constructor function as a required final field.
private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) {
return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true));
}
private static String initFieldName( String typeName ) {
if( startsClzPrefix(typeName) )
typeName = typeName.substring(clzPrefix.length());
int idx = typeName.lastIndexOf('.');
return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
}
private TypeStruct latestStruct( TypeStruct ts ) {
Type t = TYPES.get(ts._name);
return t instanceof TypeStruct ts2 ? ts2 : ts;
}
private Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) {
ts = latestStruct(ts);
Field fld = ts.field(name);
if( fld != null ) return fld;
if( !ts._open || !ts._fref ) return null;
fld = Field.make(name,Type.BOTTOM,_code.alias(_ref._cname),xfinal);
TYPES.put(ts._name,ts = ts.add(fld));
if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name )
_ctorOpenStruct = ts;
return fld;
}
private Var defineOpenConstructorField( String name ) {
if( _ctorOpenStruct==null ) return null;
Field fld = fieldOrOpen(_ctorOpenStruct,name,true);
if( fld == null ) return null;
_scope.define(name,fld._t,fld._final,con(Type.TOP),loc());
return _scope.lookup(name);
}
// 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[?]
// Structs have nested names, and can be discovered with a partial name
// match. Example, searching for a type name "C.D" in the namespace "A.B".
// This will match "A.B.C.D", then "A.C.D", then "C.D".
// Will also match external names like "sys.io" or "externModule.subtype.subtype"
private Type type() {
// Only type with a leading `{` is a function pointer...
if( peek('{') ) return typeFunPtr();
// Otherwise you get a type name
int old1 = pos();
boolean writable = match("!");
boolean readonly = !writable && match("~");
String tname = _lexer.matchId();
if( tname==null ) return posT(old1);
// Convert the type name to a type.
Type t0 = TYPES.get(tname), t1 = t0;
// No new types as keywords
if( KEYWORDS.contains(tname) ) {
if( t0 == null ) return posT(old1);
// Something like "int" or "f64" or "var".
// Can be "int[]" so still need to check array-ness
} else {
if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
if( t0 != null ) {
while( true ) {
int old2 = pos();
if( !match(".") ) break;
String sname = matchId();
if( sname==null ) { pos(old2); break; }
sname = tname+"."+sname;
Type t = TYPES.get(sname);
if( t==null ) { pos(old2); break; }
tname = sname;
t0 = t;
}
} else {
// Type A.B.C starts with just A. Search our nested type path,
// e.g. path "X.Y.Z", in order:
// If A==X its now a local type.
// If another dot, repeat for B==Y then C==Z
// Else if AX.B exists, done
// Else AX.B is a local forward ref.
//
// Else its a short cut form, one of:
// X.Y.Z.A
// X.Y.A
// X.A
// Else this all failed, assume a forward ref with A as an external symbol:
// A
String root = _nestedType;
int ridx = root.indexOf('.');
if( ridx != -1 ) root = root.substring(0,ridx);
if( tname.equals(root) ) {
boolean localForward = false;
while( true ) {
int old2 = pos();
if( !match(".") ) break;
String sname = matchId();
if( sname==null ) { pos(old2); break; }
sname = tname+"."+sname;
Type t = TYPES.get(sname);
if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) {
pos(old2);
break;
}
tname = sname;
t0 = t;
localForward |= t==null;
}
} else {
String nest = _nestedType, fullq = tname;
while( true ) {
fullq = nest+"."+tname; // Full qualified name
t0 = TYPES.get(fullq);
if( t0 != null )
{ tname = fullq; break; } // Search succeeded
// Subtract a layer from nested typenames
int idx = nest.lastIndexOf('.');
if( idx== -1 ) break; // Search failed; leave as an external symbol
nest = nest.substring(0,idx);
}
if( t0==null ) {
CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname);
if( cu != null ) {
tname = cu._cname;
t0 = TYPES.get(tname);
}
while( true ) {
int old2 = pos();
if( !match(".") ) break;
String sname = matchId();
if( sname==null ) { pos(old2); break; }
tname = tname+"."+sname;
t0 = TYPES.get(tname);
}
} else {
while( true ) {
int old2 = pos();
if( !match(".") ) break;
String sname = matchId();
if( sname==null ) { pos(old2); break; }
sname = tname+"."+sname;
Type t = TYPES.get(sname);
if( t==null ) { pos(old2); break; }
tname = sname;
t0 = t;
}
}
}
}
tname = tname.intern();
// Still no type found? Assume forward reference
t1 = t0 == null
? TypeStruct.forward(tname) // Null: assume a forward ref type
: t0;
}
// Structs are always actually references
if( t1 instanceof TypeStruct ts1 )
t1 = TypeMemPtr.make(ts1);
// 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 ) {
if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
t2 = tmp.makeNullable();
} else
throw error("Type "+t0+" cannot be null");
} else if( match("[~]") ) {
t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
} else if( match("[]") ) {
t2 = TypeMemPtr.make(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(';') || peek('=') || match("->")) || id==null )
return posT(old1); // Reset lexer to reparse
pos(old2); // Reset lexer to reparse
// Yes a forward ref, so declare it
forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj);
// Return the (array, final) type
return t2;
}
private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) {
TYPES.put(tname,tself);
String clzName = addClzPrefix( tname );
TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true));
return TypeMemPtr.make(tself);
}
private TypeMemPtr forwardRefType( String tname ) {
tname = fullTypeName(tname);
return forwardRefType(tname,TypeStruct.forward(tname));
}
private String fullTypeName( String tname ) {
String nest = _nestedType, fullq;
while( true ) {
fullq = nest+"."+tname;
if( TYPES.get(fullq) != null )
return fullq.intern();
int idx = nest.lastIndexOf('.');
if( idx== -1 ) return fullq.intern();
nest = nest.substring(0,idx);
}
}
// Make an array type of t. Always record a mutable version,
// but return the requested version.
private TypeStruct typeAry( Type t, boolean efinal ) {
// Non-null reference arrays are valid types, even though an ordinary
// zero-filled `new T[len]` cannot construct one. Allocation retains
// that check in alloc(); trusted runtimes (notably argv construction)
// may fill the private array completely before publishing it.
String tname = ("[]"+t.str()).intern();
TypeStruct ta = (TypeStruct)TYPES.get(tname);
if( ta==null ) {
// Primitive arrays use the null compunit
String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern();
ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false );
TYPES.put(tname,ta);
}
// Already have the aliases, just efinal is wrong
return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal );
}
private Type implicitSelfType() {
TypeStruct self = _scope.constructorSelf();
// Ordinary instance methods accept the declared object shape. The
// constructor-only receiver starts at the low/default field values,
// and is built explicitly by constructorFunc().
return self == null ? TypePtr.PTR : TypeMemPtr.make(self);
}
// 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
Ary ts = new Ary<>(Type.class);
// Always reserve a receiver slot. Outside constructor scopes it is
// broad and unused, so call sites can uniformly pass their self.
ts.push(implicitSelfType());
if( match("}") ) // No-arg function { -> type }
return TypeFunPtr.make(match("?"),ts.asAry(),t0);
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("?"),ts.asAry(),ret);
}
Type t1 = type();
if( t1==null ) return posT(old); // Not a function
ts.push(t1);
}
}
// True if a TypeFunPtr, without advancing parser
private boolean isTypeFun() {
int old = pos();
if( typeFunPtr()==null ) return false;
pos(old);
return true;
}
/**
* Parse an expression of the form:
*
*
* expr : bitwise [? expr [: expr]]
*
* @return an expression {@link Node}, never {@code null}
*/
private Node parseExpression() {
Node expr = parseLogical();
return match("?") ? parseTrinary(expr,":") : expr;
}
/**
* Parse an bitwise expression
*
*
* bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
*
* @return a bitwise expression {@link Node}, never {@code null}
*/
private Node parseLogical() {
Node lhs = parseBitwise();
while (true) {
if( false ) ;
else if( match("&&") ) lhs = parseTrinary(lhs, "&&");
else if( match("||") ) lhs = parseTrinary(lhs, "||");
else break;
}
return lhs;
}
private Node parseBitwise() {
Node lhs = parseEquality();
while( true ) {
if( false ) ;
else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null);
else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null);
else if( match ("^") ) lhs = new XorNode(loc(),lhs,null);
else break;
lhs.setDef(2,parseEquality());
lhs = peep(lhs);
}
return lhs;
}
/**
* Parse an eq/ne expression
*/
private Node parseEquality() {
Node lhs = parseComparison();
while( true ) {
boolean eq = false;
if( match("==") ) eq = true;
else if( !match("!=") ) break;
lhs.keep();
Node rhs = parseComparison();
lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs));
if( !eq ) lhs = peep(new NotNode(lhs));
}
return lhs;
}
/**
* Parse an expression of the form:
*
*
* expr : shiftExpr < shiftExpr <= shiftExpr...
* expr : shiftExpr > shiftExpr >= shiftExpr...
*
* @return an comparator expression {@link Node}, never {@code null}
*/
private Node parseComparison() {
Node lhs = parseShift();
int dir = parseCompDir();
// No compare
if( dir==0 ) return lhs;
lhs.keep();
// Compare
Node rhs = parseShift().keep();
Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool
// Stacked compares?
int dir0 = parseCompDir();
if( dir0 == 0 ) {
rhs.unkill();
return cmp.unkeep();
}
// rhs is keeped() and becomes lhs
// cmp is NOT keeped() and is the last test
Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole();
Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
// False side does nothing but capture memory & side-effects
ScopeNode fail = _scope.dup();
fail.ctrl(ifF);
// Loop over stacked compares. Each loop moves the old RHS to become
// the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then
// compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc.
while( dir0 != 0 ) {
if( Math.abs(dir) != Math.abs(dir0) )
throw error("Mixing relational directions in a chained relational test");
// True side parses next arm of test
ctrl(ifT.unkeep());
lhs = rhs; // lhs is keeped() and is old RHS
rhs = parseShift().keep();
cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool
ifNode = new IfNode(ctrl(), cmp).peephole();
ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
// Merge result into the fail case
ctrl(ifF);
fail.mergeScopes(_scope.dup(), loc()).peephole();
// Check next stacked compare
dir0 = parseCompDir();
}
rhs.unkill();
ctrl(ifT.unkeep());
RegionNode r = fail.mergeScopes(_scope, loc()).init();
_scope = fail;
return new PhiNode("", r,_code.ZERO,con(1)).peephole();
}
private int parseCompDir() {
if( match("<=") ) return -1; // Negative for "equals-or-less"
if( match(">=") ) return -2; // 1 for <, 2 for >
if( match("<" ) ) return 1; // Positive for just "less"
if( match(">" ) ) return 2;
return 0;
}
private Node makeCompBool( int dir0, Node lhs, Node rhs ) {
// Convert to a bool
if( Math.abs(dir0)==2 ) // Swap direction
{ Node tmp = lhs; lhs = rhs; rhs = tmp; }
lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs));
return peep(lhs);
}
/**
* Parse an additive expression
*
*
* shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
*
* @return a shift expression {@link Node}, never {@code null}
*/
private Node parseShift() {
Node lhs = parseAddition();
while( true ) {
if( false ) ;
else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null);
else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null);
else if( match(">>") ) lhs = new SarNode(loc(),lhs,null);
else break;
lhs.setDef(2,parseAddition());
ParseException err;
if( (err=lhs.err()) != null )
throw err;
lhs = peep(lhs);
}
return lhs;
}
/**
* Parse an additive expression
*
*
* additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
*
* @return an add expression {@link Node}, never {@code null}
*/
private Node parseAddition() {
Node lhs = parseMultiplication();
while( true ) {
if( false ) ;
else if( match("+") ) lhs = new AddNode(lhs,null);
else if( match("-") ) lhs = new SubNode(lhs,null);
else break;
lhs.setDef(2,parseMultiplication());
lhs = peep(lhs);
}
return lhs;
}
/**
* Parse an multiplicativeExpr expression
*
*
* multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
*
* @return a multiply expression {@link Node}, never {@code null}
*/
private Node parseMultiplication() {
var lhs = parseUnary();
while( true ) {
if( false ) ;
else if( match("*") ) lhs = new MulNode(lhs,null);
else if( match("/") ) lhs = new DivNode(lhs,null);
else break;
lhs.setDef(2,parseUnary());
lhs = peep(lhs);
}
return lhs;
}
/**
* Parse a unary minus expression.
*
*
* unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
*
* @return a unary expression {@link Node}, never {@code null}
*/
private Node parseUnary() {
// Pre-dec/pre-inc
int old = pos();
if( match("--") || match("++") ) {
int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
String name = _lexer.matchId();
if( name!=null ) {
Var n = _scope.lookup(name);
if( n != null && !(n.type() instanceof TypeMemPtr) ) {
if( n._extern!=0 )
return parsePostfixName(_scope.in(n),name,delta);
if( n._final )
throw error("Cannot reassign final '"+n._name+"'");
Node expr = 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()));
if (match("!")) return peep(new NotNode(parseUnary()));
// Else be a primary expression
return parsePrimary();
}
/**
* Parse a primary expression:
*
*
* primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
* new Type | '(' expression ')' | Id['++','--'] |
* type['.' Id]*
*
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
if( _lexer.peek('"') ) return parsePostfix(newString(parseString()));
if( matchx("true" ) ) return con(1);
if( matchx("false") ) return _code.ZERO;
if( matchx("null" ) ) return con(Type.NIL);
if( match ("'" ) ) return parseChar();
if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")"));
if( matchx("new" ) ) return parsePostfix(alloc());
if( match ("{" ) ) return parsePostfix(require(func(),"}"));
int pos = pos();
// Expect an identifier now
String id = _lexer.matchId();
if( id == null || KEYWORDS.contains(id) )
throw errorSyntax("an identifier or expression");
int pos2 = pos();
// Attempt a local var lookup first
Var var = _scope.lookup(id);
if( var==null ) {
// Not a local var, try again as a type name
pos(pos);
Type t = type();
if( t!=null ) {
// Static typename lookup
if( peek('.') ) // type.fld, which is really a field lookup in the class object
return parsePostfix(con(t));
// Direct reference to a type. Convert the instance
// type to a class type name
if( !(t instanceof TypeMemPtr tmp) ) {
//return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole();
// Not a local var, but yes a bare type... disallow a local
// FREF and return null, not-a-primary parse.
pos(pos);
return null;
}
}
// Insert FREF outside enclosing Kind.Func scope.
pos(pos2);
t = FRefNode.FREF_TYPE;
// Check if this is a class.
CompUnit cu = ParseAll.findCompUnit(_code,_ref,id);
if( cu!=null ) {
// Must be a final class-type
String clzName = addClzPrefix(cu._cname);
TypeStruct clz = (TypeStruct)TYPES.get(clzName);
if( clz == null )
TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
t = TypeMemPtr.make((byte)2,clz,true);
}
// Define the FRef
var = _scope.defineFRef(id,t,true,loc());
_frefs.push((FRefNode)_scope.in(var));
}
// Load local value
Node rvalue = _scope.in(var);
if( var._extern!=0 )
return parsePostfixName(rvalue,id,0);
if( var._fref )
return parsePostfix(rvalue);
// Required constructor fields begin as their declared pointer type
// widened to include null. Inside the explicit constructor, any read
// before all paths replace that value observes a partially initialized
// object. Ordinary methods run only after construction is complete;
// their enclosing declaration scope still carries the same Var marker.
if( inExplicitConstructor() && var._uninit && rvalue._type instanceof TypeNil tn && tn.nullable() )
throw error("Field '"+id+"' might not be initialized",var._loc);
// Check for an instance field load
int kx = _scope.kindx(var); // Declaration scope
int fx = _scope.enclosingFunction(); // Enclosing function scope
Kind kk = _scope._kinds.at(kx);
Kind fk = _scope._kinds.at(fx);
// Access instance field from 'self'
if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method )
return parsePostfixName(_scope.in(method._lexSize),id,0);
// Check for a function-escaping variable; these require true
// closures. Final constants are OK; final vars require a hidden var
// argument; not-final fields can just use an explicit struct arg.
if( kx < fx && // Out of scope
!(var._final && rvalue._type.isConstant()) ) { // Not a final constant
if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke)
throw error("Variable '"+var._name+"' is out of function scope and must be a final constant");
// var.idx==2 is self argument, 3 is args, 4 or more are now static globals
if( var._idx >= 4 ) {
// Read from global scope.
// Surely a nicer way to get the top-level scope compile name?
Var clzptr = _scope.var( 2 );
// Get the most recent sharpen global
TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name );
TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true );
return parsePostfixName( con( clztmp ), id,0 );
//return parsePostfixName(_scope.in(2),id);
}
}
// Check for assign-update, x += e0;
char ch = _lexer.matchOperAssign();
if( ch==0 ) { // Normal primary, check for postfix updates
int selfx = _scope.kindFcn(var)._lexSize;
// Might be a method call from inside a method, so no explicit "self".
// Pass the in-scope "self"
return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null)));
}
// 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;
}
// 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 OrNode(null,lhs,rhs);
case '&' -> new AndNode(null,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 TODO();
};
// Convert to float ops, or narrow int types; error if not declared type.
// Also, if postfix LHS is still keep()
return convertExpr(peep(op),t);
}
/**
Parse an allocation
*/
private Node alloc() {
Type t = type();
if( t==null ) {
String tname = _lexer.matchId();
if( tname==null || KEYWORDS.contains(tname) )
throw error("Expected a type");
t = forwardRefType(tname);
}
// Parse ary[ length_expr ]
if( match("[") ) {
if( !t.makeZero().isa(t) )
throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
Node len = parseAsgn().keep();
require("]");
return allocArray(typeAry(t,false),len);
} else {
if( !(t instanceof TypeMemPtr tmp) )
throw error("Cannot allocate a "+t.str());
return allocStruct(tmp._obj);
}
}
private Node allocStruct(TypeStruct ts) {
Ary ctorArgs = constructorArgs();
// A field declaration can leave a short forward type name in TYPES.
// Allocation happens in lexical context, so resolve that short name
// to the completed nested type before selecting its class helper.
String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name;
Type resolved = TYPES.get(typeName);
if( resolved instanceof TypeStruct rts ) ts = rts;
TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName));
if( clz==null )
throw error("Unknown struct type '" + ts._name + "'");
Field afld = clz.field("");
Node alloc;
if( afld != null ) {
TypeFunPtr tfp = (TypeFunPtr)afld._t;
FunNode fun = _code.link(tfp);
alloc = fun==null
? new FRefNode((addClzPrefix(typeName)+".").intern(),loc())
: new FunPtrNode(tfp,_code._start,fun.ret()).peephole();
} else {
if( !ts._fref )
throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
Type[] sig = new Type[ctorArgs._len];
for( int i=0; i").intern(),loc(),
TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
}
Ary args = new Ary<>(Node.class);
args.add(ctrl()); args.add(mem());
for( Node arg : ctorArgs ) args.add(arg.unkeep());
args.add(alloc);
return functionCall(args);
}
private Ary constructorArgs() {
Ary args = new Ary<>(Node.class);
if( !match("(") )
return args;
while( !peek(')') ) {
Node arg = parseAsgn();
if( arg==null ) break;
args.push(arg.keep());
if( !match(",") ) break;
}
require(")");
return args;
}
private Node allocArray(TypeStruct ts, Node len) {
Field lenFld = ts.field("#" );
Field bodyFld = ts.field("[]");
ConFldOffNode base = off(ts, "[]");
int scale = ts.aryScale();
Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale)))));
// Build a NewNode; takes in and puts out all aliases.
NewNode nnn = new NewNode(ts, ctrl(), size ).init();
ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep();
// Store length. Rest of array is zero'd via CALLOC during CodeGen.
// Length is casted to sanity.
// TODO: Needs runtime check.
Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true));
// TODO: Allow add-on "constructor" to init the array elements
Node prior = mem();
Node esc = peep(new EscapeNode(lenFld,self,st,prior));
mem(mergeAlias(prior,lenFld._alias,esc));
prior = mem();
esc = peep(new EscapeNode(bodyFld,self,smem,prior));
mem(mergeAlias(prior,bodyFld._alias,esc));
smem.unkeep();
return self.unkeep();
}
private Node newString(String s) {
TypeStruct ts = typeAry(TypeInteger.U8,true);
int lenAlias = ts.field("#" )._alias;
int elemAlias = ts.field("[]")._alias;
TypeConAryB body = TypeConAryB.make(s);
TypeInteger slen = TypeInteger.constant(s.length());
// 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", slen, lenAlias, body, elemAlias, true),true);
assert str.isConstant();
return con(str.makeRO());
}
/**
* Parse postfix expression; this can be a field expression, an array
* lookup or a postfix operator like '#'
*
*
* 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 parsePostfixMethod(Node expr, Node self) {
// TODO: This is too broad because the parser lacks strong types here.
// Some normal functions can end up with a "self" argument.
// Self method call? Self is a TMP-not-CLZ, then assume and pass self.
if( match("(") ) {
expr = parsePostfix(require(functionCall(expr,self),")"));
} else if( expr != self )
self.isKill(); // Not a method call, no need for self
return parsePostfix(expr);
}
private Node parsePostfix(Node expr) {
String name;
if( match(".") ) name = requireId();
else if( match("#") ) name = "#";
else if( match("[") ) name = "[]";
// Can get here without an explicit receiver: "f()" or
// "self.method()(next)(next)(next)". Pass the current function self.
else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")"));
else return expr; // No postfix
return parsePostfixName(expr,name,0);
}
private Node defaultSelf() {
int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize;
return _scope.in(_scope.update(_scope.var(selfx),null));
}
/**
* Parse postfix expression; this can be a field expression, an array
* lookup or a postfix operator like '#'
*
*
* expr ['++' | '--' ] // Postfix unary write operator
* expr [ [op]= expr ] // Field reference
* expr ('[' expr ']')* [ [op]= expr ] // Array reference
*
*/
// pre is +/-1 for a prefix update, otherwise parse a postfix operation.
private Node parsePostfixName(Node expr, String name, int pre) {
// Keep expr across possible updates
expr.keep();
// Get the best known TypeStruct from a pointer type.
TypeStruct ts = null, ts2;
if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)TYPES.get(tmp._obj._name))) != null )
ts = (TypeStruct)tmp._obj.join(ts2);
// A Load's flow type is intentionally not promised during parsing,
// but its declaring field type is still known. Recover that static
// type for chained accesses such as `_grow(1).buf[len++]`; otherwise
// the array Store is parsed as alias #1 and can hide the independent
// side effect of the postfix `len++`.
if( ts==null && expr instanceof LoadNode load ) {
TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null;
if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) {
Type owner0 = TYPES.get(owner._obj._name);
ots = owner0 instanceof TypeStruct obj ? obj : owner._obj;
}
if( ots != null ) {
Field loaded = ots.field(load._name);
if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) {
Type loaded0 = TYPES.get(ltmp._obj._name);
ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj;
}
}
}
Field fld = ts==null ? null : ts.field(name);
if( fld==null && ts!=null && !startsClzPrefix(ts._name) ) {
Type clz0 = TYPES.get(addClzPrefix(ts._name));
if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) {
expr.unkeep();
ts = latestStruct(clz);
expr = con(TypeMemPtr.make((byte)2,ts,true)).keep();
}
}
if( fld==null && ts!=null )
fld = fieldOrOpen(ts,name,false);
if( fld==null && ts!=null && !latestStruct(ts)._open )
throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
// We have to record "active touches" of a new class, to track when we
// need to force class initialization. Basically, we insert an CLInit,
// and if it does not fold away it'll turn into a runtime check.
Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) &&
!(ts!=null && (ts._name+".").equals(func._name)) )
ctrl(new CLInitNode(loc(),ctrl(),expr).peephole());
// With no field, we will update the bulk memory
int alias = fld==null ? 1 : fld._alias;
Type decl = fld==null ? Type.BOTTOM : fld._t;
// Field offset; fixed for structs, computed for arrays
Node off = (name=="[]"
// Array element math, with unknown base offset and shift amount.
? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<")))))
// Named field offset on an unknown struct type
: fldoff(expr,name)).keep();
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( pre==0 && matchOpx('=','=') ) {
// Field assignment
Node val = parseAsgn().keep();
// Lift value for store
Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole();
// Memory for store, post assignment expression
Node mem = mem();
// Store to field
mem.keep();
if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
throw error("Cannot modify final field '"+name+"'");
Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
storeMem(st,alias,mem);
st.unkeep();
mem.unkeep();
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
// Memory for load
Node mem = mem();
// Loading from a constant array, the declared type is the
// meet-over-elements and not the array itself.
if( name=="[]" && decl instanceof TypeConAry conary )
decl = conary.elem();
// Load field
Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off));
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
if( fld!=null && decl==Type.BOTTOM )
throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
Node op = opAssign(ch,load, decl ).keep();
mem.keep();
if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
throw error("Cannot modify final field '"+name+"'");
Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
storeMem(st,alias,mem);
st.unkeep();
mem.unkeep();
op.unkeep(); // A narrow Store may have folded away the expression's conversion.
if( postfix(ch) ) load.unkeep();
return pre==0 && postfix(ch) ? load : op;
}
off.unkill();
// Might be a method call, so pass expr as 'self'
return parsePostfixMethod(load,expr.unkeep());
}
// 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 "{}"
*
*
* { [type arg,]* -> expr }
* { expr } // The no-argument function
*
*/
private Node func() {
Ary ts = new Ary<>(Type.class);
Ary ids = new Ary<>(String.class);
java.util.BitSet finals = new java.util.BitSet();
Type self = implicitSelfType();
ts.push(self);
ids.push(self == TypePtr.PTR ? "#self" : "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 = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname));
ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
return new FunPtrNode(tfp,_code._start,ret).peephole();
}
private Node constructorFunc(String name) {
require("{");
Ary ts = new Ary<>(Type.class);
Ary ids = new Ary<>(String.class);
java.util.BitSet finals = new java.util.BitSet();
TypeStruct self = _scope.constructorSelf();
assert self != null;
ts.push(TypeMemPtr.make(constructorRecv(self)));
ids.push("self");
// Explicit constructors use the same hidden allocation arguments as
// : public memory is the call memory edge (index 1), followed by
// self (index 2) and the private, unescaped object memory (index 3).
ts.push(TypeMem.makePrivate(self));
ids.push("#selfMem");
_lexer.skipWhiteSpace();
Lexer loc = loc();
while( true ) {
Type t = type();
if( t==null ) break;
finals.set(ids.size(),bindingFinal(t));
String id = requireId();
ts .push(t );
ids.push(id);
match(",");
}
require("->");
TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname));
Node[] preParse = _scope.save();
ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry());
_scope.restore(preParse);
require("}");
return new FunPtrNode(tfp,_code._start,ret).peephole();
}
/**
* Parse function call arguments; caller will parse the surrounding "()"
*
* ( arg* )
*
*/
private Node functionCall(Node fcn, Node self) {
if( fcn._type == Type.NIL )
throw error("Calling a null function pointer");
fcn.keep(); // Keep while parsing args
Ary 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.keep());
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,mem().keep());
args.push(fcn); // Function pointer
// Unkeep them all
for( Node arg : args )
arg.unkeep();
return functionCall(args);
}
private Node functionCall( Ary args ) {
// 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,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline
// Control from CallEnd
ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
// Memory from CallEnd
Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
mem(mem);
// Call result
return new ProjNode(cend.unkeep(),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 ConstantNode externDecl( String ex, Type t ) {
if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse
int fidx = _code.fidx(_ref._cname); // Get a FIDX
tfp = tfp.makeFrom(fidx); // Become a constant
_code.externFunc(fidx,ex); // Map fidx to extern name
t = tfp;
}
return (ExternNode)(new ExternNode(t,ex).peephole());
}
// A lexical C binding has a singleton namespace with one external field.
// The namespace occupies no storage; its field offset supplies the address.
private Node externData(String name, Type t) {
if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) )
throw error("C data binding requires an integer or float type");
String cname = ("C:"+name).intern();
TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true));
Type old = TYPES.putIfAbsent(cname,ts);
if( old!=null && old!=ts )
throw error("Conflicting types for C data '"+name+"'");
return con(TypeMemPtr.make((byte)2,ts,true));
}
/**
* Parse integer literal
*
*
* integerLiteral: [1-9][0-9]* | [0]
* floatLiteral: [digits].[digits]?[e [digits]]?[f]?
*
*/
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
ConstantNode con( long con ) { return _code.con(con); }
ConstantNode con( Type t ) { return _code.con(t ); }
// Field offset for a known type (but unknown offset)
public static ConFldOffNode off( TypeStruct ts, String fname ) {
return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
}
// Field offset for an unknown type
public Node fldoff( Node n, String name ) {
return new FldOffNode(n,name,loc()).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)),"'");
}
private boolean noLowerCase(String s) {
for( int i=0; i 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;
/** True when this location precedes {@code that} in the same source. */
public boolean before(Lexer that) {
return that != null && _input == that._input && _position < that._position;
}
public String position() { return _line_number+":"+(_position-_line_start+1); }
// Numeric location for diagnostics that must survive without source text.
public long lineColumn() { return ((long)_line_number<<32) | (_position-_line_start+1); }
/**
* 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 "EOF";
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();
boolean f32 = peek()=='f';
if( f32 ) nextChar();
int n = Math.abs(len);
if( f32 )
return TypeFloat.constant(Float.parseFloat(new String(_input,old,n)));
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,n)));
}
private String parseNumberString() {
int old = _position;
int len = Math.abs(isLongOrDouble());
if( peek()=='f' ) {
nextChar();
len++;
}
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).intern();
}
//
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+" but found `" + _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; }
}
}