diff --git a/src/lib.rs b/src/lib.rs index 9259ffb75..c6dfb132e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,21 +2,21 @@ //! //! # Steps //! - **Parsing:** The parsing step first transforms a plain string into an -//! [iterator of tokens](crate::parsing::Tokens). Then parser constructs a +//! [iterator of tokens](crate::syntax::Tokens). Then, a parser constructs a //! syntax tree from the token stream. The structures describing the tree can -//! be found in the [syntax]. Dynamic functions parse their own bodies -//! themselves. +//! be found in the [syntax](crate::syntax) module. //! - **Layouting:** The next step is to transform the syntax tree into a //! portable representation of the typesetted document. Types for these can be -//! found in the [layout] module. -//! - **Exporting:** The finished document can then be exported into supported -//! formats. Submodules for the supported formats are located in the [export] -//! module. Currently the only supported format is _PDF_. +//! found in the [layout] module. A finished layout reading for exporting is a +//! [multi layout](crate::layout::MultiLayout) consisting of multiple boxes (or +//! pages). +//! - **Exporting:** The finished document can finally be exported into a supported +//! format. Submodules for these formats are located in the [export](crate::export) +//! module. Currently, the only supported output format is _PDF_. pub extern crate toddle; use std::cell::RefCell; - use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; use crate::func::Scope; @@ -36,15 +36,15 @@ pub mod size; pub mod style; pub mod syntax; -/// Transforms source code into typesetted documents. +/// Transforms source code into typesetted layouts. /// -/// Can be configured through various methods. +/// A typesetter can be configured through various methods. pub struct Typesetter<'p> { /// The font loader shared by all typesetting processes. loader: SharedFontLoader<'p>, - /// The default text style. + /// The base text style. text_style: TextStyle, - /// The default page style. + /// The base page style. page_style: PageStyle, } @@ -59,13 +59,13 @@ impl<'p> Typesetter<'p> { } } - /// Set the default page style for the document. + /// Set the base page style. #[inline] pub fn set_page_style(&mut self, style: PageStyle) { self.page_style = style; } - /// Set the default text style for the document. + /// Set the base text style. #[inline] pub fn set_text_style(&mut self, style: TextStyle) { self.text_style = style; @@ -90,7 +90,7 @@ impl<'p> Typesetter<'p> { parse(src, ParseContext { scope: &scope }) } - /// Layout a syntax tree and return the layout and the referenced font list. + /// Layout a syntax tree and return the produced layout. pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult { let space = LayoutSpace { dimensions: self.page_style.dimensions, @@ -113,7 +113,7 @@ impl<'p> Typesetter<'p> { Ok(pages) } - /// Typeset a portable document from source code. + /// Process source code directly into a layout. pub fn typeset(&self, src: &str) -> Result { let tree = self.parse(src)?; let layout = self.layout(&tree)?; @@ -123,9 +123,9 @@ impl<'p> Typesetter<'p> { /// The general error type for typesetting. pub enum TypesetError { - /// An error that occured while parsing. + /// An error that occured in the parsing step. Parse(ParseError), - /// An error that occured while layouting. + /// An error that occured in the layouting step. Layout(LayoutError), } diff --git a/src/macros.rs b/src/macros.rs index a1c182fbe..bbf404be2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,4 +1,6 @@ -/// Create an error type. +//! Auxiliary macros. + +/// Create trait implementations for an error type. macro_rules! error_type { ( $var:ident: $err:ident, @@ -38,7 +40,7 @@ macro_rules! error_type { }; } -/// Create a `Debug` implementation from a display implementation. +/// Create a `Debug` implementation from a `Display` implementation. macro_rules! debug_display { ($type:ident) => { impl std::fmt::Debug for $type { diff --git a/src/size.rs b/src/size.rs index 414fd855a..250f27c7b 100644 --- a/src/size.rs +++ b/src/size.rs @@ -6,7 +6,7 @@ use std::iter::Sum; use std::ops::*; use std::str::FromStr; -/// A general spacing type. +/// A general space type. #[derive(Copy, Clone, PartialEq, Default)] pub struct Size { /// The size in typographic points (1/72 inches). @@ -98,31 +98,31 @@ impl Size { } impl Size2D { - /// Create a new vector from two sizes. + /// Create a new 2D-size from two sizes. #[inline] pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } } - /// Create a vector with all set to zero. + /// Create a 2D-size with both sizes set to zero. #[inline] pub fn zero() -> Size2D { Size2D::default() } - /// Create a new vector with `y` set to zero and `x` to a value. + /// Create a new 2D-size with `x` set to a value and `y` zero. #[inline] pub fn with_x(x: Size) -> Size2D { Size2D { x, y: Size::zero() } } - /// Create a new vector with `x` set to zero and `y` to a value. + /// Create a new 2D-size with `y` set to a value and `x` zero. #[inline] pub fn with_y(y: Size) -> Size2D { Size2D { x: Size::zero(), y } } - /// Return a [`Size2D`] padded by the paddings of the given box. + /// Return a 2D-size padded by the paddings of the given box. #[inline] pub fn padded(&self, padding: SizeBox) -> Size2D { Size2D { @@ -131,7 +131,7 @@ impl Size2D { } } - /// Return a [`Size2D`] reduced by the paddings of the given box. + /// Return a 2D-size reduced by the paddings of the given box. #[inline] pub fn unpadded(&self, padding: SizeBox) -> Size2D { Size2D { @@ -140,8 +140,8 @@ impl Size2D { } } - /// Whether the given [`Size2D`] fits into this one, that is, - /// both coordinate values are smaller. + /// Whether the given 2D-size fits into this one, that is, + /// both coordinate values are smaller or equal. #[inline] pub fn fits(&self, other: Size2D) -> bool { self.x >= other.x && self.y >= other.y @@ -160,7 +160,7 @@ impl SizeBox { } } - /// Create a box with all set to zero. + /// Create a box with all values set to zero. #[inline] pub fn zero() -> SizeBox { SizeBox::default() @@ -319,6 +319,8 @@ impl_num_both!(Mul, mul, MulAssign, mul_assign, i32); impl_num_back!(Div, div, DivAssign, div_assign, f32); impl_num_back!(Div, div, DivAssign, div_assign, i32); +//------------------------------------------------------------------------------------------------// + impl Display for Size2D { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[{}, {}]", self.x, self.y) @@ -412,6 +414,8 @@ impl_num_both2d!(Mul, mul, MulAssign, mul_assign, i32); impl_num_back2d!(Div, div, DivAssign, div_assign, f32); impl_num_back2d!(Div, div, DivAssign, div_assign, i32); +//------------------------------------------------------------------------------------------------// + impl Display for SizeBox { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!( diff --git a/src/style.rs b/src/style.rs index 3bc1e33f2..68bd9a270 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,22 +1,22 @@ -//! Layouting styles. +//! Styles for text and pages. use toddle::query::FontClass; - +use FontClass::*; use crate::size::{Size, Size2D, SizeBox}; -/// Default styles for text. +/// Defines which fonts to use and how to space text. #[derive(Debug, Clone)] pub struct TextStyle { - /// The classes the font we want has to be part of. + /// The classes the font has to be part of. pub classes: Vec, - /// A sequence of classes. We need the font to be part of at least one of - /// these and preferably the leftmost possible. + /// The fallback classes from which the font needs to match the + /// leftmost possible one. pub fallback: Vec, /// The font size. pub font_size: f32, /// The line spacing (as a multiple of the font size). pub line_spacing: f32, - /// The paragraphs spacing (as a multiple of the line spacing). + /// The paragraphs spacing (as a multiple of the font size). pub paragraph_spacing: f32, } @@ -24,21 +24,35 @@ impl TextStyle { /// Toggle a class. /// /// If the class was one of _italic_ or _bold_, then: - /// - If it was not present, the _regular_ class will be removed. - /// - If it was present, the _regular_ class will be added in case the other + /// - If it was not present before, the _regular_ class will be removed. + /// - If it was present before, the _regular_ class will be added in case the other /// style class is not present. pub fn toggle_class(&mut self, class: FontClass) { if self.classes.contains(&class) { - self.classes.retain(|x| x != &class); - if (class == FontClass::Italic && !self.classes.contains(&FontClass::Bold)) - || (class == FontClass::Bold && !self.classes.contains(&FontClass::Italic)) - { - self.classes.push(FontClass::Regular); + // If we retain a Bold or Italic class, we will not add + // the Regular class. + let mut regular = true; + self.classes.retain(|x| { + if class == *x { + false + } else { + if class == Bold || class == Italic { + regular = false; + } + true + } + }); + + if regular { + self.classes.push(Regular); } } else { + // If we add an Italic or Bold class, we remove + // the Regular class. if class == FontClass::Italic || class == FontClass::Bold { self.classes.retain(|x| x != &FontClass::Regular); } + self.classes.push(class); } } @@ -46,7 +60,6 @@ impl TextStyle { impl Default for TextStyle { fn default() -> TextStyle { - use FontClass::*; TextStyle { classes: vec![Regular], fallback: vec![Serif], @@ -57,10 +70,10 @@ impl Default for TextStyle { } } -/// Default styles for pages. +/// Defines the size and margins of a page. #[derive(Debug, Clone)] pub struct PageStyle { - /// Width and height of the page. + /// The width and height of the page. pub dimensions: Size2D, /// The amount of white space on each side. pub margins: SizeBox, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index b46beb36a..da8cdc809 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,6 +1,5 @@ -//! Tokenized and syntax tree representations of source code. +//! Tokenization and parsing of source code. -use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; use crate::func::Function; @@ -17,8 +16,7 @@ pub use parsing::{parse, ParseContext, ParseError, ParseResult}; pub enum Token<'s> { /// One or more whitespace (non-newline) codepoints. Space, - /// A line feed (`\n`, `\r\n` and some more as defined by the Unicode - /// standard). + /// A line feed (`\n`, `\r\n` and some more as defined by the Unicode standard). Newline, /// A left bracket: `[`. LeftBracket, @@ -28,37 +26,36 @@ pub enum Token<'s> { /// header only). /// /// If a colon occurs outside of a function header, it will be tokenized as - /// a [Word](Token::Word). + /// [Text](Token::Text), just like the other tokens annotated with + /// _Function header only_. Colon, - /// An equals (`=`) sign assigning a function argument a value (Function - /// header only). + /// An equals (`=`) sign assigning a function argument a value (Function header only). Equals, /// A comma (`,`) separating two function arguments (Function header only). Comma, /// Quoted text as a string value (Function header only). Quoted(&'s str), - /// An underscore, indicating text in italics. + /// An underscore, indicating text in italics (Body only). Underscore, - /// A star, indicating bold text. + /// A star, indicating bold text (Body only). Star, - /// A backtick, indicating monospace text. + /// A backtick, indicating monospace text (Body only). Backtick, /// A line comment. LineComment(&'s str), /// A block comment. BlockComment(&'s str), - /// A star followed by a slash unexpectedly ending a block comment (the - /// comment was not started before, otherwise a - /// [BlockComment](Token::BlockComment) would be returned). + /// A star followed by a slash unexpectedly ending a block comment + /// (the comment was not started before, otherwise a + /// [BlockComment](Token::BlockComment would be returned). StarSlash, - /// Everything else is just text. + /// A unit of Plain text. Text(&'s str), } -/// A tree representation of the source. +/// A tree representation of source code. #[derive(Debug, PartialEq)] pub struct SyntaxTree { - /// The children. pub nodes: Vec, } @@ -70,10 +67,10 @@ impl SyntaxTree { } } -/// A node in the abstract syntax tree. +/// A node in the syntax tree. #[derive(Debug, PartialEq)] pub enum Node { - /// Whitespace between other nodes. + /// Whitespace. Space, /// A line feed. Newline, @@ -89,28 +86,22 @@ pub enum Node { Func(FuncCall), } -/// A function invocation consisting of header and body. +/// A function invocation, consisting of header and a dynamically parsed body. #[derive(Debug)] pub struct FuncCall { pub header: FuncHeader, pub body: Box, } -impl PartialEq for FuncCall { - fn eq(&self, other: &FuncCall) -> bool { - (self.header == other.header) && (&self.body == &other.body) - } -} - /// Contains header information of a function invocation. #[derive(Debug, Clone, PartialEq)] pub struct FuncHeader { pub name: String, pub args: Vec, - pub kwargs: HashMap, + pub kwargs: Vec<(String, Expression)>, } -/// A value expression. +/// An argument or return value. #[derive(Debug, Clone, PartialEq)] pub enum Expression { Ident(String), @@ -120,6 +111,12 @@ pub enum Expression { Bool(bool), } +impl PartialEq for FuncCall { + fn eq(&self, other: &FuncCall) -> bool { + (self.header == other.header) && (&self.body == &other.body) + } +} + impl Display for Expression { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use Expression::*; diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index c22870854..1e9497291 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,6 +1,5 @@ -//! Parsing of source code into token streams and syntax trees. +//! Parsing of token streams into syntax trees. -use std::collections::HashMap; use unicode_xid::UnicodeXID; use crate::func::{Function, Scope}; @@ -20,7 +19,7 @@ pub struct ParseContext<'a> { pub scope: &'a Scope, } -/// Transforms token streams to syntax trees. +/// Transforms token streams into syntax trees. #[derive(Debug)] struct Parser<'s> { src: &'s str, @@ -35,14 +34,15 @@ struct Parser<'s> { enum ParserState { /// The base state of the parser. Body, - /// We saw one newline already. + /// We saw one newline already and are looking for another. FirstNewline, - /// We wrote a newline. + /// We saw at least two newlines and wrote one, thus not + /// writing another one for more newlines. WroteNewline, } impl<'s> Parser<'s> { - /// Create a new parser from the source and the context. + /// Create a new parser from the source code and the context. fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> { Parser { src, @@ -53,9 +53,8 @@ impl<'s> Parser<'s> { } } - /// Parse the source into an abstract syntax tree. + /// Parse the source into a syntax tree. fn parse(mut self) -> ParseResult { - // Loop through all the tokens. while self.tokens.peek().is_some() { self.parse_white()?; self.parse_body_part()?; @@ -66,27 +65,30 @@ impl<'s> Parser<'s> { /// Parse the next part of the body. fn parse_body_part(&mut self) -> ParseResult<()> { + use Token::*; + if let Some(token) = self.tokens.peek() { match token { - // Functions - Token::LeftBracket => self.parse_func()?, - Token::RightBracket => return Err(ParseError::new("unexpected closing bracket")), + // Functions. + LeftBracket => self.parse_func()?, + RightBracket => return Err(ParseError::new("unexpected closing bracket")), - // Modifiers - Token::Underscore => self.append_consumed(Node::ToggleItalics), - Token::Star => self.append_consumed(Node::ToggleBold), - Token::Backtick => self.append_consumed(Node::ToggleMonospace), + // Modifiers. + Underscore => self.append_consumed(Node::ToggleItalics), + Star => self.append_consumed(Node::ToggleBold), + Backtick => self.append_consumed(Node::ToggleMonospace), - // Normal text - Token::Text(word) => self.append_consumed(Node::Text(word.to_owned())), - - Token::Colon | Token::Equals => panic!("bad token for body: {:?}", token), + // Normal text. + Text(word) => self.append_consumed(Node::Text(word.to_owned())), // The rest is handled elsewhere or should not happen, because `Tokens` does not - // yield colons or equals in the body, but their text equivalents instead. - _ => panic!("unexpected token: {:?}", token), + // yield these in a body. + Space | Newline | LineComment(_) | BlockComment(_) | + Colon | Equals | Comma | Quoted(_) | StarSlash + => panic!("parse_body_part: unexpected token: {:?}", token), } } + Ok(()) } @@ -122,7 +124,7 @@ impl<'s> Parser<'s> { let mut header = FuncHeader { name, args: vec![], - kwargs: HashMap::new(), + kwargs: vec![], }; self.skip_white(); @@ -147,9 +149,9 @@ impl<'s> Parser<'s> { } /// Parse the arguments to a function. - fn parse_func_args(&mut self) -> ParseResult<(Vec, HashMap)> { + fn parse_func_args(&mut self) -> ParseResult<(Vec, Vec<(String, Expression)>)> { let mut args = Vec::new(); - let kwargs = HashMap::new(); + let kwargs = Vec::new(); let mut comma = false; loop { @@ -448,38 +450,22 @@ mod tests { #[derive(Debug, PartialEq)] pub struct TreeFn(pub SyntaxTree); - impl Function for TreeFn { - fn parse(_: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult - where Self: Sized { - if let Some(src) = body { - parse(src, ctx).map(|tree| TreeFn(tree)) - } else { - Err(ParseError::new("expected body for tree fn")) - } - } + function! { + data: TreeFn, - fn layout(&self, _: LayoutContext) -> LayoutResult { - Ok(CommandList::new()) - } + parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) } + layout(_, _) { Ok(commands![]) } } /// A testing function without a body. #[derive(Debug, PartialEq)] pub struct BodylessFn; - impl Function for BodylessFn { - fn parse(_: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult - where Self: Sized { - if body.is_none() { - Ok(BodylessFn) - } else { - Err(ParseError::new("unexpected body for bodyless fn")) - } - } + function! { + data: BodylessFn, - fn layout(&self, _: LayoutContext) -> LayoutResult { - Ok(CommandList::new()) - } + parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) } + layout(_, _) { Ok(commands![]) } } } @@ -539,7 +525,7 @@ mod tests { header: FuncHeader { name: $name.to_string(), args: vec![], - kwargs: HashMap::new(), + kwargs: vec![], }, body: $body, } @@ -617,7 +603,7 @@ mod tests { header: FuncHeader { name: name.to_string(), args, - kwargs: HashMap::new(), + kwargs: vec![], }, body: Box::new(BodylessFn) })