\#if conditional compilation macro

This commit is contained in:
2021-07-12 13:08:38 -06:00
parent befaadf2d5
commit 709a34ebdc
5 changed files with 78 additions and 2 deletions

View File

@@ -11,10 +11,14 @@ import kiss.Prelude;
* macro definitions.
*/
class KissInterp extends Interp {
var nullForUnknownVar:Bool;
// TODO standardize this with KissConfig.prepareInterp
public function new() {
public function new(nullForUnknownVar = false) {
super();
this.nullForUnknownVar = nullForUnknownVar;
variables.set("Prelude", Prelude);
variables.set("Lambda", Lambda);
variables.set("Std", Std);
@@ -23,6 +27,18 @@ class KissInterp extends Interp {
variables.set("Throw", ExtraElementHandling.Throw);
}
override function resolve(id:String):Dynamic {
if (nullForUnknownVar) {
return try {
super.resolve(id);
} catch (e:Dynamic) {
null;
}
} else {
return super.resolve(id);
}
}
override function exprReturn(e):Dynamic {
// the default exprReturn() contains a try-catch which, though it is important, hides very important macroexpansion callstacks sometimes
#if macrotest

View File

@@ -8,6 +8,7 @@ import kiss.Kiss;
import kiss.CompileError;
import uuid.Uuid;
import sys.io.Process;
import hscript.Parser;
using kiss.Kiss;
using kiss.Reader;
@@ -171,6 +172,32 @@ class Macros {
]);
};
// Conditional compilation is all based on this macro:
macros["#if"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k) -> {
wholeExp.checkNumArgs(2, 3, '(#if [cond] [then] [?else])');
var conditionExp = exps.shift();
var thenExp = exps.shift();
var elseExp = if (exps.length > 0) exps.shift(); else null;
var parser = new Parser();
var conditionInterp = new KissInterp(true);
var conditionStr = Reader.toString(conditionExp.def);
var conditionHScript = parser.parseString(Prelude.convertToHScript(conditionStr));
for (flag => value in Context.getDefines()) {
conditionInterp.variables.set(flag, value);
}
try {
return if (Prelude.truthy(conditionInterp.execute(conditionHScript))) {
thenExp;
} else {
elseExp;
}
} catch (e) {
throw CompileError.fromExp(conditionExp, 'condition for #if threw error $e');
}
};
function bodyIf(formName:String, negated:Bool, wholeExp:ReaderExp, args:Array<ReaderExp>, k) {
wholeExp.checkNumArgs(2, null, '($formName [condition] [body...])');
var b = wholeExp.expBuilder();

View File

@@ -36,6 +36,16 @@ class Reader {
readTable['"'] = readString;
readTable["#"] = readRawString;
// Special symbols that wouldn't read as symbols, but should:
function forceSymbol(sym:String) {
readTable[sym] = (stream, k) -> Symbol(sym);
}
forceSymbol("#if");
forceSymbol("#when");
forceSymbol("#unless");
forceSymbol("#cond");
forceSymbol("#case");
readTable["/*"] = (stream, k) -> {
stream.takeUntilAndDrop("*/");
null;
@@ -314,7 +324,7 @@ class Reader {
case '"':
break;
default:
stream.error('Invalid syntax for raw string. Delete $next');
stream.error('Invalid syntax for raw string.');
return null;
}
} while (true);

View File

@@ -0,0 +1,20 @@
package test.cases;
import utest.Assert;
import utest.Test;
import kiss.Prelude;
@:build(kiss.Kiss.build())
class ConditionalCompilationTestCase extends Test {
function testIf() {
#if interp
Assert.isTrue(runningInHaxe);
#else
Assert.isFalse(runningInHaxe);
#end
#if (py || js)
Assert.isTrue(runningInPyOrJs);
#end
}
}

View File

@@ -0,0 +1,3 @@
(defvar runningInHaxe (#if interp true false))
(defvar runningInPyOrJs (#if (or py js) true false))