mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Merge pull request #1 from typst/named_tuples
Add named tuples and hex color tokens
This commit is contained in:
commit
0fd327bbc9
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
use std::fmt::{self, Write, Debug, Formatter};
|
use std::fmt::{self, Write, Debug, Formatter};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::u8;
|
||||||
|
|
||||||
use crate::error::Errors;
|
use crate::error::Errors;
|
||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
@ -23,8 +26,12 @@ pub enum Expr {
|
|||||||
Size(Size),
|
Size(Size),
|
||||||
/// A bool: `true, false`.
|
/// A bool: `true, false`.
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
/// A color value, including the alpha channel: `#f79143ff`
|
||||||
|
Color(RgbaColor),
|
||||||
/// A tuple: `(false, 12cm, "hi")`.
|
/// A tuple: `(false, 12cm, "hi")`.
|
||||||
Tuple(Tuple),
|
Tuple(Tuple),
|
||||||
|
/// A named tuple: `cmyk(37.7, 0, 3.9, 1.1)`.
|
||||||
|
NamedTuple(NamedTuple),
|
||||||
/// An object: `{ fit: false, size: 12pt }`.
|
/// An object: `{ fit: false, size: 12pt }`.
|
||||||
Object(Object),
|
Object(Object),
|
||||||
}
|
}
|
||||||
@ -39,7 +46,9 @@ impl Expr {
|
|||||||
Number(_) => "number",
|
Number(_) => "number",
|
||||||
Size(_) => "size",
|
Size(_) => "size",
|
||||||
Bool(_) => "bool",
|
Bool(_) => "bool",
|
||||||
|
Color(_) => "color",
|
||||||
Tuple(_) => "tuple",
|
Tuple(_) => "tuple",
|
||||||
|
NamedTuple(_) => "named tuple",
|
||||||
Object(_) => "object",
|
Object(_) => "object",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,7 +63,9 @@ impl Debug for Expr {
|
|||||||
Number(n) => n.fmt(f),
|
Number(n) => n.fmt(f),
|
||||||
Size(s) => s.fmt(f),
|
Size(s) => s.fmt(f),
|
||||||
Bool(b) => b.fmt(f),
|
Bool(b) => b.fmt(f),
|
||||||
|
Color(c) => c.fmt(f),
|
||||||
Tuple(t) => t.fmt(f),
|
Tuple(t) => t.fmt(f),
|
||||||
|
NamedTuple(t) => t.fmt(f),
|
||||||
Object(o) => o.fmt(f),
|
Object(o) => o.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,6 +108,116 @@ impl Debug for Ident {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An 8-bit RGBA color.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```typst
|
||||||
|
/// [box: background=#423abaff]
|
||||||
|
/// ^^^^^^^^
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct RgbaColor {
|
||||||
|
/// Red channel.
|
||||||
|
pub r: u8,
|
||||||
|
/// Green channel.
|
||||||
|
pub g: u8,
|
||||||
|
/// Blue channel.
|
||||||
|
pub b: u8,
|
||||||
|
/// Alpha channel.
|
||||||
|
pub a: u8,
|
||||||
|
/// Indicates whether this is a user-provided value or a
|
||||||
|
/// default value provided as a fail-over by the parser.
|
||||||
|
/// This color may be overwritten if this property is true.
|
||||||
|
pub healed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RgbaColor {
|
||||||
|
/// Constructs a new color.
|
||||||
|
pub fn new(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
|
||||||
|
RgbaColor { r, g, b, a, healed: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new color with the healed property set to true.
|
||||||
|
pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
|
||||||
|
RgbaColor { r, g, b, a, healed: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for RgbaColor {
|
||||||
|
type Err = ParseColorError;
|
||||||
|
|
||||||
|
/// Constructs a new color from a hex string like `7a03c2`.
|
||||||
|
/// Do not specify a leading `#`.
|
||||||
|
fn from_str(hex_str: &str) -> Result<RgbaColor, Self::Err> {
|
||||||
|
if !hex_str.is_ascii() {
|
||||||
|
return Err(ParseColorError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = hex_str.len();
|
||||||
|
let long = len == 6 || len == 8;
|
||||||
|
let short = len == 3 || len == 4;
|
||||||
|
let alpha = len == 4 || len == 8;
|
||||||
|
|
||||||
|
if !long && !short {
|
||||||
|
return Err(ParseColorError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut values: [u8; 4] = [255; 4];
|
||||||
|
|
||||||
|
for elem in if alpha { 0..4 } else { 0..3 } {
|
||||||
|
let item_len = if long { 2 } else { 1 };
|
||||||
|
let pos = elem * item_len;
|
||||||
|
|
||||||
|
let item = &hex_str[pos..(pos+item_len)];
|
||||||
|
values[elem] = u8::from_str_radix(item, 16)
|
||||||
|
.map_err(|_| ParseColorError)?;
|
||||||
|
|
||||||
|
if short {
|
||||||
|
// Duplicate number for shorthand notation, i.e. `a` -> `aa`
|
||||||
|
values[elem] += values[elem] * 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RgbaColor::new(values[0], values[1], values[2], values[3]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RgbaColor {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
if f.alternate() {
|
||||||
|
f.write_str("rgba(")?;
|
||||||
|
write!(f, "r: {:02}, ", self.r)?;
|
||||||
|
write!(f, "g: {:02}, ", self.g)?;
|
||||||
|
write!(f, "b: {:02}, ", self.b)?;
|
||||||
|
write!(f, "a: {:02}", self.a)?;
|
||||||
|
f.write_char(')')?;
|
||||||
|
} else {
|
||||||
|
f.write_char('#')?;
|
||||||
|
write!(f, "{:02x}", self.r)?;
|
||||||
|
write!(f, "{:02x}", self.g)?;
|
||||||
|
write!(f, "{:02x}", self.b)?;
|
||||||
|
write!(f, "{:02x}", self.a)?;
|
||||||
|
}
|
||||||
|
if self.healed {
|
||||||
|
f.write_fmt(format_args!(" [healed]"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error returned when parsing a [`RgbaColor`] from a string fails.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct ParseColorError;
|
||||||
|
|
||||||
|
impl std::error::Error for ParseColorError {}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseColorError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("invalid color")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An untyped sequence of expressions.
|
/// An untyped sequence of expressions.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -185,6 +306,35 @@ impl Debug for Tuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 and where it is in the user source.
|
||||||
|
pub name: Spanned<Ident>,
|
||||||
|
/// The elements of the tuple.
|
||||||
|
pub tuple: Spanned<Tuple>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamedTuple {
|
||||||
|
/// Create a named tuple from a tuple.
|
||||||
|
pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> NamedTuple {
|
||||||
|
NamedTuple { 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.
|
/// A key-value collection of identifiers and associated expressions.
|
||||||
///
|
///
|
||||||
/// The pairs themselves are not spanned, but the combined spans can easily be
|
/// The pairs themselves are not spanned, but the combined spans can easily be
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Parsing of source code into syntax models.
|
//! Parsing of source code into syntax models.
|
||||||
|
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::{Pass, Feedback};
|
use crate::{Pass, Feedback};
|
||||||
use super::func::{FuncHeader, FuncArgs, FuncArg};
|
use super::func::{FuncHeader, FuncArgs, FuncArg};
|
||||||
@ -187,6 +188,14 @@ impl<'s> FuncParser<'s> {
|
|||||||
// If we have an identifier we might have a keyword argument,
|
// If we have an identifier we might have a keyword argument,
|
||||||
// otherwise its for sure a postional argument.
|
// otherwise its for sure a postional argument.
|
||||||
if let Some(ident) = p.parse_ident() {
|
if let Some(ident) = p.parse_ident() {
|
||||||
|
// This could still be a named tuple
|
||||||
|
if let Some(Token::LeftParen) = p.peekv() {
|
||||||
|
return Ok(FuncArg::Pos(
|
||||||
|
p.parse_named_tuple(ident)
|
||||||
|
.map(|t| Expr::NamedTuple(t))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
p.skip_whitespace();
|
p.skip_whitespace();
|
||||||
|
|
||||||
if let Some(Token::Equals) = p.peekv() {
|
if let Some(Token::Equals) = p.peekv() {
|
||||||
@ -223,7 +232,16 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Some(match first.v {
|
Some(match first.v {
|
||||||
Token::ExprIdent(i) => take!((Expr::Ident(Ident(i.to_string())))),
|
Token::ExprIdent(i) => {
|
||||||
|
let name = take!(Ident(i.to_string()));
|
||||||
|
|
||||||
|
// This could be a named tuple or an identifier
|
||||||
|
if let Some(Token::LeftParen) = self.peekv() {
|
||||||
|
self.parse_named_tuple(name).map(|t| Expr::NamedTuple(t))
|
||||||
|
} else {
|
||||||
|
name.map(|i| Expr::Ident(i))
|
||||||
|
}
|
||||||
|
},
|
||||||
Token::ExprStr { string, terminated } => {
|
Token::ExprStr { string, terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
self.expected_at("quote", first.span.end);
|
self.expected_at("quote", first.span.end);
|
||||||
@ -235,16 +253,25 @@ impl<'s> FuncParser<'s> {
|
|||||||
Token::ExprNumber(n) => take!(Expr::Number(n)),
|
Token::ExprNumber(n) => take!(Expr::Number(n)),
|
||||||
Token::ExprSize(s) => take!(Expr::Size(s)),
|
Token::ExprSize(s) => take!(Expr::Size(s)),
|
||||||
Token::ExprBool(b) => take!(Expr::Bool(b)),
|
Token::ExprBool(b) => take!(Expr::Bool(b)),
|
||||||
|
Token::ExprHex(s) => {
|
||||||
|
if let Ok(color) = RgbaColor::from_str(s) {
|
||||||
|
take!(Expr::Color(color))
|
||||||
|
} else {
|
||||||
|
// Heal color by assuming black
|
||||||
|
self.feedback.errors.push(err!(first.span; "invalid color"));
|
||||||
|
take!(Expr::Color(RgbaColor::new_healed(0, 0, 0, 255)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
Token::LeftParen => self.parse_tuple(),
|
Token::LeftParen => self.parse_tuple().map(|t| Expr::Tuple(t)),
|
||||||
Token::LeftBrace => self.parse_object(),
|
Token::LeftBrace => self.parse_object().map(|o| Expr::Object(o)),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a tuple expression: `(<expr>, ...)`.
|
/// Parse a tuple expression: `(<expr>, ...)`.
|
||||||
fn parse_tuple(&mut self) -> Spanned<Expr> {
|
fn parse_tuple(&mut self) -> Spanned<Tuple> {
|
||||||
let token = self.eat();
|
let token = self.eat();
|
||||||
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
|
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
|
||||||
|
|
||||||
@ -252,11 +279,17 @@ impl<'s> FuncParser<'s> {
|
|||||||
// missing a `value` when an invalid token is encoutered.
|
// missing a `value` when an invalid token is encoutered.
|
||||||
self.parse_collection(Some(Token::RightParen),
|
self.parse_collection(Some(Token::RightParen),
|
||||||
|p| p.parse_expr().ok_or(("value", None)))
|
|p| p.parse_expr().ok_or(("value", None)))
|
||||||
.map(|tuple| Expr::Tuple(tuple))
|
}
|
||||||
|
|
||||||
|
/// Parse a tuple expression: `name(<expr>, ...)` with a given identifier.
|
||||||
|
fn parse_named_tuple(&mut self, name: Spanned<Ident>) -> Spanned<NamedTuple> {
|
||||||
|
let tuple = self.parse_tuple();
|
||||||
|
let span = Span::merge(name.span, tuple.span);
|
||||||
|
Spanned::new(NamedTuple::new(name, tuple), span)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an object expression: `{ <key>: <value>, ... }`.
|
/// Parse an object expression: `{ <key>: <value>, ... }`.
|
||||||
fn parse_object(&mut self) -> Spanned<Expr> {
|
fn parse_object(&mut self) -> Spanned<Object> {
|
||||||
let token = self.eat();
|
let token = self.eat();
|
||||||
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftBrace));
|
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftBrace));
|
||||||
|
|
||||||
@ -282,7 +315,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
let value = p.parse_expr().ok_or(("value", None))?;
|
let value = p.parse_expr().ok_or(("value", None))?;
|
||||||
|
|
||||||
Ok(Pair { key, value })
|
Ok(Pair { key, value })
|
||||||
}).map(|object| Expr::Object(object))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a comma-separated collection where each item is parsed through
|
/// Parse a comma-separated collection where each item is parsed through
|
||||||
@ -513,6 +546,19 @@ mod tests {
|
|||||||
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
|
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
|
||||||
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
|
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
|
||||||
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
|
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
|
||||||
|
|
||||||
|
fn Clr(r: u8, g: u8, b: u8, a: u8) -> Expr {
|
||||||
|
Expr::Color(RgbaColor::new(r, g, b, a))
|
||||||
|
}
|
||||||
|
fn ClrStr(color: &str) -> Expr {
|
||||||
|
Expr::Color(RgbaColor::from_str(color).expect("invalid test color"))
|
||||||
|
}
|
||||||
|
fn ClrStrHealed() -> Expr {
|
||||||
|
let mut c = RgbaColor::from_str("000f").expect("invalid test color");
|
||||||
|
c.healed = true;
|
||||||
|
Expr::Color(c)
|
||||||
|
}
|
||||||
|
|
||||||
fn T(text: &str) -> Node { Node::Text(text.to_string()) }
|
fn T(text: &str) -> Node { Node::Text(text.to_string()) }
|
||||||
|
|
||||||
/// Create a raw text node.
|
/// Create a raw text node.
|
||||||
@ -529,6 +575,16 @@ mod tests {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a named tuple expression.
|
||||||
|
macro_rules! named_tuple {
|
||||||
|
($name:expr $(, $items:expr)* $(,)?) => {
|
||||||
|
Expr::NamedTuple(NamedTuple::new(
|
||||||
|
zspan(Ident($name.to_string())),
|
||||||
|
zspan(Tuple { items: spanned![vec $($items),*].0 })
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Create an object expression.
|
/// Create an object expression.
|
||||||
macro_rules! object {
|
macro_rules! object {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {
|
($($key:expr => $value:expr),* $(,)?) => {
|
||||||
@ -603,6 +659,15 @@ mod tests {
|
|||||||
(@body) => (None);
|
(@body) => (None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_color_strings() {
|
||||||
|
assert_eq!(Clr(0xf6, 0x12, 0x43, 0xff), ClrStr("f61243ff"));
|
||||||
|
assert_eq!(Clr(0xb3, 0xd8, 0xb3, 0xff), ClrStr("b3d8b3"));
|
||||||
|
assert_eq!(Clr(0xfc, 0xd2, 0xa9, 0xad), ClrStr("fCd2a9AD"));
|
||||||
|
assert_eq!(Clr(0x22, 0x33, 0x33, 0xff), ClrStr("233"));
|
||||||
|
assert_eq!(Clr(0x11, 0x11, 0x11, 0xbb), ClrStr("111b"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unescape_strings() {
|
fn unescape_strings() {
|
||||||
fn test(string: &str, expected: &str) {
|
fn test(string: &str, expected: &str) {
|
||||||
@ -747,43 +812,96 @@ mod tests {
|
|||||||
p!("[val: 3.14]" => [func!("val": (Num(3.14)), {})]);
|
p!("[val: 3.14]" => [func!("val": (Num(3.14)), {})]);
|
||||||
p!("[val: 4.5cm]" => [func!("val": (Sz(Size::cm(4.5))), {})]);
|
p!("[val: 4.5cm]" => [func!("val": (Sz(Size::cm(4.5))), {})]);
|
||||||
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
|
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
|
||||||
|
p!("[val: #f7a20500]" => [func!("val": (ClrStr("f7a20500")), {})]);
|
||||||
|
|
||||||
// Unclosed string.
|
// Unclosed string.
|
||||||
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
|
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
|
||||||
(0:13, 0:13, "expected quote"),
|
(0:13, 0:13, "expected quote"),
|
||||||
(0:13, 0:13, "expected closing bracket"),
|
(0:13, 0:13, "expected closing bracket"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
//Invalid colors
|
||||||
|
p!("[val: #12345]" => [func!("val": (ClrStrHealed()), {})], [
|
||||||
|
(0:6, 0:12, "invalid color"),
|
||||||
|
]);
|
||||||
|
p!("[val: #a5]" => [func!("val": (ClrStrHealed()), {})], [
|
||||||
|
(0:6, 0:9, "invalid color"),
|
||||||
|
]);
|
||||||
|
p!("[val: #14b2ah]" => [func!("val": (ClrStrHealed()), {})], [
|
||||||
|
(0:6, 0:13, "invalid color"),
|
||||||
|
]);
|
||||||
|
p!("[val: #f075ff011]" => [func!("val": (ClrStrHealed()), {})], [
|
||||||
|
(0:6, 0:16, "invalid color"),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_tuples() {
|
fn parse_tuples() {
|
||||||
// Empty tuple
|
// Empty tuple
|
||||||
p!("[val: ()]" => [func!("val": (tuple!()), {})]);
|
p!("[val: ()]" => [func!("val": (tuple!()), {})]);
|
||||||
|
p!("[val: empty()]" => [func!("val": (named_tuple!("empty")), {})]);
|
||||||
|
|
||||||
// Invalid value
|
// Invalid value
|
||||||
p!("[val: (🌎)]" =>
|
p!("[val: (🌎)]" =>
|
||||||
[func!("val": (tuple!()), {})],
|
[func!("val": (tuple!()), {})],
|
||||||
[(0:7, 0:8, "expected value, found invalid token")],
|
[(0:7, 0:8, "expected value, found invalid token")],
|
||||||
);
|
);
|
||||||
|
p!("[val: sound(\x07)]" =>
|
||||||
|
[func!("val": (named_tuple!("sound")), {})],
|
||||||
|
[(0:12, 0:13, "expected value, found invalid token")],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalid tuple name
|
||||||
|
p!("[val: 👠(\"abc\", 13e-5)]" =>
|
||||||
|
[func!("val": (tuple!(Str("abc"), Num(13.0e-5))), {})],
|
||||||
|
[(0:6, 0:7, "expected argument, found invalid token")],
|
||||||
|
);
|
||||||
|
|
||||||
// Unclosed tuple
|
// Unclosed tuple
|
||||||
p!("[val: (hello]" =>
|
p!("[val: (hello]" =>
|
||||||
[func!("val": (tuple!(Id("hello"))), {})],
|
[func!("val": (tuple!(Id("hello"))), {})],
|
||||||
[(0:12, 0:12, "expected closing paren")],
|
[(0:12, 0:12, "expected closing paren")],
|
||||||
);
|
);
|
||||||
|
p!("[val: lang(中文]" =>
|
||||||
|
[func!("val": (named_tuple!("lang", Id("中文"))), {})],
|
||||||
|
[(0:13, 0:13, "expected closing paren")],
|
||||||
|
);
|
||||||
|
|
||||||
// Valid values
|
// Valid values
|
||||||
p!("[val: (1, 2)]" => [func!("val": (tuple!(Num(1.0), Num(2.0))), {})]);
|
p!("[val: (1, 2)]" => [func!("val": (tuple!(Num(1.0), Num(2.0))), {})]);
|
||||||
p!("[val: (\"s\",)]" => [func!("val": (tuple!(Str("s"))), {})]);
|
p!("[val: (\"s\",)]" => [func!("val": (tuple!(Str("s"))), {})]);
|
||||||
|
p!("[val: cmyk(1, 46, 0, 0)]" =>
|
||||||
|
[func!("val": (named_tuple!(
|
||||||
|
"cmyk", Num(1.0), Num(46.0), Num(0.0), Num(0.0)
|
||||||
|
)), {})]
|
||||||
|
);
|
||||||
|
p!("[val: items(\"fire\", #f93a6d)]" =>
|
||||||
|
[func!("val": (named_tuple!(
|
||||||
|
"items", Str("fire"), ClrStr("f93a6d")
|
||||||
|
)), {})]
|
||||||
|
);
|
||||||
|
|
||||||
// Nested tuples
|
// Nested tuples
|
||||||
p!("[val: (1, (2))]" => [func!("val": (tuple!(Num(1.0), tuple!(Num(2.0)))), {})]);
|
p!("[val: (1, (2))]" =>
|
||||||
|
[func!("val": (tuple!(Num(1.0), tuple!(Num(2.0)))), {})]
|
||||||
|
);
|
||||||
|
p!("[val: css(1pt, rgb(90, 102, 254), \"solid\")]" =>
|
||||||
|
[func!("val": (named_tuple!(
|
||||||
|
"css", Pt(1.0), named_tuple!(
|
||||||
|
"rgb", Num(90.0), Num(102.0), Num(254.0)
|
||||||
|
), Str("solid")
|
||||||
|
)), {})]
|
||||||
|
);
|
||||||
|
|
||||||
// Invalid commas
|
// Invalid commas
|
||||||
p!("[val: (,)]" =>
|
p!("[val: (,)]" =>
|
||||||
[func!("val": (tuple!()), {})],
|
[func!("val": (tuple!()), {})],
|
||||||
[(0:7, 0:8, "expected value, found comma")],
|
[(0:7, 0:8, "expected value, found comma")],
|
||||||
);
|
);
|
||||||
|
p!("[val: nose(,)]" =>
|
||||||
|
[func!("val": (named_tuple!("nose")), {})],
|
||||||
|
[(0:11, 0:12, "expected value, found comma")],
|
||||||
|
);
|
||||||
p!("[val: (true false)]" =>
|
p!("[val: (true false)]" =>
|
||||||
[func!("val": (tuple!(Bool(true), Bool(false))), {})],
|
[func!("val": (tuple!(Bool(true), Bool(false))), {})],
|
||||||
[(0:11, 0:11, "expected comma")],
|
[(0:11, 0:11, "expected comma")],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use super::func::FuncHeader;
|
use super::func::FuncHeader;
|
||||||
use super::expr::{Expr, Tuple, Object};
|
use super::expr::{Expr, Tuple, NamedTuple, Object};
|
||||||
use super::span::{Span, Spanned};
|
use super::span::{Span, Spanned};
|
||||||
use super::tokens::Token;
|
use super::tokens::Token;
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -127,6 +127,7 @@ impl SpanlessEq for DebugFn {
|
|||||||
impl SpanlessEq for Expr {
|
impl SpanlessEq for Expr {
|
||||||
fn spanless_eq(&self, other: &Expr) -> bool {
|
fn spanless_eq(&self, other: &Expr) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
|
(Expr::NamedTuple(a), Expr::NamedTuple(b)) => a.spanless_eq(b),
|
||||||
(Expr::Tuple(a), Expr::Tuple(b)) => a.spanless_eq(b),
|
(Expr::Tuple(a), Expr::Tuple(b)) => a.spanless_eq(b),
|
||||||
(Expr::Object(a), Expr::Object(b)) => a.spanless_eq(b),
|
(Expr::Object(a), Expr::Object(b)) => a.spanless_eq(b),
|
||||||
(a, b) => a == b,
|
(a, b) => a == b,
|
||||||
@ -142,6 +143,13 @@ impl SpanlessEq for Tuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpanlessEq for NamedTuple {
|
||||||
|
fn spanless_eq(&self, other: &NamedTuple) -> bool {
|
||||||
|
self.name.v == other.name.v
|
||||||
|
&& self.tuple.v.spanless_eq(&other.tuple.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SpanlessEq for Object {
|
impl SpanlessEq for Object {
|
||||||
fn spanless_eq(&self, other: &Object) -> bool {
|
fn spanless_eq(&self, other: &Object) -> bool {
|
||||||
self.pairs.len() == other.pairs.len()
|
self.pairs.len() == other.pairs.len()
|
||||||
|
@ -78,6 +78,8 @@ pub enum Token<'s> {
|
|||||||
ExprSize(Size),
|
ExprSize(Size),
|
||||||
/// A boolean in a function header: `true | false`.
|
/// A boolean in a function header: `true | false`.
|
||||||
ExprBool(bool),
|
ExprBool(bool),
|
||||||
|
/// A hex value in a function header: `#20d82a`
|
||||||
|
ExprHex(&'s str),
|
||||||
|
|
||||||
/// A star in body-text.
|
/// A star in body-text.
|
||||||
Star,
|
Star,
|
||||||
@ -122,6 +124,7 @@ impl<'s> Token<'s> {
|
|||||||
ExprNumber(_) => "number",
|
ExprNumber(_) => "number",
|
||||||
ExprSize(_) => "size",
|
ExprSize(_) => "size",
|
||||||
ExprBool(_) => "bool",
|
ExprBool(_) => "bool",
|
||||||
|
ExprHex(_) => "hex value",
|
||||||
Star => "star",
|
Star => "star",
|
||||||
Underscore => "underscore",
|
Underscore => "underscore",
|
||||||
Backslash => "backslash",
|
Backslash => "backslash",
|
||||||
@ -221,9 +224,13 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
// An escaped thing.
|
// An escaped thing.
|
||||||
'\\' if self.mode == Body => self.parse_escaped(),
|
'\\' if self.mode == Body => self.parse_escaped(),
|
||||||
|
|
||||||
|
// A hex expression.
|
||||||
|
'#' if self.mode == Header => self.parse_hex_value(),
|
||||||
|
|
||||||
// Expressions or just strings.
|
// Expressions or just strings.
|
||||||
c => {
|
c => {
|
||||||
let body = self.mode == Body;
|
let body = self.mode == Body;
|
||||||
|
|
||||||
let text = self.read_string_until(|n| {
|
let text = self.read_string_until(|n| {
|
||||||
match n {
|
match n {
|
||||||
c if c.is_whitespace() => true,
|
c if c.is_whitespace() => true,
|
||||||
@ -379,6 +386,15 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_hex_value(&mut self) -> Token<'s> {
|
||||||
|
// This will parse more than the permissable 0-9, a-f, A-F character
|
||||||
|
// ranges to provide nicer error messages later.
|
||||||
|
ExprHex(self.read_string_until(
|
||||||
|
|n| !n.is_ascii_alphanumeric(),
|
||||||
|
false, 0, 0
|
||||||
|
).0)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_expr(&mut self, text: &'s str) -> Token<'s> {
|
fn parse_expr(&mut self, text: &'s str) -> Token<'s> {
|
||||||
if let Ok(b) = text.parse::<bool>() {
|
if let Ok(b) = text.parse::<bool>() {
|
||||||
ExprBool(b)
|
ExprBool(b)
|
||||||
@ -499,6 +515,7 @@ mod tests {
|
|||||||
ExprNumber as Num,
|
ExprNumber as Num,
|
||||||
ExprSize as Sz,
|
ExprSize as Sz,
|
||||||
ExprBool as Bool,
|
ExprBool as Bool,
|
||||||
|
ExprHex as Hex,
|
||||||
Text as T,
|
Text as T,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -587,6 +604,8 @@ mod tests {
|
|||||||
t!(Body, "c=d, " => [T("c=d,"), S(0)]);
|
t!(Body, "c=d, " => [T("c=d,"), S(0)]);
|
||||||
t!(Header, "(){}:=," => [LP, RP, LB, RB, Colon, Equals, Comma]);
|
t!(Header, "(){}:=," => [LP, RP, LB, RB, Colon, Equals, Comma]);
|
||||||
t!(Header, "a:b" => [Id("a"), Colon, Id("b")]);
|
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, "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, "=3.14" => [Equals, Num(3.14)]);
|
||||||
t!(Header, "12.3e5" => [Num(12.3e5)]);
|
t!(Header, "12.3e5" => [Num(12.3e5)]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user