Compare commits
190 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8c50dcf04b | ||
![]() |
4bc63c6eb0 | ||
![]() |
acb7237ae4 | ||
![]() |
e3bac681ce | ||
![]() |
34516086c9 | ||
![]() |
b1e7bf51cf | ||
![]() |
d5186f2fa2 | ||
![]() |
8890b62b46 | ||
![]() |
cce63becd6 | ||
![]() |
a101378a4c | ||
![]() |
b48b300cbc | ||
![]() |
27c9b20330 | ||
![]() |
b4fa5f4fe0 | ||
![]() |
e59fab499d | ||
![]() |
3356172b7f | ||
![]() |
4fd03ed667 | ||
![]() |
4cb5ee4196 | ||
![]() |
888481094a | ||
![]() |
7686fe3e35 | ||
![]() |
747d07bfc5 | ||
![]() |
00640d8328 | ||
![]() |
77973f6007 | ||
![]() |
0df9c28ace | ||
![]() |
d90fb966d2 | ||
![]() |
9018b06279 | ||
![]() |
6ed0424093 | ||
![]() |
53868cc6f3 | ||
![]() |
296cc6c861 | ||
![]() |
56c2ed9642 | ||
![]() |
ac908bc82d | ||
![]() |
1604a135ae | ||
![]() |
5e65f5354f | ||
![]() |
8f81256731 | ||
![]() |
1b717919fa | ||
![]() |
3d9aec835e | ||
![]() |
86fa47eb0d | ||
![]() |
b5605e48ac | ||
![]() |
6f5eba983e | ||
![]() |
d5a44a9268 | ||
![]() |
a11804c3ab | ||
![]() |
380ceb39ea | ||
![]() |
c48d445a54 | ||
![]() |
b7e413d839 | ||
![]() |
e1e487079b | ||
![]() |
5f959f788e | ||
![]() |
4f84570bb7 | ||
![]() |
e398d0e694 | ||
![]() |
13d11f4f66 | ||
![]() |
d6142847eb | ||
![]() |
e6a0304016 | ||
![]() |
69368937a4 | ||
![]() |
df2389d2c8 | ||
![]() |
aad80afec2 | ||
![]() |
b68c6ac4a1 | ||
![]() |
682aeacce8 | ||
![]() |
9bcec770eb | ||
![]() |
2310b6d8e4 | ||
![]() |
7f13916154 | ||
![]() |
2b7204f8d3 | ||
![]() |
7ca0d4a650 | ||
![]() |
e38f5ae147 | ||
![]() |
4186225eb5 | ||
![]() |
f5a3d73c55 | ||
![]() |
f65df0e94b | ||
![]() |
55a4b1463f | ||
![]() |
0a6e39969b | ||
![]() |
f051345863 | ||
![]() |
74e37b4bf1 | ||
![]() |
42821979d9 | ||
![]() |
2aa0b07e9f | ||
![]() |
67e680f67f | ||
![]() |
ebade45e63 | ||
![]() |
40497bb1fc | ||
![]() |
d80b03fe9d | ||
![]() |
f269d13abf | ||
![]() |
f271cc880a | ||
![]() |
9cd59d3989 | ||
![]() |
40ee99df39 | ||
![]() |
1cb27daf46 | ||
![]() |
a9bea4287a | ||
![]() |
0be2090232 | ||
![]() |
0dc774db21 | ||
![]() |
a7c22bf5af | ||
![]() |
a164b335ed | ||
![]() |
d02306eebd | ||
![]() |
ff9ef59445 | ||
![]() |
bfc5c2b88f | ||
![]() |
57042819b1 | ||
![]() |
82d15d9ffc | ||
![]() |
0d9fff7e01 | ||
![]() |
45d5f3e0ea | ||
![]() |
800891bee2 | ||
![]() |
0802bf7f79 | ||
![]() |
42897c7cd5 | ||
![]() |
0e902745b1 | ||
![]() |
3dec4fa404 | ||
![]() |
c0ec918f52 | ||
![]() |
4eb9729f0e | ||
![]() |
7586b54ece | ||
![]() |
21c7d12be7 | ||
![]() |
f27d2796b1 | ||
![]() |
417a92cabc | ||
![]() |
4457ea7dee | ||
![]() |
209847e5ea | ||
![]() |
c94e4a0337 | ||
![]() |
cc009b2026 | ||
![]() |
0a36ee8e2b | ||
![]() |
5879773566 | ||
![]() |
58262a1810 | ||
![]() |
9ceadeb88f | ||
![]() |
15d75965ad | ||
![]() |
c7634e0a29 | ||
![]() |
49e48bc801 | ||
![]() |
c150b84f18 | ||
![]() |
04f86ce341 | ||
![]() |
45380371d8 | ||
![]() |
d94d946301 | ||
![]() |
56b7caaf01 | ||
![]() |
73e9da8435 | ||
![]() |
bf23337923 | ||
![]() |
02bd3bf69d | ||
![]() |
82a3417b97 | ||
![]() |
1f986c3732 | ||
![]() |
20e998fd12 | ||
![]() |
0f9c0d1e97 | ||
![]() |
2ae13d534e | ||
![]() |
65d341f1e1 | ||
![]() |
68fa50c7c3 | ||
![]() |
32aa2d6570 | ||
![]() |
d81642e733 | ||
![]() |
d5f4987ddb | ||
![]() |
671f1bc995 | ||
![]() |
b1012937fc | ||
![]() |
bd4b8c436f | ||
![]() |
dbebd0d70d | ||
![]() |
4c523e02a5 | ||
![]() |
f39fc3b296 | ||
![]() |
e06c40325b | ||
![]() |
7a3914094d | ||
![]() |
a597d7fa50 | ||
![]() |
3d5626d66f | ||
![]() |
88a1ffba0b | ||
![]() |
8f81959da9 | ||
![]() |
b20986ecd0 | ||
![]() |
e55b72b697 | ||
![]() |
240ace34ec | ||
![]() |
d851094aa3 | ||
![]() |
ea16f79090 | ||
![]() |
aad311f524 | ||
![]() |
f0d5d80266 | ||
![]() |
38aa0f4dc4 | ||
![]() |
f4fb7d63c3 | ||
![]() |
27de7bb3e7 | ||
![]() |
371f393299 | ||
![]() |
abbb9e5a4c | ||
![]() |
921ae162c8 | ||
![]() |
fe779872d2 | ||
![]() |
f8ab1c7354 | ||
![]() |
ba5349f55f | ||
![]() |
241919549c | ||
![]() |
92f60cbe4f | ||
![]() |
3b822b4cdb | ||
![]() |
f2b670f9e4 | ||
![]() |
ff491fe7e7 | ||
![]() |
7e869cd9c8 | ||
![]() |
983ffa16af | ||
![]() |
4207dea754 | ||
![]() |
3470f1cc47 | ||
![]() |
d84fb38d73 | ||
![]() |
065788c198 | ||
![]() |
810c2f9630 | ||
![]() |
331f186dcd | ||
![]() |
c406e0b844 | ||
![]() |
43fb9eb16a | ||
![]() |
3734bcb513 | ||
![]() |
d53937f836 | ||
![]() |
9f9675860a | ||
![]() |
c3cecad472 | ||
![]() |
0284615f99 | ||
![]() |
0be931b913 | ||
![]() |
c0b562af31 | ||
![]() |
8e0065c017 | ||
![]() |
bf6d9461cb | ||
![]() |
df01d82300 | ||
![]() |
9b4e9cb81f | ||
![]() |
b90aae8386 | ||
![]() |
ab34b31bbd | ||
![]() |
ad3526795c | ||
![]() |
f4bf23a66b | ||
![]() |
a01a1b16ae |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/bin
|
41
.travis.yml
Normal file
41
.travis.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
stages:
|
||||
- test
|
||||
- deploy
|
||||
|
||||
language: node_js
|
||||
node_js: 8
|
||||
|
||||
os:
|
||||
- linux
|
||||
# - osx
|
||||
|
||||
env:
|
||||
- HAXE_VERSION=3.4.7
|
||||
- HAXE_VERSION=edge
|
||||
|
||||
install:
|
||||
- npm i -g lix@15.5.4
|
||||
- lix install haxe $HAXE_VERSION
|
||||
- lix download
|
||||
|
||||
script:
|
||||
- lix run travix node
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# - stage: test # should uncomment this when there is no matrix above (e.g. only one os, one env, etc)
|
||||
- stage: deploy
|
||||
language: haxe
|
||||
haxe: "3.4.7"
|
||||
os: linux
|
||||
install: skip
|
||||
script: skip
|
||||
env:
|
||||
secure: T4SCtY5qmEsK1ARWPevJmqLm23tv4CobLrbPOQV3FsoQno7FCP1S/+9GmuoJKzeTjWMzdTeDsp8TVwZ6AyGjvhl2nZNjhU+QTsir4tfbYYRyvsz/QK6pveFbPQVv7OsnnaB4wbZtqGZ8mzFeQf7Ol4tsNe7iUFJb/iVc+4/lUxo=
|
||||
after_success:
|
||||
- haxelib install travix
|
||||
- haxelib run travix install
|
||||
- haxelib run travix release
|
6
.vscode/tasks.json
vendored
Normal file
6
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "haxelib",
|
||||
"args": ["run", "travix", "node"],
|
||||
"problemMatcher": "$haxe"
|
||||
}
|
97
README.md
97
README.md
@@ -1,5 +1,8 @@
|
||||
# Tinkerbell Macro Library
|
||||
|
||||
[](https://travis-ci.org/haxetink/tink_macro)
|
||||
[](https://gitter.im/haxetink/public)
|
||||
|
||||
Explained in current marketing speak, `tink_macro` is *the* macro toolkit ;)
|
||||
|
||||
### History and Mission
|
||||
@@ -14,18 +17,17 @@ The library is build on top of the haxe macro API and `tink_core`, having three
|
||||
|
||||
<!-- START INDEX -->
|
||||
- [Macro API](#macro-api)
|
||||
-
|
||||
- [Expression Tools](#expression-tools)
|
||||
- [Basic Helpers](#basic-helpers)
|
||||
- [Extracting Constants](#extracting-constants)
|
||||
- [Shortcuts](#shortcuts)
|
||||
- [Type Inspection](#type-inspection)
|
||||
- [Advanced Transformations](#advanced-transformations)
|
||||
- [Position Tools](#position-tools)
|
||||
- [Type Tools](#type-tools)
|
||||
- [Function Tools](#function-tools)
|
||||
- [Operation Tools](#operation-tools)
|
||||
- [Metadata Tools](#metadata-tools)
|
||||
- [Expression Tools](#expression-tools)
|
||||
- [Basic Helpers](#basic-helpers)
|
||||
- [Extracting Constants](#extracting-constants)
|
||||
- [Shortcuts](#shortcuts)
|
||||
- [Type Inspection](#type-inspection)
|
||||
- [Advanced Transformations](#advanced-transformations)
|
||||
- [Position Tools](#position-tools)
|
||||
- [Type Tools](#type-tools)
|
||||
- [Function Tools](#function-tools)
|
||||
- [Operation Tools](#operation-tools)
|
||||
- [Metadata Tools](#metadata-tools)
|
||||
- [Build Infrastructure](#build-infrastructure)
|
||||
- [Member](#member)
|
||||
- [ClassBuilder](#classbuilder)
|
||||
@@ -38,19 +40,19 @@ 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`.
|
||||
|
||||
### Expression Tools
|
||||
## Expression Tools
|
||||
|
||||
#### Basic Helpers
|
||||
### Basic Helpers
|
||||
|
||||
- `at(e:ExprDef, ?pos:Position):Expr`
|
||||
A short hand for creating expression as for example `EReturn(value).at(position)`, instead of the more verbose `{ expr: EReturn(value), pos: position }`.
|
||||
@@ -63,8 +65,10 @@ Rejects an expression and displays a generic or custom error message
|
||||
Converts an expression into the corresponding Haxe source code
|
||||
- `log(e:Expr, ?pos:Position):Expr`
|
||||
Traces the string representation of an expression and returns it.
|
||||
- `concat(e1:Expr, e2:Expr):Expr`
|
||||
Concats two expressions into a block. If either sub-expression is a block itself, it gets flattened into the resulting block.
|
||||
|
||||
#### Extracting Constants
|
||||
### Extracting Constants
|
||||
|
||||
- `isWildcard(e:Expr):Bool`
|
||||
Checks whether an expression is the identifier `_`
|
||||
@@ -77,7 +81,7 @@ Attempts extracting an identifier from an expression. Note that an identifier ca
|
||||
- `getName(e:Expr):Outcome<String, tink.core.Error>`
|
||||
Attempts extracting a name, i.e. a string constant or identifier from an expression
|
||||
|
||||
#### Shortcuts
|
||||
### Shortcuts
|
||||
|
||||
Often reification is prefereable to these shortcuts - if applicable. Unlike reification, the position of these expressions will default to `Context.currentPos()` rather than the position where they were created.
|
||||
|
||||
@@ -114,7 +118,7 @@ Generates a simple if statement.
|
||||
- `iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position):Expr`
|
||||
Will loop over `target` with a loop variable called `loopVar` using `body`-
|
||||
|
||||
#### Type Inspection
|
||||
### Type Inspection
|
||||
|
||||
- `is(e:Expr, c:ComplexType):Bool`
|
||||
Tells you whether a given expression has a given type.
|
||||
@@ -124,7 +128,7 @@ Inspects, whether an expression can be iterated over and if so returns the eleme
|
||||
- `typeof(expr:Expr, ?locals:Array<Var>):Outcome<Type, tink.core.Error>`
|
||||
Attempts to determine the type of an expression. Note that you can use `locals` to hint the compiler the type of certain identifiers. For example if you are in a build macro, and you want to get the type of a subexpression of a method body, you could "fake" the other members of the class as local variables, because in that context, the other members do not yet exists from the compiler's perspective.
|
||||
|
||||
#### Advanced Transformations
|
||||
### Advanced Transformations
|
||||
|
||||
- `has(e:Expr, condition:Expr->Bool, ?options: { ?enterFunctions: Bool })`
|
||||
This function actually does no transformation, but is very close to the rest of these functions. It allows you to check whether an expression has a sub-expression that satisfies `condition`. By default, it does not enter nested functions.
|
||||
@@ -135,7 +139,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 +153,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 +161,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 +172,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 {
|
||||
@@ -182,7 +186,7 @@ This will traverse an expression and will apply the `yielder` to the "leafs", wh
|
||||
|
||||
If you set `options.leaveLoops` to `true`, then loops (both for and while) will be considered leafs.
|
||||
|
||||
### Position Tools
|
||||
## Position Tools
|
||||
|
||||
- `sanitize(pos:Position):Position`
|
||||
Returns the position ITself or `Context.currentPos()` if it's null.
|
||||
@@ -197,7 +201,7 @@ Creates a failed `Outcome` associated with the supplied position.
|
||||
- `getOutcome<D, F>(pos:Position, outcome:Outcome<D, F>):D`
|
||||
Attempts getting the result of the supplied outcome. If it is a failure, it will cause an error at the given position.
|
||||
|
||||
### Type Tools
|
||||
## Type Tools
|
||||
|
||||
- `getID(t:Type, ?reduced = true):Null<String>`
|
||||
Returns a String identifier for a type if available. By default, the type will be reduced prior to getting its name (typedefs are resolved etc.). With `reduced = false` you can also get the name of a typedef.
|
||||
@@ -220,7 +224,7 @@ Will tell you whether a field is a variable or not. Signature is likely to chang
|
||||
- `toComplex(type:Type, ?option:{ ?direct: Bool }):ComplexType`
|
||||
Will convert a `Type` to a `ComplexType`. Ideally this is done with `Context.toComplexType` but for monomorphs and the like, this builtin method fails and `tink_macro` uses a hack to make it work none the less. You can also use `{ direct : true }` to force this hack in case the translation fails (which can be the case with private types).
|
||||
|
||||
### Function Tools
|
||||
## Function Tools
|
||||
|
||||
- `asExpr(f:Function, ?name:String, ?pos:Position):Expr`
|
||||
Converts a function to an expression, i.e. a local function definition.
|
||||
@@ -231,7 +235,7 @@ A shorthand to create function arguments.
|
||||
- `getArgIdents(f:Function):Array<Expr>`
|
||||
Will extract the argument list of a function as an expression list of identifiers (usefull when writing call-forwarding macros or the like).
|
||||
|
||||
### Operation Tools
|
||||
## Operation Tools
|
||||
|
||||
- `get(o:Binop, e:Expr):Outcome<{ e1:Expr, e2:Expr, pos:Position }, tink.core.Error>`
|
||||
Attempts to extract a specific binary operation from an expression.
|
||||
@@ -245,7 +249,7 @@ Attempts to extract a specific unary operation from an expression.
|
||||
- `getUnop(e:Expr):Outcome<{ op:Unop, e:Expr, postFix:Bool, pos:Position }, tink.core.Error>`
|
||||
Attempts to decompose an expression into the parts of a unary operation.
|
||||
|
||||
### Metadata Tools
|
||||
## Metadata Tools
|
||||
|
||||
- `toMap(m:Metadata):Map<String, Array<Array<Expr>>`
|
||||
Will deconstruct an array of metadata tags to a `Map` mapping the tag names to an array of the argument lists of each tag with that name. So `@foo(1) @foo(2) @bar` becomes `["foo" => [[1], [2]], "bar" => [[]]]`
|
||||
@@ -260,7 +264,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 +279,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>;
|
||||
@@ -298,7 +302,7 @@ abstract Member from Field to Field {
|
||||
|
||||
Most of the API should be self-explaining. The `isBound` property is a bad name to convey the concept that a field can be either `inline` (`true`) or `dynamic` (`false`) or neither (`null`). Equally, `isPublic` is nullable which means that normally defaults to `private`.
|
||||
|
||||
The `publish` method will make a field `public` if it is not `private`. This can also be done with `if (m.isPublic == null) m.isPublic = true;` but the implementation is far more efficient - for what its worth.
|
||||
The `publish` method will make a field `public` if it is not explicitly marked as `private`. This can also be done with `if (m.isPublic == null) m.isPublic = true;` but the implementation is far more efficient - for what its worth.
|
||||
|
||||
The `extractMeta` method will "peel of" the first tag with a given `name` - if available. Note that the tag will be removed from the member.
|
||||
|
||||
@@ -310,7 +314,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 +339,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 +347,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;
|
||||
@@ -386,7 +390,7 @@ The `init` method is the swiss army knife of initializing fields. The `options.p
|
||||
|
||||
#### Setter Bypass
|
||||
|
||||
It is important to know that when you initialize a field with `options.bypass` set to true, existing setters will by bypassed. That's particularly helpful if your setter triggers a side effect that you don't want triggered. This is achieved by generating the assignment as `(untyped this).$name = $value`. To make the code typesafe again, this is prefixed with `if (false) { var __tmp = this.$name; __tmp = $value; }`. This code is later thrown out by the compiler. Its role is to ensure type safety without interfering with the normal typing order.
|
||||
It is important to know that when you initialize a field with `options.bypass` set to true, existing setters will be bypassed. That's particularly helpful if your setter triggers a side effect that you don't want triggered. This is achieved by generating the assignment as `(untyped this).$name = $value`. To make the code typesafe again, this is prefixed with `if (false) { var __tmp = this.$name; __tmp = $value; }`. This code is later thrown out by the compiler. Its role is to ensure type safety without interfering with the normal typing order.
|
||||
|
||||
Setter bypass also causes the field to gain an `@:isVar`. And currently, with `-dce full`, additional code will be generated to avoid the field to be eliminated.
|
||||
|
||||
@@ -396,7 +400,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 +414,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.
|
||||
|
1
bin/.gitignore
vendored
1
bin/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.n
|
6
dev.hxml
Normal file
6
dev.hxml
Normal file
@@ -0,0 +1,6 @@
|
||||
tests.hxml
|
||||
-lib travix
|
||||
-lib tink_macro
|
||||
-lib hx3compat
|
||||
-js whatever.js
|
||||
--no-output
|
3
haxe_libraries/hx3compat.hxml
Normal file
3
haxe_libraries/hx3compat.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
# @install: lix --silent download "haxelib:/hx3compat#1.0.3" into hx3compat/1.0.3/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/hx3compat/1.0.3/haxelib/std
|
||||
-D hx3compat=1.0.3
|
6
haxe_libraries/hxnodejs.hxml
Normal file
6
haxe_libraries/hxnodejs.hxml
Normal file
@@ -0,0 +1,6 @@
|
||||
# @install: lix --silent download "haxelib:/hxnodejs#12.0.0" into hxnodejs/12.0.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/hxnodejs/12.0.0/haxelib/src
|
||||
-D hxnodejs=12.0.0
|
||||
--macro allowPackage('sys')
|
||||
# should behave like other target defines and not be defined in macro context
|
||||
--macro define('nodejs')
|
8
haxe_libraries/tink_cli.hxml
Normal file
8
haxe_libraries/tink_cli.hxml
Normal file
@@ -0,0 +1,8 @@
|
||||
-D tink_cli=0.3.1
|
||||
# @install: lix --silent download "haxelib:/tink_cli#0.3.1" into tink_cli/0.3.1/haxelib
|
||||
-lib tink_io
|
||||
-lib tink_stringly
|
||||
-lib tink_macro
|
||||
-cp ${HAXE_LIBCACHE}/tink_cli/0.3.1/haxelib/src
|
||||
# Make sure docs are generated
|
||||
-D use-rtti-doc
|
3
haxe_libraries/tink_core.hxml
Normal file
3
haxe_libraries/tink_core.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
-D tink_core=1.24.0
|
||||
# @install: lix --silent download "haxelib:/tink_core#1.24.0" into tink_core/1.24.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/tink_core/1.24.0/haxelib/src
|
5
haxe_libraries/tink_io.hxml
Normal file
5
haxe_libraries/tink_io.hxml
Normal file
@@ -0,0 +1,5 @@
|
||||
-D tink_io=0.5.0
|
||||
# @install: lix --silent download "haxelib:/tink_io#0.5.0" into tink_io/0.5.0/haxelib
|
||||
-lib tink_streams
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_io/0.5.0/haxelib/src
|
3
haxe_libraries/tink_macro.hxml
Normal file
3
haxe_libraries/tink_macro.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
-D tink_macro
|
||||
-cp src
|
||||
-lib tink_core
|
4
haxe_libraries/tink_streams.hxml
Normal file
4
haxe_libraries/tink_streams.hxml
Normal file
@@ -0,0 +1,4 @@
|
||||
-D tink_streams=0.2.1
|
||||
# @install: lix --silent download "haxelib:/tink_streams#0.2.1" into tink_streams/0.2.1/haxelib
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_streams/0.2.1/haxelib/src
|
4
haxe_libraries/tink_stringly.hxml
Normal file
4
haxe_libraries/tink_stringly.hxml
Normal file
@@ -0,0 +1,4 @@
|
||||
-D tink_stringly=0.2.0
|
||||
# @install: lix --silent download "haxelib:/tink_stringly#0.2.0" into tink_stringly/0.2.0/haxelib
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_stringly/0.2.0/haxelib/src
|
6
haxe_libraries/travix.hxml
Normal file
6
haxe_libraries/travix.hxml
Normal file
@@ -0,0 +1,6 @@
|
||||
-D travix=0.12.2
|
||||
# @install: lix --silent download "gh://github.com/back2dos/travix#7da3bf96717b52bf3c7e5d2273bf927a8cd7aeb5" into travix/0.12.2/github/7da3bf96717b52bf3c7e5d2273bf927a8cd7aeb5
|
||||
# @post-install: cd ${HAXE_LIBCACHE}/travix/0.12.2/github/7da3bf96717b52bf3c7e5d2273bf927a8cd7aeb5 && haxe -cp src --run travix.PostDownload
|
||||
# @run: haxelib run-dir travix ${HAXE_LIBCACHE}/travix/0.12.2/github/7da3bf96717b52bf3c7e5d2273bf927a8cd7aeb5
|
||||
-lib tink_cli
|
||||
-cp ${HAXE_LIBCACHE}/travix/0.12.2/github/7da3bf96717b52bf3c7e5d2273bf927a8cd7aeb5/src
|
24
haxelib.json
24
haxelib.json
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "tink_macro",
|
||||
"license": "MIT",
|
||||
"description": "The macro toolkit ;)",
|
||||
"classPath": "src",
|
||||
"dependencies": {
|
||||
"tink_core": ""
|
||||
},
|
||||
"url": "https://github.com/haxetink/tink_macro",
|
||||
"contributors": [
|
||||
"back2dos"
|
||||
],
|
||||
"version": "0.19.1",
|
||||
"releasenote": "More fine grained control over type normalization in BuildCache.",
|
||||
"tags": [
|
||||
"tink",
|
||||
"macro",
|
||||
"utility"
|
||||
],
|
||||
"classPath": "src",
|
||||
"description": "The macro toolkit ;)",
|
||||
"contributors": [
|
||||
"back2dos"
|
||||
],
|
||||
"releasenote": "Make class builder lazy and less opinionated.",
|
||||
"version": "0.6.3",
|
||||
"url": "http://haxetink.org/tink_macro",
|
||||
"dependencies": {
|
||||
"tink_core": ""
|
||||
}
|
||||
"license": "MIT"
|
||||
}
|
@@ -4,6 +4,7 @@ import haxe.macro.Expr.TypeDefinition;
|
||||
|
||||
using tink.CoreApi;
|
||||
using tink.macro.Positions;
|
||||
using StringTools;
|
||||
|
||||
typedef Positions = tink.macro.Positions;
|
||||
typedef ExprTools = haxe.macro.ExprTools;
|
||||
@@ -14,12 +15,11 @@ typedef Bouncer = tink.macro.Bouncer;
|
||||
typedef Types = tink.macro.Types;
|
||||
typedef Binops = tink.macro.Ops.Binary;
|
||||
typedef Unops = tink.macro.Ops.Unary;
|
||||
typedef TypeMap<T> = tink.macro.TypeMap<T>;
|
||||
|
||||
//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 Option<T> = haxe.ds.Option<T>;
|
||||
typedef MacroOutcomeTools = tink.OutcomeTools;
|
||||
|
||||
typedef Member = tink.macro.Member;
|
||||
typedef Constructor = tink.macro.Constructor;
|
||||
@@ -28,13 +28,112 @@ 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 var MAIN_CANDIDATES = ['-main', '-x', '--run'];
|
||||
static public function getMainClass():Option<String> {
|
||||
var args = Sys.args();
|
||||
|
||||
for (c in MAIN_CANDIDATES)
|
||||
switch args.indexOf(c) {
|
||||
case -1:
|
||||
case v: return Some(args[v+1]);
|
||||
}
|
||||
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
@:persistent 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 completionPoint(default, null):Option<{
|
||||
var file(default, never):String;
|
||||
var pos(default, never):Int;
|
||||
}>;
|
||||
|
||||
static public var args(default, null):Iterable<String>;
|
||||
static var initialized = initArgs();
|
||||
|
||||
static function initArgs() {
|
||||
var sysArgs = Sys.args();
|
||||
args = sysArgs;
|
||||
completionPoint = switch sysArgs.indexOf('--display') {
|
||||
case -1: None;
|
||||
case sysArgs[_ + 1] => arg:
|
||||
if (arg.startsWith('{"jsonrpc":')) {
|
||||
var payload:{
|
||||
jsonrpc:String,
|
||||
method:String,
|
||||
params:{
|
||||
file:String,
|
||||
offset:Int,
|
||||
}
|
||||
} = haxe.Json.parse(arg);
|
||||
switch payload {
|
||||
case { jsonrpc: '2.0', method: 'display/completion' }:
|
||||
Some({
|
||||
file: payload.params.file,
|
||||
pos: payload.params.offset,
|
||||
});
|
||||
default: None;
|
||||
}
|
||||
}
|
||||
else None;
|
||||
}
|
||||
try haxe.macro.Context.onMacroContextReused(initArgs)
|
||||
catch (all:Dynamic) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if (haxe_ver >= 4)
|
||||
typedef ObjectField = haxe.macro.Expr.ObjectField;
|
||||
typedef QuoteStatus = haxe.macro.Expr.QuoteStatus;
|
||||
#else
|
||||
enum QuoteStatus {
|
||||
Unquoted;
|
||||
Quoted;
|
||||
}
|
||||
private typedef F = {
|
||||
var field:String;
|
||||
var expr:haxe.macro.Expr;
|
||||
}
|
||||
|
||||
@:forward
|
||||
abstract ObjectField(F) to F {
|
||||
|
||||
static var QUOTED = "@$__hx__";
|
||||
|
||||
inline function new(o) this = o;
|
||||
|
||||
public var field(get, never):String;
|
||||
|
||||
function get_field()
|
||||
return
|
||||
if (quotes == Quoted)
|
||||
this.field.substr(QUOTED.length);
|
||||
else this.field;
|
||||
|
||||
public var quotes(get, never):QuoteStatus;
|
||||
|
||||
function get_quotes()
|
||||
return if (StringTools.startsWith(this.field, QUOTED)) Quoted else Unquoted;
|
||||
|
||||
@:from static function ofFull(o:{>F, quotes:QuoteStatus }):ObjectField
|
||||
return switch o.quotes {
|
||||
case null | Unquoted:
|
||||
new ObjectField({ field: o.field, expr: o.expr });
|
||||
default:
|
||||
new ObjectField({ field: QUOTED + o.field, expr: o.expr });
|
||||
}
|
||||
|
||||
@:from static function ofOld(o:F):ObjectField
|
||||
return new ObjectField(o);
|
||||
}
|
||||
#end
|
@@ -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);
|
||||
}
|
200
src/tink/macro/BuildCache.hx
Normal file
200
src/tink/macro/BuildCache.hx
Normal file
@@ -0,0 +1,200 @@
|
||||
package tink.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import tink.macro.TypeMap;
|
||||
|
||||
using tink.MacroApi;
|
||||
using haxe.macro.Tools;
|
||||
|
||||
typedef BuildContextN = {
|
||||
pos:Position,
|
||||
types:Array<Type>,
|
||||
usings:Array<TypePath>,
|
||||
name:String,
|
||||
}
|
||||
|
||||
|
||||
typedef BuildContext = {
|
||||
pos:Position,
|
||||
type:Type,
|
||||
usings:Array<TypePath>,
|
||||
name:String,
|
||||
}
|
||||
|
||||
typedef BuildContext2 = {>BuildContext,
|
||||
type2:Type,
|
||||
}
|
||||
|
||||
typedef BuildContext3 = {>BuildContext2,
|
||||
type3:Type,
|
||||
}
|
||||
|
||||
class BuildCache {
|
||||
|
||||
@:persistent static var cache = new Map();
|
||||
|
||||
static public function getType3(name, ?types, ?pos:Position, build:BuildContext3->TypeDefinition, ?normalizer:Type->Type) {
|
||||
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
|
||||
}), normalizer);
|
||||
}
|
||||
|
||||
static public function getTypeN(name, ?types, ?pos:Position, build:BuildContextN->TypeDefinition, ?normalizer:Type->Type) {
|
||||
|
||||
if (pos == null)
|
||||
pos = Context.currentPos();
|
||||
|
||||
if (types == null)
|
||||
switch Context.getLocalType() {
|
||||
case TInst(_.toString() == name => true, params):
|
||||
types = params;
|
||||
default:
|
||||
throw 'assert';
|
||||
}
|
||||
|
||||
var compound = ComplexType.TAnonymous([for (i in 0...types.length) {
|
||||
name: 't$i',
|
||||
pos: pos,
|
||||
kind: FVar(switch types[i] {
|
||||
case TInst(_.get().kind => KExpr(e), _):
|
||||
TPath('tink.macro.ConstParam'.asTypePath([TPExpr(e)]));
|
||||
case t: t.toComplex();
|
||||
}),
|
||||
}]).toType();
|
||||
|
||||
return getType(name, compound, pos, function (ctx) return build({
|
||||
types: types,
|
||||
pos: ctx.pos,
|
||||
name: ctx.name,
|
||||
usings: ctx.usings
|
||||
}), normalizer);
|
||||
}
|
||||
|
||||
static public function getType2(name, ?types, ?pos:Position, build:BuildContext2->TypeDefinition, ?normalizer:Type->Type) {
|
||||
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
|
||||
}), normalizer);
|
||||
}
|
||||
|
||||
static public function getParams(name:String, ?pos:Position)
|
||||
return
|
||||
switch Context.getLocalType() {
|
||||
case TInst(_.toString() == name => true, v):
|
||||
Success(v);
|
||||
case TInst(_.get() => { pos: pos }, _):
|
||||
pos.makeFailure('Expected $name');
|
||||
case v:
|
||||
pos.makeFailure('$v should be a class');
|
||||
}
|
||||
|
||||
static public function getParam(name:String, ?pos:Position)
|
||||
return
|
||||
getParams(name, pos)
|
||||
.flatMap(function (args:Array<Type>) return switch args {
|
||||
case [v]: Success(v);
|
||||
case []: pos.makeFailure('type parameter expected');
|
||||
default: pos.makeFailure('too many parameters');
|
||||
});
|
||||
|
||||
static public function getType(name, ?type, ?pos:Position, build:BuildContext->TypeDefinition, ?normalizer:Type->Type) {
|
||||
|
||||
if (type == null)
|
||||
type = getParam(name, pos).sure();
|
||||
|
||||
var forName =
|
||||
switch cache[name] {
|
||||
case null: cache[name] = new Group(name, normalizer);
|
||||
case v: v;
|
||||
}
|
||||
|
||||
var ret = forName.get(type, pos.sanitize(), build);
|
||||
ret.getFields();// workaround for https://github.com/HaxeFoundation/haxe/issues/7905
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private typedef Entry = {
|
||||
name:String,
|
||||
}
|
||||
|
||||
private class Group {
|
||||
|
||||
var name:String;
|
||||
var counter = 0;
|
||||
var entries:TypeMap<Entry>;
|
||||
|
||||
public function new(name, ?normalizer) {
|
||||
this.name = name;
|
||||
this.entries = new TypeMap(normalizer);
|
||||
}
|
||||
|
||||
public function get(type:Type, pos:Position, build:BuildContext->TypeDefinition):Type {
|
||||
|
||||
function make(path:String) {
|
||||
var usings = [];
|
||||
var def = build({
|
||||
pos: pos,
|
||||
type: type,
|
||||
usings: usings,
|
||||
name: path.split('.').pop()
|
||||
});
|
||||
|
||||
entries.set(type, { name: path } );
|
||||
Context.defineModule(path, [def], usings);
|
||||
return Context.getType(path);
|
||||
}
|
||||
|
||||
function doMake()
|
||||
while (true)
|
||||
switch '$name${counter++}' {
|
||||
case _.definedType() => Some(_):
|
||||
case v:
|
||||
return make(v);
|
||||
}
|
||||
|
||||
return
|
||||
switch entries.get(type) {
|
||||
case null:
|
||||
doMake();
|
||||
case v:
|
||||
switch v.name.definedType() {
|
||||
case Some(v): v;
|
||||
default: doMake();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,183 +9,193 @@ using tink.MacroApi;
|
||||
using Lambda;
|
||||
|
||||
class ClassBuilder {
|
||||
|
||||
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)
|
||||
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>
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 ctorExpr = ctor.expr();
|
||||
if (ctorExpr == null) throw 'Super constructor has no expression';
|
||||
var func = Context.getTypedExpr(ctorExpr).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')
|
||||
tink.core.Error.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 addMembers(td:TypeDefinition):Array<Member> {
|
||||
for (f in td.fields)
|
||||
addMember(f);
|
||||
return td.fields;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
3
src/tink/macro/ConstParam.hx
Normal file
3
src/tink/macro/ConstParam.hx
Normal file
@@ -0,0 +1,3 @@
|
||||
package tink.macro;
|
||||
|
||||
class ConstParam<Const> {}
|
@@ -1,137 +1,209 @@
|
||||
package tink.macro;
|
||||
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import tink.core.Pair;
|
||||
using haxe.macro.Tools;
|
||||
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 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) 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)
|
||||
.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,
|
||||
}
|
||||
}
|
||||
}
|
||||
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 getArgList():Array<FunctionArg>
|
||||
return beforeArgs.concat(args).concat(afterArgs);
|
||||
|
||||
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 member = owner.memberByName(name).sure(),
|
||||
initStatement = macro @:pos(pos) this.$name = $e;
|
||||
|
||||
var bypass = switch options.bypass {
|
||||
case null:
|
||||
switch member.kind {
|
||||
case FProp(_, 'default' | 'null', _):
|
||||
false;
|
||||
#if haxe4
|
||||
case FProp('default', 'never', _):
|
||||
member.isFinal = true;
|
||||
false;
|
||||
case FProp(_, 'set', _):
|
||||
member.addMeta(':isVar');
|
||||
initStatement = macro @:pos(pos) @:bypassAccessor this.$name = $e;
|
||||
false;
|
||||
#end
|
||||
case FProp(_):
|
||||
true;
|
||||
case FFun(_):
|
||||
pos.error('cannot rebind function');
|
||||
default: false;
|
||||
}
|
||||
case v: v;
|
||||
}
|
||||
|
||||
if (bypass && member.kind.match(FProp(_, 'never' | 'set', _, _))) {
|
||||
|
||||
member.addMeta(':isVar');
|
||||
|
||||
addStatement((function () {
|
||||
var fields = [for (f in (macro this).typeof().sure().getClass().fields.get()) f.name => f];
|
||||
|
||||
function setDirectly(t:TypedExpr) {
|
||||
var direct = null;
|
||||
function seek(t:TypedExpr) {
|
||||
switch t.expr {
|
||||
case TField({ expr: TConst(TThis) }, FInstance(_, _, f)) if (f.get().name == name): direct = t;
|
||||
default: t.iter(seek);
|
||||
}
|
||||
}
|
||||
seek(t);
|
||||
if (direct == null) pos.error('nope');
|
||||
var direct = Context.storeTypedExpr(direct);
|
||||
return macro @:pos(pos) $direct = $e;
|
||||
}
|
||||
|
||||
return switch fields[name] {
|
||||
case null:
|
||||
pos.error('this direct initialization causes the compiler to do really weird things');
|
||||
case f:
|
||||
switch f.kind {
|
||||
case FVar(_, AccNormal | AccNo):
|
||||
macro @:pos(pos) this.$name = $e;
|
||||
case FVar(AccNever, AccNever):
|
||||
macro @:pos(pos) this.$name = $e;
|
||||
case FVar(AccNo | AccNormal, AccNever):
|
||||
setDirectly(Context.typeExpr(macro @:pos(pos) this.$name));
|
||||
#if haxe4
|
||||
case FVar(AccCall, _):
|
||||
var target = Context.storeTypedExpr(Context.typeExpr(macro @:pos(pos) @:bypassAccessor this.$name));
|
||||
macro @:pos(pos) $target = $e;
|
||||
#else
|
||||
case FVar(AccCall, AccNever):
|
||||
setDirectly(fields['get_$name'].expr());
|
||||
#end
|
||||
case FVar(_, AccCall):
|
||||
setDirectly(fields['set_$name'].expr());
|
||||
default:
|
||||
pos.error('not implemented');
|
||||
}
|
||||
}
|
||||
}).bounce(), options.prepend);
|
||||
}
|
||||
else
|
||||
addStatement(
|
||||
initStatement,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
src/tink/macro/DirectType.hx
Normal file
6
src/tink/macro/DirectType.hx
Normal file
@@ -0,0 +1,6 @@
|
||||
package tink.macro;
|
||||
|
||||
@:genericBuild(tink.macro.Types.resolveDirectType())
|
||||
class DirectType<Const> {
|
||||
|
||||
}
|
@@ -1,12 +1,11 @@
|
||||
package tink.macro;
|
||||
|
||||
import Type in Inspect;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.PosInfos;
|
||||
import haxe.macro.Printer;
|
||||
import haxe.DynamicAccess as Dict;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
@@ -14,384 +13,444 @@ 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;
|
||||
function iterator():Iterator<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);
|
||||
}
|
||||
|
||||
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;
|
||||
return try {
|
||||
haxe.macro.ExprTools.iter(e, seek);
|
||||
false;
|
||||
}
|
||||
catch (e:Heureka) true;
|
||||
}
|
||||
|
||||
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 is(e:Expr, c:ComplexType)
|
||||
return e.as(c).typeof().isSuccess();
|
||||
|
||||
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";
|
||||
}
|
||||
static public inline function as(e:Expr, c:ComplexType)
|
||||
return ECheckType(e, c).at(e.pos);
|
||||
|
||||
static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dict<String>, ?skipFields = false, ?callPos:PosInfos) {
|
||||
if (nuPos == null)
|
||||
nuPos = Context.currentPos();
|
||||
if (rules == null)
|
||||
rules = { };
|
||||
|
||||
function replace(s:String):String
|
||||
return switch rules[s] {
|
||||
case null:
|
||||
if (s.startsWith('tmp')) {
|
||||
rules[s] = MacroApi.tempName(s.substr(3));
|
||||
replace(s);
|
||||
}
|
||||
else s;
|
||||
case v: v;
|
||||
}
|
||||
|
||||
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:Dict<Expr>, ?pos)
|
||||
return
|
||||
transform(source, function (e:Expr) {
|
||||
return switch e {
|
||||
case macro $i{name}:
|
||||
switch vars[name] {
|
||||
case null: e;
|
||||
case v: v;
|
||||
}
|
||||
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:Position):Expr {
|
||||
if (!subst.iterator().hasNext())
|
||||
return source;
|
||||
|
||||
function replace(ct:ComplexType)
|
||||
return switch ct {
|
||||
case TPath({ pack: [], name: name }) if (subst.exists(name)):
|
||||
subst.get(name);
|
||||
default: ct;
|
||||
}
|
||||
|
||||
return source
|
||||
.transform(
|
||||
function (e) return switch e {
|
||||
case macro $i{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;
|
||||
})
|
||||
.mapTypes(replace);
|
||||
}
|
||||
|
||||
static public function mapTypes(e:Expr, transformer:ComplexType->ComplexType, ?pos:Position) {
|
||||
return e.transform(
|
||||
function (e) return switch e {
|
||||
case { expr: ENew(p, args) }:
|
||||
switch TPath(p).map(transformer) {
|
||||
case TPath(nu):
|
||||
ENew(nu, args).at(e.pos);
|
||||
case v:
|
||||
pos.error(v.toString() + ' cannot be instantiated in ${e.pos}');
|
||||
}
|
||||
case macro cast($v, $ct):
|
||||
ct = ct.map(transformer);
|
||||
(macro @:pos(e.pos) cast($v, $ct));
|
||||
case { expr: ECheckType(v, ct) }: // cannot use reification, because that's `EParentheses(ECheckType)` and macros can (and apparently do) generate a non-wrapped one
|
||||
ECheckType(v, ct.map(transformer)).at(e.pos);
|
||||
case { expr: EVars(vars) }:
|
||||
EVars([for (v in vars) {
|
||||
name: v.name,
|
||||
type: v.type.map(transformer),
|
||||
expr: v.expr,
|
||||
}]).at(e.pos);
|
||||
case { expr: EFunction(kind, f) }:
|
||||
EFunction(kind, Functions.mapSignature(f, transformer)).at(e.pos);
|
||||
default: e;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr {
|
||||
function apply(e:Expr)
|
||||
return switch e {
|
||||
case null | { expr: null }: e;
|
||||
default: transformer(haxe.macro.ExprTools.map(e, apply));
|
||||
}
|
||||
|
||||
return apply(source);
|
||||
}
|
||||
|
||||
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):
|
||||
ESwitch(e, [for (c in cases) {
|
||||
expr: rec(c.expr),
|
||||
guard: c.guard,
|
||||
values: c.values,
|
||||
}], 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 public inline function iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position)
|
||||
return macro @:pos(pos.sanitize()) for ($i{loopVar} in $target) $body;
|
||||
|
||||
static public function toFields(object:Dict<Expr>, ?pos:Position)
|
||||
return EObjectDecl([for (field in object.keys())
|
||||
{ field:field, expr: 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 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 var scopes = new Array<Array<Var>>();
|
||||
|
||||
static function inScope<T>(a:Array<Var>, f:Void->T) {
|
||||
scopes.push(a);
|
||||
|
||||
inline function leave()
|
||||
scopes.pop();
|
||||
try {
|
||||
var ret = f();
|
||||
leave();
|
||||
return ret;
|
||||
}
|
||||
catch (e:Dynamic) {
|
||||
leave();
|
||||
return Error.rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
static public function scoped<T>(f:Void->T)
|
||||
return inScope([], f);
|
||||
|
||||
static public function inSubScope<T>(f:Void->T, a:Array<Var>)
|
||||
return inScope(switch scopes[scopes.length - 1] {
|
||||
case null: a;
|
||||
case v: v.concat(a);
|
||||
}, f);
|
||||
|
||||
static public function typeof(expr:Expr, ?locals)
|
||||
return
|
||||
try {
|
||||
if (locals == null)
|
||||
locals = scopes[scopes.length - 1];
|
||||
if (locals != null)
|
||||
expr = [EVars(locals).at(expr.pos), expr].toMBlock(expr.pos);
|
||||
Success(Context.typeof(expr));
|
||||
}
|
||||
catch (e:haxe.macro.Error) {
|
||||
e.pos.makeFailure(e.message);
|
||||
}
|
||||
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 public function concat(e:Expr, with:Expr, ?pos) {
|
||||
if(pos == null) pos = e.pos;
|
||||
return
|
||||
switch [e.expr, with.expr] {
|
||||
case [EBlock(e1), EBlock(e2)]: EBlock(e1.concat(e2)).at(pos);
|
||||
case [EBlock(e1), e2]: EBlock(e1.concat([with])).at(pos);
|
||||
case [e1, EBlock(e2)]: EBlock([e].concat(e2)).at(pos);
|
||||
default: EBlock([e, with]).at(pos);
|
||||
}
|
||||
}
|
||||
|
||||
static var FIRST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
static var LATER = FIRST + '0123456789';
|
||||
|
||||
static public function shortIdent(i:Int) {
|
||||
var ret = FIRST.charAt(i % FIRST.length);
|
||||
|
||||
i = Std.int(i / FIRST.length);
|
||||
|
||||
while (i > 0) {
|
||||
ret += LATER.charAt(i % LATER.length);
|
||||
i = Std.int(i / LATER.length);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
@@ -4,30 +4,55 @@ 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;
|
||||
}
|
||||
#if haxe4
|
||||
private abstract Kind(FunctionKind) from FunctionKind to FunctionKind {
|
||||
@:from static function ofString(s:String):Kind
|
||||
return FNamed(s);
|
||||
}
|
||||
#else
|
||||
private typedef Kind = String;
|
||||
#end
|
||||
|
||||
class Functions {
|
||||
static public inline function asExpr(f:Function, ?kind:Kind, ?pos)
|
||||
return EFunction(kind, 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 mapSignature(f:Function, transform):Function
|
||||
return {
|
||||
ret: Types.map(f.ret, transform),
|
||||
args: [for (a in f.args) {
|
||||
name: a.name,
|
||||
value: a.value,
|
||||
type: Types.map(a.type, transform),
|
||||
#if haxe4
|
||||
meta: a.meta,
|
||||
#end
|
||||
}],
|
||||
expr: f.expr,
|
||||
params: Types.mapTypeParamDecls(f.params, transform),
|
||||
}
|
||||
|
||||
static public function getArgIdents(f:Function):Array<Expr> {
|
||||
var ret = [];
|
||||
for (arg in f.args)
|
||||
ret.push(arg.name.resolve());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@@ -4,190 +4,216 @@ 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', nowrite ? 'null' : 'set', 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 meta(get, set):Metadata;
|
||||
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>;
|
||||
#if haxe4
|
||||
/**
|
||||
* Setting this to true will erase any getters/setters.
|
||||
*/
|
||||
public var isFinal(get, set):Bool;
|
||||
function get_isFinal() return hasAccess(AFinal);
|
||||
function set_isFinal(param) {
|
||||
if (setAccess(AFinal, param)) //TODO: perhaps also do something about AInline
|
||||
switch kind {
|
||||
case FProp(_, _, t, e):
|
||||
kind = FVar(t, e);
|
||||
default:
|
||||
}
|
||||
return param;
|
||||
}
|
||||
#end
|
||||
|
||||
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 function metaNamed(name)
|
||||
return
|
||||
if (this.meta == null) [];
|
||||
else [for (tag in this.meta) if (tag.name == name) tag];
|
||||
|
||||
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_meta() return switch this.meta {
|
||||
case null: this.meta = [];
|
||||
case v: v;
|
||||
}
|
||||
inline function set_meta(param) return this.meta = param;
|
||||
|
||||
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) return setAccess(AOverride, param);
|
||||
|
||||
inline function get_isStatic() return hasAccess(AStatic);
|
||||
inline function set_isStatic(param) return setAccess(AStatic, 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 setAccess(a:Access, isset:Bool) {
|
||||
changeAccess(
|
||||
isset ? a : null,
|
||||
isset ? null : a
|
||||
);
|
||||
return isset;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -2,20 +2,25 @@ 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 var printer = new haxe.macro.Printer();
|
||||
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];
|
||||
|
||||
static public inline function toString(m:MetadataEntry) {
|
||||
return printer.printMetadata(m);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -8,47 +8,57 @@ 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): 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
|
||||
switch Std.instance(error, tink.core.Error) {
|
||||
case null: errorFunc(sanitize(pos), Std.string(error));
|
||||
case error:
|
||||
errorFunc(
|
||||
error.pos,
|
||||
error.message +
|
||||
if (pos == null) ''
|
||||
else ' (referenced from ' + Std.string(pos).substr(5)
|
||||
);
|
||||
}
|
||||
|
||||
static function contextError(pos:Position, error:String):Dynamic
|
||||
return Context.fatalError(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 {
|
||||
|
135
src/tink/macro/Sisyphus.hx
Normal file
135
src/tink/macro/Sisyphus.hx
Normal file
@@ -0,0 +1,135 @@
|
||||
package tink.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class Sisyphus {
|
||||
|
||||
static function nullable(complexType : ComplexType) : ComplexType return macro : Null<$complexType>;
|
||||
|
||||
static function toField(cf : ClassField) : Field return {
|
||||
function varAccessToString(va : VarAccess, getOrSet : String) : String return {
|
||||
switch (va) {
|
||||
case AccNormal: "default";
|
||||
case AccNo: "null";
|
||||
case AccNever: "never";
|
||||
case AccResolve: throw "Invalid TAnonymous";
|
||||
case AccCall: getOrSet;
|
||||
case AccInline: "default";
|
||||
case AccRequire(_, _): "default";
|
||||
default: throw "not implemented";
|
||||
}
|
||||
}
|
||||
if (cf.params.length == 0) {
|
||||
name: cf.name,
|
||||
doc: cf.doc,
|
||||
access:
|
||||
(cf.isPublic ? [ APublic ] : [ APrivate ])
|
||||
#if haxe4 .concat(if (cf.isFinal) [AFinal] else []) #end
|
||||
,
|
||||
kind: switch([ cf.kind, cf.type ]) {
|
||||
#if haxe4
|
||||
case [ FVar(_, _), ret ] if (cf.isFinal):
|
||||
FVar(toComplexType(ret), null);
|
||||
#end
|
||||
case [ FVar(read, write), ret ]:
|
||||
FProp(
|
||||
varAccessToString(read, "get"),
|
||||
varAccessToString(write, "set"),
|
||||
toComplexType(ret),
|
||||
null
|
||||
);
|
||||
case [ FMethod(_), TFun(args, ret) ]:
|
||||
FFun({
|
||||
args: [
|
||||
for (a in args) {
|
||||
name: a.name,
|
||||
opt: a.opt,
|
||||
type: toComplexType(a.t),
|
||||
}
|
||||
],
|
||||
ret: toComplexType(ret),
|
||||
expr: null,
|
||||
});
|
||||
default:
|
||||
throw "Invalid TAnonymous";
|
||||
},
|
||||
pos: cf.pos,
|
||||
meta: cf.meta.get(),
|
||||
} else {
|
||||
throw "Invalid TAnonymous";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function toComplexType(type : Null<Type>) : Null<ComplexType> return {
|
||||
inline function direct()
|
||||
return Types.toComplex(type, { direct: true });
|
||||
switch (type) {
|
||||
case null:
|
||||
null;
|
||||
case TEnum(_.get().isPrivate => true, _): direct();
|
||||
case TInst(_.get().isPrivate => true, _): direct();
|
||||
case TType(_.get().isPrivate => true, _): direct();
|
||||
case TAbstract(_.get().isPrivate => true, _): direct();
|
||||
case TMono(_): direct();
|
||||
case TEnum(_.get() => baseType, params):
|
||||
TPath(toTypePath(baseType, params));
|
||||
case TInst(_.get() => classType, params):
|
||||
switch (classType.kind) {
|
||||
case KTypeParameter(_):
|
||||
var ct = Types.asComplexType(classType.name);
|
||||
switch Types.toType(ct) {
|
||||
case Success(TInst(_.get() => cl, _)) if (
|
||||
cl.kind.match(KTypeParameter(_))
|
||||
&& cl.module == classType.module
|
||||
&& cl.pack.join('.') == classType.pack.join('.')
|
||||
): ct;
|
||||
default:
|
||||
direct();
|
||||
}
|
||||
default:
|
||||
TPath(toTypePath(classType, params));
|
||||
}
|
||||
case TType(_.get() => baseType, params):
|
||||
TPath(toTypePath(baseType, params));
|
||||
case TFun(args, ret):
|
||||
TFunction(
|
||||
[ for (a in args) a.opt ? nullable(toComplexType(a.t)) : toComplexType(a.t) ],
|
||||
toComplexType(ret));
|
||||
case TAnonymous(_.get() => { fields: fields }):
|
||||
TAnonymous([ for (cf in fields) toField(cf) ]);
|
||||
case TDynamic(t):
|
||||
if (t == null) {
|
||||
macro : Dynamic;
|
||||
} else {
|
||||
var ct = toComplexType(t);
|
||||
macro : Dynamic<$ct>;
|
||||
}
|
||||
case TLazy(f):
|
||||
toComplexType(f());
|
||||
case TAbstract(_.get() => baseType, params):
|
||||
TPath(toTypePath(baseType, params));
|
||||
default:
|
||||
throw "Invalid type";
|
||||
}
|
||||
}
|
||||
static function toTypePath(baseType : BaseType, params : Array<Type>) : TypePath return {
|
||||
var module = baseType.module;
|
||||
var name = module.substring(module.lastIndexOf(".") + 1);
|
||||
var sub = switch baseType.name {
|
||||
case _ == name => true: null;
|
||||
case v: v;
|
||||
}
|
||||
|
||||
{
|
||||
pack: baseType.pack,
|
||||
name: name,
|
||||
sub: sub,
|
||||
params: [for (t in params) switch t {
|
||||
case TInst(_.get().kind => KExpr(e), _): TPExpr(e);
|
||||
default: TPType(toComplexType(t));
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
40
src/tink/macro/TypeMap.hx
Normal file
40
src/tink/macro/TypeMap.hx
Normal file
@@ -0,0 +1,40 @@
|
||||
package tink.macro;
|
||||
|
||||
import haxe.Constraints.IMap;
|
||||
import haxe.ds.BalancedTree;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Type;
|
||||
|
||||
using tink.MacroApi;
|
||||
|
||||
class TypeMap<V> extends BalancedTree<Type, V> implements IMap<Type, V> {
|
||||
var normalize:Type->Type;
|
||||
|
||||
public function new(?normalize:Type->Type) {
|
||||
this.normalize = switch normalize {
|
||||
case null: function (t) return t.reduce();
|
||||
case fn: fn;
|
||||
}
|
||||
super();
|
||||
}
|
||||
|
||||
override function compare(k1:Type, k2:Type):Int
|
||||
return normalize(k1).compare(normalize(k2), false);
|
||||
|
||||
static public function noFollow(t:Type)
|
||||
return t;
|
||||
|
||||
static public function keepNull(t:Type):Type
|
||||
return switch t {
|
||||
case TAbstract(_.get() => { name: 'Null', pack: [] }, [t])
|
||||
#if !haxe4 | TType(_.get() => { name: 'Null', pack: []}, [t]) #end
|
||||
:
|
||||
var ct = keepNull(t).toComplex({ direct: true });
|
||||
(macro : Null<$ct>).toType().sure();
|
||||
|
||||
case TLazy(f): keepNull(f());
|
||||
case TType(_, _): keepNull(Context.follow(t, true));
|
||||
default: t;
|
||||
}
|
||||
|
||||
}
|
@@ -1,170 +1,371 @@
|
||||
package tink.macro;
|
||||
|
||||
import haxe.macro.Printer;
|
||||
import Type in Enums;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
using tink.macro.Exprs;
|
||||
using tink.macro.Positions;
|
||||
using tink.macro.Functions;
|
||||
using tink.core.Outcome;
|
||||
|
||||
class Types {
|
||||
static var types = new Map<Int,Void->Type>();
|
||||
static var idCounter = 0;
|
||||
|
||||
@: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 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,
|
||||
});
|
||||
package tink.macro;
|
||||
|
||||
import haxe.macro.Printer;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using tink.MacroApi;
|
||||
using tink.CoreApi;
|
||||
|
||||
class Types {
|
||||
|
||||
static public function definedType(typeName:String)
|
||||
return
|
||||
try {
|
||||
Some(Context.getType(typeName));
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
if (Std.string(e) == 'Type not found \'$typeName\'') None;
|
||||
else tink.core.Error.rethrow(e);
|
||||
|
||||
static var types = new Map<Int,Void->Type>();
|
||||
static var idCounter = 0;
|
||||
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 public function getMeta(type:Type)
|
||||
return switch type {
|
||||
case TInst(_.get().meta => m, _): [m];
|
||||
case TEnum(_.get().meta => m, _): [m];
|
||||
case TAbstract(_.get().meta => m, _): [m];
|
||||
case TType(_.get() => t, _): [t.meta].concat(getMeta(t.type));
|
||||
case TLazy(f): getMeta(f());
|
||||
default: [];
|
||||
}
|
||||
|
||||
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 public function mapTypeParams(a:Array<TypeParam>, f)
|
||||
return
|
||||
if (a == null) null;
|
||||
else [for (p in a) switch p {
|
||||
case TPType(t): TPType(t.map(f));
|
||||
default: p;
|
||||
}];
|
||||
|
||||
static public function mapTypeParamDecls(a:Array<TypeParamDecl>, f:ComplexType->ComplexType):Array<TypeParamDecl>
|
||||
return
|
||||
if (a == null) null;
|
||||
else [for (p in a) {
|
||||
name: p.name,
|
||||
meta: p.meta,
|
||||
params: mapTypeParamDecls(p.params, f),
|
||||
constraints: switch p.constraints {
|
||||
case null: null;
|
||||
case a: [for (t in a) map(t, f)];
|
||||
}
|
||||
}];
|
||||
|
||||
static public function mapTypePath(p:TypePath, transform):TypePath
|
||||
return {
|
||||
name: p.name,
|
||||
pack: p.pack,
|
||||
sub: p.sub,
|
||||
params: mapTypeParams(p.params, transform),
|
||||
}
|
||||
|
||||
static public function map(ct:ComplexType, transform:ComplexType->ComplexType) {
|
||||
if (ct == null)
|
||||
return null;
|
||||
inline function rec(ct)
|
||||
return map(transform(ct), transform);
|
||||
|
||||
function mapFields(fields:Array<Field>):Array<Field>
|
||||
return [for (f in fields) {
|
||||
name: f.name,
|
||||
pos: f.pos,
|
||||
kind: switch f.kind {
|
||||
case FVar(t, e): FVar(rec(t), e);
|
||||
case FProp(get, set, t, e): FProp(get, set, rec(t), e);
|
||||
case FFun(f): FFun(Functions.mapSignature(f, transform));
|
||||
},
|
||||
access: f.access,
|
||||
meta: switch f.meta {
|
||||
case null: null;
|
||||
case a: [for (m in a) {
|
||||
name: m.name,
|
||||
pos: m.pos,
|
||||
params: [for (e in m.params) e.mapTypes(transform)],
|
||||
}];
|
||||
},
|
||||
doc: f.doc,
|
||||
}];
|
||||
return transform(switch ct {
|
||||
case TParent(t): TParent(rec(t));
|
||||
#if haxe4
|
||||
case TNamed(n, t): TNamed(n, rec(t));
|
||||
case TIntersection(tl): TIntersection([for (t in tl) rec(t)]);
|
||||
#end
|
||||
case TOptional(t): TOptional(rec(t));
|
||||
case TAnonymous(fields):
|
||||
TAnonymous(mapFields(fields));
|
||||
case TPath(p):
|
||||
TPath(mapTypePath(p, transform));
|
||||
case TExtend(paths, fields):
|
||||
TExtend([for (p in paths) mapTypePath(p, transform)], mapFields(fields));
|
||||
case TFunction(args, ret):
|
||||
TFunction([for (a in args) rec(a)], rec(ret));
|
||||
});
|
||||
}
|
||||
|
||||
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()) {
|
||||
ret = Success([
|
||||
for (field in ret.sure()) ({
|
||||
name: field.name,
|
||||
type: haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params),
|
||||
pos: field.pos,
|
||||
meta: field.meta,
|
||||
doc: field.doc,
|
||||
expr: function () return field.expr(),
|
||||
isPublic: field.isPublic,
|
||||
params: field.params,
|
||||
kind: field.kind,
|
||||
overloads: field.overloads,
|
||||
#if haxe4
|
||||
isExtern: field.isExtern,
|
||||
isFinal: field.isFinal,
|
||||
#end
|
||||
}:ClassField)
|
||||
]);
|
||||
}
|
||||
#if !haxe4
|
||||
fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache
|
||||
#end
|
||||
ret;
|
||||
case TAnonymous(anon): Success(anon.get().fields);
|
||||
default: Context.currentPos().makeFailure('type $t 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 getPosition(t:Type)
|
||||
return
|
||||
switch t {
|
||||
case TInst(_.get() => {pos: pos}, _)
|
||||
| TAbstract(_.get() => {pos: pos}, _)
|
||||
| TType(_.get() => {pos: pos}, _)
|
||||
| TEnum(_.get() => {pos: pos}, _) : Success(pos);
|
||||
case TMono(_.get() => t) if (t != null): getPosition(t);
|
||||
case TLazy(f): getPosition(f());
|
||||
case TDynamic(v) if(v != null): getPosition(v);
|
||||
default: Failure('type "$t" has no position');
|
||||
}
|
||||
|
||||
|
||||
|
||||
static public function toString(t:ComplexType)
|
||||
return new Printer().printComplexType(t);
|
||||
|
||||
static public function unifiesWith(from:Type, to:Type)
|
||||
return Context.unify(from, to);
|
||||
|
||||
static public function isSubTypeOf(t:Type, of:Type, ?pos)
|
||||
return
|
||||
if (Context.unify(t, of)) ECheckType(ECheckType(macro null, toComplex(t)).at(pos), toComplex(of)).at(pos).typeof();
|
||||
else Failure(new Error(t.toString() + ' should be ' + of.toString(), pos.sanitize()));
|
||||
|
||||
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 function reduce(type:Type, ?once) {
|
||||
function rec(t:Type)
|
||||
return if (once) t else reduce(t, false);
|
||||
return switch type {
|
||||
case TAbstract(_.get() => { name: 'Null', pack: [] }, [t]): rec(t);
|
||||
case TLazy(f): rec(f());
|
||||
case TType(_, _): rec(Context.follow(type, once));
|
||||
default: type;
|
||||
}
|
||||
}
|
||||
|
||||
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) tink.macro.Sisyphus.toComplexType(type);
|
||||
else null;
|
||||
if (ret == null)
|
||||
ret = lazyComplex(function () return type);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public function intersect(types:Array<ComplexType>, ?pos:Position):Outcome<ComplexType, Error> {
|
||||
|
||||
if (types.length == 1) return Success(types[1]);
|
||||
|
||||
var paths = [],
|
||||
fields = [];
|
||||
|
||||
for (t in types)
|
||||
switch t {
|
||||
case TPath(p): paths.push(p);
|
||||
case TAnonymous(f):
|
||||
|
||||
for (f in f) fields.push(f);
|
||||
|
||||
case TExtend(p, f):
|
||||
|
||||
for (f in f) fields.push(f);
|
||||
for (p in p) paths.push(p);
|
||||
|
||||
default:
|
||||
|
||||
return Failure(new Error(t.toString() + ' cannot be interesected', pos));
|
||||
}
|
||||
|
||||
return Success(TExtend(paths, fields));
|
||||
}
|
||||
|
||||
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()]();//When using compiler server, this call throws on occasion, in which case modifying this file (to update mtime and invalidate the cache) will solve the problem
|
||||
default:
|
||||
throw 'assert';
|
||||
}
|
||||
|
||||
static public function compare(t1:Type, t2:Type, ?follow:Bool = true) {
|
||||
if (follow) {
|
||||
t1 = t1.reduce();
|
||||
t2 = t2.reduce();
|
||||
}
|
||||
|
||||
return switch t1.getIndex() - t2.getIndex() {
|
||||
case 0:
|
||||
Reflect.compare(t1.toString(), t2.toString());//much to my surprise, this actually seems to work (at least with 3.4)
|
||||
case v: v;
|
||||
}
|
||||
}
|
||||
|
||||
static var SUGGESTIONS = ~/ \(Suggestions?: .*\)$/;
|
||||
|
||||
static public function getFieldSuggestions(type:ComplexType, name:String):String
|
||||
return switch (macro (null : $type).$name).typeof() {
|
||||
case Failure(SUGGESTIONS.match(_.message) => true): SUGGESTIONS.matched(0);
|
||||
default: '';
|
||||
}
|
||||
|
||||
static public function toDecl(p:TypeParameter):TypeParamDecl
|
||||
return {
|
||||
name: p.name,
|
||||
constraints: switch p.t {
|
||||
case TInst(_.get() => { kind: KTypeParameter(c)}, _): [for(c in c) c.toComplex()];
|
||||
case _: throw 'unreachable';
|
||||
}
|
||||
}
|
||||
|
||||
static public function toTypeParam(p:TypeParameter):TypeParam
|
||||
return TPType(p.t.toComplex());
|
||||
|
||||
}
|
@@ -1,7 +1,3 @@
|
||||
-cp ../tink_core/src
|
||||
-cp src
|
||||
-cp tests
|
||||
-D tink_macro
|
||||
-main Run
|
||||
-neko bin/tests.n
|
||||
#iota start Run.bat
|
||||
-lib hx3compat
|
||||
-main Run
|
@@ -1,5 +1,5 @@
|
||||
package ;
|
||||
|
||||
class Arrayish implements ArrayAccess<Int> {
|
||||
public var length:Int;
|
||||
public var length:Int;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
168
tests/Exprs.hx
168
tests/Exprs.hx
@@ -1,83 +1,99 @@
|
||||
package ;
|
||||
|
||||
import haxe.macro.Context;
|
||||
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 testShort() {
|
||||
for (i in 0...100) {
|
||||
var id = (100 * i).shortIdent();
|
||||
Context.parseInlineString(id, (macro null).pos);
|
||||
assertTrue(id.length <= 3);
|
||||
}
|
||||
}
|
||||
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 testConcat() {
|
||||
exprEq(macro {a; b;}, (macro a).concat(macro b));
|
||||
exprEq(macro {a; b; c;}, (macro {a; b;}).concat(macro c));
|
||||
exprEq(macro {a; b; c;}, (macro a).concat(macro {b; c;}));
|
||||
exprEq(macro {a; b; c; d;}, (macro {a; b;}).concat(macro {c; d;}));
|
||||
}
|
||||
}
|
11
tests/Functions.hx
Normal file
11
tests/Functions.hx
Normal file
@@ -0,0 +1,11 @@
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
using tink.MacroApi;
|
||||
|
||||
class Functions extends Base {
|
||||
function test() {
|
||||
var f:Function = (macro function () {}).getFunction().sure();
|
||||
f.asExpr('foo');
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
10
tests/Misc.hx
Normal file
10
tests/Misc.hx
Normal file
@@ -0,0 +1,10 @@
|
||||
import tink.MacroApi;
|
||||
import haxe.unit.TestCase;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Misc extends TestCase {
|
||||
function testMain() {
|
||||
assertEquals('Run', MacroApi.getMainClass().force());
|
||||
}
|
||||
}
|
1
tests/MyString.hx
Normal file
1
tests/MyString.hx
Normal file
@@ -0,0 +1 @@
|
||||
typedef MyString = String;
|
@@ -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());
|
||||
}
|
||||
}
|
50
tests/Run.hx
50
tests/Run.hx
@@ -3,27 +3,31 @@ 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(),
|
||||
new Functions(),
|
||||
new Misc(),
|
||||
];
|
||||
#end
|
||||
macro static function test() {
|
||||
var runner = new TestRunner();
|
||||
tink.macro.ClassBuilder;
|
||||
tink.macro.BuildCache;
|
||||
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!');
|
||||
}
|
||||
}
|
||||
}
|
48
tests/TypeMapTest.hx
Normal file
48
tests/TypeMapTest.hx
Normal file
@@ -0,0 +1,48 @@
|
||||
package;
|
||||
|
||||
import haxe.unit.TestCase;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using tink.MacroApi;
|
||||
|
||||
class TypeMapTest extends TestCase {
|
||||
|
||||
function testMap() {
|
||||
var t = new TypeMap();
|
||||
var t1 = (macro [{ foo: [{ bar: '5' }]}]).typeof().sure();
|
||||
var t2 = (macro [{ foo: [{ bar: 5 }]}]).typeof().sure();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function testNormalization() {
|
||||
for (settings in [
|
||||
{ count: 4, map: new TypeMap(TypeMap.noFollow)},
|
||||
{ count: 2, map: new TypeMap(TypeMap.keepNull)},
|
||||
{ count: 1, map: new TypeMap()},
|
||||
]) {
|
||||
|
||||
function set(ct:ComplexType)
|
||||
settings.map.set(ct.toType().sure(), ct);
|
||||
|
||||
set(macro : String);
|
||||
set(macro : MyString);
|
||||
set(macro : Null<String>);
|
||||
set(macro : Null<MyString>);
|
||||
|
||||
assertEquals(settings.count, Lambda.count(settings.map));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package ;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
@@ -7,41 +8,62 @@ 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());
|
||||
}
|
||||
|
||||
#if haxe4
|
||||
function testFinal() {
|
||||
var t = macro : {
|
||||
final foo:Int;
|
||||
};
|
||||
switch t.toType().sure() {
|
||||
case TAnonymous(_.get().fields => [f]): assertTrue(f.isFinal);
|
||||
default:
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function testExpr() {
|
||||
assertEquals('VarChar<255>', (macro : VarChar<255>).toType().sure().toComplex().toString());
|
||||
}
|
||||
|
||||
function testToComplex() {
|
||||
assertEquals('String', Context.getType('String').toComplex().toString());
|
||||
assertEquals('tink.CoreApi.Noise', Context.getType('tink.CoreApi.Noise').toComplex().toString());
|
||||
}
|
||||
}
|
||||
#end
|
1
tests/VarChar.hx
Normal file
1
tests/VarChar.hx
Normal file
@@ -0,0 +1 @@
|
||||
typedef VarChar<Const> = String;
|
Reference in New Issue
Block a user