fix: runtime support
This commit is contained in:
75
src/ast.rs
75
src/ast.rs
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
@@ -41,14 +41,14 @@ impl<'a> Compiler<'a> {
|
||||
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);
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
|
||||
@@ -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 }
|
||||
@@ -404,7 +409,7 @@ impl<'a> Parser<'a> {
|
||||
let ident = self.parse_ident();
|
||||
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()?);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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