Files
kiss/src/kiss/Kiss.hx

756 lines
30 KiB
Haxe

package kiss;
#if macro
import haxe.Exception;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.ExprTools;
import haxe.macro.PositionTools;
import haxe.io.Path;
import sys.io.File;
import kiss.Stream;
import kiss.Reader;
import kiss.ReaderExp;
import kiss.FieldForms;
import kiss.SpecialForms;
import kiss.Macros;
import kiss.KissError;
import kiss.cloner.Cloner;
import tink.syntaxhub.*;
import haxe.ds.Either;
using kiss.Kiss;
using kiss.Helpers;
using kiss.Reader;
using tink.MacroApi;
using haxe.io.Path;
typedef ExprConversion = (ReaderExp) -> Expr;
typedef FormDoc = {
minArgs:Null<Int>,
maxArgs:Null<Int>,
?expectedForm:String,
?doc:String
};
typedef KissState = {
className:String,
file:String,
readTable:ReadTable,
startOfLineReadTable:ReadTable,
startOfFileReadTable:ReadTable,
endOfFileReadTable:ReadTable,
fieldForms:Map<String, FieldFormFunction>,
specialForms:Map<String, SpecialFormFunction>,
macros:Map<String, MacroFunction>,
formDocs:Map<String, FormDoc>,
doc:(String, Null<Int>, Null<Int>, ?String, ?String)->Void,
wrapListExps:Bool,
loadedFiles:Map<String, Null<ReaderExp>>,
callAliases:Map<String, ReaderExpDef>,
identAliases:Map<String, ReaderExpDef>,
fieldList:Array<Field>,
// TODO This map was originally created to track whether the programmer wrote their own main function, but could also
// be used to allow macros to edit fields that were already defined (for instance, to decorate a function or add something
// to the constructor body)
fieldDict:Map<String, Field>,
loadingDirectory:String,
hscript:Bool,
macroVars:Map<String, Dynamic>,
collectedBlocks:Map<String, Array<ReaderExp>>,
inStaticFunction:Bool,
typeHints:Array<Var>,
varsInScope:Array<Var>,
varsInScopeAreStatic:Array<Bool>,
localVarsInScope:Array<Var>,
conversionStack:Array<ReaderExp>,
stateChanged:Bool,
printFieldsCalls:Array<ReaderExp>
};
#end
class Kiss {
#if macro
public static function defaultKissState(?context:FrontendContext):KissState {
var className = if (context == null) {
Context.getLocalClass().get().name;
} else {
context.name;
}
var k = {
className: className,
file: "",
readTable: Reader.builtins(),
startOfLineReadTable: new ReadTable(),
startOfFileReadTable: new ReadTable(),
endOfFileReadTable: new ReadTable(),
fieldForms: new Map(),
specialForms: null,
macros: null,
formDocs: new Map(),
doc: null,
wrapListExps: true,
loadedFiles: new Map<String, ReaderExp>(),
// Helpful built-in aliases
// These ones might conflict with a programmer's variable names, so they only apply in call expressions:
callAliases: [
// TODO some of these probably won't conflict, and could be passed as functions for a number of reasons
"print" => Symbol("Prelude.print"),
"sort" => Symbol("Prelude.sort"),
"sortBy" => Symbol("Prelude.sortBy"),
"groups" => Symbol("Prelude.groups"),
"pairs" => Symbol("Prelude.pairs"),
"reverse" => Symbol("Prelude.reverse"),
"memoize" => Symbol("Prelude.memoize"),
"fsMemoize" => Symbol("Prelude.fsMemoize"),
"symbolName" => Symbol("Prelude.symbolName"),
"symbolNameValue" => Symbol("Prelude.symbolNameValue"),
"symbol" => Symbol("Prelude.symbol"),
"expList" => Symbol("Prelude.expList"),
"map" => Symbol("Lambda.map"),
"filter" => Symbol("Prelude.filter"),
"flatten" => Symbol("Lambda.flatten"),
"has" => Symbol("Lambda.has"),
"count" => Symbol("Lambda.count"),
"enumerate" => Symbol("Prelude.enumerate"),
"assertProcess" => Symbol("Prelude.assertProcess"),
"tryProcess" => Symbol("Prelude.tryProcess"),
"libPath" => Symbol("Prelude.libPath"),
"userHome" => Symbol("Prelude.userHome"),
"random" => Symbol("Std.random"),
"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:
"min" => Symbol("Prelude.min"),
"max" => Symbol("Prelude.max"),
"iHalf" => Symbol("Prelude.iHalf"),
"iThird" => Symbol("Prelude.iThird"),
"iFourth" => Symbol("Prelude.iFourth"),
"iFifth" => Symbol("Prelude.iFifth"),
"iSixth" => Symbol("Prelude.iSixth"),
"iSeventh" => Symbol("Prelude.iSeventh"),
"iEighth" => Symbol("Prelude.iEighth"),
"iNinth" => Symbol("Prelude.iNinth"),
"iTenth" => Symbol("Prelude.iTenth"),
"fHalf" => Symbol("Prelude.fHalf"),
"fThird" => Symbol("Prelude.fThird"),
"fFourth" => Symbol("Prelude.fFourth"),
"fFifth" => Symbol("Prelude.fFifth"),
"fSixth" => Symbol("Prelude.fSixth"),
"fSeventh" => Symbol("Prelude.fSeventh"),
"fEighth" => Symbol("Prelude.fEighth"),
"fNinth" => Symbol("Prelude.fNinth"),
"fTenth" => Symbol("Prelude.fTenth"),
"uuid" => Symbol("Prelude.uuid"),
],
identAliases: [
// These ones won't conflict with variables and might commonly be used with (apply)
"+" => Symbol("Prelude.add"),
"-" => Symbol("Prelude.subtract"),
"*" => Symbol("Prelude.multiply"),
"/" => Symbol("Prelude.divide"),
"%" => Symbol("Prelude.mod"),
"^" => Symbol("Prelude.pow"),
">" => Symbol("Prelude.greaterThan"),
">=" => Symbol("Prelude.greaterEqual"),
"<" => Symbol("Prelude.lessThan"),
"<=" => Symbol("Prelude.lesserEqual"),
"=" => Symbol("Prelude.areEqual"),
// These ones *probably* won't conflict with variables and might be passed as functions
"chooseRandom" => Symbol("Prelude.chooseRandom"),
// These ones *probably* won't conflict with variables and might commonly be used with (apply) because they are variadic
"joinPath" => Symbol("Prelude.joinPath"),
"readDirectory" => Symbol("Prelude.readDirectory"),
"substr" => Symbol("Prelude.substr"),
"isListExp" => Symbol("Prelude.isListExp"),
"isNull" => Symbol("Prelude.isNull")
/* 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
applies (the Array<Array<Dynamic>>) to the result */
/* concat used to live here as an alias but now it is in a macro that also
applies (the Array<Dynamic>) to the result */
],
fieldList: [],
fieldDict: new Map(),
loadingDirectory: "",
hscript: false,
macroVars: new Map(),
collectedBlocks: new Map(),
inStaticFunction: false,
typeHints: [],
varsInScope: [],
varsInScopeAreStatic: [],
localVarsInScope: [],
conversionStack: [],
stateChanged: false,
printFieldsCalls: []
};
k.doc = (form:String, minArgs:Null<Int>, maxArgs:Null<Int>, expectedForm = "", doc = "") -> {
k.formDocs[form] = {
minArgs: minArgs,
maxArgs: maxArgs,
expectedForm: expectedForm,
doc: doc
};
return;
};
FieldForms.addBuiltins(k);
k.specialForms = SpecialForms.builtins(k, context);
k.macros = Macros.builtins(k);
return k;
}
public static function _try<T>(operation:() -> T):Null<T> {
#if !macrotest
try {
#end
return operation();
#if !macrotest
} catch (err:StreamError) {
Sys.stderr().writeString(err + "\n");
Sys.exit(1);
return null;
} catch (err:KissError) {
Sys.stderr().writeString(err + "\n");
Sys.exit(1);
return null;
} catch (err:UnmatchedBracketSignal) {
Sys.stderr().writeString(Stream.toPrint(err.position) + ': Unmatched ${err.type}\n');
Sys.exit(1);
return null;
} catch (err:Exception) {
Sys.stderr().writeString("Error: " + err.message + "\n");
Sys.stderr().writeString(err.stack.toString() + "\n");
Sys.exit(1);
return null;
}
#end
}
/**
* Initializer macro: suppress some compiler warnings that Kiss code commonly generates
* Source: https://try.haxe.org/#10F18962 by @kLabz
*/
public static macro function setup() {
haxe.macro.Context.onAfterTyping(function(_) {
haxe.macro.Context.filterMessages(function(msg) {
return switch (msg) {
case Warning("This case is unused", _): false;
case _: true;
};
});
});
return macro {};
}
#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) {
k.fieldList = Context.getBuildFields();
for (field in k.fieldList) {
k.fieldDict[field.name] = field;
switch (field.kind) {
case FVar(t, e) | FProp(_, _, t, e):
var v = {
name: field.name,
type: t,
expr: e
};
k.addVarInScope(v, false, field.access.indexOf(AStatic) != -1);
default:
}
}
}
}
/**
Build macro: add fields to a class from a corresponding .kiss file
**/
public static function build(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array<Field> {
var classPath = Context.getPosInfos(Context.currentPos()).file;
// (load... ) relative to the original file
var loadingDirectory = if (classPath == '?') {
var p = Path.directory(kissFile);
kissFile = kissFile.withoutDirectory();
p;
} else {
Path.directory(classPath);
}
if (kissFile == null) {
kissFile = classPath.withoutDirectory().withoutExtension().withExtension("kiss");
}
//trace('kiss build $kissFile');
return _try(() -> {
#if profileKiss
haxe.Timer.measure(() -> {
trace(kissFile);
#end
if (k == null)
k = defaultKissState(context);
k.addContextFields(useClassFields);
k.loadingDirectory = loadingDirectory;
var topLevelBegin = load(kissFile, k);
if (topLevelBegin != null) {
// If no main function is defined manually, Kiss expressions at the top of a file will be put in a main function.
// If a main function IS defined, this will result in an error
if (k.fieldDict.exists("main")) {
throw KissError.fromExp(topLevelBegin, '$kissFile has expressions outside of field definitions, but already defines its own main function.');
}
var b = topLevelBegin.expBuilder();
// This doesn't need to be added to the fieldDict because all code generation is done
k.fieldList.push({
name: "main",
access: [AStatic,APublic],
kind: FFun(Helpers.makeFunction(
b.symbol("main"),
false,
b.list([]),
[topLevelBegin],
k,
"function",
[])),
pos: topLevelBegin.macroPos()
});
}
k.fieldList;
#if profileKiss
});
#end
});
}
public static function load(kissFile:String, k:KissState, ?loadingDirectory:String, loadAllExps = false):Null<ReaderExp> {
if (loadingDirectory == null)
loadingDirectory = k.loadingDirectory;
var fullPath = if (Path.isAbsolute(kissFile)) {
kissFile;
} else {
Path.join([loadingDirectory, kissFile]);
};
var previousFile = k.file;
k.file = fullPath;
if (k.loadedFiles.exists(fullPath)) {
return k.loadedFiles[fullPath];
}
var stream = Stream.fromFile(fullPath);
var startPosition = stream.position();
var loadedExps = [];
Reader.readAndProcess(stream, k, (nextExp) -> {
#if test
Sys.println(nextExp.def.toString());
#end
// readerExpToHaxeExpr must be called to process readermacro, alias, and macro definitions
macroUsed = false;
var expr = 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.
// conditional compiler macros like (#when) tend to return empty blocks, or blocks containing empty blocks
// when they contain field forms, so this should also be ignored
function isEmpty(expr) {
switch (expr.expr) {
case EBlock([]):
case EBlock(blockExps):
for (exp in blockExps) {
if (!isEmpty(exp))
return false;
}
default:
return false;
}
return true;
}
// When calling from build(), we can't add all expressions to the (begin) returned by (load), because that will
// cause double-evaluation of field forms
if (loadAllExps) {
loadedExps.push(nextExp);
} else if (!isEmpty(expr)) {
// don't double-compile macros:
if (macroUsed) {
loadedExps.push(RawHaxe(expr.toString()).withPosOf(nextExp));
} else {
loadedExps.push(nextExp);
}
}
});
var exp = if (loadedExps.length > 0) {
CallExp(Symbol("begin").withPos(startPosition), loadedExps).withPos(startPosition);
} else {
null;
}
k.loadedFiles[fullPath] = exp;
k.file = previousFile;
return exp;
}
/**
* Build macro: add fields to a Haxe class by compiling multiple Kiss files in order with the same KissState
*/
public static function buildAll(kissFiles:Array<String>, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array<Field> {
if (k == null)
k = defaultKissState(context);
k.addContextFields(useClassFields);
for (file in kissFiles) {
build(file, k, false, context);
}
return k.fieldList;
}
static var macroUsed = false;
static var expCache:haxe.DynamicAccess<String> = null;
static var cacheFile = ".kissCache.json";
static var cacheThreshold = 0.2;
public static function readerExpToHaxeExpr(exp, k): Expr {
return switch (macroExpandAndConvert(exp, k, false)) {
case Right(expr): expr;
default: throw "macroExpandAndConvert is broken";
};
}
public static function macroExpand(exp, k):ReaderExp {
return switch (macroExpandAndConvert(exp, k, true)) {
case Left(exp): exp;
default: throw "macroExpandAndConvert is broken";
};
}
// Core functionality of Kiss: returns ReaderExp when macroExpandOnly is true, and haxe.macro.Expr otherwise
public static function macroExpandAndConvert(exp:ReaderExp, k:KissState, macroExpandOnly:Bool):Either<ReaderExp,Expr> {
#if kissCache
if (!macroExpandOnly) {
if (expCache == null) {
expCache = if (sys.FileSystem.exists(cacheFile)) {
haxe.Json.parse(File.getContent(cacheFile));
} else {
{};
}
}
var str = Reader.toString(exp.def);
if (expCache.exists(str)) {
return Context.parse(expCache[str], Helpers.macroPos(exp));
}
}
#end
if (k.conversionStack.length == 0) k.stateChanged = false;
k.conversionStack.push(exp);
var macros = k.macros;
var fieldForms = k.fieldForms;
var specialForms = k.specialForms;
var formDocs = k.formDocs;
// Bind the table arguments of this function for easy recursive calling/passing
var convert = macroExpandAndConvert.bind(_, k, macroExpandOnly);
function left (c:Either<ReaderExp,Expr>) {
return switch (c) {
case Left(exp): exp;
default: throw "macroExpandAndConvert is broken";
};
}
function right (c:Either<ReaderExp,Expr>) {
return switch (c) {
case Right(exp): exp;
default: throw "macroExpandAndConvert is broken";
};
}
function leftForEach(convertedExps:Array<Either<ReaderExp,Expr>>) {
return convertedExps.map(left);
}
function rightForEach(convertedExps:Array<Either<ReaderExp,Expr>>) {
return convertedExps.map(right);
}
function checkNumArgs(form:String) {
if (formDocs.exists(form)) {
var docs = formDocs[form];
// null docs can get passed around by renameAndDeprecate functions. a check here is more DRY
if (docs != null)
exp.checkNumArgs(docs.minArgs, docs.maxArgs, docs.expectedForm);
}
}
if (k.hscript)
exp = Helpers.removeTypeAnnotations(exp);
var none = EBlock([]).withMacroPosOf(exp);
var startTime = haxe.Timer.stamp();
var expr:Either<ReaderExp,Expr> = switch (exp.def) {
case None:
if (macroExpandOnly) Left(exp) else Right(none);
case Symbol(alias) if (k.identAliases.exists(alias)):
var substitution = k.identAliases[alias].withPosOf(exp);
if (macroExpandOnly) Left(substitution) else macroExpandAndConvert(substitution, k, false);
case Symbol(name) if (!macroExpandOnly):
try {
Right(Context.parse(name, exp.macroPos()));
} catch (err:haxe.Exception) {
throw KissError.fromExp(exp, "invalid symbol");
};
case StrExp(s) if (!macroExpandOnly):
Right(EConst(CString(s)).withMacroPosOf(exp));
case CallExp({pos: _, def: Symbol(ff)}, args) if (fieldForms.exists(ff) && !macroExpandOnly):
checkNumArgs(ff);
var field = fieldForms[ff](exp, args.copy(), k);
k.fieldList.push(field);
k.fieldDict[field.name] = field;
k.stateChanged = true;
Right(none); // Field forms are no-ops
case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)):
checkNumArgs(mac);
macroUsed = true;
var expanded = macros[mac](exp, args.copy(), k);
if (expanded != null) {
convert(expanded);
} else {
Right(none);
};
case CallExp({pos: _, def: Symbol(specialForm)}, args) if (specialForms.exists(specialForm) && !macroExpandOnly):
checkNumArgs(specialForm);
Right(specialForms[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):
var convertedArgs = [for (argExp in args) convert(argExp)];
if (macroExpandOnly) {
var convertedArgs = leftForEach(convertedArgs);
Left(CallExp(func, convertedArgs).withPosOf(exp));
}
else {
var convertedArgs = rightForEach(convertedArgs);
Right(ECall(right(convert(func)), convertedArgs).withMacroPosOf(exp));
}
case ListExp(elements):
var isMap = false;
var convertedElements = [
for (elementExp in elements) {
switch (elementExp.def) {
case KeyValueExp(_, _):
isMap = true;
default:
}
convert(elementExp);
}
];
if (macroExpandOnly)
Left(ListExp(leftForEach(convertedElements)).withPosOf(exp));
else {
var arrayDecl = EArrayDecl(rightForEach(convertedElements)).withMacroPosOf(exp);
Right(if (!isMap && k.wrapListExps && !k.hscript) {
ENew({
pack: ["kiss"],
name: "List"
}, [arrayDecl]).withMacroPosOf(exp);
} else {
arrayDecl;
});
}
case RawHaxe(code) if (!macroExpandOnly):
try {
Right(Context.parse(code, exp.macroPos()));
} catch (err:Exception) {
throw KissError.fromExp(exp, 'Haxe parse error: $err');
};
case RawHaxeBlock(code) if (!macroExpandOnly):
try {
Right(Context.parse('{$code}', exp.macroPos()));
} catch (err:Exception) {
throw KissError.fromExp(exp, 'Haxe parse error: $err');
};
case FieldExp(field, innerExp):
var convertedInnerExp = convert(innerExp);
if (macroExpandOnly)
Left(FieldExp(field, left(convertedInnerExp)).withPosOf(exp));
else
Right(EField(right(convertedInnerExp), field).withMacroPosOf(exp));
case KeyValueExp(keyExp, valueExp) if (!macroExpandOnly):
Right(EBinop(OpArrow, right(convert(keyExp)), right(convert(valueExp))).withMacroPosOf(exp));
case Quasiquote(innerExp) if (!macroExpandOnly):
// This statement actually turns into an HScript expression before running
Right(macro {
Helpers.evalUnquotes($v{innerExp});
});
default:
if (macroExpandOnly)
Left(exp);
else
throw KissError.fromExp(exp, 'conversion not implemented');
};
var conversionTime = haxe.Timer.stamp() - startTime;
k.conversionStack.pop();
#if kissCache
if (!macroExpandOnly) {
if (conversionTime > cacheThreshold && !k.stateChanged) {
expCache[str] = expr.toString();
File.saveContent(cacheFile, haxe.Json.stringify(expCache));
}
}
#end
return expr;
}
public static function addVarInScope(k: KissState, v:Var, local:Bool, isStatic:Bool=false) {
if (v.type != null)
k.typeHints.push(v);
k.varsInScope.push(v);
k.varsInScopeAreStatic.push(isStatic);
if (local)
k.localVarsInScope.push(v);
}
public static function removeVarInScope(k: KissState, v:Var, local:Bool) {
function removeLast(list:Array<Var>, v:Var) {
var index = list.lastIndexOf(v);
list.splice(index, 1);
return index;
}
if (v.type != null)
removeLast(k.typeHints, v);
var idx = removeLast(k.varsInScope, v);
k.varsInScopeAreStatic.splice(idx, 1);
if (local)
removeLast(k.localVarsInScope, v);
}
static function disableMacro(copy:KissState, m:String, reason:String) {
copy.macros[m] = (wholeExp:ReaderExp, exps, k) -> {
var b = wholeExp.expBuilder();
// have this throw during macroEXPANSION, not before (so assertThrows will catch it)
b.throwKissError('$m is unavailable in macros because $reason');
};
}
// This doesn't clone k because k might be modified in important ways :(
public static function forStaticFunction(k:KissState, inStaticFunction:Bool) {
k.inStaticFunction = inStaticFunction;
return k;
}
// Return an identical Kiss State, but without type annotations or wrapping list expressions as kiss.List constructor calls.
public static function forHScript(k:KissState):KissState {
var copy = new Cloner().clone(k);
copy.hscript = true;
// disallow macros that will error when run in hscript:
disableMacro(copy, "ifLet", "hscript doesn't support pattern-matching");
disableMacro(copy, "whenLet", "hscript doesn't support pattern-matching");
disableMacro(copy, "unlessLet", "hscript doesn't support pattern-matching");
copy.macros["cast"] = (wholeExp:ReaderExp, exps, k) -> {
exps[0];
};
return copy;
}
public static function forMacroEval(k:KissState): KissState {
var copy = k.forHScript();
// Catch accidental misuse of (set) on macroVars
var setLocal = copy.specialForms["set"];
copy.specialForms["set"] = (wholeExp:ReaderExp, exps, k:KissState) -> {
switch (exps[0].def) {
case Symbol(varName) if (k.macroVars.exists(varName)):
var b = wholeExp.expBuilder();
// have this throw during macroEXPANSION, not before (so assertThrows will catch it)
copy.convert(b.throwKissError('If you intend to change macroVar $varName, use setMacroVar instead. If not, rename your local variable for clarity.'));
default:
setLocal(wholeExp, exps, copy);
};
};
copy.macros["expect"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
wholeExp.checkNumArgs(3, null, "(expect <stream> <description> <stream method> <args...>)");
var b = wholeExp.expBuilder();
var streamSymbol = exps.shift();
b.callField("expect", streamSymbol, [exps.shift(), b.callSymbol("lambda", [b.list([]), b.callField(Prelude.symbolNameValue(exps.shift()), streamSymbol, exps)])]);
};
// TODO should this also be in forHScript()?
// In macro evaluation,
copy.macros.remove("eval");
// BECAUSE it is provided as a function instead.
return copy;
}
// Return an identical Kiss State, but without wrapping list expressions as kiss.List constructor calls.
public static function withoutListWrapping(k:KissState) {
var copy = new Cloner().clone(k);
copy.wrapListExps = false;
return copy;
}
// Return an identical Kiss State, but prepared for parsing a branch pattern of a (case...) expression
public static function forCaseParsing(k:KissState):KissState {
var copy = withoutListWrapping(k);
copy.macros.remove("or");
copy.specialForms["or"] = SpecialForms.caseOr;
copy.specialForms["as"] = SpecialForms.caseAs;
return copy;
}
public static function convert(k:KissState, exp:ReaderExp) {
return readerExpToHaxeExpr(exp, k);
}
#end
}