Overall cleanup.

This commit is contained in:
back2dos
2013-06-26 21:59:22 +02:00
parent fb2115aca2
commit 3a92c22790
15 changed files with 1459 additions and 4 deletions

View File

@@ -1,4 +1,4 @@
tink_macro
==========
The macro toolkit
tink_macro
==========
The macro toolkit

3
Run.bat Normal file
View File

@@ -0,0 +1,3 @@
cd bin
neko tests.n
pause

1
bin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.n

14
haxelib.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "tink_macro",
"url" : "http://haxetink.org/tink_macro",
"license": "MIT",
"tags": ["tink", "macro", "utility"],
"description": "The macro toolkit ;)",
"version": "0.0.0-alpha.2",
"releasenote": "Shorter names.",
"contributors": ["back2dos"],
"dependencies": {
"tink_core": "1.1.0-alpha.2"
},
"classPath": "src"
}

629
src/tink/macro/Exprs.hx Normal file
View File

@@ -0,0 +1,629 @@
package tink.macro;
import Type in Inspect;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.PosInfos;
import haxe.macro.Printer;
using Lambda;
using StringTools;
using tink.macro.Positions;
using tink.macro.Exprs;
using tink.macro.Types;
using tink.core.Outcome;
typedef VarDecl = { name : String, type : ComplexType, expr : Null<Expr> };
typedef ParamSubst = {
var exists(default, null):String->Bool;
var get(default, null):String->ComplexType;
}
class Exprs {
static public inline function is(e:Expr, c:ComplexType) {
return ECheckType(e, c).at(e.pos).typeof().isSuccess();
}
static var annotCounter = 0;
static var annotations = new Map<Int,Dynamic>();
static public function tag<D>(e:Expr, data:D) {
annotations.set(annotCounter, data);
return [(annotCounter++).toExpr(e.pos), e].toBlock(e.pos);
}
static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dynamic<String>, ?skipFields = false, ?callPos:PosInfos) {
if (nuPos == null)
nuPos = Context.currentPos();
if (rules == null)
rules = { };
function replace(s:String)
return {
if (Reflect.hasField(rules, s))
Reflect.field(rules, s)
else if (s.startsWith('tmp')) {
// Reflect.setField(rules, s, MacroTools.tempName(String, '__tink' + s.substr(3)));
replace(s);
}
else s;
}
return e.transform(function (e:Expr) {
return
if (Context.getPosInfos(e.pos).file != callPos.fileName) e;
else {
e.pos = nuPos;
switch (e.expr) {
case EVars(vars):
for (v in vars)
v.name = replace(v.name);
e;
case EField(owner, field):
if (skipFields) e;
else owner.field(replace(field), e.pos);
case EFunction(_, f):
for (a in f.args)
a.name = replace(a.name);
e;
case EObjectDecl(fields):
if (!skipFields)
for (f in fields)
f.field = replace(f.field);
e;
default:
switch (e.getIdent()) {
case Success(s): replace(s).resolve(e.pos);
default: e;
}
}
}
});
}
static public function untag<D>(e:Expr):{data:D, e:Expr } {
return
switch (e.expr) {
case EBlock(exprs): { e: exprs[1], data: annotations.get(exprs[0].getInt().sure()) };
default: e.reject();
}
}
static public function withPrivateAccess(e:Expr) {
return
e.transform(function (e:Expr)
return
switch (e.expr) {
case EField(owner, field):
getPrivate(owner, field, e.pos);
default: e;
}
);
}
static public function getPrivate(e:Expr, field:String, ?pos) {
return EMeta( { name: ':privateAccess', params: [], pos: pos }, e.field(field, pos)).at(pos);
}
static public function partial<D>(c:ComplexType, data:D, ?pos)
return ECheckType(macro null, c).at(pos).tag(data);
static public function substitute(source:Expr, vars:Dynamic<Expr>, ?pos)
return
transform(source, function (e:Expr) {
return
switch (e.getIdent()) {
case Success(name):
if (Reflect.hasField(vars, name))
Reflect.field(vars, name);
else
e;
default: e;
}
}, pos);
static public inline function ifNull(e:Expr, fallback:Expr)
return
if (e.getIdent().equals('null')) fallback;
else e;
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr
return crawl(
source,
function (e) return e,
function (c:ComplexType)
return
switch (c) {
case TPath(p):
if (p.pack.length == 0 && subst.exists(p.name))
subst.get(p.name);
else c;
default: c;
}
, pos);
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr
return crawl(source, transformer, function (t) return t, pos);
static function crawlArray(a:Array<Dynamic>, transformer:Expr->Expr, retyper:ComplexType-> ComplexType, pos:Position):Array<Dynamic> {
if (a == null) return a;
var ret = [];
for (v in a)
ret.push(crawl(v, transformer, retyper, pos));
return ret;
}
static public function getIterType(target:Expr)
return
(macro {
var t = null,
target = $target;
for (i in target)
t = i;
t;
}).finalize(target.pos).typeof();
static public function yield(e:Expr, yielder:Expr->Expr):Expr {
inline function rec(e)
return yield(e, yielder);
return
if (e == null) null;
else if (e.expr == null) e;
else switch (e.expr) {
case EVars(_):
(macro @:pos(e.pos) var x = { var x = 5; } ).typeof().sure();
throw 'unreachable';//the above should cause an error
case EParenthesis(e):
EParenthesis(rec(e)).at(e.pos);
case EBlock(exprs) if (exprs.length > 0):
exprs = exprs.copy();
exprs.push(rec(exprs.pop()));
EBlock(exprs).at(e.pos);
case EIf(econd, eif, eelse)
,ETernary(econd, eif, eelse):
EIf(econd, rec(eif), rec(eelse)).at(e.pos);
case ESwitch(e, cases, edef):
cases = Reflect.copy(cases);//not exactly pretty, but does the job
for (c in cases)
c.expr = rec(c.expr);
ESwitch(e, cases, rec(edef)).at(e.pos);
case EFor(it, expr):
EFor(it, rec(expr)).at(e.pos);
case EWhile(cond, body, normal):
EWhile(cond, rec(body), normal).at(e.pos);
case ECheckType(e, t):
ECheckType(rec(e), t).at(e.pos);
case EMeta(s, e):
EMeta(s, rec(e)).at(e.pos);
case EUntyped(e):
EUntyped(rec(e)).at(e.pos);
case EBreak, EContinue: e;
case EBinop(OpArrow, value, jump) if (jump.expr == EContinue || jump.expr == EBreak):
macro @:pos(e.pos) {
${rec(value)};
$jump;
}
default: yielder(e);
}
}
static function crawl(target:Dynamic, transformer:Expr->Expr, retyper:ComplexType->ComplexType, pos:Position):Dynamic {
return
if (Std.is(target, Array))
crawlArray(target, transformer, retyper, pos);
else
switch (Inspect.typeof(target)) {
case TNull, TInt, TFloat, TBool, TFunction, TUnknown, TClass(_): target;
case TEnum(e):
if (Inspect.getEnum(target) == ComplexType) return retyper(target);
else {
var ret:Dynamic = Inspect.createEnumIndex(e, Inspect.enumIndex(target), crawlArray(Inspect.enumParameters(target), transformer, retyper, pos));
return
if (Inspect.getEnum(ret) == ComplexType) retyper(ret);
else ret;
}
case TObject:
var ret:Dynamic = { };
for (field in Reflect.fields(target))
Reflect.setField(ret, field, crawl(Reflect.field(target, field), transformer, retyper, pos));
if (Std.is(ret.expr, ExprDef)) {
ret = transformer(ret);
if (pos != null) ret.pos = pos;
}
ret;
}
}
static public function map(source:Expr, f:Expr->Array<VarDecl>->Expr, ctx:Array<VarDecl>, ?pos:Position):Expr {
if (ctx == null) ctx = [];
function rec(e, ?inner)
return map(e, f, inner == null ? ctx : inner, pos);
if (source == null || source.expr == null) return source;
var mappedSource = f(source, ctx);
if (mappedSource != source) return mappedSource;
var ret = switch(mappedSource.expr) {
case ECheckType(e, t): ECheckType(rec(e), t);
case ECast(e, t): ECast(rec(e), t);
case EArray(e1, e2): EArray(rec(e1), rec(e2));
case EField(e, field): EField(rec(e), field);
case EParenthesis(e): EParenthesis(rec(e));
case ECall(e, params): ECall(rec(e), typedMapArray(params, f, ctx, pos));
case EIf(econd, eif, eelse): EIf(rec(econd), rec(eif), rec(eelse));
case ETernary(econd, eif, eelse): ETernary(rec(econd), rec(eif), rec(eelse));
case EBlock(exprs): EBlock(exprs.typedMapArray(f, ctx.copy(), pos));
case EArrayDecl(exprs): EArrayDecl(exprs.typedMapArray(f, ctx, pos));
case EIn(e1, e2): EIn(rec(e1), rec(e2));
case EWhile(econd, e, normalWhile): EWhile(rec(econd), rec(e), normalWhile);
case EUntyped(e): EUntyped(rec(e));
case EThrow(e): EThrow(rec(e));
case EReturn(e): EReturn(rec(e));
case EDisplay(e, t): EDisplay(rec(e), t);
case EDisplayNew(t): EDisplayNew(t);
case EUnop(op, postFix, e): EUnop(op, postFix, rec(e));
case ENew(t, params): ENew(t, params.typedMapArray(f, ctx, pos));
case EBinop(op, e1, e2): EBinop(op, rec(e1), rec(e2));
case EObjectDecl(fields):
var newFields = [];
for (field in fields)
newFields.push( { field:field.field, expr:rec(field.expr) } );
EObjectDecl(newFields);
case ESwitch(expr, cases, def):
var newCases = cases;
newCases = [];
expr = rec(expr);
switch (expr.typeof(ctx).sure().reduce()) {
case TEnum(e, _):
var enumDef = e.get();
for (c in cases) {
var caseValues = [],
innerCtx = ctx.copy();
for (v in c.values) {
var newVal = v;
switch (v.expr) {
case ECall(e, params):
switch (e.getIdent()) {
case Success(s):
if (!enumDef.constructs.exists(s))
e.reject('Constructor is not a part of ' + enumDef.name);
newVal = enumDef.module.split('.').concat([enumDef.name, s]).drill(e.pos);
if (caseValues.length == 0) {
switch (newVal.typeof(ctx).sure().reduce()) {
case TFun(args, _):
for (arg in 0...args.length) {
innerCtx.push({
name:params[arg].getName().sure(),
type: args[arg].t.toComplex(),
expr: null
});
}
default:
e.reject('Constructor may not have arguments');
}
}
newVal = newVal.call(params, v.pos);
default:
e.reject();
}
default:
v.reject();
}
caseValues.push(rec(newVal));
}
newCases.push( { expr: rec(c.expr, innerCtx), values: caseValues } );
}
case _:
for (c in cases) {
var caseValues = [];
for (v in c.values)
caseValues.push(rec(v));
newCases.push( { expr: rec(c.expr), values: caseValues } );
}
}
ESwitch(expr, newCases, rec(def));
case EFor(it, expr):
switch(it.expr) {
case EIn(itIdent, itExpr):
var innerCtx = ctx.copy();
switch(itExpr.typeof(ctx)) {
case Success(t):
if (t.getID() == "IntIter")
innerCtx.push( { name:itIdent.getIdent().sure(), type: "Int".asComplexType(), expr:null } );
else
innerCtx.push( { name:itIdent.getIdent().sure(), type: null, expr:itExpr.field("iterator").call().field("next").call() } );
EFor(it, rec(expr, innerCtx));
default:
innerCtx.push( { name:itIdent.getIdent().sure(), type: null, expr:itExpr.field("iterator").call().field("next").call() } );
EFor(it, rec(expr, innerCtx));
}
default:
Context.error("Internal error in " + mappedSource.toString(), mappedSource.pos);
}
case ETry(e, catches):
var newCatches = [];
for (c in catches)
{
var innerCtx = ctx.copy();
innerCtx.push({ name:c.name, expr: null, type:c.type });
newCatches.push({name:c.name, expr:rec(c.expr, innerCtx), type:c.type});
}
ETry(rec(e), newCatches);
case EFunction(name, func):
var innerCtx = ctx.copy();
for (arg in func.args)
innerCtx.push( { name:arg.name, type:arg.type, expr:null } );
func.expr = rec(func.expr, innerCtx);
EFunction(name, func);
case EVars(vars):
var ret = [];
for (v in vars)
{
var vExpr = v.expr == null ? null : map(v.expr, f, ctx);
if (v.type == null && vExpr != null)
v.type = vExpr.typeof(ctx).sure().toComplex();
ctx.push({ name:v.name, expr:null, type:v.type });
ret.push({ name:v.name, expr:vExpr == null ? null : vExpr, type:v.type });
}
EVars(ret);
default:
mappedSource.expr;
}
return ret.at(pos == null ? source.pos : pos);
}
static public function typedMapArray(source:Array<Expr>, f:Expr->Array<VarDecl>->Expr, ctx:Array<VarDecl>, ?pos) {
var ret = [];
for (e in source)
ret.push(map(e, f, ctx, pos));
return ret;
}
static public inline function iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position)
return EFor(EIn(loopVar.resolve(pos), target).at(pos), body).at(pos);
static public function toFields(object:Dynamic<Expr>, ?pos:Position) {
var args = [];
for (field in Reflect.fields(object))
args.push( { field:field, expr: untyped Reflect.field(object, field) } );
return EObjectDecl(args).at(pos);
}
static public inline function log(e:Expr, ?pos:PosInfos):Expr {
haxe.Log.trace(e.toString(), pos);
return e;
}
static public inline function reject(e:Expr, ?reason:String = 'cannot handle expression'):Dynamic
return e.pos.error(reason);
static public inline function toString(e:Expr):String
// return tink.macro.tools.Printer.printExpr('', e);
return new haxe.macro.Printer().printExpr(e);
static public inline function at(e:ExprDef, ?pos:Position)
return {
expr: e,
pos: pos.getPos()
};
static public inline function instantiate(s:String, ?args:Array<Expr>, ?params:Array<TypeParam>, ?pos:Position)
return s.asTypePath(params).instantiate(args, pos);
static public inline function assign(target:Expr, value:Expr, ?op:Binop, ?pos:Position)
return binOp(target, value, op == null ? OpAssign : OpAssignOp(op), pos);
static public inline function define(name:String, ?init:Expr, ?typ:ComplexType, ?pos:Position)
return at(EVars([ { name:name, type: typ, expr: init } ]), pos);
static public inline function add(e1, e2, ?pos)
return binOp(e1, e2, OpAdd, pos);
static public inline function unOp(e, op, ?postFix = false, ?pos)
return EUnop(op, postFix, e).at(pos);
static public inline function binOp(e1, e2, op, ?pos)
return EBinop(op, e1, e2).at(pos);
static public inline function field(e, field, ?pos)
return EField(e, field).at(pos);
static public inline function call(e, ?params, ?pos)
return ECall(e, params == null ? [] : params).at(pos);
static public inline function toExpr(v:Dynamic, ?pos:Position)
return Context.makeExpr(v, pos.getPos());
static public inline function toArray(exprs:Iterable<Expr>, ?pos)
return EArrayDecl(exprs.array()).at(pos);
static public inline function toMBlock(exprs, ?pos)
return EBlock(exprs).at(pos);
static public inline function toBlock(exprs:Iterable<Expr>, ?pos)
return toMBlock(Lambda.array(exprs), pos);
static inline function isUC(s:String)
return StringTools.fastCodeAt(s, 0) < 0x5B;
static public function drill(parts:Array<String>, ?pos:Position, ?target:Expr) {
if (target == null)
target = at(EConst(CIdent(parts.shift())), pos);
for (part in parts)
target = field(target, part, pos);
return target;
}
static public inline function resolve(s:String, ?pos)
return drill(s.split('.'), pos);
// static public function lazyType(expr:Expr, ?locals) {
// return (function () {
// return typeof(expr, locals).sure();
// }).lazyComplex();
// }
static public function typeof(expr:Expr, ?locals) {
return
try {
if (locals != null)
expr = [EVars(locals).at(expr.pos), expr].toMBlock(expr.pos);
Success(Context.typeof(expr));
}
catch (e:Error) {
var m:Dynamic = e.message;
e.pos.makeFailure(m);
}
catch (e:Dynamic) {
expr.pos.makeFailure(e);
}
}
static public inline function cond(cond:Expr, cons:Expr, ?alt:Expr, ?pos)
return EIf(cond, cons, alt).at(pos);
static public function isWildcard(e:Expr)
return
switch(e.expr) {
case EConst(c):
switch (c) {
case CIdent(s): s == '_';
default: false;
}
default: false;
}
static public function getString(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
case CString(string): Success(string);
default: e.pos.makeFailure(NOT_A_STRING);
}
default: e.pos.makeFailure(NOT_A_STRING);
}
static public function getInt(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
case CInt(id): Success(Std.parseInt(id));
default: e.pos.makeFailure(NOT_AN_INT);
}
default: e.pos.makeFailure(NOT_AN_INT);
}
static public function getIdent(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
case CIdent(id): Success(id);
default: e.pos.makeFailure(NOT_AN_IDENT);
}
default:
e.pos.makeFailure(NOT_AN_IDENT);
}
static public function getName(e:Expr)
return
switch (e.expr) {
case EConst(c):
switch (c) {
case CString(s), CIdent(s): Success(s);
default: e.pos.makeFailure(NOT_A_NAME);
}
default: e.pos.makeFailure(NOT_A_NAME);
}
static public function getFunction(e:Expr)
return
switch (e.expr) {
case EFunction(_, f): Success(f);
default: e.pos.makeFailure(NOT_A_FUNCTION);
}
static inline var NOT_AN_INT = "integer constant expected";
static inline var NOT_AN_IDENT = "identifier expected";
static inline var NOT_A_STRING = "string constant expected";
static inline var NOT_A_NAME = "name expected";
static inline var NOT_A_FUNCTION = "function expected";
static inline var EMPTY_EXPRESSION = "expression expected";
static public function match(expr:Expr, pattern:Expr)
return new Matcher().match(expr, pattern);
}
private class Matcher {
var exprs:Dynamic<Expr>;
var strings:Dynamic<String>;
public function new() {
this.exprs = {};
this.strings = {};
}
public function match(expr:Expr, pattern:Expr) {
return
try {
recurse(expr, pattern);
Success({
exprs: exprs,
strings: strings,//TODO: deprecate
names: strings,
pos: expr.pos
});
}
catch (e:String) Failure(e);
}
function matchObject(x1:Dynamic, x2:Dynamic) {
if (x2 == null) throw Std.string(x2) + ' expected but found ' + Std.string(x1);
for (f in Reflect.fields(x1))
matchAny(Reflect.field(x1, f), Reflect.field(x2, f));
}
function matchString(s1:String, s2:String) {
if (s2 == null)
equal(s1, s2);
else if (s2.startsWith('eval__') || s2.startsWith('NAME__'))
Reflect.setField(strings, s2.substr(6), s1);
else
equal(s1, s2);
}
function equal(x1:Dynamic, x2:Dynamic) {
if (x1 != x2) throw Std.string(x2) + ' expected but found ' + Std.string(x1);
}
function matchAny(x1:Dynamic, x2:Dynamic) {
switch (Inspect.typeof(x1)) {
case TNull, TInt, TFloat, TBool: equal(x1, x2);
case TObject:
if (Std.is(x1.expr, ExprDef)) recurse(x1, x2);
else matchObject(x1, x2);
case TFunction:
throw 'unexpected';
case TClass(c):
if (c == Array) matchArray(x1, x2);
else if (c == String) matchString(x1, x2);
else throw 'unexpected';
case TEnum(_): matchEnum(x1, x2);
case TUnknown:
}
}
function matchArray(a1:Array<Dynamic>, a2:Array<Dynamic>) {
equal(a1.length, a2.length);
for (i in 0...a1.length)
matchAny(a1[i], a2[i]);
}
function matchEnum(e1:Dynamic, e2:Dynamic) {
equal(Inspect.enumConstructor(e1), Inspect.enumConstructor(e2));
matchArray(Inspect.enumParameters(e1), Inspect.enumParameters(e2));
}
function recurse(expr:Expr, pattern:Expr) {
if (pattern == null) throw 'nothing expected but found ' + expr.toString();
switch (pattern.getIdent()) {
case Success(s):
if (s.startsWith('$'))
Reflect.setField(exprs, s.substr(1), expr);
else if (s.startsWith('EXPR__'))
Reflect.setField(exprs, s.substr(6), expr);
else
matchEnum(expr.expr, pattern.expr);
default:
matchEnum(expr.expr, pattern.expr);
}
}
}

View File

@@ -0,0 +1,33 @@
package tink.macro;
import haxe.macro.Expr;
using tink.macro.Exprs;
class Functions {
static public inline function asExpr(f:Function, ?name, ?pos)
return EFunction(name, f).at(pos);
static public inline function toArg(name:String, ?t, ?opt = false, ?value = null):FunctionArg {
return {
name: name,
opt: opt,
type: t,
value: value
};
}
static public inline function func(e:Expr, ?args:Array<FunctionArg>, ?ret:ComplexType, ?params, ?makeReturn = true):Function {
return {
args: args == null ? [] : args,
ret: ret,
params: params == null ? [] : params,
expr: if (makeReturn) EReturn(e).at(e.pos) else e
}
}
static public function getArgIdents(f:Function):Array<Expr> {
var ret = [];
for (arg in f.args)
ret.push(arg.name.resolve());
return ret;
}
}

View File

@@ -0,0 +1,20 @@
package tink.macro;
import haxe.macro.Expr;
class Metadatas {
static public function toMap(m:Metadata) {
var ret = new Map<String,Array<Array<Expr>>>();
for (meta in m) {
if (!ret.exists(meta.name))
ret.set(meta.name, []);
ret.get(meta.name).push(meta.params);
}
return ret;
}
static public function getValues(m:Metadata, name:String)
return [for (meta in m)
if (meta.name == name) meta.params
];
}

62
src/tink/macro/Ops.hx Normal file
View File

@@ -0,0 +1,62 @@
package tink.macro;
import haxe.macro.Expr;
using tink.core.Outcome;
using tink.macro.Tools;
class Binary {
static public function get(o:Binop, e:Expr)
return
switch e.expr {
case EBinop(op, e1, e2):
if (Type.enumEq(o, op))
Success({ e1: e1, e2:e2, pos:e.pos });
else
e.pos.makeFailure('expected ' + o + ' but found ' + op);
default:
e.pos.makeFailure('expected binary operation ' + o);
}
static public function getBinop(e:Expr)
return
switch e.expr {
case EBinop(op, e1, e2):
Success({ e1: e1, e2:e2, op:op, pos:e.pos });
default:
e.pos.makeFailure('expected binary operation but found ' + Type.enumConstructor(e.expr));
}
static public inline function make(op:Binop, e1:Expr, e2:Expr, ?pos)
return Exprs.binOp(e1, e2, op, pos);
}
class Unary {
static public function get(o:Unop, e:Expr, postfix:Bool = false) {
return
switch e.expr {
case EUnop(op, postFix, arg):
if (postFix != postfix)
e.pos.makeFailure(postfix ? 'expected postfix operator' : 'expected prefix operator');
else if (!Type.enumEq(o, op))
e.pos.makeFailure('expected ' + o + ' but found ' + op);
else
Success({ e: arg, pos:e.pos });
default:
e.pos.makeFailure('expected unary operation ' + o);
}
}
static public function getUnop(e:Expr) {
return
switch e.expr {
case EUnop(op, postFix, arg):
Success({ op: op, postfix: postFix, e: arg, pos: e.pos });
default:
e.pos.makeFailure('expected unary operation but found ' + Type.enumConstructor(e.expr));
}
}
static public function make(op:Unop, e:Expr, ?postFix = false, ?pos) {
return EUnop(op, postFix, e).at(pos);
}
}

View File

@@ -0,0 +1,64 @@
package tink.macro;
import haxe.macro.Context;
import haxe.macro.Expr;
import tink.macro.helpers.Bouncer;
using tink.macro.Positions;
using tink.core.Outcome;
class Positions {
static public function getOutcome<D, F>(pos:Position, outcome:Outcome<D, F>):D {
return
switch (outcome) {
case Success(d): d;
case Failure(f): pos.error(f);
}
}
static public function makeBlankType(pos:Position):ComplexType
return Types.toComplex(Context.typeof(macro null));
static public inline function getPos(pos:Position) {
return
if (pos == null)
Context.currentPos();
else
pos;
}
static public function errorExpr(pos:Position, error:Dynamic) {
return Bouncer.bounce(function ():Expr {
return Positions.error(pos, error);
}, pos);
}
static public inline function error(pos:Position, error:Dynamic):Dynamic {
return Context.error(Std.string(error), pos);
}
static public inline function warning<A>(pos:Position, warning:Dynamic, ?ret:A):A {
Context.warning(Std.string(warning), pos);
return ret;
}
///used to easily construct failed outcomes
static public function makeFailure<A, Reason>(pos:Position, reason:Reason):Outcome<A, MacroError<Reason>> {
return Failure(new MacroError(reason, pos));
}
}
private class MacroError<Data> implements ThrowableFailure {
public var data(default, null):Data;
public var pos(default, null):Position;
public function new(data:Data, ?pos:Position) {
this.data = data;
this.pos =
if (pos == null)
Context.currentPos();
else
pos;
}
public function toString() {
return 'Error@' + Std.string(pos) + ': ' + Std.string(data);
}
public function throwSelf():Dynamic {
return Context.error(Std.string(data), pos);
}
}

247
src/tink/macro/Printer.hx Normal file
View File

@@ -0,0 +1,247 @@
package tink.macro.tools;
import haxe.macro.Context;
import haxe.macro.Expr;
using Lambda;
using tink.macro.tools.ExprTools;
class Printer {
static var binops = '+,*,/,-,=,==,!=,>,>=,<,<=,&,|,^,&&,||,<<,>>,>>>,%,NONE,...,=>'.split(',');
static var unops = '++,--,!,-,~'.split(',');
static public function binoperator(b:Binop) {
return
switch (b) {
case OpAssignOp(op):
binoperator(op) + '=';
default:
binops[Type.enumIndex(b)];
}
}
static public function unoperator(u:Unop)
return unops[Type.enumIndex(u)];
static public function binop(?indent:String = '', b:Binop, e1:Expr, e2:Expr):String {
function rec(e)
return printExpr(indent, e);
return '(' + rec(e1) + ' ' + binoperator(b) + ' ' + rec(e2) +')';
}
static public function printExprList(indent:String, list:Iterable<Expr>, ?sep = ', ', ?border:Array<String>):String {
return printList(list.map(printExpr.bind(indent)), sep, border);
}
static public function printList(list:Iterable<String>, ?sep = ', ', ?border:Array<String>) {
if (border == null) border = '()'.split('');
return border[0] + list.list().join(sep) + border[1];
}
static public function printPath(indent:String, p:TypePath) {
var a = p.pack.copy();
a.push(p.name);
if (p.sub != null) a.push(p.sub);
var ret = a.join('.');
if (p.params.length > 0) {
var a = [];
for (p in p.params)
a.push(printParam(indent, p));
ret += '<' + a.join(', ') + '>';
}
return ret;
}
static public function printParam(indent:String, t:TypeParam) {
return
switch (t) {
case TPType(t): printType(indent, t);
case TPExpr(e): printExpr(indent, e);
}
}
static public function printType(indent:String, t:ComplexType) {
return
switch (t) {
case TOptional(t): '?' + printType(indent, t);
case TPath(p):
printPath(indent, p);
case TFunction(args, ret):
if (args.length == 0) 'Void -> ' + printType(indent, ret);
else args.concat([ret]).map(printType.bind(indent)).array().join(' -> ');
case TAnonymous(fields):
printFields(indent, fields);
case TParent(t):
'(' + printType(indent, t) + ')';
case TExtend(p, fields):
printFields(indent, fields, p);
}
}
static public function printFields(indent:String, fields:Array<Field>, ?extend:TypePath) {
var ret = '{ ';
if (extend != null) ret += '> ' + printPath(indent, extend) + '; ';
var a = [];
for (field in fields)
a.push(printField('\t' + indent, field));
if (a.length > 0)
ret += '\n\t' + indent + a.join(';\n\t' + indent) + ';\n' + indent;
return ret +'}';
}
static public function printFunction(f:Function, ?name:String, ?indent = '') {
function rec(e)
return printExpr(indent, e);
var ret = 'function ';
if (name != null) ret += name;
var args = [];
for (arg in f.args) {
var s = if (arg.opt) '?' else '';
s += arg.name;
s += typify(indent, arg.type);
if (arg.value != null) s += ' = ' + rec(arg.value);
args.push(s);
}
var params = [];
for (p in f.params) {
var constraints = [];
for (c in p.constraints)
constraints.push(printType(indent, c));
var ret = p.name;
ret +=
switch (constraints.length) {
case 0: '';
case 1: ':' + constraints[0];
default: ':(' + constraints.join(', ') + ')';
}
params.push(ret);
}
if (params.length > 0)
ret += '<' + params.join(', ') + '>';
ret += '(' + args.join(', ') + ')';
if (f.ret != null)
ret += ':' + printType(indent, f.ret);
if (f.expr != null)
ret += ' ' + rec(f.expr);
return ret;
}
static public function printInitializer(indent:String, e:Expr) {
return
if (e == null) '';
else ' = ' + printExpr(indent, e);
}
static function typify(indent:String, t:ComplexType) {
return
if (t == null) '';
else ':' + printType(indent, t);
}
static public function printField(indent:String, field:Field) {
var ret = '';
if (field.access != null)
for (a in field.access)
ret += Type.enumConstructor(a).substr(1).toLowerCase() + ' ';
ret +=
switch (field.kind) {
case FVar(t, e): 'var ' + field.name + typify(indent, t) + printInitializer(indent, e);
case FProp(get, set, t, e): 'var ' + field.name + '(' + get + ', ' + set + ')' + typify(indent, t) + printInitializer(indent, e);
case FFun(f): printFunction(f, field.name, indent);
}
return ret;
}
static public function print(e:Expr):String {
return printExpr('', e);
}
static public function printExpr(indent:String, e:Expr):String {
function rec(e)
return printExpr(indent, e);
return
if (e == null) '#NULL';
else if (e.expr == null) '#MALFORMED';
else
switch (e.expr) {
case EConst(c):
switch (c) {
case CInt(s), CFloat(s), CIdent(s): s;
case CString(s): '"' + s + '"';
case CRegexp(r, opt): '~/' + r + '/' + opt;
}
case EVars(vars):
var ret = [];
for (v in vars)
ret.push(v.name + typify(indent, v.type) + printInitializer(indent, v.expr));
'var ' + ret.join(', ');
case ECheckType(e, t): '(' + rec(e) + '/* ' + typify(indent, t) + ' */)';
case ECast(e, t):
'cast(' + rec(e) + ((t == null) ? '' : ', ' + printType(indent, t)) + ')';
case EArray(e1, e2):
rec(e1) + '[' + rec(e2) + ']';
case EField(e, field):
rec(e) + '.' + field;
case EParenthesis(e):
'(' + rec(e) + ')';
case ECall(e, params):
rec(e) + printExprList(indent, params);
case EIf(econd, eif, eelse):
'if (' + rec(econd) + ') ' + rec(eif) +
if (eelse == null)
''
else
('\n' + indent + 'else ' + rec(eelse));
case EBlock(exprs):
if (exprs.length == 0)
'{}';
else
printExprList(indent + '\t', exprs, ';\n\t' + indent, ['{\n\t' + indent, ';\n' + indent + '}']);
case EIn(e1, e2):
rec(e1) + ' in ' + rec(e2);
case EFor(it, expr):
'for (' + rec(it) + ')\n\t' + indent + printExpr(indent + '\t', expr);
case EWhile(econd, e, normalWhile):
if (normalWhile)
'while (' + rec(econd) + ') ' + rec(e);
else
'do ' + rec(e) + '\n' + indent + 'while (' + rec(econd) + ')';
case EBreak: 'break';
case EContinue: 'continue';
case EUntyped(e): '(untyped ' + rec(e) + ')';
case EThrow(e): 'throw ' + rec(e);
case EReturn(e): 'return' + if (e == null) '' else (' ' + rec(e));
case EDisplay(e, isCall):
rec(e) + (isCall ? '(/*DISPLAY*/)' : '/*.DISPLAY*/');
case EDisplayNew(t):
'new ' + printPath(indent, t) + '(/*DISPLAY*/)';
case ETernary(econd, eif, eelse):
'((' + rec(econd) + ') ? ' + rec(eif) + ' : ' + rec(eelse) + ')';
case ESwitch(e, cases, edef):
var ret = [];
for (c in cases)
ret.push(
'case ' + printExprList(indent, c.values, ['', ''])
+ (if (c.guard == null) '' else ' if '+rec(c.guard))
+ ': ' + printExpr(indent + '\t', c.expr));
if (edef != null)
ret.push('default: ' + rec(edef));
'switch (' + rec(e) + ') {\n\t' + indent + ret.join('\n\t' + indent) + '\n' + indent + '}';
case EUnop(op, postFix, e):
var op = unops[Type.enumIndex(op)];
var e = rec(e);
var inner =
if (postFix)
e + op;
else
op + e;
'(' + inner + ')';
case ETry(e, catches):
var ret = [];
for (c in catches)
ret.push('catch (' + c.name +':' + printType(indent, c.type) + ') ' + rec(c.expr));
'try ' + rec(e) + '\n' + indent + ret.join('\n' + indent);
case ENew(t, params):
'new ' + printPath(indent, t) + printExprList(indent, params);
case EBinop(op, e1, e2):
binop(indent, op, e1, e2);
case EArrayDecl(values):
printExprList(indent, values, '[]'.split(''));
case EObjectDecl(fields):
var ret = [];
for (field in fields)
ret.push(field.field + ' : ' + rec(field.expr));
printList(ret, '{}'.split(''));
case EFunction(name, f):
printFunction(f, name, indent);
case EMeta(s, e):
'@' + s.name + printExprList(indent, s.params) + ' ' + rec(e);
//default: '#UNSUPPORTED_' + Type.enumConstructor(e.expr);
}
}
}

50
src/tink/macro/Tools.hx Normal file
View File

@@ -0,0 +1,50 @@
package tink.macro;
#if macro
import haxe.macro.Context;
typedef _Positions = Positions;
typedef _Exprs = Exprs;
typedef _Functions = Functions;
typedef _Metadatas = Metadatas;
typedef _Bouncer = tink.macro.helpers.Bouncer;
typedef _Types = Types;
typedef _Binops = Ops.Binary;
typedef _Unops = Ops.Unary;
#end
class Tools {
static var idCounter = 0;
static public inline function tempName(c:Class<String>, ?prefix = '__tinkTmp'):String {
return prefix + Std.string(idCounter++);
}
#if macro
static public function deprecate<A>(at:haxe.PosInfos, useInstead:String, ret:A, ?p:haxe.PosInfos) {
try {
var lines = sys.io.File.getContent(at.fileName).split('\n'),
min = 0,
line = lines[at.lineNumber - 1];
for (i in 0...at.lineNumber-1)
min += lines[i].length + 1;
var max = min + line.length;
function count(haystack:String, needle)
return haystack.split(needle).length - 1;
if (count(line, p.methodName) == 1) {
min += line.indexOf(p.methodName);
max = min + p.methodName.length;
}
Context.warning(p.className + '::' + p.methodName + ' is deprecated. Use $useInstead instead', Context.makePosition( {
min: min,
max: max,
file: at.fileName
}));
}
catch (e:Dynamic)
haxe.Log.trace('This function is deprecated, use $useInstead instead. Call site: ${p.className}@${p.lineNumber}');
return ret;
}
#end
}

221
src/tink/macro/Types.hx Normal file
View File

@@ -0,0 +1,221 @@
package tink.macro;
import haxe.macro.Printer;
import Type in Enums;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using tink.macro.Exprs;
using tink.macro.Positions;
using tink.macro.Functions;
using tink.core.Outcome;
class Types {
static var types = new Map<Int,Void->Type>();
static var idCounter = 0;
macro static public function getType(id:Int):Type {
return types.get(id)();
}
static public function getID(t:Type, ?reduced = true) {
if (reduced)
t = reduce(t);
return
switch (t) {
case TAbstract(t, _): t.toString();
case TInst(t, _): t.toString();
case TEnum(t, _): t.toString();
case TType(t, _): t.toString();
default: null;
}
}
static public function accessToName(v:VarAccess, ?read = true) {
return
switch (v) {
case AccNormal, AccInline: 'default';
case AccNo: 'null';
case AccNever: 'never';
case AccCall: if (read) 'get' else 'set';
default:
throw 'not implemented';
}
}
static function getDeclaredFields(t:ClassType, out:Array<ClassField>, marker:Map<String,Bool>) {
for (field in t.fields.get())
if (!marker.exists(field.name)) {
marker.set(field.name, true);
out.push(field);
}
if (t.isInterface)
for (t in t.interfaces)
getDeclaredFields(t.t.get(), out, marker);
else
if (t.superClass != null)
getDeclaredFields(t.superClass.t.get(), out, marker);
}
static var fieldsCache = new Map();
static public function getFields(t:Type, ?substituteParams = true)
return
switch (reduce(t)) {
case TInst(c, _):
var id = c.toString(),
c = c.get();
if (!fieldsCache.exists(id)) {
var fields = [];
getDeclaredFields(c, fields, new Map());
fieldsCache.set(id, Success(fields));
}
var ret = fieldsCache.get(id);
if (substituteParams && ret.isSuccess()) {
var e = ECheckType(macro null, toComplex(t)).at();
var fields = Reflect.copy(ret.sure());
ret = Success(fields);
for (field in fields)
if (field.isPublic) {
var member = e.field(field.name, e.pos);
field.type =
switch (member.typeof()) {
case Success(t): t;
case Failure(f):
switch (reduce(field.type)) {
case TFun(args, _):
var fArgs = [],
fParams = [];
for (a in args)
fArgs.push(a.name.toArg());
var f = (macro null).func(fArgs, false);
f.expr = EReturn(member.call(f.getArgIdents(), e.pos)).at(e.pos);
f.asExpr(e.pos).typeof().sure();
default:
f.throwSelf();
}
}
}
else {
var kind =
switch (field.kind) {
case FVar(read, write):
FProp(accessToName(read), accessToName(write, true), field.pos.makeBlankType());
default:
switch (reduce(field.type)) {
case TFun(args, _):
var argList = [];
for (arg in args)
argList.push(
arg.name.toArg(field.pos.makeBlankType())
);
FFun({
args : argList,
ret: field.pos.makeBlankType(),
expr: null,
params: []
});
default: null;
}
}
if (kind != null) {
var f = {
name: field.name,
pos: field.pos,
kind: kind,
access: [APrivate]
};
field.type = ECheckType(e, TAnonymous([f])).at(e.pos).field(field.name, e.pos).typeof().sure();
}
}
}
ret;
case TAnonymous(anon): Success(anon.get().fields);
default: Context.currentPos().makeFailure('type has no fields');
}
static public function getStatics(t:Type)
return
switch (reduce(t)) {
case TInst(t, _): Success(t.get().statics.get());
default: Failure('type has no statics');
}
static public function toString(t:ComplexType)
return new Printer().printComplexType(t);
static public function isSubTypeOf(t:Type, of:Type, ?pos)
return
ECheckType(ECheckType('null'.resolve(), toComplex(t)).at(pos), toComplex(of)).at(pos).typeof();
static public function isDynamic(t:Type)
return switch(reduce(t)) {
case TDynamic(_): true;
default: false;
}
static public function toType(t:ComplexType, ?pos)
return [
'_'.define(t, pos),
'_'.resolve(pos)
].toBlock(pos).typeof();
static public inline function instantiate(t:TypePath, ?args, ?pos) {
return ENew(t, args == null ? [] : args).at(pos);
}
static public function asTypePath(s:String, ?params):TypePath {
var parts = s.split('.');
var name = parts.pop(),
sub = null;
if (parts.length > 0 && parts[parts.length - 1].charCodeAt(0) < 0x5B) {
sub = name;
name = parts.pop();
if(sub == name) sub = null;
}
return {
name: name,
pack: parts,
params: params == null ? [] : params,
sub: sub
};
}
static public inline function asComplexType(s:String, ?params)
return TPath(asTypePath(s, params));
static public inline function reduce(type:Type, ?once)
return Context.follow(type, once);
static public function isVar(field:ClassField) {
return switch (field.kind) {
case FVar(_, _): true;
default: false;
}
}
static public function register(type:Void->Type):Int {
var id = idCounter++;
types.set(id, type);
return id;
}
static function paramsToComplex(params:Array<Type>):Array<TypeParam> {
var ret = [];
for (p in params)
ret.push(TPType(toComplex(p, true)));
return ret;
}
static function baseToComplex(t:BaseType, params:Array<Type>)
return asComplexType(t.module + '.' + t.name, paramsToComplex(params));
static public function toComplex(type:Type, ?pretty = false):ComplexType {
var ret = haxe.macro.TypeTools.toComplexType(type);
if (ret == null)
ret = lazyComplex(function () return type);
return ret;
}
static public function lazyComplex(f:Void->Type) {
return
TPath({
pack : ['haxe','macro'],
name : 'MacroType',
params : [TPExpr('tink.macro.tools.TypeTools.getType'.resolve().call([register(f).toExpr()]))],
sub : null,
});
}
}

View File

@@ -0,0 +1,55 @@
package tink.macro.helpers;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
using tink.macro.Positions;
using tink.macro.Exprs;
#end
class Bouncer {
//TODO: as is, a more less empty class is generated in the output. This is unneccessary.
#if macro
static var idCounter = 0;
static var bounceMap = new Map<Int,Void->Expr>();
static var outerMap = new Map<Int,Expr->Expr>();
static public function bounce(f:Void->Expr, ?pos) {
var id = idCounter++;
bounceMap.set(id, f);
return 'tink.macro.helpers.Bouncer.catchBounce'.resolve(pos).call([id.toExpr(pos)], pos);
}
static public function outerTransform(e:Expr, transform:Expr->Expr) {
var id = idCounter++,
pos = e.pos;
outerMap.set(id, transform);
return 'tink.macro.helpers.Bouncer.makeOuter'.resolve(pos).call([e], pos).field('andBounce', pos).call([id.toExpr(pos)], pos);
}
static function doOuter(id:Int, e:Expr) {
return
if (outerMap.exists(id))
outerMap.get(id)(e);
else
Context.currentPos().error('unknown id ' + id);
}
static function doBounce(id:Int) {
return
if (bounceMap.exists(id))
bounceMap.get(id)();
else
Context.currentPos().error('unknown id ' + id);
}
#end
static public function makeOuter<A>(a:A):Bouncer
return null;
macro public function andBounce(ethis:Expr, id:Int) {
return
switch (ethis.expr) {
case ECall(_, params): doOuter(id, params[0]);
default: ethis.reject();
}
}
macro static public function catchBounce(id:Int) {
return doBounce(id);
}
}

6
tests.hxml Normal file
View File

@@ -0,0 +1,6 @@
-lib tink_core
-cp src
-cp tests
-main Run
-neko bin/tests.n
#iota start Run.bat

50
tests/Base.hx Normal file
View File

@@ -0,0 +1,50 @@
package ;
import haxe.PosInfos;
import haxe.unit.TestCase;
import haxe.unit.TestResult;
import haxe.unit.TestStatus;
import tink.core.Either;
abstract PhysicalType<T>(Either<Class<T>, Enum<T>>) {
function new(v) this = v;
public function toString()
return
switch this {
case Left(c): return Type.getClassName(c);
case Right(e): return Type.getEnumName(e);
}
public function check(v:T)
return
Std.is(v, this.getParameters()[0]);
@:from static function ofClass<C>(c:Class<C>)
return new PhysicalType(Left(c));
@:from static function ofEnum<E>(e:Enum<E>)
return new PhysicalType(Right(e));
}
//TODO: this helper should go somewhere
class Base extends TestCase {
function fail(msg:String, ?c : PosInfos) {
currentTest.done = true;
currentTest.success = false;
currentTest.error = msg;
currentTest.posInfos = c;
throw currentTest;
}
function throws<A>(f:Void->Void, t:PhysicalType<A>, ?check:A->Bool, ?pos:PosInfos):Void {
try f()
catch (e:Dynamic) {
if (!t.check(e)) fail('Exception $e not of type $t', pos);
if (check != null && !check(e)) fail('Exception $e does not satisfy condition', pos);
assertTrue(true);
return;
}
fail('no exception thrown', pos);
}
}