From 2a3d0f4b390457174ed09347dd29e97ff9a783e4 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 15 Dec 2021 20:27:41 +0100 Subject: [PATCH] Set Rules Episode VII: The Set Awakens --- benches/bench.typ | 2 +- src/eval/class.rs | 101 ++++++++++++++++++++++++++++++++ src/eval/mod.rs | 28 ++++++++- src/eval/node.rs | 10 ++++ src/eval/scope.rs | 29 +++++---- src/eval/value.rs | 7 ++- src/library/mod.rs | 28 +++++---- src/library/page.rs | 92 ++++++++++++++--------------- src/library/par.rs | 88 +++++++++++++++------------- src/library/text.rs | 95 +++++++++++++++--------------- src/parse/mod.rs | 65 ++++++++++---------- src/parse/parser.rs | 3 + src/parse/tokens.rs | 1 + src/source.rs | 5 ++ src/syntax/ast.rs | 23 ++++++++ src/syntax/highlight.rs | 41 +++++++------ src/syntax/mod.rs | 101 +++++++++++++++++++------------- src/syntax/pretty.rs | 12 ++++ tests/typ/code/call.typ | 11 ++-- tests/typ/code/include.typ | 2 +- tests/typ/code/spread.typ | 10 ++-- tests/typ/coma.typ | 2 +- tests/typ/elements/circle.typ | 4 +- tests/typ/elements/ellipse.typ | 2 +- tests/typ/elements/image.typ | 6 +- tests/typ/elements/rect.typ | 2 +- tests/typ/elements/square.typ | 6 +- tests/typ/layout/align.typ | 2 +- tests/typ/layout/aspect.typ | 10 ++-- tests/typ/layout/background.typ | 4 +- tests/typ/layout/containers.typ | 2 +- tests/typ/layout/grid-1.typ | 4 +- tests/typ/layout/grid-2.typ | 2 +- tests/typ/layout/grid-3.typ | 10 ++-- tests/typ/layout/grid-4.typ | 2 +- tests/typ/layout/grid-5.typ | 4 +- tests/typ/layout/pad.typ | 2 +- tests/typ/layout/page.typ | 28 ++++----- tests/typ/layout/pagebreak.typ | 12 ++-- tests/typ/layout/placed.typ | 4 +- tests/typ/layout/spacing.typ | 4 +- tests/typ/layout/stack-1.typ | 12 ++-- tests/typ/layout/stack-2.typ | 6 +- tests/typ/layout/transform.typ | 6 +- tests/typ/text/baseline.typ | 2 +- tests/typ/text/basic.typ | 2 +- tests/typ/text/bidi.typ | 42 ++++++------- tests/typ/text/chinese.typ | 2 +- tests/typ/text/decorations.typ | 2 +- tests/typ/text/features.typ | 71 +++++++++++----------- tests/typ/text/font.typ | 70 +++++++++++----------- tests/typ/text/links.typ | 4 +- tests/typ/text/par.typ | 24 ++++---- tests/typ/text/shaping.typ | 10 ++-- tests/typ/text/tracking.typ | 6 +- tests/typ/text/whitespace.typ | 4 +- 56 files changed, 678 insertions(+), 451 deletions(-) create mode 100644 src/eval/class.rs diff --git a/benches/bench.typ b/benches/bench.typ index f290844bf..75c97d025 100644 --- a/benches/bench.typ +++ b/benches/bench.typ @@ -1,5 +1,5 @@ // Configuration with `page` and `font` functions. -#page(width: 450pt, margins: 1cm) +#set page(width: 450pt, margins: 1cm) // There are variables and they can take normal values like strings, ... #let city = "Berlin" diff --git a/src/eval/class.rs b/src/eval/class.rs new file mode 100644 index 000000000..456749333 --- /dev/null +++ b/src/eval/class.rs @@ -0,0 +1,101 @@ +use std::fmt::{self, Debug, Formatter, Write}; +use std::marker::PhantomData; +use std::rc::Rc; + +use super::{Args, EvalContext, Node, Styles}; +use crate::diag::TypResult; +use crate::util::EcoString; + +/// A class of nodes. +#[derive(Clone)] +pub struct Class(Rc>); + +/// The unsized structure behind the [`Rc`]. +struct Inner { + name: EcoString, + dispatch: T, +} + +impl Class { + /// Create a new class. + pub fn new(name: EcoString) -> Self + where + T: Construct + Set + 'static, + { + Self(Rc::new(Inner { + name, + dispatch: Dispatch::(PhantomData), + })) + } + + /// The name of the class. + pub fn name(&self) -> &EcoString { + &self.0.name + } + + /// Construct an instance of the class. + pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + self.0.dispatch.construct(ctx, args) + } + + /// Execute the class's set rule. + pub fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> { + self.0.dispatch.set(styles, args) + } +} + +impl Debug for Class { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("') + } +} + +impl PartialEq for Class { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers for comparison. + std::ptr::eq( + Rc::as_ptr(&self.0) as *const (), + Rc::as_ptr(&other.0) as *const (), + ) + } +} + +/// Construct an instance of a class. +pub trait Construct { + /// Construct an instance of this class from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// class's set rule. + fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult; +} + +/// Set style properties of a class. +pub trait Set { + /// Parse the arguments and insert style properties of this class into the + /// given style map. + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()>; +} + +/// Zero-sized struct whose vtable contains the constructor and set rule of a +/// class. +struct Dispatch(PhantomData); + +trait Bounds { + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult; + fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()>; +} + +impl Bounds for Dispatch +where + T: Construct + Set, +{ + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + T::construct(ctx, args) + } + + fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> { + T::set(styles, args) + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 6dcff900f..ae3301349 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -9,6 +9,7 @@ mod value; #[macro_use] mod styles; mod capture; +mod class; mod function; mod node; mod ops; @@ -16,6 +17,7 @@ mod scope; pub use array::*; pub use capture::*; +pub use class::*; pub use dict::*; pub use function::*; pub use node::*; @@ -54,7 +56,7 @@ pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult v.eval(ctx), Self::Binary(v) => v.eval(ctx), Self::Let(v) => v.eval(ctx), + Self::Set(v) => v.eval(ctx), Self::If(v) => v.eval(ctx), Self::While(v) => v.eval(ctx), Self::For(v) => v.eval(ctx), @@ -474,9 +477,17 @@ impl Eval for CallExpr { Ok(value) } + Value::Class(class) => { + let mut styles = Styles::new(); + class.set(&mut styles, &mut args)?; + let node = class.construct(ctx, &mut args)?; + args.finish()?; + Ok(Value::Node(node.styled(styles))) + } + v => bail!( self.callee().span(), - "expected function or collection, found {}", + "expected callable or collection, found {}", v.type_name(), ), } @@ -643,6 +654,19 @@ impl Eval for LetExpr { } } +impl Eval for SetExpr { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let class = self.class(); + let class = class.eval(ctx)?.cast::().at(class.span())?; + let mut args = self.args().eval(ctx)?; + class.set(&mut ctx.styles, &mut args)?; + args.finish()?; + Ok(Value::None) + } +} + impl Eval for IfExpr { type Output = Value; diff --git a/src/eval/node.rs b/src/eval/node.rs index 5653beff1..a04fe84b7 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -36,6 +36,8 @@ pub enum Node { Inline(PackedNode), /// A block node. Block(PackedNode), + /// A page node. + Page(PackedNode), /// A sequence of nodes (which may themselves contain sequences). Sequence(Vec<(Self, Styles)>), } @@ -214,6 +216,14 @@ impl Packer { Node::Block(block) => { self.push_block(block.styled(styles)); } + Node::Page(flow) => { + if self.top { + self.pagebreak(); + self.pages.push(PageNode { child: flow, styles }); + } else { + self.push_block(flow.styled(styles)); + } + } Node::Sequence(list) => { // For a list of nodes, we apply the list's styles to each node // individually. diff --git a/src/eval/scope.rs b/src/eval/scope.rs index ffe2d63e3..5178c8191 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::rc::Rc; -use super::{Args, EvalContext, Function, Value}; +use super::{Args, Class, Construct, EvalContext, Function, Set, Value}; use crate::diag::TypResult; use crate::util::EcoString; @@ -88,15 +88,6 @@ impl Scope { self.values.insert(var.into(), Rc::new(cell)); } - /// Define a constant function. - pub fn def_func(&mut self, name: impl Into, f: F) - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, - { - let name = name.into(); - self.def_const(name.clone(), Function::new(Some(name), f)); - } - /// Define a mutable variable with a value. pub fn def_mut(&mut self, var: impl Into, value: impl Into) { self.values.insert(var.into(), Rc::new(RefCell::new(value.into()))); @@ -107,6 +98,24 @@ impl Scope { self.values.insert(var.into(), slot); } + /// Define a constant function. + pub fn def_func(&mut self, name: &str, f: F) + where + F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Function::new(Some(name), f)); + } + + /// Define a constant class. + pub fn def_class(&mut self, name: &str) + where + T: Construct + Set + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Class::new::(name)); + } + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Slot> { self.values.get(var) diff --git a/src/eval/value.rs b/src/eval/value.rs index 2cf82a269..0995ab756 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::rc::Rc; -use super::{ops, Array, Dict, Function, Node}; +use super::{ops, Array, Class, Dict, Function, Node}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::layout::Layout; @@ -46,6 +46,8 @@ pub enum Value { Node(Node), /// An executable function. Func(Function), + /// A class of nodes. + Class(Class), /// A dynamic value. Dyn(Dynamic), } @@ -86,6 +88,7 @@ impl Value { Self::Dict(_) => Dict::TYPE_NAME, Self::Node(_) => Node::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME, + Self::Class(_) => Class::TYPE_NAME, Self::Dyn(v) => v.type_name(), } } @@ -148,6 +151,7 @@ impl Debug for Value { Self::Dict(v) => Debug::fmt(v, f), Self::Node(_) => f.pad("