(#extern) without binding support

This commit is contained in:
2021-07-26 18:59:09 -06:00
parent db80da6a90
commit 700a4de08b
9 changed files with 130 additions and 14 deletions

View File

@@ -1,6 +1,7 @@
-lib hscript
-lib uuid
-lib tink_macro
-lib tink_json
-cp kiss/src
-cp cloner/src
-D analyzer-optimize

View File

@@ -12,6 +12,7 @@
"dependencies": {
"hscript": "",
"uuid": "",
"tink_macro": ""
"tink_macro": "",
"tink_json": ""
}
}

View File

@@ -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

View File

@@ -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),

View File

@@ -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;
}

View File

@@ -47,6 +47,7 @@ class Reader {
forceSymbol("#unless");
forceSymbol("#cond");
forceSymbol("#case");
forceSymbol("#extern");
readTable["/*"] = (stream, k) -> {
stream.takeUntilAndDrop("*/");

View File

@@ -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",
});
}

View 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();
}
}

View 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))))