Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
8c1c5b9719 | |||
bdd9bedba2 | |||
ded96748fe | |||
877dd0fa7a | |||
f3a4059c4e | |||
f4989595a2 | |||
35883ac728 | |||
6adaee8dd6 | |||
fbf95de14b | |||
06832c142b | |||
314443f6c6 | |||
67f3f3cc74 | |||
e3b0be3fbe | |||
34c599437c | |||
0c22bd1377 | |||
30e2edb150 | |||
a651f116c7 | |||
7be0b082be | |||
f69c24ddf1 | |||
60d260e5b1 | |||
e0fdf71ecf | |||
384c172680 | |||
de4f841d4d | |||
73aeb3fce6 | |||
73af20b7a6 | |||
2509b53ba6 | |||
a949fa24f7 | |||
d051f19c65 | |||
771652b753 | |||
6c113ca1ee | |||
efbd024c3f | |||
1e887693ea | |||
0e02c6c910 | |||
d78f103e9a | |||
ac92346109 | |||
d5b14656f5 | |||
e6005c4802 | |||
4591626818 | |||
c2ab12909f | |||
6a1c66cae7 | |||
9d37e8f2cb | |||
30221135bd | |||
d126b793b3 | |||
92d9f42388 | |||
5668bf0195 | |||
fcaaa5e9d5 | |||
c9747fd467 | |||
8ff1961ee0 | |||
fe61d3edeb | |||
597e242135 | |||
75b17384dc | |||
5044e68b40 | |||
4a95750fed | |||
7e9dd825aa | |||
463c825ee6 | |||
9a19cc8083 | |||
ff7ed5eb51 | |||
c38a93eee1 |
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
node-version: ['14']
|
||||
python-version: ['3.x']
|
||||
test-target:
|
||||
test-target:
|
||||
# HAXE TARGETS
|
||||
- cpp
|
||||
# - cs
|
||||
@@ -18,9 +18,16 @@ jobs:
|
||||
- js
|
||||
- nodejs
|
||||
- py
|
||||
- "lua 5.1"
|
||||
- "lua 5.2"
|
||||
- "lua 5.3"
|
||||
- "lua 5.4"
|
||||
- "luajit 2.0"
|
||||
- "luajit 2.1"
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CI_OS_NAME: ${{ matrix.os }}
|
||||
LUA: ${{ matrix.test-target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# Set up Kiss runtimes:
|
||||
@@ -42,6 +49,10 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
if: matrix.test-target == 'py'
|
||||
|
||||
# lua
|
||||
- run: pip install --user hererocks && hererocks env --$LUA -rlatest && source env/bin/activate && ./build-scripts/lua/install-deps.sh
|
||||
if: contains(matrix.test-target, 'lua')
|
||||
|
||||
# mono
|
||||
- run: brew install mono || brew link --overwrite mono
|
||||
if: matrix.os == 'macos-latest' && matrix.test-target == 'cs'
|
||||
@@ -56,8 +67,14 @@ jobs:
|
||||
sudo apt install mono-devel
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.test-target == 'cs'
|
||||
|
||||
# run target test:
|
||||
# run target test (not lua):
|
||||
- run: echo "KISS_TARGET=${{ matrix.test-target }}" >> $GITHUB_ENV
|
||||
if: contains(matrix.test-target, 'lua') == false
|
||||
- run: ./test.sh
|
||||
shell: bash
|
||||
|
||||
if: contains(matrix.test-target, 'lua') == false
|
||||
|
||||
# run target test (lua):
|
||||
- run: echo "KISS_TARGET=lua" >> $GITHUB_ENV
|
||||
if: contains(matrix.test-target, 'lua')
|
||||
- run: source env/bin/activate && ./test.sh
|
||||
if: contains(matrix.test-target, 'lua')
|
||||
|
26
.vscode/snippets.code-snippets
vendored
Normal file
26
.vscode/snippets.code-snippets
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
// Place your kiss workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"macro lambda": {
|
||||
"body": [
|
||||
"(wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {",
|
||||
" var b = wholeExp.expBuilder();",
|
||||
" // TODO implement this",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
7
build-scripts/lua/install-deps.sh
Executable file
7
build-scripts/lua/install-deps.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
luarocks install lrexlib-pcre2
|
||||
luarocks install luasocket
|
||||
luarocks install luasec
|
||||
luarocks install luv
|
||||
luarocks install luautf8
|
||||
luarocks install hx-lua-simdjson
|
||||
luarocks install bit32 || echo "bit32 not required"
|
2
build-scripts/lua/test.hxml
Normal file
2
build-scripts/lua/test.hxml
Normal file
@@ -0,0 +1,2 @@
|
||||
--lua bin/lua/main.lua
|
||||
--cmd lua bin/lua/main.lua
|
@@ -1,408 +0,0 @@
|
||||
package kiss;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.PositionTools;
|
||||
import sys.io.File;
|
||||
import haxe.io.Path;
|
||||
using haxe.io.Path;
|
||||
import kiss.Helpers;
|
||||
using kiss.Helpers;
|
||||
using tink.MacroApi;
|
||||
|
||||
#end
|
||||
|
||||
import kiss.Kiss;
|
||||
using kiss.Kiss;
|
||||
import kiss.ReaderExp;
|
||||
import kiss.Prelude;
|
||||
import kiss.cloner.Cloner;
|
||||
using StringTools;
|
||||
import hscript.Parser;
|
||||
import hscript.Interp;
|
||||
|
||||
typedef Continuation = () -> Void;
|
||||
typedef AsyncCommand = (AsyncEmbeddedScript, Continuation) -> Void;
|
||||
|
||||
class ObjectInterp<T> extends Interp {
|
||||
var obj:T;
|
||||
var fields:Map<String,Bool> = [];
|
||||
public function new(obj:T) {
|
||||
this.obj = obj;
|
||||
|
||||
for (field in Type.getInstanceFields(Type.getClass(obj))) {
|
||||
fields[field] = true;
|
||||
}
|
||||
|
||||
super();
|
||||
}
|
||||
|
||||
override function resolve(id:String):Dynamic {
|
||||
var fieldVal = Reflect.field(obj, id);
|
||||
if (fieldVal != null)
|
||||
return fieldVal;
|
||||
else
|
||||
return super.resolve(id);
|
||||
}
|
||||
|
||||
// TODO every method of setting variables should try to set them on the object,
|
||||
// but there are a lot of them and I might have missed some.
|
||||
|
||||
override function setVar(name:String, v:Dynamic) {
|
||||
if (Reflect.field(obj, name) != null) {
|
||||
Reflect.setField(obj, name, v);
|
||||
} else {
|
||||
super.setVar(name, v);
|
||||
}
|
||||
}
|
||||
|
||||
public override function expr( e : hscript.Expr ) : Dynamic {
|
||||
var curExpr = e;
|
||||
#if hscriptPos
|
||||
var e = e.e;
|
||||
#end
|
||||
switch( e ) {
|
||||
// Handle fuzzyMaps correctly:
|
||||
case EArray(e, index):
|
||||
var arr:Dynamic = expr(e);
|
||||
var index:Dynamic = expr(index);
|
||||
if (isMap(arr)) {
|
||||
if (kiss.FuzzyMapTools.isFuzzy(arr))
|
||||
return getMapValue(arr, kiss.FuzzyMapTools.bestMatch(arr, index));
|
||||
return getMapValue(arr, index);
|
||||
}
|
||||
else {
|
||||
return arr[index];
|
||||
}
|
||||
case ECall(e,params):
|
||||
switch( hscript.Tools.expr(e) ) {
|
||||
case EIdent(name) if (fields.exists(name)):
|
||||
var args = new Array();
|
||||
for( p in params )
|
||||
args.push(expr(p));
|
||||
return call(obj,expr(e),args);
|
||||
default:
|
||||
}
|
||||
default:
|
||||
}
|
||||
return super.expr(curExpr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Utility class for making statically typed, debuggable, ASYNC-BASED embedded Kiss-based DSLs.
|
||||
Examples are in the hollywoo project.
|
||||
**/
|
||||
class AsyncEmbeddedScript {
|
||||
private var instructions:Array<AsyncCommand> = null;
|
||||
private var breakPoints:Map<Int, () -> Bool> = [];
|
||||
private var onBreak:AsyncCommand = null;
|
||||
private var lastInstructionPointer = -1;
|
||||
private var labels:Map<String,Int> = [];
|
||||
private var noSkipInstructions:Map<Int,Bool> = [];
|
||||
|
||||
private var parser = new Parser();
|
||||
private var interp:ObjectInterp<AsyncEmbeddedScript>;
|
||||
public var interpVariables(get, null):Map<String,Dynamic>;
|
||||
private function get_interpVariables() {
|
||||
return interp.variables;
|
||||
}
|
||||
|
||||
private var hscriptInstructions:Map<Int,String> = [];
|
||||
private function hscriptInstructionFile() return "";
|
||||
|
||||
public function setBreakHandler(handler:AsyncCommand) {
|
||||
onBreak = handler;
|
||||
}
|
||||
|
||||
public function addBreakPoint(instruction:Int, ?condition:() -> Bool) {
|
||||
if (condition == null) {
|
||||
condition = () -> true;
|
||||
}
|
||||
breakPoints[instruction] = condition;
|
||||
}
|
||||
|
||||
public function removeBreakPoint(instruction:Int) {
|
||||
breakPoints.remove(instruction);
|
||||
}
|
||||
|
||||
public function new() {
|
||||
interp = new ObjectInterp(this);
|
||||
kiss.KissInterp.prepare(interp);
|
||||
if (hscriptInstructionFile().length > 0) {
|
||||
#if (sys || hxnodejs)
|
||||
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(hscriptInstructionFile()));
|
||||
for (key => value in cacheJson) {
|
||||
hscriptInstructions[Std.parseInt(key)] = value;
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
private function resetInstructions() {}
|
||||
|
||||
public function instructionCount() {
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
return instructions.length;
|
||||
}
|
||||
|
||||
#if test
|
||||
public var ranHscriptInstruction = false;
|
||||
#end
|
||||
private function runHscriptInstruction(instructionPointer:Int, cc:Continuation) {
|
||||
#if test
|
||||
ranHscriptInstruction = true;
|
||||
#end
|
||||
interp.variables['cc'] = cc;
|
||||
if (printCurrentInstruction)
|
||||
Prelude.print(hscriptInstructions[instructionPointer]);
|
||||
interp.execute(parser.parseString(hscriptInstructions[instructionPointer]));
|
||||
}
|
||||
|
||||
private function runInstruction(instructionPointer:Int, withBreakPoints = true) {
|
||||
lastInstructionPointer = instructionPointer;
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
if (withBreakPoints && breakPoints.exists(instructionPointer) && breakPoints[instructionPointer]()) {
|
||||
if (onBreak != null) {
|
||||
onBreak(this, () -> runInstruction(instructionPointer, false));
|
||||
}
|
||||
}
|
||||
var continuation = if (instructionPointer < instructions.length - 1) {
|
||||
() -> {
|
||||
// runInstruction may be called externally to skip through the script.
|
||||
// When this happens, make sure other scheduled continuations are canceled
|
||||
// by verifying that lastInstructionPointer hasn't changed
|
||||
if (lastInstructionPointer == instructionPointer) {
|
||||
runInstruction(instructionPointer + 1);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
() -> {};
|
||||
}
|
||||
if (hscriptInstructions.exists(instructionPointer)) {
|
||||
runHscriptInstruction(instructionPointer, continuation);
|
||||
} else {
|
||||
instructions[instructionPointer](this, continuation);
|
||||
}
|
||||
}
|
||||
|
||||
public function run(withBreakPoints = true) {
|
||||
runInstruction(0, withBreakPoints);
|
||||
}
|
||||
|
||||
private function skipToInstruction(ip:Int) {
|
||||
var lastCC = ()->runInstruction(ip);
|
||||
// chain together the unskippable instructions prior to running the requested ip
|
||||
var noSkipList = [];
|
||||
for (cIdx in lastInstructionPointer+1... ip) {
|
||||
if (noSkipInstructions.exists(cIdx)) {
|
||||
noSkipList.push(cIdx);
|
||||
}
|
||||
}
|
||||
if (noSkipList.length > 0) {
|
||||
var cc = null;
|
||||
cc = ()->{
|
||||
if (noSkipList.length == 0) {
|
||||
lastCC();
|
||||
} else {
|
||||
var inst = noSkipList.shift();
|
||||
lastInstructionPointer = inst;
|
||||
instructions[inst](this, cc);
|
||||
}
|
||||
};
|
||||
cc();
|
||||
} else {
|
||||
lastCC();
|
||||
}
|
||||
|
||||
// TODO remember whether breakpoints were requested
|
||||
}
|
||||
|
||||
public function skipToNextLabel() {
|
||||
var labelPointers = [for (ip in labels) ip];
|
||||
labelPointers.sort(Reflect.compare);
|
||||
for (ip in labelPointers) {
|
||||
if (ip > lastInstructionPointer) {
|
||||
skipToInstruction(ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function skipToLabel(name:String) {
|
||||
var ip = labels[name];
|
||||
if (lastInstructionPointer > ip) {
|
||||
throw "Rewinding AsyncEmbeddedScript is not implemented";
|
||||
}
|
||||
skipToInstruction(ip);
|
||||
}
|
||||
|
||||
public function labelRunners():Map<String,Void->Void> {
|
||||
return [for (label => ip in labels) label => () -> skipToInstruction(ip)];
|
||||
}
|
||||
|
||||
public var printCurrentInstruction = true;
|
||||
|
||||
#if macro
|
||||
public static function build(dslHaxelib:String, dslFile:String, scriptFile:String):Array<Field> {
|
||||
// trace('AsyncEmbeddedScript.build $dslHaxelib $dslFile $scriptFile');
|
||||
var k = Kiss.defaultKissState();
|
||||
|
||||
k.file = scriptFile;
|
||||
var classPath = Context.getPosInfos(Context.currentPos()).file;
|
||||
var loadingDirectory = Path.directory(classPath);
|
||||
var classFields = []; // Kiss.build() will already include Context.getBuildFields()
|
||||
|
||||
var hscriptInstructions:Map<String,String> = [];
|
||||
var cache:Map<String,String> = [];
|
||||
#if kissCache
|
||||
var cacheFile = scriptFile.withoutExtension().withoutDirectory() + ".cache.json";
|
||||
if (sys.FileSystem.exists(cacheFile)) {
|
||||
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(cacheFile));
|
||||
for (key => value in cacheJson)
|
||||
cache[key] = value;
|
||||
}
|
||||
#end
|
||||
|
||||
var hscriptInstructionFile = scriptFile.withoutExtension().withoutDirectory() + ".hscript.json";
|
||||
|
||||
var commandList:Array<Expr> = [];
|
||||
var labelsList:Array<Expr> = [];
|
||||
var noSkipList:Array<Expr> = [];
|
||||
|
||||
var labelNum = 0;
|
||||
k.macros["label"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
k.stateChanged = true;
|
||||
wholeExp.checkNumArgs(1, 1, '(label <label>)');
|
||||
var label = Prelude.symbolNameValue(args[0]);
|
||||
label = '${++labelNum}. '.lpad("0", 5) + label;
|
||||
labelsList.push(macro labels[$v{label}] = $v{commandList.length});
|
||||
|
||||
wholeExp.expBuilder().callSymbol("cc", []);
|
||||
};
|
||||
|
||||
k.macros["noSkip"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
k.stateChanged = true;
|
||||
wholeExp.checkNumArgs(1, null, '(noSkip <body...>)');
|
||||
noSkipList.push(macro noSkipInstructions[$v{commandList.length}] = true);
|
||||
|
||||
wholeExp.expBuilder().begin(args);
|
||||
}
|
||||
|
||||
if (dslHaxelib.length > 0) {
|
||||
dslFile = Path.join([Helpers.libPath(dslHaxelib), dslFile]);
|
||||
}
|
||||
|
||||
// This brings in the DSL's functions and global variables.
|
||||
// As a side-effect, it also fills the KissState with the macros and reader macros that make the DSL syntax
|
||||
classFields = classFields.concat(Kiss.build(dslFile, k));
|
||||
|
||||
if (Lambda.count(cache) > 0) {
|
||||
classFields.push({
|
||||
name: "hscriptInstructionFile",
|
||||
access: [AOverride],
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [],
|
||||
expr: macro return $v{hscriptInstructionFile}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
scriptFile = Path.join([loadingDirectory, scriptFile]);
|
||||
|
||||
Context.registerModuleDependency(Context.getLocalModule(), scriptFile);
|
||||
k.fieldList = [];
|
||||
Kiss._try(() -> {
|
||||
#if profileKiss
|
||||
Kiss.measure('Compiling kiss: $scriptFile', () -> {
|
||||
#end
|
||||
function process(nextExp) {
|
||||
#if kissCache
|
||||
var cacheKey = Reader.toString(nextExp.def);
|
||||
if (cache.exists(cacheKey)) {
|
||||
hscriptInstructions[Std.string(commandList.length)] = cache[cacheKey];
|
||||
commandList.push(macro null);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
nextExp = Kiss.macroExpand(nextExp, k);
|
||||
var stateChanged = k.stateChanged;
|
||||
|
||||
// Allow packing multiple commands into one exp with a (commands <...>) statement
|
||||
switch (nextExp.def) {
|
||||
case CallExp({pos: _, def: Symbol("commands")},
|
||||
commands):
|
||||
for (exp in commands) {
|
||||
process(exp);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
}
|
||||
|
||||
var exprString = Reader.toString(nextExp.def);
|
||||
var fieldCount = k.fieldList.length;
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k);
|
||||
if (expr == null || Kiss.isEmpty(expr))
|
||||
return;
|
||||
expr = macro { if (printCurrentInstruction) Prelude.print($v{exprString}); $expr; };
|
||||
expr = expr.expr.withMacroPosOf(nextExp);
|
||||
if (expr != null) {
|
||||
var c = macro function(self, cc) {
|
||||
$expr;
|
||||
};
|
||||
// If the expression didn't change the KissState when macroExpanding, it can be cached
|
||||
#if kissCache
|
||||
if (!stateChanged) {
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k.forHScript());
|
||||
cache[cacheKey] = expr.toString();
|
||||
}
|
||||
#end
|
||||
|
||||
commandList.push(c.expr.withMacroPosOf(nextExp));
|
||||
}
|
||||
|
||||
// This return is essential for type unification of concat() and push() above... ugh.
|
||||
return;
|
||||
}
|
||||
Reader.readAndProcess(Stream.fromFile(scriptFile), k, process);
|
||||
null;
|
||||
#if profileKiss
|
||||
});
|
||||
#end
|
||||
});
|
||||
|
||||
classFields = classFields.concat(k.fieldList);
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "resetInstructions",
|
||||
access: [APrivate, AOverride],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro {
|
||||
this.instructions = [$a{commandList}];
|
||||
$b{labelsList};
|
||||
$b{noSkipList};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
#if kissCache
|
||||
sys.io.File.saveContent(cacheFile, haxe.Json.stringify(cache));
|
||||
sys.io.File.saveContent(hscriptInstructionFile, haxe.Json.stringify(hscriptInstructions));
|
||||
#end
|
||||
|
||||
return classFields;
|
||||
}
|
||||
#end
|
||||
}
|
@@ -244,11 +244,13 @@ class AsyncEmbeddedScript2 {
|
||||
}
|
||||
|
||||
runWithErrorChecking(() -> {
|
||||
#if !lua
|
||||
if (hscriptInstructions.exists(instructionPointer)) {
|
||||
runHscriptInstruction(instructionPointer, skipping, continuation);
|
||||
} else {
|
||||
instructions[instructionPointer](this, skipping, continuation);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
instructions[instructionPointer](this, skipping, continuation);
|
||||
});
|
||||
|
||||
if (tryCallNextWithTailRecursion) {
|
||||
@@ -371,7 +373,7 @@ class AsyncEmbeddedScript2 {
|
||||
|
||||
var hscriptInstructions:Map<String,String> = [];
|
||||
var cache:Map<String,String> = [];
|
||||
#if kissCache
|
||||
#if (kissCache && !lua)
|
||||
var cacheFile = scriptFile.withoutExtension().withoutDirectory() + ".cache.json";
|
||||
if (sys.FileSystem.exists(cacheFile)) {
|
||||
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(cacheFile));
|
||||
@@ -458,7 +460,7 @@ class AsyncEmbeddedScript2 {
|
||||
Kiss.measure('Compiling kiss: $scriptFile', () -> {
|
||||
#end
|
||||
function process(nextExp) {
|
||||
#if kissCache
|
||||
#if (kissCache && !lua)
|
||||
var cacheKey = Reader.toString(nextExp.def);
|
||||
if (cache.exists(cacheKey)) {
|
||||
hscriptInstructions[Std.string(commandList.length)] = cache[cacheKey];
|
||||
@@ -467,7 +469,8 @@ class AsyncEmbeddedScript2 {
|
||||
}
|
||||
#end
|
||||
|
||||
nextExp = Kiss.macroExpand(nextExp, k);
|
||||
nextExp = Kiss._try(()->Kiss.macroExpand(nextExp, k));
|
||||
if (nextExp == null) return;
|
||||
var stateChanged = k.stateChanged;
|
||||
|
||||
// Allow packing multiple commands into one exp with a (commands <...>) statement
|
||||
@@ -483,7 +486,7 @@ class AsyncEmbeddedScript2 {
|
||||
|
||||
var exprString = Reader.toString(nextExp.def);
|
||||
var fieldCount = k.fieldList.length;
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k);
|
||||
var expr = Kiss._try(()->Kiss.readerExpToHaxeExpr(nextExp, k));
|
||||
if (expr == null || Kiss.isEmpty(expr))
|
||||
return;
|
||||
|
||||
@@ -516,9 +519,9 @@ class AsyncEmbeddedScript2 {
|
||||
$expr;
|
||||
};
|
||||
// If the expression didn't change the KissState when macroExpanding, it can be cached
|
||||
#if kissCache
|
||||
#if (kissCache && !lua)
|
||||
if (!stateChanged) {
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k.forHScript());
|
||||
var expr = Kiss._try(()->Kiss.readerExpToHaxeExpr(nextExp, k.forHScript()));
|
||||
cache[cacheKey] = expr.toString();
|
||||
}
|
||||
#end
|
||||
@@ -556,7 +559,7 @@ class AsyncEmbeddedScript2 {
|
||||
})
|
||||
});
|
||||
|
||||
#if kissCache
|
||||
#if (kissCache && !lua)
|
||||
sys.io.File.saveContent(cacheFile, haxe.Json.stringify(cache));
|
||||
sys.io.File.saveContent(hscriptInstructionFile, haxe.Json.stringify(hscriptInstructions));
|
||||
#end
|
||||
|
@@ -1,243 +0,0 @@
|
||||
package kiss;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.PositionTools;
|
||||
import sys.io.File;
|
||||
import haxe.io.Path;
|
||||
using kiss.Helpers;
|
||||
#end
|
||||
import kiss.Kiss;
|
||||
import kiss.cloner.Cloner;
|
||||
|
||||
// Commands handlers accept a Dynamic argument because when fork() happens
|
||||
// (1) the calling context can no longer assume the instance it constructed is the instance running the command.
|
||||
// (2) I don't know how to put a generic parameter <T extends EmbeddedScript> on the Command typedef.
|
||||
typedef Command = (Dynamic) -> Void;
|
||||
|
||||
/**
|
||||
Utility class for making statically typed, debuggable, embedded Kiss-based DSLs.
|
||||
Basic examples:
|
||||
kiss/src/test/cases/DSLTestCase.hx
|
||||
projects/aoc/year2020/BootCode.hx
|
||||
**/
|
||||
class EmbeddedScript {
|
||||
public var instructionPointer(default, null) = 0;
|
||||
|
||||
var running = false;
|
||||
|
||||
private var instructions:Array<Command> = null;
|
||||
private var breakPoints:Map<Int, () -> Bool> = [];
|
||||
private var onBreak:Command = null;
|
||||
|
||||
public function setBreakHandler(handler:Command) {
|
||||
onBreak = handler;
|
||||
}
|
||||
|
||||
public function addBreakPoint(instruction:Int, ?condition:() -> Bool) {
|
||||
if (condition == null) {
|
||||
condition = () -> true;
|
||||
}
|
||||
breakPoints[instruction] = condition;
|
||||
}
|
||||
|
||||
public function removeBreakPoint(instruction:Int) {
|
||||
breakPoints.remove(instruction);
|
||||
}
|
||||
|
||||
public function new() {}
|
||||
|
||||
#if macro
|
||||
public static function build(dslFile:String, scriptFile:String):Array<Field> {
|
||||
var k = Kiss.defaultKissState();
|
||||
k.file = scriptFile;
|
||||
|
||||
var classPath = Context.getPosInfos(Context.currentPos()).file;
|
||||
var loadingDirectory = Path.directory(classPath);
|
||||
var classFields = []; // Kiss.build() will already include Context.getBuildFields()
|
||||
|
||||
var commandList:Array<Expr> = [];
|
||||
|
||||
// This brings in the DSL's functions and global variables.
|
||||
// As a side-effect, it also fills the KissState with the macros and reader macros that make the DSL syntax
|
||||
classFields = classFields.concat(Kiss.build(dslFile, k));
|
||||
scriptFile = Path.join([loadingDirectory, scriptFile]);
|
||||
Context.registerModuleDependency(Context.getLocalModule(), scriptFile);
|
||||
|
||||
Kiss._try(() -> {
|
||||
#if profileKiss
|
||||
Kiss.measure('Compiling kiss: $scriptFile', () -> {
|
||||
#end
|
||||
Reader.readAndProcess(Stream.fromFile(scriptFile), k, (nextExp) -> {
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k);
|
||||
|
||||
if (expr != null) {
|
||||
var c = macro function(self) {
|
||||
$expr;
|
||||
};
|
||||
commandList.push(c.expr.withMacroPosOf(nextExp));
|
||||
}
|
||||
|
||||
// This return is essential for type unification of concat() and push() above... ugh.
|
||||
return;
|
||||
// TODO also allow label setting and multiple commands coming from the same expr?
|
||||
// Multiple things could come from the same expr by returning begin, or a call to a function that does more stuff
|
||||
// i.e. knot declarations need to end the previous knot, and BELOW that set a label for the new one, then increment the read count
|
||||
// TODO test await
|
||||
});
|
||||
null;
|
||||
#if profileKiss
|
||||
});
|
||||
#end
|
||||
});
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "resetInstructions",
|
||||
access: [APrivate],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro this.instructions = [$a{commandList}]
|
||||
})
|
||||
});
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "instructionCount",
|
||||
access: [APublic],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro {
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
return instructions.length;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "step",
|
||||
access: [APublic],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro {
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
instructions[instructionPointer](this);
|
||||
++instructionPointer;
|
||||
if (breakPoints.exists(instructionPointer) && breakPoints[instructionPointer]()) {
|
||||
running = false;
|
||||
if (onBreak != null) {
|
||||
onBreak(this);
|
||||
}
|
||||
} else if (instructionPointer < 0 || instructionPointer >= instructions.length) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "run",
|
||||
access: [APublic],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro {
|
||||
running = true;
|
||||
while (running) {
|
||||
step();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Start a process that needs to take control of the main thread, and will call back to resume the script
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "await",
|
||||
access: [APublic],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [
|
||||
{
|
||||
type: Helpers.parseComplexType("(()->Void)->Void", k, null),
|
||||
name: "c"
|
||||
}
|
||||
],
|
||||
expr: macro {
|
||||
running = false;
|
||||
c(run);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Fork the script down two or more different commands.
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "fork",
|
||||
access: [APublic],
|
||||
kind: FFun({
|
||||
ret: Helpers.parseComplexType("Array<EmbeddedScript>", k, null),
|
||||
args: [
|
||||
{
|
||||
type: Helpers.parseComplexType("Array<Command>", k, null),
|
||||
name: "commands"
|
||||
}
|
||||
],
|
||||
expr: macro {
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
return [
|
||||
for (command in commands) {
|
||||
// a fork needs to be a thorough copy that includes all of the EmbeddedScript subclass's
|
||||
// fields, otherwise DSL state will be lost when forking, which is unacceptable
|
||||
var fork = new kiss.cloner.Cloner().clone(this);
|
||||
fork.instructions[instructionPointer] = command;
|
||||
if (fork.breakPoints == null) {
|
||||
// This field is so much trouble to clone in C# because of its type
|
||||
fork.breakPoints = [for (point => condition in this.breakPoints) point => condition];
|
||||
}
|
||||
// trace('running a fork from ' + Std.string(instructionPointer + 1));
|
||||
fork.run();
|
||||
// trace("fork finished");
|
||||
fork;
|
||||
}
|
||||
];
|
||||
}
|
||||
})
|
||||
});
|
||||
return classFields;
|
||||
}
|
||||
#end
|
||||
}
|
@@ -18,26 +18,15 @@ using StringTools;
|
||||
typedef FieldFormFunction = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> Field;
|
||||
|
||||
class FieldForms {
|
||||
public static function addBuiltins(k:KissState) {
|
||||
var map:Map<String, FieldFormFunction> = [];
|
||||
|
||||
function renameAndDeprecate(oldName:String, newName:String) {
|
||||
var form = map[oldName];
|
||||
map[oldName] = (wholeExp, args, k) -> {
|
||||
KissError.warnFromExp(wholeExp, '$oldName has been renamed to $newName and deprecated');
|
||||
form(wholeExp, args, k);
|
||||
}
|
||||
map[newName] = form;
|
||||
k.formDocs[newName] = k.formDocs[oldName];
|
||||
}
|
||||
|
||||
public static function addBuiltins(k:KissState):Void {
|
||||
varOrProperty("var", k);
|
||||
varOrProperty("prop", k);
|
||||
|
||||
funcOrMethod("function", k);
|
||||
funcOrMethod("method", k);
|
||||
|
||||
return map;
|
||||
k.doc("redefineWithObjectArgs", 2, 3, '(redefineWithObjectArgs <function or method name> <new function or method name> <optional [<preserved list args...>]>)');
|
||||
k.fieldForms["redefineWithObjectArgs"] = redefineWithObjectArgs;
|
||||
}
|
||||
|
||||
static function fieldAccess(formName:String, fieldName:String, nameExp:ReaderExp, ?access:Array<Access>) {
|
||||
@@ -143,6 +132,111 @@ class FieldForms {
|
||||
}
|
||||
}
|
||||
|
||||
static function redefineWithObjectArgs(wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState):Field {
|
||||
switch (args[0].def) {
|
||||
case Symbol(field):
|
||||
var originalFunction = k.fieldDict[field];
|
||||
|
||||
if (originalFunction == null) {
|
||||
throw KissError.fromExp(wholeExp, 'Function or method $field does not exist to be redefined');
|
||||
}
|
||||
|
||||
switch (args[1].def) {
|
||||
case Symbol(newFieldName):
|
||||
var newField = {
|
||||
pos: wholeExp.macroPos(),
|
||||
name: newFieldName,
|
||||
meta: originalFunction.meta,
|
||||
access: originalFunction.access,
|
||||
kind: FFun(switch(originalFunction.kind) {
|
||||
case FFun({ret: ret, params: params, args: originalArgs}):
|
||||
var argIndexMap = new Map<String,Int>();
|
||||
var argMap = new Map<String,Null<FunctionArg>>();
|
||||
for (idx in 0... originalArgs.length) {
|
||||
var originalArg = originalArgs[idx];
|
||||
argIndexMap[originalArg.name] = idx;
|
||||
argMap[originalArg.name] = originalArg;
|
||||
}
|
||||
|
||||
var callExpArgs:Array<Expr> = [for (_ in 0... originalArgs.length) macro null];
|
||||
var newArgs = if (args.length > 2) {
|
||||
[for (argSymbol in Helpers.argList(args[2], "redefineWithObjectArgs"))
|
||||
switch (argSymbol.def) {
|
||||
case Symbol(argName):
|
||||
if (!argMap.exists(argName)) {
|
||||
throw KissError.fromExp(argSymbol, '$argName is not an argument in the original function or method $field');
|
||||
}
|
||||
var arg = argMap[argName];
|
||||
var index = argIndexMap[argName];
|
||||
argMap.remove(argName);
|
||||
argIndexMap.remove(argName);
|
||||
callExpArgs[index] = macro $i{argName};
|
||||
arg;
|
||||
default:
|
||||
throw KissError.fromExp(argSymbol, 'arguments in an arg list for (redefineWithObjectArgs...) should be plain symbols matching arg names of the original function or method');
|
||||
}
|
||||
];
|
||||
} else {
|
||||
[];
|
||||
};
|
||||
|
||||
var additionalArgsName = 'additionalArgs${uuid.Uuid.v4().replace("-", "_")}';
|
||||
var objectIsOpt = true;
|
||||
var fields:Array<Field> = [];
|
||||
for (argName => arg in argMap) {
|
||||
var type = arg.type;
|
||||
var meta = arg.meta;
|
||||
if (meta == null) meta = [];
|
||||
if (arg.opt == null || arg.opt == false) {
|
||||
objectIsOpt = false;
|
||||
} else {
|
||||
meta.push({pos: wholeExp.macroPos(), name: ":optional"});
|
||||
}
|
||||
fields.push({
|
||||
name: argName,
|
||||
pos: wholeExp.macroPos(),
|
||||
meta: meta,
|
||||
kind: FVar(type, null)
|
||||
});
|
||||
callExpArgs[argIndexMap[argName]] = macro $i{additionalArgsName}?.$argName;
|
||||
}
|
||||
var additionalArgType = TAnonymous(fields);
|
||||
newArgs.push({
|
||||
name: additionalArgsName,
|
||||
opt: objectIsOpt,
|
||||
type: additionalArgType
|
||||
});
|
||||
|
||||
var exp = macro $i{field}($a{callExpArgs});
|
||||
switch (ret) {
|
||||
case TPath({pack:[], name: "Void"}):
|
||||
default:
|
||||
exp = macro return $exp;
|
||||
}
|
||||
|
||||
{
|
||||
ret: ret,
|
||||
params: params,
|
||||
args: newArgs,
|
||||
expr: exp
|
||||
};
|
||||
default:
|
||||
throw KissError.fromExp(args[0], '$field is not a function or method');
|
||||
})
|
||||
};
|
||||
|
||||
return newField;
|
||||
|
||||
default:
|
||||
throw KissError.fromExp(wholeExp, "The second argument to (redefineWithObjectArgs...) should be a plain symbol of a new function or method name");
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
throw KissError.fromExp(args[0], "The first argument to (redefineWithObjectArgs...) should be a plain symbol of a function or method name");
|
||||
}
|
||||
}
|
||||
|
||||
static function funcOrMethod(formName:String, k:KissState) {
|
||||
k.doc(formName, 2, null, '($formName <optional &dynamic> <optional :Type> <name> [<argNames...>] <body...>)');
|
||||
k.fieldForms[formName] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
|
@@ -137,7 +137,7 @@ class Helpers {
|
||||
|
||||
public static function varName(formName:String, nameExp:ReaderExp, nameType = "variable") {
|
||||
return switch (nameExp.def) {
|
||||
case Symbol(name):
|
||||
case Symbol(name) if (!name.contains(".")):
|
||||
name;
|
||||
case MetaExp(_, nameExp) | TypedExp(_, nameExp):
|
||||
varName(formName, nameExp);
|
||||
@@ -229,10 +229,10 @@ class Helpers {
|
||||
{
|
||||
// These could use varName() and explicitType() but so far there are no &meta annotations for function arguments
|
||||
name: switch (funcArg.def) {
|
||||
case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}):
|
||||
case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}) if (!name.contains(".")):
|
||||
name;
|
||||
default:
|
||||
throw KissError.fromExp(funcArg, 'function argument should be a symbol or typed symbol');
|
||||
throw KissError.fromExp(funcArg, 'function argument should be a plain symbol or typed symbol');
|
||||
},
|
||||
type: switch (funcArg.def) {
|
||||
case TypedExp(type, _):
|
||||
@@ -535,6 +535,7 @@ class Helpers {
|
||||
interp.variables.set("ReaderExp", ReaderExpDef);
|
||||
interp.variables.set("nextToken", Reader.nextToken.bind(_, "a token"));
|
||||
interp.variables.set("printExp", printExp);
|
||||
interp.variables.set("macroExpand", Kiss.macroExpand.bind(_, k));
|
||||
interp.variables.set("kiss", {
|
||||
ReaderExp: {
|
||||
ReaderExpDef: ReaderExpDef
|
||||
@@ -559,10 +560,7 @@ class Helpers {
|
||||
for (name => value in k.macroVars) {
|
||||
interp.variables.set(name, value);
|
||||
}
|
||||
var locals = interp.getLocals();
|
||||
interp.setLocals(new Cloner().clone(locals));
|
||||
var value = interp.publicExprReturn(compileTimeHScript(innerExp, k));
|
||||
interp.setLocals(locals);
|
||||
if (value == null) {
|
||||
throw KissError.fromExp(exp, "compile-time evaluation returned null");
|
||||
}
|
||||
@@ -911,4 +909,59 @@ class Helpers {
|
||||
throw 'Could not find haxelib $haxelibName in class paths';
|
||||
}
|
||||
|
||||
// Like haxe.macro.ExprTools.map() but for Kiss ReaderExps
|
||||
public static function expMap(exp:ReaderExp, func:ReaderExp->ReaderExp):ReaderExp {
|
||||
var result = switch (exp.def) {
|
||||
case CallExp(f, args):
|
||||
CallExp(func(f), Lambda.map(args, func));
|
||||
case ListExp(args):
|
||||
ListExp(Lambda.map(args, func));
|
||||
case TypedExp(type, exp):
|
||||
TypedExp(type, func(exp));
|
||||
case MetaExp(meta, exp):
|
||||
MetaExp(meta, func(exp));
|
||||
case FieldExp(field, exp, safeField):
|
||||
FieldExp(field, func(exp), safeField);
|
||||
case KeyValueExp(key, value):
|
||||
KeyValueExp(func(key), func(value));
|
||||
case Quasiquote(exp):
|
||||
Quasiquote(func(exp));
|
||||
case Unquote(exp):
|
||||
Unquote(func(exp));
|
||||
case UnquoteList(exp):
|
||||
UnquoteList(func(exp));
|
||||
case ListEatingExp(exps):
|
||||
ListEatingExp(Lambda.map(exps, func));
|
||||
case TypeParams(types):
|
||||
TypeParams(Lambda.map(types, func));
|
||||
case HaxeMeta(name, params, exp):
|
||||
var newParams = if (params != null) {
|
||||
Lambda.map(params, func);
|
||||
} else {
|
||||
null;
|
||||
}
|
||||
HaxeMeta(name, newParams, func(exp));
|
||||
case StrExp(_) | Symbol(_) | RawHaxe(_) | RawHaxeBlock(_) | ListRestExp(_) | None:
|
||||
exp.def;
|
||||
};
|
||||
return result.withPosOf(exp);
|
||||
}
|
||||
|
||||
public static function expandTypeAliases(exp:ReaderExp, k:KissState) {
|
||||
return switch (exp.def) {
|
||||
case TypedExp(path, innerExp):
|
||||
TypedExp(replaceTypeAliases(path, k), innerExp).withPosOf(exp);
|
||||
default:
|
||||
expMap(exp, expandTypeAliases.bind(_, k));
|
||||
};
|
||||
}
|
||||
|
||||
public static function expandTypeSymbol(exp:ReaderExp, k:KissState) {
|
||||
return switch (exp.def) {
|
||||
case Symbol(path):
|
||||
Symbol(replaceTypeAliases(path, k)).withPosOf(exp);
|
||||
default:
|
||||
exp;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ typedef KissState = {
|
||||
endOfFileReadTable:ReadTable,
|
||||
fieldForms:Map<String, FieldFormFunction>,
|
||||
specialForms:Map<String, SpecialFormFunction>,
|
||||
specialFormMacroExpanders:Map<String, MacroFunction>,
|
||||
macros:Map<String, MacroFunction>,
|
||||
formDocs:Map<String, FormDoc>,
|
||||
doc:(String, Null<Int>, Null<Int>, ?String, ?String)->Void,
|
||||
@@ -100,6 +101,7 @@ class Kiss {
|
||||
endOfFileReadTable: new ReadTable(),
|
||||
fieldForms: new Map(),
|
||||
specialForms: null,
|
||||
specialFormMacroExpanders: null,
|
||||
macros: null,
|
||||
formDocs: new Map(),
|
||||
doc: null,
|
||||
@@ -215,6 +217,7 @@ class Kiss {
|
||||
|
||||
FieldForms.addBuiltins(k);
|
||||
k.specialForms = SpecialForms.builtins(k, context);
|
||||
k.specialFormMacroExpanders = SpecialForms.builtinMacroExpanders(k, context);
|
||||
k.macros = Macros.builtins(k);
|
||||
|
||||
return k;
|
||||
@@ -250,7 +253,9 @@ class Kiss {
|
||||
throw EExpected(expectedError);
|
||||
case null:
|
||||
printErr();
|
||||
Sys.exit(1);
|
||||
Context.onGenerate((types) -> {
|
||||
Sys.exit(1);
|
||||
});
|
||||
return null;
|
||||
default:
|
||||
printErr();
|
||||
@@ -265,7 +270,9 @@ class Kiss {
|
||||
throw EExpected(expectedError);
|
||||
case null:
|
||||
printErr();
|
||||
Sys.exit(1);
|
||||
Context.onGenerate((types) -> {
|
||||
Sys.exit(1);
|
||||
});
|
||||
return null;
|
||||
default:
|
||||
printErr();
|
||||
@@ -281,7 +288,9 @@ class Kiss {
|
||||
throw EExpected(expectedError);
|
||||
case null:
|
||||
printErr();
|
||||
Sys.exit(1);
|
||||
Context.onGenerate((types) -> {
|
||||
Sys.exit(1);
|
||||
});
|
||||
return null;
|
||||
default:
|
||||
printErr();
|
||||
@@ -523,7 +532,7 @@ class Kiss {
|
||||
|
||||
// readerExpToHaxeExpr must be called to process readermacro, alias, and macro definitions
|
||||
macroUsed = false;
|
||||
var expr = readerExpToHaxeExpr(nextExp, k);
|
||||
var expr = _try(()->readerExpToHaxeExpr(nextExp, k));
|
||||
|
||||
// exps in the loaded file that actually become haxe expressions can be inserted into the
|
||||
// file that loaded them at the position (load) was called.
|
||||
@@ -534,7 +543,7 @@ class Kiss {
|
||||
// cause double-evaluation of field forms
|
||||
if (loadAllExps) {
|
||||
loadedExps.push(nextExp);
|
||||
} else if (!isEmpty(expr)) {
|
||||
} else if (expr != null && !isEmpty(expr)) {
|
||||
// don't double-compile macros:
|
||||
if (macroUsed) {
|
||||
loadedExps.push(RawHaxe(expr.toString()).withPosOf(nextExp));
|
||||
@@ -614,6 +623,7 @@ class Kiss {
|
||||
var macros = k.macros;
|
||||
var fieldForms = k.fieldForms;
|
||||
var specialForms = k.specialForms;
|
||||
var specialFormMacroExpanders = k.specialFormMacroExpanders;
|
||||
var formDocs = k.formDocs;
|
||||
|
||||
// Bind the table arguments of this function for easy recursive calling/passing
|
||||
@@ -729,6 +739,9 @@ class Kiss {
|
||||
case CallExp({pos: _, def: Symbol(specialForm)}, args) if (specialForms.exists(specialForm) && !macroExpandOnly):
|
||||
checkNumArgs(specialForm);
|
||||
Right(Kiss.measure(specialForm, ()->specialForms[specialForm](exp, args.copy(), k), true));
|
||||
case CallExp({pos: _, def: Symbol(specialForm)}, args) if (specialFormMacroExpanders.exists(specialForm) && macroExpandOnly):
|
||||
checkNumArgs(specialForm);
|
||||
Left(specialFormMacroExpanders[specialForm](exp, args.copy(), k));
|
||||
case CallExp({pos: _, def: Symbol(alias)}, args) if (k.callAliases.exists(alias)):
|
||||
convert(CallExp(k.callAliases[alias].withPosOf(exp), args).withPosOf(exp));
|
||||
case CallExp(func, args):
|
||||
|
@@ -268,7 +268,7 @@ class Macros {
|
||||
addBodyIf("unless", "if", true);
|
||||
addBodyIf("#when", "#if", false);
|
||||
addBodyIf("#unless", "#if", true);
|
||||
|
||||
|
||||
addCond(k, macros, "cond", "if");
|
||||
addCond(k, macros, "#cond", "#if");
|
||||
|
||||
@@ -277,7 +277,7 @@ class Macros {
|
||||
var b = wholeExp.expBuilder();
|
||||
b.str(Context.definedValue(compileTimeResolveToString("The only argument to (#value...)", "a compiler flag's name", args[0], k)));
|
||||
};
|
||||
|
||||
|
||||
k.doc("#symbol", 1, 1, '(#symbol "<name>")');
|
||||
macros["#symbol"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
@@ -302,7 +302,7 @@ class Macros {
|
||||
|
||||
return b.let([b.typed("Dynamic", uniqueVarSymbol), firstVal], body);
|
||||
};
|
||||
|
||||
|
||||
|
||||
macros["or"] = _or;
|
||||
|
||||
@@ -349,7 +349,7 @@ class Macros {
|
||||
macros["assert"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
var expression = exps[0];
|
||||
|
||||
|
||||
var letVal = b.symbol();
|
||||
b.callSymbol("let", [
|
||||
b.list([
|
||||
@@ -418,7 +418,7 @@ class Macros {
|
||||
throw KissError.fromExp(exp, 'first argument to $formName should be a String or list of strings');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
k.doc("defmacro", 3, null, '(defMacro <name> [<args...>] <body...>)');
|
||||
macros["defmacro"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
k.stateChanged = true;
|
||||
@@ -451,7 +451,7 @@ class Macros {
|
||||
|
||||
var builderName:String = null;
|
||||
for (arg in argList) {
|
||||
|
||||
|
||||
switch (arg.def) {
|
||||
case MetaExp("builder", {pos: _, def: Symbol(name)}):
|
||||
if (builderName == null) {
|
||||
@@ -473,7 +473,7 @@ class Macros {
|
||||
macroCallForm += ' <?$name>';
|
||||
}
|
||||
++maxArgs;
|
||||
|
||||
|
||||
case MetaExp("opt", {pos: _, def: Symbol(name)}):
|
||||
argNames.push(name);
|
||||
macroCallForm += ' <?$name>';
|
||||
@@ -798,7 +798,7 @@ class Macros {
|
||||
switch (exps[1].def) {
|
||||
case CallExp({pos: _, def: Symbol("catch")}, catchArgs):
|
||||
exps.splice(1,1);
|
||||
rejectionHandler = b.symbol();
|
||||
rejectionHandler = b.symbol();
|
||||
rejectionHandlerArgsAndBody = catchArgs;
|
||||
default:
|
||||
usingDefaultHandler = true;
|
||||
@@ -833,7 +833,7 @@ class Macros {
|
||||
]),
|
||||
rejectionHandler
|
||||
]);
|
||||
|
||||
|
||||
if (rejectionHandlerArgsAndBody.length > 0) {
|
||||
exp = b.callSymbol("withFunctions", [
|
||||
b.list([b.call(b.typed("Dynamic", rejectionHandler),
|
||||
@@ -844,9 +844,9 @@ class Macros {
|
||||
|
||||
return exp;
|
||||
}
|
||||
|
||||
|
||||
macros["awaitLet"] = awaitLet.bind(null);
|
||||
|
||||
|
||||
k.doc("whileLet", 2, null, "(whileLet [<bindings...>] <body...>)");
|
||||
macros["whileLet"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
@@ -1027,7 +1027,7 @@ class Macros {
|
||||
k.stateChanged = true;
|
||||
var name = exps[0].symbolName().withPosOf(exps[0]);
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
|
||||
return b.callSymbol("_setMacroVar", [name, exps[1]]);
|
||||
};
|
||||
|
||||
@@ -1063,8 +1063,19 @@ class Macros {
|
||||
[];
|
||||
}
|
||||
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
for (exp in exps) {
|
||||
switch (exp.def) {
|
||||
// (objectWith obj.field) expands to (object field obj.field)
|
||||
case Symbol(withDots) if (withDots.indexOf(".") != -1):
|
||||
var fieldName = withDots.split(".").pop();
|
||||
objectExps.push(b.symbol(fieldName));
|
||||
objectExps.push(exp);
|
||||
// (objectWith .field <exp>) expands to (object field .field <exp>)
|
||||
case FieldExp(fieldName, innerExp, _):
|
||||
objectExps.push(b.symbol(fieldName));
|
||||
objectExps.push(exp);
|
||||
case Symbol(_):
|
||||
objectExps.push(exp);
|
||||
objectExps.push(exp);
|
||||
@@ -1073,7 +1084,6 @@ class Macros {
|
||||
}
|
||||
}
|
||||
|
||||
var b = wholeExp.expBuilder();
|
||||
b.callSymbol("object", objectExps);
|
||||
}
|
||||
|
||||
@@ -1353,14 +1363,14 @@ class Macros {
|
||||
macros["printAllNulls"] = printAll.bind(false, true);
|
||||
k.doc("printLocalNulls", 0, 0, "(printLocalNulls)");
|
||||
macros["printLocalNulls"] = printAll.bind(true, true);
|
||||
|
||||
|
||||
var savedVarFilename = null;
|
||||
k.doc("savedVarFile", 1, 1, '(savedVarFilename "<path>")');
|
||||
macros["savedVarFile"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
savedVarFilename = compileTimeResolveToString("The only argument to (savedVarFile...)", "a json filename", exps[0], k);
|
||||
null;
|
||||
};
|
||||
|
||||
|
||||
k.doc("savedVar", 2, 2, "(savedVar <:Type> <name> <initial value>)");
|
||||
macros["savedVar"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
@@ -1375,7 +1385,7 @@ class Macros {
|
||||
} else {
|
||||
b.str("." + k.className + ".json");
|
||||
};
|
||||
|
||||
|
||||
function ifLetFileJson(thenBlock:Array<ReaderExp>, elseBlock:Array<ReaderExp>) {
|
||||
return b.callSymbol("if", [
|
||||
b.callSymbol("and", [
|
||||
@@ -1391,34 +1401,34 @@ class Macros {
|
||||
b.begin(elseBlock)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
var setAndSave = [
|
||||
b.callSymbol("dictSet", [b.symbol("json"), b.str(nameString), b.raw("tink.Json.stringify(v)")]),
|
||||
b.callSymbol("sys.io.File.saveContent", [filename, b.raw("haxe.Json.stringify(json)")]),
|
||||
b.raw("v;")
|
||||
];
|
||||
|
||||
|
||||
b.begin([
|
||||
b.callSymbol("var", [name, b.callSymbol("property", [b.symbol("get"), b.symbol("set")])]),
|
||||
b.callSymbol(
|
||||
"function", [
|
||||
b.typed(type, b.symbol('get_${nameString}')),
|
||||
b.list([]),
|
||||
b.typed(type, b.symbol('get_${nameString}')),
|
||||
b.list([]),
|
||||
ifLetFileJson([
|
||||
b.callSymbol("if", [
|
||||
b.callSymbol("json.exists", [b.str(nameString)]),
|
||||
b.raw("{ var v:" + type + " = tink.Json.parse(json['" + nameString + "']); v;}"),
|
||||
initialValue
|
||||
])
|
||||
],
|
||||
],
|
||||
[
|
||||
initialValue
|
||||
])
|
||||
]),
|
||||
b.callSymbol(
|
||||
"function", [
|
||||
b.typed(type, b.symbol('set_${nameString}')),
|
||||
b.list([b.typed(type, b.symbol("v"))]),
|
||||
b.typed(type, b.symbol('set_${nameString}')),
|
||||
b.list([b.typed(type, b.symbol("v"))]),
|
||||
ifLetFileJson(
|
||||
setAndSave,
|
||||
[
|
||||
@@ -1481,7 +1491,7 @@ class Macros {
|
||||
default:
|
||||
throw KissError.fromExp(funcExp, "withFunctions function definition should follow this form: (<funcName> [<args...>] <body...>)");
|
||||
}
|
||||
|
||||
|
||||
} while (funcList.length > 0);
|
||||
|
||||
var exp = b.begin(localFunctions.concat(args.slice(1)));
|
||||
@@ -1508,7 +1518,7 @@ class Macros {
|
||||
};
|
||||
null;
|
||||
};
|
||||
|
||||
|
||||
k.doc("typeCase", 2, null, "(typeCase [<values>] ([:<Type> <name> <more typed names...>] <body>) <more cases...> (otherwise <required default>))");
|
||||
macros["typeCase"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
@@ -1563,12 +1573,21 @@ class Macros {
|
||||
default: c;
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
b.let(outerLetBindings, [
|
||||
b.callSymbol("case", [b.list(symbols)].concat(cases))
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
k.doc("array", 1, null, "(array <element Type> <elements...>)");
|
||||
macros["array"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
var arraySymbol = b.symbol();
|
||||
var typeName = Prelude.symbolNameValue(Helpers.expandTypeSymbol(args[0], k));
|
||||
|
||||
b.let([b.typed('Array<${typeName}>', arraySymbol), b.list([])], [for (arg in args.slice(1)) b.call(b.field("push", arraySymbol), [arg])].concat([arraySymbol]));
|
||||
};
|
||||
return macros;
|
||||
}
|
||||
|
||||
@@ -1599,7 +1618,7 @@ class Macros {
|
||||
var patternTypePath = Prelude.symbolNameValue(type);
|
||||
return switch (instance.def) {
|
||||
case TypedExp(typePath, instanceExp) if (typePath == patternTypePath):
|
||||
matchExpr(patternExp, instanceExp);
|
||||
matchExpr(patternExp, instanceExp);
|
||||
default:
|
||||
false;
|
||||
};
|
||||
|
@@ -47,6 +47,7 @@ enum KissTarget {
|
||||
NodeJS;
|
||||
Python;
|
||||
Macro;
|
||||
Lua;
|
||||
}
|
||||
|
||||
class Prelude {
|
||||
|
@@ -6,6 +6,7 @@ import kiss.Reader;
|
||||
import kiss.ReaderExp;
|
||||
import uuid.Uuid;
|
||||
import kiss.Kiss;
|
||||
import kiss.Macros;
|
||||
|
||||
using uuid.Uuid;
|
||||
using kiss.Reader;
|
||||
@@ -25,11 +26,16 @@ class SpecialForms {
|
||||
|
||||
var compileTimeResolveToString = Helpers.compileTimeResolveToString;
|
||||
|
||||
function renameAndDeprecate(oldName:String, newName:String) {
|
||||
function renameAndDeprecate(oldName:String, newName:String, full = true) {
|
||||
var form = map[oldName];
|
||||
map[oldName] = (wholeExp, args, k) -> {
|
||||
KissError.warnFromExp(wholeExp, '$oldName has been renamed to $newName and deprecated');
|
||||
form(wholeExp, args, k);
|
||||
if (full) {
|
||||
throw KissError.fromExp(wholeExp, '$oldName has been renamed to $newName and removed from kisslang');
|
||||
}
|
||||
else {
|
||||
KissError.warnFromExp(wholeExp, '$oldName has been renamed to $newName and deprecated');
|
||||
form(wholeExp, args, k);
|
||||
}
|
||||
}
|
||||
map[newName] = form;
|
||||
k.formDocs[newName] = k.formDocs[oldName];
|
||||
@@ -40,12 +46,25 @@ class SpecialForms {
|
||||
|
||||
// blocks can contain field forms that don't return an expression. These can't be included in blocks
|
||||
var exprs = [];
|
||||
var lastArg = null;
|
||||
if (args.length > 1) {
|
||||
lastArg = args.pop();
|
||||
}
|
||||
for (bodyExp in args) {
|
||||
switch(bodyExp.def) {
|
||||
case Symbol(_) if (lastArg != null):
|
||||
KissError.warnFromExp(bodyExp, "This looks like an unused value");
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
var expr = k.convert(bodyExp);
|
||||
if (expr != null) {
|
||||
exprs.push(expr);
|
||||
}
|
||||
}
|
||||
if (lastArg != null)
|
||||
exprs.push(k.convert(lastArg));
|
||||
EBlock(exprs).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
@@ -99,7 +118,7 @@ class SpecialForms {
|
||||
{
|
||||
quotes: Unquoted,
|
||||
field: switch (pair[0].def) {
|
||||
case Symbol(name): name;
|
||||
case Symbol(name) if (!name.contains(".")): name;
|
||||
case TypedExp(_,
|
||||
{pos: _, def: Symbol(_)}): throw KissError.fromExp(pair[0], "type specification on anonymous objects will be ignored");
|
||||
default: throw KissError.fromExp(pair[0], "first expression in anonymous object field binding should be a plain symbol");
|
||||
@@ -252,7 +271,7 @@ class SpecialForms {
|
||||
}
|
||||
EFunction(FAnonymous, Helpers.makeFunction(null, returnsValue, args[0], args.slice(1), k, "lambda", [])).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
|
||||
k.doc("localFunction", 3, null, "(localFunction <optional :Type> <name> [<args...>] <body...>)");
|
||||
map["localFunction"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var name = "";
|
||||
@@ -271,27 +290,27 @@ class SpecialForms {
|
||||
}
|
||||
EFunction(FNamed(name, false), Helpers.makeFunction(null, returnsValue, args[1], args.slice(2), k, "localFunction", [])).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
|
||||
function forExpr(formName:String, wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) {
|
||||
var uniqueVarName = "_" + Uuid.v4().toShort();
|
||||
var namesExp = args[0];
|
||||
var listExp = args[1];
|
||||
var bodyExps = args.slice(2);
|
||||
|
||||
|
||||
var b = wholeExp.expBuilder();
|
||||
var m = macro $i{uniqueVarName};
|
||||
|
||||
var innerLet = false;
|
||||
var varsInScope = [];
|
||||
var loopVarExpr:Expr = switch (namesExp.def) {
|
||||
case KeyValueExp({pos: _, def: Symbol(s1)}, {pos: _, def: Symbol(s2)}):
|
||||
case KeyValueExp({pos: _, def: Symbol(s1)}, {pos: _, def: Symbol(s2)}):
|
||||
varsInScope.push({name:s1});
|
||||
varsInScope.push({name:s2});
|
||||
k.convert(namesExp);
|
||||
case Symbol(s):
|
||||
case Symbol(s):
|
||||
varsInScope.push({name:s});
|
||||
k.convert(namesExp);
|
||||
case ListExp(_) | TypedExp(_, {pos:_, def:Symbol(_)}):
|
||||
case ListExp(_) | TypedExp(_, {pos:_, def:Symbol(_)}):
|
||||
innerLet = true;
|
||||
b.haxeExpr(m);
|
||||
default:
|
||||
@@ -330,7 +349,7 @@ class SpecialForms {
|
||||
EWhile(macro true, k.convert(wholeExp.expBuilder().begin(args)), true).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
|
||||
|
||||
function whileForm(invert:Bool, wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) {
|
||||
var funcName = if (invert) "until" else "while";
|
||||
var b = wholeExp.expBuilder();
|
||||
@@ -348,13 +367,13 @@ class SpecialForms {
|
||||
map["until"] = whileForm.bind(true);
|
||||
|
||||
k.doc("return", 0, 1, '(return <?value>)');
|
||||
map["return"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
map["return"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var returnExpr = if (args.length == 1) k.convert(args[0]) else null;
|
||||
EReturn(returnExpr).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
|
||||
k.doc("break", 0, 0, "(break)");
|
||||
map["break"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
map["break"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
EBreak.withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
@@ -378,7 +397,7 @@ class SpecialForms {
|
||||
var cases:kiss.List<ReaderExp> = [for (c in args.slice(1)) {
|
||||
c.expBuilder().neverCase();
|
||||
}];
|
||||
|
||||
|
||||
Helpers.checkNoEarlyOtherwise(cases);
|
||||
|
||||
var isTupleCase = switch (args[0].def) {
|
||||
@@ -405,7 +424,7 @@ class SpecialForms {
|
||||
|
||||
var canCompareNull = !isTupleCase;
|
||||
|
||||
|
||||
|
||||
// case also override's haxe's switch() behavior by refusing to match null values against <var> patterns.
|
||||
if (canCompareNull) {
|
||||
var nullExpr = defaultExpr;
|
||||
@@ -426,7 +445,7 @@ class SpecialForms {
|
||||
}
|
||||
|
||||
var nullCase = if (k.hscript) {
|
||||
b.callSymbol("null", [b.raw(nullExpr.toString())]);
|
||||
b.callSymbol("null", [b.raw(nullExpr.toString())]);
|
||||
} else {
|
||||
var gensym = b.symbol();
|
||||
b.call(b.callSymbol("when", [b.callSymbol("Prelude.isNull", [gensym]), gensym]), [b.raw(nullExpr.toString())]);
|
||||
@@ -439,11 +458,12 @@ class SpecialForms {
|
||||
};
|
||||
|
||||
// Type check syntax:
|
||||
k.doc("the", 2, 3, '(the <?package> <type> <value>)');
|
||||
k.doc("the", 2, 3, '(the <type> <value>)');
|
||||
map["the"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var pkg = "";
|
||||
var whichArg = "first";
|
||||
if (args.length == 3) {
|
||||
throw KissError.fromExp(wholeExp, "(the <package> <Type> <value>) form is no longer allowed. use (the <package.Type> <value>) instead");
|
||||
pkg = switch (args.shift().def) {
|
||||
case Symbol(pkg): pkg;
|
||||
default: throw KissError.fromExp(wholeExp, '$whichArg argument to (the... ) should be a valid haxe package');
|
||||
@@ -609,7 +629,7 @@ class SpecialForms {
|
||||
context.addImport(Reader.toString(exps[0].def), IAll, wholeExp.macroPos());
|
||||
return none(wholeExp);
|
||||
};
|
||||
|
||||
|
||||
k.doc("using", 1, null, "(using <Types...>)");
|
||||
map["using"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
|
||||
requireContext(wholeExp, "using");
|
||||
@@ -648,7 +668,109 @@ class SpecialForms {
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
public static function builtinMacroExpanders(k:KissState, ?context:FrontendContext) {
|
||||
var map:Map<String, MacroFunction> = [];
|
||||
var macroExpand = Kiss.macroExpand.bind(_, k);
|
||||
var expandTypeAliases = Helpers.expandTypeAliases.bind(_, k);
|
||||
// when macroExpanding an (object) expression, don't apply aliases to the field names
|
||||
map["object"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
var pairs = Lambda.flatten([for (pair in args.groups(2)) {
|
||||
[pair[0], macroExpand(pair[1])];
|
||||
}]);
|
||||
b.callSymbol("object", pairs);
|
||||
};
|
||||
|
||||
map["lambda"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
b.callSymbol("lambda", [expandTypeAliases(args[0])].concat([for (exp in args.slice(1)) macroExpand(exp)]));
|
||||
};
|
||||
|
||||
map["localVar"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
b.callSymbol("localVar", [expandTypeAliases(args[0]), macroExpand(args[1])]);
|
||||
};
|
||||
|
||||
map["let"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
var bindings = args[0];
|
||||
var bindingsList = Helpers.argList(bindings, "let", false);
|
||||
var newBindingsList = Lambda.flatten([
|
||||
for (pair in bindingsList.groups(2)) {
|
||||
[expandTypeAliases(pair[0]), macroExpand(pair[1])];
|
||||
}
|
||||
]);
|
||||
var newBindings = b.list(newBindingsList);
|
||||
b.callSymbol("let", [newBindings].concat(Lambda.map(args.slice(1), macroExpand)));
|
||||
};
|
||||
|
||||
map["localFunction"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
b.callSymbol("localFunction", [expandTypeAliases(args[0])].concat(args.slice(1).map(macroExpand)));
|
||||
};
|
||||
|
||||
function forExpander (keyword:String) {
|
||||
map[keyword] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
b.callSymbol(keyword, [expandTypeAliases(args[0])].concat(args.slice(1).map(macroExpand)));
|
||||
};
|
||||
}
|
||||
forExpander("for");
|
||||
forExpander("doFor");
|
||||
|
||||
map["the"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
b.callSymbol("the", [Helpers.expandTypeSymbol(args[0], k), macroExpand(args[1])]);
|
||||
};
|
||||
|
||||
map["cast"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
var newArgs = [macroExpand(args[0])];
|
||||
|
||||
if (args.length == 2) {
|
||||
newArgs.push(Helpers.expandTypeSymbol(args[1], k));
|
||||
}
|
||||
|
||||
b.callSymbol("cast", newArgs);
|
||||
};
|
||||
|
||||
map["try"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
|
||||
var tryKissExp = args[0];
|
||||
var catchKissExps = args.slice(1);
|
||||
|
||||
var newCatchExps = [
|
||||
for (catchExp in catchKissExps) {
|
||||
switch (catchExp.def) {
|
||||
case CallExp({def:Symbol("catch")}, catchBlockArgs):
|
||||
b.callSymbol("catch", [expandTypeAliases(catchBlockArgs[0])].concat(Lambda.map(catchBlockArgs.slice(1), macroExpand)));
|
||||
default:
|
||||
catchExp;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
b.callSymbol("try", [macroExpand(tryKissExp)].concat(newCatchExps));
|
||||
};
|
||||
|
||||
function identity(wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) {
|
||||
return wholeExp;
|
||||
}
|
||||
map["import"] = identity;
|
||||
map["importAs"] = identity;
|
||||
map["importAll"] = identity;
|
||||
map["using"] = identity;
|
||||
map["extends"] = identity;
|
||||
map["implements"] = identity;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static function caseOr(wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState):Expr {
|
||||
wholeExp.checkNumArgs(2, null, "(or <pattern1> <pattern2> <patterns...>)");
|
||||
var b = wholeExp.expBuilder();
|
||||
|
@@ -335,7 +335,7 @@ class BasicTestCase extends Test {
|
||||
}
|
||||
|
||||
function testEval() {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !lua)
|
||||
_testEvalStatic();
|
||||
_testEval();
|
||||
#else
|
||||
@@ -374,15 +374,15 @@ class BasicTestCase extends Test {
|
||||
function testQuickFractions() {
|
||||
_testQuickFractions();
|
||||
}
|
||||
|
||||
|
||||
function testWhenLetGuards() {
|
||||
_testWhenLetGuards();
|
||||
}
|
||||
|
||||
|
||||
function testImportAndUsingInBuildMacro() {
|
||||
_testImportAndUsingInBuildMacro();
|
||||
}
|
||||
|
||||
|
||||
function testPureKissClasses() {
|
||||
_testPureKissClasses();
|
||||
}
|
||||
@@ -424,7 +424,7 @@ class BasicTestCase extends Test {
|
||||
_testTypeCase();
|
||||
}
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !lua)
|
||||
function testTryProcess() {
|
||||
// tryProcess returns null on failure:
|
||||
Assert.equals(null, Prelude.tryProcess("_ThisCoMMaNDWillSURElYFaiLLLLLL", [], error->{return;}));
|
||||
@@ -453,6 +453,18 @@ class BasicTestCase extends Test {
|
||||
_testStreamRecording();
|
||||
}
|
||||
|
||||
function testObjectWith() {
|
||||
_testObjectWith();
|
||||
}
|
||||
|
||||
function testRedefineWithObjectArgs() {
|
||||
_testRedefineWithObjectArgs();
|
||||
}
|
||||
|
||||
function testTypedArrayMacro() {
|
||||
_testTypedArrayMacro();
|
||||
}
|
||||
|
||||
var aNullToPrint = null;
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
(catch [:String message]
|
||||
(Assert.equals "src/test/cases/BasicTestCase.kiss:4:13: Assertion failed: false should have been true
|
||||
From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" message)))
|
||||
|
||||
|
||||
(assert true)
|
||||
(assert ![])
|
||||
(assertEquals 6 6 6))
|
||||
@@ -25,7 +25,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
// #| ... |# parses and injects raw Haxe code.
|
||||
// Order of operations will apply
|
||||
(Assert.equals 23 #|5 + 6 * 3|#)
|
||||
// #{ ... }# parses and injects a raw Haxe block. It is preferred over #| |#
|
||||
// #{ ... }# parses and injects a raw Haxe block. It is preferred over #| |#
|
||||
(let [&mut a 5 &mut b 6]
|
||||
#{
|
||||
a += 6; b += 5;
|
||||
@@ -35,7 +35,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
|
||||
|
||||
// (function) declares static functions
|
||||
(function myFloor [num]
|
||||
(function myFloor [num]
|
||||
// funcalls can use dot access
|
||||
(Math.floor num))
|
||||
|
||||
@@ -64,7 +64,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(Assert.equals -2 (- 5 4 3))
|
||||
(Assert.equals -2 (- 2)))
|
||||
|
||||
(function _testMultiplication []
|
||||
(function _testMultiplication []
|
||||
(Assert.equals 60 (* 2 5 6))
|
||||
(Assert.equals 5522401584 (* 84 289 89 71 36))
|
||||
(Assert.equals "heyheyhey" (* "hey" 3)))
|
||||
@@ -86,7 +86,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(Assert.isTrue (< 1 2 3 4))
|
||||
(Assert.isFalse (< 1 1 3 4))
|
||||
(Assert.isFalse (< 1 12 12)))
|
||||
|
||||
|
||||
(function _testLesserEqual []
|
||||
(Assert.isTrue (<= 1 2 3 4))
|
||||
(Assert.isTrue (<= 1 1 3 4))
|
||||
@@ -178,13 +178,13 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
|
||||
(function _testCond []
|
||||
(Assert.equals "this one"
|
||||
(cond
|
||||
(cond
|
||||
((= 5 6) "not this")
|
||||
((= 8 9) "not this either")
|
||||
((= 1 1) "this one")
|
||||
(true "not the default")))
|
||||
(Assert.equals "the default"
|
||||
(cond
|
||||
(cond
|
||||
((= 5 6) "not this")
|
||||
((= 8 9) "not this either")
|
||||
((= 2 1) "not the third one")
|
||||
@@ -218,7 +218,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(var myNot1 (not 5))
|
||||
(var myNot2 !5)
|
||||
|
||||
(var myFilteredList (begin
|
||||
(var myFilteredList (begin
|
||||
(localVar l [-1 -2 5 -3 6])
|
||||
(l.filter (lambda [v] (< 0 v)))))
|
||||
|
||||
@@ -412,7 +412,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(Assert.equals 5 v)
|
||||
(Assert.isTrue (Type.enumEq (Some 5) inner)))
|
||||
(otherwise (Assert.fail)))
|
||||
|
||||
|
||||
// Otherwise blocks should allow multiple expressions, too:
|
||||
(case 5
|
||||
(otherwise
|
||||
@@ -427,6 +427,9 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(assertLet [(Some _) (indexOf ["hey" "found"] key)] 0)
|
||||
(assertLet [(Some _) (indexOf ["you" "me"] value)] 0))
|
||||
|
||||
// This demonstrates a nice compiler warning when you make a certain mistake:
|
||||
(when myMap.exists "hey" (print "always true"))
|
||||
|
||||
// Map destructuring:
|
||||
(let [[=>"hey" v1 =>"found" v2] myMap]
|
||||
(Assert.equals "you" v1)
|
||||
@@ -475,7 +478,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(localVar message1 (ReaderExp.StrExp "Welcome "))
|
||||
(localVar message2 (ReaderExp.StrExp " (Guest #"))
|
||||
(localVar message3 (ReaderExp.StrExp ")"))
|
||||
|
||||
|
||||
`(begin (set welcomeCount (+ welcomeCount 1))
|
||||
(+ ,message1 ,name ,message2 (Std.string welcomeCount) ,message3)))
|
||||
|
||||
@@ -530,16 +533,16 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(unlessLet [(Some (or 5 6)) some5]
|
||||
(print "something else went wrong!")
|
||||
(Assert.fail))
|
||||
|
||||
// Don't double evaluate the expression:
|
||||
|
||||
// Don't double evaluate the expression:
|
||||
(let [&mut v 1]
|
||||
(unlessLet [2 (begin (+= v 1) v)]
|
||||
(Assert.fail))
|
||||
(Assert.equals 2 v))
|
||||
|
||||
|
||||
(assertThrows (assertLet [(Some thing) none] thing))
|
||||
(Assert.equals 5 (assertLet [(Some thing) some5] thing)))
|
||||
|
||||
|
||||
// Issue #64 regression tests:
|
||||
(whenLet [something null]
|
||||
(Assert.fail))
|
||||
@@ -595,7 +598,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(function _testLetThrow []
|
||||
(try
|
||||
{
|
||||
(letThrow
|
||||
(letThrow
|
||||
(throw "the error we want")
|
||||
(catch [e] (Assert.fail)))
|
||||
(Assert.fail)}
|
||||
@@ -609,6 +612,16 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(function _testDotAccessOnAlias []
|
||||
(Assert.equals 5 owf.field))
|
||||
|
||||
(function _testObjectWith []
|
||||
(let [obj (object name "obby" purpose "idk" id 5)
|
||||
nil null
|
||||
id 7
|
||||
objWith (objectWith obj.name .purpose obj id nil?.notAField)]
|
||||
(Assert.equals "obby" objWith.name)
|
||||
(Assert.equals "idk" objWith.purpose)
|
||||
(Assert.equals 7 objWith.id)
|
||||
(Assert.equals null objWith.notAField)))
|
||||
|
||||
(function _testClamp []
|
||||
(let [&mut bigValue 12
|
||||
&mut smallValue 3]
|
||||
@@ -739,7 +752,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(assertThrows (<= a))
|
||||
(assertThrows (> a))
|
||||
(assertThrows (>= a))
|
||||
|
||||
|
||||
)
|
||||
(Assert.pass))
|
||||
|
||||
@@ -809,7 +822,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(localFunction c [val] (+ (a val) (b val)))
|
||||
(Assert.equals 5 (c 1))))
|
||||
|
||||
(function _testWithTempSet []
|
||||
(function _testWithTempSet []
|
||||
(let [&mut v 5
|
||||
&mut v2 3]
|
||||
(assertEquals 5 v)
|
||||
@@ -841,13 +854,13 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(Assert.isTrue (savedPrints.contains "anotherStaticNullToPrint: null"))
|
||||
(Assert.isTrue (savedPrints.contains "nullDefinedInHaxe: null"))
|
||||
(Assert.isTrue (savedPrints.contains "staticNullDefinedInHaxe: null"))
|
||||
|
||||
|
||||
// TODO This statement prints a warning at compile-time, which won't
|
||||
// appear in savedPrints, so it's harder to test...
|
||||
(prop tp "bad")
|
||||
|
||||
|
||||
(let [u null]
|
||||
// TODO this statement should print a warning at compile-time (#112)
|
||||
// TODO this statement should print a warning at compile-time (#112)
|
||||
(localVar v "bad")
|
||||
|
||||
(set savedPrints [])
|
||||
@@ -867,21 +880,21 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(var ts "bad")
|
||||
|
||||
(let [u null]
|
||||
// TODO this statement should print a warning at compile-time (#112)
|
||||
// TODO this statement should print a warning at compile-time (#112)
|
||||
(localVar v "bad")
|
||||
|
||||
(set savedPrints [])
|
||||
(printLocalNulls)
|
||||
(Assert.isFalse (savedPrints.contains "anotherStaticNullToPrint: null"))
|
||||
(Assert.isTrue (savedPrints.contains "u: null")))
|
||||
|
||||
|
||||
// Test for loop capture variables
|
||||
(set savedPrints [])
|
||||
(doFor a (for _ (range 5) null)
|
||||
(printLocalNulls))
|
||||
(Assert.isTrue (savedPrints.contains "a: null"))
|
||||
(Assert.equals 5 savedPrints.length)
|
||||
|
||||
|
||||
(set savedPrints [])
|
||||
(let [:Map<String,String> m (for a (range 5) =>"$a" "a")]
|
||||
(doFor =>k v m
|
||||
@@ -896,7 +909,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(printLocalNulls)))
|
||||
(Assert.isTrue (savedPrints.contains "v: null"))
|
||||
(Assert.equals 5 savedPrints.length)
|
||||
|
||||
|
||||
(set savedPrints [])
|
||||
(doFor [a b c] (for _ (range 5) [1 null 5])
|
||||
(printLocalNulls))
|
||||
@@ -941,7 +954,7 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(never otherwise)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(function _testQuickPrintOnSetVar []
|
||||
(let [&mut v 5]
|
||||
@@ -977,4 +990,28 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(let [stream (Stream.fromString " , , , ,abababababcab.ccab.")]
|
||||
(Assert.equals " , , , ," (stream.expect "" ->(stream.takeWhileOneOf (.split ", " ""))))
|
||||
(Assert.equals "ababababab" (stream.expect "" ->(stream.takeWhileOneOf (.split "ab" ""))))
|
||||
(Assert.equals "cab.ccab" (stream.expect "" ->(stream.takeWhileOneOf ["cab" ".c"])))))
|
||||
(Assert.equals "cab.ccab" (stream.expect "" ->(stream.takeWhileOneOf ["cab" ".c"])))))
|
||||
|
||||
(function funcWithMultipleArgs [:Int num :String str :Float num2]
|
||||
(+ str (+ num num2)))
|
||||
|
||||
(function :Void voidFuncWithMultipleArgs [:Int num :String str :Float num2]
|
||||
(+ str (+ num num2)))
|
||||
|
||||
(function funcWithMultipleOptArgs [&opt :Int num :String str :Float num2]
|
||||
null)
|
||||
|
||||
(redefineWithObjectArgs funcWithMultipleArgs funcWithObjectArgs [str])
|
||||
(redefineWithObjectArgs voidFuncWithMultipleArgs voidFuncWithObjectArgs [str])
|
||||
(redefineWithObjectArgs funcWithMultipleOptArgs funcWithOptObjectArgs [])
|
||||
|
||||
(function _testRedefineWithObjectArgs []
|
||||
(voidFuncWithObjectArgs "hey" (object num 5 num2 0.5))
|
||||
(Assert.equals "hey5.5" (funcWithObjectArgs "hey" (object num 5 num2 0.5)))
|
||||
(Assert.equals "hey5.5" (funcWithObjectArgs "hey" (object num2 4.5 num 1)))
|
||||
(Assert.equals null (funcWithOptObjectArgs)))
|
||||
|
||||
(function _testTypedArrayMacro []
|
||||
(let [a (array Float 1 1.5 2)]
|
||||
(Assert.isTrue (Std.isOfType a Array))
|
||||
(Assert.isTrue (Std.isOfType (first a) Float))))
|
@@ -47,6 +47,8 @@ class ConditionalCompilationTestCase extends Test {
|
||||
Assert.equals("JavaScript", targetLanguage);
|
||||
#elseif python
|
||||
Assert.equals("Python", targetLanguage);
|
||||
#elseif lua
|
||||
Assert.equals("Lua", targetLanguage);
|
||||
#end
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,8 @@
|
||||
(interp "Haxe")
|
||||
(hxnodejs "NodeJS")
|
||||
(js "JavaScript")
|
||||
(python "Python")))
|
||||
(python "Python")
|
||||
(lua "Lua")))
|
||||
|
||||
(function _testCase []
|
||||
(#case var1ForCase
|
||||
|
@@ -2,28 +2,19 @@ package test.cases;
|
||||
|
||||
import utest.Test;
|
||||
import utest.Assert;
|
||||
import kiss.EmbeddedScript;
|
||||
import kiss.AsyncEmbeddedScript;
|
||||
import kiss.AsyncEmbeddedScript2;
|
||||
import kiss.Prelude;
|
||||
import kiss.FuzzyMap;
|
||||
import kiss.FuzzyMapTools;
|
||||
|
||||
class DSLTestCase extends Test {
|
||||
function testScript() {
|
||||
var script = new DSLScript();
|
||||
script.run();
|
||||
Assert.isTrue(script.wholeScriptDone);
|
||||
}
|
||||
|
||||
function testFork() {
|
||||
new DSLScript().fork([(self) -> Assert.equals(5, 5), (self) -> Assert.equals(7, 7)]);
|
||||
}
|
||||
|
||||
#if !lua
|
||||
function testAsync() {
|
||||
var script = new AsyncDSLScript();
|
||||
script.run();
|
||||
#if !lua
|
||||
Assert.isFalse(script.ranHscriptInstruction);
|
||||
#end
|
||||
Assert.isTrue(script.wholeScriptDone);
|
||||
}
|
||||
|
||||
@@ -33,8 +24,10 @@ class DSLTestCase extends Test {
|
||||
script.run();
|
||||
var script2 = new AsyncDSLScriptThatWillCache2();
|
||||
script2.run();
|
||||
#if !lua
|
||||
Assert.isTrue(script.ranHscriptInstruction || script2.ranHscriptInstruction);
|
||||
Assert.isFalse(script.ranHscriptInstruction && script2.ranHscriptInstruction);
|
||||
#end
|
||||
Assert.isTrue(script.wholeScriptDone);
|
||||
Assert.isTrue(script2.wholeScriptDone);
|
||||
}
|
||||
@@ -51,22 +44,20 @@ class DSLTestCase extends Test {
|
||||
Assert.isTrue(scriptWithAutoCC.finished);
|
||||
Assert.isFalse(scriptWithoutAutoCC.finished);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@:build(kiss.EmbeddedScript.build("DSL.kiss", "DSLScript.dsl"))
|
||||
class DSLScript extends EmbeddedScript {}
|
||||
|
||||
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScript.dsl"))
|
||||
class AsyncDSLScript extends AsyncEmbeddedScript {}
|
||||
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScript.dsl"))
|
||||
class AsyncDSLScript extends AsyncEmbeddedScript2 {}
|
||||
|
||||
// One of these two classes will reuse instructions from the cache, but
|
||||
// I can't guarantee which one compiles first:
|
||||
|
||||
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
|
||||
class AsyncDSLScriptThatWillCache extends AsyncEmbeddedScript {}
|
||||
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
|
||||
class AsyncDSLScriptThatWillCache extends AsyncEmbeddedScript2 {}
|
||||
|
||||
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
|
||||
class AsyncDSLScriptThatWillCache2 extends AsyncEmbeddedScript {}
|
||||
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
|
||||
class AsyncDSLScriptThatWillCache2 extends AsyncEmbeddedScript2 {}
|
||||
|
||||
// Auto-call cc when the scripter forgets to:
|
||||
|
||||
|
21
src/test/cases/IdenticalUnquoteTestCase.hx
Normal file
21
src/test/cases/IdenticalUnquoteTestCase.hx
Normal file
@@ -0,0 +1,21 @@
|
||||
package test.cases;
|
||||
|
||||
import utest.Test;
|
||||
import utest.Assert;
|
||||
import kiss.Prelude;
|
||||
import kiss.List;
|
||||
import kiss.Stream;
|
||||
import haxe.ds.Option;
|
||||
import kiss.Kiss;
|
||||
#if js
|
||||
import js.lib.Promise;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:build(kiss.Kiss.build())
|
||||
class IdenticalUnquoteTestCase extends Test {
|
||||
function testDifferentValues() {
|
||||
_testDifferentValues();
|
||||
}
|
||||
}
|
13
src/test/cases/IdenticalUnquoteTestCase.kiss
Normal file
13
src/test/cases/IdenticalUnquoteTestCase.kiss
Normal file
@@ -0,0 +1,13 @@
|
||||
(defMacroFunction arrayReaderMacro [stream]
|
||||
(let [nextLineStream (stream.expect "array macro line" ->(stream.takeLineAsStream))]
|
||||
(printExp `[,(read nextLineStream) ,(read nextLineStream) ,(read nextLineStream)])))
|
||||
|
||||
(defReaderMacro "array " [stream]
|
||||
(arrayReaderMacro stream))
|
||||
|
||||
(var testArray
|
||||
array 1 2 3
|
||||
)
|
||||
|
||||
(function _testDifferentValues []
|
||||
(Assert.equals "[1,2,3]" "$testArray"))
|
22
src/test/cases/MacroExpandTestCase.hx
Normal file
22
src/test/cases/MacroExpandTestCase.hx
Normal file
@@ -0,0 +1,22 @@
|
||||
package test.cases;
|
||||
|
||||
import utest.Test;
|
||||
import utest.Assert;
|
||||
import kiss.Prelude;
|
||||
import kiss.List;
|
||||
import haxe.ds.Option;
|
||||
import kiss.Kiss;
|
||||
#if js
|
||||
import js.lib.Promise;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:build(kiss.Kiss.build())
|
||||
class MacroExpandTestCase extends Test {
|
||||
function testAllForms() {
|
||||
_testAllForms();
|
||||
Assert.pass();
|
||||
}
|
||||
}
|
||||
|
88
src/test/cases/MacroExpandTestCase.kiss
Normal file
88
src/test/cases/MacroExpandTestCase.kiss
Normal file
@@ -0,0 +1,88 @@
|
||||
(defAlias &ident Stream kiss.Stream)
|
||||
(defAlias &type Stream kiss.Stream)
|
||||
|
||||
(defMacro makeExample [expression &body b]
|
||||
`(let [normal ,(printExp expression)
|
||||
expanded ,(printExp (macroExpand expression))]
|
||||
,@b))
|
||||
|
||||
(defMacro makeExampleNoValues [expression &opt body1 body2]
|
||||
`{
|
||||
{
|
||||
,(printExp expression)
|
||||
,(or body1 `{})
|
||||
}
|
||||
{
|
||||
,(printExp (macroExpand expression))
|
||||
,(or body2 `{})
|
||||
}
|
||||
})
|
||||
|
||||
(function _testAllForms []
|
||||
// object
|
||||
(makeExample
|
||||
(object
|
||||
Stream (Stream.fromString ""))
|
||||
normal.Stream
|
||||
expanded.Stream)
|
||||
|
||||
// lambda
|
||||
(makeExample
|
||||
(lambda [Stream] (Stream.fromString ""))
|
||||
(normal null)
|
||||
(expanded null))
|
||||
|
||||
(makeExample
|
||||
(lambda [:Stream s] (Stream.fromString ""))
|
||||
(normal null)
|
||||
(expanded null))
|
||||
|
||||
// localVar
|
||||
(makeExampleNoValues
|
||||
(localVar Stream (Stream.fromString "")))
|
||||
|
||||
(makeExampleNoValues
|
||||
(localVar :Stream s (Stream.fromString "")))
|
||||
|
||||
// let
|
||||
(makeExampleNoValues
|
||||
(let [:Stream s (Stream.fromString "")
|
||||
Stream (Stream.fromString "")]
|
||||
null))
|
||||
|
||||
// localFunction
|
||||
(makeExampleNoValues
|
||||
(localFunction Stream [] (Stream.fromString "")))
|
||||
|
||||
(makeExampleNoValues
|
||||
(localFunction :Stream s [] (Stream.fromString "")))
|
||||
|
||||
// for/doFor
|
||||
|
||||
(localVar listOfLists [["a b c"]["d e f"]])
|
||||
(defAlias &ident lol listOfLists)
|
||||
(defAlias &type Texty String)
|
||||
|
||||
(makeExampleNoValues
|
||||
(for [:Texty t1 :Texty t2 :Texty t3] lol
|
||||
null))
|
||||
|
||||
(makeExampleNoValues
|
||||
(doFor [:Texty t1 :Texty t2 :Texty t3] lol
|
||||
null))
|
||||
|
||||
// the
|
||||
(makeExample
|
||||
.content (the Stream (Stream.fromString "hey"))
|
||||
(Assert.equals normal "hey\n")
|
||||
(Assert.equals expanded "hey\n"))
|
||||
(makeExample
|
||||
.content (cast (Stream.fromString "hey") Stream)
|
||||
(Assert.equals normal "hey\n")
|
||||
(Assert.equals expanded "hey\n"))
|
||||
|
||||
(makeExampleNoValues
|
||||
(try (throw (Stream.fromString "error"))
|
||||
(catch [:Stream s]
|
||||
(print "as expected"))))
|
||||
)
|
Reference in New Issue
Block a user