use std::rc::Rc;
use indexmap::IndexMap;
use jimtcl::{
Interp, JimError, JimObject, JimResult, check_or_error, custom::JimExtensionType,
object::IntoJimObj, tcl_error,
};
use crate::typedata::TypedData;
pub fn td_get<'jim>(interp: &'jim Interp, args: &[JimObject<'jim>]) -> JimResult<JimObject<'jim>> {
let usage = "usage: td get ?-native? <object> <key>...";
check_or_error!(args.len() >= 3, usage);
let (native, data, keys) = if args[1].as_str()? == "-native" {
check_or_error!(args.len() >= 4, usage);
(true, &args[2], &args[3..])
} else {
(false, &args[1], &args[2..])
};
let obj = TypedData::extract_ref(data)?;
let data = td_walk_path(obj, keys)?;
if native {
data.to_native(interp)
} else {
Ok(data.clone().to_jim(interp))
}
}
pub fn td_set<'jim>(interp: &'jim Interp, args: &[JimObject<'jim>]) -> JimResult<JimObject<'jim>> {
let usage = "usage: td set <object> <key>... <value>";
check_or_error!(args.len() >= 4, usage);
let var = args[1].as_str()?;
let keys = &args[2..args.len() - 1];
let val = TypedData::extract(&args[args.len() - 1])?;
let mut obj = {
let var_content = interp
.get_variable(var)
.ok_or_else(|| tcl_error!("variable {} not set", var))?;
TypedData::extract(&var_content)?
};
let tgt = td_walk_mut(Rc::make_mut(&mut obj), keys)?;
*tgt = (*val).clone();
let obj = obj.to_jim(interp);
interp.set_variable(var, &obj)?;
Ok(obj)
}
pub fn td_lappend<'jim>(
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
let usage = "usage: td lappend <object> ?<key>...? ?--? <value>...";
check_or_error!(args.len() >= 3, usage);
let var = args[1].as_str()?;
let n = args.len();
let (keys, tcl_vals) = {
if let Some(pos) = args[2..n].iter().position(|s| s.as_bytes() == b"--") {
(&args[2..pos + 2], &args[pos + 3..n])
} else {
(&args[2..n - 1], &args[n - 1..n])
}
};
let mut values = Vec::with_capacity(tcl_vals.len());
for o in tcl_vals {
values.push(TypedData::extract(o)?);
}
let mut obj = {
let var_content = interp
.get_variable(var)
.ok_or_else(|| tcl_error!("variable {} not set", var))?;
TypedData::extract(&var_content)?
};
let tgt = td_walk_mut(Rc::make_mut(&mut obj), keys)?;
match tgt {
TypedData::List(list) => {
Rc::make_mut(list).extend(values);
}
_ => return Err(tcl_error!("TypedData object is not a list")),
}
let obj = obj.to_jim(interp);
interp.set_variable(var, &obj)?;
Ok(obj)
}
fn td_walk_path<'jim: 'a, 'a>(
mut obj: &'a TypedData,
mut keys: &[JimObject<'jim>],
) -> JimResult<&'a TypedData> {
while !keys.is_empty() {
let key = &keys[0];
let data: &TypedData = match obj {
TypedData::List(list) => {
let i = key.as_index()?;
if i < 0 && ((-i) as usize) <= list.len() {
&list[(-i - 1) as usize]
} else if i >= 0 && (i as usize) < list.len() {
&list[i as usize]
} else {
return Err(format!("list index {} out of bounds", i).into());
}
}
TypedData::Record(map) => {
let k = key.as_str()?;
map.get(k)
.ok_or_else(|| tcl_error!("key {} does not exist", k))?
}
_ => return Err(JimError::from("typed data is not a list or object")),
};
obj = data;
keys = &keys[1..]
}
Ok(obj)
}
fn td_walk_mut<'jim: 'a, 'a>(
mut obj: &'a mut TypedData,
mut keys: &[JimObject<'jim>],
) -> JimResult<&'a mut TypedData> {
while !keys.is_empty() {
let key = &keys[0];
let data: &mut TypedData = match obj {
TypedData::List(list) => {
let arr = Rc::make_mut(list);
let i = key.as_index()?;
if i < 0 && ((-i) as usize) <= arr.len() {
Rc::make_mut(&mut arr[(-i - 1) as usize])
} else if i >= 0 && (i as usize) < arr.len() {
Rc::make_mut(&mut arr[i as usize])
} else {
return Err(format!("list index {} out of bounds", i).into());
}
}
TypedData::Record(map) => {
let m = Rc::make_mut(map);
let k = key.as_str()?;
Rc::make_mut(
m.entry(k.to_string())
.or_insert_with(|| TypedData::Record(IndexMap::new().into()).into()),
)
}
_ => return Err(JimError::from("typed data is not a list or object")),
};
obj = data;
keys = &keys[1..]
}
Ok(obj)
}