sixtyfps_compilerlib/
literals.rs

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