diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 43 | ||||
| -rw-r--r-- | src/eval/array.rs | 95 | ||||
| -rw-r--r-- | src/eval/auto.rs | 39 | ||||
| -rw-r--r-- | src/eval/cast.rs | 367 | ||||
| -rw-r--r-- | src/eval/datetime.rs | 6 | ||||
| -rw-r--r-- | src/eval/dict.rs | 19 | ||||
| -rw-r--r-- | src/eval/func.rs | 36 | ||||
| -rw-r--r-- | src/eval/int.rs | 81 | ||||
| -rw-r--r-- | src/eval/methods.rs | 153 | ||||
| -rw-r--r-- | src/eval/mod.rs | 71 | ||||
| -rw-r--r-- | src/eval/none.rs | 74 | ||||
| -rw-r--r-- | src/eval/ops.rs | 4 | ||||
| -rw-r--r-- | src/eval/scope.rs | 15 | ||||
| -rw-r--r-- | src/eval/str.rs | 80 | ||||
| -rw-r--r-- | src/eval/symbol.rs | 3 | ||||
| -rw-r--r-- | src/eval/value.rs | 63 |
16 files changed, 658 insertions, 491 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs index bea2baa1..da29eeaf 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter}; use ecow::{eco_format, EcoVec}; -use super::{Array, Cast, Dict, Str, Value}; +use super::{Array, Dict, FromValue, IntoValue, Str, Value}; use crate::diag::{bail, At, SourceResult}; use crate::syntax::{Span, Spanned}; use crate::util::pretty_array_like; @@ -29,10 +29,14 @@ pub struct Arg { impl Args { /// Create positional arguments from a span and values. - pub fn new(span: Span, values: impl IntoIterator<Item = Value>) -> Self { + pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self { let items = values .into_iter() - .map(|value| Arg { span, name: None, value: Spanned::new(value, span) }) + .map(|value| Arg { + span, + name: None, + value: Spanned::new(value.into_value(), span), + }) .collect(); Self { span, items } } @@ -49,13 +53,13 @@ impl Args { /// Consume and cast the first positional argument if there is one. pub fn eat<T>(&mut self) -> SourceResult<Option<T>> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { for (i, slot) in self.items.iter().enumerate() { if slot.name.is_none() { let value = self.items.remove(i).value; let span = value.span; - return T::cast(value).at(span).map(Some); + return T::from_value(value).at(span).map(Some); } } Ok(None) @@ -87,24 +91,24 @@ impl Args { /// left. pub fn expect<T>(&mut self, what: &str) -> SourceResult<T> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { match self.eat()? { Some(v) => Ok(v), - None => bail!(self.span, "missing argument: {}", what), + None => bail!(self.span, "missing argument: {what}"), } } /// Find and consume the first castable positional argument. pub fn find<T>(&mut self) -> SourceResult<Option<T>> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { + if slot.name.is_none() && T::castable(&slot.value.v) { let value = self.items.remove(i).value; let span = value.span; - return T::cast(value).at(span).map(Some); + return T::from_value(value).at(span).map(Some); } } Ok(None) @@ -113,7 +117,7 @@ impl Args { /// Find and consume all castable positional arguments. pub fn all<T>(&mut self) -> SourceResult<Vec<T>> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { let mut list = vec![]; while let Some(value) = self.find()? { @@ -126,7 +130,7 @@ impl Args { /// error if the conversion fails. pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { // We don't quit once we have a match because when multiple matches // exist, we want to remove all of them and use the last one. @@ -136,7 +140,7 @@ impl Args { if self.items[i].name.as_deref() == Some(name) { let value = self.items.remove(i).value; let span = value.span; - found = Some(T::cast(value).at(span)?); + found = Some(T::from_value(value).at(span)?); } else { i += 1; } @@ -147,7 +151,7 @@ impl Args { /// Same as named, but with fallback to find. pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>> where - T: Cast<Spanned<Value>>, + T: FromValue<Spanned<Value>>, { match self.named(name)? { Some(value) => Ok(Some(value)), @@ -167,13 +171,10 @@ impl Args { /// argument. pub fn finish(self) -> SourceResult<()> { if let Some(arg) = self.items.first() { - bail!( - arg.span, - match &arg.name { - Some(name) => eco_format!("unexpected argument: {}", name), - _ => eco_format!("unexpected argument"), - } - ) + match &arg.name { + Some(name) => bail!(arg.span, "unexpected argument: {name}"), + _ => bail!(arg.span, "unexpected argument"), + } } Ok(()) } diff --git a/src/eval/array.rs b/src/eval/array.rs index d0b63b35..a7a1387b 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign}; use ecow::{eco_format, EcoString, EcoVec}; -use super::{ops, Args, Func, Value, Vm}; +use super::{ops, Args, CastInfo, FromValue, Func, IntoValue, Reflect, Value, Vm}; use crate::diag::{At, SourceResult, StrResult}; use crate::syntax::Span; use crate::util::pretty_array_like; @@ -14,11 +14,16 @@ use crate::util::pretty_array_like; #[doc(hidden)] macro_rules! __array { ($value:expr; $count:expr) => { - $crate::eval::Array::from_vec($crate::eval::eco_vec![$value.into(); $count]) + $crate::eval::Array::from($crate::eval::eco_vec![ + $crate::eval::IntoValue::into_value($value); + $count + ]) }; ($($value:expr),* $(,)?) => { - $crate::eval::Array::from_vec($crate::eval::eco_vec![$($value.into()),*]) + $crate::eval::Array::from($crate::eval::eco_vec![$( + $crate::eval::IntoValue::into_value($value) + ),*]) }; } @@ -38,19 +43,14 @@ impl Array { Self::default() } - /// Create a new array from an eco vector of values. - pub fn from_vec(vec: EcoVec<Value>) -> Self { - Self(vec) - } - /// Return `true` if the length is 0. pub fn is_empty(&self) -> bool { self.0.len() == 0 } /// The length of the array. - pub fn len(&self) -> i64 { - self.0.len() as i64 + pub fn len(&self) -> usize { + self.0.len() } /// The first value in the array. @@ -134,14 +134,14 @@ impl Array { .filter(|&start| start <= self.0.len()) .ok_or_else(|| out_of_bounds(start, len))?; - let end = end.unwrap_or(self.len()); + let end = end.unwrap_or(self.len() as i64); let end = self .locate(end) .filter(|&end| end <= self.0.len()) .ok_or_else(|| out_of_bounds(end, len))? .max(start); - Ok(Self::from_vec(self.0[start..end].into())) + Ok(self.0[start..end].into()) } /// Whether the array contains a specific value. @@ -182,7 +182,7 @@ impl Array { kept.push(item.clone()) } } - Ok(Self::from_vec(kept)) + Ok(kept.into()) } /// Transform each item in the array with a function. @@ -273,7 +273,7 @@ impl Array { flat.push(item.clone()); } } - Self::from_vec(flat) + flat.into() } /// Returns a new array with reversed order. @@ -317,9 +317,7 @@ impl Array { pub fn zip(&self, other: Array) -> Array { self.iter() .zip(other) - .map(|(first, second)| { - Value::Array(Array::from_vec(eco_vec![first.clone(), second])) - }) + .map(|(first, second)| array![first.clone(), second].into_value()) .collect() } @@ -360,7 +358,7 @@ impl Array { } } }); - result.map(|_| Self::from_vec(vec)) + result.map(|_| vec.into()) } /// Repeat this array `n` times. @@ -385,19 +383,20 @@ impl Array { /// Resolve an index. fn locate(&self, index: i64) -> Option<usize> { - usize::try_from(if index >= 0 { index } else { self.len().checked_add(index)? }) - .ok() + usize::try_from(if index >= 0 { + index + } else { + (self.len() as i64).checked_add(index)? + }) + .ok() } /// Enumerate all items in the array. pub fn enumerate(&self) -> Self { - let v = self - .iter() + self.iter() .enumerate() - .map(|(i, value)| array![i, value.clone()]) - .map(Value::Array) - .collect(); - Self::from_vec(v) + .map(|(i, value)| array![i, value.clone()].into_value()) + .collect() } } @@ -453,6 +452,40 @@ impl<'a> IntoIterator for &'a Array { } } +impl From<EcoVec<Value>> for Array { + fn from(v: EcoVec<Value>) -> Self { + Array(v) + } +} + +impl From<&[Value]> for Array { + fn from(v: &[Value]) -> Self { + Array(v.into()) + } +} + +impl<T> Reflect for Vec<T> { + fn describe() -> CastInfo { + Array::describe() + } + + fn castable(value: &Value) -> bool { + Array::castable(value) + } +} + +impl<T: IntoValue> IntoValue for Vec<T> { + fn into_value(self) -> Value { + Value::Array(self.into_iter().map(IntoValue::into_value).collect()) + } +} + +impl<T: FromValue> FromValue for Vec<T> { + fn from_value(value: Value) -> StrResult<Self> { + value.cast::<Array>()?.into_iter().map(Value::cast).collect() + } +} + /// The error message when the array is empty. #[cold] fn array_is_empty() -> EcoString { @@ -461,17 +494,15 @@ fn array_is_empty() -> EcoString { /// The out of bounds access error message. #[cold] -fn out_of_bounds(index: i64, len: i64) -> EcoString { - eco_format!("array index out of bounds (index: {}, len: {})", index, len) +fn out_of_bounds(index: i64, len: usize) -> EcoString { + eco_format!("array index out of bounds (index: {index}, len: {len})") } /// The out of bounds access error message when no default value was given. #[cold] -fn out_of_bounds_no_default(index: i64, len: i64) -> EcoString { +fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString { eco_format!( - "array index out of bounds (index: {}, len: {}) \ + "array index out of bounds (index: {index}, len: {len}) \ and no default value was specified", - index, - len ) } diff --git a/src/eval/auto.rs b/src/eval/auto.rs new file mode 100644 index 00000000..e73b3f33 --- /dev/null +++ b/src/eval/auto.rs @@ -0,0 +1,39 @@ +use std::fmt::{self, Debug, Formatter}; + +use super::{CastInfo, FromValue, IntoValue, Reflect, Value}; +use crate::diag::StrResult; + +/// A value that indicates a smart default. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct AutoValue; + +impl IntoValue for AutoValue { + fn into_value(self) -> Value { + Value::Auto + } +} + +impl FromValue for AutoValue { + fn from_value(value: Value) -> StrResult<Self> { + match value { + Value::Auto => Ok(Self), + _ => Err(Self::error(&value)), + } + } +} + +impl Reflect for AutoValue { + fn describe() -> CastInfo { + CastInfo::Type("auto") + } + + fn castable(value: &Value) -> bool { + matches!(value, Value::Auto) + } +} + +impl Debug for AutoValue { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("auto") + } +} diff --git a/src/eval/cast.rs b/src/eval/cast.rs index 7ef2f1d0..29cf5f71 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -1,278 +1,185 @@ -pub use typst_macros::{cast_from_value, cast_to_value, Cast}; +pub use typst_macros::{cast, Cast}; -use std::num::{NonZeroI64, NonZeroU64, NonZeroUsize}; +use std::fmt::Write; use std::ops::Add; use ecow::EcoString; -use super::{Array, Str, Value}; -use crate::diag::StrResult; -use crate::eval::Type; -use crate::geom::Length; -use crate::syntax::Spanned; +use super::Value; +use crate::diag::{At, SourceResult, StrResult}; +use crate::syntax::{Span, Spanned}; use crate::util::separated_list; -/// Cast from a value to a specific type. -pub trait Cast<V = Value>: Sized { - /// Check whether the value is castable to `Self`. - fn is(value: &V) -> bool; - - /// Try to cast the value into an instance of `Self`. - fn cast(value: V) -> StrResult<Self>; - - /// Describe the acceptable values. +/// Determine details of a type. +/// +/// Type casting works as follows: +/// - [`Reflect for T`](Reflect) describes the possible Typst values for `T` +/// (for documentation and autocomplete). +/// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value` +/// (infallible) +/// - [`FromValue for T`](FromValue) is for conversion from `Value -> T` +/// (fallible). +/// +/// We can't use `TryFrom<Value>` due to conflicting impls. We could use +/// `From<T> for Value`, but that inverses the impl and leads to tons of +/// `.into()` all over the place that become hard to decipher. +pub trait Reflect { + /// Describe the acceptable values for this type. fn describe() -> CastInfo; - /// Produce an error for an inacceptable value. - fn error(value: Value) -> StrResult<Self> { - Err(Self::describe().error(&value)) + /// Whether the given value can be converted to `T`. + /// + /// This exists for performance. The check could also be done through the + /// [`CastInfo`], but it would be much more expensive (heap allocation + + /// dynamic checks instead of optimized machine code for each type). + fn castable(value: &Value) -> bool; + + /// Produce an error message for an inacceptable value. + /// + /// ``` + /// # use typst::eval::{Int, Reflect, Value}; + /// assert_eq!( + /// <Int as Reflect>::error(Value::None), + /// "expected integer, found none", + /// ); + /// ``` + fn error(found: &Value) -> EcoString { + Self::describe().error(found) } } -impl Cast for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult<Self> { - Ok(value) - } - +impl Reflect for Value { fn describe() -> CastInfo { CastInfo::Any } -} -impl<T: Cast> Cast<Spanned<Value>> for T { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - T::cast(value.v) + fn castable(_: &Value) -> bool { + true } +} +impl<T: Reflect> Reflect for Spanned<T> { fn describe() -> CastInfo { T::describe() } -} - -impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - fn cast(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) + fn castable(value: &Value) -> bool { + T::castable(value) } +} +impl<T: Reflect> Reflect for StrResult<T> { fn describe() -> CastInfo { T::describe() } -} - -cast_to_value! { - v: u8 => Value::Int(v as i64) -} - -cast_to_value! { - v: u16 => Value::Int(v as i64) -} - -cast_from_value! { - u32, - int: i64 => int.try_into().map_err(|_| { - if int < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, -} -cast_to_value! { - v: u32 => Value::Int(v as i64) -} - -cast_from_value! { - u64, - int: i64 => int.try_into().map_err(|_| { - if int < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, -} - -cast_to_value! { - v: u64 => Value::Int(v as i64) -} - -cast_from_value! { - usize, - int: i64 => int.try_into().map_err(|_| { - if int < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, -} - -cast_to_value! { - v: usize => Value::Int(v as i64) -} - -cast_to_value! { - v: i32 => Value::Int(v as i64) -} - -cast_from_value! { - NonZeroI64, - int: i64 => int.try_into() - .map_err(|_| if int == 0 { - "number must not be zero" - } else { - "number too large" - })?, -} - -cast_to_value! { - v: NonZeroI64 => Value::Int(v.get()) -} - -cast_from_value! { - NonZeroU64, - int: i64 => int - .try_into() - .and_then(|int: u64| int.try_into()) - .map_err(|_| if int <= 0 { - "number must be positive" - } else { - "number too large" - })?, + fn castable(value: &Value) -> bool { + T::castable(value) + } } -cast_to_value! { - v: NonZeroU64 => Value::Int(v.get() as i64) -} +impl<T: Reflect> Reflect for SourceResult<T> { + fn describe() -> CastInfo { + T::describe() + } -cast_from_value! { - NonZeroUsize, - int: i64 => int - .try_into() - .and_then(|int: usize| int.try_into()) - .map_err(|_| if int <= 0 { - "number must be positive" - } else { - "number too large" - })?, + fn castable(value: &Value) -> bool { + T::castable(value) + } } -cast_to_value! { - v: NonZeroUsize => Value::Int(v.get() as i64) -} +impl<T: Reflect> Reflect for &T { + fn describe() -> CastInfo { + T::describe() + } -cast_from_value! { - char, - string: Str => { - let mut chars = string.chars(); - match (chars.next(), chars.next()) { - (Some(c), None) => c, - _ => Err("expected exactly one character")?, - } - }, + fn castable(value: &Value) -> bool { + T::castable(value) + } } -cast_to_value! { - v: char => Value::Str(v.into()) -} +impl<T: Reflect> Reflect for &mut T { + fn describe() -> CastInfo { + T::describe() + } -cast_to_value! { - v: &str => Value::Str(v.into()) + fn castable(value: &Value) -> bool { + T::castable(value) + } } -cast_from_value! { - EcoString, - v: Str => v.into(), +/// Cast a Rust type into a Typst [`Value`]. +/// +/// See also: [`Reflect`]. +pub trait IntoValue { + /// Cast this type into a value. + fn into_value(self) -> Value; } -cast_to_value! { - v: EcoString => Value::Str(v.into()) +impl IntoValue for Value { + fn into_value(self) -> Value { + self + } } -cast_from_value! { - String, - v: Str => v.into(), +impl<T: IntoValue> IntoValue for Spanned<T> { + fn into_value(self) -> Value { + self.v.into_value() + } } -cast_to_value! { - v: String => Value::Str(v.into()) +/// Cast a Rust type or result into a [`SourceResult<Value>`]. +/// +/// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into +/// [`SourceResult<Value>`] by `Ok`-wrapping or adding span information. +pub trait IntoResult { + /// Cast this type into a value. + fn into_result(self, span: Span) -> SourceResult<Value>; } -impl<T: Cast> Cast for Option<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::None) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(None), - v if T::is(&v) => Ok(Some(T::cast(v)?)), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("none") +impl<T: IntoValue> IntoResult for T { + fn into_result(self, _: Span) -> SourceResult<Value> { + Ok(self.into_value()) } } -impl<T: Into<Value>> From<Spanned<T>> for Value { - fn from(spanned: Spanned<T>) -> Self { - spanned.v.into() +impl<T: IntoValue> IntoResult for StrResult<T> { + fn into_result(self, span: Span) -> SourceResult<Value> { + self.map(IntoValue::into_value).at(span) } } -impl<T: Into<Value>> From<Option<T>> for Value { - fn from(v: Option<T>) -> Self { - match v { - Some(v) => v.into(), - None => Value::None, - } +impl<T: IntoValue> IntoResult for SourceResult<T> { + fn into_result(self, _: Span) -> SourceResult<Value> { + self.map(IntoValue::into_value) } } -impl<T: Cast> Cast for Vec<T> { - fn is(value: &Value) -> bool { - Array::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - value.cast::<Array>()?.into_iter().map(Value::cast).collect() - } - - fn describe() -> CastInfo { - <Array as Cast>::describe() - } +/// Try to cast a Typst [`Value`] into a Rust type. +/// +/// See also: [`Reflect`]. +pub trait FromValue<V = Value>: Sized + Reflect { + /// Try to cast the value into an instance of `Self`. + fn from_value(value: V) -> StrResult<Self>; } -impl<T: Into<Value>> From<Vec<T>> for Value { - fn from(v: Vec<T>) -> Self { - Value::Array(v.into_iter().map(Into::into).collect()) +impl FromValue for Value { + fn from_value(value: Value) -> StrResult<Self> { + Ok(value) } } -/// A container for a variadic argument. -pub trait Variadics { - /// The contained type. - type Inner; +impl<T: FromValue> FromValue<Spanned<Value>> for T { + fn from_value(value: Spanned<Value>) -> StrResult<Self> { + T::from_value(value.v) + } } -impl<T> Variadics for Vec<T> { - type Inner = T; +impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> { + fn from_value(value: Spanned<Value>) -> StrResult<Self> { + let span = value.span; + T::from_value(value.v).map(|t| Spanned::new(t, span)) + } } /// Describes a possible value for a cast. @@ -332,10 +239,10 @@ impl CastInfo { } if_chain::if_chain! { if let Value::Int(i) = found; - if parts.iter().any(|p| p == Length::TYPE_NAME); + if parts.iter().any(|p| p == "length"); if !matching_type; then { - msg.push_str(&format!(": a length needs a unit – did you mean {i}pt?")); + write!(msg, ": a length needs a unit – did you mean {i}pt?").unwrap(); } }; @@ -373,19 +280,37 @@ impl Add for CastInfo { } } -/// Castable from nothing. +/// A container for a variadic argument. +pub trait Variadics { + /// The contained type. + type Inner; +} + +impl<T> Variadics for Vec<T> { + type Inner = T; +} + +/// An uninhabitable type. pub enum Never {} -impl Cast for Never { - fn is(_: &Value) -> bool { +impl Reflect for Never { + fn describe() -> CastInfo { + CastInfo::Union(vec![]) + } + + fn castable(_: &Value) -> bool { false } +} - fn cast(value: Value) -> StrResult<Self> { - <Self as Cast>::error(value) +impl IntoValue for Never { + fn into_value(self) -> Value { + match self {} } +} - fn describe() -> CastInfo { - CastInfo::Union(vec![]) +impl FromValue for Never { + fn from_value(value: Value) -> StrResult<Self> { + Err(Self::error(&value)) } } diff --git a/src/eval/datetime.rs b/src/eval/datetime.rs index 47574bae..57d0683d 100644 --- a/src/eval/datetime.rs +++ b/src/eval/datetime.rs @@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString, EcoVec}; use time::error::{Format, InvalidFormatDescription}; use time::{format_description, PrimitiveDateTime}; -use crate::eval::cast_from_value; +use crate::eval::cast; use crate::util::pretty_array_like; /// A datetime object that represents either a date, a time or a combination of @@ -153,8 +153,8 @@ impl Debug for Datetime { } } -cast_from_value! { - Datetime: "datetime", +cast! { + type Datetime: "datetime", } /// Format the `Format` error of the time crate in an appropriate way. diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 49a60147..3e6233ae 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -17,8 +17,8 @@ macro_rules! __dict { ($($key:expr => $value:expr),* $(,)?) => {{ #[allow(unused_mut)] let mut map = $crate::eval::IndexMap::new(); - $(map.insert($key.into(), $value.into());)* - $crate::eval::Dict::from_map(map) + $(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)* + $crate::eval::Dict::from(map) }}; } @@ -38,19 +38,14 @@ impl Dict { Self::default() } - /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: IndexMap<Str, Value>) -> Self { - Self(Arc::new(map)) - } - /// Whether the dictionary is empty. pub fn is_empty(&self) -> bool { self.0.is_empty() } /// The number of pairs in the dictionary. - pub fn len(&self) -> i64 { - self.0.len() as i64 + pub fn len(&self) -> usize { + self.0.len() } /// Borrow the value the given `key` maps to, @@ -217,6 +212,12 @@ impl<'a> IntoIterator for &'a Dict { } } +impl From<IndexMap<Str, Value>> for Dict { + fn from(map: IndexMap<Str, Value>) -> Self { + Self(Arc::new(map)) + } +} + /// The missing key access error message. #[cold] fn missing_key(key: &str) -> EcoString { diff --git a/src/eval/func.rs b/src/eval/func.rs index a224c5b8..86070fc7 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -1,5 +1,3 @@ -pub use typst_macros::func; - use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -9,7 +7,7 @@ use ecow::eco_format; use once_cell::sync::Lazy; use super::{ - cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm, + cast, Args, CastInfo, Eval, Flow, IntoValue, Route, Scope, Scopes, Tracer, Value, Vm, }; use crate::diag::{bail, SourceResult, StrResult}; use crate::model::{ElemFunc, Introspector, Locator, Vt}; @@ -119,10 +117,10 @@ impl Func { /// Call the function with a Vt. #[tracing::instrument(skip_all)] - pub fn call_vt( + pub fn call_vt<T: IntoValue>( &self, vt: &mut Vt, - args: impl IntoIterator<Item = Value>, + args: impl IntoIterator<Item = T>, ) -> SourceResult<Value> { let route = Route::default(); let id = SourceId::detached(); @@ -233,13 +231,9 @@ impl From<&'static NativeFunc> for Func { } } -impl<F> From<F> for Value -where - F: Fn() -> &'static NativeFunc, -{ - fn from(f: F) -> Self { - Value::Func(f().into()) - } +cast! { + &'static NativeFunc, + self => Value::Func(self.into()), } /// Details about a function. @@ -249,16 +243,16 @@ pub struct FuncInfo { pub name: &'static str, /// The display name of the function. pub display: &'static str, - /// A string of keywords. + /// A string of search keywords. pub keywords: Option<&'static str>, + /// Which category the function is part of. + pub category: &'static str, /// Documentation for the function. pub docs: &'static str, /// Details about the function's parameters. pub params: Vec<ParamInfo>, - /// Valid types for the return value. - pub returns: Vec<&'static str>, - /// Which category the function is part of. - pub category: &'static str, + /// Valid values for the return value. + pub returns: CastInfo, /// The function's own scope of fields and sub-functions. pub scope: Scope, } @@ -311,6 +305,7 @@ pub(super) struct Closure { pub body: Expr, } +/// A closure parameter. #[derive(Hash)] pub enum Param { /// A positional parameter: `x`. @@ -362,7 +357,7 @@ impl Closure { // Parse the arguments according to the parameter list. let num_pos_params = closure.params.iter().filter(|p| matches!(p, Param::Pos(_))).count(); - let num_pos_args = args.to_pos().len() as usize; + let num_pos_args = args.to_pos().len(); let sink_size = num_pos_args.checked_sub(num_pos_params); let mut sink = None; @@ -425,8 +420,9 @@ impl From<Closure> for Func { } } -cast_to_value! { - v: Closure => Value::Func(v.into()) +cast! { + Closure, + self => Value::Func(self.into()), } /// A visitor that determines which variables to capture for a closure. diff --git a/src/eval/int.rs b/src/eval/int.rs new file mode 100644 index 00000000..4e081617 --- /dev/null +++ b/src/eval/int.rs @@ -0,0 +1,81 @@ +use std::num::{NonZeroI64, NonZeroIsize, NonZeroU64, NonZeroUsize}; + +use super::{cast, Value}; + +macro_rules! signed_int { + ($($ty:ty)*) => { + $(cast! { + $ty, + self => Value::Int(self as i64), + v: i64 => v.try_into().map_err(|_| "number too large")?, + })* + } +} + +macro_rules! unsigned_int { + ($($ty:ty)*) => { + $(cast! { + $ty, + self => Value::Int(self as i64), + v: i64 => v.try_into().map_err(|_| { + if v < 0 { + "number must be at least zero" + } else { + "number too large" + } + })?, + })* + } +} + +macro_rules! signed_nonzero { + ($($ty:ty)*) => { + $(cast! { + $ty, + self => Value::Int(self.get() as i64), + v: i64 => v + .try_into() + .ok() + .and_then($ty::new) + .ok_or_else(|| if v == 0 { + "number must not be zero" + } else { + "number too large" + })?, + })* + } +} + +macro_rules! unsigned_nonzero { + ($($ty:ty)*) => { + $(cast! { + $ty, + self => Value::Int(self.get() as i64), + v: i64 => v + .try_into() + .ok() + .and_then($ty::new) + .ok_or_else(|| if v <= 0 { + "number must be positive" + } else { + "number too large" + })?, + })* + } +} + +signed_int! { + i8 i16 i32 isize +} + +unsigned_int! { + u8 u16 u32 u64 usize +} + +signed_nonzero! { + NonZeroI64 NonZeroIsize +} + +unsigned_nonzero! { + NonZeroU64 NonZeroUsize +} diff --git a/src/eval/methods.rs b/src/eval/methods.rs index f57bf84d..62ac4095 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -2,7 +2,7 @@ use ecow::EcoString; -use super::{Args, Str, Value, Vm}; +use super::{Args, IntoValue, Str, Value, Vm}; use crate::diag::{At, SourceResult}; use crate::eval::Datetime; use crate::model::{Location, Selector}; @@ -21,20 +21,20 @@ pub fn call( let output = match value { Value::Color(color) => match method { - "lighten" => Value::Color(color.lighten(args.expect("amount")?)), - "darken" => Value::Color(color.darken(args.expect("amount")?)), - "negate" => Value::Color(color.negate()), + "lighten" => color.lighten(args.expect("amount")?).into_value(), + "darken" => color.darken(args.expect("amount")?).into_value(), + "negate" => color.negate().into_value(), _ => return missing(), }, Value::Str(string) => match method { - "len" => Value::Int(string.len()), - "first" => Value::Str(string.first().at(span)?), - "last" => Value::Str(string.last().at(span)?), + "len" => string.len().into_value(), + "first" => string.first().at(span)?.into_value(), + "last" => string.last().at(span)?.into_value(), "at" => { let index = args.expect("index")?; let default = args.named::<EcoString>("default")?; - Value::Str(string.at(index, default.as_deref()).at(span)?) + string.at(index, default.as_deref()).at(span)?.into_value() } "slice" => { let start = args.expect("start")?; @@ -42,56 +42,50 @@ pub fn call( if end.is_none() { end = args.named("count")?.map(|c: i64| start + c); } - Value::Str(string.slice(start, end).at(span)?) + string.slice(start, end).at(span)?.into_value() } - "clusters" => Value::Array(string.clusters()), - "codepoints" => Value::Array(string.codepoints()), - "contains" => Value::Bool(string.contains(args.expect("pattern")?)), - "starts-with" => Value::Bool(string.starts_with(args.expect("pattern")?)), - "ends-with" => Value::Bool(string.ends_with(args.expect("pattern")?)), - "find" => { - string.find(args.expect("pattern")?).map_or(Value::None, Value::Str) - } - "position" => string - .position(args.expect("pattern")?) - .map_or(Value::None, Value::Int), - "match" => string - .match_(args.expect("pattern")?) - .map_or(Value::None, Value::Dict), - "matches" => Value::Array(string.matches(args.expect("pattern")?)), + "clusters" => string.clusters().into_value(), + "codepoints" => string.codepoints().into_value(), + "contains" => string.contains(args.expect("pattern")?).into_value(), + "starts-with" => string.starts_with(args.expect("pattern")?).into_value(), + "ends-with" => string.ends_with(args.expect("pattern")?).into_value(), + "find" => string.find(args.expect("pattern")?).into_value(), + "position" => string.position(args.expect("pattern")?).into_value(), + "match" => string.match_(args.expect("pattern")?).into_value(), + "matches" => string.matches(args.expect("pattern")?).into_value(), "replace" => { let pattern = args.expect("pattern")?; let with = args.expect("string or function")?; let count = args.named("count")?; - Value::Str(string.replace(vm, pattern, with, count)?) + string.replace(vm, pattern, with, count)?.into_value() } "trim" => { let pattern = args.eat()?; let at = args.named("at")?; let repeat = args.named("repeat")?.unwrap_or(true); - Value::Str(string.trim(pattern, at, repeat)) + string.trim(pattern, at, repeat).into_value() } - "split" => Value::Array(string.split(args.eat()?)), + "split" => string.split(args.eat()?).into_value(), _ => return missing(), }, Value::Content(content) => match method { - "func" => content.func().into(), - "has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)), + "func" => content.func().into_value(), + "has" => content.has(&args.expect::<EcoString>("field")?).into_value(), "at" => content .at(&args.expect::<EcoString>("field")?, args.named("default")?) .at(span)?, - "fields" => Value::Dict(content.dict()), + "fields" => content.dict().into_value(), "location" => content .location() .ok_or("this method can only be called on content returned by query(..)") .at(span)? - .into(), + .into_value(), _ => return missing(), }, Value::Array(array) => match method { - "len" => Value::Int(array.len()), + "len" => array.len().into_value(), "first" => array.first().at(span)?.clone(), "last" => array.last().at(span)?.clone(), "at" => array @@ -104,117 +98,104 @@ pub fn call( if end.is_none() { end = args.named("count")?.map(|c: i64| start + c); } - Value::Array(array.slice(start, end).at(span)?) + array.slice(start, end).at(span)?.into_value() } - "contains" => Value::Bool(array.contains(&args.expect("value")?)), - "find" => array.find(vm, args.expect("function")?)?.unwrap_or(Value::None), - "position" => array - .position(vm, args.expect("function")?)? - .map_or(Value::None, Value::Int), - "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), - "map" => Value::Array(array.map(vm, args.expect("function")?)?), + "contains" => array.contains(&args.expect("value")?).into_value(), + "find" => array.find(vm, args.expect("function")?)?.into_value(), + "position" => array.position(vm, args.expect("function")?)?.into_value(), + "filter" => array.filter(vm, args.expect("function")?)?.into_value(), + "map" => array.map(vm, args.expect("function")?)?.into_value(), "fold" => { array.fold(vm, args.expect("initial value")?, args.expect("function")?)? } "sum" => array.sum(args.named("default")?, span)?, "product" => array.product(args.named("default")?, span)?, - "any" => Value::Bool(array.any(vm, args.expect("function")?)?), - "all" => Value::Bool(array.all(vm, args.expect("function")?)?), - "flatten" => Value::Array(array.flatten()), - "rev" => Value::Array(array.rev()), - "split" => Value::Array(array.split(args.expect("separator")?)), + "any" => array.any(vm, args.expect("function")?)?.into_value(), + "all" => array.all(vm, args.expect("function")?)?.into_value(), + "flatten" => array.flatten().into_value(), + "rev" => array.rev().into_value(), + "split" => array.split(args.expect("separator")?).into_value(), "join" => { let sep = args.eat()?; let last = args.named("last")?; array.join(sep, last).at(span)? } - "sorted" => Value::Array(array.sorted(vm, span, args.named("key")?)?), - "zip" => Value::Array(array.zip(args.expect("other")?)), - "enumerate" => Value::Array(array.enumerate()), + "sorted" => array.sorted(vm, span, args.named("key")?)?.into_value(), + "zip" => array.zip(args.expect("other")?).into_value(), + "enumerate" => array.enumerate().into_value(), _ => return missing(), }, Value::Dict(dict) => match method { - "len" => Value::Int(dict.len()), + "len" => dict.len().into_value(), "at" => dict .at(&args.expect::<Str>("key")?, args.named("default")?.as_ref()) .at(span)? .clone(), - "keys" => Value::Array(dict.keys()), - "values" => Value::Array(dict.values()), - "pairs" => Value::Array(dict.pairs()), + "keys" => dict.keys().into_value(), + "values" => dict.values().into_value(), + "pairs" => dict.pairs().into_value(), _ => return missing(), }, Value::Func(func) => match method { - "with" => Value::Func(func.with(args.take())), + "with" => func.with(args.take()).into_value(), "where" => { let fields = args.to_named(); args.items.retain(|arg| arg.name.is_none()); - Value::dynamic( - func.element() - .ok_or("`where()` can only be called on element functions") - .at(span)? - .where_(fields), - ) + func.element() + .ok_or("`where()` can only be called on element functions") + .at(span)? + .where_(fields) + .into_value() } _ => return missing(), }, Value::Args(args) => match method { - "pos" => Value::Array(args.to_pos()), - "named" => Value::Dict(args.to_named()), + "pos" => args.to_pos().into_value(), + "named" => args.to_named().into_value(), _ => return missing(), }, Value::Dyn(dynamic) => { if let Some(location) = dynamic.downcast::<Location>() { match method { - "page" => vm.vt.introspector.page(*location).into(), - "position" => vm.vt.introspector.position(*location).into(), + "page" => vm.vt.introspector.page(*location).into_value(), + "position" => vm.vt.introspector.position(*location).into_value(), "page-numbering" => vm.vt.introspector.page_numbering(*location), _ => return missing(), } } else if let Some(selector) = dynamic.downcast::<Selector>() { match method { - "or" => selector.clone().or(args.all::<Selector>()?).into(), - "and" => selector.clone().and(args.all::<Selector>()?).into(), + "or" => selector.clone().or(args.all::<Selector>()?).into_value(), + "and" => selector.clone().and(args.all::<Selector>()?).into_value(), "before" => { let location = args.expect::<Selector>("selector")?; let inclusive = args.named_or_find::<bool>("inclusive")?.unwrap_or(true); - selector.clone().before(location, inclusive).into() + selector.clone().before(location, inclusive).into_value() } "after" => { let location = args.expect::<Selector>("selector")?; let inclusive = args.named_or_find::<bool>("inclusive")?.unwrap_or(true); - selector.clone().after(location, inclusive).into() + selector.clone().after(location, inclusive).into_value() } _ => return missing(), } } else if let Some(&datetime) = dynamic.downcast::<Datetime>() { match method { - "display" => datetime.display(args.eat()?).at(args.span)?.into(), - "year" => { - datetime.year().map_or(Value::None, |y| Value::Int(y.into())) - } - "month" => { - datetime.month().map_or(Value::None, |m| Value::Int(m.into())) - } - "weekday" => { - datetime.weekday().map_or(Value::None, |w| Value::Int(w.into())) - } - "day" => datetime.day().map_or(Value::None, |d| Value::Int(d.into())), - "hour" => { - datetime.hour().map_or(Value::None, |h| Value::Int(h.into())) - } - "minute" => { - datetime.minute().map_or(Value::None, |m| Value::Int(m.into())) - } - "second" => { - datetime.second().map_or(Value::None, |s| Value::Int(s.into())) + "display" => { + datetime.display(args.eat()?).at(args.span)?.into_value() } + "year" => datetime.year().into_value(), + "month" => datetime.month().into_value(), + "weekday" => datetime.weekday().into_value(), + "day" => datetime.day().into_value(), + "hour" => datetime.hour().into_value(), + "minute" => datetime.minute().into_value(), + "second" => datetime.second().into_value(), _ => return missing(), } } else { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2499ae22..ab75bfb4 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -13,31 +13,45 @@ mod str; #[macro_use] mod value; mod args; +mod auto; mod datetime; mod func; +mod int; mod methods; mod module; +mod none; pub mod ops; mod scope; mod symbol; #[doc(hidden)] -pub use once_cell::sync::Lazy; - -pub use self::args::*; -pub use self::array::*; -pub use self::cast::*; -pub use self::datetime::*; -pub use self::dict::*; -pub use self::func::*; -pub use self::library::*; -pub use self::module::*; -pub use self::scope::*; -pub use self::str::*; -pub use self::symbol::*; -pub use self::value::*; - -pub(crate) use self::methods::methods_on; +pub use { + self::library::LANG_ITEMS, + ecow::{eco_format, eco_vec}, + indexmap::IndexMap, + once_cell::sync::Lazy, +}; + +#[doc(inline)] +pub use typst_macros::{func, symbols}; + +pub use self::args::{Arg, Args}; +pub use self::array::{array, Array}; +pub use self::auto::AutoValue; +pub use self::cast::{ + cast, Cast, CastInfo, FromValue, IntoResult, IntoValue, Never, Reflect, Variadics, +}; +pub use self::datetime::Datetime; +pub use self::dict::{dict, Dict}; +pub use self::func::{Func, FuncInfo, NativeFunc, Param, ParamInfo}; +pub use self::library::{set_lang_items, LangItems, Library}; +pub use self::methods::methods_on; +pub use self::module::Module; +pub use self::none::NoneValue; +pub use self::scope::{Scope, Scopes}; +pub use self::str::{format_str, Regex, Str}; +pub use self::symbol::Symbol; +pub use self::value::{Dynamic, Type, Value}; use std::collections::HashSet; use std::mem; @@ -47,6 +61,7 @@ use comemo::{Track, Tracked, TrackedMut, Validate}; use ecow::{EcoString, EcoVec}; use unicode_segmentation::UnicodeSegmentation; +use self::func::{CapturesVisitor, Closure}; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; @@ -214,8 +229,8 @@ impl<'a> Vm<'a> { /// Define a variable in the current scope. #[tracing::instrument(skip_all)] - pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) { - let value = value.into(); + pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) { + let value = value.into_value(); if self.traced == Some(var.span()) { self.vt.tracer.trace(value.clone()); } @@ -934,7 +949,7 @@ impl Eval for ast::Array { } } - Ok(Array::from_vec(vec)) + Ok(vec.into()) } } @@ -965,7 +980,7 @@ impl Eval for ast::Dict { } } - Ok(Dict::from_map(map)) + Ok(map.into()) } } @@ -1285,23 +1300,23 @@ impl ast::Pattern { for p in destruct.bindings() { match p { ast::DestructuringKind::Normal(expr) => { - let Ok(v) = value.at(i, None) else { + let Ok(v) = value.at(i as i64, None) else { bail!(expr.span(), "not enough elements to destructure"); }; f(vm, expr, v.clone())?; i += 1; } ast::DestructuringKind::Sink(spread) => { - let sink_size = (1 + value.len() as usize) - .checked_sub(destruct.bindings().count()); - let sink = - sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok()); + let sink_size = + (1 + value.len()).checked_sub(destruct.bindings().count()); + let sink = sink_size + .and_then(|s| value.slice(i as i64, Some((i + s) as i64)).ok()); if let (Some(sink_size), Some(sink)) = (sink_size, sink) { if let Some(expr) = spread.expr() { f(vm, expr, Value::Array(sink.clone()))?; } - i += sink_size as i64; + i += sink_size; } else { bail!(self.span(), "not enough elements to destructure") } @@ -1590,7 +1605,7 @@ impl Eval for ast::ForLoop { #[allow(unused_parens)] for value in $iter { - $pat.define(vm, Value::from(value))?; + $pat.define(vm, value.into_value())?; let body = self.body(); let value = body.eval(vm)?; @@ -1644,7 +1659,7 @@ impl Eval for ast::ForLoop { } /// Applies imports from `import` to the current scope. -fn apply_imports<V: Into<Value>>( +fn apply_imports<V: IntoValue>( imports: Option<ast::Imports>, vm: &mut Vm, source_value: V, diff --git a/src/eval/none.rs b/src/eval/none.rs new file mode 100644 index 00000000..ab7644a7 --- /dev/null +++ b/src/eval/none.rs @@ -0,0 +1,74 @@ +use std::fmt::{self, Debug, Formatter}; + +use super::{cast, CastInfo, FromValue, IntoValue, Reflect, Value}; +use crate::diag::StrResult; + +/// A value that indicates the absence of any other value. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct NoneValue; + +impl Reflect for NoneValue { + fn describe() -> CastInfo { + CastInfo::Type("none") + } + + fn castable(value: &Value) -> bool { + matches!(value, Value::None) + } +} + +impl IntoValue for NoneValue { + fn into_value(self) -> Value { + Value::None + } +} + +impl FromValue for NoneValue { + fn from_value(value: Value) -> StrResult<Self> { + match value { + Value::None => Ok(Self), + _ => Err(Self::error(&value)), + } + } +} + +impl Debug for NoneValue { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("none") + } +} + +cast! { + (), + self => Value::None, + _: NoneValue => (), +} + +impl<T: Reflect> Reflect for Option<T> { + fn describe() -> CastInfo { + T::describe() + NoneValue::describe() + } + + fn castable(value: &Value) -> bool { + NoneValue::castable(value) || T::castable(value) + } +} + +impl<T: IntoValue> IntoValue for Option<T> { + fn into_value(self) -> Value { + match self { + Some(v) => v.into_value(), + None => Value::None, + } + } +} + +impl<T: FromValue> FromValue for Option<T> { + fn from_value(value: Value) -> StrResult<Self> { + match value { + Value::None => Ok(None), + v if T::castable(&v) => Ok(Some(T::from_value(v)?)), + _ => Err(Self::error(&value)), + } + } +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 3e397c30..bea2ea82 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -209,8 +209,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { (Int(a), Str(b)) => Str(b.repeat(a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), - (Content(a), Int(b)) => Content(a.repeat(b)?), - (Int(a), Content(b)) => Content(b.repeat(a)?), + (Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)), + (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)), (a, b) => mismatch!("cannot multiply {} with {}", a, b), }) diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 83d1703e..bc62feb1 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::hash::Hash; use ecow::{eco_format, EcoString}; -use super::{Library, Value}; +use super::{IntoValue, Library, Value}; use crate::diag::StrResult; /// A stack of scopes. @@ -95,7 +95,7 @@ impl Scope { /// Bind a value to a name. #[track_caller] - pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { + pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) { let name = name.into(); #[cfg(debug_assertions)] @@ -103,16 +103,13 @@ impl Scope { panic!("duplicate definition: {name}"); } - self.0.insert(name, Slot::new(value.into(), Kind::Normal)); + self.0.insert(name, Slot::new(value.into_value(), Kind::Normal)); } /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into<EcoString>, - value: impl Into<Value>, - ) { - self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured)); + pub fn define_captured(&mut self, var: impl Into<EcoString>, value: impl IntoValue) { + self.0 + .insert(var.into(), Slot::new(value.into_value(), Kind::Captured)); } /// Try to access a variable immutably. diff --git a/src/eval/str.rs b/src/eval/str.rs index 567ee5bd..48ba8311 100644 --- a/src/eval/str.rs +++ b/src/eval/str.rs @@ -6,9 +6,8 @@ use std::ops::{Add, AddAssign, Deref, Range}; use ecow::EcoString; use unicode_segmentation::UnicodeSegmentation; -use super::{cast_from_value, dict, Array, Dict, Func, Value, Vm}; +use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm}; use crate::diag::{At, SourceResult, StrResult}; -use crate::eval::Args; use crate::geom::GenAlign; /// Create a new [`Str`] from a format string. @@ -41,8 +40,8 @@ impl Str { } /// The length of the string in bytes. - pub fn len(&self) -> i64 { - self.0.len() as i64 + pub fn len(&self) -> usize { + self.0.len() } /// A string slice containing the entire string. @@ -82,7 +81,7 @@ impl Str { /// Extract a contiguous substring. pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { let start = self.locate(start)?; - let end = self.locate(end.unwrap_or(self.len()))?.max(start); + let end = self.locate(end.unwrap_or(self.len() as i64))?.max(start); Ok(self.0[start..end].into()) } @@ -283,7 +282,7 @@ impl Str { match &with { Replacement::Str(s) => output.push_str(s), Replacement::Func(func) => { - let args = Args::new(func.span(), [dict.into()]); + let args = Args::new(func.span(), [dict]); let piece = func.call_vm(vm, args)?.cast::<Str>().at(func.span())?; output.push_str(&piece); } @@ -329,7 +328,7 @@ impl Str { /// Errors on invalid char boundaries. fn locate_opt(&self, index: i64) -> StrResult<Option<usize>> { let wrapped = - if index >= 0 { Some(index) } else { self.len().checked_add(index) }; + if index >= 0 { Some(index) } else { (self.len() as i64).checked_add(index) }; let resolved = wrapped .and_then(|v| usize::try_from(v).ok()) @@ -351,13 +350,13 @@ impl Str { /// The out of bounds access error message. #[cold] -fn out_of_bounds(index: i64, len: i64) -> EcoString { +fn out_of_bounds(index: i64, len: usize) -> EcoString { eco_format!("string index out of bounds (index: {}, len: {})", index, len) } /// The out of bounds access error message when no default value was given. #[cold] -fn no_default_and_out_of_bounds(index: i64, len: i64) -> EcoString { +fn no_default_and_out_of_bounds(index: i64, len: usize) -> EcoString { eco_format!("no default value was specified and string index out of bounds (index: {}, len: {})", index, len) } @@ -376,10 +375,10 @@ fn string_is_empty() -> EcoString { /// Convert an item of std's `match_indices` to a dictionary. fn match_to_dict((start, text): (usize, &str)) -> Dict { dict! { - "start" => Value::Int(start as i64), - "end" => Value::Int((start + text.len()) as i64), - "text" => Value::Str(text.into()), - "captures" => Value::Array(Array::new()), + "start" => start, + "end" => start + text.len(), + "text" => text, + "captures" => Array::new(), } } @@ -387,15 +386,13 @@ fn match_to_dict((start, text): (usize, &str)) -> Dict { fn captures_to_dict(cap: regex::Captures) -> Dict { let m = cap.get(0).expect("missing first match"); dict! { - "start" => Value::Int(m.start() as i64), - "end" => Value::Int(m.end() as i64), - "text" => Value::Str(m.as_str().into()), - "captures" => Value::Array( - cap.iter() - .skip(1) - .map(|opt| opt.map_or(Value::None, |m| m.as_str().into())) - .collect(), - ), + "start" => m.start(), + "end" => m.end(), + "text" => m.as_str(), + "captures" => cap.iter() + .skip(1) + .map(|opt| opt.map_or(Value::None, |m| m.as_str().into_value())) + .collect::<Array>(), } } @@ -503,6 +500,35 @@ impl From<Str> for String { } } +cast! { + char, + self => Value::Str(self.into()), + string: Str => { + let mut chars = string.chars(); + match (chars.next(), chars.next()) { + (Some(c), None) => c, + _ => Err("expected exactly one character")?, + } + }, +} + +cast! { + &str, + self => Value::Str(self.into()), +} + +cast! { + EcoString, + self => Value::Str(self.into()), + v: Str => v.into(), +} + +cast! { + String, + self => Value::Str(self.into()), + v: Str => v.into(), +} + /// A regular expression. #[derive(Clone)] pub struct Regex(regex::Regex); @@ -540,8 +566,8 @@ impl Hash for Regex { } } -cast_from_value! { - Regex: "regex", +cast! { + type Regex: "regex", } /// A pattern which can be searched for in a string. @@ -553,7 +579,7 @@ pub enum StrPattern { Regex(Regex), } -cast_from_value! { +cast! { StrPattern, text: Str => Self::Str(text), regex: Regex => Self::Regex(regex), @@ -569,7 +595,7 @@ pub enum StrSide { End, } -cast_from_value! { +cast! { StrSide, align: GenAlign => match align { GenAlign::Start => Self::Start, @@ -587,7 +613,7 @@ pub enum Replacement { Func(Func), } -cast_from_value! { +cast! { Replacement, text: Str => Self::Str(text), func: Func => Self::Func(func) diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs index 5c8951b1..8f4c93f8 100644 --- a/src/eval/symbol.rs +++ b/src/eval/symbol.rs @@ -7,9 +7,6 @@ use ecow::EcoString; use crate::diag::StrResult; -#[doc(inline)] -pub use typst_macros::symbols; - /// A symbol, possibly with variants. #[derive(Clone, Eq, PartialEq, Hash)] pub struct Symbol(Repr); diff --git a/src/eval/value.rs b/src/eval/value.rs index 6d947866..91fdadbe 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -8,12 +8,12 @@ use ecow::eco_format; use siphasher::sip128::{Hasher128, SipHasher13}; use super::{ - cast_to_value, format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, - Label, Module, Str, Symbol, + cast, format_str, ops, Args, Array, CastInfo, Content, Dict, FromValue, Func, + IntoValue, Module, Reflect, Str, Symbol, }; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; -use crate::model::Styles; +use crate::model::{Label, Styles}; use crate::syntax::{ast, Span}; /// A computational value. @@ -79,11 +79,11 @@ impl Value { pub fn numeric(pair: (f64, ast::Unit)) -> Self { let (v, unit) = pair; match unit { - ast::Unit::Length(unit) => Abs::with_unit(v, unit).into(), - ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(), - ast::Unit::Em => Em::new(v).into(), - ast::Unit::Fr => Fr::new(v).into(), - ast::Unit::Percent => Ratio::new(v / 100.0).into(), + ast::Unit::Length(unit) => Abs::with_unit(v, unit).into_value(), + ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into_value(), + ast::Unit::Em => Em::new(v).into_value(), + ast::Unit::Fr => Fr::new(v).into_value(), + ast::Unit::Percent => Ratio::new(v / 100.0).into_value(), } } @@ -116,8 +116,8 @@ impl Value { } /// Try to cast the value into a specific type. - pub fn cast<T: Cast>(self) -> StrResult<T> { - T::cast(self) + pub fn cast<T: FromValue>(self) -> StrResult<T> { + T::from_value(self) } /// Try to access a field on the value. @@ -283,8 +283,9 @@ impl PartialEq for Dynamic { } } -cast_to_value! { - v: Dynamic => Value::Dyn(v) +cast! { + Dynamic, + self => Value::Dyn(self), } trait Bounds: Debug + Sync + Send + 'static { @@ -337,20 +338,32 @@ pub trait Type { /// Implement traits for primitives. macro_rules! primitive { ( - $type:ty: $name:literal, $variant:ident + $ty:ty: $name:literal, $variant:ident $(, $other:ident$(($binding:ident))? => $out:expr)* ) => { - impl Type for $type { + impl Type for $ty { const TYPE_NAME: &'static str = $name; } - impl Cast for $type { - fn is(value: &Value) -> bool { + impl Reflect for $ty { + fn describe() -> CastInfo { + CastInfo::Type(Self::TYPE_NAME) + } + + fn castable(value: &Value) -> bool { matches!(value, Value::$variant(_) $(| primitive!(@$other $(($binding))?))*) } + } - fn cast(value: Value) -> StrResult<Self> { + impl IntoValue for $ty { + fn into_value(self) -> Value { + Value::$variant(self) + } + } + + impl FromValue for $ty { + fn from_value(value: Value) -> StrResult<Self> { match value { Value::$variant(v) => Ok(v), $(Value::$other$(($binding))? => Ok($out),)* @@ -361,16 +374,6 @@ macro_rules! primitive { )), } } - - fn describe() -> CastInfo { - CastInfo::Type(Self::TYPE_NAME) - } - } - - impl From<$type> for Value { - fn from(v: $type) -> Self { - Value::$variant(v) - } } }; @@ -408,8 +411,8 @@ primitive! { Styles: "styles", Styles } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } -primitive! { Module: "module", Module } primitive! { Args: "arguments", Args } +primitive! { Module: "module", Module } #[cfg(test)] mod tests { @@ -418,8 +421,8 @@ mod tests { use crate::geom::RgbaColor; #[track_caller] - fn test(value: impl Into<Value>, exp: &str) { - assert_eq!(format!("{:?}", value.into()), exp); + fn test(value: impl IntoValue, exp: &str) { + assert_eq!(format!("{:?}", value.into_value()), exp); } #[test] |
