summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-16 14:23:13 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-16 14:38:30 +0200
commit9f6137d8a829fe8f34554623495fa620252a0184 (patch)
treeda62c40caa247ac1825d335fde9350150c6604db /src
parent84f30fb73518ca23cbc728b1bf414e80b344412a (diff)
Remove tuples and objects in favor of tables πŸ›’
This refactores the parser tests to make them more concise and flexible with regards to spans.
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()
}
}