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.

move to stack-based "this"

+477 -14
+63 -14
src/ant.c
··· 15 15 16 16 typedef uint32_t jsoff_t; 17 17 18 + typedef struct { 19 + jsval_t *stack; 20 + int depth; 21 + int capacity; 22 + } this_stack_t; 23 + 24 + static this_stack_t global_this_stack = {NULL, 0, 0}; 25 + 18 26 struct js { 19 27 jsoff_t css; // max observed C stack size 20 28 jsoff_t lwm; // JS ram low watermark: min free ram observed ··· 36 44 jsoff_t nogc; // entity offset to exclude from GC 37 45 jsval_t tval; // holds last parsed numeric or string literal value 38 46 jsval_t scope; // current scope 39 - jsval_t this_val; // 'this' value for method calls 40 - jsval_t caller_this; // saved 'this' from caller during function call 47 + jsval_t this_val; // 'this' value for currently executing function 41 48 uint8_t *mem; // available JS memory 42 49 jsoff_t size; // memory size 43 50 jsoff_t brk; // current mem usage boundary ··· 712 719 static void mkscope(struct js *js) { js_mkscope(js); } 713 720 static void delscope(struct js *js) { js_delscope(js); } 714 721 722 + static inline bool push_this(jsval_t this_value) { 723 + if (global_this_stack.depth >= global_this_stack.capacity) { 724 + int new_capacity = global_this_stack.capacity == 0 ? 16 : global_this_stack.capacity * 2; 725 + jsval_t *new_stack = (jsval_t *) realloc(global_this_stack.stack, new_capacity * sizeof(jsval_t)); 726 + if (!new_stack) return false; 727 + global_this_stack.stack = new_stack; 728 + global_this_stack.capacity = new_capacity; 729 + } 730 + global_this_stack.stack[global_this_stack.depth++] = this_value; 731 + return true; 732 + } 733 + 734 + static inline jsval_t pop_this() { 735 + if (global_this_stack.depth > 0) { 736 + return global_this_stack.stack[--global_this_stack.depth]; 737 + } 738 + return js_mkundef(); 739 + } 740 + 741 + static inline jsval_t peek_this() { 742 + if (global_this_stack.depth > 0) { 743 + return global_this_stack.stack[global_this_stack.depth - 1]; 744 + } 745 + return js_mkundef(); 746 + } 747 + 715 748 static jsval_t js_block(struct js *js, bool create_scope) { 716 749 jsval_t res = js_mkundef(); 717 750 if (create_scope) mkscope(js); ··· 994 1027 static jsval_t call_c(struct js *js, 995 1028 jsval_t (*fn)(struct js *, jsval_t *, int)) { 996 1029 int argc = 0; 997 - jsval_t saved_caller_this = js->caller_this; 1030 + jsval_t target_this = peek_this(); 998 1031 999 1032 while (js->pos < js->clen) { 1000 1033 if (next(js) == TOK_RPAREN) break; ··· 1007 1040 } 1008 1041 1009 1042 jsval_t saved_this = js->this_val; 1010 - js->this_val = saved_caller_this; 1043 + js->this_val = target_this; 1011 1044 reverse((jsval_t *) &js->mem[js->size], argc); 1012 1045 jsval_t res = fn(js, (jsval_t *) &js->mem[js->size], argc); 1013 1046 js->this_val = saved_this; 1014 - js->caller_this = saved_caller_this; 1015 1047 setlwm(js); 1016 1048 1017 1049 js->size += (jsoff_t) sizeof(jsval_t) * (jsoff_t) argc; ··· 1021 1053 static jsval_t call_js(struct js *js, const char *fn, jsoff_t fnlen, jsval_t closure_scope) { 1022 1054 jsoff_t fnpos = 1; 1023 1055 jsval_t saved_scope = js->scope; 1056 + jsval_t target_this = peek_this(); 1024 1057 jsoff_t parent_scope_offset; 1025 1058 1026 1059 if (vtype(closure_scope) == T_OBJ) { ··· 1051 1084 fnpos = skiptonext(fn, fnlen, fnpos); 1052 1085 if (fnpos < fnlen && fn[fnpos] == '{') fnpos++; 1053 1086 size_t n = fnlen - fnpos - 1U; 1054 - js->this_val = js->caller_this; 1087 + js->this_val = target_this; 1055 1088 js->flags = F_CALL; 1056 1089 1057 1090 jsval_t res = js_eval(js, &fn[fnpos], n); ··· 1852 1885 js->consumed = 1; 1853 1886 res = do_op(js, TOK_BRACKET, res, idx); 1854 1887 } else { 1855 - jsval_t params = js_call_params(js); 1856 - if (is_err(params)) return params; 1857 1888 jsval_t func_this = (vtype(obj) != T_UNDEF) ? obj : js->this_val; 1858 - jsval_t saved_this = js->this_val; 1859 - js->caller_this = func_this; 1889 + push_this(func_this); 1890 + jsval_t params = js_call_params(js); 1891 + if (is_err(params)) { 1892 + pop_this(); 1893 + return params; 1894 + } 1860 1895 res = do_op(js, TOK_CALL, res, params); 1861 - js->this_val = saved_this; 1896 + pop_this(); 1862 1897 obj = js_mkundef(); 1863 1898 } 1864 1899 } ··· 2548 2583 uint8_t save_flags = js->flags; 2549 2584 js->flags |= F_NOEXEC; 2550 2585 2551 - typedef struct { jsoff_t name_off, name_len, fn_start, fn_end; } MethodInfo; 2586 + typedef struct { jsoff_t name_off, name_len, fn_start, fn_end; bool is_async; } MethodInfo; 2552 2587 MethodInfo methods[32]; 2553 2588 int method_count = 0; 2554 2589 2555 2590 while (next(js) != TOK_RBRACE && next(js) != TOK_EOF && method_count < 32) { 2591 + bool is_async_method = false; 2592 + if (next(js) == TOK_ASYNC) { 2593 + is_async_method = true; 2594 + js->consumed = 1; 2595 + } 2556 2596 EXPECT(TOK_IDENTIFIER, js->flags = save_flags); 2557 2597 jsoff_t method_name_off = js->toff, method_name_len = js->tlen; 2558 2598 js->consumed = 1; ··· 2585 2625 methods[method_count].name_len = method_name_len; 2586 2626 methods[method_count].fn_start = method_params_start; 2587 2627 methods[method_count].fn_end = method_body_end; 2628 + methods[method_count].is_async = is_async_method; 2588 2629 method_count++; 2589 2630 } 2590 2631 js->consumed = 1; ··· 2639 2680 memcpy(wrapper + wp, &js->code[methods[i].name_off], methods[i].name_len); 2640 2681 wp += methods[i].name_len; 2641 2682 wrapper[wp++] = '='; 2683 + if (methods[i].is_async) { 2684 + memcpy(wrapper + wp, "async ", 6); 2685 + wp += 6; 2686 + } 2642 2687 memcpy(wrapper + wp, "function", 8); 2643 2688 wp += 8; 2644 2689 jsoff_t mlen = methods[i].fn_end - methods[i].fn_start; ··· 3691 3736 js->lwm = js->size; 3692 3737 js->gct = js->size / 2; 3693 3738 js->this_val = js->scope; 3694 - js->caller_this = js->scope; 3695 3739 3696 3740 jsval_t glob = js->scope; 3697 3741 jsval_t obj_func_obj = mkobj(js, 0); ··· 3881 3925 3882 3926 if (fnpos < fnlen && fn[fnpos] == '{') fnpos++; 3883 3927 size_t body_len = fnlen - fnpos - 1; 3928 + 3929 + jsval_t saved_this = js->this_val; 3930 + js->this_val = js_glob(js); 3931 + 3884 3932 js->flags = F_CALL; 3885 3933 jsval_t res = js_eval(js, &fn[fnpos], body_len); 3934 + if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 3886 3935 3887 - if (!is_err(res) && !(js->flags & F_RETURN)) res = js_mkundef(); 3936 + js->this_val = saved_this; 3888 3937 delscope(js); 3889 3938 3890 3939 return res;
+66
tests/test_async_class_methods.cjs
··· 1 + // Test async methods in classes 2 + 3 + class AsyncTestClass { 4 + constructor() { 5 + this.name = 'AsyncTestClass'; 6 + this.value = 42; 7 + } 8 + 9 + // Regular method 10 + regularMethod() { 11 + Ant.println('regularMethod: this.name = ' + this.name); 12 + return this.value; 13 + } 14 + 15 + // Async method 16 + async asyncMethod() { 17 + Ant.println('asyncMethod: this.name = ' + this.name); 18 + return Promise.resolve(this.value * 2); 19 + } 20 + 21 + // Async method with argument 22 + async asyncWithArg(multiplier) { 23 + Ant.println('asyncWithArg: this.name = ' + this.name + ', multiplier = ' + multiplier); 24 + return Promise.resolve(this.value * multiplier); 25 + } 26 + 27 + // Async method that calls helper 28 + async asyncWithHelper() { 29 + function helper() { 30 + return 10; 31 + } 32 + Ant.println('asyncWithHelper: this.name = ' + this.name); 33 + const extra = helper(); 34 + return Promise.resolve(this.value + extra); 35 + } 36 + } 37 + 38 + const obj = new AsyncTestClass(); 39 + 40 + Ant.println('=== Test 1: Regular method ==='); 41 + const result1 = obj.regularMethod(); 42 + Ant.println('Result: ' + result1); 43 + 44 + Ant.println('\n=== Test 2: Async method ==='); 45 + const promise2 = obj.asyncMethod(); 46 + Ant.println('Returned: ' + (typeof promise2)); 47 + promise2.then((val) => { 48 + Ant.println('Resolved value: ' + val); 49 + Ant.println('Inside .then(), this.name: ' + this.name); 50 + }); 51 + 52 + Ant.println('\n=== Test 3: Async method with argument ==='); 53 + const promise3 = obj.asyncWithArg(5); 54 + Ant.println('Returned: ' + (typeof promise3)); 55 + promise3.then((val) => { 56 + Ant.println('Resolved value: ' + val); 57 + }); 58 + 59 + Ant.println('\n=== Test 4: Async method with helper function ==='); 60 + const promise4 = obj.asyncWithHelper(); 61 + Ant.println('Returned: ' + (typeof promise4)); 62 + promise4.then((val) => { 63 + Ant.println('Resolved value: ' + val); 64 + }); 65 + 66 + Ant.println('\n=== All tests completed ===');
+63
tests/test_async_regular_function.cjs
··· 1 + // Test that 'this' works with regular functions in async callbacks 2 + 3 + class AsyncRegularFunctionTest { 4 + constructor(name) { 5 + this.name = name; 6 + this.value = 100; 7 + } 8 + 9 + testPromiseWithRegularFunction() { 10 + Ant.println('=== testPromiseWithRegularFunction ==='); 11 + Ant.println('Before promise: this.name = ' + this.name); 12 + 13 + return Promise.resolve(42).then(function(val) { 14 + Ant.println('Inside .then() with function:'); 15 + Ant.println(' typeof this: ' + typeof this); 16 + Ant.println(' this.name: ' + this.name); 17 + Ant.println(' this.value: ' + this.value); 18 + return val + 1; 19 + }); 20 + } 21 + 22 + testPromiseWithArrowFunction() { 23 + Ant.println('\n=== testPromiseWithArrowFunction ==='); 24 + Ant.println('Before promise: this.name = ' + this.name); 25 + 26 + return Promise.resolve(42).then((val) => { 27 + Ant.println('Inside .then() with arrow:'); 28 + Ant.println(' typeof this: ' + typeof this); 29 + Ant.println(' this.name: ' + this.name); 30 + Ant.println(' this.value: ' + this.value); 31 + return val + 1; 32 + }); 33 + } 34 + 35 + testMethodCallingPromiseWithFunction() { 36 + Ant.println('\n=== testMethodCallingPromiseWithFunction ==='); 37 + const self = this; 38 + Ant.println('Before promise: this.name = ' + this.name); 39 + 40 + return Promise.resolve(1).then(function() { 41 + Ant.println('In .then() with function:'); 42 + Ant.println(' this.name: ' + this.name); 43 + Ant.println(' self.name: ' + self.name); 44 + return self.value; 45 + }); 46 + } 47 + } 48 + 49 + const obj = new AsyncRegularFunctionTest('TestObject'); 50 + 51 + obj.testPromiseWithRegularFunction().then(function(result) { 52 + Ant.println('Result 1: ' + result); 53 + }); 54 + 55 + obj.testPromiseWithArrowFunction().then((result) => { 56 + Ant.println('Result 2: ' + result); 57 + }); 58 + 59 + obj.testMethodCallingPromiseWithFunction().then((result) => { 60 + Ant.println('Result 3: ' + result); 61 + }); 62 + 63 + Ant.println('\n=== All tests queued ===');
+54
tests/test_async_this.cjs
··· 1 + // Test async/promise handling with 'this' context 2 + 3 + class AsyncClass { 4 + constructor() { 5 + this.name = 'AsyncClass'; 6 + this.value = 100; 7 + } 8 + 9 + asyncMethod(arg) { 10 + Ant.println('asyncMethod called:'); 11 + Ant.println(' this.name: ' + this.name); 12 + Ant.println(' this.value: ' + this.value); 13 + Ant.println(' arg: ' + arg); 14 + return Promise.resolve(this.value + 10); 15 + } 16 + 17 + methodWithPromise() { 18 + Ant.println('methodWithPromise called:'); 19 + Ant.println(' this.name: ' + this.name); 20 + 21 + return Promise.resolve(this.value).then((v) => { 22 + Ant.println(' Inside .then(), this.name: ' + this.name); 23 + Ant.println(' Inside .then(), this.value: ' + this.value); 24 + return v * 2; 25 + }); 26 + } 27 + 28 + nestedAsync(helperFunc) { 29 + Ant.println('nestedAsync called:'); 30 + Ant.println(' this.name: ' + this.name); 31 + const result = helperFunc(); 32 + Ant.println(' After helper call, this.name: ' + this.name); 33 + return result; 34 + } 35 + } 36 + 37 + function helperFunction() { 38 + return 'helper result'; 39 + } 40 + 41 + const obj = new AsyncClass(); 42 + 43 + Ant.println('=== Test 1: Async method ==='); 44 + const p1 = obj.asyncMethod('test'); 45 + Ant.println('Promise returned: ' + (typeof p1)); 46 + 47 + Ant.println('\n=== Test 2: Method with promise chain ==='); 48 + const p2 = obj.methodWithPromise(); 49 + Ant.println('Promise returned: ' + (typeof p2)); 50 + 51 + Ant.println('\n=== Test 3: Nested call with helper ==='); 52 + obj.nestedAsync(helperFunction); 53 + 54 + Ant.println('\n=== All tests completed ===');
+44
tests/test_async_this_binding.cjs
··· 1 + // Test explicit this binding in async contexts 2 + 3 + class TestClass { 4 + constructor(name) { 5 + this.name = name; 6 + } 7 + 8 + methodThatReturnsPromise() { 9 + Ant.println('methodThatReturnsPromise: this.name = ' + this.name); 10 + 11 + // Use regular function (not arrow) in .then() 12 + return Promise.resolve('value').then(function(val) { 13 + // In standard JS, 'this' would be undefined here 14 + // Let's see what Ant does 15 + Ant.println('In .then() regular function:'); 16 + Ant.println(' typeof this: ' + typeof this); 17 + Ant.println(' this: ' + this); 18 + 19 + // Try to access properties 20 + if (typeof this.name !== 'undefined') { 21 + Ant.println(' this.name: ' + this.name); 22 + } else { 23 + Ant.println(' this.name is undefined'); 24 + } 25 + 26 + return val; 27 + }); 28 + } 29 + } 30 + 31 + const obj1 = new TestClass('Object1'); 32 + const obj2 = new TestClass('Object2'); 33 + 34 + Ant.println('=== Calling obj1.methodThatReturnsPromise() ==='); 35 + obj1.methodThatReturnsPromise().then(function(result) { 36 + Ant.println('Final result: ' + result); 37 + }); 38 + 39 + Ant.println('\n=== Calling obj2.methodThatReturnsPromise() ==='); 40 + obj2.methodThatReturnsPromise().then(function(result) { 41 + Ant.println('Final result: ' + result); 42 + }); 43 + 44 + Ant.println('\n=== Tests queued ===');
+63
tests/test_async_this_context.cjs
··· 1 + // Test that 'this' is preserved in async callbacks 2 + 3 + class AsyncContextTest { 4 + constructor(name) { 5 + this.name = name; 6 + this.value = 100; 7 + } 8 + 9 + testPromiseThen() { 10 + Ant.println('=== testPromiseThen ==='); 11 + Ant.println('Before promise: this.name = ' + this.name); 12 + 13 + return Promise.resolve(42).then((val) => { 14 + Ant.println('Inside .then(): this.name = ' + this.name); 15 + Ant.println('Inside .then(): this.value = ' + this.value); 16 + return this.value + val; 17 + }); 18 + } 19 + 20 + testNestedPromises() { 21 + Ant.println('\n=== testNestedPromises ==='); 22 + Ant.println('Before promise: this.name = ' + this.name); 23 + 24 + return Promise.resolve(10).then((val1) => { 25 + Ant.println('First .then(): this.name = ' + this.name); 26 + return Promise.resolve(val1 + this.value).then((val2) => { 27 + Ant.println('Second .then(): this.name = ' + this.name); 28 + return val2 * 2; 29 + }); 30 + }); 31 + } 32 + 33 + testMethodCallingAsync() { 34 + Ant.println('\n=== testMethodCallingAsync ==='); 35 + Ant.println('Before: this.name = ' + this.name); 36 + 37 + const helper = () => { 38 + Ant.println('Inside helper: this.name = ' + this.name); 39 + return this.value; 40 + }; 41 + 42 + return Promise.resolve(1).then(() => { 43 + Ant.println('In .then(), calling helper'); 44 + return helper(); 45 + }); 46 + } 47 + } 48 + 49 + const obj = new AsyncContextTest('TestObject'); 50 + 51 + obj.testPromiseThen().then((result) => { 52 + Ant.println('Result 1: ' + result); 53 + }); 54 + 55 + obj.testNestedPromises().then((result) => { 56 + Ant.println('Result 2: ' + result); 57 + }); 58 + 59 + obj.testMethodCallingAsync().then((result) => { 60 + Ant.println('Result 3: ' + result); 61 + }); 62 + 63 + Ant.println('\n=== All tests queued ===');
+73
tests/test_deep_this.cjs
··· 1 + // Test deeply nested calls with 'this' context 2 + 3 + class DeepClass { 4 + constructor(name) { 5 + this.name = name; 6 + this.depth = 0; 7 + } 8 + 9 + method1(func) { 10 + Ant.println('method1: this.name = ' + this.name + ', depth = ' + this.depth); 11 + this.depth++; 12 + const result = func(); 13 + this.depth--; 14 + return result; 15 + } 16 + 17 + method2(func) { 18 + Ant.println('method2: this.name = ' + this.name + ', depth = ' + this.depth); 19 + this.depth++; 20 + const result = func(); 21 + this.depth--; 22 + return result; 23 + } 24 + 25 + method3(func) { 26 + Ant.println('method3: this.name = ' + this.name + ', depth = ' + this.depth); 27 + this.depth++; 28 + const result = func(); 29 + this.depth--; 30 + return result; 31 + } 32 + 33 + leaf() { 34 + Ant.println('leaf: this.name = ' + this.name + ', depth = ' + this.depth); 35 + return 'done'; 36 + } 37 + } 38 + 39 + function helper1() { 40 + return 'helper1'; 41 + } 42 + 43 + function helper2() { 44 + return 'helper2'; 45 + } 46 + 47 + const obj1 = new DeepClass('obj1'); 48 + const obj2 = new DeepClass('obj2'); 49 + 50 + Ant.println('=== Test 1: Deeply nested calls on same object ==='); 51 + obj1.method1(() => { 52 + return obj1.method2(() => { 53 + return obj1.method3(() => { 54 + return obj1.leaf(); 55 + }); 56 + }); 57 + }); 58 + 59 + Ant.println('\n=== Test 2: Interleaved calls on different objects ==='); 60 + obj1.method1(() => { 61 + obj2.method1(() => { 62 + return obj2.leaf(); 63 + }); 64 + return obj1.method2(() => { 65 + return obj1.leaf(); 66 + }); 67 + }); 68 + 69 + Ant.println('\n=== Test 3: With helper function calls in arguments ==='); 70 + obj1.method1(() => helper1()); 71 + obj2.method2(() => helper2()); 72 + 73 + Ant.println('\n=== All deep nesting tests passed ===');
+51
tests/test_stack_depth.cjs
··· 1 + // Test that the this stack is properly managed across async boundaries 2 + 3 + class StackTest { 4 + constructor(name) { 5 + this.name = name; 6 + } 7 + 8 + // Regular method that calls another with function arg 9 + methodA(getArg) { 10 + Ant.println('methodA: this.name = ' + this.name); 11 + return this.methodB(getArg()); 12 + } 13 + 14 + methodB(arg) { 15 + Ant.println('methodB: this.name = ' + this.name + ', arg = ' + arg); 16 + return this.name + ':' + arg; 17 + } 18 + 19 + // Method that returns a promise 20 + asyncMethod() { 21 + Ant.println('asyncMethod: this.name = ' + this.name); 22 + return Promise.resolve(this.name); 23 + } 24 + } 25 + 26 + function helperFunc() { 27 + return 'helper'; 28 + } 29 + 30 + const obj1 = new StackTest('obj1'); 31 + const obj2 = new StackTest('obj2'); 32 + 33 + Ant.println('=== Test 1: Synchronous nested calls ==='); 34 + const result1 = obj1.methodA(() => helperFunc()); 35 + Ant.println('Result: ' + result1); 36 + 37 + Ant.println('\n=== Test 2: Async then sync ==='); 38 + obj1.asyncMethod().then((val) => { 39 + Ant.println('In .then(), val = ' + val); 40 + // Now do a sync call with function arg 41 + const result = obj2.methodA(() => 'fromPromise'); 42 + Ant.println('After sync call: ' + result); 43 + }); 44 + 45 + Ant.println('\n=== Test 3: Multiple objects interleaved ==='); 46 + obj1.methodA(() => { 47 + obj2.methodB('nested'); 48 + return 'test'; 49 + }); 50 + 51 + Ant.println('\n=== All tests done ===');