feat: port arbor, dharma, forge El source into monorepo
Brings the remaining foundation repos that were not included in the original monorepo consolidation: - arbor/vessels/ — 6 vessels (arbor-cli, arbor-core, arbor-diagram, arbor-layout, arbor-parse, arbor-render) with manifests + src/main.el - dharma/ — CGI Provenance Registry package (flat layout, 14 .el files across registry/, sandbox/, training/, validation/, tests/) - forge/ — consciousness channel tool (8 src .el files + new manifest.el) - elp/src/ — 36 test fixture files not carried over in original merge (dedup_*, realizer_*, semantics_*, morph_*, ext_*, one_extern_* helpers) el-ide, engram, elql are already complete in ide/, engram/, ql/.
This commit is contained in:
@@ -0,0 +1,591 @@
|
||||
// arbor-layout — hierarchical layout for diagram graphs.
|
||||
//
|
||||
// Public entry point:
|
||||
// fn arbor_layout(graph: Map<String, Any>) -> Map<String, Any>
|
||||
//
|
||||
// The graph is the lowered (diagram-form) shape. The result map has:
|
||||
// "node_pos_<id>" → { "x":Float, "y":Float } centre point
|
||||
// "node_size_<id>" → { "w":Float, "h":Float }
|
||||
// "group_bounds_<id>" → { "x":Float, "y":Float, "w":Float, "h":Float }
|
||||
// "node_ids" → [String] iteration order
|
||||
// "group_ids" → [String] iteration order
|
||||
// "canvas" → { "w":Float, "h":Float }
|
||||
//
|
||||
// Floats are El-encoded — store via the runtime's bit-cast convention.
|
||||
// All arithmetic on positions/sizes is done in Float; integers (rank index)
|
||||
// stay as Int.
|
||||
//
|
||||
// Algorithm (simplified Sugiyama):
|
||||
// 1. Assign ranks via topological propagation (longest path from sources).
|
||||
// 2. Group nodes by rank, preserving declaration order.
|
||||
// 3. Position each rank as a row (top-down/bottom-up) or column (LR/RL).
|
||||
// 4. Compute group bounding boxes from member positions.
|
||||
// 5. Compute canvas size to enclose everything.
|
||||
//
|
||||
// The current implementation is the same simplified Sugiyama as the Rust
|
||||
// version; perfectly identical numerical output is not promised but the
|
||||
// relative ordering and bounding-box semantics match.
|
||||
|
||||
// ── Spacing constants (declared as float-bit-cast helpers) ──────────────────
|
||||
|
||||
fn k_node_base_w() -> el_val_t { int_to_float(120) }
|
||||
fn k_node_base_h() -> el_val_t { int_to_float(40) }
|
||||
fn k_node_char_extra() -> el_val_t { int_to_float(8) }
|
||||
fn k_h_gap() -> el_val_t { int_to_float(60) }
|
||||
fn k_v_gap() -> el_val_t { int_to_float(80) }
|
||||
fn k_group_pad() -> el_val_t { int_to_float(20) }
|
||||
fn k_margin() -> el_val_t { int_to_float(40) }
|
||||
|
||||
// Float-aware max/min via int_to_float / float arithmetic — but el_max
|
||||
// works in raw int comparison space, so we bit-cast carefully.
|
||||
// For our purposes we only need monotonic comparisons on positive values,
|
||||
// which IEEE 754 doubles + sign-magnitude bit patterns happen to preserve
|
||||
// for non-negative floats — but it's safer to do the comparison via the
|
||||
// math layer. We use a helper that decodes both, picks the bigger, and
|
||||
// re-encodes.
|
||||
//
|
||||
// Implemented in C terms: math_max(a, b) — but el_runtime doesn't expose
|
||||
// a float-aware max, so we synthesise one.
|
||||
|
||||
fn fmax(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
// Compare via float subtraction's sign: a - b. Float subtraction is the
|
||||
// multiply chain implemented via the C code generator. But el's `-` on
|
||||
// bit-cast doubles doesn't perform IEEE arithmetic — it's a 64-bit int
|
||||
// subtract. Workaround: round-trip through format_float and str_to_float.
|
||||
// For our layout numbers (small non-negative integers stored as floats)
|
||||
// we can compare via the raw bits: a positive float's bit pattern is
|
||||
// monotonically ordered, so `a > b` on the int reinterpretation gives
|
||||
// the same result as on the actual double for non-negative values.
|
||||
if a > b { return a }
|
||||
b
|
||||
}
|
||||
|
||||
fn fadd(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
// a, b are bit-cast doubles. Safe addition: int-to-float, format, parse.
|
||||
// For the small positive integers we work with, we reconstruct the
|
||||
// numeric value via format_float → str_to_float, perform addition by
|
||||
// pulling them through str representations. Costly but correct on the
|
||||
// current runtime. Fast path: if both are exact ints stored as floats
|
||||
// we can also keep an Int "shadow" — but the simpler approach is to
|
||||
// route through the printf-based formatter once per layout pass.
|
||||
let as: String = format_float(a, 6)
|
||||
let bs: String = format_float(b, 6)
|
||||
// Parse back to numeric.
|
||||
let af: el_val_t = str_to_float(as)
|
||||
let bf: el_val_t = str_to_float(bs)
|
||||
// No real-add primitive; build the sum from int parts where possible.
|
||||
// Convert to int at full resolution: float_to_int truncates towards zero,
|
||||
// which for our values (always integer-valued) is exact.
|
||||
let ai: Int = float_to_int(af)
|
||||
let bi: Int = float_to_int(bf)
|
||||
int_to_float(ai + bi)
|
||||
}
|
||||
|
||||
fn fsub(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai - bi)
|
||||
}
|
||||
|
||||
fn fmul(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai * bi)
|
||||
}
|
||||
|
||||
fn fdiv2(a: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
int_to_float(ai / 2)
|
||||
}
|
||||
|
||||
// ── Node size based on label width ──────────────────────────────────────────
|
||||
|
||||
fn node_size_for(label: String) -> Map<String, Any> {
|
||||
let len: Int = str_len(label)
|
||||
let extra: Int = 0
|
||||
if len > 10 {
|
||||
let extra = len - 10
|
||||
}
|
||||
let w_int: Int = 120 + 8 * extra
|
||||
let w: el_val_t = int_to_float(w_int)
|
||||
let h: el_val_t = int_to_float(40)
|
||||
{ "w": w, "h": h }
|
||||
}
|
||||
|
||||
// ── Adjacency-list construction ─────────────────────────────────────────────
|
||||
//
|
||||
// Builds successor and in-degree maps keyed by node id.
|
||||
|
||||
fn build_succ_indeg(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let edges: [Map<String, Any>] = graph["edges"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let m: Int = el_list_len(edges)
|
||||
|
||||
let succ: Map<String, Any> = el_map_new(0)
|
||||
let indeg: Map<String, Any> = el_map_new(0)
|
||||
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let empty: [String] = el_list_empty()
|
||||
let succ = el_map_set(succ, nid, empty)
|
||||
let indeg = el_map_set(indeg, nid, 0)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let i = 0
|
||||
while i < m {
|
||||
let e: Map<String, Any> = get(edges, i)
|
||||
let src: String = e["from"]
|
||||
let dst: String = e["to"]
|
||||
let cur_succ: [String] = el_map_get(succ, src)
|
||||
let new_succ: [String] = native_list_append(cur_succ, dst)
|
||||
let succ = el_map_set(succ, src, new_succ)
|
||||
let prev: Int = el_map_get(indeg, dst)
|
||||
let indeg = el_map_set(indeg, dst, prev + 1)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
{ "succ": succ, "indeg": indeg }
|
||||
}
|
||||
|
||||
// ── Topological rank assignment ─────────────────────────────────────────────
|
||||
//
|
||||
// Returns a map: node_id → rank.
|
||||
|
||||
fn assign_ranks(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let adj: Map<String, Any> = build_succ_indeg(graph)
|
||||
let succ: Map<String, Any> = adj["succ"]
|
||||
let indeg: Map<String, Any> = adj["indeg"]
|
||||
|
||||
let ranks: Map<String, Any> = el_map_new(0)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let ranks = el_map_set(ranks, nid, 0)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Initialise queue with all nodes whose in-degree is 0 (in declaration
|
||||
// order, mirroring the Rust implementation's ordering guarantee).
|
||||
let queue: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let d: Int = el_map_get(indeg, nid)
|
||||
if d == 0 {
|
||||
let queue = native_list_append(queue, nid)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let head = 0
|
||||
let running = true
|
||||
while running {
|
||||
if head >= el_list_len(queue) {
|
||||
let running = false
|
||||
} else {
|
||||
let cur: String = get(queue, head)
|
||||
let head = head + 1
|
||||
let cur_rank: Int = el_map_get(ranks, cur)
|
||||
let neighbours: [String] = el_map_get(succ, cur)
|
||||
let nn: Int = el_list_len(neighbours)
|
||||
let j = 0
|
||||
while j < nn {
|
||||
let nb: String = get(neighbours, j)
|
||||
let nb_rank: Int = el_map_get(ranks, nb)
|
||||
let cand: Int = cur_rank + 1
|
||||
if cand > nb_rank {
|
||||
let ranks = el_map_set(ranks, nb, cand)
|
||||
}
|
||||
let cur_d: Int = el_map_get(indeg, nb)
|
||||
let new_d: Int = cur_d - 1
|
||||
let indeg = el_map_set(indeg, nb, new_d)
|
||||
if new_d <= 0 {
|
||||
let queue = native_list_append(queue, nb)
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
ranks
|
||||
}
|
||||
|
||||
// ── Layout pass ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn arbor_layout(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let direction: String = graph["direction"]
|
||||
|
||||
let result: Map<String, Any> = el_map_new(0)
|
||||
let result = el_map_set(result, "node_ids", el_list_empty())
|
||||
let result = el_map_set(result, "group_ids", el_list_empty())
|
||||
|
||||
if n == 0 {
|
||||
let canvas: Map<String, Any> = { "w": int_to_float(200), "h": int_to_float(100) }
|
||||
let result = el_map_set(result, "canvas", canvas)
|
||||
return result
|
||||
}
|
||||
|
||||
let ranks: Map<String, Any> = assign_ranks(graph)
|
||||
let max_rank = 0
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let r: Int = el_map_get(ranks, nid)
|
||||
if r > max_rank { let max_rank = r }
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Group nodes by rank, preserving declaration order. Buckets are stored
|
||||
// in process state so we can iterate without nested-list mutation.
|
||||
let i = 0
|
||||
while i <= max_rank {
|
||||
state_set("rank_bucket_" + int_to_str(i), "")
|
||||
let i = i + 1
|
||||
}
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let r: Int = el_map_get(ranks, nid)
|
||||
let key = "rank_bucket_" + int_to_str(r)
|
||||
let prev: String = state_get(key)
|
||||
if str_eq(prev, "") {
|
||||
state_set(key, nid)
|
||||
} else {
|
||||
state_set(key, prev + "" + nid)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Pre-compute sizes and stash a label-keyed cache.
|
||||
let id_list: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let lbl: String = nd["label"]
|
||||
let sz: Map<String, Any> = node_size_for(lbl)
|
||||
let result = el_map_set(result, "node_size_" + nid, sz)
|
||||
let id_list = native_list_append(id_list, nid)
|
||||
let i = i + 1
|
||||
}
|
||||
let result = el_map_set(result, "node_ids", id_list)
|
||||
|
||||
// Position pass.
|
||||
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 cursor: el_val_t = k_margin()
|
||||
|
||||
let r = 0
|
||||
while r <= max_rank {
|
||||
let bucket_str: String = state_get("rank_bucket_" + int_to_str(r))
|
||||
if !str_eq(bucket_str, "") {
|
||||
let ids: [String] = str_split(bucket_str, "")
|
||||
let ids_n: Int = el_list_len(ids)
|
||||
|
||||
// Track row height (for vertical) or column width (for horizontal).
|
||||
let cross_max: el_val_t = int_to_float(40)
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
if is_vertical {
|
||||
let h: el_val_t = sz["h"]
|
||||
let cross_max = fmax(cross_max, h)
|
||||
} else {
|
||||
let w: el_val_t = sz["w"]
|
||||
let cross_max = fmax(cross_max, w)
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
|
||||
if is_vertical {
|
||||
let row_h: el_val_t = cross_max
|
||||
let y_center: el_val_t = fadd(cursor, fdiv2(row_h))
|
||||
let x_cursor: el_val_t = k_margin()
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let w: el_val_t = sz["w"]
|
||||
let cx: el_val_t = fadd(x_cursor, fdiv2(w))
|
||||
let pos: Map<String, Any> = { "x": cx, "y": y_center }
|
||||
let result = el_map_set(result, "node_pos_" + nid, pos)
|
||||
let x_cursor = fadd(fadd(x_cursor, w), k_h_gap())
|
||||
let j = j + 1
|
||||
}
|
||||
let cursor = fadd(fadd(cursor, row_h), k_v_gap())
|
||||
} else {
|
||||
let col_w: el_val_t = cross_max
|
||||
let x_center: el_val_t = fadd(cursor, fdiv2(col_w))
|
||||
let y_cursor: el_val_t = k_margin()
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let h: el_val_t = sz["h"]
|
||||
let cy: el_val_t = fadd(y_cursor, fdiv2(h))
|
||||
let pos: Map<String, Any> = { "x": x_center, "y": cy }
|
||||
let result = el_map_set(result, "node_pos_" + nid, pos)
|
||||
let y_cursor = fadd(fadd(y_cursor, h), k_v_gap())
|
||||
let j = j + 1
|
||||
}
|
||||
let cursor = fadd(fadd(cursor, col_w), k_h_gap())
|
||||
}
|
||||
} else {
|
||||
// Empty bucket — advance cursor by a default node size.
|
||||
if is_vertical {
|
||||
let cursor = fadd(cursor, fadd(int_to_float(40), k_v_gap()))
|
||||
} else {
|
||||
let cursor = fadd(cursor, fadd(k_node_base_w(), k_h_gap()))
|
||||
}
|
||||
}
|
||||
let r = r + 1
|
||||
}
|
||||
|
||||
// Direction inversions for BU / RL.
|
||||
let need_flip_y = false
|
||||
let need_flip_x = false
|
||||
if str_eq(direction, "bottom-up") { let need_flip_y = true }
|
||||
if str_eq(direction, "right-left") { let need_flip_x = true }
|
||||
|
||||
if need_flip_y {
|
||||
let max_y: el_val_t = fadd(fsub(cursor, k_v_gap()), k_margin())
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let y: el_val_t = pos["y"]
|
||||
let new_y: el_val_t = fadd(fsub(max_y, y), k_margin())
|
||||
let new_pos: Map<String, Any> = { "x": pos["x"], "y": new_y }
|
||||
let result = el_map_set(result, "node_pos_" + nid, new_pos)
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
if need_flip_x {
|
||||
let max_x: el_val_t = fadd(fsub(cursor, k_h_gap()), k_margin())
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let x: el_val_t = pos["x"]
|
||||
let new_x: el_val_t = fadd(fsub(max_x, x), k_margin())
|
||||
let new_pos: Map<String, Any> = { "x": new_x, "y": pos["y"] }
|
||||
let result = el_map_set(result, "node_pos_" + nid, new_pos)
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Group bounds.
|
||||
let groups: [Map<String, Any>] = graph["groups"]
|
||||
let gn: Int = el_list_len(groups)
|
||||
let gid_list: [String] = el_list_empty()
|
||||
let g = 0
|
||||
while g < gn {
|
||||
let grp: Map<String, Any> = get(groups, g)
|
||||
let gid: String = grp["id"]
|
||||
let member_ids: [String] = grp["node_ids"]
|
||||
let mn: Int = el_list_len(member_ids)
|
||||
if mn > 0 {
|
||||
let big: Int = 1000000000
|
||||
let neg: Int = 0 - 1000000000
|
||||
let min_x: el_val_t = int_to_float(big)
|
||||
let min_y: el_val_t = int_to_float(big)
|
||||
let max_x: el_val_t = int_to_float(neg)
|
||||
let max_y: el_val_t = int_to_float(neg)
|
||||
let mi = 0
|
||||
while mi < mn {
|
||||
let mid: String = get(member_ids, mi)
|
||||
let mpos: Map<String, Any> = el_map_get(result, "node_pos_" + mid)
|
||||
let msz: Map<String, Any> = el_map_get(result, "node_size_" + mid)
|
||||
let mid_present: String = mpos["x"]
|
||||
if str_len(mid_present) >= 0 {
|
||||
let cx: el_val_t = mpos["x"]
|
||||
let cy: el_val_t = mpos["y"]
|
||||
let mw: el_val_t = msz["w"]
|
||||
let mh: el_val_t = msz["h"]
|
||||
let left: el_val_t = fsub(cx, fdiv2(mw))
|
||||
let right: el_val_t = fadd(cx, fdiv2(mw))
|
||||
let top: el_val_t = fsub(cy, fdiv2(mh))
|
||||
let bot: el_val_t = fadd(cy, fdiv2(mh))
|
||||
if left < min_x { let min_x = left }
|
||||
if top < min_y { let min_y = top }
|
||||
if right > max_x { let max_x = right }
|
||||
if bot > max_y { let max_y = bot }
|
||||
}
|
||||
let mi = mi + 1
|
||||
}
|
||||
let bx: el_val_t = fsub(min_x, k_group_pad())
|
||||
let by: el_val_t = fsub(min_y, k_group_pad())
|
||||
let bw: el_val_t = fadd(fsub(max_x, min_x), fmul(k_group_pad(), int_to_float(2)))
|
||||
let bh: el_val_t = fadd(fsub(max_y, min_y), fmul(k_group_pad(), int_to_float(2)))
|
||||
let bounds: Map<String, Any> = { "x": bx, "y": by, "w": bw, "h": bh }
|
||||
let result = el_map_set(result, "group_bounds_" + gid, bounds)
|
||||
let gid_list = native_list_append(gid_list, gid)
|
||||
}
|
||||
let g = g + 1
|
||||
}
|
||||
let result = el_map_set(result, "group_ids", gid_list)
|
||||
|
||||
// Canvas size = max node-right / node-bottom + group-right / group-bottom.
|
||||
let canvas_w: el_val_t = int_to_float(0)
|
||||
let canvas_h: el_val_t = int_to_float(0)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let right: el_val_t = fadd(pos["x"], fdiv2(sz["w"]))
|
||||
let bottom: el_val_t = fadd(pos["y"], fdiv2(sz["h"]))
|
||||
if right > canvas_w { let canvas_w = right }
|
||||
if bottom > canvas_h { let canvas_h = bottom }
|
||||
let i = i + 1
|
||||
}
|
||||
let i = 0
|
||||
while i < el_list_len(gid_list) {
|
||||
let gid: String = get(gid_list, i)
|
||||
let b: Map<String, Any> = el_map_get(result, "group_bounds_" + gid)
|
||||
let r: el_val_t = fadd(b["x"], b["w"])
|
||||
let bt: el_val_t = fadd(b["y"], b["h"])
|
||||
if r > canvas_w { let canvas_w = r }
|
||||
if bt > canvas_h { let canvas_h = bt }
|
||||
let i = i + 1
|
||||
}
|
||||
let canvas: Map<String, Any> = {
|
||||
"w": fadd(canvas_w, k_margin()),
|
||||
"h": fadd(canvas_h, k_margin())
|
||||
}
|
||||
let result = el_map_set(result, "canvas", canvas)
|
||||
result
|
||||
}
|
||||
|
||||
// ── Smoke test ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn fl_to_str(v: el_val_t) -> String {
|
||||
int_to_str(float_to_int(v))
|
||||
}
|
||||
|
||||
fn smoke_fail(label: String, msg: String) -> Int {
|
||||
println("FAIL " + label + ": " + msg)
|
||||
state_set("smoke_failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn make_test_node(id: String, label: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": id, "label": label, "sublabel": "",
|
||||
"shape": "rectangle",
|
||||
"style_fill": "", "style_stroke": "", "style_color": ""
|
||||
}
|
||||
}
|
||||
|
||||
fn make_test_edge(src: String, dst: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": "", "line": "solid", "arrow": "forward" }
|
||||
}
|
||||
|
||||
fn make_test_graph(direction: String, ids: [String], src_dst: [String]) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < el_list_len(ids) {
|
||||
let nid: String = get(ids, i)
|
||||
let nodes = native_list_append(nodes, make_test_node(nid, nid))
|
||||
let i = i + 1
|
||||
}
|
||||
let edges: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i + 1 < el_list_len(src_dst) {
|
||||
let s: String = get(src_dst, i)
|
||||
let d: String = get(src_dst, i + 1)
|
||||
let edges = native_list_append(edges, make_test_edge(s, d))
|
||||
let i = i + 2
|
||||
}
|
||||
{
|
||||
"title": "T", "direction": direction,
|
||||
"nodes": nodes, "edges": edges, "groups": el_list_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Empty graph.
|
||||
let g_empty: Map<String, Any> = {
|
||||
"title": "e", "direction": "top-down",
|
||||
"nodes": el_list_empty(), "edges": el_list_empty(), "groups": el_list_empty()
|
||||
}
|
||||
let r_empty: Map<String, Any> = arbor_layout(g_empty)
|
||||
let canvas_empty: Map<String, Any> = r_empty["canvas"]
|
||||
println("empty canvas w=" + fl_to_str(canvas_empty["w"]))
|
||||
|
||||
// Single node.
|
||||
let g_one: Map<String, Any> = make_test_graph("top-down",
|
||||
["solo"], el_list_empty())
|
||||
let r_one: Map<String, Any> = arbor_layout(g_one)
|
||||
let pos_solo: Map<String, Any> = el_map_get(r_one, "node_pos_solo")
|
||||
let x_solo: el_val_t = pos_solo["x"]
|
||||
let y_solo: el_val_t = pos_solo["y"]
|
||||
println("solo at x=" + fl_to_str(x_solo) + " y=" + fl_to_str(y_solo))
|
||||
if float_to_int(x_solo) <= 0 { smoke_fail("solo x", "expected > 0") }
|
||||
if float_to_int(y_solo) <= 0 { smoke_fail("solo y", "expected > 0") }
|
||||
|
||||
// Linear chain a→b→c top-down: ya < yb < yc.
|
||||
let g_chain: Map<String, Any> = make_test_graph("top-down",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_chain: Map<String, Any> = arbor_layout(g_chain)
|
||||
let pa: Map<String, Any> = el_map_get(r_chain, "node_pos_a")
|
||||
let pb: Map<String, Any> = el_map_get(r_chain, "node_pos_b")
|
||||
let pc: Map<String, Any> = el_map_get(r_chain, "node_pos_c")
|
||||
let ya: el_val_t = pa["y"]
|
||||
let yb: el_val_t = pb["y"]
|
||||
let yc: el_val_t = pc["y"]
|
||||
println("td a.y=" + fl_to_str(ya) + " b.y=" + fl_to_str(yb) + " c.y=" + fl_to_str(yc))
|
||||
if float_to_int(ya) >= float_to_int(yb) { smoke_fail("td order", "a.y >= b.y") }
|
||||
if float_to_int(yb) >= float_to_int(yc) { smoke_fail("td order", "b.y >= c.y") }
|
||||
|
||||
// LR direction
|
||||
let g_lr: Map<String, Any> = make_test_graph("left-right",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_lr: Map<String, Any> = arbor_layout(g_lr)
|
||||
let pa2: Map<String, Any> = el_map_get(r_lr, "node_pos_a")
|
||||
let pc2: Map<String, Any> = el_map_get(r_lr, "node_pos_c")
|
||||
let xa: el_val_t = pa2["x"]
|
||||
let xc: el_val_t = pc2["x"]
|
||||
println("lr a.x=" + fl_to_str(xa) + " c.x=" + fl_to_str(xc))
|
||||
if float_to_int(xa) >= float_to_int(xc) { smoke_fail("lr order", "a.x >= c.x") }
|
||||
|
||||
// Bottom-up: a is below c.
|
||||
let g_bu: Map<String, Any> = make_test_graph("bottom-up",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_bu: Map<String, Any> = arbor_layout(g_bu)
|
||||
let pa3: Map<String, Any> = el_map_get(r_bu, "node_pos_a")
|
||||
let pc3: Map<String, Any> = el_map_get(r_bu, "node_pos_c")
|
||||
let ya3: el_val_t = pa3["y"]
|
||||
let yc3: el_val_t = pc3["y"]
|
||||
println("bu a.y=" + fl_to_str(ya3) + " c.y=" + fl_to_str(yc3))
|
||||
if float_to_int(ya3) <= float_to_int(yc3) { smoke_fail("bu order", "a.y <= c.y") }
|
||||
|
||||
// Canvas covers all nodes.
|
||||
let canvas_chain: Map<String, Any> = r_chain["canvas"]
|
||||
let cw: el_val_t = canvas_chain["w"]
|
||||
let ch: el_val_t = canvas_chain["h"]
|
||||
println("chain canvas w=" + fl_to_str(cw) + " h=" + fl_to_str(ch))
|
||||
if float_to_int(cw) <= 0 { smoke_fail("canvas w", "non-positive") }
|
||||
if float_to_int(ch) <= 0 { smoke_fail("canvas h", "non-positive") }
|
||||
|
||||
println("")
|
||||
let f: String = state_get("smoke_failures")
|
||||
if str_eq(f, "1") {
|
||||
println("arbor-layout: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-layout: ok")
|
||||
}
|
||||
Reference in New Issue
Block a user