Compare commits
24 Commits
87fafa44c8
...
mcode2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de0659474 | ||
|
|
27a9b72b07 | ||
|
|
a3622bd5bd | ||
|
|
2f6700415e | ||
|
|
243d92f7f3 | ||
|
|
8f9d026b9b | ||
|
|
2c9ac8f7b6 | ||
|
|
80f24e131f | ||
|
|
a8f8af7662 | ||
|
|
f5b3494762 | ||
|
|
13a6f6c79d | ||
|
|
1a925371d3 | ||
|
|
08d2bacb1f | ||
|
|
7322153e57 | ||
|
|
cc72c4cb0f | ||
|
|
ae1f09a28f | ||
|
|
3c842912a1 | ||
|
|
7cacf32078 | ||
|
|
b740612761 | ||
|
|
6001c2b4bb | ||
|
|
98625fa15b | ||
|
|
45ce76aef7 | ||
|
|
3621b1ef33 | ||
|
|
0ae59705d4 |
122
source/cell.c
122
source/cell.c
@@ -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)) {
|
||||||
|
|||||||
3461
source/quickjs.c
3461
source/quickjs.c
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
657
syntax_suite.ce
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
178
vm_suite.ce
178
vm_suite.ce
@@ -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
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user