327 lines
9.1 KiB
Plaintext
327 lines
9.1 KiB
Plaintext
// Hidden vars come from env:
|
|
// CLI mode (cell_init): os, args, core_path, shop_path, emit_qbe, dump_mach
|
|
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path, shop_path
|
|
// args[0] = script name, args[1..] = user args
|
|
var load_internal = os.load_internal
|
|
function use_embed(name) {
|
|
return load_internal("js_" + name + "_use")
|
|
}
|
|
|
|
var fd = use_embed('fd')
|
|
var json = use_embed('json')
|
|
|
|
var use_cache = {}
|
|
use_cache['fd'] = fd
|
|
use_cache['os'] = os
|
|
use_cache['json'] = json
|
|
|
|
// Bootstrap: load tokenize.cm, parse.cm, fold.cm from pre-compiled mach bytecode
|
|
function use_basic(path) {
|
|
if (use_cache[path])
|
|
return use_cache[path]
|
|
var result = use_embed(replace(path, '/', '_'))
|
|
use_cache[path] = result
|
|
return result
|
|
}
|
|
|
|
// Load a module from .mach/.mcode bytecode (bootstrap modules have no source fallback)
|
|
function boot_load(name, env) {
|
|
var mach_path = core_path + '/' + name + ".cm.mach"
|
|
var mcode_path = core_path + '/' + name + ".cm.mcode"
|
|
var data = null
|
|
var mcode_json = null
|
|
if (fd.is_file(mach_path)) {
|
|
data = fd.slurp(mach_path)
|
|
return mach_load(data, env)
|
|
}
|
|
if (fd.is_file(mcode_path)) {
|
|
mcode_json = text(fd.slurp(mcode_path))
|
|
return mach_eval_mcode(name, mcode_json, env)
|
|
}
|
|
print("error: missing bootstrap bytecode: " + name + "\n")
|
|
disrupt
|
|
}
|
|
|
|
var boot_env = {use: use_basic}
|
|
var tokenize_mod = boot_load("tokenize", boot_env)
|
|
var parse_mod = boot_load("parse", boot_env)
|
|
var fold_mod = boot_load("fold", boot_env)
|
|
use_cache['tokenize'] = tokenize_mod
|
|
use_cache['parse'] = parse_mod
|
|
use_cache['fold'] = fold_mod
|
|
|
|
// Always load mcode compiler module
|
|
var mcode_mod = boot_load("mcode", boot_env)
|
|
use_cache['mcode'] = mcode_mod
|
|
var streamline_mod = null
|
|
var qbe_emit_mod = null
|
|
|
|
// Warn if any .cm source is newer than its compiled bytecode
|
|
function check_mach_stale() {
|
|
var sources = [
|
|
"tokenize.cm",
|
|
"parse.cm",
|
|
"fold.cm",
|
|
"mcode.cm",
|
|
"streamline.cm",
|
|
"qbe.cm",
|
|
"qbe_emit.cm",
|
|
"internal/bootstrap.cm",
|
|
"internal/engine.cm"
|
|
]
|
|
var stale = []
|
|
var _i = 0
|
|
var cm_path = null
|
|
var mach_path = null
|
|
var mcode_path = null
|
|
var cm_stat = null
|
|
var compiled_stat = null
|
|
var best_mtime = null
|
|
while (_i < length(sources)) {
|
|
cm_path = core_path + '/' + sources[_i]
|
|
mach_path = cm_path + '.mach'
|
|
mcode_path = cm_path + '.mcode'
|
|
best_mtime = null
|
|
if (fd.is_file(mach_path)) {
|
|
best_mtime = fd.stat(mach_path).mtime
|
|
}
|
|
if (fd.is_file(mcode_path)) {
|
|
compiled_stat = fd.stat(mcode_path)
|
|
if (best_mtime == null || compiled_stat.mtime > best_mtime) {
|
|
best_mtime = compiled_stat.mtime
|
|
}
|
|
}
|
|
if (best_mtime != null && fd.is_file(cm_path)) {
|
|
cm_stat = fd.stat(cm_path)
|
|
if (cm_stat.mtime > best_mtime) {
|
|
push(stale, sources[_i])
|
|
}
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
if (length(stale) > 0) {
|
|
print("warning: bytecode is stale for: " + text(stale, ", ") + "\n")
|
|
print("run 'make regen' or './cell --core . regen.cm' to update\n")
|
|
}
|
|
}
|
|
check_mach_stale()
|
|
|
|
// 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, tokenize_mod)
|
|
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
|
|
}
|
|
ast = fold_mod(ast)
|
|
return ast
|
|
}
|
|
|
|
// Load a module from .mach/.mcode bytecode, falling back to source compilation
|
|
function load_module(name, env) {
|
|
var mach_path = core_path + '/' + name + ".cm.mach"
|
|
var mcode_path = core_path + '/' + name + ".cm.mcode"
|
|
var data = null
|
|
var mcode_json = null
|
|
var src_path = null
|
|
var src = null
|
|
var ast = null
|
|
var compiled = null
|
|
var optimized = null
|
|
if (fd.is_file(mach_path)) {
|
|
data = fd.slurp(mach_path)
|
|
return mach_load(data, env)
|
|
}
|
|
if (fd.is_file(mcode_path)) {
|
|
mcode_json = text(fd.slurp(mcode_path))
|
|
return mach_eval_mcode(name, mcode_json, env)
|
|
}
|
|
src_path = core_path + '/' + name + ".cm"
|
|
src = text(fd.slurp(src_path))
|
|
ast = analyze(src, src_path)
|
|
compiled = mcode_mod(ast)
|
|
optimized = streamline_mod(compiled)
|
|
return mach_eval_mcode(name, json.encode(optimized), env)
|
|
}
|
|
|
|
// Load optimization pipeline modules (needs analyze to be defined)
|
|
var qbe_macros = null
|
|
streamline_mod = load_module("streamline", boot_env)
|
|
use_cache['streamline'] = streamline_mod
|
|
if (emit_qbe) {
|
|
qbe_macros = load_module("qbe", boot_env)
|
|
qbe_emit_mod = load_module("qbe_emit", boot_env)
|
|
use_cache['qbe'] = qbe_macros
|
|
use_cache['qbe_emit'] = qbe_emit_mod
|
|
}
|
|
|
|
// Run AST through mcode pipeline → register VM
|
|
function run_ast(name, ast, env) {
|
|
var compiled = mcode_mod(ast)
|
|
var optimized = streamline_mod(compiled)
|
|
var qbe_il = null
|
|
if (emit_qbe) {
|
|
qbe_il = qbe_emit_mod(optimized, qbe_macros)
|
|
print(qbe_il)
|
|
return null
|
|
}
|
|
if (dump_mach) {
|
|
mach_dump_mcode(name, json.encode(optimized), env)
|
|
return null
|
|
}
|
|
return mach_eval_mcode(name, json.encode(optimized), env)
|
|
}
|
|
|
|
// use() with ƿit pipeline for .cm modules
|
|
function use_fn(path) {
|
|
var file_path = null
|
|
var mach_path = null
|
|
var mcode_path = null
|
|
var mcode_json = null
|
|
var data = null
|
|
var script = null
|
|
var ast = null
|
|
var result = null
|
|
if (use_cache[path])
|
|
return use_cache[path]
|
|
|
|
// Try .cm.mach bytecode first (CWD then core_path)
|
|
mach_path = path + '.cm.mach'
|
|
if (!fd.is_file(mach_path))
|
|
mach_path = core_path + '/' + path + '.cm.mach'
|
|
if (fd.is_file(mach_path)) {
|
|
data = fd.slurp(mach_path)
|
|
result = mach_load(data, {use: use_fn})
|
|
use_cache[path] = result
|
|
return result
|
|
}
|
|
|
|
// Try .cm.mcode JSON IR (CWD then core_path)
|
|
mcode_path = path + '.cm.mcode'
|
|
if (!fd.is_file(mcode_path))
|
|
mcode_path = core_path + '/' + path + '.cm.mcode'
|
|
if (fd.is_file(mcode_path)) {
|
|
mcode_json = text(fd.slurp(mcode_path))
|
|
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
|
|
use_cache[path] = result
|
|
return result
|
|
}
|
|
|
|
// Try .cm source (CWD then core_path)
|
|
file_path = path + '.cm'
|
|
if (!fd.is_file(file_path))
|
|
file_path = core_path + '/' + path + '.cm'
|
|
|
|
if (fd.is_file(file_path)) {
|
|
script = text(fd.slurp(file_path))
|
|
ast = analyze(script, file_path)
|
|
result = run_ast(path, ast, {use: use_fn})
|
|
use_cache[path] = result
|
|
return result
|
|
}
|
|
|
|
// Fallback to embedded C module
|
|
result = use_embed(replace(path, '/', '_'))
|
|
use_cache[path] = result
|
|
return result
|
|
}
|
|
|
|
// Helper to load engine.cm and run it with given env
|
|
function load_engine(env) {
|
|
var engine_path = core_path + '/internal/engine.cm.mach'
|
|
var mcode_path = core_path + '/internal/engine.cm.mcode'
|
|
var data = null
|
|
var mcode_json = null
|
|
var engine_src = null
|
|
var engine_ast = null
|
|
if (fd.is_file(engine_path)) {
|
|
data = fd.slurp(engine_path)
|
|
return mach_load(data, env)
|
|
}
|
|
if (fd.is_file(mcode_path)) {
|
|
mcode_json = text(fd.slurp(mcode_path))
|
|
return mach_eval_mcode('engine', mcode_json, env)
|
|
}
|
|
engine_path = core_path + '/internal/engine.cm'
|
|
engine_src = text(fd.slurp(engine_path))
|
|
engine_ast = analyze(engine_src, engine_path)
|
|
return run_ast('engine', engine_ast, env)
|
|
}
|
|
|
|
// Detect mode and route
|
|
// CLI mode has 'args'; actor spawn mode has 'init'
|
|
var program = null
|
|
var user_args = []
|
|
var _j = 0
|
|
var script_file = null
|
|
var script = null
|
|
var ast = null
|
|
|
|
if (args != null) {
|
|
// CLI mode — parse args
|
|
program = args[0]
|
|
_j = 1
|
|
while (_j < length(args)) {
|
|
push(user_args, args[_j])
|
|
_j = _j + 1
|
|
}
|
|
|
|
// Resolve script file: try .cm then .ce in CWD then core_path
|
|
script_file = program
|
|
if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm'))
|
|
script_file = program + '.cm'
|
|
|
|
if (!fd.is_file(script_file))
|
|
script_file = core_path + '/' + program + '.cm'
|
|
if (!fd.is_file(script_file))
|
|
script_file = program + '.ce'
|
|
if (!fd.is_file(script_file))
|
|
script_file = core_path + '/' + program + '.ce'
|
|
|
|
if (ends_with(script_file, '.ce')) {
|
|
// Actor script — delegate to engine
|
|
load_engine({
|
|
os: os, actorsym: actorsym,
|
|
init: {program: program, arg: user_args},
|
|
core_path: core_path, shop_path: shop_path, json: json,
|
|
analyze: analyze, run_ast_fn: run_ast
|
|
})
|
|
} else {
|
|
// Module script — run directly
|
|
script = text(fd.slurp(script_file))
|
|
ast = analyze(script, script_file)
|
|
run_ast(program, ast, {use: use_fn, args: user_args, json: json})
|
|
}
|
|
} else {
|
|
// Actor spawn mode — load engine.cm with full actor env
|
|
load_engine({
|
|
os: os, actorsym: actorsym, init: init,
|
|
core_path: core_path, shop_path: shop_path, json: json, nota: nota, wota: wota,
|
|
analyze: analyze, run_ast_fn: run_ast
|
|
})
|
|
}
|