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.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<ReaderExp>, convert:ExprConversion) -> Field;
typedef FieldFormFunction = (pos:Position, args:Array<ReaderExp>, 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<ReaderExp>, convert:ExprConversion):Field {
static function varOrProperty(formName:String, position:Position, args:Array<ReaderExp>, 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<ReaderExp>, convert:ExprConversion):Field {
static function funcOrMethod(formName:String, position:Position, args:Array<ReaderExp>, 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()

View File

@@ -62,17 +62,17 @@ class Kiss {
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;
// 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):
{

View File

@@ -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<ReaderExp>, 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<ReaderExp>, 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<ReaderExp>, 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);
};
}
}

View File

@@ -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<ReaderExp>); // (f a1 a2...)
ListExp(exps:Array<ReaderExp>); // [v1 v2 v3]
StrExp(s:String); // "literal"
@@ -12,7 +19,7 @@ enum ReaderExp {
TypedExp(path:String, exp:ReaderExp); // :type [exp]
}
typedef ReadFunction = (Stream) -> Null<ReaderExp>;
typedef ReadFunction = (Stream) -> Null<ReaderExpDef>;
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
};
}
}

View File

@@ -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<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):
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<ReaderExp>, 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)))
};
}
}

View File

@@ -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';
}