mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-11-03 21:24:17 +00:00 
			
		
		
		
	This removes a lot of allocations and speeds up the compiler step
a bit. Sadly, this patch is very invasive as it touches a lot of
files. That said, each individual hunk is pretty trivial.
For a non-trivial real-world example, the impact is significant,
we get rid of ~29% of all allocations and improve the runtime by
about 4.8% (measured until the viewer loop would start).
Before:
```
Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint
  Time (mean ± σ):     664.2 ms ±   6.7 ms    [User: 589.2 ms, System: 74.0 ms]
  Range (min … max):   659.0 ms … 682.4 ms    10 runs
        allocations:            4886888
        temporary allocations:  857508
```
After:
```
Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint
  Time (mean ± σ):     639.5 ms ±  17.8 ms    [User: 556.9 ms, System: 76.2 ms]
  Range (min … max):   621.4 ms … 666.5 ms    10 runs
        allocations:            3544318
        temporary allocations:  495685
```
		
	
			
		
			
				
	
	
		
			180 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
// Copyright © SixtyFPS GmbH <info@slint.dev>
 | 
						|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
 | 
						|
 | 
						|
use crate::expression_tree::Expression;
 | 
						|
use crate::expression_tree::Unit;
 | 
						|
use itertools::Itertools;
 | 
						|
use smol_str::SmolStr;
 | 
						|
use strum::IntoEnumIterator;
 | 
						|
 | 
						|
/// Returns `0xaarrggbb`
 | 
						|
pub fn parse_color_literal(str: &str) -> Option<u32> {
 | 
						|
    if !str.starts_with('#') {
 | 
						|
        return None;
 | 
						|
    }
 | 
						|
    if !str.is_ascii() {
 | 
						|
        return None;
 | 
						|
    }
 | 
						|
    let str = &str[1..];
 | 
						|
    let (r, g, b, a) = match str.len() {
 | 
						|
        3 => (
 | 
						|
            u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
 | 
						|
            u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
 | 
						|
            u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
 | 
						|
            255u8,
 | 
						|
        ),
 | 
						|
        4 => (
 | 
						|
            u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
 | 
						|
            u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
 | 
						|
            u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
 | 
						|
            u8::from_str_radix(&str[3..=3], 16).ok()? * 0x11,
 | 
						|
        ),
 | 
						|
        6 => (
 | 
						|
            u8::from_str_radix(&str[0..2], 16).ok()?,
 | 
						|
            u8::from_str_radix(&str[2..4], 16).ok()?,
 | 
						|
            u8::from_str_radix(&str[4..6], 16).ok()?,
 | 
						|
            255u8,
 | 
						|
        ),
 | 
						|
        8 => (
 | 
						|
            u8::from_str_radix(&str[0..2], 16).ok()?,
 | 
						|
            u8::from_str_radix(&str[2..4], 16).ok()?,
 | 
						|
            u8::from_str_radix(&str[4..6], 16).ok()?,
 | 
						|
            u8::from_str_radix(&str[6..8], 16).ok()?,
 | 
						|
        ),
 | 
						|
        _ => return None,
 | 
						|
    };
 | 
						|
    Some((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32))
 | 
						|
}
 | 
						|
 | 
						|
#[test]
 | 
						|
fn test_parse_color_literal() {
 | 
						|
    assert_eq!(parse_color_literal("#abc"), Some(0xffaabbcc));
 | 
						|
    assert_eq!(parse_color_literal("#ABC"), Some(0xffaabbcc));
 | 
						|
    assert_eq!(parse_color_literal("#AbC"), Some(0xffaabbcc));
 | 
						|
    assert_eq!(parse_color_literal("#AbCd"), Some(0xddaabbcc));
 | 
						|
    assert_eq!(parse_color_literal("#01234567"), Some(0x67012345));
 | 
						|
    assert_eq!(parse_color_literal("#012345"), Some(0xff012345));
 | 
						|
    assert_eq!(parse_color_literal("_01234567"), None);
 | 
						|
    assert_eq!(parse_color_literal("→↓←"), None);
 | 
						|
    assert_eq!(parse_color_literal("#→↓←"), None);
 | 
						|
    assert_eq!(parse_color_literal("#1234567890"), None);
 | 
						|
}
 | 
						|
 | 
						|
pub fn unescape_string(string: &str) -> Option<SmolStr> {
 | 
						|
    if string.contains('\n') {
 | 
						|
        // FIXME: new line in string literal not yet supported
 | 
						|
        return None;
 | 
						|
    }
 | 
						|
    let string = string.strip_prefix('"').or_else(|| string.strip_prefix('}'))?;
 | 
						|
    let string = string.strip_suffix('"').or_else(|| string.strip_suffix("\\{"))?;
 | 
						|
    if !string.contains('\\') {
 | 
						|
        return Some(string.into());
 | 
						|
    }
 | 
						|
    let mut result = String::with_capacity(string.len());
 | 
						|
    let mut pos = 0;
 | 
						|
    loop {
 | 
						|
        let stop = match string[pos..].find('\\') {
 | 
						|
            Some(stop) => pos + stop,
 | 
						|
            None => {
 | 
						|
                result += &string[pos..];
 | 
						|
                return Some(result.into());
 | 
						|
            }
 | 
						|
        };
 | 
						|
        if stop + 1 >= string.len() {
 | 
						|
            return None;
 | 
						|
        }
 | 
						|
        result += &string[pos..stop];
 | 
						|
        pos = stop + 2;
 | 
						|
        match string.as_bytes()[stop + 1] {
 | 
						|
            b'"' => result += "\"",
 | 
						|
            b'\\' => result += "\\",
 | 
						|
            b'n' => result += "\n",
 | 
						|
            b'u' => {
 | 
						|
                if string.as_bytes().get(pos)? != &b'{' {
 | 
						|
                    return None;
 | 
						|
                }
 | 
						|
                let end = string[pos..].find('}')? + pos;
 | 
						|
                let x = u32::from_str_radix(&string[pos + 1..end], 16).ok()?;
 | 
						|
                result.push(std::char::from_u32(x)?);
 | 
						|
                pos = end + 1;
 | 
						|
            }
 | 
						|
            _ => return None,
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[test]
 | 
						|
fn test_unescape_string() {
 | 
						|
    assert_eq!(unescape_string(r#""foo_bar""#), Some("foo_bar".into()));
 | 
						|
    assert_eq!(unescape_string(r#""foo\"bar""#), Some("foo\"bar".into()));
 | 
						|
    assert_eq!(unescape_string(r#""foo\\\"bar""#), Some("foo\\\"bar".into()));
 | 
						|
    assert_eq!(unescape_string(r#""fo\na\\r""#), Some("fo\na\\r".into()));
 | 
						|
    assert_eq!(unescape_string(r#""fo\xa""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""fooo\""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""f\n\n\nf""#), Some("f\n\n\nf".into()));
 | 
						|
    assert_eq!(unescape_string(r#""music\♪xx""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""music\"♪\"🎝""#), Some("music\"♪\"🎝".into()));
 | 
						|
    assert_eq!(unescape_string(r#""foo_bar"#), None);
 | 
						|
    assert_eq!(unescape_string(r#""foo_bar\"#), None);
 | 
						|
    assert_eq!(unescape_string(r#"foo_bar""#), None);
 | 
						|
    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()));
 | 
						|
    assert_eq!(unescape_string(r#""xxx\""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\u""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\uxx""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\u{""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\u{22""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\u{qsdf}""#), None);
 | 
						|
    assert_eq!(unescape_string(r#""xxx\u{1234567890}""#), None);
 | 
						|
}
 | 
						|
 | 
						|
pub fn parse_number_literal(s: SmolStr) -> Result<Expression, SmolStr> {
 | 
						|
    let bytes = s.as_bytes();
 | 
						|
    let mut end = 0;
 | 
						|
    while end < bytes.len() && matches!(bytes[end], b'0'..=b'9' | b'.') {
 | 
						|
        end += 1;
 | 
						|
    }
 | 
						|
    let val = s[..end].parse().map_err(|_| "Cannot parse number literal".to_owned())?;
 | 
						|
    let unit = s[end..].parse().map_err(|_| {
 | 
						|
        format!(
 | 
						|
            "Invalid unit '{}'. Valid units are: {}",
 | 
						|
            s.get(end..).unwrap_or(&s),
 | 
						|
            Unit::iter().filter(|x| !x.to_string().is_empty()).join(", ")
 | 
						|
        )
 | 
						|
    })?;
 | 
						|
    Ok(Expression::NumberLiteral(val, unit))
 | 
						|
}
 | 
						|
 | 
						|
#[test]
 | 
						|
fn test_parse_number_literal() {
 | 
						|
    use crate::expression_tree::Unit;
 | 
						|
    use smol_str::{format_smolstr, ToSmolStr};
 | 
						|
 | 
						|
    fn doit(s: &str) -> Result<(f64, Unit), SmolStr> {
 | 
						|
        parse_number_literal(s.into()).map(|e| match e {
 | 
						|
            Expression::NumberLiteral(a, b) => (a, b),
 | 
						|
            _ => panic!(),
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    assert_eq!(doit("10"), Ok((10., Unit::None)));
 | 
						|
    assert_eq!(doit("10phx"), Ok((10., Unit::Phx)));
 | 
						|
    assert_eq!(doit("10.0phx"), Ok((10., Unit::Phx)));
 | 
						|
    assert_eq!(doit("10.0"), Ok((10., Unit::None)));
 | 
						|
    assert_eq!(doit("1.1phx"), Ok((1.1, Unit::Phx)));
 | 
						|
    assert_eq!(doit("10.10"), Ok((10.10, Unit::None)));
 | 
						|
    assert_eq!(doit("10000000"), Ok((10000000., Unit::None)));
 | 
						|
    assert_eq!(doit("10000001phx"), Ok((10000001., Unit::Phx)));
 | 
						|
 | 
						|
    let cannot_parse = Err("Cannot parse number literal".to_smolstr());
 | 
						|
    assert_eq!(doit("12.10.12phx"), cannot_parse);
 | 
						|
 | 
						|
    let valid_units = Unit::iter().filter(|x| x.to_string().len() > 0).join(", ");
 | 
						|
    let wrong_unit_spaced =
 | 
						|
        Err(format_smolstr!("Invalid unit ' phx'. Valid units are: {}", valid_units));
 | 
						|
    assert_eq!(doit("10000001 phx"), wrong_unit_spaced);
 | 
						|
    let wrong_unit_oo = Err(format_smolstr!("Invalid unit 'oo'. Valid units are: {}", valid_units));
 | 
						|
    assert_eq!(doit("12.12oo"), wrong_unit_oo);
 | 
						|
    let wrong_unit_euro =
 | 
						|
        Err(format_smolstr!("Invalid unit '€'. Valid units are: {}", valid_units));
 | 
						|
    assert_eq!(doit("12.12€"), wrong_unit_euro);
 | 
						|
}
 |