From 80156ec649db16746faa54f70f4bacb7745b8a92 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Wed, 10 Dec 2025 14:16:58 -0800 Subject: [PATCH] var scope hoist --- meson.build | 2 +- src/ant.c | 67 ++++++++++++++++++++++++- tests/ack.js | 8 +++ tests/test_var_hoisting.cjs | 97 +++++++++++++++++++++++++++++++++++++ stupid.js => tests/with.js | 0 5 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 tests/ack.js create mode 100644 tests/test_var_hoisting.cjs rename stupid.js => tests/with.js (100%) diff --git a/meson.build b/meson.build index 70c68c6..f23ac37 100644 --- a/meson.build +++ b/meson.build @@ -74,7 +74,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.8.11') +version_conf.set('ANT_VERSION', '0.0.8.12') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index 9dada94..fd8c805 100644 --- a/src/ant.c +++ b/src/ant.c @@ -319,6 +319,7 @@ static jsval_t js_arrow_func(struct js *js, jsoff_t params_start, jsoff_t params static jsval_t js_while(struct js *js); static jsval_t js_do_while(struct js *js); static jsval_t js_block_or_stmt(struct js *js); +static jsval_t js_var_decl(struct js *js); static bool parse_func_params(struct js *js, uint8_t *flags); static jsval_t js_regex_literal(struct js *js); static jsval_t js_try(struct js *js); @@ -5401,9 +5402,11 @@ static jsval_t js_for(struct js *js) { bool is_for_of = false; jsoff_t var_name_off = 0, var_name_len = 0; bool is_const_var = false; + bool is_var_decl = false; if (next(js) == TOK_LET || next(js) == TOK_CONST || next(js) == TOK_VAR) { if (js->tok == TOK_VAR) { + is_var_decl = true; if ((js->flags & F_STRICT) && !js->var_warning_shown) { fprintf(stderr, "Warning: 'var' is deprecated, use 'let' or 'const' instead\n"); js->var_warning_shown = true; @@ -5426,6 +5429,8 @@ static jsval_t js_for(struct js *js) { js->consumed = 1; if (is_const_var) { v = js_const(js); + } else if (is_var_decl) { + v = js_var_decl(js); } else { v = js_let(js); } @@ -6804,12 +6809,72 @@ static void js_throw_handle(struct js *js, jsval_t *res) { } } +static jsval_t find_var_scope(struct js *js) { + if ((js->flags & F_CALL) && global_scope_stack && utarray_len(global_scope_stack) > 0) { + jsoff_t *scope_off = (jsoff_t *)utarray_eltptr(global_scope_stack, 0); + if (scope_off && *scope_off != 0) return mkval(T_OBJ, *scope_off); + } + + jsval_t scope = js->scope; + while (vdata(upper(js, scope)) != 0) { + scope = upper(js, scope); + } + return scope; +} + +static jsval_t js_var_decl(struct js *js) { + uint8_t exe = !(js->flags & F_NOEXEC); + jsval_t var_scope = find_var_scope(js); + + js->consumed = 1; + for (;;) { + EXPECT(TOK_IDENTIFIER, ); + js->consumed = 0; + jsoff_t noff = js->toff, nlen = js->tlen; + char *name = (char *) &js->code[noff]; + + if (exe && (js->flags & F_STRICT) && is_strict_restricted(name, nlen)) { + return js_mkerr(js, "cannot use '%.*s' as variable name in strict mode", (int) nlen, name); + } + + if (exe && (js->flags & F_STRICT) && is_strict_reserved(name, nlen)) { + return js_mkerr(js, "'%.*s' is reserved in strict mode", (int) nlen, name); + } + + jsval_t v = js_mkundef(); + js->consumed = 1; + if (next(js) == TOK_ASSIGN) { + js->consumed = 1; + v = js_expr(js); + if (is_err(v)) return v; + } + + if (exe) { + jsoff_t existing_off = lkp(js, var_scope, name, nlen); + if (existing_off > 0) { + jsval_t key_val = js_mkstr(js, name, nlen); + if (!is_err(v) && vtype(v) != T_UNDEF) { + setprop(js, var_scope, key_val, resolveprop(js, v)); + } + } else { + jsval_t x = mkprop(js, var_scope, js_mkstr(js, name, nlen), resolveprop(js, v), false); + if (is_err(x)) return x; + } + } + + if (next(js) == TOK_SEMICOLON || next(js) == TOK_EOF) break; + EXPECT(TOK_COMMA, ); + } + return js_mkundef(); +} + static void js_var(struct js *js, jsval_t *res) { if ((js->flags & F_STRICT) && !js->var_warning_shown) { fprintf(stderr, "Warning: 'var' is deprecated, use 'let' or 'const' instead\n"); js->var_warning_shown = true; } - *res = js_let(js); + + *res = js_var_decl(js); } static void js_async(struct js *js, jsval_t *res) { diff --git a/tests/ack.js b/tests/ack.js new file mode 100644 index 0000000..196b67c --- /dev/null +++ b/tests/ack.js @@ -0,0 +1,8 @@ +function ack(i, j) { + console.log('ack(' + i + ', ' + j + ')'); + if (i == 0) return j + 1; + if (j == 0) return ack(i - 1, 1); + return ack(i - 1, ack(i, j - 1)); +} + +console.log(ack(3, 3)); diff --git a/tests/test_var_hoisting.cjs b/tests/test_var_hoisting.cjs new file mode 100644 index 0000000..02b6390 --- /dev/null +++ b/tests/test_var_hoisting.cjs @@ -0,0 +1,97 @@ +// Test var hoisting behavior +// In JavaScript, 'var' declarations are hoisted to the function scope (or global scope) +// regardless of where they are declared within blocks + +console.log("=== Test 1: Basic var hoisting in blocks ==="); +{ + var x = 10; +} +console.log("x should be 10:", x); + +console.log("\n=== Test 2: var in nested blocks ==="); +{ + { + { + var y = 20; + } + } +} +console.log("y should be 20:", y); + +console.log("\n=== Test 3: var in if blocks ==="); +if (true) { + var z = 30; +} +console.log("z should be 30:", z); + +console.log("\n=== Test 4: var in function scope ==="); +function testFunc() { + { + var a = 40; + } + return a; +} +console.log("a inside function should be 40:", testFunc()); + +console.log("\n=== Test 5: var in for loop ==="); +for (var i = 0; i < 3; i++) { + // loop body +} +console.log("i should be 3:", i); + +console.log("\n=== Test 6: var in with statement ==="); +var obj = { prop: 100 }; +with (obj) { + var w = 50; +} +console.log("w should be 50:", w); + +console.log("\n=== Test 7: Multiple var declarations in different blocks ==="); +{ + var m = 1; +} +{ + var n = 2; +} +console.log("m should be 1, n should be 2:", m, n); + +console.log("\n=== Test 8: var reassignment across blocks ==="); +{ + var p = 100; +} +{ + p = 200; +} +console.log("p should be 200:", p); + +console.log("\n=== Test 9: var in while loop ==="); +var count = 0; +while (count < 2) { + var q = count; + count++; +} +console.log("q should be 1:", q); + +console.log("\n=== Test 10: Function scope isolation ==="); +function outer() { + { + var funcVar = 77; + } + return funcVar; +} +console.log("funcVar inside function should be 77:", outer()); +// funcVar should not be accessible here (would be undefined in global scope) + +console.log("\n=== Test 11: var vs let comparison ==="); +{ + var varTest = "var-value"; + let letTest = "let-value"; +} +console.log("varTest should be 'var-value':", varTest); +try { + console.log("letTest should cause error:", letTest); +} catch (e) { + console.log("letTest correctly not accessible (block-scoped)"); +} + +console.log("\n=== All var hoisting tests completed ==="); diff --git a/stupid.js b/tests/with.js similarity index 100% rename from stupid.js rename to tests/with.js -- 2.51.2