Merge branch 'newsyn' into mach

This commit is contained in:
2026-02-06 03:10:14 -06:00
3 changed files with 526 additions and 268 deletions

View File

@@ -8514,17 +8514,12 @@ enum {
TOK_DEF,
TOK_THIS,
TOK_DELETE,
TOK_VOID,
TOK_NEW,
TOK_IN,
TOK_DO,
TOK_WHILE,
TOK_FOR,
TOK_BREAK,
TOK_CONTINUE,
TOK_SWITCH,
TOK_CASE,
TOK_DEFAULT,
TOK_THROW,
TOK_TRY,
TOK_CATCH,
@@ -8625,17 +8620,12 @@ static const char *ast_token_kind_str(int token_val) {
case TOK_DEF: return "def";
case TOK_THIS: return "this";
case TOK_DELETE: return "delete";
case TOK_VOID: return "void";
case TOK_NEW: return "new";
case TOK_IN: return "in";
case TOK_DO: return "do";
case TOK_WHILE: return "while";
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";
@@ -9784,17 +9774,12 @@ static const JSKeywordEntry js_keywords[] = {
{ "def", TOK_DEF, FALSE },
{ "this", TOK_THIS, FALSE },
{ "delete", TOK_DELETE, FALSE },
{ "void", TOK_VOID, FALSE },
{ "new", TOK_NEW, FALSE },
{ "in", TOK_IN, FALSE },
{ "do", TOK_DO, FALSE },
{ "while", TOK_WHILE, FALSE },
{ "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 },
@@ -12428,8 +12413,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s,
if (js_parse_array_literal (s)) return -1;
}
break;
case TOK_NEW:
return js_parse_error (s, "'new' keyword is not supported");
default:
return js_parse_error (s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
}
@@ -13593,89 +13576,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;
@@ -16648,11 +16548,9 @@ static __exception int js_parse_directives (JSParseState *s) {
case TOK_VAR:
case TOK_THIS:
case TOK_DELETE:
case TOK_NEW:
case TOK_DO:
case TOK_WHILE:
case TOK_FOR:
case TOK_SWITCH:
case TOK_THROW:
case TOK_TRY:
case TOK_FUNCTION:
@@ -28224,14 +28122,11 @@ redo:
else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR;
else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF;
else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR;
else if (len == 3 && !memcmp (start, "new", 3)) s->token_val = TOK_NEW;
else if (len == 3 && !memcmp (start, "try", 3)) s->token_val = TOK_TRY;
else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE;
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, "void", 4)) s->token_val = TOK_VOID;
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;
@@ -28239,8 +28134,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;
@@ -28552,14 +28445,11 @@ static int tokenize_next (ASTParseState *s) {
else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR;
else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF;
else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR;
else if (len == 3 && !memcmp (start, "new", 3)) s->token_val = TOK_NEW;
else if (len == 3 && !memcmp (start, "try", 3)) s->token_val = TOK_TRY;
else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE;
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, "void", 4)) s->token_val = TOK_VOID;
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;
@@ -28567,8 +28457,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;
@@ -29239,34 +29127,6 @@ static cJSON *ast_parse_unary (ASTParseState *s) {
ast_node_end (s, node, s->buf_ptr);
return node;
}
case TOK_VOID: {
ast_next_token (s);
cJSON *node = ast_node (s, "void", start);
cJSON *expr = ast_parse_unary (s);
cJSON_AddItemToObject (node, "expression", expr);
ast_node_end (s, node, s->buf_ptr);
return node;
}
case TOK_NEW: {
ast_next_token (s);
cJSON *node = ast_node (s, "new", start);
cJSON *expr = ast_parse_postfix (s);
cJSON_AddItemToObject (node, "expression", expr);
/* If followed by (, parse arguments */
if (s->token_val == '(') {
ast_next_token (s);
cJSON *list = cJSON_AddArrayToObject (node, "list");
while (s->token_val != ')' && s->token_val != TOK_EOF) {
cJSON *arg = ast_parse_assign_expr (s);
if (arg) cJSON_AddItemToArray (list, arg);
if (s->token_val == ',') ast_next_token (s);
else break;
}
if (s->token_val == ')') ast_next_token (s);
}
ast_node_end (s, node, s->buf_ptr);
return node;
}
default:
return ast_parse_postfix (s);
}
@@ -29505,7 +29365,7 @@ static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr) {
ast_next_token (s);
if (s->token_val == '=' || s->token_val == '|') {
ast_next_token (s);
cJSON *default_val = ast_parse_expr (s);
cJSON *default_val = ast_parse_assign_expr (s);
cJSON_AddItemToObject (param, "expression", default_val);
}
cJSON_AddItemToArray (params, param);
@@ -29591,7 +29451,7 @@ static cJSON *ast_parse_arrow_function (ASTParseState *s) {
/* Check for default value */
if (s->token_val == '=' || s->token_val == '|') {
ast_next_token (s);
cJSON *default_val = ast_parse_expr (s);
cJSON *default_val = ast_parse_assign_expr (s);
cJSON_AddItemToObject (param, "expression", default_val);
}
cJSON_AddItemToArray (params, param);
@@ -29655,7 +29515,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:
@@ -29894,72 +29754,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);
@@ -30103,20 +29897,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, ...) {
@@ -30140,22 +29942,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) {
@@ -30165,13 +30024,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);
@@ -30269,23 +30121,13 @@ static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr
/* Unary ops */
if (strcmp (kind, "!") == 0 || strcmp (kind, "~") == 0 ||
strcmp (kind, "typeof") == 0 || strcmp (kind, "void") == 0 ||
strcmp (kind, "typeof") == 0 ||
strcmp (kind, "delete") == 0 || strcmp (kind, "neg") == 0 ||
strcmp (kind, "pos") == 0 || strcmp (kind, "spread") == 0) {
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression"));
return;
}
/* new expression */
if (strcmp (kind, "new") == 0) {
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression"));
cJSON *arg;
cJSON_ArrayForEach (arg, cJSON_GetObjectItem (expr, "list")) {
ast_sem_check_expr (st, scope, arg);
}
return;
}
/* Array literal */
if (strcmp (kind, "array") == 0) {
cJSON *el;
@@ -30306,14 +30148,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 */
@@ -30321,6 +30173,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;
}
@@ -30333,7 +30192,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 */
@@ -30351,7 +30228,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;
@@ -30368,7 +30245,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;
@@ -30399,6 +30276,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);
@@ -30410,6 +30288,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);
@@ -30422,6 +30301,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) {
@@ -30441,22 +30321,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"));
@@ -30464,8 +30328,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;
}
@@ -30486,8 +30350,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);
}
@@ -30504,6 +30369,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);
@@ -30519,35 +30385,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) */
@@ -30560,7 +30448,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) {
@@ -30572,7 +30481,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;
@@ -30588,7 +30497,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) {