diff --git a/src/func.rs b/src/func.rs index bd273b4a3..bff4fdab3 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 OptionExt for Option { 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 115793b12..b4cfd2e27 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -14,12 +14,12 @@ pub fn align(call: FuncCall, _: &ParseState) -> Pass { let mut f = Feedback::new(); let mut args = call.args; let node = AlignNode { - content: args.pos.get::(), - aligns: args.pos.all::>().collect(), - h: args.key.get::>("horizontal", &mut f), - v: args.key.get::>("vertical", &mut f), + content: args.take::(), + aligns: args.take_all_num_vals::>().collect(), + h: args.take_with_key::<_, Spanned>("horizontal", &mut f), + v: args.take_with_key::<_, Spanned>("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 3ca3ae443..3637f0724 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -10,11 +10,11 @@ pub fn boxed(call: FuncCall, _: &ParseState) -> Pass { let mut f = Feedback::new(); let mut args = call.args; let node = BoxNode { - content: args.pos.get::().unwrap_or(SyntaxTree::new()), - width: args.key.get::("width", &mut f), - height: args.key.get::("height", &mut f), + content: args.take::().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 d445a2468..8787cc91a 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 { let mut args = call.args; let node = FontNode { - content: args.pos.get::(), - size: args.pos.get::(), - style: args.key.get::("style", &mut f), - weight: args.key.get::("weight", &mut f), - width: args.key.get::("width", &mut f), - list: { - args.pos.all::() - .map(|s| s.0.to_lowercase()) - .collect() - }, - classes: { - args.key.all::() - .collect::>() - .into_iter() - .map(|(class, mut tuple)| { - let fallback = tuple.all::() - .map(|s| s.0.to_lowercase()) - .collect(); - (class.v.0, fallback) - }) - .collect() - }, + content: args.take::(), + size: args.take::(), + 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::() + .map(|s| s.0.to_lowercase()) + .collect(), + classes: args.take_all_str::() + .map(|(class, mut table)| { + let fallback = table.take_all_num_vals::() + .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 b47749ea5..7e4e6e546 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -20,17 +20,17 @@ pub fn page(call: FuncCall, _: &ParseState) -> Pass { let mut f = Feedback::new(); let mut args = call.args; let node = PageNode { - paper: args.pos.get::(), - width: args.key.get::("width", &mut f), - height: args.key.get::("height", &mut f), - margins: args.key.get::("margins", &mut f), - left: args.key.get::("left", &mut f), - right: args.key.get::("right", &mut f), - top: args.key.get::("top", &mut f), - bottom: args.key.get::("bottom", &mut f), - flip: args.key.get::("flip", &mut f).unwrap_or(false), + paper: args.take::(), + 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 { 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 ad30a1221..81112cbda 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -22,11 +22,11 @@ fn spacing(call: FuncCall, axis: SpecAxis) -> Pass { let mut f = Feedback::new(); let mut args = call.args; let node = SpacingNode { - spacing: args.pos.expect::(&mut f) + spacing: args.expect::(&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 6e83571af..9df554012 100644 --- a/src/library/val.rs +++ b/src/library/val.rs @@ -7,7 +7,7 @@ use super::*; pub fn val(call: FuncCall, _: &ParseState) -> Pass { let mut args = call.args; let node = ValNode { - content: args.pos.get::(), + content: args.take::(), }; Pass::node(node, Feedback::new()) } diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs index a9097444d..a686596cc 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 98c673924..e92a75b05 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>), /// An operation that adds the contained expressions. @@ -49,6 +45,8 @@ pub enum Expr { Mul(Box>, Box>), /// An operation that divides the contained expressions. Div(Box>, Box>), + /// 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); - -impl Tuple { - /// Create an empty tuple. - pub fn new() -> Self { - Self(vec![]) - } - - /// Add an element. - pub fn push(&mut self, item: Spanned) { - 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(&mut self, f: &mut Feedback) -> Option { - 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(&mut self) -> Option { - 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 + '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, - /// The elements of the tuple. - pub tuple: Spanned, -} - -impl NamedTuple { - /// Create a named tuple from a name and a tuple. - pub fn new(name: Spanned, tuple: Spanned) -> 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); - -/// A key-value pair in an object. -#[derive(Debug, Clone, PartialEq)] -pub struct Pair { - pub key: Spanned, - pub value: Spanned, -} - -impl Object { - /// Create an empty object. - pub fn new() -> Self { - Self(vec![]) - } - - /// Add a pair to object. - pub fn push(&mut self, pair: Spanned) { - 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(&mut self, key: &str, f: &mut Feedback) -> Option { - 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, 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; -/// An entry in a table expression. -/// -/// Contains the key's span and the value. -pub struct TableExprEntry { - pub key: Span, - pub val: Spanned, -} - 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 + pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option where - T: TryFromExpr, K: Into>, + 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 + 'a + where + T: TryFromExpr, + { + self.take_all_num::().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, +} + +impl TableExprEntry { + /// Create a new entry. + pub fn new(key: Span, val: Spanned) -> Self { + Self { key, val } + } + + /// Create an entry for a positional argument with the same span for key and + /// value. + pub fn val(val: Spanned) -> 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::(1, &mut f), Some(false)); - assert_eq!(table.take_with_key::("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 fe0bf6b55..fdc105a35 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(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(sl: usize, sc: usize, el: usize, ec: usize, v: T) -> Spanned { + Spanned::new(v, Span::new(Pos::new(sl, sc), Pos::new(el, ec))) + } + + // Enables tests to optionally specify spans. + impl From for Spanned { + fn from(t: T) -> Self { + Spanned::zero(t) + } + } + + pub fn debug_func(call: FuncCall, _: &ParseState) -> Pass { + 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> { + unimplemented!() + } + } +} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 2980cce2b..88c930cb7 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -17,39 +17,7 @@ pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass; #[derive(Debug, Clone, PartialEq)] pub struct FuncCall { pub name: Spanned, - 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) { - 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 { - 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 { + fn parse_func_header(&mut self) -> Option { 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> { 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> { 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) -> Spanned { - 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) -> Spanned { + 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, bool) { + fn parse_table(&mut self, parens: bool) -> (Spanned, bool) { let start = self.pos(); - self.assert(Token::LeftParen); - - let mut tuple = Tuple::new(); - let mut commaless = true; - loop { - self.skip_white(); - if self.eof() || 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); - - self.skip_white(); - if self.eof() || self.check(Token::RightParen) { - break; - } - - self.expect_at(Token::Comma, behind_expr); - commaless = false; + if parens { + self.assert(Token::LeftParen); } - self.expect(Token::RightParen); - let end = self.pos(); - let coercable = commaless && !tuple.0.is_empty(); + let mut table = TableExpr::new(); + let mut coercable = true; - (Spanned::new(tuple, Span::new(start, end)), coercable) - } - - fn parse_object(&mut self) -> Spanned { - let start = self.pos(); - self.assert(Token::LeftBrace); - - let mut object = Object::new(); loop { self.skip_white(); - if self.eof() || self.check(Token::RightBrace) { + if self.eof() || (parens && self.check(Token::RightParen)) { break; } - let key = try_opt_or!(self.parse_ident(), { - self.expected("key"); - continue; - }); + let behind_arg; - let after_key = self.pos(); - self.skip_white(); - if !self.expect_at(Token::Equals, after_key) { - continue; + if let Some(ident) = self.parse_ident() { + // This could be a keyword argument, a function call or a simple + // identifier. + self.skip_white(); + + if self.check_eat(Token::Equals).is_some() { + self.skip_white(); + + let key = ident; + self.feedback.decorations + .push(Spanned::new(Decoration::TableKey, key.span)); + + let val = try_opt_or!(self.parse_expr(), { + self.expected("value"); + continue; + }); + + coercable = false; + behind_arg = val.span.end; + table.insert(key.v.0, TableExprEntry::new(key.span, val)); + + } else if self.check(Token::LeftParen) { + let call = self.parse_func_call(ident); + let expr = call.map(|call| Expr::Call(call)); + + behind_arg = expr.span.end; + table.push(TableExprEntry::val(expr)); + } else { + let expr = ident.map(|id| Expr::Ident(id)); + + 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); + } - Spanned::new(object, Span::new(start, end)) + coercable = coercable && !table.is_empty(); + + let end = self.pos(); + (Spanned::new(table, Span::new(start, end)), coercable) } } @@ -659,151 +566,153 @@ fn unescape_raw(raw: &str) -> Vec { #[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)*], []); - }; - - ($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)); - - let state = ParseState { scope }; - let pass = parse($source, Pos::ZERO, &state); - - // Test tree. - let (exp, cmp) = span_vec![$($tree)*]; - check($source, exp, pass.output, cmp); - - // Test diagnostics. - let (exp, cmp) = span_vec![$($diagnostics)*]; - let exp = exp.into_iter() - .map(|s: Spanned<&str>| s.map(|e| e.to_string())) - .collect::>(); - let found = pass.feedback.diagnostics.into_iter() - .map(|s| s.map(|e| e.message)) - .collect::>(); - check($source, exp, found, cmp); - - // Test decos. - $(let (exp, cmp) = span_vec![$($decos)*]; - check($source, exp, pass.feedback.decorations, cmp);)? - }; - } - - /// Shorthand for `p!("[val: ...]" => par![func!("val", ...)])`. - macro_rules! pval { - ($header:expr => $($tts:tt)*) => { - p!(concat!("[val: ", $header, "]") => [par![func!("val": $($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(v: T) -> Spanned { Spanned::zero(v) } - macro_rules! tuple { - ($($tts:tt)*) => { - Expr::Tuple(Tuple(span_vec![$($tts)*].0)) - }; - } - - 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)) - )) - }; - } - - 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), - })),*])) - }; - } - - macro_rules! raw { + macro_rules! R { ($($line:expr),* $(,)?) => { SyntaxNode::Raw(vec![$($line.to_string()),*]) }; } - macro_rules! par { - ($($tts:tt)*) => { - SyntaxNode::Par(span_vec![$($tts)*].0) + macro_rules! P { + ($($tts:tt)*) => { SyntaxNode::Par(Tree![@$($tts)*]) }; + } + + macro_rules! F { + ($($tts:tt)*) => { SyntaxNode::boxed(DebugNode(Call!(@$($tts)*))) } + } + + // ------------------------ Construct Expressions ----------------------- // + + 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()) } + + macro_rules! Tree { + (@$($node:expr),* $(,)?) => { + vec![$(Into::>::into($node)),*] + }; + ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) }; + } + + macro_rules! Table { + (@table=$table:expr,) => {}; + (@table=$table:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{ + let key = Into::>::into($key); + let val = Into::>::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::>::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 Neg>>(e1: T) -> Expr { + Expr::Neg(Box::new(e1.into())) + } + fn Add>>(e1: T, e2: T) -> Expr { + Expr::Add(Box::new(e1.into()), Box::new(e2.into())) + } + fn Sub>>(e1: T, e2: T) -> Expr { + Expr::Sub(Box::new(e1.into()), Box::new(e2.into())) + } + fn Mul>>(e1: T, e2: T) -> Expr { + Expr::Mul(Box::new(e1.into()), Box::new(e2.into())) + } + fn Div>>(e1: T, e2: T) -> Expr { + Expr::Div(Box::new(e1.into()), Box::new(e2.into())) + } + + macro_rules! Call { + (@$name:expr $(; $($tts:tt)*)?) => {{ + let name = Into::>::into($name); + FuncCall { + name: name.map(|n| Ident(n.to_string())), + args: Table![@$($($tts)*)?], + } + }}; + ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) }; + } + + // ----------------------------- 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! func { - ($name:tt - $(: ($($pos:tt)*) $(, { $($key:tt => $value:expr),* })? )? - $(; $($body:tt)*)? - ) => {{ - #[allow(unused_mut)] - let mut args = FuncArgs::new(); - $( - let items: SpanVec = 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 }; + // Test expressions. + macro_rules! v { + ($src:expr => $($tts:tt)*) => { + t!(concat!("[val: ", $src, "]") => P![F!("val"; $($tts)*)]); + } } - #[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")); + // 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::>(); + check($src, exp, found, true); + }; } + // 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); + }; + } + + fn parse_default(src: &str) -> Pass { + 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) + } + + // -------------------------------- 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("🌍")]]); + 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("🌍")]); - // 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)], + 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)], - ); + fn test_parse_colon_starting_func_args() { + // Just colon without args. + e!("[val:]" => ); - // 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")]); - - // 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")]]); - - // Whitespace / comments around colon. - p!("[val\n:\ntrue]" => [par![func!("val": (Bool(true)))]]); - p!("[val/*:*/://\ntrue]" => [par![func!("val": (Bool(true)))]]); + e!("[val/\"]" => s(0,4, 0,4, "expected colon"), + s(0,7, 0,7, "expected closing bracket")); } #[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"))); + 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")); - // Trailing comma. - pval!("a," => (Id("a"))); + // 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")) + ])]) + )) + ])); + } - // Simple coerced tuple. - pval!("(hi)" => (Id("hi"))); + #[test] + 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")); - // 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)))), - ))) - ))])]); + // 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"), - ]); + v!("\"hello" => Str("hello]")); + e!("[val: \"hello]" => s(0,13, 0,13, "expected quote"), + s(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")]); + // 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"))); + 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")]); - // 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)) - }); + // 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)); - // 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")], - ); + // 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")], - ); - - // 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")], - ); - - // 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")], - ); - - // 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")], - ); - } - - #[test] - fn parse_nested_tuples_and_objects() { - pval!("(1, { ab=(), d = (3, 14pt) }), false" => ( - tuple!( - Num(1.0), - object!( - "ab" => tuple!(), - "d" => tuple!(Num(3.0), Len(Length::pt(14.0))), - ), - ), - Bool(false), + 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)) )); + + // 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"), + )); + + // Unclosed. + v!("lang(δΈ­ζ–‡]" => Call!("lang"; Id("δΈ­ζ–‡"))); + e!("[val: lang(δΈ­ζ–‡]" => s(0,13, 0,13, "expected closing paren")); + + // 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_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")))]), + fn test_parse_tables_nested() { + v!("(1, ( ab=(), d = (3, 14pt) )), false" => + Table![ + Num(1.0), + Table!( + "ab" => Table![], + "d" => Table!(Num(3.0), Len(Length::pt(14.0))), + ), ], - [], - [(2:1, 2:4, ResolvedFunc)], + Bool(false), ); + } - // 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)], - ); + #[test] + 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")); - // 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)], - ); + // 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 aec54ded9..000000000 --- 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(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 { - let tree = call.args.pos.get::(); - Pass::node(DebugNode(call, tree), Feedback::new()) -} - -#[derive(Debug, Clone, PartialEq)] -pub struct DebugNode(pub FuncCall, pub Option); - -#[async_trait(?Send)] -impl Layout for DebugNode { - async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass> { - unimplemented!() - } -} diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index bf172651c..cafc7727e 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::>(); - 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::>::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::>::into($token)),*]; + let found = Tokens::new($src, Pos::ZERO, $mode).collect::>(); + 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 2b49d4517..2d07ee5e1 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 { nums: BTreeMap, strs: BTreeMap, @@ -129,6 +129,22 @@ impl Table { pub fn values(&self) -> impl Iterator { 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 { + self.nums.into_iter() + } + + /// Iterate over the string key-value pairs. + pub fn into_strs(self) -> std::collections::btree_map::IntoIter { + self.strs.into_iter() + } + + /// Move into an owned iterator over all values in the table. + pub fn into_values(self) -> impl Iterator { + self.nums.into_iter().map(|(_, v)| v) + .chain(self.strs.into_iter().map(|(_, v)| v)) + } } impl<'a, K, V> Index for Table @@ -142,37 +158,50 @@ where } } +impl Default for Table { + fn default() -> Self { + Self::new() + } +} + +impl Eq for Table {} + +impl PartialEq for Table { + fn eq(&self, other: &Self) -> bool { + self.nums().eq(other.nums()) && self.strs().eq(other.strs()) + } +} + impl Debug for Table { 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() } }