summaryrefslogtreecommitdiff
path: root/src/main/java/com/seaofnodes/simple
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/seaofnodes/simple')
-rw-r--r--src/main/java/com/seaofnodes/simple/README.md2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Coalesce.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/IFG.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/InstSelect.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Machine.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ParseAll.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java51
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/SourceError.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FldOffNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FormatNode.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Allocations.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/ControlFlow.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Declarations.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Expressions.java41
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Functions.java13
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Lexer.java139
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Parser.java23
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/README.md2
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/References.java46
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Statements.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Strings.java59
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Structs.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/Types.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/parser/package-info.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAry.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryB.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeInteger.java10
37 files changed, 503 insertions, 151 deletions
diff --git a/src/main/java/com/seaofnodes/simple/README.md b/src/main/java/com/seaofnodes/simple/README.md
index 613f66b4..af86f21e 100644
--- a/src/main/java/com/seaofnodes/simple/README.md
+++ b/src/main/java/com/seaofnodes/simple/README.md
@@ -2,7 +2,7 @@
| Directory | Contents |
| --- | --- |
-| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter29/29-grammar.md). |
+| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter30/30-grammar.md). |
| `node/` | Sea-of-Nodes IR nodes: graph edges, type computation, and local peephole rewrites. |
| `node/cpus/` | Machine-specific nodes and instruction selection support for `arm`, `riscv`, and `x86_64_v2`. |
| `codegen/` | Compilation driver and passes: module loading, optimization, scheduling, register allocation, serialization, and machine-code emission. `IterPeeps` runs the peephole worklist. |
diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
index 0b314978..8d0237ea 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -16,7 +16,7 @@ abstract public class BuildLRG {
// true if no hard-conflicts, although we still might not color.
public static boolean run(int round, RegAlloc alloc) {
FunNode fun = null;
- for( Node bb : alloc._code._cfg ) {
+ for( Node bb : alloc._cfg ) {
if( bb instanceof FunNode fun2 )
fun = fun2;
for( Node n : bb.outs() ) {
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
index 689ceceb..dbde2f8b 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
@@ -13,7 +13,7 @@ abstract public class Coalesce {
// Walk all the splits, looking for coalesce chances
boolean progress = false;
- for( CFGNode bb : alloc._code._cfg ) {
+ for( CFGNode bb : alloc._cfg ) {
outer:
for( int j=0; j<bb.nOuts(); j++ ) {
Node n = bb.out(j);
diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index d15e66da..6ad7c0ad 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -72,7 +72,7 @@ abstract public class IFG {
WORK.clear();
// Last block has nothing live out
- for( CFGNode bb : alloc._code._cfg )
+ for( CFGNode bb : alloc._cfg )
if( bb.blockHead())
WORK.push( bb );
@@ -164,6 +164,10 @@ abstract public class IFG {
// Also record use-side spills for biased coloring.
// Then make all inputs live.
for( int i=1; i<n.nIns(); i++ ) {
+ // Metadata and scheduling dependencies do not consume registers.
+ // In particular, a static call's hidden class argument has no ABI
+ // location and must not keep its constant live across other calls.
+ if( ((MachNode)n).regmap(i)==null ) continue;
Node def = n.in(i);
if( def==null ) continue;
LRG lrg1 = alloc.lrg(def);
@@ -480,7 +484,7 @@ abstract public class IFG {
private static int functionArea(FunNode fun, RegAlloc alloc) {
int area=0;
- for( CFGNode bb : alloc._code._cfg )
+ for( CFGNode bb : alloc._cfg )
if( bb.fun()==fun )
area += bb.nOuts();
return area;
diff --git a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
index 5fc002a1..2595b4ad 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
@@ -85,13 +85,7 @@ class InstSelect {
}
// Produce a machine node from n; map it to flag as done so stops cycles.
- if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
- // External fields replace base+offset with symbol+0. Keep the ideal
- // declaring type on the address for machine load/store width selection.
- TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
- n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
- n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
- }
+ if( n instanceof MemOpNode mem ) Machine.externalAddress(mem);
map.put(n, x=_code._mach.instSelect(n) );
// Carry loop-tree and pre-order info across the ideal->mach transition
if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 3eee8fc3..6ccf6b19 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -3,6 +3,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
@@ -46,6 +47,17 @@ abstract public class Machine {
// Instruction select from ideal nodes
public abstract Node instSelect( Node n );
+ // Lower external fields before selecting a memory operation, including
+ // loads absorbed into another instruction rather than selected separately.
+ public static void externalAddress(MemOpNode mem) {
+ if( mem.off() instanceof ExternOffsetNode off ) {
+ // Retain the declaring type for load/store width selection.
+ TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
+ mem._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
+ mem._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
+ }
+ }
+
/** Stack Slot Numbering
RA gives all registers a number, starting at 0. Stack slots continue this
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index 5f8a0ec0..3306ff8f 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -140,7 +140,7 @@ public abstract class ParseAll {
fref._name = TypeStruct.addClzPrefix(xcunit._cname);
cunit.addDep(xcunit);
} else {
- throw new RuntimeException("Undefined name '" + fref._name +"'");
+ throw fref.err();
}
} catch( IOException ioe ) {
throw new RuntimeException(ioe);
@@ -297,14 +297,6 @@ public abstract class ParseAll {
Parser.TYPES.put(cunit._cname,ptr._obj);
}
- // Symbol not found in source code (or we would not be here).
- // Search the module for the fref
- public static CompUnit findCompUnitOrThrow(CodeGen code, CompUnit cu, String symbol) {
- CompUnit cu2 = findCompUnit(code,cu,symbol);
- if( cu2 != null ) return cu2;
- throw new RuntimeException("Undefined name '" + symbol +"'");
- }
-
public static CompUnit findCompUnit(CodeGen code, CompUnit cu, String symbol) {
// Symbol not found in source code (or we would not be here).
// Search the module for the fref
@@ -328,8 +320,15 @@ public abstract class ParseAll {
// However if A/B contains "Y", the search would be for A.B.Y then A.Y both of
// which do not exist.
private static CompUnit findCUnitModule( CodeGen code, CompUnit cunit, String symbol ) throws IOException {
- if( cunit == null ) // Ran off top of module scope?
- return null; // Failed search
+ if( cunit == null ) {
+ // The module root is also a namespace. In particular, a root
+ // client can resolve sys.text from source, without installed .o's.
+ String fname=symbol.replace('.','/');
+ if( !new File(code._modDir+"/"+fname+".smp").exists() ) return null;
+ CompUnit root=makeCUnit(code,null,fname);
+ if( root._clz==null && WORK.find(root)==-1 ) enqueue(root);
+ return root;
+ }
// Check self
if( cunit._name.equals(symbol) )
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index f1aa18ef..3991cd48 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -54,6 +54,8 @@ public class RegAlloc {
// Top-level program graph structure
final CodeGen _code;
+ // Scheduled blocks of the function currently being allocated.
+ Ary<CFGNode> _cfg;
public int _spills, _spillScaled;
@@ -161,7 +163,7 @@ public class RegAlloc {
}
// -----------------------
- RegAlloc( CodeGen code ) { _code = code; }
+ RegAlloc( CodeGen code ) { _code = code; _cfg = code._cfg; }
public void regAlloc() {
// Insert callee-save registers
@@ -186,18 +188,41 @@ public class RegAlloc {
// Expensive assert: useful when chasing cross-function allocator edges.
//assert verifyFunctionLocalEdges();
- // Top driver: repeated rounds of coloring and splitting.
- byte round=0;
- while( !graphColor(round) ) {
- split(round);
- if( round >= 7 ) // Really expect to be done soon
- throw new IllegalStateException("Register allocation made no progress after eight rounds");
- round++;
+ // Each function owns its retry budget. Keep completed assignments while
+ // reusing the live-range and interference scratch space for the next.
+ IdentityHashMap<Node,LRG> colored = new IdentityHashMap<>();
+ _cfg = new Ary<>(CFGNode.class);
+ for( CFGNode bb : _code._cfg ) {
+ if( bb instanceof FunNode && !_cfg.isEmpty() ) {
+ allocateFunction();
+ colored.putAll(_lrgs);
+ _cfg.clear();
+ }
+ _cfg.add(bb);
}
+ if( !_cfg.isEmpty() ) {
+ allocateFunction();
+ colored.putAll(_lrgs);
+ }
+ _lrgs.clear();
+ _lrgs.putAll(colored);
+ _cfg = _code._cfg;
postColor(); // Remove no-op spills
_done = true;
}
+ private void allocateFunction() {
+ _deferredLoopSelf.clear();
+ for( byte round=0; !graphColor(round); round++ ) {
+ split(round);
+ if( round >= 7 ) {
+ FunNode fun = _cfg.at(0).fun();
+ throw new IllegalStateException("Register allocation failed after eight rounds in "+
+ (fun==null ? "<root>" : fun.label()));
+ }
+ }
+ }
+
boolean verifyFunctionLocalEdges() {
for( CFGNode bb : _code._cfg )
for( Node use : bb._outputs ) {
@@ -477,7 +502,10 @@ public class RegAlloc {
split.killOrdered();
}
for( int i=1; i<(deferred.containsKey(phi) ? 2 : phi.nIns()); i++ )
- if( i==1 || sharedPhiInput(phi,i) )
+ // Backedges may be joined indirectly through two-address
+ // chains, even when their input nodes are distinct. After
+ // the cold-only attempt, isolate those inputs as well.
+ if( i==1 || phi.region() instanceof LoopNode || sharedPhiInput(phi,i) )
insertBefore(phi,i,"use/self/phi",round,lrg);
}
// Split before two-address ops which extend the live range
@@ -653,9 +681,8 @@ public class RegAlloc {
return;
}
makeSplit(def,kind,round,lrg,umask).insertBefore(n, i);
- // Skip split-of-split same block
- if( skip && def instanceof SplitNode && cfg==def.cfg0() )
- n.in(i).setDefOrdered(1,def.in(1));
+ // Keep the def-side transfer: a kill between the two copies may be
+ // exactly why this range split. Coalescing can join them when safe.
}
void insertBefore(Node n, int i, String kind, byte round, LRG lrg) {
insertBefore(n,i,kind,round,lrg,true);
diff --git a/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
index f2f46146..5be0585f 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
@@ -16,6 +16,10 @@ public abstract class SourceError {
source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8)
: file==null ? null : Files.readAllBytes(Path.of(file));
} catch( IOException ex ) { source=null; }
+ return format(name,source,pos,message);
+ }
+
+ public static String format(String name, byte[] source, int pos, String message) {
if( source==null || pos<0 || pos>source.length )
return name+": error: "+message+" (source unavailable at byte offset "+pos+")";
@@ -28,22 +32,24 @@ public abstract class SourceError {
int point=Math.min(pos,end);
String before=new String(source,start,point-start,StandardCharsets.UTF_8);
String text=new String(source,start,end-start,StandardCharsets.UTF_8);
- int column=before.codePointCount(0,before.length())+1;
- SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n ");
+ SB excerpt=new SB();
int width=0, caret=0;
for( int i=0; i<text.length(); ) {
if( i==before.length() ) caret=width;
int ch=text.codePointAt(i);
if( ch=='\t' ) {
int spaces=8-(width%8);
- for( int j=0; j<spaces; j++ ) sb.p(' ');
+ for( int j=0; j<spaces; j++ ) excerpt.p(' ');
width+=spaces;
- } else { sb.p(new String(Character.toChars(ch))); width++; }
+ } else { excerpt.p(new String(Character.toChars(ch))); width++; }
i+=Character.charCount(ch);
}
if( before.length()==text.length() ) caret=width;
+ // Emacs compilation mode defaults to display columns, with 8-column tabs.
+ SB sb=new SB().p(name).p(':').p(line).p(':').p(caret+1).p(": error: ").p(message)
+ .p("\n ").p(excerpt.toString());
sb.p("\n ");
- for( int i=0; i<caret; i++ ) sb.p('-');
+ for( int i=0; i<caret; i++ ) sb.p('_');
return sb.p('^').toString();
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 91d27330..ac5e4eee 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -242,7 +242,7 @@ public class CallNode extends CFGNode {
if( !tfp.notNull() )
return Parser.error( "Might be null calling "+tfp, _loc);
if( nargs() != tfp.nargs() )
- return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc);
+ return Parser.error( "Expecting "+(tfp.nargs()-1)+" arguments, but found "+(nargs()-1), _loc);
// Check for args
for( int i=0; i<tfp.nargs(); i++ )
diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
index 09e5eac8..e49162f4 100644
--- a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -49,7 +49,7 @@ public class FldOffNode extends TypeNode {
@Override public Parser.ParseException err() {
if( in(1)._type instanceof TypeMemPtr tmp && tmp._obj._fref )
- return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null);
+ return Parser.error("Unknown struct type '"+tmp._obj._name+"'",_loc);
return null;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FormatNode.java b/src/main/java/com/seaofnodes/simple/node/FormatNode.java
new file mode 100644
index 00000000..d34b342d
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FormatNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+
+/** Resolve default formatting through normal type flow, including forward
+ * references. Valid programs fold this selector before serialization/codegen. */
+public final class FormatNode extends Node {
+ private final Lexer _site;
+ public FormatNode(Node value, Node ints, Node floats, Node strings, Lexer site) {
+ super(null,value,ints,floats,strings);
+ _site=site;
+ }
+ @Override public String label() { return "Format"; }
+ private int choice() {
+ Type t=in(1)._type;
+ if( t instanceof TypeInteger ) return 2;
+ if( t instanceof TypeFloat ) return 3;
+ if( t instanceof TypeMemPtr p && !p.nullable() && p._obj.isAry() &&
+ p._obj._name.equals("[]u8") ) return 4;
+ return 0;
+ }
+ @Override public Type compute() {
+ if( in(1)._type.isHigh() ) return Type.TOP;
+ int c=choice();
+ return c==0 ? in(2)._type.meet(in(3)._type).meet(in(4)._type) : in(c)._type;
+ }
+ @Override public Node idealize() { int c=choice(); return c==0 ? null : in(c); }
+ @Override public Parser.ParseException err() {
+ return Parser.error("Cannot format "+in(1)._type+"; expected an integer, float, or byte string",_site);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 85ab29f9..eb78ed1f 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -395,6 +395,9 @@ public class LoadNode extends MemOpNode {
// load will need to zero/sign-extend.
private Node extend(Node val) {
if( !(declaredType() instanceof TypeInteger ti) ) return val;
+ // A typed load can already know its width while the stored value is
+ // still unresolved. Do not replace it with an untyped shift/and chain.
+ if( !(val._type instanceof TypeInteger) ) { addDep(val); return null; }
if( ti._min==0 ) // Unsigned
return new AndNode(null,val,con(ti._max));
// Signed extension
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 1130cf44..80d2aa0a 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -638,6 +638,7 @@ public class ScopeNode extends MemMergeNode {
int i = _guards._len;
while( i > 0 ) {
Node cast = _guards.at(--i);
+ if( cast instanceof FunNode ) break; // Function boundary
if( cast instanceof CFGNode ) continue; // Marker between guard sets
Node xpred = _guards.at(--i);
if( xpred == pred )
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
index 814a82c2..da9afe71 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -6,14 +6,21 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
-// Convert a narrow C return register to Simple's full-width integer value.
+// Normalize narrow parameters and C return values to full-width integers.
public class ExtendARM extends MachConcreteNode {
final int _bits;
final boolean _signed;
ExtendARM(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjARM(prj));
+ this(prj,new ProjARM(prj),ret);
+ }
+ ExtendARM(ParmNode parm, TypeInteger decl) {
+ this(parm,new ParmARM(parm),decl);
+ }
+ private ExtendARM(Node ideal, Node value, TypeInteger ret) {
+ super(ideal);
+ _inputs.clear();
+ _inputs.add(null);
+ _inputs.add(value);
_bits = 8 << ret.log_size();
_signed = ret._min < 0;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 3c965da5..ee7b0fc3 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -332,7 +332,8 @@ public class arm extends Machine {
case NewNode nnn -> new NewARM(nnn);
case NotNode not -> new NotARM(not);
case OrNode or -> or(or);
- case ParmNode parm -> new ParmARM(parm);
+ case ParmNode parm -> parm._declaredType instanceof TypeInteger ti && ti.log_size()<3
+ ? new ExtendARM(parm,ti) : new ParmARM(parm);
case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
index 5bd3ec1c..11182133 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -6,14 +6,21 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
-// Convert a narrow C return register to Simple's full-width integer value.
+// Normalize narrow parameters and C return values to full-width integers.
public class ExtendRISC extends MachConcreteNode {
final int _bits;
final boolean _signed;
ExtendRISC(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjRISC(prj));
+ this(prj,new ProjRISC(prj),ret);
+ }
+ ExtendRISC(ParmNode parm, TypeInteger decl) {
+ this(parm,new ParmRISC(parm),decl);
+ }
+ private ExtendRISC(Node ideal, Node value, TypeInteger ret) {
+ super(ideal);
+ _inputs.clear();
+ _inputs.add(null);
+ _inputs.add(value);
_bits = 8 << ret.log_size();
_signed = ret._min < 0;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 0195542c..378955a5 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -236,7 +236,8 @@ public class riscv extends Machine {
case NewNode nnn -> nnn(nnn);
case NotNode not -> new NotRISC(not);
case OrNode or -> or(or);
- case ParmNode parm -> new ParmRISC(parm);
+ case ParmNode parm -> parm._declaredType instanceof TypeInteger ti && ti.log_size()<3
+ ? new ExtendRISC(parm,ti) : new ParmRISC(parm);
case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
index 1a6ae123..9f4da6b8 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -6,14 +6,21 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
-// Convert a narrow C return register to Simple's full-width integer value.
+// Normalize narrow parameters and C return values to full-width integers.
public class ExtendX86 extends MachConcreteNode {
final int _bits;
final boolean _signed;
ExtendX86(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjX86(prj));
+ this(prj,new ProjX86(prj),ret);
+ }
+ ExtendX86(ParmNode parm, TypeInteger decl) {
+ this(parm,new ParmX86(parm),decl);
+ }
+ private ExtendX86(Node ideal, Node value, TypeInteger ret) {
+ super(ideal);
+ _inputs.clear();
+ _inputs.add(null);
+ _inputs.add(value);
_bits = 8 << ret.log_size();
_signed = ret._min < 0;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 3dc93bf6..3d71b4ea 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -306,7 +306,8 @@ public class x86_64_v2 extends Machine {
case NewNode nnn -> new NewX86(nnn);
case NotNode not -> new NotX86(not);
case OrNode or -> or(or);
- case ParmNode parm -> new ParmX86(parm);
+ case ParmNode parm -> parm._declaredType instanceof TypeInteger ti && ti.log_size()<3
+ ? new ExtendX86(parm,ti) : new ParmX86(parm);
case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
@@ -555,6 +556,7 @@ public class x86_64_v2 extends Machine {
private static int off, scale, imm;
private static Node idx, val;
private <N extends MemOpNode > N address(N mop) {
+ externalAddress(mop);
off = scale = imm = 0; // Reset
idx = val = null;
Node base = mop.ptr();
diff --git a/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/src/main/java/com/seaofnodes/simple/parser/Allocations.java
index be68e10e..861f95b6 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Allocations.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Allocations.java
@@ -14,28 +14,30 @@ final class Allocations {
Parse an allocation
*/
Node parseNew() {
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
Type t = P.types.parseType();
if( t==null ) {
String tname = P._lexer.matchId();
if( tname==null || Parser.KEYWORDS.contains(tname) )
- throw P.error("Expected a type");
+ throw Parser.error("Expected a type after 'new'",site);
t = P.types.forwardRefType(tname);
}
// Parse ary[ length_expr ]
if( P.match("[") ) {
if( !t.makeZero().isa(t) )
- throw P.error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
+ throw Parser.error("Cannot allocate a non-nullable, since arrays are always zero/null filled",site);
Node len = P.expressions.parseAssignment().keep();
P.require("]");
return allocArray(P.types.typeAry(t,false),len);
} else {
if( !(t instanceof TypeMemPtr tmp) )
- throw P.error("Cannot allocate a "+t.str());
- return allocStruct(tmp._obj);
+ throw Parser.error("Cannot allocate a "+t.str(),site);
+ return allocStruct(tmp._obj,site);
}
}
- private Node allocStruct(TypeStruct ts) {
+ private Node allocStruct(TypeStruct ts, Lexer site) {
Ary<Node> ctorArgs = constructorArgs();
// A field declaration can leave a short forward type name in TYPES.
// Allocation happens in lexical context, so resolve that short name
@@ -45,7 +47,7 @@ final class Allocations {
if( resolved instanceof TypeStruct rts ) ts = rts;
TypeStruct clz = (TypeStruct)Parser.TYPES.get(TypeStruct.addClzPrefix(typeName));
if( clz==null )
- throw P.error("Unknown struct type '" + ts._name + "'");
+ throw Parser.error("Unknown struct type '" + ts._name + "'",site);
Field afld = clz.field("<new>");
Node alloc;
if( afld != null ) {
@@ -56,7 +58,7 @@ final class Allocations {
: new FunPtrNode(tfp,P._code._start,fun.ret()).peephole();
} else {
if( !ts._fref )
- throw P.error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ throw Parser.error("Constructor arguments for '" + ts._name + "' but no constructor is defined",site);
Type[] sig = new Type[ctorArgs._len];
for( int i = 0; i<ctorArgs._len; i++ )
sig[i] = ctorArgs.at(i)._type;
@@ -111,12 +113,12 @@ final class Allocations {
return self.unkeep();
}
- Node newString(String s) {
+ Node newString(byte[] s) {
TypeStruct ts = P.types.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());
+ 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.
diff --git a/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java b/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java
index caa73076..6c626669 100644
--- a/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java
+++ b/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java
@@ -14,6 +14,8 @@ final class ControlFlow {
// Parse a conditional expression, merging results.
Node conditional( Node pred, String fside ) {
+ Lexer site=P.loc();
+ if( !fside.equals("else") ) site._position-=fside.equals(":") ? 1 : 2;
// IfNode takes current control and predicate
pred.keep();
Node ifNode = new IfNode(P.ctrl(), pred).peephole();
@@ -88,7 +90,7 @@ final class ControlFlow {
Node ret = P.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;
+ if( !fside.equals("else") && (err = ret.err()) !=null ) throw Parser.error(err.getMessage(),site);
ret.keep();
r.peephole();
return ret.unkeep();
diff --git a/src/main/java/com/seaofnodes/simple/parser/Declarations.java b/src/main/java/com/seaofnodes/simple/parser/Declarations.java
index 91955197..13fa59b8 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Declarations.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Declarations.java
@@ -47,15 +47,17 @@ final class Declarations {
boolean inferType = t==Type.TOP || t==Type.BOTTOM;
boolean xfinal = bindingFinal(t);
Lexer loc = P.loc();
- String name = P.requireId();
+ String name = P.requireId(P._scope.klast() instanceof Kind.Func fun && FunNode.isInit(fun._name));
// 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( P.match("=") ) {
+ P._lexer.skipWhiteSpace();
+ Lexer initializer=P.loc();
if( isExternDecl() ) {
if( !(t instanceof TypeFunPtr) ) {
- Node ptr = externData(name,t);
+ Node ptr = externData(name,t,loc);
// The lexical binding is a fixed address; its contents are mutable.
if( !P._scope.define(name,t,true,ptr,loc) )
throw Parser.error("Redefining name '"+name+"'",loc);
@@ -72,8 +74,12 @@ final class Declarations {
// var/val, then type comes from expression
if( inferType ) {
if( expr._type==Type.NIL )
- throw P.error("a not-null/non-zero expression");
+ throw Parser.error("Cannot infer a type from null; declare a nullable type explicitly",initializer);
t = expr._type;
+ // A typed expression may still await a forward callee's flow
+ // type (e.g. interpolation while sys.text is parsed). Its
+ // checked conversion already supplies a binding/storage type.
+ if( t==Type.BOTTOM && expr instanceof ConvertNode convert ) t=convert.dst();
if( !xfinal ) t = Conversions.mutableInferredType(t);
// Instance fields may change until the constructor finishes.
// Keep the declared family/access, not the default's constant
@@ -133,8 +139,7 @@ final class Declarations {
// Just after parsing "type foo = " can parse `"C"`
private boolean isExternDecl( ) {
int old = P.pos();
- String s = P._lexer.parseString();
- if( "C".equals(s) ) return true;
+ if( P.match("\"C\"") ) return true;
P.pos(old);
return false;
}
@@ -151,14 +156,14 @@ final class Declarations {
// 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) {
+ private Node externData(String name, Type t, Lexer site) {
if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) )
- throw P.error("C data binding requires an integer or float type");
+ throw Parser.error("C data binding requires an integer or float type",site);
String cname = ("C:"+name).intern();
TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,P._code.externAlias(name),false,true));
Type old = Parser.TYPES.putIfAbsent(cname,ts);
if( old!=null && old!=ts )
- throw P.error("Conflicting types for C data '"+name+"'");
+ throw Parser.error("Conflicting types for C data '"+name+"'",site);
return P.con(TypeMemPtr.make((byte)2,ts,true));
}
}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Expressions.java b/src/main/java/com/seaofnodes/simple/parser/Expressions.java
index 93c07d8a..247d974e 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Expressions.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Expressions.java
@@ -15,6 +15,8 @@ final class Expressions {
/** Parse: [name '='] expr
*/
Node parseAssignment() {
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
int old = P.pos();
String name = P._lexer.matchId();
// Just a plain expression, no assignment.
@@ -24,13 +26,16 @@ final class Expressions {
// Find variable to update
Var def = P._scope.lookup(name);
- if( def==null )
+ if( def==null ) {
+ P.pos(old);
+ P._lexer.skipWhiteSpace();
throw P.error("Undefined name '" + name + "'");
+ }
if( def._extern!=0 ) // Parse the C binding as a memory lvalue
{ P.pos(old); return parseExpression(); }
- P.references.checkAssignment(def);
+ P.references.checkAssignment(def,site);
return P.references.assign(def,parseAssignment());
}
@@ -117,6 +122,8 @@ final class Expressions {
Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool
// Stacked compares?
+ P._lexer.skipWhiteSpace();
+ Lexer operator=P.loc();
int dir0 = parseCompDir();
if( dir0 == 0 ) {
rhs.unkill();
@@ -137,7 +144,7 @@ final class Expressions {
// compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc.
while( dir0 != 0 ) {
if( Math.abs(dir) != Math.abs(dir0) )
- throw P.error("Mixing relational directions in a chained relational test");
+ throw Parser.error("Mixing relational directions in a chained relational test",operator);
// True side parses next arm of test
P.ctrl(ifT.unkeep());
lhs = rhs; // Reuse the previous RHS exactly once
@@ -150,6 +157,8 @@ final class Expressions {
P.ctrl(ifF);
fail.mergeScopes(P._scope.dup(), P.loc()).peephole();
// Check next stacked compare
+ P._lexer.skipWhiteSpace();
+ operator=P.loc();
dir0 = parseCompDir();
}
rhs.unkill();
@@ -185,9 +194,11 @@ final class Expressions {
private Node parseShift() {
Node lhs = parseAddition();
while( true ) {
- if( P.match("<<") ) lhs = new ShlNode(P.loc(),lhs,null);
- else if( P.match(">>>")) lhs = new ShrNode(P.loc(),lhs,null);
- else if( P.match(">>") ) lhs = new SarNode(P.loc(),lhs,null);
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
+ if( P.match("<<") ) lhs = new ShlNode(site,lhs,null);
+ else if( P.match(">>>")) lhs = new ShrNode(site,lhs,null);
+ else if( P.match(">>") ) lhs = new SarNode(site,lhs,null);
else break;
lhs.setDef(2,parseAddition());
ParseException err;
@@ -251,12 +262,14 @@ final class Expressions {
int old = P.pos();
if( P.match("--") || P.match("++") ) {
int delta = P._lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
+ P._lexer.skipWhiteSpace();
int start = P.pos();
+ Lexer site=P.loc();
String name = P._lexer.matchId();
Var var = name==null ? null : P._scope.lookup(name);
if( var!=null && !(var.type() instanceof TypeMemPtr) ) {
if( var._extern==0 && var._final && !P._scope.canInit(var) )
- throw P.error("Cannot reassign final '"+var._name+"'");
+ throw Parser.error("Cannot reassign final '"+var._name+"'",site);
// Resolve fields just as a read or postfix update does. Updating
// their declaration's local slot would silently lose the Store.
P.pos(start);
@@ -286,7 +299,7 @@ final class Expressions {
// pre is +/-1 for a prefix update of a known name, zero otherwise.
private Node parsePrimary(int pre) {
if( P._lexer.isNumber(P._lexer.peek()) ) return parseLiteral();
- if( P._lexer.peek('"') ) return parsePostfix(P.allocations.newString(P._lexer.parseString()));
+ if( P._lexer.peek('"') ) return parsePostfix(P.strings.parse());
if( P.matchx("true" ) ) return P.con(1);
if( P.matchx("false") ) return P._code.ZERO;
if( P.matchx("null" ) ) return P.con(Type.NIL);
@@ -298,8 +311,10 @@ final class Expressions {
int pos = P.pos();
// Expect an identifier now
String id = P._lexer.matchId();
- if( id == null || Parser.KEYWORDS.contains(id) )
+ if( id == null || Parser.KEYWORDS.contains(id) ) {
+ P.pos(pos);
throw P.errorSyntax("an identifier or expression");
+ }
int pos2 = P.pos();
return P.references.parseName(id,pre,pos,pos2);
}
@@ -366,7 +381,11 @@ final class Expressions {
P._lexer.skipWhiteSpace();
Lexer site = P.loc();
String name;
- if( P.match(".") ) name = P.requireId();
+ if( P.match(".") ) {
+ P._lexer.skipWhiteSpace();
+ site=P.loc();
+ name = P.requireId(true);
+ }
else if( P.match("#") ) name = "#";
else if( P.match("[") ) name = "[]";
// Can get here without an explicit receiver: "f()" or
@@ -389,6 +408,6 @@ final class Expressions {
// Already parsed "'"
Node parseChar() {
- return P.require(P.con(TypeInteger.constant(P._lexer.parseChar())),"'");
+ return P.con(TypeInteger.constant(P._lexer.parseChar()));
}
}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Functions.java b/src/main/java/com/seaofnodes/simple/parser/Functions.java
index 14dee33b..5175acc6 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Functions.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Functions.java
@@ -73,14 +73,19 @@ final class Functions {
while( true ) {
Type t = P.types.parseType(); // Arg type
if( t==null ) break;
+ P._lexer.skipWhiteSpace();
+ Lexer qualifier=P.loc();
if( P.match("^") ) {
if( !(t instanceof TypeMemPtr ptr) || ptr._obj.isAry() )
- throw P.error("'^' requires a struct parameter");
+ throw Parser.error("'^' requires a struct parameter",qualifier);
t = ptr.withPartial(true);
P.construction._borrowed = true;
}
finals.set(ids.size(),P.declarations.bindingFinal(t));
+ P._lexer.skipWhiteSpace();
+ Lexer nameLoc=P.loc();
String id = P.requireId();
+ if( ids.find(id)!=-1 ) throw Parser.error("Duplicate parameter '"+id+"'",nameLoc);
ts .push(t ); // Push type/arg pairs
ids.push(id);
P.match(",");
@@ -98,7 +103,7 @@ final class Functions {
Lexer site = P.loc();
site._position--; // Opening '('; arguments can contain nested calls.
if( fcn._type == Type.NIL )
- throw P.error("Calling a null function pointer");
+ throw Parser.error("Calling a null function pointer",site);
fcn.keep(); // Keep while parsing args
Ary<Node> args = new Ary<>( Node.class );
@@ -207,6 +212,9 @@ final class Functions {
int oldUID = P._code.UID(); // Used to approximate function size
FunNode fun = enterFunction(funName,sig,loc,finals,ids);
+ // Guards belong to this function's control flow. Do not inherit an
+ // enclosing function's predicates or leak this body's guards on exit.
+ P._scope.addGuards(fun,null,false);
Construction.Frame outerConstruction = P.construction.enter(fun,isCtor && !fun.isClz());
Node rpc = fun.parm(0);
if( fun.isClz() ) P.structs.beginClassInit(fun,loc);
@@ -260,6 +268,7 @@ final class Functions {
P._ref.addFun(P._code,fun);
// Unstack parser state
+ P._scope.removeGuards(fun);
if( _returnScope != null ) _returnScope.kill();
_returnScope = returnScope;
P.statements._continueScope = continueScope;
diff --git a/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/src/main/java/com/seaofnodes/simple/parser/Lexer.java
index ec88738b..5e4b7311 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Lexer.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Lexer.java
@@ -3,10 +3,12 @@ package com.seaofnodes.simple.parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.codegen.SourceError;
import com.seaofnodes.simple.parser.Parser.ParseException;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.BAOS;
import java.util.HashMap;
+import java.io.ByteArrayOutputStream;
// Lexer provides low level access to the raw file bytes, peeks and matches
// short strings, parses numbers, skips comments and whitespace, tracks
@@ -35,6 +37,11 @@ public class Lexer {
public String position() { return _line_number+":"+(_position-_line_start+1); }
public int pos() { return _position; }
+ public String diagnostic(String message) {
+ return _cu==null ? SourceError.format("<input>",_input,_position,message)
+ : SourceError.format(_cu,_position,message);
+ }
+
/**
* Record the source text for lexing
*/
@@ -143,19 +150,21 @@ public class Lexer {
while( true ) {
if( isWhiteSpace() ) inc();
// Skip // to end of line
- else if( _position+2 < _input.length &&
+ else if( _position+1 < _input.length &&
_input[_position ] == '/' &&
_input[_position+1] == '/') {
inc(); inc();
while( !isEOF() && _input[_position] != '\n' )
inc();
- } else if( _position+2 < _input.length &&
+ } else if( _position+1 < _input.length &&
_input[_position ] == '/' &&
_input[_position+1] == '*') {
- // Skip /*comment*/
- while( !isEOF() && !(_input[_position-1] == '*' && _input[_position] == '/'))
+ Lexer start=new Lexer(this);
+ inc(); inc();
+ while( !isEOF() && !(peek()=='*' && _position+1<_input.length && peek(1)=='/') )
inc();
- inc();
+ if( isEOF() ) throw Parser.error("Unclosed block comment",start);
+ inc(); inc();
} else break;
}
}
@@ -229,20 +238,27 @@ public class Lexer {
// Return a constant Type, either TypeInteger or TypeFloat
Type parseNumber() {
+ Lexer start=new Lexer(this);
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 Parser.error("Syntax error: integer values cannot start with '0'",this);
- long i = Long.parseLong(new String(_input,old,len));
- return TypeInteger.constant(i);
+ String literal=new String(_input,old,n);
+ try {
+ if( f32 )
+ return TypeFloat.constant(Float.parseFloat(literal));
+ if( len > 0 ) {
+ if( len > 1 && _input[old]=='0' )
+ throw Parser.error("Syntax error: integer values cannot start with '0'",start);
+ long i = Long.parseLong(literal);
+ return TypeInteger.constant(i);
+ }
+ return TypeFloat.constant(Double.parseDouble(literal));
+ } catch( NumberFormatException ex ) {
+ throw Parser.error(len>0 && !f32 ? "Integer literal is outside the signed 64-bit range"
+ : "Invalid floating-point literal '"+literal+(f32 ? "f" : "")+"'",start);
}
- return TypeFloat.constant(Double.parseDouble(new String(_input,old,n)));
}
private String parseNumberString() {
int old = _position;
@@ -319,25 +335,77 @@ public class Lexer {
return 0;
}
- String parseString() {
- if( !peek('"') ) return null;
- inc();
- StringBuilder sb = new StringBuilder();
+ record StringPart(byte[] text, boolean end) {}
+
+ // Opening quote has been consumed. Leave a format's '%' unread so both
+ // the expression parser and deferred-body scanner use the same grammar.
+ StringPart stringPart(Lexer start) {
+ ByteArrayOutputStream sb = new ByteArrayOutputStream();
while( !isEOF() ) {
char c = nextChar();
- if( c=='"' ) return sb.toString();
+ if( c=='"' ) return new StringPart(sb.toByteArray(),true);
+ if( c=='%' ) {
+ if( peek()=='%' ) { inc(); sb.write('%'); continue; }
+ _position--;
+ return new StringPart(sb.toByteArray(),false);
+ }
if( c!='\\' ) {
- sb.append(c);
+ sb.write(c);
continue;
}
if( isEOF() )
- throw error("Unclosed string");
- sb.append(escapedChar("string"));
+ throw Parser.error("Unclosed string",start);
+ sb.write(escapedChar("string"));
}
- throw error("Unclosed string");
+ throw Parser.error("Unclosed string",start);
+ }
+
+ record Format(int kind, int flags, int width, int precision, Lexer site) {}
+
+ Format stringFormat() {
+ Lexer site = new Lexer(this);
+ assert peek()=='%';
+ inc();
+ if( peek()=='(' ) { inc(); return new Format(0,0,0,-1,site); }
+ int flags=0, bit;
+ while( (bit="-+ #0".indexOf(peek()))>=0 ) {
+ int mask=1<<bit;
+ if( (flags&mask)!=0 ) throw Parser.error("Repeated format flag",site);
+ flags |= mask;
+ inc();
+ }
+ int width = formatNumber(site);
+ int precision = -1;
+ if( peek()=='.' ) {
+ inc();
+ precision = formatNumber(site);
+ }
+ char kind=nextChar();
+ if( "diuoxXfFeEgGaAcsb".indexOf(kind)<0 )
+ throw Parser.error("Expected a string format: %(expression), %d(expression), or %%",site);
+ boolean number="diuoxXfFeEgGaA".indexOf(kind)>=0;
+ if( !number && (flags&~1)!=0 || "uoxX".indexOf(kind)>=0 && (flags&6)!=0 ||
+ "diu".indexOf(kind)>=0 && (flags&8)!=0 )
+ throw Parser.error("Invalid flags for %"+kind,site);
+ if( precision>=0 && (kind=='c' || kind=='b') )
+ throw Parser.error("Precision is not supported for %"+kind,site);
+ if( peek()!='(' ) throw Parser.error("Expected '(' after string format %"+kind,site);
+ inc();
+ return new Format(kind,flags,width,precision,site);
+ }
+
+ private int formatNumber(Lexer site) {
+ long n=0;
+ while( peek()>='0' && peek()<='9' ) {
+ n=n*10+nextChar()-'0';
+ if( n>Integer.MAX_VALUE ) throw Parser.error("String format width or precision is too large",site);
+ }
+ return (int)n;
}
private char escapedChar(String kind) {
+ Lexer start=new Lexer(this);
+ start._position--; // The backslash introducing the escape.
char esc = nextChar();
return switch( esc ) {
case '0' -> '\u0000';
@@ -347,17 +415,24 @@ public class Lexer {
case '\\' -> '\\';
case '"' -> '"';
case '\'' -> '\'';
- default -> throw error("Unknown "+kind+" escape \\\\"+esc);
+ case '%' -> '%';
+ default -> throw Parser.error("Unknown "+kind+" escape \\"+esc,start);
};
}
char parseChar() {
- if( isEOF() ) throw error("Unclosed character");
+ Lexer start=new Lexer(this);
+ start._position--; // Opening quote was consumed by the caller.
+ if( isEOF() || peek()=='\n' || peek()=='\r' ) throw Parser.error("Unclosed character literal",start);
+ if( peek()=='\'' ) throw Parser.error("Empty character literal",start);
char c = nextChar();
if( c=='\\' ) {
- if( isEOF() ) throw error("Unclosed character");
+ if( isEOF() ) throw Parser.error("Unclosed character literal",start);
c = escapedChar("character");
}
+ if( isEOF() || peek()=='\n' || peek()=='\r' ) throw Parser.error("Unclosed character literal",start);
+ if( peek()!='\'' ) throw Parser.error("Character literal must contain exactly one byte",start);
+ inc();
return c;
}
@@ -372,16 +447,18 @@ public class Lexer {
if( isEOF() ) throw error("Unclosed "+what);
char ch = peek();
if( ch==close && depth==0 ) return;
+ Lexer site=ch=='"' ? new Lexer(this) : null;
nextChar();
if( ch==open ) depth++;
else if( ch==close ) depth--;
- else if( ch=='"' || ch=='\'' ) {
- char quote = ch;
- while( !isEOF() ) {
- ch = nextChar();
- if( ch=='\\' ) nextChar();
- else if( ch==quote ) break;
+ else if( ch=='"' ) {
+ while( !stringPart(site).end() ) {
+ stringFormat();
+ skipTo('(',')',"string interpolation");
+ nextChar();
}
+ } else if( ch=='\'' ) {
+ parseChar();
}
}
}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Parser.java b/src/main/java/com/seaofnodes/simple/parser/Parser.java
index 4d140e55..0cf3c0e7 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -24,6 +24,7 @@ public class Parser {
final References references = new References(this);
final Conversions conversions = new Conversions(this);
final Construction construction = new Construction(this);
+ final Strings strings = new Strings(this);
public void checkConstructionSerializable() { construction.checkSerializable(); }
@@ -106,7 +107,7 @@ public class Parser {
put("i32" ,TypeInteger.I32);
put("i64" ,TypeInteger.BOT);
put("i8" ,TypeInteger.I8 );
- put("int" ,TypeInteger.BOT);
+ put("int" ,TypeInteger.I32);
put("u1" ,TypeInteger.U1 );
put("u16" ,TypeInteger.U16);
put("u32" ,TypeInteger.U32);
@@ -117,7 +118,8 @@ public class Parser {
public static class ParseException extends RuntimeException {
public final Lexer _loc;
- ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; }
+ ParseException( String msg, Lexer loc ) { super(msg); _loc = loc==null ? null : new Lexer(loc); }
+ public String diagnostic() { return _loc==null ? getMessage() : _loc.diagnostic(getMessage()); }
}
public Parser(CodeGen code ) {
@@ -186,9 +188,18 @@ public class Parser {
// Require and return an identifier
String requireId() {
+ return requireId(false);
+ }
+
+ // Primitive names are useful member names (sys.text.int), but remain
+ // reserved for local variables, parameters and type declarations.
+ String requireId(boolean member) {
+ _lexer.skipWhiteSpace();
+ Lexer site=loc();
String id = _lexer.matchId();
- if (id != null && !KEYWORDS.contains(id) ) return id.intern();
- throw error("Expected an identifier, found '"+id+"'");
+ if (id != null && (!KEYWORDS.contains(id) || member &&
+ (INIT_TYPES.get(id) instanceof TypeInteger || INIT_TYPES.get(id) instanceof TypeFloat)) ) return id.intern();
+ throw error("Expected an identifier, found '"+new Lexer(site).getAnyNextToken()+"'",site);
}
String matchId() {
@@ -266,7 +277,9 @@ public class Parser {
ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); }
private ParseException _errorSyntax(String msg) {
- return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`");
+ _lexer.skipWhiteSpace();
+ // Reading the token for the message must not advance the error location.
+ return error("Syntax error, "+msg+" but found `" + loc().getAnyNextToken()+"`");
}
ParseException error(String msg) { return error(msg,_lexer); }
public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
diff --git a/src/main/java/com/seaofnodes/simple/parser/README.md b/src/main/java/com/seaofnodes/simple/parser/README.md
index ac0d9f29..cee1d64a 100644
--- a/src/main/java/com/seaofnodes/simple/parser/README.md
+++ b/src/main/java/com/seaofnodes/simple/parser/README.md
@@ -2,7 +2,7 @@
The parser builds Sea-of-Nodes IR directly. Each component shares the cursor,
active scope, and compilation state through its `Parser P` reference; there is no intermediate
-AST. The [grammar map](../../../../../../../docs/chapter29/29-grammar.md) connects language
+AST. The [grammar map](../../../../../../../docs/chapter30/30-grammar.md) connects language
productions to their entry points.
| File | Responsibility |
diff --git a/src/main/java/com/seaofnodes/simple/parser/References.java b/src/main/java/com/seaofnodes/simple/parser/References.java
index 483bd7a8..6335bad2 100644
--- a/src/main/java/com/seaofnodes/simple/parser/References.java
+++ b/src/main/java/com/seaofnodes/simple/parser/References.java
@@ -15,13 +15,36 @@ final class References {
References(Parser P) { this.P = P; }
+ // Compiler-generated library references use ordinary module lookup and
+ // forward resolution, without a synthetic token stream.
+ Node classReference(String name, Lexer site) {
+ CompUnit cu=ParseAll.findCompUnit(P._code,P._ref,name);
+ if( cu==null ) throw Parser.error("Cannot find standard library '"+name+"'",site);
+ String clzName=TypeStruct.addClzPrefix(cu._cname);
+ TypeStruct clz=(TypeStruct)Parser.TYPES.computeIfAbsent(clzName,n -> TypeStruct.make(n,true));
+ FRefNode ref=new FRefNode(name,site,TypeMemPtr.make((byte)2,clz,true));
+ _frefs.push(ref);
+ return ref.peephole();
+ }
+
+ // Read a member without consuming a postfix from source text.
+ Node member(Node receiver, String name, Lexer site) {
+ Field fld=receiver._type instanceof TypeMemPtr ptr ? ptr._obj.field(name) : null;
+ return P.peep(new LoadNode(site,name,fld==null ? 1 : fld._alias,
+ P.ctrl(),P.mem(),receiver,P.fldoff(receiver,name)));
+ }
+
// Resolve a primary name, retaining its use site before speculative type lookup.
Node parseName(String id, int pre, int pos, int pos2) {
+ Lexer site=P.loc();
+ site._position=pos;
+ site.skipWhiteSpace();
// Attempt a local var lookup first
Var var = P._scope.lookup(id);
if( var==null ) {
// Not a local var, try again as a type name
P.pos(pos);
+ Lexer nameLoc = P.loc();
Type t = P.types.parseType();
if( t!=null ) {
// Static <clinit> typename lookup
@@ -52,7 +75,7 @@ final class References {
t = TypeMemPtr.make((byte)2,clz,true);
}
// Define the FRef
- var = P._scope.defineFRef(id,t,true,P.loc());
+ var = P._scope.defineFRef(id,t,true,nameLoc);
P.references._frefs.push((FRefNode)P._scope.in(var));
}
@@ -73,7 +96,7 @@ final class References {
if( kx < fx && !P.structs.inExplicitConstructor() && // 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 P.error("Variable '"+var._name+"' is out of function scope and must be a final constant");
+ throw Parser.error("Variable '"+var._name+"' is out of function scope and must be a final constant",site);
// var.idx==2 is self argument, 3 is args, 4 or more are now static globals
if( var._idx >= 4 ) {
@@ -98,7 +121,7 @@ final class References {
}
// Assign-update direct into Scope
if( var._final && !P._scope.canInit(var) )
- throw P.error("Cannot reassign final '"+var._name+"'");
+ throw Parser.error("Cannot reassign final '"+var._name+"'",site);
checkSharedFunction(var);
Node op = P.expressions.opAssign(ch,rvalue, var.type() );
P._scope.update(var,op);
@@ -106,14 +129,14 @@ final class References {
return pre==0 && Lexer.postfix(ch) ? rvalue : op;
}
- void checkAssignment(Var def) {
+ void checkAssignment(Var def, Lexer site) {
String name = def._name;
// A final field may receive its first value in its constructor.
if( P._scope.in(def._idx)._type!=Type.TOP && def._final &&
// Inside an allocation, final assign is OK, outside nope.
// The parseNew() call added the allocation scope
!P._scope.canInit(def) )
- throw P.error("Cannot reassign final '"+name+"'");
+ throw Parser.error("Cannot reassign final '"+name+"'",site);
checkSharedFunction(def);
}
@@ -201,7 +224,7 @@ final class References {
if( fld==null && ts!=null )
fld = P.types.fieldOrOpen(ts,name,false);
if( fld==null && ts!=null && !P.types.latestStruct(ts)._open )
- throw P.error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
+ throw Parser.error("Accessing unknown field '"+name+"' from '*"+ts._name+"'",site);
// We have to record "active touches" of a new class, to track when we
// need to force class initialization. Basically, we insert an CLInit,
@@ -233,7 +256,7 @@ final class References {
// Store to field
mem.keep();
if( !P.construction.own(expr) && expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
- throw P.error("Cannot modify final field '"+name+"'");
+ throw Parser.error("Cannot modify final field '"+name+"'",site);
Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), lift, P.construction.own(expr)).peephole().keep();
P.construction.store(expr,alias,st,mem);
st.unkeep();
@@ -250,17 +273,17 @@ final class References {
decl = conary.elem();
// Load field
- Node load = P.peep(new LoadNode(P.loc(),name,alias,P.ctrl(),mem,expr,off));
+ Node load = P.peep(new LoadNode(site,name,alias,P.ctrl(),mem,expr,off));
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
if( fld!=null && decl==Type.BOTTOM )
- throw P.error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
+ throw Parser.error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor",site );
Node op = P.expressions.opAssign(ch,load, decl ).keep();
mem.keep();
if( !P.construction.own(expr) && expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
- throw P.error("Cannot modify final field '"+name+"'");
+ throw Parser.error("Cannot modify final field '"+name+"'",site);
Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), op, P.construction.own(expr)).peephole().keep();
P.construction.store(expr,alias,st,mem);
st.unkeep();
@@ -302,7 +325,8 @@ final class References {
Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
for( FRefNode fref : _frefs ) {
if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
- CompUnit cu = ParseAll.findCompUnitOrThrow(P._code,P._ref,fref._name);
+ CompUnit cu = ParseAll.findCompUnit(P._code,P._ref,fref._name);
+ if( cu==null ) throw fref.err();
// Name lookup may already know this class's fields. Do not replace
// that information with an empty placeholder before Iter runs.
if( fref._con==FRefNode.FREF_TYPE )
diff --git a/src/main/java/com/seaofnodes/simple/parser/Statements.java b/src/main/java/com/seaofnodes/simple/parser/Statements.java
index 4ff4993b..707202ab 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Statements.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Statements.java
@@ -24,12 +24,14 @@ final class Statements {
* @return a {@link Node} or {@code null}
*/
Node parseStatement() {
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
if( P.matchx("return") ) return parseReturn();
else if (P.matchx("if") ) return parseIf();
else if (P.matchx("while") ) return parseWhile();
else if (P.matchx("for") ) return parseFor();
- else if (P.matchx("break") ) return parseBreak();
- else if (P.matchx("continue")) return parseContinue();
+ else if (P.matchx("break") ) return parseBreak(site);
+ else if (P.matchx("continue")) return parseContinue(site);
else if (P.matchx("struct") ) return P.structs.parseStruct();
else if (P.structs.inStructDeclaration() && P.matchx("new")) return P.structs.parseConstructorDeclaration();
else if (P.matchx(";") ) return P._code.ZERO; // Empty statement
@@ -204,7 +206,7 @@ final class Statements {
if( doFor ) {
int old = P.pos(nextPos);
if( !P.peek(')') )
- P.expressions.parseAssignment();
+ P.expressions.parseAssignment().isKill();
if( P.pos() != nextEnd )
throw P.errorSyntax( "Unexpected code after expression" );
P.pos(old);
@@ -237,8 +239,8 @@ final class Statements {
P._scope = exit;
return P._code.ZERO;
}
- private Node parseBreak () {
- checkLoopActive();
+ private Node parseBreak(Lexer site) {
+ checkLoopActive(site);
// 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.
@@ -248,15 +250,20 @@ final class Statements {
return P._code.ZERO;
}
- private Node parseContinue() { checkLoopActive(); _continueScope = P.require(jumpTo( _continueScope ),";"); return P._code.ZERO; }
+ private Node parseContinue(Lexer site) { checkLoopActive(site); _continueScope = P.require(jumpTo( _continueScope ),";"); return P._code.ZERO; }
private ScopeNode jumpTo(ScopeNode toScope) {
ScopeNode cur = P._scope.dup();
P.ctrl(P._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
+ // The clone shares lexical boundaries and Var records with the active
+ // parser scope. Promoting on the clone would move those boundaries
+ // without moving the active scope's bindings. Drop locals first, then
+ // copy forward references into the jump scope with their own indices.
while( cur.depth() > _breakScope.depth() )
- cur.pop();
+ cur._pop();
+ cur.balanceLoopFRefs(P._scope);
// 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"
@@ -264,9 +271,13 @@ final class Statements {
return cur;
// toScope is either the break scope, or a scope that was created here
assert toScope.depth() <= _breakScope.depth();
+ // A later path can discover forward references after an earlier
+ // break/continue saved its scope. Keep those bindings across the merge.
+ toScope.balanceLoopFRefs(cur);
+ cur.balanceLoopFRefs(toScope);
toScope.ctrl(toScope.mergeScopes(cur,P.loc()).peephole());
return toScope;
}
- private void checkLoopActive() { if (_breakScope == null) throw P.error("No active loop for a break or continue"); }
+ private void checkLoopActive(Lexer site) { if (_breakScope == null) throw Parser.error("No active loop for a break or continue",site); }
}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Strings.java b/src/main/java/com/seaofnodes/simple/parser/Strings.java
new file mode 100644
index 00000000..5bf76585
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/parser/Strings.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+/** Interpolation expands to ordinary sys.text calls. The library itself uses
+ * no interpolation, so it can be compiled from source without staging. */
+final class Strings {
+ private final Parser P;
+ Strings(Parser p) { P=p; }
+
+ Node parse() {
+ Lexer start=P.loc();
+ P.require("\"");
+ Lexer.StringPart part=P._lexer.stringPart(start);
+ if( part.end() ) return P.allocations.newString(part.text());
+ Node text=P.references.classReference("sys.text",P.loc()).keep();
+ P.classInit(P.loc(),text);
+ Node builder=call(text,"begin",P.loc()).keep();
+ while( true ) {
+ if( part.text().length!=0 )
+ call(text,"string",P.loc(),builder,P.allocations.newString(part.text()),P.con('s'),P.con(0),P.con(-1)).isKill();
+ if( part.end() ) break;
+ Lexer.Format fmt=P._lexer.stringFormat();
+ Node value=P.expressions.parseAssignment().keep();
+ P.require(")");
+ Node fptr;
+ if( fmt.kind()==0 ) {
+ Node ints=P.references.member(text,"integer",fmt.site()).keep();
+ Node floats=P.references.member(text,"floating",fmt.site()).keep();
+ Node strings=P.references.member(text,"string",fmt.site()).keep();
+ fptr=new FormatNode(value,ints.unkeep(),floats.unkeep(),strings.unkeep(),fmt.site()).peephole();
+ } else {
+ String family="diuoxXcb".indexOf(fmt.kind())>=0 ? "integer" : fmt.kind()=='s' ? "string" : "floating";
+ fptr=P.references.member(text,family,fmt.site());
+ }
+ call(text,fptr,fmt.site(),builder,value.unkeep(),P.con(fmt.kind() | fmt.flags()<<8),
+ P.con(fmt.width()),P.con(fmt.precision())).isKill();
+ part=P._lexer.stringPart(start);
+ }
+ // String syntax promises this result type even when sys.text is still
+ // awaiting its source parse. Check/convert through the normal IR.
+ Node result=call(text.unkeep(),"end",P.loc(),builder.unkeep());
+ return P.conversions.convertExpr(result,TypeMemPtr.make(P.types.typeAry(TypeInteger.U8,true)).makeRO());
+ }
+
+ private Node call(Node text, String name, Lexer site, Node... values) {
+ return call(text,P.references.member(text,name,site),site,values);
+ }
+
+ private Node call(Node text, Node fptr, Lexer site, Node... values) {
+ Ary<Node> args=new Ary<>(Node.class);
+ args.push(P.ctrl()); args.push(P.mem()); args.push(text);
+ for( Node value:values ) args.push(value);
+ args.push(fptr);
+ return P.functions.functionCall(args,site);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/parser/Structs.java b/src/main/java/com/seaofnodes/simple/parser/Structs.java
index a5262440..3a3d894c 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Structs.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Structs.java
@@ -78,12 +78,14 @@ final class Structs {
Node parseConstructorDeclaration() {
TypeStruct self = P._scope.constructorSelf();
assert self != null;
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
String name = P.requireId();
String expect = initFieldName(self._name);
if( name != expect )
- throw P.error("Constructor name '" + name + "' must match type name '" + expect + "'");
+ throw Parser.error("Constructor name '" + name + "' must match type name '" + expect + "'",site);
P.require("=");
- if( _pendingCtor!=null ) throw P.error("Redefining constructor '"+name+"'");
+ if( _pendingCtor!=null ) throw Parser.error("Redefining constructor '"+name+"'",site);
_pendingCtor = new Constructor(name,P.loc());
// Parse the body after all field declarations are known, so self.x
// and x resolve the same field even when x is declared below this body.
diff --git a/src/main/java/com/seaofnodes/simple/parser/Types.java b/src/main/java/com/seaofnodes/simple/parser/Types.java
index ffd3a0f4..827518fd 100644
--- a/src/main/java/com/seaofnodes/simple/parser/Types.java
+++ b/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -54,12 +54,14 @@ final class Types {
// Nest arrays and '?' as needed
Type t2 = t1;
while( true ) {
+ P._lexer.skipWhiteSpace();
+ Lexer site=P.loc();
if( P.match("?") ) {
if( t2 instanceof TypeMemPtr tmp ) {
- if( tmp.nullable() ) throw P.error("Type "+t2+" already allows null");
+ if( tmp.nullable() ) throw Parser.error("Type "+t2+" already allows null",site);
t2 = tmp.makeNullable();
} else
- throw P.error("Type "+t0+" cannot be null");
+ throw Parser.error("Type "+t0+" cannot be null",site);
} else if( P.match("[~]") ) {
t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
} else if( P.match("[]") ) {
diff --git a/src/main/java/com/seaofnodes/simple/parser/package-info.java b/src/main/java/com/seaofnodes/simple/parser/package-info.java
index 7212e629..c886f72a 100644
--- a/src/main/java/com/seaofnodes/simple/parser/package-info.java
+++ b/src/main/java/com/seaofnodes/simple/parser/package-info.java
@@ -7,7 +7,7 @@
* use-side name/field resolution; Allocations, ControlFlow, and Conversions
* contain their shared IR construction. Lexer also supplies location snapshots.
*
- * <p>See {@code chapter29/docs/29-grammar.md} for the production-to-method map.
+ * <p>See {@code chapter30/docs/30-grammar.md} for the production-to-method map.
* Evaluation order and scope lifetimes are explicit: loop updates and declared
* constructor bodies are the two deferred source fragments. There is no AST.
*/
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
index c9093e3b..9cb23e09 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -16,6 +16,7 @@ public abstract class TypeConAry<A> extends TypeScalar {
TypeConAry( boolean any, byte widen, A ary ) { super(TCONARY); _any = any; _widen = widen; _ary = ary; }
public static void gather(ArrayList<Type> ts) {
+ ts.add(TypeConAryB.make(""));
ts.add(TypeConAryB.ABC);
ts.add(TypeConAryB.ABCD);
ts.add(TypeConAryI.I123);
@@ -36,6 +37,15 @@ public abstract class TypeConAry<A> extends TypeScalar {
}
Type ymeet( TypeInteger ti ) {
+ // Empty arrays have no observed elements: use the storage bounds.
+ // Check both endpoints just as the loop checks a nonempty body;
+ // retaining high integer inputs here preserves lattice duality.
+ if( len()==0 ) {
+ TypeInteger bound=emptyElem();
+ if( !ti.isa(TypeInteger.make(bound._min,bound._min,ti._widen)) ||
+ !ti.isa(TypeInteger.make(bound._max,bound._max,ti._widen)) )
+ return ti.meet(bound);
+ }
// if i can isa *each* element, then maybe can keep.
// no good to i.isa(elem()) because fails the dual
for( int j=0; j<len(); j++ )
@@ -64,6 +74,7 @@ public abstract class TypeConAry<A> extends TypeScalar {
return _any ? Type.TOP : BOTTOM;
int len = len();
long min = Long.MAX_VALUE;
+ if( len==0 ) return emptyElem();
long max = Long.MIN_VALUE;
for( int i=0; i<len; i++ ) {
min = Math.min(min,at8(i));
@@ -71,6 +82,10 @@ public abstract class TypeConAry<A> extends TypeScalar {
}
return TypeInteger.make(min,max,_widen);
}
+ private TypeInteger emptyElem() {
+ return log_size()==0 ? TypeInteger.make(0,255,_widen)
+ : TypeInteger.make(Integer.MIN_VALUE,Integer.MAX_VALUE,_widen);
+ }
public abstract long at8(int idx);
public abstract int len();
@Override public abstract int log_size();
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
index 5877c309..84be5fb2 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -10,7 +10,7 @@ public class TypeConAryB extends TypeConAry<byte[]> {
public static TypeConAryB make( String s ) { return make(s.getBytes()); }
static final TypeConAryB ABC = make("abc");
static final TypeConAryB ABCD = make("abcd");
- @Override public long at8(int idx) { return _ary[idx]; }
+ @Override public long at8(int idx) { return _ary[idx]&255; }
@Override public int len() { return _ary.length; }
@Override public int log_size() { return 0; }
@Override public String str() { return "[\""+new String(_ary)+"\"]"; }
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
index a0c2266d..6164f721 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -53,7 +53,15 @@ public class TypeInteger extends TypeScalar {
}
public Type same_but_slightly_wider_than( ) {
- return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT;
+ if( _widen < 3 ) return make(_min,_max, (byte)(_widen+1));
+ // Start in the intersection of i32 and u32 for nonnegative ranges.
+ // Widening those straight to i32 invents negative values that prevent
+ // a u32 loop from remaining unsigned. Either 32-bit range can then
+ // grow to its own bound; unbounded arithmetic ultimately reaches BOT.
+ if( _min >= 0 && _max <= I32._max ) return make(0,I32._max,(byte)3);
+ if( _min >= I32._min && _max <= I32._max ) return I32;
+ if( _min >= 0 && _max <= U32._max ) return U32;
+ return BOT;
}
// Force max widen
@Override public Type widen() {