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)]
|
#[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);
|
||||||
|
@ -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).
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user