Skip to content

Commit c8a2e6f

Browse files
committed
Support function-level states
1 parent 9276be1 commit c8a2e6f

File tree

7 files changed

+141
-2
lines changed

7 files changed

+141
-2
lines changed

demo.hxml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44

55
-lib coconut.storybook
66
-lib coconut.vdom
7+
8+
-D analyzer-optimize

extraParams.hxml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--macro coconut.storybook.Setup.setup()
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
-lib coconut.ui
2-
-cp ${SCOPE_DIR}/./src
2+
-cp ${SCOPE_DIR}/../coconut.storybook/src
33
-D coconut.storybook=0.0.0
4+
${SCOPE_DIR}/../coconut.storybook/extraParams.hxml
45
--macro Sys.println("haxe_libraries/coconut.storybook.hxml:3: [Warning] Using dev version of library coconut.storybook")

src/coconut/storybook/Component.macro.hx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package coconut.storybook;
22

33
import haxe.macro.Context;
4+
import haxe.macro.Expr;
45

56
using tink.MacroApi;
67

78
class Component {
89
public static function build() {
910
var builder = new ClassBuilder();
11+
1012
if (!builder.target.meta.has(':tink'))
1113
builder.target.meta.add(':tink', [], Context.currentPos());
14+
15+
if (!builder.target.meta.has(':storybook'))
16+
builder.target.meta.add(':storybook', [], Context.currentPos());
17+
1218
return null;
1319
}
1420

src/coconut/storybook/Setup.hx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package coconut.storybook;
2+
3+
import haxe.macro.Context;
4+
import haxe.macro.Expr;
5+
import tink.SyntaxHub;
6+
7+
using StringTools;
8+
using tink.MacroApi;
9+
10+
class Setup {
11+
public static function setup() {
12+
var debug = false;
13+
SyntaxHub.classLevel.after('tink.lang.Sugar', builder -> {
14+
if (!builder.target.meta.has(':tink'))
15+
return false;
16+
17+
for (field in builder)
18+
switch [field.kind, field.metaNamed(':story'), field.metaNamed(':state')] {
19+
case [FFun(func), stories, v] if (stories.length > 0):
20+
// wrap with Isolated
21+
function subst(e:Expr)
22+
return switch e {
23+
case macro return $ret:
24+
macro return coconut.ui.Isolated.fromHxx({}, {children: $ret});
25+
case e:
26+
e;
27+
}
28+
func.expr = func.expr.map(subst);
29+
30+
// add states
31+
for (states in v)
32+
for (state in states.params)
33+
switch state.expr {
34+
case EVars(vars):
35+
for (v in vars) {
36+
if (v.expr == null)
37+
state.pos.error('@:state var ${v.name} requires a initializer');
38+
if (v.type == null)
39+
state.pos.error('@:state var ${v.name} requires a type hint');
40+
41+
var name = v.name;
42+
var alias = getAlias(name);
43+
var ct = v.type;
44+
45+
var original = func.expr;
46+
func.expr = macro {
47+
var $name = new tink.state.State<$ct>(${v.expr});
48+
var $alias = $i{name};
49+
${func.expr}
50+
}
51+
}
52+
53+
case _:
54+
state.pos.error('Only supports EVars expressions');
55+
}
56+
57+
case _:
58+
// skip
59+
}
60+
61+
return true;
62+
});
63+
64+
SyntaxHub.exprLevel.inward.after(_ -> true, {
65+
appliesTo: builder -> builder.target.meta.has(':storybook'),
66+
apply: (e:Expr) -> {
67+
function transform(name:String, original:Expr, transformed:Expr) {
68+
return switch Context.getLocalTVars()[name] {
69+
case null:
70+
original;
71+
case {t: _.getID() => 'tink.state.State'}:
72+
transformed;
73+
case _:
74+
original;
75+
}
76+
}
77+
78+
switch e.expr {
79+
case EConst(CIdent(name)) if (!isAlias(name)):
80+
transform(name, e, macro $i{getAlias(name)}.value);
81+
82+
case EBinop(OpAssign, macro $i{name}, rhs) if (!isAlias(name)):
83+
transform(name, e, macro $i{getAlias(name)}.set($rhs));
84+
85+
case EBinop(OpAssignOp(binop), macro $i{name}, rhs) if (!isAlias(name)):
86+
var rhs = EBinop(binop, macro $i{getAlias(name)}.value, rhs).at(e.pos);
87+
transform(name, e, macro $i{getAlias(name)}.set($rhs));
88+
89+
case _:
90+
e;
91+
}
92+
}
93+
});
94+
}
95+
96+
inline static function getAlias(name:String) {
97+
return '__alias_${name}';
98+
}
99+
100+
inline static function isAlias(name:String) {
101+
return name.startsWith('__alias_');
102+
}
103+
}

src/coconut/storybook/Storybook.macro.hx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ using tink.MacroApi;
77

88
class Storybook {
99
public static macro function add(exprs:Array<Expr>):Expr {
10+
switch exprs {
11+
case [{expr: EArrayDecl(values)}]:
12+
exprs = values;
13+
case _:
14+
}
15+
1016
var ret = [];
1117

1218
for (expr in exprs) {

tests/Demo.hx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import coconut.ui.*;
55

66
class Demo {
77
static function main() {
8-
Storybook.add(new Button(), new foo.Bar());
8+
Storybook.add([
9+
// @formatter:off
10+
new Button(),
11+
new foo.Bar(),
12+
// @formatter:on
13+
]);
914
}
1015
}
1116

@@ -31,6 +36,14 @@ class Button extends Component {
3136
</button>
3237
';
3338

39+
@:story
40+
@:state(var value:Int = 0)
41+
function withState() '
42+
<button onclick=${value += 1}>
43+
Clicked ${value} time(s)
44+
</button>
45+
';
46+
3447
function wrap(f:()->RenderResult) '
3548
<div style=${{backgroundColor: 'black'}}>${f()}</div>
3649
';
@@ -43,3 +56,10 @@ extern class Knobs {
4356
static function boolean(name:String, value:Bool):Bool;
4457
static function text(name:String, value:String):String;
4558
}
59+
60+
class Isolated extends View {
61+
@:attribute var children:Children;
62+
63+
function render()
64+
'<>${...children}</>';
65+
}

0 commit comments

Comments
 (0)