(#extern) without binding support
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
-lib hscript
|
||||
-lib uuid
|
||||
-lib tink_macro
|
||||
-lib tink_json
|
||||
-cp kiss/src
|
||||
-cp cloner/src
|
||||
-D analyzer-optimize
|
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"hscript": "",
|
||||
"uuid": "",
|
||||
"tink_macro": ""
|
||||
"tink_macro": "",
|
||||
"tink_json": ""
|
||||
}
|
||||
}
|
@@ -18,9 +18,8 @@ enum CompileLang {
|
||||
}
|
||||
|
||||
typedef CompilationArgs = {
|
||||
lang:CompileLang,
|
||||
// path to a folder where the script will be compiled
|
||||
outputFolder:String,
|
||||
?outputFolder:String,
|
||||
// path to a file with haxe import statements in it
|
||||
?importHxFile:String,
|
||||
// path to a file with hxml args in it (SHOULD NOT specify target or main class)
|
||||
@@ -41,17 +40,21 @@ class CompilerTools {
|
||||
* Compile a kiss file into a standalone script.
|
||||
* @return An expression of a function that, when called, executes the script and returns the script output as a string.
|
||||
*/
|
||||
public static function compileFileToScript(kissFile:String, args:CompilationArgs):Expr {
|
||||
public static function compileFileToScript(kissFile:String, lang:CompileLang, args:CompilationArgs):Expr {
|
||||
var k = Kiss.defaultKissState();
|
||||
var beginExpsInFile = Kiss.load(kissFile, k, "", true);
|
||||
return compileToScript([beginExpsInFile], args);
|
||||
return compileToScript([beginExpsInFile], lang, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile kiss expressions into a standalone script
|
||||
* @return An expression of a function that, when called, executes the script and returns the script output as a string.
|
||||
*/
|
||||
public static function compileToScript(exps:Array<ReaderExp>, args:CompilationArgs):Expr {
|
||||
public static function compileToScript(exps:Array<ReaderExp>, lang:CompileLang, args:CompilationArgs):Expr {
|
||||
if (args.outputFolder == null) {
|
||||
args.outputFolder = Path.join(["bin", '_kissScript${nextAnonymousScriptId++}']);
|
||||
}
|
||||
|
||||
// if folder exists, delete it
|
||||
if (FileSystem.exists(args.outputFolder)) {
|
||||
for (file in FileSystem.readDirectory(args.outputFolder)) {
|
||||
@@ -101,7 +104,7 @@ class CompilerTools {
|
||||
var buildHxmlContent = "";
|
||||
buildHxmlContent += "-lib kiss\n";
|
||||
buildHxmlContent += '--main $mainClassName\n';
|
||||
switch (args.lang) {
|
||||
switch (lang) {
|
||||
case JavaScript:
|
||||
// Throw in hxnodejs because we know node will be running the script:
|
||||
buildHxmlContent += '-lib hxnodejs\n';
|
||||
@@ -127,7 +130,7 @@ class CompilerTools {
|
||||
|
||||
var command = "";
|
||||
var scriptExt = "";
|
||||
switch (args.lang) {
|
||||
switch (lang) {
|
||||
case JavaScript:
|
||||
command = "node";
|
||||
scriptExt = "js";
|
||||
@@ -137,9 +140,13 @@ class CompilerTools {
|
||||
}
|
||||
|
||||
// return an expression for a lambda that calls new Process() that runs the target-specific file
|
||||
var callingCode = '() -> kiss.Prelude.assertProcess("$command", [haxe.io.Path.join(["${args.outputFolder}", "$mainClassName.$scriptExt"])])';
|
||||
var callingCode = 'function (?inputLines:Array<String>) { return kiss.Prelude.assertProcess("$command", [haxe.io.Path.join(["${args.outputFolder}", "$mainClassName.$scriptExt"])], inputLines); }';
|
||||
#if test
|
||||
trace(callingCode);
|
||||
#end
|
||||
return Context.parse(callingCode, Context.currentPos());
|
||||
}
|
||||
|
||||
static var nextAnonymousScriptId = 0;
|
||||
}
|
||||
#end
|
||||
|
@@ -310,7 +310,7 @@ class Helpers {
|
||||
// When we ARE running at compiletime already, the pre-existing interp will be used
|
||||
static var interps:kiss.List<Interp> = [];
|
||||
|
||||
public static function runAtCompileTime(exp:ReaderExp, k:KissState, ?args:Map<String, Dynamic>):ReaderExp {
|
||||
public static function runAtCompileTimeDynamic(exp:ReaderExp, k:KissState, ?args:Map<String, Dynamic>):Dynamic {
|
||||
var code = k.forHScript().convert(exp).toString(); // tink_macro to the rescue
|
||||
#if test
|
||||
Prelude.print("Compile-time hscript: " + code);
|
||||
@@ -353,6 +353,11 @@ class Helpers {
|
||||
if (value == null) {
|
||||
throw CompileError.fromExp(exp, "compile-time evaluation returned null");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static function runAtCompileTime(exp:ReaderExp, k:KissState, ?args:Map<String, Dynamic>):ReaderExp {
|
||||
var value = runAtCompileTimeDynamic(exp, k, args);
|
||||
var expResult = compileTimeValueToReaderExp(value, exp);
|
||||
#if test
|
||||
Prelude.print('Compile-time value: ${Reader.toString(expResult.def)}');
|
||||
@@ -363,6 +368,7 @@ class Helpers {
|
||||
// The value could be either a ReaderExp, ReaderExpDef, Array of ReaderExp/ReaderExpDefs, or something else entirely,
|
||||
// but it needs to be a ReaderExp for evalUnquotes()
|
||||
static function compileTimeValueToReaderExp(e:Dynamic, source:ReaderExp):ReaderExp {
|
||||
// TODO if it's a string, return a StrExp. That way, symbolNameValue() won't be required
|
||||
return if (Std.isOfType(e, Array)) {
|
||||
var arr:Array<Dynamic> = e;
|
||||
var listExps = arr.map(compileTimeValueToReaderExp.bind(_, source));
|
||||
@@ -463,6 +469,7 @@ class Helpers {
|
||||
return {
|
||||
call: (func:ReaderExp, args:Array<ReaderExp>) -> CallExp(func, args).withPosOf(posRef),
|
||||
callSymbol: (symbol:String, args:Array<ReaderExp>) -> CallExp(Symbol(symbol).withPosOf(posRef), args).withPosOf(posRef),
|
||||
print: (arg:ReaderExp) -> CallExp(Symbol("print").withPosOf(posRef), [arg]).withPosOf(posRef),
|
||||
list: (exps:Array<ReaderExp>) -> ListExp(exps).withPosOf(posRef),
|
||||
str: (s:String) -> StrExp(s).withPosOf(posRef),
|
||||
symbol: (?name:String) -> Prelude.symbol(name).withPosOf(posRef),
|
||||
|
@@ -6,13 +6,17 @@ import kiss.Reader;
|
||||
import kiss.ReaderExp;
|
||||
import kiss.Kiss;
|
||||
import kiss.CompileError;
|
||||
import kiss.CompilerTools;
|
||||
import uuid.Uuid;
|
||||
import hscript.Parser;
|
||||
import haxe.EnumTools;
|
||||
|
||||
using kiss.Kiss;
|
||||
using kiss.Prelude;
|
||||
using kiss.Reader;
|
||||
using kiss.Helpers;
|
||||
using StringTools;
|
||||
using tink.MacroApi;
|
||||
|
||||
// Macros generate new Kiss reader expressions from the arguments of their call expression.
|
||||
typedef MacroFunction = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> Null<ReaderExp>;
|
||||
@@ -760,6 +764,72 @@ class Macros {
|
||||
return b.call(b.symbol("Macros.exprCase"), [b.str(functionKey), toMatch, b.symbol("k")]);
|
||||
};
|
||||
|
||||
// Maybe the NEW wildest code in Kiss?
|
||||
macros["#extern"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(4, null, "(#extern <BodyType> <lang> <?compileArgs object> [<typed bindings...>] <body...>)");
|
||||
|
||||
var bodyType = exps.shift();
|
||||
var langExp = exps.shift();
|
||||
var originalLang = langExp.symbolNameValue();
|
||||
// make the lang argument forgiving, because many will assume it can match the compiler defines and command-line arguments of Haxe
|
||||
var lang = switch (originalLang) {
|
||||
case "python" | "py": "Python";
|
||||
case "js" | "javascript": "JavaScript";
|
||||
default: originalLang;
|
||||
};
|
||||
|
||||
var allowedLangs = EnumTools.getConstructors(CompileLang);
|
||||
if (allowedLangs.indexOf(lang) == -1) {
|
||||
throw CompileError.fromExp(langExp, 'unsupported lang for #extern: $originalLang should be one of $allowedLangs');
|
||||
}
|
||||
var langArg = EnumTools.createByName(CompileLang, lang);
|
||||
|
||||
var compileArgsExp = null;
|
||||
var bindingListExp = null;
|
||||
var nextArg = exps.shift();
|
||||
switch (nextArg.def) {
|
||||
case CallExp({pos: _, def: Symbol("object")}, _):
|
||||
compileArgsExp = nextArg;
|
||||
nextArg = exps.shift();
|
||||
case ListExp(_):
|
||||
// Let the next switch handle the binding list
|
||||
default:
|
||||
throw CompileError.fromExp(nextArg, "second argument to #extern can either be a CompileArgs object or a list of typed bindings");
|
||||
}
|
||||
switch (nextArg.def) {
|
||||
case ListExp(_):
|
||||
bindingListExp = nextArg;
|
||||
default:
|
||||
throw CompileError.fromExp(nextArg, "#extern requires a list of typed bindings");
|
||||
}
|
||||
|
||||
var compileArgs:CompilationArgs = if (compileArgsExp != null) {
|
||||
Helpers.runAtCompileTimeDynamic(compileArgsExp, k);
|
||||
} else {
|
||||
{};
|
||||
}
|
||||
|
||||
trace(compileArgs);
|
||||
trace(bindingListExp);
|
||||
// TODO generate tink_json writers and parsers for this
|
||||
|
||||
var b = wholeExp.expBuilder();
|
||||
var externExps = [
|
||||
b.print(
|
||||
b.callSymbol("tink.Json.stringify", [
|
||||
b.callSymbol("the", [
|
||||
bodyType, b.begin(exps)
|
||||
])
|
||||
]))
|
||||
];
|
||||
b.callSymbol("the", [
|
||||
bodyType,
|
||||
b.callSymbol("tink.Json.parse", [
|
||||
b.call(b.raw(CompilerTools.compileToScript(externExps, langArg, compileArgs).toString()), [])
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
return macros;
|
||||
}
|
||||
|
||||
|
@@ -47,6 +47,7 @@ class Reader {
|
||||
forceSymbol("#unless");
|
||||
forceSymbol("#cond");
|
||||
forceSymbol("#case");
|
||||
forceSymbol("#extern");
|
||||
|
||||
readTable["/*"] = (stream, k) -> {
|
||||
stream.takeUntilAndDrop("*/");
|
||||
|
@@ -3,6 +3,7 @@ package test.cases;
|
||||
import utest.Assert;
|
||||
import utest.Test;
|
||||
import kiss.CompilerTools;
|
||||
import kiss.Prelude;
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
@@ -15,8 +16,7 @@ class CompilerToolsTestCase extends Test {
|
||||
|
||||
static macro function _testCompileHelloWorldJs() {
|
||||
return CompilerTools.compileFileToScript(
|
||||
"kiss/template/src/template/Main.kiss", {
|
||||
lang: JavaScript,
|
||||
"kiss/template/src/template/Main.kiss", JavaScript, {
|
||||
outputFolder: "bin/helloWorldJsTest",
|
||||
});
|
||||
}
|
||||
@@ -27,8 +27,7 @@ class CompilerToolsTestCase extends Test {
|
||||
|
||||
static macro function _testCompileHelloWorldPy() {
|
||||
return CompilerTools.compileFileToScript(
|
||||
"kiss/template/src/template/Main.kiss", {
|
||||
lang: Python,
|
||||
"kiss/template/src/template/Main.kiss", Python, {
|
||||
outputFolder: "bin/helloWorldPyTest",
|
||||
});
|
||||
}
|
||||
|
17
kiss/src/test/cases/ExternTestCase.hx
Normal file
17
kiss/src/test/cases/ExternTestCase.hx
Normal file
@@ -0,0 +1,17 @@
|
||||
package test.cases;
|
||||
|
||||
import utest.Assert;
|
||||
import utest.Test;
|
||||
import kiss.CompilerTools;
|
||||
import kiss.Prelude;
|
||||
|
||||
@:build(kiss.Kiss.build())
|
||||
class ExternTestCase extends Test {
|
||||
function testExternPython() {
|
||||
_testExternPython();
|
||||
}
|
||||
|
||||
function testExternJavaScript() {
|
||||
_testExternJavaScript();
|
||||
}
|
||||
}
|
13
kiss/src/test/cases/ExternTestCase.kiss
Normal file
13
kiss/src/test/cases/ExternTestCase.kiss
Normal file
@@ -0,0 +1,13 @@
|
||||
(function _testExternPython []
|
||||
(Assert.isTrue
|
||||
(#extern Bool python []
|
||||
(#if python
|
||||
true
|
||||
false))))
|
||||
|
||||
(function _testExternJavaScript []
|
||||
(Assert.isTrue
|
||||
(#extern Bool js []
|
||||
(#if js
|
||||
true
|
||||
false))))
|
Reference in New Issue
Block a user