summaryrefslogtreecommitdiff
path: root/chapter28b/src/test/java/com/seaofnodes
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-24 15:31:01 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 15:31:01 -0700
commitfeb98e1bb6c973428b94591ed5b0aed5e4137bd0 (patch)
treeee104333658a452590546c483fda1cae315ce134 /chapter28b/src/test/java/com/seaofnodes
parente2b042bd9721995486f4f0cf96847c02d27908e3 (diff)
downloadSimple-feb98e1bb6c973428b94591ed5b0aed5e4137bd0.tar.gz
Simple-feb98e1bb6c973428b94591ed5b0aed5e4137bd0.zip
Finish separate compilation snapshot in chapter 28b
Diffstat (limited to 'chapter28b/src/test/java/com/seaofnodes')
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/BoundaryTest.java86
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/ChainedComparisonTest.java37
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter18Test.java7
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter20Test.java7
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter21Test.java8
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java12
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java65
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java90
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Simple.java25
10 files changed, 311 insertions, 28 deletions
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/BoundaryTest.java
new file mode 100644
index 00000000..b736ccb3
--- /dev/null
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/BoundaryTest.java
@@ -0,0 +1,86 @@
+package com.seaofnodes.simple;
+
+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 {
+ 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));
+ }
+ }
+}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/ChainedComparisonTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ChainedComparisonTest.java
new file mode 100644
index 00000000..8db9c024
--- /dev/null
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/ChainedComparisonTest.java
@@ -0,0 +1,37 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ChainedComparisonTest {
+ @Test public void testAdjacentOperandsAndOperators() {
+ String[] expressions = {"10 < arg < 20", "0 < arg <= 3", "3 >= arg > 0",
+ "0 <= arg < 3", "0 < arg <= 3 < 4"};
+ String[][] expected = {{"0","0","1","0"}, {"0","1","0","0"},
+ {"0","1","0","0"}, {"1","0","0","0"},
+ {"0","1","0","0"}};
+ int[] args = {0,3,15,30};
+ for(int i=0;i<expressions.length;i++) {
+ CodeGen code = new CodeGen("return "+expressions[i]+";").driver(CodeGen.Phase.TypeCheck);
+ for(int j=0;j<args.length;j++) assertEquals(expressions[i],expected[i][j],Eval2.eval(code,args[j]));
+ }
+ }
+
+ @Test public void testSingleEvaluationAndShortCircuit() {
+ for(int seed:new int[]{2,13,123}) {
+ CodeGen code = new CodeGen("int x=arg; int y=0; val b=0 < x++ <= (y+=3) < (y+=1); return x*100+y*10+b;",seed,true)
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("100",Eval2.eval(code,0));
+ assertEquals("441",Eval2.eval(code,3));
+ assertEquals("530",Eval2.eval(code,4));
+ }
+ try {
+ new CodeGen("return 0 < arg > 1;").parse();
+ fail("mixed directions");
+ } catch(RuntimeException e) {
+ assertEquals("Mixing relational directions in a chained relational test",e.getMessage());
+ }
+ }
+}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 08029c4b..4019fd38 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -300,7 +300,7 @@ A x = new A;
for( String fields : new String[] {
"!B !b; new A = { -> if (arg) b = new B; };",
"!B !b; new A = { -> if (arg) return 0; b = new B; };",
- "!B !b; new A = { !B b -> b = new B; };", // Argument shadows field
+ "!B !b; new A = { !B !b -> b = new B; };", // Argument shadows field
"new A = { -> }; !B !b;", // Field declared later
} ) {
CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;");
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index ae1dca5f..632b9539 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -436,7 +436,7 @@ return s.x;
catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*Test.S'",e.getMessage()); }
}
- @Test @Ignore
+ @Test
public void testErr5() {
CodeGen code = new CodeGen(
"""
@@ -450,8 +450,8 @@ return g();
}
- // Mutual recursion. Fails without SCCP to lift the recursive return types.
- @Ignore @Test
+ // Mutual recursion: check results, independent of how much gets inlined.
+ @Test
public void testFcnMutRec() {
CodeGen code = new CodeGen(
"""
@@ -460,7 +460,6 @@ val is_odd = { int x -> x ? is_even(x-1) : false; };
return is_even(arg);
""");
code.driver(CodeGen.Phase.TypeCheck);
- assertEquals("Stop[ return #2; return Phi(Region,0,1,#2); ]", code.print());
assertEquals("1", Eval2.eval(code, 0));
assertEquals("0", Eval2.eval(code, 1));
assertEquals("1", Eval2.eval(code, 2));
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 0458941e..cdfd16a7 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -202,7 +202,6 @@ flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0)
}
-
@Test
public void testArray1() {
String src =
@@ -257,9 +256,9 @@ val _hashCodeString = { !String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",8,null);
- testCPU(src,"arm" , "SystemV",8,null);
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",6,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 07ae42eb..2fcf65e9 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -383,9 +383,9 @@ val _hashCodeString = { !String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",8,null);
- testCPU(src,"arm" , "SystemV",8,null);
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",6,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test public void testStringExport() throws IOException {
@@ -422,7 +422,7 @@ val _hashCodeString = { !String self ->
return hash;
};
""";
- TestC.runC(src, "stringHash", "",21);
+ TestC.runC(src, "stringHash", "",12);
}
@Test public void testLoop2() {
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 275be96a..31a616b5 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -412,7 +412,7 @@ public class Chapter25Test {
@Test
public void testHelloWorldDriver() throws Exception {
String out = "build/objs/helloWorldDriver/";
- Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","docs/examples/A_helloWorld.smp"});
+ Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","src/test/smp/examples/A_helloWorld.smp"});
assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o")));
}
@@ -423,7 +423,7 @@ public class Chapter25Test {
try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) {
System.setOut(capture);
Simple.main(new String[]{"-L",SYS_FILE.toString(),"-o","build/objs/helloWorldDriverLibFile/",
- "docs/examples/A_helloWorld.smp"});
+ "src/test/smp/examples/A_helloWorld.smp"});
} finally {
System.setOut(saved);
}
@@ -471,7 +471,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2);
@Test
public void testBubbles() throws IOException {
- String src = Files.readString( Path.of("docs/examples/BubbleSort.smp"));
+ String src = Files.readString( Path.of("src/test/smp/examples/BubbleSort.smp"));
String exe = TestC.compile(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION,null,null,-1);
assertEquals("[-17, 2, 3, 999]\n",
@@ -492,7 +492,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2);
@Test
public void testCapitalize() throws IOException {
- String src = Files.readString(Path.of("docs/examples/Capitalize.smp"));
+ String src = Files.readString(Path.of("src/test/smp/examples/Capitalize.smp"));
String exe = TestC.compile(src,"Capitalize",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION,null,null,-1);
assertEquals("Hello world\n",TestC.exec(exe,"hello world"));
@@ -505,7 +505,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2);
@Test
public void testDijkstra() throws IOException {
- String src = Files.readString(Path.of("docs/examples/Dijkstra.smp"));
+ String src = Files.readString(Path.of("src/test/smp/examples/Dijkstra.smp"));
String matrix = "[0, 2, 0, 6, 0, 2, 0, 3, 8, 5, 0, 3, 0, 0, 7, 6, 8, 0, 0, 9, 0, 5, 7, 9, 0]";
String exe = TestC.compile(src,"Dijkstra",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION,null,null,-1);
@@ -520,7 +520,7 @@ return rez < buf# ? 0 : sys.libc._exit(-2);
@Test
public void testFileIO() throws IOException {
- String src = Files.readString(Path.of("docs/examples/FileIO.smp"));
+ String src = Files.readString(Path.of("src/test/smp/examples/FileIO.smp"));
String exe = TestC.compile(src,"FileIO",new Ary<>(new String[]{SYS_BLDDIR}),
TestC.CALL_CONVENTION,null,null,-1);
assertEquals("File I/O succeeded\n",TestC.exec(exe));
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
new file mode 100644
index 00000000..f583e2d2
--- /dev/null
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/FinalConstructionTest.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.StoreNode;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class FinalConstructionTest {
+ private static final String LOOP = """
+ val step={int x -> x*2+1;};
+ struct C {
+ val x=1;
+ new C={int n ->
+ x=0; x=1;
+ for(int i=0; i<n; i++) x=step(x);
+ };
+ val get={->x;};
+ };
+ return new C(arg).get();
+ """;
+
+ @Test public void testFinalConstructorLocals() throws java.io.IOException {
+ var code=new CodeGen(LOOP).driver(CodeGen.Phase.LocalSched);
+ assertEquals("1",Eval2.eval(code,0));
+ assertEquals("15",Eval2.eval(code,3));
+ assertEquals("63",Eval2.eval(code,5));
+ code._stop.walk(n -> {
+ if( n instanceof StoreNode st && st._name.equals("x") )
+ assertEquals("Final field writes belong after the loop",0,st.cfg0().loopDepth());
+ return null;
+ });
+ String[] sources={
+ "struct C { val x=1; new C={->x=1; x=2;}; }; return new C().x;",
+ "struct C { int ~x; new C={int n -> self.x=1; for(int i=0;i<n;i++) self.x*=2;}; }; return new C(arg).x;",
+ "struct C { val x=1; new C={int n -> for(int i=0;i<n;i++) ++x;}; }; return new C(arg).x;",
+ "struct C { val x=1.5; new C={->x=2.5;}; }; return new C().x;",
+ "struct C { val x=\"a\"; new C={int n -> for(int i=0;i<n;i++) x=\"b\";}; }; return new C(arg).x[0];",
+ "struct C { val x=new int[1]; new C={->x=new int[2]; x=new int[3];}; }; return new C().x#;"
+ };
+ String[] results={"2","8","4","2.5","98","3"};
+ for( int i=0; i<sources.length; i++ )
+ assertEquals(results[i],Eval2.eval(new CodeGen(sources[i]).driver(CodeGen.Phase.TypeCheck),3));
+
+ String[] rejected={
+ "struct C { val x=1; }; val c=new C(); c.x=2; return c.x;",
+ "struct C { new C={->val x=1; x=2;}; }; return new C();",
+ "struct C { val f={->1;}; new C={->f={->2;};}; }; return new C().f();"
+ };
+ for( String src : rejected ) {
+ try { new CodeGen(src).driver(CodeGen.Phase.TypeCheck); fail(src); }
+ catch( Parser.ParseException e ) { assertTrue(e.getMessage(),e.getMessage().startsWith("Cannot")); }
+ }
+
+ var dir=java.nio.file.Path.of("build/objs");
+ java.nio.file.Files.createDirectories(dir);
+ java.nio.file.Files.writeString(dir.resolve("finalConstruction.smp"),"val buildFinal={int arg ->"+LOOP+"};");
+ java.nio.file.Files.writeString(dir.resolve("finalConstruction.c"),"""
+ extern long long buildFinal(long long);
+ int main(void) { return buildFinal(0)!=1 || buildFinal(3)!=15 || buildFinal(5)!=63; }
+ """);
+ TestC.run("val buildFinal={int arg ->"+LOOP+"};","finalConstruction",null,TestC.CALL_CONVENTION,
+ TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
+ dir.resolve("finalConstruction.c").toString(),"",-1);
+ }
+}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
new file mode 100644
index 00000000..4576bc94
--- /dev/null
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
@@ -0,0 +1,90 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class SeparateCompilationTest {
+ @Test public void testImportedNativeExposure() throws Exception {
+ Path build=Path.of("build/objs/separate");
+ Files.createDirectories(build);
+ Path lib=Files.createTempDirectory(build,"callbacks-");
+ 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";
+ retain(power);
+ return power(2,n);
+ };
+ """;
+ new CodeGen(null,lib.toString(),null,"Callbacks",src,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ CodeGen code=new CodeGen(null,null,new Ary<>(new String[]{lib.toString()}),
+ "Client","return Callbacks.run(arg);",123,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.Opto);
+ assertNull(code._compunits.get("Callbacks")._src);
+ ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
+ assertNotNull(base);
+ assertSame("Native code may call the saved callback with any base",TypeInteger.BOT,base._type);
+ code.driver(CodeGen.Phase.TypeCheck);
+ }
+
+ // The client root contains no library source. The saved constructor must
+ // keep local loop values, and retained calls must use the library's storage.
+ @Test public void testImportedConstructorAndState() throws Exception {
+ Path fixtures=Path.of("src/test/smp/separate");
+ Path build=Path.of("build/objs/separate");
+ Files.createDirectories(build);
+ Path lib=Files.createTempDirectory(build,"library-");
+ Path client=Files.createTempDirectory(build,"client-");
+ new CodeGen(fixtures.resolve("library").toString(),lib.toString(),null,
+ "Counters",null,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ assertTrue(Files.isRegularFile(lib.resolve("Counters.o")));
+ assertFalse(Files.exists(fixtures.resolve("client/Counters.smp")));
+ var paths=new Ary<>(new String[]{lib.toString()});
+ for( long seed : new long[]{2,13,123} ) {
+ CodeGen code=new CodeGen(fixtures.resolve("client").toString(),client.toString(),paths,
+ "Main",null,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ CompUnit imported=code._compunits.get("Counters");
+ assertNotNull(imported);
+ assertNull("Library source must not be parsed",imported._src);
+ assertEquals(lib.resolve("Counters.o").toRealPath(),imported._obj.toPath().toRealPath());
+ assertEquals("0",Eval2.eval(code,0));
+ assertEquals("107",Eval2.eval(code,3));
+ assertEquals("131",Eval2.eval(code,5));
+ }
+ new CodeGen(fixtures.resolve("client").toString(),client.toString(),paths,
+ "Main",null,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ Path driver=client.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdio.h>
+ __attribute__((CALL_CONV)) extern long long run(long long);
+ int main(void) {
+ long long args[]={0,3,5,0,3}, expected[]={0,107,231,0,307};
+ for(int i=0;i<5;i++) {
+ long long actual=run(args[i]);
+ if(actual!=expected[i]) {
+ printf("call %d: got %lld, expected %lld\\n",i,actual,expected[i]);
+ return 1;
+ }
+ }
+ return 0;
+ }
+ """);
+ String exe=client.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(client.resolve("Main.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",driver.toString(),
+ new Ary<>(new String[]{lib.resolve("Counters.o").toString()}),exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java b/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
index 2d688bbe..7cfdf57d 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
@@ -13,7 +13,6 @@ import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.stream.Collectors;
public class Simple {
static void usage() {
@@ -83,7 +82,11 @@ Options:
static String getInput() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
- return reader.lines().collect(Collectors.joining("\n"));
+ StringBuilder text=new StringBuilder();
+ try {
+ for( String line; (line=reader.readLine())!=null; ) text.append(line).append('\n');
+ } catch( IOException e ) { throw new RuntimeException(e); }
+ return text.toString();
}
public static void main(String[] args) throws Exception {
@@ -140,6 +143,9 @@ Options:
case "-o": if (out != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
out = args[++i]; do_codegen=true;
break;
+ case "--root": if( root!=null || i+1>=args.length ) bad_usage();
+ root=args[++i];
+ break;
case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
libPaths.add(args[++i]);
break;
@@ -175,7 +181,8 @@ Options:
if( modPath == null )
modPath = Path.of(".");
} else {
- throw Utils.TODO();
+ modPath=Path.of(root);
+ inPath=modPath.resolve(inPath);
}
// Compute the module-relative paths for all sources
@@ -184,7 +191,8 @@ Options:
Path relPath = n==1 ? null : srcRelPath.subpath(0,n-1);
// Compute source/class name
String srcNameExt = srcRelPath.getName(n-1).toString();
- String srcName = srcNameExt.substring(0,srcNameExt.length()-4);
+ String srcName = srcRelPath.toString().replace('\\','/');
+ srcName=srcName.substring(0,srcName.length()-4);
// Compute the build path.
Path outPath;
@@ -219,15 +227,14 @@ Options:
if( simpleHome != null && !simpleHome.isEmpty() )
libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString());
- // Read input file
- try {
- src = input_filename.isEmpty() ? getInput() : Files.readString(Path.of(input_filename));
- } catch( IOException e ) { throw bad("Cannot read input file: "+input_filename); }
+ // Keep file-backed units so discovery sees parents and siblings.
+ src = input_filename.isEmpty() ? getInput() : null;
// Compilation pipeline
Ary<String> externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0]));
CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, true, TypeInteger.BOT );
- code.driver(Phase.LastPhase,cpu,abi, false, emit_main || do_run || do_eval, dump);
+ code.driver(do_eval && !do_codegen ? Phase.TypeCheck : Phase.LastPhase,
+ cpu,abi, false, emit_main || do_run || do_eval, dump);
if( do_codegen && print_asm )
System.out.println(code.asm());