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.

add function declaration hoisting support

+298 -9
+1 -1
meson.build
··· 75 75 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 76 76 77 77 version_conf = configuration_data() 78 - version_conf.set('ANT_VERSION', '0.2.0.4') 78 + version_conf.set('ANT_VERSION', '0.2.0.5') 79 79 version_conf.set('ANT_GIT_HASH', git_hash) 80 80 version_conf.set('ANT_BUILD_DATE', build_date) 81 81
+83 -8
src/ant.c
··· 286 286 bool had_newline; // true if newline was crossed before current token 287 287 jsval_t thrown_value; // stores the actual thrown value for catch blocks 288 288 bool has_descriptors; // true if defineProperty has ever been called 289 + bool is_hoisting; // true during function declaration hoisting pass 289 290 }; 290 291 291 292 enum { ··· 3766 3767 } 3767 3768 } 3768 3769 3770 + static jsval_t js_func_decl(struct js *js); 3771 + static jsval_t js_func_decl_async(struct js *js); 3772 + 3773 + static void hoist_function_declarations(struct js *js) { 3774 + if (js->flags & F_NOEXEC) return; 3775 + if (js->is_hoisting) return; 3776 + 3777 + js->is_hoisting = true; 3778 + 3779 + js_parse_state_t saved; 3780 + JS_SAVE_STATE(js, saved); 3781 + jsval_t saved_scope = js->scope; 3782 + 3783 + int depth = 0; 3784 + uint8_t tok; 3785 + 3786 + while ((tok = next(js)) != TOK_EOF && !(tok == TOK_RBRACE && depth == 0)) { 3787 + if (tok == TOK_LBRACE) { depth++; js->consumed = 1; continue; } 3788 + if (tok == TOK_RBRACE) { depth--; js->consumed = 1; continue; } 3789 + 3790 + if (depth == 0 && tok == TOK_EXPORT) { 3791 + js->consumed = 1; 3792 + uint8_t next_tok = next(js); 3793 + if (next_tok != TOK_FUNC && next_tok != TOK_ASYNC && next_tok != TOK_DEFAULT) 3794 + goto skip_export; 3795 + 3796 + int brace_depth = 0; 3797 + while (next(js) != TOK_EOF) { 3798 + if (js->tok == TOK_LBRACE) brace_depth++; 3799 + else if (js->tok == TOK_RBRACE && --brace_depth <= 0) break; 3800 + js->consumed = 1; 3801 + } 3802 + 3803 + skip_export: 3804 + continue; 3805 + } 3806 + 3807 + if (depth == 0 && tok == TOK_FUNC) { 3808 + jsoff_t after_func = js->pos; 3809 + js->consumed = 1; 3810 + if (next(js) == TOK_IDENTIFIER) { 3811 + js->pos = after_func; 3812 + js->tok = TOK_FUNC; 3813 + js->consumed = 1; 3814 + js_func_decl(js); 3815 + } 3816 + continue; 3817 + } 3818 + 3819 + if (depth == 0 && tok == TOK_ASYNC) { 3820 + js->consumed = 1; 3821 + if (next(js) == TOK_FUNC) { 3822 + js_func_decl_async(js); 3823 + } 3824 + continue; 3825 + } 3826 + 3827 + js->consumed = 1; 3828 + } 3829 + 3830 + js->is_hoisting = false; 3831 + JS_RESTORE_STATE(js, saved); 3832 + js->scope = saved_scope; 3833 + } 3834 + 3769 3835 static jsval_t js_block(struct js *js, bool create_scope) { 3770 3836 jsval_t res = js_mkundef(); 3771 3837 if (create_scope) mkscope(js); 3772 3838 js->consumed = 1; 3839 + hoist_function_declarations(js); 3773 3840 uint8_t peek; 3774 3841 while ((peek = next(js)) != TOK_EOF && peek != TOK_RBRACE && !is_err(res)) { 3775 3842 uint8_t t = js->tok; ··· 8601 8668 if (is_err(proto_setup)) return proto_setup; 8602 8669 8603 8670 if (exe) { 8604 - if (lkp(js, js->scope, name, nlen) > 0) 8605 - return js_mkerr(js, "'%.*s' already declared", (int) nlen, name); 8606 - jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, false); 8607 - if (is_err(x)) return x; 8671 + jsoff_t existing = lkp(js, js->scope, name, nlen); 8672 + if (existing > 0) { 8673 + saveval(js, existing + sizeof(jsoff_t) * 2, func); 8674 + } else { 8675 + jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, false); 8676 + if (is_err(x)) return x; 8677 + } 8608 8678 } 8609 8679 8610 8680 return js_mkundef(); ··· 8663 8733 if (is_err(proto_setup)) return proto_setup; 8664 8734 8665 8735 if (exe) { 8666 - if (lkp(js, js->scope, name, nlen) > 0) 8667 - return js_mkerr(js, "'%.*s' already declared", (int) nlen, name); 8668 - jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, false); 8669 - if (is_err(x)) return x; 8736 + jsoff_t existing = lkp(js, js->scope, name, nlen); 8737 + if (existing > 0) { 8738 + saveval(js, existing + sizeof(jsoff_t) * 2, func); 8739 + } else { 8740 + jsval_t x = mkprop(js, js->scope, js_mkstr(js, name, nlen), func, false); 8741 + if (is_err(x)) return x; 8742 + } 8670 8743 } 8671 8744 8672 8745 return js_mkundef(); ··· 20511 20584 mkscope(js); 20512 20585 setprop(js, js->scope, js_mkstr(js, "__strict_eval_scope__", 21), js_mktrue()); 20513 20586 } 20587 + 20588 + hoist_function_declarations(js); 20514 20589 20515 20590 while (next(js) != TOK_EOF && !is_err(res)) { 20516 20591 res = js_stmt(js);
+60
tests/test_block_function_hoisting.cjs
··· 1 + // Test function declaration hoisting inside blocks 2 + // Function declarations should be hoisted to the top of their containing block 3 + 4 + console.log("=== Test 1: Function declaration hoisted in regular block ==="); 5 + { 6 + foo(); 7 + function foo() { 8 + console.log("foo called - hoisted in block"); 9 + } 10 + } 11 + 12 + console.log("\n=== Test 2: Function hoisting in arrow function body ==="); 13 + const test2 = () => { 14 + bar(); 15 + function bar() { 16 + console.log("bar called - hoisted in arrow function"); 17 + } 18 + }; 19 + test2(); 20 + 21 + console.log("\n=== Test 3: Function hoisting in callback ==="); 22 + const arr = [1]; 23 + arr.forEach((x) => { 24 + baz(); 25 + function baz() { 26 + console.log("baz called with", x, "- hoisted in callback"); 27 + } 28 + }); 29 + 30 + console.log("\n=== Test 4: Function hoisting in .map() callback ==="); 31 + const result = [5].map((x) => { 32 + return helper(x); 33 + function helper(n) { 34 + return n * 2; 35 + } 36 + }); 37 + console.log("result should be [10]:", result); 38 + 39 + console.log("\n=== Test 5: Nested function calling before definition ==="); 40 + function outer() { 41 + inner(); 42 + function inner() { 43 + console.log("inner called - hoisted inside outer"); 44 + } 45 + } 46 + outer(); 47 + 48 + console.log("\n=== Test 6: Function modifying outer variable before definition ==="); 49 + function test6() { 50 + let value = 0; 51 + modify(); 52 + console.log("value after modify should be 42:", value); 53 + 54 + function modify() { 55 + value = 42; 56 + } 57 + } 58 + test6(); 59 + 60 + console.log("\n=== All block function hoisting tests completed ===");
+154
tests/test_class_function_hoisting.cjs
··· 1 + // Test class function (method) hoisting behavior 2 + // Class methods can be called regardless of their definition order within the class body 3 + // But classes themselves are NOT hoisted like function declarations 4 + 5 + console.log("=== Test 1: Methods calling other methods defined later ==="); 6 + class Calculator { 7 + calculate(x) { 8 + return this.double(x) + this.triple(x); 9 + } 10 + 11 + double(x) { 12 + return x * 2; 13 + } 14 + 15 + triple(x) { 16 + return x * 3; 17 + } 18 + } 19 + 20 + let calc = new Calculator(); 21 + console.log("calculate(5) should be 25:", calc.calculate(5)); 22 + 23 + console.log("\n=== Test 2: Constructor calling methods defined after it ==="); 24 + class Greeter { 25 + constructor(name) { 26 + this.name = name; 27 + this.greeting = this.makeGreeting(); 28 + } 29 + 30 + makeGreeting() { 31 + return "Hello, " + this.name + "!"; 32 + } 33 + } 34 + 35 + let greeter = new Greeter("World"); 36 + console.log("greeting should be 'Hello, World!':", greeter.greeting); 37 + 38 + console.log("\n=== Test 3: Method calling method defined before constructor ==="); 39 + class Parser { 40 + parse(input) { 41 + return this.validate(input); 42 + } 43 + 44 + constructor() { 45 + this.initialized = true; 46 + } 47 + 48 + validate(input) { 49 + return input.length > 0; 50 + } 51 + } 52 + 53 + let parser = new Parser(); 54 + console.log("parse('test') should be true:", parser.parse("test")); 55 + 56 + console.log("\n=== Test 4: Mutual method references ==="); 57 + class Counter { 58 + increment() { 59 + this.value = this.getValue() + 1; 60 + } 61 + 62 + decrement() { 63 + this.value = this.getValue() - 1; 64 + } 65 + 66 + constructor(initial) { 67 + this.value = initial; 68 + } 69 + 70 + getValue() { 71 + return this.value; 72 + } 73 + } 74 + 75 + let counter = new Counter(10); 76 + counter.increment(); 77 + console.log("after increment should be 11:", counter.getValue()); 78 + counter.decrement(); 79 + counter.decrement(); 80 + console.log("after two decrements should be 9:", counter.getValue()); 81 + 82 + console.log("\n=== Test 5: Deep method call chain ==="); 83 + class Chain { 84 + a() { 85 + return this.b() + 1; 86 + } 87 + 88 + b() { 89 + return this.c() + 2; 90 + } 91 + 92 + c() { 93 + return this.d() + 3; 94 + } 95 + 96 + d() { 97 + return 10; 98 + } 99 + } 100 + 101 + let chain = new Chain(); 102 + console.log("a() should be 16 (10+3+2+1):", chain.a()); 103 + 104 + console.log("\n=== Test 6: Class NOT hoisted (should error if accessed before) ==="); 105 + try { 106 + let early = new NotYetDefined(); 107 + console.log("ERROR: Should not reach here"); 108 + } catch (e) { 109 + console.log("Correctly caught error - class not hoisted"); 110 + } 111 + 112 + class NotYetDefined { 113 + constructor() { 114 + this.value = 42; 115 + } 116 + } 117 + 118 + let late = new NotYetDefined(); 119 + console.log("After definition, value should be 42:", late.value); 120 + 121 + console.log("\n=== Test 7: Static methods hoisting within class ==="); 122 + class StaticTest { 123 + static compute() { 124 + return StaticTest.helper() * 2; 125 + } 126 + 127 + static helper() { 128 + return 21; 129 + } 130 + } 131 + 132 + console.log("StaticTest.compute() should be 42:", StaticTest.compute()); 133 + 134 + console.log("\n=== Test 8: Getter/setter order independence ==="); 135 + class GetSet { 136 + get doubled() { 137 + return this._val * 2; 138 + } 139 + 140 + constructor(val) { 141 + this._val = val; 142 + } 143 + 144 + set doubled(val) { 145 + this._val = val / 2; 146 + } 147 + } 148 + 149 + let gs = new GetSet(5); 150 + console.log("doubled should be 10:", gs.doubled); 151 + gs.doubled = 20; 152 + console.log("after setting doubled=20, _val should be 10:", gs._val); 153 + 154 + console.log("\n=== All class function hoisting tests completed ===");