1 use proc_macro2::{Span, TokenStream}; 2 use crate::{ 3 err::Error, 4 ir::{Style, Color}, 5 }; 6 use super::{parse, expect_str_literal}; 7 8 9 impl Style { 10 /// Parses the style specifiction assuming the token stream contains a 11 /// single string literal. parse_from_tokens(tokens: TokenStream) -> Result<Self, Error>12 pub(crate) fn parse_from_tokens(tokens: TokenStream) -> Result<Self, Error> { 13 let (s, span) = parse(tokens, expect_str_literal)?; 14 Self::parse(&s, span) 15 } 16 17 /// Parses the style specification in `spec` (with `span`) and returns a token 18 /// stream representing an expression constructing the corresponding `ColorSpec` 19 /// value. parse(spec: &str, span: Span) -> Result<Self, Error>20 pub(super) fn parse(spec: &str, span: Span) -> Result<Self, Error> { 21 let mut out = Self::default(); 22 23 let mut previous_fg_color = None; 24 let mut previous_bg_color = None; 25 for fragment in spec.split('+').map(str::trim).filter(|s| !s.is_empty()) { 26 let (fragment, is_bg) = match fragment.strip_prefix("bg:") { 27 Some(color) => (color, true), 28 None => (fragment, false), 29 }; 30 31 // Parse/obtain color if a color is specified. 32 let color = match fragment { 33 "black" => Some(Color::Black), 34 "blue" => Some(Color::Blue), 35 "green" => Some(Color::Green), 36 "red" => Some(Color::Red), 37 "cyan" => Some(Color::Cyan), 38 "magenta" => Some(Color::Magenta), 39 "yellow" => Some(Color::Yellow), 40 "white" => Some(Color::White), 41 42 hex if hex.starts_with('#') => { 43 let hex = &hex[1..]; 44 45 if hex.len() != 6 { 46 let e = err!( 47 span, 48 "hex color code invalid: 6 digits expected, found {}", 49 hex.len(), 50 ); 51 return Err(e); 52 } 53 54 let digits = hex.chars() 55 .map(|c| { 56 c.to_digit(16).ok_or_else(|| { 57 err!(span, "hex color code invalid: {} is not a valid hex digit", c) 58 }) 59 }) 60 .collect::<Result<Vec<_>, _>>()?; 61 62 let r = (digits[0] * 16 + digits[1]) as u8; 63 let g = (digits[2] * 16 + digits[3]) as u8; 64 let b = (digits[4] * 16 + digits[5]) as u8; 65 66 Some(Color::Rgb(r, g, b)) 67 }, 68 69 // TODO: Ansi256 colors 70 _ => None, 71 }; 72 73 // Check for duplicate color definitions. 74 let (previous_color, color_kind) = match is_bg { 75 true => (&mut previous_bg_color, "background"), 76 false => (&mut previous_fg_color, "foreground"), 77 }; 78 match (&color, *previous_color) { 79 (Some(_), Some(old)) => { 80 let e = err!( 81 span, 82 "found '{}' but the {} color was already specified as '{}'", 83 fragment, 84 color_kind, 85 old, 86 ); 87 return Err(e); 88 } 89 (Some(_), None) => *previous_color = Some(fragment), 90 _ => {} 91 } 92 93 macro_rules! set_attr { 94 ($field:ident, $value:expr) => {{ 95 if let Some(b) = out.$field { 96 let field_s = stringify!($field); 97 let old = if b { field_s.into() } else { format!("!{}", field_s) }; 98 let new = if $value { field_s.into() } else { format!("!{}", field_s) }; 99 let e = err!( 100 span, 101 "invalid style definition: found '{}', but '{}' was specified before", 102 new, 103 old, 104 ); 105 return Err(e); 106 } 107 108 out.$field = Some($value); 109 }}; 110 } 111 112 // Obtain the final token stream for method call. 113 match (is_bg, color, fragment) { 114 (false, Some(color), _) => out.fg = Some(color), 115 (true, Some(color), _) => out.bg = Some(color), 116 (true, None, other) => { 117 return Err(err!(span, "'{}' (following 'bg:') is not a valid color", other)); 118 } 119 120 (false, None, "bold") => set_attr!(bold, true), 121 (false, None, "!bold") => set_attr!(bold, false), 122 (false, None, "italic") => set_attr!(italic, true), 123 (false, None, "!italic") => set_attr!(italic, false), 124 (false, None, "dimmed") => set_attr!(dimmed, true), 125 (false, None, "!dimmed") => set_attr!(dimmed, false), 126 (false, None, "underline") => set_attr!(underline, true), 127 (false, None, "!underline") => set_attr!(underline, false), 128 (false, None, "intense") => set_attr!(intense, true), 129 (false, None, "!intense") => set_attr!(intense, false), 130 131 (false, None, other) => { 132 return Err(err!(span, "invalid style spec fragment '{}'", other)); 133 } 134 } 135 } 136 137 Ok(out) 138 } 139 } 140