Skip to main content

i_slint_compiler/
literals.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use crate::expression_tree::Expression;
5use crate::expression_tree::Unit;
6use itertools::Itertools;
7use smol_str::SmolStr;
8use strum::IntoEnumIterator;
9
10pub fn unescape_string(string: &str) -> Option<SmolStr> {
11    if string.contains('\n') {
12        // FIXME: new line in string literal not yet supported
13        return None;
14    }
15    let string = string.strip_prefix('"').or_else(|| string.strip_prefix('}'))?;
16    let string = string.strip_suffix('"').or_else(|| string.strip_suffix("\\{"))?;
17    if !string.contains('\\') {
18        return Some(string.into());
19    }
20    let mut result = String::with_capacity(string.len());
21    let mut pos = 0;
22    loop {
23        let stop = match string[pos..].find('\\') {
24            Some(stop) => pos + stop,
25            None => {
26                result += &string[pos..];
27                return Some(result.into());
28            }
29        };
30        if stop + 1 >= string.len() {
31            return None;
32        }
33        result += &string[pos..stop];
34        pos = stop + 2;
35        match string.as_bytes()[stop + 1] {
36            b'"' => result += "\"",
37            b'\\' => result += "\\",
38            b'n' => result += "\n",
39            b'u' => {
40                if string.as_bytes().get(pos)? != &b'{' {
41                    return None;
42                }
43                let end = string[pos..].find('}')? + pos;
44                let x = u32::from_str_radix(&string[pos + 1..end], 16).ok()?;
45                result.push(std::char::from_u32(x)?);
46                pos = end + 1;
47            }
48            _ => return None,
49        }
50    }
51}
52
53#[test]
54fn test_unescape_string() {
55    assert_eq!(unescape_string(r#""foo_bar""#), Some("foo_bar".into()));
56    assert_eq!(unescape_string(r#""foo\"bar""#), Some("foo\"bar".into()));
57    assert_eq!(unescape_string(r#""foo\\\"bar""#), Some("foo\\\"bar".into()));
58    assert_eq!(unescape_string(r#""fo\na\\r""#), Some("fo\na\\r".into()));
59    assert_eq!(unescape_string(r#""fo\xa""#), None);
60    assert_eq!(unescape_string(r#""fooo\""#), None);
61    assert_eq!(unescape_string(r#""f\n\n\nf""#), Some("f\n\n\nf".into()));
62    assert_eq!(unescape_string(r#""music\♪xx""#), None);
63    assert_eq!(unescape_string(r#""music\"♪\"🎝""#), Some("music\"♪\"🎝".into()));
64    assert_eq!(unescape_string(r#""foo_bar"#), None);
65    assert_eq!(unescape_string(r#""foo_bar\"#), None);
66    assert_eq!(unescape_string(r#"foo_bar""#), None);
67    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()));
68    assert_eq!(unescape_string(r#""xxx\""#), None);
69    assert_eq!(unescape_string(r#""xxx\u""#), None);
70    assert_eq!(unescape_string(r#""xxx\uxx""#), None);
71    assert_eq!(unescape_string(r#""xxx\u{""#), None);
72    assert_eq!(unescape_string(r#""xxx\u{22""#), None);
73    assert_eq!(unescape_string(r#""xxx\u{qsdf}""#), None);
74    assert_eq!(unescape_string(r#""xxx\u{1234567890}""#), None);
75}
76
77pub fn parse_number_literal(s: SmolStr) -> Result<Expression, SmolStr> {
78    let bytes = s.as_bytes();
79    let mut end = 0;
80    while end < bytes.len() && matches!(bytes[end], b'0'..=b'9' | b'.') {
81        end += 1;
82    }
83    let val = s[..end].parse().map_err(|_| "Cannot parse number literal".to_owned())?;
84    let unit = s[end..].parse().map_err(|_| {
85        format!(
86            "Invalid unit '{}'. Valid units are: {}",
87            s.get(end..).unwrap_or(&s),
88            Unit::iter().filter(|x| !x.to_string().is_empty()).join(", ")
89        )
90    })?;
91    Ok(Expression::NumberLiteral(val, unit))
92}
93
94#[test]
95fn test_parse_number_literal() {
96    use crate::expression_tree::Unit;
97    use smol_str::{ToSmolStr, format_smolstr};
98
99    fn doit(s: &str) -> Result<(f64, Unit), SmolStr> {
100        parse_number_literal(s.into()).map(|e| match e {
101            Expression::NumberLiteral(a, b) => (a, b),
102            _ => panic!(),
103        })
104    }
105
106    assert_eq!(doit("10"), Ok((10., Unit::None)));
107    assert_eq!(doit("10phx"), Ok((10., Unit::Phx)));
108    assert_eq!(doit("10.0phx"), Ok((10., Unit::Phx)));
109    assert_eq!(doit("10.0"), Ok((10., Unit::None)));
110    assert_eq!(doit("1.1phx"), Ok((1.1, Unit::Phx)));
111    assert_eq!(doit("10.10"), Ok((10.10, Unit::None)));
112    assert_eq!(doit("10000000"), Ok((10000000., Unit::None)));
113    assert_eq!(doit("10000001phx"), Ok((10000001., Unit::Phx)));
114
115    let cannot_parse = Err("Cannot parse number literal".to_smolstr());
116    assert_eq!(doit("12.10.12phx"), cannot_parse);
117
118    let valid_units = Unit::iter().filter(|x| !x.to_string().is_empty()).join(", ");
119    let wrong_unit_spaced =
120        Err(format_smolstr!("Invalid unit ' phx'. Valid units are: {}", valid_units));
121    assert_eq!(doit("10000001 phx"), wrong_unit_spaced);
122    let wrong_unit_oo = Err(format_smolstr!("Invalid unit 'oo'. Valid units are: {}", valid_units));
123    assert_eq!(doit("12.12oo"), wrong_unit_oo);
124    let wrong_unit_euro =
125        Err(format_smolstr!("Invalid unit '€'. Valid units are: {}", valid_units));
126    assert_eq!(doit("12.12€"), wrong_unit_euro);
127}