summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
Diffstat (limited to 'src/model')
-rw-r--r--src/model/args.rs193
-rw-r--r--src/model/array.rs406
-rw-r--r--src/model/cast.rs514
-rw-r--r--src/model/content.rs8
-rw-r--r--src/model/dict.rs209
-rw-r--r--src/model/eval.rs1523
-rw-r--r--src/model/func.rs576
-rw-r--r--src/model/library.rs144
-rw-r--r--src/model/methods.rs276
-rw-r--r--src/model/mod.rs43
-rw-r--r--src/model/module.rs87
-rw-r--r--src/model/ops.rs414
-rw-r--r--src/model/scope.rs174
-rw-r--r--src/model/str.rs514
-rw-r--r--src/model/styles.rs3
-rw-r--r--src/model/symbol.rs189
-rw-r--r--src/model/typeset.rs5
-rw-r--r--src/model/value.rs497
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)");
- }
-}