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),
|
||||
|
||||
// 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() {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -336,7 +336,7 @@ pub trait Cast<V = Value>: Sized {
|
||||
macro_rules! primitive {
|
||||
(
|
||||
$type:ty: $name:literal, $variant:ident
|
||||
$(, $other:ident($binding:ident) => $out:expr)*
|
||||
$(, $other:ident$(($binding:ident))? => $out:expr)*
|
||||
) => {
|
||||
impl Type for $type {
|
||||
const TYPE_NAME: &'static str = $name;
|
||||
@ -344,13 +344,14 @@ macro_rules! primitive {
|
||||
|
||||
impl Cast for $type {
|
||||
fn is(value: &Value) -> bool {
|
||||
matches!(value, Value::$variant(_) $(| Value::$other(_))*)
|
||||
matches!(value, Value::$variant(_)
|
||||
$(| primitive!(@$other $(($binding))?))*)
|
||||
}
|
||||
|
||||
fn cast(value: Value) -> StrResult<Self> {
|
||||
match value {
|
||||
Value::$variant(v) => Ok(v),
|
||||
$(Value::$other($binding) => Ok($out),)*
|
||||
$(Value::$other$(($binding))? => Ok($out),)*
|
||||
v => Err(format!(
|
||||
"expected {}, found {}",
|
||||
Self::TYPE_NAME,
|
||||
@ -366,6 +367,9 @@ macro_rules! primitive {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(@$other:ident($binding:ident)) => { Value::$other(_) };
|
||||
(@$other:ident) => { Value::$other };
|
||||
}
|
||||
|
||||
/// Implement traits for dynamic types.
|
||||
@ -440,7 +444,7 @@ primitive! { Color: "color", Color }
|
||||
primitive! { EcoString: "string", Str }
|
||||
primitive! { Array: "array", Array }
|
||||
primitive! { Dict: "dictionary", Dict }
|
||||
primitive! { Template: "template", Template }
|
||||
primitive! { Template: "template", Template, None => Template::new() }
|
||||
primitive! { Func: "function", Func, Class(v) => v.constructor() }
|
||||
primitive! { Args: "arguments", Args }
|
||||
primitive! { Class: "class", Class }
|
||||
|
@ -25,7 +25,7 @@
|
||||
//! [evaluate]: Vm::evaluate
|
||||
//! [module]: eval::Module
|
||||
//! [template]: eval::Template
|
||||
//! [layouted]: eval::Template::layout
|
||||
//! [layouted]: eval::Template::layout_pages
|
||||
//! [cache]: layout::LayoutCache
|
||||
//! [PDF]: export::pdf
|
||||
|
||||
@ -51,6 +51,7 @@ pub mod parse;
|
||||
pub mod source;
|
||||
pub mod syntax;
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@ -218,6 +219,9 @@ pub struct Vm<'a> {
|
||||
pub modules: HashMap<SourceId, Module>,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// Currently evaluated show rules. This is used to prevent recursive show
|
||||
/// rules.
|
||||
pub rules: Vec<TypeId>,
|
||||
/// How deeply nested the current layout tree position is.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub level: usize,
|
||||
@ -236,6 +240,7 @@ impl<'a> Vm<'a> {
|
||||
route: vec![],
|
||||
modules: HashMap::new(),
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
rules: vec![],
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}])
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
},
|
||||
|
@ -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};
|
||||
|
@ -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.
|
||||
|
||||
---
|
||||
// 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)}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user