hokusai-zero 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/Dockerfile +26 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +91 -0
  5. data/LICENSE +21 -0
  6. data/README.md +28 -0
  7. data/ast/genheader +3 -0
  8. data/ast/include/hashmap.c +1151 -0
  9. data/ast/include/hashmap.c.license +20 -0
  10. data/ast/include/hashmap.h +54 -0
  11. data/ast/src/core/ast.c +448 -0
  12. data/ast/src/core/ast.h +259 -0
  13. data/ast/src/core/common.h +24 -0
  14. data/ast/src/core/component.c +85 -0
  15. data/ast/src/core/component.h +35 -0
  16. data/ast/src/core/hml.c +665 -0
  17. data/ast/src/core/hml.h +11 -0
  18. data/ast/src/core/input.c +458 -0
  19. data/ast/src/core/input.h +118 -0
  20. data/ast/src/core/style.c +101 -0
  21. data/ast/src/core/style.h +41 -0
  22. data/ast/src/core/text.c +784 -0
  23. data/ast/src/core/text.h +93 -0
  24. data/ast/src/core/util.c +140 -0
  25. data/ast/src/core/util.h +48 -0
  26. data/ast/src/hokusai.c +6 -0
  27. data/ast/src/hokusai.h +6 -0
  28. data/ast/test/fixtures/test.ui +13 -0
  29. data/ast/test/greatest.h +1266 -0
  30. data/ast/test/hokusai.c +14 -0
  31. data/ast/test/parser.c +234 -0
  32. data/ast/test/text.c +116 -0
  33. data/ext/extconf.rb +27 -0
  34. data/grammar/Cargo.lock +80 -0
  35. data/grammar/Cargo.toml +26 -0
  36. data/grammar/binding.gyp +20 -0
  37. data/grammar/bindings/node/binding.cc +28 -0
  38. data/grammar/bindings/node/index.js +19 -0
  39. data/grammar/bindings/rust/build.rs +40 -0
  40. data/grammar/bindings/rust/lib.rs +52 -0
  41. data/grammar/corpus/1_document.txt +131 -0
  42. data/grammar/corpus/2_selectors.txt +58 -0
  43. data/grammar/corpus/3_spaces.txt +69 -0
  44. data/grammar/corpus/4_errors.txt +10 -0
  45. data/grammar/corpus/5_macros.txt +175 -0
  46. data/grammar/corpus/6_styles.txt +81 -0
  47. data/grammar/grammar.js +275 -0
  48. data/grammar/package-lock.json +34 -0
  49. data/grammar/package.json +33 -0
  50. data/grammar/src/grammar.json +1269 -0
  51. data/grammar/src/node-types.json +474 -0
  52. data/grammar/src/parser.c +5772 -0
  53. data/grammar/src/scanner.c +258 -0
  54. data/grammar/src/tree_sitter/parser.h +230 -0
  55. data/grammar/src/tree_sitter/scanner.h +12 -0
  56. data/grammar/test.nml +10 -0
  57. data/hokusai.gemspec +19 -0
  58. data/ui/examples/assets/DigitalDisplay.ttf +0 -0
  59. data/ui/examples/assets/OpenSans-Regular.ttf +0 -0
  60. data/ui/examples/assets/addy.png +0 -0
  61. data/ui/examples/assets/baby_sean.png +0 -0
  62. data/ui/examples/assets/football-troll.png +0 -0
  63. data/ui/examples/assets/gear.png +0 -0
  64. data/ui/examples/assets/icecold.ttf +0 -0
  65. data/ui/examples/assets/science-troll.png +0 -0
  66. data/ui/examples/buddy.rb +31 -0
  67. data/ui/examples/clock.rb +58 -0
  68. data/ui/examples/counter.rb +123 -0
  69. data/ui/examples/dynamic.rb +147 -0
  70. data/ui/examples/foobar.rb +236 -0
  71. data/ui/examples/stock.rb +115 -0
  72. data/ui/examples/stock_decider/option.rb +74 -0
  73. data/ui/examples/tic_tac_toe.rb +246 -0
  74. data/ui/lib/lib_hokusai.rb +425 -0
  75. data/ui/spec/hokusai/ast_spec.rb +88 -0
  76. data/ui/spec/hokusai/automation/keys_transcoder_spec.rb +50 -0
  77. data/ui/spec/hokusai/automation/selector_spec.rb +68 -0
  78. data/ui/spec/hokusai/block_spec.rb +126 -0
  79. data/ui/spec/hokusai/directives_spec.rb +327 -0
  80. data/ui/spec/hokusai/e2e/client_spec.rb +58 -0
  81. data/ui/spec/hokusai/e2e/meta_spec.rb +42 -0
  82. data/ui/spec/hokusai/publisher_spec.rb +38 -0
  83. data/ui/spec/hokusai/slots_spec.rb +150 -0
  84. data/ui/spec/hokusai/util/piece_table_spec.rb +90 -0
  85. data/ui/spec/hokusai_spec.rb +0 -0
  86. data/ui/spec/spec_helper.rb +30 -0
  87. data/ui/src/hokusai/ast.rb +446 -0
  88. data/ui/src/hokusai/automation/client.rb +167 -0
  89. data/ui/src/hokusai/automation/constants.rb +98 -0
  90. data/ui/src/hokusai/automation/converters/selector_converter.rb +61 -0
  91. data/ui/src/hokusai/automation/driver.rb +54 -0
  92. data/ui/src/hokusai/automation/driver_command_queue.rb +50 -0
  93. data/ui/src/hokusai/automation/driver_commands/base.rb +79 -0
  94. data/ui/src/hokusai/automation/driver_commands/get_attribute.rb +41 -0
  95. data/ui/src/hokusai/automation/driver_commands/invoke.rb +33 -0
  96. data/ui/src/hokusai/automation/driver_commands/locate.rb +48 -0
  97. data/ui/src/hokusai/automation/driver_commands/trigger_keyboard.rb +94 -0
  98. data/ui/src/hokusai/automation/driver_commands/trigger_mouse.rb +213 -0
  99. data/ui/src/hokusai/automation/keys_transcoder.rb +128 -0
  100. data/ui/src/hokusai/automation/selector.rb +39 -0
  101. data/ui/src/hokusai/automation/server.rb +114 -0
  102. data/ui/src/hokusai/automation.rb +3 -0
  103. data/ui/src/hokusai/backends/raylib/config.rb +47 -0
  104. data/ui/src/hokusai/backends/raylib/font.rb +113 -0
  105. data/ui/src/hokusai/backends/raylib/keys.rb +124 -0
  106. data/ui/src/hokusai/backends/raylib.rb +449 -0
  107. data/ui/src/hokusai/backends/sdl2/Monaco.ttf +0 -0
  108. data/ui/src/hokusai/backends/sdl2/color.rb +12 -0
  109. data/ui/src/hokusai/backends/sdl2/config.rb +31 -0
  110. data/ui/src/hokusai/backends/sdl2/font.rb +127 -0
  111. data/ui/src/hokusai/backends/sdl2/keys.rb +119 -0
  112. data/ui/src/hokusai/backends/sdl2.rb +529 -0
  113. data/ui/src/hokusai/block.rb +237 -0
  114. data/ui/src/hokusai/blocks/button.rb +100 -0
  115. data/ui/src/hokusai/blocks/checkbox.rb +51 -0
  116. data/ui/src/hokusai/blocks/circle.rb +28 -0
  117. data/ui/src/hokusai/blocks/clipped.rb +23 -0
  118. data/ui/src/hokusai/blocks/cursor.rb +49 -0
  119. data/ui/src/hokusai/blocks/dynamic.rb +37 -0
  120. data/ui/src/hokusai/blocks/empty.rb +10 -0
  121. data/ui/src/hokusai/blocks/hblock.rb +35 -0
  122. data/ui/src/hokusai/blocks/image.rb +18 -0
  123. data/ui/src/hokusai/blocks/input.rb +200 -0
  124. data/ui/src/hokusai/blocks/label.rb +39 -0
  125. data/ui/src/hokusai/blocks/panel.rb +126 -0
  126. data/ui/src/hokusai/blocks/rect.rb +24 -0
  127. data/ui/src/hokusai/blocks/scissor_begin.rb +18 -0
  128. data/ui/src/hokusai/blocks/scissor_end.rb +12 -0
  129. data/ui/src/hokusai/blocks/scrollbar.rb +103 -0
  130. data/ui/src/hokusai/blocks/selectable.rb +77 -0
  131. data/ui/src/hokusai/blocks/svg.rb +20 -0
  132. data/ui/src/hokusai/blocks/text.rb +214 -0
  133. data/ui/src/hokusai/blocks/titlebar/osx.rb +145 -0
  134. data/ui/src/hokusai/blocks/toggle.rb +55 -0
  135. data/ui/src/hokusai/blocks/vblock.rb +35 -0
  136. data/ui/src/hokusai/commands/base.rb +22 -0
  137. data/ui/src/hokusai/commands/circle.rb +47 -0
  138. data/ui/src/hokusai/commands/image.rb +45 -0
  139. data/ui/src/hokusai/commands/rect.rb +158 -0
  140. data/ui/src/hokusai/commands/scissor.rb +22 -0
  141. data/ui/src/hokusai/commands/text.rb +92 -0
  142. data/ui/src/hokusai/commands.rb +87 -0
  143. data/ui/src/hokusai/diff.rb +124 -0
  144. data/ui/src/hokusai/error.rb +3 -0
  145. data/ui/src/hokusai/event.rb +54 -0
  146. data/ui/src/hokusai/events/keyboard.rb +84 -0
  147. data/ui/src/hokusai/events/mouse.rb +172 -0
  148. data/ui/src/hokusai/font.rb +280 -0
  149. data/ui/src/hokusai/meta.rb +152 -0
  150. data/ui/src/hokusai/mounting/loop_entry.rb +230 -0
  151. data/ui/src/hokusai/mounting/mount_entry.rb +74 -0
  152. data/ui/src/hokusai/mounting/update_entry.rb +101 -0
  153. data/ui/src/hokusai/node.rb +98 -0
  154. data/ui/src/hokusai/node_mounter.rb +102 -0
  155. data/ui/src/hokusai/painter.rb +214 -0
  156. data/ui/src/hokusai/publisher.rb +32 -0
  157. data/ui/src/hokusai/style.rb +72 -0
  158. data/ui/src/hokusai/types.rb +266 -0
  159. data/ui/src/hokusai/util/clamping_iterator.rb +202 -0
  160. data/ui/src/hokusai/util/piece_table.rb +111 -0
  161. data/ui/src/hokusai/util/selection.rb +145 -0
  162. data/ui/src/hokusai.rb +120 -0
  163. data/ui/vendor/.gitkeep +0 -0
  164. data/vendor/.gitkeep +0 -0
  165. data/xmake.lua +192 -0
  166. metadata +222 -0
@@ -0,0 +1,230 @@
1
+ require "forwardable"
2
+ require_relative "../diff"
3
+
4
+ module Hokusai
5
+ module Mounting
6
+ class LoopContext
7
+ attr_reader :table
8
+ def initialize
9
+ @table = {}
10
+ end
11
+
12
+ def add_entry(var, value)
13
+ table[var] = value
14
+ end
15
+
16
+ def send_target(target, func)
17
+ args = func.args.map do |arg|
18
+ table[arg]
19
+ end
20
+
21
+ target.public_send(func.method, *args)
22
+ end
23
+ end
24
+
25
+ class LoopEntry
26
+ extend Forwardable
27
+
28
+ INDEX_KEY = "index".freeze
29
+
30
+ def_delegators :@entry, :ast, :block, :target, :parent
31
+
32
+ def initialize(mount_entry)
33
+ @entry = mount_entry
34
+ end
35
+
36
+ def register
37
+ child_block_class = target.class.use(ast.type)
38
+ values = target.public_send(ast.loop.method)
39
+
40
+ unless values.is_a?(Enumerable)
41
+ raise Hokusai::Error.new("Loop directive `#{ast.loop.method}` on #{target.class} must return an Enumerable")
42
+ end
43
+
44
+ entries_to_return = []
45
+ secondary_entries = []
46
+
47
+ # puts "COMPILING #{ast.type}"
48
+ # portal = Node.new(ast)
49
+ # node = child_block_class.compile(ast.type, portal)
50
+
51
+ values.each_with_index do |value, index|
52
+ ctx = LoopContext.new
53
+ ctx.add_entry(ast.loop.var, value)
54
+ ctx.add_entry(INDEX_KEY, index)
55
+
56
+ if ast.has_if_condition?
57
+ if ast.if.args.size > 0
58
+ ctx.send_target(target, target.if)
59
+ else
60
+ condition = target.public_send(ast.if.method)
61
+ end
62
+
63
+ next if condition
64
+ end
65
+
66
+ portal = Node.new(ast)
67
+ node = child_block_class.compile(ast.type, portal)
68
+ child_block = child_block_class.new(node: node)
69
+ child_block.node.add_styles(target.class)
70
+ child_block.node.add_props_from_block(target, context: ctx)
71
+ child_block.node.meta.props[ast.loop.var.to_sym] = value
72
+ child_block.node.meta.publisher.add(target)
73
+
74
+ UpdateEntry.new(child_block, block, target).register(context: ctx)
75
+
76
+ child_block.class.provides.merge!(parent.class.provides)
77
+
78
+ block.node.meta << child_block
79
+
80
+ node.ast.children.each_with_index do |child, idx|
81
+ entries_to_return << MountEntry.new(index, child, child_block, child_block, child_block, context: nil)
82
+ end
83
+
84
+ siblings = []
85
+ portal.ast.children.each_with_index do |child, idx|
86
+ siblings << MountEntry.new(idx, child, child_block, child_block, target, context: ctx)
87
+ end
88
+
89
+ secondary_entries << siblings
90
+ end
91
+
92
+ update_loop
93
+
94
+ [entries_to_return, secondary_entries]
95
+ end
96
+
97
+ def update_loop
98
+ block.node.meta.on_update(target) do |ublock, uparent, utarget|
99
+ values = utarget.public_send(ast.loop.method)
100
+
101
+ unless values.is_a?(Enumerable)
102
+ raise Hokusai::Error.new("Loop directive `#{ast.loop.method}` on #{target.class} must return an Enumerable")
103
+ end
104
+
105
+ key_prop = ast.props["key"]
106
+
107
+ raise Hokusai::Error.new("Loop children must have a :key method defined") if key_prop.nil?
108
+
109
+ key_ctx = LoopContext.new
110
+
111
+ new_values = []
112
+
113
+ index_key = "index".freeze
114
+ values.each_with_index do |value, index|
115
+ key_ctx.add_entry(ast.loop.var, value)
116
+ key_ctx.add_entry(index_key, index)
117
+
118
+ if key_prop.value.args.size > 0
119
+ key = key_ctx.send_target(utarget, key_prop.value)
120
+ elsif key_ctx.table[key_prop.value.method]
121
+ key = key_ctx.table[key_prop.value.method]
122
+ else
123
+ key = utarget.public_send(key_prop.value.method)
124
+ end
125
+
126
+ new_values << [key, value]
127
+ end
128
+
129
+ previous_values = []
130
+ children = []
131
+ loop_var = ast.loop.var.to_sym
132
+
133
+ ublock.children?&.each do |child|
134
+ if key = child.node.meta.get_prop(:key)
135
+ raise Hokusai::Error.new("Loop children must use :key field") unless key
136
+
137
+ previous_values << [key, child.node.meta.get_prop(loop_var)]
138
+ end
139
+
140
+ children << child
141
+ end
142
+
143
+ next if new_values == previous_values
144
+
145
+ Diff.new(previous_values, new_values).patch do |patch|
146
+ case patch
147
+ when UpdatePatch
148
+ ctx = LoopContext.new
149
+ ctx.add_entry("index", patch.target)
150
+ ctx.add_entry(ast.loop.var, patch.value)
151
+ children[patch.target].node.add_styles(target.class)
152
+ children[patch.target].node.add_props_from_block(target, context: ctx)
153
+ children[patch.target].node.meta.set_prop(ast.loop.var.to_sym, patch.value)
154
+
155
+ UpdateEntry.new(children[patch.target], uparent, utarget).register(context: ctx)
156
+ when MovePatch
157
+ if patch.delete
158
+ from = children[patch.from]
159
+ children[patch.to] = from
160
+ children[patch.from].public_send(:on_destroy) if children[patch.from].respond_to? :on_destroy
161
+ children[patch.from].node.destroy
162
+ children[patch.from] = nil
163
+ else
164
+ from = children[patch.from]
165
+ to = children[patch.to]
166
+
167
+ children[patch.to] = from
168
+ children[patch.from] = to
169
+ end
170
+
171
+ ctx = LoopContext.new
172
+ ctx.add_entry(INDEX_KEY, patch.to)
173
+ children[patch.to].node.meta.props.each do |k, v|
174
+ ctx.add_entry(k.to_s, v)
175
+ end
176
+
177
+ children[patch.to].node.add_styles(target.class)
178
+ children[patch.to].node.add_props_from_block(target, context: ctx)
179
+ when InsertPatch
180
+ target_ast = ast
181
+ ctx = LoopContext.new
182
+ ctx.add_entry(INDEX_KEY, patch.target)
183
+ ctx.add_entry(ast.loop.var, patch.value)
184
+
185
+ if ast.has_if_condition?
186
+ if ast.if.args.size > 0
187
+ condition = ctx.send_target(target, ast.if.method)
188
+ else
189
+ condition = target.public_send(ast.if.method)
190
+ end
191
+
192
+ if !condition && ast.has_else_condition?
193
+ target_ast = ast.else_ast
194
+ elsif !condition
195
+ children[patch.target].public_send(:on_destroy) if children[patch.target].respond_to? :on_destroy
196
+ children[patch.target].node.destroy
197
+ children[patch.target] = nil
198
+ next
199
+ end
200
+ end
201
+
202
+ child_block_class = utarget.class.use(target_ast.type)
203
+ portal = Node.new(ast)
204
+ node = child_block_class.compile(target_ast.type, portal)
205
+ node.add_props_from_block(target, context: ctx)
206
+ child_block = NodeMounter.new(node, child_block_class).mount(context: ctx)
207
+ child_block.node.add_styles(target.class)
208
+ child_block.node.meta.publisher.add(target)
209
+
210
+ child_block.class.provides.merge!(parent.class.provides)
211
+
212
+ if patch.delete
213
+ children[patch.target] = child_block
214
+ else
215
+ children.insert(patch.target, child_block)
216
+ end
217
+ when DeletePatch
218
+ children[patch.target].public_send(:on_destroy) if children[patch.target].respond_to? :on_destroy
219
+ children[patch.target].node.destroy
220
+ children[patch.target] = nil
221
+ # TODO: update rest of block props
222
+ end
223
+ end
224
+
225
+ ublock.node.meta.children = children.reject(&:nil?)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,74 @@
1
+ module Hokusai
2
+ module Mounting
3
+ class MountEntry
4
+ attr_reader :block, :parent, :ast, :target, :index, :ctx
5
+
6
+ def initialize(index, ast, block, parent, target = parent, context: nil)
7
+ @index = index
8
+ @ast = ast
9
+ @block = block
10
+ @parent = parent
11
+ @target = target
12
+ @ctx = context
13
+ end
14
+
15
+ def loop?
16
+ ast.loop?
17
+ end
18
+
19
+ def virtual?
20
+ ast.virtual?
21
+ end
22
+
23
+ def slot?
24
+ ast.slot?
25
+ end
26
+
27
+ def debug
28
+ StringIO.open do |io|
29
+ io << "#{block.class} | #{ast.type} (#{index})\n"
30
+ io << "#{block.node.ast.children.map(&:type)}"
31
+ io << "parent: #{parent.class}\n"
32
+ io << "target: #{target.class}\n\n"
33
+ end.string
34
+ end
35
+
36
+ def with_block(new_block, supercede_parent: false)
37
+ parent_block = supercede_parent ? block : parent
38
+ MountEntry.new(index, ast, new_block, parent_block, target, context: ctx)
39
+ end
40
+
41
+ def mount(context: nil)
42
+ klass = target.class.use(ast.type)
43
+ portal = Node.new(ast)
44
+
45
+ node = klass.compile(ast.type, portal)
46
+ node.add_styles(target.class)
47
+ node.add_props_from_block(target, context: context || ctx)
48
+
49
+ child_block = klass.new(node: node)
50
+ child_block.node.meta.publisher.add(target) # todo
51
+ UpdateEntry.new(child_block, block, target).register(context: context || ctx)
52
+
53
+ block.class.provides.each do |k, v|
54
+ if v.is_a?(Symbol)
55
+ Node.provide(child_block.node, k, ->{ block.public_send(v) })
56
+
57
+ # child_block.node.meta.provides[k] = -> { block.public_send(v) }
58
+ # child_block.class.provides[k] = -> { block.public_send(v) }
59
+ else
60
+ Node.provide(child_block.node, k, v)
61
+ # child_block.node.meta.provides[k] = v
62
+ # child_block.class.provides[k] = v
63
+ end
64
+ end
65
+
66
+ block.node.meta << child_block
67
+
68
+ yield child_block
69
+
70
+ block.public_send(:on_mounted) if block.respond_to?(:on_mounted)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,101 @@
1
+ module Hokusai
2
+ module Mounting
3
+ class UpdateEntry
4
+ attr_reader :block, :parent, :target
5
+
6
+ def initialize(block, parent, target)
7
+ @block = block
8
+ @parent = parent
9
+ @target = target
10
+ end
11
+
12
+ def meta
13
+ block.node.meta
14
+ end
15
+
16
+ def register(context: nil)
17
+ meta.on_update(target) do |ublock, uparent, utarget|
18
+ if portal = ublock.node.portal
19
+ portal.ast.children.each_with_index do |child, index|
20
+ next unless child.has_if_condition?
21
+
22
+ child_present = ->(child) do
23
+ meta.has_ast?(child, index)
24
+ end
25
+
26
+ if child.if.args.size > 0
27
+ visible = utarget.public_send(child.if.method, context: context)
28
+ else
29
+ visible = utarget.public_send(child.if.method)
30
+ end
31
+ child_block_klass = target.class.use(child.type)
32
+
33
+ if !!visible
34
+ if child.else_condition_active?
35
+ meta.child_delete(index) if child_present.call(child)
36
+ child.else_active = 0
37
+ end
38
+
39
+ unless child_present.call(child)
40
+ portal = Node.new(child, Node.new(child))
41
+ node = child_block_klass.compile("root", portal)
42
+
43
+ stack = []
44
+ child.children.each_with_index do |ast, ast_index|
45
+ stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget)
46
+ end
47
+
48
+ child_block = NodeMounter.new(node, child_block_klass, [stack]).mount(context: context)
49
+ UpdateEntry.new(child_block, block, target).register(context: context)
50
+
51
+ child_block.node.add_styles(target.class)
52
+ child_block.node.add_props_from_block(target, context: context)
53
+ child_block.node.meta.publisher.add(target)
54
+
55
+ meta.children!.insert(index, child_block)
56
+
57
+ child_block.public_send(:before_updated) if child_block.respond_to?(:before_updated)
58
+ child_block.update
59
+ child.else_active = 0
60
+ end
61
+ elsif !visible
62
+ if !child.has_else_condition? || (child.has_else_condition? && !child.else_condition_active?)
63
+ if (child_present.call(child))
64
+ meta.child_delete(index)
65
+ end
66
+ end
67
+
68
+ if child.has_else_condition? && !child.else_condition_active?
69
+ portal = Node.new(child, Node.new(child))
70
+ else_child_block_klass = target.class.use(child.else_ast.type)
71
+ node = else_child_block_klass.compile(child.else_ast.type, portal)
72
+
73
+ stack = []
74
+ child.else_ast.children.each_with_index do |ast, ast_index|
75
+ stack << MountEntry.new(ast_index, ast, ublock, uparent, utarget)
76
+ end
77
+
78
+ child_block = NodeMounter.new(node, else_child_block_klass, [stack]).mount(context: context)
79
+ UpdateEntry.new(child_block, block, utarget).register(context: context)
80
+
81
+ child_block.node.add_styles(utarget.class)
82
+ child_block.node.add_props_from_block(utarget, context: context)
83
+ child_block.node.meta.publisher.add(utarget)
84
+ meta.children!.insert(index, child_block)
85
+
86
+ child_block.public_send(:before_updated) if child_block.respond_to?(:before_updated)
87
+ child_block.update
88
+ child.else_active = 1
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ ublock.public_send(:before_updated) if ublock.respond_to?(:before_updated)
95
+ ublock.node.add_props_from_block(utarget, context: context)
96
+ ublock.public_send(:after_updated) if ublock.respond_to?(:after_updated)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "random/formatter"
4
+ require "forwardable"
5
+ require_relative "./node_mounter"
6
+ require_relative "./meta"
7
+
8
+ module Hokusai
9
+ class Node
10
+ extend Forwardable
11
+ attr_reader :ast, :node, :uuid, :meta, :portal
12
+
13
+ def_delegators :@ast, :slot?, :type, :event
14
+
15
+ def self.provides
16
+ @provides ||= {}
17
+ end
18
+
19
+ def self.provide(node, key, value)
20
+ provides[node.uuid] ||= {}
21
+ provides[node.uuid][key] = value
22
+ end
23
+
24
+ def self.parse(template, name = "root", parent = nil)
25
+ ast = Ast.parse(template, name)
26
+
27
+ new(ast, parent)
28
+ end
29
+
30
+ def initialize(ast, portal = nil)
31
+ @ast = ast
32
+ @portal = portal
33
+ @uuid = Random.hex(6).freeze
34
+ @meta = Meta.new
35
+ end
36
+
37
+ def mount(klass)
38
+ NodeMounter.new(self, klass).mount
39
+ end
40
+
41
+ def destroy
42
+ # meta.children?&.each do |child|
43
+ # child.node.destroy
44
+ # end
45
+ #
46
+ # ast.destroy
47
+ end
48
+
49
+ def emit(name, **args)
50
+ if node = portal
51
+ if event = node.event(name)
52
+ meta.publisher.notify(event.value.name, **args)
53
+ else
54
+ raise Hokusai::Error.new("Invocation failed: @#{name} doesn't exist on #{node.type}")
55
+ end
56
+ end
57
+ end
58
+
59
+ def add_styles(klass)
60
+ return if portal.nil?
61
+
62
+ portal.ast.style_list.each do |style_name|
63
+ style = klass.styles_get[style_name]
64
+
65
+ raise Hokusai::Error.new("Style #{style_name} doesn't exist in the styles for this block #{klass} - #{klass.styles_get.keys}") if style.nil?
66
+
67
+ style.each do |key, value|
68
+ meta.set_prop(key.to_sym, value)
69
+ end
70
+ end
71
+ end
72
+
73
+ def add_props_from_block(parent, context: nil)
74
+ if local_portal = portal
75
+ if block = parent
76
+ local_portal.ast.props.each do |_, prop|
77
+ method = prop.value.method
78
+
79
+ case prop.computed?
80
+ when true
81
+ if prop.value.args.size > 0 && context
82
+ value = context.send_target(block, prop.value)
83
+ elsif context&.table&.[](method)
84
+ value = context.table[method]
85
+ else
86
+ value = block.public_send(method)
87
+ end
88
+ else
89
+ value = method
90
+ end
91
+
92
+ meta.set_prop(prop.name.to_sym, value)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,102 @@
1
+ require_relative "./mounting/loop_entry.rb"
2
+ require_relative "./mounting/mount_entry.rb"
3
+ require_relative "./mounting/update_entry.rb"
4
+
5
+ module Hokusai
6
+ class NodeMounter
7
+ attr_accessor :primary_stack, :secondary_stack
8
+ attr_reader :root
9
+
10
+ def initialize(node, klass, secondary_stack = [], previous_target = nil)
11
+ @root = klass.new(node: node)
12
+ @secondary_stack = secondary_stack
13
+ @primary_stack = []
14
+
15
+ node.ast.children.each_with_index do |child, index|
16
+ primary_stack << Mounting::MountEntry.new(index, child, root, root, previous_target || root)
17
+ end
18
+ end
19
+
20
+ def mount(context: nil)
21
+ while entry = primary_stack.shift
22
+ next if entry.virtual?
23
+
24
+ if entry.loop?
25
+ entries, secondary_entries = Mounting::LoopEntry.new(entry).register
26
+
27
+ self.primary_stack = entries + primary_stack
28
+ self.secondary_stack = secondary_entries + secondary_stack
29
+
30
+ next
31
+ end
32
+
33
+ if entry.ast.has_if_condition?
34
+ next unless entry.target.public_send(entry.ast.if.method)
35
+ end
36
+
37
+ if entry.slot?
38
+ while siblings = secondary_stack.shift
39
+ next if siblings.empty?
40
+
41
+ continue = false
42
+
43
+ while sibling_entry = siblings.pop
44
+ # if we encounter a nested slot, we will
45
+ # add the current siblings to the end of the next
46
+ # non-empty slot sibling group
47
+ # and continue processing slots
48
+ if sibling_entry.slot?
49
+ continue = true
50
+
51
+ secondary_stack.each_with_index do |previous_siblings, i|
52
+ next if previous_siblings.empty?
53
+
54
+ secondary_stack[i] = siblings + previous_siblings
55
+ siblings.clear
56
+
57
+ break
58
+ end
59
+ else
60
+ primary_stack.unshift sibling_entry.with_block(entry.block)
61
+ end
62
+ end
63
+
64
+ next if continue
65
+ break
66
+ end
67
+
68
+ next
69
+ end
70
+
71
+ entry.mount(context: context) do |child_block|
72
+ # create a subentry to register event handling and prop passing
73
+ Mounting::UpdateEntry.new(child_block, entry.block, entry.target).register(context: context || entry.ctx)
74
+
75
+ # Populate the secondary stack with the portal children
76
+ # this stack will be used to populate any slots in the primary_stack
77
+ items = []
78
+
79
+ entry.ast.children.each_with_index do |child, child_index|
80
+ child.has_if_condition?
81
+ items << Mounting::MountEntry.new(child_index, child, child_block, entry.parent, entry.target, context: entry.ctx)
82
+ end
83
+
84
+ secondary_stack.unshift items
85
+
86
+ # populate the primary stack with the newly compiled
87
+ # ast from child_block
88
+ primary_items = []
89
+
90
+ child_block.node.ast.children.each_with_index do |child, child_index|
91
+ primary_items << Mounting::MountEntry.new(child_index, child, child_block, child_block, context: entry.ctx)
92
+ end
93
+
94
+ self.primary_stack = primary_items + primary_stack
95
+ end
96
+ end
97
+
98
+ root.public_send(:on_mounted) if root.respond_to?(:on_mounted)
99
+ root
100
+ end
101
+ end
102
+ end