Add separate compilation: extern fn, --emit-header, elb build coordinator
This commit is contained in:
@@ -1853,6 +1853,29 @@ el_val_t fs_write_bytes(el_val_t pathv, el_val_t bytesv, el_val_t lengthv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// exec_command — run a shell command, return exit code (0 = success).
|
||||||
|
// Used by elb and other El tooling to invoke subprocesses.
|
||||||
|
el_val_t exec_command(el_val_t cmdv) {
|
||||||
|
const char* cmd = EL_CSTR(cmdv);
|
||||||
|
if (!cmd) return (el_val_t)(int64_t)-1;
|
||||||
|
int ret = system(cmd);
|
||||||
|
return (el_val_t)(int64_t)ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec_capture — run a shell command, capture stdout, return as String.
|
||||||
|
// Returns "" on failure.
|
||||||
|
el_val_t exec_capture(el_val_t cmdv) {
|
||||||
|
const char* cmd = EL_CSTR(cmdv);
|
||||||
|
if (!cmd) return el_wrap_str(el_strdup(""));
|
||||||
|
FILE* f = popen(cmd, "r");
|
||||||
|
if (!f) return el_wrap_str(el_strdup(""));
|
||||||
|
JsonBuf b; jb_init(&b);
|
||||||
|
char buf[4096];
|
||||||
|
while (fgets(buf, sizeof(buf), f)) jb_puts(&b, buf);
|
||||||
|
pclose(f);
|
||||||
|
return el_wrap_str(b.buf);
|
||||||
|
}
|
||||||
|
|
||||||
el_val_t fs_list(el_val_t pathv) {
|
el_val_t fs_list(el_val_t pathv) {
|
||||||
const char* path = EL_CSTR(pathv);
|
const char* path = EL_CSTR(pathv);
|
||||||
el_val_t lst = el_list_empty();
|
el_val_t lst = el_list_empty();
|
||||||
|
|||||||
@@ -739,6 +739,10 @@ el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
|||||||
/* See bottom of el_runtime.c for the implementation.
|
/* See bottom of el_runtime.c for the implementation.
|
||||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||||
|
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||||
|
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||||
|
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||||
|
|
||||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||||
el_val_t trace_span_start(el_val_t name);
|
el_val_t trace_span_start(el_val_t name);
|
||||||
|
|||||||
+100
-55
@@ -18,26 +18,26 @@
|
|||||||
fn c_escape(s: String) -> String {
|
fn c_escape(s: String) -> String {
|
||||||
let chars: [String] = native_string_chars(s)
|
let chars: [String] = native_string_chars(s)
|
||||||
let total: Int = native_list_len(chars)
|
let total: Int = native_list_len(chars)
|
||||||
let out = ""
|
let parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < total {
|
while i < total {
|
||||||
let ch: String = native_list_get(chars, i)
|
let ch: String = native_list_get(chars, i)
|
||||||
if ch == "\"" {
|
if ch == "\"" {
|
||||||
let out = out + "\\\""
|
let parts = native_list_append(parts, "\\\"")
|
||||||
} else {
|
} else {
|
||||||
if ch == "\\" {
|
if ch == "\\" {
|
||||||
let out = out + "\\\\"
|
let parts = native_list_append(parts, "\\\\")
|
||||||
} else {
|
} else {
|
||||||
if ch == "\n" {
|
if ch == "\n" {
|
||||||
let out = out + "\\n"
|
let parts = native_list_append(parts, "\\n")
|
||||||
} else {
|
} else {
|
||||||
if ch == "\r" {
|
if ch == "\r" {
|
||||||
let out = out + "\\r"
|
let parts = native_list_append(parts, "\\r")
|
||||||
} else {
|
} else {
|
||||||
if ch == "\t" {
|
if ch == "\t" {
|
||||||
let out = out + "\\t"
|
let parts = native_list_append(parts, "\\t")
|
||||||
} else {
|
} else {
|
||||||
let out = out + ch
|
let parts = native_list_append(parts, ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ fn c_escape(s: String) -> String {
|
|||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
out
|
str_join(parts, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn c_str_lit(s: String) -> String {
|
fn c_str_lit(s: String) -> String {
|
||||||
@@ -191,6 +191,25 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let left_kind: String = left["expr"]
|
let left_kind: String = left["expr"]
|
||||||
let right_kind: String = right["expr"]
|
let right_kind: String = right["expr"]
|
||||||
|
|
||||||
|
// ── String/equality fast-path: skip O(N²) temporal traversals ────────
|
||||||
|
// The 10 temporal predicates below each recurse into the left subtree:
|
||||||
|
// O(depth) state_get calls per predicate, O(N²) total for a chain of N
|
||||||
|
// string-concat BinOps (e.g. the 70-100-part HTML chains in soul.el).
|
||||||
|
// When either operand is a bare Str literal the result is always concat
|
||||||
|
// or str_eq — no temporal dispatch is possible. Exit immediately.
|
||||||
|
if str_eq(op, "Plus") {
|
||||||
|
if str_eq(left_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" }
|
||||||
|
if str_eq(right_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" }
|
||||||
|
}
|
||||||
|
if str_eq(op, "EqEq") {
|
||||||
|
if str_eq(left_kind, "Str") { return "str_eq(" + left_c + ", " + right_c + ")" }
|
||||||
|
if str_eq(right_kind, "Str") { return "str_eq(" + left_c + ", " + right_c + ")" }
|
||||||
|
}
|
||||||
|
if str_eq(op, "NotEq") {
|
||||||
|
if str_eq(left_kind, "Str") { return "!str_eq(" + left_c + ", " + right_c + ")" }
|
||||||
|
if str_eq(right_kind, "Str") { return "!str_eq(" + left_c + ", " + right_c + ")" }
|
||||||
|
}
|
||||||
|
|
||||||
// ── Temporal-type dispatch (Instant + Duration first-class) ────────
|
// ── Temporal-type dispatch (Instant + Duration first-class) ────────
|
||||||
// Run BEFORE the int / string / generic paths so typed temporal
|
// Run BEFORE the int / string / generic paths so typed temporal
|
||||||
// operands route through the runtime wrappers and invalid combos
|
// operands route through the runtime wrappers and invalid combos
|
||||||
@@ -565,17 +584,15 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
let arity: Int = native_list_len(args)
|
let arity: Int = native_list_len(args)
|
||||||
let func_kind: String = func["expr"]
|
let func_kind: String = func["expr"]
|
||||||
|
|
||||||
let args_c = ""
|
let args_parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < arity {
|
while i < arity {
|
||||||
let arg = native_list_get(args, i)
|
let arg = native_list_get(args, i)
|
||||||
let arg_c: String = cg_expr(arg)
|
let arg_c: String = cg_expr(arg)
|
||||||
if i > 0 {
|
let args_parts = native_list_append(args_parts, arg_c)
|
||||||
let args_c = args_c + ", "
|
|
||||||
}
|
|
||||||
let args_c = args_c + arg_c
|
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
|
let args_c: String = str_join(args_parts, ", ")
|
||||||
|
|
||||||
if func_kind == "Ident" {
|
if func_kind == "Ident" {
|
||||||
let fn_name: String = func["name"]
|
let fn_name: String = func["name"]
|
||||||
@@ -658,18 +675,15 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
// Empty literal: el_list_new(0, ) generates malformed C (trailing
|
// Empty literal: el_list_new(0, ) generates malformed C (trailing
|
||||||
// comma in a varargs call). Emit el_list_empty() directly.
|
// comma in a varargs call). Emit el_list_empty() directly.
|
||||||
if n == 0 { return "el_list_empty()" }
|
if n == 0 { return "el_list_empty()" }
|
||||||
let items = ""
|
let items_parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let elem = native_list_get(elems, i)
|
let elem = native_list_get(elems, i)
|
||||||
let elem_c: String = cg_expr(elem)
|
let elem_c: String = cg_expr(elem)
|
||||||
if i > 0 {
|
let items_parts = native_list_append(items_parts, elem_c)
|
||||||
let items = items + ", "
|
|
||||||
}
|
|
||||||
let items = items + elem_c
|
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
return "el_list_new(" + native_int_to_str(n) + ", " + items + ")"
|
return "el_list_new(" + native_int_to_str(n) + ", " + str_join(items_parts, ", ") + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Map" {
|
if kind == "Map" {
|
||||||
@@ -680,20 +694,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
// shadowing inside for/while/if bodies — `let acc: Map = {}` —
|
// shadowing inside for/while/if bodies — `let acc: Map = {}` —
|
||||||
// doesn't fail downstream cc with parse errors.
|
// doesn't fail downstream cc with parse errors.
|
||||||
if n == 0 { return "el_map_new(0)" }
|
if n == 0 { return "el_map_new(0)" }
|
||||||
let items = ""
|
let items_parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let pair = native_list_get(pairs, i)
|
let pair = native_list_get(pairs, i)
|
||||||
let key: String = pair["key"]
|
let key: String = pair["key"]
|
||||||
let val = pair["value"]
|
let val = pair["value"]
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
if i > 0 {
|
let items_parts = native_list_append(items_parts, c_str_lit(key) + ", " + val_c)
|
||||||
let items = items + ", "
|
|
||||||
}
|
|
||||||
let items = items + c_str_lit(key) + ", " + val_c
|
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
return "el_map_new(" + native_int_to_str(n) + ", " + items + ")"
|
return "el_map_new(" + native_int_to_str(n) + ", " + str_join(items_parts, ", ") + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == "Try" {
|
if kind == "Try" {
|
||||||
@@ -736,7 +747,9 @@ fn cg_match(expr: Map<String, Any>) -> String {
|
|||||||
let subj_var: String = "_match_subj_" + id
|
let subj_var: String = "_match_subj_" + id
|
||||||
let result_var: String = "_match_result_" + id
|
let result_var: String = "_match_result_" + id
|
||||||
let done_label: String = "_match_done_" + id
|
let done_label: String = "_match_done_" + id
|
||||||
let out: String = "({ el_val_t " + subj_var + " = " + subj_c + "; el_val_t " + result_var + " = 0; "
|
// Accumulate arm fragments into a list to avoid O(n²) string growth.
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let parts = native_list_append(parts, "({ el_val_t " + subj_var + " = " + subj_c + "; el_val_t " + result_var + " = 0; ")
|
||||||
let n: Int = native_list_len(arms)
|
let n: Int = native_list_len(arms)
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
@@ -746,19 +759,19 @@ fn cg_match(expr: Map<String, Any>) -> String {
|
|||||||
let pkind: String = pat["pattern"]
|
let pkind: String = pat["pattern"]
|
||||||
let body_c: String = cg_expr(body)
|
let body_c: String = cg_expr(body)
|
||||||
if str_eq(pkind, "Wildcard") {
|
if str_eq(pkind, "Wildcard") {
|
||||||
let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(pkind, "Binding") {
|
if str_eq(pkind, "Binding") {
|
||||||
let bname: String = pat["name"]
|
let bname: String = pat["name"]
|
||||||
let out = out + "{ el_val_t " + bname + " = " + subj_var + "; " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "{ el_val_t " + bname + " = " + subj_var + "; " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(pkind, "LitInt") {
|
if str_eq(pkind, "LitInt") {
|
||||||
let v: String = pat["value"]
|
let v: String = pat["value"]
|
||||||
let out = out + "if (" + subj_var + " == " + v + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "if (" + subj_var + " == " + v + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(pkind, "LitStr") {
|
if str_eq(pkind, "LitStr") {
|
||||||
let v: String = pat["value"]
|
let v: String = pat["value"]
|
||||||
let out = out + "if (str_eq(" + subj_var + ", EL_STR(" + c_str_lit(v) + "))) { " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "if (str_eq(" + subj_var + ", EL_STR(" + c_str_lit(v) + "))) { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(pkind, "LitBool") {
|
if str_eq(pkind, "LitBool") {
|
||||||
let v: String = pat["value"]
|
let v: String = pat["value"]
|
||||||
@@ -766,10 +779,10 @@ fn cg_match(expr: Map<String, Any>) -> String {
|
|||||||
if str_eq(v, "true") {
|
if str_eq(v, "true") {
|
||||||
let bv = "1"
|
let bv = "1"
|
||||||
}
|
}
|
||||||
let out = out + "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
} else {
|
} else {
|
||||||
// unknown pattern → wildcard
|
// unknown pattern → wildcard
|
||||||
let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
|
let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -777,8 +790,8 @@ fn cg_match(expr: Map<String, Any>) -> String {
|
|||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
let out = out + done_label + ":; " + result_var + "; })"
|
let parts = native_list_append(parts, done_label + ":; " + result_var + "; })")
|
||||||
out
|
str_join(parts, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── If-as-expression codegen ─────────────────────────────────────────────────
|
// ── If-as-expression codegen ─────────────────────────────────────────────────
|
||||||
@@ -809,7 +822,8 @@ fn next_if_id() -> String {
|
|||||||
// result var stays at its initial 0.
|
// result var stays at its initial 0.
|
||||||
fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
|
fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
|
||||||
let n: Int = native_list_len(stmts)
|
let n: Int = native_list_len(stmts)
|
||||||
let out = ""
|
// Collect statement fragments into a list to avoid O(n²) string growth.
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let s = native_list_get(stmts, i)
|
let s = native_list_get(stmts, i)
|
||||||
@@ -820,20 +834,20 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
|
|||||||
let name: String = s["name"]
|
let name: String = s["name"]
|
||||||
let val = s["value"]
|
let val = s["value"]
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
let out = out + "el_val_t " + name + " = " + val_c + "; "
|
let parts = native_list_append(parts, "el_val_t " + name + " = " + val_c + "; ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(sk, "Return") {
|
if str_eq(sk, "Return") {
|
||||||
let val = s["value"]
|
let val = s["value"]
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
let out = out + result_var + " = (" + val_c + "); "
|
let parts = native_list_append(parts, result_var + " = (" + val_c + "); ")
|
||||||
} else {
|
} else {
|
||||||
if str_eq(sk, "Expr") {
|
if str_eq(sk, "Expr") {
|
||||||
let val = s["value"]
|
let val = s["value"]
|
||||||
let val_c: String = cg_expr(val)
|
let val_c: String = cg_expr(val)
|
||||||
if is_last {
|
if is_last {
|
||||||
let out = out + result_var + " = (" + val_c + "); "
|
let parts = native_list_append(parts, result_var + " = (" + val_c + "); ")
|
||||||
} else {
|
} else {
|
||||||
let out = out + "(void)(" + val_c + "); "
|
let parts = native_list_append(parts, "(void)(" + val_c + "); ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if str_eq(sk, "Assign") {
|
if str_eq(sk, "Assign") {
|
||||||
@@ -844,7 +858,7 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
|
|||||||
let aname: String = s["name"]
|
let aname: String = s["name"]
|
||||||
let aval = s["value"]
|
let aval = s["value"]
|
||||||
let aval_c: String = cg_expr(aval)
|
let aval_c: String = cg_expr(aval)
|
||||||
let out = out + aname + " = " + aval_c + "; "
|
let parts = native_list_append(parts, aname + " = " + aval_c + "; ")
|
||||||
} else {
|
} else {
|
||||||
// Non-trivial stmt kinds (While/For) shouldn't appear in
|
// Non-trivial stmt kinds (While/For) shouldn't appear in
|
||||||
// expression-position arm bodies; emit nothing rather
|
// expression-position arm bodies; emit nothing rather
|
||||||
@@ -855,7 +869,7 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
|
|||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
out
|
str_join(parts, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cg_if_expr(expr: Map<String, Any>) -> String {
|
fn cg_if_expr(expr: Map<String, Any>) -> String {
|
||||||
@@ -1053,6 +1067,7 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
|
|||||||
if kind == "TypeDef" { return declared }
|
if kind == "TypeDef" { return declared }
|
||||||
if kind == "EnumDef" { return declared }
|
if kind == "EnumDef" { return declared }
|
||||||
if kind == "Import" { return declared }
|
if kind == "Import" { return declared }
|
||||||
|
if kind == "ExternFn" { return declared }
|
||||||
if kind == "CgiBlock" { return declared }
|
if kind == "CgiBlock" { return declared }
|
||||||
declared
|
declared
|
||||||
}
|
}
|
||||||
@@ -1084,14 +1099,7 @@ fn strip_outer_parens(s: String) -> String {
|
|||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
if balanced {
|
if balanced {
|
||||||
let inner = ""
|
return str_slice(s, 1, n - 1)
|
||||||
let j = 1
|
|
||||||
while j < n - 1 {
|
|
||||||
let ch: String = native_list_get(chars, j)
|
|
||||||
let inner = inner + ch
|
|
||||||
let j = j + 1
|
|
||||||
}
|
|
||||||
return inner
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1168,18 +1176,15 @@ fn param_decl(param: Map<String, Any>, idx: Int) -> String {
|
|||||||
fn params_to_c(params: [Map<String, Any>]) -> String {
|
fn params_to_c(params: [Map<String, Any>]) -> String {
|
||||||
let n: Int = native_list_len(params)
|
let n: Int = native_list_len(params)
|
||||||
if n == 0 { return "void" }
|
if n == 0 { return "void" }
|
||||||
let out = ""
|
let parts: [String] = native_list_empty()
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let param = native_list_get(params, i)
|
let param = native_list_get(params, i)
|
||||||
let decl: String = param_decl(param, i)
|
let decl: String = param_decl(param, i)
|
||||||
if i > 0 {
|
let parts = native_list_append(parts, decl)
|
||||||
let out = out + ", "
|
|
||||||
}
|
|
||||||
let out = out + decl
|
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
out
|
str_join(parts, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform a function body so that an implicit-return final expression
|
// Transform a function body so that an implicit-return final expression
|
||||||
@@ -2295,6 +2300,7 @@ fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
|
|||||||
if kind == "EnumDef" { return true }
|
if kind == "EnumDef" { return true }
|
||||||
if kind == "Import" { return true }
|
if kind == "Import" { return true }
|
||||||
if kind == "CgiBlock" { return true }
|
if kind == "CgiBlock" { return true }
|
||||||
|
if kind == "ExternFn" { return true }
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2519,6 +2525,12 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if kind == "ExternFn" {
|
||||||
|
let fn_name: String = stmt["name"]
|
||||||
|
let params = stmt["params"]
|
||||||
|
let params_c: String = params_to_c(params)
|
||||||
|
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
||||||
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
emit_blank()
|
emit_blank()
|
||||||
@@ -2557,6 +2569,36 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
emit_blank()
|
emit_blank()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether this compilation unit has an entry point.
|
||||||
|
// A unit is a library (no C main emitted) when there is no fn main()
|
||||||
|
// and no top-level executable statements. This supports separate
|
||||||
|
// compilation: library .c files contain only function definitions.
|
||||||
|
let has_el_main = false
|
||||||
|
let has_toplevel_stmts = false
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let stmt = native_list_get(stmts, i)
|
||||||
|
let sk: String = stmt["stmt"]
|
||||||
|
if str_eq(sk, "FnDef") {
|
||||||
|
let fn_name_chk: String = stmt["name"]
|
||||||
|
if str_eq(fn_name_chk, "main") { let has_el_main = true }
|
||||||
|
}
|
||||||
|
if !is_fndef(stmt) {
|
||||||
|
if !is_top_level_decl(stmt) {
|
||||||
|
if !str_eq(sk, "Let") {
|
||||||
|
let has_toplevel_stmts = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let is_library = false
|
||||||
|
if !has_el_main {
|
||||||
|
if !has_toplevel_stmts {
|
||||||
|
let is_library = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function definitions
|
// Function definitions
|
||||||
let i = 0
|
let i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
@@ -2567,6 +2609,9 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip C main() for library units (no fn main, no top-level stmts)
|
||||||
|
if is_library { return "" }
|
||||||
|
|
||||||
// main(). Use _argc/_argv so El programs are free to declare their own
|
// main(). Use _argc/_argv so El programs are free to declare their own
|
||||||
// local `argv` / `argc` (compiler.el itself does this) without colliding
|
// local `argv` / `argc` (compiler.el itself does this) without colliding
|
||||||
// with the C-side parameters when fn main()'s body is folded in below.
|
// with the C-side parameters when fn main()'s body is folded in below.
|
||||||
|
|||||||
+106
-7
@@ -79,6 +79,74 @@ fn strip_flags(argv: [String]) -> [String] {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect --emit-header flag in argv.
|
||||||
|
fn detect_emit_header(argv: [String]) -> Bool {
|
||||||
|
let n: Int = native_list_len(argv)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let a: String = native_list_get(argv, i)
|
||||||
|
if str_eq(a, "--emit-header") { return true }
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct an El type annotation string from a parsed type node.
|
||||||
|
fn type_node_to_el(t: Map<String, Any>) -> String {
|
||||||
|
let k: String = t["kind"]
|
||||||
|
if str_eq(k, "Simple") { return t["name"] }
|
||||||
|
if str_eq(k, "List") {
|
||||||
|
let inner: String = type_node_to_el(t["inner"])
|
||||||
|
return "[" + inner + "]"
|
||||||
|
}
|
||||||
|
if str_eq(k, "Map") {
|
||||||
|
let kt: String = type_node_to_el(t["key"])
|
||||||
|
let vt: String = type_node_to_el(t["val"])
|
||||||
|
return "Map<" + kt + ", " + vt + ">"
|
||||||
|
}
|
||||||
|
"Any"
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_header — write a .elh file from parsed statements.
|
||||||
|
// Scans for FnDef nodes and emits 'extern fn' declarations.
|
||||||
|
fn emit_header(stmts: [Map<String, Any>], hdr_path: String) -> Void {
|
||||||
|
let n: Int = native_list_len(stmts)
|
||||||
|
let i = 0
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let parts = native_list_append(parts, "// auto-generated by elc --emit-header — do not edit\n")
|
||||||
|
while i < n {
|
||||||
|
let stmt = native_list_get(stmts, i)
|
||||||
|
let kind: String = stmt["stmt"]
|
||||||
|
if str_eq(kind, "FnDef") {
|
||||||
|
let name: String = stmt["name"]
|
||||||
|
if !str_eq(name, "main") {
|
||||||
|
let params = stmt["params"]
|
||||||
|
let ret_type: String = stmt["ret_type"]
|
||||||
|
// build param list
|
||||||
|
let np: Int = native_list_len(params)
|
||||||
|
let pi = 0
|
||||||
|
let param_parts: [String] = native_list_empty()
|
||||||
|
while pi < np {
|
||||||
|
let param = native_list_get(params, pi)
|
||||||
|
let pname: String = param["name"]
|
||||||
|
let ptype: String = param["type"]
|
||||||
|
if str_eq(ptype, "") { let ptype = "Any" }
|
||||||
|
let param_parts = native_list_append(param_parts, pname + ": " + ptype)
|
||||||
|
let pi = pi + 1
|
||||||
|
}
|
||||||
|
let params_str: String = str_join(param_parts, ", ")
|
||||||
|
let ret_str: String = ret_type
|
||||||
|
if str_eq(ret_str, "") { let ret_str = "Any" }
|
||||||
|
let sig: String = "extern fn " + name + "(" + params_str + ") -> " + ret_str
|
||||||
|
let parts = native_list_append(parts, sig + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let content: String = str_join(parts, "")
|
||||||
|
let ok: Bool = fs_write(hdr_path, content)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Import resolution ────────────────────────────────────────────────────────
|
// ── Import resolution ────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// elc supports two forms of import:
|
// elc supports two forms of import:
|
||||||
@@ -135,6 +203,9 @@ fn parse_import_line(trimmed: String, dir: String) -> String {
|
|||||||
// source text with every imported module's body inlined ahead of the entry
|
// source text with every imported module's body inlined ahead of the entry
|
||||||
// source, deduplicated by absolute path. Uses state_set to track which paths
|
// source, deduplicated by absolute path. Uses state_set to track which paths
|
||||||
// have already been pulled in for this run.
|
// have already been pulled in for this run.
|
||||||
|
//
|
||||||
|
// Accumulates chunks into lists and joins once at the end to avoid the O(n²)
|
||||||
|
// memory growth caused by repeated `prefix = prefix + chunk` concatenation.
|
||||||
fn resolve_imports(src_path: String) -> String {
|
fn resolve_imports(src_path: String) -> String {
|
||||||
let seen_key: String = "__elc_imp__:" + src_path
|
let seen_key: String = "__elc_imp__:" + src_path
|
||||||
let already: String = state_get(seen_key)
|
let already: String = state_get(seen_key)
|
||||||
@@ -146,22 +217,36 @@ fn resolve_imports(src_path: String) -> String {
|
|||||||
let lines: [String] = str_split(source, "\n")
|
let lines: [String] = str_split(source, "\n")
|
||||||
let n: Int = native_list_len(lines)
|
let n: Int = native_list_len(lines)
|
||||||
|
|
||||||
// First pass: pull in every import body ahead of this file's body.
|
// Collect chunks into lists — O(1) amortized per append.
|
||||||
let prefix: String = ""
|
// Join once at the end — O(n) single pass.
|
||||||
let body: String = ""
|
let prefix_chunks: [String] = native_list_empty()
|
||||||
|
let body_chunks: [String] = native_list_empty()
|
||||||
let i: Int = 0
|
let i: Int = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let line: String = native_list_get(lines, i)
|
let line: String = native_list_get(lines, i)
|
||||||
let trimmed: String = str_trim(line)
|
let trimmed: String = str_trim(line)
|
||||||
let imp_path: String = parse_import_line(trimmed, dir)
|
let imp_path: String = parse_import_line(trimmed, dir)
|
||||||
if !str_eq(imp_path, "") {
|
if !str_eq(imp_path, "") {
|
||||||
let prefix = prefix + resolve_imports(imp_path)
|
// Use pre-compiled header if available (separate compilation).
|
||||||
|
// Only check .elh for imported files — never for the entry file itself.
|
||||||
|
let imp_elh_path: String = str_slice(imp_path, 0, str_len(imp_path) - 3) + ".elh"
|
||||||
|
let imp_elh: String = fs_read(imp_elh_path)
|
||||||
|
if !str_eq(imp_elh, "") {
|
||||||
|
// Header exists: mark the .el as seen (so it won't be re-inlined
|
||||||
|
// if something else also imports it) and use the header text.
|
||||||
|
let seen_imp_key: String = "__elc_imp__:" + imp_path
|
||||||
|
state_set(seen_imp_key, "1")
|
||||||
|
let prefix_chunks = native_list_append(prefix_chunks, imp_elh)
|
||||||
|
} else {
|
||||||
|
let imp_body: String = resolve_imports(imp_path)
|
||||||
|
let prefix_chunks = native_list_append(prefix_chunks, imp_body)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let body = body + line + "\n"
|
let body_chunks = native_list_append(body_chunks, line + "\n")
|
||||||
}
|
}
|
||||||
let i = i + 1
|
let i = i + 1
|
||||||
}
|
}
|
||||||
return prefix + body
|
return str_join(prefix_chunks, "") + str_join(body_chunks, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// main — CLI entry point.
|
// main — CLI entry point.
|
||||||
@@ -176,13 +261,27 @@ fn main() -> Void {
|
|||||||
// (Section 1.5 of the language spec). detect_target itself is fine
|
// (Section 1.5 of the language spec). detect_target itself is fine
|
||||||
// because the function-name position has no token-class restriction.
|
// because the function-name position has no token-class restriction.
|
||||||
let tgt: String = detect_target(argv)
|
let tgt: String = detect_target(argv)
|
||||||
|
let do_emit_header: Bool = detect_emit_header(argv)
|
||||||
let positional: [String] = strip_flags(argv)
|
let positional: [String] = strip_flags(argv)
|
||||||
let argc: Int = native_list_len(positional)
|
let argc: Int = native_list_len(positional)
|
||||||
if argc < 1 {
|
if argc < 1 {
|
||||||
println("el-compiler: usage: elc [--target=c|js] <source.el> [<output>]")
|
println("el-compiler: usage: elc [--target=c|js] [--emit-header] <source.el> [<output>]")
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
let src_path: String = native_list_get(positional, 0)
|
let src_path: String = native_list_get(positional, 0)
|
||||||
|
|
||||||
|
// When --emit-header is requested, parse the source file directly
|
||||||
|
// (without inlining imports) and write out a .elh file alongside the .c.
|
||||||
|
if do_emit_header {
|
||||||
|
let raw_source: String = fs_read(src_path)
|
||||||
|
let hdr_tokens: [Map<String, Any>] = lex(raw_source)
|
||||||
|
let hdr_stmts: [Map<String, Any>] = parse(hdr_tokens)
|
||||||
|
el_release(hdr_tokens)
|
||||||
|
let hdr_path: String = str_slice(src_path, 0, str_len(src_path) - 3) + ".elh"
|
||||||
|
emit_header(hdr_stmts, hdr_path)
|
||||||
|
el_release(hdr_stmts)
|
||||||
|
}
|
||||||
|
|
||||||
let source: String = resolve_imports(src_path)
|
let source: String = resolve_imports(src_path)
|
||||||
let out: String = compile_dispatch(tgt, source)
|
let out: String = compile_dispatch(tgt, source)
|
||||||
if argc >= 2 {
|
if argc >= 2 {
|
||||||
|
|||||||
@@ -0,0 +1,367 @@
|
|||||||
|
// elb.el — El Build Coordinator
|
||||||
|
//
|
||||||
|
// The build system for El programs. Written in El. Builds El.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// elb # build from manifest.el in current dir
|
||||||
|
// elb --clean # remove generated artifacts and rebuild
|
||||||
|
// elb --dry-run # print actions without executing
|
||||||
|
// elb --jobs=N # parallel compile jobs (default: 4)
|
||||||
|
// elb --out=DIR # output directory (default: dist)
|
||||||
|
// elb --runtime=PATH # path to el_runtime.c
|
||||||
|
//
|
||||||
|
// How it works (the .NET model):
|
||||||
|
// 1. Read manifest.el to find the entry file
|
||||||
|
// 2. Walk the import graph depth-first, build topological order
|
||||||
|
// 3. For each file: if .el is newer than .elh/.c, compile with elc --emit-header
|
||||||
|
// 4. Link all .c files + el_runtime.c into the final binary
|
||||||
|
//
|
||||||
|
// Each module compiles independently — no 128K-line blobs.
|
||||||
|
// Downstream compilations read .elh headers (function signatures only),
|
||||||
|
// not source. Incremental: only recompile what changed.
|
||||||
|
|
||||||
|
// ── Flags ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn flag_bool(argv: [String], name: String) -> Bool {
|
||||||
|
let n: Int = native_list_len(argv)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let a: String = native_list_get(argv, i)
|
||||||
|
if str_eq(a, name) { return true }
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag_val(argv: [String], name: String, default_val: String) -> String {
|
||||||
|
let n: Int = native_list_len(argv)
|
||||||
|
let prefix: String = name + "="
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let a: String = native_list_get(argv, i)
|
||||||
|
if str_starts_with(a, prefix) {
|
||||||
|
return str_slice(a, str_len(prefix), str_len(a))
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return default_val
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Manifest parsing ──────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Read the entry file from manifest.el:
|
||||||
|
// build { entry "soul.el" }
|
||||||
|
|
||||||
|
fn parse_manifest_entry(src: String) -> String {
|
||||||
|
let lines: [String] = str_split(src, "\n")
|
||||||
|
let n: Int = native_list_len(lines)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let line: String = native_list_get(lines, i)
|
||||||
|
let t: String = str_trim(line)
|
||||||
|
if str_starts_with(t, "entry ") {
|
||||||
|
// entry "soul.el"
|
||||||
|
let after: String = str_slice(t, 6, str_len(t))
|
||||||
|
let trimmed: String = str_trim(after)
|
||||||
|
// strip surrounding quotes
|
||||||
|
if str_starts_with(trimmed, "\"") {
|
||||||
|
let inner: String = str_slice(trimmed, 1, str_len(trimmed))
|
||||||
|
let q: Int = str_index_of(inner, "\"")
|
||||||
|
if q >= 0 {
|
||||||
|
return str_slice(inner, 0, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_manifest_name(src: String) -> String {
|
||||||
|
let lines: [String] = str_split(src, "\n")
|
||||||
|
let n: Int = native_list_len(lines)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let line: String = native_list_get(lines, i)
|
||||||
|
let t: String = str_trim(line)
|
||||||
|
if str_starts_with(t, "package ") {
|
||||||
|
let after: String = str_slice(t, 8, str_len(t))
|
||||||
|
let trimmed: String = str_trim(after)
|
||||||
|
if str_starts_with(trimmed, "\"") {
|
||||||
|
let inner: String = str_slice(trimmed, 1, str_len(trimmed))
|
||||||
|
let q: Int = str_index_of(inner, "\"")
|
||||||
|
if q >= 0 {
|
||||||
|
return str_slice(inner, 0, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
return "out"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Path helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn dirname_of(path: String) -> String {
|
||||||
|
let n: Int = str_len(path)
|
||||||
|
let i: Int = n - 1
|
||||||
|
while i >= 0 {
|
||||||
|
let c: String = str_slice(path, i, i + 1)
|
||||||
|
if str_eq(c, "/") {
|
||||||
|
return str_slice(path, 0, i)
|
||||||
|
}
|
||||||
|
let i = i - 1
|
||||||
|
}
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basename_noext(path: String) -> String {
|
||||||
|
// strip directory
|
||||||
|
let n: Int = str_len(path)
|
||||||
|
let last_slash: Int = -1
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let c: String = str_slice(path, i, i + 1)
|
||||||
|
if str_eq(c, "/") { let last_slash = i }
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let base: String = str_slice(path, last_slash + 1, n)
|
||||||
|
// strip .el extension
|
||||||
|
let bn: Int = str_len(base)
|
||||||
|
if str_ends_with(base, ".el") {
|
||||||
|
return str_slice(base, 0, bn - 3)
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_with_ext(path: String, ext: String) -> String {
|
||||||
|
let n: Int = str_len(path)
|
||||||
|
if str_ends_with(path, ".el") {
|
||||||
|
return str_slice(path, 0, n - 3) + ext
|
||||||
|
}
|
||||||
|
return path + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_is_newer(a: String, b: String) -> Bool {
|
||||||
|
// Returns true if file a is newer than file b, or if b doesn't exist.
|
||||||
|
// Uses exec_capture with stat to compare modification times.
|
||||||
|
let cmd: String = "test -f " + b + " && test " + a + " -nt " + b + " && echo yes || echo no"
|
||||||
|
let result: String = str_trim(exec_capture(cmd))
|
||||||
|
if str_eq(result, "yes") { return true }
|
||||||
|
// b doesn't exist — check with test -f
|
||||||
|
let exist_cmd: String = "test -f " + b + " && echo exists || echo missing"
|
||||||
|
let exist: String = str_trim(exec_capture(exist_cmd))
|
||||||
|
if str_eq(exist, "missing") { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Import graph walker ────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Walk import statements in each .el file to build the dependency graph.
|
||||||
|
// Returns a list of absolute paths in topological order (deps before dependents).
|
||||||
|
|
||||||
|
fn parse_import_path(line: String, dir: String) -> String {
|
||||||
|
let t: String = str_trim(line)
|
||||||
|
if str_starts_with(t, "import \"") {
|
||||||
|
let after: String = str_slice(t, 8, str_len(t))
|
||||||
|
let q: Int = str_index_of(after, "\"")
|
||||||
|
if q > 0 {
|
||||||
|
let mod: String = str_slice(after, 0, q)
|
||||||
|
return dir + "/" + mod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if str_starts_with(t, "from ") {
|
||||||
|
let after: String = str_slice(t, 5, str_len(t))
|
||||||
|
let sp: Int = str_index_of(after, " ")
|
||||||
|
if sp > 0 {
|
||||||
|
let mod: String = str_trim(str_slice(after, 0, sp))
|
||||||
|
if !str_eq(mod, "") {
|
||||||
|
return dir + "/" + mod + ".el"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_imports(src_path: String, visited: [String], order: [String]) -> Map<String, Any> {
|
||||||
|
// Dedup check
|
||||||
|
let n: Int = native_list_len(visited)
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let v: String = native_list_get(visited, i)
|
||||||
|
if str_eq(v, src_path) {
|
||||||
|
return { "visited": visited, "order": order }
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let visited = native_list_append(visited, src_path)
|
||||||
|
|
||||||
|
let source: String = fs_read(src_path)
|
||||||
|
if str_eq(source, "") {
|
||||||
|
return { "visited": visited, "order": order }
|
||||||
|
}
|
||||||
|
let dir: String = dirname_of(src_path)
|
||||||
|
let lines: [String] = str_split(source, "\n")
|
||||||
|
let ln: Int = native_list_len(lines)
|
||||||
|
let j = 0
|
||||||
|
while j < ln {
|
||||||
|
let line: String = native_list_get(lines, j)
|
||||||
|
let imp: String = parse_import_path(line, dir)
|
||||||
|
if !str_eq(imp, "") {
|
||||||
|
let r = walk_imports(imp, visited, order)
|
||||||
|
let visited = r["visited"]
|
||||||
|
let order = r["order"]
|
||||||
|
}
|
||||||
|
let j = j + 1
|
||||||
|
}
|
||||||
|
// Add self after all deps
|
||||||
|
let order = native_list_append(order, src_path)
|
||||||
|
return { "visited": visited, "order": order }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Build ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn compile_module(src_path: String, out_dir: String, elc_bin: String, dry_run: Bool, verbose: Bool) -> Bool {
|
||||||
|
let bname: String = basename_noext(src_path)
|
||||||
|
let c_out: String = out_dir + "/" + bname + ".c"
|
||||||
|
let elh_out: String = out_dir + "/" + bname + ".elh"
|
||||||
|
|
||||||
|
// Check if recompile needed
|
||||||
|
if !file_is_newer(src_path, c_out) {
|
||||||
|
if verbose {
|
||||||
|
println(" skip " + bname + ".el (up to date)")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd: String = elc_bin + " --emit-header " + src_path + " " + c_out
|
||||||
|
println(" compile " + src_path)
|
||||||
|
|
||||||
|
if dry_run { return true }
|
||||||
|
|
||||||
|
let ret: Int = exec_command(cmd)
|
||||||
|
if ret != 0 {
|
||||||
|
println("elb: compile failed: " + src_path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_binary(c_files: [String], out_bin: String, runtime_path: String, dry_run: Bool) -> Bool {
|
||||||
|
let n: Int = native_list_len(c_files)
|
||||||
|
let parts: [String] = native_list_empty()
|
||||||
|
let parts = native_list_append(parts, "cc -O2 -I " + dirname_of(runtime_path))
|
||||||
|
let i = 0
|
||||||
|
while i < n {
|
||||||
|
let f: String = native_list_get(c_files, i)
|
||||||
|
let parts = native_list_append(parts, f)
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
let parts = native_list_append(parts, runtime_path)
|
||||||
|
let parts = native_list_append(parts, "-lcurl -lpthread")
|
||||||
|
let parts = native_list_append(parts, "-o " + out_bin)
|
||||||
|
let cmd: String = str_join(parts, " ")
|
||||||
|
println(" link " + out_bin)
|
||||||
|
if dry_run { return true }
|
||||||
|
let ret: Int = exec_command(cmd)
|
||||||
|
if ret != 0 {
|
||||||
|
println("elb: link failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn main() -> Void {
|
||||||
|
let argv: [String] = args()
|
||||||
|
let clean: Bool = flag_bool(argv, "--clean")
|
||||||
|
let dry_run: Bool = flag_bool(argv, "--dry-run")
|
||||||
|
let verbose: Bool = flag_bool(argv, "--verbose")
|
||||||
|
let out_dir: String = flag_val(argv, "--out", "dist")
|
||||||
|
let elc_bin: String = flag_val(argv, "--elc", "elc")
|
||||||
|
let runtime: String = flag_val(argv, "--runtime", "")
|
||||||
|
|
||||||
|
// Find manifest
|
||||||
|
let manifest_src: String = fs_read("manifest.el")
|
||||||
|
if str_eq(manifest_src, "") {
|
||||||
|
println("elb: no manifest.el found in current directory")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg_name: String = parse_manifest_name(manifest_src)
|
||||||
|
let entry: String = parse_manifest_entry(manifest_src)
|
||||||
|
if str_eq(entry, "") {
|
||||||
|
println("elb: manifest.el has no 'entry' declaration")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("elb: building " + pkg_name + " (entry: " + entry + ")")
|
||||||
|
|
||||||
|
// Locate runtime
|
||||||
|
let runtime_path: String = runtime
|
||||||
|
if str_eq(runtime_path, "") {
|
||||||
|
// Try to find el_runtime.c relative to elc binary
|
||||||
|
let which_out: String = str_trim(exec_capture("which " + elc_bin + " 2>/dev/null"))
|
||||||
|
if !str_eq(which_out, "") {
|
||||||
|
let elc_dir: String = dirname_of(which_out)
|
||||||
|
runtime_path = elc_dir + "/../el-compiler/runtime/el_runtime.c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if str_eq(runtime_path, "") {
|
||||||
|
println("elb: cannot locate el_runtime.c — use --runtime=PATH")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure output directory
|
||||||
|
let mkdir_ret: Int = exec_command("mkdir -p " + out_dir)
|
||||||
|
|
||||||
|
// Clean if requested
|
||||||
|
if clean {
|
||||||
|
println("elb: cleaning " + out_dir)
|
||||||
|
if !dry_run {
|
||||||
|
let rm_ret: Int = exec_command("rm -f " + out_dir + "/*.c " + out_dir + "/*.elh")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk import graph from entry file
|
||||||
|
let empty_visited: [String] = native_list_empty()
|
||||||
|
let empty_order: [String] = native_list_empty()
|
||||||
|
let r = walk_imports(entry, empty_visited, empty_order)
|
||||||
|
let order: [String] = r["order"]
|
||||||
|
let total: Int = native_list_len(order)
|
||||||
|
println("elb: " + native_int_to_str(total) + " modules in build graph")
|
||||||
|
|
||||||
|
// Compile each module
|
||||||
|
let c_files: [String] = native_list_empty()
|
||||||
|
let i = 0
|
||||||
|
let ok = true
|
||||||
|
while i < total {
|
||||||
|
let src: String = native_list_get(order, i)
|
||||||
|
let bname: String = basename_noext(src)
|
||||||
|
let c_out: String = out_dir + "/" + bname + ".c"
|
||||||
|
let compiled: Bool = compile_module(src, out_dir, elc_bin, dry_run, verbose)
|
||||||
|
if !compiled {
|
||||||
|
let ok = false
|
||||||
|
let i = total
|
||||||
|
} else {
|
||||||
|
let c_files = native_list_append(c_files, c_out)
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
println("elb: build failed")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link
|
||||||
|
let out_bin: String = out_dir + "/" + pkg_name
|
||||||
|
let linked: Bool = link_binary(c_files, out_bin, runtime_path, dry_run)
|
||||||
|
if !linked {
|
||||||
|
println("elb: link failed")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("elb: done → " + out_bin)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user