diff options
18 files changed, 301 insertions, 30 deletions
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java index 1db873e0..ffd4bfe9 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java @@ -530,11 +530,11 @@ public class Parser { // Parse predicate require("("); var pred = require(parseAsgn(), ")"); - return parseTrinary(pred,true,"else"); + return parseTrinary(pred,"else"); } // Parse a conditional expression, merging results. - private Node parseTrinary( Node pred, boolean stmt, String fside ) { + private Node parseTrinary( Node pred, String fside ) { pred.keep(); // IfNode takes current control and predicate @@ -550,7 +550,15 @@ public class Parser { // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.addGuards(ifT,pred,false); // Up-cast predicate - Node lhs = (stmt ? parseStatement() : parseAsgn()).keep(); // Parse true-side + // Parse true-side flavor + Node lhs = switch( fside ) { + case "else" -> parseStatement(); // if( pred ) stmts; + case ":" -> parseAsgn(); // pred ? asgn; + case "&&" -> parseLogical(); // pred && expr + case "||" -> pred; // pred || expr_is_ignored + default -> throw Utils.TODO(); + }; + lhs.keep(); _scope.removeGuards(ifT); // See if a one-sided def was made: "if(pred) int x = 1;" and throw. @@ -566,17 +574,23 @@ public class Parser { // Up-cast predicate, even if not else clause, because predicate can // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` _scope.addGuards(ifF,pred,true); - boolean doRHS = match(fside); - Node rhs = (doRHS - ? (stmt ? parseStatement() : parseAsgn()) - : con(lhs._type.makeZero())).keep(); + // Parse false-side flavor + boolean doRHS = false; // RHS is optional for if/else and trinary + Node rhs = switch( fside ) { + case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); + case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); + case "&&" -> rhs = pred; + case "||" -> rhs = parseLogical(); + default -> throw Utils.TODO(); + }; + rhs.keep(); _scope.removeGuards(ifF); if( doRHS ) fScope = _scope; pred.unkeep(); // Check the trinary widening int/flt - if( !stmt ) { + if( !fside.equals("else") ) { rhs = widenInt( rhs.unkeep(), lhs._type ).keep(); lhs = widenInt( lhs.unkeep(), rhs._type ).keep(); } @@ -593,7 +607,7 @@ public class Parser { Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type).glb(false),r,lhs.unkeep(),rhs.unkeep())); // Immediately fail e.g. `arg ? 7 : ptr` ParseException err; - if( !stmt && (err=ret.err()) !=null ) throw err; + if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; r.peephole(); return ret; } @@ -993,8 +1007,8 @@ public class Parser { * @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { - Node expr = parseBitwise(); - return match("?") ? parseTrinary(expr,false,":") : expr; + Node expr = parseLogical(); + return match("?") ? parseTrinary(expr,":") : expr; } /** @@ -1005,13 +1019,25 @@ public class Parser { * </pre> * @return a bitwise expression {@link Node}, never {@code null} */ + + private Node parseLogical() { + Node lhs = parseBitwise(); + while (true) { + if( false ) ; + else if( match("&&") ) lhs = parseTrinary(lhs, "&&"); + else if( match("||") ) lhs = parseTrinary(lhs, "||"); + else break; + } + return lhs; + } + private Node parseBitwise() { Node lhs = parseComparison(); while( true ) { if( false ) ; - else if( match("&") ) lhs = new AndNode(loc(),lhs,null); - else if( match("|") ) lhs = new OrNode(loc(),lhs,null); - else if( match("^") ) lhs = new XorNode(loc(),lhs,null); + else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null); + else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null); + else if( match ("^") ) lhs = new XorNode(loc(),lhs,null); else break; lhs.setDef(2,parseComparison()); lhs = peep(lhs); @@ -1701,6 +1727,7 @@ public class Parser { private boolean match (String syntax) { return _lexer.match (syntax); } // Match must be "exact", not be followed by more id letters private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + private boolean matchOp(char c0 ) { return _lexer.matchOp(c0); } private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } // Return true and do NOT skip if 'ch' is next private boolean peek(char ch) { return _lexer.peek(ch); } @@ -1876,6 +1903,15 @@ public class Parser { _position -= syntax.length(); return false; } + // Match this char, and the next char must be different. + // Handles '&&' vs '&' + boolean matchOp( char c0 ) { + skipWhiteSpace(); + if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c0 ) + return false; + inc(); + return true; + } // Match these two characters in a row boolean matchOpx(char c0, char c1) { skipWhiteSpace(); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index caa7ff44..6c958f32 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -39,6 +39,8 @@ public class CodeGen { // --------------------------- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L, true ); } + public CodeGen( String src, TypeInteger arg) { this(src, arg==null ? TypeInteger.BOT : arg, 123L, true ); } + public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) { CODE = this; if( reset ) Type.reset(); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index b75852a4..d95af697 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -86,6 +86,11 @@ public class CallEndNode extends CFGNode implements MultiNode { fun.setDef(2,call.ctrl()); // Bypass the Call; fun.ret().setDef(3,null); // Return is folding also CodeGen.CODE.addAll(fun._outputs); + // Repeat defs 1 layer down, for users of Parm (Phis) + for( Node parm : fun._outputs ) + if( parm instanceof ParmNode ) + CodeGen.CODE.addAll(parm._outputs); + // Inlining immediately blows all cache idepth fields past the inline point. // Bump the global version number invalidating them en-masse. CodeGen.CODE.invalidateIDepthCaches(); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index a2cb9bba..427cf2e2 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -42,9 +42,9 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize { } @Override public void asm(CodeGen code, SB sb) { - sb.p(_name); + sb.p(_name).p(" "); for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i+2))).p(" "); + sb.p(code.reg(arg(i+2))).p(", "); sb.unchar(2); } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java index a3e1cf19..c84aa9e5 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java @@ -25,7 +25,7 @@ public class CallRRARM extends CallNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(fptr())).p(" "); for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i))).p(" "); + sb.p(code.reg(arg(i))).p(", "); sb.unchar(2); } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index a676ba60..bd5dabe5 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -57,7 +57,6 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize { sb.p(_name).p(" "); for( int i=0; i<nargs(); i++ ) sb.p(code.reg(arg(i+2))).p(", "); - sb.unchar(2).p(" ").p(code.reg(fptr())); - + sb.unchar(2); } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index ac36ed12..298789e8 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -25,7 +25,7 @@ public class CallRRISC extends CallNode implements MachNode { @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(fptr())).p(" "); for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(arg(i))).p(" "); + sb.p(code.reg(arg(i))).p(", "); sb.unchar(2); } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index ffdce7df..d74e3833 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -42,7 +42,7 @@ public class CallX86 extends CallNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { sb.p(_name).p(" "); for( int i=0; i<nargs(); i++ ) - sb.p(code.reg(in(i+2))).p(" "); + sb.p(code.reg(in(i+2))).p(", "); sb.unchar(2); } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 394ba336..13322d3d 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -180,7 +180,7 @@ return cc.cz; @Test public void testHelloWorld() throws IOException { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/helloWorld.smp")); - TestC.run(src,TestC.CALL_CONVENTION,null,null,"build/objs/helloWorld","","Hello, World!",0); + TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/helloWorld","","Hello, World!",0); // Evaluate on RISC5 emulator EvalRisc5 R5 = TestRisc5.build("helloWorld", 0, 2, false); @@ -221,7 +221,7 @@ return sum(is); @Test @Ignore public void testEcho() throws IOException { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/echo.smp")); - TestC.run(src,TestC.CALL_CONVENTION,null,null,"build/objs/echo","","",0); + TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/echo","","",0); // Evaluate on RISC5 emulator EvalRisc5 R5 = TestRisc5.build("echo", 0, 2, false); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java index 20a506fb..12e089fc 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.TypeInteger; import java.io.IOException; import java.nio.file.Files; @@ -32,6 +33,130 @@ public class Chapter23Test { } @Test + public void testOr() throws IOException { + TestC.runS("or1","Or",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("or1", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("Or",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("or1", 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("Or",arm._stdout.toString()); + } + + @Test + public void testAnd() throws IOException { + TestC.runS("and1","And",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("and1", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("And",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("and1", 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("And",arm._stdout.toString()); + } + + @Test + public void testAndPtr() throws IOException { + String src = +""" +struct S { S? fld; }; +val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; +return ptr && ptr.fld ? "true" : "false"; + """; + CodeGen code = new CodeGen(src).parse().opto().typeCheck(); + assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0)); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("ptrand", 1, 8, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("false",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("ptrand", 1, 8, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("false",arm._stdout.toString()); + } + + // conditional side effect + @Test + public void testCondSideEffAnd() throws IOException { + TestC.runS("and2","Effected",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("and2", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("Effected",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("and2", 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("Effected",arm._stdout.toString()); + } + + + @Test + public void testCondSideEffOr() throws IOException { + TestC.runS("or2","Effected",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("or2", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("Effected",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("or2", 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("Effected",arm._stdout.toString()); + } + + // test it with function calls + @Test + public void testFuncCall() throws IOException { + TestC.runS("and3","Or",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build("and3", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("Or",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("and3", 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("Or",arm._stdout.toString()); + } + + @Test public void testFRefFields() { String src = """ diff --git a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java index ec97eab0..c719b8e8 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -573,11 +573,16 @@ public class EvalArm64 { break; } case 0xD6: { + if( (ir & 0xFFFF) != 0 ) throw Utils.TODO(); + if( ((ir>>16) & 0xFF) != 0x5F ) throw Utils.TODO(); // 0x3F is register call rdid = -1; + int rn = ir << 22 >> 27; + assert rn == 0; + pc = (int)regs[30]; // Return to zero breaks simulation - if(regs[30] == 0) - break outer; - throw Utils.TODO(); + if( pc==0 ) break outer; + pc -= 4; // Undo post-increment + break; } case 0xF1: { diff --git a/chapter23/src/test/java/com/seaofnodes/simple/TestC.java b/chapter23/src/test/java/com/seaofnodes/simple/TestC.java index 9df3dc68..dbe9f02f 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/TestC.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/TestC.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.util.Utils; import java.io.IOException; @@ -29,7 +30,7 @@ public abstract class TestC { } // Compile and run a simple program - public static void run( String dir, String file, String expected, int spills ) throws IOException { + public static void run( String dir, String file, TypeInteger arg, String expected, int spills, boolean standalone) throws IOException { // Files String cfile = dir+"/"+file+".c" ; String sfile = dir+"/"+file+".smp"; @@ -37,15 +38,28 @@ public abstract class TestC { // Compile and export Simple String src = Files.readString(Path.of(sfile)); - run(src,CALL_CONVENTION,"",cfile,efile,"S",expected,spills); + run(src,CALL_CONVENTION,arg, "",standalone? null: cfile,efile,"S",expected,spills); + } + public static void run(String dir, String file, String expected, int spills ) throws IOException { + run(dir, file,null, expected, spills, false); + } + + public static void runS(String file, String expected, int spills ) throws IOException { + run("src/test/java/com/seaofnodes/simple/progs", file, null, expected, spills, true); } - public static void run( String src, String simple_conv, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException { + public static void runS(String file, TypeInteger arg, String expected, int spills ) throws IOException { + run("src/test/java/com/seaofnodes/simple/progs", file, arg, expected, spills, true); + } + + + public static void run( String src, String simple_conv, TypeInteger arg, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException { String bin = efile+xtn; String obj = bin+".o"; String exe = OS.startsWith("Windows") ? bin+".exe" : bin; // Compile simple, emit ELF - CodeGen code = new CodeGen(src).driver( CPU_PORT, simple_conv, obj); + CodeGen code = new CodeGen(src, arg).driver( CPU_PORT, simple_conv, obj); + String result = gcc(obj, c_conv, cfile, false, exe ); assertEquals(expected,result); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/and1.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/and1.smp new file mode 100644 index 00000000..ef4c904f --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/and1.smp @@ -0,0 +1,10 @@ +// -*- mode: java; -*- +int a = 1; +int b = 1; + +if(a && b) { + sys.io.p("And"); +} else { + sys.io.p("Or"); +} +return 0; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/and2.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/and2.smp new file mode 100644 index 00000000..2a463c94 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/and2.smp @@ -0,0 +1,16 @@ +// -*- mode: java; -*- +int a = 1; +int b = 1; + +int x=1; +int y=1; +int z=0; + +int g = x++ && y++ && z++; + +if(x == 2 && y == 2 && z == 1 && g == 0) { + sys.io.p("Effected"); +} else { + sys.io.p("Not effected"); +} +return 0; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/and3.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/and3.smp new file mode 100644 index 00000000..aadbdfed --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/and3.smp @@ -0,0 +1,14 @@ +// -*- mode: java; -*- +int a = 1; +int b = 1; + +var sq = { int x -> + x*x; +}; + +if(a && sq(0)) { + sys.io.p("And"); +} else { + sys.io.p("Or"); +} +return 0; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/or1.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/or1.smp new file mode 100644 index 00000000..f5f526c3 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/or1.smp @@ -0,0 +1,12 @@ +// -*- mode: java; -*- +int a = 1; +int b = 0; + +if(a++ || b++ ) { + if(b == 0 && a == 2) { + sys.io.p("Or"); + } +} else{ + sys.io.p("And"); +} +return 0; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/or2.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/or2.smp new file mode 100644 index 00000000..ac688b5a --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/or2.smp @@ -0,0 +1,23 @@ +// -*- mode: java; -*- +int a = 1; +int b = 1; + +int x = -1; +int y=1; +int z= -1; + +int g = x++ || y++ || z++; + +int switch = 0; +if(x == 4 || y == 4 || z == 4) { + switch = -1; +} else { + switch = 1; +} +int cd = 1; +if((x == 0 && y == 1 && z==-1) && switch && g) { + sys.io.p("Effected"); +} else { + sys.io.p("Not effected"); +} +return 0; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/ptrand.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/ptrand.smp new file mode 100644 index 00000000..9d7bce88 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/ptrand.smp @@ -0,0 +1,10 @@ +struct S { S? fld; }; + +val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};}; +if( ptr && ptr.fld ) { + sys.io.p("true"); +} else { + sys.io.p("false"); +} + +return 0; |
