Skip to content
This repository has been archived by the owner on Feb 15, 2024. It is now read-only.

Commit

Permalink
Support ?. operator. Close #188.
Browse files Browse the repository at this point in the history
  • Loading branch information
NQNStudios committed Apr 25, 2023
1 parent 485ee16 commit 5d593e2
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 25 deletions.
12 changes: 6 additions & 6 deletions kiss/src/kiss/Helpers.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions kiss/src/kiss/Kiss.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
11 changes: 9 additions & 2 deletions kiss/src/kiss/Macros.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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>)');
Expand Down
29 changes: 23 additions & 6 deletions kiss/src/kiss/Reader.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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):
Expand Down
14 changes: 7 additions & 7 deletions kiss/src/kiss/ReaderExp.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions kiss/src/test/cases/BasicTestCase.hx
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,10 @@ class BasicTestCase extends Test {
_testQuickPrintOnSetVar();
}

function testSafeAccess() {
_testSafeAccess();
}

var aNullToPrint = null;
}

Expand Down
11 changes: 10 additions & 1 deletion kiss/src/test/cases/BasicTestCase.kiss
Original file line number Diff line number Diff line change
Expand Up @@ -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))

0 comments on commit 5d593e2

Please sign in to comment.