| /* |
| * Regexp compilation. |
| * |
| * See doc/regexp.rst for a discussion of the compilation approach and |
| * current limitations. |
| * |
| * Regexp bytecode assumes jumps can be expressed with signed 32-bit |
| * integers. Consequently the bytecode size must not exceed 0x7fffffffL. |
| * The implementation casts duk_size_t (buffer size) to duk_(u)int32_t |
| * in many places. Although this could be changed, the bytecode format |
| * limit would still prevent regexps exceeding the signed 32-bit limit |
| * from working. |
| * |
| * XXX: The implementation does not prevent bytecode from exceeding the |
| * maximum supported size. This could be done by limiting the maximum |
| * input string size (assuming an upper bound can be computed for number |
| * of bytecode bytes emitted per input byte) or checking buffer maximum |
| * size when emitting bytecode (slower). |
| */ |
| |
| #include "duk_internal.h" |
| |
| #ifdef DUK_USE_REGEXP_SUPPORT |
| |
| /* |
| * Helper macros |
| */ |
| |
| #define DUK__RE_INITIAL_BUFSIZE 64 |
| |
| #undef DUK__RE_BUFLEN |
| #define DUK__RE_BUFLEN(re_ctx) \ |
| DUK_BW_GET_SIZE(re_ctx->thr, &re_ctx->bw) |
| |
| /* |
| * Disjunction struct: result of parsing a disjunction |
| */ |
| |
| typedef struct { |
| /* Number of characters that the atom matches (e.g. 3 for 'abc'), |
| * -1 if atom is complex and number of matched characters either |
| * varies or is not known. |
| */ |
| duk_int32_t charlen; |
| |
| #if 0 |
| /* These are not needed to implement quantifier capture handling, |
| * but might be needed at some point. |
| */ |
| |
| /* re_ctx->captures at start and end of atom parsing. |
| * Since 'captures' indicates highest capture number emitted |
| * so far in a DUK_REOP_SAVE, the captures numbers saved by |
| * the atom are: ]start_captures,end_captures]. |
| */ |
| duk_uint32_t start_captures; |
| duk_uint32_t end_captures; |
| #endif |
| } duk__re_disjunction_info; |
| |
| /* |
| * Encoding helpers |
| * |
| * Some of the typing is bytecode based, e.g. slice sizes are unsigned 32-bit |
| * even though the buffer operations will use duk_size_t. |
| */ |
| |
| /* XXX: the insert helpers should ensure that the bytecode result is not |
| * larger than expected (or at least assert for it). Many things in the |
| * bytecode, like skip offsets, won't work correctly if the bytecode is |
| * larger than say 2G. |
| */ |
| |
| DUK_LOCAL duk_uint32_t duk__encode_i32(duk_int32_t x) { |
| if (x < 0) { |
| return ((duk_uint32_t) (-x)) * 2 + 1; |
| } else { |
| return ((duk_uint32_t) x) * 2; |
| } |
| } |
| |
| /* XXX: return type should probably be duk_size_t, or explicit checks are needed for |
| * maximum size. |
| */ |
| DUK_LOCAL duk_uint32_t duk__insert_u32(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_uint32_t x) { |
| duk_uint8_t buf[DUK_UNICODE_MAX_XUTF8_LENGTH]; |
| duk_small_int_t len; |
| |
| len = duk_unicode_encode_xutf8((duk_ucodepoint_t) x, buf); |
| DUK_BW_INSERT_ENSURE_BYTES(re_ctx->thr, &re_ctx->bw, offset, buf, len); |
| return (duk_uint32_t) len; |
| } |
| |
| DUK_LOCAL duk_uint32_t duk__append_u32(duk_re_compiler_ctx *re_ctx, duk_uint32_t x) { |
| duk_uint8_t buf[DUK_UNICODE_MAX_XUTF8_LENGTH]; |
| duk_small_int_t len; |
| |
| len = duk_unicode_encode_xutf8((duk_ucodepoint_t) x, buf); |
| DUK_BW_WRITE_ENSURE_BYTES(re_ctx->thr, &re_ctx->bw, buf, len); |
| return (duk_uint32_t) len; |
| } |
| |
| DUK_LOCAL duk_uint32_t duk__insert_i32(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_int32_t x) { |
| return duk__insert_u32(re_ctx, offset, duk__encode_i32(x)); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_uint32_t duk__append_i32(duk_re_compiler_ctx *re_ctx, duk_int32_t x) { |
| return duk__append_u32(re_ctx, duk__encode_i32(x)); |
| } |
| #endif |
| |
| /* special helper for emitting u16 lists (used for character ranges for built-in char classes) */ |
| DUK_LOCAL void duk__append_u16_list(duk_re_compiler_ctx *re_ctx, const duk_uint16_t *values, duk_uint32_t count) { |
| /* Call sites don't need the result length so it's not accumulated. */ |
| while (count > 0) { |
| (void) duk__append_u32(re_ctx, (duk_uint32_t) (*values++)); |
| count--; |
| } |
| } |
| |
| DUK_LOCAL void duk__insert_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_uint32_t data_offset, duk_uint32_t data_length) { |
| DUK_BW_INSERT_ENSURE_SLICE(re_ctx->thr, &re_ctx->bw, offset, data_offset, data_length); |
| } |
| |
| DUK_LOCAL void duk__append_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t data_offset, duk_uint32_t data_length) { |
| DUK_BW_WRITE_ENSURE_SLICE(re_ctx->thr, &re_ctx->bw, data_offset, data_length); |
| } |
| |
| DUK_LOCAL void duk__remove_slice(duk_re_compiler_ctx *re_ctx, duk_uint32_t data_offset, duk_uint32_t data_length) { |
| DUK_BW_REMOVE_ENSURE_SLICE(re_ctx->thr, &re_ctx->bw, data_offset, data_length); |
| } |
| |
| /* |
| * Insert a jump offset at 'offset' to complete an instruction |
| * (the jump offset is always the last component of an instruction). |
| * The 'skip' argument must be computed relative to 'offset', |
| * -without- taking into account the skip field being inserted. |
| * |
| * ... A B C ins X Y Z ... (ins may be a JUMP, SPLIT1/SPLIT2, etc) |
| * => ... A B C ins SKIP X Y Z |
| * |
| * Computing the final (adjusted) skip value, which is relative to the |
| * first byte of the next instruction, is a bit tricky because of the |
| * variable length UTF-8 encoding. See doc/regexp.rst for discussion. |
| */ |
| DUK_LOCAL duk_uint32_t duk__insert_jump_offset(duk_re_compiler_ctx *re_ctx, duk_uint32_t offset, duk_int32_t skip) { |
| duk_small_int_t len; |
| |
| /* XXX: solve into closed form (smaller code) */ |
| |
| if (skip < 0) { |
| /* two encoding attempts suffices */ |
| len = duk_unicode_get_xutf8_length((duk_codepoint_t) duk__encode_i32(skip)); |
| len = duk_unicode_get_xutf8_length((duk_codepoint_t) duk__encode_i32(skip - (duk_int32_t) len)); |
| DUK_ASSERT(duk_unicode_get_xutf8_length(duk__encode_i32(skip - (duk_int32_t) len)) == len); /* no change */ |
| skip -= (duk_int32_t) len; |
| } |
| return duk__insert_i32(re_ctx, offset, skip); |
| } |
| |
| DUK_LOCAL duk_uint32_t duk__append_jump_offset(duk_re_compiler_ctx *re_ctx, duk_int32_t skip) { |
| return (duk_uint32_t) duk__insert_jump_offset(re_ctx, (duk_uint32_t) DUK__RE_BUFLEN(re_ctx), skip); |
| } |
| |
| /* |
| * duk_re_range_callback for generating character class ranges. |
| * |
| * When ignoreCase is false, the range is simply emitted as is. |
| * We don't, for instance, eliminate duplicates or overlapping |
| * ranges in a character class. |
| * |
| * When ignoreCase is true, the range needs to be normalized through |
| * canonicalization. Unfortunately a canonicalized version of a |
| * continuous range is not necessarily continuous (e.g. [x-{] is |
| * continuous but [X-{] is not). The current algorithm creates the |
| * canonicalized range(s) space efficiently at the cost of compile |
| * time execution time (see doc/regexp.rst for discussion). |
| * |
| * Note that the ctx->nranges is a context-wide temporary value |
| * (this is OK because there cannot be multiple character classes |
| * being parsed simultaneously). |
| */ |
| |
| DUK_LOCAL void duk__generate_ranges(void *userdata, duk_codepoint_t r1, duk_codepoint_t r2, duk_bool_t direct) { |
| duk_re_compiler_ctx *re_ctx = (duk_re_compiler_ctx *) userdata; |
| |
| DUK_DD(DUK_DDPRINT("duk__generate_ranges(): re_ctx=%p, range=[%ld,%ld] direct=%ld", |
| (void *) re_ctx, (long) r1, (long) r2, (long) direct)); |
| |
| if (!direct && (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE)) { |
| /* |
| * Canonicalize a range, generating result ranges as necessary. |
| * Needs to exhaustively scan the entire range (at most 65536 |
| * code points). If 'direct' is set, caller (lexer) has ensured |
| * that the range is already canonicalization compatible (this |
| * is used to avoid unnecessary canonicalization of built-in |
| * ranges like \W, which are not affected by canonicalization). |
| * |
| * NOTE: here is one place where we don't want to support chars |
| * outside the BMP, because the exhaustive search would be |
| * massively larger. |
| */ |
| |
| duk_codepoint_t i; |
| duk_codepoint_t t; |
| duk_codepoint_t r_start, r_end; |
| |
| r_start = duk_unicode_re_canonicalize_char(re_ctx->thr, r1); |
| r_end = r_start; |
| for (i = r1 + 1; i <= r2; i++) { |
| t = duk_unicode_re_canonicalize_char(re_ctx->thr, i); |
| if (t == r_end + 1) { |
| r_end = t; |
| } else { |
| DUK_DD(DUK_DDPRINT("canonicalized, emit range: [%ld,%ld]", (long) r_start, (long) r_end)); |
| duk__append_u32(re_ctx, (duk_uint32_t) r_start); |
| duk__append_u32(re_ctx, (duk_uint32_t) r_end); |
| re_ctx->nranges++; |
| r_start = t; |
| r_end = t; |
| } |
| } |
| DUK_DD(DUK_DDPRINT("canonicalized, emit range: [%ld,%ld]", (long) r_start, (long) r_end)); |
| duk__append_u32(re_ctx, (duk_uint32_t) r_start); |
| duk__append_u32(re_ctx, (duk_uint32_t) r_end); |
| re_ctx->nranges++; |
| } else { |
| DUK_DD(DUK_DDPRINT("direct, emit range: [%ld,%ld]", (long) r1, (long) r2)); |
| duk__append_u32(re_ctx, (duk_uint32_t) r1); |
| duk__append_u32(re_ctx, (duk_uint32_t) r2); |
| re_ctx->nranges++; |
| } |
| } |
| |
| /* |
| * Parse regexp Disjunction. Most of regexp compilation happens here. |
| * |
| * Handles Disjunction, Alternative, and Term productions directly without |
| * recursion. The only constructs requiring recursion are positive/negative |
| * lookaheads, capturing parentheses, and non-capturing parentheses. |
| * |
| * The function determines whether the entire disjunction is a 'simple atom' |
| * (see doc/regexp.rst discussion on 'simple quantifiers') and if so, |
| * returns the atom character length which is needed by the caller to keep |
| * track of its own atom character length. A disjunction with more than one |
| * alternative is never considered a simple atom (although in some cases |
| * that might be the case). |
| * |
| * Return value: simple atom character length or < 0 if not a simple atom. |
| * Appends the bytecode for the disjunction matcher to the end of the temp |
| * buffer. |
| * |
| * Regexp top level structure is: |
| * |
| * Disjunction = Term* |
| * | Term* | Disjunction |
| * |
| * Term = Assertion |
| * | Atom |
| * | Atom Quantifier |
| * |
| * An empty Term sequence is a valid disjunction alternative (e.g. /|||c||/). |
| * |
| * Notes: |
| * |
| * * Tracking of the 'simple-ness' of the current atom vs. the entire |
| * disjunction are separate matters. For instance, the disjunction |
| * may be complex, but individual atoms may be simple. Furthermore, |
| * simple quantifiers are used whenever possible, even if the |
| * disjunction as a whole is complex. |
| * |
| * * The estimate of whether an atom is simple is conservative now, |
| * and it would be possible to expand it. For instance, captures |
| * cause the disjunction to be marked complex, even though captures |
| * -can- be handled by simple quantifiers with some minor modifications. |
| * |
| * * Disjunction 'tainting' as 'complex' is handled at the end of the |
| * main for loop collectively for atoms. Assertions, quantifiers, |
| * and '|' tokens need to taint the result manually if necessary. |
| * Assertions cannot add to result char length, only atoms (and |
| * quantifiers) can; currently quantifiers will taint the result |
| * as complex though. |
| */ |
| |
| DUK_LOCAL void duk__parse_disjunction(duk_re_compiler_ctx *re_ctx, duk_bool_t expect_eof, duk__re_disjunction_info *out_atom_info) { |
| duk_int32_t atom_start_offset = -1; /* negative -> no atom matched on previous round */ |
| duk_int32_t atom_char_length = 0; /* negative -> complex atom */ |
| duk_uint32_t atom_start_captures = re_ctx->captures; /* value of re_ctx->captures at start of atom */ |
| duk_int32_t unpatched_disjunction_split = -1; |
| duk_int32_t unpatched_disjunction_jump = -1; |
| duk_uint32_t entry_offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); |
| duk_int32_t res_charlen = 0; /* -1 if disjunction is complex, char length if simple */ |
| duk__re_disjunction_info tmp_disj; |
| |
| DUK_ASSERT(out_atom_info != NULL); |
| |
| if (re_ctx->recursion_depth >= re_ctx->recursion_limit) { |
| DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_REGEXP_COMPILER_RECURSION_LIMIT); |
| } |
| re_ctx->recursion_depth++; |
| |
| #if 0 |
| out_atom_info->start_captures = re_ctx->captures; |
| #endif |
| |
| for (;;) { |
| /* atom_char_length, atom_start_offset, atom_start_offset reflect the |
| * atom matched on the previous loop. If a quantifier is encountered |
| * on this loop, these are needed to handle the quantifier correctly. |
| * new_atom_char_length etc are for the atom parsed on this round; |
| * they're written to atom_char_length etc at the end of the round. |
| */ |
| duk_int32_t new_atom_char_length; /* char length of the atom parsed in this loop */ |
| duk_int32_t new_atom_start_offset; /* bytecode start offset of the atom parsed in this loop |
| * (allows quantifiers to copy the atom bytecode) |
| */ |
| duk_uint32_t new_atom_start_captures; /* re_ctx->captures at the start of the atom parsed in this loop */ |
| |
| duk_lexer_parse_re_token(&re_ctx->lex, &re_ctx->curr_token); |
| |
| DUK_DD(DUK_DDPRINT("re token: %ld (num=%ld, char=%c)", |
| (long) re_ctx->curr_token.t, |
| (long) re_ctx->curr_token.num, |
| (re_ctx->curr_token.num >= 0x20 && re_ctx->curr_token.num <= 0x7e) ? |
| (int) re_ctx->curr_token.num : (int) '?')); |
| |
| /* set by atom case clauses */ |
| new_atom_start_offset = -1; |
| new_atom_char_length = -1; |
| new_atom_start_captures = re_ctx->captures; |
| |
| switch (re_ctx->curr_token.t) { |
| case DUK_RETOK_DISJUNCTION: { |
| /* |
| * The handling here is a bit tricky. If a previous '|' has been processed, |
| * we have a pending split1 and a pending jump (for a previous match). These |
| * need to be back-patched carefully. See docs for a detailed example. |
| */ |
| |
| /* patch pending jump and split */ |
| if (unpatched_disjunction_jump >= 0) { |
| duk_uint32_t offset; |
| |
| DUK_ASSERT(unpatched_disjunction_split >= 0); |
| offset = unpatched_disjunction_jump; |
| offset += duk__insert_jump_offset(re_ctx, |
| offset, |
| (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - offset)); |
| /* offset is now target of the pending split (right after jump) */ |
| duk__insert_jump_offset(re_ctx, |
| unpatched_disjunction_split, |
| offset - unpatched_disjunction_split); |
| } |
| |
| /* add a new pending split to the beginning of the entire disjunction */ |
| (void) duk__insert_u32(re_ctx, |
| entry_offset, |
| DUK_REOP_SPLIT1); /* prefer direct execution */ |
| unpatched_disjunction_split = entry_offset + 1; /* +1 for opcode */ |
| |
| /* add a new pending match jump for latest finished alternative */ |
| duk__append_u32(re_ctx, DUK_REOP_JUMP); |
| unpatched_disjunction_jump = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| |
| /* 'taint' result as complex */ |
| res_charlen = -1; |
| break; |
| } |
| case DUK_RETOK_QUANTIFIER: { |
| if (atom_start_offset < 0) { |
| DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_INVALID_QUANTIFIER_NO_ATOM); |
| } |
| if (re_ctx->curr_token.qmin > re_ctx->curr_token.qmax) { |
| DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_INVALID_QUANTIFIER_VALUES); |
| } |
| if (atom_char_length >= 0) { |
| /* |
| * Simple atom |
| * |
| * If atom_char_length is zero, we'll have unbounded execution time for e.g. |
| * /()*x/.exec('x'). We can't just skip the match because it might have some |
| * side effects (for instance, if we allowed captures in simple atoms, the |
| * capture needs to happen). The simple solution below is to force the |
| * quantifier to match at most once, since the additional matches have no effect. |
| * |
| * With a simple atom there can be no capture groups, so no captures need |
| * to be reset. |
| */ |
| duk_int32_t atom_code_length; |
| duk_uint32_t offset; |
| duk_uint32_t qmin, qmax; |
| |
| qmin = re_ctx->curr_token.qmin; |
| qmax = re_ctx->curr_token.qmax; |
| if (atom_char_length == 0) { |
| /* qmin and qmax will be 0 or 1 */ |
| if (qmin > 1) { |
| qmin = 1; |
| } |
| if (qmax > 1) { |
| qmax = 1; |
| } |
| } |
| |
| duk__append_u32(re_ctx, DUK_REOP_MATCH); /* complete 'sub atom' */ |
| atom_code_length = (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - atom_start_offset); |
| |
| offset = atom_start_offset; |
| if (re_ctx->curr_token.greedy) { |
| offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQGREEDY); |
| offset += duk__insert_u32(re_ctx, offset, qmin); |
| offset += duk__insert_u32(re_ctx, offset, qmax); |
| offset += duk__insert_u32(re_ctx, offset, atom_char_length); |
| offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length); |
| } else { |
| offset += duk__insert_u32(re_ctx, offset, DUK_REOP_SQMINIMAL); |
| offset += duk__insert_u32(re_ctx, offset, qmin); |
| offset += duk__insert_u32(re_ctx, offset, qmax); |
| offset += duk__insert_jump_offset(re_ctx, offset, atom_code_length); |
| } |
| DUK_UNREF(offset); /* silence scan-build warning */ |
| } else { |
| /* |
| * Complex atom |
| * |
| * The original code is used as a template, and removed at the end |
| * (this differs from the handling of simple quantifiers). |
| * |
| * NOTE: there is no current solution for empty atoms in complex |
| * quantifiers. This would need some sort of a 'progress' instruction. |
| * |
| * XXX: impose limit on maximum result size, i.e. atom_code_len * atom_copies? |
| */ |
| duk_int32_t atom_code_length; |
| duk_uint32_t atom_copies; |
| duk_uint32_t tmp_qmin, tmp_qmax; |
| |
| /* pre-check how many atom copies we're willing to make (atom_copies not needed below) */ |
| atom_copies = (re_ctx->curr_token.qmax == DUK_RE_QUANTIFIER_INFINITE) ? |
| re_ctx->curr_token.qmin : re_ctx->curr_token.qmax; |
| if (atom_copies > DUK_RE_MAX_ATOM_COPIES) { |
| DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_QUANTIFIER_TOO_MANY_COPIES); |
| } |
| |
| /* wipe the capture range made by the atom (if any) */ |
| DUK_ASSERT(atom_start_captures <= re_ctx->captures); |
| if (atom_start_captures != re_ctx->captures) { |
| DUK_ASSERT(atom_start_captures < re_ctx->captures); |
| DUK_DDD(DUK_DDDPRINT("must wipe ]atom_start_captures,re_ctx->captures]: ]%ld,%ld]", |
| (long) atom_start_captures, (long) re_ctx->captures)); |
| |
| /* insert (DUK_REOP_WIPERANGE, start, count) in reverse order so the order ends up right */ |
| duk__insert_u32(re_ctx, atom_start_offset, (re_ctx->captures - atom_start_captures) * 2); |
| duk__insert_u32(re_ctx, atom_start_offset, (atom_start_captures + 1) * 2); |
| duk__insert_u32(re_ctx, atom_start_offset, DUK_REOP_WIPERANGE); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("no need to wipe captures: atom_start_captures == re_ctx->captures == %ld", |
| (long) atom_start_captures)); |
| } |
| |
| atom_code_length = (duk_int32_t) DUK__RE_BUFLEN(re_ctx) - atom_start_offset; |
| |
| /* insert the required matches (qmin) by copying the atom */ |
| tmp_qmin = re_ctx->curr_token.qmin; |
| tmp_qmax = re_ctx->curr_token.qmax; |
| while (tmp_qmin > 0) { |
| duk__append_slice(re_ctx, atom_start_offset, atom_code_length); |
| tmp_qmin--; |
| if (tmp_qmax != DUK_RE_QUANTIFIER_INFINITE) { |
| tmp_qmax--; |
| } |
| } |
| DUK_ASSERT(tmp_qmin == 0); |
| |
| /* insert code for matching the remainder - infinite or finite */ |
| if (tmp_qmax == DUK_RE_QUANTIFIER_INFINITE) { |
| /* reuse last emitted atom for remaining 'infinite' quantifier */ |
| |
| if (re_ctx->curr_token.qmin == 0) { |
| /* Special case: original qmin was zero so there is nothing |
| * to repeat. Emit an atom copy but jump over it here. |
| */ |
| duk__append_u32(re_ctx, DUK_REOP_JUMP); |
| duk__append_jump_offset(re_ctx, atom_code_length); |
| duk__append_slice(re_ctx, atom_start_offset, atom_code_length); |
| } |
| if (re_ctx->curr_token.greedy) { |
| duk__append_u32(re_ctx, DUK_REOP_SPLIT2); /* prefer jump */ |
| } else { |
| duk__append_u32(re_ctx, DUK_REOP_SPLIT1); /* prefer direct */ |
| } |
| duk__append_jump_offset(re_ctx, -atom_code_length - 1); /* -1 for opcode */ |
| } else { |
| /* |
| * The remaining matches are emitted as sequence of SPLITs and atom |
| * copies; the SPLITs skip the remaining copies and match the sequel. |
| * This sequence needs to be emitted starting from the last copy |
| * because the SPLITs are variable length due to the variable length |
| * skip offset. This causes a lot of memory copying now. |
| * |
| * Example structure (greedy, match maximum # atoms): |
| * |
| * SPLIT1 LSEQ |
| * (atom) |
| * SPLIT1 LSEQ ; <- the byte length of this instruction is needed |
| * (atom) ; to encode the above SPLIT1 correctly |
| * ... |
| * LSEQ: |
| */ |
| duk_uint32_t offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); |
| while (tmp_qmax > 0) { |
| duk__insert_slice(re_ctx, offset, atom_start_offset, atom_code_length); |
| if (re_ctx->curr_token.greedy) { |
| duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT1); /* prefer direct */ |
| } else { |
| duk__insert_u32(re_ctx, offset, DUK_REOP_SPLIT2); /* prefer jump */ |
| } |
| duk__insert_jump_offset(re_ctx, |
| offset + 1, /* +1 for opcode */ |
| (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - (offset + 1))); |
| tmp_qmax--; |
| } |
| } |
| |
| /* remove the original 'template' atom */ |
| duk__remove_slice(re_ctx, atom_start_offset, atom_code_length); |
| } |
| |
| /* 'taint' result as complex */ |
| res_charlen = -1; |
| break; |
| } |
| case DUK_RETOK_ASSERT_START: { |
| duk__append_u32(re_ctx, DUK_REOP_ASSERT_START); |
| break; |
| } |
| case DUK_RETOK_ASSERT_END: { |
| duk__append_u32(re_ctx, DUK_REOP_ASSERT_END); |
| break; |
| } |
| case DUK_RETOK_ASSERT_WORD_BOUNDARY: { |
| duk__append_u32(re_ctx, DUK_REOP_ASSERT_WORD_BOUNDARY); |
| break; |
| } |
| case DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY: { |
| duk__append_u32(re_ctx, DUK_REOP_ASSERT_NOT_WORD_BOUNDARY); |
| break; |
| } |
| case DUK_RETOK_ASSERT_START_POS_LOOKAHEAD: |
| case DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD: { |
| duk_uint32_t offset; |
| duk_uint32_t opcode = (re_ctx->curr_token.t == DUK_RETOK_ASSERT_START_POS_LOOKAHEAD) ? |
| DUK_REOP_LOOKPOS : DUK_REOP_LOOKNEG; |
| |
| offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__parse_disjunction(re_ctx, 0, &tmp_disj); |
| duk__append_u32(re_ctx, DUK_REOP_MATCH); |
| |
| (void) duk__insert_u32(re_ctx, offset, opcode); |
| (void) duk__insert_jump_offset(re_ctx, |
| offset + 1, /* +1 for opcode */ |
| (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - (offset + 1))); |
| |
| /* 'taint' result as complex -- this is conservative, |
| * as lookaheads do not backtrack. |
| */ |
| res_charlen = -1; |
| break; |
| } |
| case DUK_RETOK_ATOM_PERIOD: { |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, DUK_REOP_PERIOD); |
| break; |
| } |
| case DUK_RETOK_ATOM_CHAR: { |
| /* Note: successive characters could be joined into string matches |
| * but this is not trivial (consider e.g. '/xyz+/); see docs for |
| * more discussion. |
| */ |
| duk_uint32_t ch; |
| |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, DUK_REOP_CHAR); |
| ch = re_ctx->curr_token.num; |
| if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) { |
| ch = duk_unicode_re_canonicalize_char(re_ctx->thr, ch); |
| } |
| duk__append_u32(re_ctx, ch); |
| break; |
| } |
| case DUK_RETOK_ATOM_DIGIT: |
| case DUK_RETOK_ATOM_NOT_DIGIT: { |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, |
| (re_ctx->curr_token.t == DUK_RETOK_ATOM_DIGIT) ? |
| DUK_REOP_RANGES : DUK_REOP_INVRANGES); |
| duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_digit) / (2 * sizeof(duk_uint16_t))); |
| duk__append_u16_list(re_ctx, duk_unicode_re_ranges_digit, sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_uint16_t)); |
| break; |
| } |
| case DUK_RETOK_ATOM_WHITE: |
| case DUK_RETOK_ATOM_NOT_WHITE: { |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, |
| (re_ctx->curr_token.t == DUK_RETOK_ATOM_WHITE) ? |
| DUK_REOP_RANGES : DUK_REOP_INVRANGES); |
| duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_white) / (2 * sizeof(duk_uint16_t))); |
| duk__append_u16_list(re_ctx, duk_unicode_re_ranges_white, sizeof(duk_unicode_re_ranges_white) / sizeof(duk_uint16_t)); |
| break; |
| } |
| case DUK_RETOK_ATOM_WORD_CHAR: |
| case DUK_RETOK_ATOM_NOT_WORD_CHAR: { |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, |
| (re_ctx->curr_token.t == DUK_RETOK_ATOM_WORD_CHAR) ? |
| DUK_REOP_RANGES : DUK_REOP_INVRANGES); |
| duk__append_u32(re_ctx, sizeof(duk_unicode_re_ranges_wordchar) / (2 * sizeof(duk_uint16_t))); |
| duk__append_u16_list(re_ctx, duk_unicode_re_ranges_wordchar, sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_uint16_t)); |
| break; |
| } |
| case DUK_RETOK_ATOM_BACKREFERENCE: { |
| duk_uint32_t backref = (duk_uint32_t) re_ctx->curr_token.num; |
| if (backref > re_ctx->highest_backref) { |
| re_ctx->highest_backref = backref; |
| } |
| new_atom_char_length = -1; /* mark as complex */ |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, DUK_REOP_BACKREFERENCE); |
| duk__append_u32(re_ctx, backref); |
| break; |
| } |
| case DUK_RETOK_ATOM_START_CAPTURE_GROUP: { |
| duk_uint32_t cap; |
| |
| new_atom_char_length = -1; /* mark as complex (capture handling) */ |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| cap = ++re_ctx->captures; |
| duk__append_u32(re_ctx, DUK_REOP_SAVE); |
| duk__append_u32(re_ctx, cap * 2); |
| duk__parse_disjunction(re_ctx, 0, &tmp_disj); /* retval (sub-atom char length) unused, tainted as complex above */ |
| duk__append_u32(re_ctx, DUK_REOP_SAVE); |
| duk__append_u32(re_ctx, cap * 2 + 1); |
| break; |
| } |
| case DUK_RETOK_ATOM_START_NONCAPTURE_GROUP: { |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__parse_disjunction(re_ctx, 0, &tmp_disj); |
| new_atom_char_length = tmp_disj.charlen; |
| break; |
| } |
| case DUK_RETOK_ATOM_START_CHARCLASS: |
| case DUK_RETOK_ATOM_START_CHARCLASS_INVERTED: { |
| /* |
| * Range parsing is done with a special lexer function which calls |
| * us for every range parsed. This is different from how rest of |
| * the parsing works, but avoids a heavy, arbitrary size intermediate |
| * value type to hold the ranges. |
| * |
| * Another complication is the handling of character ranges when |
| * case insensitive matching is used (see docs for discussion). |
| * The range handler callback given to the lexer takes care of this |
| * as well. |
| * |
| * Note that duplicate ranges are not eliminated when parsing character |
| * classes, so that canonicalization of |
| * |
| * [0-9a-fA-Fx-{] |
| * |
| * creates the result (note the duplicate ranges): |
| * |
| * [0-9A-FA-FX-Z{-{] |
| * |
| * where [x-{] is split as a result of canonicalization. The duplicate |
| * ranges are not a semantics issue: they work correctly. |
| */ |
| |
| duk_uint32_t offset; |
| |
| DUK_DD(DUK_DDPRINT("character class")); |
| |
| /* insert ranges instruction, range count patched in later */ |
| new_atom_char_length = 1; |
| new_atom_start_offset = (duk_int32_t) DUK__RE_BUFLEN(re_ctx); |
| duk__append_u32(re_ctx, |
| (re_ctx->curr_token.t == DUK_RETOK_ATOM_START_CHARCLASS) ? |
| DUK_REOP_RANGES : DUK_REOP_INVRANGES); |
| offset = (duk_uint32_t) DUK__RE_BUFLEN(re_ctx); /* patch in range count later */ |
| |
| /* parse ranges until character class ends */ |
| re_ctx->nranges = 0; /* note: ctx-wide temporary */ |
| duk_lexer_parse_re_ranges(&re_ctx->lex, duk__generate_ranges, (void *) re_ctx); |
| |
| /* insert range count */ |
| duk__insert_u32(re_ctx, offset, re_ctx->nranges); |
| break; |
| } |
| case DUK_RETOK_ATOM_END_GROUP: { |
| if (expect_eof) { |
| DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_CLOSING_PAREN); |
| } |
| goto done; |
| } |
| case DUK_RETOK_EOF: { |
| if (!expect_eof) { |
| DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_END_OF_PATTERN); |
| } |
| goto done; |
| } |
| default: { |
| DUK_ERROR_SYNTAX(re_ctx->thr, DUK_STR_UNEXPECTED_REGEXP_TOKEN); |
| } |
| } |
| |
| /* a complex (new) atom taints the result */ |
| if (new_atom_start_offset >= 0) { |
| if (new_atom_char_length < 0) { |
| res_charlen = -1; |
| } else if (res_charlen >= 0) { |
| /* only advance if not tainted */ |
| res_charlen += new_atom_char_length; |
| } |
| } |
| |
| /* record previous atom info in case next token is a quantifier */ |
| atom_start_offset = new_atom_start_offset; |
| atom_char_length = new_atom_char_length; |
| atom_start_captures = new_atom_start_captures; |
| } |
| |
| done: |
| |
| /* finish up pending jump and split for last alternative */ |
| if (unpatched_disjunction_jump >= 0) { |
| duk_uint32_t offset; |
| |
| DUK_ASSERT(unpatched_disjunction_split >= 0); |
| offset = unpatched_disjunction_jump; |
| offset += duk__insert_jump_offset(re_ctx, |
| offset, |
| (duk_int32_t) (DUK__RE_BUFLEN(re_ctx) - offset)); |
| /* offset is now target of the pending split (right after jump) */ |
| duk__insert_jump_offset(re_ctx, |
| unpatched_disjunction_split, |
| offset - unpatched_disjunction_split); |
| } |
| |
| #if 0 |
| out_atom_info->end_captures = re_ctx->captures; |
| #endif |
| out_atom_info->charlen = res_charlen; |
| DUK_DDD(DUK_DDDPRINT("parse disjunction finished: charlen=%ld", |
| (long) out_atom_info->charlen)); |
| |
| re_ctx->recursion_depth--; |
| } |
| |
| /* |
| * Flags parsing (see E5 Section 15.10.4.1). |
| */ |
| |
| DUK_LOCAL duk_uint32_t duk__parse_regexp_flags(duk_hthread *thr, duk_hstring *h) { |
| const duk_uint8_t *p; |
| const duk_uint8_t *p_end; |
| duk_uint32_t flags = 0; |
| |
| p = DUK_HSTRING_GET_DATA(h); |
| p_end = p + DUK_HSTRING_GET_BYTELEN(h); |
| |
| /* Note: can be safely scanned as bytes (undecoded) */ |
| |
| while (p < p_end) { |
| duk_uint8_t c = *p++; |
| switch ((int) c) { |
| case (int) 'g': { |
| if (flags & DUK_RE_FLAG_GLOBAL) { |
| goto error; |
| } |
| flags |= DUK_RE_FLAG_GLOBAL; |
| break; |
| } |
| case (int) 'i': { |
| if (flags & DUK_RE_FLAG_IGNORE_CASE) { |
| goto error; |
| } |
| flags |= DUK_RE_FLAG_IGNORE_CASE; |
| break; |
| } |
| case (int) 'm': { |
| if (flags & DUK_RE_FLAG_MULTILINE) { |
| goto error; |
| } |
| flags |= DUK_RE_FLAG_MULTILINE; |
| break; |
| } |
| default: { |
| goto error; |
| } |
| } |
| } |
| |
| return flags; |
| |
| error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_REGEXP_FLAGS); |
| return 0; /* never here */ |
| } |
| |
| /* |
| * Create escaped RegExp source (E5 Section 15.10.3). |
| * |
| * The current approach is to special case the empty RegExp |
| * ('' -> '(?:)') and otherwise replace unescaped '/' characters |
| * with '\/' regardless of where they occur in the regexp. |
| * |
| * Note that normalization does not seem to be necessary for |
| * RegExp literals (e.g. '/foo/') because to be acceptable as |
| * a RegExp literal, the text between forward slashes must |
| * already match the escaping requirements (e.g. must not contain |
| * unescaped forward slashes or be empty). Escaping IS needed |
| * for expressions like 'new Regexp("...", "")' however. |
| * Currently, we re-escape in either case. |
| * |
| * Also note that we process the source here in UTF-8 encoded |
| * form. This is correct, because any non-ASCII characters are |
| * passed through without change. |
| */ |
| |
| DUK_LOCAL void duk__create_escaped_source(duk_hthread *thr, int idx_pattern) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_hstring *h; |
| const duk_uint8_t *p; |
| duk_bufwriter_ctx bw_alloc; |
| duk_bufwriter_ctx *bw; |
| duk_uint8_t *q; |
| duk_size_t i, n; |
| duk_uint_fast8_t c_prev, c; |
| |
| h = duk_get_hstring(ctx, idx_pattern); |
| DUK_ASSERT(h != NULL); |
| p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h); |
| n = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h); |
| |
| if (n == 0) { |
| /* return '(?:)' */ |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_ESCAPED_EMPTY_REGEXP); |
| return; |
| } |
| |
| bw = &bw_alloc; |
| DUK_BW_INIT_PUSHBUF(thr, bw, n); |
| q = DUK_BW_GET_PTR(thr, bw); |
| |
| c_prev = (duk_uint_fast8_t) 0; |
| |
| for (i = 0; i < n; i++) { |
| c = p[i]; |
| |
| q = DUK_BW_ENSURE_RAW(thr, bw, 2, q); |
| |
| if (c == (duk_uint_fast8_t) '/' && c_prev != (duk_uint_fast8_t) '\\') { |
| /* Unescaped '/' ANYWHERE in the regexp (in disjunction, |
| * inside a character class, ...) => same escape works. |
| */ |
| *q++ = DUK_ASC_BACKSLASH; |
| } |
| *q++ = (duk_uint8_t) c; |
| |
| c_prev = c; |
| } |
| |
| DUK_BW_SETPTR_AND_COMPACT(thr, bw, q); |
| duk_to_string(ctx, -1); /* -> [ ... escaped_source ] */ |
| } |
| |
| /* |
| * Exposed regexp compilation primitive. |
| * |
| * Sets up a regexp compilation context, and calls duk__parse_disjunction() to do the |
| * actual parsing. Handles generation of the compiled regexp header and the |
| * "boilerplate" capture of the matching substring (save 0 and 1). Also does some |
| * global level regexp checks after recursive compilation has finished. |
| * |
| * An escaped version of the regexp source, suitable for use as a RegExp instance |
| * 'source' property (see E5 Section 15.10.3), is also left on the stack. |
| * |
| * Input stack: [ pattern flags ] |
| * Output stack: [ bytecode escaped_source ] (both as strings) |
| */ |
| |
| DUK_INTERNAL void duk_regexp_compile(duk_hthread *thr) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_re_compiler_ctx re_ctx; |
| duk_lexer_point lex_point; |
| duk_hstring *h_pattern; |
| duk_hstring *h_flags; |
| duk__re_disjunction_info ign_disj; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(ctx != NULL); |
| |
| /* |
| * Args validation |
| */ |
| |
| /* TypeError if fails */ |
| h_pattern = duk_require_hstring(ctx, -2); |
| h_flags = duk_require_hstring(ctx, -1); |
| |
| /* |
| * Create normalized 'source' property (E5 Section 15.10.3). |
| */ |
| |
| /* [ ... pattern flags ] */ |
| |
| duk__create_escaped_source(thr, -2); |
| |
| /* [ ... pattern flags escaped_source ] */ |
| |
| /* |
| * Init compilation context |
| */ |
| |
| /* [ ... pattern flags escaped_source buffer ] */ |
| |
| DUK_MEMZERO(&re_ctx, sizeof(re_ctx)); |
| DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ |
| re_ctx.thr = thr; |
| re_ctx.lex.thr = thr; |
| re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); |
| re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); |
| re_ctx.lex.token_limit = DUK_RE_COMPILE_TOKEN_LIMIT; |
| re_ctx.recursion_limit = DUK_USE_REGEXP_COMPILER_RECLIMIT; |
| re_ctx.re_flags = duk__parse_regexp_flags(thr, h_flags); |
| |
| DUK_BW_INIT_PUSHBUF(thr, &re_ctx.bw, DUK__RE_INITIAL_BUFSIZE); |
| |
| DUK_DD(DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08lx, recursion_limit=%ld", |
| (unsigned long) re_ctx.re_flags, (long) re_ctx.recursion_limit)); |
| |
| /* |
| * Init lexer |
| */ |
| |
| lex_point.offset = 0; /* expensive init, just want to fill window */ |
| lex_point.line = 1; |
| DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); |
| |
| /* |
| * Compilation |
| */ |
| |
| DUK_DD(DUK_DDPRINT("starting regexp compilation")); |
| |
| duk__append_u32(&re_ctx, DUK_REOP_SAVE); |
| duk__append_u32(&re_ctx, 0); |
| duk__parse_disjunction(&re_ctx, 1 /*expect_eof*/, &ign_disj); |
| duk__append_u32(&re_ctx, DUK_REOP_SAVE); |
| duk__append_u32(&re_ctx, 1); |
| duk__append_u32(&re_ctx, DUK_REOP_MATCH); |
| |
| /* |
| * Check for invalid backreferences; note that it is NOT an error |
| * to back-reference a capture group which has not yet been introduced |
| * in the pattern (as in /\1(foo)/); in fact, the backreference will |
| * always match! It IS an error to back-reference a capture group |
| * which will never be introduced in the pattern. Thus, we can check |
| * for such references only after parsing is complete. |
| */ |
| |
| if (re_ctx.highest_backref > re_ctx.captures) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_BACKREFS); |
| } |
| |
| /* |
| * Emit compiled regexp header: flags, ncaptures |
| * (insertion order inverted on purpose) |
| */ |
| |
| duk__insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); |
| duk__insert_u32(&re_ctx, 0, re_ctx.re_flags); |
| |
| /* [ ... pattern flags escaped_source buffer ] */ |
| |
| DUK_BW_COMPACT(thr, &re_ctx.bw); |
| duk_to_string(ctx, -1); /* coerce to string */ |
| |
| /* [ ... pattern flags escaped_source bytecode ] */ |
| |
| /* |
| * Finalize stack |
| */ |
| |
| duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ |
| duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ |
| |
| DUK_DD(DUK_DDPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", |
| (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2))); |
| } |
| |
| /* |
| * Create a RegExp instance (E5 Section 15.10.7). |
| * |
| * Note: the output stack left by duk_regexp_compile() is directly compatible |
| * with the input here. |
| * |
| * Input stack: [ escaped_source bytecode ] (both as strings) |
| * Output stack: [ RegExp ] |
| */ |
| |
| DUK_INTERNAL void duk_regexp_create_instance(duk_hthread *thr) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_hobject *h; |
| duk_hstring *h_bc; |
| duk_small_int_t re_flags; |
| |
| /* [ ... escape_source bytecode ] */ |
| |
| h_bc = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h_bc != NULL); |
| DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1); /* always at least the header */ |
| DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1); |
| DUK_ASSERT((duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80); /* flags always encodes to 1 byte */ |
| re_flags = (duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0]; |
| |
| /* [ ... escaped_source bytecode ] */ |
| |
| duk_push_object(ctx); |
| h = duk_get_hobject(ctx, -1); |
| DUK_ASSERT(h != NULL); |
| duk_insert(ctx, -3); |
| |
| /* [ ... regexp_object escaped_source bytecode ] */ |
| |
| DUK_HOBJECT_SET_CLASS_NUMBER(h, DUK_HOBJECT_CLASS_REGEXP); |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]); |
| |
| duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_INT_BYTECODE, DUK_PROPDESC_FLAGS_NONE); |
| |
| /* [ ... regexp_object escaped_source ] */ |
| |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_SOURCE, DUK_PROPDESC_FLAGS_NONE); |
| |
| /* [ ... regexp_object ] */ |
| |
| duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_GLOBAL)); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_GLOBAL, DUK_PROPDESC_FLAGS_NONE); |
| |
| duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_IGNORE_CASE)); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_IGNORE_CASE, DUK_PROPDESC_FLAGS_NONE); |
| |
| duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_MULTILINE)); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_MULTILINE, DUK_PROPDESC_FLAGS_NONE); |
| |
| duk_push_int(ctx, 0); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LAST_INDEX, DUK_PROPDESC_FLAGS_W); |
| |
| /* [ ... regexp_object ] */ |
| } |
| |
| #undef DUK__RE_BUFLEN |
| |
| #else /* DUK_USE_REGEXP_SUPPORT */ |
| |
| /* regexp support disabled */ |
| |
| #endif /* DUK_USE_REGEXP_SUPPORT */ |