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

View File

@@ -1 +0,0 @@
void 0

344
vm_test/syntax.txt Normal file
View File

@@ -0,0 +1,344 @@
/* comprehensive syntax test - exercises all AST paths */
// --- variables and constants ---
var a
var b = 1
var c = 2, d = 3
def PI = 3.14159
def MSG = "hello"
// --- number literals ---
var n_int = 42
var n_float = 3.14
var n_hex = 0xFF
var n_octal = 0o77
var n_binary = 0b1010
var n_underscore = 1_000_000
var n_exp = 1e10
var n_exp_neg = 2.5e-3
// --- string literals ---
var s_single = 'single'
var s_double = "double"
var s_escape = "tab\there\nnewline\\slash\"quote"
var s_unicode = "\u0041\u0042"
// --- template literals ---
var t_basic = `hello ${b} world`
var t_nested = `a ${b + c} b ${d}`
var t_expr = `result: ${b > 0 ? "yes" : "no"}`
// --- array literals ---
var arr_empty = []
var arr_simple = [1, 2, 3]
var arr_mixed = [1, "two", true, null]
var arr_nested = [[1, 2], [3, [4, 5]]]
// --- record/object literals ---
var rec_empty = {}
var rec_simple = {x: 1, y: 2}
var rec_nested = {a: {b: {c: 3}}}
var shorthand_var = 10
var rec_shorthand = {shorthand_var}
var rec_computed = {["key" + "1"]: 100}
var rec_method = {
greet() { return "hi" }
}
var rec_mixed = {
name: "test",
value: 42,
nested: {inner: true},
items: [1, 2, 3]
}
// --- all binary arithmetic operators ---
var arith_add = 1 + 2
var arith_sub = 5 - 3
var arith_mul = 4 * 5
var arith_div = 10 / 3
var arith_mod = 10 % 3
var arith_pow = 2 ** 8
// --- comparison operators ---
var cmp_lt = 1 < 2
var cmp_gt = 2 > 1
var cmp_lte = 1 <= 1
var cmp_gte = 2 >= 2
var cmp_eq = 1 == 1
var cmp_neq = 1 != 2
// --- logical operators ---
var log_and = true && false
var log_or = false || true
var log_not = !false
// --- bitwise operators ---
var bit_and = 0xFF & 0x0F
var bit_or = 0xF0 | 0x0F
var bit_xor = 0xFF ^ 0x0F
var bit_not = ~0
var bit_shl = 1 << 8
var bit_shr = 256 >> 4
var bit_shru = -1 >>> 0
// --- nullish coalescing ---
var nullish = null ?? "default"
// --- ternary operator ---
var tern = b > 0 ? "pos" : "neg"
var tern_nested = b > 0 ? (b > 10 ? "big" : "small") : "neg"
// --- comma operator ---
var comma_result = (1, 2, 3)
// --- unary operators ---
var unary_pos = +b
var unary_neg = -b
// --- increment/decrement ---
var inc_val = 0
inc_val++
inc_val--
;++inc_val
;--inc_val
// --- all compound assignments ---
var ca = 10
ca += 5
ca -= 3
ca *= 2
ca /= 4
ca %= 3
ca **= 2
ca <<= 1
ca >>= 1
ca >>>= 0
ca &= 0xFF
ca |= 0x01
ca ^= 0x10
ca &&= true
ca ||= false
ca ??= 1
// --- chained assignment ---
var ch1, ch2
ch1 = ch2 = 42
// --- property access ---
var obj = {a: {b: {c: 1}}, items: [10, 20, 30]}
var dot_access = obj.a
var dot_chain = obj.a.b.c
var bracket_access = obj["a"]
var bracket_dynamic = obj["it" + "ems"]
var bracket_index = obj.items[1]
// --- optional chaining ---
var opt_obj = {x: {y: 1}}
var opt_prop = opt_obj?.x
var opt_deep = opt_obj?.x?.y
var opt_null = null?.foo
var opt_bracket = opt_obj?.["x"]
var opt_call_obj = {f: function() { return 1 }}
var opt_call = opt_call_obj?.f?.()
// --- function declarations ---
function add(a, b) {
return a + b
}
function no_params() {
return 42
}
function with_default(x = 10, y = 20) {
return x + y
}
// --- function expressions ---
var func_expr = function(x) { return x * 2 }
var func_named = function multiply(x, y) { return x * y }
// --- arrow functions (all forms) ---
var arrow_no_param = () => 0
var arrow_one = x => x + 1
var arrow_multi = (a, b) => a + b
var arrow_block = (a, b) => { return a + b }
var arrow_default = (x = 1, y = 2) => x + y
var arrow_default_expr = (x = 1 + 2, y = arr_simple[0]) => x + y
// --- closures ---
function make_counter() {
var count = 0
return function() {
count += 1
return count
}
}
function outer_fn() {
var x = 10
function middle() {
var y = 20
function inner() {
return x + y
}
return inner()
}
return middle()
}
// --- this in methods ---
var counter = {
val: 0,
inc() { this.val += 1 },
get() { return this.val }
}
// --- if/else/else-if ---
var if_result
if (b > 0) {
if_result = "positive"
} else if (b == 0) {
if_result = "zero"
} else {
if_result = "negative"
}
// --- while loop ---
var w = 0
while (w < 5) {
w += 1
}
// --- do-while loop ---
var dw = 0
do {
dw += 1
} while (dw < 3)
// --- for loop ---
var f_sum = 0
for (var i = 0; i < 10; i++) {
f_sum += i
}
// --- for loop with break ---
var fb = 0
for (var j = 0; j < 100; j++) {
if (j == 5) break
fb = j
}
// --- for loop with continue ---
var fc = 0
for (var k = 0; k < 10; k++) {
if (k % 2 == 0) continue
fc += k
}
// --- labeled break ---
outer: for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
if (y == 1) break outer
}
}
// --- labeled continue ---
var lc = 0
loop: for (var m = 0; m < 3; m++) {
for (var n = 0; n < 3; n++) {
if (n == 1) continue loop
lc += 1
}
}
// --- try/catch/finally ---
var tc
try {
throw "error"
} catch (e) {
tc = e
}
var tcf = 0
try {
throw "err"
} catch (e) {
tcf = 1
} finally {
tcf += 10
}
// --- try/finally (no catch) ---
var tf = 0
try {
tf = 1
} finally {
tf += 1
}
// --- delete operator ---
var del_obj = {a: 1, b: 2}
delete del_obj.a
// --- in operator ---
var in_result = "b" in del_obj
// --- go statement ---
function async_task() { return 1 }
function caller() { go async_task() }
// --- IIFE ---
var iife = (function() { return 99 })()
// --- recursive function ---
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
// --- mutually recursive functions ---
function is_even(n) {
if (n == 0) return true
return is_odd(n - 1)
}
function is_odd(n) {
if (n == 0) return false
return is_even(n - 1)
}
// --- block scoping ---
var block_val = 1
{
var block_val = 2
}
// --- nested blocks ---
{
var nb = 1
{
var nb2 = nb + 1
{
var nb3 = nb2 + 1
}
}
}
// --- empty statement ---
;
// --- parenthesized expression for precedence ---
var prec = (1 + 2) * (3 + 4)
// --- complex expressions ---
var complex = arr_simple[0] + rec_simple.x * (b > 0 ? 2 : 1)
// --- operator precedence chain ---
var prec_chain = 1 + 2 * 3 ** 2 - 4 / 2 % 3
// --- regex literals ---
var re = /hello/
var re_flags = /world/gi
// --- line comment at end ---
var end = 1 // done