Parse keyword arguments 📋

This commit is contained in:
Laurenz 2019-11-06 23:03:04 +01:00
parent 110e4b9cb9
commit 271af7ed03
4 changed files with 165 additions and 80 deletions

View File

@ -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)
}
}
);
} }

View File

@ -116,8 +116,15 @@ impl FuncArgs {
} }
} }
/// An argument or return value. /// One argument passed to a function.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum FuncArg {
Positional(Spanned<Expression>),
Keyword(Spanned<(Spanned<String>, Spanned<Expression>)>),
}
/// An argument or return value.
#[derive(Clone, PartialEq)]
pub enum Expression { pub enum Expression {
Ident(String), Ident(String),
Str(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. /// 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<T> { pub struct Spanned<T> {
pub val: T, pub val: T,
pub span: Span, pub span: Span,
@ -157,8 +166,8 @@ impl<T> Spanned<T> {
Spanned { val, span } Spanned { val, span }
} }
pub fn value(&self) -> &T { pub fn value(self) -> T {
&self.val self.val
} }
pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U { pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
@ -166,8 +175,16 @@ impl<T> Spanned<T> {
} }
} }
impl<T> Display for Spanned<T> 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. /// Describes a slice of source code.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
pub struct Span { pub struct Span {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
@ -178,6 +195,13 @@ impl Span {
Span { start, end } 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 { pub fn at(index: usize) -> Span {
Span { start: index, end: index + 1 } Span { start: index, end: index + 1 }
} }
@ -187,7 +211,14 @@ impl Span {
} }
pub fn expand(&mut self, other: Span) { pub fn expand(&mut self, other: Span) {
self.start = self.start.min(other.start); *self = Span::merge(*self, other)
self.end = self.end.max(other.end);
} }
} }
impl Display for Span {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[{}, {}]", self.start, self.end)
}
}
debug_display!(Span);

View File

@ -129,7 +129,7 @@ impl<'s> Parser<'s> {
self.skip_white(); self.skip_white();
// Check for arguments // 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::RightBracket) => FuncArgs::new(),
Some(Token::Colon) => self.parse_func_args()?, Some(Token::Colon) => self.parse_func_args()?,
_ => err!("expected arguments or closing bracket"), _ => err!("expected arguments or closing bracket"),
@ -144,57 +144,83 @@ impl<'s> Parser<'s> {
/// Parse the arguments to a function. /// Parse the arguments to a function.
fn parse_func_args(&mut self) -> ParseResult<FuncArgs> { fn parse_func_args(&mut self) -> ParseResult<FuncArgs> {
let mut positional = Vec::new(); let mut positional = Vec::new();
let keyword = Vec::new(); let mut keyword = Vec::new();
let mut comma = false;
loop { loop {
self.skip_white(); self.skip_white();
match self.tokens.peek().map(|token| token.val) { match self.parse_func_arg()? {
Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => { Some(FuncArg::Positional(arg)) => positional.push(arg),
positional.push(self.parse_expression()?); Some(FuncArg::Keyword(arg)) => keyword.push(arg),
comma = true; _ => {},
} }
Some(Token::Comma) if comma => { match self.tokens.next().map(Spanned::value) {
self.advance(); Some(Token::Comma) => {},
comma = false Some(Token::RightBracket) => break,
} _ => err!("expected comma or closing bracket"),
Some(Token::RightBracket) => {
self.advance();
break;
}
_ if comma => err!("expected comma or closing bracket"),
_ => err!("expected closing bracket"),
} }
} }
Ok( FuncArgs { positional, keyword }) Ok(FuncArgs { positional, keyword })
}
/// Parse one argument to a function.
fn parse_func_arg(&mut self) -> ParseResult<Option<FuncArg>> {
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. /// Parse an expression.
fn parse_expression(&mut self) -> ParseResult<Spanned<Expression>> { fn parse_expression(token: Spanned<Token>) -> ParseResult<Spanned<Expression>> {
if let Some(token) = self.tokens.next() { Ok(Spanned::new(match token.val {
Ok(Spanned::new(match token.val { Token::Quoted(text) => Expression::Str(text.to_owned()),
Token::Quoted(text) => Expression::Str(text.to_owned()), Token::Text(text) => {
Token::Text(text) => { if let Ok(b) = text.parse::<bool>() {
if let Ok(b) = text.parse::<bool>() { Expression::Bool(b)
Expression::Bool(b) } else if let Ok(num) = text.parse::<f64>() {
} else if let Ok(num) = text.parse::<f64>() { Expression::Number(num)
Expression::Number(num) } else if let Ok(size) = text.parse::<Size>() {
} else if let Ok(size) = text.parse::<Size>() { Expression::Size(size)
Expression::Size(size) } else {
} else { Expression::Ident(text.to_owned())
Expression::Ident(text.to_owned())
}
} }
}
_ => err!("expected expression"), _ => err!("expected expression"),
}, token.span)) }, token.span))
} else {
err!("expected expression");
}
} }
/// Parse the body of a function. /// Parse the body of a function.
@ -206,7 +232,7 @@ impl<'s> Parser<'s> {
.get_parser(&header.name.val) .get_parser(&header.name.val)
.ok_or_else(|| err!(@"unknown function: '{}'", &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. // Do the parsing dependent on whether the function has a body.
Ok(if has_body { Ok(if has_body {
@ -256,9 +282,8 @@ impl<'s> Parser<'s> {
self.advance(); self.advance();
match state { match state {
NewlineState::Zero => state = NewlineState::One(token.span), NewlineState::Zero => state = NewlineState::One(token.span),
NewlineState::One(mut span) => { NewlineState::One(span) => {
span.expand(token.span); self.append(Node::Newline, Span::merge(span, token.span));
self.append(Node::Newline, span);
state = NewlineState::TwoOrMore; state = NewlineState::TwoOrMore;
}, },
NewlineState::TwoOrMore => self.append_space(token.span), 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. /// Asserts that two syntax trees are equal except for all spans inside them.
fn assert_tree_equal(a: &SyntaxTree, b: &SyntaxTree) { fn assert_tree_equal(a: &SyntaxTree, b: &SyntaxTree) {
for (x, y) in a.nodes.iter().zip(&b.nodes) { for (x, y) in a.nodes.iter().zip(&b.nodes) {
let equal = match (x, y) { let equal = match (x, y) {
(Spanned { val: F(x), .. }, Spanned { val: F(y), .. }) => { (Spanned { val: F(x), .. }, Spanned { val: F(y), .. }) => {
x.header.val.name.val == y.header.val.name.val x.header.val.name.val == y.header.val.name.val
&& x.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(Spanned::value)) .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)) && 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))) .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 _ => x.val == y.val
}; };
if !equal { 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] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn parse_function_args() { 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 func(
fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) } positional: Vec<Expression>,
keyword: Vec<(&str, Expression)>,
fn func(name: &str, positional: Vec<Expression>) -> SyntaxTree { ) -> SyntaxTree {
let args = FuncArgs { let args = FuncArgs {
positional: positional.into_iter().map(zerospan).collect(), 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(); let mut scope = Scope::new();
scope.add::<BodylessFn>("align"); scope.add::<BodylessFn>("align");
test_scoped(&scope, "[align: left]", func("align", vec![I("left")])); test_scoped(&scope, "[align: left]", func(vec![I("left")], vec![]));
test_scoped(&scope, "[align: left,right]", func("align", vec![I("left"), I("right")])); test_scoped(&scope, "[align: left,right]", func(vec![I("left"), I("right")], vec![]));
test_scoped(&scope, "[align: left, right]", func("align", vec![I("left"), I("right")])); test_scoped(&scope, "[align: left, right]", func(vec![I("left"), I("right")], vec![]));
test_scoped(&scope, "[align: \"hello\"]", func("align", vec![S("hello")])); test_scoped(&scope, "[align: \"hello\"]", func(vec![S("hello")], vec![]));
test_scoped(&scope, r#"[align: "hello\"world"]"#, func("align", vec![S(r#"hello\"world"#)])); test_scoped(&scope, r#"[align: "hello\"world"]"#, func(vec![S(r#"hello\"world"#)], vec![]));
test_scoped(&scope, "[align: 12]", func("align", vec![N(12.0)])); test_scoped(&scope, "[align: 12]", func(vec![N(12.0)], vec![]));
test_scoped(&scope, "[align: 17.53pt]", func("align", vec![Z(Size::pt(17.53))])); test_scoped(&scope, "[align: 17.53pt]", func(vec![Z(Size::pt(17.53))], vec![]));
test_scoped(&scope, "[align: 2.4in]", func("align", vec![Z(Size::inches(2.4))])); test_scoped(&scope, "[align: 2.4in]", func(vec![Z(Size::inches(2.4))], vec![]));
test_scoped(&scope, "[align: true, 10mm, left, \"hi, there\"]", 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). /// Parse comments (line and block).

View File

@ -1,6 +1,7 @@
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{BufWriter, Read, Write}; use std::io::{BufWriter, Read, Write};
use std::process::Command; use std::process::Command;
#[cfg(not(debug_assertions))]
use std::time::Instant; use std::time::Instant;
use regex::{Regex, Captures}; use regex::{Regex, Captures};
@ -59,7 +60,7 @@ fn main() {
/// Create a _PDF_ with a name from the source code. /// Create a _PDF_ with a name from the source code.
fn test(name: &str, src: &str) { fn test(name: &str, src: &str) {
println!("Testing: {}", name); println!("Testing: {}.", name);
let (src, size) = preprocess(src); let (src, size) = preprocess(src);
@ -75,23 +76,25 @@ fn test(name: &str, src: &str) {
} }
// Make run warm. // Make run warm.
let warmup_start = Instant::now(); #[cfg(not(debug_assertions))] let warmup_start = Instant::now();
typesetter.typeset(&src).unwrap(); typesetter.typeset(&src).unwrap();
let warmup_end = Instant::now(); #[cfg(not(debug_assertions))] let warmup_end = Instant::now();
// Layout into box layout. // Layout into box layout.
let start = Instant::now(); #[cfg(not(debug_assertions))] let start = Instant::now();
let tree = typesetter.parse(&src).unwrap(); 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 layouts = typesetter.layout(&tree).unwrap();
let end = Instant::now(); #[cfg(not(debug_assertions))] let end = Instant::now();
// Print measurements. // Print measurements.
println!(" - cold start: {:?}", warmup_end - warmup_start); #[cfg(not(debug_assertions))] {
println!(" - warmed up: {:?}", end - start); println!(" - cold start: {:?}", warmup_end - warmup_start);
println!(" - parsing: {:?}", mid - start); println!(" - warmed up: {:?}", end - start);
println!(" - layouting: {:?}", end - mid); println!(" - parsing: {:?}", mid - start);
println!(); println!(" - layouting: {:?}", end - mid);
println!();
}
// Write the serialed layout file. // Write the serialed layout file.
let path = format!("{}/serialized/{}.lay", CACHE_DIR, name); let path = format!("{}/serialized/{}.lay", CACHE_DIR, name);