Reader macros implemented in Kiss

This commit is contained in:
2020-12-09 13:36:50 -07:00
parent cd1947a658
commit e62a65fb57
4 changed files with 29 additions and 21 deletions

View File

@@ -9,8 +9,9 @@ import kiss.Reader;
import kiss.Kiss; import kiss.Kiss;
import kiss.CompileError; import kiss.CompileError;
using kiss.Kiss; using tink.MacroApi;
using uuid.Uuid; using uuid.Uuid;
using kiss.Kiss;
using kiss.Reader; using kiss.Reader;
using kiss.Helpers; using kiss.Helpers;
@@ -181,7 +182,7 @@ class Macros {
}; };
macros["defreadermacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> { macros["defreadermacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(3, 3, '(defreadermacro ["[startingString]" or [startingStrings...]] [[streamArgName]] [RawHaxe])'); wholeExp.checkNumArgs(3, null, '(defreadermacro ["[startingString]" or [startingStrings...]] [[streamArgName]] [body...])');
// reader macros can define a list of strings that will trigger the macro. When there are multiple, // reader macros can define a list of strings that will trigger the macro. When there are multiple,
// the macro will put back the initiating string into the stream so you can check which one it was // the macro will put back the initiating string into the stream so you can check which one it was
@@ -205,23 +206,19 @@ class Macros {
for (s in stringsThatMatch) { for (s in stringsThatMatch) {
switch (exps[1].def) { switch (exps[1].def) {
case ListExp([{pos: _, def: Symbol(streamArgName)}]): case ListExp([{pos: _, def: Symbol(streamArgName)}]):
// For now, reader macros only support a one-expression body implemented in #|raw haxe|# (which can contain a block). k.readTable[s] = (stream) -> {
switch (exps[2].def) { if (stringsThatMatch.length > 1) {
case RawHaxe(code): stream.putBackString(s);
k.readTable[s] = (stream) -> { }
if (stringsThatMatch.length > 1) { var body = CallExp(Symbol("begin").withPos(stream.position()), exps.slice(2)).withPos(stream.position());
stream.putBackString(s); var code = k.convert(body).toString(); // tink_macro to the rescue
} var parser = new Parser();
var parser = new Parser(); var interp = new Interp();
var interp = new Interp(); // TODO reader macros also need to access the readtable
// TODO reader macros also need to access the readtable interp.variables.set("ReaderExp", ReaderExpDef);
interp.variables.set("ReaderExp", ReaderExpDef); interp.variables.set(streamArgName, stream);
interp.variables.set(streamArgName, stream); interp.execute(Prelude.print(parser.parseString(code)));
interp.execute(parser.parseString(code)); };
};
default:
throw CompileError.fromExp(exps[2], 'third argument to defreadermacro should be #|raw haxe|#');
}
default: default:
throw CompileError.fromExp(exps[1], 'second argument to defreadermacro should be [steamArgName]'); throw CompileError.fromExp(exps[1], 'second argument to defreadermacro should be [steamArgName]');
} }

View File

@@ -30,6 +30,8 @@ enum ReaderExpDef {
MetaExp(meta:String, exp:ReaderExp); // &meta [exp] MetaExp(meta:String, exp:ReaderExp); // &meta [exp]
FieldExp(field:String, exp:ReaderExp); // .field [exp] FieldExp(field:String, exp:ReaderExp); // .field [exp]
KeyValueExp(key:ReaderExp, value:ReaderExp); // =>key value KeyValueExp(key:ReaderExp, value:ReaderExp); // =>key value
Quasiquote(exp:ReaderExp); // `[exp]
Unquote(exp:ReaderExp); // ,[exp]
} }
typedef ReadFunction = (Stream) -> Null<ReaderExpDef>; typedef ReadFunction = (Stream) -> Null<ReaderExpDef>;
@@ -78,6 +80,9 @@ class Reader {
throw new UnmatchedBracketSignal("]", stream.position()); throw new UnmatchedBracketSignal("]", stream.position());
}; };
readTable["`"] = (stream) -> Quasiquote(assertRead(stream, readTable));
readTable[","] = (stream) -> Unquote(assertRead(stream, readTable));
// Because macro keys are sorted by length and peekChars(0) returns "", this will be used as the default reader macro: // Because macro keys are sorted by length and peekChars(0) returns "", this will be used as the default reader macro:
readTable[""] = (stream) -> Symbol(nextToken(stream, "a symbol name")); readTable[""] = (stream) -> Symbol(nextToken(stream, "a symbol name"));
@@ -223,6 +228,10 @@ class Reader {
'.$field ${exp.def.toString()}'; '.$field ${exp.def.toString()}';
case KeyValueExp(keyExp, valueExp): case KeyValueExp(keyExp, valueExp):
'=>${keyExp.def.toString()} ${valueExp.def.toString()}'; '=>${keyExp.def.toString()} ${valueExp.def.toString()}';
case Quasiquote(exp):
'`${exp.def.toString()}';
case Unquote(exp):
',${exp.def.toString()}';
} }
} }
} }

View File

@@ -191,7 +191,7 @@ class SpecialForms {
map["lambda"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> { map["lambda"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(2, null, "(lambda [[argsNames...]] [body...])"); wholeExp.checkNumArgs(2, null, "(lambda [[argsNames...]] [body...])");
EFunction(FArrow, Helpers.makeFunction(null, args[0], args.slice(1), k)).withMacroPosOf(wholeExp); EFunction(FAnonymous, Helpers.makeFunction(null, args[0], args.slice(1), k)).withMacroPosOf(wholeExp);
}; };
function forExpr(formName:String, wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) { function forExpr(formName:String, wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) {

View File

@@ -1,4 +1,6 @@
(defreadermacro "!" [stream] #|ReaderExp.StrExp(stream.expect("a string line", function () stream.takeLine()))|#) (defreadermacro "!" [stream]
(let [line (stream.expect "a string line" (lambda [] (stream.takeLine)))]
(ReaderExp.StrExp line)))
(defun myLine [] (defun myLine []
!String that takes the rest of the line !String that takes the rest of the line