herb 0.11.1 → 0.11.2

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -2
  3. data/config.yml +6 -0
  4. data/ext/herb/extconf.rb +8 -31
  5. data/ext/herb/nodes.c +75 -75
  6. data/ext/herb/parser_options_helpers.c +26 -0
  7. data/lib/herb/analysis/partial_index.rb +1 -1
  8. data/lib/herb/analysis/partial_resolution.rb +27 -0
  9. data/lib/herb/ast/helpers.rb +1 -1
  10. data/lib/herb/configuration.rb +11 -1
  11. data/lib/herb/dev/classifier.rb +1 -1
  12. data/lib/herb/engine/compiler.rb +5 -8
  13. data/lib/herb/engine/slots/dynamics_compiler.rb +53 -14
  14. data/lib/herb/engine/slots/subtree_compiler.rb +1 -1
  15. data/lib/herb/engine/slots/visitor.rb +32 -4
  16. data/lib/herb/engine/visitors/instrumentation_visitor.rb +18 -6
  17. data/lib/herb/parser_options.rb +6 -3
  18. data/lib/herb/project.rb +19 -8
  19. data/lib/herb/version.rb +1 -1
  20. data/lib/herb.rb +1 -1
  21. data/sig/herb/analysis/partial_resolution.rbs +8 -0
  22. data/sig/herb/configuration.rbs +3 -0
  23. data/sig/herb/engine/slots/dynamics_compiler.rbs +24 -7
  24. data/sig/herb/engine/slots/visitor.rbs +14 -0
  25. data/sig/herb/engine/visitors/instrumentation_visitor.rbs +5 -1
  26. data/sig/herb/parser_options.rbs +6 -4
  27. data/sig/herb.rbs +2 -2
  28. data/src/analyze/action_view/attribute_extraction_helpers.c +40 -21
  29. data/src/analyze/action_view/generated_handlers.c +36 -36
  30. data/src/analyze/action_view/image_tag.c +7 -3
  31. data/src/analyze/action_view/javascript_include_tag.c +6 -3
  32. data/src/analyze/action_view/link_to.c +23 -23
  33. data/src/analyze/action_view/registry.c +7 -3
  34. data/src/analyze/action_view/tag_helper_node_builders.c +6 -13
  35. data/src/analyze/action_view/tag_helpers.c +139 -90
  36. data/src/analyze/action_view/turbo_frame_tag.c +5 -4
  37. data/src/analyze/analyze.c +35 -19
  38. data/src/analyze/analyze_helpers.c +161 -188
  39. data/src/analyze/analyzed_ruby.c +8 -7
  40. data/src/analyze/control_type.c +29 -22
  41. data/src/analyze/herb_directives.c +16 -20
  42. data/src/analyze/iteration_nodes.c +69 -24
  43. data/src/analyze/parse_errors.c +32 -22
  44. data/src/analyze/postfix_conditionals.c +23 -13
  45. data/src/analyze/prism_annotate.c +29 -28
  46. data/src/analyze/render_nodes.c +51 -41
  47. data/src/analyze/strict_locals.c +14 -15
  48. data/src/analyze/ternary_conditionals.c +14 -10
  49. data/src/herb.c +10 -8
  50. data/src/include/analyze/action_view/tag_helper_handler.h +1 -1
  51. data/src/include/analyze/action_view/tag_helper_node_builders.h +1 -0
  52. data/src/include/analyze/action_view/tag_helpers.h +1 -1
  53. data/src/include/analyze/analyze.h +3 -2
  54. data/src/include/analyze/analyzed_ruby.h +4 -2
  55. data/src/include/analyze/helpers.h +6 -6
  56. data/src/include/herb.h +3 -2
  57. data/src/include/parser/parser_options.h +5 -0
  58. data/src/include/prism/prism_context.h +12 -12
  59. data/src/include/prism/prism_helpers.h +30 -0
  60. data/src/include/prism_version.h +11 -0
  61. data/src/include/version.h +1 -1
  62. data/src/parser/parser_helpers.c +1 -2
  63. data/src/parser.c +6 -7
  64. data/src/parser_options_defaults.c +1 -0
  65. data/src/prism/prism_helpers.c +84 -50
  66. data/src/prism/ruby_parser.c +20 -20
  67. data/templates/ext/herb/nodes.c.erb +5 -5
  68. data/templates/ext/herb/parser_options_helpers.c.erb +33 -0
  69. data/templates/java/nodes.c.erb +6 -6
  70. data/templates/java/parser_options_helpers.c.erb +41 -2
  71. data/templates/java/parser_options_helpers.h.erb +2 -1
  72. data/templates/javascript/packages/node/extension/nodes.cpp.erb +5 -5
  73. data/templates/javascript/packages/node/extension/parser_options_helpers.cpp.erb +34 -2
  74. data/templates/javascript/packages/node/extension/parser_options_helpers.h.erb +1 -1
  75. data/templates/lib/herb/parser_options.rb.erb +1 -1
  76. data/templates/rust/src/prism/generated_deserialize.rs.erb +90 -44
  77. data/templates/rust/src/prism/generated_prettyprint.rs.erb +18 -27
  78. data/templates/src/analyze/action_view/generated_handlers.c.erb +23 -22
  79. data/templates/src/include/parser/parser_options.h.erb +10 -0
  80. data/templates/src/include/prism_version.h.erb +17 -0
  81. data/templates/src/parser_options_defaults.c.erb +1 -1
  82. data/templates/template.rb +17 -2
  83. data/templates/wasm/nodes.cpp.erb +5 -5
  84. data/templates/wasm/parser_options_helpers.cpp.erb +24 -0
  85. data/vendor/prism/Rakefile +76 -7
  86. data/vendor/prism/config.yml +174 -161
  87. data/vendor/prism/include/prism/arena.h +45 -0
  88. data/vendor/prism/include/prism/arena.hpp +42 -0
  89. data/vendor/prism/include/prism/ast.h +2738 -611
  90. data/vendor/prism/include/prism/ast.hpp +641 -0
  91. data/vendor/prism/include/prism/buffer.h +60 -0
  92. data/vendor/prism/include/prism/buffer.hpp +55 -0
  93. data/vendor/prism/include/prism/comments.h +72 -0
  94. data/vendor/prism/include/prism/compiler/accel.h +26 -0
  95. data/vendor/prism/include/prism/compiler/align.h +36 -0
  96. data/vendor/prism/include/prism/compiler/assume.h +25 -0
  97. data/vendor/prism/include/prism/compiler/branch.h +26 -0
  98. data/vendor/prism/include/prism/compiler/bswap.h +46 -0
  99. data/vendor/prism/include/prism/compiler/cplusplus.hpp +16 -0
  100. data/vendor/prism/include/prism/compiler/endianness.h +34 -0
  101. data/vendor/prism/include/prism/compiler/exceptions.hpp +17 -0
  102. data/vendor/prism/include/prism/compiler/exported.h +24 -0
  103. data/vendor/prism/include/prism/compiler/fallthrough.h +22 -0
  104. data/vendor/prism/include/prism/compiler/filesystem.h +32 -0
  105. data/vendor/prism/include/prism/compiler/flex_array.h +19 -0
  106. data/vendor/prism/include/prism/compiler/force_inline.h +21 -0
  107. data/vendor/prism/include/prism/compiler/format.h +27 -0
  108. data/vendor/prism/include/prism/compiler/inline.h +17 -0
  109. data/vendor/prism/include/prism/compiler/lifetimebound.hpp +25 -0
  110. data/vendor/prism/include/prism/compiler/nodiscard.h +22 -0
  111. data/vendor/prism/include/prism/compiler/nonnull.h +18 -0
  112. data/vendor/prism/include/prism/compiler/unused.h +18 -0
  113. data/vendor/prism/include/prism/constant_pool.h +106 -0
  114. data/vendor/prism/include/prism/diagnostic.h +55 -402
  115. data/vendor/prism/include/prism/errors_format.h +61 -0
  116. data/vendor/prism/include/prism/excludes.h +29 -0
  117. data/vendor/prism/include/prism/integer.h +41 -0
  118. data/vendor/prism/include/prism/internal/allocator.h +68 -0
  119. data/vendor/prism/include/prism/internal/allocator_debug.h +88 -0
  120. data/vendor/prism/include/prism/internal/arena.h +108 -0
  121. data/vendor/prism/include/prism/internal/bit.h +42 -0
  122. data/vendor/prism/include/prism/internal/buffer.h +94 -0
  123. data/vendor/prism/include/prism/internal/char.h +145 -0
  124. data/vendor/prism/include/prism/internal/comments.h +54 -0
  125. data/vendor/prism/include/prism/internal/constant_pool.h +152 -0
  126. data/vendor/prism/include/prism/internal/diagnostic.h +389 -0
  127. data/vendor/prism/include/prism/{encoding.h → internal/encoding.h} +27 -68
  128. data/vendor/prism/include/prism/internal/integer.h +68 -0
  129. data/vendor/prism/include/prism/internal/isinf.h +16 -0
  130. data/vendor/prism/include/prism/internal/line_offset_list.h +34 -0
  131. data/vendor/prism/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  132. data/vendor/prism/include/prism/internal/magic_comments.h +23 -0
  133. data/vendor/prism/include/prism/internal/memchr.h +15 -0
  134. data/vendor/prism/include/prism/internal/node.h +42 -0
  135. data/vendor/prism/include/prism/internal/options.h +232 -0
  136. data/vendor/prism/include/prism/internal/parser.h +1015 -0
  137. data/vendor/prism/include/prism/internal/regexp.h +26 -0
  138. data/vendor/prism/include/prism/internal/serialize.h +39 -0
  139. data/vendor/prism/include/prism/internal/source.h +72 -0
  140. data/vendor/prism/include/prism/internal/static_literals.h +60 -0
  141. data/vendor/prism/include/prism/internal/stringy.h +30 -0
  142. data/vendor/prism/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  143. data/vendor/prism/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  144. data/vendor/prism/include/prism/internal/tokens.h +11 -0
  145. data/vendor/prism/include/prism/internal/xxh3.h +16 -0
  146. data/vendor/prism/include/prism/json.h +40 -0
  147. data/vendor/prism/include/prism/line_offset_list.h +69 -0
  148. data/vendor/prism/include/prism/magic_comments.h +53 -0
  149. data/vendor/prism/include/prism/node.h +59 -49
  150. data/vendor/prism/include/prism/node.hpp +272 -0
  151. data/vendor/prism/include/prism/node_type.hpp +38 -0
  152. data/vendor/prism/include/prism/options.h +154 -279
  153. data/vendor/prism/include/prism/options.hpp +43 -0
  154. data/vendor/prism/include/prism/parser.h +367 -870
  155. data/vendor/prism/include/prism/parser.hpp +274 -0
  156. data/vendor/prism/include/prism/prettyprint.h +14 -9
  157. data/vendor/prism/include/prism/serialize.h +123 -0
  158. data/vendor/prism/include/prism/source.h +163 -0
  159. data/vendor/prism/include/prism/stream.h +36 -0
  160. data/vendor/prism/include/prism/string_query.h +82 -0
  161. data/vendor/prism/include/prism/stringy.h +61 -0
  162. data/vendor/prism/include/prism/stringy.hpp +31 -0
  163. data/vendor/prism/include/prism/version.h +20 -3
  164. data/vendor/prism/include/prism.h +35 -320
  165. data/vendor/prism/include/prism.hpp +24 -0
  166. data/vendor/prism/src/arena.c +122 -0
  167. data/vendor/prism/src/{util/pm_buffer.c → buffer.c} +53 -21
  168. data/vendor/prism/src/{util/pm_char.c → char.c} +28 -57
  169. data/vendor/prism/src/comments.c +455 -0
  170. data/vendor/prism/src/constant_pool.c +406 -0
  171. data/vendor/prism/src/diagnostic.c +120 -68
  172. data/vendor/prism/src/encoding.c +8 -3
  173. data/vendor/prism/src/errors_format.c +519 -0
  174. data/vendor/prism/src/{util/pm_integer.c → integer.c} +38 -22
  175. data/vendor/prism/src/json.c +5733 -0
  176. data/vendor/prism/src/line_offset_list.c +100 -0
  177. data/vendor/prism/src/list.c +24 -0
  178. data/vendor/prism/src/{util/pm_memchr.c → memchr.c} +8 -6
  179. data/vendor/prism/src/node.c +3465 -7787
  180. data/vendor/prism/src/options.c +161 -54
  181. data/vendor/prism/src/parser.c +371 -0
  182. data/vendor/prism/src/prettyprint.c +421 -354
  183. data/vendor/prism/src/prism.c +8522 -7280
  184. data/vendor/prism/src/regexp.c +960 -45
  185. data/vendor/prism/src/serialize.c +2434 -2048
  186. data/vendor/prism/src/source.c +524 -0
  187. data/vendor/prism/src/static_literals.c +303 -360
  188. data/vendor/prism/src/string_query.c +180 -0
  189. data/vendor/prism/src/stringy.c +61 -0
  190. data/vendor/prism/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  191. data/vendor/prism/src/strpbrk.c +447 -0
  192. data/vendor/prism/src/{token_type.c → tokens.c} +39 -18
  193. data/vendor/prism/src/xxh3.c +292 -0
  194. data/vendor/prism/templates/ext/prism/api_node.c.erb +58 -44
  195. data/vendor/prism/templates/include/prism/ast.h.erb +67 -17
  196. data/vendor/prism/templates/include/prism/ast.hpp.erb +31 -0
  197. data/vendor/prism/templates/include/prism/internal/diagnostic.h.erb +60 -0
  198. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/AbstractNodeVisitor.java.erb +1 -1
  199. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Loader.java.erb +232 -133
  200. data/vendor/prism/templates/java/{org → api/src/main/java-templates/org/ruby_lang}/prism/Nodes.java.erb +62 -23
  201. data/vendor/prism/templates/javascript/src/deserialize.js.erb +153 -146
  202. data/vendor/prism/templates/javascript/src/nodes.js.erb +4 -9
  203. data/vendor/prism/templates/lib/prism/compiler.rb.erb +14 -5
  204. data/vendor/prism/templates/lib/prism/dispatcher.rb.erb +20 -12
  205. data/vendor/prism/templates/lib/prism/dot_visitor.rb.erb +37 -11
  206. data/vendor/prism/templates/lib/prism/dsl.rb.erb +43 -4
  207. data/vendor/prism/templates/lib/prism/inspect_visitor.rb.erb +26 -10
  208. data/vendor/prism/templates/lib/prism/mutation_compiler.rb.erb +5 -2
  209. data/vendor/prism/templates/lib/prism/node.rb.erb +283 -105
  210. data/vendor/prism/templates/lib/prism/reflection.rb.erb +11 -2
  211. data/vendor/prism/templates/lib/prism/serialize.rb.erb +291 -134
  212. data/vendor/prism/templates/lib/prism/visitor.rb.erb +18 -0
  213. data/vendor/prism/templates/src/diagnostic.c.erb +114 -63
  214. data/vendor/prism/templates/src/json.c.erb +130 -0
  215. data/vendor/prism/templates/src/node.c.erb +79 -236
  216. data/vendor/prism/templates/src/prettyprint.c.erb +24 -13
  217. data/vendor/prism/templates/src/serialize.c.erb +369 -183
  218. data/vendor/prism/templates/src/{token_type.c.erb → tokens.c.erb} +22 -11
  219. data/vendor/prism/templates/template.rb +193 -91
  220. metadata +100 -42
  221. data/vendor/prism/include/prism/defines.h +0 -260
  222. data/vendor/prism/include/prism/pack.h +0 -163
  223. data/vendor/prism/include/prism/regexp.h +0 -43
  224. data/vendor/prism/include/prism/static_literals.h +0 -121
  225. data/vendor/prism/include/prism/util/pm_buffer.h +0 -236
  226. data/vendor/prism/include/prism/util/pm_char.h +0 -204
  227. data/vendor/prism/include/prism/util/pm_constant_pool.h +0 -218
  228. data/vendor/prism/include/prism/util/pm_integer.h +0 -130
  229. data/vendor/prism/include/prism/util/pm_memchr.h +0 -29
  230. data/vendor/prism/include/prism/util/pm_newline_list.h +0 -113
  231. data/vendor/prism/include/prism/util/pm_string.h +0 -200
  232. data/vendor/prism/src/pack.c +0 -509
  233. data/vendor/prism/src/util/pm_constant_pool.c +0 -342
  234. data/vendor/prism/src/util/pm_list.c +0 -49
  235. data/vendor/prism/src/util/pm_newline_list.c +0 -125
  236. data/vendor/prism/src/util/pm_string.c +0 -381
  237. data/vendor/prism/src/util/pm_strpbrk.c +0 -206
  238. data/vendor/prism/templates/include/prism/diagnostic.h.erb +0 -130
  239. data/vendor/prism/templates/rbi/prism/dsl.rbi.erb +0 -68
  240. data/vendor/prism/templates/rbi/prism/node.rbi.erb +0 -167
  241. data/vendor/prism/templates/rbi/prism/visitor.rbi.erb +0 -18
  242. data/vendor/prism/templates/sig/prism/_private/dot_visitor.rbs.erb +0 -45
  243. data/vendor/prism/templates/sig/prism/dsl.rbs.erb +0 -31
  244. data/vendor/prism/templates/sig/prism/mutation_compiler.rbs.erb +0 -7
  245. data/vendor/prism/templates/sig/prism/node.rbs.erb +0 -136
  246. data/vendor/prism/templates/sig/prism/visitor.rbs.erb +0 -17
  247. data/vendor/prism/templates/sig/prism.rbs.erb +0 -92
@@ -1,236 +1,123 @@
1
- #include "prism/static_literals.h"
1
+ #include "prism/internal/static_literals.h"
2
+
3
+ #include "prism/compiler/inline.h"
4
+ #include "prism/compiler/unused.h"
5
+
6
+ #include "prism/internal/allocator.h"
7
+ #include "prism/internal/buffer.h"
8
+ #include "prism/internal/integer.h"
9
+ #include "prism/internal/isinf.h"
10
+ #include "prism/internal/parser.h"
11
+ #include "prism/internal/stringy.h"
12
+ #include "prism/internal/xxh3.h"
13
+
14
+ #include <assert.h>
15
+ #include <inttypes.h>
16
+ #include <math.h>
17
+ #include <stdio.h>
18
+ #include <stdlib.h>
19
+ #include <string.h>
2
20
 
3
21
  /**
4
- * A small struct used for passing around a subset of the information that is
5
- * stored on the parser. We use this to avoid having static literals explicitly
6
- * depend on the parser struct.
22
+ * Hash the key built from the given values. Nodes that compare as equal must
23
+ * produce identical keys, so each value is either held directly or is the
24
+ * digest of a variable-length value. The key is an array so that its values are
25
+ * contiguous with no padding between them, which makes every hashed byte
26
+ * defined.
7
27
  */
8
- typedef struct {
9
- /** The list of newline offsets to use to calculate line numbers. */
10
- const pm_newline_list_t *newline_list;
11
-
12
- /** The line number that the parser starts on. */
13
- int32_t start_line;
14
-
15
- /** The name of the encoding that the parser is using. */
16
- const char *encoding_name;
17
- } pm_static_literals_metadata_t;
18
-
19
- static inline uint32_t
20
- murmur_scramble(uint32_t value) {
21
- value *= 0xcc9e2d51;
22
- value = (value << 15) | (value >> 17);
23
- value *= 0x1b873593;
24
- return value;
28
+ static PRISM_INLINE uint64_t
29
+ key_hash(uint64_t type, uint64_t a, uint64_t b) {
30
+ const uint64_t key[3] = { type, a, b };
31
+ return pm_xxh3((const uint8_t *) key, sizeof(key));
25
32
  }
26
33
 
27
34
  /**
28
- * Murmur hash (https://en.wikipedia.org/wiki/MurmurHash) is a non-cryptographic
29
- * general-purpose hash function. It is fast, which is what we care about in
30
- * this case.
35
+ * Return the hash of the value of an integer. An integer that fits into a
36
+ * uint32_t keys on its value directly, and a larger one keys on the digest of
37
+ * its words.
31
38
  */
32
- static uint32_t
33
- murmur_hash(const uint8_t *key, size_t length) {
34
- uint32_t hash = 0x9747b28c;
35
- uint32_t segment;
36
-
37
- for (size_t index = length >> 2; index; index--) {
38
- memcpy(&segment, key, sizeof(uint32_t));
39
- key += sizeof(uint32_t);
40
- hash ^= murmur_scramble(segment);
41
- hash = (hash << 13) | (hash >> 19);
42
- hash = hash * 5 + 0xe6546b64;
43
- }
44
-
45
- segment = 0;
46
- for (size_t index = length & 3; index; index--) {
47
- segment <<= 8;
48
- segment |= key[index - 1];
49
- }
50
-
51
- hash ^= murmur_scramble(segment);
52
- hash ^= (uint32_t) length;
53
- hash ^= hash >> 16;
54
- hash *= 0x85ebca6b;
55
- hash ^= hash >> 13;
56
- hash *= 0xc2b2ae35;
57
- hash ^= hash >> 16;
58
- return hash;
59
- }
60
-
61
- /**
62
- * Hash the value of an integer and return it.
63
- */
64
- static uint32_t
39
+ static uint64_t
65
40
  integer_hash(const pm_integer_t *integer) {
66
- uint32_t hash;
67
- if (integer->values) {
68
- hash = murmur_hash((const uint8_t *) integer->values, sizeof(uint32_t) * integer->length);
69
- } else {
70
- hash = murmur_hash((const uint8_t *) &integer->value, sizeof(uint32_t));
71
- }
72
-
73
- if (integer->negative) {
74
- hash ^= murmur_scramble((uint32_t) 1);
75
- }
76
-
77
- return hash;
41
+ uint64_t magnitude = integer->values ? pm_xxh3((const uint8_t *) integer->values, sizeof(uint32_t) * integer->length) : integer->value;
42
+ return key_hash(PM_INTEGER_NODE, integer->negative, magnitude);
78
43
  }
79
44
 
80
45
  /**
81
- * Return the hash of the given node. It is important that nodes that have
82
- * equivalent static literal values have the same hash. This is because we use
83
- * these hashes to look for duplicates.
46
+ * Return the hash of the key of the given node. It is important that nodes that
47
+ * have equivalent static literal values have the same key. This is because we
48
+ * hash these keys to look for duplicates.
84
49
  */
85
- static uint32_t
86
- node_hash(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
50
+ static uint64_t
51
+ node_hash(const pm_parser_t *parser, const pm_node_t *node) {
87
52
  switch (PM_NODE_TYPE(node)) {
88
- case PM_INTEGER_NODE: {
89
- // Integers hash their value.
90
- const pm_integer_node_t *cast = (const pm_integer_node_t *) node;
91
- return integer_hash(&cast->value);
92
- }
53
+ case PM_INTEGER_NODE:
54
+ return integer_hash(&((const pm_integer_node_t *) node)->value);
93
55
  case PM_SOURCE_LINE_NODE: {
94
- // Source lines hash their line number.
95
- const pm_line_column_t line_column = pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line);
96
- const int32_t *value = &line_column.line;
97
- return murmur_hash((const uint8_t *) value, sizeof(int32_t));
56
+ int64_t line = (int64_t) pm_line_offset_list_line_column(&parser->line_offsets, node->location.start, parser->start_line).line;
57
+ return key_hash(PM_INTEGER_NODE, line < 0, (uint64_t) (line < 0 ? -line : line));
98
58
  }
99
59
  case PM_FLOAT_NODE: {
100
- // Floats hash their value.
101
- const double *value = &((const pm_float_node_t *) node)->value;
102
- return murmur_hash((const uint8_t *) value, sizeof(double));
60
+ const pm_float_node_t *cast = (const pm_float_node_t *) node;
61
+ double value = cast->value;
62
+ if (value == 0.0) value = 0.0; /* Normalize -0.0 to 0.0. */
63
+
64
+ uint64_t bits;
65
+ memcpy(&bits, &value, sizeof(bits));
66
+ return key_hash(PM_FLOAT_NODE, bits, 0);
103
67
  }
104
68
  case PM_RATIONAL_NODE: {
105
- // Rationals hash their numerator and denominator.
106
69
  const pm_rational_node_t *cast = (const pm_rational_node_t *) node;
107
- return integer_hash(&cast->numerator) ^ integer_hash(&cast->denominator) ^ murmur_scramble((uint32_t) cast->base.type);
70
+ return key_hash(PM_RATIONAL_NODE, integer_hash(&cast->numerator), integer_hash(&cast->denominator));
108
71
  }
109
72
  case PM_IMAGINARY_NODE: {
110
- // Imaginaries hash their numeric value. Because their numeric value
111
- // is stored as a subnode, we hash that node and then mix in the
112
- // fact that this is an imaginary node.
113
- const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
114
- return node_hash(metadata, numeric) ^ murmur_scramble((uint32_t) node->type);
73
+ const pm_imaginary_node_t *cast = (const pm_imaginary_node_t *) node;
74
+ return key_hash(PM_IMAGINARY_NODE, node_hash(parser, cast->numeric), 0);
115
75
  }
116
76
  case PM_STRING_NODE: {
117
- // Strings hash their value and mix in their flags so that different
118
- // encodings are not considered equal.
119
- const pm_string_t *value = &((const pm_string_node_t *) node)->unescaped;
77
+ const pm_string_node_t *cast = (const pm_string_node_t *) node;
120
78
 
121
- pm_node_flags_t flags = node->flags;
122
- flags &= (PM_STRING_FLAGS_FORCED_BINARY_ENCODING | PM_STRING_FLAGS_FORCED_UTF8_ENCODING);
79
+ pm_node_flags_t mask = PM_STRING_FLAGS_FORCED_BINARY_ENCODING;
80
+ if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_STRING_FLAGS_FORCED_UTF8_ENCODING;
123
81
 
124
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) flags);
82
+ return key_hash(PM_STRING_NODE, pm_xxh3(pm_string_source(&cast->unescaped), pm_string_length(&cast->unescaped)), node->flags & mask);
125
83
  }
126
84
  case PM_SOURCE_FILE_NODE: {
127
- // Source files hash their value and mix in their flags so that
128
- // different encodings are not considered equal.
129
- const pm_string_t *value = &((const pm_source_file_node_t *) node)->filepath;
130
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t));
85
+ const pm_source_file_node_t *cast = (const pm_source_file_node_t *) node;
86
+ return key_hash(PM_STRING_NODE, pm_xxh3(pm_string_source(&cast->filepath), pm_string_length(&cast->filepath)), 0);
131
87
  }
132
88
  case PM_REGULAR_EXPRESSION_NODE: {
133
- // Regular expressions hash their value and mix in their flags so
134
- // that different encodings are not considered equal.
135
- const pm_string_t *value = &((const pm_regular_expression_node_t *) node)->unescaped;
136
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags);
89
+ const pm_regular_expression_node_t *cast = (const pm_regular_expression_node_t *) node;
90
+ return key_hash(PM_REGULAR_EXPRESSION_NODE, pm_xxh3(pm_string_source(&cast->unescaped), pm_string_length(&cast->unescaped)), node->flags & (pm_node_flags_t) ~PM_REGULAR_EXPRESSION_FLAGS_ONCE);
137
91
  }
138
92
  case PM_SYMBOL_NODE: {
139
- // Symbols hash their value and mix in their flags so that different
140
- // encodings are not considered equal.
141
- const pm_string_t *value = &((const pm_symbol_node_t *) node)->unescaped;
142
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags);
93
+ const pm_symbol_node_t *cast = (const pm_symbol_node_t *) node;
94
+
95
+ pm_node_flags_t mask = PM_SYMBOL_FLAGS_FORCED_BINARY_ENCODING | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING;
96
+ if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_SYMBOL_FLAGS_FORCED_UTF8_ENCODING;
97
+
98
+ return key_hash(PM_SYMBOL_NODE, pm_xxh3(pm_string_source(&cast->unescaped), pm_string_length(&cast->unescaped)), node->flags & mask);
143
99
  }
100
+ case PM_FALSE_NODE:
101
+ case PM_NIL_NODE:
102
+ case PM_SOURCE_ENCODING_NODE:
103
+ case PM_TRUE_NODE:
104
+ return key_hash(PM_NODE_TYPE(node), 0, 0);
144
105
  default:
145
106
  assert(false && "unreachable");
146
107
  return 0;
147
108
  }
148
109
  }
149
110
 
150
- /**
151
- * Insert a node into the node hash. It accepts the hash that should hold the
152
- * new node, the parser that generated the node, the node to insert, and a
153
- * comparison function. The comparison function is used for collision detection,
154
- * and must be able to compare all node types that will be stored in this hash.
155
- */
156
- static pm_node_t *
157
- pm_node_hash_insert(pm_node_hash_t *hash, const pm_static_literals_metadata_t *metadata, pm_node_t *node, bool replace, int (*compare)(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right)) {
158
- // If we are out of space, we need to resize the hash. This will cause all
159
- // of the nodes to be rehashed and reinserted into the new hash.
160
- if (hash->size * 2 >= hash->capacity) {
161
- // First, allocate space for the new node list.
162
- uint32_t new_capacity = hash->capacity == 0 ? 4 : hash->capacity * 2;
163
- pm_node_t **new_nodes = xcalloc(new_capacity, sizeof(pm_node_t *));
164
- if (new_nodes == NULL) return NULL;
165
-
166
- // It turns out to be more efficient to mask the hash value than to use
167
- // the modulo operator. Because our capacities are always powers of two,
168
- // we can use a bitwise AND to get the same result as the modulo
169
- // operator.
170
- uint32_t mask = new_capacity - 1;
171
-
172
- // Now, rehash all of the nodes into the new list.
173
- for (uint32_t index = 0; index < hash->capacity; index++) {
174
- pm_node_t *node = hash->nodes[index];
175
-
176
- if (node != NULL) {
177
- uint32_t index = node_hash(metadata, node) & mask;
178
- new_nodes[index] = node;
179
- }
180
- }
181
-
182
- // Finally, free the old node list and update the hash.
183
- xfree(hash->nodes);
184
- hash->nodes = new_nodes;
185
- hash->capacity = new_capacity;
186
- }
187
-
188
- // Now, insert the node into the hash.
189
- uint32_t mask = hash->capacity - 1;
190
- uint32_t index = node_hash(metadata, node) & mask;
191
-
192
- // We use linear probing to resolve collisions. This means that if the
193
- // current index is occupied, we will move to the next index and try again.
194
- // We are guaranteed that this will eventually find an empty slot because we
195
- // resize the hash when it gets too full.
196
- while (hash->nodes[index] != NULL) {
197
- if (compare(metadata, hash->nodes[index], node) == 0) break;
198
- index = (index + 1) & mask;
199
- }
200
-
201
- // If the current index is occupied, we need to return the node that was
202
- // already in the hash. Otherwise, we can just increment the size and insert
203
- // the new node.
204
- pm_node_t *result = hash->nodes[index];
205
-
206
- if (result == NULL) {
207
- hash->size++;
208
- hash->nodes[index] = node;
209
- } else if (replace) {
210
- hash->nodes[index] = node;
211
- }
212
-
213
- return result;
214
- }
215
-
216
- /**
217
- * Free the internal memory associated with the given node hash.
218
- */
219
- static void
220
- pm_node_hash_free(pm_node_hash_t *hash) {
221
- if (hash->capacity > 0) xfree(hash->nodes);
222
- }
223
-
224
111
  /**
225
112
  * Compare two values that can be compared with a simple numeric comparison.
226
113
  */
227
- #define PM_NUMERIC_COMPARISON(left, right) ((left < right) ? -1 : (left > right) ? 1 : 0)
114
+ #define COMPARE(left_, right_) (((left_) < (right_)) ? -1 : ((left_) > (right_)) ? 1 : 0)
228
115
 
229
116
  /**
230
117
  * Return the integer value of the given node as an int64_t.
231
118
  */
232
119
  static int64_t
233
- pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
120
+ int64_value(const pm_parser_t *parser, const pm_node_t *node) {
234
121
  switch (PM_NODE_TYPE(node)) {
235
122
  case PM_INTEGER_NODE: {
236
123
  const pm_integer_t *integer = &((const pm_integer_node_t *) node)->value;
@@ -240,7 +127,7 @@ pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *n
240
127
  return integer->negative ? -value : value;
241
128
  }
242
129
  case PM_SOURCE_LINE_NODE:
243
- return (int64_t) pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line).line;
130
+ return (int64_t) pm_line_offset_list_line_column(&parser->line_offsets, node->location.start, parser->start_line).line;
244
131
  default:
245
132
  assert(false && "unreachable");
246
133
  return 0;
@@ -252,11 +139,9 @@ pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *n
252
139
  * instances.
253
140
  */
254
141
  static int
255
- pm_compare_integer_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
142
+ compare_integer_nodes(const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) {
256
143
  if (PM_NODE_TYPE_P(left, PM_SOURCE_LINE_NODE) || PM_NODE_TYPE_P(right, PM_SOURCE_LINE_NODE)) {
257
- int64_t left_value = pm_int64_value(metadata, left);
258
- int64_t right_value = pm_int64_value(metadata, right);
259
- return PM_NUMERIC_COMPARISON(left_value, right_value);
144
+ return COMPARE(int64_value(parser, left), int64_value(parser, right));
260
145
  }
261
146
 
262
147
  const pm_integer_t *left_integer = &((const pm_integer_node_t *) left)->value;
@@ -268,48 +153,15 @@ pm_compare_integer_nodes(const pm_static_literals_metadata_t *metadata, const pm
268
153
  * A comparison function for comparing two FloatNode instances.
269
154
  */
270
155
  static int
271
- pm_compare_float_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
272
- const double left_value = ((const pm_float_node_t *) left)->value;
273
- const double right_value = ((const pm_float_node_t *) right)->value;
274
- return PM_NUMERIC_COMPARISON(left_value, right_value);
275
- }
276
-
277
- /**
278
- * A comparison function for comparing two nodes that have attached numbers.
279
- */
280
- static int
281
- pm_compare_number_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
282
- if (PM_NODE_TYPE(left) != PM_NODE_TYPE(right)) {
283
- return PM_NUMERIC_COMPARISON(PM_NODE_TYPE(left), PM_NODE_TYPE(right));
284
- }
285
-
286
- switch (PM_NODE_TYPE(left)) {
287
- case PM_IMAGINARY_NODE:
288
- return pm_compare_number_nodes(metadata, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric);
289
- case PM_RATIONAL_NODE: {
290
- const pm_rational_node_t *left_rational = (const pm_rational_node_t *) left;
291
- const pm_rational_node_t *right_rational = (const pm_rational_node_t *) right;
292
-
293
- int result = pm_integer_compare(&left_rational->denominator, &right_rational->denominator);
294
- if (result != 0) return result;
295
-
296
- return pm_integer_compare(&left_rational->numerator, &right_rational->numerator);
297
- }
298
- case PM_INTEGER_NODE:
299
- return pm_compare_integer_nodes(metadata, left, right);
300
- case PM_FLOAT_NODE:
301
- return pm_compare_float_nodes(metadata, left, right);
302
- default:
303
- assert(false && "unreachable");
304
- return 0;
305
- }
156
+ compare_float_nodes(PRISM_UNUSED const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) {
157
+ return COMPARE(((const pm_float_node_t *) left)->value, ((const pm_float_node_t *) right)->value);
306
158
  }
307
159
 
308
160
  /**
309
161
  * Return a pointer to the string value of the given node.
310
162
  */
311
163
  static const pm_string_t *
312
- pm_string_value(const pm_node_t *node) {
164
+ string_value(const pm_node_t *node) {
313
165
  switch (PM_NODE_TYPE(node)) {
314
166
  case PM_STRING_NODE:
315
167
  return &((const pm_string_node_t *) node)->unescaped;
@@ -325,134 +177,246 @@ pm_string_value(const pm_node_t *node) {
325
177
 
326
178
  /**
327
179
  * A comparison function for comparing two nodes that have attached strings.
180
+ * Only the flags in the given mask take part in the comparison.
181
+ *
182
+ * The FORCED_* flags record how a literal was written. An escape that locks the
183
+ * encoding to UTF-8 (a `\u` escape above U+007F, or any `\u` in a character
184
+ * literal) sets FORCED_UTF8 whether or not the file is already UTF-8. So in a
185
+ * UTF-8 file a `\u00E9` escape and a literal e-acute carry different flags
186
+ * while being the same string, and the flag is left out of the mask for them
187
+ * to compare equally. A source file node never carries a FORCED_* flag, because
188
+ * every instance of __FILE__ in a parse resolves to the same encoding.
328
189
  */
329
190
  static int
330
- pm_compare_string_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
331
- const pm_string_t *left_string = pm_string_value(left);
332
- const pm_string_t *right_string = pm_string_value(right);
333
- return pm_string_compare(left_string, right_string);
191
+ compare_string_nodes(const pm_node_t *left, const pm_node_t *right, pm_node_flags_t mask) {
192
+ const pm_string_t *left_string = string_value(left);
193
+ const pm_string_t *right_string = string_value(right);
194
+
195
+ int result = pm_string_compare(left_string, right_string);
196
+ if (result != 0) return result;
197
+
198
+ /*
199
+ * Equal bytes are not enough. In a binary source file the two bytes written
200
+ * as `"\xC3\xA9"` stay BINARY while a `"\u00E9"` escape is forced to UTF-8,
201
+ * so those are distinct keys even though the bytes match.
202
+ */
203
+ return COMPARE(left->flags & mask, right->flags & mask);
334
204
  }
335
205
 
336
206
  /**
337
- * A comparison function for comparing two RegularExpressionNode instances.
207
+ * A comparison function for comparing two RegularExpressionNode instances. The
208
+ * once flag is left out because it only controls how many times an
209
+ * interpolated regular expression is compiled, not its value.
338
210
  */
339
211
  static int
340
- pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
212
+ compare_regular_expression_nodes(PRISM_UNUSED const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) {
341
213
  const pm_regular_expression_node_t *left_regexp = (const pm_regular_expression_node_t *) left;
342
214
  const pm_regular_expression_node_t *right_regexp = (const pm_regular_expression_node_t *) right;
343
215
 
344
216
  int result = pm_string_compare(&left_regexp->unescaped, &right_regexp->unescaped);
345
217
  if (result != 0) return result;
346
218
 
347
- return PM_NUMERIC_COMPARISON(left_regexp->base.flags, right_regexp->base.flags);
219
+ pm_node_flags_t mask = (pm_node_flags_t) ~PM_REGULAR_EXPRESSION_FLAGS_ONCE;
220
+ return COMPARE(left->flags & mask, right->flags & mask);
348
221
  }
349
222
 
350
- #undef PM_NUMERIC_COMPARISON
351
-
352
223
  /**
353
- * Add a node to the set of static literals.
224
+ * Return the type that the given node is compared as. A source line node can
225
+ * equal an integer node and a source file node can equal a string node, so
226
+ * each takes the type of the node it can equal.
354
227
  */
355
- pm_node_t *
356
- pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line, pm_static_literals_t *literals, pm_node_t *node, bool replace) {
228
+ static enum pm_node_type
229
+ static_literal_type(const pm_node_t *node) {
357
230
  switch (PM_NODE_TYPE(node)) {
358
- case PM_INTEGER_NODE:
359
231
  case PM_SOURCE_LINE_NODE:
360
- return pm_node_hash_insert(
361
- &literals->integer_nodes,
362
- &(pm_static_literals_metadata_t) {
363
- .newline_list = newline_list,
364
- .start_line = start_line,
365
- .encoding_name = NULL
366
- },
367
- node,
368
- replace,
369
- pm_compare_integer_nodes
370
- );
232
+ return PM_INTEGER_NODE;
233
+ case PM_SOURCE_FILE_NODE:
234
+ return PM_STRING_NODE;
235
+ default:
236
+ return PM_NODE_TYPE(node);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * A comparison function for comparing any two static literal nodes. Nodes that
242
+ * are compared as different types are never equal.
243
+ */
244
+ static int
245
+ compare_nodes(const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) {
246
+ enum pm_node_type left_type = static_literal_type(left);
247
+ enum pm_node_type right_type = static_literal_type(right);
248
+ if (left_type != right_type) return COMPARE(left_type, right_type);
249
+
250
+ switch (left_type) {
251
+ case PM_INTEGER_NODE:
252
+ return compare_integer_nodes(parser, left, right);
371
253
  case PM_FLOAT_NODE:
372
- return pm_node_hash_insert(
373
- &literals->float_nodes,
374
- &(pm_static_literals_metadata_t) {
375
- .newline_list = newline_list,
376
- .start_line = start_line,
377
- .encoding_name = NULL
378
- },
379
- node,
380
- replace,
381
- pm_compare_float_nodes
382
- );
383
- case PM_RATIONAL_NODE:
254
+ return compare_float_nodes(parser, left, right);
384
255
  case PM_IMAGINARY_NODE:
385
- return pm_node_hash_insert(
386
- &literals->number_nodes,
387
- &(pm_static_literals_metadata_t) {
388
- .newline_list = newline_list,
389
- .start_line = start_line,
390
- .encoding_name = NULL
391
- },
392
- node,
393
- replace,
394
- pm_compare_number_nodes
395
- );
396
- case PM_STRING_NODE:
397
- case PM_SOURCE_FILE_NODE:
398
- return pm_node_hash_insert(
399
- &literals->string_nodes,
400
- &(pm_static_literals_metadata_t) {
401
- .newline_list = newline_list,
402
- .start_line = start_line,
403
- .encoding_name = NULL
404
- },
405
- node,
406
- replace,
407
- pm_compare_string_nodes
408
- );
409
- case PM_REGULAR_EXPRESSION_NODE:
410
- return pm_node_hash_insert(
411
- &literals->regexp_nodes,
412
- &(pm_static_literals_metadata_t) {
413
- .newline_list = newline_list,
414
- .start_line = start_line,
415
- .encoding_name = NULL
416
- },
417
- node,
418
- replace,
419
- pm_compare_regular_expression_nodes
420
- );
421
- case PM_SYMBOL_NODE:
422
- return pm_node_hash_insert(
423
- &literals->symbol_nodes,
424
- &(pm_static_literals_metadata_t) {
425
- .newline_list = newline_list,
426
- .start_line = start_line,
427
- .encoding_name = NULL
428
- },
429
- node,
430
- replace,
431
- pm_compare_string_nodes
432
- );
433
- case PM_TRUE_NODE: {
434
- pm_node_t *duplicated = literals->true_node;
435
- if ((duplicated == NULL) || replace) literals->true_node = node;
436
- return duplicated;
256
+ return compare_nodes(parser, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric);
257
+ case PM_RATIONAL_NODE: {
258
+ const pm_rational_node_t *left_rational = (const pm_rational_node_t *) left;
259
+ const pm_rational_node_t *right_rational = (const pm_rational_node_t *) right;
260
+
261
+ int result = pm_integer_compare(&left_rational->denominator, &right_rational->denominator);
262
+ if (result != 0) return result;
263
+
264
+ return pm_integer_compare(&left_rational->numerator, &right_rational->numerator);
437
265
  }
438
- case PM_FALSE_NODE: {
439
- pm_node_t *duplicated = literals->false_node;
440
- if ((duplicated == NULL) || replace) literals->false_node = node;
441
- return duplicated;
266
+ case PM_STRING_NODE: {
267
+ pm_node_flags_t mask = PM_STRING_FLAGS_FORCED_BINARY_ENCODING;
268
+ if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_STRING_FLAGS_FORCED_UTF8_ENCODING;
269
+ return compare_string_nodes(left, right, mask);
270
+ }
271
+ case PM_SYMBOL_NODE: {
272
+ pm_node_flags_t mask = PM_SYMBOL_FLAGS_FORCED_BINARY_ENCODING | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING;
273
+ if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_SYMBOL_FLAGS_FORCED_UTF8_ENCODING;
274
+ return compare_string_nodes(left, right, mask);
442
275
  }
443
- case PM_NIL_NODE: {
444
- pm_node_t *duplicated = literals->nil_node;
445
- if ((duplicated == NULL) || replace) literals->nil_node = node;
446
- return duplicated;
276
+ case PM_REGULAR_EXPRESSION_NODE:
277
+ return compare_regular_expression_nodes(parser, left, right);
278
+ case PM_FALSE_NODE:
279
+ case PM_NIL_NODE:
280
+ case PM_SOURCE_ENCODING_NODE:
281
+ case PM_TRUE_NODE:
282
+ return 0;
283
+ default:
284
+ assert(false && "unreachable");
285
+ return 0;
286
+ }
287
+ }
288
+
289
+ #undef COMPARE
290
+
291
+ /**
292
+ * Place an entry into a hash table that is known not to hold an equal node, so
293
+ * no comparisons are needed to find its slot.
294
+ */
295
+ static void
296
+ pm_static_literals_place(pm_static_literals_entry_t *entries, uint32_t mask, pm_static_literals_entry_t entry) {
297
+ uint32_t index = (uint32_t) (entry.hash & mask);
298
+ while (entries[index].node != NULL) {
299
+ index = (index + 1) & mask;
300
+ }
301
+ entries[index] = entry;
302
+ }
303
+
304
+ /**
305
+ * Move the nodes of the set into a new hash table with the given capacity. A
306
+ * set that has no hash table yet hashes its inline nodes. A set that already
307
+ * has one moves its entries, which keep their hashes.
308
+ */
309
+ static void
310
+ pm_static_literals_resize(const pm_parser_t *parser, pm_static_literals_t *literals, uint32_t capacity) {
311
+ pm_static_literals_entry_t *entries = xcalloc(capacity, sizeof(pm_static_literals_entry_t));
312
+ if (entries == NULL) abort();
313
+
314
+ /* It turns out to be more efficient to mask the hash value than to use the
315
+ * modulo operator. Because our capacities are always powers of two, we can
316
+ * use a bitwise AND to get the same result as the modulo operator. */
317
+ uint32_t mask = capacity - 1;
318
+
319
+ if (literals->capacity == 0) {
320
+ for (uint32_t index = 0; index < literals->size; index++) {
321
+ pm_node_t *node = literals->nodes[index];
322
+ pm_static_literals_place(entries, mask, (pm_static_literals_entry_t) { .node = node, .hash = node_hash(parser, node) });
447
323
  }
448
- case PM_SOURCE_ENCODING_NODE: {
449
- pm_node_t *duplicated = literals->source_encoding_node;
450
- if ((duplicated == NULL) || replace) literals->source_encoding_node = node;
451
- return duplicated;
324
+ } else {
325
+ for (uint32_t index = 0; index < literals->capacity; index++) {
326
+ if (literals->entries[index].node != NULL) {
327
+ pm_static_literals_place(entries, mask, literals->entries[index]);
328
+ }
452
329
  }
330
+
331
+ xfree_sized(literals->entries, literals->capacity * sizeof(pm_static_literals_entry_t));
332
+ }
333
+
334
+ literals->entries = entries;
335
+ literals->capacity = capacity;
336
+ }
337
+
338
+ /**
339
+ * Add a node to the set of static literals. If an equal node is already in the
340
+ * set, it is returned (and replaced by the new node if replace is set).
341
+ * Otherwise the new node is inserted and NULL is returned. Nodes that are not
342
+ * static literals are ignored.
343
+ */
344
+ pm_node_t *
345
+ pm_static_literals_add(const pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node, bool replace) {
346
+ switch (PM_NODE_TYPE(node)) {
347
+ case PM_FALSE_NODE:
348
+ case PM_FLOAT_NODE:
349
+ case PM_IMAGINARY_NODE:
350
+ case PM_INTEGER_NODE:
351
+ case PM_NIL_NODE:
352
+ case PM_RATIONAL_NODE:
353
+ case PM_REGULAR_EXPRESSION_NODE:
354
+ case PM_SOURCE_ENCODING_NODE:
355
+ case PM_SOURCE_FILE_NODE:
356
+ case PM_SOURCE_LINE_NODE:
357
+ case PM_STRING_NODE:
358
+ case PM_SYMBOL_NODE:
359
+ case PM_TRUE_NODE:
360
+ break;
453
361
  default:
454
362
  return NULL;
455
363
  }
364
+
365
+ if (literals->capacity == 0) {
366
+ /* A set without a hash table compares the new node against each of
367
+ * its nodes directly. Comparing nodes of different types returns
368
+ * early, so this costs about as much as hashing the new node. */
369
+ for (uint32_t index = 0; index < literals->size; index++) {
370
+ pm_node_t *existing = literals->nodes[index];
371
+
372
+ if (compare_nodes(parser, existing, node) == 0) {
373
+ if (replace) literals->nodes[index] = node;
374
+ return existing;
375
+ }
376
+ }
377
+
378
+ if (literals->size < PM_STATIC_LITERALS_INLINE_CAPACITY) {
379
+ literals->nodes[literals->size++] = node;
380
+ return NULL;
381
+ }
382
+
383
+ /* The set is full, so its nodes move into a hash table. The table is
384
+ * sized to stay at most half full after this node is added, with room
385
+ * to spare before its first resize. */
386
+ pm_static_literals_resize(parser, literals, PM_STATIC_LITERALS_INLINE_CAPACITY * 4);
387
+ } else if (literals->size * 2 >= literals->capacity) {
388
+ /* If we are out of space, we need to resize the hash. */
389
+ pm_static_literals_resize(parser, literals, literals->capacity * 2);
390
+ }
391
+
392
+ uint64_t hash = node_hash(parser, node);
393
+ uint32_t mask = literals->capacity - 1;
394
+ uint32_t index = (uint32_t) (hash & mask);
395
+
396
+ /* We use linear probing to resolve collisions. This means that if the
397
+ * current index is occupied, we will move to the next index and try again.
398
+ * We are guaranteed that this will eventually find an empty slot because we
399
+ * resize the hash when it gets too full. Entries whose hash differs cannot
400
+ * be equal, so only entries with a matching hash are compared. */
401
+ while (literals->entries[index].node != NULL) {
402
+ const pm_static_literals_entry_t *entry = &literals->entries[index];
403
+ if (entry->hash == hash && compare_nodes(parser, entry->node, node) == 0) break;
404
+ index = (index + 1) & mask;
405
+ }
406
+
407
+ /* If the current index is occupied, we need to return the node that was
408
+ * already in the hash. Otherwise, we can just increment the size and insert
409
+ * the new node. */
410
+ pm_node_t *result = literals->entries[index].node;
411
+
412
+ if (result == NULL) {
413
+ literals->size++;
414
+ literals->entries[index] = (pm_static_literals_entry_t) { .node = node, .hash = hash };
415
+ } else if (replace) {
416
+ literals->entries[index].node = node;
417
+ }
418
+
419
+ return result;
456
420
  }
457
421
 
458
422
  /**
@@ -460,12 +424,7 @@ pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line
460
424
  */
461
425
  void
462
426
  pm_static_literals_free(pm_static_literals_t *literals) {
463
- pm_node_hash_free(&literals->integer_nodes);
464
- pm_node_hash_free(&literals->float_nodes);
465
- pm_node_hash_free(&literals->number_nodes);
466
- pm_node_hash_free(&literals->string_nodes);
467
- pm_node_hash_free(&literals->regexp_nodes);
468
- pm_node_hash_free(&literals->symbol_nodes);
427
+ if (literals->capacity > 0) xfree_sized(literals->entries, literals->capacity * sizeof(pm_static_literals_entry_t));
469
428
  }
470
429
 
471
430
  /**
@@ -492,8 +451,8 @@ pm_static_literal_positive_p(const pm_node_t *node) {
492
451
  /**
493
452
  * Create a string-based representation of the given static literal.
494
453
  */
495
- static inline void
496
- pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
454
+ void
455
+ pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) {
497
456
  switch (PM_NODE_TYPE(node)) {
498
457
  case PM_FALSE_NODE:
499
458
  pm_buffer_append_string(buffer, "false", 5);
@@ -502,21 +461,21 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
502
461
  const double value = ((const pm_float_node_t *) node)->value;
503
462
 
504
463
  if (PRISM_ISINF(value)) {
505
- if (*node->location.start == '-') {
464
+ if (parser->start[node->location.start] == '-') {
506
465
  pm_buffer_append_byte(buffer, '-');
507
466
  }
508
467
  pm_buffer_append_string(buffer, "Infinity", 8);
509
468
  } else if (value == 0.0) {
510
- if (*node->location.start == '-') {
469
+ if (parser->start[node->location.start] == '-') {
511
470
  pm_buffer_append_byte(buffer, '-');
512
471
  }
513
472
  pm_buffer_append_string(buffer, "0.0", 3);
514
473
  } else {
515
474
  pm_buffer_append_format(buffer, "%g", value);
516
475
 
517
- // %g will not insert a .0 for 1e100 (we'll get back 1e+100). So
518
- // we check for the decimal point and add it in here if it's not
519
- // present.
476
+ /* %g will not insert a .0 for 1e100 (we'll get back 1e+100). So
477
+ * we check for the decimal point and add it in here if it's not
478
+ * present. */
520
479
  if (pm_buffer_index(buffer, '.') == SIZE_MAX) {
521
480
  size_t exponent_index = pm_buffer_index(buffer, 'e');
522
481
  size_t index = exponent_index == SIZE_MAX ? pm_buffer_length(buffer) : exponent_index;
@@ -530,7 +489,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
530
489
  const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
531
490
  pm_buffer_append_string(buffer, "(0", 2);
532
491
  if (pm_static_literal_positive_p(numeric)) pm_buffer_append_byte(buffer, '+');
533
- pm_static_literal_inspect_node(buffer, metadata, numeric);
492
+ pm_static_literal_inspect(buffer, parser, numeric);
534
493
  if (PM_NODE_TYPE_P(numeric, PM_RATIONAL_NODE)) {
535
494
  pm_buffer_append_byte(buffer, '*');
536
495
  }
@@ -566,7 +525,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
566
525
  break;
567
526
  }
568
527
  case PM_SOURCE_ENCODING_NODE:
569
- pm_buffer_append_format(buffer, "#<Encoding:%s>", metadata->encoding_name);
528
+ pm_buffer_append_format(buffer, "#<Encoding:%s>", parser->encoding->name);
570
529
  break;
571
530
  case PM_SOURCE_FILE_NODE: {
572
531
  const pm_string_t *filepath = &((const pm_source_file_node_t *) node)->filepath;
@@ -576,7 +535,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
576
535
  break;
577
536
  }
578
537
  case PM_SOURCE_LINE_NODE:
579
- pm_buffer_append_format(buffer, "%d", pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line).line);
538
+ pm_buffer_append_format(buffer, "%" PRIi32, pm_line_offset_list_line_column(&parser->line_offsets, node->location.start, parser->start_line).line);
580
539
  break;
581
540
  case PM_STRING_NODE: {
582
541
  const pm_string_t *unescaped = &((const pm_string_node_t *) node)->unescaped;
@@ -599,19 +558,3 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
599
558
  break;
600
559
  }
601
560
  }
602
-
603
- /**
604
- * Create a string-based representation of the given static literal.
605
- */
606
- void
607
- pm_static_literal_inspect(pm_buffer_t *buffer, const pm_newline_list_t *newline_list, int32_t start_line, const char *encoding_name, const pm_node_t *node) {
608
- pm_static_literal_inspect_node(
609
- buffer,
610
- &(pm_static_literals_metadata_t) {
611
- .newline_list = newline_list,
612
- .start_line = start_line,
613
- .encoding_name = encoding_name
614
- },
615
- node
616
- );
617
- }