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.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- 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
|
+
}
|