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