58 Commits
issue11 ... lua

Author SHA1 Message Date
8c1c5b9719 disable DSL test for Lua 2024-04-21 19:45:52 -06:00
bdd9bedba2 fix some tests for lua 2024-04-21 19:42:19 -06:00
ded96748fe fix ConditionalCompilationTestCase for lua 2024-04-21 19:35:08 -06:00
877dd0fa7a guard all if kissCache 2024-04-21 18:56:22 -06:00
f3a4059c4e revert that 2024-04-21 18:54:15 -06:00
f4989595a2 try -D lua 2024-04-21 18:50:59 -06:00
35883ac728 don't store cached hscript for lua 2024-04-21 18:42:20 -06:00
6adaee8dd6 lua AsyncEmbeddedScript don't run hscript for now 2024-04-21 18:39:19 -06:00
fbf95de14b put lua script in bin folder 2024-04-21 18:23:58 -06:00
06832c142b ignore bit32 install error for versions that don't need it 2024-04-21 18:20:21 -06:00
314443f6c6 install bit32 2024-04-21 18:17:03 -06:00
67f3f3cc74 install all deps 2024-04-21 18:15:14 -06:00
e3b0be3fbe another dependency 2024-04-21 18:11:26 -06:00
34c599437c install lua dependencies 2024-04-21 18:09:52 -06:00
0c22bd1377 fix last 2024-04-21 18:07:11 -06:00
30e2edb150 try move source env/bin/activate lower 2024-04-21 18:03:57 -06:00
a651f116c7 try == false 2024-04-21 18:01:20 -06:00
7be0b082be use unless 2024-04-21 18:00:27 -06:00
f69c24ddf1 try running lua test 2024-04-21 17:59:03 -06:00
60d260e5b1 fix quotes 2024-04-21 17:53:35 -06:00
e0fdf71ecf try install lua versions for testing 2024-04-21 17:52:28 -06:00
384c172680 ergonomic macro for making variadic typed arrays 2024-04-01 13:16:07 -06:00
de4f841d4d make objectArgs function arg fields optional 2024-03-11 13:03:44 +01:00
73aeb3fce6 rough implementation of redefineWithObjectArgs 2024-03-11 12:23:24 +01:00
73af20b7a6 failing test case for redefineWithObjectArgs 2024-03-11 11:59:25 +01:00
2509b53ba6 remove stale logic from fieldForms 2024-03-11 11:31:50 +01:00
a949fa24f7 add field exp support to objectWith 2024-03-08 11:27:09 +01:00
d051f19c65 fix whitespace 2024-03-08 11:26:52 +01:00
771652b753 Warning for unused values. Close #4 2024-02-17 14:21:35 -07:00
6c113ca1ee rest of the special form macro expanders. Close #7 2024-02-17 13:45:49 -07:00
efbd024c3f macroExpand cast 2024-02-17 13:28:39 -07:00
1e887693ea macroExpand the expressions 2024-02-17 13:24:32 -07:00
0e02c6c910 disable an annoying old type check syntax 2024-02-17 13:19:13 -07:00
d78f103e9a macroExpand for/doFor 2024-02-17 12:47:47 -07:00
ac92346109 properly macroExpand localFunction 2024-02-17 12:39:36 -07:00
d5b14656f5 macroExpander for let 2024-02-17 12:19:05 -07:00
e6005c4802 add a useful vscode snippet 2024-02-17 12:13:56 -07:00
4591626818 macroExpand localVar 2024-02-17 11:28:43 -07:00
c2ab12909f macroExpand expand type aliases in lambda 2024-02-17 11:05:33 -07:00
6a1c66cae7 Helpers.expMap 2024-02-17 11:05:08 -07:00
9d37e8f2cb WIP expMap() 2024-02-17 10:39:05 -07:00
30221135bd fix whitespace 2024-02-17 10:14:31 -07:00
d126b793b3 make deprecated specialForms errors 2024-02-17 10:14:18 -07:00
92d9f42388 Refactor MacroExpandTestCase 2024-02-17 10:09:05 -07:00
5668bf0195 lambda macroExpander 2024-02-17 09:57:28 -07:00
fcaaa5e9d5 special form macroExpanders, (object) implementation 2024-02-17 09:50:43 -07:00
c9747fd467 Rename test case + add lambda test 2024-02-17 09:39:08 -07:00
8ff1961ee0 function argument compiler error when . is included 2024-02-17 09:26:37 -07:00
fe61d3edeb compiler error when . is in any varName 2024-02-17 09:14:23 -07:00
597e242135 compiler error when . is in object field name 2024-02-17 09:09:55 -07:00
75b17384dc Failing test case for #7 2024-02-17 08:55:27 -07:00
5044e68b40 pass macroExpand to macro context 2024-02-17 08:49:13 -07:00
4a95750fed Delete EmbeddedScript and fix DSLTestCase 2024-02-15 18:36:47 -07:00
7e9dd825aa Delete original AsyncEmbeddedScript 2024-02-15 18:30:40 -07:00
463c825ee6 AsyncEmbeddedScript2 support multiple errors. Close #2 2024-02-15 18:30:14 -07:00
9a19cc8083 Errors print but don't stop compilation until generation. 2024-02-15 18:09:04 -07:00
ff7ed5eb51 don't clone interp locals. Fix #6 2024-02-15 17:16:57 -07:00
c38a93eee1 Failing test case for #6 2024-02-15 17:04:20 -07:00
22 changed files with 682 additions and 789 deletions

View File

@@ -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
View 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",
"}"
]
}
}

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

View File

@@ -0,0 +1,2 @@
--lua bin/lua/main.lua
--cmd lua bin/lua/main.lua

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ enum KissTarget {
NodeJS;
Python;
Macro;
Lua;
}
class Prelude {

View File

@@ -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();

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,8 @@
(interp "Haxe")
(hxnodejs "NodeJS")
(js "JavaScript")
(python "Python")))
(python "Python")
(lua "Lua")))
(function _testCase []
(#case var1ForCase

View File

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

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

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

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

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