From e01970b20a058ab1b4ebea916f229c9b706c84e4 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 18 Feb 2022 15:02:02 +0100 Subject: [PATCH] Basic show rules --- src/eval/capture.rs | 2 +- src/eval/class.rs | 8 + src/eval/mod.rs | 52 ++- src/eval/styles.rs | 387 ++++++++++++--------- src/eval/template.rs | 13 +- src/eval/value.rs | 12 +- src/lib.rs | 7 +- src/library/deco.rs | 22 +- src/library/heading.rs | 20 +- src/library/link.rs | 36 +- src/library/list.rs | 35 +- src/library/math.rs | 19 +- src/library/raw.rs | 14 +- src/library/table.rs | 16 +- src/library/text.rs | 12 +- src/parse/mod.rs | 17 +- src/syntax/ast.rs | 12 +- src/syntax/highlight.rs | 1 + src/syntax/mod.rs | 2 - src/syntax/pretty.rs | 730 --------------------------------------- tests/ref/style/show.png | Bin 0 -> 19940 bytes tests/typ/style/set.typ | 2 +- tests/typ/style/show.typ | 54 ++- tests/typ/style/wrap.typ | 2 +- tests/typeset.rs | 33 +- 25 files changed, 481 insertions(+), 1027 deletions(-) delete mode 100644 src/syntax/pretty.rs create mode 100644 tests/ref/style/show.png diff --git a/src/eval/capture.rs b/src/eval/capture.rs index d62ec55c2..fd6b01018 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 28e49a999..5601fb0b6 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, set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, } @@ -48,6 +50,7 @@ impl Class { { Self { name, + id: TypeId::of::(), 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 32ffb0c9b..1b61ac151 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::().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 { 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 { 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 { - Err("set is only allowed directly in markup").at(self.span()) + fn eval(&self, vm: &mut Vm) -> TypResult { + let class = self.class(); + let class = class.eval(vm)?.cast::().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 { - Err("show is only allowed directly in markup").at(self.span()) - } -} - -impl Eval for WrapExpr { - type Output = Value; - - fn eval(&self, _: &mut Vm) -> TypResult { - Err("wrap is only allowed directly in markup").at(self.span()) + fn eval(&self, vm: &mut Vm) -> TypResult { + let class = self.class(); + let class = class.eval(vm)?.cast::().at(class.span())?; + let closure = self.closure(); + let func = closure.eval(vm)?.cast::().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 2bf3f239f..346eb784e 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); +pub struct StyleMap { + /// Settable properties. + props: Vec, + /// Show rule recipes. + recipes: Vec, +} 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(&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 { - 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, + scoped: bool, +} + +impl Entry { + fn new(key: P, value: P::Value) -> Self { + Self { + pair: Arc::new((key, value)), + scoped: false, + } + } + + fn is(&self) -> bool { + self.pair.style_id() == TypeId::of::

() + } + + fn is_of(&self) -> bool { + self.pair.node_id() == TypeId::of::() + } + + fn is_of_id(&self, node: TypeId) -> bool { + self.pair.node_id() == node + } + + fn downcast(&self) -> Option<&P::Value> { + self.pair.as_any().downcast_ref() + } + + fn interruption(&self) -> Option { + if self.is_of::() { + Some(Interruption::Page) + } else if self.is_of::() { + 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(&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 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::

() { + &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::

() + } +} + +/// 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 { + 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, + ) -> TypResult> { + 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 { + 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 { 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, - scoped: bool, -} - -impl Entry { - fn new(key: P, value: P::Value) -> Self { - Self { - pair: Arc::new((key, value)), - scoped: false, - } - } - - fn is(&self) -> bool { - self.pair.style_id() == TypeId::of::

() - } - - fn is_of(&self) -> bool { - self.pair.node_id() == TypeId::of::() - } - - fn is_of_id(&self, node: TypeId) -> bool { - self.pair.node_id() == node - } - - fn downcast(&self) -> Option<&P::Value> { - self.pair.as_any().downcast_ref() - } - - fn interruption(&self) -> Option { - if self.is_of::() { - Some(Interruption::Page) - } else if self.is_of::() { - 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(&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 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::

() { - &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::

() - } -} diff --git a/src/eval/template.rs b/src/eval/template.rs index 1f1544e64..465e91959 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:: { items, wide, start: 1 }), - ORDERED | _ => Template::show(ListNode:: { items, wide, start: 1 }), + UNORDERED => Template::show(ListNode:: { start: 1, wide, items }), + ORDERED | _ => Template::show(ListNode:: { start: 1, wide, items }), }; let stored = self.tpa.alloc(template); diff --git a/src/eval/value.rs b/src/eval/value.rs index 2de210d57..952c7293d 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -336,7 +336,7 @@ pub trait Cast: 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 { 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 } diff --git a/src/lib.rs b/src/lib.rs index 6ad30d2f6..be1bab0d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, /// The active scopes. pub scopes: Scopes<'a>, + /// Currently evaluated show rules. This is used to prevent recursive show + /// rules. + pub rules: Vec, /// 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 790262889..aac79d05a 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -32,15 +32,19 @@ impl DecoNode { } impl Show for DecoNode { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult