fix: runtime support
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user