diff --git a/src/func/helpers.rs b/src/func/helpers.rs index ea0f6b2f0..a7c270c26 100644 --- a/src/func/helpers.rs +++ b/src/func/helpers.rs @@ -5,16 +5,16 @@ use super::prelude::*; /// Implement the function trait more concisely. #[macro_export] macro_rules! function { - (data: $ident:ident, $($tts:tt)*) => { + (data: $ident:ident, $($tts:tt)*) => ( #[allow(unused_imports)] use $crate::func::prelude::*; impl Function for $ident { function!(@parse $ident, $($tts)*); } - }; + ); - (@parse $ident:ident, parse: plain, $($tts:tt)*) => { + (@parse $ident:ident, parse: plain, $($tts:tt)*) => ( fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult where Self: Sized { @@ -25,14 +25,14 @@ macro_rules! function { Ok($ident) } function!(@layout $($tts)*); - }; + ); ( @parse $ident:ident, parse($args:ident, $body:ident, $ctx:ident) $block:block $($tts:tt)* - ) => { + ) => ( fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult where Self: Sized { @@ -42,15 +42,15 @@ macro_rules! function { $block } function!(@layout $($tts)*); - }; + ); - (@layout layout($this:pat, $ctx:pat) $block:block) => { + (@layout layout($this:pat, $ctx:pat) $block:block) => ( fn layout(&self, ctx: LayoutContext) -> LayoutResult { let $ctx = ctx; let $this = self; $block } - }; + ); } /// Parse the body of a function. @@ -65,56 +65,56 @@ macro_rules! parse { } }; - (optional: $body:expr, $ctx:expr) => { + (optional: $body:expr, $ctx:expr) => ( if let Some(body) = $body { Some($crate::syntax::parse(body, $ctx)?) } else { None } - }; + ); - (required: $body:expr, $ctx:expr) => { + (required: $body:expr, $ctx:expr) => ( if let Some(body) = $body { $crate::syntax::parse(body, $ctx)? } else { err!("expected body"); } - } + ) } /// Return a formatted parsing error. #[macro_export] macro_rules! err { - ($($tts:tt)*) => { - return Err($crate::syntax::ParseError::new(format!($($tts)*))); - }; + (@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*))); + ($($tts:tt)*) => (return Err(err!(@$($tts)*));); } /// Convenient interface for parsing function arguments. pub struct Arguments<'a> { - args: Peekable>, + args: Peekable>>, } impl<'a> Arguments<'a> { pub fn new(header: &'a FuncHeader) -> Arguments<'a> { Arguments { - args: header.args.iter().peekable() + args: header.args.positional.iter().peekable() } } - pub fn get_expr(&mut self) -> ParseResult<&'a Expression> { + pub fn get_expr(&mut self) -> ParseResult<&'a Spanned> { self.args.next() .ok_or_else(|| ParseError::new("expected expression")) } - pub fn get_ident(&mut self) -> ParseResult<&'a str> { - match self.get_expr()? { - Expression::Ident(s) => Ok(s.as_str()), - _ => Err(ParseError::new("expected identifier")), + pub fn get_ident(&mut self) -> ParseResult> { + let expr = self.get_expr()?; + match &expr.val { + Expression::Ident(s) => Ok(Spanned::new(s.as_str(), expr.span)), + _ => err!("expected identifier"), } } - pub fn get_ident_if_present(&mut self) -> ParseResult> { + pub fn get_ident_if_present(&mut self) -> ParseResult>> { if self.args.peek().is_some() { self.get_ident().map(|s| Some(s)) } else { diff --git a/src/func/mod.rs b/src/func/mod.rs index 30b5b8259..402f0111f 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -15,7 +15,7 @@ pub mod prelude { pub use crate::func::{Command, CommandList, Function}; pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout}; pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult}; - pub use crate::syntax::{Expression, FuncHeader, SyntaxTree}; + pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span}; pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult}; pub use crate::size::{Size, Size2D, SizeBox}; pub use crate::style::{PageStyle, TextStyle}; @@ -144,9 +144,9 @@ pub enum Command<'a> { } macro_rules! commands { - ($($x:expr),*$(,)*) => ({ + ($($x:expr),*$(,)*) => ( $crate::func::CommandList::from_vec(vec![$($x,)*]) - }); + ); } /// A map from identifiers to functions. diff --git a/src/layout/flex.rs b/src/layout/flex.rs index b97b42392..da1e1d59f 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -42,7 +42,7 @@ pub struct FlexContext { } macro_rules! reuse { - ($ctx:expr, $flex_spacing:expr) => { + ($ctx:expr, $flex_spacing:expr) => ( FlexContext { flex_spacing: $flex_spacing, alignment: $ctx.alignment, @@ -50,7 +50,7 @@ macro_rules! reuse { followup_spaces: $ctx.followup_spaces, shrink_to_fit: $ctx.shrink_to_fit, } - }; + ); } impl FlexContext { diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index 0097ce3e2..7521967b3 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -30,7 +30,7 @@ pub struct StackContext { } macro_rules! reuse { - ($ctx:expr, $flow:expr) => { + ($ctx:expr, $flow:expr) => ( StackContext { alignment: $ctx.alignment, space: $ctx.space, @@ -38,7 +38,7 @@ macro_rules! reuse { shrink_to_fit: $ctx.shrink_to_fit, flow: $flow } - }; + ); } impl StackContext { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index dc98bfa81..131570df5 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -104,7 +104,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { *space = space.usable_space(); } - let commands = func.body.layout(ctx)?; + let commands = func.body.val.layout(ctx)?; for command in commands { match command { diff --git a/src/library/structure.rs b/src/library/structure.rs index ae05a12be..977d7499b 100644 --- a/src/library/structure.rs +++ b/src/library/structure.rs @@ -38,7 +38,8 @@ function! { parse(args, body, ctx) { let body = parse!(optional: body, ctx); - let alignment = match args.get_ident()? { + let arg = args.get_ident()?; + let alignment = match arg.val { "left" => Alignment::Left, "right" => Alignment::Right, "center" => Alignment::Center, @@ -80,7 +81,7 @@ function! { let mut flow = Flow::Vertical; if let Some(ident) = args.get_ident_if_present()? { - flow = match ident { + flow = match ident.val { "vertical" => Flow::Vertical, "horizontal" => Flow::Horizontal, f => err!("invalid flow specifier: {}", f), @@ -105,7 +106,7 @@ function! { } macro_rules! spacefunc { - ($ident:ident, $name:expr, $var:ident => $command:expr) => { + ($ident:ident, $name:expr, $var:ident => $command:expr) => ( /// Adds whitespace. #[derive(Debug, PartialEq)] pub struct $ident(Spacing); @@ -116,9 +117,10 @@ macro_rules! spacefunc { parse(args, body, _ctx) { parse!(forbidden: body); - let spacing = match args.get_expr()? { - Expression::Size(s) => Spacing::Absolute(*s), - Expression::Number(f) => Spacing::Relative(*f as f32), + let arg = args.get_expr()?; + let spacing = match arg.val { + Expression::Size(s) => Spacing::Absolute(s), + Expression::Number(f) => Spacing::Relative(f as f32), _ => err!("invalid spacing, expected size or number"), }; @@ -134,7 +136,7 @@ macro_rules! spacefunc { Ok(commands![$command]) } } - }; + ); } /// Absolute or font-relative spacing. diff --git a/src/library/style.rs b/src/library/style.rs index 397375a48..48f4d4db2 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -2,7 +2,7 @@ use crate::func::prelude::*; use toddle::query::FontClass; macro_rules! stylefunc { - ($ident:ident) => { + ($ident:ident) => ( /// Styles text. #[derive(Debug, PartialEq)] pub struct $ident { @@ -31,7 +31,7 @@ macro_rules! stylefunc { }) } } - }; + ); } stylefunc!(Italic); diff --git a/src/macros.rs b/src/macros.rs index bbf404be2..9fba1d596 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -42,11 +42,11 @@ macro_rules! error_type { /// Create a `Debug` implementation from a `Display` implementation. macro_rules! debug_display { - ($type:ident) => { + ($type:ident) => ( impl std::fmt::Debug for $type { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } - }; + ); } diff --git a/src/size.rs b/src/size.rs index 250f27c7b..5c87ad370 100644 --- a/src/size.rs +++ b/src/size.rs @@ -252,7 +252,7 @@ impl Sum for Size { } macro_rules! impl_reflexive { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => ( impl $trait for Size { type Output = Size; @@ -270,11 +270,11 @@ macro_rules! impl_reflexive { $assign_trait::$assign_func(&mut self.points, other.points); } } - }; + ); } macro_rules! impl_num_back { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => ( impl $trait<$ty> for Size { type Output = Size; @@ -292,11 +292,11 @@ macro_rules! impl_num_back { $assign_trait::$assign_func(&mut self.points, other as f32); } } - }; + ); } macro_rules! impl_num_both { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => ( impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty); impl $trait for $ty { @@ -309,7 +309,7 @@ macro_rules! impl_num_both { } } } - }; + ); } impl_reflexive!(Add, add, AddAssign, add_assign); @@ -342,7 +342,7 @@ impl Neg for Size2D { } macro_rules! impl_reflexive2d { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => ( impl $trait for Size2D { type Output = Size2D; @@ -362,11 +362,11 @@ macro_rules! impl_reflexive2d { $assign_trait::$assign_func(&mut self.y, other.y); } } - }; + ); } macro_rules! impl_num_back2d { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => ( impl $trait<$ty> for Size2D { type Output = Size2D; @@ -386,11 +386,11 @@ macro_rules! impl_num_back2d { $assign_trait::$assign_func(&mut self.y, other as f32); } } - }; + ); } macro_rules! impl_num_both2d { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => ( impl_num_back2d!($trait, $func, $assign_trait, $assign_func, $ty); impl $trait for $ty { @@ -404,7 +404,7 @@ macro_rules! impl_num_both2d { } } } - }; + ); } impl_reflexive2d!(Add, add, AddAssign, add_assign); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index f508c6cc1..f9b24e686 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -27,13 +27,13 @@ pub enum Token<'s> { /// /// If a colon occurs outside of a function header, it will be tokenized as /// [Text](Token::Text), just like the other tokens annotated with - /// _Function header only_. + /// _Header only_. Colon, - /// An equals (`=`) sign assigning a function argument a value (Function header only). + /// An equals (`=`) sign assigning a function argument a value (Header only). Equals, - /// A comma (`,`) separating two function arguments (Function header only). + /// A comma (`,`) separating two function arguments (Header only). Comma, - /// Quoted text as a string value (Function header only). + /// Quoted text as a string value (Header only). Quoted(&'s str), /// An underscore, indicating text in italics (Body only). Underscore, @@ -47,9 +47,9 @@ pub enum Token<'s> { 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). + /// [BlockComment](Token::BlockComment) would be returned). StarSlash, - /// A unit of Plain text. + /// Any consecutive string which does not contain markup. Text(&'s str), } @@ -88,16 +88,32 @@ pub enum Node { /// A function invocation, consisting of header and a dynamically parsed body. #[derive(Debug)] pub struct FuncCall { - pub header: FuncHeader, - pub body: Box, + pub header: Spanned, + pub body: Spanned>, } /// Contains header information of a function invocation. #[derive(Debug, Clone, PartialEq)] pub struct FuncHeader { - pub name: String, - pub args: Vec, - pub kwargs: Vec<(String, Expression)>, + pub name: Spanned, + pub args: FuncArgs, +} + +/// The arguments passed to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncArgs { + pub positional: Vec>, + pub keyword: Vec, Spanned)>> +} + +impl FuncArgs { + /// Create an empty collection of arguments. + fn new() -> FuncArgs { + FuncArgs { + positional: vec![], + keyword: vec![], + } + } } /// An argument or return value. @@ -140,6 +156,14 @@ impl Spanned { pub fn new(val: T, span: Span) -> Spanned { Spanned { val, span } } + + pub fn value(&self) -> &T { + &self.val + } + + pub fn span_map(self, f: F) -> Spanned where F: FnOnce(T) -> U { + Spanned::new(f(self.val), self.span) + } } /// Describes a slice of source code. diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 70ce18591..a308cf834 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -99,7 +99,7 @@ impl<'s> Parser<'s> { let mut span = token.span; let header = self.parse_func_header()?; - let body = self.parse_func_body(&header)?; + let body = self.parse_func_body(&header.val)?; span.end = self.tokens.string_index(); @@ -110,51 +110,41 @@ impl<'s> Parser<'s> { } /// Parse a function header. - fn parse_func_header(&mut self) -> ParseResult { + fn parse_func_header(&mut self) -> ParseResult> { + let start = self.tokens.string_index() - 1; + self.skip_white(); - let name = match self.tokens.next().map(|token| token.val) { - Some(Token::Text(word)) => { + let name = match self.tokens.next() { + Some(Spanned { val: Token::Text(word), span }) => { if is_identifier(word) { - Ok(word.to_owned()) + Ok(Spanned::new(word.to_owned(), span)) } else { - Err(ParseError::new(format!("invalid identifier: '{}'", word))) + err!("invalid identifier: '{}'", word); } } - _ => Err(ParseError::new("expected identifier")), + _ => err!("expected identifier"), }?; - let mut header = FuncHeader { - name, - args: vec![], - kwargs: vec![], - }; - self.skip_white(); // Check for arguments - match self.tokens.next().map(|token| token.val) { - Some(Token::RightBracket) => {} - Some(Token::Colon) => { - let (args, kwargs) = self.parse_func_args()?; - header.args = args; - header.kwargs = kwargs; - } - _ => { - return Err(ParseError::new( - "expected function arguments or closing bracket", - )) - } - } + let args = match self.tokens.next().map(|token| token.val) { + Some(Token::RightBracket) => FuncArgs::new(), + Some(Token::Colon) => self.parse_func_args()?, + _ => err!("expected arguments or closing bracket"), + }; + + let end = self.tokens.string_index(); // Store the header information of the function invocation. - Ok(header) + Ok(Spanned::new(FuncHeader { name, args }, Span::new(start, end))) } /// Parse the arguments to a function. - fn parse_func_args(&mut self) -> ParseResult<(Vec, Vec<(String, Expression)>)> { - let mut args = Vec::new(); - let kwargs = Vec::new(); + fn parse_func_args(&mut self) -> ParseResult { + let mut positional = Vec::new(); + let keyword = Vec::new(); let mut comma = false; loop { @@ -162,7 +152,7 @@ impl<'s> Parser<'s> { match self.tokens.peek().map(|token| token.val) { Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => { - args.push(self.parse_expression()?); + positional.push(self.parse_expression()?); comma = true; } @@ -175,50 +165,53 @@ impl<'s> Parser<'s> { break; } - _ if comma => return Err(ParseError::new("expected comma or closing bracket")), - _ => return Err(ParseError::new("expected closing bracket")), + _ if comma => err!("expected comma or closing bracket"), + _ => err!("expected closing bracket"), } } - Ok((args, kwargs)) + Ok( FuncArgs { positional, keyword }) } /// Parse an expression. - fn parse_expression(&mut self) -> ParseResult { - Ok(match self.tokens.next().map(|token| token.val) { - Some(Token::Quoted(text)) => Expression::Str(text.to_owned()), - Some(Token::Text(text)) => { - if let Ok(b) = text.parse::() { - Expression::Bool(b) - } else if let Ok(num) = text.parse::() { - Expression::Number(num) - } else if let Ok(size) = text.parse::() { - Expression::Size(size) - } else { - Expression::Ident(text.to_owned()) + fn parse_expression(&mut self) -> ParseResult> { + if let Some(token) = self.tokens.next() { + Ok(Spanned::new(match token.val { + Token::Quoted(text) => Expression::Str(text.to_owned()), + Token::Text(text) => { + if let Ok(b) = text.parse::() { + Expression::Bool(b) + } else if let Ok(num) = text.parse::() { + Expression::Number(num) + } else if let Ok(size) = text.parse::() { + Expression::Size(size) + } else { + Expression::Ident(text.to_owned()) + } } - } - _ => return Err(ParseError::new("expected expression")), - }) + + _ => err!("expected expression"), + }, token.span)) + } else { + err!("expected expression"); + } } /// Parse the body of a function. - fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult> { - // Whether the function has a body. - let has_body = self.tokens.peek().map(|token| token.val) == Some(Token::LeftBracket); - if has_body { - self.advance(); - } - + fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult>> { // Now we want to parse this function dynamically. let parser = self .ctx .scope - .get_parser(&header.name) - .ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?; + .get_parser(&header.name.val) + .ok_or_else(|| err!(@"unknown function: '{}'", &header.name.val))?; + + let has_body = self.tokens.peek().map(|token| token.val) == Some(Token::LeftBracket); // Do the parsing dependent on whether the function has a body. Ok(if has_body { + self.advance(); + // Find out the string which makes the body of this function. let start = self.tokens.string_index(); let end = find_closing_bracket(&self.src[start..]) @@ -236,9 +229,10 @@ impl<'s> Parser<'s> { let token = self.tokens.next().expect("parse_func_body: expected token"); assert!(token.val == Token::RightBracket); - body + Spanned::new(body, Span::new(start - 1, end + 1)) } else { - parser(&header, None, self.ctx)? + let body = parser(&header, None, self.ctx)?; + Spanned::new(body, Span::new(0, 0)) }) } @@ -264,8 +258,8 @@ impl<'s> Parser<'s> { NewlineState::Zero => state = NewlineState::One(token.span), NewlineState::One(mut span) => { span.expand(token.span); - state = NewlineState::TwoOrMore; self.append(Node::Newline, span); + state = NewlineState::TwoOrMore; }, NewlineState::TwoOrMore => self.append_space(token.span), } @@ -273,7 +267,7 @@ impl<'s> Parser<'s> { _ => { if let NewlineState::One(span) = state { - self.append_space(span); + self.append_space(Span::new(span.start, token.span.start)); } state = NewlineState::Zero; @@ -369,7 +363,10 @@ impl<'s> PeekableTokens<'s> { } fn string_index(&mut self) -> usize { - self.tokens.string_index() + match self.peeked { + Some(Some(peeked)) => peeked.span.start, + _ => self.tokens.string_index(), + } } fn set_string_index(&mut self, index: usize) { @@ -428,6 +425,8 @@ error_type! { #[cfg(test)] mod tests { + #![allow(non_snake_case)] + use super::*; use crate::func::{CommandList, Function, Scope}; use crate::layout::{LayoutContext, LayoutResult}; @@ -451,7 +450,10 @@ mod tests { } impl PartialEq for TreeFn { - fn eq(&self, other: &TreeFn) -> bool { tree_equal(&self.0, &other.0) } + fn eq(&self, other: &TreeFn) -> bool { + assert_tree_equal(&self.0, &other.0); + true + } } /// A testing function without a body. @@ -470,8 +472,24 @@ mod tests { } } - fn tree_equal(a: &SyntaxTree, b: &SyntaxTree) -> bool { - a.nodes.iter().zip(&b.nodes).all(|(x, y)| x.val == y.val) + /// Asserts that two syntax trees are equal except for all spans inside them. + fn assert_tree_equal(a: &SyntaxTree, b: &SyntaxTree) { + for (x, y) in a.nodes.iter().zip(&b.nodes) { + let equal = match (x, y) { + (Spanned { val: F(x), .. }, Spanned { val: F(y), .. }) => { + x.header.val.name.val == y.header.val.name.val + && x.header.val.args.positional.iter().map(Spanned::value) + .eq(y.header.val.args.positional.iter().map(Spanned::value)) + && x.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val)) + .eq(y.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val))) + } + _ => x.val == y.val + }; + + if !equal { + panic!("assert_tree_equal: ({:?}) != ({:?})", x.val, y.val); + } + } } /// Test if the source code parses into the syntax tree. @@ -479,13 +497,13 @@ mod tests { let ctx = ParseContext { scope: &Scope::new(), }; - assert!(tree_equal(&parse(src, ctx).unwrap(), &tree)); + assert_tree_equal(&parse(src, ctx).unwrap(), &tree); } /// Test with a scope containing function definitions. fn test_scoped(scope: &Scope, src: &str, tree: SyntaxTree) { let ctx = ParseContext { scope }; - assert!(tree_equal(&parse(src, ctx).unwrap(), &tree)); + assert_tree_equal(&parse(src, ctx).unwrap(), &tree); } /// Test if the source parses into the error. @@ -503,18 +521,21 @@ mod tests { } /// Create a text node. - #[allow(non_snake_case)] fn T(s: &str) -> Node { Node::Text(s.to_owned()) } + fn zerospan(val: T) -> Spanned { + Spanned::new(val, Span::new(0, 0)) + } + /// Shortcut macro to create a syntax tree. Is `vec`-like and the elements /// are the nodes without spans. macro_rules! tree { ($($x:expr),*) => ({ #[allow(unused_mut)] let mut nodes = vec![]; $( - nodes.push(Spanned::new($x, Span::new(0, 0))); + nodes.push(zerospan($x)); )* SyntaxTree { nodes } }); @@ -523,22 +544,21 @@ mod tests { /// Shortcut macro to create a function. macro_rules! func { - (name => $name:expr, body => None $(,)*) => { - func!(@$name, Box::new(BodylessFn)) - }; - (name => $name:expr, body => $tree:expr $(,)*) => { - func!(@$name, Box::new(TreeFn($tree))) - }; - (@$name:expr, $body:expr) => { + (name => $name:expr) => ( + func!(@$name, Box::new(BodylessFn), FuncArgs::new()) + ); + (name => $name:expr, body => $tree:expr $(,)*) => ( + func!(@$name, Box::new(TreeFn($tree)), FuncArgs::new()) + ); + (@$name:expr, $body:expr, $args:expr) => ( FuncCall { - header: FuncHeader { - name: $name.to_string(), - args: vec![], - kwargs: vec![], - }, - body: $body, + header: zerospan(FuncHeader { + name: zerospan($name.to_string()), + args: $args, + }), + body: zerospan($body), } - } + ) } /// Parse the basic cases. @@ -573,8 +593,8 @@ mod tests { scope.add::("modifier"); scope.add::("func"); - test_scoped(&scope,"[test]", tree! [ F(func! { name => "test", body => None }) ]); - test_scoped(&scope,"[ test]", tree! [ F(func! { name => "test", body => None }) ]); + test_scoped(&scope,"[test]", tree! [ F(func! { name => "test" }) ]); + test_scoped(&scope,"[ test]", tree! [ F(func! { name => "test" }) ]); test_scoped(&scope, "This is an [modifier][example] of a function invocation.", tree! [ T("This"), S, T("is"), S, T("an"), S, F(func! { name => "modifier", body => tree! [ T("example") ] }), S, @@ -583,7 +603,7 @@ mod tests { test_scoped(&scope, "[func][Hello][modifier][Here][end]", tree! [ F(func! { name => "func", body => tree! [ T("Hello") ] }), F(func! { name => "modifier", body => tree! [ T("Here") ] }), - F(func! { name => "end", body => None }), + F(func! { name => "end" }), ]); test_scoped(&scope, "[func][]", tree! [ F(func! { name => "func", body => tree! [] }) ]); test_scoped(&scope, "[modifier][[func][call]] outside", tree! [ @@ -602,22 +622,15 @@ mod tests { fn parse_function_args() { use Expression::{Number as N, Size as Z, Bool as B}; - #[allow(non_snake_case)] fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) } - #[allow(non_snake_case)] fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) } - fn func(name: &str, args: Vec) -> SyntaxTree { - tree! [ - F(FuncCall { - header: FuncHeader { - name: name.to_string(), - args, - kwargs: vec![], - }, - body: Box::new(BodylessFn) - }) - ] + fn func(name: &str, positional: Vec) -> SyntaxTree { + let args = FuncArgs { + positional: positional.into_iter().map(zerospan).collect(), + keyword: vec![] + }; + tree! [ F(func!(@name, Box::new(BodylessFn), args)) ] } let mut scope = Scope::new(); @@ -646,9 +659,9 @@ mod tests { test_scoped(&scope, "Text\n// Comment\n More text", tree! [ T("Text"), S, T("More"), S, T("text") ]); test_scoped(&scope, "[test/*world*/]", - tree! [ F(func! { name => "test", body => None }) ]); + tree! [ F(func! { name => "test" }) ]); test_scoped(&scope, "[test/*]*/]", - tree! [ F(func! { name => "test", body => None }) ]); + tree! [ F(func! { name => "test" }) ]); } /// Test if escaped, but unbalanced parens are correctly parsed. @@ -687,10 +700,7 @@ mod tests { scope.add::("bold"); test_scoped(&scope, "[func] ⺐.", tree! [ - F(func! { - name => "func", - body => None, - }), + F(func! { name => "func" }), S, T("⺐.") ]); test_scoped(&scope, "[bold][Hello 🌍!]", tree! [ @@ -719,13 +729,23 @@ mod tests { let tree = parse("p1\n \np2"); assert_eq!(tree[1].span.pair(), (2, 5)); - let tree = parse("func [hello: pos, other][body _🌍_]"); + let tree = parse("p1\n p2"); + assert_eq!(tree[1].span.pair(), (2, 4)); + + let src = "func [hello: pos, other][body _🌍_]"; + let tree = parse(src); assert_eq!(tree[0].span.pair(), (0, 4)); assert_eq!(tree[1].span.pair(), (4, 5)); assert_eq!(tree[2].span.pair(), (5, 37)); let func = if let Node::Func(f) = &tree[2].val { f } else { panic!() }; - let body = &func.body.downcast::().unwrap().0.nodes; + assert_eq!(func.header.span.pair(), (5, 24)); + assert_eq!(func.header.val.name.span.pair(), (6, 11)); + assert_eq!(func.header.val.args.positional[0].span.pair(), (13, 16)); + assert_eq!(func.header.val.args.positional[1].span.pair(), (18, 23)); + + let body = &func.body.val.downcast::().unwrap().0.nodes; + assert_eq!(func.body.span.pair(), (24, 37)); assert_eq!(body[0].span.pair(), (0, 4)); assert_eq!(body[1].span.pair(), (4, 5)); assert_eq!(body[2].span.pair(), (5, 6)); @@ -742,7 +762,7 @@ mod tests { test_err("No functions here]", "unexpected closing bracket"); test_err_scoped(&scope, "[hello][world", "expected closing bracket"); - test_err("[hello world", "expected function arguments or closing bracket"); + test_err("[hello world", "expected arguments or closing bracket"); test_err("[ no-name][Why?]", "invalid identifier: 'no-name'"); test_err("Hello */", "unexpected end of block comment"); } diff --git a/tests/layouting.rs b/tests/layouting.rs index f77c6e850..a87f1fd9b 100644 --- a/tests/layouting.rs +++ b/tests/layouting.rs @@ -74,15 +74,23 @@ fn test(name: &str, src: &str) { }); } - let start = Instant::now(); + // Make run warm. + let warmup_start = Instant::now(); + typesetter.typeset(&src).unwrap(); + let warmup_end = Instant::now(); // Layout into box layout. + let start = Instant::now(); let tree = typesetter.parse(&src).unwrap(); + let mid = Instant::now(); let layouts = typesetter.layout(&tree).unwrap(); - let end = Instant::now(); - let duration = end - start; - println!(" => {:?}", duration); + + // Print measurements. + println!(" - cold start: {:?}", warmup_end - warmup_start); + println!(" - warmed up: {:?}", end - start); + println!(" - parsing: {:?}", mid - start); + println!(" - layouting: {:?}", end - mid); println!(); // Write the serialed layout file.