summaryrefslogtreecommitdiff
path: root/chapter24a/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-25 09:51:44 -0700
committerCliff Click <cliffc@acm.org>2026-10-25 09:51:44 -0700
commit4051ce7cd26314f01dc8c0237899a742e0ab2084 (patch)
tree29cb9b47e0461c34be1797ac36dd31e8f0b10c09 /chapter24a/src
parent5cfb046c8306a81ec40be82a59015f16f654fa93 (diff)
downloadSimple-4051ce7cd26314f01dc8c0237899a742e0ab2084.tar.gz
Simple-4051ce7cd26314f01dc8c0237899a742e0ab2084.zip
Fix ++x in methods
Fix ideaj setups
Diffstat (limited to 'chapter24a/src')
-rw-r--r--chapter24a/src/main/java/com/seaofnodes/simple/Parser.java52
-rw-r--r--chapter24a/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java53
2 files changed, 82 insertions, 23 deletions
diff --git a/chapter24a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24a/src/main/java/com/seaofnodes/simple/Parser.java
index 81c7be32..7a67c096 100644
--- a/chapter24a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter24a/src/main/java/com/seaofnodes/simple/Parser.java
@@ -1246,19 +1246,17 @@ public class Parser {
// Pre-dec/pre-inc
int old = pos();
if( match("--") || match("++") ) {
- int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post
+ int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
+ int start = pos();
String name = _lexer.matchId();
- if( name!=null ) {
- Var n = _scope.lookup(name);
- if( n != null && !(n.type() instanceof TypeMemPtr) ) {
- if( n._final )
- throw error("Cannot reassign final '"+n._name+"'");
- Node expr = n.type() instanceof TypeFloat
- ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta))))
- : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
- _scope.update(n,expr);
- return expr;
- }
+ Var var = name==null ? null : _scope.lookup(name);
+ if( var!=null && !(var.type() instanceof TypeMemPtr) ) {
+ if( var._final )
+ throw error("Cannot reassign final '"+var._name+"'");
+ // Resolve fields just as a read or postfix update does. Updating
+ // their declaration's local slot would silently lose the Store.
+ pos(start);
+ return parsePrimary(delta);
}
// Reset, try again
pos(old);
@@ -1279,7 +1277,10 @@ public class Parser {
* </pre>
* @return a primary {@link Node}, never {@code null}
*/
- private Node parsePrimary() {
+ private Node parsePrimary() { return parsePrimary(0); }
+
+ // pre is +/-1 for a prefix update of a known name, zero otherwise.
+ private Node parsePrimary(int pre) {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
if( _lexer.peek('"') ) return newString(parseString());
if( matchx("true" ) ) return con(1);
@@ -1326,7 +1327,7 @@ public class Parser {
// Else safety check for accessing fields out of function scope.
if( instance(var) ) {
Node self = _scope.in(_scope.lookup("self")); // Insert self reference
- return parsePostfixName(self,id);
+ return parsePostfixName(self,id,pre);
}
// Load local value
@@ -1337,7 +1338,7 @@ public class Parser {
else throw error("Cannot read uninitialized field '"+id+"'");
// Check for assign-update, x += e0;
- char ch = _lexer.matchOperAssign();
+ char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch==0 ) // Normal primary, check for postfix updates
return parsePostfix(rvalue);
// Assign-update direct into Scope
@@ -1345,7 +1346,8 @@ public class Parser {
throw error("Cannot reassign final '"+var._name+"'");
Node op = opAssign(ch,rvalue, var.type() );
_scope.update(var,op);
- return postfix(ch) ? rvalue.unkeep() : op;
+ if( postfix(ch) ) rvalue.unkeep();
+ return pre==0 && postfix(ch) ? rvalue : op;
}
// true if instance variable lookup (var is in any number of nested blocks,
@@ -1574,7 +1576,9 @@ public class Parser {
* expr ('[' expr ']')* [ [op]= expr ] // Array reference
* </pre>
*/
- private Node parsePostfixName(Node expr, String name) {
+ private Node parsePostfixName(Node expr, String name) { return parsePostfixName(expr,name,0); }
+
+ private Node parsePostfixName(Node expr, String name, int pre) {
if( expr._type==Type.NIL )
throw error("Accessing unknown field '" + name + "' from 'null'");
@@ -1609,7 +1613,7 @@ public class Parser {
: off(base,name)).keep();
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
- if( matchOpx('=','=') ) {
+ if( pre==0 && matchOpx('=','=') ) {
Node val = parseAsgn().keep();
Node lift = liftExpr( val, tf, false );
@@ -1628,14 +1632,16 @@ public class Parser {
load = peep(load);
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
- char ch = _lexer.matchOperAssign();
+ char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
Node op = opAssign(ch,load,tf);
+ // A narrow Store may drop its conversion; prefix still returns it.
+ if( pre!=0 ) op.keep();
store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null);
- load = postfix(ch) ? load.unkeep() : op;
- // And use the original loaded value as the result
- return load;
- //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN
+ if( pre!=0 ) op.unkeep();
+ if( postfix(ch) ) load.unkeep();
+ // Prefix returns the new value; postfix returns the old load.
+ return pre==0 && postfix(ch) ? load : op;
} else {
off.unkill();
// Method call: Loaded a function from a named field, AND the base
diff --git a/chapter24a/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java b/chapter24a/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java
new file mode 100644
index 00000000..56d553b3
--- /dev/null
+++ b/chapter24a/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java
@@ -0,0 +1,53 @@
+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 PrefixUpdateTest {
+ @Test public void testMethodFieldWrites() {
+ String[] fields = {"int x=0", "int x=3", "u8 x=0", "f32 x=0"};
+ String[] updates = {"++x", "--x", "++x", "++x"};
+ String[] results = {"221", "112", "221", "221.0"};
+ for(int i=0;i<fields.length;i++)
+ for(int seed:new int[]{2,13,123}) {
+ String src="struct C { "+fields[i]+"; val change_noInline={ -> "+updates[i]+"; }; }; "
+ +"var c=new C; val a=c.change_noInline(); val b=c.change_noInline(); return a+10*b+100*c.x;";
+ CodeGen code=new CodeGen(src,TypeInteger.BOT,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals(fields[i],results[i],Eval2.eval(code,0));
+ }
+ }
+
+ @Test public void testLocalAndFinalUpdates() {
+ CodeGen code=new CodeGen("int x=arg; return ++x*100 + --x*10 + x;").driver(CodeGen.Phase.TypeCheck);
+ assertEquals("433",Eval2.eval(code,3));
+ code=new CodeGen("struct C { int x=7; val f={int x -> ++x;}; }; var c=new C; return c.f(3)+10*c.x;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("74",Eval2.eval(code,0));
+ for(String src:new String[]{"val x=1; return ++x;",
+ "struct C { val x=1; val f={ -> ++x; }; }; var c=new C; return c.f();"}) {
+ try {
+ new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ fail("final update");
+ } catch(RuntimeException e) {
+ assertTrue(e.getMessage(),e.getMessage().contains("final"));
+ }
+ }
+ }
+
+ @Test public void testNativeByteOverflow() throws java.io.IOException {
+ String src="val prefixUpdate={int arg -> "
+ +"struct C { u8 x=255; val inc_noInline={ -> ++x; }; }; "
+ +"var c=new C; val a=c.inc_noInline(); val b=c.inc_noInline(); return a+10*b+100*c.x; };";
+ var dir=java.nio.file.Path.of("build/objs");
+ java.nio.file.Files.createDirectories(dir);
+ var driver=dir.resolve("prefixUpdate.c");
+ java.nio.file.Files.writeString(driver,"""
+ __attribute__((CALL_CONV)) extern long long prefixUpdate(long long);
+ int main(void) { return prefixUpdate(0)!=110; }
+ """);
+ String abi=TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi";
+ TestC.run(src,TestC.CALL_CONVENTION,null,abi,driver.toString(),"build/objs/prefixUpdate","S","",-1);
+ }
+}