diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-10-17 19:26:24 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-10-17 20:04:22 +0200 |
| commit | e21822665591dc19766275da1e185215a6b945ef (patch) | |
| tree | 7788e211c3c33c8b5a8ad7d5eb7574e33631eb16 /src/eval | |
| parent | 4fd031a256b2ecfe524859d5599fafb386395572 (diff) | |
Merge some modules
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/args.rs | 225 | ||||
| -rw-r--r-- | src/eval/array.rs | 369 | ||||
| -rw-r--r-- | src/eval/capture.rs | 186 | ||||
| -rw-r--r-- | src/eval/cast.rs | 361 | ||||
| -rw-r--r-- | src/eval/dict.rs | 193 | ||||
| -rw-r--r-- | src/eval/func.rs | 248 | ||||
| -rw-r--r-- | src/eval/methods.rs | 167 | ||||
| -rw-r--r-- | src/eval/mod.rs | 1242 | ||||
| -rw-r--r-- | src/eval/ops.rs | 399 | ||||
| -rw-r--r-- | src/eval/raw.rs | 294 | ||||
| -rw-r--r-- | src/eval/scope.rs | 161 | ||||
| -rw-r--r-- | src/eval/str.rs | 475 | ||||
| -rw-r--r-- | src/eval/value.rs | 454 | ||||
| -rw-r--r-- | src/eval/vm.rs | 87 |
14 files changed, 0 insertions, 4861 deletions
diff --git a/src/eval/args.rs b/src/eval/args.rs deleted file mode 100644 index f95fbf08..00000000 --- a/src/eval/args.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::fmt::{self, Debug, Formatter, Write}; - -use super::{Array, Cast, Dict, Str, Value}; -use crate::diag::{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: Vec<Arg>, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq, Hash)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option<Str>, - /// The value of the argument. - pub value: Spanned<Value>, -} - -impl Args { - /// Create positional arguments from a span and values. - pub fn new(span: Span, values: impl IntoIterator<Item = Value>) -> Self { - let items = values - .into_iter() - .map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - }) - .collect(); - Self { span, items } - } - - /// Push a positional argument. - pub fn push(&mut self, span: Span, value: Value) { - self.items.push(Arg { - span: self.span, - name: None, - value: Spanned::new(value, span), - }) - } - - /// Consume and cast the first positional argument if there is one. - pub fn eat<T>(&mut self) -> SourceResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect<T>(&mut self, what: &str) -> SourceResult<T> - where - T: Cast<Spanned<Value>>, - { - match self.eat()? { - Some(v) => Ok(v), - None => bail!(self.span, "missing argument: {}", what), - } - } - - /// Find and consume the first castable positional argument. - pub fn find<T>(&mut self) -> SourceResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume all castable positional arguments. - pub fn all<T>(&mut self) -> SourceResult<Vec<T>> - where - T: Cast<Spanned<Value>>, - { - let mut list = vec![]; - while let Some(value) = self.find()? { - list.push(value); - } - Ok(list) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - // We don't quit once we have a match because when multiple matches - // exist, we want to remove all of them and use the last one. - let mut i = 0; - let mut found = None; - while i < self.items.len() { - if self.items[i].name.as_deref() == Some(name) { - let value = self.items.remove(i).value; - let span = value.span; - found = Some(T::cast(value).at(span)?); - } else { - i += 1; - } - } - Ok(found) - } - - /// Same as named, but with fallback to find. - pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>> - where - T: Cast<Spanned<Value>>, - { - match self.named(name)? { - Some(value) => Ok(Some(value)), - None => self.find(), - } - } - - /// Take out all arguments into a new instance. - pub fn take(&mut self) -> Self { - Self { - span: self.span, - items: std::mem::take(&mut self.items), - } - } - - /// Return an "unexpected argument" error if there is any remaining - /// argument. - pub fn finish(self) -> SourceResult<()> { - if let Some(arg) = self.items.first() { - bail!(arg.span, "unexpected argument"); - } - Ok(()) - } - - /// Extract the positional arguments as an array. - pub fn to_positional(&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() - } - - /// Reinterpret these arguments as actually being an array index. - pub fn into_index(self) -> SourceResult<i64> { - self.into_castable("index") - } - - /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> SourceResult<Str> { - self.into_castable("key") - } - - /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable<T: Cast>(self, what: &str) -> SourceResult<T> { - let mut iter = self.items.into_iter(); - let value = match iter.next() { - Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, - None => { - bail!(self.span, "missing {}", what); - } - Some(Arg { name: Some(_), span, .. }) => { - bail!(span, "named pair is not allowed here"); - } - }; - - if let Some(arg) = iter.next() { - bail!(arg.span, "only one {} is allowed", what); - } - - Ok(value) - } -} - -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 deleted file mode 100644 index b77ce93c..00000000 --- a/src/eval/array.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter, Write}; -use std::ops::{Add, AddAssign}; -use std::sync::Arc; - -use super::{ops, Args, Func, Value, Vm}; -use crate::diag::{At, SourceResult, StrResult}; -use crate::syntax::Spanned; -use crate::util::ArcExt; - -/// Create a new [`Array`] from values. -#[allow(unused_macros)] -macro_rules! array { - ($value:expr; $count:expr) => { - $crate::eval::Array::from_vec(vec![$value.into(); $count]) - }; - - ($($value:expr),* $(,)?) => { - $crate::eval::Array::from_vec(vec![$($value.into()),*]) - }; -} - -/// A reference counted array with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Array(Arc<Vec<Value>>); - -impl Array { - /// Create a new, empty array. - pub fn new() -> Self { - Self::default() - } - - /// Create a new array from a vector of values. - pub fn from_vec(vec: Vec<Value>) -> Self { - Self(Arc::new(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) -> Option<&Value> { - self.0.first() - } - - /// The last value in the array. - pub fn last(&self) -> Option<&Value> { - self.0.last() - } - - /// Borrow the value at the given index. - pub fn get(&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 get_mut(&mut self, index: i64) -> StrResult<&mut Value> { - let len = self.len(); - self.locate(index) - .and_then(move |i| Arc::make_mut(&mut self.0).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) { - Arc::make_mut(&mut self.0).push(value); - } - - /// Remove the last value in the array. - pub fn pop(&mut self) -> StrResult<()> { - Arc::make_mut(&mut self.0).pop().ok_or_else(array_is_empty)?; - Ok(()) - } - - /// 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))?; - - Arc::make_mut(&mut 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))?; - - Arc::make_mut(&mut self.0).remove(i); - return Ok(()); - } - - /// Extract a contigous subregion of the array. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let len = self.len(); - let start = self - .locate(start) - .filter(|&start| start <= self.0.len()) - .ok_or_else(|| out_of_bounds(start, len))?; - - let end = end.unwrap_or(self.len()); - let end = self - .locate(end) - .filter(|&end| end <= self.0.len()) - .ok_or_else(|| out_of_bounds(end, len))? - .max(start); - - Ok(Self::from_vec(self.0[start .. end].to_vec())) - } - - /// 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, f: Spanned<Func>) -> SourceResult<Option<Value>> { - for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { - return Ok(Some(item.clone())); - } - } - - Ok(None) - } - - /// Return the index of the first matching element. - pub fn position(&self, vm: &mut Vm, f: Spanned<Func>) -> SourceResult<Option<i64>> { - for (i, item) in self.iter().enumerate() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::<bool>().at(f.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, f: Spanned<Func>) -> SourceResult<Self> { - let mut kept = vec![]; - for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::<bool>().at(f.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, f: Spanned<Func>) -> SourceResult<Self> { - let enumerate = f.v.argc() == Some(2); - self.iter() - .enumerate() - .map(|(i, item)| { - let mut args = Args::new(f.span, []); - if enumerate { - args.push(f.span, Value::Int(i as i64)); - } - args.push(f.span, item.clone()); - f.v.call(vm, args) - }) - .collect() - } - - /// Whether any element matches. - pub fn any(&self, vm: &mut Vm, f: Spanned<Func>) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if f.v.call(vm, args)?.cast::<bool>().at(f.span)? { - return Ok(true); - } - } - - Ok(false) - } - - /// Whether all elements match. - pub fn all(&self, vm: &mut Vm, f: Spanned<Func>) -> SourceResult<bool> { - for item in self.iter() { - let args = Args::new(f.span, [item.clone()]); - if !f.v.call(vm, args)?.cast::<bool>().at(f.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 = Vec::with_capacity(self.0.len()); - for item in self.iter() { - if let Value::Array(nested) = item { - flat.extend(nested.flatten().into_iter()); - } else { - flat.push(item.clone()); - } - } - Self::from_vec(flat) - } - - /// Returns a new array with reversed order. - pub fn rev(&self) -> Self { - self.0.iter().cloned().rev().collect() - } - - /// Join all values in the array, optionally with separator and last - /// separator (between the final two items). - pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> { - let len = self.0.len(); - let sep = sep.unwrap_or(Value::None); - - let mut result = Value::None; - for (i, value) in self.iter().cloned().enumerate() { - if i > 0 { - if i + 1 == len && last.is_some() { - result = ops::join(result, last.take().unwrap())?; - } else { - result = ops::join(result, sep.clone())?; - } - } - - result = ops::join(result, value)?; - } - - Ok(result) - } - - /// Return a sorted version of this array. - /// - /// Returns an error if two values could not be compared. - pub fn sorted(&self) -> StrResult<Self> { - let mut result = Ok(()); - let mut vec = (*self.0).clone(); - vec.sort_by(|a, b| { - a.partial_cmp(b).unwrap_or_else(|| { - if result.is_ok() { - result = Err(format!( - "cannot order {} and {}", - a.type_name(), - b.type_name(), - )); - } - Ordering::Equal - }) - }); - result.map(|_| Self::from_vec(vec)) - } - - /// Repeat this array `n` times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let count = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n)) - .ok_or_else(|| format!("cannot repeat this array {} times", n))?; - - Ok(self.iter().cloned().cycle().take(count).collect()) - } - - /// Extract a slice of the whole array. - pub fn as_slice(&self) -> &[Value] { - self.0.as_slice() - } - - /// Iterate over references to the contained values. - pub fn iter(&self) -> std::slice::Iter<Value> { - self.0.iter() - } - - /// Resolve an index. - fn locate(&self, index: i64) -> Option<usize> { - usize::try_from(if index >= 0 { - index - } else { - self.len().checked_add(index)? - }) - .ok() - } -} - -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: i64) -> String { - format!("array index out of bounds (index: {}, len: {})", index, len) -} - -/// The error message when the array is empty. -#[cold] -fn array_is_empty() -> String { - "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) { - match Arc::try_unwrap(rhs.0) { - Ok(vec) => self.extend(vec), - Err(rc) => self.extend(rc.iter().cloned()), - } - } -} - -impl Extend<Value> for Array { - fn extend<T: IntoIterator<Item = Value>>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); - } -} - -impl FromIterator<Value> for Array { - fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self { - Self(Arc::new(iter.into_iter().collect())) - } -} - -impl IntoIterator for Array { - type Item = Value; - type IntoIter = std::vec::IntoIter<Value>; - - fn into_iter(self) -> Self::IntoIter { - Arc::take(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/capture.rs b/src/eval/capture.rs deleted file mode 100644 index 289d31e1..00000000 --- a/src/eval/capture.rs +++ /dev/null @@ -1,186 +0,0 @@ -use super::{Scope, Scopes, Value}; -use crate::syntax::ast::TypedNode; -use crate::syntax::{ast, SyntaxNode}; - -/// A visitor that captures variable slots. -pub 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 - } - - /// Bind a new internal variable. - pub fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - pub 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()); - } - } - } - - /// 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), - - // 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()); - } - } - - 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()); - } - - // 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 show rule contains a binding, but that binding is only active - // after the target has been evaluated. - Some(ast::Expr::Show(show)) => { - self.visit(show.pattern().as_untyped()); - if let Some(binding) = show.binding() { - self.bind(binding); - } - self.visit(show.body().as_untyped()); - } - - // 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()); - let pattern = expr.pattern(); - if let Some(key) = pattern.key() { - self.bind(key); - } - self.bind(pattern.value()); - self.visit(expr.body().as_untyped()); - } - - // An import contains items, but these are active only after the - // path is evaluated. - Some(ast::Expr::Import(expr)) => { - self.visit(expr.path().as_untyped()); - if let 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); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::parse; - - #[track_caller] - fn test(text: &str, result: &[&str]) { - let mut scopes = Scopes::new(None); - 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", &[]); - - // 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"]); - - // Show rule. - test("#show x: y as x", &["y"]); - test("#show x: y as x + z", &["y", "z"]); - test("#show x: x as x", &["x"]); - - // For loop. - test("#for x in y { x + z }", &["y", "z"]); - test("#for x, y in y { x + y }", &["y"]); - - // Import. - test("#import x, y from z", &["z"]); - test("#import x, y, z from x + y", &["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/cast.rs b/src/eval/cast.rs deleted file mode 100644 index 99c34810..00000000 --- a/src/eval/cast.rs +++ /dev/null @@ -1,361 +0,0 @@ -use std::num::NonZeroUsize; - -use super::{Regex, Value}; -use crate::diag::{with_alternative, StrResult}; -use crate::geom::{Corners, Dir, Paint, Sides}; -use crate::model::{Content, Layout, LayoutNode, Pattern}; -use crate::syntax::Spanned; -use crate::util::EcoString; - -/// Cast from a value to a specific type. -pub trait Cast<V = Value>: Sized { - /// Check whether the value is castable to `Self`. - fn is(value: &V) -> bool; - - /// Try to cast the value into an instance of `Self`. - fn cast(value: V) -> StrResult<Self>; -} - -/// Implement traits for dynamic types. -macro_rules! dynamic { - ($type:ty: $name:literal, $($tts:tt)*) => { - impl $crate::eval::Type for $type { - const TYPE_NAME: &'static str = $name; - } - - castable! { - $type, - Expected: <Self as $crate::eval::Type>::TYPE_NAME, - $($tts)* - @this: Self => this.clone(), - } - - impl From<$type> for $crate::eval::Value { - fn from(v: $type) -> Self { - $crate::eval::Value::Dyn($crate::eval::Dynamic::new(v)) - } - } - }; -} - -/// Make a type castable from a value. -macro_rules! castable { - ($type:ty: $inner:ty) => { - impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &$crate::eval::Value) -> bool { - <$inner>::is(value) - } - - fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult<Self> { - <$inner>::cast(value).map(Self) - } - } - }; - - ( - $type:ty, - Expected: $expected:expr, - $($pattern:pat => $out:expr,)* - $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* - ) => { - #[allow(unreachable_patterns)] - impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &$crate::eval::Value) -> bool { - #[allow(unused_variables)] - match value { - $($pattern => true,)* - $crate::eval::Value::Dyn(dynamic) => { - false $(|| dynamic.is::<$dyn_type>())* - } - _ => false, - } - } - - fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult<Self> { - let found = match value { - $($pattern => return Ok($out),)* - $crate::eval::Value::Dyn(dynamic) => { - $(if let Some($dyn_in) = dynamic.downcast::<$dyn_type>() { - return Ok($dyn_out); - })* - dynamic.type_name() - } - v => v.type_name(), - }; - - Err(format!("expected {}, found {}", $expected, found)) - } - } - }; -} - -impl Cast for Value { - fn is(_: &Value) -> bool { - true - } - - fn cast(value: Value) -> StrResult<Self> { - Ok(value) - } -} - -impl<T: Cast> Cast<Spanned<Value>> for T { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - T::cast(value.v) - } -} - -impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> { - fn is(value: &Spanned<Value>) -> bool { - T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - let span = value.span; - T::cast(value.v).map(|t| Spanned::new(t, span)) - } -} - -dynamic! { - Dir: "direction", -} - -dynamic! { - Regex: "regular expression", -} - -castable! { - usize, - Expected: "non-negative integer", - Value::Int(int) => int.try_into().map_err(|_| { - if int < 0 { - "must be at least zero" - } else { - "number too large" - } - })?, -} - -castable! { - NonZeroUsize, - Expected: "positive integer", - Value::Int(int) => int - .try_into() - .and_then(|int: usize| int.try_into()) - .map_err(|_| if int <= 0 { - "must be positive" - } else { - "number too large" - })?, -} - -castable! { - Paint, - Expected: "color", - Value::Color(color) => Paint::Solid(color), -} - -castable! { - EcoString, - Expected: "string", - Value::Str(str) => str.into(), -} - -castable! { - String, - Expected: "string", - Value::Str(string) => string.into(), -} - -castable! { - LayoutNode, - Expected: "content", - Value::None => Self::default(), - Value::Str(text) => Content::Text(text.into()).pack(), - Value::Content(content) => content.pack(), -} - -castable! { - Pattern, - Expected: "function, string or regular expression", - Value::Func(func) => Self::Node(func.node()?), - Value::Str(text) => Self::text(&text), - @regex: Regex => Self::Regex(regex.clone()), -} - -impl<T: Cast> Cast for Option<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::None) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::None => Ok(None), - v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")), - } - } -} - -/// A value that can be automatically determined. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Smart<T> { - /// The value should be determined smartly based on the circumstances. - Auto, - /// A specific value. - Custom(T), -} - -impl<T> Smart<T> { - /// Map the contained custom value with `f`. - pub fn map<F, U>(self, f: F) -> Smart<U> - where - F: FnOnce(T) -> U, - { - match self { - Self::Auto => Smart::Auto, - Self::Custom(x) => Smart::Custom(f(x)), - } - } - - /// Keeps `self` if it contains a custom value, otherwise returns `other`. - pub fn or(self, other: Smart<T>) -> Self { - match self { - Self::Custom(x) => Self::Custom(x), - Self::Auto => other, - } - } - - /// Returns the contained custom value or a provided default value. - pub fn unwrap_or(self, default: T) -> T { - match self { - Self::Auto => default, - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or computes a default value. - pub fn unwrap_or_else<F>(self, f: F) -> T - where - F: FnOnce() -> T, - { - match self { - Self::Auto => f(), - Self::Custom(x) => x, - } - } - - /// Returns the contained custom value or the default value. - pub fn unwrap_or_default(self) -> T - where - T: Default, - { - self.unwrap_or_else(T::default) - } -} - -impl<T> Default for Smart<T> { - fn default() -> Self { - Self::Auto - } -} - -impl<T: Cast> Cast for Smart<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::Auto) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Auto => Ok(Self::Auto), - v => T::cast(v) - .map(Self::Custom) - .map_err(|msg| with_alternative(msg, "auto")), - } - } -} - -impl<T> Cast for Sides<T> -where - T: Cast + Default + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).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), - }; - - if let Some((key, _)) = dict.iter().next() { - return Err(format!("unexpected key {key:?}")); - } - - Ok(sides.map(Option::unwrap_or_default)) - } else { - T::cast(value).map(Self::splat).map_err(|msg| { - with_alternative( - msg, - "dictionary with any of \ - `left`, `top`, `right`, `bottom`, \ - `x`, `y`, or `rest` as keys", - ) - }) - } - } -} - -impl<T> Cast for Corners<T> -where - T: Cast + Default + Copy, -{ - fn is(value: &Value) -> bool { - matches!(value, Value::Dict(_)) || T::is(value) - } - - fn cast(mut value: Value) -> StrResult<Self> { - if let Value::Dict(dict) = &mut value { - let mut take = |key| dict.take(key).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), - }; - - if let Some((key, _)) = dict.iter().next() { - return Err(format!("unexpected key {key:?}")); - } - - Ok(corners.map(Option::unwrap_or_default)) - } else { - T::cast(value).map(Self::splat).map_err(|msg| { - with_alternative( - msg, - "dictionary with any of \ - `top-left`, `top-right`, `bottom-right`, `bottom-left`, \ - `left`, `top`, `right`, `bottom`, or `rest` as keys", - ) - }) - } - } -} diff --git a/src/eval/dict.rs b/src/eval/dict.rs deleted file mode 100644 index b95ead31..00000000 --- a/src/eval/dict.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter, Write}; -use std::ops::{Add, AddAssign}; -use std::sync::Arc; - -use super::{Args, Array, Func, Str, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; -use crate::parse::is_ident; -use crate::syntax::Spanned; -use crate::util::ArcExt; - -/// Create a new [`Dict`] from key-value pairs. -#[allow(unused_macros)] -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) - }}; -} - -/// A reference-counted dictionary with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Dict(Arc<BTreeMap<Str, Value>>); - -impl Dict { - /// Create a new, empty dictionary. - pub fn new() -> Self { - Self::default() - } - - /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: BTreeMap<Str, Value>) -> Self { - Self(Arc::new(map)) - } - - /// Whether the dictionary is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// The number of pairs in the dictionary. - pub fn len(&self) -> i64 { - self.0.len() as i64 - } - - /// Borrow the value the given `key` maps to. - pub fn get(&self, key: &str) -> StrResult<&Value> { - self.0.get(key).ok_or_else(|| missing_key(key)) - } - - /// Mutably borrow the value the given `key` maps to. - /// - /// This inserts the key with [`None`](Value::None) as the value if not - /// present so far. - pub fn get_mut(&mut self, key: Str) -> &mut Value { - Arc::make_mut(&mut self.0).entry(key).or_default() - } - - /// 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`. - pub fn remove(&mut self, key: &str) -> StrResult<()> { - match Arc::make_mut(&mut self.0).remove(key) { - Some(_) => Ok(()), - None => Err(missing_key(key)), - } - } - - /// Remove the value if the dictionary contains the given key. - pub fn take(&mut self, key: &str) -> Option<Value> { - Arc::make_mut(&mut self.0).remove(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() - } - - /// Transform each pair in the array with a function. - pub fn map(&self, vm: &mut Vm, f: Spanned<Func>) -> SourceResult<Array> { - self.iter() - .map(|(key, value)| { - let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); - f.v.call(vm, args) - }) - .collect() - } - - /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> { - self.0.iter() - } -} - -/// The missing key access error message. -#[cold] -fn missing_key(key: &str) -> String { - format!("dictionary does not contain key {:?}", Str::from(key)) -} - -impl Debug for Dict { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - if self.is_empty() { - f.write_char(':')?; - } - for (i, (key, value)) in self.iter().enumerate() { - if is_ident(key) { - f.write_str(key)?; - } else { - write!(f, "{key:?}")?; - } - f.write_str(": ")?; - value.fmt(f)?; - if i + 1 < self.0.len() { - f.write_str(", ")?; - } - } - f.write_char(')') - } -} - -impl Add for Dict { - type Output = Self; - - fn add(mut self, rhs: Dict) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Dict { - fn add_assign(&mut self, rhs: Dict) { - match Arc::try_unwrap(rhs.0) { - Ok(map) => self.extend(map), - Err(rc) => self.extend(rc.iter().map(|(k, v)| (k.clone(), v.clone()))), - } - } -} - -impl Extend<(Str, Value)> for Dict { - fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) { - Arc::make_mut(&mut self.0).extend(iter); - } -} - -impl FromIterator<(Str, Value)> for Dict { - fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self { - Self(Arc::new(iter.into_iter().collect())) - } -} - -impl IntoIterator for Dict { - type Item = (Str, Value); - type IntoIter = std::collections::btree_map::IntoIter<Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - Arc::take(self.0).into_iter() - } -} - -impl<'a> IntoIterator for &'a Dict { - type Item = (&'a Str, &'a Value); - type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} diff --git a/src/eval/func.rs b/src/eval/func.rs deleted file mode 100644 index c307b237..00000000 --- a/src/eval/func.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use comemo::{Track, Tracked}; - -use super::{Args, Eval, Flow, Route, Scope, Scopes, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; -use crate::model::{Content, NodeId, StyleMap}; -use crate::source::SourceId; -use crate::syntax::ast::Expr; -use crate::util::EcoString; -use crate::World; - -/// An evaluatable function. -#[derive(Clone, Hash)] -pub struct Func(Arc<Repr>); - -/// 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 native rust function. - pub fn from_fn( - name: &'static str, - func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, - ) -> Self { - Self(Arc::new(Repr::Native(Native { - name, - func, - set: None, - node: None, - }))) - } - - /// Create a new function from a native rust node. - pub fn from_node<T: Node>(name: &'static str) -> Self { - Self(Arc::new(Repr::Native(Native { - name, - 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: T::SHOWABLE.then(|| NodeId::of::<T>()), - }))) - } - - /// Create a new function from a closure. - pub fn from_closure(closure: Closure) -> Self { - Self(Arc::new(Repr::Closure(closure))) - } - - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - Self(Arc::new(Repr::With(self, args))) - } - - /// The name of the function. - pub fn name(&self) -> Option<&str> { - match self.0.as_ref() { - Repr::Native(native) => Some(native.name), - Repr::Closure(closure) => closure.name.as_deref(), - Repr::With(func, _) => func.name(), - } - } - - /// The number of positional arguments this function takes, if known. - pub fn argc(&self) -> Option<usize> { - match self.0.as_ref() { - Repr::Closure(closure) => Some( - closure.params.iter().filter(|(_, default)| default.is_none()).count(), - ), - Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub( - applied.items.iter().filter(|arg| arg.name.is_none()).count(), - )), - _ => None, - } - } - - /// Call the function with the given arguments. - pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> { - let value = match self.0.as_ref() { - Repr::Native(native) => (native.func)(vm, &mut args)?, - Repr::Closure(closure) => closure.call(vm, &mut args)?, - Repr::With(wrapped, applied) => { - args.items.splice(.. 0, applied.items.iter().cloned()); - return wrapped.call(vm, args); - } - }; - args.finish()?; - Ok(value) - } - - /// Call the function without an existing virtual machine. - pub fn call_detached( - &self, - world: Tracked<dyn World>, - args: Args, - ) -> SourceResult<Value> { - let route = Route::default(); - let mut vm = Vm::new(world, route.track(), None, Scopes::new(None)); - self.call(&mut vm, args) - } - - /// Execute the function's set rule and return the resulting style map. - pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { - let styles = match self.0.as_ref() { - Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, - _ => StyleMap::new(), - }; - args.finish()?; - Ok(styles) - } - - /// The id of the node to customize with this function's show rule. - pub fn node(&self) -> StrResult<NodeId> { - match self.0.as_ref() { - Repr::Native(Native { node: Some(id), .. }) => Ok(*id), - _ => Err("this function cannot be customized with show")?, - } - } -} - -impl Debug for Func { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.name() { - Some(name) => f.write_str(name), - None => f.write_str("(..) => {..}"), - } - } -} - -impl PartialEq for Func { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} - -/// A function defined by a native rust function or node. -struct Native { - /// The name of the function. - pub name: &'static str, - /// The function pointer. - pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, - /// The set rule. - pub set: Option<fn(&mut Args) -> SourceResult<StyleMap>>, - /// The id of the node to customize with this function's show rule. - pub node: Option<NodeId>, -} - -impl Hash for Native { - fn hash<H: Hasher>(&self, state: &mut H) { - self.name.hash(state); - (self.func as usize).hash(state); - self.set.map(|set| set as usize).hash(state); - self.node.hash(state); - } -} - -/// A constructable, stylable content node. -pub trait Node: 'static { - /// Whether this node can be customized through a show rule. - const SHOWABLE: bool; - - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>; - - /// Parse relevant arguments into style properties for this node. - /// - /// When `constructor` is true, [`construct`](Self::construct) will run - /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>; -} - -/// A user-defined closure. -#[derive(Hash)] -pub struct Closure { - /// The source file where the closure was defined. - pub location: Option<SourceId>, - /// The name of the closure. - pub name: Option<EcoString>, - /// Captured values from outer scopes. - pub captured: Scope, - /// The parameter names and default values. Parameters with default value - /// are named parameters. - pub params: Vec<(EcoString, Option<Value>)>, - /// The name of an argument sink where remaining arguments are placed. - pub sink: Option<EcoString>, - /// The expression the closure should evaluate to. - pub body: Expr, -} - -impl Closure { - /// Call the function in the context with the arguments. - pub fn call(&self, vm: &mut Vm, args: &mut Args) -> SourceResult<Value> { - // 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 = self.captured.clone(); - - // Parse the arguments according to the parameter list. - for (param, default) in &self.params { - scopes.top.define(param.clone(), match default { - None => args.expect::<Value>(param)?, - Some(default) => { - args.named::<Value>(param)?.unwrap_or_else(|| default.clone()) - } - }); - } - - // Put the remaining arguments into the sink. - if let Some(sink) = &self.sink { - scopes.top.define(sink.clone(), args.take()); - } - - // Determine the route inside the closure. - let detached = vm.location.is_none(); - let fresh = Route::new(self.location); - let route = if detached { fresh.track() } else { vm.route }; - - // Evaluate the body. - let mut sub = Vm::new(vm.world, route, self.location, scopes); - let result = self.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 - } -} diff --git a/src/eval/methods.rs b/src/eval/methods.rs deleted file mode 100644 index 57fff681..00000000 --- a/src/eval/methods.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Methods on values. - -use super::{Args, Value, Vm}; -use crate::diag::{At, SourceResult}; -use crate::syntax::Span; -use crate::util::EcoString; - -/// Call a method on a value. -pub fn call( - vm: &mut Vm, - value: Value, - method: &str, - mut args: Args, - span: Span, -) -> SourceResult<Value> { - let name = value.type_name(); - let missing = || Err(missing_method(name, method)).at(span); - - let output = match value { - Value::Str(string) => match method { - "len" => Value::Int(string.len() as i64), - "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)?) - } - "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().cloned().unwrap_or(Value::None), - "last" => array.last().cloned().unwrap_or(Value::None), - "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")?)?), - "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()), - "keys" => Value::Array(dict.keys()), - "values" => Value::Array(dict.values()), - "pairs" => Value::Array(dict.map(vm, args.expect("function")?)?), - _ => return missing(), - }, - - Value::Func(func) => match method { - "with" => Value::Func(func.clone().with(args.take())), - _ => return missing(), - }, - - Value::Args(args) => match method { - "positional" => Value::Array(args.to_positional()), - "named" => Value::Dict(args.to_named()), - _ => return missing(), - }, - - 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(), - }, - - _ => 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); - - match value { - Value::Array(array) => match method { - "push" => array.push(args.expect("value")?), - "pop" => array.pop().at(span)?, - "insert" => { - array.insert(args.expect("index")?, args.expect("value")?).at(span)? - } - "remove" => array.remove(args.expect("index")?).at(span)?, - _ => return missing(), - }, - - Value::Dict(dict) => match method { - "remove" => dict.remove(&args.expect::<EcoString>("key")?).at(span)?, - _ => return missing(), - }, - - _ => return missing(), - } - - args.finish()?; - Ok(()) -} - -/// Whether a specific method is mutating. -pub fn is_mutating(method: &str) -> bool { - matches!(method, "push" | "pop" | "insert" | "remove") -} - -/// The missing method error message. -#[cold] -fn missing_method(type_name: &str, method: &str) -> String { - format!("type {type_name} has no method `{method}`") -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs deleted file mode 100644 index 0a3d6545..00000000 --- a/src/eval/mod.rs +++ /dev/null @@ -1,1242 +0,0 @@ -//! Evaluation of markup into modules. - -#[macro_use] -mod cast; -#[macro_use] -mod array; -#[macro_use] -mod dict; -#[macro_use] -mod str; -#[macro_use] -mod value; -mod args; -mod capture; -mod func; -pub mod methods; -pub mod ops; -mod raw; -mod scope; -mod vm; - -pub use self::str::*; -pub use args::*; -pub use array::*; -pub use capture::*; -pub use cast::*; -pub use dict::*; -pub use func::*; -pub use raw::*; -pub use scope::*; -pub use typst_macros::node; -pub use value::*; -pub use vm::*; - -use std::collections::BTreeMap; -use std::sync::Arc; - -use comemo::{Track, Tracked}; -use unicode_segmentation::UnicodeSegmentation; - -use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; -use crate::geom::{Angle, Em, Fraction, Length, Ratio}; -use crate::library; -use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap}; -use crate::source::SourceId; -use crate::syntax::ast::TypedNode; -use crate::syntax::{ast, Span, Spanned, Unit}; -use crate::util::EcoString; -use crate::World; - -/// Evaluate a source file and return the resulting module. -/// -/// Returns either a module containing a scope with top-level bindings and -/// layoutable contents or diagnostics in the form of a vector of error -/// messages with file and span information. -#[comemo::memoize] -pub fn eval( - world: Tracked<dyn World>, - route: Tracked<Route>, - id: SourceId, -) -> SourceResult<Module> { - // Prevent cyclic evaluation. - if route.contains(id) { - let path = world.source(id).path().display(); - panic!("Tried to cyclicly evaluate {}", path); - } - - // Evaluate the module. - let route = unsafe { Route::insert(route, id) }; - let ast = world.source(id).ast()?; - let std = &world.config().std; - let scopes = Scopes::new(Some(std)); - let mut vm = Vm::new(world, route.track(), Some(id), scopes); - let result = ast.eval(&mut vm); - - // Handle control flow. - if let Some(flow) = vm.flow { - bail!(flow.forbidden()); - } - - // Assemble the module. - Ok(Module { scope: vm.scopes.top, content: result? }) -} - -/// A route of source ids. -#[derive(Default)] -pub struct Route { - parent: Option<Tracked<'static, Self>>, - id: Option<SourceId>, -} - -impl Route { - /// Create a new, empty route. - pub fn new(id: Option<SourceId>) -> Self { - Self { id, parent: None } - } - - /// Insert a new id into the route. - /// - /// You must guarantee that `outer` lives longer than the resulting - /// route is ever used. - unsafe fn insert(outer: Tracked<Route>, id: SourceId) -> Route { - Route { - parent: Some(std::mem::transmute(outer)), - id: Some(id), - } - } -} - -#[comemo::track] -impl Route { - /// Whether the given id is part of the route. - fn contains(&self, id: SourceId) -> bool { - self.id == Some(id) || self.parent.map_or(false, |parent| parent.contains(id)) - } -} - -/// An evaluated module, ready for importing or layouting. -#[derive(Debug, Clone)] -pub struct Module { - /// The top-level definitions that were bound in this module. - pub scope: Scope, - /// The module's layoutable contents. - pub content: Content, -} - -/// Evaluate an expression. -pub trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output>; -} - -impl Eval for ast::Markup { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - eval_markup(vm, &mut self.children()) - } -} - -/// Evaluate a stream of markup nodes. -fn eval_markup( - vm: &mut Vm, - nodes: &mut impl Iterator<Item = ast::MarkupNode>, -) -> SourceResult<Content> { - let flow = vm.flow.take(); - let mut seq = Vec::with_capacity(nodes.size_hint().1.unwrap_or_default()); - - while let Some(node) = nodes.next() { - seq.push(match node { - ast::MarkupNode::Expr(ast::Expr::Set(set)) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - eval_markup(vm, nodes)?.styled_with_map(styles) - } - ast::MarkupNode::Expr(ast::Expr::Show(show)) => { - let recipe = show.eval(vm)?; - if vm.flow.is_some() { - break; - } - - eval_markup(vm, nodes)? - .styled_with_entry(StyleEntry::Recipe(recipe).into()) - } - ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => { - let tail = eval_markup(vm, nodes)?; - vm.scopes.top.define(wrap.binding().take(), tail); - wrap.body().eval(vm)?.display() - } - - _ => node.eval(vm)?, - }); - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(Content::sequence(seq)) -} - -impl Eval for ast::MarkupNode { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - match self { - Self::Space(v) => v.eval(vm), - Self::Linebreak(v) => v.eval(vm), - Self::Text(v) => v.eval(vm), - Self::Escape(v) => v.eval(vm), - Self::Shorthand(v) => v.eval(vm), - Self::SmartQuote(v) => v.eval(vm), - Self::Strong(v) => v.eval(vm), - Self::Emph(v) => v.eval(vm), - Self::Link(v) => v.eval(vm), - Self::Raw(v) => v.eval(vm), - Self::Math(v) => v.eval(vm), - Self::Heading(v) => v.eval(vm), - Self::List(v) => v.eval(vm), - Self::Enum(v) => v.eval(vm), - Self::Desc(v) => v.eval(vm), - Self::Label(v) => v.eval(vm), - Self::Ref(v) => v.eval(vm), - Self::Expr(v) => v.eval(vm).map(Value::display), - } - } -} - -impl Eval for ast::Space { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(if self.newlines() < 2 { - Content::Space - } else { - Content::Parbreak - }) - } -} - -impl Eval for ast::Linebreak { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Linebreak { justify: false }) - } -} - -impl Eval for ast::Text { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().clone())) - } -} - -impl Eval for ast::Escape { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().into())) - } -} - -impl Eval for ast::Shorthand { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Text(self.get().into())) - } -} - -impl Eval for ast::SmartQuote { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Quote { double: self.double() }) - } -} - -impl Eval for ast::Strong { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::show(library::text::StrongNode( - self.body().eval(vm)?, - ))) - } -} - -impl Eval for ast::Emph { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::show(library::text::EmphNode( - self.body().eval(vm)?, - ))) - } -} - -impl Eval for ast::Link { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::show(library::text::LinkNode::from_url( - self.url().clone(), - ))) - } -} - -impl Eval for ast::Raw { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - let content = Content::show(library::text::RawNode { - text: self.text().clone(), - block: self.block(), - }); - Ok(match self.lang() { - Some(_) => content.styled(library::text::RawNode::LANG, self.lang().cloned()), - None => content, - }) - } -} - -impl Eval for ast::Math { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let nodes = self - .children() - .map(|node| node.eval(vm)) - .collect::<SourceResult<_>>()?; - Ok(Content::show(library::math::MathNode::Row( - Arc::new(nodes), - self.span(), - ))) - } -} - -impl Eval for ast::MathNode { - type Output = library::math::MathNode; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(match self { - Self::Space(_) => library::math::MathNode::Space, - Self::Linebreak(_) => library::math::MathNode::Linebreak, - Self::Escape(c) => library::math::MathNode::Atom(c.get().into()), - Self::Atom(atom) => library::math::MathNode::Atom(atom.get().clone()), - Self::Script(node) => node.eval(vm)?, - Self::Frac(node) => node.eval(vm)?, - Self::Align(node) => node.eval(vm)?, - Self::Group(node) => library::math::MathNode::Row( - Arc::new( - node.children() - .map(|node| node.eval(vm)) - .collect::<SourceResult<_>>()?, - ), - node.span(), - ), - Self::Expr(expr) => match expr.eval(vm)?.display() { - Content::Text(text) => library::math::MathNode::Atom(text), - _ => bail!(expr.span(), "expected text"), - }, - }) - } -} - -impl Eval for ast::Script { - type Output = library::math::MathNode; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Script(Arc::new( - library::math::ScriptNode { - base: self.base().eval(vm)?, - sub: self - .sub() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - sup: self - .sup() - .map(|node| node.eval(vm)) - .transpose()? - .map(|node| node.unparen()), - }, - ))) - } -} - -impl Eval for ast::Frac { - type Output = library::math::MathNode; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Frac(Arc::new( - library::math::FracNode { - num: self.num().eval(vm)?.unparen(), - denom: self.denom().eval(vm)?.unparen(), - }, - ))) - } -} - -impl Eval for ast::Align { - type Output = library::math::MathNode; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(library::math::MathNode::Align(self.count())) - } -} - -impl Eval for ast::Heading { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::show(library::structure::HeadingNode { - body: self.body().eval(vm)?, - level: self.level(), - })) - } -} - -impl Eval for ast::ListItem { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let body = Box::new(self.body().eval(vm)?); - Ok(Content::Item(library::structure::ListItem::List(body))) - } -} - -impl Eval for ast::EnumItem { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let number = self.number(); - let body = Box::new(self.body().eval(vm)?); - Ok(Content::Item(library::structure::ListItem::Enum( - number, body, - ))) - } -} - -impl Eval for ast::DescItem { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let term = self.term().eval(vm)?; - let body = self.body().eval(vm)?; - Ok(Content::Item(library::structure::ListItem::Desc(Box::new( - library::structure::DescItem { term, body }, - )))) - } -} - -impl Eval for ast::Label { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::Empty) - } -} - -impl Eval for ast::Ref { - type Output = Content; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(Content::show(library::structure::RefNode( - self.get().clone(), - ))) - } -} - -impl Eval for ast::Expr { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let forbidden = |name| { - error!( - self.span(), - "{} is only allowed directly in code and content blocks", name - ) - }; - - match self { - Self::Lit(v) => v.eval(vm), - Self::Ident(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::MethodCall(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::Wrap(_) => bail!(forbidden("wrap")), - 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), - } - } -} - -impl Eval for ast::Lit { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { - Ok(match self.kind() { - ast::LitKind::None => Value::None, - ast::LitKind::Auto => Value::Auto, - ast::LitKind::Bool(v) => Value::Bool(v), - ast::LitKind::Int(v) => Value::Int(v), - ast::LitKind::Float(v) => Value::Float(v), - ast::LitKind::Numeric(v, unit) => match unit { - Unit::Length(unit) => Length::with_unit(v, unit).into(), - Unit::Angle(unit) => Angle::with_unit(v, unit).into(), - Unit::Em => Em::new(v).into(), - Unit::Fr => Fraction::new(v).into(), - Unit::Percent => Ratio::new(v / 100.0).into(), - }, - ast::LitKind::Str(v) => Value::Str(v.into()), - }) - } -} - -impl Eval for ast::Ident { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.get(self).cloned().at(self.span()) - } -} - -impl Eval for ast::CodeBlock { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let output = eval_code(vm, &mut self.exprs())?; - vm.scopes.exit(); - Ok(output) - } -} - -/// Evaluate a stream of expressions. -fn eval_code( - vm: &mut Vm, - exprs: &mut impl Iterator<Item = ast::Expr>, -) -> SourceResult<Value> { - let flow = vm.flow.take(); - let mut output = Value::None; - - while let Some(expr) = exprs.next() { - let span = expr.span(); - let value = match expr { - ast::Expr::Set(set) => { - let styles = set.eval(vm)?; - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_map(styles)) - } - ast::Expr::Show(show) => { - let recipe = show.eval(vm)?; - let entry = StyleEntry::Recipe(recipe).into(); - if vm.flow.is_some() { - break; - } - - let tail = eval_code(vm, exprs)?.display(); - Value::Content(tail.styled_with_entry(entry)) - } - ast::Expr::Wrap(wrap) => { - let tail = eval_code(vm, exprs)?; - vm.scopes.top.define(wrap.binding().take(), tail); - wrap.body().eval(vm)? - } - - _ => expr.eval(vm)?, - }; - - output = ops::join(output, value).at(span)?; - - if vm.flow.is_some() { - break; - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) -} - -impl Eval for ast::ContentBlock { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - vm.scopes.enter(); - let content = self.body().eval(vm)?; - vm.scopes.exit(); - Ok(content) - } -} - -impl Eval for ast::Parenthesized { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - self.expr().eval(vm) - } -} - -impl Eval for ast::Array { - type Output = Array; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let items = self.items(); - - let mut vec = Vec::with_capacity(items.size_hint().0); - for item in items { - match item { - ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?), - ast::ArrayItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => vec.extend(array.into_iter()), - v => bail!(expr.span(), "cannot spread {} into array", v.type_name()), - }, - } - } - - Ok(Array::from_vec(vec)) - } -} - -impl Eval for ast::Dict { - type Output = Dict; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut map = BTreeMap::new(); - - for item in self.items() { - match item { - ast::DictItem::Named(named) => { - map.insert(named.name().take().into(), named.expr().eval(vm)?); - } - ast::DictItem::Keyed(keyed) => { - map.insert(keyed.key().into(), keyed.expr().eval(vm)?); - } - ast::DictItem::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Dict(dict) => map.extend(dict.into_iter()), - v => bail!( - expr.span(), - "cannot spread {} into dictionary", - v.type_name() - ), - }, - } - } - - Ok(Dict::from_map(map)) - } -} - -impl Eval for ast::Unary { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.expr().eval(vm)?; - let result = match self.op() { - ast::UnOp::Pos => ops::pos(value), - ast::UnOp::Neg => ops::neg(value), - ast::UnOp::Not => ops::not(value), - }; - Ok(result.at(self.span())?) - } -} - -impl Eval for ast::Binary { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - match self.op() { - ast::BinOp::Add => self.apply(vm, ops::add), - ast::BinOp::Sub => self.apply(vm, ops::sub), - ast::BinOp::Mul => self.apply(vm, ops::mul), - ast::BinOp::Div => self.apply(vm, ops::div), - ast::BinOp::And => self.apply(vm, ops::and), - ast::BinOp::Or => self.apply(vm, ops::or), - ast::BinOp::Eq => self.apply(vm, ops::eq), - ast::BinOp::Neq => self.apply(vm, ops::neq), - ast::BinOp::Lt => self.apply(vm, ops::lt), - ast::BinOp::Leq => self.apply(vm, ops::leq), - ast::BinOp::Gt => self.apply(vm, ops::gt), - ast::BinOp::Geq => self.apply(vm, ops::geq), - ast::BinOp::In => self.apply(vm, ops::in_), - ast::BinOp::NotIn => self.apply(vm, ops::not_in), - ast::BinOp::Assign => self.assign(vm, |_, b| Ok(b)), - ast::BinOp::AddAssign => self.assign(vm, ops::add), - ast::BinOp::SubAssign => self.assign(vm, ops::sub), - ast::BinOp::MulAssign => self.assign(vm, ops::mul), - ast::BinOp::DivAssign => self.assign(vm, ops::div), - } - } -} - -impl ast::Binary { - /// Apply a basic binary operation. - fn apply( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let lhs = self.lhs().eval(vm)?; - - // Short-circuit boolean operations. - if (self.op() == ast::BinOp::And && lhs == Value::Bool(false)) - || (self.op() == ast::BinOp::Or && lhs == Value::Bool(true)) - { - return Ok(lhs); - } - - let rhs = self.rhs().eval(vm)?; - Ok(op(lhs, rhs).at(self.span())?) - } - - /// Apply an assignment operation. - fn assign( - &self, - vm: &mut Vm, - op: fn(Value, Value) -> StrResult<Value>, - ) -> SourceResult<Value> { - let rhs = self.rhs().eval(vm)?; - let location = self.lhs().access(vm)?; - let lhs = std::mem::take(&mut *location); - *location = op(lhs, rhs).at(self.span())?; - Ok(Value::None) - } -} - -impl Eval for ast::FieldAccess { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let object = self.target().eval(vm)?; - let span = self.field().span(); - let field = self.field().take(); - - Ok(match object { - Value::Dict(dict) => dict.get(&field).at(span)?.clone(), - - Value::Content(Content::Show(_, Some(dict))) => dict - .get(&field) - .map_err(|_| format!("unknown field {field:?}")) - .at(span)? - .clone(), - - v => bail!( - self.target().span(), - "cannot access field on {}", - v.type_name() - ), - }) - } -} - -impl Eval for ast::FuncCall { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let callee = self.callee().eval(vm)?; - let args = self.args().eval(vm)?; - - Ok(match callee { - Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(), - Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(), - Value::Func(func) => { - let point = || Tracepoint::Call(func.name().map(Into::into)); - func.call(vm, args).trace(vm.world, point, self.span())? - } - - v => bail!( - self.callee().span(), - "expected callable or collection, found {}", - v.type_name(), - ), - }) - } -} - -impl Eval for ast::MethodCall { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.span(); - let method = self.method().take(); - let point = || Tracepoint::Call(Some(method.clone())); - - Ok(if methods::is_mutating(&method) { - let args = self.args().eval(vm)?; - let mut value = self.target().access(vm)?; - methods::call_mut(&mut value, &method, args, span) - .trace(vm.world, point, span)?; - Value::None - } else { - let value = self.target().eval(vm)?; - let args = self.args().eval(vm)?; - methods::call(vm, value, &method, args, span).trace(vm.world, point, span)? - }) - } -} - -impl Eval for ast::Args { - type Output = Args; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let mut items = Vec::new(); - - for arg in self.items() { - let span = arg.span(); - match arg { - ast::Arg::Pos(expr) => { - items.push(Arg { - span, - name: None, - value: Spanned::new(expr.eval(vm)?, expr.span()), - }); - } - ast::Arg::Named(named) => { - items.push(Arg { - span, - name: Some(named.name().take().into()), - value: Spanned::new(named.expr().eval(vm)?, named.expr().span()), - }); - } - ast::Arg::Spread(expr) => match expr.eval(vm)? { - Value::None => {} - Value::Array(array) => { - items.extend(array.into_iter().map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - })); - } - Value::Dict(dict) => { - items.extend(dict.into_iter().map(|(key, value)| Arg { - span, - name: Some(key), - value: Spanned::new(value, span), - })); - } - Value::Args(args) => items.extend(args.items), - v => bail!(expr.span(), "cannot spread {}", v.type_name()), - }, - } - } - - Ok(Args { span: self.span(), items }) - } -} - -impl Eval for ast::Closure { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - // The closure's name is defined by its let binding if there's one. - let name = self.name().map(ast::Ident::take); - - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; - - let mut params = Vec::new(); - let mut sink = None; - - // Collect parameters and an optional sink parameter. - for param in self.params() { - match param { - ast::Param::Pos(name) => { - params.push((name.take(), None)); - } - ast::Param::Named(named) => { - params.push((named.name().take(), Some(named.expr().eval(vm)?))); - } - ast::Param::Sink(name) => { - if sink.is_some() { - bail!(name.span(), "only one argument sink is allowed"); - } - sink = Some(name.take()); - } - } - } - - // Define the actual function. - Ok(Value::Func(Func::from_closure(Closure { - location: vm.location, - name, - captured, - params, - sink, - body: self.body(), - }))) - } -} - -impl Eval for ast::LetBinding { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = match self.init() { - Some(expr) => expr.eval(vm)?, - None => Value::None, - }; - vm.scopes.top.define(self.binding().take(), value); - Ok(Value::None) - } -} - -impl Eval for ast::SetRule { - type Output = StyleMap; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let target = self.target(); - let target = target.eval(vm)?.cast::<Func>().at(target.span())?; - let args = self.args().eval(vm)?; - Ok(target.set(args)?) - } -} - -impl Eval for ast::ShowRule { - type Output = Recipe; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - // Evaluate the target function. - let pattern = self.pattern(); - let pattern = pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())?; - - // Collect captured variables. - let captured = { - let mut visitor = CapturesVisitor::new(&vm.scopes); - visitor.visit(self.as_untyped()); - visitor.finish() - }; - - // Define parameters. - let mut params = vec![]; - if let Some(binding) = self.binding() { - params.push((binding.take(), None)); - } - - // Define the recipe function. - let body = self.body(); - let span = body.span(); - let func = Func::from_closure(Closure { - location: vm.location, - name: None, - captured, - params, - sink: None, - body, - }); - - Ok(Recipe { pattern, func: Spanned::new(func, span) }) - } -} - -impl Eval for ast::Conditional { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let condition = self.condition(); - if condition.eval(vm)?.cast::<bool>().at(condition.span())? { - self.if_body().eval(vm) - } else if let Some(else_body) = self.else_body() { - else_body.eval(vm) - } else { - Ok(Value::None) - } - } -} - -impl Eval for ast::WhileLoop { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - - let condition = self.condition(); - while condition.eval(vm)?.cast::<bool>().at(condition.span())? { - 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 => {} - } - } - - if flow.is_some() { - vm.flow = flow; - } - - Ok(output) - } -} - -impl Eval for ast::ForLoop { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let flow = vm.flow.take(); - let mut output = Value::None; - vm.scopes.enter(); - - macro_rules! iter { - (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ - #[allow(unused_parens)] - for ($($value),*) in $iter { - $(vm.scopes.top.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 => {} - } - } - - }}; - } - - let iter = self.iter().eval(vm)?; - let pattern = self.pattern(); - let key = pattern.key().map(ast::Ident::take); - let value = pattern.value().take(); - - 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; - } - - vm.scopes.exit(); - Ok(output) - } -} - -impl Eval for ast::ModuleImport { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm, &path, span)?; - - match self.imports() { - ast::Imports::Wildcard => { - for (var, value) in module.scope.iter() { - vm.scopes.top.define(var, value.clone()); - } - } - ast::Imports::Items(idents) => { - for ident in idents { - if let Some(value) = module.scope.get(&ident) { - vm.scopes.top.define(ident.take(), value.clone()); - } else { - bail!(ident.span(), "unresolved import"); - } - } - } - } - - Ok(Value::None) - } -} - -impl Eval for ast::ModuleInclude { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?; - let module = import(vm, &path, span)?; - Ok(module.content.clone()) - } -} - -/// Process an import of a module relative to the current location. -fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> { - // 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 module = - eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?; - - Ok(module) -} - -impl Eval for ast::BreakStmt { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(Flow::Break(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::ContinueStmt { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - if vm.flow.is_none() { - vm.flow = Some(Flow::Continue(self.span())); - } - Ok(Value::None) - } -} - -impl Eval for ast::ReturnStmt { - type Output = Value; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = self.body().map(|body| body.eval(vm)).transpose()?; - if vm.flow.is_none() { - vm.flow = Some(Flow::Return(self.span(), value)); - } - Ok(Value::None) - } -} - -/// Access an expression mutably. -pub 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::FieldAccess(v) => v.access(vm), - Self::FuncCall(v) => v.access(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> { - vm.scopes.get_mut(self).at(self.span()) - } -} - -impl Access for ast::FieldAccess { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - Ok(match self.target().access(vm)? { - Value::Dict(dict) => dict.get_mut(self.field().take().into()), - v => bail!( - self.target().span(), - "expected dictionary, found {}", - v.type_name(), - ), - }) - } -} - -impl Access for ast::FuncCall { - fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - let args = self.args().eval(vm)?; - Ok(match self.callee().access(vm)? { - Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?, - Value::Dict(dict) => dict.get_mut(args.into_key()?), - v => bail!( - self.callee().span(), - "expected collection, found {}", - v.type_name(), - ), - }) - } -} diff --git a/src/eval/ops.rs b/src/eval/ops.rs deleted file mode 100644 index 7e465320..00000000 --- a/src/eval/ops.rs +++ /dev/null @@ -1,399 +0,0 @@ -//! Operations on values. - -use std::cmp::Ordering; - -use super::{RawAlign, RawLength, RawStroke, Regex, Smart, Value}; -use crate::diag::StrResult; -use crate::geom::{Numeric, Relative, Spec, SpecAxis}; -use crate::model; -use Value::*; - -/// Bail with a type mismatch error. -macro_rules! mismatch { - ($fmt:expr, $($value:expr),* $(,)?) => { - return Err(format!($fmt, $($value.type_name()),*)) - }; -} - -/// Join a value with another value. -pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(model::Content::Text(a.into()) + b), - (Content(a), Str(b)) => Content(a + model::Content::Text(b.into())), - (Content(a), Content(b)) => Content(a + b), - (Array(a), Array(b)) => Array(a + b), - (Dict(a), Dict(b)) => Dict(a + b), - (a, b) => mismatch!("cannot join {} with {}", a, b), - }) -} - -/// Apply the unary plus operator to a value. -pub fn pos(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(v), - Float(v) => Float(v), - Length(v) => Length(v), - Angle(v) => Angle(v), - Ratio(v) => Ratio(v), - Relative(v) => Relative(v), - Fraction(v) => Fraction(v), - v => mismatch!("cannot apply '+' to {}", v), - }) -} - -/// Compute the negation of a value. -pub fn neg(value: Value) -> StrResult<Value> { - Ok(match value { - Int(v) => Int(-v), - Float(v) => Float(-v), - Length(v) => Length(-v), - Angle(v) => Angle(-v), - Ratio(v) => Ratio(-v), - Relative(v) => Relative(-v), - Fraction(v) => Fraction(-v), - v => mismatch!("cannot apply '-' to {}", v), - }) -} - -/// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (a, None) => a, - (None, b) => b, - - (Int(a), Int(b)) => Int(a + b), - (Int(a), Float(b)) => Float(a as f64 + b), - (Float(a), Int(b)) => Float(a + b as f64), - (Float(a), Float(b)) => Float(a + b), - - (Angle(a), Angle(b)) => Angle(a + b), - - (Length(a), Length(b)) => Length(a + b), - (Length(a), Ratio(b)) => Relative(b + a), - (Length(a), Relative(b)) => Relative(b + a), - - (Ratio(a), Length(b)) => Relative(a + b), - (Ratio(a), Ratio(b)) => Ratio(a + b), - (Ratio(a), Relative(b)) => Relative(b + a), - - (Relative(a), Length(b)) => Relative(a + b), - (Relative(a), Ratio(b)) => Relative(a + b), - (Relative(a), Relative(b)) => Relative(a + b), - - (Fraction(a), Fraction(b)) => Fraction(a + b), - - (Str(a), Str(b)) => Str(a + b), - (Content(a), Content(b)) => Content(a + b), - (Content(a), Str(b)) => Content(a + model::Content::Text(b.into())), - (Str(a), Content(b)) => Content(model::Content::Text(a.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(RawStroke { - 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::<RawAlign>(), b.downcast::<RawAlign>()) - { - if a.axis() != b.axis() { - Value::dynamic(match a.axis() { - SpecAxis::Horizontal => Spec { x: a, y: b }, - SpecAxis::Vertical => Spec { x: b, y: a }, - }) - } else { - return Err(format!("cannot add two {:?} alignments", a.axis())); - } - } else { - mismatch!("cannot add {} and {}", a, b); - } - } - - (a, b) => mismatch!("cannot add {} and {}", a, b), - }) -} - -/// Compute the difference of two values. -pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a - b), - (Int(a), Float(b)) => Float(a as f64 - b), - (Float(a), Int(b)) => Float(a - b as f64), - (Float(a), Float(b)) => Float(a - b), - - (Angle(a), Angle(b)) => Angle(a - b), - - (Length(a), Length(b)) => Length(a - b), - (Length(a), Ratio(b)) => Relative(-b + a), - (Length(a), Relative(b)) => Relative(-b + a), - - (Ratio(a), Length(b)) => Relative(a + -b), - (Ratio(a), Ratio(b)) => Ratio(a - b), - (Ratio(a), Relative(b)) => Relative(-b + a), - - (Relative(a), Length(b)) => Relative(a + -b), - (Relative(a), Ratio(b)) => Relative(a + -b), - (Relative(a), Relative(b)) => Relative(a - b), - - (Fraction(a), Fraction(b)) => Fraction(a - b), - - (a, b) => mismatch!("cannot subtract {1} from {0}", a, b), - }) -} - -/// Compute the product of two values. -pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(match (lhs, rhs) { - (Int(a), Int(b)) => Int(a * b), - (Int(a), Float(b)) => Float(a as f64 * b), - (Float(a), Int(b)) => Float(a * b as f64), - (Float(a), Float(b)) => Float(a * b), - - (Length(a), Int(b)) => Length(a * b as f64), - (Length(a), Float(b)) => Length(a * b), - (Int(a), Length(b)) => Length(b * a as f64), - (Float(a), Length(b)) => Length(b * a), - - (Angle(a), Int(b)) => Angle(a * b as f64), - (Angle(a), Float(b)) => Angle(a * b), - (Int(a), Angle(b)) => Angle(a as f64 * b), - (Float(a), Angle(b)) => Angle(a * b), - - (Ratio(a), Int(b)) => Ratio(a * b as f64), - (Ratio(a), Float(b)) => Ratio(a * b), - (Float(a), Ratio(b)) => Ratio(a * b), - (Int(a), Ratio(b)) => Ratio(a as f64 * b), - - (Relative(a), Int(b)) => Relative(a * b as f64), - (Relative(a), Float(b)) => Relative(a * b), - (Int(a), Relative(b)) => Relative(a as f64 * b), - (Float(a), Relative(b)) => Relative(a * b), - - (Float(a), Fraction(b)) => Fraction(a * b), - (Fraction(a), Int(b)) => Fraction(a * b as f64), - (Fraction(a), Float(b)) => Fraction(a * b), - (Int(a), Fraction(b)) => Fraction(a as f64 * b), - - (Str(a), Int(b)) => Str(a.repeat(b)?), - (Int(a), Str(b)) => Str(b.repeat(a)?), - (Array(a), Int(b)) => Array(a.repeat(b)?), - (Int(a), Array(b)) => Array(b.repeat(a)?), - (Content(a), Int(b)) => Content(a.repeat(b)?), - (Int(a), Content(b)) => Content(b.repeat(a)?), - - (a, b) => mismatch!("cannot multiply {} with {}", a, b), - }) -} - -/// Compute the quotient of two values. -pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { - 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(div_length(a, b)?), - (Length(a), Relative(b)) if b.rel.is_zero() => Float(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(div_length(a.abs, b)?), - (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b), - (Relative(a), Relative(b)) => Float(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), - }) -} - -/// Try to divide two lengths. -fn div_length(a: RawLength, b: RawLength) -> StrResult<f64> { - if a.length.is_zero() && b.length.is_zero() { - Ok(a.em / b.em) - } else if a.em.is_zero() && b.em.is_zero() { - Ok(a.length / b.length) - } else { - return Err("cannot divide these two lengths".into()); - } -} - -/// Try to divide two relative lengths. -fn div_relative(a: Relative<RawLength>, b: Relative<RawLength>) -> StrResult<f64> { - if a.rel.is_zero() && b.rel.is_zero() { - div_length(a.abs, b.abs) - } else if a.abs.is_zero() && b.abs.is_zero() { - Ok(a.rel / b.rel) - } else { - return Err("cannot divide these two relative lengths".into()); - } -} - -/// Compute the logical "not" of a value. -pub fn not(value: Value) -> StrResult<Value> { - match value { - Bool(b) => Ok(Bool(!b)), - v => mismatch!("cannot apply 'not' to {}", v), - } -} - -/// Compute the logical "and" of two values. -pub fn and(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a && b)), - (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), - } -} - -/// Compute the logical "or" of two values. -pub fn or(lhs: Value, rhs: Value) -> StrResult<Value> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => Ok(Bool(a || b)), - (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), - } -} - -/// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(equal(&lhs, &rhs))) -} - -/// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> { - Ok(Bool(!equal(&lhs, &rhs))) -} - -macro_rules! comparison { - ($name:ident, $op:tt, $($pat:tt)*) => { - /// Compute how a value compares with another value. - pub fn $name(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(ordering) = compare(&lhs, &rhs) { - Ok(Bool(matches!(ordering, $($pat)*))) - } else { - mismatch!(concat!("cannot apply '", $op, "' to {} and {}"), lhs, rhs); - } - } - }; -} - -comparison!(lt, "<", Ordering::Less); -comparison!(leq, "<=", Ordering::Less | Ordering::Equal); -comparison!(gt, ">", Ordering::Greater); -comparison!(geq, ">=", Ordering::Greater | Ordering::Equal); - -/// Determine whether two values are equal. -pub fn equal(lhs: &Value, rhs: &Value) -> bool { - match (lhs, rhs) { - // Compare reflexively. - (None, None) => true, - (Auto, Auto) => true, - (Bool(a), Bool(b)) => a == b, - (Int(a), Int(b)) => a == b, - (Float(a), Float(b)) => a == b, - (Length(a), Length(b)) => a == b, - (Angle(a), Angle(b)) => a == b, - (Ratio(a), Ratio(b)) => a == b, - (Relative(a), Relative(b)) => a == b, - (Fraction(a), Fraction(b)) => a == b, - (Color(a), Color(b)) => a == b, - (Str(a), Str(b)) => a == b, - (Content(a), Content(b)) => a == b, - (Array(a), Array(b)) => a == b, - (Dict(a), Dict(b)) => a == b, - (Func(a), Func(b)) => a == b, - (Dyn(a), Dyn(b)) => a == b, - - // Some technically different things should compare equal. - (&Int(a), &Float(b)) => a as f64 == b, - (&Float(a), &Int(b)) => a == b as f64, - (&Length(a), &Relative(b)) => a == b.abs && b.rel.is_zero(), - (&Ratio(a), &Relative(b)) => a == b.rel && b.abs.is_zero(), - (&Relative(a), &Length(b)) => a.abs == b && a.rel.is_zero(), - (&Relative(a), &Ratio(b)) => a.rel == b && a.abs.is_zero(), - - _ => false, - } -} - -/// Compare two values. -pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> { - match (lhs, rhs) { - (Bool(a), Bool(b)) => a.partial_cmp(b), - (Int(a), Int(b)) => a.partial_cmp(b), - (Float(a), Float(b)) => a.partial_cmp(b), - (Length(a), Length(b)) => a.partial_cmp(b), - (Angle(a), Angle(b)) => a.partial_cmp(b), - (Ratio(a), Ratio(b)) => a.partial_cmp(b), - (Relative(a), Relative(b)) => a.partial_cmp(b), - (Fraction(a), Fraction(b)) => a.partial_cmp(b), - (Str(a), Str(b)) => a.partial_cmp(b), - - // Some technically different things should be comparable. - (&Int(a), &Float(b)) => (a as f64).partial_cmp(&b), - (&Float(a), &Int(b)) => a.partial_cmp(&(b as f64)), - (&Length(a), &Relative(b)) if b.rel.is_zero() => a.partial_cmp(&b.abs), - (&Ratio(a), &Relative(b)) if b.abs.is_zero() => a.partial_cmp(&b.rel), - (&Relative(a), &Length(b)) if a.rel.is_zero() => a.abs.partial_cmp(&b), - (&Relative(a), &Ratio(b)) if a.abs.is_zero() => a.rel.partial_cmp(&b), - - _ => Option::None, - } -} - -/// Test whether one value is "in" another one. -pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(b)) - } else { - mismatch!("cannot apply 'in' to {} and {}", lhs, rhs) - } -} - -/// Test whether one value is "not in" another one. -pub fn not_in(lhs: Value, rhs: Value) -> StrResult<Value> { - if let Some(b) = contains(&lhs, &rhs) { - Ok(Bool(!b)) - } else { - mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs) - } -} - -/// Test for containment. -pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> { - Some(match (lhs, rhs) { - (Str(a), Str(b)) => b.as_str().contains(a.as_str()), - (Dyn(a), Str(b)) => { - if let Some(regex) = a.downcast::<Regex>() { - regex.is_match(b) - } else { - return Option::None; - } - } - (Str(a), Dict(b)) => b.contains(a), - (a, Array(b)) => b.contains(a), - _ => return Option::None, - }) -} diff --git a/src/eval/raw.rs b/src/eval/raw.rs deleted file mode 100644 index 9cf346b1..00000000 --- a/src/eval/raw.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::ops::{Add, Div, Mul, Neg}; - -use super::{Smart, Value}; -use crate::geom::{ - Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke, -}; -use crate::library::text::TextNode; -use crate::model::{Fold, Resolve, StyleChain}; - -/// The unresolved alignment representation. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum RawAlign { - /// Align at the start side of the text direction. - Start, - /// Align at the end side of the text direction. - End, - /// Align at a specific alignment. - Specific(Align), -} - -impl Resolve for RawAlign { - type Output = Align; - - fn resolve(self, styles: StyleChain) -> Self::Output { - let dir = styles.get(TextNode::DIR); - match self { - Self::Start => dir.start().into(), - Self::End => dir.end().into(), - Self::Specific(align) => align, - } - } -} - -impl RawAlign { - /// The axis this alignment belongs to. - pub const fn axis(self) -> SpecAxis { - match self { - Self::Start | Self::End => SpecAxis::Horizontal, - Self::Specific(align) => align.axis(), - } - } -} - -impl From<Align> for RawAlign { - fn from(align: Align) -> Self { - Self::Specific(align) - } -} - -impl Debug for RawAlign { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Start => f.pad("left"), - Self::End => f.pad("center"), - Self::Specific(align) => align.fmt(f), - } - } -} - -dynamic! { - RawAlign: "alignment", -} - -dynamic! { - Spec<RawAlign>: "2d alignment", -} - -castable! { - Spec<Option<RawAlign>>, - Expected: "1d or 2d alignment", - @align: RawAlign => { - let mut aligns = Spec::default(); - aligns.set(align.axis(), Some(*align)); - aligns - }, - @aligns: Spec<RawAlign> => aligns.map(Some), -} - -/// The unresolved stroke representation. -/// -/// In this representation, both fields are optional so that you can pass either -/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where -/// this is expected. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct RawStroke<T = RawLength> { - /// The stroke's paint. - pub paint: Smart<Paint>, - /// The stroke's thickness. - pub thickness: Smart<T>, -} - -impl RawStroke<Length> { - /// Unpack the stroke, filling missing fields from the `default`. - pub fn unwrap_or(self, default: Stroke) -> Stroke { - Stroke { - paint: self.paint.unwrap_or(default.paint), - thickness: self.thickness.unwrap_or(default.thickness), - } - } - - /// Unpack the stroke, filling missing fields with the default values. - pub fn unwrap_or_default(self) -> Stroke { - self.unwrap_or(Stroke::default()) - } -} - -impl Resolve for RawStroke { - type Output = RawStroke<Length>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - RawStroke { - paint: self.paint, - thickness: self.thickness.resolve(styles), - } - } -} - -impl Fold for RawStroke<Length> { - type Output = Self; - - fn fold(self, outer: Self::Output) -> Self::Output { - Self { - paint: self.paint.or(outer.paint), - thickness: self.thickness.or(outer.thickness), - } - } -} - -impl<T: Debug> Debug for RawStroke<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.paint, &self.thickness) { - (Smart::Custom(paint), Smart::Custom(thickness)) => { - write!(f, "{thickness:?} + {paint:?}") - } - (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), - (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), - (Smart::Auto, Smart::Auto) => f.pad("<stroke>"), - } - } -} - -dynamic! { - RawStroke: "stroke", - Value::Length(thickness) => Self { - paint: Smart::Auto, - thickness: Smart::Custom(thickness), - }, - Value::Color(color) => Self { - paint: Smart::Custom(color.into()), - thickness: Smart::Auto, - }, -} - -/// The unresolved length representation. -/// -/// Currently supports absolute and em units, but support could quite easily be -/// extended to other units that can be resolved through a style chain. -/// Probably, it would be a good idea to then move to an enum representation -/// that has a small footprint and allocates for the rare case that units are -/// mixed. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct RawLength { - /// The absolute part. - pub length: Length, - /// The font-relative part. - pub em: Em, -} - -impl RawLength { - /// The zero length. - pub const fn zero() -> Self { - Self { length: Length::zero(), em: Em::zero() } - } -} - -impl Debug for RawLength { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.length.is_zero(), self.em.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.length, self.em), - (true, false) => self.em.fmt(f), - (_, true) => self.length.fmt(f), - } - } -} - -impl Resolve for Em { - type Output = Length; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Length::zero() - } else { - self.at(styles.get(TextNode::SIZE)) - } - } -} - -impl Resolve for RawLength { - type Output = Length; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.length + self.em.resolve(styles) - } -} - -impl Numeric for RawLength { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.length.is_finite() && self.em.is_finite() - } -} - -impl PartialOrd for RawLength { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.em.is_zero() && other.em.is_zero() { - self.length.partial_cmp(&other.length) - } else if self.length.is_zero() && other.length.is_zero() { - self.em.partial_cmp(&other.em) - } else { - None - } - } -} - -impl From<Length> for RawLength { - fn from(length: Length) -> Self { - Self { length, em: Em::zero() } - } -} - -impl From<Em> for RawLength { - fn from(em: Em) -> Self { - Self { length: Length::zero(), em } - } -} - -impl From<Length> for Relative<RawLength> { - fn from(length: Length) -> Self { - Relative::from(RawLength::from(length)) - } -} - -impl Neg for RawLength { - type Output = Self; - - fn neg(self) -> Self::Output { - Self { length: -self.length, em: -self.em } - } -} - -impl Add for RawLength { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { - length: self.length + rhs.length, - em: self.em + rhs.em, - } - } -} - -sub_impl!(RawLength - RawLength -> RawLength); - -impl Mul<f64> for RawLength { - type Output = Self; - - fn mul(self, rhs: f64) -> Self::Output { - Self { - length: self.length * rhs, - em: self.em * rhs, - } - } -} - -impl Div<f64> for RawLength { - type Output = Self; - - fn div(self, rhs: f64) -> Self::Output { - Self { - length: self.length / rhs, - em: self.em / rhs, - } - } -} - -assign_impl!(RawLength += RawLength); -assign_impl!(RawLength -= RawLength); -assign_impl!(RawLength *= f64); -assign_impl!(RawLength /= f64); diff --git a/src/eval/scope.rs b/src/eval/scope.rs deleted file mode 100644 index 1ab7032c..00000000 --- a/src/eval/scope.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; - -use super::{Args, Func, Node, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; -use crate::util::EcoString; - -/// A stack of scopes. -#[derive(Debug, Default, Clone)] -pub struct Scopes<'a> { - /// The active scope. - pub top: Scope, - /// The stack of lower scopes. - pub scopes: Vec<Scope>, - /// The base scope. - pub base: Option<&'a Scope>, -} - -impl<'a> Scopes<'a> { - /// Create a new, empty hierarchy of scopes. - pub fn new(base: Option<&'a Scope>) -> 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.into_iter()) - .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(|| { - if self.base.map_or(false, |base| base.get(var).is_some()) { - "cannot mutate a constant" - } else { - "unknown variable" - } - })? - } -} - -/// A map from binding names to values. -#[derive(Default, Clone, Hash)] -pub struct Scope(BTreeMap<EcoString, Slot>); - -impl Scope { - /// Create a new empty scope. - pub fn new() -> Self { - Self::default() - } - - /// Bind a value to a name. - pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { - self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal)); - } - - /// Define a function through a native rust function. - pub fn def_fn( - &mut self, - name: &'static str, - func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, - ) { - self.define(name, Func::from_fn(name, func)); - } - - /// Define a function through a native rust node. - pub fn def_node<T: Node>(&mut self, name: &'static str) { - self.define(name, Func::from_node::<T>(name)); - } - - /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into<EcoString>, - value: impl Into<Value>, - ) { - self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured)); - } - - /// Try to access a variable immutably. - pub fn get(&self, var: &str) -> Option<&Value> { - self.0.get(var).map(Slot::read) - } - - /// Try to access a variable mutably. - pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> { - self.0.get_mut(var).map(Slot::write) - } - - /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> { - self.0.iter().map(|(k, v)| (k.as_str(), 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 deleted file mode 100644 index 9d2375d3..00000000 --- a/src/eval/str.rs +++ /dev/null @@ -1,475 +0,0 @@ -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, AddAssign, Deref}; - -use unicode_segmentation::UnicodeSegmentation; - -use super::{Array, Dict, RawAlign, Value}; -use crate::diag::StrResult; -use crate::util::EcoString; - -/// Create a new [`Str`] from a format string. -#[allow(unused_macros)] -macro_rules! format_str { - ($($tts:tt)*) => {{ - $crate::eval::Str::from(format_eco!($($tts)*)) - }}; -} - -/// 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 - } - - /// The codepoints the string consists of. - pub fn codepoints(&self) -> Array { - self.as_str().chars().map(|c| Value::Str(c.into())).collect() - } - - /// The grapheme clusters the string consists of. - pub fn graphemes(&self) -> Array { - self.as_str().graphemes(true).map(|s| Value::Str(s.into())).collect() - } - - /// Extract a contigous substring. - pub fn slice(&self, start: i64, end: Option<i64>) -> StrResult<Self> { - let len = self.len(); - let start = self - .locate(start) - .filter(|&start| start <= self.0.len()) - .ok_or_else(|| out_of_bounds(start, len))?; - - let end = end.unwrap_or(self.len()); - let end = self - .locate(end) - .filter(|&end| end <= self.0.len()) - .ok_or_else(|| out_of_bounds(end, len))? - .max(start); - - Ok(self.0[start .. end].into()) - } - - /// Resolve an index. - fn locate(&self, index: i64) -> Option<usize> { - usize::try_from(if index >= 0 { - index - } else { - self.len().checked_add(index)? - }) - .ok() - } - - /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: TextPattern) -> bool { - match pattern { - TextPattern::Str(pat) => self.0.contains(pat.as_str()), - TextPattern::Regex(re) => re.is_match(self), - } - } - - /// Whether this string begins with the given pattern. - pub fn starts_with(&self, pattern: TextPattern) -> bool { - match pattern { - TextPattern::Str(pat) => self.0.starts_with(pat.as_str()), - TextPattern::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: TextPattern) -> bool { - match pattern { - TextPattern::Str(pat) => self.0.ends_with(pat.as_str()), - TextPattern::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: TextPattern) -> Option<Self> { - match pattern { - TextPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), - TextPattern::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: TextPattern) -> Option<i64> { - match pattern { - TextPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), - TextPattern::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: TextPattern) -> Option<Dict> { - match pattern { - TextPattern::Str(pat) => { - self.0.match_indices(pat.as_str()).next().map(match_to_dict) - } - TextPattern::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: TextPattern) -> Array { - match pattern { - TextPattern::Str(pat) => self - .0 - .match_indices(pat.as_str()) - .map(match_to_dict) - .map(Value::Dict) - .collect(), - TextPattern::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<TextPattern>) -> Array { - let s = self.as_str(); - match pattern { - None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), - Some(TextPattern::Str(pat)) => { - s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() - } - Some(TextPattern::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<TextPattern>, - at: Option<TextSide>, - repeat: bool, - ) -> Self { - let mut start = matches!(at, Some(TextSide::Start) | None); - let end = matches!(at, Some(TextSide::End) | None); - - let trimmed = match pattern { - None => match at { - None => self.0.trim(), - Some(TextSide::Start) => self.0.trim_start(), - Some(TextSide::End) => self.0.trim_end(), - }, - Some(TextPattern::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(TextPattern::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: TextPattern, - with: Self, - count: Option<usize>, - ) -> Self { - match pattern { - TextPattern::Str(pat) => match count { - Some(n) => self.0.replacen(pat.as_str(), &with, n).into(), - None => self.0.replace(pat.as_str(), &with).into(), - }, - TextPattern::Regex(re) => match count { - Some(n) => re.replacen(self, n, with.as_str()).into(), - None => re.replace(self, with.as_str()).into(), - }, - } - } - - /// Repeat the string a number of times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let n = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(Self(self.0.repeat(n))) - } -} - -/// The out of bounds access error message. -#[cold] -fn out_of_bounds(index: i64, len: i64) -> String { - format!( - "string index out of bounds (index: {}, len: {})", - index, len - ) -} - -/// 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 Debug for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('"')?; - for c in self.chars() { - match c { - '\\' => f.write_str(r"\\")?, - '"' => f.write_str(r#"\""#)?, - '\n' => f.write_str(r"\n")?, - '\r' => f.write_str(r"\r")?, - '\t' => f.write_str(r"\t")?, - _ => f.write_char(c)?, - } - } - f.write_char('"') - } -} - -impl Add for Str { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Str { - fn add_assign(&mut self, rhs: Self) { - self.0.push_str(rhs.as_str()); - } -} - -impl AsRef<str> for Str { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow<str> for Str { - fn borrow(&self) -> &str { - self - } -} - -impl From<char> for Str { - fn from(c: char) -> Self { - Self(c.into()) - } -} - -impl From<&str> for Str { - fn from(s: &str) -> Self { - Self(s.into()) - } -} - -impl From<EcoString> for Str { - fn from(s: EcoString) -> Self { - Self(s) - } -} - -impl From<String> for Str { - fn from(s: String) -> Self { - Self(s.into()) - } -} -impl From<Cow<'_, str>> for Str { - fn from(s: Cow<str>) -> Self { - Self(s.into()) - } -} -impl FromIterator<char> for Str { - fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl From<Str> for EcoString { - fn from(str: Str) -> Self { - str.0 - } -} - -impl From<Str> for String { - fn from(s: Str) -> Self { - s.0.into() - } -} - -/// A regular expression. -#[derive(Clone)] -pub struct Regex(regex::Regex); - -impl Regex { - /// Create a new regular expression. - pub fn new(re: &str) -> StrResult<Self> { - regex::Regex::new(re).map(Self).map_err(|err| err.to_string()) - } -} - -impl Deref for Regex { - type Target = regex::Regex; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Debug for Regex { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "regex({:?})", self.0.as_str()) - } -} - -impl PartialEq for Regex { - fn eq(&self, other: &Self) -> bool { - self.0.as_str() == other.0.as_str() - } -} - -impl Hash for Regex { - fn hash<H: Hasher>(&self, state: &mut H) { - self.0.as_str().hash(state); - } -} - -/// A pattern which can be searched for in a string. -#[derive(Debug, Clone)] -pub enum TextPattern { - /// Just a string. - Str(Str), - /// A regular expression. - Regex(Regex), -} - -castable! { - TextPattern, - Expected: "string or regular expression", - Value::Str(text) => Self::Str(text), - @regex: Regex => Self::Regex(regex.clone()), -} - -/// A side of a string. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum TextSide { - /// The logical start of the string, may be left or right depending on the - /// language. - Start, - /// The logical end of the string. - End, -} - -castable! { - TextSide, - Expected: "start or end", - @align: RawAlign => match align { - RawAlign::Start => Self::Start, - RawAlign::End => Self::End, - _ => Err("expected either `start` or `end`")?, - }, -} diff --git a/src/eval/value.rs b/src/eval/value.rs deleted file mode 100644 index b7bd6d3c..00000000 --- a/src/eval/value.rs +++ /dev/null @@ -1,454 +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 super::{ops, Args, Array, Cast, Dict, Func, RawLength, Str}; -use crate::diag::StrResult; -use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor}; -use crate::library::text::RawNode; -use crate::model::{Content, Layout}; -use crate::util::EcoString; - -/// 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`. - Length(RawLength), - /// 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(Relative<RawLength>), - /// A fraction: `1fr`. - Fraction(Fraction), - /// A color value: `#f79143ff`. - Color(Color), - /// A string: `"string"`. - Str(Str), - /// 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 dynamic value. - Dyn(Dynamic), -} - -impl Value { - /// Create a content value from an inline-level node. - pub fn inline<T>(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::inline(node)) - } - - /// Create a content value from a block-level node. - pub fn block<T>(node: T) -> Self - where - T: Layout + Debug + Hash + Sync + Send + 'static, - { - Self::Content(Content::block(node)) - } - - /// Create a new dynamic value. - pub fn dynamic<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self::Dyn(Dynamic::new(any)) - } - - /// 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(_) => RawLength::TYPE_NAME, - Self::Angle(_) => Angle::TYPE_NAME, - Self::Ratio(_) => Ratio::TYPE_NAME, - Self::Relative(_) => Relative::<RawLength>::TYPE_NAME, - Self::Fraction(_) => Fraction::TYPE_NAME, - Self::Color(_) => Color::TYPE_NAME, - Self::Str(_) => Str::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::Dyn(v) => v.type_name(), - } - } - - /// Try to cast the value into a specific type. - pub fn cast<T: Cast>(self) -> StrResult<T> { - T::cast(self) - } - - /// Return the debug representation of the value. - pub fn repr(&self) -> Str { - format_str!("{:?}", self) - } - - /// Return the display representation of the value. - pub fn display(self) -> Content { - match self { - Value::None => Content::new(), - Value::Int(v) => Content::Text(format_eco!("{}", v)), - Value::Float(v) => Content::Text(format_eco!("{}", v)), - Value::Str(v) => Content::Text(v.into()), - Value::Content(v) => v, - - // For values which can't be shown "naturally", we return the raw - // representation with typst code syntax highlighting. - v => Content::show(RawNode { text: v.repr().into(), block: false }) - .styled(RawNode::LANG, Some("typc".into())), - } - } -} - -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::Str(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::Dyn(v) => Debug::fmt(v, f), - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - ops::equal(self, other) - } -} - -impl PartialOrd for Value { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - ops::compare(self, other) - } -} - -impl Hash for Value { - fn hash<H: Hasher>(&self, state: &mut H) { - std::mem::discriminant(self).hash(state); - match self { - Self::None => {} - Self::Auto => {} - Self::Bool(v) => v.hash(state), - Self::Int(v) => v.hash(state), - Self::Float(v) => v.to_bits().hash(state), - Self::Length(v) => v.hash(state), - Self::Angle(v) => v.hash(state), - Self::Ratio(v) => v.hash(state), - Self::Relative(v) => v.hash(state), - Self::Fraction(v) => v.hash(state), - Self::Color(v) => v.hash(state), - Self::Str(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::Dyn(v) => v.hash(state), - } - } -} - -impl From<i32> for Value { - fn from(v: i32) -> Self { - Self::Int(v as i64) - } -} - -impl From<usize> for Value { - fn from(v: usize) -> Self { - Self::Int(v as i64) - } -} - -impl From<Length> for Value { - fn from(v: Length) -> Self { - Self::Length(v.into()) - } -} - -impl From<Em> for Value { - fn from(v: Em) -> Self { - Self::Length(v.into()) - } -} - -impl From<RgbaColor> for Value { - fn from(v: RgbaColor) -> Self { - Self::Color(v.into()) - } -} - -impl From<&str> for Value { - fn from(v: &str) -> Self { - Self::Str(v.into()) - } -} - -impl From<EcoString> for Value { - fn from(v: EcoString) -> Self { - Self::Str(v.into()) - } -} - -impl From<String> for Value { - fn from(v: String) -> Self { - Self::Str(v.into()) - } -} - -impl From<Dynamic> for Value { - fn from(v: Dynamic) -> Self { - Self::Dyn(v) - } -} - -/// A dynamic value. -#[derive(Clone, Hash)] -pub struct Dynamic(Arc<dyn Bounds>); - -impl Dynamic { - /// Create a new instance from any value that satisifies the required bounds. - pub fn new<T>(any: T) -> Self - where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, - { - Self(Arc::new(any)) - } - - /// Whether the wrapped type is `T`. - pub fn is<T: Type + 'static>(&self) -> bool { - (*self.0).as_any().is::<T>() - } - - /// Try to downcast to a reference to a specific type. - pub fn downcast<T: Type + 'static>(&self) -> Option<&T> { - (*self.0).as_any().downcast_ref() - } - - /// The name of the stored value's type. - pub fn type_name(&self) -> &'static str { - self.0.dyn_type_name() - } -} - -impl Debug for Dynamic { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl PartialEq for Dynamic { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) - } -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_eq(&self, other: &Dynamic) -> bool; - fn dyn_type_name(&self) -> &'static str; - fn hash64(&self) -> u64; -} - -impl<T> Bounds for T -where - T: Type + Debug + PartialEq + Hash + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } - - fn dyn_eq(&self, other: &Dynamic) -> bool { - if let Some(other) = other.downcast::<Self>() { - self == other - } else { - false - } - } - - fn dyn_type_name(&self) -> &'static str { - T::TYPE_NAME - } - - fn hash64(&self) -> u64 { - // Also hash the TypeId since nodes with different types but - // equal data should be different. - let mut state = fxhash::FxHasher64::default(); - self.type_id().hash(&mut state); - self.hash(&mut state); - state.finish() - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.hash64()); - } -} - -/// The type of a value. -pub trait Type { - /// The name of the type. - const TYPE_NAME: &'static str; -} - -/// Implement traits for primitives. -macro_rules! primitive { - ( - $type:ty: $name:literal, $variant:ident - $(, $other:ident$(($binding:ident))? => $out:expr)* - ) => { - impl Type for $type { - const TYPE_NAME: &'static str = $name; - } - - impl Cast for $type { - fn is(value: &Value) -> bool { - matches!(value, Value::$variant(_) - $(| primitive!(@$other $(($binding))?))*) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::$variant(v) => Ok(v), - $(Value::$other$(($binding))? => Ok($out),)* - v => Err(format!( - "expected {}, found {}", - Self::TYPE_NAME, - v.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! { RawLength: "length", Length } -primitive! { Angle: "angle", Angle } -primitive! { Ratio: "ratio", Ratio } -primitive! { Relative<RawLength>: "relative length", - Relative, - Length(v) => v.into(), - Ratio(v) => v.into() -} -primitive! { Fraction: "fraction", Fraction } -primitive! { Color: "color", Color } -primitive! { Str: "string", Str } -primitive! { Content: "content", - Content, - None => Content::new(), - Str(text) => Content::Text(text.into()) -} -primitive! { Array: "array", Array } -primitive! { Dict: "dictionary", Dict } -primitive! { Func: "function", Func } -primitive! { Args: "arguments", Args } - -#[cfg(test)] -mod tests { - use super::*; - - #[track_caller] - fn test(value: impl Into<Value>, exp: &str) { - assert_eq!(format!("{:?}", value.into()), exp); - } - - #[test] - fn test_value_debug() { - // Primitives. - test(Value::None, "none"); - test(false, "false"); - test(12i64, "12"); - test(3.14, "3.14"); - test(Length::pt(5.5), "5.5pt"); - test(Angle::deg(90.0), "90deg"); - test(Ratio::one() / 2.0, "50%"); - test( - Ratio::new(0.3) + RawLength::from(Length::cm(2.0)), - "30% + 56.69pt", - ); - test(Fraction::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)"); - - // Functions, content and dynamics. - test(Content::Text("a".into()), "[...]"); - test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil"); - test(Dynamic::new(1), "1"); - } -} diff --git a/src/eval/vm.rs b/src/eval/vm.rs deleted file mode 100644 index 0604e7be..00000000 --- a/src/eval/vm.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::path::PathBuf; - -use comemo::Tracked; - -use super::{Route, Scopes, Value}; -use crate::diag::{SourceError, StrResult}; -use crate::source::SourceId; -use crate::syntax::Span; -use crate::util::PathExt; -use crate::World; - -/// A virtual machine. -pub struct Vm<'a> { - /// The core context. - pub world: Tracked<'a, dyn World>, - /// The route of source ids the machine took to reach its current location. - pub route: Tracked<'a, Route>, - /// The current location. - pub location: Option<SourceId>, - /// The stack of scopes. - pub scopes: Scopes<'a>, - /// A control flow event that is currently happening. - pub flow: Option<Flow>, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - pub fn new( - world: Tracked<'a, dyn World>, - route: Tracked<'a, Route>, - location: Option<SourceId>, - scopes: Scopes<'a>, - ) -> Self { - Self { - world, - route, - location, - scopes, - flow: None, - } - } - - /// Resolve a user-entered path to be relative to the compilation - /// environment's root. - pub fn locate(&self, path: &str) -> StrResult<PathBuf> { - if let Some(id) = self.location { - if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.config().root.join(path).normalize()); - } - - if let Some(dir) = self.world.source(id).path().parent() { - return Ok(dir.join(path).normalize()); - } - } - - return Err("cannot access file system from here".into()); - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum Flow { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option<Value>), -} - -impl Flow { - /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> SourceError { - match *self { - Self::Break(span) => { - error!(span, "cannot break outside of loop") - } - Self::Continue(span) => { - error!(span, "cannot continue outside of loop") - } - Self::Return(span, _) => { - error!(span, "cannot return outside of function") - } - } - } -} |
