Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0ccbadc
add base state and store class
inc0der Aug 18, 2023
4d55559
add macro to generate an object with methods from external classes
inc0der Aug 19, 2023
afbea86
remove because a new build macro will replace it
inc0der Aug 20, 2023
72dbed7
Add store interface and build macro base
inc0der Aug 20, 2023
06a5dd2
add status field
inc0der Aug 20, 2023
b78d29e
fix StoreMacro not in correct package
inc0der Aug 20, 2023
354ed02
add state Mutation interface and macro
inc0der Aug 23, 2023
47b435e
fix StoreMacro breaking Mutation class fields due to resolving to early
inc0der Aug 23, 2023
17d7eef
fix import name and class name incorrect after bringing it in
inc0der Aug 23, 2023
bda18f4
add store test
inc0der Aug 23, 2023
74a4b33
refactor some names and tighten up types
inc0der Aug 23, 2023
9a47ab7
refactor to tighten up types and names again
inc0der Aug 23, 2023
ba66daa
fix incorrect package name
inc0der Aug 23, 2023
1b2c89f
add support for array arguments
inc0der Aug 23, 2023
555a3ec
add :keep metadata to each mutation field to prevent removal
inc0der Aug 23, 2023
b479ba3
refactor to use ExprTools to get values of exprs
inc0der Aug 23, 2023
fec0aa6
refactor cleanup using TypeTools
inc0der Aug 23, 2023
5545989
rename var for clearer understanding
inc0der Aug 23, 2023
dd97283
add error handling when attempting to have 2 of the same mutations
inc0der Aug 23, 2023
4d61b88
update internal metadata name to something familliar and unique
inc0der Aug 23, 2023
a501c17
add conditonal compilation for ceramic
inc0der Aug 23, 2023
e2ff089
update to use static vars instead of metadata
inc0der Aug 23, 2023
8389200
improve error handling
inc0der Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions porcelain/macros/StateMutationMacro.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package porcelain.macros;

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<Field> {
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)) {
fieldsToCollect.push(lf);
}
case _:
}
}
StoreMacro.collectedFields.set(className, fieldsToCollect);
return null;
}
#end
}
116 changes: 116 additions & 0 deletions porcelain/macros/StoreMacro.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package porcelain.macros;

import haxe.macro.ExprTools;
import haxe.macro.Type;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.TypeTools;

using Lambda;
using StringTools;

class StoreMacro {
public static var collectedFields: Map<String, Array<Field>> = [];

macro public static function build(): Array<Field> {
var localFields = Context.getBuildFields();
var mutationFields = getFieldsWithMeta(localFields, 'mutation');

localFields.push({
name: 'status',
access: [Access.APublic, Access.AStatic],
kind: FProp('default', 'null', macro : String),
pos: Context.currentPos()
});

if (mutationFields != null) {
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;
}
localFields.push(field);
}
}

return localFields;
}

#if macro
static function getFieldsWithMeta(fields: Array<Field>, metaName: String): Array<Field> {
return fields.filter(i -> i.meta.exists(m -> m.name == metaName));
}

public static function createFunction(className: String, methodName: String, args: Array<FunctionArg>): Function {
var argExprs = args.map(arg -> macro $i{arg.name});

return {
args: args,
ret: null,
expr: macro $b{
[
macro status = 'mutation',
macro Reflect.callMethod($i{className}, Reflect.field($i{className}, $v{methodName}), $a{argExprs}),
macro status = 'resting'
]
}
}
}

public static function getClass(field: Field): ClassType {
var cls;
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 = getClassByName(p.name);
case _:
if (e != null) {
switch e.expr {
case ENew(t, params):
cls = getClassByName(t.name);
case _:
}
}
}

case _:
}
return cls;
}

public static function createStaticMethods(mutationFields: Array<Field>): Array<Field> {
var newMethods: Array<Field> = [];
var classesHandled: Map<String, Bool> = [];

for (field in mutationFields) {
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);
}
classesHandled.set(cls.name, true);
var clsFields = collectedFields.get(cls.name);
for (field in clsFields) {
// @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;
}
#end
}
4 changes: 4 additions & 0 deletions porcelain/store/Mutation.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package porcelain.store;

@:autoBuild(porcelain.macros.StateMutationMacro.build())
interface Mutation {}
9 changes: 9 additions & 0 deletions porcelain/store/State.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package porcelain.store;

#if ceramic
import tracker.Observable;
#end

class State implements ReadOnly #if ceramic implements Observable #end {
public function new() {}
}
4 changes: 4 additions & 0 deletions porcelain/store/Store.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package porcelain.store;

@:autoBuild(porcelain.macros.StoreMacro.build())
interface Store {}
32 changes: 32 additions & 0 deletions sample/src/tests/TestStoreState.hx
Original file line number Diff line number Diff line change
@@ -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');
}
}