mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Parse keyword arguments 📋
This commit is contained in:
parent
110e4b9cb9
commit
271af7ed03
@ -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)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -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<Expression>),
|
||||
Keyword(Spanned<(Spanned<String>, Spanned<Expression>)>),
|
||||
}
|
||||
|
||||
/// 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<T> {
|
||||
pub val: T,
|
||||
pub span: Span,
|
||||
@ -157,8 +166,8 @@ impl<T> Spanned<T> {
|
||||
Spanned { val, span }
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &T {
|
||||
&self.val
|
||||
pub fn value(self) -> T {
|
||||
self.val
|
||||
}
|
||||
|
||||
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.
|
||||
#[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);
|
||||
|
@ -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,38 +144,67 @@ impl<'s> Parser<'s> {
|
||||
/// Parse the arguments to a function.
|
||||
fn parse_func_args(&mut self) -> ParseResult<FuncArgs> {
|
||||
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 })
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn parse_expression(&mut self) -> ParseResult<Spanned<Expression>> {
|
||||
if let Some(token) = self.tokens.next() {
|
||||
fn parse_expression(token: Spanned<Token>) -> ParseResult<Spanned<Expression>> {
|
||||
Ok(Spanned::new(match token.val {
|
||||
Token::Quoted(text) => Expression::Str(text.to_owned()),
|
||||
Token::Text(text) => {
|
||||
@ -192,9 +221,6 @@ impl<'s> Parser<'s> {
|
||||
|
||||
_ => err!("expected expression"),
|
||||
}, token.span))
|
||||
} else {
|
||||
err!("expected expression");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Expression>) -> SyntaxTree {
|
||||
fn func(
|
||||
positional: Vec<Expression>,
|
||||
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::<BodylessFn>("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).
|
||||
|
@ -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.
|
||||
#[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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user