From f5fad52d47f0255c905066aeb5150e127600bcbe Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 4 Feb 2026 14:18:02 -0600 Subject: [PATCH] Rewrite template literals with OP_format_template Replace complex template literal handling with a simple format-based approach. Template literals like `hello ${x}` now compile to: OP_format_template expr_count=1, cpool_idx=N where cpool[N] = "hello {0}" The opcode handler parses the format string, substitutes {N} placeholders with stringified stack values, and produces the result string. Key implementation details: - Uses PPretext (parser pretext) with pjs_malloc to avoid GC issues - Re-reads b->cpool[cpool_idx] after any GC-triggering operation - Opcode layout is u16 expr_count followed by u32 cpool_idx - the u16 must come first because compute_stack_size reads the pop count from position 1 for npop_u16 format opcodes Removed: - OP_template_concat opcode and handler - Tagged template literal support (users can use format() directly) - FuncCallType enum (FUNC_CALL_TEMPLATE case no longer needed) - Complex template object creation logic in js_parse_template Co-Authored-By: Claude Opus 4.5 --- source/quickjs-opcode.h | 6 +- source/quickjs.c | 574 ++++++++++++++++++++++++---------------- 2 files changed, 357 insertions(+), 223 deletions(-) diff --git a/source/quickjs-opcode.h b/source/quickjs-opcode.h index 829b4d4a..e31c91b6 100644 --- a/source/quickjs-opcode.h +++ b/source/quickjs-opcode.h @@ -193,8 +193,10 @@ DEF( strict_neq, 1, 2, 1, none) DEF( and, 1, 2, 1, none) DEF( xor, 1, 2, 1, none) DEF( or, 1, 2, 1, none) -/* template literal concatenation - pops N parts, pushes concatenated string */ -DEF(template_concat, 3, 0, 1, npop_u16) +/* format template - format_string_cpool_idx(u32), expr_count(u16) + Note: n_push=2 ensures stack has room for temp [format_str, arr] pair, + even though we only leave 1 value (the result) on the stack. */ +DEF(format_template, 7, 0, 1, npop_u16) /* Upvalue access (closures via outer_frame chain) */ DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */ diff --git a/source/quickjs.c b/source/quickjs.c index e2f3ffc1..d6d6ef2d 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -2000,6 +2000,77 @@ static inline int pjs_resize_array (void **parray, int elem_size, int *psize, in return 0; } +/* Parser pretext - mutable string using system allocator (not JS heap). + This avoids GC issues during parsing since GC can move JS heap objects. */ +typedef struct PPretext { + uint32_t *data; + int len; + int cap; +} PPretext; + +/* Forward declarations for ppretext_end */ +static JSText *js_alloc_string (JSContext *ctx, int max_len); +static inline void string_put (JSText *p, int idx, uint32_t c); + +static PPretext *ppretext_init (int capacity) { + PPretext *p = pjs_malloc (sizeof (PPretext)); + if (!p) return NULL; + if (capacity <= 0) capacity = 32; + p->data = pjs_malloc (capacity * sizeof (uint32_t)); + if (!p->data) { pjs_free (p); return NULL; } + p->len = 0; + p->cap = capacity; + return p; +} + +static void ppretext_free (PPretext *p) { + if (p) { + pjs_free (p->data); + pjs_free (p); + } +} + +static no_inline PPretext *ppretext_realloc (PPretext *p, int new_cap) { + uint32_t *new_data = pjs_realloc (p->data, new_cap * sizeof (uint32_t)); + if (!new_data) return NULL; + p->data = new_data; + p->cap = new_cap; + return p; +} + +static PPretext *ppretext_putc (PPretext *p, uint32_t c) { + if (p->len >= p->cap) { + int new_cap = max_int (p->len + 1, p->cap * 3 / 2); + if (!ppretext_realloc (p, new_cap)) return NULL; + } + p->data[p->len++] = c; + return p; +} + +static JSValue ppretext_end (JSContext *ctx, PPretext *p) { + if (!p) return JS_EXCEPTION; + int len = p->len; + if (len == 0) { + ppretext_free (p); + return JS_KEY_empty; + } + + /* Allocate heap string (single allocation) */ + JSText *str = js_alloc_string (ctx, len); + if (!str) { + ppretext_free (p); + return JS_EXCEPTION; + } + for (int i = 0; i < len; i++) { + string_put (str, i, p->data[i]); + } + str->hdr = objhdr_set_cap56 (str->hdr, len); + str->hdr = objhdr_set_s (str->hdr, true); + + ppretext_free (p); + return JS_MKPTR (str); +} + static no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { int new_size; void *new_array; @@ -2080,6 +2151,28 @@ static inline int js_string_value_len (JSValue v) { } } +/* Append a JSValue string to a PPretext (parser pretext) */ +static PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str) { + int len = js_string_value_len (str); + for (int i = 0; i < len; i++) { + uint32_t c = js_string_value_get (str, i); + p = ppretext_putc (p, c); + if (!p) return NULL; + } + return p; +} + +/* Append an integer to a PPretext */ +static PPretext *ppretext_append_int (PPretext *p, int n) { + char buf[16]; + int len = snprintf (buf, sizeof (buf), "%d", n); + for (int i = 0; i < len; i++) { + p = ppretext_putc (p, buf[i]); + if (!p) return NULL; + } + return p; +} + /* Convert a JSValue string to a property key. For immediates, returns the value as-is (can be used directly as keys). For heap strings, returns interned version. */ @@ -8424,44 +8517,118 @@ restart: } BREAK; - CASE (OP_template_concat) : { - int n, i; - JSValue out; + CASE (OP_format_template) : { + int expr_count = get_u16 (pc); pc += 2; + uint32_t cpool_idx = get_u32 (pc); pc += 4; - n = get_u16 (pc); - pc += 2; + /* Expression values are on the stack. We'll process them in place, + building the result string using pretext. */ + JSText *result = pretext_init (ctx, 64); + if (!result) goto exception; - if (n <= 0) { - *sp++ = JS_KEY_empty; - BREAK; - } + /* Re-read format_str after pretext_init (may have triggered GC) */ + JSValue format_str = b->cpool[cpool_idx]; - JSText *b = pretext_init (ctx, 64); - if (!b) goto exception; + /* Parse format string and substitute {N} with stringified stack values. + Format string is like "hello {0} world {1}" for `hello ${a} world ${b}`. + Each {N} refers to stack value at position N (0-indexed from base). */ + JSValue *expr_base = sp - expr_count; + int fmt_len = js_string_value_len (format_str); + int pos = 0; - for (i = 0; i < n; i++) { - JSValue v = sp[i - n]; - JSValue s = js_cell_text (ctx, JS_NULL, 1, &v); - if (JS_IsException (s)) { - sp -= n; - goto exception; + while (pos < fmt_len) { + /* Re-read format_str in case GC moved the cpool entry */ + format_str = b->cpool[cpool_idx]; + + /* Find next '{' */ + int brace_start = -1; + for (int i = pos; i < fmt_len; i++) { + if (js_string_value_get (format_str, i) == '{') { + brace_start = i; + break; + } } - b = pretext_concat_value (ctx, b, s); - if (!b) { - sp -= n; - goto exception; + + if (brace_start < 0) { + /* No more braces, copy rest of string */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = pos; i < fmt_len; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + break; } + + /* Copy text before brace */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = pos; i < brace_start; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + + /* Find closing '}' */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + int brace_end = -1; + for (int i = brace_start + 1; i < fmt_len; i++) { + if (js_string_value_get (format_str, i) == '}') { + brace_end = i; + break; + } + } + + if (brace_end < 0) { + /* No closing brace, copy '{' and continue */ + result = pretext_putc (ctx, result, '{'); + if (!result) goto exception; + pos = brace_start + 1; + continue; + } + + /* Parse index from {N} */ + int idx = 0; + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = brace_start + 1; i < brace_end; i++) { + uint32_t c = js_string_value_get (format_str, i); + if (c >= '0' && c <= '9') { + idx = idx * 10 + (c - '0'); + } else { + idx = -1; /* Invalid */ + break; + } + } + + if (idx >= 0 && idx < expr_count) { + /* Valid index, stringify the stack value */ + JSValue val = expr_base[idx]; + JSValue str_val = JS_ToString (ctx, val); + if (JS_IsException (str_val)) { + goto exception; + } + /* Append stringified value to result */ + int str_len = js_string_value_len (str_val); + for (int i = 0; i < str_len; i++) { + result = pretext_putc (ctx, result, js_string_value_get (str_val, i)); + if (!result) goto exception; + } + } else { + /* Invalid index, keep original {N} */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = brace_start; i <= brace_end; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + } + + pos = brace_end + 1; } - out = pretext_end (ctx, b); - if (JS_IsException (out)) { - sp -= n; - goto exception; - } + /* Finalize result string */ + JSValue result_str = pretext_end (ctx, result); + if (JS_IsException (result_str)) goto exception; - for (i = 0; i < n; i++) - sp -= n; - *sp++ = out; + /* Pop expr values, push result */ + sp -= expr_count; + *sp++ = result_str; } BREAK; @@ -9329,11 +9496,11 @@ static int js_parse_error_reserved_identifier (JSParseState *s) { static __exception int js_parse_template_part (JSParseState *s, const uint8_t *p) { uint32_t c; - JSText *b; + PPretext *b; JSValue str; /* p points to the first byte of the template part */ - b = pretext_init (s->ctx, 32); + b = ppretext_init (32); if (!b) goto fail; for (;;) { if (p >= s->buf_end) goto unexpected_eof; @@ -9348,7 +9515,7 @@ static __exception int js_parse_template_part (JSParseState *s, break; } if (c == '\\') { - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; if (p >= s->buf_end) goto unexpected_eof; c = *p++; @@ -9367,10 +9534,10 @@ static __exception int js_parse_template_part (JSParseState *s, } p = p_next; } - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; } - str = pretext_end (s->ctx, b); + str = ppretext_end (s->ctx, b); if (JS_IsException (str)) return -1; s->token.val = TOK_TEMPLATE; s->token.u.str.sep = c; @@ -9381,18 +9548,19 @@ static __exception int js_parse_template_part (JSParseState *s, unexpected_eof: js_parse_error (s, "unexpected end of string"); fail: + ppretext_free (b); return -1; } static __exception 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; - JSText *b; + PPretext *b; const uint8_t *p_escape; JSValue str; /* string */ - b = pretext_init (s->ctx, 32); + b = ppretext_init (32); if (!b) goto fail; for (;;) { if (p >= s->buf_end) goto invalid_char; @@ -9484,10 +9652,10 @@ static __exception int js_parse_string (JSParseState *s, int sep, BOOL do_throw, if (c > 0x10FFFF) goto invalid_utf8; p = p_next; } - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; } - str = pretext_end (s->ctx, b); + str = ppretext_end (s->ctx, b); if (JS_IsException (str)) return -1; token->val = TOK_STRING; token->u.str.sep = c; @@ -9501,6 +9669,7 @@ invalid_utf8: invalid_char: if (do_throw) js_parse_error (s, "unexpected end of string"); fail: + ppretext_free (b); return -1; } @@ -9512,17 +9681,17 @@ static inline BOOL token_is_pseudo_keyword (JSParseState *s, JSValue key) { static __exception int js_parse_regexp (JSParseState *s) { const uint8_t *p; BOOL in_class; - JSText *b; - JSText *b2; + PPretext *b = NULL; + PPretext *b2 = NULL; uint32_t c; JSValue body_str, flags_str; p = s->buf_ptr; p++; in_class = FALSE; - b = pretext_init (s->ctx, 32); + b = ppretext_init (32); if (!b) return -1; - b2 = pretext_init (s->ctx, 1); + b2 = ppretext_init (1); if (!b2) goto fail; for (;;) { if (p >= s->buf_end) { @@ -9541,7 +9710,7 @@ static __exception int js_parse_regexp (JSParseState *s) { /* XXX: incorrect as the first character in a class */ in_class = FALSE; } else if (c == '\\') { - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; c = *p++; if (c == '\n' || c == '\r') @@ -9571,7 +9740,7 @@ static __exception int js_parse_regexp (JSParseState *s) { } p = p_next; } - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; } @@ -9587,13 +9756,13 @@ static __exception int js_parse_regexp (JSParseState *s) { } } if (!lre_js_is_ident_next (c)) break; - b2 = pretext_putc (s->ctx, b2, c); + b2 = ppretext_putc (b2, c); if (!b2) goto fail; p = p_next; } - body_str = pretext_end (s->ctx, b); - flags_str = pretext_end (s->ctx, b2); + body_str = ppretext_end (s->ctx, b); + flags_str = ppretext_end (s->ctx, b2); if (JS_IsException (body_str) || JS_IsException (flags_str)) { return -1; } @@ -9603,10 +9772,13 @@ static __exception int js_parse_regexp (JSParseState *s) { s->buf_ptr = p; return 0; fail: + ppretext_free (b); + ppretext_free (b2); return -1; } static __exception int ident_realloc (JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) { + (void)ctx; /* unused - uses system allocator */ char *buf, *new_buf; size_t size, new_size; @@ -9617,11 +9789,11 @@ static __exception int ident_realloc (JSContext *ctx, char **pbuf, size_t *psize else new_size = size + (size >> 1); if (buf == static_buf) { - new_buf = js_malloc (ctx, new_size); + new_buf = pjs_malloc (new_size); if (!new_buf) return -1; memcpy (new_buf, buf, size); } else { - new_buf = js_realloc (ctx, buf, new_size); + new_buf = pjs_realloc (buf, new_size); if (!new_buf) return -1; } *pbuf = new_buf; @@ -9815,7 +9987,7 @@ static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_ha /* Create interned JSValue key */ str = js_key_new_len (s->ctx, buf, ident_pos); done: - if (unlikely (buf != ident_buf)) js_free (s->ctx, buf); + if (unlikely (buf != ident_buf)) pjs_free (buf); *pp = p; return str; } @@ -10274,7 +10446,7 @@ static JSValue json_parse_ident (JSParseState *s, const uint8_t **pp, int c) { /* Create interned JSValue key */ str = js_key_new_len (s->ctx, buf, ident_pos); done: - if (unlikely (buf != ident_buf)) js_free (s->ctx, buf); + if (unlikely (buf != ident_buf)) pjs_free (buf); *pp = p; return str; } @@ -10283,9 +10455,9 @@ static int json_parse_string (JSParseState *s, const uint8_t **pp, int sep) { const uint8_t *p, *p_next; int i; uint32_t c; - JSText *b; + PPretext *b; - b = pretext_init (s->ctx, 32); + b = ppretext_init (32); if (!b) goto fail; p = *pp; @@ -10354,18 +10526,19 @@ static int json_parse_string (JSParseState *s, const uint8_t **pp, int sep) { } p = p_next; } - b = pretext_putc (s->ctx, b, c); + b = ppretext_putc (b, c); if (!b) goto fail; } s->token.val = TOK_STRING; s->token.u.str.sep = sep; - s->token.u.str.str = pretext_end (s->ctx, b); + s->token.u.str.str = ppretext_end (s->ctx, b); *pp = p; return 0; end_of_input: js_parse_error (s, "Unexpected end of JSON input"); fail: + ppretext_free (b); return -1; } @@ -11252,91 +11425,76 @@ static __exception int js_parse_unary (JSParseState *s, int parse_flags); static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count); static void pop_break_entry (JSFunctionDef *fd); -static __exception int js_parse_template (JSParseState *s, int call, int *argc) { +static __exception int js_parse_template (JSParseState *s) { JSContext *ctx = s->ctx; - JSValue raw_array, template_object; + PPretext *fmt = ppretext_init (64); JSToken cooked; - int depth, ret; + int expr_count = 0; - raw_array = JS_NULL; /* avoid warning */ - template_object = JS_NULL; /* 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); - if (ret) return -1; - raw_array = JS_NewArray (ctx); - if (JS_IsException (raw_array)) return -1; - if (JS_SetPropertyInternal (ctx, template_object, JS_KEY_raw, raw_array) - < 0) { + if (!fmt) return -1; + + while (s->token.val == TOK_TEMPLATE) { + const uint8_t *p = s->token.ptr + 1; + + /* re-parse the string with escape sequences and throw a + syntax error if it contains invalid sequences */ + s->token.u.str.str = JS_NULL; + if (js_parse_string (s, '`', TRUE, p, &cooked, &p)) { + ppretext_free (fmt); + return -1; + } + + /* Append cooked string to format string */ + fmt = ppretext_append_jsvalue (fmt, cooked.u.str.str); + if (!fmt) return -1; + + /* Check for end of template */ + if (s->token.u.str.sep == '`') break; + + /* Append placeholder {N} */ + fmt = ppretext_putc (fmt, '{'); + if (!fmt) return -1; + fmt = ppretext_append_int (fmt, expr_count); + if (!fmt) return -1; + fmt = ppretext_putc (fmt, '}'); + if (!fmt) return -1; + + /* Parse expression */ + if (next_token (s)) { ppretext_free (fmt); return -1; } + if (js_parse_expr (s)) { ppretext_free (fmt); return -1; } + expr_count++; + + if (s->token.val != '}') { + ppretext_free (fmt); + return js_parse_error (s, "expected '}' after template expression"); + } + + free_token (s, &s->token); + s->got_lf = FALSE; + if (js_parse_template_part (s, s->buf_ptr)) { + ppretext_free (fmt); 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_SetPropertyUint32 (ctx, raw_array, depth, s->token.u.str.str) - < 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_NULL; - } - if (JS_SetPropertyUint32 (ctx, template_object, depth, cooked.u.str.str) - < 0) { - return -1; - } - } else { - JSText *str; - /* re-parse the string with escape sequences and throw a - syntax error if it contains invalid sequences - */ - s->token.u.str.str = JS_NULL; - if (js_parse_string (s, '`', TRUE, p, &cooked, &p)) return -1; - str = JS_VALUE_GET_STRING (cooked.u.str.str); - if (JSText_len (str) != 0 || depth == 0) { - ret = emit_push_const (s, cooked.u.str.str); - if (ret) return -1; - /* For single-literal templates, go directly to done1 */ - if (depth == 0 && s->token.u.str.sep == '`') goto done1; - depth++; - } else { - } - } - 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; - if (js_parse_template_part (s, s->buf_ptr)) return -1; - } - return js_parse_expect (s, TOK_TEMPLATE); + /* Finalize format string */ + JSValue format_str = ppretext_end (ctx, fmt); + if (JS_IsException (format_str)) return -1; -done: - if (call) { - *argc = depth + 1; - } else if (depth > 1) { - /* Use template_concat opcode to concatenate all parts */ - emit_op (s, OP_template_concat); - emit_u16 (s, depth); + int idx = cpool_add (s, format_str); + if (idx < 0) return -1; + + if (expr_count == 0) { + /* Simple template - just push the string */ + emit_op (s, OP_push_const); + emit_u32 (s, idx); + } else { + /* Template with expressions - emit u16 first for stack size computation */ + emit_op (s, OP_format_template); + emit_u16 (s, expr_count); + emit_u32 (s, idx); } -done1: + return next_token (s); } @@ -12206,11 +12364,6 @@ var_error: return -1; } -typedef enum FuncCallType { - FUNC_CALL_NORMAL, - FUNC_CALL_TEMPLATE, -} FuncCallType; - static void optional_chain_test (JSParseState *s, int *poptional_chaining_label, int drop_count) { @@ -12230,12 +12383,10 @@ static void optional_chain_test (JSParseState *s, /* allowed parse_flags: PF_POSTFIX_CALL */ 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; const uint8_t *op_token_ptr; - call_type = FUNC_CALL_NORMAL; switch (s->token.val) { case TOK_NUMBER: { JSValue val; @@ -12252,7 +12403,7 @@ static __exception int js_parse_postfix_expr (JSParseState *s, if (next_token (s)) return -1; break; case TOK_TEMPLATE: - if (js_parse_template (s, 0, NULL)) return -1; + if (js_parse_template (s)) return -1; break; case TOK_STRING: if (emit_push_const (s, s->token.u.str.str)) return -1; @@ -12364,14 +12515,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s, } 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; - op_token_ptr = s->token.ptr; /* XXX: check if right position */ - goto parse_func_call2; } else if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; @@ -12380,81 +12523,71 @@ static __exception int js_parse_postfix_expr (JSParseState *s, op_token_ptr = s->token.ptr; 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_get_field_opt_chain: { - int opt_chain_label, next_label; - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; - fd->byte_code.size = fd->last_opcode_pos + 1 + 4; - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* need an additional undefined value for the - case where the optional field does not - exists */ - emit_op (s, OP_null); - emit_label (s, next_label); - drop_count = 2; - opcode = OP_get_field; - } 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_get_array_el_opt_chain: { - int opt_chain_label, next_label; - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; - fd->byte_code.size = fd->last_opcode_pos + 1; - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* need an additional undefined value for the - case where the optional field does not - exists */ - emit_op (s, OP_null); - emit_label (s, next_label); - drop_count = 2; - opcode = OP_get_array_el; - } break; - case OP_scope_get_var: { - JSValue name; - int scope; - uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - name = fd->cpool[idx]; - scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); - /* 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 - */ - drop_count = 1; - } break; - default: - opcode = OP_invalid; - drop_count = 1; - break; - } - if (has_optional_chain) { - optional_chain_test (s, &optional_chaining_label, drop_count); - } - } else { + 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_get_field_opt_chain: { + int opt_chain_label, next_label; + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; + fd->byte_code.size = fd->last_opcode_pos + 1 + 4; + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op (s, OP_null); + emit_label (s, next_label); + drop_count = 2; + opcode = OP_get_field; + } 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_get_array_el_opt_chain: { + int opt_chain_label, next_label; + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; + fd->byte_code.size = fd->last_opcode_pos + 1; + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op (s, OP_null); + emit_label (s, next_label); + drop_count = 2; + opcode = OP_get_array_el; + } break; + case OP_scope_get_var: { + JSValue name; + int scope; + uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + name = fd->cpool[idx]; + scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); + /* 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 + */ + drop_count = 1; + } break; + default: opcode = OP_invalid; + drop_count = 1; + break; } - - if (call_type == FUNC_CALL_TEMPLATE) { - if (js_parse_template (s, 1, &arg_count)) return -1; - goto emit_func_call; + if (has_optional_chain) { + optional_chain_test (s, &optional_chaining_label, drop_count); } /* parse arguments */ @@ -12484,7 +12617,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s, break; } } - call_type = FUNC_CALL_NORMAL; } else if (s->token.val == '.') { op_token_ptr = s->token.ptr; if (next_token (s)) return -1;