diff options
| author | Cliff Click <cliffc@acm.org> | 2026-11-13 16:17:13 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-11-13 16:17:13 -0800 |
| commit | afcf96444366fb4b7c83f938763013cbc360aade (patch) | |
| tree | 67eb69ac6cc58322549020550d48aceb8f2f5ff8 /chapter31 | |
| parent | d2c59dbe9d6cc8dc4ff4d1e7949bfeddd8bef5d0 (diff) | |
| download | Simple-afcf96444366fb4b7c83f938763013cbc360aade.tar.gz Simple-afcf96444366fb4b7c83f938763013cbc360aade.zip | |
Array init syntaxmain
New array init syntax; used in a bunch of places.
Allows e.g. read-only arrays with not-null entries.
Fix bug x86 encoding of mem/cmp/fp compares
Diffstat (limited to 'chapter31')
24 files changed, 589 insertions, 109 deletions
diff --git a/chapter31/README.md b/chapter31/README.md index b82d9612..356b9cdb 100644 --- a/chapter31/README.md +++ b/chapter31/README.md @@ -24,6 +24,31 @@ animation and the reusable `sys.draw.Button` widget: `make -C chapter31 run-clock CTAGS=`. Click its button or press Space to switch between green seven-segment digits and an analog dial. +## Array initializers + +Arrays can be filled with a list or an index-based initializer: + +```simple +val glyphs = new u16[~]{075557,026227,061247}; // Read-only, inferred length. +val squares = new int[10]{int i -> i*i;}; +val names = new u8[~][~2]{"Alice","Bob"}; // Read-only, non-null strings. +``` + +An explicit list length must be a constant equal to the number of elements. +A generator evaluates its length once and runs in index order, once per element +(zero times for an empty array). Its body supports statements and `return`, or +uses its last expression as the result. It can read surrounding locals but +cannot reassign them. Use `int`, `i64`, or `u32` for the index. + +`[~n]` (or `[~]` with a list) makes the array read-only; `val` alone only fixes +the binding. Initializers can construct arrays of non-null references because +every element is supplied before the array becomes accessible. Read-only access +propagates to referenced elements, following the existing view rules. Initialization runs at runtime; +read-only does not require compile-time evaluation. Canvas's font now uses a +read-only `u16` table, and `sys.text.end` uses a generator for its byte copy. + +## Running the examples + ```sh make -C chapter31 libs CTAGS= # Once while online (also: init or lib). make -C chapter31 run-graphical-tictactoe CTAGS= diff --git a/chapter31/docs/31-grammar.md b/chapter31/docs/31-grammar.md index 3083fcc2..f4ca76f1 100644 --- a/chapter31/docs/31-grammar.md +++ b/chapter31/docs/31-grammar.md @@ -185,7 +185,14 @@ argumentList : assignment (',' assignment)* ','? ; // Allocation invokes the declared constructor; inline initializer blocks from // earlier chapters are replaced by constructor arguments. -newExpression : 'new' type ('[' assignment ']' | callSuffix)? ; +newExpression + : 'new' type ('[' '~'? assignment ']' arrayInitializer? | arrayInitializer | callSuffix)? + ; +// With no explicit length, type must end in [] or [~] and the initializer is a list. +arrayInitializer + : '{' (assignment (',' assignment)* ','?)? '}' + | '{' ('int' | 'i32' | 'i64' | 'u32') IDENTIFIER '->' statement* '}' + ; INTEGER_LITERAL : [1-9] DIGIT* | '0' [0-7]* | '0' [xX] HEX_DIGIT+ ; // The f suffix also accepts an integer-shaped token, e.g. 12f. @@ -231,7 +238,7 @@ there is no inheritance chain, AST, or additional compiler pass. | `assignment` through `primaryExpression` | [`Expressions`](../src/main/java/com/seaofnodes/simple/parser/Expressions.java): `parseAssignment`, `parseExpression`, `parseLogical`, `parseBitwise`, `parseEquality`, `parseComparison`, `parseShift`, `parseAddition`, `parseMultiplication`, `parseUnary`, `parsePrimary` | | `postfixExpression`, member selection and updates | `Expressions.parsePostfix` / `parsePostfixMethod` dispatch to [`References.parseField`](../src/main/java/com/seaofnodes/simple/parser/References.java); `References.parseName` handles bare names, implicit receivers, and forward references | | `functionExpression`, `parameterList`, calls, `argumentList` | [`Functions`](../src/main/java/com/seaofnodes/simple/parser/Functions.java): `parseFunction`, `parseConstructor`, shared `parameters` and `arguments`, then `parseBody` | -| `newExpression` | [`Allocations.parseNew`](../src/main/java/com/seaofnodes/simple/parser/Allocations.java); the allocation-helper graph builder follows the expression routines | +| `newExpression`, `arrayInitializer` | [`Allocations.parseNew`](../src/main/java/com/seaofnodes/simple/parser/Allocations.java), [`ArrayInitializers`](../src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java); private initialization, list checks, and generator loops | | `stringExpression`, `interpolation` | [`Strings.parse`](../src/main/java/com/seaofnodes/simple/parser/Strings.java), using `Lexer.stringPart` / `stringFormat`; normal expression parsing resumes inside each interpolation | | Literals, identifiers, comments, token matching | [`Lexer`](../src/main/java/com/seaofnodes/simple/parser/Lexer.java); it also supplies source-location snapshots used by diagnostics and serialized IR | diff --git a/chapter31/docs/examples/Dijkstra.smp b/chapter31/docs/examples/Dijkstra.smp index 768bdd08..a8fcf3da 100644 --- a/chapter31/docs/examples/Dijkstra.smp +++ b/chapter31/docs/examples/Dijkstra.smp @@ -3,13 +3,10 @@ // https://sampleprograms.io/projects/dijkstra/ val shortestPath = { !sys.aryi64 matrix, int nodes, int src, int dst -> - i64 infinity = (1 << 63)-1; - var dist = new i64[nodes]; + i64 infinity = sys.integer.MAX_I64; var visited = new sys.adt.bitset(); - // All nodes are infinitely far from each other - for( int i=0; i<nodes; i++ ) dist[i] = infinity; - // Starting point is zero far from starting point - dist[src] = 0; + // All nodes are infinitely far from each other, except the starting point + var dist = new i64[nodes]{ int i -> i==src ? 0 : infinity; }; i64 result = -1; int count = 0; diff --git a/chapter31/docs/examples/TicTacToe.smp b/chapter31/docs/examples/TicTacToe.smp index a710e440..1e0f3ca0 100644 --- a/chapter31/docs/examples/TicTacToe.smp +++ b/chapter31/docs/examples/TicTacToe.smp @@ -3,8 +3,7 @@ val main = { u8[~][] args -> sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O. You go first.\n"); sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n"); sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n"); - val board = new u8[9]; - for( int i=0; i<board#; i++ ) board[i]=' '; + val board = new u8[9]{ int i -> ' '; }; // Initialize the board // Draw empty board _draw(board); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java index a86cd18a..418edaf6 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java @@ -21,12 +21,14 @@ abstract class TypeCheck { Ary<Node> errs = new Ary<>(Node.class); Ary<ConstructNode> constructs = new Ary<>(ConstructNode.class); Ary<InitViewNode> views = new Ary<>(InitViewNode.class); + Ary<ArrayInitNode> arrays = new Ary<>(ArrayInitNode.class); code._stop.walk( n -> { if( n.err() != null ) errs.add(n); if( n instanceof CLInitNode ) code.add(n); if( n instanceof ConstructNode con ) constructs.add(con); if( n instanceof InitViewNode view ) views.add(view); + if( n instanceof ArrayInitNode array ) arrays.add(array); return null; }); @@ -57,13 +59,15 @@ abstract class TypeCheck { if( !con.isDead() ) con.subsume(con.in(1)); for( InitViewNode view : views ) if( !view.isDead() ) view.subsume(view.in(1)); + for( ArrayInitNode array : arrays ) + if( !array.isDead() ) array.subsume(array.in(2)); } private static int errorRank(Node n) { // Bad memory access originates the error. Missing construction fields // in turn explain generic-scalar arithmetic further downstream. if( n instanceof MemOpNode && noBottomInputs(n) ) return 0; - if( n instanceof ConstructNode ) return 1; + if( n instanceof ConstructNode || n instanceof ArrayInitNode ) return 1; return noBottomInputs(n) ? 2 : 3; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java b/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java new file mode 100644 index 00000000..30f86ac6 --- /dev/null +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/ArrayInitNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.parser.Lexer; +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; + +/** Source checks for array construction and its yielded elements. Erased by + * TypeCheck before serialization and scheduling. */ +public final class ArrayInitNode extends Node { + private final int _count; // List size, -1 for a generator, -2 for a yielded element. + private final Lexer _loc; + public ArrayInitNode(Node ctrl, Node mem, Node ptr, Node len, int count, Lexer loc, Ary<Node> values) { + super(ctrl,mem,ptr,len); + _count=count; _loc=loc; + for( Node value : values ) addDef(value); + } + public ArrayInitNode(Node ctrl, Node value, Lexer loc) { + super(ctrl,null,value); + _count=-2; _loc=loc; + } + @Override public String label() { return "ArrayInit"; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("arrayInit(").n(in(2)).p(")"); } + @Override public Type compute() { return in(0)._type==Type.CONTROL ? in(2)._type : Type.TOP; } + @Override public Node idealize() { return null; } + @Override public Parser.ParseException err() { + if( in(0)._type==Type.XCONTROL ) return null; + if( _count==-2 ) + return TypeMemPtr.mayBePartial(in(2)._type) + ? Parser.error("Array initializer requires fully initialized elements",_loc) : null; + if( !(in(3)._type instanceof TypeInteger len) ) + return Parser.error("Array length must be an integer",_loc); + if( len.isConstant() && (len.value()<0 || len.value()>TypeInteger.U32._max) ) + return Parser.error("Array length is outside the u32 range",_loc); + if( _count>=0 && (!len.isConstant() || len.value()!=_count) ) + return Parser.error("Array initializer has "+_count+" elements; length must equal that constant",_loc); + for( int i=4; i<nIns(); i++ ) + if( TypeMemPtr.mayBePartial(in(i)._type) ) + return Parser.error("Array initializer requires fully initialized elements",_loc); + return null; + } + @Override public boolean eq(Node n) { return this==n; } +} diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index fcca7f8f..b0ecd685 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -67,9 +67,9 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { if( i==1 ) return null; // Memory if( i==2 ) return x86_64_v2.RMASK; // base in GPR if( i==3 ) return x86_64_v2.RMASK; // index in GPR - if( i==4 ) return _sz >= '4' - ? x86_64_v2.MEM_MASK // value in GPR or XMM - : x86_64_v2.RMASK; // Bytes and shorts in GPR only + // Integer arithmetic and comparisons require a GPR value operand. + // Only StoreX86 has alternate encodings that accept XMM values. + if( i==4 ) return x86_64_v2.RMASK; return null; // Anti-dependence } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 9ffa25fc..8dfd31e7 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -25,6 +25,9 @@ public class StoreX86 extends MemOpX86 { } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } + @Override public RegMask regmap(int i) { + return i==4 && _sz>='4' ? x86_64_v2.MEM_MASK : super.regmap(i); + } @Override public void encoding( Encoding enc ) { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java index 861f95b6..5c6b5765 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Allocations.java @@ -25,11 +25,20 @@ final class Allocations { } // Parse ary[ length_expr ] if( P.match("[") ) { - if( !t.makeZero().isa(t) ) - throw Parser.error("Cannot allocate a non-nullable, since arrays are always zero/null filled",site); + boolean readonly = P.match("~"); + if( P.match("]") ) { + P.require("{"); + return P.arrays.parse(t,readonly,null,site); + } Node len = P.expressions.parseAssignment().keep(); P.require("]"); + if( P.match("{") ) return P.arrays.parse(t,readonly,len,site); + if( !t.makeZero().isa(t) ) + throw Parser.error("Cannot allocate a non-nullable, since arrays are always zero/null filled",site); + if( readonly ) return new ReadOnlyNode(allocArray(P.types.typeAry(t,true),len)).peephole(); return allocArray(P.types.typeAry(t,false),len); + } else if( t instanceof TypeMemPtr ary && ary._obj.isAry() && P.match("{") ) { + return P.arrays.parse(ary._obj.field("[]")._t,ary._ro,null,site); } else { if( !(t instanceof TypeMemPtr tmp) ) throw Parser.error("Cannot allocate a "+t.str(),site); @@ -101,8 +110,6 @@ final class Allocations { // TODO: Needs runtime check. Node st = P.peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, ConFldOffNode.make(ts,"#"), len.unkeep(), true)); - // TODO: Allow add-on "constructor" to init the array elements - Node prior = P.mem(); Node esc = P.peep(new EscapeNode(lenFld,self,st,prior)); P.mem(P.mergeAlias(prior,lenFld._alias,esc)); diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java new file mode 100644 index 00000000..70eeae1f --- /dev/null +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/ArrayInitializers.java @@ -0,0 +1,185 @@ +package com.seaofnodes.simple.parser; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.ScopeNode.Kind; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; + +/** Array construction keeps the destination private until all elements exist. + * A generator is a scoped, immediately executed loop body, not an escaping closure. */ +final class ArrayInitializers { + private final Parser P; + Frame _frame; + + static final class Frame { + final Kind.Block _kind; + final int _depth; + final Var _value; + ScopeNode _returns; + Frame(Kind.Block kind, int depth, Var value) { + _kind=kind; _depth=depth; _value=value; + } + } + + ArrayInitializers(Parser P) { this.P=P; } + + // Opening brace consumed; an absent length means a list with inferred size. + Node parse(Type elem, boolean readonly, Node len, Lexer site) { + int pos=P.pos(); + Type indexType=P.types.parseType(); + String index=indexType==null ? null : P._lexer.matchId(); + boolean generator=index!=null && P.match("->"); + if( !generator ) P.pos(pos); + if( generator && Parser.KEYWORDS.contains(index) ) + throw Parser.error("Expected an array index name",site); + if( generator && (indexType!=TypeInteger.BOT && indexType!=TypeInteger.I32 && indexType!=TypeInteger.U32) ) + throw Parser.error("Array generator index must be int, i64, or u32",site); + if( generator && len==null ) + throw Parser.error("Array generator requires a length",site); + + Ary<Node> values=new Ary<>(Node.class); + if( !generator ) { + if( !P.peek('}') ) do { + values.add(P.conversions.convertExpr(P.expressions.parseAssignment(),elem).keep()); + } while( P.match(",") && !P.peek('}') ); + P.require("}"); + if( len==null ) len=P.con(values.size()).keep(); + } + + TypeStruct ts=P.types.typeAry(elem,readonly); + Field body=ts.field("[]"), length=ts.field("#"); + Node size=new AddNode(ConFldOffNode.make(ts,"[]"), + new ShlNode(null,len,P.con(ts.aryScale())).peephole()).peephole(); + NewNode alloc=new NewNode(ts,P.ctrl(),size).init(); + Node self=new ProjNode(alloc,0,ts.str()).init().keep(); + Node mem=new ProjNode(alloc,1,"#arrayMem").init().keep(); + Node slen=new StoreNode(site,"#",length._alias,TypeInteger.U32,null,mem,self, + ConFldOffNode.make(ts,"#"),len,true).peephole().keep(); + + if( generator ) { + mem=generator(ts,self,mem,len,index,indexType,site); + } else { + for( int i=0; i<values.size(); i++ ) { + Node st=store(ts,self,mem,P.con(i),values.at(i),site).keep(); + mem.unkeep(); + mem=st; + } + } + + Node prior=P.mem(); + P.mem(P.mergeAlias(prior,length._alias,new EscapeNode(length,self,slen.unkeep(),prior).peephole())); + prior=P.mem(); + P.mem(P.mergeAlias(prior,body._alias,new EscapeNode(body,self,mem,prior).peephole())); + Node result=new ArrayInitNode(P.ctrl(),mem.unkeep(),self.unkeep(),len.unkeep(), + generator ? -1 : values.size(),site,values).peephole(); + for( Node value : values ) value.unkeep(); + return readonly ? new ReadOnlyNode(result).peephole() : result; + } + + private Node store(TypeStruct ts, Node self, Node mem, Node idx, Node value, Lexer site) { + Field fld=ts.field("[]"); + Node off=new AddNode(ConFldOffNode.make(ts,"[]"), + new ShlNode(null,idx,P.con(ts.aryScale())).peephole()).peephole(); + return new StoreNode(site,"[]",fld._alias,fld._t,P.ctrl(),mem,self,off,value,true).peephole(); + } + + private Var define(String name, Type type, boolean fin, Node value, Lexer site) { + if( !P._scope.define(name,type,fin,value,site) ) throw Parser.error("Duplicate name '"+name+"'",site); + return P._scope.lookup(name); + } + + private Node read(Var var) { return P._scope.in(P._scope.update(var,null)); } + + private Node generator(TypeStruct ts, Node self, Node mem, Node len, String name, + Type indexType, Lexer site) { + Frame outer=_frame; + ScopeNode oldBreak=P.statements._breakScope, oldContinue=P.statements._continueScope; + P.statements._breakScope=P.statements._continueScope=null; + Kind.Block kind=new Kind.Block(); + P._scope.push(kind); + // The counter stays wide even when the source index is a narrow int. + // The private memory and result slots cannot be named in source. + Var counter=define("$arrayIndex",TypeInteger.BOT,false,P.con(0),site); + Var memory=define("$arrayMem",TypeMem.makePrivate(Type.BOTTOM),false,mem.unkeep(),site); + Var result=define("$arrayValue",ts.field("[]")._t,false,P.con(Type.TOP),site); + _frame=new Frame(kind,P._scope.depth(),result); + + P.ctrl(new LoopNode(site,null,P.ctrl()).peephole()); + ScopeNode head=P._scope.keep(); + P._xScopes.push(P._scope=P._scope.dup(true)); + Node idx=read(counter).keep(); + Node pred=new BoolNode.LT(idx,len).peephole().keep(); + Node iff=new IfNode(P.ctrl(),pred).peephole().keep(); + CFGNode yes=new CProjNode(iff,0,"True").peephole().keep(); + CFGNode no=(CFGNode)new CProjNode(iff.unkeep(),1,"False").peephole(); + P.ctrl(no); + ScopeNode exit=P._scope.dup(); + P._xScopes.push(exit); + exit.addGuards(no,pred,true); + P.ctrl(yes.unkeep()); + P._scope.addGuards(yes,pred.unkeep(),false); + + P._scope.push(new Kind.Block()); + define(name,indexType,true,new ConvertNode(indexType,idx).peephole(),site); + Node last=P._code.ZERO; + while( !P.peek('}') && !P._lexer.isEOF() ) { + P._code.add(last); + last=P.statements.parseStatement(); + } + P.require("}"); + if( P.ctrl()._type!=Type.XCONTROL ) addReturn(last); + P._scope.pop(); + if( _frame._returns!=null ) { + P._scope.kill(); + P._scope=_frame._returns; + } + Node value=read(result); + // The loop starts with the whole private array memory. Keep that shape + // on the backedge too: mixing it with an element slice yields BOTTOM, + // which would falsely publish every function when the array escapes. + Node prior=read(memory).keep(); + Node st=store(ts,self,prior,idx,value,site); + P._scope.update(memory,P.mergeAlias(prior.unkeep(),ts.field("[]")._alias,st)); + P._scope.update(counter,new AddNode(idx.unkeep(),P.con(1)).peephole()); + P._scope.removeGuards(yes); + head.balanceLoopFRefs(P._scope); + exit.balanceLoopFRefs(P._scope); + head.endLoop(P._scope,exit); + head.unkeep().kill(); + P._xScopes.pop(); + P._xScopes.pop(); + if( !P._xScopes.isEmpty() ) { P._xScopes.pop(); P._xScopes.push(exit); } + P._scope=exit; + Node done=read(memory).keep(); + P._scope.pop(); + _frame=outer; + P.statements._breakScope=oldBreak; + P.statements._continueScope=oldContinue; + return done; + } + + Node addReturn(Node expr) { + Node value=P.conversions.convertExpr(expr,_frame._value.type()); + value=new ArrayInitNode(P.ctrl(),value,P.loc()).peephole().keep(); + P.mem(); // Materialize lazy memory before cloning a return path. + ScopeNode cur=P._scope.dup(); + // Saved paths share Var records with the active scope. Drop locals + // without promoting them, then copy forward references explicitly. + while( cur.depth()>_frame._depth ) cur._pop(); + cur.balanceLoopFRefs(P._scope); + cur.update(_frame._value,value.unkeep()); + if( _frame._returns==null ) _frame._returns=cur; + else { + _frame._returns.balanceLoopFRefs(cur); + cur.balanceLoopFRefs(_frame._returns); + _frame._returns.ctrl(_frame._returns.mergeScopes(cur,P.loc()).peephole()); + } + P.ctrl(P._code.XCTRL); + return P._code.ZERO; + } + + void checkAssignment(Var var, Lexer site) { + if( _frame!=null && var._idx<_frame._kind._lexSize ) + throw Parser.error("Cannot assign captured local '"+var._name+"' in an array generator",site); + } +} diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java index 5175acc6..8047fabc 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Functions.java @@ -198,6 +198,8 @@ final class Functions { } ReturnNode parseBody( String funName, TypeFunPtr sig, Lexer loc, BitSet finals, String[] ids) { + ArrayInitializers.Frame outerArray = P.arrays._frame; + P.arrays._frame = null; Structs.Constructor outerCtor = P.structs._pendingCtor; P.structs._pendingCtor = null; boolean isInit = FunNode.isInit(funName); @@ -275,6 +277,7 @@ final class Functions { P.statements._breakScope = breakScope; P._scope.pop(); P.construction._frame = outerConstruction; + P.arrays._frame = outerArray; return ret; } diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java index 0cf3c0e7..3e7bb6b8 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -21,6 +21,7 @@ public class Parser { final Functions functions = new Functions(this); final Structs structs = new Structs(this); final Allocations allocations = new Allocations(this); + final ArrayInitializers arrays = new ArrayInitializers(this); final References references = new References(this); final Conversions conversions = new Conversions(this); final Construction construction = new Construction(this); @@ -120,6 +121,11 @@ public class Parser { public final Lexer _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()); } + // JUnit and uncaught-exception reporters use these entry points rather + // than the CLI's diagnostic(). Source errors should retain their source + // excerpt and caret there too. getMessage() stays stable for callers. + @Override public void printStackTrace(java.io.PrintStream out) { out.println(diagnostic()); } + @Override public void printStackTrace(java.io.PrintWriter out) { out.println(diagnostic()); } } public Parser(CodeGen code ) { diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java index 6335bad2..f170cae2 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/References.java @@ -120,6 +120,7 @@ final class References { return P.expressions.parsePostfixMethod(P.construction.view(rvalue),P._scope.in(P._scope.update(P._scope.var(selfx),null))); } // Assign-update direct into Scope + P.arrays.checkAssignment(var,site); if( var._final && !P._scope.canInit(var) ) throw Parser.error("Cannot reassign final '"+var._name+"'",site); checkSharedFunction(var); @@ -130,6 +131,7 @@ final class References { } void checkAssignment(Var def, Lexer site) { + if( fieldSelf(def)==null ) P.arrays.checkAssignment(def,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 && diff --git a/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java b/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java index 707202ab..0c2a25f7 100644 --- a/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java +++ b/chapter31/src/main/java/com/seaofnodes/simple/parser/Statements.java @@ -78,6 +78,7 @@ final class Statements { */ private Node parseReturn() { var expr = P.require(P.expressions.parseAssignment(), ";"); + if( P.arrays._frame!=null ) return P.arrays.addReturn(expr); return P.functions.addReturn(expr); } diff --git a/chapter31/src/main/smp/sys/aryu8.smp b/chapter31/src/main/smp/sys/aryu8.smp index 88574522..76d47ec5 100644 --- a/chapter31/src/main/smp/sys/aryu8.smp +++ b/chapter31/src/main/smp/sys/aryu8.smp @@ -69,10 +69,7 @@ val atoi = { u8[~] str -> // Zero-terminate a string for libc, reserving one array element for the zero. { u8[~] -> u8[] } str0 = { u8[~] str -> - if( str# == sys.integer.MAX_U32 ) libc._exit(1); - u8[] !strz = new u8[str#+1]; - u32 len=str#; - ary.copy(strz,0,str,0,len); - strz[str#] = '\0'; - strz; -}; + if( str# == sys.integer.MAX_U32 ) libc._exit(1); + // Copy, terminate and return + return new u8[str#+1]{i64 i -> i<str# ? str[i] : '\0'; }; +};
\ No newline at end of file diff --git a/chapter31/src/main/smp/sys/draw/Canvas.smp b/chapter31/src/main/smp/sys/draw/Canvas.smp index c736e4e2..5bd7d68e 100644 --- a/chapter31/src/main/smp/sys/draw/Canvas.smp +++ b/chapter31/src/main/smp/sys/draw/Canvas.smp @@ -112,79 +112,25 @@ struct Canvas { }; }; -// An old-school 3x5 font +// An old-school 3x5 font, ASCII 32..127. Five octal row digits need u16. +// Lowercase uses uppercase shapes; unsupported characters use '?'. +val _glyphs = new u16[~96]{ + 000000,022202,055000,057575,061202,051245,061202,022000, // space..' + 012221,042224,005250,002720,000024,000700,000002,011244, // (../ + 075557,026227,061247,061216,055711,074616,034757,071222, // 0..7 + 075757,075716,002020,002024,012421,007070,042124,061202, // 8..? + 061202,025755,065656,034443,065556,074647,074644,034553, // @..G + 055755,072227,011152,055655,044447,057755,057775,025552, // H..O + 065644,025573,065655,034216,072222,055557,055552,055775, // P..W + 055255,055222,071247,032223,044211,062226,025000,000007, // X.._ + 042000,025755,065656,034443,065556,074647,074644,034553, // `..g + 055755,072227,011152,055655,044447,057755,057775,025552, // h..o + 065644,025573,065655,034216,072222,055557,055552,055775, // p..w + 055255,055222,071247,032423,022222,062126,003600,061202, // x..DEL +}; + val _glyph = { u8 c -> - // Only space for upper-case letters in a 3x5 font - if( 'a' <= c <= 'z' ) - c -= 'a'-'A'; - // Five octal digits: one 3-bit row per digit, top to bottom. - if( c==' ' ) return 000000; - if( c=='0' ) return 075557; - if( c=='1' ) return 026227; - if( c=='2' ) return 061247; - if( c=='3' ) return 061216; - if( c=='4' ) return 055711; - if( c=='5' ) return 074616; - if( c=='6' ) return 034757; - if( c=='7' ) return 071222; - if( c=='8' ) return 075757; - if( c=='9' ) return 075716; - if( c=='A' ) return 025755; - if( c=='B' ) return 065656; - if( c=='C' ) return 034443; - if( c=='D' ) return 065556; - if( c=='E' ) return 074647; - if( c=='F' ) return 074644; - if( c=='G' ) return 034553; - if( c=='H' ) return 055755; - if( c=='I' ) return 072227; - if( c=='J' ) return 011152; - if( c=='K' ) return 055655; - if( c=='L' ) return 044447; - if( c=='M' ) return 057755; - if( c=='N' ) return 057775; - if( c=='O' ) return 025552; - if( c=='P' ) return 065644; - if( c=='Q' ) return 025573; - if( c=='R' ) return 065655; - if( c=='S' ) return 034216; - if( c=='T' ) return 072222; - if( c=='U' ) return 055557; - if( c=='V' ) return 055552; - if( c=='W' ) return 055775; - if( c=='X' ) return 055255; - if( c=='Y' ) return 055222; - if( c=='Z' ) return 071247; - if( c=='-' ) return 000700; - if( c=='.' ) return 000002; - if( c==':' ) return 002020; - if( c==',' ) return 000024; - if( c==';' ) return 002024; - if( c=='\'' ) return 022000; - if( c=='"' ) return 055000; - if( c=='`' ) return 042000; - if( c=='/' ) return 011244; - if( c=='\\' ) return 044211; - if( c=='(' ) return 012221; - if( c==')' ) return 042224; - if( c=='[' ) return 032223; - if( c==']' ) return 062226; - if( c=='{' ) return 032423; - if( c=='}' ) return 062126; - if( c=='+' ) return 002720; - if( c=='=' ) return 007070; - if( c=='_' ) return 000007; - if( c=='<' ) return 012421; - if( c=='>' ) return 042124; - if( c=='|' ) return 022222; - if( c=='^' ) return 025000; - if( c=='~' ) return 003600; - if( c=='*' ) return 005250; - if( c=='#' ) return 057575; - if( c=='%' ) return 051245; - if( c=='!' ) return 022202; - if( c=='?' ) return 061202; - return 061202; + return 32<=c<128 ? _glyphs[c-32] : 061202; }; // Pixel coverage for a round-ended segment, separate from traversal. diff --git a/chapter31/src/main/smp/sys/text.smp b/chapter31/src/main/smp/sys/text.smp index a28edf6d..2c4b44ff 100644 --- a/chapter31/src/main/smp/sys/text.smp +++ b/chapter31/src/main/smp/sys/text.smp @@ -5,10 +5,7 @@ val begin = { -> new aryu8(); }; val end = { aryu8 b -> - val bytes = new u8[b.len]; - ary.copy(bytes,0,b.buf,0,b.len); - u8[~] result = bytes; - result; + new u8[~b.len]{ i64 i -> b.buf[i]; }; }; val _pad = { !aryu8 b, i64 n, u8 c -> diff --git a/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java b/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java new file mode 100644 index 00000000..6cba5ee3 --- /dev/null +++ b/chapter31/src/test/java/com/seaofnodes/simple/ArrayInitTest.java @@ -0,0 +1,175 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import org.junit.Test; +import static org.junit.Assert.*; + +public class ArrayInitTest { + private static void value(String src, long expected) { + CodeGen code=new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals(Long.toString(expected),Eval2.eval(code,0,100000)); + } + + private static void bad(String src, String message) { + try { + new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + fail("Expected: "+message); + } catch( com.seaofnodes.simple.parser.Parser.ParseException e ) { + assertTrue(e.getMessage(),e.getMessage().contains(message)); + } + } + + @Test public void lists() { + value("val a=new u16[3]{025755,1,65538}; return a#*100000+a[0]+a[1]+a[2];",311248); + value("val a=new int[]{4,7,9,}; a[1]=2; return a#+a[0]+a[1]+a[2];",18); + value("val a=new int[]{}; return a#;",0); + bad("val a=new int[2]{1}; return a[0];","initializer has 1 elements"); + } + + @Test public void generators() { + value("int n=6; val a=new int[n]{int i -> i*i;}; return a[5]+a#;",31); + value("int base=7; val a=new int[3]{int i -> return base+i;}; return a[2];",9); + value("val a=new int[3]{int i -> if(i==1) return 9; i+1;}; return a[0]+a[1]+a[2];",13); + value("val a=new int[0]{int i -> 1/i;}; return a#;",0); + bad("val a=new int[]{int i -> i;}; return a#;","requires a length"); + bad("val f={-> int x=1; val a=new int[2]{int i -> x++;}; return a[0];}; return f();","captured local"); + } + + @Test public void referencesAndReadonly() { + value("struct S { int x; new S={i64 v -> x=v;}; }; val a=new S[~3]{int i -> new S(i+4);}; return a[2].x;",6); + value("val a=new u8[~]{1,2,3}; return a[1];",2); + value("val names=new u8[~][~2]{\"Alice\",\"Bob\"}; return names[1][0];",66); + value("val a=new int[ ~ ]{4,5}; return a[1];",5); + value("val a=new {i64 -> i64}[]{ {i64 x -> x+1;}, {i64 x -> x*2;} }; return a[1](7);",14); + value("struct S { int x=1; }; val a=new !S[1]{new S}; a[0].x=8; return a[0].x;",8); + bad("struct S { int x=1; }; val a=new !S[~1]{new S}; a[0].x=8; return a[0].x;","final field"); + value("struct S { int x; }; val a=new S?[~2]{u32 i -> null;}; return a[1]==null;",1); + bad("val a=new int[~2]{1,2}; a[0]=3; return a[0];","final field"); + bad("struct S { int x; }; val a=new S[1]{null}; return a#;","declared type"); + } + + @Test public void nativeInitializers() throws Exception { + TestC.runSF(""" + val main = { -> + int n=8; + val a=new u16[~n]{int i -> i*i+1;}; + val b=new u8[]{257,2,3}; + return a[7]==50 && b[0]==1 && a#==8 ? 0 : 1; + }; + ""","arrayInitializers","",-1); + } + + @Test public void nestedBodies() { + value(""" + val f={ -> + int offset=5; + val a=new int[4]{int i -> + int sum=offset; + for(int j=0;j<i;j++) { if(j==1) continue; sum+=j; } + if(i==2) return 20; + return sum; + }; + return a[0]+a[1]+a[2]+a[3]; + }; + return f(); + """,37); + value(""" + val a=new int[][3]{int i -> + new int[2]{int j -> i*10+j;}; + }; + return a[2][1]+a[0][0]; + """,21); + value(""" + val a=new int[2]{int i -> + val twice={i64 x -> return x*2;}; + twice(i); + }; + return a[1]; + """,2); + } + + @Test public void orderAndIdentity() throws Exception { + TestC.runSF(""" + struct Counter { + int calls=0; + val next={->calls++;}; + }; + struct Item { + int x; + new Item={i64 v -> x=v;}; + }; + val build_noInline={u32 n, !Counter c -> + new Item[~n]{u32 i -> new Item(c.next());}; + }; + val main={-> + val c=new Counter; + val empty=build_noInline(0,c); + if(empty#!=0 || c.calls!=0) return 1; + val a=build_noInline(4,c); + if(c.calls!=4 || a[0].x!=0 || a[3].x!=3 || a[0]==a[1]) return 2; + val b=new int[]{c.next(),c.next(),c.next()}; + if(b[0]!=4 || b[2]!=6) return 3; + val d=new int[c.next()]{i64 i -> c.next();}; + if(d#!=7 || d[0]!=8 || d[6]!=14 || c.calls!=15) return 4; + return 0; + }; + ""","arrayInitOrder","",-1); + } + + @Test public void diagnostics() { + bad("val f={u32 n -> new int[n]{1,2};}; return f(arg)#;","length must equal that constant"); + bad("val a=new int[2]{u8 i -> i;}; return a[0];","generator index"); + bad("val a=new int[2]{int return -> 0;}; return a[0];","index name"); + bad("val a=new int[2]{int i -> i++;}; return a[0];","final 'i'"); + bad("val f={->int x=0; return new int[2]{int i -> x=i;};}; return f()#;","captured local"); + bad("val a=new int[2]{int i -> break;}; return a[0];","No active loop"); + bad("val a=new int[-1]{}; return a#;","outside the u32 range"); + bad("val a=new int[1.5]{int i -> i;}; return a#;","integer"); + bad("val a=new int[~]{1,2}; val b=a; b[0]++; return b[0];","final field"); + for( String init : new String[]{"self","int i -> self;"} ) + bad("struct C { int x; new C={-> val a=new C[1]{"+init+"}; x=a#;}; }; return new C().x;", + "fully initialized elements"); + } + + @Test public void forwardReferencesAndLoopScopes() { + value(""" + val f={ -> + int sum=0; + for(int k=0;k<3;k++) { + val a=new int[2]{int i -> + if(i) return later(i); + later(3); + }; + if(k==1) continue; + sum+=a[0]+a[1]; + } + return sum; + }; + val later={i64 i -> i*2;}; + return f(); + """,16); + } + + @Test public void optimizationOrder() { + String src=""" + struct Box { int x; new Box={i64 v -> x=v;}; }; + val f={u32 n -> + int base=7; + val a=new Box[~n]{u32 i -> + int x=base; + for(int j=0;j<i;j++) x+=j; + if(i==2) return new Box(20); + new Box(x); + }; + return n==0 ? 0 : a[n-1].x; + }; + return f(arg&7); + """; + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(code,0)); + assertEquals("20",Eval2.eval(code,3)); + assertEquals("13",Eval2.eval(code,5)); + } + } +} diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 9bebee23..c9791de4 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -332,9 +332,7 @@ return 0; public void testFinalArray() { String src = """ int N=4; -i32[] !is = new i32[N]; -for( int i=0; i<N; i++ ) - is[i] = i*i; +i32[~] !is = new i32[~N]{ int i -> i*i; }; val _sum = { i32[~] is -> // final array int sum=0; for( int i=0; i<is#; i++ ) diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java index 6b892c65..055ef518 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -477,7 +477,7 @@ public class Chapter25Test { // Software drawing and the optional native graphics interface. assertArrayEquals(new String[]{"sys/adt", "sys/ary", - "sys/aryi64", "sys/aryu8", "sys/audio", "sys/char", "sys/draw", "sys/gfx", "sys/io", "sys/libc", "sys/rt", "sys/scan", "sys/text", "sys/time"},sys_elf._deps); + "sys/aryi64", "sys/aryu8", "sys/audio", "sys/char", "sys/draw", "sys/gfx", "sys/integer", "sys/io", "sys/libc", "sys/rt", "sys/scan", "sys/text", "sys/time"},sys_elf._deps); assertArrayEquals(new String[]{"sys"},sys_elf._units); assertSame("class:sys" ,sys_elf._clz._name); } diff --git a/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java index 5a20388b..12634f24 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java @@ -7,6 +7,37 @@ import org.junit.Test; import static org.junit.Assert.*; public class EscapeFlowTest { + @Test public void testArrayGeneratorDoesNotPublishUnrelatedFunctions() { + for( long seed : new long[]{1,123,987654321} ) + for( String access : new String[]{"","~"} ) { + CodeGen code=new CodeGen(""" + val _hidden_noInline={i64 x -> x+1;}; + val data=new i32[%s(arg&7)]{int i -> i*i;}; + return data#; + """.formatted(access),seed,true).parse(); + int hidden=named(code,"_hidden_noInline").sig().fidx(); + code.driver(CodeGen.Phase.TypeCheck); + TypeMem outside=((TypeTuple)code._start._type).mem(); + assertFalse(XInt.bit(outside._escFs,hidden)); + assertEquals("0",Eval2.eval(code,0)); + assertEquals("4",Eval2.eval(code,4)); + } + } + + @Test public void testArrayGeneratorPublishesItsCallbacks() { + CodeGen code=new CodeGen(""" + val _callback_noInline={i64 x -> x+1;}; + val callbacks=new {i64 -> i64}[~3]{int i -> _callback_noInline;}; + return callbacks; + """).parse(); + FunNode callback=named(code,"_callback_noInline"); + int fidx=callback.sig().fidx(); + code.driver(CodeGen.Phase.TypeCheck); + assertTrue(XInt.bit(((TypeTuple)code._start._type).mem()._escFs,fidx)); + assertFalse(callback.isDead()); + assertTrue(callback.unknownCallers()); + } + // A cofinite outside set must not revive private allocation helpers. @Test public void testOpaqueRoundTrip() { for( long seed : new long[]{1,123,987654321} ) { diff --git a/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java index 8d0e3d5b..b6228666 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/Eval2.java @@ -388,7 +388,7 @@ public abstract class Eval2 { TypeMem mem = (TypeMem)con._type; Object val = con(mem._t); TypeMemPtr tmp = (TypeMemPtr)esc.self()._type; - int idx = tmp._obj.findAlias(esc.fld()._alias); + int idx = layout(tmp._obj).findAlias(esc.fld()._alias); Object[] fs = (Object[])val(esc.self()); if( tmp._obj.isAry() ) { assert idx==0; // length field @@ -443,10 +443,20 @@ public abstract class Eval2 { private static Object[] classObject(TypeStruct type) { Object[] ptr = CLASSES.get(type._name); - if( ptr==null ) CLASSES.put(type._name,ptr=alloc(type)); + if( ptr==null ) CLASSES.put(type._name,ptr=alloc(layout(type))); return ptr; } + // Imported pointer views may retain parsing-only forward-reference fields. + // Like native field offsets, evaluator slots use the module's final layout. + private static TypeStruct layout(TypeStruct type) { + if( TypeStruct.startsClzPrefix(type._name) ) { + CompUnit cu=CODE._compunits.get(type._name.substring(6).replace('.','/')); + if( cu!=null ) return cu._clz; + } + return type; + } + private static Object not( NotNode not ) { Object n = val(not.in(1)); return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 ) @@ -465,7 +475,7 @@ public abstract class Eval2 { TypeMemPtr tmp = (TypeMemPtr)ld.ptr()._type; int idx = tmp._obj.isAry() ? offToIdx(x(ld.off()),tmp._obj) - : tmp._obj.find(ld._name); + : layout(tmp._obj).find(ld._name); return fs[idx]; } @@ -476,7 +486,7 @@ public abstract class Eval2 { Object[] fs = (Object[])val(st.ptr()); int idx = tmp._obj.isAry() ? offToIdx(x(st.off()),tmp._obj) - : tmp._obj.find(st._name); + : layout(tmp._obj).find(st._name); Object val = val(st.val()); // Idealization can remove conversions already performed by the store. Type type = st.declaredType(); diff --git a/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java index adeba136..6d81af85 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -8,6 +8,31 @@ import static org.junit.Assert.*; // Execution regressions are separate from the fixed spill-measurement cohorts. public class MemoryEncodingTest { + @Test public void testIntegerMemoryOperandsAcrossCalls() throws java.io.IOException { + TestC.runSF(""" + val touch_noInline={i64[] a, i64 i -> a[i]+=1;}; + val scan_noInline={i64[] a, i64 limit, i64 delta -> + int below=0, above=0; + for(int i=0;i<a#;i++) { + touch_noInline(a,i); + if(a[i]<limit) below++; + if(limit<a[i]) above++; + a[i]+=delta; + } + return below*10+above; + }; + val main={-> + val a=new i64[]{0x7ffffffffffffffe,-2,0}; + if(scan_noInline(a,0x7fffffffffffffff,0)!=20) return 1; + if(a[0]!=0x7fffffffffffffff || a[1]!=-1 || a[2]!=1) return 2; + val b=new i64[]{-3,-2,0}; + if(scan_noInline(b,-1,0x100000000)!=11) return 3; + if(b[0]!=0xfffffffe || b[1]!=0xffffffff || b[2]!=0x100000001) return 4; + return 0; + }; + ""","integerMemoryAcrossCalls","",-1); + } + @Test public void testLoopLoadSearch() { String[] sources = { """ diff --git a/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java b/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java index ef26a943..a1253d05 100644 --- a/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java +++ b/chapter31/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java @@ -134,6 +134,24 @@ public class ParserDiagnosticTest { +"\n return x;\n ^",error.diagnostic()); } + @Test public void testJUnitReportsSourceDiagnostic() { + var error=rejected("val dist=new i64[3]{int i -> i;}\ndist[0]=0;"); + String expected="Test.smp:2:1: error: Syntax error, expected `;` but found `dist`" + +"\n dist[0]=0;\n ^"+System.lineSeparator(); + var test=org.junit.runner.Description.createTestDescription(getClass(),"missingSemicolon"); + assertEquals(expected,new org.junit.runner.notification.Failure(test,error).getTrace()); + + var bytes=new java.io.ByteArrayOutputStream(); + error.printStackTrace(new java.io.PrintStream(bytes,true,StandardCharsets.UTF_8)); + assertEquals(expected,bytes.toString(StandardCharsets.UTF_8)); + assertEquals("Syntax error, expected `;` but found `dist`",error.getMessage()); + + // Compiler implementation failures must still include Java frames. + String trace=new org.junit.runner.notification.Failure(test,new IllegalStateException("compiler bug")).getTrace(); + assertTrue(trace,trace.contains("java.lang.IllegalStateException: compiler bug")); + assertTrue(trace,trace.contains("ParserDiagnosticTest.testJUnitReportsSourceDiagnostic")); + } + @Test public void testEOF() { var error=rejected("return 1"); assertEquals("Test.smp:1:9: error: Syntax error, expected `;` but found `EOF`" |
