visaljs 0.0.8 → 0.0.10
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.
- package/README.md +6 -3
- package/THIRD_PARTY_LICENSES +1 -1
- package/cli.js +2782 -770
- package/node/include/js_node.h +1 -0
- package/node/prelude/_http_agent.js +5 -0
- package/node/prelude/_http_server.js +7 -0
- package/node/prelude/dns.js +7 -1
- package/node/prelude/events.js +7 -3
- package/node/prelude/http.js +693 -285
- package/node/prelude/https.js +14 -3
- package/node/prelude/internal/events_extras.js +2 -5
- package/node/prelude/internal/features.js +17 -0
- package/node/prelude/internal/http_agent.js +88 -2
- package/node/prelude/internal/http_chars.ts +22 -0
- package/node/prelude/internal/http_symbols.js +3 -0
- package/node/prelude/internal/net_bound.js +123 -0
- package/node/prelude/internal/net_onread.js +73 -0
- package/node/prelude/internal/net_tos.js +54 -0
- package/node/prelude/internal/perf_observe.js +7 -1
- package/node/prelude/internal/tls/wrap.js +2 -1
- package/node/prelude/module.js +42 -18
- package/node/prelude/net.js +212 -61
- package/node/prelude/perf_hooks.js +4 -0
- package/node/src/host_http.c +151 -21
- package/node/src/host_net.c +75 -9
- package/node/src/host_sys.c +9 -0
- package/package.json +1 -1
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_atoms.h +4 -0
- package/runtime/include/js_buffer.h +185 -0
- package/runtime/include/js_builtins.h +53 -0
- package/runtime/include/js_exception.h +3 -0
- package/runtime/include/js_function.h +1 -1
- package/runtime/include/js_object.h +21 -3
- package/runtime/include/js_ops.h +27 -0
- package/runtime/include/js_runtime.h +1 -0
- package/runtime/include/js_web.h +3 -3
- package/runtime/include/jscontext.h +15 -0
- package/runtime/src/atoms.c +37 -3
- package/runtime/src/builtins.c +17 -4
- package/runtime/src/events.c +19 -0
- package/runtime/src/function.c +37 -10
- package/runtime/src/gc.c +7 -5
- package/runtime/src/internal.h +4 -4
- package/runtime/src/iterator.c +5 -1
- package/runtime/src/object.c +42 -6
- package/runtime/src/regexp.c +569 -12
- package/runtime/src/shape.c +68 -1
- package/runtime/src/typedarray.c +23 -0
- package/runtime/src/weak.c +95 -24
package/runtime/src/regexp.c
CHANGED
|
@@ -36,14 +36,19 @@ typedef struct RegExpObject {
|
|
|
36
36
|
bool one_class; /* the pattern is one character class: `units` (see find_class) */
|
|
37
37
|
bool negated; /* ...that matches every unit from U+0100 up */
|
|
38
38
|
uint32_t units[8]; /* its units below U+0100, a bit each */
|
|
39
|
+
bool analyzed; /* `prefilter` is set (see analyze) */
|
|
40
|
+
struct Prefilter *prefilter; /* where a match can start, or NULL: malloc'd */
|
|
39
41
|
} RegExpObject;
|
|
40
42
|
|
|
43
|
+
static void free_prefilter(RegExpObject *re);
|
|
44
|
+
|
|
41
45
|
static void regexp_visit(JSCell *c, JSTracer *trc) { js_trace_value(trc, ((RegExpObject *)c)->source); }
|
|
42
46
|
|
|
43
47
|
static void regexp_finalize(JSRuntime *rt, JSCell *c) {
|
|
44
48
|
(void)rt;
|
|
45
49
|
RegExpObject *re = (RegExpObject *)c;
|
|
46
50
|
if (re->owned) free((void *)re->bytecode);
|
|
51
|
+
free_prefilter(re);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
static const JSClass regexp_class = { "RegExp", sizeof(RegExpObject), NULL, NULL, NULL, NULL, regexp_visit, regexp_finalize, NULL };
|
|
@@ -236,6 +241,8 @@ static JSValue regexp_new(JSContext *ctx, JSValue proto) {
|
|
|
236
241
|
re->bytecode = NULL;
|
|
237
242
|
re->owned = false;
|
|
238
243
|
re->one_class = false;
|
|
244
|
+
re->analyzed = false;
|
|
245
|
+
re->prefilter = NULL;
|
|
239
246
|
js_define_data(ctx, &re->obj, JS_ATOM(lastIndex), JS_UNDEFINED, JS_PROP_WRITABLE);
|
|
240
247
|
return js_object_value(&re->obj);
|
|
241
248
|
}
|
|
@@ -371,8 +378,269 @@ static int scan_class(const RegExpObject *re, const uint8_t *buf, int shift, uin
|
|
|
371
378
|
return 0;
|
|
372
379
|
}
|
|
373
380
|
|
|
381
|
+
/* ---- Where a match can start ----------------------------------------------------------------
|
|
382
|
+
*
|
|
383
|
+
* A pattern that isn't anchored or sticky compiles to a loop that tries every position in turn
|
|
384
|
+
* (split_goto_first +6; any; goto -11; save_start 0; ...): each failed attempt costs a trip
|
|
385
|
+
* through the interpreter. When the first units of a match can only be a few (a literal,
|
|
386
|
+
* /foo|bar/, /[cgt]gggtaaa/i, /\s+/), analyze reads them off the bytecode: the units each of
|
|
387
|
+
* the first PREFIX positions can hold, over every alternative. The search then looks for a
|
|
388
|
+
* position where they all fit (memchr for one first unit) and runs a copy of the bytecode whose
|
|
389
|
+
* loop is a goto there: one attempt each. The sets may hold more than can match, never less. */
|
|
390
|
+
|
|
391
|
+
#define PREFIX 4
|
|
392
|
+
|
|
393
|
+
typedef struct {
|
|
394
|
+
uint32_t units[8]; /* below U+0100, a bit each */
|
|
395
|
+
bool high; /* and any unit from U+0100 up */
|
|
396
|
+
} UnitSet;
|
|
397
|
+
|
|
398
|
+
typedef struct Prefilter {
|
|
399
|
+
int depth; /* the positions known: 1..PREFIX */
|
|
400
|
+
int single; /* at[0]'s one unit below U+0100, without high, else -1 */
|
|
401
|
+
UnitSet at[PREFIX];
|
|
402
|
+
uint8_t *once; /* the bytecode without its loop: an attempt at the start index only */
|
|
403
|
+
} Prefilter;
|
|
404
|
+
|
|
405
|
+
/* lre_canonicalize of the units below U+0100, without and with u/v: char_i and range_i compare
|
|
406
|
+
* the canonical unit. */
|
|
407
|
+
static uint32_t canonical[2][256];
|
|
408
|
+
|
|
409
|
+
static void free_prefilter(RegExpObject *re) {
|
|
410
|
+
if (re->prefilter) free(re->prefilter->once);
|
|
411
|
+
free(re->prefilter);
|
|
412
|
+
re->prefilter = NULL;
|
|
413
|
+
re->analyzed = false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* The bytecode's size: header, code, then the group names (NUL-terminated, each with a trailer). */
|
|
417
|
+
static size_t bytecode_size(const uint8_t *bc) {
|
|
418
|
+
size_t size = 8 + (size_t)(bc[4] | bc[5] << 8 | bc[6] << 16 | (uint32_t)bc[7] << 24);
|
|
419
|
+
const char *names = lre_get_groupnames(bc);
|
|
420
|
+
if (names) {
|
|
421
|
+
for (int i = 1; i < lre_get_capture_count(bc); i++) names += strlen(names) + LRE_GROUP_NAME_TRAILER_LEN;
|
|
422
|
+
size = (size_t)((const uint8_t *)names - bc);
|
|
423
|
+
}
|
|
424
|
+
return size;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static void add_unit(UnitSet *u, uint32_t c) {
|
|
428
|
+
if (c < 0x100) u->units[c >> 5] |= 1u << (c & 31);
|
|
429
|
+
else u->high = true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
static bool has_unit_in(const UnitSet *u, uint32_t c) {
|
|
433
|
+
return c < 0x100 ? (u->units[c >> 5] >> (c & 31)) & 1 : u->high;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
static uint32_t get_u16_le(const uint8_t *p) { return p[0] | (uint32_t)p[1] << 8; }
|
|
437
|
+
static uint32_t get_u32_le(const uint8_t *p) { return p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; }
|
|
438
|
+
|
|
439
|
+
typedef struct {
|
|
440
|
+
const uint8_t *code;
|
|
441
|
+
uint32_t length;
|
|
442
|
+
bool unicode;
|
|
443
|
+
int limit; /* every path consumes at least this many units first (PREFIX at most) */
|
|
444
|
+
int steps;
|
|
445
|
+
UnitSet at[PREFIX];
|
|
446
|
+
} Walk;
|
|
447
|
+
|
|
448
|
+
/* A class op (range, range32 and their _i forms) at p: the units below U+0100 it takes. */
|
|
449
|
+
static void add_ranges(Walk *w, UnitSet *u, const uint8_t *p, bool wide, bool fold) {
|
|
450
|
+
uint32_t n = get_u16_le(p + 1), size = wide ? 8 : 4;
|
|
451
|
+
for (uint32_t c = 0; c < 0x100; c++) {
|
|
452
|
+
uint32_t k = fold ? canonical[w->unicode][c] : c;
|
|
453
|
+
for (uint32_t i = 0; i < n; i++) {
|
|
454
|
+
const uint8_t *r = p + 3 + i * size;
|
|
455
|
+
uint32_t lo = wide ? get_u32_le(r) : get_u16_le(r), hi = wide ? get_u32_le(r + 4) : get_u16_le(r + 2);
|
|
456
|
+
if (k >= lo && k <= hi) {
|
|
457
|
+
add_unit(u, c);
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const uint8_t *last = p + 3 + (n - 1) * size;
|
|
463
|
+
if (fold || wide || get_u16_le(last + 2) >= 0x100) u->high = true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* The units the paths from pc can take at positions d.. (until w->limit). */
|
|
467
|
+
static void walk(Walk *w, uint32_t pc, int d) {
|
|
468
|
+
while (d < w->limit) {
|
|
469
|
+
if (++w->steps > 1000 || pc >= w->length) {
|
|
470
|
+
w->limit = 0;
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const uint8_t *p = w->code + pc;
|
|
474
|
+
UnitSet *u = &w->at[d];
|
|
475
|
+
switch (p[0]) {
|
|
476
|
+
case JS_REOP_char:
|
|
477
|
+
case JS_REOP_char32: {
|
|
478
|
+
uint32_t c = p[0] == JS_REOP_char ? get_u16_le(p + 1) : get_u32_le(p + 1);
|
|
479
|
+
add_unit(u, c); /* from U+10000 up: a high surrogate first */
|
|
480
|
+
pc += p[0] == JS_REOP_char ? 3 : 5;
|
|
481
|
+
d++;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case JS_REOP_char_i:
|
|
485
|
+
case JS_REOP_char32_i: {
|
|
486
|
+
uint32_t c = p[0] == JS_REOP_char_i ? get_u16_le(p + 1) : get_u32_le(p + 1);
|
|
487
|
+
for (uint32_t k = 0; k < 0x100; k++)
|
|
488
|
+
if (canonical[w->unicode][k] == c) add_unit(u, k);
|
|
489
|
+
u->high = true; /* units from U+0100 up may fold to it too */
|
|
490
|
+
pc += p[0] == JS_REOP_char_i ? 3 : 5;
|
|
491
|
+
d++;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
case JS_REOP_range:
|
|
495
|
+
case JS_REOP_range_i:
|
|
496
|
+
add_ranges(w, u, p, false, p[0] == JS_REOP_range_i);
|
|
497
|
+
pc += 3 + get_u16_le(p + 1) * 4;
|
|
498
|
+
d++;
|
|
499
|
+
break;
|
|
500
|
+
case JS_REOP_range32:
|
|
501
|
+
case JS_REOP_range32_i:
|
|
502
|
+
add_ranges(w, u, p, true, p[0] == JS_REOP_range32_i);
|
|
503
|
+
pc += 3 + get_u16_le(p + 1) * 8;
|
|
504
|
+
d++;
|
|
505
|
+
break;
|
|
506
|
+
case JS_REOP_space:
|
|
507
|
+
case JS_REOP_not_space:
|
|
508
|
+
for (uint32_t k = 0; k < 0x100; k++)
|
|
509
|
+
if (lre_is_space(k) == (p[0] == JS_REOP_space)) add_unit(u, k);
|
|
510
|
+
u->high = true;
|
|
511
|
+
pc++;
|
|
512
|
+
d++;
|
|
513
|
+
break;
|
|
514
|
+
case JS_REOP_dot:
|
|
515
|
+
for (uint32_t k = 0; k < 0x100; k++)
|
|
516
|
+
if (k != '\n' && k != '\r') add_unit(u, k);
|
|
517
|
+
u->high = true;
|
|
518
|
+
pc++;
|
|
519
|
+
d++;
|
|
520
|
+
break;
|
|
521
|
+
case JS_REOP_goto:
|
|
522
|
+
pc += 5 + get_u32_le(p + 1);
|
|
523
|
+
break;
|
|
524
|
+
case JS_REOP_split_goto_first:
|
|
525
|
+
case JS_REOP_split_next_first:
|
|
526
|
+
walk(w, pc + 5, d);
|
|
527
|
+
walk(w, pc + 5 + get_u32_le(p + 1), d);
|
|
528
|
+
return;
|
|
529
|
+
case JS_REOP_save_start:
|
|
530
|
+
case JS_REOP_save_end:
|
|
531
|
+
pc += 2;
|
|
532
|
+
break;
|
|
533
|
+
case JS_REOP_save_reset:
|
|
534
|
+
pc += 3;
|
|
535
|
+
break;
|
|
536
|
+
case JS_REOP_line_start:
|
|
537
|
+
case JS_REOP_line_start_m:
|
|
538
|
+
case JS_REOP_line_end:
|
|
539
|
+
case JS_REOP_line_end_m:
|
|
540
|
+
case JS_REOP_word_boundary:
|
|
541
|
+
case JS_REOP_word_boundary_i:
|
|
542
|
+
case JS_REOP_not_word_boundary:
|
|
543
|
+
case JS_REOP_not_word_boundary_i:
|
|
544
|
+
pc++; /* zero-width: the next op's unit is at the same position */
|
|
545
|
+
break;
|
|
546
|
+
default: /* any, match, loops, lookarounds, back references: anything from here on */
|
|
547
|
+
w->limit = d;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* The share of printable ASCII the sets let through, roughly: a filter that lets most
|
|
554
|
+
* positions through only adds a call per position. */
|
|
555
|
+
static double selectivity(const Prefilter *f) {
|
|
556
|
+
double share = 1;
|
|
557
|
+
for (int k = 0; k < f->depth; k++) {
|
|
558
|
+
int n = 0;
|
|
559
|
+
for (uint32_t c = 0x20; c < 0x7F; c++) n += has_unit_in(&f->at[k], c);
|
|
560
|
+
share *= n / 95.0;
|
|
561
|
+
}
|
|
562
|
+
return share;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* The prefilter of re's bytecode, made at its first long search; NULL when it has none. */
|
|
566
|
+
static Prefilter *analyze(RegExpObject *re) {
|
|
567
|
+
re->analyzed = true;
|
|
568
|
+
const uint8_t *bc = re->bytecode;
|
|
569
|
+
uint32_t code_length = (uint32_t)(bytecode_size(bc) - JS_RE_HEADER_LEN);
|
|
570
|
+
const uint8_t *p = bc + JS_RE_HEADER_LEN;
|
|
571
|
+
/* the search loop, as js_regexp_anchor reads it (an anchored pattern has a goto instead) */
|
|
572
|
+
if (code_length < 13 || p[0] != JS_REOP_split_goto_first || p[5] != JS_REOP_any || p[6] != JS_REOP_goto || p[11] != JS_REOP_save_start)
|
|
573
|
+
return NULL;
|
|
574
|
+
Walk w = { .code = p, .length = code_length, .unicode = (lre_get_flags(bc) & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0,
|
|
575
|
+
.limit = PREFIX };
|
|
576
|
+
walk(&w, 11, 0);
|
|
577
|
+
if (w.limit == 0) return NULL;
|
|
578
|
+
Prefilter *f = calloc(1, sizeof *f);
|
|
579
|
+
if (!f) js_fatal("out of memory analyzing a regular expression");
|
|
580
|
+
f->depth = w.limit;
|
|
581
|
+
memcpy(f->at, w.at, sizeof f->at);
|
|
582
|
+
if (selectivity(f) > 0.2) {
|
|
583
|
+
free(f);
|
|
584
|
+
return NULL;
|
|
585
|
+
}
|
|
586
|
+
f->single = -1;
|
|
587
|
+
if (!f->at[0].high) {
|
|
588
|
+
int n = 0;
|
|
589
|
+
for (int c = 0; c < 0x100; c++)
|
|
590
|
+
if (has_unit_in(&f->at[0], (uint32_t)c)) f->single = n++ ? -1 : c;
|
|
591
|
+
if (n != 1) f->single = -1;
|
|
592
|
+
}
|
|
593
|
+
size_t size = bytecode_size(bc);
|
|
594
|
+
f->once = malloc(size);
|
|
595
|
+
if (!f->once) js_fatal("out of memory analyzing a regular expression");
|
|
596
|
+
memcpy(f->once, bc, size);
|
|
597
|
+
f->once[JS_RE_HEADER_LEN] = JS_REOP_goto; /* same offset: straight to save_start */
|
|
598
|
+
return f;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* The first position from `from` where the prefilter's units all fit, or UINT32_MAX. */
|
|
602
|
+
static uint32_t next_start(const Prefilter *f, const uint8_t *buf, int shift, uint32_t from, uint32_t length) {
|
|
603
|
+
if (length < (uint32_t)f->depth) return UINT32_MAX;
|
|
604
|
+
uint32_t last = length - (uint32_t)f->depth; /* a match starts no later */
|
|
605
|
+
for (uint32_t i = from; i <= last; i++) {
|
|
606
|
+
if (!shift && f->single >= 0) {
|
|
607
|
+
const uint8_t *hit = memchr(buf + i, f->single, last - i + 1);
|
|
608
|
+
if (!hit) return UINT32_MAX;
|
|
609
|
+
i = (uint32_t)(hit - buf);
|
|
610
|
+
}
|
|
611
|
+
int k = 0;
|
|
612
|
+
for (; k < f->depth; k++) {
|
|
613
|
+
uint32_t c = shift ? ((const uint16_t *)buf)[i + k] : buf[i + k];
|
|
614
|
+
if (!has_unit_in(&f->at[k], c)) break;
|
|
615
|
+
}
|
|
616
|
+
if (k == f->depth) return i;
|
|
617
|
+
}
|
|
618
|
+
return UINT32_MAX;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/* Runs re's matcher on the string from `from` (at most its length): 1 with capture[] set (from
|
|
622
|
+
* lre_exec's alloc count), 0 for no match, or LRE_RET_* < 0. */
|
|
623
|
+
static int run(JSContext *ctx, RegExpObject *re, JSStrView v, uint32_t from, uint8_t **capture) {
|
|
624
|
+
const uint8_t *buf = v.chars;
|
|
625
|
+
int shift = v.latin1 ? 0 : 1;
|
|
626
|
+
int flags = lre_get_flags(re->bytecode);
|
|
627
|
+
if (re->one_class) return scan_class(re, buf, shift, from, v.length, (flags & LRE_FLAG_STICKY) != 0, capture);
|
|
628
|
+
/* surrogate pairs: lre_exec steps over them as code points, the scan doesn't */
|
|
629
|
+
bool pairs = !v.latin1 && (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS));
|
|
630
|
+
if (!re->analyzed && v.length - from >= 64 && !pairs) re->prefilter = analyze(re);
|
|
631
|
+
const Prefilter *f = re->prefilter;
|
|
632
|
+
if (!f || pairs) return lre_exec(capture, re->bytecode, buf, (int)from, (int)v.length, shift, ctx);
|
|
633
|
+
for (uint32_t i = from;; i++) {
|
|
634
|
+
i = next_start(f, buf, shift, i, v.length);
|
|
635
|
+
if (i == UINT32_MAX) return 0;
|
|
636
|
+
int rc = lre_exec(capture, f->once, buf, (int)i, (int)v.length, shift, ctx);
|
|
637
|
+
if (rc != 0) return rc;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
374
641
|
static void set_matcher(JSContext *ctx, RegExpObject *re, JSValue source, const uint8_t *bytecode, bool owned) {
|
|
375
642
|
if (re->owned) free((void *)re->bytecode);
|
|
643
|
+
free_prefilter(re);
|
|
376
644
|
js_store(ctx, &re->obj.cell, &re->source, source);
|
|
377
645
|
re->bytecode = bytecode;
|
|
378
646
|
re->owned = owned;
|
|
@@ -407,13 +675,7 @@ static void copy_matcher(JSContext *ctx, RegExpObject *to, const RegExpObject *f
|
|
|
407
675
|
return;
|
|
408
676
|
}
|
|
409
677
|
const uint8_t *bc = from->bytecode;
|
|
410
|
-
|
|
411
|
-
size_t size = 8 + (size_t)(bc[4] | bc[5] << 8 | bc[6] << 16 | (uint32_t)bc[7] << 24);
|
|
412
|
-
const char *names = lre_get_groupnames(bc);
|
|
413
|
-
if (names) {
|
|
414
|
-
for (int i = 1; i < lre_get_capture_count(bc); i++) names += strlen(names) + LRE_GROUP_NAME_TRAILER_LEN;
|
|
415
|
-
size = (size_t)((const uint8_t *)names - bc);
|
|
416
|
-
}
|
|
678
|
+
size_t size = bytecode_size(bc);
|
|
417
679
|
uint8_t *copy = malloc(size);
|
|
418
680
|
if (!copy) js_fatal("out of memory copying a regular expression");
|
|
419
681
|
memcpy(copy, bc, size);
|
|
@@ -570,6 +832,8 @@ FLAG_GETTER(regexp_unicode, LRE_FLAG_UNICODE, "unicode")
|
|
|
570
832
|
FLAG_GETTER(regexp_unicode_sets, LRE_FLAG_UNICODE_SETS, "unicodeSets")
|
|
571
833
|
FLAG_GETTER(regexp_sticky, LRE_FLAG_STICKY, "sticky")
|
|
572
834
|
|
|
835
|
+
static const char *const flag_names[] = { "hasIndices", "global", "ignoreCase", "multiline", "dotAll", "unicode", "unicodeSets", "sticky" };
|
|
836
|
+
|
|
573
837
|
/* get RegExp.prototype.flags (§22.2.6.4): from the flag getters, observably. */
|
|
574
838
|
BUILTIN(regexp_flags) {
|
|
575
839
|
UNUSED_ARGS;
|
|
@@ -578,11 +842,10 @@ BUILTIN(regexp_flags) {
|
|
|
578
842
|
js_describe_value(ctx, this_val, what, sizeof what);
|
|
579
843
|
return js_throw_type_error(ctx, "RegExp.prototype.flags getter called on non-object %s", what);
|
|
580
844
|
}
|
|
581
|
-
static const char *const names[] = { "hasIndices", "global", "ignoreCase", "multiline", "dotAll", "unicode", "unicodeSets", "sticky" };
|
|
582
845
|
char out[9];
|
|
583
846
|
size_t n = 0;
|
|
584
847
|
for (int i = 0; i < 8; i++) {
|
|
585
|
-
JSValue v = js_get_value(ctx, this_val, js_atom_from_cstr(ctx,
|
|
848
|
+
JSValue v = js_get_value(ctx, this_val, js_atom_from_cstr(ctx, flag_names[i]));
|
|
586
849
|
if (js_is_exception(v)) return v;
|
|
587
850
|
if (js_truthy(v)) out[n++] = flag_chars[i];
|
|
588
851
|
}
|
|
@@ -674,9 +937,7 @@ static JSValue builtin_exec(JSContext *ctx, JSValue rx, JSValue s) {
|
|
|
674
937
|
if (!capture) js_fatal("out of memory matching a regular expression");
|
|
675
938
|
int shift = v.latin1 ? 0 : 1;
|
|
676
939
|
const uint8_t *buf = v.chars;
|
|
677
|
-
int rc = last_index > v.length ? 2
|
|
678
|
-
: re->one_class ? scan_class(re, buf, shift, (uint32_t)last_index, v.length, (flags & LRE_FLAG_STICKY) != 0, capture)
|
|
679
|
-
: lre_exec(capture, bc, buf, (int)last_index, (int)v.length, shift, ctx);
|
|
940
|
+
int rc = last_index > v.length ? 2 : run(ctx, re, v, (uint32_t)last_index, capture);
|
|
680
941
|
JSValue result = JS_NULL;
|
|
681
942
|
if (rc < 0) {
|
|
682
943
|
result = throw_exec_failure(ctx, rc);
|
|
@@ -776,11 +1037,139 @@ BUILTIN(regexp_proto_exec) {
|
|
|
776
1037
|
return js_is_exception(s) ? s : builtin_exec(ctx, this_val, s);
|
|
777
1038
|
}
|
|
778
1039
|
|
|
1040
|
+
/* ---- Unmodified RegExps --------------------------------------------------------------------
|
|
1041
|
+
*
|
|
1042
|
+
* The methods below follow the spec step by step: Get(rx, "flags") through the flag getters,
|
|
1043
|
+
* RegExpExec through `exec`, lastIndex read and written as a property, a match array for every
|
|
1044
|
+
* match. For a RegExp as created, on a RegExp.prototype whose methods are the originals, none of
|
|
1045
|
+
* that is observable: test, @@match, @@replace, @@search and @@split then run the matcher
|
|
1046
|
+
* directly and make only what they return (V8 does the same). Checked at every call, after the
|
|
1047
|
+
* steps that can run code (ToString of the arguments). */
|
|
1048
|
+
|
|
1049
|
+
static const JSNativeFn flag_fns[] = { regexp_has_indices, regexp_global, regexp_ignore_case, regexp_multiline,
|
|
1050
|
+
regexp_dot_all, regexp_unicode, regexp_unicode_sets, regexp_sticky };
|
|
1051
|
+
|
|
1052
|
+
static bool is_builtin(JSValue f, JSNativeFn fn) { return js_is_function(f) && js_as_function(f)->entry == fn; }
|
|
1053
|
+
|
|
1054
|
+
/* o's own `key` is a data property holding the built-in fn, or (getter) an accessor whose getter is. */
|
|
1055
|
+
static bool own_builtin(JSObject *o, JSValue key, JSNativeFn fn, bool getter) {
|
|
1056
|
+
uint8_t attrs;
|
|
1057
|
+
int32_t slot = js_shape_find(o->shape, key, &attrs);
|
|
1058
|
+
if (slot < 0 || ((attrs & JS_PROP_ACCESSOR) != 0) != getter) return false;
|
|
1059
|
+
JSValue v = *js_object_slot(o, (uint32_t)slot);
|
|
1060
|
+
return is_builtin(getter ? ((JSAccessorPair *)js_as_cell(v))->get : v, fn);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/* rx, when it's a RegExp whose one own property is lastIndex, writable and a number, on the
|
|
1064
|
+
* realm's RegExp.prototype with the original exec: RegExpExec is then RegExpBuiltinExec, and
|
|
1065
|
+
* lastIndex reads and writes as its slot. Else NULL. */
|
|
1066
|
+
static RegExpObject *unmodified(JSContext *ctx, JSValue rx) {
|
|
1067
|
+
RegExpObject *re = as_regexp(rx);
|
|
1068
|
+
if (!re) return NULL;
|
|
1069
|
+
JSShape *s = re->obj.shape;
|
|
1070
|
+
JSValue proto = ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE];
|
|
1071
|
+
if (s->prop_count != 1 || (s->flags & JS_SHAPE_DICTIONARY) || !js_same_bits(s->proto, proto) ||
|
|
1072
|
+
!js_same_bits(s->key, JS_ATOM(lastIndex)) || s->attrs != JS_PROP_WRITABLE || !js_is_number(*js_object_slot(&re->obj, 0)))
|
|
1073
|
+
return NULL;
|
|
1074
|
+
return own_builtin(js_as_object(proto), JS_ATOM(exec), regexp_proto_exec, false) ? re : NULL;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/* Get(rx, "flags") on an unmodified RegExp runs the original getters only: its flags are its
|
|
1078
|
+
* bytecode's. */
|
|
1079
|
+
static bool flags_unmodified(JSContext *ctx) {
|
|
1080
|
+
JSObject *p = js_as_object(ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE]);
|
|
1081
|
+
if (!own_builtin(p, JS_ATOM(flags), regexp_flags, true)) return false;
|
|
1082
|
+
for (int i = 0; i < 8; i++)
|
|
1083
|
+
if (!own_builtin(p, js_atom_from_cstr(ctx, flag_names[i]), flag_fns[i], true)) return false;
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
static double slot_last_index(JSContext *ctx, RegExpObject *re) {
|
|
1088
|
+
double d;
|
|
1089
|
+
if (to_length(ctx, *js_object_slot(&re->obj, 0), &d) < 0) js_fatal("ToLength of a number");
|
|
1090
|
+
return d;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
static void store_last_index(JSContext *ctx, RegExpObject *re, double index) {
|
|
1094
|
+
js_store(ctx, &re->obj.cell, js_object_slot(&re->obj, 0), js_number(index));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* The index in v of a position lre_exec gave. */
|
|
1098
|
+
static uint32_t index_of(JSStrView v, const uint8_t *p) {
|
|
1099
|
+
return (uint32_t)((p - (const uint8_t *)v.chars) >> (v.latin1 ? 0 : 1));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/* lre_exec's capture[] for bc: `small`, or malloc'd (free it when it isn't `small`). */
|
|
1103
|
+
static uint8_t **capture_buffer(const uint8_t *bc, uint8_t **small) {
|
|
1104
|
+
int n = lre_get_alloc_count(bc);
|
|
1105
|
+
if (n <= 32) return small;
|
|
1106
|
+
uint8_t **capture = malloc(sizeof *capture * (size_t)n);
|
|
1107
|
+
if (!capture) js_fatal("out of memory matching a regular expression");
|
|
1108
|
+
return capture;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/* test(s) (§22.2.6.16) on an unmodified RegExp: RegExpBuiltinExec without the match array. */
|
|
1112
|
+
static JSValue test_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
|
|
1113
|
+
int flags = lre_get_flags(re->bytecode);
|
|
1114
|
+
bool global_or_sticky = (flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) != 0;
|
|
1115
|
+
double from = global_or_sticky ? slot_last_index(ctx, re) : 0;
|
|
1116
|
+
JSStrView v = js_string_view(ctx, s);
|
|
1117
|
+
uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
|
|
1118
|
+
int rc = from > v.length ? 0 : run(ctx, re, v, (uint32_t)from, capture);
|
|
1119
|
+
if (rc > 0 && global_or_sticky) store_last_index(ctx, re, index_of(v, capture[1]));
|
|
1120
|
+
if (rc == 0 && global_or_sticky) store_last_index(ctx, re, 0);
|
|
1121
|
+
if (capture != small) free(capture);
|
|
1122
|
+
return rc < 0 ? throw_exec_failure(ctx, rc) : js_bool(rc > 0);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/* @@match with the g flag on an unmodified RegExp: the matched strings, or null. */
|
|
1126
|
+
static JSValue match_global_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
|
|
1127
|
+
bool unicode = (lre_get_flags(re->bytecode) & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
|
|
1128
|
+
uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
|
|
1129
|
+
JSValue a = js_new_array(ctx, 0), result;
|
|
1130
|
+
double i = 0;
|
|
1131
|
+
for (;;) {
|
|
1132
|
+
JSStrView v = js_string_view(ctx, s);
|
|
1133
|
+
int rc = i > v.length ? 0 : run(ctx, re, v, (uint32_t)i, capture);
|
|
1134
|
+
if (rc < 0) {
|
|
1135
|
+
store_last_index(ctx, re, i);
|
|
1136
|
+
result = throw_exec_failure(ctx, rc);
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
if (rc == 0) {
|
|
1140
|
+
store_last_index(ctx, re, 0);
|
|
1141
|
+
result = js_array_length(js_as_object(a)) ? a : JS_NULL;
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
uint32_t start = index_of(v, capture[0]), end = index_of(v, capture[1]);
|
|
1145
|
+
JSValue m = js_substring(ctx, s, start, end);
|
|
1146
|
+
if (js_is_exception(m) || js_array_push(ctx, js_as_object(a), m) < 0) {
|
|
1147
|
+
result = JS_EXCEPTION;
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
i = end == start ? advance_index(ctx, s, end, unicode) : end;
|
|
1151
|
+
}
|
|
1152
|
+
if (capture != small) free(capture);
|
|
1153
|
+
return result;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/* @@search on an unmodified RegExp: lastIndex ends as it was, so it's left alone. */
|
|
1157
|
+
static JSValue search_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
|
|
1158
|
+
JSStrView v = js_string_view(ctx, s);
|
|
1159
|
+
uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
|
|
1160
|
+
int rc = run(ctx, re, v, 0, capture);
|
|
1161
|
+
double index = rc > 0 ? (double)index_of(v, capture[0]) : -1;
|
|
1162
|
+
if (capture != small) free(capture);
|
|
1163
|
+
return rc < 0 ? throw_exec_failure(ctx, rc) : js_number(index);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
779
1166
|
BUILTIN(regexp_test) {
|
|
780
1167
|
UNUSED_ARGS;
|
|
781
1168
|
if (!js_is_object(this_val)) return throw_incompatible(ctx, "test", this_val);
|
|
782
1169
|
JSValue s = js_to_string(ctx, ARG(0));
|
|
783
1170
|
if (js_is_exception(s)) return s;
|
|
1171
|
+
RegExpObject *re = unmodified(ctx, this_val);
|
|
1172
|
+
if (re) return test_unmodified(ctx, re, s);
|
|
784
1173
|
JSValue r = regexp_exec(ctx, this_val, s);
|
|
785
1174
|
return js_is_exception(r) ? r : js_bool(!js_is_null(r));
|
|
786
1175
|
}
|
|
@@ -805,6 +1194,9 @@ BUILTIN(regexp_symbol_match) {
|
|
|
805
1194
|
if (!js_is_object(rx)) return throw_incompatible(ctx, "@@match", rx);
|
|
806
1195
|
JSValue s = js_to_string(ctx, ARG(0));
|
|
807
1196
|
if (js_is_exception(s)) return s;
|
|
1197
|
+
RegExpObject *re = unmodified(ctx, rx);
|
|
1198
|
+
if (re && flags_unmodified(ctx))
|
|
1199
|
+
return lre_get_flags(re->bytecode) & LRE_FLAG_GLOBAL ? match_global_unmodified(ctx, re, s) : builtin_exec(ctx, rx, s);
|
|
808
1200
|
JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
|
|
809
1201
|
JSValue f = js_is_exception(flags) ? flags : js_to_string(ctx, flags);
|
|
810
1202
|
if (js_is_exception(f)) return f;
|
|
@@ -1006,6 +1398,83 @@ static int get_substitution(JSContext *ctx, Builder *b, JSValue matched, JSValue
|
|
|
1006
1398
|
return 0;
|
|
1007
1399
|
}
|
|
1008
1400
|
|
|
1401
|
+
/* @@replace on an unmodified RegExp without named groups, of at most 32 captures. Every match is
|
|
1402
|
+
* found before a replacement function first runs, as in the spec, which collects the match
|
|
1403
|
+
* arrays first: positions only here, the strings made as each replacement needs them. */
|
|
1404
|
+
static JSValue replace_unmodified(JSContext *ctx, RegExpObject *re, JSValue s, JSValue replace_value, bool functional) {
|
|
1405
|
+
const uint8_t *bc = re->bytecode;
|
|
1406
|
+
int flags = lre_get_flags(bc);
|
|
1407
|
+
uint32_t ncap = (uint32_t)lre_get_capture_count(bc);
|
|
1408
|
+
bool global = (flags & LRE_FLAG_GLOBAL) != 0, sticky = (flags & LRE_FLAG_STICKY) != 0;
|
|
1409
|
+
bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
|
|
1410
|
+
uint32_t *pos = NULL; /* each match's captures: start and end, UINT32_MAX when unset */
|
|
1411
|
+
size_t count = 0, cap = 0;
|
|
1412
|
+
uint8_t *small[32], **capture = capture_buffer(bc, small);
|
|
1413
|
+
double i = global || !sticky ? 0 : slot_last_index(ctx, re);
|
|
1414
|
+
int rc;
|
|
1415
|
+
for (;;) {
|
|
1416
|
+
JSStrView v = js_string_view(ctx, s);
|
|
1417
|
+
rc = i > v.length ? 0 : run(ctx, re, v, (uint32_t)i, capture);
|
|
1418
|
+
if (rc <= 0) break;
|
|
1419
|
+
if (count == cap) {
|
|
1420
|
+
cap = cap ? cap * 2 : 8;
|
|
1421
|
+
uint32_t *grown = realloc(pos, cap * 2 * ncap * sizeof *pos);
|
|
1422
|
+
if (!grown) js_fatal("out of memory replacing");
|
|
1423
|
+
pos = grown;
|
|
1424
|
+
}
|
|
1425
|
+
uint32_t *m = pos + count++ * 2 * ncap;
|
|
1426
|
+
for (uint32_t k = 0; k < ncap; k++) {
|
|
1427
|
+
bool set = capture[2 * k] && capture[2 * k + 1];
|
|
1428
|
+
m[2 * k] = set ? index_of(v, capture[2 * k]) : UINT32_MAX;
|
|
1429
|
+
m[2 * k + 1] = set ? index_of(v, capture[2 * k + 1]) : UINT32_MAX;
|
|
1430
|
+
}
|
|
1431
|
+
if (!global) break;
|
|
1432
|
+
i = m[1] == m[0] ? advance_index(ctx, s, m[1], unicode) : m[1];
|
|
1433
|
+
}
|
|
1434
|
+
if (capture != small) free(capture);
|
|
1435
|
+
if (global) store_last_index(ctx, re, rc < 0 ? i : 0);
|
|
1436
|
+
else if (sticky && rc >= 0) store_last_index(ctx, re, rc ? pos[1] : 0);
|
|
1437
|
+
if (rc < 0) {
|
|
1438
|
+
free(pos);
|
|
1439
|
+
return throw_exec_failure(ctx, rc);
|
|
1440
|
+
}
|
|
1441
|
+
bool dollar = !functional && has_unit(ctx, replace_value, '$');
|
|
1442
|
+
Builder b = { 0 };
|
|
1443
|
+
uint32_t next = 0;
|
|
1444
|
+
for (size_t j = 0; j < count; j++) {
|
|
1445
|
+
const uint32_t *m = pos + j * 2 * ncap;
|
|
1446
|
+
if (m[0] > next) put_view(&b, js_string_view(ctx, s), next, m[0]);
|
|
1447
|
+
if (!functional && !dollar) {
|
|
1448
|
+
put_string(&b, ctx, replace_value);
|
|
1449
|
+
} else {
|
|
1450
|
+
JSValue args[34]; /* the match, its captures, the position, the string */
|
|
1451
|
+
for (uint32_t k = 0; k < ncap; k++) {
|
|
1452
|
+
args[k] = m[2 * k] == UINT32_MAX ? JS_UNDEFINED : js_substring(ctx, s, m[2 * k], m[2 * k + 1]);
|
|
1453
|
+
if (js_is_exception(args[k])) goto fail;
|
|
1454
|
+
}
|
|
1455
|
+
if (functional) {
|
|
1456
|
+
args[ncap] = js_number(m[0]);
|
|
1457
|
+
args[ncap + 1] = s;
|
|
1458
|
+
JSValue r = js_call(ctx, replace_value, JS_UNDEFINED, ncap + 2, args);
|
|
1459
|
+
JSValue replacement = js_is_exception(r) ? r : js_to_string(ctx, r);
|
|
1460
|
+
if (js_is_exception(replacement)) goto fail;
|
|
1461
|
+
put_string(&b, ctx, replacement);
|
|
1462
|
+
} else if (get_substitution(ctx, &b, args[0], s, m[0], args + 1, ncap - 1, JS_UNDEFINED, replace_value) < 0) {
|
|
1463
|
+
goto fail;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
next = m[1];
|
|
1467
|
+
}
|
|
1468
|
+
uint32_t length = js_string_length(s);
|
|
1469
|
+
if (next < length) put_view(&b, js_string_view(ctx, s), next, length);
|
|
1470
|
+
free(pos);
|
|
1471
|
+
return finish(ctx, &b);
|
|
1472
|
+
fail:
|
|
1473
|
+
free(pos);
|
|
1474
|
+
free(b.units);
|
|
1475
|
+
return JS_EXCEPTION;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1009
1478
|
/* RegExp.prototype[@@replace](string, replaceValue) (§22.2.6.11) */
|
|
1010
1479
|
BUILTIN(regexp_symbol_replace) {
|
|
1011
1480
|
UNUSED_ARGS;
|
|
@@ -1019,6 +1488,9 @@ BUILTIN(regexp_symbol_replace) {
|
|
|
1019
1488
|
replace_value = js_to_string(ctx, replace_value);
|
|
1020
1489
|
if (js_is_exception(replace_value)) return replace_value;
|
|
1021
1490
|
}
|
|
1491
|
+
RegExpObject *re = unmodified(ctx, rx);
|
|
1492
|
+
if (re && lre_get_capture_count(re->bytecode) <= 32 && !lre_get_groupnames(re->bytecode) && flags_unmodified(ctx))
|
|
1493
|
+
return replace_unmodified(ctx, re, s, replace_value, functional);
|
|
1022
1494
|
JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
|
|
1023
1495
|
JSValue f = js_is_exception(flags) ? flags : js_to_string(ctx, flags);
|
|
1024
1496
|
if (js_is_exception(f)) return f;
|
|
@@ -1121,6 +1593,8 @@ BUILTIN(regexp_symbol_search) {
|
|
|
1121
1593
|
if (!js_is_object(rx)) return throw_incompatible(ctx, "@@search", rx);
|
|
1122
1594
|
JSValue s = js_to_string(ctx, ARG(0));
|
|
1123
1595
|
if (js_is_exception(s)) return s;
|
|
1596
|
+
RegExpObject *re = unmodified(ctx, rx);
|
|
1597
|
+
if (re) return search_unmodified(ctx, re, s);
|
|
1124
1598
|
JSValue previous = js_get_value(ctx, rx, JS_ATOM(lastIndex));
|
|
1125
1599
|
if (js_is_exception(previous)) return previous;
|
|
1126
1600
|
if (!js_same_value(ctx, previous, js_int32(0)) && set_last_index(ctx, rx, 0) < 0) return JS_EXCEPTION;
|
|
@@ -1133,6 +1607,80 @@ BUILTIN(regexp_symbol_search) {
|
|
|
1133
1607
|
return js_get_value(ctx, result, JS_ATOM(index));
|
|
1134
1608
|
}
|
|
1135
1609
|
|
|
1610
|
+
/* @@split's splitter is new %RegExp%(rx, flags + "y") without running code when rx is unmodified,
|
|
1611
|
+
* RegExp.prototype's constructor and @@match and RegExp's @@species are the originals. */
|
|
1612
|
+
static bool species_unmodified(JSContext *ctx) {
|
|
1613
|
+
JSObject *p = js_as_object(ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE]);
|
|
1614
|
+
JSValue c = ctx->intrinsics[JS_INTRINSIC_REGEXP];
|
|
1615
|
+
uint8_t attrs;
|
|
1616
|
+
int32_t slot = js_shape_find(p->shape, JS_ATOM(constructor), &attrs);
|
|
1617
|
+
return slot >= 0 && !(attrs & JS_PROP_ACCESSOR) && js_same_bits(*js_object_slot(p, (uint32_t)slot), c) &&
|
|
1618
|
+
own_builtin(p, JS_SYMBOL(match), regexp_symbol_match, false) && own_builtin(js_as_object(c), JS_SYMBOL(species), regexp_species, true);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/* @@split on an unmodified RegExp without the y, u or v flags. The spec's splitter tries a
|
|
1622
|
+
* sticky match at each position q in turn; a search from q finds the first q that matches, with
|
|
1623
|
+
* the same match there, and no splitter is made. */
|
|
1624
|
+
static JSValue split_unmodified(JSContext *ctx, RegExpObject *re, JSValue s, uint32_t lim) {
|
|
1625
|
+
JSValue a = js_new_array(ctx, 0);
|
|
1626
|
+
if (lim == 0) return a;
|
|
1627
|
+
uint32_t ncap = (uint32_t)lre_get_capture_count(re->bytecode);
|
|
1628
|
+
uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
|
|
1629
|
+
uint32_t size = js_string_length(s), p = 0, q = 0;
|
|
1630
|
+
JSValue result = a;
|
|
1631
|
+
if (size == 0) {
|
|
1632
|
+
int rc = run(ctx, re, js_string_view(ctx, s), 0, capture);
|
|
1633
|
+
if (rc < 0) result = throw_exec_failure(ctx, rc);
|
|
1634
|
+
else if (rc == 0 && js_array_push(ctx, js_as_object(a), s) < 0) result = JS_EXCEPTION;
|
|
1635
|
+
goto out;
|
|
1636
|
+
}
|
|
1637
|
+
while (q < size) {
|
|
1638
|
+
JSStrView v = js_string_view(ctx, s);
|
|
1639
|
+
int rc = run(ctx, re, v, q, capture);
|
|
1640
|
+
if (rc < 0) {
|
|
1641
|
+
result = throw_exec_failure(ctx, rc);
|
|
1642
|
+
goto out;
|
|
1643
|
+
}
|
|
1644
|
+
if (rc == 0) break;
|
|
1645
|
+
uint32_t start = index_of(v, capture[0]), e = index_of(v, capture[1]);
|
|
1646
|
+
if (start >= size) break; /* the spec's loop stops before trying the end */
|
|
1647
|
+
if (e == p) { /* empty, where the last piece ended */
|
|
1648
|
+
q = start + 1;
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
uint32_t bounds[64]; /* the captures, read before allocating */
|
|
1652
|
+
for (uint32_t k = 1; k < ncap && k < 33; k++) {
|
|
1653
|
+
bool set = capture[2 * k] && capture[2 * k + 1];
|
|
1654
|
+
bounds[2 * (k - 1)] = set ? index_of(v, capture[2 * k]) : UINT32_MAX;
|
|
1655
|
+
bounds[2 * (k - 1) + 1] = set ? index_of(v, capture[2 * k + 1]) : UINT32_MAX;
|
|
1656
|
+
}
|
|
1657
|
+
JSValue sub = js_substring(ctx, s, p, start);
|
|
1658
|
+
if (js_is_exception(sub) || js_array_push(ctx, js_as_object(a), sub) < 0) {
|
|
1659
|
+
result = JS_EXCEPTION;
|
|
1660
|
+
goto out;
|
|
1661
|
+
}
|
|
1662
|
+
if (js_array_length(js_as_object(a)) == lim) goto out;
|
|
1663
|
+
p = e;
|
|
1664
|
+
for (uint32_t k = 1; k < ncap; k++) {
|
|
1665
|
+
uint32_t lo = bounds[2 * (k - 1)], hi = bounds[2 * (k - 1) + 1];
|
|
1666
|
+
JSValue c = lo == UINT32_MAX ? JS_UNDEFINED : js_substring(ctx, s, lo, hi);
|
|
1667
|
+
if (js_is_exception(c) || js_array_push(ctx, js_as_object(a), c) < 0) {
|
|
1668
|
+
result = JS_EXCEPTION;
|
|
1669
|
+
goto out;
|
|
1670
|
+
}
|
|
1671
|
+
if (js_array_length(js_as_object(a)) == lim) goto out;
|
|
1672
|
+
}
|
|
1673
|
+
q = p;
|
|
1674
|
+
}
|
|
1675
|
+
{
|
|
1676
|
+
JSValue tail = js_substring(ctx, s, p < size ? p : size, size);
|
|
1677
|
+
if (js_is_exception(tail) || js_array_push(ctx, js_as_object(a), tail) < 0) result = JS_EXCEPTION;
|
|
1678
|
+
}
|
|
1679
|
+
out:
|
|
1680
|
+
if (capture != small) free(capture);
|
|
1681
|
+
return result;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1136
1684
|
/* RegExp.prototype[@@split](string, limit) (§22.2.6.14) */
|
|
1137
1685
|
BUILTIN(regexp_symbol_split) {
|
|
1138
1686
|
UNUSED_ARGS;
|
|
@@ -1140,6 +1688,11 @@ BUILTIN(regexp_symbol_split) {
|
|
|
1140
1688
|
if (!js_is_object(rx)) return throw_incompatible(ctx, "@@split", rx);
|
|
1141
1689
|
JSValue s = js_to_string(ctx, ARG(0));
|
|
1142
1690
|
if (js_is_exception(s)) return s;
|
|
1691
|
+
RegExpObject *re = unmodified(ctx, rx);
|
|
1692
|
+
JSValue limit = ARG(1);
|
|
1693
|
+
if (re && !(lre_get_flags(re->bytecode) & (LRE_FLAG_STICKY | LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) &&
|
|
1694
|
+
(js_is_undefined(limit) || js_is_number(limit)) && flags_unmodified(ctx) && species_unmodified(ctx))
|
|
1695
|
+
return split_unmodified(ctx, re, s, js_is_undefined(limit) ? UINT32_MAX : (uint32_t)js_double_to_int32(js_as_number(limit)));
|
|
1143
1696
|
JSValue c = species_constructor(ctx, rx);
|
|
1144
1697
|
if (js_is_exception(c)) return c;
|
|
1145
1698
|
JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
|
|
@@ -1253,6 +1806,10 @@ static const JSBuiltin regexp_string_iterator_proto[] = {
|
|
|
1253
1806
|
};
|
|
1254
1807
|
|
|
1255
1808
|
static void install_regexp(JSContext *ctx, JSObject *global) {
|
|
1809
|
+
for (uint32_t c = 0; c < 0x100; c++) { /* the same in every realm */
|
|
1810
|
+
canonical[0][c] = (uint32_t)lre_canonicalize(c, 0);
|
|
1811
|
+
canonical[1][c] = (uint32_t)lre_canonicalize(c, 1);
|
|
1812
|
+
}
|
|
1256
1813
|
JSValue proto = js_new_object_proto(ctx, ctx->intrinsics[JS_INTRINSIC_OBJECT_PROTOTYPE], 8);
|
|
1257
1814
|
ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE] = proto;
|
|
1258
1815
|
JSValue key = js_atom_from_cstr(ctx, "RegExp");
|