This commit is contained in:
2020-12-31 14:05:46 -07:00
parent 186024a5ff
commit de3577eb85
4 changed files with 104 additions and 2 deletions

View File

@@ -197,6 +197,94 @@ class Macros {
};
}
macros["defmacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(3, null, '(defmacro [name] [[args...]] [body...])');
var table = k.macros;
var name = switch (exps[0].def) {
case Symbol(name): name;
default: throw CompileError.fromExp(exps[0], "macro name should be a symbol");
};
var argList = switch (exps[1].def) {
case ListExp(macroArgs): macroArgs;
case CallExp(_, _):
throw CompileError.fromExp(exps[1], 'expected a macro argument list. Change the parens () to brackets []');
default:
throw CompileError.fromExp(exps[1], 'expected a macro argument list');
};
// This is similar to &opt and &rest processing done by Helpers.makeFunction()
// but combining them would probably make things less readable and harder
// to maintain, because defmacro makes an actual function, not a function definition
var minArgs = 0;
var maxArgs = 0;
// Once the &opt meta appears, all following arguments are optional until &rest
var optIndex = -1;
// Once the &rest meta appears, no other arguments can be declared
var restIndex = -1;
var argNames = [];
var macroCallForm = '($name';
for (arg in argList) {
if (restIndex != -1) {
throw CompileError.fromExp(arg, "macros cannot declare arguments after a &rest argument");
}
switch (arg.def) {
case Symbol(name):
argNames.push(name);
if (optIndex == -1) {
++minArgs;
macroCallForm += ' [$name]';
} else {
macroCallForm += ' [?$name]';
}
++maxArgs;
case MetaExp("opt", {pos: _, def: Symbol(name)}):
argNames.push(name);
macroCallForm += ' [?$name]';
optIndex = maxArgs;
++maxArgs;
case MetaExp("rest", {pos: _, def: Symbol(name)}):
argNames.push(name);
macroCallForm += ' [$name...]';
restIndex = maxArgs;
maxArgs = null;
default:
throw CompileError.fromExp(arg, "macro argument should be an untyped symbol or a symbol annotated with &opt or &rest");
}
}
macroCallForm += ')';
if (optIndex == -1)
optIndex = minArgs;
if (restIndex == -1)
restIndex = optIndex;
macros[name] = (wholeExp:ReaderExp, innerExps:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(minArgs, maxArgs, macroCallForm);
var innerArgNames = argNames.copy();
var args:Map<String, Dynamic> = [];
for (idx in 0...optIndex) {
args[innerArgNames.shift()] = innerExps[idx];
}
for (idx in optIndex...restIndex) {
args[innerArgNames.shift()] = if (exps.length > idx) innerExps[idx] else null;
}
if (innerArgNames.length > 0)
args[innerArgNames.shift()] = innerExps.slice(restIndex);
// Return the macro expansion:
var expDef:ReaderExpDef = Helpers.runAtCompileTime(CallExp(Symbol("begin").withPosOf(wholeExp), exps.slice(2)).withPosOf(wholeExp), k, args);
expDef.withPosOf(wholeExp);
};
null;
};
macros["defreadermacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(3, null, '(defreadermacro ["[startingString]" or [startingStrings...]] [[streamArgName]] [body...])');
@@ -223,6 +311,8 @@ class Macros {
var body = CallExp(Symbol("begin").withPos(stream.position()), exps.slice(2)).withPos(stream.position());
Helpers.runAtCompileTime(body, k, [streamArgName => stream]);
};
case CallExp(_, []):
throw CompileError.fromExp(exps[1], 'expected an argument list. Change the parens () to brackets []');
default:
throw CompileError.fromExp(exps[1], 'second argument to defreadermacro should be [steamArgName]');
}

View File

@@ -56,7 +56,6 @@ class Reader {
null;
};
readTable["#|"] = (stream, k) -> RawHaxe(stream.expect("closing |#", () -> stream.takeUntilAndDrop("|#")));
// For defmacrofuns, unquoting with , is syntactic sugar for calling a Quote (Void->T)
readTable[":"] = (stream, k) -> TypedExp(nextToken(stream, "a type path"), assertRead(stream, k));

View File

@@ -249,6 +249,10 @@ class BasicTestCase extends Test {
function testTypeParsing() {
_testTypeParsing();
}
function testDefmacro() {
_testDefmacro();
}
}
class BasicObject {

View File

@@ -370,4 +370,13 @@
(Assert.equals 5 (doSomething (lambda [i] i)))
(Assert.equals 7 (doSomething (lambda [i] (+ i 2))))
// Pass null to the really crazy one because I'm lazy:
(Assert.equals "but it still compiles" (itsAMonster null)))
(Assert.equals "but it still compiles" (itsAMonster null)))
(defmacro defconstfunc [name const] `(defun ,name [] ,const))
(defconstfunc func5 5)
(defconstfunc funcHello "hello")
(defun _testDefmacro []
(Assert.equals 5 (func5))
(Assert.equals "hello" (funcHello)))