diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 52efbe531..3f5357290 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -3,6 +3,7 @@ use std::fmt::{self, Write, Debug, Formatter}; use std::iter::FromIterator; use std::ops::Deref; +use std::str::FromStr; use std::u8; use crate::error::Errors; @@ -141,11 +142,16 @@ impl 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 `#`. - pub fn from_str(hex_str: &str) -> Option { + fn from_str(hex_str: &str) -> Result { if !hex_str.is_ascii() { - return None; + return Err(ParseColorError); } let len = hex_str.len(); @@ -154,7 +160,7 @@ impl RgbaColor { let alpha = len == 4 || len == 8; if !long && !short { - return None; + return Err(ParseColorError); } let mut values: [u8; 4] = [255; 4]; @@ -164,17 +170,16 @@ impl RgbaColor { let pos = elem * item_len; let item = &hex_str[pos..(pos+item_len)]; - values[elem] = u8::from_str_radix(item, 16).ok()?; - + 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; } } - Some( - RgbaColor::new(values[0], values[1], values[2], values[3]) - ) + Ok(RgbaColor::new(values[0], values[1], values[2], values[3])) } } @@ -182,23 +187,34 @@ impl Debug for RgbaColor { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { f.write_str("rgba(")?; - f.write_fmt(format_args!("r: {:02}, ", self.r))?; - f.write_fmt(format_args!("g: {:02}, ", self.g))?; - f.write_fmt(format_args!("b: {:02}, ", self.b))?; - f.write_fmt(format_args!("a: {:02}", self.a))?; + 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('#')?; - f.write_fmt(format_args!("{:02x}", self.r))?; - f.write_fmt(format_args!("{:02x}", self.g))?; - f.write_fmt(format_args!("{:02x}", self.b))?; - f.write_fmt(format_args!("{:02x}", self.a))?; + 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]")) - } else { - Ok(()) + 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") } } @@ -296,7 +312,7 @@ impl Debug for Tuple { /// ```typst /// hsl(93, 10, 19.4) /// ``` -#[derive(Clone, PartialEq, Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct NamedTuple { /// The name of the tuple and where it is in the user source. pub name: Spanned, diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 772f2dc0e..cfbf2bf3f 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,6 +1,7 @@ //! Parsing of source code into syntax models. use std::iter::FromIterator; +use std::str::FromStr; use crate::{Pass, Feedback}; use super::func::{FuncHeader, FuncArgs, FuncArg}; @@ -253,12 +254,11 @@ impl<'s> FuncParser<'s> { Token::ExprSize(s) => take!(Expr::Size(s)), Token::ExprBool(b) => take!(Expr::Bool(b)), Token::ExprHex(s) => { - if let Some(color) = RgbaColor::from_str(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")); + self.feedback.errors.push(err!(first.span; "invalid color")); take!(Expr::Color(RgbaColor::new_healed(0, 0, 0, 255))) } }, @@ -284,9 +284,8 @@ impl<'s> FuncParser<'s> { /// Parse a tuple expression: `name(, ...)` with a given identifier. fn parse_named_tuple(&mut self, name: Spanned) -> Spanned { let tuple = self.parse_tuple(); - let start = name.span.start; - let end = tuple.span.end; - Spanned::new(NamedTuple::new(name, tuple), Span::new(start, end)) + let span = Span::merge(name.span, tuple.span); + Spanned::new(NamedTuple::new(name, tuple), span) } /// Parse an object expression: `{ : , ... }`. @@ -548,20 +547,17 @@ mod tests { 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 ClrS_Healed() -> Expr { - let mut c = RgbaColor::from_str("000f") - .expect("Test color invalid"); - c.healed = true; - Expr::Color(c) - } 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()) } @@ -665,11 +661,11 @@ 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")); + 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] @@ -816,7 +812,7 @@ 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")), {})]); + p!("[val: #f7a20500]" => [func!("val": (ClrStr("f7a20500")), {})]); // Unclosed string. p!("[val: \"hello]" => [func!("val": (Str("hello]")), {})], [ @@ -825,16 +821,16 @@ mod tests { ]); //Invalid colors - p!("[val: #12345]" => [func!("val": (ClrS_Healed()), {})], [ + p!("[val: #12345]" => [func!("val": (ClrStrHealed()), {})], [ (0:6, 0:12, "invalid color"), ]); - p!("[val: #a5]" => [func!("val": (ClrS_Healed()), {})], [ + p!("[val: #a5]" => [func!("val": (ClrStrHealed()), {})], [ (0:6, 0:9, "invalid color"), ]); - p!("[val: #14b2ah]" => [func!("val": (ClrS_Healed()), {})], [ + p!("[val: #14b2ah]" => [func!("val": (ClrStrHealed()), {})], [ (0:6, 0:13, "invalid color"), ]); - p!("[val: #f075ff011]" => [func!("val": (ClrS_Healed()), {})], [ + p!("[val: #f075ff011]" => [func!("val": (ClrStrHealed()), {})], [ (0:6, 0:16, "invalid color"), ]); } @@ -874,19 +870,19 @@ mod tests { // 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)]" => + 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)]" => + p!("[val: items(\"fire\", #f93a6d)]" => [func!("val": (named_tuple!( - "items", Str("fire"), ClrS("f93a6d") + "items", Str("fire"), ClrStr("f93a6d") )), {})] ); - + // Nested tuples - p!("[val: (1, (2))]" => + p!("[val: (1, (2))]" => [func!("val": (tuple!(Num(1.0), tuple!(Num(2.0)))), {})] ); p!("[val: css(1pt, rgb(90, 102, 254), \"solid\")]" => @@ -896,7 +892,7 @@ mod tests { ), Str("solid") )), {})] ); - + // Invalid commas p!("[val: (,)]" => [func!("val": (tuple!()), {})], diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 07a1ac92b..adf29de23 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -230,7 +230,7 @@ impl<'s> Iterator for Tokens<'s> { // Expressions or just strings. c => { let body = self.mode == Body; - + let text = self.read_string_until(|n| { match n { c if c.is_whitespace() => true, @@ -389,12 +389,10 @@ 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. - let payload = self.read_string_until( - |n| !n.is_ascii_alphanumeric(), + ExprHex(self.read_string_until( + |n| !n.is_ascii_alphanumeric(), false, 0, 0 - ).0; - - ExprHex(payload) + ).0) } fn parse_expr(&mut self, text: &'s str) -> Token<'s> {