Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9f9675860a | ||
![]() |
c3cecad472 | ||
![]() |
0284615f99 | ||
![]() |
0be931b913 | ||
![]() |
c0b562af31 | ||
![]() |
8e0065c017 | ||
![]() |
bf6d9461cb | ||
![]() |
df01d82300 | ||
![]() |
9b4e9cb81f | ||
![]() |
b90aae8386 | ||
![]() |
ab34b31bbd | ||
![]() |
ad3526795c | ||
![]() |
f4bf23a66b | ||
![]() |
a01a1b16ae | ||
![]() |
1a404103ca | ||
![]() |
b4bfa69376 | ||
![]() |
b17005d9ab | ||
![]() |
61505ec63b | ||
![]() |
ef2054d8ff |
19
.travis.yml
Normal file
19
.travis.yml
Normal 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
|
43
README.md
43
README.md
@@ -38,13 +38,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 +135,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 +149,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 +157,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 +168,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 +260,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 +275,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 +310,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 +335,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 +343,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 +396,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 +410,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.
|
@@ -11,8 +11,8 @@
|
||||
"contributors": [
|
||||
"back2dos"
|
||||
],
|
||||
"releasenote": "Do not lose constructor metadata.",
|
||||
"version": "0.6.1",
|
||||
"releasenote": "Add TypeMap.",
|
||||
"version": "0.7.0",
|
||||
"url": "http://haxetink.org/tink_macro",
|
||||
"dependencies": {
|
||||
"tink_core": ""
|
||||
|
@@ -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>;
|
||||
|
||||
|
@@ -10,13 +10,14 @@ 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;
|
||||
var superFields:Map<String,Bool>;
|
||||
|
||||
var initializeFrom:Array<Field>;
|
||||
|
||||
public function new(?target, ?fields) {
|
||||
if (target == null)
|
||||
target = Context.getLocalClass().get();
|
||||
@@ -24,23 +25,34 @@ class ClassBuilder {
|
||||
if (fields == null)
|
||||
fields = Context.getBuildFields();
|
||||
|
||||
this.memberMap = new Map();
|
||||
this.initializeFrom = fields;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (initializeFrom == null) return;
|
||||
|
||||
var fields = initializeFrom;
|
||||
initializeFrom = null;
|
||||
|
||||
this.memberList = [];
|
||||
this.macros = new Map();
|
||||
this.target = target;
|
||||
|
||||
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, m.meta);
|
||||
this.constructor = new Constructor(this, m.getFunction().sure(), m.isPublic, m.pos, field.meta);
|
||||
}
|
||||
else
|
||||
addMember(field);
|
||||
doAddMember(field);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function getConstructor(?fallback:Function):Constructor {
|
||||
init();
|
||||
if (constructor == null)
|
||||
if (fallback != null)
|
||||
new Constructor(this, fallback);
|
||||
@@ -77,10 +89,13 @@ class ClassBuilder {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
public function hasConstructor():Bool
|
||||
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)
|
||||
@@ -100,12 +115,16 @@ class ClassBuilder {
|
||||
|
||||
return ret;
|
||||
}
|
||||
public function iterator():Iterator<Member>
|
||||
public function iterator():Iterator<Member> {
|
||||
init();
|
||||
return this.memberList.copy().iterator();
|
||||
}
|
||||
|
||||
public function hasOwnMember(name:String):Bool
|
||||
public function hasOwnMember(name:String):Bool {
|
||||
init();
|
||||
return
|
||||
macros.exists(name) || memberMap.exists(name);
|
||||
macros.exists(name) || memberByName(name).isSuccess();
|
||||
}
|
||||
|
||||
public function hasSuperField(name:String):Bool {
|
||||
if (superFields == null) {
|
||||
@@ -120,33 +139,34 @@ class ClassBuilder {
|
||||
}
|
||||
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
|
||||
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
|
||||
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 {
|
||||
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 (!m.isStatic && hasSuperField(m.name))
|
||||
m.overrides = true;
|
||||
memberMap.set(m.name, m);
|
||||
//if (hasOwnMember(m.name))
|
||||
//m.pos.error('duplicate member declaration ' + m.name);
|
||||
|
||||
if (front)
|
||||
memberList.unshift(m);
|
||||
else
|
||||
@@ -155,6 +175,15 @@ class ClassBuilder {
|
||||
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)
|
||||
|
@@ -96,9 +96,7 @@ class Constructor {
|
||||
case Success(member): member.addMeta(':isVar');
|
||||
default:
|
||||
}
|
||||
|
||||
addStatement(macro @:pos(pos) if (false) { var $tmp = this.$name; $i{tmp} = $e; }, true);
|
||||
addStatement(macro @:pos(pos) (cast this).$name = $e, options.prepend);
|
||||
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);
|
||||
|
@@ -14,7 +14,7 @@ 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 = {
|
||||
|
32
src/tink/macro/TypeMap.hx
Normal file
32
src/tink/macro/TypeMap.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -10,7 +10,7 @@ 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>();
|
||||
|
@@ -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
|
@@ -11,6 +11,7 @@ class Run {
|
||||
new Exprs(),
|
||||
new Types(),
|
||||
new Positions(),
|
||||
new TypeMapTest(),
|
||||
];
|
||||
#end
|
||||
macro static function test() {
|
||||
|
30
tests/TypeMapTest.hx
Normal file
30
tests/TypeMapTest.hx
Normal 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);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user