Compare commits

...

42 Commits

Author SHA1 Message Date
Juraj Kirchheim
92f60cbe4f Release 0.10.0 2016-07-08 11:49:30 +02:00
Juraj Kirchheim
3b822b4cdb Merge branch 'master' of https://github.com/haxetink/tink_macro 2016-07-08 11:49:00 +02:00
Juraj Kirchheim
f2b670f9e4 Expand build cache for 2 and 3 type parameters. 2016-07-08 11:48:40 +02:00
Kevin Leung
ff491fe7e7 Add gitter link 2016-07-06 15:22:47 +08:00
Juraj Kirchheim
7e869cd9c8 Fix logic in Exprs.has 2016-06-23 05:19:53 +02:00
Juraj Kirchheim
983ffa16af Avoid stringly approach. 2016-06-10 08:20:09 +02:00
Juraj Kirchheim
4207dea754 Release 0.9.1 2016-05-20 12:41:35 +02:00
Juraj Kirchheim
3470f1cc47 Store user defined constructors. 2016-05-20 12:41:20 +02:00
Juraj Kirchheim
d84fb38d73 Release 0.9.0 2016-05-18 07:59:04 +02:00
Juraj Kirchheim
065788c198 Replace MacroType with @:genericBuild for direct type converions. 2016-05-18 07:58:28 +02:00
Juraj Kirchheim
810c2f9630 Release 0.8.1 2016-05-02 17:58:30 +02:00
Juraj Kirchheim
331f186dcd Minor API improvement. 2016-05-02 17:58:09 +02:00
Juraj Kirchheim
c406e0b844 Release 0.8.0 2016-05-02 17:54:13 +02:00
Juraj Kirchheim
43fb9eb16a Add build cache utility. 2016-05-02 17:45:45 +02:00
Juraj Kirchheim
3734bcb513 Release 0.7.1 2016-04-19 18:23:57 +02:00
Juraj Kirchheim
d53937f836 Avoid stale fields cache. 2016-04-19 18:23:40 +02:00
Juraj Kirchheim
9f9675860a Release 0.7.0 2016-04-19 12:53:17 +02:00
Juraj Kirchheim
c3cecad472 Add TypeMap with test. 2016-04-19 12:52:45 +02:00
Juraj Kirchheim
0284615f99 Use travix. Update readme. Fixes #5. 2016-04-19 12:32:42 +02:00
Juraj Kirchheim
0be931b913 Merge branch 'master' of https://github.com/haxetink/tink_macro 2016-04-14 13:54:32 +02:00
Juraj Kirchheim
c0b562af31 Fix indenting. 2016-04-14 13:54:26 +02:00
Juraj Kirchheim
8e0065c017 Merge pull request #6 from markknol/patch-1
Syntax highlighting
2016-03-09 22:00:12 +01:00
Mark Knol
bf6d9461cb Syntax highlighting 2016-03-09 13:56:55 +01:00
back2dos
df01d82300 Release 0.6.4 2015-05-28 08:59:18 +02:00
back2dos
9b4e9cb81f Merge branch 'master' of https://github.com/haxetink/tink_macro 2015-05-28 08:58:18 +02:00
back2dos
b90aae8386 Minor simplification in constructor generation. 2015-05-28 08:57:55 +02:00
back2dos
ab34b31bbd Updated doc. 2015-05-22 09:01:01 +02:00
Juraj Kirchheim
ad3526795c Merge pull request #3 from grisevg/master
Made imports and using expressions to use CoreApi redefinitions.
2015-05-19 18:27:27 +02:00
Eugene Griskov
f4bf23a66b Made imports and using expressions to use CoreApi redefinitions. 2015-05-19 17:22:23 +01:00
back2dos
a01a1b16ae Minor fix. 2015-05-19 01:45:02 +02:00
back2dos
1a404103ca Release 0.6.3 2015-05-19 01:39:41 +02:00
back2dos
b4bfa69376 Make class builder lazy and less opinionated. 2015-05-19 01:38:37 +02:00
back2dos
b17005d9ab Release 0.6.2 2015-05-18 14:52:49 +02:00
back2dos
61505ec63b Release 0.6.1 2015-05-18 14:52:25 +02:00
back2dos
ef2054d8ff Minor fix. 2015-05-18 14:51:29 +02:00
back2dos
0683be706c Release 0.6.1 2015-05-18 14:49:33 +02:00
back2dos
78e9087ba2 Do not lose constructor metadata. 2015-05-18 14:48:35 +02:00
back2dos
6b8aefa7ca Release 0.6.0 2015-05-18 01:21:03 +02:00
back2dos
0db8ee09f1 Removed type resolution queue in favor of tink_syntaxhub. 2015-05-18 01:18:17 +02:00
back2dos
dc29f07217 Remove the good old typed map. 2015-05-17 22:58:45 +02:00
back2dos
b02e27b589 Minor cleanup. 2015-04-29 11:30:24 +02:00
back2dos
30c4196f4f Avoid DCE tango on JS. 2014-08-28 12:22:25 +02:00
25 changed files with 1651 additions and 1605 deletions

19
.travis.yml Normal file
View File

@@ -0,0 +1,19 @@
sudo: required
dist: trusty
language: haxe
haxe:
- "3.2.1"
- development
matrix:
allow_failures:
- haxe: development
install:
- haxelib install travix
- haxelib run travix install
script:
- haxelib run travix node

View File

@@ -1,4 +1,5 @@
# Tinkerbell Macro Library
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](https://gitter.im/haxetink/public)
Explained in current marketing speak, `tink_macro` is *the* macro toolkit ;)
@@ -38,13 +39,13 @@ The library is build on top of the haxe macro API and `tink_core`, having three
- [Setter Bypass](#setter-bypass)
- [Initialization Options](#initialization-options)
- [Expression Level Transformation](#expression-level-transformation)
- [Type Resolution Infrastructure](#type-resolution-infrastructure)
- [TypeMap](#typemap)
<!-- END INDEX -->
# Macro API
It is suggested to use this API by `using tink.MacroAPI;`
It is suggested to use this API by `using tink.MacroApi;`
Apart form `tink_macro` specific things, it will also use `haxe.macro.ExprTools` and `tink.core.Outcome`.
@@ -135,7 +136,7 @@ Will build a new expression substituting identifiers given found as fields of `v
- `substParams(source:Expr, rule: ParamSubst, ?pos:Position):Expr`
Traverse an expression and replace any *type* that looks like a type parameter following the given `rule` of the following structure:
```
```haxe
typedef ParamSubst = {
var exists(default, null):String->Bool;
var get(default, null):String->ComplexType;
@@ -149,7 +150,7 @@ Similar to transform, but handles expressions in top-down order and keeps track
- `bounce(f:Void->Expr, ?pos:Position):Expr`
This is a way to "bounce" out of a macro for a while. Assume you have this expression: `{ var a = 5, b = 6; a + b; }` and you want to analyze the second statement, you either have to track variables manually or do a `typedMap` but that may be too much work. What you would do here is something like this (stupid example):
```
```haxe
function onBounce() { trace(block[1].typeof().sure()); return block[1]; }
[block[0], onBounce.bounce(block[1].pos)].toBlock();
```
@@ -157,7 +158,7 @@ This is a way to "bounce" out of a macro for a while. Assume you have this expre
- `yield(source:Expr, yielder:Expr->Expr, ?options:{ ?leaveLoops:Bool }):Expr`
This will traverse an expression and will apply the `yielder` to the "leafs", which in this context are the subexpressions that determine a return value. Example:
```
```haxe
yield(
macro if (foo) bar else { var x = y; for (i in a) bla; },
function (e) return macro trace($e)
@@ -168,7 +169,7 @@ This will traverse an expression and will apply the `yielder` to the "leafs", wh
To implement array comprehensions yourself with this you would do:
```
```haxe
e.transform(function (e) return switch e {
case macro [for ($it) $body]:
macro {
@@ -260,7 +261,7 @@ Writing build macros can sometimes be a little tedious. But `tink_macro` is here
Let's have a look at the most important type involved in build macros:
```
```haxe
typedef Field = {
var name : String;
@:optional var doc : Null<String>;
@@ -275,7 +276,7 @@ No doubt, it gets the job done. There's a few things that could be nicer though.
For this reason and more, we have `tink.macro.Member` which looks like this:
```
```haxe
abstract Member from Field to Field {
var name(get, set):String;
var doc(get, set):Null<String>;
@@ -310,7 +311,7 @@ At any time you can also use `asField` to interact with the data the good old wa
To make handling multiple fields easier, we have the `ClassBuilder` with the following API:
```
```haxe
class ClassBuilder {
var target(default, null):ClassType;
function new():Void;
@@ -335,7 +336,7 @@ The first thing to point out is that constructors are handled separately. This i
As for the rest of the members, you can just iterate over them. It's worth noting that the iterator runs over a snapshot made at the time of its creation, so removing and adding fields during iteration has no effect on the iteration itself.
You can add a member. If you try adding a member named `"new"`, you'll get an exception. So don't do it. Read up on constructors below. If you try adding a duplicate member, you will get a compilation error at the second member's `Position`. If you add a member that already exists in the super class, the `override` is added automatically.
You can add a member. If you try adding a member named `"new"`, you'll get an exception - so don't. Find out about how tink_macro handles constructors below. If you add a member that already exists in the super class, the `override` is added automatically.
And when you're done, you can `export` everything to an array of fields. If you set `verbose` to true, you will get compiler warnings for every generated field at the position of the field. This is way you can see the generated code even if the application cannot compile for some reason.
@@ -343,13 +344,13 @@ The intended use is with `run` that will send the same `ClassBuilder` through a
## Constructor
Constructors are relatively tricky, especially when you have inheritance. If you do not specify a constructor, than that of the the super class is used. If you do specify one, then it needn't be compatible with the super class, but it needs to call it. Macros represent them as an instance field called `new` that must be a function. However if you think about it, a constructor belongs to a class, not an instance. So this is all a little dodgy.
Constructors are relatively tricky, especially when you have inheritance. If you do not specify a constructor, than that of the the super class is used. If you do specify one, then it needn't be compatible with the super class, but it needs to call it. Macros represent them as an instance field called `new` that must be a function. However if you think about it, a constructor belongs to a class, not an instance. So this is all a little dodgy. The constructor API is an attempt to create a more rigid solution.
The `Constructor` API is the result of countless struggles with constructors. Still it may not be for you. In that case feedback is appreciated and currently the suggested method is to deal with the constructor after you've exported all fields from the `ClassBuilder`.
Constructors are represented by this API:
```
```haxe
class Constructor {
var isPublic:Null<Bool>;
function publish():Void;
@@ -396,7 +397,7 @@ Please do note, that `value` will be in the generated code twice, therefore if i
The different options for initialization are as follows:
```
```haxe
enum FieldInit {
Value(e:Expr);
Arg(?t:ComplexType, ?noPublish:Bool);
@@ -410,19 +411,6 @@ Here, `Value` will just use a plain expression, whereas `Arg` and `OptArg` will
Because the state of a constructor is rather delicate, the API prohibits you to just mess around with the whole constructor body at an expression level. For that to happen, you can register `onGenerate` hooks. These will be called when the corresponding `ClassBuilder` does its export. The hooks are cleared after the export.
# Type Resolution Infrastructure
# TypeMap
The plain `Context.onTypeNotFound` API has two major drawbacks:
1. There is no way for two resolvers to communicate with one another. As soon as the first one is able to create a fallback type, all subsequent ones are not invoked.
2. Calls to `Context.error` from within a type resolver will cause abortion of the call, which makes error reporting tricky.
In `tink_macro` we define the following:
```
typedef TypeResolution = Ref<Either<String, TypeDefinition>>;
```
This is the current state of the resolution, meaning we either have a `String`, which is the name of the type that wasn't found, or a `TypeDefinition` which is a "proposal" given by already invoked resolvers.
To register a type resolver, you can add a callback to `tink.MacroApi.typeNotFound` which is a `Signal<TypeResolution>`.
You can find a type map, i.e. a map where the keys are `haxe.macro.Type`, in `tink.macro.TypeMap`. It's pretty much an ordinary map. Currently, it relies rather strongly on [`haxe.macro.TypeTools.toString()`](http://api.haxe.org/haxe/macro/TypeTools.html#toString) and it remains to be determined whether that is a reliable choice. Please report any issues you might face.

View File

@@ -1 +1,20 @@
{"name":"tink_macro","license":"MIT","tags":["tink","macro","utility"],"classPath":"src","description":"The macro toolkit ;)","contributors":["back2dos"],"releasenote":"Extended API.","version":"0.5.0","url":"http://haxetink.org/tink_macro","dependencies":{"tink_core":""}}
{
"name": "tink_macro",
"license": "MIT",
"tags": [
"tink",
"macro",
"utility"
],
"classPath": "src",
"description": "The macro toolkit ;)",
"contributors": [
"back2dos"
],
"releasenote": "Expand build cache for 2 and 3 type parameters.",
"version": "0.10.0",
"url": "http://haxetink.org/tink_macro",
"dependencies": {
"tink_core": ""
}
}

View File

@@ -17,7 +17,7 @@ typedef Unops = tink.macro.Ops.Unary;
//TODO: consider adding stuff from haxe.macro.Expr here
typedef MacroOutcome<D, F> = tink.core.Outcome<D, F>;
typedef MacroOutcomeTools = tink.core.Outcome.OutcomeTools;
typedef MacroOutcomeTools = tink.OutcomeTools;
typedef Option<T> = haxe.ds.Option<T>;
@@ -28,67 +28,13 @@ typedef ClassBuilder = tink.macro.ClassBuilder;
typedef TypeResolution = Ref<Either<String, TypeDefinition>>;
class MacroApi {
static var idCounter = 0;
@:noUsing static public inline function tempName(?prefix:String = 'tmp'):String
return '__tink_' + prefix + Std.string(idCounter++);
static public function pos()
return haxe.macro.Context.currentPos();
static public var typeNotFound(get, null):Signal<TypeResolution>;
static function get_typeNotFound() {
if (typeNotFound == null) {
var trigger = Signal.trigger();
var listening = false;
function register() {
if (listening) return;
listening = true;
haxe.macro.Context.onTypeNotFound(function (name:String) {
@:privateAccess Positions.errorFunc = @:privateAccess Positions.abortTypeBuild;
var def = Ref.to(Left(name));
try trigger.trigger(def)
catch (abort:tink.macro.Positions.AbortBuild) {
var cl = macro class {
static var __error = ${Positions.errorExpr(abort.pos, abort.message)};
}
var path = name.split('.');
cl.name = path.pop();
cl.pack = path;
cl.pos = abort.pos;
def.value = Right(cl);
}
@:privateAccess Positions.errorFunc = @:privateAccess Positions.contextError;
return switch def.value {
case Right(def): def;
default: null;
}
});
}
register();
haxe.macro.Context.onMacroContextReused(function () {
listening = false;
return true;
});
var ret = trigger.asSignal();
typeNotFound = new Signal(function (cb) {
register();
return ret.handle(cb);
});
}
return typeNotFound;
}
static var idCounter = 0;
@:noUsing static public inline function tempName(?prefix:String = 'tmp'):String
return '__tink_' + prefix + Std.string(idCounter++);
static public function pos()
return haxe.macro.Context.currentPos();
}

View File

@@ -1,63 +1,63 @@
package tink.macro;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
using tink.macro.Positions;
using tink.macro.Exprs;
import haxe.macro.Context;
import haxe.macro.Expr;
using tink.macro.Positions;
using tink.macro.Exprs;
#end
@:exclude class Bouncer {
#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 bounceExpr(e:Expr, transform:Expr->Expr) {
var id = idCounter++,
pos = e.pos;
outerMap.set(id, transform);
return macro @:pos(e.pos) tink.macro.Bouncer.catchBounceExpr($e, $v{id});
}
static public function bounce(f:Void->Expr, ?pos:Position) {
var id = idCounter++;
pos = pos.sanitize();
bounceMap.set(id, f);
return macro @:pos(pos) tink.macro.Bouncer.catchBounce($v{id});
}
static public function outerTransform(e:Expr, transform:Expr->Expr) {
var id = idCounter++,
pos = e.pos;
outerMap.set(id, transform);
return macro @:pos(e.pos) tink.macro.Bouncer.makeOuter($e).andBounce($v{id});
}
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);
}
#else
@:noUsing static public function makeOuter<A>(a:A):Bouncer
return null;
#end
@:noUsing macro public function andBounce(ethis:Expr, id:Int)
return
switch (ethis.expr) {
case ECall(_, params): doOuter(id, params[0]);
default: ethis.reject();
}
#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 bounceExpr(e:Expr, transform:Expr->Expr) {
var id = idCounter++,
pos = e.pos;
outerMap.set(id, transform);
return macro @:pos(e.pos) tink.macro.Bouncer.catchBounceExpr($e, $v{id});
}
static public function bounce(f:Void->Expr, ?pos:Position) {
var id = idCounter++;
pos = pos.sanitize();
bounceMap.set(id, f);
return macro @:pos(pos) tink.macro.Bouncer.catchBounce($v{id});
}
static public function outerTransform(e:Expr, transform:Expr->Expr) {
var id = idCounter++,
pos = e.pos;
outerMap.set(id, transform);
return macro @:pos(e.pos) tink.macro.Bouncer.makeOuter($e).andBounce($v{id});
}
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);
}
#else
@:noUsing static public function makeOuter<A>(a:A):Bouncer
return null;
#end
@:noUsing macro public function andBounce(ethis:Expr, id:Int)
return
switch (ethis.expr) {
case ECall(_, params): doOuter(id, params[0]);
default: ethis.reject();
}
@:noUsing macro static public function catchBounce(id:Int)
return doBounce(id);
@:noUsing macro static public function catchBounceExpr(e:Expr, id:Int)
return doOuter(id, e);
@:noUsing macro static public function catchBounce(id:Int)
return doBounce(id);
@:noUsing macro static public function catchBounceExpr(e:Expr, id:Int)
return doOuter(id, e);
}

View File

@@ -0,0 +1,123 @@
package tink.macro;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
import tink.macro.TypeMap;
using haxe.macro.ComplexTypeTools;
using haxe.macro.TypeTools;
typedef BuildContext = {
pos:Position,
type:Type,
usings:Array<TypePath>,
name:String,
}
typedef BuildContext2 = {>BuildContext,
type2:Type,
}
typedef BuildContext3 = {>BuildContext2,
type3:Type,
}
class BuildCache {
static var cache = init();
static function init() {
function refresh() {
cache = new Map();
return true;
}
Context.onMacroContextReused(refresh);
refresh();
return cache;
}
static public function getType3(name, ?types, ?pos:Position, build:BuildContext3->TypeDefinition) {
if (types == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, [t1, t2, t3]):
types = { t1: t1, t2: t2, t3: t3 };
default:
throw 'assert';
}
var t1 = types.t1.toComplexType(),
t2 = types.t2.toComplexType(),
t3 = types.t2.toComplexType();
return getType(name, (macro : { t1: $t1, t2: $t2, t3: $t3 } ).toType(), pos, function (ctx) return build({
type: types.t1,
type2: types.t2,
type3: types.t3,
pos: ctx.pos,
name: ctx.name,
usings: ctx.usings
}));
}
static public function getType2(name, ?types, ?pos:Position, build:BuildContext2->TypeDefinition) {
if (types == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, [t1, t2]):
types = { t1: t1, t2: t2 };
default:
throw 'assert';
}
var t1 = types.t1.toComplexType(),
t2 = types.t2.toComplexType();
return getType(name, (macro : { t1: $t1, t2: $t2 } ).toType(), pos, function (ctx) return build({
type: types.t1,
type2: types.t2,
pos: ctx.pos,
name: ctx.name,
usings: ctx.usings
}));
}
static public function getType(name, ?type, ?pos:Position, build:BuildContext->TypeDefinition) {
if (pos == null)
pos = Context.currentPos();
if (type == null)
switch Context.getLocalType() {
case TInst(_.toString() == name => true, [v]):
type = v;
default:
throw 'assert';
}
var forName =
switch cache[name] {
case null: cache[name] = new TypeMap();
case v: v;
}
if (!forName.exists(type)) {
var path = '$name${Lambda.count(forName)}',
usings = [];
var def = build({
pos: pos,
type: type,
usings: usings,
name: path.split('.').pop()
});
Context.defineModule(path, [def], usings);
forName.set(type, Context.getType(path));
}
return forName.get(type);
}
}

View File

@@ -9,160 +9,185 @@ using tink.MacroApi;
using Lambda;
class ClassBuilder {
var memberMap:Map<String,Member>;
var memberList:Array<Member>;
var macros:Map<String,Field>;
var constructor:Null<Constructor>;
public var target(default, null):ClassType;//TODO: this could be lazy
var superFields:Map<String,Bool>;
var keepers:Array<Expr>;//hack to force field generation
public function new() {
this.memberMap = new Map();
this.memberList = [];
this.macros = new Map();
this.target = Context.getLocalClass().get();
switch (target.kind) {
case KAbstractImpl(a):
//TODO: remove this whole workaround
var meta = target.meta;
for (tag in a.get().meta.get())
if (!meta.has(tag.name))
meta.add(tag.name, tag.params, tag.pos);
default:
}
for (field in Context.getBuildFields())
if (field.access.has(AMacro))
macros.set(field.name, field)
else if (field.name == 'new') {
var m:Member = field;
this.constructor = new Constructor(this, m.getFunction().sure(), m.isPublic, m.pos);
}
else
addMember(field);
}
public function getConstructor(?fallback:Function):Constructor {
if (constructor == null)
if (fallback != null)
new Constructor(this, fallback);
else {
var sup = target.superClass;
while (sup != null) {
var cl = sup.t.get();
if (cl.constructor != null) {
try {
var ctor = cl.constructor.get();
var func = Context.getTypedExpr(ctor.expr()).getFunction().sure();
for (arg in func.args) //this is to deal with type parameter substitutions
arg.type = null;
func.expr = "super".resolve().call(func.getArgIdents());
constructor = new Constructor(this, func);
if (ctor.isPublic)
constructor.publish();
}
catch (e:Dynamic) {//fails for unknown reason
if (e == 'assert')
neko.Lib.rethrow(e);
constructor = new Constructor(this, null);
}
break;
}
else sup = cl.superClass;
}
if (constructor == null)
constructor = new Constructor(this, null);
}
return constructor;
}
public function hasConstructor():Bool
return this.constructor != null;
public function export(?verbose):Array<Field> {
var ret = (constructor == null || target.isInterface) ? [] : [constructor.toHaxe()];
for (member in memberList) {
if (member.isBound)
switch (member.kind) {//TODO: this seems like an awful place for a cleanup. If all else fails, this should go into a separate plugin (?)
case FVar(_, _): if (!member.isStatic) member.isBound = null;
case FProp(_, _, _, _): member.isBound = null;
default:
}
ret.push(member);
}
for (m in macros)
ret.push(m);
if (verbose)
for (field in ret)
Context.warning(new Printer().printField(field), field.pos);
return ret;
}
public function iterator():Iterator<Member>
return this.memberList.copy().iterator();
public function hasOwnMember(name:String):Bool
return
macros.exists(name) || memberMap.exists(name);
public function hasSuperField(name:String):Bool {
if (superFields == null) {
superFields = new Map();
var cl = target.superClass;
while (cl != null) {
var c = cl.t.get();
for (f in c.fields.get())
superFields.set(f.name, true);
cl = c.superClass;
}
}
return superFields.get(name);
}
public function memberByName(name:String, ?pos:Position)
return
if (memberMap.exists(name)) Success(memberMap.get(name));
else pos.makeFailure('unknown member $name');
public function removeMember(member:Member):Bool
return
member != null
&&
memberMap.get(member.name) == member
&&
memberMap.remove(member.name)
&&
memberList.remove(member);
public function hasMember(name:String):Bool
return hasOwnMember(name) || hasSuperField(name);
public function addMember(m:Member, ?front:Bool = false):Member {
if (m.name == 'new')
throw 'Constructor must not be registered as ordinary member';
if (hasOwnMember(m.name))
m.pos.error('duplicate member declaration ' + m.name);
if (!m.isStatic && hasSuperField(m.name))
m.overrides = true;
memberMap.set(m.name, m);
if (front)
memberList.unshift(m);
else
memberList.push(m);
return m;
}
static public function run(plugins:Array<ClassBuilder->Void>, ?verbose) {
var builder = new ClassBuilder();
for (p in plugins)
p(builder);
return builder.export(verbose);
}
var memberList:Array<Member>;
var macros:Map<String,Field>;
var constructor:Null<Constructor>;
public var target(default, null):ClassType;
var superFields:Map<String,Bool>;
var initializeFrom:Array<Field>;
public function new(?target, ?fields) {
if (target == null)
target = Context.getLocalClass().get();
if (fields == null)
fields = Context.getBuildFields();
this.initializeFrom = fields;
this.target = target;
}
function init() {
if (initializeFrom == null) return;
var fields = initializeFrom;
initializeFrom = null;
this.memberList = [];
this.macros = new Map();
for (field in fields)
if (field.access.has(AMacro))
macros.set(field.name, field)
else if (field.name == 'new') {
var m:Member = field;
this.constructor = new Constructor(this, m.getFunction().sure(), m.isPublic, m.pos, field.meta);
}
else
doAddMember(field);
}
public function getConstructor(?fallback:Function):Constructor {
init();
if (constructor == null)
if (fallback != null)
constructor = new Constructor(this, fallback);
else {
var sup = target.superClass;
while (sup != null) {
var cl = sup.t.get();
if (cl.constructor != null) {
try {
var ctor = cl.constructor.get();
var func = Context.getTypedExpr(ctor.expr()).getFunction().sure();
for (arg in func.args) //this is to deal with type parameter substitutions
arg.type = null;
func.expr = "super".resolve().call(func.getArgIdents());
constructor = new Constructor(this, func);
if (ctor.isPublic)
constructor.publish();
}
catch (e:Dynamic) {//fails for unknown reason
if (e == 'assert')
neko.Lib.rethrow(e);
constructor = new Constructor(this, null);
}
break;
}
else sup = cl.superClass;
}
if (constructor == null)
constructor = new Constructor(this, null);
}
return constructor;
}
public function hasConstructor():Bool {
init();
return this.constructor != null;
}
public function export(?verbose):Array<Field> {
if (initializeFrom != null) return null;
var ret = (constructor == null || target.isInterface) ? [] : [constructor.toHaxe()];
for (member in memberList) {
if (member.isBound)
switch (member.kind) {//TODO: this seems like an awful place for a cleanup. If all else fails, this should go into a separate plugin (?)
case FVar(_, _): if (!member.isStatic) member.isBound = null;
case FProp(_, _, _, _): member.isBound = null;
default:
}
ret.push(member);
}
for (m in macros)
ret.push(m);
if (verbose)
for (field in ret)
Context.warning(new Printer().printField(field), field.pos);
return ret;
}
public function iterator():Iterator<Member> {
init();
return this.memberList.copy().iterator();
}
public function hasOwnMember(name:String):Bool {
init();
return
macros.exists(name) || memberByName(name).isSuccess();
}
public function hasSuperField(name:String):Bool {
if (superFields == null) {
superFields = new Map();
var cl = target.superClass;
while (cl != null) {
var c = cl.t.get();
for (f in c.fields.get())
superFields.set(f.name, true);
cl = c.superClass;
}
}
return superFields.get(name);
}
public function memberByName(name:String, ?pos:Position) {
init();
for (m in memberList)
if (m.name == name)
return Success(m);
return pos.makeFailure('unknown member $name');
}
public function removeMember(member:Member):Bool {
init();
return
memberList.remove(member);
}
public function hasMember(name:String):Bool
return hasOwnMember(name) || hasSuperField(name);
function doAddMember(m:Member, ?front:Bool = false):Member {
init();
if (m.name == 'new')
throw 'Constructor must not be registered as ordinary member';
//if (hasOwnMember(m.name))
//m.pos.error('duplicate member declaration ' + m.name);
if (front)
memberList.unshift(m);
else
memberList.push(m);
return m;
}
public function addMember(m:Member, ?front:Bool = false):Member {
doAddMember(m, front);
if (!m.isStatic && hasSuperField(m.name))
m.overrides = true;
return m;
}
static public function run(plugins:Array<ClassBuilder->Void>, ?verbose) {
var builder = new ClassBuilder();
for (p in plugins)
p(builder);
return builder.export(verbose);
}
}

View File

@@ -6,138 +6,130 @@ import tink.core.Pair;
using tink.MacroApi;
enum FieldInit {
Value(e:Expr);
Arg(?t:ComplexType, ?noPublish:Bool);
OptArg(?e:Expr, ?t:ComplexType, ?noPublish:Bool);
Value(e:Expr);
Arg(?t:ComplexType, ?noPublish:Bool);
OptArg(?e:Expr, ?t:ComplexType, ?noPublish:Bool);
}
class Constructor {
var oldStatements:Array<Expr>;
var nuStatements:Array<Expr>;
var beforeArgs:Array<FunctionArg>;
var args:Array<FunctionArg>;
var afterArgs:Array<FunctionArg>;
var pos:Position;
var onGenerateHooks:Array<Function->Void>;
var superCall:Expr;
var owner:ClassBuilder;
var keepers:Array<Expr>;//hack to force field generation
public var isPublic:Null<Bool>;
public function new(owner:ClassBuilder, f:Function, ?isPublic:Null<Bool> = null, ?pos:Position, ?meta:Metadata) {
this.nuStatements = [];
this.owner = owner;
this.isPublic = isPublic;
this.pos = pos.sanitize();
this.onGenerateHooks = [];
this.args = [];
this.beforeArgs = [];
this.afterArgs = [];
this.keepers = [];
this.oldStatements =
if (f == null) [];
else {
for (i in 0...f.args.length) {
var a = f.args[i];
if (a.name == '_') {
afterArgs = f.args.slice(i + 1);
break;
}
beforeArgs.push(a);
}
if (f.expr == null) [];
else
switch (f.expr.expr) {
case EBlock(exprs): exprs;
default: oldStatements = [f.expr];
}
}
superCall =
if (oldStatements.length == 0) [].toBlock();
else switch oldStatements[0] {
case macro super($a{_}): oldStatements.shift();
default: [].toBlock();
}
}
public function addStatement(e:Expr, ?prepend)
if (prepend)
this.nuStatements.unshift(e)
else
this.nuStatements.push(e);
public function addArg(name:String, ?t:ComplexType, ?e:Expr, ?opt = false)
args.push( { name : name, opt : opt || e != null, type : t, value: e } );
public function init(name:String, pos:Position, with:FieldInit, ?options:{ ?prepend:Bool, ?bypass:Bool }) {
if (options == null)
options = {};
var e =
switch with {
case Arg(t, noPublish):
if (noPublish != true)
publish();
args.push( { name : name, opt : false, type : t } );
name.resolve(pos);
case OptArg(e, t, noPublish):
if (noPublish != true)
publish();
args.push( { name : name, opt : true, type : t, value: e } );
name.resolve(pos);
case Value(e): e;
}
var tmp = MacroApi.tempName();
if (options.bypass) {
switch owner.memberByName(name) {
case Success(member): member.addMeta(':isVar');
default:
}
if (Context.defined('dce') && Context.definedValue('dce') == 'full') {
if (keepers.length == 0)
keepers.push(macro return);
keepers.push(macro this.$name = $e);
}
addStatement(macro @:pos(pos) if (false) { var $tmp = this.$name; $i{tmp} = $e; }, true);
addStatement(macro @:pos(pos) (cast this).$name = $e, options.prepend);
}
else
addStatement(macro @:pos(pos) this.$name = $e, options.prepend);
}
public inline function publish()
if (isPublic == null)
isPublic = true;
function toBlock()
return [superCall]
.concat(nuStatements)
.concat(oldStatements)
.concat(keepers)
.toBlock(pos);
public function onGenerate(hook)
this.onGenerateHooks.push(hook);
public function toHaxe():Field {
var f:Function = {
args: this.beforeArgs.concat(this.args).concat(this.afterArgs),
ret: 'Void'.asComplexType(),
expr: toBlock(),
params: []
};
for (hook in onGenerateHooks) hook(f);
onGenerateHooks = [];
return {
name: 'new',
doc : null,
access : isPublic ? [APublic] : [],
kind : FFun(f),
pos : pos,
meta : []
}
}
var oldStatements:Array<Expr>;
var nuStatements:Array<Expr>;
var beforeArgs:Array<FunctionArg>;
var args:Array<FunctionArg>;
var afterArgs:Array<FunctionArg>;
var pos:Position;
var onGenerateHooks:Array<Function->Void>;
var superCall:Expr;
var owner:ClassBuilder;
var meta:Metadata;
public var isPublic:Null<Bool>;
public function new(owner:ClassBuilder, f:Function, ?isPublic:Null<Bool> = null, ?pos:Position, ?meta:Metadata) {
this.nuStatements = [];
this.owner = owner;
this.isPublic = isPublic;
this.pos = pos.sanitize();
this.onGenerateHooks = [];
this.args = [];
this.beforeArgs = [];
this.afterArgs = [];
this.meta = meta;
this.oldStatements =
if (f == null) [];
else {
for (i in 0...f.args.length) {
var a = f.args[i];
if (a.name == '_') {
afterArgs = f.args.slice(i + 1);
break;
}
beforeArgs.push(a);
}
if (f.expr == null) [];
else
switch (f.expr.expr) {
case EBlock(exprs): exprs;
default: oldStatements = [f.expr];
}
}
superCall =
if (oldStatements.length == 0) [].toBlock();
else switch oldStatements[0] {
case macro super($a{_}): oldStatements.shift();
default: [].toBlock();
}
}
public function addStatement(e:Expr, ?prepend)
if (prepend)
this.nuStatements.unshift(e)
else
this.nuStatements.push(e);
public function addArg(name:String, ?t:ComplexType, ?e:Expr, ?opt = false)
args.push( { name : name, opt : opt || e != null, type : t, value: e } );
public function init(name:String, pos:Position, with:FieldInit, ?options:{ ?prepend:Bool, ?bypass:Bool }) {
if (options == null)
options = {};
var e =
switch with {
case Arg(t, noPublish):
if (noPublish != true)
publish();
args.push( { name : name, opt : false, type : t } );
name.resolve(pos);
case OptArg(e, t, noPublish):
if (noPublish != true)
publish();
args.push( { name : name, opt : true, type : t, value: e } );
name.resolve(pos);
case Value(e): e;
}
var tmp = MacroApi.tempName();
if (options.bypass) {
switch owner.memberByName(name) {
case Success(member): member.addMeta(':isVar');
default:
}
addStatement(macro @:pos(pos) (cast this).$name = if (true) $e else this.$name, options.prepend);
}
else
addStatement(macro @:pos(pos) this.$name = $e, options.prepend);
}
public inline function publish()
if (isPublic == null)
isPublic = true;
function toBlock()
return [superCall]
.concat(nuStatements)
.concat(oldStatements)
.toBlock(pos);
public function onGenerate(hook)
this.onGenerateHooks.push(hook);
public function toHaxe():Field {
var f:Function = {
args: this.beforeArgs.concat(this.args).concat(this.afterArgs),
ret: 'Void'.asComplexType(),
expr: toBlock(),
params: []
};
for (hook in onGenerateHooks) hook(f);
onGenerateHooks = [];
return {
name: 'new',
doc : null,
access : isPublic ? [APublic] : [],
kind : FFun(f),
pos : pos,
meta : this.meta,
}
}
}

View File

@@ -0,0 +1,6 @@
package tink.macro;
@:genericBuild(tink.macro.Types.resolveDirectType())
class DirectType<Const> {
}

View File

@@ -14,524 +14,384 @@ using StringTools;
using tink.macro.Positions;
using tink.macro.Exprs;
using tink.macro.Types;
using tink.core.Outcome;
using tink.CoreApi;
typedef VarDecl = { name : String, type : ComplexType, expr : Null<Expr> };
typedef ParamSubst = {
var exists(default, null):String->Bool;
var get(default, null):String->ComplexType;
var exists(default, null):String->Bool;
var get(default, null):String->ComplexType;
}
private class Heureka { public function new() {} }
class Exprs {
static public function has(e:Expr, condition:Expr->Bool, ?options: { ?enterFunctions: Bool }) {
var enterFunctions = options != null && options.enterFunctions;
function seek(e:Expr)
switch e {
case { expr: EFunction(_) } if (options != null || options.enterFunctions != true):
case _ if (condition(e)): throw new Heureka();
default: haxe.macro.ExprTools.iter(e, seek);
}
return try {
haxe.macro.ExprTools.iter(e, seek);
false;
}
catch (e:Heureka) true;
}
static public function has(e:Expr, condition:Expr->Bool, ?options: { ?enterFunctions: Bool }) {
var skipFunctions = options == null || options.enterFunctions != true;
function seek(e:Expr)
switch e {
case { expr: EFunction(_) } if (skipFunctions):
case _ if (condition(e)): throw new Heureka();
default: haxe.macro.ExprTools.iter(e, seek);
}
return try {
haxe.macro.ExprTools.iter(e, seek);
false;
}
catch (e:Heureka) true;
}
static public inline function is(e:Expr, c:ComplexType)
return ECheckType(e, c).at(e.pos).typeof().isSuccess();
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, MacroApi.tempName(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 withPrivateAccess(e:Expr)
return
e.transform(function (e:Expr)
return
switch (e.expr) {
case EField(owner, field):
getPrivate(owner, field, e.pos);//TODO: this needs to leave types untouched
default: e;
}
);
static public function getPrivate(e:Expr, field:String, ?pos:Position)
return macro @:pos(pos.sanitize()) @:privateAccess $e.$field;
static public inline function is(e:Expr, c:ComplexType)
return ECheckType(e, c).at(e.pos).typeof().isSuccess();
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, MacroApi.tempName(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 withPrivateAccess(e:Expr)
return
e.transform(function (e:Expr)
return
switch (e.expr) {
case EField(owner, field):
getPrivate(owner, field, e.pos);//TODO: this needs to leave types untouched
default: e;
}
);
static public function getPrivate(e:Expr, field:String, ?pos:Position)
return macro @:pos(pos.sanitize()) @:privateAccess $e.$field;
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
switch e {
case macro null: fallback;
default: e;
}
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr
return crawl(
source,
function (e)
return switch e.expr {
case ENew({ pack: [], name: name }, args) if (subst.exists(name)):
switch subst.get(name) {
case TPath(p):
ENew(p, args).at(e.pos);
default: e;//TODO: report an error?
}
case EConst(CIdent(name)) if (subst.exists(name)):
switch subst.get(name) {
case TPath({ pack: pack, name: name }):
pack.concat([name]).drill(e.pos);
default: e;//TODO: report an error?
}
default: e;
},
function (c:ComplexType)
return
switch (c) {
case TPath({ pack: [], name: name }) if (subst.exists(name)):
subst.get(name);
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>
return
if (a == null) a;
else
[for (v in a)
crawl(v, transformer, retyper, pos)
];
static public function getIterType(target:Expr)
return
(macro @:pos(target.pos) {
var t = null,
target = $target;
for (i in target)
t = i;
t;
}).typeof();
static public function yield(e:Expr, yielder:Expr->Expr, ?options: { ?leaveLoops: Bool }):Expr {
inline function rec(e)
return yield(e, yielder, options);
return
if (e == null || e.expr == null) e;
else switch (e.expr) {
case EVars(_):
e.pos.error('Variable declaration not supported here');
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) if (options == null || options.leaveLoops != true):
EFor(it, rec(expr)).at(e.pos);
case EWhile(cond, body, normal) if (options == null || options.leaveLoops != true):
EWhile(cond, rec(body), normal).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):
var ret:Dynamic = Inspect.createEnumIndex(e, Inspect.enumIndex(target), crawlArray(Inspect.enumParameters(target), transformer, retyper, pos));
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;
}
//TODO: this whole thing needs an overhaul
static public function typedMap(source:Expr, f:Expr->Array<VarDecl>->Expr, ?ctx:Array<VarDecl>, ?pos:Position):Expr {
if (ctx == null) ctx = [];
function rec(e, ?inner)
return typedMap(e, f, inner == null ? ctx : inner, pos);
function def(name:String, ?e:Expr, ?t:ComplexType)
return { name: name, expr: e, type: t };
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):
EObjectDecl([for (field in fields) {
field: field.field,
expr: rec(field.expr)
}]);
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)
newCases.push({
expr: rec(c.expr),
values: [for (v in c.values) rec(v)]
});
}
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):
ETry(
rec(e),
[for (c in catches) {
name: c.name,
expr: rec(c.expr, ctx.concat([def(c.name, c.type)])),
type: c.type
}]
);
case EFunction(name, func):
func.expr = rec(func.expr, ctx.concat([for (arg in func.args) def(arg.name, arg.type)]));
EFunction(name, func);
case EVars(vars):
//This is incorrect. When declaring multiple variables, they are declared in the previous context "at once"
var ret = [];
for (v in vars) {
var vExpr = v.expr == null ? null : typedMap(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)
return [for (e in source) typedMap(e, f, ctx, pos)];
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)
return EObjectDecl([for (field in Reflect.fields(object))
{ field:field, expr: untyped Reflect.field(object, field) }
]).at(pos);
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
switch e {
case macro null: fallback;
default: e;
}
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr
return crawl(
source,
function (e)
return switch e.expr {
case ENew({ pack: [], name: name }, args) if (subst.exists(name)):
switch subst.get(name) {
case TPath(p):
ENew(p, args).at(e.pos);
default: e;//TODO: report an error?
}
case EConst(CIdent(name)) if (subst.exists(name)):
switch subst.get(name) {
case TPath({ pack: pack, name: name }):
pack.concat([name]).drill(e.pos);
default: e;//TODO: report an error?
}
default: e;
},
function (c:ComplexType)
return
switch (c) {
case TPath({ pack: [], name: name }) if (subst.exists(name)):
subst.get(name);
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>
return
if (a == null) a;
else
[for (v in a)
crawl(v, transformer, retyper, pos)
];
static public function getIterType(target:Expr)
return
(macro @:pos(target.pos) {
var t = null,
target = $target;
for (i in target)
t = i;
t;
}).typeof();
static public function yield(e:Expr, yielder:Expr->Expr, ?options: { ?leaveLoops: Bool }):Expr {
inline function rec(e)
return yield(e, yielder, options);
if (options == null)
options = { };
var loops = options.leaveLoops != true;
return
if (e == null || e.expr == null) e;
else switch (e.expr) {
case EVars(_):
e.pos.error('Variable declaration not supported here');
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) if (loops):
EFor(it, rec(expr)).at(e.pos);
case EWhile(cond, body, normal) if (loops):
EWhile(cond, rec(body), normal).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):
var ret:Dynamic = Inspect.createEnumIndex(e, Inspect.enumIndex(target), crawlArray(Inspect.enumParameters(target), transformer, retyper, pos));
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 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)
return EObjectDecl([for (field in Reflect.fields(object))
{ field:field, expr: untyped Reflect.field(object, field) }
]).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 new haxe.macro.Printer().printExpr(e);
static public inline function at(e:ExprDef, ?pos:Position)
return {
expr: e,
pos: pos.sanitize()
};
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:Expr, e2, ?pos)
return binOp(e1, e2, OpAdd, pos);
static public inline function unOp(e:Expr, op, ?postFix = false, ?pos)
return EUnop(op, postFix, e).at(pos);
static public inline function binOp(e1:Expr, e2, op, ?pos)
return EBinop(op, e1, e2).at(pos);
static public inline function field(e:Expr, field, ?pos)
return EField(e, field).at(pos);
static public inline function call(e:Expr, ?params, ?pos)
return ECall(e, params == null ? [] : params).at(pos);
static public inline function toExpr(v:Dynamic, ?pos:Position)
return Context.makeExpr(v, pos.sanitize());
static public inline function toArray(exprs:Iterable<Expr>, ?pos)
return EArrayDecl(exprs.array()).at(pos);
static public inline function toMBlock(exprs:Array<Expr>, ?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 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 {
case macro _: true;
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 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 new haxe.macro.Printer().printExpr(e);
static public inline function at(e:ExprDef, ?pos:Position)
return {
expr: e,
pos: pos.sanitize()
};
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:Expr, e2, ?pos)
return binOp(e1, e2, OpAdd, pos);
static public inline function unOp(e:Expr, op, ?postFix = false, ?pos)
return EUnop(op, postFix, e).at(pos);
static public inline function binOp(e1:Expr, e2, op, ?pos)
return EBinop(op, e1, e2).at(pos);
static public inline function field(e:Expr, field, ?pos)
return EField(e, field).at(pos);
static public inline function call(e:Expr, ?params, ?pos)
return ECall(e, params == null ? [] : params).at(pos);
static public inline function toExpr(v:Dynamic, ?pos:Position)
return Context.makeExpr(v, pos.sanitize());
static public inline function toArray(exprs:Iterable<Expr>, ?pos)
return EArrayDecl(exprs.array()).at(pos);
static public inline function toMBlock(exprs:Array<Expr>, ?pos)
return EBlock(exprs).at(pos);
static public inline function toBlock(exprs:Iterable<Expr>, ?pos)
return toMBlock(Lambda.array(exprs), pos);
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 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 {
case macro _: true;
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";
}

View File

@@ -5,29 +5,29 @@ 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;
}
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

@@ -4,190 +4,190 @@ import haxe.macro.Expr;
using tink.MacroApi;
abstract Member(Field) from Field to Field {
static public function prop(name:String, t:ComplexType, pos, ?noread = false, ?nowrite = false):Member {
var ret:Field = {
name: name,
pos: pos,
access: [APublic],
kind: FProp(noread ? 'null' : 'get_' + name, nowrite ? 'null' : ('set_' + name), t),
}
return ret;
}
static public function getter(field:String, ?pos, e:Expr, ?t:ComplexType)
return method('get_' + field, pos, false, e.func(t));
static public function setter(field:String, ?param = 'param', ?pos, e:Expr, ?t:ComplexType)
return method('set_' + field, pos, false, [e, param.resolve(pos)].toBlock(pos).func([param.toArg(t)], t));
static public function method(name:String, ?pos, ?isPublic = true, f:Function) {
var f:Field = {
name: name,
pos: if (pos == null) f.expr.pos else pos,
kind: FFun(f)
};
var ret:Member = f;
ret.isPublic = isPublic;
return ret;
}
public var name(get, set):String;
public var doc(get, set):Null<String>;
public var kind(get, set):FieldType;
public var pos(get, set):Position;
public var overrides(get, set):Bool;
public var isStatic(get, set):Bool;
public var isPublic(get, set):Null<Bool>;
public var isBound(get, set):Null<Bool>;
public function getFunction()
return
switch kind {
case FFun(f): Success(f);
default: pos.makeFailure('Field should be function');
}
public function getVar(?pure = false)
return
switch kind {
case FVar(t, e): Success({ get: 'default', set: 'default', type: t, expr: e });
case FProp(get, set, t, e) if (!pure): Success({ get: get, set: set, type: t, expr: e });
default: pos.makeFailure('Field should be a variable ' + if (pure) '' else 'or property');
}
public function addMeta(name, ?pos, ?params):Member {
if (this.meta == null)
this.meta = [];
this.meta.push({
name: name,
pos: if (pos == null) this.pos else pos,
params: if (params == null) [] else params
});
return this;
}
public function extractMeta(name) {
if (this.meta != null)
for (tag in this.meta) {
if (tag.name == name) {
this.meta.remove(tag);
return Success(tag);
}
}
return pos.makeFailure('missing @$name');
}
public inline function asField():Field return this;
public function publish()
if (this.access == null) this.access = [APublic];
else {
for (a in this.access)
if (a == APrivate || a == APublic) return;
this.access.push(APublic);
}
inline function get_name() return this.name;
inline function set_name(param) return this.name = param;
inline function get_doc() return this.doc;
inline function set_doc(param) return this.doc = param;
inline function get_kind() return this.kind;
inline function set_kind(param) return this.kind = param;
inline function get_pos() return this.pos;
inline function set_pos(param) return this.pos = param;
inline function get_overrides() return hasAccess(AOverride);
inline function set_overrides(param) {
changeAccess(
param ? AOverride : null,
param ? null : AOverride
);
return param;
}
inline function get_isStatic() return hasAccess(AStatic);
function set_isStatic(param) {
changeAccess(
param ? AStatic : null,
param ? null : AStatic
);
return param;
}
function get_isPublic() {
if (this.access != null)
for (a in this.access)
switch a {
case APublic: return true;
case APrivate: return false;
default:
}
return null;
}
function set_isPublic(param) {
if (param == null) {
changeAccess(null, APublic);
changeAccess(null, APrivate);
}
else if (param)
changeAccess(APublic, APrivate);
else
changeAccess(APrivate, APublic);
return param;
}
function get_isBound() {
if (this.access != null)
for (a in this.access)
switch a {
case AInline: return true;
case ADynamic: return false;
default:
}
return null;
}
function set_isBound(param) {
if (param == null) {
changeAccess(null, AInline);
changeAccess(null, ADynamic);
}
else if (param)
changeAccess(AInline, ADynamic);
else
changeAccess(ADynamic, AInline);
return param;
}
//TODO: add some sanitazation stuff to normalize / report invalid access combinations
function hasAccess(a:Access) {
if (this.access != null)
for (x in this.access)
if (x == a) return true;
return false;
}
function changeAccess(add:Access, remove:Access) {
var i = 0;
if (this.access == null)
this.access = [];
while (i < this.access.length) {
var a = this.access[i];
if (a == remove) {
this.access.splice(i, 1);
if (add == null) return;
remove = null;
}
else {
i++;
if (a == add) {
add = null;
if (remove == null) return;
}
}
}
if (add != null)
this.access.push(add);
}
static public function prop(name:String, t:ComplexType, pos, ?noread = false, ?nowrite = false):Member {
var ret:Field = {
name: name,
pos: pos,
access: [APublic],
kind: FProp(noread ? 'null' : 'get_' + name, nowrite ? 'null' : ('set_' + name), t),
}
return ret;
}
static public function getter(field:String, ?pos, e:Expr, ?t:ComplexType)
return method('get_' + field, pos, false, e.func(t));
static public function setter(field:String, ?param = 'param', ?pos, e:Expr, ?t:ComplexType)
return method('set_' + field, pos, false, [e, param.resolve(pos)].toBlock(pos).func([param.toArg(t)], t));
static public function method(name:String, ?pos, ?isPublic = true, f:Function) {
var f:Field = {
name: name,
pos: if (pos == null) f.expr.pos else pos,
kind: FFun(f)
};
var ret:Member = f;
ret.isPublic = isPublic;
return ret;
}
public var name(get, set):String;
public var doc(get, set):Null<String>;
public var kind(get, set):FieldType;
public var pos(get, set):Position;
public var overrides(get, set):Bool;
public var isStatic(get, set):Bool;
public var isPublic(get, set):Null<Bool>;
public var isBound(get, set):Null<Bool>;
public function getFunction()
return
switch kind {
case FFun(f): Success(f);
default: pos.makeFailure('Field should be function');
}
public function getVar(?pure = false)
return
switch kind {
case FVar(t, e): Success({ get: 'default', set: 'default', type: t, expr: e });
case FProp(get, set, t, e) if (!pure): Success({ get: get, set: set, type: t, expr: e });
default: pos.makeFailure('Field should be a variable ' + if (pure) '' else 'or property');
}
public function addMeta(name, ?pos, ?params):Member {
if (this.meta == null)
this.meta = [];
this.meta.push({
name: name,
pos: if (pos == null) this.pos else pos,
params: if (params == null) [] else params
});
return this;
}
public function extractMeta(name) {
if (this.meta != null)
for (tag in this.meta) {
if (tag.name == name) {
this.meta.remove(tag);
return Success(tag);
}
}
return pos.makeFailure('missing @$name');
}
public inline function asField():Field return this;
public function publish()
if (this.access == null) this.access = [APublic];
else {
for (a in this.access)
if (a == APrivate || a == APublic) return;
this.access.push(APublic);
}
inline function get_name() return this.name;
inline function set_name(param) return this.name = param;
inline function get_doc() return this.doc;
inline function set_doc(param) return this.doc = param;
inline function get_kind() return this.kind;
inline function set_kind(param) return this.kind = param;
inline function get_pos() return this.pos;
inline function set_pos(param) return this.pos = param;
inline function get_overrides() return hasAccess(AOverride);
inline function set_overrides(param) {
changeAccess(
param ? AOverride : null,
param ? null : AOverride
);
return param;
}
inline function get_isStatic() return hasAccess(AStatic);
function set_isStatic(param) {
changeAccess(
param ? AStatic : null,
param ? null : AStatic
);
return param;
}
function get_isPublic() {
if (this.access != null)
for (a in this.access)
switch a {
case APublic: return true;
case APrivate: return false;
default:
}
return null;
}
function set_isPublic(param) {
if (param == null) {
changeAccess(null, APublic);
changeAccess(null, APrivate);
}
else if (param)
changeAccess(APublic, APrivate);
else
changeAccess(APrivate, APublic);
return param;
}
function get_isBound() {
if (this.access != null)
for (a in this.access)
switch a {
case AInline: return true;
case ADynamic: return false;
default:
}
return null;
}
function set_isBound(param) {
if (param == null) {
changeAccess(null, AInline);
changeAccess(null, ADynamic);
}
else if (param)
changeAccess(AInline, ADynamic);
else
changeAccess(ADynamic, AInline);
return param;
}
//TODO: add some sanitazation stuff to normalize / report invalid access combinations
function hasAccess(a:Access) {
if (this.access != null)
for (x in this.access)
if (x == a) return true;
return false;
}
function changeAccess(add:Access, remove:Access) {
var i = 0;
if (this.access == null)
this.access = [];
while (i < this.access.length) {
var a = this.access[i];
if (a == remove) {
this.access.splice(i, 1);
if (add == null) return;
remove = null;
}
else {
i++;
if (a == add) {
add = null;
if (remove == null) return;
}
}
}
if (add != null)
this.access.push(add);
}
}

View File

@@ -2,20 +2,20 @@ package tink.macro;
import haxe.macro.Expr;
class Metadatas {
static public function toMap(m:Metadata) {
var ret = new Map<String,Array<Array<Expr>>>();
if (m != null)
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
if (m == null) [];
else [for (meta in m) if (meta.name == name) meta.params];
class Metadatas {
static public function toMap(m:Metadata) {
var ret = new Map<String,Array<Array<Expr>>>();
if (m != null)
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
if (m == null) [];
else [for (meta in m) if (meta.name == name) meta.params];
}

View File

@@ -5,55 +5,55 @@ import haxe.macro.Expr;
using tink.MacroApi;
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);
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 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 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);
static public function make(op:Unop, e:Expr, ?postFix = false, ?pos)
return EUnop(op, postFix, e).at(pos);
}

View File

@@ -8,47 +8,47 @@ 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): sanitize(pos).error(f);
}
static public function makeBlankType(pos:Position):ComplexType
return Types.toComplex(Context.typeof(macro @:pos(pos.sanitize()) null));
static public inline function sanitize(pos:Position)
return
if (pos == null)
Context.currentPos();
else
pos;
static public function getOutcome<D, F>(pos:Position, outcome:Outcome<D, F>):D
return
switch outcome {
case Success(d): d;
case Failure(f): sanitize(pos).error(f);
}
static public function makeBlankType(pos:Position):ComplexType
return Types.toComplex(Context.typeof(macro @:pos(pos.sanitize()) null));
static public inline function sanitize(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 function errorExpr(pos:Position, error:Dynamic)
return Bouncer.bounce(function ():Expr {
return Positions.error(pos, error);
}, pos);
static public function error(pos:Position, error:Dynamic):Dynamic
return errorFunc(sanitize(pos), Std.string(error));
static function contextError(pos:Position, error:String):Dynamic
return Context.error(error, pos);
static function abortTypeBuild(pos:Position, error:String):Dynamic
return throw new AbortBuild(error, pos);
static var errorFunc = contextError;
static public inline function warning<A>(pos:Position, warning:Dynamic, ?ret:A):A {
Context.warning(Std.string(warning), pos);
return ret;
}
static public function error(pos:Position, error:Dynamic):Dynamic
return errorFunc(sanitize(pos), Std.string(error));
static function contextError(pos:Position, error:String):Dynamic
return Context.error(error, pos);
static function abortTypeBuild(pos:Position, error:String):Dynamic
return throw new AbortBuild(error, pos);
static var errorFunc = contextError;
static public inline function warning<A>(pos:Position, warning:Dynamic, ?ret:A):A {
Context.warning(Std.string(warning), pos);
return ret;
}
static public function makeFailure<A>(pos:Position, reason:String):Outcome<A, Error>
return Failure(new Error(reason, pos));
static public function makeFailure<A>(pos:Position, reason:String):Outcome<A, Error>
return Failure(new Error(reason, pos));
}
class AbortBuild extends tink.core.Error {

32
src/tink/macro/TypeMap.hx Normal file
View File

@@ -0,0 +1,32 @@
package tink.macro;
import haxe.Constraints.IMap;
import haxe.ds.BalancedTree;
import haxe.macro.Context;
import haxe.macro.Type;
using haxe.macro.Tools;
using tink.MacroApi;
class TypeMap<V> extends BalancedTree<Type, V> implements IMap<Type, V> {
var follow:Bool;
public function new(?noFollow:Bool) {
this.follow = noFollow != true;
super();
}
override function compare(k1:Type, k2:Type):Int {
if (follow) {
k1 = k1.reduce();
k2 = k2.reduce();
}
//trace(k1.toString());
//trace(k2.toString());
return switch k1.getIndex() - k2.getIndex() {
case 0: Reflect.compare(k1.toString(), k2.toString());//TODO: this may be rather expensive and not very reliable
case v: v;
}
}
}

View File

@@ -10,161 +10,171 @@ import haxe.macro.Type;
using tink.macro.Exprs;
using tink.macro.Positions;
using tink.macro.Functions;
using tink.core.Outcome;
using tink.CoreApi;
class Types {
static var types = new Map<Int,Void->Type>();
static var idCounter = 0;
@:noUsing macro static public function getType(id:Int):Type
return types.get(id)();
static public function getID(t:Type, ?reduced = true)
return
if (reduced)
getID(reduce(t), false);
else
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 var types = new Map<Int,Void->Type>();
static var idCounter = 0;
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, params):
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());
for (field in fields)
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params);
}
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(macro null, 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:Position)
return (macro @:pos(pos.sanitize()) {
var v:$t = null;
v;
}).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 {
types.set(idCounter, type);
return idCounter++;
}
static function paramsToComplex(params:Array<Type>):Array<TypeParam>
return [for (p in params) TPType(toComplex(p))];
static function baseToComplex(t:BaseType, params:Array<Type>)
return asComplexType(t.module + '.' + t.name, paramsToComplex(params));
static public function toComplex(type:Type, ?options:{ ?direct: Bool }):ComplexType {
var ret =
if (options == null || options.direct != true) haxe.macro.TypeTools.toComplexType(type);
else null;
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.Types.getType'.resolve().call([register(f).toExpr()]))],
sub : null,
});
}
//@:noUsing macro static public function getType(id:Int):Type
//return types.get(id)();
static public function getID(t:Type, ?reduced = true)
return
if (reduced)
getID(reduce(t), false);
else
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, params):
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 fields = Reflect.copy(ret.sure());
for (field in fields)
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params);
}
fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache
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(macro null, 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:Position)
return (macro @:pos(pos.sanitize()) {
var v:$t = null;
v;
}).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 {
types.set(idCounter, type);
return idCounter++;
}
static function paramsToComplex(params:Array<Type>):Array<TypeParam>
return [for (p in params) TPType(toComplex(p))];
static function baseToComplex(t:BaseType, params:Array<Type>)
return asComplexType(t.module + '.' + t.name, paramsToComplex(params));
static public function toComplex(type:Type, ?options:{ ?direct: Bool }):ComplexType {
var ret =
if (options == null || options.direct != true) haxe.macro.TypeTools.toComplexType(type);
else null;
if (ret == null)
ret = lazyComplex(function () return type);
return ret;
}
static public function lazyComplex(f:Void->Type)
return
TPath({
pack : ['tink','macro'],
name : 'DirectType',
params : [TPExpr(register(f).toExpr())],
sub : null,
});
static function resolveDirectType()
return
switch reduce(Context.getLocalType()) {
case TInst(_, [TInst(_.get() => { kind: KExpr(e) }, _)]):
types[e.getInt().sure()]();
default:
throw 'assert';
}
}

View File

@@ -1,7 +1,2 @@
-cp ../tink_core/src
-cp src
-cp tests
-D tink_macro
-main Run
-neko bin/tests.n
#iota start Run.bat
-main Run

View File

@@ -1,5 +1,5 @@
package ;
class Arrayish implements ArrayAccess<Int> {
public var length:Int;
public var length:Int;
}

View File

@@ -7,44 +7,44 @@ 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): Type.getClassName(c);
case Right(e): 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));
function new(v) this = v;
public function toString()
return
switch this {
case Left(c): Type.getClassName(c);
case Right(e): 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 != null && !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);
}
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 != null && !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);
}
}

View File

@@ -4,80 +4,80 @@ import haxe.macro.Expr;
using tink.MacroApi;
class Exprs extends Base {
function exprEq(e1:Expr, e2:Expr) {
assertEquals(e1.toString(), e2.toString());
}
function testGet() {
assertEquals('foo', (macro foo).getIdent().sure());
assertEquals('foo', (macro "foo").getString().sure());
assertEquals('foo', (macro foo).getName().sure());
assertEquals('foo', (macro "foo").getName().sure());
assertEquals(5, (macro 5).getInt().sure());
exprEq(macro [a, b, c], (macro function (a, b, c) [a, b, c]).getFunction().sure().expr);
assertEquals('a,b,c', [for (arg in (macro function (a, b, c) [a, b, c]).getFunction().sure().args) arg.name].join(','));
assertFalse((macro 'foo').getIdent().isSuccess());
assertFalse((macro foo).getString().isSuccess());
assertFalse((macro 5).getName().isSuccess());
assertFalse((macro 5.1).getInt().isSuccess());
assertFalse((macro foo).getFunction().isSuccess());
}
function testShortcuts() {
assertTrue(true);
}
function testIterType() {
assertEquals('Int', (macro [1, 2]).getIterType().sure().getID());
assertEquals('Int', (macro [1, 2].iterator()).getIterType().sure().getID());
assertEquals('Int', ECheckType(macro null, macro: Arrayish).at().getIterType().sure().getID());
}
function testYield() {
function yielder(e) return macro @yield $e;
function test(x:Expr, e:Expr, ?options)
exprEq(x, e.yield(yielder, options));
test(macro @yield foo, macro foo);
test(macro @yield (foo), macro (foo));
test(macro for (_) @yield foo, macro for (_) foo);
test(macro while (_) @yield foo, macro while (_) foo);
test(macro @yield while (_) foo, macro while (_) foo, { leaveLoops: true });
test(macro @yield [while (_) foo], macro [while (_) foo]);
}
function testSubstitute() {
exprEq(
macro foo.call(arg1, arg2),
(macro bar.call(x, y)).substitute({ x: macro arg1, y: macro arg2, bar: macro foo })
);
exprEq(
macro {
var x:Map<Int, String> = new Map(),
y:Array<Float> = [];
},
(macro {
var x:Map<A, B> = new Map(),
y:C = [];
}).substParams([
'A' => macro : Int,
'B' => macro : String,
'C' => macro : Array<Float>
])
);
exprEq(
macro {
new Foo<Bar>(1, 2, 3);
Bar.foo();
},
(macro {
new X(1, 2, 3);
Y.foo();
}).substParams([
'X' => macro : Foo<Bar>,
'Y' => macro : Bar
])
);
}
function exprEq(e1:Expr, e2:Expr) {
assertEquals(e1.toString(), e2.toString());
}
function testGet() {
assertEquals('foo', (macro foo).getIdent().sure());
assertEquals('foo', (macro "foo").getString().sure());
assertEquals('foo', (macro foo).getName().sure());
assertEquals('foo', (macro "foo").getName().sure());
assertEquals(5, (macro 5).getInt().sure());
exprEq(macro [a, b, c], (macro function (a, b, c) [a, b, c]).getFunction().sure().expr);
assertEquals('a,b,c', [for (arg in (macro function (a, b, c) [a, b, c]).getFunction().sure().args) arg.name].join(','));
assertFalse((macro 'foo').getIdent().isSuccess());
assertFalse((macro foo).getString().isSuccess());
assertFalse((macro 5).getName().isSuccess());
assertFalse((macro 5.1).getInt().isSuccess());
assertFalse((macro foo).getFunction().isSuccess());
}
function testShortcuts() {
assertTrue(true);
}
function testIterType() {
assertEquals('Int', (macro [1, 2]).getIterType().sure().getID());
assertEquals('Int', (macro [1, 2].iterator()).getIterType().sure().getID());
assertEquals('Int', ECheckType(macro null, macro: Arrayish).at().getIterType().sure().getID());
}
function testYield() {
function yielder(e) return macro @yield $e;
function test(x:Expr, e:Expr, ?options)
exprEq(x, e.yield(yielder, options));
test(macro @yield foo, macro foo);
test(macro @yield (foo), macro (foo));
test(macro for (_) @yield foo, macro for (_) foo);
test(macro while (_) @yield foo, macro while (_) foo);
test(macro @yield while (_) foo, macro while (_) foo, { leaveLoops: true });
test(macro @yield [while (_) foo], macro [while (_) foo]);
}
function testSubstitute() {
exprEq(
macro foo.call(arg1, arg2),
(macro bar.call(x, y)).substitute({ x: macro arg1, y: macro arg2, bar: macro foo })
);
exprEq(
macro {
var x:Map<Int, String> = new Map(),
y:Array<Float> = [];
},
(macro {
var x:Map<A, B> = new Map(),
y:C = [];
}).substParams([
'A' => macro : Int,
'B' => macro : String,
'C' => macro : Array<Float>
])
);
exprEq(
macro {
new Foo<Bar>(1, 2, 3);
Bar.foo();
},
(macro {
new X(1, 2, 3);
Y.foo();
}).substParams([
'X' => macro : Foo<Bar>,
'Y' => macro : Bar
])
);
}
}

View File

@@ -6,19 +6,19 @@ import haxe.macro.Expr;
using tink.MacroApi;
class Positions extends Base {
function stringCompare<A>(v1:A, v2:A)
assertEquals(Std.string(v1), Std.string(v2));
function testSanitize() {
var p:Position = null;
stringCompare(Context.currentPos(), p.sanitize());
p = Context.makePosition({ min: 0, max: 10, file: 'foo.txt' });
stringCompare(p, p);
}
function testBlank() {
var p:Position = null;
var t = p.makeBlankType();
stringCompare('TMono(<mono>)', cast t.toType().sure());
}
function stringCompare<A>(v1:A, v2:A)
assertEquals(Std.string(v1), Std.string(v2));
function testSanitize() {
var p:Position = null;
stringCompare(Context.currentPos(), p.sanitize());
p = Context.makePosition({ min: 0, max: 10, file: 'foo.txt' });
stringCompare(p, p);
}
function testBlank() {
var p:Position = null;
var t = p.makeBlankType();
stringCompare('TMono(<mono>)', cast t.toType().sure());
}
}

View File

@@ -3,27 +3,28 @@ package ;
import haxe.unit.*;
class Run {
#if !macro
static function main()
test();//It compiles ...
#else
static var cases:Array<TestCase> = [
new Exprs(),
new Types(),
new Positions(),
];
#end
macro static function test() {
var runner = new TestRunner();
tink.macro.ClassBuilder;
for (c in cases)
runner.add(c);
runner.run();
if (!runner.result.success)
haxe.macro.Context.error(runner.result.toString(), haxe.macro.Context.currentPos());
return macro {
trace('Let\'s ship it!');
}
}
#if !macro
static function main()
test();//It compiles ...
#else
static var cases:Array<TestCase> = [
new Exprs(),
new Types(),
new Positions(),
new TypeMapTest(),
];
#end
macro static function test() {
var runner = new TestRunner();
tink.macro.ClassBuilder;
for (c in cases)
runner.add(c);
runner.run();
if (!runner.result.success)
haxe.macro.Context.error(runner.result.toString(), haxe.macro.Context.currentPos());
return macro {
trace('Let\'s ship it!');
}
}
}

30
tests/TypeMapTest.hx Normal file
View File

@@ -0,0 +1,30 @@
package;
import haxe.unit.TestCase;
import tink.macro.TypeMap;
using haxe.macro.Context;
class TypeMapTest extends TestCase {
function testMap() {
var t = new TypeMap();
var t1 = (macro [{ foo: [{ bar: '5' }]}]).typeof();
var t2 = (macro [{ foo: [{ bar: 5 }]}]).typeof();
t.set(t1, 0);
assertEquals(Lambda.count(t), 1);
t.set(t2, 1);
assertEquals(Lambda.count(t), 2);
t.set(t1, 2);
assertEquals(Lambda.count(t), 2);
t.set(t2, 3);
assertEquals(Lambda.count(t), 2);
assertEquals(t.get(t1), 2);
assertEquals(t.get(t2), 3);
assertTrue(true);
}
}

View File

@@ -7,41 +7,41 @@ using tink.CoreApi;
using tink.MacroApi;
class Types extends Base {
function type(c:ComplexType)
return c.toType().sure();
function resolve(type:String)
return Context.getType(type);
inline function assertSuccess<S, F>(o:Outcome<S, F>)
assertTrue(o.isSuccess());
inline function assertFailure<S, F>(o:Outcome<S, F>)
assertFalse(o.isSuccess());
function testIs() {
assertSuccess(resolve('Int').isSubTypeOf(resolve('Float')));
assertFailure(resolve('Float').isSubTypeOf(resolve('Int')));
}
function testFields() {
var expected = type(macro : Void -> Iterator<Arrayish>),
iterator = type(macro : haxe.ds.StringMap<Arrayish>).getFields(true).sure().filter(function (c) return c.name == 'iterator')[0];
assertSuccess(iterator.type.isSubTypeOf(expected));
assertSuccess(expected.isSubTypeOf(iterator.type));
}
function testConvert() {
assertSuccess((macro : Int).toType());
assertFailure((macro : Tni).toType());
function blank()
return type(MacroApi.pos().makeBlankType());
var bool = type(macro : Bool);
assertTrue(blank().isSubTypeOf(bool).isSuccess());
assertTrue(bool.isSubTypeOf(blank()).isSuccess());
MacroApi.pos().makeBlankType().toString();
}
function type(c:ComplexType)
return c.toType().sure();
function resolve(type:String)
return Context.getType(type);
inline function assertSuccess<S, F>(o:Outcome<S, F>)
assertTrue(o.isSuccess());
inline function assertFailure<S, F>(o:Outcome<S, F>)
assertFalse(o.isSuccess());
function testIs() {
assertSuccess(resolve('Int').isSubTypeOf(resolve('Float')));
assertFailure(resolve('Float').isSubTypeOf(resolve('Int')));
}
function testFields() {
var expected = type(macro : Void -> Iterator<Arrayish>),
iterator = type(macro : haxe.ds.StringMap<Arrayish>).getFields(true).sure().filter(function (c) return c.name == 'iterator')[0];
assertSuccess(iterator.type.isSubTypeOf(expected));
assertSuccess(expected.isSubTypeOf(iterator.type));
}
function testConvert() {
assertSuccess((macro : Int).toType());
assertFailure((macro : Tni).toType());
function blank()
return type(MacroApi.pos().makeBlankType());
var bool = type(macro : Bool);
assertTrue(blank().isSubTypeOf(bool).isSuccess());
assertTrue(bool.isSubTypeOf(blank()).isSuccess());
MacroApi.pos().makeBlankType().toString();
}
}