summaryrefslogtreecommitdiff
path: root/src/syntax/parsing.rs
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2020-07-14 19:09:58 +0200
committerMartin Haug <mhaug@live.de>2020-07-14 19:09:58 +0200
commit28c3a797ec7a6fbe3f5069604e5b19a00ae52344 (patch)
treefa66354f1ed9459a8f4f8856164ccf6fc1d2f74c /src/syntax/parsing.rs
parent1658b00282b631fb5218f477ea7f45f925644cea (diff)
Add named tuples and hex color tokens
Diffstat (limited to 'src/syntax/parsing.rs')
-rw-r--r--src/syntax/parsing.rs136
1 files changed, 126 insertions, 10 deletions
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 2fb8b58a..c85a9c31 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -187,6 +187,14 @@ impl<'s> FuncParser<'s> {
// If we have an identifier we might have a keyword argument,
// otherwise its for sure a postional argument.
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();
if let Some(Token::Equals) = p.peekv() {
@@ -223,7 +231,16 @@ impl<'s> FuncParser<'s> {
}
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 } => {
if !terminated {
self.expected_at("quote", first.span.end);
@@ -235,16 +252,28 @@ impl<'s> FuncParser<'s> {
Token::ExprNumber(n) => take!(Expr::Number(n)),
Token::ExprSize(s) => take!(Expr::Size(s)),
Token::ExprBool(b) => take!(Expr::Bool(b)),
+ Token::ExprHex(s) => {
+ let color_opt = RgbaColor::from_str(s);
+
+ if let Some(color) = color_opt {
+ take!(Expr::Color(color))
+ } else {
+ // Heal color by assuming black
+ self.feedback.errors.push(err!(first.span;
+ "expected valid color, found invalid color #{}", s));
+ take!(Expr::Color(RgbaColor::new(0, 0, 0, 255)))
+ }
+ },
- Token::LeftParen => self.parse_tuple(),
- Token::LeftBrace => self.parse_object(),
+ Token::LeftParen => self.parse_tuple().map(|t| Expr::Tuple(t)),
+ Token::LeftBrace => self.parse_object().map(|o| Expr::Object(o)),
_ => return None,
})
}
/// Parse a tuple expression: `(<expr>, ...)`.
- fn parse_tuple(&mut self) -> Spanned<Expr> {
+ fn parse_tuple(&mut self) -> Spanned<Tuple> {
let token = self.eat();
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftParen));
@@ -252,11 +281,18 @@ impl<'s> FuncParser<'s> {
// missing a `value` when an invalid token is encoutered.
self.parse_collection(Some(Token::RightParen),
|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 start = name.span.start.clone();
+ let end = tuple.span.end.clone();
+ Spanned::new(NamedTuple::new(name, tuple), Span::new(start, end))
}
/// Parse an object expression: `{ <key>: <value>, ... }`.
- fn parse_object(&mut self) -> Spanned<Expr> {
+ fn parse_object(&mut self) -> Spanned<Object> {
let token = self.eat();
debug_assert_eq!(token.map(Spanned::value), Some(Token::LeftBrace));
@@ -282,7 +318,7 @@ impl<'s> FuncParser<'s> {
let value = p.parse_expr().ok_or(("value", None))?;
Ok(Pair { key, value })
- }).map(|object| Expr::Object(object))
+ })
}
/// Parse a comma-separated collection where each item is parsed through
@@ -513,6 +549,14 @@ mod tests {
fn Id(text: &str) -> Expr { Expr::Ident(Ident(text.to_string())) }
fn Str(text: &str) -> Expr { Expr::Str(text.to_string()) }
fn Pt(points: f32) -> Expr { Expr::Size(Size::pt(points)) }
+ fn ClrS(color: &str) -> Expr {
+ Expr::Color(
+ RgbaColor::from_str(color).expect("Test color invalid")
+ )
+ }
+ fn Clr(r: u8, g: u8, b: u8, a: u8) -> Expr {
+ Expr::Color(RgbaColor::new(r, g, b, a))
+ }
fn T(text: &str) -> Node { Node::Text(text.to_string()) }
/// Create a raw text node.
@@ -529,6 +573,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.
macro_rules! object {
($($key:expr => $value:expr),* $(,)?) => {
@@ -604,6 +658,15 @@ mod tests {
}
#[test]
+ fn parse_color_strings() {
+ assert_eq!(Clr(0xf6, 0x12, 0x43, 0xff), ClrS("f61243ff"));
+ assert_eq!(Clr(0xb3, 0xd8, 0xb3, 0xff), ClrS("b3d8b3"));
+ assert_eq!(Clr(0xfc, 0xd2, 0xa9, 0xad), ClrS("fCd2a9AD"));
+ assert_eq!(Clr(0x22, 0x33, 0x33, 0xff), ClrS("233"));
+ assert_eq!(Clr(0x11, 0x11, 0x11, 0xbb), ClrS("111b"));
+ }
+
+ #[test]
fn unescape_strings() {
fn test(string: &str, expected: &str) {
assert_eq!(unescape_string(string), expected.to_string());
@@ -747,43 +810,96 @@ mod tests {
p!("[val: 3.14]" => [func!("val": (Num(3.14)), {})]);
p!("[val: 4.5cm]" => [func!("val": (Sz(Size::cm(4.5))), {})]);
p!("[val: 12e1pt]" => [func!("val": (Pt(12e1)), {})]);
+ p!("[val: #f7a20500]" => [func!("val": (ClrS("f7a20500")), {})]);
// Unclosed string.
p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [
(0:13, 0:13, "expected quote"),
(0:13, 0:13, "expected closing bracket"),
]);
+
+ //Invalid colors
+ p!("[val: #12345]" => [func!("val": (ClrS("000f")), {})], [
+ (0:6, 0:12, "expected valid color, found invalid color #12345"),
+ ]);
+ p!("[val: #a5]" => [func!("val": (ClrS("000f")), {})], [
+ (0:6, 0:9, "expected valid color, found invalid color #a5"),
+ ]);
+ p!("[val: #14b2ah]" => [func!("val": (ClrS("000f")), {})], [
+ (0:6, 0:13, "expected valid color, found invalid color #14b2a"),
+ ]);
+ p!("[val: #f075ff011]" => [func!("val": (ClrS("000f")), {})], [
+ (0:6, 0:16, "expected valid color, found invalid color #f075ff011"),
+ ]);
}
#[test]
fn parse_tuples() {
// Empty tuple
p!("[val: ()]" => [func!("val": (tuple!()), {})]);
+ p!("[val: empty()]" => [func!("val": (named_tuple!("empty")), {})]);
// Invalid value
p!("[val: (🌎)]" =>
[func!("val": (tuple!()), {})],
[(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
p!("[val: (hello]" =>
[func!("val": (tuple!(Id("hello"))), {})],
[(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
p!("[val: (1, 2)]" => [func!("val": (tuple!(Num(1.0), Num(2.0))), {})]);
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"), ClrS("f93a6d")
+ )), {})]
+ );
+
// 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
p!("[val: (,)]" =>
[func!("val": (tuple!()), {})],
[(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)]" =>
[func!("val": (tuple!(Bool(true), Bool(false))), {})],
[(0:11, 0:11, "expected comma")],