diff options
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 216 | ||||
| -rw-r--r-- | src/eval/array.rs | 508 | ||||
| -rw-r--r-- | src/eval/auto.rs | 39 | ||||
| -rw-r--r-- | src/eval/cast.rs | 316 | ||||
| -rw-r--r-- | src/eval/datetime.rs | 201 | ||||
| -rw-r--r-- | src/eval/dict.rs | 235 | ||||
| -rw-r--r-- | src/eval/func.rs | 643 | ||||
| -rw-r--r-- | src/eval/int.rs | 81 | ||||
| -rw-r--r-- | src/eval/library.rs | 182 | ||||
| -rw-r--r-- | src/eval/methods.rs | 373 | ||||
| -rw-r--r-- | src/eval/mod.rs | 1908 | ||||
| -rw-r--r-- | src/eval/module.rs | 98 | ||||
| -rw-r--r-- | src/eval/none.rs | 74 | ||||
| -rw-r--r-- | src/eval/ops.rs | 429 | ||||
| -rw-r--r-- | src/eval/scope.rs | 178 | ||||
| -rw-r--r-- | src/eval/str.rs | 620 | ||||
| -rw-r--r-- | src/eval/symbol.rs | 210 | ||||
| -rw-r--r-- | src/eval/value.rs | 461 |
18 files changed, 0 insertions, 6772 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs deleted file mode 100644 index da29eeaf..00000000 --- a/src/eval/args.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; - -use ecow::{eco_format, EcoVec}; - -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; - -/// Evaluated arguments to a function. -#[derive(Clone, PartialEq, Hash)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional and named arguments. - pub items: EcoVec<Arg>, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq, Hash)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option<Str>, - /// The value of the argument. - pub value: Spanned<Value>, -} - -impl Args { - /// Create positional arguments from a span and values. - 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.into_value(), span), - }) - .collect(); - Self { span, items } - } - - /// Push a positional argument. - pub fn push(&mut self, span: Span, value: Value) { - self.items.push(Arg { - span: self.span, - name: None, - value: Spanned::new(value, span), - }) - } - - /// Consume and cast the first positional argument if there is one. - pub fn eat<T>(&mut self) -> SourceResult<Option<T>> - where - 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::from_value(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Consume n positional arguments if possible. - pub fn consume(&mut self, n: usize) -> SourceResult<Vec<Arg>> { - let mut list = vec![]; - - let mut i = 0; - while i < self.items.len() && list.len() < n { - if self.items[i].name.is_none() { - list.push(self.items.remove(i)); - } else { - i += 1; - } - } - - if list.len() < n { - bail!(self.span, "not enough arguments"); - } - - Ok(list) - } - - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect<T>(&mut self, what: &str) -> SourceResult<T> - where - T: FromValue<Spanned<Value>>, - { - match self.eat()? { - Some(v) => Ok(v), - 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: FromValue<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::castable(&slot.value.v) { - let value = self.items.remove(i).value; - let span = value.span; - return T::from_value(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume all castable positional arguments. - pub fn all<T>(&mut self) -> SourceResult<Vec<T>> - where - T: FromValue<Spanned<Value>>, - { - let mut list = vec![]; - while let Some(value) = self.find()? { - list.push(value); - } - Ok(list) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - 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. - let mut i = 0; - let mut found = None; - while i < self.items.len() { - if self.items[i].name.as_deref() == Some(name) { - let value = self.items.remove(i).value; - let span = value.span; - found = Some(T::from_value(value).at(span)?); - } else { - i += 1; - } - } - Ok(found) - } - - /// Same as named, but with fallback to find. - pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - T: FromValue<Spanned<Value>>, - { - match self.named(name)? { - Some(value) => Ok(Some(value)), - None => self.find(), - } - } - - /// Take out all arguments into a new instance. - pub fn take(&mut self) -> Self { - Self { - span: self.span, - items: std::mem::take(&mut self.items), - } - } - - /// Return an "unexpected argument" error if there is any remaining - /// argument. - pub fn finish(self) -> SourceResult<()> { - if let Some(arg) = self.items.first() { - match &arg.name { - Some(name) => bail!(arg.span, "unexpected argument: {name}"), - _ => bail!(arg.span, "unexpected argument"), - } - } - Ok(()) - } - - /// Extract the positional arguments as an array. - pub fn to_pos(&self) -> Array { - self.items - .iter() - .filter(|item| item.name.is_none()) - .map(|item| item.value.v.clone()) - .collect() - } - - /// Extract the named arguments as a dictionary. - pub fn to_named(&self) -> Dict { - self.items - .iter() - .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone()))) - .collect() - } -} - -impl Debug for Args { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let pieces: Vec<_> = - self.items.iter().map(|arg| eco_format!("{arg:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, false)) - } -} - -impl Debug for Arg { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(name) = &self.name { - f.write_str(name)?; - f.write_str(": ")?; - } - Debug::fmt(&self.value.v, f) - } -} diff --git a/src/eval/array.rs b/src/eval/array.rs deleted file mode 100644 index a7a1387b..00000000 --- a/src/eval/array.rs +++ /dev/null @@ -1,508 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::ops::{Add, AddAssign}; - -use ecow::{eco_format, EcoString, EcoVec}; - -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; - -/// Create a new [`Array`] from values. -#[macro_export] -#[doc(hidden)] -macro_rules! __array { - ($value:expr; $count:expr) => { - $crate::eval::Array::from($crate::eval::eco_vec![ - $crate::eval::IntoValue::into_value($value); - $count - ]) - }; - - ($($value:expr),* $(,)?) => { - $crate::eval::Array::from($crate::eval::eco_vec![$( - $crate::eval::IntoValue::into_value($value) - ),*]) - }; -} - -#[doc(inline)] -pub use crate::__array as array; -use crate::eval::ops::{add, mul}; -#[doc(hidden)] -pub use ecow::eco_vec; - -/// A reference counted array with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Array(EcoVec<Value>); - -impl Array { - /// Create a new, empty array. - pub fn new() -> Self { - Self::default() - } - - /// 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) -> usize { - self.0.len() - } - - /// The first value in the array. - pub fn first(&self) -> StrResult<&Value> { - self.0.first().ok_or_else(array_is_empty) - } - - /// Mutably borrow the first value in the array. - pub fn first_mut(&mut self) -> StrResult<&mut Value> { - self.0.make_mut().first_mut().ok_or_else(array_is_empty) - } - - /// The last value in the array. - pub fn last(&self) -> StrResult<&Value> { - self.0.last().ok_or_else(array_is_empty) - } - - /// Mutably borrow the last value in the array. - pub fn last_mut(&mut self) -> StrResult<&mut Value> { - self.0.make_mut().last_mut().ok_or_else(array_is_empty) - } - - /// Borrow the value at the given index. - pub fn at<'a>( - &'a self, - index: i64, - default: Option<&'a Value>, - ) -> StrResult<&'a Value> { - self.locate(index) - .and_then(|i| self.0.get(i)) - .or(default) - .ok_or_else(|| out_of_bounds_no_default(index, self.len())) - } - - /// Mutably borrow the value at the given index. - pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> { - let len = self.len(); - self.locate(index) - .and_then(move |i| self.0.make_mut().get_mut(i)) - .ok_or_else(|| out_of_bounds_no_default(index, len)) - } - - /// Push a value to the end of the array. - pub fn push(&mut self, value: Value) { - self.0.push(value); - } - - /// Remove the last value in the array. - pub fn pop(&mut self) -> StrResult<Value> { - self.0.pop().ok_or_else(array_is_empty) - } - - /// Insert a value at the specified index. - pub fn insert(&mut self, index: i64, value: Value) -> StrResult<()> { - let len = self.len(); - let i = self - .locate(index) - .filter(|&i| i <= self.0.len()) - .ok_or_else(|| out_of_bounds(index, len))?; - - self.0.insert(i, value); - Ok(()) - } - - /// Remove and return the value at the specified index. - pub fn remove(&mut self, index: i64) -> StrResult<Value> { - let len = self.len(); - let i = self - .locate(index) - .filter(|&i| i < self.0.len()) - .ok_or_else(|| out_of_bounds(index, len))?; - - Ok(self.0.remove(i)) - } - - /// Extract a contiguous subregion of the array. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let len = self.len(); - let start = self - .locate(start) - .filter(|&start| start <= self.0.len()) - .ok_or_else(|| out_of_bounds(start, 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.0[start..end].into()) - } - - /// Whether the array contains a specific value. - pub fn contains(&self, value: &Value) -> bool { - self.0.contains(value) - } - - /// Return the first matching item. - pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(item.clone())); - } - } - Ok(None) - } - - /// Return the index of the first matching item. - pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> { - for (i, item) in self.iter().enumerate() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(i as i64)); - } - } - - Ok(None) - } - - /// Return a new array with only those items for which the function returns - /// true. - pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - let mut kept = EcoVec::new(); - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - kept.push(item.clone()) - } - } - Ok(kept.into()) - } - - /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - self.iter() - .map(|item| { - let args = Args::new(func.span(), [item.clone()]); - func.call_vm(vm, args) - }) - .collect() - } - - /// Fold all of the array's items into one with a function. - pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> { - let mut acc = init; - for item in self.iter() { - let args = Args::new(func.span(), [acc, item.clone()]); - acc = func.call_vm(vm, args)?; - } - Ok(acc) - } - - /// Calculates the sum of the array's items - pub fn sum(&self, default: Option<Value>, span: Span) -> SourceResult<Value> { - let mut acc = self - .first() - .map(|x| x.clone()) - .or_else(|_| { - default.ok_or_else(|| { - eco_format!("cannot calculate sum of empty array with no default") - }) - }) - .at(span)?; - for i in self.iter().skip(1) { - acc = add(acc, i.clone()).at(span)?; - } - Ok(acc) - } - - /// Calculates the product of the array's items - pub fn product(&self, default: Option<Value>, span: Span) -> SourceResult<Value> { - let mut acc = self - .first() - .map(|x| x.clone()) - .or_else(|_| { - default.ok_or_else(|| { - eco_format!("cannot calculate product of empty array with no default") - }) - }) - .at(span)?; - for i in self.iter().skip(1) { - acc = mul(acc, i.clone()).at(span)?; - } - Ok(acc) - } - - /// Whether any item matches. - pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(true); - } - } - - Ok(false) - } - - /// Whether all items match. - pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if !func.call_vm(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(false); - } - } - - Ok(true) - } - - /// Return a new array with all items from this and nested arrays. - pub fn flatten(&self) -> Self { - let mut flat = EcoVec::with_capacity(self.0.len()); - for item in self.iter() { - if let Value::Array(nested) = item { - flat.extend(nested.flatten().into_iter()); - } else { - flat.push(item.clone()); - } - } - flat.into() - } - - /// Returns a new array with reversed order. - pub fn rev(&self) -> Self { - self.0.iter().cloned().rev().collect() - } - - /// Split all values in the array. - pub fn split(&self, at: Value) -> Array { - self.as_slice() - .split(|value| *value == at) - .map(|subslice| Value::Array(subslice.iter().cloned().collect())) - .collect() - } - - /// Join all values in the array, optionally with separator and last - /// separator (between the final two items). - pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> { - let len = self.0.len(); - let sep = sep.unwrap_or(Value::None); - - let mut result = Value::None; - for (i, value) in self.iter().cloned().enumerate() { - if i > 0 { - if i + 1 == len && last.is_some() { - result = ops::join(result, last.take().unwrap())?; - } else { - result = ops::join(result, sep.clone())?; - } - } - - result = ops::join(result, value)?; - } - - Ok(result) - } - - /// Zips the array with another array. If the two arrays are of unequal length, it will only - /// zip up until the last element of the smaller array and the remaining elements will be - /// ignored. The return value is an array where each element is yet another array of size 2. - pub fn zip(&self, other: Array) -> Array { - self.iter() - .zip(other) - .map(|(first, second)| array![first.clone(), second].into_value()) - .collect() - } - - /// Return a sorted version of this array, optionally by a given key function. - /// - /// Returns an error if two values could not be compared or if the key function (if given) - /// yields an error. - pub fn sorted( - &self, - vm: &mut Vm, - span: Span, - key: Option<Func>, - ) -> SourceResult<Self> { - let mut result = Ok(()); - let mut vec = self.0.clone(); - let mut key_of = |x: Value| match &key { - // NOTE: We are relying on `comemo`'s memoization of function - // evaluation to not excessively reevaluate the `key`. - Some(f) => f.call_vm(vm, Args::new(f.span(), [x])), - None => Ok(x), - }; - vec.make_mut().sort_by(|a, b| { - // Until we get `try` blocks :) - match (key_of(a.clone()), key_of(b.clone())) { - (Ok(a), Ok(b)) => { - typst::eval::ops::compare(&a, &b).unwrap_or_else(|err| { - if result.is_ok() { - result = Err(err).at(span); - } - Ordering::Equal - }) - } - (Err(e), _) | (_, Err(e)) => { - if result.is_ok() { - result = Err(e); - } - Ordering::Equal - } - } - }); - result.map(|_| vec.into()) - } - - /// Repeat this array `n` times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let count = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n)) - .ok_or_else(|| format!("cannot repeat this array {} times", n))?; - - Ok(self.iter().cloned().cycle().take(count).collect()) - } - - /// Extract a slice of the whole array. - pub fn as_slice(&self) -> &[Value] { - self.0.as_slice() - } - - /// Iterate over references to the contained values. - pub fn iter(&self) -> std::slice::Iter<Value> { - self.0.iter() - } - - /// Resolve an index. - fn locate(&self, index: i64) -> Option<usize> { - 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 { - self.iter() - .enumerate() - .map(|(i, value)| array![i, value.clone()].into_value()) - .collect() - } -} - -impl Debug for Array { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect(); - f.write_str(&pretty_array_like(&pieces, self.len() == 1)) - } -} - -impl Add for Array { - type Output = Self; - - fn add(mut self, rhs: Array) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Array { - fn add_assign(&mut self, rhs: Array) { - self.0.extend(rhs.0); - } -} - -impl Extend<Value> for Array { - fn extend<T: IntoIterator<Item = Value>>(&mut self, iter: T) { - self.0.extend(iter); - } -} - -impl FromIterator<Value> for Array { - fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl IntoIterator for Array { - type Item = Value; - type IntoIter = ecow::vec::IntoIter<Value>; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a> IntoIterator for &'a Array { - type Item = &'a Value; - type IntoIter = std::slice::Iter<'a, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -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 { - "array is empty".into() -} - -/// The out of bounds access error message. -#[cold] -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: usize) -> EcoString { - eco_format!( - "array index out of bounds (index: {index}, len: {len}) \ - and no default value was specified", - ) -} diff --git a/src/eval/auto.rs b/src/eval/auto.rs deleted file mode 100644 index e73b3f33..00000000 --- a/src/eval/auto.rs +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index 917972ed..00000000 --- a/src/eval/cast.rs +++ /dev/null @@ -1,316 +0,0 @@ -pub use typst_macros::{cast, Cast}; - -use std::fmt::Write; -use std::ops::Add; - -use ecow::EcoString; - -use super::Value; -use crate::diag::{At, SourceResult, StrResult}; -use crate::syntax::{Span, Spanned}; -use crate::util::separated_list; - -/// 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; - - /// 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 Reflect for Value { - fn describe() -> CastInfo { - CastInfo::Any - } - - fn castable(_: &Value) -> bool { - true - } -} - -impl<T: Reflect> Reflect for Spanned<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for StrResult<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for SourceResult<T> { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for &T { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -impl<T: Reflect> Reflect for &mut T { - fn describe() -> CastInfo { - T::describe() - } - - fn castable(value: &Value) -> bool { - T::castable(value) - } -} - -/// 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; -} - -impl IntoValue for Value { - fn into_value(self) -> Value { - self - } -} - -impl<T: IntoValue> IntoValue for Spanned<T> { - fn into_value(self) -> Value { - self.v.into_value() - } -} - -/// 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: IntoValue> IntoResult for T { - fn into_result(self, _: Span) -> SourceResult<Value> { - Ok(self.into_value()) - } -} - -impl<T: IntoValue> IntoResult for StrResult<T> { - fn into_result(self, span: Span) -> SourceResult<Value> { - self.map(IntoValue::into_value).at(span) - } -} - -impl<T: IntoValue> IntoResult for SourceResult<T> { - fn into_result(self, _: Span) -> SourceResult<Value> { - self.map(IntoValue::into_value) - } -} - -/// 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 FromValue for Value { - fn from_value(value: Value) -> StrResult<Self> { - Ok(value) - } -} - -impl<T: FromValue> FromValue<Spanned<Value>> for T { - fn from_value(value: Spanned<Value>) -> StrResult<Self> { - T::from_value(value.v) - } -} - -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. -#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] -pub enum CastInfo { - /// Any value is okay. - Any, - /// A specific value, plus short documentation for that value. - Value(Value, &'static str), - /// Any value of a type. - Type(&'static str), - /// Multiple alternatives. - Union(Vec<Self>), -} - -impl CastInfo { - /// Produce an error message describing what was expected and what was - /// found. - pub fn error(&self, found: &Value) -> EcoString { - fn accumulate( - info: &CastInfo, - found: &Value, - parts: &mut Vec<EcoString>, - matching_type: &mut bool, - ) { - match info { - CastInfo::Any => parts.push("anything".into()), - CastInfo::Value(value, _) => { - parts.push(value.repr().into()); - if value.type_name() == found.type_name() { - *matching_type = true; - } - } - CastInfo::Type(ty) => parts.push((*ty).into()), - CastInfo::Union(options) => { - for option in options { - accumulate(option, found, parts, matching_type); - } - } - } - } - - let mut matching_type = false; - let mut parts = vec![]; - accumulate(self, found, &mut parts, &mut matching_type); - - let mut msg = String::from("expected "); - if parts.is_empty() { - msg.push_str(" nothing"); - } - - msg.push_str(&separated_list(&parts, "or")); - - if !matching_type { - msg.push_str(", found "); - msg.push_str(found.type_name()); - } - if_chain::if_chain! { - if let Value::Int(i) = found; - if parts.iter().any(|p| p == "length"); - if !matching_type; - then { - write!(msg, ": a length needs a unit - did you mean {i}pt?").unwrap(); - } - }; - - msg.into() - } -} - -impl Add for CastInfo { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - Self::Union(match (self, rhs) { - (Self::Union(mut lhs), Self::Union(rhs)) => { - for cast in rhs { - if !lhs.contains(&cast) { - lhs.push(cast); - } - } - lhs - } - (Self::Union(mut lhs), rhs) => { - if !lhs.contains(&rhs) { - lhs.push(rhs); - } - lhs - } - (lhs, Self::Union(mut rhs)) => { - if !rhs.contains(&lhs) { - rhs.insert(0, lhs); - } - rhs - } - (lhs, rhs) => vec![lhs, rhs], - }) - } -} - -/// 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 Reflect for Never { - fn describe() -> CastInfo { - CastInfo::Union(vec![]) - } - - fn castable(_: &Value) -> bool { - false - } -} - -impl IntoValue for Never { - fn into_value(self) -> Value { - match self {} - } -} - -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 deleted file mode 100644 index f3c4a5a1..00000000 --- a/src/eval/datetime.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::fmt; -use std::fmt::{Debug, Formatter}; -use std::hash::Hash; - -use ecow::{eco_format, EcoString, EcoVec}; -use time::error::{Format, InvalidFormatDescription}; -use time::{format_description, PrimitiveDateTime}; - -use crate::eval::cast; -use crate::util::pretty_array_like; - -/// A datetime object that represents either a date, a time or a combination of -/// both. -#[derive(Clone, Copy, PartialEq, Hash)] -pub enum Datetime { - /// Representation as a date. - Date(time::Date), - /// Representation as a time. - Time(time::Time), - /// Representation as a combination of date and time. - Datetime(time::PrimitiveDateTime), -} - -impl Datetime { - /// Display the date and/or time in a certain format. - pub fn display(&self, pattern: Option<EcoString>) -> Result<EcoString, EcoString> { - let pattern = pattern.as_ref().map(EcoString::as_str).unwrap_or(match self { - Datetime::Date(_) => "[year]-[month]-[day]", - Datetime::Time(_) => "[hour]:[minute]:[second]", - Datetime::Datetime(_) => "[year]-[month]-[day] [hour]:[minute]:[second]", - }); - - let format = format_description::parse(pattern) - .map_err(format_time_invalid_format_description_error)?; - - let formatted_result = match self { - Datetime::Date(date) => date.format(&format), - Datetime::Time(time) => time.format(&format), - Datetime::Datetime(datetime) => datetime.format(&format), - } - .map(EcoString::from); - - formatted_result.map_err(format_time_format_error) - } - - /// Return the year of the datetime, if existing. - pub fn year(&self) -> Option<i32> { - match self { - Datetime::Date(date) => Some(date.year()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.year()), - } - } - - /// Return the month of the datetime, if existing. - pub fn month(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.month().into()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.month().into()), - } - } - - /// Return the weekday of the datetime, if existing. - pub fn weekday(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.weekday().number_from_monday()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.weekday().number_from_monday()), - } - } - - /// Return the day of the datetime, if existing. - pub fn day(&self) -> Option<u8> { - match self { - Datetime::Date(date) => Some(date.day()), - Datetime::Time(_) => None, - Datetime::Datetime(datetime) => Some(datetime.day()), - } - } - - /// Return the hour of the datetime, if existing. - pub fn hour(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.hour()), - Datetime::Datetime(datetime) => Some(datetime.hour()), - } - } - - /// Return the minute of the datetime, if existing. - pub fn minute(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.minute()), - Datetime::Datetime(datetime) => Some(datetime.minute()), - } - } - - /// Return the second of the datetime, if existing. - pub fn second(&self) -> Option<u8> { - match self { - Datetime::Date(_) => None, - Datetime::Time(time) => Some(time.second()), - Datetime::Datetime(datetime) => Some(datetime.second()), - } - } - - /// Create a datetime from year, month, and day. - pub fn from_ymd(year: i32, month: u8, day: u8) -> Option<Self> { - Some(Datetime::Date( - time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day) - .ok()?, - )) - } - - /// Create a datetime from hour, minute, and second. - pub fn from_hms(hour: u8, minute: u8, second: u8) -> Option<Self> { - Some(Datetime::Time(time::Time::from_hms(hour, minute, second).ok()?)) - } - - /// Create a datetime from day and time. - pub fn from_ymd_hms( - year: i32, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, - ) -> Option<Self> { - let date = - time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day) - .ok()?; - let time = time::Time::from_hms(hour, minute, second).ok()?; - Some(Datetime::Datetime(PrimitiveDateTime::new(date, time))) - } -} - -impl Debug for Datetime { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let year = self.year().map(|y| eco_format!("year: {y}")); - let month = self.month().map(|m| eco_format!("month: {m}")); - let day = self.day().map(|d| eco_format!("day: {d}")); - let hour = self.hour().map(|h| eco_format!("hour: {h}")); - let minute = self.minute().map(|m| eco_format!("minute: {m}")); - let second = self.second().map(|s| eco_format!("second: {s}")); - let filtered = [year, month, day, hour, minute, second] - .into_iter() - .flatten() - .collect::<EcoVec<_>>(); - - write!(f, "datetime{}", &pretty_array_like(&filtered, false)) - } -} - -cast! { - type Datetime: "datetime", -} - -/// Format the `Format` error of the time crate in an appropriate way. -fn format_time_format_error(error: Format) -> EcoString { - match error { - Format::InvalidComponent(name) => eco_format!("invalid component '{}'", name), - _ => "failed to format datetime in the requested format".into(), - } -} - -/// Format the `InvalidFormatDescription` error of the time crate in an -/// appropriate way. -fn format_time_invalid_format_description_error( - error: InvalidFormatDescription, -) -> EcoString { - match error { - InvalidFormatDescription::UnclosedOpeningBracket { index, .. } => { - eco_format!("missing closing bracket for bracket at index {}", index) - } - InvalidFormatDescription::InvalidComponentName { name, index, .. } => { - eco_format!("invalid component name '{}' at index {}", name, index) - } - InvalidFormatDescription::InvalidModifier { value, index, .. } => { - eco_format!("invalid modifier '{}' at index {}", value, index) - } - InvalidFormatDescription::Expected { what, index, .. } => { - eco_format!("expected {} at index {}", what, index) - } - InvalidFormatDescription::MissingComponentName { index, .. } => { - eco_format!("expected component name at index {}", index) - } - InvalidFormatDescription::MissingRequiredModifier { name, index, .. } => { - eco_format!( - "missing required modifier {} for component at index {}", - name, - index - ) - } - InvalidFormatDescription::NotSupported { context, what, index, .. } => { - eco_format!("{} is not supported in {} at index {}", what, context, index) - } - _ => "failed to parse datetime format".into(), - } -} diff --git a/src/eval/dict.rs b/src/eval/dict.rs deleted file mode 100644 index 3e6233ae..00000000 --- a/src/eval/dict.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign}; -use std::sync::Arc; - -use ecow::{eco_format, EcoString}; - -use super::{array, Array, Str, Value}; -use crate::diag::StrResult; -use crate::syntax::is_ident; -use crate::util::{pretty_array_like, separated_list, ArcExt}; - -/// Create a new [`Dict`] from key-value pairs. -#[macro_export] -#[doc(hidden)] -macro_rules! __dict { - ($($key:expr => $value:expr),* $(,)?) => {{ - #[allow(unused_mut)] - let mut map = $crate::eval::IndexMap::new(); - $(map.insert($key.into(), $crate::eval::IntoValue::into_value($value));)* - $crate::eval::Dict::from(map) - }}; -} - -#[doc(inline)] -pub use crate::__dict as dict; - -#[doc(inline)] -pub use indexmap::IndexMap; - -/// A reference-counted dictionary with value semantics. -#[derive(Default, Clone, PartialEq)] -pub struct Dict(Arc<IndexMap<Str, Value>>); - -impl Dict { - /// Create a new, empty dictionary. - pub fn new() -> Self { - Self::default() - } - - /// 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) -> usize { - self.0.len() - } - - /// Borrow the value the given `key` maps to, - pub fn at<'a>( - &'a self, - key: &str, - default: Option<&'a Value>, - ) -> StrResult<&'a Value> { - self.0.get(key).or(default).ok_or_else(|| missing_key_no_default(key)) - } - - /// Mutably borrow the value the given `key` maps to. - pub fn at_mut(&mut self, key: &str) -> StrResult<&mut Value> { - Arc::make_mut(&mut self.0) - .get_mut(key) - .ok_or_else(|| missing_key_no_default(key)) - } - - /// Remove the value if the dictionary contains the given key. - pub fn take(&mut self, key: &str) -> StrResult<Value> { - Arc::make_mut(&mut self.0) - .remove(key) - .ok_or_else(|| eco_format!("missing key: {:?}", Str::from(key))) - } - - /// Whether the dictionary contains a specific key. - pub fn contains(&self, key: &str) -> bool { - self.0.contains_key(key) - } - - /// Insert a mapping from the given `key` to the given `value`. - pub fn insert(&mut self, key: Str, value: Value) { - Arc::make_mut(&mut self.0).insert(key, value); - } - - /// Remove a mapping by `key` and return the value. - pub fn remove(&mut self, key: &str) -> StrResult<Value> { - match Arc::make_mut(&mut self.0).shift_remove(key) { - Some(value) => Ok(value), - None => Err(missing_key(key)), - } - } - - /// Clear the dictionary. - pub fn clear(&mut self) { - if Arc::strong_count(&self.0) == 1 { - Arc::make_mut(&mut self.0).clear(); - } else { - *self = Self::new(); - } - } - - /// Return the keys of the dictionary as an array. - pub fn keys(&self) -> Array { - self.0.keys().cloned().map(Value::Str).collect() - } - - /// Return the values of the dictionary as an array. - pub fn values(&self) -> Array { - self.0.values().cloned().collect() - } - - /// Return the values of the dictionary as an array of pairs (arrays of - /// length two). - pub fn pairs(&self) -> Array { - self.0 - .iter() - .map(|(k, v)| Value::Array(array![k.clone(), v.clone()])) - .collect() - } - - /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> indexmap::map::Iter<Str, Value> { - self.0.iter() - } - - /// Return an "unexpected key" error if there is any remaining pair. - pub fn finish(&self, expected: &[&str]) -> StrResult<()> { - if let Some((key, _)) = self.iter().next() { - let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect(); - let mut msg = format!("unexpected key {key:?}, valid keys are "); - msg.push_str(&separated_list(&parts, "and")); - return Err(msg.into()); - } - Ok(()) - } -} - -impl Debug for Dict { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.is_empty() { - return f.write_str("(:)"); - } - - let pieces: Vec<_> = self - .iter() - .map(|(key, value)| { - if is_ident(key) { - eco_format!("{key}: {value:?}") - } else { - eco_format!("{key:?}: {value:?}") - } - }) - .collect(); - - f.write_str(&pretty_array_like(&pieces, false)) - } -} - -impl Add for Dict { - type Output = Self; - - fn add(mut self, rhs: Dict) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Dict { - fn add_assign(&mut self, rhs: Dict) { - match Arc::try_unwrap(rhs.0) { - Ok(map) => self.extend(map), - Err(rc) => self.extend(rc.iter().map(|(k, v)| (k.clone(), v.clone()))), - } - } -} - -impl Hash for Dict { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_usize(self.0.len()); - for item in self { - item.hash(state); - } - } -} - -impl Extend<(Str, Value)> for Dict { - fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); - } -} - -impl FromIterator<(Str, Value)> for Dict { - fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self { - Self(Arc::new(iter.into_iter().collect())) - } -} - -impl IntoIterator for Dict { - type Item = (Str, Value); - type IntoIter = indexmap::map::IntoIter<Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - Arc::take(self.0).into_iter() - } -} - -impl<'a> IntoIterator for &'a Dict { - type Item = (&'a Str, &'a Value); - type IntoIter = indexmap::map::Iter<'a, Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -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 { - eco_format!("dictionary does not contain key {:?}", Str::from(key)) -} - -/// The missing key access error message when no default was fiven. -#[cold] -fn missing_key_no_default(key: &str) -> EcoString { - eco_format!( - "dictionary does not contain key {:?} \ - and no default value was specified", - Str::from(key) - ) -} diff --git a/src/eval/func.rs b/src/eval/func.rs deleted file mode 100644 index 22f948ce..00000000 --- a/src/eval/func.rs +++ /dev/null @@ -1,643 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use comemo::{Prehashed, Tracked, TrackedMut}; -use ecow::eco_format; -use once_cell::sync::Lazy; - -use super::{ - cast, Args, CastInfo, Eval, FlowEvent, IntoValue, Route, Scope, Scopes, Tracer, - Value, Vm, -}; -use crate::diag::{bail, SourceResult, StrResult}; -use crate::file::FileId; -use crate::model::{DelayedErrors, ElemFunc, Introspector, Locator, Vt}; -use crate::syntax::ast::{self, AstNode, Expr, Ident}; -use crate::syntax::{Span, SyntaxNode}; -use crate::World; - -/// An evaluatable function. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Func { - /// The internal representation. - repr: Repr, - /// The span with which errors are reported when this function is called. - span: Span, -} - -/// The different kinds of function representations. -#[derive(Clone, PartialEq, Hash)] -enum Repr { - /// A native Rust function. - Native(&'static NativeFunc), - /// A function for an element. - Elem(ElemFunc), - /// A user-defined closure. - Closure(Arc<Prehashed<Closure>>), - /// A nested function with pre-applied arguments. - With(Arc<(Func, Args)>), -} - -impl Func { - /// The name of the function. - pub fn name(&self) -> Option<&str> { - match &self.repr { - Repr::Native(native) => Some(native.info.name), - Repr::Elem(func) => Some(func.info().name), - Repr::Closure(closure) => closure.name.as_deref(), - Repr::With(arc) => arc.0.name(), - } - } - - /// Extract details the function. - pub fn info(&self) -> Option<&FuncInfo> { - match &self.repr { - Repr::Native(native) => Some(&native.info), - Repr::Elem(func) => Some(func.info()), - Repr::Closure(_) => None, - Repr::With(arc) => arc.0.info(), - } - } - - /// The function's span. - pub fn span(&self) -> Span { - self.span - } - - /// Attach a span to this function if it doesn't already have one. - pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; - } - self - } - - /// Call the function with the given arguments. - pub fn call_vm(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> { - let _span = tracing::info_span!( - "call", - name = self.name().unwrap_or("<anon>"), - file = 0, - ); - - match &self.repr { - Repr::Native(native) => { - let value = (native.func)(vm, &mut args)?; - args.finish()?; - Ok(value) - } - Repr::Elem(func) => { - let value = func.construct(vm, &mut args)?; - args.finish()?; - Ok(Value::Content(value)) - } - Repr::Closure(closure) => { - // Determine the route inside the closure. - let fresh = Route::new(closure.location); - let route = - if vm.location.is_detached() { fresh.track() } else { vm.route }; - - Closure::call( - self, - vm.world(), - route, - vm.vt.introspector, - vm.vt.locator.track(), - TrackedMut::reborrow_mut(&mut vm.vt.delayed), - TrackedMut::reborrow_mut(&mut vm.vt.tracer), - vm.depth + 1, - args, - ) - } - Repr::With(arc) => { - args.items = arc.1.items.iter().cloned().chain(args.items).collect(); - arc.0.call_vm(vm, args) - } - } - } - - /// Call the function with a Vt. - #[tracing::instrument(skip_all)] - pub fn call_vt<T: IntoValue>( - &self, - vt: &mut Vt, - args: impl IntoIterator<Item = T>, - ) -> SourceResult<Value> { - let route = Route::default(); - let scopes = Scopes::new(None); - let mut locator = Locator::chained(vt.locator.track()); - let vt = Vt { - world: vt.world, - introspector: vt.introspector, - locator: &mut locator, - delayed: TrackedMut::reborrow_mut(&mut vt.delayed), - tracer: TrackedMut::reborrow_mut(&mut vt.tracer), - }; - let mut vm = Vm::new(vt, route.track(), FileId::detached(), scopes); - let args = Args::new(self.span(), args); - self.call_vm(&mut vm, args) - } - - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - let span = self.span; - Self { repr: Repr::With(Arc::new((self, args))), span } - } - - /// Extract the element function, if it is one. - pub fn element(&self) -> Option<ElemFunc> { - match self.repr { - Repr::Elem(func) => Some(func), - _ => None, - } - } - - /// Get a field from this function's scope, if possible. - pub fn get(&self, field: &str) -> StrResult<&Value> { - match &self.repr { - Repr::Native(func) => func.info.scope.get(field).ok_or_else(|| { - eco_format!( - "function `{}` does not contain field `{}`", - func.info.name, - field - ) - }), - Repr::Elem(func) => func.info().scope.get(field).ok_or_else(|| { - eco_format!( - "function `{}` does not contain field `{}`", - func.name(), - field - ) - }), - Repr::Closure(_) => { - Err(eco_format!("cannot access fields on user-defined functions")) - } - Repr::With(arc) => arc.0.get(field), - } - } -} - -impl Debug for Func { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.name() { - Some(name) => write!(f, "{name}"), - None => f.write_str("(..) => .."), - } - } -} - -impl PartialEq for Func { - fn eq(&self, other: &Self) -> bool { - self.repr == other.repr - } -} - -impl From<Repr> for Func { - fn from(repr: Repr) -> Self { - Self { repr, span: Span::detached() } - } -} - -impl From<ElemFunc> for Func { - fn from(func: ElemFunc) -> Self { - Repr::Elem(func).into() - } -} - -/// A Typst function defined by a native Rust function. -pub struct NativeFunc { - /// The function's implementation. - pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, - /// Details about the function. - pub info: Lazy<FuncInfo>, -} - -impl PartialEq for NativeFunc { - fn eq(&self, other: &Self) -> bool { - self.func as usize == other.func as usize - } -} - -impl Eq for NativeFunc {} - -impl Hash for NativeFunc { - fn hash<H: Hasher>(&self, state: &mut H) { - (self.func as usize).hash(state); - } -} - -impl From<&'static NativeFunc> for Func { - fn from(native: &'static NativeFunc) -> Self { - Repr::Native(native).into() - } -} - -cast! { - &'static NativeFunc, - self => Value::Func(self.into()), -} - -/// Details about a function. -#[derive(Debug, Clone)] -pub struct FuncInfo { - /// The function's name. - pub name: &'static str, - /// The display name of the function. - pub display: &'static str, - /// 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 values for the return value. - pub returns: CastInfo, - /// The function's own scope of fields and sub-functions. - pub scope: Scope, -} - -impl FuncInfo { - /// Get the parameter info for a parameter with the given name - pub fn param(&self, name: &str) -> Option<&ParamInfo> { - self.params.iter().find(|param| param.name == name) - } -} - -/// Describes a named parameter. -#[derive(Debug, Clone)] -pub struct ParamInfo { - /// The parameter's name. - pub name: &'static str, - /// Documentation for the parameter. - pub docs: &'static str, - /// Valid values for the parameter. - pub cast: CastInfo, - /// Creates an instance of the parameter's default value. - pub default: Option<fn() -> Value>, - /// Is the parameter positional? - pub positional: bool, - /// Is the parameter named? - /// - /// Can be true even if `positional` is true if the parameter can be given - /// in both variants. - pub named: bool, - /// Can the parameter be given any number of times? - pub variadic: bool, - /// Is the parameter required? - pub required: bool, - /// Is the parameter settable with a set rule? - pub settable: bool, -} - -/// A user-defined closure. -#[derive(Hash)] -pub(super) struct Closure { - /// The source file where the closure was defined. - pub location: FileId, - /// The name of the closure. - pub name: Option<Ident>, - /// Captured values from outer scopes. - pub captured: Scope, - /// The list of parameters. - pub params: Vec<Param>, - /// The expression the closure should evaluate to. - pub body: Expr, -} - -/// A closure parameter. -#[derive(Hash)] -pub enum Param { - /// A positional parameter: `x`. - Pos(ast::Pattern), - /// A named parameter with a default value: `draw: false`. - Named(Ident, Value), - /// An argument sink: `..args`. - Sink(Option<Ident>), -} - -impl Closure { - /// Call the function in the context with the arguments. - #[comemo::memoize] - #[tracing::instrument(skip_all)] - #[allow(clippy::too_many_arguments)] - fn call( - this: &Func, - world: Tracked<dyn World + '_>, - route: Tracked<Route>, - introspector: Tracked<Introspector>, - locator: Tracked<Locator>, - delayed: TrackedMut<DelayedErrors>, - tracer: TrackedMut<Tracer>, - depth: usize, - mut args: Args, - ) -> SourceResult<Value> { - let closure = match &this.repr { - Repr::Closure(closure) => closure, - _ => panic!("`this` must be a closure"), - }; - - // Don't leak the scopes from the call site. Instead, we use the scope - // of captured variables we collected earlier. - let mut scopes = Scopes::new(None); - scopes.top = closure.captured.clone(); - - // Prepare VT. - let mut locator = Locator::chained(locator); - let vt = Vt { - world, - introspector, - locator: &mut locator, - delayed, - tracer, - }; - - // Prepare VM. - let mut vm = Vm::new(vt, route, closure.location, scopes); - vm.depth = depth; - - // Provide the closure itself for recursive calls. - if let Some(name) = &closure.name { - vm.define(name.clone(), Value::Func(this.clone())); - } - - // 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(); - let sink_size = num_pos_args.checked_sub(num_pos_params); - - let mut sink = None; - let mut sink_pos_values = None; - for p in &closure.params { - match p { - Param::Pos(pattern) => match pattern { - ast::Pattern::Normal(ast::Expr::Ident(ident)) => { - vm.define(ident.clone(), args.expect::<Value>(ident)?) - } - ast::Pattern::Normal(_) => unreachable!(), - _ => { - pattern.define( - &mut vm, - args.expect::<Value>("pattern parameter")?, - )?; - } - }, - Param::Sink(ident) => { - sink = ident.clone(); - if let Some(sink_size) = sink_size { - sink_pos_values = Some(args.consume(sink_size)?); - } - } - Param::Named(ident, default) => { - let value = - args.named::<Value>(ident)?.unwrap_or_else(|| default.clone()); - vm.define(ident.clone(), value); - } - } - } - - if let Some(sink) = sink { - let mut remaining_args = args.take(); - if let Some(sink_pos_values) = sink_pos_values { - remaining_args.items.extend(sink_pos_values); - } - vm.define(sink, remaining_args); - } - - // Ensure all arguments have been used. - args.finish()?; - - // Handle control flow. - let result = closure.body.eval(&mut vm); - match vm.flow { - Some(FlowEvent::Return(_, Some(explicit))) => return Ok(explicit), - Some(FlowEvent::Return(_, None)) => {} - Some(flow) => bail!(flow.forbidden()), - None => {} - } - - result - } -} - -impl From<Closure> for Func { - fn from(closure: Closure) -> Self { - Repr::Closure(Arc::new(Prehashed::new(closure))).into() - } -} - -cast! { - Closure, - self => Value::Func(self.into()), -} - -/// A visitor that determines which variables to capture for a closure. -pub(super) struct CapturesVisitor<'a> { - external: &'a Scopes<'a>, - internal: Scopes<'a>, - captures: Scope, -} - -impl<'a> CapturesVisitor<'a> { - /// Create a new visitor for the given external scopes. - pub fn new(external: &'a Scopes) -> Self { - Self { - external, - internal: Scopes::new(None), - captures: Scope::new(), - } - } - - /// Return the scope of captured variables. - pub fn finish(self) -> Scope { - self.captures - } - - /// Visit any node and collect all captured variables. - #[tracing::instrument(skip_all)] - pub fn visit(&mut self, node: &SyntaxNode) { - match node.cast() { - // Every identifier is a potential variable that we need to capture. - // Identifiers that shouldn't count as captures because they - // actually bind a new name are handled below (individually through - // the expressions that contain them). - Some(ast::Expr::Ident(ident)) => self.capture(ident), - Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident), - - // Code and content blocks create a scope. - Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => { - self.internal.enter(); - for child in node.children() { - self.visit(child); - } - self.internal.exit(); - } - - // A closure contains parameter bindings, which are bound before the - // body is evaluated. Care must be taken so that the default values - // of named parameters cannot access previous parameter bindings. - Some(ast::Expr::Closure(expr)) => { - for param in expr.params().children() { - if let ast::Param::Named(named) = param { - self.visit(named.expr().as_untyped()); - } - } - - self.internal.enter(); - if let Some(name) = expr.name() { - self.bind(name); - } - - for param in expr.params().children() { - match param { - ast::Param::Pos(pattern) => { - for ident in pattern.idents() { - self.bind(ident); - } - } - ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(spread) => { - self.bind(spread.name().unwrap_or_default()) - } - } - } - - self.visit(expr.body().as_untyped()); - self.internal.exit(); - } - - // A let expression contains a binding, but that binding is only - // active after the body is evaluated. - Some(ast::Expr::Let(expr)) => { - if let Some(init) = expr.init() { - self.visit(init.as_untyped()); - } - - for ident in expr.kind().idents() { - self.bind(ident); - } - } - - // A for loop contains one or two bindings in its pattern. These are - // active after the iterable is evaluated but before the body is - // evaluated. - Some(ast::Expr::For(expr)) => { - self.visit(expr.iter().as_untyped()); - self.internal.enter(); - - let pattern = expr.pattern(); - for ident in pattern.idents() { - self.bind(ident); - } - - self.visit(expr.body().as_untyped()); - self.internal.exit(); - } - - // An import contains items, but these are active only after the - // path is evaluated. - Some(ast::Expr::Import(expr)) => { - self.visit(expr.source().as_untyped()); - if let Some(ast::Imports::Items(items)) = expr.imports() { - for item in items { - self.bind(item); - } - } - } - - // Everything else is traversed from left to right. - _ => { - for child in node.children() { - self.visit(child); - } - } - } - } - - /// Bind a new internal variable. - fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - fn capture(&mut self, ident: ast::Ident) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } - - /// Capture a variable in math mode if it isn't internal. - fn capture_in_math(&mut self, ident: ast::MathIdent) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get_in_math(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::parse; - - #[track_caller] - fn test(text: &str, result: &[&str]) { - let mut scopes = Scopes::new(None); - scopes.top.define("f", 0); - scopes.top.define("x", 0); - scopes.top.define("y", 0); - scopes.top.define("z", 0); - - let mut visitor = CapturesVisitor::new(&scopes); - let root = parse(text); - visitor.visit(&root); - - let captures = visitor.finish(); - let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); - names.sort(); - - assert_eq!(names, result); - } - - #[test] - fn test_captures() { - // Let binding and function definition. - test("#let x = x", &["x"]); - test("#let x; #(x + y)", &["y"]); - test("#let f(x, y) = x + y", &[]); - test("#let f(x, y) = f", &[]); - test("#let f = (x, y) => f", &["f"]); - - // Closure with different kinds of params. - test("#((x, y) => x + z)", &["z"]); - test("#((x: y, z) => x + z)", &["y"]); - test("#((..x) => x + y)", &["y"]); - test("#((x, y: x + z) => x + y)", &["x", "z"]); - test("#{x => x; x}", &["x"]); - - // Show rule. - test("#show y: x => x", &["y"]); - test("#show y: x => x + z", &["y", "z"]); - test("#show x: x => x", &["x"]); - - // For loop. - test("#for x in y { x + z }", &["y", "z"]); - test("#for (x, y) in y { x + y }", &["y"]); - test("#for x in y {} #x", &["x", "y"]); - - // Import. - test("#import z: x, y", &["z"]); - test("#import x + y: x, y, z", &["x", "y"]); - - // Blocks. - test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("#[#let x = 1]#x", &["x"]); - } -} diff --git a/src/eval/int.rs b/src/eval/int.rs deleted file mode 100644 index 4e081617..00000000 --- a/src/eval/int.rs +++ /dev/null @@ -1,81 +0,0 @@ -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/library.rs b/src/eval/library.rs deleted file mode 100644 index 1b05de83..00000000 --- a/src/eval/library.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; - -use comemo::Tracked; -use ecow::EcoString; -use std::sync::OnceLock; - -use super::{Args, Dynamic, Module, Value, Vm}; -use crate::diag::SourceResult; -use crate::doc::Document; -use crate::geom::{Abs, Dir}; -use crate::model::{Content, ElemFunc, Introspector, Label, StyleChain, Styles, Vt}; -use crate::syntax::Span; -use crate::util::hash128; - -/// Definition of Typst's standard library. -#[derive(Debug, Clone, Hash)] -pub struct Library { - /// The scope containing definitions that are available everywhere. - pub global: Module, - /// The scope containing definitions available in math mode. - pub math: Module, - /// The default properties for page size, font selection and so on. - pub styles: Styles, - /// Defines which standard library items fulfill which syntactical roles. - pub items: LangItems, -} - -/// Definition of library items the language is aware of. -#[derive(Clone)] -pub struct LangItems { - /// The root layout function. - pub layout: - fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult<Document>, - /// Access the em size. - pub em: fn(StyleChain) -> Abs, - /// Access the text direction. - pub dir: fn(StyleChain) -> Dir, - /// Whitespace. - pub space: fn() -> Content, - /// A forced line break: `\`. - pub linebreak: fn() -> Content, - /// Plain text without markup. - pub text: fn(text: EcoString) -> Content, - /// The text function. - pub text_func: ElemFunc, - /// Get the string if this is a text element. - pub text_str: fn(&Content) -> Option<EcoString>, - /// A smart quote: `'` or `"`. - pub smart_quote: fn(double: bool) -> Content, - /// A paragraph break. - pub parbreak: fn() -> Content, - /// Strong content: `*Strong*`. - pub strong: fn(body: Content) -> Content, - /// Emphasized content: `_Emphasized_`. - pub emph: fn(body: Content) -> Content, - /// Raw text with optional syntax highlighting: `` `...` ``. - pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content, - /// The language names and tags supported by raw text. - pub raw_languages: fn() -> Vec<(&'static str, Vec<&'static str>)>, - /// A hyperlink: `https://typst.org`. - pub link: fn(url: EcoString) -> Content, - /// A reference: `@target`, `@target[..]`. - pub reference: fn(target: Label, supplement: Option<Content>) -> Content, - /// The keys contained in the bibliography and short descriptions of them. - #[allow(clippy::type_complexity)] - pub bibliography_keys: - fn(introspector: Tracked<Introspector>) -> Vec<(EcoString, Option<EcoString>)>, - /// A section heading: `= Introduction`. - pub heading: fn(level: NonZeroUsize, body: Content) -> Content, - /// The heading function. - pub heading_func: ElemFunc, - /// An item in a bullet list: `- ...`. - pub list_item: fn(body: Content) -> Content, - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - pub enum_item: fn(number: Option<usize>, body: Content) -> Content, - /// An item in a term list: `/ Term: Details`. - pub term_item: fn(term: Content, description: Content) -> Content, - /// A mathematical equation: `$x$`, `$ x^2 $`. - pub equation: fn(body: Content, block: bool) -> Content, - /// An alignment point in math: `&`. - pub math_align_point: fn() -> Content, - /// Matched delimiters in math: `[x + y]`. - pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, - /// A base with optional attachments in math: `a_1^2`. - #[allow(clippy::type_complexity)] - pub math_attach: fn( - base: Content, - // Positioned smartly. - t: Option<Content>, - b: Option<Content>, - // Fixed positions. - tl: Option<Content>, - bl: Option<Content>, - tr: Option<Content>, - br: Option<Content>, - ) -> Content, - /// A base with an accent: `arrow(x)`. - pub math_accent: fn(base: Content, accent: char) -> Content, - /// A fraction in math: `x/2`. - pub math_frac: fn(num: Content, denom: Content) -> Content, - /// A root in math: `√x`, `∛x` or `∜x`. - pub math_root: fn(index: Option<Content>, radicand: Content) -> Content, - /// Dispatch a method on a library value. - pub library_method: fn( - vm: &mut Vm, - dynamic: &Dynamic, - method: &str, - args: Args, - span: Span, - ) -> SourceResult<Value>, -} - -impl Debug for LangItems { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("LangItems { .. }") - } -} - -impl Hash for LangItems { - fn hash<H: Hasher>(&self, state: &mut H) { - (self.layout as usize).hash(state); - (self.em as usize).hash(state); - (self.dir as usize).hash(state); - self.space.hash(state); - self.linebreak.hash(state); - self.text.hash(state); - self.text_func.hash(state); - (self.text_str as usize).hash(state); - self.smart_quote.hash(state); - self.parbreak.hash(state); - self.strong.hash(state); - self.emph.hash(state); - self.raw.hash(state); - self.raw_languages.hash(state); - self.link.hash(state); - self.reference.hash(state); - (self.bibliography_keys as usize).hash(state); - self.heading.hash(state); - self.heading_func.hash(state); - self.list_item.hash(state); - self.enum_item.hash(state); - self.term_item.hash(state); - self.equation.hash(state); - self.math_align_point.hash(state); - self.math_delimited.hash(state); - self.math_attach.hash(state); - self.math_accent.hash(state); - self.math_frac.hash(state); - self.math_root.hash(state); - (self.library_method as usize).hash(state); - } -} - -/// Global storage for lang items. -#[doc(hidden)] -pub static LANG_ITEMS: OnceLock<LangItems> = OnceLock::new(); - -/// Set the lang items. -/// -/// This is a hack :( -/// -/// Passing the lang items everywhere they are needed (especially text related -/// things) is very painful. By storing them globally, in theory, we break -/// incremental, but only when different sets of lang items are used in the same -/// program. For this reason, if this function is called multiple times, the -/// items must be the same (and this is enforced). -pub fn set_lang_items(items: LangItems) { - if let Err(items) = LANG_ITEMS.set(items) { - let first = hash128(LANG_ITEMS.get().unwrap()); - let second = hash128(&items); - assert_eq!(first, second, "set differing lang items"); - } -} - -/// Access a lang item. -macro_rules! item { - ($name:ident) => { - $crate::eval::LANG_ITEMS.get().unwrap().$name - }; -} diff --git a/src/eval/methods.rs b/src/eval/methods.rs deleted file mode 100644 index 62ac4095..00000000 --- a/src/eval/methods.rs +++ /dev/null @@ -1,373 +0,0 @@ -//! Methods on values. - -use ecow::EcoString; - -use super::{Args, IntoValue, Str, Value, Vm}; -use crate::diag::{At, SourceResult}; -use crate::eval::Datetime; -use crate::model::{Location, Selector}; -use crate::syntax::Span; - -/// Call a method on a value. -pub fn call( - vm: &mut Vm, - value: Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - - let output = match value { - Value::Color(color) => match method { - "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" => 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")?; - string.at(index, default.as_deref()).at(span)?.into_value() - } - "slice" => { - let start = args.expect("start")?; - let mut end = args.eat()?; - if end.is_none() { - end = args.named("count")?.map(|c: i64| start + c); - } - string.slice(start, end).at(span)?.into_value() - } - "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")?; - 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); - string.trim(pattern, at, repeat).into_value() - } - "split" => string.split(args.eat()?).into_value(), - _ => return missing(), - }, - - Value::Content(content) => match method { - "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" => content.dict().into_value(), - "location" => content - .location() - .ok_or("this method can only be called on content returned by query(..)") - .at(span)? - .into_value(), - _ => return missing(), - }, - - Value::Array(array) => match method { - "len" => array.len().into_value(), - "first" => array.first().at(span)?.clone(), - "last" => array.last().at(span)?.clone(), - "at" => array - .at(args.expect("index")?, args.named("default")?.as_ref()) - .at(span)? - .clone(), - "slice" => { - let start = args.expect("start")?; - let mut end = args.eat()?; - if end.is_none() { - end = args.named("count")?.map(|c: i64| start + c); - } - array.slice(start, end).at(span)?.into_value() - } - "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" => 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" => 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" => dict.len().into_value(), - "at" => dict - .at(&args.expect::<Str>("key")?, args.named("default")?.as_ref()) - .at(span)? - .clone(), - "keys" => dict.keys().into_value(), - "values" => dict.values().into_value(), - "pairs" => dict.pairs().into_value(), - _ => return missing(), - }, - - Value::Func(func) => match method { - "with" => func.with(args.take()).into_value(), - "where" => { - let fields = args.to_named(); - args.items.retain(|arg| arg.name.is_none()); - 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" => 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_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_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_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_value() - } - _ => return missing(), - } - } else if let Some(&datetime) = dynamic.downcast::<Datetime>() { - match method { - "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 { - return (vm.items.library_method)(vm, &dynamic, method, args, span); - } - } - - _ => return missing(), - }; - - args.finish()?; - Ok(output) -} - -/// Call a mutating method on a value. -pub fn call_mut( - value: &mut Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - let mut output = Value::None; - - match value { - Value::Array(array) => match method { - "push" => array.push(args.expect("value")?), - "pop" => output = array.pop().at(span)?, - "insert" => { - array.insert(args.expect("index")?, args.expect("value")?).at(span)? - } - "remove" => output = array.remove(args.expect("index")?).at(span)?, - _ => return missing(), - }, - - Value::Dict(dict) => match method { - "insert" => dict.insert(args.expect::<Str>("key")?, args.expect("value")?), - "remove" => { - output = dict.remove(&args.expect::<EcoString>("key")?).at(span)? - } - _ => return missing(), - }, - - _ => return missing(), - } - - args.finish()?; - Ok(output) -} - -/// Call an accessor method on a value. -pub fn call_access<'a>( - value: &'a mut Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<&'a mut Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - - let slot = match value { - Value::Array(array) => match method { - "first" => array.first_mut().at(span)?, - "last" => array.last_mut().at(span)?, - "at" => array.at_mut(args.expect("index")?).at(span)?, - _ => return missing(), - }, - Value::Dict(dict) => match method { - "at" => dict.at_mut(&args.expect::<Str>("key")?).at(span)?, - _ => return missing(), - }, - _ => return missing(), - }; - - args.finish()?; - Ok(slot) -} - -/// Whether a specific method is mutating. -pub fn is_mutating(method: &str) -> bool { - matches!(method, "push" | "pop" | "insert" | "remove") -} - -/// Whether a specific method is an accessor. -pub fn is_accessor(method: &str) -> bool { - matches!(method, "first" | "last" | "at") -} - -/// The missing method error message. -#[cold] -fn missing_method(type_name: &str, method: &str) -> String { - format!("type {type_name} has no method `{method}`") -} - -/// List the available methods for a type and whether they take arguments. -pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { - match type_name { - "color" => &[("lighten", true), ("darken", true), ("negate", false)], - "string" => &[ - ("len", false), - ("at", true), - ("clusters", false), - ("codepoints", false), - ("contains", true), - ("ends-with", true), - ("find", true), - ("first", false), - ("last", false), - ("match", true), - ("matches", true), - ("position", true), - ("replace", true), - ("slice", true), - ("split", true), - ("starts-with", true), - ("trim", true), - ], - "content" => &[ - ("func", false), - ("has", true), - ("at", true), - ("fields", false), - ("location", false), - ], - "array" => &[ - ("all", true), - ("any", true), - ("at", true), - ("contains", true), - ("filter", true), - ("find", true), - ("first", false), - ("flatten", false), - ("fold", true), - ("insert", true), - ("split", true), - ("join", true), - ("last", false), - ("len", false), - ("map", true), - ("pop", false), - ("position", true), - ("push", true), - ("remove", true), - ("rev", false), - ("slice", true), - ("sorted", false), - ("enumerate", false), - ("zip", true), - ], - "dictionary" => &[ - ("at", true), - ("insert", true), - ("keys", false), - ("len", false), - ("pairs", false), - ("remove", true), - ("values", false), - ], - "function" => &[("where", true), ("with", true)], - "arguments" => &[("named", false), ("pos", false)], - "location" => &[("page", false), ("position", false), ("page-numbering", false)], - "selector" => &[("or", true), ("and", true), ("before", true), ("after", true)], - "counter" => &[ - ("display", true), - ("at", true), - ("final", true), - ("step", true), - ("update", true), - ], - "state" => &[("display", true), ("at", true), ("final", true), ("update", true)], - _ => &[], - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs deleted file mode 100644 index fe28e3f3..00000000 --- a/src/eval/mod.rs +++ /dev/null @@ -1,1908 +0,0 @@ -//! Evaluation of markup into modules. - -#[macro_use] -mod library; -#[macro_use] -mod cast; -#[macro_use] -mod array; -#[macro_use] -mod dict; -#[macro_use] -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 { - 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; -use std::path::Path; - -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, -}; -use crate::file::{FileId, PackageManifest, PackageSpec}; -use crate::model::{ - Content, DelayedErrors, Introspector, Label, Locator, Recipe, ShowableSelector, - Styles, Transform, Unlabellable, Vt, -}; -use crate::syntax::ast::{self, AstNode}; -use crate::syntax::{parse_code, Source, Span, Spanned, SyntaxKind, SyntaxNode}; -use crate::World; - -const MAX_ITERATIONS: usize = 10_000; -const MAX_CALL_DEPTH: usize = 64; - -/// Evaluate a source file and return the resulting module. -#[comemo::memoize] -#[tracing::instrument(skip(world, route, tracer, source))] -pub fn eval( - world: Tracked<dyn World + '_>, - route: Tracked<Route>, - tracer: TrackedMut<Tracer>, - source: &Source, -) -> SourceResult<Module> { - // Prevent cyclic evaluation. - let id = source.id(); - if route.contains(id) { - panic!("Tried to cyclicly evaluate {}", id.path().display()); - } - - // Hook up the lang items. - let library = world.library(); - set_lang_items(library.items.clone()); - - // Prepare VT. - let mut locator = Locator::default(); - let introspector = Introspector::default(); - let mut delayed = DelayedErrors::default(); - let vt = Vt { - world, - introspector: introspector.track(), - locator: &mut locator, - delayed: delayed.track_mut(), - tracer, - }; - - // Prepare VM. - let route = Route::insert(route, id); - let scopes = Scopes::new(Some(library)); - let mut vm = Vm::new(vt, route.track(), id, scopes); - let root = match source.root().cast::<ast::Markup>() { - Some(markup) if vm.traced.is_some() => markup, - _ => source.ast()?, - }; - - // Evaluate the module. - let result = root.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - // Assemble the module. - let name = id.path().file_stem().unwrap_or_default().to_string_lossy(); - Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?)) -} - -/// Evaluate a string as code and return the resulting value. -/// -/// Everything in the output is associated with the given `span`. -#[comemo::memoize] -pub fn eval_string( - world: Tracked<dyn World + '_>, - code: &str, - span: Span, -) -> SourceResult<Value> { - let mut root = parse_code(code); - root.synthesize(span); - - let errors = root.errors(); - if !errors.is_empty() { - return Err(Box::new(errors)); - } - - // Prepare VT. - let mut tracer = Tracer::default(); - let mut locator = Locator::default(); - let mut delayed = DelayedErrors::default(); - let introspector = Introspector::default(); - let vt = Vt { - world, - introspector: introspector.track(), - locator: &mut locator, - delayed: delayed.track_mut(), - tracer: tracer.track_mut(), - }; - - // Prepare VM. - let route = Route::default(); - let id = FileId::detached(); - let scopes = Scopes::new(Some(world.library())); - let mut vm = Vm::new(vt, route.track(), id, scopes); - - // Evaluate the code. - let code = root.cast::<ast::Code>().unwrap(); - let result = code.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - result -} - -/// A virtual machine. -/// -/// Holds the state needed to [evaluate](eval) Typst sources. A new -/// virtual machine is created for each module evaluation and function call. -pub struct Vm<'a> { - /// The underlying virtual typesetter. - pub vt: Vt<'a>, - /// The language items. - items: LangItems, - /// The route of source ids the VM took to reach its current location. - route: Tracked<'a, Route<'a>>, - /// The current location. - location: FileId, - /// A control flow event that is currently happening. - flow: Option<FlowEvent>, - /// The stack of scopes. - scopes: Scopes<'a>, - /// The current call depth. - depth: usize, - /// A span that is currently traced. - traced: Option<Span>, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - fn new( - vt: Vt<'a>, - route: Tracked<'a, Route>, - location: FileId, - scopes: Scopes<'a>, - ) -> Self { - let traced = vt.tracer.span(location); - let items = vt.world.library().items.clone(); - Self { - vt, - items, - route, - location, - flow: None, - scopes, - depth: 0, - traced, - } - } - - /// Access the underlying world. - pub fn world(&self) -> Tracked<'a, dyn World + 'a> { - self.vt.world - } - - /// The location to which paths are relative currently. - pub fn location(&self) -> FileId { - self.location - } - - /// Define a variable in the current scope. - #[tracing::instrument(skip_all)] - 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()); - } - self.scopes.top.define(var.take(), value); - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum FlowEvent { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option<Value>), -} - -impl FlowEvent { - /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> SourceError { - match *self { - Self::Break(span) => { - error!(span, "cannot break outside of loop") - } - Self::Continue(span) => { - error!(span, "cannot continue outside of loop") - } - Self::Return(span, _) => { - error!(span, "cannot return outside of function") - } - } - } -} - -/// A route of source ids. -#[derive(Default)] -pub struct Route<'a> { - // We need to override the constraint's lifetime here so that `Tracked` is - // covariant over the constraint. If it becomes invariant, we're in for a - // world of lifetime pain. - outer: Option<Tracked<'a, Self, <Route<'static> as Validate>::Constraint>>, - id: Option<FileId>, -} - -impl<'a> Route<'a> { - /// Create a new route with just one entry. - pub fn new(id: FileId) -> Self { - Self { id: Some(id), outer: None } - } - - /// Insert a new id into the route. - /// - /// You must guarantee that `outer` lives longer than the resulting - /// route is ever used. - pub fn insert(outer: Tracked<'a, Self>, id: FileId) -> Self { - Route { outer: Some(outer), id: Some(id) } - } - - /// Start tracking this locator. - /// - /// In comparison to [`Track::track`], this method skips this chain link - /// if it does not contribute anything. - pub fn track(&self) -> Tracked<'_, Self> { - match self.outer { - Some(outer) if self.id.is_none() => outer, - _ => Track::track(self), - } - } -} - -#[comemo::track] -impl<'a> Route<'a> { - /// Whether the given id is part of the route. - fn contains(&self, id: FileId) -> bool { - self.id == Some(id) || self.outer.map_or(false, |outer| outer.contains(id)) - } -} - -/// Traces which values existed for an expression at a span. -#[derive(Default, Clone)] -pub struct Tracer { - span: Option<Span>, - values: Vec<Value>, -} - -impl Tracer { - /// The maximum number of traced items. - pub const MAX: usize = 10; - - /// Create a new tracer, possibly with a span under inspection. - pub fn new(span: Option<Span>) -> Self { - Self { span, values: vec![] } - } - - /// Get the traced values. - pub fn finish(self) -> Vec<Value> { - self.values - } -} - -#[comemo::track] -impl Tracer { - /// The traced span if it is part of the given source file. - fn span(&self, id: FileId) -> Option<Span> { - if self.span.map(Span::id) == Some(id) { - self.span - } else { - None - } - } - - /// Trace a value for the span. - fn trace(&mut self, v: Value) { - if self.values.len() < Self::MAX { - self.values.push(v); - } - } -} - -/// Evaluate an expression. -pub(super) trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output>; -} - -impl Eval for ast::Markup { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - eval_markup(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of markup. -fn eval_markup( - vm: &mut Vm, - exprs: &mut impl Iterator<Item = ast::Expr>, -) -> SourceResult<Content> { - let flow = vm.flow.take(); - let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default()); - - while let Some(expr) = exprs.next() { - match expr { - ast::Expr::Set(set) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - seq.push(eval_markup(vm, exprs)?.styled_with_map(styles)) - } - ast::Expr::Show(show) => { - let recipe = show.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_markup(vm, exprs)?; - seq.push(tail.styled_with_recipe(vm, recipe)?) - } - expr => match expr.eval(vm)? { - Value::Label(label) => { - if let Some(elem) = - seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>()) - { - *elem = mem::take(elem).labelled(label); - } - } - value => seq.push(value.display().spanned(expr.span())), - }, - } - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(Content::sequence(seq)) -} - -impl Eval for ast::Expr { - type Output = Value; - - #[tracing::instrument(name = "Expr::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - let forbidden = |name| { - error!(span, "{} is only allowed directly in code and content blocks", name) - }; - - let v = match self { - Self::Text(v) => v.eval(vm).map(Value::Content), - Self::Space(v) => v.eval(vm).map(Value::Content), - Self::Linebreak(v) => v.eval(vm).map(Value::Content), - Self::Parbreak(v) => v.eval(vm).map(Value::Content), - Self::Escape(v) => v.eval(vm), - Self::Shorthand(v) => v.eval(vm), - Self::SmartQuote(v) => v.eval(vm).map(Value::Content), - Self::Strong(v) => v.eval(vm).map(Value::Content), - Self::Emph(v) => v.eval(vm).map(Value::Content), - Self::Raw(v) => v.eval(vm).map(Value::Content), - Self::Link(v) => v.eval(vm).map(Value::Content), - Self::Label(v) => v.eval(vm), - Self::Ref(v) => v.eval(vm).map(Value::Content), - Self::Heading(v) => v.eval(vm).map(Value::Content), - Self::List(v) => v.eval(vm).map(Value::Content), - Self::Enum(v) => v.eval(vm).map(Value::Content), - Self::Term(v) => v.eval(vm).map(Value::Content), - Self::Equation(v) => v.eval(vm).map(Value::Content), - Self::Math(v) => v.eval(vm).map(Value::Content), - Self::MathIdent(v) => v.eval(vm), - Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), - Self::MathDelimited(v) => v.eval(vm).map(Value::Content), - Self::MathAttach(v) => v.eval(vm).map(Value::Content), - Self::MathFrac(v) => v.eval(vm).map(Value::Content), - Self::MathRoot(v) => v.eval(vm).map(Value::Content), - Self::Ident(v) => v.eval(vm), - Self::None(v) => v.eval(vm), - Self::Auto(v) => v.eval(vm), - Self::Bool(v) => v.eval(vm), - Self::Int(v) => v.eval(vm), - Self::Float(v) => v.eval(vm), - Self::Numeric(v) => v.eval(vm), - Self::Str(v) => v.eval(vm), - Self::Code(v) => v.eval(vm), - Self::Content(v) => v.eval(vm).map(Value::Content), - Self::Array(v) => v.eval(vm).map(Value::Array), - Self::Dict(v) => v.eval(vm).map(Value::Dict), - Self::Parenthesized(v) => v.eval(vm), - Self::FieldAccess(v) => v.eval(vm), - Self::FuncCall(v) => v.eval(vm), - Self::Closure(v) => v.eval(vm), - Self::Unary(v) => v.eval(vm), - Self::Binary(v) => v.eval(vm), - Self::Let(v) => v.eval(vm), - Self::DestructAssign(v) => v.eval(vm), - Self::Set(_) => bail!(forbidden("set")), - Self::Show(_) => bail!(forbidden("show")), - Self::Conditional(v) => v.eval(vm), - Self::While(v) => v.eval(vm), - Self::For(v) => v.eval(vm), - Self::Import(v) => v.eval(vm), - Self::Include(v) => v.eval(vm).map(Value::Content), - Self::Break(v) => v.eval(vm), - Self::Continue(v) => v.eval(vm), - Self::Return(v) => v.eval(vm), - }? - .spanned(span); - - if vm.traced == Some(span) { - vm.vt.tracer.trace(v.clone()); - } - - Ok(v) - } -} - -impl ast::Expr { - fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok(self.eval(vm)?.display().spanned(self.span())) - } -} - -impl Eval for ast::Text { - type Output = Content; - - #[tracing::instrument(name = "Text::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.text)(self.get().clone())) - } -} - -impl Eval for ast::Space { - type Output = Content; - - #[tracing::instrument(name = "Space::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.space)()) - } -} - -impl Eval for ast::Linebreak { - type Output = Content; - - #[tracing::instrument(name = "Linebreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.linebreak)()) - } -} - -impl Eval for ast::Parbreak { - type Output = Content; - - #[tracing::instrument(name = "Parbreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.parbreak)()) - } -} - -impl Eval for ast::Escape { - type Output = Value; - - #[tracing::instrument(name = "Escape::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::Shorthand { - type Output = Value; - - #[tracing::instrument(name = "Shorthand::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::SmartQuote { - type Output = Content; - - #[tracing::instrument(name = "SmartQuote::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.smart_quote)(self.double())) - } -} - -impl Eval for ast::Strong { - type Output = Content; - - #[tracing::instrument(name = "Strong::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.strong)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Emph { - type Output = Content; - - #[tracing::instrument(name = "Emph::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.emph)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Raw { - type Output = Content; - - #[tracing::instrument(name = "Raw::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let text = self.text(); - let lang = self.lang().map(Into::into); - let block = self.block(); - Ok((vm.items.raw)(text, lang, block)) - } -} - -impl Eval for ast::Link { - type Output = Content; - - #[tracing::instrument(name = "Link::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.link)(self.get().clone())) - } -} - -impl Eval for ast::Label { - type Output = Value; - - #[tracing::instrument(name = "Label::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Label(Label(self.get().into()))) - } -} - -impl Eval for ast::Ref { - type Output = Content; - - #[tracing::instrument(name = "Ref::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let label = Label(self.target().into()); - let supplement = self.supplement().map(|block| block.eval(vm)).transpose()?; - Ok((vm.items.reference)(label, supplement)) - } -} - -impl Eval for ast::Heading { - type Output = Content; - - #[tracing::instrument(name = "Heading::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let level = self.level(); - let body = self.body().eval(vm)?; - Ok((vm.items.heading)(level, body)) - } -} - -impl Eval for ast::ListItem { - type Output = Content; - - #[tracing::instrument(name = "ListItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.list_item)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::EnumItem { - type Output = Content; - - #[tracing::instrument(name = "EnumItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let number = self.number(); - let body = self.body().eval(vm)?; - Ok((vm.items.enum_item)(number, body)) - } -} - -impl Eval for ast::TermItem { - type Output = Content; - - #[tracing::instrument(name = "TermItem::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let term = self.term().eval(vm)?; - let description = self.description().eval(vm)?; - Ok((vm.items.term_item)(term, description)) - } -} - -impl Eval for ast::Equation { - type Output = Content; - - #[tracing::instrument(name = "Equation::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let body = self.body().eval(vm)?; - let block = self.block(); - Ok((vm.items.equation)(body, block)) - } -} - -impl Eval for ast::Math { - type Output = Content; - - #[tracing::instrument(name = "Math::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::sequence( - self.exprs() - .map(|expr| expr.eval_display(vm)) - .collect::<SourceResult<Vec<_>>>()?, - )) - } -} - -impl Eval for ast::MathIdent { - type Output = Value; - - #[tracing::instrument(name = "MathIdent::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.get_in_math(self).cloned().at(self.span()) - } -} - -impl Eval for ast::MathAlignPoint { - type Output = Content; - - #[tracing::instrument(name = "MathAlignPoint::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_align_point)()) - } -} - -impl Eval for ast::MathDelimited { - type Output = Content; - - #[tracing::instrument(name = "MathDelimited::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let open = self.open().eval_display(vm)?; - let body = self.body().eval(vm)?; - let close = self.close().eval_display(vm)?; - Ok((vm.items.math_delimited)(open, body, close)) - } -} - -impl Eval for ast::MathAttach { - type Output = Content; - - #[tracing::instrument(name = "MathAttach::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let base = self.base().eval_display(vm)?; - let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?; - let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?; - Ok((vm.items.math_attach)(base, top, bottom, None, None, None, None)) - } -} - -impl Eval for ast::MathFrac { - type Output = Content; - - #[tracing::instrument(name = "MathFrac::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let num = self.num().eval_display(vm)?; - let denom = self.denom().eval_display(vm)?; - Ok((vm.items.math_frac)(num, denom)) - } -} - -impl Eval for ast::MathRoot { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let index = self.index().map(|i| (vm.items.text)(eco_format!("{i}"))); - let radicand = self.radicand().eval_display(vm)?; - Ok((vm.items.math_root)(index, radicand)) - } -} - -impl Eval for ast::Ident { - type Output = Value; - - #[tracing::instrument(name = "Ident::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.get(self).cloned().at(self.span()) - } -} - -impl Eval for ast::None { - type Output = Value; - - #[tracing::instrument(name = "None::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::None) - } -} - -impl Eval for ast::Auto { - type Output = Value; - - #[tracing::instrument(name = "Auto::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Auto) - } -} - -impl Eval for ast::Bool { - type Output = Value; - - #[tracing::instrument(name = "Bool::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Bool(self.get())) - } -} - -impl Eval for ast::Int { - type Output = Value; - - #[tracing::instrument(name = "Int::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Int(self.get())) - } -} - -impl Eval for ast::Float { - type Output = Value; - - #[tracing::instrument(name = "Float::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Float(self.get())) - } -} - -impl Eval for ast::Numeric { - type Output = Value; - - #[tracing::instrument(name = "Numeric::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::numeric(self.get())) - } -} - -impl Eval for ast::Str { - type Output = Value; - - #[tracing::instrument(name = "Str::eval", skip_all)] - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Str(self.get().into())) - } -} - -impl Eval for ast::CodeBlock { - type Output = Value; - - #[tracing::instrument(name = "CodeBlock::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let output = self.body().eval(vm)?; - vm.scopes.exit(); - Ok(output) - } -} - -impl Eval for ast::Code { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - eval_code(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of expressions. -fn eval_code( - vm: &mut Vm, - exprs: &mut impl Iterator<Item = ast::Expr>, -) -> SourceResult<Value> { - let flow = vm.flow.take(); - let mut output = Value::None; - - while let Some(expr) = exprs.next() { - let span = expr.span(); - let value = match expr { - ast::Expr::Set(set) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_map(styles)) - } - ast::Expr::Show(show) => { - let recipe = show.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_recipe(vm, recipe)?) - } - _ => expr.eval(vm)?, - }; - - output = ops::join(output, value).at(span)?; - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) -} - -impl Eval for ast::ContentBlock { - type Output = Content; - - #[tracing::instrument(name = "ContentBlock::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let content = self.body().eval(vm)?; - vm.scopes.exit(); - Ok(content) - } -} - -impl Eval for ast::Parenthesized { - type Output = Value; - - #[tracing::instrument(name = "Parenthesized::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - self.expr().eval(vm) - } -} - -impl Eval for ast::Array { - type Output = Array; - - #[tracing::instrument(skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let items = self.items(); - - let mut vec = EcoVec::with_capacity(items.size_hint().0); - for item in items { - match item { - ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?), - ast::ArrayItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => vec.extend(array.into_iter()), - v => bail!(expr.span(), "cannot spread {} into array", v.type_name()), - }, - } - } - - Ok(vec.into()) - } -} - -impl Eval for ast::Dict { - type Output = Dict; - - #[tracing::instrument(skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut map = indexmap::IndexMap::new(); - - for item in self.items() { - match item { - ast::DictItem::Named(named) => { - map.insert(named.name().take().into(), named.expr().eval(vm)?); - } - ast::DictItem::Keyed(keyed) => { - map.insert(keyed.key().get().into(), keyed.expr().eval(vm)?); - } - ast::DictItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Dict(dict) => map.extend(dict.into_iter()), - v => bail!( - expr.span(), - "cannot spread {} into dictionary", - v.type_name() - ), - }, - } - } - - Ok(map.into()) - } -} - -impl Eval for ast::Unary { - type Output = Value; - - #[tracing::instrument(name = "Unary::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.expr().eval(vm)?; - let result = match self.op() { - ast::UnOp::Pos => ops::pos(value), - ast::UnOp::Neg => ops::neg(value), - ast::UnOp::Not => ops::not(value), - }; - result.at(self.span()) - } -} - -impl Eval for ast::Binary { - type Output = Value; - - #[tracing::instrument(name = "Binary::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - match self.op() { - ast::BinOp::Add => self.apply(vm, ops::add), - ast::BinOp::Sub => self.apply(vm, ops::sub), - ast::BinOp::Mul => self.apply(vm, ops::mul), - ast::BinOp::Div => self.apply(vm, ops::div), - ast::BinOp::And => self.apply(vm, ops::and), - ast::BinOp::Or => self.apply(vm, ops::or), - ast::BinOp::Eq => self.apply(vm, ops::eq), - ast::BinOp::Neq => self.apply(vm, ops::neq), - ast::BinOp::Lt => self.apply(vm, ops::lt), - ast::BinOp::Leq => self.apply(vm, ops::leq), - ast::BinOp::Gt => self.apply(vm, ops::gt), - ast::BinOp::Geq => self.apply(vm, ops::geq), - ast::BinOp::In => self.apply(vm, ops::in_), - ast::BinOp::NotIn => self.apply(vm, ops::not_in), - ast::BinOp::Assign => self.assign(vm, |_, b| Ok(b)), - ast::BinOp::AddAssign => self.assign(vm, ops::add), - ast::BinOp::SubAssign => self.assign(vm, ops::sub), - ast::BinOp::MulAssign => self.assign(vm, ops::mul), - ast::BinOp::DivAssign => self.assign(vm, ops::div), - } - } -} - -impl ast::Binary { - /// Apply a basic binary operation. - fn apply( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let lhs = self.lhs().eval(vm)?; - - // Short-circuit boolean operations. - if (self.op() == ast::BinOp::And && lhs == Value::Bool(false)) - || (self.op() == ast::BinOp::Or && lhs == Value::Bool(true)) - { - return Ok(lhs); - } - - let rhs = self.rhs().eval(vm)?; - op(lhs, rhs).at(self.span()) - } - - /// Apply an assignment operation. - fn assign( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let rhs = self.rhs().eval(vm)?; - let lhs = self.lhs(); - - // An assignment to a dictionary field is different from a normal access - // since it can create the field instead of just modifying it. - if self.op() == ast::BinOp::Assign { - if let ast::Expr::FieldAccess(access) = &lhs { - let dict = access.access_dict(vm)?; - dict.insert(access.field().take().into(), rhs); - return Ok(Value::None); - } - } - - let location = self.lhs().access(vm)?; - let lhs = std::mem::take(&mut *location); - *location = op(lhs, rhs).at(self.span())?; - Ok(Value::None) - } -} - -impl Eval for ast::FieldAccess { - type Output = Value; - - #[tracing::instrument(name = "FieldAccess::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.target().eval(vm)?; - let field = self.field(); - value.field(&field).at(field.span()) - } -} - -impl Eval for ast::FuncCall { - type Output = Value; - - #[tracing::instrument(name = "FuncCall::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } - - let callee = self.callee(); - let in_math = in_math(&callee); - let callee_span = callee.span(); - let args = self.args(); - - // Try to evaluate as a method call. This is possible if the callee is a - // field access and does not evaluate to a module. - let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { - let target = access.target(); - let field = access.field(); - let field_span = field.span(); - let field = field.take(); - let point = || Tracepoint::Call(Some(field.clone())); - if methods::is_mutating(&field) { - let args = args.eval(vm)?; - let target = target.access(vm)?; - - // Prioritize a function's own methods (with, where) over its - // fields. This is fine as we define each field of a function, - // if it has any. - // ('methods_on' will be empty for Symbol and Module - their - // method calls always refer to their fields.) - if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) - || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) - { - return methods::call_mut(target, &field, args, span).trace( - vm.world(), - point, - span, - ); - } - (target.field(&field).at(field_span)?, args) - } else { - let target = target.eval(vm)?; - let args = args.eval(vm)?; - - if !matches!(target, Value::Symbol(_) | Value::Module(_) | Value::Func(_)) - || methods_on(target.type_name()).iter().any(|(m, _)| m == &field) - { - return methods::call(vm, target, &field, args, span).trace( - vm.world(), - point, - span, - ); - } - (target.field(&field).at(field_span)?, args) - } - } else { - (callee.eval(vm)?, args.eval(vm)?) - }; - - // Handle math special cases for non-functions: - // Combining accent symbols apply themselves while everything else - // simply displays the arguments verbatim. - if in_math && !matches!(callee, Value::Func(_)) { - if let Value::Symbol(sym) = &callee { - let c = sym.get(); - if let Some(accent) = Symbol::combining_accent(c) { - let base = args.expect("base")?; - args.finish()?; - return Ok(Value::Content((vm.items.math_accent)(base, accent))); - } - } - let mut body = Content::empty(); - for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { - if i > 0 { - body += (vm.items.text)(','.into()); - } - body += arg; - } - return Ok(Value::Content( - callee.display().spanned(callee_span) - + (vm.items.math_delimited)( - (vm.items.text)('('.into()), - body, - (vm.items.text)(')'.into()), - ), - )); - } - - let callee = callee.cast::<Func>().at(callee_span)?; - let point = || Tracepoint::Call(callee.name().map(Into::into)); - let f = || callee.call_vm(vm, args).trace(vm.world(), point, span); - - // Stacker is broken on WASM. - #[cfg(target_arch = "wasm32")] - return f(); - - #[cfg(not(target_arch = "wasm32"))] - stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f) - } -} - -fn in_math(expr: &ast::Expr) -> bool { - match expr { - ast::Expr::MathIdent(_) => true, - ast::Expr::FieldAccess(access) => in_math(&access.target()), - _ => false, - } -} - -impl Eval for ast::Args { - type Output = Args; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut items = EcoVec::new(); - - for arg in self.items() { - let span = arg.span(); - match arg { - ast::Arg::Pos(expr) => { - items.push(Arg { - span, - name: None, - value: Spanned::new(expr.eval(vm)?, expr.span()), - }); - } - ast::Arg::Named(named) => { - items.push(Arg { - span, - name: Some(named.name().take().into()), - value: Spanned::new(named.expr().eval(vm)?, named.expr().span()), - }); - } - ast::Arg::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => { - items.extend(array.into_iter().map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - })); - } - Value::Dict(dict) => { - items.extend(dict.into_iter().map(|(key, value)| Arg { - span, - name: Some(key), - value: Spanned::new(value, span), - })); - } - Value::Args(args) => items.extend(args.items), - v => bail!(expr.span(), "cannot spread {}", v.type_name()), - }, - } - } - - Ok(Args { span: self.span(), items }) - } -} - -impl Eval for ast::Closure { - type Output = Value; - - #[tracing::instrument(name = "Closure::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - // The closure's name is defined by its let binding if there's one. - let name = self.name(); - - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; - - // Collect parameters and an optional sink parameter. - let mut params = Vec::new(); - for param in self.params().children() { - match param { - ast::Param::Pos(pattern) => params.push(Param::Pos(pattern)), - ast::Param::Named(named) => { - params.push(Param::Named(named.name(), named.expr().eval(vm)?)); - } - ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), - } - } - - // Define the closure. - let closure = Closure { - location: vm.location, - name, - captured, - params, - body: self.body(), - }; - - Ok(Value::Func(Func::from(closure).spanned(self.params().span()))) - } -} - -impl ast::Pattern { - fn destruct_array<F>( - &self, - vm: &mut Vm, - value: Array, - f: F, - destruct: &ast::Destructuring, - ) -> SourceResult<Value> - where - F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - let mut i = 0; - let len = value.as_slice().len(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Normal(expr) => { - 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 + len).checked_sub(destruct.bindings().count()); - let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s)); - if let (Some(sink_size), Some(sink)) = (sink_size, sink) { - if let Some(expr) = spread.expr() { - f(vm, expr, Value::Array(sink.into()))?; - } - i += sink_size; - } else { - bail!(self.span(), "not enough elements to destructure") - } - } - ast::DestructuringKind::Named(named) => { - bail!(named.span(), "cannot destructure named elements from an array") - } - ast::DestructuringKind::Placeholder(underscore) => { - if i < len { - i += 1 - } else { - bail!(underscore.span(), "not enough elements to destructure") - } - } - } - } - if i < len { - bail!(self.span(), "too many elements to destructure"); - } - - Ok(Value::None) - } - - fn destruct_dict<F>( - &self, - vm: &mut Vm, - dict: Dict, - f: F, - destruct: &ast::Destructuring, - ) -> SourceResult<Value> - where - F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - let mut sink = None; - let mut used = HashSet::new(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => { - let v = dict - .at(&ident, None) - .map_err(|_| "destructuring key not found in dictionary") - .at(ident.span())?; - f(vm, ast::Expr::Ident(ident.clone()), v.clone())?; - used.insert(ident.take()); - } - ast::DestructuringKind::Sink(spread) => sink = spread.expr(), - ast::DestructuringKind::Named(named) => { - let name = named.name(); - let v = dict - .at(&name, None) - .map_err(|_| "destructuring key not found in dictionary") - .at(name.span())?; - f(vm, named.expr(), v.clone())?; - used.insert(name.take()); - } - ast::DestructuringKind::Placeholder(_) => {} - ast::DestructuringKind::Normal(expr) => { - bail!(expr.span(), "expected key, found expression"); - } - } - } - - if let Some(expr) = sink { - let mut sink = Dict::new(); - for (key, value) in dict { - if !used.contains(key.as_str()) { - sink.insert(key, value); - } - } - f(vm, expr, Value::Dict(sink))?; - } - - Ok(Value::None) - } - - /// Destruct the given value into the pattern and apply the function to each binding. - #[tracing::instrument(skip_all)] - fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value> - where - T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, - { - match self { - ast::Pattern::Normal(expr) => { - f(vm, expr.clone(), value)?; - Ok(Value::None) - } - ast::Pattern::Placeholder(_) => Ok(Value::None), - ast::Pattern::Destructuring(destruct) => match value { - Value::Array(value) => self.destruct_array(vm, value, f, destruct), - Value::Dict(value) => self.destruct_dict(vm, value, f, destruct), - _ => bail!(self.span(), "cannot destructure {}", value.type_name()), - }, - } - } - - /// Destruct the value into the pattern by binding. - pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - self.apply(vm, value, |vm, expr, value| match expr { - ast::Expr::Ident(ident) => { - vm.define(ident, value); - Ok(Value::None) - } - _ => bail!(expr.span(), "nested patterns are currently not supported"), - }) - } - - /// Destruct the value into the pattern by assignment. - pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - self.apply(vm, value, |vm, expr, value| { - let location = expr.access(vm)?; - *location = value; - Ok(Value::None) - }) - } -} - -impl Eval for ast::LetBinding { - type Output = Value; - - #[tracing::instrument(name = "LetBinding::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = match self.init() { - Some(expr) => expr.eval(vm)?, - None => Value::None, - }; - - match self.kind() { - ast::LetBindingKind::Normal(pattern) => pattern.define(vm, value), - ast::LetBindingKind::Closure(ident) => { - vm.define(ident, value); - Ok(Value::None) - } - } - } -} - -impl Eval for ast::DestructAssignment { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.value().eval(vm)?; - self.pattern().assign(vm, value)?; - Ok(Value::None) - } -} - -impl Eval for ast::SetRule { - type Output = Styles; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if let Some(condition) = self.condition() { - if !condition.eval(vm)?.cast::<bool>().at(condition.span())? { - return Ok(Styles::new()); - } - } - - let target = self.target(); - let target = target - .eval(vm)? - .cast::<Func>() - .and_then(|func| { - func.element().ok_or_else(|| { - "only element functions can be used in set rules".into() - }) - }) - .at(target.span())?; - let args = self.args().eval(vm)?; - Ok(target.set(args)?.spanned(self.span())) - } -} - -impl Eval for ast::ShowRule { - type Output = Recipe; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let selector = self - .selector() - .map(|sel| sel.eval(vm)?.cast::<ShowableSelector>().at(sel.span())) - .transpose()? - .map(|selector| selector.0); - - let transform = self.transform(); - let span = transform.span(); - - let transform = match transform { - ast::Expr::Set(set) => Transform::Style(set.eval(vm)?), - expr => expr.eval(vm)?.cast::<Transform>().at(span)?, - }; - - Ok(Recipe { span, selector, transform }) - } -} - -impl Eval for ast::Conditional { - type Output = Value; - - #[tracing::instrument(name = "Conditional::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let condition = self.condition(); - if condition.eval(vm)?.cast::<bool>().at(condition.span())? { - self.if_body().eval(vm) - } else if let Some(else_body) = self.else_body() { - else_body.eval(vm) - } else { - Ok(Value::None) - } - } -} - -impl Eval for ast::WhileLoop { - type Output = Value; - - #[tracing::instrument(name = "WhileLoop::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - let mut i = 0; - - let condition = self.condition(); - let body = self.body(); - - while condition.eval(vm)?.cast::<bool>().at(condition.span())? { - if i == 0 - && is_invariant(condition.as_untyped()) - && !can_diverge(body.as_untyped()) - { - bail!(condition.span(), "condition is always true"); - } else if i >= MAX_ITERATIONS { - bail!(self.span(), "loop seems to be infinite"); - } - - let value = body.eval(vm)?; - output = ops::join(output, value).at(body.span())?; - - match vm.flow { - Some(FlowEvent::Break(_)) => { - vm.flow = None; - break; - } - Some(FlowEvent::Continue(_)) => vm.flow = None, - Some(FlowEvent::Return(..)) => break, - None => {} - } - - i += 1; - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -/// Whether the expression always evaluates to the same value. -fn is_invariant(expr: &SyntaxNode) -> bool { - match expr.cast() { - Some(ast::Expr::Ident(_)) => false, - Some(ast::Expr::MathIdent(_)) => false, - Some(ast::Expr::FieldAccess(access)) => { - is_invariant(access.target().as_untyped()) - } - Some(ast::Expr::FuncCall(call)) => { - is_invariant(call.callee().as_untyped()) - && is_invariant(call.args().as_untyped()) - } - _ => expr.children().all(is_invariant), - } -} - -/// Whether the expression contains a break or return. -fn can_diverge(expr: &SyntaxNode) -> bool { - matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return) - || expr.children().any(can_diverge) -} - -impl Eval for ast::ForLoop { - type Output = Value; - - #[tracing::instrument(name = "ForLoop::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - - macro_rules! iter { - (for $pat:ident in $iter:expr) => {{ - vm.scopes.enter(); - - #[allow(unused_parens)] - for value in $iter { - $pat.define(vm, value.into_value())?; - - let body = self.body(); - let value = body.eval(vm)?; - output = ops::join(output, value).at(body.span())?; - - match vm.flow { - Some(FlowEvent::Break(_)) => { - vm.flow = None; - break; - } - Some(FlowEvent::Continue(_)) => vm.flow = None, - Some(FlowEvent::Return(..)) => break, - None => {} - } - } - - vm.scopes.exit(); - }}; - } - - let iter = self.iter().eval(vm)?; - let pattern = self.pattern(); - - match (&pattern, iter.clone()) { - (ast::Pattern::Normal(_), Value::Str(string)) => { - // Iterate over graphemes of string. - iter!(for pattern in string.as_str().graphemes(true)); - } - (_, Value::Dict(dict)) => { - // Iterate over pairs of dict. - iter!(for pattern in dict.pairs()); - } - (_, Value::Array(array)) => { - // Iterate over values of array. - iter!(for pattern in array); - } - (ast::Pattern::Normal(_), _) => { - bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); - } - (_, _) => { - bail!(pattern.span(), "cannot destructure values of {}", iter.type_name()) - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -/// Applies imports from `import` to the current scope. -fn apply_imports<V: IntoValue>( - imports: Option<ast::Imports>, - vm: &mut Vm, - source_value: V, - name: impl Fn(&V) -> EcoString, - scope: impl Fn(&V) -> &Scope, -) -> SourceResult<()> { - match imports { - None => { - vm.scopes.top.define(name(&source_value), source_value); - } - Some(ast::Imports::Wildcard) => { - for (var, value) in scope(&source_value).iter() { - vm.scopes.top.define(var.clone(), value.clone()); - } - } - Some(ast::Imports::Items(idents)) => { - let mut errors = vec![]; - let scope = scope(&source_value); - for ident in idents { - if let Some(value) = scope.get(&ident) { - vm.define(ident, value.clone()); - } else { - errors.push(error!(ident.span(), "unresolved import")); - } - } - if !errors.is_empty() { - return Err(Box::new(errors)); - } - } - } - - Ok(()) -} - -impl Eval for ast::ModuleImport { - type Output = Value; - - #[tracing::instrument(name = "ModuleImport::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.source().span(); - let source = self.source().eval(vm)?; - if let Value::Func(func) = source { - if func.info().is_none() { - bail!(span, "cannot import from user-defined functions"); - } - apply_imports( - self.imports(), - vm, - func, - |func| func.info().unwrap().name.into(), - |func| &func.info().unwrap().scope, - )?; - } else { - let module = import(vm, source, span, true)?; - apply_imports( - self.imports(), - vm, - module, - |module| module.name().clone(), - |module| module.scope(), - )?; - } - - Ok(Value::None) - } -} - -impl Eval for ast::ModuleInclude { - type Output = Content; - - #[tracing::instrument(name = "ModuleInclude::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.source().span(); - let source = self.source().eval(vm)?; - let module = import(vm, source, span, false)?; - Ok(module.content()) - } -} - -/// Process an import of a module relative to the current location. -fn import( - vm: &mut Vm, - source: Value, - span: Span, - accept_functions: bool, -) -> SourceResult<Module> { - let path = match source { - Value::Str(path) => path, - Value::Module(module) => return Ok(module), - v => { - if accept_functions { - bail!(span, "expected path, module or function, found {}", v.type_name()) - } else { - bail!(span, "expected path or module, found {}", v.type_name()) - } - } - }; - - // Handle package and file imports. - let path = path.as_str(); - if path.starts_with('@') { - let spec = path.parse::<PackageSpec>().at(span)?; - import_package(vm, spec, span) - } else { - import_file(vm, path, span) - } -} - -/// Import an external package. -fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> { - // Evaluate the manifest. - let manifest_id = FileId::new(Some(spec.clone()), Path::new("/typst.toml")); - let bytes = vm.world().file(manifest_id).at(span)?; - let manifest = PackageManifest::parse(&bytes).at(span)?; - manifest.validate(&spec).at(span)?; - - // Evaluate the entry point. - let entrypoint_id = manifest_id.join(&manifest.package.entrypoint).at(span)?; - let source = vm.world().source(entrypoint_id).at(span)?; - let point = || Tracepoint::Import; - Ok(eval(vm.world(), vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source) - .trace(vm.world(), point, span)? - .with_name(manifest.package.name)) -} - -/// Import a file from a path. -fn import_file(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { - // Load the source file. - let world = vm.world(); - let id = vm.location().join(path).at(span)?; - let source = world.source(id).at(span)?; - - // Prevent cyclic importing. - if vm.route.contains(source.id()) { - bail!(span, "cyclic import"); - } - - // Evaluate the file. - let point = || Tracepoint::Import; - eval(world, vm.route, TrackedMut::reborrow_mut(&mut vm.vt.tracer), &source) - .trace(world, point, span) -} - -impl Eval for ast::LoopBreak { - type Output = Value; - - #[tracing::instrument(name = "LoopBreak::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Break(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::LoopContinue { - type Output = Value; - - #[tracing::instrument(name = "LoopContinue::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Continue(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::FuncReturn { - type Output = Value; - - #[tracing::instrument(name = "FuncReturn::eval", skip_all)] - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.body().map(|body| body.eval(vm)).transpose()?; - if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Return(self.span(), value)); - } - Ok(Value::None) - } -} - -/// Access an expression mutably. -trait Access { - /// Access the value. - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>; -} - -impl Access for ast::Expr { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - match self { - Self::Ident(v) => v.access(vm), - Self::Parenthesized(v) => v.access(vm), - Self::FieldAccess(v) => v.access(vm), - Self::FuncCall(v) => v.access(vm), - _ => { - let _ = self.eval(vm)?; - bail!(self.span(), "cannot mutate a temporary value"); - } - } - } -} - -impl Access for ast::Ident { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - let span = self.span(); - let value = vm.scopes.get_mut(self).at(span)?; - if vm.traced == Some(span) { - vm.vt.tracer.trace(value.clone()); - } - Ok(value) - } -} - -impl Access for ast::Parenthesized { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - self.expr().access(vm) - } -} - -impl Access for ast::FieldAccess { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - self.access_dict(vm)?.at_mut(&self.field().take()).at(self.span()) - } -} - -impl ast::FieldAccess { - fn access_dict<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Dict> { - match self.target().access(vm)? { - Value::Dict(dict) => Ok(dict), - value => bail!( - self.target().span(), - "expected dictionary, found {}", - value.type_name(), - ), - } - } -} - -impl Access for ast::FuncCall { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - if let ast::Expr::FieldAccess(access) = self.callee() { - let method = access.field().take(); - if methods::is_accessor(&method) { - let span = self.span(); - let world = vm.world(); - let args = self.args().eval(vm)?; - let value = access.target().access(vm)?; - let result = methods::call_access(value, &method, args, span); - let point = || Tracepoint::Call(Some(method.clone())); - return result.trace(world, point, span); - } - } - - let _ = self.eval(vm)?; - bail!(self.span(), "cannot mutate a temporary value"); - } -} diff --git a/src/eval/module.rs b/src/eval/module.rs deleted file mode 100644 index 0bc6bf38..00000000 --- a/src/eval/module.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::sync::Arc; - -use ecow::{eco_format, EcoString}; - -use super::{Content, Scope, Value}; -use crate::diag::StrResult; - -/// An evaluated module, ready for importing or typesetting. -/// -/// Values of this type are cheap to clone and hash. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Module { - /// The module's name. - name: EcoString, - /// The reference-counted inner fields. - inner: Arc<Repr>, -} - -/// The internal representation. -#[derive(Clone, Hash)] -struct Repr { - /// The top-level definitions that were bound in this module. - scope: Scope, - /// The module's layoutable contents. - content: Content, -} - -impl Module { - /// Create a new module. - pub fn new(name: impl Into<EcoString>) -> Self { - Self { - name: name.into(), - inner: Arc::new(Repr { scope: Scope::new(), content: Content::empty() }), - } - } - - /// Update the module's name. - pub fn with_name(mut self, name: impl Into<EcoString>) -> Self { - self.name = name.into(); - self - } - - /// Update the module's scope. - pub fn with_scope(mut self, scope: Scope) -> Self { - Arc::make_mut(&mut self.inner).scope = scope; - self - } - - /// Update the module's content. - pub fn with_content(mut self, content: Content) -> Self { - Arc::make_mut(&mut self.inner).content = content; - self - } - - /// Get the module's name. - pub fn name(&self) -> &EcoString { - &self.name - } - - /// Access the module's scope. - pub fn scope(&self) -> &Scope { - &self.inner.scope - } - - /// Access the module's scope, mutably. - pub fn scope_mut(&mut self) -> &mut Scope { - &mut Arc::make_mut(&mut self.inner).scope - } - - /// Try to access a definition in the module. - pub fn get(&self, name: &str) -> StrResult<&Value> { - self.scope().get(name).ok_or_else(|| { - eco_format!("module `{}` does not contain `{name}`", self.name()) - }) - } - - /// Extract the module's content. - pub fn content(self) -> Content { - match Arc::try_unwrap(self.inner) { - Ok(repr) => repr.content, - Err(arc) => arc.content.clone(), - } - } -} - -impl Debug for Module { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "<module {}>", self.name()) - } -} - -impl PartialEq for Module { - fn eq(&self, other: &Self) -> bool { - self.name == other.name && Arc::ptr_eq(&self.inner, &other.inner) - } -} diff --git a/src/eval/none.rs b/src/eval/none.rs deleted file mode 100644 index ab7644a7..00000000 --- a/src/eval/none.rs +++ /dev/null @@ -1,74 +0,0 @@ -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 deleted file mode 100644 index 0880a87e..00000000 --- a/src/eval/ops.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Operations on values. - -use std::cmp::Ordering; -use std::fmt::Debug; - -use ecow::eco_format; - -use super::{format_str, Regex, Value}; -use crate::diag::{bail, StrResult}; -use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart}; -use Value::*; - -/// Bail with a type mismatch error. -macro_rules! mismatch { - ($fmt:expr, $($value:expr),* $(,)?) => { - return Err(eco_format!($fmt, $($value.type_name()),*)) - }; -} - -/// Join a value with another value. -pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Str(a), Str(b)) => Str(a + b), - (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), - (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), - (Content(a), Str(b)) => Content(a + item!(text)(b.into())), - (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), - (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), - (Array(a), Array(b)) => Array(a + b), - (Dict(a), Dict(b)) => Dict(a + b), - (a, b) => mismatch!("cannot join {} with {}", a, b), - }) -} - -/// Apply the unary plus operator to a value. -pub fn pos(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(v), - Float(v) => Float(v), - Length(v) => Length(v), - Angle(v) => Angle(v), - Ratio(v) => Ratio(v), - Relative(v) => Relative(v), - Fraction(v) => Fraction(v), - v => mismatch!("cannot apply '+' to {}", v), - }) -} - -/// Compute the negation of a value. -pub fn neg(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(v.checked_neg().ok_or("value is too large")?), - Float(v) => Float(-v), - Length(v) => Length(-v), - Angle(v) => Angle(-v), - Ratio(v) => Ratio(-v), - Relative(v) => Relative(-v), - Fraction(v) => Fraction(-v), - v => mismatch!("cannot apply '-' to {}", v), - }) -} - -/// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - - (Int(a), Int(b)) => Int(a.checked_add(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 + b), - (Float(a), Int(b)) => Float(a + b as f64), - (Float(a), Float(b)) => Float(a + b), - - (Angle(a), Angle(b)) => Angle(a + b), - - (Length(a), Length(b)) => Length(a + b), - (Length(a), Ratio(b)) => Relative(b + a), - (Length(a), Relative(b)) => Relative(b + a), - - (Ratio(a), Length(b)) => Relative(a + b), - (Ratio(a), Ratio(b)) => Ratio(a + b), - (Ratio(a), Relative(b)) => Relative(b + a), - - (Relative(a), Length(b)) => Relative(a + b), - (Relative(a), Ratio(b)) => Relative(a + b), - (Relative(a), Relative(b)) => Relative(a + b), - - (Fraction(a), Fraction(b)) => Fraction(a + b), - - (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Str(a), Str(b)) => Str(a + b), - (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), - (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), - (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), - (Content(a), Str(b)) => Content(a + item!(text)(b.into())), - (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), - (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), - - (Array(a), Array(b)) => Array(a + b), - (Dict(a), Dict(b)) => Dict(a + b), - - (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { - Value::dynamic(PartialStroke { - paint: Smart::Custom(color.into()), - thickness: Smart::Custom(thickness), - ..PartialStroke::default() - }) - } - - (Dyn(a), Dyn(b)) => { - // 1D alignments can be summed into 2D alignments. - if let (Some(&a), Some(&b)) = - (a.downcast::<GenAlign>(), b.downcast::<GenAlign>()) - { - if a.axis() == b.axis() { - return Err(eco_format!("cannot add two {:?} alignments", a.axis())); - } - - return Ok(Value::dynamic(match a.axis() { - Axis::X => Axes { x: a, y: b }, - Axis::Y => Axes { x: b, y: a }, - })); - }; - - mismatch!("cannot add {} and {}", a, b); - } - - (a, b) => mismatch!("cannot add {} and {}", a, b), - }) -} - -/// Compute the difference of two values. -pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a.checked_sub(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 - b), - (Float(a), Int(b)) => Float(a - b as f64), - (Float(a), Float(b)) => Float(a - b), - - (Angle(a), Angle(b)) => Angle(a - b), - - (Length(a), Length(b)) => Length(a - b), - (Length(a), Ratio(b)) => Relative(-b + a), - (Length(a), Relative(b)) => Relative(-b + a), - - (Ratio(a), Length(b)) => Relative(a + -b), - (Ratio(a), Ratio(b)) => Ratio(a - b), - (Ratio(a), Relative(b)) => Relative(-b + a), - - (Relative(a), Length(b)) => Relative(a + -b), - (Relative(a), Ratio(b)) => Relative(a + -b), - (Relative(a), Relative(b)) => Relative(a - b), - - (Fraction(a), Fraction(b)) => Fraction(a - b), - - (a, b) => mismatch!("cannot subtract {1} from {0}", a, b), - }) -} - -/// Compute the product of two values. -pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a.checked_mul(b).ok_or("value is too large")?), - (Int(a), Float(b)) => Float(a as f64 * b), - (Float(a), Int(b)) => Float(a * b as f64), - (Float(a), Float(b)) => Float(a * b), - - (Length(a), Int(b)) => Length(a * b as f64), - (Length(a), Float(b)) => Length(a * b), - (Length(a), Ratio(b)) => Length(a * b.get()), - (Int(a), Length(b)) => Length(b * a as f64), - (Float(a), Length(b)) => Length(b * a), - (Ratio(a), Length(b)) => Length(b * a.get()), - - (Angle(a), Int(b)) => Angle(a * b as f64), - (Angle(a), Float(b)) => Angle(a * b), - (Angle(a), Ratio(b)) => Angle(a * b.get()), - (Int(a), Angle(b)) => Angle(a as f64 * b), - (Float(a), Angle(b)) => Angle(a * b), - (Ratio(a), Angle(b)) => Angle(a.get() * b), - - (Ratio(a), Ratio(b)) => Ratio(a * b), - (Ratio(a), Int(b)) => Ratio(a * b as f64), - (Ratio(a), Float(b)) => Ratio(a * b), - (Int(a), Ratio(b)) => Ratio(a as f64 * b), - (Float(a), Ratio(b)) => Ratio(a * b), - - (Relative(a), Int(b)) => Relative(a * b as f64), - (Relative(a), Float(b)) => Relative(a * b), - (Relative(a), Ratio(b)) => Relative(a * b.get()), - (Int(a), Relative(b)) => Relative(a as f64 * b), - (Float(a), Relative(b)) => Relative(a * b), - (Ratio(a), Relative(b)) => Relative(a.get() * b), - - (Fraction(a), Int(b)) => Fraction(a * b as f64), - (Fraction(a), Float(b)) => Fraction(a * b), - (Fraction(a), Ratio(b)) => Fraction(a * b.get()), - (Int(a), Fraction(b)) => Fraction(a as f64 * b), - (Float(a), Fraction(b)) => Fraction(a * b), - (Ratio(a), Fraction(b)) => Fraction(a.get() * b), - - (Str(a), Int(b)) => Str(a.repeat(b)?), - (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), b @ Int(_)) => Content(a.repeat(b.cast()?)), - (a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)), - - (a, b) => mismatch!("cannot multiply {} with {}", a, b), - }) -} - -/// Compute the quotient of two values. -pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { - if is_zero(&rhs) { - bail!("cannot divide by zero"); - } - - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Float(a as f64 / b as f64), - (Int(a), Float(b)) => Float(a as f64 / b), - (Float(a), Int(b)) => Float(a / b as f64), - (Float(a), Float(b)) => Float(a / b), - - (Length(a), Int(b)) => Length(a / b as f64), - (Length(a), Float(b)) => Length(a / b), - (Length(a), Length(b)) => Float(try_div_length(a, b)?), - (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?), - - (Angle(a), Int(b)) => Angle(a / b as f64), - (Angle(a), Float(b)) => Angle(a / b), - (Angle(a), Angle(b)) => Float(a / b), - - (Ratio(a), Int(b)) => Ratio(a / b as f64), - (Ratio(a), Float(b)) => Ratio(a / b), - (Ratio(a), Ratio(b)) => Float(a / b), - (Ratio(a), Relative(b)) if b.abs.is_zero() => Float(a / b.rel), - - (Relative(a), Int(b)) => Relative(a / b as f64), - (Relative(a), Float(b)) => Relative(a / b), - (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?), - (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b), - (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?), - - (Fraction(a), Int(b)) => Fraction(a / b as f64), - (Fraction(a), Float(b)) => Fraction(a / b), - (Fraction(a), Fraction(b)) => Float(a / b), - - (a, b) => mismatch!("cannot divide {} by {}", a, b), - }) -} - -/// Whether a value is a numeric zero. -fn is_zero(v: &Value) -> bool { - match *v { - Int(v) => v == 0, - Float(v) => v == 0.0, - Length(v) => v.is_zero(), - Angle(v) => v.is_zero(), - Ratio(v) => v.is_zero(), - Relative(v) => v.is_zero(), - Fraction(v) => v.is_zero(), - _ => false, - } -} - -/// Try to divide two lengths. -fn try_div_length(a: Length, b: Length) -> StrResult<f64> { - a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into()) -} - -/// Try to divide two relative lengths. -fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> { - a.try_div(b) - .ok_or_else(|| "cannot divide these two relative lengths".into()) -} - -/// Compute the logical "not" of a value. -pub fn not(value: Value) -> StrResult<Value> { - match value { - Bool(b) => Ok(Bool(!b)), - v => mismatch!("cannot apply 'not' to {}", v), - } -} - -/// Compute the logical "and" of two values. -pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a && b)), - (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), - } -} - -/// Compute the logical "or" of two values. -pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a || b)), - (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), - } -} - -/// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(equal(&lhs, &rhs))) -} - -/// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(!equal(&lhs, &rhs))) -} - -macro_rules! comparison { - ($name:ident, $op:tt, $($pat:tt)*) => { - /// Compute how a value compares with another value. - pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> { - let ordering = compare(&lhs, &rhs)?; - Ok(Bool(matches!(ordering, $($pat)*))) - } - }; -} - -comparison!(lt, "<", Ordering::Less); -comparison!(leq, "<=", Ordering::Less | Ordering::Equal); -comparison!(gt, ">", Ordering::Greater); -comparison!(geq, ">=", Ordering::Greater | Ordering::Equal); - -/// Determine whether two values are equal. -pub fn equal(lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - // Compare reflexively. - (None, None) => true, - (Auto, Auto) => true, - (Bool(a), Bool(b)) => a == b, - (Int(a), Int(b)) => a == b, - (Float(a), Float(b)) => a == b, - (Length(a), Length(b)) => a == b, - (Angle(a), Angle(b)) => a == b, - (Ratio(a), Ratio(b)) => a == b, - (Relative(a), Relative(b)) => a == b, - (Fraction(a), Fraction(b)) => a == b, - (Color(a), Color(b)) => a == b, - (Symbol(a), Symbol(b)) => a == b, - (Str(a), Str(b)) => a == b, - (Label(a), Label(b)) => a == b, - (Content(a), Content(b)) => a == b, - (Array(a), Array(b)) => a == b, - (Dict(a), Dict(b)) => a == b, - (Func(a), Func(b)) => a == b, - (Args(a), Args(b)) => a == b, - (Module(a), Module(b)) => a == b, - (Dyn(a), Dyn(b)) => a == b, - - // Some technically different things should compare equal. - (&Int(a), &Float(b)) => a as f64 == b, - (&Float(a), &Int(b)) => a == b as f64, - (&Length(a), &Relative(b)) => a == b.abs && b.rel.is_zero(), - (&Ratio(a), &Relative(b)) => a == b.rel && b.abs.is_zero(), - (&Relative(a), &Length(b)) => a.abs == b && a.rel.is_zero(), - (&Relative(a), &Ratio(b)) => a.rel == b && a.abs.is_zero(), - - _ => false, - } -} - -/// Compare two values. -pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> { - Ok(match (lhs, rhs) { - (Bool(a), Bool(b)) => a.cmp(b), - (Int(a), Int(b)) => a.cmp(b), - (Float(a), Float(b)) => try_cmp_values(a, b)?, - (Length(a), Length(b)) => try_cmp_values(a, b)?, - (Angle(a), Angle(b)) => a.cmp(b), - (Ratio(a), Ratio(b)) => a.cmp(b), - (Relative(a), Relative(b)) => try_cmp_values(a, b)?, - (Fraction(a), Fraction(b)) => a.cmp(b), - (Str(a), Str(b)) => a.cmp(b), - - // Some technically different things should be comparable. - (Int(a), Float(b)) => try_cmp_values(&(*a as f64), b)?, - (Float(a), Int(b)) => try_cmp_values(a, &(*b as f64))?, - (Length(a), Relative(b)) if b.rel.is_zero() => try_cmp_values(a, &b.abs)?, - (Ratio(a), Relative(b)) if b.abs.is_zero() => a.cmp(&b.rel), - (Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?, - (Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b), - - _ => mismatch!("cannot compare {} and {}", lhs, rhs), - }) -} - -/// Try to compare two values. -fn try_cmp_values<T: PartialOrd + Debug>(a: &T, b: &T) -> StrResult<Ordering> { - a.partial_cmp(b) - .ok_or_else(|| eco_format!("cannot compare {:?} with {:?}", a, b)) -} - -/// Test whether one value is "in" another one. -pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(b)) - } else { - mismatch!("cannot apply 'in' to {} and {}", lhs, rhs) - } -} - -/// Test whether one value is "not in" another one. -pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(!b)) - } else { - mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs) - } -} - -/// Test for containment. -pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> { - match (lhs, rhs) { - (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), - (Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)), - (Str(a), Dict(b)) => Some(b.contains(a)), - (a, Array(b)) => Some(b.contains(a)), - _ => Option::None, - } -} diff --git a/src/eval/scope.rs b/src/eval/scope.rs deleted file mode 100644 index f3e13715..00000000 --- a/src/eval/scope.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; - -use ecow::{eco_format, EcoString}; - -use super::{IntoValue, Library, Value}; -use crate::diag::{bail, StrResult}; - -/// A stack of scopes. -#[derive(Debug, Default, Clone)] -pub struct Scopes<'a> { - /// The active scope. - pub top: Scope, - /// The stack of lower scopes. - pub scopes: Vec<Scope>, - /// The standard library. - pub base: Option<&'a Library>, -} - -impl<'a> Scopes<'a> { - /// Create a new, empty hierarchy of scopes. - pub fn new(base: Option<&'a Library>) -> Self { - Self { top: Scope::new(), scopes: vec![], base } - } - - /// Enter a new scope. - pub fn enter(&mut self) { - self.scopes.push(std::mem::take(&mut self.top)); - } - - /// Exit the topmost scope. - /// - /// This panics if no scope was entered. - pub fn exit(&mut self) { - self.top = self.scopes.pop().expect("no pushed scope"); - } - - /// Try to access a variable immutably. - pub fn get(&self, var: &str) -> StrResult<&Value> { - std::iter::once(&self.top) - .chain(self.scopes.iter().rev()) - .chain(self.base.map(|base| base.global.scope())) - .find_map(|scope| scope.get(var)) - .ok_or_else(|| unknown_variable(var)) - } - - /// Try to access a variable immutably in math. - pub fn get_in_math(&self, var: &str) -> StrResult<&Value> { - std::iter::once(&self.top) - .chain(self.scopes.iter().rev()) - .chain(self.base.map(|base| base.math.scope())) - .find_map(|scope| scope.get(var)) - .ok_or_else(|| eco_format!("unknown variable: {}", var)) - } - - /// Try to access a variable mutably. - pub fn get_mut(&mut self, var: &str) -> StrResult<&mut Value> { - std::iter::once(&mut self.top) - .chain(&mut self.scopes.iter_mut().rev()) - .find_map(|scope| scope.get_mut(var)) - .ok_or_else(|| { - match self.base.and_then(|base| base.global.scope().get(var)) { - Some(_) => eco_format!("cannot mutate a constant: {}", var), - _ => unknown_variable(var), - } - })? - } -} - -/// The error message when a variable is not found. -#[cold] -fn unknown_variable(var: &str) -> EcoString { - if var.contains('-') { - eco_format!("unknown variable: {} - if you meant to use subtraction, try adding spaces around the minus sign.", var) - } else { - eco_format!("unknown variable: {}", var) - } -} - -/// A map from binding names to values. -#[derive(Default, Clone, Hash)] -pub struct Scope(BTreeMap<EcoString, Slot>, bool); - -impl Scope { - /// Create a new empty scope. - pub fn new() -> Self { - Self(BTreeMap::new(), false) - } - - /// Create a new scope with duplication prevention. - pub fn deduplicating() -> Self { - Self(BTreeMap::new(), true) - } - - /// Bind a value to a name. - #[track_caller] - pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) { - let name = name.into(); - - #[cfg(debug_assertions)] - if self.1 && self.0.contains_key(&name) { - panic!("duplicate definition: {name}"); - } - - 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 IntoValue) { - self.0 - .insert(var.into(), Slot::new(value.into_value(), Kind::Captured)); - } - - /// Try to access a variable immutably. - pub fn get(&self, var: &str) -> Option<&Value> { - self.0.get(var).map(Slot::read) - } - - /// Try to access a variable mutably. - pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> { - self.0.get_mut(var).map(Slot::write) - } - - /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value)> { - self.0.iter().map(|(k, v)| (k, v.read())) - } -} - -impl Debug for Scope { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Scope ")?; - f.debug_map() - .entries(self.0.iter().map(|(k, v)| (k, v.read()))) - .finish() - } -} - -/// A slot where a value is stored. -#[derive(Clone, Hash)] -struct Slot { - /// The stored value. - value: Value, - /// The kind of slot, determines how the value can be accessed. - kind: Kind, -} - -/// The different kinds of slots. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -enum Kind { - /// A normal, mutable binding. - Normal, - /// A captured copy of another variable. - Captured, -} - -impl Slot { - /// Create a new slot. - fn new(value: Value, kind: Kind) -> Self { - Self { value, kind } - } - - /// Read the value. - fn read(&self) -> &Value { - &self.value - } - - /// Try to write to the value. - fn write(&mut self) -> StrResult<&mut Value> { - match self.kind { - Kind::Normal => Ok(&mut self.value), - Kind::Captured => { - bail!("variables from outside the function are read-only and cannot be modified") - } - } - } -} diff --git a/src/eval/str.rs b/src/eval/str.rs deleted file mode 100644 index f5e5ab00..00000000 --- a/src/eval/str.rs +++ /dev/null @@ -1,620 +0,0 @@ -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign, Deref, Range}; - -use ecow::EcoString; -use unicode_segmentation::UnicodeSegmentation; - -use super::{cast, dict, Args, Array, Dict, Func, IntoValue, Value, Vm}; -use crate::diag::{bail, At, SourceResult, StrResult}; -use crate::geom::GenAlign; - -/// Create a new [`Str`] from a format string. -#[macro_export] -#[doc(hidden)] -macro_rules! __format_str { - ($($tts:tt)*) => {{ - $crate::eval::Str::from($crate::eval::eco_format!($($tts)*)) - }}; -} - -#[doc(inline)] -pub use crate::__format_str as format_str; -#[doc(hidden)] -pub use ecow::eco_format; - -/// An immutable reference counted string. -#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Str(EcoString); - -impl Str { - /// Create a new, empty string. - pub fn new() -> Self { - Self(EcoString::new()) - } - - /// Return `true` if the length is 0. - pub fn is_empty(&self) -> bool { - self.0.len() == 0 - } - - /// The length of the string in bytes. - pub fn len(&self) -> usize { - self.0.len() - } - - /// A string slice containing the entire string. - pub fn as_str(&self) -> &str { - self - } - - /// Extract the first grapheme cluster. - pub fn first(&self) -> StrResult<Self> { - self.0 - .graphemes(true) - .next() - .map(Into::into) - .ok_or_else(string_is_empty) - } - - /// Extract the last grapheme cluster. - pub fn last(&self) -> StrResult<Self> { - self.0 - .graphemes(true) - .next_back() - .map(Into::into) - .ok_or_else(string_is_empty) - } - - /// Extract the grapheme cluster at the given index. - pub fn at<'a>(&'a self, index: i64, default: Option<&'a str>) -> StrResult<Self> { - let len = self.len(); - let grapheme = self - .locate_opt(index)? - .and_then(|i| self.0[i..].graphemes(true).next()) - .or(default) - .ok_or_else(|| no_default_and_out_of_bounds(index, len))?; - Ok(grapheme.into()) - } - - /// 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() as i64))?.max(start); - Ok(self.0[start..end].into()) - } - - /// The grapheme clusters the string consists of. - pub fn clusters(&self) -> Array { - self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect() - } - - /// The codepoints the string consists of. - pub fn codepoints(&self) -> Array { - self.chars().map(|c| Value::Str(c.into())).collect() - } - - /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()), - StrPattern::Regex(re) => re.is_match(self), - } - } - - /// Whether this string begins with the given pattern. - pub fn starts_with(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), - StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), - } - } - - /// Whether this string ends with the given pattern. - pub fn ends_with(&self, pattern: StrPattern) -> bool { - match pattern { - StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), - StrPattern::Regex(re) => { - re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len()) - } - } - } - - /// The text of the pattern's first match in this string. - pub fn find(&self, pattern: StrPattern) -> Option<Self> { - match pattern { - StrPattern::Str(pat) => self.0.contains(pat.as_str()).then_some(pat), - StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), - } - } - - /// The position of the pattern's first match in this string. - pub fn position(&self, pattern: StrPattern) -> Option<i64> { - match pattern { - StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), - StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64), - } - } - - /// The start and, text and capture groups (if any) of the first match of - /// the pattern in this string. - pub fn match_(&self, pattern: StrPattern) -> Option<Dict> { - match pattern { - StrPattern::Str(pat) => { - self.0.match_indices(pat.as_str()).next().map(match_to_dict) - } - StrPattern::Regex(re) => re.captures(self).map(captures_to_dict), - } - } - - /// The start, end, text and capture groups (if any) of all matches of the - /// pattern in this string. - pub fn matches(&self, pattern: StrPattern) -> Array { - match pattern { - StrPattern::Str(pat) => self - .0 - .match_indices(pat.as_str()) - .map(match_to_dict) - .map(Value::Dict) - .collect(), - StrPattern::Regex(re) => re - .captures_iter(self) - .map(captures_to_dict) - .map(Value::Dict) - .collect(), - } - } - - /// Split this string at whitespace or a specific pattern. - pub fn split(&self, pattern: Option<StrPattern>) -> Array { - let s = self.as_str(); - match pattern { - None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), - Some(StrPattern::Str(pat)) => { - s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() - } - Some(StrPattern::Regex(re)) => { - re.split(s).map(|v| Value::Str(v.into())).collect() - } - } - } - - /// Trim either whitespace or the given pattern at both or just one side of - /// the string. If `repeat` is true, the pattern is trimmed repeatedly - /// instead of just once. Repeat must only be given in combination with a - /// pattern. - pub fn trim( - &self, - pattern: Option<StrPattern>, - at: Option<StrSide>, - repeat: bool, - ) -> Self { - let mut start = matches!(at, Some(StrSide::Start) | None); - let end = matches!(at, Some(StrSide::End) | None); - - let trimmed = match pattern { - None => match at { - None => self.0.trim(), - Some(StrSide::Start) => self.0.trim_start(), - Some(StrSide::End) => self.0.trim_end(), - }, - Some(StrPattern::Str(pat)) => { - let pat = pat.as_str(); - let mut s = self.as_str(); - if repeat { - if start { - s = s.trim_start_matches(pat); - } - if end { - s = s.trim_end_matches(pat); - } - } else { - if start { - s = s.strip_prefix(pat).unwrap_or(s); - } - if end { - s = s.strip_suffix(pat).unwrap_or(s); - } - } - s - } - Some(StrPattern::Regex(re)) => { - let s = self.as_str(); - let mut last = 0; - let mut range = 0..s.len(); - - for m in re.find_iter(s) { - // Does this match follow directly after the last one? - let consecutive = last == m.start(); - - // As long as we're consecutive and still trimming at the - // start, trim. - start &= consecutive; - if start { - range.start = m.end(); - start &= repeat; - } - - // Reset end trim if we aren't consecutive anymore or aren't - // repeating. - if end && (!consecutive || !repeat) { - range.end = m.start(); - } - - last = m.end(); - } - - // Is the last match directly at the end? - if last < s.len() { - range.end = s.len(); - } - - &s[range.start..range.start.max(range.end)] - } - }; - - trimmed.into() - } - - /// Replace at most `count` occurrences of the given pattern with a - /// replacement string or function (beginning from the start). If no count - /// is given, all occurrences are replaced. - pub fn replace( - &self, - vm: &mut Vm, - pattern: StrPattern, - with: Replacement, - count: Option<usize>, - ) -> SourceResult<Self> { - // Heuristic: Assume the new string is about the same length as - // the current string. - let mut output = EcoString::with_capacity(self.as_str().len()); - - // Replace one match of a pattern with the replacement. - let mut last_match = 0; - let mut handle_match = |range: Range<usize>, dict: Dict| -> SourceResult<()> { - // Push everything until the match. - output.push_str(&self[last_match..range.start]); - last_match = range.end; - - // Determine and push the replacement. - match &with { - Replacement::Str(s) => output.push_str(s), - Replacement::Func(func) => { - let args = Args::new(func.span(), [dict]); - let piece = func.call_vm(vm, args)?.cast::<Str>().at(func.span())?; - output.push_str(&piece); - } - } - - Ok(()) - }; - - // Iterate over the matches of the `pattern`. - let count = count.unwrap_or(usize::MAX); - match &pattern { - StrPattern::Str(pat) => { - for m in self.match_indices(pat.as_str()).take(count) { - let (start, text) = m; - handle_match(start..start + text.len(), match_to_dict(m))?; - } - } - StrPattern::Regex(re) => { - for caps in re.captures_iter(self).take(count) { - // Extract the entire match over all capture groups. - let m = caps.get(0).unwrap(); - handle_match(m.start()..m.end(), captures_to_dict(caps))?; - } - } - } - - // Push the remainder. - output.push_str(&self[last_match..]); - Ok(output.into()) - } - - /// Repeat the string a number of times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let n = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(Self(self.0.repeat(n))) - } - - /// Resolve an index, if it is within bounds. - /// Errors on invalid char boundaries. - fn locate_opt(&self, index: i64) -> StrResult<Option<usize>> { - let wrapped = - if index >= 0 { Some(index) } else { (self.len() as i64).checked_add(index) }; - - let resolved = wrapped - .and_then(|v| usize::try_from(v).ok()) - .filter(|&v| v <= self.0.len()); - - if resolved.map_or(false, |i| !self.0.is_char_boundary(i)) { - return Err(not_a_char_boundary(index)); - } - - Ok(resolved) - } - - /// Resolve an index or throw an out of bounds error. - fn locate(&self, index: i64) -> StrResult<usize> { - self.locate_opt(index)? - .ok_or_else(|| out_of_bounds(index, self.len())) - } -} - -/// The out of bounds access error message. -#[cold] -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: usize) -> EcoString { - eco_format!("no default value was specified and string index out of bounds (index: {}, len: {})", index, len) -} - -/// The char boundary access error message. -#[cold] -fn not_a_char_boundary(index: i64) -> EcoString { - eco_format!("string index {} is not a character boundary", index) -} - -/// The error message when the string is empty. -#[cold] -fn string_is_empty() -> EcoString { - "string is empty".into() -} - -/// Convert an item of std's `match_indices` to a dictionary. -fn match_to_dict((start, text): (usize, &str)) -> Dict { - dict! { - "start" => start, - "end" => start + text.len(), - "text" => text, - "captures" => Array::new(), - } -} - -/// Convert regex captures to a dictionary. -fn captures_to_dict(cap: regex::Captures) -> Dict { - let m = cap.get(0).expect("missing first match"); - dict! { - "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>(), - } -} - -impl Deref for Str { - type Target = str; - - fn deref(&self) -> &str { - &self.0 - } -} - -impl Display for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self) - } -} - -impl Debug for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('"')?; - for c in self.chars() { - match c { - '\0' => f.write_str("\\u{0}")?, - '\'' => f.write_str("'")?, - '"' => f.write_str(r#"\""#)?, - _ => Display::fmt(&c.escape_debug(), f)?, - } - } - f.write_char('"') - } -} - -impl Add for Str { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Str { - fn add_assign(&mut self, rhs: Self) { - self.0.push_str(rhs.as_str()); - } -} - -impl AsRef<str> for Str { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow<str> for Str { - fn borrow(&self) -> &str { - self - } -} - -impl From<char> for Str { - fn from(c: char) -> Self { - Self(c.into()) - } -} - -impl From<&str> for Str { - fn from(s: &str) -> Self { - Self(s.into()) - } -} - -impl From<EcoString> for Str { - fn from(s: EcoString) -> Self { - Self(s) - } -} - -impl From<String> for Str { - fn from(s: String) -> Self { - Self(s.into()) - } -} - -impl From<Cow<'_, str>> for Str { - fn from(s: Cow<str>) -> Self { - Self(s.into()) - } -} - -impl FromIterator<char> for Str { - fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl From<Str> for EcoString { - fn from(str: Str) -> Self { - str.0 - } -} - -impl From<Str> for String { - fn from(s: Str) -> Self { - s.0.into() - } -} - -cast! { - char, - self => Value::Str(self.into()), - string: Str => { - let mut chars = string.chars(); - match (chars.next(), chars.next()) { - (Some(c), None) => c, - _ => bail!("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); - -impl Regex { - /// Create a new regular expression. - pub fn new(re: &str) -> StrResult<Self> { - regex::Regex::new(re).map(Self).map_err(|err| eco_format!("{err}")) - } -} - -impl Deref for Regex { - type Target = regex::Regex; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Debug for Regex { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "regex({:?})", self.0.as_str()) - } -} - -impl PartialEq for Regex { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Hash for Regex { - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -cast! { - type Regex: "regular expression", -} - -/// A pattern which can be searched for in a string. -#[derive(Debug, Clone)] -pub enum StrPattern { - /// Just a string. - Str(Str), - /// A regular expression. - Regex(Regex), -} - -cast! { - StrPattern, - text: Str => Self::Str(text), - regex: Regex => Self::Regex(regex), -} - -/// A side of a string. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum StrSide { - /// The logical start of the string, may be left or right depending on the - /// language. - Start, - /// The logical end of the string. - End, -} - -cast! { - StrSide, - align: GenAlign => match align { - GenAlign::Start => Self::Start, - GenAlign::End => Self::End, - _ => bail!("expected either `start` or `end`"), - }, -} - -/// A replacement for a matched [`Str`] -pub enum Replacement { - /// A string a match is replaced with. - Str(Str), - /// Function of type Dict -> Str (see `captures_to_dict` or `match_to_dict`) - /// whose output is inserted for the match. - Func(Func), -} - -cast! { - Replacement, - text: Str => Self::Str(text), - func: Func => Self::Func(func) -} diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs deleted file mode 100644 index 0925202e..00000000 --- a/src/eval/symbol.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::cmp::Reverse; -use std::collections::BTreeSet; -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::sync::Arc; - -use ecow::EcoString; - -use crate::diag::{bail, StrResult}; - -/// A symbol, possibly with variants. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Symbol(Repr); - -/// The internal representation. -#[derive(Clone, Eq, PartialEq, Hash)] -enum Repr { - Single(char), - Const(&'static [(&'static str, char)]), - Multi(Arc<(List, EcoString)>), -} - -/// A collection of symbols. -#[derive(Clone, Eq, PartialEq, Hash)] -enum List { - Static(&'static [(&'static str, char)]), - Runtime(Box<[(EcoString, char)]>), -} - -impl Symbol { - /// Create a new symbol from a single character. - pub const fn new(c: char) -> Self { - Self(Repr::Single(c)) - } - - /// Create a symbol with a static variant list. - #[track_caller] - pub const fn list(list: &'static [(&'static str, char)]) -> Self { - debug_assert!(!list.is_empty()); - Self(Repr::Const(list)) - } - - /// Create a symbol with a runtime variant list. - #[track_caller] - pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { - debug_assert!(!list.is_empty()); - Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) - } - - /// Get the symbol's text. - pub fn get(&self) -> char { - match &self.0 { - Repr::Single(c) => *c, - Repr::Const(_) => find(self.variants(), "").unwrap(), - Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(), - } - } - - /// Apply a modifier to the symbol. - pub fn modified(mut self, modifier: &str) -> StrResult<Self> { - if let Repr::Const(list) = self.0 { - self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new()))); - } - - if let Repr::Multi(arc) = &mut self.0 { - let (list, modifiers) = Arc::make_mut(arc); - if !modifiers.is_empty() { - modifiers.push('.'); - } - modifiers.push_str(modifier); - if find(list.variants(), modifiers).is_some() { - return Ok(self); - } - } - - bail!("unknown symbol modifier") - } - - /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { - match &self.0 { - Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Const(list) => Variants::Static(list.iter()), - Repr::Multi(arc) => arc.0.variants(), - } - } - - /// Possible modifiers. - pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { - let mut set = BTreeSet::new(); - let modifiers = match &self.0 { - Repr::Multi(arc) => arc.1.as_str(), - _ => "", - }; - for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(modifiers, modifier) { - set.insert(modifier); - } - } - set.into_iter() - } - - /// Normalize an accent to a combining one. - pub fn combining_accent(c: char) -> Option<char> { - Some(match c { - '\u{0300}' | '`' => '\u{0300}', - '\u{0301}' | '´' => '\u{0301}', - '\u{0302}' | '^' | 'ˆ' => '\u{0302}', - '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', - '\u{0304}' | '¯' => '\u{0304}', - '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', - '\u{0306}' | '˘' => '\u{0306}', - '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', - '\u{0308}' | '¨' => '\u{0308}', - '\u{20db}' => '\u{20db}', - '\u{20dc}' => '\u{20dc}', - '\u{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - _ => return None, - }) - } -} - -impl Debug for Symbol { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char(self.get()) - } -} - -impl Display for Symbol { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char(self.get()) - } -} - -impl List { - /// The characters that are covered by this list. - fn variants(&self) -> Variants<'_> { - match self { - List::Static(list) => Variants::Static(list.iter()), - List::Runtime(list) => Variants::Runtime(list.iter()), - } - } -} - -/// Iterator over variants. -enum Variants<'a> { - Single(std::option::IntoIter<char>), - Static(std::slice::Iter<'static, (&'static str, char)>), - Runtime(std::slice::Iter<'a, (EcoString, char)>), -} - -impl<'a> Iterator for Variants<'a> { - type Item = (&'a str, char); - - fn next(&mut self) -> Option<Self::Item> { - match self { - Self::Single(iter) => Some(("", iter.next()?)), - Self::Static(list) => list.next().copied(), - Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)), - } - } -} - -/// Find the best symbol from the list. -fn find<'a>( - variants: impl Iterator<Item = (&'a str, char)>, - modifiers: &str, -) -> Option<char> { - let mut best = None; - let mut best_score = None; - - // Find the best table entry with this name. - 'outer: for candidate in variants { - for modifier in parts(modifiers) { - if !contained(candidate.0, modifier) { - continue 'outer; - } - } - - let mut matching = 0; - let mut total = 0; - for modifier in parts(candidate.0) { - if contained(modifiers, modifier) { - matching += 1; - } - total += 1; - } - - let score = (matching, Reverse(total)); - if best_score.map_or(true, |b| score > b) { - best = Some(candidate.1); - best_score = Some(score); - } - } - - best -} - -/// Split a modifier list into its parts. -fn parts(modifiers: &str) -> impl Iterator<Item = &str> { - modifiers.split('.').filter(|s| !s.is_empty()) -} - -/// Whether the modifier string contains the modifier `m`. -fn contained(modifiers: &str, m: &str) -> bool { - parts(modifiers).any(|part| part == m) -} diff --git a/src/eval/value.rs b/src/eval/value.rs deleted file mode 100644 index b1782cab..00000000 --- a/src/eval/value.rs +++ /dev/null @@ -1,461 +0,0 @@ -use std::any::Any; -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use ecow::eco_format; -use siphasher::sip128::{Hasher128, SipHasher13}; - -use super::{ - 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::{Label, Styles}; -use crate::syntax::{ast, Span}; -use crate::util::Bytes; - -/// A computational value. -#[derive(Default, Clone)] -pub enum Value { - /// The value that indicates the absence of a meaningful value. - #[default] - None, - /// A value that indicates some smart default behaviour. - Auto, - /// A boolean: `true, false`. - Bool(bool), - /// An integer: `120`. - Int(i64), - /// A floating-point number: `1.2`, `10e-4`. - Float(f64), - /// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`. - Length(Length), - /// An angle: `1.5rad`, `90deg`. - Angle(Angle), - /// A ratio: `50%`. - Ratio(Ratio), - /// A relative length, combination of a ratio and a length: `20% + 5cm`. - Relative(Rel<Length>), - /// A fraction: `1fr`. - Fraction(Fr), - /// A color value: `#f79143ff`. - Color(Color), - /// A symbol: `arrow.l`. - Symbol(Symbol), - /// A string: `"string"`. - Str(Str), - /// Raw bytes. - Bytes(Bytes), - /// A label: `<intro>`. - Label(Label), - /// A content value: `[*Hi* there]`. - Content(Content), - // Content styles. - Styles(Styles), - /// An array of values: `(1, "hi", 12cm)`. - Array(Array), - /// A dictionary value: `(a: 1, b: "hi")`. - Dict(Dict), - /// An executable function. - Func(Func), - /// Captured arguments to a function. - Args(Args), - /// A module. - Module(Module), - /// A dynamic value. - Dyn(Dynamic), -} - -impl Value { - /// Create a new dynamic value. - pub fn dynamic<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self::Dyn(Dynamic::new(any)) - } - - /// Create a numeric value from a number with a unit. - pub fn numeric(pair: (f64, ast::Unit)) -> Self { - let (v, unit) = pair; - match unit { - 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(), - } - } - - /// The name of the stored value's type. - pub fn type_name(&self) -> &'static str { - match self { - Self::None => "none", - Self::Auto => "auto", - Self::Bool(_) => bool::TYPE_NAME, - Self::Int(_) => i64::TYPE_NAME, - Self::Float(_) => f64::TYPE_NAME, - Self::Length(_) => Length::TYPE_NAME, - Self::Angle(_) => Angle::TYPE_NAME, - Self::Ratio(_) => Ratio::TYPE_NAME, - Self::Relative(_) => Rel::<Length>::TYPE_NAME, - Self::Fraction(_) => Fr::TYPE_NAME, - Self::Color(_) => Color::TYPE_NAME, - Self::Symbol(_) => Symbol::TYPE_NAME, - Self::Str(_) => Str::TYPE_NAME, - Self::Bytes(_) => Bytes::TYPE_NAME, - Self::Label(_) => Label::TYPE_NAME, - Self::Content(_) => Content::TYPE_NAME, - Self::Styles(_) => Styles::TYPE_NAME, - Self::Array(_) => Array::TYPE_NAME, - Self::Dict(_) => Dict::TYPE_NAME, - Self::Func(_) => Func::TYPE_NAME, - Self::Args(_) => Args::TYPE_NAME, - Self::Module(_) => Module::TYPE_NAME, - Self::Dyn(v) => v.type_name(), - } - } - - /// Try to cast the value into a specific type. - pub fn cast<T: FromValue>(self) -> StrResult<T> { - T::from_value(self) - } - - /// Try to access a field on the value. - pub fn field(&self, field: &str) -> StrResult<Value> { - match self { - Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol), - Self::Dict(dict) => dict.at(field, None).cloned(), - Self::Content(content) => content.at(field, None), - Self::Module(module) => module.get(field).cloned(), - Self::Func(func) => func.get(field).cloned(), - v => Err(eco_format!("cannot access fields on type {}", v.type_name())), - } - } - - /// Return the debug representation of the value. - pub fn repr(&self) -> Str { - format_str!("{:?}", self) - } - - /// Attach a span to the value, if possible. - pub fn spanned(self, span: Span) -> Self { - match self { - Value::Content(v) => Value::Content(v.spanned(span)), - Value::Func(v) => Value::Func(v.spanned(span)), - v => v, - } - } - - /// Return the display representation of the value. - pub fn display(self) -> Content { - match self { - Self::None => Content::empty(), - Self::Int(v) => item!(text)(eco_format!("{}", v)), - Self::Float(v) => item!(text)(eco_format!("{}", v)), - Self::Str(v) => item!(text)(v.into()), - Self::Symbol(v) => item!(text)(v.get().into()), - Self::Content(v) => v, - Self::Func(_) => Content::empty(), - Self::Module(module) => module.content(), - _ => item!(raw)(self.repr().into(), Some("typc".into()), false), - } - } - - /// Try to extract documentation for the value. - pub fn docs(&self) -> Option<&'static str> { - match self { - Self::Func(func) => func.info().map(|info| info.docs), - _ => None, - } - } -} - -impl Debug for Value { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::None => f.pad("none"), - Self::Auto => f.pad("auto"), - Self::Bool(v) => Debug::fmt(v, f), - Self::Int(v) => Debug::fmt(v, f), - Self::Float(v) => Debug::fmt(v, f), - Self::Length(v) => Debug::fmt(v, f), - Self::Angle(v) => Debug::fmt(v, f), - Self::Ratio(v) => Debug::fmt(v, f), - Self::Relative(v) => Debug::fmt(v, f), - Self::Fraction(v) => Debug::fmt(v, f), - Self::Color(v) => Debug::fmt(v, f), - Self::Symbol(v) => Debug::fmt(v, f), - Self::Str(v) => Debug::fmt(v, f), - Self::Bytes(v) => Debug::fmt(v, f), - Self::Label(v) => Debug::fmt(v, f), - Self::Content(v) => Debug::fmt(v, f), - Self::Styles(v) => Debug::fmt(v, f), - Self::Array(v) => Debug::fmt(v, f), - Self::Dict(v) => Debug::fmt(v, f), - Self::Func(v) => Debug::fmt(v, f), - Self::Args(v) => Debug::fmt(v, f), - Self::Module(v) => Debug::fmt(v, f), - Self::Dyn(v) => Debug::fmt(v, f), - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - ops::equal(self, other) - } -} - -impl PartialOrd for Value { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - ops::compare(self, other).ok() - } -} - -impl Hash for Value { - fn hash<H: Hasher>(&self, state: &mut H) { - std::mem::discriminant(self).hash(state); - match self { - Self::None => {} - Self::Auto => {} - Self::Bool(v) => v.hash(state), - Self::Int(v) => v.hash(state), - Self::Float(v) => v.to_bits().hash(state), - Self::Length(v) => v.hash(state), - Self::Angle(v) => v.hash(state), - Self::Ratio(v) => v.hash(state), - Self::Relative(v) => v.hash(state), - Self::Fraction(v) => v.hash(state), - Self::Color(v) => v.hash(state), - Self::Symbol(v) => v.hash(state), - Self::Str(v) => v.hash(state), - Self::Bytes(v) => v.hash(state), - Self::Label(v) => v.hash(state), - Self::Content(v) => v.hash(state), - Self::Styles(v) => v.hash(state), - Self::Array(v) => v.hash(state), - Self::Dict(v) => v.hash(state), - Self::Func(v) => v.hash(state), - Self::Args(v) => v.hash(state), - Self::Module(v) => v.hash(state), - Self::Dyn(v) => v.hash(state), - } - } -} - -/// A dynamic value. -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Dynamic(Arc<dyn Bounds>); - -impl Dynamic { - /// Create a new instance from any value that satisfies the required bounds. - pub fn new<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self(Arc::new(any)) - } - - /// Whether the wrapped type is `T`. - pub fn is<T: Type + 'static>(&self) -> bool { - (*self.0).as_any().is::<T>() - } - - /// Try to downcast to a reference to a specific type. - pub fn downcast<T: Type + 'static>(&self) -> Option<&T> { - (*self.0).as_any().downcast_ref() - } - - /// The name of the stored value's type. - pub fn type_name(&self) -> &'static str { - self.0.dyn_type_name() - } -} - -impl Debug for Dynamic { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl PartialEq for Dynamic { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) - } -} - -cast! { - Dynamic, - self => Value::Dyn(self), -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_eq(&self, other: &Dynamic) -> bool; - fn dyn_type_name(&self) -> &'static str; - fn hash128(&self) -> u128; -} - -impl<T> Bounds for T -where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn dyn_eq(&self, other: &Dynamic) -> bool { - let Some(other) = other.downcast::<Self>() else { return false }; - self == other - } - - fn dyn_type_name(&self) -> &'static str { - T::TYPE_NAME - } - - #[tracing::instrument(skip_all)] - fn hash128(&self) -> u128 { - // Also hash the TypeId since values with different types but - // equal data should be different. - let mut state = SipHasher13::new(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish128().as_u128() - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u128(self.hash128()); - } -} - -/// The type of a value. -pub trait Type { - /// The name of the type. - const TYPE_NAME: &'static str; -} - -/// Implement traits for primitives. -macro_rules! primitive { - ( - $ty:ty: $name:literal, $variant:ident - $(, $other:ident$(($binding:ident))? => $out:expr)* - ) => { - impl Type for $ty { - const TYPE_NAME: &'static str = $name; - } - - impl Reflect for $ty { - fn describe() -> CastInfo { - CastInfo::Type(Self::TYPE_NAME) - } - - fn castable(value: &Value) -> bool { - matches!(value, Value::$variant(_) - $(| primitive!(@$other $(($binding))?))*) - } - } - - 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),)* - v => Err(eco_format!( - "expected {}, found {}", - Self::TYPE_NAME, - v.type_name(), - )), - } - } - } - }; - - (@$other:ident($binding:ident)) => { Value::$other(_) }; - (@$other:ident) => { Value::$other }; -} - -primitive! { bool: "boolean", Bool } -primitive! { i64: "integer", Int } -primitive! { f64: "float", Float, Int(v) => v as f64 } -primitive! { Length: "length", Length } -primitive! { Angle: "angle", Angle } -primitive! { Ratio: "ratio", Ratio } -primitive! { Rel<Length>: "relative length", - Relative, - Length(v) => v.into(), - Ratio(v) => v.into() -} -primitive! { Fr: "fraction", Fraction } -primitive! { Color: "color", Color } -primitive! { Symbol: "symbol", Symbol } -primitive! { - Str: "string", - Str, - Symbol(symbol) => symbol.get().into() -} -primitive! { Bytes: "bytes", Bytes } -primitive! { Label: "label", Label } -primitive! { Content: "content", - Content, - None => Content::empty(), - Symbol(v) => item!(text)(v.get().into()), - Str(v) => item!(text)(v.into()) -} -primitive! { Styles: "styles", Styles } -primitive! { Array: "array", Array } -primitive! { Dict: "dictionary", Dict } -primitive! { Func: "function", Func } -primitive! { Args: "arguments", Args } -primitive! { Module: "module", Module } - -#[cfg(test)] -mod tests { - use super::*; - use crate::eval::{array, dict}; - use crate::geom::RgbaColor; - - #[track_caller] - fn test(value: impl IntoValue, exp: &str) { - assert_eq!(format!("{:?}", value.into_value()), exp); - } - - #[test] - fn test_value_debug() { - // Primitives. - test(Value::None, "none"); - test(false, "false"); - test(12i64, "12"); - test(3.24, "3.24"); - test(Abs::pt(5.5), "5.5pt"); - test(Angle::deg(90.0), "90deg"); - test(Ratio::one() / 2.0, "50%"); - test(Ratio::new(0.3) + Length::from(Abs::cm(2.0)), "30% + 56.69pt"); - test(Fr::one() * 7.55, "7.55fr"); - test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "rgb(\"#010101\")"); - - // Collections. - test("hello", r#""hello""#); - test("\n", r#""\n""#); - test("\\", r#""\\""#); - test("\"", r#""\"""#); - test(array![], "()"); - test(array![Value::None], "(none,)"); - test(array![1, 2], "(1, 2)"); - test(dict![], "(:)"); - test(dict!["one" => 1], "(one: 1)"); - test(dict!["two" => false, "one" => 1], "(two: false, one: 1)"); - } -} |
