diff --git a/src/kiss/FieldForms.hx b/src/kiss/FieldForms.hx index 94140b35..9887b191 100644 --- a/src/kiss/FieldForms.hx +++ b/src/kiss/FieldForms.hx @@ -5,11 +5,13 @@ import haxe.macro.Context; import kiss.Reader; import kiss.Types; import kiss.Helpers; +import kiss.Stream; +using kiss.Reader; using StringTools; // Field forms convert Kiss reader expressions into Haxe macro class fields -typedef FieldFormFunction = (position:String, args:Array, convert:ExprConversion) -> Field; +typedef FieldFormFunction = (pos:Position, args:Array, convert:ExprConversion) -> Field; class FieldForms { public static function builtins() { @@ -33,27 +35,27 @@ class FieldForms { return access; } - static function fieldName(formName:String, position:String, nameExp:ReaderExp) { - return switch (nameExp) { - case Symbol(name) | TypedExp(_, Symbol(name)): + static function fieldName(formName:String, nameExp:ReaderExp) { + return switch (nameExp.def) { + case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): name; default: - throw 'The first argument to $formName at $position should be a variable name or typed variable name'; + throw 'The first argument to $formName at ${nameExp.pos} should be a variable name or typed variable name'; }; } - static function varOrProperty(formName:String, position:String, args:Array, convert:ExprConversion):Field { + static function varOrProperty(formName:String, position:Position, args:Array, convert:ExprConversion):Field { if (args.length != 2) { throw '$formName with $args at $position is not a valid field definition'; } - var name = fieldName(formName, position, args[0]); + var name = fieldName(formName, args[0]); var access = fieldAccess(formName, name); return { name: name, access: access, - kind: FVar(switch (args[0]) { + kind: FVar(switch (args[0].def) { case TypedExp(type, _): Helpers.parseComplexType(type); default: null; @@ -63,12 +65,12 @@ class FieldForms { } // TODO &rest, &body and &optional arguments - static function funcOrMethod(formName:String, position:String, args:Array, convert:ExprConversion):Field { + static function funcOrMethod(formName:String, position:Position, args:Array, convert:ExprConversion):Field { if (args.length <= 2) { throw '$formName with $args is not a valid function/method definition'; } - var name = fieldName(formName, position, args[0]); + var name = fieldName(formName, args[0]); var access = fieldAccess(formName, name); return { @@ -76,19 +78,19 @@ class FieldForms { access: access, // TODO type parameter declarations kind: FFun({ - args: switch (args[1]) { + args: switch (args[1].def) { case ListExp(funcArgs): [ // TODO optional arguments, default values for (funcArg in funcArgs) { - name: switch (funcArg) { - case Symbol(name) | TypedExp(_, Symbol(name)): + name: switch (funcArg.def) { + case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): name; default: throw '$funcArg should be a symbol or typed symbol for a function argument'; }, - type: switch (funcArg) { + type: switch (funcArg.def) { case TypedExp(type, _): Helpers.parseComplexType(type); default: null; @@ -100,13 +102,13 @@ class FieldForms { default: throw '${args[1]} should be an argument list'; }, - ret: switch (args[0]) { + ret: switch (args[0].def) { case TypedExp(type, _): Helpers.parseComplexType(type); default: null; }, expr: { pos: Context.currentPos(), - expr: EReturn(convert(CallExp(Symbol("begin"), args.slice(2)))) + expr: EReturn(convert(CallExp(Symbol("begin").withPos(args[2].pos), args.slice(2)).withPos(args[2].pos))) } }), pos: Context.currentPos() diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 7c6a446e..b7075bce 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -62,17 +62,17 @@ class Kiss { return classFields; } - static function readerExpToField(exp:ReaderExp, position:String, k:KissState):Null { + static function readerExpToField(exp:ReaderExp, position:Position, 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) { - case CallExp(Symbol(mac), args) if (macros.exists(mac)): + return switch (exp.def) { + case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): var expandedExp = macros[mac](args, k); if (expandedExp != null) readerExpToField(macros[mac](args, k), position, k) else null; - case CallExp(Symbol(formName), args) if (fieldForms.exists(formName)): + case CallExp({pos: _, def: Symbol(formName)}, args) if (fieldForms.exists(formName)): fieldForms[formName](position, args, readerExpToHaxeExpr.bind(_, k)); default: throw '$exp at $position is not a valid field form'; @@ -84,7 +84,7 @@ class Kiss { 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) { + var expr = switch (exp.def) { case Symbol(name): Context.parse(name, Context.currentPos()); case StrExp(s): @@ -92,9 +92,9 @@ class Kiss { pos: Context.currentPos(), expr: EConst(CString(s)) }; - case CallExp(Symbol(mac), args) if (macros.exists(mac)): + case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)): convert(macros[mac](args, k)); - case CallExp(Symbol(specialForm), args) if (specialForms.exists(specialForm)): + case CallExp({pos: _, def: Symbol(specialForm)}, args) if (specialForms.exists(specialForm)): specialForms[specialForm](args, convert); case CallExp(func, body): { diff --git a/src/kiss/Macros.hx b/src/kiss/Macros.hx index bda9c703..33894f25 100644 --- a/src/kiss/Macros.hx +++ b/src/kiss/Macros.hx @@ -7,6 +7,7 @@ import hscript.Interp; import kiss.Reader; import kiss.Kiss; +using kiss.Reader; using kiss.Helpers; // Macros generate new Kiss reader expressions from the arguments of their call expression. @@ -28,14 +29,14 @@ class Macros { if (exps.length != 2) { throw 'Got ${exps.length} arguments for % instead of 2.'; } - CallExp(Symbol("Prelude.mod"), [exps[1], exps[0]]); + CallExp(Symbol("Prelude.mod").withPos(exps[0].pos), [exps[1], exps[0]]).withPos(exps[0].pos); }; macros["^"] = (exps:Array, k) -> { if (exps.length != 2) { throw 'Got ${exps.length} arguments for ^ instead of 2.'; } - CallExp(Symbol("Prelude.pow"), [exps[1], exps[0]]); + CallExp(Symbol("Prelude.pow").withPos(exps[0].pos), [exps[1], exps[0]]).withPos(exps[0].pos); }; macros["min"] = foldMacro("Prelude.minInclusive"); @@ -54,7 +55,7 @@ class Macros { macros["defmacrofun"] = (exps:Array, k:KissState) -> { if (exps.length < 3) throw '${exps.length} is not enough arguments for (defmacrofun [name] [args] [body])'; - var macroName = switch (exps[0]) { + var macroName = switch (exps[0].def) { case Symbol(name): name; default: throw 'first argument ${exps[0]} for defmacrofun should be a symbol for the macro name'; }; @@ -69,7 +70,7 @@ class Macros { ]).withContextPos(); }; - CallExp(Symbol("defun"), exps); + CallExp(Symbol("defun").withPos(exps[0].pos), exps).withPos(exps[0].pos); } // For now, reader macros only support a one-expression body implemented in #|raw haxe|# @@ -77,16 +78,16 @@ class Macros { if (exps.length != 3) { throw 'wrong number of expressions for defreadermacro: $exps should be String, [streamArgName], RawHaxe'; } - switch (exps[0]) { + switch (exps[0].def) { case StrExp(s): - switch (exps[1]) { - case ListExp([Symbol(streamArgName)]): - switch (exps[2]) { + switch (exps[1].def) { + case ListExp([{pos: _, def: Symbol(streamArgName)}]): + switch (exps[2].def) { case RawHaxe(code): k.readTable[s] = (stream) -> { var parser = new Parser(); var interp = new Interp(); - interp.variables.set("ReaderExp", ReaderExp); + interp.variables.set("ReaderExp", ReaderExpDef); interp.variables.set(streamArgName, stream); interp.execute(parser.parseString(code)); }; @@ -107,8 +108,12 @@ class Macros { } static function foldMacro(func:String):MacroFunction { - return (exps, k) -> { - CallExp(Symbol("Lambda.fold"), [ListExp(exps.slice(1)), Symbol(func), exps[0]]); + return (exps:Array, k) -> { + CallExp(Symbol("Lambda.fold").withPos(exps[0].pos), [ + ListExp(exps.slice(1)).withPos(exps[0].pos), + Symbol(func).withPos(exps[0].pos), + exps[0] + ]).withPos(exps[0].pos); }; } } diff --git a/src/kiss/Reader.hx b/src/kiss/Reader.hx index 47c0f89e..19d676a5 100644 --- a/src/kiss/Reader.hx +++ b/src/kiss/Reader.hx @@ -3,7 +3,14 @@ package kiss; import haxe.ds.Option; import kiss.Stream; -enum ReaderExp { +using kiss.Reader; + +typedef ReaderExp = { + pos:Position, + def:ReaderExpDef +}; + +enum ReaderExpDef { CallExp(func:ReaderExp, args:Array); // (f a1 a2...) ListExp(exps:Array); // [v1 v2 v3] StrExp(s:String); // "literal" @@ -12,7 +19,7 @@ enum ReaderExp { TypedExp(path:String, exp:ReaderExp); // :type [exp] } -typedef ReadFunction = (Stream) -> Null; +typedef ReadFunction = (Stream) -> Null; class Reader { // The built-in readtable @@ -64,6 +71,7 @@ class Reader { if (stream.isEmpty()) return None; + var position = stream.position(); var readTableKeys = [for (key in readTable.keys()) key]; readTableKeys.sort((a, b) -> b.length - a.length); @@ -72,7 +80,11 @@ class Reader { case Some(k) if (k == key): stream.dropString(key); var expOrNull = readTable[key](stream); - return if (expOrNull != null) Some(expOrNull) else read(stream, readTable); + return if (expOrNull != null) { + Some(expOrNull.withPos(position)); + } else { + read(stream, readTable); + } default: } } @@ -90,4 +102,11 @@ class Reader { stream.dropString(end); return array; } + + public static function withPos(def:ReaderExpDef, pos:Position) { + return { + pos: pos, + def: def + }; + } } diff --git a/src/kiss/SpecialForms.hx b/src/kiss/SpecialForms.hx index 28bbbc1f..619f5bab 100644 --- a/src/kiss/SpecialForms.hx +++ b/src/kiss/SpecialForms.hx @@ -5,6 +5,7 @@ import haxe.macro.Context; import kiss.Reader; import kiss.Types; +using kiss.Reader; using kiss.Helpers; using kiss.Prelude; @@ -33,7 +34,7 @@ class SpecialForms { if (args.length < 1) { throw '(new [type] constructorArgs...) is missing a type!'; } - var classType = switch (args[0]) { + var classType = switch (args[0].def) { case Symbol(name): name; default: throw 'first arg in (new [type] ...) should be a class to instantiate'; }; @@ -46,7 +47,7 @@ class SpecialForms { // TODO refactor out EVar generation and allow var bindings to destructure lists and key-value pairs map["let"] = (args:Array, convert:ExprConversion) -> { - var bindingList = switch (args[0]) { + var bindingList = switch (args[0].def) { case ListExp(bindingExps) if (bindingExps.length > 0 && bindingExps.length % 2 == 0): bindingExps; default: @@ -56,13 +57,13 @@ class SpecialForms { var varDefs = [ for (bindingPair in bindingPairs) { - name: switch (bindingPair[0]) { - case Symbol(name) | TypedExp(_, Symbol(name)): + name: switch (bindingPair[0].def) { + case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): name; default: throw 'first element of binding pair $bindingPair should be a symbol or typed symbol'; }, - type: switch (bindingPair[0]) { + type: switch (bindingPair[0].def) { case TypedExp(type, _): Helpers.parseComplexType(type); default: null; @@ -97,7 +98,7 @@ class SpecialForms { if (args.length != 2) { throw '(the [type] [value]) expression has wrong number of arguments: ${args.length}'; } - ECheckType(convert(args[1]), switch (args[0]) { + ECheckType(convert(args[1]), switch (args[0].def) { case Symbol(type): Helpers.parseComplexType(type); default: throw 'first argument to (the... ) should be a valid type'; }).withContextPos(); @@ -111,19 +112,24 @@ class SpecialForms { var catchKissExps = args.slice(1); ETry(convert(tryKissExp), [ for (catchKissExp in catchKissExps) { - switch (catchKissExp) { - case CallExp(Symbol("catch"), catchArgs): + switch (catchKissExp.def) { + case CallExp({pos: _, def: Symbol("catch")}, catchArgs): { - name: switch (catchArgs[0]) { - case ListExp([Symbol(name) | TypedExp(_, Symbol(name))]): name; + name: switch (catchArgs[0].def) { + case ListExp([ + { + pos: _, + def: Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}) + } + ]): name; default: throw 'first argument to (catch... ) should be a one-element argument list, not ${catchArgs[0]}'; }, - type: switch (catchArgs[0]) { - case ListExp([TypedExp(type, _)]): + type: switch (catchArgs[0].def) { + case ListExp([{pos: _, def: TypedExp(type, _)}]): Helpers.parseComplexType(type); default: null; }, - expr: convert(CallExp(Symbol("begin"), catchArgs.slice(1))) + expr: convert(CallExp(Symbol("begin").withPos(catchArgs[1].pos), catchArgs.slice(1)).withPos(catchArgs[1].pos)) }; default: throw 'expressions following the first expression in a (try... ) should all be (catch... ) expressions, but you used $catchKissExp'; @@ -165,7 +171,7 @@ class SpecialForms { static function foldComparison(func:String) { return (args:Array, convert:ExprConversion) -> { pos: Context.currentPos(), - expr: EBinop(OpEq, convert(args[0]), convert(CallExp(Symbol(func), args))) + expr: EBinop(OpEq, convert(args[0]), convert(CallExp(Symbol(func).withPos(args[0].pos), args).withPos(args[0].pos))) }; } } diff --git a/src/kiss/Stream.hx b/src/kiss/Stream.hx index 76410bbb..9b9c5e29 100644 --- a/src/kiss/Stream.hx +++ b/src/kiss/Stream.hx @@ -6,6 +6,8 @@ import haxe.ds.Option; using StringTools; using Lambda; +typedef Position = String; + class Stream { var content:String; var file:String; @@ -34,7 +36,7 @@ class Stream { return content.length == 0; } - public function position() { + public function position():Position { return '$file:$line:$column'; }