yeptris 0.6.57.1-arm-linux-eabihf → 0.6.59.1-arm-linux-eabihf
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 +4 -4
- data/ext/yeptris_native/version.h +2 -2
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/document.rb +15 -0
- data/lib/yeptris/ffi.rb +93 -0
- data/lib/yeptris/psych/visitors.rb +16 -6
- data/lib/yeptris/psych.rb +11 -5
- data/lib/yeptris/validation.rb +320 -0
- data/lib/yeptris/yaml.rb +67 -6
- data/lib/yeptris.rb +2 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/CMakeLists.txt +6 -0
- data/vendor/libyeptris/src/include/yeptris/error.h +2 -1
- data/vendor/libyeptris/src/include/yeptris/resolve.h +3 -1
- data/vendor/libyeptris/src/include/yeptris/validate.h +200 -0
- data/vendor/libyeptris/src/include/yeptris.h +1 -0
- data/vendor/libyeptris/src/yeptris/parse.c +24 -10
- data/vendor/libyeptris/src/yeptris/resolve/json12.c +103 -0
- data/vendor/libyeptris/src/yeptris/resolve/resolver.h +9 -0
- data/vendor/libyeptris/src/yeptris/validate/compile.c +270 -0
- data/vendor/libyeptris/src/yeptris/validate/internal.h +131 -0
- data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +1392 -0
- data/vendor/libyeptris/src/yeptris/validate/regexp.c +1134 -0
- data/vendor/libyeptris/src/yeptris/validate/validate.c +900 -0
- metadata +9 -1
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
/* regexp.c — the bounded ECMA-regex subset behind JSON Schema's
|
|
2
|
+
* `pattern` keyword (#882 slice 2; #885 item 2).
|
|
3
|
+
*
|
|
4
|
+
* Supported: literals, '.', [...] classes (ranges, negation, \d\w\s
|
|
5
|
+
* and their negations, \n\t\r\f\v\0, \xHH), groups (capturing ignored,
|
|
6
|
+
* (?:...) fine), alternation, the quantifiers * + ? {n} {n,} {n,m}
|
|
7
|
+
* (lazy markers parse and are ignored — match/no-match has no
|
|
8
|
+
* preference), the ^ $ \b \B assertions. Unsupported constructs
|
|
9
|
+
* (backreferences, lookaround) are SyntaxErrors, and unknown LETTER
|
|
10
|
+
* escapes are identity escapes (non-unicode ECMA-262 Annex B: \p
|
|
11
|
+
* without the u flag is the literal 'p'): the caller treats an invalid
|
|
12
|
+
* pattern as an absent keyword (2020-12's ignore posture), never a
|
|
13
|
+
* guess.
|
|
14
|
+
*
|
|
15
|
+
* Shape: byte-oriented — classes are ASCII, UTF-8 literals match
|
|
16
|
+
* byte-wise (ASCII patterns, the JSON Schema norm, are exact).
|
|
17
|
+
* Parse -> AST -> Thompson NFA -> Pike-style simulation: O(subject *
|
|
18
|
+
* program), NO backtracking blowup, no recursion over the subject.
|
|
19
|
+
* Every array is a fixed stack budget; overflow is a SyntaxError (-1),
|
|
20
|
+
* never a crash. Truth lives here once (MECE: a second consumer moves
|
|
21
|
+
* this to its own module).
|
|
22
|
+
*
|
|
23
|
+
* Two consumption shapes over one compiler (#885 item 3):
|
|
24
|
+
* `yep_re_search` compiles into a stack ctx and drops it (the one-shot
|
|
25
|
+
* walk, zero allocation); the retained-program cache
|
|
26
|
+
* (`yep_re_cache_*`) compiles a schema's patterns ONCE into exact-size
|
|
27
|
+
* heap programs and runs them per validated string — the XSD
|
|
28
|
+
* content-model economy. A cache miss recompiles (identical verdicts);
|
|
29
|
+
* the cache itself is immutable once populated.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
#include <string.h>
|
|
33
|
+
|
|
34
|
+
#include "internal.h"
|
|
35
|
+
|
|
36
|
+
#define RE_MAX_PAT 1024
|
|
37
|
+
#define RE_MAX_AST 256
|
|
38
|
+
#define RE_MAX_INST 320
|
|
39
|
+
#define RE_MAX_CLS 24
|
|
40
|
+
#define RE_AST_DEPTH 64
|
|
41
|
+
#define RE_REP_INF 0xFFFFu
|
|
42
|
+
|
|
43
|
+
enum {
|
|
44
|
+
A_BYTE = 1, /* a: the literal byte */
|
|
45
|
+
A_CLASS, /* cls */
|
|
46
|
+
A_ASSERT, /* a: assert kind */
|
|
47
|
+
A_CAT, /* a, b */
|
|
48
|
+
A_ALT, /* a, b */
|
|
49
|
+
A_REP, /* a; min..max */
|
|
50
|
+
A_EMPTY,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
enum { RE_AS_BOL = 0, RE_AS_EOL, RE_AS_WBOUND, RE_AS_NWBOUND };
|
|
54
|
+
|
|
55
|
+
typedef struct {
|
|
56
|
+
uint8_t kind;
|
|
57
|
+
uint8_t a, b; /* children / literal byte / assert kind */
|
|
58
|
+
uint8_t cls; /* class id (A_CLASS) */
|
|
59
|
+
uint8_t lazy; /* parsed, ignored */
|
|
60
|
+
uint16_t min, max;
|
|
61
|
+
} re_ast;
|
|
62
|
+
|
|
63
|
+
typedef struct {
|
|
64
|
+
uint8_t op; /* RE_OP_* */
|
|
65
|
+
uint8_t cls; /* class id (CHAR) / assert kind (ASSERT) */
|
|
66
|
+
uint16_t x; /* jump target / literal byte */
|
|
67
|
+
uint16_t y; /* second jump target (SPLIT) */
|
|
68
|
+
} re_inst;
|
|
69
|
+
|
|
70
|
+
enum { RE_OP_CHAR = 1, RE_OP_BYTE, RE_OP_SPLIT, RE_OP_JMP, RE_OP_ASSERT, RE_OP_MATCH };
|
|
71
|
+
|
|
72
|
+
/* Pending exit arrows of one compiled fragment: a bitset over
|
|
73
|
+
* (pc * 2 + slot), exactly RE_MAX_INST * 2 bits. frag_connect resolves
|
|
74
|
+
* and CLEARS — an arrow points at exactly one target, once. */
|
|
75
|
+
#define RE_FRAG_BYTES ((RE_MAX_INST * 2 + 7) / 8)
|
|
76
|
+
typedef struct {
|
|
77
|
+
int in; /* entry pc; -1 = empty (arrows only) */
|
|
78
|
+
uint8_t bits[RE_FRAG_BYTES];
|
|
79
|
+
} re_frag;
|
|
80
|
+
|
|
81
|
+
typedef struct {
|
|
82
|
+
/* parse */
|
|
83
|
+
const char* pat;
|
|
84
|
+
uint32_t pn, pi;
|
|
85
|
+
int err;
|
|
86
|
+
re_ast ast[RE_MAX_AST];
|
|
87
|
+
int ast_n;
|
|
88
|
+
uint8_t cls[RE_MAX_CLS][32];
|
|
89
|
+
int cls_n;
|
|
90
|
+
/* compile */
|
|
91
|
+
re_inst inst[RE_MAX_INST];
|
|
92
|
+
int inst_n;
|
|
93
|
+
} re_ctx;
|
|
94
|
+
|
|
95
|
+
static int re_word(unsigned char b) {
|
|
96
|
+
return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ---- parse ------------------------------------------------------------ */
|
|
100
|
+
|
|
101
|
+
static int ast_new(re_ctx* c, uint8_t kind) {
|
|
102
|
+
if (c->ast_n >= RE_MAX_AST) {
|
|
103
|
+
c->err = 1;
|
|
104
|
+
return -1;
|
|
105
|
+
}
|
|
106
|
+
int id = c->ast_n++;
|
|
107
|
+
memset(&c->ast[id], 0, sizeof(c->ast[id]));
|
|
108
|
+
c->ast[id].kind = kind;
|
|
109
|
+
c->ast[id].min = 1;
|
|
110
|
+
c->ast[id].max = 1;
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static int cls_new(re_ctx* c) {
|
|
115
|
+
if (c->cls_n >= RE_MAX_CLS) {
|
|
116
|
+
c->err = 1;
|
|
117
|
+
return -1;
|
|
118
|
+
}
|
|
119
|
+
int id = c->cls_n++;
|
|
120
|
+
memset(c->cls[id], 0, 32);
|
|
121
|
+
return id;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static void cls_set(re_ctx* c, int id, unsigned b) {
|
|
125
|
+
c->cls[id][(b >> 3) & 31] |= (uint8_t)(1u << (b & 7));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static void cls_add_named(re_ctx* c, int id, int neg, int kind) {
|
|
129
|
+
for (int b = 0; b < 256; b++) {
|
|
130
|
+
int m;
|
|
131
|
+
if (kind == 0) {
|
|
132
|
+
m = (b >= '0' && b <= '9');
|
|
133
|
+
} else if (kind == 1) {
|
|
134
|
+
m = (b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') ||
|
|
135
|
+
b == '_';
|
|
136
|
+
} else {
|
|
137
|
+
m = b == ' ' || b == '\t' || b == '\n' || b == '\r' || b == '\f' || b == '\v';
|
|
138
|
+
}
|
|
139
|
+
if (neg) {
|
|
140
|
+
m = !m;
|
|
141
|
+
}
|
|
142
|
+
if (m) {
|
|
143
|
+
cls_set(c, id, (unsigned)b);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static int p_hex(re_ctx* c, int nibbles) {
|
|
149
|
+
int v = 0;
|
|
150
|
+
for (int i = 0; i < nibbles; i++) {
|
|
151
|
+
if (c->pi >= c->pn) {
|
|
152
|
+
return -1;
|
|
153
|
+
}
|
|
154
|
+
char ch = c->pat[c->pi];
|
|
155
|
+
int d;
|
|
156
|
+
if (ch >= '0' && ch <= '9') {
|
|
157
|
+
d = ch - '0';
|
|
158
|
+
} else if (ch >= 'a' && ch <= 'f') {
|
|
159
|
+
d = ch - 'a' + 10;
|
|
160
|
+
} else if (ch >= 'A' && ch <= 'F') {
|
|
161
|
+
d = ch - 'A' + 10;
|
|
162
|
+
} else {
|
|
163
|
+
return -1;
|
|
164
|
+
}
|
|
165
|
+
v = v * 16 + d;
|
|
166
|
+
c->pi++;
|
|
167
|
+
}
|
|
168
|
+
return v;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* One escape OUTSIDE a class. 0: byte in *out; 1: class id in *out;
|
|
172
|
+
* -2: assertion kind in *out; -1: SyntaxError. */
|
|
173
|
+
static int p_escape(re_ctx* c, int* out) {
|
|
174
|
+
if (c->pi >= c->pn) {
|
|
175
|
+
c->err = 1;
|
|
176
|
+
return -1;
|
|
177
|
+
}
|
|
178
|
+
char e = c->pat[c->pi++];
|
|
179
|
+
switch (e) {
|
|
180
|
+
case 'd':
|
|
181
|
+
case 'w':
|
|
182
|
+
case 's': {
|
|
183
|
+
int id = cls_new(c);
|
|
184
|
+
if (id < 0) {
|
|
185
|
+
return -1;
|
|
186
|
+
}
|
|
187
|
+
cls_add_named(c, id, 0, e == 'd' ? 0 : (e == 'w' ? 1 : 2));
|
|
188
|
+
*out = id;
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
case 'D':
|
|
192
|
+
case 'W':
|
|
193
|
+
case 'S': {
|
|
194
|
+
int id = cls_new(c);
|
|
195
|
+
if (id < 0) {
|
|
196
|
+
return -1;
|
|
197
|
+
}
|
|
198
|
+
cls_add_named(c, id, 1, e == 'D' ? 0 : (e == 'W' ? 1 : 2));
|
|
199
|
+
*out = id;
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
case 'b':
|
|
203
|
+
*out = RE_AS_WBOUND;
|
|
204
|
+
return -2;
|
|
205
|
+
case 'B':
|
|
206
|
+
*out = RE_AS_NWBOUND;
|
|
207
|
+
return -2;
|
|
208
|
+
case 'n':
|
|
209
|
+
*out = '\n';
|
|
210
|
+
return 0;
|
|
211
|
+
case 't':
|
|
212
|
+
*out = '\t';
|
|
213
|
+
return 0;
|
|
214
|
+
case 'r':
|
|
215
|
+
*out = '\r';
|
|
216
|
+
return 0;
|
|
217
|
+
case 'f':
|
|
218
|
+
*out = '\f';
|
|
219
|
+
return 0;
|
|
220
|
+
case 'v':
|
|
221
|
+
*out = '\v';
|
|
222
|
+
return 0;
|
|
223
|
+
case '0':
|
|
224
|
+
*out = 0;
|
|
225
|
+
return 0;
|
|
226
|
+
case 'x': {
|
|
227
|
+
int v = p_hex(c, 2);
|
|
228
|
+
if (v < 0) {
|
|
229
|
+
c->err = 1;
|
|
230
|
+
return -1;
|
|
231
|
+
}
|
|
232
|
+
*out = v;
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
/* non-unicode ECMA-262 (Annex B): unknown LETTER escapes are
|
|
237
|
+
* identity escapes — \p outside unicode mode is the literal
|
|
238
|
+
* 'p' (property escapes need the u flag this kernel does not
|
|
239
|
+
* carry). Digits stay SyntaxError: 1-9 are backreferences,
|
|
240
|
+
* out of the subset — an ignored pattern beats a misread one.
|
|
241
|
+
* Punctuation is identity too. */
|
|
242
|
+
if (e >= '0' && e <= '9') {
|
|
243
|
+
c->err = 1;
|
|
244
|
+
return -1;
|
|
245
|
+
}
|
|
246
|
+
*out = (unsigned char)e;
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static int p_byte_atom(re_ctx* c, unsigned b) {
|
|
252
|
+
int id = ast_new(c, A_BYTE);
|
|
253
|
+
if (id < 0) {
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
256
|
+
c->ast[id].a = (uint8_t)b;
|
|
257
|
+
return id;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static int p_class_atom(re_ctx* c, int cid) {
|
|
261
|
+
int id = ast_new(c, A_CLASS);
|
|
262
|
+
if (id < 0) {
|
|
263
|
+
return -1;
|
|
264
|
+
}
|
|
265
|
+
c->ast[id].cls = (uint8_t)cid;
|
|
266
|
+
return id;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Parses the body of a [...] (the '[' already consumed); -1 on error. */
|
|
270
|
+
static int p_class(re_ctx* c) {
|
|
271
|
+
int id = cls_new(c);
|
|
272
|
+
if (id < 0) {
|
|
273
|
+
return -1;
|
|
274
|
+
}
|
|
275
|
+
int neg = 0;
|
|
276
|
+
if (c->pi < c->pn && c->pat[c->pi] == '^') {
|
|
277
|
+
neg = 1;
|
|
278
|
+
c->pi++;
|
|
279
|
+
}
|
|
280
|
+
int first = 1;
|
|
281
|
+
for (;;) {
|
|
282
|
+
if (c->pi >= c->pn) {
|
|
283
|
+
c->err = 1; /* unterminated class */
|
|
284
|
+
return -1;
|
|
285
|
+
}
|
|
286
|
+
char ch = c->pat[c->pi];
|
|
287
|
+
if (ch == ']' && !first) {
|
|
288
|
+
c->pi++;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
first = 0;
|
|
292
|
+
int lo;
|
|
293
|
+
if (ch == '\\') {
|
|
294
|
+
c->pi++;
|
|
295
|
+
int v = 0;
|
|
296
|
+
int rc = p_escape(c, &v);
|
|
297
|
+
if (rc < 0) {
|
|
298
|
+
c->err = 1; /* \b-as-BACKSPACE unsupported on purpose */
|
|
299
|
+
return -1;
|
|
300
|
+
}
|
|
301
|
+
if (rc == 1) {
|
|
302
|
+
for (int b = 0; b < 256; b++) {
|
|
303
|
+
if (c->cls[v][(b >> 3) & 31] & (1u << (b & 7))) {
|
|
304
|
+
cls_set(c, id, (unsigned)b);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
lo = v;
|
|
310
|
+
} else {
|
|
311
|
+
lo = (unsigned char)ch;
|
|
312
|
+
c->pi++;
|
|
313
|
+
}
|
|
314
|
+
if (c->pi + 1 < c->pn && c->pat[c->pi] == '-' && c->pat[c->pi + 1] != ']') {
|
|
315
|
+
c->pi++; /* '-' */
|
|
316
|
+
char hc = c->pat[c->pi];
|
|
317
|
+
int hi;
|
|
318
|
+
if (hc == '\\') {
|
|
319
|
+
c->pi++;
|
|
320
|
+
int v = 0;
|
|
321
|
+
if (p_escape(c, &v) != 0) {
|
|
322
|
+
c->err = 1;
|
|
323
|
+
return -1;
|
|
324
|
+
}
|
|
325
|
+
hi = v;
|
|
326
|
+
} else {
|
|
327
|
+
hi = (unsigned char)hc;
|
|
328
|
+
c->pi++;
|
|
329
|
+
}
|
|
330
|
+
if (hi < lo) {
|
|
331
|
+
c->err = 1;
|
|
332
|
+
return -1;
|
|
333
|
+
}
|
|
334
|
+
for (int b = lo; b <= hi; b++) {
|
|
335
|
+
cls_set(c, id, (unsigned)b);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
cls_set(c, id, (unsigned)lo);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (neg) {
|
|
342
|
+
for (int b = 0; b < 32; b++) {
|
|
343
|
+
c->cls[id][b] = (uint8_t)~c->cls[id][b];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return id;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* quantifier: 0 none, 1 '*' 2 '+' 3 '?' 4 '{n,m}'; -1 SyntaxError */
|
|
350
|
+
static int p_quant(re_ctx* c, uint16_t* min, uint16_t* max, int* lazy) {
|
|
351
|
+
if (c->pi >= c->pn) {
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
char q = c->pat[c->pi];
|
|
355
|
+
int kind = 0;
|
|
356
|
+
if (q == '*') {
|
|
357
|
+
kind = 1;
|
|
358
|
+
*min = 0;
|
|
359
|
+
*max = RE_REP_INF;
|
|
360
|
+
c->pi++;
|
|
361
|
+
} else if (q == '+') {
|
|
362
|
+
kind = 2;
|
|
363
|
+
*min = 1;
|
|
364
|
+
*max = RE_REP_INF;
|
|
365
|
+
c->pi++;
|
|
366
|
+
} else if (q == '?') {
|
|
367
|
+
kind = 3;
|
|
368
|
+
*min = 0;
|
|
369
|
+
*max = 1;
|
|
370
|
+
c->pi++;
|
|
371
|
+
} else if (q == '{') {
|
|
372
|
+
uint32_t save = c->pi;
|
|
373
|
+
c->pi++;
|
|
374
|
+
uint32_t n = 0;
|
|
375
|
+
int dn = 0;
|
|
376
|
+
while (c->pi < c->pn && c->pat[c->pi] >= '0' && c->pat[c->pi] <= '9') {
|
|
377
|
+
n = n * 10 + (uint32_t)(c->pat[c->pi] - '0');
|
|
378
|
+
if (n > 255) {
|
|
379
|
+
c->pi = save;
|
|
380
|
+
return 0; /* literal '{' */
|
|
381
|
+
}
|
|
382
|
+
c->pi++;
|
|
383
|
+
dn++;
|
|
384
|
+
}
|
|
385
|
+
if (dn == 0) {
|
|
386
|
+
c->pi = save;
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
uint32_t m = n;
|
|
390
|
+
int have_comma = 0;
|
|
391
|
+
if (c->pi < c->pn && c->pat[c->pi] == ',') {
|
|
392
|
+
have_comma = 1;
|
|
393
|
+
c->pi++;
|
|
394
|
+
m = 0;
|
|
395
|
+
int dm = 0;
|
|
396
|
+
while (c->pi < c->pn && c->pat[c->pi] >= '0' && c->pat[c->pi] <= '9') {
|
|
397
|
+
m = m * 10 + (uint32_t)(c->pat[c->pi] - '0');
|
|
398
|
+
if (m > 255) {
|
|
399
|
+
c->pi = save;
|
|
400
|
+
return 0;
|
|
401
|
+
}
|
|
402
|
+
c->pi++;
|
|
403
|
+
dm++;
|
|
404
|
+
}
|
|
405
|
+
if (dm == 0) {
|
|
406
|
+
m = RE_REP_INF; /* {n,} */
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (c->pi >= c->pn || c->pat[c->pi] != '}') {
|
|
410
|
+
c->pi = save;
|
|
411
|
+
return 0; /* literal '{' */
|
|
412
|
+
}
|
|
413
|
+
c->pi++;
|
|
414
|
+
if (!have_comma) {
|
|
415
|
+
m = n; /* {n} */
|
|
416
|
+
}
|
|
417
|
+
if (m != RE_REP_INF && m < n) {
|
|
418
|
+
c->err = 1;
|
|
419
|
+
return -1;
|
|
420
|
+
}
|
|
421
|
+
*min = (uint16_t)n;
|
|
422
|
+
*max = (uint16_t)m;
|
|
423
|
+
kind = 4;
|
|
424
|
+
}
|
|
425
|
+
if (kind != 0) {
|
|
426
|
+
*lazy = 0;
|
|
427
|
+
if (c->pi < c->pn && c->pat[c->pi] == '?') {
|
|
428
|
+
*lazy = 1;
|
|
429
|
+
c->pi++;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return kind;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
static int p_alt(re_ctx* c, int depth);
|
|
436
|
+
|
|
437
|
+
static int p_atom(re_ctx* c, int depth) {
|
|
438
|
+
if (c->pi >= c->pn) {
|
|
439
|
+
c->err = 1;
|
|
440
|
+
return -1;
|
|
441
|
+
}
|
|
442
|
+
char ch = c->pat[c->pi];
|
|
443
|
+
if (ch == '(') {
|
|
444
|
+
c->pi++;
|
|
445
|
+
if (c->pi + 1 < c->pn && c->pat[c->pi] == '?' && c->pat[c->pi + 1] == ':') {
|
|
446
|
+
c->pi += 2;
|
|
447
|
+
} else if (c->pi < c->pn && c->pat[c->pi] == '?') {
|
|
448
|
+
c->err = 1; /* lookaround/flags unsupported */
|
|
449
|
+
return -1;
|
|
450
|
+
}
|
|
451
|
+
int id = p_alt(c, depth + 1);
|
|
452
|
+
if (id < 0) {
|
|
453
|
+
return -1;
|
|
454
|
+
}
|
|
455
|
+
if (c->pi >= c->pn || c->pat[c->pi] != ')') {
|
|
456
|
+
c->err = 1;
|
|
457
|
+
return -1;
|
|
458
|
+
}
|
|
459
|
+
c->pi++;
|
|
460
|
+
return id;
|
|
461
|
+
}
|
|
462
|
+
if (ch == '[') {
|
|
463
|
+
c->pi++;
|
|
464
|
+
int cid = p_class(c);
|
|
465
|
+
return cid < 0 ? -1 : p_class_atom(c, cid);
|
|
466
|
+
}
|
|
467
|
+
if (ch == '.') {
|
|
468
|
+
c->pi++;
|
|
469
|
+
int cid = cls_new(c);
|
|
470
|
+
if (cid < 0) {
|
|
471
|
+
return -1;
|
|
472
|
+
}
|
|
473
|
+
for (int b = 0; b < 256; b++) {
|
|
474
|
+
if (b != '\n') {
|
|
475
|
+
cls_set(c, cid, (unsigned)b);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return p_class_atom(c, cid);
|
|
479
|
+
}
|
|
480
|
+
if (ch == '^' || ch == '$') {
|
|
481
|
+
c->pi++;
|
|
482
|
+
int id = ast_new(c, A_ASSERT);
|
|
483
|
+
if (id < 0) {
|
|
484
|
+
return -1;
|
|
485
|
+
}
|
|
486
|
+
c->ast[id].a = (ch == '^') ? RE_AS_BOL : RE_AS_EOL;
|
|
487
|
+
return id;
|
|
488
|
+
}
|
|
489
|
+
if (ch == '\\') {
|
|
490
|
+
c->pi++;
|
|
491
|
+
int v = 0;
|
|
492
|
+
int rc = p_escape(c, &v);
|
|
493
|
+
if (rc < 0) {
|
|
494
|
+
if (rc == -2) {
|
|
495
|
+
int id = ast_new(c, A_ASSERT);
|
|
496
|
+
if (id < 0) {
|
|
497
|
+
return -1;
|
|
498
|
+
}
|
|
499
|
+
c->ast[id].a = (uint8_t)v;
|
|
500
|
+
return id;
|
|
501
|
+
}
|
|
502
|
+
return -1;
|
|
503
|
+
}
|
|
504
|
+
return rc == 1 ? p_class_atom(c, v) : p_byte_atom(c, (unsigned)v);
|
|
505
|
+
}
|
|
506
|
+
if (ch == '*' || ch == '+' || ch == '?') {
|
|
507
|
+
c->err = 1; /* dangling quantifier */
|
|
508
|
+
return -1;
|
|
509
|
+
}
|
|
510
|
+
if (ch == ')' || ch == '|') {
|
|
511
|
+
c->err = 1; /* misplaced terminator */
|
|
512
|
+
return -1;
|
|
513
|
+
}
|
|
514
|
+
c->pi++; /* literal byte (UTF-8 rides byte-wise) */
|
|
515
|
+
return p_byte_atom(c, (unsigned)(unsigned char)ch);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
static int p_seq(re_ctx* c, int depth) {
|
|
519
|
+
int acc = -1;
|
|
520
|
+
for (;;) {
|
|
521
|
+
if (c->pi >= c->pn) {
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
char ch = c->pat[c->pi];
|
|
525
|
+
if (ch == '|' || ch == ')') {
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
int atom = p_atom(c, depth);
|
|
529
|
+
if (atom < 0) {
|
|
530
|
+
return -1;
|
|
531
|
+
}
|
|
532
|
+
uint16_t qmin = 1, qmax = 1;
|
|
533
|
+
int lazy = 0;
|
|
534
|
+
int q = p_quant(c, &qmin, &qmax, &lazy);
|
|
535
|
+
if (q < 0) {
|
|
536
|
+
return -1;
|
|
537
|
+
}
|
|
538
|
+
if (q > 0) {
|
|
539
|
+
int rep = ast_new(c, A_REP);
|
|
540
|
+
if (rep < 0) {
|
|
541
|
+
return -1;
|
|
542
|
+
}
|
|
543
|
+
c->ast[rep].a = (uint8_t)atom;
|
|
544
|
+
c->ast[rep].min = qmin;
|
|
545
|
+
c->ast[rep].max = qmax;
|
|
546
|
+
c->ast[rep].lazy = (uint8_t)lazy;
|
|
547
|
+
atom = rep;
|
|
548
|
+
}
|
|
549
|
+
if (acc < 0) {
|
|
550
|
+
acc = atom;
|
|
551
|
+
} else {
|
|
552
|
+
int cat = ast_new(c, A_CAT);
|
|
553
|
+
if (cat < 0) {
|
|
554
|
+
return -1;
|
|
555
|
+
}
|
|
556
|
+
c->ast[cat].a = (uint8_t)acc;
|
|
557
|
+
c->ast[cat].b = (uint8_t)atom;
|
|
558
|
+
acc = cat;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (acc < 0) {
|
|
562
|
+
acc = ast_new(c, A_EMPTY);
|
|
563
|
+
if (acc < 0) {
|
|
564
|
+
return -1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return acc;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
static int p_alt(re_ctx* c, int depth) {
|
|
571
|
+
if (depth > RE_AST_DEPTH) {
|
|
572
|
+
c->err = 1;
|
|
573
|
+
return -1;
|
|
574
|
+
}
|
|
575
|
+
int first = p_seq(c, depth);
|
|
576
|
+
if (first < 0) {
|
|
577
|
+
return -1;
|
|
578
|
+
}
|
|
579
|
+
int acc = first;
|
|
580
|
+
while (c->pi < c->pn && c->pat[c->pi] == '|') {
|
|
581
|
+
c->pi++;
|
|
582
|
+
int right = p_seq(c, depth);
|
|
583
|
+
if (right < 0) {
|
|
584
|
+
return -1;
|
|
585
|
+
}
|
|
586
|
+
int id = ast_new(c, A_ALT);
|
|
587
|
+
if (id < 0) {
|
|
588
|
+
return -1;
|
|
589
|
+
}
|
|
590
|
+
c->ast[id].a = (uint8_t)acc;
|
|
591
|
+
c->ast[id].b = (uint8_t)right;
|
|
592
|
+
acc = id;
|
|
593
|
+
}
|
|
594
|
+
return acc;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* ---- compile (AST -> NFA) --------------------------------------------- */
|
|
598
|
+
|
|
599
|
+
static int emit_inst(re_ctx* c, uint8_t op, uint8_t cls, uint16_t x, uint16_t y) {
|
|
600
|
+
if (c->inst_n >= RE_MAX_INST) {
|
|
601
|
+
c->err = 1;
|
|
602
|
+
return -1;
|
|
603
|
+
}
|
|
604
|
+
int pc = c->inst_n++;
|
|
605
|
+
c->inst[pc].op = op;
|
|
606
|
+
c->inst[pc].cls = cls;
|
|
607
|
+
c->inst[pc].x = x;
|
|
608
|
+
c->inst[pc].y = y;
|
|
609
|
+
return pc;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
static void frag_init(re_frag* f, int in) {
|
|
613
|
+
memset(f->bits, 0, sizeof(f->bits));
|
|
614
|
+
f->in = in;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
static void frag_bit(re_frag* f, int pc, int slot) {
|
|
618
|
+
int bit = pc * 2 + slot;
|
|
619
|
+
f->bits[bit >> 3] |= (uint8_t)(1u << (bit & 7));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static void frag_or(re_frag* dst, const re_frag* src) {
|
|
623
|
+
for (int i = 0; i < (int)RE_FRAG_BYTES; i++) {
|
|
624
|
+
dst->bits[i] |= src->bits[i];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Resolves (and clears) every pending arrow to `target`. */
|
|
629
|
+
static void frag_connect(re_frag* f, re_inst* inst, int target) {
|
|
630
|
+
for (int bit = 0; bit < RE_MAX_INST * 2; bit++) {
|
|
631
|
+
if (f->bits[bit >> 3] & (1u << (bit & 7))) {
|
|
632
|
+
int pc = bit >> 1;
|
|
633
|
+
if (bit & 1) {
|
|
634
|
+
inst[pc].y = (uint16_t)target;
|
|
635
|
+
} else {
|
|
636
|
+
inst[pc].x = (uint16_t)target;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
memset(f->bits, 0, sizeof(f->bits));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* Adopts src's pending arrows (dst's own are already resolved). */
|
|
644
|
+
static void frag_adopt(re_frag* dst, const re_frag* src) {
|
|
645
|
+
memcpy(dst->bits, src->bits, sizeof(dst->bits));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/* An empty fragment still needs an entry instruction to hang its
|
|
649
|
+
* pending exit on: a bare JMP. */
|
|
650
|
+
static int frag_force_entry(re_ctx* c, re_frag* f) {
|
|
651
|
+
if (f->in >= 0) {
|
|
652
|
+
return 0;
|
|
653
|
+
}
|
|
654
|
+
int j = emit_inst(c, RE_OP_JMP, 0, 0xFFFF, 0);
|
|
655
|
+
if (j < 0) {
|
|
656
|
+
return -1;
|
|
657
|
+
}
|
|
658
|
+
f->in = j;
|
|
659
|
+
frag_bit(f, j, 0);
|
|
660
|
+
return 0;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
static int c_node(re_ctx* c, int id, re_frag* out, int depth) {
|
|
664
|
+
if (depth > RE_AST_DEPTH + 8) { /* the left-folded CAT chain runs
|
|
665
|
+
* deeper than the parse tree */
|
|
666
|
+
c->err = 1;
|
|
667
|
+
return -1;
|
|
668
|
+
}
|
|
669
|
+
const re_ast* a = &c->ast[id];
|
|
670
|
+
switch (a->kind) {
|
|
671
|
+
case A_EMPTY: {
|
|
672
|
+
frag_init(out, -1);
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
case A_BYTE: {
|
|
676
|
+
/* the literal rides cls: x/y are the jump-target pair, and the
|
|
677
|
+
* pending-exit machinery writes x — it must never carry data */
|
|
678
|
+
int pc = emit_inst(c, RE_OP_BYTE, a->a, 0, 0);
|
|
679
|
+
if (pc < 0) {
|
|
680
|
+
return -1;
|
|
681
|
+
}
|
|
682
|
+
frag_init(out, pc);
|
|
683
|
+
frag_bit(out, pc, 0); /* the fall-through exit */
|
|
684
|
+
return 0;
|
|
685
|
+
}
|
|
686
|
+
case A_CLASS: {
|
|
687
|
+
int pc = emit_inst(c, RE_OP_CHAR, a->cls, 0, 0);
|
|
688
|
+
if (pc < 0) {
|
|
689
|
+
return -1;
|
|
690
|
+
}
|
|
691
|
+
frag_init(out, pc);
|
|
692
|
+
frag_bit(out, pc, 0);
|
|
693
|
+
return 0;
|
|
694
|
+
}
|
|
695
|
+
case A_ASSERT: {
|
|
696
|
+
int pc = emit_inst(c, RE_OP_ASSERT, a->a, 0, 0);
|
|
697
|
+
if (pc < 0) {
|
|
698
|
+
return -1;
|
|
699
|
+
}
|
|
700
|
+
frag_init(out, pc);
|
|
701
|
+
frag_bit(out, pc, 0);
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
704
|
+
case A_CAT: {
|
|
705
|
+
re_frag f1, f2;
|
|
706
|
+
if (c_node(c, a->a, &f1, depth + 1) < 0 || c_node(c, a->b, &f2, depth + 1) < 0) {
|
|
707
|
+
return -1;
|
|
708
|
+
}
|
|
709
|
+
if (f1.in < 0) {
|
|
710
|
+
*out = f2;
|
|
711
|
+
return 0;
|
|
712
|
+
}
|
|
713
|
+
if (f2.in >= 0) {
|
|
714
|
+
frag_connect(&f1, c->inst, f2.in); /* clears f1's arrows */
|
|
715
|
+
frag_or(&f1, &f2); /* f2's pending ride on */
|
|
716
|
+
}
|
|
717
|
+
*out = f1;
|
|
718
|
+
return 0;
|
|
719
|
+
}
|
|
720
|
+
case A_ALT: {
|
|
721
|
+
int split = emit_inst(c, RE_OP_SPLIT, 0, 0xFFFF, 0xFFFF);
|
|
722
|
+
if (split < 0) {
|
|
723
|
+
return -1;
|
|
724
|
+
}
|
|
725
|
+
re_frag f1, f2;
|
|
726
|
+
if (c_node(c, a->a, &f1, depth + 1) < 0 || c_node(c, a->b, &f2, depth + 1) < 0) {
|
|
727
|
+
return -1;
|
|
728
|
+
}
|
|
729
|
+
if (frag_force_entry(c, &f1) < 0 || frag_force_entry(c, &f2) < 0) {
|
|
730
|
+
return -1;
|
|
731
|
+
}
|
|
732
|
+
c->inst[split].x = (uint16_t)f1.in;
|
|
733
|
+
c->inst[split].y = (uint16_t)f2.in;
|
|
734
|
+
frag_init(out, split);
|
|
735
|
+
frag_or(out, &f1);
|
|
736
|
+
frag_or(out, &f2);
|
|
737
|
+
return 0;
|
|
738
|
+
}
|
|
739
|
+
case A_REP: {
|
|
740
|
+
/* the required prefix: min plain copies, left-folded */
|
|
741
|
+
re_frag acc;
|
|
742
|
+
frag_init(&acc, -1);
|
|
743
|
+
for (uint16_t i = 0; i < a->min; i++) {
|
|
744
|
+
re_frag f;
|
|
745
|
+
if (c_node(c, a->a, &f, depth + 1) < 0) {
|
|
746
|
+
return -1;
|
|
747
|
+
}
|
|
748
|
+
if (f.in < 0) {
|
|
749
|
+
continue; /* an empty child requires nothing */
|
|
750
|
+
}
|
|
751
|
+
if (acc.in < 0) {
|
|
752
|
+
acc = f;
|
|
753
|
+
} else {
|
|
754
|
+
frag_connect(&acc, c->inst, f.in); /* prior exits enter
|
|
755
|
+
* this copy */
|
|
756
|
+
frag_adopt(&acc, &f);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (a->max == RE_REP_INF) {
|
|
760
|
+
/* star over the child: SPLIT(body, EXIT); body -> SPLIT */
|
|
761
|
+
int split = emit_inst(c, RE_OP_SPLIT, 0, 0xFFFF, 0xFFFF);
|
|
762
|
+
if (split < 0) {
|
|
763
|
+
return -1;
|
|
764
|
+
}
|
|
765
|
+
re_frag f;
|
|
766
|
+
if (c_node(c, a->a, &f, depth + 1) < 0) {
|
|
767
|
+
return -1;
|
|
768
|
+
}
|
|
769
|
+
if (f.in >= 0) {
|
|
770
|
+
c->inst[split].x = (uint16_t)f.in;
|
|
771
|
+
frag_connect(&f, c->inst, split); /* the body loops back */
|
|
772
|
+
} else {
|
|
773
|
+
c->inst[split].x = (uint16_t)split; /* empty body: exit only */
|
|
774
|
+
}
|
|
775
|
+
if (acc.in >= 0) {
|
|
776
|
+
frag_connect(&acc, c->inst, split); /* the prefix enters */
|
|
777
|
+
}
|
|
778
|
+
*out = acc; /* every arrow resolved: bits are clear */
|
|
779
|
+
if (out->in < 0) {
|
|
780
|
+
out->in = split;
|
|
781
|
+
}
|
|
782
|
+
frag_bit(out, split, 1); /* the star's one exit */
|
|
783
|
+
return 0;
|
|
784
|
+
}
|
|
785
|
+
/* the bounded tail: (max - min) optional copies, each
|
|
786
|
+
* SPLIT(body_i, EXIT) — body exits chain into the next copy's
|
|
787
|
+
* split, every EXIT rides pending toward the final target */
|
|
788
|
+
re_frag chain, exits;
|
|
789
|
+
chain = acc; /* the prefix's exits fall into the first split */
|
|
790
|
+
frag_init(&exits, -1);
|
|
791
|
+
uint16_t opt = (uint16_t)(a->max - a->min);
|
|
792
|
+
for (uint16_t i = 0; i < opt; i++) {
|
|
793
|
+
int split = emit_inst(c, RE_OP_SPLIT, 0, 0xFFFF, 0xFFFF);
|
|
794
|
+
if (split < 0) {
|
|
795
|
+
return -1;
|
|
796
|
+
}
|
|
797
|
+
frag_connect(&chain, c->inst, split); /* entries -> this copy */
|
|
798
|
+
re_frag f;
|
|
799
|
+
if (c_node(c, a->a, &f, depth + 1) < 0) {
|
|
800
|
+
return -1;
|
|
801
|
+
}
|
|
802
|
+
if (f.in >= 0) {
|
|
803
|
+
c->inst[split].x = (uint16_t)f.in;
|
|
804
|
+
} else {
|
|
805
|
+
frag_bit(&chain, split, 0); /* empty copy: both arms exit */
|
|
806
|
+
}
|
|
807
|
+
frag_or(&chain, &f); /* the copy's exits join the chain */
|
|
808
|
+
frag_bit(&exits, split, 1); /* the skip arm */
|
|
809
|
+
if (acc.in < 0) {
|
|
810
|
+
acc.in = split; /* entry = the first split */
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
out->in = acc.in;
|
|
814
|
+
frag_init(out, out->in);
|
|
815
|
+
frag_or(out, &chain); /* the last body's exits reach the end */
|
|
816
|
+
frag_or(out, &exits);
|
|
817
|
+
return 0;
|
|
818
|
+
}
|
|
819
|
+
default:
|
|
820
|
+
c->err = 1;
|
|
821
|
+
return -1;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* ---- compile: pattern -> program --------------------------------------- */
|
|
826
|
+
|
|
827
|
+
/* parse + compile into the caller's ctx: MATCH emits last, every
|
|
828
|
+
* pending arrow resolves. 0 ok (the fragment entry pc in *entry_out),
|
|
829
|
+
* -1 SyntaxError (or a budget trip — the caller treats both as
|
|
830
|
+
* "pattern outside the subset", never a guess). */
|
|
831
|
+
static int re_build(re_ctx* c, const char* pat, uint32_t pat_len, int* entry_out) {
|
|
832
|
+
memset(c, 0, sizeof(*c));
|
|
833
|
+
c->pat = pat;
|
|
834
|
+
c->pn = pat_len;
|
|
835
|
+
int root = p_alt(c, 0);
|
|
836
|
+
if (root < 0 || c->pi != c->pn) {
|
|
837
|
+
return -1; /* trailing garbage is a SyntaxError */
|
|
838
|
+
}
|
|
839
|
+
re_frag f;
|
|
840
|
+
if (c_node(c, root, &f, 0) < 0) {
|
|
841
|
+
return -1;
|
|
842
|
+
}
|
|
843
|
+
if (emit_inst(c, RE_OP_MATCH, 0, 0, 0) < 0) {
|
|
844
|
+
return -1;
|
|
845
|
+
}
|
|
846
|
+
frag_connect(&f, c->inst, c->inst_n - 1); /* MATCH emits last */
|
|
847
|
+
if (c->err) {
|
|
848
|
+
return -1;
|
|
849
|
+
}
|
|
850
|
+
*entry_out = f.in;
|
|
851
|
+
return 0;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/* ---- the Pike VM (shared by the one-shot and the retained programs) ----- */
|
|
855
|
+
|
|
856
|
+
/* Runs one compiled program over the subject: 1 match, 0 no match.
|
|
857
|
+
* `entry` is the fragment entry pc (MATCH is emitted last, so the graph
|
|
858
|
+
* is self-contained). Never fails: a program exists only when its
|
|
859
|
+
* pattern compiled. */
|
|
860
|
+
static int re_vm(const re_inst* inst, const uint8_t (*cls)[32], int entry, const char* s,
|
|
861
|
+
uint32_t s_len) {
|
|
862
|
+
uint8_t mark[RE_MAX_INST];
|
|
863
|
+
uint16_t cur[RE_MAX_INST], nxt[RE_MAX_INST];
|
|
864
|
+
/* closure depth: the seeds (m+1: live threads + the unanchored
|
|
865
|
+
* restart) plus one net push per newly marked instruction (m) —
|
|
866
|
+
* 2m+2, not 2m (a 320-instruction program once had one 2-byte
|
|
867
|
+
* overflow slot) */
|
|
868
|
+
uint16_t stack[RE_MAX_INST * 2 + 2];
|
|
869
|
+
uint32_t gen = 1;
|
|
870
|
+
memset(mark, 0, sizeof(mark));
|
|
871
|
+
|
|
872
|
+
if (entry < 0) {
|
|
873
|
+
return 1; /* an empty pattern matches everywhere */
|
|
874
|
+
}
|
|
875
|
+
/* initial closure at subject offset 0 (from the fragment entry;
|
|
876
|
+
* MATCH is emitted LAST, so pc 0 is a pattern instruction) */
|
|
877
|
+
int cur_n = 0;
|
|
878
|
+
{
|
|
879
|
+
int st_n = 0;
|
|
880
|
+
stack[st_n++] = (uint16_t)entry;
|
|
881
|
+
while (st_n > 0) {
|
|
882
|
+
uint16_t pc = stack[--st_n];
|
|
883
|
+
if (mark[pc] == (uint8_t)gen) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
mark[pc] = (uint8_t)gen;
|
|
887
|
+
uint8_t op = inst[pc].op;
|
|
888
|
+
if (op == RE_OP_JMP) {
|
|
889
|
+
stack[st_n++] = inst[pc].x;
|
|
890
|
+
} else if (op == RE_OP_SPLIT) {
|
|
891
|
+
stack[st_n++] = inst[pc].x;
|
|
892
|
+
stack[st_n++] = inst[pc].y;
|
|
893
|
+
} else if (op == RE_OP_MATCH) {
|
|
894
|
+
return 1; /* the empty match at offset 0 */
|
|
895
|
+
} else if (op == RE_OP_ASSERT) {
|
|
896
|
+
/* offset 0: BOL always holds; EOL only on the empty
|
|
897
|
+
* subject; a word boundary holds exactly when the
|
|
898
|
+
* subject starts inside a word (no previous char) */
|
|
899
|
+
int ok = 0;
|
|
900
|
+
switch (inst[pc].cls) {
|
|
901
|
+
case RE_AS_BOL:
|
|
902
|
+
ok = 1;
|
|
903
|
+
break;
|
|
904
|
+
case RE_AS_EOL:
|
|
905
|
+
ok = (int)s_len == 0;
|
|
906
|
+
break;
|
|
907
|
+
default: {
|
|
908
|
+
int wn = s_len > 0 && re_word((unsigned char)s[0]);
|
|
909
|
+
ok = inst[pc].cls == RE_AS_WBOUND ? (int)wn : !(int)wn;
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (ok) {
|
|
914
|
+
stack[st_n++] = inst[pc].x; /* the connected exit */
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
cur[cur_n++] = pc; /* CHAR/BYTE: a live thread */
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
for (uint32_t sp = 0; sp < s_len; sp++) {
|
|
922
|
+
gen++;
|
|
923
|
+
if ((gen & 0xFF) == 0) { /* the mark generation wrapped */
|
|
924
|
+
memset(mark, 0, sizeof(mark));
|
|
925
|
+
gen++;
|
|
926
|
+
}
|
|
927
|
+
uint8_t byte = (unsigned char)s[sp];
|
|
928
|
+
int nxt_n = 0;
|
|
929
|
+
for (int i = 0; i < cur_n; i++) {
|
|
930
|
+
uint16_t pc = cur[i];
|
|
931
|
+
int hit = inst[pc].op == RE_OP_BYTE
|
|
932
|
+
? (inst[pc].cls == byte)
|
|
933
|
+
: (cls[inst[pc].cls][(byte >> 3) & 31] & (1u << (byte & 7))) != 0;
|
|
934
|
+
if (hit) {
|
|
935
|
+
/* the exit is the CONNECTED target (frag_connect), not
|
|
936
|
+
* a fall-through: fragments link arbitrary pcs */
|
|
937
|
+
nxt[nxt_n++] = inst[pc].x;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
nxt[nxt_n++] = (uint16_t)entry; /* unanchored: the search restarts
|
|
941
|
+
* at every offset */
|
|
942
|
+
gen++;
|
|
943
|
+
if ((gen & 0xFF) == 0) {
|
|
944
|
+
memset(mark, 0, sizeof(mark));
|
|
945
|
+
gen++;
|
|
946
|
+
}
|
|
947
|
+
/* closure at subject offset sp + 1 */
|
|
948
|
+
int st_n = 0, live_n = 0, matched = 0;
|
|
949
|
+
for (int i = 0; i < nxt_n; i++) {
|
|
950
|
+
stack[st_n++] = nxt[i];
|
|
951
|
+
}
|
|
952
|
+
while (st_n > 0) {
|
|
953
|
+
uint16_t pc = stack[--st_n];
|
|
954
|
+
if (mark[pc] == (uint8_t)gen) {
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
mark[pc] = (uint8_t)gen;
|
|
958
|
+
uint8_t op = inst[pc].op;
|
|
959
|
+
if (op == RE_OP_JMP) {
|
|
960
|
+
stack[st_n++] = inst[pc].x;
|
|
961
|
+
} else if (op == RE_OP_SPLIT) {
|
|
962
|
+
stack[st_n++] = inst[pc].x;
|
|
963
|
+
stack[st_n++] = inst[pc].y;
|
|
964
|
+
} else if (op == RE_OP_MATCH) {
|
|
965
|
+
matched = 1;
|
|
966
|
+
} else if (op == RE_OP_ASSERT) {
|
|
967
|
+
int ok = 0;
|
|
968
|
+
switch (inst[pc].cls) {
|
|
969
|
+
case RE_AS_BOL:
|
|
970
|
+
ok = (sp + 1) == 0;
|
|
971
|
+
break;
|
|
972
|
+
case RE_AS_EOL:
|
|
973
|
+
ok = (int)(sp + 1) == (int)s_len;
|
|
974
|
+
break;
|
|
975
|
+
default: {
|
|
976
|
+
int wp = re_word((unsigned char)s[sp]);
|
|
977
|
+
int wn = (sp + 1) < (uint32_t)s_len && re_word((unsigned char)s[sp + 1]);
|
|
978
|
+
ok = inst[pc].cls == RE_AS_WBOUND ? (wp != wn) : (wp == wn);
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (ok) {
|
|
983
|
+
stack[st_n++] = inst[pc].x; /* the connected exit */
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
nxt[live_n++] = pc; /* a live thread for the next byte */
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (matched) {
|
|
990
|
+
return 1;
|
|
991
|
+
}
|
|
992
|
+
memcpy(cur, nxt, (size_t)live_n * sizeof(uint16_t));
|
|
993
|
+
cur_n = live_n;
|
|
994
|
+
}
|
|
995
|
+
return 0;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/* ---- the one-shot search (compile fresh, run, drop) --------------------- */
|
|
999
|
+
|
|
1000
|
+
int yep_re_search(const char* pat, uint32_t pat_len, const char* s, uint32_t s_len) {
|
|
1001
|
+
if (pat == NULL || pat_len == 0 || pat_len > RE_MAX_PAT) {
|
|
1002
|
+
return -1;
|
|
1003
|
+
}
|
|
1004
|
+
re_ctx c;
|
|
1005
|
+
int entry = -1;
|
|
1006
|
+
if (re_build(&c, pat, pat_len, &entry) != 0) {
|
|
1007
|
+
return -1;
|
|
1008
|
+
}
|
|
1009
|
+
return re_vm(c.inst, c.cls, entry, s, s_len);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/* ---- the retained programs (the compiled schema's pattern cache) -------- */
|
|
1013
|
+
|
|
1014
|
+
/* A compiled program: exact-size copies of the instruction stream and
|
|
1015
|
+
* the class table (the ctx's bounded budgets, retained past the
|
|
1016
|
+
* compile call). */
|
|
1017
|
+
typedef struct {
|
|
1018
|
+
re_inst* inst;
|
|
1019
|
+
uint8_t (*cls)[32];
|
|
1020
|
+
int inst_n, cls_n;
|
|
1021
|
+
int entry;
|
|
1022
|
+
} re_prog;
|
|
1023
|
+
|
|
1024
|
+
static void re_prog_free(re_prog* p) {
|
|
1025
|
+
const yep_allocator* sys = yep_system_allocator();
|
|
1026
|
+
yep_free(sys, p->inst);
|
|
1027
|
+
yep_free(sys, p->cls);
|
|
1028
|
+
p->inst = NULL;
|
|
1029
|
+
p->cls = NULL;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/* Compiles into a heap program. 0 ok; -1 SyntaxError or allocation
|
|
1033
|
+
* failure (nothing retained; both read as "the pattern is unusable",
|
|
1034
|
+
* the kernel's ignore posture). */
|
|
1035
|
+
static int re_compile(const char* pat, uint32_t pat_len, re_prog* out) {
|
|
1036
|
+
if (pat == NULL || pat_len == 0 || pat_len > RE_MAX_PAT) {
|
|
1037
|
+
return -1;
|
|
1038
|
+
}
|
|
1039
|
+
re_ctx c;
|
|
1040
|
+
int entry = -1;
|
|
1041
|
+
if (re_build(&c, pat, pat_len, &entry) != 0) {
|
|
1042
|
+
return -1;
|
|
1043
|
+
}
|
|
1044
|
+
const yep_allocator* sys = yep_system_allocator();
|
|
1045
|
+
re_prog p;
|
|
1046
|
+
memset(&p, 0, sizeof(p));
|
|
1047
|
+
p.inst_n = c.inst_n;
|
|
1048
|
+
p.cls_n = c.cls_n;
|
|
1049
|
+
p.entry = entry;
|
|
1050
|
+
p.inst = yep_alloc(sys, (size_t)c.inst_n * sizeof(re_inst));
|
|
1051
|
+
p.cls = yep_alloc(sys, (size_t)(c.cls_n > 0 ? c.cls_n : 1) * 32);
|
|
1052
|
+
if (p.inst == NULL || p.cls == NULL) {
|
|
1053
|
+
re_prog_free(&p);
|
|
1054
|
+
return -1;
|
|
1055
|
+
}
|
|
1056
|
+
memcpy(p.inst, c.inst, (size_t)c.inst_n * sizeof(re_inst));
|
|
1057
|
+
memcpy(p.cls, c.cls, (size_t)c.cls_n * 32);
|
|
1058
|
+
*out = p;
|
|
1059
|
+
return 0;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/* The table: pattern source -> compiled program. Keys are the schema's
|
|
1063
|
+
* own byte views (stable for the handle's lifetime); lookups compare
|
|
1064
|
+
* content so equal patterns share one program. Bounded: over
|
|
1065
|
+
* RE_CACHE_MAX entries a miss recompiles — the one-shot behavior. */
|
|
1066
|
+
#define RE_CACHE_MAX 256
|
|
1067
|
+
typedef struct {
|
|
1068
|
+
const char* pat;
|
|
1069
|
+
uint32_t plen;
|
|
1070
|
+
re_prog prog;
|
|
1071
|
+
} re_cache_slot;
|
|
1072
|
+
|
|
1073
|
+
struct yep_re_cache {
|
|
1074
|
+
re_cache_slot slot[RE_CACHE_MAX];
|
|
1075
|
+
int n;
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
yep_re_cache* yep_re_cache_new(void) {
|
|
1079
|
+
yep_re_cache* t = yep_alloc(yep_system_allocator(), sizeof(yep_re_cache));
|
|
1080
|
+
if (t != NULL) {
|
|
1081
|
+
memset(t, 0, sizeof(*t)); /* the allocator does not zero: n starts empty */
|
|
1082
|
+
}
|
|
1083
|
+
return t;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
void yep_re_cache_free(yep_re_cache* t) {
|
|
1087
|
+
if (t == NULL) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
for (int i = 0; i < t->n; i++) {
|
|
1091
|
+
re_prog_free(&t->slot[i].prog);
|
|
1092
|
+
}
|
|
1093
|
+
yep_free(yep_system_allocator(), t);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
int yep_re_cache_add(yep_re_cache* t, const char* pat, uint32_t plen) {
|
|
1097
|
+
if (t == NULL || pat == NULL || plen == 0) {
|
|
1098
|
+
return 0; /* nothing to retain */
|
|
1099
|
+
}
|
|
1100
|
+
for (int i = 0; i < t->n; i++) {
|
|
1101
|
+
if (t->slot[i].plen == plen && memcmp(t->slot[i].pat, pat, plen) == 0) {
|
|
1102
|
+
return 1; /* already retained (equal patterns share one program) */
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (t->n >= RE_CACHE_MAX) {
|
|
1106
|
+
return 0; /* the table is full: misses recompile per use, as the
|
|
1107
|
+
* one-shot face always has */
|
|
1108
|
+
}
|
|
1109
|
+
re_prog p;
|
|
1110
|
+
if (re_compile(pat, plen, &p) != 0) {
|
|
1111
|
+
return 0; /* invalid pattern: not retained; every use reads -1,
|
|
1112
|
+
* exactly the uncached verdict */
|
|
1113
|
+
}
|
|
1114
|
+
t->slot[t->n].pat = pat;
|
|
1115
|
+
t->slot[t->n].plen = plen;
|
|
1116
|
+
t->slot[t->n].prog = p;
|
|
1117
|
+
t->n++;
|
|
1118
|
+
return 1;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
int yep_re_cache_search(const yep_re_cache* t, const char* pat, uint32_t plen, const char* s,
|
|
1122
|
+
uint32_t s_len) {
|
|
1123
|
+
if (t != NULL && pat != NULL) {
|
|
1124
|
+
for (int i = 0; i < t->n; i++) {
|
|
1125
|
+
if (t->slot[i].plen == plen && memcmp(t->slot[i].pat, pat, plen) == 0) {
|
|
1126
|
+
return re_vm(t->slot[i].prog.inst, t->slot[i].prog.cls, t->slot[i].prog.entry, s,
|
|
1127
|
+
s_len);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
/* a miss (or no cache: the one-shot face) compiles fresh — identical
|
|
1132
|
+
* verdicts either way, and the handle stays untouched */
|
|
1133
|
+
return yep_re_search(pat, plen, s, s_len);
|
|
1134
|
+
}
|