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:
parent
84f30fb735
commit
9f6137d8a8
15
src/func.rs
15
src/func.rs
@ -8,8 +8,8 @@ pub mod prelude {
|
||||
pub use crate::layout::Command::{self, *};
|
||||
pub use crate::style::*;
|
||||
pub use crate::syntax::expr::*;
|
||||
pub use crate::syntax::parsing::{parse, FuncArgs, FuncCall, ParseState};
|
||||
pub use crate::syntax::span::{Span, SpanVec, Spanned};
|
||||
pub use crate::syntax::parsing::{parse, FuncCall, ParseState};
|
||||
pub use crate::syntax::span::{Pos, Span, SpanVec, Spanned};
|
||||
pub use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||
pub use crate::{Pass, Feedback};
|
||||
pub use super::*;
|
||||
@ -41,14 +41,3 @@ impl<T> OptionExt<T> for Option<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate `unexpected argument` errors for all remaining arguments.
|
||||
pub fn drain_args(args: FuncArgs, f: &mut Feedback) {
|
||||
for arg in args.pos.0 {
|
||||
error!(@f, arg.span, "unexpected argument");
|
||||
}
|
||||
|
||||
for arg in args.key.0 {
|
||||
error!(@f, arg.span, "unexpected argument");
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
@ -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!()
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
77
src/table.rs
77
src/table.rs
@ -12,7 +12,7 @@ use std::ops::Index;
|
||||
///
|
||||
/// The keys of a table may be strings or integers (`u64`). The table is generic
|
||||
/// over the value type.
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub struct Table<V> {
|
||||
nums: BTreeMap<u64, V>,
|
||||
strs: BTreeMap<String, V>,
|
||||
@ -129,6 +129,22 @@ impl<V> Table<V> {
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Iterate over the number key-value pairs.
|
||||
pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
|
||||
self.nums.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over the string key-value pairs.
|
||||
pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
|
||||
self.strs.into_iter()
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over all values in the table.
|
||||
pub fn into_values(self) -> impl Iterator<Item = V> {
|
||||
self.nums.into_iter().map(|(_, v)| v)
|
||||
.chain(self.strs.into_iter().map(|(_, v)| v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> Index<K> for Table<V>
|
||||
@ -142,37 +158,50 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for Table<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Eq> Eq for Table<V> {}
|
||||
|
||||
impl<V: PartialEq> PartialEq for Table<V> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.nums().eq(other.nums()) && self.strs().eq(other.strs())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Debug> Debug for Table<V> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("(")?;
|
||||
if f.alternate() && (!self.nums.is_empty() || !self.strs.is_empty()) {
|
||||
f.write_str("\n")?;
|
||||
if self.is_empty() {
|
||||
return f.write_str("()");
|
||||
}
|
||||
|
||||
let len = self.len();
|
||||
let nums = self.nums().map(|(k, v)| (k as &dyn Debug, v));
|
||||
let strings = self.strs().map(|(k, v)| (k as &dyn Debug, v));
|
||||
let pairs = nums.chain(strings);
|
||||
let mut builder = f.debug_tuple("");
|
||||
|
||||
for (i, (key, value)) in pairs.enumerate() {
|
||||
if f.alternate() {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
key.fmt(f)?;
|
||||
if f.alternate() {
|
||||
f.write_str(" = ")?;
|
||||
} else {
|
||||
f.write_str("=")?;
|
||||
}
|
||||
value.fmt(f)?;
|
||||
if f.alternate() {
|
||||
f.write_str(",\n")?;
|
||||
} else if i + 1 < len {
|
||||
f.write_str(", ")?;
|
||||
struct Entry<'a>(&'a dyn Debug, &'a dyn Debug);
|
||||
impl<'a> Debug for Entry<'a> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)?;
|
||||
if f.alternate() {
|
||||
f.write_str(" = ")?;
|
||||
} else {
|
||||
f.write_str("=")?;
|
||||
}
|
||||
self.1.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
f.write_str(")")
|
||||
for (key, value) in self.nums() {
|
||||
builder.field(&Entry(&key, &value));
|
||||
}
|
||||
|
||||
for (key, value) in self.strs() {
|
||||
builder.field(&Entry(&key, &value));
|
||||
}
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user