diff --git a/src/func.rs b/src/func.rs index af3cd091d..d3bc13cc7 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,8 +1,8 @@ //! Trait and prelude for custom functions. -use crate::Pass; -use crate::syntax::ParseState; -use crate::syntax::func::FuncCall; +use crate::{Pass, Feedback}; +use crate::syntax::parsing::{FuncCall, ParseState}; +use crate::syntax::span::Span; /// Types that are useful for creating your own functions. pub mod prelude { @@ -10,10 +10,11 @@ pub mod prelude { pub use crate::layout::prelude::*; pub use crate::layout::Command::{self, *}; pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; - pub use crate::syntax::SyntaxModel; pub use crate::syntax::expr::*; - pub use crate::syntax::func::*; + pub use crate::syntax::model::SyntaxModel; pub use crate::syntax::span::{Span, Spanned}; + pub use crate::syntax::value::*; + pub use super::OptionExt; } /// Parse a function from source code. @@ -37,47 +38,34 @@ pub trait ParseFunc { ) -> Pass where Self: Sized; } +/// Extra methods on [`Options`](Option) used for argument parsing. +pub trait OptionExt: Sized { + /// Calls `f` with `val` if this is `Some(val)`. + fn with(self, f: impl FnOnce(T)); + + /// Reports an error about a missing argument with the given name and span + /// if the option is `None`. + fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self; +} + +impl OptionExt for Option { + fn with(self, f: impl FnOnce(T)) { + if let Some(val) = self { + f(val); + } + } + + fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self { + if self.is_none() { + error!(@f, span, "missing argument: {}", arg); + } + self + } +} + /// Allows to implement a function type concisely. /// -/// # Example -/// A function that hides its body depending on a boolean argument. -/// ``` -/// use typstc::func::prelude::*; -/// -/// function! { -/// #[derive(Debug, Clone, PartialEq)] -/// pub struct HiderFunc { -/// body: Option, -/// } -/// -/// parse(header, body, state, f) { -/// let body = body!(opt: body, state, f); -/// let hidden = header.args.pos.get::(&mut f.diagnostics) -/// .or_missing(&mut f.diagnostics, header.name.span, "hidden") -/// .unwrap_or(false); -/// -/// HiderFunc { body: if hidden { None } else { body } } -/// } -/// -/// layout(self, ctx, f) { -/// match &self.body { -/// Some(model) => vec![LayoutSyntaxModel(model)], -/// None => vec![], -/// } -/// } -/// } -/// ``` -/// This function can be used as follows: -/// ```typst -/// [hider: true][Hi, you.] => Nothing -/// [hider: false][Hi, you.] => Text: "Hi, you." -/// -/// [hider][Hi, you.] => Text: "Hi, you." -/// ^^^^^ -/// missing argument: hidden -/// ``` -/// -/// # More examples +/// # Examples /// Look at the source code of the [`library`](crate::library) module for more /// examples on how the macro works. #[macro_export] @@ -117,8 +105,8 @@ macro_rules! function { type Meta = $meta; fn parse( - #[allow(unused)] mut call: $crate::syntax::func::FuncCall, - #[allow(unused)] $state: &$crate::syntax::ParseState, + #[allow(unused)] mut call: $crate::syntax::parsing::FuncCall, + #[allow(unused)] $state: &$crate::syntax::parsing::ParseState, #[allow(unused)] $metadata: Self::Meta, ) -> $crate::Pass where Self: Sized { let mut feedback = $crate::Feedback::new(); @@ -128,7 +116,11 @@ macro_rules! function { let func = $code; - for arg in call.header.args.into_iter() { + for arg in call.header.args.pos.0 { + error!(@feedback, arg.span, "unexpected argument"); + } + + for arg in call.header.args.key.0 { error!(@feedback, arg.span, "unexpected argument"); } @@ -140,7 +132,7 @@ macro_rules! function { }; (@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => { - impl $crate::syntax::Model for $name { + impl $crate::syntax::model::Model for $name { fn layout<'a, 'b, 't>( #[allow(unused)] &'a $this, #[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>, @@ -177,7 +169,7 @@ macro_rules! function { macro_rules! body { (opt: $body:expr, $state:expr, $feedback:expr) => ({ $body.map(|body| { - let parsed = $crate::syntax::parse(body.v, body.span.start, $state); + let parsed = $crate::syntax::parsing::parse(body.v, body.span.start, $state); $feedback.extend(parsed.feedback); parsed.output }) diff --git a/src/geom.rs b/src/geom.rs index dcac20004..557d244c2 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -45,7 +45,7 @@ impl Value2 { pub fn with_all(s: T) -> Value2 { Value2 { x: s.clone(), y: s } } /// Get the specificed component. - pub fn get(self, axis: SpecificAxis) -> T { + pub fn get(self, axis: SpecAxis) -> T { match axis { Horizontal => self.x, Vertical => self.y, @@ -53,7 +53,7 @@ impl Value2 { } /// Borrow the specificed component mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T { + pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T { match axis { Horizontal => &mut self.x, Vertical => &mut self.y, @@ -148,19 +148,19 @@ impl Size { /// /// This assumes the size to be generalized such that `x` corresponds to the /// primary axis. - pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size { + pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Size { Size { - x: anchor(self.x, alignment.primary, axes.primary), - y: anchor(self.x, alignment.secondary, axes.secondary), + x: anchor(self.x, align.primary, axes.primary), + y: anchor(self.y, align.secondary, axes.secondary), } } } -fn anchor(length: f64, alignment: Alignment, direction: Direction) -> f64 { - match (direction.is_positive(), alignment) { - (true, Origin) | (false, End) => 0.0, +fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { + match (dir.is_positive(), align) { + (true, Start) | (false, End) => 0.0, (_, Center) => length / 2.0, - (true, End) | (false, Origin) => length, + (true, End) | (false, Start) => length, } } @@ -208,16 +208,16 @@ impl Value4 { /// alignment. /// /// Center alignment is treated the same as origin alignment. - pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T { - if alignment == End { - direction = direction.inv(); + pub fn get_mut(&mut self, mut dir: Dir, align: GenAlign) -> &mut T { + if align == End { + dir = dir.inv(); } - match direction { - LeftToRight => &mut self.left, - RightToLeft => &mut self.right, - TopToBottom => &mut self.top, - BottomToTop => &mut self.bottom, + match dir { + LTT => &mut self.left, + RTL => &mut self.right, + TTB => &mut self.top, + BTT => &mut self.bottom, } } diff --git a/src/layout/line.rs b/src/layout/line.rs index 0ef58878b..aafdab60e 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -32,7 +32,7 @@ pub struct LineContext { pub axes: LayoutAxes, /// Which alignment to set on the resulting layout. This affects how it will /// be positioned in a parent box. - pub alignment: LayoutAlignment, + pub align: LayoutAlign, /// Whether to have repeated spaces or to use only the first and only once. pub repeat: bool, /// Whether to output a command which renders a debugging box showing the @@ -56,7 +56,7 @@ struct LineRun { /// When a new run is created the alignment is yet to be determined. Once a /// layout is added, it is decided which alignment the run has and all /// further elements of the run must have this alignment. - alignment: Option, + align: Option, /// If another line run with different alignment already took up some space /// of the line, this run has less space and how much is stored here. usable: Option, @@ -71,7 +71,7 @@ impl LineLayouter { stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), axes: ctx.axes, - alignment: ctx.alignment, + align: ctx.align, repeat: ctx.repeat, debug: ctx.debug, }), @@ -84,27 +84,27 @@ impl LineLayouter { pub fn add(&mut self, layout: Layout) { let axes = self.ctx.axes; - if let Some(alignment) = self.run.alignment { - if layout.alignment.secondary != alignment.secondary { + if let Some(align) = self.run.align { + if layout.align.secondary != align.secondary { // TODO: Issue warning for non-fitting alignment in // non-repeating context. - let fitting = self.stack.is_fitting_alignment(layout.alignment); + let fitting = self.stack.is_fitting_alignment(layout.align); if !fitting && self.ctx.repeat { self.finish_space(true); } else { self.finish_line(); } - } else if layout.alignment.primary < alignment.primary { + } else if layout.align.primary < align.primary { self.finish_line(); - } else if layout.alignment.primary > alignment.primary { + } else if layout.align.primary > align.primary { let mut rest_run = LineRun::new(); let usable = self.stack.usable().primary(axes); - rest_run.usable = Some(match layout.alignment.primary { - Alignment::Origin => unreachable!("origin > x"), - Alignment::Center => usable - 2.0 * self.run.size.x, - Alignment::End => usable - self.run.size.x, + rest_run.usable = Some(match layout.align.primary { + GenAlign::Start => unreachable!("start > x"), + GenAlign::Center => usable - 2.0 * self.run.size.x, + GenAlign::End => usable - self.run.size.x, }); rest_run.size.y = self.run.size.y; @@ -133,7 +133,7 @@ impl LineLayouter { } } - self.run.alignment = Some(layout.alignment); + self.run.align = Some(layout.align); self.run.layouts.push((self.run.size.x, layout)); self.run.size.x += size.x; @@ -269,8 +269,8 @@ impl LineLayouter { self.stack.add(Layout { dimensions: self.run.size.specialized(self.ctx.axes), - alignment: self.run.alignment - .unwrap_or(LayoutAlignment::new(Origin, Origin)), + align: self.run.align + .unwrap_or(LayoutAlign::new(Start, Start)), actions: actions.into_vec(), }); @@ -292,7 +292,7 @@ impl LineRun { LineRun { layouts: vec![], size: Size::ZERO, - alignment: None, + align: None, usable: None, last_spacing: LastSpacing::Hard, } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a6af0f827..510f504ad 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -11,20 +11,20 @@ use self::prelude::*; pub mod line; pub mod stack; pub mod text; - pub_use_mod!(actions); pub_use_mod!(model); /// Basic types used across the layouting engine. pub mod prelude { pub use super::{ - LayoutContext, layout, LayoutSpace, Commands, - LayoutAxes, LayoutAlignment, LayoutExpansion + layout, LayoutContext, LayoutSpace, Command, Commands, + LayoutAxes, LayoutAlign, LayoutExpansion, }; - pub use super::GenericAxis::{self, *}; - pub use super::SpecificAxis::{self, *}; - pub use super::Direction::{self, *}; - pub use super::Alignment::{self, *}; + pub use super::Dir::{self, *}; + pub use super::GenAxis::{self, *}; + pub use super::SpecAxis::{self, *}; + pub use super::GenAlign::{self, *}; + pub use super::SpecAlign::{self, *}; } /// A collection of layouts. @@ -37,7 +37,7 @@ pub struct Layout { pub dimensions: Size, /// How to align this layout in a parent container. #[serde(skip)] - pub alignment: LayoutAlignment, + pub align: LayoutAlign, /// The actions composing this layout. pub actions: Vec, } @@ -95,82 +95,31 @@ impl LayoutSpace { } } -/// The two generic layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum GenericAxis { - /// The primary axis along which words are laid out. - Primary, - /// The secondary axis along which lines and paragraphs are laid out. - Secondary, -} - -impl GenericAxis { - /// The specific version of this axis in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - axes.get(self).axis() - } -} - -impl Display for GenericAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Primary => write!(f, "primary"), - Secondary => write!(f, "secondary"), - } - } -} - -/// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum SpecificAxis { - /// The horizontal layouting axis. - Horizontal, - /// The vertical layouting axis. - Vertical, -} - -impl SpecificAxis { - /// The generic version of this axis in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { - if self == axes.primary.axis() { Primary } else { Secondary } - } -} - -impl Display for SpecificAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Horizontal => write!(f, "horizontal"), - Vertical => write!(f, "vertical"), - } - } -} - -/// Specifies along which directions content is laid out. +/// Specifies along which axes content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { /// The primary layouting direction. - pub primary: Direction, + pub primary: Dir, /// The secondary layouting direction. - pub secondary: Direction, + pub secondary: Dir, } impl LayoutAxes { /// Create a new instance from the two values. /// /// # Panics - /// This function panics if the directions are aligned, that is, they are + /// This function panics if the axes are aligned, that is, they are /// on the same axis. - pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes { + pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes { if primary.axis() == secondary.axis() { - panic!("LayoutAxes::new: invalid aligned axes \ - {} and {}", primary, secondary); + panic!("invalid aligned axes {} and {}", primary, secondary); } LayoutAxes { primary, secondary } } /// Return the direction of the specified generic axis. - pub fn get(self, axis: GenericAxis) -> Direction { + pub fn get(self, axis: GenAxis) -> Dir { match axis { Primary => self.primary, Secondary => self.secondary, @@ -178,7 +127,7 @@ impl LayoutAxes { } /// Borrow the direction of the specified generic axis mutably. - pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction { + pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir { match axis { Primary => &mut self.primary, Secondary => &mut self.secondary, @@ -188,30 +137,29 @@ impl LayoutAxes { /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] -pub enum Direction { - LeftToRight, - RightToLeft, - TopToBottom, - BottomToTop, +pub enum Dir { + LTT, + RTL, + TTB, + BTT, } -impl Direction { +impl Dir { /// The specific axis this direction belongs to. - pub fn axis(self) -> SpecificAxis { + pub fn axis(self) -> SpecAxis { match self { - LeftToRight | RightToLeft => Horizontal, - TopToBottom | BottomToTop => Vertical, + LTT | RTL => Horizontal, + TTB | BTT => Vertical, } } /// Whether this axis points into the positive coordinate direction. /// - /// The positive directions are left-to-right and top-to-bottom. + /// The positive axes are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { - LeftToRight | TopToBottom => true, - RightToLeft | BottomToTop => false, + LTT | TTB => true, + RTL | BTT => false, } } @@ -224,44 +172,94 @@ impl Direction { } /// The inverse axis. - pub fn inv(self) -> Direction { + pub fn inv(self) -> Dir { match self { - LeftToRight => RightToLeft, - RightToLeft => LeftToRight, - TopToBottom => BottomToTop, - BottomToTop => TopToBottom, + LTT => RTL, + RTL => LTT, + TTB => BTT, + BTT => TTB, } } } -impl Display for Direction { +impl Display for Dir { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - LeftToRight => write!(f, "left-to-right"), - RightToLeft => write!(f, "right-to-left"), - TopToBottom => write!(f, "top-to-bottom"), - BottomToTop => write!(f, "bottom-to-top"), - } + f.pad(match self { + LTT => "ltr", + RTL => "rtl", + TTB => "ttb", + BTT => "btt", + }) + } +} + +/// The two generic layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum GenAxis { + /// The primary axis along which words are laid out. + Primary, + /// The secondary axis along which lines and paragraphs are laid out. + Secondary, +} + +impl GenAxis { + /// The specific version of this axis in the given system of axes. + pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis { + axes.get(self).axis() + } +} + +impl Display for GenAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Primary => "primary", + Secondary => "secondary", + }) + } +} + +/// The two specific layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum SpecAxis { + /// The horizontal layouting axis. + Horizontal, + /// The vertical layouting axis. + Vertical, +} + +impl SpecAxis { + /// The generic version of this axis in the given system of axes. + pub fn to_generic(self, axes: LayoutAxes) -> GenAxis { + if self == axes.primary.axis() { Primary } else { Secondary } + } +} + +impl Display for SpecAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Horizontal => "horizontal", + Vertical => "vertical", + }) } } /// Specifies where to align a layout in a parent container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutAlignment { +pub struct LayoutAlign { /// The alignment along the primary axis. - pub primary: Alignment, + pub primary: GenAlign, /// The alignment along the secondary axis. - pub secondary: Alignment, + pub secondary: GenAlign, } -impl LayoutAlignment { +impl LayoutAlign { /// Create a new instance from the two values. - pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment { - LayoutAlignment { primary, secondary } + pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign { + LayoutAlign { primary, secondary } } /// Return the alignment of the specified generic axis. - pub fn get(self, axis: GenericAxis) -> Alignment { + pub fn get(self, axis: GenAxis) -> GenAlign { match axis { Primary => self.primary, Secondary => self.secondary, @@ -269,7 +267,7 @@ impl LayoutAlignment { } /// Borrow the alignment of the specified generic axis mutably. - pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment { + pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign { match axis { Primary => &mut self.primary, Secondary => &mut self.secondary, @@ -277,28 +275,88 @@ impl LayoutAlignment { } } -/// Where to align content. +/// Where to align content along a generic context. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum Alignment { - /// Align content at the start of the axis. - Origin, - /// Align content centered on the axis. +pub enum GenAlign { + Start, Center, - /// Align content at the end of the axis. End, } -impl Alignment { +impl GenAlign { /// The inverse alignment. - pub fn inv(self) -> Alignment { + pub fn inv(self) -> GenAlign { match self { - Origin => End, + Start => End, Center => Center, - End => Origin, + End => Start, } } } +impl Display for GenAlign { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Start => "start", + Center => "center", + End => "end", + }) + } +} + +/// Where to align content in a specific context. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum SpecAlign { + Left, + Right, + Top, + Bottom, + Center, +} + +impl SpecAlign { + /// The specific axis this alignment refers to. + /// + /// Returns `None` if this is center. + pub fn axis(self) -> Option { + match self { + Self::Left => Some(Horizontal), + Self::Right => Some(Horizontal), + Self::Top => Some(Vertical), + Self::Bottom => Some(Vertical), + Self::Center => None, + } + } + + /// Convert this to a generic alignment. + pub fn to_generic(self, axes: LayoutAxes) -> GenAlign { + let get = |spec: SpecAxis, align: GenAlign| { + let axis = spec.to_generic(axes); + if axes.get(axis).is_positive() { align } else { align.inv() } + }; + + match self { + Self::Left => get(Horizontal, Start), + Self::Right => get(Horizontal, End), + Self::Top => get(Vertical, Start), + Self::Bottom => get(Vertical, End), + Self::Center => GenAlign::Center, + } + } +} + +impl Display for SpecAlign { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Left => "left", + Self::Right => "right", + Self::Top => "top", + Self::Bottom => "bottom", + Self::Center => "center", + }) + } +} + /// Specifies whether to expand a layout to the full size of the space it is /// laid out in or to shrink it to fit the content. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -316,7 +374,7 @@ impl LayoutExpansion { } /// Return the expansion value for the given specific axis. - pub fn get(self, axis: SpecificAxis) -> bool { + pub fn get(self, axis: SpecAxis) -> bool { match axis { Horizontal => self.horizontal, Vertical => self.vertical, @@ -324,7 +382,7 @@ impl LayoutExpansion { } /// Borrow the expansion value for the given specific axis mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { + pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool { match axis { Horizontal => &mut self.horizontal, Vertical => &mut self.vertical, diff --git a/src/layout/model.rs b/src/layout/model.rs index c78c733eb..08a9ec0e3 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -10,7 +10,8 @@ use crate::{Pass, Feedback}; use crate::SharedFontLoader; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::geom::Size; -use crate::syntax::{Model, SyntaxModel, Node, Decoration}; +use crate::syntax::decoration::Decoration; +use crate::syntax::model::{Model, SyntaxModel, Node}; use crate::syntax::span::{Span, Spanned}; use super::line::{LineLayouter, LineContext}; use super::text::{layout_text, TextContext}; @@ -42,7 +43,7 @@ pub struct LayoutContext<'a> { /// The initial axes along which content is laid out. pub axes: LayoutAxes, /// The alignment of the finished layout. - pub alignment: LayoutAlignment, + pub align: LayoutAlign, /// Whether the layout that is to be created will be nested in a parent /// container. pub nested: bool, @@ -74,7 +75,7 @@ pub enum Command<'a> { /// Add spacing of given [kind](super::SpacingKind) along the primary or /// secondary axis. The spacing kind defines how the spacing interacts with /// surrounding spacing. - AddSpacing(f64, SpacingKind, GenericAxis), + AddSpacing(f64, SpacingKind, GenAxis), /// Start a new line. BreakLine, @@ -90,9 +91,9 @@ pub enum Command<'a> { SetPageStyle(PageStyle), /// Update the alignment for future boxes added to this layouting process. - SetAlignment(LayoutAlignment), - /// Update the layouting axes along which future boxes will be laid out. - /// This finishes the current line. + SetAlignment(LayoutAlign), + /// Update the layouting axes along which future boxes will be laid + /// out. This finishes the current line. SetAxes(LayoutAxes), } @@ -115,7 +116,7 @@ impl<'a> ModelLayouter<'a> { layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, - alignment: ctx.alignment, + align: ctx.align, repeat: ctx.repeat, debug: ctx.debug && ctx.nested, line_spacing: ctx.style.text.line_spacing(), @@ -127,10 +128,10 @@ impl<'a> ModelLayouter<'a> { } /// Flatly layout a model into this layouting process. - pub fn layout<'r>( + pub async fn layout<'r>( &'r mut self, model: Spanned<&'r dyn Model> - ) -> DynFuture<'r, ()> { Box::pin(async move { + ) { // Execute the model's layout function which generates the commands. let layouted = model.v.layout(LayoutContext { style: &self.style, @@ -145,14 +146,14 @@ impl<'a> ModelLayouter<'a> { for command in layouted.output { self.execute_command(command, model.span).await; } - }) } + } /// Layout a syntax model by directly processing the nodes instead of using /// the command based architecture. - pub fn layout_syntax_model<'r>( + pub async fn layout_syntax_model<'r>( &'r mut self, model: &'r SyntaxModel - ) -> DynFuture<'r, ()> { Box::pin(async move { + ) { use Node::*; for Spanned { v: node, span } in &model.nodes { @@ -213,7 +214,7 @@ impl<'a> ModelLayouter<'a> { } } } - }) } + } /// Compute the finished list of boxes. pub fn finish(self) -> Pass { @@ -280,7 +281,7 @@ impl<'a> ModelLayouter<'a> { } } - SetAlignment(alignment) => self.ctx.alignment = alignment, + SetAlignment(align) => self.ctx.align = align, SetAxes(axes) => { self.layouter.set_axes(axes); self.ctx.axes = axes; @@ -294,7 +295,7 @@ impl<'a> ModelLayouter<'a> { loader: &self.ctx.loader, style: &self.style.text, axes: self.ctx.axes, - alignment: self.ctx.alignment, + align: self.ctx.align, }).await) } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 2dd67ea99..e684a6abb 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -46,7 +46,7 @@ pub struct StackContext { pub axes: LayoutAxes, /// Which alignment to set on the resulting layout. This affects how it will /// be positioned in a parent box. - pub alignment: LayoutAlignment, + pub align: LayoutAlign, /// Whether to have repeated spaces or to use only the first and only once. pub repeat: bool, /// Whether to output a command which renders a debugging box showing the @@ -72,7 +72,7 @@ struct Space { extra: Size, /// The rulers of a space dictate which alignments for new boxes are still /// allowed and which require a new space to be started. - rulers: Value4, + rulers: Value4, /// The last added spacing if the last added thing was spacing. last_spacing: LastSpacing, } @@ -93,7 +93,7 @@ impl StackLayouter { // If the alignment cannot be fitted in this space, finish it. // TODO: Issue warning for non-fitting alignment in // non-repeating context. - if !self.update_rulers(layout.alignment) && self.ctx.repeat { + if !self.update_rulers(layout.align) && self.ctx.repeat { self.finish_space(true); } @@ -139,7 +139,7 @@ impl StackLayouter { self.update_metrics(dimensions); self.space.layouts.push((self.ctx.axes, Layout { dimensions: dimensions.specialized(self.ctx.axes), - alignment: LayoutAlignment::new(Origin, Origin), + align: LayoutAlign::new(Start, Start), actions: vec![] })); @@ -183,26 +183,26 @@ impl StackLayouter { /// Update the rulers to account for the new layout. Returns true if a /// space break is necessary. - fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool { - let allowed = self.is_fitting_alignment(alignment); + fn update_rulers(&mut self, align: LayoutAlign) -> bool { + let allowed = self.is_fitting_alignment(align); if allowed { - *self.space.rulers.get_mut(self.ctx.axes.secondary, Origin) - = alignment.secondary; + *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) + = align.secondary; } allowed } /// Whether a layout with the given alignment can still be layouted in the /// active space. - pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool { - self.is_fitting_axis(self.ctx.axes.primary, alignment.primary) - && self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary) + pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { + self.is_fitting_axis(self.ctx.axes.primary, align.primary) + && self.is_fitting_axis(self.ctx.axes.secondary, align.secondary) } /// Whether the given alignment is still allowed according to the rulers. - fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool { - alignment >= *self.space.rulers.get_mut(direction, Origin) - && alignment <= self.space.rulers.get_mut(direction, End).inv() + fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool { + align >= *self.space.rulers.get_mut(dir, Start) + && align <= self.space.rulers.get_mut(dir, End).inv() } /// Change the layouting axes used by this layouter. @@ -326,7 +326,7 @@ impl StackLayouter { // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at it's origin by its // extent along the secondary axis. - *bound.get_mut(axes.secondary, Origin) + *bound.get_mut(axes.secondary, Start) += axes.secondary.factor() * layout.dimensions.secondary(*axes); } @@ -342,7 +342,7 @@ impl StackLayouter { for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() { let (axes, layout) = entry; - // When the axes get rotated, the the maximal primary size + // When the axes are rotated, the the maximal primary size // (`extent.x`) dictates how much secondary extent the whole run // had. This value is thus stored in `extent.y`. The primary extent // is reset for this new axis-aligned run. @@ -377,7 +377,7 @@ impl StackLayouter { let layouts = std::mem::replace(&mut self.space.layouts, vec![]); for ((axes, layout), bound) in layouts.into_iter().zip(bounds) { let size = layout.dimensions.specialized(axes); - let alignment = layout.alignment; + let align = layout.align; // The space in which this layout is aligned is given by the // distances between the borders of it's bounding box. @@ -385,7 +385,7 @@ impl StackLayouter { Size::new(bound.right - bound.left, bound.bottom - bound.top) .generalized(axes); - let local = usable.anchor(alignment, axes) - size.anchor(alignment, axes); + let local = usable.anchor(align, axes) - size.anchor(align, axes); let pos = Size::new(bound.left, bound.top) + local.specialized(axes); actions.add_layout(pos, layout); @@ -393,7 +393,7 @@ impl StackLayouter { self.layouts.push(Layout { dimensions, - alignment: self.ctx.alignment, + align: self.ctx.align, actions: actions.into_vec(), }); @@ -424,7 +424,7 @@ impl Space { size: Size::ZERO, usable, extra: Size::ZERO, - rulers: Value4::with_all(Origin), + rulers: Value4::with_all(Start), last_spacing: LastSpacing::Hard, } } diff --git a/src/layout/text.rs b/src/layout/text.rs index 30995be05..6698a0fa3 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -34,7 +34,7 @@ pub struct TextContext<'a> { /// primary-horizontal layouting is supported. pub axes: LayoutAxes, /// The alignment of the finished layout. - pub alignment: LayoutAlignment, + pub align: LayoutAlign, } /// Layouts text into a box. @@ -75,7 +75,7 @@ impl<'a> TextLayouter<'a> { Layout { dimensions: Size::new(self.width, self.ctx.style.font_size()), - alignment: self.ctx.alignment, + align: self.ctx.align, actions: self.actions.into_vec(), } } diff --git a/src/length.rs b/src/length.rs index 4c14c894b..8e07a8e64 100644 --- a/src/length.rs +++ b/src/length.rs @@ -179,7 +179,6 @@ impl Display for ParseLengthError { /// Either an absolute length or a factor of some entity. #[derive(Copy, Clone, PartialEq)] -#[allow(missing_docs)] pub enum ScaleLength { Absolute(Length), Scaled(f64), diff --git a/src/lib.rs b/src/lib.rs index 22ef52085..a295e1521 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,10 @@ use crate::diagnostic::Diagnostics; use crate::font::SharedFontLoader; use crate::layout::MultiLayout; use crate::style::{LayoutStyle, PageStyle, TextStyle}; -use crate::syntax::{Decorations, SyntaxModel, Scope, ParseState, parse}; +use crate::syntax::decoration::Decorations; +use crate::syntax::model::SyntaxModel; +use crate::syntax::parsing::{parse, ParseState}; +use crate::syntax::scope::Scope; use crate::syntax::span::{Offset, Pos}; /// Declare a module and reexport all its contents. @@ -112,8 +115,8 @@ impl Typesetter { expansion: LayoutExpansion::new(true, true), }], repeat: true, - axes: LayoutAxes::new(LeftToRight, TopToBottom), - alignment: LayoutAlignment::new(Origin, Origin), + axes: LayoutAxes::new(LTT, TTB), + align: LayoutAlign::new(Start, Start), nested: false, debug: self.debug, }, diff --git a/src/library/font.rs b/src/library/font.rs index be9b8c471..a5ee7d9c6 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -16,63 +16,64 @@ function! { } parse(header, body, ctx, f) { - let size = header.args.pos.get_first::(&mut f.diagnostics); + let size = header.args.pos.get::(); - let style = header.args.key.get::(&mut f.diagnostics, "style"); - let weight = header.args.key.get::(&mut f.diagnostics, "weight"); - let width = header.args.key.get::(&mut f.diagnostics, "width"); + let style = header.args.key.get::("style", f); + let weight = header.args.key.get::("weight", f); + let width = header.args.key.get::("width", f); - let list = header.args.pos.get_all::(&mut f.diagnostics) + let list = header.args.pos.all::() .map(|s| s.0.to_lowercase()) .collect(); let classes = header.args.key - .get_all::(&mut f.diagnostics) + .all::() .collect::>() .into_iter() .map(|(class, mut tuple)| { - let fallback = tuple.get_all::(&mut f.diagnostics) + let fallback = tuple.all::() .map(|s| s.0.to_lowercase()) .collect(); - (class.to_lowercase(), fallback) + (class.v.0, fallback) }) .collect(); FontFunc { body: body!(opt: body, ctx, f), size, - list, - classes, style, weight, width, + list, + classes, } } layout(self, ctx, f) { - styled(&self.body, ctx, Some(()), - |t, _| { - self.size.with(|s| match s { - ScaleLength::Absolute(length) => { - t.base_font_size = length.as_raw(); - t.font_scale = 1.0; - } - ScaleLength::Scaled(scale) => t.font_scale = scale, - }); - - self.style.with(|s| t.variant.style = s); - self.weight.with(|w| t.variant.weight = w); - self.width.with(|w| t.variant.width = w); - - if !self.list.is_empty() { - *t.fallback.list_mut() = self.list.clone(); + styled(&self.body, ctx, Some(()), |t, _| { + self.size.with(|s| match s { + ScaleLength::Absolute(length) => { + t.base_font_size = length.as_raw(); + t.font_scale = 1.0; } + ScaleLength::Scaled(scale) => t.font_scale = scale, + }); - for (class, fallback) in &self.classes { - t.fallback.set_class_list(class.clone(), fallback.clone()); - } + self.style.with(|s| t.variant.style = s); + self.weight.with(|w| t.variant.weight = w); + self.width.with(|w| t.variant.width = w); - t.fallback.flatten(); - }) + if !self.list.is_empty() { + *t.fallback.list_mut() = self.list.iter() + .map(|s| s.to_lowercase()) + .collect(); + } + + for (class, fallback) in &self.classes { + t.fallback.set_class_list(class.clone(), fallback.clone()); + } + + t.fallback.flatten(); + }) } } diff --git a/src/library/layout.rs b/src/library/layout.rs index 7d9895558..ed936816f 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,116 +1,22 @@ use crate::length::ScaleLength; use super::*; -function! { - /// `align`: Aligns content along the layouting axes. - #[derive(Debug, Clone, PartialEq)] - pub struct AlignFunc { - body: Option, - map: PosAxisMap, - } - - parse(header, body, ctx, f) { - AlignFunc { - body: body!(opt: body, ctx, f), - map: PosAxisMap::parse::(&mut f.diagnostics, &mut header.args), - } - } - - layout(self, ctx, f) { - ctx.base = ctx.spaces[0].dimensions; - - let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |alignment| { - alignment.axis().map(|s| s.to_generic(ctx.axes)) - }); - - for &axis in &[Primary, Secondary] { - if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) { - if let Some(generic) = alignment.to_generic(ctx.axes, axis) { - *ctx.alignment.get_mut(axis) = generic; - } else { - error!( - @f, span, - "invalid alignment `{}` for {} axis", alignment, axis, - ); - } - } - } - - match &self.body { - Some(body) => { - let layouted = layout(body, ctx).await; - f.extend(layouted.feedback); - vec![AddMultiple(layouted.output)] - } - None => vec![SetAlignment(ctx.alignment)], - } - } -} - -function! { - /// `direction`: Sets the directions of the layouting axes. - #[derive(Debug, Clone, PartialEq)] - pub struct DirectionFunc { - name_span: Span, - body: Option, - map: PosAxisMap, - } - - parse(header, body, ctx, f) { - DirectionFunc { - name_span: header.name.span, - body: body!(opt: body, ctx, f), - map: PosAxisMap::parse::(&mut f.diagnostics, &mut header.args), - } - } - - layout(self, ctx, f) { - ctx.base = ctx.spaces[0].dimensions; - - let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |direction| { - Some(direction.axis().to_generic(ctx.axes)) - }); - - let mut axes = ctx.axes; - - map.with(Primary, |&dir| axes.primary = dir); - map.with(Secondary, |&dir| axes.secondary = dir); - - if axes.primary.axis() == axes.secondary.axis() { - error!( - @f, self.name_span, - "invalid aligned primary and secondary axes: `{}`, `{}`", - ctx.axes.primary, ctx.axes.secondary, - ); - } else { - ctx.axes = axes; - } - - match &self.body { - Some(body) => { - let layouted = layout(body, ctx).await; - f.extend(layouted.feedback); - vec![AddMultiple(layouted.output)] - } - None => vec![SetAxes(ctx.axes)], - } - } -} - function! { /// `box`: Layouts content into a box. #[derive(Debug, Clone, PartialEq)] pub struct BoxFunc { body: SyntaxModel, - extents: AxisMap, + width: Option, + height: Option, debug: Option, } parse(header, body, ctx, f) { BoxFunc { body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()), - extents: AxisMap::parse::(&mut f.diagnostics, &mut header.args.key), - debug: header.args.key.get::(&mut f.diagnostics, "debug"), + width: header.args.key.get::("width", f), + height: header.args.key.get::("height", f), + debug: header.args.key.get::("debug", f), } } @@ -122,15 +28,19 @@ function! { ctx.debug = debug; } - let map = self.extents.dedup(&mut f.diagnostics, ctx.axes); - for &axis in &[Horizontal, Vertical] { - if let Some(scale) = map.get(axis) { - let length = scale.raw_scaled(ctx.base.get(axis)); - *ctx.base.get_mut(axis) = length; - *ctx.spaces[0].dimensions.get_mut(axis) = length; - *ctx.spaces[0].expansion.get_mut(axis) = true; - } - } + self.width.with(|v| { + let length = v.raw_scaled(ctx.base.x); + ctx.base.x = length; + ctx.spaces[0].dimensions.x = length; + ctx.spaces[0].expansion.horizontal = true; + }); + + self.height.with(|v| { + let length = v.raw_scaled(ctx.base.y); + ctx.base.y = length; + ctx.spaces[0].dimensions.y = length; + ctx.spaces[0].expansion.vertical = true; + }); let layouted = layout(&self.body, ctx).await; let layout = layouted.output.into_iter().next().unwrap(); @@ -139,3 +49,62 @@ function! { vec![Add(layout)] } } + +function! { + /// `align`: Aligns content along the layouting axes. + #[derive(Debug, Clone, PartialEq)] + pub struct AlignFunc { + body: Option, + aligns: Vec>, + h: Option>, + v: Option>, + } + + parse(header, body, ctx, f) { + AlignFunc { + body: body!(opt: body, ctx, f), + aligns: header.args.pos.all::>().collect(), + h: header.args.key.get::>("horizontal", f), + v: header.args.key.get::>("vertical", f), + } + } + + layout(self, ctx, f) { + ctx.base = ctx.spaces[0].dimensions; + + let axes = ctx.axes; + let all = self.aligns.iter() + .map(|align| { + let spec = align.v.axis().unwrap_or(axes.primary.axis()); + (spec, align) + }) + .chain(self.h.iter().map(|align| (Horizontal, align))) + .chain(self.v.iter().map(|align| (Vertical, align))); + + let mut had = [false; 2]; + for (axis, align) in all { + if align.v.axis().map(|a| a != axis).unwrap_or(false) { + error!( + @f, align.span, + "invalid alignment {} for {} axis", align.v, axis, + ); + } else if had[axis as usize] { + error!(@f, align.span, "duplicate alignment for {} axis", axis); + } else { + had[axis as usize] = true; + let gen_axis = axis.to_generic(ctx.axes); + let gen_align = align.v.to_generic(ctx.axes); + *ctx.align.get_mut(gen_axis) = gen_align; + } + } + + match &self.body { + Some(body) => { + let layouted = layout(body, ctx).await; + f.extend(layouted.feedback); + vec![AddMultiple(layouted.output)] + } + None => vec![SetAlignment(ctx.align)], + } + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index eac455671..7b7034a07 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,6 +1,6 @@ //! The _Typst_ standard library. -use crate::syntax::Scope; +use crate::syntax::scope::Scope; use crate::func::prelude::*; pub_use_mod!(font); @@ -12,31 +12,15 @@ pub_use_mod!(spacing); pub fn std() -> Scope { let mut std = Scope::new::(); - // Basics std.add::("val"); - - // Font setup std.add::("font"); - std.add_with_meta::("word.spacing", ContentKind::Word); - - // Layout - std.add_with_meta::("line.spacing", ContentKind::Line); - std.add_with_meta::("par.spacing", ContentKind::Paragraph); - std.add::("align"); - std.add::("direction"); - std.add::("box"); - - // Spacing - std.add::("n"); - std.add::("line.break"); - std.add::("par.break"); - std.add::("page.break"); - std.add_with_meta::("spacing", None); - std.add_with_meta::("h", Some(Horizontal)); - std.add_with_meta::("v", Some(Vertical)); - - // Page setup std.add::("page"); + std.add::("align"); + std.add::("box"); + std.add_with_meta::("h", Horizontal); + std.add_with_meta::("v", Vertical); + std.add::("parbreak"); + std.add::("pagebreak"); std } @@ -49,8 +33,8 @@ function! { } parse(header, body, state, f) { - header.args.pos.items.clear(); - header.args.key.pairs.clear(); + header.args.pos.0.clear(); + header.args.key.0.clear(); ValFunc { body: body!(opt: body, state, f) } } diff --git a/src/library/page.rs b/src/library/page.rs index 85a061e9c..f1dcc9bca 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,4 +1,4 @@ -use crate::length::Length; +use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass}; use super::*; @@ -7,18 +7,28 @@ function! { #[derive(Debug, Clone, PartialEq)] pub struct PageFunc { paper: Option, - extents: AxisMap, - padding: PaddingMap, + width: Option, + height: Option, + margins: Option, + left: Option, + right: Option, + top: Option, + bottom: Option, flip: bool, } parse(header, body, state, f) { body!(nope: body, f); PageFunc { - paper: header.args.pos.get::(&mut f.diagnostics), - extents: AxisMap::parse::(&mut f.diagnostics, &mut header.args.key), - padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args), - flip: header.args.key.get::(&mut f.diagnostics, "flip").unwrap_or(false), + paper: header.args.pos.get::(), + width: header.args.key.get::("width", f), + height: header.args.key.get::("height", f), + margins: header.args.key.get::("margins", f), + left: header.args.key.get::("left", f), + right: header.args.key.get::("right", f), + top: header.args.key.get::("top", f), + bottom: header.args.key.get::("bottom", f), + flip: header.args.key.get::("flip", f).unwrap_or(false), } } @@ -28,20 +38,22 @@ function! { if let Some(paper) = self.paper { style.class = paper.class; style.dimensions = paper.size(); - } else { + } else if self.width.is_some() || self.height.is_some() { style.class = PaperClass::Custom; } - let map = self.extents.dedup(&mut f.diagnostics, ctx.axes); - map.with(Horizontal, |&width| style.dimensions.x = width.as_raw()); - map.with(Vertical, |&height| style.dimensions.y = height.as_raw()); + self.width.with(|v| style.dimensions.x = v.as_raw()); + self.height.with(|v| style.dimensions.y = v.as_raw()); + self.margins.with(|v| style.margins.set_all(Some(v))); + self.left.with(|v| style.margins.left = Some(v)); + self.right.with(|v| style.margins.right = Some(v)); + self.top.with(|v| style.margins.top = Some(v)); + self.bottom.with(|v| style.margins.bottom = Some(v)); if self.flip { style.dimensions.swap(); } - self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins); - vec![SetPageStyle(style)] } } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index c632bdb4a..68ef27f23 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -1,20 +1,9 @@ use crate::length::ScaleLength; use crate::layout::SpacingKind; - use super::*; -use self::ContentKind::*; function! { - /// `line.break`, `n`: Ends the current line. - #[derive(Debug, Default, Clone, PartialEq)] - pub struct LineBreakFunc; - - parse(default) - layout(self, ctx, f) { vec![BreakLine] } -} - -function! { - /// `par.break`: Ends the current paragraph. + /// `parbreak`: Ends the current paragraph. /// /// self has the same effect as two subsequent newlines. #[derive(Debug, Default, Clone, PartialEq)] @@ -25,7 +14,7 @@ function! { } function! { - /// `page.break`: Ends the current page. + /// `pagebreak`: Ends the current page. #[derive(Debug, Default, Clone, PartialEq)] pub struct PageBreakFunc; @@ -34,64 +23,20 @@ function! { } function! { - /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between - /// words, lines or paragraphs as a multiple of the font size. - #[derive(Debug, Clone, PartialEq)] - pub struct ContentSpacingFunc { - body: Option, - content: ContentKind, - spacing: Option, - } - - type Meta = ContentKind; - - parse(header, body, state, f, meta) { - ContentSpacingFunc { - body: body!(opt: body, state, f), - content: meta, - spacing: header.args.pos.get::(&mut f.diagnostics) - .map(|num| num as f64) - .or_missing(&mut f.diagnostics, header.name.span, "spacing"), - } - } - - layout(self, ctx, f) { - styled(&self.body, ctx, self.spacing, |t, s| match self.content { - Word => t.word_spacing_scale = s, - Line => t.line_spacing_scale = s, - Paragraph => t.paragraph_spacing_scale = s, - }) - } -} - -/// The different kinds of content that can be spaced. Used as a metadata type -/// for the [`ContentSpacingFunc`]. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] -pub enum ContentKind { - Word, - Line, - Paragraph, -} - -function! { - /// `spacing`, `h`, `v`: Adds spacing along an axis. + /// `h` and `v`: Add spacing along an axis. #[derive(Debug, Clone, PartialEq)] pub struct SpacingFunc { - spacing: Option<(AxisKey, ScaleLength)>, + spacing: Option<(SpecAxis, ScaleLength)>, } - type Meta = Option; + type Meta = SpecAxis; parse(header, body, state, f, meta) { body!(nope: body, f); SpacingFunc { - spacing: if let Some(axis) = meta { - header.args.pos.get::(&mut f.diagnostics) - .map(|s| (AxisKey::Specific(axis), s)) - } else { - header.args.key.get_with_key::(&mut f.diagnostics) - }.or_missing(&mut f.diagnostics, header.name.span, "spacing"), + spacing: header.args.pos.expect::(f) + .map(|s| (meta, s)) + .or_missing(header.name.span, "spacing", f), } } diff --git a/src/paper.rs b/src/paper.rs index eb059090e..e8913d120 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -28,7 +28,6 @@ impl Paper { /// Paper classes define default margins for a class of related papers. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[allow(missing_docs)] pub enum PaperClass { Custom, Base, diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs new file mode 100644 index 000000000..ab327237a --- /dev/null +++ b/src/syntax/decoration.rs @@ -0,0 +1,41 @@ +//! Decorations for semantic syntax highlighting. + +use serde::Serialize; +use super::span::SpanVec; + +/// A list of spanned decorations. +pub type Decorations = SpanVec; + +/// Decorations for semantic syntax highlighting. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum Decoration { + /// A valid function name. + /// ```typst + /// [box] + /// ^^^ + /// ``` + ValidFuncName, + /// An invalid function name. + /// ```typst + /// [blabla] + /// ^^^^^^ + /// ``` + InvalidFuncName, + /// A key of a keyword argument. + /// ```typst + /// [box: width=5cm] + /// ^^^^^ + /// ``` + ArgumentKey, + /// A key in an object. + /// ```typst + /// [box: padding={ left: 1cm, right: 2cm}] + /// ^^^^ ^^^^^ + /// ``` + ObjectKey, + /// An italic word. + Italic, + /// A bold word. + Bold, +} diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 51daf304d..a551c2b60 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,16 +1,15 @@ //! Expressions in function headers. use std::fmt::{self, Debug, Formatter}; -use std::iter::FromIterator; use std::ops::Deref; use std::str::FromStr; use std::u8; -use crate::diagnostic::Diagnostics; +use crate::Feedback; use crate::length::Length; -use super::func::{Key, Value}; -use super::span::{Span, Spanned}; +use super::span::Spanned; use super::tokens::is_identifier; +use super::value::Value; /// An argument or return value. #[derive(Clone, PartialEq)] @@ -238,103 +237,63 @@ impl fmt::Display for ParseColorError { /// (false, 12cm, "hi") /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Tuple { - /// The elements of the tuple. - pub items: Vec>, -} +pub struct Tuple(pub Vec>); impl Tuple { /// Create an empty tuple. pub fn new() -> Tuple { - Tuple { items: vec![] } + Tuple(vec![]) } /// Add an element. - pub fn add(&mut self, item: Spanned) { - self.items.push(item); + pub fn push(&mut self, item: Spanned) { + self.0.push(item); } - /// Extract (and remove) the first matching value and remove and generate - /// diagnostics for all previous items that did not match. - pub fn get(&mut self, diagnostics: &mut Diagnostics) -> Option { - while !self.items.is_empty() { - let expr = self.items.remove(0); - let span = expr.span; - match V::parse(expr) { - Ok(output) => return Some(output), - Err(v) => diagnostics.push(Spanned { v, span }), + /// Expect a specific value type and generate errors for every argument + /// until an argument of the value type is found. + pub fn expect(&mut self, f: &mut Feedback) -> Option { + while !self.0.is_empty() { + let item = self.0.remove(0); + if let Some(val) = V::parse(item, f) { + return Some(val); } } None } - /// Extract (and remove) the first matching value without removing and - /// generating diagnostics for all previous items that did not match. - pub fn get_first(&mut self, _: &mut Diagnostics) -> Option { + /// Extract the first argument of the value type if there is any. + pub fn get(&mut self) -> Option { + for (i, item) in self.0.iter().enumerate() { + if let Some(val) = V::parse(item.clone(), &mut Feedback::new()) { + self.0.remove(i); + return Some(val); + } + } + None + } + + /// Extract all arguments of the value type. + pub fn all<'a, V: Value>(&'a mut self) -> impl Iterator + 'a { let mut i = 0; - while i < self.items.len() { - let expr = self.items[i].clone(); - match V::parse(expr) { - Ok(output) => { - self.items.remove(i); - return Some(output) - } - Err(_) => {}, - } - i += 1; - } - - None - } - - /// Extract and return an iterator over all values that match and generate - /// diagnostics for all items that do not match. - pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics) - -> impl Iterator + 'a { - self.items.drain(..).filter_map(move |expr| { - let span = expr.span; - match V::parse(expr) { - Ok(output) => Some(output), - Err(v) => { diagnostics.push(Spanned { v, span }); None } + std::iter::from_fn(move || { + while i < self.0.len() { + let val = V::parse(self.0[i].clone(), &mut Feedback::new()); + if val.is_some() { + self.0.remove(i); + return val; + } else { + i += 1; + } } + None }) } - - /// Iterate over the items of this tuple. - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned> { - self.items.iter() - } -} - -impl IntoIterator for Tuple { - type Item = Spanned; - type IntoIter = std::vec::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl<'a> IntoIterator for &'a Tuple { - type Item = &'a Spanned; - type IntoIter = std::slice::Iter<'a, Spanned>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator> for Tuple { - fn from_iter>>(iter: I) -> Self { - Tuple { items: iter.into_iter().collect() } - } } impl Debug for Tuple { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list() - .entries(&self.items) - .finish() + f.debug_list().entries(&self.0).finish() } } @@ -374,10 +333,7 @@ impl Deref for NamedTuple { /// { fit: false, width: 12cm, items: (1, 2, 3) } /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Object { - /// The key-value pairs of the object. - pub pairs: Vec>, -} +pub struct Object(pub Vec>); /// A key-value pair in an object. #[derive(Debug, Clone, PartialEq)] @@ -399,126 +355,52 @@ pub struct Pair { impl Object { /// Create an empty object. pub fn new() -> Object { - Object { pairs: vec![] } + Object(vec![]) } /// Add a pair to object. - pub fn add(&mut self, pair: Spanned) { - self.pairs.push(pair); + pub fn push(&mut self, pair: Spanned) { + self.0.push(pair); } - /// Extract (and remove) a pair with the given key string and matching - /// value. + /// Extract an argument with the given key if there is any. /// - /// Inserts an error if the value does not match. If the key is not - /// contained, no error is inserted. - pub fn get(&mut self, diagnostics: &mut Diagnostics, key: &str) -> Option { - let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?; - self.get_index::(diagnostics, index) - } - - /// Extract (and remove) a pair with a matching key and value. - /// - /// Inserts an error if the value does not match. If no matching key is - /// found, no error is inserted. - pub fn get_with_key( - &mut self, - diagnostics: &mut Diagnostics, - ) -> Option<(K, V)> { - for (index, pair) in self.pairs.iter().enumerate() { - let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span }; - if let Some(key) = K::parse(key) { - return self.get_index::(diagnostics, index).map(|value| (key, value)); + /// Generates an error if there is a matching key, but the value is of the + /// wrong type. + pub fn get(&mut self, key: &str, f: &mut Feedback) -> Option { + for (i, pair) in self.0.iter().enumerate() { + if pair.v.key.v.as_str() == key { + let pair = self.0.remove(i); + return V::parse(pair.v.value, f); } } None } - /// Extract (and remove) all pairs with matching keys and values. - /// - /// Inserts errors for values that do not match. - pub fn get_all<'a, K: Key, V: Value>( - &'a mut self, - diagnostics: &'a mut Diagnostics, - ) -> impl Iterator + 'a { - let mut index = 0; + /// Extract all key-value pairs where the value is of the given type. + pub fn all<'a, V: Value>(&'a mut self) + -> impl Iterator, V)> + 'a + { + let mut i = 0; std::iter::from_fn(move || { - if index < self.pairs.len() { - let key = &self.pairs[index].v.key; - let key = Spanned { v: key.v.as_str(), span: key.span }; - - Some(if let Some(key) = K::parse(key) { - self.get_index::(diagnostics, index).map(|v| (key, v)) - } else { - index += 1; - None - }) - } else { - None + while i < self.0.len() { + let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new()); + if let Some(val) = val { + let pair = self.0.remove(i); + return Some((pair.v.key, val)); + } else { + i += 1; + } } - }).filter_map(|x| x) - } - - /// Extract all key value pairs with span information. - /// - /// The spans are over both key and value, like so: - /// ```typst - /// { key: value } - /// ^^^^^^^^^^ - /// ``` - pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( - &'a mut self, - diagnostics: &'a mut Diagnostics, - ) -> impl Iterator> + 'a { - self.get_all::, Spanned>(diagnostics) - .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) - } - - /// Extract the argument at the given index and insert an error if the value - /// does not match. - fn get_index(&mut self, diagnostics: &mut Diagnostics, index: usize) -> Option { - let expr = self.pairs.remove(index).v.value; - let span = expr.span; - match V::parse(expr) { - Ok(output) => Some(output), - Err(v) => { diagnostics.push(Spanned { v, span }); None } - } - } - - /// Iterate over the pairs of this object. - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned> { - self.pairs.iter() - } -} - -impl IntoIterator for Object { - type Item = Spanned; - type IntoIter = std::vec::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.pairs.into_iter() - } -} - -impl<'a> IntoIterator for &'a Object { - type Item = &'a Spanned; - type IntoIter = std::slice::Iter<'a, Spanned>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator> for Object { - fn from_iter>>(iter: I) -> Self { - Object { pairs: iter.into_iter().collect() } + None + }) } } impl Debug for Object { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_map() - .entries(self.pairs.iter().map(|p| (&p.v.key.v, &p.v.value.v))) + .entries(self.0.iter().map(|p| (&p.v.key.v, &p.v.value.v))) .finish() } } diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs deleted file mode 100644 index 558667ddf..000000000 --- a/src/syntax/func/keys.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Key types for identifying keyword arguments. - -use crate::layout::prelude::*; -use super::values::AlignmentValue::{self, *}; -use super::*; - -use self::AxisKey::*; -use self::PaddingKey::*; - -/// Key types are used to extract keyword arguments from -/// [`Objects`](crate::syntax::expr::Object). They represent the key part of a -/// keyword argument. -/// ```typst -/// [func: key=value] -/// ^^^ -/// ``` -/// -/// # Example implementation -/// An implementation for the `AxisKey` that identifies layouting axes might -/// look as follows: -/// ``` -/// # use typstc::syntax::func::Key; -/// # use typstc::syntax::span::Spanned; -/// # #[derive(Eq, PartialEq)] enum Axis { Horizontal, Vertical, Primary, Secondary } -/// # #[derive(Eq, PartialEq)] enum AxisKey { Specific(Axis), Generic(Axis) } -/// # use Axis::*; -/// # use AxisKey::*; -/// impl Key for AxisKey { -/// fn parse(key: Spanned<&str>) -> Option { -/// match key.v { -/// "horizontal" | "h" => Some(Specific(Horizontal)), -/// "vertical" | "v" => Some(Specific(Vertical)), -/// "primary" | "p" => Some(Generic(Primary)), -/// "secondary" | "s" => Some(Generic(Secondary)), -/// _ => None, -/// } -/// } -/// } -/// ``` -pub trait Key: Sized + Eq { - /// Parse a key string into this type if it is valid for it. - fn parse(key: Spanned<&str>) -> Option; -} - -impl Key for String { - fn parse(key: Spanned<&str>) -> Option { - Some(key.v.to_string()) - } -} - -impl Key for Spanned { - fn parse(key: Spanned<&str>) -> Option { - K::parse(key).map(|v| Spanned { v, span: key.span }) - } -} - -/// Implements [`Key`] for types that just need to match on strings. -macro_rules! key { - ($type:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { - impl Key for $type { - fn parse(key: Spanned<&str>) -> Option { - match key.v { - $($($p)|* => Some($r)),*, - _ => None, - } - } - } - }; -} - -/// A key which identifies a layouting axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] -pub enum AxisKey { - Generic(GenericAxis), - Specific(SpecificAxis), -} - -impl AxisKey { - /// The generic version of this axis key in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { - match self { - Generic(axis) => axis, - Specific(axis) => axis.to_generic(axes), - } - } - - /// The specific version of this axis key in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - match self { - Generic(axis) => axis.to_specific(axes), - Specific(axis) => axis, - } - } -} - -key!(AxisKey, - "horizontal" | "h" => Specific(Horizontal), - "vertical" | "v" => Specific(Vertical), - "primary" | "p" => Generic(Primary), - "secondary" | "s" => Generic(Secondary), -); - -/// A key which is equivalent to a [`AxisKey`] but uses typical extent keywords -/// instead of axis keywords, e.g. `width` instead of `horizontal`. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ExtentKey(pub AxisKey); - -key!(ExtentKey, - "width" | "w" => ExtentKey(Specific(Horizontal)), - "height" | "h" => ExtentKey(Specific(Vertical)), - "primary-size" | "ps" => ExtentKey(Generic(Primary)), - "secondary-size" | "ss" => ExtentKey(Generic(Secondary)), -); - -impl From for AxisKey { - fn from(key: ExtentKey) -> AxisKey { - key.0 - } -} - -/// A key which identifies an axis, but alternatively allows for two positional -/// arguments with unspecified axes. -/// -/// This type does not implement `Key` in itself since it cannot be parsed from -/// a string. Rather, [`AxisKeys`](AxisKey) and positional arguments should be -/// parsed separately and mapped onto this key, as happening in the -/// [`PosAxisMap`](super::maps::PosAxisMap). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PosAxisKey { - /// The first positional argument. - First, - /// The second positional argument. - Second, - /// An axis keyword argument. - Keyword(AxisKey), -} - -/// An argument key which identifies a margin or padding target. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaddingKey { - /// All four sides should have the specified padding. - All, - /// Both sides of the given axis should have the specified padding. - Both(Axis), - /// Only the given side of the given axis should have the specified padding. - Side(Axis, AlignmentValue), -} - -key!(PaddingKey, - "horizontal" | "h" => Both(Specific(Horizontal)), - "vertical" | "v" => Both(Specific(Vertical)), - "primary" | "p" => Both(Generic(Primary)), - "secondary" | "s" => Both(Generic(Secondary)), - - "left" => Side(Specific(Horizontal), Left), - "right" => Side(Specific(Horizontal), Right), - "top" => Side(Specific(Vertical), Top), - "bottom" => Side(Specific(Vertical), Bottom), - - "primary-origin" => Side(Generic(Primary), Align(Origin)), - "primary-end" => Side(Generic(Primary), Align(End)), - "secondary-origin" => Side(Generic(Secondary), Align(Origin)), - "secondary-end" => Side(Generic(Secondary), Align(End)), - "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), - "horizontal-end" => Side(Specific(Horizontal), Align(End)), - "vertical-origin" => Side(Specific(Vertical), Align(Origin)), - "vertical-end" => Side(Specific(Vertical), Align(End)), -); diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs deleted file mode 100644 index bc290a9e4..000000000 --- a/src/syntax/func/maps.rs +++ /dev/null @@ -1,233 +0,0 @@ -//! Deduplicating maps and keys for argument parsing. - -use crate::diagnostic::Diagnostics; -use crate::geom::Value4; -use crate::layout::prelude::*; -use crate::length::ScaleLength; -use crate::syntax::span::Spanned; -use super::keys::*; -use super::values::*; -use super::*; - -/// A map which deduplicates redundant arguments. -/// -/// Whenever a duplicate argument is inserted into the map, through the -/// functions `from_iter`, `insert` or `extend` an diagnostics is added to the error -/// list that needs to be passed to those functions. -/// -/// All entries need to have span information to enable the error reporting. -#[derive(Debug, Default, Clone, Eq, PartialEq)] -pub struct DedupMap where K: Eq { - map: Vec>, -} - -impl DedupMap where K: Eq { - /// Create a new deduplicating map. - pub fn new() -> DedupMap { - DedupMap { map: vec![] } - } - - /// Create a new map from an iterator of spanned keys and values. - pub fn from_iter(diagnostics: &mut Diagnostics, iter: I) -> DedupMap - where I: IntoIterator> { - let mut map = DedupMap::new(); - map.extend(diagnostics, iter); - map - } - - /// Add a spanned key-value pair. - pub fn insert(&mut self, diagnostics: &mut Diagnostics, entry: Spanned<(K, V)>) { - if self.map.iter().any(|e| e.v.0 == entry.v.0) { - diagnostics.push(error!(entry.span, "duplicate argument")); - } else { - self.map.push(entry); - } - } - - /// Add multiple spanned key-value pairs. - pub fn extend(&mut self, diagnostics: &mut Diagnostics, items: I) - where I: IntoIterator> { - for item in items.into_iter() { - self.insert(diagnostics, item); - } - } - - /// Get the value corresponding to a key if it is present. - pub fn get(&self, key: K) -> Option<&V> { - self.map.iter().find(|e| e.v.0 == key).map(|e| &e.v.1) - } - - /// Get the value and its span corresponding to a key if it is present. - pub fn get_spanned(&self, key: K) -> Option> { - self.map.iter().find(|e| e.v.0 == key) - .map(|e| Spanned { v: &e.v.1, span: e.span }) - } - - /// Call a function with the value if the key is present. - pub fn with(&self, key: K, callback: F) where F: FnOnce(&V) { - if let Some(value) = self.get(key) { - callback(value); - } - } - - /// Create a new map where keys and values are mapped to new keys and - /// values. When the mapping introduces new duplicates, diagnostics are - /// generated. - pub fn dedup(&self, diagnostics: &mut Diagnostics, mut f: F) -> DedupMap - where F: FnMut(&K, &V) -> (K2, V2), K2: Eq { - let mut map = DedupMap::new(); - - for Spanned { v: (key, value), span } in self.map.iter() { - let (key, value) = f(key, value); - map.insert(diagnostics, Spanned { v: (key, value), span: *span }); - } - - map - } - - /// Iterate over the (key, value) pairs. - pub fn iter(&self) -> impl Iterator { - self.map.iter().map(|e| &e.v) - } -} - -/// A map for storing a value for axes given by keyword arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct AxisMap(DedupMap); - -impl AxisMap { - /// Parse an axis map from the object. - pub fn parse( - diagnostics: &mut Diagnostics, - object: &mut Object, - ) -> AxisMap where K: Key + Into { - let values: Vec<_> = object - .get_all_spanned::(diagnostics) - .map(|s| s.map(|(k, v)| (k.into(), v))) - .collect(); - - AxisMap(DedupMap::from_iter(diagnostics, values)) - } - - /// Deduplicate from specific or generic to just specific axes. - pub fn dedup(&self, diagnostics: &mut Diagnostics, axes: LayoutAxes) -> DedupMap - where V: Clone { - self.0.dedup(diagnostics, |key, val| (key.to_specific(axes), val.clone())) - } -} - -/// A map for storing values for axes that are given through a combination of -/// (two) positional and keyword arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct PosAxisMap(DedupMap); - -impl PosAxisMap { - /// Parse a positional/axis map from the function arguments. - pub fn parse( - diagnostics: &mut Diagnostics, - args: &mut FuncArgs, - ) -> PosAxisMap where K: Key + Into { - let mut map = DedupMap::new(); - - for &key in &[PosAxisKey::First, PosAxisKey::Second] { - if let Some(Spanned { v, span }) = args.pos.get::>(diagnostics) { - map.insert(diagnostics, Spanned { v: (key, v), span }) - } - } - - let keywords: Vec<_> = args.key - .get_all_spanned::(diagnostics) - .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v))) - .collect(); - - map.extend(diagnostics, keywords); - - PosAxisMap(map) - } - - /// Deduplicate from positional arguments and keyword arguments for generic - /// or specific axes to just generic axes. - pub fn dedup( - &self, - diagnostics: &mut Diagnostics, - axes: LayoutAxes, - mut f: F, - ) -> DedupMap - where - F: FnMut(&V) -> Option, - V: Clone, - { - self.0.dedup(diagnostics, |key, val| { - (match key { - PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary), - PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary), - PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), - PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, - }, val.clone()) - }) - } -} - -/// A map for storing padding given for a combination of all sides, opposing -/// sides or single sides. -#[derive(Debug, Clone, PartialEq)] -pub struct PaddingMap(DedupMap, Option>); - -impl PaddingMap { - /// Parse a padding map from the function arguments. - pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap { - let mut map = DedupMap::new(); - - let all = args.key.get::>>(diagnostics, "margins"); - if let Some(Spanned { v, span }) = all { - map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span }); - } - - let paddings: Vec<_> = args.key - .get_all_spanned::, Defaultable>(diagnostics) - .map(|s| s.map(|(k, v)| (k, v.into()))) - .collect(); - - map.extend(diagnostics, paddings); - - PaddingMap(map) - } - - /// Apply the specified padding on a value box of optional, scalable sizes. - pub fn apply( - &self, - diagnostics: &mut Diagnostics, - axes: LayoutAxes, - padding: &mut Value4> - ) { - use PaddingKey::*; - - let map = self.0.dedup(diagnostics, |key, &val| { - (match key { - All => All, - Both(axis) => Both(axis.to_specific(axes)), - Side(axis, alignment) => { - let generic = axis.to_generic(axes); - let axis = axis.to_specific(axes); - Side(axis, alignment.to_specific(axes, generic)) - } - }, val) - }); - - map.with(All, |&val| padding.set_all(val)); - map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); - map.with(Both(Vertical), |&val| padding.set_vertical(val)); - - for &(key, val) in map.iter() { - if let Side(_, alignment) = key { - match alignment { - AlignmentValue::Left => padding.left = val, - AlignmentValue::Right => padding.right = val, - AlignmentValue::Top => padding.top = val, - AlignmentValue::Bottom => padding.bottom = val, - _ => {}, - } - } - } - } -} diff --git a/src/syntax/func/mod.rs b/src/syntax/func/mod.rs deleted file mode 100644 index 37dccc3da..000000000 --- a/src/syntax/func/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Primitives for argument parsing in library functions. - -use std::iter::FromIterator; -use crate::diagnostic::{Diagnostic, Diagnostics}; -use super::expr::{Expr, Ident, Tuple, Object, Pair}; -use super::span::{Span, Spanned}; - -pub_use_mod!(maps); -pub_use_mod!(keys); -pub_use_mod!(values); - -/// An invocation of a function. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncCall<'s> { - pub header: FuncHeader, - /// The body as a raw string containing what's inside of the brackets. - pub body: Option>, -} - -/// The parsed header of a function (everything in the first set of brackets). -#[derive(Debug, Clone, PartialEq)] -pub struct FuncHeader { - pub name: Spanned, - pub args: FuncArgs, -} - -/// The positional and keyword arguments passed to a function. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct FuncArgs { - /// The positional arguments. - pub pos: Tuple, - /// They keyword arguments. - pub key: Object, -} - -impl FuncArgs { - /// Create new empty function arguments. - pub fn new() -> FuncArgs { - FuncArgs { - pos: Tuple::new(), - key: Object::new(), - } - } - - /// Add an argument. - pub fn add(&mut self, arg: Spanned) { - match arg.v { - FuncArg::Pos(item) => self.pos.add(Spanned::new(item, arg.span)), - FuncArg::Key(pair) => self.key.add(Spanned::new(pair, arg.span)), - } - } - - /// Iterate over all arguments. - pub fn into_iter(self) -> impl Iterator> { - let pos = self.pos.items.into_iter() - .map(|spanned| spanned.map(|item| FuncArg::Pos(item))); - - let key = self.key.pairs.into_iter() - .map(|spanned| spanned.map(|pair| FuncArg::Key(pair))); - - pos.chain(key) - } -} - -impl FromIterator> for FuncArgs { - fn from_iter>>(iter: I) -> Self { - let mut args = FuncArgs::new(); - for item in iter.into_iter() { - args.add(item); - } - args - } -} - -/// Either a positional or keyword argument. -#[derive(Debug, Clone, PartialEq)] -pub enum FuncArg { - /// A positional argument. - Pos(Expr), - /// A keyword argument. - Key(Pair), -} - -/// Extra methods on [`Options`](Option) used for argument parsing. -pub trait OptionExt: Sized { - /// Calls `f` with `val` if this is `Some(val)`. - fn with(self, f: impl FnOnce(T)); - - /// Add an error about a missing argument `arg` with the given span if the - /// option is `None`. - fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self; -} - -impl OptionExt for Option { - fn with(self, f: impl FnOnce(T)) { - if let Some(val) = self { - f(val); - } - } - - fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self { - if self.is_none() { - diagnostics.push(error!(span, "missing argument: {}", arg)); - } - self - } -} diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs deleted file mode 100644 index d5e9c6e8e..000000000 --- a/src/syntax/func/values.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Value types for extracting function arguments. - -use std::fmt::{self, Display, Formatter}; -use fontdock::{FontStyle, FontWeight, FontWidth}; - -use crate::layout::prelude::*; -use crate::length::{Length, ScaleLength}; -use crate::paper::Paper; -use super::*; - -use self::AlignmentValue::*; - -/// Value types are used to extract the values of positional and keyword -/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and -/// [`Objects`](crate::syntax::expr::Object). They represent the value part of -/// an argument. -/// ```typst -/// [func: value, key=value] -/// ^^^^^ ^^^^^ -/// ``` -/// -/// # Example implementation -/// An implementation for `bool` might look as follows: -/// ``` -/// # use typstc::error; -/// # use typstc::diagnostic::Diagnostic; -/// # use typstc::syntax::expr::Expr; -/// # use typstc::syntax::func::Value; -/// # use typstc::syntax::span::Spanned; -/// # struct Bool; /* -/// impl Value for bool { -/// # */ impl Value for Bool { -/// fn parse(expr: Spanned) -> Result { -/// match expr.v { -/// # /* -/// Expr::Bool(b) => Ok(b), -/// # */ Expr::Bool(_) => Ok(Bool), -/// other => Err(error!("expected bool, found {}", other.name())), -/// } -/// } -/// } -/// ``` -pub trait Value: Sized { - /// Parse an expression into this value or return an error if the expression - /// is valid for this value type. - fn parse(expr: Spanned) -> Result; -} - -impl Value for Spanned { - fn parse(expr: Spanned) -> Result { - let span = expr.span; - V::parse(expr).map(|v| Spanned { v, span }) - } -} - -/// Implements [`Value`] for types that just need to match on expressions. -macro_rules! value { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl Value for $type { - fn parse(expr: Spanned) -> Result { - #[allow(unreachable_patterns)] - match expr.v { - $($p => Ok($r)),*, - other => Err( - error!("expected {}, found {}", $name, other.name()) - ), - } - } - } - }; -} - -value!(Expr, "expression", e => e); - -value!(Ident, "identifier", Expr::Ident(i) => i); -value!(String, "string", Expr::Str(s) => s); -value!(f64, "number", Expr::Number(n) => n); -value!(bool, "bool", Expr::Bool(b) => b); -value!(Length, "length", Expr::Length(l) => l); -value!(Tuple, "tuple", Expr::Tuple(t) => t); -value!(Object, "object", Expr::Object(o) => o); - -value!(ScaleLength, "number or length", - Expr::Length(length) => ScaleLength::Absolute(length), - Expr::Number(scale) => ScaleLength::Scaled(scale), -); - -/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements -/// `Into`. -pub struct StringLike(pub String); - -value!(StringLike, "identifier or string", - Expr::Ident(Ident(s)) => StringLike(s), - Expr::Str(s) => StringLike(s), -); - -impl From for String { - fn from(like: StringLike) -> String { - like.0 - } -} - -/// A value type that matches the identifier `default` or a value type `V` and -/// implements `Into