glt 0.1.4

Glint compiler library
Documentation
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;
                        
                        // Condition evaluates at runtime via Rhei
                        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(())
    }
}