glt 0.1.1

Glint compiler library
Documentation
#[cfg(test)]
mod parser_tests {
    use crate::ast::Value;
    use crate::parser::Parser;

    #[test]
    fn test_parser_exact_element_state() {
        let input = r#"Button(id="btn", active=true, color=#ff0000, pad=10.5) "Click Me""#;
        let mut parser = Parser::new(input);
        parser.parse_all().expect("Парсинг не должен падать");

        let m = &parser.module;

        assert_eq!(
            m.elem_types.len(),
            1,
            "Должен быть 1 элемент с inline-контентом"
        );
        assert_eq!(m.elem_prop_spans.len(), 1);
        assert_eq!(m.elem_child_spans.len(), 1);
        assert_eq!(m.elem_content.len(), 1);

        assert_eq!(m.elem_types[0], "Button");

        let (p_start, p_len) = m.elem_prop_spans[0];
        assert_eq!(p_start, 0);
        assert_eq!(p_len, 4);

        let expected_keys = vec!["id", "active", "color", "pad"];
        let expected_vals = vec![
            Value::String("btn".to_string()),
            Value::Bool(true),
            Value::Color("#ff0000".to_string()),
            Value::Float(10.5),
        ];

        for i in 0..4 {
            let idx = (p_start + i) as usize;
            assert_eq!(
                m.prop_keys[idx], expected_keys[i as usize],
                "Ключ {} не совпал",
                i
            );
            assert_eq!(
                m.prop_values[idx], expected_vals[i as usize],
                "Значение {} не совпало",
                i
            );
        }

        assert_eq!(
            m.elem_content[0],
            Some(Value::String("Click Me".to_string())),
            "Контент текста потерян или искажен"
        );
    }

    #[test]
    fn test_parser_hierarchy_spans() {
        let input = r#"
            Root {
                ChildA {}
                ChildB { SubChild {} }
            }
        "#;
        let mut parser = Parser::new(input);
        parser.parse_all().expect("Парсинг должен пройти");

        let m = &parser.module;

        let root_idx = m.elem_types.iter().position(|t| t == "Root").unwrap();
        let a_idx = m.elem_types.iter().position(|t| t == "ChildA").unwrap();
        let b_idx = m.elem_types.iter().position(|t| t == "ChildB").unwrap();

        let root_span = m.elem_child_spans[root_idx];
        assert_eq!(root_span.1, 2, "У Root должно быть ровно 2 ребенка");

        let child1 = &m.hierarchy[(root_span.0) as usize];
        let child2 = &m.hierarchy[(root_span.0 + 1) as usize];

        match (child1, child2) {
            (crate::ast::NodeId::Element(id1), crate::ast::NodeId::Element(id2)) => {
                assert_eq!(*id1 as usize, a_idx);
                assert_eq!(*id2 as usize, b_idx);
            }
            _ => panic!("Дети Root должны быть элементами"),
        }
    }
}

#[cfg(test)]
mod compiler_tests {
    use crate::compiler::Compiler;
    use crate::opcodes::*;
    use crate::parser::Parser;

    struct BytecodeBuilder {
        buf: Vec<u8>,
    }
    impl BytecodeBuilder {
        fn new() -> Self {
            let mut b = Self { buf: Vec::new() };
            b.buf.extend_from_slice(&MAGIC_HEADER);
            b
        }
        fn push_u8(mut self, val: u8) -> Self {
            self.buf.push(val);
            self
        }
        fn push_str(mut self, s: &str) -> Self {
            let bytes = s.as_bytes();
            self.buf
                .extend_from_slice(&(bytes.len() as u32).to_le_bytes());
            self.buf.extend_from_slice(bytes);
            self
        }
        fn push_i64(mut self, val: i64) -> Self {
            self.buf.extend_from_slice(&val.to_le_bytes());
            self
        }
        fn build(self) -> Vec<u8> {
            self.buf
        }
    }

    #[test]
    fn test_compiler_exact_bytes_directive() {
        let input = "@version 42";
        let mut parser = Parser::new(input);
        parser.parse_all().unwrap();

        let root_nodes = vec![parser.module.hierarchy[0]];

        let compiler = Compiler::new(&parser.module);
        let bytecode = compiler.compile(&root_nodes);

        let expected = BytecodeBuilder::new()
            .push_u8(OP_VERSION)
            .push_i64(42)
            .build();

        assert_eq!(bytecode, expected);
    }

    #[test]
    fn test_compiler_exact_bytes_element() {
        let input = r#"Box(id="main")"#;
        let mut parser = Parser::new(input);
        parser.parse_all().unwrap();

        let root_nodes = vec![parser.module.hierarchy[0]];
        let compiler = Compiler::new(&parser.module);
        let bytecode = compiler.compile(&root_nodes);

        let expected = BytecodeBuilder::new()
            .push_u8(OP_ELEM_PUSH)
            .push_str("Box")
            .push_u8(OP_PROP_STR)
            .push_str("id")
            .push_str("main")
            .push_u8(OP_ELEM_POP)
            .build();

        assert_eq!(bytecode, expected);
    }

    #[test]
    fn test_compiler_rhei_zero_parsing() {
        let input = r#"@global $net = !rhei: sys.network().status"#;
        let mut parser = Parser::new(input);
        parser.parse_all().unwrap();

        let root_nodes = vec![parser.module.hierarchy[0]];
        let compiler = Compiler::new(&parser.module);
        let bytecode = compiler.compile(&root_nodes);

        let expected = BytecodeBuilder::new()
            .push_u8(OP_GLOBAL)
            .push_str("net")
            .push_u8(OP_PROP_RHEI)
            .push_str("sys.network().status")
            .build();

        assert_eq!(bytecode, expected);
    }
}

const TEST_GLINT_INPUT: &str = r#"
@version 1
@style "desktop.glts"
@global $username   = !rhei: os.env("USER")
@global $hostname   = !rhei: os.hostname()
@global $locale     = "ru_RU"
@global $scaleFactor = 1.0
@singleton Config {
    wallpaper      = fs:/home/$username/.config/de/wallpaper.jpg
    wallpaperFit   = "cover"
    iconTheme      = "papirus-dark"
    fontMain       = "Inter"
    fontMono       = "JetBrains Mono"
    fontSize       = 13
    workspaces     = 4
    animEnabled    = true
    accentColor    = #cba6f7
    taskbarPos     = "bottom"
}
Screen(id="root", width=1920, height=1080, scale=$scaleFactor) {
    Panel(id="taskbar", class="taskbar") {
        Button(id="launcher-btn", class="launcher-button") {
            Image(src=fs:/usr/share/glint-de/logo.svg) {}
        }
    }
}
"#;

#[test]
fn test_full_desktop_compilation() {
    use crate::compiler::Compiler;
    use crate::opcodes::MAGIC_HEADER;
    use crate::parser::Parser;

    let mut parser = Parser::new(TEST_GLINT_INPUT);

    assert!(
        parser.parse_all().is_ok(),
        "Парсер вернул ошибку на валидном конфиге DE!"
    );

    let m = &parser.module;
    assert!(!m.elem_types.is_empty(), "Не найдено ни одного элемента!");

    assert_eq!(m.elem_types.len(), m.elem_prop_spans.len());
    assert_eq!(m.elem_prop_spans.len(), m.elem_child_spans.len());
    assert_eq!(m.prop_keys.len(), m.prop_values.len());

    let mut child_indices = std::collections::HashSet::new();
    for (start, len) in &m.elem_child_spans {
        for i in *start..(*start + *len) {
            child_indices.insert(i as usize);
        }
    }

    let mut root_nodes = Vec::new();
    for (i, node) in m.hierarchy.iter().enumerate() {
        if !child_indices.contains(&i) {
            root_nodes.push(node.clone());
        }
    }

    let compiler = Compiler::new(m);
    let bytecode = compiler.compile(&root_nodes);

    assert_eq!(
        &bytecode[0..4],
        &MAGIC_HEADER,
        "Отсутствует или поврежден GLBC заголовок"
    );
    assert!(bytecode.len() > 50, "Байткод подозрительно мал");
}