diff --git a/internal/bootstrap.cm b/internal/bootstrap.cm index a217dd48..3a3ee860 100644 --- a/internal/bootstrap.cm +++ b/internal/bootstrap.cm @@ -1,5 +1,5 @@ -// Hidden vars (os, args) come from env -// args[0] = script filename, args[1..] = user args +// Hidden vars (os, args, core_path) come from env +// args[0] = script name, args[1..] = user args var load_internal = os.load_internal function use_embed(name) { return load_internal("js_" + name + "_use") @@ -13,23 +13,76 @@ use_cache['fd'] = fd use_cache['os'] = os use_cache['json'] = json -function use(path) { +// Bootstrap: load tokenize.cm and parse.cm via C pipeline (mach_eval) +function use_basic(path) { if (use_cache[path]) - return use_cache[path]; + return use_cache[path] + var result = use_embed(replace(path, '/', '_')) + use_cache[path] = result + return result +} +var tok_path = core_path + "/tokenize.cm" +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}) + +// 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 _i = 0 + var prev_line = -1 + var prev_msg = null + var e = null + var msg = null + var line = null + var col = null + var has_errors = ast.errors != null && length(ast.errors) > 0 + if (has_errors) { + while (_i < length(ast.errors)) { + e = ast.errors[_i] + msg = e.message + line = e.line + col = e.column + if (msg != prev_msg || line != prev_line) { + if (line != null && col != null) { + print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`) + } else { + print(`${filename}: error: ${msg}`) + } + } + prev_line = line + prev_msg = msg + _i = _i + 1 + } + disrupt + } + return ast +} + +// use() with ƿit pipeline for .cm modules +function use(path) { var file_path = path + '.cm' var script = null + var ast = null var result = null - var exports = null + if (use_cache[path]) + return use_cache[path] + + // Check CWD first, then core_path + if (!fd.is_file(file_path)) + file_path = core_path + '/' + path + '.cm' if (fd.is_file(file_path)) { script = text(fd.slurp(file_path)) - result = mach_eval(path, script, {use: use}) + ast = analyze(script, file_path) + result = mach_eval_ast(path, json.encode(ast), {use: use}) use_cache[path] = result return result } - // Try embedded C module + // Fallback to embedded C module result = use_embed(replace(path, '/', '_')) use_cache[path] = result return result @@ -37,14 +90,19 @@ function use(path) { // Load and run the user's program var program = args[0] +var script_file = program + +// Add .ce extension if not already present +if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm')) + script_file = program + '.ce' var user_args = [] -var _i = 1 -while (_i < length(args)) { - push(user_args, args[_i]) - _i = _i + 1 +var _j = 1 +while (_j < length(args)) { + push(user_args, args[_j]) + _j = _j + 1 } -var blob = fd.slurp(program) -stone(blob) -var script = text(blob) -mach_eval(program, script, {use: use, args: user_args, json: json}) + +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}) diff --git a/parse.ce b/parse.ce index da795b37..8c0c3b0a 100644 --- a/parse.ce +++ b/parse.ce @@ -1,7 +1,8 @@ +var fd = use("fd") var tokenize = use("tokenize") var parse = use("parse") -var src = args[0] -var filename = length(args) > 1 ? args[1] : "" +var filename = args[0] +var src = text(fd.slurp(filename)) var result = tokenize(src, filename) var ast = parse(result.tokens, src, filename) print(json.encode(ast)) diff --git a/source/cell.c b/source/cell.c index 3f6eebb0..b4bad4de 100644 --- a/source/cell.c +++ b/source/cell.c @@ -650,6 +650,7 @@ 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)); JSValue args_arr = JS_NewArray(ctx); for (int i = 2; i < argc; i++) { JSValue str = JS_NewString(ctx, argv[i]); @@ -679,41 +680,72 @@ int cell_init(int argc, char **argv) return exit_code; } - int script_start = 1; + /* Default: run script through mach-run bootstrap pipeline */ + if (!find_cell_shop()) return 1; - /* Find the cell shop at ~/.cell */ - int found = find_cell_shop(); - if (!found) { + 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); return 1; } - /* Create the initial actor from the command line */ - int actor_argc = argc - script_start; - char **actor_argv = argv + script_start; + 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; + } - WotaBuffer startwota; - wota_buffer_init(&startwota, 5); - wota_write_record(&startwota, 2); - wota_write_text(&startwota, "program"); - wota_write_text(&startwota, actor_argv[0]); - wota_write_text(&startwota, "arg"); - wota_write_array(&startwota, actor_argc - 1); - for (int i = 1; i < actor_argc; i++) - wota_write_text(&startwota, actor_argv[i]); - - /* Initialize synchronization primitives */ - actor_initialize(); - - root_cell = create_actor(startwota.data); -#ifndef TARGET_PLAYDATE - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - signal(SIGSEGV, signal_handler); - signal(SIGABRT, signal_handler); -#endif - actor_loop(); + if (print_tree_errors(boot_ast)) { + cJSON_Delete(boot_ast); + return 1; + } - return 0; + JSRuntime *rt = JS_NewRuntime(); + if (!rt) { + printf("Failed to create JS runtime\n"); + cJSON_Delete(boot_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); + 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 = 1; 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); + + int exit_code = 0; + if (JS_IsException(result)) { + JS_GetException(ctx); + exit_code = 1; + } else if (!JS_IsNull(result)) { + const char *str = JS_ToCString(ctx, result); + if (str) { + printf("%s\n", str); + JS_FreeCString(ctx, str); + } + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return exit_code; } int JS_ArrayLength(JSContext *js, JSValue a) diff --git a/source/runtime.c b/source/runtime.c index 03d0ac49..f2247014 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -10434,6 +10434,39 @@ static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue return result; } +/* mach_eval_ast(name, ast_json, env?) - compile pre-parsed AST and run */ +static JSValue js_mach_eval_ast (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) + return JS_ThrowTypeError (ctx, "mach_eval_ast requires (name, ast_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 *ast = cJSON_Parse (json_str); + JS_FreeCString (ctx, json_str); + + if (!ast) { + JS_FreeCString (ctx, name); + return JS_ThrowSyntaxError (ctx, "mach_eval_ast: failed to parse AST JSON"); + } + + /* Set the filename on the AST root */ + cJSON_DeleteItemFromObjectCaseSensitive (ast, "filename"); + cJSON_AddStringToObject (ast, "filename", name); + + JSValue env = (argc >= 3 && JS_IsObject (argv[2])) ? argv[2] : JS_NULL; + JSValue result = JS_RunMachTree (ctx, ast, env); + cJSON_Delete (ast); + JS_FreeCString (ctx, name); + return result; +} + /* ============================================================================ * stone() function - deep freeze with blob support * ============================================================================ @@ -11527,6 +11560,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { /* Core functions - using GC-safe helper */ 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, "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); diff --git a/tokenize.ce b/tokenize.ce index 5284a078..fa074035 100644 --- a/tokenize.ce +++ b/tokenize.ce @@ -1,5 +1,6 @@ +var fd = use("fd") var tokenize = use("tokenize") -var src = args[0] -var filename = length(args) > 1 ? args[1] : "" +var filename = args[0] +var src = text(fd.slurp(filename)) var result = tokenize(src, filename) print(json.encode({filename: result.filename, tokens: result.tokens}))