herb 0.9.4-arm-linux-gnu → 0.9.6-arm-linux-gnu

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/config.yml +57 -21
  3. data/ext/herb/extension.c +8 -0
  4. data/ext/herb/extension_helpers.c +1 -0
  5. data/ext/herb/nodes.c +93 -55
  6. data/lib/herb/3.0/herb.so +0 -0
  7. data/lib/herb/3.1/herb.so +0 -0
  8. data/lib/herb/3.2/herb.so +0 -0
  9. data/lib/herb/3.3/herb.so +0 -0
  10. data/lib/herb/3.4/herb.so +0 -0
  11. data/lib/herb/4.0/herb.so +0 -0
  12. data/lib/herb/action_view/helper_registry.rb +8107 -0
  13. data/lib/herb/ast/nodes.rb +212 -78
  14. data/lib/herb/engine/compiler.rb +4 -6
  15. data/lib/herb/parser_options.rb +7 -2
  16. data/lib/herb/project.rb +2 -5
  17. data/lib/herb/version.rb +1 -1
  18. data/lib/herb/visitor.rb +8 -2
  19. data/sig/herb/action_view/helper_registry.rbs +1144 -0
  20. data/sig/herb/ast/nodes.rbs +85 -34
  21. data/sig/herb/parser_options.rbs +6 -2
  22. data/sig/herb/visitor.rbs +5 -2
  23. data/sig/serialized_ast_nodes.rbs +20 -9
  24. data/src/analyze/action_view/generated_handlers.c +355 -0
  25. data/src/analyze/action_view/generated_handlers.h +16 -0
  26. data/src/analyze/action_view/helper_registry.c +7244 -0
  27. data/src/analyze/action_view/image_tag.c +4 -31
  28. data/src/analyze/action_view/javascript_include_tag.c +1 -42
  29. data/src/analyze/action_view/javascript_tag.c +26 -40
  30. data/src/analyze/action_view/registry.c +2 -2
  31. data/src/analyze/action_view/tag_helper_node_builders.c +23 -2
  32. data/src/analyze/action_view/tag_helpers.c +61 -134
  33. data/src/analyze/action_view/turbo_frame_tag.c +1 -36
  34. data/src/analyze/analyze.c +28 -0
  35. data/src/analyze/analyze_helpers.c +406 -0
  36. data/src/analyze/builders.c +1 -0
  37. data/src/analyze/missing_end.c +16 -0
  38. data/src/analyze/parse_errors.c +3 -2
  39. data/src/analyze/postfix_conditionals.c +326 -0
  40. data/src/analyze/render_nodes.c +231 -35
  41. data/src/analyze/strict_locals.c +22 -338
  42. data/src/analyze/ternary_conditionals.c +265 -0
  43. data/src/analyze/transform.c +23 -2
  44. data/src/ast/ast_nodes.c +114 -57
  45. data/src/ast/ast_pretty_print.c +109 -25
  46. data/src/include/analyze/action_view/helper_registry.h +325 -0
  47. data/src/include/analyze/action_view/tag_helper_handler.h +3 -0
  48. data/src/include/analyze/action_view/tag_helper_node_builders.h +7 -0
  49. data/src/include/analyze/action_view/tag_helpers.h +0 -1
  50. data/src/include/analyze/helpers.h +18 -0
  51. data/src/include/analyze/postfix_conditionals.h +9 -0
  52. data/src/include/analyze/ternary_conditionals.h +15 -0
  53. data/src/include/ast/ast_nodes.h +27 -13
  54. data/src/include/parser/parser.h +1 -0
  55. data/src/include/version.h +1 -1
  56. data/src/parser/match_tags.c +37 -6
  57. data/src/parser.c +9 -0
  58. data/src/visitor.c +50 -7
  59. data/templates/java/org/herb/ast/HelperRegistry.java.erb +258 -0
  60. data/templates/javascript/packages/core/src/action-view-helpers.ts.erb +171 -0
  61. data/templates/javascript/packages/core/src/nodes.ts.erb +5 -1
  62. data/templates/lib/herb/action_view/helper_registry.rb.erb +288 -0
  63. data/templates/rust/src/action_view_helpers.rs.erb +154 -0
  64. data/templates/src/analyze/action_view/generated_handlers.c.erb +230 -0
  65. data/templates/src/analyze/action_view/generated_handlers.h.erb +12 -0
  66. data/templates/src/analyze/action_view/helper_registry.c.erb +114 -0
  67. data/templates/src/include/analyze/action_view/helper_registry.h.erb +82 -0
  68. data/templates/template.rb +338 -1
  69. metadata +19 -3
  70. data/src/analyze/action_view/content_tag.c +0 -78
  71. data/src/analyze/action_view/tag.c +0 -87
@@ -2,12 +2,14 @@
2
2
  #include "../include/analyze/action_view/tag_helper_node_builders.h"
3
3
  #include "../include/analyze/action_view/tag_helpers.h"
4
4
  #include "../include/analyze/analyze.h"
5
+ #include "../include/analyze/helpers.h"
5
6
  #include "../include/ast/ast_nodes.h"
6
7
  #include "../include/errors.h"
7
8
  #include "../include/lib/hb_allocator.h"
8
9
  #include "../include/lib/hb_array.h"
9
10
  #include "../include/lib/hb_buffer.h"
10
11
  #include "../include/lib/hb_string.h"
12
+ #include "../include/lib/string.h"
11
13
  #include "../include/util/util.h"
12
14
  #include "../include/visitor.h"
13
15
 
@@ -101,40 +103,9 @@ static pm_parameters_node_t* find_parameters_node(pm_node_t* root) {
101
103
  return def_node->parameters;
102
104
  }
103
105
 
104
- static token_T* create_strict_local_token(
105
- const uint8_t* prism_node_start,
106
- const uint8_t* prism_node_end,
107
- const char* name,
108
- token_type_T type,
109
- const char* source,
110
- size_t erb_content_byte_offset,
111
- const char* content_bytes,
112
- const char* params_open,
113
- const uint8_t* synthetic_start,
114
- hb_allocator_T* allocator
115
- ) {
116
- size_t params_in_content = (size_t) (params_open - content_bytes);
117
- size_t prism_start_in_synthetic = (size_t) (prism_node_start - synthetic_start);
118
- size_t prism_end_in_synthetic = (size_t) (prism_node_end - synthetic_start);
119
- size_t content_start = params_in_content + (prism_start_in_synthetic - strlen(SYNTHETIC_PREFIX));
120
- size_t content_end = params_in_content + (prism_end_in_synthetic - strlen(SYNTHETIC_PREFIX));
121
-
122
- position_T start = byte_offset_to_position(source, erb_content_byte_offset + content_start);
123
- position_T end = byte_offset_to_position(source, erb_content_byte_offset + content_end);
124
-
125
- return create_synthetic_token(allocator, name, type, start, end);
126
- }
127
-
128
- static char* extract_name_from_location(pm_location_t name_location, hb_allocator_T* allocator) {
129
- size_t length = (size_t) (name_location.end - name_location.start);
130
-
131
- if (length > 0 && name_location.start[length - 1] == ':') { length--; }
132
-
133
- return hb_allocator_strndup(allocator, (const char*) name_location.start, length);
134
- }
135
-
136
106
  static hb_array_T* extract_strict_locals(
137
107
  pm_parameters_node_t* params,
108
+ pm_parser_t* parser,
138
109
  const char* source,
139
110
  size_t erb_content_byte_offset,
140
111
  const char* content_bytes,
@@ -143,317 +114,29 @@ static hb_array_T* extract_strict_locals(
143
114
  hb_array_T* errors,
144
115
  hb_allocator_T* allocator
145
116
  ) {
146
- hb_array_T* locals = hb_array_init(4, allocator);
147
-
148
- if (!params) { return locals; }
149
-
150
- for (size_t index = 0; index < params->optionals.size; index++) {
151
- pm_node_t* optional = params->optionals.nodes[index];
152
- if (!optional || optional->type != PM_OPTIONAL_PARAMETER_NODE) { continue; }
117
+ if (!params) { return hb_array_init(0, allocator); }
153
118
 
154
- pm_optional_parameter_node_t* optional_param = (pm_optional_parameter_node_t*) optional;
155
- pm_location_t location = optional_param->name_loc;
156
- size_t length = (size_t) (location.end - location.start);
157
- char* name = hb_allocator_strndup(allocator, (const char*) location.start, length);
158
-
159
- token_T* name_token = create_strict_local_token(
160
- location.start,
161
- location.end,
162
- name,
163
- TOKEN_IDENTIFIER,
164
- source,
165
- erb_content_byte_offset,
166
- content_bytes,
167
- params_open,
168
- synthetic_start,
169
- allocator
170
- );
171
-
172
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
173
- position_T end = name_token ? name_token->location.end : start;
174
-
175
- size_t value_prism_start = (size_t) (optional_param->value->location.start - synthetic_start);
176
- size_t value_prism_end = (size_t) (optional_param->value->location.end - synthetic_start);
177
- size_t params_in_content = (size_t) (params_open - content_bytes);
178
- size_t value_content_start = params_in_content + (value_prism_start - strlen(SYNTHETIC_PREFIX));
179
- size_t value_content_end = params_in_content + (value_prism_end - strlen(SYNTHETIC_PREFIX));
180
-
181
- size_t value_length = value_prism_end - value_prism_start;
182
- char* value_string =
183
- hb_allocator_strndup(allocator, (const char*) optional_param->value->location.start, value_length);
184
-
185
- position_T value_start = byte_offset_to_position(source, erb_content_byte_offset + value_content_start);
186
- position_T value_end = byte_offset_to_position(source, erb_content_byte_offset + value_content_end);
187
-
188
- AST_RUBY_LITERAL_NODE_T* value_node = ast_ruby_literal_node_init(
189
- hb_string_from_c_string(value_string),
190
- value_start,
191
- value_end,
192
- hb_array_init(0, allocator),
193
- allocator
194
- );
195
-
196
- hb_array_T* node_errors = hb_array_init(1, allocator);
197
- append_strict_locals_positional_argument_error(hb_string_from_c_string(name), start, end, allocator, node_errors);
198
-
199
- AST_RUBY_STRICT_LOCAL_NODE_T* local =
200
- ast_ruby_strict_local_node_init(name_token, value_node, false, false, start, value_end, node_errors, allocator);
201
-
202
- hb_array_append(locals, local);
203
- hb_allocator_dealloc(allocator, name);
204
- hb_allocator_dealloc(allocator, value_string);
205
- }
206
-
207
- for (size_t index = 0; index < params->requireds.size; index++) {
208
- pm_node_t* required = params->requireds.nodes[index];
209
- if (!required) { continue; }
210
-
211
- pm_location_t location = required->location;
212
- size_t length = (size_t) (location.end - location.start);
213
- char* name = hb_allocator_strndup(allocator, (const char*) location.start, length);
214
-
215
- token_T* name_token = create_strict_local_token(
216
- location.start,
217
- location.end,
218
- name,
219
- TOKEN_IDENTIFIER,
220
- source,
221
- erb_content_byte_offset,
222
- content_bytes,
223
- params_open,
224
- synthetic_start,
225
- allocator
226
- );
227
-
228
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
229
- position_T end = name_token ? name_token->location.end : start;
230
-
231
- hb_array_T* node_errors = hb_array_init(1, allocator);
232
- append_strict_locals_positional_argument_error(hb_string_from_c_string(name), start, end, allocator, node_errors);
233
-
234
- AST_RUBY_STRICT_LOCAL_NODE_T* local =
235
- ast_ruby_strict_local_node_init(name_token, NULL, true, false, start, end, node_errors, allocator);
236
-
237
- hb_array_append(locals, local);
238
- hb_allocator_dealloc(allocator, name);
239
- }
240
-
241
- if (params->block) {
242
- pm_block_parameter_node_t* block_param = params->block;
243
-
244
- if (block_param->name) {
245
- size_t length = (size_t) (block_param->name_loc.end - block_param->name_loc.start);
246
- char* name = hb_allocator_strndup(allocator, (const char*) block_param->name_loc.start, length);
247
-
248
- token_T* name_token = create_strict_local_token(
249
- block_param->name_loc.start,
250
- block_param->name_loc.end,
251
- name,
252
- TOKEN_IDENTIFIER,
253
- source,
254
- erb_content_byte_offset,
255
- content_bytes,
256
- params_open,
257
- synthetic_start,
258
- allocator
259
- );
260
-
261
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
262
- position_T end = name_token ? name_token->location.end : start;
263
-
264
- hb_array_T* node_errors = hb_array_init(1, allocator);
265
- append_strict_locals_block_argument_error(hb_string_from_c_string(name), start, end, allocator, node_errors);
266
-
267
- AST_RUBY_STRICT_LOCAL_NODE_T* local =
268
- ast_ruby_strict_local_node_init(name_token, NULL, false, false, start, end, node_errors, allocator);
269
-
270
- hb_array_append(locals, local);
271
- hb_allocator_dealloc(allocator, name);
272
- }
273
- }
274
-
275
- if (params->rest && params->rest->type == PM_REST_PARAMETER_NODE) {
276
- pm_rest_parameter_node_t* splat_param = (pm_rest_parameter_node_t*) params->rest;
277
-
278
- if (splat_param->name) {
279
- size_t length = (size_t) (splat_param->name_loc.end - splat_param->name_loc.start);
280
- char* name = hb_allocator_strndup(allocator, (const char*) splat_param->name_loc.start, length);
281
-
282
- token_T* name_token = create_strict_local_token(
283
- splat_param->name_loc.start,
284
- splat_param->name_loc.end,
285
- name,
286
- TOKEN_IDENTIFIER,
287
- source,
288
- erb_content_byte_offset,
289
- content_bytes,
290
- params_open,
291
- synthetic_start,
292
- allocator
293
- );
294
-
295
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
296
- position_T end = name_token ? name_token->location.end : start;
297
-
298
- hb_array_T* node_errors = hb_array_init(1, allocator);
299
- append_strict_locals_splat_argument_error(hb_string_from_c_string(name), start, end, allocator, node_errors);
300
-
301
- AST_RUBY_STRICT_LOCAL_NODE_T* local =
302
- ast_ruby_strict_local_node_init(name_token, NULL, false, false, start, end, node_errors, allocator);
303
-
304
- hb_array_append(locals, local);
305
- hb_allocator_dealloc(allocator, name);
306
- }
307
- }
308
-
309
- for (size_t index = 0; index < params->keywords.size; index++) {
310
- pm_node_t* keyword_node = params->keywords.nodes[index];
311
-
312
- if (keyword_node->type == PM_REQUIRED_KEYWORD_PARAMETER_NODE) {
313
- pm_required_keyword_parameter_node_t* keyword_param = (pm_required_keyword_parameter_node_t*) keyword_node;
314
-
315
- char* name = extract_name_from_location(keyword_param->name_loc, allocator);
316
- if (!name) { continue; }
317
-
318
- token_T* name_token = create_strict_local_token(
319
- keyword_param->name_loc.start,
320
- keyword_param->name_loc.end,
321
- name,
322
- TOKEN_IDENTIFIER,
323
- source,
324
- erb_content_byte_offset,
325
- content_bytes,
326
- params_open,
327
- synthetic_start,
328
- allocator
329
- );
330
-
331
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
332
- position_T end = name_token ? name_token->location.end : start;
333
-
334
- AST_RUBY_STRICT_LOCAL_NODE_T* local = ast_ruby_strict_local_node_init(
335
- name_token,
336
- NULL,
337
- true,
338
- false,
339
- start,
340
- end,
341
- hb_array_init(0, allocator),
342
- allocator
343
- );
344
-
345
- hb_array_append(locals, local);
346
- hb_allocator_dealloc(allocator, name);
347
-
348
- } else if (keyword_node->type == PM_OPTIONAL_KEYWORD_PARAMETER_NODE) {
349
- pm_optional_keyword_parameter_node_t* keyword_param = (pm_optional_keyword_parameter_node_t*) keyword_node;
350
-
351
- char* name = extract_name_from_location(keyword_param->name_loc, allocator);
352
- if (!name) { continue; }
353
-
354
- size_t value_prism_start = (size_t) (keyword_param->value->location.start - synthetic_start);
355
- size_t value_prism_end = (size_t) (keyword_param->value->location.end - synthetic_start);
356
- size_t params_in_content = (size_t) (params_open - content_bytes);
357
- size_t value_content_start = params_in_content + (value_prism_start - strlen(SYNTHETIC_PREFIX));
358
- size_t value_content_end = params_in_content + (value_prism_end - strlen(SYNTHETIC_PREFIX));
359
-
360
- size_t value_length = value_prism_end - value_prism_start;
361
- char* value_string =
362
- hb_allocator_strndup(allocator, (const char*) keyword_param->value->location.start, value_length);
363
-
364
- position_T value_start = byte_offset_to_position(source, erb_content_byte_offset + value_content_start);
365
- position_T value_end = byte_offset_to_position(source, erb_content_byte_offset + value_content_end);
366
-
367
- AST_RUBY_LITERAL_NODE_T* value_node = ast_ruby_literal_node_init(
368
- hb_string_from_c_string(value_string),
369
- value_start,
370
- value_end,
371
- hb_array_init(0, allocator),
372
- allocator
373
- );
374
-
375
- token_T* name_token = create_strict_local_token(
376
- keyword_param->name_loc.start,
377
- keyword_param->name_loc.end,
378
- name,
379
- TOKEN_IDENTIFIER,
380
- source,
381
- erb_content_byte_offset,
382
- content_bytes,
383
- params_open,
384
- synthetic_start,
385
- allocator
386
- );
387
-
388
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
389
-
390
- AST_RUBY_STRICT_LOCAL_NODE_T* local = ast_ruby_strict_local_node_init(
391
- name_token,
392
- value_node,
393
- false,
394
- false,
395
- start,
396
- value_end,
397
- hb_array_init(0, allocator),
398
- allocator
399
- );
400
-
401
- hb_array_append(locals, local);
402
- hb_allocator_dealloc(allocator, name);
403
- hb_allocator_dealloc(allocator, value_string);
404
- }
405
- }
406
-
407
- if (params->keyword_rest && params->keyword_rest->type == PM_KEYWORD_REST_PARAMETER_NODE) {
408
- pm_keyword_rest_parameter_node_t* keyword_rest_param = (pm_keyword_rest_parameter_node_t*) params->keyword_rest;
409
-
410
- if (keyword_rest_param->name) {
411
- char* name = extract_name_from_location(keyword_rest_param->name_loc, allocator);
412
- if (!name) { return locals; }
413
-
414
- token_T* name_token = create_strict_local_token(
415
- keyword_rest_param->name_loc.start,
416
- keyword_rest_param->name_loc.end,
417
- name,
418
- TOKEN_IDENTIFIER,
419
- source,
420
- erb_content_byte_offset,
421
- content_bytes,
422
- params_open,
423
- synthetic_start,
424
- allocator
425
- );
426
-
427
- position_T start = name_token ? name_token->location.start : (position_T) { .line = 1, .column = 1 };
428
- position_T end = name_token ? name_token->location.end : start;
429
-
430
- AST_RUBY_STRICT_LOCAL_NODE_T* local = ast_ruby_strict_local_node_init(
431
- name_token,
432
- NULL,
433
- false,
434
- true,
435
- start,
436
- end,
437
- hb_array_init(0, allocator),
438
- allocator
439
- );
119
+ size_t params_in_content = (size_t) (params_open - content_bytes);
120
+ size_t prefix_length = strlen(SYNTHETIC_PREFIX);
121
+ size_t source_base_offset = erb_content_byte_offset + params_in_content - prefix_length;
440
122
 
441
- hb_array_append(locals, local);
442
- hb_allocator_dealloc(allocator, name);
443
- } else {
444
- size_t params_in_content = (size_t) (params_open - content_bytes);
445
- size_t operator_prism_start = (size_t) (keyword_rest_param->operator_loc.start - synthetic_start);
446
- size_t operator_prism_end = (size_t) (keyword_rest_param->operator_loc.end - synthetic_start);
447
- size_t content_start = params_in_content + (operator_prism_start - strlen(SYNTHETIC_PREFIX));
448
- size_t content_end = params_in_content + (operator_prism_end - strlen(SYNTHETIC_PREFIX));
123
+ hb_array_T* locals =
124
+ extract_parameters_from_prism(params, parser, source, source_base_offset, synthetic_start, allocator);
449
125
 
450
- position_T start = byte_offset_to_position(source, erb_content_byte_offset + content_start);
451
- position_T end = byte_offset_to_position(source, erb_content_byte_offset + content_end);
126
+ for (size_t index = 0; index < hb_array_size(locals); index++) {
127
+ AST_RUBY_PARAMETER_NODE_T* local = hb_array_get(locals, index);
128
+ if (!local) { continue; }
452
129
 
453
- AST_RUBY_STRICT_LOCAL_NODE_T* local =
454
- ast_ruby_strict_local_node_init(NULL, NULL, false, true, start, end, hb_array_init(0, allocator), allocator);
130
+ position_T start = local->base.location.start;
131
+ position_T end = local->base.location.end;
132
+ hb_string_T name = local->name ? local->name->value : hb_string("");
455
133
 
456
- hb_array_append(locals, local);
134
+ if (string_equals(local->kind.data, "positional")) {
135
+ append_strict_locals_positional_argument_error(name, start, end, allocator, local->base.errors);
136
+ } else if (string_equals(local->kind.data, "rest")) {
137
+ append_strict_locals_splat_argument_error(name, start, end, allocator, local->base.errors);
138
+ } else if (string_equals(local->kind.data, "block")) {
139
+ append_strict_locals_block_argument_error(name, start, end, allocator, local->base.errors);
457
140
  }
458
141
  }
459
142
 
@@ -575,6 +258,7 @@ static AST_ERB_STRICT_LOCALS_NODE_T* create_strict_locals_node(
575
258
  } else {
576
259
  locals = extract_strict_locals(
577
260
  params_node,
261
+ &parser,
578
262
  source,
579
263
  erb_content_byte_offset,
580
264
  content_bytes,
@@ -0,0 +1,265 @@
1
+ #include "../include/analyze/ternary_conditionals.h"
2
+ #include "../include/analyze/action_view/tag_helper_node_builders.h"
3
+ #include "../include/analyze/analyze.h"
4
+ #include "../include/analyze/analyzed_ruby.h"
5
+ #include "../include/ast/ast_nodes.h"
6
+ #include "../include/lib/hb_allocator.h"
7
+ #include "../include/lib/hb_array.h"
8
+ #include "../include/lib/hb_string.h"
9
+ #include "../include/prism/herb_prism_node.h"
10
+ #include "../include/visitor.h"
11
+
12
+ #include <prism.h>
13
+ #include <stdbool.h>
14
+ #include <string.h>
15
+
16
+ static bool is_erb_output_tag(AST_ERB_CONTENT_NODE_T* erb_node) {
17
+ if (!erb_node || !erb_node->tag_opening) { return false; }
18
+
19
+ hb_string_T opening = erb_node->tag_opening->value;
20
+
21
+ return opening.length >= 3 && opening.data[0] == '<' && opening.data[1] == '%' && opening.data[2] == '=';
22
+ }
23
+
24
+ static pm_node_t* find_ternary_statement(analyzed_ruby_T* analyzed) {
25
+ if (!analyzed || !analyzed->valid || !analyzed->root) { return NULL; }
26
+
27
+ if (analyzed->root->type != PM_PROGRAM_NODE) { return NULL; }
28
+ pm_program_node_t* program = (pm_program_node_t*) analyzed->root;
29
+
30
+ if (!program->statements || program->statements->body.size != 1) { return NULL; }
31
+ pm_node_t* statement = program->statements->body.nodes[0];
32
+
33
+ if (statement->type != PM_IF_NODE) { return NULL; }
34
+ pm_if_node_t* if_node = (pm_if_node_t*) statement;
35
+
36
+ if (if_node->if_keyword_loc.start == NULL && if_node->subsequent != NULL) { return statement; }
37
+
38
+ return NULL;
39
+ }
40
+
41
+ typedef struct {
42
+ char* source;
43
+ size_t offset_in_content;
44
+ size_t length;
45
+ } body_info_T;
46
+
47
+ static body_info_T extract_statements_body_info(
48
+ pm_statements_node_t* statements,
49
+ analyzed_ruby_T* analyzed,
50
+ hb_allocator_T* allocator
51
+ ) {
52
+ body_info_T info = { .source = NULL, .offset_in_content = 0, .length = 0 };
53
+
54
+ if (!statements || statements->body.size == 0) { return info; }
55
+
56
+ pm_node_t* first = statements->body.nodes[0];
57
+ pm_node_t* last = statements->body.nodes[statements->body.size - 1];
58
+
59
+ const uint8_t* start = first->location.start;
60
+ const uint8_t* end = last->location.end;
61
+
62
+ const uint8_t* parser_start = analyzed->parser.start;
63
+ size_t source_length = (size_t) (analyzed->parser.end - parser_start);
64
+
65
+ if (start < parser_start || end > parser_start + source_length) { return info; }
66
+
67
+ const uint8_t* body_start = start;
68
+ const uint8_t* body_end = end;
69
+
70
+ if (body_start > parser_start && *(body_start - 1) == ' ') { body_start--; }
71
+ if (body_end < parser_start + source_length && *body_end == ' ') { body_end++; }
72
+
73
+ info.offset_in_content = (size_t) (body_start - parser_start);
74
+ info.length = (size_t) (body_end - body_start);
75
+ info.source = hb_allocator_strndup(allocator, (const char*) body_start, info.length);
76
+
77
+ return info;
78
+ }
79
+
80
+ static char* extract_condition_source(pm_if_node_t* if_node, hb_allocator_T* allocator) {
81
+ pm_node_t* predicate = if_node->predicate;
82
+
83
+ if (!predicate) { return NULL; }
84
+
85
+ size_t length = (size_t) (predicate->location.end - predicate->location.start);
86
+
87
+ return hb_allocator_strndup(allocator, (const char*) predicate->location.start, length);
88
+ }
89
+
90
+ AST_NODE_T* transform_ternary_expression(
91
+ AST_ERB_CONTENT_NODE_T* erb_node,
92
+ pm_if_node_t* if_node,
93
+ hb_allocator_T* allocator
94
+ ) {
95
+ body_info_T true_info = extract_statements_body_info(if_node->statements, erb_node->analyzed_ruby, allocator);
96
+ if (!true_info.source) { return NULL; }
97
+
98
+ pm_else_node_t* else_node = (pm_else_node_t*) if_node->subsequent;
99
+ if (!else_node) { return NULL; }
100
+
101
+ body_info_T false_info = extract_statements_body_info(else_node->statements, erb_node->analyzed_ruby, allocator);
102
+ if (!false_info.source) { return NULL; }
103
+
104
+ char* condition_source = extract_condition_source(if_node, allocator);
105
+ if (!condition_source) { return NULL; }
106
+
107
+ position_T start = erb_node->base.location.start;
108
+ position_T end = erb_node->base.location.end;
109
+ position_T content_start = erb_node->content->location.start;
110
+
111
+ position_T true_content_start = { .line = content_start.line,
112
+ .column = content_start.column + (uint32_t) true_info.offset_in_content };
113
+
114
+ position_T true_content_end = { .line = content_start.line,
115
+ .column = content_start.column
116
+ + (uint32_t) (true_info.offset_in_content + true_info.length) };
117
+
118
+ token_T* true_content =
119
+ create_synthetic_token(allocator, true_info.source, TOKEN_ERB_CONTENT, true_content_start, true_content_end);
120
+
121
+ AST_ERB_CONTENT_NODE_T* true_erb_node = ast_erb_content_node_init(
122
+ erb_node->tag_opening,
123
+ true_content,
124
+ erb_node->tag_closing,
125
+ NULL,
126
+ false,
127
+ true,
128
+ HERB_PRISM_NODE_EMPTY,
129
+ start,
130
+ end,
131
+ hb_array_init(0, allocator),
132
+ allocator
133
+ );
134
+
135
+ if (!true_erb_node) { return NULL; }
136
+
137
+ position_T false_content_start = { .line = content_start.line,
138
+ .column = content_start.column + (uint32_t) false_info.offset_in_content };
139
+
140
+ position_T false_content_end = { .line = content_start.line,
141
+ .column = content_start.column
142
+ + (uint32_t) (false_info.offset_in_content + false_info.length) };
143
+
144
+ token_T* false_content =
145
+ create_synthetic_token(allocator, false_info.source, TOKEN_ERB_CONTENT, false_content_start, false_content_end);
146
+
147
+ AST_ERB_CONTENT_NODE_T* false_erb_node = ast_erb_content_node_init(
148
+ erb_node->tag_opening,
149
+ false_content,
150
+ erb_node->tag_closing,
151
+ NULL,
152
+ false,
153
+ true,
154
+ HERB_PRISM_NODE_EMPTY,
155
+ start,
156
+ end,
157
+ hb_array_init(0, allocator),
158
+ allocator
159
+ );
160
+
161
+ if (!false_erb_node) { return NULL; }
162
+
163
+ hb_array_T* true_statements = hb_array_init(1, allocator);
164
+ hb_array_T* false_statements = hb_array_init(1, allocator);
165
+
166
+ hb_array_append(true_statements, (AST_NODE_T*) true_erb_node);
167
+ hb_array_append(false_statements, (AST_NODE_T*) false_erb_node);
168
+
169
+ token_T* else_opening = create_synthetic_token(allocator, "<%", TOKEN_ERB_START, end, end);
170
+ token_T* else_content_token = create_synthetic_token(allocator, " else ", TOKEN_ERB_CONTENT, end, end);
171
+ token_T* else_closing = create_synthetic_token(allocator, "%>", TOKEN_ERB_END, end, end);
172
+
173
+ AST_ERB_ELSE_NODE_T* erb_else_node = ast_erb_else_node_init(
174
+ else_opening,
175
+ else_content_token,
176
+ else_closing,
177
+ false_statements,
178
+ end,
179
+ end,
180
+ hb_array_init(0, allocator),
181
+ allocator
182
+ );
183
+
184
+ hb_buffer_T condition_buffer;
185
+ hb_buffer_init(&condition_buffer, 64, allocator);
186
+ hb_buffer_append(&condition_buffer, " if ");
187
+ hb_buffer_append(&condition_buffer, condition_source);
188
+ hb_buffer_append(&condition_buffer, " ");
189
+
190
+ const char* condition_content = hb_buffer_value(&condition_buffer);
191
+
192
+ token_T* if_opening = create_synthetic_token(allocator, "<%", TOKEN_ERB_START, start, start);
193
+ token_T* if_content = create_synthetic_token(allocator, condition_content, TOKEN_ERB_CONTENT, start, end);
194
+ token_T* if_closing = create_synthetic_token(allocator, "%>", TOKEN_ERB_END, end, end);
195
+
196
+ token_T* end_opening = create_synthetic_token(allocator, "<%", TOKEN_ERB_START, end, end);
197
+ token_T* end_content = create_synthetic_token(allocator, " end ", TOKEN_ERB_CONTENT, end, end);
198
+ token_T* end_closing = create_synthetic_token(allocator, "%>", TOKEN_ERB_END, end, end);
199
+
200
+ AST_ERB_END_NODE_T* end_node =
201
+ ast_erb_end_node_init(end_opening, end_content, end_closing, end, end, hb_array_init(0, allocator), allocator);
202
+
203
+ herb_prism_node_T empty_prism_node = HERB_PRISM_NODE_EMPTY;
204
+
205
+ AST_ERB_IF_NODE_T* result_if_node = ast_erb_if_node_init(
206
+ if_opening,
207
+ if_content,
208
+ if_closing,
209
+ NULL,
210
+ empty_prism_node,
211
+ true_statements,
212
+ (AST_NODE_T*) erb_else_node,
213
+ end_node,
214
+ start,
215
+ end,
216
+ hb_array_init(0, allocator),
217
+ allocator
218
+ );
219
+
220
+ return (AST_NODE_T*) result_if_node;
221
+ }
222
+
223
+ static void transform_ternary_array(hb_array_T* array, analyze_ruby_context_T* context) {
224
+ if (!array || !context) { return; }
225
+
226
+ for (size_t i = 0; i < hb_array_size(array); i++) {
227
+ AST_NODE_T* child = hb_array_get(array, i);
228
+ if (!child) { continue; }
229
+ if (child->type != AST_ERB_CONTENT_NODE) { continue; }
230
+
231
+ AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) child;
232
+ if (!is_erb_output_tag(erb_node)) { continue; }
233
+ if (!erb_node->analyzed_ruby) { continue; }
234
+
235
+ pm_node_t* ternary_node = find_ternary_statement(erb_node->analyzed_ruby);
236
+ if (!ternary_node) { continue; }
237
+
238
+ AST_NODE_T* replacement = transform_ternary_expression(erb_node, (pm_if_node_t*) ternary_node, context->allocator);
239
+ if (replacement) { hb_array_set(array, i, replacement); }
240
+ }
241
+ }
242
+
243
+ static void transform_ternary_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
244
+ if (!node || !context) { return; }
245
+
246
+ switch (node->type) {
247
+ case AST_DOCUMENT_NODE: transform_ternary_array(((AST_DOCUMENT_NODE_T*) node)->children, context); break;
248
+ case AST_HTML_ELEMENT_NODE: transform_ternary_array(((AST_HTML_ELEMENT_NODE_T*) node)->body, context); break;
249
+ case AST_HTML_OPEN_TAG_NODE: transform_ternary_array(((AST_HTML_OPEN_TAG_NODE_T*) node)->children, context); break;
250
+ case AST_HTML_ATTRIBUTE_VALUE_NODE:
251
+ transform_ternary_array(((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children, context);
252
+ break;
253
+ default: break;
254
+ }
255
+ }
256
+
257
+ bool transform_ternary_conditional_nodes(const AST_NODE_T* node, void* data) {
258
+ analyze_ruby_context_T* context = (analyze_ruby_context_T*) data;
259
+
260
+ transform_ternary_blocks(node, context);
261
+
262
+ herb_visit_child_nodes(node, transform_ternary_conditional_nodes, data);
263
+
264
+ return false;
265
+ }