From e01490774d645b260070a7b701a92a1615d6f48b Mon Sep 17 00:00:00 2001 From: Faynot Date: Mon, 18 May 2026 18:30:04 +0300 Subject: [PATCH] fix: parse & compile, add null & array support --- src/ast.rs | 2 + src/compiler.rs | 26 ++++++++++- src/opcodes.rs | 2 + src/parser.rs | 118 ++++++++++++++++++++++++++++++------------------ 4 files changed, 102 insertions(+), 46 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index b979f5d..0cb4a50 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -16,6 +16,8 @@ pub enum Value { FsPath(String), Variable(String), Rhei(String), + Null, + Array(Vec), } #[derive(Debug, Clone)] diff --git a/src/compiler.rs b/src/compiler.rs index 2df76ae..1cba336 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -123,10 +123,10 @@ impl<'a> Compiler<'a> { self.compile_value(condition); self.compile_block(*child_span); if let Some(es) = else_span { - self.buf.push(1); // Has else + self.buf.push(1); self.compile_block(*es); } else { - self.buf.push(0); // No else + self.buf.push(0); } } Directive::Each { @@ -202,6 +202,18 @@ impl<'a> Compiler<'a> { self.write_string(key); self.write_string(r); } + Value::Null => { + self.buf.push(OP_PROP_NULL); + self.write_string(key); + } + Value::Array(arr) => { + self.buf.push(OP_PROP_ARRAY); + self.write_string(key); + self.write_u32(arr.len() as u32); + for v in arr { + self.compile_value(v); + } + } } } @@ -239,6 +251,16 @@ impl<'a> Compiler<'a> { self.buf.push(OP_PROP_RHEI); self.write_string(r); } + Value::Null => { + self.buf.push(OP_PROP_NULL); + } + Value::Array(arr) => { + self.buf.push(OP_PROP_ARRAY); + self.write_u32(arr.len() as u32); + for v in arr { + self.compile_value(v); + } + } } } diff --git a/src/opcodes.rs b/src/opcodes.rs index 4703c54..9f33c8b 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -11,6 +11,8 @@ pub const OP_IF: u8 = 0x07; pub const OP_EACH: u8 = 0x08; pub const OP_ON: u8 = 0x09; pub const OP_RHEI_BLK: u8 = 0x0A; +pub const OP_PROP_NULL: u8 = 0x28; +pub const OP_PROP_ARRAY: u8 = 0x29; // elements managment pub const OP_ELEM_PUSH: u8 = 0x10; diff --git a/src/parser.rs b/src/parser.rs index 049cf88..34cc6db 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -66,7 +66,7 @@ impl<'a> Parser<'a> { .to_string() } - fn parse_rhei_expr(&mut self) -> Result { +fn parse_rhei_expr(&mut self) -> Result { self.skip_whitespace(); if self.consume_if(b'{') { let start = self.pos; @@ -74,21 +74,16 @@ impl<'a> Parser<'a> { while depth > 0 { let c = self.peek().ok_or("Unexpected EOF in Rhei block")?; self.advance(); - if c == b'{' { - depth += 1; - } - if c == b'}' { - depth -= 1; - } + if c == b'{' { depth += 1; } + if c == b'}' { depth -= 1; } } let expr = std::str::from_utf8(&self.src[start..self.pos - 1]) - .unwrap() - .trim() - .to_string(); + .unwrap().trim().to_string(); Ok(expr) } else { let start = self.pos; let mut parens = 0; + let mut brackets = 0; let mut in_str = false; let mut escape = false; while let Some(c) = self.peek() { @@ -102,29 +97,27 @@ impl<'a> Parser<'a> { self.advance(); continue; } - if c == b'"' { - in_str = !in_str; - } + if c == b'"' { in_str = !in_str; } if !in_str { - if c == b'(' { - parens += 1; - } + if c == b'(' { parens += 1; } if c == b')' { - if parens == 0 { - break; - } + if parens == 0 { break; } parens -= 1; } - if parens == 0 && (c == b',' || c == b'\n' || c == b'}' || c == b'{') { - break; + if c == b'[' { brackets += 1; } + if c == b']' { + if brackets > 0 { brackets -= 1; } + } + if parens == 0 && brackets == 0 { + if c == b',' || c == b'\n' || c == b'}' || c == b'{' { + break; + } } } self.advance(); } let expr = std::str::from_utf8(&self.src[start..self.pos]) - .unwrap() - .trim() - .to_string(); + .unwrap().trim().to_string(); Ok(expr) } } @@ -142,12 +135,23 @@ impl<'a> Parser<'a> { } self.advance(); } - let val = std::str::from_utf8(&self.src[start..self.pos]) - .unwrap() - .to_string(); + let val = std::str::from_utf8(&self.src[start..self.pos]).unwrap().to_string(); self.advance(); Ok(Value::String(val)) } + b'[' => { + self.advance(); + let mut items = Vec::new(); + loop { + self.skip_whitespace(); + if self.consume_if(b']') { break; } + let val = self.parse_value()?; + items.push(val); + self.skip_whitespace(); + self.consume_if(b','); + } + Ok(Value::Array(items)) + } b'#' => { self.advance(); let mut color = String::from("#"); @@ -161,9 +165,7 @@ impl<'a> Parser<'a> { b'!' => { self.advance(); let ident = self.parse_ident(); - if ident != "rhei" { - return Err("Expected !rhei".into()); - } + if ident != "rhei" { return Err("Expected !rhei".into()); } self.consume_if(b':'); Ok(Value::Rhei(self.parse_rhei_expr()?)) } @@ -180,6 +182,7 @@ impl<'a> Parser<'a> { match s.as_str() { "true" => Ok(Value::Bool(true)), "false" => Ok(Value::Bool(false)), + "null" => Ok(Value::Null), "fs" => { self.consume_if(b':'); let start = self.pos; @@ -189,9 +192,7 @@ impl<'a> Parser<'a> { } self.advance(); } - let path = std::str::from_utf8(&self.src[start..self.pos]) - .unwrap() - .to_string(); + let path = std::str::from_utf8(&self.src[start..self.pos]).unwrap().to_string(); Ok(Value::FsPath(path)) } _ => Err(format!("Unknown value token: {}", s)), @@ -320,7 +321,16 @@ impl<'a> Parser<'a> { if self.consume_if(b')') { break; } + let pname = self.parse_ident(); + + if pname.is_empty() { + return Err(format!( + "Expected parameter name in @component, found {:?}", + self.peek().map(|b| b as char) + )); + } + self.skip_whitespace(); self.consume_if(b':'); self.skip_whitespace(); @@ -349,8 +359,12 @@ impl<'a> Parser<'a> { value: val, } } - "if" => { - let condition = self.parse_value()?; +"if" => { + self.skip_whitespace(); + + let expr_str = self.parse_rhei_expr()?; + let condition = Value::Rhei(expr_str); + let child_span = self.parse_block()?; let mut else_span = None; self.skip_whitespace(); @@ -418,7 +432,7 @@ impl<'a> Parser<'a> { Ok(NodeId::Directive(self.module.push_directive(dir))) } - fn parse_element(&mut self) -> Result { +fn parse_element(&mut self) -> Result { let name = self.parse_ident(); let el_id = self.module.push_element(name); @@ -433,9 +447,11 @@ impl<'a> Parser<'a> { if p == Some(b'{') { let span = self.parse_block()?; self.module.elem_child_spans[el_id as usize] = span; - } else if p == Some(b'"') || p == Some(b'$') { - let val = self.parse_value()?; - self.module.elem_content[el_id as usize] = Some(val); + } else if let Some(ch) = p { + if ch == b'"' || ch == b'#' || ch == b'$' || ch == b'!' || ch == b'-' || ch.is_ascii_digit() || ch == b'[' || ch.is_ascii_lowercase() { + let val = self.parse_value()?; + self.module.elem_content[el_id as usize] = Some(val); + } } Ok(NodeId::Element(el_id)) @@ -450,9 +466,7 @@ impl<'a> Parser<'a> { b'!' => { self.advance(); let i = self.parse_ident(); - if i != "rhei" { - return Err("Expected rhei".into()); - } + if i != "rhei" { return Err("Expected rhei".into()); } self.consume_if(b':'); let expr = self.parse_rhei_expr()?; Ok(NodeId::Directive( @@ -460,13 +474,29 @@ impl<'a> Parser<'a> { )) } b'A'..=b'Z' => self.parse_element(), - b'"' | b'$' => { + b'"' | b'#' | b'$' | b'-' | b'0'..=b'9' | b'[' => { let val = self.parse_value()?; let el_id = self.module.push_element("#text".to_string()); self.module.elem_content[el_id as usize] = Some(val); Ok(NodeId::Element(el_id)) } - _ => Err(format!("Unexpected token: {}", c as char)), + ch if ch.is_ascii_lowercase() => { + let val = self.parse_value()?; + let el_id = self.module.push_element("#text".to_string()); + self.module.elem_content[el_id as usize] = Some(val); + Ok(NodeId::Element(el_id)) + } + _ => { + let extract_start = self.pos.saturating_sub(10); + let extract_end = std::cmp::min(self.src.len(), self.pos + 20); + let context = std::str::from_utf8(&self.src[extract_start..extract_end]) + .unwrap_or(""); + + Err(format!( + "Unexpected token: '{}' at position {}. Context around: \"...{}...\"", + c as char, self.pos, context.trim() + )) + } } }