diff --git a/TestHScript.hx b/TestHScript.hx index db279557..4fbc881f 100644 --- a/TestHScript.hx +++ b/TestHScript.hx @@ -131,6 +131,31 @@ class TestHScript extends TestCase { assertScript("pt2?.pt?.x", 10, vars); } + function testNullCoalescing():Void { + var pt = {x: 10}; + var vars = { + ptnull: null, + pt: pt, + pt2null: {pt: null}, + pt2: {pt: pt}, + valuenull: null, + value: 10 + } + assertScript("ptnull?.x ?? 5", 5, vars); + assertScript("pt?.x ?? 5", 10, vars); + assertScript("pt2null?.pt ?? 5", 5, vars); + assertScript("pt2null?.pt?.x ?? 5", 5, vars); + assertScript("pt2?.pt ?? 5", pt, vars); + assertScript("pt2?.pt?.x ?? 5", 10, vars); + #if cpp + assertScript('valuenull ??${"="} 5; valuenull', 5, vars); + assertScript('value ??${"="} 5; value', 10, vars); + #else + assertScript("valuenull ??= 5; valuenull", 5, vars); + assertScript("value ??= 5; value", 10, vars); + #end + } + function testIsOperator():Void { var vars = { String: String, diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 46632390..170cc838 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -122,6 +122,7 @@ class Interp { assignOp("*=",function(v1:Float,v2:Float) return v1 * v2); assignOp("/=",function(v1:Float,v2:Float) return v1 / v2); assignOp("%=",function(v1:Float,v2:Float) return v1 % v2); + assignOp(#if cpp "??"+"=" #else "??=" #end,function(v1:Dynamic,v2:Dynamic) if ( v1 == null ) return v1 = v2 else return v1); assignOp("&=",function(v1,v2) return v1 & v2); assignOp("|=",function(v1,v2) return v1 | v2); assignOp("^=",function(v1,v2) return v1 ^ v2); diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 12c51e7a..ed35ed00 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -33,6 +33,7 @@ enum Token { TBrClose; TDot; TQuestionDot; + TQuestionQuestion; TComma; TSemicolon; TBkOpen; @@ -127,7 +128,7 @@ class Parser { ["..."], ["&&"], ["||"], - ["=","+=","-=","*=","/=","%=","<<=",">>=",">>>=","|=","&=","^=","=>"], + ["=","+=","-=","*=","/=","%=",#if cpp "??"+"=" #else "??=" #end,"<<=",">>=",">>>=","|=","&=","^=","=>"], ["->"] ]; #if haxe3 @@ -821,6 +822,18 @@ class Parser { )) ]),pmin(e1)); return parseExprNext(e); + case TQuestionQuestion: + var e2 = parseExpr(); + var tmp = "__a_" + (uid++); + var e = mk(EBlock([ + mk(EVar(tmp, null, e1), pmin(e1), pmax(e1)), + mk(ETernary( + mk(EBinop("==", mk(EIdent(tmp),pmin(e1),pmax(e1)), mk(EIdent("null"),pmin(e1),pmax(e1)))), + e2, + mk(EIdent(tmp),pmin(e1),pmax(e1)) + )) + ]),pmin(e1)); + return parseExprNext(e); case TPOpen: return parseExprNext(mk(ECall(e1,parseExprList(TPClose)),pmin(e1))); case TBkOpen: @@ -1508,6 +1521,12 @@ class Parser { char = readChar(); if( char == ".".code ) return TQuestionDot; + else if ( char == "?".code ) { + char = readChar(); + if ( char == "=".code ) + return TOp(#if cpp "??"+"=" #else "??=" #end); + return TQuestionQuestion; + } this.char = char; return TQuestion; case ":".code: return TDoubleDot; @@ -1738,6 +1757,7 @@ class Parser { case TBrClose: "}"; case TDot: "."; case TQuestionDot: "?."; + case TQuestionQuestion: "??"; case TComma: ","; case TSemicolon: ";"; case TBkOpen: "[";