24 Commits

Author SHA1 Message Date
John Alanbrook
4de0659474 allow tokens as properties 2026-02-08 00:34:15 -06:00
John Alanbrook
27a9b72b07 functino tests; default args for mach and mcode 2026-02-08 00:31:18 -06:00
John Alanbrook
a3622bd5bd better parser error reporting 2026-02-08 00:23:47 -06:00
John Alanbrook
2f6700415e add functinos 2026-02-07 23:38:39 -06:00
John Alanbrook
243d92f7f3 rm ?? and .? 2026-02-07 22:09:40 -06:00
John Alanbrook
8f9d026b9b use casesensitive json 2026-02-07 17:01:11 -06:00
John Alanbrook
2c9ac8f7b6 no json roundtrip for mcode 2026-02-07 16:29:04 -06:00
John Alanbrook
80f24e131f all suite asan errors fixed for mcode 2026-02-07 16:15:58 -06:00
John Alanbrook
a8f8af7662 Merge branch 'mach' into mcode2 2026-02-07 15:49:38 -06:00
John Alanbrook
f5b3494762 memfree for mcode 2026-02-07 15:49:36 -06:00
John Alanbrook
13a6f6c79d faster mach bytecode generation 2026-02-07 15:49:09 -06:00
John Alanbrook
1a925371d3 faster parsing 2026-02-07 15:38:36 -06:00
John Alanbrook
08d2bacb1f improve ast parsing time 2026-02-07 15:22:18 -06:00
John Alanbrook
7322153e57 Merge branch 'mach' into mcode2 2026-02-07 14:53:41 -06:00
John Alanbrook
cc72c4cb0f fix mem errors for mcode 2026-02-07 14:53:35 -06:00
John Alanbrook
ae1f09a28f fix all memory errors 2026-02-07 14:53:14 -06:00
John Alanbrook
3c842912a1 gc fixing in mach vm 2026-02-07 14:25:04 -06:00
John Alanbrook
7cacf32078 Merge branch 'mach' into mcode2 2026-02-07 14:24:52 -06:00
John Alanbrook
b740612761 gc fixing in mach vm 2026-02-07 14:24:49 -06:00
John Alanbrook
6001c2b4bb Merge branch 'mach' into mcode2 2026-02-07 14:19:19 -06:00
John Alanbrook
98625fa15b mcode fix tests 2026-02-07 14:19:17 -06:00
John Alanbrook
45ce76aef7 fixes 2026-02-07 12:50:46 -06:00
John Alanbrook
3621b1ef33 Merge branch 'mach' into mcode2 2026-02-07 11:53:44 -06:00
John Alanbrook
0ae59705d4 fix errors 2026-02-07 11:53:26 -06:00
6 changed files with 3372 additions and 1112 deletions

View File

@@ -105,35 +105,46 @@ static char* load_core_file(const char *filename, size_t *out_size) {
return data; return data;
} }
static int print_json_errors(const char *json) { static int print_tree_errors(cJSON *root) {
if (!json) return 0;
cJSON *root = cJSON_Parse(json);
if (!root) return 0; if (!root) return 0;
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors"); cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) { if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0)
cJSON_Delete(root);
return 0; return 0;
}
const char *filename = "<unknown>"; const char *filename = "<unknown>";
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename"); cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
if (cJSON_IsString(fname)) if (cJSON_IsString(fname))
filename = fname->valuestring; filename = fname->valuestring;
int prev_line = -1;
const char *prev_msg = NULL;
cJSON *e; cJSON *e;
cJSON_ArrayForEach(e, errors) { cJSON_ArrayForEach(e, errors) {
const char *msg = cJSON_GetStringValue( const char *msg = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(e, "message")); cJSON_GetObjectItemCaseSensitive(e, "message"));
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line"); cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column"); cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column");
int cur_line = cJSON_IsNumber(line) ? (int)line->valuedouble : -1;
if (prev_msg && msg && cur_line == prev_line && strcmp(msg, prev_msg) == 0)
continue;
prev_line = cur_line;
prev_msg = msg;
if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col)) if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
fprintf(stderr, "%s:%d:%d: error: %s\n", fprintf(stderr, "%s:%d:%d: error: %s\n",
filename, (int)line->valuedouble, (int)col->valuedouble, msg); filename, (int)line->valuedouble, (int)col->valuedouble, msg);
else if (msg) else if (msg)
fprintf(stderr, "%s: error: %s\n", filename, msg); fprintf(stderr, "%s: error: %s\n", filename, msg);
} }
cJSON_Delete(root);
return 1; return 1;
} }
static int print_json_errors(const char *json) {
if (!json) return 0;
cJSON *root = cJSON_Parse(json);
if (!root) return 0;
int result = print_tree_errors(root);
cJSON_Delete(root);
return result;
}
// Get the core path for use by scripts // Get the core path for use by scripts
const char* cell_get_core_path(void) { const char* cell_get_core_path(void) {
return core_path; return core_path;
@@ -399,11 +410,13 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file; script = (char *)script_or_file;
} }
char *json = JS_AST(script, strlen(script), filename); cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (json) { if (ast) {
int has_errors = print_json_errors(json); int has_errors = print_tree_errors(ast);
printf("%s\n", json); char *pretty = cJSON_Print(ast);
free(json); cJSON_Delete(ast);
printf("%s\n", pretty);
free(pretty);
free(allocated_script); free(allocated_script);
return has_errors ? 1 : 0; return has_errors ? 1 : 0;
} else { } else {
@@ -445,8 +458,14 @@ int cell_init(int argc, char **argv)
char *json = JS_Tokenize(script, strlen(script), filename); char *json = JS_Tokenize(script, strlen(script), filename);
if (json) { if (json) {
int has_errors = print_json_errors(json); int has_errors = print_json_errors(json);
printf("%s\n", json); cJSON *root = cJSON_Parse(json);
free(json); free(json);
if (root) {
char *pretty = cJSON_Print(root);
cJSON_Delete(root);
printf("%s\n", pretty);
free(pretty);
}
free(allocated_script); free(allocated_script);
return has_errors ? 1 : 0; return has_errors ? 1 : 0;
} else { } else {
@@ -485,30 +504,32 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file; script = (char *)script_or_file;
} }
char *ast_json = JS_AST(script, strlen(script), filename); cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast_json) { if (!ast) {
printf("Failed to parse AST\n"); printf("Failed to parse AST\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
if (print_json_errors(ast_json)) { if (print_tree_errors(ast)) {
free(ast_json); cJSON_Delete(ast);
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
char *mcode_json = JS_Mcode(ast_json); cJSON *mcode = JS_McodeTree(ast);
free(ast_json); cJSON_Delete(ast);
if (!mcode_json) { if (!mcode) {
printf("Failed to generate MCODE\n"); printf("Failed to generate MCODE\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
printf("%s\n", mcode_json); char *pretty = cJSON_Print(mcode);
free(mcode_json); cJSON_Delete(mcode);
printf("%s\n", pretty);
free(pretty);
free(allocated_script); free(allocated_script);
return 0; return 0;
@@ -536,40 +557,39 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file; script = (char *)script_or_file;
} }
char *ast_json = JS_AST(script, strlen(script), filename); cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast_json) { if (!ast) {
printf("Failed to parse AST\n"); printf("Failed to parse AST\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
if (print_json_errors(ast_json)) { if (print_tree_errors(ast)) {
free(ast_json); free(allocated_script); cJSON_Delete(ast); free(allocated_script);
return 1; return 1;
} }
char *mcode_json = JS_Mcode(ast_json); cJSON *mcode = JS_McodeTree(ast);
free(ast_json); cJSON_Delete(ast);
if (!mcode_json) { if (!mcode) {
printf("Failed to generate MCODE\n"); printf("Failed to generate MCODE\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
if (print_json_errors(mcode_json)) { if (print_tree_errors(mcode)) {
free(mcode_json); free(allocated_script); cJSON_Delete(mcode); free(allocated_script);
return 1; return 1;
} }
/* Use a larger heap context for execution */ /* Use a larger heap context for execution */
JSRuntime *rt = JS_NewRuntime(); JSRuntime *rt = JS_NewRuntime();
if (!rt) { printf("Failed to create JS runtime\n"); free(mcode_json); free(allocated_script); return 1; } if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(mcode); free(allocated_script); return 1; }
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024); JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
if (!ctx) { printf("Failed to create execution context\n"); free(mcode_json); JS_FreeRuntime(rt); free(allocated_script); return 1; } if (!ctx) { printf("Failed to create execution context\n"); cJSON_Delete(mcode); JS_FreeRuntime(rt); free(allocated_script); return 1; }
JSValue result = JS_CallMcode(ctx, mcode_json); JSValue result = JS_CallMcodeTree(ctx, mcode); /* takes ownership of mcode */
free(mcode_json);
if (JS_IsException(result)) { if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx); JSValue exc = JS_GetException(ctx);
@@ -629,15 +649,15 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file; script = (char *)script_or_file;
} }
char *ast_json = JS_AST(script, strlen(script), filename); cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast_json) { if (!ast) {
printf("Failed to parse AST\n"); printf("Failed to parse AST\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
if (print_json_errors(ast_json)) { if (print_tree_errors(ast)) {
free(ast_json); cJSON_Delete(ast);
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
@@ -645,18 +665,18 @@ int cell_init(int argc, char **argv)
JSRuntime *rt = JS_NewRuntime(); JSRuntime *rt = JS_NewRuntime();
if (!rt) { if (!rt) {
printf("Failed to create JS runtime\n"); printf("Failed to create JS runtime\n");
free(ast_json); free(allocated_script); cJSON_Delete(ast); free(allocated_script);
return 1; return 1;
} }
JSContext *ctx = JS_NewContext(rt); JSContext *ctx = JS_NewContext(rt);
if (!ctx) { if (!ctx) {
printf("Failed to create JS context\n"); printf("Failed to create JS context\n");
free(ast_json); JS_FreeRuntime(rt); free(allocated_script); cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1; return 1;
} }
JS_DumpMach(ctx, ast_json, JS_NULL); JS_DumpMachTree(ctx, ast, JS_NULL);
free(ast_json); cJSON_Delete(ast);
JS_FreeContext(ctx); JS_FreeContext(ctx);
JS_FreeRuntime(rt); JS_FreeRuntime(rt);
@@ -693,15 +713,15 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file; script = (char *)script_or_file;
} }
char *ast_json = JS_AST(script, strlen(script), filename); cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast_json) { if (!ast) {
printf("Failed to parse AST\n"); printf("Failed to parse AST\n");
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
if (print_json_errors(ast_json)) { if (print_tree_errors(ast)) {
free(ast_json); cJSON_Delete(ast);
free(allocated_script); free(allocated_script);
return 1; return 1;
} }
@@ -709,18 +729,18 @@ int cell_init(int argc, char **argv)
JSRuntime *rt = JS_NewRuntime(); JSRuntime *rt = JS_NewRuntime();
if (!rt) { if (!rt) {
printf("Failed to create JS runtime\n"); printf("Failed to create JS runtime\n");
free(ast_json); free(allocated_script); cJSON_Delete(ast); free(allocated_script);
return 1; return 1;
} }
JSContext *ctx = JS_NewContext(rt); JSContext *ctx = JS_NewContext(rt);
if (!ctx) { if (!ctx) {
printf("Failed to create JS context\n"); printf("Failed to create JS context\n");
free(ast_json); JS_FreeRuntime(rt); free(allocated_script); cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1; return 1;
} }
JSValue result = JS_RunMach(ctx, ast_json, JS_NULL); JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
free(ast_json); cJSON_Delete(ast);
int exit_code = 0; int exit_code = 0;
if (JS_IsException(result)) { if (JS_IsException(result)) {

File diff suppressed because it is too large Load Diff

View File

@@ -1218,45 +1218,57 @@ CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */ Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename); CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename);
/* Parse source code and return AST as cJSON tree.
Caller must call cJSON_Delete() on result. */
struct cJSON *JS_ASTTree (const char *source, size_t len, const char *filename);
/* Parse source code and return AST as JSON string. /* Parse source code and return AST as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. Returns malloc'd JSON string (caller must free), or NULL on error. */
No JSContext needed — pure string transformation. */
char *JS_AST (const char *source, size_t len, const char *filename); char *JS_AST (const char *source, size_t len, const char *filename);
/* Tokenize source code and return token array as JSON string. /* Tokenize source code and return token array as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. Returns malloc'd JSON string (caller must free), or NULL on error. */
No JSContext needed — pure string transformation. */
char *JS_Tokenize (const char *source, size_t len, const char *filename); char *JS_Tokenize (const char *source, size_t len, const char *filename);
/* Compiled bytecode (context-free, serializable) */ /* Compiled bytecode (context-free, serializable) */
typedef struct MachCode MachCode; typedef struct MachCode MachCode;
/* Compile AST JSON to context-free MachCode. /* Compile AST cJSON tree to context-free MachCode. */
Returns MachCode* (caller must free with JS_FreeMachCode), or NULL on error. */ MachCode *JS_CompileMachTree(struct cJSON *ast);
/* Compile AST JSON string to context-free MachCode. */
MachCode *JS_CompileMach(const char *ast_json); MachCode *JS_CompileMach(const char *ast_json);
/* Free a compiled MachCode tree. */ /* Free a compiled MachCode tree. */
void JS_FreeMachCode(MachCode *mc); void JS_FreeMachCode(MachCode *mc);
/* Load compiled MachCode into a JSContext, materializing JSValues. /* Load compiled MachCode into a JSContext, materializing JSValues. */
Returns JSCodeRegister* linked and ready for execution. */
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env); struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
/* Dump MACH bytecode to stdout for debugging. Takes AST JSON. /* Dump MACH bytecode to stdout. Takes AST cJSON tree. */
Internally compiles, loads, and dumps binary bytecode. */ void JS_DumpMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
/* Dump MACH bytecode to stdout. Takes AST JSON string. */
void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env); void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Compile and execute MACH bytecode. Takes AST JSON. /* Compile and execute MACH bytecode from AST cJSON tree. */
Internally compiles, loads, and executes. JSValue JS_RunMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
Returns result of execution, or JS_EXCEPTION on error. */
/* Compile and execute MACH bytecode from AST JSON string. */
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env); JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Compile AST JSON to MCODE JSON (string-based IR). /* Compile AST cJSON tree to MCODE cJSON tree.
Returns malloc'd JSON string, or NULL on error. Caller must free. Caller must call cJSON_Delete() on result. */
No JSContext needed — pure string transformation. */ struct cJSON *JS_McodeTree (struct cJSON *ast);
/* Compile AST JSON string to MCODE JSON string.
Returns malloc'd JSON string, or NULL on error. Caller must free. */
char *JS_Mcode (const char *ast_json); char *JS_Mcode (const char *ast_json);
/* Parse and execute MCODE JSON directly via the MCODE interpreter. /* Execute MCODE from cJSON tree. Takes ownership of root. */
JSValue JS_CallMcodeTree (JSContext *ctx, struct cJSON *root);
/* Parse and execute MCODE JSON string.
Returns result of execution, or JS_EXCEPTION on error. */ Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json); JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);

View File

@@ -2393,22 +2393,22 @@ TEST(ast_sem_nested_function_scope) {
TEST(mach_compile_basic) { TEST(mach_compile_basic) {
const char *src = "var x = 1; x = x + 1"; const char *src = "var x = 1; x = x + 1";
char *ast_json = JS_AST(src, strlen(src), "<test>"); cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
MachCode *mc = JS_CompileMach(ast_json); MachCode *mc = JS_CompileMachTree(ast);
free(ast_json); cJSON_Delete(ast);
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL"); ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
JS_FreeMachCode(mc); JS_FreeMachCode(mc);
return 1; return 1;
} }
TEST(mach_compile_function) { TEST(mach_compile_function) {
const char *src = "function f(x) { return x + 1 }"; const char *src = "function f(x) { return x + 1 }";
char *ast_json = JS_AST(src, strlen(src), "<test>"); cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL"); ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
MachCode *mc = JS_CompileMach(ast_json); MachCode *mc = JS_CompileMachTree(ast);
free(ast_json); cJSON_Delete(ast);
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL"); ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
JS_FreeMachCode(mc); JS_FreeMachCode(mc);
return 1; return 1;
} }

657
syntax_suite.ce Normal file
View File

@@ -0,0 +1,657 @@
// Syntax suite: covers every syntactic feature of cell script
// Run: ./cell --mach-run syntax_suite.ce
var passed = 0
var failed = 0
var error_names = []
var error_reasons = []
var fail_msg = ""
for (var _i = 0; _i < 100; _i++) {
error_names[] = null
error_reasons[] = null
}
var fail = function(msg) {
fail_msg = msg
disrupt
}
var assert_eq = function(actual, expected, msg) {
if (actual != expected) fail(msg + " (got=" + text(actual) + " expected=" + text(expected) + ")")
}
var run = function(name, fn) {
fail_msg = ""
fn()
passed = passed + 1
} disruption {
error_names[failed] = name
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
failed = failed + 1
}
var should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
// === LITERALS ===
run("number literals", function() {
assert_eq(42, 42, "integer")
assert_eq(3.14 > 3, true, "float")
assert_eq(-5, -5, "negative")
assert_eq(0, 0, "zero")
assert_eq(1e3, 1000, "scientific")
})
run("string literals", function() {
assert_eq("hello", "hello", "double quote")
assert_eq("", "", "empty string")
assert_eq("line1\nline2" != "line1line2", true, "escape sequence")
})
run("template literals", function() {
var x = "world"
assert_eq(`hello ${x}`, "hello world", "interpolation")
assert_eq(`${1 + 2}`, "3", "expression interpolation")
assert_eq(`plain`, "plain", "no interpolation")
})
run("boolean literals", function() {
assert_eq(true, true, "true")
assert_eq(false, false, "false")
})
run("null literal", function() {
assert_eq(null, null, "null")
})
run("array literal", function() {
var a = [1, 2, 3]
assert_eq(length(a), 3, "array length")
assert_eq(a[0], 1, "first element")
var e = []
assert_eq(length(e), 0, "empty array")
})
run("object literal", function() {
var o = {a: 1, b: "two"}
assert_eq(o.a, 1, "object prop")
var e = {}
assert_eq(e.x, null, "empty object missing prop")
})
run("regex literal", function() {
var r = /\d+/
var result = extract("abc123", r)
assert_eq(result[0], "123", "regex match")
var ri = /hello/i
var result2 = extract("Hello", ri)
assert_eq(result2[0], "Hello", "regex flags")
})
// === DECLARATIONS ===
run("var declaration", function() {
var x = 5
assert_eq(x, 5, "var init")
})
run("var uninitialized", function() {
var x
assert_eq(x, null, "var defaults to null")
})
run("var multiple declaration", function() {
var a = 1, b = 2, c = 3
assert_eq(a + b + c, 6, "multi var")
})
run("def declaration", function() {
def x = 42
assert_eq(x, 42, "def const")
})
// === ARITHMETIC OPERATORS ===
run("arithmetic operators", function() {
assert_eq(2 + 3, 5, "add")
assert_eq(5 - 3, 2, "sub")
assert_eq(3 * 4, 12, "mul")
assert_eq(12 / 4, 3, "div")
assert_eq(10 % 3, 1, "mod")
assert_eq(2 ** 3, 8, "exp")
})
// === COMPARISON OPERATORS ===
run("comparison operators", function() {
assert_eq(5 == 5, true, "eq")
assert_eq(5 != 6, true, "neq")
assert_eq(3 < 5, true, "lt")
assert_eq(5 > 3, true, "gt")
assert_eq(3 <= 3, true, "lte")
assert_eq(5 >= 5, true, "gte")
})
// === LOGICAL OPERATORS ===
run("logical operators", function() {
assert_eq(true && true, true, "and")
assert_eq(true && false, false, "and false")
assert_eq(false || true, true, "or")
assert_eq(false || false, false, "or false")
assert_eq(!true, false, "not")
assert_eq(!false, true, "not false")
})
run("short circuit", function() {
var called = false
var fn = function() { called = true; return true }
var r = false && fn()
assert_eq(called, false, "and short circuit")
r = true || fn()
assert_eq(called, false, "or short circuit")
})
// === BITWISE OPERATORS ===
run("bitwise operators", function() {
assert_eq(5 & 3, 1, "and")
assert_eq(5 | 3, 7, "or")
assert_eq(5 ^ 3, 6, "xor")
assert_eq(~0, -1, "not")
assert_eq(1 << 3, 8, "lshift")
assert_eq(8 >> 3, 1, "rshift")
assert_eq(-1 >>> 1, 2147483647, "unsigned rshift")
})
// === UNARY OPERATORS ===
run("unary operators", function() {
assert_eq(+5, 5, "unary plus")
assert_eq(-5, -5, "unary minus")
assert_eq(-(-5), 5, "double negate")
})
run("increment decrement", function() {
var x = 5
assert_eq(x++, 5, "postfix inc returns old")
assert_eq(x, 6, "postfix inc side effect")
x = 5
assert_eq(++x, 6, "prefix inc returns new")
x = 5
assert_eq(x--, 5, "postfix dec returns old")
assert_eq(x, 4, "postfix dec side effect")
x = 5
assert_eq(--x, 4, "prefix dec returns new")
})
// === COMPOUND ASSIGNMENT ===
run("compound assignment", function() {
var x = 10
x += 3; assert_eq(x, 13, "+=")
x -= 3; assert_eq(x, 10, "-=")
x *= 2; assert_eq(x, 20, "*=")
x /= 4; assert_eq(x, 5, "/=")
x %= 3; assert_eq(x, 2, "%=")
})
// === TERNARY OPERATOR ===
run("ternary operator", function() {
var a = true ? 1 : 2
assert_eq(a, 1, "ternary true")
var b = false ? 1 : 2
assert_eq(b, 2, "ternary false")
var c = true ? (false ? 1 : 2) : 3
assert_eq(c, 2, "ternary nested")
})
// === COMMA OPERATOR ===
run("comma operator", function() {
var x = (1, 2, 3)
assert_eq(x, 3, "comma returns last")
})
// === IN OPERATOR ===
run("in operator", function() {
var o = {a: 1}
assert_eq("a" in o, true, "key exists")
assert_eq("b" in o, false, "key missing")
})
// === DELETE OPERATOR ===
run("delete operator", function() {
var o = {a: 1, b: 2}
delete o.a
assert_eq("a" in o, false, "delete removes key")
assert_eq(o.b, 2, "delete leaves others")
})
// === PROPERTY ACCESS ===
run("dot access", function() {
var o = {x: 10}
assert_eq(o.x, 10, "dot read")
o.x = 20
assert_eq(o.x, 20, "dot write")
})
run("bracket access", function() {
var o = {x: 10}
assert_eq(o["x"], 10, "bracket read")
var key = "x"
assert_eq(o[key], 10, "computed bracket")
o["y"] = 20
assert_eq(o.y, 20, "bracket write")
})
run("object-as-key", function() {
var k = {}
var o = {}
o[k] = 42
assert_eq(o[k], 42, "object key set/get")
assert_eq(o[{}], null, "new object is different key")
assert_eq(k in o, true, "object key in")
delete o[k]
assert_eq(k in o, false, "object key delete")
})
run("chained access", function() {
var d = {a: {b: [1, {c: 99}]}}
assert_eq(d.a.b[1].c, 99, "mixed chain")
})
// === ARRAY PUSH/POP SYNTAX ===
run("array push pop", function() {
var a = [1, 2]
a[] = 3
assert_eq(length(a), 3, "push length")
assert_eq(a[2], 3, "push value")
var v = a[]
assert_eq(v, 3, "pop value")
assert_eq(length(a), 2, "pop length")
})
// === CONTROL FLOW: IF/ELSE ===
run("if else", function() {
var x = 0
if (true) x = 1
assert_eq(x, 1, "if true")
if (false) x = 2 else x = 3
assert_eq(x, 3, "if else")
if (false) x = 4
else if (true) x = 5
else x = 6
assert_eq(x, 5, "else if")
})
// === CONTROL FLOW: WHILE ===
run("while loop", function() {
var i = 0
while (i < 5) i++
assert_eq(i, 5, "while basic")
})
run("while break continue", function() {
var i = 0
while (true) {
if (i >= 3) break
i++
}
assert_eq(i, 3, "while break")
var sum = 0
i = 0
while (i < 5) {
i++
if (i % 2 == 0) continue
sum += i
}
assert_eq(sum, 9, "while continue")
})
// === CONTROL FLOW: FOR ===
run("for loop", function() {
var sum = 0
for (var i = 0; i < 5; i++) sum += i
assert_eq(sum, 10, "for basic")
})
run("for break continue", function() {
var sum = 0
for (var i = 0; i < 10; i++) {
if (i == 5) break
sum += i
}
assert_eq(sum, 10, "for break")
sum = 0
for (var i = 0; i < 5; i++) {
if (i % 2 == 0) continue
sum += i
}
assert_eq(sum, 4, "for continue")
})
run("nested for", function() {
var sum = 0
for (var i = 0; i < 3; i++)
for (var j = 0; j < 3; j++)
sum++
assert_eq(sum, 9, "nested for")
})
// === BLOCK SCOPING ===
run("block scoping", function() {
var x = 1
{
var x = 2
assert_eq(x, 2, "inner block")
}
assert_eq(x, 1, "outer preserved")
})
run("for iterator scope", function() {
for (var i = 0; i < 1; i++) {}
assert_eq(should_disrupt(function() { var y = i }), true, "for var does not leak")
})
// === FUNCTIONS ===
run("function expression", function() {
var fn = function(a, b) { return a + b }
assert_eq(fn(2, 3), 5, "basic call")
})
run("arrow function", function() {
var double = x => x * 2
assert_eq(double(5), 10, "arrow single param")
var add = (a, b) => a + b
assert_eq(add(2, 3), 5, "arrow multi param")
var block = x => {
var y = x * 2
return y + 1
}
assert_eq(block(5), 11, "arrow block body")
})
run("function no return", function() {
var fn = function() { var x = 1 }
assert_eq(fn(), null, "no return gives null")
})
run("function early return", function() {
var fn = function() { return 1; return 2 }
assert_eq(fn(), 1, "early return")
})
run("extra and missing args", function() {
var fn = function(a, b) { return a + b }
assert_eq(fn(1, 2, 3), 3, "extra args ignored")
var fn2 = function(a, b) { return a }
assert_eq(fn2(1), 1, "missing args ok")
})
run("iife", function() {
var r = (function(x) { return x * 2 })(21)
assert_eq(r, 42, "immediately invoked")
})
// === CLOSURES ===
run("closure", function() {
var make = function(x) {
return function(y) { return x + y }
}
var add5 = make(5)
assert_eq(add5(3), 8, "closure captures")
})
run("closure mutation", function() {
var counter = function() {
var n = 0
return function() { n = n + 1; return n }
}
var c = counter()
assert_eq(c(), 1, "first")
assert_eq(c(), 2, "second")
})
// === RECURSION ===
run("recursion", function() {
var fact = function(n) {
if (n <= 1) return 1
return n * fact(n - 1)
}
assert_eq(fact(5), 120, "factorial")
})
// === THIS BINDING ===
run("this binding", function() {
var obj = {
val: 10,
get: function() { return this.val }
}
assert_eq(obj.get(), 10, "method this")
})
// === DISRUPTION ===
run("disrupt keyword", function() {
assert_eq(should_disrupt(function() { disrupt }), true, "bare disrupt")
})
run("disruption handler", function() {
var x = 0
var fn = function() { x = 1 } disruption { x = 2 }
fn()
assert_eq(x, 1, "no disruption path")
var fn2 = function() { disrupt } disruption { x = 3 }
fn2()
assert_eq(x, 3, "disruption caught")
})
run("disruption re-raise", function() {
var outer_caught = false
var outer = function() {
var inner = function() { disrupt } disruption { disrupt }
inner()
} disruption {
outer_caught = true
}
outer()
assert_eq(outer_caught, true, "re-raise propagates")
})
// === PROTOTYPAL INHERITANCE ===
run("meme and proto", function() {
var parent = {x: 10}
var child = meme(parent)
assert_eq(child.x, 10, "inherited prop")
assert_eq(proto(child), parent, "proto returns parent")
child.x = 20
assert_eq(parent.x, 10, "override does not mutate parent")
})
run("meme with mixins", function() {
var p = {a: 1}
var m1 = {b: 2}
var m2 = {c: 3}
var child = meme(p, [m1, m2])
assert_eq(child.a, 1, "parent prop")
assert_eq(child.b, 2, "mixin1")
assert_eq(child.c, 3, "mixin2")
})
// === STONE (FREEZE) ===
run("stone", function() {
var o = {x: 1}
assert_eq(is_stone(o), false, "not frozen")
stone(o)
assert_eq(is_stone(o), true, "frozen")
assert_eq(should_disrupt(function() { o.x = 2 }), true, "write disrupts")
})
// === FUNCTION PROXY ===
run("function proxy", function() {
var proxy = function(name, args) {
return `${name}:${length(args)}`
}
assert_eq(proxy.hello(), "hello:0", "proxy dot call")
assert_eq(proxy.add(1, 2), "add:2", "proxy with args")
assert_eq(proxy["method"](), "method:0", "proxy bracket call")
var m = "dynamic"
assert_eq(proxy[m](), "dynamic:0", "proxy computed name")
})
run("non-proxy function prop access disrupts", function() {
var fn = function() { return 1 }
assert_eq(should_disrupt(function() { var x = fn.foo }), true, "prop read disrupts")
assert_eq(should_disrupt(function() { fn.foo = 1 }), true, "prop write disrupts")
})
// === TYPE CHECKING ===
run("is_* functions", function() {
assert_eq(is_number(42), true, "is_number")
assert_eq(is_text("hi"), true, "is_text")
assert_eq(is_logical(true), true, "is_logical")
assert_eq(is_object({}), true, "is_object")
assert_eq(is_array([]), true, "is_array")
assert_eq(is_function(function(){}), true, "is_function")
assert_eq(is_null(null), true, "is_null")
assert_eq(is_object([]), false, "array not object")
assert_eq(is_array({}), false, "object not array")
})
// === TRUTHINESS / FALSINESS ===
run("falsy values", function() {
if (false) fail("false")
if (0) fail("0")
if ("") fail("empty string")
if (null) fail("null")
assert_eq(true, true, "all falsy passed")
})
run("truthy values", function() {
if (!1) fail("1")
if (!"hi") fail("string")
if (!{}) fail("object")
if (![]) fail("array")
if (!true) fail("true")
assert_eq(true, true, "all truthy passed")
})
// === VARIABLE SHADOWING ===
run("variable shadowing", function() {
var x = 10
var fn = function() {
var x = 20
return x
}
assert_eq(fn(), 20, "inner shadows")
assert_eq(x, 10, "outer unchanged")
})
// === OPERATOR PRECEDENCE ===
run("precedence", function() {
assert_eq(2 + 3 * 4, 14, "mul before add")
assert_eq((2 + 3) * 4, 20, "parens override")
assert_eq(-2 * 3, -6, "unary before mul")
})
// === CURRYING / HIGHER-ORDER ===
run("curried function", function() {
var f = function(a) {
return function(b) {
return function(c) { return a + b + c }
}
}
assert_eq(f(1)(2)(3), 6, "triple curry")
})
// === SELF-REFERENCING STRUCTURES ===
run("self-referencing object", function() {
var o = {name: "root"}
o.self = o
assert_eq(o.self.self.name, "root", "cycle access")
})
// === IDENTIFIER ? AND ! ===
run("question mark in identifier", function() {
var nil? = (x) => x == null
assert_eq(nil?(null), true, "nil? null")
assert_eq(nil?(42), false, "nil? 42")
})
run("bang in identifier", function() {
var set! = (x) => x + 1
assert_eq(set!(5), 6, "set! call")
})
run("question mark mid identifier", function() {
var is?valid = (x) => x > 0
assert_eq(is?valid(3), true, "is?valid true")
assert_eq(is?valid(-1), false, "is?valid false")
})
run("bang mid identifier", function() {
var do!stuff = () => 42
assert_eq(do!stuff(), 42, "do!stuff call")
})
run("ternary after question ident", function() {
var nil? = (x) => x == null
var a = nil?(null) ? "yes" : "no"
assert_eq(a, "yes", "ternary true branch")
var b = nil?(42) ? "yes" : "no"
assert_eq(b, "no", "ternary false branch")
})
run("bang not confused with logical not", function() {
assert_eq(!true, false, "logical not true")
assert_eq(!false, true, "logical not false")
})
run("inequality not confused with bang ident", function() {
assert_eq(1 != 2, true, "inequality true")
assert_eq(1 != 1, false, "inequality false")
})
// === SUMMARY ===
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
if (failed > 0) {
print("")
for (var _j = 0; _j < failed; _j++) {
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
}
}

View File

@@ -9,7 +9,7 @@ var error_reasons = []
var fail_msg = "" var fail_msg = ""
// pre-allocate 500 slots to avoid array growth during disruption handlers // pre-allocate 500 slots to avoid array growth during disruption handlers
for (var _i = 0; _i < 500; _i++) { for (var _i = 0; _i < 5; _i++) {
error_names[] = null error_names[] = null
error_reasons[] = null error_reasons[] = null
} }
@@ -3369,6 +3369,182 @@ run("gc object from keys function under pressure", function() {
} }
}) })
// ============================================================================
// DEFAULT PARAMETER TESTS
// ============================================================================
run("default param constant", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1), 11, "default param constant")
})
run("default param overridden", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1, 2), 3, "default param overridden")
})
run("default param uses earlier param", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5), 6, "default param uses earlier param")
})
run("default param uses earlier param overridden", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5, 20), 20, "default param uses earlier param overridden")
})
run("multiple default params", function() {
var f = function(a, b=10, c=a+1) { return a + b + c }
assert_eq(f(1), 13, "multiple defaults f(1)")
assert_eq(f(1, 2), 5, "multiple defaults f(1,2)")
assert_eq(f(1, 2, 3), 6, "multiple defaults f(1,2,3)")
})
run("arrow function default param", function() {
var g = (x, y=100) => x + y
assert_eq(g(5), 105, "arrow default param")
assert_eq(g(5, 20), 25, "arrow default param overridden")
})
run("default param null passed explicitly", function() {
var f = function(a, b=10) { return b }
assert_eq(f(1, null), 10, "explicit null triggers default")
})
run("default param with string", function() {
var f = function(a, b="hello") { return b }
assert_eq(f(1), "hello", "default string param")
assert_eq(f(1, "world"), "world", "default string overridden")
})
run("default param first param has no default", function() {
var f = function(a, b=42) { return a }
assert_eq(f(7), 7, "first param no default")
})
// ============================================================================
// FUNCTINO TESTS
// ============================================================================
run("functino +! addition", function() {
assert_eq(+!(3, 4), 7, "+! addition")
})
run("functino -! subtraction", function() {
assert_eq(-!(10, 3), 7, "-! subtraction")
})
run("functino *! multiplication", function() {
assert_eq(*!(5, 6), 30, "*! multiplication")
})
run("functino /! division", function() {
assert_eq(/!(10, 2), 5, "/! division")
})
run("functino %! modulo", function() {
assert_eq(%!(10, 3), 1, "%! modulo")
})
run("functino **! power", function() {
assert_eq(**!(2, 10), 1024, "**! power")
})
run("functino <! less than", function() {
assert_eq(<!(3, 5), true, "<! true")
assert_eq(<!(5, 3), false, "<! false")
assert_eq(<!(3, 3), false, "<! equal")
})
run("functino >! greater than", function() {
assert_eq(>!(5, 3), true, ">! true")
assert_eq(>!(3, 5), false, ">! false")
assert_eq(>!(3, 3), false, ">! equal")
})
run("functino <=! less or equal", function() {
assert_eq(<=!(3, 5), true, "<=! less")
assert_eq(<=!(3, 3), true, "<=! equal")
assert_eq(<=!(5, 3), false, "<=! greater")
})
run("functino >=! greater or equal", function() {
assert_eq(>=!(5, 3), true, ">=! greater")
assert_eq(>=!(3, 3), true, ">=! equal")
assert_eq(>=!(3, 5), false, ">=! less")
})
run("functino =! equality", function() {
assert_eq(=!(5, 5), true, "=! true")
assert_eq(=!(5, 3), false, "=! false")
})
run("functino !=! inequality", function() {
assert_eq(!=!(5, 3), true, "!=! true")
assert_eq(!=!(5, 5), false, "!=! false")
})
run("functino =! with tolerance", function() {
assert_eq(=!(1.0, 1.0001, 0.001), true, "=! within tolerance")
assert_eq(=!(1.0, 1.01, 0.001), false, "=! outside tolerance")
})
run("functino !=! with tolerance", function() {
assert_eq(!=!(1.0, 1.01, 0.001), true, "!=! outside tolerance")
assert_eq(!=!(1.0, 1.0001, 0.001), false, "!=! within tolerance")
})
run("functino &! bitwise and", function() {
assert_eq(&!(0xff, 0x0f), 0x0f, "&! bitwise and")
})
run("functino |! bitwise or", function() {
assert_eq(|!(0xf0, 0x0f), 0xff, "|! bitwise or")
})
run("functino ^! bitwise xor", function() {
assert_eq(^!(0xff, 0x0f), 0xf0, "^! bitwise xor")
})
run("functino <<! shift left", function() {
assert_eq(<<!(1, 4), 16, "<<! shift left")
})
run("functino >>! shift right", function() {
assert_eq(>>!(16, 4), 1, ">>! shift right")
})
run("functino ~! bitwise not", function() {
assert_eq(~!(0), -1, "~! bitwise not 0")
})
run("functino []! array index", function() {
var arr = [10, 20, 30]
assert_eq([]!(arr, 0), 10, "[]! index 0")
assert_eq([]!(arr, 1), 20, "[]! index 1")
assert_eq([]!(arr, 2), 30, "[]! index 2")
})
run("functino &&! logical and", function() {
assert_eq(&&!(true, true), true, "&&! true true")
assert_eq(&&!(true, false), false, "&&! true false")
assert_eq(&&!(false, true), false, "&&! false true")
assert_eq(&&!(1, 2), 2, "&&! truthy returns right")
assert_eq(&&!(0, 2), 0, "&&! falsy returns left")
})
run("functino ||! logical or", function() {
assert_eq(||!(false, true), true, "||! false true")
assert_eq(||!(true, false), true, "||! true false")
assert_eq(||!(false, false), false, "||! false false")
assert_eq(||!(0, 5), 5, "||! falsy returns right")
assert_eq(||!(3, 5), 3, "||! truthy returns left")
})
run("functino >>>! unsigned shift right", function() {
assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right")
})
// ============================================================================ // ============================================================================
// SUMMARY // SUMMARY
// ============================================================================ // ============================================================================