use num_traits::AsPrimitive;
use std::f64::consts::{E, PI, TAU};
use std::mem::take;
use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use parking_lot::Mutex;
use super::bytecode::Chunk;
use super::bytecode::{BuiltinId, MethodName, PathId, PathRef};
use super::enum_def::EnumKind;
use super::methods::{self};
use super::native::Native;
use super::shared::Args;
use super::value::{ClosureData, Map, MapKey, Value};
use super::vm::Vm;
impl Vm {
pub(super) fn render_fmt(
self: &Arc<Self>,
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(self, &f.template, &positional, &named)
}
pub(super) fn user_fmt_text(
self: &Arc<Self>,
v: &Value,
debug: bool,
) -> Result<Option<String>> {
Ok(self.user_fmt(v, debug)?.map(|(text, _)| text))
}
pub(super) fn user_fmt(
self: &Arc<Self>,
v: &Value,
debug: bool,
) -> Result<Option<(String, bool)>> {
let Some(methods) = self.impls.of_value(v) else {
return Ok(None);
};
let Some(chunk) = (if debug {
&methods.debug
} else {
&methods.display
})
.clone() else {
return Ok(None);
};
let handle = Arc::new(parking_lot::Mutex::new(Native::Fmt {
text: String::new(),
padded: false,
}));
let args = vec![v.clone(), Value::Native(handle.clone())];
self.run_chunk(&chunk, &args, &[])?;
let out = match &*handle.lock() {
Native::Fmt { text, padded } => (text.clone(), *padded),
_ => (String::new(), false),
};
Ok(Some(out))
}
pub(super) fn run_user_drop(self: &Arc<Self>, value: Value) -> Result<()> {
match value {
Value::Struct(s) => {
self.run_drop_impl(Value::Struct(s.clone()))?;
let fields = take(&mut *s.values.lock());
for field in fields {
self.run_user_drop(field)?;
}
Ok(())
}
Value::Enum { def, variant, data } => {
self.run_drop_impl(Value::Enum {
def,
variant,
data: data.clone(),
})?;
let payload = take(&mut *data.lock());
for field in payload {
self.run_user_drop(field)?;
}
Ok(())
}
Value::Vec(list) | Value::Tuple(list) => {
let items = take(&mut *list.lock());
for item in items {
self.run_user_drop(item)?;
}
Ok(())
}
Value::Map(map, _) => {
let entries = take(&mut *map.lock());
for (_, entry) in entries {
self.run_user_drop(entry)?;
}
Ok(())
}
Value::Cell(kind, slot) => {
if kind.is_shared_pointer() && Arc::strong_count(&slot) != 1 {
return Ok(());
}
let inner = take(&mut *slot.lock());
self.run_user_drop(inner)
}
Value::Native(handle) => {
let leftover = match &mut *handle.lock() {
Native::Iterator(state) => state.take_remaining(),
_ => Vec::new(),
};
for item in leftover {
self.run_user_drop(item)?;
}
Ok(())
}
_ => Ok(()),
}
}
fn run_drop_impl(self: &Arc<Self>, value: Value) -> Result<()> {
let Some(chunk) = self
.impls
.of_value(&value)
.and_then(|methods| methods.drop.clone())
else {
return Ok(());
};
self.run_chunk(&chunk, &[value], &[])?;
Ok(())
}
pub(super) fn eval_path_value(&self, path: &PathRef) -> Result<Value> {
if path.id == PathId::Other {
return self.user_path_value(path);
}
if let Some(v) = path_constant(path.id) {
return Ok(v);
}
let arity = usize::from(!matches!(path.id.name(), "new" | "default"));
Ok(path_closure(path.clone(), arity))
}
fn user_path_value(&self, path: &PathRef) -> Result<Value> {
let segs = &path.segs;
let Some(last) = segs.last().map(String::as_str) else {
bail!("empty path");
};
if segs.len() >= 2 {
let ty = segs[segs.len() - 2].as_str();
if let Some(v) = self.unit_variant(Some(ty), last) {
return Ok(v);
}
} else {
if let Some(v) = self.unit_variant(None, last) {
return Ok(v);
}
if let Some(name) = self
.unit_structs
.iter()
.find(|name| &***name == last || super::resolver::bare(name) == last)
{
let type_id = self.impls.type_id(name);
return Ok(Value::structure(
super::value::StructShape::typed(
&**name,
type_id,
Vec::new(),
Vec::new(),
Vec::new(),
),
Vec::new(),
));
}
if let Some(chunk) = self.user_function(last) {
return Ok(path_closure(path.clone(), chunk.num_params));
}
}
if matches!(last, "new" | "default") {
return Ok(path_closure(path.clone(), 0));
}
if segs.len() >= 2
&& let Some(chunk) = self
.user_method(&segs[segs.len() - 2], last)
.or_else(|| self.user_function(&path.display()))
.or_else(|| self.user_function(last))
{
return Ok(path_closure(path.clone(), chunk.num_params));
}
if last.chars().any(|c| c.is_ascii_uppercase())
&& !last.chars().any(|c| c.is_ascii_lowercase())
{
bail!("unsupported constant `{}`", path.display());
}
Ok(path_closure(path.clone(), 1))
}
pub(super) fn dispatch_call(
self: &Arc<Self>,
path: &PathRef,
mut args: Vec<Value>,
) -> Result<Value> {
if path.id == PathId::Other {
return self.dispatch_user_call(path, args);
}
for arg in &mut args {
if let Some(image) = arg.bridge_image() {
*arg = image;
}
}
match path.id {
PathId::Other => return self.dispatch_user_call(path, args),
PathId::Some => return Ok(Value::some(one(args)?)),
PathId::Ok => return Ok(Value::ok(one(args)?)),
PathId::Err => return Ok(Value::err(one(args)?)),
PathId::Drop => {
self.run_user_drop(one(args)?)?;
return Ok(Value::Unit);
}
PathId::CtrlcSetHandler => {
let closure = arg(&args, 0)?;
return Ok(match super::set_ctrlc_handler(closure) {
Ok(()) => Value::ok(Value::Unit),
Err(e) => Value::err(Value::str(e.to_string())),
});
}
PathId::ThreadSleep => {
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);
}
PathId::TokioSyncMutexNew => {
let inner = one(args)?;
return Ok(super::cell::make_cell(
super::value::CellKind::TokioMutex,
inner,
));
}
PathId::ValueString
| PathId::ValueBool
| PathId::ValueNumber
| PathId::ValueArray
| PathId::ValueObject => return one(args),
_ => {}
}
let images: Vec<Value> = args
.iter()
.map(|arg| match arg.bridge_image() {
Some(image) => image,
None => arg.clone(),
})
.collect();
match bridge_call(path.id, &images)? {
Some(v) => Ok(v),
None => bail!("unsupported call `{}`", path.display()),
}
}
fn dispatch_user_call(self: &Arc<Self>, path: &PathRef, args: Vec<Value>) -> Result<Value> {
let [.., namespace, last] = path.segs.as_slice() else {
let name = path.segs.first().map_or("", String::as_str);
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}`");
};
if namespace == "thread" {
bail!("std::thread is not supported beyond sleep, use tokio::spawn");
}
if let Some(chunk) = self.user_function(&path.display()) {
return self.run_chunk(&chunk, &args, &[]);
}
if last == "from"
&& args.len() == 1
&& let Some(chunk) = self.conversion_impl(namespace, &args[0])
{
return self.run_chunk(&chunk, &args, &[]);
}
if let Some(chunk) = self.user_method(namespace, last) {
return self.run_chunk(&chunk, &args, &[]);
}
if let Some(v) = self.make_tuple_variant(Some(namespace), last, &args) {
return Ok(v);
}
if let Some((recv, rest)) = args.split_first() {
let recv = recv.clone();
let mut rest = rest.to_vec();
let name = self.impls.method_name(last);
return self.eval_method(&recv, &name, &mut rest);
}
bail!("unsupported call `{}`", path.display())
}
pub(super) fn eval_method(
self: &Arc<Self>,
recv: &Value,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
let dereferenced = match recv {
Value::Ref(reference) => match deref_receiver(reference, name, args)? {
RefRead::Value(value) => Some(value),
RefRead::StrGrown => return Ok(Value::Unit),
},
_ => None,
};
let recv = dereferenced.as_ref().unwrap_or(recv);
if let Value::Cell(kind, slot) = recv {
if let Some(v) = super::cell::cell_method(*kind, slot, name.id, args)? {
return Ok(v);
}
let inner = slot.lock().clone();
return self.eval_method(&inner, name, args);
}
if let Some(v) = self.user_impl_method(recv, name, args)? {
return Ok(v);
}
if let Some(v) = self.any_receiver_method(recv, name, args)? {
return Ok(v);
}
if let Some(result) = int_method(recv, name, args) {
return result;
}
if let Value::F32(f) = recv
&& let Some(value) = f32_method(*f, name.id, args)?
{
return Ok(value);
}
let widened;
let recv = match recv.bridge_image() {
Some(image) => {
widened = image;
&widened
}
None => recv,
};
image_args(recv, name, args)?;
let expanded;
let recv = match recv {
Value::Range { .. } => {
if let Some(v) = range_builtin(recv, name, args)? {
return Ok(v);
}
expanded = self.iterator_value(recv.clone())?;
&expanded
}
_ if self.has_user_next(recv) => {
expanded = self.iterator_value(recv.clone())?;
&expanded
}
_ => recv,
};
if name.id.is_higher_order()
&& let Some(v) = self.higher_order(recv, name.id, &*args)?
{
return Ok(v);
}
self.method_by_receiver(recv, name, args)
}
fn user_impl_method(
self: &Arc<Self>,
recv: &Value,
name: &MethodName,
args: &[Value],
) -> Result<Option<Value>> {
let Some(chunk) = self
.impls
.of_receiver(recv, name.scalar.as_ref())
.and_then(|methods| methods.get(name))
else {
return Ok(None);
};
let chunk = chunk.clone();
let mut full = Vec::with_capacity(args.len() + 1);
full.push(recv.clone());
full.extend(args.iter().cloned());
self.run_chunk(&chunk, &full, &[]).map(Some)
}
fn any_receiver_method(
self: &Arc<Self>,
recv: &Value,
name: &MethodName,
args: &[Value],
) -> Result<Option<Value>> {
let tagged = matches!(recv, Value::IntW(..) | Value::F32(_));
Ok(match name.id {
BuiltinId::ToString => match self.user_fmt_text(recv, false)? {
Some(text) => Some(Value::str(text)),
None if tagged => Some(Value::str(recv.display())),
None => None,
},
BuiltinId::Clone if tagged => Some(recv.clone()),
_ => methods::json_type_test(recv, name)
.or_else(|| methods::json_value_method(recv, name, args)),
})
}
fn method_by_receiver(
self: &Arc<Self>,
recv: &Value,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
match recv {
Value::Str(s) => methods::str_method(s, name, args),
Value::Vec(v) => {
if matches!(name.id, BuiltinId::Extend | BuiltinId::ExtendFromSlice)
&& let Some(first) = args.first()
&& !matches!(first, Value::Vec(_))
{
let items = self.drain_items(first.clone())?;
args[0] = Value::vec(items);
}
super::vecmap::vec_method(v, name, args)
}
Value::Map(map, kind) => super::vecmap::map_method(map, *kind, name, args),
Value::Enum { def, .. } if def.kind == EnumKind::Option => {
methods::opt_method(recv, name, args)
}
Value::Enum { def, .. } if def.kind == EnumKind::Result => {
methods::res_method(recv, name, args)
}
Value::Enum { .. } => methods::generic_method(recv, name, args),
Value::Struct(st) => {
if let Some(res) = super::http::http_method(recv, name, args) {
return res;
}
if super::ratatui::is_ratatui_struct(st.name()) {
return super::ratatui::struct_method(st, name, args);
}
Self::bridge_struct_method(recv, st, name, args)
}
Value::Native(native) => {
let family = match &*native.lock() {
Native::Iterator(_) => NativeFamily::Iterator,
Native::HttpClient(_) | Native::BlockingHttpClient(_) => NativeFamily::Http,
_ => NativeFamily::Other,
};
match family {
NativeFamily::Iterator => {
if let Some(v) = self.iterator_method(native, name, args)? {
return Ok(v);
}
}
NativeFamily::Http => {
if let Some(res) = super::http::http_method(recv, name, args) {
return res;
}
}
NativeFamily::Other | NativeFamily::Entry(..) | NativeFamily::Regex => {}
}
Self::native_method(native, name, args)
}
Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Char(_) => {
scalar_method(recv, name, args)
}
other => methods::generic_method(other, name, args),
}
}
fn bridge_struct_method(
recv: &Value,
st: &Arc<super::value::StructData>,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
match &**st.name() {
"Command" => super::process::command_method(recv, name, args),
"Child" => super::process::child_method(recv, name, args),
"ExitStatus" => exitstatus_method(st, name),
"Output" => output_method(st, name),
"Duration" => duration_method(st, name, args),
"DateTime" => datetime_method(st, name, args),
"Path" | "PathBuf" => super::std_bridge::path_method(st, name, args),
"OsString" => super::std_bridge::os_string_method(st, name),
"DirEntry" => super::std_bridge::dir_entry_method(st, name),
"FileType" => super::std_bridge::file_type_method(st, name),
"Metadata" => super::std_bridge::metadata_method(st, name),
"StdStream" => super::std_bridge::std_stream_method(st, name, args),
"OpenOptions" => super::std_bridge::openoptions_method(st, name, args),
"Permissions" => match name.id {
BuiltinId::Mode => Ok(st.get("mode").unwrap_or(Value::Int(0))),
BuiltinId::Readonly => Ok(st.get("readonly").unwrap_or(Value::Bool(false))),
BuiltinId::SetReadonly => Ok(Value::Unit),
_ => bail!("unknown method `{name}` on Permissions"),
},
"Rng" => super::crates_bridge::rng_method(name, args),
"Base64Engine" => super::crates_bridge::base64_method(st, name, args),
"Element" => super::xmltree_bridge::element_method(st, name, args),
"RegKey" => super::winreg_bridge::winreg_method(st, name, args),
"ServiceManager" => super::service_bridge::manager_method(st, name, args),
"Service" => super::service_bridge::service_method(st, name, args),
"WmiConnection" => super::wmi_bridge::wmi_method(st, name, args),
_ => methods::generic_method(recv, name, args),
}
}
fn native_method(
native: &Arc<Mutex<Native>>,
name: &MethodName,
args: &mut [Value],
) -> Result<Value> {
let family = match &*native.lock() {
Native::Entry { map, key } => NativeFamily::Entry(map.clone(), key.clone()),
Native::Instant(instant) if name.id == BuiltinId::Elapsed => {
return Ok(super::std_bridge::make_duration(instant.elapsed()));
}
Native::Regex(_) | Native::RegexMatch(_) | Native::RegexCaptures(_) => {
NativeFamily::Regex
}
_ => NativeFamily::Other,
};
match family {
NativeFamily::Entry(map, key) => {
return methods::entry_method(&map, &key, name, args);
}
NativeFamily::Regex => {
if let Some(v) = super::regex_bridge::regex_native_method(native, name, args)? {
return Ok(v);
}
}
NativeFamily::Other => {
if let Some(v) = super::native_methods::native_method(native, name, args)? {
return Ok(v);
}
}
NativeFamily::Iterator | NativeFamily::Http => {
unreachable!("picked in method_by_receiver")
}
}
methods::generic_method(&Value::Native(native.clone()), name, args)
}
}
enum NativeFamily {
Iterator,
Http,
Entry(Map, MapKey),
Regex,
Other,
}
fn path_constant(id: PathId) -> Option<Value> {
let text = match id {
PathId::UnixEpoch => return Some(Native::SystemTime(std::time::UNIX_EPOCH).wrap()),
PathId::ValueNull => return Some(Value::none()),
PathId::ConstsPi => return Some(Value::Float(PI)),
PathId::ConstsTau => return Some(Value::Float(TAU)),
PathId::ConstsE => return Some(Value::Float(E)),
PathId::ConstsOs => std::env::consts::OS,
PathId::ConstsArch => std::env::consts::ARCH,
PathId::ConstsFamily => std::env::consts::FAMILY,
PathId::ConstsExeExtension => std::env::consts::EXE_EXTENSION,
PathId::ConstsExeSuffix => std::env::consts::EXE_SUFFIX,
_ => {
return numeric_limit(id)
.or_else(|| super::crates_bridge::base64_engine(id))
.or_else(|| super::winreg_bridge::winreg_const(id))
.or_else(|| super::service_bridge::service_const(id))
.or_else(|| super::ratatui::ratatui_const(id));
}
};
Some(Value::str(text))
}
fn image_args(recv: &Value, name: &MethodName, args: &mut [Value]) -> Result<()> {
if name.id == BuiltinId::Repeat
&& let Some(count) = args.first()
&& count
.int_parts()
.is_some_and(|(n, _)| n > i128::from(i64::MAX))
{
let empty = match recv {
Value::Str(s) => s.is_empty(),
Value::Vec(v) => v.lock().is_empty(),
_ => false,
};
if !empty {
bail!("capacity overflow");
}
}
let hands_args_through = matches!(
recv,
Value::Enum { .. } | Value::Bool(_) | Value::Vec(_) | Value::Map(..)
) || matches!(recv, Value::Native(n) if matches!(&*n.lock(), Native::Entry { .. }))
|| name.id == BuiltinId::Fold;
if hands_args_through {
return Ok(());
}
for arg in args.iter_mut() {
if let Some(image) = arg.bridge_image() {
*arg = image;
}
}
Ok(())
}
fn one(args: Vec<Value>) -> Result<Value> {
args.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("expected one argument"))
}
pub(super) fn arg(args: &[Value], i: usize) -> Result<Value> {
args.get(i)
.cloned()
.ok_or_else(|| anyhow!("missing argument {}", i + 1))
}
fn path_closure(path: PathRef, num_params: usize) -> Value {
Value::Closure(Arc::new(ClosureData {
chunk: super::bytecode::path_call_chunk(path, 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(),
)
}
}
enum RefRead {
Value(Value),
StrGrown,
}
fn deref_receiver(
reference: &super::value::ValueRef,
name: &MethodName,
args: &[Value],
) -> Result<RefRead> {
let Some(value) = reference.get() else {
bail!("method call through a dangling reference");
};
if let Value::Str(s) = &value
&& matches!(name.id, BuiltinId::Push | BuiltinId::PushStr)
{
let mut grown = s.clone();
methods::str_grow(&mut grown, name.id, &arg(args, 0)?)?;
reference.set(Value::Str(grown));
return Ok(RefRead::StrGrown);
}
if matches!(value, Value::Str(_)) && name.id == BuiltinId::Clear && args.is_empty() {
reference.set(Value::str(String::new()));
return Ok(RefRead::StrGrown);
}
if matches!(
name.id,
BuiltinId::MakeAsciiUppercase | BuiltinId::MakeAsciiLowercase
) {
let upper = name.id == BuiltinId::MakeAsciiUppercase;
let cased = match &value {
Value::Str(s) => Some(Value::str(if upper {
s.to_ascii_uppercase()
} else {
s.to_ascii_lowercase()
})),
Value::Char(c) => Some(Value::Char(if upper {
c.to_ascii_uppercase()
} else {
c.to_ascii_lowercase()
})),
_ => None,
};
if let Some(cased) = cased {
reference.set(cased);
return Ok(RefRead::StrGrown);
}
}
Ok(RefRead::Value(value))
}
mod path_calls;
mod scalar_dispatch;
pub(in crate::interpreter) use scalar_dispatch::int_method;
mod template;
use path_calls::{
bridge_call, datetime_method, duration_method, exitstatus_method, numeric_limit, output_method,
range_builtin,
};
use scalar_dispatch::{f32_method, scalar_method};
use template::render_template;