#[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, "Байткод подозрительно мал");
}