diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-07-10 20:01:18 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-07-10 23:10:17 +0200 |
| commit | 6a4823461f491aef63451f097ddfe5602e0b2157 (patch) | |
| tree | ad11b0ad169d030942d950573c729d50f7b3291b | |
| parent | 36b3067c19c8743032a44f888ee48702b88d135b (diff) | |
Reference-count complex values
Rename some nodes types
| -rw-r--r-- | src/eco.rs | 45 | ||||
| -rw-r--r-- | src/eval/array.rs | 148 | ||||
| -rw-r--r-- | src/eval/dict.rs | 129 | ||||
| -rw-r--r-- | src/eval/function.rs | 176 | ||||
| -rw-r--r-- | src/eval/mod.rs | 33 | ||||
| -rw-r--r-- | src/eval/ops.rs | 37 | ||||
| -rw-r--r-- | src/eval/scope.rs | 4 | ||||
| -rw-r--r-- | src/eval/template.rs | 139 | ||||
| -rw-r--r-- | src/eval/value.rs | 310 | ||||
| -rw-r--r-- | src/exec/context.rs | 10 | ||||
| -rw-r--r-- | src/exec/mod.rs | 27 | ||||
| -rw-r--r-- | src/layout/background.rs | 4 | ||||
| -rw-r--r-- | src/layout/fixed.rs | 4 | ||||
| -rw-r--r-- | src/layout/grid.rs | 8 | ||||
| -rw-r--r-- | src/layout/image.rs | 2 | ||||
| -rw-r--r-- | src/layout/mod.rs | 30 | ||||
| -rw-r--r-- | src/layout/pad.rs | 4 | ||||
| -rw-r--r-- | src/layout/par.rs | 6 | ||||
| -rw-r--r-- | src/layout/stack.rs | 4 | ||||
| -rw-r--r-- | src/library/elements.rs | 4 | ||||
| -rw-r--r-- | src/library/layout.rs | 4 | ||||
| -rw-r--r-- | src/library/mod.rs | 2 | ||||
| -rw-r--r-- | src/library/text.rs | 8 | ||||
| -rw-r--r-- | src/parse/mod.rs | 52 | ||||
| -rw-r--r-- | src/pretty.rs | 40 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 2 | ||||
| -rw-r--r-- | src/syntax/node.rs | 2 | ||||
| -rw-r--r-- | src/syntax/span.rs | 50 | ||||
| -rw-r--r-- | src/syntax/visit.rs | 24 |
29 files changed, 831 insertions, 477 deletions
@@ -7,7 +7,7 @@ use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign, Deref}; use std::rc::Rc; -/// A economical string with inline storage and clone-on-write value semantics. +/// An economical string with inline storage and clone-on-write value semantics. #[derive(Clone)] pub struct EcoString(Repr); @@ -22,8 +22,9 @@ enum Repr { /// The maximum number of bytes that can be stored inline. /// -/// The value is chosen such that `Repr` fits exactly into 16 bytes -/// (which are needed anyway due to `Rc`s alignment). +/// The value is chosen such that an `EcoString` fits exactly into 16 bytes +/// (which are needed anyway due to the `Rc`s alignment, at least on 64-bit +/// platforms). /// /// Must be at least 4 to hold any char. const LIMIT: usize = 14; @@ -77,7 +78,7 @@ impl EcoString { self } - /// Appends the given character at the end. + /// Append the given character at the end. pub fn push(&mut self, c: char) { match &mut self.0 { Repr::Small { buf, len } => { @@ -93,7 +94,7 @@ impl EcoString { } } - /// Appends the given string slice at the end. + /// Append the given string slice at the end. pub fn push_str(&mut self, string: &str) { match &mut self.0 { Repr::Small { buf, len } => { @@ -113,7 +114,7 @@ impl EcoString { } } - /// Removes the last character from the string. + /// Remove the last character from the string. pub fn pop(&mut self) -> Option<char> { let c = self.as_str().chars().rev().next()?; match &mut self.0 { @@ -127,7 +128,21 @@ impl EcoString { Some(c) } - /// Repeats this string `n` times. + /// Clear the string. + pub fn clear(&mut self) { + match &mut self.0 { + Repr::Small { len, .. } => *len = 0, + Repr::Large(rc) => { + if Rc::strong_count(rc) == 1 { + Rc::make_mut(rc).clear(); + } else { + *self = Self::new(); + } + } + } + } + + /// Repeat this string `n` times. pub fn repeat(&self, n: usize) -> Self { if let Repr::Small { buf, len } = &self.0 { let prev = usize::from(*len); @@ -209,15 +224,15 @@ impl Default for EcoString { } } -impl Debug for EcoString { +impl Display for EcoString { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_str(), f) + Display::fmt(self.as_str(), f) } } -impl Display for EcoString { +impl Debug for EcoString { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self.as_str(), f) + Debug::fmt(self.as_str(), f) } } @@ -259,6 +274,14 @@ impl PartialOrd for EcoString { } } +impl Add<&Self> for EcoString { + type Output = Self; + + fn add(self, rhs: &Self) -> Self::Output { + self + rhs.as_str() + } +} + impl Add<&str> for EcoString { type Output = Self; diff --git a/src/eval/array.rs b/src/eval/array.rs new file mode 100644 index 00000000..f62bda9f --- /dev/null +++ b/src/eval/array.rs @@ -0,0 +1,148 @@ +use std::fmt::{self, Debug, Formatter}; +use std::iter::FromIterator; +use std::ops::{Add, AddAssign}; +use std::rc::Rc; + +use super::Value; + +/// Create a new [`Array`] from values. +#[macro_export] +macro_rules! array { + ($value:expr; $count:expr) => { + $crate::eval::Array::from_vec(vec![$crate::eval::Value::from($value); $count]) + }; + + ($($value:expr),* $(,)?) => { + $crate::eval::Array::from_vec(vec![$($crate::eval::Value::from($value)),*]) + }; +} + +/// A variably-typed array with clone-on-write value semantics. +#[derive(Clone, PartialEq)] +pub struct Array { + vec: Rc<Vec<Value>>, +} + +impl Array { + /// Create a new, empty array. + pub fn new() -> Self { + Self { vec: Rc::new(vec![]) } + } + + /// Create a new array from a vector of values. + pub fn from_vec(vec: Vec<Value>) -> Self { + Self { vec: Rc::new(vec) } + } + + /// Create a new, empty array with the given `capacity`. + pub fn with_capacity(capacity: usize) -> Self { + Self { + vec: Rc::new(Vec::with_capacity(capacity)), + } + } + + /// Whether the array is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// The length of the array. + pub fn len(&self) -> usize { + self.vec.len() + } + + /// Borrow the value at the given index. + pub fn get(&self, index: usize) -> Option<&Value> { + self.vec.get(index) + } + + /// Mutably borrow the value at the given index. + pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> { + Rc::make_mut(&mut self.vec).get_mut(index) + } + + /// Set the value at the given index. + /// + /// This panics the `index` is out of range. + pub fn set(&mut self, index: usize, value: Value) { + Rc::make_mut(&mut self.vec)[index] = value; + } + + /// Push a value to the end of the array. + pub fn push(&mut self, value: Value) { + Rc::make_mut(&mut self.vec).push(value); + } + + /// Extend the array with the values from another array. + pub fn extend(&mut self, other: &Array) { + Rc::make_mut(&mut self.vec).extend(other.into_iter()) + } + + /// Clear the array. + pub fn clear(&mut self) { + if Rc::strong_count(&mut self.vec) == 1 { + Rc::make_mut(&mut self.vec).clear(); + } else { + *self = Self::new(); + } + } + + /// Repeat this array `n` times. + pub fn repeat(&self, n: usize) -> Self { + let len = self.len().checked_mul(n).expect("capacity overflow"); + self.into_iter().cycle().take(len).collect() + } + + /// Iterate over references to the contained values. + pub fn iter(&self) -> std::slice::Iter<Value> { + self.vec.iter() + } + + /// Iterate over the contained values. + pub fn into_iter(&self) -> impl Iterator<Item = Value> + Clone + '_ { + // TODO: Actually consume the vector if the ref-count is 1? + self.iter().cloned() + } +} + +impl Default for Array { + fn default() -> Self { + Self::new() + } +} + +impl Debug for Array { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_list().entries(self.vec.iter()).finish() + } +} + +impl FromIterator<Value> for Array { + fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self { + Array { vec: Rc::new(iter.into_iter().collect()) } + } +} + +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() + } +} + +impl Add<&Array> for Array { + type Output = Self; + + fn add(mut self, rhs: &Array) -> Self::Output { + self.extend(rhs); + self + } +} + +impl AddAssign<&Array> for Array { + fn add_assign(&mut self, rhs: &Array) { + self.extend(rhs); + } +} diff --git a/src/eval/dict.rs b/src/eval/dict.rs new file mode 100644 index 00000000..bf99ea17 --- /dev/null +++ b/src/eval/dict.rs @@ -0,0 +1,129 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Debug, Formatter}; +use std::iter::FromIterator; +use std::ops::{Add, AddAssign}; +use std::rc::Rc; + +use super::Value; +use crate::eco::EcoString; + +/// Create a new [`Dict`] from key-value pairs. +#[macro_export] +macro_rules! dict { + ($($key:expr => $value:expr),* $(,)?) => {{ + #[allow(unused_mut)] + let mut map = std::collections::BTreeMap::new(); + $(map.insert($crate::eco::EcoString::from($key), $crate::eval::Value::from($value));)* + $crate::eval::Dict::from_map(map) + }}; +} + +/// A variably-typed dictionary with clone-on-write value semantics. +#[derive(Clone, PartialEq)] +pub struct Dict { + map: Rc<BTreeMap<EcoString, 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<EcoString, Value>) -> Self { + Self { map: Rc::new(map) } + } + + /// Whether the dictionary is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// The number of pairs in the dictionary. + pub fn len(&self) -> usize { + self.map.len() + } + + /// Borrow the value the given `key` maps to. + pub fn get(&self, key: &str) -> Option<&Value> { + self.map.get(key) + } + + /// Mutably borrow the value the given `key` maps to. + pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { + Rc::make_mut(&mut self.map).get_mut(key) + } + + /// Insert a mapping from the given `key` to the given `value`. + pub fn insert(&mut self, key: EcoString, value: Value) { + Rc::make_mut(&mut self.map).insert(key, value); + } + + /// Extend the dictionary with the values from another dictionary. + pub fn extend(&mut self, other: &Dict) { + Rc::make_mut(&mut self.map).extend(other.into_iter()) + } + + /// Clear the dictionary. + pub fn clear(&mut self) { + if Rc::strong_count(&mut self.map) == 1 { + Rc::make_mut(&mut self.map).clear(); + } else { + *self = Self::new(); + } + } + + /// Iterate over pairs of the contained keys and values. + pub fn into_iter(&self) -> impl Iterator<Item = (EcoString, Value)> + Clone + '_ { + // TODO: Actually consume the map if the ref-count is 1? + self.iter().map(|(k, v)| (k.clone(), v.clone())) + } + + /// Iterate over pairs of references to the contained keys and values. + pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> { + self.map.iter() + } +} + +impl Default for Dict { + fn default() -> Self { + Self::new() + } +} + +impl Debug for Dict { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_map().entries(self.map.iter()).finish() + } +} + +impl FromIterator<(EcoString, Value)> for Dict { + fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self { + Dict { map: Rc::new(iter.into_iter().collect()) } + } +} + +impl<'a> IntoIterator for &'a Dict { + type Item = (&'a EcoString, &'a Value); + type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Add<&Dict> for Dict { + type Output = Self; + + fn add(mut self, rhs: &Dict) -> Self::Output { + self.extend(rhs); + self + } +} + +impl AddAssign<&Dict> for Dict { + fn add_assign(&mut self, rhs: &Dict) { + self.extend(rhs); + } +} diff --git a/src/eval/function.rs b/src/eval/function.rs new file mode 100644 index 00000000..c986d71a --- /dev/null +++ b/src/eval/function.rs @@ -0,0 +1,176 @@ +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::rc::Rc; + +use super::{Cast, CastResult, EvalContext, Value}; +use crate::eco::EcoString; +use crate::syntax::{Span, Spanned}; + +/// An evaluatable function. +#[derive(Clone)] +pub struct Function { + /// The name of the function. + /// + /// The string is boxed to make the whole struct fit into 24 bytes, so that + /// a value fits into 32 bytes. + name: Option<Box<EcoString>>, + /// The closure that defines the function. + f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>, +} + +impl Function { + /// Create a new function from a rust closure. + pub fn new<F>(name: Option<EcoString>, f: F) -> Self + where + F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, + { + Self { name: name.map(Box::new), f: Rc::new(f) } + } + + /// The name of the function. + pub fn name(&self) -> Option<&EcoString> { + self.name.as_ref().map(|s| &**s) + } +} + +impl Deref for Function { + type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value; + + fn deref(&self) -> &Self::Target { + self.f.as_ref() + } +} + +impl Debug for Function { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("ValueFunc").field("name", &self.name).finish() + } +} + +impl PartialEq for Function { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers because we don't want to compare vtables. + Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const () + } +} + +/// Evaluated arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArgs { + /// The span of the whole argument list. + pub span: Span, + /// The positional arguments. + pub items: Vec<FuncArg>, +} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArg { + /// The span of the whole argument. + pub span: Span, + /// The name of the argument (`None` for positional arguments). + pub name: Option<EcoString>, + /// The value of the argument. + pub value: Spanned<Value>, +} + +impl FuncArgs { + /// Find and consume the first castable positional argument. + pub fn eat<T>(&mut self, ctx: &mut EvalContext) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + (0 .. self.items.len()).find_map(|index| { + let slot = &mut self.items[index]; + if slot.name.is_some() { + return None; + } + + let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => { + self.items.remove(index); + Some(t) + } + CastResult::Warn(t, m) => { + self.items.remove(index); + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + slot.value = value; + None + } + } + }) + } + + /// Find and consume the first castable positional argument, producing a + /// `missing argument: {what}` error if no match was found. + pub fn expect<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + let found = self.eat(ctx); + if found.is_none() { + ctx.diag(error!(self.span, "missing argument: {}", what)); + } + found + } + + /// Find, consume and collect all castable positional arguments. + /// + /// This function returns a vector instead of an iterator because the + /// iterator would require unique access to the context, rendering it rather + /// unusable. If you need to process arguments one-by-one, you probably want + /// to use a while-let loop together with [`eat()`](Self::eat). + pub fn all<T>(&mut self, ctx: &mut EvalContext) -> Vec<T> + where + T: Cast<Spanned<Value>>, + { + std::iter::from_fn(|| self.eat(ctx)).collect() + } + + /// Cast and remove the value for the given named argument, producing an + /// error if the conversion fails. + pub fn named<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> + where + T: Cast<Spanned<Value>>, + { + let index = self + .items + .iter() + .position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?; + + let value = self.items.remove(index).value; + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => Some(t), + CastResult::Warn(t, m) => { + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + ctx.diag(error!( + span, + "expected {}, found {}", + T::TYPE_NAME, + value.v.type_name(), + )); + None + } + } + } + + /// Produce "unexpected argument" errors for all remaining arguments. + pub fn finish(self, ctx: &mut EvalContext) { + for arg in &self.items { + if arg.value.v != Value::Error { + ctx.diag(error!(arg.span, "unexpected argument")); + } + } + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index dc4ab7ee..689234bd 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,13 +1,23 @@ //! Evaluation of syntax trees. #[macro_use] +mod array; +#[macro_use] +mod dict; +#[macro_use] mod value; mod capture; +mod function; mod ops; mod scope; +mod template; +pub use array::*; pub use capture::*; +pub use dict::*; +pub use function::*; pub use scope::*; +pub use template::*; pub use value::*; use std::collections::HashMap; @@ -45,7 +55,7 @@ pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, /// The template defined by this module. - pub template: TemplateValue, + pub template: Template, } /// The context for evaluation. @@ -213,7 +223,7 @@ pub trait Eval { } impl Eval for Rc<SyntaxTree> { - type Output = TemplateValue; + type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { struct ExprVisitor<'a, 'b> { @@ -230,10 +240,7 @@ impl Eval for Rc<SyntaxTree> { let mut visitor = ExprVisitor { ctx, map: ExprMap::new() }; visitor.visit_tree(self); - Rc::new(vec![TemplateNode::Tree { - tree: Rc::clone(self), - map: visitor.map, - }]) + TemplateTree { tree: Rc::clone(self), map: visitor.map }.into() } } @@ -280,7 +287,7 @@ impl Eval for Expr { } impl Eval for ArrayExpr { - type Output = ArrayValue; + type Output = Array; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.items.iter().map(|expr| expr.eval(ctx)).collect() @@ -288,7 +295,7 @@ impl Eval for ArrayExpr { } impl Eval for DictExpr { - type Output = DictValue; + type Output = Dict; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.items @@ -299,7 +306,7 @@ impl Eval for DictExpr { } impl Eval for TemplateExpr { - type Output = TemplateValue; + type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { self.tree.eval(ctx) @@ -476,7 +483,7 @@ impl Eval for CallExpr { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) { + if let Some(func) = ctx.cast::<Function>(callee, self.callee.span()) { let mut args = self.args.eval(ctx); let returned = func(ctx, &mut args); args.finish(ctx); @@ -530,7 +537,7 @@ impl Eval for ClosureExpr { }; let name = self.name.as_ref().map(|name| name.string.clone()); - Value::Func(FuncValue::new(name, move |ctx, args| { + Value::Func(Function::new(name, move |ctx, args| { // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev = mem::take(&mut ctx.scopes); @@ -554,10 +561,10 @@ impl Eval for WithExpr { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) { + if let Some(func) = ctx.cast::<Function>(callee, self.callee.span()) { let applied = self.args.eval(ctx); let name = func.name().cloned(); - Value::Func(FuncValue::new(name, move |ctx, args| { + Value::Func(Function::new(name, move |ctx, args| { // Remove named arguments that were overridden. let kept: Vec<_> = applied .items diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 3b48140c..c1dd726b 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,7 +1,6 @@ use std::cmp::Ordering::*; -use std::rc::Rc; -use super::{TemplateNode, Value}; +use super::Value; use Value::*; /// Apply the plus operator to a value. @@ -90,11 +89,6 @@ pub fn sub(lhs: Value, rhs: Value) -> Value { /// Compute the product of two values. pub fn mul(lhs: Value, rhs: Value) -> Value { - fn repeat<T: Clone>(vec: Vec<T>, n: usize) -> Vec<T> { - let len = n * vec.len(); - vec.into_iter().cycle().take(len).collect() - } - match (lhs, rhs) { (Int(a), Int(b)) => Int(a * b), (Int(a), Float(b)) => Float(a as f64 * b), @@ -128,8 +122,8 @@ pub fn mul(lhs: Value, rhs: Value) -> Value { (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), - (Array(a), Int(b)) => Array(repeat(a, b.max(0) as usize)), - (Int(a), Array(b)) => Array(repeat(b, a.max(0) as usize)), + (Array(a), Int(b)) => Array(a.repeat(b.max(0) as usize)), + (Int(a), Array(b)) => Array(b.repeat(a.max(0) as usize)), _ => Error, } @@ -240,26 +234,11 @@ pub fn join(lhs: Value, rhs: Value) -> Result<Value, Value> { fn concat(lhs: Value, rhs: Value) -> Result<Value, Value> { Ok(match (lhs, rhs) { (Str(a), Str(b)) => Str(a + &b), - (Array(mut a), Array(b)) => Array({ - a.extend(b); - a - }), - (Dict(mut a), Dict(b)) => Dict({ - a.extend(b); - a - }), - (Template(mut a), Template(b)) => Template({ - Rc::make_mut(&mut a).extend(b.iter().cloned()); - a - }), - (Template(mut a), Str(b)) => Template({ - Rc::make_mut(&mut a).push(TemplateNode::Str(b)); - a - }), - (Str(a), Template(mut b)) => Template({ - Rc::make_mut(&mut b).insert(0, TemplateNode::Str(a)); - b - }), + (Array(a), Array(b)) => Array(a + &b), + (Dict(a), Dict(b)) => Dict(a + &b), + (Template(a), Template(b)) => Template(a + &b), + (Template(a), Str(b)) => Template(a + b), + (Str(a), Template(b)) => Template(a + b), (a, _) => return Err(a), }) } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 05bbeda2..4a9d5970 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::iter; use std::rc::Rc; -use super::{AnyValue, EcoString, EvalContext, FuncArgs, FuncValue, Type, Value}; +use super::{AnyValue, EcoString, EvalContext, FuncArgs, Function, Type, Value}; /// A slot where a variable is stored. pub type Slot = Rc<RefCell<Value>>; @@ -92,7 +92,7 @@ impl Scope { F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, { let name = name.into(); - self.def_const(name.clone(), FuncValue::new(Some(name), f)); + self.def_const(name.clone(), Function::new(Some(name), f)); } /// Define a constant variable with a value of variant `Value::Any`. diff --git a/src/eval/template.rs b/src/eval/template.rs new file mode 100644 index 00000000..4d003998 --- /dev/null +++ b/src/eval/template.rs @@ -0,0 +1,139 @@ +use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; +use std::ops::{Add, Deref}; +use std::rc::Rc; + +use super::Value; +use crate::eco::EcoString; +use crate::exec::ExecContext; +use crate::syntax::{Expr, SyntaxTree}; + +/// A template value: `[*Hi* there]`. +#[derive(Default, Debug, Clone)] +pub struct Template { + nodes: Rc<Vec<TemplateNode>>, +} + +impl Template { + /// Create a new template from a vector of nodes. + pub fn new(nodes: Vec<TemplateNode>) -> Self { + Self { nodes: Rc::new(nodes) } + } + + /// Iterate over the contained template nodes. + pub fn iter(&self) -> impl Iterator<Item = &TemplateNode> + '_ { + self.nodes.iter() + } +} + +impl From<TemplateTree> for Template { + fn from(tree: TemplateTree) -> Self { + Self::new(vec![TemplateNode::Tree(tree)]) + } +} + +impl From<TemplateFunc> for Template { + fn from(func: TemplateFunc) -> Self { + Self::new(vec![TemplateNode::Func(func)]) + } +} + +impl From<EcoString> for Template { + fn from(string: EcoString) -> Self { + Self::new(vec![TemplateNode::Str(string)]) + } +} + +impl PartialEq for Template { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.nodes, &other.nodes) + } +} + +impl Add<&Template> for Template { + type Output = Self; + + fn add(mut self, rhs: &Self) -> Self::Output { + Rc::make_mut(&mut self.nodes).extend(rhs.nodes.iter().cloned()); + self + } +} + +impl Add<EcoString> for Template { + type Output = Self; + + fn add(mut self, rhs: EcoString) -> Self::Output { + Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs)); + self + } +} + +impl Add<Template> for EcoString { + type Output = Template; + + fn add(self, mut rhs: Template) -> Self::Output { + Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self)); + rhs + } +} + +/// One node of a template. +/// +/// Evaluating a template expression creates only a single node. Adding multiple +/// templates can yield multi-node templates. +#[derive(Debug, Clone)] +pub enum TemplateNode { + /// A template that was evaluated from a template expression. + Tree(TemplateTree), + /// A function template that can implement custom behaviour. + Func(TemplateFunc), + /// A template that was converted from a string. + Str(EcoString), +} + +/// A template that consists of a syntax tree plus already evaluated +/// expressions. +#[derive(Debug, Clone)] +pub struct TemplateTree { + /// The syntax tree of the corresponding template expression. + pub tree: Rc<SyntaxTree>, + /// The evaluated expressions in the syntax tree. + pub map: ExprMap, +} + +/// A map from expressions to the values they evaluated to. +/// +/// The raw pointers point into the expressions contained in some +/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope +/// while the hash map still lives. Although this could lead to lookup panics, +/// it is not unsafe since the pointers are never dereferenced. +pub type ExprMap = HashMap<*const Expr, Value>; + +/// A reference-counted dynamic template node that can implement custom +/// behaviour. +#[derive(Clone)] +pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>); + +impl TemplateFunc { + /// Create a new function template from a rust function or closure. + pub fn new<F>(f: F) -> Self + where + F: Fn(&mut ExecContext) + 'static, + { + Self(Rc::new(f)) + } +} + +impl Deref for TemplateFunc { + type Target = dyn Fn(&mut ExecContext); + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl Debug for TemplateFunc { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("TemplateFunc").finish() + } +} diff --git a/src/eval/value.rs b/src/eval/value.rs index 2881399b..fe9494b1 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -1,15 +1,13 @@ use std::any::Any; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap}; use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::Deref; -use std::rc::Rc; -use super::*; +use super::{ops, Array, Dict, EvalContext, Function, Template, TemplateFunc}; use crate::color::{Color, RgbaColor}; +use crate::eco::EcoString; use crate::exec::ExecContext; use crate::geom::{Angle, Fractional, Length, Linear, Relative}; -use crate::syntax::{Expr, Span, Spanned, SyntaxTree}; +use crate::syntax::{Span, Spanned}; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -38,14 +36,14 @@ pub enum Value { Color(Color), /// A string: `"string"`. Str(EcoString), - /// An array value: `(1, "hi", 12cm)`. - Array(ArrayValue), + /// An array of values: `(1, "hi", 12cm)`. + Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. - Dict(DictValue), + Dict(Dict), /// A template value: `[*Hi* there]`. - Template(TemplateValue), + Template(Template), /// An executable function. - Func(FuncValue), + Func(Function), /// Any object. Any(AnyValue), /// The result of invalid operations. @@ -53,12 +51,12 @@ pub enum Value { } impl Value { - /// Create a new template value consisting of a single dynamic node. + /// Create a new template consisting of a single function node. pub fn template<F>(f: F) -> Self where F: Fn(&mut ExecContext) + 'static, { - Self::Template(Rc::new(vec![TemplateNode::Func(TemplateFunc::new(f))])) + Self::Template(TemplateFunc::new(f).into()) } /// The name of the stored value's type. @@ -76,10 +74,10 @@ impl Value { Self::Fractional(_) => Fractional::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME, Self::Str(_) => EcoString::TYPE_NAME, - Self::Array(_) => ArrayValue::TYPE_NAME, - Self::Dict(_) => DictValue::TYPE_NAME, - Self::Template(_) => TemplateValue::TYPE_NAME, - Self::Func(_) => FuncValue::TYPE_NAME, + Self::Array(_) => Array::TYPE_NAME, + Self::Dict(_) => Dict::TYPE_NAME, + Self::Template(_) => Template::TYPE_NAME, + Self::Func(_) => Function::TYPE_NAME, Self::Any(v) => v.type_name(), Self::Error => "error", } @@ -101,7 +99,6 @@ impl Value { a.len() == b.len() && a.iter().all(|(k, x)| b.get(k).map_or(false, |y| x.eq(y))) } - (Self::Template(a), Self::Template(b)) => Rc::ptr_eq(a, b), (a, b) => a == b, } } @@ -146,245 +143,6 @@ impl Default for Value { } } -/// An array value: `(1, "hi", 12cm)`. -pub type ArrayValue = Vec<Value>; - -/// A dictionary value: `(color: #f79143, pattern: dashed)`. -pub type DictValue = BTreeMap<EcoString, Value>; - -/// A template value: `[*Hi* there]`. -pub type TemplateValue = Rc<Vec<TemplateNode>>; - -/// One chunk of a template. -/// -/// Evaluating a template expression creates only a single node. Adding multiple -/// templates can yield multi-node templates. -#[derive(Debug, Clone)] -pub enum TemplateNode { - /// A template that consists of a syntax tree plus already evaluated - /// expression. - Tree { - /// The syntax tree of the corresponding template expression. - tree: Rc<SyntaxTree>, - /// The evaluated expressions in the syntax tree. - map: ExprMap, - }, - /// A template that was converted from a string. - Str(EcoString), - /// A function template that can implement custom behaviour. - Func(TemplateFunc), -} - -impl PartialEq for TemplateNode { - fn eq(&self, _: &Self) -> bool { - false - } -} - -/// A map from expressions to the values they evaluated to. -/// -/// The raw pointers point into the expressions contained in some -/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope -/// while the hash map still lives. Although this could lead to lookup panics, -/// it is not unsafe since the pointers are never dereferenced. -pub type ExprMap = HashMap<*const Expr, Value>; - -/// A reference-counted dynamic template node that can implement custom -/// behaviour. -#[derive(Clone)] -pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>); - -impl TemplateFunc { - /// Create a new function template from a rust function or closure. - pub fn new<F>(f: F) -> Self - where - F: Fn(&mut ExecContext) + 'static, - { - Self(Rc::new(f)) - } -} - -impl Deref for TemplateFunc { - type Target = dyn Fn(&mut ExecContext); - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - -impl Debug for TemplateFunc { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("TemplateAny").finish() - } -} - -/// A wrapper around a reference-counted executable function. -#[derive(Clone)] -pub struct FuncValue { - /// The string is boxed to make the whole struct fit into 24 bytes, so that - /// a [`Value`] fits into 32 bytes. - name: Option<Box<EcoString>>, - /// The closure that defines the function. - f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>, -} - -impl FuncValue { - /// Create a new function value from a rust function or closure. - pub fn new<F>(name: Option<EcoString>, f: F) -> Self - where - F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, - { - Self { name: name.map(Box::new), f: Rc::new(f) } - } - - /// The name of the function. - pub fn name(&self) -> Option<&EcoString> { - self.name.as_ref().map(|s| &**s) - } -} - -impl PartialEq for FuncValue { - fn eq(&self, other: &Self) -> bool { - // We cast to thin pointers because we don't want to compare vtables. - Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const () - } -} - -impl Deref for FuncValue { - type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value; - - fn deref(&self) -> &Self::Target { - self.f.as_ref() - } -} - -impl Debug for FuncValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("ValueFunc").field("name", &self.name).finish() - } -} - -/// Evaluated arguments to a function. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncArgs { - /// The span of the whole argument list. - pub span: Span, - /// The positional arguments. - pub items: Vec<FuncArg>, -} - -impl FuncArgs { - /// Find and consume the first castable positional argument. - pub fn eat<T>(&mut self, ctx: &mut EvalContext) -> Option<T> - where - T: Cast<Spanned<Value>>, - { - (0 .. self.items.len()).find_map(|index| { - let slot = &mut self.items[index]; - if slot.name.is_some() { - return None; - } - - let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); - let span = value.span; - - match T::cast(value) { - CastResult::Ok(t) => { - self.items.remove(index); - Some(t) - } - CastResult::Warn(t, m) => { - self.items.remove(index); - ctx.diag(warning!(span, "{}", m)); - Some(t) - } - CastResult::Err(value) => { - slot.value = value; - None - } - } - }) - } - - /// Find and consume the first castable positional argument, producing a - /// `missing argument: {what}` error if no match was found. - pub fn expect<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T> - where - T: Cast<Spanned<Value>>, - { - let found = self.eat(ctx); - if found.is_none() { - ctx.diag(error!(self.span, "missing argument: {}", what)); - } - found - } - - /// Find, consume and collect all castable positional arguments. - /// - /// This function returns a vector instead of an iterator because the - /// iterator would require unique access to the context, rendering it rather - /// unusable. If you need to process arguments one-by-one, you probably want - /// to use a while-let loop together with [`eat()`](Self::eat). - pub fn all<T>(&mut self, ctx: &mut EvalContext) -> Vec<T> - where - T: Cast<Spanned<Value>>, - { - std::iter::from_fn(|| self.eat(ctx)).collect() - } - - /// Cast and remove the value for the given named argument, producing an - /// error if the conversion fails. - pub fn named<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> - where - T: Cast<Spanned<Value>>, - { - let index = self - .items - .iter() - .position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?; - - let value = self.items.remove(index).value; - let span = value.span; - - match T::cast(value) { - CastResult::Ok(t) => Some(t), - CastResult::Warn(t, m) => { - ctx.diag(warning!(span, "{}", m)); - Some(t) - } - CastResult::Err(value) => { - ctx.diag(error!( - span, - "expected {}, found {}", - T::TYPE_NAME, - value.v.type_name(), - )); - None - } - } - } - - /// Produce "unexpected argument" errors for all remaining arguments. - pub fn finish(self, ctx: &mut EvalContext) { - for arg in &self.items { - if arg.value.v != Value::Error { - ctx.diag(error!(arg.span, "unexpected argument")); - } - } - } -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncArg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option<EcoString>, - /// The value of the argument. - pub value: Spanned<Value>, -} - /// A wrapper around a dynamic value. pub struct AnyValue(Box<dyn Bounds>); @@ -422,15 +180,9 @@ impl AnyValue { } } -impl Clone for AnyValue { - fn clone(&self) -> Self { - Self(self.0.dyn_clone()) - } -} - -impl PartialEq for AnyValue { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) +impl Display for AnyValue { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(&self.0, f) } } @@ -440,9 +192,15 @@ impl Debug for AnyValue { } } -impl Display for AnyValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(&self.0, f) +impl Clone for AnyValue { + fn clone(&self) -> Self { + Self(self.0.dyn_clone()) + } +} + +impl PartialEq for AnyValue { + fn eq(&self, other: &Self) -> bool { + self.0.dyn_eq(other) } } @@ -608,14 +366,20 @@ primitive! { primitive! { Fractional: "fractional", Value::Fractional } primitive! { Color: "color", Value::Color } primitive! { EcoString: "string", Value::Str } -primitive! { ArrayValue: "array", Value::Array } -primitive! { DictValue: "dictionary", Value::Dict } +primitive! { Array: "array", Value::Array } +primitive! { Dict: "dictionary", Value::Dict } primitive! { - TemplateValue: "template", + Template: "template", Value::Template, - Value::Str(v) => Rc::new(vec![TemplateNode::Str(v)]), + Value::Str(v) => v.into(), +} +primitive! { Function: "function", Value::Func } + +impl From<i32> for Value { + fn from(v: i32) -> Self { + Self::Int(v as i64) + } } -primitive! { FuncValue: "function", Value::Func } impl From<usize> for Value { fn from(v: usize) -> Self { diff --git a/src/exec/context.rs b/src/exec/context.rs index 4764a808..04c0169d 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -4,10 +4,10 @@ use std::rc::Rc; use super::{Exec, ExecWithMap, FontFamily, State}; use crate::diag::{Diag, DiagSet, Pass}; use crate::eco::EcoString; -use crate::eval::{ExprMap, TemplateValue}; +use crate::eval::{ExprMap, Template}; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ - AnyNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, + LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, }; use crate::syntax::{Span, SyntaxTree}; @@ -53,7 +53,7 @@ impl ExecContext { } /// Execute a template and return the result as a stack node. - pub fn exec_template_stack(&mut self, template: &TemplateValue) -> StackNode { + pub fn exec_template_stack(&mut self, template: &Template) -> StackNode { self.exec_stack(|ctx| template.exec(ctx)) } @@ -88,13 +88,13 @@ impl ExecContext { } /// Push any node into the active paragraph. - pub fn push_into_par(&mut self, node: impl Into<AnyNode>) { + pub fn push_into_par(&mut self, node: impl Into<LayoutNode>) { let align = self.state.aligns.cross; self.stack.par.push(ParChild::Any(node.into(), align)); } /// Push any node into the active stack. - pub fn push_into_stack(&mut self, node: impl Into<AnyNode>) { + pub fn push_into_stack(&mut self, node: impl Into<LayoutNode>) { self.parbreak(); let aligns = self.state.aligns; self.stack.push(StackChild::Any(node.into(), aligns)); diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 2a145fbc..752bdba5 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -10,15 +10,15 @@ use std::fmt::Write; use std::rc::Rc; use crate::diag::Pass; -use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value}; +use crate::eco::EcoString; +use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; use crate::geom::{Dir, Gen}; use crate::layout::{LayoutTree, StackChild, StackNode}; use crate::pretty::pretty; -use crate::eco::EcoString; use crate::syntax::*; /// Execute a template to produce a layout tree. -pub fn exec(template: &TemplateValue, state: State) -> Pass<LayoutTree> { +pub fn exec(template: &Template, state: State) -> Pass<LayoutTree> { let mut ctx = ExecContext::new(state); template.exec(&mut ctx); ctx.finish() @@ -50,7 +50,7 @@ impl ExecWithMap for SyntaxTree { } } -impl ExecWithMap for Node { +impl ExecWithMap for SyntaxNode { fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { match self { Self::Text(text) => ctx.push_text(text), @@ -117,12 +117,7 @@ impl ExecWithMap for EnumItem { } } -fn exec_item( - ctx: &mut ExecContext, - label: EcoString, - body: &SyntaxTree, - map: &ExprMap, -) { +fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &ExprMap) { let label = ctx.exec_stack(|ctx| ctx.push_text(label)); let body = ctx.exec_tree_stack(body, map); let stack = StackNode { @@ -159,7 +154,7 @@ impl Exec for Value { } } -impl Exec for TemplateValue { +impl Exec for Template { fn exec(&self, ctx: &mut ExecContext) { for node in self.iter() { node.exec(ctx); @@ -170,13 +165,19 @@ impl Exec for TemplateValue { impl Exec for TemplateNode { fn exec(&self, ctx: &mut ExecContext) { match self { - Self::Tree { tree, map } => tree.exec_with_map(ctx, &map), - Self::Str(v) => ctx.push_text(v), + Self::Tree(v) => v.exec(ctx), Self::Func(v) => v.exec(ctx), + Self::Str(v) => ctx.push_text(v), } } } +impl Exec for TemplateTree { + fn exec(&self, ctx: &mut ExecContext) { + self.tree.exec_with_map(ctx, &self.map) + } +} + impl Exec for TemplateFunc { fn exec(&self, ctx: &mut ExecContext) { let snapshot = ctx.state.clone(); diff --git a/src/layout/background.rs b/src/layout/background.rs index 867783bf..76ce431b 100644 --- a/src/layout/background.rs +++ b/src/layout/background.rs @@ -9,7 +9,7 @@ pub struct BackgroundNode { /// Background color / texture. pub fill: Paint, /// The child node to be filled. - pub child: AnyNode, + pub child: LayoutNode, } /// The kind of shape to use as a background. @@ -45,7 +45,7 @@ impl Layout for BackgroundNode { } } -impl From<BackgroundNode> for AnyNode { +impl From<BackgroundNode> for LayoutNode { fn from(background: BackgroundNode) -> Self { Self::new(background) } diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index dfcd4038..9fa2af8a 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -9,7 +9,7 @@ pub struct FixedNode { /// The fixed height, if any. pub height: Option<Linear>, /// The child node whose size to fix. - pub child: AnyNode, + pub child: LayoutNode, } impl Layout for FixedNode { @@ -47,7 +47,7 @@ impl Layout for FixedNode { } } -impl From<FixedNode> for AnyNode { +impl From<FixedNode> for LayoutNode { fn from(fixed: FixedNode) -> Self { Self::new(fixed) } diff --git a/src/layout/grid.rs b/src/layout/grid.rs index 06b6596c..bccdf381 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -14,7 +14,7 @@ pub struct GridNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Gen<Vec<TrackSizing>>, /// The nodes to be arranged in a grid. - pub children: Vec<AnyNode>, + pub children: Vec<LayoutNode>, } /// Defines how to size a grid cell along an axis. @@ -45,7 +45,7 @@ impl Layout for GridNode { } } -impl From<GridNode> for AnyNode { +impl From<GridNode> for LayoutNode { fn from(grid: GridNode) -> Self { Self::new(grid) } @@ -64,7 +64,7 @@ struct GridLayouter<'a> { /// The row tracks including gutter tracks. rows: Vec<TrackSizing>, /// The children of the grid. - children: &'a [AnyNode], + children: &'a [LayoutNode], /// The region to layout into. regions: Regions, /// Resolved column sizes. @@ -517,7 +517,7 @@ impl<'a> GridLayouter<'a> { /// Get the node in the cell in column `x` and row `y`. /// /// Returns `None` if it's a gutter cell. - fn cell(&self, x: usize, y: usize) -> Option<&'a AnyNode> { + fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> { assert!(x < self.cols.len()); assert!(y < self.rows.len()); diff --git a/src/layout/image.rs b/src/layout/image.rs index 9ea9db55..07d7799c 100644 --- a/src/layout/image.rs +++ b/src/layout/image.rs @@ -59,7 +59,7 @@ impl Layout for ImageNode { } } -impl From<ImageNode> for AnyNode { +impl From<ImageNode> for LayoutNode { fn from(image: ImageNode) -> Self { Self::new(image) } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a07ccdc5..523d1a92 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -71,7 +71,7 @@ pub struct PageRun { pub size: Size, /// The layout node that produces the actual pages (typically a /// [`StackNode`]). - pub child: AnyNode, + pub child: LayoutNode, } impl PageRun { @@ -86,14 +86,14 @@ impl PageRun { } } -/// A wrapper around a dynamic layouting node. -pub struct AnyNode { +/// A dynamic layouting node. +pub struct LayoutNode { node: Box<dyn Bounds>, #[cfg(feature = "layout-cache")] hash: u64, } -impl AnyNode { +impl LayoutNode { /// Create a new instance from any node that satisifies the required bounds. #[cfg(feature = "layout-cache")] pub fn new<T>(node: T) -> Self @@ -120,7 +120,7 @@ impl AnyNode { } } -impl Layout for AnyNode { +impl Layout for LayoutNode { fn layout( &self, ctx: &mut LayoutContext, @@ -143,7 +143,13 @@ impl Layout for AnyNode { } } -impl Clone for AnyNode { +impl Debug for LayoutNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +impl Clone for LayoutNode { fn clone(&self) -> Self { Self { node: self.node.dyn_clone(), @@ -153,27 +159,21 @@ impl Clone for AnyNode { } } -impl Eq for AnyNode {} +impl Eq for LayoutNode {} -impl PartialEq for AnyNode { +impl PartialEq for LayoutNode { fn eq(&self, other: &Self) -> bool { self.node.dyn_eq(other.node.as_ref()) } } #[cfg(feature = "layout-cache")] -impl Hash for AnyNode { +impl Hash for LayoutNode { fn hash<H: Hasher>(&self, state: &mut H) { state.write_u64(self.hash); } } -impl Debug for AnyNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) - } -} - trait Bounds: Layout + Debug + 'static { fn as_any(&self) -> &dyn Any; fn dyn_eq(&self, other: &dyn Bounds) -> bool; diff --git a/src/layout/pad.rs b/src/layout/pad.rs index 3770c754..619bee41 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -7,7 +7,7 @@ pub struct PadNode { /// The amount of padding. pub padding: Sides<Linear>, /// The child node whose sides to pad. - pub child: AnyNode, + pub child: LayoutNode, } impl Layout for PadNode { @@ -61,7 +61,7 @@ fn solve(padding: Sides<Linear>, size: Size) -> Size { ) } -impl From<PadNode> for AnyNode { +impl From<PadNode> for LayoutNode { fn from(pad: PadNode) -> Self { Self::new(pad) } diff --git a/src/layout/par.rs b/src/layout/par.rs index bd744201..72b3edfb 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -5,8 +5,8 @@ use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::*; -use crate::exec::FontState; use crate::eco::EcoString; +use crate::exec::FontState; use crate::util::{RangeExt, SliceExt}; type Range = std::ops::Range<usize>; @@ -32,7 +32,7 @@ pub enum ParChild { /// A run of text and how to align it in its line. Text(EcoString, Align, Rc<FontState>), /// Any child node and how to align it in its line. - Any(AnyNode, Align), + Any(LayoutNode, Align), } impl Layout for ParNode { @@ -89,7 +89,7 @@ impl ParNode { } } -impl From<ParNode> for AnyNode { +impl From<ParNode> for LayoutNode { fn from(par: ParNode) -> Self { Self::new(par) } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 516a2284..ed053dd7 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -26,7 +26,7 @@ pub enum StackChild { /// Spacing between other nodes. Spacing(Length), /// Any child node and how to align it in the stack. - Any(AnyNode, Gen<Align>), + Any(LayoutNode, Gen<Align>), } impl Layout for StackNode { @@ -39,7 +39,7 @@ impl Layout for StackNode { } } -impl From<StackNode> for AnyNode { +impl From<StackNode> for LayoutNode { fn from(stack: StackNode) -> Self { Self::new(stack) } diff --git a/src/library/elements.rs b/src/library/elements.rs index e669369a..6c6c66db 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -55,7 +55,7 @@ fn rect_impl( height: Option<Linear>, aspect: Option<N64>, fill: Option<Color>, - body: TemplateValue, + body: Template, ) -> Value { Value::template(move |ctx| { let mut stack = ctx.exec_template_stack(&body); @@ -99,7 +99,7 @@ fn ellipse_impl( height: Option<Linear>, aspect: Option<N64>, fill: Option<Color>, - body: TemplateValue, + body: Template, ) -> Value { Value::template(move |ctx| { // This padding ratio ensures that the rectangular padded region fits diff --git a/src/library/layout.rs b/src/library/layout.rs index 28ee27e1..d7ada806 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -20,7 +20,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let right = args.named(ctx, "right"); let bottom = args.named(ctx, "bottom"); let flip = args.named(ctx, "flip"); - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { let snapshot = ctx.state.clone(); @@ -108,7 +108,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let second = args.eat::<AlignValue>(ctx); let mut horizontal = args.named::<AlignValue>(ctx, "horizontal"); let mut vertical = args.named::<AlignValue>(ctx, "vertical"); - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); for value in first.into_iter().chain(second) { match value.axis() { diff --git a/src/library/mod.rs b/src/library/mod.rs index 7c3e0a71..28387218 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -18,7 +18,7 @@ use std::rc::Rc; use crate::color::{Color, RgbaColor}; use crate::eco::EcoString; -use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value}; +use crate::eval::{EvalContext, FuncArgs, Scope, Template, Value}; use crate::exec::{Exec, FontFamily}; use crate::font::{FontStyle, FontWeight, VerticalFontMetric}; use crate::geom::*; diff --git a/src/library/text.rs b/src/library/text.rs index a0ffc56c..e6de04a1 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -23,7 +23,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let serif = args.named(ctx, "serif"); let sans_serif = args.named(ctx, "sans-serif"); let monospace = args.named(ctx, "monospace"); - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { let font = ctx.state.font_mut(); @@ -163,7 +163,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let spacing = args.named(ctx, "spacing"); let leading = args.named(ctx, "leading"); let word_spacing = args.named(ctx, "word-spacing"); - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { if let Some(spacing) = spacing { @@ -194,7 +194,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } None => None, }; - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); Value::template(move |ctx| { if let Some(dir) = dir.or(iso) { @@ -239,7 +239,7 @@ fn line_impl( let thickness = args.eat(ctx).or_else(|| args.named::<Linear>(ctx, "thickness")); let offset = args.named(ctx, "offset"); let extent = args.named(ctx, "extent").unwrap_or_default(); - let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default(); + let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); // Suppress any existing strikethrough if strength is explicitly zero. let state = thickness.map_or(true, |s| !s.is_zero()).then(|| { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4c2fd129..ecce0138 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -55,10 +55,10 @@ where let mut tree = vec![]; while !p.eof() && f(p) { if let Some(mut node) = node(p, &mut at_start) { - at_start &= matches!(node, Node::Space | Node::Parbreak(_)); + at_start &= matches!(node, SyntaxNode::Space | SyntaxNode::Parbreak(_)); // Look for wide call. - if let Node::Expr(Expr::Call(call)) = &mut node { + if let SyntaxNode::Expr(Expr::Call(call)) = &mut node { if call.wide { let start = p.next_start(); let tree = tree_while(p, true, f); @@ -77,7 +77,7 @@ where } /// Parse a syntax node. -fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { +fn node(p: &mut Parser, at_start: &mut bool) -> Option<SyntaxNode> { let token = p.peek()?; let span = p.peek_span(); let node = match token { @@ -85,30 +85,32 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { Token::Space(newlines) => { *at_start |= newlines > 0; if newlines < 2 { - Node::Space + SyntaxNode::Space } else { - Node::Parbreak(span) + SyntaxNode::Parbreak(span) } } // Text. - Token::Text(text) => Node::Text(text.into()), - Token::Tilde => Node::Text("\u{00A0}".into()), - Token::HyphHyph => Node::Text("\u{2013}".into()), - Token::HyphHyphHyph => Node::Text("\u{2014}".into()), - Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), + Token::Text(text) => SyntaxNode::Text(text.into()), + Token::Tilde => SyntaxNode::Text("\u{00A0}".into()), + Token::HyphHyph => SyntaxNode::Text("\u{2013}".into()), + Token::HyphHyphHyph => SyntaxNode::Text("\u{2014}".into()), + Token::UnicodeEscape(t) => SyntaxNode::Text(unicode_escape(p, t)), // Markup. - Token::Backslash => Node::Linebreak(span), - Token::Star => Node::Strong(span), - Token::Underscore => Node::Emph(span), + Token::Backslash => SyntaxNode::Linebreak(span), + Token::Star => SyntaxNode::Strong(span), + Token::Underscore => SyntaxNode::Emph(span), Token::Raw(t) => raw(p, t), Token::Eq if *at_start => return Some(heading(p)), Token::Hyph if *at_start => return Some(list_item(p)), Token::Numbering(number) if *at_start => return Some(enum_item(p, number)), // Line-based markup that is not currently at the start of the line. - Token::Eq | Token::Hyph | Token::Numbering(_) => Node::Text(p.peek_src().into()), + Token::Eq | Token::Hyph | Token::Numbering(_) => { + SyntaxNode::Text(p.peek_src().into()) + } // Hashtag + keyword / identifier. Token::Ident(_) @@ -128,12 +130,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { } p.end_group(); - return expr.map(Node::Expr); + return expr.map(SyntaxNode::Expr); } // Block and template. - Token::LeftBrace => return Some(Node::Expr(block(p, false))), - Token::LeftBracket => return Some(Node::Expr(template(p))), + Token::LeftBrace => return Some(SyntaxNode::Expr(block(p, false))), + Token::LeftBracket => return Some(SyntaxNode::Expr(template(p))), // Comments. Token::LineComment(_) | Token::BlockComment(_) => { @@ -170,17 +172,17 @@ fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString { } /// Handle a raw block. -fn raw(p: &mut Parser, token: RawToken) -> Node { +fn raw(p: &mut Parser, token: RawToken) -> SyntaxNode { let span = p.peek_span(); let raw = resolve::resolve_raw(span, token.text, token.backticks); if !token.terminated { p.diag(error!(p.peek_span().end, "expected backtick(s)")); } - Node::Raw(raw) + SyntaxNode::Raw(raw) } /// Parse a heading. -fn heading(p: &mut Parser) -> Node { +fn heading(p: &mut Parser) -> SyntaxNode { let start = p.next_start(); p.assert(Token::Eq); @@ -197,7 +199,7 @@ fn heading(p: &mut Parser) -> Node { let body = tree_indented(p); - Node::Heading(HeadingNode { + SyntaxNode::Heading(HeadingNode { span: p.span(start), level, body: Rc::new(body), @@ -205,19 +207,19 @@ fn heading(p: &mut Parser) -> Node { } /// Parse a single list item. -fn list_item(p: &mut Parser) -> Node { +fn list_item(p: &mut Parser) -> SyntaxNode { let start = p.next_start(); p.assert(Token::Hyph); let body = tree_indented(p); - Node::List(ListItem { span: p.span(start), body }) + SyntaxNode::List(ListItem { span: p.span(start), body }) } /// Parse a single enum item. -fn enum_item(p: &mut Parser, number: Option<usize>) -> Node { +fn enum_item(p: &mut Parser, number: Option<usize>) -> SyntaxNode { let start = p.next_start(); p.assert(Token::Numbering(number)); let body = tree_indented(p); - Node::Enum(EnumItem { span: p.span(start), number, body }) + SyntaxNode::Enum(EnumItem { span: p.span(start), number, body }) } /// Parse an expression. diff --git a/src/pretty.rs b/src/pretty.rs index 83c207b5..2a825b14 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -86,7 +86,7 @@ impl Pretty for SyntaxTree { } } -impl Pretty for Node { +impl Pretty for SyntaxNode { fn pretty(&self, p: &mut Printer) { match self { // TODO: Handle escaping. @@ -493,7 +493,7 @@ impl Pretty for Value { } } -impl Pretty for ArrayValue { +impl Pretty for Array { fn pretty(&self, p: &mut Printer) { p.push('('); p.join(self, ", ", |item, p| item.pretty(p)); @@ -504,7 +504,7 @@ impl Pretty for ArrayValue { } } -impl Pretty for DictValue { +impl Pretty for Dict { fn pretty(&self, p: &mut Printer) { p.push('('); if self.is_empty() { @@ -520,13 +520,13 @@ impl Pretty for DictValue { } } -impl Pretty for TemplateValue { +impl Pretty for Template { fn pretty(&self, p: &mut Printer) { p.push_str("<template>"); } } -impl Pretty for FuncValue { +impl Pretty for Function { fn pretty(&self, p: &mut Printer) { p.push_str("<function"); if let Some(name) = self.name() { @@ -608,20 +608,9 @@ pretty_display! { #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::*; use crate::parse::parse; - macro_rules! map { - ($($k:ident: $v:expr),* $(,)?) => {{ - #[allow(unused_mut)] - let mut m = BTreeMap::new(); - $(m.insert(stringify!($k).into(), $v);)* - m - }}; - } - #[track_caller] fn roundtrip(src: &str) { test_parse(src, src); @@ -757,18 +746,15 @@ mod tests { test_value("\n", r#""\n""#); test_value("\\", r#""\\""#); test_value("\"", r#""\"""#); - test_value(Value::Array(vec![]), "()"); - test_value(vec![Value::None], "(none,)"); - test_value(vec![Value::Int(1), Value::Int(2)], "(1, 2)"); - test_value(map![], "(:)"); - test_value(map![one: Value::Int(1)], "(one: 1)"); - test_value( - map![two: Value::Bool(false), one: Value::Int(1)], - "(one: 1, two: false)", - ); - test_value(FuncValue::new(None, |_, _| Value::None), "<function>"); + test_value(array![], "()"); + test_value(array![Value::None], "(none,)"); + test_value(array![1, 2], "(1, 2)"); + test_value(dict![], "(:)"); + test_value(dict!["one" => 1], "(one: 1)"); + test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); + test_value(Function::new(None, |_, _| Value::None), "<function>"); test_value( - FuncValue::new(Some("nil".into()), |_, _| Value::None), + Function::new(Some("nil".into()), |_, _| Value::None), "<function nil>", ); test_value(AnyValue::new(1), "1"); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 1de5c1dd..895a5bc5 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -18,4 +18,4 @@ use crate::eco::EcoString; /// The abstract syntax tree. /// /// This type can represent a full parsed document. -pub type SyntaxTree = Vec<Node>; +pub type SyntaxTree = Vec<SyntaxNode>; diff --git a/src/syntax/node.rs b/src/syntax/node.rs index bb9ff098..9294fecd 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -4,7 +4,7 @@ use super::*; /// A syntax node, encompassing a single logical entity of parsed source code. #[derive(Debug, Clone, PartialEq)] -pub enum Node { +pub enum SyntaxNode { /// Plain text. Text(EcoString), /// Whitespace containing less than two newlines. diff --git a/src/syntax/span.rs b/src/syntax/span.rs index f9b1d312..8a630faa 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -120,20 +120,6 @@ impl Span { } } -impl Eq for Span {} - -impl PartialEq for Span { - fn eq(&self, other: &Self) -> bool { - !Self::cmp() || (self.start == other.start && self.end == other.end) - } -} - -impl Default for Span { - fn default() -> Self { - Span::ZERO - } -} - impl<T> From<T> for Span where T: Into<Pos> + Copy, @@ -152,12 +138,26 @@ where } } +impl Default for Span { + fn default() -> Self { + Span::ZERO + } +} + impl Debug for Span { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "<{:?}-{:?}>", self.start, self.end) } } +impl Eq for Span {} + +impl PartialEq for Span { + fn eq(&self, other: &Self) -> bool { + !Self::cmp() || (self.start == other.start && self.end == other.end) + } +} + /// A byte position in source code. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub struct Pos(pub u32); @@ -172,17 +172,6 @@ impl Pos { } } -impl<T> Add<T> for Pos -where - T: Into<Pos>, -{ - type Output = Self; - - fn add(self, rhs: T) -> Self { - Pos(self.0 + rhs.into().0) - } -} - impl From<u32> for Pos { fn from(index: u32) -> Self { Self(index) @@ -207,6 +196,17 @@ impl Debug for Pos { } } +impl<T> Add<T> for Pos +where + T: Into<Pos>, +{ + type Output = Self; + + fn add(self, rhs: T) -> Self { + Pos(self.0 + rhs.into().0) + } +} + /// A one-indexed line-column position in source code. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub struct Location { diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 1184010b..b6ee657a 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -85,19 +85,19 @@ impl_visitors! { } } - visit_node(v, node: Node) { + visit_node(v, node: SyntaxNode) { match node { - Node::Text(_) => {} - Node::Space => {} - Node::Linebreak(_) => {} - Node::Parbreak(_) => {} - Node::Strong(_) => {} - Node::Emph(_) => {} - Node::Raw(_) => {} - Node::Heading(n) => v.visit_heading(n), - Node::List(n) => v.visit_list(n), - Node::Enum(n) => v.visit_enum(n), - Node::Expr(n) => v.visit_expr(n), + SyntaxNode::Text(_) => {} + SyntaxNode::Space => {} + SyntaxNode::Linebreak(_) => {} + SyntaxNode::Parbreak(_) => {} + SyntaxNode::Strong(_) => {} + SyntaxNode::Emph(_) => {} + SyntaxNode::Raw(_) => {} + SyntaxNode::Heading(n) => v.visit_heading(n), + SyntaxNode::List(n) => v.visit_list(n), + SyntaxNode::Enum(n) => v.visit_enum(n), + SyntaxNode::Expr(n) => v.visit_expr(n), } } |
