/*
 *  Lexer for source files, ToNumber() string conversions, RegExp expressions,
 *  and JSON.
 *
 *  Provides a stream of ECMAScript tokens from an UTF-8/CESU-8 buffer.  The
 *  caller can also rewind the token stream into a certain position which is
 *  needed by the compiler part for multi-pass scanning.  Tokens are
 *  represented as duk_token structures, and contain line number information.
 *  Token types are identified with DUK_TOK_* defines.
 *
 *  Characters are decoded into a fixed size lookup window consisting of
 *  decoded Unicode code points, with window positions past the end of the
 *  input filled with an invalid codepoint (-1).  The tokenizer can thus
 *  perform multiple character lookups efficiently and with few sanity
 *  checks (such as access outside the end of the input), which keeps the
 *  tokenization code small at the cost of performance.
 *
 *  Character data in tokens, such as identifier names and string literals,
 *  is encoded into CESU-8 format on-the-fly while parsing the token in
 *  question.  The string data is made reachable to garbage collection by
 *  placing the token-related values in value stack entries allocated for
 *  this purpose by the caller.  The characters exist in Unicode code point
 *  form only in the fixed size lookup window, which keeps character data
 *  expansion (of especially ASCII data) low.
 *
 *  Token parsing supports the full range of Unicode characters as described
 *  in the E5 specification.  Parsing has been optimized for ASCII characters
 *  because ordinary ECMAScript code consists almost entirely of ASCII
 *  characters.  Matching of complex Unicode codepoint sets (such as in the
 *  IdentifierStart and IdentifierPart productions) is optimized for size,
 *  and is done using a linear scan of a bit-packed list of ranges.  This is
 *  very slow, but should never be entered unless the source code actually
 *  contains Unicode characters.
 *
 *  ECMAScript tokenization is partially context sensitive.  First,
 *  additional future reserved words are recognized in strict mode (see E5
 *  Section 7.6.1.2).  Second, a forward slash character ('/') can be
 *  recognized either as starting a RegExp literal or as a division operator,
 *  depending on context.  The caller must provide necessary context flags
 *  when requesting a new token.
 *
 *  Future work:
 *
 *    * Make line number tracking optional, as it consumes space.
 *
 *    * Add a feature flag for disabling UTF-8 decoding of input, as most
 *      source code is ASCII.  Because of Unicode escapes written in ASCII,
 *      this does not allow Unicode support to be removed from e.g.
 *      duk_unicode_is_identifier_start() nor does it allow removal of CESU-8
 *      encoding of e.g. string literals.
 *
 *    * Add a feature flag for disabling Unicode compliance of e.g. identifier
 *      names.  This allows for a build more than a kilobyte smaller, because
 *      Unicode ranges needed by duk_unicode_is_identifier_start() and
 *      duk_unicode_is_identifier_part() can be dropped.  String literals
 *      should still be allowed to contain escaped Unicode, so this still does
 *      not allow removal of CESU-8 encoding of e.g. string literals.
 *
 *    * Character lookup tables for codepoints above BMP could be stripped.
 *
 *    * Strictly speaking, E5 specification requires that source code consists
 *      of 16-bit code units, and if not, must be conceptually converted to
 *      that format first.  The current lexer processes Unicode code points
 *      and allows characters outside the BMP.  These should be converted to
 *      surrogate pairs while reading the source characters into the window,
 *      not after tokens have been formed (as is done now).  However, the fix
 *      is not trivial because two characters are decoded from one codepoint.
 *
 *    * Optimize for speed as well as size.  Large if-else ladders are (at
 *      least potentially) slow.
 */

#include "duk_internal.h"

/*
 *  Various defines and file specific helper macros
 */

#define DUK__MAX_RE_DECESC_DIGITS     9
#define DUK__MAX_RE_QUANT_DIGITS      9   /* Does not allow e.g. 2**31-1, but one more would allow overflows of u32. */

/* whether to use macros or helper function depends on call count */
#define DUK__ISDIGIT(x)          ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_9)
#define DUK__ISHEXDIGIT(x)       duk__is_hex_digit((x))
#define DUK__ISOCTDIGIT(x)       ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_7)
#define DUK__ISDIGIT03(x)        ((x) >= DUK_ASC_0 && (x) <= DUK_ASC_3)
#define DUK__ISDIGIT47(x)        ((x) >= DUK_ASC_4 && (x) <= DUK_ASC_7)

/* lexer character window helpers */
#define DUK__LOOKUP(lex_ctx,idx)            ((lex_ctx)->window[(idx)].codepoint)
#define DUK__ADVANCECHARS(lex_ctx,count)    duk__advance_chars((lex_ctx), (count))
#define DUK__ADVANCEBYTES(lex_ctx,count)    duk__advance_bytes((lex_ctx), (count))
#define DUK__INITBUFFER(lex_ctx)            duk__initbuffer((lex_ctx))
#define DUK__APPENDBUFFER(lex_ctx,x)        duk__appendbuffer((lex_ctx), (duk_codepoint_t) (x))
#define DUK__APPENDBUFFER_ASCII(lex_ctx,x)  duk__appendbuffer_ascii((lex_ctx), (duk_codepoint_t) (x))

/* lookup shorthands (note: assume context variable is named 'lex_ctx') */
#define DUK__L0()  DUK__LOOKUP(lex_ctx, 0)
#define DUK__L1()  DUK__LOOKUP(lex_ctx, 1)
#define DUK__L2()  DUK__LOOKUP(lex_ctx, 2)
#define DUK__L3()  DUK__LOOKUP(lex_ctx, 3)
#define DUK__L4()  DUK__LOOKUP(lex_ctx, 4)
#define DUK__L5()  DUK__LOOKUP(lex_ctx, 5)

/* packed advance/token number macro used by multiple functions */
#define DUK__ADVTOK(advbytes,tok)  ((((advbytes) * sizeof(duk_lexer_codepoint)) << 8) + (tok))

/*
 *  Advance lookup window by N characters, filling in new characters as
 *  necessary.  After returning caller is guaranteed a character window of
 *  at least DUK_LEXER_WINDOW_SIZE characters.
 *
 *  The main function duk__advance_bytes() is called at least once per every
 *  token so it has a major lexer/compiler performance impact.  There are two
 *  variants for the main duk__advance_bytes() algorithm: a sliding window
 *  approach which is slightly faster at the cost of larger code footprint,
 *  and a simple copying one.
 *
 *  Decoding directly from the source string would be another lexing option.
 *  But the lookup window based approach has the advantage of hiding the
 *  source string and its encoding effectively which gives more flexibility
 *  going forward to e.g. support chunked streaming of source from flash.
 *
 *  Decodes UTF-8/CESU-8 leniently with support for code points from U+0000 to
 *  U+10FFFF, causing an error if the input is unparseable.  Leniency means:
 *
 *    * Unicode code point validation is intentionally not performed,
 *      except to check that the codepoint does not exceed 0x10ffff.
 *
 *    * In particular, surrogate pairs are allowed and not combined, which
 *      allows source files to represent all SourceCharacters with CESU-8.
 *      Broken surrogate pairs are allowed, as ECMAScript does not mandate
 *      their validation.
 *
 *    * Allow non-shortest UTF-8 encodings.
 *
 *  Leniency here causes few security concerns because all character data is
 *  decoded into Unicode codepoints before lexer processing, and is then
 *  re-encoded into CESU-8.  The source can be parsed as strict UTF-8 with
 *  a compiler option.  However, ECMAScript source characters include -all-
 *  16-bit unsigned integer codepoints, so leniency seems to be appropriate.
 *
 *  Note that codepoints above the BMP are not strictly SourceCharacters,
 *  but the lexer still accepts them as such.  Before ending up in a string
 *  or an identifier name, codepoints above BMP are converted into surrogate
 *  pairs and then CESU-8 encoded, resulting in 16-bit Unicode data as
 *  expected by ECMAScript.
 *
 *  An alternative approach to dealing with invalid or partial sequences
 *  would be to skip them and replace them with e.g. the Unicode replacement
 *  character U+FFFD.  This has limited utility because a replacement character
 *  will most likely cause a parse error, unless it occurs inside a string.
 *  Further, ECMAScript source is typically pure ASCII.
 *
 *  See:
 *
 *     http://en.wikipedia.org/wiki/UTF-8
 *     http://en.wikipedia.org/wiki/CESU-8
 *     http://tools.ietf.org/html/rfc3629
 *     http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences
 *
 *  Future work:
 *
 *    * Reject other invalid Unicode sequences (see Wikipedia entry for examples)
 *      in strict UTF-8 mode.
 *
 *    * Size optimize.  An attempt to use a 16-byte lookup table for the first
 *      byte resulted in a code increase though.
 *
 *    * Is checking against maximum 0x10ffff really useful?  4-byte encoding
 *      imposes a certain limit anyway.
 *
 *    * Support chunked streaming of source code.  Can be implemented either
 *      by streaming chunks of bytes or chunks of codepoints.
 */

#if defined(DUK_USE_LEXER_SLIDING_WINDOW)
DUK_LOCAL void duk__fill_lexer_buffer(duk_lexer_ctx *lex_ctx, duk_small_uint_t start_offset_bytes) {
	duk_lexer_codepoint *cp, *cp_end;
	duk_ucodepoint_t x;
	duk_small_uint_t contlen;
	const duk_uint8_t *p, *p_end;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
	duk_ucodepoint_t mincp;
#endif
	duk_int_t input_line;

	/* Use temporaries and update lex_ctx only when finished. */
	input_line = lex_ctx->input_line;
	p = lex_ctx->input + lex_ctx->input_offset;
	p_end = lex_ctx->input + lex_ctx->input_length;

	cp = (duk_lexer_codepoint *) (void *) ((duk_uint8_t *) lex_ctx->buffer + start_offset_bytes);
	cp_end = lex_ctx->buffer + DUK_LEXER_BUFFER_SIZE;

	for (; cp != cp_end; cp++) {
		cp->offset = (duk_size_t) (p - lex_ctx->input);
		cp->line = input_line;

		/* XXX: potential issue with signed pointers, p_end < p. */
		if (DUK_UNLIKELY(p >= p_end)) {
			/* If input_offset were assigned a negative value, it would
			 * result in a large positive value.  Most likely it would be
			 * larger than input_length and be caught here.  In any case
			 * no memory unsafe behavior would happen.
			 */
			cp->codepoint = -1;
			continue;
		}

		x = (duk_ucodepoint_t) (*p++);

		/* Fast path. */

		if (DUK_LIKELY(x < 0x80UL)) {
			DUK_ASSERT(x != 0x2028UL && x != 0x2029UL);  /* not LS/PS */
			if (DUK_UNLIKELY(x <= 0x000dUL)) {
				if ((x == 0x000aUL) ||
				    ((x == 0x000dUL) && (p >= p_end || *p != 0x000aUL))) {
					/* lookup for 0x000a above assumes shortest encoding now */

					/* E5 Section 7.3, treat the following as newlines:
					 *   LF
					 *   CR [not followed by LF]
					 *   LS
					 *   PS
					 *
					 * For CR LF, CR is ignored if it is followed by LF, and the LF will bump
					 * the line number.
					 */
					input_line++;
				}
			}

			cp->codepoint = (duk_codepoint_t) x;
			continue;
		}

		/* Slow path. */

		if (x < 0xc0UL) {
			/* 10xx xxxx -> invalid */
			goto error_encoding;
		} else if (x < 0xe0UL) {
			/* 110x xxxx   10xx xxxx  */
			contlen = 1;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
			mincp = 0x80UL;
#endif
			x = x & 0x1fUL;
		} else if (x < 0xf0UL) {
			/* 1110 xxxx   10xx xxxx   10xx xxxx */
			contlen = 2;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
			mincp = 0x800UL;
#endif
			x = x & 0x0fUL;
		} else if (x < 0xf8UL) {
			/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx */
			contlen = 3;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
			mincp = 0x10000UL;
#endif
			x = x & 0x07UL;
		} else {
			/* no point in supporting encodings of 5 or more bytes */
			goto error_encoding;
		}

		DUK_ASSERT(p_end >= p);
		if ((duk_size_t) contlen > (duk_size_t) (p_end - p)) {
			goto error_clipped;
		}

		while (contlen > 0) {
			duk_small_uint_t y;
			y = *p++;
			if ((y & 0xc0U) != 0x80U) {
				/* check that byte has the form 10xx xxxx */
				goto error_encoding;
			}
			x = x << 6;
			x += y & 0x3fUL;
			contlen--;
		}

		/* check final character validity */

		if (x > 0x10ffffUL) {
			goto error_encoding;
		}
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
		if (x < mincp || (x >= 0xd800UL && x <= 0xdfffUL) || x == 0xfffeUL) {
			goto error_encoding;
		}
#endif

		DUK_ASSERT(x != 0x000aUL && x != 0x000dUL);
		if ((x == 0x2028UL) || (x == 0x2029UL)) {
			input_line++;
		}

		cp->codepoint = (duk_codepoint_t) x;
	}

	lex_ctx->input_offset = (duk_size_t) (p - lex_ctx->input);
	lex_ctx->input_line = input_line;
	return;

 error_clipped:   /* clipped codepoint */
 error_encoding:  /* invalid codepoint encoding or codepoint */
	lex_ctx->input_offset = (duk_size_t) (p - lex_ctx->input);
	lex_ctx->input_line = input_line;

	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_SOURCE_DECODE_FAILED);
	DUK_WO_NORETURN(return;);
}

DUK_LOCAL void duk__advance_bytes(duk_lexer_ctx *lex_ctx, duk_small_uint_t count_bytes) {
	duk_small_uint_t used_bytes, avail_bytes;

	DUK_ASSERT_DISABLE(count_bytes >= 0);  /* unsigned */
	DUK_ASSERT(count_bytes <= (duk_small_uint_t) (DUK_LEXER_WINDOW_SIZE * sizeof(duk_lexer_codepoint)));
	DUK_ASSERT(lex_ctx->window >= lex_ctx->buffer);
	DUK_ASSERT(lex_ctx->window < lex_ctx->buffer + DUK_LEXER_BUFFER_SIZE);
	DUK_ASSERT((duk_uint8_t *) lex_ctx->window + count_bytes <= (duk_uint8_t *) lex_ctx->buffer + DUK_LEXER_BUFFER_SIZE * sizeof(duk_lexer_codepoint));

	/* Zero 'count' is also allowed to make call sites easier.
	 * Arithmetic in bytes generates better code in GCC.
	 */

	lex_ctx->window = (duk_lexer_codepoint *) (void *) ((duk_uint8_t *) lex_ctx->window + count_bytes);  /* avoid multiply */
	used_bytes = (duk_small_uint_t) ((duk_uint8_t *) lex_ctx->window - (duk_uint8_t *) lex_ctx->buffer);
	avail_bytes = DUK_LEXER_BUFFER_SIZE * sizeof(duk_lexer_codepoint) - used_bytes;
	if (avail_bytes < (duk_small_uint_t) (DUK_LEXER_WINDOW_SIZE * sizeof(duk_lexer_codepoint))) {
		/* Not enough data to provide a full window, so "scroll" window to
		 * start of buffer and fill up the rest.
		 */
		duk_memmove((void *) lex_ctx->buffer,
		            (const void *) lex_ctx->window,
		            (size_t) avail_bytes);
		lex_ctx->window = lex_ctx->buffer;
		duk__fill_lexer_buffer(lex_ctx, avail_bytes);
	}
}

DUK_LOCAL void duk__init_lexer_window(duk_lexer_ctx *lex_ctx) {
	lex_ctx->window = lex_ctx->buffer;
	duk__fill_lexer_buffer(lex_ctx, 0);
}
#else  /* DUK_USE_LEXER_SLIDING_WINDOW */
DUK_LOCAL duk_codepoint_t duk__read_char(duk_lexer_ctx *lex_ctx) {
	duk_ucodepoint_t x;
	duk_small_uint_t len;
	duk_small_uint_t i;
	const duk_uint8_t *p;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
	duk_ucodepoint_t mincp;
#endif
	duk_size_t input_offset;

	input_offset = lex_ctx->input_offset;
	if (DUK_UNLIKELY(input_offset >= lex_ctx->input_length)) {
		/* If input_offset were assigned a negative value, it would
		 * result in a large positive value.  Most likely it would be
		 * larger than input_length and be caught here.  In any case
		 * no memory unsafe behavior would happen.
		 */
		return -1;
	}

	p = lex_ctx->input + input_offset;
	x = (duk_ucodepoint_t) (*p);

	if (DUK_LIKELY(x < 0x80UL)) {
		/* 0xxx xxxx -> fast path */

		/* input offset tracking */
		lex_ctx->input_offset++;

		DUK_ASSERT(x != 0x2028UL && x != 0x2029UL);  /* not LS/PS */
		if (DUK_UNLIKELY(x <= 0x000dUL)) {
			if ((x == 0x000aUL) ||
			    ((x == 0x000dUL) && (lex_ctx->input_offset >= lex_ctx->input_length ||
			                         lex_ctx->input[lex_ctx->input_offset] != 0x000aUL))) {
				/* lookup for 0x000a above assumes shortest encoding now */

				/* E5 Section 7.3, treat the following as newlines:
				 *   LF
				 *   CR [not followed by LF]
				 *   LS
				 *   PS
				 *
				 * For CR LF, CR is ignored if it is followed by LF, and the LF will bump
				 * the line number.
				 */
				lex_ctx->input_line++;
			}
		}

		return (duk_codepoint_t) x;
	}

	/* Slow path. */

	if (x < 0xc0UL) {
		/* 10xx xxxx -> invalid */
		goto error_encoding;
	} else if (x < 0xe0UL) {
		/* 110x xxxx   10xx xxxx  */
		len = 2;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
		mincp = 0x80UL;
#endif
		x = x & 0x1fUL;
	} else if (x < 0xf0UL) {
		/* 1110 xxxx   10xx xxxx   10xx xxxx */
		len = 3;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
		mincp = 0x800UL;
#endif
		x = x & 0x0fUL;
	} else if (x < 0xf8UL) {
		/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx */
		len = 4;
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
		mincp = 0x10000UL;
#endif
		x = x & 0x07UL;
	} else {
		/* no point in supporting encodings of 5 or more bytes */
		goto error_encoding;
	}

	DUK_ASSERT(lex_ctx->input_length >= lex_ctx->input_offset);
	if ((duk_size_t) len > (duk_size_t) (lex_ctx->input_length - lex_ctx->input_offset)) {
		goto error_clipped;
	}

	p++;
	for (i = 1; i < len; i++) {
		duk_small_uint_t y;
		y = *p++;
		if ((y & 0xc0U) != 0x80U) {
			/* check that byte has the form 10xx xxxx */
			goto error_encoding;
		}
		x = x << 6;
		x += y & 0x3fUL;
	}

	/* check final character validity */

	if (x > 0x10ffffUL) {
		goto error_encoding;
	}
#if defined(DUK_USE_STRICT_UTF8_SOURCE)
	if (x < mincp || (x >= 0xd800UL && x <= 0xdfffUL) || x == 0xfffeUL) {
		goto error_encoding;
	}
#endif

	/* input offset tracking */
	lex_ctx->input_offset += len;

	/* line tracking */
	DUK_ASSERT(x != 0x000aUL && x != 0x000dUL);
	if ((x == 0x2028UL) || (x == 0x2029UL)) {
		lex_ctx->input_line++;
	}

	return (duk_codepoint_t) x;

 error_clipped:   /* clipped codepoint */
 error_encoding:  /* invalid codepoint encoding or codepoint */
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_SOURCE_DECODE_FAILED);
	DUK_WO_NORETURN(return 0;);
}

DUK_LOCAL void duk__advance_bytes(duk_lexer_ctx *lex_ctx, duk_small_uint_t count_bytes) {
	duk_small_uint_t keep_bytes;
	duk_lexer_codepoint *cp, *cp_end;

	DUK_ASSERT_DISABLE(count_bytes >= 0);  /* unsigned */
	DUK_ASSERT(count_bytes <= (duk_small_uint_t) (DUK_LEXER_WINDOW_SIZE * sizeof(duk_lexer_codepoint)));

	/* Zero 'count' is also allowed to make call sites easier. */

	keep_bytes = DUK_LEXER_WINDOW_SIZE * sizeof(duk_lexer_codepoint) - count_bytes;
	duk_memmove((void *) lex_ctx->window,
	            (const void *) ((duk_uint8_t *) lex_ctx->window + count_bytes),
	            (size_t) keep_bytes);

	cp = (duk_lexer_codepoint *) ((duk_uint8_t *) lex_ctx->window + keep_bytes);
	cp_end = lex_ctx->window + DUK_LEXER_WINDOW_SIZE;
	for (; cp != cp_end; cp++) {
		cp->offset = lex_ctx->input_offset;
		cp->line = lex_ctx->input_line;
		cp->codepoint = duk__read_char(lex_ctx);
	}
}

DUK_LOCAL void duk__init_lexer_window(duk_lexer_ctx *lex_ctx) {
	/* Call with count == DUK_LEXER_WINDOW_SIZE to fill buffer initially. */
	duk__advance_bytes(lex_ctx, DUK_LEXER_WINDOW_SIZE * sizeof(duk_lexer_codepoint));  /* fill window */
}
#endif  /* DUK_USE_LEXER_SLIDING_WINDOW */

DUK_LOCAL void duk__advance_chars(duk_lexer_ctx *lex_ctx, duk_small_uint_t count_chars) {
	duk__advance_bytes(lex_ctx, count_chars * sizeof(duk_lexer_codepoint));
}

/*
 *  (Re)initialize the temporary byte buffer.  May be called extra times
 *  with little impact.
 */

DUK_LOCAL void duk__initbuffer(duk_lexer_ctx *lex_ctx) {
	/* Reuse buffer as is unless buffer has grown large. */
	if (DUK_HBUFFER_DYNAMIC_GET_SIZE(lex_ctx->buf) < DUK_LEXER_TEMP_BUF_LIMIT) {
		/* Keep current size */
	} else {
		duk_hbuffer_resize(lex_ctx->thr, lex_ctx->buf, DUK_LEXER_TEMP_BUF_LIMIT);
	}

	DUK_BW_INIT_WITHBUF(lex_ctx->thr, &lex_ctx->bw, lex_ctx->buf);
}

/*
 *  Append a Unicode codepoint to the temporary byte buffer.  Performs
 *  CESU-8 surrogate pair encoding for codepoints above the BMP.
 *  Existing surrogate pairs are allowed and also encoded into CESU-8.
 */

DUK_LOCAL void duk__appendbuffer(duk_lexer_ctx *lex_ctx, duk_codepoint_t x) {
	/*
	 *  Since character data is only generated by decoding the source or by
	 *  the compiler itself, we rely on the input codepoints being correct
	 *  and avoid a check here.
	 *
	 *  Character data can also come here through decoding of Unicode
	 *  escapes ("\udead\ubeef") so all 16-but unsigned values can be
	 *  present, even when the source file itself is strict UTF-8.
	 */
	DUK_ASSERT(x >= 0 && x <= 0x10ffffL);

	DUK_BW_WRITE_ENSURE_CESU8(lex_ctx->thr, &lex_ctx->bw, (duk_ucodepoint_t) x);
}

DUK_LOCAL void duk__appendbuffer_ascii(duk_lexer_ctx *lex_ctx, duk_codepoint_t x) {
	/* ASCII characters can be emitted as a single byte without encoding
	 * which matters for some fast paths.
	 */
	DUK_ASSERT(x >= 0 && x <= 0x7f);

	DUK_BW_WRITE_ENSURE_U8(lex_ctx->thr, &lex_ctx->bw, (duk_uint8_t) x);
}

/*
 *  Intern the temporary byte buffer into a valstack slot
 *  (in practice, slot1 or slot2).
 */

DUK_LOCAL duk_hstring *duk__internbuffer(duk_lexer_ctx *lex_ctx, duk_idx_t valstack_idx) {
	DUK_ASSERT(valstack_idx == lex_ctx->slot1_idx || valstack_idx == lex_ctx->slot2_idx);

	DUK_BW_PUSH_AS_STRING(lex_ctx->thr, &lex_ctx->bw);
	duk_replace(lex_ctx->thr, valstack_idx);
	return duk_known_hstring(lex_ctx->thr, valstack_idx);
}

/*
 *  Init lexer context
 */

DUK_INTERNAL void duk_lexer_initctx(duk_lexer_ctx *lex_ctx) {
	DUK_ASSERT(lex_ctx != NULL);

	duk_memzero(lex_ctx, sizeof(*lex_ctx));
#if defined(DUK_USE_EXPLICIT_NULL_INIT)
#if defined(DUK_USE_LEXER_SLIDING_WINDOW)
	lex_ctx->window = NULL;
#endif
	lex_ctx->thr = NULL;
	lex_ctx->input = NULL;
	lex_ctx->buf = NULL;
#endif
}

/*
 *  Set lexer input position and reinitialize lookup window.
 */

DUK_INTERNAL void duk_lexer_getpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt) {
	pt->offset = lex_ctx->window[0].offset;
	pt->line = lex_ctx->window[0].line;
}

DUK_INTERNAL void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt) {
	DUK_ASSERT_DISABLE(pt->offset >= 0);  /* unsigned */
	DUK_ASSERT(pt->line >= 1);
	lex_ctx->input_offset = pt->offset;
	lex_ctx->input_line = pt->line;
	duk__init_lexer_window(lex_ctx);
}

/*
 *  Lexing helpers
 */

/* Numeric value of a hex digit (also covers octal and decimal digits) or
 * -1 if not a valid hex digit.
 */
DUK_LOCAL duk_codepoint_t duk__hexval_validate(duk_codepoint_t x) {
	duk_small_int_t t;

	/* Here 'x' is a Unicode codepoint */
	if (DUK_LIKELY(x >= 0 && x <= 0xff)) {
		t = duk_hex_dectab[x];
		if (DUK_LIKELY(t >= 0)) {
			return t;
		}
	}

	return -1;
}

/* Just a wrapper for call sites where 'x' is known to be valid so
 * we assert for it before decoding.
 */
DUK_LOCAL duk_codepoint_t duk__hexval(duk_codepoint_t x) {
	duk_codepoint_t ret;

	DUK_ASSERT((x >= DUK_ASC_0 && x <= DUK_ASC_9) ||
	           (x >= DUK_ASC_LC_A && x <= DUK_ASC_LC_F) ||
	           (x >= DUK_ASC_UC_A && x <= DUK_ASC_UC_F));
	ret = duk__hexval_validate(x);
	DUK_ASSERT(ret >= 0 && ret <= 15);
	return ret;
}

/* having this as a separate function provided a size benefit */
DUK_LOCAL duk_bool_t duk__is_hex_digit(duk_codepoint_t x) {
	if (DUK_LIKELY(x >= 0 && x <= 0xff)) {
		return (duk_hex_dectab[x] >= 0);
	}
	return 0;
}

/* Parse a Unicode escape of the form \xHH, \uHHHH, or \u{H+}.  Shared by
 * source and RegExp parsing.
 */
DUK_LOCAL duk_codepoint_t duk__lexer_parse_escape(duk_lexer_ctx *lex_ctx, duk_bool_t allow_es6) {
	duk_small_int_t digits;  /* Initial value 2 or 4 for fixed length escapes, 0 for ES2015 \u{H+}. */
	duk_codepoint_t escval;
	duk_codepoint_t x;
	duk_small_uint_t adv;

	DUK_ASSERT(DUK__L0() == DUK_ASC_BACKSLASH);  /* caller responsibilities */
	DUK_ASSERT(DUK__L1() == DUK_ASC_LC_X || DUK__L1() == DUK_ASC_LC_U);
	DUK_UNREF(allow_es6);

	adv = 2;
	digits = 2;
	if (DUK__L1() == DUK_ASC_LC_U) {
		digits = 4;
#if defined(DUK_USE_ES6_UNICODE_ESCAPE)
		if (DUK__L2() == DUK_ASC_LCURLY && allow_es6) {
			digits = 0;
			adv = 3;
		}
#endif
	}
	DUK__ADVANCECHARS(lex_ctx, adv);

	escval = 0;
	for (;;) {
		/* One of the escape forms: \xHH, \uHHHH, \u{H+}.
		 * The 'digits' variable tracks parsing state and is
		 * initialized to:
		 *
		 *   \xHH     2
		 *   \uHH     4
		 *   \u{H+}   0 first time, updated to -1 to indicate
		 *            at least one digit has been parsed
		 *
		 * Octal parsing is handled separately because it can be
		 * done with fixed lookahead and also has validation
		 * rules which depend on the escape length (which is
		 * variable).
		 *
		 * We don't need a specific check for x < 0 (end of
		 * input) or duk_unicode_is_line_terminator(x)
		 * because the 'dig' decode will fail and lead to a
		 * SyntaxError.
		 */
		duk_codepoint_t dig;

		x = DUK__L0();
		DUK__ADVANCECHARS(lex_ctx, 1);

		dig = duk__hexval_validate(x);
		if (digits > 0) {
			digits--;
			if (dig < 0) {
				goto fail_escape;
			}
			DUK_ASSERT(dig >= 0x00 && dig <= 0x0f);
			escval = (escval << 4) + dig;
			if (digits == 0) {
				DUK_ASSERT(escval >= 0 && escval <= 0xffffL);
				break;
			}
		} else {
#if defined(DUK_USE_ES6_UNICODE_ESCAPE)
			DUK_ASSERT(digits == 0 /* first time */ || digits == -1 /* others */);
			if (dig >= 0) {
				DUK_ASSERT(dig >= 0x00 && dig <= 0x0f);
				escval = (escval << 4) + dig;
				if (escval > 0x10ffffL) {
					goto fail_escape;
				}
			} else if (x == DUK_ASC_RCURLY) {
				if (digits == 0) {
					/* Empty escape, \u{}. */
					goto fail_escape;
				}
				DUK_ASSERT(escval >= 0 && escval <= 0x10ffffL);
				break;
			} else {
				goto fail_escape;
			}
			digits = -1;  /* Indicate we have at least one digit. */
#else  /* DUK_USE_ES6_UNICODE_ESCAPE */
			DUK_ASSERT(0);  /* Never happens if \u{H+} support disabled. */
#endif  /* DUK_USE_ES6_UNICODE_ESCAPE */
		}
	}

	return escval;

 fail_escape:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_ESCAPE);
	DUK_WO_NORETURN(return 0;);
}

/* Parse legacy octal escape of the form \N{1,3}, e.g. \0, \5, \0377.  Maximum
 * allowed value is \0377 (U+00FF), longest match is used.  Used for both string
 * RegExp octal escape parsing.  Window[0] must be the slash '\' and the first
 * digit must already be validated to be in [0-9] by the caller.
 */
DUK_LOCAL duk_codepoint_t duk__lexer_parse_legacy_octal(duk_lexer_ctx *lex_ctx, duk_small_uint_t *out_adv, duk_bool_t reject_annex_b) {
	duk_codepoint_t cp;
	duk_small_uint_t lookup_idx;
	duk_small_uint_t adv;
	duk_codepoint_t tmp;

	DUK_ASSERT(out_adv != NULL);
	DUK_ASSERT(DUK__LOOKUP(lex_ctx, 0) == DUK_ASC_BACKSLASH);
	DUK_ASSERT(DUK__LOOKUP(lex_ctx, 1) >= DUK_ASC_0 && DUK__LOOKUP(lex_ctx, 1) <= DUK_ASC_9);

	cp = 0;
	tmp = 0;
	for (lookup_idx = 1; lookup_idx <= 3; lookup_idx++) {
		DUK_DDD(DUK_DDDPRINT("lookup_idx=%ld, cp=%ld", (long) lookup_idx, (long) cp));
		tmp = DUK__LOOKUP(lex_ctx, lookup_idx);
		if (tmp < DUK_ASC_0 || tmp > DUK_ASC_7) {
			/* No more valid digits. */
			break;
		}
		tmp = (cp << 3) + (tmp - DUK_ASC_0);
		if (tmp > 0xff) {
			/* Three digit octal escapes above \377 (= 0xff)
			 * are not allowed.
			 */
			break;
		}
		cp = tmp;
	}
	DUK_DDD(DUK_DDDPRINT("final lookup_idx=%ld, cp=%ld", (long) lookup_idx, (long) cp));

	adv = lookup_idx;
	if (lookup_idx == 1) {
		DUK_DDD(DUK_DDDPRINT("\\8 or \\9 -> treat as literal, accept in strict mode too"));
		DUK_ASSERT(tmp == DUK_ASC_8 || tmp == DUK_ASC_9);
		cp = tmp;
		adv++;  /* correction to above, eat offending character */
	} else if (lookup_idx == 2 && cp == 0) {
		/* Note: 'foo\0bar' is OK in strict mode, but 'foo\00bar' is not.
		 * It won't be interpreted as 'foo\u{0}0bar' but as a SyntaxError.
		 */
		DUK_DDD(DUK_DDDPRINT("\\0 -> accept in strict mode too"));
	} else {
		/* This clause also handles non-shortest zero, e.g. \00. */
		if (reject_annex_b) {
			DUK_DDD(DUK_DDDPRINT("non-zero octal literal %ld -> reject in strict-mode", (long) cp));
			cp = -1;
		} else {
			DUK_DDD(DUK_DDDPRINT("non-zero octal literal %ld -> accepted", (long) cp));
			DUK_ASSERT(cp >= 0 && cp <= 0xff);
		}
	}

	*out_adv = adv;

	DUK_ASSERT((cp >= 0 && cp <= 0xff) || (cp == -1 && reject_annex_b));
	return cp;
}

/* XXX: move strict mode to lex_ctx? */
DUK_LOCAL void duk__lexer_parse_string_literal(duk_lexer_ctx *lex_ctx, duk_token *out_token, duk_small_int_t quote, duk_bool_t strict_mode) {
	duk_small_uint_t adv;

	for (adv = 1 /* initial quote */ ;;) {
		duk_codepoint_t x;

		DUK__ADVANCECHARS(lex_ctx, adv);  /* eat opening quote on first loop */
		x = DUK__L0();

		adv = 1;
		if (x == quote) {
			DUK__ADVANCECHARS(lex_ctx, 1);  /* eat closing quote */
			break;
		} else if (x == '\\') {
			/* DUK__L0        -> '\' char
			 * DUK__L1 ... DUK__L5 -> more lookup
			 */
			duk_small_int_t emitcp = -1;

			x = DUK__L1();

			/* How much to advance before next loop. */
			adv = 2;  /* note: long live range */

			switch (x) {
			case '\'':
				emitcp = 0x0027;
				break;
			case '"':
				emitcp = 0x0022;
				break;
			case '\\':
				emitcp = 0x005c;
				break;
			case 'b':
				emitcp = 0x0008;
				break;
			case 'f':
				emitcp = 0x000c;
				break;
			case 'n':
				emitcp = 0x000a;
				break;
			case 'r':
				emitcp = 0x000d;
				break;
			case 't':
				emitcp = 0x0009;
				break;
			case 'v':
				emitcp = 0x000b;
				break;
			case 'x':
			case 'u': {
				duk_codepoint_t esc_cp;
				esc_cp = duk__lexer_parse_escape(lex_ctx, 1 /*allow_es6*/);
				DUK__APPENDBUFFER(lex_ctx, esc_cp);
				adv = 0;
				break;
			}
			default: {
				if (duk_unicode_is_line_terminator(x)) {
					/* line continuation */
					if (x == 0x000d && DUK__L2() == 0x000a) {
						/* CR LF again a special case */
						adv = 3;  /* line terminator, CR, LF */
					}
				} else if (DUK__ISDIGIT(x)) {
					/*
					 *  Octal escape or zero escape:
					 *    \0                                     (lookahead not OctalDigit)
					 *    \1 ... \7                              (lookahead not OctalDigit)
					 *    \ZeroToThree OctalDigit                (lookahead not OctalDigit)
					 *    \FourToSeven OctalDigit                (no lookahead restrictions)
					 *    \ZeroToThree OctalDigit OctalDigit     (no lookahead restrictions)
					 *
					 *  Zero escape is part of the standard syntax.  Octal escapes are
					 *  defined in E5 Section B.1.2, and are only allowed in non-strict mode.
					 *  Any other productions starting with a decimal digit are invalid
					 *  but are in practice treated like identity escapes.
					 *
					 *  Parse octal (up to 3 digits) from the lookup window.
					 */

					emitcp = duk__lexer_parse_legacy_octal(lex_ctx, &adv, strict_mode /*reject_annex_b*/);
					if (emitcp < 0) {
						goto fail_escape;
					}
				} else if (x < 0) {
					goto fail_unterminated;
				} else {
					/* escaped NonEscapeCharacter */
					DUK__APPENDBUFFER(lex_ctx, x);
				}
			}  /* end default clause */
			}  /* end switch */

			/* Shared handling for single codepoint escapes. */
			if (emitcp >= 0) {
				DUK__APPENDBUFFER(lex_ctx, emitcp);
			}

			/* Track number of escapes; count not really needed but directive
			 * prologues need to detect whether there were any escapes or line
			 * continuations or not.
			 */
			out_token->num_escapes++;
		} else if (x >= 0x20 && x <= 0x7f) {
			/* Fast path for ASCII case, avoids line terminator
			 * check and CESU-8 encoding.
			 */
			DUK_ASSERT(x >= 0);
			DUK_ASSERT(!duk_unicode_is_line_terminator(x));
			DUK_ASSERT(x != quote);
			DUK_ASSERT(x != DUK_ASC_BACKSLASH);
			DUK__APPENDBUFFER_ASCII(lex_ctx, x);
		} else if (x < 0 || duk_unicode_is_line_terminator(x)) {
			goto fail_unterminated;
		} else {
			/* Character which is part of the string but wasn't handled
			 * by the fast path.
			 */
			DUK__APPENDBUFFER(lex_ctx, x);
		}
	} /* string parse loop */

	return;

 fail_escape:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_ESCAPE);
	DUK_WO_NORETURN(return;);

 fail_unterminated:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_UNTERMINATED_STRING);
	DUK_WO_NORETURN(return;);
}

/* Skip to end-of-line (or end-of-file), used for single line comments. */
DUK_LOCAL void duk__lexer_skip_to_endofline(duk_lexer_ctx *lex_ctx) {
	for (;;) {
		duk_codepoint_t x;

		x = DUK__L0();
		if (x < 0 || duk_unicode_is_line_terminator(x)) {
			break;
		}
		DUK__ADVANCECHARS(lex_ctx, 1);
	}
}

/*
 *  Parse ECMAScript source InputElementDiv or InputElementRegExp
 *  (E5 Section 7), skipping whitespace, comments, and line terminators.
 *
 *  Possible results are:
 *    (1) a token
 *    (2) a line terminator (skipped)
 *    (3) a comment (skipped)
 *    (4) EOF
 *
 *  White space is automatically skipped from the current position (but
 *  not after the input element).  If input has already ended, returns
 *  DUK_TOK_EOF indefinitely.  If a parse error occurs, uses an DUK_ERROR()
 *  macro call (and hence a longjmp through current heap longjmp context).
 *  Comments and line terminator tokens are automatically skipped.
 *
 *  The input element being matched is determined by regexp_mode; if set,
 *  parses a InputElementRegExp, otherwise a InputElementDiv.  The
 *  difference between these are handling of productions starting with a
 *  forward slash.
 *
 *  If strict_mode is set, recognizes additional future reserved words
 *  specific to strict mode, and refuses to parse octal literals.
 *
 *  The matching strategy below is to (currently) use a six character
 *  lookup window to quickly determine which production is the -longest-
 *  matching one, and then parse that.  The top-level if-else clauses
 *  match the first character, and the code blocks for each clause
 *  handle -all- alternatives for that first character.  ECMAScript
 *  specification uses the "longest match wins" semantics, so the order
 *  of the if-clauses matters.
 *
 *  Misc notes:
 *
 *    * ECMAScript numeric literals do not accept a sign character.
 *      Consequently e.g. "-1.0" is parsed as two tokens: a negative
 *      sign and a positive numeric literal.  The compiler performs
 *      the negation during compilation, so this has no adverse impact.
 *
 *    * There is no token for "undefined": it is just a value available
 *      from the global object (or simply established by doing a reference
 *      to an undefined value).
 *
 *    * Some contexts want Identifier tokens, which are IdentifierNames
 *      excluding reserved words, while some contexts want IdentifierNames
 *      directly.  In the latter case e.g. "while" is interpreted as an
 *      identifier name, not a DUK_TOK_WHILE token.  The solution here is
 *      to provide both token types: DUK_TOK_WHILE goes to 't' while
 *      DUK_TOK_IDENTIFIER goes to 't_nores', and 'slot1' always contains
 *      the identifier / keyword name.
 *
 *    * Directive prologue needs to identify string literals such as
 *      "use strict" and 'use strict', which are sensitive to line
 *      continuations and escape sequences.  For instance, "use\u0020strict"
 *      is a valid directive but is distinct from "use strict".  The solution
 *      here is to decode escapes while tokenizing, but to keep track of the
 *      number of escapes.  Directive detection can then check that the
 *      number of escapes is zero.
 *
 *    * Multi-line comments with one or more internal LineTerminator are
 *      treated like a line terminator to comply with automatic semicolon
 *      insertion.
 */

DUK_INTERNAL
void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx,
                                      duk_token *out_token,
                                      duk_bool_t strict_mode,
                                      duk_bool_t regexp_mode) {
	duk_codepoint_t x;           /* temporary, must be signed and 32-bit to hold Unicode code points */
	duk_small_uint_t advtok = 0; /* (advance << 8) + token_type, updated at function end,
	                              * init is unnecessary but suppresses "may be used uninitialized" warnings.
	                              */
	duk_bool_t got_lineterm = 0;  /* got lineterm preceding non-whitespace, non-lineterm token */

	if (++lex_ctx->token_count >= lex_ctx->token_limit) {
		goto fail_token_limit;
	}

	out_token->t = DUK_TOK_EOF;
	out_token->t_nores = DUK_TOK_INVALID;  /* marker: copy t if not changed */
#if 0  /* not necessary to init, disabled for faster parsing */
	out_token->num = DUK_DOUBLE_NAN;
	out_token->str1 = NULL;
	out_token->str2 = NULL;
#endif
	out_token->num_escapes = 0;
	/* out_token->lineterm set by caller */

	/* This would be nice, but parsing is faster without resetting the
	 * value slots.  The only side effect is that references to temporary
	 * string values may linger until lexing is finished; they're then
	 * freed normally.
	 */
#if 0
	duk_to_undefined(lex_ctx->thr, lex_ctx->slot1_idx);
	duk_to_undefined(lex_ctx->thr, lex_ctx->slot2_idx);
#endif

	/* 'advtok' indicates how much to advance and which token id to assign
	 * at the end.  This shared functionality minimizes code size.  All
	 * code paths are required to set 'advtok' to some value, so no default
	 * init value is used.  Code paths calling DUK_ERROR() never return so
	 * they don't need to set advtok.
	 */

	/*
	 *  Matching order:
	 *
	 *    Punctuator first chars, also covers comments, regexps
	 *    LineTerminator
	 *    Identifier or reserved word, also covers null/true/false literals
	 *    NumericLiteral
	 *    StringLiteral
	 *    EOF
	 *
	 *  The order does not matter as long as the longest match is
	 *  always correctly identified.  There are order dependencies
	 *  in the clauses, so it's not trivial to convert to a switch.
	 */

 restart_lineupdate:
	out_token->start_line = lex_ctx->window[0].line;

 restart:
	out_token->start_offset = lex_ctx->window[0].offset;

	x = DUK__L0();

	switch (x) {
	case DUK_ASC_SPACE:
	case DUK_ASC_HT:  /* fast paths for space and tab */
		DUK__ADVANCECHARS(lex_ctx, 1);
		goto restart;
	case DUK_ASC_LF:  /* LF line terminator; CR LF and Unicode lineterms are handled in slow path */
		DUK__ADVANCECHARS(lex_ctx, 1);
		got_lineterm = 1;
		goto restart_lineupdate;
#if defined(DUK_USE_SHEBANG_COMMENTS)
	case DUK_ASC_HASH:  /* '#' */
		if (DUK__L1() == DUK_ASC_EXCLAMATION && lex_ctx->window[0].offset == 0 &&
		    (lex_ctx->flags & DUK_COMPILE_SHEBANG)) {
			/* "Shebang" comment ('#! ...') on first line. */
			/* DUK__ADVANCECHARS(lex_ctx, 2) would be correct here, but not necessary */
			duk__lexer_skip_to_endofline(lex_ctx);
			goto restart;  /* line terminator will be handled on next round */
		}
		goto fail_token;
#endif  /* DUK_USE_SHEBANG_COMMENTS */
	case DUK_ASC_SLASH:  /* '/' */
		if (DUK__L1() == DUK_ASC_SLASH) {
			/*
			 *  E5 Section 7.4, allow SourceCharacter (which is any 16-bit
			 *  code point).
			 */

			/* DUK__ADVANCECHARS(lex_ctx, 2) would be correct here, but not necessary */
			duk__lexer_skip_to_endofline(lex_ctx);
			goto restart;  /* line terminator will be handled on next round */
		} else if (DUK__L1() == DUK_ASC_STAR) {
			/*
			 *  E5 Section 7.4.  If the multi-line comment contains a newline,
			 *  it is treated like a single line terminator for automatic
			 *  semicolon insertion.
			 */

			duk_bool_t last_asterisk = 0;
			DUK__ADVANCECHARS(lex_ctx, 2);
			for (;;) {
				x = DUK__L0();
				if (x < 0) {
					goto fail_unterm_comment;
				}
				DUK__ADVANCECHARS(lex_ctx, 1);
				if (last_asterisk && x == DUK_ASC_SLASH) {
					break;
				}
				if (duk_unicode_is_line_terminator(x)) {
					got_lineterm = 1;
				}
				last_asterisk = (x == DUK_ASC_STAR);
			}
			goto restart_lineupdate;
		} else if (regexp_mode) {
#if defined(DUK_USE_REGEXP_SUPPORT)
			/*
			 *  "/" followed by something in regexp mode.  See E5 Section 7.8.5.
			 *
			 *  RegExp parsing is a bit complex.  First, the regexp body is delimited
			 *  by forward slashes, but the body may also contain forward slashes as
			 *  part of an escape sequence or inside a character class (delimited by
			 *  square brackets).  A mini state machine is used to implement these.
			 *
			 *  Further, an early (parse time) error must be thrown if the regexp
			 *  would cause a run-time error when used in the expression new RegExp(...).
			 *  Parsing here simply extracts the (candidate) regexp, and also accepts
			 *  invalid regular expressions (which are delimited properly).  The caller
			 *  (compiler) must perform final validation and regexp compilation.
			 *
			 *  RegExp first char may not be '/' (single line comment) or '*' (multi-
			 *  line comment).  These have already been checked above, so there is no
			 *  need below for special handling of the first regexp character as in
			 *  the E5 productions.
			 *
			 *  About unicode escapes within regexp literals:
			 *
			 *      E5 Section 7.8.5 grammar does NOT accept \uHHHH escapes.
			 *      However, Section 6 states that regexps accept the escapes,
			 *      see paragraph starting with "In string literals...".
			 *      The regexp grammar, which sees the decoded regexp literal
			 *      (after lexical parsing) DOES have a \uHHHH unicode escape.
			 *      So, for instance:
			 *
			 *          /\u1234/
			 *
			 *      should first be parsed by the lexical grammar as:
			 *
			 *          '\' 'u'      RegularExpressionBackslashSequence
			 *          '1'          RegularExpressionNonTerminator
			 *          '2'          RegularExpressionNonTerminator
			 *          '3'          RegularExpressionNonTerminator
			 *          '4'          RegularExpressionNonTerminator
			 *
			 *      and the escape itself is then parsed by the regexp engine.
			 *      This is the current implementation.
			 *
			 *  Minor spec inconsistency:
			 *
			 *      E5 Section 7.8.5 RegularExpressionBackslashSequence is:
			 *
			 *         \ RegularExpressionNonTerminator
			 *
			 *      while Section A.1 RegularExpressionBackslashSequence is:
			 *
			 *         \ NonTerminator
			 *
			 *      The latter is not normative and a typo.
			 *
			 */

			/* first, parse regexp body roughly */

			duk_small_int_t state = 0;  /* 0=base, 1=esc, 2=class, 3=class+esc */

			DUK__INITBUFFER(lex_ctx);
			for (;;) {
				DUK__ADVANCECHARS(lex_ctx, 1);  /* skip opening slash on first loop */
				x = DUK__L0();
				if (x < 0 || duk_unicode_is_line_terminator(x)) {
					goto fail_unterm_regexp;
				}
				x = DUK__L0();  /* re-read to avoid spill / fetch */
				if (state == 0) {
					if (x == DUK_ASC_SLASH) {
						DUK__ADVANCECHARS(lex_ctx, 1);  /* eat closing slash */
						break;
					} else if (x == DUK_ASC_BACKSLASH) {
						state = 1;
					} else if (x == DUK_ASC_LBRACKET) {
						state = 2;
					}
				} else if (state == 1) {
					state = 0;
				} else if (state == 2) {
					if (x == DUK_ASC_RBRACKET) {
						state = 0;
					} else if (x == DUK_ASC_BACKSLASH) {
						state = 3;
					}
				} else { /* state == 3 */
					state = 2;
				}
				DUK__APPENDBUFFER(lex_ctx, x);
			}
			out_token->str1 = duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);

			/* second, parse flags */

			DUK__INITBUFFER(lex_ctx);
			for (;;) {
				x = DUK__L0();
				if (!duk_unicode_is_identifier_part(x)) {
					break;
				}
				x = DUK__L0();  /* re-read to avoid spill / fetch */
				DUK__APPENDBUFFER(lex_ctx, x);
				DUK__ADVANCECHARS(lex_ctx, 1);
			}
			out_token->str2 = duk__internbuffer(lex_ctx, lex_ctx->slot2_idx);

			DUK__INITBUFFER(lex_ctx);  /* free some memory */

			/* validation of the regexp is caller's responsibility */

			advtok = DUK__ADVTOK(0, DUK_TOK_REGEXP);
#else  /* DUK_USE_REGEXP_SUPPORT */
			goto fail_regexp_support;
#endif  /* DUK_USE_REGEXP_SUPPORT */
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			/* "/=" and not in regexp mode */
			advtok = DUK__ADVTOK(2, DUK_TOK_DIV_EQ);
		} else {
			/* "/" and not in regexp mode */
			advtok = DUK__ADVTOK(1, DUK_TOK_DIV);
		}
		break;
	case DUK_ASC_LCURLY:  /* '{' */
		advtok = DUK__ADVTOK(1, DUK_TOK_LCURLY);
		break;
	case DUK_ASC_RCURLY:  /* '}' */
		advtok = DUK__ADVTOK(1, DUK_TOK_RCURLY);
		break;
	case DUK_ASC_LPAREN:  /* '(' */
		advtok = DUK__ADVTOK(1, DUK_TOK_LPAREN);
		break;
	case DUK_ASC_RPAREN:  /* ')' */
		advtok = DUK__ADVTOK(1, DUK_TOK_RPAREN);
		break;
	case DUK_ASC_LBRACKET:  /* '[' */
		advtok = DUK__ADVTOK(1, DUK_TOK_LBRACKET);
		break;
	case DUK_ASC_RBRACKET:  /* ']' */
		advtok = DUK__ADVTOK(1, DUK_TOK_RBRACKET);
		break;
	case DUK_ASC_PERIOD:  /* '.' */
		if (DUK__ISDIGIT(DUK__L1())) {
			/* Period followed by a digit can only start DecimalLiteral
			 * (handled in slow path).  We could jump straight into the
			 * DecimalLiteral handling but should avoid goto to inside
			 * a block.
			 */
			goto slow_path;
		}
		advtok = DUK__ADVTOK(1, DUK_TOK_PERIOD);
		break;
	case DUK_ASC_SEMICOLON:  /* ';' */
		advtok = DUK__ADVTOK(1, DUK_TOK_SEMICOLON);
		break;
	case DUK_ASC_COMMA:  /* ',' */
		advtok = DUK__ADVTOK(1, DUK_TOK_COMMA);
		break;
	case DUK_ASC_LANGLE:  /* '<' */
#if defined(DUK_USE_HTML_COMMENTS)
		if (DUK__L1() == DUK_ASC_EXCLAMATION && DUK__L2() == DUK_ASC_MINUS && DUK__L3() == DUK_ASC_MINUS) {
			/*
			 *  ES2015: B.1.3, handle "<!--" SingleLineHTMLOpenComment
			 */

			/* DUK__ADVANCECHARS(lex_ctx, 4) would be correct here, but not necessary */
			duk__lexer_skip_to_endofline(lex_ctx);
			goto restart;  /* line terminator will be handled on next round */
		}
		else
#endif  /* DUK_USE_HTML_COMMENTS */
		if (DUK__L1() == DUK_ASC_LANGLE && DUK__L2() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(3, DUK_TOK_ALSHIFT_EQ);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_LE);
		} else if (DUK__L1() == DUK_ASC_LANGLE) {
			advtok = DUK__ADVTOK(2, DUK_TOK_ALSHIFT);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_LT);
		}
		break;
	case DUK_ASC_RANGLE:  /* '>' */
		if (DUK__L1() == DUK_ASC_RANGLE && DUK__L2() == DUK_ASC_RANGLE && DUK__L3() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(4, DUK_TOK_RSHIFT_EQ);
		} else if (DUK__L1() == DUK_ASC_RANGLE && DUK__L2() == DUK_ASC_RANGLE) {
			advtok = DUK__ADVTOK(3, DUK_TOK_RSHIFT);
		} else if (DUK__L1() == DUK_ASC_RANGLE && DUK__L2() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(3, DUK_TOK_ARSHIFT_EQ);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_GE);
		} else if (DUK__L1() == DUK_ASC_RANGLE) {
			advtok = DUK__ADVTOK(2, DUK_TOK_ARSHIFT);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_GT);
		}
		break;
	case DUK_ASC_EQUALS:  /* '=' */
		if (DUK__L1() == DUK_ASC_EQUALS && DUK__L2() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(3, DUK_TOK_SEQ);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_EQUALSIGN);
		}
		break;
	case DUK_ASC_EXCLAMATION:  /* '!' */
		if (DUK__L1() == DUK_ASC_EQUALS && DUK__L2() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(3, DUK_TOK_SNEQ);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_NEQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_LNOT);
		}
		break;
	case DUK_ASC_PLUS:  /* '+' */
		if (DUK__L1() == DUK_ASC_PLUS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_INCREMENT);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_ADD_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_ADD);
		}
		break;
	case DUK_ASC_MINUS:  /* '-' */
#if defined(DUK_USE_HTML_COMMENTS)
		if (got_lineterm && DUK__L1() == DUK_ASC_MINUS && DUK__L2() == DUK_ASC_RANGLE) {
			/*
			 *  ES2015: B.1.3, handle "-->" SingleLineHTMLCloseComment
			 *  Only allowed:
			 *  - on new line
			 *  - preceded only by whitespace
			 *  - preceded by end of multiline comment and optional whitespace
			 *
			 * Since whitespace generates no tokens, and multiline comments
			 * are treated as a line ending, consulting `got_lineterm` is
			 * sufficient to test for these three options.
			 */

			/* DUK__ADVANCECHARS(lex_ctx, 3) would be correct here, but not necessary */
			duk__lexer_skip_to_endofline(lex_ctx);
			goto restart;  /* line terminator will be handled on next round */
		} else
#endif  /* DUK_USE_HTML_COMMENTS */
		if (DUK__L1() == DUK_ASC_MINUS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_DECREMENT);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_SUB_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_SUB);
		}
		break;
	case DUK_ASC_STAR:  /* '*' */
#if defined(DUK_USE_ES7_EXP_OPERATOR)
		if (DUK__L1() == DUK_ASC_STAR && DUK__L2() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(3, DUK_TOK_EXP_EQ);
		} else if (DUK__L1() == DUK_ASC_STAR) {
			advtok = DUK__ADVTOK(2, DUK_TOK_EXP);
		} else
#endif
		if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_MUL_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_MUL);
		}
		break;
	case DUK_ASC_PERCENT:  /* '%' */
		if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_MOD_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_MOD);
		}
		break;
	case DUK_ASC_AMP:  /* '&' */
		if (DUK__L1() == DUK_ASC_AMP) {
			advtok = DUK__ADVTOK(2, DUK_TOK_LAND);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_BAND_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_BAND);
		}
		break;
	case DUK_ASC_PIPE:  /* '|' */
		if (DUK__L1() == DUK_ASC_PIPE) {
			advtok = DUK__ADVTOK(2, DUK_TOK_LOR);
		} else if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_BOR_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_BOR);
		}
		break;
	case DUK_ASC_CARET:  /* '^' */
		if (DUK__L1() == DUK_ASC_EQUALS) {
			advtok = DUK__ADVTOK(2, DUK_TOK_BXOR_EQ);
		} else {
			advtok = DUK__ADVTOK(1, DUK_TOK_BXOR);
		}
		break;
	case DUK_ASC_TILDE:  /* '~' */
		advtok = DUK__ADVTOK(1, DUK_TOK_BNOT);
		break;
	case DUK_ASC_QUESTION:  /* '?' */
		advtok = DUK__ADVTOK(1, DUK_TOK_QUESTION);
		break;
	case DUK_ASC_COLON:  /* ':' */
		advtok = DUK__ADVTOK(1, DUK_TOK_COLON);
		break;
	case DUK_ASC_DOUBLEQUOTE:    /* '"' */
	case DUK_ASC_SINGLEQUOTE: {  /* '\'' */
		DUK__INITBUFFER(lex_ctx);
		duk__lexer_parse_string_literal(lex_ctx, out_token, x /*quote*/, strict_mode);
		duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
		out_token->str1 = duk_known_hstring(lex_ctx->thr, lex_ctx->slot1_idx);

		DUK__INITBUFFER(lex_ctx);  /* free some memory */

		advtok = DUK__ADVTOK(0, DUK_TOK_STRING);
		break;
	}
	default:
		goto slow_path;
	}  /* switch */

	goto skip_slow_path;

 slow_path:
	if (duk_unicode_is_line_terminator(x)) {
		if (x == 0x000d && DUK__L1() == 0x000a) {
			/*
			 *  E5 Section 7.3: CR LF is detected as a single line terminator for
			 *  line numbers.  Here we also detect it as a single line terminator
			 *  token.
			 */
			DUK__ADVANCECHARS(lex_ctx, 2);
		} else {
			DUK__ADVANCECHARS(lex_ctx, 1);
		}
		got_lineterm = 1;
		goto restart_lineupdate;
	} else if (duk_unicode_is_identifier_start(x) || x == DUK_ASC_BACKSLASH) {
		/*
		 *  Parse an identifier and then check whether it is:
		 *    - reserved word (keyword or other reserved word)
		 *    - "null"  (NullLiteral)
		 *    - "true"  (BooleanLiteral)
		 *    - "false" (BooleanLiteral)
		 *    - anything else => identifier
		 *
		 *  This does not follow the E5 productions cleanly, but is
		 *  useful and compact.
		 *
		 *  Note that identifiers may contain Unicode escapes,
		 *  see E5 Sections 6 and 7.6.  They must be decoded first,
		 *  and the result checked against allowed characters.
		 *  The above if-clause accepts an identifier start and an
		 *  '\' character -- no other token can begin with a '\'.
		 *
		 *  Note that "get" and "set" are not reserved words in E5
		 *  specification so they are recognized as plain identifiers
		 *  (the tokens DUK_TOK_GET and DUK_TOK_SET are actually not
		 *  used now).  The compiler needs to work around this.
		 *
		 *  Strictly speaking, following ECMAScript longest match
		 *  specification, an invalid escape for the first character
		 *  should cause a syntax error.  However, an invalid escape
		 *  for IdentifierParts should just terminate the identifier
		 *  early (longest match), and let the next tokenization
		 *  fail.  For instance Rhino croaks with 'foo\z' when
		 *  parsing the identifier.  This has little practical impact.
		 */

		duk_small_uint_t i, i_end;
		duk_bool_t first = 1;
		duk_hstring *str;

		DUK__INITBUFFER(lex_ctx);
		for (;;) {
			/* re-lookup first char on first loop */
			if (DUK__L0() == DUK_ASC_BACKSLASH) {
				duk_codepoint_t esc_cp;
				if (DUK__L1() != DUK_ASC_LC_U) {
					goto fail_escape;
				}
				esc_cp = duk__lexer_parse_escape(lex_ctx, 1 /*allow_es6*/);
				DUK__APPENDBUFFER(lex_ctx, esc_cp);

				/* IdentifierStart is stricter than IdentifierPart, so if the first
				 * character is escaped, must have a stricter check here.
				 */
				if (!(first ? duk_unicode_is_identifier_start(esc_cp) : duk_unicode_is_identifier_part(esc_cp))) {
					goto fail_escape;
				}

				/* Track number of escapes: necessary for proper keyword
				 * detection.
				 */
				out_token->num_escapes++;
			} else {
				/* Note: first character is checked against this.  But because
				 * IdentifierPart includes all IdentifierStart characters, and
				 * the first character (if unescaped) has already been checked
				 * in the if condition, this is OK.
				 */
				if (!duk_unicode_is_identifier_part(DUK__L0())) {
					break;
				}
				DUK__APPENDBUFFER(lex_ctx, DUK__L0());
				DUK__ADVANCECHARS(lex_ctx, 1);
			}
			first = 0;
		}

		out_token->str1 = duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);
		str = out_token->str1;
		out_token->t_nores = DUK_TOK_IDENTIFIER;

		DUK__INITBUFFER(lex_ctx);  /* free some memory */

		/*
		 *  Interned identifier is compared against reserved words, which are
		 *  currently interned into the heap context.  See genbuiltins.py.
		 *
		 *  Note that an escape in the identifier disables recognition of
		 *  keywords; e.g. "\u0069f = 1;" is a valid statement (assigns to
		 *  identifier named "if").  This is not necessarily compliant,
		 *  see test-dec-escaped-char-in-keyword.js.
		 *
		 *  Note: "get" and "set" are awkward.  They are not officially
		 *  ReservedWords (and indeed e.g. "var set = 1;" is valid), and
		 *  must come out as DUK_TOK_IDENTIFIER.  The compiler needs to
		 *  work around this a bit.
		 */

		/* XXX: optimize by adding the token numbers directly into the
		 * always interned duk_hstring objects (there should be enough
		 * flag bits free for that)?
		 */

		i_end = (strict_mode ? DUK_STRIDX_END_RESERVED : DUK_STRIDX_START_STRICT_RESERVED);

		advtok = DUK__ADVTOK(0, DUK_TOK_IDENTIFIER);
		if (out_token->num_escapes == 0) {
			for (i = DUK_STRIDX_START_RESERVED; i < i_end; i++) {
				DUK_ASSERT_DISABLE(i >= 0);  /* unsigned */
				DUK_ASSERT(i < DUK_HEAP_NUM_STRINGS);
				if (DUK_HTHREAD_GET_STRING(lex_ctx->thr, i) == str) {
					advtok = DUK__ADVTOK(0, DUK_STRIDX_TO_TOK(i));
					break;
				}
			}
		}
	} else if (DUK__ISDIGIT(x) || (x == DUK_ASC_PERIOD)) {
		/* Note: decimal number may start with a period, but must be followed by a digit */

		/*
		 *  Pre-parsing for decimal, hex, octal (both legacy and ES2015),
		 *  and binary literals, followed by an actual parser step
		 *  provided by numconv.
		 *
		 *  Note: the leading sign character ('+' or '-') is -not- part of
		 *  the production in E5 grammar, and that the a DecimalLiteral
		 *  starting with a '0' must be followed by a non-digit.
		 *
		 *  XXX: the two step parsing process is quite awkward, it would
		 *  be more straightforward to allow numconv to parse the longest
		 *  valid prefix (it already does that, it only needs to indicate
		 *  where the input ended).  However, the lexer decodes characters
		 *  using a limited lookup window, so this is not a trivial change.
		 */

		/* XXX: because of the final check below (that the literal is not
		 * followed by a digit), this could maybe be simplified, if we bail
		 * out early from a leading zero (and if there are no periods etc).
		 * Maybe too complex.
		 */

		duk_double_t val;
		duk_bool_t legacy_oct = 0;
		duk_small_int_t state;  /* 0=before period/exp,
		                         * 1=after period, before exp
		                         * 2=after exp, allow '+' or '-'
		                         * 3=after exp and exp sign
		                         */
		duk_small_uint_t s2n_flags;
		duk_codepoint_t y, z;
		duk_small_int_t s2n_radix = 10;
		duk_small_uint_t pre_adv = 0;

		DUK__INITBUFFER(lex_ctx);
		y = DUK__L1();

		if (x == DUK_ASC_0) {
			z = DUK_LOWERCASE_CHAR_ASCII(y);

			pre_adv = 2;  /* default for 0xNNN, 0oNNN, 0bNNN. */
			if (z == DUK_ASC_LC_X) {
				s2n_radix = 16;
			} else if (z == DUK_ASC_LC_O) {
				s2n_radix = 8;
			} else if (z == DUK_ASC_LC_B) {
				s2n_radix = 2;
			} else {
				pre_adv = 0;
				if (DUK__ISDIGIT(y)) {
					if (strict_mode) {
						/* Reject octal like \07 but also octal-lookalike
						 * decimal like \08 in strict mode.
						 */
						goto fail_number_literal;
					} else {
						/* Legacy OctalIntegerLiteral or octal-lookalice
						 * decimal.  Deciding between the two happens below
						 * in digit scanning.
						 */
						DUK__APPENDBUFFER(lex_ctx, x);
						pre_adv = 1;
						legacy_oct = 1;
						s2n_radix = 8;  /* tentative unless conflicting digits found */
					}
				}
			}
		}

		DUK__ADVANCECHARS(lex_ctx, pre_adv);

		/* XXX: we could parse integers here directly, and fall back
		 * to numconv only when encountering a fractional expression
		 * or when an octal literal turned out to be decimal (0778 etc).
		 */
		state = 0;
		for (;;) {
			x = DUK__L0();  /* re-lookup curr char on first round */
			if (DUK__ISDIGIT(x)) {
				/* Note: intentionally allow leading zeroes here, as the
				 * actual parser will check for them.
				 */
				if (state == 0 && legacy_oct && (x == DUK_ASC_8 || x == DUK_ASC_9)) {
					/* Started out as an octal-lookalike
					 * but interpreted as decimal, e.g.
					 * '0779' -> 779.  This also means
					 * that fractions are allowed, e.g.
					 * '0779.123' is allowed but '0777.123'
					 * is not!
					 */
					s2n_radix = 10;
				}
				if (state == 2) {
					state = 3;
				}
			} else if (s2n_radix == 16 && DUK__ISHEXDIGIT(x)) {
				/* Note: 'e' and 'E' are also accepted here. */
				;
			} else if (x == DUK_ASC_PERIOD) {
				if (state >= 1 || s2n_radix != 10) {
					break;
				} else {
					state = 1;
				}
			} else if (x == DUK_ASC_LC_E || x == DUK_ASC_UC_E) {
				if (state >= 2 || s2n_radix != 10) {
					break;
				} else {
					state = 2;
				}
			} else if (x == DUK_ASC_MINUS || x == DUK_ASC_PLUS) {
				if (state != 2) {
					break;
				} else {
					state = 3;
				}
			} else {
				break;
			}
			DUK__APPENDBUFFER(lex_ctx, x);
			DUK__ADVANCECHARS(lex_ctx, 1);
		}

		/* XXX: better coercion */
		(void) duk__internbuffer(lex_ctx, lex_ctx->slot1_idx);

		if (s2n_radix != 10) {
			/* For bases other than 10, integer only. */
			s2n_flags = DUK_S2N_FLAG_ALLOW_LEADING_ZERO;
		} else {
			s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
			            DUK_S2N_FLAG_ALLOW_FRAC |
			            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
			            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
			            DUK_S2N_FLAG_ALLOW_LEADING_ZERO;
		}

		duk_dup(lex_ctx->thr, lex_ctx->slot1_idx);
		duk_numconv_parse(lex_ctx->thr, s2n_radix, s2n_flags);
		val = duk_to_number_m1(lex_ctx->thr);
		if (DUK_ISNAN(val)) {
			goto fail_number_literal;
		}
		duk_replace(lex_ctx->thr, lex_ctx->slot1_idx);  /* could also just pop? */

		DUK__INITBUFFER(lex_ctx);  /* free some memory */

		/* Section 7.8.3 (note): NumericLiteral must be followed by something other than
		 * IdentifierStart or DecimalDigit.
		 */

		if (DUK__ISDIGIT(DUK__L0()) || duk_unicode_is_identifier_start(DUK__L0())) {
			goto fail_number_literal;
		}

		out_token->num = val;
		advtok = DUK__ADVTOK(0, DUK_TOK_NUMBER);
	} else if (duk_unicode_is_whitespace(DUK__LOOKUP(lex_ctx, 0))) {
		DUK__ADVANCECHARS(lex_ctx, 1);
		goto restart;
	} else if (x < 0) {
		advtok = DUK__ADVTOK(0, DUK_TOK_EOF);
	} else {
		goto fail_token;
	}
 skip_slow_path:

	/*
	 *  Shared exit path
	 */

	DUK__ADVANCEBYTES(lex_ctx, advtok >> 8);
	out_token->t = advtok & 0xff;
	if (out_token->t_nores == DUK_TOK_INVALID) {
		out_token->t_nores = out_token->t;
	}
	out_token->lineterm = got_lineterm;

	/* Automatic semicolon insertion is allowed if a token is preceded
	 * by line terminator(s), or terminates a statement list (right curly
	 * or EOF).
	 */
	if (got_lineterm || out_token->t == DUK_TOK_RCURLY || out_token->t == DUK_TOK_EOF) {
		out_token->allow_auto_semi = 1;
	} else {
		out_token->allow_auto_semi = 0;
	}

	return;

 fail_token_limit:
	DUK_ERROR_RANGE(lex_ctx->thr, DUK_STR_TOKEN_LIMIT);
	DUK_WO_NORETURN(return;);

 fail_token:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_TOKEN);
	DUK_WO_NORETURN(return;);

 fail_number_literal:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_NUMBER_LITERAL);
	DUK_WO_NORETURN(return;);

 fail_escape:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_ESCAPE);
	DUK_WO_NORETURN(return;);

 fail_unterm_regexp:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_UNTERMINATED_REGEXP);
	DUK_WO_NORETURN(return;);

 fail_unterm_comment:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_UNTERMINATED_COMMENT);
	DUK_WO_NORETURN(return;);

#if !defined(DUK_USE_REGEXP_SUPPORT)
 fail_regexp_support:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_REGEXP_SUPPORT_DISABLED);
	DUK_WO_NORETURN(return;);
#endif
}

#if defined(DUK_USE_REGEXP_SUPPORT)

/*
 *  Parse a RegExp token.  The grammar is described in E5 Section 15.10.
 *  Terminal constructions (such as quantifiers) are parsed directly here.
 *
 *  0xffffffffU is used as a marker for "infinity" in quantifiers.  Further,
 *  DUK__MAX_RE_QUANT_DIGITS limits the maximum number of digits that
 *  will be accepted for a quantifier.
 */

DUK_INTERNAL void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token) {
	duk_small_uint_t advtok = 0;  /* init is unnecessary but suppresses "may be used uninitialized" warnings */
	duk_codepoint_t x, y;

	if (++lex_ctx->token_count >= lex_ctx->token_limit) {
		goto fail_token_limit;
	}

	duk_memzero(out_token, sizeof(*out_token));

	x = DUK__L0();
	y = DUK__L1();

	DUK_DDD(DUK_DDDPRINT("parsing regexp token, L0=%ld, L1=%ld", (long) x, (long) y));

	switch (x) {
	case DUK_ASC_PIPE: {
		advtok = DUK__ADVTOK(1, DUK_RETOK_DISJUNCTION);
		break;
	}
	case DUK_ASC_CARET: {
		advtok = DUK__ADVTOK(1, DUK_RETOK_ASSERT_START);
		break;
	}
	case DUK_ASC_DOLLAR: {
		advtok = DUK__ADVTOK(1, DUK_RETOK_ASSERT_END);
		break;
	}
	case DUK_ASC_QUESTION: {
		out_token->qmin = 0;
		out_token->qmax = 1;
		if (y == DUK_ASC_QUESTION) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case DUK_ASC_STAR: {
		out_token->qmin = 0;
		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
		if (y == DUK_ASC_QUESTION) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case DUK_ASC_PLUS: {
		out_token->qmin = 1;
		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
		if (y == DUK_ASC_QUESTION) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = DUK__ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case DUK_ASC_LCURLY: {
		/* Production allows 'DecimalDigits', including leading zeroes */
		duk_uint32_t val1 = 0;
		duk_uint32_t val2 = DUK_RE_QUANTIFIER_INFINITE;
		duk_small_int_t digits = 0;
#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
		duk_lexer_point lex_pt;
#endif

#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
		/* Store lexer position, restoring if quantifier is invalid. */
		DUK_LEXER_GETPOINT(lex_ctx, &lex_pt);
#endif

		for (;;) {
			DUK__ADVANCECHARS(lex_ctx, 1);  /* eat '{' on entry */
			x = DUK__L0();
			if (DUK__ISDIGIT(x)) {
				digits++;
				val1 = val1 * 10 + (duk_uint32_t) duk__hexval(x);
			} else if (x == DUK_ASC_COMMA) {
				if (digits > DUK__MAX_RE_QUANT_DIGITS) {
					goto invalid_quantifier;
				}
				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
					goto invalid_quantifier;
				}
				if (DUK__L1() == DUK_ASC_RCURLY) {
					/* form: { DecimalDigits , }, val1 = min count */
					if (digits == 0) {
						goto invalid_quantifier;
					}
					out_token->qmin = val1;
					out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
					DUK__ADVANCECHARS(lex_ctx, 2);
					break;
				}
				val2 = val1;
				val1 = 0;
				digits = 0;  /* not strictly necessary because of lookahead '}' above */
			} else if (x == DUK_ASC_RCURLY) {
				if (digits > DUK__MAX_RE_QUANT_DIGITS) {
					goto invalid_quantifier;
				}
				if (digits == 0) {
					goto invalid_quantifier;
				}
				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
					/* val2 = min count, val1 = max count */
					out_token->qmin = val2;
					out_token->qmax = val1;
				} else {
					/* val1 = count */
					out_token->qmin = val1;
					out_token->qmax = val1;
				}
				DUK__ADVANCECHARS(lex_ctx, 1);
				break;
			} else {
				goto invalid_quantifier;
			}
		}
		if (DUK__L0() == DUK_ASC_QUESTION) {
			out_token->greedy = 0;
			DUK__ADVANCECHARS(lex_ctx, 1);
		} else {
			out_token->greedy = 1;
		}
		advtok = DUK__ADVTOK(0, DUK_RETOK_QUANTIFIER);
		break;
 invalid_quantifier:
#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
		/* Failed to match the quantifier, restore lexer and parse
		 * opening brace as a literal.
		 */
		DUK_LEXER_SETPOINT(lex_ctx, &lex_pt);
		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_CHAR);
		out_token->num = DUK_ASC_LCURLY;
#else
		goto fail_quantifier;
#endif
		break;
	}
	case DUK_ASC_PERIOD: {
		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_PERIOD);
		break;
	}
	case DUK_ASC_BACKSLASH: {
		/* The E5.1 specification does not seem to allow IdentifierPart characters
		 * to be used as identity escapes.  Unfortunately this includes '$', which
		 * cannot be escaped as '\$'; it needs to be escaped e.g. as '\u0024'.
		 * Many other implementations (including V8 and Rhino, for instance) do
		 * accept '\$' as a valid identity escape, which is quite pragmatic, and
		 * ES2015 Annex B relaxes the rules to allow these (and other) real world forms.
		 */

		advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_CHAR);  /* default: char escape (two chars) */
		if (y == DUK_ASC_LC_B) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ASSERT_WORD_BOUNDARY);
		} else if (y == DUK_ASC_UC_B) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY);
		} else if (y == DUK_ASC_LC_F) {
			out_token->num = 0x000c;
		} else if (y == DUK_ASC_LC_N) {
			out_token->num = 0x000a;
		} else if (y == DUK_ASC_LC_T) {
			out_token->num = 0x0009;
		} else if (y == DUK_ASC_LC_R) {
			out_token->num = 0x000d;
		} else if (y == DUK_ASC_LC_V) {
			out_token->num = 0x000b;
		} else if (y == DUK_ASC_LC_C) {
			x = DUK__L2();
			if ((x >= DUK_ASC_LC_A && x <= DUK_ASC_LC_Z) ||
			    (x >= DUK_ASC_UC_A && x <= DUK_ASC_UC_Z)) {
				out_token->num = (duk_uint32_t) (x % 32);
				advtok = DUK__ADVTOK(3, DUK_RETOK_ATOM_CHAR);
			} else {
				goto fail_escape;
			}
		} else if (y == DUK_ASC_LC_X || y == DUK_ASC_LC_U) {
			/* The token value is the Unicode codepoint without
			 * it being decode into surrogate pair characters
			 * here.  The \u{H+} is only allowed in Unicode mode
			 * which we don't support yet.
			 */
			out_token->num = (duk_uint32_t) duk__lexer_parse_escape(lex_ctx, 0 /*allow_es6*/);
			advtok = DUK__ADVTOK(0, DUK_RETOK_ATOM_CHAR);
		} else if (y == DUK_ASC_LC_D) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_DIGIT);
		} else if (y == DUK_ASC_UC_D) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_DIGIT);
		} else if (y == DUK_ASC_LC_S) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_WHITE);
		} else if (y == DUK_ASC_UC_S) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_WHITE);
		} else if (y == DUK_ASC_LC_W) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_WORD_CHAR);
		} else if (y == DUK_ASC_UC_W) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_NOT_WORD_CHAR);
		} else if (DUK__ISDIGIT(y)) {
			/* E5 Section 15.10.2.11 */
			if (y == DUK_ASC_0) {
				if (DUK__ISDIGIT(DUK__L2())) {
					goto fail_escape;
				}
				out_token->num = 0x0000;
				advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_CHAR);
			} else {
				/* XXX: shared parsing? */
				duk_uint32_t val = 0;
				duk_small_int_t i;
				for (i = 0; ; i++) {
					if (i >= DUK__MAX_RE_DECESC_DIGITS) {
						goto fail_escape;
					}
					DUK__ADVANCECHARS(lex_ctx, 1);  /* eat backslash on entry */
					x = DUK__L0();
					if (!DUK__ISDIGIT(x)) {
						break;
					}
					val = val * 10 + (duk_uint32_t) duk__hexval(x);
				}
				/* DUK__L0() cannot be a digit, because the loop doesn't terminate if it is */
				advtok = DUK__ADVTOK(0, DUK_RETOK_ATOM_BACKREFERENCE);
				out_token->num = val;
			}
#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
		} else if (y >= 0) {
			/* For ES2015 Annex B, accept any source character as identity
			 * escape except 'c' which is used for control characters.
			 * http://www.ecma-international.org/ecma-262/6.0/#sec-regular-expressions-patterns
			 * Careful not to match end-of-buffer (<0) here.
			 * This is not yet full ES2015 Annex B because cases above
			 * (like hex escape) won't backtrack.
			 */
			DUK_ASSERT(y != DUK_ASC_LC_C);  /* covered above */
#else  /* DUK_USE_ES6_REGEXP_SYNTAX */
		} else if ((y >= 0 && !duk_unicode_is_identifier_part(y)) ||
		           y == DUK_UNICODE_CP_ZWNJ ||
		           y == DUK_UNICODE_CP_ZWJ) {
			/* For ES5.1 identity escapes are not allowed for identifier
			 * parts.  This conflicts with a lot of real world code as this
			 * doesn't e.g. allow escaping a dollar sign as /\$/, see
			 * test-regexp-identity-escape-dollar.js.
			 */
#endif  /* DUK_USE_ES6_REGEXP_SYNTAX */
			out_token->num = (duk_uint32_t) y;
		} else {
			goto fail_escape;
		}
		break;
	}
	case DUK_ASC_LPAREN: {
		/* XXX: naming is inconsistent: ATOM_END_GROUP ends an ASSERT_START_LOOKAHEAD */

		if (y == DUK_ASC_QUESTION) {
			if (DUK__L2() == DUK_ASC_EQUALS) {
				/* (?= */
				advtok = DUK__ADVTOK(3, DUK_RETOK_ASSERT_START_POS_LOOKAHEAD);
			} else if (DUK__L2() == DUK_ASC_EXCLAMATION) {
				/* (?! */
				advtok = DUK__ADVTOK(3, DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD);
			} else if (DUK__L2() == DUK_ASC_COLON) {
				/* (?: */
				advtok = DUK__ADVTOK(3, DUK_RETOK_ATOM_START_NONCAPTURE_GROUP);
			} else {
				goto fail_group;
			}
		} else {
			/* ( */
			advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_START_CAPTURE_GROUP);
		}
		break;
	}
	case DUK_ASC_RPAREN: {
		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_END_GROUP);
		break;
	}
	case DUK_ASC_LBRACKET: {
		/*
		 *  To avoid creating a heavy intermediate value for the list of ranges,
		 *  only the start token ('[' or '[^') is parsed here.  The regexp
		 *  compiler parses the ranges itself.
		 */

		/* XXX: with DUK_USE_ES6_REGEXP_SYNTAX we should allow left bracket
		 * literal too, but it's not easy to parse without backtracking.
		 */

		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_START_CHARCLASS);
		if (y == DUK_ASC_CARET) {
			advtok = DUK__ADVTOK(2, DUK_RETOK_ATOM_START_CHARCLASS_INVERTED);
		}
		break;
	}
#if !defined(DUK_USE_ES6_REGEXP_SYNTAX)
	case DUK_ASC_RCURLY:
	case DUK_ASC_RBRACKET: {
		/* Although these could be parsed as PatternCharacters unambiguously (here),
		 * E5 Section 15.10.1 grammar explicitly forbids these as PatternCharacters.
		 */
		goto fail_invalid_char;
		break;
	}
#endif
	case -1: {
		/* EOF */
		advtok = DUK__ADVTOK(0, DUK_TOK_EOF);
		break;
	}
	default: {
		/* PatternCharacter, all excluded characters are matched by cases above */
		advtok = DUK__ADVTOK(1, DUK_RETOK_ATOM_CHAR);
		out_token->num = (duk_uint32_t) x;
		break;
	}
	}

	/*
	 *  Shared exit path
	 */

	DUK__ADVANCEBYTES(lex_ctx, advtok >> 8);
	out_token->t = advtok & 0xff;
	return;

 fail_token_limit:
	DUK_ERROR_RANGE(lex_ctx->thr, DUK_STR_TOKEN_LIMIT);
	DUK_WO_NORETURN(return;);

 fail_escape:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_ESCAPE);
	DUK_WO_NORETURN(return;);

 fail_group:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_GROUP);
	DUK_WO_NORETURN(return;);

#if !defined(DUK_USE_ES6_REGEXP_SYNTAX)
 fail_invalid_char:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_CHARACTER);
	DUK_WO_NORETURN(return;);

 fail_quantifier:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_QUANTIFIER);
	DUK_WO_NORETURN(return;);
#endif
}

/*
 *  Special parser for character classes; calls callback for every
 *  range parsed and returns the number of ranges present.
 */

/* XXX: this duplicates functionality in duk_regexp.c where a similar loop is
 * required anyway.  We could use that BUT we need to update the regexp compiler
 * 'nranges' too.  Work this out a bit more cleanly to save space.
 */

/* XXX: the handling of character range detection is a bit convoluted.
 * Try to simplify and make smaller.
 */

/* XXX: logic for handling character ranges is now incorrect, it will accept
 * e.g. [\d-z] whereas it should croak from it?  SMJS accepts this too, though.
 *
 * Needs a read through and a lot of additional tests.
 */

DUK_LOCAL
void duk__emit_u16_direct_ranges(duk_lexer_ctx *lex_ctx,
                                 duk_re_range_callback gen_range,
                                 void *userdata,
                                 const duk_uint16_t *ranges,
                                 duk_small_int_t num) {
	const duk_uint16_t *ranges_end;

	DUK_UNREF(lex_ctx);

	ranges_end = ranges + num;
	while (ranges < ranges_end) {
		/* mark range 'direct', bypass canonicalization (see Wiki) */
		gen_range(userdata, (duk_codepoint_t) ranges[0], (duk_codepoint_t) ranges[1], 1);
		ranges += 2;
	}
}

DUK_INTERNAL void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata) {
	duk_codepoint_t start = -1;
	duk_codepoint_t ch;
	duk_codepoint_t x;
	duk_bool_t dash = 0;
	duk_small_uint_t adv = 0;

	DUK_DD(DUK_DDPRINT("parsing regexp ranges"));

	for (;;) {
		DUK__ADVANCECHARS(lex_ctx, adv);
		adv = 1;

		x = DUK__L0();

		ch = -1;  /* not strictly necessary, but avoids "uninitialized variable" warnings */
		DUK_UNREF(ch);

		if (x < 0) {
			goto fail_unterm_charclass;
		} else if (x == DUK_ASC_RBRACKET) {
			if (start >= 0) {
				gen_range(userdata, start, start, 0);
			}
			DUK__ADVANCECHARS(lex_ctx, 1);  /* eat ']' before finishing */
			break;
		} else if (x == DUK_ASC_MINUS) {
			if (start >= 0 && !dash && DUK__L1() != DUK_ASC_RBRACKET) {
				/* '-' as a range indicator */
				dash = 1;
				continue;
			} else {
				/* '-' verbatim */
				ch = x;
			}
		} else if (x == DUK_ASC_BACKSLASH) {
			/*
			 *  The escapes are same as outside a character class, except that \b has a
			 *  different meaning, and \B and backreferences are prohibited (see E5
			 *  Section 15.10.2.19).  However, it's difficult to share code because we
			 *  handle e.g. "\n" very differently: here we generate a single character
			 *  range for it.
			 */

			/* XXX: ES2015 surrogate pair handling. */

			x = DUK__L1();

			adv = 2;

			if (x == DUK_ASC_LC_B) {
				/* Note: '\b' in char class is different than outside (assertion),
				 * '\B' is not allowed and is caught by the duk_unicode_is_identifier_part()
				 * check below.
				 */
				ch = 0x0008;
			} else if (x == DUK_ASC_LC_F) {
				ch = 0x000c;
			} else if (x == DUK_ASC_LC_N) {
				ch = 0x000a;
			} else if (x == DUK_ASC_LC_T) {
				ch = 0x0009;
			} else if (x == DUK_ASC_LC_R) {
				ch = 0x000d;
			} else if (x == DUK_ASC_LC_V) {
				ch = 0x000b;
			} else if (x == DUK_ASC_LC_C) {
				x = DUK__L2();
				adv = 3;
				if ((x >= DUK_ASC_LC_A && x <= DUK_ASC_LC_Z) ||
				    (x >= DUK_ASC_UC_A && x <= DUK_ASC_UC_Z)) {
					ch = (x % 32);
				} else {
					goto fail_escape;
				}
			} else if (x == DUK_ASC_LC_X || x == DUK_ASC_LC_U) {
				/* The \u{H+} form is only allowed in Unicode mode which
				 * we don't support yet.
				 */
				ch = duk__lexer_parse_escape(lex_ctx, 0 /*allow_es6*/);
				adv = 0;
			} else if (x == DUK_ASC_LC_D) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_digit,
				                            sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (x == DUK_ASC_UC_D) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_not_digit,
				                            sizeof(duk_unicode_re_ranges_not_digit) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (x == DUK_ASC_LC_S) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_white,
				                            sizeof(duk_unicode_re_ranges_white) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (x == DUK_ASC_UC_S) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_not_white,
				                            sizeof(duk_unicode_re_ranges_not_white) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (x == DUK_ASC_LC_W) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_wordchar,
				                            sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (x == DUK_ASC_UC_W) {
				duk__emit_u16_direct_ranges(lex_ctx,
				                            gen_range,
				                            userdata,
				                            duk_unicode_re_ranges_not_wordchar,
				                            sizeof(duk_unicode_re_ranges_not_wordchar) / sizeof(duk_uint16_t));
				ch = -1;
			} else if (DUK__ISDIGIT(x)) {
				/* DecimalEscape, only \0 is allowed, no leading
				 * zeroes are allowed.
				 *
				 * ES2015 Annex B also allows (maximal match) legacy
				 * octal escapes up to \377 and \8 and \9 are
				 * accepted as literal '8' and '9', also in strict mode.
				 */

#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
				ch = duk__lexer_parse_legacy_octal(lex_ctx, &adv, 0 /*reject_annex_b*/);
				DUK_ASSERT(ch >= 0);  /* no rejections */
#else
				if (x == DUK_ASC_0 && !DUK__ISDIGIT(DUK__L2())) {
					ch = 0x0000;
				} else {
					goto fail_escape;
				}
#endif
#if defined(DUK_USE_ES6_REGEXP_SYNTAX)
			} else if (x >= 0) {
				/* IdentityEscape: ES2015 Annex B allows almost all
				 * source characters here.  Match anything except
				 * EOF here.
				 */
				ch = x;
#else  /* DUK_USE_ES6_REGEXP_SYNTAX */
			} else if (!duk_unicode_is_identifier_part(x)) {
				/* IdentityEscape: ES5.1 doesn't allow identity escape
				 * for identifier part characters, which conflicts with
				 * some real world code.  For example, it doesn't allow
				 * /[\$]/ which is awkward.
				 */
				ch = x;
#endif  /* DUK_USE_ES6_REGEXP_SYNTAX */
			} else {
				goto fail_escape;
			}
		} else {
			/* character represents itself */
			ch = x;
		}

		/* ch is a literal character here or -1 if parsed entity was
		 * an escape such as "\s".
		 */

		if (ch < 0) {
			/* multi-character sets not allowed as part of ranges, see
			 * E5 Section 15.10.2.15, abstract operation CharacterRange.
			 */
			if (start >= 0) {
				if (dash) {
					goto fail_range;
				} else {
					gen_range(userdata, start, start, 0);
					start = -1;
					/* dash is already 0 */
				}
			}
		} else {
			if (start >= 0) {
				if (dash) {
					if (start > ch) {
						goto fail_range;
					}
					gen_range(userdata, start, ch, 0);
					start = -1;
					dash = 0;
				} else {
					gen_range(userdata, start, start, 0);
					start = ch;
					/* dash is already 0 */
				}
			} else {
				start = ch;
			}
		}
	}

	return;

 fail_escape:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_REGEXP_ESCAPE);
	DUK_WO_NORETURN(return;);

 fail_range:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_INVALID_RANGE);
	DUK_WO_NORETURN(return;);

 fail_unterm_charclass:
	DUK_ERROR_SYNTAX(lex_ctx->thr, DUK_STR_UNTERMINATED_CHARCLASS);
	DUK_WO_NORETURN(return;);
}

#endif  /* DUK_USE_REGEXP_SUPPORT */
