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)]
|
#[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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()?;
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user