#![cfg_attr(not(feature = "full"), allow(dead_code))]
mod options;
mod tag;
#[cfg(test)]
mod drift_tests;
#[cfg(test)]
mod tests;
use std::time::Duration as StdDuration;
use crate::value::{DictMap, VmClosure, VmError, VmValue};
pub(crate) use options::Options;
#[cfg(test)]
pub(crate) use tag::tag_is_canonical;
pub(crate) use tag::Expected;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ErrorKind {
Runtime,
TypeError,
Thrown,
}
impl ErrorKind {
pub(crate) fn err(self, message: impl Into<String>) -> VmError {
match self {
Self::Runtime => VmError::Runtime(message.into()),
Self::TypeError => VmError::TypeError(message.into()),
Self::Thrown => VmError::Thrown(VmValue::string(message.into())),
}
}
}
pub(crate) fn fn_err(fn_name: &str, kind: ErrorKind, message: impl std::fmt::Display) -> VmError {
kind.err(format!("{fn_name}: {message}"))
}
pub(crate) struct ArgError;
impl ArgError {
pub(crate) fn required(fn_name: &str, kind: ErrorKind, name: &str) -> VmError {
fn_err(fn_name, kind, format_args!("`{name}` is required"))
}
pub(crate) fn wrong_type(
fn_name: &str,
kind: ErrorKind,
name: &str,
expected: Expected,
got: &VmValue,
) -> VmError {
fn_err(
fn_name,
kind,
format_args!(
"`{name}` must be {expected}, got {}",
crate::stdlib::args::describe(got)
),
)
}
pub(crate) fn wrong_type_optional(
fn_name: &str,
kind: ErrorKind,
name: &str,
expected: Expected,
got: &VmValue,
) -> VmError {
fn_err(
fn_name,
kind,
format_args!(
"`{name}` must be {expected} or nil, got {}",
crate::stdlib::args::describe(got)
),
)
}
pub(crate) fn empty(fn_name: &str, kind: ErrorKind, name: &str) -> VmError {
fn_err(fn_name, kind, format_args!("`{name}` must not be empty"))
}
pub(crate) fn not_one_of(
fn_name: &str,
kind: ErrorKind,
name: &str,
allowed: &[&str],
got: &str,
) -> VmError {
let allowed = allowed
.iter()
.map(|value| format!("`{value}`"))
.collect::<Vec<_>>()
.join(", ");
fn_err(
fn_name,
kind,
format_args!("`{name}` must be one of {allowed}; got `{got}`"),
)
}
pub(crate) fn constraint(
fn_name: &str,
kind: ErrorKind,
name: &str,
constraint: impl std::fmt::Display,
) -> VmError {
fn_err(fn_name, kind, format_args!("`{name}` {constraint}"))
}
}
fn describe(value: &VmValue) -> &'static str {
value.type_name()
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Args<'name, 'a> {
fn_name: &'name str,
values: &'a [VmValue],
kind: ErrorKind,
}
impl<'name, 'a> Args<'name, 'a> {
pub(crate) fn new(fn_name: &'name str, values: &'a [VmValue]) -> Self {
Self {
fn_name,
values,
kind: ErrorKind::TypeError,
}
}
pub(crate) fn thrown(fn_name: &'name str, values: &'a [VmValue]) -> Self {
Self {
fn_name,
values,
kind: ErrorKind::Thrown,
}
}
pub(crate) fn runtime(fn_name: &'name str, values: &'a [VmValue]) -> Self {
Self {
fn_name,
values,
kind: ErrorKind::Runtime,
}
}
pub(crate) fn runtime_options(
fn_name: &'name str,
dict: Option<&'a DictMap>,
) -> Options<'name, 'a> {
Options::new(fn_name, ErrorKind::Runtime, dict)
}
pub(crate) fn single(fn_name: &'name str, kind: ErrorKind, value: Option<&'a VmValue>) -> Self {
Self {
fn_name,
values: value.map_or(&[], std::slice::from_ref),
kind,
}
}
pub(crate) fn fn_name(&self) -> &'name str {
self.fn_name
}
pub(crate) fn kind(&self) -> ErrorKind {
self.kind
}
pub(crate) fn err(&self, message: impl std::fmt::Display) -> VmError {
fn_err(self.fn_name, self.kind, message)
}
pub(crate) fn get(&self, index: usize) -> Option<&'a VmValue> {
match self.values.get(index) {
None | Some(VmValue::Nil) => None,
Some(value) => Some(value),
}
}
pub(crate) fn raw(&self, index: usize) -> Option<&'a VmValue> {
self.values.get(index)
}
pub(crate) fn arity(&self, min: usize, max: usize) -> Result<(), VmError> {
let count = self.values.len();
if count >= min && count <= max {
return Ok(());
}
let expected = if min == max {
format!("{min}")
} else {
format!("{min}-{max}")
};
Err(self.err(format_args!("expected {expected} argument(s), got {count}")))
}
pub(crate) fn min_arity(&self, min: usize) -> Result<(), VmError> {
let count = self.values.len();
if count >= min {
return Ok(());
}
Err(self.err(format_args!(
"expected at least {min} argument(s), got {count}"
)))
}
fn required_at(&self, index: usize, name: &str) -> Result<&'a VmValue, VmError> {
self.get(index)
.ok_or_else(|| ArgError::required(self.fn_name, self.kind, name))
}
fn wrong(&self, name: &str, expected: Expected, got: &VmValue) -> VmError {
ArgError::wrong_type(self.fn_name, self.kind, name, expected, got)
}
fn wrong_optional(&self, name: &str, expected: Expected, got: &VmValue) -> VmError {
ArgError::wrong_type_optional(self.fn_name, self.kind, name, expected, got)
}
pub(crate) fn string(&self, index: usize, name: &str) -> Result<&'a str, VmError> {
match self.required_at(index, name)? {
VmValue::String(text) => Ok(text.as_str()),
other => Err(self.wrong(name, Expected::STRING, other)),
}
}
pub(crate) fn non_empty_string(&self, index: usize, name: &str) -> Result<&'a str, VmError> {
let text = self.string(index, name)?.trim();
if text.is_empty() {
return Err(ArgError::empty(self.fn_name, self.kind, name));
}
Ok(text)
}
pub(crate) fn opt_string(&self, index: usize, name: &str) -> Result<Option<&'a str>, VmError> {
match self.get(index) {
None => Ok(None),
Some(VmValue::String(text)) => Ok(Some(text.as_str())),
Some(other) => Err(self.wrong_optional(name, Expected::STRING, other)),
}
}
pub(crate) fn int(&self, index: usize, name: &str) -> Result<i64, VmError> {
match self.required_at(index, name)? {
VmValue::Int(value) => Ok(*value),
other => Err(self.wrong(name, Expected::INT, other)),
}
}
pub(crate) fn opt_int(&self, index: usize, name: &str) -> Result<Option<i64>, VmError> {
match self.get(index) {
None => Ok(None),
Some(VmValue::Int(value)) => Ok(Some(*value)),
Some(other) => Err(self.wrong_optional(name, Expected::INT, other)),
}
}
pub(crate) fn whole_int(&self, index: usize, name: &str) -> Result<i64, VmError> {
match self.required_at(index, name)? {
VmValue::Int(value) => Ok(*value),
VmValue::Float(value) if value.fract() == 0.0 => Ok(*value as i64),
other => Err(self.wrong(name, Expected::INT_OR_FLOAT, other)),
}
}
pub(crate) fn enum_string(
&self,
index: usize,
name: &str,
allowed: &[&str],
) -> Result<&'a str, VmError> {
let text = self.string(index, name)?;
if allowed.contains(&text) {
return Ok(text);
}
Err(ArgError::not_one_of(
self.fn_name,
self.kind,
name,
allowed,
text,
))
}
pub(crate) fn bool(&self, index: usize, name: &str) -> Result<bool, VmError> {
match self.required_at(index, name)? {
VmValue::Bool(value) => Ok(*value),
other => Err(self.wrong(name, Expected::BOOL, other)),
}
}
pub(crate) fn opt_bool(&self, index: usize, name: &str) -> Result<Option<bool>, VmError> {
match self.get(index) {
None => Ok(None),
Some(VmValue::Bool(value)) => Ok(Some(*value)),
Some(other) => Err(self.wrong_optional(name, Expected::BOOL, other)),
}
}
pub(crate) fn bool_or(&self, index: usize, name: &str, default: bool) -> Result<bool, VmError> {
Ok(self.opt_bool(index, name)?.unwrap_or(default))
}
pub(crate) fn float(&self, index: usize, name: &str) -> Result<f64, VmError> {
match self.required_at(index, name)? {
VmValue::Float(value) => Ok(*value),
other => Err(self.wrong(name, Expected::FLOAT, other)),
}
}
pub(crate) fn dict(&self, index: usize, name: &str) -> Result<&'a DictMap, VmError> {
match self.required_at(index, name)? {
VmValue::Dict(dict) => Ok(dict.as_ref()),
other => Err(self.wrong(name, Expected::DICT, other)),
}
}
pub(crate) fn opt_dict(
&self,
index: usize,
name: &str,
) -> Result<Option<&'a DictMap>, VmError> {
match self.get(index) {
None => Ok(None),
Some(VmValue::Dict(dict)) => Ok(Some(dict.as_ref())),
Some(other) => Err(self.wrong_optional(name, Expected::DICT, other)),
}
}
pub(crate) fn list(&self, index: usize, name: &str) -> Result<&'a [VmValue], VmError> {
match self.required_at(index, name)? {
VmValue::List(list) => Ok(list.as_slice()),
other => Err(self.wrong(name, Expected::LIST, other)),
}
}
pub(crate) fn list_shared(
&self,
index: usize,
name: &str,
) -> Result<&'a std::sync::Arc<Vec<VmValue>>, VmError> {
match self.required_at(index, name)? {
VmValue::List(list) => Ok(list),
other => Err(self.wrong(name, Expected::LIST, other)),
}
}
pub(crate) fn opt_list(
&self,
index: usize,
name: &str,
) -> Result<Option<&'a [VmValue]>, VmError> {
match self.get(index) {
None => Ok(None),
Some(VmValue::List(list)) => Ok(Some(list.as_slice())),
Some(other) => Err(self.wrong_optional(name, Expected::LIST, other)),
}
}
pub(crate) fn string_list(&self, index: usize, name: &str) -> Result<Vec<&'a str>, VmError> {
self.collect_string_list(self.list(index, name)?, name)
}
pub(crate) fn opt_string_list(
&self,
index: usize,
name: &str,
) -> Result<Option<Vec<&'a str>>, VmError> {
let Some(list) = self.opt_list(index, name)? else {
return Ok(None);
};
self.collect_string_list(list, name).map(Some)
}
fn collect_string_list(
&self,
list: &'a [VmValue],
name: &str,
) -> Result<Vec<&'a str>, VmError> {
list.iter()
.map(|value| match value {
VmValue::String(text) => Ok(text.as_str()),
other => Err(self.wrong(name, Expected::STRING_LIST, other)),
})
.collect()
}
pub(crate) fn bytes(&self, index: usize, name: &str) -> Result<&'a [u8], VmError> {
match self.required_at(index, name)? {
VmValue::Bytes(bytes) => Ok(bytes.as_slice()),
other => Err(self.wrong(name, Expected::BYTES, other)),
}
}
pub(crate) fn bytes_or_string(&self, index: usize, name: &str) -> Result<&'a [u8], VmError> {
match self.required_at(index, name)? {
VmValue::Bytes(bytes) => Ok(bytes.as_slice()),
VmValue::String(text) => Ok(text.as_bytes()),
other => Err(self.wrong(name, Expected::BYTES_OR_STRING, other)),
}
}
pub(crate) fn closure(&self, index: usize, name: &str) -> Result<&'a VmClosure, VmError> {
match self.required_at(index, name)? {
VmValue::Closure(closure) => Ok(closure.as_ref()),
other => Err(self.wrong(name, Expected::CLOSURE, other)),
}
}
pub(crate) fn millis(&self, index: usize, name: &str) -> Result<u64, VmError> {
let value = self.required_at(index, name)?;
self.millis_from(value, name)
}
pub(crate) fn duration(&self, index: usize, name: &str) -> Result<StdDuration, VmError> {
self.millis(index, name).map(StdDuration::from_millis)
}
fn millis_from(&self, value: &VmValue, name: &str) -> Result<u64, VmError> {
match value {
VmValue::Duration(millis) | VmValue::Int(millis) if *millis >= 0 => Ok(*millis as u64),
VmValue::Duration(_) | VmValue::Int(_) => Err(ArgError::constraint(
self.fn_name,
self.kind,
name,
"must be >= 0",
)),
VmValue::Float(millis)
if millis.is_finite() && *millis >= 0.0 && *millis <= u64::MAX as f64 =>
{
Ok(*millis as u64)
}
VmValue::Float(_) => Err(ArgError::constraint(
self.fn_name,
self.kind,
name,
"must be a finite millisecond count >= 0",
)),
other => Err(self.wrong(name, Expected::DURATION_OR_INT, other)),
}
}
pub(crate) fn json_object(
&self,
index: usize,
name: &str,
) -> Result<serde_json::Map<String, serde_json::Value>, VmError> {
let Some(value) = self.get(index) else {
return Ok(serde_json::Map::new());
};
match value {
VmValue::Dict(_) => match crate::llm::helpers::vm_value_to_json(value) {
serde_json::Value::Object(map) => Ok(map),
other => unreachable!("a dict converts to a JSON object, got {other:?}"),
},
other => Err(self.wrong_optional(name, Expected::DICT, other)),
}
}
pub(crate) fn options(&self, index: usize, name: &str) -> Result<Options<'name, 'a>, VmError> {
Ok(Options::new(
self.fn_name,
self.kind,
self.opt_dict(index, name)?,
))
}
}