prism 2.0.0.rc1-x86_64-linux-gnu → 2.0.0-x86_64-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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -2
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/LICENSE.md +1 -1
  5. data/docs/serialization.md +20 -6
  6. data/ext/prism/api_node.c +81 -81
  7. data/ext/prism/extension.c +54 -69
  8. data/ext/prism/extension.h +1 -1
  9. data/include/prism/arena.h +8 -0
  10. data/include/prism/arena.hpp +42 -0
  11. data/include/prism/ast.h +11 -1
  12. data/include/prism/ast.hpp +641 -0
  13. data/include/prism/buffer.h +8 -0
  14. data/include/prism/buffer.hpp +55 -0
  15. data/include/prism/comments.h +17 -0
  16. data/include/prism/compiler/accel.h +3 -1
  17. data/include/prism/compiler/branch.h +26 -0
  18. data/include/prism/compiler/bswap.h +46 -0
  19. data/include/prism/compiler/cplusplus.hpp +16 -0
  20. data/include/prism/compiler/endianness.h +34 -0
  21. data/include/prism/compiler/exceptions.hpp +17 -0
  22. data/include/prism/compiler/format.h +2 -0
  23. data/include/prism/compiler/lifetimebound.hpp +25 -0
  24. data/include/prism/constant_pool.h +25 -0
  25. data/include/prism/diagnostic.h +18 -0
  26. data/include/prism/errors_format.h +8 -0
  27. data/include/prism/internal/buffer.h +3 -0
  28. data/include/prism/internal/node.h +8 -4
  29. data/include/prism/internal/parser.h +20 -0
  30. data/include/prism/internal/stringy.h +1 -6
  31. data/include/prism/internal/xxh3.h +16 -0
  32. data/include/prism/json.h +8 -0
  33. data/include/prism/line_offset_list.h +8 -0
  34. data/include/prism/magic_comments.h +18 -0
  35. data/include/prism/node.h +55 -10
  36. data/include/prism/node.hpp +272 -0
  37. data/include/prism/node_type.hpp +38 -0
  38. data/include/prism/options.h +10 -1
  39. data/include/prism/options.hpp +43 -0
  40. data/include/prism/parser.h +93 -64
  41. data/include/prism/parser.hpp +274 -0
  42. data/include/prism/prettyprint.h +8 -0
  43. data/include/prism/serialize.h +8 -0
  44. data/include/prism/source.h +8 -0
  45. data/include/prism/stream.h +8 -0
  46. data/include/prism/string_query.h +19 -0
  47. data/include/prism/stringy.h +16 -27
  48. data/include/prism/stringy.hpp +31 -0
  49. data/include/prism/version.h +9 -1
  50. data/include/prism.h +0 -8
  51. data/include/prism.hpp +24 -0
  52. data/lib/prism/3.3/prism.so +0 -0
  53. data/lib/prism/3.4/prism.so +0 -0
  54. data/lib/prism/4.0/prism.so +0 -0
  55. data/lib/prism/ffi.rb +6 -0
  56. data/lib/prism/node.rb +736 -0
  57. data/lib/prism/parse_result.rb +23 -8
  58. data/lib/prism/serialize.rb +2567 -3286
  59. data/lib/prism/string_query.rb +8 -0
  60. data/prism.gemspec +21 -4
  61. data/rbi/generated/prism/node.rbi +2430 -968
  62. data/rbi/generated/prism/parse_result.rbi +16 -4
  63. data/rbi/generated/prism/serialize.rbi +8 -4
  64. data/rbi/generated/prism/string_query.rbi +7 -0
  65. data/sig/generated/prism/node.rbs +2439 -967
  66. data/sig/generated/prism/parse_result.rbs +16 -4
  67. data/sig/generated/prism/serialize.rbs +9 -4
  68. data/sig/generated/prism/string_query.rbs +7 -0
  69. data/src/buffer.c +9 -0
  70. data/src/comments.c +2 -2
  71. data/src/diagnostic.c +8 -0
  72. data/src/node.c +216 -368
  73. data/src/options.c +7 -13
  74. data/src/parser.c +70 -48
  75. data/src/prism.c +581 -419
  76. data/src/regexp.c +0 -1
  77. data/src/serialize.c +2301 -1950
  78. data/src/source.c +8 -2
  79. data/src/string_query.c +14 -0
  80. data/src/stringy.c +3 -33
  81. data/src/xxh3.c +292 -0
  82. 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
  */
@@ -700,17 +683,38 @@ pm_parser_err_heredoc_term(pm_parser_t *parser, const uint8_t *ident_start, size
700
683
  /* Scope-related functions */
701
684
  /******************************************************************************/
702
685
 
686
+ /**
687
+ * The largest locals table that a popped scope hands to the next pushed scope.
688
+ */
689
+ #define PM_LOCALS_REUSE_CAPACITY 16
690
+
703
691
  /**
704
692
  * Allocate and initialize a new scope. Push it onto the scope stack.
705
693
  */
706
- static bool
694
+ static void
707
695
  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;
696
+ pm_scope_t *scope = parser->free_scopes;
697
+ pm_locals_t locals = { 0 };
698
+
699
+ if (scope != NULL) {
700
+ parser->free_scopes = scope->previous;
701
+
702
+ /* Reuse the locals table of the popped scope when it is small, so that
703
+ * most scopes allocate no locals storage at all. Large tables are
704
+ * dropped instead, because every scope that reuses a table pays to
705
+ * clear it and to scan its full capacity when the locals are ordered. */
706
+ if (scope->locals.capacity > 0 && scope->locals.capacity <= PM_LOCALS_REUSE_CAPACITY) {
707
+ locals.capacity = scope->locals.capacity;
708
+ locals.locals = scope->locals.locals;
709
+ memset(locals.locals, 0, locals.capacity * sizeof(pm_local_t));
710
+ }
711
+ } else {
712
+ scope = (pm_scope_t *) pm_arena_alloc(&parser->metadata_arena, sizeof(pm_scope_t), PRISM_ALIGNOF(pm_scope_t));
713
+ }
710
714
 
711
715
  *scope = (pm_scope_t) {
712
716
  .previous = parser->current_scope,
713
- .locals = { 0 },
717
+ .locals = locals,
714
718
  .parameters = PM_SCOPE_PARAMETERS_NONE,
715
719
  .implicit_parameters = { 0 },
716
720
  .shareable_constant = parser->current_scope == NULL ? PM_SCOPE_SHAREABLE_CONSTANT_NONE : parser->current_scope->shareable_constant,
@@ -718,7 +722,6 @@ pm_parser_scope_push(pm_parser_t *parser, bool closed) {
718
722
  };
719
723
 
720
724
  parser->current_scope = scope;
721
- return true;
722
725
  }
723
726
 
724
727
  /**
@@ -876,24 +879,16 @@ pm_parser_scope_shareable_constant_set(pm_parser_t *parser, pm_shareable_constan
876
879
  */
877
880
  #define PM_LOCALS_HASH_THRESHOLD 5
878
881
 
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
882
  /**
887
883
  * Resize the locals list to be twice its current size. If the next capacity is
888
884
  * above the threshold for switching to a hash, then we'll switch to a hash.
889
885
  */
890
886
  static void
891
- pm_locals_resize(pm_locals_t *locals) {
887
+ pm_locals_resize(pm_arena_t *arena, pm_locals_t *locals) {
892
888
  uint32_t next_capacity = locals->capacity == 0 ? 4 : (locals->capacity * 2);
893
889
  assert(next_capacity > locals->capacity);
894
890
 
895
- pm_local_t *next_locals = xcalloc(next_capacity, sizeof(pm_local_t));
896
- if (next_locals == NULL) abort();
891
+ pm_local_t *next_locals = (pm_local_t *) pm_arena_zalloc(arena, next_capacity * sizeof(pm_local_t), PRISM_ALIGNOF(pm_local_t));
897
892
 
898
893
  if (next_capacity < PM_LOCALS_HASH_THRESHOLD) {
899
894
  if (locals->size > 0) {
@@ -918,7 +913,6 @@ pm_locals_resize(pm_locals_t *locals) {
918
913
  }
919
914
  }
920
915
 
921
- pm_locals_free(locals);
922
916
  locals->locals = next_locals;
923
917
  locals->capacity = next_capacity;
924
918
  }
@@ -939,9 +933,9 @@ pm_locals_resize(pm_locals_t *locals) {
939
933
  * @return True if the local was added, and false if the local already exists.
940
934
  */
941
935
  static bool
942
- pm_locals_write(pm_locals_t *locals, pm_constant_id_t name, uint32_t start, uint32_t length, uint32_t reads) {
936
+ 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
937
  if (locals->size >= (locals->capacity / 4 * 3)) {
944
- pm_locals_resize(locals);
938
+ pm_locals_resize(arena, locals);
945
939
  }
946
940
 
947
941
  locals->bloom |= (1u << (name & 31));
@@ -2441,7 +2435,7 @@ pm_arguments_node_arguments_append(pm_arena_t *arena, pm_arguments_node_t *node,
2441
2435
  PM_NODE_LENGTH_SET_NODE(node, argument);
2442
2436
  }
2443
2437
 
2444
- pm_node_list_append(arena, &node->arguments, argument);
2438
+ pm_node_list_append_inline(arena, &node->arguments, argument);
2445
2439
 
2446
2440
  if (PM_NODE_TYPE_P(argument, PM_SPLAT_NODE)) {
2447
2441
  if (PM_NODE_FLAG_P(node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) {
@@ -2489,7 +2483,7 @@ pm_array_node_elements_append(pm_arena_t *arena, pm_array_node_t *node, pm_node_
2489
2483
  PM_NODE_START_SET_NODE(node, element);
2490
2484
  }
2491
2485
 
2492
- pm_node_list_append(arena, &node->elements, element);
2486
+ pm_node_list_append_inline(arena, &node->elements, element);
2493
2487
  PM_NODE_LENGTH_SET_NODE(node, element);
2494
2488
 
2495
2489
  // If the element is not a static literal, then the array is not a static
@@ -2542,9 +2536,9 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node
2542
2536
  node->rest = child;
2543
2537
  found_rest = true;
2544
2538
  } else if (found_rest) {
2545
- pm_node_list_append(parser->arena, &node->posts, child);
2539
+ pm_node_list_append_inline(parser->arena, &node->posts, child);
2546
2540
  } else {
2547
- pm_node_list_append(parser->arena, &node->requireds, child);
2541
+ pm_node_list_append_inline(parser->arena, &node->requireds, child);
2548
2542
  }
2549
2543
  }
2550
2544
 
@@ -2612,7 +2606,7 @@ pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *openin
2612
2606
 
2613
2607
  static PRISM_INLINE void
2614
2608
  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);
2609
+ pm_node_list_append_inline(arena, &node->requireds, inner);
2616
2610
  }
2617
2611
 
2618
2612
  /**
@@ -2876,7 +2870,7 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name)
2876
2870
  */
2877
2871
  static void
2878
2872
  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));
2873
+ pm_node_list_append_inline(arena, &node->locals, UP(local));
2880
2874
 
2881
2875
  if (PM_NODE_LENGTH(node) == 0) {
2882
2876
  PM_NODE_START_SET_NODE(node, local);
@@ -3529,7 +3523,7 @@ static void
3529
3523
  pm_case_node_condition_append(pm_arena_t *arena, pm_case_node_t *node, pm_node_t *condition) {
3530
3524
  assert(PM_NODE_TYPE_P(condition, PM_WHEN_NODE));
3531
3525
 
3532
- pm_node_list_append(arena, &node->conditions, condition);
3526
+ pm_node_list_append_inline(arena, &node->conditions, condition);
3533
3527
  PM_NODE_LENGTH_SET_NODE(node, condition);
3534
3528
  }
3535
3529
 
@@ -3576,7 +3570,7 @@ static void
3576
3570
  pm_case_match_node_condition_append(pm_arena_t *arena, pm_case_match_node_t *node, pm_node_t *condition) {
3577
3571
  assert(PM_NODE_TYPE_P(condition, PM_IN_NODE));
3578
3572
 
3579
- pm_node_list_append(arena, &node->conditions, condition);
3573
+ pm_node_list_append_inline(arena, &node->conditions, condition);
3580
3574
  PM_NODE_LENGTH_SET_NODE(node, condition);
3581
3575
  }
3582
3576
 
@@ -4116,7 +4110,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) {
4116
4110
  // much more efficient, as we could instead resize the node list to only point
4117
4111
  // to 1...-1.
4118
4112
  for (size_t index = 1; index < nodes->size - 1; index++) {
4119
- pm_node_list_append(parser->arena, &node->requireds, nodes->nodes[index]);
4113
+ pm_node_list_append_inline(parser->arena, &node->requireds, nodes->nodes[index]);
4120
4114
  }
4121
4115
 
4122
4116
  return node;
@@ -4613,7 +4607,7 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) {
4613
4607
  */
4614
4608
  static PRISM_INLINE void
4615
4609
  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);
4610
+ pm_node_list_append_inline(arena, &hash->elements, element);
4617
4611
 
4618
4612
  bool static_literal = PM_NODE_TYPE_P(element, PM_ASSOC_NODE);
4619
4613
  if (static_literal) {
@@ -5038,7 +5032,7 @@ pm_interpolated_node_append(pm_arena_t *arena, pm_node_t *node, pm_node_list_t *
5038
5032
  break;
5039
5033
  }
5040
5034
 
5041
- pm_node_list_append(arena, parts, part);
5035
+ pm_node_list_append_inline(arena, parts, part);
5042
5036
  }
5043
5037
 
5044
5038
  /**
@@ -5180,7 +5174,7 @@ pm_interpolated_string_node_append(pm_parser_t *parser, pm_interpolated_string_n
5180
5174
  // These will only happen in error cases. But we want to handle it
5181
5175
  // here so that we don't fail the assertion.
5182
5176
  CLEAR_FLAGS(node);
5183
- pm_node_list_append(arena, &node->parts, UP(pm_error_recovery_node_create_unexpected(parser, part)));
5177
+ pm_node_list_append_inline(arena, &node->parts, UP(pm_error_recovery_node_create_unexpected(parser, part)));
5184
5178
  return;
5185
5179
  case PM_ERROR_RECOVERY_NODE:
5186
5180
  CLEAR_FLAGS(node);
@@ -5190,7 +5184,7 @@ pm_interpolated_string_node_append(pm_parser_t *parser, pm_interpolated_string_n
5190
5184
  break;
5191
5185
  }
5192
5186
 
5193
- pm_node_list_append(arena, &node->parts, part);
5187
+ pm_node_list_append_inline(arena, &node->parts, part);
5194
5188
 
5195
5189
  #undef CLEAR_FLAGS
5196
5190
  #undef MUTABLE_FLAGS
@@ -5370,7 +5364,7 @@ pm_keyword_hash_node_elements_append(pm_arena_t *arena, pm_keyword_hash_node_t *
5370
5364
  pm_node_flag_unset(UP(hash), PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS);
5371
5365
  }
5372
5366
 
5373
- pm_node_list_append(arena, &hash->elements, element);
5367
+ pm_node_list_append_inline(arena, &hash->elements, element);
5374
5368
  if (PM_NODE_LENGTH(hash) == 0) {
5375
5369
  PM_NODE_START_SET_NODE(hash, element);
5376
5370
  }
@@ -5718,19 +5712,19 @@ pm_multi_target_node_targets_append(pm_parser_t *parser, pm_multi_target_node_t
5718
5712
  node->rest = target;
5719
5713
  } else {
5720
5714
  pm_parser_err_node(parser, target, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS);
5721
- pm_node_list_append(parser->arena, &node->rights, target);
5715
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5722
5716
  }
5723
5717
  } else if (PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) {
5724
5718
  if (node->rest == NULL) {
5725
5719
  node->rest = target;
5726
5720
  } else {
5727
5721
  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);
5722
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5729
5723
  }
5730
5724
  } else if (node->rest == NULL) {
5731
- pm_node_list_append(parser->arena, &node->lefts, target);
5725
+ pm_node_list_append_inline(parser->arena, &node->lefts, target);
5732
5726
  } else {
5733
- pm_node_list_append(parser->arena, &node->rights, target);
5727
+ pm_node_list_append_inline(parser->arena, &node->rights, target);
5734
5728
  }
5735
5729
 
5736
5730
  if (PM_NODE_LENGTH(node) == 0 || (PM_NODE_START(node) > PM_NODE_START(target))) {
@@ -6005,7 +5999,7 @@ pm_parameters_node_location_set(pm_parameters_node_t *params, pm_node_t *param)
6005
5999
  static void
6006
6000
  pm_parameters_node_requireds_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6007
6001
  pm_parameters_node_location_set(params, param);
6008
- pm_node_list_append(arena, &params->requireds, param);
6002
+ pm_node_list_append_inline(arena, &params->requireds, param);
6009
6003
  }
6010
6004
 
6011
6005
  /**
@@ -6014,7 +6008,7 @@ pm_parameters_node_requireds_append(pm_arena_t *arena, pm_parameters_node_t *par
6014
6008
  static void
6015
6009
  pm_parameters_node_optionals_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_optional_parameter_node_t *param) {
6016
6010
  pm_parameters_node_location_set(params, UP(param));
6017
- pm_node_list_append(arena, &params->optionals, UP(param));
6011
+ pm_node_list_append_inline(arena, &params->optionals, UP(param));
6018
6012
  }
6019
6013
 
6020
6014
  /**
@@ -6023,7 +6017,7 @@ pm_parameters_node_optionals_append(pm_arena_t *arena, pm_parameters_node_t *par
6023
6017
  static void
6024
6018
  pm_parameters_node_posts_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6025
6019
  pm_parameters_node_location_set(params, param);
6026
- pm_node_list_append(arena, &params->posts, param);
6020
+ pm_node_list_append_inline(arena, &params->posts, param);
6027
6021
  }
6028
6022
 
6029
6023
  /**
@@ -6041,7 +6035,7 @@ pm_parameters_node_rest_set(pm_parameters_node_t *params, pm_node_t *param) {
6041
6035
  static void
6042
6036
  pm_parameters_node_keywords_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) {
6043
6037
  pm_parameters_node_location_set(params, param);
6044
- pm_node_list_append(arena, &params->keywords, param);
6038
+ pm_node_list_append_inline(arena, &params->keywords, param);
6045
6039
  }
6046
6040
 
6047
6041
  /**
@@ -6329,7 +6323,7 @@ pm_rescue_node_subsequent_set(pm_rescue_node_t *node, pm_rescue_node_t *subseque
6329
6323
  */
6330
6324
  static void
6331
6325
  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);
6326
+ pm_node_list_append_inline(arena, &node->exceptions, exception);
6333
6327
  PM_NODE_LENGTH_SET_NODE(node, exception);
6334
6328
  }
6335
6329
 
@@ -6562,7 +6556,7 @@ pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node,
6562
6556
  }
6563
6557
  }
6564
6558
 
6565
- pm_node_list_append(parser->arena, &node->body, statement);
6559
+ pm_node_list_append_inline(parser->arena, &node->body, statement);
6566
6560
  if (newline) pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE);
6567
6561
  }
6568
6562
 
@@ -6809,7 +6803,7 @@ pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) {
6809
6803
  ((pm_string_t) { 0 })
6810
6804
  );
6811
6805
 
6812
- pm_string_constant_init(&node->unescaped, content, strlen(content));
6806
+ pm_string_init(&node->unescaped, (const uint8_t *) content, strlen(content));
6813
6807
  return node;
6814
6808
  }
6815
6809
 
@@ -6952,7 +6946,7 @@ pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) {
6952
6946
  static void
6953
6947
  pm_undef_node_append(pm_arena_t *arena, pm_undef_node_t *node, pm_node_t *name) {
6954
6948
  PM_NODE_LENGTH_SET_NODE(node, name);
6955
- pm_node_list_append(arena, &node->names, name);
6949
+ pm_node_list_append_inline(arena, &node->names, name);
6956
6950
  }
6957
6951
 
6958
6952
  /**
@@ -7094,7 +7088,7 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) {
7094
7088
  static void
7095
7089
  pm_when_node_conditions_append(pm_arena_t *arena, pm_when_node_t *node, pm_node_t *condition) {
7096
7090
  PM_NODE_LENGTH_SET_NODE(node, condition);
7097
- pm_node_list_append(arena, &node->conditions, condition);
7091
+ pm_node_list_append_inline(arena, &node->conditions, condition);
7098
7092
  }
7099
7093
 
7100
7094
  /**
@@ -7268,7 +7262,7 @@ pm_parser_local_depth(pm_parser_t *parser, pm_token_t *token) {
7268
7262
  */
7269
7263
  static PRISM_INLINE void
7270
7264
  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);
7265
+ pm_locals_write(&parser->metadata_arena, &parser->current_scope->locals, constant_id, U32(start - parser->start), U32(end - start), reads);
7272
7266
  }
7273
7267
 
7274
7268
  /**
@@ -7351,8 +7345,8 @@ static void
7351
7345
  pm_parser_scope_pop(pm_parser_t *parser) {
7352
7346
  pm_scope_t *scope = parser->current_scope;
7353
7347
  parser->current_scope = scope->previous;
7354
- pm_locals_free(&scope->locals);
7355
- xfree_sized(scope, sizeof(pm_scope_t));
7348
+ scope->previous = parser->free_scopes;
7349
+ parser->free_scopes = scope;
7356
7350
  }
7357
7351
 
7358
7352
  /******************************************************************************/
@@ -7420,8 +7414,7 @@ pm_do_loop_stack_p(pm_parser_t *parser) {
7420
7414
  * lexed: the do-loop stack (so a `do` inside is not read as a `while`/`until`
7421
7415
  * loop body) and the accepts-block stack (so a `do` inside is accepted as a
7422
7416
  * 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:
7417
+ * both. Keep the following in mind before touching either stack:
7425
7418
  *
7426
7419
  * - The LEXER owns the frame for delimiter-bound stuff. Every token that opens
7427
7420
  * a matched pair calls this on the open and `pm_enclosure_frame_pop` on the
@@ -7429,14 +7422,20 @@ pm_do_loop_stack_p(pm_parser_t *parser) {
7429
7422
  * `parser_lex` and the `#{` case in `lex_interpolation`; the pop sites are
7430
7423
  * the matching `)`, `]`, `}` (including `}` as `EMBEXPR_END`).
7431
7424
  * - 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`).
7425
+ * delimiter token to hang it on. The `-> do end` lambda body pushes the full
7426
+ * frame through this helper: the lexer produces the lambda's `do` before
7427
+ * consulting the do-loop stack, so the body can open inside a loop condition
7428
+ * and must reset it. The `do`/`end` forms in `parse_block` push only
7429
+ * `accepts_block`: the lexer produces a block's `do` only when the do-loop
7430
+ * stack is already false, so another false frame would change nothing. Both
7431
+ * pop before consuming `end`.
7434
7432
  * - The `parse_arguments_list` command-args branch juggles ONLY `accepts_block`
7435
7433
  * (never through this helper), because the lexer has pushed a delimiter frame
7436
7434
  * one token early and the command-args frame must be threaded beneath it. Its
7437
7435
  * 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.
7436
+ * Command arguments change which call a `do` block binds to, not whether a
7437
+ * `do` closes a loop condition (`while foo bar do ... end` still loops), so
7438
+ * the do-loop stack is left alone.
7440
7439
  */
7441
7440
  static PRISM_INLINE void
7442
7441
  pm_enclosure_frame_push(pm_parser_t *parser) {
@@ -7676,6 +7675,27 @@ parser_lex_magic_comment_emacs_marker(pm_parser_t *parser, const uint8_t *cursor
7676
7675
  return NULL;
7677
7676
  }
7678
7677
 
7678
+ /**
7679
+ * Returns true if the given magic comment key matches the given name, which is
7680
+ * written in lowercase with underscores and is the same length as the key.
7681
+ * Keys match case-insensitively, and a dash in the key matches an underscore in
7682
+ * the name. Every byte of a name is ASCII, so a byte of a multibyte character
7683
+ * in the key never matches one.
7684
+ */
7685
+ static bool
7686
+ parser_magic_comment_key_p(const uint8_t *key, size_t key_length, const char *name) {
7687
+ assert(name[key_length] == '\0');
7688
+
7689
+ for (size_t index = 0; index < key_length; index++) {
7690
+ uint8_t byte = key[index];
7691
+ if (byte == '-') byte = '_';
7692
+ if ('A' <= byte && byte <= 'Z') byte |= 0x20;
7693
+ if (byte != (uint8_t) name[index]) return false;
7694
+ }
7695
+
7696
+ return true;
7697
+ }
7698
+
7679
7699
  /**
7680
7700
  * Parse the current token on the parser to see if it's a magic comment and
7681
7701
  * potentially perform some action based on that. A regular expression that this
@@ -7767,73 +7787,34 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
7767
7787
  if (cursor != end) return false;
7768
7788
  }
7769
7789
 
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;
7790
+ /* Finally, we can start checking the key against the list of known
7791
+ * magic comment keys, and potentially change state based on that. */
7773
7792
  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
7793
  uint32_t value_length = (uint32_t) (value_end - value_start);
7796
7794
 
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;
7795
+ switch (key_length) {
7796
+ case 6:
7797
+ /* We only want to attempt to compare against encoding comments
7798
+ * if it is the first line in the file (or the second in the
7799
+ * case of a shebang). */
7800
+ if (parser->current.start == parser->encoding_comment_start && !parser->encoding_locked) {
7801
+ if (parser_magic_comment_key_p(key_start, key_length, "coding")) {
7802
+ result = parser_lex_magic_comment_encoding_value(parser, value_start, value_end);
7803
+ }
7828
7804
  }
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 {
7805
+ break;
7806
+ case 8:
7807
+ /* We only want to attempt to compare against encoding comments
7808
+ * if it is the first line in the file (or the second in the
7809
+ * case of a shebang). */
7810
+ if (parser->current.start == parser->encoding_comment_start && !parser->encoding_locked) {
7811
+ if (parser_magic_comment_key_p(key_start, key_length, "encoding")) {
7812
+ result = parser_lex_magic_comment_encoding_value(parser, value_start, value_end);
7813
+ }
7814
+ }
7815
+ break;
7816
+ case 11:
7817
+ if (parser_magic_comment_key_p(key_start, key_length, "warn_indent")) {
7837
7818
  switch (parser_lex_magic_comment_boolean_value(value_start, value_length)) {
7838
7819
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_INVALID:
7839
7820
  PM_PARSER_WARN_TOKEN_FORMAT(
@@ -7841,53 +7822,79 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
7841
7822
  &parser->current,
7842
7823
  PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7843
7824
  (int) key_length,
7844
- (const char *) key_source,
7825
+ "warn_indent",
7845
7826
  (int) value_length,
7846
7827
  (const char *) value_start
7847
7828
  );
7848
7829
  break;
7849
7830
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_FALSE:
7850
- parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED;
7831
+ parser->warn_mismatched_indentation = false;
7851
7832
  break;
7852
7833
  case PM_MAGIC_COMMENT_BOOLEAN_VALUE_TRUE:
7853
- parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED;
7834
+ parser->warn_mismatched_indentation = true;
7854
7835
  break;
7855
7836
  }
7856
7837
  }
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
- );
7838
+ break;
7839
+ case 21:
7840
+ if (parser_magic_comment_key_p(key_start, key_length, "frozen_string_literal")) {
7841
+ /* We only want to handle frozen string literal comments if
7842
+ * it is before any semantic tokens have been seen. */
7843
+ if (semantic_token_seen) {
7844
+ pm_parser_warn_token(parser, &parser->current, PM_WARN_IGNORED_FROZEN_STRING_LITERAL);
7845
+ } else {
7846
+ switch (parser_lex_magic_comment_boolean_value(value_start, value_length)) {
7847
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_INVALID:
7848
+ PM_PARSER_WARN_TOKEN_FORMAT(
7849
+ parser,
7850
+ &parser->current,
7851
+ PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7852
+ (int) key_length,
7853
+ "frozen_string_literal",
7854
+ (int) value_length,
7855
+ (const char *) value_start
7856
+ );
7857
+ break;
7858
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_FALSE:
7859
+ parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED;
7860
+ break;
7861
+ case PM_MAGIC_COMMENT_BOOLEAN_VALUE_TRUE:
7862
+ parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED;
7863
+ break;
7864
+ }
7865
+ }
7883
7866
  }
7884
- }
7867
+ break;
7868
+ case 24:
7869
+ if (parser_magic_comment_key_p(key_start, key_length, "shareable_constant_value")) {
7870
+ const uint8_t *cursor = parser->current.start;
7871
+ while ((cursor > parser->start) && ((cursor[-1] == ' ') || (cursor[-1] == '\t'))) cursor--;
7872
+
7873
+ if (!((cursor == parser->start) || (cursor[-1] == '\n'))) {
7874
+ pm_parser_warn_token(parser, &parser->current, PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE);
7875
+ } else if (value_length == 4 && pm_strncasecmp(value_start, (const uint8_t *) "none", 4) == 0) {
7876
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_NONE);
7877
+ } else if (value_length == 7 && pm_strncasecmp(value_start, (const uint8_t *) "literal", 7) == 0) {
7878
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_LITERAL);
7879
+ } else if (value_length == 23 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_everything", 23) == 0) {
7880
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING);
7881
+ } else if (value_length == 17 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_copy", 17) == 0) {
7882
+ pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY);
7883
+ } else {
7884
+ PM_PARSER_WARN_TOKEN_FORMAT(
7885
+ parser,
7886
+ &parser->current,
7887
+ PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
7888
+ (int) key_length,
7889
+ "shareable_constant_value",
7890
+ (int) value_length,
7891
+ (const char *) value_start
7892
+ );
7893
+ }
7894
+ }
7895
+ break;
7885
7896
  }
7886
7897
 
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
7898
  // Allocate a new magic comment node to append to the parser's list.
7892
7899
  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
7900
  magic_comment->node.next = NULL;
@@ -7986,8 +7993,12 @@ PM_STATIC_ASSERT(__LINE__, PM_CONTEXT_MAXIMUM <= 64, "Expected every context to
7986
7993
 
7987
7994
  static bool
7988
7995
  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;
7996
+ pm_context_node_t *context_node = parser->free_contexts;
7997
+ if (context_node != NULL) {
7998
+ parser->free_contexts = context_node->prev;
7999
+ } else {
8000
+ context_node = (pm_context_node_t *) pm_arena_alloc(&parser->metadata_arena, sizeof(pm_context_node_t), PRISM_ALIGNOF(pm_context_node_t));
8001
+ }
7991
8002
 
7992
8003
  pm_context_node_t *previous = parser->current_context;
7993
8004
  *context_node = (pm_context_node_t) {
@@ -8002,9 +8013,10 @@ context_push(pm_parser_t *parser, pm_context_t context) {
8002
8013
 
8003
8014
  static void
8004
8015
  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;
8016
+ pm_context_node_t *context_node = parser->current_context;
8017
+ parser->current_context = context_node->prev;
8018
+ context_node->prev = parser->free_contexts;
8019
+ parser->free_contexts = context_node;
8008
8020
  }
8009
8021
 
8010
8022
  static bool
@@ -8508,82 +8520,186 @@ lex_global_variable(pm_parser_t *parser) {
8508
8520
  }
8509
8521
 
8510
8522
  /**
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:
8523
+ * A Ruby keyword, along with the lex state that it transitions to and the
8524
+ * token types that it produces.
8525
+ */
8526
+ typedef struct {
8527
+ /* The keyword itself. */
8528
+ const char *name;
8529
+
8530
+ /* The keyword's first, middle, and last bytes and its length, packed by
8531
+ * PM_KEYWORD_PACK. */
8532
+ uint32_t packed;
8533
+
8534
+ /* The state to transition to when the keyword is lexed. */
8535
+ pm_lex_state_t state;
8536
+
8537
+ /* The token type of the keyword. */
8538
+ pm_token_type_t type;
8539
+
8540
+ /* The token type of the keyword in modifier position, or PM_TOKEN_EOF if
8541
+ * the keyword has no modifier form. */
8542
+ pm_token_type_t modifier_type;
8543
+ } pm_keyword_t;
8544
+
8545
+ /**
8546
+ * The length of the longest keyword, __ENCODING__.
8547
+ */
8548
+ #define PM_KEYWORD_LENGTH_MAXIMUM 12
8549
+
8550
+ /**
8551
+ * Pack the first, middle (at index length / 2), and last bytes of an identifier
8552
+ * together with its length. No two keywords pack to the same value.
8553
+ */
8554
+ #define PM_KEYWORD_PACK(first, middle, last, length) \
8555
+ ((uint32_t) (first) | ((uint32_t) (middle) << 8) | ((uint32_t) (last) << 16) | ((uint32_t) (length) << 24))
8556
+
8557
+ /**
8558
+ * The number of bits in a slot index of pm_keywords, which has
8559
+ * 1 << PM_KEYWORD_SLOT_BITS slots.
8560
+ */
8561
+ #define PM_KEYWORD_SLOT_BITS 6
8562
+
8563
+ /**
8564
+ * Return the slot in pm_keywords for a packed identifier. This is
8565
+ * multiplicative hashing: the product is taken modulo 2^32, and the slot is its
8566
+ * top PM_KEYWORD_SLOT_BITS bits. The top bits are used because the low bits of
8567
+ * a product depend only on the low bits of its operands (here, the low bits of
8568
+ * the first byte), while the top bits depend on all of them.
8513
8569
  *
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
- }
8570
+ * The multiplier comes from `bin/prism keywords`, which searches for one that
8571
+ * gives every keyword its own slot, making this a perfect hash over the
8572
+ * keywords. Rerun it whenever the set of keywords changes. The table below is
8573
+ * indexed with this same macro, so if two keywords ever shared a slot, the
8574
+ * compiler would reject the duplicate initializer.
8575
+ */
8576
+ #define PM_KEYWORD_SLOT(packed) ((uint32_t) ((uint32_t) (packed) * 0x75f796ddU) >> (32 - PM_KEYWORD_SLOT_BITS))
8577
+
8578
+ /**
8579
+ * Define the entry in pm_keywords for a keyword, given its first, middle, and
8580
+ * last characters.
8581
+ */
8582
+ #define PM_KEYWORD(name, first, middle, last, state, type, modifier_type) \
8583
+ [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 }
8584
+
8585
+ /**
8586
+ * The keywords, indexed by PM_KEYWORD_SLOT. Unused slots are zeroed, and a
8587
+ * packed value of zero never matches an identifier because its length is at
8588
+ * least one.
8589
+ */
8590
+ static const pm_keyword_t pm_keywords[1 << PM_KEYWORD_SLOT_BITS] = {
8591
+ PM_KEYWORD("do", 'd', 'o', 'o', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_DO, PM_TOKEN_EOF),
8592
+ PM_KEYWORD("if", 'i', 'f', 'f', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IF, PM_TOKEN_KEYWORD_IF_MODIFIER),
8593
+ PM_KEYWORD("in", 'i', 'n', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_IN, PM_TOKEN_EOF),
8594
+ PM_KEYWORD("or", 'o', 'r', 'r', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_OR, PM_TOKEN_EOF),
8595
+ PM_KEYWORD("and", 'a', 'n', 'd', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_AND, PM_TOKEN_EOF),
8596
+ PM_KEYWORD("def", 'd', 'e', 'f', PM_LEX_STATE_FNAME, PM_TOKEN_KEYWORD_DEF, PM_TOKEN_EOF),
8597
+ PM_KEYWORD("end", 'e', 'n', 'd', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END, PM_TOKEN_EOF),
8598
+ PM_KEYWORD("END", 'E', 'N', 'D', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_END_UPCASE, PM_TOKEN_EOF),
8599
+ PM_KEYWORD("for", 'f', 'o', 'r', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_FOR, PM_TOKEN_EOF),
8600
+ PM_KEYWORD("nil", 'n', 'i', 'l', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_NIL, PM_TOKEN_EOF),
8601
+ PM_KEYWORD("not", 'n', 'o', 't', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_NOT, PM_TOKEN_EOF),
8602
+ PM_KEYWORD("case", 'c', 's', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_CASE, PM_TOKEN_EOF),
8603
+ PM_KEYWORD("else", 'e', 's', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_EOF),
8604
+ PM_KEYWORD("next", 'n', 'x', 't', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_NEXT, PM_TOKEN_EOF),
8605
+ PM_KEYWORD("redo", 'r', 'd', 'o', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_REDO, PM_TOKEN_EOF),
8606
+ PM_KEYWORD("self", 's', 'l', 'f', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_SELF, PM_TOKEN_EOF),
8607
+ PM_KEYWORD("then", 't', 'e', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_EOF),
8608
+ PM_KEYWORD("true", 't', 'u', 'e', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_TRUE, PM_TOKEN_EOF),
8609
+ PM_KEYWORD("when", 'w', 'e', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHEN, PM_TOKEN_EOF),
8610
+ PM_KEYWORD("alias", 'a', 'i', 's', PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_ALIAS, PM_TOKEN_EOF),
8611
+ PM_KEYWORD("begin", 'b', 'g', 'n', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_BEGIN, PM_TOKEN_EOF),
8612
+ PM_KEYWORD("BEGIN", 'B', 'G', 'N', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_BEGIN_UPCASE, PM_TOKEN_EOF),
8613
+ PM_KEYWORD("break", 'b', 'e', 'k', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_BREAK, PM_TOKEN_EOF),
8614
+ PM_KEYWORD("class", 'c', 'a', 's', PM_LEX_STATE_CLASS, PM_TOKEN_KEYWORD_CLASS, PM_TOKEN_EOF),
8615
+ PM_KEYWORD("elsif", 'e', 's', 'f', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ELSIF, PM_TOKEN_EOF),
8616
+ PM_KEYWORD("false", 'f', 'l', 'e', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_FALSE, PM_TOKEN_EOF),
8617
+ PM_KEYWORD("retry", 'r', 't', 'y', PM_LEX_STATE_END, PM_TOKEN_KEYWORD_RETRY, PM_TOKEN_EOF),
8618
+ PM_KEYWORD("super", 's', 'p', 'r', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_SUPER, PM_TOKEN_EOF),
8619
+ PM_KEYWORD("undef", 'u', 'd', 'f', PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM, PM_TOKEN_KEYWORD_UNDEF, PM_TOKEN_EOF),
8620
+ PM_KEYWORD("until", 'u', 't', 'l', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNTIL, PM_TOKEN_KEYWORD_UNTIL_MODIFIER),
8621
+ PM_KEYWORD("while", 'w', 'i', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_WHILE, PM_TOKEN_KEYWORD_WHILE_MODIFIER),
8622
+ PM_KEYWORD("yield", 'y', 'e', 'd', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_YIELD, PM_TOKEN_EOF),
8623
+ PM_KEYWORD("ensure", 'e', 'u', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_EOF),
8624
+ PM_KEYWORD("module", 'm', 'u', 'e', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_MODULE, PM_TOKEN_EOF),
8625
+ PM_KEYWORD("rescue", 'r', 'c', 'e', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_RESCUE_MODIFIER),
8626
+ PM_KEYWORD("return", 'r', 'u', 'n', PM_LEX_STATE_MID, PM_TOKEN_KEYWORD_RETURN, PM_TOKEN_EOF),
8627
+ PM_KEYWORD("unless", 'u', 'e', 's', PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_UNLESS, PM_TOKEN_KEYWORD_UNLESS_MODIFIER),
8628
+ PM_KEYWORD("defined?", 'd', 'n', '?', PM_LEX_STATE_ARG, PM_TOKEN_KEYWORD_DEFINED, PM_TOKEN_EOF),
8629
+ PM_KEYWORD("__LINE__", '_', 'N', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___LINE__, PM_TOKEN_EOF),
8630
+ PM_KEYWORD("__FILE__", '_', 'L', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___FILE__, PM_TOKEN_EOF),
8631
+ PM_KEYWORD("__ENCODING__", '_', 'D', '_', PM_LEX_STATE_END, PM_TOKEN_KEYWORD___ENCODING__, PM_TOKEN_EOF)
8632
+ };
8534
8633
 
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
- }
8634
+ #undef PM_KEYWORD
8540
8635
 
8541
- return type;
8542
- }
8636
+ /**
8637
+ * Return the keyword that the identifier corresponds to or NULL if it is not in
8638
+ * the table.
8639
+ */
8640
+ static PRISM_INLINE const pm_keyword_t *
8641
+ pm_keyword_find(const uint8_t *start, const uint8_t *end) {
8642
+ ptrdiff_t width = end - start;
8643
+ if (width < 2 || width > PM_KEYWORD_LENGTH_MAXIMUM) return NULL;
8543
8644
 
8544
- return PM_TOKEN_EOF;
8645
+ size_t uwidth = (size_t) width;
8646
+ uint32_t packed = PM_KEYWORD_PACK(start[0], start[uwidth >> 1], start[uwidth - 1], uwidth);
8647
+
8648
+ const pm_keyword_t *keyword = &pm_keywords[PM_KEYWORD_SLOT(packed)];
8649
+ return (keyword->packed == packed && memcmp(start + 1, keyword->name + 1, uwidth - 2) == 0) ? keyword : NULL;
8650
+ }
8651
+
8652
+ /**
8653
+ * Returns true if the bytes between start and end spell a keyword, using the
8654
+ * same lookup that the lexer does.
8655
+ */
8656
+ bool
8657
+ pm_keyword_p(const uint8_t *start, const uint8_t *end) {
8658
+ return pm_keyword_find(start, end) != NULL;
8545
8659
  }
8546
8660
 
8547
8661
  static pm_token_type_t
8548
8662
  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;
8663
+ /* Lex as far as we can into the current identifier. */
8551
8664
  const uint8_t *end = parser->end;
8552
8665
  const uint8_t *current_start = parser->current.start;
8553
8666
  const uint8_t *current_end = parser->current.end;
8554
8667
  bool encoding_changed = parser->encoding_changed;
8555
8668
 
8556
8669
  if (encoding_changed) {
8670
+ size_t width;
8557
8671
  while ((width = char_is_identifier(parser, current_end, end - current_end)) > 0) {
8558
8672
  current_end += width;
8559
8673
  }
8560
8674
  } else {
8561
- // Fast path: scan ASCII identifier bytes using wide operations.
8675
+ /* Fast path: scan ASCII identifier bytes using wide operations. */
8562
8676
  current_end += scan_identifier_ascii(current_end, end);
8563
8677
 
8564
- // Byte-at-a-time fallback for the tail and any UTF-8 sequences.
8678
+ /* Byte-at-a-time fallback for the tail and any UTF-8 sequences. */
8679
+ size_t width;
8565
8680
  while ((width = char_is_identifier_utf8(current_end, end - current_end)) > 0) {
8566
8681
  current_end += width;
8567
8682
  }
8568
8683
  }
8684
+
8569
8685
  parser->current.end = current_end;
8570
8686
 
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);
8687
+ /* Whether the identifier ends in ! or ?, which makes it a method name
8688
+ * unless it spells a keyword (defined?). */
8689
+ bool method_name = false;
8574
8690
 
8575
8691
  if (current_end < end) {
8576
8692
  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++;
8693
+ /* Extend the identifier by the ! or ?, so that the keyword lookup
8694
+ * below sees the whole name. */
8695
+ current_end++;
8580
8696
 
8581
8697
  if (
8582
8698
  ((lex_state_p(parser, PM_LEX_STATE_LABEL | PM_LEX_STATE_ENDFN) && !previous_command_start) || lex_state_arg_p(parser)) &&
8583
8699
  (peek(parser) == ':') && (peek_offset(parser, 1) != ':')
8584
8700
  ) {
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.
8701
+ /* If we are in a position where we can accept a : at the end of
8702
+ * an identifier, then we will optionally accept it. */
8587
8703
  lex_state_set(parser, PM_LEX_STATE_ARG | PM_LEX_STATE_LABELED);
8588
8704
  (void) match(parser, ':');
8589
8705
 
@@ -8593,27 +8709,17 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) {
8593
8709
  return PM_TOKEN_LABEL;
8594
8710
  }
8595
8711
 
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.
8712
+ method_name = true;
8713
+ } 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, '=')) {
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. */
8608
8716
  return PM_TOKEN_IDENTIFIER;
8609
- }
8610
-
8611
- if (
8717
+ } else if (
8612
8718
  ((lex_state_p(parser, PM_LEX_STATE_LABEL | PM_LEX_STATE_ENDFN) && !previous_command_start) || lex_state_arg_p(parser)) &&
8613
8719
  peek(parser) == ':' && peek_offset(parser, 1) != ':'
8614
8720
  ) {
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.
8721
+ /* If we are in a position where we can accept a : at the end of an
8722
+ * identifier, then we will optionally accept it. */
8617
8723
  lex_state_set(parser, PM_LEX_STATE_ARG | PM_LEX_STATE_LABELED);
8618
8724
  (void) match(parser, ':');
8619
8725
 
@@ -8625,106 +8731,66 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) {
8625
8731
  }
8626
8732
 
8627
8733
  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. */
8734
+ /* The lex state from before the keyword changes it, which decides
8735
+ * whether the keyword is in its modifier form and whether `do` is a
8736
+ * plain name below. */
8632
8737
  pm_lex_state_t previous_lex_state = parser->lex_state;
8738
+ const pm_keyword_t *keyword = pm_keyword_find(current_start, current_end);
8633
8739
 
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
- }
8740
+ if (keyword != NULL) {
8741
+ if (previous_lex_state & PM_LEX_STATE_FNAME) {
8742
+ lex_state_set(parser, PM_LEX_STATE_ENDFN);
8743
+ } else {
8744
+ lex_state_set(parser, keyword->state);
8745
+ if (keyword->state == PM_LEX_STATE_BEG) {
8746
+ parser->command_start = true;
8747
+ }
8748
+
8749
+ if ((keyword->modifier_type != PM_TOKEN_EOF) && !(previous_lex_state & (PM_LEX_STATE_BEG | PM_LEX_STATE_LABELED | PM_LEX_STATE_CLASS))) {
8750
+ lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL);
8751
+ return keyword->modifier_type;
8752
+ }
8753
+ }
8754
+
8755
+ if (keyword->type == PM_TOKEN_KEYWORD_DO) {
8756
+ /* In FNAME position (a symbol like `:do` or a method name like
8757
+ * `def do`), `do` is a plain name rather than a block, loop, or
8758
+ * lambda opener, so none of the discrimination below applies
8759
+ * and the lambda nesting is left untouched. */
8760
+ if (previous_lex_state & PM_LEX_STATE_FNAME) {
8670
8761
  return PM_TOKEN_KEYWORD_DO;
8671
8762
  }
8763
+ if (parser->enclosure_nesting == parser->lambda_enclosure_nesting) {
8764
+ /* At the bare nesting level of a lambda literal (no
8765
+ * delimiter opened since `->`), a `do` opens the lambda
8766
+ * body. This is a distinct token so that a command in a
8767
+ * parameter default cannot consume it as its own block
8768
+ * (`-> a = foo do end` is `->(a = foo) do end`).
8769
+ *
8770
+ * Clear the nesting so that no token within the `do`/`end`
8771
+ * body is considered to be at the beginning of a lambda;
8772
+ * the parser restores the enclosing value once the lambda
8773
+ * has been fully parsed. */
8774
+ parser->lambda_enclosure_nesting = -1;
8775
+ return PM_TOKEN_KEYWORD_DO_LAMBDA;
8776
+ }
8777
+ if (pm_do_loop_stack_p(parser)) {
8778
+ return PM_TOKEN_KEYWORD_DO_LOOP;
8779
+ }
8780
+ if (!pm_accepts_block_stack_p(parser)) {
8781
+ return PM_TOKEN_KEYWORD_DO_BLOCK;
8782
+ }
8783
+ return PM_TOKEN_KEYWORD_DO;
8784
+ }
8672
8785
 
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;
8786
+ return keyword->type;
8725
8787
  }
8726
8788
  }
8727
8789
 
8790
+ if (method_name) {
8791
+ return PM_TOKEN_METHOD_NAME;
8792
+ }
8793
+
8728
8794
  if (encoding_changed) {
8729
8795
  return parser->encoding->isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER;
8730
8796
  }
@@ -9575,7 +9641,7 @@ lex_question_mark(pm_parser_t *parser) {
9575
9641
 
9576
9642
  // Copy buffer data into the arena and free the heap buffer.
9577
9643
  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);
9644
+ pm_string_init(&parser->current_string, (const uint8_t *) arena_data, buffer.length);
9579
9645
  pm_buffer_cleanup(&buffer);
9580
9646
 
9581
9647
  return PM_TOKEN_CHARACTER_LITERAL;
@@ -9899,7 +9965,7 @@ pm_token_buffer_copy(pm_parser_t *parser, pm_token_buffer_t *token_buffer) {
9899
9965
  // Copy buffer data into the arena and free the heap buffer.
9900
9966
  size_t len = pm_buffer_length(&token_buffer->buffer);
9901
9967
  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);
9968
+ pm_string_init(&parser->current_string, (const uint8_t *) arena_data, len);
9903
9969
  pm_buffer_cleanup(&token_buffer->buffer);
9904
9970
  }
9905
9971
 
@@ -12952,6 +13018,19 @@ expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t di
12952
13018
  */
12953
13019
  #define PM_PARSE_ACCEPTS_STATEMENT ((uint8_t) 0x10)
12954
13020
 
13021
+ /**
13022
+ * Indicates that an endless method definition may have a command call as its
13023
+ * body, as in `def f = foo 1`. Such a definition is a command write, accepted
13024
+ * as a statement (including the value of a statement-level rescue modifier)
13025
+ * and as the value of a single-target write. Since Ruby 4.0 it is also
13026
+ * accepted as the sole argument of a call, where other command writes such as
13027
+ * `x = foo 1` are not. Expression positions such as conditional predicates and
13028
+ * the operands of `not`, `!`, `and`, and `or` accept a command call but not a
13029
+ * command write, so this is set alongside PM_PARSE_ACCEPTS_COMMAND_CALL only
13030
+ * where both are accepted.
13031
+ */
13032
+ #define PM_PARSE_ACCEPTS_COMMAND_DEF ((uint8_t) 0x20)
13033
+
12955
13034
  static pm_node_t *
12956
13035
  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
13036
 
@@ -13067,6 +13146,23 @@ token_begins_pattern_p(pm_token_type_t type) {
13067
13146
  );
13068
13147
  }
13069
13148
 
13149
+ /**
13150
+ * Returns true if the given token can begin an argument in an unparenthesized
13151
+ * argument list. This is the set of tokens that can begin an expression plus
13152
+ * the prefix operators that only appear in argument position — `&` (block
13153
+ * arguments), `*` (splats), and `**` (keyword splats) — which are excluded
13154
+ * from token_begins_expression_p.
13155
+ */
13156
+ static PRISM_INLINE bool
13157
+ token_begins_argument_p(pm_token_type_t type) {
13158
+ return (
13159
+ token_begins_expression_p(type) ||
13160
+ type == PM_TOKEN_UAMPERSAND ||
13161
+ type == PM_TOKEN_USTAR ||
13162
+ type == PM_TOKEN_USTAR_STAR
13163
+ );
13164
+ }
13165
+
13070
13166
  /**
13071
13167
  * Parse an expression with the given binding power that may be optionally
13072
13168
  * prefixed by the * operator.
@@ -13082,7 +13178,7 @@ parse_starred_expression(pm_parser_t *parser, pm_binding_power_t binding_power,
13082
13178
  return parse_value_expression(parser, binding_power, flags, diag_id, depth);
13083
13179
  }
13084
13180
 
13085
- static bool
13181
+ static pm_visit_result_t
13086
13182
  pm_node_unreference_each(const pm_node_t *node, void *data) {
13087
13183
  switch (PM_NODE_TYPE(node)) {
13088
13184
  /* When we are about to destroy a set of nodes that could potentially
@@ -13108,15 +13204,15 @@ pm_node_unreference_each(const pm_node_t *node, void *data) {
13108
13204
  }
13109
13205
  parser->current_block_exits->size--;
13110
13206
 
13111
- /* Note returning true here because these nodes could have
13207
+ /* Note descending here because these nodes could have
13112
13208
  * arguments that are themselves block exits. */
13113
- return true;
13209
+ return PM_VISIT_DESCEND;
13114
13210
  }
13115
13211
 
13116
13212
  index++;
13117
13213
  }
13118
13214
 
13119
- return true;
13215
+ return PM_VISIT_DESCEND;
13120
13216
  }
13121
13217
  /* When an implicit local variable is written to or targeted, it becomes
13122
13218
  * a regular, named local variable. This branch removes it from the list
@@ -13140,10 +13236,10 @@ pm_node_unreference_each(const pm_node_t *node, void *data) {
13140
13236
  }
13141
13237
  }
13142
13238
 
13143
- return false;
13239
+ return PM_VISIT_SKIP;
13144
13240
  }
13145
13241
  default:
13146
- return true;
13242
+ return PM_VISIT_DESCEND;
13147
13243
  }
13148
13244
  }
13149
13245
 
@@ -13708,7 +13804,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context, uint16_t depth) {
13708
13804
  context_push(parser, context);
13709
13805
 
13710
13806
  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));
13807
+ 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
13808
  pm_statements_node_body_append(parser, statements, node, true);
13713
13809
 
13714
13810
  // If we're recovering from a syntax error, then we need to stop parsing
@@ -14135,6 +14231,27 @@ pm_command_call_value_p(const pm_parser_t *parser, const pm_node_t *node) {
14135
14231
  }
14136
14232
  }
14137
14233
 
14234
+ /**
14235
+ * Returns true if the given node is a command that can end before a comma
14236
+ * without consuming it: a constant-path command with a brace block
14237
+ * (`Foo::Bar { }`), or an endless method whose command body ends in a rescue
14238
+ * modifier value (`def f = foo 1 rescue z`). Other commands take a following
14239
+ * comma into their own arguments. A command can be neither one of several
14240
+ * arguments nor one of several values of a write, so a comma after one of
14241
+ * these is an error.
14242
+ */
14243
+ static bool
14244
+ pm_command_before_comma_p(const pm_parser_t *parser, const pm_node_t *node) {
14245
+ switch (PM_NODE_TYPE(node)) {
14246
+ case PM_CALL_NODE:
14247
+ return pm_constant_path_command_call_p(parser, (const pm_call_node_t *) node);
14248
+ case PM_DEF_NODE:
14249
+ return pm_command_call_value_p(parser, node);
14250
+ default:
14251
+ return false;
14252
+ }
14253
+ }
14254
+
14138
14255
  /**
14139
14256
  * Returns true if the given node is a block call: a command
14140
14257
  * with a do-block, or any call chained (via `.`, `::`, `&.`) from such a node.
@@ -14321,7 +14438,17 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
14321
14438
  PRISM_FALLTHROUGH
14322
14439
  default: {
14323
14440
  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));
14441
+ /* The first argument may be a command call, and since Ruby
14442
+ * 4.0 also an endless method with a command body
14443
+ * (`private def f = foo 1`), either of which must then be
14444
+ * the sole argument. */
14445
+ uint8_t command_flags = 0;
14446
+ if (!parsed_first_argument) {
14447
+ command_flags = PM_PARSE_ACCEPTS_COMMAND_CALL;
14448
+ if (parser->version >= PM_OPTIONS_VERSION_CRUBY_4_0) command_flags |= PM_PARSE_ACCEPTS_COMMAND_DEF;
14449
+ }
14450
+
14451
+ 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
14452
  }
14326
14453
 
14327
14454
  bool contains_keywords = false;
@@ -14418,6 +14545,12 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
14418
14545
  break;
14419
14546
  }
14420
14547
  }
14548
+
14549
+ /* A command can only be the sole argument, as in
14550
+ * `foo Foo::Bar { }, y` or `foo def f = bar 1 rescue z, y`. */
14551
+ if (pm_command_before_comma_p(parser, argument)) {
14552
+ pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
14553
+ }
14421
14554
  } else {
14422
14555
  // If there is no comma at the end of the argument list then we're
14423
14556
  // done parsing arguments and can break out of this loop.
@@ -15628,7 +15761,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool full_a
15628
15761
 
15629
15762
  arguments->closing_loc = TOK2LOC(parser, &parser->previous);
15630
15763
  }
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)) {
15764
+ } else if ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type) && !match1(parser, PM_TOKEN_BRACE_LEFT)) {
15632
15765
  found |= true;
15633
15766
  parsed_command_args = true;
15634
15767
 
@@ -15848,7 +15981,7 @@ ensure_valid_jump(pm_parser_t *parser, pm_node_t *node) {
15848
15981
  // block exit to the list of exits for the expression, and
15849
15982
  // the node parsing will handle validating it instead.
15850
15983
  assert(parser->current_block_exits != NULL);
15851
- pm_node_list_append(parser->arena, parser->current_block_exits, node);
15984
+ pm_node_list_append_inline(parser->arena, parser->current_block_exits, node);
15852
15985
  return;
15853
15986
  case PM_CONTEXT_BEGIN_ELSE:
15854
15987
  case PM_CONTEXT_BEGIN_ENSURE:
@@ -16586,12 +16719,12 @@ parse_variable(pm_parser_t *parser) {
16586
16719
  }
16587
16720
 
16588
16721
  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);
16722
+ pm_node_list_append_inline(parser->arena, &current_scope->implicit_parameters, node);
16590
16723
 
16591
16724
  return node;
16592
16725
  } else if ((parser->version >= PM_OPTIONS_VERSION_CRUBY_3_4) && pm_token_is_it(parser->previous.start, parser->previous.end)) {
16593
16726
  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);
16727
+ pm_node_list_append_inline(parser->arena, &current_scope->implicit_parameters, node);
16595
16728
 
16596
16729
  return node;
16597
16730
  }
@@ -16648,18 +16781,13 @@ parse_method_definition_name(pm_parser_t *parser) {
16648
16781
 
16649
16782
  static void
16650
16783
  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
- }
16784
+ // Make a writable copy of the string in the arena, since strings only view
16785
+ // their bytes. We keep a mutable pointer to the arena memory so we can
16786
+ // memmove into it below without casting away const from the string's
16787
+ // source field.
16788
+ size_t length = pm_string_length(string);
16789
+ uint8_t *writable = (uint8_t *) pm_arena_memdup(arena, pm_string_source(string), length, PRISM_ALIGNOF(uint8_t));
16790
+ pm_string_init(string, writable, length);
16663
16791
 
16664
16792
  // Now get the bounds of the existing string. We'll use this as a
16665
16793
  // destination to move bytes into. We'll also use it for bounds checking
@@ -16824,11 +16952,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16824
16952
  if (match1(parser, PM_TOKEN_STRING_CONTENT)) {
16825
16953
  pm_node_list_t parts = { 0 };
16826
16954
  pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &content, NULL, &unescaped));
16827
- pm_node_list_append(parser->arena, &parts, part);
16955
+ pm_node_list_append_inline(parser->arena, &parts, part);
16828
16956
 
16829
16957
  do {
16830
16958
  part = UP(pm_string_node_create_current_string(parser, NULL, &parser->current, NULL));
16831
- pm_node_list_append(parser->arena, &parts, part);
16959
+ pm_node_list_append_inline(parser->arena, &parts, part);
16832
16960
  parser_lex(parser);
16833
16961
  } while (match1(parser, PM_TOKEN_STRING_CONTENT));
16834
16962
 
@@ -16883,11 +17011,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16883
17011
  pm_node_list_t parts = { 0 };
16884
17012
  pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &parser->previous, NULL, &unescaped));
16885
17013
  pm_node_flag_set(part, parse_unescaped_encoding(parser, explicit_encoding));
16886
- pm_node_list_append(parser->arena, &parts, part);
17014
+ pm_node_list_append_inline(parser->arena, &parts, part);
16887
17015
 
16888
17016
  while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) {
16889
17017
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
16890
- pm_node_list_append(parser->arena, &parts, part);
17018
+ pm_node_list_append_inline(parser->arena, &parts, part);
16891
17019
  }
16892
17020
  }
16893
17021
 
@@ -16911,7 +17039,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1
16911
17039
 
16912
17040
  while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) {
16913
17041
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
16914
- pm_node_list_append(parser->arena, &parts, part);
17042
+ pm_node_list_append_inline(parser->arena, &parts, part);
16915
17043
  }
16916
17044
  }
16917
17045
 
@@ -17297,7 +17425,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17297
17425
  }
17298
17426
 
17299
17427
  pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value));
17300
- pm_node_list_append(parser->arena, &assocs, assoc);
17428
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17301
17429
  break;
17302
17430
  }
17303
17431
  }
@@ -17312,7 +17440,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17312
17440
  pm_node_t *value = UP(pm_error_recovery_node_create(parser, PM_NODE_START(first_node), PM_NODE_LENGTH(first_node)));
17313
17441
  pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value));
17314
17442
 
17315
- pm_node_list_append(parser->arena, &assocs, assoc);
17443
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17316
17444
  break;
17317
17445
  }
17318
17446
  }
@@ -17340,7 +17468,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17340
17468
  rest = assoc;
17341
17469
  } else {
17342
17470
  pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
17343
- pm_node_list_append(parser->arena, &assocs, assoc);
17471
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17344
17472
  }
17345
17473
  } else {
17346
17474
  pm_node_t *key;
@@ -17386,7 +17514,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_node_
17386
17514
  pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
17387
17515
  }
17388
17516
 
17389
- pm_node_list_append(parser->arena, &assocs, assoc);
17517
+ pm_node_list_append_inline(parser->arena, &assocs, assoc);
17390
17518
  }
17391
17519
  }
17392
17520
 
@@ -17666,7 +17794,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_set_t *captures, pm_
17666
17794
  * are not captures of this pattern. A nested alternation is not visited
17667
17795
  * either: its operands were already checked when it was parsed.
17668
17796
  */
17669
- static bool
17797
+ static pm_visit_result_t
17670
17798
  parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17671
17799
  pm_parser_t *parser = (pm_parser_t *) data;
17672
17800
 
@@ -17677,7 +17805,7 @@ parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17677
17805
  if (peek_at(parser, parser->start + PM_NODE_START(node)) != '_') {
17678
17806
  pm_parser_err(parser, PM_NODE_START(node), PM_NODE_LENGTH(node), PM_ERR_PATTERN_CAPTURE_IN_ALTERNATIVE);
17679
17807
  }
17680
- return false;
17808
+ return PM_VISIT_SKIP;
17681
17809
  case PM_ARRAY_PATTERN_NODE:
17682
17810
  case PM_ASSOC_NODE:
17683
17811
  case PM_ASSOC_SPLAT_NODE:
@@ -17687,9 +17815,9 @@ parse_pattern_alternation_error_each(const pm_node_t *node, void *data) {
17687
17815
  case PM_IMPLICIT_NODE:
17688
17816
  case PM_PARENTHESES_NODE:
17689
17817
  case PM_SPLAT_NODE:
17690
- return true;
17818
+ return PM_VISIT_DESCEND;
17691
17819
  default:
17692
- return false;
17820
+ return PM_VISIT_SKIP;
17693
17821
  }
17694
17822
  }
17695
17823
 
@@ -17885,7 +18013,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17885
18013
  // or a find pattern. We need to parse all of the patterns, put them
17886
18014
  // into a big list, and then determine which type of node we have.
17887
18015
  pm_node_list_t nodes = { 0 };
17888
- pm_node_list_append(parser->arena, &nodes, node);
18016
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17889
18017
 
17890
18018
  // Gather up all of the patterns into the list.
17891
18019
  while (accept1(parser, PM_TOKEN_COMMA)) {
@@ -17904,7 +18032,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17904
18032
  }
17905
18033
 
17906
18034
  node = UP(pm_implicit_rest_node_create(parser, &parser->previous));
17907
- pm_node_list_append(parser->arena, &nodes, node);
18035
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17908
18036
  trailing_rest = true;
17909
18037
  break;
17910
18038
  }
@@ -17924,7 +18052,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_set_t *captures, uint8_t flags
17924
18052
  node = parse_pattern_primitives(parser, captures, NULL, PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
17925
18053
  }
17926
18054
 
17927
- pm_node_list_append(parser->arena, &nodes, node);
18055
+ pm_node_list_append_inline(parser->arena, &nodes, node);
17928
18056
  }
17929
18057
 
17930
18058
  // If the first pattern and the last pattern are rest patterns, then we
@@ -18556,7 +18684,7 @@ parse_class(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
18556
18684
  * Parse a method definition expression (the `def` keyword).
18557
18685
  */
18558
18686
  static pm_node_t *
18559
- parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags, uint16_t depth) {
18687
+ parse_def(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
18560
18688
  pm_node_list_t current_block_exits = { 0 };
18561
18689
  pm_node_list_t *previous_block_exits = push_block_exits(parser, &current_block_exits);
18562
18690
 
@@ -18806,14 +18934,9 @@ parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
18806
18934
  pm_do_loop_stack_push(parser, false);
18807
18935
  statements = UP(pm_statements_node_create(parser));
18808
18936
 
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
- }
18937
+ /* The body accepts a command call (`def f = foo 1`) only where the
18938
+ * caller accepts an endless method with a command body. */
18939
+ uint8_t allow_flags = (flags & PM_PARSE_ACCEPTS_COMMAND_DEF) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0;
18817
18940
 
18818
18941
  /* Inside a def body, we push true onto the accepts_block_stack so that
18819
18942
  * `do` is lexed as PM_TOKEN_KEYWORD_DO (which can only start a block
@@ -18858,7 +18981,7 @@ parse_def(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
18858
18981
  pm_accepts_block_stack_pop(parser);
18859
18982
 
18860
18983
  /* 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
18984
+ * `def f = def g = foo bar`) is a command write and cannot appear
18862
18985
  * as a def body. */
18863
18986
  if (PM_NODE_TYPE_P(statement, PM_DEF_NODE) && pm_command_call_value_p(parser, statement)) {
18864
18987
  PM_PARSER_ERR_NODE_FORMAT(parser, statement, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_str(parser->current.type));
@@ -19041,7 +19164,7 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
19041
19164
  /* Otherwise, we're going to parse the first statement in the list of
19042
19165
  * statements within the parentheses. */
19043
19166
  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));
19167
+ 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
19168
  context_pop(parser);
19046
19169
 
19047
19170
  /* Determine if this statement is followed by a terminator. In the case of a
@@ -19154,7 +19277,7 @@ parse_parentheses(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
19154
19277
 
19155
19278
  /* Parse each statement within the parentheses. */
19156
19279
  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));
19280
+ 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
19281
  pm_statements_node_body_append(parser, statements, node, true);
19159
19282
 
19160
19283
  /* If we're recovering from a syntax error, then we need to stop parsing
@@ -19435,11 +19558,11 @@ parse_heredoc(pm_parser_t *parser, uint16_t depth) {
19435
19558
  // so we'll need to create an interpolated string node to hold
19436
19559
  // them all.
19437
19560
  pm_node_list_t parts = { 0 };
19438
- pm_node_list_append(parser->arena, &parts, part);
19561
+ pm_node_list_append_inline(parser->arena, &parts, part);
19439
19562
 
19440
19563
  while (!match2(parser, PM_TOKEN_HEREDOC_END, PM_TOKEN_EOF)) {
19441
19564
  if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) {
19442
- pm_node_list_append(parser->arena, &parts, part);
19565
+ pm_node_list_append_inline(parser->arena, &parts, part);
19443
19566
  }
19444
19567
  }
19445
19568
 
@@ -19670,17 +19793,17 @@ parse_lambda(pm_parser_t *parser, uint16_t depth) {
19670
19793
  /* Restore the enclosing lambda's nesting now that the body has been
19671
19794
  * parsed, so that the token following the closing `}` is lexed in the
19672
19795
  * 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`. */
19796
+ * level, which every token inside the braces sits above. */
19675
19797
  parser->lambda_enclosure_nesting = previous_lambda_enclosure_nesting;
19676
19798
  expect1_opening(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_LAMBDA_TERM_BRACE, &opening);
19677
19799
  } 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);
19800
+ /* A `-> { }` body is delimited by `{`/`}`, whose enclosure frame the
19801
+ * lexer manages. A `-> do end` body is delimited by keywords, so push
19802
+ * the frame here and pop it before `end`. The frame isolates the body
19803
+ * from an enclosing `while`/`until`/`for` condition, so a `do` inside
19804
+ * it opens a block rather than the loop body. The push must precede
19805
+ * consuming the `do`, which lexes the first token of the body. */
19806
+ pm_enclosure_frame_push(parser);
19684
19807
  expect1(parser, PM_TOKEN_KEYWORD_DO_LAMBDA, PM_ERR_LAMBDA_OPEN);
19685
19808
  opening = parser->previous;
19686
19809
 
@@ -19701,7 +19824,7 @@ parse_lambda(pm_parser_t *parser, uint16_t depth) {
19701
19824
  parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false);
19702
19825
  }
19703
19826
 
19704
- pm_accepts_block_stack_pop(parser);
19827
+ pm_enclosure_frame_pop(parser);
19705
19828
 
19706
19829
  /* As with the brace branch above, restore the nesting before consuming
19707
19830
  * the closing `end`, which lexes the token that follows it. */
@@ -20040,7 +20163,7 @@ parse_identifier(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
20040
20163
  // can still be a method call if it is followed by arguments or
20041
20164
  // a block, so we need to check for that here.
20042
20165
  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))) ||
20166
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type)) ||
20044
20167
  (pm_accepts_block_stack_p(parser) && match1(parser, PM_TOKEN_KEYWORD_DO)) ||
20045
20168
  match1(parser, PM_TOKEN_BRACE_LEFT)
20046
20169
  ) {
@@ -20497,7 +20620,14 @@ parse_jump(pm_parser_t *parser, uint8_t flags, uint16_t depth) {
20497
20620
 
20498
20621
  if (binding_power == PM_BINDING_POWER_UNSET || binding_power >= PM_BINDING_POWER_RANGE) {
20499
20622
  pm_token_t next = parser->current;
20623
+
20624
+ /* The arguments of a jump start a fresh command context, so a
20625
+ * `do` inside them binds to the nearest call within the
20626
+ * arguments rather than to an enclosing command call, as in
20627
+ * `check 1, case x when 1 then return bar do end end`. */
20628
+ pm_accepts_block_stack_push(parser, true);
20500
20629
  parse_arguments(parser, &arguments, false, PM_TOKEN_EOF, flags, (uint16_t) (depth + 1));
20630
+ pm_accepts_block_stack_pop(parser);
20501
20631
 
20502
20632
  // Reject `foo && return bar`.
20503
20633
  if (!(flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && arguments.arguments != NULL) {
@@ -20590,7 +20720,16 @@ parse_not(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t flags,
20590
20720
  }
20591
20721
  }
20592
20722
  } 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));
20723
+ /* The operand of `not` may be a command call, except at the start of
20724
+ * an endless method body, where it may be one only if the body may
20725
+ * be: `def f = not foo 1` has a command body, so it is rejected where
20726
+ * that is not accepted (`if def f = not foo 1; end`). A nested `not`
20727
+ * is still at the start of the body, so the operand carries the
20728
+ * endless method context on (`def f = not not foo 1`). */
20729
+ uint8_t operand_flags = (flags & PM_PARSE_IN_ENDLESS_DEF)
20730
+ ? (uint8_t) (flags & (PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_IN_ENDLESS_DEF))
20731
+ : PM_PARSE_ACCEPTS_COMMAND_CALL;
20732
+ 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
20733
  }
20595
20734
 
20596
20735
  return UP(pm_call_node_not_create(parser, receiver, &message, &arguments));
@@ -20736,7 +20875,7 @@ parse_constant(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t fl
20736
20875
  * fact a method call, not a constant read. */
20737
20876
  if (
20738
20877
  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))) ||
20878
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type)) ||
20740
20879
  (pm_accepts_block_stack_p(parser) && match1(parser, PM_TOKEN_KEYWORD_DO)) ||
20741
20880
  match1(parser, PM_TOKEN_BRACE_LEFT)
20742
20881
  ) {
@@ -20996,7 +21135,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u
20996
21135
  case PM_TOKEN_KEYWORD_CLASS:
20997
21136
  return parse_class(parser, flags, depth);
20998
21137
  case PM_TOKEN_KEYWORD_DEF:
20999
- return parse_def(parser, binding_power, flags, depth);
21138
+ return parse_def(parser, flags, depth);
21000
21139
  case PM_TOKEN_KEYWORD_DEFINED:
21001
21140
  return UP(parse_defined(parser, flags, depth));
21002
21141
  case PM_TOKEN_KEYWORD_END_UPCASE:
@@ -21138,6 +21277,18 @@ parse_rescue_modifier_terminator(pm_parser_t *parser, uint8_t flags, uint16_t de
21138
21277
  }
21139
21278
  }
21140
21279
 
21280
+ /**
21281
+ * A command is a complete value of a write: it cannot begin a multi-value
21282
+ * right-hand side (`x = Foo::Bar { }, y`), nor stand as the first element of
21283
+ * one when nested in another write (`x = y = def f = foo 1 rescue z, w`).
21284
+ */
21285
+ static void
21286
+ parse_write_value_comma_check(pm_parser_t *parser, const pm_node_t *value) {
21287
+ if (match1(parser, PM_TOKEN_COMMA) && pm_command_before_comma_p(parser, value)) {
21288
+ pm_parser_err_current(parser, PM_ERR_INVALID_COMMA);
21289
+ }
21290
+ }
21291
+
21141
21292
  /**
21142
21293
  * Parse a value that is going to be written to some kind of variable or method
21143
21294
  * call. We need to handle this separately because the rescue modifier is
@@ -21149,7 +21300,21 @@ parse_rescue_modifier_terminator(pm_parser_t *parser, uint8_t flags, uint16_t de
21149
21300
  */
21150
21301
  static pm_node_t *
21151
21302
  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));
21303
+ /* Where an operator write accepts a command call it also accepts an
21304
+ * endless method with a command body (`x += def f = foo 1`). */
21305
+ uint8_t command_flags = (previous_binding_power == PM_BINDING_POWER_ASSIGNMENT)
21306
+ ? (uint8_t) (flags & PM_PARSE_ACCEPTS_COMMAND_CALL)
21307
+ : (previous_binding_power < PM_BINDING_POWER_MATCH ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0);
21308
+ if (command_flags) command_flags |= PM_PARSE_ACCEPTS_COMMAND_DEF;
21309
+
21310
+ 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));
21311
+
21312
+ /* An operator write never takes a multi-value right-hand side, so a comma
21313
+ * after a statement-level one is already reported by the statement that
21314
+ * contains it. */
21315
+ if (previous_binding_power != PM_BINDING_POWER_STATEMENT && !(flags & PM_PARSE_ACCEPTS_STATEMENT)) {
21316
+ parse_write_value_comma_check(parser, value);
21317
+ }
21153
21318
 
21154
21319
  // Assignments whose value is a command call (e.g., a = b c) can only
21155
21320
  // be followed by modifiers (if/unless/while/until/rescue) and not by
@@ -21253,12 +21418,21 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
21253
21418
  ? (uint8_t) (flags & PM_PARSE_ACCEPTS_COMMAND_CALL)
21254
21419
  : ((previous_binding_power < PM_BINDING_POWER_MODIFIER || statement_level) ? PM_PARSE_ACCEPTS_COMMAND_CALL : 0);
21255
21420
 
21421
+ /* Where a single-target write accepts a command call it also accepts an
21422
+ * endless method with a command body (`x = def f = foo 1`), but a
21423
+ * multi-write accepts only the command call. */
21424
+ if (command_call_flag && binding_power != PM_BINDING_POWER_MULTI_ASSIGNMENT + 1) {
21425
+ command_call_flag |= PM_PARSE_ACCEPTS_COMMAND_DEF;
21426
+ }
21427
+
21256
21428
  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
21429
  if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE);
21258
21430
 
21259
21431
  parse_assignment_value_local(parser, value);
21260
21432
  bool single_value = true;
21261
21433
 
21434
+ parse_write_value_comma_check(parser, value);
21435
+
21262
21436
  // Block calls (command call + do block, e.g., `foo bar do end`) cannot
21263
21437
  // be followed by a comma to form a multi-value RHS because each element
21264
21438
  // of a multi-value assignment must be an `arg`, not a `block_call`.
@@ -21360,7 +21534,7 @@ parse_rescue_modifier_value(pm_parser_t *parser, uint8_t flags, bool statement,
21360
21534
  // splat right-hand side (`b = c, d` / `b = *c`); a comma _before_ an
21361
21535
  // `=`, or a parenthesized target list (`(b, c), d = 1`), instead
21362
21536
  // 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));
21537
+ 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
21538
  multiple = match1(parser, PM_TOKEN_COMMA) || PM_NODE_TYPE_P(value, PM_MULTI_TARGET_NODE);
21365
21539
  }
21366
21540
 
@@ -21657,7 +21831,7 @@ parse_regular_expression_named_capture(pm_parser_t *parser, const pm_string_t *c
21657
21831
  pm_token_t token = { .type = 0, .start = start, .end = end };
21658
21832
  pm_location_t token_loc = TOK2LOC(parser, &token);
21659
21833
  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);
21834
+ pm_node_list_append_inline(parser->arena, &name_data->match->targets, target);
21661
21835
  }
21662
21836
 
21663
21837
  pm_buffer_cleanup(&unescaped);
@@ -21735,7 +21909,7 @@ parse_constant_path_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power
21735
21909
 
21736
21910
  if (
21737
21911
  (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)))
21912
+ ((flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && token_begins_argument_p(parser->current.type))
21739
21913
  ) {
21740
21914
  /* If we have a constant immediately following a '::' operator,
21741
21915
  * then this can either be a constant path or a method call,
@@ -21826,9 +22000,7 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21826
22000
  }
21827
22001
 
21828
22002
  if (!interpolated && total_length > 0) {
21829
- void *memory = xmalloc(total_length);
21830
- if (!memory) abort();
21831
-
22003
+ uint8_t *memory = (uint8_t *) pm_arena_alloc(&parser->metadata_arena, total_length, PRISM_ALIGNOF(uint8_t));
21832
22004
  uint8_t *cursor = memory;
21833
22005
  PM_NODE_LIST_FOREACH(parts, index, part) {
21834
22006
  pm_string_t *unescaped = &((pm_string_node_t *) part)->unescaped;
@@ -21838,11 +22010,9 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21838
22010
  cursor += length;
21839
22011
  }
21840
22012
 
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);
22013
+ pm_string_t content;
22014
+ pm_string_init(&content, memory, total_length);
22015
+ result = parse_interpolated_regular_expression_named_captures(parser, &content, call, PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED));
21846
22016
  }
21847
22017
  } else if (PM_NODE_TYPE_P(node, PM_REGULAR_EXPRESSION_NODE)) {
21848
22018
  /* If we have a regular expression node, then we can parse the named
@@ -21852,7 +22022,7 @@ parse_match(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t binding_pow
21852
22022
  .call = call,
21853
22023
  .match = NULL,
21854
22024
  .names = { 0 },
21855
- .shared = regexp->unescaped.type == PM_STRING_SHARED,
22025
+ .shared = pm_string_source(&regexp->unescaped) == parser->start + regexp->content_loc.start,
21856
22026
  };
21857
22027
 
21858
22028
  pm_node_flag_set(UP(regexp), pm_regexp_parse(parser, regexp, parse_regular_expression_named_capture, &name_data));
@@ -22709,7 +22879,7 @@ parse_expression_terminator(pm_parser_t *parser, pm_node_t *node) {
22709
22879
  return left > PM_BINDING_POWER_COMPOSITION && left < PM_BINDING_POWER_CALL && pm_block_call_p(node);
22710
22880
  case PM_DEF_NODE:
22711
22881
  // 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
22882
+ // `def f = foo bar`) is a command write and can only be
22713
22883
  // followed by modifiers.
22714
22884
  return left > PM_BINDING_POWER_MODIFIER && pm_command_call_value_p(parser, node);
22715
22885
  case PM_RESCUE_MODIFIER_NODE:
@@ -22864,7 +23034,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
22864
23034
  cast->block != NULL && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)
22865
23035
  )
22866
23036
  ) {
22867
- flags &= (uint8_t) ~PM_PARSE_ACCEPTS_COMMAND_CALL;
23037
+ flags &= (uint8_t) ~(PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF);
22868
23038
  }
22869
23039
  break;
22870
23040
  }
@@ -22872,7 +23042,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
22872
23042
  case PM_CONSTANT_PATH_NODE:
22873
23043
  break;
22874
23044
  default:
22875
- flags &= (uint8_t) ~PM_PARSE_ACCEPTS_COMMAND_CALL;
23045
+ flags &= (uint8_t) ~(PM_PARSE_ACCEPTS_COMMAND_CALL | PM_PARSE_ACCEPTS_COMMAND_DEF);
22876
23046
  break;
22877
23047
  }
22878
23048
  }
@@ -23378,17 +23548,8 @@ pm_parser_new(pm_arena_t *arena, const uint8_t *source, size_t size, const pm_op
23378
23548
  */
23379
23549
  void
23380
23550
  pm_parser_cleanup(pm_parser_t *parser) {
23381
- pm_string_cleanup(&parser->filepath);
23382
23551
  pm_arena_cleanup(&parser->metadata_arena);
23383
23552
 
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
23553
  while (parser->lex_modes.index >= PM_LEX_STACK_SIZE) {
23393
23554
  lex_mode_pop(parser);
23394
23555
  }
@@ -23728,6 +23889,7 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s
23728
23889
  pm_serialize_header(buffer);
23729
23890
  pm_serialize_encoding(parser.encoding, buffer);
23730
23891
  pm_buffer_append_varsint(buffer, parser.start_line);
23892
+ pm_buffer_append_uint64(buffer, pm_parser_checksum(&parser));
23731
23893
  pm_serialize_line_offset_list(&parser.line_offsets, buffer);
23732
23894
  pm_serialize_comment_list(&parser.comment_list, buffer);
23733
23895