use num_traits::AsPrimitive;
use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use super::int_methods::{from_bytes, from_bytes_order};
use super::jwt_bridge::jwt_assoc;
use super::native::Native;
use super::numeric::IntWidth;
use super::std_bridge::{
arg_int, arg_str, as_i64, bytes_to_string, make_duration, make_path, open_file, path_like,
};
use super::value::Value;
pub(super) fn assoc_fn(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
if matches!(ty, "Header" | "EncodingKey") {
return jwt_assoc(ty, func, args);
}
if let Some(v) = super::ratatui::ratatui_assoc(ty, func, args) {
return Ok(Some(v));
}
if let Some(v) = conversion_assoc(ty, func, args)? {
return Ok(Some(v));
}
if let Some(v) = container_assoc(ty, func, args)? {
return Ok(Some(v));
}
if let Some(v) = fs_process_assoc(ty, func, args)? {
return Ok(Some(v));
}
misc_assoc(ty, func, args)
}
fn conversion_assoc(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(Some(match (ty, func) {
("String", "new" | "with_capacity") => Value::str(""),
("String", "from") => Value::str(args.first().map(Value::display).unwrap_or_default()),
("String", "from_utf8_lossy") => Value::str(bytes_to_string(args.first())),
("char", "from") => match args.first() {
Some(Value::Char(c)) => Value::Char(*c),
Some(Value::Int(n)) => match u8::try_from(*n) {
Ok(b) => Value::Char(char::from(b)),
Err(_) => bail!("`char::from` needs a u8"),
},
_ => bail!("`char::from` needs a u8"),
},
("char", "from_u32") => match args.first().and_then(as_i64) {
Some(n) => match u32::try_from(n).ok().and_then(char::from_u32) {
Some(c) => Value::some(Value::Char(c)),
None => Value::none(),
},
_ => Value::none(),
},
("char", "from_digit") => {
let n = args.first().and_then(as_i64).unwrap_or(-1);
let radix = args.get(1).and_then(as_i64).unwrap_or(10);
match (u32::try_from(n), u32::try_from(radix)) {
(Ok(n), Ok(radix)) => match char::from_digit(n, radix) {
Some(c) => Value::some(Value::Char(c)),
None => Value::none(),
},
_ => Value::none(),
}
}
(
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
| "usize",
"from_str_radix",
) => {
let text = args.first().map(Value::display).unwrap_or_default();
let radix = args
.get(1)
.and_then(as_i64)
.and_then(|r| u32::try_from(r).ok())
.unwrap_or(10);
match i64::from_str_radix(text.trim(), radix) {
Ok(n) => Value::ok(Value::Int(n)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
(
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
| "usize",
"from",
) => Value::Int(int_from_arg(ty, args.first())?),
("f32" | "f64", "from") => match args.first() {
Some(Value::Float(f)) => Value::Float(*f),
Some(Value::Int(n)) => Value::Float(AsPrimitive::<f64>::as_(*n)),
Some(Value::Bool(b)) => Value::Float(if *b { 1.0 } else { 0.0 }),
_ => bail!("`{ty}::from` needs a number"),
},
(
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
| "usize",
"try_from",
) => {
let n = int_from_arg(ty, args.first())?;
if int_fits(ty, n) {
Value::ok(Value::Int(n))
} else {
Value::err(Value::str(
"out of range integral type conversion attempted",
))
}
}
(
"i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize",
"from_le_bytes" | "from_be_bytes" | "from_ne_bytes",
) => int_from_bytes(ty, func, args)?,
("String", "from_utf8") => Value::ok(Value::str(bytes_to_string(args.first()))),
_ => return Ok(None),
}))
}
fn container_assoc(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(Some(match (ty, func) {
("Command", "new") => command_new(args.first().cloned().unwrap_or_else(|| Value::str(""))),
("Vec", "new" | "with_capacity") => Value::vec(vec![]),
("Vec", "from") => match args.first() {
Some(Value::Vec(v)) => Value::vec(v.lock().clone()),
Some(other) => Value::vec(vec![other.clone()]),
None => Value::vec(vec![]),
},
("HashMap" | "BTreeMap", "new") | ("HashMap", "with_capacity") => Value::map(),
("HashSet" | "BTreeSet", "new") | ("HashSet", "with_capacity") => Value::set(),
("Box" | "Rc" | "Arc" | "RefCell" | "Cell", "new") => {
args.first().cloned().unwrap_or(Value::Unit)
}
("BufReader" | "BufWriter", "new" | "with_capacity") => match args.last() {
Some(Value::Native(h)) if matches!(&*h.lock(), Native::Stream(_)) => {
let cloned = {
let locked = h.lock();
let Native::Stream(s) = &*locked else {
unreachable!()
};
s.try_clone()
};
match cloned {
Ok(clone) => Native::Reader(std::io::BufReader::new(
Box::new(clone) as Box<dyn std::io::Read + Send>
))
.wrap(),
Err(e) => return Err(anyhow!("cannot buffer socket: {e}")),
}
}
other => other.cloned().unwrap_or(Value::Unit),
},
("PathBuf", "new") => make_path(""),
("PathBuf" | "Path", "from") | ("Path", "new") => {
make_path(args.first().map(path_like).unwrap_or_default())
}
("Regex", "new") => {
let pat = args.first().map(Value::display).unwrap_or_default();
match regex::Regex::new(&pat) {
Ok(compiled) => Value::ok(super::regex_bridge::make_regex(compiled, &pat)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
("Some", _) | ("Option", "Some") => {
Value::some(args.first().cloned().unwrap_or(Value::Unit))
}
("Result", "Ok") => Value::ok(args.first().cloned().unwrap_or(Value::Unit)),
("Result", "Err") => Value::err(args.first().cloned().unwrap_or(Value::Unit)),
_ => return Ok(None),
}))
}
pub(super) fn command_new(program: Value) -> Value {
Value::struct_of(
"Command",
[
("program".into(), program),
("args".into(), Value::vec(vec![])),
("cwd".into(), Value::Unit),
("envs".into(), Value::Unit),
("stdin".into(), Value::Unit),
("stdout".into(), Value::Unit),
("stderr".into(), Value::Unit),
],
)
}
fn fs_process_assoc(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(Some(match (ty, func) {
("Permissions", "from_mode") => {
let mode = args.first().and_then(as_i64).unwrap_or(0o644);
Value::struct_of("Permissions", vec![("mode".into(), Value::Int(mode))])
}
("File", "open") => open_file(&arg_str(args, 0), std::fs::OpenOptions::new().read(true)),
("File", "create") => open_file(
&arg_str(args, 0),
std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true),
),
("File", "create_new") => open_file(
&arg_str(args, 0),
std::fs::OpenOptions::new().write(true).create_new(true),
),
("OpenOptions", "new") => Value::struct_of(
"OpenOptions",
[
"read",
"write",
"append",
"create",
"create_new",
"truncate",
]
.into_iter()
.map(|k| (Arc::from(k), Value::Bool(false))),
),
("Stdio", "piped" | "inherit" | "null") => {
Value::struct_of("Stdio", [("kind".into(), Value::str(func))])
}
("Stdio", "from") => {
let Some(file @ Value::Native(_)) = args.first() else {
bail!(
"Stdio::from takes an open File, got {}",
args.first().map_or("nothing", Value::type_name)
);
};
Value::struct_of(
"Stdio",
[
("kind".into(), Value::str("file")),
("file".into(), file.clone()),
],
)
}
_ => return Ok(None),
}))
}
fn misc_assoc(ty: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(Some(match (ty, func) {
("Instant", "now") => Native::Instant(std::time::Instant::now()).wrap(),
("SystemTime", "now") => Native::SystemTime(std::time::SystemTime::now()).wrap(),
("Duration", "from_secs") => make_duration(std::time::Duration::from_secs(
u64::try_from(arg_int(args, 0)).unwrap_or_default(),
)),
("Duration", "from_millis") => make_duration(std::time::Duration::from_millis(
u64::try_from(arg_int(args, 0)).unwrap_or_default(),
)),
("Duration", "from_micros") => make_duration(std::time::Duration::from_micros(
u64::try_from(arg_int(args, 0)).unwrap_or_default(),
)),
("Duration", "from_nanos") => make_duration(std::time::Duration::from_nanos(
u64::try_from(arg_int(args, 0)).unwrap_or_default(),
)),
("Duration", "new") => make_duration(std::time::Duration::new(
u64::try_from(arg_int(args, 0)).unwrap_or_default(),
u32::try_from(arg_int(args, 1)).unwrap_or_default(),
)),
("TcpListener", "bind") => match std::net::TcpListener::bind(arg_str(args, 0)) {
Ok(l) => Value::ok(Native::Listener(l).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("TcpStream", "connect") => match std::net::TcpStream::connect(arg_str(args, 0)) {
Ok(s) => Value::ok(Native::Stream(s).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("UdpSocket", "bind") => match std::net::UdpSocket::bind(arg_str(args, 0)) {
Ok(s) => Value::ok(Native::Udp(s).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("Document", "load") => super::pdf_bridge::load(&arg_str(args, 0)),
("Element", "parse") => super::xmltree_bridge::parse(args),
("Element", "new") => super::xmltree_bridge::new_element(&arg_str(args, 0)),
("XMLNode", "Element" | "Text" | "Comment" | "CData" | "ProcessingInstruction") => {
Value::Enum {
enum_name: Arc::from("XMLNode"),
variant: Arc::from(func),
data: args.iter().cloned().collect(),
}
}
("SeekFrom", "Start" | "End" | "Current") => Value::Enum {
enum_name: Arc::from("SeekFrom"),
variant: Arc::from(func),
data: Arc::from(vec![args.first().cloned().unwrap_or(Value::Int(0))]),
},
_ => return Ok(None),
}))
}
fn int_from_arg(ty: &str, v: Option<&Value>) -> Result<i64> {
match v {
Some(Value::Int(n)) => Ok(*n),
Some(Value::Bool(b)) => Ok(i64::from(*b)),
Some(Value::Char(c)) => Ok(*c as i64),
_ => bail!("`{ty}` conversion needs an integer"),
}
}
fn int_from_bytes(ty: &str, func: &str, args: &[Value]) -> Result<Value> {
let (Some(width), Some(order)) = (IntWidth::parse(ty), from_bytes_order(func)) else {
bail!("`{ty}::{func}` is not a byte conversion");
};
let bytes = byte_array(ty, func, args.first())?;
Ok(Value::int_of_width(
from_bytes(width, order, &bytes)?,
width,
))
}
fn byte_array(ty: &str, func: &str, arg: Option<&Value>) -> Result<Vec<i128>> {
let Some(Value::Vec(items)) = arg else {
bail!("`{ty}::{func}` needs a byte array");
};
let items = items.lock();
let mut out = Vec::with_capacity(items.len());
for item in items.iter() {
let Some((value, _)) = item.int_parts() else {
bail!("`{ty}::{func}` needs a byte array");
};
out.push(value);
}
Ok(out)
}
fn int_fits(ty: &str, n: i64) -> bool {
match ty {
"i8" => i8::try_from(n).is_ok(),
"i16" => i16::try_from(n).is_ok(),
"i32" => i32::try_from(n).is_ok(),
"u8" => u8::try_from(n).is_ok(),
"u16" => u16::try_from(n).is_ok(),
"u32" => u32::try_from(n).is_ok(),
"u64" | "u128" | "usize" => n >= 0,
_ => true,
}
}