cosmopolitan/third_party/quickjs/parse.c
Justine Tunney ae5d06dc53 Unbloat build config
- 10.5% reduction of o//depend dependency graph
- 8.8% reduction in latency of make command
- Fix issue with temporary file cleanup

There's a new -w option in compile.com that turns off the recent
Landlock output path workaround for "good commands" which do not
unlink() the output file like GNU tooling does.

Our new GNU Make unveil sandboxing appears to have zero overhead
in the grand scheme of things. Full builds are pretty fast since
the only thing that's actually slowed us down is probably libcxx

    make -j16 MODE=rel
    RL: took 85,732,063µs wall time
    RL: ballooned to 323,612kb in size
    RL: needed 828,560,521µs cpu (11% kernel)
    RL: caused 39,080,670 page faults (99% memcpy)
    RL: 350,073 context switches (72% consensual)
    RL: performed 0 reads and 11,494,960 write i/o operations

pledge() and unveil() no longer consider ENOSYS to be an error.
These functions have also been added to Python's cosmo module.

This change also removes some WIN32 APIs and System Five magnums
which we're not using and it's doubtful anyone else would be too
2022-08-10 04:43:09 -07:00

6150 lines
219 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/fmt/fmt.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "third_party/quickjs/internal.h"
#include "third_party/quickjs/libregexp.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
static __exception int js_parse_assign_expr(JSParseState *);
static __exception int js_parse_assign_expr2(JSParseState *, int);
static __exception int js_parse_expr(JSParseState *);
static __exception int js_parse_function_decl(JSParseState *, JSParseFunctionEnum, JSFunctionKindEnum, JSAtom, const uint8_t *, int);
static __exception int js_parse_function_decl2(JSParseState *, JSParseFunctionEnum, JSFunctionKindEnum, JSAtom, const uint8_t *, int, JSParseExportEnum, JSFunctionDef **);
static __exception int js_parse_postfix_expr(JSParseState *, int);
static __exception int js_parse_unary(JSParseState *, int);
void js_parse_init(JSContext *ctx, JSParseState *s,
const char *input, size_t input_len,
const char *filename)
{
bzero(s, sizeof(*s));
s->ctx = ctx;
s->filename = filename;
s->line_num = 1;
s->buf_ptr = (const uint8_t *)input;
s->buf_end = s->buf_ptr + input_len;
s->token.val = ' ';
s->token.line_num = 1;
}
/* Note: all the fields are already sealed except length */
static int seal_template_obj(JSContext *ctx, JSValueConst obj)
{
JSObject *p;
JSShapeProperty *prs;
p = JS_VALUE_GET_OBJ(obj);
prs = find_own_property1(p, JS_ATOM_length);
if (prs) {
if (js_update_property_flags(ctx, p, &prs,
prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)))
return -1;
}
p->extensible = FALSE;
return 0;
}
static __exception int emit_push_const(JSParseState *s, JSValueConst val,
BOOL as_atom)
{
int idx;
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) {
JSAtom atom;
/* warning: JS_NewAtomStr frees the string value */
JS_DupValue(s->ctx, val);
atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val));
if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) {
emit_op(s, OP_push_atom_value);
emit_u32(s, atom);
return 0;
}
}
idx = cpool_add(s, JS_DupValue(s->ctx, val));
if (idx < 0)
return -1;
emit_op(s, OP_push_const);
emit_u32(s, idx);
return 0;
}
int js_parse_template_part(JSParseState *s, const uint8_t *p)
{
uint32_t c;
StringBuffer b_s, *b = &b_s;
/* p points to the first byte of the template part */
if (string_buffer_init(s->ctx, b, 32))
goto fail;
for(;;) {
if (p >= s->buf_end)
goto unexpected_eof;
c = *p++;
if (c == '`') {
/* template end part */
break;
}
if (c == '$' && *p == '{') {
/* template start or middle part */
p++;
break;
}
if (c == '\\') {
if (string_buffer_putc8(b, c))
goto fail;
if (p >= s->buf_end)
goto unexpected_eof;
c = *p++;
}
/* newline sequences are normalized as single '\n' bytes */
if (c == '\r') {
if (*p == '\n')
p++;
c = '\n';
}
if (c == '\n') {
s->line_num++;
} else if (c >= 0x80) {
const uint8_t *p_next;
c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF) {
js_parse_error(s, "invalid UTF-8 sequence");
goto fail;
}
p = p_next;
}
if (string_buffer_putc(b, c))
goto fail;
}
s->token.val = TOK_TEMPLATE;
s->token.u.str.sep = c;
s->token.u.str.str = string_buffer_end(b);
s->buf_ptr = p;
return 0;
unexpected_eof:
js_parse_error(s, "unexpected end of string");
fail:
string_buffer_free(b);
return -1;
}
int js_parse_string(JSParseState *s, int sep,
BOOL do_throw, const uint8_t *p,
JSToken *token, const uint8_t **pp)
{
int ret;
uint32_t c;
StringBuffer b_s, *b = &b_s;
/* string */
if (string_buffer_init(s->ctx, b, 32))
goto fail;
for(;;) {
if (p >= s->buf_end)
goto invalid_char;
c = *p;
if (c < 0x20) {
if (!s->cur_func) {
if (do_throw)
js_parse_error(s, "invalid character in a JSON string");
goto fail;
}
if (sep == '`') {
if (c == '\r') {
if (p[1] == '\n')
p++;
c = '\n';
}
/* do not update s->line_num */
} else if (c == '\n' || c == '\r')
goto invalid_char;
}
p++;
if (c == sep)
break;
if (c == '$' && *p == '{' && sep == '`') {
/* template start or middle part */
p++;
break;
}
if (c == '\\') {
c = *p;
/* XXX: need a specific JSON case to avoid
accepting invalid escapes */
switch(c) {
case '\0':
if (p >= s->buf_end)
goto invalid_char;
p++;
break;
case '\'':
case '\"':
case '\\':
p++;
break;
case '\r': /* accept DOS and MAC newline sequences */
if (p[1] == '\n') {
p++;
}
/* fall thru */
case '\n':
/* ignore escaped newline sequence */
p++;
if (sep != '`')
s->line_num++;
continue;
default:
if (c >= '0' && c <= '9') {
if (!s->cur_func)
goto invalid_escape; /* JSON case */
if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`')
goto parse_escape;
if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) {
p++;
c = '\0';
} else {
if (c >= '8' || sep == '`') {
/* Note: according to ES2021, \8 and \9 are not
accepted in strict mode or in templates. */
goto invalid_escape;
} else {
if (do_throw)
js_parse_error(s, "octal escape sequences are not allowed in strict mode");
}
goto fail;
}
} else if (c >= 0x80) {
const uint8_t *p_next;
c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF) {
goto invalid_utf8;
}
p = p_next;
/* LS or PS are skipped */
if (c == CP_LS || c == CP_PS)
continue;
} else {
parse_escape:
ret = lre_parse_escape(&p, TRUE);
if (ret == -1) {
invalid_escape:
if (do_throw)
js_parse_error(s, "malformed escape sequence in string literal");
goto fail;
} else if (ret < 0) {
/* ignore the '\' (could output a warning) */
p++;
} else {
c = ret;
}
}
break;
}
} else if (c >= 0x80) {
const uint8_t *p_next;
c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF)
goto invalid_utf8;
p = p_next;
}
if (string_buffer_putc(b, c))
goto fail;
}
token->val = TOK_STRING;
token->u.str.sep = c;
token->u.str.str = string_buffer_end(b);
*pp = p;
return 0;
invalid_utf8:
if (do_throw)
js_parse_error(s, "invalid UTF-8 sequence");
goto fail;
invalid_char:
if (do_throw)
js_parse_error(s, "unexpected end of string");
fail:
string_buffer_free(b);
return -1;
}
int js_parse_expect(JSParseState *s, int tok)
{
if (s->token.val != tok) {
/* XXX: dump token correctly in all cases */
return js_parse_error(s, "expecting '%c'", tok);
}
return next_token(s);
}
static int js_parse_expect_semi(JSParseState *s)
{
if (s->token.val != ';') {
/* automatic insertion of ';' */
if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) {
return 0;
}
return js_parse_error(s, "expecting '%c'", ';');
}
return next_token(s);
}
static __exception int js_parse_statement_or_decl(JSParseState *s,
int decl_mask);
static __exception int js_parse_statement(JSParseState *s)
{
return js_parse_statement_or_decl(s, 0);
}
static int get_first_lexical_var(JSFunctionDef *fd, int scope)
{
while (scope >= 0) {
int scope_idx = fd->scopes[scope].first;
if (scope_idx >= 0)
return scope_idx;
scope = fd->scopes[scope].parent;
}
return -1;
}
static void pop_scope(JSParseState *s) {
if (s->cur_func) {
/* disable scoped variables */
JSFunctionDef *fd = s->cur_func;
int scope = fd->scope_level;
emit_op(s, OP_leave_scope);
emit_u16(s, scope);
fd->scope_level = fd->scopes[scope].parent;
fd->scope_first = get_first_lexical_var(fd, fd->scope_level);
}
}
static __exception int js_parse_block(JSParseState *s)
{
if (js_parse_expect(s, '{'))
return -1;
if (s->token.val != '}') {
push_scope(s);
for(;;) {
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
return -1;
if (s->token.val == '}')
break;
}
pop_scope(s);
}
if (next_token(s))
return -1;
return 0;
}
static __exception int js_parse_template(JSParseState *s, int call, int *argc)
{
JSContext *ctx = s->ctx;
JSValue raw_array, template_object;
JSToken cooked;
int depth, ret;
raw_array = JS_UNDEFINED; /* avoid warning */
template_object = JS_UNDEFINED; /* avoid warning */
if (call) {
/* Create a template object: an array of cooked strings */
/* Create an array of raw strings and store it to the raw property */
template_object = JS_NewArray(ctx);
if (JS_IsException(template_object))
return -1;
// pool_idx = s->cur_func->cpool_count;
ret = emit_push_const(s, template_object, 0);
JS_FreeValue(ctx, template_object);
if (ret)
return -1;
raw_array = JS_NewArray(ctx);
if (JS_IsException(raw_array))
return -1;
if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw,
raw_array, JS_PROP_THROW) < 0) {
return -1;
}
}
depth = 0;
while (s->token.val == TOK_TEMPLATE) {
const uint8_t *p = s->token.ptr + 1;
cooked = s->token;
if (call) {
if (JS_DefinePropertyValueUint32(ctx, raw_array, depth,
JS_DupValue(ctx, s->token.u.str.str),
JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
return -1;
}
/* re-parse the string with escape sequences but do not throw a
syntax error if it contains invalid sequences
*/
if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) {
cooked.u.str.str = JS_UNDEFINED;
}
if (JS_DefinePropertyValueUint32(ctx, template_object, depth,
cooked.u.str.str,
JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) {
return -1;
}
} else {
JSString *str;
/* re-parse the string with escape sequences and throw a
syntax error if it contains invalid sequences
*/
JS_FreeValue(ctx, s->token.u.str.str);
s->token.u.str.str = JS_UNDEFINED;
if (js_parse_string(s, '`', TRUE, p, &cooked, &p))
return -1;
str = JS_VALUE_GET_STRING(cooked.u.str.str);
if (str->len != 0 || depth == 0) {
ret = emit_push_const(s, cooked.u.str.str, 1);
JS_FreeValue(s->ctx, cooked.u.str.str);
if (ret)
return -1;
if (depth == 0) {
if (s->token.u.str.sep == '`')
goto done1;
emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_concat);
}
depth++;
} else {
JS_FreeValue(s->ctx, cooked.u.str.str);
}
}
if (s->token.u.str.sep == '`')
goto done;
if (next_token(s))
return -1;
if (js_parse_expr(s))
return -1;
depth++;
if (s->token.val != '}') {
return js_parse_error(s, "expected '}' after template expression");
}
/* XXX: should convert to string at this stage? */
free_token(s, &s->token);
/* Resume TOK_TEMPLATE parsing (s->token.line_num and
* s->token.ptr are OK) */
s->got_lf = FALSE;
s->last_line_num = s->token.line_num;
if (js_parse_template_part(s, s->buf_ptr))
return -1;
}
return js_parse_expect(s, TOK_TEMPLATE);
done:
if (call) {
/* Seal the objects */
seal_template_obj(ctx, raw_array);
seal_template_obj(ctx, template_object);
*argc = depth + 1;
} else {
emit_op(s, OP_call_method);
emit_u16(s, depth - 1);
}
done1:
return next_token(s);
}
static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) {
return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom &&
!s->token.u.ident.has_escape;
}
static int js_parse_get_pos(JSParseState *s, JSParsePos *sp)
{
sp->last_line_num = s->last_line_num;
sp->line_num = s->token.line_num;
sp->ptr = s->token.ptr;
sp->got_lf = s->got_lf;
return 0;
}
static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp)
{
s->token.line_num = sp->last_line_num;
s->line_num = sp->line_num;
s->buf_ptr = sp->ptr;
s->got_lf = sp->got_lf;
return next_token(s);
}
/* test if the current token is a let keyword. Use simplistic look-ahead scanner */
static int is_let(JSParseState *s, int decl_mask)
{
int res = FALSE;
if (token_is_pseudo_keyword(s, JS_ATOM_let)) {
#if 1
JSParsePos pos;
js_parse_get_pos(s, &pos);
for (;;) {
if (next_token(s)) {
res = -1;
break;
}
if (s->token.val == '[') {
/* let [ is a syntax restriction:
it never introduces an ExpressionStatement */
res = TRUE;
break;
}
if (s->token.val == '{' ||
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) ||
s->token.val == TOK_LET ||
s->token.val == TOK_YIELD ||
s->token.val == TOK_AWAIT) {
/* Check for possible ASI if not scanning for Declaration */
/* XXX: should also check that `{` introduces a BindingPattern,
but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */
if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) {
res = TRUE;
break;
}
break;
}
break;
}
if (js_parse_seek_token(s, &pos)) {
res = -1;
}
#else
int tok = peek_token(s, TRUE);
if (tok == '{' || tok == TOK_IDENT || peek_token(s, FALSE) == '[') {
res = TRUE;
}
#endif
}
return res;
}
static int peek_token(JSParseState *s, BOOL no_line_terminator)
{
const uint8_t *p = s->buf_ptr;
return simple_next_token(&p, no_line_terminator);
}
/* test if the current token is a label. Use simplistic look-ahead scanner */
static BOOL is_label(JSParseState *s)
{
return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
peek_token(s, FALSE) == ':');
}
static BOOL token_is_ident(int tok)
{
/* Accept keywords and reserved words as property names */
return (tok == TOK_IDENT ||
(tok >= TOK_FIRST_KEYWORD &&
tok <= TOK_LAST_KEYWORD));
}
/* if the property is an expression, name = JS_ATOM_NULL */
static int __exception js_parse_property_name(JSParseState *s,
JSAtom *pname,
BOOL allow_method, BOOL allow_var,
BOOL allow_private)
{
int is_private = 0;
BOOL is_non_reserved_ident;
JSAtom name;
int prop_type;
prop_type = PROP_TYPE_IDENT;
if (allow_method) {
if (token_is_pseudo_keyword(s, JS_ATOM_get)
|| token_is_pseudo_keyword(s, JS_ATOM_set)) {
/* get x(), set x() */
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail1;
if (s->token.val == ':' || s->token.val == ',' ||
s->token.val == '}' || s->token.val == '(') {
is_non_reserved_ident = TRUE;
goto ident_found;
}
prop_type = PROP_TYPE_GET + (name == JS_ATOM_set);
JS_FreeAtom(s->ctx, name);
} else if (s->token.val == '*') {
if (next_token(s))
goto fail;
prop_type = PROP_TYPE_STAR;
} else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) != '\n') {
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail1;
if (s->token.val == ':' || s->token.val == ',' ||
s->token.val == '}' || s->token.val == '(') {
is_non_reserved_ident = TRUE;
goto ident_found;
}
JS_FreeAtom(s->ctx, name);
if (s->token.val == '*') {
if (next_token(s))
goto fail;
prop_type = PROP_TYPE_ASYNC_STAR;
} else {
prop_type = PROP_TYPE_ASYNC;
}
}
}
if (token_is_ident(s->token.val)) {
/* variable can only be a non-reserved identifier */
is_non_reserved_ident =
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved);
/* keywords and reserved words have a valid atom */
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail1;
ident_found:
if (is_non_reserved_ident &&
prop_type == PROP_TYPE_IDENT && allow_var) {
if (!(s->token.val == ':' ||
(s->token.val == '(' && allow_method))) {
prop_type = PROP_TYPE_VAR;
}
}
} else if (s->token.val == TOK_STRING) {
name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
if (name == JS_ATOM_NULL)
goto fail;
if (next_token(s))
goto fail1;
} else if (s->token.val == TOK_NUMBER) {
JSValue val;
val = s->token.u.num.val;
#ifdef CONFIG_BIGNUM
if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) {
JSBigFloat *p = JS_VALUE_GET_PTR(val);
val = s->ctx->rt->bigfloat_ops.
mul_pow10_to_float64(s->ctx, &p->num,
s->token.u.num.exponent);
if (JS_IsException(val))
goto fail;
name = JS_ValueToAtom(s->ctx, val);
JS_FreeValue(s->ctx, val);
} else
#endif
{
name = JS_ValueToAtom(s->ctx, val);
}
if (name == JS_ATOM_NULL)
goto fail;
if (next_token(s))
goto fail1;
} else if (s->token.val == '[') {
if (next_token(s))
goto fail;
if (js_parse_expr(s))
goto fail;
if (js_parse_expect(s, ']'))
goto fail;
name = JS_ATOM_NULL;
} else if (s->token.val == TOK_PRIVATE_NAME && allow_private) {
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail1;
is_private = PROP_TYPE_PRIVATE;
} else {
goto invalid_prop;
}
if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR &&
s->token.val != '(') {
JS_FreeAtom(s->ctx, name);
invalid_prop:
js_parse_error(s, "invalid property name");
goto fail;
}
*pname = name;
return prop_type | is_private;
fail1:
JS_FreeAtom(s->ctx, name);
fail:
*pname = JS_ATOM_NULL;
return -1;
}
static void set_object_name_computed(JSParseState *s)
{
JSFunctionDef *fd = s->cur_func;
int opcode;
opcode = get_prev_opcode(fd);
if (opcode == OP_set_name) {
/* XXX: should free atom after OP_set_name? */
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
emit_op(s, OP_set_name_computed);
} else if (opcode == OP_set_class_name) {
int define_class_pos;
define_class_pos = fd->last_opcode_pos + 1 -
get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
fd->byte_code.buf[define_class_pos] = OP_define_class_computed;
fd->last_opcode_pos = -1;
}
}
static void set_object_name(JSParseState *s, JSAtom name)
{
JSFunctionDef *fd = s->cur_func;
int opcode;
opcode = get_prev_opcode(fd);
if (opcode == OP_set_name) {
/* XXX: should free atom after OP_set_name? */
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
emit_op(s, OP_set_name);
emit_atom(s, name);
} else if (opcode == OP_set_class_name) {
int define_class_pos;
JSAtom atom;
define_class_pos = fd->last_opcode_pos + 1 -
get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
assert(fd->byte_code.buf[define_class_pos] == OP_define_class);
/* for consistency we free the previous atom which is
JS_ATOM_empty_string */
atom = get_u32(fd->byte_code.buf + define_class_pos + 1);
JS_FreeAtom(s->ctx, atom);
put_u32(fd->byte_code.buf + define_class_pos + 1,
JS_DupAtom(s->ctx, name));
fd->last_opcode_pos = -1;
}
}
static __exception int js_parse_object_literal(JSParseState *s)
{
JSAtom name = JS_ATOM_NULL;
const uint8_t *start_ptr;
int start_line, prop_type;
BOOL has_proto;
if (next_token(s))
goto fail;
/* XXX: add an initial length that will be patched back */
emit_op(s, OP_object);
has_proto = FALSE;
while (s->token.val != '}') {
/* specific case for getter/setter */
start_ptr = s->token.ptr;
start_line = s->token.line_num;
if (s->token.val == TOK_ELLIPSIS) {
if (next_token(s))
return -1;
if (js_parse_assign_expr(s))
return -1;
emit_op(s, OP_null); /* dummy excludeList */
emit_op(s, OP_copy_data_properties);
emit_u8(s, 2 | (1 << 2) | (0 << 5));
emit_op(s, OP_drop); /* pop excludeList */
emit_op(s, OP_drop); /* pop src object */
goto next;
}
prop_type = js_parse_property_name(s, &name, TRUE, TRUE, FALSE);
if (prop_type < 0)
goto fail;
if (prop_type == PROP_TYPE_VAR) {
/* shortcut for x: x */
emit_op(s, OP_scope_get_var);
emit_atom(s, name);
emit_u16(s, s->cur_func->scope_level);
emit_op(s, OP_define_field);
emit_atom(s, name);
} else if (s->token.val == '(') {
BOOL is_getset = (prop_type == PROP_TYPE_GET ||
prop_type == PROP_TYPE_SET);
JSParseFunctionEnum func_type;
JSFunctionKindEnum func_kind;
int op_flags;
func_kind = JS_FUNC_NORMAL;
if (is_getset) {
func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET;
} else {
func_type = JS_PARSE_FUNC_METHOD;
if (prop_type == PROP_TYPE_STAR)
func_kind = JS_FUNC_GENERATOR;
else if (prop_type == PROP_TYPE_ASYNC)
func_kind = JS_FUNC_ASYNC;
else if (prop_type == PROP_TYPE_ASYNC_STAR)
func_kind = JS_FUNC_ASYNC_GENERATOR;
}
if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL,
start_ptr, start_line))
goto fail;
if (name == JS_ATOM_NULL) {
emit_op(s, OP_define_method_computed);
} else {
emit_op(s, OP_define_method);
emit_atom(s, name);
}
if (is_getset) {
op_flags = OP_DEFINE_METHOD_GETTER +
prop_type - PROP_TYPE_GET;
} else {
op_flags = OP_DEFINE_METHOD_METHOD;
}
emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE);
} else {
if (js_parse_expect(s, ':'))
goto fail;
if (js_parse_assign_expr(s))
goto fail;
if (name == JS_ATOM_NULL) {
set_object_name_computed(s);
emit_op(s, OP_define_array_el);
emit_op(s, OP_drop);
} else if (name == JS_ATOM___proto__) {
if (has_proto) {
js_parse_error(s, "duplicate __proto__ property name");
goto fail;
}
emit_op(s, OP_set_proto);
has_proto = TRUE;
} else {
set_object_name(s, name);
emit_op(s, OP_define_field);
emit_atom(s, name);
}
}
JS_FreeAtom(s->ctx, name);
next:
name = JS_ATOM_NULL;
if (s->token.val != ',')
break;
if (next_token(s))
goto fail;
}
if (js_parse_expect(s, '}'))
goto fail;
return 0;
fail:
JS_FreeAtom(s->ctx, name);
return -1;
}
/* return TRUE if a regexp literal is allowed after this token */
static BOOL is_regexp_allowed(int tok)
{
switch (tok) {
case TOK_NUMBER:
case TOK_STRING:
case TOK_REGEXP:
case TOK_DEC:
case TOK_INC:
case TOK_NULL:
case TOK_FALSE:
case TOK_TRUE:
case TOK_THIS:
case ')':
case ']':
case '}': /* XXX: regexp may occur after */
case TOK_IDENT:
return FALSE;
default:
return TRUE;
}
}
static __exception int js_parse_regexp(JSParseState *s)
{
const uint8_t *p;
BOOL in_class;
StringBuffer b_s, *b = &b_s;
StringBuffer b2_s, *b2 = &b2_s;
uint32_t c;
p = s->buf_ptr;
p++;
in_class = FALSE;
if (string_buffer_init(s->ctx, b, 32))
return -1;
if (string_buffer_init(s->ctx, b2, 1))
goto fail;
for(;;) {
if (p >= s->buf_end) {
eof_error:
js_parse_error(s, "unexpected end of regexp");
goto fail;
}
c = *p++;
if (c == '\n' || c == '\r') {
goto eol_error;
} else if (c == '/') {
if (!in_class)
break;
} else if (c == '[') {
in_class = TRUE;
} else if (c == ']') {
/* XXX: incorrect as the first character in a class */
in_class = FALSE;
} else if (c == '\\') {
if (string_buffer_putc8(b, c))
goto fail;
c = *p++;
if (c == '\n' || c == '\r')
goto eol_error;
else if (c == '\0' && p >= s->buf_end)
goto eof_error;
else if (c >= 0x80) {
const uint8_t *p_next;
c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF) {
goto invalid_utf8;
}
p = p_next;
if (c == CP_LS || c == CP_PS)
goto eol_error;
}
} else if (c >= 0x80) {
const uint8_t *p_next;
c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF) {
invalid_utf8:
js_parse_error(s, "invalid UTF-8 sequence");
goto fail;
}
p = p_next;
/* LS or PS are considered as line terminator */
if (c == CP_LS || c == CP_PS) {
eol_error:
js_parse_error(s, "unexpected line terminator in regexp");
goto fail;
}
}
if (string_buffer_putc(b, c))
goto fail;
}
/* flags */
for(;;) {
const uint8_t *p_next = p;
c = *p_next++;
if (c >= 0x80) {
c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next);
if (c > 0x10FFFF) {
goto invalid_utf8;
}
}
if (!lre_js_is_ident_next(c))
break;
if (string_buffer_putc(b2, c))
goto fail;
p = p_next;
}
s->token.val = TOK_REGEXP;
s->token.u.regexp.body = string_buffer_end(b);
s->token.u.regexp.flags = string_buffer_end(b2);
s->buf_ptr = p;
return 0;
fail:
string_buffer_free(b);
string_buffer_free(b2);
return -1;
}
/* XXX: improve speed with early bailout */
/* XXX: no longer works if regexps are present. Could use previous
regexp parsing heuristics to handle most cases */
static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator)
{
char state[256];
size_t level = 0;
JSParsePos pos;
int last_tok, tok = TOK_EOF;
int c, tok_len, bits = 0;
/* protect from underflow */
state[level++] = 0;
js_parse_get_pos(s, &pos);
last_tok = 0;
for (;;) {
switch(s->token.val) {
case '(':
case '[':
case '{':
if (level >= sizeof(state))
goto done;
state[level++] = s->token.val;
break;
case ')':
if (state[--level] != '(')
goto done;
break;
case ']':
if (state[--level] != '[')
goto done;
break;
case '}':
c = state[--level];
if (c == '`') {
/* continue the parsing of the template */
free_token(s, &s->token);
/* Resume TOK_TEMPLATE parsing (s->token.line_num and
* s->token.ptr are OK) */
s->got_lf = FALSE;
s->last_line_num = s->token.line_num;
if (js_parse_template_part(s, s->buf_ptr))
goto done;
goto handle_template;
} else if (c != '{') {
goto done;
}
break;
case TOK_TEMPLATE:
handle_template:
if (s->token.u.str.sep != '`') {
/* '${' inside the template : closing '}' and continue
parsing the template */
if (level >= sizeof(state))
goto done;
state[level++] = '`';
}
break;
case TOK_EOF:
goto done;
case ';':
if (level == 2) {
bits |= SKIP_HAS_SEMI;
}
break;
case TOK_ELLIPSIS:
if (level == 2) {
bits |= SKIP_HAS_ELLIPSIS;
}
break;
case '=':
bits |= SKIP_HAS_ASSIGNMENT;
break;
case TOK_DIV_ASSIGN:
tok_len = 2;
goto parse_regexp;
case '/':
tok_len = 1;
parse_regexp:
if (is_regexp_allowed(last_tok)) {
s->buf_ptr -= tok_len;
if (js_parse_regexp(s)) {
/* XXX: should clear the exception */
goto done;
}
}
break;
}
/* last_tok is only used to recognize regexps */
if (s->token.val == TOK_IDENT &&
(token_is_pseudo_keyword(s, JS_ATOM_of) ||
token_is_pseudo_keyword(s, JS_ATOM_yield))) {
last_tok = TOK_OF;
} else {
last_tok = s->token.val;
}
if (next_token(s)) {
/* XXX: should clear the exception generated by next_token() */
break;
}
if (level <= 1) {
tok = s->token.val;
if (token_is_pseudo_keyword(s, JS_ATOM_of))
tok = TOK_OF;
if (no_line_terminator && s->last_line_num != s->token.line_num)
tok = '\n';
break;
}
}
done:
if (pbits) {
*pbits = bits;
}
if (js_parse_seek_token(s, &pos))
return -1;
return tok;
}
static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name)
{
/* Check for duplicate parameter names */
JSFunctionDef *fd = s->cur_func;
int i;
for (i = 0; i < fd->arg_count; i++) {
if (fd->args[i].var_name == name)
goto duplicate;
}
for (i = 0; i < fd->var_count; i++) {
if (fd->vars[i].var_name == name)
goto duplicate;
}
return 0;
duplicate:
return js_parse_error(s, "duplicate parameter names not allowed in this context");
}
static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg)
{
JSAtom name;
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
|| ((s->cur_func->js_mode & JS_MODE_STRICT) &&
(s->token.u.ident.atom == JS_ATOM_eval || s->token.u.ident.atom == JS_ATOM_arguments))) {
js_parse_error(s, "invalid destructuring target");
return JS_ATOM_NULL;
}
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (is_arg && js_parse_check_duplicate_parameter(s, name))
goto fail;
if (next_token(s))
goto fail;
return name;
fail:
JS_FreeAtom(s->ctx, name);
return JS_ATOM_NULL;
}
static __exception int js_parse_expr_paren(JSParseState *s)
{
if (js_parse_expect(s, '('))
return -1;
if (js_parse_expr(s))
return -1;
if (js_parse_expect(s, ')'))
return -1;
return 0;
}
static int js_parse_error_reserved_identifier(JSParseState *s)
{
char buf1[ATOM_GET_STR_BUF_SIZE];
return js_parse_error(s, "'%s' is a reserved identifier",
JS_AtomGetStr(s->ctx, buf1, sizeof(buf1),
s->token.u.ident.atom));
}
static __exception int js_parse_left_hand_side_expr(JSParseState *s)
{
return js_parse_postfix_expr(s, PF_POSTFIX_CALL);
}
/* add a private field variable in the current scope */
static int add_private_class_field(JSParseState *s, JSFunctionDef *fd,
JSAtom name, JSVarKindEnum var_kind)
{
JSContext *ctx = s->ctx;
JSVarDef *vd;
int idx;
idx = add_scope_var(ctx, fd, name, var_kind);
if (idx < 0)
return idx;
vd = &fd->vars[idx];
vd->is_lexical = 1;
vd->is_const = 1;
return idx;
}
static BOOL js_is_live_code(JSParseState *s) {
switch (get_prev_opcode(s->cur_func)) {
case OP_tail_call:
case OP_tail_call_method:
case OP_return:
case OP_return_undef:
case OP_return_async:
case OP_throw:
case OP_throw_error:
case OP_goto:
#if SHORT_OPCODES
case OP_goto8:
case OP_goto16:
#endif
case OP_ret:
return FALSE;
default:
return TRUE;
}
}
/* return label or -1 if dead code */
static int emit_goto(JSParseState *s, int opcode, int label)
{
if (js_is_live_code(s)) {
if (label < 0)
label = new_label(s);
emit_op(s, opcode);
emit_u32(s, label);
s->cur_func->label_slots[label].ref_count++;
return label;
}
return -1;
}
/* create a function to initialize class fields */
static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s)
{
JSFunctionDef *fd;
fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE,
s->filename, 0);
if (!fd)
return NULL;
fd->func_name = JS_ATOM_NULL;
fd->has_prototype = FALSE;
fd->has_home_object = TRUE;
fd->has_arguments_binding = FALSE;
fd->has_this_binding = TRUE;
fd->is_derived_class_constructor = FALSE;
fd->new_target_allowed = TRUE;
fd->super_call_allowed = FALSE;
fd->super_allowed = fd->has_home_object;
fd->arguments_allowed = FALSE;
fd->func_kind = JS_FUNC_NORMAL;
fd->func_type = JS_PARSE_FUNC_METHOD;
return fd;
}
static __exception int emit_class_init_start(JSParseState *s,
ClassFieldsDef *cf)
{
int label_add_brand;
cf->fields_init_fd = js_parse_function_class_fields_init(s);
if (!cf->fields_init_fd)
return -1;
s->cur_func = cf->fields_init_fd;
/* XXX: would be better to add the code only if needed, maybe in a
later pass */
emit_op(s, OP_push_false); /* will be patched later */
cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos;
label_add_brand = emit_goto(s, OP_if_false, -1);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_home_object);
emit_u16(s, 0);
emit_op(s, OP_add_brand);
emit_label(s, label_add_brand);
s->cur_func = s->cur_func->parent;
return 0;
}
static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf)
{
if (!cf->has_brand) {
/* define the brand field in 'this' of the initializer */
if (!cf->fields_init_fd) {
if (emit_class_init_start(s, cf))
return -1;
}
/* patch the start of the function to enable the OP_add_brand code */
cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true;
cf->has_brand = TRUE;
}
return 0;
}
static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n)
{
char buf[16];
snprintf(buf, sizeof(buf), "%u", n);
return js_atom_concat_str(ctx, name, buf);
}
/* XXX: could generate specific bytecode */
static __exception int js_parse_class_default_ctor(JSParseState *s,
BOOL has_super,
JSFunctionDef **pfd)
{
JSParsePos pos;
const char *str;
int ret, line_num;
JSParseFunctionEnum func_type;
const uint8_t *saved_buf_end;
js_parse_get_pos(s, &pos);
if (has_super) {
/* spec change: no argument evaluation */
str = "(){super(...arguments);}";
func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
} else {
str = "(){}";
func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
}
line_num = s->token.line_num;
saved_buf_end = s->buf_end;
s->buf_ptr = (uint8_t *)str;
s->buf_end = (uint8_t *)(str + strlen(str));
ret = next_token(s);
if (!ret) {
ret = js_parse_function_decl2(s, func_type, JS_FUNC_NORMAL,
JS_ATOM_NULL, (uint8_t *)str,
line_num, JS_PARSE_EXPORT_NONE, pfd);
}
s->buf_end = saved_buf_end;
ret |= js_parse_seek_token(s, &pos);
return ret;
}
static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf)
{
int cpool_idx;
s->cur_func = cf->fields_init_fd;
emit_op(s, OP_return_undef);
s->cur_func = s->cur_func->parent;
cpool_idx = cpool_add(s, JS_NULL);
cf->fields_init_fd->parent_cpool_idx = cpool_idx;
emit_op(s, OP_fclosure);
emit_u32(s, cpool_idx);
emit_op(s, OP_set_home_object);
}
static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr,
JSParseExportEnum export_flag)
{
JSContext *ctx = s->ctx;
JSFunctionDef *fd = s->cur_func;
JSAtom name = JS_ATOM_NULL, class_name = JS_ATOM_NULL, class_name1;
JSAtom class_var_name = JS_ATOM_NULL;
JSFunctionDef *method_fd, *ctor_fd;
int saved_js_mode, class_name_var_idx, prop_type, ctor_cpool_offset;
int class_flags = 0, i, define_class_offset;
BOOL is_static, is_private;
const uint8_t *class_start_ptr = s->token.ptr;
const uint8_t *start_ptr;
ClassFieldsDef class_fields[2];
/* classes are parsed and executed in strict mode */
saved_js_mode = fd->js_mode;
fd->js_mode |= JS_MODE_STRICT;
if (next_token(s))
goto fail;
if (s->token.val == TOK_IDENT) {
if (s->token.u.ident.is_reserved) {
js_parse_error_reserved_identifier(s);
goto fail;
}
class_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail;
} else if (!is_class_expr && export_flag != JS_PARSE_EXPORT_DEFAULT) {
js_parse_error(s, "class statement requires a name");
goto fail;
}
if (!is_class_expr) {
if (class_name == JS_ATOM_NULL)
class_var_name = JS_ATOM__default_; /* export default */
else
class_var_name = class_name;
class_var_name = JS_DupAtom(ctx, class_var_name);
}
push_scope(s);
if (s->token.val == TOK_EXTENDS) {
class_flags = JS_DEFINE_CLASS_HAS_HERITAGE;
if (next_token(s))
goto fail;
if (js_parse_left_hand_side_expr(s))
goto fail;
} else {
emit_op(s, OP_undefined);
}
/* add a 'const' definition for the class name */
if (class_name != JS_ATOM_NULL) {
class_name_var_idx = define_var(s, fd, class_name, JS_VAR_DEF_CONST);
if (class_name_var_idx < 0)
goto fail;
}
if (js_parse_expect(s, '{'))
goto fail;
/* this scope contains the private fields */
push_scope(s);
emit_op(s, OP_push_const);
ctor_cpool_offset = fd->byte_code.size;
emit_u32(s, 0); /* will be patched at the end of the class parsing */
if (class_name == JS_ATOM_NULL) {
if (class_var_name != JS_ATOM_NULL)
class_name1 = JS_ATOM_default;
else
class_name1 = JS_ATOM_empty_string;
} else {
class_name1 = class_name;
}
emit_op(s, OP_define_class);
emit_atom(s, class_name1);
emit_u8(s, class_flags);
define_class_offset = fd->last_opcode_pos;
for(i = 0; i < 2; i++) {
ClassFieldsDef *cf = &class_fields[i];
cf->fields_init_fd = NULL;
cf->computed_fields_count = 0;
cf->has_brand = FALSE;
}
ctor_fd = NULL;
while (s->token.val != '}') {
if (s->token.val == ';') {
if (next_token(s))
goto fail;
continue;
}
is_static = (s->token.val == TOK_STATIC);
prop_type = -1;
if (is_static) {
if (next_token(s))
goto fail;
/* allow "static" field name */
if (s->token.val == ';' || s->token.val == '=') {
is_static = FALSE;
name = JS_DupAtom(ctx, JS_ATOM_static);
prop_type = PROP_TYPE_IDENT;
}
}
if (is_static)
emit_op(s, OP_swap);
start_ptr = s->token.ptr;
if (prop_type < 0) {
prop_type = js_parse_property_name(s, &name, TRUE, FALSE, TRUE);
if (prop_type < 0)
goto fail;
}
is_private = prop_type & PROP_TYPE_PRIVATE;
prop_type &= ~PROP_TYPE_PRIVATE;
if ((name == JS_ATOM_constructor && !is_static &&
prop_type != PROP_TYPE_IDENT) ||
(name == JS_ATOM_prototype && is_static) ||
name == JS_ATOM_hash_constructor) {
js_parse_error(s, "invalid method name");
goto fail;
}
if (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET) {
BOOL is_set = prop_type - PROP_TYPE_GET;
JSFunctionDef *method_fd;
if (is_private) {
int idx, var_kind;
idx = find_private_class_field(ctx, fd, name, fd->scope_level);
if (idx >= 0) {
var_kind = fd->vars[idx].var_kind;
if (var_kind == JS_VAR_PRIVATE_FIELD ||
var_kind == JS_VAR_PRIVATE_METHOD ||
var_kind == JS_VAR_PRIVATE_GETTER_SETTER ||
var_kind == (JS_VAR_PRIVATE_GETTER + is_set)) {
goto private_field_already_defined;
}
fd->vars[idx].var_kind = JS_VAR_PRIVATE_GETTER_SETTER;
} else {
if (add_private_class_field(s, fd, name,
JS_VAR_PRIVATE_GETTER + is_set) < 0)
goto fail;
}
if (add_brand(s, &class_fields[is_static]) < 0)
goto fail;
}
if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set,
JS_FUNC_NORMAL, JS_ATOM_NULL,
start_ptr, s->token.line_num,
JS_PARSE_EXPORT_NONE, &method_fd))
goto fail;
if (is_private) {
method_fd->need_home_object = TRUE; /* needed for brand check */
emit_op(s, OP_set_home_object);
/* XXX: missing function name */
emit_op(s, OP_scope_put_var_init);
if (is_set) {
JSAtom setter_name;
int ret;
setter_name = get_private_setter_name(ctx, name);
if (setter_name == JS_ATOM_NULL)
goto fail;
emit_atom(s, setter_name);
ret = add_private_class_field(s, fd, setter_name,
JS_VAR_PRIVATE_SETTER);
JS_FreeAtom(ctx, setter_name);
if (ret < 0)
goto fail;
} else {
emit_atom(s, name);
}
emit_u16(s, s->cur_func->scope_level);
} else {
if (name == JS_ATOM_NULL) {
emit_op(s, OP_define_method_computed);
} else {
emit_op(s, OP_define_method);
emit_atom(s, name);
}
emit_u8(s, OP_DEFINE_METHOD_GETTER + is_set);
}
} else if (prop_type == PROP_TYPE_IDENT && s->token.val != '(') {
ClassFieldsDef *cf = &class_fields[is_static];
JSAtom field_var_name = JS_ATOM_NULL;
/* class field */
/* XXX: spec: not consistent with method name checks */
if (name == JS_ATOM_constructor || name == JS_ATOM_prototype) {
js_parse_error(s, "invalid field name");
goto fail;
}
if (is_private) {
if (find_private_class_field(ctx, fd, name,
fd->scope_level) >= 0) {
goto private_field_already_defined;
}
if (add_private_class_field(s, fd, name,
JS_VAR_PRIVATE_FIELD) < 0)
goto fail;
emit_op(s, OP_private_symbol);
emit_atom(s, name);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, s->cur_func->scope_level);
}
if (!cf->fields_init_fd) {
if (emit_class_init_start(s, cf))
goto fail;
}
if (name == JS_ATOM_NULL ) {
/* save the computed field name into a variable */
field_var_name = js_atom_concat_num(ctx, JS_ATOM_computed_field + is_static, cf->computed_fields_count);
if (field_var_name == JS_ATOM_NULL)
goto fail;
if (define_var(s, fd, field_var_name, JS_VAR_DEF_CONST) < 0) {
JS_FreeAtom(ctx, field_var_name);
goto fail;
}
emit_op(s, OP_to_propkey);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, field_var_name);
emit_u16(s, s->cur_func->scope_level);
}
s->cur_func = cf->fields_init_fd;
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
if (name == JS_ATOM_NULL) {
emit_op(s, OP_scope_get_var);
emit_atom(s, field_var_name);
emit_u16(s, s->cur_func->scope_level);
cf->computed_fields_count++;
JS_FreeAtom(ctx, field_var_name);
} else if (is_private) {
emit_op(s, OP_scope_get_var);
emit_atom(s, name);
emit_u16(s, s->cur_func->scope_level);
}
if (s->token.val == '=') {
if (next_token(s))
goto fail;
if (js_parse_assign_expr(s))
goto fail;
} else {
emit_op(s, OP_undefined);
}
if (is_private) {
set_object_name_computed(s);
emit_op(s, OP_define_private_field);
} else if (name == JS_ATOM_NULL) {
set_object_name_computed(s);
emit_op(s, OP_define_array_el);
emit_op(s, OP_drop);
} else {
set_object_name(s, name);
emit_op(s, OP_define_field);
emit_atom(s, name);
}
s->cur_func = s->cur_func->parent;
if (js_parse_expect_semi(s))
goto fail;
} else {
JSParseFunctionEnum func_type;
JSFunctionKindEnum func_kind;
func_type = JS_PARSE_FUNC_METHOD;
func_kind = JS_FUNC_NORMAL;
if (prop_type == PROP_TYPE_STAR) {
func_kind = JS_FUNC_GENERATOR;
} else if (prop_type == PROP_TYPE_ASYNC) {
func_kind = JS_FUNC_ASYNC;
} else if (prop_type == PROP_TYPE_ASYNC_STAR) {
func_kind = JS_FUNC_ASYNC_GENERATOR;
} else if (name == JS_ATOM_constructor && !is_static) {
if (ctor_fd) {
js_parse_error(s, "property constructor appears more than once");
goto fail;
}
if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE)
func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR;
else
func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR;
}
if (is_private) {
if (add_brand(s, &class_fields[is_static]) < 0)
goto fail;
}
if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd))
goto fail;
if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR ||
func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
ctor_fd = method_fd;
} else if (is_private) {
method_fd->need_home_object = TRUE; /* needed for brand check */
if (find_private_class_field(ctx, fd, name,
fd->scope_level) >= 0) {
private_field_already_defined:
js_parse_error(s, "private class field is already defined");
goto fail;
}
if (add_private_class_field(s, fd, name,
JS_VAR_PRIVATE_METHOD) < 0)
goto fail;
emit_op(s, OP_set_home_object);
emit_op(s, OP_set_name);
emit_atom(s, name);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, s->cur_func->scope_level);
} else {
if (name == JS_ATOM_NULL) {
emit_op(s, OP_define_method_computed);
} else {
emit_op(s, OP_define_method);
emit_atom(s, name);
}
emit_u8(s, OP_DEFINE_METHOD_METHOD);
}
}
if (is_static)
emit_op(s, OP_swap);
JS_FreeAtom(ctx, name);
name = JS_ATOM_NULL;
}
if (s->token.val != '}') {
js_parse_error(s, "expecting '%c'", '}');
goto fail;
}
if (!ctor_fd) {
if (js_parse_class_default_ctor(s, class_flags & JS_DEFINE_CLASS_HAS_HERITAGE, &ctor_fd))
goto fail;
}
/* patch the constant pool index for the constructor */
put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx);
/* store the class source code in the constructor. */
if (!(fd->js_mode & JS_MODE_STRIP)) {
js_free(ctx, ctor_fd->source);
ctor_fd->source_len = s->buf_ptr - class_start_ptr;
ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr,
ctor_fd->source_len);
if (!ctor_fd->source)
goto fail;
}
/* consume the '}' */
if (next_token(s))
goto fail;
/* store the function to initialize the fields to that it can be
referenced by the constructor */
{
ClassFieldsDef *cf = &class_fields[0];
int var_idx;
var_idx = define_var(s, fd, JS_ATOM_class_fields_init,
JS_VAR_DEF_CONST);
if (var_idx < 0)
goto fail;
if (cf->fields_init_fd) {
emit_class_init_end(s, cf);
} else {
emit_op(s, OP_undefined);
}
emit_op(s, OP_scope_put_var_init);
emit_atom(s, JS_ATOM_class_fields_init);
emit_u16(s, s->cur_func->scope_level);
}
/* drop the prototype */
emit_op(s, OP_drop);
/* initialize the static fields */
if (class_fields[1].fields_init_fd != NULL) {
ClassFieldsDef *cf = &class_fields[1];
emit_op(s, OP_dup);
emit_class_init_end(s, cf);
emit_op(s, OP_call_method);
emit_u16(s, 0);
emit_op(s, OP_drop);
}
if (class_name != JS_ATOM_NULL) {
/* store the class name in the scoped class name variable (it
is independent from the class statement variable
definition) */
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, class_name);
emit_u16(s, fd->scope_level);
}
pop_scope(s);
pop_scope(s);
/* the class statements have a block level scope */
if (class_var_name != JS_ATOM_NULL) {
if (define_var(s, fd, class_var_name, JS_VAR_DEF_LET) < 0)
goto fail;
emit_op(s, OP_scope_put_var_init);
emit_atom(s, class_var_name);
emit_u16(s, fd->scope_level);
} else {
if (class_name == JS_ATOM_NULL) {
/* cannot use OP_set_name because the name of the class
must be defined before the static initializers are
executed */
emit_op(s, OP_set_class_name);
emit_u32(s, fd->last_opcode_pos + 1 - define_class_offset);
}
}
if (export_flag != JS_PARSE_EXPORT_NONE) {
if (!add_export_entry(s, fd->module,
class_var_name,
export_flag == JS_PARSE_EXPORT_NAMED ? class_var_name : JS_ATOM_default,
JS_EXPORT_TYPE_LOCAL))
goto fail;
}
JS_FreeAtom(ctx, class_name);
JS_FreeAtom(ctx, class_var_name);
fd->js_mode = saved_js_mode;
return 0;
fail:
JS_FreeAtom(ctx, name);
JS_FreeAtom(ctx, class_name);
JS_FreeAtom(ctx, class_var_name);
fd->js_mode = saved_js_mode;
return -1;
}
static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope,
JSAtom *pname, int *plabel, int *pdepth, BOOL keep,
int tok)
{
JSFunctionDef *fd;
int opcode, scope, label, depth;
JSAtom name;
/* we check the last opcode to get the lvalue type */
fd = s->cur_func;
scope = 0;
name = JS_ATOM_NULL;
label = -1;
depth = 0;
switch(opcode = get_prev_opcode(fd)) {
case OP_scope_get_var:
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) &&
(fd->js_mode & JS_MODE_STRICT)) {
return js_parse_error(s, "invalid lvalue in strict mode");
}
if (name == JS_ATOM_this || name == JS_ATOM_new_target)
goto invalid_lvalue;
depth = 2; /* will generate OP_get_ref_value */
break;
case OP_get_field:
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
depth = 1;
break;
case OP_scope_get_private_field:
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
depth = 1;
break;
case OP_get_array_el:
depth = 2;
break;
case OP_get_super_value:
depth = 3;
break;
default:
invalid_lvalue:
if (tok == TOK_FOR) {
return js_parse_error(s, "invalid for in/of left hand-side");
} else if (tok == TOK_INC || tok == TOK_DEC) {
return js_parse_error(s, "invalid increment/decrement operand");
} else if (tok == '[' || tok == '{') {
return js_parse_error(s, "invalid destructuring target");
} else {
return js_parse_error(s, "invalid assignment left-hand side");
}
}
/* remove the last opcode */
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
if (keep) {
/* get the value but keep the object/fields on the stack */
switch(opcode) {
case OP_scope_get_var:
label = new_label(s);
emit_op(s, OP_scope_make_ref);
emit_atom(s, name);
emit_u32(s, label);
emit_u16(s, scope);
update_label(fd, label, 1);
emit_op(s, OP_get_ref_value);
opcode = OP_get_ref_value;
break;
case OP_get_field:
emit_op(s, OP_get_field2);
emit_atom(s, name);
break;
case OP_scope_get_private_field:
emit_op(s, OP_scope_get_private_field2);
emit_atom(s, name);
emit_u16(s, scope);
break;
case OP_get_array_el:
/* XXX: replace by a single opcode ? */
emit_op(s, OP_to_propkey2);
emit_op(s, OP_dup2);
emit_op(s, OP_get_array_el);
break;
case OP_get_super_value:
emit_op(s, OP_to_propkey);
emit_op(s, OP_dup3);
emit_op(s, OP_get_super_value);
break;
default:
abort();
}
} else {
switch(opcode) {
case OP_scope_get_var:
label = new_label(s);
emit_op(s, OP_scope_make_ref);
emit_atom(s, name);
emit_u32(s, label);
emit_u16(s, scope);
update_label(fd, label, 1);
opcode = OP_get_ref_value;
break;
case OP_get_array_el:
emit_op(s, OP_to_propkey2);
break;
case OP_get_super_value:
emit_op(s, OP_to_propkey);
break;
}
}
*popcode = opcode;
*pscope = scope;
/* name has refcount for OP_get_field and OP_get_ref_value,
and JS_ATOM_NULL for other opcodes */
*pname = name;
*plabel = label;
if (pdepth)
*pdepth = depth;
return 0;
}
static __exception int js_define_var(JSParseState *s, JSAtom name, int tok)
{
JSFunctionDef *fd = s->cur_func;
JSVarDefEnum var_def_type;
if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
return js_parse_error(s, "yield is a reserved identifier");
}
if ((name == JS_ATOM_arguments || name == JS_ATOM_eval)
&& (fd->js_mode & JS_MODE_STRICT)) {
return js_parse_error(s, "invalid variable name in strict mode");
}
if ((name == JS_ATOM_let || name == JS_ATOM_undefined)
&& (tok == TOK_LET || tok == TOK_CONST)) {
return js_parse_error(s, "invalid lexical variable name");
}
switch(tok) {
case TOK_LET:
var_def_type = JS_VAR_DEF_LET;
break;
case TOK_CONST:
var_def_type = JS_VAR_DEF_CONST;
break;
case TOK_VAR:
var_def_type = JS_VAR_DEF_VAR;
break;
case TOK_CATCH:
var_def_type = JS_VAR_DEF_CATCH;
break;
default:
abort();
}
if (define_var(s, fd, name, var_def_type) < 0)
return -1;
return 0;
}
/* name has a live reference. 'is_let' is only used with opcode =
OP_scope_get_var which is never generated by get_lvalue(). */
static void put_lvalue(JSParseState *s, int opcode, int scope,
JSAtom name, int label, PutLValueEnum special,
BOOL is_let)
{
switch(opcode) {
case OP_get_field:
case OP_scope_get_private_field:
/* depth = 1 */
switch(special) {
case PUT_LVALUE_NOKEEP:
case PUT_LVALUE_NOKEEP_DEPTH:
break;
case PUT_LVALUE_KEEP_TOP:
emit_op(s, OP_insert2); /* obj v -> v obj v */
break;
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_swap);
break;
default:
abort();
}
break;
case OP_get_array_el:
case OP_get_ref_value:
/* depth = 2 */
if (opcode == OP_get_ref_value) {
JS_FreeAtom(s->ctx, name);
emit_label(s, label);
}
switch(special) {
case PUT_LVALUE_NOKEEP:
emit_op(s, OP_nop); /* will trigger optimization */
break;
case PUT_LVALUE_NOKEEP_DEPTH:
break;
case PUT_LVALUE_KEEP_TOP:
emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */
break;
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_rot3l);
break;
default:
abort();
}
break;
case OP_get_super_value:
/* depth = 3 */
switch(special) {
case PUT_LVALUE_NOKEEP:
case PUT_LVALUE_NOKEEP_DEPTH:
break;
case PUT_LVALUE_KEEP_TOP:
emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */
break;
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_rot4l);
break;
default:
abort();
}
break;
default:
break;
}
switch(opcode) {
case OP_scope_get_var: /* val -- */
assert(special == PUT_LVALUE_NOKEEP ||
special == PUT_LVALUE_NOKEEP_DEPTH);
emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var);
emit_u32(s, name); /* has refcount */
emit_u16(s, scope);
break;
case OP_get_field:
emit_op(s, OP_put_field);
emit_u32(s, name); /* name has refcount */
break;
case OP_scope_get_private_field:
emit_op(s, OP_scope_put_private_field);
emit_u32(s, name); /* name has refcount */
emit_u16(s, scope);
break;
case OP_get_array_el:
emit_op(s, OP_put_array_el);
break;
case OP_get_ref_value:
emit_op(s, OP_put_ref_value);
break;
case OP_get_super_value:
emit_op(s, OP_put_super_value);
break;
default:
abort();
}
}
static void js_emit_spread_code(JSParseState *s, int depth)
{
int label_rest_next, label_rest_done;
/* XXX: could check if enum object is an actual array and optimize
slice extraction. enumeration record and target array are in a
different order from OP_append case. */
/* enum_rec xxx -- enum_rec xxx array 0 */
emit_op(s, OP_array_from);
emit_u16(s, 0);
emit_op(s, OP_push_i32);
emit_u32(s, 0);
emit_label(s, label_rest_next = new_label(s));
emit_op(s, OP_for_of_next);
emit_u8(s, 2 + depth);
label_rest_done = emit_goto(s, OP_if_true, -1);
/* array idx val -- array idx */
emit_op(s, OP_define_array_el);
emit_op(s, OP_inc);
emit_goto(s, OP_goto, label_rest_next);
emit_label(s, label_rest_done);
/* enum_rec xxx array idx undef -- enum_rec xxx array */
emit_op(s, OP_drop);
emit_op(s, OP_drop);
}
static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
JSAtom label_name,
int label_break, int label_cont,
int drop_count)
{
be->prev = fd->top_break;
fd->top_break = be;
be->label_name = label_name;
be->label_break = label_break;
be->label_cont = label_cont;
be->drop_count = drop_count;
be->label_finally = -1;
be->scope_level = fd->scope_level;
be->has_iterator = FALSE;
}
static void pop_break_entry(JSFunctionDef *fd)
{
BlockEnv *be;
be = fd->top_break;
fd->top_break = be->prev;
}
/* Return -1 if error, 0 if no initializer, 1 if an initializer is
present at the top level. */
static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg,
int hasval, int has_ellipsis,
BOOL allow_initializer)
{
int label_parse, label_assign, label_done, label_lvalue, depth_lvalue;
int start_addr, assign_addr;
JSAtom prop_name, var_name;
int opcode, scope, tok1, skip_bits;
BOOL has_initializer;
if (has_ellipsis < 0) {
/* pre-parse destructuration target for spread detection */
js_parse_skip_parens_token(s, &skip_bits, FALSE);
has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS;
}
label_parse = new_label(s);
label_assign = new_label(s);
start_addr = s->cur_func->byte_code.size;
if (hasval) {
/* consume value from the stack */
emit_op(s, OP_dup);
emit_op(s, OP_undefined);
emit_op(s, OP_strict_eq);
emit_goto(s, OP_if_true, label_parse);
emit_label(s, label_assign);
} else {
emit_goto(s, OP_goto, label_parse);
emit_label(s, label_assign);
/* leave value on the stack */
emit_op(s, OP_dup);
}
assign_addr = s->cur_func->byte_code.size;
if (s->token.val == '{') {
if (next_token(s))
return -1;
/* throw an exception if the value cannot be converted to an object */
emit_op(s, OP_to_object);
if (has_ellipsis) {
/* add excludeList on stack just below src object */
emit_op(s, OP_object);
emit_op(s, OP_swap);
}
while (s->token.val != '}') {
int prop_type;
if (s->token.val == TOK_ELLIPSIS) {
if (!has_ellipsis) {
JS_ThrowInternalError(s->ctx, "unexpected ellipsis token");
return -1;
}
if (next_token(s))
return -1;
if (tok) {
var_name = js_parse_destructuring_var(s, tok, is_arg);
if (var_name == JS_ATOM_NULL)
return -1;
opcode = OP_scope_get_var;
scope = s->cur_func->scope_level;
label_lvalue = -1;
depth_lvalue = 0;
} else {
if (js_parse_left_hand_side_expr(s))
return -1;
if (get_lvalue(s, &opcode, &scope, &var_name,
&label_lvalue, &depth_lvalue, FALSE, '{'))
return -1;
}
if (s->token.val != '}') {
js_parse_error(s, "assignment rest property must be last");
goto var_error;
}
emit_op(s, OP_object); /* target */
emit_op(s, OP_copy_data_properties);
emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5));
goto set_val;
}
prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE, FALSE);
if (prop_type < 0)
return -1;
var_name = JS_ATOM_NULL;
opcode = OP_scope_get_var;
scope = s->cur_func->scope_level;
label_lvalue = -1;
depth_lvalue = 0;
if (prop_type == PROP_TYPE_IDENT) {
if (next_token(s))
goto prop_error;
if ((s->token.val == '[' || s->token.val == '{')
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
tok1 == '=' || tok1 == '}')) {
if (prop_name == JS_ATOM_NULL) {
/* computed property name on stack */
if (has_ellipsis) {
/* define the property in excludeList */
emit_op(s, OP_to_propkey); /* avoid calling ToString twice */
emit_op(s, OP_perm3); /* TOS: src excludeList prop */
emit_op(s, OP_null); /* TOS: src excludeList prop null */
emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */
emit_op(s, OP_perm3); /* TOS: excludeList src prop */
}
/* get the computed property from the source object */
emit_op(s, OP_get_array_el2);
} else {
/* named property */
if (has_ellipsis) {
/* define the property in excludeList */
emit_op(s, OP_swap); /* TOS: src excludeList */
emit_op(s, OP_null); /* TOS: src excludeList null */
emit_op(s, OP_define_field); /* TOS: src excludeList */
emit_atom(s, prop_name);
emit_op(s, OP_swap); /* TOS: excludeList src */
}
/* get the named property from the source object */
emit_op(s, OP_get_field2);
emit_u32(s, prop_name);
}
if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE) < 0)
return -1;
if (s->token.val == '}')
break;
/* accept a trailing comma before the '}' */
if (js_parse_expect(s, ','))
return -1;
continue;
}
if (prop_name == JS_ATOM_NULL) {
emit_op(s, OP_to_propkey2);
if (has_ellipsis) {
/* define the property in excludeList */
emit_op(s, OP_perm3);
emit_op(s, OP_null);
emit_op(s, OP_define_array_el);
emit_op(s, OP_perm3);
}
/* source prop -- source source prop */
emit_op(s, OP_dup1);
} else {
if (has_ellipsis) {
/* define the property in excludeList */
emit_op(s, OP_swap);
emit_op(s, OP_null);
emit_op(s, OP_define_field);
emit_atom(s, prop_name);
emit_op(s, OP_swap);
}
/* source -- source source */
emit_op(s, OP_dup);
}
if (tok) {
var_name = js_parse_destructuring_var(s, tok, is_arg);
if (var_name == JS_ATOM_NULL)
goto prop_error;
} else {
if (js_parse_left_hand_side_expr(s))
goto prop_error;
lvalue:
if (get_lvalue(s, &opcode, &scope, &var_name,
&label_lvalue, &depth_lvalue, FALSE, '{'))
goto prop_error;
/* swap ref and lvalue object if any */
if (prop_name == JS_ATOM_NULL) {
switch(depth_lvalue) {
case 1:
/* source prop x -> x source prop */
emit_op(s, OP_rot3r);
break;
case 2:
/* source prop x y -> x y source prop */
emit_op(s, OP_swap2); /* t p2 s p1 */
break;
case 3:
/* source prop x y z -> x y z source prop */
emit_op(s, OP_rot5l);
emit_op(s, OP_rot5l);
break;
}
} else {
switch(depth_lvalue) {
case 1:
/* source x -> x source */
emit_op(s, OP_swap);
break;
case 2:
/* source x y -> x y source */
emit_op(s, OP_rot3l);
break;
case 3:
/* source x y z -> x y z source */
emit_op(s, OP_rot4l);
break;
}
}
}
if (prop_name == JS_ATOM_NULL) {
/* computed property name on stack */
/* XXX: should have OP_get_array_el2x with depth */
/* source prop -- val */
emit_op(s, OP_get_array_el);
} else {
/* named property */
/* XXX: should have OP_get_field2x with depth */
/* source -- val */
emit_op(s, OP_get_field);
emit_u32(s, prop_name);
}
} else {
/* prop_type = PROP_TYPE_VAR, cannot be a computed property */
if (is_arg && js_parse_check_duplicate_parameter(s, prop_name))
goto prop_error;
if ((s->cur_func->js_mode & JS_MODE_STRICT) &&
(prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) {
js_parse_error(s, "invalid destructuring target");
goto prop_error;
}
if (has_ellipsis) {
/* define the property in excludeList */
emit_op(s, OP_swap);
emit_op(s, OP_null);
emit_op(s, OP_define_field);
emit_atom(s, prop_name);
emit_op(s, OP_swap);
}
if (!tok || tok == TOK_VAR) {
/* generate reference */
/* source -- source source */
emit_op(s, OP_dup);
emit_op(s, OP_scope_get_var);
emit_atom(s, prop_name);
emit_u16(s, s->cur_func->scope_level);
goto lvalue;
}
var_name = JS_DupAtom(s->ctx, prop_name);
/* source -- source val */
emit_op(s, OP_get_field2);
emit_u32(s, prop_name);
}
set_val:
if (tok) {
if (js_define_var(s, var_name, tok))
goto var_error;
scope = s->cur_func->scope_level;
}
if (s->token.val == '=') { /* handle optional default value */
int label_hasval;
emit_op(s, OP_dup);
emit_op(s, OP_undefined);
emit_op(s, OP_strict_eq);
label_hasval = emit_goto(s, OP_if_false, -1);
if (next_token(s))
goto var_error;
emit_op(s, OP_drop);
if (js_parse_assign_expr(s))
goto var_error;
if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
set_object_name(s, var_name);
emit_label(s, label_hasval);
}
/* store value into lvalue object */
put_lvalue(s, opcode, scope, var_name, label_lvalue,
PUT_LVALUE_NOKEEP_DEPTH,
(tok == TOK_CONST || tok == TOK_LET));
if (s->token.val == '}')
break;
/* accept a trailing comma before the '}' */
if (js_parse_expect(s, ','))
return -1;
}
/* drop the source object */
emit_op(s, OP_drop);
if (has_ellipsis) {
emit_op(s, OP_drop); /* pop excludeList */
}
if (next_token(s))
return -1;
} else if (s->token.val == '[') {
BOOL has_spread;
int enum_depth;
BlockEnv block_env;
if (next_token(s))
return -1;
/* the block environment is only needed in generators in case
'yield' triggers a 'return' */
push_break_entry(s->cur_func, &block_env,
JS_ATOM_NULL, -1, -1, 2);
block_env.has_iterator = TRUE;
emit_op(s, OP_for_of_start);
has_spread = FALSE;
while (s->token.val != ']') {
/* get the next value */
if (s->token.val == TOK_ELLIPSIS) {
if (next_token(s))
return -1;
if (s->token.val == ',' || s->token.val == ']')
return js_parse_error(s, "missing binding pattern...");
has_spread = TRUE;
}
if (s->token.val == ',') {
/* do nothing, skip the value, has_spread is false */
emit_op(s, OP_for_of_next);
emit_u8(s, 0);
emit_op(s, OP_drop);
emit_op(s, OP_drop);
} else if ((s->token.val == '[' || s->token.val == '{')
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' ||
tok1 == '=' || tok1 == ']')) {
if (has_spread) {
if (tok1 == '=')
return js_parse_error(s, "rest element cannot have a default value");
js_emit_spread_code(s, 0);
} else {
emit_op(s, OP_for_of_next);
emit_u8(s, 0);
emit_op(s, OP_drop);
}
if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
return -1;
} else {
var_name = JS_ATOM_NULL;
enum_depth = 0;
if (tok) {
var_name = js_parse_destructuring_var(s, tok, is_arg);
if (var_name == JS_ATOM_NULL)
goto var_error;
if (js_define_var(s, var_name, tok))
goto var_error;
opcode = OP_scope_get_var;
scope = s->cur_func->scope_level;
} else {
if (js_parse_left_hand_side_expr(s))
return -1;
if (get_lvalue(s, &opcode, &scope, &var_name,
&label_lvalue, &enum_depth, FALSE, '[')) {
return -1;
}
}
if (has_spread) {
js_emit_spread_code(s, enum_depth);
} else {
emit_op(s, OP_for_of_next);
emit_u8(s, enum_depth);
emit_op(s, OP_drop);
}
if (s->token.val == '=' && !has_spread) {
/* handle optional default value */
int label_hasval;
emit_op(s, OP_dup);
emit_op(s, OP_undefined);
emit_op(s, OP_strict_eq);
label_hasval = emit_goto(s, OP_if_false, -1);
if (next_token(s))
goto var_error;
emit_op(s, OP_drop);
if (js_parse_assign_expr(s))
goto var_error;
if (opcode == OP_scope_get_var || opcode == OP_get_ref_value)
set_object_name(s, var_name);
emit_label(s, label_hasval);
}
/* store value into lvalue object */
put_lvalue(s, opcode, scope, var_name,
label_lvalue, PUT_LVALUE_NOKEEP_DEPTH,
(tok == TOK_CONST || tok == TOK_LET));
}
if (s->token.val == ']')
break;
if (has_spread)
return js_parse_error(s, "rest element must be the last one");
/* accept a trailing comma before the ']' */
if (js_parse_expect(s, ','))
return -1;
}
/* close iterator object:
if completed, enum_obj has been replaced by undefined */
emit_op(s, OP_iterator_close);
pop_break_entry(s->cur_func);
if (next_token(s))
return -1;
} else {
return js_parse_error(s, "invalid assignment syntax");
}
if (s->token.val == '=' && allow_initializer) {
label_done = emit_goto(s, OP_goto, -1);
if (next_token(s))
return -1;
emit_label(s, label_parse);
if (hasval)
emit_op(s, OP_drop);
if (js_parse_assign_expr(s))
return -1;
emit_goto(s, OP_goto, label_assign);
emit_label(s, label_done);
has_initializer = TRUE;
} else {
/* normally hasval is true except if
js_parse_skip_parens_token() was wrong in the parsing */
// assert(hasval);
if (!hasval) {
js_parse_error(s, "too complicated destructuring expression");
return -1;
}
/* remove test and decrement label ref count */
memset(s->cur_func->byte_code.buf + start_addr, OP_nop,
assign_addr - start_addr);
s->cur_func->label_slots[label_parse].ref_count--;
has_initializer = FALSE;
}
return has_initializer;
prop_error:
JS_FreeAtom(s->ctx, prop_name);
var_error:
JS_FreeAtom(s->ctx, var_name);
return -1;
}
static __exception int js_parse_array_literal(JSParseState *s)
{
uint32_t idx;
BOOL need_length;
if (next_token(s))
return -1;
/* small regular arrays are created on the stack */
idx = 0;
while (s->token.val != ']' && idx < 32) {
if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS)
break;
if (js_parse_assign_expr(s))
return -1;
idx++;
/* accept trailing comma */
if (s->token.val == ',') {
if (next_token(s))
return -1;
} else
if (s->token.val != ']')
goto done;
}
emit_op(s, OP_array_from);
emit_u16(s, idx);
/* larger arrays and holes are handled with explicit indices */
need_length = FALSE;
while (s->token.val != ']' && idx < 0x7fffffff) {
if (s->token.val == TOK_ELLIPSIS)
break;
need_length = TRUE;
if (s->token.val != ',') {
if (js_parse_assign_expr(s))
return -1;
emit_op(s, OP_define_field);
emit_u32(s, __JS_AtomFromUInt32(idx));
need_length = FALSE;
}
idx++;
/* accept trailing comma */
if (s->token.val == ',') {
if (next_token(s))
return -1;
}
}
if (s->token.val == ']') {
if (need_length) {
/* Set the length: Cannot use OP_define_field because
length is not configurable */
emit_op(s, OP_dup);
emit_op(s, OP_push_i32);
emit_u32(s, idx);
emit_op(s, OP_put_field);
emit_atom(s, JS_ATOM_length);
}
goto done;
}
/* huge arrays and spread elements require a dynamic index on the stack */
emit_op(s, OP_push_i32);
emit_u32(s, idx);
/* stack has array, index */
while (s->token.val != ']') {
if (s->token.val == TOK_ELLIPSIS) {
if (next_token(s))
return -1;
if (js_parse_assign_expr(s))
return -1;
#if 1
emit_op(s, OP_append);
#else
int label_next, label_done;
label_next = new_label(s);
label_done = new_label(s);
/* enumerate object */
emit_op(s, OP_for_of_start);
emit_op(s, OP_rot5l);
emit_op(s, OP_rot5l);
emit_label(s, label_next);
/* on stack: enum_rec array idx */
emit_op(s, OP_for_of_next);
emit_u8(s, 2);
emit_goto(s, OP_if_true, label_done);
/* append element */
/* enum_rec array idx val -> enum_rec array new_idx */
emit_op(s, OP_define_array_el);
emit_op(s, OP_inc);
emit_goto(s, OP_goto, label_next);
emit_label(s, label_done);
/* close enumeration */
emit_op(s, OP_drop); /* drop undef val */
emit_op(s, OP_nip1); /* drop enum_rec */
emit_op(s, OP_nip1);
emit_op(s, OP_nip1);
#endif
} else {
need_length = TRUE;
if (s->token.val != ',') {
if (js_parse_assign_expr(s))
return -1;
/* a idx val */
emit_op(s, OP_define_array_el);
need_length = FALSE;
}
emit_op(s, OP_inc);
}
if (s->token.val != ',')
break;
if (next_token(s))
return -1;
}
if (need_length) {
/* Set the length: cannot use OP_define_field because
length is not configurable */
emit_op(s, OP_dup1); /* array length - array array length */
emit_op(s, OP_put_field);
emit_atom(s, JS_ATOM_length);
} else {
emit_op(s, OP_drop); /* array length - array */
}
done:
return js_parse_expect(s, ']');
}
/* XXX: remove */
static BOOL has_with_scope(JSFunctionDef *s, int scope_level)
{
/* check if scope chain contains a with statement */
while (s) {
int scope_idx = s->scopes[scope_level].first;
while (scope_idx >= 0) {
JSVarDef *vd = &s->vars[scope_idx];
if (vd->var_name == JS_ATOM__with_)
return TRUE;
scope_idx = vd->scope_next;
}
/* check parent scopes */
scope_level = s->parent_scope_level;
s = s->parent;
}
return FALSE;
}
static void optional_chain_test(JSParseState *s, int *poptional_chaining_label,
int drop_count)
{
int label_next, i;
if (*poptional_chaining_label < 0)
*poptional_chaining_label = new_label(s);
/* XXX: could be more efficient with a specific opcode */
emit_op(s, OP_dup);
emit_op(s, OP_is_undefined_or_null);
label_next = emit_goto(s, OP_if_false, -1);
for(i = 0; i < drop_count; i++)
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
emit_goto(s, OP_goto, *poptional_chaining_label);
emit_label(s, label_next);
}
/* initialize the class fields, called by the constructor. Note:
super() can be called in an arrow function, so <this> and
<class_fields_init> can be variable references */
static void emit_class_field_init(JSParseState *s)
{
int label_next;
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_class_fields_init);
emit_u16(s, s->cur_func->scope_level);
/* no need to call the class field initializer if not defined */
emit_op(s, OP_dup);
label_next = emit_goto(s, OP_if_false, -1);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_op(s, OP_swap);
emit_op(s, OP_call_method);
emit_u16(s, 0);
emit_label(s, label_next);
emit_op(s, OP_drop);
}
/* allowed parse_flags: PF_POSTFIX_CALL, PF_ARROW_FUNC */
static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
{
FuncCallType call_type;
int optional_chaining_label;
BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0;
call_type = FUNC_CALL_NORMAL;
switch(s->token.val) {
case TOK_NUMBER:
{
JSValue val;
val = s->token.u.num.val;
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
emit_op(s, OP_push_i32);
emit_u32(s, JS_VALUE_GET_INT(val));
} else
#ifdef CONFIG_BIGNUM
if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) {
slimb_t e;
int ret;
/* need a runtime conversion */
/* XXX: could add a cache and/or do it once at
the start of the function */
if (emit_push_const(s, val, 0) < 0)
return -1;
e = s->token.u.num.exponent;
if (e == (int32_t)e) {
emit_op(s, OP_push_i32);
emit_u32(s, e);
} else {
val = JS_NewBigInt64_1(s->ctx, e);
if (JS_IsException(val))
return -1;
ret = emit_push_const(s, val, 0);
JS_FreeValue(s->ctx, val);
if (ret < 0)
return -1;
}
emit_op(s, OP_mul_pow10);
} else
#endif
{
if (emit_push_const(s, val, 0) < 0)
return -1;
}
}
if (next_token(s))
return -1;
break;
case TOK_TEMPLATE:
if (js_parse_template(s, 0, NULL))
return -1;
break;
case TOK_STRING:
if (emit_push_const(s, s->token.u.str.str, 1))
return -1;
if (next_token(s))
return -1;
break;
case TOK_DIV_ASSIGN:
s->buf_ptr -= 2;
goto parse_regexp;
case '/':
s->buf_ptr--;
parse_regexp:
{
JSValue str;
int ret, backtrace_flags;
if (!s->ctx->compile_regexp)
return js_parse_error(s, "RegExp are not supported");
/* the previous token is '/' or '/=', so no need to free */
if (js_parse_regexp(s))
return -1;
ret = emit_push_const(s, s->token.u.regexp.body, 0);
str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body,
s->token.u.regexp.flags);
if (JS_IsException(str)) {
/* add the line number info */
backtrace_flags = 0;
if (s->cur_func && s->cur_func->backtrace_barrier)
backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL;
build_backtrace(s->ctx, s->ctx->rt->current_exception,
s->filename, s->token.line_num,
backtrace_flags);
return -1;
}
ret = emit_push_const(s, str, 0);
JS_FreeValue(s->ctx, str);
if (ret)
return -1;
/* we use a specific opcode to be sure the correct
function is called (otherwise the bytecode would have
to be verified by the RegExp constructor) */
emit_op(s, OP_regexp);
if (next_token(s))
return -1;
}
break;
case '(':
if ((parse_flags & PF_ARROW_FUNC) &&
js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) {
if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num))
return -1;
} else {
if (js_parse_expr_paren(s))
return -1;
}
break;
case TOK_FUNCTION:
if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num))
return -1;
break;
case TOK_CLASS:
if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE))
return -1;
break;
case TOK_NULL:
if (next_token(s))
return -1;
emit_op(s, OP_null);
break;
case TOK_THIS:
if (next_token(s))
return -1;
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
break;
case TOK_FALSE:
if (next_token(s))
return -1;
emit_op(s, OP_push_false);
break;
case TOK_TRUE:
if (next_token(s))
return -1;
emit_op(s, OP_push_true);
break;
case TOK_IDENT:
{
JSAtom name;
if (s->token.u.ident.is_reserved) {
return js_parse_error_reserved_identifier(s);
}
if ((parse_flags & PF_ARROW_FUNC) &&
peek_token(s, TRUE) == TOK_ARROW) {
if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num))
return -1;
} else if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) != '\n') {
const uint8_t *source_ptr;
int source_line_num;
source_ptr = s->token.ptr;
source_line_num = s->token.line_num;
if (next_token(s))
return -1;
if (s->token.val == TOK_FUNCTION) {
if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR,
JS_FUNC_ASYNC, JS_ATOM_NULL,
source_ptr, source_line_num))
return -1;
} else if ((parse_flags & PF_ARROW_FUNC) &&
((s->token.val == '(' &&
js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) ||
(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved &&
peek_token(s, TRUE) == TOK_ARROW))) {
if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW,
JS_FUNC_ASYNC, JS_ATOM_NULL,
source_ptr, source_line_num))
return -1;
} else {
name = JS_DupAtom(s->ctx, JS_ATOM_async);
goto do_get_var;
}
} else {
if (s->token.u.ident.atom == JS_ATOM_arguments &&
!s->cur_func->arguments_allowed) {
js_parse_error(s, "'arguments' identifier is not allowed in class field initializer");
return -1;
}
name = JS_DupAtom(s->ctx, s->token.u.ident.atom);
if (next_token(s)) /* update line number before emitting code */
return -1;
do_get_var:
emit_op(s, OP_scope_get_var);
emit_u32(s, name);
emit_u16(s, s->cur_func->scope_level);
}
}
break;
case '{':
case '[':
{
int skip_bits;
if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
return -1;
} else {
if (s->token.val == '{') {
if (js_parse_object_literal(s))
return -1;
} else {
if (js_parse_array_literal(s))
return -1;
}
}
}
break;
case TOK_NEW:
if (next_token(s))
return -1;
if (s->token.val == '.') {
if (next_token(s))
return -1;
if (!token_is_pseudo_keyword(s, JS_ATOM_target))
return js_parse_error(s, "expecting target");
if (!s->cur_func->new_target_allowed)
return js_parse_error(s, "new.target only allowed within functions");
if (next_token(s))
return -1;
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_new_target);
emit_u16(s, 0);
} else {
if (js_parse_postfix_expr(s, 0))
return -1;
accept_lparen = TRUE;
if (s->token.val != '(') {
/* new operator on an object */
emit_op(s, OP_dup);
emit_op(s, OP_call_constructor);
emit_u16(s, 0);
} else {
call_type = FUNC_CALL_NEW;
}
}
break;
case TOK_SUPER:
if (next_token(s))
return -1;
if (s->token.val == '(') {
if (!s->cur_func->super_call_allowed)
return js_parse_error(s, "super() is only valid in a derived class constructor");
call_type = FUNC_CALL_SUPER_CTOR;
} else if (s->token.val == '.' || s->token.val == '[') {
if (!s->cur_func->super_allowed)
return js_parse_error(s, "'super' is only valid in a method");
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_home_object);
emit_u16(s, 0);
emit_op(s, OP_get_super);
} else {
return js_parse_error(s, "invalid use of 'super'");
}
break;
case TOK_IMPORT:
if (next_token(s))
return -1;
if (s->token.val == '.') {
if (next_token(s))
return -1;
if (!token_is_pseudo_keyword(s, JS_ATOM_meta))
return js_parse_error(s, "meta expected");
if (!s->is_module)
return js_parse_error(s, "import.meta only valid in module code");
if (next_token(s))
return -1;
emit_op(s, OP_special_object);
emit_u8(s, OP_SPECIAL_OBJECT_IMPORT_META);
} else {
if (js_parse_expect(s, '('))
return -1;
if (!accept_lparen)
return js_parse_error(s, "invalid use of 'import()'");
if (js_parse_assign_expr(s))
return -1;
if (js_parse_expect(s, ')'))
return -1;
emit_op(s, OP_import);
}
break;
default:
return js_parse_error(s, "unexpected token in expression: '%.*s'",
(int)(s->buf_ptr - s->token.ptr), s->token.ptr);
}
optional_chaining_label = -1;
for(;;) {
JSFunctionDef *fd = s->cur_func;
BOOL has_optional_chain = FALSE;
if (s->token.val == TOK_QUESTION_MARK_DOT) {
/* optional chaining */
if (next_token(s))
return -1;
has_optional_chain = TRUE;
if (s->token.val == '(' && accept_lparen) {
goto parse_func_call;
} else if (s->token.val == '[') {
goto parse_array_access;
} else {
goto parse_property;
}
} else if (s->token.val == TOK_TEMPLATE &&
call_type == FUNC_CALL_NORMAL) {
if (optional_chaining_label >= 0) {
return js_parse_error(s, "template literal cannot appear in an optional chain");
}
call_type = FUNC_CALL_TEMPLATE;
goto parse_func_call2;
} else if (s->token.val == '(' && accept_lparen) {
int opcode, arg_count, drop_count;
/* function call */
parse_func_call:
if (next_token(s))
return -1;
if (call_type == FUNC_CALL_NORMAL) {
parse_func_call2:
switch(opcode = get_prev_opcode(fd)) {
case OP_get_field:
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
drop_count = 2;
break;
case OP_scope_get_private_field:
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
drop_count = 2;
break;
case OP_get_array_el:
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
drop_count = 2;
break;
case OP_scope_get_var:
{
JSAtom name;
int scope;
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5);
if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) {
/* direct 'eval' */
opcode = OP_eval;
} else {
/* verify if function name resolves to a simple
get_loc/get_arg: a function call inside a `with`
statement can resolve to a method call of the
`with` context object
*/
/* XXX: always generate the OP_scope_get_ref
and remove it in variable resolution
pass ? */
if (has_with_scope(fd, scope)) {
opcode = OP_scope_get_ref;
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
}
}
drop_count = 1;
}
break;
case OP_get_super_value:
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el;
/* on stack: this func_obj */
opcode = OP_get_array_el;
drop_count = 2;
break;
default:
opcode = OP_invalid;
drop_count = 1;
break;
}
if (has_optional_chain) {
optional_chain_test(s, &optional_chaining_label,
drop_count);
}
} else {
opcode = OP_invalid;
}
if (call_type == FUNC_CALL_TEMPLATE) {
if (js_parse_template(s, 1, &arg_count))
return -1;
goto emit_func_call;
} else if (call_type == FUNC_CALL_SUPER_CTOR) {
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this_active_func);
emit_u16(s, 0);
emit_op(s, OP_get_super);
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_new_target);
emit_u16(s, 0);
} else if (call_type == FUNC_CALL_NEW) {
emit_op(s, OP_dup); /* new.target = function */
}
/* parse arguments */
arg_count = 0;
while (s->token.val != ')') {
if (arg_count >= 65535) {
return js_parse_error(s, "Too many call arguments");
}
if (s->token.val == TOK_ELLIPSIS)
break;
if (js_parse_assign_expr(s))
return -1;
arg_count++;
if (s->token.val == ')')
break;
/* accept a trailing comma before the ')' */
if (js_parse_expect(s, ','))
return -1;
}
if (s->token.val == TOK_ELLIPSIS) {
emit_op(s, OP_array_from);
emit_u16(s, arg_count);
emit_op(s, OP_push_i32);
emit_u32(s, arg_count);
/* on stack: array idx */
while (s->token.val != ')') {
if (s->token.val == TOK_ELLIPSIS) {
if (next_token(s))
return -1;
if (js_parse_assign_expr(s))
return -1;
#if 1
/* XXX: could pass is_last indicator? */
emit_op(s, OP_append);
#else
int label_next, label_done;
label_next = new_label(s);
label_done = new_label(s);
/* push enumerate object below array/idx pair */
emit_op(s, OP_for_of_start);
emit_op(s, OP_rot5l);
emit_op(s, OP_rot5l);
emit_label(s, label_next);
/* on stack: enum_rec array idx */
emit_op(s, OP_for_of_next);
emit_u8(s, 2);
emit_goto(s, OP_if_true, label_done);
/* append element */
/* enum_rec array idx val -> enum_rec array new_idx */
emit_op(s, OP_define_array_el);
emit_op(s, OP_inc);
emit_goto(s, OP_goto, label_next);
emit_label(s, label_done);
/* close enumeration, drop enum_rec and idx */
emit_op(s, OP_drop); /* drop undef */
emit_op(s, OP_nip1); /* drop enum_rec */
emit_op(s, OP_nip1);
emit_op(s, OP_nip1);
#endif
} else {
if (js_parse_assign_expr(s))
return -1;
/* array idx val */
emit_op(s, OP_define_array_el);
emit_op(s, OP_inc);
}
if (s->token.val == ')')
break;
/* accept a trailing comma before the ')' */
if (js_parse_expect(s, ','))
return -1;
}
if (next_token(s))
return -1;
/* drop the index */
emit_op(s, OP_drop);
/* apply function call */
switch(opcode) {
case OP_get_field:
case OP_scope_get_private_field:
case OP_get_array_el:
case OP_scope_get_ref:
/* obj func array -> func obj array */
emit_op(s, OP_perm3);
emit_op(s, OP_apply);
emit_u16(s, call_type == FUNC_CALL_NEW);
break;
case OP_eval:
emit_op(s, OP_apply_eval);
emit_u16(s, fd->scope_level);
fd->has_eval_call = TRUE;
break;
default:
if (call_type == FUNC_CALL_SUPER_CTOR) {
emit_op(s, OP_apply);
emit_u16(s, 1);
/* set the 'this' value */
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_class_field_init(s);
} else if (call_type == FUNC_CALL_NEW) {
/* obj func array -> func obj array */
emit_op(s, OP_perm3);
emit_op(s, OP_apply);
emit_u16(s, 1);
} else {
/* func array -> func undef array */
emit_op(s, OP_undefined);
emit_op(s, OP_swap);
emit_op(s, OP_apply);
emit_u16(s, 0);
}
break;
}
} else {
if (next_token(s))
return -1;
emit_func_call:
switch(opcode) {
case OP_get_field:
case OP_scope_get_private_field:
case OP_get_array_el:
case OP_scope_get_ref:
emit_op(s, OP_call_method);
emit_u16(s, arg_count);
break;
case OP_eval:
emit_op(s, OP_eval);
emit_u16(s, arg_count);
emit_u16(s, fd->scope_level);
fd->has_eval_call = TRUE;
break;
default:
if (call_type == FUNC_CALL_SUPER_CTOR) {
emit_op(s, OP_call_constructor);
emit_u16(s, arg_count);
/* set the 'this' value */
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_class_field_init(s);
} else if (call_type == FUNC_CALL_NEW) {
emit_op(s, OP_call_constructor);
emit_u16(s, arg_count);
} else {
emit_op(s, OP_call);
emit_u16(s, arg_count);
}
break;
}
}
call_type = FUNC_CALL_NORMAL;
} else if (s->token.val == '.') {
if (next_token(s))
return -1;
parse_property:
if (s->token.val == TOK_PRIVATE_NAME) {
/* private class field */
if (get_prev_opcode(fd) == OP_get_super) {
return js_parse_error(s, "private class field forbidden after super");
}
if (has_optional_chain) {
optional_chain_test(s, &optional_chaining_label, 1);
}
emit_op(s, OP_scope_get_private_field);
emit_atom(s, s->token.u.ident.atom);
emit_u16(s, s->cur_func->scope_level);
} else {
if (!token_is_ident(s->token.val)) {
return js_parse_error(s, "expecting field name");
}
if (get_prev_opcode(fd) == OP_get_super) {
JSValue val;
int ret;
val = JS_AtomToValue(s->ctx, s->token.u.ident.atom);
ret = emit_push_const(s, val, 1);
JS_FreeValue(s->ctx, val);
if (ret)
return -1;
emit_op(s, OP_get_super_value);
} else {
if (has_optional_chain) {
optional_chain_test(s, &optional_chaining_label, 1);
}
emit_op(s, OP_get_field);
emit_atom(s, s->token.u.ident.atom);
}
}
if (next_token(s))
return -1;
} else if (s->token.val == '[') {
int prev_op;
parse_array_access:
prev_op = get_prev_opcode(fd);
if (has_optional_chain) {
optional_chain_test(s, &optional_chaining_label, 1);
}
if (next_token(s))
return -1;
if (js_parse_expr(s))
return -1;
if (js_parse_expect(s, ']'))
return -1;
if (prev_op == OP_get_super) {
emit_op(s, OP_get_super_value);
} else {
emit_op(s, OP_get_array_el);
}
} else {
break;
}
}
if (optional_chaining_label >= 0)
emit_label(s, optional_chaining_label);
return 0;
}
static int js_unsupported_keyword(JSParseState *s, JSAtom atom)
{
char buf[ATOM_GET_STR_BUF_SIZE];
return js_parse_error(s, "unsupported keyword: %s",
JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom));
}
/* XXX: handle IteratorClose when exiting the loop before the
enumeration is done */
static __exception int js_parse_for_in_of(JSParseState *s, int label_name,
BOOL is_async)
{
JSContext *ctx = s->ctx;
JSFunctionDef *fd = s->cur_func;
JSAtom var_name;
BOOL has_initializer, is_for_of, has_destructuring;
int tok, tok1, opcode, scope, block_scope_level;
int label_next, label_expr, label_cont, label_body, label_break;
int pos_next, pos_expr;
BlockEnv break_entry;
has_initializer = FALSE;
has_destructuring = FALSE;
is_for_of = FALSE;
block_scope_level = fd->scope_level;
label_cont = new_label(s);
label_body = new_label(s);
label_break = new_label(s);
label_next = new_label(s);
/* create scope for the lexical variables declared in the enumeration
expressions. XXX: Not completely correct because of weird capturing
semantics in `for (i of o) a.push(function(){return i})` */
push_scope(s);
/* local for_in scope starts here so individual elements
can be closed in statement. */
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, label_cont, 1);
break_entry.scope_level = block_scope_level;
label_expr = emit_goto(s, OP_goto, -1);
pos_next = s->cur_func->byte_code.size;
emit_label(s, label_next);
tok = s->token.val;
switch (is_let(s, DECL_MASK_OTHER)) {
case TRUE:
tok = TOK_LET;
break;
case FALSE:
break;
default:
return -1;
}
if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
if (next_token(s))
return -1;
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
if (s->token.val == '[' || s->token.val == '{') {
if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE) < 0)
return -1;
has_destructuring = TRUE;
} else {
return js_parse_error(s, "variable name expected");
}
var_name = JS_ATOM_NULL;
} else {
var_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s)) {
JS_FreeAtom(s->ctx, var_name);
return -1;
}
if (js_define_var(s, var_name, tok)) {
JS_FreeAtom(s->ctx, var_name);
return -1;
}
emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
OP_scope_put_var_init : OP_scope_put_var);
emit_atom(s, var_name);
emit_u16(s, fd->scope_level);
}
} else {
int skip_bits;
if ((s->token.val == '[' || s->token.val == '{')
&& ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) {
if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
return -1;
} else {
int lvalue_label;
if (js_parse_left_hand_side_expr(s))
return -1;
if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label,
NULL, FALSE, TOK_FOR))
return -1;
put_lvalue(s, opcode, scope, var_name, lvalue_label,
PUT_LVALUE_NOKEEP_BOTTOM, FALSE);
}
var_name = JS_ATOM_NULL;
}
emit_goto(s, OP_goto, label_body);
pos_expr = s->cur_func->byte_code.size;
emit_label(s, label_expr);
if (s->token.val == '=') {
/* XXX: potential scoping issue if inside `with` statement */
has_initializer = TRUE;
/* parse and evaluate initializer prior to evaluating the
object (only used with "for in" with a non lexical variable
in non strict mode */
if (next_token(s) || js_parse_assign_expr2(s, 0)) {
JS_FreeAtom(ctx, var_name);
return -1;
}
if (var_name != JS_ATOM_NULL) {
emit_op(s, OP_scope_put_var);
emit_atom(s, var_name);
emit_u16(s, fd->scope_level);
}
}
JS_FreeAtom(ctx, var_name);
if (token_is_pseudo_keyword(s, JS_ATOM_of)) {
break_entry.has_iterator = is_for_of = TRUE;
break_entry.drop_count += 2;
if (has_initializer)
goto initializer_error;
} else if (s->token.val == TOK_IN) {
if (is_async)
return js_parse_error(s, "'for await' loop should be used with 'of'");
if (has_initializer &&
(tok != TOK_VAR || (fd->js_mode & JS_MODE_STRICT) ||
has_destructuring)) {
initializer_error:
return js_parse_error(s, "a declaration in the head of a for-%s loop can't have an initializer",
is_for_of ? "of" : "in");
}
} else {
return js_parse_error(s, "expected 'of' or 'in' in for control expression");
}
if (next_token(s))
return -1;
if (is_for_of) {
if (js_parse_assign_expr(s))
return -1;
} else {
if (js_parse_expr(s))
return -1;
}
/* close the scope after having evaluated the expression so that
the TDZ values are in the closures */
close_scopes(s, s->cur_func->scope_level, block_scope_level);
if (is_for_of) {
if (is_async)
emit_op(s, OP_for_await_of_start);
else
emit_op(s, OP_for_of_start);
/* on stack: enum_rec */
} else {
emit_op(s, OP_for_in_start);
/* on stack: enum_obj */
}
emit_goto(s, OP_goto, label_cont);
if (js_parse_expect(s, ')'))
return -1;
if (OPTIMIZE) {
/* move the `next` code here */
DynBuf *bc = &s->cur_func->byte_code;
int chunk_size = pos_expr - pos_next;
int offset = bc->size - pos_next;
int i;
dbuf_realloc(bc, bc->size + chunk_size);
dbuf_put(bc, bc->buf + pos_next, chunk_size);
memset(bc->buf + pos_next, OP_nop, chunk_size);
/* `next` part ends with a goto */
s->cur_func->last_opcode_pos = bc->size - 5;
/* relocate labels */
for (i = label_cont; i < s->cur_func->label_count; i++) {
LabelSlot *ls = &s->cur_func->label_slots[i];
if (ls->pos >= pos_next && ls->pos < pos_expr)
ls->pos += offset;
}
}
emit_label(s, label_body);
if (js_parse_statement(s))
return -1;
close_scopes(s, s->cur_func->scope_level, block_scope_level);
emit_label(s, label_cont);
if (is_for_of) {
if (is_async) {
/* call the next method */
/* stack: iter_obj next catch_offset */
emit_op(s, OP_dup3);
emit_op(s, OP_drop);
emit_op(s, OP_call_method);
emit_u16(s, 0);
/* get the result of the promise */
emit_op(s, OP_await);
/* unwrap the value and done values */
emit_op(s, OP_iterator_get_value_done);
} else {
emit_op(s, OP_for_of_next);
emit_u8(s, 0);
}
} else {
emit_op(s, OP_for_in_next);
}
/* on stack: enum_rec / enum_obj value bool */
emit_goto(s, OP_if_false, label_next);
/* drop the undefined value from for_xx_next */
emit_op(s, OP_drop);
emit_label(s, label_break);
if (is_for_of) {
/* close and drop enum_rec */
emit_op(s, OP_iterator_close);
} else {
emit_op(s, OP_drop);
}
pop_break_entry(s->cur_func);
pop_scope(s);
return 0;
}
/* allowed parse_flags: PF_IN_ACCEPTED */
static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok,
BOOL export_flag)
{
JSContext *ctx = s->ctx;
JSFunctionDef *fd = s->cur_func;
JSAtom name = JS_ATOM_NULL;
for (;;) {
if (s->token.val == TOK_IDENT) {
if (s->token.u.ident.is_reserved) {
return js_parse_error_reserved_identifier(s);
}
name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) {
js_parse_error(s, "'let' is not a valid lexical identifier");
goto var_error;
}
if (next_token(s))
goto var_error;
if (js_define_var(s, name, tok))
goto var_error;
if (export_flag) {
if (!add_export_entry(s, s->cur_func->module, name, name,
JS_EXPORT_TYPE_LOCAL))
goto var_error;
}
if (s->token.val == '=') {
if (next_token(s))
goto var_error;
if (tok == TOK_VAR) {
/* Must make a reference for proper `with` semantics */
int opcode, scope, label;
JSAtom name1;
emit_op(s, OP_scope_get_var);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0)
goto var_error;
if (js_parse_assign_expr2(s, parse_flags)) {
JS_FreeAtom(ctx, name1);
goto var_error;
}
set_object_name(s, name);
put_lvalue(s, opcode, scope, name1, label,
PUT_LVALUE_NOKEEP, FALSE);
} else {
if (js_parse_assign_expr2(s, parse_flags))
goto var_error;
set_object_name(s, name);
emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ?
OP_scope_put_var_init : OP_scope_put_var);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
}
} else {
if (tok == TOK_CONST) {
js_parse_error(s, "missing initializer for const variable");
goto var_error;
}
if (tok == TOK_LET) {
/* initialize lexical variable upon entering its scope */
emit_op(s, OP_undefined);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
}
}
JS_FreeAtom(ctx, name);
} else {
int skip_bits;
if ((s->token.val == '[' || s->token.val == '{')
&& js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') {
emit_op(s, OP_undefined);
if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0)
return -1;
} else {
return js_parse_error(s, "variable name expected");
}
}
if (s->token.val != ',')
break;
if (next_token(s))
return -1;
}
return 0;
var_error:
JS_FreeAtom(ctx, name);
return -1;
}
/* execute the finally blocks before return */
static void emit_return(JSParseState *s, BOOL hasval)
{
BlockEnv *top;
int drop_count;
drop_count = 0;
top = s->cur_func->top_break;
while (top != NULL) {
/* XXX: emit the appropriate OP_leave_scope opcodes? Probably not
required as all local variables will be closed upon returning
from JS_CallInternal, but not in the same order. */
if (top->has_iterator) {
/* with 'yield', the exact number of OP_drop to emit is
unknown, so we use a specific operation to look for
the catch offset */
if (!hasval) {
emit_op(s, OP_undefined);
hasval = TRUE;
}
emit_op(s, OP_iterator_close_return);
if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
int label_next, label_next2;
emit_op(s, OP_drop); /* catch offset */
emit_op(s, OP_drop); /* next */
emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_return);
/* stack: iter_obj return_func */
emit_op(s, OP_dup);
emit_op(s, OP_is_undefined_or_null);
label_next = emit_goto(s, OP_if_true, -1);
emit_op(s, OP_call_method);
emit_u16(s, 0);
emit_op(s, OP_iterator_check_object);
emit_op(s, OP_await);
label_next2 = emit_goto(s, OP_goto, -1);
emit_label(s, label_next);
emit_op(s, OP_drop);
emit_label(s, label_next2);
emit_op(s, OP_drop);
} else {
emit_op(s, OP_iterator_close);
}
drop_count = -3;
}
drop_count += top->drop_count;
if (top->label_finally != -1) {
while(drop_count) {
/* must keep the stack top if hasval */
emit_op(s, hasval ? OP_nip : OP_drop);
drop_count--;
}
if (!hasval) {
/* must push return value to keep same stack size */
emit_op(s, OP_undefined);
hasval = TRUE;
}
emit_goto(s, OP_gosub, top->label_finally);
}
top = top->prev;
}
if (s->cur_func->is_derived_class_constructor) {
int label_return;
/* 'this' can be uninitialized, so it may be accessed only if
the derived class constructor does not return an object */
if (hasval) {
emit_op(s, OP_check_ctor_return);
label_return = emit_goto(s, OP_if_false, -1);
emit_op(s, OP_drop);
} else {
label_return = -1;
}
/* XXX: if this is not initialized, should throw the
ReferenceError in the caller realm */
emit_op(s, OP_scope_get_var);
emit_atom(s, JS_ATOM_this);
emit_u16(s, 0);
emit_label(s, label_return);
emit_op(s, OP_return);
} else if (s->cur_func->func_kind != JS_FUNC_NORMAL) {
if (!hasval) {
emit_op(s, OP_undefined);
} else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) {
emit_op(s, OP_await);
}
emit_op(s, OP_return_async);
} else {
emit_op(s, hasval ? OP_return : OP_return_undef);
}
}
static void set_eval_ret_undefined(JSParseState *s)
{
if (s->cur_func->eval_ret_idx >= 0) {
emit_op(s, OP_undefined);
emit_op(s, OP_put_loc);
emit_u16(s, s->cur_func->eval_ret_idx);
}
}
/* allowed parse_flags: PF_IN_ACCEPTED */
static __exception int js_parse_expr2(JSParseState *s, int parse_flags)
{
BOOL comma = FALSE;
for(;;) {
if (js_parse_assign_expr2(s, parse_flags))
return -1;
if (comma) {
/* prevent get_lvalue from using the last expression
as an lvalue. This also prevents the conversion of
of get_var to get_ref for method lookup in function
call inside `with` statement.
*/
s->cur_func->last_opcode_pos = -1;
}
if (s->token.val != ',')
break;
comma = TRUE;
if (next_token(s))
return -1;
emit_op(s, OP_drop);
}
return 0;
}
static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
{
BlockEnv *top;
int i, scope_level;
scope_level = s->cur_func->scope_level;
top = s->cur_func->top_break;
while (top != NULL) {
close_scopes(s, scope_level, top->scope_level);
scope_level = top->scope_level;
if (is_cont &&
top->label_cont != -1 &&
(name == JS_ATOM_NULL || top->label_name == name)) {
/* continue stays inside the same block */
emit_goto(s, OP_goto, top->label_cont);
return 0;
}
if (!is_cont &&
top->label_break != -1 &&
(name == JS_ATOM_NULL || top->label_name == name)) {
emit_goto(s, OP_goto, top->label_break);
return 0;
}
i = 0;
if (top->has_iterator) {
emit_op(s, OP_iterator_close);
i += 3;
}
for(; i < top->drop_count; i++)
emit_op(s, OP_drop);
if (top->label_finally != -1) {
/* must push dummy value to keep same stack depth */
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, top->label_finally);
emit_op(s, OP_drop);
}
top = top->prev;
}
if (name == JS_ATOM_NULL) {
if (is_cont)
return js_parse_error(s, "continue must be inside loop");
else
return js_parse_error(s, "break must be inside loop or switch");
} else {
return js_parse_error(s, "break/continue label not found");
}
}
static int js_parse_statement_or_decl(JSParseState *s, int decl_mask)
{
JSContext *ctx = s->ctx;
JSAtom label_name;
int tok;
/* specific label handling */
/* XXX: support multiple labels on loop statements */
label_name = JS_ATOM_NULL;
if (is_label(s)) {
BlockEnv *be;
label_name = JS_DupAtom(ctx, s->token.u.ident.atom);
for (be = s->cur_func->top_break; be; be = be->prev) {
if (be->label_name == label_name) {
js_parse_error(s, "duplicate label name");
goto fail;
}
}
if (next_token(s))
goto fail;
if (js_parse_expect(s, ':'))
goto fail;
if (s->token.val != TOK_FOR
&& s->token.val != TOK_DO
&& s->token.val != TOK_WHILE) {
/* labelled regular statement */
int label_break, mask;
BlockEnv break_entry;
label_break = new_label(s);
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, -1, 0);
if (!(s->cur_func->js_mode & JS_MODE_STRICT) &&
(decl_mask & DECL_MASK_FUNC_WITH_LABEL)) {
mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL;
} else {
mask = 0;
}
if (js_parse_statement_or_decl(s, mask))
goto fail;
emit_label(s, label_break);
pop_break_entry(s->cur_func);
goto done;
}
}
switch(tok = s->token.val) {
case '{':
if (js_parse_block(s))
goto fail;
break;
case TOK_RETURN:
if (s->cur_func->is_eval) {
js_parse_error(s, "return not in a function");
goto fail;
}
if (next_token(s))
goto fail;
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
if (js_parse_expr(s))
goto fail;
emit_return(s, TRUE);
} else {
emit_return(s, FALSE);
}
if (js_parse_expect_semi(s))
goto fail;
break;
case TOK_THROW:
if (next_token(s))
goto fail;
if (s->got_lf) {
js_parse_error(s, "line terminator not allowed after throw");
goto fail;
}
if (js_parse_expr(s))
goto fail;
emit_op(s, OP_throw);
if (js_parse_expect_semi(s))
goto fail;
break;
case TOK_LET:
case TOK_CONST:
haslet:
if (!(decl_mask & DECL_MASK_OTHER)) {
js_parse_error(s, "lexical declarations can't appear in single-statement context");
goto fail;
}
/* fall thru */
case TOK_VAR:
if (next_token(s))
goto fail;
if (js_parse_var(s, TRUE, tok, FALSE))
goto fail;
if (js_parse_expect_semi(s))
goto fail;
break;
case TOK_IF:
{
int label1, label2, mask;
if (next_token(s))
goto fail;
/* create a new scope for `let f;if(1) function f(){}` */
push_scope(s);
set_eval_ret_undefined(s);
if (js_parse_expr_paren(s))
goto fail;
label1 = emit_goto(s, OP_if_false, -1);
if (s->cur_func->js_mode & JS_MODE_STRICT)
mask = 0;
else
mask = DECL_MASK_FUNC; /* Annex B.3.4 */
if (js_parse_statement_or_decl(s, mask))
goto fail;
if (s->token.val == TOK_ELSE) {
label2 = emit_goto(s, OP_goto, -1);
if (next_token(s))
goto fail;
emit_label(s, label1);
if (js_parse_statement_or_decl(s, mask))
goto fail;
label1 = label2;
}
emit_label(s, label1);
pop_scope(s);
}
break;
case TOK_WHILE:
{
int label_cont, label_break;
BlockEnv break_entry;
label_cont = new_label(s);
label_break = new_label(s);
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, label_cont, 0);
if (next_token(s))
goto fail;
set_eval_ret_undefined(s);
emit_label(s, label_cont);
if (js_parse_expr_paren(s))
goto fail;
emit_goto(s, OP_if_false, label_break);
if (js_parse_statement(s))
goto fail;
emit_goto(s, OP_goto, label_cont);
emit_label(s, label_break);
pop_break_entry(s->cur_func);
}
break;
case TOK_DO:
{
int label_cont, label_break, label1;
BlockEnv break_entry;
label_cont = new_label(s);
label_break = new_label(s);
label1 = new_label(s);
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, label_cont, 0);
if (next_token(s))
goto fail;
emit_label(s, label1);
set_eval_ret_undefined(s);
if (js_parse_statement(s))
goto fail;
emit_label(s, label_cont);
if (js_parse_expect(s, TOK_WHILE))
goto fail;
if (js_parse_expr_paren(s))
goto fail;
/* Insert semicolon if missing */
if (s->token.val == ';') {
if (next_token(s))
goto fail;
}
emit_goto(s, OP_if_true, label1);
emit_label(s, label_break);
pop_break_entry(s->cur_func);
}
break;
case TOK_FOR:
{
int label_cont, label_break, label_body, label_test;
int pos_cont, pos_body, block_scope_level;
BlockEnv break_entry;
int tok, bits;
BOOL is_async;
if (next_token(s))
goto fail;
set_eval_ret_undefined(s);
bits = 0;
is_async = FALSE;
if (s->token.val == '(') {
js_parse_skip_parens_token(s, &bits, FALSE);
} else if (s->token.val == TOK_AWAIT) {
if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) {
js_parse_error(s, "for await is only valid in asynchronous functions");
goto fail;
}
is_async = TRUE;
if (next_token(s))
goto fail;
}
if (js_parse_expect(s, '('))
goto fail;
if (!(bits & SKIP_HAS_SEMI)) {
/* parse for/in or for/of */
if (js_parse_for_in_of(s, label_name, is_async))
goto fail;
break;
}
block_scope_level = s->cur_func->scope_level;
/* create scope for the lexical variables declared in the initial,
test and increment expressions */
push_scope(s);
/* initial expression */
tok = s->token.val;
if (tok != ';') {
switch (is_let(s, DECL_MASK_OTHER)) {
case TRUE:
tok = TOK_LET;
break;
case FALSE:
break;
default:
goto fail;
}
if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) {
if (next_token(s))
goto fail;
if (js_parse_var(s, FALSE, tok, FALSE))
goto fail;
} else {
if (js_parse_expr2(s, FALSE))
goto fail;
emit_op(s, OP_drop);
}
/* close the closures before the first iteration */
close_scopes(s, s->cur_func->scope_level, block_scope_level);
}
if (js_parse_expect(s, ';'))
goto fail;
label_test = new_label(s);
label_cont = new_label(s);
label_body = new_label(s);
label_break = new_label(s);
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, label_cont, 0);
/* test expression */
if (s->token.val == ';') {
/* no test expression */
label_test = label_body;
} else {
emit_label(s, label_test);
if (js_parse_expr(s))
goto fail;
emit_goto(s, OP_if_false, label_break);
}
if (js_parse_expect(s, ';'))
goto fail;
if (s->token.val == ')') {
/* no end expression */
break_entry.label_cont = label_cont = label_test;
pos_cont = 0; /* avoid warning */
} else {
/* skip the end expression */
emit_goto(s, OP_goto, label_body);
pos_cont = s->cur_func->byte_code.size;
emit_label(s, label_cont);
if (js_parse_expr(s))
goto fail;
emit_op(s, OP_drop);
if (label_test != label_body)
emit_goto(s, OP_goto, label_test);
}
if (js_parse_expect(s, ')'))
goto fail;
pos_body = s->cur_func->byte_code.size;
emit_label(s, label_body);
if (js_parse_statement(s))
goto fail;
/* close the closures before the next iteration */
/* XXX: check continue case */
close_scopes(s, s->cur_func->scope_level, block_scope_level);
if (OPTIMIZE && label_test != label_body && label_cont != label_test) {
/* move the increment code here */
DynBuf *bc = &s->cur_func->byte_code;
int chunk_size = pos_body - pos_cont;
int offset = bc->size - pos_cont;
int i;
dbuf_realloc(bc, bc->size + chunk_size);
dbuf_put(bc, bc->buf + pos_cont, chunk_size);
memset(bc->buf + pos_cont, OP_nop, chunk_size);
/* increment part ends with a goto */
s->cur_func->last_opcode_pos = bc->size - 5;
/* relocate labels */
for (i = label_cont; i < s->cur_func->label_count; i++) {
LabelSlot *ls = &s->cur_func->label_slots[i];
if (ls->pos >= pos_cont && ls->pos < pos_body)
ls->pos += offset;
}
} else {
emit_goto(s, OP_goto, label_cont);
}
emit_label(s, label_break);
pop_break_entry(s->cur_func);
pop_scope(s);
}
break;
case TOK_BREAK:
case TOK_CONTINUE:
{
int is_cont = s->token.val - TOK_BREAK;
int label;
if (next_token(s))
goto fail;
if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
label = s->token.u.ident.atom;
else
label = JS_ATOM_NULL;
if (emit_break(s, label, is_cont))
goto fail;
if (label != JS_ATOM_NULL) {
if (next_token(s))
goto fail;
}
if (js_parse_expect_semi(s))
goto fail;
}
break;
case TOK_SWITCH:
{
int label_case, label_break, label1;
int default_label_pos;
BlockEnv break_entry;
if (next_token(s))
goto fail;
set_eval_ret_undefined(s);
if (js_parse_expr_paren(s))
goto fail;
push_scope(s);
label_break = new_label(s);
push_break_entry(s->cur_func, &break_entry,
label_name, label_break, -1, 1);
if (js_parse_expect(s, '{'))
goto fail;
default_label_pos = -1;
label_case = -1;
while (s->token.val != '}') {
if (s->token.val == TOK_CASE) {
label1 = -1;
if (label_case >= 0) {
/* skip the case if needed */
label1 = emit_goto(s, OP_goto, -1);
}
emit_label(s, label_case);
label_case = -1;
for (;;) {
/* parse a sequence of case clauses */
if (next_token(s))
goto fail;
emit_op(s, OP_dup);
if (js_parse_expr(s))
goto fail;
if (js_parse_expect(s, ':'))
goto fail;
emit_op(s, OP_strict_eq);
if (s->token.val == TOK_CASE) {
label1 = emit_goto(s, OP_if_true, label1);
} else {
label_case = emit_goto(s, OP_if_false, -1);
emit_label(s, label1);
break;
}
}
} else if (s->token.val == TOK_DEFAULT) {
if (next_token(s))
goto fail;
if (js_parse_expect(s, ':'))
goto fail;
if (default_label_pos >= 0) {
js_parse_error(s, "duplicate default");
goto fail;
}
if (label_case < 0) {
/* falling thru direct from switch expression */
label_case = emit_goto(s, OP_goto, -1);
}
/* Emit a dummy label opcode. Label will be patched after
the end of the switch body. Do not use emit_label(s, 0)
because it would clobber label 0 address, preventing
proper optimizer operation.
*/
emit_op(s, OP_label);
emit_u32(s, 0);
default_label_pos = s->cur_func->byte_code.size - 4;
} else {
if (label_case < 0) {
/* falling thru direct from switch expression */
js_parse_error(s, "invalid switch statement");
goto fail;
}
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
goto fail;
}
}
if (js_parse_expect(s, '}'))
goto fail;
if (default_label_pos >= 0) {
/* Ugly patch for the the `default` label, shameful and risky */
put_u32(s->cur_func->byte_code.buf + default_label_pos,
label_case);
s->cur_func->label_slots[label_case].pos = default_label_pos + 4;
} else {
emit_label(s, label_case);
}
emit_label(s, label_break);
emit_op(s, OP_drop); /* drop the switch expression */
pop_break_entry(s->cur_func);
pop_scope(s);
}
break;
case TOK_TRY:
{
int label_catch, label_catch2, label_finally, label_end;
JSAtom name;
BlockEnv block_env;
set_eval_ret_undefined(s);
if (next_token(s))
goto fail;
label_catch = new_label(s);
label_catch2 = new_label(s);
label_finally = new_label(s);
label_end = new_label(s);
emit_goto(s, OP_catch, label_catch);
push_break_entry(s->cur_func, &block_env,
JS_ATOM_NULL, -1, -1, 1);
block_env.label_finally = label_finally;
if (js_parse_block(s))
goto fail;
pop_break_entry(s->cur_func);
if (js_is_live_code(s)) {
/* drop the catch offset */
emit_op(s, OP_drop);
/* must push dummy value to keep same stack size */
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, label_finally);
emit_op(s, OP_drop);
emit_goto(s, OP_goto, label_end);
}
if (s->token.val == TOK_CATCH) {
if (next_token(s))
goto fail;
push_scope(s); /* catch variable */
emit_label(s, label_catch);
if (s->token.val == '{') {
/* support optional-catch-binding feature */
emit_op(s, OP_drop); /* pop the exception object */
} else {
if (js_parse_expect(s, '('))
goto fail;
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) {
if (s->token.val == '[' || s->token.val == '{') {
/* XXX: TOK_LET is not completely correct */
if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE) < 0)
goto fail;
} else {
js_parse_error(s, "identifier expected");
goto fail;
}
} else {
name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s)
|| js_define_var(s, name, TOK_CATCH) < 0) {
JS_FreeAtom(ctx, name);
goto fail;
}
/* store the exception value in the catch variable */
emit_op(s, OP_scope_put_var);
emit_u32(s, name);
emit_u16(s, s->cur_func->scope_level);
}
if (js_parse_expect(s, ')'))
goto fail;
}
/* XXX: should keep the address to nop it out if there is no finally block */
emit_goto(s, OP_catch, label_catch2);
push_scope(s); /* catch block */
push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
-1, -1, 1);
block_env.label_finally = label_finally;
if (js_parse_block(s))
goto fail;
pop_break_entry(s->cur_func);
pop_scope(s); /* catch block */
pop_scope(s); /* catch variable */
if (js_is_live_code(s)) {
/* drop the catch2 offset */
emit_op(s, OP_drop);
/* XXX: should keep the address to nop it out if there is no finally block */
/* must push dummy value to keep same stack size */
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, label_finally);
emit_op(s, OP_drop);
emit_goto(s, OP_goto, label_end);
}
/* catch exceptions thrown in the catch block to execute the
* finally clause and rethrow the exception */
emit_label(s, label_catch2);
/* catch value is at TOS, no need to push undefined */
emit_goto(s, OP_gosub, label_finally);
emit_op(s, OP_throw);
} else if (s->token.val == TOK_FINALLY) {
/* finally without catch : execute the finally clause
* and rethrow the exception */
emit_label(s, label_catch);
/* catch value is at TOS, no need to push undefined */
emit_goto(s, OP_gosub, label_finally);
emit_op(s, OP_throw);
} else {
js_parse_error(s, "expecting catch or finally");
goto fail;
}
emit_label(s, label_finally);
if (s->token.val == TOK_FINALLY) {
int saved_eval_ret_idx = 0; /* avoid warning */
if (next_token(s))
goto fail;
/* on the stack: ret_value gosub_ret_value */
push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL,
-1, -1, 2);
if (s->cur_func->eval_ret_idx >= 0) {
/* 'finally' updates eval_ret only if not a normal
termination */
saved_eval_ret_idx =
add_var(s->ctx, s->cur_func, JS_ATOM__ret_);
if (saved_eval_ret_idx < 0)
goto fail;
emit_op(s, OP_get_loc);
emit_u16(s, s->cur_func->eval_ret_idx);
emit_op(s, OP_put_loc);
emit_u16(s, saved_eval_ret_idx);
set_eval_ret_undefined(s);
}
if (js_parse_block(s))
goto fail;
if (s->cur_func->eval_ret_idx >= 0) {
emit_op(s, OP_get_loc);
emit_u16(s, saved_eval_ret_idx);
emit_op(s, OP_put_loc);
emit_u16(s, s->cur_func->eval_ret_idx);
}
pop_break_entry(s->cur_func);
}
emit_op(s, OP_ret);
emit_label(s, label_end);
}
break;
case ';':
/* empty statement */
if (next_token(s))
goto fail;
break;
case TOK_WITH:
if (s->cur_func->js_mode & JS_MODE_STRICT) {
js_parse_error(s, "invalid keyword: with");
goto fail;
} else {
int with_idx;
if (next_token(s))
goto fail;
if (js_parse_expr_paren(s))
goto fail;
push_scope(s);
with_idx = define_var(s, s->cur_func, JS_ATOM__with_,
JS_VAR_DEF_WITH);
if (with_idx < 0)
goto fail;
emit_op(s, OP_to_object);
emit_op(s, OP_put_loc);
emit_u16(s, with_idx);
set_eval_ret_undefined(s);
if (js_parse_statement(s))
goto fail;
/* Popping scope drops lexical context for the with object variable */
pop_scope(s);
}
break;
case TOK_FUNCTION:
/* ES6 Annex B.3.2 and B.3.3 semantics */
if (!(decl_mask & DECL_MASK_FUNC))
goto func_decl_error;
if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*')
goto func_decl_error;
goto parse_func_var;
case TOK_IDENT:
if (s->token.u.ident.is_reserved) {
js_parse_error_reserved_identifier(s);
goto fail;
}
/* Determine if `let` introduces a Declaration or an ExpressionStatement */
switch (is_let(s, decl_mask)) {
case TRUE:
tok = TOK_LET;
goto haslet;
case FALSE:
break;
default:
goto fail;
}
if (token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) == TOK_FUNCTION) {
if (!(decl_mask & DECL_MASK_OTHER)) {
func_decl_error:
js_parse_error(s, "function declarations can't appear in single-statement context");
goto fail;
}
parse_func_var:
if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num))
goto fail;
break;
}
goto hasexpr;
case TOK_CLASS:
if (!(decl_mask & DECL_MASK_OTHER)) {
js_parse_error(s, "class declarations can't appear in single-statement context");
goto fail;
}
if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE))
return -1;
break;
case TOK_DEBUGGER:
/* currently no debugger, so just skip the keyword */
if (next_token(s))
goto fail;
if (js_parse_expect_semi(s))
goto fail;
break;
case TOK_ENUM:
case TOK_EXPORT:
case TOK_EXTENDS:
js_unsupported_keyword(s, s->token.u.ident.atom);
goto fail;
default:
hasexpr:
if (js_parse_expr(s))
goto fail;
if (s->cur_func->eval_ret_idx >= 0) {
/* store the expression value so that it can be returned
by eval() */
emit_op(s, OP_put_loc);
emit_u16(s, s->cur_func->eval_ret_idx);
} else {
emit_op(s, OP_drop); /* drop the result */
}
if (js_parse_expect_semi(s))
goto fail;
break;
}
done:
JS_FreeAtom(ctx, label_name);
return 0;
fail:
JS_FreeAtom(ctx, label_name);
return -1;
}
static __exception int js_parse_delete(JSParseState *s)
{
JSFunctionDef *fd = s->cur_func;
JSAtom name;
int opcode;
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
switch(opcode = get_prev_opcode(fd)) {
case OP_get_field:
{
JSValue val;
int ret;
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
val = JS_AtomToValue(s->ctx, name);
ret = emit_push_const(s, val, 1);
JS_FreeValue(s->ctx, val);
JS_FreeAtom(s->ctx, name);
if (ret)
return ret;
}
goto do_delete;
case OP_get_array_el:
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
do_delete:
emit_op(s, OP_delete);
break;
case OP_scope_get_var:
/* 'delete this': this is not a reference */
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
if (name == JS_ATOM_this || name == JS_ATOM_new_target)
goto ret_true;
if (fd->js_mode & JS_MODE_STRICT) {
return js_parse_error(s, "cannot delete a direct reference in strict mode");
} else {
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var;
}
break;
case OP_scope_get_private_field:
return js_parse_error(s, "cannot delete a private class field");
case OP_get_super_value:
emit_op(s, OP_throw_error);
emit_atom(s, JS_ATOM_NULL);
emit_u8(s, JS_THROW_ERROR_DELETE_SUPER);
break;
default:
ret_true:
emit_op(s, OP_drop);
emit_op(s, OP_push_true);
break;
}
return 0;
}
/* allowed parse_flags: PF_ARROW_FUNC, PF_POW_ALLOWED, PF_POW_FORBIDDEN */
static __exception int js_parse_unary(JSParseState *s, int parse_flags)
{
int op;
switch(s->token.val) {
case '+':
case '-':
case '!':
case '~':
case TOK_VOID:
op = s->token.val;
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
switch(op) {
case '-':
emit_op(s, OP_neg);
break;
case '+':
emit_op(s, OP_plus);
break;
case '!':
emit_op(s, OP_lnot);
break;
case '~':
emit_op(s, OP_not);
break;
case TOK_VOID:
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
break;
default:
abort();
}
parse_flags = 0;
break;
case TOK_DEC:
case TOK_INC:
{
int opcode, op, scope, label;
JSAtom name;
op = s->token.val;
if (next_token(s))
return -1;
if (js_parse_unary(s, 0))
return -1;
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
return -1;
emit_op(s, OP_dec + op - TOK_DEC);
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP,
FALSE);
}
break;
case TOK_TYPEOF:
{
JSFunctionDef *fd;
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
/* reference access should not return an exception, so we
patch the get_var */
fd = s->cur_func;
if (get_prev_opcode(fd) == OP_scope_get_var) {
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef;
}
emit_op(s, OP_typeof);
parse_flags = 0;
}
break;
case TOK_DELETE:
if (js_parse_delete(s))
return -1;
parse_flags = 0;
break;
case TOK_AWAIT:
if (!(s->cur_func->func_kind & JS_FUNC_ASYNC))
return js_parse_error(s, "unexpected 'await' keyword");
if (!s->cur_func->in_function_body)
return js_parse_error(s, "await in default expression");
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
emit_op(s, OP_await);
parse_flags = 0;
break;
default:
if (js_parse_postfix_expr(s, (parse_flags & PF_ARROW_FUNC) |
PF_POSTFIX_CALL))
return -1;
if (!s->got_lf &&
(s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
int opcode, op, scope, label;
JSAtom name;
op = s->token.val;
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op))
return -1;
emit_op(s, OP_post_dec + op - TOK_DEC);
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND,
FALSE);
if (next_token(s))
return -1;
}
break;
}
if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) {
#ifdef CONFIG_BIGNUM
if (s->token.val == TOK_POW || s->token.val == TOK_MATH_POW) {
/* Extended exponentiation syntax rules: we extend the ES7
grammar in order to have more intuitive semantics:
-2**2 evaluates to -4. */
if (!(s->cur_func->js_mode & JS_MODE_MATH)) {
if (parse_flags & PF_POW_FORBIDDEN) {
JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'");
return -1;
}
}
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_ALLOWED))
return -1;
emit_op(s, OP_pow);
}
#else
if (s->token.val == TOK_POW) {
/* Strict ES7 exponentiation syntax rules: To solve
conficting semantics between different implementations
regarding the precedence of prefix operators and the
postifx exponential, ES7 specifies that -2**2 is a
syntax error. */
if (parse_flags & PF_POW_FORBIDDEN) {
JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'");
return -1;
}
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_ALLOWED))
return -1;
emit_op(s, OP_pow);
}
#endif
}
return 0;
}
/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */
static __exception int js_parse_expr_binary(JSParseState *s, int level,
int parse_flags)
{
int op, opcode;
if (level == 0) {
return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) |
PF_POW_ALLOWED);
}
if (js_parse_expr_binary(s, level - 1, parse_flags))
return -1;
for(;;) {
op = s->token.val;
switch(level) {
case 1:
switch(op) {
case '*':
opcode = OP_mul;
break;
case '/':
opcode = OP_div;
break;
case '%':
#ifdef CONFIG_BIGNUM
if (s->cur_func->js_mode & JS_MODE_MATH)
opcode = OP_math_mod;
else
#endif
opcode = OP_mod;
break;
default:
return 0;
}
break;
case 2:
switch(op) {
case '+':
opcode = OP_add;
break;
case '-':
opcode = OP_sub;
break;
default:
return 0;
}
break;
case 3:
switch(op) {
case TOK_SHL:
opcode = OP_shl;
break;
case TOK_SAR:
opcode = OP_sar;
break;
case TOK_SHR:
opcode = OP_shr;
break;
default:
return 0;
}
break;
case 4:
switch(op) {
case '<':
opcode = OP_lt;
break;
case '>':
opcode = OP_gt;
break;
case TOK_LTE:
opcode = OP_lte;
break;
case TOK_GTE:
opcode = OP_gte;
break;
case TOK_INSTANCEOF:
opcode = OP_instanceof;
break;
case TOK_IN:
if (parse_flags & PF_IN_ACCEPTED) {
opcode = OP_in;
} else {
return 0;
}
break;
default:
return 0;
}
break;
case 5:
switch(op) {
case TOK_EQ:
opcode = OP_eq;
break;
case TOK_NEQ:
opcode = OP_neq;
break;
case TOK_STRICT_EQ:
opcode = OP_strict_eq;
break;
case TOK_STRICT_NEQ:
opcode = OP_strict_neq;
break;
default:
return 0;
}
break;
case 6:
switch(op) {
case '&':
opcode = OP_and;
break;
default:
return 0;
}
break;
case 7:
switch(op) {
case '^':
opcode = OP_xor;
break;
default:
return 0;
}
break;
case 8:
switch(op) {
case '|':
opcode = OP_or;
break;
default:
return 0;
}
break;
default:
abort();
}
if (next_token(s))
return -1;
if (js_parse_expr_binary(s, level - 1, parse_flags & ~PF_ARROW_FUNC))
return -1;
emit_op(s, opcode);
}
return 0;
}
/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */
static __exception int js_parse_logical_and_or(JSParseState *s, int op,
int parse_flags)
{
int label1;
if (op == TOK_LAND) {
if (js_parse_expr_binary(s, 8, parse_flags))
return -1;
} else {
if (js_parse_logical_and_or(s, TOK_LAND, parse_flags))
return -1;
}
if (s->token.val == op) {
label1 = new_label(s);
for(;;) {
if (next_token(s))
return -1;
emit_op(s, OP_dup);
emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1);
emit_op(s, OP_drop);
if (op == TOK_LAND) {
if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC))
return -1;
} else {
if (js_parse_logical_and_or(s, TOK_LAND,
parse_flags & ~PF_ARROW_FUNC))
return -1;
}
if (s->token.val != op) {
if (s->token.val == TOK_DOUBLE_QUESTION_MARK)
return js_parse_error(s, "cannot mix ?? with && or ||");
break;
}
}
emit_label(s, label1);
}
return 0;
}
static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags)
{
int label1;
if (js_parse_logical_and_or(s, TOK_LOR, parse_flags))
return -1;
if (s->token.val == TOK_DOUBLE_QUESTION_MARK) {
label1 = new_label(s);
for(;;) {
if (next_token(s))
return -1;
emit_op(s, OP_dup);
emit_op(s, OP_is_undefined_or_null);
emit_goto(s, OP_if_false, label1);
emit_op(s, OP_drop);
if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC))
return -1;
if (s->token.val != TOK_DOUBLE_QUESTION_MARK)
break;
}
emit_label(s, label1);
}
return 0;
}
/* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */
static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags)
{
int label1, label2;
if (js_parse_coalesce_expr(s, parse_flags))
return -1;
if (s->token.val == '?') {
if (next_token(s))
return -1;
label1 = emit_goto(s, OP_if_false, -1);
if (js_parse_assign_expr(s))
return -1;
if (js_parse_expect(s, ':'))
return -1;
label2 = emit_goto(s, OP_goto, -1);
emit_label(s, label1);
if (js_parse_assign_expr2(s, parse_flags & PF_IN_ACCEPTED))
return -1;
emit_label(s, label2);
}
return 0;
}
/* allowed parse_flags: PF_IN_ACCEPTED */
static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags)
{
int opcode, op, scope;
JSAtom name0 = JS_ATOM_NULL;
JSAtom name;
if (s->token.val == TOK_YIELD) {
BOOL is_star = FALSE, is_async;
if (!(s->cur_func->func_kind & JS_FUNC_GENERATOR))
return js_parse_error(s, "unexpected 'yield' keyword");
if (!s->cur_func->in_function_body)
return js_parse_error(s, "yield in default expression");
if (next_token(s))
return -1;
/* XXX: is there a better method to detect 'yield' without
parameters ? */
if (s->token.val != ';' && s->token.val != ')' &&
s->token.val != ']' && s->token.val != '}' &&
s->token.val != ',' && s->token.val != ':' && !s->got_lf) {
if (s->token.val == '*') {
is_star = TRUE;
if (next_token(s))
return -1;
}
if (js_parse_assign_expr2(s, parse_flags))
return -1;
} else {
emit_op(s, OP_undefined);
}
is_async = (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR);
if (is_star) {
int label_loop, label_return, label_next;
int label_return1, label_yield, label_throw, label_throw1;
int label_throw2;
label_loop = new_label(s);
label_yield = new_label(s);
emit_op(s, is_async ? OP_for_await_of_start : OP_for_of_start);
/* remove the catch offset (XXX: could avoid pushing back
undefined) */
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
emit_op(s, OP_undefined); /* initial value */
emit_label(s, label_loop);
emit_op(s, OP_iterator_next);
if (is_async)
emit_op(s, OP_await);
emit_op(s, OP_iterator_check_object);
emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_done);
label_next = emit_goto(s, OP_if_true, -1); /* end of loop */
emit_label(s, label_yield);
if (is_async) {
/* OP_async_yield_star takes the value as parameter */
emit_op(s, OP_get_field);
emit_atom(s, JS_ATOM_value);
emit_op(s, OP_await);
emit_op(s, OP_async_yield_star);
} else {
/* OP_yield_star takes (value, done) as parameter */
emit_op(s, OP_yield_star);
}
emit_op(s, OP_dup);
label_return = emit_goto(s, OP_if_true, -1);
emit_op(s, OP_drop);
emit_goto(s, OP_goto, label_loop);
emit_label(s, label_return);
emit_op(s, OP_push_i32);
emit_u32(s, 2);
emit_op(s, OP_strict_eq);
label_throw = emit_goto(s, OP_if_true, -1);
/* return handling */
if (is_async)
emit_op(s, OP_await);
emit_op(s, OP_iterator_call);
emit_u8(s, 0);
label_return1 = emit_goto(s, OP_if_true, -1);
if (is_async)
emit_op(s, OP_await);
emit_op(s, OP_iterator_check_object);
emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_done);
emit_goto(s, OP_if_false, label_yield);
emit_op(s, OP_get_field);
emit_atom(s, JS_ATOM_value);
emit_label(s, label_return1);
emit_op(s, OP_nip);
emit_op(s, OP_nip);
emit_op(s, OP_nip);
emit_return(s, TRUE);
/* throw handling */
emit_label(s, label_throw);
emit_op(s, OP_iterator_call);
emit_u8(s, 1);
label_throw1 = emit_goto(s, OP_if_true, -1);
if (is_async)
emit_op(s, OP_await);
emit_op(s, OP_iterator_check_object);
emit_op(s, OP_get_field2);
emit_atom(s, JS_ATOM_done);
emit_goto(s, OP_if_false, label_yield);
emit_goto(s, OP_goto, label_next);
/* close the iterator and throw a type error exception */
emit_label(s, label_throw1);
emit_op(s, OP_iterator_call);
emit_u8(s, 2);
label_throw2 = emit_goto(s, OP_if_true, -1);
if (is_async)
emit_op(s, OP_await);
emit_label(s, label_throw2);
emit_op(s, OP_throw_error);
emit_atom(s, JS_ATOM_NULL);
emit_u8(s, JS_THROW_ERROR_ITERATOR_THROW);
emit_label(s, label_next);
emit_op(s, OP_get_field);
emit_atom(s, JS_ATOM_value);
emit_op(s, OP_nip); /* keep the value associated with
done = true */
emit_op(s, OP_nip);
emit_op(s, OP_nip);
} else {
int label_next;
if (is_async)
emit_op(s, OP_await);
emit_op(s, OP_yield);
label_next = emit_goto(s, OP_if_false, -1);
emit_return(s, TRUE);
emit_label(s, label_next);
}
return 0;
}
if (s->token.val == TOK_IDENT) {
/* name0 is used to check for OP_set_name pattern, not duplicated */
name0 = s->token.u.ident.atom;
}
if (js_parse_cond_expr(s, parse_flags | PF_ARROW_FUNC))
return -1;
op = s->token.val;
if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) {
int label;
if (next_token(s))
return -1;
if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0)
return -1;
if (js_parse_assign_expr2(s, parse_flags)) {
JS_FreeAtom(s->ctx, name);
return -1;
}
if (op == '=') {
if (opcode == OP_get_ref_value && name == name0) {
set_object_name(s, name);
}
} else {
static const uint8_t assign_opcodes[] = {
OP_mul, OP_div, OP_mod, OP_add, OP_sub,
OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or,
#ifdef CONFIG_BIGNUM
OP_pow,
#endif
OP_pow,
};
op = assign_opcodes[op - TOK_MUL_ASSIGN];
#ifdef CONFIG_BIGNUM
if (s->cur_func->js_mode & JS_MODE_MATH) {
if (op == OP_mod)
op = OP_math_mod;
}
#endif
emit_op(s, op);
}
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE);
} else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) {
int label, label1, depth_lvalue, label2;
if (next_token(s))
return -1;
if (get_lvalue(s, &opcode, &scope, &name, &label,
&depth_lvalue, TRUE, op) < 0)
return -1;
emit_op(s, OP_dup);
if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN)
emit_op(s, OP_is_undefined_or_null);
label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false,
-1);
emit_op(s, OP_drop);
if (js_parse_assign_expr2(s, parse_flags)) {
JS_FreeAtom(s->ctx, name);
return -1;
}
if (opcode == OP_get_ref_value && name == name0) {
set_object_name(s, name);
}
switch(depth_lvalue) {
case 1:
emit_op(s, OP_insert2);
break;
case 2:
emit_op(s, OP_insert3);
break;
case 3:
emit_op(s, OP_insert4);
break;
default:
abort();
}
/* XXX: we disable the OP_put_ref_value optimization by not
using put_lvalue() otherwise depth_lvalue is not correct */
put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH,
FALSE);
label2 = emit_goto(s, OP_goto, -1);
emit_label(s, label1);
/* remove the lvalue stack entries */
while (depth_lvalue != 0) {
emit_op(s, OP_nip);
depth_lvalue--;
}
emit_label(s, label2);
}
return 0;
}
static __exception int js_parse_assign_expr(JSParseState *s)
{
return js_parse_assign_expr2(s, PF_IN_ACCEPTED);
}
static __exception int js_parse_expr(JSParseState *s)
{
return js_parse_expr2(s, PF_IN_ACCEPTED);
}
static __exception JSAtom js_parse_from_clause(JSParseState *s)
{
JSAtom module_name;
if (!token_is_pseudo_keyword(s, JS_ATOM_from)) {
js_parse_error(s, "from clause expected");
return JS_ATOM_NULL;
}
if (next_token(s))
return JS_ATOM_NULL;
if (s->token.val != TOK_STRING) {
js_parse_error(s, "string expected");
return JS_ATOM_NULL;
}
module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str);
if (module_name == JS_ATOM_NULL)
return JS_ATOM_NULL;
if (next_token(s)) {
JS_FreeAtom(s->ctx, module_name);
return JS_ATOM_NULL;
}
return module_name;
}
static int add_import(JSParseState *s, JSModuleDef *m,
JSAtom local_name, JSAtom import_name)
{
JSContext *ctx = s->ctx;
int i, var_idx;
JSImportEntry *mi;
BOOL is_local;
if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval)
return js_parse_error(s, "invalid import binding");
if (local_name != JS_ATOM_default) {
for (i = 0; i < s->cur_func->closure_var_count; i++) {
if (s->cur_func->closure_var[i].var_name == local_name)
return js_parse_error(s, "duplicate import binding");
}
}
is_local = (import_name == JS_ATOM__star_);
var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE,
m->import_entries_count,
local_name, TRUE, TRUE, FALSE);
if (var_idx < 0)
return -1;
if (js_resize_array(ctx, (void **)&m->import_entries,
sizeof(JSImportEntry),
&m->import_entries_size,
m->import_entries_count + 1))
return -1;
mi = &m->import_entries[m->import_entries_count++];
mi->import_name = JS_DupAtom(ctx, import_name);
mi->var_idx = var_idx;
return 0;
}
static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
JSAtom module_name)
{
JSReqModuleEntry *rme;
int i;
/* no need to add the module request if it is already present */
for(i = 0; i < m->req_module_entries_count; i++) {
rme = &m->req_module_entries[i];
if (rme->module_name == module_name)
return i;
}
if (js_resize_array(ctx, (void **)&m->req_module_entries,
sizeof(JSReqModuleEntry),
&m->req_module_entries_size,
m->req_module_entries_count + 1))
return -1;
rme = &m->req_module_entries[m->req_module_entries_count++];
rme->module_name = JS_DupAtom(ctx, module_name);
rme->module = NULL;
return i;
}
static __exception int js_parse_import(JSParseState *s)
{
JSContext *ctx = s->ctx;
JSModuleDef *m = s->cur_func->module;
JSAtom local_name, import_name, module_name;
int first_import, i, idx;
if (next_token(s))
return -1;
first_import = m->import_entries_count;
if (s->token.val == TOK_STRING) {
module_name = JS_ValueToAtom(ctx, s->token.u.str.str);
if (module_name == JS_ATOM_NULL)
return -1;
if (next_token(s)) {
JS_FreeAtom(ctx, module_name);
return -1;
}
} else {
if (s->token.val == TOK_IDENT) {
if (s->token.u.ident.is_reserved) {
return js_parse_error_reserved_identifier(s);
}
/* "default" import */
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
import_name = JS_ATOM_default;
if (next_token(s))
goto fail;
if (add_import(s, m, local_name, import_name))
goto fail;
JS_FreeAtom(ctx, local_name);
if (s->token.val != ',')
goto end_import_clause;
if (next_token(s))
return -1;
}
if (s->token.val == '*') {
/* name space import */
if (next_token(s))
return -1;
if (!token_is_pseudo_keyword(s, JS_ATOM_as))
return js_parse_error(s, "expecting 'as'");
if (next_token(s))
return -1;
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
return -1;
}
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
import_name = JS_ATOM__star_;
if (next_token(s))
goto fail;
if (add_import(s, m, local_name, import_name))
goto fail;
JS_FreeAtom(ctx, local_name);
} else if (s->token.val == '{') {
if (next_token(s))
return -1;
while (s->token.val != '}') {
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
return -1;
}
import_name = JS_DupAtom(ctx, s->token.u.ident.atom);
local_name = JS_ATOM_NULL;
if (next_token(s))
goto fail;
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
if (next_token(s))
goto fail;
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
goto fail;
}
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s)) {
fail:
JS_FreeAtom(ctx, local_name);
JS_FreeAtom(ctx, import_name);
return -1;
}
} else {
local_name = JS_DupAtom(ctx, import_name);
}
if (add_import(s, m, local_name, import_name))
goto fail;
JS_FreeAtom(ctx, local_name);
JS_FreeAtom(ctx, import_name);
if (s->token.val != ',')
break;
if (next_token(s))
return -1;
}
if (js_parse_expect(s, '}'))
return -1;
}
end_import_clause:
module_name = js_parse_from_clause(s);
if (module_name == JS_ATOM_NULL)
return -1;
}
idx = add_req_module_entry(ctx, m, module_name);
JS_FreeAtom(ctx, module_name);
if (idx < 0)
return -1;
for(i = first_import; i < m->import_entries_count; i++)
m->import_entries[i].req_module_idx = idx;
return js_parse_expect_semi(s);
}
static int add_star_export_entry(JSContext *ctx, JSModuleDef *m,
int req_module_idx)
{
JSStarExportEntry *se;
if (js_resize_array(ctx, (void **)&m->star_export_entries,
sizeof(JSStarExportEntry),
&m->star_export_entries_size,
m->star_export_entries_count + 1))
return -1;
se = &m->star_export_entries[m->star_export_entries_count++];
se->req_module_idx = req_module_idx;
return 0;
}
int js_parse_export(JSParseState *s)
{
JSContext *ctx = s->ctx;
JSModuleDef *m = s->cur_func->module;
JSAtom local_name, export_name;
int first_export, idx, i, tok;
JSAtom module_name;
JSExportEntry *me;
if (next_token(s))
return -1;
tok = s->token.val;
if (tok == TOK_CLASS) {
return js_parse_class(s, FALSE, JS_PARSE_EXPORT_NAMED);
} else if (tok == TOK_FUNCTION ||
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) == TOK_FUNCTION)) {
return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num,
JS_PARSE_EXPORT_NAMED, NULL);
}
if (next_token(s))
return -1;
switch(tok) {
case '{':
first_export = m->export_entries_count;
while (s->token.val != '}') {
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
return -1;
}
local_name = JS_DupAtom(ctx, s->token.u.ident.atom);
export_name = JS_ATOM_NULL;
if (next_token(s))
goto fail;
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
if (next_token(s))
goto fail;
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
goto fail;
}
export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s)) {
fail:
JS_FreeAtom(ctx, local_name);
fail1:
JS_FreeAtom(ctx, export_name);
return -1;
}
} else {
export_name = JS_DupAtom(ctx, local_name);
}
me = add_export_entry(s, m, local_name, export_name,
JS_EXPORT_TYPE_LOCAL);
JS_FreeAtom(ctx, local_name);
JS_FreeAtom(ctx, export_name);
if (!me)
return -1;
if (s->token.val != ',')
break;
if (next_token(s))
return -1;
}
if (js_parse_expect(s, '}'))
return -1;
if (token_is_pseudo_keyword(s, JS_ATOM_from)) {
module_name = js_parse_from_clause(s);
if (module_name == JS_ATOM_NULL)
return -1;
idx = add_req_module_entry(ctx, m, module_name);
JS_FreeAtom(ctx, module_name);
if (idx < 0)
return -1;
for(i = first_export; i < m->export_entries_count; i++) {
me = &m->export_entries[i];
me->export_type = JS_EXPORT_TYPE_INDIRECT;
me->u.req_module_idx = idx;
}
}
break;
case '*':
if (token_is_pseudo_keyword(s, JS_ATOM_as)) {
/* export ns from */
if (next_token(s))
return -1;
if (!token_is_ident(s->token.val)) {
js_parse_error(s, "identifier expected");
return -1;
}
export_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s))
goto fail1;
module_name = js_parse_from_clause(s);
if (module_name == JS_ATOM_NULL)
goto fail1;
idx = add_req_module_entry(ctx, m, module_name);
JS_FreeAtom(ctx, module_name);
if (idx < 0)
goto fail1;
me = add_export_entry(s, m, JS_ATOM__star_, export_name,
JS_EXPORT_TYPE_INDIRECT);
JS_FreeAtom(ctx, export_name);
if (!me)
return -1;
me->u.req_module_idx = idx;
} else {
module_name = js_parse_from_clause(s);
if (module_name == JS_ATOM_NULL)
return -1;
idx = add_req_module_entry(ctx, m, module_name);
JS_FreeAtom(ctx, module_name);
if (idx < 0)
return -1;
if (add_star_export_entry(ctx, m, idx) < 0)
return -1;
}
break;
case TOK_DEFAULT:
if (s->token.val == TOK_CLASS) {
return js_parse_class(s, FALSE, JS_PARSE_EXPORT_DEFAULT);
} else if (s->token.val == TOK_FUNCTION ||
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) == TOK_FUNCTION)) {
return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num,
JS_PARSE_EXPORT_DEFAULT, NULL);
} else {
if (js_parse_assign_expr(s))
return -1;
}
/* set the name of anonymous functions */
set_object_name(s, JS_ATOM_default);
/* store the value in the _default_ global variable and export
it */
local_name = JS_ATOM__default_;
if (define_var(s, s->cur_func, local_name, JS_VAR_DEF_LET) < 0)
return -1;
emit_op(s, OP_scope_put_var_init);
emit_atom(s, local_name);
emit_u16(s, 0);
if (!add_export_entry(s, m, local_name, JS_ATOM_default,
JS_EXPORT_TYPE_LOCAL))
return -1;
break;
case TOK_VAR:
case TOK_LET:
case TOK_CONST:
return js_parse_var(s, TRUE, tok, TRUE);
default:
return js_parse_error(s, "invalid export syntax");
}
return js_parse_expect_semi(s);
}
static __exception int js_parse_source_element(JSParseState *s)
{
JSFunctionDef *fd = s->cur_func;
int tok;
if (s->token.val == TOK_FUNCTION ||
(token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) == TOK_FUNCTION)) {
if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT,
JS_FUNC_NORMAL, JS_ATOM_NULL,
s->token.ptr, s->token.line_num))
return -1;
} else if (s->token.val == TOK_EXPORT && fd->module) {
if (js_parse_export(s))
return -1;
} else if (s->token.val == TOK_IMPORT && fd->module &&
((tok = peek_token(s, FALSE)) != '(' && tok != '.')) {
/* the peek_token is needed to avoid confusion with ImportCall
(dynamic import) or import.meta */
if (js_parse_import(s))
return -1;
} else {
if (js_parse_statement_or_decl(s, DECL_MASK_ALL))
return -1;
}
return 0;
}
static __exception int js_parse_directives(JSParseState *s)
{
char str[20];
JSParsePos pos;
BOOL has_semi;
if (s->token.val != TOK_STRING)
return 0;
js_parse_get_pos(s, &pos);
while(s->token.val == TOK_STRING) {
/* Copy actual source string representation */
snprintf(str, sizeof str, "%.*s",
(int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1);
if (next_token(s))
return -1;
has_semi = FALSE;
switch (s->token.val) {
case ';':
if (next_token(s))
return -1;
has_semi = TRUE;
break;
case '}':
case TOK_EOF:
has_semi = TRUE;
break;
case TOK_NUMBER:
case TOK_STRING:
case TOK_TEMPLATE:
case TOK_IDENT:
case TOK_REGEXP:
case TOK_DEC:
case TOK_INC:
case TOK_NULL:
case TOK_FALSE:
case TOK_TRUE:
case TOK_IF:
case TOK_RETURN:
case TOK_VAR:
case TOK_THIS:
case TOK_DELETE:
case TOK_TYPEOF:
case TOK_NEW:
case TOK_DO:
case TOK_WHILE:
case TOK_FOR:
case TOK_SWITCH:
case TOK_THROW:
case TOK_TRY:
case TOK_FUNCTION:
case TOK_DEBUGGER:
case TOK_WITH:
case TOK_CLASS:
case TOK_CONST:
case TOK_ENUM:
case TOK_EXPORT:
case TOK_IMPORT:
case TOK_SUPER:
case TOK_INTERFACE:
case TOK_LET:
case TOK_PACKAGE:
case TOK_PRIVATE:
case TOK_PROTECTED:
case TOK_PUBLIC:
case TOK_STATIC:
/* automatic insertion of ';' */
if (s->got_lf)
has_semi = TRUE;
break;
default:
break;
}
if (!has_semi)
break;
if (!strcmp(str, "use strict")) {
s->cur_func->has_use_strict = TRUE;
s->cur_func->js_mode |= JS_MODE_STRICT;
}
#if !defined(DUMP_BYTECODE) || !(DUMP_BYTECODE & 8)
else if (!strcmp(str, "use strip")) {
s->cur_func->js_mode |= JS_MODE_STRIP;
}
#endif
#ifdef CONFIG_BIGNUM
else if (s->ctx->bignum_ext && !strcmp(str, "use math")) {
s->cur_func->js_mode |= JS_MODE_MATH;
}
#endif
}
return js_parse_seek_token(s, &pos);
}
static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd,
JSAtom func_name)
{
JSAtom name;
int i, idx;
if (fd->js_mode & JS_MODE_STRICT) {
if (!fd->has_simple_parameter_list && fd->has_use_strict) {
return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter");
}
if (func_name == JS_ATOM_eval || func_name == JS_ATOM_arguments) {
return js_parse_error(s, "invalid function name in strict code");
}
for (idx = 0; idx < fd->arg_count; idx++) {
name = fd->args[idx].var_name;
if (name == JS_ATOM_eval || name == JS_ATOM_arguments) {
return js_parse_error(s, "invalid argument name in strict code");
}
}
}
/* check async_generator case */
if ((fd->js_mode & JS_MODE_STRICT)
|| !fd->has_simple_parameter_list
|| (fd->func_type == JS_PARSE_FUNC_METHOD && fd->func_kind == JS_FUNC_ASYNC)
|| fd->func_type == JS_PARSE_FUNC_ARROW
|| fd->func_type == JS_PARSE_FUNC_METHOD) {
for (idx = 0; idx < fd->arg_count; idx++) {
name = fd->args[idx].var_name;
if (name != JS_ATOM_NULL) {
for (i = 0; i < idx; i++) {
if (fd->args[i].var_name == name)
goto duplicate;
}
/* Check if argument name duplicates a destructuring parameter */
/* XXX: should have a flag for such variables */
for (i = 0; i < fd->var_count; i++) {
if (fd->vars[i].var_name == name &&
fd->vars[i].scope_level == 0)
goto duplicate;
}
}
}
}
return 0;
duplicate:
return js_parse_error(s, "duplicate argument names not allowed in this context");
}
static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
{
JSVarDef *vd;
/* the local variable indexes are currently stored on 16 bits */
if (fd->arg_count >= JS_MAX_LOCAL_VARS) {
JS_ThrowInternalError(ctx, "too many arguments");
return -1;
}
if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]),
&fd->arg_size, fd->arg_count + 1))
return -1;
vd = &fd->args[fd->arg_count++];
bzero(vd, sizeof(*vd));
vd->var_name = JS_DupAtom(ctx, name);
vd->func_pool_idx = -1;
return fd->arg_count - 1;
}
/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and
JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */
static __exception int js_parse_function_decl2(JSParseState *s,
JSParseFunctionEnum func_type,
JSFunctionKindEnum func_kind,
JSAtom func_name,
const uint8_t *ptr,
int function_line_num,
JSParseExportEnum export_flag,
JSFunctionDef **pfd)
{
JSContext *ctx = s->ctx;
JSFunctionDef *fd = s->cur_func;
BOOL is_expr;
int func_idx, lexical_func_idx = -1;
BOOL has_opt_arg;
BOOL create_func_var = FALSE;
is_expr = (func_type != JS_PARSE_FUNC_STATEMENT &&
func_type != JS_PARSE_FUNC_VAR);
if (func_type == JS_PARSE_FUNC_STATEMENT ||
func_type == JS_PARSE_FUNC_VAR ||
func_type == JS_PARSE_FUNC_EXPR) {
if (func_kind == JS_FUNC_NORMAL &&
token_is_pseudo_keyword(s, JS_ATOM_async) &&
peek_token(s, TRUE) != '\n') {
if (next_token(s))
return -1;
func_kind = JS_FUNC_ASYNC;
}
if (next_token(s))
return -1;
if (s->token.val == '*') {
if (next_token(s))
return -1;
func_kind |= JS_FUNC_GENERATOR;
}
if (s->token.val == TOK_IDENT) {
if (s->token.u.ident.is_reserved ||
(s->token.u.ident.atom == JS_ATOM_yield &&
func_type == JS_PARSE_FUNC_EXPR &&
(func_kind & JS_FUNC_GENERATOR)) ||
(s->token.u.ident.atom == JS_ATOM_await &&
func_type == JS_PARSE_FUNC_EXPR &&
(func_kind & JS_FUNC_ASYNC))) {
return js_parse_error_reserved_identifier(s);
}
}
if (s->token.val == TOK_IDENT ||
(((s->token.val == TOK_YIELD && !(fd->js_mode & JS_MODE_STRICT)) ||
(s->token.val == TOK_AWAIT && !s->is_module)) &&
func_type == JS_PARSE_FUNC_EXPR)) {
func_name = JS_DupAtom(ctx, s->token.u.ident.atom);
if (next_token(s)) {
JS_FreeAtom(ctx, func_name);
return -1;
}
} else {
if (func_type != JS_PARSE_FUNC_EXPR &&
export_flag != JS_PARSE_EXPORT_DEFAULT) {
return js_parse_error(s, "function name expected");
}
}
} else if (func_type != JS_PARSE_FUNC_ARROW) {
func_name = JS_DupAtom(ctx, func_name);
}
if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_MODULE &&
(func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR)) {
JSGlobalVar *hf;
hf = find_global_var(fd, func_name);
/* XXX: should check scope chain */
if (hf && hf->scope_level == fd->scope_level) {
js_parse_error(s, "invalid redefinition of global identifier in module code");
JS_FreeAtom(ctx, func_name);
return -1;
}
}
if (func_type == JS_PARSE_FUNC_VAR) {
if (!(fd->js_mode & JS_MODE_STRICT)
&& func_kind == JS_FUNC_NORMAL
&& find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0
&& !((func_idx = find_var(ctx, fd, func_name)) >= 0 && (func_idx & ARGUMENT_VAR_OFFSET))
&& !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) {
create_func_var = TRUE;
}
/* Create the lexical name here so that the function closure
contains it */
if (fd->is_eval &&
(fd->eval_type == JS_EVAL_TYPE_GLOBAL ||
fd->eval_type == JS_EVAL_TYPE_MODULE) &&
fd->scope_level == fd->body_scope) {
/* avoid creating a lexical variable in the global
scope. XXX: check annex B */
JSGlobalVar *hf;
hf = find_global_var(fd, func_name);
/* XXX: should check scope chain */
if (hf && hf->scope_level == fd->scope_level) {
js_parse_error(s, "invalid redefinition of global identifier");
JS_FreeAtom(ctx, func_name);
return -1;
}
} else {
/* Always create a lexical name, fail if at the same scope as
existing name */
/* Lexical variable will be initialized upon entering scope */
lexical_func_idx = define_var(s, fd, func_name,
func_kind != JS_FUNC_NORMAL ?
JS_VAR_DEF_NEW_FUNCTION_DECL :
JS_VAR_DEF_FUNCTION_DECL);
if (lexical_func_idx < 0) {
JS_FreeAtom(ctx, func_name);
return -1;
}
}
}
fd = js_new_function_def(ctx, fd, FALSE, is_expr,
s->filename, function_line_num);
if (!fd) {
JS_FreeAtom(ctx, func_name);
return -1;
}
if (pfd)
*pfd = fd;
s->cur_func = fd;
fd->func_name = func_name;
/* XXX: test !fd->is_generator is always false */
fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT ||
func_type == JS_PARSE_FUNC_VAR ||
func_type == JS_PARSE_FUNC_EXPR) &&
func_kind == JS_FUNC_NORMAL;
fd->has_home_object = (func_type == JS_PARSE_FUNC_METHOD ||
func_type == JS_PARSE_FUNC_GETTER ||
func_type == JS_PARSE_FUNC_SETTER ||
func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW);
fd->has_this_binding = fd->has_arguments_binding;
fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR);
if (func_type == JS_PARSE_FUNC_ARROW) {
fd->new_target_allowed = fd->parent->new_target_allowed;
fd->super_call_allowed = fd->parent->super_call_allowed;
fd->super_allowed = fd->parent->super_allowed;
fd->arguments_allowed = fd->parent->arguments_allowed;
} else {
fd->new_target_allowed = TRUE;
fd->super_call_allowed = fd->is_derived_class_constructor;
fd->super_allowed = fd->has_home_object;
fd->arguments_allowed = TRUE;
}
/* fd->in_function_body == FALSE prevents yield/await during the parsing
of the arguments in generator/async functions. They are parsed as
regular identifiers for other function kinds. */
fd->func_kind = func_kind;
fd->func_type = func_type;
if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR ||
func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
/* error if not invoked as a constructor */
emit_op(s, OP_check_ctor);
}
if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) {
emit_class_field_init(s);
}
/* parse arguments */
fd->has_simple_parameter_list = TRUE;
fd->has_parameter_expressions = FALSE;
has_opt_arg = FALSE;
if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) {
JSAtom name;
if (s->token.u.ident.is_reserved) {
js_parse_error_reserved_identifier(s);
goto fail;
}
name = s->token.u.ident.atom;
if (add_arg(ctx, fd, name) < 0)
goto fail;
fd->defined_arg_count = 1;
} else {
if (s->token.val == '(') {
int skip_bits;
/* if there is an '=' inside the parameter list, we
consider there is a parameter expression inside */
js_parse_skip_parens_token(s, &skip_bits, FALSE);
if (skip_bits & SKIP_HAS_ASSIGNMENT)
fd->has_parameter_expressions = TRUE;
if (next_token(s))
goto fail;
} else {
if (js_parse_expect(s, '('))
goto fail;
}
if (fd->has_parameter_expressions) {
fd->scope_level = -1; /* force no parent scope */
if (push_scope(s) < 0)
return -1;
}
while (s->token.val != ')') {
JSAtom name;
BOOL rest = FALSE;
int idx, has_initializer;
if (s->token.val == TOK_ELLIPSIS) {
fd->has_simple_parameter_list = FALSE;
rest = TRUE;
if (next_token(s))
goto fail;
}
if (s->token.val == '[' || s->token.val == '{') {
fd->has_simple_parameter_list = FALSE;
if (rest) {
emit_op(s, OP_rest);
emit_u16(s, fd->arg_count);
} else {
/* unnamed arg for destructuring */
idx = add_arg(ctx, fd, JS_ATOM_NULL);
emit_op(s, OP_get_arg);
emit_u16(s, idx);
}
has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE);
if (has_initializer < 0)
goto fail;
if (has_initializer)
has_opt_arg = TRUE;
if (!has_opt_arg)
fd->defined_arg_count++;
} else if (s->token.val == TOK_IDENT) {
if (s->token.u.ident.is_reserved) {
js_parse_error_reserved_identifier(s);
goto fail;
}
name = s->token.u.ident.atom;
if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) {
js_parse_error_reserved_identifier(s);
goto fail;
}
if (fd->has_parameter_expressions) {
if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0)
goto fail;
}
/* XXX: could avoid allocating an argument if rest is true */
idx = add_arg(ctx, fd, name);
if (idx < 0)
goto fail;
if (next_token(s))
goto fail;
if (rest) {
emit_op(s, OP_rest);
emit_u16(s, idx);
if (fd->has_parameter_expressions) {
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
}
emit_op(s, OP_put_arg);
emit_u16(s, idx);
fd->has_simple_parameter_list = FALSE;
has_opt_arg = TRUE;
} else if (s->token.val == '=') {
int label;
fd->has_simple_parameter_list = FALSE;
has_opt_arg = TRUE;
if (next_token(s))
goto fail;
label = new_label(s);
emit_op(s, OP_get_arg);
emit_u16(s, idx);
emit_op(s, OP_dup);
emit_op(s, OP_undefined);
emit_op(s, OP_strict_eq);
emit_goto(s, OP_if_false, label);
emit_op(s, OP_drop);
if (js_parse_assign_expr(s))
goto fail;
set_object_name(s, name);
emit_op(s, OP_dup);
emit_op(s, OP_put_arg);
emit_u16(s, idx);
emit_label(s, label);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
} else {
if (!has_opt_arg) {
fd->defined_arg_count++;
}
if (fd->has_parameter_expressions) {
/* copy the argument to the argument scope */
emit_op(s, OP_get_arg);
emit_u16(s, idx);
emit_op(s, OP_scope_put_var_init);
emit_atom(s, name);
emit_u16(s, fd->scope_level);
}
}
} else {
js_parse_error(s, "missing formal parameter");
goto fail;
}
if (rest && s->token.val != ')') {
js_parse_expect(s, ')');
goto fail;
}
if (s->token.val == ')')
break;
if (js_parse_expect(s, ','))
goto fail;
}
if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) ||
(func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) {
js_parse_error(s, "invalid number of arguments for getter or setter");
goto fail;
}
}
if (fd->has_parameter_expressions) {
int idx;
/* Copy the variables in the argument scope to the variable
scope (see FunctionDeclarationInstantiation() in spec). The
normal arguments are already present, so no need to copy
them. */
idx = fd->scopes[fd->scope_level].first;
while (idx >= 0) {
JSVarDef *vd = &fd->vars[idx];
if (vd->scope_level != fd->scope_level)
break;
if (find_var(ctx, fd, vd->var_name) < 0) {
if (add_var(ctx, fd, vd->var_name) < 0)
goto fail;
vd = &fd->vars[idx]; /* fd->vars may have been reallocated */
emit_op(s, OP_scope_get_var);
emit_atom(s, vd->var_name);
emit_u16(s, fd->scope_level);
emit_op(s, OP_scope_put_var);
emit_atom(s, vd->var_name);
emit_u16(s, 0);
}
idx = vd->scope_next;
}
/* the argument scope has no parent, hence we don't use pop_scope(s) */
emit_op(s, OP_leave_scope);
emit_u16(s, fd->scope_level);
/* set the variable scope as the current scope */
fd->scope_level = 0;
fd->scope_first = fd->scopes[fd->scope_level].first;
}
if (next_token(s))
goto fail;
/* generator function: yield after the parameters are evaluated */
if (func_kind == JS_FUNC_GENERATOR ||
func_kind == JS_FUNC_ASYNC_GENERATOR)
emit_op(s, OP_initial_yield);
/* in generators, yield expression is forbidden during the parsing
of the arguments */
fd->in_function_body = TRUE;
push_scope(s); /* enter body scope */
fd->body_scope = fd->scope_level;
if (s->token.val == TOK_ARROW) {
if (next_token(s))
goto fail;
if (s->token.val != '{') {
if (js_parse_function_check_names(s, fd, func_name))
goto fail;
if (js_parse_assign_expr(s))
goto fail;
if (func_kind != JS_FUNC_NORMAL)
emit_op(s, OP_return_async);
else
emit_op(s, OP_return);
if (!(fd->js_mode & JS_MODE_STRIP)) {
/* save the function source code */
/* the end of the function source code is after the last
token of the function source stored into s->last_ptr */
fd->source_len = s->last_ptr - ptr;
fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
if (!fd->source)
goto fail;
}
goto done;
}
}
if (js_parse_expect(s, '{'))
goto fail;
if (js_parse_directives(s))
goto fail;
/* in strict_mode, check function and argument names */
if (js_parse_function_check_names(s, fd, func_name))
goto fail;
while (s->token.val != '}') {
if (js_parse_source_element(s))
goto fail;
}
if (!(fd->js_mode & JS_MODE_STRIP)) {
/* save the function source code */
fd->source_len = s->buf_ptr - ptr;
fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len);
if (!fd->source)
goto fail;
}
if (next_token(s)) {
/* consume the '}' */
goto fail;
}
/* in case there is no return, add one */
if (js_is_live_code(s)) {
emit_return(s, FALSE);
}
done:
s->cur_func = fd->parent;
/* create the function object */
{
int idx;
JSAtom func_name = fd->func_name;
/* the real object will be set at the end of the compilation */
idx = cpool_add(s, JS_NULL);
fd->parent_cpool_idx = idx;
if (is_expr) {
/* for constructors, no code needs to be generated here */
if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR &&
func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) {
/* OP_fclosure creates the function object from the bytecode
and adds the scope information */
emit_op(s, OP_fclosure);
emit_u32(s, idx);
if (func_name == JS_ATOM_NULL) {
emit_op(s, OP_set_name);
emit_u32(s, JS_ATOM_NULL);
}
}
} else if (func_type == JS_PARSE_FUNC_VAR) {
emit_op(s, OP_fclosure);
emit_u32(s, idx);
if (create_func_var) {
if (s->cur_func->is_global_var) {
JSGlobalVar *hf;
/* the global variable must be defined at the start of the
function */
hf = add_global_var(ctx, s->cur_func, func_name);
if (!hf)
goto fail;
/* it is considered as defined at the top level
(needed for annex B.3.3.4 and B.3.3.5
checks) */
hf->scope_level = 0;
hf->force_init = ((s->cur_func->js_mode & JS_MODE_STRICT) != 0);
/* store directly into global var, bypass lexical scope */
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var);
emit_atom(s, func_name);
emit_u16(s, 0);
} else {
/* do not call define_var to bypass lexical scope check */
func_idx = find_var(ctx, s->cur_func, func_name);
if (func_idx < 0) {
func_idx = add_var(ctx, s->cur_func, func_name);
if (func_idx < 0)
goto fail;
}
/* store directly into local var, bypass lexical catch scope */
emit_op(s, OP_dup);
emit_op(s, OP_scope_put_var);
emit_atom(s, func_name);
emit_u16(s, 0);
}
}
if (lexical_func_idx >= 0) {
/* lexical variable will be initialized upon entering scope */
s->cur_func->vars[lexical_func_idx].func_pool_idx = idx;
emit_op(s, OP_drop);
} else {
/* store function object into its lexical name */
/* XXX: could use OP_put_loc directly */
emit_op(s, OP_scope_put_var_init);
emit_atom(s, func_name);
emit_u16(s, s->cur_func->scope_level);
}
} else {
if (!s->cur_func->is_global_var) {
int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR);
if (var_idx < 0)
goto fail;
/* the variable will be assigned at the top of the function */
if (var_idx & ARGUMENT_VAR_OFFSET) {
s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx;
} else {
s->cur_func->vars[var_idx].func_pool_idx = idx;
}
} else {
JSAtom func_var_name;
JSGlobalVar *hf;
if (func_name == JS_ATOM_NULL)
func_var_name = JS_ATOM__default_; /* export default */
else
func_var_name = func_name;
/* the variable will be assigned at the top of the function */
hf = add_global_var(ctx, s->cur_func, func_var_name);
if (!hf)
goto fail;
hf->cpool_idx = idx;
if (export_flag != JS_PARSE_EXPORT_NONE) {
if (!add_export_entry(s, s->cur_func->module, func_var_name,
export_flag == JS_PARSE_EXPORT_NAMED ? func_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL))
goto fail;
}
}
}
}
return 0;
fail:
s->cur_func = fd->parent;
js_free_function_def(ctx, fd);
if (pfd)
*pfd = NULL;
return -1;
}
static __exception int js_parse_function_decl(JSParseState *s,
JSParseFunctionEnum func_type,
JSFunctionKindEnum func_kind,
JSAtom func_name,
const uint8_t *ptr,
int function_line_num)
{
return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr,
function_line_num, JS_PARSE_EXPORT_NONE,
NULL);
}
int js_parse_program(JSParseState *s)
{
JSFunctionDef *fd = s->cur_func;
int idx;
if (next_token(s))
return -1;
if (js_parse_directives(s))
return -1;
fd->is_global_var = (fd->eval_type == JS_EVAL_TYPE_GLOBAL) ||
(fd->eval_type == JS_EVAL_TYPE_MODULE) ||
!(fd->js_mode & JS_MODE_STRICT);
if (!s->is_module) {
/* hidden variable for the return value */
fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_);
if (idx < 0)
return -1;
}
while (s->token.val != TOK_EOF) {
if (js_parse_source_element(s))
return -1;
}
if (!s->is_module) {
/* return the value of the hidden variable eval_ret_idx */
emit_op(s, OP_get_loc);
emit_u16(s, fd->eval_ret_idx);
emit_op(s, OP_return);
} else {
emit_op(s, OP_return_undef);
}
return 0;
}