Compare commits
33 Commits
fossils
...
fix-things
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ea0066d32 | |||
| 877b965812 | |||
| e36fb991c6 | |||
| 22c8ce4247 | |||
| 35471dc318 | |||
| 8597cc0de9 | |||
| 2e0991d2d2 | |||
| 4ddf68e3db | |||
| a54a5a0e15 | |||
| 38773bc700 | |||
| 8e3bb78829 | |||
| 6ec40da338 | |||
| da26e94c59 | |||
| e09993ea78 | |||
| 1ae2eaa3c5 | |||
| 70464e4bce | |||
| 4a457f57a7 | |||
| abdbc976e1 | |||
| 97df54c84f | |||
| 5d0fce9298 | |||
| a1cdf75140 | |||
| 9619563210 | |||
| 19bdd5ae00 | |||
| 2212149866 | |||
| a89c50293c | |||
| 1563da604c | |||
| 72b76831e8 | |||
| a4fbf27891 | |||
| f78d25233a | |||
| 2d495a4619 | |||
| 8a20705008 | |||
| 85e4bc1004 | |||
| 8bc1093b0c |
@@ -4,4 +4,5 @@
|
||||
-lib tink_macro
|
||||
-lib tink_syntaxhub
|
||||
-lib haxe-strings
|
||||
-cp src
|
||||
--run kiss.Main
|
||||
@@ -1,4 +1,4 @@
|
||||
# @install: lix --silent download "haxelib:/tink_macro#1.0.1" into tink_macro/1.0.1/haxelib
|
||||
# @install: lix --silent download "gh://github.com/kiss-lang/tink_macro#8b60a484b1141d1176b34ba3af9ac65b499079ff" into tink_macro/1.0.3/github/8b60a484b1141d1176b34ba3af9ac65b499079ff
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_macro/1.0.1/haxelib/src
|
||||
-D tink_macro=1.0.1
|
||||
-cp ${HAXE_LIBCACHE}/tink_macro/1.0.3/github/8b60a484b1141d1176b34ba3af9ac65b499079ff/src
|
||||
-D tink_macro=1.0.3
|
||||
@@ -136,7 +136,7 @@ class AsyncEmbeddedScript2 {
|
||||
interp = new ObjectInterp2(this);
|
||||
kiss.KissInterp.prepare(interp);
|
||||
if (hscriptInstructionFile().length > 0) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(hscriptInstructionFile()));
|
||||
for (key => value in cacheJson) {
|
||||
hscriptInstructions[Std.parseInt(key)] = value;
|
||||
@@ -311,7 +311,7 @@ class AsyncEmbeddedScript2 {
|
||||
Prelude.printStr(e.message);
|
||||
if (onError != null) {
|
||||
onError(e, () -> {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
Sys.exit(1);
|
||||
#end
|
||||
throw e;
|
||||
@@ -321,7 +321,7 @@ class AsyncEmbeddedScript2 {
|
||||
()->{trace("no-op cc");};
|
||||
});
|
||||
} else {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
Sys.exit(1);
|
||||
#end
|
||||
throw e;
|
||||
@@ -476,7 +476,7 @@ class AsyncEmbeddedScript2 {
|
||||
#if profileKiss
|
||||
Kiss.measure('Compiling kiss: $scriptFile', () -> {
|
||||
#end
|
||||
function process(nextExp:ReaderExp) {
|
||||
function process(nextExp:ReaderExp, str) {
|
||||
#if (kissCache && !lua)
|
||||
var cacheKey = Reader.toString(nextExp.def);
|
||||
if (cache.exists(Prelude.hashableString(cacheKey))) {
|
||||
@@ -490,7 +490,7 @@ class AsyncEmbeddedScript2 {
|
||||
if (autoLabelInterval > 0 && commandList.length > lastLabelIp + autoLabelInterval){
|
||||
var b = nextExp.expBuilder();
|
||||
lastLabelIp = commandList.length;
|
||||
process(b.callSymbol("label", [b.symbol("auto")]));
|
||||
process(b.callSymbol("label", [b.symbol("auto")]), "(label auto)");
|
||||
}
|
||||
|
||||
nextExp = Kiss._try(()->Kiss.macroExpand(nextExp, k));
|
||||
@@ -502,7 +502,7 @@ class AsyncEmbeddedScript2 {
|
||||
case CallExp({pos: _, def: Symbol("commands")},
|
||||
commands):
|
||||
for (exp in commands) {
|
||||
process(exp);
|
||||
process(exp, Reader.toString(exp.def));
|
||||
}
|
||||
return;
|
||||
default:
|
||||
|
||||
163
src/kiss/ExpBuilder.hx
Normal file
163
src/kiss/ExpBuilder.hx
Normal file
@@ -0,0 +1,163 @@
|
||||
package kiss;
|
||||
|
||||
import kiss.ReaderExp;
|
||||
using kiss.Helpers;
|
||||
using kiss.Reader;
|
||||
using kiss.ExpBuilder;
|
||||
|
||||
class ExpBuilder {
|
||||
// Return convenient functions for succinctly making new ReaderExps that link back to an original exp's
|
||||
// position in source code
|
||||
public static function expBuilder(posRef:ReaderExp) {
|
||||
function _symbol(?name:String) {
|
||||
return Prelude.symbol(name).withPosOf(posRef);
|
||||
}
|
||||
function call(func:ReaderExp, args:Array<ReaderExp>) {
|
||||
return CallExp(func, args).withPosOf(posRef);
|
||||
}
|
||||
function callSymbol(symbol:String, args:Array<ReaderExp>) {
|
||||
return call(_symbol(symbol), args);
|
||||
}
|
||||
function field(f:String, exp:ReaderExp, ?safe:Bool) {
|
||||
return FieldExp(f, exp, safe != null && safe).withPosOf(posRef);
|
||||
}
|
||||
function list(exps:Array<ReaderExp>) {
|
||||
return ListExp(exps).withPosOf(posRef);
|
||||
}
|
||||
function objectWith (bindings:Array<ReaderExp>, captures:Array<ReaderExp>) {
|
||||
return callSymbol("objectWith", [list(bindings)].concat(captures));
|
||||
}
|
||||
function str(s:String) {
|
||||
return StrExp(s).withPosOf(posRef);
|
||||
}
|
||||
function raw(code:String) {
|
||||
return RawHaxe(code).withPosOf(posRef);
|
||||
}
|
||||
function int(v:Int) {
|
||||
return _symbol(Std.string(v));
|
||||
}
|
||||
function float(v:Float) {
|
||||
return _symbol(Std.string(v));
|
||||
}
|
||||
function let(bindings:Array<ReaderExp>, body:Array<ReaderExp>) {
|
||||
return callSymbol("let", [list(bindings)].concat(body));
|
||||
}
|
||||
function _if(condition:ReaderExp, then:ReaderExp, ?_else:ReaderExp) {
|
||||
var args = [condition, then];
|
||||
if (_else != null)
|
||||
args.push(_else);
|
||||
return callSymbol("if", args);
|
||||
}
|
||||
function throwAssertOrNeverError(messageExp:ReaderExp) {
|
||||
var failureError = KissError.fromExp(posRef, "").toString(AssertionFail);
|
||||
var colonsInPrefix = if (Sys.systemName() == "Windows") 5 else 4;
|
||||
return callSymbol("throw", [
|
||||
callSymbol("kiss.Prelude.runtimeInsertAssertionMessage", [messageExp, str(failureError), int(colonsInPrefix)])
|
||||
]);
|
||||
}
|
||||
function whenUnless(which:String, condition:ReaderExp, body:Array<ReaderExp>) {
|
||||
return callSymbol(which, [condition].concat(body));
|
||||
}
|
||||
return {
|
||||
call: call,
|
||||
callSymbol: callSymbol,
|
||||
callField: (fieldName:String, callOn:ReaderExp, args:Array<ReaderExp>) -> call(field(fieldName, callOn), args),
|
||||
print: (arg:ReaderExp) -> CallExp(Symbol("print").withPosOf(posRef), [arg]).withPosOf(posRef),
|
||||
the: (type:ReaderExp, value:ReaderExp) -> callSymbol("the", [type, value]),
|
||||
not: (exp:ReaderExp) -> callSymbol("not", [exp]),
|
||||
list: list,
|
||||
str: str,
|
||||
symbol: _symbol,
|
||||
_if: _if,
|
||||
int: int,
|
||||
float: float,
|
||||
raw: raw,
|
||||
typed: (path:String, exp:ReaderExp) -> TypedExp(path, exp).withPosOf(posRef),
|
||||
meta: (m:String, exp:ReaderExp) -> MetaExp(m, exp).withPosOf(posRef),
|
||||
field: field,
|
||||
keyValue: (key:ReaderExp, value:ReaderExp) -> KeyValueExp(key, value).withPosOf(posRef),
|
||||
begin: (exps:Array<ReaderExp>) -> callSymbol("begin", exps),
|
||||
set: (v:ReaderExp, value:ReaderExp) -> callSymbol("set", [v, value]),
|
||||
when: whenUnless.bind("when"),
|
||||
unless: whenUnless.bind("unless"),
|
||||
let: let,
|
||||
objectWith: objectWith,
|
||||
expFromDef: (def:ReaderExpDef) -> def.withPosOf(posRef),
|
||||
// Only use within assertion macros
|
||||
throwAssertionError: () -> {
|
||||
var usage = "throwAssertionError can only be used in a builder of an assertion macro";
|
||||
var exps = switch (posRef.def) {
|
||||
case CallExp(_, exps):
|
||||
exps;
|
||||
default:
|
||||
throw KissError.fromExp(_symbol("throwAssertionError"), usage);
|
||||
}
|
||||
var messageExp = if (exps.length > 1) {
|
||||
exps[1];
|
||||
} else {
|
||||
str("");
|
||||
};
|
||||
throwAssertOrNeverError(messageExp);
|
||||
},
|
||||
neverCase: () -> {
|
||||
switch (posRef.def) {
|
||||
case CallExp({pos: _, def: Symbol("never")}, neverExps):
|
||||
posRef.checkNumArgs(1, 1, '(never <pattern>)');
|
||||
call(neverExps[0], [
|
||||
throwAssertOrNeverError(str('case should never match pattern ${Reader.toString(neverExps[0].def)}'))
|
||||
]);
|
||||
default:
|
||||
posRef;
|
||||
}
|
||||
},
|
||||
// Compile-time only!
|
||||
throwKissError: (reason:String) -> {
|
||||
callSymbol("throw", [
|
||||
callSymbol("KissError.fromExpStr", [
|
||||
// pos
|
||||
objectWith([
|
||||
_symbol("file"), str(posRef.pos.file),
|
||||
_symbol("line"), int(posRef.pos.line),
|
||||
_symbol("column"), int(posRef.pos.column),
|
||||
_symbol("absoluteChar"), int(posRef.pos.absoluteChar),
|
||||
], []),
|
||||
// expStr
|
||||
str(Reader.toString(posRef.def)),
|
||||
str(reason)
|
||||
])
|
||||
]);
|
||||
},
|
||||
#if macro
|
||||
haxeExpr: (e:haxe.macro.Expr) -> Helpers.withMacroPosOf(e.expr, posRef),
|
||||
#end
|
||||
none: () -> None.withPosOf(posRef)
|
||||
};
|
||||
}
|
||||
|
||||
public static function checkNumArgs(wholeExp:ReaderExp, min:Null<Int>, max:Null<Int>, ?expectedForm:String) {
|
||||
if (expectedForm == null) {
|
||||
expectedForm = if (max == min) {
|
||||
'$min arguments';
|
||||
} else if (max == null) {
|
||||
'at least $min arguments';
|
||||
} else if (min == null) {
|
||||
'no more than $max arguments';
|
||||
} else if (min == null && max == null) {
|
||||
throw 'checkNumArgs() needs a min or a max';
|
||||
} else {
|
||||
'between $min and $max arguments';
|
||||
};
|
||||
}
|
||||
|
||||
var args = switch (wholeExp.def) {
|
||||
case CallExp(_, args): args;
|
||||
default: throw KissError.fromExp(wholeExp, "Can only check number of args in a CallExp");
|
||||
};
|
||||
|
||||
if (min != null && args.length < min) {
|
||||
throw KissError.fromExp(wholeExp, 'Not enough arguments. Expected $expectedForm');
|
||||
} else if (max != null && args.length > max) {
|
||||
throw KissError.fromExp(wholeExp, 'Too many arguments. Expected $expectedForm');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,11 @@ typedef MapInfo = {
|
||||
matches:Map<String,Dynamic>
|
||||
};
|
||||
|
||||
enum FuzzyGetResult<T> {
|
||||
Found(realKey:String, value:T, score:Float);
|
||||
NotFound;
|
||||
}
|
||||
|
||||
class FuzzyMapTools {
|
||||
static var serializingMaps = new Map<StringMap<Dynamic>, MapInfo>();
|
||||
|
||||
@@ -63,9 +68,18 @@ class FuzzyMapTools {
|
||||
return bestKey;
|
||||
}
|
||||
|
||||
public static function fuzzyGet<T>(map:FuzzyMap<T>, fuzzySearchKey:String): FuzzyGetResult<T> {
|
||||
return switch (bestMatch(map, fuzzySearchKey, false)) {
|
||||
case null:
|
||||
NotFound;
|
||||
case key:
|
||||
Found(key, map[key], fuzzyMatchScore(key, fuzzySearchKey));
|
||||
};
|
||||
}
|
||||
|
||||
@:allow(kiss.FuzzyMap)
|
||||
static function onMatchMade(m:StringMap<Dynamic>, key:String, value:Dynamic) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
if (serializingMaps.exists(m)) {
|
||||
var info = serializingMaps[m];
|
||||
info.matches[key] = value;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package kiss;
|
||||
#if macro
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
@@ -9,6 +10,7 @@ import kiss.Reader;
|
||||
import kiss.ReaderExp;
|
||||
import kiss.KissError;
|
||||
import kiss.Kiss;
|
||||
import kiss.ExpBuilder;
|
||||
import kiss.SpecialForms;
|
||||
import kiss.Prelude;
|
||||
import kiss.cloner.Cloner;
|
||||
@@ -434,30 +436,7 @@ class Helpers {
|
||||
Throw a KissError if the given expression has the wrong number of arguments
|
||||
**/
|
||||
public static function checkNumArgs(wholeExp:ReaderExp, min:Null<Int>, max:Null<Int>, ?expectedForm:String) {
|
||||
if (expectedForm == null) {
|
||||
expectedForm = if (max == min) {
|
||||
'$min arguments';
|
||||
} else if (max == null) {
|
||||
'at least $min arguments';
|
||||
} else if (min == null) {
|
||||
'no more than $max arguments';
|
||||
} else if (min == null && max == null) {
|
||||
throw 'checkNumArgs() needs a min or a max';
|
||||
} else {
|
||||
'between $min and $max arguments';
|
||||
};
|
||||
}
|
||||
|
||||
var args = switch (wholeExp.def) {
|
||||
case CallExp(_, args): args;
|
||||
default: throw KissError.fromExp(wholeExp, "Can only check number of args in a CallExp");
|
||||
};
|
||||
|
||||
if (min != null && args.length < min) {
|
||||
throw KissError.fromExp(wholeExp, 'Not enough arguments. Expected $expectedForm');
|
||||
} else if (max != null && args.length > max) {
|
||||
throw KissError.fromExp(wholeExp, 'Too many arguments. Expected $expectedForm');
|
||||
}
|
||||
ExpBuilder.checkNumArgs(wholeExp, min, max, expectedForm);
|
||||
}
|
||||
|
||||
public static function removeTypeAnnotations(exp:ReaderExp):ReaderExp {
|
||||
@@ -687,6 +666,8 @@ class Helpers {
|
||||
CallExp(recurse(func), evalUnquoteLists(callArgs, innerRunAtCompileTime).map(recurse));
|
||||
case ListExp(elements):
|
||||
ListExp(evalUnquoteLists(elements, innerRunAtCompileTime).map(recurse));
|
||||
case BraceExp(elements):
|
||||
BraceExp(evalUnquoteLists(elements, innerRunAtCompileTime).map(recurse));
|
||||
case TypedExp(type, innerExp):
|
||||
TypedExp(type, recurse(innerExp));
|
||||
case FieldExp(field, innerExp, safe):
|
||||
@@ -706,126 +687,7 @@ class Helpers {
|
||||
return def.withPosOf(exp);
|
||||
}
|
||||
|
||||
// Return convenient functions for succinctly making new ReaderExps that link back to an original exp's
|
||||
// position in source code
|
||||
public static function expBuilder(posRef:ReaderExp) {
|
||||
function _symbol(?name:String) {
|
||||
return Prelude.symbol(name).withPosOf(posRef);
|
||||
}
|
||||
function call(func:ReaderExp, args:Array<ReaderExp>) {
|
||||
return CallExp(func, args).withPosOf(posRef);
|
||||
}
|
||||
function callSymbol(symbol:String, args:Array<ReaderExp>) {
|
||||
return call(_symbol(symbol), args);
|
||||
}
|
||||
function field(f:String, exp:ReaderExp, ?safe:Bool) {
|
||||
return FieldExp(f, exp, safe != null && safe).withPosOf(posRef);
|
||||
}
|
||||
function list(exps:Array<ReaderExp>) {
|
||||
return ListExp(exps).withPosOf(posRef);
|
||||
}
|
||||
function objectWith (bindings:Array<ReaderExp>, captures:Array<ReaderExp>) {
|
||||
return callSymbol("objectWith", [list(bindings)].concat(captures));
|
||||
}
|
||||
function str(s:String) {
|
||||
return StrExp(s).withPosOf(posRef);
|
||||
}
|
||||
function raw(code:String) {
|
||||
return RawHaxe(code).withPosOf(posRef);
|
||||
}
|
||||
function int(v:Int) {
|
||||
return _symbol(Std.string(v));
|
||||
}
|
||||
function float(v:Float) {
|
||||
return _symbol(Std.string(v));
|
||||
}
|
||||
function let(bindings:Array<ReaderExp>, body:Array<ReaderExp>) {
|
||||
return callSymbol("let", [list(bindings)].concat(body));
|
||||
}
|
||||
function _if(condition:ReaderExp, then:ReaderExp, ?_else:ReaderExp) {
|
||||
var args = [condition, then];
|
||||
if (_else != null)
|
||||
args.push(_else);
|
||||
return callSymbol("if", args);
|
||||
}
|
||||
function throwAssertOrNeverError(messageExp:ReaderExp) {
|
||||
var failureError = KissError.fromExp(posRef, "").toString(AssertionFail);
|
||||
var colonsInPrefix = if (Sys.systemName() == "Windows") 5 else 4;
|
||||
return callSymbol("throw", [
|
||||
callSymbol("kiss.Prelude.runtimeInsertAssertionMessage", [messageExp, str(failureError), int(colonsInPrefix)])
|
||||
]);
|
||||
}
|
||||
return {
|
||||
call: call,
|
||||
callSymbol: callSymbol,
|
||||
callField: (fieldName:String, callOn:ReaderExp, args:Array<ReaderExp>) -> call(field(fieldName, callOn), args),
|
||||
print: (arg:ReaderExp) -> CallExp(Symbol("print").withPosOf(posRef), [arg]).withPosOf(posRef),
|
||||
the: (type:ReaderExp, value:ReaderExp) -> callSymbol("the", [type, value]),
|
||||
not: (exp:ReaderExp) -> callSymbol("not", [exp]),
|
||||
list: list,
|
||||
str: str,
|
||||
symbol: _symbol,
|
||||
_if: _if,
|
||||
int: int,
|
||||
float: float,
|
||||
raw: raw,
|
||||
typed: (path:String, exp:ReaderExp) -> TypedExp(path, exp).withPosOf(posRef),
|
||||
meta: (m:String, exp:ReaderExp) -> MetaExp(m, exp).withPosOf(posRef),
|
||||
field: field,
|
||||
keyValue: (key:ReaderExp, value:ReaderExp) -> KeyValueExp(key, value).withPosOf(posRef),
|
||||
begin: (exps:Array<ReaderExp>) -> callSymbol("begin", exps),
|
||||
set: (v:ReaderExp, value:ReaderExp) -> callSymbol("set", [v, value]),
|
||||
let: let,
|
||||
objectWith: objectWith,
|
||||
expFromDef: (def:ReaderExpDef) -> def.withPosOf(posRef),
|
||||
// Only use within assertion macros
|
||||
throwAssertionError: () -> {
|
||||
var usage = "throwAssertionError can only be used in a builder of an assertion macro";
|
||||
var exps = switch (posRef.def) {
|
||||
case CallExp(_, exps):
|
||||
exps;
|
||||
default:
|
||||
throw KissError.fromExp(_symbol("throwAssertionError"), usage);
|
||||
}
|
||||
var messageExp = if (exps.length > 1) {
|
||||
exps[1];
|
||||
} else {
|
||||
str("");
|
||||
};
|
||||
throwAssertOrNeverError(messageExp);
|
||||
},
|
||||
neverCase: () -> {
|
||||
switch (posRef.def) {
|
||||
case CallExp({pos: _, def: Symbol("never")}, neverExps):
|
||||
posRef.checkNumArgs(1, 1, '(never <pattern>)');
|
||||
call(neverExps[0], [
|
||||
throwAssertOrNeverError(str('case should never match pattern ${Reader.toString(neverExps[0].def)}'))
|
||||
]);
|
||||
default:
|
||||
posRef;
|
||||
}
|
||||
},
|
||||
// Compile-time only!
|
||||
throwKissError: (reason:String) -> {
|
||||
callSymbol("throw", [
|
||||
callSymbol("KissError.fromExpStr", [
|
||||
// pos
|
||||
objectWith([
|
||||
_symbol("file"), str(posRef.pos.file),
|
||||
_symbol("line"), int(posRef.pos.line),
|
||||
_symbol("column"), int(posRef.pos.column),
|
||||
_symbol("absoluteChar"), int(posRef.pos.absoluteChar),
|
||||
], []),
|
||||
// expStr
|
||||
str(Reader.toString(posRef.def)),
|
||||
str(reason)
|
||||
])
|
||||
]);
|
||||
},
|
||||
haxeExpr: (e:haxe.macro.Expr) -> withMacroPosOf(e.expr, posRef),
|
||||
none: () -> None.withPosOf(posRef)
|
||||
};
|
||||
}
|
||||
public static function expBuilder(posRef:ReaderExp) return ExpBuilder.expBuilder(posRef);
|
||||
|
||||
public static function checkNoEarlyOtherwise(cases:kiss.List<ReaderExp>) {
|
||||
for (i in 0...cases.length) {
|
||||
@@ -838,37 +700,11 @@ class Helpers {
|
||||
}
|
||||
|
||||
public static function argList(exp:ReaderExp, forThis:String, allowEmpty = true):Array<ReaderExp> {
|
||||
return switch (exp.def) {
|
||||
// At macro-time, a list of exps could be passed instead of a ListExp. Handle
|
||||
// that tricky case:
|
||||
case null if (Std.isOfType(exp, Array)):
|
||||
var expList = cast(exp, Array<Dynamic>);
|
||||
var expDynamic:Dynamic = exp;
|
||||
argList({pos:expList[0].pos, def: ListExp(expDynamic)}, forThis, allowEmpty);
|
||||
case ListExp([]) if (allowEmpty):
|
||||
[];
|
||||
case ListExp([]) if (!allowEmpty):
|
||||
throw KissError.fromExp(exp, 'arg list for $forThis must not be empty');
|
||||
case ListExp(argExps):
|
||||
argExps;
|
||||
default:
|
||||
throw KissError.fromExp(exp, '$forThis arg list should be a list or list expression');
|
||||
};
|
||||
return Prelude.argList(exp, forThis, allowEmpty);
|
||||
}
|
||||
|
||||
public static function bindingList(exp:ReaderExp, forThis:String, allowEmpty = false):Array<ReaderExp> {
|
||||
return switch (exp.def) {
|
||||
// At macro-time, a list of exps could be passed instead of a ListExp. Handle
|
||||
// that tricky case:
|
||||
case null if (Std.isOfType(exp, Array)):
|
||||
var expList = cast(exp, Array<Dynamic>);
|
||||
var expDynamic:Dynamic = exp;
|
||||
bindingList({pos:expList[0].pos, def: ListExp(expDynamic)}, forThis, allowEmpty);
|
||||
case ListExp(bindingExps) if ((allowEmpty || bindingExps.length > 0) && bindingExps.length % 2 == 0):
|
||||
bindingExps;
|
||||
default:
|
||||
throw KissError.fromExp(exp, '$forThis bindings should be a list or list expression with an even number of sub expressions (at least 2)');
|
||||
};
|
||||
return Prelude.bindingList(exp, forThis, allowEmpty);
|
||||
}
|
||||
|
||||
public static function compileTimeResolveToString(description:String, description2:String, exp:ReaderExp, k:KissState):String {
|
||||
@@ -930,6 +766,8 @@ class Helpers {
|
||||
CallExp(func(f), Lambda.map(args, func));
|
||||
case ListExp(args):
|
||||
ListExp(Lambda.map(args, func));
|
||||
case BraceExp(args):
|
||||
BraceExp(Lambda.map(args, func));
|
||||
case TypedExp(type, exp):
|
||||
TypedExp(type, func(exp));
|
||||
case MetaExp(meta, exp):
|
||||
@@ -979,3 +817,5 @@ class Helpers {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
@@ -41,13 +41,10 @@ typedef FormDoc = {
|
||||
};
|
||||
|
||||
typedef KissState = {
|
||||
> HasReadTables,
|
||||
className:String,
|
||||
pack:Array<String>,
|
||||
file:String,
|
||||
readTable:ReadTable,
|
||||
startOfLineReadTable:ReadTable,
|
||||
startOfFileReadTable:ReadTable,
|
||||
endOfFileReadTable:ReadTable,
|
||||
fieldForms:Map<String, FieldFormFunction>,
|
||||
specialForms:Map<String, SpecialFormFunction>,
|
||||
specialFormMacroExpanders:Map<String, MacroFunction>,
|
||||
@@ -57,7 +54,6 @@ typedef KissState = {
|
||||
wrapListExps:Bool,
|
||||
loadedFiles:Map<String, Null<ReaderExp>>,
|
||||
callAliases:Map<String, ReaderExpDef>,
|
||||
identAliases:Map<String, ReaderExpDef>,
|
||||
typeAliases:Map<String, String>,
|
||||
fieldList:Array<Field>,
|
||||
// TODO This map was originally created to track whether the programmer wrote their own main function, but could also
|
||||
@@ -143,7 +139,8 @@ class Kiss {
|
||||
"walkDirectory" => Symbol("Prelude.walkDirectory"),
|
||||
"purgeDirectory" => Symbol("Prelude.purgeDirectory"),
|
||||
"getTarget" => Symbol("Prelude.getTarget"),
|
||||
// These work with (apply) because they are added as "opAliases" in Macros.kiss:
|
||||
"fuzzyGet" => Symbol("kiss.FuzzyMapTools.fuzzyGet"),
|
||||
// These work with (apply) because they are added as "opAliases" in Macros.hx:
|
||||
"min" => Symbol("Prelude.min"),
|
||||
"max" => Symbol("Prelude.max"),
|
||||
"iHalf" => Symbol("Prelude.iHalf"),
|
||||
@@ -186,7 +183,8 @@ class Kiss {
|
||||
"readDirectory" => Symbol("Prelude.readDirectory"),
|
||||
"substr" => Symbol("Prelude.substr"),
|
||||
"isListExp" => Symbol("Prelude.isListExp"),
|
||||
"isNull" => Symbol("Prelude.isNull")
|
||||
"isNull" => Symbol("Prelude.isNull"),
|
||||
"isNotNull" => Symbol("Prelude.isNotNull")
|
||||
/* zip functions used to live here as aliases but now they are macros that also
|
||||
apply (the Array<Array<Dynamic>>) to the result */
|
||||
/* intersect used to live here as an alias but now it is in a macro that also
|
||||
@@ -318,36 +316,7 @@ class Kiss {
|
||||
}
|
||||
|
||||
#end
|
||||
public static macro function exp(kissCode:ExprOf<String>) {
|
||||
var pos = kissCode.pos;
|
||||
var pos = PositionTools.getInfos(pos);
|
||||
var kissCode = ExprTools.getValue(kissCode);
|
||||
|
||||
var content = File.getContent(pos.file).substr(0, pos.min);
|
||||
var lines:kiss.List<String> = content.split('\n');
|
||||
var lineNumber = lines.length;
|
||||
var column = lines[-1].length + 1;
|
||||
var pos = {
|
||||
file: pos.file,
|
||||
absoluteChar: pos.min,
|
||||
line: lineNumber,
|
||||
column: column
|
||||
};
|
||||
|
||||
return _try(() -> {
|
||||
var exp = null;
|
||||
var stream = Stream.fromString(kissCode, pos);
|
||||
var k = defaultKissState();
|
||||
Reader.readAndProcess(stream, k, (nextExp) -> {
|
||||
if (exp == null) {
|
||||
exp = readerExpToHaxeExpr(nextExp, k);
|
||||
} else {
|
||||
throw KissError.fromExp(nextExp, "can't have multiple top-level expressions in Kiss.exp() input");
|
||||
}
|
||||
});
|
||||
return exp;
|
||||
});
|
||||
}
|
||||
|
||||
#if macro
|
||||
static function addContextFields(k:KissState, useClassFields:Bool) {
|
||||
if (useClassFields) {
|
||||
@@ -552,7 +521,7 @@ class Kiss {
|
||||
static final fossilStart = "\n\t// BEGIN KISS FOSSIL CODE\n\t// "; // TODO remove the boneyard comments
|
||||
static final fossilEnd = "\t// END KISS FOSSIL CODE\n";
|
||||
|
||||
static function complexTypeToString(type:ComplexType) {
|
||||
static function complexTypeToString(type:ComplexType, emptyForVoid = false) {
|
||||
var fossilCode = "";
|
||||
switch (type) {
|
||||
case TPath(path):
|
||||
@@ -577,7 +546,7 @@ class Kiss {
|
||||
}
|
||||
case TFunction(args, ret):
|
||||
fossilCode += "(";
|
||||
fossilCode += [for (arg in args) complexTypeToString(arg)].join(",");
|
||||
fossilCode += [for (arg in args) complexTypeToString(arg, true)].join(",");
|
||||
fossilCode += ")->";
|
||||
fossilCode += complexTypeToString(ret);
|
||||
case TAnonymous(fields):
|
||||
@@ -595,9 +564,31 @@ class Kiss {
|
||||
default:
|
||||
fossilCode += '{ComplexType $type not supported for fossilization}';
|
||||
}
|
||||
if (emptyForVoid && fossilCode == "Void") return "";
|
||||
return fossilCode;
|
||||
}
|
||||
|
||||
public static function typeParamDeclToString(param:TypeParamDecl) {
|
||||
var str = param.name;
|
||||
|
||||
if (param.params != null && param.params.length > 0) {
|
||||
str += "<";
|
||||
str += [for (innerParam in param.params) typeParamDeclToString(innerParam)].join(",");
|
||||
str += ">";
|
||||
}
|
||||
|
||||
if (param.defaultType != null || (param.constraints != null && param.constraints.length > 0)) {
|
||||
str += "{type paramater default types and constraints are not supported for fossilization}";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static function toStringTabbed(e:Expr) {
|
||||
var str = e.toString();
|
||||
return str.replace("\n", "\n\t");
|
||||
}
|
||||
|
||||
public static function fossilBuild(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext, ?expectedError:EType):Array<Field> {
|
||||
var pos = Context.currentPos();
|
||||
var haxeFile = Context.getPosInfos(pos).file;
|
||||
@@ -688,11 +679,21 @@ class Kiss {
|
||||
fossilCode += complexTypeToString(type);
|
||||
}
|
||||
if (e != null) {
|
||||
fossilCode += ' = ' + e.toString();
|
||||
fossilCode += ' = ' + toStringTabbed(e);
|
||||
}
|
||||
fossilCode += ';';
|
||||
case FFun(f):
|
||||
fossilCode += "function " + field.name + "(";
|
||||
fossilCode += "function " + field.name;
|
||||
if (f.params != null && f.params.length > 0) {
|
||||
fossilCode += "<";
|
||||
fossilCode += [
|
||||
for (param in f.params) {
|
||||
typeParamDeclToString(param);
|
||||
}
|
||||
].join(",");
|
||||
fossilCode += ">";
|
||||
}
|
||||
fossilCode += "(";
|
||||
var firstArg = true;
|
||||
for (arg in f.args) {
|
||||
if (!firstArg) {
|
||||
@@ -709,7 +710,7 @@ class Kiss {
|
||||
fossilCode += complexTypeToString(arg.type);
|
||||
}
|
||||
if (arg.value != null) {
|
||||
fossilCode += " = " + arg.value.toString();
|
||||
fossilCode += " = " + toStringTabbed(arg.value);
|
||||
}
|
||||
}
|
||||
fossilCode += ")";
|
||||
@@ -717,7 +718,7 @@ class Kiss {
|
||||
fossilCode += ':' + complexTypeToString(f.ret) + " ";
|
||||
}
|
||||
if (f.expr != null) {
|
||||
var funcExpToString = f.expr.toString();
|
||||
var funcExpToString = toStringTabbed(f.expr);
|
||||
fossilCode += " " + funcExpToString;
|
||||
if (!funcExpToString.contains("\n") && !funcExpToString.endsWith(";"))
|
||||
fossilCode += ";";
|
||||
@@ -729,7 +730,7 @@ class Kiss {
|
||||
case FProp(get, set, type, e):
|
||||
fossilCode += "var " + field.name + "(" + get + "," + set + "):" + complexTypeToString(type);
|
||||
if (e != null) {
|
||||
fossilCode += " = " + e.toString();
|
||||
fossilCode += " = " + toStringTabbed(e);
|
||||
}
|
||||
fossilCode += ";";
|
||||
default:
|
||||
@@ -785,9 +786,9 @@ class Kiss {
|
||||
}
|
||||
var startPosition = stream.position();
|
||||
var loadedExps = [];
|
||||
Reader.readAndProcess(stream, k, (nextExp) -> {
|
||||
Reader.readAndProcess(stream, k, (nextExp, str) -> {
|
||||
#if test
|
||||
Sys.println(nextExp.def.toString());
|
||||
Sys.println(str);
|
||||
#end
|
||||
|
||||
// readerExpToHaxeExpr must be called to process readermacro, alias, and macro definitions
|
||||
|
||||
@@ -26,6 +26,7 @@ class KissError {
|
||||
return new KissError([exp], message);
|
||||
}
|
||||
|
||||
#if macro
|
||||
public static function fromExpStr(pos:Position, expStr:String, message:String) {
|
||||
switch (Reader.read(Stream.fromString(expStr), Kiss.defaultKissState())) {
|
||||
case Some(exp):
|
||||
@@ -34,6 +35,7 @@ class KissError {
|
||||
throw 'bad'; // TODO better message
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public static function fromArgs(exps:Array<ReaderExp>, message:String) {
|
||||
return new KissError(exps, message);
|
||||
|
||||
@@ -35,7 +35,7 @@ class KissInterp extends Interp {
|
||||
interp.variables.set("FuzzyMapTools", FuzzyMapTools);
|
||||
interp.variables.set("StringTools", StringTools);
|
||||
interp.variables.set("Path", haxe.io.Path);
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
interp.variables.set("Sys", Sys);
|
||||
interp.variables.set("FileSystem", sys.FileSystem);
|
||||
interp.variables.set("File", sys.io.File);
|
||||
@@ -65,14 +65,14 @@ class KissInterp extends Interp {
|
||||
public var cacheConvertedHScript = false;
|
||||
|
||||
public function evalKiss(kissStr:String):Dynamic {
|
||||
#if !(sys || hxnodejs)
|
||||
#if !((sys || hxnodejs) && !frontend)
|
||||
if (cacheConvertedHScript) {
|
||||
throw "Cannot used cacheConvertedHScript on a non-sys target";
|
||||
}
|
||||
#end
|
||||
|
||||
var convert =
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
if (cacheConvertedHScript) {
|
||||
Prelude.cachedConvertToHScript;
|
||||
} else
|
||||
@@ -157,7 +157,7 @@ class KissInterp extends Interp {
|
||||
}
|
||||
|
||||
kiss.Prelude.print(varDump);
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
sys.io.File.saveContent(file, varDump);
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package kiss;
|
||||
#if macro
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
@@ -610,7 +611,7 @@ class Macros {
|
||||
if (streamArgName == null) throw messageForBadArgs;
|
||||
|
||||
for (s in strings) {
|
||||
table[s] = (stream, k) -> {
|
||||
table[s] = (stream, k:Dynamic) -> {
|
||||
if (strings.length > 1) {
|
||||
stream.putBackString(s);
|
||||
}
|
||||
@@ -748,7 +749,7 @@ class Macros {
|
||||
return b.let(
|
||||
[gensym, firstValue],
|
||||
[b.callSymbol("if", [
|
||||
gensym,
|
||||
b.callSymbol("Prelude.isNotNull", [gensym]),
|
||||
b.callSymbol("case", [
|
||||
gensym,
|
||||
b.call(firstPattern, [
|
||||
@@ -1598,6 +1599,13 @@ class Macros {
|
||||
|
||||
b.let([b.typed('Array<${typeName}>', arraySymbol), b.list([])], [for (arg in args.slice(1)) b.call(b.field("push", arraySymbol), [arg])].concat([arraySymbol]));
|
||||
};
|
||||
|
||||
k.doc("default", 2, 2, "(default <variable> <value>)");
|
||||
macros["default"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var b = wholeExp.expBuilder();
|
||||
b.when(b.callSymbol("isNull", [args[0]]), [b.set(args[0], args[1])]);
|
||||
};
|
||||
|
||||
return macros;
|
||||
}
|
||||
|
||||
@@ -1701,3 +1709,5 @@ class Macros {
|
||||
macros[formName] = cond;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
@@ -313,7 +313,7 @@ class Main {
|
||||
|
||||
if (args.indexOf("--all") != -1) {
|
||||
var kissInputStream = Stream.fromString(Sys.stdin().readAll().toString());
|
||||
Reader.readAndProcess(kissInputStream, k, (readerExp) -> {
|
||||
Reader.readAndProcess(kissInputStream, k, (readerExp, str) -> {
|
||||
print(Kiss.readerExpToHaxeExpr(readerExp, k).toString());
|
||||
});
|
||||
} else {
|
||||
@@ -334,7 +334,7 @@ class Main {
|
||||
}
|
||||
|
||||
var kissInputStream = Stream.fromString(line);
|
||||
Reader.readAndProcess(kissInputStream, k, (readerExp) -> {
|
||||
Reader.readAndProcess(kissInputStream, k, (readerExp, str) -> {
|
||||
print(Kiss.readerExpToHaxeExpr(readerExp, k).toString());
|
||||
});
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ import haxe.DynamicAccess;
|
||||
#if js
|
||||
import js.lib.Promise;
|
||||
#end
|
||||
#if (!macro && hxnodejs)
|
||||
#if (!macro && hxnodejs && !frontend)
|
||||
import js.node.ChildProcess;
|
||||
import js.node.Buffer;
|
||||
#elseif sys
|
||||
import sys.io.Process;
|
||||
#end
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
#end
|
||||
@@ -291,7 +291,7 @@ class Prelude {
|
||||
|
||||
public static var concat:Function = Reflect.makeVarArgs(_concat);
|
||||
|
||||
static function _zip(iterables:Array<Dynamic>, extraHandling:ExtraElementHandling):kiss.List<kiss.List<Dynamic>> {
|
||||
public static function _zip(iterables:Array<Dynamic>, extraHandling:ExtraElementHandling):kiss.List<kiss.List<Dynamic>> {
|
||||
var lists = [];
|
||||
var iterators = [for (iterable in iterables) iterable.iterator()];
|
||||
|
||||
@@ -431,11 +431,12 @@ class Prelude {
|
||||
|
||||
public static var joinPath:Function = Reflect.makeVarArgs(_joinPath);
|
||||
|
||||
public static function isNull<T>(v:T) {
|
||||
return switch (Type.typeof(v)) {
|
||||
case TNull: true;
|
||||
default: false;
|
||||
}
|
||||
public static function isNull<T>(v:Null<T>) {
|
||||
return v == null;
|
||||
}
|
||||
|
||||
public static function isNotNull<T>(v:Null<T>) {
|
||||
return v != null;
|
||||
}
|
||||
|
||||
public static dynamic function truthy<T>(v:T) {
|
||||
@@ -476,7 +477,7 @@ class Prelude {
|
||||
} else {
|
||||
var ret = Reflect.callMethod(caller, func, args);
|
||||
argMap[argString] = ret;
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
if (jsonFile != null) {
|
||||
File.saveContent(jsonFile, Json.stringify(argMap));
|
||||
}
|
||||
@@ -488,7 +489,7 @@ class Prelude {
|
||||
return f;
|
||||
}
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
public static function fsMemoize(func:Function, funcName:String, cacheDirectory = "", ?caller:Dynamic):Function {
|
||||
var fileName = '${funcName}.memoized';
|
||||
if (cacheDirectory.length > 0) {
|
||||
@@ -505,26 +506,13 @@ class Prelude {
|
||||
#end
|
||||
|
||||
public static function _printStr(s:String) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || nodejs) && !frontend)
|
||||
Sys.println(s);
|
||||
#else
|
||||
trace(s);
|
||||
#end
|
||||
}
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
static var externLogFile = "externLog.txt";
|
||||
|
||||
public static function _externPrintStr(s:String) {
|
||||
var logContent = try {
|
||||
File.getContent(externLogFile);
|
||||
} catch (e) {
|
||||
"";
|
||||
}
|
||||
File.saveContent(externLogFile, '${logContent}${s}\n');
|
||||
}
|
||||
#end
|
||||
|
||||
public static var printStr:(String) -> Void = _printStr;
|
||||
|
||||
public static function withLabel(v:Any, label = "") {
|
||||
@@ -550,6 +538,40 @@ class Prelude {
|
||||
throw 'expected $s to be ${toBeWhat}';
|
||||
#end
|
||||
}
|
||||
|
||||
public static function bindingList(exp:ReaderExp, forThis:String, allowEmpty = false):Array<ReaderExp> {
|
||||
return switch (exp.def) {
|
||||
// At macro-time, a list of exps could be passed instead of a ListExp. Handle
|
||||
// that tricky case:
|
||||
case null if (Std.isOfType(exp, Array)):
|
||||
var expList = cast(exp, Array<Dynamic>);
|
||||
var expDynamic:Dynamic = exp;
|
||||
bindingList({pos:expList[0].pos, def: ListExp(expDynamic)}, forThis, allowEmpty);
|
||||
case ListExp(bindingExps) if ((allowEmpty || bindingExps.length > 0) && bindingExps.length % 2 == 0):
|
||||
bindingExps;
|
||||
default:
|
||||
throw KissError.fromExp(exp, '$forThis bindings should be a list or list expression with an even number of sub expressions (at least 2)');
|
||||
};
|
||||
}
|
||||
|
||||
public static function argList(exp:ReaderExp, forThis:String, allowEmpty = true):Array<ReaderExp> {
|
||||
return switch (exp.def) {
|
||||
// At macro-time, a list of exps could be passed instead of a ListExp. Handle
|
||||
// that tricky case:
|
||||
case null if (Std.isOfType(exp, Array)):
|
||||
var expList = cast(exp, Array<Dynamic>);
|
||||
var expDynamic:Dynamic = exp;
|
||||
argList({pos:expList[0].pos, def: ListExp(expDynamic)}, forThis, allowEmpty);
|
||||
case ListExp([]) if (allowEmpty):
|
||||
[];
|
||||
case ListExp([]) if (!allowEmpty):
|
||||
throw KissError.fromExp(exp, 'arg list for $forThis must not be empty');
|
||||
case ListExp(argExps):
|
||||
argExps;
|
||||
default:
|
||||
throw KissError.fromExp(exp, '$forThis arg list should be a list or list expression');
|
||||
};
|
||||
}
|
||||
|
||||
public static function symbolNameValue(s:ReaderExp, allowTyped:Null<Bool> = false, allowMeta:Null<Bool> = false):String {
|
||||
return switch (s.def) {
|
||||
@@ -626,7 +648,7 @@ class Prelude {
|
||||
|
||||
public static function walkDirectory(basePath, directory, processFile:(String) -> Void, ?filterFolderBefore:(String) -> Bool,
|
||||
?processFolderAfter:(String) -> Void) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
for (fileOrFolder in FileSystem.readDirectory(joinPath(basePath, directory))) {
|
||||
switch (fileOrFolder) {
|
||||
case folder if (FileSystem.isDirectory(joinPath(basePath, directory, folder))):
|
||||
@@ -650,7 +672,7 @@ class Prelude {
|
||||
}
|
||||
|
||||
public static function purgeDirectory(directory) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
walkDirectory("", directory, FileSystem.deleteFile, null, FileSystem.deleteDirectory);
|
||||
FileSystem.deleteDirectory(directory);
|
||||
#else
|
||||
@@ -668,7 +690,7 @@ class Prelude {
|
||||
public static function convertToHScript(kissStr:String):String {
|
||||
var unsupportedMessage = "Can't convert Kiss to HScript on this target.";
|
||||
|
||||
#if (!sys && !hxnodejs)
|
||||
#if (!macro && (!sys && !hxnodejs) || frontend)
|
||||
throw unsupportedMessage;
|
||||
#else
|
||||
|
||||
@@ -687,7 +709,7 @@ class Prelude {
|
||||
#if macro
|
||||
return Kiss.measure("Prelude.convertToHScript", () -> {
|
||||
#end
|
||||
#if (!macro && hxnodejs)
|
||||
#if (!macro && hxnodejs && !frontend)
|
||||
var hscript = try {
|
||||
assertProcess("haxe", [buildHxml, "convert", "--all", "--hscript"], kissStr.split('\n'), true, cwd);
|
||||
} catch (e) {
|
||||
@@ -737,7 +759,7 @@ class Prelude {
|
||||
#end
|
||||
}
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if (macro || (sys || hxnodejs) && !frontend)
|
||||
public static function userHome() {
|
||||
var msysHome = Sys.getEnv("MSYSHOME");
|
||||
var home = Sys.getEnv("HOME");
|
||||
@@ -762,7 +784,7 @@ class Prelude {
|
||||
CSharp;
|
||||
#elseif interp
|
||||
Haxe;
|
||||
#elseif hxnodejs
|
||||
#elseif (hxnodejs && !frontend)
|
||||
NodeJS;
|
||||
#elseif js
|
||||
JavaScript;
|
||||
@@ -864,7 +886,7 @@ class Prelude {
|
||||
handleError('process $command $args failed: $e');
|
||||
return null;
|
||||
}
|
||||
#elseif hxnodejs
|
||||
#elseif (hxnodejs && !frontend)
|
||||
var p = if (inputLines != null) {
|
||||
ChildProcess.spawnSync(command, args, {input: inputLines.join("\n"), cwd: cwd});
|
||||
} else {
|
||||
@@ -897,7 +919,7 @@ class Prelude {
|
||||
static var shellCount = 0;
|
||||
|
||||
public static function shellExecute(script:String, shell:String) {
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
if (shell.length == 0) {
|
||||
shell = if (Sys.systemName() == "Windows") "cmd /c" else "bash";
|
||||
}
|
||||
@@ -936,7 +958,7 @@ class Prelude {
|
||||
return Lambda.filter(l, p);
|
||||
}
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if ((sys || hxnodejs) && !frontend)
|
||||
public static function readDirectory(dir:String) {
|
||||
return [for (file in FileSystem.readDirectory(dir)) {
|
||||
joinPath(dir, file);
|
||||
|
||||
@@ -2,12 +2,11 @@ package kiss;
|
||||
|
||||
import haxe.ds.Option;
|
||||
import kiss.Stream;
|
||||
import kiss.Kiss;
|
||||
import kiss.ReaderExp;
|
||||
|
||||
using kiss.Reader;
|
||||
using kiss.ExpBuilder;
|
||||
using kiss.Stream;
|
||||
using kiss.Helpers;
|
||||
using StringTools;
|
||||
|
||||
class UnmatchedBracketSignal {
|
||||
@@ -22,13 +21,26 @@ class UnmatchedBracketSignal {
|
||||
}
|
||||
}
|
||||
|
||||
typedef ReadFunction = (Stream, KissState) -> Null<ReaderExpDef>;
|
||||
typedef ReadFunction = (Stream, HasReadTables) -> Null<ReaderExpDef>;
|
||||
typedef ReadTable = Map<String, ReadFunction>;
|
||||
typedef HasReadTables = {
|
||||
readTable:ReadTable,
|
||||
startOfLineReadTable:ReadTable,
|
||||
startOfFileReadTable:ReadTable,
|
||||
endOfFileReadTable:ReadTable,
|
||||
identAliases:Map<String,ReaderExpDef>
|
||||
};
|
||||
|
||||
typedef ReadTableOptions = {
|
||||
// When this is false (the default), {<...>} always becomes (begin <...>) instead of BraceExp(<...>)
|
||||
?keepBraceExps:Bool
|
||||
};
|
||||
|
||||
@:allow(kiss.Helpers)
|
||||
class Reader {
|
||||
// The built-in readtable
|
||||
public static function builtins() {
|
||||
public static function builtins(?options:ReadTableOptions) {
|
||||
if (options == null) options = {};
|
||||
var readTable:ReadTable = [];
|
||||
|
||||
readTable["("] = (stream, k) -> {
|
||||
@@ -43,9 +55,13 @@ class Reader {
|
||||
readTable["[::"] = (stream, k) -> ListEatingExp(_readExpArray(stream, "]", k));
|
||||
readTable["..."] = (stream, k) -> ListRestExp(nextToken(stream, "name for list-eating rest exp", true));
|
||||
|
||||
// Provides a nice syntactic sugar for (if... {<then block...>} {<else block...>}),
|
||||
// and also handles string interpolation cases like "${exp}moreString":
|
||||
readTable["{"] = (stream:Stream, k) -> CallExp(Symbol("begin").withPos(stream.position()), _readExpArray(stream, "}", k));
|
||||
if (options.keepBraceExps) {
|
||||
readTable["{"] = (stream, k) -> BraceExp(_readExpArray(stream, "}", k));
|
||||
} else {
|
||||
// Provides a nice syntactic sugar for (if... {<then block...>} {<else block...>}),
|
||||
// and also handles string interpolation cases like "${exp}moreString":
|
||||
readTable["{"] = (stream:Stream, k) -> CallExp(Symbol("begin").withPos(stream.position()), _readExpArray(stream, "}", k));
|
||||
}
|
||||
|
||||
readTable["<>["] = (stream, k) -> TypeParams(_readExpArray(stream, "]", k));
|
||||
|
||||
@@ -163,7 +179,7 @@ class Reader {
|
||||
// -+>countVar {body}
|
||||
// -+>countVar (body)
|
||||
// or any of those with the first expression after -> or -+> prefixed by :Void
|
||||
function arrowSyntax(countingLambda:Bool, stream:Stream, k:KissState) {
|
||||
function arrowSyntax(countingLambda:Bool, stream:Stream, k:HasReadTables) {
|
||||
var countVar = if (countingLambda) {
|
||||
_assertRead(stream, k);
|
||||
} else {
|
||||
@@ -209,7 +225,7 @@ class Reader {
|
||||
readTable["-+>"] = arrowSyntax.bind(true);
|
||||
|
||||
// Because macro keys are sorted by length and peekChars(0) returns "", this will be used as the default reader macro:
|
||||
readTable[""] = (stream:Stream, k:KissState) -> {
|
||||
readTable[""] = (stream:Stream, k:HasReadTables) -> {
|
||||
var position = stream.position();
|
||||
var token = nextToken(stream, "a symbol name");
|
||||
// Process dot-access on alias identifiers
|
||||
@@ -263,11 +279,11 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
public static function assertRead(stream:Stream, k:KissState):ReaderExp {
|
||||
public static function assertRead(stream:Stream, k:HasReadTables):ReaderExp {
|
||||
return _assertRead(stream, k);
|
||||
}
|
||||
|
||||
static function _assertRead(stream:Stream, k:KissState):ReaderExp {
|
||||
static function _assertRead(stream:Stream, k:HasReadTables):ReaderExp {
|
||||
var position = stream.position();
|
||||
return switch (_read(stream, k)) {
|
||||
case Some(exp):
|
||||
@@ -294,12 +310,12 @@ class Reader {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function read(stream:Stream, k:KissState):Option<ReaderExp> {
|
||||
public static function read(stream:Stream, k:HasReadTables):Option<ReaderExp> {
|
||||
assertNoPriorState(stream);
|
||||
return _read(stream, k);
|
||||
}
|
||||
|
||||
static function _read(stream:Stream, k:KissState):Option<ReaderExp> {
|
||||
static function _read(stream:Stream, k:HasReadTables):Option<ReaderExp> {
|
||||
var readTable = k.readTable;
|
||||
stream.dropWhitespace();
|
||||
|
||||
@@ -327,7 +343,7 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
static function _readOr(stream:Stream, k:KissState, defaultExp:ReaderExp):ReaderExp {
|
||||
static function _readOr(stream:Stream, k:HasReadTables, defaultExp:ReaderExp):ReaderExp {
|
||||
return switch (_read(stream, k)) {
|
||||
case Some(exp):
|
||||
exp;
|
||||
@@ -356,12 +372,12 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
public static function readExpArray(stream:Stream, end:String, k:KissState, allowEof=false, startingPos=null):Array<ReaderExp> {
|
||||
public static function readExpArray(stream:Stream, end:String, k:HasReadTables, allowEof=false, startingPos=null):Array<ReaderExp> {
|
||||
assertNoPriorState(stream);
|
||||
return _readExpArray(stream, end, k, allowEof, startingPos);
|
||||
}
|
||||
|
||||
static function _readExpArray(stream:Stream, end:String, k:KissState, allowEof=false, startingPos=null):Array<ReaderExp> {
|
||||
static function _readExpArray(stream:Stream, end:String, k:HasReadTables, allowEof=false, startingPos=null):Array<ReaderExp> {
|
||||
var array = [];
|
||||
if (startingPos == null)
|
||||
startingPos = stream.position();
|
||||
@@ -401,50 +417,16 @@ class Reader {
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
Read all the expressions in the given stream, processing them one by one while reading.
|
||||
They can't be read all at once because some expressions change the Readtable state
|
||||
**/
|
||||
public static function readAndProcess(stream:Stream, k:KissState, process:(ReaderExp) -> Void, nested = false) {
|
||||
static function _handleNesting(stream:Stream, nested:Bool) {
|
||||
if (nested) {
|
||||
nestedReadExpArrayStartPositions.push(readExpArrayStartPositions);
|
||||
readExpArrayStartPositions = [];
|
||||
} else {
|
||||
assertNoPriorState(stream);
|
||||
}
|
||||
}
|
||||
|
||||
var startOfFileMacro = chooseReadFunction(stream, k.startOfFileReadTable);
|
||||
if (startOfFileMacro != null) {
|
||||
var pos = stream.position();
|
||||
var v = startOfFileMacro(stream, k);
|
||||
if (v != null)
|
||||
process(v.withPos(pos));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
stream.dropWhitespace();
|
||||
|
||||
var endOfFileMacro = chooseReadFunction(stream, k.endOfFileReadTable, true);
|
||||
if (endOfFileMacro != null) {
|
||||
var pos = stream.position();
|
||||
var v = endOfFileMacro(stream, k);
|
||||
if (v != null)
|
||||
process(v.withPos(pos));
|
||||
}
|
||||
|
||||
if (stream.isEmpty())
|
||||
break;
|
||||
var position = stream.position();
|
||||
var nextExp = Reader._read(stream, k);
|
||||
// The last expression might be a comment, in which case None will be returned
|
||||
switch (nextExp) {
|
||||
case Some(nextExp):
|
||||
process(nextExp);
|
||||
case None:
|
||||
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
|
||||
}
|
||||
}
|
||||
|
||||
static function _finish(stream:Stream, nested:Bool) {
|
||||
if (readExpArrayStartPositions.length != 0) {
|
||||
throw new StreamError(stream.position(), "readExpArray() state is remaining in Reader after readAndProcess()");
|
||||
}
|
||||
@@ -454,6 +436,99 @@ class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
static function _startOfFile(stream:Stream, k:HasReadTables, process:(ReaderExp,String,Void->Void)->Void, ?cc:Void->Void) {
|
||||
if (cc == null) cc = () -> {};
|
||||
var startOfFileMacro = chooseReadFunction(stream, k.startOfFileReadTable);
|
||||
if (startOfFileMacro != null) {
|
||||
var pos = stream.position();
|
||||
var v = null;
|
||||
var str = stream.recordTransaction(Both, () -> {
|
||||
v = startOfFileMacro(stream, k);
|
||||
});
|
||||
if (v != null) {
|
||||
process(v.withPos(pos), str, cc);
|
||||
}
|
||||
} else {
|
||||
cc();
|
||||
}
|
||||
}
|
||||
|
||||
static function _endOfFile(stream:Stream, k:HasReadTables, process:(ReaderExp,String,Void->Void)->Void, ?cc:Void->Void){
|
||||
if (cc == null) cc = () -> {};
|
||||
var endOfFileMacro = chooseReadFunction(stream, k.endOfFileReadTable, true);
|
||||
if (endOfFileMacro != null) {
|
||||
var pos = stream.position();
|
||||
|
||||
var v = null;
|
||||
var str = stream.recordTransaction(Both, () -> {
|
||||
v = endOfFileMacro(stream, k);
|
||||
});
|
||||
if (v != null){
|
||||
process(v.withPos(pos), str, cc);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.isEmpty()){
|
||||
cc();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static function _nextNormalExp(stream:Stream, k:HasReadTables, process:(ReaderExp,String,Void->Void)->Void, ?cc:Void->Void) {
|
||||
if (cc == null) cc = () -> {};
|
||||
|
||||
var nextExp = null;
|
||||
var str = stream.recordTransaction(Both, () -> {
|
||||
nextExp = Reader._read(stream, k);
|
||||
});
|
||||
// The last expression might be a comment, in which case None will be returned
|
||||
switch (nextExp) {
|
||||
case Some(nextExp):
|
||||
process(nextExp, str, cc);
|
||||
case None:
|
||||
stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Read all the expressions in the given stream, processing them one by one while reading.
|
||||
They can't be read all at once because some expressions change the Readtable state
|
||||
**/
|
||||
public static function readAndProcessCC(stream:Stream, k:HasReadTables, process:(ReaderExp, String, cc:Void->Void) -> Void, nested = false) {
|
||||
_handleNesting(stream, nested);
|
||||
|
||||
function afterStartOfFile():Void {
|
||||
stream.dropWhitespace();
|
||||
|
||||
if (_endOfFile(stream, k, process, _finish.bind(stream, nested)))
|
||||
return;
|
||||
|
||||
_nextNormalExp(stream, k, process, afterStartOfFile);
|
||||
}
|
||||
|
||||
_startOfFile(stream, k, process, afterStartOfFile);
|
||||
}
|
||||
|
||||
public static function readAndProcess(stream:Stream, k:HasReadTables, process:(ReaderExp,String) -> Void, nested = false) {
|
||||
var wrappedProcess = (exp, str, cc) -> {
|
||||
process(exp, str);
|
||||
};
|
||||
_handleNesting(stream, nested);
|
||||
_startOfFile(stream, k, wrappedProcess);
|
||||
stream.dropWhitespace();
|
||||
while (true) {
|
||||
if (_endOfFile(stream, k, wrappedProcess, _finish.bind(stream, nested))) {
|
||||
return;
|
||||
}
|
||||
|
||||
_nextNormalExp(stream, k, wrappedProcess);
|
||||
stream.dropWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
public static function withPos(def:ReaderExpDef, pos:Position) {
|
||||
return {
|
||||
pos: pos,
|
||||
@@ -469,7 +544,7 @@ class Reader {
|
||||
}
|
||||
|
||||
// Read a string literal OR a shell section which supports interpolation
|
||||
static function readString(stream:Stream, k:KissState, shell = false) {
|
||||
static function readString(stream:Stream, k:HasReadTables, shell = false) {
|
||||
var pos = stream.position();
|
||||
var stringParts:Array<ReaderExp> = [];
|
||||
var currentStringPart = "";
|
||||
@@ -554,7 +629,7 @@ class Reader {
|
||||
}
|
||||
|
||||
// Read a raw string literal
|
||||
static function readRawString(stream:Stream, k:KissState) {
|
||||
static function readRawString(stream:Stream, k:HasReadTables) {
|
||||
var terminator = '"#';
|
||||
do {
|
||||
var next = stream.expect('# or "', () -> stream.takeChars(1));
|
||||
@@ -606,6 +681,16 @@ class Reader {
|
||||
].join(" ");
|
||||
str += ']';
|
||||
str;
|
||||
case BraceExp(exps):
|
||||
// {e1 e2 e3}
|
||||
var str = '{';
|
||||
str += [
|
||||
for (exp in exps) {
|
||||
exp.def.toString();
|
||||
}
|
||||
].join(" ");
|
||||
str += '}';
|
||||
str;
|
||||
case TypeParams(exps):
|
||||
// <>[T1 T2 T3]
|
||||
var str = '<>[';
|
||||
|
||||
@@ -10,6 +10,7 @@ typedef ReaderExp = {
|
||||
enum ReaderExpDef {
|
||||
CallExp(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...)
|
||||
ListExp(exps:Array<ReaderExp>); // [v1 v2 v3]
|
||||
BraceExp(exps:Array<ReaderExp>); // {e1 e2 e3}
|
||||
StrExp(s:String); // "literal"
|
||||
Symbol(name:String); // s
|
||||
RawHaxe(code:String); // #| haxeCode() |# // deprecated!
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package kiss;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import kiss.Reader;
|
||||
@@ -799,3 +800,5 @@ class SpecialForms {
|
||||
return b.haxeExpr(m);
|
||||
};
|
||||
}
|
||||
|
||||
#end
|
||||
@@ -1,6 +1,6 @@
|
||||
package kiss;
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if (macro || ((sys || hxnodejs) && !frontend))
|
||||
import sys.io.File;
|
||||
#end
|
||||
import haxe.ds.Option;
|
||||
@@ -44,7 +44,7 @@ class Stream {
|
||||
|
||||
public var startOfLine = true;
|
||||
|
||||
#if (sys || hxnodejs)
|
||||
#if (macro || ((sys || hxnodejs) && !frontend))
|
||||
public static function fromFile(file:String) {
|
||||
return new Stream(file, File.getContent(file));
|
||||
}
|
||||
@@ -123,17 +123,25 @@ class Stream {
|
||||
|
||||
/** Every drop call should end up calling dropChars() or the position tracker and recording will be wrong. **/
|
||||
public function dropChars(count:Int, taking:Bool) {
|
||||
if (count < 0) {
|
||||
error(this, "Can't drop negative characters");
|
||||
}
|
||||
for (idx in 0...count) {
|
||||
switch (content.charAt(idx)) {
|
||||
// newline
|
||||
case "\n":
|
||||
_currentTab = "";
|
||||
absoluteChar += absolutePerNewline;
|
||||
line += 1;
|
||||
lineLengths.push(column);
|
||||
column = 1;
|
||||
startOfLine = true;
|
||||
// other whitespace character
|
||||
case c if (c.trim() == ""):
|
||||
_currentTab += c;
|
||||
absoluteChar += 1;
|
||||
column += 1;
|
||||
// non-whitespace
|
||||
default:
|
||||
absoluteChar += 1;
|
||||
column += 1;
|
||||
@@ -159,9 +167,8 @@ class Stream {
|
||||
}
|
||||
|
||||
public function putBackString(s:String) {
|
||||
// I don't know what should happen when putBackString() is used while recording. So for now, it is an error.
|
||||
if (recordingType != Neither) {
|
||||
error(this, "Undefined behavior: calling putBackString() while in the middle of recording a stream transaction");
|
||||
recording = recording.substr(0, recording.length - s.length);
|
||||
}
|
||||
#if macro
|
||||
Kiss.measure("Stream.putBackString", () -> {
|
||||
@@ -173,6 +180,12 @@ class Stream {
|
||||
case "\n":
|
||||
line -= 1;
|
||||
column = lineLengths.pop();
|
||||
// Revert tabs
|
||||
case c if (c.trim() == ""):
|
||||
var lastNewlineIdx = content.lastIndexOf('\n', absoluteChar + 1);
|
||||
if (lastNewlineIdx >= 0 && content.substr(lastNewlineIdx,absoluteChar + 1).trim() == "")
|
||||
_currentTab = _currentTab.substr(0, _currentTab.length - 1);
|
||||
column -= 1;
|
||||
default:
|
||||
column -= 1;
|
||||
}
|
||||
@@ -184,10 +197,21 @@ class Stream {
|
||||
#end
|
||||
}
|
||||
|
||||
var _currentTab = "";
|
||||
|
||||
public function currentTab():String {
|
||||
return _currentTab;
|
||||
}
|
||||
|
||||
public var linePrefix = '';
|
||||
|
||||
public function takeChars(count:Int):Option<String> {
|
||||
if (count > content.length)
|
||||
return None;
|
||||
var toReturn = content.substr(0, count);
|
||||
if (linePrefix.length > 0) {
|
||||
toReturn = toReturn.replace('\n{linePrefix}', '\n');
|
||||
}
|
||||
dropChars(count, true);
|
||||
return Some(toReturn);
|
||||
}
|
||||
@@ -213,6 +237,12 @@ class Stream {
|
||||
dropChars(content.indexOf(s), false);
|
||||
}
|
||||
|
||||
public function tryDropUntil(s:String) {
|
||||
if (content.indexOf(s) != -1) {
|
||||
dropUntil(s);
|
||||
}
|
||||
}
|
||||
|
||||
public function dropWhitespace() {
|
||||
var trimmed = content.ltrim();
|
||||
dropChars(content.length - trimmed.length, false);
|
||||
@@ -263,6 +293,22 @@ class Stream {
|
||||
return Some(toReturn);
|
||||
}
|
||||
|
||||
public function dropOneOf(options:Array<String>) {
|
||||
switch(_dropWhileOneOf(options, true, true)) {
|
||||
case None:
|
||||
error(this, 'expected to drop one of ${options}');
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
public function takeOneOf(options:Array<String>) {
|
||||
return _dropWhileOneOf(options, true, true);
|
||||
}
|
||||
|
||||
function _dropOneOf(options:Array<String>, take:Bool) {
|
||||
_dropWhileOneOf(options, take, true);
|
||||
}
|
||||
|
||||
public function dropWhileOneOf(options:Array<String>) {
|
||||
_dropWhileOneOf(options, false);
|
||||
}
|
||||
@@ -271,7 +317,7 @@ class Stream {
|
||||
return _dropWhileOneOf(options, true);
|
||||
}
|
||||
|
||||
function _dropWhileOneOf(options:Array<String>, take:Bool):Option<String> {
|
||||
function _dropWhileOneOf(options:Array<String>, take:Bool, justOnce=false):Option<String> {
|
||||
var taken = "";
|
||||
|
||||
var lengths = [for (option in options) option.length => true];
|
||||
@@ -283,7 +329,7 @@ class Stream {
|
||||
for (length => _ in lengths) {
|
||||
var sample = content.substr(0, length);
|
||||
if (optsMap.exists(sample)) {
|
||||
nextIs = true;
|
||||
nextIs = !justOnce && true;
|
||||
if (take) {
|
||||
taken += sample;
|
||||
}
|
||||
|
||||
@@ -39,10 +39,6 @@ class BasicTestCase extends Test {
|
||||
_testHaxeInsertion();
|
||||
}
|
||||
|
||||
function testKissInsertion() {
|
||||
Assert.equals(10, Kiss.exp('(+ 5 2 3)'));
|
||||
}
|
||||
|
||||
function testStaticFunction() {
|
||||
Assert.equals(6, BasicTestCase.myFloor(6.5));
|
||||
}
|
||||
@@ -476,6 +472,10 @@ class BasicTestCase extends Test {
|
||||
function testTypedArrayMacro() {
|
||||
_testTypedArrayMacro();
|
||||
}
|
||||
|
||||
|
||||
function testDefault() {
|
||||
_testDefault();
|
||||
}
|
||||
|
||||
var aNullToPrint = null;
|
||||
}
|
||||
@@ -1014,4 +1014,19 @@ From:[(assert false (+ \"false \" \"should \" \"have \" \"been \" \"true\"))]" m
|
||||
(function _testTypedArrayMacro []
|
||||
(let [a (array Float 1 1.5 2)]
|
||||
(Assert.isTrue (Std.isOfType a Array))
|
||||
(Assert.isTrue (Std.isOfType (first a) Float))))
|
||||
(Assert.isTrue (Std.isOfType (first a) Float))))
|
||||
|
||||
(function _testDefault []
|
||||
// TODO it would be nice to allow &mut on the whole list and &final to override it
|
||||
(let [&mut f false
|
||||
&mut t true
|
||||
&mut n null
|
||||
&mut i 5]
|
||||
(default f true)
|
||||
(Assert.equals false f )
|
||||
(default t false)
|
||||
(Assert.equals true t)
|
||||
(default n 5)
|
||||
(Assert.equals 5 n)
|
||||
(default i 9)
|
||||
(Assert.equals i 5)))
|
||||
|
||||
19
src/test/cases/FuzzyMapTestCase.hx
Normal file
19
src/test/cases/FuzzyMapTestCase.hx
Normal file
@@ -0,0 +1,19 @@
|
||||
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;
|
||||
import kiss.FuzzyMapTools;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:build(kiss.Kiss.build())
|
||||
class FuzzyMapTestCase extends Test {
|
||||
function testFuzzyGet() {
|
||||
_testFuzzyGet();
|
||||
}
|
||||
}
|
||||
5
src/test/cases/FuzzyMapTestCase.kiss
Normal file
5
src/test/cases/FuzzyMapTestCase.kiss
Normal file
@@ -0,0 +1,5 @@
|
||||
(function _testFuzzyGet []
|
||||
(let [:kiss.FuzzyMap<String> m [=>"glurg" "burgle"]]
|
||||
(assertLet [(Found "glurg" "burgle" _) (fuzzyGet m "gurg")
|
||||
NotFound (fuzzyGet m "totally different string")]
|
||||
(Assert.pass))))
|
||||
@@ -26,4 +26,12 @@ class ReaderMacroTestCase extends Test {
|
||||
function testQuasiquoteMacro() {
|
||||
_testQuasiquoteMacro();
|
||||
}
|
||||
|
||||
function testStartOfLineInMiddle() {
|
||||
_testStartOfLineInMiddle();
|
||||
}
|
||||
|
||||
function testEofMacro() {
|
||||
_testEofMacro();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
rassert fluffers
|
||||
(Assert.pass))
|
||||
|
||||
(undefReaderMacro ["b" "c"])
|
||||
|
||||
(function _testCommentAtBlockOrArrayEnd []
|
||||
[
|
||||
(+ 1 2)
|
||||
@@ -40,3 +42,18 @@
|
||||
// Comment
|
||||
}
|
||||
(Assert.pass))
|
||||
|
||||
(defReaderMacro &start "bob" [stream] `var1)
|
||||
(defReaderMacro "bob" [stream] `var2)
|
||||
|
||||
|
||||
(function _testStartOfLineInMiddle []
|
||||
(let [var1 "first"
|
||||
var2 "second"]
|
||||
|
||||
(Assert.equals "firstsecond" (+
|
||||
bob bob))))
|
||||
|
||||
(defReaderMacro &eof "" [stream]
|
||||
`(function _testEofMacro []
|
||||
(Assert.pass)))
|
||||
|
||||
Reference in New Issue
Block a user