Skip to content

Commit cd83557

Browse files
committed
Add String.prototype.includes()
1 parent 7d639be commit cd83557

2 files changed

Lines changed: 126 additions & 74 deletions

File tree

src-input/builtins.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,15 @@ objects:
12551255
present_if:
12561256
- DUK_USE_ES6
12571257
- DUK_USE_STRING_BUILTIN
1258+
- key: "includes"
1259+
value:
1260+
type: function
1261+
native: duk_bi_string_prototype_includes
1262+
length: 1
1263+
nargs: 2
1264+
present_if:
1265+
- DUK_USE_ES6
1266+
- DUK_USE_STRING_BUILTIN
12581267

12591268
# Non-standard extension: E5 Section B.2.3
12601269

src-input/duk_bi_string.c

Lines changed: 117 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,96 @@
1919

2020
#if defined(DUK_USE_STRING_BUILTIN)
2121

22+
/*
23+
* Helpers
24+
*/
25+
26+
DUK_LOCAL duk_hstring *duk__str_tostring_notregexp(duk_context *ctx, duk_idx_t idx) {
27+
duk_hstring *h;
28+
29+
if (duk_get_class_number(ctx, idx) == DUK_HOBJECT_CLASS_REGEXP) {
30+
DUK_ERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
31+
}
32+
h = duk_to_hstring(ctx, idx);
33+
DUK_ASSERT(h != NULL);
34+
35+
return h;
36+
}
37+
38+
DUK_LOCAL duk_int_t duk__str_search_shared(duk_context *ctx, duk_hstring *h_this, duk_hstring *h_search, duk_int_t start_cpos, duk_bool_t backwards) {
39+
duk_int_t cpos;
40+
duk_int_t bpos;
41+
const duk_uint8_t *p_start, *p_end, *p;
42+
const duk_uint8_t *q_start;
43+
duk_int_t q_blen;
44+
duk_uint8_t firstbyte;
45+
duk_uint8_t t;
46+
47+
cpos = start_cpos;
48+
49+
/* Empty searchstring always matches; cpos must be clamped here.
50+
* (If q_blen were < 0 due to clamped coercion, it would also be
51+
* caught here.)
52+
*/
53+
q_start = DUK_HSTRING_GET_DATA(h_search);
54+
q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search);
55+
if (q_blen <= 0) {
56+
return cpos;
57+
}
58+
DUK_ASSERT(q_blen > 0);
59+
60+
bpos = (duk_int_t) duk_heap_strcache_offset_char2byte((duk_hthread *) ctx, h_this, (duk_uint32_t) cpos);
61+
62+
p_start = DUK_HSTRING_GET_DATA(h_this);
63+
p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
64+
p = p_start + bpos;
65+
66+
/* This loop is optimized for size. For speed, there should be
67+
* two separate loops, and we should ensure that memcmp() can be
68+
* used without an extra "will searchstring fit" check. Doing
69+
* the preconditioning for 'p' and 'p_end' is easy but cpos
70+
* must be updated if 'p' is wound back (backward scanning).
71+
*/
72+
73+
firstbyte = q_start[0]; /* leading byte of match string */
74+
while (p <= p_end && p >= p_start) {
75+
t = *p;
76+
77+
/* For Ecmascript strings, this check can only match for
78+
* initial UTF-8 bytes (not continuation bytes). For other
79+
* strings all bets are off.
80+
*/
81+
82+
if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) {
83+
DUK_ASSERT(q_blen > 0); /* no issues with memcmp() zero size, even if broken */
84+
if (DUK_MEMCMP((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) {
85+
return cpos;
86+
}
87+
}
88+
89+
/* track cpos while scanning */
90+
if (backwards) {
91+
/* when going backwards, we decrement cpos 'early';
92+
* 'p' may point to a continuation byte of the char
93+
* at offset 'cpos', but that's OK because we'll
94+
* backtrack all the way to the initial byte.
95+
*/
96+
if ((t & 0xc0) != 0x80) {
97+
cpos--;
98+
}
99+
p--;
100+
} else {
101+
if ((t & 0xc0) != 0x80) {
102+
cpos++;
103+
}
104+
p++;
105+
}
106+
}
107+
108+
/* Not found. Empty string case is handled specially above. */
109+
return -1;
110+
}
111+
22112
/*
23113
* Constructor
24114
*/
@@ -369,12 +459,6 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx)
369459
duk_hstring *h_search;
370460
duk_int_t clen_this;
371461
duk_int_t cpos;
372-
duk_int_t bpos;
373-
const duk_uint8_t *p_start, *p_end, *p;
374-
const duk_uint8_t *q_start;
375-
duk_int_t q_blen;
376-
duk_uint8_t firstbyte;
377-
duk_uint8_t t;
378462
duk_small_int_t is_lastindexof = duk_get_current_magic(ctx); /* 0=indexOf, 1=lastIndexOf */
379463

380464
h_this = duk_push_this_coercible_to_string(ctx);
@@ -383,8 +467,6 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx)
383467

384468
h_search = duk_to_hstring(ctx, 0);
385469
DUK_ASSERT(h_search != NULL);
386-
q_start = DUK_HSTRING_GET_DATA(h_search);
387-
q_blen = (duk_int_t) DUK_HSTRING_GET_BYTELEN(h_search);
388470

389471
duk_to_number(ctx, 1);
390472
if (duk_is_nan(ctx, 1) && is_lastindexof) {
@@ -397,67 +479,8 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_indexof_shared(duk_context *ctx)
397479
cpos = duk_to_int_clamped(ctx, 1, 0, clen_this);
398480
}
399481

400-
/* Empty searchstring always matches; cpos must be clamped here.
401-
* (If q_blen were < 0 due to clamped coercion, it would also be
402-
* caught here.)
403-
*/
404-
if (q_blen <= 0) {
405-
duk_push_int(ctx, cpos);
406-
return 1;
407-
}
408-
DUK_ASSERT(q_blen > 0);
409-
410-
bpos = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_this, (duk_uint32_t) cpos);
411-
412-
p_start = DUK_HSTRING_GET_DATA(h_this);
413-
p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
414-
p = p_start + bpos;
415-
416-
/* This loop is optimized for size. For speed, there should be
417-
* two separate loops, and we should ensure that memcmp() can be
418-
* used without an extra "will searchstring fit" check. Doing
419-
* the preconditioning for 'p' and 'p_end' is easy but cpos
420-
* must be updated if 'p' is wound back (backward scanning).
421-
*/
422-
423-
firstbyte = q_start[0]; /* leading byte of match string */
424-
while (p <= p_end && p >= p_start) {
425-
t = *p;
426-
427-
/* For Ecmascript strings, this check can only match for
428-
* initial UTF-8 bytes (not continuation bytes). For other
429-
* strings all bets are off.
430-
*/
431-
432-
if ((t == firstbyte) && ((duk_size_t) (p_end - p) >= (duk_size_t) q_blen)) {
433-
DUK_ASSERT(q_blen > 0); /* no issues with memcmp() zero size, even if broken */
434-
if (DUK_MEMCMP((const void *) p, (const void *) q_start, (size_t) q_blen) == 0) {
435-
duk_push_int(ctx, cpos);
436-
return 1;
437-
}
438-
}
439-
440-
/* track cpos while scanning */
441-
if (is_lastindexof) {
442-
/* when going backwards, we decrement cpos 'early';
443-
* 'p' may point to a continuation byte of the char
444-
* at offset 'cpos', but that's OK because we'll
445-
* backtrack all the way to the initial byte.
446-
*/
447-
if ((t & 0xc0) != 0x80) {
448-
cpos--;
449-
}
450-
p--;
451-
} else {
452-
if ((t & 0xc0) != 0x80) {
453-
cpos++;
454-
}
455-
p++;
456-
}
457-
}
458-
459-
/* Not found. Empty string case is handled specially above. */
460-
duk_push_int(ctx, -1);
482+
cpos = duk__str_search_shared(ctx, h_this, h_search, cpos, is_lastindexof /*backwards*/);
483+
duk_push_int(ctx, cpos);
461484
return 1;
462485
}
463486

@@ -1460,10 +1483,8 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *
14601483
h = duk_push_this_coercible_to_string(ctx);
14611484
DUK_ASSERT(h != NULL);
14621485

1463-
if (duk_get_class_number(ctx, 0) == DUK_HOBJECT_CLASS_REGEXP) {
1464-
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
1465-
}
1466-
h_search = duk_to_hstring(ctx, 0);
1486+
h_search = duk__str_tostring_notregexp(ctx, 0);
1487+
DUK_ASSERT(h_search = NULL);
14671488

14681489
magic = duk_get_current_magic(ctx);
14691490

@@ -1502,7 +1523,7 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *
15021523

15031524
result = 0;
15041525
if (p_cmp_start >= DUK_HSTRING_GET_DATA(h) &&
1505-
p_cmp_start + blen_search <= DUK_HSTRING_GET_DATA(h) + DUK_HSTRING_GET_BYTELEN(h)) {
1526+
p_cmp_start - (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h) + blen_search <= DUK_HSTRING_GET_BYTELEN(h)) {
15061527
if (DUK_MEMCMP((const void *) p_cmp_start,
15071528
(const void *) DUK_HSTRING_GET_DATA(h_search),
15081529
(size_t) blen_search) == 0) {
@@ -1515,4 +1536,26 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_context *
15151536
}
15161537
#endif /* DUK_USE_ES6 */
15171538

1539+
#if defined(DUK_USE_ES6)
1540+
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_includes(duk_context *ctx) {
1541+
duk_hstring *h;
1542+
duk_hstring *h_search;
1543+
duk_int_t len;
1544+
duk_int_t pos;
1545+
1546+
h = duk_push_this_coercible_to_string(ctx);
1547+
DUK_ASSERT(h != NULL);
1548+
1549+
h_search = duk__str_tostring_notregexp(ctx, 0);
1550+
DUK_ASSERT(h_search = NULL);
1551+
1552+
len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
1553+
pos = duk_to_int_clamped(ctx, 1, 0, len);
1554+
DUK_ASSERT(pos >= 0 && pos <= len);
1555+
1556+
pos = duk__str_search_shared(ctx, h, h_search, pos, 0 /*backwards*/);
1557+
duk_push_boolean(ctx, pos >= 0);
1558+
return 1;
1559+
}
1560+
#endif /* DUK_USE_ES6 */
15181561
#endif /* DUK_USE_STRING_BUILTIN */

0 commit comments

Comments
 (0)