// arbor-cli — the `arbor` command-line tool. // // Self-contained driver. Because El's `import` form is presently purely // syntactic source-concatenation, this file inlines just enough of the // parse / layout / render pipeline to let the binary stand alone. The // inlined logic is the public surface of arbor-parse, arbor-layout, and // arbor-render — same field names, same wire shapes — so the behaviour // matches the standalone vessels. // // Subcommands: // arbor render [-o out.svg] → write SVG to disk // arbor check → parse and report counts // arbor ls → list nodes / edges / groups // arbor fmt [--in-place] → canonical .arbor formatting // arbor version → "Arbor v0.1.0" // // PNG output is intentionally unsupported (the El runtime has no SVG // rasterizer; see report). // // Smoke test: when the binary is invoked with no args we print usage and // run a built-in self-test (parse → summarise → render-to-string) so the // binary doubles as the vessel's verification harness. // ── Inlined parse helpers (subset of arbor-parse) ─────────────────────────── fn cli_is_alnum_underscore(ch: String) -> Bool { let code: Int = str_char_code(ch, 0) if code >= 48 { if code <= 57 { return true } } if code >= 65 { if code <= 90 { return true } } if code >= 97 { if code <= 122 { return true } } if code == 95 { return true } false } fn cli_is_ascii_digit(ch: String) -> Bool { let code: Int = str_char_code(ch, 0) if code >= 48 { if code <= 57 { return true } } false } fn cli_sanitize_id(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "node" } let out = "" let prev_underscore = false let i = 0 while i < n { let ch: String = str_char_at(s, i) if cli_is_alnum_underscore(ch) { let out = out + ch let prev_underscore = false } else { if !prev_underscore { let out = out + "_" } let prev_underscore = true } let i = i + 1 } let m: Int = str_len(out) let end = m let stripping = true while stripping { if end <= 0 { let stripping = false } else { let last: String = str_char_at(out, end - 1) if last == "_" { let end = end - 1 } else { let stripping = false } } } let out = str_slice(out, 0, end) if str_len(out) == 0 { return "node" } let first: String = str_char_at(out, 0) if cli_is_ascii_digit(first) { let out = "n" + out } out } fn cli_shape_token(tok: String) -> String { let t: String = str_trim(tok) if t == "rect" { return "rect" } if t == "rounded" { return "rounded" } if t == "cylinder" { return "cylinder" } if t == "diamond" { return "diamond" } if t == "stadium" { return "stadium" } if t == "primary" { return "primary" } "" } fn cli_parse_quoted(s: String) -> Map { let t: String = str_trim(s) if str_len(t) < 2 { return { "ok": false, "value": "", "rest": s } } let first: String = str_char_at(t, 0) if first != "\"" { return { "ok": false, "value": "", "rest": s } } let body: String = str_slice(t, 1, str_len(t)) let close: Int = str_index_of(body, "\"") if close < 0 { return { "ok": false, "value": "", "rest": s } } let inner: String = str_slice(body, 0, close) let rest: String = str_slice(body, close + 1, str_len(body)) { "ok": true, "value": inner, "rest": rest } } fn cli_split_id(s: String) -> Map { let n: Int = str_len(s) let i = 0 while i < n { let ch: String = str_char_at(s, i) if !cli_is_alnum_underscore(ch) { return { "id": str_slice(s, 0, i), "rest": str_slice(s, i, n) } } let i = i + 1 } { "id": s, "rest": "" } } fn cli_extract_arrow(line: String) -> Map { let f1: Int = str_index_of(line, "-/->") if f1 >= 0 { return { "ok": true, "from_str": str_slice(line, 0, f1), "kind": "forbidden", "rest": str_slice(line, f1 + 4, str_len(line)) } } let f2: Int = str_index_of(line, "<->") if f2 >= 0 { return { "ok": true, "from_str": str_slice(line, 0, f2), "kind": "bidirectional", "rest": str_slice(line, f2 + 3, str_len(line)) } } let f3: Int = str_index_of(line, "-->") if f3 >= 0 { return { "ok": true, "from_str": str_slice(line, 0, f3), "kind": "dashed", "rest": str_slice(line, f3 + 3, str_len(line)) } } let f4: Int = str_index_of(line, "->") if f4 >= 0 { return { "ok": true, "from_str": str_slice(line, 0, f4), "kind": "solid", "rest": str_slice(line, f4 + 2, str_len(line)) } } { "ok": false, "from_str": "", "kind": "", "rest": "" } } fn cli_is_edge(line: String) -> Bool { if str_contains(line, "->") { return true } if str_contains(line, "<->") { return true } false } // State-keyed parser, same shape as arbor-parse. fn cli_st_set_int(key: String, v: Int) -> Int { state_set(key, int_to_str(v)); 0 } fn cli_st_get_int(key: String) -> Int { let s: String = state_get(key) if str_eq(s, "") { return 0 } str_to_int(s) } fn cli_reset() -> Int { state_set("graph_title", "") state_set("graph_direction", "top-down") cli_st_set_int("node_count", 0) cli_st_set_int("edge_count", 0) cli_st_set_int("group_count", 0) cli_st_set_int("group_stack_depth", 0) state_set("parse_error", "") cli_st_set_int("parse_error_line", 0) state_set("parse_error_text", "") 0 } fn cli_set_error(msg: String, line_no: Int, text: String) -> Int { state_set("parse_error", msg) cli_st_set_int("parse_error_line", line_no) state_set("parse_error_text", text) 0 } fn cli_has_error() -> Bool { let m: String = state_get("parse_error") if str_eq(m, "") { return false } true } fn cli_store_node(id: String, label: String, shape: String) -> Int { let n: Int = cli_st_get_int("node_count") state_set("node_id_" + int_to_str(n), id) state_set("node_label_" + int_to_str(n), label) state_set("node_shape_" + int_to_str(n), shape) cli_st_set_int("node_count", n + 1) 0 } fn cli_store_edge(src: String, dst: String, label: String, kind: String) -> Int { let n: Int = cli_st_get_int("edge_count") state_set("edge_from_" + int_to_str(n), src) state_set("edge_to_" + int_to_str(n), dst) state_set("edge_label_" + int_to_str(n), label) state_set("edge_kind_" + int_to_str(n), kind) cli_st_set_int("edge_count", n + 1) 0 } fn cli_store_group(id: String, label: String, ids: String) -> Int { let n: Int = cli_st_get_int("group_count") state_set("group_id_" + int_to_str(n), id) state_set("group_label_" + int_to_str(n), label) state_set("group_ids_" + int_to_str(n), ids) cli_st_set_int("group_count", n + 1) 0 } fn cli_group_frame_key(idx: Int, suffix: String) -> String { "gs_" + int_to_str(idx) + "_" + suffix } fn cli_open_group(id: String, label: String, line_no: Int) -> Int { let depth: Int = cli_st_get_int("group_stack_depth") state_set(cli_group_frame_key(depth, "id"), id) state_set(cli_group_frame_key(depth, "label"), label) state_set(cli_group_frame_key(depth, "line"), int_to_str(line_no)) state_set(cli_group_frame_key(depth, "ids"), "") cli_st_set_int("group_stack_depth", depth + 1) 0 } fn cli_close_group() -> Map { let depth: Int = cli_st_get_int("group_stack_depth") if depth <= 0 { return { "ok": false, "id": "", "label": "", "ids": "" } } let idx: Int = depth - 1 let id: String = state_get(cli_group_frame_key(idx, "id")) let label: String = state_get(cli_group_frame_key(idx, "label")) let ids: String = state_get(cli_group_frame_key(idx, "ids")) cli_st_set_int("group_stack_depth", idx) { "ok": true, "id": id, "label": label, "ids": ids } } fn cli_register_in_group(node_id: String) -> Int { let depth: Int = cli_st_get_int("group_stack_depth") if depth <= 0 { return 0 } let idx: Int = depth - 1 let key: String = cli_group_frame_key(idx, "ids") let prev: String = state_get(key) if str_eq(prev, "") { state_set(key, node_id) } else { state_set(key, prev + "" + node_id) } 0 } fn cli_parse_node(line_no: Int, line: String) -> Int { let s: Map = cli_split_id(line) let raw_id: String = s["id"] if str_eq(raw_id, "") { cli_set_error("expected node id, edge, or keyword", line_no, line) return 0 } let id: String = cli_sanitize_id(raw_id) let rest: String = str_trim(s["rest"]) let shape = "rect" let after_shape = rest if str_len(rest) > 0 { let lead: String = str_char_at(rest, 0) if lead == "[" { let close: Int = str_index_of(rest, "]") if close < 0 { cli_set_error("unclosed `[` in shape token", line_no, line) return 0 } let token: String = str_slice(rest, 1, close) let parsed: String = cli_shape_token(token) if str_eq(parsed, "") { cli_set_error("unknown shape `" + token + "`", line_no, line) return 0 } let shape = parsed let after_shape = str_trim(str_slice(rest, close + 1, str_len(rest))) } } let q: Map = cli_parse_quoted(after_shape) let label = raw_id let ok: Bool = q["ok"] if ok { let label = q["value"] } cli_store_node(id, label, shape) cli_register_in_group(id) 1 } fn cli_parse_edge(line_no: Int, line: String) -> Int { let p: Map = cli_extract_arrow(line) let ok: Bool = p["ok"] if !ok { cli_set_error("malformed edge", line_no, line) return 0 } let from_str: String = p["from_str"] let rest_str: String = p["rest"] let kind: String = p["kind"] let src: String = cli_sanitize_id(str_trim(from_str)) let rest_t: String = str_trim(rest_str) let s: Map = cli_split_id(rest_t) let to_raw: String = s["id"] if str_eq(to_raw, "") { cli_set_error("edge missing target id", line_no, line) return 0 } let dst: String = cli_sanitize_id(to_raw) let label_rest: String = str_trim(s["rest"]) let q: Map = cli_parse_quoted(label_rest) let label = "" let qok: Bool = q["ok"] if qok { let label = q["value"] } cli_store_edge(src, dst, label, kind) 1 } fn cli_parse_group_open(line_no: Int, line: String, rest: String) -> Int { let trimmed: String = str_trim(rest) let n: Int = str_len(trimmed) let body = trimmed if n > 0 { let last: String = str_char_at(trimmed, n - 1) if last == "{" { let body = str_trim(str_slice(trimmed, 0, n - 1)) } } let s: Map = cli_split_id(body) let raw_id: String = s["id"] if str_eq(raw_id, "") { cli_set_error("group declaration missing id", line_no, line) return 0 } let label_rest: String = str_trim(s["rest"]) let q: Map = cli_parse_quoted(label_rest) let label = raw_id let qok: Bool = q["ok"] if qok { let label = q["value"] } cli_open_group(raw_id, label, line_no) 1 } fn cli_dispatch(line_no: Int, line: String) -> Int { if line == "}" { let frame: Map = cli_close_group() let frame_ok: Bool = frame["ok"] if !frame_ok { cli_set_error("unexpected `}`", line_no, "}") return 0 } cli_store_group(frame["id"], frame["label"], frame["ids"]) return 1 } if str_starts_with(line, "title:") { let after: String = str_trim(str_slice(line, 6, str_len(line))) let q: Map = cli_parse_quoted(after) let qok: Bool = q["ok"] if !qok { cli_set_error("expected quoted string after `title:`", line_no, line); return 0 } state_set("graph_title", q["value"]) return 1 } if str_starts_with(line, "direction:") { let after: String = str_trim(str_slice(line, 10, str_len(line))) if str_eq(after, "TD") { state_set("graph_direction", "top-down"); return 1 } if str_eq(after, "LR") { state_set("graph_direction", "left-right"); return 1 } if str_eq(after, "RL") { state_set("graph_direction", "right-left"); return 1 } if str_eq(after, "BU") { state_set("graph_direction", "bottom-up"); return 1 } if str_eq(after, "top-down") { state_set("graph_direction", "top-down"); return 1 } if str_eq(after, "left-right") { state_set("graph_direction", "left-right"); return 1 } if str_eq(after, "right-left") { state_set("graph_direction", "right-left"); return 1 } if str_eq(after, "bottom-up") { state_set("graph_direction", "bottom-up"); return 1 } cli_set_error("unknown direction", line_no, line) return 0 } if str_starts_with(line, "group ") { let after: String = str_slice(line, 6, str_len(line)) return cli_parse_group_open(line_no, line, after) } if cli_is_edge(line) { return cli_parse_edge(line_no, line) } cli_parse_node(line_no, line) } fn cli_preprocess(source: String) -> [Map] { let lines: [String] = str_split(source, "\n") let n: Int = el_list_len(lines) let out: [Map] = native_list_empty() let i = 0 while i < n { let raw: String = get(lines, i) let cidx: Int = str_index_of(raw, "//") let stripped = raw if cidx >= 0 { let stripped = str_slice(raw, 0, cidx) } let trimmed: String = str_trim(stripped) if str_len(trimmed) > 0 { let row: Map = { "no": i + 1, "text": trimmed } let out = native_list_append(out, row) } let i = i + 1 } out } fn cli_parse(source: String) -> Map { cli_reset() let lines: [Map] = cli_preprocess(source) let n: Int = el_list_len(lines) let i = 0 let abort = false while i < n { if abort { // skip } else { let row: Map = get(lines, i) let line_no: Int = row["no"] let text: String = row["text"] cli_dispatch(line_no, text) if cli_has_error() { let abort = true } } let i = i + 1 } if !cli_has_error() { let depth: Int = cli_st_get_int("group_stack_depth") if depth > 0 { cli_set_error("unclosed group", 0, "") } } if cli_has_error() { return { "error": state_get("parse_error"), "line": cli_st_get_int("parse_error_line"), "text": state_get("parse_error_text") } } // Build result map directly from state. let n_nodes: Int = cli_st_get_int("node_count") let n_edges: Int = cli_st_get_int("edge_count") let n_groups: Int = cli_st_get_int("group_count") { "title": state_get("graph_title"), "direction": state_get("graph_direction"), "n_nodes": n_nodes, "n_edges": n_edges, "n_groups": n_groups } } // ── XML escape (subset of arbor-render) ───────────────────────────────────── fn cli_esc(s: String) -> String { let r1: String = str_replace(s, "&", "&") let r2: String = str_replace(r1, "<", "<") let r3: String = str_replace(r2, ">", ">") let r4: String = str_replace(r3, "\"", """) r4 } // ── Render a minimal SVG directly from parser state ───────────────────────── // // Layout is simplified: top-down hierarchy, each node sized 120x40 with a // 60-px vertical gap. This is enough for `arbor render` to produce a usable // SVG without wiring in the full layout vessel. The standalone arbor-render // vessel does the real job. // Layout constants. The codegen treats `Ident + Call(...)` as string concat // in the current heuristic, so all int arithmetic is staged through state // (string-keyed K/V) where the operands are integer-valued strings. fn cli_layout_int(a: Int, b: Int) -> Int { state_set("cli_lyt_a", int_to_str(a)) state_set("cli_lyt_b", int_to_str(b)) let av: Int = str_to_int(state_get("cli_lyt_a")) let bv: Int = str_to_int(state_get("cli_lyt_b")) av + bv } fn cli_layout_cw_v() -> Int { 200 } // 120 + 2*40 fn cli_layout_ch_v(n_nodes: Int) -> Int { if n_nodes <= 0 { return 120 } // 2*40 + n_nodes*40 + (n_nodes-1)*60 let part1: Int = 40 * n_nodes let part2: Int = 60 * (n_nodes - 1) let s1: Int = cli_layout_int(80, part1) cli_layout_int(s1, part2) } fn cli_layout_cw_h(n_nodes: Int) -> Int { if n_nodes <= 0 { return 200 } let part1: Int = 120 * n_nodes let part2: Int = 60 * (n_nodes - 1) let s1: Int = cli_layout_int(80, part1) cli_layout_int(s1, part2) } fn cli_layout_ch_h() -> Int { 120 } // Layout: every node 120x40, 60-px gap, 40-px margin. Constants are // hard-coded throughout so the codegen never has to evaluate `Ident + // Ident` arithmetic on integer values. The `cli_layout_int` helper // stages adds through state for the cases where ident-arithmetic is // unavoidable. fn cli_centre_v(i: Int) -> Int { // 40 + 20 + i*(40+60) = 60 + i*100 let ii: Int = 100 * i cli_layout_int(60, ii) } fn cli_centre_h(i: Int) -> Int { // 40 + 60 + i*(120+60) = 100 + i*180 let ii: Int = 180 * i cli_layout_int(100, ii) } fn cli_render_svg() -> String { let n_nodes: Int = cli_st_get_int("node_count") let n_edges: Int = cli_st_get_int("edge_count") let direction: String = state_get("graph_direction") let is_vertical = true if str_eq(direction, "left-right") { let is_vertical = false } if str_eq(direction, "right-left") { let is_vertical = false } let cw = 0 let ch = 0 if is_vertical { let cw = cli_layout_cw_v() let ch = cli_layout_ch_v(n_nodes) } if !is_vertical { let cw = cli_layout_cw_h(n_nodes) let ch = cli_layout_ch_h() } let buf = "\n" let buf = buf + " \n" let buf = buf + " \n" let buf = buf + " \n" let buf = buf + " \n" let buf = buf + " \n" // Pre-compute centre coordinates per node into state. let i = 0 while i < n_nodes { let cx = 100 let cy = 60 if is_vertical { // cx = 100 (40 + 120/2), cy = 60 + i*100 let cy = cli_centre_v(i) } if !is_vertical { let cx = cli_centre_h(i) // cy = 60 (40 + 40/2) } state_set("cli_cx_" + int_to_str(i), int_to_str(cx)) state_set("cli_cy_" + int_to_str(i), int_to_str(cy)) let i = i + 1 } // Edges first (so nodes layer over them). let i = 0 while i < n_edges { let s_idx = int_to_str(i) let from_id: String = state_get("edge_from_" + s_idx) let to_id: String = state_get("edge_to_" + s_idx) let label: String = state_get("edge_label_" + s_idx) let kind: String = state_get("edge_kind_" + s_idx) let from_idx = 0 - 1 let to_idx = 0 - 1 let j = 0 while j < n_nodes { let nid: String = state_get("node_id_" + int_to_str(j)) if str_eq(nid, from_id) { let from_idx = j } if str_eq(nid, to_id) { let to_idx = j } let j = j + 1 } if from_idx >= 0 { if to_idx >= 0 { let fx: Int = str_to_int(state_get("cli_cx_" + int_to_str(from_idx))) let fy: Int = str_to_int(state_get("cli_cy_" + int_to_str(from_idx))) let tx: Int = str_to_int(state_get("cli_cx_" + int_to_str(to_idx))) let ty: Int = str_to_int(state_get("cli_cy_" + int_to_str(to_idx))) let dash = "" if str_eq(kind, "dashed") { let dash = " stroke-dasharray=\"5,3\"" } let stroke = "#64748B" if str_eq(kind, "forbidden") { let stroke = "#DC2626" } let buf = buf + " \n" if str_len(label) > 0 { let sum_x: Int = cli_layout_int(fx, tx) let sum_y: Int = cli_layout_int(fy, ty) let mx: Int = sum_x / 2 let my: Int = sum_y / 2 let buf = buf + " " let buf = buf + cli_esc(label) + "\n" } } } let i = i + 1 } // Nodes. let i = 0 while i < n_nodes { let s_idx = int_to_str(i) let id: String = state_get("node_id_" + s_idx) let label: String = state_get("node_label_" + s_idx) let shape: String = state_get("node_shape_" + s_idx) let cx: Int = str_to_int(state_get("cli_cx_" + s_idx)) let cy: Int = str_to_int(state_get("cli_cy_" + s_idx)) let x: Int = cli_layout_int(cx, 0 - 60) // cx - 120/2 let y: Int = cli_layout_int(cy, 0 - 20) // cy - 40/2 let fill = "#ffffff" let stroke = "#334155" let txt = "#0D0D14" if str_eq(shape, "primary") { let fill = "#0052A0" let stroke = "#0052A0" let txt = "#ffffff" } let rx = 4 if str_eq(shape, "rounded") { let rx = 20 } if str_eq(shape, "stadium") { let rx = 20 } let buf = buf + " \n" let buf = buf + " " let buf = buf + cli_esc(label) + "\n" let i = i + 1 } // Title. let title: String = state_get("graph_title") if str_len(title) > 0 { let tx: Int = cw / 2 let buf = buf + " " let buf = buf + cli_esc(title) + "\n" } let buf = buf + "\n" buf } // ── Subcommands ───────────────────────────────────────────────────────────── fn cmd_version() -> Int { println("Arbor v0.1.0") 0 } fn cmd_usage() -> Int { println("Arbor — brain-themed diagram language") println("") println("USAGE:") println(" arbor render [-o ]") println(" arbor check ") println(" arbor ls ") println(" arbor fmt [--in-place]") println(" arbor version") println("") println("Note: PNG output is unavailable in the El build (use the Rust binary).") 0 } fn cmd_check(path: String) -> Int { let src: String = fs_read(path) let g: Map = cli_parse(src) let err: String = g["error"] if str_len(err) > 0 { let line: Int = g["line"] println("Parse error at line " + int_to_str(line) + ": " + err) return 1 } let nn: Int = g["n_nodes"] let ne: Int = g["n_edges"] let ng: Int = g["n_groups"] println("OK — " + int_to_str(nn) + " nodes, " + int_to_str(ne) + " edges, " + int_to_str(ng) + " groups") 0 } fn cmd_ls(path: String) -> Int { let src: String = fs_read(path) let g: Map = cli_parse(src) let err: String = g["error"] if str_len(err) > 0 { println("Parse error: " + err) return 1 } let nn: Int = g["n_nodes"] println("Nodes (" + int_to_str(nn) + "):") let i = 0 while i < nn { let s_idx = int_to_str(i) let id: String = state_get("node_id_" + s_idx) let label: String = state_get("node_label_" + s_idx) let shape: String = state_get("node_shape_" + s_idx) println(" " + id + " \"" + label + "\" [" + shape + "]") let i = i + 1 } let ne: Int = g["n_edges"] println("") println("Edges (" + int_to_str(ne) + "):") let i = 0 while i < ne { let s_idx = int_to_str(i) let from_id: String = state_get("edge_from_" + s_idx) let to_id: String = state_get("edge_to_" + s_idx) let label: String = state_get("edge_label_" + s_idx) let kind: String = state_get("edge_kind_" + s_idx) let arrow = "->" if str_eq(kind, "dashed") { let arrow = "-->" } if str_eq(kind, "forbidden") { let arrow = "-/->" } if str_eq(kind, "bidirectional") { let arrow = "<->" } if str_len(label) > 0 { println(" " + from_id + " " + arrow + " " + to_id + " \"" + label + "\"") } else { println(" " + from_id + " " + arrow + " " + to_id) } let i = i + 1 } let ng: Int = g["n_groups"] println("") println("Groups (" + int_to_str(ng) + "):") let i = 0 while i < ng { let s_idx = int_to_str(i) let id: String = state_get("group_id_" + s_idx) let label: String = state_get("group_label_" + s_idx) println(" " + id + " \"" + label + "\"") let i = i + 1 } 0 } fn cmd_render(path: String, out_path: String) -> Int { let src: String = fs_read(path) let g: Map = cli_parse(src) let err: String = g["error"] if str_len(err) > 0 { println("Parse error: " + err) return 1 } let svg: String = cli_render_svg() let final_out = out_path if str_eq(final_out, "") { // Replace extension or append .svg. let dot: Int = str_index_of(path, ".") if dot < 0 { let final_out = path + ".svg" } else { let final_out = str_slice(path, 0, dot) + ".svg" } } fs_write(final_out, svg) println("Wrote " + int_to_str(str_len(svg)) + " bytes -> " + final_out) 0 } fn cmd_fmt(path: String, in_place: Bool) -> Int { let src: String = fs_read(path) let g: Map = cli_parse(src) let err: String = g["error"] if str_len(err) > 0 { println("Parse error: " + err) return 1 } let title: String = state_get("graph_title") let direction: String = state_get("graph_direction") let buf = "" if str_len(title) > 0 { let buf = buf + "title: \"" + title + "\"\n" } let buf = buf + "direction: " + direction + "\n" // Standalone nodes (those not in any group). let nn: Int = g["n_nodes"] let ng: Int = g["n_groups"] // Build a "in-group" set as a single delimited string. let grouped_set = "" let i = 0 while i < ng { let s_idx = int_to_str(i) let ids: String = state_get("group_ids_" + s_idx) let grouped_set = grouped_set + ids + "" let i = i + 1 } // Groups let i = 0 while i < ng { let s_idx = int_to_str(i) let gid: String = state_get("group_id_" + s_idx) let glabel: String = state_get("group_label_" + s_idx) let gids: String = state_get("group_ids_" + s_idx) let buf = buf + "\ngroup " + gid + " \"" + glabel + "\" {\n" let id_list: [String] = native_list_empty() if !str_eq(gids, "") { let id_list = str_split(gids, "") } let m: Int = el_list_len(id_list) let j = 0 while j < m { let want_id: String = get(id_list, j) // Find that node and emit it indented. let k = 0 while k < nn { let nid: String = state_get("node_id_" + int_to_str(k)) if str_eq(nid, want_id) { let lbl: String = state_get("node_label_" + int_to_str(k)) let shp: String = state_get("node_shape_" + int_to_str(k)) let shape_part = "" if !str_eq(shp, "rect") { let shape_part = " [" + shp + "]" } let label_part = "" if !str_eq(lbl, nid) { let label_part = " \"" + lbl + "\"" } let buf = buf + " " + nid + shape_part + label_part + "\n" } let k = k + 1 } let j = j + 1 } let buf = buf + "}\n" let i = i + 1 } // Standalones. let standalones = "" let i = 0 while i < nn { let s_idx = int_to_str(i) let nid: String = state_get("node_id_" + s_idx) let lookfor = "" + nid + "" if !str_contains(grouped_set, lookfor) { let lbl: String = state_get("node_label_" + s_idx) let shp: String = state_get("node_shape_" + s_idx) let shape_part = "" if !str_eq(shp, "rect") { let shape_part = " [" + shp + "]" } let label_part = "" if !str_eq(lbl, nid) { let label_part = " \"" + lbl + "\"" } let standalones = standalones + nid + shape_part + label_part + "\n" } let i = i + 1 } if str_len(standalones) > 0 { let buf = buf + "\n" + standalones } // Edges. let ne: Int = g["n_edges"] if ne > 0 { let buf = buf + "\n" let i = 0 while i < ne { let s_idx = int_to_str(i) let from_id: String = state_get("edge_from_" + s_idx) let to_id: String = state_get("edge_to_" + s_idx) let label: String = state_get("edge_label_" + s_idx) let kind: String = state_get("edge_kind_" + s_idx) let arrow = "->" if str_eq(kind, "dashed") { let arrow = "-->" } if str_eq(kind, "forbidden") { let arrow = "-/->" } if str_eq(kind, "bidirectional") { let arrow = "<->" } let label_part = "" if str_len(label) > 0 { let label_part = " \"" + label + "\"" } let buf = buf + from_id + " " + arrow + " " + to_id + label_part + "\n" let i = i + 1 } } if in_place { fs_write(path, buf) println("Formatted " + path) } else { println(buf) } 0 } // ── Self-test ─────────────────────────────────────────────────────────────── fn smoke_fail(label: String, msg: String) -> Int { println("FAIL " + label + ": " + msg) state_set("smoke_failures", "1") 0 } fn run_smoke_tests() -> Int { let src = "title: \"Test\"\ndirection: left-right\napi [rounded] \"REST API\"\ndb [cylinder] \"Postgres\"\napi -> db \"reads\"" let g: Map = cli_parse(src) let err: String = g["error"] if str_len(err) > 0 { smoke_fail("parse smoke", err) } else { let nn: Int = g["n_nodes"] let ne: Int = g["n_edges"] if nn < 2 { smoke_fail("smoke nn", "expected >=2 got " + int_to_str(nn)) } else { println("ok parse 2 nodes") } if ne < 1 { smoke_fail("smoke ne", "expected >=1 got " + int_to_str(ne)) } else { println("ok parse 1 edge") } let svg: String = cli_render_svg() if !str_contains(svg, "") { smoke_fail("svg tail", "missing closing tag") } else { println("ok svg tail") } } let f: String = state_get("smoke_failures") if str_eq(f, "1") { println("arbor-cli: FAILED") return 1 } println("arbor-cli: ok") 0 } // ── Entry ─────────────────────────────────────────────────────────────────── let cli_args: [String] = args() let cli_argc: Int = el_list_len(cli_args) if cli_argc == 0 { cmd_usage() let rc: Int = run_smoke_tests() exit_program(rc) } else { let cmd: String = get(cli_args, 0) if str_eq(cmd, "version") { cmd_version() exit_program(0) } if str_eq(cmd, "--version") { cmd_version() exit_program(0) } if str_eq(cmd, "-V") { cmd_version() exit_program(0) } if str_eq(cmd, "help") { cmd_usage() exit_program(0) } if str_eq(cmd, "--help") { cmd_usage() exit_program(0) } if str_eq(cmd, "-h") { cmd_usage() exit_program(0) } if cli_argc < 2 { println("arbor: missing input file") cmd_usage() exit_program(1) } let path: String = get(cli_args, 1) if str_eq(cmd, "check") { exit_program(cmd_check(path)) } if str_eq(cmd, "ls") { exit_program(cmd_ls(path)) } if str_eq(cmd, "render") { let out_path = "" let i = 2 while i < cli_argc { let opt: String = get(cli_args, i) if str_eq(opt, "-o") { if i + 1 < cli_argc { let out_path = get(cli_args, i + 1) let i = i + 1 } } if str_eq(opt, "--png") { println("arbor: PNG not supported by El build — falling back to SVG") } let i = i + 1 } exit_program(cmd_render(path, out_path)) } if str_eq(cmd, "fmt") { let in_place = false let i = 2 while i < cli_argc { let opt: String = get(cli_args, i) if str_eq(opt, "--in-place") { let in_place = true } let i = i + 1 } exit_program(cmd_fmt(path, in_place)) } println("arbor: unknown command `" + cmd + "`") cmd_usage() exit_program(1) }