(#extern) without binding support
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
-lib hscript
|
-lib hscript
|
||||||
-lib uuid
|
-lib uuid
|
||||||
-lib tink_macro
|
-lib tink_macro
|
||||||
|
-lib tink_json
|
||||||
-cp kiss/src
|
-cp kiss/src
|
||||||
-cp cloner/src
|
-cp cloner/src
|
||||||
-D analyzer-optimize
|
-D analyzer-optimize
|
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hscript": "",
|
"hscript": "",
|
||||||
"uuid": "",
|
"uuid": "",
|
||||||
"tink_macro": ""
|
"tink_macro": "",
|
||||||
|
"tink_json": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,9 +18,8 @@ enum CompileLang {
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef CompilationArgs = {
|
typedef CompilationArgs = {
|
||||||
lang:CompileLang,
|
|
||||||
// path to a folder where the script will be compiled
|
// path to a folder where the script will be compiled
|
||||||
outputFolder:String,
|
?outputFolder:String,
|
||||||
// path to a file with haxe import statements in it
|
// path to a file with haxe import statements in it
|
||||||
?importHxFile:String,
|
?importHxFile:String,
|
||||||
// path to a file with hxml args in it (SHOULD NOT specify target or main class)
|
// 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.
|
* 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.
|
* @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 k = Kiss.defaultKissState();
|
||||||
var beginExpsInFile = Kiss.load(kissFile, k, "", true);
|
var beginExpsInFile = Kiss.load(kissFile, k, "", true);
|
||||||
return compileToScript([beginExpsInFile], args);
|
return compileToScript([beginExpsInFile], lang, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile kiss expressions into a standalone script
|
* 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.
|
* @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 folder exists, delete it
|
||||||
if (FileSystem.exists(args.outputFolder)) {
|
if (FileSystem.exists(args.outputFolder)) {
|
||||||
for (file in FileSystem.readDirectory(args.outputFolder)) {
|
for (file in FileSystem.readDirectory(args.outputFolder)) {
|
||||||
@@ -101,7 +104,7 @@ class CompilerTools {
|
|||||||
var buildHxmlContent = "";
|
var buildHxmlContent = "";
|
||||||
buildHxmlContent += "-lib kiss\n";
|
buildHxmlContent += "-lib kiss\n";
|
||||||
buildHxmlContent += '--main $mainClassName\n';
|
buildHxmlContent += '--main $mainClassName\n';
|
||||||
switch (args.lang) {
|
switch (lang) {
|
||||||
case JavaScript:
|
case JavaScript:
|
||||||
// Throw in hxnodejs because we know node will be running the script:
|
// Throw in hxnodejs because we know node will be running the script:
|
||||||
buildHxmlContent += '-lib hxnodejs\n';
|
buildHxmlContent += '-lib hxnodejs\n';
|
||||||
@@ -127,7 +130,7 @@ class CompilerTools {
|
|||||||
|
|
||||||
var command = "";
|
var command = "";
|
||||||
var scriptExt = "";
|
var scriptExt = "";
|
||||||
switch (args.lang) {
|
switch (lang) {
|
||||||
case JavaScript:
|
case JavaScript:
|
||||||
command = "node";
|
command = "node";
|
||||||
scriptExt = "js";
|
scriptExt = "js";
|
||||||
@@ -137,9 +140,13 @@ class CompilerTools {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return an expression for a lambda that calls new Process() that runs the target-specific file
|
// 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);
|
trace(callingCode);
|
||||||
|
#end
|
||||||
return Context.parse(callingCode, Context.currentPos());
|
return Context.parse(callingCode, Context.currentPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var nextAnonymousScriptId = 0;
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
@@ -310,7 +310,7 @@ class Helpers {
|
|||||||
// When we ARE running at compiletime already, the pre-existing interp will be used
|
// When we ARE running at compiletime already, the pre-existing interp will be used
|
||||||
static var interps:kiss.List<Interp> = [];
|
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
|
var code = k.forHScript().convert(exp).toString(); // tink_macro to the rescue
|
||||||
#if test
|
#if test
|
||||||
Prelude.print("Compile-time hscript: " + code);
|
Prelude.print("Compile-time hscript: " + code);
|
||||||
@@ -353,6 +353,11 @@ class Helpers {
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw CompileError.fromExp(exp, "compile-time evaluation returned 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);
|
var expResult = compileTimeValueToReaderExp(value, exp);
|
||||||
#if test
|
#if test
|
||||||
Prelude.print('Compile-time value: ${Reader.toString(expResult.def)}');
|
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,
|
// 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()
|
// but it needs to be a ReaderExp for evalUnquotes()
|
||||||
static function compileTimeValueToReaderExp(e:Dynamic, source:ReaderExp):ReaderExp {
|
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)) {
|
return if (Std.isOfType(e, Array)) {
|
||||||
var arr:Array<Dynamic> = e;
|
var arr:Array<Dynamic> = e;
|
||||||
var listExps = arr.map(compileTimeValueToReaderExp.bind(_, source));
|
var listExps = arr.map(compileTimeValueToReaderExp.bind(_, source));
|
||||||
@@ -463,6 +469,7 @@ class Helpers {
|
|||||||
return {
|
return {
|
||||||
call: (func:ReaderExp, args:Array<ReaderExp>) -> CallExp(func, args).withPosOf(posRef),
|
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),
|
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),
|
list: (exps:Array<ReaderExp>) -> ListExp(exps).withPosOf(posRef),
|
||||||
str: (s:String) -> StrExp(s).withPosOf(posRef),
|
str: (s:String) -> StrExp(s).withPosOf(posRef),
|
||||||
symbol: (?name:String) -> Prelude.symbol(name).withPosOf(posRef),
|
symbol: (?name:String) -> Prelude.symbol(name).withPosOf(posRef),
|
||||||
|
@@ -6,13 +6,17 @@ import kiss.Reader;
|
|||||||
import kiss.ReaderExp;
|
import kiss.ReaderExp;
|
||||||
import kiss.Kiss;
|
import kiss.Kiss;
|
||||||
import kiss.CompileError;
|
import kiss.CompileError;
|
||||||
|
import kiss.CompilerTools;
|
||||||
import uuid.Uuid;
|
import uuid.Uuid;
|
||||||
import hscript.Parser;
|
import hscript.Parser;
|
||||||
|
import haxe.EnumTools;
|
||||||
|
|
||||||
using kiss.Kiss;
|
using kiss.Kiss;
|
||||||
|
using kiss.Prelude;
|
||||||
using kiss.Reader;
|
using kiss.Reader;
|
||||||
using kiss.Helpers;
|
using kiss.Helpers;
|
||||||
using StringTools;
|
using StringTools;
|
||||||
|
using tink.MacroApi;
|
||||||
|
|
||||||
// Macros generate new Kiss reader expressions from the arguments of their call expression.
|
// Macros generate new Kiss reader expressions from the arguments of their call expression.
|
||||||
typedef MacroFunction = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> Null<ReaderExp>;
|
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")]);
|
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;
|
return macros;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,6 +47,7 @@ class Reader {
|
|||||||
forceSymbol("#unless");
|
forceSymbol("#unless");
|
||||||
forceSymbol("#cond");
|
forceSymbol("#cond");
|
||||||
forceSymbol("#case");
|
forceSymbol("#case");
|
||||||
|
forceSymbol("#extern");
|
||||||
|
|
||||||
readTable["/*"] = (stream, k) -> {
|
readTable["/*"] = (stream, k) -> {
|
||||||
stream.takeUntilAndDrop("*/");
|
stream.takeUntilAndDrop("*/");
|
||||||
|
@@ -3,6 +3,7 @@ package test.cases;
|
|||||||
import utest.Assert;
|
import utest.Assert;
|
||||||
import utest.Test;
|
import utest.Test;
|
||||||
import kiss.CompilerTools;
|
import kiss.CompilerTools;
|
||||||
|
import kiss.Prelude;
|
||||||
#if macro
|
#if macro
|
||||||
import haxe.macro.Expr;
|
import haxe.macro.Expr;
|
||||||
import haxe.macro.Context;
|
import haxe.macro.Context;
|
||||||
@@ -15,8 +16,7 @@ class CompilerToolsTestCase extends Test {
|
|||||||
|
|
||||||
static macro function _testCompileHelloWorldJs() {
|
static macro function _testCompileHelloWorldJs() {
|
||||||
return CompilerTools.compileFileToScript(
|
return CompilerTools.compileFileToScript(
|
||||||
"kiss/template/src/template/Main.kiss", {
|
"kiss/template/src/template/Main.kiss", JavaScript, {
|
||||||
lang: JavaScript,
|
|
||||||
outputFolder: "bin/helloWorldJsTest",
|
outputFolder: "bin/helloWorldJsTest",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,7 @@ class CompilerToolsTestCase extends Test {
|
|||||||
|
|
||||||
static macro function _testCompileHelloWorldPy() {
|
static macro function _testCompileHelloWorldPy() {
|
||||||
return CompilerTools.compileFileToScript(
|
return CompilerTools.compileFileToScript(
|
||||||
"kiss/template/src/template/Main.kiss", {
|
"kiss/template/src/template/Main.kiss", Python, {
|
||||||
lang: Python,
|
|
||||||
outputFolder: "bin/helloWorldPyTest",
|
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