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.

arrow this, .call, .bind, shorthand, number in obj fixes

+393 -24
+1 -1
meson.build
··· 74 74 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 75 75 76 76 version_conf = configuration_data() 77 - version_conf.set('ANT_VERSION', '0.0.7.30') 77 + version_conf.set('ANT_VERSION', '0.0.7.31') 78 78 version_conf.set('ANT_GIT_HASH', git_hash) 79 79 version_conf.set('ANT_BUILD_DATE', build_date) 80 80
+269 -23
src/ant.c
··· 319 319 static jsval_t js_while(struct js *js); 320 320 static jsval_t js_do_while(struct js *js); 321 321 static jsval_t js_block_or_stmt(struct js *js); 322 + static bool parse_func_params(struct js *js, uint8_t *flags); 323 + static jsval_t js_regex_literal(struct js *js); 322 324 static jsval_t js_try(struct js *js); 323 325 static jsval_t js_switch(struct js *js); 324 326 static jsval_t do_op(struct js *, uint8_t op, jsval_t l, jsval_t r); ··· 393 395 static jsval_t set_size(struct js *js, jsval_t *args, int nargs); 394 396 static jsval_t set_values(struct js *js, jsval_t *args, int nargs); 395 397 static jsval_t set_forEach(struct js *js, jsval_t *args, int nargs); 398 + static jsval_t builtin_function_call(struct js *js, jsval_t *args, int nargs); 399 + static jsval_t builtin_function_apply(struct js *js, jsval_t *args, int nargs); 400 + static jsval_t builtin_function_bind(struct js *js, jsval_t *args, int nargs); 396 401 397 402 static jsval_t builtin_Math_abs(struct js *js, jsval_t *args, int nargs); 398 403 static jsval_t builtin_Math_acos(struct js *js, jsval_t *args, int nargs); ··· 2342 2347 jsval_t as_obj = (t == T_OBJ) ? obj : mkval(T_OBJ, vdata(obj)); 2343 2348 2344 2349 jsoff_t off = lkp(js, as_obj, "__proto__", 9); 2345 - if (off == 0) return js_mknull(); 2350 + if (off == 0) { 2351 + if (t == T_FUNC || t == T_ARR) return get_prototype_for_type(js, t); 2352 + return js_mknull(); 2353 + } 2346 2354 2347 2355 jsval_t val = resolveprop(js, mkval(T_PROP, off)); 2348 2356 uint8_t vt = vtype(val); ··· 2713 2721 2714 2722 if (t == T_FUNC) { 2715 2723 jsval_t func_obj = mkval(T_OBJ, vdata(l)); 2716 - jsoff_t off = lkp_proto(js, func_obj, ptr, plen); 2724 + jsoff_t off = lkp_proto(js, l, ptr, plen); 2717 2725 if (off != 0) return mkval(T_PROP, off); 2718 2726 if (streq(ptr, plen, "name", 4)) return js_mkstr(js, "", 0); 2719 2727 jsval_t key = js_mkstr(js, ptr, plen); ··· 3026 3034 closure_scope = resolveprop(js, mkval(T_PROP, scope_off)); 3027 3035 } 3028 3036 3037 + jsoff_t this_off = lkp(js, func_obj, "__this", 6); 3038 + if (this_off != 0) { 3039 + jsval_t captured_this = resolveprop(js, mkval(T_PROP, this_off)); 3040 + pop_this(); 3041 + push_this(captured_this); 3042 + } 3043 + 3029 3044 return call_js_code_with_args(js, fn, fnlen, closure_scope, args, nargs); 3030 3045 } 3031 3046 ··· 3195 3210 jsval_t async_val = resolveprop(js, mkval(T_PROP, async_off)); 3196 3211 is_async = vtype(async_val) == T_BOOL && vdata(async_val) == 1; 3197 3212 } 3213 + 3214 + jsval_t captured_this = js_mkundef(); 3215 + bool is_arrow = false; 3216 + jsoff_t this_off = lkp(js, func_obj, "__this", 6); 3217 + if (this_off != 0) { 3218 + captured_this = resolveprop(js, mkval(T_PROP, this_off)); 3219 + is_arrow = true; 3220 + } 3221 + 3198 3222 if (fnlen == 16 && memcmp(code_str, "__builtin_Object", 16) == 0) { 3199 3223 res = call_c(js, builtin_Object); 3200 3224 } else { ··· 3220 3244 jsval_t saved_func = js->current_func; 3221 3245 js->current_func = func; 3222 3246 js->nogc = (jsoff_t) (fnoff - sizeof(jsoff_t)); 3247 + 3248 + if (is_arrow) { 3249 + pop_this(); 3250 + push_this(captured_this); 3251 + } 3223 3252 3224 3253 if (is_async) { 3225 3254 res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, NULL, 0); ··· 3708 3737 return arr; 3709 3738 } 3710 3739 3740 + static jsval_t js_regex_literal(struct js *js) { 3741 + jsoff_t start = js->pos; 3742 + jsoff_t pattern_start = start; 3743 + bool in_class = false; 3744 + 3745 + while (js->pos < js->clen) { 3746 + char c = js->code[js->pos]; 3747 + if (c == '\\' && js->pos + 1 < js->clen) { 3748 + js->pos += 2; 3749 + continue; 3750 + } 3751 + if (c == '[') in_class = true; 3752 + else if (c == ']') in_class = false; 3753 + else if (c == '/' && !in_class) break; 3754 + js->pos++; 3755 + } 3756 + 3757 + if (js->pos >= js->clen || js->code[js->pos] != '/') { 3758 + return js_mkerr(js, "unterminated regex"); 3759 + } 3760 + 3761 + jsoff_t pattern_end = js->pos; 3762 + js->pos++; 3763 + 3764 + jsoff_t flags_start = js->pos; 3765 + while (js->pos < js->clen) { 3766 + char c = js->code[js->pos]; 3767 + if (c == 'g' || c == 'i' || c == 'm' || c == 's' || c == 'u' || c == 'y') { 3768 + js->pos++; 3769 + } else { 3770 + break; 3771 + } 3772 + } 3773 + jsoff_t flags_end = js->pos; 3774 + 3775 + if (js->flags & F_NOEXEC) return js_mkundef(); 3776 + 3777 + jsval_t pattern = js_mkstr(js, &js->code[pattern_start], pattern_end - pattern_start); 3778 + jsval_t flags = js_mkstr(js, &js->code[flags_start], flags_end - flags_start); 3779 + 3780 + jsval_t regexp_obj = mkobj(js, 0); 3781 + setprop(js, regexp_obj, js_mkstr(js, "source", 6), pattern); 3782 + setprop(js, regexp_obj, js_mkstr(js, "flags", 5), flags); 3783 + 3784 + jsoff_t flen = flags_end - flags_start; 3785 + const char *fstr = &js->code[flags_start]; 3786 + bool global = false, ignoreCase = false, multiline = false; 3787 + for (jsoff_t i = 0; i < flen; i++) { 3788 + if (fstr[i] == 'g') global = true; 3789 + if (fstr[i] == 'i') ignoreCase = true; 3790 + if (fstr[i] == 'm') multiline = true; 3791 + } 3792 + 3793 + setprop(js, regexp_obj, js_mkstr(js, "global", 6), mkval(T_BOOL, global ? 1 : 0)); 3794 + setprop(js, regexp_obj, js_mkstr(js, "ignoreCase", 10), mkval(T_BOOL, ignoreCase ? 1 : 0)); 3795 + setprop(js, regexp_obj, js_mkstr(js, "multiline", 9), mkval(T_BOOL, multiline ? 1 : 0)); 3796 + 3797 + return regexp_obj; 3798 + } 3799 + 3711 3800 static jsval_t js_obj_literal(struct js *js) { 3712 3801 uint8_t exe = !(js->flags & F_NOEXEC); 3713 3802 jsval_t obj = exe ? mkobj(js, 0) : js_mkundef(); ··· 3728 3817 if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen); 3729 3818 } else if (js->tok == TOK_STRING) { 3730 3819 if (exe) key = js_str_literal(js); 3820 + } else if (js->tok == TOK_NUMBER) { 3821 + if (exe) key = js_mkstr(js, js->code + js->toff, js->tlen); 3731 3822 } else { 3732 3823 return js_mkerr(js, "parse error"); 3733 3824 } ··· 3741 3832 jsval_t res = setprop(js, obj, key, resolveprop(js, val)); 3742 3833 if (is_err(res)) return res; 3743 3834 } 3835 + } else if (id_len > 0 && next(js) == TOK_LPAREN) { 3836 + uint8_t flags = js->flags; 3837 + jsoff_t pos = js->pos - 1; 3838 + js->consumed = 1; 3839 + if (!parse_func_params(js, &flags)) { 3840 + js->flags = flags; 3841 + return js_mkerr(js, "invalid parameters"); 3842 + } 3843 + EXPECT(TOK_RPAREN, js->flags = flags); 3844 + EXPECT(TOK_LBRACE, js->flags = flags); 3845 + js->consumed = 0; 3846 + js->flags |= F_NOEXEC; 3847 + jsval_t block_res = js_block(js, false); 3848 + if (is_err(block_res)) { 3849 + js->flags = flags; 3850 + return block_res; 3851 + } 3852 + js->flags = flags; 3853 + js->consumed = 1; 3854 + 3855 + if (exe) { 3856 + jsval_t str = js_mkstr(js, &js->code[pos], js->pos - pos); 3857 + jsval_t func_obj = mkobj(js, 0); 3858 + if (is_err(func_obj)) return func_obj; 3859 + jsval_t code_key = js_mkstr(js, "__code", 6); 3860 + setprop(js, func_obj, code_key, str); 3861 + jsval_t name_key = js_mkstr(js, "name", 4); 3862 + setprop(js, func_obj, name_key, key); 3863 + jsval_t scope_key = js_mkstr(js, "__scope", 7); 3864 + setprop(js, func_obj, scope_key, js->scope); 3865 + jsval_t val = mkval(T_FUNC, (unsigned long) vdata(func_obj)); 3866 + jsval_t res = setprop(js, obj, key, val); 3867 + if (is_err(res)) return res; 3868 + } 3744 3869 } else { 3745 3870 EXPECT(TOK_COLON, ); 3746 3871 jsval_t val = js_expr(js); ··· 3949 4074 case TOK_TEMPLATE: return js_template_literal(js); 3950 4075 case TOK_LBRACE: return js_obj_literal(js); 3951 4076 case TOK_LBRACKET: return js_arr_literal(js); 4077 + case TOK_DIV: return js_regex_literal(js); 3952 4078 case TOK_FUNC: return js_func_literal(js, false); 3953 4079 case TOK_ASYNC: { 3954 4080 js->consumed = 1; ··· 4168 4294 if (is_err(scope_key)) return scope_key; 4169 4295 jsval_t res2 = setprop(js, func_obj, scope_key, js->scope); 4170 4296 if (is_err(res2)) return res2; 4297 + 4298 + jsval_t this_key = js_mkstr(js, "__this", 6); 4299 + if (is_err(this_key)) return this_key; 4300 + jsval_t res3 = setprop(js, func_obj, this_key, js->this_val); 4301 + if (is_err(res3)) return res3; 4171 4302 } 4172 4303 4173 4304 return mkval(T_FUNC, (unsigned long) vdata(func_obj)); ··· 6692 6823 return mkval(T_FUNC, (unsigned long) vdata(func_obj)); 6693 6824 } 6694 6825 6826 + static jsval_t builtin_function_call(struct js *js, jsval_t *args, int nargs) { 6827 + jsval_t func = js->this_val; 6828 + if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) { 6829 + return js_mkerr(js, "call requires a function"); 6830 + } 6831 + 6832 + jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef(); 6833 + 6834 + jsval_t *call_args = NULL; 6835 + int call_nargs = (nargs > 1) ? nargs - 1 : 0; 6836 + if (call_nargs > 0) { 6837 + call_args = &args[1]; 6838 + } 6839 + 6840 + jsval_t saved_this = js->this_val; 6841 + push_this(this_arg); 6842 + js->this_val = this_arg; 6843 + 6844 + jsval_t result = call_js_with_args(js, func, call_args, call_nargs); 6845 + 6846 + pop_this(); 6847 + js->this_val = saved_this; 6848 + 6849 + return result; 6850 + } 6851 + 6852 + static int extract_array_args(struct js *js, jsval_t arr, jsval_t **out_args) { 6853 + jsoff_t len_off = lkp(js, arr, "length", 6); 6854 + if (len_off == 0) return 0; 6855 + 6856 + jsval_t len_val = resolveprop(js, mkval(T_PROP, len_off)); 6857 + if (vtype(len_val) != T_NUM) return 0; 6858 + 6859 + int len = (int) tod(len_val); 6860 + if (len <= 0) return 0; 6861 + 6862 + jsval_t *args_out = (jsval_t *)ANT_GC_MALLOC(sizeof(jsval_t) * len); 6863 + if (!args_out) return 0; 6864 + 6865 + for (int i = 0; i < len; i++) { 6866 + char idx[16]; 6867 + snprintf(idx, sizeof(idx), "%d", i); 6868 + jsoff_t prop_off = lkp(js, arr, idx, strlen(idx)); 6869 + args_out[i] = (prop_off != 0) ? resolveprop(js, mkval(T_PROP, prop_off)) : js_mkundef(); 6870 + } 6871 + 6872 + *out_args = args_out; 6873 + return len; 6874 + } 6875 + 6876 + static jsval_t builtin_function_apply(struct js *js, jsval_t *args, int nargs) { 6877 + jsval_t func = js->this_val; 6878 + if (vtype(func) != T_FUNC && vtype(func) != T_CFUNC) { 6879 + return js_mkerr(js, "apply requires a function"); 6880 + } 6881 + 6882 + jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef(); 6883 + jsval_t *call_args = NULL; 6884 + int call_nargs = 0; 6885 + 6886 + if (nargs > 1 && vtype(args[1]) == T_ARR) { 6887 + call_nargs = extract_array_args(js, args[1], &call_args); 6888 + } 6889 + 6890 + jsval_t saved_this = js->this_val; 6891 + push_this(this_arg); 6892 + js->this_val = this_arg; 6893 + 6894 + jsval_t result = call_js_with_args(js, func, call_args, call_nargs); 6895 + 6896 + pop_this(); 6897 + js->this_val = saved_this; 6898 + 6899 + if (call_args) ANT_GC_FREE(call_args); 6900 + 6901 + return result; 6902 + } 6903 + 6904 + static jsval_t builtin_function_bind(struct js *js, jsval_t *args, int nargs) { 6905 + jsval_t func = js->this_val; 6906 + if (vtype(func) != T_FUNC) { 6907 + return js_mkerr(js, "bind requires a function"); 6908 + } 6909 + 6910 + jsval_t this_arg = (nargs > 0) ? args[0] : js_mkundef(); 6911 + 6912 + jsval_t func_obj = mkval(T_OBJ, vdata(func)); 6913 + jsval_t bound_func = mkobj(js, 0); 6914 + if (is_err(bound_func)) return bound_func; 6915 + 6916 + jsoff_t code_off = lkp(js, func_obj, "__code", 6); 6917 + if (code_off != 0) { 6918 + jsval_t code_val = resolveprop(js, mkval(T_PROP, code_off)); 6919 + setprop(js, bound_func, js_mkstr(js, "__code", 6), code_val); 6920 + } 6921 + 6922 + jsoff_t scope_off = lkp(js, func_obj, "__scope", 7); 6923 + if (scope_off != 0) { 6924 + jsval_t scope_val = resolveprop(js, mkval(T_PROP, scope_off)); 6925 + setprop(js, bound_func, js_mkstr(js, "__scope", 7), scope_val); 6926 + } 6927 + 6928 + jsoff_t async_off = lkp(js, func_obj, "__async", 7); 6929 + if (async_off != 0) { 6930 + jsval_t async_val = resolveprop(js, mkval(T_PROP, async_off)); 6931 + setprop(js, bound_func, js_mkstr(js, "__async", 7), async_val); 6932 + } 6933 + 6934 + setprop(js, bound_func, js_mkstr(js, "__this", 6), this_arg); 6935 + 6936 + return mkval(T_FUNC, (unsigned long) vdata(bound_func)); 6937 + } 6938 + 6695 6939 static jsval_t builtin_Array(struct js *js, jsval_t *args, int nargs) { 6696 6940 jsval_t arr = mkarr(js); 6697 6941 ··· 7718 7962 7719 7963 if (vtype(search) == T_OBJ) { 7720 7964 jsoff_t pattern_off = lkp(js, search, "source", 6); 7965 + if (pattern_off == 0) goto not_regex; 7966 + 7967 + jsval_t pattern_val = resolveprop(js, mkval(T_PROP, pattern_off)); 7968 + if (vtype(pattern_val) != T_STR) goto not_regex; 7969 + 7970 + is_regex = true; 7971 + jsoff_t plen, poff = vstr(js, pattern_val, &plen); 7972 + pattern_len = plen < sizeof(pattern_buf) - 1 ? plen : sizeof(pattern_buf) - 1; 7973 + memcpy(pattern_buf, &js->mem[poff], pattern_len); 7974 + pattern_buf[pattern_len] = '\0'; 7975 + 7721 7976 jsoff_t flags_off = lkp(js, search, "flags", 5); 7977 + if (flags_off == 0) goto not_regex; 7722 7978 7723 - if (pattern_off != 0) { 7724 - jsval_t pattern_val = resolveprop(js, mkval(T_PROP, pattern_off)); 7725 - if (vtype(pattern_val) == T_STR) { 7726 - is_regex = true; 7727 - jsoff_t plen, poff = vstr(js, pattern_val, &plen); 7728 - pattern_len = plen < sizeof(pattern_buf) - 1 ? plen : sizeof(pattern_buf) - 1; 7729 - memcpy(pattern_buf, &js->mem[poff], pattern_len); 7730 - pattern_buf[pattern_len] = '\0'; 7731 - 7732 - if (flags_off != 0) { 7733 - jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off)); 7734 - if (vtype(flags_val) == T_STR) { 7735 - jsoff_t flen, foff = vstr(js, flags_val, &flen); 7736 - const char *flags_str = (char *) &js->mem[foff]; 7737 - for (jsoff_t i = 0; i < flen; i++) { 7738 - if (flags_str[i] == 'g') global_flag = true; 7739 - } 7740 - } 7741 - } 7742 - } 7979 + jsval_t flags_val = resolveprop(js, mkval(T_PROP, flags_off)); 7980 + if (vtype(flags_val) != T_STR) goto not_regex; 7981 + 7982 + jsoff_t flen, foff = vstr(js, flags_val, &flen); 7983 + const char *flags_str = (char *) &js->mem[foff]; 7984 + for (jsoff_t i = 0; i < flen; i++) { 7985 + if (flags_str[i] == 'g') global_flag = true; 7743 7986 } 7744 7987 } 7988 + not_regex: 7745 7989 7746 7990 if (vtype(replacement) != T_STR) return str; 7747 7991 jsoff_t repl_len, repl_off = vstr(js, replacement, &repl_len); ··· 9909 10153 9910 10154 jsval_t function_proto = js_mkobj(js); 9911 10155 set_proto(js, function_proto, object_proto); 10156 + setprop(js, function_proto, js_mkstr(js, "call", 4), js_mkfun(builtin_function_call)); 10157 + setprop(js, function_proto, js_mkstr(js, "apply", 5), js_mkfun(builtin_function_apply)); 10158 + setprop(js, function_proto, js_mkstr(js, "bind", 4), js_mkfun(builtin_function_bind)); 9912 10159 9913 10160 jsval_t array_proto = js_mkobj(js); 9914 10161 set_proto(js, array_proto, object_proto); ··· 10081 10328 setprop(js, glob, js_mkstr(js, "NaN", 3), tov(NAN)); 10082 10329 setprop(js, glob, js_mkstr(js, "Infinity", 8), tov(INFINITY)); 10083 10330 10084 - // Math object 10085 10331 jsval_t math_obj = mkobj(js, 0); 10086 10332 set_proto(js, math_obj, object_proto); 10087 10333 setprop(js, math_obj, js_mkstr(js, "E", 1), tov(M_E));
+66
tests/test_arrow_this.cjs
··· 1 + // Test arrow function 'this' binding - arrows capture 'this' from surrounding scope 2 + 3 + // Test 1: Arrow inside regular function captures outer this 4 + let obj1 = { 5 + name: "test", 6 + regular() { return this.name; }, 7 + method() { 8 + let inner = () => this.name; 9 + return inner(); 10 + } 11 + }; 12 + console.log("Test 1a - regular function this:", obj1.regular()); // Should be "test" 13 + console.log("Test 1b - arrow captures outer this:", obj1.method()); // Should be "test" 14 + 15 + // Test 2: Nested arrows maintain lexical this 16 + let obj2 = { 17 + value: 42, 18 + outer() { 19 + let first = () => { 20 + let second = () => this.value; 21 + return second(); 22 + }; 23 + return first(); 24 + } 25 + }; 26 + console.log("Test 2 - nested arrows:", obj2.outer()); // Should be 42 27 + 28 + // Test 3: Arrow in callback maintains this 29 + let obj3 = { 30 + items: [1, 2, 3], 31 + multiplier: 10, 32 + process() { 33 + let results = []; 34 + let self = this; 35 + for (let i = 0; i < this.items.length; i++) { 36 + let fn = () => self.items[i] * self.multiplier; 37 + results[i] = fn(); 38 + } 39 + return results; 40 + } 41 + }; 42 + let result3 = obj3.process(); 43 + console.log("Test 3 - arrow in loop:", result3[0], result3[1], result3[2]); // Should be 10 20 30 44 + 45 + // Test 4: Arrow function stored and called later still uses captured this 46 + let obj4 = { 47 + secret: "hidden", 48 + getGetter() { 49 + return () => this.secret; 50 + } 51 + }; 52 + let getter = obj4.getGetter(); 53 + console.log("Test 4 - stored arrow:", getter()); // Should be "hidden" 54 + 55 + // Test 5: Arrow passed to different object still uses captured this 56 + let obj5 = { 57 + id: "obj5", 58 + arrow() { 59 + return () => this.id; 60 + } 61 + }; 62 + let arrowFromObj5 = obj5.arrow(); 63 + let obj6 = { id: "obj6", stolenArrow: arrowFromObj5 }; 64 + console.log("Test 5 - arrow keeps original this:", obj6.stolenArrow()); // Should still be "obj5" 65 + 66 + console.log("\nAll arrow function this tests completed!");
+57
tests/test_function_call_apply_bind.cjs
··· 1 + // Test Function.prototype.call, apply, and bind methods 2 + 3 + // Test 1: Basic call with no this 4 + function greet(name) { return "Hello, " + name; } 5 + console.log("Test 1 - call:", greet.call(null, "World")); // Hello, World 6 + 7 + // Test 2: call with custom this 8 + function showThis() { return this.value; } 9 + let obj = { value: 42 }; 10 + console.log("Test 2 - call with this:", showThis.call(obj)); // 42 11 + 12 + // Test 3: call with multiple arguments 13 + function add(a, b, c) { return a + b + c; } 14 + console.log("Test 3 - call multiple args:", add.call(null, 1, 2, 3)); // 6 15 + 16 + // Test 4: apply with array of arguments 17 + function sum(a, b, c) { return a + b + c; } 18 + console.log("Test 4 - apply:", sum.apply(null, [10, 20, 30])); // 60 19 + 20 + // Test 5: apply with custom this 21 + function getInfo() { return this.name + " is " + this.age; } 22 + let person = { name: "Alice", age: 30 }; 23 + console.log("Test 5 - apply with this:", getInfo.apply(person, [])); // Alice is 30 24 + 25 + // Test 6: bind creates new function with bound this 26 + function getValue() { return this.x; } 27 + let data = { x: 100 }; 28 + let boundFn = getValue.bind(data); 29 + console.log("Test 6 - bind:", boundFn()); // 100 30 + 31 + // Test 7: bound function ignores call-site this 32 + let other = { x: 999 }; 33 + other.fn = boundFn; 34 + console.log("Test 7 - bound ignores new this:", other.fn()); // 100 35 + 36 + // Test 8: call with undefined this (should work) 37 + function returnArg(x) { return x * 2; } 38 + console.log("Test 8 - call undefined this:", returnArg.call(undefined, 5)); // 10 39 + 40 + // Test 9: Method borrowing with call 41 + let arr1 = { 0: "a", 1: "b", length: 2 }; 42 + function joinItems() { 43 + let result = ""; 44 + for (let i = 0; i < this.length; i++) { 45 + if (i > 0) result = result + ","; 46 + result = result + this[i]; 47 + } 48 + return result; 49 + } 50 + console.log("Test 9 - method borrowing:", joinItems.call(arr1)); // a,b 51 + 52 + // Test 10: Chained function operations 53 + function multiply(factor) { return this.base * factor; } 54 + let calc = { base: 5 }; 55 + console.log("Test 10 - chained:", multiply.call(calc, 3)); // 15 56 + 57 + console.log("\nAll Function.prototype.call/apply/bind tests completed!");