Merge branch 'mach' into mqbe

This commit is contained in:
2026-02-09 18:36:47 -06:00
10 changed files with 2079 additions and 152 deletions

View File

@@ -1,4 +1,4 @@
// Hidden vars (os, args, core_path) come from env
// Hidden vars (os, args, core_path, use_mcode) come from env
// args[0] = script name, args[1..] = user args
var load_internal = os.load_internal
function use_embed(name) {
@@ -27,10 +27,18 @@ var par_path = core_path + "/parse.cm"
var tokenize_mod = mach_eval("tokenize", text(fd.slurp(tok_path)), {use: use_basic})
var parse_mod = mach_eval("parse", text(fd.slurp(par_path)), {use: use_basic})
// Optionally load mcode compiler module
var mcode_mod = null
var mcode_path = null
if (use_mcode) {
mcode_path = core_path + "/mcode.cm"
mcode_mod = mach_eval("mcode", text(fd.slurp(mcode_path)), {use: use_basic})
}
// analyze: tokenize + parse, check for errors
function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename)
var ast = parse_mod(tok_result.tokens, src, filename)
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0
var prev_line = -1
var prev_msg = null
@@ -61,6 +69,16 @@ function analyze(src, filename) {
return ast
}
// Run AST through either mcode or mach pipeline
function run_ast(name, ast, env) {
var compiled = null
if (use_mcode) {
compiled = mcode_mod(ast)
return mcode_run(name, json.encode(compiled), env)
}
return mach_eval_ast(name, json.encode(ast), env)
}
// use() with ƿit pipeline for .cm modules
function use(path) {
var file_path = path + '.cm'
@@ -77,7 +95,7 @@ function use(path) {
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = mach_eval_ast(path, json.encode(ast), {use: use})
result = run_ast(path, ast, {use: use})
use_cache[path] = result
return result
}
@@ -105,4 +123,4 @@ while (_j < length(args)) {
var script = text(fd.slurp(script_file))
var ast = analyze(script, script_file)
mach_eval_ast(program, json.encode(ast), {use: use, args: user_args, json: json})
run_ast(program, ast, {use: use, args: user_args, json: json})

10
mcode.ce Normal file
View File

@@ -0,0 +1,10 @@
var fd = use("fd")
var tokenize = use("tokenize")
var parse = use("parse")
var mcode = use("mcode")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename)
var compiled = mcode(ast)
print(json.encode(compiled))

1742
mcode.cm Normal file

File diff suppressed because it is too large Load Diff

117
parse.cm
View File

@@ -5,7 +5,7 @@ var is_alpha = function(c) {
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
}
var parse = function(tokens, src, filename) {
var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src)
var cp = []
var _i = 0
@@ -167,6 +167,23 @@ var parse = function(tokens, src, filename) {
var rpos = 0
var pattern_str = ""
var flags = ""
var tv = null
var has_interp = false
var ti = 0
var tpl_list = null
var fmt = null
var idx = 0
var tvi = 0
var tvlen = 0
var depth = 0
var expr_str = null
var tc = null
var tq = null
var esc_ch = null
var expr_tokens = null
var sub_ast = null
var sub_stmt = null
var sub_expr = null
if (k == "number") {
node = ast_node("number", start)
@@ -177,8 +194,96 @@ var parse = function(tokens, src, filename) {
return node
}
if (k == "text") {
node = ast_node("text", start)
node.value = tok.value
// Check for template interpolation: ${...}
tv = tok.value
has_interp = false
ti = 0
while (ti < length(tv) - 1) {
if (tv[ti] == "$" && tv[ti + 1] == "{") {
if (ti == 0 || tv[ti - 1] != "\\") {
has_interp = true
break
}
}
ti = ti + 1
}
if (!has_interp || tokenizer == null) {
node = ast_node("text", start)
node.value = tok.value
advance()
ast_node_end(node)
return node
}
// Template literal with interpolation
node = ast_node("text literal", start)
tpl_list = []
node.list = tpl_list
fmt = ""
idx = 0
tvi = 0
tvlen = length(tv)
while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1]
if (esc_ch == "n") { fmt = fmt + "\n" }
else if (esc_ch == "t") { fmt = fmt + "\t" }
else if (esc_ch == "r") { fmt = fmt + "\r" }
else if (esc_ch == "\\") { fmt = fmt + "\\" }
else if (esc_ch == "`") { fmt = fmt + "`" }
else if (esc_ch == "$") { fmt = fmt + "$" }
else if (esc_ch == "0") { fmt = fmt + character(0) }
else { fmt = fmt + esc_ch }
tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
tvi = tvi + 2
depth = 1
expr_str = ""
while (tvi < tvlen && depth > 0) {
tc = tv[tvi]
if (tc == "{") { depth = depth + 1; expr_str = expr_str + tc; tvi = tvi + 1 }
else if (tc == "}") {
depth = depth - 1
if (depth > 0) { expr_str = expr_str + tc }
tvi = tvi + 1
}
else if (tc == "'" || tc == "\"" || tc == "`") {
tq = tc
expr_str = expr_str + tc
tvi = tvi + 1
while (tvi < tvlen && tv[tvi] != tq) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
expr_str = expr_str + tv[tvi]
tvi = tvi + 1
}
expr_str = expr_str + tv[tvi]
tvi = tvi + 1
}
if (tvi < tvlen) { expr_str = expr_str + tv[tvi]; tvi = tvi + 1 }
} else {
expr_str = expr_str + tc
tvi = tvi + 1
}
}
expr_tokens = tokenizer(expr_str, "<template>").tokens
sub_ast = parse(expr_tokens, expr_str, "<template>", tokenizer)
if (sub_ast != null && sub_ast.statements != null && length(sub_ast.statements) > 0) {
sub_stmt = sub_ast.statements[0]
sub_expr = null
if (sub_stmt.kind == "call") {
sub_expr = sub_stmt.expression
} else {
sub_expr = sub_stmt
}
push(tpl_list, sub_expr)
}
fmt = fmt + "{" + text(idx) + "}"
idx = idx + 1
} else {
fmt = fmt + tv[tvi]
tvi = tvi + 1
}
}
node.value = fmt
advance()
ast_node_end(node)
return node
@@ -357,7 +462,11 @@ var parse = function(tokens, src, filename) {
}
node.pattern = pattern_str
if (length(flags) > 0) node.flags = flags
advance()
// Skip all tokens consumed by the regex re-scan
while (true) {
advance()
if (tok.kind == "eof" || tok.at >= rpos) break
}
ast_node_end(node)
return node
}

View File

@@ -295,7 +295,7 @@ static void print_usage(const char *prog)
printf("Options:\n");
printf(" --ast <code|file> Output AST as JSON\n");
printf(" --tokenize <code|file> Output token array as JSON\n");
printf(" --mcode <code|file> Output MCODE IR as JSON\n");
printf(" --mcode <script> [args] Run through mcode compilation pipeline\n");
printf(" --run-mcode <code|file> Compile and run through MCODE interpreter\n");
printf(" --mach <code|file> Output MACH bytecode\n");
printf(" --mach-run <code|file> Compile and run through MACH VM\n");
@@ -420,66 +420,6 @@ int cell_init(int argc, char **argv)
}
}
/* Check for --mcode flag to output MCODE JSON IR */
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
free(allocated_script);
return 1;
}
cJSON *mcode = JS_McodeTree(ast);
cJSON_Delete(ast);
if (!mcode) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
char *pretty = cJSON_Print(mcode);
cJSON_Delete(mcode);
printf("%s\n", pretty);
free(pretty);
free(allocated_script);
return 0;
}
/* Check for --run-mcode flag to execute via MCODE interpreter */
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
const char *filename = argv[2];
@@ -612,59 +552,81 @@ int cell_init(int argc, char **argv)
/* Check for --mach-run flag to compile and run through MACH VM */
if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) {
if (!find_cell_shop()) return 1;
const char *script_name = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = script_name;
size_t boot_size;
char *boot_data = load_core_file("internal/bootstrap.cm", &boot_size);
if (!boot_data) {
printf("ERROR: Could not load internal/bootstrap.cm from %s\n", core_path);
struct stat st;
if (stat(script_name, &st) == 0 && S_ISREG(st.st_mode)) {
/* Exact name found */
} else {
/* Try .ce then .cm extension */
static char pathbuf[4096];
snprintf(pathbuf, sizeof(pathbuf), "%s.ce", script_name);
if (stat(pathbuf, &st) == 0 && S_ISREG(st.st_mode)) {
script_name = pathbuf;
filename = pathbuf;
} else {
snprintf(pathbuf, sizeof(pathbuf), "%s.cm", script_name);
if (stat(pathbuf, &st) == 0 && S_ISREG(st.st_mode)) {
script_name = pathbuf;
filename = pathbuf;
} else {
printf("Failed to find file: %s\n", argv[2]);
return 1;
}
}
}
FILE *f = fopen(script_name, "r");
if (!f) {
printf("Failed to open file: %s\n", script_name);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
cJSON *ast = JS_ASTTree(script, read_size, filename);
free(allocated_script);
if (!ast) {
printf("Failed to parse %s\n", filename);
return 1;
}
cJSON *boot_ast = JS_ASTTree(boot_data, boot_size, "internal/bootstrap.cm");
free(boot_data);
if (!boot_ast) {
printf("Failed to parse internal/bootstrap.cm\n");
return 1;
}
if (print_tree_errors(boot_ast)) {
cJSON_Delete(boot_ast);
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return 1;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
cJSON_Delete(boot_ast);
cJSON_Delete(ast);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(rt, 16 * 1024 * 1024);
if (!ctx) {
printf("Failed to create JS context\n");
cJSON_Delete(boot_ast); JS_FreeRuntime(rt);
cJSON_Delete(ast); JS_FreeRuntime(rt);
return 1;
}
JS_FreeValue(ctx, js_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JSValue args_arr = JS_NewArray(ctx);
for (int i = 2; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
JSValue result = JS_RunMachTree(ctx, boot_ast, hidden_env);
cJSON_Delete(boot_ast);
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
int exit_code = 0;
if (JS_IsException(result)) {
/* Error already printed to stderr by JS_Throw* */
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
@@ -681,6 +643,13 @@ int cell_init(int argc, char **argv)
}
/* Default: run script through mach-run bootstrap pipeline */
int use_mcode = 0;
int arg_start = 1;
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
use_mcode = 1;
arg_start = 2;
}
if (!find_cell_shop()) return 1;
size_t boot_size;
@@ -720,8 +689,9 @@ int cell_init(int argc, char **argv)
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode));
JSValue args_arr = JS_NewArray(ctx);
for (int i = 1; i < argc; i++) {
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}

View File

@@ -1707,20 +1707,24 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
for (uint32_t i = 0; i < code->instr_count; i++) {
MachInstr32 instr = code->instructions[i];
if (MACH_GET_OP(instr) != MACH_GETNAME) continue;
int a = MACH_GET_A(instr);
int bx = MACH_GET_Bx(instr);
int in_env = 0;
if (!JS_IsNull(env) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env, code->cpool[bx]);
if (!JS_IsNull(env_ref.val) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env_ref.val, code->cpool[bx]);
in_env = !JS_IsNull(val) && !JS_IsException(val);
}
code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx);
}
for (uint32_t i = 0; i < code->func_count; i++)
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env);
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env_ref.val);
JS_PopGCRef(ctx, &env_ref);
}
/* ---- Top-level compiler ---- */
@@ -1832,6 +1836,11 @@ void JS_FreeMachCode(MachCode *mc) {
/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */
JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
/* Protect env from GC — materialize/link calls can trigger collection */
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister));
code->arity = mc->arity;
code->nr_close_slots = mc->nr_close_slots;
@@ -1849,7 +1858,7 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
if (mc->func_count > 0) {
code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *));
for (uint32_t i = 0; i < mc->func_count; i++)
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env);
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env_ref.val);
} else {
code->functions = NULL;
}
@@ -1866,8 +1875,9 @@ JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
code->disruption_pc = mc->disruption_pc;
/* Link: resolve GETNAME to GETENV/GETINTRINSIC */
mach_link_code(ctx, code, env);
mach_link_code(ctx, code, env_ref.val);
JS_PopGCRef(ctx, &env_ref);
return code;
}
@@ -2229,6 +2239,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
returning from a called register function can read code/env from frame */
JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val);
JS_PopGCRef(ctx, &of_gc);
env = env_gc.val; /* refresh — GC may have moved env during allocation */
JS_PopGCRef(ctx, &env_gc);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->function = top_fn;
@@ -2278,6 +2289,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
switch (op) {
case MACH_NOP:
break;
@@ -2499,9 +2511,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
case MACH_GETENV: {
/* Read env fresh from frame->function — C local env can go stale after GC */
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_GetProperty(ctx, env, key);
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = val;
break;
@@ -2512,8 +2526,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_NULL;
if (!JS_IsNull(env)) {
val = JS_GetProperty(ctx, env, key);
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
if (!JS_IsNull(cur_env)) {
val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
if (JS_IsNull(val) || JS_IsException(val)) {
@@ -2661,8 +2676,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JS_PushGCRef(ctx, &key_ref);
key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c];
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) {
/* Proxy call: obj(name, [args...]) */
if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val) &&
JS_VALUE_GET_FUNCTION(frame->slots[base])->length == 2) {
/* Proxy call (arity-2 functions only): obj(name, [args...]) */
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; }
@@ -2816,7 +2832,9 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int bx = MACH_GET_Bx(instr);
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val);
/* Read env fresh from frame->function — C local can be stale */
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue fn_val = js_new_register_function(ctx, fn_code, cur_env, frame_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = fn_val;
} else {
@@ -3251,8 +3269,14 @@ JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) {
return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode");
}
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL);
/* Protect env from GC — JS_LoadMachCode allocates on GC heap */
JSGCRef env_ref;
JS_PushGCRef(ctx, &env_ref);
env_ref.val = env;
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
JS_PopGCRef(ctx, &env_ref);
return result;
}

View File

@@ -3061,8 +3061,9 @@ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
nargs -= 2;
if (nargs < 0) nargs = 0;
if (JS_IsFunction(obj) && JS_VALUE_IS_TEXT(key)) {
/* Proxy call: obj(key, [args...]) */
if (JS_IsFunction(obj) && JS_VALUE_IS_TEXT(key) &&
JS_VALUE_GET_FUNCTION(obj)->length == 2) {
/* Proxy call (arity-2 functions only): obj(key, [args...]) */
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = key; /* protect key on value stack */
ctx->value_stack_top = vs_base + 1; /* protect key from GC */
@@ -3087,7 +3088,8 @@ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
} else if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot use non-text bracket notation on function");
/* Non-proxy function: bracket access not allowed */
JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
} else {

View File

@@ -2515,8 +2515,16 @@ static int js_intrinsic_array_push (JSContext *ctx, JSValue *arr_ptr, JSValue va
}
if (arr->len >= js_array_cap (arr)) {
if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0)
/* Root val across js_array_grow which can trigger GC */
JSGCRef val_ref;
JS_PushGCRef (ctx, &val_ref);
val_ref.val = val;
if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0) {
JS_PopGCRef (ctx, &val_ref);
return -1;
}
val = val_ref.val;
JS_PopGCRef (ctx, &val_ref);
arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */
}
@@ -5974,7 +5982,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
int64_t i, len;
int ret;
BOOL has_content;
JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref;
JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref, v_ref;
/* Root all values that can be heap pointers and survive across GC points */
JS_PushGCRef (ctx, &val_ref);
@@ -5984,6 +5992,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JS_PushGCRef (ctx, &sep1_ref);
JS_PushGCRef (ctx, &tab_ref);
JS_PushGCRef (ctx, &prop_ref);
JS_PushGCRef (ctx, &v_ref);
val_ref.val = val;
indent_ref.val = indent;
@@ -5992,6 +6001,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
sep1_ref.val = JS_NULL;
tab_ref.val = JS_NULL;
prop_ref.val = JS_NULL;
v_ref.val = JS_NULL;
if (js_check_stack_overflow (ctx, 0)) {
JS_ThrowStackOverflow (ctx);
@@ -6038,10 +6048,11 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JSC_B_CONCAT (jsc, sep_ref.val);
v = JS_GetPropertyInt64 (ctx, val_ref.val, i);
if (JS_IsException (v)) goto exception;
v_ref.val = v; /* root v — JS_ToString below can trigger GC */
/* XXX: could do this string conversion only when needed */
prop_ref.val = JS_ToString (ctx, JS_NewInt64 (ctx, i));
if (JS_IsException (prop_ref.val)) goto exception;
v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val);
v = js_json_check (ctx, jsc, val_ref.val, v_ref.val, prop_ref.val);
prop_ref.val = JS_NULL;
if (JS_IsException (v)) goto exception;
if (JS_IsNull (v)) v = JS_NULL;
@@ -6069,6 +6080,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val);
if (JS_IsException (v)) goto exception;
if (!JS_IsNull (v)) {
v_ref.val = v; /* root v — allocations below can trigger GC */
if (has_content) {
JSC_B_PUTC (jsc, ',');
}
@@ -6080,7 +6092,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
JSC_B_CONCAT (jsc, prop_ref.val);
JSC_B_PUTC (jsc, ':');
JSC_B_CONCAT (jsc, sep1_ref.val);
if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception;
if (js_json_to_str (ctx, jsc, val_ref.val, v_ref.val, indent1_ref.val)) goto exception;
has_content = TRUE;
}
}
@@ -6116,6 +6128,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho
}
done:
JS_PopGCRef (ctx, &v_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &tab_ref);
JS_PopGCRef (ctx, &sep1_ref);
@@ -6126,6 +6139,7 @@ done:
return 0;
exception_ret:
JS_PopGCRef (ctx, &v_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &tab_ref);
JS_PopGCRef (ctx, &sep1_ref);
@@ -6283,8 +6297,8 @@ static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSVal
return val;
}
/* Handle string */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
/* Handle string (immediate ASCII or heap JSText) */
if (JS_IsText (val)) {
const char *str = JS_ToCString (ctx, val);
if (!str) return JS_EXCEPTION;
@@ -6925,7 +6939,7 @@ JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *
int tag = JS_VALUE_GET_TAG (arg);
/* Handle string - return first character */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
if (JS_IsText (arg)) {
if (js_string_value_len (arg) == 0) return JS_NewString (ctx, "");
return js_sub_string_val (ctx, arg, 0, 1);
}
@@ -6978,7 +6992,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
int tag = JS_VALUE_GET_TAG (arg);
/* Handle string / rope */
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
if (JS_IsText (arg)) {
JSValue str = JS_ToString (ctx, arg); /* owned + flattens rope */
if (JS_IsException (str)) return JS_EXCEPTION;
@@ -10467,6 +10481,35 @@ static JSValue js_mach_eval_ast (JSContext *ctx, JSValue this_val, int argc, JSV
return result;
}
/* mcode_run(name, mcode_json, env?) - run pre-compiled mcode JSON */
static JSValue js_mcode_run (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
return JS_ThrowTypeError (ctx, "mcode_run requires (name, mcode_json) text arguments");
const char *name = JS_ToCString (ctx, argv[0]);
if (!name) return JS_EXCEPTION;
const char *json_str = JS_ToCString (ctx, argv[1]);
if (!json_str) {
JS_FreeCString (ctx, name);
return JS_EXCEPTION;
}
cJSON *mcode = cJSON_Parse (json_str);
JS_FreeCString (ctx, json_str);
if (!mcode) {
JS_FreeCString (ctx, name);
return JS_ThrowSyntaxError (ctx, "mcode_run: failed to parse mcode JSON");
}
JSValue env = (argc >= 3 && JS_IsObject (argv[2])) ? argv[2] : JS_NULL;
JSValue result = JS_CallMcodeTreeEnv (ctx, mcode, env);
cJSON_Delete (mcode);
JS_FreeCString (ctx, name);
return result;
}
/* ============================================================================
* stone() function - deep freeze with blob support
* ============================================================================
@@ -11561,6 +11604,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "eval", js_cell_eval, 2);
js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 3);
js_set_global_cfunc(ctx, "mach_eval_ast", js_mach_eval_ast, 3);
js_set_global_cfunc(ctx, "mcode_run", js_mcode_run, 3);
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3);

View File

@@ -64,6 +64,7 @@ var tokenize = function(src, filename) {
def CP_o = 111
def CP_r = 114
def CP_t = 116
def CP_u = 117
def CP_x = 120
def CP_z = 122
def CP_LBRACE = 123
@@ -113,6 +114,23 @@ var tokenize = function(src, filename) {
return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F)
}
var hex_val = function(c) {
if (c >= CP_0 && c <= CP_9) return c - CP_0
if (c >= CP_a && c <= CP_f) return c - CP_a + 10
if (c >= CP_A && c <= CP_F) return c - CP_A + 10
return 0
}
var read_unicode_escape = function() {
var cp_val = 0
var hi = 0
while (hi < 4 && pos < len && is_hex(pk())) {
cp_val = cp_val * 16 + hex_val(adv())
hi = hi + 1
}
return character(cp_val)
}
var is_alpha = function(c) {
return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z)
}
@@ -158,6 +176,7 @@ var tokenize = function(src, filename) {
else if (esc == CP_DQUOTE) { value = value + "\"" }
else if (esc == CP_0) { value = value + character(0) }
else if (esc == CP_BACKTICK) { value = value + "`" }
else if (esc == CP_u) { value = value + read_unicode_escape() }
else { value = value + character(esc) }
} else {
value = value + character(adv())
@@ -177,39 +196,37 @@ var tokenize = function(src, filename) {
var start_row = row
var start_col = col
var value = ""
var esc = 0
var depth = 0
var tc = 0
var q = 0
adv() // skip opening backtick
while (pos < len && pk() != CP_BACKTICK) {
if (pk() == CP_BSLASH && pos + 1 < len) {
adv()
esc = adv()
if (esc == CP_n) { value = value + "\n" }
else if (esc == CP_t) { value = value + "\t" }
else if (esc == CP_r) { value = value + "\r" }
else if (esc == CP_BSLASH) { value = value + "\\" }
else if (esc == CP_BACKTICK) { value = value + "`" }
else if (esc == CP_DOLLAR) { value = value + "$" }
else if (esc == CP_0) { value = value + character(0) }
else { value = value + character(esc) }
value = value + character(adv())
value = value + character(adv())
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) {
adv() // $
adv() // {
value = value + character(adv()) // $
value = value + character(adv()) // {
depth = 1
while (pos < len && depth > 0) {
tc = pk()
if (tc == CP_LBRACE) { depth = depth + 1; adv() }
else if (tc == CP_RBRACE) { depth = depth - 1; adv() }
if (tc == CP_LBRACE) { depth = depth + 1; value = value + character(adv()) }
else if (tc == CP_RBRACE) {
depth = depth - 1
if (depth > 0) { value = value + character(adv()) }
else { value = value + character(adv()) }
}
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) {
q = adv()
value = value + character(q)
while (pos < len && pk() != q) {
if (pk() == CP_BSLASH && pos + 1 < len) adv()
adv()
if (pk() == CP_BSLASH && pos + 1 < len) {
value = value + character(adv())
}
value = value + character(adv())
}
if (pos < len) adv()
} else { adv() }
if (pos < len) { value = value + character(adv()) }
} else { value = value + character(adv()) }
}
} else {
value = value + character(adv())

View File

@@ -2926,15 +2926,6 @@ run("array map with exit", function() {
if (length(result) != 5) fail("array map with exit length unexpected")
})
// ============================================================================
// ERROR OBJECTS
// ============================================================================
run("error creation", function() {
var e = Error("test message")
if (e.message != "test message") fail("Error creation failed")
})
// ============================================================================
// STRING METHOD EDGE CASES
// ============================================================================