This commit is contained in:
2020-11-15 12:47:06 -07:00
parent 8b7c018a4d
commit 5a77d393be
6 changed files with 94 additions and 11 deletions

View File

@@ -7,12 +7,15 @@ import kiss.Reader;
import kiss.FieldForms;
import kiss.SpecialForms;
import kiss.Macros;
import kiss.Types;
typedef KissState = {
className:String,
readTable:Map<String, ReadFunction>,
fieldForms:Map<String, FieldFormFunction>,
specialForms:Map<String, SpecialFormFunction>,
macros:Map<String, MacroFunction>
macros:Map<String, MacroFunction>,
convert:ExprConversion
};
class Kiss {
@@ -21,15 +24,19 @@ class Kiss {
**/
macro static public function build(kissFile:String):Array<Field> {
var classFields = Context.getBuildFields();
var className = Context.getLocalClass().get().name;
var stream = new Stream(kissFile);
var k = {
className: className,
readTable: Reader.builtins(),
fieldForms: FieldForms.builtins(),
specialForms: SpecialForms.builtins(),
macros: Macros.builtins()
};
macros: Macros.builtins(),
convert: null
}
k.convert = readerExpToHaxeExpr.bind(_, k);
while (true) {
stream.dropWhitespace();
@@ -55,7 +62,12 @@ class Kiss {
static function readerExpToField(exp:ReaderExp, position:String, k:KissState):Field {
var fieldForms = k.fieldForms;
// Macros at top-level are allowed if they expand into a fieldform, or don't become an expression, like defmacro
var macros = k.macros;
return switch (exp) {
case CallExp(Symbol(mac), args) if (macros.exists(mac)):
readerExpToField(macros[mac](args, k), position, k);
case CallExp(Symbol(formName), args) if (fieldForms.exists(formName)):
fieldForms[formName](position, args, readerExpToHaxeExpr.bind(_, k));
default:
@@ -77,7 +89,7 @@ class Kiss {
expr: EConst(CString(s))
};
case CallExp(Symbol(mac), args) if (macros.exists(mac)):
convert(macros[mac](args));
convert(macros[mac](args, k));
case CallExp(Symbol(specialForm), args) if (specialForms.exists(specialForm)):
specialForms[specialForm](args, convert);
case CallExp(func, body):

View File

@@ -1,9 +1,14 @@
package kiss;
import haxe.macro.Expr;
import haxe.macro.Context;
import kiss.Reader;
import kiss.Kiss;
// Macros generate Kiss new reader from the arguments of their call expression.
typedef MacroFunction = (Array<ReaderExp>) -> ReaderExp;
using kiss.Helpers;
// Macros generate new Kiss reader expressions from the arguments of their call expression.
typedef MacroFunction = (Array<ReaderExp>, KissState) -> ReaderExp;
class Macros {
public static function builtins() {
@@ -17,14 +22,14 @@ class Macros {
macros["/"] = foldMacro("Prelude.divide");
macros["%"] = (exps:Array<ReaderExp>) -> {
macros["%"] = (exps:Array<ReaderExp>, k) -> {
if (exps.length != 2) {
throw 'Got ${exps.length} arguments for % instead of 2.';
}
CallExp(Symbol("Prelude.mod"), [exps[1], exps[0]]);
};
macros["^"] = (exps:Array<ReaderExp>) -> {
macros["^"] = (exps:Array<ReaderExp>, k) -> {
if (exps.length != 2) {
throw 'Got ${exps.length} arguments for ^ instead of 2.';
}
@@ -41,11 +46,35 @@ class Macros {
macros["_eq"] = foldMacro("Prelude.areEqual");
// TODO when
// Under the hood, (defmacro ...) defines a runtime function that accepts Quote arguments and a special form that quotes the arguments to macro calls
macros["defmacro"] = (exps:Array<ReaderExp>, k:KissState) -> {
if (exps.length < 3)
throw '${exps.length} is not enough arguments for (defmacro [name] [args] [body])';
var macroName = switch (exps[0]) {
case Symbol(name): name;
default: throw 'first argument ${exps[0]} for defmacro should be a symbol for the macro name';
};
k.specialForms[macroName] = (callArgs:Array<ReaderExp>, convert) -> {
ECall(Context.parse('${k.className}.${macroName}', Context.currentPos()), [
for (callArg in callArgs)
EFunction(FArrow, {
args: [],
ret: null,
expr: EReturn(k.convert(callArg)).withPos()
}).withPos()
]).withPos();
};
CallExp(Symbol("defun"), exps);
}
return macros;
}
static function foldMacro(func:String):MacroFunction {
return (exps) -> {
return (exps, k) -> {
CallExp(Symbol("Lambda.fold"), [ListExp(exps.slice(1)), Symbol(func), exps[0]]);
};
}

19
src/kiss/Quote.hx Normal file
View File

@@ -0,0 +1,19 @@
package kiss;
/** Under the hood, a quoted expression is just a zero-argument lambda that returns the value. **/
// TODO this type isn't actually used for anything yet, but may come in handy
abstract Quote<T>(Void->T) from Void->T to Void->T {
public inline function new(unquote:Void->T) {
this = unquote;
}
@:from
public static function fromLambda<T>(unquote:Void->T) {
return new Quote(unquote);
}
@:to
public function toLambda<T>() {
return this;
}
}

View File

@@ -30,7 +30,9 @@ class Reader {
stream.dropUntil("\n");
null;
};
readTable["#|"] = (stream) -> RawHaxe(stream.expect("closing |", () -> stream.takeUntilAndDrop("|#")));
readTable["#|"] = (stream) -> RawHaxe(stream.expect("closing |#", () -> stream.takeUntilAndDrop("|#")));
// Unquote is syntactic sugar for calling a Quote (Void->T)
readTable[","] = (stream) -> CallExp(assertRead(stream, readTable), []);
return readTable;
}

View File

@@ -118,4 +118,13 @@ class BasicTestCase extends Test {
Assert.equals(true, BasicTestCase.myIf7);
Assert.equals(false, BasicTestCase.myIf8);
}
function testMacros() {
Assert.equals(7, BasicTestCase.incrementTwice(5));
var seasonsGreetings = "ho ";
Assert.equals("ho ho ho ", BasicTestCase.doTwiceString(() -> {
seasonsGreetings += "ho ";
}));
}
}

View File

@@ -62,3 +62,15 @@
(defvar myIf6 (if false true false))
(defvar myIf7 (if "string" true false))
(defvar myIf8 (if "" true false))
(defmacro doTwiceInt [intOp]
,intOp
,intOp)
// I think this causes doTwiceInt's runtime function to be typed as requiring Quote<Int> and returning Int
(defun incrementTwice [val]
(doTwiceInt ++val))
(defmacro doTwiceString [stringOp]
,stringOp
,stringOp)