summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/func.rs15
-rw-r--r--src/library/align.rs10
-rw-r--r--src/library/boxed.rs8
-rw-r--r--src/library/font.rs42
-rw-r--r--src/library/page.rs22
-rw-r--r--src/library/spacing.rs4
-rw-r--r--src/library/val.rs2
-rw-r--r--src/syntax/decoration.rs6
-rw-r--r--src/syntax/expr.rs296
-rw-r--r--src/syntax/mod.rs61
-rw-r--r--src/syntax/parsing.rs1137
-rw-r--r--src/syntax/test.rs73
-rw-r--r--src/syntax/tokens.rs345
-rw-r--r--src/table.rs77
14 files changed, 847 insertions, 1251 deletions
diff --git a/src/func.rs b/src/func.rs
index bd273b4a..bff4fdab 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -8,8 +8,8 @@ pub mod prelude {
pub use crate::layout::Command::{self, *};
pub use crate::style::*;
pub use crate::syntax::expr::*;
- pub use crate::syntax::parsing::{parse, FuncArgs, FuncCall, ParseState};
- pub use crate::syntax::span::{Span, SpanVec, Spanned};
+ pub use crate::syntax::parsing::{parse, FuncCall, ParseState};
+ pub use crate::syntax::span::{Pos, Span, SpanVec, Spanned};
pub use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
pub use crate::{Pass, Feedback};
pub use super::*;
@@ -41,14 +41,3 @@ impl<T> OptionExt<T> for Option<T> {
self
}
}
-
-/// Generate `unexpected argument` errors for all remaining arguments.
-pub fn drain_args(args: FuncArgs, f: &mut Feedback) {
- for arg in args.pos.0 {
- error!(@f, arg.span, "unexpected argument");
- }
-
- for arg in args.key.0 {
- error!(@f, arg.span, "unexpected argument");
- }
-}
diff --git a/src/library/align.rs b/src/library/align.rs
index 115793b1..b4cfd2e2 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -14,12 +14,12 @@ pub fn align(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut f = Feedback::new();
let mut args = call.args;
let node = AlignNode {
- content: args.pos.get::<SyntaxTree>(),
- aligns: args.pos.all::<Spanned<SpecAlign>>().collect(),
- h: args.key.get::<Spanned<SpecAlign>>("horizontal", &mut f),
- v: args.key.get::<Spanned<SpecAlign>>("vertical", &mut f),
+ content: args.take::<SyntaxTree>(),
+ aligns: args.take_all_num_vals::<Spanned<SpecAlign>>().collect(),
+ h: args.take_with_key::<_, Spanned<SpecAlign>>("horizontal", &mut f),
+ v: args.take_with_key::<_, Spanned<SpecAlign>>("vertical", &mut f),
};
- drain_args(args, &mut f);
+ args.unexpected(&mut f);
Pass::node(node, f)
}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index 3ca3ae44..3637f072 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -10,11 +10,11 @@ pub fn boxed(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut f = Feedback::new();
let mut args = call.args;
let node = BoxNode {
- content: args.pos.get::<SyntaxTree>().unwrap_or(SyntaxTree::new()),
- width: args.key.get::<ScaleLength>("width", &mut f),
- height: args.key.get::<ScaleLength>("height", &mut f),
+ content: args.take::<SyntaxTree>().unwrap_or(SyntaxTree::new()),
+ width: args.take_with_key::<_, ScaleLength>("width", &mut f),
+ height: args.take_with_key::<_, ScaleLength>("height", &mut f),
};
- drain_args(args, &mut f);
+ args.unexpected(&mut f);
Pass::node(node, f)
}
diff --git a/src/library/font.rs b/src/library/font.rs
index d445a246..8787cc91 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -13,7 +13,7 @@ use super::*;
/// - `style`: `normal`, `italic` or `oblique`.
/// - `weight`: `100` - `900` or a name like `thin`.
/// - `width`: `1` - `9` or a name like `condensed`.
-/// - Any other keyword argument whose value is a tuple of strings is a class
+/// - Any other keyword argument whose value is a table of strings is a class
/// fallback definition like:
/// ```typst
/// serif = ("Source Serif Pro", "Noto Serif")
@@ -23,31 +23,25 @@ pub fn font(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut args = call.args;
let node = FontNode {
- content: args.pos.get::<SyntaxTree>(),
- size: args.pos.get::<ScaleLength>(),
- style: args.key.get::<FontStyle>("style", &mut f),
- weight: args.key.get::<FontWeight>("weight", &mut f),
- width: args.key.get::<FontWidth>("width", &mut f),
- list: {
- args.pos.all::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect()
- },
- classes: {
- args.key.all::<Tuple>()
- .collect::<Vec<_>>()
- .into_iter()
- .map(|(class, mut tuple)| {
- let fallback = tuple.all::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect();
- (class.v.0, fallback)
- })
- .collect()
- },
+ content: args.take::<SyntaxTree>(),
+ size: args.take::<ScaleLength>(),
+ style: args.take_with_key::<_, FontStyle>("style", &mut f),
+ weight: args.take_with_key::<_, FontWeight>("weight", &mut f),
+ width: args.take_with_key::<_, FontWidth>("width", &mut f),
+ list: args.take_all_num_vals::<StringLike>()
+ .map(|s| s.0.to_lowercase())
+ .collect(),
+ classes: args.take_all_str::<TableExpr>()
+ .map(|(class, mut table)| {
+ let fallback = table.take_all_num_vals::<StringLike>()
+ .map(|s| s.0.to_lowercase())
+ .collect();
+ (class, fallback)
+ })
+ .collect()
};
- drain_args(args, &mut f);
+ args.unexpected(&mut f);
Pass::node(node, f)
}
diff --git a/src/library/page.rs b/src/library/page.rs
index b47749ea..7e4e6e54 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -20,17 +20,17 @@ pub fn page(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut f = Feedback::new();
let mut args = call.args;
let node = PageNode {
- paper: args.pos.get::<Paper>(),
- width: args.key.get::<Length>("width", &mut f),
- height: args.key.get::<Length>("height", &mut f),
- margins: args.key.get::<ScaleLength>("margins", &mut f),
- left: args.key.get::<ScaleLength>("left", &mut f),
- right: args.key.get::<ScaleLength>("right", &mut f),
- top: args.key.get::<ScaleLength>("top", &mut f),
- bottom: args.key.get::<ScaleLength>("bottom", &mut f),
- flip: args.key.get::<bool>("flip", &mut f).unwrap_or(false),
+ paper: args.take::<Paper>(),
+ width: args.take_with_key::<_, Length>("width", &mut f),
+ height: args.take_with_key::<_, Length>("height", &mut f),
+ margins: args.take_with_key::<_, ScaleLength>("margins", &mut f),
+ left: args.take_with_key::<_, ScaleLength>("left", &mut f),
+ right: args.take_with_key::<_, ScaleLength>("right", &mut f),
+ top: args.take_with_key::<_, ScaleLength>("top", &mut f),
+ bottom: args.take_with_key::<_, ScaleLength>("bottom", &mut f),
+ flip: args.take_with_key::<_, bool>("flip", &mut f).unwrap_or(false),
};
- drain_args(args, &mut f);
+ args.unexpected(&mut f);
Pass::node(node, f)
}
@@ -78,7 +78,7 @@ impl Layout for PageNode {
/// `pagebreak`: Ends the current page.
pub fn pagebreak(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut f = Feedback::new();
- drain_args(call.args, &mut f);
+ call.args.unexpected(&mut f);
Pass::node(PageBreakNode, f)
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index ad30a122..81112cbd 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -22,11 +22,11 @@ fn spacing(call: FuncCall, axis: SpecAxis) -> Pass<SyntaxNode> {
let mut f = Feedback::new();
let mut args = call.args;
let node = SpacingNode {
- spacing: args.pos.expect::<ScaleLength>(&mut f)
+ spacing: args.expect::<ScaleLength>(&mut f)
.map(|s| (axis, s))
.or_missing(call.name.span, "spacing", &mut f),
};
- drain_args(args, &mut f);
+ args.unexpected(&mut f);
Pass::node(node, f)
}
diff --git a/src/library/val.rs b/src/library/val.rs
index 6e83571a..9df55401 100644
--- a/src/library/val.rs
+++ b/src/library/val.rs
@@ -7,7 +7,7 @@ use super::*;
pub fn val(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
let mut args = call.args;
let node = ValNode {
- content: args.pos.get::<SyntaxTree>(),
+ content: args.take::<SyntaxTree>(),
};
Pass::node(node, Feedback::new())
}
diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs
index a9097444..a686596c 100644
--- a/src/syntax/decoration.rs
+++ b/src/syntax/decoration.rs
@@ -17,10 +17,8 @@ pub enum Decoration {
ResolvedFunc,
/// An invalid, unresolved function name.
UnresolvedFunc,
- /// A key part of a keyword argument.
- ArgumentKey,
- /// A key part of a pair in an object.
- ObjectKey,
+ /// The key part of a key-value entry in a table.
+ TableKey,
/// Text in italics.
Italic,
/// Text in bold.
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 98c67392..e92a75b0 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -1,7 +1,6 @@
//! Expressions in function headers.
use std::fmt::{self, Debug, Formatter};
-use std::ops::Deref;
use std::str::FromStr;
use std::u8;
@@ -12,7 +11,8 @@ use crate::length::{Length, ScaleLength};
use crate::paper::Paper;
use crate::table::{BorrowedKey, Table};
use crate::Feedback;
-use super::span::{Span, SpanVec, Spanned};
+use super::parsing::FuncCall;
+use super::span::{Span, Spanned};
use super::tokens::is_identifier;
use super::tree::SyntaxTree;
@@ -33,12 +33,8 @@ pub enum Expr {
Color(RgbaColor),
/// A syntax tree containing typesetting content.
Tree(SyntaxTree),
- /// A tuple: `(false, 12cm, "hi")`.
- Tuple(Tuple),
- /// A named tuple: `cmyk(37.7, 0, 3.9, 1.1)`.
- NamedTuple(NamedTuple),
- /// An object: `{ fit=false, width=12pt }`.
- Object(Object),
+ /// A table: `(false, 12cm, greeting="hi")`.
+ Table(TableExpr),
/// An operation that negates the contained expression.
Neg(Box<Spanned<Expr>>),
/// An operation that adds the contained expressions.
@@ -49,6 +45,8 @@ pub enum Expr {
Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
/// An operation that divides the contained expressions.
Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
+ /// A function call: `cmyk(37.7, 0, 3.9, 1.1)`.
+ Call(FuncCall),
}
impl Expr {
@@ -57,21 +55,20 @@ impl Expr {
pub fn name(&self) -> &'static str {
use Expr::*;
match self {
- Ident(_) => "identifier",
- Str(_) => "string",
- Bool(_) => "bool",
- Number(_) => "number",
- Length(_) => "length",
- Color(_) => "color",
- Tree(_) => "syntax tree",
- Tuple(_) => "tuple",
- NamedTuple(_) => "named tuple",
- Object(_) => "object",
- Neg(_) => "negation",
- Add(_, _) => "addition",
- Sub(_, _) => "subtraction",
- Mul(_, _) => "multiplication",
- Div(_, _) => "division",
+ Ident(_) => "identifier",
+ Str(_) => "string",
+ Bool(_) => "bool",
+ Number(_) => "number",
+ Length(_) => "length",
+ Color(_) => "color",
+ Tree(_) => "syntax tree",
+ Table(_) => "table",
+ Neg(_) => "negation",
+ Add(_, _) => "addition",
+ Sub(_, _) => "subtraction",
+ Mul(_, _) => "multiplication",
+ Div(_, _) => "division",
+ Call(_) => "function call",
}
}
}
@@ -87,14 +84,13 @@ impl Debug for Expr {
Length(s) => s.fmt(f),
Color(c) => c.fmt(f),
Tree(t) => t.fmt(f),
- Tuple(t) => t.fmt(f),
- NamedTuple(t) => t.fmt(f),
- Object(o) => o.fmt(f),
+ Table(t) => t.fmt(f),
Neg(e) => write!(f, "-{:?}", e),
Add(a, b) => write!(f, "({:?} + {:?})", a, b),
Sub(a, b) => write!(f, "({:?} - {:?})", a, b),
Mul(a, b) => write!(f, "({:?} * {:?})", a, b),
Div(a, b) => write!(f, "({:?} / {:?})", a, b),
+ Call(c) => c.fmt(f),
}
}
}
@@ -231,174 +227,6 @@ impl fmt::Display for ParseColorError {
}
}
-/// An untyped sequence of expressions.
-///
-/// # Example
-/// ```typst
-/// (false, 12cm, "hi")
-/// ```
-#[derive(Default, Clone, PartialEq)]
-pub struct Tuple(pub SpanVec<Expr>);
-
-impl Tuple {
- /// Create an empty tuple.
- pub fn new() -> Self {
- Self(vec![])
- }
-
- /// Add an element.
- pub fn push(&mut self, item: Spanned<Expr>) {
- self.0.push(item);
- }
-
- /// Expect a specific value type and generate errors for every argument
- /// until an argument of the value type is found.
- pub fn expect<T: TryFromExpr>(&mut self, f: &mut Feedback) -> Option<T> {
- while !self.0.is_empty() {
- let item = self.0.remove(0);
- if let Some(val) = T::try_from_expr(item.as_ref(), f) {
- return Some(val);
- }
- }
- None
- }
-
- /// Extract the first argument of the value type if there is any.
- pub fn get<T: TryFromExpr>(&mut self) -> Option<T> {
- for (i, item) in self.0.iter().enumerate() {
- let expr = item.as_ref();
- if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
- self.0.remove(i);
- return Some(val);
- }
- }
- None
- }
-
- /// Extract all arguments of the value type.
- pub fn all<'a, T: TryFromExpr>(&'a mut self) -> impl Iterator<Item = T> + 'a {
- let mut i = 0;
- std::iter::from_fn(move || {
- while i < self.0.len() {
- let expr = self.0[i].as_ref();
- let val = T::try_from_expr(expr, &mut Feedback::new());
- if val.is_some() {
- self.0.remove(i);
- return val;
- } else {
- i += 1;
- }
- }
- None
- })
- }
-}
-
-impl Debug for Tuple {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_list().entries(&self.0).finish()
- }
-}
-
-/// A named, untyped sequence of expressions.
-///
-/// # Example
-/// ```typst
-/// hsl(93, 10, 19.4)
-/// ```
-#[derive(Debug, Clone, PartialEq)]
-pub struct NamedTuple {
- /// The name of the tuple.
- pub name: Spanned<Ident>,
- /// The elements of the tuple.
- pub tuple: Spanned<Tuple>,
-}
-
-impl NamedTuple {
- /// Create a named tuple from a name and a tuple.
- pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> Self {
- Self { name, tuple }
- }
-}
-
-impl Deref for NamedTuple {
- type Target = Tuple;
-
- fn deref(&self) -> &Self::Target {
- &self.tuple.v
- }
-}
-
-/// A key-value collection of identifiers and associated expressions.
-///
-/// # Example
-/// ```typst
-/// { fit = false, width = 12cm, items = (1, 2, 3) }
-/// ```
-#[derive(Default, Clone, PartialEq)]
-pub struct Object(pub SpanVec<Pair>);
-
-/// A key-value pair in an object.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Pair {
- pub key: Spanned<Ident>,
- pub value: Spanned<Expr>,
-}
-
-impl Object {
- /// Create an empty object.
- pub fn new() -> Self {
- Self(vec![])
- }
-
- /// Add a pair to object.
- pub fn push(&mut self, pair: Spanned<Pair>) {
- self.0.push(pair);
- }
-
- /// Extract an argument with the given key if there is any.
- ///
- /// Generates an error if there is a matching key, but the value is of the
- /// wrong type.
- pub fn get<T: TryFromExpr>(&mut self, key: &str, f: &mut Feedback) -> Option<T> {
- for (i, pair) in self.0.iter().enumerate() {
- if pair.v.key.v.as_str() == key {
- let pair = self.0.remove(i);
- return T::try_from_expr(pair.v.value.as_ref(), f);
- }
- }
- None
- }
-
- /// Extract all key-value pairs where the value is of the given type.
- pub fn all<'a, T: TryFromExpr>(&'a mut self)
- -> impl Iterator<Item = (Spanned<Ident>, T)> + 'a
- {
- let mut i = 0;
- std::iter::from_fn(move || {
- while i < self.0.len() {
- let expr = self.0[i].v.value.as_ref();
- let val = T::try_from_expr(expr, &mut Feedback::new());
- if let Some(val) = val {
- let pair = self.0.remove(i);
- return Some((pair.v.key, val));
- } else {
- i += 1;
- }
- }
- None
- })
- }
-}
-
-impl Debug for Object {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_map()
- .entries(self.0.iter().map(|p| (&p.v.key.v, &p.v.value.v)))
- .finish()
- }
-}
-
/// A table expression.
///
/// # Example
@@ -407,14 +235,6 @@ impl Debug for Object {
/// ```
pub type TableExpr = Table<TableExprEntry>;
-/// An entry in a table expression.
-///
-/// Contains the key's span and the value.
-pub struct TableExprEntry {
- pub key: Span,
- pub val: Spanned<Expr>,
-}
-
impl TableExpr {
/// Retrieve and remove the matching value with the lowest number key,
/// skipping and ignoring all non-matching entries with lower keys.
@@ -446,10 +266,10 @@ impl TableExpr {
/// there is any.
///
/// Generates an error if the key exists but the value does not match.
- pub fn take_with_key<'a, T, K>(&mut self, key: K, f: &mut Feedback) -> Option<T>
+ pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option<T>
where
- T: TryFromExpr,
K: Into<BorrowedKey<'a>>,
+ T: TryFromExpr,
{
self.remove(key).and_then(|entry| {
let expr = entry.val.as_ref();
@@ -480,6 +300,18 @@ impl TableExpr {
})
}
+
+ /// Retrieve and remove all matching values with number keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The values are returned in order of increasing keys.
+ pub fn take_all_num_vals<'a, T: 'a>(&'a mut self) -> impl Iterator<Item = T> + 'a
+ where
+ T: TryFromExpr,
+ {
+ self.take_all_num::<T>().map(|(_, v)| v)
+ }
+
/// Retrieve and remove all matching pairs with string keys, skipping and
/// ignoring non-matching entries.
///
@@ -513,6 +345,39 @@ impl TableExpr {
}
}
+/// An entry in a table expression.
+///
+/// Contains the key's span and the value.
+#[derive(Clone, PartialEq)]
+pub struct TableExprEntry {
+ pub key: Span,
+ pub val: Spanned<Expr>,
+}
+
+impl TableExprEntry {
+ /// Create a new entry.
+ pub fn new(key: Span, val: Spanned<Expr>) -> Self {
+ Self { key, val }
+ }
+
+ /// Create an entry for a positional argument with the same span for key and
+ /// value.
+ pub fn val(val: Spanned<Expr>) -> Self {
+ Self { key: Span::ZERO, val }
+ }
+}
+
+impl Debug for TableExprEntry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if f.alternate() {
+ f.write_str("key")?;
+ self.key.fmt(f)?;
+ f.write_str(" ")?;
+ }
+ self.val.fmt(f)
+ }
+}
+
/// A trait for converting expressions into specific types.
pub trait TryFromExpr: Sized {
// This trait takes references because we don't want to move the expression
@@ -583,8 +448,7 @@ impl_match!(bool, "bool", Expr::Bool(b) => b.clone());
impl_match!(f64, "number", Expr::Number(n) => n.clone());
impl_match!(Length, "length", Expr::Length(l) => l.clone());
impl_match!(SyntaxTree, "tree", Expr::Tree(t) => t.clone());
-impl_match!(Tuple, "tuple", Expr::Tuple(t) => t.clone());
-impl_match!(Object, "object", Expr::Object(o) => o.clone());
+impl_match!(TableExpr, "table", Expr::Table(t) => t.clone());
impl_match!(ScaleLength, "number or length",
&Expr::Length(length) => ScaleLength::Absolute(length),
&Expr::Number(scale) => ScaleLength::Scaled(scale),
@@ -701,6 +565,22 @@ impl TryFromExpr for FontWidth {
mod tests {
use super::*;
+ #[test]
+ fn parse_color_strings() {
+ fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
+ assert_eq!(
+ RgbaColor::from_str(hex),
+ Ok(RgbaColor::new(r, g, b, a)),
+ );
+ }
+
+ test("f61243ff", 0xf6, 0x12, 0x43, 0xff);
+ test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff);
+ test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad);
+ test("233", 0x22, 0x33, 0x33, 0xff);
+ test("111b", 0x11, 0x11, 0x11, 0xbb);
+ }
+
fn entry(expr: Expr) -> TableExprEntry {
TableExprEntry {
key: Span::ZERO,
@@ -737,8 +617,8 @@ mod tests {
let mut table = TableExpr::new();
table.insert(1, entry(Expr::Bool(false)));
table.insert("hi", entry(Expr::Bool(true)));
- assert_eq!(table.take_with_key::<bool, _>(1, &mut f), Some(false));
- assert_eq!(table.take_with_key::<f64, _>("hi", &mut f), None);
+ assert_eq!(table.take_with_key::<_, bool>(1, &mut f), Some(false));
+ assert_eq!(table.take_with_key::<_, f64>("hi", &mut f), None);
assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]);
assert!(table.is_empty());
}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index fe0bf6b5..fdc105a3 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -1,9 +1,5 @@
//! Syntax trees, parsing and tokenization.
-#[cfg(test)]
-#[macro_use]
-mod test;
-
pub mod decoration;
pub mod expr;
pub mod parsing;
@@ -11,3 +7,60 @@ pub mod scope;
pub mod span;
pub mod tokens;
pub mod tree;
+
+#[cfg(test)]
+mod tests {
+ use std::fmt::Debug;
+ use crate::func::prelude::*;
+ use super::span;
+
+ /// Assert that expected and found are equal, printing both and panicking
+ /// and the source of their test case if they aren't.
+ ///
+ /// When `cmp_spans` is false, spans are ignored.
+ pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
+ where
+ T: Debug + PartialEq,
+ {
+ span::set_cmp(cmp_spans);
+ let equal = exp == found;
+ span::set_cmp(true);
+
+ if !equal {
+ println!("source: {:?}", src);
+ if cmp_spans {
+ println!("expected: {:#?}", exp);
+ println!("found: {:#?}", found);
+ } else {
+ println!("expected: {:?}", exp);
+ println!("found: {:?}", found);
+ }
+ panic!("test failed");
+ }
+ }
+
+ pub fn s<T>(sl: usize, sc: usize, el: usize, ec: usize, v: T) -> Spanned<T> {
+ Spanned::new(v, Span::new(Pos::new(sl, sc), Pos::new(el, ec)))
+ }
+
+ // Enables tests to optionally specify spans.
+ impl<T> From<T> for Spanned<T> {
+ fn from(t: T) -> Self {
+ Spanned::zero(t)
+ }
+ }
+
+ pub fn debug_func(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+ Pass::node(DebugNode(call), Feedback::new())
+ }
+
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct DebugNode(pub FuncCall);
+
+ #[async_trait(?Send)]
+ impl Layout for DebugNode {
+ async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
+ unimplemented!()
+ }
+ }
+}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 2980cce2..88c930cb 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -17,39 +17,7 @@ pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<SyntaxNode>;
#[derive(Debug, Clone, PartialEq)]
pub struct FuncCall {
pub name: Spanned<Ident>,
- pub args: FuncArgs,
-}
-
-/// The positional and keyword arguments passed to a function.
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct FuncArgs {
- pub pos: Tuple,
- pub key: Object,
-}
-
-impl FuncArgs {
- /// Create new empty function arguments.
- pub fn new() -> Self {
- Self {
- pos: Tuple::new(),
- key: Object::new(),
- }
- }
-
- /// Add an argument.
- pub fn push(&mut self, arg: Spanned<FuncArg>) {
- match arg.v {
- FuncArg::Pos(item) => self.pos.push(Spanned::new(item, arg.span)),
- FuncArg::Key(pair) => self.key.push(Spanned::new(pair, arg.span)),
- }
- }
-}
-
-/// Either a positional or keyword argument.
-#[derive(Debug, Clone, PartialEq)]
-pub enum FuncArg {
- Pos(Expr),
- Key(Pair),
+ pub args: TableExpr,
}
/// The state which can influence how a string of source code is parsed.
@@ -155,7 +123,7 @@ impl<'s> FuncParser<'s> {
}
fn parse(mut self) -> Pass<SyntaxNode> {
- let (parser, mut call) = if let Some(call) = self.parse_func_call() {
+ let (parser, mut call) = if let Some(call) = self.parse_func_header() {
let name = call.name.v.as_str();
let (parser, deco) = match self.state.scope.get_parser(name) {
// The function exists in the scope.
@@ -180,19 +148,20 @@ impl<'s> FuncParser<'s> {
let parser = self.state.scope.get_fallback_parser();
let call = FuncCall {
name: Spanned::new(Ident(String::new()), Span::ZERO),
- args: FuncArgs::new(),
+ args: TableExpr::new(),
};
(parser, call)
};
if let Some(body) = self.body {
- let tree = body.map(|src| {
- let parsed = parse(src, body.span.start, &self.state);
- self.feedback.extend(parsed.feedback);
- Expr::Tree(parsed.output)
+ call.args.push(TableExprEntry {
+ key: Span::ZERO,
+ val: body.map(|src| {
+ let parsed = parse(src, body.span.start, &self.state);
+ self.feedback.extend(parsed.feedback);
+ Expr::Tree(parsed.output)
+ }),
});
-
- call.args.pos.push(tree);
}
let parsed = parser(call, self.state);
@@ -201,7 +170,7 @@ impl<'s> FuncParser<'s> {
Pass::new(parsed.output, self.feedback)
}
- fn parse_func_call(&mut self) -> Option<FuncCall> {
+ fn parse_func_header(&mut self) -> Option<FuncCall> {
let after_bracket = self.pos();
self.skip_white();
@@ -212,71 +181,16 @@ impl<'s> FuncParser<'s> {
self.skip_white();
let args = match self.eat().map(Spanned::value) {
- Some(Token::Colon) => self.parse_func_args(),
+ Some(Token::Colon) => self.parse_table(false).0.v,
Some(_) => {
self.expected_at("colon", name.span.end);
- FuncArgs::new()
+ TableExpr::new()
}
- None => FuncArgs::new(),
+ None => TableExpr::new(),
};
Some(FuncCall { name, args })
}
-
- fn parse_func_args(&mut self) -> FuncArgs {
- let mut args = FuncArgs::new();
- loop {
- self.skip_white();
- if self.eof() {
- break;
- }
-
- let arg = if let Some(ident) = self.parse_ident() {
- self.skip_white();
-
- // This could be a keyword argument, or a positional argument of
- // type named tuple or identifier.
- if self.check_eat(Token::Equals).is_some() {
- self.skip_white();
-
- let key = ident;
- self.feedback.decorations
- .push(Spanned::new(Decoration::ArgumentKey, key.span));
-
- let value = try_opt_or!(self.parse_expr(), {
- self.expected("value");
- continue;
- });
-
- let span = Span::merge(key.span, value.span);
- let arg = FuncArg::Key(Pair { key, value });
- Spanned::new(arg, span)
- } else if self.check(Token::LeftParen) {
- let tuple = self.parse_named_tuple(ident);
- tuple.map(|tup| FuncArg::Pos(Expr::NamedTuple(tup)))
- } else {
- ident.map(|id| FuncArg::Pos(Expr::Ident(id)))
- }
- } else {
- // It's a positional argument.
- try_opt_or!(self.parse_expr(), {
- self.expected("argument");
- continue;
- }).map(|expr| FuncArg::Pos(expr))
- };
-
- let behind_arg = arg.span.end;
- args.push(arg);
-
- self.skip_white();
- if self.eof() {
- break;
- }
-
- self.expect_at(Token::Comma, behind_arg);
- }
- args
- }
}
// Parsing expressions and values
@@ -343,9 +257,9 @@ impl FuncParser<'_> {
fn parse_factor(&mut self) -> Option<Spanned<Expr>> {
if let Some(hyph) = self.check_eat(Token::Hyphen) {
self.skip_white();
- if let Some(value) = self.parse_value() {
- let span = Span::merge(hyph.span, value.span);
- Some(Spanned::new(Expr::Neg(Box::new(value)), span))
+ if let Some(factor) = self.parse_factor() {
+ let span = Span::merge(hyph.span, factor.span);
+ Some(Spanned::new(Expr::Neg(Box::new(factor)), span))
} else {
error!(@self.feedback, hyph.span, "dangling minus");
None
@@ -358,13 +272,13 @@ impl FuncParser<'_> {
fn parse_value(&mut self) -> Option<Spanned<Expr>> {
let Spanned { v: token, span } = self.peek()?;
match token {
- // This could be a named tuple or an identifier.
+ // This could be a function call or an identifier.
Token::Ident(id) => {
let name = Spanned::new(Ident(id.to_string()), span);
self.eat();
self.skip_white();
Some(if self.check(Token::LeftParen) {
- self.parse_named_tuple(name).map(|tup| Expr::NamedTuple(tup))
+ self.parse_func_call(name).map(|call| Expr::Call(call))
} else {
name.map(|id| Expr::Ident(id))
})
@@ -391,118 +305,111 @@ impl FuncParser<'_> {
}
}
- // This could be a tuple or a parenthesized expression. We parse as
- // a tuple in any case and coerce the tuple into a value if it is
+ // This could be a table or a parenthesized expression. We parse as
+ // a table in any case and coerce the table into a value if it is
// coercable (length 1 and no trailing comma).
Token::LeftParen => {
- let (tuple, coercable) = self.parse_tuple();
+ let (table, coercable) = self.parse_table(true);
Some(if coercable {
- tuple.map(|v| {
- v.0.into_iter().next().expect("tuple is coercable").v
+ table.map(|v| {
+ v.into_values()
+ .next()
+ .expect("table is coercable").val.v
})
} else {
- tuple.map(|tup| Expr::Tuple(tup))
+ table.map(|tab| Expr::Table(tab))
})
}
- Token::LeftBrace => {
- Some(self.parse_object().map(|obj| Expr::Object(obj)))
- }
_ => None,
}
}
- fn parse_named_tuple(&mut self, name: Spanned<Ident>) -> Spanned<NamedTuple> {
- let tuple = self.parse_tuple().0;
- let span = Span::merge(name.span, tuple.span);
- Spanned::new(NamedTuple::new(name, tuple), span)
+ fn parse_func_call(&mut self, name: Spanned<Ident>) -> Spanned<FuncCall> {
+ let args = self.parse_table(true).0;
+ let span = Span::merge(name.span, args.span);
+ Spanned::new(FuncCall { name, args: args.v }, span)
}
- /// The boolean tells you whether the tuple can be coerced into a value
+ /// The boolean tells you whether the table can be coerced into an expression
/// (this is the case when it's length 1 and has no trailing comma).
- fn parse_tuple(&mut self) -> (Spanned<Tuple>, bool) {
+ fn parse_table(&mut self, parens: bool) -> (Spanned<TableExpr>, bool) {
let start = self.pos();
- self.assert(Token::LeftParen);
+ if parens {
+ self.assert(Token::LeftParen);
+ }
+
+ let mut table = TableExpr::new();
+ let mut coercable = true;
- let mut tuple = Tuple::new();
- let mut commaless = true;
loop {
self.skip_white();
- if self.eof() || self.check(Token::RightParen) {
+ if self.eof() || (parens && self.check(Token::RightParen)) {
break;
}
- let expr = try_opt_or!(self.parse_expr(), {
- self.expected("value");
- continue;
- });
-
- let behind_expr = expr.span.end;
- tuple.push(expr);
+ let behind_arg;
- self.skip_white();
- if self.eof() || self.check(Token::RightParen) {
- break;
- }
+ if let Some(ident) = self.parse_ident() {
+ // This could be a keyword argument, a function call or a simple
+ // identifier.
+ self.skip_white();
- self.expect_at(Token::Comma, behind_expr);
- commaless = false;
- }
+ if self.check_eat(Token::Equals).is_some() {
+ self.skip_white();
- self.expect(Token::RightParen);
- let end = self.pos();
- let coercable = commaless && !tuple.0.is_empty();
+ let key = ident;
+ self.feedback.decorations
+ .push(Spanned::new(Decoration::TableKey, key.span));
- (Spanned::new(tuple, Span::new(start, end)), coercable)
- }
+ let val = try_opt_or!(self.parse_expr(), {
+ self.expected("value");
+ continue;
+ });
- fn parse_object(&mut self) -> Spanned<Object> {
- let start = self.pos();
- self.assert(Token::LeftBrace);
+ coercable = false;
+ behind_arg = val.span.end;
+ table.insert(key.v.0, TableExprEntry::new(key.span, val));
- let mut object = Object::new();
- loop {
- self.skip_white();
- if self.eof() || self.check(Token::RightBrace) {
- break;
- }
+ } else if self.check(Token::LeftParen) {
+ let call = self.parse_func_call(ident);
+ let expr = call.map(|call| Expr::Call(call));
- let key = try_opt_or!(self.parse_ident(), {
- self.expected("key");
- continue;
- });
+ behind_arg = expr.span.end;
+ table.push(TableExprEntry::val(expr));
+ } else {
+ let expr = ident.map(|id| Expr::Ident(id));
- let after_key = self.pos();
- self.skip_white();
- if !self.expect_at(Token::Equals, after_key) {
- continue;
+ behind_arg = expr.span.end;
+ table.push(TableExprEntry::val(expr));
+ }
+ } else {
+ // It's a positional argument.
+ let expr = try_opt_or!(self.parse_expr(), {
+ self.expected("value");
+ continue;
+ });
+ behind_arg = expr.span.end;
+ table.push(TableExprEntry::val(expr));
}
- self.feedback.decorations
- .push(Spanned::new(Decoration::ObjectKey, key.span));
-
self.skip_white();
- let value = try_opt_or!(self.parse_expr(), {
- self.expected("value");
- continue;
- });
-
- let behind_value = value.span.end;
- let span = Span::merge(key.span, value.span);
- object.push(Spanned::new(Pair { key, value }, span));
-
- self.skip_white();
- if self.eof() || self.check(Token::RightBrace) {
+ if self.eof() || (parens && self.check(Token::RightParen)) {
break;
}
- self.expect_at(Token::Comma, behind_value);
+ self.expect_at(Token::Comma, behind_arg);
+ coercable = false;
}
- self.expect(Token::RightBrace);
- let end = self.pos();
+ if parens {
+ self.expect(Token::RightParen);
+ }
+
+ coercable = coercable && !table.is_empty();
- Spanned::new(object, Span::new(start, end))
+ let end = self.pos();
+ (Spanned::new(table, Span::new(start, end)), coercable)
}
}
@@ -659,151 +566,153 @@ fn unescape_raw(raw: &str) -> Vec<String> {
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
+ use crate::syntax::tests::*;
use crate::length::Length;
- use crate::syntax::span::SpanVec;
- use crate::syntax::test::{check, debug_func, DebugNode};
use super::*;
-
use Decoration::*;
- use Expr::{Bool, Length as Len, Number as Num};
+
+ // ----------------------- Construct Syntax Nodes ----------------------- //
+
use SyntaxNode::{
- Spacing as S, Linebreak, ToggleItalic as Italic, ToggleBolder as Bold,
+ Spacing as S,
+ Linebreak as L,
+ ToggleItalic as I,
+ ToggleBolder as B,
};
- /// Test whether the given string parses into
- /// - the given SyntaxNode list (required).
- /// - the given error list (optional, if omitted checks against empty list).
- /// - the given decoration list (optional, if omitted it is not tested).
- macro_rules! p {
- ($source:expr => [$($tree:tt)*]) => {
- p!($source => [$($tree)*], []);
+ fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) }
+
+ macro_rules! R {
+ ($($line:expr),* $(,)?) => {
+ SyntaxNode::Raw(vec![$($line.to_string()),*])
};
+ }
- ($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
- let mut scope = Scope::new(Box::new(debug_func));
- scope.insert("f", Box::new(debug_func));
- scope.insert("n", Box::new(debug_func));
- scope.insert("box", Box::new(debug_func));
- scope.insert("val", Box::new(debug_func));
+ macro_rules! P {
+ ($($tts:tt)*) => { SyntaxNode::Par(Tree![@$($tts)*]) };
+ }
- let state = ParseState { scope };
- let pass = parse($source, Pos::ZERO, &state);
+ macro_rules! F {
+ ($($tts:tt)*) => { SyntaxNode::boxed(DebugNode(Call!(@$($tts)*))) }
+ }
- // Test tree.
- let (exp, cmp) = span_vec![$($tree)*];
- check($source, exp, pass.output, cmp);
+ // ------------------------ Construct Expressions ----------------------- //
- // Test diagnostics.
- let (exp, cmp) = span_vec![$($diagnostics)*];
- let exp = exp.into_iter()
- .map(|s: Spanned<&str>| s.map(|e| e.to_string()))
- .collect::<Vec<_>>();
- let found = pass.feedback.diagnostics.into_iter()
- .map(|s| s.map(|e| e.message))
- .collect::<Vec<_>>();
- check($source, exp, found, cmp);
+ use Expr::{Bool, Number as Num, Length as Len, Color};
+
+ fn Id(ident: &str) -> Expr { Expr::Ident(Ident(ident.to_string())) }
+ fn Str(string: &str) -> Expr { Expr::Str(string.to_string()) }
- // Test decos.
- $(let (exp, cmp) = span_vec![$($decos)*];
- check($source, exp, pass.feedback.decorations, cmp);)?
+ macro_rules! Tree {
+ (@$($node:expr),* $(,)?) => {
+ vec![$(Into::<Spanned<SyntaxNode>>::into($node)),*]
};
+ ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) };
}
- /// Shorthand for `p!("[val: ...]" => par![func!("val", ...)])`.
- macro_rules! pval {
- ($header:expr => $($tts:tt)*) => {
- p!(concat!("[val: ", $header, "]") => [par![func!("val": $($tts)*)]]);
- }
+ macro_rules! Table {
+ (@table=$table:expr,) => {};
+ (@table=$table:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{
+ let key = Into::<Spanned<&str>>::into($key);
+ let val = Into::<Spanned<Expr>>::into($value);
+ $table.insert(key.v, TableExprEntry::new(key.span, val));
+ Table![@table=$table, $($($tts)*)?];
+ }};
+ (@table=$table:expr, $value:expr $(, $($tts:tt)*)?) => {
+ let val = Into::<Spanned<Expr>>::into($value);
+ $table.push(TableExprEntry::val(val));
+ Table![@table=$table, $($($tts)*)?];
+ };
+ (@$($tts:tt)*) => {{
+ #[allow(unused_mut)]
+ let mut table = TableExpr::new();
+ Table![@table=table, $($tts)*];
+ table
+ }};
+ ($($tts:tt)*) => { Expr::Table(Table![@$($tts)*]) };
}
- fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
- fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
- fn Color(r: u8, g: u8, b: u8, a: u8) -> Expr { Expr::Color(RgbaColor::new(r, g, b, a)) }
- fn ColorStr(color: &str) -> Expr { Expr::Color(RgbaColor::from_str(color).expect("invalid test color")) }
- fn Neg(e1: Expr) -> Expr { Expr::Neg(Box::new(Z(e1))) }
- fn Add(e1: Expr, e2: Expr) -> Expr { Expr::Add(Box::new(Z(e1)), Box::new(Z(e2))) }
- fn Sub(e1: Expr, e2: Expr) -> Expr { Expr::Sub(Box::new(Z(e1)), Box::new(Z(e2))) }
- fn Mul(e1: Expr, e2: Expr) -> Expr { Expr::Mul(Box::new(Z(e1)), Box::new(Z(e2))) }
- fn Div(e1: Expr, e2: Expr) -> Expr { Expr::Div(Box::new(Z(e1)), Box::new(Z(e2))) }
- fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) }
- fn Z<T>(v: T) -> Spanned<T> { Spanned::zero(v) }
+ fn Neg<T: Into<Spanned<Expr>>>(e1: T) -> Expr {
+ Expr::Neg(Box::new(e1.into()))
+ }
+ fn Add<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
+ Expr::Add(Box::new(e1.into()), Box::new(e2.into()))
+ }
+ fn Sub<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
+ Expr::Sub(Box::new(e1.into()), Box::new(e2.into()))
+ }
+ fn Mul<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
+ Expr::Mul(Box::new(e1.into()), Box::new(e2.into()))
+ }
+ fn Div<T: Into<Spanned<Expr>>>(e1: T, e2: T) -> Expr {
+ Expr::Div(Box::new(e1.into()), Box::new(e2.into()))
+ }
- macro_rules! tuple {
- ($($tts:tt)*) => {
- Expr::Tuple(Tuple(span_vec![$($tts)*].0))
- };
+ macro_rules! Call {
+ (@$name:expr $(; $($tts:tt)*)?) => {{
+ let name = Into::<Spanned<&str>>::into($name);
+ FuncCall {
+ name: name.map(|n| Ident(n.to_string())),
+ args: Table![@$($($tts)*)?],
+ }
+ }};
+ ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) };
}
- macro_rules! named_tuple {
- ($name:tt $(, $($tts:tt)*)?) => {
- Expr::NamedTuple(NamedTuple::new(
- span_item!($name).map(|n| Ident(n.to_string())),
- Z(Tuple(span_vec![$($($tts)*)?].0))
- ))
+ // ----------------------------- Test Macros ---------------------------- //
+
+ // Test syntax trees with or without spans.
+ macro_rules! t { ($($tts:tt)*) => {test!(@spans=false, $($tts)*)} }
+ macro_rules! ts { ($($tts:tt)*) => {test!(@spans=true, $($tts)*)} }
+ macro_rules! test {
+ (@spans=$spans:expr, $src:expr => $($tts:tt)*) => {
+ let exp = Tree![@$($tts)*];
+ let pass = parse_default($src);
+ check($src, exp, pass.output, $spans);
};
}
- macro_rules! object {
- ($($key:tt => $value:expr),* $(,)?) => {
- Expr::Object(Object(vec![$(Z(Pair {
- key: span_item!($key).map(|k| Ident(k.to_string())),
- value: Z($value),
- })),*]))
- };
+ // Test expressions.
+ macro_rules! v {
+ ($src:expr => $($tts:tt)*) => {
+ t!(concat!("[val: ", $src, "]") => P![F!("val"; $($tts)*)]);
+ }
}
- macro_rules! raw {
- ($($line:expr),* $(,)?) => {
- SyntaxNode::Raw(vec![$($line.to_string()),*])
+ // Test error messages.
+ macro_rules! e {
+ ($src:expr => $($tts:tt)*) => {
+ let exp = vec![$($tts)*];
+ let pass = parse_default($src);
+ let found = pass.feedback.diagnostics.iter()
+ .map(|s| s.as_ref().map(|e| e.message.as_str()))
+ .collect::<Vec<_>>();
+ check($src, exp, found, true);
};
}
- macro_rules! par {
- ($($tts:tt)*) => {
- SyntaxNode::Par(span_vec![$($tts)*].0)
+ // Test decorations.
+ macro_rules! d {
+ ($src:expr => $($tts:tt)*) => {
+ let exp = vec![$($tts)*];
+ let pass = parse_default($src);
+ check($src, exp, pass.feedback.decorations, true);
};
}
- macro_rules! func {
- ($name:tt
- $(: ($($pos:tt)*) $(, { $($key:tt => $value:expr),* })? )?
- $(; $($body:tt)*)?
- ) => {{
- #[allow(unused_mut)]
- let mut args = FuncArgs::new();
- $(
- let items: SpanVec<Expr> = span_vec![$($pos)*].0;
- for item in items {
- args.push(item.map(|v| FuncArg::Pos(v)));
- }
- $($(args.push(Z(FuncArg::Key(Pair {
- key: span_item!($key).map(|k| Ident(k.to_string())),
- value: Z($value),
- })));)*)?
- )?
- SyntaxNode::boxed(DebugNode(
- FuncCall {
- name: span_item!($name).map(|s| Ident(s.to_string())),
- args,
- },
- func!(@body $($($body)*)?),
- ))
- }};
- (@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) };
- (@body) => { None };
+ fn parse_default(src: &str) -> Pass<SyntaxTree> {
+ let mut scope = Scope::new(Box::new(debug_func));
+ scope.insert("box", Box::new(debug_func));
+ scope.insert("val", Box::new(debug_func));
+ scope.insert("f", Box::new(debug_func));
+ let state = ParseState { scope };
+ parse(src, Pos::ZERO, &state)
}
- #[test]
- fn parse_color_strings() {
- assert_eq!(Color(0xf6, 0x12, 0x43, 0xff), ColorStr("f61243ff"));
- assert_eq!(Color(0xb3, 0xd8, 0xb3, 0xff), ColorStr("b3d8b3"));
- assert_eq!(Color(0xfc, 0xd2, 0xa9, 0xad), ColorStr("fCd2a9AD"));
- assert_eq!(Color(0x22, 0x33, 0x33, 0xff), ColorStr("233"));
- assert_eq!(Color(0x11, 0x11, 0x11, 0xbb), ColorStr("111b"));
- }
+ // -------------------------------- Tests ------------------------------- //
#[test]
- fn unescape_strings() {
+ fn test_unescape_strings() {
fn test(string: &str, expected: &str) {
assert_eq!(unescape_string(string), expected.to_string());
}
@@ -820,489 +729,299 @@ mod tests {
}
#[test]
- fn unescape_raws() {
- fn test(raw: &str, expected: SyntaxNode) {
- let vec = if let SyntaxNode::Raw(v) = expected { v } else { panic!() };
- assert_eq!(unescape_raw(raw), vec);
+ fn test_unescape_raws() {
+ fn test(raw: &str, expected: Vec<&str>) {
+ assert_eq!(unescape_raw(raw), expected);
}
- test("raw\\`", raw!["raw`"]);
- test("raw\ntext", raw!["raw", "text"]);
- test("a\r\nb", raw!["a", "b"]);
- test("a\n\nb", raw!["a", "", "b"]);
- test("a\r\x0Bb", raw!["a", "", "b"]);
- test("a\r\n\r\nb", raw!["a", "", "b"]);
- test("raw\\a", raw!["raw\\a"]);
- test("raw\\", raw!["raw\\"]);
+ test("raw\\`", vec!["raw`"]);
+ test("raw\ntext", vec!["raw", "text"]);
+ test("a\r\nb", vec!["a", "b"]);
+ test("a\n\nb", vec!["a", "", "b"]);
+ test("a\r\x0Bb", vec!["a", "", "b"]);
+ test("a\r\n\r\nb", vec!["a", "", "b"]);
+ test("raw\\a", vec!["raw\\a"]);
+ test("raw\\", vec!["raw\\"]);
}
#[test]
- fn parse_basic_nodes() {
- // Basic nodes.
- p!("" => []);
- p!("hi" => [par![T("hi")]]);
- p!("*hi" => [par![Bold, T("hi")]]);
- p!("hi_" => [par![T("hi"), Italic]]);
- p!("hi you" => [par![T("hi"), S, T("you")]]);
- p!("hi// you\nw" => [par![T("hi"), S, T("w")]]);
- p!("\n\n\nhello" => [par![T("hello")]]);
- p!("first//\n//\nsecond" => [par![T("first"), S, S, T("second")]]);
- p!("first//\n \nsecond" => [par![T("first")], par![T("second")]]);
- p!("first/*\n \n*/second" => [par![T("first"), T("second")]]);
- p!(r"a\ b" => [par![T("a"), Linebreak, S, T("b")]]);
- p!("💜\n\n 🌍" => [par![T("💜")], par![T("🌍")]]);
-
- // Raw markup.
- p!("`py`" => [par![raw!["py"]]]);
- p!("[val][`hi]`]" => [par![func!("val"; [par![raw!["hi]"]]])]]);
- p!("`hi\nyou" => [par![raw!["hi", "you"]]], [(1:3, 1:3, "expected backtick")]);
- p!("`hi\\`du`" => [par![raw!["hi`du"]]]);
-
- // Spanned SyntaxNodes.
- p!("Hi" => [(0:0, 0:2, par![(0:0, 0:2, T("Hi"))])]);
- p!("*Hi*" => [(0:0, 0:4, par![(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)])]);
- p!("🌎\n*/[n]" =>
- [(0:0, 1:5, par![(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "n")))])],
- [(1:0, 1:2, "unexpected end of block comment")],
- [(1:3, 1:4, ResolvedFunc)],
+ fn test_parse_simple_nodes() {
+ t!("" => );
+ t!("hi" => P![T("hi")]);
+ t!("*hi" => P![B, T("hi")]);
+ t!("hi_" => P![T("hi"), I]);
+ t!("hi you" => P![T("hi"), S, T("you")]);
+ t!("\n\n\nhello" => P![T("hello")]);
+ t!(r"a\ b" => P![T("a"), L, S, T("b")]);
+ t!("`py`" => P![R!["py"]]);
+ t!("`hi\nyou" => P![R!["hi", "you"]]);
+ e!("`hi\nyou" => s(1,3, 1,3, "expected backtick"));
+ t!("`hi\\`du`" => P![R!["hi`du"]]);
+ t!("💜\n\n 🌍" => P![T("💜")], P![T("🌍")]);
+
+ ts!("hi" => s(0,0, 0,2, P![s(0,0, 0,2, T("hi"))]));
+ ts!("*Hi*" => s(0,0, 0,4, P![
+ s(0,0, 0,1, B), s(0,1, 0,3, T("Hi")), s(0,3, 0,4, B),
+ ]));
+ ts!("💜\n\n 🌍" =>
+ s(0,0, 0,1, P![s(0,0, 0,1, T("💜"))]),
+ s(2,1, 2,2, P![s(2,1, 2,2, T("🌍"))]),
);
}
#[test]
- fn parse_function_names() {
+ fn test_parse_comments() {
+ // In body.
+ t!("hi// you\nw" => P![T("hi"), S, T("w")]);
+ t!("first//\n//\nsecond" => P![T("first"), S, S, T("second")]);
+ t!("first//\n \nsecond" => P![T("first")], P![T("second")]);
+ t!("first/*\n \n*/second" => P![T("first"), T("second")]);
+ e!("🌎\n*/n" => s(1,0, 1,2, "unexpected end of block comment"));
+
+ // In header.
+ t!("[val:/*12pt*/]" => P![F!("val")]);
+ t!("[val \n /* \n */:]" => P![F!("val")]);
+ e!("[val \n /* \n */:]" => );
+ e!("[val : 12, /* \n */ 14]" => );
+ }
+
+ #[test]
+ fn test_parse_function_names() {
// No closing bracket.
- p!("[" => [par![func!("")]], [
- (0:1, 0:1, "expected function name"),
- (0:1, 0:1, "expected closing bracket")
- ]);
+ t!("[" => P![F!("")]);
+ e!("[" => s(0,1, 0,1, "expected function name"),
+ s(0,1, 0,1, "expected closing bracket"));
// No name.
- p!("[]" => [par![func!("")]], [(0:1, 0:1, "expected function name")]);
- p!("[\"]" => [par![func!("")]], [
- (0:1, 0:3, "expected function name, found string"),
- (0:3, 0:3, "expected closing bracket"),
- ]);
+ e!("[]" => s(0,1, 0,1, "expected function name"));
+ e!("[\"]" => s(0,1, 0,3, "expected function name, found string"),
+ s(0,3, 0,3, "expected closing bracket"));
// An unknown name.
- p!("[hi]" =>
- [par![func!("hi")]],
- [(0:1, 0:3, "unknown function")],
- [(0:1, 0:3, UnresolvedFunc)],
- );
+ t!("[hi]" => P![F!("hi")]);
+ e!("[hi]" => s(0,1, 0,3, "unknown function"));
+ d!("[hi]" => s(0,1, 0,3, UnresolvedFunc));
// A valid name.
- p!("[f]" => [par![func!("f")]], [], [(0:1, 0:2, ResolvedFunc)]);
- p!("[ f]" => [par![func!("f")]], [], [(0:3, 0:4, ResolvedFunc)]);
+ t!("[f]" => P![F!("f")]);
+ t!("[ f]" => P![F!("f")]);
+ d!("[ f]" => s(0,3, 0,4, ResolvedFunc));
- // An invalid token for a name.
- p!("[12]" => [par![func!("")]], [(0:1, 0:3, "expected function name, found number")], []);
- p!("[🌎]" => [par![func!("")]], [(0:1, 0:2, "expected function name, found invalid token")], []);
- p!("[ 🌎]" => [par![func!("")]], [(0:3, 0:4, "expected function name, found invalid token")], []);
+ // An invalid name.
+ e!("[12]" => s(0,1, 0,3, "expected function name, found number"));
+ e!("[ 🌎]" => s(0,3, 0,4, "expected function name, found invalid token"));
}
#[test]
- fn parse_colon_starting_function_arguments() {
- // Valid.
- p!("[val: true]" =>
- [par![func!["val": (Bool(true))]]], [],
- [(0:1, 0:4, ResolvedFunc)],
- );
-
- // No colon before arg.
- p!("[val\"s\"]" => [par![func!("val")]], [(0:4, 0:4, "expected colon")]);
-
- // No colon before valid, but wrong token.
- p!("[val=]" => [par![func!("val")]], [(0:4, 0:4, "expected colon")]);
+ fn test_parse_colon_starting_func_args() {
+ // Just colon without args.
+ e!("[val:]" => );
- // No colon before invalid tokens, which are ignored.
- p!("[val/🌎:$]" =>
- [par![func!("val")]],
- [(0:4, 0:4, "expected colon")],
- [(0:1, 0:4, ResolvedFunc)],
- );
+ // Wrong token.
+ t!("[val=]" => P![F!("val")]);
+ e!("[val=]" => s(0,4, 0,4, "expected colon"));
+ e!("[val/🌎:$]" => s(0,4, 0,4, "expected colon"));
+ d!("[val=]" => s(0,1, 0,4, ResolvedFunc));
// String in invalid header without colon still parsed as string
// Note: No "expected quote" error because not even the string was
// expected.
- p!("[val/\"]" => [par![func!("val")]], [
- (0:4, 0:4, "expected colon"),
- (0:7, 0:7, "expected closing bracket"),
- ]);
-
- // Just colon without args.
- p!("[val:]" => [par![func!("val")]]);
- p!("[val:/*12pt*/]" => [par![func!("val")]]);
+ e!("[val/\"]" => s(0,4, 0,4, "expected colon"),
+ s(0,7, 0,7, "expected closing bracket"));
+ }
- // Whitespace / comments around colon.
- p!("[val\n:\ntrue]" => [par![func!("val": (Bool(true)))]]);
- p!("[val/*:*/://\ntrue]" => [par![func!("val": (Bool(true)))]]);
+ #[test]
+ fn test_parse_function_bodies() {
+ t!("[val: 1][*Hi*]" => P![F!("val"; Num(1.0), Tree![P![B, T("Hi"), B]])]);
+ e!(" [val][ */ ]" => s(0,8, 0,10, "unexpected end of block comment"));
+
+ // Spanned.
+ ts!(" [box][Oh my]" => s(0,0, 0,13, P![
+ s(0,0, 0,1, S),
+ s(0,1, 0,13, F!(s(0,1, 0,4, "box");
+ s(0,6, 0,11, Tree![s(0,6, 0,11, P![
+ s(0,6, 0,8, T("Oh")), s(0,8, 0,9, S), s(0,9, 0,11, T("my"))
+ ])])
+ ))
+ ]));
}
#[test]
- fn parse_one_positional_argument() {
- // Different expressions.
- pval!("_" => (Id("_")));
- pval!("name" => (Id("name")));
- pval!("\"hi\"" => (Str("hi")));
- pval!("3.14" => (Num(3.14)));
- pval!("4.5cm" => (Len(Length::cm(4.5))));
- pval!("12e1pt" => (Len(Length::pt(12e1))));
- pval!("#f7a20500" => (ColorStr("f7a20500")));
- pval!("\"a\n[]\\\"string\"" => (Str("a\n[]\"string")));
-
- // Trailing comma.
- pval!("a," => (Id("a")));
-
- // Simple coerced tuple.
- pval!("(hi)" => (Id("hi")));
-
- // Math.
- pval!("3.2in + 6pt" => (Add(Len(Length::inches(3.2)), Len(Length::pt(6.0)))));
- pval!("5 - 0.01" => (Sub(Num(5.0), Num(0.01))));
- pval!("(3mm * 2)" => (Mul(Len(Length::mm(3.0)), Num(2.0))));
- pval!("12e-3cm/1pt" => (Div(Len(Length::cm(12e-3)), Len(Length::pt(1.0)))));
-
- // Span of expression.
- p!("[val: 1 + 3]" => [(0:0, 0:12, par![(0:0, 0:12, func!(
- (0:1, 0:4, "val"): ((0:6, 0:11, Expr::Add(
- Box::new(span_item!((0:6, 0:7, Num(1.0)))),
- Box::new(span_item!((0:10, 0:11, Num(3.0)))),
- )))
- ))])]);
+ fn test_parse_simple_values() {
+ v!("_" => Id("_"));
+ v!("name" => Id("name"));
+ v!("α" => Id("α"));
+ v!("\"hi\"" => Str("hi"));
+ v!("true" => Bool(true));
+ v!("false" => Bool(false));
+ v!("1.0e-4" => Num(1e-4));
+ v!("3.14" => Num(3.14));
+ v!("50%" => Num(0.5));
+ v!("4.5cm" => Len(Length::cm(4.5)));
+ v!("12e1pt" => Len(Length::pt(12e1)));
+ v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00)));
+ v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string"));
+
+ // Healed colors.
+ v!("#12345" => Color(RgbaColor::new_healed(0, 0, 0, 0xff)));
+ e!("[val: #12345]" => s(0,6, 0,12, "invalid color"));
+ e!("[val: #a5]" => s(0,6, 0,9, "invalid color"));
+ e!("[val: #14b2ah]" => s(0,6, 0,13, "invalid color"));
+ e!("[val: #f075ff011]" => s(0,6, 0,16, "invalid color"));
// Unclosed string.
- p!("[val: \"hello]" => [par![func!("val": (Str("hello]")), {})]], [
- (0:13, 0:13, "expected quote"),
- (0:13, 0:13, "expected closing bracket"),
- ]);
-
- // Invalid, healed colors.
- let healed = Expr::Color(RgbaColor::new_healed(0, 0, 0, 255));
- p!("[val: #12345]" => [par![func!("val": (healed.clone()))]], [(0:6, 0:12, "invalid color")]);
- p!("[val: #a5]" => [par![func!("val": (healed.clone()))]], [(0:6, 0:9, "invalid color")]);
- p!("[val: #14b2ah]" => [par![func!("val": (healed.clone()))]], [(0:6, 0:13, "invalid color")]);
- p!("[val: #f075ff011]" => [par![func!("val": (healed.clone()))]], [(0:6, 0:16, "invalid color")]);
+ v!("\"hello" => Str("hello]"));
+ e!("[val: \"hello]" => s(0,13, 0,13, "expected quote"),
+ s(0,13, 0,13, "expected closing bracket"));
+
+ // Spanned.
+ ts!("[val: 1.4]" => s(0,0, 0,10, P![
+ s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.4))))
+ ]));
}
#[test]
- fn parse_complex_mathematical_expressions() {
- // Valid expressions.
- pval!("(3.2in + 6pt)*(5/2-1)" => (Mul(
+ fn test_parse_expressions() {
+ // Coerced table.
+ v!("(hi)" => Id("hi"));
+
+ // Operations.
+ v!("-1" => Neg(Num(1.0)));
+ v!("-- 1" => Neg(Neg(Num(1.0))));
+ v!("3.2in + 6pt" => Add(Len(Length::inches(3.2)), Len(Length::pt(6.0))));
+ v!("5 - 0.01" => Sub(Num(5.0), Num(0.01)));
+ v!("(3mm * 2)" => Mul(Len(Length::mm(3.0)), Num(2.0)));
+ v!("12e-3cm/1pt" => Div(Len(Length::cm(12e-3)), Len(Length::pt(1.0))));
+
+ // More complex.
+ v!("(3.2in + 6pt)*(5/2-1)" => Mul(
Add(Len(Length::inches(3.2)), Len(Length::pt(6.0))),
Sub(Div(Num(5.0), Num(2.0)), Num(1.0))
- )));
- pval!("(6.3E+2+4* - 3.2pt)/2" => (Div(
+ ));
+ v!("(6.3E+2+4* - 3.2pt)/2" => Div(
Add(Num(6.3e2), Mul(Num(4.0), Neg(Len(Length::pt(3.2))))),
Num(2.0)
- )));
+ ));
// Associativity of multiplication and division.
- pval!("3/4*5" => (Mul(Div(Num(3.0), Num(4.0)), Num(5.0))));
+ v!("3/4*5" => Mul(Div(Num(3.0), Num(4.0)), Num(5.0)));
+
+ // Spanned.
+ ts!("[val: 1 + 3]" => s(0,0, 0,12, P![s(0,0, 0,12, F!(
+ s(0,1, 0,4, "val"); s(0,6, 0,11, Add(
+ s(0,6, 0,7, Num(1.0)),
+ s(0,10, 0,11, Num(3.0)),
+ ))
+ ))]));
// Span of parenthesized expression contains parens.
- p!("[val: (1)]" => [(0:0, 0:10, par![
- (0:0, 0:10, func!((0:1, 0:4, "val"): ((0:6, 0:9, Num(1.0)))))
- ])]);
+ ts!("[val: (1)]" => s(0,0, 0,10, P![
+ s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.0))))
+ ]));
// Invalid expressions.
- p!("[val: 4pt--]" => [par![func!("val": (Len(Length::pt(4.0))))]], [
- (0:10, 0:11, "dangling minus"),
- (0:6, 0:10, "missing right summand")
- ]);
- p!("[val: 3mm+4pt*]" =>
- [par![func!("val": (Add(Len(Length::mm(3.0)), Len(Length::pt(4.0)))))]],
- [(0:10, 0:14, "missing right factor")],
- );
+ v!("4pt--" => Len(Length::pt(4.0)));
+ e!("[val: 4pt--]" => s(0,10, 0,11, "dangling minus"),
+ s(0,6, 0,10, "missing right summand"));
+
+ v!("3mm+4pt*" => Add(Len(Length::mm(3.0)), Len(Length::pt(4.0))));
+ e!("[val: 3mm+4pt*]" => s(0,10, 0,14, "missing right factor"));
}
#[test]
- fn parse_tuples() {
- // Empty tuple.
- pval!("()" => (tuple!()));
- pval!("empty()" => (named_tuple!("empty")));
-
- // Space between name and tuple.
- pval!("add ( 1 , 2 )" => (named_tuple!("add", Num(1.0), Num(2.0))));
- pval!("num = add ( 1 , 2 )" => (), {
- "num" => named_tuple!("add", Num(1.0), Num(2.0))
- });
-
- // Invalid value.
- p!("[val: sound(\x07)]" =>
- [par![func!("val": (named_tuple!("sound")), {})]],
- [(0:12, 0:13, "expected value, found invalid token")],
- );
-
- // Invalid tuple name.
- p!("[val: 👠(\"abc\", 13e-5)]" =>
- [par![func!("val": (tuple!(Str("abc"), Num(13.0e-5))), {})]],
- [(0:6, 0:7, "expected argument, found invalid token")],
- );
-
- // Unclosed tuple.
- p!("[val: lang(中文]" =>
- [par![func!("val": (named_tuple!("lang", Id("中文"))), {})]],
- [(0:13, 0:13, "expected closing paren")],
- );
-
- // Valid values.
- pval!("(1, 2)" => (tuple!(Num(1.0), Num(2.0))));
- pval!("(\"s\",)" => (tuple!(Str("s"))));
- pval!("items(\"fire\", #f93a6d)" => (
- named_tuple!("items", Str("fire"), ColorStr("f93a6d")
- )));
-
- // Nested tuples.
- pval!("css(1pt, rgb(90, 102, 254), \"solid\")" => (named_tuple!(
- "css",
- Len(Length::pt(1.0)),
- named_tuple!("rgb", Num(90.0), Num(102.0), Num(254.0)),
- Str("solid"),
- )));
-
- // Invalid commas.
- p!("[val: (,)]" =>
- [par![func!("val": (tuple!()), {})]],
- [(0:7, 0:8, "expected value, found comma")],
- );
- p!("[val: (true false)]" =>
- [par![func!("val": (tuple!(Bool(true), Bool(false))), {})]],
- [(0:11, 0:11, "expected comma")],
- );
+ fn test_parse_tables() {
+ // Okay.
+ v!("()" => Table![]);
+ v!("(false)" => Bool(false));
+ v!("(true,)" => Table![Bool(true)]);
+ v!("(key=val)" => Table!["key" => Id("val")]);
+ v!("(1, 2)" => Table![Num(1.0), Num(2.0)]);
+ v!("(1, key=\"value\")" => Table![Num(1.0), "key" => Str("value")]);
+
+ // Decorations.
+ d!("[val: key=hi]" => s(0,6, 0,9, TableKey), s(0,1, 0,4, ResolvedFunc));
+ d!("[val: (key=hi)]" => s(0,7, 0,10, TableKey), s(0,1, 0,4, ResolvedFunc));
+ d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey), s(0,1, 0,4, ResolvedFunc));
+
+ // Spanned with spacing around keyword arguments.
+ ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, P![
+ s(0,0, 4,2, F!(
+ s(0,1, 0,4, "val");
+ s(1,1, 1,3, "hi") => s(3,4, 4,1, Str("s\n"))
+ ))
+ ]));
+ e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => );
}
#[test]
- fn parse_objects() {
- let val = || par![func!("val": (object! {}), {})];
-
- // Okay objects.
- pval!("{}" => (object! {}));
- pval!("{ key = value }" => (object! { "key" => Id("value") }));
-
- // Unclosed object.
- p!("[val: {hello = world]" =>
- [par![func!("val": (object! { "hello" => Id("world") }), {})]],
- [(0:20, 0:20, "expected closing brace")],
- );
- p!("[val: { a]" =>
- [par![func!("val": (object! {}), {})]],
- [(0:9, 0:9, "expected equals sign"), (0:9, 0:9, "expected closing brace")],
- );
+ fn test_parse_tables_compute_func_calls() {
+ v!("empty()" => Call!("empty"));
+ v!("add ( 1 , 2 )" => Call!("add"; Num(1.0), Num(2.0)));
+ v!("items(\"fire\", #f93a6d)" => Call!("items";
+ Str("fire"), Color(RgbaColor::new(0xf9, 0x3a, 0x6d, 0xff))
+ ));
- // Missing key.
- p!("[val: {,}]" => [val()], [(0:7, 0:8, "expected key, found comma")]);
- p!("[val: { 12pt }]" => [val()], [(0:8, 0:12, "expected key, found length")]);
- p!("[val: { : }]" => [val()], [(0:8, 0:9, "expected key, found colon")]);
-
- // Missing colon.
- p!("[val: { key }]" => [val()], [(0:11, 0:11, "expected equals sign")]);
- p!("[val: { key false }]" => [val()], [
- (0:11, 0:11, "expected equals sign"),
- (0:12, 0:17, "expected key, found bool"),
- ]);
- p!("[val: { a b=c }]" =>
- [par![func!("val": (object! { "b" => Id("c") }), {})]],
- [(0:9, 0:9, "expected equals sign")],
- );
+ // More complex.
+ v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
+ "css";
+ Len(Length::pt(1.0)),
+ Call!("rgb"; Num(90.0), Num(102.0), Num(254.0)),
+ Str("solid"),
+ ));
- // Missing value.
- p!("[val: { key= : }]" => [val()], [(0:13, 0:14, "expected value, found colon")]);
- p!("[val: { key= , k= \"s\" }]" =>
- [par![func!("val": (object! { "k" => Str("s") }), {})]],
- [(0:13, 0:14, "expected value, found comma")],
- );
+ // Unclosed.
+ v!("lang(中文]" => Call!("lang"; Id("中文")));
+ e!("[val: lang(中文]" => s(0,13, 0,13, "expected closing paren"));
- // Missing comma, invalid token.
- p!("[val: left={ a=2, b=false 🌎 }]" =>
- [par![func!("val": (), {
- "left" => object! {
- "a" => Num(2.0),
- "b" => Bool(false),
- }
- })]],
- [(0:25, 0:25, "expected comma"),
- (0:26, 0:27, "expected key, found invalid token")],
- );
+ // Invalid name.
+ v!("👠(\"abc\", 13e-5)" => Table!(Str("abc"), Num(13.0e-5)));
+ e!("[val: 👠(\"abc\", 13e-5)]" => s(0,6, 0,7, "expected value, found invalid token"));
}
#[test]
- fn parse_nested_tuples_and_objects() {
- pval!("(1, { ab=(), d = (3, 14pt) }), false" => (
- tuple!(
+ fn test_parse_tables_nested() {
+ v!("(1, ( ab=(), d = (3, 14pt) )), false" =>
+ Table![
Num(1.0),
- object!(
- "ab" => tuple!(),
- "d" => tuple!(Num(3.0), Len(Length::pt(14.0))),
+ Table!(
+ "ab" => Table![],
+ "d" => Table!(Num(3.0), Len(Length::pt(14.0))),
),
- ),
+ ],
Bool(false),
- ));
- }
-
- #[test]
- fn parse_one_keyword_argument() {
- // Correct
- p!("[val: x=true]" =>
- [par![func!("val": (), { "x" => Bool(true) })]], [],
- [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
);
-
- // Spacing around keyword arguments
- p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" =>
- [par![S, func!("val": (), { "hi" => Str("s\n") })]], [],
- [(2:1, 2:3, ArgumentKey), (1:2, 1:5, ResolvedFunc)],
- );
-
- // Missing value
- p!("[val: x=]" =>
- [par![func!("val")]],
- [(0:8, 0:8, "expected value")],
- [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
- );
- }
-
- #[test]
- fn parse_multiple_mixed_arguments() {
- p!("[val: 12pt, key=value]" =>
- [par![func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })]],
- [],
- [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
- );
- pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") });
}
#[test]
- fn parse_invalid_values() {
- p!("[val: )]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found closing paren")]);
- p!("[val: }]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found closing brace")]);
- p!("[val: :]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found colon")]);
- p!("[val: ,]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found comma")]);
- p!("[val: =]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found equals sign")]);
- p!("[val: 🌎]" => [par![func!("val")]], [(0:6, 0:7, "expected argument, found invalid token")]);
- p!("[val: 12ept]" => [par![func!("val")]], [(0:6, 0:11, "expected argument, found invalid token")]);
- p!("[val: [hi]]" =>
- [par![func!("val")]],
- [(0:6, 0:10, "expected argument, found function")],
- [(0:1, 0:4, ResolvedFunc)],
- );
- }
-
- #[test]
- fn parse_invalid_key_value_pairs() {
- // Invalid keys.
- p!("[val: true=you]" =>
- [par![func!("val": (Bool(true), Id("you")), {})]],
- [(0:10, 0:10, "expected comma"),
- (0:10, 0:11, "expected argument, found equals sign")],
- [(0:1, 0:4, ResolvedFunc)],
- );
-
- // Unexpected equals.
- p!("[box: z=y=4]" =>
- [par![func!("box": (Num(4.0)), { "z" => Id("y") })]],
- [(0:9, 0:9, "expected comma"),
- (0:9, 0:10, "expected argument, found equals sign")],
- );
-
- // Invalid colon after keyable positional argument.
- p!("[val: key:12]" =>
- [par![func!("val": (Id("key"), Num(12.0)), {})]],
- [(0:9, 0:9, "expected comma"),
- (0:9, 0:10, "expected argument, found colon")],
- [(0:1, 0:4, ResolvedFunc)],
- );
-
- // Invalid colon after unkeyable positional argument.
- p!("[val: true:12]" =>
- [par![func!("val": (Bool(true), Num(12.0)), {})]],
- [(0:10, 0:10, "expected comma"),
- (0:10, 0:11, "expected argument, found colon")],
- [(0:1, 0:4, ResolvedFunc)],
- );
- }
-
- #[test]
- fn parse_invalid_commas() {
- // Missing commas.
- p!("[val: 1pt 1]" =>
- [par![func!("val": (Len(Length::pt(1.0)), Num(1.0)), {})]],
- [(0:9, 0:9, "expected comma")],
- );
- p!(r#"[val: _"s"]"# =>
- [par![func!("val": (Id("_"), Str("s")), {})]],
- [(0:7, 0:7, "expected comma")],
- );
-
- // Unexpected commas.
- p!("[val:,]" => [par![func!("val")]], [(0:5, 0:6, "expected argument, found comma")]);
- p!("[val: key=,]" => [par![func!("val")]], [(0:10, 0:11, "expected value, found comma")]);
- p!("[val:, true]" =>
- [par![func!("val": (Bool(true)), {})]],
- [(0:5, 0:6, "expected argument, found comma")],
- );
- }
-
- #[test]
- fn parse_bodies() {
- p!("[val][Hi]" => [par![func!("val"; [par![T("Hi")]])]]);
- p!("[val:*][*Hi*]" =>
- [par![func!("val"; [par![Bold, T("Hi"), Bold]])]],
- [(0:5, 0:6, "expected argument, found star")],
- );
- // Errors in bodies.
- p!(" [val][ */ ]" =>
- [par![S, func!("val"; [par![S, S]])]],
- [(0:8, 0:10, "unexpected end of block comment")],
- );
- }
-
- #[test]
- fn parse_spanned_functions() {
- // Space before function
- p!(" [val]" =>
- [(0:0, 0:6, par![(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))])],
- [],
- [(0:2, 0:5, ResolvedFunc)],
- );
-
- // Newline before function
- p!("a \n\r\n[val]" =>
- [
- (0:0, 0:1, par![(0:0, 0:1, T("a"))]),
- (2:0, 2:5, par![(2:0, 2:5, func!((0:1, 0:4, "val")))]),
- ],
- [],
- [(2:1, 2:4, ResolvedFunc)],
- );
-
- // Content before function
- p!("hello [val][world] 🌎" =>
- [(0:0, 0:20, par![
- (0:0, 0:5, T("hello")),
- (0:5, 0:6, S),
- (0:6, 0:18, func!((0:1, 0:4, "val"); [
- (0:6, 0:11, par![(0:6, 0:11, T("world"))])
- ])),
- (0:18, 0:19, S),
- (0:19, 0:20, T("🌎"))
- ])],
- [],
- [(0:7, 0:10, ResolvedFunc)],
- );
-
- // Nested function
- p!(" [val][\nbody[ box]\n ]" =>
- [(0:0, 2:2, par![
- (0:0, 0:1, S),
- (0:1, 2:2, func!((0:1, 0:4, "val"); [(0:6, 2:1, par![
- (0:6, 1:0, S),
- (1:0, 1:4, T("body")),
- (1:4, 1:10, func!((0:2, 0:5, "box"))),
- (1:10, 2:1, S),
- ])]))
- ])],
- [],
- [(0:2, 0:5, ResolvedFunc), (1:6, 1:9, ResolvedFunc)],
- );
+ fn test_parse_tables_errors() {
+ // Expected value.
+ e!("[val: (=)]" => s(0,7, 0,8, "expected value, found equals sign"));
+ e!("[val: (,)]" => s(0,7, 0,8, "expected value, found comma"));
+ v!("(\x07 abc,)" => Table![Id("abc")]);
+ e!("[val: (\x07 abc,)]" => s(0,7, 0,8, "expected value, found invalid token"));
+ e!("[val: (key=,)]" => s(0,11, 0,12, "expected value, found comma"));
+ e!("[val: [hi]]" => s(0,6, 0,10, "expected value, found function"));
+
+ // Expected comma.
+ v!("(true false)" => Table![Bool(true), Bool(false)]);
+ e!("[val: (true false)]" => s(0,11, 0,11, "expected comma"));
+
+ // Expected closing paren.
+ e!("[val: (#000]" => s(0,11, 0,11, "expected closing paren"));
+ e!("[val: (key]" => s(0,10, 0,10, "expected closing paren"));
+ e!("[val: (key=]" => s(0,11, 0,11, "expected value"),
+ s(0,11, 0,11, "expected closing paren"));
+
+ // Bad key.
+ v!("true=you" => Bool(true), Id("you"));
+ e!("[val: true=you]" =>
+ s(0,10, 0,10, "expected comma"),
+ s(0,10, 0,11, "expected value, found equals sign"));
+
+ // Unexpected equals sign.
+ v!("z=y=4" => Num(4.0), "z" => Id("y"));
+ e!("[val: z=y=4]" =>
+ s(0,9, 0,9, "expected comma"),
+ s(0,9, 0,10, "expected value, found equals sign"));
}
}
diff --git a/src/syntax/test.rs b/src/syntax/test.rs
deleted file mode 100644
index aec54ded..00000000
--- a/src/syntax/test.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use std::fmt::Debug;
-
-use crate::func::prelude::*;
-use super::tree::SyntaxNode;
-use super::span;
-
-pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
-where
- T: Debug + PartialEq,
-{
- span::set_cmp(cmp_spans);
- let equal = exp == found;
- span::set_cmp(true);
-
- if !equal {
- println!("source: {:?}", src);
- println!("expected: {:#?}", exp);
- println!("found: {:#?}", found);
- panic!("test failed");
- }
-}
-
-/// Create a vector of optionally spanned expressions from a list description.
-///
-/// # Examples
-/// ```
-/// // With spans
-/// spanned![(0:0, 0:5, "hello"), (0:5, 0:3, "world")]
-///
-/// // Without spans: Implicit zero spans.
-/// spanned!["hello", "world"]
-/// ```
-macro_rules! span_vec {
- ($(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)),* $(,)?) => {
- (vec![$(span_item!(($sl:$sc, $el:$ec, $v))),*], true)
- };
-
- ($($v:expr),* $(,)?) => {
- (vec![$(span_item!($v)),*], false)
- };
-}
-
-macro_rules! span_item {
- (($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => {{
- use $crate::syntax::span::{Pos, Span, Spanned};
- Spanned {
- span: Span::new(
- Pos::new($sl, $sc),
- Pos::new($el, $ec)
- ),
- v: $v
- }
- }};
-
- ($v:expr) => {
- $crate::syntax::span::Spanned::zero($v)
- };
-}
-
-pub fn debug_func(mut call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
- let tree = call.args.pos.get::<SyntaxTree>();
- Pass::node(DebugNode(call, tree), Feedback::new())
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct DebugNode(pub FuncCall, pub Option<SyntaxTree>);
-
-#[async_trait(?Send)]
-impl Layout for DebugNode {
- async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
- unimplemented!()
- }
-}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index bf172651..cafc7727 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -116,34 +116,34 @@ impl<'s> Token<'s> {
/// The natural-language name for this token for use in error messages.
pub fn name(self) -> &'static str {
match self {
- Space(_) => "space",
- LineComment(_) => "line comment",
+ Space(_) => "space",
+ LineComment(_) => "line comment",
BlockComment(_) => "block comment",
Function { .. } => "function",
- LeftParen => "opening paren",
- RightParen => "closing paren",
- LeftBrace => "opening brace",
- RightBrace => "closing brace",
- Colon => "colon",
- Comma => "comma",
- Equals => "equals sign",
- Ident(_) => "identifier",
- Str { .. } => "string",
- Bool(_) => "bool",
- Number(_) => "number",
- Length(_) => "length",
- Hex(_) => "hex value",
- Plus => "plus",
- Hyphen => "minus",
- Slash => "slash",
- Star => "star",
- Underscore => "underscore",
- Backslash => "backslash",
- Raw { .. } => "raw text",
- Text(_) => "text",
- Invalid("]") => "closing bracket",
- Invalid("*/") => "end of block comment",
- Invalid(_) => "invalid token",
+ LeftParen => "opening paren",
+ RightParen => "closing paren",
+ LeftBrace => "opening brace",
+ RightBrace => "closing brace",
+ Colon => "colon",
+ Comma => "comma",
+ Equals => "equals sign",
+ Ident(_) => "identifier",
+ Str { .. } => "string",
+ Bool(_) => "bool",
+ Number(_) => "number",
+ Length(_) => "length",
+ Hex(_) => "hex value",
+ Plus => "plus",
+ Hyphen => "minus",
+ Slash => "slash",
+ Star => "star",
+ Underscore => "underscore",
+ Backslash => "backslash",
+ Raw { .. } => "raw text",
+ Text(_) => "text",
+ Invalid("]") => "closing bracket",
+ Invalid("*/") => "end of block comment",
+ Invalid(_) => "invalid token",
}
}
}
@@ -230,13 +230,16 @@ impl<'s> Iterator for Tokens<'s> {
'-' if self.mode == Header => Hyphen,
'/' if self.mode == Header => Slash,
- // String values.
- '"' if self.mode == Header => self.read_string(),
-
// Star serves a double purpose as a style modifier
// and a expression operator in the header.
'*' => Star,
+ // A hex expression.
+ '#' if self.mode == Header => self.read_hex(),
+
+ // String values.
+ '"' if self.mode == Header => self.read_string(),
+
// Style toggles.
'_' if self.mode == Body => Underscore,
'`' if self.mode == Body => self.read_raw(),
@@ -244,9 +247,6 @@ impl<'s> Iterator for Tokens<'s> {
// An escaped thing.
'\\' if self.mode == Body => self.read_escaped(),
- // A hex expression.
- '#' if self.mode == Header => self.read_hex(),
-
// Expressions or just strings.
c => {
let body = self.mode == Body;
@@ -535,7 +535,7 @@ pub fn is_identifier(string: &str) -> bool {
#[allow(non_snake_case)]
mod tests {
use crate::length::Length;
- use super::super::test::check;
+ use crate::syntax::tests::*;
use super::*;
use Token::{
Space as S,
@@ -554,194 +554,201 @@ mod tests {
Text as T,
};
- /// Test whether the given string tokenizes into the given list of tokens.
- macro_rules! t {
- ($mode:expr, $source:expr => [$($tokens:tt)*]) => {
- let (exp, spans) = span_vec![$($tokens)*];
- let found = Tokens::new($source, Pos::ZERO, $mode).collect::<Vec<_>>();
- check($source, exp, found, spans);
- }
- }
-
fn Str(string: &str, terminated: bool) -> Token { Token::Str { string, terminated } }
fn Raw(raw: &str, terminated: bool) -> Token { Token::Raw { raw, terminated } }
- macro_rules! func {
- ($header:expr, Some($($tokens:tt)*), $terminated:expr) => {
- Function {
- header: $header,
- body: Some(span_item!(($($tokens)*))),
- terminated: $terminated,
+ macro_rules! F {
+ ($h:expr, None, $t:expr) => {
+ Token::Function { header: $h, body: None, terminated: $t }
+ };
+ ($h:expr, $b:expr, $t:expr) => {
+ Token::Function {
+ header: $h,
+ body: Some(Into::<Spanned<&str>>::into($b)),
+ terminated: $t,
}
};
- ($header:expr, None, $terminated:expr) => {
- Function { header: $header, body: None, terminated: $terminated }
+ }
+
+ macro_rules! t { ($($tts:tt)*) => {test!(@spans=false, $($tts)*)} }
+ macro_rules! ts { ($($tts:tt)*) => {test!(@spans=true, $($tts)*)} }
+ macro_rules! test {
+ (@spans=$spans:expr, $mode:expr, $src:expr => $($token:expr),*) => {
+ let exp = vec![$(Into::<Spanned<Token>>::into($token)),*];
+ let found = Tokens::new($src, Pos::ZERO, $mode).collect::<Vec<_>>();
+ check($src, exp, found, $spans);
}
}
#[test]
fn tokenize_whitespace() {
- t!(Body, "" => []);
- t!(Body, " " => [S(0)]);
- t!(Body, " " => [S(0)]);
- t!(Body, "\t" => [S(0)]);
- t!(Body, " \t" => [S(0)]);
- t!(Body, "\n" => [S(1)]);
- t!(Body, "\n " => [S(1)]);
- t!(Body, " \n" => [S(1)]);
- t!(Body, " \n " => [S(1)]);
- t!(Body, "\r\n" => [S(1)]);
- t!(Body, " \n\t \n " => [S(2)]);
- t!(Body, "\n\r" => [S(2)]);
- t!(Body, " \r\r\n \x0D" => [S(3)]);
+ t!(Body, "" => );
+ t!(Body, " " => S(0));
+ t!(Body, " " => S(0));
+ t!(Body, "\t" => S(0));
+ t!(Body, " \t" => S(0));
+ t!(Body, "\n" => S(1));
+ t!(Body, "\n " => S(1));
+ t!(Body, " \n" => S(1));
+ t!(Body, " \n " => S(1));
+ t!(Body, "\r\n" => S(1));
+ t!(Body, " \n\t \n " => S(2));
+ t!(Body, "\n\r" => S(2));
+ t!(Body, " \r\r\n \x0D" => S(3));
}
#[test]
fn tokenize_comments() {
- t!(Body, "a // bc\n " => [T("a"), S(0), LC(" bc"), S(1)]);
- t!(Body, "a //a//b\n " => [T("a"), S(0), LC("a//b"), S(1)]);
- t!(Body, "a //a//b\r\n" => [T("a"), S(0), LC("a//b"), S(1)]);
- t!(Body, "a //a//b\n\nhello" => [T("a"), S(0), LC("a//b"), S(2), T("hello")]);
- t!(Body, "/**/" => [BC("")]);
- t!(Body, "_/*_/*a*/*/" => [Underscore, BC("_/*a*/")]);
- t!(Body, "/*/*/" => [BC("/*/")]);
- t!(Body, "abc*/" => [T("abc"), Invalid("*/")]);
- t!(Body, "/***/" => [BC("*")]);
- t!(Body, "/**\\****/*/*/" => [BC("*\\***"), Invalid("*/"), Invalid("*/")]);
- t!(Body, "/*abc" => [BC("abc")]);
+ t!(Body, "a // bc\n " => T("a"), S(0), LC(" bc"), S(1));
+ t!(Body, "a //a//b\n " => T("a"), S(0), LC("a//b"), S(1));
+ t!(Body, "a //a//b\r\n" => T("a"), S(0), LC("a//b"), S(1));
+ t!(Body, "a //a//b\n\nhello" => T("a"), S(0), LC("a//b"), S(2), T("hello"));
+ t!(Body, "/**/" => BC(""));
+ t!(Body, "_/*_/*a*/*/" => Underscore, BC("_/*a*/"));
+ t!(Body, "/*/*/" => BC("/*/"));
+ t!(Body, "abc*/" => T("abc"), Invalid("*/"));
+ t!(Body, "/***/" => BC("*"));
+ t!(Body, "/**\\****/*/*/" => BC("*\\***"), Invalid("*/"), Invalid("*/"));
+ t!(Body, "/*abc" => BC("abc"));
}
#[test]
fn tokenize_body_only_tokens() {
- t!(Body, "_*" => [Underscore, Star]);
- t!(Body, "***" => [Star, Star, Star]);
- t!(Body, "[func]*bold*" => [func!("func", None, true), Star, T("bold"), Star]);
- t!(Body, "hi_you_ there" => [T("hi"), Underscore, T("you"), Underscore, S(0), T("there")]);
- t!(Body, "`raw`" => [Raw("raw", true)]);
- t!(Body, "`[func]`" => [Raw("[func]", true)]);
- t!(Body, "`]" => [Raw("]", false)]);
- t!(Body, "`\\``" => [Raw("\\`", true)]);
- t!(Body, "\\ " => [Backslash, S(0)]);
- t!(Header, "_`" => [Invalid("_`")]);
+ t!(Body, "_*" => Underscore, Star);
+ t!(Body, "***" => Star, Star, Star);
+ t!(Body, "[func]*bold*" => F!("func", None, true), Star, T("bold"), Star);
+ t!(Body, "hi_you_ there" => T("hi"), Underscore, T("you"), Underscore, S(0), T("there"));
+ t!(Body, "`raw`" => Raw("raw", true));
+ t!(Body, "`[func]`" => Raw("[func]", true));
+ t!(Body, "`]" => Raw("]", false));
+ t!(Body, "`\\``" => Raw("\\`", true));
+ t!(Body, "\\ " => Backslash, S(0));
+ t!(Header, "_`" => Invalid("_`"));
}
#[test]
fn tokenize_header_only_tokens() {
- t!(Body, "a: b" => [T("a:"), S(0), T("b")]);
- t!(Body, "c=d, " => [T("c=d,"), S(0)]);
- t!(Header, "(){}:=," => [LP, RP, LB, RB, Colon, Equals, Comma]);
- t!(Header, "a:b" => [Id("a"), Colon, Id("b")]);
- t!(Header, "#6ae6dd" => [Hex("6ae6dd")]);
- t!(Header, "#8A083c" => [Hex("8A083c")]);
- t!(Header, "a: true, x=1" => [Id("a"), Colon, S(0), Bool(true), Comma, S(0), Id("x"), Equals, Num(1.0)]);
- t!(Header, "=3.14" => [Equals, Num(3.14)]);
- t!(Header, "12.3e5" => [Num(12.3e5)]);
- t!(Header, "120%" => [Num(1.2)]);
- t!(Header, "12e4%" => [Num(1200.0)]);
- t!(Header, "__main__" => [Id("__main__")]);
- t!(Header, ".func.box" => [Id(".func.box")]);
- t!(Header, "arg, _b, _1" => [Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")]);
- t!(Header, "12_pt, 12pt" => [Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0))]);
- t!(Header, "1e5in" => [Len(Length::inches(100000.0))]);
- t!(Header, "2.3cm" => [Len(Length::cm(2.3))]);
- t!(Header, "12e-3in" => [Len(Length::inches(12e-3))]);
- t!(Header, "6.1cm + 4pt,a=1*2" => [Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)), Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0)]);
- t!(Header, "(5 - 1) / 2.1" => [LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP, S(0), Slash, S(0), Num(2.1)]);
- t!(Header, "02.4mm" => [Len(Length::mm(2.4))]);
- 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!(Body, "a: b" => T("a:"), S(0), T("b"));
+ t!(Body, "c=d, " => T("c=d,"), S(0));
+ t!(Header, "(){}:=," => LP, RP, LB, RB, Colon, Equals, Comma);
+ t!(Header, "a:b" => Id("a"), Colon, Id("b"));
+ t!(Header, "#6ae6dd" => Hex("6ae6dd"));
+ t!(Header, "#8A083c" => Hex("8A083c"));
+ t!(Header, "a: true, x=1" => Id("a"), Colon, S(0), Bool(true), Comma, S(0),
+ Id("x"), Equals, Num(1.0));
+ t!(Header, "=3.14" => Equals, Num(3.14));
+ t!(Header, "12.3e5" => Num(12.3e5));
+ t!(Header, "120%" => Num(1.2));
+ t!(Header, "12e4%" => Num(1200.0));
+ t!(Header, "__main__" => Id("__main__"));
+ t!(Header, ".func.box" => Id(".func.box"));
+ t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
+ t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
+ t!(Header, "1e5in" => Len(Length::inches(100000.0)));
+ t!(Header, "2.3cm" => Len(Length::cm(2.3)));
+ t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
+ t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
+ Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0));
+ t!(Header, "(5 - 1) / 2.1" => LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP,
+ S(0), Slash, S(0), Num(2.1));
+ t!(Header, "-1" => Min, Num(1.0));
+ t!(Header, "--1" => Min, Min, Num(1.0));
+ t!(Header, "- 1" => Min, S(0), Num(1.0));
+ t!(Header, "02.4mm" => Len(Length::mm(2.4)));
+ 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);
}
#[test]
fn tokenize_strings() {
- t!(Body, "a \"hi\" string" => [T("a"), S(0), T("\"hi\""), S(0), T("string")]);
- t!(Header, "\"hello" => [Str("hello", false)]);
- t!(Header, "\"hello world\"" => [Str("hello world", true)]);
- t!(Header, "\"hello\nworld\"" => [Str("hello\nworld", true)]);
- t!(Header, r#"1"hello\nworld"false"# => [Num(1.0), Str("hello\\nworld", true), Bool(false)]);
- t!(Header, r#""a\"bc""# => [Str(r#"a\"bc"#, true)]);
- t!(Header, r#""a\\"bc""# => [Str(r#"a\\"#, true), Id("bc"), Str("", false)]);
- t!(Header, r#""a\tbc"# => [Str("a\\tbc", false)]);
- t!(Header, "\"🌎\"" => [Str("🌎", true)]);
+ t!(Body, "a \"hi\" string" => T("a"), S(0), T("\"hi\""), S(0), T("string"));
+ t!(Header, "\"hello" => Str("hello", false));
+ t!(Header, "\"hello world\"" => Str("hello world", true));
+ t!(Header, "\"hello\nworld\"" => Str("hello\nworld", true));
+ t!(Header, r#"1"hello\nworld"false"# => Num(1.0), Str("hello\\nworld", true), Bool(false));
+ t!(Header, r#""a\"bc""# => Str(r#"a\"bc"#, true));
+ t!(Header, r#""a\\"bc""# => Str(r#"a\\"#, true), Id("bc"), Str("", false));
+ t!(Header, r#""a\tbc"# => Str("a\\tbc", false));
+ t!(Header, "\"🌎\"" => Str("🌎", true));
}
#[test]
fn tokenize_functions() {
- t!(Body, "a[f]" => [T("a"), func!("f", None, true)]);
- t!(Body, "[f]a" => [func!("f", None, true), T("a")]);
- t!(Body, "\n\n[f][ ]" => [S(2), func!("f", Some(0:4, 0:5, " "), true)]);
- t!(Body, "abc [f][ ]a" => [T("abc"), S(0), func!("f", Some(0:4, 0:5, " "), true), T("a")]);
- t!(Body, "[f: [=][*]]" => [func!("f: [=][*]", None, true)]);
- t!(Body, "[_][[,],]," => [func!("_", Some(0:4, 0:8, "[,],"), true), T(",")]);
- t!(Body, "[=][=][=]" => [func!("=", Some(0:4, 0:5, "="), true), func!("=", None, true)]);
- t!(Body, "[=][[=][=][=]]" => [func!("=", Some(0:4, 0:13, "[=][=][=]"), true)]);
- t!(Header, "[" => [func!("", None, false)]);
- t!(Header, "]" => [Invalid("]")]);
+ t!(Body, "a[f]" => T("a"), F!("f", None, true));
+ t!(Body, "[f]a" => F!("f", None, true), T("a"));
+ t!(Body, "\n\n[f][ ]" => S(2), F!("f", " ", true));
+ t!(Body, "abc [f][ ]a" => T("abc"), S(0), F!("f", " ", true), T("a"));
+ t!(Body, "[f: [=][*]]" => F!("f: [=][*]", None, true));
+ t!(Body, "[_][[,],]," => F!("_", "[,],", true), T(","));
+ t!(Body, "[=][=][=]" => F!("=", "=", true), F!("=", None, true));
+ t!(Body, "[=][[=][=][=]]" => F!("=", "[=][=][=]", true));
+ t!(Header, "[" => F!("", None, false));
+ t!(Header, "]" => Invalid("]"));
}
#[test]
fn tokenize_correct_end_of_function() {
// End of function with strings and carets in headers
- t!(Body, r#"[f: "]"# => [func!(r#"f: "]"#, None, false)]);
- t!(Body, "[f: \"s\"]" => [func!("f: \"s\"", None, true)]);
- t!(Body, r#"[f: \"\"\"]"# => [func!(r#"f: \"\"\""#, None, true)]);
- t!(Body, "[f: `]" => [func!("f: `", None, true)]);
+ t!(Body, r#"[f: "]"# => F!(r#"f: "]"#, None, false));
+ t!(Body, "[f: \"s\"]" => F!("f: \"s\"", None, true));
+ t!(Body, r#"[f: \"\"\"]"# => F!(r#"f: \"\"\""#, None, true));
+ t!(Body, "[f: `]" => F!("f: `", None, true));
// End of function with strings and carets in bodies
- t!(Body, "[f][\"]" => [func!("f", Some(0:4, 0:5, "\""), true)]);
- t!(Body, r#"[f][\"]"# => [func!("f", Some(0:4, 0:6, r#"\""#), true)]);
- t!(Body, "[f][`]" => [func!("f", Some(0:4, 0:6, "`]"), false)]);
- t!(Body, "[f][\\`]" => [func!("f", Some(0:4, 0:6, "\\`"), true)]);
- t!(Body, "[f][`raw`]" => [func!("f", Some(0:4, 0:9, "`raw`"), true)]);
- t!(Body, "[f][`raw]" => [func!("f", Some(0:4, 0:9, "`raw]"), false)]);
- t!(Body, "[f][`raw]`]" => [func!("f", Some(0:4, 0:10, "`raw]`"), true)]);
- t!(Body, "[f][`\\`]" => [func!("f", Some(0:4, 0:8, "`\\`]"), false)]);
- t!(Body, "[f][`\\\\`]" => [func!("f", Some(0:4, 0:8, "`\\\\`"), true)]);
+ t!(Body, "[f][\"]" => F!("f", s(0,4, 0,5, "\""), true));
+ t!(Body, r#"[f][\"]"# => F!("f", s(0,4, 0,6, r#"\""#), true));
+ t!(Body, "[f][`]" => F!("f", s(0,4, 0,6, "`]"), false));
+ t!(Body, "[f][\\`]" => F!("f", s(0,4, 0,6, "\\`"), true));
+ t!(Body, "[f][`raw`]" => F!("f", s(0,4, 0,9, "`raw`"), true));
+ t!(Body, "[f][`raw]" => F!("f", s(0,4, 0,9, "`raw]"), false));
+ t!(Body, "[f][`raw]`]" => F!("f", s(0,4, 0,10, "`raw]`"), true));
+ t!(Body, "[f][`\\`]" => F!("f", s(0,4, 0,8, "`\\`]"), false));
+ t!(Body, "[f][`\\\\`]" => F!("f", s(0,4, 0,8, "`\\\\`"), true));
// End of function with comments
- t!(Body, "[f][/*]" => [func!("f", Some(0:4, 0:7, "/*]"), false)]);
- t!(Body, "[f][/*`*/]" => [func!("f", Some(0:4, 0:9, "/*`*/"), true)]);
- t!(Body, "[f: //]\n]" => [func!("f: //]\n", None, true)]);
- t!(Body, "[f: \"//]\n]" => [func!("f: \"//]\n]", None, false)]);
+ t!(Body, "[f][/*]" => F!("f", s(0,4, 0,7, "/*]"), false));
+ t!(Body, "[f][/*`*/]" => F!("f", s(0,4, 0,9, "/*`*/"), true));
+ t!(Body, "[f: //]\n]" => F!("f: //]\n", None, true));
+ t!(Body, "[f: \"//]\n]" => F!("f: \"//]\n]", None, false));
// End of function with escaped brackets
- t!(Body, "[f][\\]]" => [func!("f", Some(0:4, 0:6, "\\]"), true)]);
- t!(Body, "[f][\\[]" => [func!("f", Some(0:4, 0:6, "\\["), true)]);
+ t!(Body, "[f][\\]]" => F!("f", s(0,4, 0,6, "\\]"), true));
+ t!(Body, "[f][\\[]" => F!("f", s(0,4, 0,6, "\\["), true));
}
#[test]
fn tokenize_escaped_symbols() {
- t!(Body, r"\\" => [T(r"\")]);
- t!(Body, r"\[" => [T("[")]);
- t!(Body, r"\]" => [T("]")]);
- t!(Body, r"\*" => [T("*")]);
- t!(Body, r"\_" => [T("_")]);
- t!(Body, r"\`" => [T("`")]);
- t!(Body, r"\/" => [T("/")]);
- t!(Body, r#"\""# => [T("\"")]);
+ t!(Body, r"\\" => T(r"\"));
+ t!(Body, r"\[" => T("["));
+ t!(Body, r"\]" => T("]"));
+ t!(Body, r"\*" => T("*"));
+ t!(Body, r"\_" => T("_"));
+ t!(Body, r"\`" => T("`"));
+ t!(Body, r"\/" => T("/"));
+ t!(Body, r#"\""# => T("\""));
}
#[test]
fn tokenize_unescapable_symbols() {
- t!(Body, r"\a" => [T("\\"), T("a")]);
- t!(Body, r"\:" => [T(r"\"), T(":")]);
- t!(Body, r"\=" => [T(r"\"), T("=")]);
- t!(Header, r"\\\\" => [Invalid(r"\\\\")]);
- t!(Header, r"\a" => [Invalid(r"\a")]);
- t!(Header, r"\:" => [Invalid(r"\"), Colon]);
- t!(Header, r"\=" => [Invalid(r"\"), Equals]);
- t!(Header, r"\," => [Invalid(r"\"), Comma]);
+ t!(Body, r"\a" => T("\\"), T("a"));
+ t!(Body, r"\:" => T(r"\"), T(":"));
+ t!(Body, r"\=" => T(r"\"), T("="));
+ t!(Header, r"\\\\" => Invalid(r"\\\\"));
+ t!(Header, r"\a" => Invalid(r"\a"));
+ t!(Header, r"\:" => Invalid(r"\"), Colon);
+ t!(Header, r"\=" => Invalid(r"\"), Equals);
+ t!(Header, r"\," => Invalid(r"\"), Comma);
}
#[test]
fn tokenize_with_spans() {
- t!(Body, "hello" => [(0:0, 0:5, T("hello"))]);
- t!(Body, "ab\r\nc" => [(0:0, 0:2, T("ab")), (0:2, 1:0, S(1)), (1:0, 1:1, T("c"))]);
- t!(Body, "[x = \"(1)\"]*" => [(0:0, 0:11, func!("x = \"(1)\"", None, true)), (0:11, 0:12, Star)]);
- t!(Body, "// ab\r\n\nf" => [(0:0, 0:5, LC(" ab")), (0:5, 2:0, S(2)), (2:0, 2:1, T("f"))]);
- t!(Body, "/*b*/_" => [(0:0, 0:5, BC("b")), (0:5, 0:6, Underscore)]);
- t!(Header, "a=10" => [(0:0, 0:1, Id("a")), (0:1, 0:2, Equals), (0:2, 0:4, Num(10.0))]);
+ ts!(Body, "hello" => s(0,0, 0,5, T("hello")));
+ ts!(Body, "ab\r\nc" => s(0,0, 0,2, T("ab")), s(0,2, 1,0, S(1)), s(1,0, 1,1, T("c")));
+ ts!(Body, "[x = \"(1)\"]*" => s(0,0, 0,11, F!("x = \"(1)\"", None, true)), s(0,11, 0,12, Star));
+ ts!(Body, "// ab\r\n\nf" => s(0,0, 0,5, LC(" ab")), s(0,5, 2,0, S(2)), s(2,0, 2,1, T("f")));
+ ts!(Body, "/*b*/_" => s(0,0, 0,5, BC("b")), s(0,5, 0,6, Underscore));
+ ts!(Header, "a=10" => s(0,0, 0,1, Id("a")), s(0,1, 0,2, Equals), s(0,2, 0,4, Num(10.0)));
}
}
diff --git a/src/table.rs b/src/table.rs
index 2b49d451..2d07ee5e 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -12,7 +12,7 @@ use std::ops::Index;
///
/// The keys of a table may be strings or integers (`u64`). The table is generic
/// over the value type.
-#[derive(Default, Clone, PartialEq)]
+#[derive(Clone)]
pub struct Table<V> {
nums: BTreeMap<u64, V>,
strs: BTreeMap<String, V>,
@@ -129,6 +129,22 @@ impl<V> Table<V> {
pub fn values(&self) -> impl Iterator<Item = &V> {
self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
}
+
+ /// Iterate over the number key-value pairs.
+ pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
+ self.nums.into_iter()
+ }
+
+ /// Iterate over the string key-value pairs.
+ pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
+ self.strs.into_iter()
+ }
+
+ /// Move into an owned iterator over all values in the table.
+ pub fn into_values(self) -> impl Iterator<Item = V> {
+ self.nums.into_iter().map(|(_, v)| v)
+ .chain(self.strs.into_iter().map(|(_, v)| v))
+ }
}
impl<'a, K, V> Index<K> for Table<V>
@@ -142,37 +158,50 @@ where
}
}
+impl<V> Default for Table<V> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<V: Eq> Eq for Table<V> {}
+
+impl<V: PartialEq> PartialEq for Table<V> {
+ fn eq(&self, other: &Self) -> bool {
+ self.nums().eq(other.nums()) && self.strs().eq(other.strs())
+ }
+}
+
impl<V: Debug> Debug for Table<V> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("(")?;
- if f.alternate() && (!self.nums.is_empty() || !self.strs.is_empty()) {
- f.write_str("\n")?;
+ if self.is_empty() {
+ return f.write_str("()");
}
- let len = self.len();
- let nums = self.nums().map(|(k, v)| (k as &dyn Debug, v));
- let strings = self.strs().map(|(k, v)| (k as &dyn Debug, v));
- let pairs = nums.chain(strings);
+ let mut builder = f.debug_tuple("");
- for (i, (key, value)) in pairs.enumerate() {
- if f.alternate() {
- f.write_str(" ")?;
- }
- key.fmt(f)?;
- if f.alternate() {
- f.write_str(" = ")?;
- } else {
- f.write_str("=")?;
- }
- value.fmt(f)?;
- if f.alternate() {
- f.write_str(",\n")?;
- } else if i + 1 < len {
- f.write_str(", ")?;
+ struct Entry<'a>(&'a dyn Debug, &'a dyn Debug);
+ impl<'a> Debug for Entry<'a> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)?;
+ if f.alternate() {
+ f.write_str(" = ")?;
+ } else {
+ f.write_str("=")?;
+ }
+ self.1.fmt(f)
}
}
- f.write_str(")")
+ for (key, value) in self.nums() {
+ builder.field(&Entry(&key, &value));
+ }
+
+ for (key, value) in self.strs() {
+ builder.field(&Entry(&key, &value));
+ }
+
+ builder.finish()
}
}