tree-mumu 0.1.0-rc.2

Creates Linux `tree`-style renderings of MuMu values
Documentation
// src/share/label.rs
//
// Value labelling utilities: escaping and short type/leaf previews.

use mumu::parser::types::Value;

/// Escape a string (double-quote ready).
pub fn escape_str(s: &str) -> String {
    let mut out = String::with_capacity(s.len() + 8);
    for ch in s.chars() {
        match ch {
            '\\' => out.push_str("\\\\"),
            '"' => out.push_str("\\\""),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            other => out.push(other),
        }
    }
    out
}

/// Short human-readable type label for a value.
pub fn short_type(v: &Value) -> &'static str {
    match v {
        Value::Bool(_)         => "bool",
        Value::Int(_)          => "int",
        Value::Long(_)         => "long",
        Value::Float(_)        => "float",
        Value::SingleString(_) => "string",

        Value::IntArray(_)     => "int_array",
        Value::FloatArray(_)   => "float_array",
        Value::BoolArray(_)    => "bool_array",
        Value::StrArray(_)     => "str_array",

        Value::Int2DArray(_)   => "int2d_array",
        Value::Float2DArray(_) => "float2d_array",

        Value::MixedArray(_)   => "mixed_array",
        Value::KeyedArray(_)   => "keyed_array",
        Value::KeyedError(_)   => "keyed_error",

        Value::Function(_)     => "function",
        Value::Stream(_)       => "stream",
        Value::Iterator(_)     => "iterator",
        Value::Tensor(_)       => "tensor",
        Value::Ref(_)          => "ref",
        Value::Regex(_)        => "regex",
        Value::Undefined       => "undefined",
        Value::Placeholder     => "placeholder",
    }
}

/// Build a leaf preview text for a scalar-like value.
/// If `quote` is true, strings are quoted/escaped.
/// If `show_types` is true, append a ` (type)` suffix.
pub fn leaf_preview(v: &Value, quote: bool, show_types: bool) -> String {
    let body = match v {
        Value::SingleString(s) if quote => format!("\"{}\"", escape_str(s)),
        Value::SingleString(s) => s.clone(),
        Value::Int(i) => i.to_string(),
        Value::Long(l) => l.to_string(),
        Value::Float(f) => f.to_string(),
        Value::Bool(b) => b.to_string(),
        Value::Regex(rx) => format!("Regex(/{}{}/)", rx.pattern, rx.flags),
        Value::Function(_) => "[Function]".to_string(),
        Value::Stream(h) => format!("<Stream id={}, label={}>", h.stream_id, h.label),
        Value::Iterator(_) => "[Iterator]".to_string(),
        Value::Tensor(_) => "[Tensor]".to_string(),
        Value::Undefined => "undefined".to_string(),
        Value::Placeholder => "_".to_string(),
        Value::KeyedError(m) => {
            let msg = m.get("message").and_then(|v| if let Value::SingleString(s)=v {Some(s)} else {None}).cloned();
            match msg {
                Some(s) => format!("Error(\"{}\")", escape_str(&s)),
                None => "Error".to_string(),
            }
        }

        // Containers: show a compact tag as a leaf if we ever fall back to leaf
        Value::KeyedArray(_) => "{…}".to_string(),
        Value::MixedArray(_) => "[…]".to_string(),
        Value::IntArray(xs) => format!("[int; {}]", xs.len()),
        Value::FloatArray(xs) => format!("[float; {}]", xs.len()),
        Value::BoolArray(xs) => format!("[bool; {}]", xs.len()),
        Value::StrArray(xs) => format!("[str; {}]", xs.len()),
        Value::Int2DArray(rows) => {
            let cols = rows.get(0).map(|r| r.len()).unwrap_or(0);
            format!("[int; {}x{}]", rows.len(), cols)
        }
        Value::Float2DArray(rows) => {
            let cols = rows.get(0).map(|r| r.len()).unwrap_or(0);
            format!("[float; {}x{}]", rows.len(), cols)
        }
        Value::Ref(_) => "".to_string(),
    };

    if show_types {
        format!("{} ({})", body, short_type(v))
    } else {
        body
    }
}