diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-02-18 15:02:02 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-02-18 16:57:53 +0100 |
| commit | e01970b20a058ab1b4ebea916f229c9b706c84e4 (patch) | |
| tree | 5c5efc75abd6e607bd45a0602603231edf520503 | |
| parent | 05ec0f993b4a1b8481e494ee16285d23f000872f (diff) | |
Basic show rules
| -rw-r--r-- | src/eval/capture.rs | 2 | ||||
| -rw-r--r-- | src/eval/class.rs | 8 | ||||
| -rw-r--r-- | src/eval/mod.rs | 52 | ||||
| -rw-r--r-- | src/eval/styles.rs | 387 | ||||
| -rw-r--r-- | src/eval/template.rs | 13 | ||||
| -rw-r--r-- | src/eval/value.rs | 12 | ||||
| -rw-r--r-- | src/lib.rs | 7 | ||||
| -rw-r--r-- | src/library/deco.rs | 22 | ||||
| -rw-r--r-- | src/library/heading.rs | 20 | ||||
| -rw-r--r-- | src/library/link.rs | 36 | ||||
| -rw-r--r-- | src/library/list.rs | 35 | ||||
| -rw-r--r-- | src/library/math.rs | 19 | ||||
| -rw-r--r-- | src/library/raw.rs | 14 | ||||
| -rw-r--r-- | src/library/table.rs | 16 | ||||
| -rw-r--r-- | src/library/text.rs | 12 | ||||
| -rw-r--r-- | src/parse/mod.rs | 17 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 12 | ||||
| -rw-r--r-- | src/syntax/highlight.rs | 1 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 2 | ||||
| -rw-r--r-- | src/syntax/pretty.rs | 730 | ||||
| -rw-r--r-- | tests/ref/style/show.png | bin | 0 -> 19940 bytes | |||
| -rw-r--r-- | tests/typ/style/set.typ | 2 | ||||
| -rw-r--r-- | tests/typ/style/show.typ | 52 | ||||
| -rw-r--r-- | tests/typ/style/wrap.typ | 2 | ||||
| -rw-r--r-- | tests/typeset.rs | 33 |
25 files changed, 480 insertions, 1026 deletions
diff --git a/src/eval/capture.rs b/src/eval/capture.rs index d62ec55c..fd6b0101 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -50,7 +50,7 @@ impl<'a> CapturesVisitor<'a> { Some(Expr::Ident(ident)) => self.capture(ident), // A closure contains parameter bindings, which are bound before the - // body is evaluated. Take must be taken so that the default values + // body is evaluated. Care must be taken so that the default values // of named parameters cannot access previous parameter bindings. Some(Expr::Closure(expr)) => { for param in expr.params() { diff --git a/src/eval/class.rs b/src/eval/class.rs index 28e49a99..5601fb0b 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -1,3 +1,4 @@ +use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; @@ -36,6 +37,7 @@ use crate::diag::TypResult; #[derive(Clone)] pub struct Class { name: &'static str, + id: TypeId, construct: fn(&mut Vm, &mut Args) -> TypResult<Value>, set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, } @@ -48,6 +50,7 @@ impl Class { { Self { name, + id: TypeId::of::<T>(), construct: |ctx, args| { let mut styles = StyleMap::new(); T::set(args, &mut styles)?; @@ -63,6 +66,11 @@ impl Class { self.name } + /// The type id of the class. + pub fn id(&self) -> TypeId { + self.id + } + /// Return the class constructor as a function. pub fn constructor(&self) -> Func { Func::native(self.name, self.construct) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 32ffb0c9..1b61ac15 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -36,7 +36,6 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; -use crate::layout::Layout; use crate::library; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; @@ -80,15 +79,12 @@ fn eval_markup( while let Some(node) = nodes.next() { seq.push(match node { MarkupNode::Expr(Expr::Set(set)) => { - let class = set.class(); - let class = class.eval(vm)?.cast::<Class>().at(class.span())?; - let args = set.args().eval(vm)?; - let styles = class.set(args)?; - let tail = eval_markup(vm, nodes)?; - tail.styled_with_map(styles) + let styles = set.eval(vm)?; + eval_markup(vm, nodes)?.styled_with_map(styles) } MarkupNode::Expr(Expr::Show(show)) => { - return Err("show rules are not yet implemented").at(show.span()); + let styles = show.eval(vm)?; + eval_markup(vm, nodes)?.styled_with_map(styles) } MarkupNode::Expr(Expr::Wrap(wrap)) => { let tail = eval_markup(vm, nodes)?; @@ -182,7 +178,7 @@ impl Eval for ListNode { fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { Ok(Template::List(library::ListItem { number: None, - body: self.body().eval(vm)?.pack(), + body: Box::new(self.body().eval(vm)?), })) } } @@ -193,7 +189,7 @@ impl Eval for EnumNode { fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { Ok(Template::Enum(library::ListItem { number: self.number(), - body: self.body().eval(vm)?.pack(), + body: Box::new(self.body().eval(vm)?), })) } } @@ -216,9 +212,10 @@ impl Eval for Expr { Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), Self::Let(v) => v.eval(vm), - Self::Set(v) => v.eval(vm), - Self::Show(v) => v.eval(vm), - Self::Wrap(v) => v.eval(vm), + Self::Set(_) | Self::Show(_) | Self::Wrap(_) => { + Err("set, show and wrap are only allowed directly in markup") + .at(self.span()) + } Self::If(v) => v.eval(vm), Self::While(v) => v.eval(vm), Self::For(v) => v.eval(vm), @@ -551,26 +548,27 @@ impl Eval for LetExpr { } impl Eval for SetExpr { - type Output = Value; + type Output = StyleMap; - fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> { - Err("set is only allowed directly in markup").at(self.span()) + fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { + let class = self.class(); + let class = class.eval(vm)?.cast::<Class>().at(class.span())?; + let args = self.args().eval(vm)?; + class.set(args) } } impl Eval for ShowExpr { - type Output = Value; + type Output = StyleMap; - fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> { - Err("show is only allowed directly in markup").at(self.span()) - } -} - -impl Eval for WrapExpr { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> { - Err("wrap is only allowed directly in markup").at(self.span()) + fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { + let class = self.class(); + let class = class.eval(vm)?.cast::<Class>().at(class.span())?; + let closure = self.closure(); + let func = closure.eval(vm)?.cast::<Func>().at(closure.span())?; + let mut styles = StyleMap::new(); + styles.set_recipe(class.id(), func, self.span()); + Ok(styles) } } diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 2bf3f239..346eb784 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -3,21 +3,28 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; +use super::{Args, Func, Span, Template, Value, Vm}; +use crate::diag::{At, TypResult}; use crate::library::{PageNode, ParNode}; /// A map of style properties. #[derive(Default, Clone, PartialEq, Hash)] -pub struct StyleMap(Vec<Entry>); +pub struct StyleMap { + /// Settable properties. + props: Vec<Entry>, + /// Show rule recipes. + recipes: Vec<Recipe>, +} impl StyleMap { /// Create a new, empty style map. pub fn new() -> Self { - Self(vec![]) + Self { props: vec![], recipes: vec![] } } /// Whether this map contains no styles. pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.props.is_empty() && self.recipes.is_empty() } /// Create a style map from a single property-value pair. @@ -29,7 +36,7 @@ impl StyleMap { /// Set the value for a style property. pub fn set<P: Property>(&mut self, key: P, value: P::Value) { - self.0.push(Entry::new(key, value)); + self.props.push(Entry::new(key, value)); } /// Set a value for a style property if it is `Some(_)`. @@ -39,11 +46,16 @@ impl StyleMap { } } + /// Set a recipe. + pub fn set_recipe(&mut self, node: TypeId, func: Func, span: Span) { + self.recipes.push(Recipe { node, func, span }); + } + /// Mark all contained properties as _scoped_. This means that they only /// apply to the first descendant node (of their type) in the hierarchy and /// not its children, too. This is used by class constructors. pub fn scoped(mut self) -> Self { - for entry in &mut self.0 { + for entry in &mut self.props { entry.scoped = true; } self @@ -51,7 +63,7 @@ impl StyleMap { /// Whether this map contains scoped styles. pub fn has_scoped(&self) -> bool { - self.0.iter().any(|e| e.scoped) + self.props.iter().any(|e| e.scoped) } /// Make `self` the first link of the style chain `outer`. @@ -78,20 +90,24 @@ impl StyleMap { /// lifetime, for example, because you want to store the style map inside a /// packed node. pub fn apply(&mut self, outer: &Self) { - self.0.splice(0 .. 0, outer.0.clone()); + self.props.splice(0 .. 0, outer.props.iter().cloned()); + self.recipes.splice(0 .. 0, outer.recipes.iter().cloned()); } /// The highest-level interruption of the map. pub fn interruption(&self) -> Option<Interruption> { - self.0.iter().filter_map(|entry| entry.interruption()).max() + self.props.iter().filter_map(|entry| entry.interruption()).max() } } impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in self.0.iter().rev() { + for entry in self.props.iter().rev() { writeln!(f, "{:#?}", entry)?; } + for recipe in self.recipes.iter().rev() { + writeln!(f, "#[Recipe for {:?} from {:?}]", recipe.node, recipe.span)?; + } Ok(()) } } @@ -105,6 +121,169 @@ pub enum Interruption { Page, } +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[class]` proc-macro. +pub trait Property: Sync + Send + 'static { + /// The type of value that is returned when getting this property from a + /// style map. For example, this could be [`Length`](crate::geom::Length) + /// for a `WIDTH` property. + type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// Whether the property needs folding. + const FOLDING: bool = false; + + /// The type id of the node this property belongs to. + fn node_id() -> TypeId; + + /// The default value of the property. + fn default() -> Self::Value; + + /// A static reference to the default value of the property. + /// + /// This is automatically implemented through lazy-initialization in the + /// `#[class]` macro. This way, expensive defaults don't need to be + /// recreated all the time. + fn default_ref() -> &'static Self::Value; + + /// Fold the property with an outer value. + /// + /// For example, this would fold a relative font size with an outer + /// absolute font size. + #[allow(unused_variables)] + fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { + inner + } +} + +/// Marker trait that indicates that a property doesn't need folding. +pub trait Nonfolding {} + +/// An entry for a single style property. +#[derive(Clone)] +struct Entry { + pair: Arc<dyn Bounds>, + scoped: bool, +} + +impl Entry { + fn new<P: Property>(key: P, value: P::Value) -> Self { + Self { + pair: Arc::new((key, value)), + scoped: false, + } + } + + fn is<P: Property>(&self) -> bool { + self.pair.style_id() == TypeId::of::<P>() + } + + fn is_of<T: 'static>(&self) -> bool { + self.pair.node_id() == TypeId::of::<T>() + } + + fn is_of_id(&self, node: TypeId) -> bool { + self.pair.node_id() == node + } + + fn downcast<P: Property>(&self) -> Option<&P::Value> { + self.pair.as_any().downcast_ref() + } + + fn interruption(&self) -> Option<Interruption> { + if self.is_of::<PageNode>() { + Some(Interruption::Page) + } else if self.is_of::<ParNode>() { + Some(Interruption::Par) + } else { + None + } + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("#[")?; + self.pair.dyn_fmt(f)?; + if self.scoped { + f.write_str(" (scoped)")?; + } + f.write_str("]") + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.pair.dyn_eq(other) && self.scoped == other.scoped + } +} + +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.pair.hash64()); + state.write_u8(self.scoped as u8); + } +} + +/// This trait is implemented for pairs of zero-sized property keys and their +/// value types below. Although it is zero-sized, the property `P` must be part +/// of the implementing type so that we can use it in the methods (it must be a +/// constrained type parameter). +trait Bounds: Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; + fn dyn_eq(&self, other: &Entry) -> bool; + fn hash64(&self) -> u64; + fn node_id(&self) -> TypeId; + fn style_id(&self) -> TypeId; +} + +impl<P: Property> Bounds for (P, P::Value) { + fn as_any(&self) -> &dyn Any { + &self.1 + } + + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} = {:?}", P::NAME, self.1) + } + + fn dyn_eq(&self, other: &Entry) -> bool { + self.style_id() == other.pair.style_id() + && if let Some(other) = other.downcast::<P>() { + &self.1 == other + } else { + false + } + } + + fn hash64(&self) -> u64 { + let mut state = fxhash::FxHasher64::default(); + self.style_id().hash(&mut state); + self.1.hash(&mut state); + state.finish() + } + + fn node_id(&self) -> TypeId { + P::node_id() + } + + fn style_id(&self) -> TypeId { + TypeId::of::<P>() + } +} + +/// A show rule recipe. +#[derive(Debug, Clone, PartialEq, Hash)] +struct Recipe { + node: TypeId, + func: Func, + span: Span, +} + /// A chain of style maps, similar to a linked list. /// /// A style chain allows to conceptually merge (and fold) properties from @@ -162,11 +341,18 @@ impl<'a> StyleChain<'a> { *self = self.outer.copied().unwrap_or_default(); } + /// Return the span of a recipe for the given node. + pub(crate) fn recipe_span(self, node: TypeId) -> Option<Span> { + self.recipes(node).next().map(|recipe| recipe.span) + } + /// Return the chain, but without the last link if that one contains only /// scoped styles. This is a hack. pub(crate) fn unscoped(mut self, node: TypeId) -> Self { if let Some(Link::Map(map)) = self.link { - if map.0.iter().all(|e| e.scoped && e.is_of_id(node)) { + if map.props.iter().all(|e| e.scoped && e.is_of_id(node)) + && map.recipes.is_empty() + { self.pop(); } } @@ -225,6 +411,21 @@ impl<'a> StyleChain<'a> { } } + /// Execute a user recipe for a node. + pub fn show( + self, + node: &dyn Any, + vm: &mut Vm, + values: impl IntoIterator<Item = Value>, + ) -> TypResult<Option<Template>> { + Ok(if let Some(recipe) = self.recipes(node.type_id()).next() { + let args = Args::from_values(recipe.span, values); + Some(recipe.func.call(vm, args)?.cast().at(recipe.span)?) + } else { + None + }) + } + /// Insert a barrier into the style chain. /// /// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style @@ -233,7 +434,7 @@ impl<'a> StyleChain<'a> { pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { if self .maps() - .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node))) + .any(|map| map.props.iter().any(|entry| entry.scoped && entry.is_of_id(node))) { StyleChain { link: Some(Link::Barrier(node)), @@ -252,7 +453,7 @@ impl<'a> StyleChain<'a> { self.links().flat_map(move |link| { let mut entries: &[Entry] = &[]; match link { - Link::Map(map) => entries = &map.0, + Link::Map(map) => entries = &map.props, Link::Barrier(id) => depth += (id == P::node_id()) as usize, } entries @@ -263,6 +464,13 @@ impl<'a> StyleChain<'a> { }) } + /// Iterate over the recipes for the given node. + fn recipes(self, node: TypeId) -> impl Iterator<Item = &'a Recipe> { + self.maps() + .flat_map(|map| map.recipes.iter().rev()) + .filter(move |recipe| recipe.node == node) + } + /// Iterate over the map links of the chain. fn maps(self) -> impl Iterator<Item = &'a StyleMap> { self.links().filter_map(|link| match link { @@ -443,158 +651,3 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> { Self::new() } } - -/// Style property keys. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[class]` proc-macro. -pub trait Property: Sync + Send + 'static { - /// The type of value that is returned when getting this property from a - /// style map. For example, this could be [`Length`](crate::geom::Length) - /// for a `WIDTH` property. - type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// Whether the property needs folding. - const FOLDING: bool = false; - - /// The type id of the node this property belongs to. - fn node_id() -> TypeId; - - /// The default value of the property. - fn default() -> Self::Value; - - /// A static reference to the default value of the property. - /// - /// This is automatically implemented through lazy-initialization in the - /// `#[class]` macro. This way, expensive defaults don't need to be - /// recreated all the time. - fn default_ref() -> &'static Self::Value; - - /// Fold the property with an outer value. - /// - /// For example, this would fold a relative font size with an outer - /// absolute font size. - #[allow(unused_variables)] - fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { - inner - } -} - -/// Marker trait that indicates that a property doesn't need folding. -pub trait Nonfolding {} - -/// An entry for a single style property. -#[derive(Clone)] -struct Entry { - pair: Arc<dyn Bounds>, - scoped: bool, -} - -impl Entry { - fn new<P: Property>(key: P, value: P::Value) -> Self { - Self { - pair: Arc::new((key, value)), - scoped: false, - } - } - - fn is<P: Property>(&self) -> bool { - self.pair.style_id() == TypeId::of::<P>() - } - - fn is_of<T: 'static>(&self) -> bool { - self.pair.node_id() == TypeId::of::<T>() - } - - fn is_of_id(&self, node: TypeId) -> bool { - self.pair.node_id() == node - } - - fn downcast<P: Property>(&self) -> Option<&P::Value> { - self.pair.as_any().downcast_ref() - } - - fn interruption(&self) -> Option<Interruption> { - if self.is_of::<PageNode>() { - Some(Interruption::Page) - } else if self.is_of::<ParNode>() { - Some(Interruption::Par) - } else { - None - } - } -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("#[")?; - self.pair.dyn_fmt(f)?; - if self.scoped { - f.write_str(" (scoped)")?; - } - f.write_str("]") - } -} - -impl PartialEq for Entry { - fn eq(&self, other: &Self) -> bool { - self.pair.dyn_eq(other) && self.scoped == other.scoped - } -} - -impl Hash for Entry { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.pair.hash64()); - state.write_u8(self.scoped as u8); - } -} - -/// This trait is implemented for pairs of zero-sized property keys and their -/// value types below. Although it is zero-sized, the property `P` must be part -/// of the implementing type so that we can use it in the methods (it must be a -/// constrained type parameter). -trait Bounds: Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; - fn dyn_eq(&self, other: &Entry) -> bool; - fn hash64(&self) -> u64; - fn node_id(&self) -> TypeId; - fn style_id(&self) -> TypeId; -} - -impl<P: Property> Bounds for (P, P::Value) { - fn as_any(&self) -> &dyn Any { - &self.1 - } - - fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} = {:?}", P::NAME, self.1) - } - - fn dyn_eq(&self, other: &Entry) -> bool { - self.style_id() == other.pair.style_id() - && if let Some(other) = other.downcast::<P>() { - &self.1 == other - } else { - false - } - } - - fn hash64(&self) -> u64 { - let mut state = fxhash::FxHasher64::default(); - self.style_id().hash(&mut state); - self.1.hash(&mut state); - state.finish() - } - - fn node_id(&self) -> TypeId { - P::node_id() - } - - fn style_id(&self) -> TypeId { - TypeId::of::<P>() - } -} diff --git a/src/eval/template.rs b/src/eval/template.rs index 1f1544e6..465e9195 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -403,9 +403,16 @@ impl<'a> Builder<'a> { } } Template::Show(node) => { + let id = node.id(); + if vm.rules.contains(&id) { + let span = styles.recipe_span(id).unwrap(); + return Err("show rule is recursive").at(span)?; + } + vm.rules.push(id); let template = node.show(vm, styles)?; let stored = self.tpa.alloc(template); - self.process(vm, stored, styles.unscoped(node.id()))?; + self.process(vm, stored, styles.unscoped(id))?; + vm.rules.pop(); } Template::Styled(styled) => { let (sub, map) = styled.as_ref(); @@ -455,8 +462,8 @@ impl<'a> Builder<'a> { }; let template = match kind { - UNORDERED => Template::show(ListNode::<UNORDERED> { items, wide, start: 1 }), - ORDERED | _ => Template::show(ListNode::<ORDERED> { items, wide, start: 1 }), + UNORDERED => Template::show(ListNode::<UNORDERED> { start: 1, wide, items }), + ORDERED | _ => Template::show(ListNode::<ORDERED> { start: 1, wide, items }), }; let stored = self.tpa.alloc(template); diff --git a/src/eval/value.rs b/src/eval/value.rs index 2de210d5..952c7293 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -336,7 +336,7 @@ pub trait Cast<V = Value>: Sized { macro_rules! primitive { ( $type:ty: $name:literal, $variant:ident - $(, $other:ident($binding:ident) => $out:expr)* + $(, $other:ident$(($binding:ident))? => $out:expr)* ) => { impl Type for $type { const TYPE_NAME: &'static str = $name; @@ -344,13 +344,14 @@ macro_rules! primitive { impl Cast for $type { fn is(value: &Value) -> bool { - matches!(value, Value::$variant(_) $(| Value::$other(_))*) + 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),)* + $(Value::$other$(($binding))? => Ok($out),)* v => Err(format!( "expected {}, found {}", Self::TYPE_NAME, @@ -366,6 +367,9 @@ macro_rules! primitive { } } }; + + (@$other:ident($binding:ident)) => { Value::$other(_) }; + (@$other:ident) => { Value::$other }; } /// Implement traits for dynamic types. @@ -440,7 +444,7 @@ primitive! { Color: "color", Color } primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } -primitive! { Template: "template", Template } +primitive! { Template: "template", Template, None => Template::new() } primitive! { Func: "function", Func, Class(v) => v.constructor() } primitive! { Args: "arguments", Args } primitive! { Class: "class", Class } @@ -25,7 +25,7 @@ //! [evaluate]: Vm::evaluate //! [module]: eval::Module //! [template]: eval::Template -//! [layouted]: eval::Template::layout +//! [layouted]: eval::Template::layout_pages //! [cache]: layout::LayoutCache //! [PDF]: export::pdf @@ -51,6 +51,7 @@ pub mod parse; pub mod source; pub mod syntax; +use std::any::TypeId; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -218,6 +219,9 @@ pub struct Vm<'a> { pub modules: HashMap<SourceId, Module>, /// The active scopes. pub scopes: Scopes<'a>, + /// Currently evaluated show rules. This is used to prevent recursive show + /// rules. + pub rules: Vec<TypeId>, /// How deeply nested the current layout tree position is. #[cfg(feature = "layout-cache")] pub level: usize, @@ -236,6 +240,7 @@ impl<'a> Vm<'a> { route: vec![], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), + rules: vec![], level: 0, } } diff --git a/src/library/deco.rs b/src/library/deco.rs index 79026288..aac79d05 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -32,15 +32,19 @@ impl<const L: DecoLine> DecoNode<L> { } impl<const L: DecoLine> Show for DecoNode<L> { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { - Ok(self.0.clone().styled(TextNode::LINES, vec![Decoration { - line: L, - stroke: styles.get(Self::STROKE), - thickness: styles.get(Self::THICKNESS), - offset: styles.get(Self::OFFSET), - extent: styles.get(Self::EXTENT), - evade: styles.get(Self::EVADE), - }])) + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + Ok(styles + .show(self, vm, [Value::Template(self.0.clone())])? + .unwrap_or_else(|| { + self.0.clone().styled(TextNode::LINES, vec![Decoration { + line: L, + stroke: styles.get(Self::STROKE), + thickness: styles.get(Self::THICKNESS), + offset: styles.get(Self::OFFSET), + extent: styles.get(Self::EXTENT), + evade: styles.get(Self::EVADE), + }]) + })) } } diff --git a/src/library/heading.rs b/src/library/heading.rs index 1b8fcef9..49975655 100644 --- a/src/library/heading.rs +++ b/src/library/heading.rs @@ -34,6 +34,8 @@ impl HeadingNode { pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero()); /// The extra padding below the heading. pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero()); + /// Whether the heading is block-level. + pub const BLOCK: Leveled<bool> = Leveled::Value(true); fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { Ok(Template::show(Self { @@ -51,6 +53,14 @@ impl Show for HeadingNode { }; } + // Resolve the user recipe. + let mut body = styles + .show(self, vm, [ + Value::Int(self.level as i64), + Value::Template(self.body.clone()), + ])? + .unwrap_or_else(|| self.body.clone()); + let mut map = StyleMap::new(); map.set(TextNode::SIZE, resolve!(Self::SIZE)); @@ -76,7 +86,6 @@ impl Show for HeadingNode { } let mut seq = vec![]; - let mut body = self.body.clone(); if resolve!(Self::UNDERLINE) { body = body.underlined(); } @@ -93,9 +102,12 @@ impl Show for HeadingNode { seq.push(Template::Vertical(below.into())); } - Ok(Template::block( - Template::sequence(seq).styled_with_map(map), - )) + let mut template = Template::sequence(seq).styled_with_map(map); + if resolve!(Self::BLOCK) { + template = Template::block(template); + } + + Ok(template) } } diff --git a/src/library/link.rs b/src/library/link.rs index 95fff089..41209549 100644 --- a/src/library/link.rs +++ b/src/library/link.rs @@ -10,7 +10,7 @@ pub struct LinkNode { /// The url the link points to. pub url: EcoString, /// How the link is represented. - pub body: Template, + pub body: Option<Template>, } #[class] @@ -22,22 +22,31 @@ impl LinkNode { pub const UNDERLINE: bool = true; fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { - let url = args.expect::<EcoString>("url")?; - let body = args.find()?.unwrap_or_else(|| { - let mut text = url.as_str(); - for prefix in ["mailto:", "tel:"] { - text = text.trim_start_matches(prefix); - } - let shorter = text.len() < url.len(); - Template::Text(if shorter { text.into() } else { url.clone() }) - }); - - Ok(Template::show(Self { url, body })) + Ok(Template::show(Self { + url: args.expect::<EcoString>("url")?, + body: args.find()?, + })) } } impl Show for LinkNode { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + let mut body = styles + .show(self, vm, [Value::Str(self.url.clone()), match &self.body { + Some(body) => Value::Template(body.clone()), + None => Value::None, + }])? + .or_else(|| self.body.clone()) + .unwrap_or_else(|| { + let url = &self.url; + let mut text = url.as_str(); + for prefix in ["mailto:", "tel:"] { + text = text.trim_start_matches(prefix); + } + let shorter = text.len() < url.len(); + Template::Text(if shorter { text.into() } else { url.clone() }) + }); + let mut map = StyleMap::new(); map.set(TextNode::LINK, Some(self.url.clone())); @@ -45,7 +54,6 @@ impl Show for LinkNode { map.set(TextNode::FILL, fill); } - let mut body = self.body.clone(); if styles.get(Self::UNDERLINE) { body = body.underlined(); } diff --git a/src/library/list.rs b/src/library/list.rs index 13f21a04..37dda843 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -8,13 +8,13 @@ use crate::parse::Scanner; /// An unordered or ordered list. #[derive(Debug, Hash)] pub struct ListNode<const L: ListKind> { - /// The individual bulleted or numbered items. - pub items: Vec<ListItem>, + /// Where the list starts. + pub start: usize, /// If true, there is paragraph spacing between the items, if false /// there is list spacing between the items. pub wide: bool, - /// Where the list starts. - pub start: usize, + /// The individual bulleted or numbered items. + pub items: Vec<ListItem>, } /// An item in a list. @@ -23,7 +23,7 @@ pub struct ListItem { /// The number of the item. pub number: Option<usize>, /// The node that produces the item's body. - pub body: LayoutNode, + pub body: Box<Template>, } #[class] @@ -39,19 +39,27 @@ impl<const L: ListKind> ListNode<L> { fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { Ok(Template::show(Self { + start: args.named("start")?.unwrap_or(0), + wide: args.named("wide")?.unwrap_or(false), items: args .all()? .into_iter() - .map(|body| ListItem { number: None, body }) + .map(|body| ListItem { number: None, body: Box::new(body) }) .collect(), - wide: args.named("wide")?.unwrap_or(false), - start: args.named("start")?.unwrap_or(0), })) } } impl<const L: ListKind> Show for ListNode<L> { fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + if let Some(template) = styles.show( + self, + vm, + self.items.iter().map(|item| Value::Template((*item.body).clone())), + )? { + return Ok(template); + } + let mut children = vec![]; let mut number = self.start; @@ -66,7 +74,7 @@ impl<const L: ListKind> Show for ListNode<L> { children.push(LayoutNode::default()); children.push(label.resolve(vm, L, number)?.pack()); children.push(LayoutNode::default()); - children.push(item.body.clone()); + children.push((*item.body).clone().pack()); number += 1; } @@ -119,8 +127,6 @@ pub enum Label { Pattern(EcoString, Numbering, bool, EcoString), /// A bare template. Template(Template), - /// A simple mapping from an item number to a template. - Mapping(fn(usize) -> Template), /// A closure mapping from an item number to a value. Func(Func, Span), } @@ -144,10 +150,9 @@ impl Label { Template::Text(format_eco!("{}{}{}", prefix, mid, suffix)) } Self::Template(template) => template.clone(), - Self::Mapping(mapping) => mapping(number), - &Self::Func(ref func, span) => { - let args = Args::from_values(span, [Value::Int(number as i64)]); - func.call(vm, args)?.cast().at(span)? + Self::Func(func, span) => { + let args = Args::from_values(*span, [Value::Int(number as i64)]); + func.call(vm, args)?.cast().at(*span)? } }) } diff --git a/src/library/math.rs b/src/library/math.rs index d3c8b5e5..40a1990e 100644 --- a/src/library/math.rs +++ b/src/library/math.rs @@ -22,11 +22,18 @@ impl MathNode { } impl Show for MathNode { - fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> { - let mut template = Template::Text(self.formula.trim().into()); - if self.display { - template = Template::Block(template.pack()); - } - Ok(template.monospaced()) + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + Ok(styles + .show(self, vm, [ + Value::Str(self.formula.clone()), + Value::Bool(self.display), + ])? + .unwrap_or_else(|| { + let mut template = Template::Text(self.formula.trim().into()); + if self.display { + template = Template::Block(template.pack()); + } + template.monospaced() + })) } } diff --git a/src/library/raw.rs b/src/library/raw.rs index da926679..bb4e2c96 100644 --- a/src/library/raw.rs +++ b/src/library/raw.rs @@ -40,8 +40,20 @@ impl RawNode { } impl Show for RawNode { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { let lang = styles.get_ref(Self::LANG).as_ref(); + + if let Some(template) = styles.show(self, vm, [ + Value::Str(self.text.clone()), + match lang { + Some(lang) => Value::Str(lang.clone()), + None => Value::None, + }, + Value::Bool(self.block), + ])? { + return Ok(template); + } + let foreground = THEME .settings .foreground diff --git a/src/library/table.rs b/src/library/table.rs index 8c088c09..f4de0f55 100644 --- a/src/library/table.rs +++ b/src/library/table.rs @@ -11,7 +11,7 @@ pub struct TableNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Spec<Vec<TrackSizing>>, /// The nodes to be arranged in the table. - pub children: Vec<LayoutNode>, + pub children: Vec<Template>, } #[class] @@ -55,7 +55,15 @@ impl TableNode { } impl Show for TableNode { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + if let Some(template) = styles.show( + self, + vm, + self.children.iter().map(|child| Value::Template(child.clone())), + )? { + return Ok(template); + } + let primary = styles.get(Self::PRIMARY); let secondary = styles.get(Self::SECONDARY); let thickness = styles.get(Self::THICKNESS); @@ -68,8 +76,8 @@ impl Show for TableNode { .iter() .cloned() .enumerate() - .map(|(i, mut child)| { - child = child.padded(Sides::splat(padding)); + .map(|(i, child)| { + let mut child = child.pack().padded(Sides::splat(padding)); if let Some(stroke) = stroke { child = child.stroked(stroke); diff --git a/src/library/text.rs b/src/library/text.rs index 721a8eac..a67fbcf5 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -123,8 +123,10 @@ impl StrongNode { } impl Show for StrongNode { - fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> { - Ok(self.0.clone().styled(TextNode::STRONG, true)) + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + Ok(styles + .show(self, vm, [Value::Template(self.0.clone())])? + .unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true))) } } @@ -140,8 +142,10 @@ impl EmphNode { } impl Show for EmphNode { - fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> { - Ok(self.0.clone().styled(TextNode::EMPH, true)) + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { + Ok(styles + .show(self, vm, [Value::Template(self.0.clone())])? + .unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true))) } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index fbace15b..c14c45cf 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -752,9 +752,20 @@ fn set_expr(p: &mut Parser) -> ParseResult { fn show_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ShowExpr, |p| { p.eat_assert(&NodeKind::Show); - expr(p)?; - p.eat_expect(&NodeKind::As)?; - expr(p) + ident(p)?; + if !p.at(&NodeKind::LeftParen) { + p.expected_found("parameter list"); + return Err(ParseError); + } + p.perform(NodeKind::Closure, |p| { + let marker = p.marker(); + p.start_group(Group::Paren); + collection(p); + p.end_group(); + params(p, marker); + p.eat_expect(&NodeKind::As)?; + expr(p) + }) }) } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 8b88096a..9d22121b 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -919,14 +919,14 @@ node! { } impl ShowExpr { - /// The pattern that decides which node's appearence to redefine. - pub fn pattern(&self) -> Expr { - self.0.cast_first_child().expect("show expression is missing pattern") + /// The class to set the show rule for. + pub fn class(&self) -> Ident { + self.0.cast_first_child().expect("show expression is missing class") } - /// The expression that defines the node's appearence. - pub fn body(&self) -> Expr { - self.0.cast_last_child().expect("show expression is missing body") + /// The closure that defines the rule. + pub fn closure(&self) -> ClosureExpr { + self.0.cast_first_child().expect("show expression is missing closure") } } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index b806b4e4..af6fb0df 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -181,6 +181,7 @@ impl Category { } NodeKind::WithExpr => Some(Category::Function), NodeKind::SetExpr => Some(Category::Function), + NodeKind::ShowExpr => Some(Category::Function), NodeKind::Call => Some(Category::Function), _ => Some(Category::Variable), }, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index c0de081d..c702199e 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -2,7 +2,6 @@ pub mod ast; mod highlight; -mod pretty; mod span; use std::fmt::{self, Debug, Display, Formatter}; @@ -11,7 +10,6 @@ use std::ops::Range; use std::sync::Arc; pub use highlight::*; -pub use pretty::*; pub use span::*; use self::ast::{MathNode, RawNode, TypedNode}; diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs deleted file mode 100644 index 07ab979b..00000000 --- a/src/syntax/pretty.rs +++ /dev/null @@ -1,730 +0,0 @@ -//! Pretty printing. - -use std::fmt::{self, Arguments, Write}; - -use super::ast::*; - -/// Pretty print an item and return the resulting string. -pub fn pretty<T>(item: &T) -> String -where - T: Pretty + ?Sized, -{ - let mut p = Printer::new(); - item.pretty(&mut p); - p.finish() -} - -/// Pretty print an item. -pub trait Pretty { - /// Pretty print this item into the given printer. - fn pretty(&self, p: &mut Printer); -} - -/// A buffer into which items can be pretty printed. -#[derive(Default)] -pub struct Printer { - buf: String, -} - -impl Printer { - /// Create a new pretty printer. - pub fn new() -> Self { - Self::default() - } - - /// Push a character into the buffer. - pub fn push(&mut self, c: char) { - self.buf.push(c); - } - - /// Push a string into the buffer. - pub fn push_str(&mut self, string: &str) { - self.buf.push_str(string); - } - - /// Write formatted items into the buffer. - pub fn write_fmt(&mut self, fmt: Arguments<'_>) -> fmt::Result { - Write::write_fmt(self, fmt) - } - - /// Write a list of items joined by a joiner and return how many there were. - pub fn join<T, I, F>(&mut self, items: I, joiner: &str, mut write_item: F) -> usize - where - I: IntoIterator<Item = T>, - F: FnMut(T, &mut Self), - { - let mut count = 0; - let mut iter = items.into_iter(); - if let Some(first) = iter.next() { - write_item(first, self); - count += 1; - } - for item in iter { - self.push_str(joiner); - write_item(item, self); - count += 1; - } - count - } - - /// Finish pretty printing and return the underlying buffer. - pub fn finish(self) -> String { - self.buf - } -} - -impl Write for Printer { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.push_str(s); - Ok(()) - } -} - -impl Pretty for Markup { - fn pretty(&self, p: &mut Printer) { - for node in self.nodes() { - node.pretty(p); - } - } -} - -impl Pretty for MarkupNode { - fn pretty(&self, p: &mut Printer) { - match self { - // TODO: Handle escaping. - Self::Space => p.push(' '), - Self::Linebreak => p.push_str(r"\"), - Self::Parbreak => p.push_str("\n\n"), - Self::Strong(strong) => strong.pretty(p), - Self::Emph(emph) => emph.pretty(p), - Self::Text(text) => p.push_str(text), - Self::Raw(raw) => raw.pretty(p), - Self::Math(math) => math.pretty(p), - Self::Heading(heading) => heading.pretty(p), - Self::List(list) => list.pretty(p), - Self::Enum(enum_) => enum_.pretty(p), - Self::Expr(expr) => { - if expr.has_short_form() { - p.push('#'); - } - expr.pretty(p); - } - } - } -} - -impl Pretty for StrongNode { - fn pretty(&self, p: &mut Printer) { - p.push('*'); - self.body().pretty(p); - p.push('*'); - } -} - -impl Pretty for EmphNode { - fn pretty(&self, p: &mut Printer) { - p.push('_'); - self.body().pretty(p); - p.push('_'); - } -} - -impl Pretty for RawNode { - fn pretty(&self, p: &mut Printer) { - // Find out how many backticks we need. - let mut backticks = 1; - - // Language tag and block-level are only possible with 3+ backticks. - if self.lang.is_some() || self.block { - backticks = 3; - } - - // More backticks may be required if there are lots of consecutive - // backticks. - let mut count = 0; - for c in self.text.chars() { - if c == '`' { - count += 1; - backticks = backticks.max(3).max(count + 1); - } else { - count = 0; - } - } - - // Starting backticks. - for _ in 0 .. backticks { - p.push('`'); - } - - // Language tag. - if let Some(lang) = &self.lang { - p.push_str(lang); - } - - // Start untrimming. - if self.block { - p.push('\n'); - } else if backticks >= 3 { - p.push(' '); - } - - // The lines. - p.push_str(&self.text); - - // End untrimming. - if self.block { - p.push('\n'); - } else if self.text.trim_end().ends_with('`') { - p.push(' '); - } - - // Ending backticks. - for _ in 0 .. backticks { - p.push('`'); - } - } -} - -impl Pretty for MathNode { - fn pretty(&self, p: &mut Printer) { - p.push('$'); - if self.display { - p.push('['); - } - p.push_str(&self.formula); - if self.display { - p.push(']'); - } - p.push('$'); - } -} - -impl Pretty for HeadingNode { - fn pretty(&self, p: &mut Printer) { - for _ in 0 .. self.level() { - p.push('='); - } - p.push(' '); - self.body().pretty(p); - } -} - -impl Pretty for ListNode { - fn pretty(&self, p: &mut Printer) { - p.push_str("- "); - self.body().pretty(p); - } -} - -impl Pretty for EnumNode { - fn pretty(&self, p: &mut Printer) { - if let Some(number) = self.number() { - write!(p, "{}", number).unwrap(); - } - p.push_str(". "); - self.body().pretty(p); - } -} - -impl Pretty for Expr { - fn pretty(&self, p: &mut Printer) { - match self { - Self::Lit(v) => v.pretty(p), - Self::Ident(v) => v.pretty(p), - Self::Array(v) => v.pretty(p), - Self::Dict(v) => v.pretty(p), - Self::Template(v) => v.pretty(p), - Self::Group(v) => v.pretty(p), - Self::Block(v) => v.pretty(p), - Self::Unary(v) => v.pretty(p), - Self::Binary(v) => v.pretty(p), - Self::Call(v) => v.pretty(p), - Self::Closure(v) => v.pretty(p), - Self::With(v) => v.pretty(p), - Self::Let(v) => v.pretty(p), - Self::Set(v) => v.pretty(p), - Self::Show(v) => v.pretty(p), - Self::Wrap(v) => v.pretty(p), - Self::If(v) => v.pretty(p), - Self::While(v) => v.pretty(p), - Self::For(v) => v.pretty(p), - Self::Import(v) => v.pretty(p), - Self::Include(v) => v.pretty(p), - Self::Break(v) => v.pretty(p), - Self::Continue(v) => v.pretty(p), - Self::Return(v) => v.pretty(p), - } - } -} - -impl Pretty for Lit { - fn pretty(&self, p: &mut Printer) { - match self.kind() { - LitKind::None => p.push_str("none"), - LitKind::Auto => p.push_str("auto"), - LitKind::Bool(v) => write!(p, "{}", v).unwrap(), - LitKind::Int(v) => write!(p, "{}", v).unwrap(), - LitKind::Float(v) => write!(p, "{}", v).unwrap(), - LitKind::Length(v, u) => write!(p, "{}{:?}", v, u).unwrap(), - LitKind::Angle(v, u) => write!(p, "{}{:?}", v, u).unwrap(), - LitKind::Percent(v) => write!(p, "{}%", v).unwrap(), - LitKind::Fractional(v) => write!(p, "{}fr", v).unwrap(), - LitKind::Str(v) => write!(p, "{:?}", v).unwrap(), - } - } -} - -impl Pretty for ArrayExpr { - fn pretty(&self, p: &mut Printer) { - p.push('('); - - let items = self.items(); - let len = p.join(items, ", ", |item, p| item.pretty(p)); - if len == 1 { - p.push(','); - } - p.push(')'); - } -} - -impl Pretty for DictExpr { - fn pretty(&self, p: &mut Printer) { - p.push('('); - let len = p.join(self.items(), ", ", |named, p| named.pretty(p)); - if len == 0 { - p.push(':'); - } - p.push(')'); - } -} - -impl Pretty for Named { - fn pretty(&self, p: &mut Printer) { - self.name().pretty(p); - p.push_str(": "); - self.expr().pretty(p); - } -} - -impl Pretty for TemplateExpr { - fn pretty(&self, p: &mut Printer) { - p.push('['); - self.body().pretty(p); - p.push(']'); - } -} - -impl Pretty for GroupExpr { - fn pretty(&self, p: &mut Printer) { - p.push('('); - self.expr().pretty(p); - p.push(')'); - } -} - -impl Pretty for BlockExpr { - fn pretty(&self, p: &mut Printer) { - p.push('{'); - if self.exprs().count() > 1 { - p.push(' '); - } - let len = p.join(self.exprs(), "; ", |expr, p| expr.pretty(p)); - if len > 1 { - p.push(' '); - } - p.push('}'); - } -} - -impl Pretty for UnaryExpr { - fn pretty(&self, p: &mut Printer) { - let op = self.op(); - op.pretty(p); - if op == UnOp::Not { - p.push(' '); - } - self.expr().pretty(p); - } -} - -impl Pretty for UnOp { - fn pretty(&self, p: &mut Printer) { - p.push_str(self.as_str()); - } -} - -impl Pretty for BinaryExpr { - fn pretty(&self, p: &mut Printer) { - self.lhs().pretty(p); - p.push(' '); - self.op().pretty(p); - p.push(' '); - self.rhs().pretty(p); - } -} - -impl Pretty for BinOp { - fn pretty(&self, p: &mut Printer) { - p.push_str(self.as_str()); - } -} - -impl Pretty for CallExpr { - fn pretty(&self, p: &mut Printer) { - self.callee().pretty(p); - - let mut write_args = |items: &[CallArg]| { - p.push('('); - p.join(items, ", ", |item, p| item.pretty(p)); - p.push(')'); - }; - - let args: Vec<_> = self.args().items().collect(); - match args.as_slice() { - // This can be moved behind the arguments. - // - // Example: Transforms "#v(a, [b])" => "#v(a)[b]". - [head @ .., CallArg::Pos(Expr::Template(template))] => { - if !head.is_empty() { - write_args(head); - } - template.pretty(p); - } - items => write_args(items), - } - } -} - -impl Pretty for CallArgs { - fn pretty(&self, p: &mut Printer) { - p.join(self.items(), ", ", |item, p| item.pretty(p)); - } -} - -impl Pretty for CallArg { - fn pretty(&self, p: &mut Printer) { - match self { - Self::Pos(expr) => expr.pretty(p), - Self::Named(named) => named.pretty(p), - Self::Spread(expr) => { - p.push_str(".."); - expr.pretty(p); - } - } - } -} - -impl Pretty for ClosureExpr { - fn pretty(&self, p: &mut Printer) { - let params: Vec<_> = self.params().collect(); - if let [param] = params.as_slice() { - param.pretty(p); - } else { - p.push('('); - p.join(params.iter(), ", ", |item, p| item.pretty(p)); - p.push(')'); - } - p.push_str(" => "); - self.body().pretty(p); - } -} - -impl Pretty for ClosureParam { - fn pretty(&self, p: &mut Printer) { - match self { - Self::Pos(ident) => ident.pretty(p), - Self::Named(named) => named.pretty(p), - Self::Sink(ident) => { - p.push_str(".."); - ident.pretty(p); - } - } - } -} - -impl Pretty for WithExpr { - fn pretty(&self, p: &mut Printer) { - self.callee().pretty(p); - p.push_str(" with ("); - self.args().pretty(p); - p.push(')'); - } -} - -impl Pretty for LetExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("let "); - self.binding().pretty(p); - if let Some(Expr::Closure(closure)) = self.init() { - p.push('('); - p.join(closure.params(), ", ", |item, p| item.pretty(p)); - p.push_str(") = "); - closure.body().pretty(p); - } else if let Some(init) = self.init() { - p.push_str(" = "); - init.pretty(p); - } - } -} - -impl Pretty for SetExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("set "); - self.class().pretty(p); - p.push_str("("); - self.args().pretty(p); - p.push(')'); - } -} - -impl Pretty for ShowExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("show "); - self.pattern().pretty(p); - p.push_str(" as "); - self.body().pretty(p); - } -} - -impl Pretty for WrapExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("wrap "); - self.binding().pretty(p); - p.push_str(" in "); - self.body().pretty(p); - } -} - -impl Pretty for IfExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("if "); - self.condition().pretty(p); - p.push(' '); - self.if_body().pretty(p); - if let Some(expr) = self.else_body() { - p.push_str(" else "); - expr.pretty(p); - } - } -} - -impl Pretty for WhileExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("while "); - self.condition().pretty(p); - p.push(' '); - self.body().pretty(p); - } -} - -impl Pretty for ForExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("for "); - self.pattern().pretty(p); - p.push_str(" in "); - self.iter().pretty(p); - p.push(' '); - self.body().pretty(p); - } -} - -impl Pretty for ForPattern { - fn pretty(&self, p: &mut Printer) { - if let Some(key) = self.key() { - key.pretty(p); - p.push_str(", "); - } - - self.value().pretty(p); - } -} - -impl Pretty for ImportExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("import "); - self.imports().pretty(p); - p.push_str(" from "); - self.path().pretty(p); - } -} - -impl Pretty for Imports { - fn pretty(&self, p: &mut Printer) { - match self { - Self::Wildcard => p.push('*'), - Self::Items(idents) => { - p.join(idents, ", ", |item, p| item.pretty(p)); - } - } - } -} - -impl Pretty for IncludeExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("include "); - self.path().pretty(p); - } -} - -impl Pretty for BreakExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("break"); - } -} - -impl Pretty for ContinueExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("continue"); - } -} - -impl Pretty for ReturnExpr { - fn pretty(&self, p: &mut Printer) { - p.push_str("return"); - if let Some(body) = self.body() { - p.push(' '); - body.pretty(p); - } - } -} - -impl Pretty for Ident { - fn pretty(&self, p: &mut Printer) { - p.push_str(self); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::source::SourceFile; - - #[track_caller] - fn roundtrip(src: &str) { - test_parse(src, src); - } - - #[track_caller] - fn test_parse(src: &str, expected: &str) { - let source = SourceFile::detached(src); - let ast = source.ast().unwrap(); - let found = pretty(&ast); - if found != expected { - println!("tree: {ast:#?}"); - println!("expected: {expected}"); - println!("found: {found}"); - panic!("test failed"); - } - } - - #[test] - fn test_pretty_print_markup() { - // Basic stuff. - roundtrip(" "); - roundtrip("*ab*"); - roundtrip("\\ "); - roundtrip("\n\n"); - roundtrip("hi"); - roundtrip("_ab_"); - roundtrip("= *Ok*"); - roundtrip("- Ok"); - - // Raw node. - roundtrip("``"); - roundtrip("`nolang 1`"); - roundtrip("```lang 1```"); - roundtrip("```lang 1 ```"); - roundtrip("```hi line ```"); - roundtrip("```py\ndef\n```"); - roundtrip("```\n line \n```"); - roundtrip("```\n`\n```"); - roundtrip("``` ` ```"); - roundtrip("````\n```\n```\n````"); - test_parse("```lang```", "```lang ```"); - test_parse("```1 ```", "``"); - test_parse("``` 1```", "`1`"); - test_parse("``` 1 ```", "`1 `"); - test_parse("```` ` ````", "``` ` ```"); - - // Math node. - roundtrip("$$"); - roundtrip("$a+b$"); - roundtrip("$[ a^2 + b^2 = c^2 ]$"); - } - - #[test] - fn test_pretty_print_expr() { - // Basic expressions. - roundtrip("{none}"); - roundtrip("{auto}"); - roundtrip("{true}"); - roundtrip("{10}"); - roundtrip("{3.14}"); - roundtrip("{10pt}"); - roundtrip("{14.1deg}"); - roundtrip("{20%}"); - roundtrip("{0.5fr}"); - roundtrip(r#"{"hi"}"#); - roundtrip(r#"{"let's \" go"}"#); - roundtrip("{hi}"); - - // Arrays. - roundtrip("{()}"); - roundtrip("{(1)}"); - roundtrip("{(1, 2, 3)}"); - - // Dictionaries. - roundtrip("{(:)}"); - roundtrip("{(key: value)}"); - roundtrip("{(a: 1, b: 2)}"); - - // Templates. - roundtrip("[]"); - roundtrip("[*Ok*]"); - roundtrip("{[f]}"); - - // Groups. - roundtrip("{(1)}"); - - // Blocks. - roundtrip("{}"); - roundtrip("{1}"); - roundtrip("{ let x = 1; x += 2; x + 1 }"); - roundtrip("[{}]"); - - // Operators. - roundtrip("{-x}"); - roundtrip("{not true}"); - roundtrip("{1 + 3}"); - - // Functions. - roundtrip("{v()}"); - roundtrip("{v()()}"); - roundtrip("{v(1)}"); - roundtrip("{v(a: 1, b)}"); - roundtrip("#v()"); - roundtrip("#v(1)"); - roundtrip("#v(1, 2)[*Ok*]"); - roundtrip("#v(1, f[2])"); - roundtrip("{x => x + 1}"); - roundtrip("{(a, b) => a + b}"); - - // Control flow. - roundtrip("#let x = 1 + 2"); - roundtrip("#let f(x) = y"); - roundtrip("#set text(size: 12pt)"); - roundtrip("#show heading(body) as [*{body}*]"); - roundtrip("#wrap body in columns(2, body)"); - roundtrip("#if x [y] else [z]"); - roundtrip("#if x {} else if y {} else {}"); - roundtrip("#while x {y}"); - roundtrip("#for x in y {z}"); - roundtrip("#for k, x in y {z}"); - roundtrip("#import * from \"file.typ\""); - roundtrip("#include \"chapter1.typ\""); - roundtrip("{break}"); - roundtrip("{continue}"); - roundtrip("{return}"); - roundtrip("{return x + 1}"); - } -} diff --git a/tests/ref/style/show.png b/tests/ref/style/show.png Binary files differnew file mode 100644 index 00000000..0f1a16a3 --- /dev/null +++ b/tests/ref/style/show.png diff --git a/tests/typ/style/set.typ b/tests/typ/style/set.typ index 0d4d47cc..6ce1d303 100644 --- a/tests/typ/style/set.typ +++ b/tests/typ/style/set.typ @@ -1,5 +1,5 @@ // General tests for set. --- -// Error: 2-10 set is only allowed directly in markup +// Error: 2-10 set, show and wrap are only allowed directly in markup {set f(x)} diff --git a/tests/typ/style/show.typ b/tests/typ/style/show.typ index 533e50f5..1055f9c7 100644 --- a/tests/typ/style/show.typ +++ b/tests/typ/style/show.typ @@ -1,9 +1,53 @@ // Test show rules. +#set page("a8", footer: p => v(-5pt) + align(right, [#p])) + +#let i = 1 +#set heading(size: 100%) +#show heading(level, body) as { + if level == 1 { + v(10pt) + underline(text(150%, blue)[{i}. #body]) + i += 1 + } else { + text(red, body) + } +} + +#v(-10pt) + += Aufgabe +Some text. + +== Subtask +Some more text. + +== Another subtask +Some more text. + += Aufgabe +Another text. + +--- +#set heading(size: 100%, strong: false, block: false) +#show heading(a, b) as [B] +A [= Heading] C + +--- +// Error: 1-22 unexpected argument +#show heading() as [] += Heading + +--- +// Error: 1-28 expected template, found string +#show heading(_, _) as "hi" += Heading + --- -// Error: 1-34 show rules are not yet implemented -#show heading(body) as [*{body}*] +// Error: 1-29 show rule is recursive +#show strong(x) as strong(x) +*Hi* --- -// Error: 2-15 show is only allowed directly in markup -{show (a) as b} +// Error: 2-19 set, show and wrap are only allowed directly in markup +{show list(a) as b} diff --git a/tests/typ/style/wrap.typ b/tests/typ/style/wrap.typ index 2fa7716e..9c94b9ae 100644 --- a/tests/typ/style/wrap.typ +++ b/tests/typ/style/wrap.typ @@ -25,5 +25,5 @@ A [_B #wrap c in [*#c*]; C_] D Forest --- -// Error: 6-17 wrap is only allowed directly in markup +// Error: 6-17 set, show and wrap are only allowed directly in markup {1 + wrap x in y} diff --git a/tests/typeset.rs b/tests/typeset.rs index dadcfed3..98c85df9 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -23,7 +23,7 @@ use typst::{Context, Vm}; use { filedescriptor::{FileDescriptor, StdioDescriptor::*}, std::fs::File, - typst::eval::Template, + typst::source::SourceId, }; const TYP_DIR: &str = "./typ"; @@ -110,7 +110,7 @@ fn main() { &png_path, &ref_path, pdf_path.as_deref(), - args.debug, + args.syntax, ) as usize; } @@ -126,7 +126,7 @@ fn main() { struct Args { filter: Vec<String>, exact: bool, - debug: bool, + syntax: bool, pdf: bool, } @@ -134,7 +134,7 @@ impl Args { fn new(args: impl Iterator<Item = String>) -> Self { let mut filter = Vec::new(); let mut exact = false; - let mut debug = false; + let mut syntax = false; let mut pdf = false; for arg in args { @@ -146,13 +146,13 @@ impl Args { // Generate PDFs. "--pdf" => pdf = true, // Debug print the layout trees. - "--debug" | "-d" => debug = true, + "--syntax" => syntax = true, // Everything else is a file filter. _ => filter.push(arg), } } - Self { filter, pdf, debug, exact } + Self { filter, pdf, syntax, exact } } fn matches(&self, path: &Path) -> bool { @@ -172,7 +172,7 @@ fn test( png_path: &Path, ref_path: &Path, pdf_path: Option<&Path>, - debug: bool, + syntax: bool, ) -> bool { let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path); println!("Testing {}", name.display()); @@ -208,7 +208,7 @@ fn test( i, compare_ref, line, - debug, + syntax, &mut rng, ); ok &= part_ok; @@ -242,7 +242,7 @@ fn test( } if ok { - if !debug { + if !syntax { print!("\x1b[1A"); } println!("Testing {} ✔", name.display()); @@ -258,14 +258,14 @@ fn test_part( i: usize, compare_ref: bool, line: usize, - debug: bool, + syntax: bool, rng: &mut LinearShift, ) -> (bool, bool, Vec<Arc<Frame>>) { let mut ok = true; let id = ctx.sources.provide(src_path, src); let source = ctx.sources.get(id); - if debug { + if syntax { println!("Syntax Tree: {:#?}", source.root()) } @@ -277,13 +277,8 @@ fn test_part( let mut vm = Vm::new(ctx); let (frames, mut errors) = match vm.typeset(id) { Ok(mut frames) => { - let module = vm.evaluate(id).unwrap(); - if debug { - println!("Template: {:#?}", module.template); - } - #[cfg(feature = "layout-cache")] - (ok &= test_incremental(ctx, i, &module.template, &frames)); + (ok &= test_incremental(ctx, i, id, &frames)); if !compare_ref { frames.clear(); @@ -483,7 +478,7 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool { fn test_incremental( ctx: &mut Context, i: usize, - template: &Template, + id: SourceId, frames: &[Arc<Frame>], ) -> bool { let mut ok = true; @@ -498,7 +493,7 @@ fn test_incremental( ctx.layout_cache.turnaround(); - let cached = silenced(|| template.layout_pages(&mut Vm::new(ctx)).unwrap()); + let cached = silenced(|| ctx.typeset(id).unwrap()); let total = reference.levels() - 1; let misses = ctx .layout_cache |
