Merge branch 'mach' into mcode2
This commit is contained in:
2
Makefile
2
Makefile
@@ -58,7 +58,7 @@ static:
|
||||
# Bootstrap: build cell from scratch using meson (only needed once)
|
||||
# Also installs core scripts to ~/.cell/core
|
||||
bootstrap:
|
||||
meson setup build_bootstrap -Dbuildtype=debug -Db_sanitize=address
|
||||
meson setup build_bootstrap -Dbuildtype=debugoptimized
|
||||
meson compile -C build_bootstrap
|
||||
cp build_bootstrap/cell .
|
||||
cp build_bootstrap/libcell_runtime.dylib .
|
||||
|
||||
@@ -105,15 +105,11 @@ static char* load_core_file(const char *filename, size_t *out_size) {
|
||||
return data;
|
||||
}
|
||||
|
||||
static int print_json_errors(const char *json) {
|
||||
if (!json) return 0;
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
static int print_tree_errors(cJSON *root) {
|
||||
if (!root) return 0;
|
||||
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
|
||||
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) {
|
||||
cJSON_Delete(root);
|
||||
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0)
|
||||
return 0;
|
||||
}
|
||||
const char *filename = "<unknown>";
|
||||
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
|
||||
if (cJSON_IsString(fname))
|
||||
@@ -130,10 +126,18 @@ static int print_json_errors(const char *json) {
|
||||
else if (msg)
|
||||
fprintf(stderr, "%s: error: %s\n", filename, msg);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
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
|
||||
const char* cell_get_core_path(void) {
|
||||
return core_path;
|
||||
@@ -399,11 +403,13 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
char *json = JS_AST(script, strlen(script), filename);
|
||||
if (json) {
|
||||
int has_errors = print_json_errors(json);
|
||||
printf("%s\n", json);
|
||||
free(json);
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (ast) {
|
||||
int has_errors = print_tree_errors(ast);
|
||||
char *pretty = cJSON_Print(ast);
|
||||
cJSON_Delete(ast);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
free(allocated_script);
|
||||
return has_errors ? 1 : 0;
|
||||
} else {
|
||||
@@ -445,8 +451,14 @@ int cell_init(int argc, char **argv)
|
||||
char *json = JS_Tokenize(script, strlen(script), filename);
|
||||
if (json) {
|
||||
int has_errors = print_json_errors(json);
|
||||
printf("%s\n", json);
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
free(json);
|
||||
if (root) {
|
||||
char *pretty = cJSON_Print(root);
|
||||
cJSON_Delete(root);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
}
|
||||
free(allocated_script);
|
||||
return has_errors ? 1 : 0;
|
||||
} else {
|
||||
@@ -507,8 +519,14 @@ int cell_init(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s\n", mcode_json);
|
||||
cJSON *root = cJSON_Parse(mcode_json);
|
||||
free(mcode_json);
|
||||
if (root) {
|
||||
char *pretty = cJSON_Print(root);
|
||||
cJSON_Delete(root);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
}
|
||||
|
||||
free(allocated_script);
|
||||
return 0;
|
||||
@@ -629,15 +647,15 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json);
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
@@ -645,18 +663,18 @@ int cell_init(int argc, char **argv)
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
free(ast_json); free(allocated_script);
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
if (!ctx) {
|
||||
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;
|
||||
}
|
||||
|
||||
JS_DumpMach(ctx, ast_json, JS_NULL);
|
||||
free(ast_json);
|
||||
JS_DumpMachTree(ctx, ast, JS_NULL);
|
||||
cJSON_Delete(ast);
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
@@ -693,15 +711,15 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json);
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
@@ -709,18 +727,18 @@ int cell_init(int argc, char **argv)
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
free(ast_json); free(allocated_script);
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
if (!ctx) {
|
||||
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;
|
||||
}
|
||||
|
||||
JSValue result = JS_RunMach(ctx, ast_json, JS_NULL);
|
||||
free(ast_json);
|
||||
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
|
||||
cJSON_Delete(ast);
|
||||
|
||||
int exit_code = 0;
|
||||
if (JS_IsException(result)) {
|
||||
|
||||
100
source/quickjs.c
100
source/quickjs.c
@@ -28409,6 +28409,7 @@ typedef struct ASTParseState {
|
||||
int has_error;
|
||||
int in_disruption;
|
||||
char *decoded_str; /* allocated buffer for decoded string escapes */
|
||||
GetLineColCache lc_cache;
|
||||
union {
|
||||
struct {
|
||||
const char *str;
|
||||
@@ -28434,11 +28435,12 @@ typedef struct ASTParseState {
|
||||
|
||||
/* Add a length-delimited string to a cJSON object (source pointers aren't null-terminated) */
|
||||
static void cjson_add_strn (cJSON *obj, const char *key, const char *str, size_t len) {
|
||||
char *tmp = sys_malloc (len + 1);
|
||||
char buf[256];
|
||||
char *tmp = (len < sizeof (buf)) ? buf : sys_malloc (len + 1);
|
||||
memcpy (tmp, str, len);
|
||||
tmp[len] = '\0';
|
||||
cJSON_AddStringToObject (obj, key, tmp);
|
||||
sys_free (tmp);
|
||||
if (tmp != buf) sys_free (tmp);
|
||||
}
|
||||
|
||||
/* Compare a length-delimited token string against a null-terminated literal */
|
||||
@@ -28488,19 +28490,7 @@ static void ast_free_token (ASTParseState *s) {
|
||||
}
|
||||
|
||||
static void ast_get_line_col (ASTParseState *s, const uint8_t *ptr, int *line, int *col) {
|
||||
int line_num = 0, col_num = 0;
|
||||
const uint8_t *p = s->buf_start;
|
||||
while (p < ptr) {
|
||||
if (*p == '\n') {
|
||||
line_num++;
|
||||
col_num = 0;
|
||||
} else if (*p < 0x80 || *p >= 0xc0) {
|
||||
col_num++;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
*line = line_num;
|
||||
*col = col_num;
|
||||
*line = get_line_col_cached (&s->lc_cache, col, ptr);
|
||||
}
|
||||
|
||||
static cJSON *ast_node (ASTParseState *s, const char *kind, const uint8_t *start_ptr) {
|
||||
@@ -29828,16 +29818,9 @@ static const ASTBinOp ast_binops[] = {
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static int ast_get_binop_prec (int token) {
|
||||
static const ASTBinOp *ast_get_binop (int token) {
|
||||
for (int i = 0; ast_binops[i].kind; i++) {
|
||||
if (ast_binops[i].token == token) return ast_binops[i].prec;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *ast_get_binop_kind (int token) {
|
||||
for (int i = 0; ast_binops[i].kind; i++) {
|
||||
if (ast_binops[i].token == token) return ast_binops[i].kind;
|
||||
if (ast_binops[i].token == token) return &ast_binops[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -29848,17 +29831,16 @@ static cJSON *ast_parse_binary (ASTParseState *s, int min_prec) {
|
||||
|
||||
for (;;) {
|
||||
const uint8_t *start = s->token_ptr;
|
||||
int prec = ast_get_binop_prec (s->token_val);
|
||||
if (prec == 0 || prec < min_prec) break;
|
||||
const ASTBinOp *op = ast_get_binop (s->token_val);
|
||||
if (!op || op->prec < min_prec) break;
|
||||
|
||||
const char *kind = ast_get_binop_kind (s->token_val);
|
||||
ast_next_token (s);
|
||||
|
||||
/* Right associativity for ** */
|
||||
int next_prec = (prec == 14) ? prec : prec + 1;
|
||||
int next_prec = (op->prec == 14) ? op->prec : op->prec + 1;
|
||||
cJSON *right = ast_parse_binary (s, next_prec);
|
||||
|
||||
cJSON *node = ast_node (s, kind, start);
|
||||
cJSON *node = ast_node (s, op->kind, start);
|
||||
cJSON_AddItemToObject (node, "left", left);
|
||||
cJSON_AddItemToObject (node, "right", right);
|
||||
ast_node_end (s, node, s->buf_ptr);
|
||||
@@ -31271,7 +31253,7 @@ static void ast_semantic_check (cJSON *ast, cJSON **errors_out,
|
||||
*intrinsics_out = intr_arr;
|
||||
}
|
||||
|
||||
char *JS_AST (const char *source, size_t len, const char *filename) {
|
||||
cJSON *JS_ASTTree (const char *source, size_t len, const char *filename) {
|
||||
ASTParseState s;
|
||||
memset (&s, 0, sizeof (s));
|
||||
|
||||
@@ -31282,6 +31264,8 @@ char *JS_AST (const char *source, size_t len, const char *filename) {
|
||||
s.function_nr = 1;
|
||||
s.errors = NULL;
|
||||
s.has_error = 0;
|
||||
s.lc_cache.ptr = s.buf_start;
|
||||
s.lc_cache.buf_start = s.buf_start;
|
||||
|
||||
/* Get first token */
|
||||
ast_next_token (&s);
|
||||
@@ -31321,10 +31305,14 @@ char *JS_AST (const char *source, size_t len, const char *filename) {
|
||||
cJSON_AddItemToObject (ast, "errors", sem_errors);
|
||||
}
|
||||
|
||||
/* Convert to JSON string */
|
||||
char *json = cJSON_Print (ast);
|
||||
cJSON_Delete (ast);
|
||||
return ast;
|
||||
}
|
||||
|
||||
char *JS_AST (const char *source, size_t len, const char *filename) {
|
||||
cJSON *ast = JS_ASTTree (source, len, filename);
|
||||
if (!ast) return NULL;
|
||||
char *json = cJSON_PrintUnformatted (ast);
|
||||
cJSON_Delete (ast);
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -31425,7 +31413,7 @@ char *JS_Tokenize (const char *source, size_t len, const char *filename) {
|
||||
cJSON_AddItemToObject (root, "errors", s.errors);
|
||||
}
|
||||
|
||||
char *json = cJSON_Print (root);
|
||||
char *json = cJSON_PrintUnformatted (root);
|
||||
cJSON_Delete (root);
|
||||
return json;
|
||||
}
|
||||
@@ -33137,8 +33125,7 @@ static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) {
|
||||
}
|
||||
|
||||
/* Public API: compile AST JSON to MachCode (context-free) */
|
||||
MachCode *JS_CompileMach(const char *ast_json) {
|
||||
cJSON *ast = cJSON_Parse(ast_json);
|
||||
MachCode *JS_CompileMachTree(cJSON *ast) {
|
||||
if (!ast) return NULL;
|
||||
|
||||
MachCompState cs = {0};
|
||||
@@ -33152,6 +33139,13 @@ MachCode *JS_CompileMach(const char *ast_json) {
|
||||
sys_free(cs.vars[i].name);
|
||||
sys_free(cs.vars);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
MachCode *JS_CompileMach(const char *ast_json) {
|
||||
cJSON *ast = cJSON_Parse(ast_json);
|
||||
if (!ast) return NULL;
|
||||
MachCode *code = JS_CompileMachTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
return code;
|
||||
}
|
||||
@@ -35830,7 +35824,7 @@ char *JS_Mcode (const char *ast_json) {
|
||||
if (s.errors)
|
||||
cJSON_AddItemToObject (mach, "errors", s.errors);
|
||||
|
||||
char *json = cJSON_Print (mach);
|
||||
char *json = cJSON_PrintUnformatted (mach);
|
||||
cJSON_Delete (mach);
|
||||
return json;
|
||||
}
|
||||
@@ -37924,9 +37918,9 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent)
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump MACH bytecode to stdout for debugging. Takes AST JSON. */
|
||||
void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
/* Dump MACH bytecode to stdout for debugging. Takes AST cJSON tree. */
|
||||
void JS_DumpMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
if (!mc) {
|
||||
printf("=== MACH Bytecode ===\nFailed to compile\n=== End MACH Bytecode ===\n");
|
||||
return;
|
||||
@@ -37938,9 +37932,19 @@ void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
printf("=== End MACH Bytecode ===\n");
|
||||
}
|
||||
|
||||
/* Compile and execute MACH bytecode. Takes AST JSON. */
|
||||
JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
cJSON *ast = cJSON_Parse(ast_json);
|
||||
if (!ast) {
|
||||
printf("=== MACH Bytecode ===\nFailed to parse\n=== End MACH Bytecode ===\n");
|
||||
return;
|
||||
}
|
||||
JS_DumpMachTree(ctx, ast, env);
|
||||
cJSON_Delete(ast);
|
||||
}
|
||||
|
||||
/* Compile and execute MACH bytecode. Takes AST cJSON tree. */
|
||||
JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
if (!mc) {
|
||||
return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode");
|
||||
}
|
||||
@@ -37949,3 +37953,13 @@ JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) {
|
||||
cJSON *ast = cJSON_Parse(ast_json);
|
||||
if (!ast) {
|
||||
return JS_ThrowSyntaxError(ctx, "failed to parse AST JSON");
|
||||
}
|
||||
JSValue result = JS_RunMachTree(ctx, ast, env);
|
||||
cJSON_Delete(ast);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1218,37 +1218,43 @@ CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_
|
||||
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);
|
||||
|
||||
/* 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.
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error.
|
||||
No JSContext needed — pure string transformation. */
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error. */
|
||||
char *JS_AST (const char *source, size_t len, const char *filename);
|
||||
|
||||
/* Tokenize source code and return token array as JSON string.
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error.
|
||||
No JSContext needed — pure string transformation. */
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error. */
|
||||
char *JS_Tokenize (const char *source, size_t len, const char *filename);
|
||||
|
||||
/* Compiled bytecode (context-free, serializable) */
|
||||
typedef struct MachCode MachCode;
|
||||
|
||||
/* Compile AST JSON to context-free MachCode.
|
||||
Returns MachCode* (caller must free with JS_FreeMachCode), or NULL on error. */
|
||||
/* Compile AST cJSON tree to context-free MachCode. */
|
||||
MachCode *JS_CompileMachTree(struct cJSON *ast);
|
||||
|
||||
/* Compile AST JSON string to context-free MachCode. */
|
||||
MachCode *JS_CompileMach(const char *ast_json);
|
||||
|
||||
/* Free a compiled MachCode tree. */
|
||||
void JS_FreeMachCode(MachCode *mc);
|
||||
|
||||
/* Load compiled MachCode into a JSContext, materializing JSValues.
|
||||
Returns JSCodeRegister* linked and ready for execution. */
|
||||
/* Load compiled MachCode into a JSContext, materializing JSValues. */
|
||||
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
|
||||
|
||||
/* Dump MACH bytecode to stdout for debugging. Takes AST JSON.
|
||||
Internally compiles, loads, and dumps binary bytecode. */
|
||||
/* Dump MACH bytecode to stdout. Takes AST cJSON tree. */
|
||||
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);
|
||||
|
||||
/* Compile and execute MACH bytecode. Takes AST JSON.
|
||||
Internally compiles, loads, and executes.
|
||||
Returns result of execution, or JS_EXCEPTION on error. */
|
||||
/* Compile and execute MACH bytecode from AST cJSON tree. */
|
||||
JSValue JS_RunMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
|
||||
|
||||
/* Compile and execute MACH bytecode from AST JSON string. */
|
||||
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
|
||||
|
||||
/* Compile AST JSON to MCODE JSON (string-based IR).
|
||||
|
||||
@@ -2393,22 +2393,22 @@ TEST(ast_sem_nested_function_scope) {
|
||||
|
||||
TEST(mach_compile_basic) {
|
||||
const char *src = "var x = 1; x = x + 1";
|
||||
char *ast_json = JS_AST(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
free(ast_json);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
|
||||
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
|
||||
JS_FreeMachCode(mc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(mach_compile_function) {
|
||||
const char *src = "function f(x) { return x + 1 }";
|
||||
char *ast_json = JS_AST(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
free(ast_json);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
|
||||
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
|
||||
JS_FreeMachCode(mc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
615
syntax_suite.ce
Normal file
615
syntax_suite.ce
Normal file
@@ -0,0 +1,615 @@
|
||||
// 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")
|
||||
})
|
||||
|
||||
// === 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])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user