fix: runtime support

This commit is contained in:
faynot
2026-07-08 23:35:24 +03:00
parent 04b354d85c
commit 97a534438f
5 changed files with 187 additions and 165 deletions

View File

@@ -1,16 +1,16 @@
use crate::ast::*;
use std::collections::HashMap;
pub struct StyleParser<'a> {
pub struct StyleParser<'a, 'm> {
src: &'a [u8],
pos: usize,
pub module: &'a mut ModuleSoA,
variables: HashMap<String, Value>,
mixins: HashMap<String, Vec<(String, Value)>>,
pub module: &'m mut ModuleSoA<'a>,
variables: HashMap<String, Value<'a>>,
mixins: HashMap<String, Vec<(&'a str, Value<'a>)>>,
}
impl<'a> StyleParser<'a> {
pub fn new(src: &'a str, module: &'a mut ModuleSoA) -> Self {
impl<'a, 'm> StyleParser<'a, 'm> {
pub fn new(src: &'a str, module: &'m mut ModuleSoA<'a>) -> Self {
Self {
src: src.as_bytes(),
pos: 0,
@@ -20,14 +20,17 @@ impl<'a> StyleParser<'a> {
}
}
#[inline(always)]
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
#[inline(always)]
fn advance(&mut self) {
self.pos += 1;
}
#[inline(always)]
fn consume_if(&mut self, expected: u8) -> bool {
if self.peek() == Some(expected) {
self.pos += 1;
@@ -37,6 +40,7 @@ impl<'a> StyleParser<'a> {
}
}
#[inline]
fn skip_whitespace(&mut self) {
let len = self.src.len();
while self.pos < len {
@@ -54,44 +58,46 @@ impl<'a> StyleParser<'a> {
}
}
fn parse_ident(&mut self) -> String {
#[inline]
fn parse_ident(&mut self) -> &'a str {
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'_') {
if c.is_ascii_alphanumeric() || c == b'-' || c == b'_' {
self.pos += 1;
} else {
break;
}
}
unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]).to_string() }
unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) }
}
fn parse_selector(&mut self) -> String {
#[inline]
fn parse_selector(&mut self) -> &'a str {
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() }
unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) }.trim()
}
fn parse_single_value(&self, s: &str) -> Value {
fn parse_single_value(&self, s: &'a str) -> Value<'a> {
let s = s.trim_end_matches(',');
if let Some(stripped) = s.strip_prefix('#') {
return Value::Color(format!("#{}", stripped));
if let Some(_stripped) = s.strip_prefix('#') {
return Value::Color(s);
}
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());
return Value::Variable(var_name);
}
if s.contains('(') && s.ends_with(')') {
let parts: Vec<&str> = s.splitn(2, '(').collect();
let name = parts[0].trim().to_string();
let name = parts[0].trim();
let args_str = parts[1].trim_end_matches(')');
let args = args_str.split(',')
.filter(|a| !a.trim().is_empty())
@@ -109,24 +115,27 @@ impl<'a> StyleParser<'a> {
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());
return Value::Unit(num, &s[num_end..]);
}
}
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())
match s {
"true" => Value::Bool(true),
"false" => Value::Bool(false),
"null" => Value::Null,
_ => Value::Ident(s),
}
}
fn parse_value_line(&mut self) -> Value {
fn parse_value_line(&mut self) -> Value<'a> {
let start = self.pos;
let mut parens = 0;
let len = self.src.len();
while self.pos < self.src.len() {
while self.pos < len {
let c = self.src[self.pos];
if c == b'(' { parens += 1; }
else if c == b')' { parens -= 1; }
@@ -140,31 +149,43 @@ impl<'a> StyleParser<'a> {
if self.peek() == Some(b';') { self.advance(); }
let mut tokens = Vec::new();
let mut current = String::new();
let mut token_start = None;
let mut p = 0;
for c in line.chars() {
let bytes = line.as_bytes();
for (i, &c) in bytes.iter().enumerate() {
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();
b'(' => {
p += 1;
if token_start.is_none() { token_start = Some(i); }
}
b')' => {
if p > 0 { p -= 1; }
if token_start.is_none() { token_start = Some(i); }
}
b' ' | b'\t' if p == 0 => {
if let Some(s_idx) = token_start {
tokens.push(&line[s_idx..i]);
token_start = None;
}
}
_ => current.push(c),
_ => {
if token_start.is_none() { token_start = Some(i); }
}
}
}
if !current.is_empty() { tokens.push(current); }
if let Some(s_idx) = token_start {
tokens.push(&line[s_idx..]);
}
if tokens.len() == 1 {
self.parse_single_value(&tokens[0])
self.parse_single_value(tokens[0])
} else {
Value::Array(tokens.into_iter().map(|t| self.parse_single_value(&t)).collect())
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> {
fn parse_properties(&mut self, target: &mut Vec<(&'a str, Value<'a>)>) -> Result<(), String> {
self.consume_if(b'{');
loop {
self.skip_whitespace();
@@ -176,7 +197,7 @@ impl<'a> StyleParser<'a> {
if ident == "use" {
self.skip_whitespace();
let mixin_name = self.parse_ident();
if let Some(props) = self.mixins.get(&mixin_name) {
if let Some(props) = self.mixins.get(mixin_name) {
target.extend(props.clone());
} else {
return Err(format!("Unknown mixin: {}", mixin_name));
@@ -202,7 +223,7 @@ impl<'a> StyleParser<'a> {
match self.peek() {
Some(b'$') => {
self.advance();
let name = self.parse_ident();
let name = self.parse_ident().to_string();
self.skip_whitespace();
self.consume_if(b'=');
self.skip_whitespace();
@@ -213,10 +234,10 @@ impl<'a> StyleParser<'a> {
Some(b'@') => {
self.advance();
let name = self.parse_ident();
match name.as_str() {
match name {
"mixin" => {
self.skip_whitespace();
let mixin_name = self.parse_ident();
let mixin_name = self.parse_ident().to_string();
self.skip_whitespace();
let mut props = Vec::new();
self.parse_properties(&mut props)?;
@@ -234,7 +255,7 @@ impl<'a> StyleParser<'a> {
self.skip_whitespace();
if self.consume_if(b'}') { break; }
let step = self.parse_selector();
let step = self.parse_selector().to_string();
let mut props = Vec::new();
self.parse_properties(&mut props)?;
@@ -267,8 +288,7 @@ impl<'a> StyleParser<'a> {
}
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 condition = Value::Rhei(cond_str);
let id = self.module.push_directive(Directive::If {
condition,
child_span: (start_idx, len),