MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

tco, static member fixes

+587 -116
+587 -116
src/ant.c
··· 291 291 bool is_strict; 292 292 bool is_expr; 293 293 bool uses_arguments; 294 + bool has_tail_calls; 294 295 jsoff_t rest_param_start; 295 296 jsoff_t rest_param_len; 296 297 UT_array *params; ··· 393 394 394 395 static jsval_t get_proto(struct js *js, jsval_t obj); 395 396 static void set_proto(struct js *js, jsval_t obj, jsval_t proto); 397 + 398 + enum tail_scan { TAIL_NONE, TAIL_OK, TAIL_UNSAFE }; 399 + static enum tail_scan scan_tail_span(const char *code, jsoff_t clen, jsoff_t start, jsoff_t end); 396 400 397 401 static void clear_break_label(void) { 398 402 break_target_label = NULL; ··· 6679 6683 } return false; 6680 6684 } 6681 6685 6686 + static inline int find_statement_end(const token_stream_t *restrict ts, int start) { 6687 + const cached_token_t *restrict toks = ts->tokens; 6688 + const int count = ts->count; 6689 + if (start >= count) return count; 6690 + 6691 + static const void *dispatch[] = { 6692 + [TOK_LPAREN] = &&open, 6693 + [TOK_LBRACKET] = &&open, 6694 + [TOK_LBRACE] = &&open, 6695 + [TOK_RPAREN] = &&close, 6696 + [TOK_RBRACKET] = &&close, 6697 + [TOK_RBRACE] = &&close, 6698 + [TOK_SEMICOLON] = &&semi, 6699 + [TOK_EOF] = &&semi, 6700 + }; 6701 + 6702 + int depth = 0; 6703 + int j = start; 6704 + 6705 + for (;;) { 6706 + uint8_t t = toks[j].tok; 6707 + if (t < sizeof(dispatch) / sizeof(*dispatch) && dispatch[t]) goto *dispatch[t]; 6708 + 6709 + next: 6710 + if (++j >= count) return count; 6711 + continue; 6712 + 6713 + open: 6714 + depth++; 6715 + goto next; 6716 + 6717 + close: 6718 + if (depth == 0) return j; 6719 + depth--; 6720 + goto next; 6721 + 6722 + semi: 6723 + if (depth == 0) return j; 6724 + goto next; 6725 + } 6726 + } 6727 + 6728 + 6729 + static inline void find_top_level_tokens( 6730 + const token_stream_t *restrict ts, int start, int end, 6731 + uint8_t tok1, int *restrict first_tok1, 6732 + uint8_t tok2, int *restrict first_tok2_after_tok1 6733 + ) { 6734 + const cached_token_t *restrict toks = ts->tokens; 6735 + int depth = 0; 6736 + int j = start; 6737 + 6738 + for (; j < end; j++) { 6739 + uint8_t t = toks[j].tok; 6740 + if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++; 6741 + else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--; 6742 + else if (depth == 0 && t == tok1) { 6743 + *first_tok1 = j; 6744 + if (!tok2 || !first_tok2_after_tok1) return; 6745 + j++; goto phase2; 6746 + } 6747 + } 6748 + 6749 + *first_tok1 = -1; 6750 + if (first_tok2_after_tok1) *first_tok2_after_tok1 = -1; 6751 + return; 6752 + 6753 + phase2: 6754 + for (; j < end; j++) { 6755 + uint8_t t = toks[j].tok; 6756 + if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++; 6757 + else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--; 6758 + else if (depth == 0 && t == tok2) { *first_tok2_after_tok1 = j; return; } 6759 + } 6760 + *first_tok2_after_tok1 = -1; 6761 + } 6762 + 6763 + static inline int find_last_top_level_token(const token_stream_t *restrict ts, int start, int end, uint8_t tok) { 6764 + const cached_token_t *restrict toks = ts->tokens; 6765 + int depth = 0; 6766 + for (int j = end - 1; j >= start; j--) { 6767 + uint8_t t = toks[j].tok; 6768 + if (t == TOK_RPAREN || t == TOK_RBRACKET) depth++; 6769 + else if (t == TOK_LPAREN || t == TOK_LBRACKET) depth--; 6770 + else if (depth == 0 && t == tok) return j; 6771 + } 6772 + return -1; 6773 + } 6774 + 6775 + static inline int find_matching_open(const token_stream_t *restrict ts, int rparen_idx) { 6776 + const cached_token_t *restrict toks = ts->tokens; 6777 + int depth = 0; 6778 + for (int j = rparen_idx; j >= 0; j--) { 6779 + uint8_t t = toks[j].tok; 6780 + if (t == TOK_RPAREN) depth++; 6781 + else if (t == TOK_LPAREN) if (--depth == 0) return j; 6782 + } 6783 + return -1; 6784 + } 6785 + 6786 + static inline bool expr_ends_with_bare_call(const token_stream_t *restrict ts, int start, int end) { 6787 + if (start >= end) return false; 6788 + 6789 + const cached_token_t *restrict toks = ts->tokens; 6790 + if (toks[end - 1].tok != TOK_RPAREN) return false; 6791 + 6792 + int depth = 0; 6793 + for (int j = start; j < end - 1; j++) { 6794 + uint8_t t = toks[j].tok; 6795 + if (t == TOK_LPAREN || t == TOK_LBRACKET) depth++; 6796 + else if (t == TOK_RPAREN || t == TOK_RBRACKET) depth--; 6797 + } 6798 + 6799 + if (depth != 1) return false; 6800 + int match = find_matching_open(ts, end - 1); 6801 + if (match < 0 || match <= start) return false; 6802 + 6803 + uint8_t before = toks[match - 1].tok; 6804 + return before == TOK_IDENTIFIER || before == TOK_RPAREN || before == TOK_RBRACKET; 6805 + } 6806 + 6807 + static inline bool return_expr_is_tail_call( 6808 + const token_stream_t *restrict ts, int start, 6809 + const char *body, jsoff_t body_len 6810 + ) { 6811 + int end = find_statement_end(ts, start); 6812 + int last = end - 1; 6813 + 6814 + if (last < start) return false; 6815 + const int count = ts->count; 6816 + 6817 + int ternary, colon; 6818 + find_top_level_tokens(ts, start, end, TOK_Q, &ternary, TOK_COLON, &colon); 6819 + 6820 + if (ternary >= 0) { 6821 + if (colon < 0) return false; 6822 + jsoff_t then_start = ts->tokens[ternary + 1].toff; 6823 + jsoff_t then_end = ts->tokens[colon].toff; 6824 + jsoff_t else_start = (colon + 1 < count) ? ts->tokens[colon + 1].toff : body_len; 6825 + jsoff_t else_end = (end < count) ? ts->tokens[end].toff : body_len; 6826 + 6827 + int then_r = scan_tail_span(body, body_len, then_start, then_end); 6828 + if (then_r == TAIL_UNSAFE) return false; 6829 + int else_r = scan_tail_span(body, body_len, else_start, else_end); 6830 + 6831 + return else_r != TAIL_UNSAFE && (then_r == TAIL_OK || else_r == TAIL_OK); 6832 + } 6833 + 6834 + int last_comma = find_last_top_level_token(ts, start, end, TOK_COMMA); 6835 + if (last_comma >= 0) return return_expr_is_tail_call(ts, last_comma + 1, body, body_len); 6836 + 6837 + return expr_ends_with_bare_call(ts, start, end); 6838 + } 6839 + 6840 + static inline bool tokens_have_tail_calls( 6841 + const token_stream_t *restrict ts, 6842 + const char *body, jsoff_t body_len, bool is_expr 6843 + ) { 6844 + if (!ts || ts->count == 0) return false; 6845 + if (is_expr) return scan_tail_span(body, body_len, 0, body_len) == TAIL_OK; 6846 + 6847 + const cached_token_t *restrict toks = ts->tokens; 6848 + const int count = ts->count; 6849 + 6850 + for (int i = 0; i < count; i++) { 6851 + if (toks[i].tok != TOK_RETURN) continue; 6852 + 6853 + int j = i + 1; 6854 + if (j >= count) continue; 6855 + uint8_t first = toks[j].tok; 6856 + 6857 + if (first == TOK_SEMICOLON || first == TOK_RBRACE || first == TOK_EOF) continue; 6858 + if (return_expr_is_tail_call(ts, j, body, body_len)) return true; 6859 + } 6860 + 6861 + return false; 6862 + } 6863 + 6682 6864 static parsed_func_t *get_or_parse_func(struct js *js, const char *fn, jsoff_t fnlen) { 6683 6865 uint64_t h = hash_key(fn, fnlen); 6684 6866 parsed_func_t *cached = NULL; ··· 6775 6957 pf->is_strict = is_strict_function_body(&fn[fnpos], pf->body_len); 6776 6958 pf->uses_arguments = code_uses_arguments(&fn[pf->body_start], pf->body_len); 6777 6959 pf->tokens = (pf->body_len > 0) ? tokenize_body(js, &fn[pf->body_start], pf->body_len) : NULL; 6960 + pf->has_tail_calls = tokens_have_tail_calls(pf->tokens, &fn[pf->body_start], pf->body_len, pf->is_expr); 6778 6961 6779 6962 HASH_ADD(hh, func_parse_cache, code_hash, sizeof(pf->code_hash), pf); 6780 6963 return pf; ··· 6840 7023 if (saved_scope_stack == NULL) utarray_new(saved_scope_stack, &jsval_icd); 6841 7024 utarray_push_back(saved_scope_stack, &js->scope); 6842 7025 utarray_push_back(saved_scope_stack, &js->this_val); 6843 - 7026 + 7027 + jsval_t res; 7028 + jsval_t *tc_args = NULL; 7029 + int tc_argc = 0; 7030 + 7031 + for (;;) { 6844 7032 jsval_t target_this = peek_this(); 6845 7033 jsoff_t parent_scope_offset; 6846 7034 6847 - if (vtype(closure_scope) == T_OBJ) { 6848 - parent_scope_offset = (jsoff_t) vdata(closure_scope); 6849 - } else { 6850 - parent_scope_offset = (jsoff_t) vdata(js->scope); 6851 - } 7035 + if (vtype(closure_scope) == T_OBJ) parent_scope_offset = (jsoff_t) vdata(closure_scope); 7036 + else parent_scope_offset = (jsoff_t) vdata(js->scope); 6852 7037 6853 7038 if (global_scope_stack == NULL) utarray_new(global_scope_stack, &jsoff_icd); 6854 7039 jsval_t function_scope = mkobj(js, parent_scope_offset); 6855 7040 jsoff_t function_scope_offset = (jsoff_t)vdata(function_scope); 6856 7041 utarray_push_back(global_scope_stack, &function_scope_offset); 6857 7042 6858 - const char *caller_code = js->code; 6859 - jsoff_t caller_clen = js->clen; 6860 - jsoff_t caller_pos = js->pos; 6861 - 6862 7043 jsval_t args_buf[8]; 6863 - jsval_t *args = args_buf; 7044 + jsval_t *args; 6864 7045 6865 - int argc = 0; int args_cap = 8; 6866 - bool args_on_heap = false; 7046 + int argc; 7047 + bool args_on_heap; 6867 7048 6868 - #define ARGS_PUSH(val) do { \ 6869 - if (argc >= args_cap) { \ 6870 - int _new_cap = args_cap * 2; \ 6871 - jsval_t *_new = malloc(_new_cap * sizeof(jsval_t)); \ 6872 - memcpy(_new, args, argc * sizeof(jsval_t)); \ 6873 - if (args_on_heap) free(args); \ 6874 - args = _new; \ 6875 - args_cap = _new_cap; \ 6876 - args_on_heap = true; \ 6877 - } \ 6878 - args[argc++] = (val); \ 6879 - } while (0) 7049 + if (tc_args) { 7050 + args = tc_args; 7051 + argc = tc_argc; 7052 + args_on_heap = (tc_argc > 0); 7053 + tc_args = NULL; 7054 + tc_argc = 0; 7055 + } else { 7056 + const char *caller_code = js->code; 7057 + jsoff_t caller_clen = js->clen; 7058 + jsoff_t caller_pos = js->pos; 7059 + 7060 + args = args_buf; 7061 + argc = 0; 7062 + args_on_heap = false; 7063 + int args_cap = 8; 6880 7064 6881 - for (int i = 0; i < bound_argc; i++) ARGS_PUSH(bound_args[i]); 6882 - caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL); 7065 + #define ARGS_PUSH(val) do { \ 7066 + if (argc >= args_cap) { \ 7067 + int _new_cap = args_cap * 2; \ 7068 + jsval_t *_new = malloc(_new_cap * sizeof(jsval_t)); \ 7069 + memcpy(_new, args, argc * sizeof(jsval_t)); \ 7070 + if (args_on_heap) free(args); \ 7071 + args = _new; \ 7072 + args_cap = _new_cap; \ 7073 + args_on_heap = true; \ 7074 + } \ 7075 + args[argc++] = (val); \ 7076 + } while (0) 6883 7077 6884 - while (caller_pos < caller_clen && caller_code[caller_pos] != ')') { 6885 - bool is_spread = ( 6886 - caller_code[caller_pos] == '.' && caller_pos + 2 < caller_clen && 6887 - caller_code[caller_pos + 1] == '.' && caller_code[caller_pos + 2] == '.' 6888 - ); 6889 - if (is_spread) caller_pos += 3; 6890 - js->pos = caller_pos; 6891 - js->consumed = 1; 6892 - jsval_t arg = resolveprop(js, js_expr(js)); 6893 - caller_pos = js->pos; 6894 - if (is_spread && vtype(arg) == T_ARR) { 6895 - jsoff_t len = js_arr_len(js, arg); 6896 - for (jsoff_t i = 0; i < len; i++) ARGS_PUSH(js_arr_get(js, arg, i)); 6897 - } else ARGS_PUSH(arg); 6898 - caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL); 6899 - if (caller_pos < caller_clen && caller_code[caller_pos] == ',') caller_pos++; 7078 + for (int i = 0; i < bound_argc; i++) ARGS_PUSH(bound_args[i]); 6900 7079 caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL); 7080 + 7081 + while (caller_pos < caller_clen && caller_code[caller_pos] != ')') { 7082 + bool is_spread = ( 7083 + caller_code[caller_pos] == '.' && caller_pos + 2 < caller_clen && 7084 + caller_code[caller_pos + 1] == '.' && caller_code[caller_pos + 2] == '.' 7085 + ); 7086 + if (is_spread) caller_pos += 3; 7087 + js->pos = caller_pos; 7088 + js->consumed = 1; 7089 + jsval_t arg = resolveprop(js, js_expr(js)); 7090 + caller_pos = js->pos; 7091 + if (is_spread && vtype(arg) == T_ARR) { 7092 + jsoff_t len = js_arr_len(js, arg); 7093 + for (jsoff_t i = 0; i < len; i++) ARGS_PUSH(js_arr_get(js, arg, i)); 7094 + } else ARGS_PUSH(arg); 7095 + caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL); 7096 + if (caller_pos < caller_clen && caller_code[caller_pos] == ',') caller_pos++; 7097 + caller_pos = skiptonext(caller_code, caller_clen, caller_pos, NULL); 7098 + } 7099 + 7100 + #undef ARGS_PUSH 7101 + js->pos = caller_pos; 6901 7102 } 6902 - 6903 - #undef ARGS_PUSH 6904 - 6905 - js->pos = caller_pos; 7103 + 6906 7104 js->scope = function_scope; 7105 + parsed_func_t *pf = get_or_parse_func(js, fn, fnlen); 6907 7106 6908 - parsed_func_t *pf = get_or_parse_func(js, fn, fnlen); 6909 7107 if (!pf) { 6910 7108 if (args_on_heap) free(args); 6911 7109 restore_saved_scope(js); ··· 6913 7111 return js_mkerr(js, "failed to parse function"); 6914 7112 } 6915 7113 7114 + bool func_strict = pf->is_strict; 7115 + if (!func_strict && vtype(func_val) == T_FUNC) { 7116 + jsval_t strict_slot = get_slot(js, mkval(T_OBJ, vdata(func_val)), SLOT_STRICT); 7117 + func_strict = (vtype(strict_slot) == T_BOOL && vdata(strict_slot) == 1); 7118 + } 7119 + 7120 + if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL || 7121 + (vtype(target_this) == T_OBJ && vdata(target_this) == 0))) { 7122 + js->this_val = js_mkundef(); 7123 + } else js->this_val = target_this; 7124 + 6916 7125 int argi = 0; 6917 7126 for (int i = 0; i < pf->param_count; i++) { 6918 7127 parsed_param_t *pp = (parsed_param_t *)utarray_eltptr(pf->params, (unsigned int)i); ··· 6931 7140 } 6932 7141 } else { 6933 7142 jsval_t v; 6934 - if (argi < argc) { 6935 - v = args[argi++]; 6936 - } else if (pp->default_len > 0) { 6937 - v = js_eval_str(js, &fn[pp->default_start], pp->default_len); 6938 - } else { 6939 - v = js_mkundef(); 6940 - } 7143 + if (argi < argc) v = args[argi++]; 7144 + else if (pp->default_len > 0) v = js_eval_str(js, &fn[pp->default_start], pp->default_len); 7145 + else v = js_mkundef(); 6941 7146 jsval_t k = js_mkstr(js, &fn[pp->name_off], pp->name_len); 6942 7147 if (!is_err(k)) mkprop_fast(js, function_scope, k, v, 0); 6943 7148 } ··· 6954 7159 } 6955 7160 6956 7161 bool needs_arguments = pf->uses_arguments; 6957 - bool func_strict = pf->is_strict; 6958 - 6959 - if (!func_strict && vtype(func_val) == T_FUNC) { 6960 - jsval_t func_obj = mkval(T_OBJ, vdata(func_val)); 6961 - jsval_t strict_slot = get_slot(js, func_obj, SLOT_STRICT); 6962 - func_strict = (vtype(strict_slot) == T_BOOL && vdata(strict_slot) == 1); 6963 - } 6964 - 6965 - if (needs_arguments) { 6966 - setup_arguments(js, function_scope, args, argc, func_strict); 6967 - } 7162 + if (needs_arguments) setup_arguments(js, function_scope, args, argc, func_strict); 6968 7163 6969 7164 jsval_t slot_name = get_slot(js, func_val, SLOT_NAME); 6970 7165 if (vtype(slot_name) == T_STR && vtype(func_val) == T_FUNC) { ··· 6980 7175 js->skip_func_hoist = (vtype(no_func_decls) == T_BOOL && vdata(no_func_decls) == 1); 6981 7176 } else js->skip_func_hoist = false; 6982 7177 6983 - if (func_strict && (vtype(target_this) == T_UNDEF || vtype(target_this) == T_NULL || 6984 - (vtype(target_this) == T_OBJ && vdata(target_this) == 0))) { 6985 - js->this_val = js_mkundef(); 6986 - } else js->this_val = target_this; 6987 - 6988 7178 js->flags = F_CALL | (func_strict ? F_STRICT : 0); 6989 - jsval_t res; 6990 - 6991 7179 void *saved_token_stream = js->token_stream; 7180 + 6992 7181 int saved_token_stream_pos = js->token_stream_pos; 6993 7182 const char *saved_token_stream_code = js->token_stream_code; 6994 7183 ··· 7001 7190 js->token_stream_code = NULL; 7002 7191 } 7003 7192 7193 + bool saved_has_tail = js->has_tail_calls; 7194 + js->has_tail_calls = pf->has_tail_calls; 7195 + 7004 7196 if (pf->is_expr) { 7197 + js->tail_ctx = pf->has_tail_calls; 7005 7198 res = js_eval_str(js, &fn[pf->body_start], pf->body_len); 7006 - res = resolveprop(js, res); 7199 + js->tail_ctx = false; 7200 + if (res != (jsval_t)T_TAILCALL) res = resolveprop(js, res); 7007 7201 } else { 7008 7202 res = js_eval(js, &fn[pf->body_start], pf->body_len); 7009 7203 if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 7010 7204 } 7011 - 7205 + 7206 + js->has_tail_calls = saved_has_tail; 7012 7207 js->token_stream = saved_token_stream; 7013 7208 js->token_stream_pos = saved_token_stream_pos; 7014 7209 js->token_stream_code = saved_token_stream_code; 7015 7210 7016 7211 js->skip_func_hoist = false; 7017 - if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack); 7018 7212 7213 + if (global_scope_stack && utarray_len(global_scope_stack) > 0) utarray_pop_back(global_scope_stack); 7019 7214 if (args_on_heap) free(args); 7215 + 7216 + if (res == (jsval_t)T_TAILCALL && js->tc.pending) { 7217 + js->tc.pending = false; 7218 + fn = js->tc.code_str; 7219 + fnlen = js->tc.fnlen; 7220 + closure_scope = js->tc.closure_scope; 7221 + func_val = js->tc.func; 7222 + bound_args = NULL; 7223 + bound_argc = 0; 7224 + tc_args = js->tc.args; 7225 + tc_argc = js->tc.argc; 7226 + continue; 7227 + } 7228 + 7229 + break; 7230 + } // end trampoline 7231 + 7020 7232 restore_saved_scope(js); 7021 - 7022 7233 return res; 7023 7234 } 7024 7235 ··· 7280 7491 7281 7492 if (vtype(args) != T_CODEREF) return js_mkerr(js, "bad call"); 7282 7493 if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC && vtype(func) != T_FFI) return js_mkerr(js, "calling non-function"); 7494 + 7495 + bool is_tail = js->tail_ctx; 7496 + js->tail_ctx = false; 7283 7497 7284 7498 if (vtype(func) == T_FFI) { 7285 7499 const char *code = js->code; ··· 7466 7680 const jsoff_t *metadata = (const jsoff_t *)(&js->mem[meta_ptr_off]); 7467 7681 7468 7682 for (int i = 0; i < field_count; i++) { 7469 - jsoff_t name_off = metadata[i * 4 + 0]; 7470 - jsoff_t name_len = metadata[i * 4 + 1]; 7471 - jsoff_t init_start = metadata[i * 4 + 2]; 7472 - jsoff_t init_end = metadata[i * 4 + 3]; 7683 + jsoff_t name_off = metadata[i * 5 + 0]; 7684 + jsoff_t name_len = metadata[i * 5 + 1]; 7685 + jsoff_t init_start = metadata[i * 5 + 2]; 7686 + jsoff_t init_end = metadata[i * 5 + 3]; 7687 + bool computed = metadata[i * 5 + 4] != 0; 7688 + 7689 + jsval_t fname; 7690 + if (computed) { 7691 + jsval_t key_val = js_eval_str(js, &source[name_off], name_len); 7692 + key_val = resolveprop(js, key_val); 7693 + fname = coerce_to_str(js, key_val); 7694 + } else fname = js_mkstr(js, &source[name_off], name_len); 7473 7695 7474 - jsval_t fname = js_mkstr(js, &source[name_off], name_len); 7475 7696 if (is_err(fname)) { 7476 7697 js->current_func = saved_func; 7477 7698 pop_call_frame(); ··· 7510 7731 int argc = (int)utarray_len(call_args); 7511 7732 res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, argv, argc); 7512 7733 utarray_free(call_args); 7734 + } else if (is_tail && !is_arrow && !is_bound && vtype(js->new_target) == T_UNDEF) { 7735 + UT_array *tc_args_arr; 7736 + utarray_new(tc_args_arr, &jsval_icd); 7737 + if (bound_args) { 7738 + for (int i = 0; i < bound_argc; i++) utarray_push_back(tc_args_arr, &bound_args[i]); 7739 + free(bound_args); bound_args = NULL; 7740 + } 7741 + jsval_t tc_err; 7742 + int tc_argc = parse_call_args(js, tc_args_arr, &tc_err); 7743 + if (tc_argc < 0) { 7744 + utarray_free(tc_args_arr); 7745 + pop_call_frame(); 7746 + js->super_val = saved_super; 7747 + js->current_func = saved_func; 7748 + js->code = code; js->clen = clen; js->pos = pos; 7749 + js->flags = (flags & ~F_THROW) | (js->flags & F_THROW); 7750 + js->tok = tok; js->consumed = 1; 7751 + return tc_err; 7752 + } 7753 + jsval_t *tc_argv = NULL; 7754 + if (tc_argc > 0) { 7755 + tc_argv = malloc(tc_argc * sizeof(jsval_t)); 7756 + memcpy(tc_argv, utarray_front(tc_args_arr), tc_argc * sizeof(jsval_t)); 7757 + } 7758 + utarray_free(tc_args_arr); 7759 + js->tc.pending = true; 7760 + js->tc.func = func; 7761 + js->tc.closure_scope = closure_scope; 7762 + js->tc.code_str = code_str; 7763 + js->tc.fnlen = fnlen; 7764 + js->tc.args = tc_argv; 7765 + js->tc.argc = tc_argc; 7766 + pop_call_frame(); 7767 + js->super_val = saved_super; 7768 + js->current_func = saved_func; 7769 + js->code = code; js->clen = clen; js->pos = pos; 7770 + js->flags = (flags & ~F_THROW) | (js->flags & F_THROW); 7771 + js->tok = tok; 7772 + js->consumed = 1; 7773 + return (jsval_t)T_TAILCALL; 7513 7774 } else res = call_js_internal(js, code_str, fnlen, closure_scope, bound_args, bound_argc, func); 7514 7775 pop_call_frame(); 7515 7776 if (bound_args) free(bound_args); ··· 9448 9709 } 9449 9710 } 9450 9711 js_call_dot_loop: 9451 - while (next(js) == TOK_LPAREN || next(js) == TOK_DOT || next(js) == TOK_OPTIONAL_CHAIN || next(js) == TOK_LBRACKET || next(js) == TOK_TEMPLATE) { 9712 + bool opt_chain_skip = false; 9713 + uint8_t cd_tok; 9714 + while ( 9715 + cd_tok = next(js), 9716 + cd_tok == TOK_LPAREN 9717 + || cd_tok == TOK_DOT 9718 + || cd_tok == TOK_OPTIONAL_CHAIN 9719 + || cd_tok == TOK_LBRACKET 9720 + || cd_tok == TOK_TEMPLATE 9721 + ) { 9722 + if (opt_chain_skip) { 9723 + if (js->tok == TOK_OPTIONAL_CHAIN || js->tok == TOK_DOT) { 9724 + js->consumed = 1; 9725 + uint8_t nxt = next(js); 9726 + if (nxt == TOK_HASH) { js->consumed = 1; next(js); } 9727 + if (next(js) == TOK_IDENTIFIER || is_keyword_propname(next(js))) js->consumed = 1; 9728 + else if (next(js) == TOK_LBRACKET) { 9729 + js->consumed = 1; js_expr(js); 9730 + if (next(js) == TOK_RBRACKET) js->consumed = 1; 9731 + } 9732 + if (js->tok == TOK_OPTIONAL_CHAIN) opt_chain_skip = false; 9733 + continue; 9734 + } else if (js->tok == TOK_LBRACKET) { 9735 + js->consumed = 1; js_expr(js); 9736 + if (next(js) == TOK_RBRACKET) js->consumed = 1; 9737 + continue; 9738 + } else if (js->tok == TOK_LPAREN) { 9739 + js->consumed = 1; js_call_params(js); 9740 + continue; 9741 + } else opt_chain_skip = false; 9742 + } 9452 9743 if (js->tok == TOK_TEMPLATE) { 9453 9744 if (vtype(res) == T_PROP) res = resolveprop(js, res); 9454 9745 if (is_err(res)) return res; ··· 9469 9760 return js_mkerr_typed(js, JS_ERR_SYNTAX, "private field name expected"); 9470 9761 } 9471 9762 js->consumed = 1; 9472 - prop_name = mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen); 9763 + prop_name = mkcoderef((jsoff_t) (js->toff - 1), (jsoff_t) (js->tlen + 1)); 9473 9764 } else if (nxt == TOK_IDENTIFIER || is_keyword_propname(nxt)) { 9474 9765 js->consumed = 1; 9475 9766 prop_name = mkcoderef((jsoff_t) js->toff, (jsoff_t) js->tlen); ··· 9481 9772 js->consumed = 1; 9482 9773 if (op == TOK_OPTIONAL_CHAIN && (vtype(obj) == T_NULL || vtype(obj) == T_UNDEF)) { 9483 9774 res = js_mkundef(); 9484 - } else { 9485 - res = do_op(js, TOK_BRACKET, res, idx); 9486 - } 9775 + opt_chain_skip = true; 9776 + } else res = do_op(js, TOK_BRACKET, res, idx); 9487 9777 continue; 9488 - } else { 9489 - prop_name = js_group(js); 9490 - } 9778 + } else prop_name = js_group(js); 9491 9779 if (op == TOK_OPTIONAL_CHAIN && (vtype(obj) == T_NULL || vtype(obj) == T_UNDEF)) { 9492 9780 res = js_mkundef(); 9493 - } else { 9494 - res = do_op(js, op, res, prop_name); 9495 - } 9781 + opt_chain_skip = true; 9782 + } else res = do_op(js, op, res, prop_name); 9496 9783 } else if (js->tok == TOK_LBRACKET) { 9497 9784 js->consumed = 1; 9498 9785 if (vtype(res) != T_PROP && vtype(res) != T_PROPREF) { 9499 9786 obj = res; 9500 - } else { 9501 - obj = resolveprop(js, res); 9502 - } 9787 + } else obj = resolveprop(js, res); 9503 9788 jsval_t idx = js_expr(js); 9504 9789 if (is_err(idx)) return idx; 9505 9790 if (next(js) != TOK_RBRACKET) return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected"); ··· 12106 12391 return js_mkundef(); 12107 12392 } 12108 12393 12394 + static inline jsoff_t skip_comment_or_string(const char *code, jsoff_t clen, jsoff_t p) { 12395 + char c = code[p]; 12396 + if (c == '/' && p + 1 < clen) { 12397 + if (code[p + 1] == '/') return skip_line_comment(code, clen, p); 12398 + if (code[p + 1] == '*') return skip_block_comment(code, clen, p); 12399 + } 12400 + if (c == '\'' || c == '"') return skip_string_literal(code, clen, p, c); 12401 + if (c == '`') return skip_template_literal(code, clen, p); 12402 + return 0; 12403 + } 12404 + 12405 + static inline bool is_stmt_end(char c) { 12406 + return c == ';' || c == '}' || c == '\n'; 12407 + } 12408 + 12409 + static const uint8_t binop_table[128] = { 12410 + ['+']=1, ['*']=1, ['%']=1, ['^']=1, [',']=1, ['=']=1, 12411 + ['/']=2, ['-']=2, ['&']=2, ['|']=2, ['<']=2, ['>']=2, ['!']=2, 12412 + }; 12413 + 12414 + static bool is_binop_char(const char *code, jsoff_t clen, jsoff_t p) { 12415 + unsigned char c = (unsigned char)code[p]; 12416 + if (c > 127) return false; 12417 + uint8_t kind = binop_table[c]; 12418 + if (kind == 1) return true; 12419 + if (kind == 0) return false; 12420 + if (p + 1 >= clen) return c == '/'; 12421 + char n = code[p + 1]; 12422 + switch (c) { 12423 + case '/': return n != '/' && n != '*'; 12424 + case '-': return n != '-'; 12425 + case '&': return n != '&'; 12426 + case '|': return n != '|'; 12427 + case '<': return n != '<'; 12428 + case '>': return n != '>'; 12429 + case '!': return n == '='; 12430 + } 12431 + return false; 12432 + } 12433 + 12434 + static jsoff_t find_ternary_colon(const char *code, jsoff_t clen, jsoff_t p) { 12435 + int depth = 0, nesting = 0; 12436 + for (; p < clen; p++) { 12437 + jsoff_t skip = skip_comment_or_string(code, clen, p); 12438 + if (skip) { p = skip - 1; continue; } 12439 + switch (code[p]) { 12440 + case '(': case '[': depth++; break; 12441 + case ')': case ']': depth--; break; 12442 + case '?': if (!depth) nesting++; break; 12443 + case ':': if (!depth && nesting-- == 0) return p; break; 12444 + } 12445 + } 12446 + return clen; 12447 + } 12448 + 12449 + static enum tail_scan scan_tail_span(const char *code, jsoff_t clen, jsoff_t start, jsoff_t end) { 12450 + jsoff_t p = skiptonext(code, end, start, NULL); 12451 + int depth = 0; jsoff_t last_paren_close = 0; 12452 + bool seen_binop = false; 12453 + 12454 + while (p < end) { 12455 + jsoff_t skip = skip_comment_or_string(code, end, p); 12456 + if (skip) { p = skip; continue; } 12457 + char c = code[p]; 12458 + if (c == '(') { depth++; p++; continue; } 12459 + if (c == ')') { 12460 + depth--; p++; 12461 + if (depth < 0) return TAIL_UNSAFE; 12462 + if (depth == 0) last_paren_close = p; 12463 + continue; 12464 + } 12465 + if (depth == 0) { 12466 + if (c == '?') { 12467 + p++; 12468 + jsoff_t colon = find_ternary_colon(code, end, p); 12469 + if (colon >= end) return TAIL_UNSAFE; 12470 + enum tail_scan t = scan_tail_span(code, colon, p, colon); 12471 + enum tail_scan f = scan_tail_span(code, end, colon + 1, end); 12472 + if (t == TAIL_UNSAFE || f == TAIL_UNSAFE) return TAIL_UNSAFE; 12473 + if (t == TAIL_OK || f == TAIL_OK) return TAIL_OK; 12474 + return TAIL_NONE; 12475 + } 12476 + if (is_binop_char(code, end, p)) seen_binop = true; 12477 + } p++; 12478 + } 12479 + 12480 + if (last_paren_close == 0) return TAIL_NONE; 12481 + if (seen_binop) return TAIL_UNSAFE; 12482 + jsoff_t after = skiptonext(code, end, last_paren_close, NULL); 12483 + if ((after >= end) || is_stmt_end(code[after])) return TAIL_OK; 12484 + return TAIL_UNSAFE; 12485 + } 12486 + 12487 + static bool is_tail_call_expr(struct js *js) { 12488 + const char *code = js->code; 12489 + jsoff_t clen = js->clen; 12490 + jsoff_t start = skiptonext(code, clen, js->pos, NULL); 12491 + return scan_tail_span(code, clen, start, clen) == TAIL_OK; 12492 + } 12493 + 12109 12494 static jsval_t js_return(struct js *js) { 12110 12495 uint8_t exe = !(js->flags & F_NOEXEC); 12111 12496 uint8_t in_func = js->flags & F_CALL; ··· 12114 12499 12115 12500 uint8_t nxt = next(js); 12116 12501 if (nxt != TOK_SEMICOLON && nxt != TOK_RBRACE && nxt != TOK_EOF && !js->had_newline) { 12502 + bool prev_tail = js->tail_ctx; 12503 + if (exe && in_func && js->has_tail_calls && is_tail_call_expr(js)) js->tail_ctx = true; 12117 12504 res = resolveprop(js, js_expr_comma(js)); 12505 + js->tail_ctx = prev_tail; 12118 12506 } 12119 12507 12120 12508 if (exe && !in_func) return js_mkundef(); ··· 12211 12599 } 12212 12600 } 12213 12601 12214 - } else { 12215 - break; 12216 - } 12602 + } else break; 12217 12603 } 12218 12604 12219 12605 if (!expect(js, TOK_RBRACE, &res)) { ··· 12314 12700 js->consumed = 0; 12315 12701 12316 12702 uint8_t preserve = 0; 12317 - if (js->flags & F_RETURN) { 12318 - preserve = js->flags & (F_RETURN | F_NOEXEC); 12319 - } 12320 - if (js->flags & F_THROW) { 12321 - preserve = js->flags & (F_THROW | F_NOEXEC); 12322 - } 12703 + if (js->flags & F_RETURN) preserve = js->flags & (F_RETURN | F_NOEXEC); 12704 + if (js->flags & F_THROW) preserve = js->flags & (F_THROW | F_NOEXEC); 12323 12705 js->flags = (flags & ~F_SWITCH) | preserve; 12324 12706 12325 12707 return res; ··· 12416 12798 bool is_private; 12417 12799 bool is_getter; 12418 12800 bool is_setter; 12801 + bool is_static_block; 12802 + bool is_computed; 12419 12803 jsoff_t field_start, field_end; 12420 12804 jsoff_t param_start; 12421 12805 } MethodInfo; ··· 12458 12842 if (next(js) == TOK_IDENTIFIER) { 12459 12843 bool is_get = (js->tlen == 3 && memcmp(&js->code[js->toff], "get", 3) == 0); 12460 12844 bool is_set = (js->tlen == 3 && memcmp(&js->code[js->toff], "set", 3) == 0); 12461 - if (!is_private_member && (is_get || is_set)) { 12845 + if (is_get || is_set) { 12462 12846 jsoff_t saved_pos = js->pos; 12463 12847 jsoff_t saved_toff = js->toff; 12464 12848 jsoff_t saved_tlen = js->tlen; 12465 12849 uint8_t saved_tok = js->tok; 12466 12850 int saved_stream_pos = js->token_stream_pos; 12467 12851 js->consumed = 1; 12468 - if (next(js) == TOK_IDENTIFIER) { 12852 + uint8_t peek = next(js); 12853 + bool next_is_name = (peek == TOK_IDENTIFIER); 12854 + if (!next_is_name && peek == TOK_HASH) { 12855 + js->consumed = 1; 12856 + if (next(js) == TOK_IDENTIFIER) { 12857 + next_is_name = true; 12858 + is_private_member = true; 12859 + } 12860 + } 12861 + if (next_is_name) { 12469 12862 is_getter_method = is_get; 12470 12863 is_setter_method = is_set; 12471 12864 } else { ··· 12479 12872 } 12480 12873 } 12481 12874 12875 + if (is_static_member && !is_getter_method && !is_setter_method && next(js) == TOK_LBRACE) { 12876 + jsoff_t block_start = js->pos - 1; js->consumed = 0; 12877 + jsval_t blk = js_block(js, false); 12878 + if (is_err(blk)) { js->flags = save_flags; utarray_free(methods); return blk; } 12879 + jsoff_t block_end = js->pos; 12880 + MethodInfo static_block = {0}; 12881 + static_block.is_static = true; 12882 + static_block.is_static_block = true; 12883 + static_block.field_start = block_start; 12884 + static_block.field_end = block_end; 12885 + utarray_push_back(methods, &static_block); 12886 + js->consumed = 1; 12887 + continue; 12888 + } 12889 + 12890 + if (next(js) == TOK_LBRACKET) { 12891 + jsoff_t key_start = js->pos; 12892 + js->consumed = 1; 12893 + jsval_t computed_key = js_expr(js); 12894 + if (is_err(computed_key)) { js->flags = save_flags; utarray_free(methods); return computed_key; } 12895 + if (next(js) != TOK_RBRACKET) { js->flags = save_flags; return js_mkerr_typed(js, JS_ERR_SYNTAX, "] expected"); } 12896 + jsoff_t key_end = js->pos - 1; 12897 + js->consumed = 1; 12898 + 12899 + MethodInfo computed = {0}; 12900 + computed.is_static = is_static_member; 12901 + computed.is_field = true; 12902 + computed.is_computed = true; 12903 + computed.name_off = key_start; 12904 + computed.name_len = key_end - key_start; 12905 + 12906 + if (next(js) == TOK_ASSIGN) { 12907 + js->consumed = 1; 12908 + jsoff_t field_start = js->pos; 12909 + jsval_t field_expr = js_expr(js); 12910 + if (is_err(field_expr)) { js->flags = save_flags; utarray_free(methods); return field_expr; } 12911 + jsoff_t field_end = js->pos; 12912 + computed.field_start = field_start; 12913 + computed.field_end = field_end; 12914 + if (next(js) == TOK_SEMICOLON) js->consumed = 1; 12915 + } else if (next(js) == TOK_SEMICOLON) js->consumed = 1; 12916 + utarray_push_back(methods, &computed); 12917 + continue; 12918 + } 12919 + 12482 12920 if (next(js) != TOK_IDENTIFIER && (next(js) < TOK_ASYNC || next(js) > TOK_STATIC)) { 12483 12921 js->flags = save_flags; 12484 12922 return js_mkerr_typed(js, JS_ERR_SYNTAX, "method name expected"); 12485 12923 } 12486 - jsoff_t method_name_off = js->toff, method_name_len = js->tlen; 12924 + 12925 + jsoff_t method_name_off = is_private_member ? js->toff - 1 : js->toff; 12926 + jsoff_t method_name_len = is_private_member ? js->tlen + 1 : js->tlen; 12487 12927 js->consumed = 1; 12488 12928 12489 12929 if (next(js) == TOK_ASSIGN) { ··· 12668 13108 } 12669 13109 12670 13110 if (instance_field_count > 0) { 12671 - size_t metadata_size = instance_field_count * sizeof(jsoff_t) * 4; 13111 + size_t metadata_size = instance_field_count * sizeof(jsoff_t) * 5; 12672 13112 jsoff_t meta_len = (jsoff_t) (metadata_size + 1); 12673 13113 jsoff_t meta_header = (jsoff_t) ((meta_len << 3) | T_STR); 12674 13114 jsoff_t meta_off = js_alloc(js, meta_len + sizeof(meta_header)); ··· 12683 13123 if (m->is_static) continue; 12684 13124 if (!m->is_field) continue; 12685 13125 12686 - metadata[meta_idx * 4 + 0] = m->name_off; 12687 - metadata[meta_idx * 4 + 1] = m->name_len; 12688 - metadata[meta_idx * 4 + 2] = m->field_start; 12689 - metadata[meta_idx * 4 + 3] = m->field_end; 13126 + metadata[meta_idx * 5 + 0] = m->name_off; 13127 + metadata[meta_idx * 5 + 1] = m->name_len; 13128 + metadata[meta_idx * 5 + 2] = m->field_start; 13129 + metadata[meta_idx * 5 + 3] = m->field_end; 13130 + metadata[meta_idx * 5 + 4] = m->is_computed ? 1 : 0; 12690 13131 meta_idx++; 12691 13132 } 12692 13133 ··· 12738 13179 MethodInfo *m = (MethodInfo *)utarray_eltptr(methods, i); 12739 13180 if (!m->is_static) continue; 12740 13181 12741 - jsval_t member_name = js_mkstr(js, &js->code[m->name_off], m->name_len); 13182 + if (m->is_static_block) { 13183 + if (m->field_start > 0 && m->field_end > m->field_start) { 13184 + const char *saved_code = js->code; 13185 + jsoff_t saved_clen = js->clen, saved_pos = js->pos; 13186 + uint8_t saved_tok = js->tok, saved_consumed = js->consumed; 13187 + jsval_t blk_res = js_eval(js, &saved_code[m->field_start], m->field_end - m->field_start); 13188 + js->code = saved_code; js->clen = saved_clen; js->pos = saved_pos; 13189 + js->tok = saved_tok; js->consumed = saved_consumed; 13190 + if (is_err(blk_res)) { utarray_free(methods); return blk_res; } 13191 + } 13192 + continue; 13193 + } 13194 + 13195 + jsval_t member_name; 13196 + if (m->is_computed) { 13197 + jsval_t key_val = js_eval_slice(js, m->name_off, m->name_len); 13198 + key_val = resolveprop(js, key_val); 13199 + member_name = coerce_to_str(js, key_val); 13200 + } else member_name = js_mkstr(js, &js->code[m->name_off], m->name_len); 13201 + 12742 13202 if (is_err(member_name)) return member_name; 12743 13203 12744 13204 if (m->is_field) { ··· 12763 13223 if (vtype(method_func_proto) == T_FUNC) set_proto(js, method_obj, method_func_proto); 12764 13224 12765 13225 jsval_t method_func = mkval(T_FUNC, (unsigned long) vdata(method_obj)); 12766 - jsval_t set_res = js_setprop(js, func_obj, member_name, method_func); 12767 - if (is_err(set_res)) return set_res; 13226 + 13227 + if (m->is_getter || m->is_setter) { 13228 + jsoff_t nm_len; 13229 + const char *nm_str = (const char *)&js->mem[vstr(js, member_name, &nm_len)]; 13230 + if (m->is_getter) { 13231 + js_set_getter_desc(js, func_obj, nm_str, nm_len, method_func, JS_DESC_C); 13232 + } else { 13233 + js_set_setter_desc(js, func_obj, nm_str, nm_len, method_func, JS_DESC_C); 13234 + } 13235 + } else { 13236 + jsval_t set_res = js_setprop(js, func_obj, member_name, method_func); 13237 + if (is_err(set_res)) return set_res; 13238 + } 12768 13239 } 12769 13240 } 12770 13241