herb 0.7.1-x86-linux-musl → 0.7.3-x86-linux-musl

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +2 -0
  3. data/README.md +1 -1
  4. data/Rakefile +46 -1
  5. data/config.yml +714 -0
  6. data/ext/herb/error_helpers.c +27 -27
  7. data/ext/herb/extconf.rb +2 -1
  8. data/ext/herb/extension.c +6 -6
  9. data/ext/herb/extension_helpers.c +3 -3
  10. data/ext/herb/nodes.c +35 -35
  11. data/herb.gemspec +3 -0
  12. data/lib/herb/3.0/herb.so +0 -0
  13. data/lib/herb/3.1/herb.so +0 -0
  14. data/lib/herb/3.2/herb.so +0 -0
  15. data/lib/herb/3.3/herb.so +0 -0
  16. data/lib/herb/3.4/herb.so +0 -0
  17. data/lib/herb/engine/debug_visitor.rb +41 -21
  18. data/lib/herb/engine.rb +20 -6
  19. data/lib/herb/version.rb +1 -1
  20. data/sig/herb/engine/debug_visitor.rbs +3 -3
  21. data/sig/herb/engine.rbs +5 -0
  22. data/src/analyze.c +5 -9
  23. data/src/analyze_helpers.c +17 -6
  24. data/src/include/pretty_print.h +1 -1
  25. data/src/include/version.h +1 -1
  26. data/src/parser.c +6 -9
  27. data/src/pretty_print.c +1 -1
  28. data/templates/ext/herb/error_helpers.c.erb +85 -0
  29. data/templates/ext/herb/error_helpers.h.erb +12 -0
  30. data/templates/ext/herb/nodes.c.erb +90 -0
  31. data/templates/ext/herb/nodes.h.erb +9 -0
  32. data/templates/javascript/packages/core/src/errors.ts.erb +193 -0
  33. data/templates/javascript/packages/core/src/node-type-guards.ts.erb +325 -0
  34. data/templates/javascript/packages/core/src/nodes.ts.erb +414 -0
  35. data/templates/javascript/packages/core/src/visitor.ts.erb +29 -0
  36. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +113 -0
  37. data/templates/javascript/packages/node/extension/error_helpers.h.erb +17 -0
  38. data/templates/javascript/packages/node/extension/nodes.cpp.erb +111 -0
  39. data/templates/javascript/packages/node/extension/nodes.h.erb +17 -0
  40. data/templates/lib/herb/ast/nodes.rb.erb +117 -0
  41. data/templates/lib/herb/errors.rb.erb +106 -0
  42. data/templates/lib/herb/visitor.rb.erb +28 -0
  43. data/templates/sig/serialized_ast_errors.rbs.erb +10 -0
  44. data/templates/sig/serialized_ast_nodes.rbs.erb +10 -0
  45. data/templates/src/ast_nodes.c.erb +145 -0
  46. data/templates/src/ast_pretty_print.c.erb +97 -0
  47. data/templates/src/errors.c.erb +245 -0
  48. data/templates/src/include/ast_nodes.h.erb +46 -0
  49. data/templates/src/include/ast_pretty_print.h.erb +14 -0
  50. data/templates/src/include/errors.h.erb +58 -0
  51. data/templates/src/visitor.c.erb +47 -0
  52. data/templates/template.rb +406 -0
  53. data/templates/wasm/error_helpers.cpp.erb +93 -0
  54. data/templates/wasm/error_helpers.h.erb +15 -0
  55. data/templates/wasm/nodes.cpp.erb +79 -0
  56. data/templates/wasm/nodes.h.erb +15 -0
  57. data/vendor/prism/Rakefile +75 -0
  58. data/vendor/prism/config.yml +4713 -0
  59. data/vendor/prism/include/prism/ast.h +8190 -0
  60. data/vendor/prism/include/prism/defines.h +260 -0
  61. data/vendor/prism/include/prism/diagnostic.h +455 -0
  62. data/vendor/prism/include/prism/encoding.h +283 -0
  63. data/vendor/prism/include/prism/node.h +129 -0
  64. data/vendor/prism/include/prism/options.h +482 -0
  65. data/vendor/prism/include/prism/pack.h +163 -0
  66. data/vendor/prism/include/prism/parser.h +933 -0
  67. data/vendor/prism/include/prism/prettyprint.h +34 -0
  68. data/vendor/prism/include/prism/regexp.h +43 -0
  69. data/vendor/prism/include/prism/static_literals.h +121 -0
  70. data/vendor/prism/include/prism/util/pm_buffer.h +236 -0
  71. data/vendor/prism/include/prism/util/pm_char.h +204 -0
  72. data/vendor/prism/include/prism/util/pm_constant_pool.h +218 -0
  73. data/vendor/prism/include/prism/util/pm_integer.h +130 -0
  74. data/vendor/prism/include/prism/util/pm_list.h +103 -0
  75. data/vendor/prism/include/prism/util/pm_memchr.h +29 -0
  76. data/vendor/prism/include/prism/util/pm_newline_list.h +113 -0
  77. data/vendor/prism/include/prism/util/pm_string.h +200 -0
  78. data/vendor/prism/include/prism/util/pm_strncasecmp.h +32 -0
  79. data/vendor/prism/include/prism/util/pm_strpbrk.h +46 -0
  80. data/vendor/prism/include/prism/version.h +29 -0
  81. data/vendor/prism/include/prism.h +408 -0
  82. data/vendor/prism/src/diagnostic.c +848 -0
  83. data/vendor/prism/src/encoding.c +5235 -0
  84. data/vendor/prism/src/node.c +8676 -0
  85. data/vendor/prism/src/options.c +328 -0
  86. data/vendor/prism/src/pack.c +509 -0
  87. data/vendor/prism/src/prettyprint.c +8941 -0
  88. data/vendor/prism/src/prism.c +23302 -0
  89. data/vendor/prism/src/regexp.c +790 -0
  90. data/vendor/prism/src/serialize.c +2268 -0
  91. data/vendor/prism/src/static_literals.c +617 -0
  92. data/vendor/prism/src/token_type.c +703 -0
  93. data/vendor/prism/src/util/pm_buffer.c +357 -0
  94. data/vendor/prism/src/util/pm_char.c +318 -0
  95. data/vendor/prism/src/util/pm_constant_pool.c +342 -0
  96. data/vendor/prism/src/util/pm_integer.c +670 -0
  97. data/vendor/prism/src/util/pm_list.c +49 -0
  98. data/vendor/prism/src/util/pm_memchr.c +35 -0
  99. data/vendor/prism/src/util/pm_newline_list.c +125 -0
  100. data/vendor/prism/src/util/pm_string.c +383 -0
  101. data/vendor/prism/src/util/pm_strncasecmp.c +36 -0
  102. data/vendor/prism/src/util/pm_strpbrk.c +206 -0
  103. data/vendor/prism/templates/ext/prism/api_node.c.erb +282 -0
  104. data/vendor/prism/templates/include/prism/ast.h.erb +226 -0
  105. data/vendor/prism/templates/include/prism/diagnostic.h.erb +130 -0
  106. data/vendor/prism/templates/java/org/prism/AbstractNodeVisitor.java.erb +22 -0
  107. data/vendor/prism/templates/java/org/prism/Loader.java.erb +434 -0
  108. data/vendor/prism/templates/java/org/prism/Nodes.java.erb +403 -0
  109. data/vendor/prism/templates/javascript/src/deserialize.js.erb +448 -0
  110. data/vendor/prism/templates/javascript/src/nodes.js.erb +197 -0
  111. data/vendor/prism/templates/javascript/src/visitor.js.erb +78 -0
  112. data/vendor/prism/templates/lib/prism/compiler.rb.erb +43 -0
  113. data/vendor/prism/templates/lib/prism/dispatcher.rb.erb +103 -0
  114. data/vendor/prism/templates/lib/prism/dot_visitor.rb.erb +189 -0
  115. data/vendor/prism/templates/lib/prism/dsl.rb.erb +133 -0
  116. data/vendor/prism/templates/lib/prism/inspect_visitor.rb.erb +131 -0
  117. data/vendor/prism/templates/lib/prism/mutation_compiler.rb.erb +19 -0
  118. data/vendor/prism/templates/lib/prism/node.rb.erb +515 -0
  119. data/vendor/prism/templates/lib/prism/reflection.rb.erb +136 -0
  120. data/vendor/prism/templates/lib/prism/serialize.rb.erb +602 -0
  121. data/vendor/prism/templates/lib/prism/visitor.rb.erb +55 -0
  122. data/vendor/prism/templates/rbi/prism/dsl.rbi.erb +68 -0
  123. data/vendor/prism/templates/rbi/prism/node.rbi.erb +164 -0
  124. data/vendor/prism/templates/rbi/prism/visitor.rbi.erb +18 -0
  125. data/vendor/prism/templates/sig/prism/_private/dot_visitor.rbs.erb +45 -0
  126. data/vendor/prism/templates/sig/prism/dsl.rbs.erb +31 -0
  127. data/vendor/prism/templates/sig/prism/mutation_compiler.rbs.erb +7 -0
  128. data/vendor/prism/templates/sig/prism/node.rbs.erb +132 -0
  129. data/vendor/prism/templates/sig/prism/visitor.rbs.erb +17 -0
  130. data/vendor/prism/templates/sig/prism.rbs.erb +89 -0
  131. data/vendor/prism/templates/src/diagnostic.c.erb +523 -0
  132. data/vendor/prism/templates/src/node.c.erb +333 -0
  133. data/vendor/prism/templates/src/prettyprint.c.erb +166 -0
  134. data/vendor/prism/templates/src/serialize.c.erb +406 -0
  135. data/vendor/prism/templates/src/token_type.c.erb +369 -0
  136. data/vendor/prism/templates/template.rb +689 -0
  137. metadata +112 -2
@@ -0,0 +1,790 @@
1
+ #include "prism/regexp.h"
2
+
3
+ #define PM_REGEXP_PARSE_DEPTH_MAX 4096
4
+
5
+ /**
6
+ * This is the parser that is going to handle parsing regular expressions.
7
+ */
8
+ typedef struct {
9
+ /** The parser that is currently being used. */
10
+ pm_parser_t *parser;
11
+
12
+ /** A pointer to the start of the source that we are parsing. */
13
+ const uint8_t *start;
14
+
15
+ /** A pointer to the current position in the source. */
16
+ const uint8_t *cursor;
17
+
18
+ /** A pointer to the end of the source that we are parsing. */
19
+ const uint8_t *end;
20
+
21
+ /**
22
+ * Whether or not the regular expression currently being parsed is in
23
+ * extended mode, wherein whitespace is ignored and comments are allowed.
24
+ */
25
+ bool extended_mode;
26
+
27
+ /** Whether the encoding has changed from the default. */
28
+ bool encoding_changed;
29
+
30
+ /** The encoding of the source. */
31
+ const pm_encoding_t *encoding;
32
+
33
+ /** The callback to call when a named capture group is found. */
34
+ pm_regexp_name_callback_t name_callback;
35
+
36
+ /** The data to pass to the name callback. */
37
+ void *name_data;
38
+
39
+ /** The callback to call when a parse error is found. */
40
+ pm_regexp_error_callback_t error_callback;
41
+
42
+ /** The data to pass to the error callback. */
43
+ void *error_data;
44
+ } pm_regexp_parser_t;
45
+
46
+ /**
47
+ * Append an error to the parser.
48
+ */
49
+ static inline void
50
+ pm_regexp_parse_error(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end, const char *message) {
51
+ parser->error_callback(start, end, message, parser->error_data);
52
+ }
53
+
54
+ /**
55
+ * This appends a new string to the list of named captures. This function
56
+ * assumes the caller has already checked the validity of the name callback.
57
+ */
58
+ static void
59
+ pm_regexp_parser_named_capture(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end) {
60
+ pm_string_t string;
61
+ pm_string_shared_init(&string, start, end);
62
+ parser->name_callback(&string, parser->name_data);
63
+ pm_string_free(&string);
64
+ }
65
+
66
+ /**
67
+ * Returns true if the next character is the end of the source.
68
+ */
69
+ static inline bool
70
+ pm_regexp_char_is_eof(pm_regexp_parser_t *parser) {
71
+ return parser->cursor >= parser->end;
72
+ }
73
+
74
+ /**
75
+ * Optionally accept a char and consume it if it exists.
76
+ */
77
+ static inline bool
78
+ pm_regexp_char_accept(pm_regexp_parser_t *parser, uint8_t value) {
79
+ if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
80
+ parser->cursor++;
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * Expect a character to be present and consume it.
88
+ */
89
+ static inline bool
90
+ pm_regexp_char_expect(pm_regexp_parser_t *parser, uint8_t value) {
91
+ if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
92
+ parser->cursor++;
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+
98
+ /**
99
+ * This advances the current token to the next instance of the given character.
100
+ */
101
+ static bool
102
+ pm_regexp_char_find(pm_regexp_parser_t *parser, uint8_t value) {
103
+ if (pm_regexp_char_is_eof(parser)) {
104
+ return false;
105
+ }
106
+
107
+ const uint8_t *end = (const uint8_t *) pm_memchr(parser->cursor, value, (size_t) (parser->end - parser->cursor), parser->encoding_changed, parser->encoding);
108
+ if (end == NULL) {
109
+ return false;
110
+ }
111
+
112
+ parser->cursor = end + 1;
113
+ return true;
114
+ }
115
+
116
+ /**
117
+ * Range quantifiers are a special class of quantifiers that look like
118
+ *
119
+ * * {digit}
120
+ * * {digit,}
121
+ * * {digit,digit}
122
+ * * {,digit}
123
+ *
124
+ * Unfortunately, if there are any spaces in between, then this just becomes a
125
+ * regular character match expression and we have to backtrack. So when this
126
+ * function first starts running, we'll create a "save" point and then attempt
127
+ * to parse the quantifier. If it fails, we'll restore the save point and
128
+ * return.
129
+ *
130
+ * The properly track everything, we're going to build a little state machine.
131
+ * It looks something like the following:
132
+ *
133
+ * +-------+ +---------+ ------------+
134
+ * ---- lbrace ---> | start | ---- digit ---> | minimum | |
135
+ * +-------+ +---------+ <--- digit -+
136
+ * | | |
137
+ * +-------+ | | rbrace
138
+ * | comma | <----- comma +---- comma -------+ |
139
+ * +-------+ V V
140
+ * | +---------+ +---------+
141
+ * +-- digit --> | maximum | -- rbrace --> || final ||
142
+ * +---------+ +---------+
143
+ * | ^
144
+ * +- digit -+
145
+ *
146
+ * Note that by the time we've hit this function, the lbrace has already been
147
+ * consumed so we're in the start state.
148
+ */
149
+ static bool
150
+ pm_regexp_parse_range_quantifier(pm_regexp_parser_t *parser) {
151
+ const uint8_t *savepoint = parser->cursor;
152
+
153
+ enum {
154
+ PM_REGEXP_RANGE_QUANTIFIER_STATE_START,
155
+ PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM,
156
+ PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM,
157
+ PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA
158
+ } state = PM_REGEXP_RANGE_QUANTIFIER_STATE_START;
159
+
160
+ while (1) {
161
+ if (parser->cursor >= parser->end) {
162
+ parser->cursor = savepoint;
163
+ return true;
164
+ }
165
+
166
+ switch (state) {
167
+ case PM_REGEXP_RANGE_QUANTIFIER_STATE_START:
168
+ switch (*parser->cursor) {
169
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
170
+ parser->cursor++;
171
+ state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM;
172
+ break;
173
+ case ',':
174
+ parser->cursor++;
175
+ state = PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA;
176
+ break;
177
+ default:
178
+ parser->cursor = savepoint;
179
+ return true;
180
+ }
181
+ break;
182
+ case PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM:
183
+ switch (*parser->cursor) {
184
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
185
+ parser->cursor++;
186
+ break;
187
+ case ',':
188
+ parser->cursor++;
189
+ state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
190
+ break;
191
+ case '}':
192
+ parser->cursor++;
193
+ return true;
194
+ default:
195
+ parser->cursor = savepoint;
196
+ return true;
197
+ }
198
+ break;
199
+ case PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA:
200
+ switch (*parser->cursor) {
201
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
202
+ parser->cursor++;
203
+ state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
204
+ break;
205
+ default:
206
+ parser->cursor = savepoint;
207
+ return true;
208
+ }
209
+ break;
210
+ case PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM:
211
+ switch (*parser->cursor) {
212
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
213
+ parser->cursor++;
214
+ break;
215
+ case '}':
216
+ parser->cursor++;
217
+ return true;
218
+ default:
219
+ parser->cursor = savepoint;
220
+ return true;
221
+ }
222
+ break;
223
+ }
224
+ }
225
+
226
+ return true;
227
+ }
228
+
229
+ /**
230
+ * quantifier : star-quantifier
231
+ * | plus-quantifier
232
+ * | optional-quantifier
233
+ * | range-quantifier
234
+ * | <empty>
235
+ * ;
236
+ */
237
+ static bool
238
+ pm_regexp_parse_quantifier(pm_regexp_parser_t *parser) {
239
+ while (!pm_regexp_char_is_eof(parser)) {
240
+ switch (*parser->cursor) {
241
+ case '*':
242
+ case '+':
243
+ case '?':
244
+ parser->cursor++;
245
+ break;
246
+ case '{':
247
+ parser->cursor++;
248
+ if (!pm_regexp_parse_range_quantifier(parser)) return false;
249
+ break;
250
+ default:
251
+ // In this case there is no quantifier.
252
+ return true;
253
+ }
254
+ }
255
+
256
+ return true;
257
+ }
258
+
259
+ /**
260
+ * match-posix-class : '[' '[' ':' '^'? CHAR+ ':' ']' ']'
261
+ * ;
262
+ */
263
+ static bool
264
+ pm_regexp_parse_posix_class(pm_regexp_parser_t *parser) {
265
+ if (!pm_regexp_char_expect(parser, ':')) {
266
+ return false;
267
+ }
268
+
269
+ pm_regexp_char_accept(parser, '^');
270
+
271
+ return (
272
+ pm_regexp_char_find(parser, ':') &&
273
+ pm_regexp_char_expect(parser, ']') &&
274
+ pm_regexp_char_expect(parser, ']')
275
+ );
276
+ }
277
+
278
+ // Forward declaration because character sets can be nested.
279
+ static bool
280
+ pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth);
281
+
282
+ /**
283
+ * match-char-set : '[' '^'? (match-range | match-char)* ']'
284
+ * ;
285
+ */
286
+ static bool
287
+ pm_regexp_parse_character_set(pm_regexp_parser_t *parser, uint16_t depth) {
288
+ pm_regexp_char_accept(parser, '^');
289
+
290
+ while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ']') {
291
+ switch (*parser->cursor++) {
292
+ case '[':
293
+ pm_regexp_parse_lbracket(parser, (uint16_t) (depth + 1));
294
+ break;
295
+ case '\\':
296
+ if (!pm_regexp_char_is_eof(parser)) {
297
+ parser->cursor++;
298
+ }
299
+ break;
300
+ default:
301
+ // do nothing, we've already advanced the cursor
302
+ break;
303
+ }
304
+ }
305
+
306
+ return pm_regexp_char_expect(parser, ']');
307
+ }
308
+
309
+ /**
310
+ * A left bracket can either mean a POSIX class or a character set.
311
+ */
312
+ static bool
313
+ pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth) {
314
+ if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
315
+ pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
316
+ return false;
317
+ }
318
+
319
+ if ((parser->cursor < parser->end) && parser->cursor[0] == ']') {
320
+ parser->cursor++;
321
+ pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "empty char-class");
322
+ return true;
323
+ }
324
+
325
+ const uint8_t *reset = parser->cursor;
326
+
327
+ if ((parser->cursor + 2 < parser->end) && parser->cursor[0] == '[' && parser->cursor[1] == ':') {
328
+ parser->cursor++;
329
+ if (pm_regexp_parse_posix_class(parser)) return true;
330
+
331
+ parser->cursor = reset;
332
+ }
333
+
334
+ return pm_regexp_parse_character_set(parser, depth);
335
+ }
336
+
337
+ // Forward declaration here since parsing groups needs to go back up the grammar
338
+ // to parse expressions within them.
339
+ static bool
340
+ pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth);
341
+
342
+ /**
343
+ * These are the states of the options that are configurable on the regular
344
+ * expression (or from within a group).
345
+ */
346
+ typedef enum {
347
+ PM_REGEXP_OPTION_STATE_INVALID,
348
+ PM_REGEXP_OPTION_STATE_TOGGLEABLE,
349
+ PM_REGEXP_OPTION_STATE_ADDABLE,
350
+ PM_REGEXP_OPTION_STATE_ADDED,
351
+ PM_REGEXP_OPTION_STATE_REMOVED
352
+ } pm_regexp_option_state_t;
353
+
354
+ // These are the options that are configurable on the regular expression (or
355
+ // from within a group).
356
+
357
+ #define PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM 'a'
358
+ #define PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM 'x'
359
+ #define PRISM_REGEXP_OPTION_STATE_SLOTS (PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM + 1)
360
+
361
+ /**
362
+ * This is the set of options that are configurable on the regular expression.
363
+ */
364
+ typedef struct {
365
+ /** The current state of each option. */
366
+ uint8_t values[PRISM_REGEXP_OPTION_STATE_SLOTS];
367
+ } pm_regexp_options_t;
368
+
369
+ /**
370
+ * Initialize a new set of options to their default values.
371
+ */
372
+ static void
373
+ pm_regexp_options_init(pm_regexp_options_t *options) {
374
+ memset(options, PM_REGEXP_OPTION_STATE_INVALID, sizeof(uint8_t) * PRISM_REGEXP_OPTION_STATE_SLOTS);
375
+ options->values['i' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
376
+ options->values['m' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
377
+ options->values['x' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
378
+ options->values['d' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
379
+ options->values['a' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
380
+ options->values['u' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
381
+ }
382
+
383
+ /**
384
+ * Attempt to add the given option to the set of options. Returns true if it was
385
+ * added, false if it was already present.
386
+ */
387
+ static bool
388
+ pm_regexp_options_add(pm_regexp_options_t *options, uint8_t key) {
389
+ if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
390
+ key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
391
+
392
+ switch (options->values[key]) {
393
+ case PM_REGEXP_OPTION_STATE_INVALID:
394
+ case PM_REGEXP_OPTION_STATE_REMOVED:
395
+ return false;
396
+ case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
397
+ case PM_REGEXP_OPTION_STATE_ADDABLE:
398
+ options->values[key] = PM_REGEXP_OPTION_STATE_ADDED;
399
+ return true;
400
+ case PM_REGEXP_OPTION_STATE_ADDED:
401
+ return true;
402
+ }
403
+ }
404
+
405
+ return false;
406
+ }
407
+
408
+ /**
409
+ * Attempt to remove the given option from the set of options. Returns true if
410
+ * it was removed, false if it was already absent.
411
+ */
412
+ static bool
413
+ pm_regexp_options_remove(pm_regexp_options_t *options, uint8_t key) {
414
+ if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
415
+ key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
416
+
417
+ switch (options->values[key]) {
418
+ case PM_REGEXP_OPTION_STATE_INVALID:
419
+ case PM_REGEXP_OPTION_STATE_ADDABLE:
420
+ return false;
421
+ case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
422
+ case PM_REGEXP_OPTION_STATE_ADDED:
423
+ case PM_REGEXP_OPTION_STATE_REMOVED:
424
+ options->values[key] = PM_REGEXP_OPTION_STATE_REMOVED;
425
+ return true;
426
+ }
427
+ }
428
+
429
+ return false;
430
+ }
431
+
432
+ /**
433
+ * True if the given key is set in the options.
434
+ */
435
+ static uint8_t
436
+ pm_regexp_options_state(pm_regexp_options_t *options, uint8_t key) {
437
+ if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
438
+ key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
439
+ return options->values[key];
440
+ }
441
+
442
+ return false;
443
+ }
444
+
445
+ /**
446
+ * Groups can have quite a few different patterns for syntax. They basically
447
+ * just wrap a set of expressions, but they can potentially have options after a
448
+ * question mark. If there _isn't_ a question mark, then it's just a set of
449
+ * expressions. If there _is_, then here are the options:
450
+ *
451
+ * * (?#...) - inline comments
452
+ * * (?:subexp) - non-capturing group
453
+ * * (?=subexp) - positive lookahead
454
+ * * (?!subexp) - negative lookahead
455
+ * * (?>subexp) - atomic group
456
+ * * (?~subexp) - absence operator
457
+ * * (?<=subexp) - positive lookbehind
458
+ * * (?<!subexp) - negative lookbehind
459
+ * * (?<name>subexp) - named capturing group
460
+ * * (?'name'subexp) - named capturing group
461
+ * * (?(cond)yes-subexp) - conditional expression
462
+ * * (?(cond)yes-subexp|no-subexp) - conditional expression
463
+ * * (?imxdau-imx) - turn on and off configuration
464
+ * * (?imxdau-imx:subexp) - turn on and off configuration for an expression
465
+ */
466
+ static bool
467
+ pm_regexp_parse_group(pm_regexp_parser_t *parser, uint16_t depth) {
468
+ const uint8_t *group_start = parser->cursor;
469
+
470
+ pm_regexp_options_t options;
471
+ pm_regexp_options_init(&options);
472
+
473
+ // First, parse any options for the group.
474
+ if (pm_regexp_char_accept(parser, '?')) {
475
+ if (pm_regexp_char_is_eof(parser)) {
476
+ pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
477
+ return false;
478
+ }
479
+
480
+ switch (*parser->cursor) {
481
+ case '#': { // inline comments
482
+ parser->cursor++;
483
+ if (pm_regexp_char_is_eof(parser)) {
484
+ pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
485
+ return false;
486
+ }
487
+
488
+ if (parser->encoding_changed && parser->encoding->multibyte) {
489
+ bool escaped = false;
490
+
491
+ // Here we're going to take a slow path and iterate through
492
+ // each multibyte character to find the close paren. We do
493
+ // this because \ can be a trailing byte in some encodings.
494
+ while (parser->cursor < parser->end) {
495
+ if (!escaped && *parser->cursor == ')') {
496
+ parser->cursor++;
497
+ return true;
498
+ }
499
+
500
+ size_t width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
501
+ if (width == 0) return false;
502
+
503
+ escaped = (width == 1) && (*parser->cursor == '\\');
504
+ parser->cursor += width;
505
+ }
506
+
507
+ return false;
508
+ } else {
509
+ // Here we can take the fast path and use memchr to find the
510
+ // next ) because we are safe checking backward for \ since
511
+ // it cannot be a trailing character.
512
+ bool found = pm_regexp_char_find(parser, ')');
513
+
514
+ while (found && (parser->start <= parser->cursor - 2) && (*(parser->cursor - 2) == '\\')) {
515
+ found = pm_regexp_char_find(parser, ')');
516
+ }
517
+
518
+ return found;
519
+ }
520
+ }
521
+ case ':': // non-capturing group
522
+ case '=': // positive lookahead
523
+ case '!': // negative lookahead
524
+ case '>': // atomic group
525
+ case '~': // absence operator
526
+ parser->cursor++;
527
+ break;
528
+ case '<':
529
+ parser->cursor++;
530
+ if (pm_regexp_char_is_eof(parser)) {
531
+ pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
532
+ return false;
533
+ }
534
+
535
+ switch (*parser->cursor) {
536
+ case '=': // positive lookbehind
537
+ case '!': // negative lookbehind
538
+ parser->cursor++;
539
+ break;
540
+ default: { // named capture group
541
+ const uint8_t *start = parser->cursor;
542
+ if (!pm_regexp_char_find(parser, '>')) {
543
+ return false;
544
+ }
545
+
546
+ if (parser->cursor - start == 1) {
547
+ pm_regexp_parse_error(parser, start, parser->cursor, "group name is empty");
548
+ }
549
+
550
+ if (parser->name_callback != NULL) {
551
+ pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
552
+ }
553
+
554
+ break;
555
+ }
556
+ }
557
+ break;
558
+ case '\'': { // named capture group
559
+ const uint8_t *start = ++parser->cursor;
560
+ if (!pm_regexp_char_find(parser, '\'')) {
561
+ return false;
562
+ }
563
+
564
+ if (parser->name_callback != NULL) {
565
+ pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
566
+ }
567
+
568
+ break;
569
+ }
570
+ case '(': // conditional expression
571
+ if (!pm_regexp_char_find(parser, ')')) {
572
+ return false;
573
+ }
574
+ break;
575
+ case 'i': case 'm': case 'x': case 'd': case 'a': case 'u': // options
576
+ while (!pm_regexp_char_is_eof(parser) && *parser->cursor != '-' && *parser->cursor != ':' && *parser->cursor != ')') {
577
+ if (!pm_regexp_options_add(&options, *parser->cursor)) {
578
+ return false;
579
+ }
580
+ parser->cursor++;
581
+ }
582
+
583
+ if (pm_regexp_char_is_eof(parser)) {
584
+ return false;
585
+ }
586
+
587
+ // If we are at the end of the group of options and there is no
588
+ // subexpression, then we are going to be setting the options
589
+ // for the parent group. In this case we are safe to return now.
590
+ if (*parser->cursor == ')') {
591
+ if (pm_regexp_options_state(&options, 'x') == PM_REGEXP_OPTION_STATE_ADDED) {
592
+ parser->extended_mode = true;
593
+ }
594
+
595
+ parser->cursor++;
596
+ return true;
597
+ }
598
+
599
+ // If we hit a -, then we're done parsing options.
600
+ if (*parser->cursor != '-') break;
601
+
602
+ PRISM_FALLTHROUGH
603
+ case '-':
604
+ parser->cursor++;
605
+ while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ':' && *parser->cursor != ')') {
606
+ if (!pm_regexp_options_remove(&options, *parser->cursor)) {
607
+ return false;
608
+ }
609
+ parser->cursor++;
610
+ }
611
+
612
+ if (pm_regexp_char_is_eof(parser)) {
613
+ return false;
614
+ }
615
+
616
+ // If we are at the end of the group of options and there is no
617
+ // subexpression, then we are going to be setting the options
618
+ // for the parent group. In this case we are safe to return now.
619
+ if (*parser->cursor == ')') {
620
+ switch (pm_regexp_options_state(&options, 'x')) {
621
+ case PM_REGEXP_OPTION_STATE_ADDED:
622
+ parser->extended_mode = true;
623
+ break;
624
+ case PM_REGEXP_OPTION_STATE_REMOVED:
625
+ parser->extended_mode = false;
626
+ break;
627
+ }
628
+
629
+ parser->cursor++;
630
+ return true;
631
+ }
632
+
633
+ break;
634
+ default:
635
+ parser->cursor++;
636
+ pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "undefined group option");
637
+ break;
638
+ }
639
+ }
640
+
641
+ bool extended_mode = parser->extended_mode;
642
+ switch (pm_regexp_options_state(&options, 'x')) {
643
+ case PM_REGEXP_OPTION_STATE_ADDED:
644
+ parser->extended_mode = true;
645
+ break;
646
+ case PM_REGEXP_OPTION_STATE_REMOVED:
647
+ parser->extended_mode = false;
648
+ break;
649
+ }
650
+
651
+ // Now, parse the expressions within this group.
652
+ while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')') {
653
+ if (!pm_regexp_parse_expression(parser, (uint16_t) (depth + 1))) {
654
+ parser->extended_mode = extended_mode;
655
+ return false;
656
+ }
657
+ pm_regexp_char_accept(parser, '|');
658
+ }
659
+
660
+ // Finally, make sure we have a closing parenthesis.
661
+ parser->extended_mode = extended_mode;
662
+ if (pm_regexp_char_expect(parser, ')')) return true;
663
+
664
+ pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
665
+ return false;
666
+ }
667
+
668
+ /**
669
+ * item : anchor
670
+ * | match-posix-class
671
+ * | match-char-set
672
+ * | match-char-class
673
+ * | match-char-prop
674
+ * | match-char
675
+ * | match-any
676
+ * | group
677
+ * | quantified
678
+ * ;
679
+ */
680
+ static bool
681
+ pm_regexp_parse_item(pm_regexp_parser_t *parser, uint16_t depth) {
682
+ switch (*parser->cursor) {
683
+ case '^':
684
+ case '$':
685
+ parser->cursor++;
686
+ return pm_regexp_parse_quantifier(parser);
687
+ case '\\':
688
+ parser->cursor++;
689
+ if (!pm_regexp_char_is_eof(parser)) {
690
+ parser->cursor++;
691
+ }
692
+ return pm_regexp_parse_quantifier(parser);
693
+ case '(':
694
+ parser->cursor++;
695
+ return pm_regexp_parse_group(parser, depth) && pm_regexp_parse_quantifier(parser);
696
+ case '[':
697
+ parser->cursor++;
698
+ return pm_regexp_parse_lbracket(parser, depth) && pm_regexp_parse_quantifier(parser);
699
+ case '*':
700
+ case '?':
701
+ case '+':
702
+ parser->cursor++;
703
+ pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "target of repeat operator is not specified");
704
+ return true;
705
+ case ')':
706
+ parser->cursor++;
707
+ pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "unmatched close parenthesis");
708
+ return true;
709
+ case '#':
710
+ if (parser->extended_mode) {
711
+ if (!pm_regexp_char_find(parser, '\n')) parser->cursor = parser->end;
712
+ return true;
713
+ }
714
+ PRISM_FALLTHROUGH
715
+ default: {
716
+ size_t width;
717
+ if (!parser->encoding_changed) {
718
+ width = pm_encoding_utf_8_char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
719
+ } else {
720
+ width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
721
+ }
722
+
723
+ if (width == 0) return false; // TODO: add appropriate error
724
+ parser->cursor += width;
725
+
726
+ return pm_regexp_parse_quantifier(parser);
727
+ }
728
+ }
729
+ }
730
+
731
+ /**
732
+ * expression : item+
733
+ * ;
734
+ */
735
+ static bool
736
+ pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth) {
737
+ if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
738
+ pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
739
+ return false;
740
+ }
741
+
742
+ if (!pm_regexp_parse_item(parser, depth)) {
743
+ return false;
744
+ }
745
+
746
+ while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')' && *parser->cursor != '|') {
747
+ if (!pm_regexp_parse_item(parser, depth)) {
748
+ return false;
749
+ }
750
+ }
751
+
752
+ return true;
753
+ }
754
+
755
+ /**
756
+ * pattern : EOF
757
+ * | expression EOF
758
+ * | expression '|' pattern
759
+ * ;
760
+ */
761
+ static bool
762
+ pm_regexp_parse_pattern(pm_regexp_parser_t *parser) {
763
+ do {
764
+ if (pm_regexp_char_is_eof(parser)) return true;
765
+ if (!pm_regexp_parse_expression(parser, 0)) return false;
766
+ } while (pm_regexp_char_accept(parser, '|'));
767
+
768
+ return pm_regexp_char_is_eof(parser);
769
+ }
770
+
771
+ /**
772
+ * Parse a regular expression and extract the names of all of the named capture
773
+ * groups.
774
+ */
775
+ PRISM_EXPORTED_FUNCTION void
776
+ pm_regexp_parse(pm_parser_t *parser, const uint8_t *source, size_t size, bool extended_mode, pm_regexp_name_callback_t name_callback, void *name_data, pm_regexp_error_callback_t error_callback, void *error_data) {
777
+ pm_regexp_parse_pattern(&(pm_regexp_parser_t) {
778
+ .parser = parser,
779
+ .start = source,
780
+ .cursor = source,
781
+ .end = source + size,
782
+ .extended_mode = extended_mode,
783
+ .encoding_changed = parser->encoding_changed,
784
+ .encoding = parser->encoding,
785
+ .name_callback = name_callback,
786
+ .name_data = name_data,
787
+ .error_callback = error_callback,
788
+ .error_data = error_data
789
+ });
790
+ }