From 8db95c654bba3c8f57d1313844c9f6fed0d77e00 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 6 Feb 2026 03:00:46 -0600 Subject: [PATCH] more info in AST parser --- source/quickjs.c | 455 ++++++++++++++++++--------------------------- vm_test/syntax.txt | 16 -- 2 files changed, 180 insertions(+), 291 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 769bb244..320b8d24 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -8535,9 +8535,6 @@ enum { TOK_FOR, TOK_BREAK, TOK_CONTINUE, - TOK_SWITCH, - TOK_CASE, - TOK_DEFAULT, TOK_THROW, TOK_TRY, TOK_CATCH, @@ -8644,9 +8641,6 @@ static const char *ast_token_kind_str(int token_val) { case TOK_FOR: return "for"; case TOK_BREAK: return "break"; case TOK_CONTINUE: return "continue"; - case TOK_SWITCH: return "switch"; - case TOK_CASE: return "case"; - case TOK_DEFAULT: return "default"; case TOK_THROW: return "throw"; case TOK_TRY: return "try"; case TOK_CATCH: return "catch"; @@ -9801,9 +9795,6 @@ static const JSKeywordEntry js_keywords[] = { { "for", TOK_FOR, FALSE }, { "break", TOK_BREAK, FALSE }, { "continue", TOK_CONTINUE, FALSE }, - { "switch", TOK_SWITCH, FALSE }, - { "case", TOK_CASE, FALSE }, - { "default", TOK_DEFAULT, FALSE }, { "throw", TOK_THROW, FALSE }, { "try", TOK_TRY, FALSE }, { "catch", TOK_CATCH, FALSE }, @@ -13600,89 +13591,6 @@ static __exception int js_parse_statement_or_decl (JSParseState *s, } if (js_parse_expect_semi (s)) goto fail; } break; - case TOK_SWITCH: { - int label_case, label_break, label1; - int default_label_pos; - BlockEnv break_entry; - - if (next_token (s)) goto fail; - - if (js_parse_expr_paren (s)) goto fail; - - push_scope (s); - label_break = new_label (s); - push_break_entry (s->cur_func, &break_entry, label_name, label_break, -1, 1); - - if (js_parse_expect (s, '{')) goto fail; - - default_label_pos = -1; - label_case = -1; - while (s->token.val != '}') { - if (s->token.val == TOK_CASE) { - label1 = -1; - if (label_case >= 0) { - /* skip the case if needed */ - label1 = emit_goto (s, OP_goto, -1); - } - emit_label (s, label_case); - label_case = -1; - for (;;) { - /* parse a sequence of case clauses */ - if (next_token (s)) goto fail; - emit_op (s, OP_dup); - if (js_parse_expr (s)) goto fail; - if (js_parse_expect (s, ':')) goto fail; - emit_op (s, OP_strict_eq); - if (s->token.val == TOK_CASE) { - label1 = emit_goto (s, OP_if_true, label1); - } else { - label_case = emit_goto (s, OP_if_false, -1); - emit_label (s, label1); - break; - } - } - } else if (s->token.val == TOK_DEFAULT) { - if (next_token (s)) goto fail; - if (js_parse_expect (s, ':')) goto fail; - if (default_label_pos >= 0) { - js_parse_error (s, "duplicate default"); - goto fail; - } - if (label_case < 0) { - /* falling thru direct from switch expression */ - label_case = emit_goto (s, OP_goto, -1); - } - /* Emit a dummy label opcode. Label will be patched after - the end of the switch body. Do not use emit_label(s, 0) - because it would clobber label 0 address, preventing - proper optimizer operation. - */ - emit_op (s, OP_label); - emit_u32 (s, 0); - default_label_pos = s->cur_func->byte_code.size - 4; - } else { - if (label_case < 0) { - /* falling thru direct from switch expression */ - js_parse_error (s, "invalid switch statement"); - goto fail; - } - if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) goto fail; - } - } - if (js_parse_expect (s, '}')) goto fail; - if (default_label_pos >= 0) { - /* Ugly patch for the the `default` label, shameful and risky */ - put_u32 (s->cur_func->byte_code.buf + default_label_pos, label_case); - s->cur_func->label_slots[label_case].pos = default_label_pos + 4; - } else { - emit_label (s, label_case); - } - emit_label (s, label_break); - emit_op (s, OP_drop); /* drop the switch expression */ - - pop_break_entry (s->cur_func); - pop_scope (s); - } break; case TOK_TRY: { int label_catch, label_catch2, label_finally, label_end; JSValue name; @@ -16658,7 +16566,6 @@ static __exception int js_parse_directives (JSParseState *s) { case TOK_DO: case TOK_WHILE: case TOK_FOR: - case TOK_SWITCH: case TOK_THROW: case TOK_TRY: case TOK_FUNCTION: @@ -28235,7 +28142,6 @@ redo: else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; - else if (len == 4 && !memcmp (start, "case", 4)) s->token_val = TOK_CASE; else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; @@ -28243,8 +28149,6 @@ redo: else if (len == 5 && !memcmp (start, "catch", 5)) s->token_val = TOK_CATCH; else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; - else if (len == 6 && !memcmp (start, "switch", 6)) s->token_val = TOK_SWITCH; - else if (len == 7 && !memcmp (start, "default", 7)) s->token_val = TOK_DEFAULT; else if (len == 7 && !memcmp (start, "finally", 7)) s->token_val = TOK_FINALLY; else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; @@ -28561,7 +28465,6 @@ static int tokenize_next (ASTParseState *s) { else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; - else if (len == 4 && !memcmp (start, "case", 4)) s->token_val = TOK_CASE; else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; @@ -28569,8 +28472,6 @@ static int tokenize_next (ASTParseState *s) { else if (len == 5 && !memcmp (start, "catch", 5)) s->token_val = TOK_CATCH; else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; - else if (len == 6 && !memcmp (start, "switch", 6)) s->token_val = TOK_SWITCH; - else if (len == 7 && !memcmp (start, "default", 7)) s->token_val = TOK_DEFAULT; else if (len == 7 && !memcmp (start, "finally", 7)) s->token_val = TOK_FINALLY; else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; @@ -29629,7 +29530,7 @@ static void ast_sync_to_statement (ASTParseState *s) { return; /* don't consume - let caller handle */ case TOK_VAR: case TOK_DEF: case TOK_IF: case TOK_WHILE: case TOK_FOR: case TOK_RETURN: case TOK_THROW: case TOK_TRY: - case TOK_SWITCH: case TOK_FUNCTION: case TOK_BREAK: + case TOK_FUNCTION: case TOK_BREAK: case TOK_CONTINUE: case TOK_DO: return; /* statement-starting keyword found */ default: @@ -29868,72 +29769,6 @@ static cJSON *ast_parse_statement (ASTParseState *s) { ast_node_end (s, node, s->buf_ptr); } break; - case TOK_SWITCH: { - node = ast_node (s, "switch", start); - ast_next_token (s); - if (s->token_val == '(') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected '(' before switch expression"); - cJSON *expr = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", expr); - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ')' after switch expression"); - - cJSON *cases = cJSON_AddArrayToObject (node, "cases"); - if (s->token_val == '{') { - int has_default = 0; - ast_next_token (s); - while (s->token_val != '}' && s->token_val != TOK_EOF) { - if (s->token_val == TOK_CASE) { - cJSON *case_node = ast_node (s, "case", s->token_ptr); - ast_next_token (s); - cJSON *case_expr = ast_parse_expr (s); - cJSON_AddItemToObject (case_node, "expression", case_expr); - if (s->token_val == ':') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ':' after case expression"); - cJSON *case_stmts = cJSON_AddArrayToObject (case_node, "statements"); - while (s->token_val != TOK_CASE && s->token_val != TOK_DEFAULT && - s->token_val != '}' && s->token_val != TOK_EOF) { - const uint8_t *before = s->token_ptr; - cJSON *stmt = ast_parse_statement (s); - if (stmt) { - cJSON_AddItemToArray (case_stmts, stmt); - } else if (s->token_ptr == before) { - ast_sync_to_statement (s); - } - } - ast_node_end (s, case_node, s->buf_ptr); - cJSON_AddItemToArray (cases, case_node); - } else if (s->token_val == TOK_DEFAULT) { - if (has_default) - ast_error (s, s->token_ptr, "more than one default clause in switch"); - has_default = 1; - cJSON *default_node = ast_node (s, "default", s->token_ptr); - ast_next_token (s); - if (s->token_val == ':') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ':' after default"); - cJSON *default_stmts = cJSON_AddArrayToObject (default_node, "statements"); - while (s->token_val != TOK_CASE && s->token_val != '}' && s->token_val != TOK_EOF) { - const uint8_t *before = s->token_ptr; - cJSON *stmt = ast_parse_statement (s); - if (stmt) { - cJSON_AddItemToArray (default_stmts, stmt); - } else if (s->token_ptr == before) { - ast_sync_to_statement (s); - } - } - ast_node_end (s, default_node, s->buf_ptr); - cJSON_AddItemToArray (cases, default_node); - } else { - break; - } - } - if (s->token_val == '}') ast_next_token (s); - } else { - ast_error (s, s->token_ptr, "expected '{' after switch"); - } - ast_node_end (s, node, s->buf_ptr); - } break; - case TOK_TRY: { node = ast_node (s, "try", start); ast_next_token (s); @@ -30077,20 +29912,28 @@ static cJSON *ast_parse_program (ASTParseState *s) { typedef struct ASTSemVar { const char *name; - int is_const; /* 1 for def, function args; 0 for var */ + int is_const; + const char *make; /* "def", "var", "function", "input" */ + int function_nr; /* which function this var belongs to */ + int nr_uses; /* reference count */ + int closure; /* 1 if used by inner function */ } ASTSemVar; typedef struct ASTSemScope { struct ASTSemScope *parent; ASTSemVar vars[AST_SEM_MAX_VARS]; int var_count; - int in_loop; /* inside a loop (while, for, do) */ - int in_switch; /* inside a switch */ + int in_loop; + int function_nr; /* function_nr of enclosing function */ + int is_function_scope; /* 1 if this is a function's top-level scope */ } ASTSemScope; typedef struct ASTSemState { cJSON *errors; int has_error; + cJSON *scopes_array; + const char *intrinsics[256]; + int intrinsic_count; } ASTSemState; static void ast_sem_error (ASTSemState *st, cJSON *node, const char *fmt, ...) { @@ -30114,22 +29957,79 @@ static void ast_sem_error (ASTSemState *st, cJSON *node, const char *fmt, ...) { st->has_error = 1; } -static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const) { +static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const, + const char *make, int function_nr) { if (scope->var_count < AST_SEM_MAX_VARS) { - scope->vars[scope->var_count].name = name; - scope->vars[scope->var_count].is_const = is_const; + ASTSemVar *v = &scope->vars[scope->var_count]; + v->name = name; + v->is_const = is_const; + v->make = make; + v->function_nr = function_nr; + v->nr_uses = 0; + v->closure = 0; scope->var_count++; } } -static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) { +typedef struct { + ASTSemVar *var; + int level; + int def_function_nr; +} ASTSemLookup; + +static ASTSemLookup ast_sem_lookup_var (ASTSemScope *scope, const char *name) { + ASTSemLookup result = {NULL, 0, -1}; + int cur_fn = scope->function_nr; for (ASTSemScope *s = scope; s; s = s->parent) { for (int i = 0; i < s->var_count; i++) { - if (strcmp (s->vars[i].name, name) == 0) - return &s->vars[i]; + if (strcmp (s->vars[i].name, name) == 0) { + result.var = &s->vars[i]; + result.def_function_nr = s->vars[i].function_nr; + return result; + } + } + /* When crossing into a parent with a different function_nr, increment level */ + if (s->parent && s->parent->function_nr != cur_fn) { + result.level++; + cur_fn = s->parent->function_nr; } } - return NULL; + return result; +} + +static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) { + ASTSemLookup r = ast_sem_lookup_var (scope, name); + return r.var; +} + +static void ast_sem_add_intrinsic (ASTSemState *st, const char *name) { + for (int i = 0; i < st->intrinsic_count; i++) { + if (strcmp (st->intrinsics[i], name) == 0) return; + } + if (st->intrinsic_count < 256) { + st->intrinsics[st->intrinsic_count++] = name; + } +} + +static cJSON *ast_sem_build_scope_record (ASTSemScope *scope, int *nr_slots, int *nr_close) { + cJSON *rec = cJSON_CreateObject (); + cJSON_AddNumberToObject (rec, "function_nr", scope->function_nr); + int slots = 0, close_slots = 0; + for (int i = 0; i < scope->var_count; i++) { + ASTSemVar *v = &scope->vars[i]; + cJSON *entry = cJSON_CreateObject (); + cJSON_AddStringToObject (entry, "make", v->make); + cJSON_AddNumberToObject (entry, "function_nr", v->function_nr); + cJSON_AddNumberToObject (entry, "nr_uses", v->nr_uses); + cJSON_AddBoolToObject (entry, "closure", v->closure); + cJSON_AddNumberToObject (entry, "level", 0); + cJSON_AddItemToObject (rec, v->name, entry); + slots++; + if (v->closure) close_slots++; + } + *nr_slots = slots; + *nr_close = close_slots; + return rec; } static int ast_sem_in_loop (ASTSemScope *scope) { @@ -30139,13 +30039,6 @@ static int ast_sem_in_loop (ASTSemScope *scope) { return 0; } -static int ast_sem_in_loop_or_switch (ASTSemScope *scope) { - for (ASTSemScope *s = scope; s; s = s->parent) { - if (s->in_loop || s->in_switch) return 1; - } - return 0; -} - static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr); static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt); @@ -30270,14 +30163,24 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr /* Function expression / arrow function — create new scope */ if (strcmp (kind, "function") == 0) { + cJSON *fn_nr_node = cJSON_GetObjectItem (expr, "function_nr"); + int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; + ASTSemScope fn_scope = {0}; fn_scope.parent = scope; + fn_scope.function_nr = fn_nr; + fn_scope.is_function_scope = 1; - /* Add parameters as const */ + cJSON_AddNumberToObject (expr, "outer", scope->function_nr); + + /* Add parameters as input */ cJSON *param; cJSON_ArrayForEach (param, cJSON_GetObjectItem (expr, "list")) { const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); - if (pname) ast_sem_add_var (&fn_scope, pname, 1); + if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); + /* Check default value expressions */ + cJSON *def_val = cJSON_GetObjectItem (param, "default"); + if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); } /* Check function body */ @@ -30285,6 +30188,13 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) { ast_sem_check_stmt (st, &fn_scope, stmt); } + + /* Build scope record and attach to scopes array */ + int nr_slots, nr_close; + cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); + cJSON_AddItemToArray (st->scopes_array, rec); + cJSON_AddNumberToObject (expr, "nr_slots", nr_slots); + cJSON_AddNumberToObject (expr, "nr_close_slots", nr_close); return; } @@ -30297,7 +30207,25 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr return; } - /* name, number, string, regexp, null, true, false, this — leaf nodes, no check needed */ + /* Name token — annotate with level and function_nr */ + if (strcmp (kind, "name") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "name")); + if (name) { + ASTSemLookup r = ast_sem_lookup_var (scope, name); + if (r.var) { + cJSON_AddNumberToObject (expr, "level", r.level); + cJSON_AddNumberToObject (expr, "function_nr", r.def_function_nr); + r.var->nr_uses++; + if (r.level > 0) r.var->closure = 1; + } else { + cJSON_AddNumberToObject (expr, "level", -1); + ast_sem_add_intrinsic (st, name); + } + } + return; + } + + /* number, string, regexp, null, true, false, this — leaf nodes, no check needed */ } /* Check a statement for semantic errors */ @@ -30315,7 +30243,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt if (existing && existing->is_const) { ast_sem_error (st, left, "cannot redeclare constant '%s'", name); } - ast_sem_add_var (scope, name, 0); + ast_sem_add_var (scope, name, 0, "var", scope->function_nr); } ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right")); return; @@ -30332,7 +30260,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt } else if (existing) { ast_sem_error (st, left, "cannot redeclare '%s' as constant", name); } - ast_sem_add_var (scope, name, 1); + ast_sem_add_var (scope, name, 1, "def", scope->function_nr); } ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right")); return; @@ -30363,6 +30291,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt ASTSemScope loop_scope = {0}; loop_scope.parent = scope; loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) { ast_sem_check_stmt (st, &loop_scope, s2); @@ -30374,6 +30303,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt ASTSemScope loop_scope = {0}; loop_scope.parent = scope; loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) { ast_sem_check_stmt (st, &loop_scope, s2); @@ -30386,6 +30316,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt ASTSemScope loop_scope = {0}; loop_scope.parent = scope; loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; /* init may be a var/def statement or expression */ cJSON *init = cJSON_GetObjectItem (stmt, "init"); if (init) { @@ -30405,22 +30336,6 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt return; } - if (strcmp (kind, "switch") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression")); - ASTSemScope sw_scope = {0}; - sw_scope.parent = scope; - sw_scope.in_switch = 1; - cJSON *c; - cJSON_ArrayForEach (c, cJSON_GetObjectItem (stmt, "cases")) { - ast_sem_check_expr (st, &sw_scope, cJSON_GetObjectItem (c, "expression")); - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItem (c, "statements")) { - ast_sem_check_stmt (st, &sw_scope, s2); - } - } - return; - } - if (strcmp (kind, "return") == 0 || strcmp (kind, "throw") == 0 || strcmp (kind, "go") == 0) { ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression")); @@ -30428,8 +30343,8 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt } if (strcmp (kind, "break") == 0) { - if (!ast_sem_in_loop_or_switch (scope)) { - ast_sem_error (st, stmt, "'break' used outside of loop or switch"); + if (!ast_sem_in_loop (scope)) { + ast_sem_error (st, stmt, "'break' used outside of loop"); } return; } @@ -30450,8 +30365,9 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt if (catch_node) { ASTSemScope catch_scope = {0}; catch_scope.parent = scope; + catch_scope.function_nr = scope->function_nr; const char *catch_name = cJSON_GetStringValue (cJSON_GetObjectItem (catch_node, "name")); - if (catch_name) ast_sem_add_var (&catch_scope, catch_name, 0); + if (catch_name) ast_sem_add_var (&catch_scope, catch_name, 0, "var", scope->function_nr); cJSON_ArrayForEach (s2, cJSON_GetObjectItem (catch_node, "statements")) { ast_sem_check_stmt (st, &catch_scope, s2); } @@ -30468,6 +30384,7 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt if (strcmp (kind, "block") == 0) { ASTSemScope block_scope = {0}; block_scope.parent = scope; + block_scope.function_nr = scope->function_nr; cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) { ast_sem_check_stmt (st, &block_scope, s2); @@ -30483,35 +30400,57 @@ static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt if (strcmp (kind, "function") == 0) { /* Function declaration — register name, then check body in new scope */ const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name")); - if (name) ast_sem_add_var (scope, name, 0); + if (name) ast_sem_add_var (scope, name, 0, "function", scope->function_nr); + + cJSON *fn_nr_node = cJSON_GetObjectItem (stmt, "function_nr"); + int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; ASTSemScope fn_scope = {0}; fn_scope.parent = scope; + fn_scope.function_nr = fn_nr; + fn_scope.is_function_scope = 1; + + cJSON_AddNumberToObject (stmt, "outer", scope->function_nr); cJSON *param; cJSON_ArrayForEach (param, cJSON_GetObjectItem (stmt, "list")) { const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); - if (pname) ast_sem_add_var (&fn_scope, pname, 1); + if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); + /* Check default value expressions */ + cJSON *def_val = cJSON_GetObjectItem (param, "default"); + if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); } cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) { ast_sem_check_stmt (st, &fn_scope, s2); } + + /* Build scope record and attach to scopes array */ + int nr_slots, nr_close; + cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); + cJSON_AddItemToArray (st->scopes_array, rec); + cJSON_AddNumberToObject (stmt, "nr_slots", nr_slots); + cJSON_AddNumberToObject (stmt, "nr_close_slots", nr_close); return; } } /* Run the semantic pass on a parsed AST, adding errors to the AST */ -static void ast_semantic_check (cJSON *ast, cJSON **errors_out) { +static void ast_semantic_check (cJSON *ast, cJSON **errors_out, + cJSON **scopes_out, cJSON **intrinsics_out) { ASTSemState st = {0}; + st.scopes_array = cJSON_CreateArray (); + ASTSemScope global_scope = {0}; + global_scope.function_nr = 0; + global_scope.is_function_scope = 1; /* Process top-level function declarations first (they are hoisted) */ cJSON *stmt; cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (ast, "functions")) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name")); - if (name) ast_sem_add_var (&global_scope, name, 0); + if (name) ast_sem_add_var (&global_scope, name, 0, "function", 0); } /* Check all statements (var/def are registered as they are encountered) */ @@ -30524,7 +30463,28 @@ static void ast_semantic_check (cJSON *ast, cJSON **errors_out) { ast_sem_check_stmt (&st, &global_scope, stmt); } + /* Build program scope record (function_nr 0) and prepend to scopes array */ + int nr_slots, nr_close; + cJSON *prog_rec = ast_sem_build_scope_record (&global_scope, &nr_slots, &nr_close); + /* Prepend: detach all children, add prog_rec, re-add children */ + cJSON *existing = st.scopes_array->child; + st.scopes_array->child = NULL; + cJSON_AddItemToArray (st.scopes_array, prog_rec); + if (existing) { + cJSON *last = prog_rec; + last->next = existing; + existing->prev = last; + } + + /* Build intrinsics array */ + cJSON *intr_arr = cJSON_CreateArray (); + for (int i = 0; i < st.intrinsic_count; i++) { + cJSON_AddItemToArray (intr_arr, cJSON_CreateString (st.intrinsics[i])); + } + *errors_out = st.errors; + *scopes_out = st.scopes_array; + *intrinsics_out = intr_arr; } char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename) { @@ -30536,7 +30496,7 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena s.buf_start = (const uint8_t *)source; s.buf_ptr = (const uint8_t *)source; s.buf_end = (const uint8_t *)source + len; - s.function_nr = 0; + s.function_nr = 1; s.errors = NULL; s.has_error = 0; @@ -30552,7 +30512,13 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena /* Run semantic pass */ cJSON *sem_errors = NULL; - ast_semantic_check (ast, &sem_errors); + cJSON *scopes = NULL; + cJSON *intrinsics = NULL; + ast_semantic_check (ast, &sem_errors, &scopes, &intrinsics); + + /* Attach scopes and intrinsics to AST */ + if (scopes) cJSON_AddItemToObject (ast, "scopes", scopes); + if (intrinsics) cJSON_AddItemToObject (ast, "intrinsics", intrinsics); /* Merge parse errors and semantic errors */ if (s.errors && sem_errors) { @@ -32097,7 +32063,7 @@ static void mach_gen_statement (MachGenState *s, cJSON *stmt) { if (s->loop_break) { mach_emit_jump (s, s->loop_break); } else { - mach_error (s, stmt, "'break' used outside of loop or switch"); + mach_error (s, stmt, "'break' used outside of loop"); } return; } @@ -32112,67 +32078,6 @@ static void mach_gen_statement (MachGenState *s, cJSON *stmt) { return; } - /* Switch statement */ - if (strcmp (kind, "switch") == 0) { - cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); - cJSON *cases = cJSON_GetObjectItem (stmt, "cases"); - - int switch_val = mach_gen_expr (s, expr); - char *end_label = mach_gen_label (s, "switch_end"); - char *default_label = NULL; - - const char *old_break = s->loop_break; - s->loop_break = end_label; - - /* First pass: generate case tests and jumps */ - cJSON *case_node; - cJSON_ArrayForEach (case_node, cases) { - const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "kind")); - if (strcmp (case_kind, "default") == 0) { - default_label = mach_gen_label (s, "switch_default"); - } else { - char *case_label = mach_gen_label (s, "switch_case"); - cJSON *case_expr = cJSON_GetObjectItem (case_node, "expression"); - int case_val = mach_gen_expr (s, case_expr); - int cmp_slot = mach_alloc_slot (s); - mach_emit_3 (s, "eq", cmp_slot, switch_val, case_val); - mach_emit_jump_cond (s, "jump_true", cmp_slot, case_label); - /* Store label in case node for second pass */ - cJSON_AddStringToObject (case_node, "_label", case_label); - sys_free (case_label); - } - } - - /* Jump to default or end */ - if (default_label) { - mach_emit_jump (s, default_label); - } else { - mach_emit_jump (s, end_label); - } - - /* Second pass: generate case bodies */ - cJSON_ArrayForEach (case_node, cases) { - const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "kind")); - if (strcmp (case_kind, "default") == 0) { - mach_emit_label (s, default_label); - sys_free (default_label); - } else { - const char *label = cJSON_GetStringValue (cJSON_GetObjectItem (case_node, "_label")); - mach_emit_label (s, label); - } - cJSON *case_stmts = cJSON_GetObjectItem (case_node, "statements"); - cJSON *child; - cJSON_ArrayForEach (child, case_stmts) { - mach_gen_statement (s, child); - } - } - - mach_emit_label (s, end_label); - s->loop_break = old_break; - sys_free (end_label); - return; - } - /* Try-catch-finally */ if (strcmp (kind, "try") == 0) { cJSON *try_stmts = cJSON_GetObjectItem (stmt, "statements"); diff --git a/vm_test/syntax.txt b/vm_test/syntax.txt index 437fb271..74bf51e1 100644 --- a/vm_test/syntax.txt +++ b/vm_test/syntax.txt @@ -252,22 +252,6 @@ loop: for (var m = 0; m < 3; m++) { } } -// --- switch/case/default --- -var sw -switch (2) { - case 1: - sw = "one" - break - case 2: - sw = "two" - break - case 3: - sw = "three" - break - default: - sw = "other" -} - // --- try/catch/finally --- var tc try {