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

@@ -7,91 +7,91 @@ pub enum NodeId {
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
String(String),
pub enum Value<'a> {
String(&'a str),
Int(i64),
Float(f64),
Bool(bool),
Color(String),
FsPath(String),
Variable(String),
Rhei(String),
Color(&'a str),
FsPath(&'a str),
Variable(&'a str),
Rhei(&'a str),
Null,
Array(Vec<Value>),
// glts
Call(String, Vec<Value>), // rgba(30, 30, 46, 0.88)
Unit(f64, String), // 8px, 120ms
Ident(String),
Array(Vec<Value<'a>>),
Call(&'a str, Vec<Value<'a>>),
Unit(f64, &'a str),
Ident(&'a str),
}
#[derive(Debug, Clone)]
pub enum Directive {
pub enum Directive<'a> {
Version(i64),
Style(String),
Style(&'a str),
Global {
name: String,
value: Value,
name: &'a str,
value: Value<'a>,
},
Singleton {
name: String,
name: &'a str,
prop_span: (u32, u32),
},
Component {
name: String,
params: Vec<(String, String)>,
name: &'a str,
params: Vec<(&'a str, &'a str)>,
child_span: (u32, u32),
},
Let {
name: String,
value: Value,
name: &'a str,
value: Value<'a>,
},
If {
condition: Value,
condition: Value<'a>,
child_span: (u32, u32),
else_span: Option<(u32, u32)>,
},
Each {
item: String,
collection: Value,
item: &'a str,
collection: Value<'a>,
child_span: (u32, u32),
},
On {
event: String,
args: Vec<(String, Value)>,
event: &'a str,
args: Vec<(&'a str, Value<'a>)>,
child_span: (u32, u32),
},
RheiBlock(String),
RheiBlock(&'a str),
StyleRule {
selector: String,
selector: &'a str,
prop_span: (u32, u32),
},
StyleAnim {
name: String,
name: &'a str,
frames: Vec<(String, (u32, u32))>,
},
}
#[derive(Debug, Default)]
pub struct ModuleSoA {
pub elem_types: Vec<String>,
pub struct ModuleSoA<'a> {
pub elem_types: Vec<&'a str>,
pub elem_prop_spans: Vec<(u32, u32)>,
pub elem_child_spans: Vec<(u32, u32)>,
pub elem_content: Vec<Option<Value>>,
pub elem_content: Vec<Option<Value<'a>>>,
pub prop_keys: Vec<String>,
pub prop_values: Vec<Value>,
pub prop_keys: Vec<&'a str>,
pub prop_values: Vec<Value<'a>>,
pub directives: Vec<Directive>,
pub directives: Vec<Directive<'a>>,
pub hierarchy: Vec<NodeId>,
}
impl ModuleSoA {
impl<'a> ModuleSoA<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn push_element(&mut self, typ: String) -> u32 {
#[inline]
pub fn push_element(&mut self, typ: &'a str) -> u32 {
let id = self.elem_types.len() as u32;
self.elem_types.push(typ);
self.elem_prop_spans.push((0, 0));
@@ -100,7 +100,8 @@ impl ModuleSoA {
id
}
pub fn push_directive(&mut self, dir: Directive) -> u32 {
#[inline]
pub fn push_directive(&mut self, dir: Directive<'a>) -> u32 {
let id = self.directives.len() as u32;
self.directives.push(dir);
id

View File

@@ -1,13 +1,13 @@
use crate::ast::*;
use crate::opcodes::*;
pub struct Compiler<'a> {
module: &'a ModuleSoA,
pub struct Compiler<'a, 'b> {
module: &'b ModuleSoA<'a>,
buf: Vec<u8>,
}
impl<'a> Compiler<'a> {
pub fn new(module: &'a ModuleSoA) -> Self {
impl<'a, 'b> Compiler<'a, 'b> {
pub fn new(module: &'b ModuleSoA<'a>) -> Self {
Self {
module,
buf: Vec::with_capacity(module.hierarchy.len() * 32 + std::mem::size_of_val(&MAGIC_HEADER)),
@@ -38,17 +38,17 @@ impl<'a> Compiler<'a> {
self.buf.push(OP_END_BLOCK);
}
fn compile_element(&mut self, id: u32) {
fn compile_element(&mut self, id: u32) {
let idx = id as usize;
self.buf.push(OP_ELEM_PUSH);
self.write_string(&self.module.elem_types[idx]);
self.write_string(self.module.elem_types[idx]);
let (p_start, p_len) = self.module.elem_prop_spans[idx];
let p_start = p_start as usize;
let p_end = p_start + p_len as usize;
for i in p_start..p_end {
self.compile_property(&self.module.prop_keys[i], &self.module.prop_values[i]);
self.compile_property(self.module.prop_keys[i], &self.module.prop_values[i]);
}
if let Some(content) = &self.module.elem_content[idx] {
@@ -90,7 +90,7 @@ fn compile_element(&mut self, id: u32) {
let start = prop_span.0 as usize;
let end = start + prop_span.1 as usize;
for i in start..end {
self.write_string(&self.module.prop_keys[i]);
self.write_string(self.module.prop_keys[i]);
self.compile_value(&self.module.prop_values[i]);
}
}
@@ -148,7 +148,7 @@ fn compile_element(&mut self, id: u32) {
let start = prop_span.0 as usize;
let end = start + prop_span.1 as usize;
for i in start..end {
self.write_string(&self.module.prop_keys[i]);
self.write_string(self.module.prop_keys[i]);
self.compile_value(&self.module.prop_values[i]);
}
}
@@ -162,7 +162,7 @@ fn compile_element(&mut self, id: u32) {
let start = span.0 as usize;
let end = start + span.1 as usize;
for i in start..end {
self.write_string(&self.module.prop_keys[i]);
self.write_string(self.module.prop_keys[i]);
self.compile_value(&self.module.prop_values[i]);
}
}
@@ -183,7 +183,6 @@ fn compile_element(&mut self, id: u32) {
Value::Rhei(_) => OP_PROP_RHEI,
Value::Null => OP_PROP_NULL,
Value::Array(_) => OP_PROP_ARRAY,
// glts
Value::Call(_, _) => OP_PROP_CALL,
Value::Unit(_, _) => OP_PROP_UNIT,
Value::Ident(_) => OP_PROP_IDENT,
@@ -210,7 +209,6 @@ fn compile_element(&mut self, id: u32) {
Value::Rhei(_) => OP_PROP_RHEI,
Value::Null => OP_PROP_NULL,
Value::Array(_) => OP_PROP_ARRAY,
// glts
Value::Call(_, _) => OP_PROP_CALL,
Value::Unit(_, _) => OP_PROP_UNIT,
Value::Ident(_) => OP_PROP_IDENT,
@@ -222,7 +220,7 @@ fn compile_element(&mut self, id: u32) {
#[inline]
fn compile_value_data(&mut self, val: &Value) {
match val {
Value::String(s) | Value::Color(s) | Value::FsPath(s) | Value::Variable(s) | Value::Rhei(s) => {
Value::String(s) | Value::Color(s) | Value::FsPath(s) | Value::Variable(s) | Value::Rhei(s) | Value::Ident(s) => {
self.write_string(s);
}
Value::Int(i) => self.write_i64(*i),
@@ -235,7 +233,6 @@ fn compile_element(&mut self, id: u32) {
self.compile_value(v);
}
}
Value::Ident(s) => self.write_string(s),
Value::Unit(num, unit) => {
self.write_f64(*num);
self.write_string(unit);

View File

@@ -11,7 +11,6 @@ pub use style_parser::StyleParser;
pub fn compile_project(gltm_src: &str, glts_src: &str) -> Result<Vec<u8>, String> {
let mut module = ModuleSoA::new();
let mut style_parser = StyleParser::new(glts_src, &mut module);
style_parser.parse_all()?;

View File

@@ -3,16 +3,15 @@ use crate::ast::*;
pub struct Parser<'a> {
src: &'a [u8],
pos: usize,
pub module: ModuleSoA,
pub module: ModuleSoA<'a>,
}
impl<'a> Parser<'a> {
pub fn new(src: &'a str) -> Self {
Self::with_module(src, ModuleSoA::new())
}
pub fn with_module(src: &'a str, module: ModuleSoA) -> Self {
pub fn with_module(src: &'a str, module: ModuleSoA<'a>) -> Self {
Self {
src: src.as_bytes(),
pos: 0,
@@ -30,9 +29,9 @@ impl<'a> Parser<'a> {
self.pos += 1;
}
#[inline]
#[inline(always)]
fn consume_if(&mut self, expected: u8) -> bool {
if self.peek() == Some(expected) {
if self.src.get(self.pos) == Some(&expected) {
self.pos += 1;
true
} else {
@@ -40,6 +39,7 @@ impl<'a> Parser<'a> {
}
}
#[inline]
fn skip_whitespace(&mut self) {
let len = self.src.len();
while self.pos < len {
@@ -57,21 +57,23 @@ impl<'a> Parser<'a> {
}
}
fn parse_ident(&mut self) -> String {
#[inline]
fn parse_ident(&mut self) -> &'a str {
let start = self.pos;
let len = self.src.len();
while self.pos < len {
let c = self.src[self.pos];
if c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.') {
if c.is_ascii_alphanumeric() || c == b'-' || 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_rhei_expr(&mut self) -> Result<String, String> {
#[inline]
fn parse_rhei_expr(&mut self) -> Result<&'a str, String> {
self.skip_whitespace();
if self.consume_if(b'{') {
let start = self.pos;
@@ -91,7 +93,7 @@ impl<'a> Parser<'a> {
}
let expr = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos - 1]) };
Ok(expr.trim().to_string())
Ok(expr.trim())
} else {
let start = self.pos;
let mut parens = 0;
@@ -124,11 +126,11 @@ impl<'a> Parser<'a> {
self.pos += 1;
}
let expr = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) };
Ok(expr.trim().to_string())
Ok(expr.trim())
}
}
fn parse_value(&mut self) -> Result<Value, String> {
fn parse_value(&mut self) -> Result<Value<'a>, String> {
self.skip_whitespace();
let c = self.peek().ok_or("Expected value, found EOF")?;
@@ -143,7 +145,7 @@ impl<'a> Parser<'a> {
if self.pos >= len {
return Err("Unexpected EOF inside string literal".into());
}
let val = std::str::from_utf8(&self.src[start..self.pos]).unwrap().to_string();
let val = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) };
self.advance();
Ok(Value::String(val))
}
@@ -160,8 +162,11 @@ impl<'a> Parser<'a> {
Ok(Value::Array(items))
}
b'#' => {
let start = self.pos;
self.advance();
Ok(Value::Color(format!("#{}", self.parse_ident())))
let _ = self.parse_ident();
let color_str = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) };
Ok(Value::Color(color_str))
}
b'$' => {
self.advance();
@@ -183,7 +188,7 @@ impl<'a> Parser<'a> {
}
_ => {
let s = self.parse_ident();
match s.as_str() {
match s {
"true" => Ok(Value::Bool(true)),
"false" => Ok(Value::Bool(false)),
"null" => Ok(Value::Null),
@@ -198,7 +203,7 @@ impl<'a> Parser<'a> {
}
self.pos += 1;
}
let path = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) }.to_string();
let path = unsafe { std::str::from_utf8_unchecked(&self.src[start..self.pos]) };
Ok(Value::FsPath(path))
}
_ => Err(format!("Unknown value token: {}", s)),
@@ -257,7 +262,7 @@ impl<'a> Parser<'a> {
fn parse_directive(&mut self) -> Result<NodeId, String> {
self.advance();
let name = self.parse_ident();
let dir = match name.as_str() {
let dir = match name {
"version" => {
self.skip_whitespace();
Directive::Version(self.parse_ident().parse().map_err(|_| "Invalid version")?)
@@ -269,17 +274,17 @@ impl<'a> Parser<'a> {
"global" => {
self.skip_whitespace();
if !self.consume_if(b'$') { return Err("Expected '$' for variable name in @global".into()); }
let name = self.parse_ident();
let var_name = self.parse_ident();
self.skip_whitespace();
if !self.consume_if(b'=') { return Err(format!("Expected '=' after global variable name '${}'", name)); }
Directive::Global { name, value: self.parse_value()? }
if !self.consume_if(b'=') { return Err(format!("Expected '=' after global variable name '${}'", var_name)); }
Directive::Global { name: var_name, value: self.parse_value()? }
}
"singleton" => {
self.skip_whitespace();
let name = self.parse_ident();
let s_name = self.parse_ident();
self.skip_whitespace();
let start_idx = self.module.prop_keys.len() as u32;
if !self.consume_if(b'{') { return Err(format!("Expected '{{' for singleton '{}'", name)); }
if !self.consume_if(b'{') { return Err(format!("Expected '{{' for singleton '{}'", s_name)); }
loop {
self.skip_whitespace();
if self.consume_if(b'}') { break; }
@@ -296,11 +301,11 @@ impl<'a> Parser<'a> {
self.consume_if(b',');
}
let len = (self.module.prop_keys.len() as u32) - start_idx;
Directive::Singleton { name, prop_span: (start_idx, len) }
Directive::Singleton { name: s_name, prop_span: (start_idx, len) }
}
"component" => {
self.skip_whitespace();
let name = self.parse_ident();
let comp_name = self.parse_ident();
let mut params = Vec::new();
self.skip_whitespace();
if self.consume_if(b'(') {
@@ -322,15 +327,15 @@ impl<'a> Parser<'a> {
self.consume_if(b',');
}
}
Directive::Component { name, params, child_span: self.parse_block()? }
Directive::Component { name: comp_name, params, child_span: self.parse_block()? }
}
"let" => {
self.skip_whitespace();
if !self.consume_if(b'$') { return Err("Expected '$' for variable name in @let".into()); }
let name = self.parse_ident();
let var_name = self.parse_ident();
self.skip_whitespace();
if !self.consume_if(b'=') { return Err(format!("Expected '=' after variable name '${}'", name)); }
Directive::Let { name, value: self.parse_value()? }
if !self.consume_if(b'=') { return Err(format!("Expected '=' after variable name '${}'", var_name)); }
Directive::Let { name: var_name, value: self.parse_value()? }
}
"if" => {
self.skip_whitespace();
@@ -344,7 +349,7 @@ impl<'a> Parser<'a> {
if self.parse_ident() == "else" {
else_span = Some(self.parse_block()?);
} else {
self.pos = backup; // rollback
self.pos = backup;
}
}
Directive::If { condition, child_span, else_span }
@@ -383,36 +388,36 @@ impl<'a> Parser<'a> {
}
fn parse_element(&mut self) -> Result<NodeId, String> {
let name = self.parse_ident();
let el_id = self.module.push_element(name);
let name = self.parse_ident();
let el_id = self.module.push_element(name);
self.skip_whitespace();
if self.peek() == Some(b'(') {
self.module.elem_prop_spans[el_id as usize] = self.parse_properties()?;
}
self.skip_whitespace();
match self.peek() {
Some(b'{') => {
self.module.elem_child_spans[el_id as usize] = self.parse_block()?;
self.skip_whitespace();
if self.peek() == Some(b'(') {
self.module.elem_prop_spans[el_id as usize] = self.parse_properties()?;
}
Some(ch) if matches!(ch, b'"' | b'#' | b'$' | b'!' | b'-' | b'[' | b'0'..=b'9') => {
self.module.elem_content[el_id as usize] = Some(self.parse_value()?);
}
Some(b'a'..=b'z') => {
let backup = self.pos;
let ident = self.parse_ident();
self.pos = backup;
if matches!(ident.as_str(), "true" | "false" | "null" | "fs") {
self.skip_whitespace();
match self.peek() {
Some(b'{') => {
self.module.elem_child_spans[el_id as usize] = self.parse_block()?;
}
Some(ch) if matches!(ch, b'"' | b'#' | b'$' | b'!' | b'-' | b'[' | b'0'..=b'9') => {
self.module.elem_content[el_id as usize] = Some(self.parse_value()?);
}
}
_ => {}
}
Some(b'a'..=b'z') => {
let backup = self.pos;
let ident = self.parse_ident();
self.pos = backup;
Ok(NodeId::Element(el_id))
}
if matches!(ident, "true" | "false" | "null" | "fs") {
self.module.elem_content[el_id as usize] = Some(self.parse_value()?);
}
}
_ => {}
}
Ok(NodeId::Element(el_id))
}
pub fn parse_node(&mut self) -> Result<NodeId, String> {
self.skip_whitespace();
@@ -430,7 +435,7 @@ impl<'a> Parser<'a> {
b'A'..=b'Z' => self.parse_element(),
ch if matches!(ch, b'"' | b'#' | b'$' | b'-' | b'[' | b'0'..=b'9') => {
let val = self.parse_value()?;
let el_id = self.module.push_element("#text".to_string());
let el_id = self.module.push_element("#text");
self.module.elem_content[el_id as usize] = Some(val);
Ok(NodeId::Element(el_id))
}

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),