Basic show rules

This commit is contained in:
Laurenz 2022-02-18 15:02:02 +01:00
parent 05ec0f993b
commit e01970b20a
25 changed files with 481 additions and 1027 deletions

View File

@ -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() {

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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>()
}
}

View File

@ -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);

View File

@ -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 }

View File

@ -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,
}
}

View File

@ -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),
}])
}))
}
}

View File

@ -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)
}
}

View File

@ -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();
}

View File

@ -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)?
}
})
}

View File

@ -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()
}))
}
}

View File

@ -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

View File

@ -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);

View File

@ -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)))
}
}

View File

@ -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)
})
})
}

View File

@ -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")
}
}

View File

@ -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),
},

View File

@ -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};

View File

@ -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}");
}
}

BIN
tests/ref/style/show.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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)}

View File

@ -1,9 +1,53 @@
// Test show rules.
---
// Error: 1-34 show rules are not yet implemented
#show heading(body) as [*{body}*]
#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.
---
// Error: 2-15 show is only allowed directly in markup
{show (a) as b}
#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-29 show rule is recursive
#show strong(x) as strong(x)
*Hi*
---
// Error: 2-19 set, show and wrap are only allowed directly in markup
{show list(a) as b}

View File

@ -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}

View File

@ -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