qr_reader-native 0.0.1

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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,1379 @@
1
+ /* sp_re.c -- regexp wrappers + MatchData (see sp_re.h).
2
+ *
3
+ * Moved out of spinel_rt.h so this layer compiles once into
4
+ * libspinel_rt.a. It calls the regexp engine (re_*) and the shared string
5
+ * heap / arrays; sp_sprintf / sp_raise_cls resolve at the final link. */
6
+ #include <stdlib.h>
7
+ #include <string.h>
8
+ #include <stdio.h>
9
+ #include "sp_re.h"
10
+ #include "sp_string.h" /* sp_String builder */
11
+ #include "sp_inspect.h" /* sp_inspect_container (poly element render) */
12
+ #include "sp_gc.h" /* sp_sym_name_fn (Symbol operand of === ) */
13
+
14
+ #ifndef SPL
15
+ #define SPL(s) (&("\xff" s)[1])
16
+ #endif
17
+ const char *sp_sprintf(const char *fmt, ...); /* defined in the generated TU */
18
+
19
+ /* match-register state (declared extern in sp_re.h). */
20
+ SP_TLS const char *sp_re_captures[10] = {0}; /* per-worker (SP_TLS); see sp_re.h */
21
+ SP_TLS int sp_re_caps[64];
22
+ SP_TLS int sp_re_sub_matched = 0;
23
+ SP_TLS const char *sp_re_last_str = NULL;
24
+ SP_TLS const char *sp_re_match_str = NULL;
25
+ /* $` and $' are built only when read: a gsub over a large subject matches
26
+ thousands of times, and copying the whole prefix and suffix at every match
27
+ made the scan quadratic. sp_re_pp_span holds the last match's span in
28
+ sp_re_last_str so the accessors below can build them on demand. */
29
+ SP_TLS const char *sp_re_match_pre = NULL;
30
+ SP_TLS const char *sp_re_match_post = NULL;
31
+ static SP_TLS int sp_re_pp_span[2] = {-1, -1};
32
+ const char *sp_re_startup_err = NULL;
33
+
34
+ /* Stop-the-world support: push this worker's live match-register strings onto its
35
+ GC root stack so a collector marks them while the worker is parked at a
36
+ safepoint (the registers are per-worker TLS, so the collector's own globals
37
+ hook does not reach another worker's copy). Only heap strings still unmarked
38
+ (leading byte 0xfe, or 0xfa frozen) are pushed: frozen literals (0xf1) are
39
+ immortal and never swept, and sp_gc_mark only understands those markers. The
40
+ caller snapshots the root stack right after and then restores its depth, so
41
+ these entries live only inside the published snapshot. */
42
+ void sp_re_push_match_roots(void) {
43
+ #define SP_RE_PUSH_LIVE(addr) do { const char *_s = *(addr); \
44
+ if (_s && ((unsigned char)_s[-1] | 0x04) == 0xfe) _sp_gc_root_push((void **)(addr)); } while (0)
45
+ SP_RE_PUSH_LIVE(&sp_re_last_str);
46
+ for (int i = 0; i < 10; i++) SP_RE_PUSH_LIVE(&sp_re_captures[i]);
47
+ SP_RE_PUSH_LIVE(&sp_re_match_str);
48
+ SP_RE_PUSH_LIVE(&sp_re_match_pre);
49
+ SP_RE_PUSH_LIVE(&sp_re_match_post);
50
+ #undef SP_RE_PUSH_LIVE
51
+ }
52
+
53
+ const char *sp_re_last_paren_match(void) {
54
+ for (int i = 9; i >= 1; i--) {
55
+ if (sp_re_captures[i]) return sp_re_captures[i];
56
+ }
57
+ return NULL;
58
+ }
59
+ void sp_MatchData_scan(void *p); /* defined below */
60
+ static sp_MatchData *sp_md_alloc(int pairs); /* defined below */
61
+ SP_TLS int sp_re_last_ncap = 0;
62
+ SP_TLS const mrb_regexp_pattern *sp_re_last_pat = NULL;
63
+ /* Set when the last match was a String pattern's (gsub / sub / scan with a
64
+ String). CRuby's `$~.regexp` for one is the String escaped into a Regexp,
65
+ which the String scan itself never needs, so it is built only when `$~` is
66
+ read (sp_re_lit_pattern). The matched text is the pattern's own bytes, so
67
+ sp_re_match_str is what it is built from. */
68
+ SP_TLS int sp_re_last_lit = 0;
69
+ /* The Regexp a String-pattern match answers for `$~.regexp`. The last one
70
+ built is kept, so reading `$~` after every turn of a loop that does the same
71
+ `gsub("x", ...)` compiles once. A pattern it replaces is not freed: a
72
+ MatchData or a saved frame may still hold it. */
73
+ static const mrb_regexp_pattern *sp_re_lit_pattern(const char *lit) {
74
+ static SP_TLS char *src = NULL;
75
+ static SP_TLS size_t srclen = 0;
76
+ static SP_TLS mrb_regexp_pattern *pat = NULL;
77
+ size_t n = sp_str_byte_len(lit);
78
+ if (pat && n == srclen && memcmp(src, lit, n) == 0) return pat;
79
+ const char *esc = sp_re_escape(lit);
80
+ mrb_regexp_pattern *p = re_compile(esc, (int64_t)sp_str_byte_len(esc), 0);
81
+ if (!p) return NULL;
82
+ char *copy = (char *)malloc(n + 1);
83
+ memcpy(copy, lit, n); copy[n] = 0;
84
+ free(src);
85
+ src = copy; srclen = n; pat = p;
86
+ return p;
87
+ }
88
+ /* $~ as a first-class MatchData: build it lazily from the TLS match
89
+ registers (NULL when the last match failed / none ran). */
90
+ sp_MatchData *sp_re_last_matchdata(void) {
91
+ if (!sp_re_last_str || sp_re_last_ncap <= 0 || sp_re_caps[0] < 0) return NULL;
92
+ if (sp_re_last_lit && sp_re_match_str) {
93
+ sp_re_last_pat = sp_re_lit_pattern(sp_re_match_str);
94
+ sp_re_last_lit = 0;
95
+ }
96
+ int n = sp_re_last_ncap * 2;
97
+ if (n > 64) n = 64;
98
+ sp_MatchData *md = sp_md_alloc(n / 2);
99
+ md->source = sp_re_last_str;
100
+ for (int i = 0; i < n; i++) md->caps[i] = sp_re_caps[i];
101
+ md->ncap = sp_re_last_ncap;
102
+ md->pat = sp_re_last_pat;
103
+ return md;
104
+ }
105
+ /* CRuby's $~ (and the $1..$9 / $` / $' derived from it) is a FRAME-local, so
106
+ a match inside a method leaves the caller's registers alone once the method
107
+ returns. The registers here are per-worker globals, so a method that matches
108
+ saves them on entry and puts them back on the way out; the emitter gives
109
+ such a method one of these frames (#3629). */
110
+ void sp_re_frame_push(sp_re_frame *f) {
111
+ if (!f) return;
112
+ for (int i = 0; i < 10; i++) f->captures[i] = sp_re_captures[i];
113
+ for (int i = 0; i < 64; i++) f->caps[i] = sp_re_caps[i];
114
+ f->last_str = sp_re_last_str;
115
+ f->match_str = sp_re_match_str;
116
+ f->match_pre = sp_re_match_pre;
117
+ f->match_post = sp_re_match_post;
118
+ f->last_ncap = sp_re_last_ncap;
119
+ f->last_pat = sp_re_last_pat;
120
+ f->last_lit = sp_re_last_lit;
121
+ f->pp_span[0] = sp_re_pp_span[0]; f->pp_span[1] = sp_re_pp_span[1];
122
+ }
123
+ void sp_re_frame_pop(sp_re_frame *f) {
124
+ if (!f) return;
125
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = f->captures[i];
126
+ for (int i = 0; i < 64; i++) sp_re_caps[i] = f->caps[i];
127
+ sp_re_last_str = f->last_str;
128
+ sp_re_match_str = f->match_str;
129
+ sp_re_match_pre = f->match_pre;
130
+ sp_re_match_post = f->match_post;
131
+ sp_re_last_ncap = f->last_ncap;
132
+ sp_re_last_pat = f->last_pat;
133
+ sp_re_last_lit = f->last_lit;
134
+ /* the span $` and $' are built from lazily: the caller's, not the callee's */
135
+ sp_re_pp_span[0] = f->pp_span[0]; sp_re_pp_span[1] = f->pp_span[1];
136
+ }
137
+ void sp_re_set_captures(const char *str, int *caps, int ncaps) {SP_GC_ROOT_STR(str);
138
+ sp_re_last_str = str;
139
+ sp_re_last_ncap = ncaps;
140
+ sp_re_last_lit = 0;
141
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
142
+ for (int i = 1; i < ncaps && i < 10; i++) {
143
+ if (caps[i*2] >= 0 && caps[(i*2)+1] >= 0) {
144
+ int len = caps[(i*2)+1] - caps[i*2];
145
+ char *buf = sp_str_alloc_raw(len+1);
146
+ memcpy(buf, str+caps[i*2], len); buf[len] = 0;
147
+ sp_str_set_len(buf, (size_t)len);
148
+ sp_re_captures[i] = buf;
149
+ }
150
+ }
151
+ /* Populate the symbolic back-references from caps[0]/[1] (the whole
152
+ match span). NULL when the match failed; the codegen ternary
153
+ falls back to "". */
154
+ sp_re_match_str = NULL;
155
+ sp_re_match_pre = NULL;
156
+ sp_re_match_post = NULL;
157
+ sp_re_pp_span[0] = sp_re_pp_span[1] = -1;
158
+ if (ncaps >= 1 && caps[0] >= 0 && caps[1] >= 0) {
159
+ int mlen = caps[1] - caps[0];
160
+ char *m = sp_str_alloc_raw(mlen + 1);
161
+ memcpy(m, str + caps[0], mlen); m[mlen] = 0;
162
+ sp_str_set_len(m, (size_t)mlen);
163
+ sp_str_set_len(m, (size_t)mlen);
164
+ sp_re_match_str = m;
165
+ sp_re_pp_span[0] = caps[0]; sp_re_pp_span[1] = caps[1];
166
+ }
167
+ }
168
+
169
+ /* No match: `$~`, `$1`.. and the rest read nil (#848). */
170
+ void sp_re_clear_last_match(void) {
171
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
172
+ sp_re_last_str = NULL;
173
+ sp_re_match_str = NULL;
174
+ sp_re_match_pre = NULL;
175
+ sp_re_match_post = NULL;
176
+ sp_re_pp_span[0] = sp_re_pp_span[1] = -1;
177
+ }
178
+ /* Whether gsub, sub and scan record their last match for `$~`. The
179
+ generated main sets it when the program reads `$~` or what derives from it
180
+ anywhere (g_reads_match_regs); recording copies the match and its groups
181
+ per call, which a program that never reads them should not pay for, so
182
+ the runtime below checks it at each call site. One value for the program,
183
+ set before anything runs, so a plain global. */
184
+ int sp_re_track_last = 0;
185
+ /* gsub, sub and scan leave `$~` at their LAST match, or nil when nothing
186
+ matched, as CRuby's do. They scan with a caps array of their own, so they
187
+ hand the last match's `n` positions to the registers once they are done. */
188
+ void sp_re_set_last_match(const mrb_regexp_pattern *pat, const char *str, const int *caps, int n) {
189
+ if (n <= 0) { sp_re_clear_last_match(); return; }
190
+ if (n > 64) n = 64;
191
+ for (int i = 0; i < n; i++) sp_re_caps[i] = caps[i];
192
+ sp_re_last_pat = pat;
193
+ sp_re_set_captures(str, sp_re_caps, n / 2);
194
+ }
195
+ /* The same for a String pattern found at bytes [beg, end) of str: a match
196
+ with no groups, whose Regexp is built only if `$~` is read. */
197
+ void sp_re_set_lit_match(const char *str, sp_int beg, sp_int end) {
198
+ sp_re_caps[0] = (int)beg; sp_re_caps[1] = (int)end;
199
+ sp_re_last_pat = NULL;
200
+ sp_re_set_captures(str, sp_re_caps, 1);
201
+ sp_re_last_lit = 1;
202
+ }
203
+
204
+ /* $` -- everything before the last match. */
205
+ const char *sp_re_pre_match(void) {
206
+ if (sp_re_match_pre || sp_re_pp_span[0] < 0 || !sp_re_last_str) return sp_re_match_pre;
207
+ int n = sp_re_pp_span[0];
208
+ char *pre = sp_str_alloc_raw(n + 1);
209
+ memcpy(pre, sp_re_last_str, n); pre[n] = 0;
210
+ sp_str_set_len(pre, (size_t)n);
211
+ sp_str_set_len(pre, (size_t)n);
212
+ sp_re_match_pre = pre;
213
+ return pre;
214
+ }
215
+
216
+ /* $\' -- everything after the last match. */
217
+ const char *sp_re_post_match(void) {
218
+ if (sp_re_match_post || sp_re_pp_span[1] < 0 || !sp_re_last_str) return sp_re_match_post;
219
+ int n = (int)sp_str_byte_len(sp_re_last_str) - sp_re_pp_span[1];
220
+ if (n < 0) n = 0;
221
+ char *post = sp_str_alloc_raw(n + 1);
222
+ memcpy(post, sp_re_last_str + sp_re_pp_span[1], n); post[n] = 0;
223
+ sp_str_set_len(post, (size_t)n);
224
+ sp_str_set_len(post, (size_t)n);
225
+ sp_re_match_post = post;
226
+ return post;
227
+ }
228
+ sp_int sp_re_match(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str);
229
+ if (!str) return -1;
230
+ int64_t slen = (int64_t)sp_str_byte_len(str);
231
+ int ncaps = 32;
232
+ int n = re_exec(pat, str, slen, 0, sp_re_caps, ncaps, sp_str_is_binary(str));
233
+ if (n > 0) { sp_re_last_pat = pat; sp_re_set_captures(str, sp_re_caps, n/2); return sp_re_caps[0]; }
234
+ /* Issue #848: clear backrefs on no-match so a subsequent `$1`
235
+ reads as nil rather than the previous match's group. */
236
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
237
+ sp_re_last_str = NULL;
238
+ sp_re_match_str = NULL;
239
+ sp_re_match_pre = NULL;
240
+ sp_re_match_post = NULL;
241
+ sp_re_pp_span[0] = sp_re_pp_span[1] = -1;
242
+ return -1;
243
+ }
244
+ /* Like sp_re_match, but search from byte offset `pos` in the FULL string so a
245
+ zero-width anchor (`\b`, a lookbehind) sees the preceding context -- passing
246
+ `str + pos` instead would make every position look like a string start (the
247
+ gsub/sub-with-block scan loop bug, #2910). Returns the match start relative
248
+ to `pos` (so the caller's `str + pos` arithmetic and the `< 0` no-match check
249
+ are unchanged); sp_re_caps stay full-string-relative for capture extraction. */
250
+ sp_int sp_re_match_at(mrb_regexp_pattern *pat, const char *str, sp_int pos) {SP_GC_ROOT_STR(str);
251
+ if (!str) return -1;
252
+ int64_t slen = (int64_t)sp_str_byte_len(str);
253
+ int ncaps = 32;
254
+ int n = re_exec(pat, str, slen, pos, sp_re_caps, ncaps, sp_str_is_binary(str));
255
+ if (n > 0) { sp_re_last_pat = pat; sp_re_set_captures(str, sp_re_caps, n/2); return sp_re_caps[0] - pos; }
256
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
257
+ sp_re_last_str = NULL;
258
+ sp_re_match_str = NULL;
259
+ sp_re_match_pre = NULL;
260
+ sp_re_match_post = NULL;
261
+ sp_re_pp_span[0] = sp_re_pp_span[1] = -1;
262
+ return -1;
263
+ }
264
+ /* sp_re_match_at for the turns of gsub / sub with a block: a miss leaves the
265
+ registers at the loop's last match, which is what `$~` reads once the call
266
+ returns. The loop clears them before its first turn, so a subject with no
267
+ match leaves nil. */
268
+ sp_int sp_re_match_next(mrb_regexp_pattern *pat, const char *str, sp_int pos) {SP_GC_ROOT_STR(str);
269
+ if (!str) return -1;
270
+ int caps[64];
271
+ int n = re_exec(pat, str, (int64_t)sp_str_byte_len(str), pos, caps, 64, sp_str_is_binary(str));
272
+ if (n <= 0 || caps[0] < 0) return -1;
273
+ sp_re_set_last_match(pat, str, caps, n);
274
+ return sp_re_caps[0] - pos;
275
+ }
276
+ /* MatchData#inspect: CRuby's #<MatchData "full" 1:"g1" ...> (named groups
277
+ render by name; unmatched groups render nil). */
278
+ const char *sp_MatchData_inspect(sp_MatchData *m) {SP_GC_ROOT(m);
279
+ if (!m) return SPL("nil");
280
+ sp_String *b = sp_String_new("#<MatchData "); SP_GC_ROOT(b);
281
+ sp_String_append(b, sp_str_inspect(sp_str_substr(m->source + m->caps[0], 0, m->caps[1] - m->caps[0])));
282
+ for (int g = 1; g < m->ncap; g++) {
283
+ const char *gname = re_group_name(m->pat, g);
284
+ sp_String_append(b, " ");
285
+ if (gname) { sp_String_append(b, gname); }
286
+ else {
287
+ char num[16]; snprintf(num, sizeof num, "%d", g);
288
+ sp_String_append(b, num);
289
+ }
290
+ sp_String_append(b, ":");
291
+ if (m->caps[g * 2] < 0) sp_String_append(b, "nil");
292
+ else sp_String_append(b, sp_str_inspect(sp_str_substr(m->source + m->caps[g * 2], 0, m->caps[g * 2 + 1] - m->caps[g * 2])));
293
+ }
294
+ sp_String_append(b, ">");
295
+ return sp_str_dup(sp_String_cstr(b));
296
+ }
297
+ /* s[/re/] = val: replace the first match's byte span with val (taken
298
+ literally, no template expansion); no match raises IndexError. */
299
+ const char *sp_str_splice_re(mrb_regexp_pattern *pat, const char *s, const char *val) {SP_GC_ROOT_STR(s);SP_GC_ROOT_STR(val);
300
+ if (!s) s = "";
301
+ if (!val) val = "";
302
+ int64_t slen = (int64_t)sp_str_byte_len(s);
303
+ int caps[2];
304
+ int n = re_exec(pat, s, slen, 0, caps, 2, sp_str_is_binary(s));
305
+ if (n <= 0) { sp_raise_cls("IndexError", "regexp not matched"); return s; }
306
+ return sp_sprintf("%.*s%s%s", (int)caps[0], s, val, s + caps[1]);
307
+ }
308
+ /* String#slice!(regexp): the removed match (or NULL when unmatched), with
309
+ the receiver's remainder written through rest_out and the match
310
+ registers set (cleared on no-match, like sp_re_match). */
311
+ const char *sp_str_slice_re(mrb_regexp_pattern *pat, const char *s, const char **rest_out) {SP_GC_ROOT_STR(s);
312
+ if (!s) s = &("\xff" "")[1]; /* header-safe empty: s flows to sp_str_byteslice -> sp_str_byte_len(s[-1]) */
313
+ int64_t slen = (int64_t)sp_str_byte_len(s);
314
+ int n = re_exec(pat, s, slen, 0, sp_re_caps, 32, sp_str_is_binary(s));
315
+ if (n <= 0) {
316
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
317
+ sp_re_last_str = NULL;
318
+ sp_re_match_str = NULL;
319
+ sp_re_match_pre = NULL;
320
+ sp_re_match_post = NULL;
321
+ if (rest_out) *rest_out = s;
322
+ return NULL;
323
+ }
324
+ int mb = sp_re_caps[0], me = sp_re_caps[1];
325
+ sp_re_set_captures(s, sp_re_caps, n / 2);
326
+ const char *m = sp_str_byteslice(s, mb, me - mb);
327
+ SP_GC_ROOT_STR(m);
328
+ if (rest_out) *rest_out = sp_sprintf("%.*s%s", mb, s, s + me);
329
+ return m;
330
+ }
331
+ sp_int sp_re_rindex(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str);
332
+ int64_t slen = (int64_t)sp_str_byte_len(str);
333
+ int caps[2];
334
+ int64_t pos = 0;
335
+ sp_int last = -1;
336
+ while (pos <= slen) {
337
+ int n = re_exec(pat, str, slen, pos, caps, 2, sp_str_is_binary(str));
338
+ if (n <= 0) break;
339
+ last = caps[0];
340
+ /* rindex keys on the rightmost match START (MRI reverse search): step
341
+ one past this start so a later-starting shorter match wins ("aaa"
342
+ rindex /a+/ is 2, not 0). Advancing past the whole match skipped it. */
343
+ pos = caps[0] + 1;
344
+ }
345
+ /* the result is a character index; caps[] holds byte offsets */
346
+ return last < 0 ? -1 : sp_str_count_chars(str, (size_t)last);
347
+ }
348
+ sp_StrArray *sp_re_rpartition(mrb_regexp_pattern *pat, const char *str) {
349
+ SP_GC_ROOT_STR(str);
350
+ int64_t slen = (int64_t)sp_str_byte_len(str);
351
+ int caps[2];
352
+ int64_t pos = 0;
353
+ sp_int ms = -1, me = -1;
354
+ while (pos <= slen) {
355
+ int n = re_exec(pat, str, slen, pos, caps, 2, sp_str_is_binary(str));
356
+ if (n <= 0) break;
357
+ ms = caps[0]; me = caps[1];
358
+ /* rpartition keys on the rightmost match START (MRI reverse search),
359
+ so step one past this start to look for a later-starting match. */
360
+ pos = caps[0] + 1;
361
+ }
362
+ sp_StrArray *r = sp_StrArray_new();
363
+ SP_GC_ROOT(r);
364
+ if (ms < 0) {
365
+ /* new Strings, as CRuby's: not the receiver, nor one shared "" */
366
+ sp_StrArray_push(r, sp_str_dup(sp_str_empty));
367
+ sp_StrArray_push(r, sp_str_dup(sp_str_empty));
368
+ sp_StrArray_push(r, sp_str_dup(str));
369
+ return r;
370
+ }
371
+ char *before = sp_str_alloc_raw(ms + 1);
372
+ memcpy(before, str, ms); before[ms] = 0;
373
+ sp_str_set_len(before, (size_t)ms);
374
+ int mlen = (int)(me - ms);
375
+ char *mid = sp_str_alloc_raw(mlen + 1);
376
+ memcpy(mid, str + ms, mlen); mid[mlen] = 0;
377
+ sp_str_set_len(mid, (size_t)mlen);
378
+ int alen = (int)(slen - me);
379
+ char *after = sp_str_alloc_raw(alen + 1);
380
+ memcpy(after, str + me, alen); after[alen] = 0;
381
+ sp_str_set_len(after, (size_t)alen);
382
+ sp_StrArray_push(r, before);
383
+ sp_StrArray_push(r, mid);
384
+ sp_StrArray_push(r, after);
385
+ return r;
386
+ }
387
+ sp_bool sp_re_match_p(mrb_regexp_pattern *pat, const char *str) {
388
+ if (!str) return FALSE;
389
+ int64_t slen = (int64_t)sp_str_byte_len(str);
390
+ int caps[2];
391
+ return re_exec(pat, str, slen, 0, caps, 2, sp_str_is_binary(str)) > 0;
392
+ }
393
+ sp_bool sp_re_match_p_at(mrb_regexp_pattern *pat, const char *str, sp_int pos) {
394
+ if (!str) return FALSE;
395
+ int64_t slen = (int64_t)sp_str_byte_len(str);
396
+ if (pos < 0) pos += slen;
397
+ if (pos < 0 || pos > slen) return FALSE;
398
+ int caps[2];
399
+ return re_exec(pat, str, slen, (sp_int)pos, caps, 2, sp_str_is_binary(str)) > 0;
400
+ }
401
+ /* Regexp#=== on a boxed operand (a case/when arm, an explicit ===). Only a
402
+ String (plain or shared-mutable handle) or a Symbol can match; a match
403
+ updates the $~ registers like =~. Any other operand answers false and
404
+ clears the registers (CRuby sets the backref to nil there). */
405
+ sp_bool sp_re_case_eq(mrb_regexp_pattern *pat, sp_RbVal v) {SP_GC_ROOT_RBVAL(v);
406
+ const char *s = NULL;
407
+ if (v.tag == SP_TAG_STR) s = v.v.s ? v.v.s : "";
408
+ else if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_STRBUF && v.v.p)
409
+ s = sp_String_cstr((sp_String *)v.v.p);
410
+ else if (v.tag == SP_TAG_SYM && sp_sym_name_fn)
411
+ s = sp_sym_name_fn((sp_sym)v.v.i);
412
+ if (!s) {
413
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
414
+ sp_re_last_str = NULL;
415
+ sp_re_match_str = NULL;
416
+ sp_re_match_pre = NULL;
417
+ sp_re_match_post = NULL;
418
+ return FALSE;
419
+ }
420
+ return sp_re_match(pat, s) >= 0;
421
+ }
422
+ /* The pattern behind a boxed value: a Regexp box is one already, and a String
423
+ is compiled the way `str.match?("b")` compiles its argument. Answers NULL for
424
+ anything else. Used by the poly-receiver match forms below (#3961). */
425
+ mrb_regexp_pattern *sp_poly_as_pattern(sp_RbVal v) {SP_GC_ROOT_RBVAL(v);
426
+ if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_REGEX) return (mrb_regexp_pattern *)v.v.p;
427
+ const char *s = NULL;
428
+ if (v.tag == SP_TAG_STR) s = v.v.s ? v.v.s : "";
429
+ else if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_STRBUF && v.v.p)
430
+ s = sp_String_cstr((sp_String *)v.v.p);
431
+ if (!s) return NULL;
432
+ return re_compile(s, (int64_t)sp_str_byte_len(s), 0);
433
+ }
434
+ /* The subject string behind a boxed value (a plain string, a shared handle, a
435
+ Symbol); NULL when the value is not one. */
436
+ static const char *sp_poly_subject(sp_RbVal v) {
437
+ if (v.tag == SP_TAG_STR) return v.v.s ? v.v.s : SPL("");
438
+ if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_STRBUF && v.v.p)
439
+ return sp_String_cstr((sp_String *)v.v.p);
440
+ if (v.tag == SP_TAG_SYM && sp_sym_name_fn) return sp_sym_name_fn((sp_sym)v.v.i);
441
+ return NULL;
442
+ }
443
+ /* `a.match?(b)` / `a.match(b)` / `a =~ b` where either operand only reads poly:
444
+ whichever side is the Regexp is the pattern, and the other is the subject --
445
+ the same rule CRuby applies, and the reason both `re.match?(s)` and
446
+ `s.match?(re)` work. A pattern that cannot be built answers no match. */
447
+ static int sp_poly_match_pair(sp_RbVal a, sp_RbVal b,
448
+ mrb_regexp_pattern **pat_out, const char **str_out) {
449
+ sp_RbVal pv, sv;
450
+ if (b.tag == SP_TAG_OBJ && b.cls_id == SP_BUILTIN_REGEX) { pv = b; sv = a; }
451
+ else if (a.tag == SP_TAG_OBJ && a.cls_id == SP_BUILTIN_REGEX) { pv = a; sv = b; }
452
+ /* Neither side is a Regexp, which is `str.match("x")`: CRuby builds the
453
+ pattern from the ARGUMENT and matches the receiver against it. Taking the
454
+ receiver as the pattern matched "x" against /xyz/ and answered no match
455
+ for every such call. */
456
+ else { pv = b; sv = a; }
457
+ const char *s = sp_poly_subject(sv);
458
+ if (!s) return 0;
459
+ mrb_regexp_pattern *p = sp_poly_as_pattern(pv);
460
+ if (!p) return 0;
461
+ *pat_out = p; *str_out = s;
462
+ return 1;
463
+ }
464
+ sp_bool sp_poly_match_p(sp_RbVal a, sp_RbVal b) {SP_GC_ROOT_RBVAL(a);SP_GC_ROOT_RBVAL(b);
465
+ mrb_regexp_pattern *p; const char *s;
466
+ if (!sp_poly_match_pair(a, b, &p, &s)) return FALSE;
467
+ return sp_re_match_p(p, s);
468
+ }
469
+ /* match?(pattern, pos) with a boxed operand: the same pair, from character
470
+ position pos of the subject */
471
+ sp_bool sp_poly_match_p_at(sp_RbVal a, sp_RbVal b, sp_int pos) {SP_GC_ROOT_RBVAL(a);SP_GC_ROOT_RBVAL(b);
472
+ mrb_regexp_pattern *p; const char *s;
473
+ if (!sp_poly_match_pair(a, b, &p, &s)) return FALSE;
474
+ return sp_str_re_match_p_at(p, s, pos);
475
+ }
476
+ sp_MatchData *sp_poly_match_data(sp_RbVal a, sp_RbVal b) {SP_GC_ROOT_RBVAL(a);SP_GC_ROOT_RBVAL(b);
477
+ mrb_regexp_pattern *p; const char *s;
478
+ if (!sp_poly_match_pair(a, b, &p, &s)) return NULL;
479
+ return sp_re_matchdata(p, s);
480
+ }
481
+ sp_int sp_poly_match_index(sp_RbVal a, sp_RbVal b) {SP_GC_ROOT_RBVAL(a);SP_GC_ROOT_RBVAL(b);
482
+ mrb_regexp_pattern *p; const char *s;
483
+ if (!sp_poly_match_pair(a, b, &p, &s)) return SP_INT_NIL;
484
+ sp_int r = sp_re_match(p, s);
485
+ return r < 0 ? SP_INT_NIL : r;
486
+ }
487
+ void sp_re_expand_rep(const mrb_regexp_pattern *pat,
488
+ char **out_io, size_t *olen_io, size_t *cap_io,
489
+ const char *rep, size_t rlen,
490
+ const char *src, int *caps, int ncaps) {
491
+ size_t olen = *olen_io;
492
+ char *out = *out_io;
493
+ size_t cap = *cap_io;
494
+ size_t i = 0;
495
+ while (i < rlen) {
496
+ char c = rep[i];
497
+ if (c == '\\' && i + 1 < rlen) {
498
+ char d = rep[i+1];
499
+ if ((d >= '0' && d <= '9') || d == '&' || d == '+') {
500
+ int gi = (d == '&') ? 0 : (d == '+') ? 0 : (d - '0');
501
+ /* A pattern that names a group turns `\1` through `\9` off, the same
502
+ rule that stops a plain `(...)` from taking a number there: the
503
+ number a named group answers to for md[1] is not one a replacement
504
+ may spend, and `\k<name>` is what reaches it. `\0` is the whole
505
+ match, which naming a group does not touch, and neither do `\&` and
506
+ `\+`. A literal String pattern hands in no pattern and has no group
507
+ for a number to reach either way. */
508
+ if (d >= '1' && d <= '9' && pat && re_num_named(pat) > 0) { i += 2; continue; }
509
+ if (d == '+') {
510
+ /* \+: the highest-numbered group that participated in the match;
511
+ none participating expands to "" (gi stays 0 with caps[0] the
512
+ whole match -- so scan for a real group first) */
513
+ gi = -1;
514
+ for (int g = 1; (g * 2) + 1 < ncaps; g++)
515
+ if (caps[g*2] >= 0 && caps[(g*2)+1] >= 0) gi = g;
516
+ if (gi < 0) { i += 2; continue; }
517
+ }
518
+ if ((gi*2) + 1 < ncaps && caps[gi*2] >= 0 && caps[(gi*2)+1] >= 0) {
519
+ int g_len = caps[(gi*2)+1] - caps[gi*2];
520
+ if (olen + g_len + 1 >= cap) { cap = ((olen + g_len) * 2) + 64; out = (char*)realloc(out, cap); }
521
+ memcpy(out+olen, src + caps[gi*2], g_len);
522
+ olen += g_len;
523
+ }
524
+ i += 2;
525
+ continue;
526
+ }
527
+ else if (d == '`' || d == '\'') {
528
+ /* \` is the text before the match, \' the text after it (#3550) */
529
+ int seg_beg = (d == '`') ? 0 : caps[1];
530
+ int seg_end = (d == '`') ? caps[0] : (int)strlen(src);
531
+ if (seg_beg >= 0 && seg_end >= seg_beg) {
532
+ int seg_len = seg_end - seg_beg;
533
+ if (olen + seg_len + 1 >= cap) { cap = ((olen + seg_len) * 2) + 64; out = (char*)realloc(out, cap); }
534
+ memcpy(out + olen, src + seg_beg, (size_t)seg_len);
535
+ olen += (size_t)seg_len;
536
+ }
537
+ i += 2;
538
+ continue;
539
+ }
540
+ else if (d == 'k' && i + 2 < rlen && rep[i+2] == '<') {
541
+ /* \k<name>: named backreference. The name resolves against the
542
+ pattern; an unknown or empty name raises IndexError (mirroring
543
+ md[:name]), and a group that did not participate expands to "".
544
+ Only the angle-bracket form is a replacement backref -- \k'name'
545
+ is regex-only syntax, so it is left literal (handled by the
546
+ default copy below when no closing '>' is found). */
547
+ size_t j = i + 3;
548
+ while (j < rlen && rep[j] != '>') j++;
549
+ if (j < rlen) {
550
+ size_t nlen = j - (i + 3);
551
+ /* Group names are short; keep them on the stack and only fall back
552
+ to malloc for an exceptionally long name. */
553
+ char name_buf[64];
554
+ char *name = name_buf;
555
+ if (nlen >= sizeof(name_buf)) {
556
+ name = (char*)malloc(nlen + 1);
557
+ if (!name) { perror("malloc"); exit(1); }
558
+ }
559
+ memcpy(name, rep + i + 3, nlen);
560
+ name[nlen] = '\0';
561
+ int gi = re_named_group(pat, name);
562
+ if (gi < 0) {
563
+ /* Build the message before freeing the name, then release the
564
+ scratch buffer: sp_raise_cls longjmps, so the caller's free()
565
+ never runs and `out` (post-realloc) would otherwise leak. */
566
+ const char *msg = sp_sprintf("undefined group name reference: %s", name);
567
+ if (name != name_buf) free(name);
568
+ free(out);
569
+ sp_raise_cls("IndexError", msg);
570
+ }
571
+ if (name != name_buf) free(name);
572
+ if ((gi*2) + 1 < ncaps && caps[gi*2] >= 0 && caps[(gi*2)+1] >= 0) {
573
+ int g_len = caps[(gi*2)+1] - caps[gi*2];
574
+ if (olen + g_len + 1 >= cap) { cap = ((olen + g_len) * 2) + 64; out = (char*)realloc(out, cap); }
575
+ memcpy(out+olen, src + caps[gi*2], g_len);
576
+ olen += g_len;
577
+ }
578
+ i = j + 1;
579
+ continue;
580
+ }
581
+ }
582
+ else if (d == '\\') {
583
+ if (olen + 1 >= cap) { cap = (cap * 2) + 64; out = (char*)realloc(out, cap); }
584
+ out[olen++] = '\\';
585
+ i += 2;
586
+ continue;
587
+ }
588
+ }
589
+ if (olen + 1 >= cap) { cap = (cap * 2) + 64; out = (char*)realloc(out, cap); }
590
+ out[olen++] = c;
591
+ i++;
592
+ }
593
+ *out_io = out; *olen_io = olen; *cap_io = cap;
594
+ }
595
+ const char *sp_re_gsub(mrb_regexp_pattern *pat, const char *str, const char *rep) {SP_GC_ROOT_STR(str);SP_GC_ROOT_STR(rep);if(!str)sp_nil_recv("gsub");
596
+ int64_t slen = (int64_t)sp_str_byte_len(str); size_t rlen = sp_str_byte_len(rep);
597
+ size_t cap = (slen * 2) + (rlen * 4) + 64;
598
+ /* Build into a plain malloc scratch: the buffer is grown with realloc
599
+ here and inside sp_re_expand_rep, which is only valid on a real
600
+ malloc base (a sp_str body pointer is offset past its header). The
601
+ final string is allocated at the exact length below. */
602
+ char *out = (char *)malloc(cap); size_t olen = 0;
603
+ int64_t pos = 0; int caps[64];
604
+ int lastcaps[64], lastn = 0; /* the last match, for `$~` */
605
+ while (pos <= slen) {
606
+ int n = re_exec(pat, str, slen, pos, caps, 64, sp_str_is_binary(str));
607
+ if (n <= 0 || caps[0] < 0) break;
608
+ sp_re_sub_matched = 1;
609
+ if (sp_re_track_last) { lastn = n > 64 ? 64 : n; memcpy(lastcaps, caps, sizeof(int) * (size_t)lastn); }
610
+ size_t before = caps[0] - pos;
611
+ if (olen+before+rlen >= cap) { cap = ((olen+before+rlen)*2)+64; out = (char*)realloc(out, cap); }
612
+ memcpy(out+olen, str+pos, before); olen += before;
613
+ sp_re_expand_rep(pat, &out, &olen, &cap, rep, rlen, str, caps, n);
614
+ if (caps[0] == caps[1]) {
615
+ /* Zero-width match (/^/, /$/, /\b/, an empty pattern): Ruby inserts
616
+ the replacement before the char at this position, keeps that char,
617
+ and advances past it. Copy the char and step by one so the scan
618
+ makes progress without dropping it or spinning on the same spot. */
619
+ if (caps[1] < slen) {
620
+ if (olen+1 >= cap) { cap = (olen*2)+64; out = (char*)realloc(out, cap); }
621
+ out[olen++] = str[caps[1]];
622
+ }
623
+ pos = caps[1] + 1;
624
+ }
625
+ else {
626
+ pos = caps[1];
627
+ }
628
+ }
629
+ /* pos can land at slen+1 after a zero-width match at the end; guard the
630
+ tail copy so `slen - pos` doesn't underflow size_t. */
631
+ if (pos < slen) {
632
+ size_t rest = slen - pos;
633
+ if (olen+rest+1 >= cap) { cap = olen+rest+64; out = (char*)realloc(out, cap); }
634
+ memcpy(out+olen, str+pos, rest); olen += rest;
635
+ }
636
+ if (sp_re_track_last) sp_re_set_last_match(pat, str, lastcaps, lastn);
637
+ /* Emit a string sized to exactly the bytes written (sp_str_alloc sets
638
+ the length and null-terminates); release the scratch. */
639
+ char *res = sp_str_alloc(olen);
640
+ memcpy(res, out, olen);
641
+ free(out);
642
+ return res;
643
+ }
644
+ const char *sp_re_sub(mrb_regexp_pattern *pat, const char *str, const char *rep) {SP_GC_ROOT_STR(str);SP_GC_ROOT_STR(rep);if(!str)sp_nil_recv("sub");
645
+ int64_t slen = (int64_t)sp_str_byte_len(str); size_t rlen = sp_str_byte_len(rep);
646
+ int caps[64];
647
+ int n = re_exec(pat, str, slen, 0, caps, 64, sp_str_is_binary(str));
648
+ /* no match: a new String all the same, as CRuby's sub and as gsub here */
649
+ if (n <= 0 || caps[0] < 0) { if (sp_re_track_last) sp_re_clear_last_match(); return sp_str_dup(str); }
650
+ sp_re_sub_matched = 1;
651
+ /* Issue #855: expand `\1`..`\9` / `\&` from rep against caps. */
652
+ size_t cap = caps[0] + (rlen * 4) + (slen - caps[1]) + 64;
653
+ /* malloc scratch: sp_re_expand_rep and the tail grow it with realloc,
654
+ which needs a real malloc base. Exact-sized string emitted below. */
655
+ char *out = (char *)malloc(cap);
656
+ memcpy(out, str, caps[0]);
657
+ size_t olen = caps[0];
658
+ sp_re_expand_rep(pat, &out, &olen, &cap, rep, rlen, str, caps, n);
659
+ size_t rest = slen - caps[1];
660
+ if (olen + rest + 1 >= cap) { cap = olen + rest + 64; out = (char*)realloc(out, cap); }
661
+ memcpy(out+olen, str+caps[1], rest); olen += rest;
662
+ if (sp_re_track_last) sp_re_set_last_match(pat, str, caps, n);
663
+ char *res = sp_str_alloc(olen);
664
+ memcpy(res, out, olen);
665
+ free(out);
666
+ return res;
667
+ }
668
+ sp_StrArray *sp_re_scan(mrb_regexp_pattern *pat, const char *str) {
669
+ SP_GC_ROOT_STR(str);
670
+ if (!str) sp_nil_recv("scan"); /* a nullable String's nil receiver */
671
+ sp_StrArray *arr = sp_StrArray_new();
672
+ SP_GC_ROOT(arr);
673
+ int64_t slen = (int64_t)sp_str_byte_len(str); int64_t pos = 0; int caps[64];
674
+ int lastcaps[64], lastn = 0; /* the last match, for `$~` */
675
+ while (pos <= slen) {
676
+ int n = re_exec(pat, str, slen, pos, caps, 64, sp_str_is_binary(str));
677
+ if (n <= 0 || caps[0] < 0) break;
678
+ if (sp_re_track_last) { lastn = n > 64 ? 64 : n; memcpy(lastcaps, caps, sizeof(int) * (size_t)lastn); }
679
+ int len = caps[1] - caps[0];
680
+ char *m = sp_str_alloc_raw(len+1); memcpy(m, str+caps[0], len); m[len] = 0;
681
+ sp_str_set_len(m, (size_t)len);
682
+ sp_StrArray_push(arr, m);
683
+ pos = caps[1]; if (caps[0] == caps[1]) pos++;
684
+ }
685
+ if (sp_re_track_last) sp_re_set_last_match(pat, str, lastcaps, lastn);
686
+ return arr;
687
+ }
688
+ /* Forward decl from the regexp engine (lib/regexp/re_utf8.c). */
689
+ int re_utf8_charlen(const char *s, const char *end);
690
+
691
+ /* String#split(regexp[, limit]). Mirrors CRuby / upstream mruby-regexp
692
+ string_regexp.rb#split: a zero-width match steps past one whole (multibyte-
693
+ aware) character and never emits an empty leading field; capture groups are
694
+ spliced between the surrounding fields (unmatched optional groups are
695
+ omitted, like md[i].nil?); and with the default limit (0) trailing empty
696
+ fields are stripped. limit > 0 caps the field count (the last field is the
697
+ unsplit remainder); limit < 0 keeps trailing empties. An empty subject is
698
+ always []. */
699
+ static void split_push_slice(sp_StrArray *arr, const char *str, int64_t from, int64_t to) {
700
+ int len = (int)(to - from);
701
+ char *m = sp_str_alloc_raw(len + 1);
702
+ memcpy(m, str + from, len); m[len] = 0;
703
+ sp_str_set_len(m, (size_t)len);
704
+ sp_StrArray_push(arr, m);
705
+ }
706
+
707
+ sp_StrArray *sp_re_split_limit(mrb_regexp_pattern *pat, const char *str, sp_int limit) {SP_GC_ROOT_STR(str);if(!str)sp_nil_recv("split");
708
+ sp_StrArray *arr = sp_StrArray_new();
709
+ int64_t slen = (int64_t)sp_str_byte_len(str);
710
+
711
+ /* limit == 1: the whole string is the single field; "" splits to []. */
712
+ if (limit == 1) {
713
+ if (slen > 0) split_push_slice(arr, str, 0, slen);
714
+ return arr;
715
+ }
716
+
717
+ int64_t field_start = 0, search_pos = 0, count = 0;
718
+ int caps[64];
719
+ while (search_pos <= slen) {
720
+ if (limit > 0 && count >= limit - 1) {
721
+ split_push_slice(arr, str, field_start, slen);
722
+ return arr;
723
+ }
724
+ int n = re_exec(pat, str, slen, search_pos, caps, 64, sp_str_is_binary(str));
725
+ if (n <= 0 || caps[0] < 0) break;
726
+ int64_t match_start = caps[0], match_end = caps[1];
727
+
728
+ if (match_start == match_end) {
729
+ /* zero-width: advance past one whole character so multibyte text is
730
+ not split between bytes. */
731
+ if (match_end < slen) {
732
+ search_pos = match_end + re_utf8_charlen(str + match_end, str + slen);
733
+ }
734
+ else {
735
+ search_pos = match_end + 1;
736
+ }
737
+ if (match_start == field_start) continue; /* nothing to emit yet */
738
+ }
739
+
740
+ split_push_slice(arr, str, field_start, match_start);
741
+ count++;
742
+ field_start = match_end;
743
+ if (match_start != match_end) search_pos = match_end;
744
+
745
+ /* Splice captured groups (caps[0] is the whole match; groups 1.. follow).
746
+ Unmatched optional groups are skipped, matching CRuby. */
747
+ for (int gi = 1; (gi * 2) + 1 < n; gi++) {
748
+ if (caps[gi*2] >= 0 && caps[(gi*2)+1] >= 0) {
749
+ split_push_slice(arr, str, caps[gi*2], caps[(gi*2)+1]);
750
+ }
751
+ }
752
+ }
753
+
754
+ /* Trailing field (omitted for an empty subject, or when stripped below). */
755
+ if (slen > 0 && field_start <= slen && (field_start < slen || limit != 0)) {
756
+ split_push_slice(arr, str, field_start, slen);
757
+ }
758
+
759
+ /* Default limit strips trailing empty fields. */
760
+ if (limit == 0) {
761
+ while (arr->len > 0 && arr->data[arr->len - 1][0] == '\0') arr->len--;
762
+ }
763
+ return arr;
764
+ }
765
+
766
+ sp_StrArray *sp_re_split(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str);if(!str)sp_nil_recv("split");
767
+ return sp_re_split_limit(pat, str, 0);
768
+ }
769
+ sp_int sp_re_rindex_opt(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str); sp_int n = sp_re_rindex(pat, str); return n < 0 ? SP_INT_NIL : n; }
770
+ sp_RbVal sp_re_rindex_poly(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str); sp_int n = sp_re_rindex(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(n); }
771
+ sp_RbVal sp_re_index_poly(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str); sp_int n = sp_re_match(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(sp_str_byte_to_char(str, n)); } /* char offset (#3056) */
772
+ /* String#index(regexp, start): first match at or after char position `start`,
773
+ as a char index -- SP_INT_NIL on miss / out-of-range (a nullable int, matching
774
+ sp_str_index_from_opt's ABI). */
775
+ /* String#byteindex(regexp[, start]): first match at or after BYTE offset
776
+ `start`, answered as a byte offset (SP_INT_NIL on miss). */
777
+ sp_int sp_re_byteindex_opt(mrb_regexp_pattern *pat, const char *str, sp_int start) {
778
+ if (!str) return SP_INT_NIL;
779
+ sp_int bl = (sp_int)sp_str_byte_len(str);
780
+ if (start < 0) start += bl;
781
+ if (start < 0 || start > bl) return SP_INT_NIL;
782
+ int caps[64];
783
+ int n = re_exec(pat, str, (int64_t)bl, start, caps, 64, sp_str_is_binary(str));
784
+ if (n <= 0 || caps[0] < 0) return SP_INT_NIL;
785
+ return (sp_int)caps[0];
786
+ }
787
+ /* String#byterindex(regexp[, start]): last match starting at or before BYTE
788
+ offset `start`, answered as a byte offset (SP_INT_NIL on miss). */
789
+ sp_int sp_re_byterindex_opt(mrb_regexp_pattern *pat, const char *str, sp_int start) {
790
+ if (!str) return SP_INT_NIL;
791
+ sp_int bl = (sp_int)sp_str_byte_len(str);
792
+ if (start < 0) start += bl;
793
+ if (start < 0) return SP_INT_NIL;
794
+ if (start > bl) start = bl;
795
+ /* the match STARTING latest wins (a match at 3 beats a longer one at 2),
796
+ so probe each start position from `start` downward */
797
+ int caps[2];
798
+ for (sp_int p = start; p >= 0; p--) {
799
+ int n = re_exec(pat, str, (int64_t)bl, p, caps, 2, sp_str_is_binary(str));
800
+ if (n > 0 && caps[0] == (int)p) return p;
801
+ }
802
+ return SP_INT_NIL;
803
+ }
804
+ sp_int sp_re_index_from_opt(mrb_regexp_pattern *pat, const char *str, sp_int start) {SP_GC_ROOT_STR(str);
805
+ if (!str) return SP_INT_NIL;
806
+ sp_int cl = sp_str_length(str);
807
+ if (start < 0) start += cl;
808
+ if (start < 0 || start > cl) return SP_INT_NIL;
809
+ size_t boff = sp_utf8_byte_offset(str, start);
810
+ int caps[64];
811
+ int n = re_exec(pat, str, (int64_t)sp_str_byte_len(str), (sp_int)boff, caps, 64, sp_str_is_binary(str));
812
+ if (n <= 0 || caps[0] < 0) return SP_INT_NIL;
813
+ return sp_str_count_chars(str, (size_t)caps[0]);
814
+ }
815
+ /* String#rindex(regexp, start): last match whose start is at or before char
816
+ position `start`, as a char index (SP_INT_NIL on miss). */
817
+ sp_int sp_re_rindex_from_opt(mrb_regexp_pattern *pat, const char *str, sp_int start) {SP_GC_ROOT_STR(str);
818
+ if (!str) return SP_INT_NIL;
819
+ sp_int cl = sp_str_length(str);
820
+ if (start < 0) start += cl;
821
+ if (start < 0) return SP_INT_NIL;
822
+ if (start > cl) start = cl;
823
+ size_t limit = sp_utf8_byte_offset(str, start);
824
+ int64_t slen = (int64_t)sp_str_byte_len(str);
825
+ int caps[2];
826
+ int64_t pos = 0; sp_int last = -1;
827
+ while (pos <= slen) {
828
+ int n = re_exec(pat, str, slen, pos, caps, 2, sp_str_is_binary(str));
829
+ if (n <= 0 || caps[0] < 0) break;
830
+ if ((size_t)caps[0] > limit) break;
831
+ last = caps[0];
832
+ int64_t next = caps[1]; if (next <= pos) next = pos + 1; pos = next;
833
+ }
834
+ return last < 0 ? SP_INT_NIL : sp_str_count_chars(str, (size_t)last);
835
+ }
836
+ sp_RbVal sp_re_match_poly(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str); sp_int n = sp_re_match(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(sp_str_byte_to_char(str, n)); } /* char offset (#3056) */
837
+ /* Value of the named group `name` from the most recent match registers (set by
838
+ sp_re_match / sp_re_match_poly). NULL (nil) when the last match failed, the
839
+ name is unknown, or the group did not participate. Used by `/(?<n>..)/ =~ s`
840
+ named-capture local binding (MatchWriteNode). */
841
+ /* A name the pattern has no group for is CRuby's IndexError; the callers
842
+ ask only once the pattern matched, so a failed match stays nil. */
843
+ const char *sp_re_named_capture(const mrb_regexp_pattern *pat, const char *name) {
844
+ if (!pat || !name || !sp_re_last_str) return NULL;
845
+ int g = re_named_group(pat, name);
846
+ if (g < 0) sp_raise_cls("IndexError", sp_sprintf("undefined group name reference: %s", name));
847
+ if ((g * 2) + 1 >= 64) return NULL;
848
+ int b = sp_re_caps[g * 2], e = sp_re_caps[(g * 2) + 1];
849
+ /* e < b also covers e < 0 once b >= 0; guards against a malformed register
850
+ state yielding a negative len that would cast to a huge size_t. */
851
+ if (b < 0 || e < b) return NULL;
852
+ int len = e - b;
853
+ char *out = sp_str_alloc(len);
854
+ memcpy(out, sp_re_last_str + b, len);
855
+ return out;
856
+ }
857
+ /* `a|b`, built byte-wise: sp_sprintf's %s ends at an embedded NUL, so a
858
+ branch carrying one joined short. Used by Regexp.union, both the runtime
859
+ array form below and the fixed-argument form the emitter builds. */
860
+ const char *sp_re_alt_join(const char *a, const char *b) {SP_GC_ROOT_STR(a);SP_GC_ROOT_STR(b);
861
+ size_t al = sp_str_byte_len(a), bl = sp_str_byte_len(b);
862
+ char *buf = sp_str_alloc_raw(al + 1 + bl + 1);
863
+ memcpy(buf, a, al);
864
+ buf[al] = '|';
865
+ memcpy(buf + al + 1, b, bl);
866
+ buf[al + 1 + bl] = 0;
867
+ sp_str_set_len(buf, al + 1 + bl);
868
+ return buf;
869
+ }
870
+
871
+ const char *sp_re_escape(const char *src) {SP_GC_ROOT_STR(src);
872
+ /* the text may hold a NUL, and strlen would escape only the part before
873
+ it -- which stayed invisible while a NUL-free prefix needed no escaping
874
+ at all and the function returned `src` untouched */
875
+ size_t i, in_len = sp_str_byte_len(src);
876
+ size_t out_len = 0;
877
+ for (i = 0; i < in_len; i++) {
878
+ unsigned char c = (unsigned char)src[i];
879
+ if (c == '\\' || c == '.' || c == '?' || c == '*' || c == '+' ||
880
+ c == '^' || c == '$' || c == '|' || c == '(' || c == ')' ||
881
+ c == '[' || c == ']' || c == '{' || c == '}' || c == '#' ||
882
+ c == '-' || c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
883
+ c == '\f' || c == '\v') {
884
+ out_len += 2;
885
+ }
886
+ else {
887
+ out_len += 1;
888
+ }
889
+ }
890
+ if (out_len == in_len) {
891
+ return src;
892
+ }
893
+ char *buf = sp_str_alloc(out_len);
894
+ size_t j = 0;
895
+ for (i = 0; i < in_len; i++) {
896
+ unsigned char c = (unsigned char)src[i];
897
+ if (c == '\\' || c == '.' || c == '?' || c == '*' || c == '+' ||
898
+ c == '^' || c == '$' || c == '|' || c == '(' || c == ')' ||
899
+ c == '[' || c == ']' || c == '{' || c == '}' || c == '#' ||
900
+ c == '-' || c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
901
+ c == '\f' || c == '\v') {
902
+ buf[j++] = '\\';
903
+ /* a control character escapes to its LETTER form ("\n", not a backslash
904
+ followed by a real newline), which is what CRuby produces (#3635) */
905
+ buf[j++] = c == '\n' ? 'n' : c == '\t' ? 't' : c == '\r' ? 'r'
906
+ : c == '\f' ? 'f' : c == '\v' ? 'v' : (char)c;
907
+ }
908
+ else {
909
+ buf[j++] = (char)c;
910
+ }
911
+ }
912
+ buf[j] = 0;
913
+ sp_str_set_len(buf, j);
914
+ return buf;
915
+ }
916
+ /* Regexp.union over a runtime array (elements known only at run time, e.g. an
917
+ Array held in a variable). Each element joins by alternation: a String is
918
+ regexp-escaped, a Regexp contributes its #to_s form `(?on-off:src)` so its
919
+ options survive. An empty array yields the never-matching /(?!)/. */
920
+ mrb_regexp_pattern *sp_re_union_array(sp_PolyArray *a) {
921
+ if (!a || a->len == 0) return re_compile("(?!)", 4, 0);
922
+ /* a lone Regexp is the answer itself, its source and flags as they are */
923
+ if (a->len == 1) {
924
+ sp_RbVal v0 = sp_PolyArray_get(a, 0);
925
+ if (v0.tag == SP_TAG_OBJ && v0.cls_id == SP_BUILTIN_REGEX && v0.v.p) return (mrb_regexp_pattern *)v0.v.p;
926
+ }
927
+ const char *joined = NULL;
928
+ for (sp_int i = 0; i < a->len; i++) {
929
+ sp_RbVal v = sp_PolyArray_get(a, i);
930
+ const char *part;
931
+ if (v.tag == SP_TAG_STR) part = sp_re_escape(v.v.s ? v.v.s : "");
932
+ else if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_REGEX) part = sp_re_to_s_str(v.v.p);
933
+ else { sp_raise_cls("TypeError", "no implicit conversion of element into String or Regexp"); return NULL; }
934
+ joined = (i == 0) ? part : sp_re_alt_join(joined, part);
935
+ }
936
+ return re_compile(joined, (int64_t)sp_str_byte_len(joined), 0);
937
+ }
938
+ sp_PolyArray *sp_re_scan_poly(mrb_regexp_pattern *pat, const char *str) {
939
+ SP_GC_ROOT_STR(str);
940
+ if (!str) sp_nil_recv("scan");
941
+ sp_PolyArray *arr = sp_PolyArray_new();
942
+ SP_GC_ROOT(arr);
943
+ int64_t slen = (int64_t)sp_str_byte_len(str);
944
+ int64_t pos = 0;
945
+ int ncaps = 64;
946
+ int caps[64];
947
+ int lastcaps[64], lastn = 0; /* the last match, for `$~` */
948
+ while (pos <= slen) {
949
+ int n = re_exec(pat, str, slen, pos, caps, ncaps, sp_str_is_binary(str));
950
+ if (n <= 0 || caps[0] < 0) break;
951
+ if (sp_re_track_last) { lastn = n > ncaps ? ncaps : n; memcpy(lastcaps, caps, sizeof(int) * (size_t)lastn); }
952
+ int pairs = (n > ncaps ? ncaps : n) / 2;
953
+ if (pairs <= 1) {
954
+ int len = caps[1] - caps[0];
955
+ char *m = sp_str_alloc_raw(len + 1);
956
+ memcpy(m, str + caps[0], len);
957
+ m[len] = 0;
958
+ sp_str_set_len(m, (size_t)len);
959
+ sp_PolyArray_push(arr, sp_box_str(m));
960
+ }
961
+ else {
962
+ sp_PolyArray *row = sp_PolyArray_new();
963
+ SP_GC_ROOT(row);
964
+ for (int gi = 1; gi < pairs; gi++) {
965
+ if (caps[gi * 2] >= 0 && caps[(gi * 2) + 1] >= 0) {
966
+ int glen = caps[(gi * 2) + 1] - caps[gi * 2];
967
+ char *gm = sp_str_alloc_raw(glen + 1);
968
+ memcpy(gm, str + caps[gi * 2], glen);
969
+ gm[glen] = 0;
970
+ sp_str_set_len(gm, (size_t)glen);
971
+ sp_PolyArray_push(row, sp_box_str(gm));
972
+ }
973
+ else {
974
+ sp_PolyArray_push(row, sp_box_nil());
975
+ }
976
+ }
977
+ sp_PolyArray_push(arr, sp_box_poly_array(row));
978
+ }
979
+ pos = caps[1];
980
+ if (caps[0] == caps[1]) pos++;
981
+ }
982
+ if (sp_re_track_last) sp_re_set_last_match(pat, str, lastcaps, lastn);
983
+ return arr;
984
+ }
985
+ sp_PolyArray *sp_re_match_data(mrb_regexp_pattern *pat, const char *str) {
986
+ SP_GC_ROOT_STR(str);
987
+ int64_t slen = (int64_t)sp_str_byte_len(str);
988
+ int ncaps = 64;
989
+ int n = re_exec(pat, str, slen, 0, sp_re_caps, ncaps, sp_str_is_binary(str));
990
+ if (n <= 0 || sp_re_caps[0] < 0) {
991
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
992
+ sp_re_last_str = NULL;
993
+ sp_re_match_str = NULL;
994
+ sp_re_match_pre = NULL;
995
+ sp_re_match_post = NULL;
996
+ return NULL;
997
+ }
998
+ int pairs = (n > ncaps ? ncaps : n) / 2;
999
+ sp_re_set_captures(str, sp_re_caps, pairs);
1000
+ sp_PolyArray *arr = sp_PolyArray_new();
1001
+ SP_GC_ROOT(arr);
1002
+ for (int i = 0; i < pairs; i++) {
1003
+ int start = sp_re_caps[i * 2];
1004
+ int end = sp_re_caps[(i * 2) + 1];
1005
+ if (start >= 0 && end >= start) {
1006
+ int len = end - start;
1007
+ char *buf = sp_str_alloc_raw(len + 1);
1008
+ memcpy(buf, str + start, len);
1009
+ buf[len] = 0;
1010
+ sp_str_set_len(buf, (size_t)len);
1011
+ sp_PolyArray_push(arr, sp_box_str(buf));
1012
+ }
1013
+ else {
1014
+ sp_PolyArray_push(arr, sp_box_nil());
1015
+ }
1016
+ }
1017
+ return arr;
1018
+ }
1019
+ /* Regexp#inspect and #to_s, built byte-wise.
1020
+ These used to live beside the engine in lib/regexp, where the only string
1021
+ builder available is sp_sprintf -- whose %s ends at a NUL, so a pattern
1022
+ holding one rendered short. They belong on this side anyway: the engine is
1023
+ a port from mruby-regexp and these are spinel's own surface. */
1024
+ static void sp_re_append_escaped_source(sp_String *b, void *vpat) {
1025
+ const char *src = sp_re_source(vpat);
1026
+ size_t n = sp_re_source_len(vpat);
1027
+ for (size_t i = 0; i < n; i++) {
1028
+ unsigned char c = (unsigned char)src[i];
1029
+ /* a literal `/` is escaped, unless it already is */
1030
+ if (c == '/' && (i == 0 || src[i - 1] != '\\')) {
1031
+ sp_String_append(b, "\\");
1032
+ sp_fd_append_len(b, src + i, 1);
1033
+ continue;
1034
+ }
1035
+ /* CRuby renders a byte that is neither printable nor one of the five
1036
+ whitespace controls as \xNN: measured, what it leaves raw is 0x09-0x0D,
1037
+ 0x20-0x7E, and everything above ASCII (a multi-byte character stays
1038
+ itself). A NUL rendered raw would also end any C string the text is
1039
+ handed to. */
1040
+ if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
1041
+ char hex[8];
1042
+ snprintf(hex, sizeof hex, "\\x%02X", c);
1043
+ sp_String_append(b, hex);
1044
+ continue;
1045
+ }
1046
+ sp_fd_append_len(b, src + i, 1);
1047
+ }
1048
+ }
1049
+
1050
+ const char *sp_re_inspect_str(void *vpat) {
1051
+ uint32_t f = sp_re_raw_flags(vpat);
1052
+ sp_String *b = sp_String_new(""); SP_GC_ROOT(b);
1053
+ sp_String_append(b, "/");
1054
+ sp_re_append_escaped_source(b, vpat);
1055
+ sp_String_append(b, "/");
1056
+ if (f & SP_RE_F_DOTALL) sp_String_append(b, "m");
1057
+ if (f & SP_RE_F_IGNORECASE) sp_String_append(b, "i");
1058
+ if (f & SP_RE_F_EXTENDED) sp_String_append(b, "x");
1059
+ return b->data;
1060
+ }
1061
+
1062
+ const char *sp_re_to_s_str(void *vpat) {
1063
+ uint32_t f = sp_re_raw_flags(vpat);
1064
+ char on[4], off[4]; int no = 0, nf = 0;
1065
+ if (f & SP_RE_F_DOTALL) on[no++] = 'm'; else off[nf++] = 'm';
1066
+ if (f & SP_RE_F_IGNORECASE) on[no++] = 'i'; else off[nf++] = 'i';
1067
+ if (f & SP_RE_F_EXTENDED) on[no++] = 'x'; else off[nf++] = 'x';
1068
+ on[no] = 0; off[nf] = 0;
1069
+ sp_String *b = sp_String_new(""); SP_GC_ROOT(b);
1070
+ sp_String_append(b, "(?");
1071
+ sp_String_append(b, on);
1072
+ if (nf) { sp_String_append(b, "-"); sp_String_append(b, off); }
1073
+ sp_String_append(b, ":");
1074
+ sp_re_append_escaped_source(b, vpat);
1075
+ sp_String_append(b, ")");
1076
+ return b->data;
1077
+ }
1078
+
1079
+ void sp_MatchData_scan(void *p) { sp_MatchData *m = (sp_MatchData *)p; if (m->source) sp_mark_string(m->source); }
1080
+ /* One block for the struct and the positions it carries, sized to the match
1081
+ rather than to the widest one this engine allows. The single allocation is
1082
+ also what keeps the two from ever disagreeing about who owns which. */
1083
+ static sp_MatchData *sp_md_alloc(int pairs) {
1084
+ size_t sz = sizeof(sp_MatchData) + (size_t)pairs * 2 * sizeof(int);
1085
+ return (sp_MatchData *)sp_gc_alloc(sz, NULL, sp_MatchData_scan);
1086
+ }
1087
+ sp_MatchData *sp_re_matchdata(mrb_regexp_pattern *pat, const char *str) {SP_GC_ROOT_STR(str);
1088
+ if (!str) return NULL; /* Regexp#match(nil) is nil, not a walk off NULL (#3633) */
1089
+ int64_t slen = (int64_t)sp_str_byte_len(str);
1090
+ int caps[64];
1091
+ int n = re_exec(pat, str, slen, 0, caps, 64, sp_str_is_binary(str));
1092
+ if (n <= 0 || caps[0] < 0) {
1093
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
1094
+ sp_re_last_str = NULL; sp_re_match_str = NULL;
1095
+ sp_re_match_pre = NULL; sp_re_match_post = NULL;
1096
+ return NULL;
1097
+ }
1098
+ int pairs = (n > 64 ? 64 : n) / 2;
1099
+ sp_re_set_captures(str, caps, pairs);
1100
+ sp_MatchData *m = sp_md_alloc(pairs);
1101
+ m->source = str;
1102
+ m->ncap = pairs;
1103
+ m->pat = pat;
1104
+ for (int i = 0; i < pairs * 2; i++) m->caps[i] = caps[i];
1105
+ return m;
1106
+ }
1107
+ /* String#match(/re/, pos) -- pos is a codepoint index (CRuby semantics). */
1108
+ sp_MatchData *sp_re_matchdata_at(mrb_regexp_pattern *pat, const char *str, sp_int cpos) {SP_GC_ROOT_STR(str);
1109
+ if (!str) return NULL;
1110
+ sp_int cl = sp_str_length(str);
1111
+ if (cpos < 0) cpos += cl;
1112
+ if (cpos < 0 || cpos > cl) return NULL;
1113
+ size_t boff = sp_utf8_byte_offset(str, cpos);
1114
+ int64_t slen = (int64_t)sp_str_byte_len(str);
1115
+ int caps[64];
1116
+ int n = re_exec(pat, str, slen, (sp_int)boff, caps, 64, sp_str_is_binary(str));
1117
+ if (n <= 0 || caps[0] < 0) {
1118
+ for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
1119
+ sp_re_last_str = NULL; sp_re_match_str = NULL;
1120
+ sp_re_match_pre = NULL; sp_re_match_post = NULL;
1121
+ return NULL;
1122
+ }
1123
+ int pairs = (n > 64 ? 64 : n) / 2;
1124
+ sp_re_set_captures(str, caps, pairs);
1125
+ sp_MatchData *m = sp_md_alloc(pairs);
1126
+ m->source = str;
1127
+ m->ncap = pairs;
1128
+ m->pat = pat;
1129
+ for (int i = 0; i < pairs * 2; i++) m->caps[i] = caps[i];
1130
+ return m;
1131
+ }
1132
+ /* group i substring, or NULL for a non-participating / out-of-range group */
1133
+ const char *sp_MatchData_aref(sp_MatchData *m, sp_int i) {SP_GC_ROOT(m);
1134
+ if (!m) return NULL;
1135
+ /* A negative index reaches the CAPTURE groups only: m[-1] is the last one and
1136
+ m[-(ncap)] -- which would be the whole match -- is nil, as in CRuby (#3628). */
1137
+ if (i < 0) { i += m->ncap; if (i < 1) return NULL; }
1138
+ if (i >= m->ncap) return NULL;
1139
+ int s = m->caps[i * 2], e = m->caps[(i * 2) + 1];
1140
+ if (s < 0 || e < s) return NULL;
1141
+ int len = e - s;
1142
+ char *b = sp_str_alloc((size_t)len);
1143
+ memcpy(b, m->source + s, len);
1144
+ b[len] = 0;
1145
+ sp_str_set_len(b, (size_t)len);
1146
+ return b;
1147
+ }
1148
+ /* group by name (`md[:name]` / `md["name"]`): resolve the name to its capture
1149
+ group via the pattern, then return that group's substring (NULL if the name
1150
+ is unknown or the group did not participate). */
1151
+ const char *sp_MatchData_aref_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name);
1152
+ if (!m || !name) return NULL;
1153
+ int g = re_named_group(m->pat, name);
1154
+ if (g < 0) sp_raise_cls("IndexError", sp_sprintf("undefined group name reference: %s", name));
1155
+ return sp_MatchData_aref(m, g);
1156
+ }
1157
+ /* `md.names`: the capture names in declaration order. */
1158
+ /* Regexp#names on the pattern itself: named groups in declaration order. */
1159
+ /* Regexp.linear_time?: a backreference (\1..\9, \k<name>, \g<name>) defeats
1160
+ the linear-time matcher. Inside a character class those are not
1161
+ backreferences, so track class membership (#3684). */
1162
+ sp_bool sp_re_src_linear_time(const char *src) {SP_GC_ROOT_STR(src);
1163
+ if (!src) return TRUE;
1164
+ int in_class = 0;
1165
+ for (const char *s = src; *s; s++) {
1166
+ if (*s == '\\' && s[1]) {
1167
+ char n = s[1];
1168
+ if (!in_class && ((n >= '1' && n <= '9') || n == 'k' || n == 'g')) return FALSE;
1169
+ s++;
1170
+ continue;
1171
+ }
1172
+ if (in_class) { if (*s == ']') in_class = 0; }
1173
+ else if (*s == '[') in_class = 1;
1174
+ }
1175
+ return TRUE;
1176
+ }
1177
+
1178
+ sp_StrArray *sp_Regexp_names(const mrb_regexp_pattern *pat) {
1179
+ sp_StrArray *a = sp_StrArray_new();
1180
+ SP_GC_ROOT(a);
1181
+ if (!pat) return a;
1182
+ int n = re_num_named(pat);
1183
+ for (int i = 0; i < n; i++) {
1184
+ const char *nm = re_named_name(pat, i, NULL);
1185
+ /* a name reused across alternatives is one name, however many groups
1186
+ carry it (#3682) */
1187
+ if (nm && !sp_StrArray_include(a, nm)) sp_StrArray_push(a, sp_str_dup(nm));
1188
+ }
1189
+ return a;
1190
+ }
1191
+
1192
+ /* MatchData#string: the frozen match subject. */
1193
+ const char *sp_MatchData_string(sp_MatchData *m) {
1194
+ return m ? m->source : NULL;
1195
+ }
1196
+
1197
+ sp_StrArray *sp_MatchData_names(sp_MatchData *m) {SP_GC_ROOT(m);
1198
+ sp_StrArray *a = sp_StrArray_new();
1199
+ SP_GC_ROOT(a);
1200
+ if (!m) return a;
1201
+ int n = re_num_named(m->pat);
1202
+ for (int i = 0; i < n; i++) {
1203
+ const char *nm = re_named_name(m->pat, i, NULL);
1204
+ if (nm && !sp_StrArray_include(a, nm)) sp_StrArray_push(a, sp_str_dup(nm));
1205
+ }
1206
+ return a;
1207
+ }
1208
+ sp_int sp_MatchData_length(sp_MatchData *m) { return m ? m->ncap : 0; }
1209
+ /* MatchData#== / #eql?: same match over the same source with identical capture
1210
+ spans (#2529). */
1211
+ sp_bool sp_MatchData_eq(sp_MatchData *a, sp_MatchData *b) {
1212
+ if (a == b) return TRUE;
1213
+ if (!a || !b || a->ncap != b->ncap) return FALSE;
1214
+ if (a->source && b->source) { if (strcmp(a->source, b->source) != 0) return FALSE; }
1215
+ else if (a->source != b->source) return FALSE;
1216
+ for (int i = 0; i < a->ncap * 2; i++) if (a->caps[i] != b->caps[i]) return FALSE;
1217
+ return TRUE;
1218
+ }
1219
+ /* A content-based hash over the same fields sp_MatchData_eq compares, so equal
1220
+ MatchData hash alike and different matches (usually) do not (#3014). */
1221
+ sp_int sp_MatchData_hash(sp_MatchData *m) {
1222
+ if (!m) return 0;
1223
+ uint64_t h = 1469598103934665603ULL; /* FNV-1a */
1224
+ if (m->source) for (const char *p = m->source; *p; p++) { h ^= (unsigned char)*p; h *= 1099511628211ULL; }
1225
+ h ^= (uint64_t)m->ncap; h *= 1099511628211ULL;
1226
+ for (int i = 0; i < m->ncap * 2; i++) { h ^= (uint64_t)(uint32_t)m->caps[i]; h *= 1099511628211ULL; }
1227
+ return (sp_int)(h >> 1); /* non-negative */
1228
+ }
1229
+ /* MatchData#[range]: the groups selected by a Range of indices (#2532). */
1230
+ sp_PolyArray *sp_MatchData_aref_range(sp_MatchData *m, sp_int beg, sp_int end, int excl) {SP_GC_ROOT(m);
1231
+ sp_PolyArray *a = sp_PolyArray_new(); SP_GC_ROOT(a);
1232
+ if (!m) return a;
1233
+ sp_int n = m->ncap;
1234
+ /* a beginless or endless bound carries the range sentinel, which the
1235
+ negative-index fixup below turned into a wild offset (#3628) */
1236
+ if (beg == INTPTR_MIN) beg = 0;
1237
+ if (end == INTPTR_MAX) { end = n - 1; excl = 0; }
1238
+ if (beg < 0) beg += n;
1239
+ if (end < 0) end += n;
1240
+ sp_int last = excl ? end - 1 : end;
1241
+ if (beg < 0 || beg > n) return NULL; /* nil in Ruby */
1242
+ for (sp_int i = beg; i <= last && i < n; i++) {
1243
+ const char *g = sp_MatchData_aref(m, i);
1244
+ sp_PolyArray_push(a, g ? sp_box_str(g) : sp_box_nil());
1245
+ }
1246
+ return a;
1247
+ }
1248
+ /* MatchData#[start, length]: an Array of `length` groups from `start` (nil for
1249
+ a group that did not participate), like Array#[start, length] (#2507). */
1250
+ sp_PolyArray *sp_MatchData_aref_len(sp_MatchData *m, sp_int start, sp_int len) {SP_GC_ROOT(m);
1251
+ sp_PolyArray *a = sp_PolyArray_new(); SP_GC_ROOT(a);
1252
+ if (!m) return a;
1253
+ if (start < 0) start += m->ncap;
1254
+ if (start < 0 || start > m->ncap || len < 0) return NULL; /* nil in Ruby */
1255
+ for (sp_int i = start; i < start + len && i < m->ncap; i++) {
1256
+ const char *g = sp_MatchData_aref(m, i);
1257
+ sp_PolyArray_push(a, g ? sp_box_str(g) : sp_box_nil());
1258
+ }
1259
+ return a;
1260
+ }
1261
+ /* char offset of a byte position within source */
1262
+ sp_int sp_md_char_off(sp_MatchData *m, int byteoff) {SP_GC_ROOT(m);
1263
+ if (byteoff < 0) return SP_INT_NIL;
1264
+ return sp_str_count_chars(m->source, (size_t)byteoff);
1265
+ }
1266
+ /* An index outside the match's groups is CRuby's IndexError, not nil (#3626). */
1267
+ static void sp_md_check_index(sp_MatchData *m, sp_int i) {
1268
+ if (!m || i < 0 || i >= m->ncap)
1269
+ sp_raise_cls("IndexError", sp_sprintf("index %lld out of matches", (long long)i));
1270
+ }
1271
+ sp_int sp_MatchData_begin(sp_MatchData *m, sp_int i) {SP_GC_ROOT(m);
1272
+ sp_md_check_index(m, i);
1273
+ if (!m || i < 0 || i >= m->ncap) return SP_INT_NIL;
1274
+ return sp_md_char_off(m, m->caps[i * 2]);
1275
+ }
1276
+ sp_int sp_MatchData_end(sp_MatchData *m, sp_int i) {SP_GC_ROOT(m);
1277
+ sp_md_check_index(m, i);
1278
+ if (!m || i < 0 || i >= m->ncap) return SP_INT_NIL;
1279
+ return sp_md_char_off(m, m->caps[(i * 2) + 1]);
1280
+ }
1281
+ sp_IntArray *sp_MatchData_offset(sp_MatchData *m, sp_int i) {SP_GC_ROOT(m);
1282
+ sp_md_check_index(m, i);
1283
+ sp_IntArray *a = sp_IntArray_new();
1284
+ if (!m || i < 0 || i >= m->ncap) { SP_MAY_NIL(a) = 1; sp_IntArray_push(a, SP_INT_NIL); sp_IntArray_push(a, SP_INT_NIL); return a; }
1285
+ sp_IntArray_push_nilable(a, sp_md_char_off(m, m->caps[i * 2]));
1286
+ sp_IntArray_push_nilable(a, sp_md_char_off(m, m->caps[(i * 2) + 1]));
1287
+ return a;
1288
+ }
1289
+ /* byte-offset accessors: the raw byte positions in source (no char conversion). */
1290
+ sp_int sp_MatchData_bytebegin(sp_MatchData *m, sp_int i) {
1291
+ sp_md_check_index(m, i);
1292
+ if (!m || i < 0 || i >= m->ncap || m->caps[i * 2] < 0) return SP_INT_NIL;
1293
+ return m->caps[i * 2];
1294
+ }
1295
+ sp_int sp_MatchData_byteend(sp_MatchData *m, sp_int i) {
1296
+ sp_md_check_index(m, i);
1297
+ if (!m || i < 0 || i >= m->ncap || m->caps[i * 2] < 0) return SP_INT_NIL;
1298
+ return m->caps[(i * 2) + 1];
1299
+ }
1300
+ sp_IntArray *sp_MatchData_byteoffset(sp_MatchData *m, sp_int i) {SP_GC_ROOT(m);
1301
+ sp_IntArray *a = sp_IntArray_new();
1302
+ if (!m || i < 0 || i >= m->ncap || m->caps[i * 2] < 0) { SP_MAY_NIL(a) = 1; sp_IntArray_push(a, SP_INT_NIL); sp_IntArray_push(a, SP_INT_NIL); return a; }
1303
+ sp_IntArray_push(a, m->caps[i * 2]);
1304
+ sp_IntArray_push(a, m->caps[(i * 2) + 1]);
1305
+ return a;
1306
+ }
1307
+ /* begin/end/offset/byte* by capture NAME (`md.begin("a")` / `:a`): resolve the
1308
+ name to its group index like MatchData#[], then defer to the index form. An
1309
+ unknown name raises IndexError, matching MRI. */
1310
+ static int sp_md_group_by_name(sp_MatchData *m, const char *name) {SP_GC_ROOT_STR(name);
1311
+ int g = m ? re_named_group(m->pat, name) : -1;
1312
+ if (g < 0) sp_raise_cls("IndexError", sp_sprintf("undefined group name reference: %s", name));
1313
+ return g;
1314
+ }
1315
+ sp_int sp_MatchData_begin_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_begin(m, sp_md_group_by_name(m, name)); }
1316
+ sp_int sp_MatchData_end_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_end(m, sp_md_group_by_name(m, name)); }
1317
+ sp_IntArray *sp_MatchData_offset_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_offset(m, sp_md_group_by_name(m, name)); }
1318
+ sp_int sp_MatchData_bytebegin_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_bytebegin(m, sp_md_group_by_name(m, name)); }
1319
+ sp_int sp_MatchData_byteend_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_byteend(m, sp_md_group_by_name(m, name)); }
1320
+ sp_IntArray *sp_MatchData_byteoffset_name(sp_MatchData *m, const char *name) {SP_GC_ROOT(m);SP_GC_ROOT_STR(name); return sp_MatchData_byteoffset(m, sp_md_group_by_name(m, name)); }
1321
+ /* whole-match string (group 0) -- also MatchData#to_s */
1322
+ const char *sp_MatchData_to_s(sp_MatchData *m) {SP_GC_ROOT(m); const char *r = sp_MatchData_aref(m, 0); return r ? r : sp_str_empty; }
1323
+ static sp_PolyArray *sp_md_groups_from(sp_MatchData *m, sp_int from) {SP_GC_ROOT(m);
1324
+ sp_PolyArray *r = sp_PolyArray_new();
1325
+ if (!m) return r;
1326
+ SP_GC_ROOT(r);
1327
+ for (sp_int i = from; i < m->ncap; i++) {
1328
+ const char *g = sp_MatchData_aref(m, i);
1329
+ sp_PolyArray_push(r, g ? sp_box_str(g) : sp_box_nil());
1330
+ }
1331
+ return r;
1332
+ }
1333
+ /* captures: groups 1..n-1 as a poly array (nil for non-participating) */
1334
+ sp_PolyArray *sp_MatchData_captures(sp_MatchData *m) { return sp_md_groups_from(m, 1); }
1335
+ /* to_a: group 0 + captures */
1336
+ sp_PolyArray *sp_MatchData_to_a(sp_MatchData *m) { return sp_md_groups_from(m, 0); }
1337
+ const char *sp_MatchData_pre_match(sp_MatchData *m) {SP_GC_ROOT(m);
1338
+ if (!m) return sp_str_empty;
1339
+ int e = m->caps[0];
1340
+ if (e <= 0) return sp_str_empty;
1341
+ char *b = sp_str_alloc((size_t)e);
1342
+ memcpy(b, m->source, e); b[e] = 0;
1343
+ sp_str_set_len(b, (size_t)e);
1344
+ return b;
1345
+ }
1346
+ const char *sp_MatchData_post_match(sp_MatchData *m) {SP_GC_ROOT(m);
1347
+ if (!m) return sp_str_empty;
1348
+ int s = m->caps[1];
1349
+ size_t sl = sp_str_byte_len(m->source);
1350
+ if (s < 0 || (size_t)s >= sl) return sp_str_empty;
1351
+ size_t len = sl - (size_t)s;
1352
+ char *b = sp_str_alloc(len);
1353
+ memcpy(b, m->source + s, len); b[len] = 0;
1354
+ sp_str_set_len(b, len);
1355
+ return b;
1356
+ }
1357
+ void sp_re_default_error_handler(const char *msg) {SP_GC_ROOT_STR(msg);
1358
+ /* msg points at the regex compiler's stack buffer. sp_raise_cls stores
1359
+ the pointer and longjmps past that frame, leaving it dangling -- copy
1360
+ to a GC-managed string first (mirrors sp_re_startup_error_handler).
1361
+ gcc happened to leave the stack intact; clang reused it, so e.message
1362
+ read garbage (regexp_error_catchable). */
1363
+ if (msg) {
1364
+ size_t n = strlen(msg);
1365
+ char *buf = sp_str_alloc_raw(n + 1);
1366
+ memcpy(buf, msg, n);
1367
+ buf[n] = 0;
1368
+ sp_str_set_len(buf, (size_t)n);
1369
+ msg = buf;
1370
+ }
1371
+ sp_raise_cls("RegexpError", msg);
1372
+ }
1373
+
1374
+ /* Regexp#hash: the source AND the flags. /ab/ and /ab/i are not eql?, so a
1375
+ hash over the source alone made them collide as Hash keys (#3816). */
1376
+ sp_int sp_re_hash(void *pat) {
1377
+ sp_int h = (sp_int)sp_str_hash(sp_re_source(pat));
1378
+ return h ^ ((sp_int)sp_re_options(pat) * 0x9E3779B1);
1379
+ }