package kiss; #if macro import haxe.Exception; import haxe.macro.Context; import haxe.macro.Expr; import kiss.Stream; import kiss.Reader; import kiss.FieldForms; import kiss.SpecialForms; import kiss.Macros; import kiss.CompileError; using kiss.Helpers; using kiss.Reader; using tink.MacroApi; typedef ExprConversion = (ReaderExp) -> Expr; typedef KissState = { className:String, readTable:Map, fieldForms:Map, specialForms:Map, macros:Map, convert:ExprConversion }; class Kiss { /** Build a Haxe class from a corresponding .kiss file **/ macro static public function build(kissFile:String):Array { try { var classFields = Context.getBuildFields(); var className = Context.getLocalClass().get().name; var stream = new Stream(kissFile); var k = { className: className, readTable: Reader.builtins(), fieldForms: FieldForms.builtins(), specialForms: SpecialForms.builtins(), macros: Macros.builtins(), convert: null } k.convert = readerExpToHaxeExpr.bind(_, k); // Helpful aliases k.defAlias("print", "Prelude.print"); k.defAlias("map", "Lambda.map"); k.defAlias("filter", "Lambda.filter"); k.defAlias("has", "Lambda.has"); while (true) { stream.dropWhitespace(); if (stream.isEmpty()) break; var position = stream.position(); var nextExp = Reader.read(stream, k.readTable); // The last expression might be a comment, in which case None will be returned switch (nextExp) { case Some(nextExp): #if test Sys.println(nextExp.def.toString()); #end var field = readerExpToField(nextExp, k); if (field != null) classFields.push(field); case None: stream.dropWhitespace(); // If there was a comment, drop whitespace that comes after } } return classFields; } catch (err:CompileError) { Sys.println(err); Sys.exit(1); return null; // Necessary for build() to compile } catch (err:Exception) { throw err; // Re-throw haxe exceptions for precise stacks } } static function readerExpToField(exp:ReaderExp, k:KissState):Null { var fieldForms = k.fieldForms; // Macros at top-level are allowed if they expand into a fieldform, or null like defreadermacro var macros = k.macros; return switch (exp.def) { case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): var expandedExp = macros[mac](exp, args, k); if (expandedExp != null) readerExpToField(macros[mac](expandedExp, args, k), k) else null; case CallExp({pos: _, def: Symbol(formName)}, args) if (fieldForms.exists(formName)): fieldForms[formName](exp, args, k); default: throw CompileError.fromExp(exp, 'invalid valid field form'); }; } static function readerExpToHaxeExpr(exp:ReaderExp, k:KissState):Expr { var macros = k.macros; var specialForms = k.specialForms; // Bind the table arguments of this function for easy recursive calling/passing var convert = readerExpToHaxeExpr.bind(_, k); var expr = switch (exp.def) { case Symbol(name): Context.parse(name, exp.macroPos()); case StrExp(s): EConst(CString(s)).withMacroPosOf(exp); case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): convert(macros[mac](exp, args, k)); case CallExp({pos: _, def: Symbol(specialForm)}, args) if (specialForms.exists(specialForm)): specialForms[specialForm](exp, args, k); case CallExp(func, args): ECall(convert(func), [for (argExp in args) convert(argExp)]).withMacroPosOf(exp); /* // Typed expressions in the wild become casts: case TypedExp(type, innerExp): ECast(convert(innerExp), if (type.length > 0) Helpers.parseComplexType(type, exp) else null).withMacroPosOf(wholeExp); */ case ListExp(elements): ENew({ pack: ["kiss"], name: "List" }, [ EArrayDecl([for (elementExp in elements) convert(elementExp)]).withMacroPosOf(exp) ]).withMacroPosOf(exp); case RawHaxe(code): Context.parse(code, exp.macroPos()); case FieldExp(field, innerExp): EField(convert(innerExp), field).withMacroPosOf(exp); default: throw CompileError.fromExp(exp, 'conversion not implemented'); }; #if test Sys.println(expr.toString()); #end return expr; } } #end