From 6ab7760822ccd24b4ef126d4737d41f1be15fe19 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 1 Mar 2023 16:30:58 +0100 Subject: Split up `model` module --- src/doc.rs | 5 +- src/eval/args.rs | 193 +++++++ src/eval/array.rs | 406 +++++++++++++ src/eval/cast.rs | 513 +++++++++++++++++ src/eval/dict.rs | 209 +++++++ src/eval/func.rs | 574 +++++++++++++++++++ src/eval/library.rs | 145 +++++ src/eval/methods.rs | 276 +++++++++ src/eval/mod.rs | 1554 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/eval/module.rs | 87 +++ src/eval/ops.rs | 414 ++++++++++++++ src/eval/scope.rs | 174 ++++++ src/eval/str.rs | 514 +++++++++++++++++ src/eval/symbol.rs | 189 ++++++ src/eval/value.rs | 497 ++++++++++++++++ src/ide/analyze.rs | 2 +- src/ide/complete.rs | 2 +- src/ide/tooltip.rs | 2 +- src/lib.rs | 12 +- src/model/args.rs | 193 ------- src/model/array.rs | 406 ------------- src/model/cast.rs | 514 ----------------- src/model/content.rs | 8 +- src/model/dict.rs | 209 ------- src/model/eval.rs | 1523 ------------------------------------------------- src/model/func.rs | 576 ------------------- src/model/library.rs | 144 ----- src/model/methods.rs | 276 --------- src/model/mod.rs | 43 +- src/model/module.rs | 87 --- src/model/ops.rs | 414 -------------- src/model/scope.rs | 174 ------ src/model/str.rs | 514 ----------------- src/model/styles.rs | 3 +- src/model/symbol.rs | 189 ------ src/model/typeset.rs | 5 +- src/model/value.rs | 497 ---------------- 37 files changed, 5769 insertions(+), 5774 deletions(-) create mode 100644 src/eval/args.rs create mode 100644 src/eval/array.rs create mode 100644 src/eval/cast.rs create mode 100644 src/eval/dict.rs create mode 100644 src/eval/func.rs create mode 100644 src/eval/library.rs create mode 100644 src/eval/methods.rs create mode 100644 src/eval/mod.rs create mode 100644 src/eval/module.rs create mode 100644 src/eval/ops.rs create mode 100644 src/eval/scope.rs create mode 100644 src/eval/str.rs create mode 100644 src/eval/symbol.rs create mode 100644 src/eval/value.rs delete mode 100644 src/model/args.rs delete mode 100644 src/model/array.rs delete mode 100644 src/model/cast.rs delete mode 100644 src/model/dict.rs delete mode 100644 src/model/eval.rs delete mode 100644 src/model/func.rs delete mode 100644 src/model/library.rs delete mode 100644 src/model/methods.rs delete mode 100644 src/model/module.rs delete mode 100644 src/model/ops.rs delete mode 100644 src/model/scope.rs delete mode 100644 src/model/str.rs delete mode 100644 src/model/symbol.rs delete mode 100644 src/model/value.rs (limited to 'src') diff --git a/src/doc.rs b/src/doc.rs index 55e2f467..cba3ca99 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -7,15 +7,14 @@ use std::sync::Arc; use ecow::EcoString; +use crate::eval::{dict, Dict, Value}; use crate::font::Font; use crate::geom::{ self, rounded_rect, Abs, Align, Axes, Color, Corners, Dir, Em, Geometry, Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; -use crate::model::{ - capable, dict, node, Content, Dict, Fold, StableId, StyleChain, Value, -}; +use crate::model::{capable, node, Content, Fold, StableId, StyleChain}; /// A finished document with metadata and page frames. #[derive(Debug, Default, Clone, Hash)] diff --git a/src/eval/args.rs b/src/eval/args.rs new file mode 100644 index 00000000..159e9a77 --- /dev/null +++ b/src/eval/args.rs @@ -0,0 +1,193 @@ +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, +} + +/// 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, + /// The value of the argument. + pub value: Spanned, +} + +impl Args { + /// Create positional arguments from a span and values. + pub fn new(span: Span, values: impl IntoIterator) -> 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(&mut self) -> SourceResult> + where + T: Cast>, + { + 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(&mut self, what: &str) -> SourceResult + where + T: Cast>, + { + 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(&mut self) -> SourceResult> + where + T: Cast>, + { + 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(&mut self) -> SourceResult> + where + T: Cast>, + { + 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(&mut self, name: &str) -> SourceResult> + where + T: Cast>, + { + // 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(&mut self, name: &str) -> SourceResult> + where + T: Cast>, + { + 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/eval/array.rs b/src/eval/array.rs new file mode 100644 index 00000000..53bae06f --- /dev/null +++ b/src/eval/array.rs @@ -0,0 +1,406 @@ +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::eval::Array::from_vec($crate::eval::eco_vec![$value.into(); $count]) + }; + + ($($value:expr),* $(,)?) => { + $crate::eval::Array::from_vec($crate::eval::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); + +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) -> 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 { + 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 { + 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) -> StrResult { + 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> { + 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::().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> { + 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::().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 { + 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::().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 { + 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 { + 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 { + 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::().at(func.span())? { + return Ok(true); + } + } + + Ok(false) + } + + /// Whether all elements match. + pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult { + 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::().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, mut last: Option) -> StrResult { + 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 { + 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 { + 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 { + self.0.iter() + } + + /// Resolve an index. + fn locate(&self, index: i64) -> Option { + 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 for Array { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl FromIterator for Array { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl IntoIterator for Array { + type Item = Value; + type IntoIter = ecow::vec::IntoIter; + + 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/eval/cast.rs b/src/eval/cast.rs new file mode 100644 index 00000000..77521f7f --- /dev/null +++ b/src/eval/cast.rs @@ -0,0 +1,513 @@ +use std::num::NonZeroUsize; +use std::ops::Add; +use std::str::FromStr; + +use ecow::EcoString; + +use super::{castable, Array, Dict, Func, Regex, Str, 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::model::{Content, Label, Selector, Transform}; +use crate::syntax::Spanned; + +/// Cast from a value to a specific type. +pub trait Cast: 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; + + /// Describe the acceptable values. + fn describe() -> CastInfo; + + /// Produce an error for an inacceptable value. + fn error(value: Value) -> StrResult { + 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), +} + +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, + 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 { + Ok(value) + } + + fn describe() -> CastInfo { + CastInfo::Any + } +} + +impl Cast> for T { + fn is(value: &Spanned) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + T::cast(value.v) + } + + fn describe() -> CastInfo { + T::describe() + } +} + +impl Cast> for Spanned { + fn is(value: &Spanned) -> bool { + T::is(&value.v) + } + + fn cast(value: Spanned) -> StrResult { + 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: "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>, + align: GenAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(align)); + aligns + }, + aligns: Axes => aligns.map(Some), +} + +castable! { + Axes>, + 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 { + match value { + Value::None => Ok(Self), + _ => ::error(value), + } + } + + fn describe() -> CastInfo { + CastInfo::Type("none") + } +} + +impl Cast for Option { + fn is(value: &Value) -> bool { + matches!(value, Value::None) || T::is(value) + } + + fn cast(value: Value) -> StrResult { + match value { + Value::None => Ok(None), + v if T::is(&v) => Ok(Some(T::cast(v)?)), + _ => ::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 { + match value { + Value::Auto => Ok(Self), + _ => ::error(value), + } + } + + fn describe() -> CastInfo { + CastInfo::Type("auto") + } +} + +impl Cast for Smart { + fn is(value: &Value) -> bool { + matches!(value, Value::Auto) || T::is(value) + } + + fn cast(value: Value) -> StrResult { + match value { + Value::Auto => Ok(Self::Auto), + v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), + _ => ::error(value), + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("auto") + } +} + +impl Cast for Sides> +where + T: Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult { + 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 { + ::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} + +impl Cast for Corners> +where + T: Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult { + 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 { + ::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} diff --git a/src/eval/dict.rs b/src/eval/dict.rs new file mode 100644 index 00000000..6c1934c9 --- /dev/null +++ b/src/eval/dict.rs @@ -0,0 +1,209 @@ +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::eval::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>); + +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) -> 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 { + 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 { + 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 { + 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>(&mut self, iter: T) { + Arc::make_mut(&mut self.0).extend(iter); + } +} + +impl FromIterator<(Str, Value)> for Dict { + fn from_iter>(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; + + 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/eval/func.rs b/src/eval/func.rs new file mode 100644 index 00000000..e5280932 --- /dev/null +++ b/src/eval/func.rs @@ -0,0 +1,574 @@ +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, Route, Scope, Scopes, Tracer, Value, Vm}; +use crate::diag::{bail, SourceResult, StrResult}; +use crate::model::{Node, NodeId, Selector, StyleMap}; +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>, 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(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, + 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(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::()), + 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 { + 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 { + 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, + args: Args, + ) -> SourceResult { + 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 { + 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 { + 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) -> StrResult { + 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, ""), + None => f.write_str(""), + } + } +} + +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, + /// The set rule. + set: Option SourceResult>, + /// The id of the node to customize with this function's show rule. + node: Option, + /// Documentation of the function. + info: FuncInfo, +} + +impl Hash for Native { + fn hash(&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, + /// 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, + /// 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)>, + /// The name of an argument sink where remaining arguments are placed. + pub sink: Option, + /// 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, + route: Tracked, + tracer: TrackedMut, + depth: usize, + mut args: Args, + ) -> SourceResult { + 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::(param)?.unwrap_or_else(|| default.clone()) + } + None => args.expect::(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 { + 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/eval/library.rs b/src/eval/library.rs new file mode 100644 index 00000000..adfcc6e7 --- /dev/null +++ b/src/eval/library.rs @@ -0,0 +1,145 @@ +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::Module; +use crate::diag::SourceResult; +use crate::doc::Document; +use crate::geom::{Abs, Dir}; +use crate::model::{Content, NodeId, StyleChain, StyleMap, Vt}; +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, + /// 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, 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, 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, top: Option) -> 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(&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 = 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::eval::LANG_ITEMS.get().unwrap().$name + }; +} diff --git a/src/eval/methods.rs b/src/eval/methods.rs new file mode 100644 index 00000000..dcb1ca31 --- /dev/null +++ b/src/eval/methods.rs @@ -0,0 +1,276 @@ +//! 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 { + 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::("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 { + 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::("key")?, args.expect("value")?), + "remove" => { + output = dict.remove(&args.expect::("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::("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/eval/mod.rs b/src/eval/mod.rs new file mode 100644 index 00000000..2cf6f4d1 --- /dev/null +++ b/src/eval/mod.rs @@ -0,0 +1,1554 @@ +//! Evaluation of markup into modules. + +#[macro_use] +mod library; +#[macro_use] +mod cast; +#[macro_use] +mod array; +#[macro_use] +mod dict; +#[macro_use] +mod str; +#[macro_use] +mod value; +mod args; +mod func; +mod methods; +mod module; +mod ops; +mod scope; +mod symbol; + +pub use typst_macros::{castable, func}; + +pub use self::args::*; +pub use self::array::*; +pub use self::cast::*; +pub use self::dict::*; +pub use self::func::*; +pub use self::library::*; +pub use self::methods::*; +pub use self::module::*; +pub use self::scope::*; +pub use self::str::*; +pub use self::symbol::*; +pub use self::value::*; + +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 crate::diag::{ + bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, +}; +use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform}; +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, + route: Tracked, + tracer: TrackedMut, + source: &Source, +) -> SourceResult { + // 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(); + 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::() { + 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, + text: &str, + span: Span, +) -> SourceResult { + 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::().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, + /// 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, +} + +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) { + 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 { + 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), +} + +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>, + id: Option, +} + +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, 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, + values: Vec, +} + +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) -> Self { + Self { span, values: vec![] } + } + + /// Get the traced values. + pub fn finish(self) -> Vec { + self.values + } +} + +#[comemo::track] +impl Tracer { + /// The traced span if it is part of the given source file. + fn span(&self, id: SourceId) -> Option { + 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; +} + +impl Eval for ast::Markup { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + eval_markup(vm, &mut self.exprs()) + } +} + +/// Evaluate a stream of markup. +fn eval_markup( + vm: &mut Vm, + exprs: &mut impl Iterator, +) -> SourceResult { + 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 { + 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 { + Ok((vm.items.text)(self.get().clone())) + } +} + +impl Eval for ast::Space { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.space)()) + } +} + +impl Eval for ast::Linebreak { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.linebreak)()) + } +} + +impl Eval for ast::Parbreak { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.parbreak)()) + } +} + +impl Eval for ast::Escape { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Symbol(Symbol::new(self.get()))) + } +} + +impl Eval for ast::Shorthand { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Symbol(Symbol::new(self.get()))) + } +} + +impl Eval for ast::SmartQuote { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.smart_quote)(self.double())) + } +} + +impl Eval for ast::Strong { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.strong)(self.body().eval(vm)?)) + } +} + +impl Eval for ast::Emph { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.emph)(self.body().eval(vm)?)) + } +} + +impl Eval for ast::Raw { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + Ok((vm.items.link)(self.get().clone())) + } +} + +impl Eval for ast::Label { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Label(Label(self.get().into()))) + } +} + +impl Eval for ast::Ref { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.ref_)(self.get().into())) + } +} + +impl Eval for ast::Heading { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + Ok((vm.items.list_item)(self.body().eval(vm)?)) + } +} + +impl Eval for ast::EnumItem { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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 { + 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 { + Ok(Content::sequence( + self.exprs() + .map(|expr| Ok(expr.eval(vm)?.display())) + .collect::>()?, + ) + .spanned(self.span())) + } +} + +impl Eval for ast::MathIdent { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + Ok((vm.items.math_align_point)()) + } +} + +impl Eval for ast::MathDelimited { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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 { + 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 { + Ok(vm.scopes.get(self).cloned().at(self.span())?) + } +} + +impl Eval for ast::None { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::None) + } +} + +impl Eval for ast::Auto { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Auto) + } +} + +impl Eval for ast::Bool { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Bool(self.get())) + } +} + +impl Eval for ast::Int { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Int(self.get())) + } +} + +impl Eval for ast::Float { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Float(self.get())) + } +} + +impl Eval for ast::Numeric { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::numeric(self.get())) + } +} + +impl Eval for ast::Str { + type Output = Value; + + fn eval(&self, _: &mut Vm) -> SourceResult { + Ok(Value::Str(self.get().into())) + } +} + +impl Eval for ast::CodeBlock { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + eval_code(vm, &mut self.exprs()) + } +} + +/// Evaluate a stream of expressions. +fn eval_code( + vm: &mut Vm, + exprs: &mut impl Iterator, +) -> SourceResult { + 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 { + 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.expr().eval(vm) + } +} + +impl Eval for ast::Array { + type Output = Array; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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 { + 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 { + 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, + ) -> SourceResult { + 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, + ) -> SourceResult { + 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 { + 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 { + 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::()?.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::().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 { + 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 { + // 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 { + 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 { + if let Some(condition) = self.condition() { + if !condition.eval(vm)?.cast::().at(condition.span())? { + return Ok(StyleMap::new()); + } + } + + let target = self.target(); + let target = target.eval(vm)?.cast::().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 { + let selector = self + .selector() + .map(|sel| sel.eval(vm)?.cast::().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::().at(span)?, + }; + + Ok(Recipe { span, selector, transform }) + } +} + +impl Eval for ast::Conditional { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + let condition = self.condition(); + if condition.eval(vm)?.cast::().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 { + 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::().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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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/eval/module.rs b/src/eval/module.rs new file mode 100644 index 00000000..e911d859 --- /dev/null +++ b/src/eval/module.rs @@ -0,0 +1,87 @@ +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); + +/// 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) -> 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, "", self.name()) + } +} + +impl PartialEq for Module { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs new file mode 100644 index 00000000..52b9b06a --- /dev/null +++ b/src/eval/ops.rs @@ -0,0 +1,414 @@ +//! 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 { + 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 { + 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 { + 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 { + 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::(), b.downcast::()) + { + 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 { + 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 { + 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 { + 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 { + 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, b: Rel) -> StrResult { + 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 { + 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 { + 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 { + 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 { + Ok(Bool(equal(&lhs, &rhs))) +} + +/// Compute whether two values are unequal. +pub fn neq(lhs: Value, rhs: Value) -> StrResult { + 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 { + 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 { + 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 { + 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 { + 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 { + match (lhs, rhs) { + (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), + (Dyn(a), Str(b)) => a.downcast::().map(|regex| regex.is_match(b)), + (Str(a), Dict(b)) => Some(b.contains(a)), + (a, Array(b)) => Some(b.contains(a)), + _ => Option::None, + } +} diff --git a/src/eval/scope.rs b/src/eval/scope.rs new file mode 100644 index 00000000..f6bd2164 --- /dev/null +++ b/src/eval/scope.rs @@ -0,0 +1,174 @@ +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, + /// 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, 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, value: impl Into) { + 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(&mut self, name: &'static str) { + self.define(name, Func::from_type::(name)); + } + + /// Define a captured, immutable binding. + pub fn define_captured( + &mut self, + var: impl Into, + value: impl Into, + ) { + 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> { + self.0.get_mut(var).map(Slot::write) + } + + /// Iterate over all definitions. + pub fn iter(&self) -> impl Iterator { + 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/eval/str.rs b/src/eval/str.rs new file mode 100644 index 00000000..63ea5dc8 --- /dev/null +++ b/src/eval/str.rs @@ -0,0 +1,514 @@ +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::eval::Str::from($crate::eval::eco_format!($($tts)*)) + }}; +} + +#[doc(inline)] +pub use crate::__format_str as format_str; +#[doc(hidden)] +pub use ecow::eco_format; + +/// An immutable reference counted string. +#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Str(EcoString); + +impl Str { + /// Create a new, empty string. + pub fn new() -> Self { + Self(EcoString::new()) + } + + /// 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.0 + .graphemes(true) + .next() + .map(Into::into) + .ok_or_else(string_is_empty) + } + + /// Extract the last grapheme cluster. + pub fn last(&self) -> StrResult { + 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 { + 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) -> StrResult { + 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 { + 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 { + 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 { + 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) -> 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, + at: Option, + 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) -> 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 { + 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 { + 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 for Str { + fn as_ref(&self) -> &str { + self + } +} + +impl Borrow for Str { + fn borrow(&self) -> &str { + self + } +} + +impl From 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 for Str { + fn from(s: EcoString) -> Self { + Self(s) + } +} + +impl From for Str { + fn from(s: String) -> Self { + Self(s.into()) + } +} + +impl From> for Str { + fn from(s: Cow) -> Self { + Self(s.into()) + } +} + +impl FromIterator for Str { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl From for EcoString { + fn from(str: Str) -> Self { + str.0 + } +} + +impl From 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 { + 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(&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/eval/symbol.rs b/src/eval/symbol.rs new file mode 100644 index 00000000..73c41067 --- /dev/null +++ b/src/eval/symbol.rs @@ -0,0 +1,189 @@ +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 { + 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 { + 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 + '_ { + 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), + 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 { + 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, + modifiers: &str, +) -> Option { + 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 { + 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 { + 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/eval/value.rs b/src/eval/value.rs new file mode 100644 index 00000000..5e06da76 --- /dev/null +++ b/src/eval/value.rs @@ -0,0 +1,497 @@ +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), + /// A fraction: `1fr`. + Fraction(Fr), + /// A color value: `#f79143ff`. + Color(Color), + /// A symbol: `arrow.l`. + Symbol(Symbol), + /// A string: `"string"`. + Str(Str), + /// A label: ``. + 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(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::::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(self) -> StrResult { + T::cast(self) + } + + /// Try to access a field on the value. + pub fn field(&self, field: &str) -> StrResult { + 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 { + ops::compare(self, other) + } +} + +impl Hash for Value { + fn hash(&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 for Value { + fn from(v: i32) -> Self { + Self::Int(v as i64) + } +} + +impl From for Value { + fn from(v: usize) -> Self { + Self::Int(v as i64) + } +} + +impl From for Value { + fn from(v: Abs) -> Self { + Self::Length(v.into()) + } +} + +impl From for Value { + fn from(v: Em) -> Self { + Self::Length(v.into()) + } +} + +impl From 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 for Value { + fn from(v: EcoString) -> Self { + Self::Str(v.into()) + } +} + +impl From for Value { + fn from(v: String) -> Self { + Self::Str(v.into()) + } +} + +impl From for Value { + fn from(v: Dynamic) -> Self { + Self::Dyn(v) + } +} + +/// A dynamic value. +#[derive(Clone, Hash)] +pub struct Dynamic(Arc); + +impl Dynamic { + /// Create a new instance from any value that satisifies the required bounds. + pub fn new(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(&self) -> bool { + (*self.0).as_any().is::() + } + + /// Try to downcast to a reference to a specific type. + pub fn downcast(&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 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::() 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(&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 { + 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: "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::eval::{array, dict}; + + #[track_caller] + fn test(value: impl Into, 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)"); + } +} diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index 357e98f8..3c46cca1 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use comemo::Track; -use crate::model::{eval, Module, Route, Tracer, Value}; +use crate::eval::{eval, Module, Route, Tracer, Value}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::util::PathExt; use crate::World; diff --git a/src/ide/complete.rs b/src/ide/complete.rs index fe1afe66..06ab53a1 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -4,7 +4,7 @@ use ecow::{eco_format, EcoString}; use if_chain::if_chain; use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; -use crate::model::{methods_on, CastInfo, Scope, Value}; +use crate::eval::{methods_on, CastInfo, Scope, Value}; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::World; diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs index 0796c09e..ac0cb60b 100644 --- a/src/ide/tooltip.rs +++ b/src/ide/tooltip.rs @@ -2,8 +2,8 @@ use if_chain::if_chain; use unicode_segmentation::UnicodeSegmentation; use super::{analyze_expr, plain_docs_sentence, summarize_font_family}; +use crate::eval::{CastInfo, Tracer, Value}; use crate::geom::{round_2, Length, Numeric}; -use crate::model::{CastInfo, Tracer, Value}; use crate::syntax::ast; use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::World; diff --git a/src/lib.rs b/src/lib.rs index 1c399864..d73055d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,8 +23,8 @@ //! [parsed]: syntax::parse //! [syntax tree]: syntax::SyntaxNode //! [AST]: syntax::ast -//! [evaluate]: model::eval -//! [module]: model::Module +//! [evaluate]: eval::eval +//! [module]: eval::Module //! [content]: model::Content //! [typeset]: model::typeset //! [document]: doc::Document @@ -43,23 +43,23 @@ pub mod geom; #[macro_use] pub mod diag; #[macro_use] -pub mod model; +pub mod eval; pub mod doc; pub mod export; pub mod font; pub mod ide; pub mod image; +pub mod model; pub mod syntax; use std::path::Path; use comemo::{Prehashed, Track}; -use model::Tracer; use crate::diag::{FileResult, SourceResult}; use crate::doc::Document; +use crate::eval::{Library, Route, Tracer}; use crate::font::{Font, FontBook}; -use crate::model::{Library, Route}; use crate::syntax::{Source, SourceId}; use crate::util::Buffer; @@ -68,7 +68,7 @@ pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult, -} - -/// 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, - /// The value of the argument. - pub value: Spanned, -} - -impl Args { - /// Create positional arguments from a span and values. - pub fn new(span: Span, values: impl IntoIterator) -> 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(&mut self) -> SourceResult> - where - T: Cast>, - { - 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(&mut self, what: &str) -> SourceResult - where - T: Cast>, - { - 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(&mut self) -> SourceResult> - where - T: Cast>, - { - 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(&mut self) -> SourceResult> - where - T: Cast>, - { - 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(&mut self, name: &str) -> SourceResult> - where - T: Cast>, - { - // 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(&mut self, name: &str) -> SourceResult> - where - T: Cast>, - { - 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); - -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) -> 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 { - 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 { - 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) -> StrResult { - 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> { - 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::().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> { - 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::().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 { - 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::().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 { - 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 { - 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 { - 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::().at(func.span())? { - return Ok(true); - } - } - - Ok(false) - } - - /// Whether all elements match. - pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult { - 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::().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, mut last: Option) -> StrResult { - 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 { - 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 { - 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 { - self.0.iter() - } - - /// Resolve an index. - fn locate(&self, index: i64) -> Option { - 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 for Array { - fn extend>(&mut self, iter: T) { - self.0.extend(iter); - } -} - -impl FromIterator for Array { - fn from_iter>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl IntoIterator for Array { - type Item = Value; - type IntoIter = ecow::vec::IntoIter; - - 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: 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; - - /// Describe the acceptable values. - fn describe() -> CastInfo; - - /// Produce an error for an inacceptable value. - fn error(value: Value) -> StrResult { - 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), -} - -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, - 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 { - Ok(value) - } - - fn describe() -> CastInfo { - CastInfo::Any - } -} - -impl Cast> for T { - fn is(value: &Spanned) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - T::cast(value.v) - } - - fn describe() -> CastInfo { - T::describe() - } -} - -impl Cast> for Spanned { - fn is(value: &Spanned) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned) -> StrResult { - 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: "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>, - align: GenAlign => { - let mut aligns = Axes::default(); - aligns.set(align.axis(), Some(align)); - aligns - }, - aligns: Axes => aligns.map(Some), -} - -castable! { - Axes>, - 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 { - match value { - Value::None => Ok(Self), - _ => ::error(value), - } - } - - fn describe() -> CastInfo { - CastInfo::Type("none") - } -} - -impl Cast for Option { - fn is(value: &Value) -> bool { - matches!(value, Value::None) || T::is(value) - } - - fn cast(value: Value) -> StrResult { - match value { - Value::None => Ok(None), - v if T::is(&v) => Ok(Some(T::cast(v)?)), - _ => ::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 { - match value { - Value::Auto => Ok(Self), - _ => ::error(value), - } - } - - fn describe() -> CastInfo { - CastInfo::Type("auto") - } -} - -impl Cast for Smart { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) || T::is(value) - } - - fn cast(value: Value) -> StrResult { - match value { - Value::Auto => Ok(Self::Auto), - v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), - _ => ::error(value), - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("auto") - } -} - -impl Cast for Sides> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult { - 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 { - ::error(value) - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("dictionary") - } -} - -impl Cast for Corners> -where - T: Cast + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult { - 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 { - ::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::() } 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>); - -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) -> 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 { - 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 { - 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 { - 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>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); - } -} - -impl FromIterator<(Str, Value)> for Dict { - fn from_iter>(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; - - 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, - route: Tracked, - tracer: TrackedMut, - source: &Source, -) -> SourceResult { - // 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::() { - 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, - text: &str, - span: Span, -) -> SourceResult { - 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::().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, - /// 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, -} - -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) { - 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 { - 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), -} - -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>, - id: Option, -} - -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, 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, - values: Vec, -} - -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) -> Self { - Self { span, values: vec![] } - } - - /// Get the traced values. - pub fn finish(self) -> Vec { - self.values - } -} - -#[comemo::track] -impl Tracer { - /// The traced span if it is part of the given source file. - fn span(&self, id: SourceId) -> Option { - 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; -} - -impl Eval for ast::Markup { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - eval_markup(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of markup. -fn eval_markup( - vm: &mut Vm, - exprs: &mut impl Iterator, -) -> SourceResult { - 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 { - 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 { - Ok((vm.items.text)(self.get().clone())) - } -} - -impl Eval for ast::Space { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.space)()) - } -} - -impl Eval for ast::Linebreak { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.linebreak)()) - } -} - -impl Eval for ast::Parbreak { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.parbreak)()) - } -} - -impl Eval for ast::Escape { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::Shorthand { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::new(self.get()))) - } -} - -impl Eval for ast::SmartQuote { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.smart_quote)(self.double())) - } -} - -impl Eval for ast::Strong { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.strong)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Emph { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.emph)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::Raw { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - Ok((vm.items.link)(self.get().clone())) - } -} - -impl Eval for ast::Label { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Label(Label(self.get().into()))) - } -} - -impl Eval for ast::Ref { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.ref_)(self.get().into())) - } -} - -impl Eval for ast::Heading { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - Ok((vm.items.list_item)(self.body().eval(vm)?)) - } -} - -impl Eval for ast::EnumItem { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - 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 { - 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 { - Ok(Content::sequence( - self.exprs() - .map(|expr| Ok(expr.eval(vm)?.display())) - .collect::>()?, - ) - .spanned(self.span())) - } -} - -impl Eval for ast::MathIdent { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - Ok((vm.items.math_align_point)()) - } -} - -impl Eval for ast::MathDelimited { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - 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 { - 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 { - Ok(vm.scopes.get(self).cloned().at(self.span())?) - } -} - -impl Eval for ast::None { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::None) - } -} - -impl Eval for ast::Auto { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Auto) - } -} - -impl Eval for ast::Bool { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Bool(self.get())) - } -} - -impl Eval for ast::Int { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Int(self.get())) - } -} - -impl Eval for ast::Float { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Float(self.get())) - } -} - -impl Eval for ast::Numeric { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::numeric(self.get())) - } -} - -impl Eval for ast::Str { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult { - Ok(Value::Str(self.get().into())) - } -} - -impl Eval for ast::CodeBlock { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - eval_code(vm, &mut self.exprs()) - } -} - -/// Evaluate a stream of expressions. -fn eval_code( - vm: &mut Vm, - exprs: &mut impl Iterator, -) -> SourceResult { - 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 { - 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.expr().eval(vm) - } -} - -impl Eval for ast::Array { - type Output = Array; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - 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 { - 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 { - 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, - ) -> SourceResult { - 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, - ) -> SourceResult { - 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 { - 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 { - 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::()?.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::().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 { - 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 { - // 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 { - 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 { - if let Some(condition) = self.condition() { - if !condition.eval(vm)?.cast::().at(condition.span())? { - return Ok(StyleMap::new()); - } - } - - let target = self.target(); - let target = target.eval(vm)?.cast::().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 { - let selector = self - .selector() - .map(|sel| sel.eval(vm)?.cast::().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::().at(span)?, - }; - - Ok(Recipe { span, selector, transform }) - } -} - -impl Eval for ast::Conditional { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - let condition = self.condition(); - if condition.eval(vm)?.cast::().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 { - 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::().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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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>, 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(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, - 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(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::()), - 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 { - 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 { - 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, - args: Args, - ) -> SourceResult { - 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 { - 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 { - 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) -> StrResult { - 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, ""), - None => f.write_str(""), - } - } -} - -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, - /// The set rule. - set: Option SourceResult>, - /// The id of the node to customize with this function's show rule. - node: Option, - /// Documentation of the function. - info: FuncInfo, -} - -impl Hash for Native { - fn hash(&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, - /// 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, - /// 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)>, - /// The name of an argument sink where remaining arguments are placed. - pub sink: Option, - /// 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, - route: Tracked, - tracer: TrackedMut, - depth: usize, - mut args: Args, - ) -> SourceResult { - 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::(param)?.unwrap_or_else(|| default.clone()) - } - None => args.expect::(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 { - 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, - /// 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, 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, 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, top: Option) -> 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(&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 = 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 { - 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::("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 { - 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::("key")?, args.expect("value")?), - "remove" => { - output = dict.remove(&args.expect::("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::("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); - -/// 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) -> 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, "", 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 { - 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 { - 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 { - 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 { - 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::(), b.downcast::()) - { - 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 { - 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 { - 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 { - 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 { - 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, b: Rel) -> StrResult { - 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 { - 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 { - 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 { - 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 { - Ok(Bool(equal(&lhs, &rhs))) -} - -/// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> StrResult { - 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 { - 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 { - 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 { - 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 { - 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 { - match (lhs, rhs) { - (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), - (Dyn(a), Str(b)) => a.downcast::().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, - /// 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, 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, value: impl Into) { - 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(&mut self, name: &'static str) { - self.define(name, Func::from_type::(name)); - } - - /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into, - value: impl Into, - ) { - 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> { - self.0.get_mut(var).map(Slot::write) - } - - /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator { - 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.0 - .graphemes(true) - .next() - .map(Into::into) - .ok_or_else(string_is_empty) - } - - /// Extract the last grapheme cluster. - pub fn last(&self) -> StrResult { - 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 { - 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) -> StrResult { - 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 { - 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 { - 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 { - 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) -> 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, - at: Option, - 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) -> 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 { - 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 { - 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 for Str { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow for Str { - fn borrow(&self) -> &str { - self - } -} - -impl From 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 for Str { - fn from(s: EcoString) -> Self { - Self(s) - } -} - -impl From for Str { - fn from(s: String) -> Self { - Self(s.into()) - } -} - -impl From> for Str { - fn from(s: Cow) -> Self { - Self(s.into()) - } -} - -impl FromIterator for Str { - fn from_iter>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl From for EcoString { - fn from(str: Str) -> Self { - str.0 - } -} - -impl From 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 { - 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(&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 { - 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 { - 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 + '_ { - 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), - 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 { - 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, - modifiers: &str, -) -> Option { - 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 { - 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 { - 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, content: &Content) -> SourceResult { /// 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), - /// A fraction: `1fr`. - Fraction(Fr), - /// A color value: `#f79143ff`. - Color(Color), - /// A symbol: `arrow.l`. - Symbol(Symbol), - /// A string: `"string"`. - Str(Str), - /// A label: ``. - 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(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::::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(self) -> StrResult { - T::cast(self) - } - - /// Try to access a field on the value. - pub fn field(&self, field: &str) -> StrResult { - 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 { - ops::compare(self, other) - } -} - -impl Hash for Value { - fn hash(&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 for Value { - fn from(v: i32) -> Self { - Self::Int(v as i64) - } -} - -impl From for Value { - fn from(v: usize) -> Self { - Self::Int(v as i64) - } -} - -impl From for Value { - fn from(v: Abs) -> Self { - Self::Length(v.into()) - } -} - -impl From for Value { - fn from(v: Em) -> Self { - Self::Length(v.into()) - } -} - -impl From 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 for Value { - fn from(v: EcoString) -> Self { - Self::Str(v.into()) - } -} - -impl From for Value { - fn from(v: String) -> Self { - Self::Str(v.into()) - } -} - -impl From for Value { - fn from(v: Dynamic) -> Self { - Self::Dyn(v) - } -} - -/// A dynamic value. -#[derive(Clone, Hash)] -pub struct Dynamic(Arc); - -impl Dynamic { - /// Create a new instance from any value that satisifies the required bounds. - pub fn new(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(&self) -> bool { - (*self.0).as_any().is::() - } - - /// Try to downcast to a reference to a specific type. - pub fn downcast(&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 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::() 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(&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 { - 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: "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, 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)"); - } -} -- cgit v1.2.3