summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-18 15:02:02 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-18 16:57:53 +0100
commite01970b20a058ab1b4ebea916f229c9b706c84e4 (patch)
tree5c5efc75abd6e607bd45a0602603231edf520503 /src
parent05ec0f993b4a1b8481e494ee16285d23f000872f (diff)
Basic show rules
Diffstat (limited to 'src')
-rw-r--r--src/eval/capture.rs2
-rw-r--r--src/eval/class.rs8
-rw-r--r--src/eval/mod.rs52
-rw-r--r--src/eval/styles.rs387
-rw-r--r--src/eval/template.rs13
-rw-r--r--src/eval/value.rs12
-rw-r--r--src/lib.rs7
-rw-r--r--src/library/deco.rs22
-rw-r--r--src/library/heading.rs20
-rw-r--r--src/library/link.rs36
-rw-r--r--src/library/list.rs35
-rw-r--r--src/library/math.rs19
-rw-r--r--src/library/raw.rs14
-rw-r--r--src/library/table.rs16
-rw-r--r--src/library/text.rs12
-rw-r--r--src/parse/mod.rs17
-rw-r--r--src/syntax/ast.rs12
-rw-r--r--src/syntax/highlight.rs1
-rw-r--r--src/syntax/mod.rs2
-rw-r--r--src/syntax/pretty.rs730
20 files changed, 416 insertions, 1001 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 }
diff --git a/src/lib.rs b/src/lib.rs
index 6ad30d2f..be1bab0d 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<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}");
- }
-}