Merge commit '289e516fe3d91b935e74c29e698d0d06815efd0d' as 'projects/tink_syntaxhub'

This commit is contained in:
2023-04-25 11:38:12 -06:00
30 changed files with 779 additions and 0 deletions

1
projects/tink_syntaxhub/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bin

View File

@@ -0,0 +1,4 @@
{
"version": "4.2.5",
"resolveLibs": "scoped"
}

View 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

View 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.

View File

@@ -0,0 +1,189 @@
# Tinkerbell Syntax Hub
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](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!

View File

@@ -0,0 +1 @@
--macro tink.SyntaxHub.use()

View 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

View 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()

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -0,0 +1,4 @@
-cp src
-D tink_syntaxhub
--macro tink.SyntaxHub.use()
-lib tink_priority

View 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

View 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"
}

View 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;
}

View File

@@ -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;
}

View 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];
}
}

View File

@@ -0,0 +1,6 @@
package tink.syntaxhub;
interface FrontendPlugin {
function extensions():Iterator<String>;
function parse(file:String, context:FrontendContext):Void;
}

View 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()

View File

@@ -0,0 +1 @@
Hello, hello!!!

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" ?>
<data>&lt;3</data>

View 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();
}
}

View 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());
}

View 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());
}

View 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()