[macro] added @:view macro, refactored @:template macro

This commit is contained in:
2018-03-06 15:44:07 +03:00
parent 4a65cef02a
commit a53510d1d4
11 changed files with 243 additions and 198 deletions

View File

@@ -4,7 +4,7 @@
"license": "BSD", "license": "BSD",
"tags": ["flash", "openfl"], "tags": ["flash", "openfl"],
"description": "Framework.", "description": "Framework.",
"version": "0.7.2", "version": "0.8.0",
"releasenote": "Update.", "releasenote": "Update.",
"contributors": ["shmyga"], "contributors": ["shmyga"],
"classPath": "src/main", "classPath": "src/main",

View File

@@ -1,4 +1,9 @@
-cp src -cp src
-lib haxework -cp ../../src/main
-lib yaml
-lib promhx
-main ViewExample.hx -main ViewExample.hx
--macro haxework.parser.Parser.auto()
-swf target/ViewExample.swf -swf target/ViewExample.swf
#-as3 target

View File

@@ -1,29 +1,39 @@
package; package;
import haxework.gui.ViewBuilder; import haxework.gui.View;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
import haxework.gui.ButtonView; import haxework.gui.ButtonView;
import haxework.gui.Root; import haxework.gui.Root;
@:template("form.json")
class FormView extends VGroupView implements ViewBuilder {} @:template2("form.json")
class FormView extends VGroupView {
@:view public var panel(default, null):View;
@:view public var button1(default, null):View;
@:view public var button2(default, null):View;
@:view public var button3(default, null):View;
private function init() {
trace('Init');
}
}
class ViewExample { class ViewExample {
public static function main() { public static function main() {
new ViewExample(); new ViewExample();
} }
public function new() { public function new() {
var form = new FormView({listener:this}); var form:FormView = new FormView();
Root.bind(form); Root.bind(form);
trace(form.panel); trace(form.panel);
trace(form.button1); trace(form.button1);
trace(form.button2); trace(form.button2);
trace(form.button3); trace(form.button3);
} }
public function onPress(view:ButtonView):Void { public function onPress(view:ButtonView):Void {
trace("onPress: " + view.id); trace("onPress: " + view.id);
} }
} }

View File

@@ -37,8 +37,7 @@
"width":100, "width":100,
"pHeight":100, "pHeight":100,
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0xcc0000"}, "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0xcc0000"},
"text":"Text1", "text":"Text1"
"onPress":"@link:listener"
}, },
{ {
"id":"button2", "id":"button2",
@@ -47,8 +46,7 @@
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cc00"}, "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cc00"},
"text":"Text2", "text":"Text2",
"fontFamily":"Georgia", "fontFamily":"Georgia",
"fontColor":"0xffffff", "fontColor":"0xffffff"
"onPress":"@link:listener"
}, },
{ {
"id":"button3", "id":"button3",
@@ -57,8 +55,7 @@
"skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cccc"}, "skin":{"@type":"haxework.gui.skin.ButtonColorSkin", "color":"0x00cccc"},
"text":"Text 3333333333 ddd", "text":"Text 3333333333 ddd",
"fontFamily":"Tahoma", "fontFamily":"Tahoma",
"fontColor":"0xff0000", "fontColor":"0xff0000"
"onPress":"@link:listener"
} }
] ]
} }

View File

@@ -1,16 +0,0 @@
package haxework.gui;
#if macro
import haxework.gui.build.Builder;
#end
@:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build())
extern interface ViewBuilder {}
class ViewBuilderImpl {
#if macro
public static function build() {
return new Builder().build();
}
#end
}

View File

@@ -1,13 +1,11 @@
package haxework.gui.build; package haxework.macro;
#if macro
import yaml.Parser; import yaml.Parser;
import yaml.Yaml; import yaml.Yaml;
import haxe.macro.Context; import haxe.macro.Context;
import haxe.macro.Expr.Constant;
import haxe.macro.Expr.ExprDef;
class BuilderUtil {
class FileUtil {
public static function loadJsonFile(path:String):Dynamic { public static function loadJsonFile(path:String):Dynamic {
Context.registerModuleDependency(Context.getLocalModule(), path); Context.registerModuleDependency(Context.getLocalModule(), path);
@@ -39,23 +37,9 @@ class BuilderUtil {
public static function loadFile(path:String):Dynamic { public static function loadFile(path:String):Dynamic {
var ext = path.split('.').pop(); var ext = path.split('.').pop();
return switch(ext) { return switch(ext) {
case 'json': BuilderUtil.loadJsonFile(path); case 'json': loadJsonFile(path);
case 'yml' | 'yaml': BuilderUtil.loadYamlFile(path); case 'yml' | 'yaml': loadYamlFile(path);
case x: throw 'Unsupported template format: "${x}"'; case x: throw 'Unsupported file format: "${x}"';
} }
} }
public static function getMeta(key:String):Array<String> {
var c = Context.getLocalClass().get();
for (meta in c.meta.get()) {
if (meta.name == key) {
return meta.params.map(function(param) return switch(param.expr) {
case ExprDef.EConst(Constant.CString(value)): value;
case _: null;
});
}
}
return [];
}
} }
#end

View File

@@ -1,4 +1,4 @@
package haxework.gui.build; package haxework.macro;
typedef JsonKeyPosition = { typedef JsonKeyPosition = {
var min:Int; var min:Int;

View File

@@ -0,0 +1,52 @@
package haxework.macro;
import haxe.macro.Context;
import haxe.macro.Expr;
class ProvideMacro {
public static function has(field:Field):Bool {
for (md in field.meta) if (md.name == ":provide") {
return true;
}
return false;
}
private var field:Field;
public function new(field:Field) {
this.field = field;
}
public function apply():Array<Field> {
var result:Array<Field> = [];
var type:ComplexType = switch field.kind {
case FieldType.FVar(t): t;
default: null;
}
var name:String = switch type {
case ComplexType.TPath(p): p.name;
default: null;
}
result.push({
name: field.name,
access: [Access.APublic],
pos: field.pos,
kind: FieldType.FProp('get', 'never', type)
});
result.push({
name: 'get_${field.name}',
access: [Access.APrivate, Access.AInline],
pos: field.pos,
kind: FieldType.FFun({
args: [],
expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos),
params: [],
ret: type
})
});
return result;
}
}

View File

@@ -1,44 +1,54 @@
package haxework.gui.build; package haxework.macro;
#if macro
import haxework.gui.build.BuilderUtil;
import haxe.macro.Context; import haxe.macro.Context;
import haxework.gui.build.PositionJsonParser;
import haxe.macro.Expr; import haxe.macro.Expr;
import haxe.macro.Expr.Field; import haxe.macro.Type;
import haxework.macro.PositionJsonParser;
class Builder {
private var templateFile:String; class TemplateMacro {
private var templateKey:String;
private var styleFile:String;
private var template:Dynamic; private static inline var metaName:String = ':template';
public static function has(classType:ClassType):Bool {
for (md in classType.meta.get()) if (md.name == metaName) {
return true;
}
return false;
}
private var classType:ClassType;
private var fields:Array<Field>; private var fields:Array<Field>;
private var exprs:Array<Expr>; private var bindings:Map<String, String>;
private var style:Dynamic;
private var meta(get, never):MetadataEntry;
private var templateFile:String;
private var template:Dynamic;
private var style:Dynamic;
private var i:Int; private var i:Int;
public function new() { private function get_meta():MetadataEntry {
var templateMeta = BuilderUtil.getMeta(":template"); for (md in classType.meta.get()) if (md.name == metaName) {
var templatePath = templateMeta[0].split("@"); return md;
templateFile = Context.resolvePath(templatePath[0]);
templateKey = templatePath[1];
template = BuilderUtil.loadFile(templateFile);
if (templateKey != null) template = Reflect.field(template, templateKey);
if (templateMeta[1] != null) {
styleFile = Context.resolvePath(templateMeta[1]);
style = BuilderUtil.loadFile(styleFile);
} }
return null;
fields = Context.getBuildFields();
exprs = [];
i = 0;
} }
public function new(classType:ClassType, fields:Array<Field>) {
this.classType = classType;
this.fields = fields;
var params = Util.getMetaParams(meta);
templateFile = Context.resolvePath(params[0]);
template = FileUtil.loadFile(templateFile);
if (params.length > 1) {
var styleFile = params.length > 1 ? Context.resolvePath(params[1]) : null;
style = FileUtil.loadFile(styleFile);
}
bindings = findViewsBindings(fields);
}
private static function getSpecField(object:Dynamic, field:String):Dynamic { private static function getSpecField(object:Dynamic, field:String):Dynamic {
if (Reflect.hasField(object, "@" + field)) { if (Reflect.hasField(object, "@" + field)) {
return Reflect.field(object, "@" + field); return Reflect.field(object, "@" + field);
@@ -56,7 +66,7 @@ class Builder {
return Context.makePosition({min:min, max:max, file:file}); return Context.makePosition({min:min, max:max, file:file});
} }
private function specialValue(name:String, key:String, a:Array<String>, position:JsonKeyPosition):Dynamic { private function specialValue(name:String, key:String, a:Array<String>, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
return switch (a[0]) { return switch (a[0]) {
case "asset": case "asset":
switch (a[1]) { switch (a[1]) {
@@ -76,8 +86,8 @@ class Builder {
case "class": case "class":
a[1]; a[1];
case "layout": case "layout":
var template = BuilderUtil.loadJsonFile(a[1]); var template = FileUtil.loadJsonFile(a[1]);
return getValue(name, key, template, position); return createValue(name, key, template, position, exprs);
case "link": case "link":
"(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")"; "(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")";
case _: case _:
@@ -93,14 +103,14 @@ class Builder {
return type; return type;
} }
private function getValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition):Dynamic { private function createValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
return if (Std.is(value, Array)) { return if (Std.is(value, Array)) {
value.map(function(v) { value.map(function(v) {
return getValue(null, null, v, position); return createValue(null, null, v, position, exprs);
}); });
} else if (Std.is(value, String)) { } else if (Std.is(value, String)) {
if (value.charAt(0) == "@" || value.charAt(0) == "$") { if (value.charAt(0) == "@" || value.charAt(0) == "$") {
specialValue(name, key, value.substring(1, value.length).split(":"), position); specialValue(name, key, value.substring(1, value.length).split(":"), position, exprs);
} else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) { } else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) {
Std.parseInt(StringTools.replace(Std.string(value), "#", "0x")); Std.parseInt(StringTools.replace(Std.string(value), "#", "0x"));
} else { } else {
@@ -111,14 +121,14 @@ class Builder {
} else if (value != null) { } else if (value != null) {
var type = getType(value, getPosition(position)); var type = getType(value, getPosition(position));
if (type != null) { if (type != null) {
var n = "a" + i++; var n = 'a${i++}';
if (type == "Dynamic") { if (type == "Dynamic") {
//ToDo: //ToDo:
exprs.push(Context.parse("var " + n + " = cast {}", getPosition(position))); exprs.push(Context.parse("var " + n + " = cast {}", getPosition(position)));
} else { } else {
exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition(position))); exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition(position)));
} }
createElement(value, n); createElement(n, value, exprs);
n; n;
} else { } else {
null; null;
@@ -128,85 +138,95 @@ class Builder {
} }
} }
private function createElement(template:Dynamic, name:String):String { private function createElement(name:String, data:Dynamic, exprs:Array<Expr>):String {
var s = getSpecField(template, "style"); var s = getSpecField(data, "style");
if (s != null) { if (s != null) {
var s = Reflect.field(style, s); var s = Reflect.field(style, s);
for (key in Reflect.fields(s)) { for (key in Reflect.fields(s)) {
if (key.charAt(0) != "$" && !Reflect.hasField(template, key)) { if (key.charAt(0) != "$" && !Reflect.hasField(data, key)) {
Reflect.setField(template, key, Reflect.field(s, key)); Reflect.setField(data, key, Reflect.field(s, key));
Reflect.setField(template, "$" + key, Reflect.field(s, "$" + key)); Reflect.setField(data, "$" + key, Reflect.field(s, "$" + key));
} }
} }
} }
if (Reflect.hasField(template, "id")) { if (Reflect.hasField(data, "id")) {
var id = Reflect.field(template, "id"); var id = Reflect.field(data, "id");
var type = getType(template, getPosition()); if (bindings.exists(id)) {
var expr = Context.parse("var a:" + type, getPosition()); var bind = bindings.get(id);
var complexType = switch (expr.expr) { exprs.push(Context.parse('this.${bind} = ${name}', getPosition()));
case EVars(vars): vars[0].type;
case _: null;
} }
fields.push({
name: id,
access: [APublic],
pos: getPosition(),
kind: FProp("default", "null", complexType)
});
exprs.push(Context.parse("this." + id + " = " + name, getPosition()));
} }
for (key in Reflect.fields(template)) { for (key in Reflect.fields(data)) {
if (key.charAt(0) == "$" || key.charAt(0) == "@") continue; if (key.charAt(0) == "$" || key.charAt(0) == "@") continue;
var position = Reflect.field(template, "$" + key); var position = Reflect.field(data, "$" + key);
var value = getValue(name, key, Reflect.field(template, key), position); var value = createValue(name, key, Reflect.field(data, key), position, exprs);
if (value != null) { if (value != null) {
exprs.push(Context.parse(name + "." + key + " = " + value, getPosition(position))); exprs.push(Context.parse(name + "." + key + " = " + value, getPosition(position)));
} }
} }
return name; return name;
} }
private function buildBuild(exprs:Array<Expr>):Field {
public function build():Array<Field> { return {
createElement(template, "this");
var init = false;
for (f in fields) if (f.name == "init") {
init = true;
break;
}
fields.push({
name: "build", name: "build",
access: [APublic], access: [Access.APrivate],
pos: getPosition(), pos: getPosition(),
kind: FFun({ kind: FieldType.FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], args: [],
expr: macro $b{exprs}, expr: macro $b{exprs},
params: [], params: [],
ret: null ret: null
}) })
}); }
}
private function buildConstructor(init:Bool):Field {
var contstrExprs = []; var contstrExprs = [];
contstrExprs.push(macro super()); contstrExprs.push(macro super());
contstrExprs.push(macro build(links)); contstrExprs.push(macro build());
if (init) contstrExprs.push(macro init()); if (init) contstrExprs.push(macro init());
fields.push({ return {
name: "new", name: "new",
access: [APublic], access: [Access.APublic],
pos: getPosition(), pos: getPosition(),
kind: FFun({ kind: FieldType.FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}], args: [],
expr: macro $b{contstrExprs}, expr: macro $b{contstrExprs},
params: [], params: [],
ret: null ret: null
}) })
}); };
return fields; }
private static function findViewsBindings(fields:Array<Field>):Map<String, String> {
var result:Map<String, String> = new Map();
for (field in fields) if (field.meta != null) {
for (meta in field.meta) {
if (meta.name == ':view') {
var viewId:String = meta.params.length == 0 ? field.name : switch meta.params[0].expr {
case ExprDef.EConst(Constant.CString(value)): value;
default: null;
}
result.set(viewId, field.name);
}
}
}
return result;
}
public function apply():Array<Field> {
i = 0;
var result:Array<Field> = fields.slice(0);
var exprs:Array<Expr> = [];
var init = Lambda.exists(result, function(f) return f.name == 'init');
createElement("this", template, exprs);
result.push(buildBuild(exprs));
result.push(buildConstructor(init));
return result;
} }
} }
#end

View File

@@ -0,0 +1,18 @@
package haxework.macro;
import haxe.macro.Expr;
class Util {
public static function getMetaParams(meta:MetadataEntry):Array<String> {
return meta.params.map(function(param:Expr) return switch(param.expr) {
case ExprDef.EConst(Constant.CString(value)): value;
case _: null;
});
}
public inline static function DynamicType():ComplexType {
return ComplexType.TPath({name:'Dynamic', pack:[], params:[]});
}
}

View File

@@ -1,17 +1,16 @@
package haxework.parser; package haxework.parser;
import haxe.macro.Expr;
#if macro
import haxe.macro.Context; import haxe.macro.Context;
#end import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Type.ClassType; import haxe.macro.Type.ClassType;
import haxe.macro.Type.Ref; import haxe.macro.Type.Ref;
import haxe.macro.Type;
import haxework.macro.ProvideMacro;
import haxework.macro.TemplateMacro;
class Parser { class Parser {
#if macro
private static function auto():Void { private static function auto():Void {
haxe.macro.Compiler.addGlobalMetadata("", "@:build(haxework.parser.Parser.autoRun())", true, true, false); haxe.macro.Compiler.addGlobalMetadata("", "@:build(haxework.parser.Parser.autoRun())", true, true, false);
} }
@@ -21,55 +20,31 @@ class Parser {
switch (t) { switch (t) {
case null: return null; case null: return null;
case Type.TInst(_.get() => ct, _): case Type.TInst(_.get() => ct, _):
var hasMeta:Bool = false; var modify:Bool = false;
var hasAutoBuild:Bool = false; var fields:Array<Field> = Context.getBuildFields();
for (md in ct.meta.get()) var result:Array<Field> = [];
if (md.name == ":haxework") { var appends:Array<Field> = [];
hasMeta = true; // process fields meta
for (field in fields) {
if (ProvideMacro.has(field)) {
modify = true;
var provide = new ProvideMacro(field);
result = result.concat(provide.apply());
} else {
result.push(field);
} }
if (hasMeta) {
var result:Array<Field> = [];
var fields:Array<Field> = Context.getBuildFields();
for (field in fields) {
var remove:Bool = false;
for (md in field.meta)
if (md.name == ":provide") {
var type:ComplexType = switch field.kind {
case FieldType.FVar(t): t;
default: null;
}
var name:String = switch type {
case ComplexType.TPath(p): p.name;
default: null;
}
remove = true;
result.push({
name: field.name,
access: [Access.APublic],
pos: field.pos,
kind: FieldType.FProp('get', 'never', type)
});
result.push({
name: 'get_${field.name}',
access: field.access,
pos: field.pos,
kind: FieldType.FFun({
args: [],
expr: Context.parse('return haxework.provider.Provider.get(${name})', field.pos),
params: [],
ret: type
})
});
}
if (!remove) {
result.push(field);
}
}
return result;
} }
return null; if (modify) {
fields = result;
}
// process class meta
if (TemplateMacro.has(ct)) {
modify = true;
var template = new TemplateMacro(ct, fields);
fields = template.apply();
}
return modify ? fields : null;
default: return null; default: return null;
} }
} }
#end
} }