phlex 2.3.1 → 2.4.0.beta2

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.
@@ -0,0 +1,579 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Phlex::Compiler
6
+ class MethodCompiler < Refract::MutationVisitor
7
+ def initialize(component)
8
+ super()
9
+ @component = component
10
+ @preamble = []
11
+ @optimized = false
12
+ end
13
+
14
+ def compile(node)
15
+ tree = visit(node)
16
+
17
+ if @optimized
18
+ Compactor.new.visit(
19
+ tree
20
+ )
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ visit Refract::ClassNode do |node|
27
+ node
28
+ end
29
+
30
+ visit Refract::ModuleNode do |node|
31
+ node
32
+ end
33
+
34
+ visit Refract::DefNode do |node|
35
+ if @stack.size == 1
36
+ node.copy(
37
+ body: Refract::BeginNode.new(
38
+ statements: Refract::StatementsNode.new(
39
+ body: [
40
+ Refract::StatementsNode.new(
41
+ body: @preamble
42
+ ),
43
+ Refract::NilNode.new,
44
+ visit(node.body),
45
+ ]
46
+ ),
47
+ rescue_clause: Refract::RescueNode.new(
48
+ exceptions: [],
49
+ reference: Refract::LocalVariableTargetNode.new(
50
+ name: :__phlex_exception__
51
+ ),
52
+ statements: Refract::StatementsNode.new(
53
+ body: [
54
+ Refract::CallNode.new(
55
+ receiver: Refract::ConstantReadNode.new(
56
+ name: :Kernel
57
+ ),
58
+ name: :raise,
59
+ arguments: Refract::ArgumentsNode.new(
60
+ arguments: [
61
+ Refract::CallNode.new(
62
+ name: :__map_exception__,
63
+ arguments: Refract::ArgumentsNode.new(
64
+ arguments: [
65
+ Refract::LocalVariableReadNode.new(
66
+ name: :__phlex_exception__
67
+ ),
68
+ ]
69
+ )
70
+ ),
71
+ ]
72
+ )
73
+ ),
74
+ ]
75
+ ),
76
+ subsequent: nil
77
+ ),
78
+ else_clause: nil,
79
+ ensure_clause: nil
80
+ )
81
+ )
82
+ else
83
+ node
84
+ end
85
+ end
86
+
87
+ visit Refract::CallNode do |node|
88
+ if nil == node.receiver
89
+ if (tag = standard_element?(node))
90
+ return compile_standard_element(node, tag)
91
+ elsif (tag = void_element?(node))
92
+ return compile_void_element(node, tag)
93
+ elsif whitespace_helper?(node)
94
+ return compile_whitespace_helper(node)
95
+ elsif doctype_helper?(node)
96
+ return compile_doctype_helper(node)
97
+ elsif plain_helper?(node)
98
+ return compile_plain_helper(node)
99
+ elsif fragment_helper?(node)
100
+ return compile_fragment_helper(node)
101
+ elsif comment_helper?(node)
102
+ return compile_comment_helper(node)
103
+ elsif raw_helper?(node)
104
+ return compile_raw_helper(node)
105
+ end
106
+ end
107
+
108
+ super(node)
109
+ end
110
+
111
+ visit Refract::BlockNode do |node|
112
+ if node.body
113
+ node.copy(
114
+ body: compile_block_body_node(
115
+ node.body
116
+ )
117
+ )
118
+ else
119
+ node
120
+ end
121
+ end
122
+
123
+ def compile_standard_element(node, tag)
124
+ node => Refract::CallNode
125
+
126
+ Refract::StatementsNode.new(
127
+ body: [
128
+ raw("<#{tag}"),
129
+ *(
130
+ if node.arguments
131
+ compile_phlex_attributes(node.arguments)
132
+ end
133
+ ),
134
+ raw(">"),
135
+ *(
136
+ if node.block
137
+ compile_phlex_block(node.block)
138
+ end
139
+ ),
140
+ raw("</#{tag}>"),
141
+ ]
142
+ )
143
+ end
144
+
145
+ def compile_void_element(node, tag)
146
+ node => Refract::CallNode
147
+
148
+ Refract::StatementsNode.new(
149
+ body: [
150
+ raw("<#{tag}"),
151
+ *(
152
+ if node.arguments
153
+ compile_phlex_attributes(node.arguments)
154
+ end
155
+ ),
156
+ raw(">"),
157
+ ]
158
+ )
159
+ end
160
+
161
+ def compile_phlex_attributes(node)
162
+ arguments = node.arguments
163
+
164
+ if arguments.size == 1 && Refract::KeywordHashNode === (first_argument = arguments[0])
165
+ attributes = first_argument.elements
166
+ literal_attributes = attributes.all? do |attribute|
167
+ Refract::AssocNode === attribute && static_attribute_value_literal?(attribute)
168
+ end
169
+
170
+ if literal_attributes
171
+ return raw(
172
+ Phlex::SGML::Attributes.generate_attributes(
173
+ eval(
174
+ "{#{Refract::Formatter.new.format_node(node).source}}",
175
+ TOPLEVEL_BINDING
176
+ )
177
+ )
178
+ )
179
+ end
180
+
181
+ Refract::CallNode.new(
182
+ name: :__render_attributes__,
183
+ arguments: Refract::ArgumentsNode.new(
184
+ arguments: [
185
+ node,
186
+ ]
187
+ )
188
+ )
189
+ end
190
+ end
191
+
192
+ def compile_phlex_block(node)
193
+ case node
194
+ when Refract::BlockNode
195
+ if output_block?(node)
196
+ return visit(node.body)
197
+ elsif static_content_block?(node)
198
+ content = node.body.body.first
199
+ case content
200
+ when Refract::StringNode, Refract::SymbolNode
201
+ return plain(content.unescaped)
202
+ when Refract::InterpolatedStringNode
203
+ return compile_interpolated_string_node(content)
204
+ when Refract::NilNode
205
+ return nil
206
+ else
207
+ raise
208
+ end
209
+ end
210
+ end
211
+
212
+ Refract::CallNode.new(
213
+ name: :__yield_content__,
214
+ block: node
215
+ )
216
+ end
217
+
218
+ def compile_block_body_node(node)
219
+ node => Refract::StatementsNode | Refract::BeginNode
220
+
221
+ Refract::StatementsNode.new(
222
+ body: [
223
+ Refract::IfNode.new(
224
+ inline: false,
225
+ predicate: Refract::CallNode.new(
226
+ receiver: Refract::SelfNode.new,
227
+ name: :==,
228
+ arguments: Refract::ArgumentsNode.new(
229
+ arguments: [
230
+ Refract::LocalVariableReadNode.new(
231
+ name: self_local
232
+ ),
233
+ ]
234
+ )
235
+ ),
236
+ statements: Refract::StatementsNode.new(
237
+ body: node.body.map { |n| visit(n) }
238
+ ),
239
+ subsequent: Refract::ElseNode.new(
240
+ statements: node
241
+ )
242
+ ),
243
+ ]
244
+ )
245
+ end
246
+
247
+ def compile_interpolated_string_node(node)
248
+ node => Refract::InterpolatedStringNode
249
+
250
+ Refract::StatementsNode.new(
251
+ body: node.parts.map do |part|
252
+ case part
253
+ when Refract::StringNode
254
+ plain(part.unescaped)
255
+ when Refract::EmbeddedVariableNode
256
+ interpolate(part.variable)
257
+ when Refract::EmbeddedStatementsNode
258
+ interpolate(part.statements)
259
+ else
260
+ raise Phlex::Compiler::Error, "Unexpected node type in InterpolatedStringNode: #{part.class}"
261
+ end
262
+ end
263
+ )
264
+ end
265
+
266
+ def compile_whitespace_helper(node)
267
+ node => Refract::CallNode
268
+
269
+ if node.block
270
+ Refract::StatementsNode.new(
271
+ body: [
272
+ raw(" "),
273
+ compile_phlex_block(node.block),
274
+ raw(" "),
275
+ ]
276
+ )
277
+ else
278
+ raw(" ")
279
+ end
280
+ end
281
+
282
+ def compile_doctype_helper(node)
283
+ node => Refract::CallNode
284
+
285
+ raw("<!doctype html>")
286
+ end
287
+
288
+ def compile_plain_helper(node)
289
+ node => Refract::CallNode
290
+
291
+ if node.arguments.arguments in [Refract::StringNode]
292
+ raw(node.arguments.arguments.first.unescaped)
293
+ else
294
+ node
295
+ end
296
+ end
297
+
298
+ def compile_fragment_helper(node)
299
+ node => Refract::CallNode
300
+
301
+ node.copy(
302
+ block: compile_fragment_helper_block(node.block)
303
+ )
304
+ end
305
+
306
+ def compile_fragment_helper_block(node)
307
+ node => Refract::BlockNode
308
+
309
+ node.copy(
310
+ body: Refract::StatementsNode.new(
311
+ body: [
312
+ Refract::LocalVariableWriteNode.new(
313
+ name: :__phlex_original_should_render__,
314
+ value: Refract::LocalVariableReadNode.new(
315
+ name: should_render_local
316
+ )
317
+ ),
318
+ Refract::LocalVariableWriteNode.new(
319
+ name: should_render_local,
320
+ value: Refract::CallNode.new(
321
+ receiver: Refract::LocalVariableReadNode.new(
322
+ name: state_local
323
+ ),
324
+ name: :should_render?
325
+ )
326
+ ),
327
+ visit(node.body),
328
+ Refract::LocalVariableWriteNode.new(
329
+ name: should_render_local,
330
+ value: Refract::LocalVariableReadNode.new(
331
+ name: :__phlex_original_should_render__
332
+ )
333
+ ),
334
+ ]
335
+ )
336
+ )
337
+ end
338
+
339
+ def compile_comment_helper(node)
340
+ node => Refract::CallNode
341
+
342
+ Refract::StatementsNode.new(
343
+ body: [
344
+ raw("<!-- "),
345
+ compile_phlex_block(node.block),
346
+ raw(" -->"),
347
+ ]
348
+ )
349
+ end
350
+
351
+ def compile_raw_helper(node)
352
+ node => Refract::CallNode
353
+ node
354
+ end
355
+
356
+ private def plain(value)
357
+ value => String
358
+ raw(Phlex::Escape.html_escape(value))
359
+ end
360
+
361
+ private def raw(value)
362
+ value => String
363
+
364
+ buffer(
365
+ Refract::StringNode.new(
366
+ unescaped: value
367
+ )
368
+ )
369
+ end
370
+
371
+ private def interpolate(statements)
372
+ buffer(
373
+ Refract::EmbeddedStatementsNode.new(
374
+ statements: Refract::StatementsNode.new(
375
+ body: [
376
+ Refract::CallNode.new(
377
+ receiver: Refract::ConstantPathNode.new(
378
+ parent: Refract::ConstantPathNode.new(
379
+ name: "Phlex"
380
+ ),
381
+ name: "Escape"
382
+ ),
383
+ name: :html_escape,
384
+ arguments: Refract::ArgumentsNode.new(
385
+ arguments: [
386
+ Refract::CallNode.new(
387
+ receiver: Refract::ParenthesesNode.new(
388
+ body: statements
389
+ ),
390
+ name: :to_s
391
+ ),
392
+ ]
393
+ )
394
+ ),
395
+ ]
396
+ )
397
+ )
398
+ )
399
+ end
400
+
401
+ private def buffer(node)
402
+ @optimized = true
403
+ node => Refract::StringNode | Refract::EmbeddedStatementsNode
404
+
405
+ should_render_local
406
+ buffer_local
407
+
408
+ Refract::StatementsNode.new(
409
+ body: [
410
+ Concat.new(
411
+ node
412
+ ),
413
+ ]
414
+ )
415
+ end
416
+
417
+ private def output_block?(node)
418
+ node.body&.body&.any? do |child|
419
+ Refract::CallNode === child && (
420
+ standard_element?(child) ||
421
+ void_element?(child) ||
422
+ plain_helper?(child) ||
423
+ whitespace_helper?(child) ||
424
+ raw_helper?(child)
425
+ )
426
+ end
427
+ end
428
+
429
+ private def static_content_block?(node)
430
+ return false unless node.body&.body&.length == 1
431
+ node.body.body.first in Refract::StringNode | Refract::InterpolatedStringNode | Refract::SymbolNode | Refract::NilNode
432
+ end
433
+
434
+ private def standard_element?(node)
435
+ if (tag = Phlex::HTML::StandardElements.__registered_elements__[node.name]) &&
436
+ (Phlex::HTML::StandardElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
437
+
438
+ tag
439
+ else
440
+ false
441
+ end
442
+ end
443
+
444
+ private def void_element?(node)
445
+ if (tag = Phlex::HTML::VoidElements.__registered_elements__[node.name]) &&
446
+ (Phlex::HTML::VoidElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
447
+
448
+ tag
449
+ else
450
+ false
451
+ end
452
+ end
453
+
454
+ private def static_attribute_value_literal?(value)
455
+ case value
456
+ when Refract::SymbolNode, Refract::StringNode, Refract::IntegerNode, Refract::FloatNode, Refract::TrueNode, Refract::FalseNode, Refract::NilNode
457
+ true
458
+ when Refract::ArrayNode
459
+ value.elements.all? { |n| static_token_value_literal?(n) }
460
+ when Refract::HashNode
461
+ value.elements.all? { |n| static_attribute_value_literal?(n) }
462
+ when Refract::AssocNode
463
+ (Refract::StringNode === value.key || Refract::SymbolNode === value.key) && static_attribute_value_literal?(value.value)
464
+ when Refract::CallNode
465
+ if value in { receiver: Prism::ConstantReadNode[name: :Set]| Prism::ConstantPathNode[name: :Set, parent: nil], name: :[] }
466
+ value.arguments.arguments.all? { |n| static_token_value_literal?(n) }
467
+ else
468
+ false
469
+ end
470
+ else
471
+ false
472
+ end
473
+ end
474
+
475
+ private def static_token_value_literal?(value)
476
+ case value
477
+ when Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::NilNode
478
+ true
479
+ when Prism::ArrayNode
480
+ value.elements.all? { |n| static_token_value_literal?(n) }
481
+ else
482
+ false
483
+ end
484
+ end
485
+
486
+ private def whitespace_helper?(node)
487
+ node.name == :whitespace && own_method_without_scope?(node)
488
+ end
489
+
490
+ private def doctype_helper?(node)
491
+ node.name == :doctype && own_method_without_scope?(node)
492
+ end
493
+
494
+ private def plain_helper?(node)
495
+ node.name == :plain && own_method_without_scope?(node)
496
+ end
497
+
498
+ private def fragment_helper?(node)
499
+ node.name == :fragment && own_method_without_scope?(node)
500
+ end
501
+
502
+ private def comment_helper?(node)
503
+ node.name == :comment && own_method_without_scope?(node)
504
+ end
505
+
506
+ private def raw_helper?(node)
507
+ node.name == :raw && own_method_without_scope?(node)
508
+ end
509
+
510
+ ALLOWED_OWNERS = Set[Phlex::SGML, Phlex::HTML, Phlex::SVG]
511
+ private def own_method_without_scope?(node)
512
+ ALLOWED_OWNERS.include?(Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
513
+ end
514
+
515
+ private def state_local
516
+ :__phlex_state__.tap do |local|
517
+ unless @state_local_set
518
+ @preamble << Refract::LocalVariableWriteNode.new(
519
+ name: local,
520
+ value: Refract::InstanceVariableReadNode.new(
521
+ name: :@_state
522
+ )
523
+ )
524
+
525
+ @state_local_set = true
526
+ end
527
+ end
528
+ end
529
+
530
+ private def buffer_local
531
+ :__phlex_buffer__.tap do |local|
532
+ unless @buffer_local_set
533
+ @preamble << Refract::LocalVariableWriteNode.new(
534
+ name: local,
535
+ value: Refract::CallNode.new(
536
+ receiver: Refract::LocalVariableReadNode.new(
537
+ name: state_local
538
+ ),
539
+ name: :buffer,
540
+ )
541
+ )
542
+
543
+ @buffer_local_set = true
544
+ end
545
+ end
546
+ end
547
+
548
+ private def self_local
549
+ :__phlex_self__.tap do |local|
550
+ unless @self_local_set
551
+ @preamble << Refract::LocalVariableWriteNode.new(
552
+ name: local,
553
+ value: Refract::SelfNode.new
554
+ )
555
+
556
+ @self_local_set = true
557
+ end
558
+ end
559
+ end
560
+
561
+ private def should_render_local
562
+ :__phlex_should_render__.tap do |local|
563
+ unless @should_render_local_set
564
+ @preamble << Refract::LocalVariableWriteNode.new(
565
+ name: :__phlex_should_render__,
566
+ value: Refract::CallNode.new(
567
+ receiver: Refract::LocalVariableReadNode.new(
568
+ name: state_local
569
+ ),
570
+ name: :should_render?
571
+ )
572
+ )
573
+
574
+ @should_render_local_set = true
575
+ end
576
+ end
577
+ end
578
+ end
579
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require "refract"
5
+
6
+ module Phlex::Compiler
7
+ MAP = {}
8
+ DEPENDENCIES = {}
9
+
10
+ Error = Class.new(StandardError)
11
+
12
+ Concat = Data.define(:node) do
13
+ def start_line = nil
14
+ def accept(visitor) = self
15
+ end
16
+
17
+ def self.compile(component)
18
+ path, line = Object.const_source_location(component.name)
19
+ compile_file(path)
20
+ end
21
+
22
+ def self.compile_file(path)
23
+ unless File.exist?(path)
24
+ raise ArgumentError, "Can’t compile #{path} because it doesn’t exist."
25
+ end
26
+
27
+ require(path)
28
+
29
+ source = File.read(path)
30
+ tree = Prism.parse(source).value
31
+ refract = Refract::Converter.new.visit(tree)
32
+
33
+ last_line = source.count("\n")
34
+
35
+ starting_line = last_line + 1
36
+
37
+ results = FileCompiler.new(path).compile(refract)
38
+
39
+ result = Refract::StatementsNode.new(
40
+ body: results.map do |result|
41
+ result.namespace.reverse_each.reduce(
42
+ result.compiled_snippets
43
+ ) do |body, scope|
44
+ scope.copy(
45
+ body: Refract::StatementsNode.new(
46
+ body: [body]
47
+ )
48
+ )
49
+ end
50
+ end
51
+ )
52
+
53
+ formatting_result = Refract::Formatter.new(starting_line:).format_node(result)
54
+
55
+ MAP[path] = formatting_result.source_map
56
+
57
+ eval("# frozen_string_literal: true\n#{formatting_result.source}", TOPLEVEL_BINDING, path, starting_line - 1)
58
+ end
59
+ end
data/lib/phlex/html.rb CHANGED
@@ -58,7 +58,7 @@ class Phlex::HTML < Phlex::SGML
58
58
  if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
59
59
  if attributes.length > 0 # with attributes
60
60
  if block_given # with content block
61
- buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
61
+ buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
62
62
  if tag == "svg"
63
63
  render Phlex::SVG.new(&)
64
64
  else
@@ -66,7 +66,7 @@ class Phlex::HTML < Phlex::SGML
66
66
  end
67
67
  buffer << "</#{tag}>"
68
68
  else # without content
69
- buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << "></#{tag}>"
69
+ buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
70
70
  end
71
71
  else # without attributes
72
72
  if block_given # with content block
@@ -87,7 +87,7 @@ class Phlex::HTML < Phlex::SGML
87
87
  end
88
88
 
89
89
  if attributes.length > 0 # with attributes
90
- buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
90
+ buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
91
91
  else # without attributes
92
92
  buffer << "<#{tag}>"
93
93
  end