Support ?. operator. Close #188.
This commit is contained in:
@@ -451,8 +451,8 @@ class Helpers {
|
||||
innerExp.def;
|
||||
case MetaExp(meta, innerExp):
|
||||
MetaExp(meta, removeTypeAnnotations(innerExp));
|
||||
case FieldExp(field, innerExp):
|
||||
FieldExp(field, removeTypeAnnotations(innerExp));
|
||||
case FieldExp(field, innerExp, safe):
|
||||
FieldExp(field, removeTypeAnnotations(innerExp), safe);
|
||||
case KeyValueExp(keyExp, valueExp):
|
||||
KeyValueExp(removeTypeAnnotations(keyExp), removeTypeAnnotations(valueExp));
|
||||
case Unquote(innerExp):
|
||||
@@ -659,8 +659,8 @@ class Helpers {
|
||||
ListExp(evalUnquoteLists(elements, innerRunAtCompileTime).map(recurse));
|
||||
case TypedExp(type, innerExp):
|
||||
TypedExp(type, recurse(innerExp));
|
||||
case FieldExp(field, innerExp):
|
||||
FieldExp(field, recurse(innerExp));
|
||||
case FieldExp(field, innerExp, safe):
|
||||
FieldExp(field, recurse(innerExp), safe);
|
||||
case KeyValueExp(keyExp, valueExp):
|
||||
KeyValueExp(recurse(keyExp), recurse(valueExp));
|
||||
case Unquote(innerExp):
|
||||
@@ -686,8 +686,8 @@ class Helpers {
|
||||
function callSymbol(symbol:String, args:Array<ReaderExp>) {
|
||||
return call(_symbol(symbol), args);
|
||||
}
|
||||
function field(f:String, exp:ReaderExp) {
|
||||
return FieldExp(f, exp).withPosOf(posRef);
|
||||
function field(f:String, exp:ReaderExp, ?safe:Bool) {
|
||||
return FieldExp(f, exp, safe != null && safe).withPosOf(posRef);
|
||||
}
|
||||
function list(exps:Array<ReaderExp>) {
|
||||
return ListExp(exps).withPosOf(posRef);
|
||||
|
@@ -653,12 +653,12 @@ class Kiss {
|
||||
} catch (err:Exception) {
|
||||
throw KissError.fromExp(exp, 'Haxe parse error: $err');
|
||||
};
|
||||
case FieldExp(field, innerExp):
|
||||
case FieldExp(field, innerExp, safe):
|
||||
var convertedInnerExp = convert(innerExp);
|
||||
if (macroExpandOnly)
|
||||
Left(FieldExp(field, left(convertedInnerExp)).withPosOf(exp));
|
||||
Left(FieldExp(field, left(convertedInnerExp), safe).withPosOf(exp));
|
||||
else
|
||||
Right(EField(right(convertedInnerExp), field).withMacroPosOf(exp));
|
||||
Right(EField(right(convertedInnerExp), field, if (safe) Safe else Normal).withMacroPosOf(exp));
|
||||
case KeyValueExp(keyExp, valueExp) if (!macroExpandOnly):
|
||||
Right(EBinop(OpArrow, right(convert(keyExp)), right(convert(valueExp))).withMacroPosOf(exp));
|
||||
case Quasiquote(innerExp) if (!macroExpandOnly):
|
||||
|
@@ -109,8 +109,10 @@ class Macros {
|
||||
k.doc("apply", 2, 2, '(apply <func> <argList>)' );
|
||||
macros["apply"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
var isSafe = false;
|
||||
var callOn = switch (exps[0].def) {
|
||||
case FieldExp(field, exp):
|
||||
case FieldExp(field, exp, safe):
|
||||
isSafe = safe;
|
||||
exp;
|
||||
default:
|
||||
b.symbol("null");
|
||||
@@ -122,10 +124,15 @@ class Macros {
|
||||
exps[0];
|
||||
};
|
||||
var args = exps[1];
|
||||
b.call(
|
||||
var exp = b.call(
|
||||
b.symbol("Reflect.callMethod"), [
|
||||
callOn, func, args
|
||||
]);
|
||||
|
||||
if (isSafe)
|
||||
exp = b.callSymbol("when", [callOn, exp]);
|
||||
|
||||
exp;
|
||||
};
|
||||
|
||||
k.doc("range", 1, 3, '(range <?min> <max> <?step>)');
|
||||
|
@@ -113,7 +113,9 @@ class Reader {
|
||||
readTable["?"] = (stream:Stream, k) -> CallExp(Symbol("Prelude.truthy").withPos(stream.position()), [assertRead(stream, k)]);
|
||||
|
||||
// Lets you dot-access a function result without binding it to a name
|
||||
readTable["."] = (stream:Stream, k) -> FieldExp(nextToken(stream, "a field name"), assertRead(stream, k));
|
||||
readTable["."] = (stream:Stream, k) -> FieldExp(nextToken(stream, "a field name"), assertRead(stream, k), false);
|
||||
// Safe version (new feature of Haxe 4.3.0)
|
||||
readTable["?."] = (stream:Stream, k) -> FieldExp(nextToken(stream, "a field name"), assertRead(stream, k), true);
|
||||
|
||||
// Lets you construct key-value pairs for map literals or for-loops
|
||||
readTable["=>"] = (stream:Stream, k) -> KeyValueExp(assertRead(stream, k), assertRead(stream, k));
|
||||
@@ -214,13 +216,26 @@ class Reader {
|
||||
Symbol(token);
|
||||
} else {
|
||||
var tokenParts = token.split(".");
|
||||
var fieldExpVal = tokenParts.shift();
|
||||
var fieldExp = Symbol(fieldExpVal);
|
||||
var fieldExpVal = tokenParts[0];
|
||||
if (fieldExpVal.endsWith("?"))
|
||||
fieldExpVal = fieldExpVal.substr(0, fieldExpVal.length - 1);
|
||||
var fieldExp = null;
|
||||
var firstPart = true;
|
||||
var nextIsSafe = false;
|
||||
if (k.identAliases.exists(fieldExpVal)) {
|
||||
while (tokenParts.length > 0) {
|
||||
fieldExp = FieldExp(tokenParts.shift(), fieldExp.withPos(position));
|
||||
var nextPart = tokenParts.shift();
|
||||
var isSafe = nextPart.endsWith("?");
|
||||
if (isSafe)
|
||||
nextPart = nextPart.substr(0, nextPart.length - 1);
|
||||
fieldExp = if (fieldExp == null) {
|
||||
k.identAliases[fieldExpVal].withPos(position);
|
||||
} else {
|
||||
fieldExp = FieldExp(nextPart, fieldExp, nextIsSafe).withPos(position);
|
||||
}
|
||||
nextIsSafe = isSafe;
|
||||
}
|
||||
fieldExp;
|
||||
fieldExp.def;
|
||||
} else {
|
||||
Symbol(token);
|
||||
}
|
||||
@@ -549,8 +564,10 @@ class Reader {
|
||||
case MetaExp(meta, exp):
|
||||
// &meta
|
||||
'&$meta ${exp.def.toString()}';
|
||||
case FieldExp(field, exp):
|
||||
case FieldExp(field, exp, false):
|
||||
'.$field ${exp.def.toString()}';
|
||||
case FieldExp(field, exp, true):
|
||||
'?.$field ${exp.def.toString()}';
|
||||
case KeyValueExp(keyExp, valueExp):
|
||||
'=>${keyExp.def.toString()} ${valueExp.def.toString()}';
|
||||
case Quasiquote(exp):
|
||||
|
@@ -14,15 +14,15 @@ enum ReaderExpDef {
|
||||
Symbol(name:String); // s
|
||||
RawHaxe(code:String); // #| haxeCode() |# // deprecated!
|
||||
RawHaxeBlock(code:String); // #{ haxeCode(); moreHaxeCode(); }#
|
||||
TypedExp(path:String, exp:ReaderExp); // :Type [exp]
|
||||
MetaExp(meta:String, exp:ReaderExp); // &meta [exp]
|
||||
FieldExp(field:String, exp:ReaderExp); // .field [exp]
|
||||
TypedExp(path:String, exp:ReaderExp); // :Type <exp>
|
||||
MetaExp(meta:String, exp:ReaderExp); // &meta <exp>
|
||||
FieldExp(field:String, exp:ReaderExp, safeField:Bool); // .field <exp> or ?.field <exp>
|
||||
KeyValueExp(key:ReaderExp, value:ReaderExp); // =>key value
|
||||
Quasiquote(exp:ReaderExp); // `[exp]
|
||||
Unquote(exp:ReaderExp); // ,[exp]
|
||||
UnquoteList(exp:ReaderExp); // ,@[exp]
|
||||
Quasiquote(exp:ReaderExp); // `<exp>
|
||||
Unquote(exp:ReaderExp); // ,<exp>
|
||||
UnquoteList(exp:ReaderExp); // ,@<exp>
|
||||
ListEatingExp(exps:Array<ReaderExp>); // [::exp exp ...exps exp]
|
||||
ListRestExp(name:String); // ...exps or ...
|
||||
ListRestExp(name:String); // ...<exp> or ...
|
||||
TypeParams(types:Array<ReaderExp>); // <>[T :Constraint U :Constraint1 :Constraint2 V]
|
||||
HaxeMeta(name:String, params:Null<Array<ReaderExp>>, exp:ReaderExp); // @meta <exp> or @(meta <params...>) <exp>
|
||||
None; // not an expression, i.e. (#unless falseCondition exp)
|
||||
|
@@ -443,6 +443,10 @@ class BasicTestCase extends Test {
|
||||
_testQuickPrintOnSetVar();
|
||||
}
|
||||
|
||||
function testSafeAccess() {
|
||||
_testSafeAccess();
|
||||
}
|
||||
|
||||
var aNullToPrint = null;
|
||||
}
|
||||
|
||||
|
@@ -969,4 +969,13 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(set (print v) 8)
|
||||
(set (Prelude.print v) 9)
|
||||
(set ~v 6)
|
||||
(Assert.equals v 6)))
|
||||
(Assert.equals v 6)))
|
||||
|
||||
(defAlias &ident otherNameForObj obj)
|
||||
(function _testSafeAccess []
|
||||
(let [:Dynamic obj null]
|
||||
(obj?.doThing)
|
||||
(otherNameForObj?.doThing)
|
||||
(?.doThing obj)
|
||||
(?.doThing otherNameForObj))
|
||||
(Assert.pass))
|
Reference in New Issue
Block a user