Remove tuples and objects in favor of tables 🛢

This refactores the parser tests to make them more concise and flexible with regards to spans.
This commit is contained in:
Laurenz 2020-08-16 14:23:13 +02:00
parent 84f30fb735
commit 9f6137d8a8
14 changed files with 865 additions and 1269 deletions

View File

@ -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");
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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.

View File

@ -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());
}

View File

@ -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!()
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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!()
}
}

View File

@ -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)));
}
}

View File

@ -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()
}
}