use sim_kernel::{CodecId, Cx, Expr, Result};
use sim_lib_scene::{node, sym};
use crate::contract::View;
pub struct UniversalView;
impl View for UniversalView {
fn encode(&self, _cx: &mut Cx, value: &Expr) -> Result<Expr> {
Ok(node(
"stack",
vec![
("id", sym("universal")),
("dir", sym("column")),
(
"children",
Expr::List(vec![
summary_card(value),
structure_tree(value),
canonical_text(value),
operations_inspector(value),
]),
),
],
))
}
}
fn text_line(text: String) -> Expr {
node("text", vec![("text", Expr::String(text))])
}
fn badge(status: &str, label: &str) -> Expr {
node(
"badge",
vec![
("status", sym(status)),
("label", Expr::String(label.to_owned())),
],
)
}
fn summary_card(value: &Expr) -> Expr {
let roundtrip = roundtrip_badge(value);
node(
"box",
vec![
("role", sym("summary")),
(
"children",
Expr::List(vec![
text_line(format!("kind: {}", expr_kind(value))),
text_line(format!("label: {}", short_label(value))),
roundtrip,
]),
),
],
)
}
fn structure_tree(value: &Expr) -> Expr {
node(
"box",
vec![
("role", sym("structure")),
("children", Expr::List(vec![tree_of("value", value)])),
],
)
}
fn tree_of(label: &str, value: &Expr) -> Expr {
match value {
Expr::Map(entries) => node(
"tree",
vec![
("label", Expr::String(label.to_owned())),
(
"nodes",
Expr::List(
entries
.iter()
.map(|(key, child)| tree_of(&render_value(key), child))
.collect(),
),
),
],
),
Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => node(
"tree",
vec![
("label", Expr::String(format!("{label} [{}]", items.len()))),
(
"nodes",
Expr::List(
items
.iter()
.enumerate()
.map(|(index, child)| tree_of(&format!("[{index}]"), child))
.collect(),
),
),
],
),
atom => text_line(format!("{label}: {}", render_value(atom))),
}
}
fn canonical_text(value: &Expr) -> Expr {
let mut children = vec![text_line(render_value(value))];
match value {
Expr::Map(entries) => {
for (key, child) in entries {
if is_scalar(child) {
children.push(editable_leaf(value, key_path(key), child));
}
}
}
Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
for (index, item) in items.iter().enumerate() {
if is_scalar(item) {
children.push(editable_leaf(value, index_path(index), item));
}
}
}
scalar => {
children.push(editable_leaf(scalar, Expr::List(Vec::new()), scalar));
}
}
node(
"box",
vec![
("role", sym("canonical-text")),
("children", Expr::List(children)),
],
)
}
fn is_scalar(value: &Expr) -> bool {
!matches!(
value,
Expr::Map(_) | Expr::List(_) | Expr::Vector(_) | Expr::Set(_)
)
}
fn key_path(key: &Expr) -> Expr {
Expr::List(vec![Expr::Vector(vec![sym("k"), key.clone()])])
}
fn index_path(index: usize) -> Expr {
Expr::List(vec![Expr::Vector(vec![
sym("i"),
Expr::String(index.to_string()),
])])
}
fn editable_leaf(root: &Expr, path: Expr, leaf: &Expr) -> Expr {
node(
"field",
vec![
("kind", sym("text")),
("value", Expr::String(render_value(leaf))),
("target", root.clone()),
("path", path),
("readonly", Expr::Bool(false)),
],
)
}
fn operations_inspector(value: &Expr) -> Expr {
node(
"stack",
vec![
("role", sym("operations")),
("dir", sym("column")),
(
"children",
Expr::List(vec![
action_button("copy", "Copy", value),
action_button("edit", "Edit", value),
]),
),
],
)
}
fn action_button(control: &str, label: &str, value: &Expr) -> Expr {
node(
"button",
vec![
("control", sym(control)),
("label", Expr::String(label.to_owned())),
("target", value.clone()),
],
)
}
fn roundtrip_badge(value: &Expr) -> Expr {
let codec = CodecId(0);
match sim_codec::encode_portable(codec, value) {
Ok(text) => match sim_codec::decode_portable(codec, &text) {
Ok(decoded) if &decoded == value => badge("ok", "round-trips"),
Ok(_) => badge("warn", "round-trip differs"),
Err(_) => badge("warn", "decode failed"),
},
Err(_) => badge("info", "non-data value"),
}
}
pub(crate) fn universal_regions(value: &Expr) -> Vec<Expr> {
vec![
summary_card(value),
canonical_text(value),
structure_tree(value),
operations_inspector(value),
]
}
pub use sim_value::kind::expr_kind;
fn short_label(value: &Expr) -> String {
let rendered = render_value(value);
if rendered.len() <= 48 {
rendered
} else {
format!("{}...", &rendered[..45])
}
}
pub fn render_value(value: &Expr) -> String {
match value {
Expr::Nil => "nil".to_owned(),
Expr::Bool(flag) => flag.to_string(),
Expr::Number(number) => number.canonical.clone(),
Expr::Symbol(symbol) | Expr::Local(symbol) => symbol.as_qualified_str(),
Expr::String(text) => format!("{text:?}"),
Expr::Bytes(bytes) => format!("#bytes({})", bytes.len()),
Expr::List(items) => format!("({})", render_items(items)),
Expr::Vector(items) => format!("[{}]", render_items(items)),
Expr::Set(items) => format!("#{{{}}}", render_items(items)),
Expr::Map(entries) => {
let body = entries
.iter()
.map(|(key, value)| format!("{}: {}", render_value(key), render_value(value)))
.collect::<Vec<_>>()
.join(", ");
format!("{{{body}}}")
}
other => format!("<{}>", expr_kind(other)),
}
}
fn render_items(items: &[Expr]) -> String {
items.iter().map(render_value).collect::<Vec<_>>().join(" ")
}