From 19b9102cf7290de1e70c962e556caccc4e0fd5d9 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 3 Oct 2026 14:03:48 -0700 Subject: Chapter 17b: Syntax Sugar Co-authored-by: Jun Kawasaki --- .../java/com/seaofnodes/simple/BrainFuckTest.java | 72 +++ .../java/com/seaofnodes/simple/Chapter11Test.java | 5 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter17aTest.java | 18 +- .../java/com/seaofnodes/simple/Chapter17bTest.java | 549 +++++++++++++++++++++ .../seaofnodes/simple/CompoundAssignmentTest.java | 77 +++ .../java/com/seaofnodes/simple/MutabilityTest.java | 6 + 7 files changed, 717 insertions(+), 12 deletions(-) create mode 100644 src/test/java/com/seaofnodes/simple/BrainFuckTest.java create mode 100644 src/test/java/com/seaofnodes/simple/Chapter17bTest.java create mode 100644 src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java (limited to 'src/test') diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java new file mode 100644 index 00000000..154b74a2 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -0,0 +1,72 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Test; +import org.junit.Ignore; + +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class BrainFuckTest { + + @Test + public void testBrainfuck() { + var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(StandardCharsets.UTF_8); + var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];"); + for (int i = 0; i < program.length; i++) { + int value = program[i] & 0xFF; + encoded.append("program[").append(i).append("] = ").append(value).append(";"); + } + + Parser parser = new Parser( + encoded + """ +var d = 0; +u8[] !output = new u8[0]; +u8[] !data = new u8[100]; + +for( int pc = 0; pc < program#; pc++ ) { + var command = program[pc]; + if (command == 62) { + d++; + } else if (command == 60) { + d--; + } else if (command == 43) { + data[d]++; + } else if (command == 45) { + data[d]--; + } else if (command == 46) { + // Output a byte; increase the output array size + var old = output; + output = new u8[output# + 1]; + for( var i = 0; i < old#; i++ ) + output[i] = old[i]; + output[old#] = data[d]; // Add the extra byte on the end + } else if (command == 44) { + data[d] = 42; + } else if (command == 91) { + if (data[d] == 0) { + for( var d = 1; d > 0; ) { + command = program[++pc]; + if (command == 91) d++; + if (command == 93) d--; + } + } + } else if (command == 93) { + if (data[d]) { + for( var d = 1; d > 0; ) { + command = program[--pc]; + if (command == 93) d++; + if (command == 91) d--; + } + } + } +} +return output; + """); + StopNode stop = parser.parse().iterate(); + assertEquals("Hello World!\n", Evaluator.evaluate(stop, 0, 10000).toString()); + } +} diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 60ff05d8..f0caed02 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -34,7 +34,8 @@ public class Chapter11Test { """).parse().iterate(); int[] phis = {0}; stop.walk(n -> { - if (n instanceof MemPhiNode) phis[0]++; + // Store sinking can replace field memory Phis with value Phis. + if (n instanceof PhiNode) phis[0]++; if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) assertTrue(a=3); + assertTrue(phis[0]>=4); // Three fields and the loop counter. assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 695777e2..8f64701f 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -293,7 +293,7 @@ else { } """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); + assertEquals("Stop[ return 9; return 0; return 0; return 0; return 0; ]", stop.toString()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java index 7d61a913..423eade0 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java +++ b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java @@ -51,7 +51,7 @@ return p; Parser parser = new Parser(""" struct Point { int x=3, y=4; }; Point p = new Point { x=5; y=6; }; -p.x=p.x+1; +p.x++; return p; """); try { parser.parse().iterate(); fail(); } @@ -63,7 +63,7 @@ return p; Parser parser = new Parser(""" struct Point { int x=3, y=4; }; Point p = new Point; -p.x=p.x+1; +p.x++; return p; """); try { parser.parse().iterate(); fail(); } @@ -72,8 +72,8 @@ return p; @Test public void testStructFinal3() { - for( String fields : new String[] { "int ~x; int ~y;", "int[] !x;", "int[] !x;" } ) { - String src = "struct Point { "+fields+" }; Point p = new Point; p.x=p.x+1; return p;"; + for( String fields : new String[] { "var x; var y;", "int[] !x;", "int[] !x;" } ) { + String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;"; try { new Parser(src).parse().iterate(); fail(fields); } catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); } } @@ -82,21 +82,21 @@ return p; @Test public void testStructFinal4() { Parser parser = new Parser(""" -struct Point { int ~x=3; int ~y=4; }; +struct Point { val x=3; val y=4; }; Point p = new Point; -p.x=p.x+1; +p.x++; return p; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } } @Test public void testStructFinal5() { Parser parser = new Parser(""" -struct Point { int x=3; int y=4; }; +struct Point { var x=3; var y=4; }; !Point !p = new Point; -p.x=p.x+1; +p.x++; return p; """); StopNode stop = parser.parse().iterate(); diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java new file mode 100644 index 00000000..64b606d3 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -0,0 +1,549 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.StoreNode; +import org.junit.Test; +import org.junit.Ignore; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter17bTest { + + @Test public void testSinkLoopStores() { + StopNode stop = new Parser(""" + struct Totals { int sum; int visits; }; + val t = new Totals { sum=7; visits=2; }; + val data = new int[4]; + for( int i=0; i { + if( n instanceof StoreNode st && !st._name.equals("[]") ) { + assertEquals(stop.in(0).in(0),st.in(0)); // Both stores at the exit. + stores[0]++; + } + return null; + }); + assertEquals(2,stores[0]); + for( int arg : new int[]{-1,0,1,3,4,5} ) { + long n = Math.max(0,Math.min(arg,4)); + var obj = (Evaluator.Obj)Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(new Object[]{7+n*arg+n*(n-1)/2,2+n},obj.fields()); + } + } + + @Test public void testKeepObservedLoopStore() { + StopNode stop = new Parser(""" + struct S { int x; }; + val a = new S; + val b = new S; + val p = arg ? a : b; + int sum=0; + for( int i=0; i<4; i++ ) { + a.x=i+1; + sum+=p.x; + } + return sum; + """).parse().iterate(); + stop.typeCheck(); + assertEquals(0L,Evaluator.evaluate(stop,0)); + assertEquals(10L,Evaluator.evaluate(stop,1)); + } + + @Ignore + @Test + public void testJig() { + Parser parser = new Parser("return 1;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + + // --------------------------------------------------------------- + @Test + public void testInc0() { + Parser parser = new Parser(""" +return arg++; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return arg;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testInc1() { + Parser parser = new Parser(""" +return arg+++arg++; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return ((arg*2)+1);", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testInc2() { + Parser parser = new Parser(""" +// -(arg--)-(arg--) +return -arg---arg--; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return (-((arg*2)+-1));", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testInc3() { + Parser parser = new Parser(""" +int[] !xs = new int[arg]; +xs[0]++; +xs[1]++; +return xs[0]; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 2)); + } + + @Test + public void testInc4() { + Parser parser = new Parser(""" +u8[] !xs = new u8[1]; +xs[0]--; +return xs[0]; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 255;", stop.toString()); + assertEquals(255L, Evaluator.evaluate(stop, 2)); + } + + @Test + public void testInc5() { + Parser parser = new Parser(""" +struct S { u16 x; }; +!S !s = new S; +s.x--; +return s.x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 65535;", stop.toString()); + assertEquals(65535L, Evaluator.evaluate(stop, 2)); + } + + @Test + public void testInc6() { + Parser parser = new Parser("return --arg;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return (arg+-1);", stop.toString()); + assertEquals(-1L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testInc7() { + Parser parser = new Parser("u8 x=0; return --x;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 255;", stop.toString()); + assertEquals(255L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testInc8() { + Parser parser = new Parser("int x; x+=2; return x+=3;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 5;", stop.toString()); + assertEquals(5L, Evaluator.evaluate(stop, 0)); + } + + // --------------------------------------------------------------- + @Test public void testVar0() { + Parser parser = new Parser("var d; return d;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); } + } + + @Test public void testVar1() { + Parser parser = new Parser("val d; return d;"); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); } + } + + @Test public void testVar2() { + Parser parser = new Parser("int x; x=3; x++; return x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar3() { + Parser parser = new Parser("int x=3; x++; return x; // Ok, primitive so x is mutable despite initializer "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar4() { + Parser parser = new Parser("struct S{int x;}; !S !s; s=new S; s.x++; return s.x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar5() { + Parser parser = new Parser("struct S{int x;}; !S !s; s=new S{x=3;}; s.x++; return s.x; // Ok, no initializer so x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar6() { + Parser parser = new Parser("struct S{int x;}; S s=new S; s.x++; return s.x; // Error initializer so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar7() { + Parser parser = new Parser("struct S{int x;}; S s=new S{x=3;}; s.x++; return s.x; // Error initializer so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar8() { + Parser parser = new Parser("struct S{int x;}; !S !s=new S; s.x++; return s.x; // Ok, has '!' so s.x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar9() { + Parser parser = new Parser("struct S{int x;}; var s=new S; s.x++; return s.x; // Ok, has var so s.x is mutable "); + StopNode stop = parser.parse().iterate(); + assertEquals("return 1;", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testVar10() { + Parser parser = new Parser("struct S{int x;}; ~S s=new S; s.x++; return s.x; // Error, has val so x is immutable "); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar11() { + Parser parser = new Parser(""" +struct Bar { int x; }; +!Bar !bar = new Bar; +bar.x = 3; // Ok, bar is mutable + +struct Foo { !Bar? !bar; int y; }; +!Foo !foo = new Foo { bar = bar; }; +foo.bar = bar; // Ok foo is mutable +foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable + +~Foo xfoo = foo; // Throw away mutability +xfoo.bar.x++; // Error, cannot mutate through xfoo + """); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); } + } + + @Test public void testVar12() { + Parser parser = new Parser(""" +struct Bar { int x; }; +!Bar !bar = new Bar; +bar.x = 3; // Ok, bar is mutable + +struct Foo { !Bar? !bar; int y; }; +!Foo !foo = new Foo; +foo.bar = bar; // Ok bar is mutable +foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable + +~Foo xfoo = foo; // Throw away mutability +int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4 +foo.bar.x++; // Bumps to 5 +int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5 +return x4*10+x5; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 45;", stop.toString()); + assertEquals(45L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testVar13() { + Parser parser = new Parser(""" +int i,i++; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Redefining name 'i'",e.getMessage()); } + } + + + @Test + public void testVar14() { + Parser parser = new Parser(""" +struct B {}; +struct A { !B !b; }; +A x = new A { + return b; // read before init + b = new B; +}; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot read uninitialized field 'b'",e.getMessage()); } + } + + @Test + public void testVar15() { + Parser parser = new Parser(""" +struct B {}; +struct A { !B !b; }; +return new A { + if (arg) b = new B; // Constructor ends with partial init of b +}.b; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("'A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); } + } + + // --------------------------------------------------------------- + @Test + public void testTrinary0() { + Parser parser = new Parser(""" +return arg ? 1 : 2; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region,1,2);", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testTrinary1() { + Parser parser = new Parser(""" +return arg ? 0 : arg; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testTrinary2() { + Parser parser = new Parser(""" +struct Bar { int x; }; +var b = arg ? new Bar : null; +return b ? b.x++ + b.x++ : -1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region,1,-1);", stop.toString()); + assertEquals(-1L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testTrinary3() { + Parser parser = new Parser(""" +struct Bar { int x; }; +var b = arg ? new Bar; +return b ? b.x++ + b.x++ : -1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region,1,-1);", stop.toString()); + assertEquals(-1L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testTrinary4() { + // This test case will benefit from an unzipping transformation + Parser parser = new Parser(""" +struct Bar { !Bar? !next; int x; }; +var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; +return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region,Phi(Region,.x,.x),0);", stop.toString()); + assertEquals( 0L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + assertEquals( 2L, Evaluator.evaluate(stop, 2)); + } + + @Test + public void testTrinary5() { + Parser parser = new Parser(""" +flt f=arg?1:1.2; +return f; // missing widening +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region,1.0,1.2);", stop.toString()); + assertEquals(1.2, Evaluator.evaluate(stop, 0)); + } + + // --------------------------------------------------------------- + @Test + public void testFor0() { + Parser parser = new Parser(""" +int sum=0; +for( int i=0; i=arg ) break; + sum += i; +} +return sum; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", stop.toString()); + assertEquals(3L, Evaluator.evaluate(stop, 3)); + assertEquals(45L, Evaluator.evaluate(stop, 10)); + } + @Test + public void testFor3() { + Parser parser = new Parser(""" +int sum=0; +for( int i=0; i{f1=null,f2=null}", Evaluator.evaluate(stop).toString()); + } + + @Test + public void testForward1() { + Parser parser = new Parser(""" +struct A{ + !B?[]? !nil_array_of_b; + !B?[] not_array_of_b = new !B?[0]; +}; +return new A.not_array_of_b; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return [*B?];", stop.toString()); + assertEquals("Obj<[*B?]>{#=0,[]=[]}", Evaluator.evaluate(stop).toString()); + } + + // --------------------------------------------------------------- + @Test + public void testLinkedList2() { + Parser parser = new Parser(""" +struct LLI { !LLI? !next; int i; }; +!LLI? !head = null; +while( arg-- ) + head = new LLI { next=head; i=arg; }; +int sum=0; +var ptr = head; // A read-only ptr, to be assigned from read-only next fields +for( ; ptr; ptr = ptr.next ) + sum += ptr.i; +return sum; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop,0,(Phi_sum+.i));", stop.toString()); + assertEquals(45L, Evaluator.evaluate(stop, 10)); + } + + @Test + public void sieveOfEratosthenes() { + Parser parser = new Parser( +""" +var ary = new bool[arg], primes = new int[arg]; +var nprimes=0, p=0; +// Find primes while p^2 < arg +for( p=2; p*p < arg; p++ ) { + // skip marked non-primes + while( ary[p] ) p++; + // p is now a prime + primes[nprimes++] = p; + // Mark out the rest non-primes + for( int i = p + p; i < ary#; i += p ) + ary[i] = true; +} +// Now just collect the remaining primes, no more marking +for( ; p < arg; p++ ) + if( !ary[p] ) + primes[nprimes++] = p; +// Copy/shrink the result array +var !rez = new int[nprimes]; +for( int j=0; j>=", ">>>="}; + + private static long apply(String op, long lhs, long rhs) { + return switch (op) { + case "&=" -> lhs & rhs; + case "|=" -> lhs | rhs; + case "^=" -> lhs ^ rhs; + case "<<=" -> lhs << rhs; + case ">>=" -> lhs >> rhs; + case ">>>=" -> lhs >>> rhs; + default -> throw new AssertionError(op); + }; + } + + @Test public void testLocals() { + for (String op : OPS) + for (long arg : new long[]{-8, 0, 5, 64}) { + long expected = apply(op, arg, 3); + check("int x=arg; int r=(x " + op + " 3); return x*10+r;", + arg, expected*11); + } + } + + @Test public void testRhsOnce() { + for (String op : OPS) { + long expected = apply(op, 5, 1); + check("int x=arg; int count=0; int r=(x " + op + + " (count=count+1)); return count*1000+x*10+r;", 5, 1000+expected*11); + } + } + + @Test public void testNarrowingAndChaining() { + check("u8 x=255; x <<= 1; return x;", 0, 254); + check("u8 x=255; x ^= 256; return x;", 0, 255); + check("int x=1; int y=3; x |= y <<= 2; return x*100+y;", 0, 1312); + check("int x=-8; x >>= 65; return x;", 0, -4); + check("int x=-8; x >>>= 65; return x;", 0, Long.MAX_VALUE-3); + } + + @Test public void testExistingOperators() { + check("int x=8; x+=2; x-=1; x*=3; x/=9; return x;", 0, 3); + check("int x=arg; int y=x++; int z=x--; return x*100+y*10+z;", 5, 556); + check("return (arg << 2) + (arg >> 1) + (arg >>> 1);", 8, 40); + check("return arg <= 8;", 8, 1); + check("return arg >= 8;", 8, 1); + } + + @Test public void testRejectedAssignments() { + for (String op : OPS) { + rejected("val x=3; x " + op + " 1; return x;"); + rejected("int x=3; x " + op + " 1.5; return x;"); + } + } + + private static void rejected(String src) { + try { + check(src, 0, 0); + fail("Accepted invalid assignment: " + src); + } catch (RuntimeException e) { + assertEquals("Must report a language error", RuntimeException.class, e.getClass()); + assertNotNull(e.getMessage()); + assertFalse("Must report a language error: " + e, e.getMessage().contains("Not yet implemented")); + } + } + + private static void check(String src, long arg, long expected) { + var stop = new Parser(src).parse().iterate(); + assertEquals(src, Long.valueOf(expected), Evaluator.evaluate(stop, arg)); + } +} diff --git a/src/test/java/com/seaofnodes/simple/MutabilityTest.java b/src/test/java/com/seaofnodes/simple/MutabilityTest.java index 3442c54a..8f99a059 100644 --- a/src/test/java/com/seaofnodes/simple/MutabilityTest.java +++ b/src/test/java/com/seaofnodes/simple/MutabilityTest.java @@ -37,6 +37,12 @@ public class MutabilityTest { bad("struct P { !P? next; int x; }; P p=new P{next=new P;}; p.next.x=7; return 0;"); } + @Test public void testInferencePreservesAccess() { + ok("struct P { int x; }; val p=new P; p.x=7; return p.x;", "7"); + bad("struct P { int x; }; val p=new P; p=new P; return 0;"); + bad("struct P { int x; }; P p=new P; var q=p; q.x=7; return 0;"); + } + @Test public void testConstructorScope() { ok("struct P { int ~x; }; P p=new P{if(arg) x=3; else x=7;}; return p.x;", "7"); bad("int ~outer=1; struct P { int x; }; P p=new P{outer=7;}; return outer;"); -- cgit v1.2.3