summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-10-17 19:26:24 +0200
committerLaurenz <laurmaedje@gmail.com>2022-10-17 20:04:22 +0200
commite21822665591dc19766275da1e185215a6b945ef (patch)
tree7788e211c3c33c8b5a8ad7d5eb7574e33631eb16 /src/eval
parent4fd031a256b2ecfe524859d5599fafb386395572 (diff)
Merge some modules
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/args.rs225
-rw-r--r--src/eval/array.rs369
-rw-r--r--src/eval/capture.rs186
-rw-r--r--src/eval/cast.rs361
-rw-r--r--src/eval/dict.rs193
-rw-r--r--src/eval/func.rs248
-rw-r--r--src/eval/methods.rs167
-rw-r--r--src/eval/mod.rs1242
-rw-r--r--src/eval/ops.rs399
-rw-r--r--src/eval/raw.rs294
-rw-r--r--src/eval/scope.rs161
-rw-r--r--src/eval/str.rs475
-rw-r--r--src/eval/value.rs454
-rw-r--r--src/eval/vm.rs87
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")
- }
- }
- }
-}