more info in AST parser
This commit is contained in:
455
source/quickjs.c
455
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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user