143 lines
6.0 KiB
EmacsLisp
143 lines
6.0 KiB
EmacsLisp
// el-ui-compiler — El→browser-target component compiler (STUB).
|
|
//
|
|
// The Rust crate transforms `.el` component files into JavaScript using a
|
|
// real lexer/parser/codegen pipeline backed by the el-ui runtime
|
|
// (`el-ui.js`). This El surface is a placeholder.
|
|
//
|
|
// RUNTIME GAP — load-bearing:
|
|
// `elc` (the bootstrap El compiler) currently emits C only. There is no
|
|
// JavaScript or WebAssembly backend yet, so the El surface cannot
|
|
// actually compile a component to JS today.
|
|
//
|
|
// What this vessel provides:
|
|
// - The shape of the public API (Compiler with runtime_path,
|
|
// compile_component, compile_app).
|
|
// - A `CompileResult` record consumers can branch on.
|
|
// - Stub bodies that return a deterministic "not yet implemented"
|
|
// module string so downstream tooling can integration-test against
|
|
// a stable contract.
|
|
//
|
|
// When elc gains a JS/Wasm backend, swap the bodies of `lex`, `parse`,
|
|
// `semantic_check`, and `codegen` for real implementations — the public
|
|
// surface should not need to change.
|
|
|
|
// ── Errors ──────────────────────────────────────────────────────────────────
|
|
|
|
fn err_lex() -> String { "elc.lex" }
|
|
fn err_parse() -> String { "elc.parse" }
|
|
fn err_semantic() -> String { "elc.semantic" }
|
|
fn err_codegen() -> String { "elc.codegen" }
|
|
fn err_backend_missing() -> String { "elc.backend_missing" }
|
|
|
|
// ── Compile result ──────────────────────────────────────────────────────────
|
|
//
|
|
// Modeled as JSON since the runtime's product types and field accessors
|
|
// don't yet support the Bool/String mix we want here cleanly.
|
|
|
|
fn result_ok(code: String) -> String {
|
|
"{\"ok\":true,\"code\":" + json_quote(code) + ",\"error\":\"\"}"
|
|
}
|
|
|
|
fn result_err(error: String) -> String {
|
|
"{\"ok\":false,\"code\":\"\",\"error\":\"" + error + "\"}"
|
|
}
|
|
|
|
// Minimal JSON string quoter (escape backslash + double-quote only).
|
|
// elc still doesn't expose a json_string_quote builtin.
|
|
fn json_quote(s: String) -> String {
|
|
let escaped: String = str_replace(s, "\\", "\\\\")
|
|
let escaped = str_replace(escaped, "\"", "\\\"")
|
|
"\"" + escaped + "\""
|
|
}
|
|
|
|
fn result_ok_p(result_json: String) -> Bool {
|
|
json_get_bool(result_json, "ok")
|
|
}
|
|
|
|
// ── Compiler config ─────────────────────────────────────────────────────────
|
|
|
|
fn compiler_default_runtime_path() -> String { "./el-ui.js" }
|
|
fn compiler_default_target() -> String { "js" }
|
|
|
|
fn compiler_new() -> String {
|
|
"{\"runtime_path\":\"./el-ui.js\",\"target\":\"js\"}"
|
|
}
|
|
|
|
fn compiler_with_runtime(c_json: String, path: String) -> String {
|
|
json_set(c_json, "runtime_path", json_quote(path))
|
|
}
|
|
|
|
fn compiler_with_target(c_json: String, target: String) -> String {
|
|
json_set(c_json, "target", json_quote(target))
|
|
}
|
|
|
|
fn compiler_runtime_path(c_json: String) -> String {
|
|
json_get_string(c_json, "runtime_path")
|
|
}
|
|
|
|
fn compiler_target(c_json: String) -> String {
|
|
json_get_string(c_json, "target")
|
|
}
|
|
|
|
// ── Pipeline stages (stubs) ─────────────────────────────────────────────────
|
|
//
|
|
// Real implementations live in the Rust crate (lexer.rs, parser.rs,
|
|
// semantic.rs, codegen.rs). These El stubs only validate inputs and
|
|
// produce a marker payload so callers can wire the contract.
|
|
|
|
fn lex(source: String) -> String {
|
|
if str_eq(source, "") { return "" }
|
|
"[]" // would be tokens JSON
|
|
}
|
|
|
|
fn parse(tokens_json: String) -> String {
|
|
if str_eq(tokens_json, "") { return "" }
|
|
"[]" // would be Component AST JSON
|
|
}
|
|
|
|
fn semantic_check(ast_json: String) -> String {
|
|
ast_json // pass-through stub
|
|
}
|
|
|
|
fn codegen(c_json: String, ast_json: String) -> String {
|
|
let runtime: String = compiler_runtime_path(c_json)
|
|
let preamble: String = "// AUTOGENERATED by el-ui-compiler (STUB)\n"
|
|
let import_line: String = "import { register, h } from \"" + runtime + "\";\n"
|
|
let body: String = "throw new Error(\"" + err_backend_missing()
|
|
+ ": el-ui-compiler has no JS backend yet; emit C with elc instead\");\n"
|
|
preamble + import_line + body
|
|
}
|
|
|
|
// ── Public entry points ─────────────────────────────────────────────────────
|
|
|
|
fn compile_component(c_json: String, source: String) -> String {
|
|
let tokens: String = lex(source)
|
|
if str_eq(tokens, "") { return result_err(err_lex()) }
|
|
let ast: String = parse(tokens)
|
|
if str_eq(ast, "") { return result_err(err_parse()) }
|
|
let checked: String = semantic_check(ast)
|
|
if str_eq(checked, "") { return result_err(err_semantic()) }
|
|
let js: String = codegen(c_json, checked)
|
|
result_ok(js)
|
|
}
|
|
|
|
// `entry_source` is the app entry; `components_json` is a JSON object map of
|
|
// name -> source text. We compile each component first, then the entry.
|
|
fn compile_app(c_json: String, entry_source: String, components_json: String) -> String {
|
|
// RUNTIME GAP: no json_object_keys() yet, so we cannot iterate the map
|
|
// generically here. Real implementation will iterate and short-circuit
|
|
// on first compile error. For the stub we just compile the entry.
|
|
compile_component(c_json, entry_source)
|
|
}
|
|
|
|
// ── Entry — smoke test ──────────────────────────────────────────────────────
|
|
|
|
let c: String = compiler_new()
|
|
let c = compiler_with_runtime(c, "./el-ui.js")
|
|
let r: String = compile_component(c, "component Hello { template { <p>hi</p> } }")
|
|
if result_ok_p(r) {
|
|
println("[el-ui-compiler] STUB compiled (target=" + compiler_target(c) + ")")
|
|
} else {
|
|
println("[el-ui-compiler] error: " + json_get_string(r, "error"))
|
|
}
|