blob: 64c411870455c1f9293c5a6139d47e1fded1e0e8 [file] [log] [blame]
/*
* Various Unicode help functions for character classification predicates,
* case conversion, decoding, etc.
*/
#include "duk_internal.h"
/*
* Fast path tables
*/
#if defined(DUK_USE_IDCHAR_FASTPATH)
DUK_INTERNAL const duk_int8_t duk_is_idchar_tab[128] = {
/* 0: not IdentifierStart or IdentifierPart
* 1: IdentifierStart and IdentifierPart
* -1: IdentifierPart only
*/
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00...0x0f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10...0x1f */
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20...0x2f */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, /* 0x30...0x3f */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40...0x4f */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x50...0x5f */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60...0x6f */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 /* 0x70...0x7f */
};
#endif
/*
* XUTF-8 and CESU-8 encoding/decoding
*/
DUK_INTERNAL duk_small_int_t duk_unicode_get_xutf8_length(duk_ucodepoint_t cp) {
duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
if (x < 0x80UL) {
/* 7 bits */
return 1;
} else if (x < 0x800UL) {
/* 11 bits */
return 2;
} else if (x < 0x10000UL) {
/* 16 bits */
return 3;
} else if (x < 0x200000UL) {
/* 21 bits */
return 4;
} else if (x < 0x4000000UL) {
/* 26 bits */
return 5;
} else if (x < (duk_ucodepoint_t) 0x80000000UL) {
/* 31 bits */
return 6;
} else {
/* 36 bits */
return 7;
}
}
#if defined(DUK_USE_ASSERTIONS)
DUK_INTERNAL duk_small_int_t duk_unicode_get_cesu8_length(duk_ucodepoint_t cp) {
duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
if (x < 0x80UL) {
/* 7 bits */
return 1;
} else if (x < 0x800UL) {
/* 11 bits */
return 2;
} else if (x < 0x10000UL) {
/* 16 bits */
return 3;
} else {
/* Encoded as surrogate pair, each encoding to 3 bytes for
* 6 bytes total. Codepoints above U+10FFFF encode as 6 bytes
* too, see duk_unicode_encode_cesu8().
*/
return 3 + 3;
}
}
#endif /* DUK_USE_ASSERTIONS */
DUK_INTERNAL const duk_uint8_t duk_unicode_xutf8_markers[7] = {
0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
};
/* Encode to extended UTF-8; 'out' must have space for at least
* DUK_UNICODE_MAX_XUTF8_LENGTH bytes. Allows encoding of any
* 32-bit (unsigned) codepoint.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_encode_xutf8(duk_ucodepoint_t cp, duk_uint8_t *out) {
duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
duk_small_int_t len;
duk_uint8_t marker;
duk_small_int_t i;
len = duk_unicode_get_xutf8_length(cp);
DUK_ASSERT(len > 0);
marker = duk_unicode_xutf8_markers[len - 1]; /* 64-bit OK because always >= 0 */
i = len;
DUK_ASSERT(i > 0);
do {
i--;
if (i > 0) {
out[i] = (duk_uint8_t) (0x80 + (x & 0x3f));
x >>= 6;
} else {
/* Note: masking of 'x' is not necessary because of
* range check and shifting -> no bits overlapping
* the marker should be set.
*/
out[0] = (duk_uint8_t) (marker + x);
}
} while (i > 0);
return len;
}
/* Encode to CESU-8; 'out' must have space for at least
* DUK_UNICODE_MAX_CESU8_LENGTH bytes; codepoints above U+10FFFF
* will encode to garbage but won't overwrite the output buffer.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_encode_cesu8(duk_ucodepoint_t cp, duk_uint8_t *out) {
duk_uint_fast32_t x = (duk_uint_fast32_t) cp;
duk_small_int_t len;
if (x < 0x80UL) {
out[0] = (duk_uint8_t) x;
len = 1;
} else if (x < 0x800UL) {
out[0] = (duk_uint8_t) (0xc0 + ((x >> 6) & 0x1f));
out[1] = (duk_uint8_t) (0x80 + (x & 0x3f));
len = 2;
} else if (x < 0x10000UL) {
/* surrogate pairs get encoded here */
out[0] = (duk_uint8_t) (0xe0 + ((x >> 12) & 0x0f));
out[1] = (duk_uint8_t) (0x80 + ((x >> 6) & 0x3f));
out[2] = (duk_uint8_t) (0x80 + (x & 0x3f));
len = 3;
} else {
/*
* Unicode codepoints above U+FFFF are encoded as surrogate
* pairs here. This ensures that all CESU-8 codepoints are
* 16-bit values as expected in Ecmascript. The surrogate
* pairs always get a 3-byte encoding (each) in CESU-8.
* See: http://en.wikipedia.org/wiki/Surrogate_pair
*
* 20-bit codepoint, 10 bits (A and B) per surrogate pair:
*
* x = 0b00000000 0000AAAA AAAAAABB BBBBBBBB
* sp1 = 0b110110AA AAAAAAAA (0xd800 + ((x >> 10) & 0x3ff))
* sp2 = 0b110111BB BBBBBBBB (0xdc00 + (x & 0x3ff))
*
* Encoded into CESU-8:
*
* sp1 -> 0b11101101 (0xe0 + ((sp1 >> 12) & 0x0f))
* -> 0b1010AAAA (0x80 + ((sp1 >> 6) & 0x3f))
* -> 0b10AAAAAA (0x80 + (sp1 & 0x3f))
* sp2 -> 0b11101101 (0xe0 + ((sp2 >> 12) & 0x0f))
* -> 0b1011BBBB (0x80 + ((sp2 >> 6) & 0x3f))
* -> 0b10BBBBBB (0x80 + (sp2 & 0x3f))
*
* Note that 0x10000 must be subtracted first. The code below
* avoids the sp1, sp2 temporaries which saves around 20 bytes
* of code.
*/
x -= 0x10000UL;
out[0] = (duk_uint8_t) (0xed);
out[1] = (duk_uint8_t) (0xa0 + ((x >> 16) & 0x0f));
out[2] = (duk_uint8_t) (0x80 + ((x >> 10) & 0x3f));
out[3] = (duk_uint8_t) (0xed);
out[4] = (duk_uint8_t) (0xb0 + ((x >> 6) & 0x0f));
out[5] = (duk_uint8_t) (0x80 + (x & 0x3f));
len = 6;
}
return len;
}
/* Decode helper. Return zero on error. */
DUK_INTERNAL duk_small_int_t duk_unicode_decode_xutf8(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end, duk_ucodepoint_t *out_cp) {
const duk_uint8_t *p;
duk_uint32_t res;
duk_uint_fast8_t ch;
duk_small_int_t n;
DUK_UNREF(thr);
p = *ptr;
if (p < ptr_start || p >= ptr_end) {
goto fail;
}
/*
* UTF-8 decoder which accepts longer than standard byte sequences.
* This allows full 32-bit code points to be used.
*/
ch = (duk_uint_fast8_t) (*p++);
if (ch < 0x80) {
/* 0xxx xxxx [7 bits] */
res = (duk_uint32_t) (ch & 0x7f);
n = 0;
} else if (ch < 0xc0) {
/* 10xx xxxx -> invalid */
goto fail;
} else if (ch < 0xe0) {
/* 110x xxxx 10xx xxxx [11 bits] */
res = (duk_uint32_t) (ch & 0x1f);
n = 1;
} else if (ch < 0xf0) {
/* 1110 xxxx 10xx xxxx 10xx xxxx [16 bits] */
res = (duk_uint32_t) (ch & 0x0f);
n = 2;
} else if (ch < 0xf8) {
/* 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx [21 bits] */
res = (duk_uint32_t) (ch & 0x07);
n = 3;
} else if (ch < 0xfc) {
/* 1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [26 bits] */
res = (duk_uint32_t) (ch & 0x03);
n = 4;
} else if (ch < 0xfe) {
/* 1111 110x 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [31 bits] */
res = (duk_uint32_t) (ch & 0x01);
n = 5;
} else if (ch < 0xff) {
/* 1111 1110 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [36 bits] */
res = (duk_uint32_t) (0);
n = 6;
} else {
/* 8-byte format could be:
* 1111 1111 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx [41 bits]
*
* However, this format would not have a zero bit following the
* leading one bits and would not allow 0xFF to be used as an
* "invalid xutf-8" marker for internal keys. Further, 8-byte
* encodings (up to 41 bit code points) are not currently needed.
*/
goto fail;
}
DUK_ASSERT(p >= ptr_start); /* verified at beginning */
if (p + n > ptr_end) {
/* check pointer at end */
goto fail;
}
while (n > 0) {
DUK_ASSERT(p >= ptr_start && p < ptr_end);
res = res << 6;
res += (duk_uint32_t) ((*p++) & 0x3f);
n--;
}
*ptr = p;
*out_cp = res;
return 1;
fail:
return 0;
}
/* used by e.g. duk_regexp_executor.c, string built-ins */
DUK_INTERNAL duk_ucodepoint_t duk_unicode_decode_xutf8_checked(duk_hthread *thr, const duk_uint8_t **ptr, const duk_uint8_t *ptr_start, const duk_uint8_t *ptr_end) {
duk_ucodepoint_t cp;
if (duk_unicode_decode_xutf8(thr, ptr, ptr_start, ptr_end, &cp)) {
return cp;
}
DUK_ERROR_INTERNAL(thr, "utf-8 decode failed"); /* XXX: 'internal error' is a bit of a misnomer */
DUK_UNREACHABLE();
return 0;
}
/* Compute (extended) utf-8 length without codepoint encoding validation,
* used for string interning.
*
* NOTE: This algorithm is performance critical, more so than string hashing
* in some cases. It is needed when interning a string and needs to scan
* every byte of the string with no skipping. Having an ASCII fast path
* is useful if possible in the algorithm. The current algorithms were
* chosen from several variants, based on x64 gcc -O2 testing. See:
* https://github.com/svaarala/duktape/pull/422
*
* NOTE: must match src/dukutil.py:duk_unicode_unvalidated_utf8_length().
*/
#if defined(DUK_USE_PREFER_SIZE)
/* Small variant; roughly 150 bytes smaller than the fast variant. */
DUK_INTERNAL duk_size_t duk_unicode_unvalidated_utf8_length(const duk_uint8_t *data, duk_size_t blen) {
const duk_uint8_t *p;
const duk_uint8_t *p_end;
duk_size_t ncont;
duk_size_t clen;
p = data;
p_end = data + blen;
ncont = 0;
while (p != p_end) {
duk_uint8_t x;
x = *p++;
if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) {
ncont++;
}
}
DUK_ASSERT(ncont <= blen);
clen = blen - ncont;
DUK_ASSERT(clen <= blen);
return clen;
}
#else /* DUK_USE_PREFER_SIZE */
/* This seems like a good overall approach. Fast path for ASCII in 4 byte
* blocks.
*/
DUK_INTERNAL duk_size_t duk_unicode_unvalidated_utf8_length(const duk_uint8_t *data, duk_size_t blen) {
const duk_uint8_t *p;
const duk_uint8_t *p_end;
const duk_uint32_t *p32_end;
const duk_uint32_t *p32;
duk_size_t ncont;
duk_size_t clen;
ncont = 0; /* number of continuation (non-initial) bytes in [0x80,0xbf] */
p = data;
p_end = data + blen;
if (blen < 16) {
goto skip_fastpath;
}
/* Align 'p' to 4; the input data may have arbitrary alignment.
* End of string check not needed because blen >= 16.
*/
while (((duk_size_t) (const void *) p) & 0x03U) {
duk_uint8_t x;
x = *p++;
if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) {
ncont++;
}
}
/* Full, aligned 4-byte reads. */
p32_end = (const duk_uint32_t *) (const void *) (p + ((duk_size_t) (p_end - p) & (duk_size_t) (~0x03)));
p32 = (const duk_uint32_t *) (const void *) p;
while (p32 != (const duk_uint32_t *) p32_end) {
duk_uint32_t x;
x = *p32++;
if (DUK_LIKELY((x & 0x80808080UL) == 0)) {
; /* ASCII fast path */
} else {
/* Flip highest bit of each byte which changes
* the bit pattern 10xxxxxx into 00xxxxxx which
* allows an easy bit mask test.
*/
x ^= 0x80808080UL;
if (DUK_UNLIKELY(!(x & 0xc0000000UL))) {
ncont++;
}
if (DUK_UNLIKELY(!(x & 0x00c00000UL))) {
ncont++;
}
if (DUK_UNLIKELY(!(x & 0x0000c000UL))) {
ncont++;
}
if (DUK_UNLIKELY(!(x & 0x000000c0UL))) {
ncont++;
}
}
}
p = (const duk_uint8_t *) p32;
/* Fall through to handle the rest. */
skip_fastpath:
while (p != p_end) {
duk_uint8_t x;
x = *p++;
if (DUK_UNLIKELY(x >= 0x80 && x <= 0xbf)) {
ncont++;
}
}
DUK_ASSERT(ncont <= blen);
clen = blen - ncont;
DUK_ASSERT(clen <= blen);
return clen;
}
#endif /* DUK_USE_PREFER_SIZE */
/*
* Unicode range matcher
*
* Matches a codepoint against a packed bitstream of character ranges.
* Used for slow path Unicode matching.
*/
/* Must match src/extract_chars.py, generate_match_table3(). */
DUK_LOCAL duk_uint32_t duk__uni_decode_value(duk_bitdecoder_ctx *bd_ctx) {
duk_uint32_t t;
t = (duk_uint32_t) duk_bd_decode(bd_ctx, 4);
if (t <= 0x0eU) {
return t;
}
t = (duk_uint32_t) duk_bd_decode(bd_ctx, 8);
if (t <= 0xfdU) {
return t + 0x0f;
}
if (t == 0xfeU) {
t = (duk_uint32_t) duk_bd_decode(bd_ctx, 12);
return t + 0x0fU + 0xfeU;
} else {
t = (duk_uint32_t) duk_bd_decode(bd_ctx, 24);
return t + 0x0fU + 0xfeU + 0x1000UL;
}
}
DUK_LOCAL duk_small_int_t duk__uni_range_match(const duk_uint8_t *unitab, duk_size_t unilen, duk_codepoint_t cp) {
duk_bitdecoder_ctx bd_ctx;
duk_codepoint_t prev_re;
DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
bd_ctx.data = (const duk_uint8_t *) unitab;
bd_ctx.length = (duk_size_t) unilen;
prev_re = 0;
for (;;) {
duk_codepoint_t r1, r2;
r1 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx);
if (r1 == 0) {
break;
}
r2 = (duk_codepoint_t) duk__uni_decode_value(&bd_ctx);
r1 = prev_re + r1;
r2 = r1 + r2;
prev_re = r2;
/* [r1,r2] is the range */
DUK_DDD(DUK_DDDPRINT("duk__uni_range_match: cp=%06lx range=[0x%06lx,0x%06lx]",
(unsigned long) cp, (unsigned long) r1, (unsigned long) r2));
if (cp >= r1 && cp <= r2) {
return 1;
}
}
return 0;
}
/*
* "WhiteSpace" production check.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_is_whitespace(duk_codepoint_t cp) {
/*
* E5 Section 7.2 specifies six characters specifically as
* white space:
*
* 0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATION;;;;
* 000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;;
* 000C;<control>;Cc;0;WS;;;;;N;FORM FEED (FF);;;;
* 0020;SPACE;Zs;0;WS;;;;;N;;;;;
* 00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;;
*
* It also specifies any Unicode category 'Zs' characters as white
* space. These can be extracted with the "src/extract_chars.py" script.
* Current result:
*
* RAW OUTPUT:
* ===========
* 0020;SPACE;Zs;0;WS;;;;;N;;;;;
* 00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
* 1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;;
* 180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;;
* 2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;;
* 2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;;
* 2002;EN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2003;EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2004;THREE-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2005;FOUR-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2006;SIX-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2007;FIGURE SPACE;Zs;0;WS;<noBreak> 0020;;;;N;;;;;
* 2008;PUNCTUATION SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 2009;THIN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 200A;HAIR SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 202F;NARROW NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;;;;;
* 205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
* 3000;IDEOGRAPHIC SPACE;Zs;0;WS;<wide> 0020;;;;N;;;;;
*
* RANGES:
* =======
* 0x0020
* 0x00a0
* 0x1680
* 0x180e
* 0x2000 ... 0x200a
* 0x202f
* 0x205f
* 0x3000
*
* A manual decoder (below) is probably most compact for this.
*/
duk_uint_fast8_t lo;
duk_uint_fast32_t hi;
/* cp == -1 (EOF) never matches and causes return value 0 */
lo = (duk_uint_fast8_t) (cp & 0xff);
hi = (duk_uint_fast32_t) (cp >> 8); /* does not fit into an uchar */
if (hi == 0x0000UL) {
if (lo == 0x09U || lo == 0x0bU || lo == 0x0cU ||
lo == 0x20U || lo == 0xa0U) {
return 1;
}
} else if (hi == 0x0020UL) {
if (lo <= 0x0aU || lo == 0x2fU || lo == 0x5fU) {
return 1;
}
} else if (cp == 0x1680L || cp == 0x180eL || cp == 0x3000L ||
cp == 0xfeffL) {
return 1;
}
return 0;
}
/*
* "LineTerminator" production check.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_is_line_terminator(duk_codepoint_t cp) {
/*
* E5 Section 7.3
*
* A LineTerminatorSequence essentially merges <CR> <LF> sequences
* into a single line terminator. This must be handled by the caller.
*/
if (cp == 0x000aL || cp == 0x000dL || cp == 0x2028L ||
cp == 0x2029L) {
return 1;
}
return 0;
}
/*
* "IdentifierStart" production check.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_is_identifier_start(duk_codepoint_t cp) {
/*
* E5 Section 7.6:
*
* IdentifierStart:
* UnicodeLetter
* $
* _
* \ UnicodeEscapeSequence
*
* IdentifierStart production has one multi-character production:
*
* \ UnicodeEscapeSequence
*
* The '\' character is -not- matched by this function. Rather, the caller
* should decode the escape and then call this function to check whether the
* decoded character is acceptable (see discussion in E5 Section 7.6).
*
* The "UnicodeLetter" alternative of the production allows letters
* from various Unicode categories. These can be extracted with the
* "src/extract_chars.py" script.
*
* Because the result has hundreds of Unicode codepoint ranges, matching
* for any values >= 0x80 are done using a very slow range-by-range scan
* and a packed range format.
*
* The ASCII portion (codepoints 0x00 ... 0x7f) is fast-pathed below because
* it matters the most. The ASCII related ranges of IdentifierStart are:
*
* 0x0041 ... 0x005a ['A' ... 'Z']
* 0x0061 ... 0x007a ['a' ... 'z']
* 0x0024 ['$']
* 0x005f ['_']
*/
/* ASCII (and EOF) fast path -- quick accept and reject */
if (cp <= 0x7fL) {
#if defined(DUK_USE_IDCHAR_FASTPATH)
return (cp >= 0) && (duk_is_idchar_tab[cp] > 0);
#else
if ((cp >= 'a' && cp <= 'z') ||
(cp >= 'A' && cp <= 'Z') ||
cp == '_' || cp == '$') {
return 1;
}
return 0;
#endif
}
/* Non-ASCII slow path (range-by-range linear comparison), very slow */
#ifdef DUK_USE_SOURCE_NONBMP
if (duk__uni_range_match(duk_unicode_ids_noa,
(duk_size_t) sizeof(duk_unicode_ids_noa),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
#else
if (cp < 0x10000L) {
if (duk__uni_range_match(duk_unicode_ids_noabmp,
sizeof(duk_unicode_ids_noabmp),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
} else {
/* without explicit non-BMP support, assume non-BMP characters
* are always accepted as identifier characters.
*/
return 1;
}
#endif
}
/*
* "IdentifierPart" production check.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_is_identifier_part(duk_codepoint_t cp) {
/*
* E5 Section 7.6:
*
* IdentifierPart:
* IdentifierStart
* UnicodeCombiningMark
* UnicodeDigit
* UnicodeConnectorPunctuation
* <ZWNJ> [U+200C]
* <ZWJ> [U+200D]
*
* IdentifierPart production has one multi-character production
* as part of its IdentifierStart alternative. The '\' character
* of an escape sequence is not matched here, see discussion in
* duk_unicode_is_identifier_start().
*
* To match non-ASCII characters (codepoints >= 0x80), a very slow
* linear range-by-range scan is used. The codepoint is first compared
* to the IdentifierStart ranges, and if it doesn't match, then to a
* set consisting of code points in IdentifierPart but not in
* IdentifierStart. This is done to keep the unicode range data small,
* at the expense of speed.
*
* The ASCII fast path consists of:
*
* 0x0030 ... 0x0039 ['0' ... '9', UnicodeDigit]
* 0x0041 ... 0x005a ['A' ... 'Z', IdentifierStart]
* 0x0061 ... 0x007a ['a' ... 'z', IdentifierStart]
* 0x0024 ['$', IdentifierStart]
* 0x005f ['_', IdentifierStart and
* UnicodeConnectorPunctuation]
*
* UnicodeCombiningMark has no code points <= 0x7f.
*
* The matching code reuses the "identifier start" tables, and then
* consults a separate range set for characters in "identifier part"
* but not in "identifier start". These can be extracted with the
* "src/extract_chars.py" script.
*
* UnicodeCombiningMark -> categories Mn, Mc
* UnicodeDigit -> categories Nd
* UnicodeConnectorPunctuation -> categories Pc
*/
/* ASCII (and EOF) fast path -- quick accept and reject */
if (cp <= 0x7fL) {
#if defined(DUK_USE_IDCHAR_FASTPATH)
return (cp >= 0) && (duk_is_idchar_tab[cp] != 0);
#else
if ((cp >= 'a' && cp <= 'z') ||
(cp >= 'A' && cp <= 'Z') ||
(cp >= '0' && cp <= '9') ||
cp == '_' || cp == '$') {
return 1;
}
return 0;
#endif
}
/* Non-ASCII slow path (range-by-range linear comparison), very slow */
#ifdef DUK_USE_SOURCE_NONBMP
if (duk__uni_range_match(duk_unicode_ids_noa,
sizeof(duk_unicode_ids_noa),
(duk_codepoint_t) cp) ||
duk__uni_range_match(duk_unicode_idp_m_ids_noa,
sizeof(duk_unicode_idp_m_ids_noa),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
#else
if (cp < 0x10000L) {
if (duk__uni_range_match(duk_unicode_ids_noabmp,
sizeof(duk_unicode_ids_noabmp),
(duk_codepoint_t) cp) ||
duk__uni_range_match(duk_unicode_idp_m_ids_noabmp,
sizeof(duk_unicode_idp_m_ids_noabmp),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
} else {
/* without explicit non-BMP support, assume non-BMP characters
* are always accepted as identifier characters.
*/
return 1;
}
#endif
}
/*
* Unicode letter check.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_is_letter(duk_codepoint_t cp) {
/*
* Unicode letter is now taken to be the categories:
*
* Lu, Ll, Lt, Lm, Lo
*
* (Not sure if this is exactly correct.)
*
* The ASCII fast path consists of:
*
* 0x0041 ... 0x005a ['A' ... 'Z']
* 0x0061 ... 0x007a ['a' ... 'z']
*/
/* ASCII (and EOF) fast path -- quick accept and reject */
if (cp <= 0x7fL) {
if ((cp >= 'a' && cp <= 'z') ||
(cp >= 'A' && cp <= 'Z')) {
return 1;
}
return 0;
}
/* Non-ASCII slow path (range-by-range linear comparison), very slow */
#ifdef DUK_USE_SOURCE_NONBMP
if (duk__uni_range_match(duk_unicode_ids_noa,
sizeof(duk_unicode_ids_noa),
(duk_codepoint_t) cp) &&
!duk__uni_range_match(duk_unicode_ids_m_let_noa,
sizeof(duk_unicode_ids_m_let_noa),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
#else
if (cp < 0x10000L) {
if (duk__uni_range_match(duk_unicode_ids_noabmp,
sizeof(duk_unicode_ids_noabmp),
(duk_codepoint_t) cp) &&
!duk__uni_range_match(duk_unicode_ids_m_let_noabmp,
sizeof(duk_unicode_ids_m_let_noabmp),
(duk_codepoint_t) cp)) {
return 1;
}
return 0;
} else {
/* without explicit non-BMP support, assume non-BMP characters
* are always accepted as letters.
*/
return 1;
}
#endif
}
/*
* Complex case conversion helper which decodes a bit-packed conversion
* control stream generated by unicode/extract_caseconv.py. The conversion
* is very slow because it runs through the conversion data in a linear
* fashion to save space (which is why ASCII characters have a special
* fast path before arriving here).
*
* The particular bit counts etc have been determined experimentally to
* be small but still sufficient, and must match the Python script
* (src/extract_caseconv.py).
*
* The return value is the case converted codepoint or -1 if the conversion
* results in multiple characters (this is useful for regexp Canonicalization
* operation). If 'buf' is not NULL, the result codepoint(s) are also
* appended to the hbuffer.
*
* Context and locale specific rules must be checked before consulting
* this function.
*/
DUK_LOCAL
duk_codepoint_t duk__slow_case_conversion(duk_hthread *thr,
duk_bufwriter_ctx *bw,
duk_codepoint_t cp,
duk_bitdecoder_ctx *bd_ctx) {
duk_small_int_t skip = 0;
duk_small_int_t n;
duk_small_int_t t;
duk_small_int_t count;
duk_codepoint_t tmp_cp;
duk_codepoint_t start_i;
duk_codepoint_t start_o;
DUK_UNREF(thr);
DUK_ASSERT(bd_ctx != NULL);
DUK_DDD(DUK_DDDPRINT("slow case conversion for codepoint: %ld", (long) cp));
/* range conversion with a "skip" */
DUK_DDD(DUK_DDDPRINT("checking ranges"));
for (;;) {
skip++;
n = (duk_small_int_t) duk_bd_decode(bd_ctx, 6);
if (n == 0x3f) {
/* end marker */
break;
}
DUK_DDD(DUK_DDDPRINT("skip=%ld, n=%ld", (long) skip, (long) n));
while (n--) {
start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
count = (duk_small_int_t) duk_bd_decode(bd_ctx, 7);
DUK_DDD(DUK_DDDPRINT("range: start_i=%ld, start_o=%ld, count=%ld, skip=%ld",
(long) start_i, (long) start_o, (long) count, (long) skip));
if (cp >= start_i) {
tmp_cp = cp - start_i; /* always >= 0 */
if (tmp_cp < (duk_codepoint_t) count * (duk_codepoint_t) skip &&
(tmp_cp % (duk_codepoint_t) skip) == 0) {
DUK_DDD(DUK_DDDPRINT("range matches input codepoint"));
cp = start_o + tmp_cp;
goto single;
}
}
}
}
/* 1:1 conversion */
n = (duk_small_int_t) duk_bd_decode(bd_ctx, 6);
DUK_DDD(DUK_DDDPRINT("checking 1:1 conversions (count %ld)", (long) n));
while (n--) {
start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
start_o = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
DUK_DDD(DUK_DDDPRINT("1:1 conversion %ld -> %ld", (long) start_i, (long) start_o));
if (cp == start_i) {
DUK_DDD(DUK_DDDPRINT("1:1 matches input codepoint"));
cp = start_o;
goto single;
}
}
/* complex, multicharacter conversion */
n = (duk_small_int_t) duk_bd_decode(bd_ctx, 7);
DUK_DDD(DUK_DDDPRINT("checking 1:n conversions (count %ld)", (long) n));
while (n--) {
start_i = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
t = (duk_small_int_t) duk_bd_decode(bd_ctx, 2);
DUK_DDD(DUK_DDDPRINT("1:n conversion %ld -> %ld chars", (long) start_i, (long) t));
if (cp == start_i) {
DUK_DDD(DUK_DDDPRINT("1:n matches input codepoint"));
if (bw != NULL) {
while (t--) {
tmp_cp = (duk_codepoint_t) duk_bd_decode(bd_ctx, 16);
DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) tmp_cp);
}
}
return -1;
} else {
while (t--) {
(void) duk_bd_decode(bd_ctx, 16);
}
}
}
/* default: no change */
DUK_DDD(DUK_DDDPRINT("no rule matches, output is same as input"));
/* fall through */
single:
if (bw != NULL) {
DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) cp);
}
return cp;
}
/*
* Case conversion helper, with context/local sensitivity.
* For proper case conversion, one needs to know the character
* and the preceding and following characters, as well as
* locale/language.
*/
/* XXX: add 'language' argument when locale/language sensitive rule
* support added.
*/
DUK_LOCAL
duk_codepoint_t duk__case_transform_helper(duk_hthread *thr,
duk_bufwriter_ctx *bw,
duk_codepoint_t cp,
duk_codepoint_t prev,
duk_codepoint_t next,
duk_bool_t uppercase) {
duk_bitdecoder_ctx bd_ctx;
/* fast path for ASCII */
if (cp < 0x80L) {
/* XXX: there are language sensitive rules for the ASCII range.
* If/when language/locale support is implemented, they need to
* be implemented here for the fast path. There are no context
* sensitive rules for ASCII range.
*/
if (uppercase) {
if (cp >= 'a' && cp <= 'z') {
cp = cp - 'a' + 'A';
}
} else {
if (cp >= 'A' && cp <= 'Z') {
cp = cp - 'A' + 'a';
}
}
if (bw != NULL) {
DUK_BW_WRITE_RAW_U8(thr, bw, (duk_uint8_t) cp);
}
return cp;
}
/* context and locale specific rules which cannot currently be represented
* in the caseconv bitstream: hardcoded rules in C
*/
if (uppercase) {
/* XXX: turkish / azeri */
} else {
/*
* Final sigma context specific rule. This is a rather tricky
* rule and this handling is probably not 100% correct now.
* The rule is not locale/language specific so it is supported.
*/
if (cp == 0x03a3L && /* U+03A3 = GREEK CAPITAL LETTER SIGMA */
duk_unicode_is_letter(prev) && /* prev exists and is not a letter */
!duk_unicode_is_letter(next)) { /* next does not exist or next is not a letter */
/* Capital sigma occurred at "end of word", lowercase to
* U+03C2 = GREEK SMALL LETTER FINAL SIGMA. Otherwise
* fall through and let the normal rules lowercase it to
* U+03C3 = GREEK SMALL LETTER SIGMA.
*/
cp = 0x03c2L;
goto singlechar;
}
/* XXX: lithuanian not implemented */
/* XXX: lithuanian, explicit dot rules */
/* XXX: turkish / azeri, lowercase rules */
}
/* 1:1 or special conversions, but not locale/context specific: script generated rules */
DUK_MEMZERO(&bd_ctx, sizeof(bd_ctx));
if (uppercase) {
bd_ctx.data = (const duk_uint8_t *) duk_unicode_caseconv_uc;
bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_uc);
} else {
bd_ctx.data = (const duk_uint8_t *) duk_unicode_caseconv_lc;
bd_ctx.length = (duk_size_t) sizeof(duk_unicode_caseconv_lc);
}
return duk__slow_case_conversion(thr, bw, cp, &bd_ctx);
singlechar:
if (bw != NULL) {
DUK_BW_WRITE_RAW_XUTF8(thr, bw, (duk_ucodepoint_t) cp);
}
return cp;
/* unused now, not needed until Turkish/Azeri */
#if 0
nochar:
return -1;
#endif
}
/*
* Replace valstack top with case converted version.
*/
DUK_INTERNAL void duk_unicode_case_convert_string(duk_hthread *thr, duk_small_int_t uppercase) {
duk_context *ctx = (duk_context *) thr;
duk_hstring *h_input;
duk_bufwriter_ctx bw_alloc;
duk_bufwriter_ctx *bw;
const duk_uint8_t *p, *p_start, *p_end;
duk_codepoint_t prev, curr, next;
h_input = duk_require_hstring(ctx, -1);
DUK_ASSERT(h_input != NULL);
bw = &bw_alloc;
DUK_BW_INIT_PUSHBUF(thr, bw, DUK_HSTRING_GET_BYTELEN(h_input));
/* [ ... input buffer ] */
p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input);
p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
p = p_start;
prev = -1; DUK_UNREF(prev);
curr = -1;
next = -1;
for (;;) {
prev = curr;
curr = next;
next = -1;
if (p < p_end) {
next = (int) duk_unicode_decode_xutf8_checked(thr, &p, p_start, p_end);
} else {
/* end of input and last char has been processed */
if (curr < 0) {
break;
}
}
/* on first round, skip */
if (curr >= 0) {
/* XXX: could add a fast path to process chunks of input codepoints,
* but relative benefit would be quite small.
*/
/* Ensure space for maximum multi-character result; estimate is overkill. */
DUK_BW_ENSURE(thr, bw, 8 * DUK_UNICODE_MAX_XUTF8_LENGTH);
duk__case_transform_helper(thr,
bw,
(duk_codepoint_t) curr,
prev,
next,
uppercase);
}
}
DUK_BW_COMPACT(thr, bw);
duk_to_string(ctx, -1); /* invalidates h_buf pointer */
duk_remove(ctx, -2);
}
#ifdef DUK_USE_REGEXP_SUPPORT
/*
* Canonicalize() abstract operation needed for canonicalization of individual
* codepoints during regexp compilation and execution, see E5 Section 15.10.2.8.
* Note that codepoints are canonicalized one character at a time, so no context
* specific rules can apply. Locale specific rules can apply, though.
*/
DUK_INTERNAL duk_codepoint_t duk_unicode_re_canonicalize_char(duk_hthread *thr, duk_codepoint_t cp) {
#if defined(DUK_USE_REGEXP_CANON_WORKAROUND)
/* Fast canonicalization lookup at the cost of 128kB footprint. */
DUK_ASSERT(cp >= 0);
DUK_UNREF(thr);
if (DUK_LIKELY(cp < 0x10000L)) {
return (duk_codepoint_t) duk_unicode_re_canon_lookup[cp];
}
return cp;
#else /* DUK_USE_REGEXP_CANON_WORKAROUND */
duk_codepoint_t y;
y = duk__case_transform_helper(thr,
NULL, /* NULL is allowed, no output */
cp, /* curr char */
-1, /* prev char */
-1, /* next char */
1); /* uppercase */
if ((y < 0) || (cp >= 0x80 && y < 0x80)) {
/* multiple codepoint conversion or non-ASCII mapped to ASCII
* --> leave as is.
*/
return cp;
}
return y;
#endif /* DUK_USE_REGEXP_CANON_WORKAROUND */
}
/*
* E5 Section 15.10.2.6 "IsWordChar" abstract operation. Assume
* x < 0 for characters read outside the string.
*/
DUK_INTERNAL duk_small_int_t duk_unicode_re_is_wordchar(duk_codepoint_t x) {
/*
* Note: the description in E5 Section 15.10.2.6 has a typo, it
* contains 'A' twice and lacks 'a'; the intent is [0-9a-zA-Z_].
*/
if ((x >= '0' && x <= '9') ||
(x >= 'a' && x <= 'z') ||
(x >= 'A' && x <= 'Z') ||
(x == '_')) {
return 1;
}
return 0;
}
/*
* Regexp range tables
*/
/* exposed because lexer needs these too */
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_digit[2] = {
(duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL,
};
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_white[22] = {
(duk_uint16_t) 0x0009UL, (duk_uint16_t) 0x000DUL,
(duk_uint16_t) 0x0020UL, (duk_uint16_t) 0x0020UL,
(duk_uint16_t) 0x00A0UL, (duk_uint16_t) 0x00A0UL,
(duk_uint16_t) 0x1680UL, (duk_uint16_t) 0x1680UL,
(duk_uint16_t) 0x180EUL, (duk_uint16_t) 0x180EUL,
(duk_uint16_t) 0x2000UL, (duk_uint16_t) 0x200AUL,
(duk_uint16_t) 0x2028UL, (duk_uint16_t) 0x2029UL,
(duk_uint16_t) 0x202FUL, (duk_uint16_t) 0x202FUL,
(duk_uint16_t) 0x205FUL, (duk_uint16_t) 0x205FUL,
(duk_uint16_t) 0x3000UL, (duk_uint16_t) 0x3000UL,
(duk_uint16_t) 0xFEFFUL, (duk_uint16_t) 0xFEFFUL,
};
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_wordchar[8] = {
(duk_uint16_t) 0x0030UL, (duk_uint16_t) 0x0039UL,
(duk_uint16_t) 0x0041UL, (duk_uint16_t) 0x005AUL,
(duk_uint16_t) 0x005FUL, (duk_uint16_t) 0x005FUL,
(duk_uint16_t) 0x0061UL, (duk_uint16_t) 0x007AUL,
};
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_digit[4] = {
(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL,
(duk_uint16_t) 0x003AUL, (duk_uint16_t) 0xFFFFUL,
};
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_white[24] = {
(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x0008UL,
(duk_uint16_t) 0x000EUL, (duk_uint16_t) 0x001FUL,
(duk_uint16_t) 0x0021UL, (duk_uint16_t) 0x009FUL,
(duk_uint16_t) 0x00A1UL, (duk_uint16_t) 0x167FUL,
(duk_uint16_t) 0x1681UL, (duk_uint16_t) 0x180DUL,
(duk_uint16_t) 0x180FUL, (duk_uint16_t) 0x1FFFUL,
(duk_uint16_t) 0x200BUL, (duk_uint16_t) 0x2027UL,
(duk_uint16_t) 0x202AUL, (duk_uint16_t) 0x202EUL,
(duk_uint16_t) 0x2030UL, (duk_uint16_t) 0x205EUL,
(duk_uint16_t) 0x2060UL, (duk_uint16_t) 0x2FFFUL,
(duk_uint16_t) 0x3001UL, (duk_uint16_t) 0xFEFEUL,
(duk_uint16_t) 0xFF00UL, (duk_uint16_t) 0xFFFFUL,
};
DUK_INTERNAL const duk_uint16_t duk_unicode_re_ranges_not_wordchar[10] = {
(duk_uint16_t) 0x0000UL, (duk_uint16_t) 0x002FUL,
(duk_uint16_t) 0x003AUL, (duk_uint16_t) 0x0040UL,
(duk_uint16_t) 0x005BUL, (duk_uint16_t) 0x005EUL,
(duk_uint16_t) 0x0060UL, (duk_uint16_t) 0x0060UL,
(duk_uint16_t) 0x007BUL, (duk_uint16_t) 0xFFFFUL,
};
#endif /* DUK_USE_REGEXP_SUPPORT */