summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs216
-rw-r--r--src/eval/array.rs508
-rw-r--r--src/eval/auto.rs39
-rw-r--r--src/eval/cast.rs316
-rw-r--r--src/eval/datetime.rs201
-rw-r--r--src/eval/dict.rs235
-rw-r--r--src/eval/func.rs643
-rw-r--r--src/eval/int.rs81
-rw-r--r--src/eval/library.rs182
-rw-r--r--src/eval/methods.rs373
-rw-r--r--src/eval/mod.rs1908
-rw-r--r--src/eval/module.rs98
-rw-r--r--src/eval/none.rs74
-rw-r--r--src/eval/ops.rs429
-rw-r--r--src/eval/scope.rs178
-rw-r--r--src/eval/str.rs620
-rw-r--r--src/eval/symbol.rs210
-rw-r--r--src/eval/value.rs461
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)");
- }
-}