throwCompileError in expBuilder

This commit is contained in:
2021-11-10 15:45:04 -07:00
parent d6a2a3e8e2
commit 6b387fd932
7 changed files with 80 additions and 6 deletions

View File

@@ -19,6 +19,15 @@ class CompileError {
return new CompileError([exp], message);
}
public static function fromExpStr(pos:Position, expStr:String, message:String) {
switch (Reader.read(Stream.fromString(expStr), Kiss.defaultKissState())) {
case Some(exp):
return fromExp({pos: pos, def: exp.def}, message);
default:
throw 'bad'; // TODO better message
}
}
public static function fromArgs(exps:Array<ReaderExp>, message:String) {
return new CompileError(exps, message);
}

View File

@@ -558,6 +558,21 @@ class Helpers {
function list(exps:Array<ReaderExp>) {
return ListExp(exps).withPosOf(posRef);
}
function objectWith (bindings:Array<ReaderExp>, captures:Array<ReaderExp>) {
return callSymbol("objectWith", [list(bindings)].concat(captures));
}
function str(s:String) {
return StrExp(s).withPosOf(posRef);
}
function raw(code:String) {
return RawHaxe(code).withPosOf(posRef);
}
function int(v:Int) {
return _symbol(Std.string(v));
}
function float(v:Float) {
return _symbol(Std.string(v));
}
return {
call: call,
callSymbol: callSymbol,
@@ -565,15 +580,34 @@ class Helpers {
print: (arg:ReaderExp) -> CallExp(Symbol("print").withPosOf(posRef), [arg]).withPosOf(posRef),
the: (type:ReaderExp, value:ReaderExp) -> callSymbol("the", [type, value]),
list: list,
str: (s:String) -> StrExp(s).withPosOf(posRef),
str: str,
symbol: _symbol,
raw: (code:String) -> RawHaxe(code).withPosOf(posRef),
int: int,
float: float,
raw: raw,
typed: (path:String, exp:ReaderExp) -> TypedExp(path, exp).withPosOf(posRef),
meta: (m:String, exp:ReaderExp) -> MetaExp(m, exp).withPosOf(posRef),
field: field,
keyValue: (key:ReaderExp, value:ReaderExp) -> KeyValueExp(key, value).withPosOf(posRef),
begin: (exps:Array<ReaderExp>) -> callSymbol("begin", exps),
let: (bindings:Array<ReaderExp>, body:Array<ReaderExp>) -> callSymbol("let", [list(bindings)].concat(body)),
objectWith: objectWith,
throwCompileError: (reason:String) -> {
callSymbol("throw", [
callSymbol("CompileError.fromExpStr", [
// pos
objectWith([
_symbol("file"), str(posRef.pos.file),
_symbol("line"), int(posRef.pos.line),
_symbol("column"), int(posRef.pos.column),
_symbol("absoluteChar"), int(posRef.pos.absoluteChar),
], []),
// expStr
str(Reader.toString(posRef.def)),
str(reason)
])
]);
},
none: () -> None.withPosOf(posRef)
};
}

View File

@@ -376,7 +376,8 @@ class Kiss {
static function disableMacro(copy:KissState, m:String, reason:String) {
copy.macros[m] = (wholeExp:ReaderExp, exps, k) -> {
var b = wholeExp.expBuilder();
b.callSymbol("throw", [b.str('$m is unavailable in macros because $reason')]);
// have this throw during macroEXPANSION, not before (so assertThrows will catch it)
b.throwCompileError('$m is unavailable in macros because $reason');
};
}
@@ -399,8 +400,18 @@ class Kiss {
public static function forMacroEval(k:KissState): KissState {
var copy = k.forHScript();
// Disable macros that will cause problems in macro evaluation:
disableMacro(copy, "set", "you don't want your macros to be stateful");
// Catch accidental misuse of (set) on macroVars
var setLocal = copy.specialForms["set"];
copy.specialForms["set"] = (wholeExp:ReaderExp, exps, k:KissState) -> {
switch (exps[0].def) {
case Symbol(varName) if (k.macroVars.exists(varName)):
var b = wholeExp.expBuilder();
// have this throw during macroEXPANSION, not before (so assertThrows will catch it)
copy.convert(b.throwCompileError('If you intend to change macroVar $varName, use defMacroVar instead. If not, rename your local variable for clarity.'));
default:
setLocal(wholeExp, exps, copy);
};
};
return copy;
}

View File

@@ -44,6 +44,10 @@ class KissInterp extends Interp {
variables.set("Http", sys.Http);
#end
#if macro
variables.set("CompileError", kiss.CompileError);
#end
// Might eventually need to simulate types in the namespace:
variables.set("kiss", {});
}

View File

@@ -470,7 +470,9 @@ class Macros {
try {
// Return the macro expansion:
return Helpers.runAtCompileTime(b.callSymbol("begin", exps.slice(2)), k, args);
} catch (error) {
} catch (error:CompileError) {
throw error;
} catch (error:Dynamic) {
throw CompileError.fromExp(wholeExp, 'Macro expansion error: $error');
};
};

View File

@@ -46,6 +46,10 @@ class MacroTestCase extends Test {
_testSetMacroVar();
}
function testRedefineMacroVar() {
_testRedefineMacroVar();
}
function testTryCatchWithoutDynamic () {
_testTryCatchWithoutDynamic();
}

View File

@@ -60,6 +60,7 @@
(_testPrintAtMacroTimeMacro)
(Assert.pass))
// Calling (set) on a macroVar is a faux-pas, but calling defMacroVar again is not
(defMacroVar count 0)
(defMacro _testSetMacroVarMacro []
(assertThrows (set count (+ count 1)))
@@ -69,6 +70,15 @@
(_testSetMacroVarMacro)
(Assert.pass))
(defMacro _testRedefineMacroVarMacro []
(defMacroVar count (+ count 1))
(symbol (Std.string count)))
(function _testRedefineMacroVar []
(Assert.equals 1 (_testRedefineMacroVarMacro))
(Assert.equals 2 (_testRedefineMacroVarMacro)))
// ifLet and its derivatives should be disabled in defMacro bodies:
(defMacro _testIfLetDisabledMacro []
(assertThrows (ifLet [a "b"] a))