use num_traits::AsPrimitive;
use std::f64::consts::PI;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Result, bail};
use parking_lot::Mutex;
use super::bytecode::Chunk;
use super::bytecode::{BuiltinId, MethodName};
use super::methods::{self, make_ordering};
use super::native::Native;
use super::shared::{self, Args, CharOut, F32Out, Num, NumOut, parse_rfc3339};
use super::value::{ClosureData, Value};
use super::vm::Vm;
impl Vm {
pub(super) fn render_fmt(chunk: &Chunk, spec: u16, regs: &[Value]) -> Result<String> {
let f = &chunk.fmts[spec as usize];
let positional: Vec<Value> = f
.positional
.iter()
.map(|r| regs[*r as usize].clone())
.collect();
let named: Vec<(&str, Value)> = f
.named
.iter()
.map(|(n, r)| (n.as_str(), regs[*r as usize].clone()))
.collect();
render_template(&f.template, &positional, &named)
}
pub(super) fn eval_path_value(&self, raw: &[String]) -> Result<Value> {
let segs = self.canonical(raw);
match segs.last().map(String::as_str) {
Some("None") => Ok(Value::none()),
Some("UNIX_EPOCH") => Ok(Native::SystemTime(std::time::UNIX_EPOCH).wrap()),
Some(other) => {
if let Some(v) = super::crates_bridge::base64_engine(other) {
return Ok(v);
}
if let Some(v) = super::winreg_bridge::winreg_const(other) {
return Ok(v);
}
if let Some(v) = super::service_bridge::service_const(other) {
return Ok(v);
}
if segs.len() >= 2 {
let ty = segs[segs.len() - 2].as_str();
if let Some(v) = typed_path_constant(ty, other) {
return Ok(v);
}
if let Some(v) = self.unit_variant(Some(ty), other) {
return Ok(v);
}
} else {
if let Some(v) = self.unit_variant(None, other) {
return Ok(v);
}
if let Some(name) = self
.unit_structs
.iter()
.find(|name| &***name == other || super::resolver::bare(name) == other)
{
return Ok(Value::structure(
super::value::StructShape::new(&**name, Vec::new()),
Vec::new(),
));
}
}
if segs.len() == 1
&& let Some(chunk) = self.user_function(other)
{
return Ok(path_closure(segs.clone(), chunk.num_params));
}
if matches!(other, "new" | "default") {
return Ok(path_closure(segs.clone(), 0));
}
let function = segs.join("::");
if segs.len() >= 2
&& let Some(chunk) = self
.user_method(&segs[segs.len() - 2], other)
.or_else(|| self.user_function(&function))
.or_else(|| self.user_function(other))
{
return Ok(path_closure(segs.clone(), chunk.num_params));
}
if other.chars().any(|c| c.is_ascii_uppercase())
&& !other.chars().any(|c| c.is_ascii_lowercase())
{
bail!("unsupported constant `{function}`");
}
Ok(path_closure(segs.clone(), 1))
}
None => bail!("empty path"),
}
}
fn dispatch_bare_call(self: &Arc<Self>, name: &str, args: Vec<Value>) -> Result<Value> {
match name {
"Some" => return Ok(Value::some(one(args)?)),
"Ok" => return Ok(Value::ok(one(args)?)),
"Err" => return Ok(Value::err(one(args)?)),
"drop" => return Ok(Value::Unit),
_ => {}
}
if let Some(chunk) = self.user_function(name) {
return self.run_chunk(&chunk, &args, &[]);
}
if self.struct_names.contains(name) {
return Ok(Self::make_tuple_struct(name, args));
}
if let Some(v) = self.make_tuple_variant(None, name, &args) {
return Ok(v);
}
bail!("unknown function `{name}`");
}
pub(super) fn dispatch_call(
self: &Arc<Self>,
segs: &[String],
args: Vec<Value>,
) -> Result<Value> {
let canon = self.canonical(segs);
if canon.len() == 1 {
return self.dispatch_bare_call(&canon[0], args);
}
let last = canon[canon.len() - 1].as_str();
let namespace = canon[canon.len() - 2].as_str();
if namespace == "ctrlc" && last == "set_handler" {
let closure = args.first().cloned().unwrap_or(Value::Unit);
return Ok(match super::set_ctrlc_handler(closure) {
Ok(()) => Value::ok(Value::Unit),
Err(e) => Value::err(Value::str(e.to_string())),
});
}
if namespace == "thread" {
if last == "sleep" {
let Some(d) = args
.first()
.and_then(super::std_bridge::duration_from_value)
else {
bail!("thread::sleep takes a Duration");
};
std::thread::sleep(d);
return Ok(Value::Unit);
}
bail!("std::thread is not supported beyond sleep, use tokio::spawn");
}
let images: Vec<Value> = args
.iter()
.map(|arg| match arg.bridge_image() {
Some(image) => image,
None => arg.clone(),
})
.collect();
if canon.first().map(String::as_str) == Some("reqwest") {
return super::http::reqwest_call(&canon, &images);
}
if let Some(v) = super::ratatui::ratatui_assoc(namespace, last, &images) {
return Ok(v);
}
match (namespace, last) {
("serde_json", _) => super::json_bridge::bridge_serde_json(last, &images),
("Utc" | "Local", "now") => Ok(now_datetime(namespace == "Local")),
("DateTime", "parse_from_rfc3339") => {
Ok(match parse_rfc3339(&arg0(&images).display()) {
Ok((unix_secs, nanos, offset)) => {
Value::ok(datetime_value(unix_secs, nanos, false, offset))
}
Err(e) => Value::err(Value::str(e)),
})
}
("time", "sleep") => Ok(sleep_future(&images)),
("task", "yield_now") => Ok(yield_future()),
_ => {
if let Some(chunk) = self.user_function(&canon.join("::")) {
return self.run_chunk(&chunk, &args, &[]);
}
if let Some(v) = super::std_bridge::native_call(namespace, last, &images)? {
return Ok(v);
}
if let Some(chunk) = self.user_method(namespace, last) {
return self.run_chunk(&chunk, &args, &[]);
}
if let Some(v) = super::assoc::assoc_fn(namespace, last, &images)? {
return Ok(v);
}
if let Some(v) = self.make_tuple_variant(Some(namespace), last, &args) {
return Ok(v);
}
if namespace == "Value"
&& matches!(last, "String" | "Bool" | "Number" | "Array" | "Object")
{
return one(args);
}
if let Some((recv, rest)) = args.split_first() {
let recv = recv.clone();
let mut rest = rest.to_vec();
let name = MethodName {
id: BuiltinId::resolve(last),
text: last.to_string(),
scalar: None,
};
return self.eval_method(&recv, &name, &mut rest);
}
bail!("unsupported call `{}`", canon.join("::"))
}
}
}
pub(super) fn eval_method(
self: &Arc<Self>,
recv: &Value,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
let dereferenced = if let Value::Ref(reference) = recv {
let Some(value) = reference.get() else {
bail!("method call through a dangling reference");
};
Some(value)
} else {
None
};
let recv = dereferenced.as_ref().unwrap_or(recv);
if matches!(recv, Value::IntW(..) | Value::F32(_)) {
match name.id {
BuiltinId::ToString => return Ok(Value::str(recv.display())),
BuiltinId::Clone => return Ok(recv.clone()),
_ => {}
}
}
if let Some(v) = methods::json_type_test(recv, &name.text) {
return Ok(v);
}
if let Some(v) = methods::json_value_method(recv, &name.text, &*args) {
return Ok(v);
}
if let Some(result) = int_method(recv, &name.text, args) {
return result;
}
if let Value::F32(f) = recv
&& let Some(value) = f32_method(*f, &name.text, args)?
{
return Ok(value);
}
let widened;
let recv = match recv.bridge_image() {
Some(image) => {
widened = image;
&widened
}
None => recv,
};
let hands_args_through = matches!(
recv,
Value::Enum { .. } | Value::Bool(_) | Value::Vec(_) | Value::Map(..)
) || matches!(recv, Value::Struct(st) if &**st.name() == "Entry")
|| name.id == BuiltinId::Fold;
if !hands_args_through {
for arg in args.iter_mut() {
if let Some(image) = arg.bridge_image() {
*arg = image;
}
}
}
if let Some(res) = super::http::http_method(recv, &name.text, args) {
return res;
}
if let Some(v) = range_builtin(recv, name, args)? {
return Ok(v);
}
let expanded;
let recv = if matches!(recv, Value::Range { .. }) {
expanded = Self::iterator_value(recv.clone())?;
&expanded
} else {
recv
};
if let Value::Native(iterator) = recv
&& matches!(&*iterator.lock(), Native::Iterator(_))
&& let Some(value) = self.iterator_method(iterator, name, args)?
{
return Ok(value);
}
let type_key = match recv {
Value::Struct(st) => Some(st.name().to_string()),
Value::Enum { enum_name, .. } => Some(enum_name.to_string()),
_ => None,
};
if let Some(tk) = &type_key
&& let Some(chunk) = self.user_method(tk, &name.text)
{
let mut full = Vec::with_capacity(args.len() + 1);
full.push(recv.clone());
full.extend(args.iter().cloned());
return self.run_chunk(&chunk, &full, &[]);
}
if name.id.is_higher_order()
&& let Some(v) = self.higher_order(recv, &name.text, &*args)?
{
return Ok(v);
}
if matches!(recv, Value::Vec(_))
&& matches!(name.text.as_str(), "extend" | "extend_from_slice")
&& let Some(first) = args.first()
&& !matches!(first, Value::Vec(_))
{
let items = self.drain_items(first.clone())?;
args[0] = Value::vec(items);
}
Self::method_by_receiver(recv, name, args)
}
fn method_by_receiver(recv: &Value, name: &MethodName, args: &mut [Value]) -> Result<Value> {
let m = name.text.as_str();
match recv {
Value::Str(s) => methods::str_method(s, name, args),
Value::Vec(v) => super::vecmap::vec_method(v, name, args),
Value::Map(map, kind) => super::vecmap::map_method(map, *kind, name, args),
Value::Enum { enum_name, .. } if &**enum_name == "Option" => {
methods::opt_method(recv, name, args)
}
Value::Enum { enum_name, .. } if &**enum_name == "Result" => {
methods::res_method(recv, name, args)
}
Value::Enum { .. } => methods::generic_method(recv, m, args),
Value::Struct(st) if super::ratatui::is_ratatui_struct(st.name()) => {
super::ratatui::struct_method(st, m, args)
}
Value::Struct(st) => match &**st.name() {
"Command" => super::process::command_method(recv, m, args),
"Child" => super::process::child_method(recv, m, args),
"ExitStatus" => exitstatus_method(st, m),
"Output" => output_method(st, m),
"Duration" => duration_method(st, m, args),
"DateTime" => datetime_method(st, m, args),
"Entry" => methods::entry_method(st, m, args),
"Path" | "PathBuf" => super::std_bridge::path_method(st, m, args),
"OsString" => super::std_bridge::os_string_method(st, m),
"DirEntry" => super::std_bridge::dir_entry_method(st, m),
"FileType" => super::std_bridge::file_type_method(st, m),
"Metadata" => super::std_bridge::metadata_method(st, m),
"StdStream" => super::std_bridge::std_stream_method(st, m, args),
"OpenOptions" => super::std_bridge::openoptions_method(st, m, args),
"Permissions" => match m {
"mode" => Ok(st.get("mode").unwrap_or(Value::Int(0))),
"readonly" => Ok(st.get("readonly").unwrap_or(Value::Bool(false))),
"set_readonly" => Ok(Value::Unit),
_ => bail!("unknown method `{m}` on Permissions"),
},
"Rng" => super::crates_bridge::rng_method(m, args),
"Base64Engine" => super::crates_bridge::base64_method(st, m, args),
"Element" => super::xmltree_bridge::element_method(st, m, args),
"RegKey" => super::winreg_bridge::winreg_method(st, m, args),
"ServiceManager" => super::service_bridge::manager_method(st, m, args),
"Service" => super::service_bridge::service_method(st, m, args),
"WmiConnection" => super::wmi_bridge::wmi_method(st, m, args),
_ => methods::generic_method(recv, m, args),
},
Value::Native(native) => Self::native_method(native, name, args),
Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Char(_) => {
scalar_method(recv, m, args)
}
other => methods::generic_method(other, m, args),
}
}
fn native_method(
native: &Arc<Mutex<Native>>,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
let m = name.text.as_str();
if let Native::Instant(instant) = &*native.lock()
&& m == "elapsed"
{
return Ok(super::std_bridge::make_duration(instant.elapsed()));
}
if let Some(v) = super::native_methods::native_method(native, m, args)? {
return Ok(v);
}
if let Some(v) = super::regex_bridge::regex_native_method(native, m, args)? {
return Ok(v);
}
methods::generic_method(&Value::Native(native.clone()), m, args)
}
}
fn typed_path_constant(ty: &str, name: &str) -> Option<Value> {
if ty == "consts" {
let text = match name {
"OS" => std::env::consts::OS,
"ARCH" => std::env::consts::ARCH,
"FAMILY" => std::env::consts::FAMILY,
"EXE_EXTENSION" => std::env::consts::EXE_EXTENSION,
"EXE_SUFFIX" => std::env::consts::EXE_SUFFIX,
"PI" => return Some(Value::Float(PI)),
_ => return None,
};
return Some(Value::str(text));
}
if let Some(v) = int_limit(ty, name) {
return Some(v);
}
if let Some(v) = super::ratatui::ratatui_const(ty, name) {
return Some(v);
}
if let Some(v) = super::service_bridge::service_variant(ty, name) {
return Some(v);
}
if let Some(v) = super::jwt_bridge::jwt_algorithm(ty, name) {
return Some(v);
}
if ty == "Ordering" {
use std::cmp::Ordering::{Equal, Greater, Less};
let o = match name {
"Less" => Less,
"Greater" => Greater,
"Equal" => Equal,
_ => return None,
};
return Some(make_ordering(o));
}
if ty == "Value" && name == "Null" {
return Some(Value::none());
}
None
}
fn one(args: Vec<Value>) -> Result<Value> {
args.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("expected one argument"))
}
fn arg0(args: &[Value]) -> Value {
args.first().cloned().unwrap_or(Value::Unit)
}
fn range_builtin(recv: &Value, name: &MethodName, args: &[Value]) -> Result<Option<Value>> {
let Value::Range {
start,
end,
inclusive,
} = recv
else {
return Ok(None);
};
match name.id {
BuiltinId::Clone => return Ok(Some(recv.clone())),
BuiltinId::Contains => {
let Some(Value::Int(value)) = args.first() else {
bail!("range contains needs an integer");
};
return Ok(Some(Value::Bool(if *inclusive {
*value >= *start && *value <= *end
} else {
*value >= *start && *value < *end
})));
}
BuiltinId::Len | BuiltinId::Count => {
let extra = i64::from(*inclusive && end >= start);
return Ok(Some(Value::Int(end.saturating_sub(*start) + extra)));
}
BuiltinId::IsEmpty => {
return Ok(Some(Value::Bool(if *inclusive {
start > end
} else {
start >= end
})));
}
_ => {}
}
Ok(None)
}
fn int_limit(ty: &str, name: &str) -> Option<Value> {
if ty == "f64" {
let v = match name {
"EPSILON" => f64::EPSILON,
"MAX" => f64::MAX,
"MIN" => f64::MIN,
"MIN_POSITIVE" => f64::MIN_POSITIVE,
"INFINITY" => f64::INFINITY,
"NEG_INFINITY" => f64::NEG_INFINITY,
"NAN" => f64::NAN,
_ => return None,
};
return Some(Value::Float(v));
}
if ty == "f32" {
let v = match name {
"EPSILON" => f32::EPSILON,
"MAX" => f32::MAX,
"MIN" => f32::MIN,
"MIN_POSITIVE" => f32::MIN_POSITIVE,
"INFINITY" => f32::INFINITY,
"NEG_INFINITY" => f32::NEG_INFINITY,
"NAN" => f32::NAN,
_ => return None,
};
return Some(Value::F32(v));
}
let clamp = match ty {
"i128" => Some((i128::from(i64::MIN), i128::from(i64::MAX))),
"u128" => Some((0, i128::from(i64::MAX))),
_ => None,
};
let Some((min, max)) = clamp else {
let w = super::numeric::IntWidth::parse(ty)?;
let value = match name {
"MAX" => w.max(),
"MIN" => w.min(),
_ => return None,
};
return Some(Value::int_of_width(value, w));
};
match name {
"MAX" => Some(Value::Int(
i64::try_from(max).expect("clamped to i64 range"),
)),
"MIN" => Some(Value::Int(
i64::try_from(min).expect("clamped to i64 range"),
)),
_ => None,
}
}
fn exitstatus_method(s: &Arc<super::value::StructData>, m: &str) -> Result<Value> {
let success = matches!(s.get("success"), Some(Value::Bool(true)));
let code = match s.get("code") {
Some(Value::Int(c)) => Some(c),
_ => None,
};
match shared::exit_status_core(m, success, code) {
Some(shared::ExitOut::Bool(b)) => Ok(Value::Bool(b)),
Some(shared::ExitOut::OptInt(Some(c))) => Ok(Value::some(Value::Int(c))),
Some(shared::ExitOut::OptInt(None)) => Ok(Value::none()),
None => bail!("unknown method `{m}` on ExitStatus"),
}
}
fn output_method(s: &Arc<super::value::StructData>, m: &str) -> Result<Value> {
Ok(match m {
"status" | "stdout" | "stderr" => s.get(m).unwrap_or(Value::Unit),
_ => bail!("unknown method `{m}` on Output"),
})
}
fn sleep_future(args: &[Value]) -> Value {
let duration = args
.first()
.and_then(super::std_bridge::duration_from_value)
.unwrap_or(Duration::ZERO);
Native::Future(Box::pin(async move {
tokio::time::sleep(duration).await;
Value::Unit
}))
.wrap()
}
fn yield_future() -> Value {
Native::Future(Box::pin(async {
tokio::task::yield_now().await;
Value::Unit
}))
.wrap()
}
fn datetime_value(secs: i64, nanos: u32, local: bool, offset: i32) -> Value {
Value::struct_of(
"DateTime",
[
("secs".into(), Value::Int(secs)),
("nanos".into(), Value::Int(i64::from(nanos))),
("local".into(), Value::Bool(local)),
("offset".into(), Value::Int(i64::from(offset))),
],
)
}
fn now_datetime(local: bool) -> Value {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
datetime_value(
i64::try_from(now.as_secs()).unwrap_or(i64::MAX),
now.subsec_nanos(),
local,
0,
)
}
fn datetime_method(s: &Arc<super::value::StructData>, m: &str, args: &[Value]) -> Result<Value> {
let secs = match s.get("secs") {
Some(Value::Int(v)) => v,
_ => 0,
};
let nanos = match s.get("nanos") {
Some(Value::Int(v)) => u32::try_from(v).unwrap_or_default(),
_ => 0,
};
let local = matches!(s.get("local"), Some(Value::Bool(true)));
let offset = match s.get("offset") {
Some(Value::Int(v)) => i32::try_from(v).unwrap_or_default(),
_ => 0,
};
match shared::datetime_core(m, secs, nanos, local, offset, &VArgs(args)) {
Some(shared::DateOut::Int(i)) => Ok(Value::Int(i)),
Some(shared::DateOut::Text(t)) => Ok(Value::str(t)),
None => bail!("unknown method `{m}` on DateTime"),
}
}
fn duration_method(s: &Arc<super::value::StructData>, m: &str, args: &[Value]) -> Result<Value> {
let secs = u64::try_from(super::std_bridge::field_int(s, "secs")).unwrap_or_default();
let nanos = u32::try_from(super::std_bridge::field_int(s, "nanos")).unwrap_or_default();
if let "checked_add" | "checked_sub" = m {
let own = Duration::new(secs, nanos);
let Some(other) = args
.first()
.and_then(super::std_bridge::duration_from_value)
else {
bail!("`{m}` on Duration takes a Duration argument");
};
let out = match m {
"checked_add" => own.checked_add(other),
_ => own.checked_sub(other),
};
return Ok(out.map_or_else(Value::none, |d| {
Value::some(super::std_bridge::make_duration(d))
}));
}
match shared::duration_core(m, secs, nanos) {
Some(shared::DurOut::Int(i)) => Ok(Value::Int(i)),
Some(shared::DurOut::Float(f)) => Ok(Value::Float(f)),
Some(shared::DurOut::Bool(b)) => Ok(Value::Bool(b)),
None => bail!("unknown method `{m}` on Duration"),
}
}
fn int_method(recv: &Value, m: &str, args: &[Value]) -> Option<Result<Value>> {
let (value, mut width) = recv.int_parts()?;
let mut decoded = Vec::with_capacity(args.len());
for arg in args {
let (arg_value, arg_width) = arg.int_parts()?;
decoded.push(arg_value);
if !super::int_methods::takes_amount_arg(m)
&& let Ok(unified) = super::numeric::unify(width, arg_width)
{
width = unified;
}
}
Some(
match super::int_methods::int_method(m, width, value, &decoded)? {
Ok(out) => Ok(int_out(out, width)),
Err(error) => Err(error),
},
)
}
fn f32_method(recv: f32, name: &str, args: &[Value]) -> Result<Option<Value>> {
Ok(
shared::f32_core(recv, name, &VArgs(args))?.map(|out| match out {
F32Out::Val(value) => Value::F32(value),
F32Out::Bool(value) => Value::Bool(value),
F32Out::SomeOrdering(ordering) => Value::some(make_ordering(ordering)),
}),
)
}
fn int_out(out: super::int_methods::IntOut, width: super::numeric::IntWidth) -> Value {
use super::int_methods::IntOut;
match out {
IntOut::Same(value) => Value::int_of_width(value, width),
IntOut::Count(count) => {
Value::int_of_width(i128::from(count), super::numeric::IntWidth::U32)
}
IntOut::Bool(value) => Value::Bool(value),
IntOut::Checked(Some(value)) => Value::some(Value::int_of_width(value, width)),
IntOut::Checked(None) => Value::none(),
IntOut::SomeFloat(value) => Value::some(Value::Float(value)),
IntOut::Ordering(ordering) => make_ordering(ordering),
IntOut::Bytes(bytes) => Value::vec(
bytes
.into_iter()
.map(|byte| Value::Int(i64::from(byte)))
.collect(),
),
}
}
fn scalar_method(recv: &Value, m: &str, args: &[Value]) -> Result<Value> {
match m {
"to_string" => return Ok(Value::str(recv.display())),
"clone" | "into" => return Ok(recv.clone()),
_ => {}
}
if matches!(
m,
"as_str"
| "as_i64"
| "as_u64"
| "as_f64"
| "as_bool"
| "as_array"
| "as_array_mut"
| "as_object"
| "as_object_mut"
) {
let matched = match (recv, m) {
(Value::Bool(_), "as_bool")
| (Value::Str(_), "as_str")
| (Value::Int(_) | Value::IntW(..), "as_i64" | "as_u64")
| (Value::Float(_), "as_f64") => true,
(Value::Int(i), "as_f64") => {
return Ok(Value::some(Value::Float(AsPrimitive::<f64>::as_(*i))));
}
_ => false,
};
return Ok(if matched {
Value::some(recv.clone())
} else {
Value::none()
});
}
let n = match recv {
Value::Int(i) => Some(Num::Int(*i)),
Value::Float(f) => Some(Num::Float(*f)),
_ => None,
};
if let Some(n) = n {
if let Some(out) = shared::num_core(n, m, &VArgs(args))? {
return Ok(num_out(out));
}
bail!("unknown method `{m}` on a number");
}
if let Value::Char(ch) = recv
&& let Some(out) = shared::char_method(*ch, m, &VArgs(args))
{
return Ok(match out? {
CharOut::Bool(v) => Value::Bool(v),
CharOut::Char(c) => Value::Char(c),
CharOut::Str(s) => Value::str(s),
CharOut::OptU32(Some(digit)) => Value::some(Value::int_of_width(
i128::from(digit),
super::numeric::IntWidth::U32,
)),
CharOut::OptU32(None) => Value::none(),
});
}
methods::generic_method(recv, m, args)
}
fn num_out(out: NumOut) -> Value {
match out {
NumOut::Int(i) => Value::Int(i),
NumOut::Float(f) => Value::Float(f),
NumOut::Bool(b) => Value::Bool(b),
NumOut::SomeInt(i) => Value::some(Value::Int(i)),
NumOut::SomeFloat(f) => Value::some(Value::Float(f)),
NumOut::Nothing => Value::none(),
NumOut::Ordering(o) => make_ordering(o),
NumOut::SomeOrdering(o) => Value::some(make_ordering(o)),
}
}
fn path_closure(segs: Vec<String>, num_params: usize) -> Value {
Value::Closure(Arc::new(ClosureData {
chunk: super::bytecode::path_call_chunk(segs, num_params),
captured: Vec::new(),
}))
}
pub(super) struct VArgs<'a>(pub(super) &'a [Value]);
impl Args for VArgs<'_> {
fn text(&self, i: usize) -> String {
self.0.get(i).map(Value::display).unwrap_or_default()
}
fn int(&self, i: usize) -> Option<i64> {
match self.0.get(i) {
Some(Value::Int(n)) => Some(*n),
Some(tagged @ Value::IntW(..)) => tagged.untag_int(),
_ => None,
}
}
fn float(&self, i: usize) -> Option<f64> {
match self.0.get(i) {
Some(Value::Float(f)) => Some(*f),
Some(Value::F32(f)) => Some(f64::from(*f)),
Some(Value::Int(n)) => Some(AsPrimitive::<f64>::as_(*n)),
Some(tagged @ Value::IntW(..)) => tagged.untag_int().map(AsPrimitive::<f64>::as_),
_ => None,
}
}
fn pattern_chars(&self, i: usize) -> Option<Vec<char>> {
let Some(Value::Vec(items)) = self.0.get(i) else {
return None;
};
Some(
items
.lock()
.iter()
.filter_map(|v| match v {
Value::Char(c) => Some(*c),
Value::Str(text) => text.chars().next(),
_ => None,
})
.collect(),
)
}
}
fn render_template(
template: &str,
positional: &[Value],
named: &[(&str, Value)],
) -> Result<String> {
let mut out = String::with_capacity(template.len());
let mut chars = template.chars().peekable();
let mut next_pos = 0usize;
while let Some(c) = chars.next() {
match c {
'{' if chars.peek() == Some(&'{') => {
chars.next();
out.push('{');
}
'}' if chars.peek() == Some(&'}') => {
chars.next();
out.push('}');
}
'{' => {
let mut spec = String::new();
for c in chars.by_ref() {
if c == '}' {
break;
}
spec.push(c);
}
let (name, fmt) = spec.split_once(':').unwrap_or((&spec, ""));
let value = resolve_arg(name, &mut next_pos, positional, named)?;
let mut lookup = |token: &str| -> Result<i64> {
let mut pos = 0;
match resolve_arg(token, &mut pos, positional, named)? {
Value::Int(i) => Ok(i),
ref other @ Value::IntW(..) => other
.untag_int()
.ok_or_else(|| anyhow::anyhow!("format width out of range")),
other => {
bail!("format width must be an integer, got {}", other.type_name())
}
}
};
let fmt = super::format::expand_widths_with(fmt, &mut lookup)?;
let number = match &value {
Value::Float(f) => Some(super::format::SpecNumber::Float(*f)),
Value::F32(f) => Some(super::format::SpecNumber::F32(*f)),
Value::Int(i) => Some(super::format::SpecNumber::Int(*i)),
Value::IntW(v, w) => Some(super::format::SpecNumber::Sized {
value: w.decode(*v),
bits: w.bits(),
}),
_ => None,
};
out.push_str(&super::format::apply_spec(
&fmt,
&value.display(),
&value.debug(),
number,
));
}
other => out.push(other),
}
}
Ok(out)
}
fn resolve_arg(
name: &str,
next_pos: &mut usize,
positional: &[Value],
named: &[(&str, Value)],
) -> Result<Value> {
if name.is_empty() {
let i = *next_pos;
*next_pos += 1;
return positional
.get(i)
.cloned()
.ok_or_else(|| anyhow::anyhow!("format argument {i} is missing"));
}
if let Ok(i) = name.parse::<usize>() {
return positional
.get(i)
.cloned()
.ok_or_else(|| anyhow::anyhow!("format argument {i} is missing"));
}
named
.iter()
.find(|(n, _)| *n == name)
.map(|(_, v)| v.clone())
.ok_or_else(|| anyhow::anyhow!("format name `{name}` is missing"))
}