summaryrefslogtreecommitdiff
path: root/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
committerCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
commitf4bd723d929e53475f7990374dca7b7dfcbbdc42 (patch)
tree7bb080119e7b88ad4ac9b30af019fe6a5e4ba9dd /chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
parent0ac0c9e10287972ac3af5a4ee43d26f0d5b318fe (diff)
downloadSimple-main.tar.gz
Simple-main.zip
Add Chapter 29 for tech debt cleanup with the parser splitmain
Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks.
Diffstat (limited to 'chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java')
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java97
1 files changed, 97 insertions, 0 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
new file mode 100644
index 00000000..de502371
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/BoundaryTest.java
@@ -0,0 +1,97 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+// The new module boundary must preserve the preceding chapters' examples.
+public class BoundaryTest {
+ @Test public void testLocalConstruction() throws java.io.IOException {
+ // Nested factories must preserve memory duals during construction,
+ // for both direct fields and getters on the returned instance.
+ for( String body : new String[]{"int x=7;", "int _x=7; val get={ -> _x; };" } )
+ for( long seed : new long[]{1,13,123} ) {
+ String src = "struct Outer { struct _Hidden { "+body+
+ " }; val make={ -> new _Hidden(); }; }; return new Outer().make().";
+ CodeGen factory = new CodeGen(src+(body.startsWith("int x") ? "x;" : "get();"),seed,true)
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("7",Eval2.eval(factory,0));
+ }
+ String src="struct Counter { int x; int y; int z; new Counter={int n -> while(x<n) { int old=x; y=x+1; x=y+1; z=old; }}; }; val c=new Counter(arg); return c.x*100+c.z;";
+ CodeGen code=new CodeGen(src).driver(CodeGen.Phase.LocalSched);
+ assertEquals("2018",Eval2.eval(code,19));
+ assertEquals("0",Eval2.eval(code,0));
+ code._stop.walk(n -> {
+ if( n instanceof StoreNode st ) assertEquals(0,st.cfg0().loopDepth());
+ return null;
+ });
+ code=new CodeGen("struct Byte { new Byte={int n -> self.x=n; self.x+=3;}; u8 x; }; return new Byte(255).x;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("2",Eval2.eval(code,0));
+ // data is still unset when the constructor reads and updates x.
+ code=new CodeGen("struct Box { int x=7; int[] data; new Box={ -> self.x++; int n=self.x; data=new int[n]; }; }; return new Box().data#;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("8",Eval2.eval(code,0));
+ for( String field : new String[]{"data","self.data"} ) {
+ try {
+ new CodeGen("struct Box { int[] data; new Box={ -> int n="+field+"#; data=new int[n]; }; }; return new Box();")
+ .driver(CodeGen.Phase.TypeCheck);
+ fail("An uninitialized field must not be read");
+ } catch( Parser.ParseException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("might not be initialized"));
+ }
+ }
+ java.nio.file.Path cfile=java.nio.file.Path.of("build/objs/constructionBoundary.c");
+ java.nio.file.Files.createDirectories(cfile.getParent());
+ java.nio.file.Files.writeString(cfile,"""
+ __attribute__((CALL_CONV)) extern long long count(long long);
+ int main(void) { return count(19)!=2018 || count(0)!=0; }
+ """);
+ TestC.run("val count={int arg ->"+src+"};","constructionBoundary",null,TestC.CALL_CONVENTION,
+ TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",cfile.toString(),"",-1);
+ }
+
+ @Test public void testRecursiveCallback() {
+ String src="""
+ val run={int n ->
+ val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,exponent-1);};
+ val sumPowers={int n,{int int -> int} f -> if(n<=0) return 0; return f(2,n)+sumPowers(n-1,f);};
+ return sumPowers(n,power);
+ };
+ return run(arg);
+ """;
+ for( long seed : new long[]{2,13,123} ) {
+ CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.Opto);
+ ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
+ assertNotNull(base);
+ assertSame(TypeInteger.constant(2),base._type);
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertEquals("14",Eval2.eval(code,3));
+ }
+ }
+
+ @Test public void testNativeExposure() {
+ for( String tail : new String[]{
+ "retain(power); return power(2,n);",
+ "if(n) { retain(power); while(true) {} } return power(2,n);"
+ } ) {
+ String src="val run={int n -> val power={int base,int exponent -> if(exponent<=0) return 1; return base*power(base,exponent-1);}; { {int int -> int} -> int } retain=\"C\";"+tail+"}; return run(arg);";
+ CodeGen code=new CodeGen(src).driver(CodeGen.Phase.Opto);
+ ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
+ assertNotNull(base);
+ assertSame(TypeInteger.BOT,base._type);
+ code.driver(CodeGen.Phase.TypeCheck);
+ }
+ }
+
+ @Test public void testLazyTables() {
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen("src/test/smp/lazy","build/objs",null,"App/Main",null,seed,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("0",Eval2.eval(code,0));
+ assertEquals("125",Eval2.eval(code,1));
+ }
+ }
+}