prism 2.0.0.rc1-arm-linux-gnu → 2.1.0-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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -41
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/LICENSE.md +1 -1
  5. data/Makefile +8 -8
  6. data/README.md +3 -1
  7. data/docs/fuzzing.md +5 -5
  8. data/docs/serialization.md +20 -6
  9. data/ext/prism/api_node.c +81 -81
  10. data/ext/prism/extension.c +54 -69
  11. data/ext/prism/extension.h +1 -1
  12. data/include/prism/arena.h +8 -0
  13. data/include/prism/arena.hpp +42 -0
  14. data/include/prism/ast.h +11 -1
  15. data/include/prism/ast.hpp +641 -0
  16. data/include/prism/buffer.h +8 -0
  17. data/include/prism/buffer.hpp +55 -0
  18. data/include/prism/comments.h +17 -0
  19. data/include/prism/compiler/accel.h +3 -1
  20. data/include/prism/compiler/branch.h +26 -0
  21. data/include/prism/compiler/bswap.h +46 -0
  22. data/include/prism/compiler/cplusplus.hpp +16 -0
  23. data/include/prism/compiler/endianness.h +34 -0
  24. data/include/prism/compiler/exceptions.hpp +17 -0
  25. data/include/prism/compiler/format.h +2 -0
  26. data/include/prism/compiler/lifetimebound.hpp +25 -0
  27. data/include/prism/constant_pool.h +25 -0
  28. data/include/prism/diagnostic.h +18 -0
  29. data/include/prism/errors_format.h +8 -0
  30. data/include/prism/internal/buffer.h +3 -0
  31. data/include/prism/internal/comments.h +2 -1
  32. data/include/prism/internal/node.h +8 -4
  33. data/include/prism/internal/parser.h +44 -13
  34. data/include/prism/internal/static_literals.h +24 -64
  35. data/include/prism/internal/stringy.h +1 -6
  36. data/include/prism/internal/xxh3.h +16 -0
  37. data/include/prism/json.h +8 -0
  38. data/include/prism/line_offset_list.h +8 -0
  39. data/include/prism/magic_comments.h +18 -0
  40. data/include/prism/node.h +55 -10
  41. data/include/prism/node.hpp +272 -0
  42. data/include/prism/node_type.hpp +38 -0
  43. data/include/prism/options.h +10 -1
  44. data/include/prism/options.hpp +43 -0
  45. data/include/prism/parser.h +93 -64
  46. data/include/prism/parser.hpp +274 -0
  47. data/include/prism/prettyprint.h +8 -0
  48. data/include/prism/serialize.h +8 -0
  49. data/include/prism/source.h +8 -0
  50. data/include/prism/stream.h +8 -0
  51. data/include/prism/string_query.h +19 -0
  52. data/include/prism/stringy.h +16 -27
  53. data/include/prism/stringy.hpp +31 -0
  54. data/include/prism/version.h +10 -2
  55. data/include/prism.h +0 -8
  56. data/include/prism.hpp +24 -0
  57. data/lib/prism/3.3/prism.so +0 -0
  58. data/lib/prism/3.4/prism.so +0 -0
  59. data/lib/prism/4.0/prism.so +0 -0
  60. data/lib/prism/ffi.rb +6 -0
  61. data/lib/prism/node.rb +736 -0
  62. data/lib/prism/parse_result.rb +23 -8
  63. data/lib/prism/serialize.rb +2568 -3287
  64. data/lib/prism/string_query.rb +8 -0
  65. data/prism.gemspec +21 -4
  66. data/rbi/generated/prism/node.rbi +2430 -968
  67. data/rbi/generated/prism/parse_result.rbi +16 -4
  68. data/rbi/generated/prism/serialize.rbi +8 -4
  69. data/rbi/generated/prism/string_query.rbi +7 -0
  70. data/sig/generated/prism/node.rbs +2439 -967
  71. data/sig/generated/prism/parse_result.rbs +16 -4
  72. data/sig/generated/prism/serialize.rbs +9 -4
  73. data/sig/generated/prism/string_query.rbs +7 -0
  74. data/src/arena.c +6 -1
  75. data/src/buffer.c +9 -0
  76. data/src/comments.c +11 -10
  77. data/src/diagnostic.c +24 -4
  78. data/src/integer.c +6 -1
  79. data/src/node.c +216 -368
  80. data/src/options.c +7 -13
  81. data/src/parser.c +70 -48
  82. data/src/prism.c +625 -462
  83. data/src/regexp.c +0 -1
  84. data/src/serialize.c +2301 -1950
  85. data/src/source.c +8 -2
  86. data/src/static_literals.c +280 -405
  87. data/src/string_query.c +14 -0
  88. data/src/stringy.c +3 -33
  89. data/src/xxh3.c +292 -0
  90. metadata +20 -3
data/src/prism.c CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "prism/compiler/accel.h"
2
+ #include "prism/compiler/branch.h"
2
3
  #include "prism/compiler/fallthrough.h"
3
4
  #include "prism/compiler/unused.h"
4
5
 
@@ -68,24 +69,6 @@
68
69
  # define PM_STATIC_ASSERT(line, condition, message) typedef char PM_CONCATENATE(static_assert_, line)[(condition) ? 1 : -1]
69
70
  #endif
70
71
 
71
- /**
72
- * Support PRISM_LIKELY and PRISM_UNLIKELY to help the compiler optimize its
73
- * branch predication.
74
- */
75
- #if defined(__GNUC__) || defined(__clang__)
76
- /** The compiler should predicate that this branch will be taken. */
77
- #define PRISM_LIKELY(x) __builtin_expect(!!(x), 1)
78
-
79
- /** The compiler should predicate that this branch will not be taken. */
80
- #define PRISM_UNLIKELY(x) __builtin_expect(!!(x), 0)
81
- #else
82
- /** Void because this platform does not support branch prediction hints. */
83
- #define PRISM_LIKELY(x) (x)
84
-
85
- /** Void because this platform does not support branch prediction hints. */
86
- #define PRISM_UNLIKELY(x) (x)
87
- #endif
88
-
89
72
  /**
90
73
  * The prism version and the serialization format.
91
74
  */
@@ -201,27 +184,22 @@ lex_mode_terminator(const uint8_t start) {
201
184
  }
202
185
 
203
186
  /**
204
- * Push a new lex state onto the stack. If we're still within the pre-allocated
205
- * space of the lex state stack, then we'll just use a new slot. Otherwise we'll
206
- * allocate a new pointer and use that.
187
+ * Push a new lex state onto the stack, reusing a popped lex mode if there is
188
+ * one and allocating a new one otherwise.
207
189
  */
208
190
  static void
209
191
  lex_mode_push(pm_parser_t *parser, pm_lex_mode_t lex_mode) {
210
- lex_mode.prev = parser->lex_modes.current;
211
- parser->lex_modes.index++;
192
+ pm_lex_mode_t *next = parser->lex_modes.free;
212
193
 
213
- if (parser->lex_modes.index > PM_LEX_STACK_SIZE - 1) {
214
- parser->lex_modes.current = (pm_lex_mode_t *) xmalloc(sizeof(pm_lex_mode_t));
215
- if (PRISM_UNLIKELY(parser->lex_modes.current == NULL)) {
216
- fprintf(stderr, "prism: out of memory; aborting\n");
217
- abort();
218
- }
219
-
220
- *parser->lex_modes.current = lex_mode;
194
+ if (next != NULL) {
195
+ parser->lex_modes.free = next->prev;
221
196
  } else {
222
- parser->lex_modes.stack[parser->lex_modes.index] = lex_mode;
223
- parser->lex_modes.current = &parser->lex_modes.stack[parser->lex_modes.index];
197
+ next = (pm_lex_mode_t *) pm_arena_alloc(&parser->scratch_arena, sizeof(pm_lex_mode_t), PRISM_ALIGNOF(pm_lex_mode_t));
224
198
  }
199
+
200
+ lex_mode.prev = parser->lex_modes.current;
201
+ *next = lex_mode;
202
+ parser->lex_modes.current = next;
225
203
  }
226
204
 
227
205
  /**
@@ -374,22 +352,20 @@ lex_mode_push_string_eof(pm_parser_t *parser) {
374
352
  }
375
353
 
376
354
  /**
377
- * Pop the current lex state off the stack. If we're within the pre-allocated
378
- * space of the lex state stack, then we'll just decrement the index. Otherwise
379
- * we'll free the current pointer and use the previous pointer.
355
+ * Pop the current lex state off the stack, keeping it for reuse by the next
356
+ * push. The base lex mode is never popped, so popping it instead resets it to
357
+ * the default mode.
380
358
  */
381
359
  static void
382
360
  lex_mode_pop(pm_parser_t *parser) {
383
- if (parser->lex_modes.index == 0) {
384
- parser->lex_modes.current->mode = PM_LEX_DEFAULT;
385
- } else if (parser->lex_modes.index < PM_LEX_STACK_SIZE) {
386
- parser->lex_modes.index--;
387
- parser->lex_modes.current = &parser->lex_modes.stack[parser->lex_modes.index];
361
+ pm_lex_mode_t *popped = parser->lex_modes.current;
362
+
363
+ if (popped == &parser->lex_modes.base) {
364
+ popped->mode = PM_LEX_DEFAULT;
388
365
  } else {
389
- parser->lex_modes.index--;
390
- pm_lex_mode_t *prev = parser->lex_modes.current->prev;
391
- xfree_sized(parser->lex_modes.current, sizeof(pm_lex_mode_t));
392
- parser->lex_modes.current = prev;
366
+ parser->lex_modes.current = popped->prev;
367
+ popped->prev = parser->lex_modes.free;
368
+ parser->lex_modes.free = popped;
393
369
  }
394
370
  }
395
371
 
@@ -700,17 +676,38 @@ pm_parser_err_heredoc_term(pm_parser_t *parser, const uint8_t *ident_start, size
700
676
  /* Scope-related functions */
701
677
  /******************************************************************************/
702
678
 
679
+ /**
680
+ * The largest locals table that a popped scope hands to the next pushed scope.
681
+ */
682
+ #define PM_LOCALS_REUSE_CAPACITY 16
683
+
703
684
  /**
704
685
  * Allocate and initialize a new scope. Push it onto the scope stack.
705
686
  */
706
- static bool
687
+ static void
707
688
  pm_parser_scope_push(pm_parser_t *parser, bool closed) {
708
- pm_scope_t *scope = (pm_scope_t *) xmalloc(sizeof(pm_scope_t));
709
- if (scope == NULL) return false;
689
+ pm_scope_t *scope = parser->free_scopes;
690
+ pm_locals_t locals = { 0 };
691
+
692
+ if (scope != NULL) {
693
+ parser->free_scopes = scope->previous;
694
+
695
+ /* Reuse the locals table of the popped scope when it is small, so that
696
+ * most scopes allocate no locals storage at all. Large tables are
697
+ * dropped instead, because every scope that reuses a table pays to
698
+ * clear it and to scan its full capacity when the locals are ordered. */
699
+ if (scope->locals.capacity > 0 && scope->locals.capacity <= PM_LOCALS_REUSE_CAPACITY) {
700
+ locals.capacity = scope->locals.capacity;
701
+ locals.locals = scope->locals.locals;
702
+ memset(locals.locals, 0, locals.capacity * sizeof(pm_local_t));
703
+ }
704
+ } else {
705
+ scope = (pm_scope_t *) pm_arena_alloc(&parser->scratch_arena, sizeof(pm_scope_t), PRISM_ALIGNOF(pm_scope_t));
706
+ }
710
707
 
711
708
  *scope = (pm_scope_t) {
712
709
  .previous = parser->current_scope,
713
- .locals = { 0 },
710
+ .locals = locals,
714
711
  .parameters = PM_SCOPE_PARAMETERS_NONE,
715
712
  .implicit_parameters = { 0 },
716
713
  .shareable_constant = parser->current_scope == NULL ? PM_SCOPE_SHAREABLE_CONSTANT_NONE : parser->current_scope->shareable_constant,
@@ -718,7 +715,6 @@ pm_parser_scope_push(pm_parser_t *parser, bool closed) {
718
715
  };
719
716
 
720
717
  parser->current_scope = scope;
721
- return true;
722
718
  }
723
719
 
724
720
  /**
@@ -876,24 +872,16 @@ pm_parser_scope_shareable_constant_set(pm_parser_t *parser, pm_shareable_constan
876
872
  */
877
873
  #define PM_LOCALS_HASH_THRESHOLD 5
878
874
 
879
- static void
880
- pm_locals_free(pm_locals_t *locals) {
881
- if (locals->capacity > 0) {
882
- xfree_sized(locals->locals, locals->capacity * sizeof(pm_local_t));
883
- }
884
- }
885
-
886
875
  /**
887
876
  * Resize the locals list to be twice its current size. If the next capacity is
888
877
  * above the threshold for switching to a hash, then we'll switch to a hash.
889
878
  */
890
879
  static void
891
- pm_locals_resize(pm_locals_t *locals) {
880
+ pm_locals_resize(pm_arena_t *arena, pm_locals_t *locals) {
892
881
  uint32_t next_capacity = locals->capacity == 0 ? 4 : (locals->capacity * 2);
893
882
  assert(next_capacity > locals->capacity);
894
883
 
895
- pm_local_t *next_locals = xcalloc(next_capacity, sizeof(pm_local_t));
896
- if (next_locals == NULL) abort();
884
+ pm_local_t *next_locals = (pm_local_t *) pm_arena_zalloc(arena, next_capacity * sizeof(pm_local_t), PRISM_ALIGNOF(pm_local_t));
897
885
 
898
886
  if (next_capacity < PM_LOCALS_HASH_THRESHOLD) {
899
887
  if (locals->size > 0) {
@@ -918,7 +906,6 @@ pm_locals_resize(pm_locals_t *locals) {
918
906
  }
919
907
  }
920
908
 
921
- pm_locals_free(locals);
922
909
  locals->locals = next_locals;
923
910
  locals->capacity = next_capacity;
924
911
  }
@@ -939,9 +926,9 @@ pm_locals_resize(pm_locals_t *locals) {
939
926
  * @return True if the local was added, and false if the local already exists.
940
927
  */
941
928
  static bool
942
- pm_locals_write(pm_locals_t *locals, pm_constant_id_t name, uint32_t start, uint32_t length, uint32_t reads) {
929
+ pm_locals_write(pm_arena_t *arena, pm_locals_t *locals, pm_constant_id_t name, uint32_t start, uint32_t length, uint32_t reads) {
943
930
  if (locals->size >= (locals->capacity / 4 * 3)) {
944
- pm_locals_resize(locals);
931
+ pm_locals_resize(arena, locals);
945
932
  }
946
933
 
947
934
  locals->bloom |= (1u << (name & 31));
@@ -2441,7 +2428,7 @@ pm_arguments_node_arguments_append(pm_arena_t *arena, pm_arguments_node_t *node,
2441
2428
  PM_NODE_LENGTH_SET_NODE(node, argument);
2442
2429
  }
2443
2430
 
2444
- pm_node_list_append(arena, &node->arguments, argument);
2431
+ pm_node_list_append_inline(arena, &node->arguments, argument);
2445
2432
 
2446
2433
  if (PM_NODE_TYPE_P(argument, PM_SPLAT_NODE)) {
2447
2434
  if (PM_NODE_FLAG_P(node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) {
@@ -2489,7 +2476,7 @@ pm_array_node_elements_append(pm_arena_t *arena, pm_array_node_t *node, pm_node_
2489
2476
  PM_NODE_START_SET_NODE(node, element);
2490
2477
  }
2491
2478
 
2492
- pm_node_list_append(arena, &node->elements, element);
2479
+ pm_node_list_append_inline(arena, &node->elements, element);
2493
2480
  PM_NODE_LENGTH_SET_NODE(node, element);
2494
2481
 
2495
2482
  // If the element is not a static literal, then the array is not a static
@@ -2542,9 +2529,9 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node
2542
2529
  node->rest = child;
2543
2530
  found_rest = true;
2544
2531
  } else if (found_rest) {
2545
- pm_node_list_append(parser->arena, &node->posts, child);
2532
+ pm_node_list_append_inline(parser->arena, &node->posts, child);
2546
2533
  } else {
2547
- pm_node_list_append(parser->arena, &node->requireds, child);
2534
+ pm_node_list_append_inline(parser->arena, &node->requireds, child);
2548
2535
  }
2549
2536
  }
2550
2537
 
@@ -2612,7 +2599,7 @@ pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *openin
2612
2599
 
2613
2600
  static PRISM_INLINE void
2614
2601
  pm_array_pattern_node_requireds_append(pm_arena_t *arena, pm_array_pattern_node_t *node, pm_node_t *inner) {
2615
- pm_node_list_append(arena, &node->requireds, inner);
2602
+ pm_node_list_append_inline(arena, &node->requireds, inner);
2616
2603
  }
2617
2604
 
2618
2605
  /**
@@ -2876,7 +2863,7 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name)
2876
2863
  */
2877
2864
  static void
2878
2865
  pm_block_parameters_node_append_local(pm_arena_t *arena, pm_block_parameters_node_t *node, const pm_block_local_variable_node_t *local) {
2879
- pm_node_list_append(arena, &node->locals, UP(local));
2866
+ pm_node_list_append_inline(arena, &node->locals, UP(local));
2880
2867
 
2881
2868
  if (PM_NODE_LENGTH(node) == 0) {
2882
2869
  PM_NODE_START_SET_NODE(node, local);
@@ -3529,7 +3516,7 @@ static void
3529
3516
  pm_case_node_condition_append(pm_arena_t *arena, pm_case_node_t *node, pm_node_t *condition) {
3530
3517
  assert(PM_NODE_TYPE_P(condition, PM_WHEN_NODE));
3531
3518
 
3532
- pm_node_list_append(arena, &node->conditions, condition);
3519
+ pm_node_list_append_inline(arena, &node->conditions, condition);
3533
3520
  PM_NODE_LENGTH_SET_NODE(node, condition);
3534
3521
  }
3535
3522
 
@@ -3576,7 +3563,7 @@ static void
3576
3563
  pm_case_match_node_condition_append(pm_arena_t *arena, pm_case_match_node_t *node, pm_node_t *condition) {
3577
3564
  assert(PM_NODE_TYPE_P(condition, PM_IN_NODE));
3578
3565
 
3579
- pm_node_list_append(arena, &node->conditions, condition);
3566
+ pm_node_list_append_inline(arena, &node->conditions, condition);
3580
3567
  PM_NODE_LENGTH_SET_NODE(node, condition);
3581
3568
  }
3582
3569
 
@@ -4116,7 +4103,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) {
4116
4103
  // much more efficient, as we could instead resize the node list to only point
4117
4104
  // to 1...-1.
4118
4105
  for (size_t index = 1; index < nodes->size - 1; index++) {
4119
- pm_node_list_append(parser->arena, &node->requireds, nodes->nodes[index]);
4106
+ pm_node_list_append_inline(parser->arena, &node->requireds, nodes->nodes[index]);
4120
4107
  }
4121
4108
 
4122
4109
  return node;
@@ -4613,7 +4600,7 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) {
4613
4600
  */
4614
4601
  static PRISM_INLINE void
4615
4602
  pm_hash_node_elements_append(pm_arena_t *arena, pm_hash_node_t *hash, pm_node_t *element) {
4616
- pm_node_list_append(arena, &hash->elements, element);
4603
+ pm_node_list_append_inline(arena, &hash->elements, element);
4617
4604
 
4618
4605
  bool static_literal = PM_NODE_TYPE_P(element, PM_ASSOC_NODE);
4619
4606
  if (static_literal) {
@@ -5038,7 +5025,7 @@ pm_interpolated_node_append(pm_arena_t *arena, pm_node_t *node, pm_node_list_t *
5038
5025
  break;
5039
5026
  }
5040
5027
 
5041
- pm_node_list_append(arena, parts, part);
5028
+ pm_node_list_append_inline(arena, parts, part);
5042
5029
  }
5043
5030
 
5044
5031
  /**
@@ -5180,7 +5167,7 @@ pm_interpolated_string_node_append(pm_parser_t *parser, pm_interpolated_string_n
5180
5167
  // These will only happen in error cases. But we want to handle it
5181
5168
  // here so that we don't fail the assertion.
5182
5169
  CLEAR_FLAGS(node);
5183
- pm_node_list_append(arena, &node->parts, UP(pm_error_recovery_node_create_unexpected(parser, part)));
5170
+ pm_node_list_append_inline(arena, &node->parts, UP(pm_error_recovery_node_create_unexpected(parser, part)));
5184
5171
  return;
5185
5172
  case PM_ERROR_RECOVERY_NODE:
5186
5173
  CLEAR_FLAGS(node);
@@ -5190,7 +5177,7 @@ pm_interpolated_string_node_append(pm_parser_t *parser, pm_interpolated_string_n
5190
5177
  break;
5191
5178
  }
5192
5179
 
5193
- pm_node_list_append(arena, &node->parts, part);
5180
+ pm_node_list_append_inline(arena, &node->parts, part);
5194
5181
 
5195
5182
  #undef CLEAR_FLAGS
5196
5183
  #undef MUTABLE_FLAGS
@@ -5370,7 +5357,7 @@ pm_keyword_hash_node_elements_append(pm_arena_t *arena, pm_keyword_hash_node_t *
5370
5357
  pm_node_flag_unset(UP(hash), PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS);
5371
5358
  }
5372
5359
 
5373
- pm_node_list_append(arena, &hash->elements, element);
5360
+ pm_node_list_append_inline(arena, &hash->elements, element);
5374
5361
  if (PM_NODE_LENGTH(hash) == 0) {
5375
5362
  PM_NODE_START_SET_NODE(hash, element);
5376
5363
  }
@@ -5718,19 +5705,19 @@ pm_multi_target_node_targets_append(pm_parser_t *parser, pm_multi_target_node_t
5718
5705
  node->rest = target;
5719
5706
  } else {
5720
5707
  pm_parser_err_node(parser, target, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS);
5721
- pm_node_list_append(parser->arena, &node->rights, target);
5708
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5722
5709
  }
5723
5710
  } else if (PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) {
5724
5711
  if (node->rest == NULL) {
5725
5712
  node->rest = target;
5726
5713
  } else {
5727
5714
  PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, &parser->current, PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST);
5728
- pm_node_list_append(parser->arena, &node->rights, target);
5715
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5729
5716
  }
5730
5717
  } else if (node->rest == NULL) {
5731
- pm_node_list_append(parser->arena, &node->lefts, target);
5718
+ pm_node_list_append_inline(parser->arena, &node->lefts, target);
5732
5719
  } else {
5733
- pm_node_list_append(parser->arena, &node->rights, target);
5720
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5734
5721
  }
5735
5722
 
5736
5723
  if (PM_NODE_LENGTH(node) == 0 || (PM_NODE_START(node) > PM_NODE_START(target))) {
@@ -6005,7 +5992,7 @@ pm_parameters_node_location_set(pm_parameters_node_t *params, pm_node_t *param)
6005
5992
  static void
6006
5993
  pm_parameters_node_requireds_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6007
5994
  pm_parameters_node_location_set(params, param);
6008
- pm_node_list_append(arena, &params->requireds, param);
5995
+ pm_node_list_append_inline(arena, &params->requireds, param);
6009
5996
  }
6010
5997
 
6011
5998
  /**
@@ -6014,7 +6001,7 @@ pm_parameters_node_requireds_append(pm_arena_t *arena, pm_parameters_node_t *par
6014
6001
  static void
6015
6002
  pm_parameters_node_optionals_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_optional_parameter_node_t *param) {
6016
6003
  pm_parameters_node_location_set(params, UP(param));
6017
- pm_node_list_append(arena, &params->optionals, UP(param));
6004
+ pm_node_list_append_inline(arena, &params->optionals, UP(param));
6018
6005
  }
6019
6006
 
6020
6007
  /**
@@ -6023,7 +6010,7 @@ pm_parameters_node_optionals_append(pm_arena_t *arena, pm_parameters_node_t *par
6023
6010
  static void
6024
6011
  pm_parameters_node_posts_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6025
6012
  pm_parameters_node_location_set(params, param);
6026
- pm_node_list_append(arena, &params->posts, param);
6013
+ pm_node_list_append_inline(arena, &params->posts, param);
6027
6014
  }
6028
6015
 
6029
6016
  /**
@@ -6041,7 +6028,7 @@ pm_parameters_node_rest_set(pm_parameters_node_t *params, pm_node_t *param) {
6041
6028
  static void
6042
6029
  pm_parameters_node_keywords_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6043
6030
  pm_parameters_node_location_set(params, param);
6044
- pm_node_list_append(arena, &params->keywords, param);
6031
+ pm_node_list_append_inline(arena, &params->keywords, param);
6045
6032
  }
6046
6033
 
6047
6034
  /**
@@ -6329,7 +6316,7 @@ pm_rescue_node_subsequent_set(pm_rescue_node_t *node, pm_rescue_node_t *subseque
6329
6316
  */
6330
6317
  static void
6331
6318
  pm_rescue_node_exceptions_append(pm_arena_t *arena, pm_rescue_node_t *node, pm_node_t *exception) {
6332
- pm_node_list_append(arena, &node->exceptions, exception);
6319
+ pm_node_list_append_inline(arena, &node->exceptions, exception);
6333
6320
  PM_NODE_LENGTH_SET_NODE(node, exception);
6334
6321
  }
6335
6322
 
@@ -6562,7 +6549,7 @@ pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node,
6562
6549
  }
6563
6550
  }
6564
6551
 
6565
- pm_node_list_append(parser->arena, &node->body, statement);
6552
+ pm_node_list_append_inline(parser->arena, &node->body, statement);
6566
6553
  if (newline) pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE);
6567
6554
  }
6568
6555
 
@@ -6809,7 +6796,7 @@ pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) {
6809
6796
  ((pm_string_t) { 0 })
6810
6797
  );
6811
6798
 
6812
- pm_string_constant_init(&node->unescaped, content, strlen(content));
6799
+ pm_string_init(&node->unescaped, (const uint8_t *) content, strlen(content));
6813
6800
  return node;
6814
6801
  }
6815
6802
 
@@ -6952,7 +6939,7 @@ pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) {
6952
6939
  static void
6953
6940
  pm_undef_node_append(pm_arena_t *arena, pm_undef_node_t *node, pm_node_t *name) {
6954
6941
  PM_NODE_LENGTH_SET_NODE(node, name);
6955
- pm_node_list_append(arena, &node->names, name);
6942
+ pm_node_list_append_inline(arena, &node->names, name);
6956
6943
  }
6957
6944
 
6958
6945
  /**
@@ -7094,7 +7081,7 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) {
7094
7081
  static void
7095
7082
  pm_when_node_conditions_append(pm_arena_t *arena, pm_when_node_t *node, pm_node_t *condition) {
7096
7083
  PM_NODE_LENGTH_SET_NODE(node, condition);
7097
- pm_node_list_append(arena, &node->conditions, condition);
7084
+ pm_node_list_append_inline(arena, &node->conditions, condition);
7098
7085
  }
7099
7086
 
7100
7087
  /**
@@ -7268,7 +7255,7 @@ pm_parser_local_depth(pm_parser_t *parser, pm_token_t *token) {
7268
7255
  */
7269
7256
  static PRISM_INLINE void
7270
7257
  pm_parser_local_add(pm_parser_t *parser, pm_constant_id_t constant_id, const uint8_t *start, const uint8_t *end, uint32_t reads) {
7271
- pm_locals_write(&parser->current_scope->locals, constant_id, U32(start - parser->start), U32(end - start), reads);
7258
+ pm_locals_write(&parser->scratch_arena, &parser->current_scope->locals, constant_id, U32(start - parser->start), U32(end - start), reads);
7272
7259
  }
7273
7260
 
7274
7261
  /**
@@ -7351,8 +7338,8 @@ static void
7351
7338
  pm_parser_scope_pop(pm_parser_t *parser) {
7352
7339
  pm_scope_t *scope = parser->current_scope;
7353
7340
  parser->current_scope = scope->previous;
7354
- pm_locals_free(&scope->locals);
7355
- xfree_sized(scope, sizeof(pm_scope_t));
7341
+ scope->previous = parser->free_scopes;
7342
+ parser->free_scopes = scope;
7356
7343
  }
7357
7344
 
7358
7345
  /******************************************************************************/
@@ -7420,8 +7407,7 @@ pm_do_loop_stack_p(pm_parser_t *parser) {
7420
7407
  * lexed: the do-loop stack (so a `do` inside is not read as a `while`/`until`
7421
7408
  * loop body) and the accepts-block stack (so a `do` inside is accepted as a
7422
7409
  * block rather than binding to an enclosing command). The matching close pops
7423
- * both. This mirrors parse.y's paired `COND_PUSH(0); CMDARG_PUSH(0)`. Keep the
7424
- * following in mind before touching either stack:
7410
+ * both. Keep the following in mind before touching either stack:
7425
7411
  *
7426
7412
  * - The LEXER owns the frame for delimiter-bound stuff. Every token that opens
7427
7413
  * a matched pair calls this on the open and `pm_enclosure_frame_pop` on the
@@ -7429,14 +7415,20 @@ pm_do_loop_stack_p(pm_parser_t *parser) {
7429
7415
  * `parser_lex` and the `#{` case in `lex_interpolation`; the pop sites are
7430
7416
  * the matching `)`, `]`, `}` (including `}` as `EMBEXPR_END`).
7431
7417
  * - The PARSER owns the frame for keyword-bounded blocks, where there is no
7432
- * delimiter token to hang it on: the `do`/`end` forms in `parse_block` and
7433
- * the lambda push `accepts_block` directly (and pop before consuming `end`).
7418
+ * delimiter token to hang it on. The `-> do end` lambda body pushes the full
7419
+ * frame through this helper: the lexer produces the lambda's `do` before
7420
+ * consulting the do-loop stack, so the body can open inside a loop condition
7421
+ * and must reset it. The `do`/`end` forms in `parse_block` push only
7422
+ * `accepts_block`: the lexer produces a block's `do` only when the do-loop
7423
+ * stack is already false, so another false frame would change nothing. Both
7424
+ * pop before consuming `end`.
7434
7425
  * - The `parse_arguments_list` command-args branch juggles ONLY `accepts_block`
7435
7426
  * (never through this helper), because the lexer has pushed a delimiter frame
7436
7427
  * one token early and the command-args frame must be threaded beneath it. Its
7437
7428
  * match lookahead sets must stay in sync with the lexer push sites above.
7438
- * This matches parse.y, whose `command_args` rule juggles CMDARG but not
7439
- * COND.
7429
+ * Command arguments change which call a `do` block binds to, not whether a
7430
+ * `do` closes a loop condition (`while foo bar do ... end` still loops), so
7431
+ * the do-loop stack is left alone.
7440
7432
  */
7441
7433
  static PRISM_INLINE void
7442
7434
  pm_enclosure_frame_push(pm_parser_t *parser) {
@@ -7676,6 +7668,27 @@ parser_lex_magic_comment_emacs_marker(pm_parser_t *parser, const uint8_t *cursor
7676
7668
  return NULL;
7677
7669
  }
7678
7670
 
7671
+ /**
7672
+ * Returns true if the given magic comment key matches the given name, which is
7673
+ * written in lowercase with underscores and is the same length as the key.
7674
+ * Keys match case-insensitively, and a dash in the key matches an underscore in
7675
+ * the name. Every byte of a name is ASCII, so a byte of a multibyte character
7676
+ * in the key never matches one.
7677
+ */
7678
+ static bool
7679
+ parser_magic_comment_key_p(const uint8_t *key, size_t key_length, const char *name) {
7680
+ assert(name[key_length] == '\0');
7681
+
7682
+ for (size_t index = 0; index < key_length; index++) {
7683
+ uint8_t byte = key[index];
7684
+ if (byte == '-') byte = '_';
7685
+ if ('A' <= byte && byte <= 'Z') byte |= 0x20;
7686
+ if (byte != (uint8_t) name[index]) return false;
7687
+ }
7688
+
7689
+ return true;
7690
+ }
7691
+
7679
7692
  /**
7680
7693
  * Parse the current token on the parser to see if it's a magic comment and
7681
7694
  * potentially perform some action based on that. A regular expression that this
@@ -7767,73 +7780,34 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
7767
7780
  if (cursor != end) return false;
7768
7781
  }
7769
7782
 
7770
- // Here, we need to do some processing on the key to swap out dashes for
7771
- // underscores. We only need to do this if there _is_ a dash in the key.
7772
- pm_string_t key;
7783
+ /* Finally, we can start checking the key against the list of known
7784
+ * magic comment keys, and potentially change state based on that. */
7773
7785
  const size_t key_length = (size_t) (key_end - key_start);
7774
- const uint8_t *dash = pm_memchr(key_start, '-', key_length, parser->encoding_changed, parser->encoding);
7775
-
7776
- if (dash == NULL) {
7777
- pm_string_shared_init(&key, key_start, key_end);
7778
- } else {
7779
- uint8_t *buffer = xmalloc(key_length);
7780
- if (buffer == NULL) break;
7781
-
7782
- memcpy(buffer, key_start, key_length);
7783
- buffer[dash - key_start] = '_';
7784
-
7785
- while ((dash = pm_memchr(dash + 1, '-', (size_t) (key_end - dash - 1), parser->encoding_changed, parser->encoding)) != NULL) {
7786
- buffer[dash - key_start] = '_';
7787
- }
7788
-
7789
- pm_string_owned_init(&key, buffer, key_length);
7790
- }
7791
-
7792
- // Finally, we can start checking the key against the list of known
7793
- // magic comment keys, and potentially change state based on that.
7794
- const uint8_t *key_source = pm_string_source(&key);
7795
7786
  uint32_t value_length = (uint32_t) (value_end - value_start);
7796
7787
 
7797
- // We only want to attempt to compare against encoding comments if it's
7798
- // the first line in the file (or the second in the case of a shebang).
7799
- if (parser->current.start == parser->encoding_comment_start && !parser->encoding_locked) {
7800
- if (
7801
- (key_length == 8 && pm_strncasecmp(key_source, (const uint8_t *) "encoding", 8) == 0) ||
7802
- (key_length == 6 && pm_strncasecmp(key_source, (const uint8_t *) "coding", 6) == 0)
7803
- ) {
7804
- result = parser_lex_magic_comment_encoding_value(parser, value_start, value_end);
7805
- }
7806
- }
7807
-
7808
- if (key_length == 11) {
7809
- if (pm_strncasecmp(key_source, (const uint8_t *) "warn_indent", 11) == 0) {
7810
- switch (parser_lex_magic_comment_boolean_value(value_start, value_length)) {
7811
- case PM_MAGIC_COMMENT_BOOLEAN_VALUE_INVALID:
7812
- PM_PARSER_WARN_TOKEN_FORMAT(
7813
- parser,
7814
- &parser->current,
7815
- PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7816
- (int) key_length,
7817
- (const char *) key_source,
7818
- (int) value_length,
7819
- (const char *) value_start
7820
- );
7821
- break;
7822
- case PM_MAGIC_COMMENT_BOOLEAN_VALUE_FALSE:
7823
- parser->warn_mismatched_indentation = false;
7824
- break;
7825
- case PM_MAGIC_COMMENT_BOOLEAN_VALUE_TRUE:
7826
- parser->warn_mismatched_indentation = true;
7827
- break;
7788
+ switch (key_length) {
7789
+ case 6:
7790
+ /* We only want to attempt to compare against encoding comments
7791
+ * if it is the first line in the file (or the second in the
7792
+ * case of a shebang). */
7793
+ if (parser->current.start == parser->encoding_comment_start && !parser->encoding_locked) {
7794
+ if (parser_magic_comment_key_p(key_start, key_length, "coding")) {
7795
+ result = parser_lex_magic_comment_encoding_value(parser, value_start, value_end);
7796
+ }
7828
7797
  }
7829
- }
7830
- } else if (key_length == 21) {
7831
- if (pm_strncasecmp(key_source, (const uint8_t *) "frozen_string_literal", 21) == 0) {
7832
- // We only want to handle frozen string literal comments if it's
7833
- // before any semantic tokens have been seen.
7834
- if (semantic_token_seen) {
7835
- pm_parser_warn_token(parser, &parser->current, PM_WARN_IGNORED_FROZEN_STRING_LITERAL);
7836
- } else {
7798
+ break;
7799
+ case 8:
7800
+ /* We only want to attempt to compare against encoding comments
7801
+ * if it is the first line in the file (or the second in the
7802
+ * case of a shebang). */
7803
+ if (parser->current.start == parser->encoding_comment_start && !parser->encoding_locked) {
7804
+ if (parser_magic_comment_key_p(key_start, key_length, "encoding")) {
7805
+ result = parser_lex_magic_comment_encoding_value(parser, value_start, value_end);
7806
+ }
7807
+ }
7808
+ break;
7809
+ case 11:
7810
+ if (parser_magic_comment_key_p(key_start, key_length, "warn_indent")) {
7837
7811
  switch (parser_lex_magic_comment_boolean_value(value_start, value_length)) {
7838
7812
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_INVALID:
7839
7813
  PM_PARSER_WARN_TOKEN_FORMAT(
@@ -7841,53 +7815,79 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
7841
7815
  &parser->current,
7842
7816
  PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7843
7817
  (int) key_length,
7844
- (const char *) key_source,
7818
+ "warn_indent",
7845
7819
  (int) value_length,
7846
7820
  (const char *) value_start
7847
7821
  );
7848
7822
  break;
7849
7823
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_FALSE:
7850
- parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED;
7824
+ parser->warn_mismatched_indentation = false;
7851
7825
  break;
7852
7826
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_TRUE:
7853
- parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED;
7827
+ parser->warn_mismatched_indentation = true;
7854
7828
  break;
7855
7829
  }
7856
7830
  }
7857
- }
7858
- } else if (key_length == 24) {
7859
- if (pm_strncasecmp(key_source, (const uint8_t *) "shareable_constant_value", 24) == 0) {
7860
- const uint8_t *cursor = parser->current.start;
7861
- while ((cursor > parser->start) && ((cursor[-1] == ' ') || (cursor[-1] == '\t'))) cursor--;
7862
-
7863
- if (!((cursor == parser->start) || (cursor[-1] == '\n'))) {
7864
- pm_parser_warn_token(parser, &parser->current, PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE);
7865
- } else if (value_length == 4 && pm_strncasecmp(value_start, (const uint8_t *) "none", 4) == 0) {
7866
- pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_NONE);
7867
- } else if (value_length == 7 && pm_strncasecmp(value_start, (const uint8_t *) "literal", 7) == 0) {
7868
- pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_LITERAL);
7869
- } else if (value_length == 23 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_everything", 23) == 0) {
7870
- pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING);
7871
- } else if (value_length == 17 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_copy", 17) == 0) {
7872
- pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY);
7873
- } else {
7874
- PM_PARSER_WARN_TOKEN_FORMAT(
7875
- parser,
7876
- &parser->current,
7877
- PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7878
- (int) key_length,
7879
- (const char *) key_source,
7880
- (int) value_length,
7881
- (const char *) value_start
7882
- );
7831
+ break;
7832
+ case 21:
7833
+ if (parser_magic_comment_key_p(key_start, key_length, "frozen_string_literal")) {
7834
+ /* We only want to handle frozen string literal comments if
7835
+ * it is before any semantic tokens have been seen. */
7836
+ if (semantic_token_seen) {
7837
+ pm_parser_warn_token(parser, &parser->current, PM_WARN_IGNORED_FROZEN_STRING_LITERAL);
7838
+ } else {
7839
+ switch (parser_lex_magic_comment_boolean_value(value_start, value_length)) {
7840
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_INVALID:
7841
+ PM_PARSER_WARN_TOKEN_FORMAT(
7842
+ parser,
7843
+ &parser->current,
7844
+ PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7845
+ (int) key_length,
7846
+ "frozen_string_literal",
7847
+ (int) value_length,
7848
+ (const char *) value_start
7849
+ );
7850
+ break;
7851
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_FALSE:
7852
+ parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED;
7853
+ break;
7854
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_TRUE:
7855
+ parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED;
7856
+ break;
7857
+ }
7858
+ }
7883
7859
  }
7884
- }
7860
+ break;
7861
+ case 24:
7862
+ if (parser_magic_comment_key_p(key_start, key_length, "shareable_constant_value")) {
7863
+ const uint8_t *cursor = parser->current.start;
7864
+ while ((cursor > parser->start) && ((cursor[-1] == ' ') || (cursor[-1] == '\t'))) cursor--;
7865
+
7866
+ if (!((cursor == parser->start) || (cursor[-1] == '\n'))) {
7867
+ pm_parser_warn_token(parser, &parser->current, PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE);
7868
+ } else if (value_length == 4 && pm_strncasecmp(value_start, (const uint8_t *) "none", 4) == 0) {
7869
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_NONE);
7870
+ } else if (value_length == 7 && pm_strncasecmp(value_start, (const uint8_t *) "literal", 7) == 0) {
7871
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_LITERAL);
7872
+ } else if (value_length == 23 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_everything", 23) == 0) {
7873
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING);
7874
+ } else if (value_length == 17 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_copy", 17) == 0) {
7875
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY);
7876
+ } else {
7877
+ PM_PARSER_WARN_TOKEN_FORMAT(
7878
+ parser,
7879
+ &parser->current,
7880
+ PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7881
+ (int) key_length,
7882
+ "shareable_constant_value",
7883
+ (int) value_length,
7884
+ (const char *) value_start
7885
+ );
7886
+ }
7887
+ }
7888
+ break;
7885
7889
  }
7886
7890
 
7887
- // When we're done, we want to free the string in case we had to
7888
- // allocate memory for it.
7889
- pm_string_cleanup(&key);
7890
-
7891
7891
  // Allocate a new magic comment node to append to the parser's list.
7892
7892
  pm_magic_comment_t *magic_comment = (pm_magic_comment_t *) pm_arena_alloc(&parser->metadata_arena, sizeof(pm_magic_comment_t), PRISM_ALIGNOF(pm_magic_comment_t));
7893
7893
  magic_comment->node.next = NULL;
@@ -7986,8 +7986,12 @@ PM_STATIC_ASSERT(__LINE__, PM_CONTEXT_MAXIMUM <= 64, "Expected every context to
7986
7986
 
7987
7987
  static bool
7988
7988
  context_push(pm_parser_t *parser, pm_context_t context) {
7989
- pm_context_node_t *context_node = (pm_context_node_t *) xmalloc(sizeof(pm_context_node_t));
7990
- if (context_node == NULL) return false;
7989
+ pm_context_node_t *context_node = parser->free_contexts;
7990
+ if (context_node != NULL) {
7991
+ parser->free_contexts = context_node->prev;
7992
+ } else {
7993
+ context_node = (pm_context_node_t *) pm_arena_alloc(&parser->scratch_arena, sizeof(pm_context_node_t), PRISM_ALIGNOF(pm_context_node_t));
7994
+ }
7991
7995
 
7992
7996
  pm_context_node_t *previous = parser->current_context;
7993
7997
  *context_node = (pm_context_node_t) {
@@ -8002,9 +8006,10 @@ context_push(pm_parser_t *parser, pm_context_t context) {
8002
8006
 
8003
8007
  static void
8004
8008
  context_pop(pm_parser_t *parser) {
8005
- pm_context_node_t *prev = parser->current_context->prev;
8006
- xfree_sized(parser->current_context, sizeof(pm_context_node_t));
8007
- parser->current_context = prev;
8009
+ pm_context_node_t *context_node = parser->current_context;
8010
+ parser->current_context = context_node->prev;
8011
+ context_node->prev = parser->free_contexts;
8012
+ parser->free_contexts = context_node;
8008
8013
  }
8009
8014
 
8010
8015
  static bool
@@ -8508,82 +8513,186 @@ lex_global_variable(pm_parser_t *parser) {
8508
8513
  }
8509
8514
 
8510
8515
  /**
8511
- * This function checks if the current token matches a keyword. If it does, it
8512
- * returns the token type. Otherwise, it returns PM_TOKEN_EOF. The arguments are as follows:
8516
+ * A Ruby keyword, along with the lex state that it transitions to and the
8517
+ * token types that it produces.
8518
+ */
8519
+ typedef struct {
8520
+ /* The keyword itself. */
8521
+ const char *name;
8522
+
8523
+ /* The keyword's first, middle, and last bytes and its length, packed by
8524
+ * PM_KEYWORD_PACK. */
8525
+ uint32_t packed;
8526
+
8527
+ /* The state to transition to when the keyword is lexed. */
8528
+ pm_lex_state_t state;
8529
+
8530
+ /* The token type of the keyword. */
8531
+ pm_token_type_t type;
8532
+
8533
+ /* The token type of the keyword in modifier position, or PM_TOKEN_EOF if
8534
+ * the keyword has no modifier form. */
8535
+ pm_token_type_t modifier_type;
8536
+ } pm_keyword_t;
8537
+
8538
+ /**
8539
+ * The length of the longest keyword, __ENCODING__.
8540
+ */
8541
+ #define PM_KEYWORD_LENGTH_MAXIMUM 12
8542
+
8543
+ /**
8544
+ * Pack the first, middle (at index length / 2), and last bytes of an identifier
8545
+ * together with its length. No two keywords pack to the same value.
8546
+ */
8547
+ #define PM_KEYWORD_PACK(first, middle, last, length) \
8548
+ ((uint32_t) (first) | ((uint32_t) (middle) << 8) | ((uint32_t) (last) << 16) | ((uint32_t) (length) << 24))
8549
+
8550
+ /**
8551
+ * The number of bits in a slot index of pm_keywords, which has
8552
+ * 1 << PM_KEYWORD_SLOT_BITS slots.
8553
+ */
8554
+ #define PM_KEYWORD_SLOT_BITS 6
8555
+
8556
+ /**
8557
+ * Return the slot in pm_keywords for a packed identifier. This is
8558
+ * multiplicative hashing: the product is taken modulo 2^32, and the slot is its
8559
+ * top PM_KEYWORD_SLOT_BITS bits. The top bits are used because the low bits of
8560
+ * a product depend only on the low bits of its operands (here, the low bits of
8561
+ * the first byte), while the top bits depend on all of them.
8513
8562
  *
8514
- * * `parser` - the parser object
8515
- * * `current_start` - pointer to the start of the current token
8516
- * * `value` - the literal string that we're checking for
8517
- * * `vlen` - the length of the token
8518
- * * `state` - the state that we should transition to if the token matches
8519
- * * `type` - the expected token type
8520
- * * `modifier_type` - the expected modifier token type
8521
- */
8522
- static PRISM_INLINE pm_token_type_t
8523
- lex_keyword(pm_parser_t *parser, const uint8_t *current_start, const char *value, size_t vlen, pm_lex_state_t state, pm_token_type_t type, pm_token_type_t modifier_type) {
8524
- if (memcmp(current_start, value, vlen) == 0) {
8525
- pm_lex_state_t last_state = parser->lex_state;
8526
-
8527
- if (parser->lex_state & PM_LEX_STATE_FNAME) {
8528
- lex_state_set(parser, PM_LEX_STATE_ENDFN);
8529
- } else {
8530
- lex_state_set(parser, state);
8531
- if (state == PM_LEX_STATE_BEG) {
8532
- parser->command_start = true;
8533
- }
8563
+ * The multiplier comes from `bin/prism keywords`, which searches for one that
8564
+ * gives every keyword its own slot, making this a perfect hash over the
8565
+ * keywords. Rerun it whenever the set of keywords changes. The table below is
8566
+ * indexed with this same macro, so if two keywords ever shared a slot, the
8567
+ * compiler would reject the duplicate initializer.
8568
+ */
8569
+ #define PM_KEYWORD_SLOT(packed) ((uint32_t) ((uint32_t) (packed) * 0x75f796ddU) >> (32 - PM_KEYWORD_SLOT_BITS))
8570
+
8571
+ /**
8572
+ * Define the entry in pm_keywords for a keyword, given its first, middle, and
8573
+ * last characters.
8574
+ */
8575
+ #define PM_KEYWORD(name, first, middle, last, state, type, modifier_type) \
8576
+ [PM_KEYWORD_SLOT(PM_KEYWORD_PACK(first, middle, last, sizeof(name) - 1))] = { name, PM_KEYWORD_PACK(first, middle, last, sizeof(name) - 1), state, type, modifier_type }
8577
+
8578
+ /**
8579
+ * The keywords, indexed by PM_KEYWORD_SLOT. Unused slots are zeroed, and a
8580
+ * packed value of zero never matches an identifier because its length is at
8581
+ * least one.
8582
+ */
8583
+ static const pm_keyword_t pm_keywords[1 << PM_KEYWORD_SLOT_BITS] = {
8584
+ PM_KEYWORD("do", 'd', 'o', 'o', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_DO, PM_TOKEN_EOF),
8585
+ PM_KEYWORD("if", 'i', 'f', 'f', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IF, PM_TOKEN_KEYWORD_IF_MODIFIER),
8586
+ PM_KEYWORD("in", 'i', 'n', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IN, PM_TOKEN_EOF),
8587
+ PM_KEYWORD("or", 'o', 'r', 'r', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_OR, PM_TOKEN_EOF),
8588
+ PM_KEYWORD("and", 'a', 'n', 'd', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_AND, PM_TOKEN_EOF),
8589
+ PM_KEYWORD("def", 'd', 'e', 'f', PM_LEX_STATE_FNAME, PM_TOKEN_KEYWORD_DEF, PM_TOKEN_EOF),
8590
+ PM_KEYWORD("end", 'e', 'n', 'd', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END, PM_TOKEN_EOF),
8591
+ PM_KEYWORD("END", 'E', 'N', 'D', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END_UPCASE, PM_TOKEN_EOF),
8592
+ PM_KEYWORD("for", 'f', 'o', 'r', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_FOR, PM_TOKEN_EOF),
8593
+ PM_KEYWORD("nil", 'n', 'i', 'l', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_NIL, PM_TOKEN_EOF),
8594
+ PM_KEYWORD("not", 'n', 'o', 't', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_NOT, PM_TOKEN_EOF),
8595
+ PM_KEYWORD("case", 'c', 's', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_CASE, PM_TOKEN_EOF),
8596
+ PM_KEYWORD("else", 'e', 's', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_EOF),
8597
+ PM_KEYWORD("next", 'n', 'x', 't', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_NEXT, PM_TOKEN_EOF),
8598
+ PM_KEYWORD("redo", 'r', 'd', 'o', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_REDO, PM_TOKEN_EOF),
8599
+ PM_KEYWORD("self", 's', 'l', 'f', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_SELF, PM_TOKEN_EOF),
8600
+ PM_KEYWORD("then", 't', 'e', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_EOF),
8601
+ PM_KEYWORD("true", 't', 'u', 'e', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_TRUE, PM_TOKEN_EOF),
8602
+ PM_KEYWORD("when", 'w', 'e', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHEN, PM_TOKEN_EOF),
8603
+ PM_KEYWORD("alias", 'a', 'i', 's', PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_ALIAS, PM_TOKEN_EOF),
8604
+ PM_KEYWORD("begin", 'b', 'g', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_BEGIN, PM_TOKEN_EOF),
8605
+ PM_KEYWORD("BEGIN", 'B', 'G', 'N', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_BEGIN_UPCASE, PM_TOKEN_EOF),
8606
+ PM_KEYWORD("break", 'b', 'e', 'k', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_BREAK, PM_TOKEN_EOF),
8607
+ PM_KEYWORD("class", 'c', 'a', 's', PM_LEX_STATE_CLASS, PM_TOKEN_KEYWORD_CLASS, PM_TOKEN_EOF),
8608
+ PM_KEYWORD("elsif", 'e', 's', 'f', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSIF, PM_TOKEN_EOF),
8609
+ PM_KEYWORD("false", 'f', 'l', 'e', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_FALSE, PM_TOKEN_EOF),
8610
+ PM_KEYWORD("retry", 'r', 't', 'y', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_RETRY, PM_TOKEN_EOF),
8611
+ PM_KEYWORD("super", 's', 'p', 'r', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_SUPER, PM_TOKEN_EOF),
8612
+ PM_KEYWORD("undef", 'u', 'd', 'f', PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_UNDEF, PM_TOKEN_EOF),
8613
+ PM_KEYWORD("until", 'u', 't', 'l', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNTIL, PM_TOKEN_KEYWORD_UNTIL_MODIFIER),
8614
+ PM_KEYWORD("while", 'w', 'i', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHILE, PM_TOKEN_KEYWORD_WHILE_MODIFIER),
8615
+ PM_KEYWORD("yield", 'y', 'e', 'd', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_YIELD, PM_TOKEN_EOF),
8616
+ PM_KEYWORD("ensure", 'e', 'u', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_EOF),
8617
+ PM_KEYWORD("module", 'm', 'u', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_MODULE, PM_TOKEN_EOF),
8618
+ PM_KEYWORD("rescue", 'r', 'c', 'e', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_RESCUE_MODIFIER),
8619
+ PM_KEYWORD("return", 'r', 'u', 'n', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RETURN, PM_TOKEN_EOF),
8620
+ PM_KEYWORD("unless", 'u', 'e', 's', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNLESS, PM_TOKEN_KEYWORD_UNLESS_MODIFIER),
8621
+ PM_KEYWORD("defined?", 'd', 'n', '?', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_DEFINED, PM_TOKEN_EOF),
8622
+ PM_KEYWORD("__LINE__", '_', 'N', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___LINE__, PM_TOKEN_EOF),
8623
+ PM_KEYWORD("__FILE__", '_', 'L', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___FILE__, PM_TOKEN_EOF),
8624
+ PM_KEYWORD("__ENCODING__", '_', 'D', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___ENCODING__, PM_TOKEN_EOF)
8625
+ };
8534
8626
 
8535
- if ((modifier_type != PM_TOKEN_EOF) && !(last_state & (PM_LEX_STATE_BEG | PM_LEX_STATE_LABELED | PM_LEX_STATE_CLASS))) {
8536
- lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL);
8537
- return modifier_type;
8538
- }
8539
- }
8627
+ #undef PM_KEYWORD
8540
8628
 
8541
- return type;
8542
- }
8629
+ /**
8630
+ * Return the keyword that the identifier corresponds to or NULL if it is not in
8631
+ * the table.
8632
+ */
8633
+ static PRISM_INLINE const pm_keyword_t *
8634
+ pm_keyword_find(const uint8_t *start, const uint8_t *end) {
8635
+ ptrdiff_t width = end - start;
8636
+ if (width < 2 || width > PM_KEYWORD_LENGTH_MAXIMUM) return NULL;
8543
8637
 
8544
- return PM_TOKEN_EOF;
8638
+ size_t uwidth = (size_t) width;
8639
+ uint32_t packed = PM_KEYWORD_PACK(start[0], start[uwidth >> 1], start[uwidth - 1], uwidth);
8640
+
8641
+ const pm_keyword_t *keyword = &pm_keywords[PM_KEYWORD_SLOT(packed)];
8642
+ return (keyword->packed == packed && memcmp(start + 1, keyword->name + 1, uwidth - 2) == 0) ? keyword : NULL;
8643
+ }
8644
+
8645
+ /**
8646
+ * Returns true if the bytes between start and end spell a keyword, using the
8647
+ * same lookup that the lexer does.
8648
+ */
8649
+ bool
8650
+ pm_keyword_p(const uint8_t *start, const uint8_t *end) {
8651
+ return pm_keyword_find(start, end) != NULL;
8545
8652
  }
8546
8653
 
8547
8654
  static pm_token_type_t
8548
8655
  lex_identifier(pm_parser_t *parser, bool previous_command_start) {
8549
- // Lex as far as we can into the current identifier.
8550
- size_t width;
8656
+ /* Lex as far as we can into the current identifier. */
8551
8657
  const uint8_t *end = parser->end;
8552
8658
  const uint8_t *current_start = parser->current.start;
8553
8659
  const uint8_t *current_end = parser->current.end;
8554
8660
  bool encoding_changed = parser->encoding_changed;
8555
8661
 
8556
8662
  if (encoding_changed) {
8663
+ size_t width;
8557
8664
  while ((width = char_is_identifier(parser, current_end, end - current_end)) > 0) {
8558
8665
  current_end += width;
8559
8666
  }
8560
8667
  } else {
8561
- // Fast path: scan ASCII identifier bytes using wide operations.
8668
+ /* Fast path: scan ASCII identifier bytes using wide operations. */
8562
8669
  current_end += scan_identifier_ascii(current_end, end);
8563
8670
 
8564
- // Byte-at-a-time fallback for the tail and any UTF-8 sequences.
8671
+ /* Byte-at-a-time fallback for the tail and any UTF-8 sequences. */
8672
+ size_t width;
8565
8673
  while ((width = char_is_identifier_utf8(current_end, end - current_end)) > 0) {
8566
8674
  current_end += width;
8567
8675
  }
8568
8676
  }
8677
+
8569
8678
  parser->current.end = current_end;
8570
8679
 
8571
- // Now cache the length of the identifier so that we can quickly compare it
8572
- // against known keywords.
8573
- width = (size_t) (current_end - current_start);
8680
+ /* Whether the identifier ends in ! or ?, which makes it a method name
8681
+ * unless it spells a keyword (defined?). */
8682
+ bool method_name = false;
8574
8683
 
8575
8684
  if (current_end < end) {
8576
8685
  if (((current_end + 1 >= end) || (current_end[1] != '=')) && (match(parser, '!') || match(parser, '?'))) {
8577
- // First we'll attempt to extend the identifier by a ! or ?. Then we'll
8578
- // check if we're returning the defined? keyword or just an identifier.
8579
- width++;
8686
+ /* Extend the identifier by the ! or ?, so that the keyword lookup
8687
+ * below sees the whole name. */
8688
+ current_end++;
8580
8689
 
8581
8690
  if (
8582
8691
  ((lex_state_p(parser, PM_LEX_STATE_LABEL | PM_LEX_STATE_ENDFN) && !previous_command_start) || lex_state_arg_p(parser)) &&
8583
8692
  (peek(parser) == ':') && (peek_offset(parser, 1) != ':')
8584
8693
  ) {
8585
- // If we're in a position where we can accept a : at the end of an
8586
- // identifier, then we'll optionally accept it.
8694
+ /* If we are in a position where we can accept a : at the end of
8695
+ * an identifier, then we will optionally accept it. */
8587
8696
  lex_state_set(parser, PM_LEX_STATE_ARG | PM_LEX_STATE_LABELED);
8588
8697
  (void) match(parser, ':');
8589
8698
 
@@ -8593,27 +8702,17 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) {
8593
8702
  return PM_TOKEN_LABEL;
8594
8703
  }
8595
8704
 
8596
- if (parser->lex_state != PM_LEX_STATE_DOT) {
8597
- if (width == 8 && (lex_keyword(parser, current_start, "defined?", width, PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_DEFINED, PM_TOKEN_EOF) != PM_TOKEN_EOF)) {
8598
- return PM_TOKEN_KEYWORD_DEFINED;
8599
- }
8600
- }
8601
-
8602
- return PM_TOKEN_METHOD_NAME;
8603
- }
8604
-
8605
- if (lex_state_p(parser, PM_LEX_STATE_FNAME) && peek_offset(parser, 1) != '~' && peek_offset(parser, 1) != '>' && (peek_offset(parser, 1) != '=' || peek_offset(parser, 2) == '>') && match(parser, '=')) {
8606
- // If we're in a position where we can accept a = at the end of an
8607
- // identifier, then we'll optionally accept it.
8705
+ method_name = true;
8706
+ } else if (lex_state_p(parser, PM_LEX_STATE_FNAME) && peek_offset(parser, 1) != '~' && peek_offset(parser, 1) != '>' && (peek_offset(parser, 1) != '=' || peek_offset(parser, 2) == '>') && match(parser, '=')) {
8707
+ /* If we are in a position where we can accept a = at the end of an
8708
+ * identifier, then we will optionally accept it. */
8608
8709
  return PM_TOKEN_IDENTIFIER;
8609
- }
8610
-
8611
- if (
8710
+ } else if (
8612
8711
  ((lex_state_p(parser, PM_LEX_STATE_LABEL | PM_LEX_STATE_ENDFN) && !previous_command_start) || lex_state_arg_p(parser)) &&
8613
8712
  peek(parser) == ':' && peek_offset(parser, 1) != ':'
8614
8713
  ) {
8615
- // If we're in a position where we can accept a : at the end of an
8616
- // identifier, then we'll optionally accept it.
8714
+ /* If we are in a position where we can accept a : at the end of an
8715
+ * identifier, then we will optionally accept it. */
8617
8716
  lex_state_set(parser, PM_LEX_STATE_ARG | PM_LEX_STATE_LABELED);
8618
8717
  (void) match(parser, ':');
8619
8718
 
@@ -8625,106 +8724,66 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) {
8625
8724
  }
8626
8725
 
8627
8726
  if (parser->lex_state != PM_LEX_STATE_DOT) {
8628
- pm_token_type_t type;
8629
-
8630
- /* The lex state from before lex_keyword transitions it, mirroring the
8631
- * `state = p->lex.state` capture in parse.y's keyword handling. */
8727
+ /* The lex state from before the keyword changes it, which decides
8728
+ * whether the keyword is in its modifier form and whether `do` is a
8729
+ * plain name below. */
8632
8730
  pm_lex_state_t previous_lex_state = parser->lex_state;
8731
+ const pm_keyword_t *keyword = pm_keyword_find(current_start, current_end);
8633
8732
 
8634
- switch (width) {
8635
- case 2:
8636
- if (lex_keyword(parser, current_start, "do", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_DO, PM_TOKEN_EOF) != PM_TOKEN_EOF) {
8637
- /* In FNAME position (a symbol like `:do` or a method name
8638
- * like `def do`), `do` is a plain name rather than a
8639
- * block, loop, or lambda opener, so none of the
8640
- * discrimination below applies. This mirrors parse.y,
8641
- * whose EXPR_FNAME early-return precedes all of the
8642
- * keyword_do special-casing (and never touches
8643
- * lpar_beg). */
8644
- if (previous_lex_state & PM_LEX_STATE_FNAME) {
8645
- return PM_TOKEN_KEYWORD_DO;
8646
- }
8647
- if (parser->enclosure_nesting == parser->lambda_enclosure_nesting) {
8648
- // At the bare nesting level of a lambda literal (no
8649
- // delimiter opened since `->`), a `do` opens the lambda
8650
- // body. This is a distinct token so that a command in a
8651
- // parameter default cannot consume it as its own block
8652
- // (`-> a = foo do end` is `->(a = foo) do end`). It
8653
- // mirrors CRuby's keyword_do_LAMBDA.
8654
- //
8655
- // Clear the nesting so that no token within the
8656
- // `do`/`end` body is considered to be at the beginning
8657
- // of a lambda; the parser restores the enclosing value
8658
- // once the lambda has been fully parsed. This mirrors
8659
- // parse.y setting `p->lex.lpar_beg = -1` when lexing
8660
- // keyword_do_LAMBDA.
8661
- parser->lambda_enclosure_nesting = -1;
8662
- return PM_TOKEN_KEYWORD_DO_LAMBDA;
8663
- }
8664
- if (pm_do_loop_stack_p(parser)) {
8665
- return PM_TOKEN_KEYWORD_DO_LOOP;
8666
- }
8667
- if (!pm_accepts_block_stack_p(parser)) {
8668
- return PM_TOKEN_KEYWORD_DO_BLOCK;
8669
- }
8733
+ if (keyword != NULL) {
8734
+ if (previous_lex_state & PM_LEX_STATE_FNAME) {
8735
+ lex_state_set(parser, PM_LEX_STATE_ENDFN);
8736
+ } else {
8737
+ lex_state_set(parser, keyword->state);
8738
+ if (keyword->state == PM_LEX_STATE_BEG) {
8739
+ parser->command_start = true;
8740
+ }
8741
+
8742
+ if ((keyword->modifier_type != PM_TOKEN_EOF) && !(previous_lex_state & (PM_LEX_STATE_BEG | PM_LEX_STATE_LABELED | PM_LEX_STATE_CLASS))) {
8743
+ lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL);
8744
+ return keyword->modifier_type;
8745
+ }
8746
+ }
8747
+
8748
+ if (keyword->type == PM_TOKEN_KEYWORD_DO) {
8749
+ /* In FNAME position (a symbol like `:do` or a method name like
8750
+ * `def do`), `do` is a plain name rather than a block, loop, or
8751
+ * lambda opener, so none of the discrimination below applies
8752
+ * and the lambda nesting is left untouched. */
8753
+ if (previous_lex_state & PM_LEX_STATE_FNAME) {
8670
8754
  return PM_TOKEN_KEYWORD_DO;
8671
8755
  }
8756
+ if (parser->enclosure_nesting == parser->lambda_enclosure_nesting) {
8757
+ /* At the bare nesting level of a lambda literal (no
8758
+ * delimiter opened since `->`), a `do` opens the lambda
8759
+ * body. This is a distinct token so that a command in a
8760
+ * parameter default cannot consume it as its own block
8761
+ * (`-> a = foo do end` is `->(a = foo) do end`).
8762
+ *
8763
+ * Clear the nesting so that no token within the `do`/`end`
8764
+ * body is considered to be at the beginning of a lambda;
8765
+ * the parser restores the enclosing value once the lambda
8766
+ * has been fully parsed. */
8767
+ parser->lambda_enclosure_nesting = -1;
8768
+ return PM_TOKEN_KEYWORD_DO_LAMBDA;
8769
+ }
8770
+ if (pm_do_loop_stack_p(parser)) {
8771
+ return PM_TOKEN_KEYWORD_DO_LOOP;
8772
+ }
8773
+ if (!pm_accepts_block_stack_p(parser)) {
8774
+ return PM_TOKEN_KEYWORD_DO_BLOCK;
8775
+ }
8776
+ return PM_TOKEN_KEYWORD_DO;
8777
+ }
8672
8778
 
8673
- if ((type = lex_keyword(parser, current_start, "if", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IF, PM_TOKEN_KEYWORD_IF_MODIFIER)) != PM_TOKEN_EOF) return type;
8674
- if ((type = lex_keyword(parser, current_start, "in", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IN, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8675
- if ((type = lex_keyword(parser, current_start, "or", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_OR, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8676
- break;
8677
- case 3:
8678
- if ((type = lex_keyword(parser, current_start, "and", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_AND, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8679
- if ((type = lex_keyword(parser, current_start, "def", width, PM_LEX_STATE_FNAME, PM_TOKEN_KEYWORD_DEF, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8680
- if ((type = lex_keyword(parser, current_start, "end", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8681
- if ((type = lex_keyword(parser, current_start, "END", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END_UPCASE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8682
- if ((type = lex_keyword(parser, current_start, "for", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_FOR, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8683
- if ((type = lex_keyword(parser, current_start, "nil", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_NIL, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8684
- if ((type = lex_keyword(parser, current_start, "not", width, PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_NOT, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8685
- break;
8686
- case 4:
8687
- if ((type = lex_keyword(parser, current_start, "case", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_CASE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8688
- if ((type = lex_keyword(parser, current_start, "else", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8689
- if ((type = lex_keyword(parser, current_start, "next", width, PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_NEXT, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8690
- if ((type = lex_keyword(parser, current_start, "redo", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_REDO, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8691
- if ((type = lex_keyword(parser, current_start, "self", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_SELF, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8692
- if ((type = lex_keyword(parser, current_start, "then", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8693
- if ((type = lex_keyword(parser, current_start, "true", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_TRUE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8694
- if ((type = lex_keyword(parser, current_start, "when", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHEN, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8695
- break;
8696
- case 5:
8697
- if ((type = lex_keyword(parser, current_start, "alias", width, PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_ALIAS, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8698
- if ((type = lex_keyword(parser, current_start, "begin", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_BEGIN, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8699
- if ((type = lex_keyword(parser, current_start, "BEGIN", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_BEGIN_UPCASE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8700
- if ((type = lex_keyword(parser, current_start, "break", width, PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_BREAK, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8701
- if ((type = lex_keyword(parser, current_start, "class", width, PM_LEX_STATE_CLASS, PM_TOKEN_KEYWORD_CLASS, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8702
- if ((type = lex_keyword(parser, current_start, "elsif", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSIF, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8703
- if ((type = lex_keyword(parser, current_start, "false", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_FALSE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8704
- if ((type = lex_keyword(parser, current_start, "retry", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD_RETRY, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8705
- if ((type = lex_keyword(parser, current_start, "super", width, PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_SUPER, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8706
- if ((type = lex_keyword(parser, current_start, "undef", width, PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_UNDEF, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8707
- if ((type = lex_keyword(parser, current_start, "until", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNTIL, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) != PM_TOKEN_EOF) return type;
8708
- if ((type = lex_keyword(parser, current_start, "while", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHILE, PM_TOKEN_KEYWORD_WHILE_MODIFIER)) != PM_TOKEN_EOF) return type;
8709
- if ((type = lex_keyword(parser, current_start, "yield", width, PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_YIELD, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8710
- break;
8711
- case 6:
8712
- if ((type = lex_keyword(parser, current_start, "ensure", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8713
- if ((type = lex_keyword(parser, current_start, "module", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_MODULE, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8714
- if ((type = lex_keyword(parser, current_start, "rescue", width, PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) != PM_TOKEN_EOF) return type;
8715
- if ((type = lex_keyword(parser, current_start, "return", width, PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RETURN, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8716
- if ((type = lex_keyword(parser, current_start, "unless", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNLESS, PM_TOKEN_KEYWORD_UNLESS_MODIFIER)) != PM_TOKEN_EOF) return type;
8717
- break;
8718
- case 8:
8719
- if ((type = lex_keyword(parser, current_start, "__LINE__", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD___LINE__, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8720
- if ((type = lex_keyword(parser, current_start, "__FILE__", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD___FILE__, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8721
- break;
8722
- case 12:
8723
- if ((type = lex_keyword(parser, current_start, "__ENCODING__", width, PM_LEX_STATE_END, PM_TOKEN_KEYWORD___ENCODING__, PM_TOKEN_EOF)) != PM_TOKEN_EOF) return type;
8724
- break;
8779
+ return keyword->type;
8725
8780
  }
8726
8781
  }
8727
8782
 
8783
+ if (method_name) {
8784
+ return PM_TOKEN_METHOD_NAME;
8785
+ }
8786
+
8728
8787
  if (encoding_changed) {
8729
8788
  return parser->encoding->isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER;
8730
8789
  }
@@ -9575,7 +9634,7 @@ lex_question_mark(pm_parser_t *parser) {
9575
9634
 
9576
9635
  // Copy buffer data into the arena and free the heap buffer.
9577
9636
  void *arena_data = pm_arena_memdup(parser->arena, buffer.value, buffer.length, PRISM_ALIGNOF(uint8_t));
9578
- pm_string_constant_init(&parser->current_string, (const char *) arena_data, buffer.length);
9637
+ pm_string_init(&parser->current_string, (const uint8_t *) arena_data, buffer.length);
9579
9638
  pm_buffer_cleanup(&buffer);
9580
9639
 
9581
9640
  return PM_TOKEN_CHARACTER_LITERAL;
@@ -9899,7 +9958,7 @@ pm_token_buffer_copy(pm_parser_t *parser, pm_token_buffer_t *token_buffer) {
9899
9958
  // Copy buffer data into the arena and free the heap buffer.
9900
9959
  size_t len = pm_buffer_length(&token_buffer->buffer);
9901
9960
  void *arena_data = pm_arena_memdup(parser->arena, pm_buffer_value(&token_buffer->buffer), len, PRISM_ALIGNOF(uint8_t));
9902
- pm_string_constant_init(&parser->current_string, (const char *) arena_data, len);
9961
+ pm_string_init(&parser->current_string, (const uint8_t *) arena_data, len);
9903
9962
  pm_buffer_cleanup(&token_buffer->buffer);
9904
9963
  }
9905
9964
 
@@ -12952,6 +13011,19 @@ expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t di
12952
13011
  */
12953
13012
  #define PM_PARSE_ACCEPTS_STATEMENT ((uint8_t) 0x10)
12954
13013
 
13014
+ /**
13015
+ * Indicates that an endless method definition may have a command call as its
13016
+ * body, as in `def f = foo 1`. Such a definition is a command write, accepted
13017
+ * as a statement (including the value of a statement-level rescue modifier)
13018
+ * and as the value of a single-target write. Since Ruby 4.0 it is also
13019
+ * accepted as the sole argument of a call, where other command writes such as
13020
+ * `x = foo 1` are not. Expression positions such as conditional predicates and
13021
+ * the operands of `not`, `!`, `and`, and `or` accept a command call but not a
13022
+ * command write, so this is set alongside PM_PARSE_ACCEPTS_COMMAND_CALL only
13023
+ * where both are accepted.
13024
+ */
13025
+ #define PM_PARSE_ACCEPTS_COMMAND_DEF ((uint8_t) 0x20)
13026
+
12955
13027
  static pm_node_t *
12956
13028
  parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth);
12957
13029
 
@@ -13067,6 +13139,23 @@ token_begins_pattern_p(pm_token_type_t type) {
13067
13139
  );
13068
13140
  }
13069
13141
 
13142
+ /**
13143
+ * Returns true if the given token can begin an argument in an unparenthesized
13144
+ * argument list. This is the set of tokens that can begin an expression plus
13145
+ * the prefix operators that only appear in argument position — `&` (block
13146
+ * arguments), `*` (splats), and `**` (keyword splats) — which are excluded
13147
+ * from token_begins_expression_p.
13148
+ */
13149
+ static PRISM_INLINE bool
13150
+ token_begins_argument_p(pm_token_type_t type) {
13151
+ return (
13152
+ token_begins_expression_p(type) ||
13153
+ type == PM_TOKEN_UAMPERSAND ||
13154
+ type == PM_TOKEN_USTAR ||
13155
+ type == PM_TOKEN_USTAR_STAR
13156
+ );
13157
+ }
13158
+
13070
13159
  /**
13071
13160
  * Parse an expression with the given binding power that may be optionally
13072
13161
  * prefixed by the * operator.
@@ -13082,7 +13171,7 @@ parse_starred_expression(pm_parser_t *parser, pm_binding_power_t binding_power,
13082
13171
  return parse_value_expression(parser, binding_power, flags, diag_id, depth);
13083
13172
  }
13084
13173
 
13085
- static bool
13174
+ static pm_visit_result_t
13086
13175
  pm_node_unreference_each(const pm_node_t *node, void *data) {
13087
13176
  switch (PM_NODE_TYPE(node)) {
13088
13177
  /* When we are about to destroy a set of nodes that could potentially
@@ -13108,15 +13197,15 @@ pm_node_unreference_each(const pm_node_t *node, void *data) {
13108
13197
  }
13109
13198
  parser->current_block_exits->size--;
13110
13199
 
13111
- /* Note returning true here because these nodes could have
13200
+ /* Note descending here because these nodes could have
13112
13201
  * arguments that are themselves block exits. */
13113
- return true;
13202
+ return PM_VISIT_DESCEND;
13114
13203
  }
13115
13204
 
13116
13205
  index++;
13117
13206
  }
13118
13207
 
13119
- return true;
13208
+ return PM_VISIT_DESCEND;
13120
13209
  }
13121
13210
  /* When an implicit local variable is written to or targeted, it becomes
13122
13211
  * a regular, named local variable. This branch removes it from the list
@@ -13140,10 +13229,10 @@ pm_node_unreference_each(const pm_node_t *node, void *data) {
13140
13229
  }
13141
13230
  }
13142
13231
 
13143
- return false;
13232
+ return PM_VISIT_SKIP;
13144
13233
  }
13145
13234
  default:
13146
- return true;
13235
+ return PM_VISIT_DESCEND;
13147
13236
  }
13148
13237
  }
13149
13238
 
@@ -13708,7 +13797,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context, uint16_t depth) {
13708
13797
  context_push(parser, context);
13709
13798
 
13710
13799
  while (true) {
13711
- pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
13800
+ pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
13712
13801
  pm_statements_node_body_append(parser, statements, node, true);
13713
13802
 
13714
13803
  // If we're recovering from a syntax error, then we need to stop parsing
@@ -13794,7 +13883,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context, uint16_t depth) {
13794
13883
  static void
13795
13884
  pm_hash_key_duplicated_warn(pm_parser_t *parser, const pm_node_t *duplicated, const pm_node_t *node) {
13796
13885
  pm_buffer_t buffer = { 0 };
13797
- pm_static_literal_inspect(&buffer, &parser->line_offsets, parser->start, parser->start_line, parser->encoding, duplicated);
13886
+ pm_static_literal_inspect(&buffer, parser, duplicated);
13798
13887
 
13799
13888
  pm_diagnostic_list_append_format(
13800
13889
  &parser->metadata_arena,
@@ -13816,7 +13905,7 @@ pm_hash_key_duplicated_warn(pm_parser_t *parser, const pm_node_t *duplicated, co
13816
13905
  */
13817
13906
  static void
13818
13907
  pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) {
13819
- const pm_node_t *duplicated = pm_static_literals_add(&parser->line_offsets, parser->start, parser->start_line, parser->encoding, literals, node, true);
13908
+ const pm_node_t *duplicated = pm_static_literals_add(parser, literals, node, true);
13820
13909
 
13821
13910
  if (duplicated != NULL) {
13822
13911
  pm_hash_key_duplicated_warn(parser, duplicated, node);
@@ -13840,7 +13929,7 @@ pm_hash_key_static_literals_merge(pm_parser_t *parser, pm_static_literals_t *lit
13840
13929
  switch (PM_NODE_TYPE(element)) {
13841
13930
  case PM_ASSOC_NODE: {
13842
13931
  pm_node_t *key = ((pm_assoc_node_t *) element)->key;
13843
- const pm_node_t *duplicated = pm_static_literals_add(&parser->line_offsets, parser->start, parser->start_line, parser->encoding, literals, key, true);
13932
+ const pm_node_t *duplicated = pm_static_literals_add(parser, literals, key, true);
13844
13933
 
13845
13934
  if (duplicated != NULL && PM_NODE_START(duplicated) < boundary) {
13846
13935
  pm_hash_key_duplicated_warn(parser, duplicated, key);
@@ -13871,7 +13960,7 @@ static void
13871
13960
  pm_when_clause_static_literals_add(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) {
13872
13961
  pm_node_t *previous;
13873
13962
 
13874
- if ((previous = pm_static_literals_add(&parser->line_offsets, parser->start, parser->start_line, parser->encoding, literals, node, false)) != NULL) {
13963
+ if ((previous = pm_static_literals_add(parser, literals, node, false)) != NULL) {
13875
13964
  pm_diagnostic_list_append_format(
13876
13965
  &parser->metadata_arena,
13877
13966
  &parser->warning_list,
@@ -14135,6 +14224,27 @@ pm_command_call_value_p(const pm_parser_t *parser, const pm_node_t *node) {
14135
14224
  }
14136
14225
  }
14137
14226
 
14227
+ /**
14228
+ * Returns true if the given node is a command that can end before a comma
14229
+ * without consuming it: a constant-path command with a brace block
14230
+ * (`Foo::Bar { }`), or an endless method whose command body ends in a rescue
14231
+ * modifier value (`def f = foo 1 rescue z`). Other commands take a following
14232
+ * comma into their own arguments. A command can be neither one of several
14233
+ * arguments nor one of several values of a write, so a comma after one of
14234
+ * these is an error.
14235
+ */
14236
+ static bool
14237
+ pm_command_before_comma_p(const pm_parser_t *parser, const pm_node_t *node) {
14238
+ switch (PM_NODE_TYPE(node)) {
14239
+ case PM_CALL_NODE:
14240
+ return pm_constant_path_command_call_p(parser, (const pm_call_node_t *) node);
14241
+ case PM_DEF_NODE:
14242
+ return pm_command_call_value_p(parser, node);
14243
+ default:
14244
+ return false;
14245
+ }
14246
+ }
14247
+
14138
14248
  /**
14139
14249
  * Returns true if the given node is a block call: a command
14140
14250
  * with a do-block, or any call chained (via `.`, `::`, `&.`) from such a node.
@@ -14321,7 +14431,17 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
14321
14431
  PRISM_FALLTHROUGH
14322
14432
  default: {
14323
14433
  if (argument == NULL) {
14324
- argument = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (!parsed_first_argument ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0u) | PM_PARSE_ACCEPTS_LABEL), PM_ERR_EXPECT_ARGUMENT, (uint16_t) (depth + 1));
14434
+ /* The first argument may be a command call, and since Ruby
14435
+ * 4.0 also an endless method with a command body
14436
+ * (`private def f = foo 1`), either of which must then be
14437
+ * the sole argument. */
14438
+ uint8_t command_flags = 0;
14439
+ if (!parsed_first_argument) {
14440
+ command_flags = PM_PARSE_ACCEPTS_COMMAND_CALL;
14441
+ if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) command_flags |= PM_PARSE_ACCEPTS_COMMAND_DEF;
14442
+ }
14443
+
14444
+ argument = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | command_flags | PM_PARSE_ACCEPTS_LABEL), PM_ERR_EXPECT_ARGUMENT, (uint16_t) (depth + 1));
14325
14445
  }
14326
14446
 
14327
14447
  bool contains_keywords = false;
@@ -14418,6 +14538,12 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
14418
14538
  break;
14419
14539
  }
14420
14540
  }
14541
+
14542
+ /* A command can only be the sole argument, as in
14543
+ * `foo Foo::Bar { }, y` or `foo def f = bar 1 rescue z, y`. */
14544
+ if (pm_command_before_comma_p(parser, argument)) {
14545
+ pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
14546
+ }
14421
14547
  } else {
14422
14548
  // If there is no comma at the end of the argument list then we're
14423
14549
  // done parsing arguments and can break out of this loop.
@@ -15628,7 +15754,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool full_a
15628
15754
 
15629
15755
  arguments->closing_loc = TOK2LOC(parser, &parser->previous);
15630
15756
  }
15631
- } else if ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR, PM_TOKEN_UAMPERSAND)) && !match1(parser, PM_TOKEN_BRACE_LEFT)) {
15757
+ } else if ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type) && !match1(parser, PM_TOKEN_BRACE_LEFT)) {
15632
15758
  found |= true;
15633
15759
  parsed_command_args = true;
15634
15760
 
@@ -15848,7 +15974,7 @@ ensure_valid_jump(pm_parser_t *parser, pm_node_t *node) {
15848
15974
  // block exit to the list of exits for the expression, and
15849
15975
  // the node parsing will handle validating it instead.
15850
15976
  assert(parser->current_block_exits != NULL);
15851
- pm_node_list_append(parser->arena, parser->current_block_exits, node);
15977
+ pm_node_list_append_inline(&parser->scratch_arena, parser->current_block_exits, node);
15852
15978
  return;
15853
15979
  case PM_CONTEXT_BEGIN_ELSE:
15854
15980
  case PM_CONTEXT_BEGIN_ENSURE:
@@ -15940,7 +16066,7 @@ pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) {
15940
16066
  // However, they could still become valid in a higher level context if
15941
16067
  // there is another list above this one. In this case we'll push all of
15942
16068
  // the block exits up to the previous list.
15943
- pm_node_list_concat(parser->arena, previous_block_exits, parser->current_block_exits);
16069
+ pm_node_list_concat(&parser->scratch_arena, previous_block_exits, parser->current_block_exits);
15944
16070
  parser->current_block_exits = previous_block_exits;
15945
16071
  } else {
15946
16072
  // If we did not match a trailing while/until and this was the last
@@ -16586,12 +16712,12 @@ parse_variable(pm_parser_t *parser) {
16586
16712
  }
16587
16713
 
16588
16714
  pm_node_t *node = UP(pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false));
16589
- pm_node_list_append(parser->arena, &current_scope->implicit_parameters, node);
16715
+ pm_node_list_append_inline(&parser->scratch_arena, &current_scope->implicit_parameters, node);
16590
16716
 
16591
16717
  return node;
16592
16718
  } else if ((parser->version >= PM_OPTIONS_VERSION_CRUBY_3_4) && pm_token_is_it(parser->previous.start, parser->previous.end)) {
16593
16719
  pm_node_t *node = UP(pm_it_local_variable_read_node_create(parser, &parser->previous));
16594
- pm_node_list_append(parser->arena, &current_scope->implicit_parameters, node);
16720
+ pm_node_list_append_inline(&parser->scratch_arena, &current_scope->implicit_parameters, node);
16595
16721
 
16596
16722
  return node;
16597
16723
  }
@@ -16648,18 +16774,13 @@ parse_method_definition_name(pm_parser_t *parser) {
16648
16774
 
16649
16775
  static void
16650
16776
  parse_heredoc_dedent_string(pm_arena_t *arena, pm_string_t *string, size_t common_whitespace) {
16651
- // Make a writable copy in the arena if the string isn't already writable.
16652
- // We keep a mutable pointer to the arena memory so we can memmove into it
16653
- // below without casting away const from the string's source field.
16654
- uint8_t *writable;
16655
-
16656
- if (string->type != PM_STRING_OWNED) {
16657
- size_t length = pm_string_length(string);
16658
- writable = (uint8_t *) pm_arena_memdup(arena, pm_string_source(string), length, PRISM_ALIGNOF(uint8_t));
16659
- pm_string_constant_init(string, (const char *) writable, length);
16660
- } else {
16661
- writable = (uint8_t *) string->source;
16662
- }
16777
+ // Make a writable copy of the string in the arena, since strings only view
16778
+ // their bytes. We keep a mutable pointer to the arena memory so we can
16779
+ // memmove into it below without casting away const from the string's
16780
+ // source field.
16781
+ size_t length = pm_string_length(string);
16782
+ uint8_t *writable = (uint8_t *) pm_arena_memdup(arena, pm_string_source(string), length, PRISM_ALIGNOF(uint8_t));
16783
+ pm_string_init(string, writable, length);
16663
16784
 
16664
16785
  // Now get the bounds of the existing string. We'll use this as a
16665
16786
  // destination to move bytes into. We'll also use it for bounds checking
@@ -16824,11 +16945,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16824
16945
  if (match1(parser, PM_TOKEN_STRING_CONTENT)) {
16825
16946
  pm_node_list_t parts = { 0 };
16826
16947
  pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &content, NULL, &unescaped));
16827
- pm_node_list_append(parser->arena, &parts, part);
16948
+ pm_node_list_append_inline(parser->arena, &parts, part);
16828
16949
 
16829
16950
  do {
16830
16951
  part = UP(pm_string_node_create_current_string(parser, NULL, &parser->current, NULL));
16831
- pm_node_list_append(parser->arena, &parts, part);
16952
+ pm_node_list_append_inline(parser->arena, &parts, part);
16832
16953
  parser_lex(parser);
16833
16954
  } while (match1(parser, PM_TOKEN_STRING_CONTENT));
16834
16955
 
@@ -16883,11 +17004,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16883
17004
  pm_node_list_t parts = { 0 };
16884
17005
  pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &parser->previous, NULL, &unescaped));
16885
17006
  pm_node_flag_set(part, parse_unescaped_encoding(parser, explicit_encoding));
16886
- pm_node_list_append(parser->arena, &parts, part);
17007
+ pm_node_list_append_inline(parser->arena, &parts, part);
16887
17008
 
16888
17009
  while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) {
16889
17010
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
16890
- pm_node_list_append(parser->arena, &parts, part);
17011
+ pm_node_list_append_inline(parser->arena, &parts, part);
16891
17012
  }
16892
17013
  }
16893
17014
 
@@ -16911,7 +17032,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16911
17032
 
16912
17033
  while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) {
16913
17034
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
16914
- pm_node_list_append(parser->arena, &parts, part);
17035
+ pm_node_list_append_inline(parser->arena, &parts, part);
16915
17036
  }
16916
17037
  }
16917
17038
 
@@ -16983,7 +17104,7 @@ parse_pattern_capture(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_co
16983
17104
  // Skip this capture if it starts with an underscore.
16984
17105
  if (peek_at(parser, parser->start + location->start) == '_') return;
16985
17106
 
16986
- if (!pm_constant_id_set_insert(parser->arena, captures, capture)) {
17107
+ if (!pm_constant_id_set_insert(&parser->scratch_arena, captures, capture)) {
16987
17108
  pm_parser_err(parser, location->start, location->length, PM_ERR_PATTERN_CAPTURE_DUPLICATE);
16988
17109
  }
16989
17110
  }
@@ -17249,7 +17370,7 @@ parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_set_t *cap
17249
17370
  */
17250
17371
  static void
17251
17372
  parse_pattern_hash_key(pm_parser_t *parser, pm_static_literals_t *keys, pm_node_t *node) {
17252
- if (pm_static_literals_add(&parser->line_offsets, parser->start, parser->start_line, parser->encoding, keys, node, true) != NULL) {
17373
+ if (pm_static_literals_add(parser, keys, node, true) != NULL) {
17253
17374
  pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_KEY_DUPLICATE);
17254
17375
  }
17255
17376
  }
@@ -17297,7 +17418,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17297
17418
  }
17298
17419
 
17299
17420
  pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value));
17300
- pm_node_list_append(parser->arena, &assocs, assoc);
17421
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17301
17422
  break;
17302
17423
  }
17303
17424
  }
@@ -17312,7 +17433,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17312
17433
  pm_node_t *value = UP(pm_error_recovery_node_create(parser, PM_NODE_START(first_node), PM_NODE_LENGTH(first_node)));
17313
17434
  pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value));
17314
17435
 
17315
- pm_node_list_append(parser->arena, &assocs, assoc);
17436
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17316
17437
  break;
17317
17438
  }
17318
17439
  }
@@ -17340,7 +17461,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17340
17461
  rest = assoc;
17341
17462
  } else {
17342
17463
  pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
17343
- pm_node_list_append(parser->arena, &assocs, assoc);
17464
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17344
17465
  }
17345
17466
  } else {
17346
17467
  pm_node_t *key;
@@ -17386,7 +17507,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17386
17507
  pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
17387
17508
  }
17388
17509
 
17389
- pm_node_list_append(parser->arena, &assocs, assoc);
17510
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17390
17511
  }
17391
17512
  }
17392
17513
 
@@ -17666,7 +17787,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_
17666
17787
  * are not captures of this pattern. A nested alternation is not visited
17667
17788
  * either: its operands were already checked when it was parsed.
17668
17789
  */
17669
- static bool
17790
+ static pm_visit_result_t
17670
17791
  parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17671
17792
  pm_parser_t *parser = (pm_parser_t *) data;
17672
17793
 
@@ -17677,7 +17798,7 @@ parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17677
17798
  if (peek_at(parser, parser->start + PM_NODE_START(node)) != '_') {
17678
17799
  pm_parser_err(parser, PM_NODE_START(node), PM_NODE_LENGTH(node), PM_ERR_PATTERN_CAPTURE_IN_ALTERNATIVE);
17679
17800
  }
17680
- return false;
17801
+ return PM_VISIT_SKIP;
17681
17802
  case PM_ARRAY_PATTERN_NODE:
17682
17803
  case PM_ASSOC_NODE:
17683
17804
  case PM_ASSOC_SPLAT_NODE:
@@ -17687,9 +17808,9 @@ parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17687
17808
  case PM_IMPLICIT_NODE:
17688
17809
  case PM_PARENTHESES_NODE:
17689
17810
  case PM_SPLAT_NODE:
17690
- return true;
17811
+ return PM_VISIT_DESCEND;
17691
17812
  default:
17692
- return false;
17813
+ return PM_VISIT_SKIP;
17693
17814
  }
17694
17815
  }
17695
17816
 
@@ -17885,7 +18006,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17885
18006
  // or a find pattern. We need to parse all of the patterns, put them
17886
18007
  // into a big list, and then determine which type of node we have.
17887
18008
  pm_node_list_t nodes = { 0 };
17888
- pm_node_list_append(parser->arena, &nodes, node);
18009
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17889
18010
 
17890
18011
  // Gather up all of the patterns into the list.
17891
18012
  while (accept1(parser, PM_TOKEN_COMMA)) {
@@ -17904,7 +18025,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17904
18025
  }
17905
18026
 
17906
18027
  node = UP(pm_implicit_rest_node_create(parser, &parser->previous));
17907
- pm_node_list_append(parser->arena, &nodes, node);
18028
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17908
18029
  trailing_rest = true;
17909
18030
  break;
17910
18031
  }
@@ -17924,7 +18045,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17924
18045
  node = parse_pattern_primitives(parser, captures, NULL, PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
17925
18046
  }
17926
18047
 
17927
- pm_node_list_append(parser->arena, &nodes, node);
18048
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17928
18049
  }
17929
18050
 
17930
18051
  // If the first pattern and the last pattern are rest patterns, then we
@@ -18556,7 +18677,7 @@ parse_class(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
18556
18677
  * Parse a method definition expression (the `def` keyword).
18557
18678
  */
18558
18679
  static pm_node_t *
18559
- parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, uint16_t depth) {
18680
+ parse_def(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
18560
18681
  pm_node_list_t current_block_exits = { 0 };
18561
18682
  pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18562
18683
 
@@ -18806,14 +18927,9 @@ parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
18806
18927
  pm_do_loop_stack_push(parser, false);
18807
18928
  statements = UP(pm_statements_node_create(parser));
18808
18929
 
18809
- uint8_t allow_flags;
18810
- if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) {
18811
- allow_flags = flags & PM_PARSE_ACCEPTS_COMMAND_CALL;
18812
- } else {
18813
- /* Allow `def foo = puts "Hello"` but not
18814
- * `private def foo = puts "Hello"` */
18815
- allow_flags = (binding_power == PM_BINDING_POWER_ASSIGNMENT || binding_power < PM_BINDING_POWER_COMPOSITION) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0;
18816
- }
18930
+ /* The body accepts a command call (`def f = foo 1`) only where the
18931
+ * caller accepts an endless method with a command body. */
18932
+ uint8_t allow_flags = (flags & PM_PARSE_ACCEPTS_COMMAND_DEF) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0;
18817
18933
 
18818
18934
  /* Inside a def body, we push true onto the accepts_block_stack so that
18819
18935
  * `do` is lexed as PM_TOKEN_KEYWORD_DO (which can only start a block
@@ -18858,7 +18974,7 @@ parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
18858
18974
  pm_accepts_block_stack_pop(parser);
18859
18975
 
18860
18976
  /* A nested endless def whose body is a command call (e.g.,
18861
- * `def f = def g = foo bar`) is a command assignment and cannot appear
18977
+ * `def f = def g = foo bar`) is a command write and cannot appear
18862
18978
  * as a def body. */
18863
18979
  if (PM_NODE_TYPE_P(statement, PM_DEF_NODE) && pm_command_call_value_p(parser, statement)) {
18864
18980
  PM_PARSER_ERR_NODE_FORMAT(parser, statement, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_str(parser->current.type));
@@ -19041,7 +19157,7 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
19041
19157
  /* Otherwise, we're going to parse the first statement in the list of
19042
19158
  * statements within the parentheses. */
19043
19159
  context_push(parser, PM_CONTEXT_PARENS);
19044
- pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
19160
+ pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
19045
19161
  context_pop(parser);
19046
19162
 
19047
19163
  /* Determine if this statement is followed by a terminator. In the case of a
@@ -19154,7 +19270,7 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
19154
19270
 
19155
19271
  /* Parse each statement within the parentheses. */
19156
19272
  while (true) {
19157
- pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
19273
+ pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF | PM_PARSE_ACCEPTS_DO_BLOCK, PM_ERR_CANNOT_PARSE_EXPRESSION, (uint16_t) (depth + 1));
19158
19274
  pm_statements_node_body_append(parser, statements, node, true);
19159
19275
 
19160
19276
  /* If we're recovering from a syntax error, then we need to stop parsing
@@ -19435,11 +19551,11 @@ parse_heredoc(pm_parser_t *parser, uint16_t depth) {
19435
19551
  // so we'll need to create an interpolated string node to hold
19436
19552
  // them all.
19437
19553
  pm_node_list_t parts = { 0 };
19438
- pm_node_list_append(parser->arena, &parts, part);
19554
+ pm_node_list_append_inline(parser->arena, &parts, part);
19439
19555
 
19440
19556
  while (!match2(parser, PM_TOKEN_HEREDOC_END, PM_TOKEN_EOF)) {
19441
19557
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
19442
- pm_node_list_append(parser->arena, &parts, part);
19558
+ pm_node_list_append_inline(parser->arena, &parts, part);
19443
19559
  }
19444
19560
  }
19445
19561
 
@@ -19670,17 +19786,17 @@ parse_lambda(pm_parser_t *parser, uint16_t depth) {
19670
19786
  /* Restore the enclosing lambda's nesting now that the body has been
19671
19787
  * parsed, so that the token following the closing `}` is lexed in the
19672
19788
  * enclosing context. During the body the nesting held this lambda's own
19673
- * level, which every token inside the braces sits above. This mirrors
19674
- * parse.y restoring `p->lex.lpar_beg` after `lambda_body`. */
19789
+ * level, which every token inside the braces sits above. */
19675
19790
  parser->lambda_enclosure_nesting = previous_lambda_enclosure_nesting;
19676
19791
  expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_LAMBDA_TERM_BRACE, &opening);
19677
19792
  } else {
19678
- /* A `-> { }` body is delimited by `{`/`}`, whose block-accepting frame
19679
- * the lexer manages. A `-> do end` body is delimited by keywords, so
19680
- * push the frame here and pop it before `end`. The push must precede
19681
- * consuming the `do`, which lexes the first token of the body; this
19682
- * matches parse.y's CMDARG_PUSH(0) before `lambda_body`. */
19683
- pm_accepts_block_stack_push(parser, true);
19793
+ /* A `-> { }` body is delimited by `{`/`}`, whose enclosure frame the
19794
+ * lexer manages. A `-> do end` body is delimited by keywords, so push
19795
+ * the frame here and pop it before `end`. The frame isolates the body
19796
+ * from an enclosing `while`/`until`/`for` condition, so a `do` inside
19797
+ * it opens a block rather than the loop body. The push must precede
19798
+ * consuming the `do`, which lexes the first token of the body. */
19799
+ pm_enclosure_frame_push(parser);
19684
19800
  expect1(parser, PM_TOKEN_KEYWORD_DO_LAMBDA, PM_ERR_LAMBDA_OPEN);
19685
19801
  opening = parser->previous;
19686
19802
 
@@ -19701,7 +19817,7 @@ parse_lambda(pm_parser_t *parser, uint16_t depth) {
19701
19817
  parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false);
19702
19818
  }
19703
19819
 
19704
- pm_accepts_block_stack_pop(parser);
19820
+ pm_enclosure_frame_pop(parser);
19705
19821
 
19706
19822
  /* As with the brace branch above, restore the nesting before consuming
19707
19823
  * the closing `end`, which lexes the token that follows it. */
@@ -20040,7 +20156,7 @@ parse_identifier(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
20040
20156
  // can still be a method call if it is followed by arguments or
20041
20157
  // a block, so we need to check for that here.
20042
20158
  if (
20043
- ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_UAMPERSAND, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR))) ||
20159
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type)) ||
20044
20160
  (pm_accepts_block_stack_p(parser) && match1(parser, PM_TOKEN_KEYWORD_DO)) ||
20045
20161
  match1(parser, PM_TOKEN_BRACE_LEFT)
20046
20162
  ) {
@@ -20497,7 +20613,14 @@ parse_jump(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
20497
20613
 
20498
20614
  if (binding_power == PM_BINDING_POWER_UNSET || binding_power >= PM_BINDING_POWER_RANGE) {
20499
20615
  pm_token_t next = parser->current;
20616
+
20617
+ /* The arguments of a jump start a fresh command context, so a
20618
+ * `do` inside them binds to the nearest call within the
20619
+ * arguments rather than to an enclosing command call, as in
20620
+ * `check 1, case x when 1 then return bar do end end`. */
20621
+ pm_accepts_block_stack_push(parser, true);
20500
20622
  parse_arguments(parser, &arguments, false, PM_TOKEN_EOF, flags, (uint16_t) (depth + 1));
20623
+ pm_accepts_block_stack_pop(parser);
20501
20624
 
20502
20625
  // Reject `foo && return bar`.
20503
20626
  if (!(flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && arguments.arguments != NULL) {
@@ -20590,7 +20713,16 @@ parse_not(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
20590
20713
  }
20591
20714
  }
20592
20715
  } else {
20593
- receiver = parse_expression(parser, PM_BINDING_POWER_NOT, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | PM_PARSE_ACCEPTS_COMMAND_CALL), PM_ERR_NOT_EXPRESSION, (uint16_t) (depth + 1));
20716
+ /* The operand of `not` may be a command call, except at the start of
20717
+ * an endless method body, where it may be one only if the body may
20718
+ * be: `def f = not foo 1` has a command body, so it is rejected where
20719
+ * that is not accepted (`if def f = not foo 1; end`). A nested `not`
20720
+ * is still at the start of the body, so the operand carries the
20721
+ * endless method context on (`def f = not not foo 1`). */
20722
+ uint8_t operand_flags = (flags & PM_PARSE_IN_ENDLESS_DEF)
20723
+ ? (uint8_t) (flags & (PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_IN_ENDLESS_DEF))
20724
+ : PM_PARSE_ACCEPTS_COMMAND_CALL;
20725
+ receiver = parse_expression(parser, PM_BINDING_POWER_NOT, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | operand_flags), PM_ERR_NOT_EXPRESSION, (uint16_t) (depth + 1));
20594
20726
  }
20595
20727
 
20596
20728
  return UP(pm_call_node_not_create(parser, receiver, &message, &arguments));
@@ -20736,7 +20868,7 @@ parse_constant(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t fl
20736
20868
  * fact a method call, not a constant read. */
20737
20869
  if (
20738
20870
  match1(parser, PM_TOKEN_PARENTHESIS_LEFT) ||
20739
- ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_UAMPERSAND, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR))) ||
20871
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type)) ||
20740
20872
  (pm_accepts_block_stack_p(parser) && match1(parser, PM_TOKEN_KEYWORD_DO)) ||
20741
20873
  match1(parser, PM_TOKEN_BRACE_LEFT)
20742
20874
  ) {
@@ -20996,7 +21128,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u
20996
21128
  case PM_TOKEN_KEYWORD_CLASS:
20997
21129
  return parse_class(parser, flags, depth);
20998
21130
  case PM_TOKEN_KEYWORD_DEF:
20999
- return parse_def(parser, binding_power, flags, depth);
21131
+ return parse_def(parser, flags, depth);
21000
21132
  case PM_TOKEN_KEYWORD_DEFINED:
21001
21133
  return UP(parse_defined(parser, flags, depth));
21002
21134
  case PM_TOKEN_KEYWORD_END_UPCASE:
@@ -21138,6 +21270,18 @@ parse_rescue_modifier_terminator(pm_parser_t *parser, uint8_t flags, uint16_t de
21138
21270
  }
21139
21271
  }
21140
21272
 
21273
+ /**
21274
+ * A command is a complete value of a write: it cannot begin a multi-value
21275
+ * right-hand side (`x = Foo::Bar { }, y`), nor stand as the first element of
21276
+ * one when nested in another write (`x = y = def f = foo 1 rescue z, w`).
21277
+ */
21278
+ static void
21279
+ parse_write_value_comma_check(pm_parser_t *parser, const pm_node_t *value) {
21280
+ if (match1(parser, PM_TOKEN_COMMA) && pm_command_before_comma_p(parser, value)) {
21281
+ pm_parser_err_current(parser, PM_ERR_INVALID_COMMA);
21282
+ }
21283
+ }
21284
+
21141
21285
  /**
21142
21286
  * Parse a value that is going to be written to some kind of variable or method
21143
21287
  * call. We need to handle this separately because the rescue modifier is
@@ -21149,7 +21293,21 @@ parse_rescue_modifier_terminator(pm_parser_t *parser, uint8_t flags, uint16_t de
21149
21293
  */
21150
21294
  static pm_node_t *
21151
21295
  parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, uint8_t flags, pm_diagnostic_id_t diag_id, uint16_t depth) {
21152
- pm_node_t *value = parse_value_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? (flags & PM_PARSE_ACCEPTS_COMMAND_CALL) : (previous_binding_power < PM_BINDING_POWER_MATCH ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0))), diag_id, (uint16_t) (depth + 1));
21296
+ /* Where an operator write accepts a command call it also accepts an
21297
+ * endless method with a command body (`x += def f = foo 1`). */
21298
+ uint8_t command_flags = (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT)
21299
+ ? (uint8_t) (flags & PM_PARSE_ACCEPTS_COMMAND_CALL)
21300
+ : (previous_binding_power < PM_BINDING_POWER_MATCH ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0);
21301
+ if (command_flags) command_flags |= PM_PARSE_ACCEPTS_COMMAND_DEF;
21302
+
21303
+ pm_node_t *value = parse_value_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | command_flags), diag_id, (uint16_t) (depth + 1));
21304
+
21305
+ /* An operator write never takes a multi-value right-hand side, so a comma
21306
+ * after a statement-level one is already reported by the statement that
21307
+ * contains it. */
21308
+ if (previous_binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) {
21309
+ parse_write_value_comma_check(parser, value);
21310
+ }
21153
21311
 
21154
21312
  // Assignments whose value is a command call (e.g., a = b c) can only
21155
21313
  // be followed by modifiers (if/unless/while/until/rescue) and not by
@@ -21253,12 +21411,21 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
21253
21411
  ? (uint8_t) (flags & PM_PARSE_ACCEPTS_COMMAND_CALL)
21254
21412
  : ((previous_binding_power < PM_BINDING_POWER_MODIFIER || statement_level) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0);
21255
21413
 
21414
+ /* Where a single-target write accepts a command call it also accepts an
21415
+ * endless method with a command body (`x = def f = foo 1`), but a
21416
+ * multi-write accepts only the command call. */
21417
+ if (command_call_flag && binding_power != PM_BINDING_POWER_MULTI_ASSIGNMENT + 1) {
21418
+ command_call_flag |= PM_PARSE_ACCEPTS_COMMAND_DEF;
21419
+ }
21420
+
21256
21421
  pm_node_t *value = parse_starred_expression(parser, binding_power, (uint8_t) ((flags & PM_PARSE_ACCEPTS_DO_BLOCK) | command_call_flag), diag_id, (uint16_t) (depth + 1));
21257
21422
  if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE);
21258
21423
 
21259
21424
  parse_assignment_value_local(parser, value);
21260
21425
  bool single_value = true;
21261
21426
 
21427
+ parse_write_value_comma_check(parser, value);
21428
+
21262
21429
  // Block calls (command call + do block, e.g., `foo bar do end`) cannot
21263
21430
  // be followed by a comma to form a multi-value RHS because each element
21264
21431
  // of a multi-value assignment must be an `arg`, not a `block_call`.
@@ -21360,7 +21527,7 @@ parse_rescue_modifier_value(pm_parser_t *parser, uint8_t flags, bool statement,
21360
21527
  // splat right-hand side (`b = c, d` / `b = *c`); a comma _before_ an
21361
21528
  // `=`, or a parenthesized target list (`(b, c), d = 1`), instead
21362
21529
  // promotes to a multiple assignment target list below.
21363
- value = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, flags | PM_PARSE_ACCEPTS_STATEMENT, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1));
21530
+ value = parse_expression(parser, pm_binding_powers[PM_TOKEN_KEYWORD_RESCUE_MODIFIER].right, flags | PM_PARSE_ACCEPTS_STATEMENT | PM_PARSE_ACCEPTS_COMMAND_DEF, PM_ERR_RESCUE_MODIFIER_VALUE, (uint16_t) (depth + 1));
21364
21531
  multiple = match1(parser, PM_TOKEN_COMMA) || PM_NODE_TYPE_P(value, PM_MULTI_TARGET_NODE);
21365
21532
  }
21366
21533
 
@@ -21630,7 +21797,7 @@ parse_regular_expression_named_capture(pm_parser_t *parser, const pm_string_t *c
21630
21797
  * Add this name to the set of constants if it is valid, not duplicated,
21631
21798
  * and not a keyword.
21632
21799
  */
21633
- if (name != 0 && pm_constant_id_set_insert(parser->arena, names, name)) {
21800
+ if (name != 0 && pm_constant_id_set_insert(&parser->scratch_arena, names, name)) {
21634
21801
 
21635
21802
  int depth;
21636
21803
  if ((depth = pm_parser_local_depth_constant_id(parser, name)) == -1) {
@@ -21657,7 +21824,7 @@ parse_regular_expression_named_capture(pm_parser_t *parser, const pm_string_t *c
21657
21824
  pm_token_t token = { .type = 0, .start = start, .end = end };
21658
21825
  pm_location_t token_loc = TOK2LOC(parser, &token);
21659
21826
  pm_node_t *target = UP(pm_local_variable_target_node_create(parser, &token_loc, name, depth == -1 ? 0 : (uint32_t) depth));
21660
- pm_node_list_append(parser->arena, &name_data->match->targets, target);
21827
+ pm_node_list_append_inline(parser->arena, &name_data->match->targets, target);
21661
21828
  }
21662
21829
 
21663
21830
  pm_buffer_cleanup(&unescaped);
@@ -21735,7 +21902,7 @@ parse_constant_path_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power
21735
21902
 
21736
21903
  if (
21737
21904
  (parser->current.type == PM_TOKEN_PARENTHESIS_LEFT) ||
21738
- ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_UAMPERSAND, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR)))
21905
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type))
21739
21906
  ) {
21740
21907
  /* If we have a constant immediately following a '::' operator,
21741
21908
  * then this can either be a constant path or a method call,
@@ -21826,9 +21993,7 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21826
21993
  }
21827
21994
 
21828
21995
  if (!interpolated && total_length > 0) {
21829
- void *memory = xmalloc(total_length);
21830
- if (!memory) abort();
21831
-
21996
+ uint8_t *memory = (uint8_t *) pm_arena_alloc(&parser->scratch_arena, total_length, PRISM_ALIGNOF(uint8_t));
21832
21997
  uint8_t *cursor = memory;
21833
21998
  PM_NODE_LIST_FOREACH(parts, index, part) {
21834
21999
  pm_string_t *unescaped = &((pm_string_node_t *) part)->unescaped;
@@ -21838,11 +22003,9 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21838
22003
  cursor += length;
21839
22004
  }
21840
22005
 
21841
- pm_string_t owned;
21842
- pm_string_owned_init(&owned, (uint8_t *) memory, total_length);
21843
-
21844
- result = parse_interpolated_regular_expression_named_captures(parser, &owned, call, PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED));
21845
- pm_string_cleanup(&owned);
22006
+ pm_string_t content;
22007
+ pm_string_init(&content, memory, total_length);
22008
+ result = parse_interpolated_regular_expression_named_captures(parser, &content, call, PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED));
21846
22009
  }
21847
22010
  } else if (PM_NODE_TYPE_P(node, PM_REGULAR_EXPRESSION_NODE)) {
21848
22011
  /* If we have a regular expression node, then we can parse the named
@@ -21852,7 +22015,7 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21852
22015
  .call = call,
21853
22016
  .match = NULL,
21854
22017
  .names = { 0 },
21855
- .shared = regexp->unescaped.type == PM_STRING_SHARED,
22018
+ .shared = pm_string_source(&regexp->unescaped) == parser->start + regexp->content_loc.start,
21856
22019
  };
21857
22020
 
21858
22021
  pm_node_flag_set(UP(regexp), pm_regexp_parse(parser, regexp, parse_regular_expression_named_capture, &name_data));
@@ -22709,7 +22872,7 @@ parse_expression_terminator(pm_parser_t *parser, pm_node_t *node) {
22709
22872
  return left > PM_BINDING_POWER_COMPOSITION && left < PM_BINDING_POWER_CALL && pm_block_call_p(node);
22710
22873
  case PM_DEF_NODE:
22711
22874
  // An endless method whose body is a command-style call (e.g.,
22712
- // `def f = foo bar`) is a command assignment and can only be
22875
+ // `def f = foo bar`) is a command write and can only be
22713
22876
  // followed by modifiers.
22714
22877
  return left > PM_BINDING_POWER_MODIFIER && pm_command_call_value_p(parser, node);
22715
22878
  case PM_RESCUE_MODIFIER_NODE:
@@ -22864,7 +23027,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
22864
23027
  cast->block != NULL && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)
22865
23028
  )
22866
23029
  ) {
22867
- flags &= (uint8_t) ~PM_PARSE_ACCEPTS_COMMAND_CALL;
23030
+ flags &= (uint8_t) ~(PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF);
22868
23031
  }
22869
23032
  break;
22870
23033
  }
@@ -22872,7 +23035,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
22872
23035
  case PM_CONSTANT_PATH_NODE:
22873
23036
  break;
22874
23037
  default:
22875
- flags &= (uint8_t) ~PM_PARSE_ACCEPTS_COMMAND_CALL;
23038
+ flags &= (uint8_t) ~(PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF);
22876
23039
  break;
22877
23040
  }
22878
23041
  }
@@ -23091,6 +23254,16 @@ pm_parser_init_shebang(pm_parser_t *parser, const pm_options_t *options, const c
23091
23254
  parser->frozen_string_literal = next_options.frozen_string_literal;
23092
23255
  }
23093
23256
 
23257
+ /**
23258
+ * The smallest capacity the constant pool starts with, whatever the source
23259
+ * size suggests. An embedder that interns a fixed set of names into the pool
23260
+ * at every parse (a symbol table) can raise it to that set's size, so the
23261
+ * pool does not grow to hold them and leave each outgrown array in the arena.
23262
+ */
23263
+ #ifndef PM_CONSTANT_POOL_MIN_CAPACITY
23264
+ #define PM_CONSTANT_POOL_MIN_CAPACITY 4
23265
+ #endif
23266
+
23094
23267
  /**
23095
23268
  * Initialize a parser with the given start and end pointers.
23096
23269
  */
@@ -23110,9 +23283,8 @@ pm_parser_init(pm_arena_t *arena, pm_parser_t *parser, const uint8_t *source, si
23110
23283
  .do_loop_stack = 0,
23111
23284
  .accepts_block_stack = 0,
23112
23285
  .lex_modes = {
23113
- .index = 0,
23114
- .stack = {{ .mode = PM_LEX_DEFAULT }},
23115
- .current = &parser->lex_modes.stack[0],
23286
+ .current = &parser->lex_modes.base,
23287
+ .base = { .mode = PM_LEX_DEFAULT },
23116
23288
  },
23117
23289
  .start = source,
23118
23290
  .end = source + size,
@@ -23169,7 +23341,8 @@ pm_parser_init(pm_arena_t *arena, pm_parser_t *parser, const uint8_t *source, si
23169
23341
  * frequency. Resizes are cheap with arena allocation, so we lean toward
23170
23342
  * under-estimating. */
23171
23343
  uint32_t constant_size = ((uint32_t) size) / 120;
23172
- pm_constant_pool_init(&parser->metadata_arena, &parser->constant_pool, constant_size < 4 ? 4 : constant_size);
23344
+ if (constant_size < PM_CONSTANT_POOL_MIN_CAPACITY) constant_size = PM_CONSTANT_POOL_MIN_CAPACITY;
23345
+ pm_constant_pool_init(&parser->metadata_arena, &parser->constant_pool, constant_size);
23173
23346
 
23174
23347
  /* Initialize the line offset list. Similar to the constant pool, we are
23175
23348
  * going to estimate the number of newlines that we will need based on the
@@ -23378,20 +23551,8 @@ pm_parser_new(pm_arena_t *arena, const uint8_t *source, size_t size, const pm_op
23378
23551
  */
23379
23552
  void
23380
23553
  pm_parser_cleanup(pm_parser_t *parser) {
23381
- pm_string_cleanup(&parser->filepath);
23382
23554
  pm_arena_cleanup(&parser->metadata_arena);
23383
-
23384
- while (parser->current_scope != NULL) {
23385
- // Normally, popping the scope doesn't free the locals since it is
23386
- // assumed that ownership has transferred to the AST. However if we have
23387
- // scopes while we're freeing the parser, it's likely they came from
23388
- // eval scopes and we need to free them explicitly here.
23389
- pm_parser_scope_pop(parser);
23390
- }
23391
-
23392
- while (parser->lex_modes.index >= PM_LEX_STACK_SIZE) {
23393
- lex_mode_pop(parser);
23394
- }
23555
+ pm_arena_cleanup(&parser->scratch_arena);
23395
23556
  }
23396
23557
 
23397
23558
  /**
@@ -23573,6 +23734,7 @@ pm_parse(pm_parser_t *parser) {
23573
23734
  pm_node_t *node = parse_program(parser);
23574
23735
  pm_parse_continuable(parser);
23575
23736
  if (parser->attach_comments) pm_comments_attach(parser, node);
23737
+ pm_arena_cleanup(&parser->scratch_arena);
23576
23738
  return node;
23577
23739
  }
23578
23740
 
@@ -23728,6 +23890,7 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s
23728
23890
  pm_serialize_header(buffer);
23729
23891
  pm_serialize_encoding(parser.encoding, buffer);
23730
23892
  pm_buffer_append_varsint(buffer, parser.start_line);
23893
+ pm_buffer_append_uint64(buffer, pm_parser_checksum(&parser));
23731
23894
  pm_serialize_line_offset_list(&parser.line_offsets, buffer);
23732
23895
  pm_serialize_comment_list(&parser.comment_list, buffer);
23733
23896