diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-12-15 20:27:41 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-12-15 20:27:41 +0100 |
| commit | 2a3d0f4b390457174ed09347dd29e97ff9a783e4 (patch) | |
| tree | 0e0634bff6b7f64131267f4cbe05651c1c91d900 /src | |
| parent | 244ad386ec271ff86a2101eb4cc38d37a55552b9 (diff) | |
Set Rules Episode VII: The Set Awakens
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/class.rs | 101 | ||||
| -rw-r--r-- | src/eval/mod.rs | 28 | ||||
| -rw-r--r-- | src/eval/node.rs | 10 | ||||
| -rw-r--r-- | src/eval/scope.rs | 29 | ||||
| -rw-r--r-- | src/eval/value.rs | 7 | ||||
| -rw-r--r-- | src/library/mod.rs | 28 | ||||
| -rw-r--r-- | src/library/page.rs | 92 | ||||
| -rw-r--r-- | src/library/par.rs | 88 | ||||
| -rw-r--r-- | src/library/text.rs | 95 | ||||
| -rw-r--r-- | src/parse/mod.rs | 65 | ||||
| -rw-r--r-- | src/parse/parser.rs | 3 | ||||
| -rw-r--r-- | src/parse/tokens.rs | 1 | ||||
| -rw-r--r-- | src/source.rs | 5 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 23 | ||||
| -rw-r--r-- | src/syntax/highlight.rs | 29 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 101 | ||||
| -rw-r--r-- | src/syntax/pretty.rs | 12 |
17 files changed, 471 insertions, 246 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs new file mode 100644 index 00000000..45674933 --- /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<Inner<dyn Bounds>>); + +/// The unsized structure behind the [`Rc`]. +struct Inner<T: ?Sized> { + name: EcoString, + dispatch: T, +} + +impl Class { + /// Create a new class. + pub fn new<T>(name: EcoString) -> Self + where + T: Construct + Set + 'static, + { + Self(Rc::new(Inner { + name, + dispatch: Dispatch::<T>(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<Node> { + 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("<class ")?; + f.write_str(&self.0.name)?; + f.write_char('>') + } +} + +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<Node>; +} + +/// 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<T>(PhantomData<T>); + +trait Bounds { + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>; + fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()>; +} + +impl<T> Bounds for Dispatch<T> +where + T: Construct + Set, +{ + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + 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 6dcff900..ae330134 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<M pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, - /// The node defined by this module. + /// The module's layoutable contents. pub node: Node, } @@ -288,6 +290,7 @@ impl Eval for Expr { Self::Unary(v) => 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<Self::Output> { + let class = self.class(); + let class = class.eval(ctx)?.cast::<Class>().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 5653beff..a04fe84b 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 ffe2d63e..5178c819 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<F>(&mut self, name: impl Into<EcoString>, f: F) - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + '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<EcoString>, value: impl Into<Value>) { 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<F>(&mut self, name: &str, f: F) + where + F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Function::new(Some(name), f)); + } + + /// Define a constant class. + pub fn def_class<T>(&mut self, name: &str) + where + T: Construct + Set + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Class::new::<T>(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 2cf82a26..0995ab75 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("<template>"), Self::Func(v) => Debug::fmt(v, f), + Self::Class(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } } @@ -394,6 +398,7 @@ primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Node: "template", Node } primitive! { Function: "function", Func } +primitive! { Class: "class", Class } impl Cast<Value> for Value { fn is(_: &Value) -> bool { diff --git a/src/library/mod.rs b/src/library/mod.rs index 9b6da6a9..1e2ee224 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -27,7 +27,9 @@ mod prelude { pub use std::rc::Rc; pub use crate::diag::{At, TypResult}; - pub use crate::eval::{Args, EvalContext, Node, Property, Smart, Styles, Value}; + pub use crate::eval::{ + Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value, + }; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::*; @@ -60,22 +62,24 @@ use crate::geom::*; pub fn new() -> Scope { let mut std = Scope::new(); - // Text. - std.def_func("font", font); - std.def_func("par", par); - std.def_func("parbreak", parbreak); + // Classes. + std.def_class::<PageNode>("page"); + std.def_class::<ParNode>("par"); + std.def_class::<TextNode>("text"); + + // Text functions. std.def_func("strike", strike); std.def_func("underline", underline); std.def_func("overline", overline); std.def_func("link", link); - // Layout. - std.def_func("page", page); - std.def_func("pagebreak", pagebreak); + // Layout functions. std.def_func("h", h); std.def_func("v", v); std.def_func("box", box_); std.def_func("block", block); + std.def_func("pagebreak", pagebreak); + std.def_func("parbreak", parbreak); std.def_func("stack", stack); std.def_func("grid", grid); std.def_func("pad", pad); @@ -85,14 +89,14 @@ pub fn new() -> Scope { std.def_func("scale", scale); std.def_func("rotate", rotate); - // Elements. + // Element functions. std.def_func("image", image); std.def_func("rect", rect); std.def_func("square", square); std.def_func("ellipse", ellipse); std.def_func("circle", circle); - // Utility. + // Utility functions. std.def_func("assert", assert); std.def_func("type", type_); std.def_func("repr", repr); @@ -110,14 +114,14 @@ pub fn new() -> Scope { std.def_func("len", len); std.def_func("sorted", sorted); - // Colors. + // Predefined colors. std.def_const("white", RgbaColor::WHITE); std.def_const("black", RgbaColor::BLACK); std.def_const("eastern", RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF)); std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - // Arbitrary constants. + // Other constants. std.def_const("ltr", Dir::LTR); std.def_const("rtl", Dir::RTL); std.def_const("ttb", Dir::TTB); diff --git a/src/library/page.rs b/src/library/page.rs index 4a4fab03..0b0cc2d9 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -6,53 +6,6 @@ use std::str::FromStr; use super::prelude::*; use super::PadNode; -/// `page`: Configure pages. -pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - castable! { - Paper, - Expected: "string", - Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?, - } - - let body: Option<Node> = args.find(); - - let mut map = Styles::new(); - let styles = match body { - Some(_) => &mut map, - None => &mut ctx.styles, - }; - - if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) { - styles.set(PageNode::CLASS, paper.class()); - styles.set(PageNode::WIDTH, Smart::Custom(paper.width())); - styles.set(PageNode::HEIGHT, Smart::Custom(paper.height())); - } - - if let Some(width) = args.named("width")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::WIDTH, width); - } - - if let Some(height) = args.named("height")? { - styles.set(PageNode::CLASS, PaperClass::Custom); - styles.set(PageNode::HEIGHT, height); - } - - let margins = args.named("margins")?; - - set!(styles, PageNode::FLIPPED => args.named("flipped")?); - set!(styles, PageNode::LEFT => args.named("left")?.or(margins)); - set!(styles, PageNode::TOP => args.named("top")?.or(margins)); - set!(styles, PageNode::RIGHT => args.named("right")?.or(margins)); - set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins)); - set!(styles, PageNode::FILL => args.named("fill")?); - - Ok(match body { - Some(body) => Value::block(body.into_block().styled(map)), - None => Value::None, - }) -} - /// `pagebreak`: Start a new page. pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { Ok(Value::Node(Node::Pagebreak)) @@ -90,6 +43,45 @@ properties! { FILL: Option<Paint> = None, } +impl Construct for PageNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // TODO(set): Make sure it's really a page so that it doesn't merge + // with adjacent pages. + Ok(Node::Page(args.expect::<Node>("body")?.into_block())) + } +} + +impl Set for PageNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) { + styles.set(PageNode::CLASS, paper.class()); + styles.set(PageNode::WIDTH, Smart::Custom(paper.width())); + styles.set(PageNode::HEIGHT, Smart::Custom(paper.height())); + } + + if let Some(width) = args.named("width")? { + styles.set(PageNode::CLASS, PaperClass::Custom); + styles.set(PageNode::WIDTH, width); + } + + if let Some(height) = args.named("height")? { + styles.set(PageNode::CLASS, PaperClass::Custom); + styles.set(PageNode::HEIGHT, height); + } + + let margins = args.named("margins")?; + + set!(styles, PageNode::FLIPPED => args.named("flipped")?); + set!(styles, PageNode::LEFT => args.named("left")?.or(margins)); + set!(styles, PageNode::TOP => args.named("top")?.or(margins)); + set!(styles, PageNode::RIGHT => args.named("right")?.or(margins)); + set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins)); + set!(styles, PageNode::FILL => args.named("fill")?); + + Ok(()) + } +} + impl PageNode { /// Layout the page run into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { @@ -182,6 +174,12 @@ impl Default for Paper { } } +castable! { + Paper, + Expected: "string", + Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?, +} + /// Defines default margins for a class of related papers. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PaperClass { diff --git a/src/library/par.rs b/src/library/par.rs index 6ea960bf..d63c2315 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -9,46 +9,6 @@ use super::prelude::*; use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode}; use crate::util::{EcoString, RangeExt, RcExt, SliceExt}; -/// `par`: Configure paragraphs. -pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let spacing = args.named("spacing")?; - let leading = args.named("leading")?; - - let mut dir = - args.named("lang")? - .map(|iso: EcoString| match iso.to_lowercase().as_str() { - "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL, - "en" | "fr" | "de" => Dir::LTR, - _ => Dir::LTR, - }); - - if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? { - if v.axis() != SpecAxis::Horizontal { - bail!(span, "must be horizontal"); - } - dir = Some(v); - } - - let mut align = None; - if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { - if v.axis() != SpecAxis::Horizontal { - bail!(span, "must be horizontal"); - } - align = Some(v); - } - - if let (Some(dir), None) = (dir, align) { - align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); - } - - set!(ctx.styles, ParNode::DIR => dir); - set!(ctx.styles, ParNode::ALIGN => align); - set!(ctx.styles, ParNode::LEADING => leading); - set!(ctx.styles, ParNode::SPACING => spacing); - - Ok(Value::None) -} - /// `parbreak`: Start a new paragraph. pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { Ok(Value::Node(Node::Parbreak)) @@ -71,6 +31,54 @@ properties! { SPACING: Linear = Relative::new(1.2).into(), } +impl Construct for ParNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // Lift to a block so that it doesn't merge with adjacent stuff. + Ok(Node::Block(args.expect::<Node>("body")?.into_block())) + } +} + +impl Set for ParNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + let spacing = args.named("spacing")?; + let leading = args.named("leading")?; + + let mut dir = + args.named("lang")? + .map(|iso: EcoString| match iso.to_lowercase().as_str() { + "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL, + "en" | "fr" | "de" => Dir::LTR, + _ => Dir::LTR, + }); + + if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? { + if v.axis() != SpecAxis::Horizontal { + bail!(span, "must be horizontal"); + } + dir = Some(v); + } + + let mut align = None; + if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? { + if v.axis() != SpecAxis::Horizontal { + bail!(span, "must be horizontal"); + } + align = Some(v); + } + + if let (Some(dir), None) = (dir, align) { + align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right }); + } + + set!(styles, ParNode::DIR => dir); + set!(styles, ParNode::ALIGN => align); + set!(styles, ParNode::LEADING => leading); + set!(styles, ParNode::SPACING => spacing); + + Ok(()) + } +} + impl Layout for ParNode { fn layout( &self, diff --git a/src/library/text.rs b/src/library/text.rs index 4ab378c2..4baf4bc5 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -15,54 +15,6 @@ use crate::font::{ use crate::geom::{Dir, Em, Length, Point, Size}; use crate::util::{EcoString, SliceExt}; -/// `font`: Configure the font. -pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let body = args.find::<Node>(); - - let mut map = Styles::new(); - let styles = match body { - Some(_) => &mut map, - None => &mut ctx.styles, - }; - - let list = args.named("family")?.or_else(|| { - let families: Vec<_> = args.all().collect(); - (!families.is_empty()).then(|| families) - }); - - set!(styles, TextNode::FAMILY_LIST => list); - set!(styles, TextNode::SERIF_LIST => args.named("serif")?); - set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?); - set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?); - set!(styles, TextNode::FALLBACK => args.named("fallback")?); - set!(styles, TextNode::STYLE => args.named("style")?); - set!(styles, TextNode::WEIGHT => args.named("weight")?); - set!(styles, TextNode::STRETCH => args.named("stretch")?); - set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find())); - set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find())); - set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new)); - set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?); - set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?); - set!(styles, TextNode::KERNING => args.named("kerning")?); - set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?); - set!(styles, TextNode::ALTERNATES => args.named("alternates")?); - set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?); - set!(styles, TextNode::LIGATURES => args.named("ligatures")?); - set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); - set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); - set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?); - set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?); - set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?); - set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?); - set!(styles, TextNode::FRACTIONS => args.named("fractions")?); - set!(styles, TextNode::FEATURES => args.named("features")?); - - Ok(match body { - Some(body) => Value::Node(body.styled(map)), - None => Value::None, - }) -} - /// `strike`: Typeset striken-through text. pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { line_impl(args, LineKind::Strikethrough) @@ -172,6 +124,53 @@ properties! { FEATURES: Vec<(Tag, u32)> = vec![], } +impl Construct for TextNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> { + // We don't need to do anything more here because the whole point of the + // text constructor is to apply the styles and that happens + // automatically during class construction. + args.expect::<Node>("body") + } +} + +impl Set for TextNode { + fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> { + let list = args.named("family")?.or_else(|| { + let families: Vec<_> = args.all().collect(); + (!families.is_empty()).then(|| families) + }); + + set!(styles, TextNode::FAMILY_LIST => list); + set!(styles, TextNode::SERIF_LIST => args.named("serif")?); + set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?); + set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?); + set!(styles, TextNode::FALLBACK => args.named("fallback")?); + set!(styles, TextNode::STYLE => args.named("style")?); + set!(styles, TextNode::WEIGHT => args.named("weight")?); + set!(styles, TextNode::STRETCH => args.named("stretch")?); + set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find())); + set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find())); + set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new)); + set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?); + set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?); + set!(styles, TextNode::KERNING => args.named("kerning")?); + set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?); + set!(styles, TextNode::ALTERNATES => args.named("alternates")?); + set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?); + set!(styles, TextNode::LIGATURES => args.named("ligatures")?); + set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?); + set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?); + set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?); + set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?); + set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?); + set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?); + set!(styles, TextNode::FRACTIONS => args.named("fractions")?); + set!(styles, TextNode::FEATURES => args.named("features")?); + + Ok(()) + } +} + impl Debug for TextNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index dbec0a5e..0a2f73f5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -110,12 +110,13 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { // Hashtag + keyword / identifier. NodeKind::Ident(_) | NodeKind::Let + | NodeKind::Set | NodeKind::If | NodeKind::While | NodeKind::For | NodeKind::Import | NodeKind::Include => { - let stmt = matches!(token, NodeKind::Let | NodeKind::Import); + let stmt = matches!(token, NodeKind::Let | NodeKind::Set | NodeKind::Import); let group = if stmt { Group::Stmt } else { Group::Expr }; p.start_group(group); @@ -265,6 +266,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { // Keywords. Some(NodeKind::Let) => let_expr(p), + Some(NodeKind::Set) => set_expr(p), Some(NodeKind::If) => if_expr(p), Some(NodeKind::While) => while_expr(p), Some(NodeKind::For) => for_expr(p), @@ -507,45 +509,40 @@ fn block(p: &mut Parser) { /// Parse a function call. fn call(p: &mut Parser, callee: Marker) -> ParseResult { - callee.perform(p, NodeKind::Call, |p| match p.peek_direct() { - Some(NodeKind::LeftParen | NodeKind::LeftBracket) => { - args(p, true); - Ok(()) - } - _ => { - p.expected_at("argument list"); - Err(()) - } - }) + callee.perform(p, NodeKind::Call, |p| args(p, true, true)) } /// Parse the arguments to a function call. -fn args(p: &mut Parser, allow_template: bool) { +fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { + match if direct { p.peek_direct() } else { p.peek() } { + Some(NodeKind::LeftParen) => {} + Some(NodeKind::LeftBracket) if brackets => {} + _ => { + p.expected("argument list"); + return Err(()); + } + } + p.perform(NodeKind::CallArgs, |p| { - if !allow_template || p.peek_direct() == Some(&NodeKind::LeftParen) { + if p.at(&NodeKind::LeftParen) { p.start_group(Group::Paren); collection(p); p.end_group(); } - while allow_template && p.peek_direct() == Some(&NodeKind::LeftBracket) { + while brackets && p.peek_direct() == Some(&NodeKind::LeftBracket) { template(p); } - }) + }); + + Ok(()) } /// Parse a with expression. fn with_expr(p: &mut Parser, marker: Marker) -> ParseResult { marker.perform(p, NodeKind::WithExpr, |p| { p.eat_assert(&NodeKind::With); - - if p.at(&NodeKind::LeftParen) { - args(p, false); - Ok(()) - } else { - p.expected("argument list"); - Err(()) - } + args(p, false, false) }) } @@ -587,6 +584,15 @@ fn let_expr(p: &mut Parser) -> ParseResult { }) } +/// Parse a set expression. +fn set_expr(p: &mut Parser) -> ParseResult { + p.perform(NodeKind::SetExpr, |p| { + p.eat_assert(&NodeKind::Set); + ident(p)?; + args(p, true, false) + }) +} + /// Parse an if expresion. fn if_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::IfExpr, |p| { @@ -612,8 +618,7 @@ fn while_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::WhileExpr, |p| { p.eat_assert(&NodeKind::While); expr(p)?; - body(p)?; - Ok(()) + body(p) }) } @@ -624,8 +629,7 @@ fn for_expr(p: &mut Parser) -> ParseResult { for_pattern(p)?; p.eat_expect(&NodeKind::In)?; expr(p)?; - body(p)?; - Ok(()) + body(p) }) } @@ -664,9 +668,7 @@ fn import_expr(p: &mut Parser) -> ParseResult { }; p.eat_expect(&NodeKind::From)?; - expr(p)?; - - Ok(()) + expr(p) }) } @@ -674,8 +676,7 @@ fn import_expr(p: &mut Parser) -> ParseResult { fn include_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::IncludeExpr, |p| { p.eat_assert(&NodeKind::Include); - expr(p)?; - Ok(()) + expr(p) }) } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 6598b1f2..503158a9 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -125,6 +125,7 @@ impl<'s> Parser<'s> { } /// Eat, debug-asserting that the token is the given one. + #[track_caller] pub fn eat_assert(&mut self, t: &NodeKind) { debug_assert_eq!(self.peek(), Some(t)); self.eat(); @@ -199,6 +200,7 @@ impl<'s> Parser<'s> { /// to `end_group`. /// /// This panics if the current token does not start the given group. + #[track_caller] pub fn start_group(&mut self, kind: Group) { self.groups.push(GroupEntry { kind, prev_mode: self.tokens.mode() }); self.tokens.set_mode(match kind { @@ -220,6 +222,7 @@ impl<'s> Parser<'s> { /// End the parsing of a group. /// /// This panics if no group was started. + #[track_caller] pub fn end_group(&mut self) { let group_mode = self.tokens.mode(); let group = self.groups.pop().expect("no started group"); diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 07a6fe12..27ec046d 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -527,6 +527,7 @@ fn keyword(ident: &str) -> Option<NodeKind> { "or" => NodeKind::Or, "with" => NodeKind::With, "let" => NodeKind::Let, + "set" => NodeKind::Set, "if" => NodeKind::If, "else" => NodeKind::Else, "for" => NodeKind::For, diff --git a/src/source.rs b/src/source.rs index 509b0a76..5fd85ed9 100644 --- a/src/source.rs +++ b/src/source.rs @@ -149,6 +149,11 @@ impl SourceFile { Self::new(SourceId(0), Path::new(""), src.into()) } + /// The root node of the untyped green tree. + pub fn root(&self) -> &Rc<GreenNode> { + &self.root + } + /// The file's abstract syntax tree. pub fn ast(&self) -> TypResult<Markup> { let red = RedNode::from_root(self.root.clone(), self.id); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 8df25f59..9190953f 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -211,6 +211,8 @@ pub enum Expr { With(WithExpr), /// A let expression: `let x = 1`. Let(LetExpr), + /// A set expression: `set text(...)`. + Set(SetExpr), /// An if-else expression: `if x { y } else { z }`. If(IfExpr), /// A while loop expression: `while x { y }`. @@ -238,6 +240,7 @@ impl TypedNode for Expr { NodeKind::Closure => node.cast().map(Self::Closure), NodeKind::WithExpr => node.cast().map(Self::With), NodeKind::LetExpr => node.cast().map(Self::Let), + NodeKind::SetExpr => node.cast().map(Self::Set), NodeKind::IfExpr => node.cast().map(Self::If), NodeKind::WhileExpr => node.cast().map(Self::While), NodeKind::ForExpr => node.cast().map(Self::For), @@ -262,6 +265,7 @@ impl TypedNode for Expr { Self::Closure(v) => v.as_red(), Self::With(v) => v.as_red(), Self::Let(v) => v.as_red(), + Self::Set(v) => v.as_red(), Self::If(v) => v.as_red(), Self::While(v) => v.as_red(), Self::For(v) => v.as_red(), @@ -838,6 +842,25 @@ impl LetExpr { } node! { + /// A set expression: `set text(...)`. + SetExpr +} + +impl SetExpr { + /// The class to set style properties for. + pub fn class(&self) -> Ident { + self.0.cast_first_child().expect("set expression is missing class") + } + + /// The style properties to set. + pub fn args(&self) -> CallArgs { + self.0 + .cast_first_child() + .expect("set expression is missing argument list") + } +} + +node! { /// An import expression: `import a, b, c from "utils.typ"`. ImportExpr } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 22e6cf50..85fbef12 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -96,22 +96,23 @@ impl Category { NodeKind::EnDash => Some(Category::Shortcut), NodeKind::EmDash => Some(Category::Shortcut), NodeKind::Escape(_) => Some(Category::Escape), + NodeKind::Not => Some(Category::Keyword), + NodeKind::And => Some(Category::Keyword), + NodeKind::Or => Some(Category::Keyword), + NodeKind::With => Some(Category::Keyword), NodeKind::Let => Some(Category::Keyword), + NodeKind::Set => Some(Category::Keyword), NodeKind::If => Some(Category::Keyword), NodeKind::Else => Some(Category::Keyword), + NodeKind::While => Some(Category::Keyword), NodeKind::For => Some(Category::Keyword), NodeKind::In => Some(Category::Keyword), - NodeKind::While => Some(Category::Keyword), NodeKind::Break => Some(Category::Keyword), NodeKind::Continue => Some(Category::Keyword), NodeKind::Return => Some(Category::Keyword), NodeKind::Import => Some(Category::Keyword), - NodeKind::Include => Some(Category::Keyword), NodeKind::From => Some(Category::Keyword), - NodeKind::Not => Some(Category::Keyword), - NodeKind::And => Some(Category::Keyword), - NodeKind::Or => Some(Category::Keyword), - NodeKind::With => Some(Category::Keyword), + NodeKind::Include => Some(Category::Keyword), NodeKind::Plus => Some(Category::Operator), NodeKind::Star => Some(Category::Operator), NodeKind::Slash => Some(Category::Operator), @@ -139,6 +140,7 @@ impl Category { Some(Category::Function) } NodeKind::WithExpr => Some(Category::Function), + NodeKind::SetExpr => Some(Category::Function), NodeKind::Call => Some(Category::Function), _ => Some(Category::Variable), }, @@ -161,21 +163,22 @@ impl Category { NodeKind::Array => None, NodeKind::Dict => None, NodeKind::Named => None, + NodeKind::Template => None, NodeKind::Group => None, + NodeKind::Block => None, NodeKind::Unary => None, NodeKind::Binary => None, NodeKind::Call => None, NodeKind::CallArgs => None, + NodeKind::Spread => None, NodeKind::Closure => None, NodeKind::ClosureParams => None, - NodeKind::Spread => None, - NodeKind::Template => None, - NodeKind::Block => None, - NodeKind::ForExpr => None, - NodeKind::WhileExpr => None, - NodeKind::IfExpr => None, - NodeKind::LetExpr => None, NodeKind::WithExpr => None, + NodeKind::LetExpr => None, + NodeKind::SetExpr => None, + NodeKind::IfExpr => None, + NodeKind::WhileExpr => None, + NodeKind::ForExpr => None, NodeKind::ForPattern => None, NodeKind::ImportExpr => None, NodeKind::ImportItems => None, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index e9011a4d..b9b00487 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -83,19 +83,15 @@ impl Default for Green { impl Debug for Green { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {}", self.kind(), self.len())?; - if let Self::Node(n) = self { - if !n.children.is_empty() { - f.write_str(" ")?; - f.debug_list().entries(&n.children).finish()?; - } + match self { + Self::Node(node) => node.fmt(f), + Self::Token(token) => token.fmt(f), } - Ok(()) } } /// An inner node in the untyped green tree. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct GreenNode { /// Node metadata. data: GreenData, @@ -145,8 +141,19 @@ impl From<Rc<GreenNode>> for Green { } } +impl Debug for GreenNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.data.fmt(f)?; + if !self.children.is_empty() { + f.write_str(" ")?; + f.debug_list().entries(&self.children).finish()?; + } + Ok(()) + } +} + /// Data shared between inner and leaf nodes. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub struct GreenData { /// What kind of node this is (each kind would have its own struct in a /// strongly typed AST). @@ -178,6 +185,12 @@ impl From<GreenData> for Green { } } +impl Debug for GreenData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}: {}", self.kind, self.len()) + } +} + /// A owned wrapper for a green node with span information. /// /// Owned variant of [`RedRef`]. Can be [cast](Self::cast) to an AST node. @@ -465,6 +478,8 @@ pub enum NodeKind { Auto, /// The `let` keyword. Let, + /// The `set` keyword. + Set, /// The `if` keyword. If, /// The `else` keyword. @@ -552,8 +567,12 @@ pub enum NodeKind { Dict, /// A named pair: `thickness: 3pt`. Named, + /// A template expression: `[*Hi* there!]`. + Template, /// A grouped expression: `(1 + 2)`. Group, + /// A block expression: `{ let x = 1; x + 2 }`. + Block, /// A unary operation: `-x`. Unary, /// A binary operation: `a + b`. @@ -562,39 +581,37 @@ pub enum NodeKind { Call, /// A function call's argument list: `(x, y)`. CallArgs, + /// Spreaded arguments or a parameter sink: `..x`. + Spread, /// A closure expression: `(x, y) => z`. Closure, /// A closure's parameters: `(x, y)`. ClosureParams, - /// A parameter sink: `..x`. - Spread, - /// A template expression: `[*Hi* there!]`. - Template, - /// A block expression: `{ let x = 1; x + 2 }`. - Block, - /// A for loop expression: `for x in y { ... }`. - ForExpr, - /// A while loop expression: `while x { ... }`. - WhileExpr, - /// An if expression: `if x { ... }`. - IfExpr, + /// A with expression: `f with (x, y: 1)`. + WithExpr, /// A let expression: `let x = 1`. LetExpr, - /// The `with` expression: `with (1)`. - WithExpr, + /// A set expression: `set text(...)`. + SetExpr, + /// An if-else expression: `if x { y } else { z }`. + IfExpr, + /// A while loop expression: `while x { ... }`. + WhileExpr, + /// A for loop expression: `for x in y { ... }`. + ForExpr, /// A for loop's destructuring pattern: `x` or `x, y`. ForPattern, - /// The import expression: `import x from "foo.typ"`. + /// An import expression: `import a, b, c from "utils.typ"`. ImportExpr, /// Items to import: `a, b, c`. ImportItems, - /// The include expression: `include "foo.typ"`. + /// An include expression: `include "chapter1.typ"`. IncludeExpr, - /// Two slashes followed by inner contents, terminated with a newline: - /// `//<str>\n`. + /// A line comment, two slashes followed by inner contents, terminated with + /// a newline: `//<str>\n`. LineComment, - /// A slash and a star followed by inner contents, terminated with a star - /// and a slash: `/*<str>*/`. + /// A block comment, a slash and a star followed by inner contents, + /// terminated with a star and a slash: `/*<str>*/`. /// /// The comment can contain nested block comments. BlockComment, @@ -616,11 +633,6 @@ pub enum ErrorPos { } impl NodeKind { - /// Whether this is some kind of parenthesis. - pub fn is_paren(&self) -> bool { - matches!(self, Self::LeftParen | Self::RightParen) - } - /// Whether this is some kind of bracket. pub fn is_bracket(&self) -> bool { matches!(self, Self::LeftBracket | Self::RightBracket) @@ -631,6 +643,11 @@ impl NodeKind { matches!(self, Self::LeftBrace | Self::RightBrace) } + /// Whether this is some kind of parenthesis. + pub fn is_paren(&self) -> bool { + matches!(self, Self::LeftParen | Self::RightParen) + } + /// Whether this is some kind of error. pub fn is_error(&self) -> bool { matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_)) @@ -672,6 +689,7 @@ impl NodeKind { Self::None => "`none`", Self::Auto => "`auto`", Self::Let => "keyword `let`", + Self::Set => "keyword `set`", Self::If => "keyword `if`", Self::Else => "keyword `else`", Self::For => "keyword `for`", @@ -712,21 +730,22 @@ impl NodeKind { Self::Array => "array", Self::Dict => "dictionary", Self::Named => "named argument", + Self::Template => "template", Self::Group => "group", + Self::Block => "block", Self::Unary => "unary expression", Self::Binary => "binary expression", Self::Call => "call", Self::CallArgs => "call arguments", + Self::Spread => "parameter sink", Self::Closure => "closure", Self::ClosureParams => "closure parameters", - Self::Spread => "parameter sink", - Self::Template => "template", - Self::Block => "block", - Self::ForExpr => "for-loop expression", - Self::WhileExpr => "while-loop expression", - Self::IfExpr => "`if` expression", - Self::LetExpr => "`let` expression", Self::WithExpr => "`with` expression", + Self::LetExpr => "`let` expression", + Self::SetExpr => "`set` expression", + Self::IfExpr => "`if` expression", + Self::WhileExpr => "while-loop expression", + Self::ForExpr => "for-loop expression", Self::ForPattern => "for-loop destructuring pattern", Self::ImportExpr => "`import` expression", Self::ImportItems => "import items", diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index c453fb56..62ecb8cd 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -225,6 +225,7 @@ impl Pretty for Expr { 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::If(v) => v.pretty(p), Self::While(v) => v.pretty(p), Self::For(v) => v.pretty(p), @@ -444,6 +445,16 @@ impl Pretty for LetExpr { } } +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 IfExpr { fn pretty(&self, p: &mut Printer) { p.push_str("if "); @@ -639,6 +650,7 @@ mod tests { // Control flow. roundtrip("#let x = 1 + 2"); roundtrip("#let f(x) = y"); + roundtrip("#set text(size: 12pt)"); roundtrip("#if x [y] else [z]"); roundtrip("#if x {} else if y {} else {}"); roundtrip("#while x {y}"); |
