better errors
This commit is contained in:
502
source/quickjs.c
502
source/quickjs.c
@@ -29246,6 +29246,19 @@ static cJSON *ast_parse_assign (ASTParseState *s) {
|
||||
return left;
|
||||
}
|
||||
|
||||
/* Validate assignment target */
|
||||
{
|
||||
const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItem (left, "kind"));
|
||||
if (left_kind &&
|
||||
strcmp (left_kind, "name") != 0 &&
|
||||
strcmp (left_kind, ".") != 0 &&
|
||||
strcmp (left_kind, "[") != 0 &&
|
||||
strcmp (left_kind, "?.") != 0 &&
|
||||
strcmp (left_kind, "?.[") != 0) {
|
||||
ast_error (s, start, "invalid assignment left-hand side");
|
||||
}
|
||||
}
|
||||
|
||||
ast_next_token (s);
|
||||
cJSON *right = ast_parse_assign (s);
|
||||
|
||||
@@ -29860,6 +29873,474 @@ static cJSON *ast_parse_program (ASTParseState *s) {
|
||||
return root;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
AST Semantic Pass
|
||||
============================================================ */
|
||||
|
||||
#define AST_SEM_MAX_VARS 256
|
||||
|
||||
typedef struct ASTSemVar {
|
||||
const char *name;
|
||||
int is_const; /* 1 for def, function args; 0 for var */
|
||||
} 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 */
|
||||
} ASTSemScope;
|
||||
|
||||
typedef struct ASTSemState {
|
||||
cJSON *errors;
|
||||
int has_error;
|
||||
} ASTSemState;
|
||||
|
||||
static void ast_sem_error (ASTSemState *st, cJSON *node, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char buf[256];
|
||||
|
||||
va_start (ap, fmt);
|
||||
vsnprintf (buf, sizeof (buf), fmt, ap);
|
||||
va_end (ap);
|
||||
|
||||
cJSON *err = cJSON_CreateObject ();
|
||||
cJSON_AddStringToObject (err, "message", buf);
|
||||
|
||||
cJSON *line_obj = cJSON_GetObjectItem (node, "from_row");
|
||||
cJSON *col_obj = cJSON_GetObjectItem (node, "from_column");
|
||||
if (line_obj) cJSON_AddNumberToObject (err, "line", cJSON_GetNumberValue (line_obj) + 1);
|
||||
if (col_obj) cJSON_AddNumberToObject (err, "column", cJSON_GetNumberValue (col_obj) + 1);
|
||||
|
||||
if (!st->errors) st->errors = cJSON_CreateArray ();
|
||||
cJSON_AddItemToArray (st->errors, err);
|
||||
st->has_error = 1;
|
||||
}
|
||||
|
||||
static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const) {
|
||||
if (scope->var_count < AST_SEM_MAX_VARS) {
|
||||
scope->vars[scope->var_count].name = name;
|
||||
scope->vars[scope->var_count].is_const = is_const;
|
||||
scope->var_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ast_sem_in_loop (ASTSemScope *scope) {
|
||||
for (ASTSemScope *s = scope; s; s = s->parent) {
|
||||
if (s->in_loop) return 1;
|
||||
}
|
||||
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);
|
||||
|
||||
/* Check whether an expression is being assigned to (=, +=, etc.) */
|
||||
static void ast_sem_check_assign_target (ASTSemState *st, ASTSemScope *scope, cJSON *left) {
|
||||
if (!left) return;
|
||||
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (left, "kind"));
|
||||
if (!kind) return;
|
||||
|
||||
if (strcmp (kind, "name") == 0) {
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name"));
|
||||
if (!name) return;
|
||||
ASTSemVar *v = ast_sem_find_var (scope, name);
|
||||
if (v && v->is_const) {
|
||||
ast_sem_error (st, left, "cannot assign to constant '%s'", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Recursively check an expression for semantic errors */
|
||||
static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr) {
|
||||
if (!expr) return;
|
||||
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "kind"));
|
||||
if (!kind) return;
|
||||
|
||||
/* Assignment operators */
|
||||
if (strcmp (kind, "assign") == 0 || strcmp (kind, "+=") == 0 ||
|
||||
strcmp (kind, "-=") == 0 || strcmp (kind, "*=") == 0 ||
|
||||
strcmp (kind, "/=") == 0 || strcmp (kind, "%=") == 0 ||
|
||||
strcmp (kind, "<<=") == 0 || strcmp (kind, ">>=") == 0 ||
|
||||
strcmp (kind, ">>>=") == 0 || strcmp (kind, "&=") == 0 ||
|
||||
strcmp (kind, "^=") == 0 || strcmp (kind, "|=") == 0 ||
|
||||
strcmp (kind, "**=") == 0 || strcmp (kind, "&&=") == 0 ||
|
||||
strcmp (kind, "||=") == 0 || strcmp (kind, "??=") == 0) {
|
||||
ast_sem_check_assign_target (st, scope, cJSON_GetObjectItem (expr, "left"));
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "right"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Increment/decrement */
|
||||
if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) {
|
||||
cJSON *operand = cJSON_GetObjectItem (expr, "expression");
|
||||
if (operand) {
|
||||
const char *op_kind = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "kind"));
|
||||
if (op_kind && strcmp (op_kind, "name") == 0) {
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "name"));
|
||||
if (name) {
|
||||
ASTSemVar *v = ast_sem_find_var (scope, name);
|
||||
if (v && v->is_const) {
|
||||
ast_sem_error (st, expr, "cannot assign to constant '%s'", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Binary ops, ternary, comma — recurse into children */
|
||||
if (strcmp (kind, ",") == 0 || strcmp (kind, "+") == 0 ||
|
||||
strcmp (kind, "-") == 0 || strcmp (kind, "*") == 0 ||
|
||||
strcmp (kind, "/") == 0 || strcmp (kind, "%") == 0 ||
|
||||
strcmp (kind, "==") == 0 || strcmp (kind, "!=") == 0 ||
|
||||
strcmp (kind, "<") == 0 || strcmp (kind, ">") == 0 ||
|
||||
strcmp (kind, "<=") == 0 || strcmp (kind, ">=") == 0 ||
|
||||
strcmp (kind, "&&") == 0 || strcmp (kind, "||") == 0 ||
|
||||
strcmp (kind, "??") == 0 || strcmp (kind, "&") == 0 ||
|
||||
strcmp (kind, "|") == 0 || strcmp (kind, "^") == 0 ||
|
||||
strcmp (kind, "<<") == 0 || strcmp (kind, ">>") == 0 ||
|
||||
strcmp (kind, ">>>") == 0 || strcmp (kind, "**") == 0 ||
|
||||
strcmp (kind, "in") == 0 || strcmp (kind, "of") == 0 ||
|
||||
strcmp (kind, ".") == 0 || strcmp (kind, "[") == 0 ||
|
||||
strcmp (kind, "?.") == 0 || strcmp (kind, "?.[") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "left"));
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "right"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ternary */
|
||||
if (strcmp (kind, "then") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression"));
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "then"));
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "else"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Call and optional call */
|
||||
if (strcmp (kind, "(") == 0 || strcmp (kind, "?.(") == 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;
|
||||
}
|
||||
|
||||
/* Unary ops */
|
||||
if (strcmp (kind, "!") == 0 || strcmp (kind, "~") == 0 ||
|
||||
strcmp (kind, "typeof") == 0 || strcmp (kind, "void") == 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;
|
||||
cJSON_ArrayForEach (el, cJSON_GetObjectItem (expr, "list")) {
|
||||
ast_sem_check_expr (st, scope, el);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Object literal */
|
||||
if (strcmp (kind, "object") == 0) {
|
||||
cJSON *prop;
|
||||
cJSON_ArrayForEach (prop, cJSON_GetObjectItem (expr, "list")) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (prop, "value"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Function expression / arrow function — create new scope */
|
||||
if (strcmp (kind, "function") == 0) {
|
||||
ASTSemScope fn_scope = {0};
|
||||
fn_scope.parent = scope;
|
||||
|
||||
/* Add parameters as const */
|
||||
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);
|
||||
}
|
||||
|
||||
/* Check function body */
|
||||
cJSON *stmt;
|
||||
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) {
|
||||
ast_sem_check_stmt (st, &fn_scope, stmt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Template literal */
|
||||
if (strcmp (kind, "template") == 0) {
|
||||
cJSON *el;
|
||||
cJSON_ArrayForEach (el, cJSON_GetObjectItem (expr, "list")) {
|
||||
ast_sem_check_expr (st, scope, el);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* name, number, string, regexp, null, true, false, this — leaf nodes, no check needed */
|
||||
}
|
||||
|
||||
/* Check a statement for semantic errors */
|
||||
static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt) {
|
||||
if (!stmt) return;
|
||||
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind"));
|
||||
if (!kind) return;
|
||||
|
||||
if (strcmp (kind, "var") == 0) {
|
||||
/* Register variable */
|
||||
cJSON *left = cJSON_GetObjectItem (stmt, "left");
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name"));
|
||||
if (name) {
|
||||
ASTSemVar *existing = ast_sem_find_var (scope, name);
|
||||
if (existing && existing->is_const) {
|
||||
ast_sem_error (st, left, "cannot redeclare constant '%s'", name);
|
||||
}
|
||||
ast_sem_add_var (scope, name, 0);
|
||||
}
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "def") == 0) {
|
||||
/* Register constant */
|
||||
cJSON *left = cJSON_GetObjectItem (stmt, "left");
|
||||
const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name"));
|
||||
if (name) {
|
||||
ASTSemVar *existing = ast_sem_find_var (scope, name);
|
||||
if (existing && existing->is_const) {
|
||||
ast_sem_error (st, left, "cannot redeclare constant '%s'", name);
|
||||
} else if (existing) {
|
||||
ast_sem_error (st, left, "cannot redeclare '%s' as constant", name);
|
||||
}
|
||||
ast_sem_add_var (scope, name, 1);
|
||||
}
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "right"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "call") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "if") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "then")) {
|
||||
ast_sem_check_stmt (st, scope, s2);
|
||||
}
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "list")) {
|
||||
ast_sem_check_stmt (st, scope, s2);
|
||||
}
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "else")) {
|
||||
ast_sem_check_stmt (st, scope, s2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "while") == 0) {
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &loop_scope, s2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "do") == 0) {
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &loop_scope, s2);
|
||||
}
|
||||
ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "for") == 0) {
|
||||
ASTSemScope loop_scope = {0};
|
||||
loop_scope.parent = scope;
|
||||
loop_scope.in_loop = 1;
|
||||
/* init may be a var/def statement or expression */
|
||||
cJSON *init = cJSON_GetObjectItem (stmt, "init");
|
||||
if (init) {
|
||||
const char *init_kind = cJSON_GetStringValue (cJSON_GetObjectItem (init, "kind"));
|
||||
if (init_kind && (strcmp (init_kind, "var") == 0 || strcmp (init_kind, "def") == 0)) {
|
||||
ast_sem_check_stmt (st, &loop_scope, init);
|
||||
} else {
|
||||
ast_sem_check_expr (st, &loop_scope, init);
|
||||
}
|
||||
}
|
||||
ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItem (stmt, "test"));
|
||||
ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItem (stmt, "update"));
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &loop_scope, s2);
|
||||
}
|
||||
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"));
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "continue") == 0) {
|
||||
if (!ast_sem_in_loop (scope)) {
|
||||
ast_sem_error (st, stmt, "'continue' used outside of loop");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "try") == 0) {
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, scope, s2);
|
||||
}
|
||||
cJSON *catch_node = cJSON_GetObjectItem (stmt, "catch");
|
||||
if (catch_node) {
|
||||
ASTSemScope catch_scope = {0};
|
||||
catch_scope.parent = scope;
|
||||
const char *catch_name = cJSON_GetStringValue (cJSON_GetObjectItem (catch_node, "name"));
|
||||
if (catch_name) ast_sem_add_var (&catch_scope, catch_name, 0);
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (catch_node, "statements")) {
|
||||
ast_sem_check_stmt (st, &catch_scope, s2);
|
||||
}
|
||||
}
|
||||
cJSON *finally_node = cJSON_GetObjectItem (stmt, "finally");
|
||||
if (finally_node) {
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (finally_node, "statements")) {
|
||||
ast_sem_check_stmt (st, scope, s2);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "block") == 0) {
|
||||
ASTSemScope block_scope = {0};
|
||||
block_scope.parent = scope;
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &block_scope, s2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp (kind, "label") == 0) {
|
||||
ast_sem_check_stmt (st, scope, cJSON_GetObjectItem (stmt, "statement"));
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ASTSemScope fn_scope = {0};
|
||||
fn_scope.parent = scope;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
cJSON *s2;
|
||||
cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) {
|
||||
ast_sem_check_stmt (st, &fn_scope, s2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the semantic pass on a parsed AST, adding errors to the AST */
|
||||
static void ast_semantic_check (cJSON *ast, cJSON **errors_out) {
|
||||
ASTSemState st = {0};
|
||||
ASTSemScope global_scope = {0};
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Check all statements (var/def are registered as they are encountered) */
|
||||
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (ast, "statements")) {
|
||||
ast_sem_check_stmt (&st, &global_scope, stmt);
|
||||
}
|
||||
|
||||
/* Check function bodies */
|
||||
cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (ast, "functions")) {
|
||||
ast_sem_check_stmt (&st, &global_scope, stmt);
|
||||
}
|
||||
|
||||
*errors_out = st.errors;
|
||||
}
|
||||
|
||||
char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename) {
|
||||
ASTParseState s;
|
||||
memset (&s, 0, sizeof (s));
|
||||
@@ -29883,9 +30364,26 @@ char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filena
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Add errors to output if any */
|
||||
if (s.errors) {
|
||||
/* Run semantic pass */
|
||||
cJSON *sem_errors = NULL;
|
||||
ast_semantic_check (ast, &sem_errors);
|
||||
|
||||
/* Merge parse errors and semantic errors */
|
||||
if (s.errors && sem_errors) {
|
||||
/* Append semantic errors to parse errors */
|
||||
cJSON *err;
|
||||
cJSON *next;
|
||||
for (err = sem_errors->child; err; err = next) {
|
||||
next = err->next;
|
||||
cJSON_DetachItemViaPointer (sem_errors, err);
|
||||
cJSON_AddItemToArray (s.errors, err);
|
||||
}
|
||||
cJSON_Delete (sem_errors);
|
||||
cJSON_AddItemToObject (ast, "errors", s.errors);
|
||||
} else if (s.errors) {
|
||||
cJSON_AddItemToObject (ast, "errors", s.errors);
|
||||
} else if (sem_errors) {
|
||||
cJSON_AddItemToObject (ast, "errors", sem_errors);
|
||||
}
|
||||
|
||||
/* Convert to JSON string */
|
||||
|
||||
106
source/suite.c
106
source/suite.c
@@ -2293,6 +2293,100 @@ TEST(ast_valid_no_errors) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
AST SEMANTIC ERROR TESTS
|
||||
============================================================================ */
|
||||
|
||||
TEST(ast_sem_assign_to_const) {
|
||||
const char *src = "def x = 5; x = 3";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_assign_to_arg) {
|
||||
const char *src = "function(x) { x = 5; }";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_redeclare_const) {
|
||||
const char *src = "def x = 1; def x = 2";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "cannot redeclare constant");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_break_outside_loop) {
|
||||
const char *src = "break";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_continue_outside_loop) {
|
||||
const char *src = "continue";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_break_inside_loop_ok) {
|
||||
const char *src = "while (true) { break; }";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_NO_ERRORS(json);
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_increment_const) {
|
||||
const char *src = "def x = 1; x++";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_shadow_var_ok) {
|
||||
const char *src = "var array = []; array";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_NO_ERRORS(json);
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_var_assign_ok) {
|
||||
const char *src = "var x = 1; x = x + 1";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_NO_ERRORS(json);
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(ast_sem_nested_function_scope) {
|
||||
const char *src = "var x = 1; function f(x) { return x + 1; }";
|
||||
char *json = JS_AST(ctx, src, strlen(src), "<test>");
|
||||
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
|
||||
ASSERT_NO_ERRORS(json);
|
||||
free(json);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
CODEGEN/SEMANTIC ERROR TESTS
|
||||
============================================================================ */
|
||||
@@ -2624,6 +2718,18 @@ int run_c_test_suite(JSContext *ctx)
|
||||
RUN_TEST(ast_recovery_continues_after_error);
|
||||
RUN_TEST(ast_valid_no_errors);
|
||||
|
||||
printf("\nAST Semantic Errors:\n");
|
||||
RUN_TEST(ast_sem_assign_to_const);
|
||||
RUN_TEST(ast_sem_assign_to_arg);
|
||||
RUN_TEST(ast_sem_redeclare_const);
|
||||
RUN_TEST(ast_sem_break_outside_loop);
|
||||
RUN_TEST(ast_sem_continue_outside_loop);
|
||||
RUN_TEST(ast_sem_break_inside_loop_ok);
|
||||
RUN_TEST(ast_sem_increment_const);
|
||||
RUN_TEST(ast_sem_shadow_var_ok);
|
||||
RUN_TEST(ast_sem_var_assign_ok);
|
||||
RUN_TEST(ast_sem_nested_function_scope);
|
||||
|
||||
printf("\nCodegen Errors:\n");
|
||||
RUN_TEST(mach_assign_to_const);
|
||||
RUN_TEST(mach_assign_to_arg);
|
||||
|
||||
Reference in New Issue
Block a user