mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
10fd8bdb70
This change resurrects ae5d06dc53
6150 lines
219 KiB
C
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;
|
|
}
|