1use crate::expression_tree::Expression;
5use crate::expression_tree::Unit;
6use itertools::Itertools;
7use smol_str::SmolStr;
8use strum::IntoEnumIterator;
9
10pub fn parse_color_literal(str: &str) -> Option<u32> {
12 if !str.starts_with('#') {
13 return None;
14 }
15 if !str.is_ascii() {
16 return None;
17 }
18 let str = &str[1..];
19 let (r, g, b, a) = match str.len() {
20 3 => (
21 u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
22 u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
23 u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
24 255u8,
25 ),
26 4 => (
27 u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
28 u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
29 u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
30 u8::from_str_radix(&str[3..=3], 16).ok()? * 0x11,
31 ),
32 6 => (
33 u8::from_str_radix(&str[0..2], 16).ok()?,
34 u8::from_str_radix(&str[2..4], 16).ok()?,
35 u8::from_str_radix(&str[4..6], 16).ok()?,
36 255u8,
37 ),
38 8 => (
39 u8::from_str_radix(&str[0..2], 16).ok()?,
40 u8::from_str_radix(&str[2..4], 16).ok()?,
41 u8::from_str_radix(&str[4..6], 16).ok()?,
42 u8::from_str_radix(&str[6..8], 16).ok()?,
43 ),
44 _ => return None,
45 };
46 Some((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32))
47}
48
49#[test]
50fn test_parse_color_literal() {
51 assert_eq!(parse_color_literal("#abc"), Some(0xffaabbcc));
52 assert_eq!(parse_color_literal("#ABC"), Some(0xffaabbcc));
53 assert_eq!(parse_color_literal("#AbC"), Some(0xffaabbcc));
54 assert_eq!(parse_color_literal("#AbCd"), Some(0xddaabbcc));
55 assert_eq!(parse_color_literal("#01234567"), Some(0x67012345));
56 assert_eq!(parse_color_literal("#012345"), Some(0xff012345));
57 assert_eq!(parse_color_literal("_01234567"), None);
58 assert_eq!(parse_color_literal("→↓←"), None);
59 assert_eq!(parse_color_literal("#→↓←"), None);
60 assert_eq!(parse_color_literal("#1234567890"), None);
61}
62
63pub fn unescape_string(string: &str) -> Option<SmolStr> {
64 if string.contains('\n') {
65 return None;
67 }
68 let string = string.strip_prefix('"').or_else(|| string.strip_prefix('}'))?;
69 let string = string.strip_suffix('"').or_else(|| string.strip_suffix("\\{"))?;
70 if !string.contains('\\') {
71 return Some(string.into());
72 }
73 let mut result = String::with_capacity(string.len());
74 let mut pos = 0;
75 loop {
76 let stop = match string[pos..].find('\\') {
77 Some(stop) => pos + stop,
78 None => {
79 result += &string[pos..];
80 return Some(result.into());
81 }
82 };
83 if stop + 1 >= string.len() {
84 return None;
85 }
86 result += &string[pos..stop];
87 pos = stop + 2;
88 match string.as_bytes()[stop + 1] {
89 b'"' => result += "\"",
90 b'\\' => result += "\\",
91 b'n' => result += "\n",
92 b'u' => {
93 if string.as_bytes().get(pos)? != &b'{' {
94 return None;
95 }
96 let end = string[pos..].find('}')? + pos;
97 let x = u32::from_str_radix(&string[pos + 1..end], 16).ok()?;
98 result.push(std::char::from_u32(x)?);
99 pos = end + 1;
100 }
101 _ => return None,
102 }
103 }
104}
105
106#[test]
107fn test_unescape_string() {
108 assert_eq!(unescape_string(r#""foo_bar""#), Some("foo_bar".into()));
109 assert_eq!(unescape_string(r#""foo\"bar""#), Some("foo\"bar".into()));
110 assert_eq!(unescape_string(r#""foo\\\"bar""#), Some("foo\\\"bar".into()));
111 assert_eq!(unescape_string(r#""fo\na\\r""#), Some("fo\na\\r".into()));
112 assert_eq!(unescape_string(r#""fo\xa""#), None);
113 assert_eq!(unescape_string(r#""fooo\""#), None);
114 assert_eq!(unescape_string(r#""f\n\n\nf""#), Some("f\n\n\nf".into()));
115 assert_eq!(unescape_string(r#""music\♪xx""#), None);
116 assert_eq!(unescape_string(r#""music\"♪\"🎝""#), Some("music\"♪\"🎝".into()));
117 assert_eq!(unescape_string(r#""foo_bar"#), None);
118 assert_eq!(unescape_string(r#""foo_bar\"#), None);
119 assert_eq!(unescape_string(r#"foo_bar""#), None);
120 assert_eq!(unescape_string(r#""d\u{8}a\u{d4}f\u{Ed3}""#), Some("d\u{8}a\u{d4}f\u{ED3}".into()));
121 assert_eq!(unescape_string(r#""xxx\""#), None);
122 assert_eq!(unescape_string(r#""xxx\u""#), None);
123 assert_eq!(unescape_string(r#""xxx\uxx""#), None);
124 assert_eq!(unescape_string(r#""xxx\u{""#), None);
125 assert_eq!(unescape_string(r#""xxx\u{22""#), None);
126 assert_eq!(unescape_string(r#""xxx\u{qsdf}""#), None);
127 assert_eq!(unescape_string(r#""xxx\u{1234567890}""#), None);
128}
129
130pub fn parse_number_literal(s: SmolStr) -> Result<Expression, SmolStr> {
131 let bytes = s.as_bytes();
132 let mut end = 0;
133 while end < bytes.len() && matches!(bytes[end], b'0'..=b'9' | b'.') {
134 end += 1;
135 }
136 let val = s[..end].parse().map_err(|_| "Cannot parse number literal".to_owned())?;
137 let unit = s[end..].parse().map_err(|_| {
138 format!(
139 "Invalid unit '{}'. Valid units are: {}",
140 s.get(end..).unwrap_or(&s),
141 Unit::iter().filter(|x| !x.to_string().is_empty()).join(", ")
142 )
143 })?;
144 Ok(Expression::NumberLiteral(val, unit))
145}
146
147#[test]
148fn test_parse_number_literal() {
149 use crate::expression_tree::Unit;
150 use smol_str::{format_smolstr, ToSmolStr};
151
152 fn doit(s: &str) -> Result<(f64, Unit), SmolStr> {
153 parse_number_literal(s.into()).map(|e| match e {
154 Expression::NumberLiteral(a, b) => (a, b),
155 _ => panic!(),
156 })
157 }
158
159 assert_eq!(doit("10"), Ok((10., Unit::None)));
160 assert_eq!(doit("10phx"), Ok((10., Unit::Phx)));
161 assert_eq!(doit("10.0phx"), Ok((10., Unit::Phx)));
162 assert_eq!(doit("10.0"), Ok((10., Unit::None)));
163 assert_eq!(doit("1.1phx"), Ok((1.1, Unit::Phx)));
164 assert_eq!(doit("10.10"), Ok((10.10, Unit::None)));
165 assert_eq!(doit("10000000"), Ok((10000000., Unit::None)));
166 assert_eq!(doit("10000001phx"), Ok((10000001., Unit::Phx)));
167
168 let cannot_parse = Err("Cannot parse number literal".to_smolstr());
169 assert_eq!(doit("12.10.12phx"), cannot_parse);
170
171 let valid_units = Unit::iter().filter(|x| x.to_string().len() > 0).join(", ");
172 let wrong_unit_spaced =
173 Err(format_smolstr!("Invalid unit ' phx'. Valid units are: {}", valid_units));
174 assert_eq!(doit("10000001 phx"), wrong_unit_spaced);
175 let wrong_unit_oo = Err(format_smolstr!("Invalid unit 'oo'. Valid units are: {}", valid_units));
176 assert_eq!(doit("12.12oo"), wrong_unit_oo);
177 let wrong_unit_euro =
178 Err(format_smolstr!("Invalid unit '€'. Valid units are: {}", valid_units));
179 assert_eq!(doit("12.12€"), wrong_unit_euro);
180}