Parse tuples and objects 🍒

Generalizes the parsing of tuples, objects and function arguments into generic comma-separated collections.
This commit is contained in:
Laurenz 2020-02-11 21:30:39 +01:00
parent 5badb4e8ff
commit 60099aed50
6 changed files with 542 additions and 294 deletions

View File

@ -1,6 +1,7 @@
//! Expressions in function headers. //! Expressions in function headers.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Write, Debug, Formatter};
use std::iter::FromIterator;
use crate::error::Errors; use crate::error::Errors;
use crate::size::Size; use crate::size::Size;
@ -90,7 +91,9 @@ impl Ident {
impl Debug for Ident { impl Debug for Ident {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0) f.write_char('`')?;
f.write_str(&self.0)?;
f.write_char('`')
} }
} }
@ -143,15 +146,42 @@ impl Tuple {
} }
}) })
} }
/// Iterate over the items of this tuple.
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Expr>> {
self.items.iter()
}
}
impl IntoIterator for Tuple {
type Item = Spanned<Expr>;
type IntoIter = std::vec::IntoIter<Spanned<Expr>>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
impl<'a> IntoIterator for &'a Tuple {
type Item = &'a Spanned<Expr>;
type IntoIter = std::slice::Iter<'a, Spanned<Expr>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl FromIterator<Spanned<Expr>> for Tuple {
fn from_iter<I: IntoIterator<Item=Spanned<Expr>>>(iter: I) -> Self {
Tuple { items: iter.into_iter().collect() }
}
} }
impl Debug for Tuple { impl Debug for Tuple {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut tuple = f.debug_tuple(""); f.debug_list()
for item in &self.items { .entries(&self.items)
tuple.field(item); .finish()
}
tuple.finish()
} }
} }
@ -276,6 +306,35 @@ impl Object {
Err(err) => { errors.push(Spanned { v: err, span }); None } Err(err) => { errors.push(Spanned { v: err, span }); None }
} }
} }
/// Iterate over the pairs of this object.
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Pair> {
self.pairs.iter()
}
}
impl IntoIterator for Object {
type Item = Pair;
type IntoIter = std::vec::IntoIter<Pair>;
fn into_iter(self) -> Self::IntoIter {
self.pairs.into_iter()
}
}
impl<'a> IntoIterator for &'a Object {
type Item = &'a Pair;
type IntoIter = std::slice::Iter<'a, Pair>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl FromIterator<Pair> for Object {
fn from_iter<I: IntoIterator<Item=Pair>>(iter: I) -> Self {
Object { pairs: iter.into_iter().collect() }
}
} }
impl Debug for Object { impl Debug for Object {

View File

@ -1,5 +1,6 @@
//! Primitives for argument parsing in library functions. //! Primitives for argument parsing in library functions.
use std::iter::FromIterator;
use crate::error::{Error, Errors}; use crate::error::{Error, Errors};
use super::expr::{Expr, Ident, Tuple, Object, Pair}; use super::expr::{Expr, Ident, Tuple, Object, Pair};
use super::span::{Span, Spanned}; use super::span::{Span, Spanned};
@ -55,6 +56,16 @@ impl FuncArgs {
} }
} }
impl FromIterator<FuncArg> for FuncArgs {
fn from_iter<I: IntoIterator<Item=FuncArg>>(iter: I) -> Self {
let mut args = FuncArgs::new();
for item in iter.into_iter() {
args.add(item);
}
args
}
}
/// Either a positional or keyword argument. /// Either a positional or keyword argument.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum FuncArg { pub enum FuncArg {

View File

@ -107,19 +107,23 @@ pub enum Decoration {
/// ^^^^^^ /// ^^^^^^
/// ``` /// ```
InvalidFuncName, InvalidFuncName,
/// A key of a keyword argument:
/// The key of a keyword argument:
/// ```typst /// ```typst
/// [box: width=5cm] /// [box: width=5cm]
/// ^^^^^ /// ^^^^^
/// ``` /// ```
ArgumentKey, ArgumentKey,
/// A key in an object.
/// Italic. /// ```typst
/// [box: padding={ left: 1cm, right: 2cm}]
/// ^^^^ ^^^^^
/// ```
ObjectKey,
/// An italic word.
Italic, Italic,
/// Bold. /// A bold word.
Bold, Bold,
/// Monospace. /// A monospace word.
Monospace, Monospace,
} }

View File

@ -1,5 +1,7 @@
//! Parsing of source code into syntax models. //! Parsing of source code into syntax models.
use std::iter::FromIterator;
use crate::{Pass, Feedback}; use crate::{Pass, Feedback};
use super::func::{FuncHeader, FuncArgs, FuncArg}; use super::func::{FuncHeader, FuncArgs, FuncArg};
use super::expr::*; use super::expr::*;
@ -145,11 +147,10 @@ impl<'s> FuncParser<'s> {
let start = self.pos(); let start = self.pos();
self.skip_whitespace(); self.skip_whitespace();
let name = match self.eat() { let name = match self.parse_ident() {
Some(Spanned { v: Token::ExprIdent(ident), span }) => { Some(ident) => ident,
Spanned { v: Ident(ident.to_string()), span } None => {
} let other = self.eat();
other => {
self.expected_found_or_at("identifier", other, start); self.expected_found_or_at("identifier", other, start);
return None; return None;
} }
@ -168,88 +169,62 @@ impl<'s> FuncParser<'s> {
Some(FuncHeader { name, args }) Some(FuncHeader { name, args })
} }
/// Parse the function arguments after a colon. /// Parse the argument list between colons and end of the header.
fn parse_func_args(&mut self) -> FuncArgs { fn parse_func_args(&mut self) -> FuncArgs {
let mut args = FuncArgs::new(); // Parse a collection until the token is `None`, that is, the end of the
// header.
self.parse_collection(None, |p| {
// If we have an identifier we might have a keyword argument,
// otherwise its for sure a postional argument.
if let Some(ident) = p.parse_ident() {
p.skip_whitespace();
self.skip_whitespace(); if let Some(Token::Equals) = p.peekv() {
while self.peek().is_some() { p.eat();
match self.parse_arg() { p.skip_whitespace();
Some(arg) => args.add(arg),
None => {}
}
self.skip_whitespace(); // Semantic highlighting for argument keys.
} p.feedback.decos.push(
Spanned::new(Decoration::ArgumentKey, ident.span));
args let value = p.parse_expr().ok_or(("value", None))?;
}
/// Parse a positional or keyword argument. // Add a keyword argument.
fn parse_arg(&mut self) -> Option<FuncArg> { Ok(FuncArg::Key(Pair { key: ident, value }))
let first = self.peek()?;
let span = first.span;
let arg = if let Token::ExprIdent(ident) = first.v {
self.eat();
self.skip_whitespace();
let ident = Ident(ident.to_string());
if let Some(Token::Equals) = self.peekv() {
self.eat();
self.skip_whitespace();
self.feedback.decos.push(Spanned::new(Decoration::ArgumentKey, span));
self.parse_expr().map(|value| {
FuncArg::Key(Pair {
key: Spanned { v: ident, span },
value,
})
})
} else { } else {
Some(FuncArg::Pos(Spanned::new(Expr::Ident(ident), span))) // Add a positional argument because there was no equals
// sign after the identifier that could have been a key.
Ok(FuncArg::Pos(ident.map(|id| Expr::Ident(id))))
} }
} else { } else {
self.parse_expr().map(|expr| FuncArg::Pos(expr)) // Add a positional argument because we haven't got an
}; // identifier that could be an argument key.
p.parse_expr().map(|expr| FuncArg::Pos(expr))
if let Some(arg) = &arg { .ok_or(("argument", None))
self.skip_whitespace();
match self.peekv() {
Some(Token::Comma) => { self.eat(); }
Some(_) => self.expected_at("comma", arg.span().end),
_ => {}
} }
} else { }).v
let found = self.eat();
self.expected_found_or_at("value", found, self.pos());
}
arg
} }
/// Parse an atomic or compound (tuple / object) expression. /// Parse an atomic or compound (tuple / object) expression.
fn parse_expr(&mut self) -> Option<Spanned<Expr>> { fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
let first = self.peek()?; let first = self.peek()?;
let spanned = |v| Spanned { v, span: first.span }; macro_rules! take {
($v:expr) => ({ self.eat(); Spanned { v: $v, span: first.span } });
}
Some(match first.v { Some(match first.v {
Token::ExprIdent(i) => { Token::ExprIdent(i) => take!((Expr::Ident(Ident(i.to_string())))),
self.eat();
spanned(Expr::Ident(Ident(i.to_string())))
}
Token::ExprStr { string, terminated } => { Token::ExprStr { string, terminated } => {
if !terminated { if !terminated {
self.expected_at("quote", first.span.end); self.expected_at("quote", first.span.end);
} }
self.eat(); take!(Expr::Str(unescape(string)))
spanned(Expr::Str(unescape(string)))
} }
Token::ExprNumber(n) => { self.eat(); spanned(Expr::Number(n)) }
Token::ExprSize(s) => { self.eat(); spanned(Expr::Size(s)) } Token::ExprNumber(n) => take!(Expr::Number(n)),
Token::ExprBool(b) => { self.eat(); spanned(Expr::Bool(b)) } Token::ExprSize(s) => take!(Expr::Size(s)),
Token::ExprBool(b) => take!(Expr::Bool(b)),
Token::LeftParen => self.parse_tuple(), Token::LeftParen => self.parse_tuple(),
Token::LeftBrace => self.parse_object(), Token::LeftBrace => self.parse_object(),
@ -258,30 +233,126 @@ impl<'s> FuncParser<'s> {
}) })
} }
/// Parse a tuple expression. /// Parse a tuple expression: `(<expr>, ...)`.
fn parse_tuple(&mut self) -> Spanned<Expr> { fn parse_tuple(&mut self) -> Spanned<Expr> {
let start = self.pos(); let token = self.eat();
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
// TODO: Do the thing. // Parse a collection until a right paren appears and complain about
self.eat_until(|t| t == Token::RightParen, true); // missing a `value` when an invalid token is encoutered.
self.parse_collection(Some(Token::RightParen),
let end = self.pos(); |p| p.parse_expr().ok_or(("value", None)))
let span = Span { start, end }; .map(|tuple| Expr::Tuple(tuple))
Spanned { v: Expr::Tuple(Tuple::new()), span }
} }
/// Parse an object expression. /// Parse an object expression: `{ <key>: <value>, ... }`.
fn parse_object(&mut self) -> Spanned<Expr> { fn parse_object(&mut self) -> Spanned<Expr> {
let token = self.eat();
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftBrace));
// Parse a collection until a right brace appears.
self.parse_collection(Some(Token::RightBrace), |p| {
// Expect an identifier as the key.
let key = p.parse_ident().ok_or(("key", None))?;
// Expect a colon behind the key (only separated by whitespace).
let behind_key = p.pos();
p.skip_whitespace();
if p.peekv() != Some(Token::Colon) {
return Err(("colon", Some(behind_key)));
}
p.eat();
p.skip_whitespace();
// Semantic highlighting for object keys.
p.feedback.decos.push(
Spanned::new(Decoration::ObjectKey, key.span));
let value = p.parse_expr().ok_or(("value", None))?;
Ok(Pair { key, value })
}).map(|object| Expr::Object(object))
}
/// Parse a comma-separated collection where each item is parsed through
/// `parse_item` until the `end` token is met.
fn parse_collection<C, I, F>(
&mut self,
end: Option<Token>,
mut parse_item: F
) -> Spanned<C>
where
C: FromIterator<I>,
F: FnMut(&mut Self) -> Result<I, (&'static str, Option<Position>)>,
{
let start = self.pos(); let start = self.pos();
// TODO: Do the thing. // Parse the comma separated items.
self.eat_until(|t| t == Token::RightBrace, true); let collection = std::iter::from_fn(|| {
self.skip_whitespace();
let peeked = self.peekv();
// We finished as expected.
if peeked == end {
self.eat();
return None;
}
// We finished without the expected end token (which has to be a
// `Some` value at this point since otherwise we would have already
// returned in the previous case).
if peeked == None {
self.eat();
self.expected_at(end.unwrap().name(), self.pos());
return None;
}
// Try to parse a collection item.
match parse_item(self) {
Ok(item) => {
// Expect a comma behind the item (only separated by
// whitespace).
let behind_item = self.pos();
self.skip_whitespace();
match self.peekv() {
Some(Token::Comma) => { self.eat(); }
t @ Some(_) if t != end => self.expected_at("comma", behind_item),
_ => {}
}
return Some(Some(item));
}
// The item parser expected something different at either some
// given position or instead of the currently peekable token.
Err((expected, Some(pos))) => self.expected_at(expected, pos),
Err((expected, None)) => {
let token = self.peek();
if token.map(Spanned::value) != end {
self.eat();
}
self.expected_found_or_at(expected, token, self.pos());
}
}
Some(None)
}).filter_map(|x| x).collect();
let end = self.pos(); let end = self.pos();
let span = Span { start, end }; Spanned::new(collection, Span { start, end })
}
Spanned { v: Expr::Object(Object::new()), span } /// Try to parse an identifier and do nothing if the peekable token is no
/// identifier.
fn parse_ident(&mut self) -> Option<Spanned<Ident>> {
match self.peek() {
Some(Spanned { v: Token::ExprIdent(s), span }) => {
self.eat();
Some(Spanned { v: Ident(s.to_string()), span })
}
_ => None
}
} }
/// Skip all whitespace/comment tokens. /// Skip all whitespace/comment tokens.
@ -432,53 +503,48 @@ mod tests {
}; };
} }
/// Test whether the given string parses into the given transform pass. /// Test whether the given string parses into
macro_rules! test { /// - the given node list (required).
($source:expr => [$($model:tt)*], $transform:expr) => { /// - the given error list (optional, if omitted checks against empty list).
let (exp, cmp) = spanned![vec $($model)*]; /// - the given decoration list (optional, if omitted it is not tested).
macro_rules! p {
($source:expr => [$($model:tt)*]) => {
p!($source => [$($model)*], []);
};
($source:expr => [$($model:tt)*], [$($errors:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
let mut scope = Scope::new::<DebugFn>(); let mut scope = Scope::new::<DebugFn>();
scope.add::<DebugFn>("f"); scope.add::<DebugFn>("f");
scope.add::<DebugFn>("n"); scope.add::<DebugFn>("n");
scope.add::<DebugFn>("box"); scope.add::<DebugFn>("box");
scope.add::<DebugFn>("val"); scope.add::<DebugFn>("val");
let ctx = ParseContext { scope: &scope }; let ctx = ParseContext { scope: &scope };
let pass = parse(Position::ZERO, $source, ctx);
let found = parse(Position::ZERO, $source, ctx); // Test model
let (exp, found) = $transform(exp, found); let (exp, cmp) = spanned![vec $($model)*];
check($source, exp, pass.output.nodes, cmp);
// Test errors
let (exp, cmp) = spanned![vec $($errors)*];
let exp = exp.into_iter()
.map(|s: Spanned<&str>| s.map(|e| e.to_string()))
.collect::<Vec<_>>();
let found = pass.feedback.errors.into_iter()
.map(|s| s.map(|e| e.message))
.collect::<Vec<_>>();
check($source, exp, found, cmp); check($source, exp, found, cmp);
};
}
/// Test whether the given string parses into the given node list. // Test decos
macro_rules! p { $(let (exp, cmp) = spanned![vec $($decos)*];
($($tts:tt)*) => { check($source, exp, pass.feedback.decos, cmp);)?
test!($($tts)*, |exp, found: Pass<SyntaxModel>| (exp, found.output.nodes));
};
}
/// Test whether the given string yields the given parse errors.
macro_rules! e {
($($tts:tt)*) => {
test!($($tts)*, |exp: Vec<Spanned<&str>>, found: Pass<SyntaxModel>| (
exp.into_iter().map(|s| s.map(|e| e.to_string())).collect::<Vec<_>>(),
found.feedback.errors.into_iter().map(|s| s.map(|e| e.message))
.collect::<Vec<_>>()
));
};
}
/// Test whether the given string yields the given decorations.
macro_rules! d {
($($tts:tt)*) => {
test!($($tts)*, |exp, found: Pass<SyntaxModel>| (exp, found.feedback.decos));
}; };
} }
/// Write down a `DebugFn` function model compactly. /// Write down a `DebugFn` function model compactly.
macro_rules! func { macro_rules! func {
($name:tt $(, ($($pos:tt)*), { $($key:tt)* } )? $(; $($body:tt)*)?) => ({ ($name:tt $(: ($($pos:tt)*), { $($key:tt)* } )? $(; $($body:tt)*)?) => ({
#[allow(unused_mut)] #[allow(unused_mut)]
let mut args = FuncArgs::new(); let mut args = FuncArgs::new();
$(args.pos = Tuple::parse(zspan(tuple!($($pos)*))).unwrap();)? $(args.pos = Tuple::parse(zspan(tuple!($($pos)*))).unwrap();)?
@ -516,6 +582,7 @@ mod tests {
#[test] #[test]
fn parse_flat_nodes() { fn parse_flat_nodes() {
// Basic nodes
p!("" => []); p!("" => []);
p!("hi" => [T("hi")]); p!("hi" => [T("hi")]);
p!("*hi" => [Bold, T("hi")]); p!("*hi" => [Bold, T("hi")]);
@ -529,204 +596,305 @@ mod tests {
p!("first/*\n \n*/second" => [T("first"), T("second")]); p!("first/*\n \n*/second" => [T("first"), T("second")]);
p!("💜\n\n 🌍" => [T("💜"), N, T("🌍")]); p!("💜\n\n 🌍" => [T("💜"), N, T("🌍")]);
// Spanned nodes
p!("Hi" => [(0:0, 0:2, T("Hi"))]); p!("Hi" => [(0:0, 0:2, T("Hi"))]);
p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]); p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]);
p!("🌎*/[n]" => [(0:0, 0:1, T("🌎")), (0:3, 0:6, func!((0:1, 0:2, "n")))]); p!("🌎\n*/[n]" =>
[(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "n")))],
e!("hi\n */" => [(1:1, 1:3, "unexpected end of block comment")]); [(1:0, 1:2, "unexpected end of block comment")],
[(1:3, 1:4, ValidFuncName)],
);
} }
#[test] #[test]
fn parse_function_names() { fn parse_function_names() {
// No closing bracket // No closing bracket
p!("[" => [func!("")]); p!("[" => [func!("")], [
e!("[" => [
(0:1, 0:1, "expected identifier"), (0:1, 0:1, "expected identifier"),
(0:1, 0:1, "expected closing bracket") (0:1, 0:1, "expected closing bracket")
]); ]);
// No name // No name
p!("[]" => [func!("")]); p!("[]" => [func!("")], [(0:1, 0:1, "expected identifier")]);
e!("[]" => [(0:1, 0:1, "expected identifier")]); p!("[\"]" => [func!("")], [
p!("[\"]" => [func!("")]);
e!("[\"]" => [
(0:1, 0:3, "expected identifier, found string"), (0:1, 0:3, "expected identifier, found string"),
(0:3, 0:3, "expected closing bracket"), (0:3, 0:3, "expected closing bracket"),
]); ]);
// A valid name
p!("[f]" => [func!("f")]);
e!("[f]" => []);
d!("[f]" => [(0:1, 0:2, ValidFuncName)]);
p!("[ f]" => [func!("f")]);
e!("[ f]" => []);
d!("[ f]" => [(0:3, 0:4, ValidFuncName)]);
// An unknown name // An unknown name
p!("[hi]" => [func!("hi")]); p!("[hi]" =>
e!("[hi]" => [(0:1, 0:3, "unknown function")]); [func!("hi")],
d!("[hi]" => [(0:1, 0:3, InvalidFuncName)]); [(0:1, 0:3, "unknown function")],
[(0:1, 0:3, InvalidFuncName)],
);
// An invalid token // A valid name
p!("[🌎]" => [func!("")]); p!("[f]" => [func!("f")], [], [(0:1, 0:2, ValidFuncName)]);
e!("[🌎]" => [(0:1, 0:2, "expected identifier, found invalid token")]); p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ValidFuncName)]);
d!("[🌎]" => []);
p!("[ 🌎]" => [func!("")]); // An invalid token for a name
e!("[ 🌎]" => [(0:3, 0:4, "expected identifier, found invalid token")]); p!("[12]" => [func!("")], [(0:1, 0:3, "expected identifier, found number")], []);
d!("[ 🌎]" => []); p!("[🌎]" => [func!("")], [(0:1, 0:2, "expected identifier, found invalid token")], []);
p!("[ 🌎]" => [func!("")], [(0:3, 0:4, "expected identifier, found invalid token")], []);
} }
#[test] #[test]
fn parse_colon_starting_function_arguments() { fn parse_colon_starting_function_arguments() {
// No colon before arg // No colon before arg
p!("[val\"s\"]" => [func!("val")]); p!("[val\"s\"]" => [func!("val")], [(0:4, 0:4, "expected colon")]);
e!("[val\"s\"]" => [(0:4, 0:4, "expected colon")]);
// No colon before valid, but wrong token // No colon before valid, but wrong token
p!("[val=]" => [func!("val")]); p!("[val=]" => [func!("val")], [(0:4, 0:4, "expected colon")]);
e!("[val=]" => [(0:4, 0:4, "expected colon")]);
// No colon before invalid tokens, which are ignored // No colon before invalid tokens, which are ignored
p!("[val/🌎:$]" => [func!("val")]); p!("[val/🌎:$]" =>
e!("[val/🌎:$]" => [(0:4, 0:4, "expected colon")]); [func!("val")],
d!("[val/🌎:$]" => [(0:1, 0:4, ValidFuncName)]); [(0:4, 0:4, "expected colon")],
[(0:1, 0:4, ValidFuncName)],
);
// String in invalid header without colon still parsed as string // String in invalid header without colon still parsed as string
// Note: No "expected quote" error because not even the string was // Note: No "expected quote" error because not even the string was
// expected. // expected.
e!("[val/\"]" => [ p!("[val/\"]" => [func!("val")], [
(0:4, 0:4, "expected colon"), (0:4, 0:4, "expected colon"),
(0:7, 0:7, "expected closing bracket"), (0:7, 0:7, "expected closing bracket"),
]); ]);
// Just colon without args // Just colon without args
p!("[val:]" => [func!("val")]); p!("[val:]" => [func!("val")]);
e!("[val:]" => []);
p!("[val:/*12pt*/]" => [func!("val")]); p!("[val:/*12pt*/]" => [func!("val")]);
// Whitespace / comments around colon // Whitespace / comments around colon
p!("[val\n:\ntrue]" => [func!("val", (Bool(true)), {})]); p!("[val\n:\ntrue]" => [func!("val": (Bool(true)), {})]);
p!("[val/*:*/://\ntrue]" => [func!("val", (Bool(true)), {})]); p!("[val/*:*/://\ntrue]" => [func!("val": (Bool(true)), {})]);
e!("[val/*:*/://\ntrue]" => []);
} }
#[test] #[test]
fn parse_one_positional_argument() { fn parse_one_positional_argument() {
// Different expressions // Different expressions
d!("[val: true]" => [(0:1, 0:4, ValidFuncName)]); p!("[val: true]" =>
p!("[val: true]" => [func!("val", (Bool(true)), {})]); [func!("val": (Bool(true)), {})], [],
p!("[val: _]" => [func!("val", (Id("_")), {})]); [(0:1, 0:4, ValidFuncName)],
p!("[val: name]" => [func!("val", (Id("name")), {})]); );
p!("[val: \"hi\"]" => [func!("val", (Str("hi")), {})]); p!("[val: _]" => [func!("val": (Id("_")), {})]);
p!("[val: \"a\n[]\\\"string\"]" => [func!("val", (Str("a\n[]\"string")), {})]); p!("[val: name]" => [func!("val": (Id("name")), {})]);
p!("[val: 3.14]" => [func!("val", (Num(3.14)), {})]); p!("[val: \"hi\"]" => [func!("val": (Str("hi")), {})]);
p!("[val: 4.5cm]" => [func!("val", (Sz(Size::cm(4.5))), {})]); p!("[val: \"a\n[]\\\"string\"]" => [func!("val": (Str("a\n[]\"string")), {})]);
p!("[val: 12e1pt]" => [func!("val", (Pt(12e1)), {})]); p!("[val: 3.14]" => [func!("val": (Num(3.14)), {})]);
p!("[val: 4.5cm]" => [func!("val": (Sz(Size::cm(4.5))), {})]);
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
// Unclosed string. // Unclosed string.
p!("[val: \"hello]" => [func!("val", (Str("hello]")), {})]); p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
e!("[val: \"hello]" => [
(0:13, 0:13, "expected quote"), (0:13, 0:13, "expected quote"),
(0:13, 0:13, "expected closing bracket"), (0:13, 0:13, "expected closing bracket"),
]); ]);
}
// Tuple: unimplemented #[test]
p!("[val: ()]" => [func!("val", (tuple!()), {})]); fn parse_tuples() {
// Empty tuple
p!("[val: ()]" => [func!("val": (tuple!()), {})]);
// Object: unimplemented // Invalid value
p!("[val: {}]" => [func!("val", (object! {}), {})]); p!("[val: (🌎)]" =>
[func!("val": (tuple!()), {})],
[(0:7, 0:8, "expected value, found invalid token")],
);
// Unclosed tuple
p!("[val: (hello]" =>
[func!("val": (tuple!(Id("hello"))), {})],
[(0:12, 0:12, "expected closing paren")],
);
// Valid values
p!("[val: (1, 2)]" => [func!("val": (tuple!(Num(1.0), Num(2.0))), {})]);
p!("[val: (\"s\",)]" => [func!("val": (tuple!(Str("s"))), {})]);
// Nested tuples
p!("[val: (1, (2))]" => [func!("val": (tuple!(Num(1.0), tuple!(Num(2.0)))), {})]);
// Invalid commas
p!("[val: (,)]" =>
[func!("val": (tuple!()), {})],
[(0:7, 0:8, "expected value, found comma")],
);
p!("[val: (true false)]" =>
[func!("val": (tuple!(Bool(true), Bool(false))), {})],
[(0:11, 0:11, "expected comma")],
);
}
#[test]
fn parse_objects() {
let f = || func!("val": (object! {}), {});
// Okay objects
p!("[val: {}]" => [f()]);
p!("[val: { key: value }]" =>
[func!("val": (object! { "key" => Id("value") }), {})]);
// Unclosed object
p!("[val: {hello: world]" =>
[func!("val": (object! { "hello" => Id("world") }), {})],
[(0:19, 0:19, "expected closing brace")],
);
p!("[val: { a]" =>
[func!("val": (object! {}), {})],
[(0:9, 0:9, "expected colon"), (0:9, 0:9, "expected closing brace")],
);
// Missing key
p!("[val: {,}]" => [f()], [(0:7, 0:8, "expected key, found comma")]);
p!("[val: { 12pt }]" => [f()], [(0:8, 0:12, "expected key, found size")]);
p!("[val: { : }]" => [f()], [(0:8, 0:9, "expected key, found colon")]);
// Missing colon
p!("[val: { key }]" => [f()], [(0:11, 0:11, "expected colon")]);
p!("[val: { key false }]" => [f()], [
(0:11, 0:11, "expected colon"),
(0:12, 0:17, "expected key, found bool"),
]);
p!("[val: { a b:c }]" =>
[func!("val": (object! { "b" => Id("c") }), {})],
[(0:9, 0:9, "expected colon")],
);
// Missing value
p!("[val: { key: : }]" => [f()], [(0:13, 0:14, "expected value, found colon")]);
p!("[val: { key: , k: \"s\" }]" =>
[func!("val": (object! { "k" => Str("s") }), {})],
[(0:13, 0:14, "expected value, found comma")],
);
// Missing comma, invalid token
p!("[val: left={ a: 2, b: false 🌎 }]" =>
[func!("val": (), {
"left" => object! {
"a" => Num(2.0),
"b" => Bool(false),
}
})],
[(0:27, 0:27, "expected comma"),
(0:28, 0:29, "expected key, found invalid token")],
);
}
#[test]
fn parse_nested_tuples_and_objects() {
p!("[val: (1, { ab: (), d: (3, 14pt) }), false]" => [func!("val": (
tuple!(
Num(1.0),
object!(
"ab" => tuple!(),
"d" => tuple!(Num(3.0), Pt(14.0)),
),
),
Bool(false),
), {})]);
} }
#[test] #[test]
fn parse_one_keyword_argument() { fn parse_one_keyword_argument() {
// Correct // Correct
p!("[val: x=true]" => [func!("val", (), { "x" => Bool(true) })]); p!("[val: x=true]" =>
d!("[val: x=true]" => [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)]); [func!("val": (), { "x" => Bool(true) })], [],
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)],
);
// Spacing around keyword arguments // Spacing around keyword arguments
p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" => [S, func!("val", (), { "hi" => Str("s\n") })]); p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" =>
d!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" => [(2:1, 2:3, ArgumentKey), (1:2, 1:5, ValidFuncName)]); [S, func!("val": (), { "hi" => Str("s\n") })], [],
e!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" => []); [(2:1, 2:3, ArgumentKey), (1:2, 1:5, ValidFuncName)],
);
// Missing value // Missing value
p!("[val: x=]" => [func!("val")]); p!("[val: x=]" =>
e!("[val: x=]" => [(0:8, 0:8, "expected value")]); [func!("val")],
d!("[val: x=]" => [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)]); [(0:8, 0:8, "expected value")],
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)],
);
} }
#[test] #[test]
fn parse_multiple_mixed_arguments() { fn parse_multiple_mixed_arguments() {
p!("[val: a,]" => [func!("val", (Id("a")), {})]); p!("[val: a,]" => [func!("val": (Id("a")), {})]);
e!("[val: a,]" => []); p!("[val: 12pt, key=value]" =>
p!("[val: 12pt, key=value]" => [func!("val", (Pt(12.0)), { "key" => Id("value") })]); [func!("val": (Pt(12.0)), { "key" => Id("value") })], [],
d!("[val: 12pt, key=value]" => [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)]); [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)],
e!("[val: 12pt, key=value]" => []); );
p!("[val: a , \"b\" , c]" => [func!("val", (Id("a"), Str("b"), Id("c")), {})]); p!("[val: a , \"b\" , c]" => [func!("val": (Id("a"), Str("b"), Id("c")), {})]);
e!("[val: a , \"b\" , c]" => []);
} }
#[test] #[test]
fn parse_invalid_values() { fn parse_invalid_values() {
e!("[val: )]" => [(0:6, 0:7, "expected value, found closing paren")]); p!("[val: )]" => [func!("val")], [(0:6, 0:7, "expected argument, found closing paren")]);
e!("[val: }]" => [(0:6, 0:7, "expected value, found closing brace")]); p!("[val: }]" => [func!("val")], [(0:6, 0:7, "expected argument, found closing brace")]);
e!("[val: :]" => [(0:6, 0:7, "expected value, found colon")]); p!("[val: :]" => [func!("val")], [(0:6, 0:7, "expected argument, found colon")]);
e!("[val: ,]" => [(0:6, 0:7, "expected value, found comma")]); p!("[val: ,]" => [func!("val")], [(0:6, 0:7, "expected argument, found comma")]);
e!("[val: =]" => [(0:6, 0:7, "expected value, found equals sign")]); p!("[val: =]" => [func!("val")], [(0:6, 0:7, "expected argument, found equals sign")]);
e!("[val: 🌎]" => [(0:6, 0:7, "expected value, found invalid token")]); p!("[val: 🌎]" => [func!("val")], [(0:6, 0:7, "expected argument, found invalid token")]);
e!("[val: 12ept]" => [(0:6, 0:11, "expected value, found invalid token")]); p!("[val: 12ept]" => [func!("val")], [(0:6, 0:11, "expected argument, found invalid token")]);
e!("[val: [hi]]" => [(0:6, 0:10, "expected value, found function")]); p!("[val: [hi]]" =>
d!("[val: [hi]]" => [(0:1, 0:4, ValidFuncName)]); [func!("val")],
[(0:6, 0:10, "expected argument, found function")],
[(0:1, 0:4, ValidFuncName)],
);
} }
#[test] #[test]
fn parse_invalid_key_value_pairs() { fn parse_invalid_key_value_pairs() {
// Invalid keys // Invalid keys
p!("[val: true=you]" => [func!("val", (Bool(true), Id("you")), {})]); p!("[val: true=you]" =>
e!("[val: true=you]" => [ [func!("val": (Bool(true), Id("you")), {})],
(0:10, 0:10, "expected comma"), [(0:10, 0:10, "expected comma"),
(0:10, 0:11, "expected value, found equals sign"), (0:10, 0:11, "expected argument, found equals sign")],
]); [(0:1, 0:4, ValidFuncName)],
d!("[val: true=you]" => [(0:1, 0:4, ValidFuncName)]); );
p!("[box: z=y=4]" => [func!("box", (Num(4.0)), { "z" => Id("y") })]); p!("[box: z=y=4]" =>
e!("[box: z=y=4]" => [ [func!("box": (Num(4.0)), { "z" => Id("y") })],
(0:9, 0:9, "expected comma"), [(0:9, 0:9, "expected comma"),
(0:9, 0:10, "expected value, found equals sign"), (0:9, 0:10, "expected argument, found equals sign")],
]); );
// Invalid colon after keyable positional argument // Invalid colon after keyable positional argument
p!("[val: key:12]" => [func!("val", (Id("key"), Num(12.0)), {})]); p!("[val: key:12]" =>
e!("[val: key:12]" => [ [func!("val": (Id("key"), Num(12.0)), {})],
(0:9, 0:9, "expected comma"), [(0:9, 0:9, "expected comma"),
(0:9, 0:10, "expected value, found colon"), (0:9, 0:10, "expected argument, found colon")],
]); [(0:1, 0:4, ValidFuncName)],
d!("[val: key:12]" => [(0:1, 0:4, ValidFuncName)]); );
// Invalid colon after non-keyable positional argument // Invalid colon after non-keyable positional argument
p!("[val: true:12]" => [func!("val", (Bool(true), Num(12.0)), {})]); p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})],
e!("[val: true:12]" => [ [(0:10, 0:10, "expected comma"),
(0:10, 0:10, "expected comma"), (0:10, 0:11, "expected argument, found colon")],
(0:10, 0:11, "expected value, found colon"), [(0:1, 0:4, ValidFuncName)],
]); );
d!("[val: true:12]" => [(0:1, 0:4, ValidFuncName)]);
} }
#[test] #[test]
fn parse_invalid_commas() { fn parse_invalid_commas() {
// Missing commas // Missing commas
p!("[val: 1pt 1]" => [func!("val", (Pt(1.0), Num(1.0)), {})]); p!("[val: 1pt 1]" =>
e!("[val: 1pt 1]" => [(0:9, 0:9, "expected comma")]); [func!("val": (Pt(1.0), Num(1.0)), {})],
p!(r#"[val: _"s"]"# => [func!("val", (Id("_"), Str("s")), {})]); [(0:9, 0:9, "expected comma")],
e!(r#"[val: _"s"]"# => [(0:7, 0:7, "expected comma")]); );
p!(r#"[val: _"s"]"# =>
[func!("val": (Id("_"), Str("s")), {})],
[(0:7, 0:7, "expected comma")],
);
// Unexpected commas // Unexpected commas
p!("[val:,]" => [func!("val")]); p!("[val:,]" => [func!("val")], [(0:5, 0:6, "expected argument, found comma")]);
e!("[val:,]" => [(0:5, 0:6, "expected value, found comma")]); p!("[val: key=,]" => [func!("val")], [(0:10, 0:11, "expected value, found comma")]);
p!("[val:, true]" => [func!("val", (Bool(true)), {})]); p!("[val:, true]" =>
e!("[val:, true]" => [(0:5, 0:6, "expected value, found comma")]); [func!("val": (Bool(true)), {})],
p!("[val: key=,]" => [func!("val")]); [(0:5, 0:6, "expected argument, found comma")],
e!("[val: key=,]" => [(0:10, 0:11, "expected value, found comma")]); );
} }
#[test] #[test]
@ -734,37 +902,47 @@ mod tests {
p!("[val][Hi]" => [func!("val"; [T("Hi")])]); p!("[val][Hi]" => [func!("val"; [T("Hi")])]);
// Body nodes in bodies. // Body nodes in bodies.
p!("[val:*][*Hi*]" => [func!("val"; [Bold, T("Hi"), Bold])]); p!("[val:*][*Hi*]" =>
e!("[val:*][*Hi*]" => [(0:5, 0:6, "expected value, found invalid token")]); [func!("val"; [Bold, T("Hi"), Bold])],
[(0:5, 0:6, "expected argument, found invalid token")],
);
// Errors in bodies. // Errors in bodies.
p!(" [val][ */ ]" => [S, func!("val"; [S, S])]); p!(" [val][ */ ]" =>
e!(" [val][ */ ]" => [(0:8, 0:10, "unexpected end of block comment")]); [S, func!("val"; [S, S])],
[(0:8, 0:10, "unexpected end of block comment")],
);
} }
#[test] #[test]
fn parse_spanned_functions() { fn parse_spanned_functions() {
// Space before function // Space before function
p!(" [val]" => [(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))]); p!(" [val]" =>
d!(" [val]" => [(0:2, 0:5, ValidFuncName)]); [(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))], [],
[(0:2, 0:5, ValidFuncName)],
);
// Newline before function // Newline before function
p!(" \n\r\n[val]" => [(0:0, 2:0, N), (2:0, 2:5, func!((0:1, 0:4, "val")))]); p!(" \n\r\n[val]" =>
d!(" \n\r\n[val]" => [(2:1, 2:4, ValidFuncName)]); [(0:0, 2:0, N), (2:0, 2:5, func!((0:1, 0:4, "val")))], [],
[(2:1, 2:4, ValidFuncName)],
);
// Content before function // Content before function
p!("hello [val][world] 🌎" => [ p!("hello [val][world] 🌎" =>
[
(0:0, 0:5, T("hello")), (0:0, 0:5, T("hello")),
(0:5, 0:6, S), (0:5, 0:6, S),
(0:6, 0:18, func!((0:1, 0:4, "val"); [(0:6, 0:11, T("world"))])), (0:6, 0:18, func!((0:1, 0:4, "val"); [(0:6, 0:11, T("world"))])),
(0:18, 0:19, S), (0:18, 0:19, S),
(0:19, 0:20, T("🌎")), (0:19, 0:20, T("🌎"))
]); ], [],
d!("hello [val][world] 🌎" => [(0:7, 0:10, ValidFuncName)]); [(0:7, 0:10, ValidFuncName)],
e!("hello [val][world] 🌎" => []); );
// Nested function // Nested function
p!(" [val][\nbody[ box]\n ]" => [ p!(" [val][\nbody[ box]\n ]" =>
[
(0:0, 0:1, S), (0:0, 0:1, S),
(0:1, 2:2, func!((0:1, 0:4, "val"); [ (0:1, 2:2, func!((0:1, 0:4, "val"); [
(0:6, 1:0, S), (0:6, 1:0, S),
@ -772,10 +950,8 @@ mod tests {
(1:4, 1:10, func!((0:2, 0:5, "box"))), (1:4, 1:10, func!((0:2, 0:5, "box"))),
(1:10, 2:1, S), (1:10, 2:1, S),
])) ]))
]); ], [],
d!(" [val][\nbody[ box]\n ]" => [ [(0:2, 0:5, ValidFuncName), (1:6, 1:9, ValidFuncName)],
(0:2, 0:5, ValidFuncName), );
(1:6, 1:9, ValidFuncName)
]);
} }
} }

View File

@ -149,9 +149,9 @@ impl<T> Spanned<T> {
impl<T: Debug> Debug for Spanned<T> { impl<T: Debug> Debug for Spanned<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.span.fmt(f)?; self.v.fmt(f)?;
f.write_str(": ")?; f.write_str(" ")?;
self.v.fmt(f) self.span.fmt(f)
} }
} }

View File

@ -112,7 +112,7 @@ impl<'s> Token<'s> {
ExprStr { .. } => "string", ExprStr { .. } => "string",
ExprNumber(_) => "number", ExprNumber(_) => "number",
ExprSize(_) => "size", ExprSize(_) => "size",
ExprBool(_) => "boolean", ExprBool(_) => "bool",
Star => "star", Star => "star",
Underscore => "underscore", Underscore => "underscore",
Backtick => "backtick", Backtick => "backtick",
@ -209,7 +209,7 @@ impl<'s> Iterator for Tokens<'s> {
'`' if self.mode == Body => Backtick, '`' if self.mode == Body => Backtick,
// An escaped thing. // An escaped thing.
'\\' => self.parse_escaped(), '\\' if self.mode == Body => self.parse_escaped(),
// Expressions or just strings. // Expressions or just strings.
c => { c => {
@ -217,9 +217,10 @@ impl<'s> Iterator for Tokens<'s> {
let text = self.read_string_until(|n| { let text = self.read_string_until(|n| {
match n { match n {
c if c.is_whitespace() => true, c if c.is_whitespace() => true,
'\\' | '[' | ']' | '/' => true, '[' | ']' | '/' => true,
'*' | '_' | '`' if body => true, '\\' | '*' | '_' | '`' if body => true,
':' | '=' | ',' | '"' if !body => true, ':' | '=' | ',' | '"' |
'(' | ')' | '{' | '}' if !body => true,
_ => false, _ => false,
} }
}, false, -(c.len_utf8() as isize), 0).0; }, false, -(c.len_utf8() as isize), 0).0;
@ -340,24 +341,19 @@ impl<'s> Tokens<'s> {
fn parse_escaped(&mut self) -> Token<'s> { fn parse_escaped(&mut self) -> Token<'s> {
fn is_escapable(c: char) -> bool { fn is_escapable(c: char) -> bool {
match c { match c {
'\\' | '[' | ']' | '*' | '_' | '`' | '/' => true, '[' | ']' | '\\' | '/' | '*' | '_' | '`' => true,
_ => false, _ => false,
} }
} }
let c = self.peek().unwrap_or('n'); Text(match self.peek() {
let string = if is_escapable(c) { Some(c) if is_escapable(c) => {
let index = self.index(); let index = self.index();
self.eat(); self.eat();
&self.src[index .. index + c.len_utf8()] &self.src[index .. index + c.len_utf8()]
} else {
"\\"
};
match self.mode {
Header => Invalid(string),
Body => Text(string),
} }
_ => "\\"
})
} }
fn parse_expr(&mut self, text: &'s str) -> Token<'s> { fn parse_expr(&mut self, text: &'s str) -> Token<'s> {
@ -570,6 +566,8 @@ mod tests {
t!(Header, "2.3cm" => [Sz(Size::cm(2.3))]); t!(Header, "2.3cm" => [Sz(Size::cm(2.3))]);
t!(Header, "02.4mm" => [Sz(Size::mm(2.4))]); t!(Header, "02.4mm" => [Sz(Size::mm(2.4))]);
t!(Header, "2.4.cm" => [Invalid("2.4.cm")]); t!(Header, "2.4.cm" => [Invalid("2.4.cm")]);
t!(Header, "(1,2)" => [LP, Num(1.0), Comma, Num(2.0), RP]);
t!(Header, "{abc}" => [LB, Id("abc"), RB]);
t!(Header, "🌓, 🌍," => [Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma]); t!(Header, "🌓, 🌍," => [Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma]);
} }
@ -616,8 +614,8 @@ mod tests {
t!(Body, r"\a" => [T("\\"), T("a")]); t!(Body, r"\a" => [T("\\"), T("a")]);
t!(Body, r"\:" => [T(r"\"), T(":")]); t!(Body, r"\:" => [T(r"\"), T(":")]);
t!(Body, r"\=" => [T(r"\"), T("=")]); t!(Body, r"\=" => [T(r"\"), T("=")]);
t!(Header, r"\\\\" => [Invalid("\\"), Invalid("\\")]); t!(Header, r"\\\\" => [Invalid(r"\\\\")]);
t!(Header, r"\a" => [Invalid("\\"), Id("a")]); t!(Header, r"\a" => [Invalid(r"\a")]);
t!(Header, r"\:" => [Invalid(r"\"), Colon]); t!(Header, r"\:" => [Invalid(r"\"), Colon]);
t!(Header, r"\=" => [Invalid(r"\"), Equals]); t!(Header, r"\=" => [Invalid(r"\"), Equals]);
t!(Header, r"\," => [Invalid(r"\"), Comma]); t!(Header, r"\," => [Invalid(r"\"), Comma]);