Compare commits
54 Commits
8cf98d8a9e
...
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 | ||
|
|
87fafa44c8 | ||
|
|
45ce76aef7 | ||
|
|
32fb44857c | ||
|
|
31d67f6710 | ||
|
|
3621b1ef33 | ||
|
|
836227c8d3 | ||
|
|
0ae59705d4 | ||
|
|
8e2607b6ca | ||
|
|
dc73e86d8c | ||
|
|
555cceb9d6 | ||
|
|
fbb7933eb6 | ||
|
|
0287d6ada4 | ||
|
|
73cd6a255d | ||
|
|
83ea67c01b | ||
|
|
16059cca4e | ||
|
|
9ffe60ebef | ||
|
|
2beafec5d9 | ||
|
|
aba8eb66bd | ||
|
|
1abcaa92c7 | ||
|
|
168f7c71d5 | ||
|
|
56ed895b6e | ||
|
|
1e4646999d | ||
|
|
68d6c907fe | ||
|
|
8150c64c7d | ||
|
|
024d796ca4 | ||
|
|
ea185dbffd | ||
|
|
6571262af0 | ||
|
|
77ae133747 | ||
|
|
142a2d518b | ||
|
|
5b65c64fe5 | ||
|
|
e985fa5fe1 | ||
|
|
160ade2410 | ||
|
|
e2bc5948c1 |
287
docs/pitcode.md
Normal file
287
docs/pitcode.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Pitmachine
|
||||
A pitmachine is an abstract register machine for executing pitcode.
|
||||
|
||||
The pitmachine assembly language is JSON. The instructions field contains an array of labels and instructions. Labels are the targets of branch instructions. They are simple text. Slots are elements in an activation frame. They are designated by a small positive integer. Slots are the general registers of the pitmachine. Slots hold the arguments, variables, and temporaries of a function invocation.
|
||||
|
||||
{
|
||||
"name": "program_name",
|
||||
"data": {⸳⸳⸳},
|
||||
"source": "...",
|
||||
The labels record associates labels with tokens for use by debuggers.
|
||||
|
||||
"labels": {
|
||||
"entry": token,
|
||||
"beyond": token,
|
||||
...
|
||||
},
|
||||
The instructions array is a list of instructions and labels.
|
||||
|
||||
instructions: [
|
||||
A statement label:
|
||||
|
||||
"entry",
|
||||
go to beyond:
|
||||
|
||||
["jump", "beyond"],
|
||||
assign slot 8: pi / 2
|
||||
|
||||
["access", 13, {"kind": "name", "name": "pi", "make": "intrinsic", ⸳⸳⸳}],
|
||||
["int", 14, 2],
|
||||
["divide", 8, 13, 14],
|
||||
⸳⸳⸳
|
||||
"beyond"
|
||||
⸳⸳⸳
|
||||
]
|
||||
}
|
||||
|
||||
## Pitcode instructions
|
||||
This is a register based machine.
|
||||
|
||||
### Arithmetic
|
||||
Arithmetic instructions perform operations on numbers. All other cases disrupt (except add, which is polymorphic).
|
||||
|
||||
"add", dest, left, right // dest, left, and right are numbers designating slots in the current activation frame. Also works on texts (concatenation).
|
||||
|
||||
"subtract", dest, left, right
|
||||
|
||||
"multiply", dest, left, right
|
||||
|
||||
"divide", dest, left, right
|
||||
|
||||
"integer_divide", dest, left, right
|
||||
|
||||
"modulo", dest, left, right
|
||||
|
||||
"remainder", dest, left, right
|
||||
|
||||
"max", dest, left, right
|
||||
|
||||
"min", dest, left, right
|
||||
|
||||
"abs", dest, right
|
||||
|
||||
"neg", dest, right
|
||||
|
||||
"sign", dest, right
|
||||
|
||||
"fraction", dest, right
|
||||
|
||||
"integer", dest, right
|
||||
|
||||
"ceiling", dest, right, place
|
||||
|
||||
"floor", dest, right, place
|
||||
|
||||
"round", dest, right, place
|
||||
|
||||
"trunc", dest, right, place
|
||||
|
||||
### Text
|
||||
Text instructions perform operations on texts. All other cases disrupt.
|
||||
|
||||
"concat", dest, left, right // concat two texts
|
||||
|
||||
"concat_space", dest, left, right
|
||||
|
||||
"character", dest, right
|
||||
|
||||
"codepoint", dest, right
|
||||
|
||||
"length", dest, right
|
||||
|
||||
"lower", dest, right
|
||||
|
||||
"upper", dest, right
|
||||
|
||||
"append", pretext, right
|
||||
|
||||
Append the right text to the pretext, forwarding and growing its capacity if necessary.
|
||||
|
||||
### Comparison
|
||||
Comparison instructions perform operations on texts or numbers. All other cases disrupt.
|
||||
|
||||
"eq", dest, left, right
|
||||
|
||||
"ne", dest, left, right
|
||||
|
||||
"lt", dest, left, right
|
||||
|
||||
"le", dest, left, right
|
||||
|
||||
"gt", dest, left, right
|
||||
|
||||
"ge", dest, left, right
|
||||
|
||||
### Logical
|
||||
|
||||
"not", dest, right
|
||||
|
||||
### Bitwise
|
||||
Bitwise instructions convert operands to 32-bit integers. Non-numbers disrupt.
|
||||
|
||||
"bitand", dest, left, right
|
||||
|
||||
"bitor", dest, left, right
|
||||
|
||||
"bitxor", dest, left, right
|
||||
|
||||
"bitnot", dest, right
|
||||
|
||||
"shl", dest, left, right
|
||||
|
||||
"shr", dest, left, right
|
||||
|
||||
"ushr", dest, left, right
|
||||
|
||||
### Function
|
||||
|
||||
"frame", dest, func, nr_args
|
||||
|
||||
Prepare to invoke the func object. If the nr_args is too large, disrupt. Allocate the new activation frame. Put the current frame pointer into it.
|
||||
|
||||
"goframe", dest, func, nr_args
|
||||
|
||||
Same as frame, except that the current frame is reused if it is large enough.
|
||||
|
||||
"invoke", frame
|
||||
|
||||
Store the next instruction address in the current frame. Make the new frame the current frame. Jump to the entry point.
|
||||
|
||||
"goinvoke", frame
|
||||
|
||||
"apply", func, array
|
||||
|
||||
"return", value
|
||||
|
||||
"return_value", dest
|
||||
|
||||
"setarg", frame, slot, value // set the slot of frame to value
|
||||
|
||||
### Branching
|
||||
|
||||
"jump", label
|
||||
|
||||
"jump_true", slot, label
|
||||
|
||||
If the value in the slot is true, jump to the label. Otherwise, continue with the next instruction.
|
||||
|
||||
"jump_false": slot, label
|
||||
|
||||
If the value in the slot is false, jump to the label. Otherwise, continue with the next instruction.
|
||||
|
||||
"jump_null": slot, label
|
||||
|
||||
"jump_empty": slot, label
|
||||
|
||||
"wary_true", slot, label
|
||||
|
||||
If the value in the slot is true, jump to the label. If the value is false, continue with the next instruction. Otherwise disrupt because of a type error.
|
||||
|
||||
"wary_false": slot, label
|
||||
|
||||
If the value in the slot is false, jump to the label. If the value is true, continue with the next instruction. Otherwise disrupt because of a type error.
|
||||
|
||||
### Sensory
|
||||
|
||||
Does the right slot contain a value of the indicated type?
|
||||
|
||||
"array?", dest, right
|
||||
|
||||
"blob?", dest, right
|
||||
|
||||
"character?", dest, right
|
||||
|
||||
"data?", dest, right
|
||||
|
||||
"digit?", dest, right
|
||||
|
||||
"false?", dest, right
|
||||
|
||||
"fit?", dest, right
|
||||
|
||||
"function?", dest, right
|
||||
|
||||
"integer?", dest, right
|
||||
|
||||
"letter?", dest, right
|
||||
|
||||
"logical?", dest, right
|
||||
|
||||
"null?", dest, right
|
||||
|
||||
"pattern?", dest, right
|
||||
|
||||
"record?", dest, right
|
||||
|
||||
"stone?", dest, right
|
||||
|
||||
"text?", dest, right
|
||||
|
||||
"true?", dest, right
|
||||
|
||||
"upper?", dest, right
|
||||
|
||||
"whitespace?", dest, right
|
||||
|
||||
### Potpourri
|
||||
|
||||
"stone", dest, right // stone an object
|
||||
|
||||
"true", dest
|
||||
|
||||
"false", dest
|
||||
|
||||
"null", dest
|
||||
|
||||
"move", dest, right
|
||||
|
||||
"int", dest, small_int
|
||||
|
||||
"access", dest, literal
|
||||
|
||||
This is used to access values (numbers, texts) from the program's immutable memory. The literal is a number or text.
|
||||
|
||||
"load", dest, object, subscript
|
||||
|
||||
This is used to load values from records and arrays.
|
||||
|
||||
"store", dest, object, subscript
|
||||
|
||||
This is used to store values into records and arrays.
|
||||
|
||||
"delete", object, subscript
|
||||
|
||||
used to delete a field from a record.
|
||||
|
||||
"get", dest, slot, level
|
||||
|
||||
This is used to get values from slots in outer frames.
|
||||
|
||||
"put", value, slot, level
|
||||
|
||||
This is used to store values into slots in outer frames.
|
||||
|
||||
"push", array, slot
|
||||
|
||||
Append to a mutable array.
|
||||
|
||||
"pop", dest, array
|
||||
|
||||
Remove the last element of a mutable array, putting the element into dest.
|
||||
|
||||
"disrupt"
|
||||
|
||||
### Make
|
||||
|
||||
"array", dest, nr_elements
|
||||
|
||||
"blob", dest, nr_bits
|
||||
|
||||
"function", dest, code_name_text
|
||||
|
||||
"pretext", dest, nr_characters
|
||||
|
||||
A pretext must be converted to text by stone before it can leave the function scope.
|
||||
|
||||
"record", dest, nr_elements
|
||||
|
||||
152
source/cell.c
152
source/cell.c
@@ -105,35 +105,46 @@ 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))
|
||||
filename = fname->valuestring;
|
||||
int prev_line = -1;
|
||||
const char *prev_msg = NULL;
|
||||
cJSON *e;
|
||||
cJSON_ArrayForEach(e, errors) {
|
||||
const char *msg = cJSON_GetStringValue(
|
||||
cJSON_GetObjectItemCaseSensitive(e, "message"));
|
||||
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
|
||||
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))
|
||||
fprintf(stderr, "%s:%d:%d: error: %s\n",
|
||||
filename, (int)line->valuedouble, (int)col->valuedouble, msg);
|
||||
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 +410,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 +458,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 {
|
||||
@@ -485,30 +504,32 @@ 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;
|
||||
}
|
||||
|
||||
char *mcode_json = JS_Mcode(ast_json);
|
||||
free(ast_json);
|
||||
cJSON *mcode = JS_McodeTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
|
||||
if (!mcode_json) {
|
||||
if (!mcode) {
|
||||
printf("Failed to generate MCODE\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s\n", mcode_json);
|
||||
free(mcode_json);
|
||||
char *pretty = cJSON_Print(mcode);
|
||||
cJSON_Delete(mcode);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
|
||||
free(allocated_script);
|
||||
return 0;
|
||||
@@ -536,45 +557,58 @@ 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); free(allocated_script);
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *mcode_json = JS_Mcode(ast_json);
|
||||
free(ast_json);
|
||||
cJSON *mcode = JS_McodeTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
|
||||
if (!mcode_json) {
|
||||
if (!mcode) {
|
||||
printf("Failed to generate MCODE\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_json_errors(mcode_json)) {
|
||||
free(mcode_json); free(allocated_script);
|
||||
if (print_tree_errors(mcode)) {
|
||||
cJSON_Delete(mcode); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Use a larger heap context for execution */
|
||||
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);
|
||||
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);
|
||||
free(mcode_json);
|
||||
JSValue result = JS_CallMcodeTree(ctx, mcode); /* takes ownership of mcode */
|
||||
|
||||
if (JS_IsException(result)) {
|
||||
JSValue exc = JS_GetException(ctx);
|
||||
const char *str = JS_ToCString(ctx, exc);
|
||||
if (str) { printf("Exception: %s\n", str); JS_FreeCString(ctx, str); }
|
||||
if (str) { printf("Error: %s\n", str); JS_FreeCString(ctx, str); }
|
||||
cJSON *stack = JS_GetStack(ctx);
|
||||
if (stack) {
|
||||
int n = cJSON_GetArraySize(stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *fr = cJSON_GetArrayItem(stack, i);
|
||||
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
|
||||
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
|
||||
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
|
||||
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
|
||||
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
|
||||
}
|
||||
cJSON_Delete(stack);
|
||||
}
|
||||
JS_FreeValue(ctx, exc);
|
||||
} else if (!JS_IsNull(result)) {
|
||||
const char *str = JS_ToCString(ctx, result);
|
||||
if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); }
|
||||
@@ -615,15 +649,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;
|
||||
}
|
||||
@@ -631,18 +665,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);
|
||||
@@ -679,15 +713,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;
|
||||
}
|
||||
@@ -695,18 +729,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)) {
|
||||
@@ -716,6 +750,20 @@ int cell_init(int argc, char **argv)
|
||||
printf("Error: %s\n", err_str);
|
||||
JS_FreeCString(ctx, err_str);
|
||||
}
|
||||
cJSON *stack = JS_GetStack(ctx);
|
||||
if (stack) {
|
||||
int n = cJSON_GetArraySize(stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *fr = cJSON_GetArrayItem(stack, i);
|
||||
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
|
||||
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
|
||||
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
|
||||
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
|
||||
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
|
||||
}
|
||||
cJSON_Delete(stack);
|
||||
}
|
||||
JS_FreeValue(ctx, exc);
|
||||
exit_code = 1;
|
||||
} else if (!JS_IsNull(result)) {
|
||||
const char *str = JS_ToCString(ctx, result);
|
||||
|
||||
7098
source/quickjs.c
7098
source/quickjs.c
File diff suppressed because it is too large
Load Diff
@@ -1218,48 +1218,65 @@ 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).
|
||||
Returns malloc'd JSON string, or NULL on error. Caller must free.
|
||||
No JSContext needed — pure string transformation. */
|
||||
/* Compile AST cJSON tree to MCODE cJSON tree.
|
||||
Caller must call cJSON_Delete() on result. */
|
||||
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);
|
||||
|
||||
/* 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. */
|
||||
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
|
||||
|
||||
/* Get stack trace as cJSON array of frame objects.
|
||||
Returns NULL if no register VM frame is active.
|
||||
Caller must call cJSON_Delete() on the result. */
|
||||
struct cJSON *JS_GetStack (JSContext *ctx);
|
||||
|
||||
/* Integrate a CellModule with an environment and execute.
|
||||
Returns callable function value, or JS_EXCEPTION on error. */
|
||||
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
39
tests/demo.ce
Normal file
39
tests/demo.ce
Normal file
@@ -0,0 +1,39 @@
|
||||
function safe_add(a, b) {
|
||||
return a + b
|
||||
} disruption {
|
||||
print("disruption caught in safe_add")
|
||||
}
|
||||
|
||||
function inner() {
|
||||
disrupt
|
||||
}
|
||||
|
||||
function outer() {
|
||||
inner()
|
||||
} disruption {
|
||||
print("disruption caught in outer — from inner()")
|
||||
}
|
||||
|
||||
// Test 1: explicit disrupt with handler
|
||||
function test_explicit() {
|
||||
disrupt
|
||||
} disruption {
|
||||
print("test 1: explicit disrupt handled")
|
||||
}
|
||||
test_explicit()
|
||||
|
||||
// Test 2: type error disrupt (number + function)
|
||||
safe_add(1, print)
|
||||
|
||||
// Test 3: unwinding — inner disrupts, outer catches
|
||||
outer()
|
||||
|
||||
// Test 4: disrupt from inside disruption clause
|
||||
function test_nested() {
|
||||
disrupt
|
||||
} disruption {
|
||||
print("test 4: first disruption")
|
||||
}
|
||||
test_nested()
|
||||
|
||||
print("done")
|
||||
3558
vm_suite.ce
Normal file
3558
vm_suite.ce
Normal file
File diff suppressed because it is too large
Load Diff
@@ -252,29 +252,21 @@ loop: for (var m = 0; m < 3; m++) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- try/catch/finally ---
|
||||
var tc
|
||||
try {
|
||||
throw "error"
|
||||
} catch (e) {
|
||||
tc = e
|
||||
// --- disrupt and disruption ---
|
||||
function disrupt_test() {
|
||||
disrupt
|
||||
}
|
||||
|
||||
var tcf = 0
|
||||
try {
|
||||
throw "err"
|
||||
} catch (e) {
|
||||
tcf = 1
|
||||
} finally {
|
||||
tcf += 10
|
||||
function disruption_test() {
|
||||
var x = 1
|
||||
} disruption {
|
||||
var y = 2
|
||||
}
|
||||
|
||||
// --- try/finally (no catch) ---
|
||||
var tf = 0
|
||||
try {
|
||||
tf = 1
|
||||
} finally {
|
||||
tf += 1
|
||||
function disrupt_with_disruption() {
|
||||
disrupt
|
||||
} disruption {
|
||||
var handled = true
|
||||
}
|
||||
|
||||
// --- delete operator ---
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
try { 1 } catch(e) { 2 }
|
||||
@@ -1 +0,0 @@
|
||||
var x = 1; try { throw 0 } catch(e) { x = 2 } finally { x = x + 1 }; x
|
||||
@@ -1 +0,0 @@
|
||||
var x = 1; try { x = 2 } finally { x = 3 }; x
|
||||
@@ -1 +0,0 @@
|
||||
try { throw "err" } catch(e) { e }
|
||||
Reference in New Issue
Block a user