summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple/Parser.java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-03 14:03:48 -0700
committerCliff Click <cliffc@acm.org>2026-10-04 13:30:41 -0700
commit9a5ca20d2537179f3a8e01762c8db6b52a4db2d9 (patch)
tree6fe0ab1ae48b05fb6a6e98f73c4dfdc52aec5c5d /src/main/java/com/seaofnodes/simple/Parser.java
parent63fe16fa2ea123b8ce2f976662b0a6d09f352868 (diff)
downloadSimple-linear-chapter17a.tar.gz
Simple-linear-chapter17a.zip
Chapter 17a: Mutability and Read-Only Viewslinear-chapter17a
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/Parser.java')
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java464
1 files changed, 289 insertions, 175 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index deb359ce..f5c371cc 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -4,7 +4,6 @@ import com.seaofnodes.graph.GraphObserver;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
-import static com.seaofnodes.simple.Utils.TODO;
import java.util.*;
@@ -99,12 +98,12 @@ public class Parser {
public final Stack<ScopeNode> _xScopes = new Stack<>();
ScopeNode _continueScope;
- ScopeNode _breakScope;
+ ScopeNode _breakScope; // Merge all the while-breaks here
// Mapping from a type name to a Type. The string name matches
// `type.str()` call. No TypeMemPtrs are in here, because Simple does not
// have C-style '*ptr' references.
- public final HashMap<String, Type> TYPES;
+ public static HashMap<String, Type> TYPES;
// Mapping from a type name to the constructor for a Type.
public final HashMap<String, StructNode> INITS;
@@ -157,7 +156,7 @@ public class Parser {
public static Node find(int nid) { return START.find(nid); }
private Node ctrl() { return _scope.ctrl(); }
- private Node ctrl(Node n) { return _scope.ctrl(n); }
+ private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
public StopNode parse() {
_xScopes.push(_scope);
@@ -168,11 +167,11 @@ public class Parser {
_scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole());
// Parse whole program
- parseBlock();
+ parseBlock(false);
if( ctrl()._type==Type.CONTROL )
STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole());
- _scope.pop();
+ _scope.kill();
_xScopes.pop();
for( StructNode init : INITS.values() )
init.unkeep().kill();
@@ -191,9 +190,9 @@ public class Parser {
* Does not parse the opening or closing '{}'
* @return a {@link Node} or {@code null}
*/
- private Node parseBlock() {
+ private Node parseBlock(boolean inCon) {
// Enter a new scope
- _scope.push();
+ _scope.push(inCon);
while (!peek('}') && !_lexer.isEOF())
parseStatement();
// Exit scope
@@ -212,17 +211,17 @@ public class Parser {
private Node parseStatement() {
if( false ) return null;
else if (matchx("return") ) return parseReturn();
- else if (match ("{") ) return require(parseBlock(),"}");
+ else if (match ("{") ) return require(parseBlock(false),"}");
else if (matchx("if") ) return parseIf();
else if (matchx("while") ) return parseWhile();
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
else if (matchx(";") ) return null; // Empty statement
- // declarations of vars with struct type are handled in parseExpressionStatement due
- // to ambiguity
- else return parseExpressionStatement();
+ // Declaration or normal assignment/expression
+ else return parseDeclarationStatement();
}
+
/**
* Parses a while statement
*
@@ -231,13 +230,13 @@ public class Parser {
* </pre>
* @return a {@link Node}, never {@code null}
*/
+
private Node parseWhile() {
+ require("(");
var savedContinueScope = _continueScope;
var savedBreakScope = _breakScope;
- require("(");
-
// Loop region has two control inputs, the first is the entry
// point, and second is back edge that is set after loop is parsed
// (see end_loop() call below). Note that the absence of back edge is
@@ -257,9 +256,11 @@ public class Parser {
_xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis
// Parse predicate
- var pred = require(parseExpression(), ")");
+ var pred = parseAsgn();
+ require(")");
+
// IfNode takes current control and predicate
- Node ifNode = new IfNode(ctrl(), pred).peephole();
+ Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
// Setup projection nodes
Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
@@ -270,6 +271,7 @@ public class Parser {
// the loop predicate. Note that body Scope is still our current scope.
ctrl(ifF);
_xScopes.push(_breakScope = _scope.dup());
+ _breakScope.addGuards(ifF,pred,true); // Up-cast predicate
// No continues yet
_continueScope = null;
@@ -277,7 +279,9 @@ public class Parser {
// Parse the true side, which corresponds to loop body
// Our current scope is the body Scope
ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate
parseStatement(); // Parse loop body
+ _scope.removeGuards(ifT);
// Merge the loop bottom into other continue statements
if (_continueScope != null) {
@@ -305,7 +309,8 @@ public class Parser {
// the scope is the exit scope after the exit test.
_xScopes.pop();
_xScopes.push(exit);
- return _scope = exit;
+ _scope = exit;
+ return _scope;
}
private ScopeNode jumpTo(ScopeNode toScope) {
@@ -322,14 +327,23 @@ public class Parser {
return cur;
// toScope is either the break scope, or a scope that was created here
assert toScope._lexSize.size() <= _breakScope._lexSize.size();
- toScope.ctrl(toScope.mergeScopes(cur));
+ toScope.ctrl(toScope.mergeScopes(cur).peephole());
return toScope;
}
private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); }
- private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); }
private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); }
+ private Node parseBreak () {
+ checkLoopActive();
+ // At the time of the break, and loop-exit conditions are only valid if
+ // they are ALSO valid at the break. It is the intersection of
+ // conditions here, not the union.
+ _breakScope.removeGuards(_breakScope.ctrl());
+ _breakScope = require(jumpTo(_breakScope ),";");
+ _breakScope.addGuards(_breakScope.ctrl(), null, false);
+ return _breakScope;
+ }
/**
* Parses a statement
@@ -340,9 +354,12 @@ public class Parser {
* @return a {@link Node}, never {@code null}
*/
private Node parseIf() {
- require("(");
// Parse predicate
- var pred = require(parseExpression(), ")").keep();
+ require("(");
+ var pred = require(parseAsgn(), ")");
+
+ pred.keep();
+
// IfNode takes current control and predicate
Node ifNode = new IfNode(ctrl(), pred).peephole();
// Setup projection nodes
@@ -357,20 +374,26 @@ public class Parser {
// Parse the true side
ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
- _scope.upcast(ifT,pred,false); // Up-cast predicate
- parseStatement(); // Parse true-side
+ _scope.addGuards(ifT,pred,false); // Up-cast predicate
+ parseStatement(); // Parse true-side
+ _scope.removeGuards(ifT);
+
ScopeNode tScope = _scope;
// Parse the false side
_scope = fScope; // Restore scope, then parse else block if any
ifF.unkeep(); // fScope already owns the false control
- _scope.upcast(ifF,pred,true); // Up-cast predicate
- if (matchx("else")) {
- parseStatement();
+ // Up-cast predicate, even if not else clause, because predicate can
+ // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;`
+ _scope.addGuards(ifF,pred,true);
+ boolean doRHS = matchx("else");
+ if( doRHS ) parseStatement();
+ _scope.removeGuards(ifF);
+ if( doRHS )
fScope = _scope;
- }
pred.unkeep();
+ // Check for `if(pred) int x=17;`
if( tScope.nIns() != ndefs || fScope.nIns() != ndefs )
throw error("Cannot define a new name on one arm of an if");
@@ -378,10 +401,12 @@ public class Parser {
_scope = tScope;
_xScopes.pop(); // Discard pushed from graph display
- return ctrl(tScope.mergeScopes(fScope));
+ RegionNode r = ctrl(tScope.mergeScopes(fScope));
+ Node ret = r;
+ r.peephole();
+ return ret;
}
-
/**
* Parses a return statement; "return" already parsed.
* The $ctrl edge is killed.
@@ -392,7 +417,7 @@ public class Parser {
* @return an expression {@link Node}, never {@code null}
*/
private Node parseReturn() {
- var expr = require(parseExpression(), ";");
+ var expr = require(parseAsgn(), ";");
Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole());
ctrl(XCTRL); // Kill control
return ret;
@@ -400,89 +425,113 @@ public class Parser {
- /** Parse: name [=expr]
+ /** Parse: [name '='] expr
*/
- private Node parseAsgn(Type t) {
- boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name
- int old = _lexer._position;
- String name = requireId();
- Node expr;
- if( peek(';') || peek(',') ) {
- // Bare "name" is not allowed
- if( t==null ) throw errorSyntax("expression");
- // Does this declaration need an initializer and not getting one?
- Type init = t instanceof TypeMemPtr tmp && !tmp._nil
- // Must be initialized in the "new" constructor
- ? Type.TOP
- // Else takes the default
- : t.makeInit();
- expr = con(init);
-
- } else if( !match("=") ) { // Something else
- _lexer._position = old;
- return null;
- } else
- expr = parseExpression();
-
- // Defining a new variable vs updating an old one
- if( !isDecl ) { // Assigning over an existing name
- // Lookup
- ScopeNode.Var def = _scope.lookup(name);
- if( def==null )
- throw error("Undefined name '" + name + "'");
- t = def._type; // Declared field type
- }
+ private Node parseAsgn() {
+ int old = pos();
+ String name = _lexer.matchId();
+ // Just a plain expression, no assignment.
+ // Distinguish `var==expr` from `var=expr`
+ if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') )
+ { pos(old); return parseExpression(); }
+
+ // Parse assignment expression
+ Node expr = parseAsgn();
+
+ // Final variable to update
+ ScopeNode.Var def = _scope.lookup(name);
+ if( def==null )
+ throw error("Undefined name '" + name + "'");
+
+ // TOP fields are for late-initialized fields; these have never
+ // been written to, and this must be the final write. Other writes
+ // outside the constructor need to check the final bit.
+ if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.canInit(def) )
+ throw error("Cannot reassign final '"+name+"'");
+
+ // Lift expression, based on type
+ Node lift = liftExpr(expr, def.type());
+ // Update
+ _scope.update(name,lift);
+ // Return un-lifted expr
+ return expr;
+ }
+ // Apply declared access permissions; widen floats and narrow integers.
+ // Early error if types do not match variable.
+ private Node liftExpr( Node expr, Type t ) {
+ assert !(expr._type instanceof TypeMemPtr tmp) || !tmp.isFRef();
+ // Read-only access is deep; a fixed binding alone does not remove access.
+ if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) {
+ expr = peep(new ReadOnlyNode(expr));
+ }
// Auto-widen int to float
- if( expr._type instanceof TypeInteger && t instanceof TypeFloat )
- expr = new ToFloatNode(expr).peephole();
+ expr = widenInt( expr, t );
// Auto-narrow wide ints to narrow ints
expr = zsMask(expr,t);
- // Auto-deepen forward ref types
- Type e = expr._type;
- if( e instanceof TypeMemPtr tmp && tmp._obj._fields==null )
- e = TYPES.get(tmp._obj._name);
// Type is sane
- if( !e.isa(t) )
- throw error("Type " + e.str() + " is not of declared type " + t.str());
-
- if( isDecl ) {
- if( !_scope.define(name,t,false,expr) )
- throw error("Redefining name '" + name + "'");
- } else
- _scope.update(name,expr);
+ if( !expr._type.isa(t) || !expr._type.accessISA(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
return expr;
}
- /**
- * Parses an expression statement or a declaration statement where type is a struct
- *
- * <pre>
- * type decl [, decl]*; // Define many names with the same type
- * asgn; // Assign or load a variable
- * expr; // Something else
- * name; // Bare name is an error
+ private Node widenInt( Node expr, Type t ) {
+ return expr._type instanceof TypeInteger && t instanceof TypeFloat
+ ? peep(new ToFloatNode(expr)) : expr;
+ }
- * </pre>
- * @return an expression {@link Node}, never {@code null}
+ /**
+ * Parse declaration or expression statement
+ * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';'
+ * <p>
+ * exprAsgn = var '=' exprAsgn | expr
*/
- private Node parseExpressionStatement() {
- Node n;
+ private Node parseDeclarationStatement() {
Type t = type();
- if( t != null ) {
- // Parse comma-separated declarations
- n = parseAsgn(t);
- while( match(",") )
- n = parseAsgn(t);
-
- // Parse "asgn;" which is just "name = expr;"
- } else if( ( n = parseAsgn(null)) == null ) {
- // Something else
- n = parseExpression();
- }
+ if( t == null )
+ return require(parseAsgn(),";");
+
+ // now parse var['=' asgnexpr] in a loop
+ Node n = parseDeclaration(t);
+ while( match(",") )
+ n = parseDeclaration(t);
return require(n,";");
}
+ // The binding modifier sits immediately before the name. It never
+ // changes permissions on the referenced object.
+ private boolean bindingFinal(Type t) {
+ if( match("!") ) return false;
+ if( match("~") ) return true;
+ return t instanceof TypeMemPtr;
+ }
+
+ /** Parse final: [!|~]var['=' asgn]
+ */
+ private Node parseDeclaration(Type t) {
+ assert t!=null;
+ boolean xfinal = bindingFinal(t);
+ String name = requireId();
+
+ // Optional initializing expression follows
+ Node expr;
+ if( match("=") ) {
+ expr = parseAsgn();
+
+ } else {
+ expr = con(xfinal && !(t instanceof TypeMemPtr) ? Type.TOP : t.makeInit());
+ }
+
+ // Lift expression, based on type
+ Node lift = liftExpr(expr, t);
+
+ // Define a new name,
+ if( !_scope.define(name,t,xfinal,lift) )
+ throw error("Redefining name '" + name + "'");
+ return null;
+ }
+
+
/**
* Parse a struct declaration, and return the following statement.
@@ -495,11 +544,12 @@ public class Parser {
if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope");
String typeName = requireId();
Type t = TYPES.get(typeName);
- if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) )
+ if( t!=null && !(t instanceof TypeMemPtr tmp && tmp.isFRef() ) )
throw errorSyntax("struct '" + typeName + "' cannot be redefined");
// A Block scope parse, and inspect the scope afterward for fields.
- _scope.push();
+ _scope.push(true);
+ _xScopes.push(_scope);
require("{");
while (!peek('}') && !_lexer.isEOF())
parseStatement();
@@ -512,7 +562,7 @@ public class Parser {
for( int i=lexlen; i<varlen; i++ ) {
s.addDef(_scope.in(i));
ScopeNode.Var v = _scope._vars.at(i);
- fs[i-lexlen] = Field.make(v._name,v._type,ALIAS++,v._final);
+ fs[i-lexlen] = Field.make(v._name,v.type(),ALIAS++,v._final);
}
TypeStruct ts = s._ts = TypeStruct.make(typeName, fs);
TYPES.put(typeName, TypeMemPtr.make(ts));
@@ -520,6 +570,7 @@ public class Parser {
// Done with struct/block scope
require("}");
require(";");
+ _xScopes.pop();
_scope.pop();
return null;
}
@@ -529,44 +580,58 @@ public class Parser {
// '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 | struct[?]
private Type type() {
- int old1 = _lexer._position;
+ int old1 = pos();
+ boolean writable = match("!");
+ boolean readonly = !writable && match("~");
String tname = _lexer.matchId();
- if( tname==null ) return null;
+ if( tname==null ) return posT(old1);
// Convert the type name to a type.
Type t0 = TYPES.get(tname);
- Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.make(tname)) : t0; // Null: assume a forward ref type
+ // No new types as keywords
+ if( t0 == null && KEYWORDS.contains(tname) )
+ return posT(old1);
+ Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) : t0; // Null: assume a forward ref type
+ // A qualifier before the name applies to the struct reference, not
+ // to an enclosing array. Arrays have their own [] / [~] qualifier.
+ if( t1 instanceof TypeMemPtr tmp ) {
+ if( readonly || !writable ) t1 = tmp.makeRO();
+ } else if( writable || readonly )
+ return posT(old1);
+
// Nest arrays and '?' as needed
+ Type t2 = t1;
while( true ) {
- assert !(t1 instanceof TypeStruct);
+ assert !(t2 instanceof TypeStruct);
if( match("?") ) {
- if( !(t1 instanceof TypeMemPtr tmp) )
+ if( !(t2 instanceof TypeMemPtr tmp) )
throw error("Type "+t0+" cannot be null");
- if( tmp._nil ) throw error("Type "+t1+" already allows null");
- t1 = TypeMemPtr.make(tmp._obj,true);
- continue;
- }
- if( match("[]") ) {
- t1 = typeAry(t1);
- continue;
- }
- break;
+ if( tmp._nil ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeFrom(true);
+ } else if( match("[~]") ) {
+ TypeMemPtr ary = typeAry(t2);
+ t2 = ary.makeFrom(ary._obj.makeRO()).makeRO();
+ } else if( match("[]") ) {
+ t2 = typeAry(t2);
+ } else
+ break;
}
// Check no forward ref
- if( t0 != null ) return t1;
+ 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 = _lexer._position;
+ int old2 = pos();
+ if( !match("!") ) match("~");
String id = _lexer.matchId();
- _lexer._position = old2; // Reset lexer to reparse
- if( id==null ) {
- _lexer._position = old1; // Reset lexer to reparse
- return null; // Not a type
- }
+ pos(old2); // Reset lexer to reparse
+ if( id==null || _scope.lookup(id)!=null )
+ return posT(old1); // Reset lexer to reparse
// Yes a forward ref, so declare it
- TYPES.put(tname,t1);
- return t1;
+ TYPES.put(tname,((TypeMemPtr)t1).withAccess(false));
+ return t2;
}
// Make an array type of t
@@ -575,7 +640,15 @@ public class Parser {
throw error("Arrays of reference types must always be nullable");
String tname = "["+t.str()+"]";
Type ta = TYPES.get(tname);
- if( ta != null ) return (TypeMemPtr)ta;
+ if( ta != null ) {
+ TypeStruct ary = ((TypeMemPtr)ta)._obj;
+ // Keep the original shallow struct reference. Resolving a recursive
+ // struct must not expand its array element type on each lookup.
+ if( t instanceof TypeMemPtr ptr && (ptr._obj._fields==null || !ptr._obj.isAry()) &&
+ ary._fields[1]._type instanceof TypeMemPtr old )
+ t = old.withAccess(ptr._ro);
+ return TypeMemPtr.make(TypeStruct.makeAry(TypeInteger.BOT,ary._fields[0]._alias,t,ary._fields[1]._alias));
+ }
// Need make an array type.
TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++);
assert ts.str().equals(tname);
@@ -584,12 +657,11 @@ public class Parser {
return tary;
}
-
/**
* Parse an expression of the form:
*
* <pre>
- * expr : compareExpr
+ * expr : bitwise
* </pre>
* @return an expression {@link Node}, never {@code null}
*/
@@ -612,7 +684,7 @@ public class Parser {
else if( match("^") ) lhs = new XorNode(lhs,null);
else break;
lhs.setDef(2,parseComparison());
- lhs = lhs.peephole();
+ lhs = peep(lhs);
}
return lhs;
}
@@ -640,9 +712,9 @@ public class Parser {
else break;
// Peepholes can fire, but lhs is already "hooked", kept alive
lhs.setDef(idx,parseShift());
- lhs = lhs.widen().peephole();
+ lhs = peep(lhs.widen());
if( negate ) // Extra negate for !=
- lhs = new NotNode(lhs).peephole();
+ lhs = peep(new NotNode(lhs));
}
return lhs;
}
@@ -664,7 +736,7 @@ public class Parser {
else if( match(">>") ) lhs = new SarNode(lhs,null);
else break;
lhs.setDef(2,parseAddition());
- lhs = lhs.widen().peephole();
+ lhs = peep(lhs.widen());
}
return lhs;
}
@@ -685,7 +757,7 @@ public class Parser {
else if( match("-") ) lhs = new SubNode(lhs,null);
else break;
lhs.setDef(2,parseMultiplication());
- lhs = lhs.widen().peephole();
+ lhs = peep(lhs.widen());
}
return lhs;
}
@@ -706,7 +778,7 @@ public class Parser {
else if( match("/") ) lhs = new DivNode(lhs,null);
else break;
lhs.setDef(2,parseUnary());
- lhs = lhs.widen().peephole();
+ lhs = peep(lhs.widen());
}
return lhs;
}
@@ -715,13 +787,13 @@ public class Parser {
* Parse a unary minus expression.
*
* <pre>
- * unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
+ * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
* </pre>
* @return a unary expression {@link Node}, never {@code null}
*/
private Node parseUnary() {
- if (match("-")) return new MinusNode(parseUnary()).widen().peephole();
- if (match("!")) return new NotNode(parseUnary()).peephole();
+ if( match("-") ) return peep(new MinusNode(parseUnary()).widen());
+ if( match("!") ) return peep(new NotNode(parseUnary()));
return parsePostfix(parsePrimary());
}
@@ -729,23 +801,33 @@ public class Parser {
* Parse a primary expression:
*
* <pre>
- * primaryExpr : integerLiteral | Identifier | true | false | null | new Type | '(' expression ')'
+ * primaryExpr : integerLiteral | true | false | null | new Type | '(' expression ')' | Id['++','--']
* </pre>
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
- if( match("(") ) return require(parseExpression(), ")");
if( matchx("true" ) ) return con(1);
if( matchx("false") ) return ZERO;
if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR);
+ if( match("(") ) return require(parseAsgn(), ")");
if( matchx("new" ) ) return alloc();
// Expect an identifier now
- String name = _lexer.matchId();
- if( name == null) throw errorSyntax("an identifier or expression");
- ScopeNode.Var n = _scope.lookup(name);
- if( n!=null ) return _scope.in(n);
- throw error("Undefined name '" + name + "'");
+ ScopeNode.Var n = requireLookupId("an identifier or expression");
+ Node rvalue = _scope.in(n);
+ if( rvalue._type == Type.BOTTOM )
+ throw error("Cannot read uninitialized field '"+n._name+"'");
+ return rvalue;
+ }
+
+ ScopeNode.Var requireLookupId(String msg) {
+ String id = _lexer.matchId();
+ if( id == null || KEYWORDS.contains(id) )
+ throw errorSyntax(msg);
+ ScopeNode.Var n = _scope.lookup(id);
+ if( n==null )
+ throw error("Undefined name '" + id + "'");
+ return n;
}
/**
@@ -756,7 +838,7 @@ public class Parser {
if( t==null ) throw error("Expected a type");
// Parse ary[ length_expr ]
if( match("[") ) {
- Node len = parseExpression().keep();
+ Node len = parseAsgn();
if( !(len._type instanceof TypeInteger) )
throw error("Cannot allocate an array with length "+len._type);
require("]");
@@ -781,15 +863,15 @@ public class Parser {
// Push a scope, and pre-assign all struct fields.
_scope.push();
for( int i=0; i<fs.length; i++ )
- _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i));
+ _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i));
// Parse the constructor body
- parseBlock();
+ parseBlock(true);
require("}");
init = _scope._inputs;
}
// Check that all fields are initialized
for( int i=idx; i<init.size(); i++ )
- if( init.get(i)._type == Type.TOP )
+ if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
Node ptr = newStruct(tmp._obj, con(tmp._obj.offset(fs.length)), idx, init );
if( hasConstructor )
@@ -824,7 +906,7 @@ public class Parser {
mem(after.peephole());
out.unkeep();
nnn.unkeep();
- if( prior.unkeep().isUnused() ) prior.kill();
+ prior.unkill();
return ptr.unkeep();
}
@@ -832,11 +914,9 @@ public class Parser {
private Node newArray(TypeStruct ary, Node len) {
int base = ary.aryBase ();
int scale= ary.aryScale();
- Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole();
- ALTMP.clear(); ALTMP.add(len); ALTMP.add(con(ary._fields[1]._type.makeInit()));
- Node ptr = newStruct(ary,size,0,ALTMP);
- len.unkeep();
- return ptr;
+ Node size = peep(new AddNode(con(base),peep(new ShlNode(len.keep(),con(scale)))));
+ ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._type.makeInit()));
+ return newStruct(ary,size,0,ALTMP);
}
// Memory is one hidden SSA variable, including across branches and loops.
@@ -844,12 +924,14 @@ public class Parser {
private void mem(Node n) { _scope.mem(n); }
private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
+ if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
Node prior = mem().keep();
Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init);
st.setDef(0,ctrl);
st = st.peephole();
mem(new MemMergeNode(prior,alias,st).peephole());
- if( prior.unkeep().isUnused() ) prior.kill();
+ prior.unkill();
}
/**
@@ -864,7 +946,7 @@ public class Parser {
*/
private Node parsePostfix(Node expr) {
String name = null;
- if( match(".") ) name = requireId().intern();
+ if( match(".") ) name = requireId();
else if( match("#") ) name = "#";
else if( match("[") ) name = "[]";
else return expr; // No postfix
@@ -878,40 +960,49 @@ public class Parser {
// spoil the user experience with error messages.
if( ctrl()._type==Type.XCONTROL )
// Exit out via parsing the trailing expression
- return matchOpx('=','=') ? parseExpression() : parsePostfix(con(Type.TOP));
+ return matchOpx('=','=') ? parseAsgn() : parsePostfix(con(Type.TOP));
// Sanity check field name for existing
TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name);
- if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
- TypeStruct base = tmp._obj;
+ if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr + "'");
+ TypeStruct base = ptr._obj.isAry() ? ptr._obj : tmp._obj;
int fidx = base.find(name);
if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
// Get field type and layout offset from base type and field index fidx
Field f = base._fields[fidx]; // Field from field index
+ Type tf = f._type;
+ if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
+ tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
expr.keep();
- Node off = name.equals("[]") // If field is an array body
+ // Field offset; fixed for structs, computed for arrays
+ Node off = (name.equals("[]") // If field is an array body
// Array index math
- ? new AddNode(con(base.aryBase()),new ShlNode(require(parseExpression(),"]"),con(base.aryScale())).peephole()).peephole()
+ ? peep(new AddNode(con(base.aryBase()),peep(new ShlNode(require(parseAsgn(),"]"),con(base.aryScale())))))
// Struct field offsets are hardwired
- : con(base.offset(fidx));
+ : con(base.offset(fidx))).keep();
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( matchOpx('=','=') ) {
- off.keep();
- Node val = zsMask(parseExpression(),f._type).keep();
- // Array control stands in for the future bounds check.
- store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null);
- return val.unkeep();
+ Node val = parseAsgn().keep();
+ Node lift = liftExpr( val, tf );
+
+ 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(name, f._alias, f._type.glb(), mem(), expr.unkeep(), off);
+ Node load = new LoadNode(name, f._alias, (tf instanceof TypeMemPtr ? tf : tf.glb()), 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() ) load.setDef(0,ctrl());
- return parsePostfix(load.peephole());
+ if( base.isAry() ) load.setDef(0,ctrl());
+ load = peep(load);
+
+ expr.unkill();
+ off.unkill();
+
+ return parsePostfix(load);
}
@@ -922,16 +1013,16 @@ public class Parser {
if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
return val;
// Float rounding
- return new RoundF32Node(val).peephole();
+ return peep(new RoundF32Node(val));
}
if( t0._min==0 ) // Unsigned
- return new AndNode(val,con(t0._max)).peephole();
+ return peep(new AndNode(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 new SarNode(new ShlNode(val,shf.keep()).peephole(),shf.unkeep()).peephole();
+ return peep(new SarNode(peep(new ShlNode(val,shf.keep())),shf.unkeep()));
}
@@ -946,6 +1037,10 @@ public class Parser {
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
public static Node con( long con ) { return 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());
+ }
//////////////////////////////////
// Utilities for lexical analysis
@@ -959,14 +1054,28 @@ public class Parser {
private boolean peek(char ch) { return _lexer.peek(ch); }
private boolean peekIsId() { return _lexer.peekIsId(); }
+ private int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ private Type posT(int pos) { _lexer._position = pos; return null; }
+
// Require and return an identifier
private String requireId() {
String id = _lexer.matchId();
- if (id != null && !KEYWORDS.contains(id) ) return id;
+ 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; }
@@ -1023,6 +1132,8 @@ public class Parser {
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 char nextChar() {
char ch = peek();
@@ -1051,6 +1162,8 @@ public class Parser {
}
}
+ // 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.
@@ -1157,12 +1270,13 @@ public class Parser {
//
private boolean isPunctuation(char ch) {
- return "=;[]<>()+-/*".indexOf(ch) != -1;
+ return "=;[]<>()+-/*&|^".indexOf(ch) != -1;
}
private String parsePunctuation() {
int start = _position;
return new String(_input, start, 1);
}
+
}
}