Link generated Exprs with their source .kiss positions
This commit is contained in:
@@ -3,6 +3,7 @@ package kiss;
|
||||
import kiss.Reader;
|
||||
import kiss.List;
|
||||
|
||||
using kiss.Stream;
|
||||
using kiss.Reader;
|
||||
|
||||
class CompileError {
|
||||
@@ -25,10 +26,12 @@ class CompileError {
|
||||
public function toString() {
|
||||
var posPrefix = switch (exps.length) {
|
||||
case 1:
|
||||
exps[0].pos;
|
||||
exps[0].pos.toPrint();
|
||||
default:
|
||||
var justLineAndColumnIdx = exps[-1].pos.indexOf(":") + 1;
|
||||
exps[0].pos + '-' + exps[-1].pos.substr(justLineAndColumnIdx);
|
||||
var firstPos = exps[0].pos.toPrint();
|
||||
var lastPos = exps[-1].pos.toPrint();
|
||||
var justLineAndColumnIdx = lastPos.indexOf(":") + 1;
|
||||
firstPos + '-' + lastPos.substr(justLineAndColumnIdx);
|
||||
}
|
||||
|
||||
return '\nKiss compilation failed!\n'
|
||||
|
||||
@@ -75,7 +75,7 @@ class FieldForms {
|
||||
Helpers.parseComplexType(type, args[0]);
|
||||
default: null;
|
||||
}, k.convert(args[1])),
|
||||
pos: Context.currentPos()
|
||||
pos: wholeExp.macroPos()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class FieldForms {
|
||||
name: name,
|
||||
access: access,
|
||||
kind: FFun(Helpers.makeFunction(args[0], args[1], args.slice(2), k)),
|
||||
pos: Context.currentPos()
|
||||
pos: wholeExp.macroPos()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package kiss;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.PositionTools;
|
||||
import kiss.Reader;
|
||||
import kiss.CompileError;
|
||||
import kiss.Kiss;
|
||||
@@ -12,9 +13,18 @@ using kiss.Helpers;
|
||||
using StringTools;
|
||||
|
||||
class Helpers {
|
||||
public static function withContextPos(e:ExprDef):Expr {
|
||||
public static function macroPos(exp:ReaderExp) {
|
||||
var kissPos = exp.pos;
|
||||
return PositionTools.make({
|
||||
min: kissPos.absoluteChar,
|
||||
max: kissPos.absoluteChar,
|
||||
file: kissPos.file
|
||||
});
|
||||
}
|
||||
|
||||
public static function withMacroPosOf(e:ExprDef, exp:ReaderExp):Expr {
|
||||
return {
|
||||
pos: Context.currentPos(),
|
||||
pos: macroPos(exp),
|
||||
expr: e
|
||||
};
|
||||
}
|
||||
@@ -67,7 +77,7 @@ class Helpers {
|
||||
}
|
||||
|
||||
// TODO generic type parameter declarations
|
||||
public static function makeFunction(?name:ReaderExp, argList:ReaderExp, body:Array<ReaderExp>, k:KissState):Function {
|
||||
public static function makeFunction(?name:ReaderExp, argList:ReaderExp, body:List<ReaderExp>, k:KissState):Function {
|
||||
var funcName = if (name != null) {
|
||||
switch (name.def) {
|
||||
case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}):
|
||||
@@ -102,7 +112,7 @@ class Helpers {
|
||||
var realCallArgs = args.slice(0, numArgs);
|
||||
var restArgs = args.slice(numArgs);
|
||||
realCallArgs.push(ListExp(restArgs).withPosOf(wholeExp));
|
||||
ECall(k.convert(Symbol(funcName).withPosOf(wholeExp)), realCallArgs.map(k.convert)).withContextPos();
|
||||
ECall(k.convert(Symbol(funcName).withPosOf(wholeExp)), realCallArgs.map(k.convert)).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
opt = true;
|
||||
@@ -148,7 +158,7 @@ class Helpers {
|
||||
default:
|
||||
throw CompileError.fromExp(argList, 'expected an argument list');
|
||||
},
|
||||
expr: EReturn(k.convert(CallExp(Symbol("begin").withPos(body[0].pos), body).withPos(body[0].pos))).withContextPos()
|
||||
expr: EReturn(k.convert(CallExp(Symbol("begin").withPos(body[0].pos), body).withPos(body[0].pos))).withMacroPosOf(body[-1])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,32 +107,30 @@ class Kiss {
|
||||
var convert = readerExpToHaxeExpr.bind(_, k);
|
||||
var expr = switch (exp.def) {
|
||||
case Symbol(name):
|
||||
Context.parse(name, Context.currentPos());
|
||||
Context.parse(name, exp.macroPos());
|
||||
case StrExp(s):
|
||||
{
|
||||
pos: Context.currentPos(),
|
||||
expr: EConst(CString(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)]).withContextPos();
|
||||
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).withContextPos();
|
||||
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)]).withContextPos()]).withContextPos();
|
||||
}, [
|
||||
EArrayDecl([for (elementExp in elements) convert(elementExp)]).withMacroPosOf(exp)
|
||||
]).withMacroPosOf(exp);
|
||||
case RawHaxe(code):
|
||||
Context.parse(code, Context.currentPos());
|
||||
Context.parse(code, exp.macroPos());
|
||||
default:
|
||||
throw CompileError.fromExp(exp, 'conversion not implemented');
|
||||
};
|
||||
|
||||
@@ -114,14 +114,14 @@ class Macros {
|
||||
k.specialForms[macroName] = (wholeExp:ReaderExp, callArgs:Array<ReaderExp>, convert) -> {
|
||||
// Macro functions don't need to check their argument numbers
|
||||
// because macro function calls expand to function calls that the Haxe compiler will check
|
||||
ECall(Context.parse('${k.className}.${macroName}', Context.currentPos()), [
|
||||
ECall(Context.parse('${k.className}.${macroName}', wholeExp.macroPos()), [
|
||||
for (callArg in callArgs)
|
||||
EFunction(FArrow, {
|
||||
args: [],
|
||||
ret: null,
|
||||
expr: EReturn(k.convert(callArg)).withContextPos()
|
||||
}).withContextPos()
|
||||
]).withContextPos();
|
||||
expr: EReturn(k.convert(callArg)).withMacroPosOf(wholeExp)
|
||||
}).withMacroPosOf(wholeExp)
|
||||
]).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
CallExp(Symbol("defun").withPosOf(wholeExp), exps).withPosOf(wholeExp);
|
||||
|
||||
@@ -22,18 +22,18 @@ class SpecialForms {
|
||||
map["begin"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
// Sometimes empty blocks are useful, so a checkNumArgs() seems unnecessary here for now.
|
||||
|
||||
EBlock([for (bodyExp in args) k.convert(bodyExp)]).withContextPos();
|
||||
EBlock([for (bodyExp in args) k.convert(bodyExp)]).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["nth"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(2, 2, "(nth [list] [idx])");
|
||||
EArray(k.convert(args[0]), k.convert(args[1])).withContextPos();
|
||||
EArray(k.convert(args[0]), k.convert(args[1])).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
function makeQuickNth(idx:Int, name:String) {
|
||||
map[name] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(1, 1, '($name [list])');
|
||||
EArray(k.convert(args[0]), macro $v{idx}).withContextPos();
|
||||
EArray(k.convert(args[0]), macro $v{idx}).withMacroPosOf(wholeExp);
|
||||
};
|
||||
}
|
||||
makeQuickNth(0, "first");
|
||||
@@ -58,12 +58,12 @@ class SpecialForms {
|
||||
case Symbol(name): name;
|
||||
default: throw CompileError.fromExp(args[0], 'first arg in (new [type] ...) should be a class to instantiate');
|
||||
};
|
||||
ENew(Helpers.parseTypePath(classType, args[0]), args.slice(1).map(k.convert)).withContextPos();
|
||||
ENew(Helpers.parseTypePath(classType, args[0]), args.slice(1).map(k.convert)).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["set"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(2, 2, "(set [variable] [value])");
|
||||
EBinop(OpAssign, k.convert(args[0]), k.convert(args[1])).withContextPos();
|
||||
EBinop(OpAssign, k.convert(args[0]), k.convert(args[1])).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
function toVar(nameExp:ReaderExp, valueExp:ReaderExp, k:KissState, ?isFinal:Bool):Var {
|
||||
@@ -120,7 +120,7 @@ class SpecialForms {
|
||||
|
||||
map["deflocal"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(2, 3, "(deflocal [optional :type] [variable] [optional: &mut] [value])");
|
||||
EVars(toVars(args[0], args[1], k)).withContextPos();
|
||||
EVars(toVars(args[0], args[1], k)).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["let"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
@@ -142,12 +142,15 @@ class SpecialForms {
|
||||
throw CompileError.fromArgs(args, '(let....) expression needs a body');
|
||||
}
|
||||
|
||||
EBlock([EVars(varDefs).withContextPos(), EBlock(body.map(k.convert)).withContextPos()]).withContextPos();
|
||||
EBlock([
|
||||
EVars(varDefs).withMacroPosOf(wholeExp),
|
||||
EBlock(body.map(k.convert)).withMacroPosOf(wholeExp)
|
||||
]).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["lambda"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
wholeExp.checkNumArgs(2, null, "(lambda [[argsNames...]] [body...])");
|
||||
EFunction(FArrow, Helpers.makeFunction(null, args[0], args.slice(1), k)).withContextPos();
|
||||
EFunction(FArrow, Helpers.makeFunction(null, args[0], args.slice(1), k)).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
function forExpr(formName:String, wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) {
|
||||
@@ -158,12 +161,13 @@ class SpecialForms {
|
||||
var bodyExps = args.slice(2);
|
||||
bodyExps.insert(0, CallExp(Symbol("deflocal").withPosOf(args[2]), [namesExp, Symbol(uniqueVarName).withPosOf(args[2])]).withPosOf(args[2]));
|
||||
var body = CallExp(Symbol("begin").withPosOf(args[2]), bodyExps).withPosOf(args[2]);
|
||||
return EFor(EBinop(OpIn, EConst(CIdent(uniqueVarName)).withContextPos(), k.convert(listExp)).withContextPos(), k.convert(body)).withContextPos();
|
||||
return EFor(EBinop(OpIn, EConst(CIdent(uniqueVarName)).withMacroPosOf(wholeExp), k.convert(listExp)).withMacroPosOf(wholeExp),
|
||||
k.convert(body)).withMacroPosOf(wholeExp);
|
||||
}
|
||||
|
||||
map["doFor"] = forExpr.bind("doFor");
|
||||
map["for"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
EArrayDecl([forExpr("for", wholeExp, args, k)]).withContextPos();
|
||||
EArrayDecl([forExpr("for", wholeExp, args, k)]).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
// TODO (case... ) for switch
|
||||
@@ -174,7 +178,7 @@ class SpecialForms {
|
||||
ECheckType(k.convert(args[1]), switch (args[0].def) {
|
||||
case Symbol(type): Helpers.parseComplexType(type, args[0]);
|
||||
default: throw CompileError.fromExp(args[0], 'first argument to (the... ) should be a valid type');
|
||||
}).withContextPos();
|
||||
}).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["try"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
@@ -207,14 +211,14 @@ class SpecialForms {
|
||||
'expressions following the first expression in a (try... ) should all be (catch [[error]] [body...]) expressions');
|
||||
}
|
||||
}
|
||||
]).withContextPos();
|
||||
]).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["throw"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
if (args.length != 1) {
|
||||
throw CompileError.fromExp(wholeExp, 'throw expression should only throw one value');
|
||||
}
|
||||
EThrow(k.convert(args[0])).withContextPos();
|
||||
EThrow(k.convert(args[0])).withMacroPosOf(wholeExp);
|
||||
};
|
||||
|
||||
map["<"] = foldComparison("_min");
|
||||
@@ -257,7 +261,7 @@ class SpecialForms {
|
||||
return (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
var callFoldMacroExpr = k.convert(CallExp(Symbol(func).withPosOf(wholeExp), args).withPosOf(wholeExp));
|
||||
wholeExp.checkNumArgs(1, null);
|
||||
EBinop(OpEq, k.convert(args[0]), macro ${callFoldMacroExpr}.toDynamic()).withContextPos();
|
||||
EBinop(OpEq, k.convert(args[0]), macro ${callFoldMacroExpr}.toDynamic()).withMacroPosOf(wholeExp);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,19 @@ import haxe.ds.Option;
|
||||
using StringTools;
|
||||
using Lambda;
|
||||
|
||||
typedef Position = String;
|
||||
typedef Position = {
|
||||
file:String,
|
||||
line:Int,
|
||||
column:Int,
|
||||
absoluteChar:Int
|
||||
};
|
||||
|
||||
class Stream {
|
||||
var content:String;
|
||||
var file:String;
|
||||
var line:Int;
|
||||
var column:Int;
|
||||
var absoluteChar:Int;
|
||||
|
||||
public function new(file:String) {
|
||||
// Banish ye Windows line-endings
|
||||
@@ -24,6 +30,7 @@ class Stream {
|
||||
this.file = file;
|
||||
line = 1;
|
||||
column = 1;
|
||||
absoluteChar = 0;
|
||||
}
|
||||
|
||||
public function peekChars(chars:Int):Option<String> {
|
||||
@@ -37,7 +44,16 @@ class Stream {
|
||||
}
|
||||
|
||||
public function position():Position {
|
||||
return '$file:$line:$column';
|
||||
return {
|
||||
file: file,
|
||||
line: line,
|
||||
column: column,
|
||||
absoluteChar: absoluteChar
|
||||
};
|
||||
}
|
||||
|
||||
public static function toPrint(p:Position) {
|
||||
return '${p.file}:${p.line}:${p.column}';
|
||||
}
|
||||
|
||||
public function startsWith(s:String) {
|
||||
@@ -50,6 +66,7 @@ class Stream {
|
||||
/** Every drop call should end up calling dropChars() or the position tracker will be wrong. **/
|
||||
private function dropChars(count:Int) {
|
||||
for (idx in 0...count) {
|
||||
absoluteChar += 1;
|
||||
switch (content.charAt(idx)) {
|
||||
case "\n":
|
||||
line += 1;
|
||||
|
||||
Reference in New Issue
Block a user