use std::sync::{Arc, Mutex};
use indexmap::IndexMap;
use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::Value;
use super::glyphs::{GlyphSet, ASCII, UTF};
use super::render::render_lines;
use super::walk::{build_tree, WalkOpts};
pub fn to_string_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
call_entry(args)
}
fn is_placeholder(v: &Value) -> bool {
matches!(v, Value::Placeholder)
|| matches!(v, Value::SingleString(s) if s == "_")
|| matches!(v, Value::StrArray(ss) if ss.len()==1 && ss[0] == "_")
}
#[derive(Debug, Clone)]
struct Opts {
glyphs: GlyphSet,
root: String,
walk: WalkOpts,
}
impl Default for Opts {
fn default() -> Self {
Self {
glyphs: UTF,
root: "<root>".to_string(),
walk: WalkOpts {
quote_strings: true,
show_types: false,
index_labels: true,
max_depth: None,
},
}
}
}
fn parse_options(map: &IndexMap<String, Value>) -> Result<Opts, String> {
let mut o = Opts::default();
for (k, v) in map.iter() {
match k.as_str() {
"ascii" => o.glyphs = if expect_bool("ascii", v)? { ASCII } else { UTF },
"root" => o.root = expect_string("root", v)?,
"quote_strings" => o.walk.quote_strings = expect_bool("quote_strings", v)?,
"show_types" => o.walk.show_types = expect_bool("show_types", v)?,
"index_labels" => o.walk.index_labels = expect_bool("index_labels", v)?,
"max_depth" => {
let n = expect_int("max_depth", v)?;
if n < 0 { return Err("tree:to_string => max_depth must be >= 0".to_string()); }
o.walk.max_depth = Some(n as usize);
}
_ => { }
}
}
Ok(o)
}
fn expect_bool(name: &str, v: &Value) -> Result<bool, String> {
match v {
Value::Bool(b) => Ok(*b),
_ => Err(format!("tree:to_string => {} must be bool", name)),
}
}
fn expect_string(name: &str, v: &Value) -> Result<String, String> {
match v {
Value::SingleString(s) => Ok(s.clone()),
Value::StrArray(a) if a.len() == 1 => Ok(a[0].clone()),
_ => Err(format!("tree:to_string => {} must be string", name)),
}
}
fn expect_int(name: &str, v: &Value) -> Result<i32, String> {
match v {
Value::Int(i) => Ok(*i),
_ => Err(format!("tree:to_string => {} must be int", name)),
}
}
fn render_to_string(opts: &Opts, data: &Value) -> String {
let root = build_tree(&opts.root, data, &opts.walk);
let lines = render_lines(&root, &opts.glyphs);
lines.join("\n")
}
fn call_entry(mut args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => Ok(make_partial(None, None)),
1 => {
let a0 = args.remove(0);
if is_placeholder(&a0) {
Ok(make_partial(None, None))
} else if let Value::KeyedArray(map) = a0 {
let _ = parse_options(&map)?; Ok(make_partial(Some(Value::KeyedArray(map)), None))
} else {
Err("tree:to_string => first argument must be options (keyed array) or '_'".to_string())
}
}
2 => {
let a0 = args.remove(0);
let a1 = args.remove(0);
let hole_left = is_placeholder(&a0);
let hole_right = is_placeholder(&a1);
if hole_left && hole_right {
return Ok(make_partial(None, None));
}
if hole_left {
return Ok(make_partial(None, Some(a1)));
}
if hole_right {
if !matches!(a0, Value::KeyedArray(_)) {
return Err("tree:to_string => options must be a keyed array".to_string());
}
return Ok(make_partial(Some(a0), None));
}
let opts = match a0 {
Value::KeyedArray(ref m) => parse_options(m)?,
_ => return Err("tree:to_string => options must be a keyed array".to_string()),
};
let out = render_to_string(&opts, &a1);
Ok(Value::SingleString(out))
}
n => Err(format!("tree:to_string => expected 0, 1 or 2 args, got {}", n)),
}
}
fn make_partial(opts: Option<Value>, data: Option<Value>) -> Value {
use mumu::parser::types::FunctionValue::RustClosure;
let state = Arc::new(Mutex::new((opts, data)));
let closure = RustClosure(
"tree:to_string-partial".to_string(),
Arc::new(Mutex::new(move |_interp: &mut Interpreter, new_args: Vec<Value>| {
let mut st = state.lock().map_err(|_| "tree:to_string => partial lock error".to_string())?;
for v in new_args {
if st.0.is_none() {
if is_placeholder(&v) {
} else if matches!(v, Value::KeyedArray(_)) {
st.0 = Some(v);
} else {
return Err("tree:to_string => options must be a keyed array or '_'".to_string());
}
} else if st.1.is_none() {
if is_placeholder(&v) {
} else {
st.1 = Some(v);
}
} else {
return Err("tree:to_string => partial received too many arguments".to_string());
}
}
if let (Some(o), Some(d)) = (&st.0, &st.1) {
let opts = match o {
Value::KeyedArray(m) => parse_options(m)?,
_ => return Err("tree:to_string => options must be a keyed array".to_string()),
};
let out = render_to_string(&opts, d);
return Ok(Value::SingleString(out));
}
Ok(make_partial(st.0.clone(), st.1.clone()))
})),
0,
);
Value::Function(Box::new(closure))
}