summaryrefslogtreecommitdiff
path: root/chapter22/src/test/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-17 16:39:32 -0700
committerCliff Click <cliffc@acm.org>2026-09-17 16:39:32 -0700
commit200162345900136663027b6deae21d72cf30818c (patch)
treea9b26f38b5f920f61837327ab56f97c5f9a01016 /chapter22/src/test/java
parent42681f41318796afc58bc14ce04620b7c5c7eb18 (diff)
downloadSimple-200162345900136663027b6deae21d72cf30818c.tar.gz
Simple-200162345900136663027b6deae21d72cf30818c.zip
Backport string escapes and many op-assign
Diffstat (limited to 'chapter22/src/test/java')
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java94
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/LiteralTest.java65
2 files changed, 159 insertions, 0 deletions
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/chapter22/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java
new file mode 100644
index 00000000..0d67d3c5
--- /dev/null
+++ b/chapter22/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java
@@ -0,0 +1,94 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class CompoundAssignmentTest {
+ private static final String[] OPS = {"&=", "|=", "^=", "<<=", ">>=", ">>>="};
+
+ 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) {
+ assertTrue("Must report a language error: " + e, e instanceof Parser.ParseException);
+ assertNotNull(e.getMessage());
+ assertFalse("Must report a language error: " + e, e.getMessage().contains("Not yet implemented"));
+ }
+ }
+
+ @Test public void testFields() {
+ for (String op : OPS) {
+ long expected = apply(op, -8, 3);
+ check("struct S { int x; }; S !s=new S; s.x=arg; int r=(s.x " +
+ op + " 3); return s.x*10+r;", -8, expected*11);
+ }
+ }
+
+ @Test public void testArrayAddressAndRhsOnce() {
+ for (String op : OPS) {
+ long expected = apply(op, 5, 1);
+ check("int[] !a=new int[2]; a[0]=arg; int i=0; int count=0; " +
+ "int r=(a[i++] " + op + " (count=count+1)); " +
+ "return i*1000+count*100+a[0]*10+r;", 5, 1100+expected*11);
+ }
+ }
+
+ private static void check(String src, long arg, long expected) {
+ var code = new CodeGen(src).parse().opto().typeCheck();
+ assertEquals(src, Long.toString(expected), Eval2.eval(code, arg));
+ }
+}
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/LiteralTest.java b/chapter22/src/test/java/com/seaofnodes/simple/LiteralTest.java
new file mode 100644
index 00000000..2375f213
--- /dev/null
+++ b/chapter22/src/test/java/com/seaofnodes/simple/LiteralTest.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.TypeConAryB;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class LiteralTest {
+ private static final String[] ESCAPES = {"\\0", "\\n", "\\t", "\\r", "\\\\", "\\\"", "\\'"};
+ private static final int[] VALUES = {0, 10, 9, 13, 92, 34, 39};
+
+ @Test public void testStringEscapes() {
+ for (int i = 0; i < ESCAPES.length; i++)
+ assertString(ESCAPES[i], new byte[]{(byte)VALUES[i]});
+ assertString("a\\\"b\\\\c\\0d", new byte[]{97, 34, 98, 92, 99, 0, 100});
+ assertString("", new byte[]{});
+ assertString("plain", new byte[]{112, 108, 97, 105, 110});
+ }
+
+ private static void assertString(String text, byte[] expected) {
+ var code = new CodeGen("return \"" + text + "\";").parse().opto().typeCheck();
+ TypeMemPtr ptr = (TypeMemPtr)code.expr()._type;
+ TypeConAryB bytes = (TypeConAryB)ptr._obj._con;
+ assertArrayEquals(text, expected, bytes._ary);
+ }
+
+ @Test public void testCharacterEscapes() {
+ for (int i = 0; i < ESCAPES.length; i++)
+ assertCharacter(ESCAPES[i], VALUES[i]);
+ assertCharacter("A", 65);
+ }
+
+ private static void assertCharacter(String text, int expected) {
+ var code = new CodeGen("return '" + text + "';").parse().opto().typeCheck();
+ assertEquals(text, expected, ((TypeInteger)code.expr()._type).value());
+ }
+
+ @Test public void testMalformedStrings() {
+ assertBadLiteral("return \"\\q\";", "Unknown string escape");
+ assertBadLiteral("return \"abc", "Unclosed string");
+ assertBadLiteral("return \"abc\\", "Unclosed string");
+ // The quote closes the string at EOF; the missing token is the semicolon.
+ assertBadLiteral("return \"plain\"", ";");
+ }
+
+ @Test public void testMalformedCharacters() {
+ assertBadLiteral("return '\\q';", "Unknown character escape");
+ assertBadLiteral("return '", "Unclosed character");
+ assertBadLiteral("return '\\", "Unclosed character");
+ assertBadLiteral("return '';", "Syntax error");
+ assertBadLiteral("return 'ab';", "Syntax error");
+ assertBadLiteral("return 'a", "Syntax error");
+ }
+
+ private static void assertBadLiteral(String src, String message) {
+ try {
+ new CodeGen(src).parse();
+ fail("Accepted malformed literal: " + src);
+ } catch (Parser.ParseException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains(message));
+ }
+ }
+}