diff --git a/src/kiss/KissInterp.hx b/src/kiss/KissInterp.hx index c8537f9..0df24bb 100644 --- a/src/kiss/KissInterp.hx +++ b/src/kiss/KissInterp.hx @@ -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 diff --git a/src/kiss/Macros.hx b/src/kiss/Macros.hx index eb85c0e..da8c5b0 100644 --- a/src/kiss/Macros.hx +++ b/src/kiss/Macros.hx @@ -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, 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, k) { wholeExp.checkNumArgs(2, null, '($formName [condition] [body...])'); var b = wholeExp.expBuilder(); diff --git a/src/kiss/Reader.hx b/src/kiss/Reader.hx index 53473ca..da7c033 100644 --- a/src/kiss/Reader.hx +++ b/src/kiss/Reader.hx @@ -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); diff --git a/src/test/cases/ConditionalCompilationTestCase.hx b/src/test/cases/ConditionalCompilationTestCase.hx new file mode 100644 index 0000000..a7d9c11 --- /dev/null +++ b/src/test/cases/ConditionalCompilationTestCase.hx @@ -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 + } +} diff --git a/src/test/cases/ConditionalCompilationTestCase.kiss b/src/test/cases/ConditionalCompilationTestCase.kiss new file mode 100644 index 0000000..8564fa2 --- /dev/null +++ b/src/test/cases/ConditionalCompilationTestCase.kiss @@ -0,0 +1,3 @@ +(defvar runningInHaxe (#if interp true false)) +(defvar runningInPyOrJs (#if (or py js) true false)) +