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

View File

@@ -1,13 +1,13 @@
use crate::ast::*; use crate::ast::*;
use crate::opcodes::*; use crate::opcodes::*;
pub struct Compiler<'a> { pub struct Compiler<'a, 'b> {
module: &'a ModuleSoA, module: &'b ModuleSoA<'a>,
buf: Vec<u8>, buf: Vec<u8>,
} }
impl<'a> Compiler<'a> { impl<'a, 'b> Compiler<'a, 'b> {
pub fn new(module: &'a ModuleSoA) -> Self { pub fn new(module: &'b ModuleSoA<'a>) -> Self {
Self { Self {
module, module,
buf: Vec::with_capacity(module.hierarchy.len() * 32 + std::mem::size_of_val(&MAGIC_HEADER)), 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); 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; let idx = id as usize;
self.buf.push(OP_ELEM_PUSH); 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_len) = self.module.elem_prop_spans[idx];
let p_start = p_start as usize; let p_start = p_start as usize;
let p_end = p_start + p_len as usize; let p_end = p_start + p_len as usize;
for i in p_start..p_end { 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] { 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 start = prop_span.0 as usize;
let end = start + prop_span.1 as usize; let end = start + prop_span.1 as usize;
for i in start..end { 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]); 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 start = prop_span.0 as usize;
let end = start + prop_span.1 as usize; let end = start + prop_span.1 as usize;
for i in start..end { 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]); 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 start = span.0 as usize;
let end = start + span.1 as usize; let end = start + span.1 as usize;
for i in start..end { 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]); 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::Rhei(_) => OP_PROP_RHEI,
Value::Null => OP_PROP_NULL, Value::Null => OP_PROP_NULL,
Value::Array(_) => OP_PROP_ARRAY, Value::Array(_) => OP_PROP_ARRAY,
// glts
Value::Call(_, _) => OP_PROP_CALL, Value::Call(_, _) => OP_PROP_CALL,
Value::Unit(_, _) => OP_PROP_UNIT, Value::Unit(_, _) => OP_PROP_UNIT,
Value::Ident(_) => OP_PROP_IDENT, Value::Ident(_) => OP_PROP_IDENT,
@@ -210,7 +209,6 @@ fn compile_element(&mut self, id: u32) {
Value::Rhei(_) => OP_PROP_RHEI, Value::Rhei(_) => OP_PROP_RHEI,
Value::Null => OP_PROP_NULL, Value::Null => OP_PROP_NULL,
Value::Array(_) => OP_PROP_ARRAY, Value::Array(_) => OP_PROP_ARRAY,
// glts
Value::Call(_, _) => OP_PROP_CALL, Value::Call(_, _) => OP_PROP_CALL,
Value::Unit(_, _) => OP_PROP_UNIT, Value::Unit(_, _) => OP_PROP_UNIT,
Value::Ident(_) => OP_PROP_IDENT, Value::Ident(_) => OP_PROP_IDENT,
@@ -222,7 +220,7 @@ fn compile_element(&mut self, id: u32) {
#[inline] #[inline]
fn compile_value_data(&mut self, val: &Value) { fn compile_value_data(&mut self, val: &Value) {
match val { 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); self.write_string(s);
} }
Value::Int(i) => self.write_i64(*i), Value::Int(i) => self.write_i64(*i),
@@ -235,7 +233,6 @@ fn compile_element(&mut self, id: u32) {
self.compile_value(v); self.compile_value(v);
} }
} }
Value::Ident(s) => self.write_string(s),
Value::Unit(num, unit) => { Value::Unit(num, unit) => {
self.write_f64(*num); self.write_f64(*num);
self.write_string(unit); 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> { pub fn compile_project(gltm_src: &str, glts_src: &str) -> Result<Vec<u8>, String> {
let mut module = ModuleSoA::new(); let mut module = ModuleSoA::new();
let mut style_parser = StyleParser::new(glts_src, &mut module); let mut style_parser = StyleParser::new(glts_src, &mut module);
style_parser.parse_all()?; style_parser.parse_all()?;

View File

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

View File

@@ -1,16 +1,16 @@
use crate::ast::*; use crate::ast::*;
use std::collections::HashMap; use std::collections::HashMap;
pub struct StyleParser<'a> { pub struct StyleParser<'a, 'm> {
src: &'a [u8], src: &'a [u8],
pos: usize, pos: usize,
pub module: &'a mut ModuleSoA, pub module: &'m mut ModuleSoA<'a>,
variables: HashMap<String, Value>, variables: HashMap<String, Value<'a>>,
mixins: HashMap<String, Vec<(String, Value)>>, mixins: HashMap<String, Vec<(&'a str, Value<'a>)>>,
} }
impl<'a> StyleParser<'a> { impl<'a, 'm> StyleParser<'a, 'm> {
pub fn new(src: &'a str, module: &'a mut ModuleSoA) -> Self { pub fn new(src: &'a str, module: &'m mut ModuleSoA<'a>) -> Self {
Self { Self {
src: src.as_bytes(), src: src.as_bytes(),
pos: 0, pos: 0,
@@ -20,14 +20,17 @@ impl<'a> StyleParser<'a> {
} }
} }
#[inline(always)]
fn peek(&self) -> Option<u8> { fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied() self.src.get(self.pos).copied()
} }
#[inline(always)]
fn advance(&mut self) { fn advance(&mut self) {
self.pos += 1; self.pos += 1;
} }
#[inline(always)]
fn consume_if(&mut self, expected: u8) -> bool { fn consume_if(&mut self, expected: u8) -> bool {
if self.peek() == Some(expected) { if self.peek() == Some(expected) {
self.pos += 1; self.pos += 1;
@@ -37,6 +40,7 @@ impl<'a> StyleParser<'a> {
} }
} }
#[inline]
fn skip_whitespace(&mut self) { fn skip_whitespace(&mut self) {
let len = self.src.len(); let len = self.src.len();
while self.pos < 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; let start = self.pos;
while self.pos < self.src.len() { while self.pos < self.src.len() {
let c = self.src[self.pos]; 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; self.pos += 1;
} else { } else {
break; 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; let start = self.pos;
while self.pos < self.src.len() && self.src[self.pos] != b'{' { while self.pos < self.src.len() && self.src[self.pos] != b'{' {
self.pos += 1; 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(','); let s = s.trim_end_matches(',');
if let Some(stripped) = s.strip_prefix('#') { if let Some(_stripped) = s.strip_prefix('#') {
return Value::Color(format!("#{}", stripped)); return Value::Color(s);
} }
if let Some(var_name) = s.strip_prefix('$') { if let Some(var_name) = s.strip_prefix('$') {
if let Some(val) = self.variables.get(var_name) { if let Some(val) = self.variables.get(var_name) {
return val.clone(); return val.clone();
} }
return Value::Variable(var_name.to_string()); return Value::Variable(var_name);
} }
if s.contains('(') && s.ends_with(')') { if s.contains('(') && s.ends_with(')') {
let parts: Vec<&str> = s.splitn(2, '(').collect(); 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_str = parts[1].trim_end_matches(')');
let args = args_str.split(',') let args = args_str.split(',')
.filter(|a| !a.trim().is_empty()) .filter(|a| !a.trim().is_empty())
@@ -109,24 +115,27 @@ impl<'a> StyleParser<'a> {
if num_end > 0 && num_end < s.len() { if num_end > 0 && num_end < s.len() {
if let Ok(num) = s[..num_end].parse::<f64>() { 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(i) = s.parse::<i64>() { return Value::Int(i); }
if let Ok(f) = s.parse::<f64>() { return Value::Float(f); } 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 start = self.pos;
let mut parens = 0; 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]; let c = self.src[self.pos];
if c == b'(' { parens += 1; } if c == b'(' { parens += 1; }
else 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(); } if self.peek() == Some(b';') { self.advance(); }
let mut tokens = Vec::new(); let mut tokens = Vec::new();
let mut current = String::new(); let mut token_start = None;
let mut p = 0; let mut p = 0;
for c in line.chars() {
let bytes = line.as_bytes();
for (i, &c) in bytes.iter().enumerate() {
match c { match c {
'(' => { p += 1; current.push(c); } b'(' => {
')' => { p -= 1; current.push(c); } p += 1;
' ' | '\t' if p == 0 => { if token_start.is_none() { token_start = Some(i); }
if !current.is_empty() { }
tokens.push(current.clone()); b')' => {
current.clear(); 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 { if tokens.len() == 1 {
self.parse_single_value(&tokens[0]) self.parse_single_value(tokens[0])
} else { } 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'{'); self.consume_if(b'{');
loop { loop {
self.skip_whitespace(); self.skip_whitespace();
@@ -176,7 +197,7 @@ impl<'a> StyleParser<'a> {
if ident == "use" { if ident == "use" {
self.skip_whitespace(); self.skip_whitespace();
let mixin_name = self.parse_ident(); 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()); target.extend(props.clone());
} else { } else {
return Err(format!("Unknown mixin: {}", mixin_name)); return Err(format!("Unknown mixin: {}", mixin_name));
@@ -202,7 +223,7 @@ impl<'a> StyleParser<'a> {
match self.peek() { match self.peek() {
Some(b'$') => { Some(b'$') => {
self.advance(); self.advance();
let name = self.parse_ident(); let name = self.parse_ident().to_string();
self.skip_whitespace(); self.skip_whitespace();
self.consume_if(b'='); self.consume_if(b'=');
self.skip_whitespace(); self.skip_whitespace();
@@ -213,10 +234,10 @@ impl<'a> StyleParser<'a> {
Some(b'@') => { Some(b'@') => {
self.advance(); self.advance();
let name = self.parse_ident(); let name = self.parse_ident();
match name.as_str() { match name {
"mixin" => { "mixin" => {
self.skip_whitespace(); self.skip_whitespace();
let mixin_name = self.parse_ident(); let mixin_name = self.parse_ident().to_string();
self.skip_whitespace(); self.skip_whitespace();
let mut props = Vec::new(); let mut props = Vec::new();
self.parse_properties(&mut props)?; self.parse_properties(&mut props)?;
@@ -234,7 +255,7 @@ impl<'a> StyleParser<'a> {
self.skip_whitespace(); self.skip_whitespace();
if self.consume_if(b'}') { break; } if self.consume_if(b'}') { break; }
let step = self.parse_selector(); let step = self.parse_selector().to_string();
let mut props = Vec::new(); let mut props = Vec::new();
self.parse_properties(&mut props)?; self.parse_properties(&mut props)?;
@@ -267,8 +288,7 @@ impl<'a> StyleParser<'a> {
} }
let len = self.module.hierarchy.len() as u32 - start_idx; let len = self.module.hierarchy.len() as u32 - start_idx;
// Condition evaluates at runtime via Rhei let condition = Value::Rhei(cond_str);
let condition = Value::Rhei(cond_str.to_string());
let id = self.module.push_directive(Directive::If { let id = self.module.push_directive(Directive::If {
condition, condition,
child_span: (start_idx, len), child_span: (start_idx, len),