mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Basic show rules
This commit is contained in:
parent
05ec0f993b
commit
e01970b20a
@ -50,7 +50,7 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
Some(Expr::Ident(ident)) => self.capture(ident),
|
Some(Expr::Ident(ident)) => self.capture(ident),
|
||||||
|
|
||||||
// A closure contains parameter bindings, which are bound before the
|
// 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.
|
// of named parameters cannot access previous parameter bindings.
|
||||||
Some(Expr::Closure(expr)) => {
|
Some(Expr::Closure(expr)) => {
|
||||||
for param in expr.params() {
|
for param in expr.params() {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::any::TypeId;
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ use crate::diag::TypResult;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Class {
|
pub struct Class {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
id: TypeId,
|
||||||
construct: fn(&mut Vm, &mut Args) -> TypResult<Value>,
|
construct: fn(&mut Vm, &mut Args) -> TypResult<Value>,
|
||||||
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
|
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
|
||||||
}
|
}
|
||||||
@ -48,6 +50,7 @@ impl Class {
|
|||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
|
id: TypeId::of::<T>(),
|
||||||
construct: |ctx, args| {
|
construct: |ctx, args| {
|
||||||
let mut styles = StyleMap::new();
|
let mut styles = StyleMap::new();
|
||||||
T::set(args, &mut styles)?;
|
T::set(args, &mut styles)?;
|
||||||
@ -63,6 +66,11 @@ impl Class {
|
|||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type id of the class.
|
||||||
|
pub fn id(&self) -> TypeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the class constructor as a function.
|
/// Return the class constructor as a function.
|
||||||
pub fn constructor(&self) -> Func {
|
pub fn constructor(&self) -> Func {
|
||||||
Func::native(self.name, self.construct)
|
Func::native(self.name, self.construct)
|
||||||
|
@ -36,7 +36,6 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||||
use crate::layout::Layout;
|
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::syntax::ast::*;
|
use crate::syntax::ast::*;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
@ -80,15 +79,12 @@ fn eval_markup(
|
|||||||
while let Some(node) = nodes.next() {
|
while let Some(node) = nodes.next() {
|
||||||
seq.push(match node {
|
seq.push(match node {
|
||||||
MarkupNode::Expr(Expr::Set(set)) => {
|
MarkupNode::Expr(Expr::Set(set)) => {
|
||||||
let class = set.class();
|
let styles = set.eval(vm)?;
|
||||||
let class = class.eval(vm)?.cast::<Class>().at(class.span())?;
|
eval_markup(vm, nodes)?.styled_with_map(styles)
|
||||||
let args = set.args().eval(vm)?;
|
|
||||||
let styles = class.set(args)?;
|
|
||||||
let tail = eval_markup(vm, nodes)?;
|
|
||||||
tail.styled_with_map(styles)
|
|
||||||
}
|
}
|
||||||
MarkupNode::Expr(Expr::Show(show)) => {
|
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)) => {
|
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
||||||
let tail = eval_markup(vm, nodes)?;
|
let tail = eval_markup(vm, nodes)?;
|
||||||
@ -182,7 +178,7 @@ impl Eval for ListNode {
|
|||||||
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Template::List(library::ListItem {
|
Ok(Template::List(library::ListItem {
|
||||||
number: None,
|
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> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Template::Enum(library::ListItem {
|
Ok(Template::Enum(library::ListItem {
|
||||||
number: self.number(),
|
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::Unary(v) => v.eval(vm),
|
||||||
Self::Binary(v) => v.eval(vm),
|
Self::Binary(v) => v.eval(vm),
|
||||||
Self::Let(v) => v.eval(vm),
|
Self::Let(v) => v.eval(vm),
|
||||||
Self::Set(v) => v.eval(vm),
|
Self::Set(_) | Self::Show(_) | Self::Wrap(_) => {
|
||||||
Self::Show(v) => v.eval(vm),
|
Err("set, show and wrap are only allowed directly in markup")
|
||||||
Self::Wrap(v) => v.eval(vm),
|
.at(self.span())
|
||||||
|
}
|
||||||
Self::If(v) => v.eval(vm),
|
Self::If(v) => v.eval(vm),
|
||||||
Self::While(v) => v.eval(vm),
|
Self::While(v) => v.eval(vm),
|
||||||
Self::For(v) => v.eval(vm),
|
Self::For(v) => v.eval(vm),
|
||||||
@ -551,26 +548,27 @@ impl Eval for LetExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for SetExpr {
|
impl Eval for SetExpr {
|
||||||
type Output = Value;
|
type Output = StyleMap;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Err("set is only allowed directly in markup").at(self.span())
|
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 {
|
impl Eval for ShowExpr {
|
||||||
type Output = Value;
|
type Output = StyleMap;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Err("show is only allowed directly in markup").at(self.span())
|
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())?;
|
||||||
impl Eval for WrapExpr {
|
let mut styles = StyleMap::new();
|
||||||
type Output = Value;
|
styles.set_recipe(class.id(), func, self.span());
|
||||||
|
Ok(styles)
|
||||||
fn eval(&self, _: &mut Vm) -> TypResult<Self::Output> {
|
|
||||||
Err("wrap is only allowed directly in markup").at(self.span())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,21 +3,28 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{Args, Func, Span, Template, Value, Vm};
|
||||||
|
use crate::diag::{At, TypResult};
|
||||||
use crate::library::{PageNode, ParNode};
|
use crate::library::{PageNode, ParNode};
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone, PartialEq, Hash)]
|
#[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 {
|
impl StyleMap {
|
||||||
/// Create a new, empty style map.
|
/// Create a new, empty style map.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(vec![])
|
Self { props: vec![], recipes: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this map contains no styles.
|
/// Whether this map contains no styles.
|
||||||
pub fn is_empty(&self) -> bool {
|
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.
|
/// Create a style map from a single property-value pair.
|
||||||
@ -29,7 +36,7 @@ impl StyleMap {
|
|||||||
|
|
||||||
/// Set the value for a style property.
|
/// Set the value for a style property.
|
||||||
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
|
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(_)`.
|
/// 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
|
/// Mark all contained properties as _scoped_. This means that they only
|
||||||
/// apply to the first descendant node (of their type) in the hierarchy and
|
/// apply to the first descendant node (of their type) in the hierarchy and
|
||||||
/// not its children, too. This is used by class constructors.
|
/// not its children, too. This is used by class constructors.
|
||||||
pub fn scoped(mut self) -> Self {
|
pub fn scoped(mut self) -> Self {
|
||||||
for entry in &mut self.0 {
|
for entry in &mut self.props {
|
||||||
entry.scoped = true;
|
entry.scoped = true;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -51,7 +63,7 @@ impl StyleMap {
|
|||||||
|
|
||||||
/// Whether this map contains scoped styles.
|
/// Whether this map contains scoped styles.
|
||||||
pub fn has_scoped(&self) -> bool {
|
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`.
|
/// 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
|
/// lifetime, for example, because you want to store the style map inside a
|
||||||
/// packed node.
|
/// packed node.
|
||||||
pub fn apply(&mut self, outer: &Self) {
|
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.
|
/// The highest-level interruption of the map.
|
||||||
pub fn interruption(&self) -> Option<Interruption> {
|
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 {
|
impl Debug for StyleMap {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
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)?;
|
writeln!(f, "{:#?}", entry)?;
|
||||||
}
|
}
|
||||||
|
for recipe in self.recipes.iter().rev() {
|
||||||
|
writeln!(f, "#[Recipe for {:?} from {:?}]", recipe.node, recipe.span)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,6 +121,169 @@ pub enum Interruption {
|
|||||||
Page,
|
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 chain of style maps, similar to a linked list.
|
||||||
///
|
///
|
||||||
/// A style chain allows to conceptually merge (and fold) properties from
|
/// 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();
|
*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
|
/// Return the chain, but without the last link if that one contains only
|
||||||
/// scoped styles. This is a hack.
|
/// scoped styles. This is a hack.
|
||||||
pub(crate) fn unscoped(mut self, node: TypeId) -> Self {
|
pub(crate) fn unscoped(mut self, node: TypeId) -> Self {
|
||||||
if let Some(Link::Map(map)) = self.link {
|
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();
|
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.
|
/// Insert a barrier into the style chain.
|
||||||
///
|
///
|
||||||
/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style
|
/// 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> {
|
pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
|
||||||
if self
|
if self
|
||||||
.maps()
|
.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 {
|
StyleChain {
|
||||||
link: Some(Link::Barrier(node)),
|
link: Some(Link::Barrier(node)),
|
||||||
@ -252,7 +453,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
self.links().flat_map(move |link| {
|
self.links().flat_map(move |link| {
|
||||||
let mut entries: &[Entry] = &[];
|
let mut entries: &[Entry] = &[];
|
||||||
match link {
|
match link {
|
||||||
Link::Map(map) => entries = &map.0,
|
Link::Map(map) => entries = &map.props,
|
||||||
Link::Barrier(id) => depth += (id == P::node_id()) as usize,
|
Link::Barrier(id) => depth += (id == P::node_id()) as usize,
|
||||||
}
|
}
|
||||||
entries
|
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.
|
/// Iterate over the map links of the chain.
|
||||||
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
|
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
|
||||||
self.links().filter_map(|link| match link {
|
self.links().filter_map(|link| match link {
|
||||||
@ -443,158 +651,3 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> {
|
|||||||
Self::new()
|
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>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -403,9 +403,16 @@ impl<'a> Builder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Template::Show(node) => {
|
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 template = node.show(vm, styles)?;
|
||||||
let stored = self.tpa.alloc(template);
|
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) => {
|
Template::Styled(styled) => {
|
||||||
let (sub, map) = styled.as_ref();
|
let (sub, map) = styled.as_ref();
|
||||||
@ -455,8 +462,8 @@ impl<'a> Builder<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let template = match kind {
|
let template = match kind {
|
||||||
UNORDERED => Template::show(ListNode::<UNORDERED> { items, wide, start: 1 }),
|
UNORDERED => Template::show(ListNode::<UNORDERED> { start: 1, wide, items }),
|
||||||
ORDERED | _ => Template::show(ListNode::<ORDERED> { items, wide, start: 1 }),
|
ORDERED | _ => Template::show(ListNode::<ORDERED> { start: 1, wide, items }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let stored = self.tpa.alloc(template);
|
let stored = self.tpa.alloc(template);
|
||||||
|
@ -336,7 +336,7 @@ pub trait Cast<V = Value>: Sized {
|
|||||||
macro_rules! primitive {
|
macro_rules! primitive {
|
||||||
(
|
(
|
||||||
$type:ty: $name:literal, $variant:ident
|
$type:ty: $name:literal, $variant:ident
|
||||||
$(, $other:ident($binding:ident) => $out:expr)*
|
$(, $other:ident$(($binding:ident))? => $out:expr)*
|
||||||
) => {
|
) => {
|
||||||
impl Type for $type {
|
impl Type for $type {
|
||||||
const TYPE_NAME: &'static str = $name;
|
const TYPE_NAME: &'static str = $name;
|
||||||
@ -344,13 +344,14 @@ macro_rules! primitive {
|
|||||||
|
|
||||||
impl Cast for $type {
|
impl Cast for $type {
|
||||||
fn is(value: &Value) -> bool {
|
fn is(value: &Value) -> bool {
|
||||||
matches!(value, Value::$variant(_) $(| Value::$other(_))*)
|
matches!(value, Value::$variant(_)
|
||||||
|
$(| primitive!(@$other $(($binding))?))*)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cast(value: Value) -> StrResult<Self> {
|
fn cast(value: Value) -> StrResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::$variant(v) => Ok(v),
|
Value::$variant(v) => Ok(v),
|
||||||
$(Value::$other($binding) => Ok($out),)*
|
$(Value::$other$(($binding))? => Ok($out),)*
|
||||||
v => Err(format!(
|
v => Err(format!(
|
||||||
"expected {}, found {}",
|
"expected {}, found {}",
|
||||||
Self::TYPE_NAME,
|
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.
|
/// Implement traits for dynamic types.
|
||||||
@ -440,7 +444,7 @@ primitive! { Color: "color", Color }
|
|||||||
primitive! { EcoString: "string", Str }
|
primitive! { EcoString: "string", Str }
|
||||||
primitive! { Array: "array", Array }
|
primitive! { Array: "array", Array }
|
||||||
primitive! { Dict: "dictionary", Dict }
|
primitive! { Dict: "dictionary", Dict }
|
||||||
primitive! { Template: "template", Template }
|
primitive! { Template: "template", Template, None => Template::new() }
|
||||||
primitive! { Func: "function", Func, Class(v) => v.constructor() }
|
primitive! { Func: "function", Func, Class(v) => v.constructor() }
|
||||||
primitive! { Args: "arguments", Args }
|
primitive! { Args: "arguments", Args }
|
||||||
primitive! { Class: "class", Class }
|
primitive! { Class: "class", Class }
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
//! [evaluate]: Vm::evaluate
|
//! [evaluate]: Vm::evaluate
|
||||||
//! [module]: eval::Module
|
//! [module]: eval::Module
|
||||||
//! [template]: eval::Template
|
//! [template]: eval::Template
|
||||||
//! [layouted]: eval::Template::layout
|
//! [layouted]: eval::Template::layout_pages
|
||||||
//! [cache]: layout::LayoutCache
|
//! [cache]: layout::LayoutCache
|
||||||
//! [PDF]: export::pdf
|
//! [PDF]: export::pdf
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ pub mod parse;
|
|||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
|
use std::any::TypeId;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -218,6 +219,9 @@ pub struct Vm<'a> {
|
|||||||
pub modules: HashMap<SourceId, Module>,
|
pub modules: HashMap<SourceId, Module>,
|
||||||
/// The active scopes.
|
/// The active scopes.
|
||||||
pub scopes: Scopes<'a>,
|
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.
|
/// How deeply nested the current layout tree position is.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
@ -236,6 +240,7 @@ impl<'a> Vm<'a> {
|
|||||||
route: vec![],
|
route: vec![],
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
scopes: Scopes::new(Some(&ctx.std)),
|
scopes: Scopes::new(Some(&ctx.std)),
|
||||||
|
rules: vec![],
|
||||||
level: 0,
|
level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,15 +32,19 @@ impl<const L: DecoLine> DecoNode<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||||
Ok(self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
Ok(styles
|
||||||
|
.show(self, vm, [Value::Template(self.0.clone())])?
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
||||||
line: L,
|
line: L,
|
||||||
stroke: styles.get(Self::STROKE),
|
stroke: styles.get(Self::STROKE),
|
||||||
thickness: styles.get(Self::THICKNESS),
|
thickness: styles.get(Self::THICKNESS),
|
||||||
offset: styles.get(Self::OFFSET),
|
offset: styles.get(Self::OFFSET),
|
||||||
extent: styles.get(Self::EXTENT),
|
extent: styles.get(Self::EXTENT),
|
||||||
evade: styles.get(Self::EVADE),
|
evade: styles.get(Self::EVADE),
|
||||||
}]))
|
}])
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ impl HeadingNode {
|
|||||||
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
|
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
|
||||||
/// The extra padding below the heading.
|
/// The extra padding below the heading.
|
||||||
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
|
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> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
||||||
Ok(Template::show(Self {
|
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();
|
let mut map = StyleMap::new();
|
||||||
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
||||||
|
|
||||||
@ -76,7 +86,6 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
let mut body = self.body.clone();
|
|
||||||
if resolve!(Self::UNDERLINE) {
|
if resolve!(Self::UNDERLINE) {
|
||||||
body = body.underlined();
|
body = body.underlined();
|
||||||
}
|
}
|
||||||
@ -93,9 +102,12 @@ impl Show for HeadingNode {
|
|||||||
seq.push(Template::Vertical(below.into()));
|
seq.push(Template::Vertical(below.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Template::block(
|
let mut template = Template::sequence(seq).styled_with_map(map);
|
||||||
Template::sequence(seq).styled_with_map(map),
|
if resolve!(Self::BLOCK) {
|
||||||
))
|
template = Template::block(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(template)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ pub struct LinkNode {
|
|||||||
/// The url the link points to.
|
/// The url the link points to.
|
||||||
pub url: EcoString,
|
pub url: EcoString,
|
||||||
/// How the link is represented.
|
/// How the link is represented.
|
||||||
pub body: Template,
|
pub body: Option<Template>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[class]
|
#[class]
|
||||||
@ -22,8 +22,23 @@ impl LinkNode {
|
|||||||
pub const UNDERLINE: bool = true;
|
pub const UNDERLINE: bool = true;
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
||||||
let url = args.expect::<EcoString>("url")?;
|
Ok(Template::show(Self {
|
||||||
let body = args.find()?.unwrap_or_else(|| {
|
url: args.expect::<EcoString>("url")?,
|
||||||
|
body: args.find()?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for LinkNode {
|
||||||
|
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();
|
let mut text = url.as_str();
|
||||||
for prefix in ["mailto:", "tel:"] {
|
for prefix in ["mailto:", "tel:"] {
|
||||||
text = text.trim_start_matches(prefix);
|
text = text.trim_start_matches(prefix);
|
||||||
@ -32,12 +47,6 @@ impl LinkNode {
|
|||||||
Template::Text(if shorter { text.into() } else { url.clone() })
|
Template::Text(if shorter { text.into() } else { url.clone() })
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Template::show(Self { url, body }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for LinkNode {
|
|
||||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
map.set(TextNode::LINK, Some(self.url.clone()));
|
map.set(TextNode::LINK, Some(self.url.clone()));
|
||||||
|
|
||||||
@ -45,7 +54,6 @@ impl Show for LinkNode {
|
|||||||
map.set(TextNode::FILL, fill);
|
map.set(TextNode::FILL, fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut body = self.body.clone();
|
|
||||||
if styles.get(Self::UNDERLINE) {
|
if styles.get(Self::UNDERLINE) {
|
||||||
body = body.underlined();
|
body = body.underlined();
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@ use crate::parse::Scanner;
|
|||||||
/// An unordered or ordered list.
|
/// An unordered or ordered list.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ListNode<const L: ListKind> {
|
pub struct ListNode<const L: ListKind> {
|
||||||
/// The individual bulleted or numbered items.
|
/// Where the list starts.
|
||||||
pub items: Vec<ListItem>,
|
pub start: usize,
|
||||||
/// If true, there is paragraph spacing between the items, if false
|
/// If true, there is paragraph spacing between the items, if false
|
||||||
/// there is list spacing between the items.
|
/// there is list spacing between the items.
|
||||||
pub wide: bool,
|
pub wide: bool,
|
||||||
/// Where the list starts.
|
/// The individual bulleted or numbered items.
|
||||||
pub start: usize,
|
pub items: Vec<ListItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item in a list.
|
/// An item in a list.
|
||||||
@ -23,7 +23,7 @@ pub struct ListItem {
|
|||||||
/// The number of the item.
|
/// The number of the item.
|
||||||
pub number: Option<usize>,
|
pub number: Option<usize>,
|
||||||
/// The node that produces the item's body.
|
/// The node that produces the item's body.
|
||||||
pub body: LayoutNode,
|
pub body: Box<Template>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[class]
|
#[class]
|
||||||
@ -39,19 +39,27 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
||||||
Ok(Template::show(Self {
|
Ok(Template::show(Self {
|
||||||
|
start: args.named("start")?.unwrap_or(0),
|
||||||
|
wide: args.named("wide")?.unwrap_or(false),
|
||||||
items: args
|
items: args
|
||||||
.all()?
|
.all()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|body| ListItem { number: None, body })
|
.map(|body| ListItem { number: None, body: Box::new(body) })
|
||||||
.collect(),
|
.collect(),
|
||||||
wide: args.named("wide")?.unwrap_or(false),
|
|
||||||
start: args.named("start")?.unwrap_or(0),
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: ListKind> Show for ListNode<L> {
|
impl<const L: ListKind> Show for ListNode<L> {
|
||||||
fn show(&self, vm: &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.items.iter().map(|item| Value::Template((*item.body).clone())),
|
||||||
|
)? {
|
||||||
|
return Ok(template);
|
||||||
|
}
|
||||||
|
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
let mut number = self.start;
|
let mut number = self.start;
|
||||||
|
|
||||||
@ -66,7 +74,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
children.push(LayoutNode::default());
|
children.push(LayoutNode::default());
|
||||||
children.push(label.resolve(vm, L, number)?.pack());
|
children.push(label.resolve(vm, L, number)?.pack());
|
||||||
children.push(LayoutNode::default());
|
children.push(LayoutNode::default());
|
||||||
children.push(item.body.clone());
|
children.push((*item.body).clone().pack());
|
||||||
number += 1;
|
number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +127,6 @@ pub enum Label {
|
|||||||
Pattern(EcoString, Numbering, bool, EcoString),
|
Pattern(EcoString, Numbering, bool, EcoString),
|
||||||
/// A bare template.
|
/// A bare template.
|
||||||
Template(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.
|
/// A closure mapping from an item number to a value.
|
||||||
Func(Func, Span),
|
Func(Func, Span),
|
||||||
}
|
}
|
||||||
@ -144,10 +150,9 @@ impl Label {
|
|||||||
Template::Text(format_eco!("{}{}{}", prefix, mid, suffix))
|
Template::Text(format_eco!("{}{}{}", prefix, mid, suffix))
|
||||||
}
|
}
|
||||||
Self::Template(template) => template.clone(),
|
Self::Template(template) => template.clone(),
|
||||||
Self::Mapping(mapping) => mapping(number),
|
Self::Func(func, span) => {
|
||||||
&Self::Func(ref func, span) => {
|
let args = Args::from_values(*span, [Value::Int(number as i64)]);
|
||||||
let args = Args::from_values(span, [Value::Int(number as i64)]);
|
func.call(vm, args)?.cast().at(*span)?
|
||||||
func.call(vm, args)?.cast().at(span)?
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,18 @@ impl MathNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for MathNode {
|
impl Show for MathNode {
|
||||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
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());
|
let mut template = Template::Text(self.formula.trim().into());
|
||||||
if self.display {
|
if self.display {
|
||||||
template = Template::Block(template.pack());
|
template = Template::Block(template.pack());
|
||||||
}
|
}
|
||||||
Ok(template.monospaced())
|
template.monospaced()
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,20 @@ impl RawNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for 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();
|
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
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
.foreground
|
.foreground
|
||||||
|
@ -11,7 +11,7 @@ pub struct TableNode {
|
|||||||
/// Defines sizing of gutter rows and columns between content.
|
/// Defines sizing of gutter rows and columns between content.
|
||||||
pub gutter: Spec<Vec<TrackSizing>>,
|
pub gutter: Spec<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in the table.
|
/// The nodes to be arranged in the table.
|
||||||
pub children: Vec<LayoutNode>,
|
pub children: Vec<Template>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[class]
|
#[class]
|
||||||
@ -55,7 +55,15 @@ impl TableNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for 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 primary = styles.get(Self::PRIMARY);
|
||||||
let secondary = styles.get(Self::SECONDARY);
|
let secondary = styles.get(Self::SECONDARY);
|
||||||
let thickness = styles.get(Self::THICKNESS);
|
let thickness = styles.get(Self::THICKNESS);
|
||||||
@ -68,8 +76,8 @@ impl Show for TableNode {
|
|||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, mut child)| {
|
.map(|(i, child)| {
|
||||||
child = child.padded(Sides::splat(padding));
|
let mut child = child.pack().padded(Sides::splat(padding));
|
||||||
|
|
||||||
if let Some(stroke) = stroke {
|
if let Some(stroke) = stroke {
|
||||||
child = child.stroked(stroke);
|
child = child.stroked(stroke);
|
||||||
|
@ -123,8 +123,10 @@ impl StrongNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrongNode {
|
impl Show for StrongNode {
|
||||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||||
Ok(self.0.clone().styled(TextNode::STRONG, true))
|
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 {
|
impl Show for EmphNode {
|
||||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||||
Ok(self.0.clone().styled(TextNode::EMPH, true))
|
Ok(styles
|
||||||
|
.show(self, vm, [Value::Template(self.0.clone())])?
|
||||||
|
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,10 +752,21 @@ fn set_expr(p: &mut Parser) -> ParseResult {
|
|||||||
fn show_expr(p: &mut Parser) -> ParseResult {
|
fn show_expr(p: &mut Parser) -> ParseResult {
|
||||||
p.perform(NodeKind::ShowExpr, |p| {
|
p.perform(NodeKind::ShowExpr, |p| {
|
||||||
p.eat_assert(&NodeKind::Show);
|
p.eat_assert(&NodeKind::Show);
|
||||||
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)?;
|
p.eat_expect(&NodeKind::As)?;
|
||||||
expr(p)
|
expr(p)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a wrap expression.
|
/// Parse a wrap expression.
|
||||||
|
@ -919,14 +919,14 @@ node! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ShowExpr {
|
impl ShowExpr {
|
||||||
/// The pattern that decides which node's appearence to redefine.
|
/// The class to set the show rule for.
|
||||||
pub fn pattern(&self) -> Expr {
|
pub fn class(&self) -> Ident {
|
||||||
self.0.cast_first_child().expect("show expression is missing pattern")
|
self.0.cast_first_child().expect("show expression is missing class")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The expression that defines the node's appearence.
|
/// The closure that defines the rule.
|
||||||
pub fn body(&self) -> Expr {
|
pub fn closure(&self) -> ClosureExpr {
|
||||||
self.0.cast_last_child().expect("show expression is missing body")
|
self.0.cast_first_child().expect("show expression is missing closure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +181,7 @@ impl Category {
|
|||||||
}
|
}
|
||||||
NodeKind::WithExpr => Some(Category::Function),
|
NodeKind::WithExpr => Some(Category::Function),
|
||||||
NodeKind::SetExpr => Some(Category::Function),
|
NodeKind::SetExpr => Some(Category::Function),
|
||||||
|
NodeKind::ShowExpr => Some(Category::Function),
|
||||||
NodeKind::Call => Some(Category::Function),
|
NodeKind::Call => Some(Category::Function),
|
||||||
_ => Some(Category::Variable),
|
_ => Some(Category::Variable),
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
mod highlight;
|
mod highlight;
|
||||||
mod pretty;
|
|
||||||
mod span;
|
mod span;
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
@ -11,7 +10,6 @@ use std::ops::Range;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use highlight::*;
|
pub use highlight::*;
|
||||||
pub use pretty::*;
|
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
|
|
||||||
use self::ast::{MathNode, RawNode, TypedNode};
|
use self::ast::{MathNode, RawNode, TypedNode};
|
||||||
|
@ -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
BIN
tests/ref/style/show.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -1,5 +1,5 @@
|
|||||||
// General tests for set.
|
// 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)}
|
{set f(x)}
|
||||||
|
@ -1,9 +1,53 @@
|
|||||||
// Test show rules.
|
// Test show rules.
|
||||||
|
|
||||||
---
|
#set page("a8", footer: p => v(-5pt) + align(right, [#p]))
|
||||||
// Error: 1-34 show rules are not yet implemented
|
|
||||||
#show heading(body) as [*{body}*]
|
#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
|
#set heading(size: 100%, strong: false, block: false)
|
||||||
{show (a) as b}
|
#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}
|
||||||
|
@ -25,5 +25,5 @@ A [_B #wrap c in [*#c*]; C_] D
|
|||||||
Forest
|
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}
|
{1 + wrap x in y}
|
||||||
|
@ -23,7 +23,7 @@ use typst::{Context, Vm};
|
|||||||
use {
|
use {
|
||||||
filedescriptor::{FileDescriptor, StdioDescriptor::*},
|
filedescriptor::{FileDescriptor, StdioDescriptor::*},
|
||||||
std::fs::File,
|
std::fs::File,
|
||||||
typst::eval::Template,
|
typst::source::SourceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TYP_DIR: &str = "./typ";
|
const TYP_DIR: &str = "./typ";
|
||||||
@ -110,7 +110,7 @@ fn main() {
|
|||||||
&png_path,
|
&png_path,
|
||||||
&ref_path,
|
&ref_path,
|
||||||
pdf_path.as_deref(),
|
pdf_path.as_deref(),
|
||||||
args.debug,
|
args.syntax,
|
||||||
) as usize;
|
) as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ fn main() {
|
|||||||
struct Args {
|
struct Args {
|
||||||
filter: Vec<String>,
|
filter: Vec<String>,
|
||||||
exact: bool,
|
exact: bool,
|
||||||
debug: bool,
|
syntax: bool,
|
||||||
pdf: bool,
|
pdf: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ impl Args {
|
|||||||
fn new(args: impl Iterator<Item = String>) -> Self {
|
fn new(args: impl Iterator<Item = String>) -> Self {
|
||||||
let mut filter = Vec::new();
|
let mut filter = Vec::new();
|
||||||
let mut exact = false;
|
let mut exact = false;
|
||||||
let mut debug = false;
|
let mut syntax = false;
|
||||||
let mut pdf = false;
|
let mut pdf = false;
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
@ -146,13 +146,13 @@ impl Args {
|
|||||||
// Generate PDFs.
|
// Generate PDFs.
|
||||||
"--pdf" => pdf = true,
|
"--pdf" => pdf = true,
|
||||||
// Debug print the layout trees.
|
// Debug print the layout trees.
|
||||||
"--debug" | "-d" => debug = true,
|
"--syntax" => syntax = true,
|
||||||
// Everything else is a file filter.
|
// Everything else is a file filter.
|
||||||
_ => filter.push(arg),
|
_ => filter.push(arg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { filter, pdf, debug, exact }
|
Self { filter, pdf, syntax, exact }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, path: &Path) -> bool {
|
fn matches(&self, path: &Path) -> bool {
|
||||||
@ -172,7 +172,7 @@ fn test(
|
|||||||
png_path: &Path,
|
png_path: &Path,
|
||||||
ref_path: &Path,
|
ref_path: &Path,
|
||||||
pdf_path: Option<&Path>,
|
pdf_path: Option<&Path>,
|
||||||
debug: bool,
|
syntax: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
||||||
println!("Testing {}", name.display());
|
println!("Testing {}", name.display());
|
||||||
@ -208,7 +208,7 @@ fn test(
|
|||||||
i,
|
i,
|
||||||
compare_ref,
|
compare_ref,
|
||||||
line,
|
line,
|
||||||
debug,
|
syntax,
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
ok &= part_ok;
|
ok &= part_ok;
|
||||||
@ -242,7 +242,7 @@ fn test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
if !debug {
|
if !syntax {
|
||||||
print!("\x1b[1A");
|
print!("\x1b[1A");
|
||||||
}
|
}
|
||||||
println!("Testing {} ✔", name.display());
|
println!("Testing {} ✔", name.display());
|
||||||
@ -258,14 +258,14 @@ fn test_part(
|
|||||||
i: usize,
|
i: usize,
|
||||||
compare_ref: bool,
|
compare_ref: bool,
|
||||||
line: usize,
|
line: usize,
|
||||||
debug: bool,
|
syntax: bool,
|
||||||
rng: &mut LinearShift,
|
rng: &mut LinearShift,
|
||||||
) -> (bool, bool, Vec<Arc<Frame>>) {
|
) -> (bool, bool, Vec<Arc<Frame>>) {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let id = ctx.sources.provide(src_path, src);
|
let id = ctx.sources.provide(src_path, src);
|
||||||
let source = ctx.sources.get(id);
|
let source = ctx.sources.get(id);
|
||||||
if debug {
|
if syntax {
|
||||||
println!("Syntax Tree: {:#?}", source.root())
|
println!("Syntax Tree: {:#?}", source.root())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,13 +277,8 @@ fn test_part(
|
|||||||
let mut vm = Vm::new(ctx);
|
let mut vm = Vm::new(ctx);
|
||||||
let (frames, mut errors) = match vm.typeset(id) {
|
let (frames, mut errors) = match vm.typeset(id) {
|
||||||
Ok(mut frames) => {
|
Ok(mut frames) => {
|
||||||
let module = vm.evaluate(id).unwrap();
|
|
||||||
if debug {
|
|
||||||
println!("Template: {:#?}", module.template);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
(ok &= test_incremental(ctx, i, &module.template, &frames));
|
(ok &= test_incremental(ctx, i, id, &frames));
|
||||||
|
|
||||||
if !compare_ref {
|
if !compare_ref {
|
||||||
frames.clear();
|
frames.clear();
|
||||||
@ -483,7 +478,7 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
fn test_incremental(
|
fn test_incremental(
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
i: usize,
|
i: usize,
|
||||||
template: &Template,
|
id: SourceId,
|
||||||
frames: &[Arc<Frame>],
|
frames: &[Arc<Frame>],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
@ -498,7 +493,7 @@ fn test_incremental(
|
|||||||
|
|
||||||
ctx.layout_cache.turnaround();
|
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 total = reference.levels() - 1;
|
||||||
let misses = ctx
|
let misses = ctx
|
||||||
.layout_cache
|
.layout_cache
|
||||||
|
Loading…
x
Reference in New Issue
Block a user