From 200162345900136663027b6deae21d72cf30818c Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Thu, 17 Sep 2026 16:39:32 -0700 Subject: Backport string escapes and many op-assign --- chapter17/README.md | 9 ++- .../main/java/com/seaofnodes/simple/Parser.java | 26 ++++++-- .../java/com/seaofnodes/simple/node/ShrNode.java | 5 ++ .../seaofnodes/simple/CompoundAssignmentTest.java | 77 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 chapter17/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java (limited to 'chapter17') diff --git a/chapter17/README.md b/chapter17/README.md index 331cdc3d..cdbca21b 100644 --- a/chapter17/README.md +++ b/chapter17/README.md @@ -25,7 +25,12 @@ Allow pre-increment for identifiers only, for now: `--pc`. ## Operator assignment Along the same lines as post-increment, we now allow `op=` assignment -with semantics similar to other languages. +on local variables with semantics similar to other languages. The supported +operators are `+=`, `-=`, `*=`, `/=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`. +Bitwise and shift assignments require integer operands; `>>=` preserves the sign +while `>>>=` shifts in zeros. The assignment evaluates its right-hand side once, +stores the result narrowed to the variable's type, and yields that stored value. +Assignments associate right-to-left, as in `x |= y <<= 2`. `x += y` @@ -159,4 +164,4 @@ for( int i=0; i new AddNode(rvalue,null); - case '-' -> new SubNode(rvalue,null); - case '*' -> new MulNode(rvalue,null); - case '/' -> new DivNode(rvalue,null); - case 1 -> new AddNode(rvalue,con( 1)); - case (char)-1 -> new AddNode(rvalue,con(-1)); - default -> throw Utils.TODO(); + case '+' -> new AddNode(rvalue,null); + case '-' -> new SubNode(rvalue,null); + case '*' -> new MulNode(rvalue,null); + case '/' -> new DivNode(rvalue,null); + case '&' -> new AndNode(rvalue,null); + case '|' -> new OrNode(rvalue,null); + case '^' -> new XorNode(rvalue,null); + case Lexer.SHL -> new ShlNode(rvalue,null); + case Lexer.SAR -> new SarNode(rvalue,null); + case Lexer.SHR -> new ShrNode(rvalue,null); + case 1 -> new AddNode(rvalue,con( 1)); + case (char)-1 -> new AddNode(rvalue,con(-1)); + default -> throw Utils.TODO(); }; // Return pre-value (x+=1) or post-value (x++) boolean pre = op.in(2)==null; @@ -1403,8 +1409,14 @@ public class Parser { // Next oper= character, or 0. // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535) // Disallow e.g. "arg--1" which should parse as "arg - -1" + // Distinct tags for the multi-character compound operators. + static final char SHL = 2, SAR = 3, SHR = 4; + public char matchOperAssign() { skipWhiteSpace(); + if( match("<<=" ) ) return SHL; + if( match(">>>=") ) return SHR; + if( match(">>=" ) ) return SAR; if( _position+2 >= _input.length ) return 0; char ch0 = (char)_input[_position]; if( "+-/*&|^".indexOf(ch0) == -1 ) return 0; diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 22eb45a0..779193de 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -49,4 +49,9 @@ public class ShrNode extends Node { return null; } @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); } + @Override String err() { + if( !(in(1)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(1)._type; + if( !(in(2)._type instanceof TypeInteger) ) return "Cannot '>>>' " + in(2)._type; + return null; + } } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/chapter17/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java new file mode 100644 index 00000000..0fa4283b --- /dev/null +++ b/chapter17/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java @@ -0,0 +1,77 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +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) { + 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)); + } +} -- cgit v1.2.3