prism 2.0.0 → 2.1.0

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.
@@ -7,280 +7,117 @@
7
7
  #include "prism/internal/buffer.h"
8
8
  #include "prism/internal/integer.h"
9
9
  #include "prism/internal/isinf.h"
10
+ #include "prism/internal/parser.h"
10
11
  #include "prism/internal/stringy.h"
12
+ #include "prism/internal/xxh3.h"
11
13
 
12
14
  #include <assert.h>
15
+ #include <inttypes.h>
13
16
  #include <math.h>
14
17
  #include <stdio.h>
15
18
  #include <stdlib.h>
16
19
  #include <string.h>
17
20
 
18
21
  /**
19
- * A small struct used for passing around a subset of the information that is
20
- * stored on the parser. We use this to avoid having static literals explicitly
21
- * 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.
22
27
  */
23
- typedef struct {
24
- /** The list of newline offsets to use to calculate line numbers. */
25
- const pm_line_offset_list_t *line_offsets;
26
-
27
- /** The start of the source being parsed. */
28
- const uint8_t *start;
29
-
30
- /** The line number that the parser starts on. */
31
- int32_t start_line;
32
-
33
- /** The encoding that the parser is using. */
34
- const pm_encoding_t *encoding;
35
- } pm_static_literals_metadata_t;
36
-
37
- static PRISM_INLINE uint32_t
38
- murmur_scramble(uint32_t value) {
39
- value *= 0xcc9e2d51;
40
- value = (value << 15) | (value >> 17);
41
- value *= 0x1b873593;
42
- 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));
43
32
  }
44
33
 
45
34
  /**
46
- * Murmur hash (https://en.wikipedia.org/wiki/MurmurHash) is a non-cryptographic
47
- * general-purpose hash function. It is fast, which is what we care about in
48
- * 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.
49
38
  */
50
- static uint32_t
51
- murmur_hash(const uint8_t *key, size_t length) {
52
- uint32_t hash = 0x9747b28c;
53
- uint32_t segment;
54
-
55
- for (size_t index = length >> 2; index; index--) {
56
- memcpy(&segment, key, sizeof(uint32_t));
57
- key += sizeof(uint32_t);
58
- hash ^= murmur_scramble(segment);
59
- hash = (hash << 13) | (hash >> 19);
60
- hash = hash * 5 + 0xe6546b64;
61
- }
62
-
63
- segment = 0;
64
- for (size_t index = length & 3; index; index--) {
65
- segment <<= 8;
66
- segment |= key[index - 1];
67
- }
68
-
69
- hash ^= murmur_scramble(segment);
70
- hash ^= (uint32_t) length;
71
- hash ^= hash >> 16;
72
- hash *= 0x85ebca6b;
73
- hash ^= hash >> 13;
74
- hash *= 0xc2b2ae35;
75
- hash ^= hash >> 16;
76
- return hash;
77
- }
78
-
79
- /**
80
- * Hash the value of an integer and return it.
81
- */
82
- static uint32_t
39
+ static uint64_t
83
40
  integer_hash(const pm_integer_t *integer) {
84
- uint32_t hash;
85
- if (integer->values) {
86
- hash = murmur_hash((const uint8_t *) integer->values, sizeof(uint32_t) * integer->length);
87
- } else {
88
- hash = murmur_hash((const uint8_t *) &integer->value, sizeof(uint32_t));
89
- }
90
-
91
- if (integer->negative) {
92
- hash ^= murmur_scramble((uint32_t) 1);
93
- }
94
-
95
- return hash;
96
- }
97
-
98
- /**
99
- * Return the flags of the given node that determine the encoding of its string.
100
- *
101
- * The FORCED_* flags record how a literal was written. An escape that locks the
102
- * encoding to UTF-8 (a `\u` escape above U+007F, or any `\u` in a character
103
- * literal) sets FORCED_UTF8 whether or not the file is already UTF-8. So in a
104
- * UTF-8 file a `\u00E9` escape and a literal e-acute carry different flags
105
- * while being the same string, and the flag has to be dropped for them to
106
- * compare equally.
107
- */
108
- static pm_node_flags_t
109
- node_encoding_flags(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
110
- switch (PM_NODE_TYPE(node)) {
111
- case PM_STRING_NODE: {
112
- pm_node_flags_t mask = PM_STRING_FLAGS_FORCED_BINARY_ENCODING;
113
- if (metadata->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_STRING_FLAGS_FORCED_UTF8_ENCODING;
114
- return node->flags & mask;
115
- }
116
- case PM_SYMBOL_NODE: {
117
- pm_node_flags_t mask = PM_SYMBOL_FLAGS_FORCED_BINARY_ENCODING | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING;
118
- if (metadata->encoding != PM_ENCODING_UTF_8_ENTRY) mask |= PM_SYMBOL_FLAGS_FORCED_UTF8_ENCODING;
119
- return node->flags & mask;
120
- }
121
- case PM_SOURCE_FILE_NODE:
122
- /* __FILE__ takes the encoding of the filepath, and every instance
123
- * of it in a parse resolves the same way. */
124
- return 0;
125
- default:
126
- assert(false && "unreachable");
127
- return 0;
128
- }
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);
129
43
  }
130
44
 
131
45
  /**
132
- * Return the hash of the given node. It is important that nodes that have
133
- * equivalent static literal values have the same hash. This is because we use
134
- * 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.
135
49
  */
136
- static uint32_t
137
- 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) {
138
52
  switch (PM_NODE_TYPE(node)) {
139
- case PM_INTEGER_NODE: {
140
- // Integers hash their value.
141
- const pm_integer_node_t *cast = (const pm_integer_node_t *) node;
142
- return integer_hash(&cast->value);
143
- }
53
+ case PM_INTEGER_NODE:
54
+ return integer_hash(&((const pm_integer_node_t *) node)->value);
144
55
  case PM_SOURCE_LINE_NODE: {
145
- // Source lines hash their line number.
146
- const pm_line_column_t line_column = pm_line_offset_list_line_column(metadata->line_offsets, node->location.start, metadata->start_line);
147
- const int32_t *value = &line_column.line;
148
- 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));
149
58
  }
150
59
  case PM_FLOAT_NODE: {
151
- // Floats hash their value.
152
- const double *value = &((const pm_float_node_t *) node)->value;
153
- 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);
154
67
  }
155
68
  case PM_RATIONAL_NODE: {
156
- // Rationals hash their numerator and denominator.
157
69
  const pm_rational_node_t *cast = (const pm_rational_node_t *) node;
158
- 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));
159
71
  }
160
72
  case PM_IMAGINARY_NODE: {
161
- // Imaginaries hash their numeric value. Because their numeric value
162
- // is stored as a subnode, we hash that node and then mix in the
163
- // fact that this is an imaginary node.
164
- const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
165
- 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);
166
75
  }
167
76
  case PM_STRING_NODE: {
168
- // Strings hash their value and mix in their flags so that different
169
- // encodings are not considered equal.
170
- const pm_string_t *value = &((const pm_string_node_t *) node)->unescaped;
171
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node_encoding_flags(metadata, node));
77
+ const pm_string_node_t *cast = (const pm_string_node_t *) node;
78
+
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;
81
+
82
+ return key_hash(PM_STRING_NODE, pm_xxh3(pm_string_source(&cast->unescaped), pm_string_length(&cast->unescaped)), node->flags & mask);
172
83
  }
173
84
  case PM_SOURCE_FILE_NODE: {
174
- // Source files hash their value and mix in their flags so that
175
- // different encodings are not considered equal.
176
- const pm_string_t *value = &((const pm_source_file_node_t *) node)->filepath;
177
- 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);
178
87
  }
179
88
  case PM_REGULAR_EXPRESSION_NODE: {
180
- // Regular expressions hash their value and mix in their flags so
181
- // that different encodings are not considered equal.
182
- const pm_string_t *value = &((const pm_regular_expression_node_t *) node)->unescaped;
183
- 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);
184
91
  }
185
92
  case PM_SYMBOL_NODE: {
186
- // Symbols hash their value and mix in their flags so that different
187
- // encodings are not considered equal.
188
- const pm_string_t *value = &((const pm_symbol_node_t *) node)->unescaped;
189
- return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node_encoding_flags(metadata, node));
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);
190
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);
191
105
  default:
192
106
  assert(false && "unreachable");
193
107
  return 0;
194
108
  }
195
109
  }
196
110
 
197
- /**
198
- * Insert a node into the node hash. It accepts the hash that should hold the
199
- * new node, the parser that generated the node, the node to insert, and a
200
- * comparison function. The comparison function is used for collision detection,
201
- * and must be able to compare all node types that will be stored in this hash.
202
- */
203
- static pm_node_t *
204
- 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)) {
205
- // If we are out of space, we need to resize the hash. This will cause all
206
- // of the nodes to be rehashed and reinserted into the new hash.
207
- if (hash->size * 2 >= hash->capacity) {
208
- // First, allocate space for the new node list.
209
- uint32_t new_capacity = hash->capacity == 0 ? 4 : hash->capacity * 2;
210
- pm_node_t **new_nodes = xcalloc(new_capacity, sizeof(pm_node_t *));
211
- if (new_nodes == NULL) abort();
212
-
213
- // It turns out to be more efficient to mask the hash value than to use
214
- // the modulo operator. Because our capacities are always powers of two,
215
- // we can use a bitwise AND to get the same result as the modulo
216
- // operator.
217
- uint32_t mask = new_capacity - 1;
218
-
219
- // Now, rehash all of the nodes into the new list.
220
- for (uint32_t index = 0; index < hash->capacity; index++) {
221
- pm_node_t *node = hash->nodes[index];
222
-
223
- if (node != NULL) {
224
- uint32_t new_index = node_hash(metadata, node) & mask;
225
- while (new_nodes[new_index] != NULL) {
226
- new_index = (new_index + 1) & mask;
227
- }
228
- new_nodes[new_index] = node;
229
- }
230
- }
231
-
232
- // Finally, free the old node list and update the hash.
233
- xfree_sized(hash->nodes, hash->capacity * sizeof(pm_node_t *));
234
- hash->nodes = new_nodes;
235
- hash->capacity = new_capacity;
236
- }
237
-
238
- // Now, insert the node into the hash.
239
- uint32_t mask = hash->capacity - 1;
240
- uint32_t index = node_hash(metadata, node) & mask;
241
-
242
- // We use linear probing to resolve collisions. This means that if the
243
- // current index is occupied, we will move to the next index and try again.
244
- // We are guaranteed that this will eventually find an empty slot because we
245
- // resize the hash when it gets too full.
246
- while (hash->nodes[index] != NULL) {
247
- if (compare(metadata, hash->nodes[index], node) == 0) break;
248
- index = (index + 1) & mask;
249
- }
250
-
251
- // If the current index is occupied, we need to return the node that was
252
- // already in the hash. Otherwise, we can just increment the size and insert
253
- // the new node.
254
- pm_node_t *result = hash->nodes[index];
255
-
256
- if (result == NULL) {
257
- hash->size++;
258
- hash->nodes[index] = node;
259
- } else if (replace) {
260
- hash->nodes[index] = node;
261
- }
262
-
263
- return result;
264
- }
265
-
266
- /**
267
- * Free the internal memory associated with the given node hash.
268
- */
269
- static void
270
- pm_node_hash_free(pm_node_hash_t *hash) {
271
- if (hash->capacity > 0) xfree_sized(hash->nodes, hash->capacity * sizeof(pm_node_t *));
272
- }
273
-
274
111
  /**
275
112
  * Compare two values that can be compared with a simple numeric comparison.
276
113
  */
277
- #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)
278
115
 
279
116
  /**
280
117
  * Return the integer value of the given node as an int64_t.
281
118
  */
282
119
  static int64_t
283
- 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) {
284
121
  switch (PM_NODE_TYPE(node)) {
285
122
  case PM_INTEGER_NODE: {
286
123
  const pm_integer_t *integer = &((const pm_integer_node_t *) node)->value;
@@ -290,7 +127,7 @@ pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *n
290
127
  return integer->negative ? -value : value;
291
128
  }
292
129
  case PM_SOURCE_LINE_NODE:
293
- return (int64_t) pm_line_offset_list_line_column(metadata->line_offsets, 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;
294
131
  default:
295
132
  assert(false && "unreachable");
296
133
  return 0;
@@ -302,11 +139,9 @@ pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *n
302
139
  * instances.
303
140
  */
304
141
  static int
305
- 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) {
306
143
  if (PM_NODE_TYPE_P(left, PM_SOURCE_LINE_NODE) || PM_NODE_TYPE_P(right, PM_SOURCE_LINE_NODE)) {
307
- int64_t left_value = pm_int64_value(metadata, left);
308
- int64_t right_value = pm_int64_value(metadata, right);
309
- return PM_NUMERIC_COMPARISON(left_value, right_value);
144
+ return COMPARE(int64_value(parser, left), int64_value(parser, right));
310
145
  }
311
146
 
312
147
  const pm_integer_t *left_integer = &((const pm_integer_node_t *) left)->value;
@@ -318,48 +153,15 @@ pm_compare_integer_nodes(const pm_static_literals_metadata_t *metadata, const pm
318
153
  * A comparison function for comparing two FloatNode instances.
319
154
  */
320
155
  static int
321
- pm_compare_float_nodes(PRISM_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
322
- const double left_value = ((const pm_float_node_t *) left)->value;
323
- const double right_value = ((const pm_float_node_t *) right)->value;
324
- return PM_NUMERIC_COMPARISON(left_value, right_value);
325
- }
326
-
327
- /**
328
- * A comparison function for comparing two nodes that have attached numbers.
329
- */
330
- static int
331
- pm_compare_number_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
332
- if (PM_NODE_TYPE(left) != PM_NODE_TYPE(right)) {
333
- return PM_NUMERIC_COMPARISON(PM_NODE_TYPE(left), PM_NODE_TYPE(right));
334
- }
335
-
336
- switch (PM_NODE_TYPE(left)) {
337
- case PM_IMAGINARY_NODE:
338
- return pm_compare_number_nodes(metadata, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric);
339
- case PM_RATIONAL_NODE: {
340
- const pm_rational_node_t *left_rational = (const pm_rational_node_t *) left;
341
- const pm_rational_node_t *right_rational = (const pm_rational_node_t *) right;
342
-
343
- int result = pm_integer_compare(&left_rational->denominator, &right_rational->denominator);
344
- if (result != 0) return result;
345
-
346
- return pm_integer_compare(&left_rational->numerator, &right_rational->numerator);
347
- }
348
- case PM_INTEGER_NODE:
349
- return pm_compare_integer_nodes(metadata, left, right);
350
- case PM_FLOAT_NODE:
351
- return pm_compare_float_nodes(metadata, left, right);
352
- default:
353
- assert(false && "unreachable");
354
- return 0;
355
- }
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);
356
158
  }
357
159
 
358
160
  /**
359
161
  * Return a pointer to the string value of the given node.
360
162
  */
361
163
  static const pm_string_t *
362
- pm_string_value(const pm_node_t *node) {
164
+ string_value(const pm_node_t *node) {
363
165
  switch (PM_NODE_TYPE(node)) {
364
166
  case PM_STRING_NODE:
365
167
  return &((const pm_string_node_t *) node)->unescaped;
@@ -375,11 +177,20 @@ pm_string_value(const pm_node_t *node) {
375
177
 
376
178
  /**
377
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.
378
189
  */
379
190
  static int
380
- pm_compare_string_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
381
- const pm_string_t *left_string = pm_string_value(left);
382
- const pm_string_t *right_string = pm_string_value(right);
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);
383
194
 
384
195
  int result = pm_string_compare(left_string, right_string);
385
196
  if (result != 0) return result;
@@ -389,137 +200,223 @@ pm_compare_string_nodes(const pm_static_literals_metadata_t *metadata, const pm_
389
200
  * as `"\xC3\xA9"` stay BINARY while a `"\u00E9"` escape is forced to UTF-8,
390
201
  * so those are distinct keys even though the bytes match.
391
202
  */
392
- pm_node_flags_t left_flags = node_encoding_flags(metadata, left);
393
- pm_node_flags_t right_flags = node_encoding_flags(metadata, right);
394
- return PM_NUMERIC_COMPARISON(left_flags, right_flags);
203
+ return COMPARE(left->flags & mask, right->flags & mask);
395
204
  }
396
205
 
397
206
  /**
398
- * 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.
399
210
  */
400
211
  static int
401
- pm_compare_regular_expression_nodes(PRISM_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) {
402
213
  const pm_regular_expression_node_t *left_regexp = (const pm_regular_expression_node_t *) left;
403
214
  const pm_regular_expression_node_t *right_regexp = (const pm_regular_expression_node_t *) right;
404
215
 
405
216
  int result = pm_string_compare(&left_regexp->unescaped, &right_regexp->unescaped);
406
217
  if (result != 0) return result;
407
218
 
408
- 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);
409
221
  }
410
222
 
411
- #undef PM_NUMERIC_COMPARISON
412
-
413
223
  /**
414
- * 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.
415
227
  */
416
- pm_node_t *
417
- pm_static_literals_add(const pm_line_offset_list_t *line_offsets, const uint8_t *start, int32_t start_line, const pm_encoding_t *encoding, 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) {
418
230
  switch (PM_NODE_TYPE(node)) {
419
- case PM_INTEGER_NODE:
420
231
  case PM_SOURCE_LINE_NODE:
421
- return pm_node_hash_insert(
422
- &literals->integer_nodes,
423
- &(pm_static_literals_metadata_t) {
424
- .line_offsets = line_offsets,
425
- .start = start,
426
- .start_line = start_line,
427
- .encoding = encoding
428
- },
429
- node,
430
- replace,
431
- pm_compare_integer_nodes
432
- );
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);
433
253
  case PM_FLOAT_NODE:
434
- return pm_node_hash_insert(
435
- &literals->float_nodes,
436
- &(pm_static_literals_metadata_t) {
437
- .line_offsets = line_offsets,
438
- .start = start,
439
- .start_line = start_line,
440
- .encoding = encoding
441
- },
442
- node,
443
- replace,
444
- pm_compare_float_nodes
445
- );
446
- case PM_RATIONAL_NODE:
254
+ return compare_float_nodes(parser, left, right);
447
255
  case PM_IMAGINARY_NODE:
448
- return pm_node_hash_insert(
449
- &literals->number_nodes,
450
- &(pm_static_literals_metadata_t) {
451
- .line_offsets = line_offsets,
452
- .start = start,
453
- .start_line = start_line,
454
- .encoding = encoding
455
- },
456
- node,
457
- replace,
458
- pm_compare_number_nodes
459
- );
460
- case PM_STRING_NODE:
461
- case PM_SOURCE_FILE_NODE:
462
- return pm_node_hash_insert(
463
- &literals->string_nodes,
464
- &(pm_static_literals_metadata_t) {
465
- .line_offsets = line_offsets,
466
- .start = start,
467
- .start_line = start_line,
468
- .encoding = encoding
469
- },
470
- node,
471
- replace,
472
- pm_compare_string_nodes
473
- );
474
- case PM_REGULAR_EXPRESSION_NODE:
475
- return pm_node_hash_insert(
476
- &literals->regexp_nodes,
477
- &(pm_static_literals_metadata_t) {
478
- .line_offsets = line_offsets,
479
- .start = start,
480
- .start_line = start_line,
481
- .encoding = encoding
482
- },
483
- node,
484
- replace,
485
- pm_compare_regular_expression_nodes
486
- );
487
- case PM_SYMBOL_NODE:
488
- return pm_node_hash_insert(
489
- &literals->symbol_nodes,
490
- &(pm_static_literals_metadata_t) {
491
- .line_offsets = line_offsets,
492
- .start = start,
493
- .start_line = start_line,
494
- .encoding = encoding
495
- },
496
- node,
497
- replace,
498
- pm_compare_string_nodes
499
- );
500
- case PM_TRUE_NODE: {
501
- pm_node_t *duplicated = literals->true_node;
502
- if ((duplicated == NULL) || replace) literals->true_node = node;
503
- 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);
265
+ }
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);
504
270
  }
505
- case PM_FALSE_NODE: {
506
- pm_node_t *duplicated = literals->false_node;
507
- if ((duplicated == NULL) || replace) literals->false_node = node;
508
- return duplicated;
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);
509
275
  }
510
- case PM_NIL_NODE: {
511
- pm_node_t *duplicated = literals->nil_node;
512
- if ((duplicated == NULL) || replace) literals->nil_node = node;
513
- 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) });
514
323
  }
515
- case PM_SOURCE_ENCODING_NODE: {
516
- pm_node_t *duplicated = literals->source_encoding_node;
517
- if ((duplicated == NULL) || replace) literals->source_encoding_node = node;
518
- 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
+ }
519
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;
520
361
  default:
521
362
  return NULL;
522
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;
523
420
  }
524
421
 
525
422
  /**
@@ -527,12 +424,7 @@ pm_static_literals_add(const pm_line_offset_list_t *line_offsets, const uint8_t
527
424
  */
528
425
  void
529
426
  pm_static_literals_free(pm_static_literals_t *literals) {
530
- pm_node_hash_free(&literals->integer_nodes);
531
- pm_node_hash_free(&literals->float_nodes);
532
- pm_node_hash_free(&literals->number_nodes);
533
- pm_node_hash_free(&literals->string_nodes);
534
- pm_node_hash_free(&literals->regexp_nodes);
535
- pm_node_hash_free(&literals->symbol_nodes);
427
+ if (literals->capacity > 0) xfree_sized(literals->entries, literals->capacity * sizeof(pm_static_literals_entry_t));
536
428
  }
537
429
 
538
430
  /**
@@ -559,8 +451,8 @@ pm_static_literal_positive_p(const pm_node_t *node) {
559
451
  /**
560
452
  * Create a string-based representation of the given static literal.
561
453
  */
562
- static PRISM_INLINE void
563
- 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) {
564
456
  switch (PM_NODE_TYPE(node)) {
565
457
  case PM_FALSE_NODE:
566
458
  pm_buffer_append_string(buffer, "false", 5);
@@ -569,21 +461,21 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
569
461
  const double value = ((const pm_float_node_t *) node)->value;
570
462
 
571
463
  if (PRISM_ISINF(value)) {
572
- if (metadata->start[node->location.start] == '-') {
464
+ if (parser->start[node->location.start] == '-') {
573
465
  pm_buffer_append_byte(buffer, '-');
574
466
  }
575
467
  pm_buffer_append_string(buffer, "Infinity", 8);
576
468
  } else if (value == 0.0) {
577
- if (metadata->start[node->location.start] == '-') {
469
+ if (parser->start[node->location.start] == '-') {
578
470
  pm_buffer_append_byte(buffer, '-');
579
471
  }
580
472
  pm_buffer_append_string(buffer, "0.0", 3);
581
473
  } else {
582
474
  pm_buffer_append_format(buffer, "%g", value);
583
475
 
584
- // %g will not insert a .0 for 1e100 (we'll get back 1e+100). So
585
- // we check for the decimal point and add it in here if it's not
586
- // 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. */
587
479
  if (pm_buffer_index(buffer, '.') == SIZE_MAX) {
588
480
  size_t exponent_index = pm_buffer_index(buffer, 'e');
589
481
  size_t index = exponent_index == SIZE_MAX ? pm_buffer_length(buffer) : exponent_index;
@@ -597,7 +489,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
597
489
  const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
598
490
  pm_buffer_append_string(buffer, "(0", 2);
599
491
  if (pm_static_literal_positive_p(numeric)) pm_buffer_append_byte(buffer, '+');
600
- pm_static_literal_inspect_node(buffer, metadata, numeric);
492
+ pm_static_literal_inspect(buffer, parser, numeric);
601
493
  if (PM_NODE_TYPE_P(numeric, PM_RATIONAL_NODE)) {
602
494
  pm_buffer_append_byte(buffer, '*');
603
495
  }
@@ -633,7 +525,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
633
525
  break;
634
526
  }
635
527
  case PM_SOURCE_ENCODING_NODE:
636
- pm_buffer_append_format(buffer, "#<Encoding:%s>", metadata->encoding->name);
528
+ pm_buffer_append_format(buffer, "#<Encoding:%s>", parser->encoding->name);
637
529
  break;
638
530
  case PM_SOURCE_FILE_NODE: {
639
531
  const pm_string_t *filepath = &((const pm_source_file_node_t *) node)->filepath;
@@ -643,7 +535,7 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
643
535
  break;
644
536
  }
645
537
  case PM_SOURCE_LINE_NODE:
646
- pm_buffer_append_format(buffer, "%d", pm_line_offset_list_line_column(metadata->line_offsets, 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);
647
539
  break;
648
540
  case PM_STRING_NODE: {
649
541
  const pm_string_t *unescaped = &((const pm_string_node_t *) node)->unescaped;
@@ -666,20 +558,3 @@ pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_met
666
558
  break;
667
559
  }
668
560
  }
669
-
670
- /**
671
- * Create a string-based representation of the given static literal.
672
- */
673
- void
674
- pm_static_literal_inspect(pm_buffer_t *buffer, const pm_line_offset_list_t *line_offsets, const uint8_t *start, int32_t start_line, const pm_encoding_t *encoding, const pm_node_t *node) {
675
- pm_static_literal_inspect_node(
676
- buffer,
677
- &(pm_static_literals_metadata_t) {
678
- .line_offsets = line_offsets,
679
- .start = start,
680
- .start_line = start_line,
681
- .encoding = encoding
682
- },
683
- node
684
- );
685
- }