diff options
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/args.rs | 193 | ||||
| -rw-r--r-- | src/model/array.rs | 406 | ||||
| -rw-r--r-- | src/model/cast.rs | 514 | ||||
| -rw-r--r-- | src/model/content.rs | 8 | ||||
| -rw-r--r-- | src/model/dict.rs | 209 | ||||
| -rw-r--r-- | src/model/eval.rs | 1523 | ||||
| -rw-r--r-- | src/model/func.rs | 576 | ||||
| -rw-r--r-- | src/model/library.rs | 144 | ||||
| -rw-r--r-- | src/model/methods.rs | 276 | ||||
| -rw-r--r-- | src/model/mod.rs | 43 | ||||
| -rw-r--r-- | src/model/module.rs | 87 | ||||
| -rw-r--r-- | src/model/ops.rs | 414 | ||||
| -rw-r--r-- | src/model/scope.rs | 174 | ||||
| -rw-r--r-- | src/model/str.rs | 514 | ||||
| -rw-r--r-- | src/model/styles.rs | 3 | ||||
| -rw-r--r-- | src/model/symbol.rs | 189 | ||||
| -rw-r--r-- | src/model/typeset.rs | 5 | ||||
| -rw-r--r-- | src/model/value.rs | 497 |
18 files changed, 13 insertions, 5762 deletions
diff --git a/src/model/args.rs b/src/model/args.rs deleted file mode 100644 index 159e9a77..00000000 --- a/src/model/args.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; - -use ecow::EcoVec; - -use super::{Array, Cast, Dict, Str, Value}; -use crate::diag::{bail, At, SourceResult}; -use crate::syntax::{Span, Spanned}; - -/// 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(span: Span, values: impl IntoIterator<Item = Value>) -> Self { - let items = values - .into_iter() - .map(|value| Arg { span, name: None, value: Spanned::new(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: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// 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: Cast<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: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(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: Cast<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: Cast<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::cast(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: Cast<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() { - 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 { - f.write_char('(')?; - for (i, arg) in self.items.iter().enumerate() { - arg.fmt(f)?; - if i + 1 < self.items.len() { - f.write_str(", ")?; - } - } - f.write_char(')') - } -} - -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/model/array.rs b/src/model/array.rs deleted file mode 100644 index 746763ab..00000000 --- a/src/model/array.rs +++ /dev/null @@ -1,406 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter, Write}; -use std::ops::{Add, AddAssign}; - -use ecow::{eco_format, EcoString, EcoVec}; - -use super::{ops, Args, Func, Value, Vm}; -use crate::diag::{bail, At, SourceResult, StrResult}; - -/// Create a new [`Array`] from values. -#[macro_export] -#[doc(hidden)] -macro_rules! __array { - ($value:expr; $count:expr) => { - $crate::model::Array::from_vec($crate::model::eco_vec![$value.into(); $count]) - }; - - ($($value:expr),* $(,)?) => { - $crate::model::Array::from_vec($crate::model::eco_vec![$($value.into()),*]) - }; -} - -#[doc(inline)] -pub use crate::__array as array; -#[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() - } - - /// Create a new array from an eco vector of values. - pub fn from_vec(vec: EcoVec<Value>) -> Self { - Self(vec) - } - - /// The length of the array. - pub fn len(&self) -> i64 { - self.0.len() as i64 - } - - /// 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(&self, index: i64) -> StrResult<&Value> { - self.locate(index) - .and_then(|i| self.0.get(i)) - .ok_or_else(|| out_of_bounds(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(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 contigous 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()); - let end = self - .locate(end) - .filter(|&end| end <= self.0.len()) - .ok_or_else(|| out_of_bounds(end, len))? - .max(start); - - Ok(Self::from_vec(self.0[start..end].into())) - } - - /// Whether the array contains a specific value. - pub fn contains(&self, value: &Value) -> bool { - self.0.contains(value) - } - - /// Return the first matching element. - pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> { - if func.argc().map_or(false, |count| count != 1) { - bail!(func.span(), "function must have exactly one parameter"); - } - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(item.clone())); - } - } - - Ok(None) - } - - /// Return the index of the first matching element. - pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> { - if func.argc().map_or(false, |count| count != 1) { - bail!(func.span(), "function must have exactly one parameter"); - } - for (i, item) in self.iter().enumerate() { - let args = Args::new(func.span(), [item.clone()]); - if func.call(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(Some(i as i64)); - } - } - - Ok(None) - } - - /// Return a new array with only those elements for which the function - /// returns true. - pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - if func.argc().map_or(false, |count| count != 1) { - bail!(func.span(), "function must have exactly one parameter"); - } - let mut kept = EcoVec::new(); - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call(vm, args)?.cast::<bool>().at(func.span())? { - kept.push(item.clone()) - } - } - Ok(Self::from_vec(kept)) - } - - /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { - if func.argc().map_or(false, |count| !(1..=2).contains(&count)) { - bail!(func.span(), "function must have one or two parameters"); - } - let enumerate = func.argc() == Some(2); - self.iter() - .enumerate() - .map(|(i, item)| { - let mut args = Args::new(func.span(), []); - if enumerate { - args.push(func.span(), Value::Int(i as i64)); - } - args.push(func.span(), item.clone()); - func.call(vm, args) - }) - .collect() - } - - /// Fold all of the array's elements into one with a function. - pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> { - if func.argc().map_or(false, |count| count != 2) { - bail!(func.span(), "function must have exactly two parameters"); - } - let mut acc = init; - for item in self.iter() { - let args = Args::new(func.span(), [acc, item.clone()]); - acc = func.call(vm, args)?; - } - Ok(acc) - } - - /// Whether any element matches. - pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - if func.argc().map_or(false, |count| count != 1) { - bail!(func.span(), "function must have exactly one parameter"); - } - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if func.call(vm, args)?.cast::<bool>().at(func.span())? { - return Ok(true); - } - } - - Ok(false) - } - - /// Whether all elements match. - pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { - if func.argc().map_or(false, |count| count != 1) { - bail!(func.span(), "function must have exactly one parameter"); - } - for item in self.iter() { - let args = Args::new(func.span(), [item.clone()]); - if !func.call(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()); - } - } - Self::from_vec(flat) - } - - /// Returns a new array with reversed order. - pub fn rev(&self) -> Self { - self.0.iter().cloned().rev().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) - } - - /// Return a sorted version of this array. - /// - /// Returns an error if two values could not be compared. - pub fn sorted(&self) -> StrResult<Self> { - let mut result = Ok(()); - let mut vec = self.0.clone(); - vec.make_mut().sort_by(|a, b| { - a.partial_cmp(b).unwrap_or_else(|| { - if result.is_ok() { - result = Err(eco_format!( - "cannot order {} and {}", - a.type_name(), - b.type_name(), - )); - } - Ordering::Equal - }) - }); - result.map(|_| Self::from_vec(vec)) - } - - /// 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().checked_add(index)? }) - .ok() - } -} - -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: i64) -> EcoString { - eco_format!("array index out of bounds (index: {}, len: {})", index, len) -} - -/// The error message when the array is empty. -#[cold] -fn array_is_empty() -> EcoString { - "array is empty".into() -} - -impl Debug for Array { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - for (i, value) in self.iter().enumerate() { - value.fmt(f)?; - if i + 1 < self.0.len() { - f.write_str(", ")?; - } - } - if self.len() == 1 { - f.write_char(',')?; - } - f.write_char(')') - } -} - -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() - } -} diff --git a/src/model/cast.rs b/src/model/cast.rs deleted file mode 100644 index 4c300550..00000000 --- a/src/model/cast.rs +++ /dev/null @@ -1,514 +0,0 @@ -use std::num::NonZeroUsize; -use std::ops::Add; -use std::str::FromStr; - -use ecow::EcoString; - -use super::{ - castable, Array, Content, Dict, Func, Label, Regex, Selector, Str, Transform, Value, -}; -use crate::diag::StrResult; -use crate::doc::{Destination, Lang, Location, Region}; -use crate::font::{FontStretch, FontStyle, FontWeight}; -use crate::geom::{ - Axes, Color, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Ratio, - Rel, Sides, Smart, -}; -use crate::syntax::Spanned; - -/// Cast from a value to a specific type. -pub trait Cast<V = Value>: Sized { - /// Check whether the value is castable to `Self`. - fn is(value: &V) -> bool; - - /// Try to cast the value into an instance of `Self`. - fn cast(value: V) -> StrResult<Self>; - - /// Describe the acceptable values. - fn describe() -> CastInfo; - - /// Produce an error for an inacceptable value. - fn error(value: Value) -> StrResult<Self> { - Err(Self::describe().error(&value)) - } -} - -/// Describes a possible value for a cast. -#[derive(Debug, Clone, Hash)] -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"); - } - - crate::diag::comma_list(&mut msg, &parts, "or"); - - if !matching_type { - msg.push_str(", found "); - msg.push_str(found.type_name()); - } - - 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)) => { - lhs.extend(rhs); - lhs - } - (Self::Union(mut lhs), rhs) => { - lhs.push(rhs); - lhs - } - (lhs, Self::Union(mut rhs)) => { - rhs.insert(0, lhs); - rhs - } - (lhs, rhs) => vec![lhs, rhs], - }) - } -} - -impl Cast for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult<Self> { - Ok(value) - } - - fn describe() -> CastInfo { - CastInfo::Any - } -} - -impl<T: Cast> Cast<Spanned<Value>> for T { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - T::cast(value.v) - } - - fn describe() -> CastInfo { - T::describe() - } -} - -impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) - } - - fn describe() -> CastInfo { - T::describe() - } -} - -castable! { - Dir: "direction", -} - -castable! { - GenAlign: "alignment", -} - -castable! { - Regex: "regular expression", -} - -castable! { - Selector: "selector", - text: EcoString => Self::text(&text), - label: Label => Self::Label(label), - func: Func => func.select(None)?, - regex: Regex => Self::Regex(regex), -} - -castable! { - Axes<GenAlign>: "2d alignment", -} - -castable! { - PartialStroke: "stroke", - thickness: Length => Self { - paint: Smart::Auto, - thickness: Smart::Custom(thickness), - }, - color: Color => Self { - paint: Smart::Custom(color.into()), - thickness: Smart::Auto, - }, -} - -castable! { - u32, - int: i64 => int.try_into().map_err(|_| { - if int < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, -} - -castable! { - usize, - int: i64 => int.try_into().map_err(|_| { - if int < 0 { - "number must be at least zero" - } else { - "number too large" - } - })?, -} - -castable! { - NonZeroUsize, - int: i64 => int - .try_into() - .and_then(|int: usize| int.try_into()) - .map_err(|_| if int <= 0 { - "number must be positive" - } else { - "number too large" - })?, -} - -castable! { - Paint, - color: Color => Self::Solid(color), -} - -castable! { - char, - string: Str => { - let mut chars = string.chars(); - match (chars.next(), chars.next()) { - (Some(c), None) => c, - _ => Err("expected exactly one character")?, - } - }, -} - -castable! { - EcoString, - string: Str => string.into(), -} - -castable! { - String, - string: Str => string.into(), -} - -castable! { - Transform, - content: Content => Self::Content(content), - func: Func => { - if func.argc().map_or(false, |count| count != 1) { - Err("function must have exactly one parameter")? - } - Self::Func(func) - }, -} - -castable! { - Axes<Option<GenAlign>>, - align: GenAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(align)); - aligns - }, - aligns: Axes<GenAlign> => aligns.map(Some), -} - -castable! { - Axes<Rel<Length>>, - array: Array => { - let mut iter = array.into_iter(); - match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), - _ => Err("point array must contain exactly two entries")?, - } - }, -} - -castable! { - Location, - mut dict: Dict => { - let page = dict.take("page")?.cast()?; - let x: Length = dict.take("x")?.cast()?; - let y: Length = dict.take("y")?.cast()?; - dict.finish(&["page", "x", "y"])?; - Self { page, pos: Point::new(x.abs, y.abs) } - }, -} - -castable! { - Destination, - loc: Location => Self::Internal(loc), - string: EcoString => Self::Url(string), -} - -castable! { - FontStyle, - /// The default, typically upright style. - "normal" => Self::Normal, - /// A cursive style with custom letterform. - "italic" => Self::Italic, - /// Just a slanted version of the normal style. - "oblique" => Self::Oblique, -} - -castable! { - FontWeight, - v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16), - /// Thin weight (100). - "thin" => Self::THIN, - /// Extra light weight (200). - "extralight" => Self::EXTRALIGHT, - /// Light weight (300). - "light" => Self::LIGHT, - /// Regular weight (400). - "regular" => Self::REGULAR, - /// Medium weight (500). - "medium" => Self::MEDIUM, - /// Semibold weight (600). - "semibold" => Self::SEMIBOLD, - /// Bold weight (700). - "bold" => Self::BOLD, - /// Extrabold weight (800). - "extrabold" => Self::EXTRABOLD, - /// Black weight (900). - "black" => Self::BLACK, -} - -castable! { - FontStretch, - v: Ratio => Self::from_ratio(v.get() as f32), -} - -castable! { - Lang, - string: EcoString => Self::from_str(&string)?, -} - -castable! { - Region, - string: EcoString => Self::from_str(&string)?, -} - -/// Castable from [`Value::None`]. -pub struct NoneValue; - -impl Cast for NoneValue { - fn is(value: &Value) -> bool { - matches!(value, Value::None) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(Self), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - CastInfo::Type("none") - } -} - -impl<T: Cast> Cast for Option<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::None) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(None), - v if T::is(&v) => Ok(Some(T::cast(v)?)), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("none") - } -} - -/// Castable from [`Value::Auto`]. -pub struct AutoValue; - -impl Cast for AutoValue { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - CastInfo::Type("auto") - } -} - -impl<T: Cast> Cast for Smart<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self::Auto), - v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), - _ => <Self as Cast>::error(value), - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("auto") - } -} - -impl<T> Cast for Sides<Option<T>> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); - - let rest = take("rest")?; - let x = take("x")?.or(rest); - let y = take("y")?.or(rest); - let sides = Sides { - left: take("left")?.or(x), - top: take("top")?.or(y), - right: take("right")?.or(x), - bottom: take("bottom")?.or(y), - }; - - dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; - - Ok(sides) - } else if T::is(&value) { - Ok(Self::splat(Some(T::cast(value)?))) - } else { - <Self as Cast>::error(value) - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("dictionary") - } -} - -impl<T> Cast for Corners<Option<T>> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); - - let rest = take("rest")?; - let left = take("left")?.or(rest); - let top = take("top")?.or(rest); - let right = take("right")?.or(rest); - let bottom = take("bottom")?.or(rest); - let corners = Corners { - top_left: take("top-left")?.or(top).or(left), - top_right: take("top-right")?.or(top).or(right), - bottom_right: take("bottom-right")?.or(bottom).or(right), - bottom_left: take("bottom-left")?.or(bottom).or(left), - }; - - dict.finish(&[ - "top-left", - "top-right", - "bottom-right", - "bottom-left", - "left", - "top", - "right", - "bottom", - "rest", - ])?; - - Ok(corners) - } else if T::is(&value) { - Ok(Self::splat(Some(T::cast(value)?))) - } else { - <Self as Cast>::error(value) - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("dictionary") - } -} diff --git a/src/model/content.rs b/src/model/content.rs index b8047ffa..b10a3409 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -10,11 +10,9 @@ use ecow::{EcoString, EcoVec}; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{ - capability, capable, Args, Guard, Key, ParamInfo, Property, Recipe, Style, StyleMap, - Value, Vm, -}; +use super::{capability, capable, Guard, Key, Property, Recipe, Style, StyleMap}; use crate::diag::{SourceResult, StrResult}; +use crate::eval::{Args, ParamInfo, Value, Vm}; use crate::syntax::Span; use crate::util::ReadableTypeId; use crate::World; @@ -243,7 +241,7 @@ impl Content { } /// Whether a label can be attached to the content. - pub(super) fn labellable(&self) -> bool { + pub(crate) fn labellable(&self) -> bool { !self.has::<dyn Unlabellable>() } diff --git a/src/model/dict.rs b/src/model/dict.rs deleted file mode 100644 index 50a2275f..00000000 --- a/src/model/dict.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter, Write}; -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::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 = std::collections::BTreeMap::new(); - $(map.insert($key.into(), $value.into());)* - $crate::model::Dict::from_map(map) - }}; -} - -#[doc(inline)] -pub use crate::__dict as dict; - -/// A reference-counted dictionary with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Dict(Arc<BTreeMap<Str, Value>>); - -impl Dict { - /// Create a new, empty dictionary. - pub fn new() -> Self { - Self::default() - } - - /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: BTreeMap<Str, Value>) -> Self { - Self(Arc::new(map)) - } - - /// Whether the dictionary is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// The number of pairs in the dictionary. - pub fn len(&self) -> i64 { - self.0.len() as i64 - } - - /// Borrow the value the given `key` maps to. - pub fn at(&self, key: &str) -> StrResult<&Value> { - self.0.get(key).ok_or_else(|| missing_key(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(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).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) -> std::collections::btree_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 "); - crate::diag::comma_list(&mut msg, &parts, "and"); - return Err(msg.into()); - } - Ok(()) - } -} - -/// The missing key access error message. -#[cold] -fn missing_key(key: &str) -> EcoString { - eco_format!("dictionary does not contain key {:?}", Str::from(key)) -} - -impl Debug for Dict { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - if self.is_empty() { - f.write_char(':')?; - } - for (i, (key, value)) in self.iter().enumerate() { - if is_ident(key) { - f.write_str(key)?; - } else { - write!(f, "{key:?}")?; - } - f.write_str(": ")?; - value.fmt(f)?; - if i + 1 < self.0.len() { - f.write_str(", ")?; - } - } - f.write_char(')') - } -} - -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 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 = std::collections::btree_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 = std::collections::btree_map::Iter<'a, Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} diff --git a/src/model/eval.rs b/src/model/eval.rs deleted file mode 100644 index 225c5e7a..00000000 --- a/src/model/eval.rs +++ /dev/null @@ -1,1523 +0,0 @@ -//! Evaluation of markup into modules. - -use std::collections::BTreeMap; -use std::mem; -use std::path::{Path, PathBuf}; - -use comemo::{Track, Tracked, TrackedMut}; -use ecow::EcoVec; -use unicode_segmentation::UnicodeSegmentation; - -use super::{ - combining_accent, methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, - Dict, Func, Label, LangItems, Module, Recipe, Scopes, Selector, StyleMap, Symbol, - Transform, Value, -}; -use crate::diag::{ - bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, -}; -use crate::syntax::ast::AstNode; -use crate::syntax::{ - ast, parse_code, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, -}; -use crate::util::PathExt; -use crate::World; - -const MAX_ITERATIONS: usize = 10_000; -const MAX_CALL_DEPTH: usize = 256; - -/// Evaluate a source file and return the resulting module. -#[comemo::memoize] -pub fn eval( - world: Tracked<dyn World>, - route: Tracked<Route>, - tracer: TrackedMut<Tracer>, - source: &Source, -) -> SourceResult<Module> { - // Prevent cyclic evaluation. - let id = source.id(); - let path = if id.is_detached() { Path::new("") } else { world.source(id).path() }; - if route.contains(id) { - panic!("Tried to cyclicly evaluate {}", path.display()); - } - - // Hook up the lang items. - let library = world.library(); - super::set_lang_items(library.items.clone()); - - // Evaluate the module. - let route = unsafe { Route::insert(route, id) }; - let scopes = Scopes::new(Some(library)); - let mut vm = Vm::new(world, route.track(), tracer, id, scopes, 0); - let root = match source.root().cast::<ast::Markup>() { - Some(markup) if vm.traced.is_some() => markup, - _ => source.ast()?, - }; - - let result = root.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - // Assemble the module. - let name = 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_code_str( - world: Tracked<dyn World>, - text: &str, - span: Span, -) -> SourceResult<Value> { - let mut root = parse_code(text); - root.synthesize(span); - - let errors = root.errors(); - if !errors.is_empty() { - return Err(Box::new(errors)); - } - - let id = SourceId::detached(); - let library = world.library(); - let scopes = Scopes::new(Some(library)); - let route = Route::default(); - let mut tracer = Tracer::default(); - let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0); - 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 compilation environment. - pub(super) world: Tracked<'a, dyn World>, - /// The language items. - pub(super) items: LangItems, - /// The route of source ids the VM took to reach its current location. - pub(super) route: Tracked<'a, Route>, - /// The tracer for inspection of the values an expression produces. - pub(super) tracer: TrackedMut<'a, Tracer>, - /// The current location. - pub(super) location: SourceId, - /// A control flow event that is currently happening. - pub(super) flow: Option<Flow>, - /// The stack of scopes. - pub(super) scopes: Scopes<'a>, - /// The current call depth. - pub(super) depth: usize, - /// A span that is currently traced. - pub(super) traced: Option<Span>, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - pub(super) fn new( - world: Tracked<'a, dyn World>, - route: Tracked<'a, Route>, - tracer: TrackedMut<'a, Tracer>, - location: SourceId, - scopes: Scopes<'a>, - depth: usize, - ) -> Self { - let traced = tracer.span(location); - Self { - world, - items: world.library().items.clone(), - route, - tracer, - location, - flow: None, - scopes, - depth, - traced, - } - } - - /// Access the underlying world. - pub fn world(&self) -> Tracked<'a, dyn World> { - self.world - } - - /// Define a variable in the current scope. - pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) { - let value = value.into(); - if self.traced == Some(var.span()) { - self.tracer.trace(value.clone()); - } - self.scopes.top.define(var.take(), value); - } - - /// Resolve a user-entered path to be relative to the compilation - /// environment's root. - pub fn locate(&self, path: &str) -> StrResult<PathBuf> { - if !self.location.is_detached() { - if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.root().join(path).normalize()); - } - - if let Some(dir) = self.world.source(self.location).path().parent() { - return Ok(dir.join(path).normalize()); - } - } - - Err("cannot access file system from here".into()) - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum Flow { - /// 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 Flow { - /// 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 { - parent: Option<Tracked<'static, Self>>, - id: Option<SourceId>, -} - -impl Route { - /// Create a new route with just one entry. - pub fn new(id: SourceId) -> Self { - Self { id: Some(id), parent: None } - } - - /// Insert a new id into the route. - /// - /// You must guarantee that `outer` lives longer than the resulting - /// route is ever used. - unsafe fn insert(outer: Tracked<Route>, id: SourceId) -> Route { - Route { - parent: Some(std::mem::transmute(outer)), - id: Some(id), - } - } -} - -#[comemo::track] -impl Route { - /// Whether the given id is part of the route. - fn contains(&self, id: SourceId) -> bool { - self.id == Some(id) || self.parent.map_or(false, |parent| parent.contains(id)) - } -} - -/// Traces which values existed for the expression with the given 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: SourceId) -> Option<Span> { - if self.span.map(Span::source) == 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.world, recipe)?) - } - expr => match expr.eval(vm)? { - Value::Label(label) => { - if let Some(node) = - seq.iter_mut().rev().find(|node| node.labellable()) - { - *node = mem::take(node).labelled(label); - } - } - value => seq.push(value.display()), - }, - } - - 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; - - 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::Formula(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::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::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.tracer.trace(v.clone()); - } - - Ok(v) - } -} - -impl Eval for ast::Text { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.text)(self.get().clone())) - } -} - -impl Eval for ast::Space { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.space)()) - } -} - -impl Eval for ast::Linebreak { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.linebreak)()) - } -} - -impl Eval for ast::Parbreak { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.parbreak)()) - } -} - -impl Eval for ast::Escape { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::Shorthand { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::SmartQuote { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.smart_quote)(self.double())) - } -} - -impl Eval for ast::Strong { - type Output = Content; - - 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; - - 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; - - 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; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.link)(self.get().clone())) - } -} - -impl Eval for ast::Label { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Label(Label(self.get().into()))) - } -} - -impl Eval for ast::Ref { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.ref_)(self.get().into())) - } -} - -impl Eval for ast::Heading { - type Output = Content; - - 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; - - 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; - - 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; - - 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::Formula { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let body = self.body().eval(vm)?; - let block = self.block(); - Ok((vm.items.formula)(body, block)) - } -} - -impl Eval for ast::Math { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::sequence( - self.exprs() - .map(|expr| Ok(expr.eval(vm)?.display())) - .collect::<SourceResult<_>>()?, - ) - .spanned(self.span())) - } -} - -impl Eval for ast::MathIdent { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?) - } -} - -impl Eval for ast::MathAlignPoint { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_align_point)()) - } -} - -impl Eval for ast::MathDelimited { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let open = self.open().eval(vm)?.display(); - let body = self.body().eval(vm)?; - let close = self.close().eval(vm)?.display(); - Ok((vm.items.math_delimited)(open, body, close)) - } -} - -impl Eval for ast::MathAttach { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let base = self.base().eval(vm)?.display(); - let bottom = self - .bottom() - .map(|expr| expr.eval(vm).map(Value::display)) - .transpose()?; - let top = self.top().map(|expr| expr.eval(vm).map(Value::display)).transpose()?; - Ok((vm.items.math_attach)(base, bottom, top)) - } -} - -impl Eval for ast::MathFrac { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let num = self.num().eval(vm)?.display(); - let denom = self.denom().eval(vm)?.display(); - Ok((vm.items.math_frac)(num, denom)) - } -} - -impl Eval for ast::Ident { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(vm.scopes.get(self).cloned().at(self.span())?) - } -} - -impl Eval for ast::None { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::None) - } -} - -impl Eval for ast::Auto { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Auto) - } -} - -impl Eval for ast::Bool { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Bool(self.get())) - } -} - -impl Eval for ast::Int { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Int(self.get())) - } -} - -impl Eval for ast::Float { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Float(self.get())) - } -} - -impl Eval for ast::Numeric { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::numeric(self.get())) - } -} - -impl Eval for ast::Str { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Value::Str(self.get().into())) - } -} - -impl Eval for ast::CodeBlock { - type Output = Value; - - 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.world, 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; - - 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; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - self.expr().eval(vm) - } -} - -impl Eval for ast::Array { - type Output = Array; - - 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(Array::from_vec(vec)) - } -} - -impl Eval for ast::Dict { - type Output = Dict; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut map = BTreeMap::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(Dict::from_map(map)) - } -} - -impl Eval for ast::Unary { - type Output = Value; - - 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; - - 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; - - 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; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - 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)?; - if !matches!(target, Value::Symbol(_) | Value::Module(_)) { - 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(_)) { - 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) = 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() - + (vm.items.math_delimited)( - (vm.items.text)('('.into()), - body, - (vm.items.text)(')'.into()), - ), - )); - } - - // Finally, just a normal function call! - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } - - let callee = callee.cast::<Func>().at(callee_span)?; - let point = || Tracepoint::Call(callee.name().map(Into::into)); - callee.call(vm, args).trace(vm.world, point, span) - } -} - -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; - - 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().map(ast::Ident::take); - - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; - - let mut params = Vec::new(); - let mut sink = None; - - // Collect parameters and an optional sink parameter. - for param in self.params() { - match param { - ast::Param::Pos(name) => { - params.push((name.take(), None)); - } - ast::Param::Named(named) => { - params.push((named.name().take(), Some(named.expr().eval(vm)?))); - } - ast::Param::Sink(name) => { - if sink.is_some() { - bail!(name.span(), "only one argument sink is allowed"); - } - sink = Some(name.take()); - } - } - } - - // Define the closure. - let closure = Closure { - location: vm.location, - name, - captured, - params, - sink, - body: self.body(), - }; - - Ok(Value::Func(Func::from_closure(closure, self.span()))) - } -} - -impl Eval for ast::LetBinding { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = match self.init() { - Some(expr) => expr.eval(vm)?, - None => Value::None, - }; - vm.define(self.binding(), value); - Ok(Value::None) - } -} - -impl Eval for ast::SetRule { - type Output = StyleMap; - - 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(StyleMap::new()); - } - } - - let target = self.target(); - let target = target.eval(vm)?.cast::<Func>().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::<Selector>().at(sel.span())) - .transpose()?; - - 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; - - 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; - - 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(Flow::Break(_)) => { - vm.flow = None; - break; - } - Some(Flow::Continue(_)) => vm.flow = None, - Some(Flow::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; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - - macro_rules! iter { - (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ - vm.scopes.enter(); - - #[allow(unused_parens)] - for ($($value),*) in $iter { - $(vm.define($binding.clone(), $value);)* - - let body = self.body(); - let value = body.eval(vm)?; - output = ops::join(output, value).at(body.span())?; - - match vm.flow { - Some(Flow::Break(_)) => { - vm.flow = None; - break; - } - Some(Flow::Continue(_)) => vm.flow = None, - Some(Flow::Return(..)) => break, - None => {} - } - } - - vm.scopes.exit(); - }}; - } - - let iter = self.iter().eval(vm)?; - let pattern = self.pattern(); - let key = pattern.key(); - let value = pattern.value(); - - match (key, value, iter) { - (None, v, Value::Str(string)) => { - iter!(for (v => value) in string.as_str().graphemes(true)); - } - (None, v, Value::Array(array)) => { - iter!(for (v => value) in array.into_iter()); - } - (Some(i), v, Value::Array(array)) => { - iter!(for (i => idx, v => value) in array.into_iter().enumerate()); - } - (None, v, Value::Dict(dict)) => { - iter!(for (v => value) in dict.into_iter().map(|p| p.1)); - } - (Some(k), v, Value::Dict(dict)) => { - iter!(for (k => key, v => value) in dict.into_iter()); - } - (None, v, Value::Args(args)) => { - iter!(for (v => value) in args.items.into_iter() - .filter(|arg| arg.name.is_none()) - .map(|arg| arg.value.v)); - } - (Some(k), v, Value::Args(args)) => { - iter!(for (k => key, v => value) in args.items.into_iter() - .map(|arg| (arg.name.map_or(Value::None, Value::Str), arg.value.v))); - } - (_, _, Value::Str(_)) => { - bail!(pattern.span(), "mismatched pattern"); - } - (_, _, iter) => { - bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -impl Eval for ast::ModuleImport { - type Output = Value; - - 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)?; - - match self.imports() { - None => { - vm.scopes.top.define(module.name().clone(), module); - } - Some(ast::Imports::Wildcard) => { - for (var, value) in module.scope().iter() { - vm.scopes.top.define(var.clone(), value.clone()); - } - } - Some(ast::Imports::Items(idents)) => { - let mut errors = vec![]; - for ident in idents { - if let Some(value) = module.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(Value::None) - } -} - -impl Eval for ast::ModuleInclude { - type Output = Content; - - 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)?; - Ok(module.content()) - } -} - -/// Process an import of a module relative to the current location. -fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult<Module> { - let path = match source { - Value::Str(path) => path, - Value::Module(module) => return Ok(module), - v => bail!(span, "expected path or module, found {}", v.type_name()), - }; - - // Load the source file. - let full = vm.locate(&path).at(span)?; - let id = vm.world.resolve(&full).at(span)?; - - // Prevent cyclic importing. - if vm.route.contains(id) { - bail!(span, "cyclic import"); - } - - // Evaluate the file. - let source = vm.world.source(id); - let point = || Tracepoint::Import; - eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source) - .trace(vm.world, point, span) -} - -impl Eval for ast::LoopBreak { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(Flow::Break(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::LoopContinue { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(Flow::Continue(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::FuncReturn { - type Output = Value; - - 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(Flow::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.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/model/func.rs b/src/model/func.rs deleted file mode 100644 index 2ba462d3..00000000 --- a/src/model/func.rs +++ /dev/null @@ -1,576 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use comemo::{Prehashed, Track, Tracked, TrackedMut}; -use ecow::EcoString; - -use super::{ - Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, - StyleMap, Tracer, Value, Vm, -}; -use crate::diag::{bail, SourceResult, StrResult}; -use crate::syntax::ast::{self, AstNode, Expr}; -use crate::syntax::{SourceId, Span, SyntaxNode}; -use crate::util::hash128; -use crate::World; - -/// An evaluatable function. -#[derive(Clone, Hash)] -pub struct Func(Arc<Prehashed<Repr>>, Span); - -/// The different kinds of function representations. -#[derive(Hash)] -enum Repr { - /// A native rust function. - Native(Native), - /// A user-defined closure. - Closure(Closure), - /// A nested function with pre-applied arguments. - With(Func, Args), -} - -impl Func { - /// Create a new function from a type that can be turned into a function. - pub fn from_type<T: FuncType>(name: &'static str) -> Self { - T::create_func(name) - } - - /// Create a new function from a native rust function. - pub fn from_fn( - func: fn(&Vm, &mut Args) -> SourceResult<Value>, - info: FuncInfo, - ) -> Self { - Self( - Arc::new(Prehashed::new(Repr::Native(Native { - func, - set: None, - node: None, - info, - }))), - Span::detached(), - ) - } - - /// Create a new function from a native rust node. - pub fn from_node<T: Node>(mut info: FuncInfo) -> Self { - info.params.extend(T::properties()); - Self( - Arc::new(Prehashed::new(Repr::Native(Native { - func: |ctx, args| { - let styles = T::set(args, true)?; - let content = T::construct(ctx, args)?; - Ok(Value::Content(content.styled_with_map(styles.scoped()))) - }, - set: Some(|args| T::set(args, false)), - node: Some(NodeId::of::<T>()), - info, - }))), - Span::detached(), - ) - } - - /// Create a new function from a closure. - pub(super) fn from_closure(closure: Closure, span: Span) -> Self { - Self(Arc::new(Prehashed::new(Repr::Closure(closure))), span) - } - - /// The name of the function. - pub fn name(&self) -> Option<&str> { - match &**self.0 { - Repr::Native(native) => Some(native.info.name), - Repr::Closure(closure) => closure.name.as_deref(), - Repr::With(func, _) => func.name(), - } - } - - /// Extract details the function. - pub fn info(&self) -> Option<&FuncInfo> { - match &**self.0 { - Repr::Native(native) => Some(&native.info), - Repr::With(func, _) => func.info(), - _ => None, - } - } - - /// The function's span. - pub fn span(&self) -> Span { - self.1 - } - - /// Attach a span to the function. - pub fn spanned(mut self, span: Span) -> Self { - self.1 = span; - self - } - - /// The number of positional arguments this function takes, if known. - pub fn argc(&self) -> Option<usize> { - match &**self.0 { - Repr::Closure(closure) => closure.argc(), - Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub( - applied.items.iter().filter(|arg| arg.name.is_none()).count(), - )), - _ => None, - } - } - - /// Call the function with the given arguments. - pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> { - match &**self.0 { - Repr::Native(native) => { - let value = (native.func)(vm, &mut args)?; - args.finish()?; - Ok(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, - TrackedMut::reborrow_mut(&mut vm.tracer), - vm.depth + 1, - args, - ) - } - Repr::With(wrapped, applied) => { - args.items = applied.items.iter().cloned().chain(args.items).collect(); - return wrapped.call(vm, args); - } - } - } - - /// Call the function without an existing virtual machine. - pub fn call_detached( - &self, - world: Tracked<dyn World>, - args: Args, - ) -> SourceResult<Value> { - let route = Route::default(); - let id = SourceId::detached(); - let scopes = Scopes::new(None); - let mut tracer = Tracer::default(); - let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0); - self.call(&mut vm, args) - } - - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - let span = self.1; - Self(Arc::new(Prehashed::new(Repr::With(self, args))), span) - } - - /// Create a selector for this function's node type, filtering by node's - /// whose [fields](super::Content::field) match the given arguments. - pub fn where_(self, args: &mut Args) -> StrResult<Selector> { - let fields = args.to_named(); - args.items.retain(|arg| arg.name.is_none()); - self.select(Some(fields)) - } - - /// Execute the function's set rule and return the resulting style map. - pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { - Ok(match &**self.0 { - Repr::Native(Native { set: Some(set), .. }) => { - let styles = set(&mut args)?; - args.finish()?; - styles - } - _ => StyleMap::new(), - }) - } - - /// Create a selector for this function's node type. - pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> { - match **self.0 { - Repr::Native(Native { node: Some(id), .. }) => { - if id == item!(text_id) { - Err("to select text, please use a string or regex instead")?; - } - - Ok(Selector::Node(id, fields)) - } - _ => Err("this function is not selectable")?, - } - } -} - -impl Debug for Func { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.name() { - Some(name) => write!(f, "<function {name}>"), - None => f.write_str("<function>"), - } - } -} - -impl PartialEq for Func { - fn eq(&self, other: &Self) -> bool { - hash128(&self.0) == hash128(&other.0) - } -} - -/// Types that can be turned into functions. -pub trait FuncType { - /// Create a function with the given name from this type. - fn create_func(name: &'static str) -> Func; -} - -/// A function defined by a native rust function or node. -struct Native { - /// The function pointer. - func: fn(&Vm, &mut Args) -> SourceResult<Value>, - /// The set rule. - set: Option<fn(&mut Args) -> SourceResult<StyleMap>>, - /// The id of the node to customize with this function's show rule. - node: Option<NodeId>, - /// Documentation of the function. - info: FuncInfo, -} - -impl Hash for Native { - fn hash<H: Hasher>(&self, state: &mut H) { - (self.func as usize).hash(state); - self.set.map(|set| set as usize).hash(state); - self.node.hash(state); - } -} - -/// 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, - /// Documentation for the function. - pub docs: &'static str, - /// Details about the function's parameters. - pub params: Vec<ParamInfo>, - /// Valid types for the return value. - pub returns: Vec<&'static str>, - /// Which category the function is part of. - pub category: &'static str, -} - -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, - /// 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, - /// Is the parameter required? - pub required: bool, - /// Can the parameter be given any number of times? - pub variadic: 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: SourceId, - /// The name of the closure. - pub name: Option<EcoString>, - /// Captured values from outer scopes. - pub captured: Scope, - /// The parameter names and default values. Parameters with default value - /// are named parameters. - pub params: Vec<(EcoString, Option<Value>)>, - /// The name of an argument sink where remaining arguments are placed. - pub sink: Option<EcoString>, - /// The expression the closure should evaluate to. - pub body: Expr, -} - -impl Closure { - /// Call the function in the context with the arguments. - #[comemo::memoize] - fn call( - this: &Func, - world: Tracked<dyn World>, - route: Tracked<Route>, - tracer: TrackedMut<Tracer>, - depth: usize, - mut args: Args, - ) -> SourceResult<Value> { - let closure = match &**this.0 { - 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(); - - // Provide the closure itself for recursive calls. - if let Some(name) = &closure.name { - scopes.top.define(name.clone(), Value::Func(this.clone())); - } - - // Parse the arguments according to the parameter list. - for (param, default) in &closure.params { - scopes.top.define( - param.clone(), - match default { - Some(default) => { - args.named::<Value>(param)?.unwrap_or_else(|| default.clone()) - } - None => args.expect::<Value>(param)?, - }, - ); - } - - // Put the remaining arguments into the sink. - if let Some(sink) = &closure.sink { - scopes.top.define(sink.clone(), args.take()); - } - - // Ensure all arguments have been used. - args.finish()?; - - // Evaluate the body. - let mut sub = Vm::new(world, route, tracer, closure.location, scopes, depth); - let result = closure.body.eval(&mut sub); - - // Handle control flow. - match sub.flow { - Some(Flow::Return(_, Some(explicit))) => return Ok(explicit), - Some(Flow::Return(_, None)) => {} - Some(flow) => bail!(flow.forbidden()), - None => {} - } - - result - } - - /// The number of positional arguments this function takes, if known. - fn argc(&self) -> Option<usize> { - if self.sink.is_some() { - return None; - } - - Some(self.params.iter().filter(|(_, default)| default.is_none()).count()) - } -} - -/// 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. - 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() { - 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() { - match param { - ast::Param::Pos(ident) => self.bind(ident), - ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(ident) => self.bind(ident), - } - } - - 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()); - } - self.bind(expr.binding()); - } - - // 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(); - if let Some(key) = pattern.key() { - self.bind(key); - } - self.bind(pattern.value()); - 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/model/library.rs b/src/model/library.rs deleted file mode 100644 index 8ef22f10..00000000 --- a/src/model/library.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; - -use ecow::EcoString; -use once_cell::sync::OnceCell; - -use super::{Content, Module, NodeId, StyleChain, StyleMap, Vt}; -use crate::diag::SourceResult; -use crate::doc::Document; -use crate::geom::{Abs, Dir}; -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: StyleMap, - /// 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 id of the text node. - pub text_id: NodeId, - /// Get the string if this is a text node. - pub text_str: fn(&Content) -> Option<&str>, - /// 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, - /// A hyperlink: `https://typst.org`. - pub link: fn(url: EcoString) -> Content, - /// A reference: `@target`. - pub ref_: fn(target: EcoString) -> Content, - /// A section heading: `= Introduction`. - pub heading: fn(level: NonZeroUsize, body: Content) -> Content, - /// 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<NonZeroUsize>, body: Content) -> Content, - /// An item in a term list: `/ Term: Details`. - pub term_item: fn(term: Content, description: Content) -> Content, - /// A mathematical formula: `$x$`, `$ x^2 $`. - pub formula: fn(body: Content, block: bool) -> Content, - /// An alignment point in a formula: `&`. - pub math_align_point: fn() -> Content, - /// Matched delimiters surrounding math in a formula: `[x + y]`. - pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, - /// A base with optional attachments in a formula: `a_1^2`. - pub math_attach: - fn(base: Content, bottom: Option<Content>, top: Option<Content>) -> Content, - /// A base with an accent: `arrow(x)`. - pub math_accent: fn(base: Content, accent: char) -> Content, - /// A fraction in a formula: `x/2`. - pub math_frac: fn(num: Content, denom: Content) -> Content, -} - -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_id.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.link.hash(state); - self.ref_.hash(state); - self.heading.hash(state); - self.list_item.hash(state); - self.enum_item.hash(state); - self.term_item.hash(state); - self.formula.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); - } -} - -/// Global storage for lang items. -#[doc(hidden)] -pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new(); - -/// Set the lang items. This is a hack :( -/// -/// Passing the lang items everywhere they are needed (especially the text node -/// 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. -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::model::LANG_ITEMS.get().unwrap().$name - }; -} diff --git a/src/model/methods.rs b/src/model/methods.rs deleted file mode 100644 index dcb1ca31..00000000 --- a/src/model/methods.rs +++ /dev/null @@ -1,276 +0,0 @@ -//! Methods on values. - -use ecow::EcoString; - -use super::{Args, Str, Value, Vm}; -use crate::diag::{At, SourceResult}; -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" => Value::Color(color.lighten(args.expect("amount")?)), - "darken" => Value::Color(color.darken(args.expect("amount")?)), - "negate" => Value::Color(color.negate()), - _ => return missing(), - }, - - Value::Str(string) => match method { - "len" => Value::Int(string.len() as i64), - "first" => Value::Str(string.first().at(span)?), - "last" => Value::Str(string.last().at(span)?), - "at" => Value::Str(string.at(args.expect("index")?).at(span)?), - "slice" => { - let start = args.expect("start")?; - let mut end = args.eat()?; - if end.is_none() { - end = args.named("count")?.map(|c: i64| start + c); - } - Value::Str(string.slice(start, end).at(span)?) - } - "clusters" => Value::Array(string.clusters()), - "codepoints" => Value::Array(string.codepoints()), - "contains" => Value::Bool(string.contains(args.expect("pattern")?)), - "starts-with" => Value::Bool(string.starts_with(args.expect("pattern")?)), - "ends-with" => Value::Bool(string.ends_with(args.expect("pattern")?)), - "find" => { - string.find(args.expect("pattern")?).map_or(Value::None, Value::Str) - } - "position" => string - .position(args.expect("pattern")?) - .map_or(Value::None, Value::Int), - "match" => string - .match_(args.expect("pattern")?) - .map_or(Value::None, Value::Dict), - "matches" => Value::Array(string.matches(args.expect("pattern")?)), - "replace" => { - let pattern = args.expect("pattern")?; - let with = args.expect("replacement string")?; - let count = args.named("count")?; - Value::Str(string.replace(pattern, with, count)) - } - "trim" => { - let pattern = args.eat()?; - let at = args.named("at")?; - let repeat = args.named("repeat")?.unwrap_or(true); - Value::Str(string.trim(pattern, at, repeat)) - } - "split" => Value::Array(string.split(args.eat()?)), - _ => return missing(), - }, - - Value::Array(array) => match method { - "len" => Value::Int(array.len()), - "first" => array.first().at(span)?.clone(), - "last" => array.last().at(span)?.clone(), - "at" => array.at(args.expect("index")?).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); - } - Value::Array(array.slice(start, end).at(span)?) - } - "contains" => Value::Bool(array.contains(&args.expect("value")?)), - "find" => array.find(vm, args.expect("function")?)?.unwrap_or(Value::None), - "position" => array - .position(vm, args.expect("function")?)? - .map_or(Value::None, Value::Int), - "filter" => Value::Array(array.filter(vm, args.expect("function")?)?), - "map" => Value::Array(array.map(vm, args.expect("function")?)?), - "fold" => { - array.fold(vm, args.expect("initial value")?, args.expect("function")?)? - } - "any" => Value::Bool(array.any(vm, args.expect("function")?)?), - "all" => Value::Bool(array.all(vm, args.expect("function")?)?), - "flatten" => Value::Array(array.flatten()), - "rev" => Value::Array(array.rev()), - "join" => { - let sep = args.eat()?; - let last = args.named("last")?; - array.join(sep, last).at(span)? - } - "sorted" => Value::Array(array.sorted().at(span)?), - _ => return missing(), - }, - - Value::Dict(dict) => match method { - "len" => Value::Int(dict.len()), - "at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?, - "keys" => Value::Array(dict.keys()), - "values" => Value::Array(dict.values()), - "pairs" => Value::Array(dict.pairs()), - _ => return missing(), - }, - - Value::Func(func) => match method { - "with" => Value::Func(func.with(args.take())), - "where" => Value::dynamic(func.where_(&mut args).at(span)?), - _ => return missing(), - }, - - Value::Args(args) => match method { - "pos" => Value::Array(args.to_pos()), - "named" => Value::Dict(args.to_named()), - _ => return missing(), - }, - - _ => 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), - ], - "array" => &[ - ("all", true), - ("any", true), - ("at", true), - ("contains", true), - ("filter", true), - ("find", true), - ("first", false), - ("flatten", false), - ("fold", true), - ("insert", true), - ("join", true), - ("last", false), - ("len", false), - ("map", true), - ("pop", false), - ("position", true), - ("push", true), - ("remove", true), - ("rev", false), - ("slice", true), - ("sorted", false), - ], - "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)], - _ => &[], - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index 32b0a003..692d18d5 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,49 +1,16 @@ -//! Content and computation model. +//! The document model. #[macro_use] -mod library; -#[macro_use] -mod cast; -#[macro_use] -mod array; -#[macro_use] -mod dict; -#[macro_use] -mod str; -#[macro_use] -mod value; -#[macro_use] mod styles; -mod args; mod content; -mod eval; -mod func; -mod methods; -mod module; -mod ops; mod realize; -mod scope; -mod symbol; mod typeset; -#[doc(hidden)] -pub use once_cell; -pub use typst_macros::{capability, capable, castable, func, node}; - -pub use self::args::*; -pub use self::array::*; -pub use self::cast::*; pub use self::content::*; -pub use self::dict::*; -pub use self::eval::*; -pub use self::func::*; -pub use self::library::*; -pub use self::methods::*; -pub use self::module::*; pub use self::realize::*; -pub use self::scope::*; -pub use self::str::*; pub use self::styles::*; -pub use self::symbol::*; pub use self::typeset::*; -pub use self::value::*; + +#[doc(hidden)] +pub use once_cell; +pub use typst_macros::{capability, capable, node}; diff --git a/src/model/module.rs b/src/model/module.rs deleted file mode 100644 index e911d859..00000000 --- a/src/model/module.rs +++ /dev/null @@ -1,87 +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. -#[derive(Clone, Hash)] -pub struct Module(Arc<Repr>); - -/// The internal representation. -#[derive(Clone, Hash)] -struct Repr { - /// The module's name. - name: EcoString, - /// 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(Arc::new(Repr { - name: name.into(), - scope: Scope::new(), - content: Content::empty(), - })) - } - - /// Update the module's scope. - pub fn with_scope(mut self, scope: Scope) -> Self { - Arc::make_mut(&mut self.0).scope = scope; - self - } - - /// Update the module's content. - pub fn with_content(mut self, content: Content) -> Self { - Arc::make_mut(&mut self.0).content = content; - self - } - - /// Get the module's name. - pub fn name(&self) -> &EcoString { - &self.0.name - } - - /// Access the module's scope. - pub fn scope(&self) -> &Scope { - &self.0.scope - } - - /// Access the module's scope, mutably. - pub fn scope_mut(&mut self) -> &mut Scope { - &mut Arc::make_mut(&mut self.0).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.0) { - 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 { - Arc::ptr_eq(&self.0, &other.0) - } -} diff --git a/src/model/ops.rs b/src/model/ops.rs deleted file mode 100644 index 52b9b06a..00000000 --- a/src/model/ops.rs +++ /dev/null @@ -1,414 +0,0 @@ -//! Operations on values. - -use std::cmp::Ordering; - -use ecow::eco_format; - -use super::{format_str, Regex, Value}; -use crate::diag::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), - 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 + b), - (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), - }) - } - - (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 - b), - (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 * b), - (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), - (Int(a), Length(b)) => Length(b * a as f64), - (Float(a), Length(b)) => Length(b * a), - - (Angle(a), Int(b)) => Angle(a * b as f64), - (Angle(a), Float(b)) => Angle(a * b), - (Int(a), Angle(b)) => Angle(a as f64 * b), - (Float(a), Angle(b)) => Angle(a * b), - - (Ratio(a), Int(b)) => Ratio(a * b as f64), - (Ratio(a), Float(b)) => Ratio(a * b), - (Float(a), Ratio(b)) => Ratio(a * b), - (Int(a), Ratio(b)) => Ratio(a as f64 * b), - - (Relative(a), Int(b)) => Relative(a * b as f64), - (Relative(a), Float(b)) => Relative(a * b), - (Int(a), Relative(b)) => Relative(a as f64 * b), - (Float(a), Relative(b)) => Relative(a * b), - - (Float(a), Fraction(b)) => Fraction(a * b), - (Fraction(a), Int(b)) => Fraction(a * b as f64), - (Fraction(a), Float(b)) => Fraction(a * b), - (Int(a), Fraction(b)) => Fraction(a as f64 * 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), Int(b)) => Content(a.repeat(b)?), - (Int(a), Content(b)) => Content(b.repeat(a)?), - - (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) { - Err("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> { - if let Some(ordering) = compare(&lhs, &rhs) { - Ok(Bool(matches!(ordering, $($pat)*))) - } else { - mismatch!(concat!("cannot apply '", $op, "' to {} and {}"), lhs, rhs); - } - } - }; -} - -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, - (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) -> Option<Ordering> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => a.partial_cmp(b), - (Int(a), Int(b)) => a.partial_cmp(b), - (Float(a), Float(b)) => a.partial_cmp(b), - (Length(a), Length(b)) => a.partial_cmp(b), - (Angle(a), Angle(b)) => a.partial_cmp(b), - (Ratio(a), Ratio(b)) => a.partial_cmp(b), - (Relative(a), Relative(b)) => a.partial_cmp(b), - (Fraction(a), Fraction(b)) => a.partial_cmp(b), - (Str(a), Str(b)) => a.partial_cmp(b), - - // Some technically different things should be comparable. - (&Int(a), &Float(b)) => (a as f64).partial_cmp(&b), - (&Float(a), &Int(b)) => a.partial_cmp(&(b as f64)), - (&Length(a), &Relative(b)) if b.rel.is_zero() => a.partial_cmp(&b.abs), - (&Ratio(a), &Relative(b)) if b.abs.is_zero() => a.partial_cmp(&b.rel), - (&Relative(a), &Length(b)) if a.rel.is_zero() => a.abs.partial_cmp(&b), - (&Relative(a), &Ratio(b)) if a.abs.is_zero() => a.rel.partial_cmp(&b), - - _ => Option::None, - } -} - -/// 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/model/scope.rs b/src/model/scope.rs deleted file mode 100644 index f6bd2164..00000000 --- a/src/model/scope.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; - -use ecow::EcoString; - -use super::{Func, FuncType, Library, Value}; -use crate::diag::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> { - Ok(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("unknown variable")?) - } - - /// Try to access a variable immutably from within a math formula. - pub fn get_in_math(&self, var: &str) -> StrResult<&Value> { - Ok(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("unknown variable")?) - } - - /// 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(_) => "cannot mutate a constant", - _ => "unknown variable", - } - })? - } -} - -/// 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 Into<Value>) { - 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(), Kind::Normal)); - } - - /// Define a function through a native rust function. - pub fn def_func<T: FuncType>(&mut self, name: &'static str) { - self.define(name, Func::from_type::<T>(name)); - } - - /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into<EcoString>, - value: impl Into<Value>, - ) { - self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured)); - } - - /// 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 => Err("cannot mutate a captured variable")?, - } - } -} diff --git a/src/model/str.rs b/src/model/str.rs deleted file mode 100644 index 5fcc1d05..00000000 --- a/src/model/str.rs +++ /dev/null @@ -1,514 +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}; - -use ecow::EcoString; -use unicode_segmentation::UnicodeSegmentation; - -use super::{castable, dict, Array, Dict, Value}; -use crate::diag::StrResult; -use crate::geom::GenAlign; - -/// Create a new [`Str`] from a format string. -#[macro_export] -#[doc(hidden)] -macro_rules! __format_str { - ($($tts:tt)*) => {{ - $crate::model::Str::from($crate::model::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()) - } - - /// The length of the string in bytes. - pub fn len(&self) -> i64 { - self.0.len() as i64 - } - - /// 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(&self, index: i64) -> StrResult<Self> { - let len = self.len(); - let grapheme = self.0[self.locate(index)?..] - .graphemes(true) - .next() - .ok_or_else(|| out_of_bounds(index, len))?; - Ok(grapheme.into()) - } - - /// Extract a contigous substring. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let start = self.locate(start)?; - let end = self.locate(end.unwrap_or(self.len()))?.max(start); - 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(|| 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` occurances of the given pattern with a - /// replacement string (beginning from the start). - pub fn replace(&self, pattern: StrPattern, with: Self, count: Option<usize>) -> Self { - match pattern { - StrPattern::Str(pat) => match count { - Some(n) => self.0.replacen(pat.as_str(), &with, n).into(), - None => self.0.replace(pat.as_str(), &with).into(), - }, - StrPattern::Regex(re) => match count { - Some(n) => re.replacen(self, n, with.as_str()).into(), - None => re.replace(self, with.as_str()).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. - fn locate(&self, index: i64) -> StrResult<usize> { - let wrapped = - if index >= 0 { Some(index) } else { self.len().checked_add(index) }; - - let resolved = wrapped - .and_then(|v| usize::try_from(v).ok()) - .filter(|&v| v <= self.0.len()) - .ok_or_else(|| out_of_bounds(index, self.len()))?; - - if !self.0.is_char_boundary(resolved) { - return Err(not_a_char_boundary(index)); - } - - Ok(resolved) - } -} - -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: i64) -> EcoString { - eco_format!("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" => Value::Int(start as i64), - "end" => Value::Int((start + text.len()) as i64), - "text" => Value::Str(text.into()), - "captures" => Value::Array(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" => Value::Int(m.start() as i64), - "end" => Value::Int(m.end() as i64), - "text" => Value::Str(m.as_str().into()), - "captures" => Value::Array( - cap.iter() - .skip(1) - .map(|opt| opt.map_or(Value::None, |m| m.as_str().into())) - .collect(), - ), - } -} - -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() - } -} - -/// 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); - } -} - -/// 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), -} - -castable! { - 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, -} - -castable! { - StrSide, - align: GenAlign => match align { - GenAlign::Start => Self::Start, - GenAlign::End => Self::End, - _ => Err("expected either `start` or `end`")?, - }, -} diff --git a/src/model/styles.rs b/src/model/styles.rs index 27c40309..18507491 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,8 +7,9 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; -use super::{Args, Content, Dict, Func, Label, NodeId, Regex, Value}; +use super::{Content, Label, NodeId}; use crate::diag::{SourceResult, Trace, Tracepoint}; +use crate::eval::{Args, Dict, Func, Regex, Value}; use crate::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, Smart, diff --git a/src/model/symbol.rs b/src/model/symbol.rs deleted file mode 100644 index 73c41067..00000000 --- a/src/model/symbol.rs +++ /dev/null @@ -1,189 +0,0 @@ -use std::cmp::Reverse; -use std::collections::BTreeSet; -use std::fmt::{self, Debug, Display, Formatter, Write}; - -use ecow::{EcoString, EcoVec}; - -use crate::diag::StrResult; - -#[doc(inline)] -pub use typst_macros::symbols; - -/// A symbol. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Symbol { - repr: Repr, - modifiers: EcoString, -} - -/// A collection of symbols. -#[derive(Clone, Eq, PartialEq, Hash)] -enum Repr { - Single(char), - Static(&'static [(&'static str, char)]), - Runtime(EcoVec<(EcoString, char)>), -} - -impl Symbol { - /// Create a new symbol from a single character. - pub const fn new(c: char) -> Self { - Self { repr: Repr::Single(c), modifiers: EcoString::new() } - } - - /// 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: Repr::Static(list), - modifiers: EcoString::new(), - } - } - - /// Create a symbol with a runtime variant list. - #[track_caller] - pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self { - debug_assert!(!list.is_empty()); - Self { - repr: Repr::Runtime(list), - modifiers: EcoString::new(), - } - } - - /// Get the symbol's text. - pub fn get(&self) -> char { - match self.repr { - Repr::Single(c) => c, - _ => find(self.variants(), &self.modifiers).unwrap(), - } - } - - /// Apply a modifier to the symbol. - pub fn modified(mut self, modifier: &str) -> StrResult<Self> { - if !self.modifiers.is_empty() { - self.modifiers.push('.'); - } - self.modifiers.push_str(modifier); - if find(self.variants(), &self.modifiers).is_none() { - Err("unknown modifier")? - } - Ok(self) - } - - /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { - match &self.repr { - Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Static(list) => Variants::Static(list.iter()), - Repr::Runtime(list) => Variants::Runtime(list.iter()), - } - } - - /// Possible modifiers. - pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { - let mut set = BTreeSet::new(); - for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(&self.modifiers, modifier) { - set.insert(modifier); - } - } - set.into_iter() - } -} - -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()) - } -} - -/// 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) -} - -/// 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{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - _ => return None, - }) -} diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 7af8094c..f8b5e012 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -5,9 +5,10 @@ use std::num::NonZeroUsize; use comemo::{Track, Tracked, TrackedMut}; -use super::{Content, Selector, StyleChain, Value}; +use super::{Content, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; +use crate::eval::Value; use crate::geom::Transform; use crate::util::hash128; use crate::World; @@ -46,7 +47,7 @@ pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Doc /// A virtual typesetter. /// /// Holds the state needed to [typeset] content. This is the equivalent to the -/// [Vm](super::Vm) for typesetting. +/// [Vm](crate::eval::Vm) for typesetting. pub struct Vt<'a> { /// The compilation environment. #[doc(hidden)] diff --git a/src/model/value.rs b/src/model/value.rs deleted file mode 100644 index f6ab95de..00000000 --- a/src/model/value.rs +++ /dev/null @@ -1,497 +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, EcoString}; -use siphasher::sip128::{Hasher128, SipHasher}; - -use super::{ - format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module, - Str, Symbol, -}; -use crate::diag::StrResult; -use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; -use crate::syntax::{ast, Span}; - -/// A computational value. -#[derive(Clone)] -pub enum Value { - /// The value that indicates the absence of a meaningful value. - 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), - /// A label: `<intro>`. - Label(Label), - /// A content value: `[*Hi* there]`. - Content(Content), - /// An array of values: `(1, "hi", 12cm)`. - Array(Array), - /// A dictionary value: `(color: #f79143, pattern: dashed)`. - 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(), - ast::Unit::Angle(unit) => Angle::with_unit(v, unit).into(), - ast::Unit::Em => Em::new(v).into(), - ast::Unit::Fr => Fr::new(v).into(), - ast::Unit::Percent => Ratio::new(v / 100.0).into(), - } - } - - /// 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::Label(_) => Label::TYPE_NAME, - Self::Content(_) => Content::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: Cast>(self) -> StrResult<T> { - T::cast(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).cloned(), - Self::Content(content) => content - .field(&field) - .ok_or_else(|| eco_format!("unknown field `{field}`")), - Self::Module(module) => module.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 Default for Value { - fn default() -> Self { - Value::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::Label(v) => Debug::fmt(v, f), - Self::Content(_) => f.pad("[...]"), - 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) - } -} - -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::Label(v) => v.hash(state), - Self::Content(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), - } - } -} - -impl From<i32> for Value { - fn from(v: i32) -> Self { - Self::Int(v as i64) - } -} - -impl From<usize> for Value { - fn from(v: usize) -> Self { - Self::Int(v as i64) - } -} - -impl From<Abs> for Value { - fn from(v: Abs) -> Self { - Self::Length(v.into()) - } -} - -impl From<Em> for Value { - fn from(v: Em) -> Self { - Self::Length(v.into()) - } -} - -impl From<RgbaColor> for Value { - fn from(v: RgbaColor) -> Self { - Self::Color(v.into()) - } -} - -impl From<&str> for Value { - fn from(v: &str) -> Self { - Self::Str(v.into()) - } -} - -impl From<EcoString> for Value { - fn from(v: EcoString) -> Self { - Self::Str(v.into()) - } -} - -impl From<String> for Value { - fn from(v: String) -> Self { - Self::Str(v.into()) - } -} - -impl From<Dynamic> for Value { - fn from(v: Dynamic) -> Self { - Self::Dyn(v) - } -} - -/// A dynamic value. -#[derive(Clone, Hash)] -pub struct Dynamic(Arc<dyn Bounds>); - -impl Dynamic { - /// Create a new instance from any value that satisifies 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) - } -} - -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 - } - - fn hash128(&self) -> u128 { - // Also hash the TypeId since values with different types but - // equal data should be different. - let mut state = SipHasher::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 { - ( - $type:ty: $name:literal, $variant:ident - $(, $other:ident$(($binding:ident))? => $out:expr)* - ) => { - impl Type for $type { - const TYPE_NAME: &'static str = $name; - } - - impl Cast for $type { - fn is(value: &Value) -> bool { - matches!(value, Value::$variant(_) - $(| primitive!(@$other $(($binding))?))*) - } - - fn cast(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(), - )), - } - } - - fn describe() -> CastInfo { - CastInfo::Type(Self::TYPE_NAME) - } - } - - impl From<$type> for Value { - fn from(v: $type) -> Self { - Value::$variant(v) - } - } - }; - - (@$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! { Label: "label", Label } -primitive! { Content: "content", - Content, - None => Content::empty(), - Symbol(v) => item!(text)(v.get().into()), - Str(v) => item!(text)(v.into()) -} -primitive! { Array: "array", Array } -primitive! { Dict: "dictionary", Dict } -primitive! { Func: "function", Func } -primitive! { Module: "module", Module } -primitive! { Args: "arguments", Args } - -#[cfg(test)] -mod tests { - use super::*; - use crate::model::{array, dict}; - - #[track_caller] - fn test(value: impl Into<Value>, exp: &str) { - assert_eq!(format!("{:?}", value.into()), exp); - } - - #[test] - fn test_value_debug() { - // Primitives. - test(Value::None, "none"); - test(false, "false"); - test(12i64, "12"); - test(3.14, "3.14"); - 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], "(one: 1, two: false)"); - } -} |
