Merge commit '289e516fe3d91b935e74c29e698d0d06815efd0d' as 'projects/tink_syntaxhub'
This commit is contained in:
1
projects/tink_syntaxhub/.gitignore
vendored
Normal file
1
projects/tink_syntaxhub/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bin
|
||||
4
projects/tink_syntaxhub/.haxerc
Normal file
4
projects/tink_syntaxhub/.haxerc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "4.2.5",
|
||||
"resolveLibs": "scoped"
|
||||
}
|
||||
51
projects/tink_syntaxhub/.travis.yml
Normal file
51
projects/tink_syntaxhub/.travis.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
stages:
|
||||
- test
|
||||
- deploy
|
||||
|
||||
language: node_js
|
||||
node_js: 8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/haxe
|
||||
|
||||
os:
|
||||
- linux
|
||||
# - osx
|
||||
|
||||
env:
|
||||
- HAXE_VERSION=3.4.7
|
||||
- HAXE_VERSION=latest
|
||||
|
||||
|
||||
before_install:
|
||||
- args=()
|
||||
- if [ "$HAXE_VERSION" == "latest" ]; then args+=(-lib); args+=(hx3compat); fi
|
||||
|
||||
install:
|
||||
- npm i -g lix
|
||||
- lix install haxe $HAXE_VERSION
|
||||
- lix download
|
||||
|
||||
script:
|
||||
- lix run travix interp "${args[@]}"
|
||||
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# - stage: test # should uncomment this when there is no matrix above (e.g. only one os, one env, etc)
|
||||
- stage: deploy
|
||||
os: linux
|
||||
install:
|
||||
- npm i -g lix
|
||||
- lix download
|
||||
script: skip
|
||||
env:
|
||||
- secure: "j3U6/DIvvuRRSls2Y4MOff7eEqVwjt9H6qdvvuPMvBshYB40mlAmkPzGxaVBZ1ia1DOqPmlCsHX3FwrQl+n4lAWPiyWzKaThK8Tb5TkOF1OJxHbbtzVz40ZTKBdOyowwEewxnFUO1eUs4NuOYHYTiZBPKA024oebm/1z7HJkx3k="
|
||||
|
||||
after_success:
|
||||
- lix run travix install
|
||||
- lix run travix release
|
||||
22
projects/tink_syntaxhub/LICENSE
Normal file
22
projects/tink_syntaxhub/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 haxetink
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
189
projects/tink_syntaxhub/README.md
Normal file
189
projects/tink_syntaxhub/README.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Tinkerbell Syntax Hub
|
||||
[](https://gitter.im/haxetink/public)
|
||||
|
||||
As you add more and more macros to a code base, they begin stepping onto each others feet. The issue in fact arose a lot in the development of `tink_lang` which for a long time had its own plugin in system to make it perform its magic in an orderly fashion. This plugin system has been extracted and expanded to `tink_syntaxhub` which provides a plugin architecture for 4 things:
|
||||
|
||||
1. A plugin point for additional macro based frontends
|
||||
2. A plugin point for expression level syntax sugar
|
||||
3. A plugin point for class wide build macros
|
||||
4. A plugin point for macros that need to modify just the main function
|
||||
|
||||
With the advent of `haxe.macro.Compiler.addGlobalMetadata` it is possible to define global build macros and that is what `tink_syntaxhub` does: register one global build macro that runs all of the plugins in an orderly fashion.
|
||||
|
||||
## Basic structure
|
||||
|
||||
The syntax hub is organized on `tink_priority` queues, which in allow for plugins to take priority over one another. This still means that if two libraries conflict, one of them must resolve the conflict by registering its steps so they no longer conflict with those of the other library (by either running sooner or later or whatever). While not perfect, it is a step forward from having to make changes for both libraries, possibly introducing more dependencies. Being based on `tink_priority`, a dependency is only loosely expressed against IDs, which are just arbitrary strings, although they should reflect fully qualified class names - they in fact do this by default.
|
||||
|
||||
## Additional macro based frontends
|
||||
|
||||
By using `haxe.macro.Context.onTypeNotFound`, you can add additional frontends to the haxe compiler. With `tink_syntaxhub` this should turn out a little less raw. A frontend is expressed like so:
|
||||
|
||||
```haxe
|
||||
interface FrontendPlugin {
|
||||
function extensions():Iterator<String>;
|
||||
function parse(file:String, context:FrontendContext):Void;
|
||||
}
|
||||
```
|
||||
|
||||
There's not much to it. Before we go into detail and look at what a FrontendContext is, let's have an example.
|
||||
|
||||
### Example Frontend
|
||||
|
||||
Let's build our own silly frontend! One that takes text files and turns them into classes with one static property.
|
||||
|
||||
```haxe
|
||||
import tink.syntaxhub.*;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class TxtFrontend implements FrontendPlugin {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function extensions()
|
||||
return ['txt'].iterator();
|
||||
|
||||
public function parse(file:String, context:FrontendContext):Void {
|
||||
|
||||
var text = sys.io.File.getContent(file);
|
||||
var pos = Context.makePosition({ file: file, min: 0, max: text.length });
|
||||
|
||||
context.getType().fields.push({
|
||||
name: 'TEXT',
|
||||
access: [AStatic, APublic],
|
||||
kind: FProp('default', 'null', macro : String, macro $v{text}),
|
||||
pos: pos,
|
||||
});
|
||||
}
|
||||
static function use()
|
||||
tink.SyntaxHub.frontends.whenever(new TxtFrontend());
|
||||
}
|
||||
```
|
||||
|
||||
Put a `HelloWorld.txt` in your classpath and compile this with `haxe --macro TxtFrontend.use() -main Main --interp` :
|
||||
|
||||
```haxe
|
||||
class Main {
|
||||
static function main()
|
||||
trace(HelloWorld.TEXT);
|
||||
}
|
||||
```
|
||||
|
||||
Et voila! Awesome sauce! So hey, why not do the same for XMLs?
|
||||
|
||||
```haxe
|
||||
import tink.syntaxhub.*;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class XmlFrontend implements FrontendPlugin {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function extensions()
|
||||
return ['xml'].iterator();
|
||||
|
||||
public function parse(file:String, context:FrontendContext):Void {
|
||||
|
||||
var text = sys.io.File.getContent(file);
|
||||
var pos = Context.makePosition({ file: file, min: 0, max: text.length });
|
||||
|
||||
try
|
||||
Xml.parse(text)
|
||||
catch (e:Dynamic)
|
||||
Context.error('Failed to parse $file because: $e', pos);
|
||||
|
||||
context.getType().fields.push({
|
||||
name: 'XML',
|
||||
access: [AStatic, APublic],
|
||||
kind: FProp('default', 'null', macro : Xml, macro Xml.parse($v{text})),
|
||||
pos: pos,
|
||||
});
|
||||
}
|
||||
static function use()
|
||||
tink.SyntaxHub.frontends.whenever(new XmlFrontend());
|
||||
}
|
||||
```
|
||||
|
||||
Add a `HelloWorld.xml` in your classpath and this time compile with `haxe --macro TxtFrontend.use() --macro XmlFrontend.use() -main Main --interp`:
|
||||
|
||||
```haxe
|
||||
class Main {
|
||||
static function main() {
|
||||
trace(HelloWorld.TEXT);
|
||||
trace(HelloWorld.XML);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
So now both frontends affect the same class. That was easy, right? You can use the tests to see a working setup.
|
||||
|
||||
### The Frontend API
|
||||
|
||||
Let's recall what a frontend is:
|
||||
|
||||
```haxe
|
||||
interface FrontendPlugin {
|
||||
function extensions():Iterator<String>;
|
||||
function parse(file:String, context:FrontendContext):Void;
|
||||
}
|
||||
```
|
||||
|
||||
When the compiler cannot find a specific file, the syntax hub looks through all classpaths looking for files that have extensions matching any of the registered frontends and then leaves the parsing to said frontends. In the above example, we asked for `HelloWorld`, for which no `.hx` file exists. The two frontends jumped in and declared the class and each added a static field to it.
|
||||
|
||||
Now to understand *how* a frontend would do its work, we need to know what `FrontendContext` is. A context represents an interface to building the module that was not found by the Haxe compiler. This is what it looks like:
|
||||
|
||||
```haxe
|
||||
class FrontendContext {
|
||||
|
||||
public var name(default, null):String;
|
||||
public var pack(default, null):Array<String>;
|
||||
|
||||
public function getType(?name:String, ?orCreate:tink.core.Lazy<TypeDefinition>):TypeDefinition;
|
||||
|
||||
public function addDependency(file:String):Void;
|
||||
public function addImport(name:String, mode:ImportMode, pos:Position):Void;
|
||||
public function addUsing(name:String, pos:Position):Void;
|
||||
}
|
||||
```
|
||||
|
||||
First we have the name and the package of the module beeing processed. The last three calls are also quite self explanatory, assuming you are familiar with `haxe.macro.Context`. The little magic there is, is in `getType`, which if no name is supplied gets the module's main type. If the requested type was not yet created, you get to create one with the `orCreate` argument. It defaults to `macro class {}` but you may find more complex use cases.
|
||||
|
||||
### Registering Frontends
|
||||
|
||||
You register a `FrontendPlugin` on the `tink.SyntaxHub.frontends` priority queue. No magic here.
|
||||
|
||||
### Implement frontend as class level macro
|
||||
|
||||
The suggested way of implementing a frontend is to actually by pushing down the heavy lifting to a class level macro. So instead of constructing the whole class in your `FrontendPlugin` it is wiser to generate an empty class with a `@:build` directive that then fills the class. This approach leads to more understandable error messages and also helps to reduce loops.
|
||||
|
||||
## Expression level syntax sugar
|
||||
|
||||
Under `tink.SyntaxHub.exprLevel` you will find an object defined like this:
|
||||
|
||||
```haxe
|
||||
class ExprLevelSyntax {
|
||||
public var inward(default, null):Queue<ExprLevelRule>;
|
||||
public var outward(default, null):Queue<ExprLevelRule>;
|
||||
public var id(default, null):ID;
|
||||
}
|
||||
|
||||
typedef ExprLevelRule = {
|
||||
function appliesTo(c:ClassBuilder):Bool;
|
||||
function apply(e:Expr):Expr;
|
||||
}
|
||||
```
|
||||
|
||||
First, let's examine what an `ExprLevelRule` is. That's where you plugin in your magic. The `appliesTo` method should tell `tink_syntaxhub` whether the rule should be applied to the current class, and if so, `apply` is given practically every expression found in that class. For example all `tink_lang` syntax rules implement their `appliesTo` function with `c.target.meta.has(':tink')`. Your implementation of `appliesTo` should not cause side effects if possible.
|
||||
|
||||
Now, what's the `inward` and `outward` stuff all about? When the rules are applied, complex expressions are first traversed inward, i.e. from the outside to the inside or from the root to the leafs if you will, and then back outward. This nuance becomes particularly interesting when certain syntaxes are being nested into one another.
|
||||
|
||||
## Class level syntax sugar
|
||||
|
||||
You will find `tink.SyntaxHub.classLevel` to define a `Queue<ClassBuilder->Bool>`. All registered plugins are called in order of priority and if none of them returns `true`, then the class will be considered unmodified and the build macro will thus return `null`.
|
||||
|
||||
In this queue, there is already one item under the the same ID as `tink.SyntaxHub.exprLevel.id`. Use that to either run before or after expression level plugins.
|
||||
|
||||
## Modifying the main function
|
||||
|
||||
This is no doubt the least spectacular bit. You will find `tink.SyntaxHub.mainTransform` to define a `Queue<Expr->Expr>`, which passes the main functions body to each plugin in order of priority. Nothing fancy, but very handy!
|
||||
1
projects/tink_syntaxhub/extraParams.hxml
Normal file
1
projects/tink_syntaxhub/extraParams.hxml
Normal file
@@ -0,0 +1 @@
|
||||
--macro tink.SyntaxHub.use()
|
||||
3
projects/tink_syntaxhub/haxe_libraries/hx3compat.hxml
Normal file
3
projects/tink_syntaxhub/haxe_libraries/hx3compat.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
# @install: lix --silent download "haxelib:/hx3compat#1.0.4" into hx3compat/1.0.4/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/hx3compat/1.0.4/haxelib/std
|
||||
-D hx3compat=1.0.4
|
||||
7
projects/tink_syntaxhub/haxe_libraries/hxnodejs.hxml
Normal file
7
projects/tink_syntaxhub/haxe_libraries/hxnodejs.hxml
Normal file
@@ -0,0 +1,7 @@
|
||||
# @install: lix --silent download "haxelib:/hxnodejs#12.1.0" into hxnodejs/12.1.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/hxnodejs/12.1.0/haxelib/src
|
||||
-D hxnodejs=12.1.0
|
||||
--macro allowPackage('sys')
|
||||
# should behave like other target defines and not be defined in macro context
|
||||
--macro define('nodejs')
|
||||
--macro _internal.SuppressDeprecated.run()
|
||||
3
projects/tink_syntaxhub/haxe_libraries/tink_chunk.hxml
Normal file
3
projects/tink_syntaxhub/haxe_libraries/tink_chunk.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
-D tink_chunk=0.2.0
|
||||
# @install: lix --silent download "haxelib:/tink_chunk#0.2.0" into tink_chunk/0.2.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/tink_chunk/0.2.0/haxelib/src
|
||||
8
projects/tink_syntaxhub/haxe_libraries/tink_cli.hxml
Normal file
8
projects/tink_syntaxhub/haxe_libraries/tink_cli.hxml
Normal file
@@ -0,0 +1,8 @@
|
||||
-D tink_cli=0.4.1
|
||||
# @install: lix --silent download "haxelib:/tink_cli#0.4.1" into tink_cli/0.4.1/haxelib
|
||||
-lib tink_io
|
||||
-lib tink_stringly
|
||||
-lib tink_macro
|
||||
-cp ${HAXE_LIBCACHE}/tink_cli/0.4.1/haxelib/src
|
||||
# Make sure docs are generated
|
||||
-D use-rtti-doc
|
||||
3
projects/tink_syntaxhub/haxe_libraries/tink_core.hxml
Normal file
3
projects/tink_syntaxhub/haxe_libraries/tink_core.hxml
Normal file
@@ -0,0 +1,3 @@
|
||||
# @install: lix --silent download "haxelib:/tink_core#2.1.0" into tink_core/2.1.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/tink_core/2.1.0/haxelib/src
|
||||
-D tink_core=2.1.0
|
||||
5
projects/tink_syntaxhub/haxe_libraries/tink_io.hxml
Normal file
5
projects/tink_syntaxhub/haxe_libraries/tink_io.hxml
Normal file
@@ -0,0 +1,5 @@
|
||||
-D tink_io=0.6.2
|
||||
# @install: lix --silent download "haxelib:/tink_io#0.6.2" into tink_io/0.6.2/haxelib
|
||||
-lib tink_chunk
|
||||
-lib tink_streams
|
||||
-cp ${HAXE_LIBCACHE}/tink_io/0.6.2/haxelib/src
|
||||
4
projects/tink_syntaxhub/haxe_libraries/tink_macro.hxml
Normal file
4
projects/tink_syntaxhub/haxe_libraries/tink_macro.hxml
Normal file
@@ -0,0 +1,4 @@
|
||||
# @install: lix --silent download "haxelib:/tink_macro#1.0.1" into tink_macro/1.0.1/haxelib
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_macro/1.0.1/haxelib/src
|
||||
-D tink_macro=1.0.1
|
||||
@@ -0,0 +1,3 @@
|
||||
-D tink_priority=0.1.3
|
||||
# @install: lix --silent download "gh://github.com/haxetink/tink_priority#ea736d31dc788aae703a2aa415c25d5b80d0e7d1" into tink_priority/0.1.3/github/ea736d31dc788aae703a2aa415c25d5b80d0e7d1
|
||||
-cp ${HAXE_LIBCACHE}/tink_priority/0.1.3/github/ea736d31dc788aae703a2aa415c25d5b80d0e7d1/src
|
||||
6
projects/tink_syntaxhub/haxe_libraries/tink_streams.hxml
Normal file
6
projects/tink_syntaxhub/haxe_libraries/tink_streams.hxml
Normal file
@@ -0,0 +1,6 @@
|
||||
-D tink_streams=0.3.2
|
||||
# @install: lix --silent download "haxelib:/tink_streams#0.3.2" into tink_streams/0.3.2/haxelib
|
||||
-lib tink_core
|
||||
-cp ${HAXE_LIBCACHE}/tink_streams/0.3.2/haxelib/src
|
||||
# temp for development, delete this file when pure branch merged
|
||||
-D pure
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
-cp src
|
||||
-D tink_syntaxhub
|
||||
--macro tink.SyntaxHub.use()
|
||||
-lib tink_priority
|
||||
6
projects/tink_syntaxhub/haxe_libraries/travix.hxml
Normal file
6
projects/tink_syntaxhub/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
projects/tink_syntaxhub/haxelib.json
Normal file
24
projects/tink_syntaxhub/haxelib.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "tink_syntaxhub",
|
||||
"description": "Hub for plugging in language extensions.",
|
||||
"classPath": "src",
|
||||
"dependencies": {
|
||||
"tink_priority": "",
|
||||
"tink_macro": ""
|
||||
},
|
||||
"url": "https://github.com/haxetink/tink_syntaxhub",
|
||||
"contributors": [
|
||||
"back2dos"
|
||||
],
|
||||
"version": "0.6.0",
|
||||
"releasenote": "Get main class from tink_macro, thus supporting main transforms on eval from Haxe 4.3 onwards.",
|
||||
"tags": [
|
||||
"tink",
|
||||
"cross",
|
||||
"utility",
|
||||
"macro",
|
||||
"syntax",
|
||||
"sugar"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
106
projects/tink_syntaxhub/src/tink/SyntaxHub.hx
Normal file
106
projects/tink_syntaxhub/src/tink/SyntaxHub.hx
Normal file
@@ -0,0 +1,106 @@
|
||||
package tink;
|
||||
|
||||
import haxe.macro.*;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.ds.Option;
|
||||
|
||||
import tink.macro.ClassBuilder;
|
||||
|
||||
import tink.priority.Queue;
|
||||
import tink.syntaxhub.*;
|
||||
|
||||
using tink.CoreApi;
|
||||
using haxe.macro.Tools;
|
||||
|
||||
class SyntaxHub {
|
||||
|
||||
static var MAIN:Null<String> = null;
|
||||
static var registered = false;
|
||||
static function use() {
|
||||
if (registered) {
|
||||
return;
|
||||
registered = true;
|
||||
}
|
||||
var args = Sys.args();
|
||||
|
||||
MAIN = tink.MacroApi.getMainClass().orNull();
|
||||
|
||||
FrontendContext.resetCache();
|
||||
classLevel.whenever(makeSyntax(exprLevel.appliedTo), exprLevel.id);//Apperently reinserting this every time is more reliable with the cache
|
||||
Context.onTypeNotFound(FrontendContext.findType);
|
||||
Compiler.addGlobalMetadata('', '@:build(tink.SyntaxHub.build())', true, true, false);
|
||||
|
||||
}
|
||||
|
||||
static function build():Array<Field>
|
||||
return
|
||||
switch Context.getLocalType() {
|
||||
case null: null;
|
||||
case TInst(_.get() => c, _):
|
||||
|
||||
var builder = new ClassBuilder();
|
||||
|
||||
var changed = false;
|
||||
|
||||
for (plugin in classLevel.getData())
|
||||
changed = plugin(builder) || changed;
|
||||
|
||||
changed = applyMainTransform(builder) || changed;
|
||||
|
||||
if (changed)
|
||||
builder.export(builder.target.meta.has(':explain'));
|
||||
else
|
||||
null;
|
||||
default: null;
|
||||
}
|
||||
|
||||
static public var classLevel(default, null) = new Queue<ClassBuilder->Bool>();
|
||||
static public var exprLevel(default, null) = new ExprLevelSyntax('tink.SyntaxHub::exprLevel');
|
||||
static public var transformMain(default, null) = new Queue<Expr->Expr>();
|
||||
|
||||
static public var frontends(get, never):Queue<FrontendPlugin>;
|
||||
|
||||
static inline function get_frontends()
|
||||
return FrontendContext.plugins;
|
||||
|
||||
static public function makeSyntax(rule:ClassBuilder->Option<Expr->Expr>):ClassBuilder->Bool
|
||||
return function (ctx:ClassBuilder)
|
||||
return switch rule(ctx) {
|
||||
case Some(rule):
|
||||
|
||||
function transform(f:Function)
|
||||
if (f.expr != null)
|
||||
f.expr = rule(f.expr);
|
||||
|
||||
if (ctx.hasConstructor())
|
||||
ctx.getConstructor().onGenerate(transform);
|
||||
|
||||
for (m in ctx)
|
||||
switch m.kind {
|
||||
case FFun(f): transform(f);
|
||||
case FProp(_, _, _, e), FVar(_, e):
|
||||
if (e != null)
|
||||
e.expr = rule(e).expr;//TODO: it might be better to just create a new kind, rather than modifying the expression in place
|
||||
}
|
||||
|
||||
true;
|
||||
case None:
|
||||
false;
|
||||
}
|
||||
|
||||
static function applyMainTransform(c:ClassBuilder)
|
||||
return
|
||||
if (c.target.pack.concat([c.target.name]).join('.') == MAIN) {
|
||||
var main = c.memberByName('main').sure();
|
||||
var f = main.getFunction().sure();
|
||||
|
||||
if (f.expr == null)
|
||||
f.expr = macro @:pos(main.pos) { };
|
||||
|
||||
for (rule in transformMain)
|
||||
f.expr = rule(f.expr);
|
||||
|
||||
true;
|
||||
}
|
||||
else false;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package tink.syntaxhub;
|
||||
|
||||
import haxe.ds.Option;
|
||||
import haxe.macro.Expr;
|
||||
import tink.macro.ClassBuilder;
|
||||
import tink.priority.ID;
|
||||
|
||||
import tink.priority.Queue;
|
||||
|
||||
using tink.MacroApi;
|
||||
|
||||
class ExprLevelSyntax {
|
||||
public var inward(default, null):Queue<ExprLevelRule>;
|
||||
public var outward(default, null):Queue<ExprLevelRule>;
|
||||
public var id(default, null):ID;
|
||||
|
||||
public function new(id) {
|
||||
this.inward = new Queue();
|
||||
this.outward = new Queue();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public function appliedTo(c:ClassBuilder):Option<Expr->Expr> {
|
||||
function getRelevant(q:Queue<ExprLevelRule>)
|
||||
return [for (p in q.getData()) if (p.appliesTo(c)) p];
|
||||
|
||||
var inward = getRelevant(inward),
|
||||
outward = getRelevant(outward);
|
||||
|
||||
if (inward.length + outward.length == 0)
|
||||
return None;
|
||||
|
||||
function apply(e:Expr)
|
||||
return
|
||||
if (e == null || e.expr == null) e;
|
||||
else
|
||||
switch e.expr {
|
||||
case EMeta( { name: ':diet' }, _): e;
|
||||
default:
|
||||
for (rule in inward)
|
||||
e = rule.apply(e);
|
||||
|
||||
e = e.map(apply);
|
||||
|
||||
for (rule in outward)
|
||||
e = rule.apply(e);
|
||||
|
||||
e;
|
||||
}
|
||||
|
||||
return Some(apply);
|
||||
}
|
||||
}
|
||||
|
||||
typedef ExprLevelRule = {
|
||||
function appliesTo(c:ClassBuilder):Bool;
|
||||
function apply(e:Expr):Expr;
|
||||
}
|
||||
160
projects/tink_syntaxhub/src/tink/syntaxhub/FrontendContext.hx
Normal file
160
projects/tink_syntaxhub/src/tink/syntaxhub/FrontendContext.hx
Normal file
@@ -0,0 +1,160 @@
|
||||
package tink.syntaxhub;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
import haxe.ds.Option;
|
||||
import tink.core.Lazy;
|
||||
import tink.priority.Queue;
|
||||
|
||||
using sys.FileSystem;
|
||||
using tink.MacroApi;
|
||||
|
||||
enum IncludeKind {
|
||||
KImport(i:ImportExpr);
|
||||
KUsing(u:TypePath);
|
||||
}
|
||||
|
||||
class FrontendContext {
|
||||
var types:Array<TypeDefinition>;
|
||||
public var name(default, null):String;
|
||||
public var pack(default, null):Array<String>;
|
||||
|
||||
var dependencies:Array<String>;
|
||||
var includes:Array<{ kind: IncludeKind, pos:Position }>;
|
||||
|
||||
function new(name, pack) {
|
||||
types = [];
|
||||
dependencies = [];
|
||||
includes = [];
|
||||
|
||||
this.name = name;
|
||||
this.pack = pack;
|
||||
}
|
||||
|
||||
public function getType(?name:String, ?orCreate:Lazy<TypeDefinition>) {
|
||||
if (name == null)
|
||||
name = this.name;
|
||||
|
||||
for (t in types) {
|
||||
if (t.name == name) return t;
|
||||
}
|
||||
|
||||
var ret =
|
||||
if (orCreate != null) orCreate.get();
|
||||
else macro class { };
|
||||
|
||||
ret.name = name;
|
||||
ret.pack = this.pack;
|
||||
|
||||
types.push(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public function addDependency(file:String)
|
||||
this.dependencies.push(file);
|
||||
|
||||
public function addImport(name:String, mode:ImportMode, pos:Position)
|
||||
includes.push({
|
||||
pos: pos,
|
||||
kind: KImport({
|
||||
mode: mode,
|
||||
path: [for (p in name.split('.')) {
|
||||
name: p,
|
||||
pos: pos,
|
||||
}]
|
||||
})
|
||||
});
|
||||
|
||||
public function addUsing(name:String, pos:Position)
|
||||
includes.push( {
|
||||
pos: pos,
|
||||
kind: KUsing(name.asTypePath())
|
||||
});
|
||||
|
||||
static public var plugins(default, null) = new Queue<FrontendPlugin>();
|
||||
|
||||
static function buildModule(pack:Array<String>, name:String) {
|
||||
var ret = new FrontendContext(name, pack);
|
||||
|
||||
for (result in seekFile(pack, name, plugins.getData())) {
|
||||
ret.addDependency(result.file);
|
||||
result.plugin.parse(result.file, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
static public function seekFile<T:FrontendPlugin>(pack:Array<String>, name:String, plugins:Iterable<T>) {
|
||||
var ret = [];
|
||||
for (cp in Context.getClassPath()) {
|
||||
var pack = pack.copy();
|
||||
pack.unshift(cp);
|
||||
pack.push(name);
|
||||
var fileName = haxe.io.Path.join(pack);
|
||||
for (p in plugins)
|
||||
for (ext in p.extensions()) {
|
||||
var candidate = '$fileName.$ext';
|
||||
if (candidate.exists())
|
||||
ret.push({ file: candidate, plugin: p });
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static function moduleForType(name:String) {
|
||||
if (name.indexOf('__impl') != -1 || plugins.getData().length == 0) return;
|
||||
var pack = name.split('.');
|
||||
var tname = pack.pop();
|
||||
var actual = pack.concat(['__impl', tname]).join('.');
|
||||
cache[name] = {
|
||||
pack: pack,
|
||||
name: tname,
|
||||
pos: Context.currentPos(),
|
||||
fields: [],
|
||||
kind: TDAlias(actual.asComplexType())
|
||||
}
|
||||
var exists =
|
||||
try {
|
||||
Context.getType(actual);
|
||||
true;
|
||||
}
|
||||
catch (e:Dynamic) false;
|
||||
|
||||
if (!exists) {
|
||||
var module = buildModule(pack, tname);
|
||||
if (module.types.length == 0) {
|
||||
cache[name] = null; // clean the entry, but not in a way we would try to build this again
|
||||
return;
|
||||
}
|
||||
|
||||
var imports = [],
|
||||
usings = [];
|
||||
|
||||
for (d in module.includes)
|
||||
switch d.kind {
|
||||
case KImport(i):
|
||||
imports.push(i);
|
||||
case KUsing(u):
|
||||
var ct = TPath(u);
|
||||
(macro @:pos(d.pos) ([][0] : $ct)).typeof().sure();
|
||||
usings.push(u);
|
||||
}
|
||||
|
||||
Context.defineModule(actual, module.types, imports, usings);
|
||||
for (d in module.dependencies)
|
||||
Context.registerModuleDependency(actual, d);
|
||||
}
|
||||
}
|
||||
|
||||
static var cache:Map<String,TypeDefinition>;
|
||||
static public function resetCache()
|
||||
cache = new Map();
|
||||
|
||||
@:noDoc
|
||||
static public function findType(name:String):TypeDefinition {
|
||||
if (!cache.exists(name))
|
||||
moduleForType(name);
|
||||
return cache[name];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package tink.syntaxhub;
|
||||
|
||||
interface FrontendPlugin {
|
||||
function extensions():Iterator<String>;
|
||||
function parse(file:String, context:FrontendContext):Void;
|
||||
}
|
||||
10
projects/tink_syntaxhub/tests.hxml
Normal file
10
projects/tink_syntaxhub/tests.hxml
Normal file
@@ -0,0 +1,10 @@
|
||||
-lib tink_core
|
||||
-lib tink_priority
|
||||
-lib tink_macro
|
||||
-cp src
|
||||
-cp tests
|
||||
-main Main
|
||||
extraParams.hxml
|
||||
-dce full
|
||||
--macro TxtFrontend.use()
|
||||
--macro XmlFrontend.use()
|
||||
1
projects/tink_syntaxhub/tests/HelloWorld.txt
Normal file
1
projects/tink_syntaxhub/tests/HelloWorld.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello, hello!!!
|
||||
2
projects/tink_syntaxhub/tests/HelloWorld.xml
Normal file
2
projects/tink_syntaxhub/tests/HelloWorld.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<data><3</data>
|
||||
19
projects/tink_syntaxhub/tests/Main.hx
Normal file
19
projects/tink_syntaxhub/tests/Main.hx
Normal file
@@ -0,0 +1,19 @@
|
||||
package;
|
||||
import haxe.unit.TestCase;
|
||||
import haxe.unit.TestRunner;
|
||||
|
||||
class Main extends TestCase {
|
||||
|
||||
function testTxt()
|
||||
assertEquals('Hello, hello!!!', HelloWorld.TEXT);
|
||||
|
||||
function testXml()
|
||||
assertEquals('<3', HelloWorld.XML.firstElement().firstChild().nodeValue);
|
||||
|
||||
static function main() {
|
||||
var t = new TestRunner();
|
||||
t.add(new Main());
|
||||
t.run();
|
||||
}
|
||||
|
||||
}
|
||||
27
projects/tink_syntaxhub/tests/TxtFrontend.hx
Normal file
27
projects/tink_syntaxhub/tests/TxtFrontend.hx
Normal file
@@ -0,0 +1,27 @@
|
||||
import tink.syntaxhub.*;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class TxtFrontend implements FrontendPlugin {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function extensions()
|
||||
return ['txt'].iterator();
|
||||
|
||||
public function parse(file:String, context:FrontendContext):Void {
|
||||
|
||||
var text = sys.io.File.getContent(file);
|
||||
var pos = Context.makePosition({ file: file, min: 0, max: text.length });
|
||||
|
||||
context.getType().fields.push({
|
||||
name: 'TEXT',
|
||||
access: [AStatic, APublic],
|
||||
kind: FProp('default', 'null', macro : String, macro $v{text}),
|
||||
pos: pos,
|
||||
});
|
||||
}
|
||||
static function use()
|
||||
tink.SyntaxHub.frontends.whenever(new TxtFrontend());
|
||||
}
|
||||
33
projects/tink_syntaxhub/tests/XmlFrontend.hx
Normal file
33
projects/tink_syntaxhub/tests/XmlFrontend.hx
Normal file
@@ -0,0 +1,33 @@
|
||||
package;
|
||||
|
||||
import tink.syntaxhub.*;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class XmlFrontend implements FrontendPlugin {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function extensions()
|
||||
return ['xml'].iterator();
|
||||
|
||||
public function parse(file:String, context:FrontendContext):Void {
|
||||
|
||||
var text = sys.io.File.getContent(file);
|
||||
var pos = Context.makePosition({ file: file, min: 0, max: text.length });
|
||||
|
||||
try
|
||||
Xml.parse(text)
|
||||
catch (e:Dynamic)
|
||||
Context.error('Failed to parse $file because: $e', pos);
|
||||
|
||||
context.getType().fields.push({
|
||||
name: 'XML',
|
||||
access: [AStatic, APublic],
|
||||
kind: FProp('default', 'null', macro : Xml, macro Xml.parse($v{text})),
|
||||
pos: pos,
|
||||
});
|
||||
}
|
||||
static function use()
|
||||
tink.SyntaxHub.frontends.whenever(new XmlFrontend());
|
||||
}
|
||||
9
projects/tink_syntaxhub/tink_syntaxhub.hxml
Normal file
9
projects/tink_syntaxhub/tink_syntaxhub.hxml
Normal file
@@ -0,0 +1,9 @@
|
||||
-lib tink_syntaxhub
|
||||
-cp tests
|
||||
-js bin/tinksyntaxhub.js
|
||||
-D analyzer
|
||||
-main Main
|
||||
extraParams.hxml
|
||||
-dce full
|
||||
--macro TxtFrontend.use()
|
||||
--macro XmlFrontend.use()
|
||||
Reference in New Issue
Block a user