Merge branch 'mach' into mqbe
This commit is contained in:
@@ -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
10
mcode.ce
Normal 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))
|
||||
117
parse.cm
117
parse.cm
@@ -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
|
||||
}
|
||||
|
||||
154
source/cell.c
154
source/cell.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
55
tokenize.cm
55
tokenize.cm
@@ -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())
|
||||
|
||||
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user