summaryrefslogtreecommitdiff
path: root/chapter27
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 /chapter27
parent5cfb046c8306a81ec40be82a59015f16f654fa93 (diff)
downloadSimple-4051ce7cd26314f01dc8c0237899a742e0ab2084.tar.gz
Simple-4051ce7cd26314f01dc8c0237899a742e0ab2084.zip
Fix ++x in methods
Fix ideaj setups
Diffstat (limited to 'chapter27')
-rw-r--r--chapter27/chapter27.iml31
-rw-r--r--chapter27/src/main/java/com/seaofnodes/simple/Parser.java76
-rw-r--r--chapter27/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java53
3 files changed, 126 insertions, 34 deletions
diff --git a/chapter27/chapter27.iml b/chapter27/chapter27.iml
new file mode 100644
index 00000000..40cc410d
--- /dev/null
+++ b/chapter27/chapter27.iml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/../build/idea/production/chapter27" />
+ <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter27" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ <excludeFolder url="file://$MODULE_DIR$/lib" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="graph" />
+ <orderEntry type="module" module-name="print" />
+ <orderEntry type="module" module-name="isa" />
+ <orderEntry type="module" module-name="isa-eval" scope="TEST" />
+ <orderEntry type="module-library" scope="TEST">
+ <library name="JUnit4">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/lib/junit-4.12.jar!/" />
+ <root url="jar://$MODULE_DIR$/lib/hamcrest-core-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ </component>
+</module>
diff --git a/chapter27/src/main/java/com/seaofnodes/simple/Parser.java b/chapter27/src/main/java/com/seaofnodes/simple/Parser.java
index 145feb15..93ad8f4e 100644
--- a/chapter27/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter27/src/main/java/com/seaofnodes/simple/Parser.java
@@ -1475,18 +1475,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 && !_scope.canInit(n) )
- throw error("Cannot reassign final '"+n._name+"'");
- checkSharedField(n);
- Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true);
- _scope.update(n,expr);
- return expr;
- }
+ Var var = name==null ? null : _scope.lookup(name);
+ if( var!=null && !(var.type() instanceof TypeMemPtr) ) {
+ if( var._final && !_scope.canInit(var) )
+ 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);
@@ -1507,7 +1506,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);
@@ -1555,7 +1557,7 @@ public class Parser {
if( instance(var) ) {
Var selfVar = _scope.lookup("self");
Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true);
- return parsePostfixName(self,id);
+ return parsePostfixName(self,id,pre);
}
// Load local value
@@ -1570,7 +1572,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
@@ -1580,7 +1582,8 @@ public class Parser {
checkCaptureWrite(var);
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,
@@ -1877,8 +1880,10 @@ public class Parser {
* expr ('[' expr ']')* [ [op]= expr ] // Array reference
* </pre>
*/
- private Node parsePostfixName(Node expr, String name) {
- if( privateSelf(expr) ) return parsePrivateField(name);
+ private Node parsePostfixName(Node expr, String name) { return parsePostfixName(expr,name,0); }
+
+ private Node parsePostfixName(Node expr, String name, int pre) {
+ if( privateSelf(expr) ) return parsePrivateField(name,pre);
if( !privateSelf(expr) && mayBeSelf(expr,new java.util.BitSet()) )
throw error("Cannot access self through a merged alias during construction");
@@ -1887,7 +1892,7 @@ public class Parser {
// Sanity check expr for being a reference
if( !(expr._type instanceof TypeMemPtr ptr) )
- return parseUnknownField(expr,name);
+ return parseUnknownField(expr,name,pre);
// Find the field from the Type. Lookup in the base object field names.
TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name);
@@ -1895,7 +1900,7 @@ public class Parser {
ptr = ptr.makeFrom(ptr2._obj); // The declaration fixes the field shape; retain this view's access.
TypeStruct base = ptr._obj;
int fidx = base.find(name);
- if( fidx==-1 && base._open ) return parseUnknownField(expr,name);
+ if( fidx==-1 && base._open ) return parseUnknownField(expr,name,pre);
if( fidx == -1 )
throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
@@ -1918,7 +1923,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 );
@@ -1937,14 +1942,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
@@ -1969,7 +1976,7 @@ public class Parser {
// Syntax fixes the access even when a forward definition or a loop
// leaves the receiver untyped. Resolve its alias and layout in the graph.
- private Node parseUnknownField(Node expr, String name) {
+ private Node parseUnknownField(Node expr, String name, int pre) {
expr.keep();
Node off = new FldOffNode(expr,name).peephole();
if( name.equals("[]") ) {
@@ -1978,7 +1985,7 @@ public class Parser {
off = peep(new AddNode(off,peep(new ShlNode(loc(),index,scale))));
}
off.keep();
- if( matchOpx('=','=') ) {
+ if( pre==0 && matchOpx('=','=') ) {
Node value = parseAsgn().keep();
unknownStore(name,expr,off,value);
expr.unkill();
@@ -1988,7 +1995,7 @@ public class Parser {
Node load = new LoadNode(loc(),name,1,mem(),expr,off);
load.setDef(0,ctrl());
load = peep(load);
- char ch = _lexer.matchOperAssign();
+ char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
Node op = opAssign(ch,load,Type.BOTTOM).keep();
unknownStore(name,expr,off,op);
@@ -1996,7 +2003,7 @@ public class Parser {
// the stored value after conversion; postfix returns the old load.
// Reading our own Store folds once its alias and width are known.
if( postfix(ch) ) load.unkeep();
- else {
+ if( pre!=0 || !postfix(ch) ) {
load = new LoadNode(loc(),name,1,mem(),expr,off);
load.setDef(0,ctrl());
load = peep(load);
@@ -2010,7 +2017,7 @@ public class Parser {
} else load.keep();
expr.unkill();
off.unkill();
- return parsePostfix(load.unkeep());
+ return pre==0 ? parsePostfix(load.unkeep()) : load.unkeep();
}
private void unknownStore(String name, Node ptr, Node off, Node value) {
@@ -2022,12 +2029,12 @@ public class Parser {
// Explicit self.x and an unqualified field name use the same local slot.
// The map also keeps self.x distinct from a parameter/local named x.
- private Node parsePrivateField(String name) {
+ private Node parsePrivateField(String name, int pre) {
Var v=_fieldVars.get(name);
if( v==null ) throw error("Cannot resolve field '"+name+"'");
if( v.type() instanceof TypeFunPtr ctor && ctor.ret() instanceof TypeMem )
throw error("A constructor is only called by new");
- if( matchOpx('=','=') ) {
+ if( pre==0 && matchOpx('=','=') ) {
checkPrivateWrite(name);
Node value=parseAsgn().keep();
checkPublication(value);
@@ -2037,13 +2044,14 @@ public class Parser {
Node value=_scope.in(_scope.update(v,null));
if( value instanceof ConstantNode && (value._type==Type.BOTTOM || value._type==Type.TOP) )
throw error("Cannot read uninitialized field '"+name+"'");
- char ch=_lexer.matchOperAssign();
+ char ch=pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
checkPrivateWrite(name);
Node op=opAssign(ch,value,v.type());
checkPublication(op);
_scope.update(v,op);
- return postfix(ch) ? value.unkeep() : op;
+ if( postfix(ch) ) value.unkeep();
+ return pre==0 && postfix(ch) ? value : op;
}
if( value._type instanceof TypeFunPtr && match("(") )
return parsePostfix(require(functionCall(value,self()),")"));
diff --git a/chapter27/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java b/chapter27/src/test/java/com/seaofnodes/simple/PrefixUpdateTest.java
new file mode 100644
index 00000000..56d553b3
--- /dev/null
+++ b/chapter27/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);
+ }
+}