From 0ccbadc7a3dca44b927380eb5c8a88806f4f8e23 Mon Sep 17 00:00:00 2001 From: inc0der Date: Fri, 18 Aug 2023 13:12:54 -0300 Subject: [PATCH 01/23] add base state and store class --- porcelain/store/State.hx | 7 +++++++ porcelain/store/Store.hx | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 porcelain/store/State.hx create mode 100644 porcelain/store/Store.hx diff --git a/porcelain/store/State.hx b/porcelain/store/State.hx new file mode 100644 index 0000000..ab97639 --- /dev/null +++ b/porcelain/store/State.hx @@ -0,0 +1,7 @@ +package porcelain.store; + +import tracker.Observable; + +class State implements ReadOnly implements Observable { + public function new() {} +} diff --git a/porcelain/store/Store.hx b/porcelain/store/Store.hx new file mode 100644 index 0000000..dd2b45b --- /dev/null +++ b/porcelain/store/Store.hx @@ -0,0 +1,17 @@ +package porcelain.store; + +import tracker.Observable; +import ceramic.Entity; +import ceramic.PersistentData; + +class Store extends Entity implements Observable { + public static final store: Store = new Store(); + + public var state: State; + + private var status: String = 'resting'; + + private function new() { + super(); + } +} From 4d555592745f7a5e983eb7b2b095ec68700b8ba8 Mon Sep 17 00:00:00 2001 From: inc0der Date: Sat, 19 Aug 2023 18:55:59 -0300 Subject: [PATCH 02/23] add macro to generate an object with methods from external classes --- porcelain/macros/GenerateMethodObject.hx | 165 +++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 porcelain/macros/GenerateMethodObject.hx diff --git a/porcelain/macros/GenerateMethodObject.hx b/porcelain/macros/GenerateMethodObject.hx new file mode 100644 index 0000000..63ee492 --- /dev/null +++ b/porcelain/macros/GenerateMethodObject.hx @@ -0,0 +1,165 @@ +package porcelain.macros; + +import haxe.macro.Type; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.TypeTools; + +using Lambda; +using StringTools; + +/** + * Adds method names from a class and returns an object with those method names. + * + * This allows you to call the methods with full type support which is useful + * for store patterns. + * + * `store.commit().mutationMethodName(arg, arg2);` + * + * instead of something like this which is common in JS store patterns + * + * `store.commit('mutationMethodName', [arg, arg2])` + * + */ +class GenerateMethodObject { + static inline function getFunctionArgs(type) { + var fieldArgs = []; + + switch type { + case TFun(args, ret): + for (arg in args) { + fieldArgs.push({ + name: arg.name, + opt: arg.opt, + type: TypeTools.toComplexType(arg.t) + }); + } + case _: + } + + return fieldArgs; + } + + static inline function isFunctionType(type) { + switch type { + case TFun(args, ret): + return true; + case _: + return false; + } + } + + macro public static function apply(classRef, extraRefs: Array): Expr { + var block: Array = []; + var classRefTypes = [{ ref: classRef, type: Context.typeof(classRef) }]; + var funcExprs = []; + + if (extraRefs.count() > 0) { + for (ref in extraRefs) + classRefTypes.push({ + type: Context.typeof(ref), + ref: ref + }); + } + + var allMethods = new Map(); + var localMutations = new Map(); + + for (refType in classRefTypes) { + var classRef = refType.ref; + + switch refType.type { + case TType(t, _): + var classType = t.get().type; + + switch classType { + case TAnonymous(a): + var fields: Array = a.get().fields; + + for (field in fields) { + var type = field.type; + + if (isFunctionType(type)) { + var name = field.name; + var args = getFunctionArgs(type); + var nameIdent = macro $v{name}; + + switch args.length { + case 1: + var argName = args[0].name; + var argType = args[0].type; + + funcExprs.push({ + name: name, + expr: macro function($argName: $argType) { + return Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), [$i{argName}]); + } + }); + + case 2: + var argName = args[0].name; + var argType = args[0].type; + var argName2 = args[1].name; + var argType2 = args[1].type; + var reflectArgs = [macro $i{argName}, macro $i{argName2}]; + + funcExprs.push({ + name: name, + expr: macro function($argName: $argType, $argName2: $argType2) { + return return + Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), $a{reflectArgs}); + } + }); + + case 3: + var argName = args[0].name; + var argType = args[0].type; + var argName2 = args[1].name; + var argType2 = args[1].type; + var argName3 = args[2].name; + var argType3 = args[2].type; + var reflectArgs = [macro $i{argName}, macro $i{argName2}, macro $i{argName3}]; + + funcExprs.push({ + name: name, + expr: macro function($argName: $argType, $argName2: $argType2, $argName3: $argType3) { + return + Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), $a{reflectArgs}); + } + }); + + default: + trace('Does not support functions with more than 3 arguments'); + } + } + } + + for (funcExpr in funcExprs) { + if (allMethods.exists(funcExpr.name)) { + trace('${funcExpr.name} already exists, overwriting with new one from $classRef'); + } + allMethods.set(funcExpr.name, { + field: funcExpr.name, + expr: funcExpr.expr + }); + } + + if (allMethods.count() <= 0) { + trace('No methods in $classRef to add'); + } + + case _: + trace('Only supports Anonymous types'); + } + case _: + } + } + + var mutations = { expr: EObjectDecl(allMethods.array()), pos: Context.currentPos() }; + + block.push(macro var mutationMethods = $mutations); + block.push(macro return mutationMethods); + + return macro $b{block}; + } +} From afbea864c42a59ce4fa675842886e02da904f361 Mon Sep 17 00:00:00 2001 From: inc0der Date: Sun, 20 Aug 2023 12:47:50 -0300 Subject: [PATCH 03/23] remove because a new build macro will replace it --- porcelain/macros/GenerateMethodObject.hx | 165 ----------------------- 1 file changed, 165 deletions(-) delete mode 100644 porcelain/macros/GenerateMethodObject.hx diff --git a/porcelain/macros/GenerateMethodObject.hx b/porcelain/macros/GenerateMethodObject.hx deleted file mode 100644 index 63ee492..0000000 --- a/porcelain/macros/GenerateMethodObject.hx +++ /dev/null @@ -1,165 +0,0 @@ -package porcelain.macros; - -import haxe.macro.Type; -import haxe.macro.Context; -import haxe.macro.Expr; -import haxe.macro.TypeTools; - -using Lambda; -using StringTools; - -/** - * Adds method names from a class and returns an object with those method names. - * - * This allows you to call the methods with full type support which is useful - * for store patterns. - * - * `store.commit().mutationMethodName(arg, arg2);` - * - * instead of something like this which is common in JS store patterns - * - * `store.commit('mutationMethodName', [arg, arg2])` - * - */ -class GenerateMethodObject { - static inline function getFunctionArgs(type) { - var fieldArgs = []; - - switch type { - case TFun(args, ret): - for (arg in args) { - fieldArgs.push({ - name: arg.name, - opt: arg.opt, - type: TypeTools.toComplexType(arg.t) - }); - } - case _: - } - - return fieldArgs; - } - - static inline function isFunctionType(type) { - switch type { - case TFun(args, ret): - return true; - case _: - return false; - } - } - - macro public static function apply(classRef, extraRefs: Array): Expr { - var block: Array = []; - var classRefTypes = [{ ref: classRef, type: Context.typeof(classRef) }]; - var funcExprs = []; - - if (extraRefs.count() > 0) { - for (ref in extraRefs) - classRefTypes.push({ - type: Context.typeof(ref), - ref: ref - }); - } - - var allMethods = new Map(); - var localMutations = new Map(); - - for (refType in classRefTypes) { - var classRef = refType.ref; - - switch refType.type { - case TType(t, _): - var classType = t.get().type; - - switch classType { - case TAnonymous(a): - var fields: Array = a.get().fields; - - for (field in fields) { - var type = field.type; - - if (isFunctionType(type)) { - var name = field.name; - var args = getFunctionArgs(type); - var nameIdent = macro $v{name}; - - switch args.length { - case 1: - var argName = args[0].name; - var argType = args[0].type; - - funcExprs.push({ - name: name, - expr: macro function($argName: $argType) { - return Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), [$i{argName}]); - } - }); - - case 2: - var argName = args[0].name; - var argType = args[0].type; - var argName2 = args[1].name; - var argType2 = args[1].type; - var reflectArgs = [macro $i{argName}, macro $i{argName2}]; - - funcExprs.push({ - name: name, - expr: macro function($argName: $argType, $argName2: $argType2) { - return return - Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), $a{reflectArgs}); - } - }); - - case 3: - var argName = args[0].name; - var argType = args[0].type; - var argName2 = args[1].name; - var argType2 = args[1].type; - var argName3 = args[2].name; - var argType3 = args[2].type; - var reflectArgs = [macro $i{argName}, macro $i{argName2}, macro $i{argName3}]; - - funcExprs.push({ - name: name, - expr: macro function($argName: $argType, $argName2: $argType2, $argName3: $argType3) { - return - Reflect.callMethod(${classRef}, Reflect.field(${classRef}, $nameIdent), $a{reflectArgs}); - } - }); - - default: - trace('Does not support functions with more than 3 arguments'); - } - } - } - - for (funcExpr in funcExprs) { - if (allMethods.exists(funcExpr.name)) { - trace('${funcExpr.name} already exists, overwriting with new one from $classRef'); - } - allMethods.set(funcExpr.name, { - field: funcExpr.name, - expr: funcExpr.expr - }); - } - - if (allMethods.count() <= 0) { - trace('No methods in $classRef to add'); - } - - case _: - trace('Only supports Anonymous types'); - } - case _: - } - } - - var mutations = { expr: EObjectDecl(allMethods.array()), pos: Context.currentPos() }; - - block.push(macro var mutationMethods = $mutations); - block.push(macro return mutationMethods); - - return macro $b{block}; - } -} From 72dbed710b44de2a95ce340efbb9176fa6e89bd3 Mon Sep 17 00:00:00 2001 From: inc0der Date: Sun, 20 Aug 2023 12:59:36 -0300 Subject: [PATCH 04/23] Add store interface and build macro base --- porcelain/macros/StoreMacro.hx | 106 +++++++++++++++++++++++++++++++++ porcelain/store/Store.hx | 17 +----- 2 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 porcelain/macros/StoreMacro.hx diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx new file mode 100644 index 0000000..7383ae4 --- /dev/null +++ b/porcelain/macros/StoreMacro.hx @@ -0,0 +1,106 @@ +package porcelain.macros; + +import haxe.macro.Type; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.TypeTools; + +using Lambda; +using StringTools; + +typedef ClassFieldsPair = { + className: Dynamic, + fields: Array +} + +class StoreMacro { + macro public static function build(): Array { + var localFields = Context.getBuildFields(); + var mutationFields = getFieldsWithMeta(localFields, 'mutation'); + + if (mutationFields != null) { + var commitMethodFields = createStaticMethods(mutationFields); + for (field in commitMethodFields) { + if (localFields.exists(f -> f.name == field.name)) { + trace('A field with the name ${field.name} already exists, skipping'); + continue; + } + localFields.push(field); + } + } + + return localFields; + } + + #if macro + static function getFieldsWithMeta(fields: Array, metaName: String): Array { + return fields.filter(i -> i.meta.exists(m -> m.name == metaName)); + } + + public static function createFunction(className: String, methodName: String, args: Dynamic): Function { + var argExprs = args.map(arg -> macro $i{arg.name}); + var funcArgs = args.map(arg -> { + var funcArg: FunctionArg = { + name: arg.name, + opt: arg.opt, + type: TypeTools.toComplexType(arg.t), + } + return funcArg; + }); + + return { + args: funcArgs, + expr: macro return Reflect.callMethod($i{className}, Reflect.field($i{className}, $v{methodName}), $a{argExprs}) + } + } + + public static function getClassFieldsFromKind(kind): Array { + var fields: Array = []; + + switch kind { + case FVar(TPath(p), e): + switch Context.getType(p.name) { + case TInst(t, params): + var type = t.get(); + var tFields = type.fields.get(); + var tStatics = type.statics.get(); + + fields.push({ + className: p.name, + fields: tFields.concat(tStatics) + }); + case _: + } + case _: + } + return fields; + } + + public static function createStaticMethods(mutationFields: Array): Array { + var funcsToCopy: Array = []; + + for (field in mutationFields) { + var typeFieldPairs = getClassFieldsFromKind(field.kind); + for (pair in typeFieldPairs) { + for (tField in pair.fields) { + switch TypeTools.follow(tField.type) { + case TFun(args, ret): + var name = tField.name; + var func = createFunction(pair.className, name, args); + var newMethod: Field = { + name: name, + kind: FieldType.FFun(func), + access: [Access.APublic, Access.AStatic], + pos: Context.currentPos(), + doc: tField.doc + }; + funcsToCopy.push(newMethod); + case _: + } + } + } + } + return funcsToCopy; + } + #end +} diff --git a/porcelain/store/Store.hx b/porcelain/store/Store.hx index dd2b45b..0bfc851 100644 --- a/porcelain/store/Store.hx +++ b/porcelain/store/Store.hx @@ -1,17 +1,4 @@ package porcelain.store; -import tracker.Observable; -import ceramic.Entity; -import ceramic.PersistentData; - -class Store extends Entity implements Observable { - public static final store: Store = new Store(); - - public var state: State; - - private var status: String = 'resting'; - - private function new() { - super(); - } -} +@:autoBuild(porcelain.macros.StoreMacro.build()) +interface Store {} From 06a5dd2748ae5762b7b51dcd1b3cd301afbbc5b0 Mon Sep 17 00:00:00 2001 From: inc0der Date: Sun, 20 Aug 2023 18:46:46 -0300 Subject: [PATCH 05/23] add status field --- porcelain/macros/StoreMacro.hx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 7383ae4..9c268b7 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -1,4 +1,4 @@ -package porcelain.macros; +package; import haxe.macro.Type; import haxe.macro.Context; @@ -18,6 +18,13 @@ class StoreMacro { var localFields = Context.getBuildFields(); var mutationFields = getFieldsWithMeta(localFields, 'mutation'); + localFields.push({ + name: 'status', + access: [Access.APrivate, Access.AStatic], + kind: FProp('default', 'null', macro : String), + pos: Context.currentPos() + }); + if (mutationFields != null) { var commitMethodFields = createStaticMethods(mutationFields); for (field in commitMethodFields) { @@ -50,7 +57,13 @@ class StoreMacro { return { args: funcArgs, - expr: macro return Reflect.callMethod($i{className}, Reflect.field($i{className}, $v{methodName}), $a{argExprs}) + expr: macro $b{ + [ + macro status = 'mutation', + macro Reflect.callMethod($i{className}, Reflect.field($i{className}, $v{methodName}), $a{argExprs}), + macro status = 'resting' + ] + } } } From b78d29e39f06e5aa2e1b5094d07a7bb2a605928d Mon Sep 17 00:00:00 2001 From: inc0der Date: Sun, 20 Aug 2023 20:24:17 -0300 Subject: [PATCH 06/23] fix StoreMacro not in correct package --- porcelain/macros/StoreMacro.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 9c268b7..164294d 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -1,4 +1,4 @@ -package; +package porcelain.macros; import haxe.macro.Type; import haxe.macro.Context; From 354ed02c4ad54bcab7abec720f1c5a24e0436dcf Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:15:32 -0300 Subject: [PATCH 07/23] add state Mutation interface and macro --- porcelain/macros/StateMutationMacro.hx | 50 ++++++++++++++++++++++++++ porcelain/store/Mutation.hx | 4 +++ 2 files changed, 54 insertions(+) create mode 100644 porcelain/macros/StateMutationMacro.hx create mode 100644 porcelain/store/Mutation.hx diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx new file mode 100644 index 0000000..84943eb --- /dev/null +++ b/porcelain/macros/StateMutationMacro.hx @@ -0,0 +1,50 @@ +package; + +import haxe.macro.Type; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.TypeTools; + +using Lambda; +using StringTools; + +class StateMutationMacro { + #if macro + public static function build(): Array { + var localFields = Context.getBuildFields(); + + for (lf in localFields) { + switch lf.kind { + case FFun(f): + if (lf.access.contains(AStatic)) { + var name = lf.name; + var args: Array = f.args; + var argsForMeta = []; + + for (arg in args) { + switch arg.type { + case TPath(s): + argsForMeta.push({ + name: arg.name, + opt: arg.opt, + typeName: s.name + }); + case _: + } + } + + // var type + lf.meta.push({ + name: 'tempFieldData', + params: [macro $v{name}, macro $v{argsForMeta}], + pos: lf.pos + }); + } + case _: + } + } + + return localFields; + } + #end +} diff --git a/porcelain/store/Mutation.hx b/porcelain/store/Mutation.hx new file mode 100644 index 0000000..77ed99c --- /dev/null +++ b/porcelain/store/Mutation.hx @@ -0,0 +1,4 @@ +package porcelain.store; + +@:autoBuild(porcelain.macros.StateMutationMacro.build()) +interface Mutation {} From 47b435ee986614362a205a06e63131b7307eb876 Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:17:23 -0300 Subject: [PATCH 08/23] fix StoreMacro breaking Mutation class fields due to resolving to early --- porcelain/macros/StoreMacro.hx | 129 +++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 40 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 164294d..40cfb0a 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -1,4 +1,4 @@ -package porcelain.macros; +package; import haxe.macro.Type; import haxe.macro.Context; @@ -8,19 +8,16 @@ import haxe.macro.TypeTools; using Lambda; using StringTools; -typedef ClassFieldsPair = { - className: Dynamic, - fields: Array -} +class TestMacro { + public static var collectedFields: Map> = []; -class StoreMacro { macro public static function build(): Array { var localFields = Context.getBuildFields(); var mutationFields = getFieldsWithMeta(localFields, 'mutation'); localFields.push({ name: 'status', - access: [Access.APrivate, Access.AStatic], + access: [Access.APublic, Access.AStatic], kind: FProp('default', 'null', macro : String), pos: Context.currentPos() }); @@ -49,14 +46,15 @@ class StoreMacro { var funcArgs = args.map(arg -> { var funcArg: FunctionArg = { name: arg.name, - opt: arg.opt, - type: TypeTools.toComplexType(arg.t), + opt: false, + type: arg.type } return funcArg; }); return { args: funcArgs, + ret: null, expr: macro $b{ [ macro status = 'mutation', @@ -67,48 +65,99 @@ class StoreMacro { } } - public static function getClassFieldsFromKind(kind): Array { - var fields: Array = []; - + public static function getClassFromKind(kind) { + var cls; + var getClsKind = (name: String) -> { + return switch Context.getType(name) { + case TInst(t, params): t.get(); + case _: null; + } + } switch kind { - case FVar(TPath(p), e): - switch Context.getType(p.name) { - case TInst(t, params): - var type = t.get(); - var tFields = type.fields.get(); - var tStatics = type.statics.get(); - - fields.push({ - className: p.name, - fields: tFields.concat(tStatics) - }); + case FVar(t, e): + switch t { + case TPath(p): + cls = getClsKind(p.name); case _: + if (e != null) { + switch e.expr { + case ENew(t, params): + cls = getClsKind(t.name); + case _: + } + } } + case _: } - return fields; + return cls; + } + + public static function isExprTrue(expr: Expr): Bool { + return switch expr.expr { + case EConst(CIdent(s)): + return s == 'true'; + case _: + null; + } + } + + public static function extractStringFromExpr(expr: Expr): String { + return switch expr.expr { + case EConst(CString(s, kind)): + s; + case _: + null; + } + } + + public static function extractObjFieldsFromArrayExpr(expr: Expr): Dynamic { + return switch expr.expr { + case EArrayDecl(values): + var exValues = []; + for (value in values) { + switch value.expr { + case EObjectDecl(fields): + var t = fields.list(); + var typeField = t.find(i -> i.field == 'typeName'); + var nameField = t.find(i -> i.field == 'name'); + var optField = t.find(i -> i.field == 'opt'); + var typeName = extractStringFromExpr(typeField.expr); + exValues.push({ + type: Context.toComplexType(Context.getType(typeName)), + opt: isExprTrue(optField.expr), + name: extractStringFromExpr(nameField.expr) + }); + case _: + } + } + exValues; + case _: + null; + } } public static function createStaticMethods(mutationFields: Array): Array { var funcsToCopy: Array = []; for (field in mutationFields) { - var typeFieldPairs = getClassFieldsFromKind(field.kind); - for (pair in typeFieldPairs) { - for (tField in pair.fields) { - switch TypeTools.follow(tField.type) { - case TFun(args, ret): - var name = tField.name; - var func = createFunction(pair.className, name, args); - var newMethod: Field = { - name: name, - kind: FieldType.FFun(func), - access: [Access.APublic, Access.AStatic], - pos: Context.currentPos(), - doc: tField.doc - }; - funcsToCopy.push(newMethod); - case _: + var cls = getClassFromKind(field.kind); + var clsFields = cls.statics.get(); + for (field in clsFields) { + var fieldMeta = field.meta.get(); + for (meta in fieldMeta) { + if (meta.name == 'tempFieldData') { + var metaParams = meta.params; + var fieldName = extractStringFromExpr(metaParams[0]); + var fieldArgs = extractObjFieldsFromArrayExpr(metaParams[1]); + var func = createFunction(cls.name, fieldName, fieldArgs); + var newMethod: Field = { + name: fieldName, + kind: FieldType.FFun(func), + access: [Access.APublic, Access.AStatic], + pos: Context.currentPos() + }; + funcsToCopy.push(newMethod); } } } From 17d7eef234291e1cb58f0fc242137b266a34918f Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:24:41 -0300 Subject: [PATCH 09/23] fix import name and class name incorrect after bringing it in --- porcelain/macros/StoreMacro.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 40cfb0a..143a1fb 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -1,4 +1,4 @@ -package; +package porcelain.macros; import haxe.macro.Type; import haxe.macro.Context; @@ -8,7 +8,7 @@ import haxe.macro.TypeTools; using Lambda; using StringTools; -class TestMacro { +class StoreMacro { public static var collectedFields: Map> = []; macro public static function build(): Array { From bda18f43848f30549233d342e5af681071e49a0d Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:24:59 -0300 Subject: [PATCH 10/23] add store test --- sample/src/tests/TestStoreState.hx | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 sample/src/tests/TestStoreState.hx diff --git a/sample/src/tests/TestStoreState.hx b/sample/src/tests/TestStoreState.hx new file mode 100644 index 0000000..28afaf1 --- /dev/null +++ b/sample/src/tests/TestStoreState.hx @@ -0,0 +1,32 @@ +package tests; + +import porcelain.store.*; + +class AppState extends State { + @observe public var projectPath: String = ''; +} + +class AppStore implements Store { + @state public static var state = new AppState(); + + public var test = 'string'; + + @mutation var projectMutations: StateMutations; +} + +class StateMutations implements Mutation { + public function new() {} + + public static function updateProjectPath(path: String) { + @:privateAccess AppStore.state.projectPath = ''; + } +} + +class TestStoreState { + public function new() { + AppStore.state.onProjectPathChange(null, (current, prev) -> { + trace('projectPath has changed values from $prev to $current'); + }); + AppStore.updateProjectPath('new/path'); + } +} From 74a4b333df08b75f002d5b20f8fce3f1f9d0e6d8 Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:38:52 -0300 Subject: [PATCH 11/23] refactor some names and tighten up types --- porcelain/macros/StoreMacro.hx | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 143a1fb..5a9a50a 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -23,8 +23,8 @@ class StoreMacro { }); if (mutationFields != null) { - var commitMethodFields = createStaticMethods(mutationFields); - for (field in commitMethodFields) { + var newMethodFields = createStaticMethods(mutationFields); + for (field in newMethodFields) { if (localFields.exists(f -> f.name == field.name)) { trace('A field with the name ${field.name} already exists, skipping'); continue; @@ -41,19 +41,11 @@ class StoreMacro { return fields.filter(i -> i.meta.exists(m -> m.name == metaName)); } - public static function createFunction(className: String, methodName: String, args: Dynamic): Function { + public static function createFunction(className: String, methodName: String, args: Array): Function { var argExprs = args.map(arg -> macro $i{arg.name}); - var funcArgs = args.map(arg -> { - var funcArg: FunctionArg = { - name: arg.name, - opt: false, - type: arg.type - } - return funcArg; - }); return { - args: funcArgs, + args: args, ret: null, expr: macro $b{ [ @@ -111,7 +103,7 @@ class StoreMacro { } } - public static function extractObjFieldsFromArrayExpr(expr: Expr): Dynamic { + public static function extractArgsFromObjectArrayExpr(expr: Expr): Array { return switch expr.expr { case EArrayDecl(values): var exValues = []; @@ -149,7 +141,7 @@ class StoreMacro { if (meta.name == 'tempFieldData') { var metaParams = meta.params; var fieldName = extractStringFromExpr(metaParams[0]); - var fieldArgs = extractObjFieldsFromArrayExpr(metaParams[1]); + var fieldArgs = extractArgsFromObjectArrayExpr(metaParams[1]); var func = createFunction(cls.name, fieldName, fieldArgs); var newMethod: Field = { name: fieldName, From 9a47ab78484fe400c9fd408fc1aaf7e88fc5dff6 Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:41:00 -0300 Subject: [PATCH 12/23] refactor to tighten up types and names again --- porcelain/macros/StoreMacro.hx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 5a9a50a..4da1b0d 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -57,9 +57,9 @@ class StoreMacro { } } - public static function getClassFromKind(kind) { + public static function getClassFromKind(kind): ClassType { var cls; - var getClsKind = (name: String) -> { + var getClsType = (name: String) -> { return switch Context.getType(name) { case TInst(t, params): t.get(); case _: null; @@ -69,12 +69,12 @@ class StoreMacro { case FVar(t, e): switch t { case TPath(p): - cls = getClsKind(p.name); + cls = getClsType(p.name); case _: if (e != null) { switch e.expr { case ENew(t, params): - cls = getClsKind(t.name); + cls = getClsType(t.name); case _: } } @@ -90,7 +90,7 @@ class StoreMacro { case EConst(CIdent(s)): return s == 'true'; case _: - null; + false; } } From ba66daa3c51a1dd8fec3a25945d425075b971963 Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 21:46:00 -0300 Subject: [PATCH 13/23] fix incorrect package name --- porcelain/macros/StateMutationMacro.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx index 84943eb..0fd817d 100644 --- a/porcelain/macros/StateMutationMacro.hx +++ b/porcelain/macros/StateMutationMacro.hx @@ -1,4 +1,4 @@ -package; +package porcelain.macros; import haxe.macro.Type; import haxe.macro.Context; From 1b2c89f2f850c9c5c0947c0ef6c50253e1aea134 Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 23:09:58 -0300 Subject: [PATCH 14/23] add support for array arguments --- porcelain/macros/StateMutationMacro.hx | 13 ++++++++++++- porcelain/macros/StoreMacro.hx | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx index 0fd817d..3340bf1 100644 --- a/porcelain/macros/StateMutationMacro.hx +++ b/porcelain/macros/StateMutationMacro.hx @@ -24,10 +24,21 @@ class StateMutationMacro { for (arg in args) { switch arg.type { case TPath(s): + var arrayTypeName = null; + if (s.name == 'Array') { + var argType = s.params[0]; + switch argType { + case TPType(TPath(p)): + arrayTypeName = p.name; + case _: + } + } argsForMeta.push({ name: arg.name, opt: arg.opt, - typeName: s.name + typeName: s.name, + isArray: arrayTypeName != null, + arrayType: arrayTypeName }); case _: } diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 4da1b0d..eeeabb2 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -114,9 +114,23 @@ class StoreMacro { var typeField = t.find(i -> i.field == 'typeName'); var nameField = t.find(i -> i.field == 'name'); var optField = t.find(i -> i.field == 'opt'); + var isArray = isExprTrue(t.find(i -> i.field == 'isArray').expr); + var arrType = null; + + if (isArray) { + var arrTypeField = t.find(i -> i.field == 'arrayType'); + var arrTypeName = extractStringFromExpr(arrTypeField.expr); + var arrParamType = Context.toComplexType(Context.getType(arrTypeName)); + arrType = TPath({ + name: 'Array', + pack: [], + params: [TPType(arrParamType)] + }); + } var typeName = extractStringFromExpr(typeField.expr); + var type = Context.toComplexType(Context.getType(typeName)); exValues.push({ - type: Context.toComplexType(Context.getType(typeName)), + type: isArray ? arrType : type, opt: isExprTrue(optField.expr), name: extractStringFromExpr(nameField.expr) }); From 555a3ecb649062783128b417358c6d60623d18df Mon Sep 17 00:00:00 2001 From: inc0der Date: Tue, 22 Aug 2023 23:30:42 -0300 Subject: [PATCH 15/23] add :keep metadata to each mutation field to prevent removal --- porcelain/macros/StateMutationMacro.hx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx index 3340bf1..985a110 100644 --- a/porcelain/macros/StateMutationMacro.hx +++ b/porcelain/macros/StateMutationMacro.hx @@ -44,12 +44,16 @@ class StateMutationMacro { } } - // var type lf.meta.push({ name: 'tempFieldData', params: [macro $v{name}, macro $v{argsForMeta}], pos: lf.pos }); + + lf.meta.push({ + name: ':keep', + pos: lf.pos + }); } case _: } From b479ba373bb0d9d9462869d353533495165c20bc Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 00:08:41 -0300 Subject: [PATCH 16/23] refactor to use ExprTools to get values of exprs --- porcelain/macros/StoreMacro.hx | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index eeeabb2..bc18464 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -1,5 +1,6 @@ package porcelain.macros; +import haxe.macro.ExprTools; import haxe.macro.Type; import haxe.macro.Context; import haxe.macro.Expr; @@ -85,24 +86,6 @@ class StoreMacro { return cls; } - public static function isExprTrue(expr: Expr): Bool { - return switch expr.expr { - case EConst(CIdent(s)): - return s == 'true'; - case _: - false; - } - } - - public static function extractStringFromExpr(expr: Expr): String { - return switch expr.expr { - case EConst(CString(s, kind)): - s; - case _: - null; - } - } - public static function extractArgsFromObjectArrayExpr(expr: Expr): Array { return switch expr.expr { case EArrayDecl(values): @@ -114,12 +97,12 @@ class StoreMacro { var typeField = t.find(i -> i.field == 'typeName'); var nameField = t.find(i -> i.field == 'name'); var optField = t.find(i -> i.field == 'opt'); - var isArray = isExprTrue(t.find(i -> i.field == 'isArray').expr); + var isArray = ExprTools.getValue(t.find(i -> i.field == 'isArray').expr); var arrType = null; if (isArray) { var arrTypeField = t.find(i -> i.field == 'arrayType'); - var arrTypeName = extractStringFromExpr(arrTypeField.expr); + var arrTypeName = ExprTools.getValue((arrTypeField.expr)); var arrParamType = Context.toComplexType(Context.getType(arrTypeName)); arrType = TPath({ name: 'Array', @@ -127,12 +110,12 @@ class StoreMacro { params: [TPType(arrParamType)] }); } - var typeName = extractStringFromExpr(typeField.expr); + var typeName = ExprTools.getValue(typeField.expr); var type = Context.toComplexType(Context.getType(typeName)); exValues.push({ type: isArray ? arrType : type, - opt: isExprTrue(optField.expr), - name: extractStringFromExpr(nameField.expr) + opt: ExprTools.getValue(optField.expr), + name: ExprTools.getValue(nameField.expr) }); case _: } @@ -154,7 +137,7 @@ class StoreMacro { for (meta in fieldMeta) { if (meta.name == 'tempFieldData') { var metaParams = meta.params; - var fieldName = extractStringFromExpr(metaParams[0]); + var fieldName = ExprTools.getValue(metaParams[0]); var fieldArgs = extractArgsFromObjectArrayExpr(metaParams[1]); var func = createFunction(cls.name, fieldName, fieldArgs); var newMethod: Field = { From fec0aa67d18b948f50183f09d0b4d6a9b643dca2 Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 00:22:34 -0300 Subject: [PATCH 17/23] refactor cleanup using TypeTools --- porcelain/macros/StoreMacro.hx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index bc18464..2bdd006 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -60,22 +60,16 @@ class StoreMacro { public static function getClassFromKind(kind): ClassType { var cls; - var getClsType = (name: String) -> { - return switch Context.getType(name) { - case TInst(t, params): t.get(); - case _: null; - } - } switch kind { case FVar(t, e): switch t { case TPath(p): - cls = getClsType(p.name); + cls = TypeTools.getClass(Context.getType(p.name)); case _: if (e != null) { switch e.expr { case ENew(t, params): - cls = getClsType(t.name); + cls = TypeTools.getClass(Context.getType(t.name)); case _: } } From 55459893221fbfa97317b7fede7ce800859aaf13 Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 00:31:14 -0300 Subject: [PATCH 18/23] rename var for clearer understanding --- porcelain/macros/StoreMacro.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 2bdd006..f762d34 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -121,7 +121,7 @@ class StoreMacro { } public static function createStaticMethods(mutationFields: Array): Array { - var funcsToCopy: Array = []; + var newMethods: Array = []; for (field in mutationFields) { var cls = getClassFromKind(field.kind); @@ -140,12 +140,12 @@ class StoreMacro { access: [Access.APublic, Access.AStatic], pos: Context.currentPos() }; - funcsToCopy.push(newMethod); + newMethods.push(newMethod); } } } } - return funcsToCopy; + return newMethods; } #end } From dd9728376ab0a243ec92626175ab19b3c8a76aa2 Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 00:51:00 -0300 Subject: [PATCH 19/23] add error handling when attempting to have 2 of the same mutations --- porcelain/macros/StoreMacro.hx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index f762d34..0412f5a 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -122,9 +122,14 @@ class StoreMacro { public static function createStaticMethods(mutationFields: Array): Array { var newMethods: Array = []; + var classesHandled: Map = []; for (field in mutationFields) { var cls = getClassFromKind(field.kind); + if (classesHandled.exists(cls.name)) { + Context.error('Cannot have more than one mutation class of the same type "${cls.name}"', field.pos); + } + classesHandled.set(cls.name, true); var clsFields = cls.statics.get(); for (field in clsFields) { var fieldMeta = field.meta.get(); From 4d61b885593ac04b1225e84c129f628bbc0e598b Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 00:53:27 -0300 Subject: [PATCH 20/23] update internal metadata name to something familliar and unique --- porcelain/macros/StateMutationMacro.hx | 2 +- porcelain/macros/StoreMacro.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx index 985a110..b71094f 100644 --- a/porcelain/macros/StateMutationMacro.hx +++ b/porcelain/macros/StateMutationMacro.hx @@ -45,7 +45,7 @@ class StateMutationMacro { } lf.meta.push({ - name: 'tempFieldData', + name: 'mutationFieldData', params: [macro $v{name}, macro $v{argsForMeta}], pos: lf.pos }); diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index 0412f5a..dfae38c 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -134,7 +134,7 @@ class StoreMacro { for (field in clsFields) { var fieldMeta = field.meta.get(); for (meta in fieldMeta) { - if (meta.name == 'tempFieldData') { + if (meta.name == 'mutationFieldData') { var metaParams = meta.params; var fieldName = ExprTools.getValue(metaParams[0]); var fieldArgs = extractArgsFromObjectArrayExpr(metaParams[1]); From a501c17391a7df57f1bb7d694abd96e7be95db88 Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 01:01:43 -0300 Subject: [PATCH 21/23] add conditonal compilation for ceramic --- porcelain/store/State.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/porcelain/store/State.hx b/porcelain/store/State.hx index ab97639..281e875 100644 --- a/porcelain/store/State.hx +++ b/porcelain/store/State.hx @@ -1,7 +1,9 @@ package porcelain.store; +#if ceramic import tracker.Observable; +#end -class State implements ReadOnly implements Observable { +class State implements ReadOnly #if ceramic implements Observable #end { public function new() {} } From e2ff0896a3c2cb25501038ad1d73765bd41b7708 Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 11:39:59 -0300 Subject: [PATCH 22/23] update to use static vars instead of metadata --- porcelain/macros/StateMutationMacro.hx | 45 +++-------------- porcelain/macros/StoreMacro.hx | 67 ++++---------------------- 2 files changed, 16 insertions(+), 96 deletions(-) diff --git a/porcelain/macros/StateMutationMacro.hx b/porcelain/macros/StateMutationMacro.hx index b71094f..25ad150 100644 --- a/porcelain/macros/StateMutationMacro.hx +++ b/porcelain/macros/StateMutationMacro.hx @@ -12,54 +12,21 @@ class StateMutationMacro { #if macro public static function build(): Array { var localFields = Context.getBuildFields(); + var cls = Context.getLocalClass(); + var className = cls.get().name; + var fieldsToCollect = []; for (lf in localFields) { switch lf.kind { case FFun(f): if (lf.access.contains(AStatic)) { - var name = lf.name; - var args: Array = f.args; - var argsForMeta = []; - - for (arg in args) { - switch arg.type { - case TPath(s): - var arrayTypeName = null; - if (s.name == 'Array') { - var argType = s.params[0]; - switch argType { - case TPType(TPath(p)): - arrayTypeName = p.name; - case _: - } - } - argsForMeta.push({ - name: arg.name, - opt: arg.opt, - typeName: s.name, - isArray: arrayTypeName != null, - arrayType: arrayTypeName - }); - case _: - } - } - - lf.meta.push({ - name: 'mutationFieldData', - params: [macro $v{name}, macro $v{argsForMeta}], - pos: lf.pos - }); - - lf.meta.push({ - name: ':keep', - pos: lf.pos - }); + fieldsToCollect.push(lf); } case _: } } - - return localFields; + StoreMacro.collectedFields.set(className, fieldsToCollect); + return null; } #end } diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index dfae38c..dc4b87a 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -80,46 +80,6 @@ class StoreMacro { return cls; } - public static function extractArgsFromObjectArrayExpr(expr: Expr): Array { - return switch expr.expr { - case EArrayDecl(values): - var exValues = []; - for (value in values) { - switch value.expr { - case EObjectDecl(fields): - var t = fields.list(); - var typeField = t.find(i -> i.field == 'typeName'); - var nameField = t.find(i -> i.field == 'name'); - var optField = t.find(i -> i.field == 'opt'); - var isArray = ExprTools.getValue(t.find(i -> i.field == 'isArray').expr); - var arrType = null; - - if (isArray) { - var arrTypeField = t.find(i -> i.field == 'arrayType'); - var arrTypeName = ExprTools.getValue((arrTypeField.expr)); - var arrParamType = Context.toComplexType(Context.getType(arrTypeName)); - arrType = TPath({ - name: 'Array', - pack: [], - params: [TPType(arrParamType)] - }); - } - var typeName = ExprTools.getValue(typeField.expr); - var type = Context.toComplexType(Context.getType(typeName)); - exValues.push({ - type: isArray ? arrType : type, - opt: ExprTools.getValue(optField.expr), - name: ExprTools.getValue(nameField.expr) - }); - case _: - } - } - exValues; - case _: - null; - } - } - public static function createStaticMethods(mutationFields: Array): Array { var newMethods: Array = []; var classesHandled: Map = []; @@ -130,24 +90,17 @@ class StoreMacro { Context.error('Cannot have more than one mutation class of the same type "${cls.name}"', field.pos); } classesHandled.set(cls.name, true); - var clsFields = cls.statics.get(); + var clsFields = collectedFields.get(cls.name); for (field in clsFields) { - var fieldMeta = field.meta.get(); - for (meta in fieldMeta) { - if (meta.name == 'mutationFieldData') { - var metaParams = meta.params; - var fieldName = ExprTools.getValue(metaParams[0]); - var fieldArgs = extractArgsFromObjectArrayExpr(metaParams[1]); - var func = createFunction(cls.name, fieldName, fieldArgs); - var newMethod: Field = { - name: fieldName, - kind: FieldType.FFun(func), - access: [Access.APublic, Access.AStatic], - pos: Context.currentPos() - }; - newMethods.push(newMethod); - } - } + // @TODO do we want to reuse the same function or create custom which uses Reflect? + var fieldFunc = field.kind; + var newMethod: Field = { + name: field.name, + kind: field.kind, + access: [Access.APublic, Access.AStatic], + pos: Context.currentPos() + }; + newMethods.push(newMethod); } } return newMethods; From 8389200d840fd35c2f8a6324bd228ce2cef02cde Mon Sep 17 00:00:00 2001 From: inc0der Date: Wed, 23 Aug 2023 11:46:29 -0300 Subject: [PATCH 23/23] improve error handling --- porcelain/macros/StoreMacro.hx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/porcelain/macros/StoreMacro.hx b/porcelain/macros/StoreMacro.hx index dc4b87a..5b0a30b 100644 --- a/porcelain/macros/StoreMacro.hx +++ b/porcelain/macros/StoreMacro.hx @@ -58,18 +58,25 @@ class StoreMacro { } } - public static function getClassFromKind(kind): ClassType { + public static function getClass(field: Field): ClassType { var cls; - switch kind { + var getClassByName = (name) -> { + try { + TypeTools.getClass(Context.getType(name)); + } catch (error) { + Context.fatalError('Cannot find class $name', field.pos); + } + } + switch field.kind { case FVar(t, e): switch t { case TPath(p): - cls = TypeTools.getClass(Context.getType(p.name)); + cls = getClassByName(p.name); case _: if (e != null) { switch e.expr { case ENew(t, params): - cls = TypeTools.getClass(Context.getType(t.name)); + cls = getClassByName(t.name); case _: } } @@ -85,7 +92,7 @@ class StoreMacro { var classesHandled: Map = []; for (field in mutationFields) { - var cls = getClassFromKind(field.kind); + var cls = getClass(field); if (classesHandled.exists(cls.name)) { Context.error('Cannot have more than one mutation class of the same type "${cls.name}"', field.pos); }