better errors

This commit is contained in:
2026-02-05 18:35:48 -06:00
parent 27229c675c
commit 7909b11f6b
2 changed files with 606 additions and 2 deletions

View File

@@ -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 */

View File

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