use crate::ast::*;
use std::collections::HashMap;
pub struct StyleParser<'a> {
src: &'a [u8],
pos: usize,
pub module: &'a mut ModuleSoA,
variables: HashMap<String, Value>,
mixins: HashMap<String, Vec<(String, Value)>>,
}
impl<'a> StyleParser<'a> {
pub fn new(src: &'a str, module: &'a mut ModuleSoA) -> Self {
Self {
src: src.as_bytes(),
pos: 0,
module,
variables: HashMap::new(),
mixins: HashMap::new(),
}
}
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
fn advance(&mut self) {
self.pos += 1;
}
fn consume_if(&mut self, expected: u8) -> bool {
if self.peek() == Some(expected) {
self.pos += 1;
true
} else {
false
}
}
fn skip_whitespace(&mut self) {
let len = self.src.len();
while self.pos < len {
let c = self.src[self.pos];
if c.is_ascii_whitespace() {
self.pos += 1;
} else if c == b'/' && self.pos + 1 < len && self.src[self.pos + 1] == b'/' {
self.pos += 2;
while self.pos < len && self.src[self.pos] != b'\n' {
self.pos += 1;
}
} else {
break;
}
}
}
fn parse_ident(&mut self) -> String {
let start = self.pos;
while self.pos < self.src.len() {
let c = self.src[self.pos];
if c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_') {
self.pos += 1;
} else {
break;
}
}
unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]).to_string() }
}
fn parse_selector(&mut self) -> String {
let start = self.pos;
while self.pos < self.src.len() && self.src[self.pos] != b'{' {
self.pos += 1;
}
unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]).trim().to_string() }
}
fn parse_single_value(&self, s: &str) -> Value {
let s = s.trim_end_matches(',');
if let Some(stripped) = s.strip_prefix('#') {
return Value::Color(format!("#{}", stripped));
}
if let Some(var_name) = s.strip_prefix('$') {
if let Some(val) = self.variables.get(var_name) {
return val.clone();
}
return Value::Variable(var_name.to_string());
}
if s.contains('(') && s.ends_with(')') {
let parts: Vec<&str> = s.splitn(2, '(').collect();
let name = parts[0].trim().to_string();
let args_str = parts[1].trim_end_matches(')');
let args = args_str.split(',')
.filter(|a| !a.trim().is_empty())
.map(|a| self.parse_single_value(a.trim()))
.collect();
return Value::Call(name, args);
}
let mut num_end = 0;
for (i, c) in s.char_indices() {
if c.is_ascii_digit() || c == '.' || c == '-' {
num_end = i + c.len_utf8();
} else { break; }
}
if num_end > 0 && num_end < s.len() {
if let Ok(num) = s[..num_end].parse::<f64>() {
return Value::Unit(num, s[num_end..].to_string());
}
}
if let Ok(i) = s.parse::<i64>() { return Value::Int(i); }
if let Ok(f) = s.parse::<f64>() { return Value::Float(f); }
if s == "true" { return Value::Bool(true); }
if s == "false" { return Value::Bool(false); }
if s == "null" { return Value::Null; }
Value::Ident(s.to_string())
}
fn parse_value_line(&mut self) -> Value {
let start = self.pos;
let mut parens = 0;
while self.pos < self.src.len() {
let c = self.src[self.pos];
if c == b'(' { parens += 1; }
else if c == b')' { parens -= 1; }
else if parens == 0 && matches!(c, b'\n' | b'\r' | b';' | b'}') {
break;
}
self.pos += 1;
}
let line = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) }.trim();
if self.peek() == Some(b';') { self.advance(); }
let mut tokens = Vec::new();
let mut current = String::new();
let mut p = 0;
for c in line.chars() {
match c {
'(' => { p += 1; current.push(c); }
')' => { p -= 1; current.push(c); }
' ' | '\t' if p == 0 => {
if !current.is_empty() {
tokens.push(current.clone());
current.clear();
}
}
_ => current.push(c),
}
}
if !current.is_empty() { tokens.push(current); }
if tokens.len() == 1 {
self.parse_single_value(&tokens[0])
} else {
Value::Array(tokens.into_iter().map(|t| self.parse_single_value(&t)).collect())
}
}
fn parse_properties(&mut self, target: &mut Vec<(String, Value)>) -> Result<(), String> {
self.consume_if(b'{');
loop {
self.skip_whitespace();
if self.consume_if(b'}') { break; }
if self.peek() == Some(b'@') {
self.advance();
let ident = self.parse_ident();
if ident == "use" {
self.skip_whitespace();
let mixin_name = self.parse_ident();
if let Some(props) = self.mixins.get(&mixin_name) {
target.extend(props.clone());
} else {
return Err(format!("Unknown mixin: {}", mixin_name));
}
}
continue;
}
let key = self.parse_ident();
if key.is_empty() { return Err("Expected property key".into()); }
self.skip_whitespace();
if !self.consume_if(b':') { return Err("Expected ':' after property".into()); }
self.skip_whitespace();
let val = self.parse_value_line();
target.push((key, val));
}
Ok(())
}
fn parse_top_level(&mut self) -> Result<Option<NodeId>, String> {
match self.peek() {
Some(b'$') => {
self.advance();
let name = self.parse_ident();
self.skip_whitespace();
self.consume_if(b'=');
self.skip_whitespace();
let val = self.parse_value_line();
self.variables.insert(name, val);
Ok(None)
}
Some(b'@') => {
self.advance();
let name = self.parse_ident();
match name.as_str() {
"mixin" => {
self.skip_whitespace();
let mixin_name = self.parse_ident();
self.skip_whitespace();
let mut props = Vec::new();
self.parse_properties(&mut props)?;
self.mixins.insert(mixin_name, props);
Ok(None)
}
"anim" => {
self.skip_whitespace();
let anim_name = self.parse_ident();
self.skip_whitespace();
self.consume_if(b'{');
let mut frames = Vec::new();
loop {
self.skip_whitespace();
if self.consume_if(b'}') { break; }
let step = self.parse_selector();
let mut props = Vec::new();
self.parse_properties(&mut props)?;
let start_idx = self.module.prop_keys.len() as u32;
for (k, v) in props {
self.module.prop_keys.push(k);
self.module.prop_values.push(v);
}
let len = self.module.prop_keys.len() as u32 - start_idx;
frames.push((step, (start_idx, len)));
}
let id = self.module.push_directive(Directive::StyleAnim { name: anim_name, frames });
Ok(Some(NodeId::Directive(id)))
}
"if" => {
self.skip_whitespace();
let start = self.pos;
while self.pos < self.src.len() && self.src[self.pos] != b'{' { self.pos += 1; }
let cond_str = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) }.trim();
self.consume_if(b'{');
let start_idx = self.module.hierarchy.len() as u32;
loop {
self.skip_whitespace();
if self.consume_if(b'}') { break; }
if let Some(node) = self.parse_top_level()? {
self.module.hierarchy.push(node);
}
}
let len = self.module.hierarchy.len() as u32 - start_idx;
let condition = Value::Rhei(cond_str.to_string());
let id = self.module.push_directive(Directive::If {
condition,
child_span: (start_idx, len),
else_span: None
});
Ok(Some(NodeId::Directive(id)))
}
_ => Err(format!("Unknown directive: @{}", name)),
}
}
_ => {
let selector = self.parse_selector();
if selector.is_empty() { return Err("Expected selector".into()); }
let mut props = Vec::new();
self.parse_properties(&mut props)?;
let start_idx = self.module.prop_keys.len() as u32;
for (k, v) in props {
self.module.prop_keys.push(k);
self.module.prop_values.push(v);
}
let len = self.module.prop_keys.len() as u32 - start_idx;
let id = self.module.push_directive(Directive::StyleRule { selector, prop_span: (start_idx, len) });
Ok(Some(NodeId::Directive(id)))
}
}
}
pub fn parse_all(&mut self) -> Result<(), String> {
self.skip_whitespace();
while self.pos < self.src.len() {
if let Some(node) = self.parse_top_level()? {
self.module.hierarchy.push(node);
}
self.skip_whitespace();
}
Ok(())
}
}