more info in AST parser

This commit is contained in:
2026-02-06 03:00:46 -06:00
parent c814c0e1d8
commit 8db95c654b
2 changed files with 180 additions and 291 deletions

View File

@@ -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");

View File

@@ -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 {