Add positions to ReaderExp

This commit is contained in:
2020-11-24 17:16:18 -07:00
parent 8019c8893d
commit 000bd555db
6 changed files with 86 additions and 52 deletions

View File

@@ -5,11 +5,13 @@ import haxe.macro.Context;
import kiss.Reader; import kiss.Reader;
import kiss.Types; import kiss.Types;
import kiss.Helpers; import kiss.Helpers;
import kiss.Stream;
using kiss.Reader;
using StringTools; using StringTools;
// Field forms convert Kiss reader expressions into Haxe macro class fields // Field forms convert Kiss reader expressions into Haxe macro class fields
typedef FieldFormFunction = (position:String, args:Array<ReaderExp>, convert:ExprConversion) -> Field; typedef FieldFormFunction = (pos:Position, args:Array<ReaderExp>, convert:ExprConversion) -> Field;
class FieldForms { class FieldForms {
public static function builtins() { public static function builtins() {
@@ -33,27 +35,27 @@ class FieldForms {
return access; return access;
} }
static function fieldName(formName:String, position:String, nameExp:ReaderExp) { static function fieldName(formName:String, nameExp:ReaderExp) {
return switch (nameExp) { return switch (nameExp.def) {
case Symbol(name) | TypedExp(_, Symbol(name)): case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}):
name; name;
default: 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<ReaderExp>, convert:ExprConversion):Field { static function varOrProperty(formName:String, position:Position, args:Array<ReaderExp>, convert:ExprConversion):Field {
if (args.length != 2) { if (args.length != 2) {
throw '$formName with $args at $position is not a valid field definition'; 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); var access = fieldAccess(formName, name);
return { return {
name: name, name: name,
access: access, access: access,
kind: FVar(switch (args[0]) { kind: FVar(switch (args[0].def) {
case TypedExp(type, _): case TypedExp(type, _):
Helpers.parseComplexType(type); Helpers.parseComplexType(type);
default: null; default: null;
@@ -63,12 +65,12 @@ class FieldForms {
} }
// TODO &rest, &body and &optional arguments // TODO &rest, &body and &optional arguments
static function funcOrMethod(formName:String, position:String, args:Array<ReaderExp>, convert:ExprConversion):Field { static function funcOrMethod(formName:String, position:Position, args:Array<ReaderExp>, convert:ExprConversion):Field {
if (args.length <= 2) { if (args.length <= 2) {
throw '$formName with $args is not a valid function/method definition'; 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); var access = fieldAccess(formName, name);
return { return {
@@ -76,19 +78,19 @@ class FieldForms {
access: access, access: access,
// TODO type parameter declarations // TODO type parameter declarations
kind: FFun({ kind: FFun({
args: switch (args[1]) { args: switch (args[1].def) {
case ListExp(funcArgs): case ListExp(funcArgs):
[ [
// TODO optional arguments, default values // TODO optional arguments, default values
for (funcArg in funcArgs) for (funcArg in funcArgs)
{ {
name: switch (funcArg) { name: switch (funcArg.def) {
case Symbol(name) | TypedExp(_, Symbol(name)): case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}):
name; name;
default: default:
throw '$funcArg should be a symbol or typed symbol for a function argument'; throw '$funcArg should be a symbol or typed symbol for a function argument';
}, },
type: switch (funcArg) { type: switch (funcArg.def) {
case TypedExp(type, _): case TypedExp(type, _):
Helpers.parseComplexType(type); Helpers.parseComplexType(type);
default: null; default: null;
@@ -100,13 +102,13 @@ class FieldForms {
default: default:
throw '${args[1]} should be an argument list'; throw '${args[1]} should be an argument list';
}, },
ret: switch (args[0]) { ret: switch (args[0].def) {
case TypedExp(type, _): Helpers.parseComplexType(type); case TypedExp(type, _): Helpers.parseComplexType(type);
default: null; default: null;
}, },
expr: { expr: {
pos: Context.currentPos(), 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() pos: Context.currentPos()

View File

@@ -62,17 +62,17 @@ class Kiss {
return classFields; return classFields;
} }
static function readerExpToField(exp:ReaderExp, position:String, k:KissState):Null<Field> { static function readerExpToField(exp:ReaderExp, position:Position, k:KissState):Null<Field> {
var fieldForms = k.fieldForms; var fieldForms = k.fieldForms;
// Macros at top-level are allowed if they expand into a fieldform, or null like defreadermacro // Macros at top-level are allowed if they expand into a fieldform, or null like defreadermacro
var macros = k.macros; var macros = k.macros;
return switch (exp) { return switch (exp.def) {
case CallExp(Symbol(mac), args) if (macros.exists(mac)): case CallExp({pos: _, def: Symbol(mac)}, args) if (macros.exists(mac)):
var expandedExp = macros[mac](args, k); var expandedExp = macros[mac](args, k);
if (expandedExp != null) readerExpToField(macros[mac](args, k), position, k) else null; 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)); fieldForms[formName](position, args, readerExpToHaxeExpr.bind(_, k));
default: default:
throw '$exp at $position is not a valid field form'; throw '$exp at $position is not a valid field form';
@@ -84,7 +84,7 @@ class Kiss {
var specialForms = k.specialForms; var specialForms = k.specialForms;
// Bind the table arguments of this function for easy recursive calling/passing // Bind the table arguments of this function for easy recursive calling/passing
var convert = readerExpToHaxeExpr.bind(_, k); var convert = readerExpToHaxeExpr.bind(_, k);
var expr = switch (exp) { var expr = switch (exp.def) {
case Symbol(name): case Symbol(name):
Context.parse(name, Context.currentPos()); Context.parse(name, Context.currentPos());
case StrExp(s): case StrExp(s):
@@ -92,9 +92,9 @@ class Kiss {
pos: Context.currentPos(), pos: Context.currentPos(),
expr: EConst(CString(s)) 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)); 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); specialForms[specialForm](args, convert);
case CallExp(func, body): case CallExp(func, body):
{ {

View File

@@ -7,6 +7,7 @@ import hscript.Interp;
import kiss.Reader; import kiss.Reader;
import kiss.Kiss; import kiss.Kiss;
using kiss.Reader;
using kiss.Helpers; using kiss.Helpers;
// Macros generate new Kiss reader expressions from the arguments of their call expression. // Macros generate new Kiss reader expressions from the arguments of their call expression.
@@ -28,14 +29,14 @@ class Macros {
if (exps.length != 2) { if (exps.length != 2) {
throw 'Got ${exps.length} arguments for % instead of 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<ReaderExp>, k) -> { macros["^"] = (exps:Array<ReaderExp>, k) -> {
if (exps.length != 2) { if (exps.length != 2) {
throw 'Got ${exps.length} arguments for ^ instead of 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"); macros["min"] = foldMacro("Prelude.minInclusive");
@@ -54,7 +55,7 @@ class Macros {
macros["defmacrofun"] = (exps:Array<ReaderExp>, k:KissState) -> { macros["defmacrofun"] = (exps:Array<ReaderExp>, k:KissState) -> {
if (exps.length < 3) if (exps.length < 3)
throw '${exps.length} is not enough arguments for (defmacrofun [name] [args] [body])'; 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; case Symbol(name): name;
default: throw 'first argument ${exps[0]} for defmacrofun should be a symbol for the macro name'; default: throw 'first argument ${exps[0]} for defmacrofun should be a symbol for the macro name';
}; };
@@ -69,7 +70,7 @@ class Macros {
]).withContextPos(); ]).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|# // For now, reader macros only support a one-expression body implemented in #|raw haxe|#
@@ -77,16 +78,16 @@ class Macros {
if (exps.length != 3) { if (exps.length != 3) {
throw 'wrong number of expressions for defreadermacro: $exps should be String, [streamArgName], RawHaxe'; throw 'wrong number of expressions for defreadermacro: $exps should be String, [streamArgName], RawHaxe';
} }
switch (exps[0]) { switch (exps[0].def) {
case StrExp(s): case StrExp(s):
switch (exps[1]) { switch (exps[1].def) {
case ListExp([Symbol(streamArgName)]): case ListExp([{pos: _, def: Symbol(streamArgName)}]):
switch (exps[2]) { switch (exps[2].def) {
case RawHaxe(code): case RawHaxe(code):
k.readTable[s] = (stream) -> { k.readTable[s] = (stream) -> {
var parser = new Parser(); var parser = new Parser();
var interp = new Interp(); var interp = new Interp();
interp.variables.set("ReaderExp", ReaderExp); interp.variables.set("ReaderExp", ReaderExpDef);
interp.variables.set(streamArgName, stream); interp.variables.set(streamArgName, stream);
interp.execute(parser.parseString(code)); interp.execute(parser.parseString(code));
}; };
@@ -107,8 +108,12 @@ class Macros {
} }
static function foldMacro(func:String):MacroFunction { static function foldMacro(func:String):MacroFunction {
return (exps, k) -> { return (exps:Array<ReaderExp>, k) -> {
CallExp(Symbol("Lambda.fold"), [ListExp(exps.slice(1)), Symbol(func), exps[0]]); 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);
}; };
} }
} }

View File

@@ -3,7 +3,14 @@ package kiss;
import haxe.ds.Option; import haxe.ds.Option;
import kiss.Stream; import kiss.Stream;
enum ReaderExp { using kiss.Reader;
typedef ReaderExp = {
pos:Position,
def:ReaderExpDef
};
enum ReaderExpDef {
CallExp(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...) CallExp(func:ReaderExp, args:Array<ReaderExp>); // (f a1 a2...)
ListExp(exps:Array<ReaderExp>); // [v1 v2 v3] ListExp(exps:Array<ReaderExp>); // [v1 v2 v3]
StrExp(s:String); // "literal" StrExp(s:String); // "literal"
@@ -12,7 +19,7 @@ enum ReaderExp {
TypedExp(path:String, exp:ReaderExp); // :type [exp] TypedExp(path:String, exp:ReaderExp); // :type [exp]
} }
typedef ReadFunction = (Stream) -> Null<ReaderExp>; typedef ReadFunction = (Stream) -> Null<ReaderExpDef>;
class Reader { class Reader {
// The built-in readtable // The built-in readtable
@@ -64,6 +71,7 @@ class Reader {
if (stream.isEmpty()) if (stream.isEmpty())
return None; return None;
var position = stream.position();
var readTableKeys = [for (key in readTable.keys()) key]; var readTableKeys = [for (key in readTable.keys()) key];
readTableKeys.sort((a, b) -> b.length - a.length); readTableKeys.sort((a, b) -> b.length - a.length);
@@ -72,7 +80,11 @@ class Reader {
case Some(k) if (k == key): case Some(k) if (k == key):
stream.dropString(key); stream.dropString(key);
var expOrNull = readTable[key](stream); 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: default:
} }
} }
@@ -90,4 +102,11 @@ class Reader {
stream.dropString(end); stream.dropString(end);
return array; return array;
} }
public static function withPos(def:ReaderExpDef, pos:Position) {
return {
pos: pos,
def: def
};
}
} }

View File

@@ -5,6 +5,7 @@ import haxe.macro.Context;
import kiss.Reader; import kiss.Reader;
import kiss.Types; import kiss.Types;
using kiss.Reader;
using kiss.Helpers; using kiss.Helpers;
using kiss.Prelude; using kiss.Prelude;
@@ -33,7 +34,7 @@ class SpecialForms {
if (args.length < 1) { if (args.length < 1) {
throw '(new [type] constructorArgs...) is missing a type!'; throw '(new [type] constructorArgs...) is missing a type!';
} }
var classType = switch (args[0]) { var classType = switch (args[0].def) {
case Symbol(name): name; case Symbol(name): name;
default: throw 'first arg in (new [type] ...) should be a class to instantiate'; 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 // TODO refactor out EVar generation and allow var bindings to destructure lists and key-value pairs
map["let"] = (args:Array<ReaderExp>, convert:ExprConversion) -> { map["let"] = (args:Array<ReaderExp>, convert:ExprConversion) -> {
var bindingList = switch (args[0]) { var bindingList = switch (args[0].def) {
case ListExp(bindingExps) if (bindingExps.length > 0 && bindingExps.length % 2 == 0): case ListExp(bindingExps) if (bindingExps.length > 0 && bindingExps.length % 2 == 0):
bindingExps; bindingExps;
default: default:
@@ -56,13 +57,13 @@ class SpecialForms {
var varDefs = [ var varDefs = [
for (bindingPair in bindingPairs) for (bindingPair in bindingPairs)
{ {
name: switch (bindingPair[0]) { name: switch (bindingPair[0].def) {
case Symbol(name) | TypedExp(_, Symbol(name)): case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}):
name; name;
default: default:
throw 'first element of binding pair $bindingPair should be a symbol or typed symbol'; 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, _): case TypedExp(type, _):
Helpers.parseComplexType(type); Helpers.parseComplexType(type);
default: null; default: null;
@@ -97,7 +98,7 @@ class SpecialForms {
if (args.length != 2) { if (args.length != 2) {
throw '(the [type] [value]) expression has wrong number of arguments: ${args.length}'; 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); case Symbol(type): Helpers.parseComplexType(type);
default: throw 'first argument to (the... ) should be a valid type'; default: throw 'first argument to (the... ) should be a valid type';
}).withContextPos(); }).withContextPos();
@@ -111,19 +112,24 @@ class SpecialForms {
var catchKissExps = args.slice(1); var catchKissExps = args.slice(1);
ETry(convert(tryKissExp), [ ETry(convert(tryKissExp), [
for (catchKissExp in catchKissExps) { for (catchKissExp in catchKissExps) {
switch (catchKissExp) { switch (catchKissExp.def) {
case CallExp(Symbol("catch"), catchArgs): case CallExp({pos: _, def: Symbol("catch")}, catchArgs):
{ {
name: switch (catchArgs[0]) { name: switch (catchArgs[0].def) {
case ListExp([Symbol(name) | TypedExp(_, Symbol(name))]): name; 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]}'; default: throw 'first argument to (catch... ) should be a one-element argument list, not ${catchArgs[0]}';
}, },
type: switch (catchArgs[0]) { type: switch (catchArgs[0].def) {
case ListExp([TypedExp(type, _)]): case ListExp([{pos: _, def: TypedExp(type, _)}]):
Helpers.parseComplexType(type); Helpers.parseComplexType(type);
default: null; 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: default:
throw 'expressions following the first expression in a (try... ) should all be (catch... ) expressions, but you used $catchKissExp'; 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) { static function foldComparison(func:String) {
return (args:Array<ReaderExp>, convert:ExprConversion) -> { return (args:Array<ReaderExp>, convert:ExprConversion) -> {
pos: Context.currentPos(), 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)))
}; };
} }
} }

View File

@@ -6,6 +6,8 @@ import haxe.ds.Option;
using StringTools; using StringTools;
using Lambda; using Lambda;
typedef Position = String;
class Stream { class Stream {
var content:String; var content:String;
var file:String; var file:String;
@@ -34,7 +36,7 @@ class Stream {
return content.length == 0; return content.length == 0;
} }
public function position() { public function position():Position {
return '$file:$line:$column'; return '$file:$line:$column';
} }