From 271af7ed0308c9eca7da5dce93d52d38be84889f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 6 Nov 2019 23:03:04 +0100 Subject: [PATCH] =?UTF-8?q?Parse=20keyword=20arguments=20=F0=9F=93=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/macros.rs | 7 ++ src/syntax/mod.rs | 45 +++++++++-- src/syntax/parsing.rs | 168 ++++++++++++++++++++++++++---------------- tests/layouting.rs | 25 ++++--- 4 files changed, 165 insertions(+), 80 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 9fba1d596..70c67105e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -49,4 +49,11 @@ macro_rules! debug_display { } } ); + ($type:ident; $generics:tt where $($bounds:tt)*) => ( + impl<$generics> std::fmt::Debug for $type<$generics> where $($bounds)* { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } + } + ); } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index f9b24e686..286bb2c77 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -116,8 +116,15 @@ impl FuncArgs { } } -/// An argument or return value. +/// One argument passed to a function. #[derive(Debug, Clone, PartialEq)] +pub enum FuncArg { + Positional(Spanned), + Keyword(Spanned<(Spanned, Spanned)>), +} + +/// An argument or return value. +#[derive(Clone, PartialEq)] pub enum Expression { Ident(String), Str(String), @@ -145,8 +152,10 @@ impl Display for Expression { } } +debug_display!(Expression); + /// Annotates a value with the part of the source code it corresponds to. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq)] pub struct Spanned { pub val: T, pub span: Span, @@ -157,8 +166,8 @@ impl Spanned { Spanned { val, span } } - pub fn value(&self) -> &T { - &self.val + pub fn value(self) -> T { + self.val } pub fn span_map(self, f: F) -> Spanned where F: FnOnce(T) -> U { @@ -166,8 +175,16 @@ impl Spanned { } } +impl Display for Spanned where T: std::fmt::Debug { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({:?}:{})", self.val, self.span) + } +} + +debug_display!(Spanned; T where T: std::fmt::Debug); + /// Describes a slice of source code. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq)] pub struct Span { pub start: usize, pub end: usize, @@ -178,6 +195,13 @@ impl Span { Span { start, end } } + pub fn merge(a: Span, b: Span) -> Span { + Span { + start: a.start.min(b.start), + end: a.end.max(b.end), + } + } + pub fn at(index: usize) -> Span { Span { start: index, end: index + 1 } } @@ -187,7 +211,14 @@ impl Span { } pub fn expand(&mut self, other: Span) { - self.start = self.start.min(other.start); - self.end = self.end.max(other.end); + *self = Span::merge(*self, other) } } + +impl Display for Span { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}, {}]", self.start, self.end) + } +} + +debug_display!(Span); diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index a308cf834..f952f5e6a 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -129,7 +129,7 @@ impl<'s> Parser<'s> { self.skip_white(); // Check for arguments - let args = match self.tokens.next().map(|token| token.val) { + let args = match self.tokens.next().map(Spanned::value) { Some(Token::RightBracket) => FuncArgs::new(), Some(Token::Colon) => self.parse_func_args()?, _ => err!("expected arguments or closing bracket"), @@ -144,57 +144,83 @@ impl<'s> Parser<'s> { /// Parse the arguments to a function. fn parse_func_args(&mut self) -> ParseResult { let mut positional = Vec::new(); - let keyword = Vec::new(); + let mut keyword = Vec::new(); - let mut comma = false; loop { self.skip_white(); - match self.tokens.peek().map(|token| token.val) { - Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => { - positional.push(self.parse_expression()?); - comma = true; - } + match self.parse_func_arg()? { + Some(FuncArg::Positional(arg)) => positional.push(arg), + Some(FuncArg::Keyword(arg)) => keyword.push(arg), + _ => {}, + } - Some(Token::Comma) if comma => { - self.advance(); - comma = false - } - Some(Token::RightBracket) => { - self.advance(); - break; - } - - _ if comma => err!("expected comma or closing bracket"), - _ => err!("expected closing bracket"), + match self.tokens.next().map(Spanned::value) { + Some(Token::Comma) => {}, + Some(Token::RightBracket) => break, + _ => err!("expected comma or closing bracket"), } } - Ok( FuncArgs { positional, keyword }) + Ok(FuncArgs { positional, keyword }) + } + + /// Parse one argument to a function. + fn parse_func_arg(&mut self) -> ParseResult> { + let token = match self.tokens.peek() { + Some(token) => token, + None => return Ok(None), + }; + + Ok(match token.val { + Token::Text(name) => { + self.advance(); + self.skip_white(); + + Some(match self.tokens.peek().map(Spanned::value) { + Some(Token::Equals) => { + self.advance(); + self.skip_white(); + + let name = token.span_map(|_| name.to_string()); + let next = self.tokens.next().ok_or_else(|| err!(@"expected value"))?; + let val = Self::parse_expression(next)?; + let span = Span::merge(name.span, val.span); + + FuncArg::Keyword(Spanned::new((name, val), span)) + } + + _ => FuncArg::Positional(Self::parse_expression(token)?), + }) + } + + Token::Quoted(_) => { + self.advance(); + Some(FuncArg::Positional(Self::parse_expression(token)?)) + } + + _ => None, + }) } /// Parse an expression. - 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()) - } + fn parse_expression(token: Spanned) -> ParseResult> { + 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()) } + } - _ => err!("expected expression"), - }, token.span)) - } else { - err!("expected expression"); - } + _ => err!("expected expression"), + }, token.span)) } /// Parse the body of a function. @@ -206,7 +232,7 @@ impl<'s> Parser<'s> { .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); + let has_body = self.tokens.peek().map(Spanned::value) == Some(Token::LeftBracket); // Do the parsing dependent on whether the function has a body. Ok(if has_body { @@ -256,9 +282,8 @@ impl<'s> Parser<'s> { self.advance(); match state { NewlineState::Zero => state = NewlineState::One(token.span), - NewlineState::One(mut span) => { - span.expand(token.span); - self.append(Node::Newline, span); + NewlineState::One(span) => { + self.append(Node::Newline, Span::merge(span, token.span)); state = NewlineState::TwoOrMore; }, NewlineState::TwoOrMore => self.append_space(token.span), @@ -472,22 +497,31 @@ mod tests { } } + mod args { + use super::Expression; + pub use Expression::{Number as N, Size as Z, Bool as B}; + + pub fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) } + pub fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) } + } + /// 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.positional.iter().map(|span| &span.val) + .eq(y.header.val.args.positional.iter().map(|span| &span.val)) && 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.body.val == &y.body.val } _ => x.val == y.val }; if !equal { - panic!("assert_tree_equal: ({:?}) != ({:?})", x.val, y.val); + panic!("assert_tree_equal: ({:#?}) != ({:#?})", x.val, y.val); } } } @@ -620,32 +654,42 @@ mod tests { #[test] #[rustfmt::skip] fn parse_function_args() { - use Expression::{Number as N, Size as Z, Bool as B}; + use args::*; - fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) } - fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) } - - fn func(name: &str, positional: Vec) -> SyntaxTree { + fn func( + positional: Vec, + keyword: Vec<(&str, Expression)>, + ) -> SyntaxTree { let args = FuncArgs { positional: positional.into_iter().map(zerospan).collect(), - keyword: vec![] + keyword: keyword.into_iter() + .map(|(s, e)| zerospan((zerospan(s.to_string()), zerospan(e)))) + .collect() }; - tree! [ F(func!(@name, Box::new(BodylessFn), args)) ] + tree! [ F(func!(@"align", Box::new(BodylessFn), args)) ] } let mut scope = Scope::new(); scope.add::("align"); - test_scoped(&scope, "[align: left]", func("align", vec![I("left")])); - test_scoped(&scope, "[align: left,right]", func("align", vec![I("left"), I("right")])); - test_scoped(&scope, "[align: left, right]", func("align", vec![I("left"), I("right")])); - test_scoped(&scope, "[align: \"hello\"]", func("align", vec![S("hello")])); - test_scoped(&scope, r#"[align: "hello\"world"]"#, func("align", vec![S(r#"hello\"world"#)])); - test_scoped(&scope, "[align: 12]", func("align", vec![N(12.0)])); - test_scoped(&scope, "[align: 17.53pt]", func("align", vec![Z(Size::pt(17.53))])); - test_scoped(&scope, "[align: 2.4in]", func("align", vec![Z(Size::inches(2.4))])); + test_scoped(&scope, "[align: left]", func(vec![I("left")], vec![])); + test_scoped(&scope, "[align: left,right]", func(vec![I("left"), I("right")], vec![])); + test_scoped(&scope, "[align: left, right]", func(vec![I("left"), I("right")], vec![])); + test_scoped(&scope, "[align: \"hello\"]", func(vec![S("hello")], vec![])); + test_scoped(&scope, r#"[align: "hello\"world"]"#, func(vec![S(r#"hello\"world"#)], vec![])); + test_scoped(&scope, "[align: 12]", func(vec![N(12.0)], vec![])); + test_scoped(&scope, "[align: 17.53pt]", func(vec![Z(Size::pt(17.53))], vec![])); + test_scoped(&scope, "[align: 2.4in]", func(vec![Z(Size::inches(2.4))], vec![])); test_scoped(&scope, "[align: true, 10mm, left, \"hi, there\"]", - func("align", vec![B(true), Z(Size::mm(10.0)), I("left"), S("hi, there")])); + func(vec![B(true), Z(Size::mm(10.0)), I("left"), S("hi, there")], vec![])); + + test_scoped(&scope, "[align: right=true]", func(vec![], vec![("right", B(true))])); + test_scoped(&scope, "[align: flow = horizontal]", + func(vec![], vec![("flow", I("horizontal"))])); + test_scoped(&scope, "[align: x=1cm, y=20mm]", + func(vec![], vec![("x", Z(Size::cm(1.0))), ("y", Z(Size::mm(20.0)))])); + test_scoped(&scope, "[align: x=5.14,a, \"b\", c=me,d=you]", + func(vec![I("a"), S("b")], vec![("x", N(5.14)), ("c", I("me")), ("d", I("you"))])); } /// Parse comments (line and block). diff --git a/tests/layouting.rs b/tests/layouting.rs index a87f1fd9b..9a41b7907 100644 --- a/tests/layouting.rs +++ b/tests/layouting.rs @@ -1,6 +1,7 @@ use std::fs::{self, File}; use std::io::{BufWriter, Read, Write}; use std::process::Command; +#[cfg(not(debug_assertions))] use std::time::Instant; use regex::{Regex, Captures}; @@ -59,7 +60,7 @@ fn main() { /// Create a _PDF_ with a name from the source code. fn test(name: &str, src: &str) { - println!("Testing: {}", name); + println!("Testing: {}.", name); let (src, size) = preprocess(src); @@ -75,23 +76,25 @@ fn test(name: &str, src: &str) { } // Make run warm. - let warmup_start = Instant::now(); + #[cfg(not(debug_assertions))] let warmup_start = Instant::now(); typesetter.typeset(&src).unwrap(); - let warmup_end = Instant::now(); + #[cfg(not(debug_assertions))] let warmup_end = Instant::now(); // Layout into box layout. - let start = Instant::now(); + #[cfg(not(debug_assertions))] let start = Instant::now(); let tree = typesetter.parse(&src).unwrap(); - let mid = Instant::now(); + #[cfg(not(debug_assertions))] let mid = Instant::now(); let layouts = typesetter.layout(&tree).unwrap(); - let end = Instant::now(); + #[cfg(not(debug_assertions))] let end = Instant::now(); // Print measurements. - println!(" - cold start: {:?}", warmup_end - warmup_start); - println!(" - warmed up: {:?}", end - start); - println!(" - parsing: {:?}", mid - start); - println!(" - layouting: {:?}", end - mid); - println!(); + #[cfg(not(debug_assertions))] { + 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. let path = format!("{}/serialized/{}.lay", CACHE_DIR, name);