From 61b54e8f6b177f33a1428ab6a3622503a63cc1d6 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Tue, 9 Jun 2026 14:02:53 -0700 Subject: Identifiers work! --- src/lexer.rs | 27 ++++++++---- src/lib.rs | 132 +++++++++++++++++++++++++++++++++++++++------------------- src/parser.rs | 65 +++++++++++++++++++++++++---- 3 files changed, 165 insertions(+), 59 deletions(-) diff --git a/src/lexer.rs b/src/lexer.rs index 0724df2..2e43914 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -20,6 +20,7 @@ pub enum SymbolType<'a> { Output, Show, Equals, + Period, Token(&'a str), Literal(&'a str), Text(&'a str), @@ -78,13 +79,20 @@ pub struct Lexer<'a> { pos: Position, } -fn is_valid_token_char( c: char ) -> bool { +fn is_valid_token_char( c: char, first: bool ) -> bool { if c.is_whitespace() { return false; } - match c { - 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' => true, - _ => false + if first { + match c { + 'a'..'z' | 'A'..'Z' | '_' => true, + _ => false + } + } else { + match c { + 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' => true, + _ => false + } } } @@ -214,13 +222,18 @@ impl<'a> Lexer<'a> { self.next(); return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); } + Some('.') => { + self.next(); + return Some(Symbol::new(SymbolType::Period, self.pos, self.pos)); + } _ => {} } let start = self.cur_index(); let start_pos = self.pos.clone(); - - while self.next().is_some_and(|ch| is_valid_token_char(ch) ) && - !self.is_end_tag() {} + + let mut first = true; + while self.next().is_some_and(|ch| is_valid_token_char(ch, first) ) && + !self.is_end_tag() { first = false; } let end = self.cur_index(); let end_pos = self.pos.clone(); let s = &self.data[start..end]; diff --git a/src/lib.rs b/src/lib.rs index 7790b9a..a2bfc37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,14 +38,14 @@ struct View { } impl View { - fn process(&self, input: &Value) -> Result { - let mut out_vars = HashMap::new(); + fn process(&self, input: &impl MappedStructure) -> Result { + let mut out_vars = Context::new(); for output in &self.outputs { let mut buf = String::new(); exec( input, &output.code, &mut buf )?; out_vars.insert( output.name.clone(), Value::String(buf) ); } - Ok(Value::Dictionary(out_vars)) + Ok(out_vars) } } @@ -59,17 +59,60 @@ type Properties = HashMap; #[derive(Debug)] enum Token { - Show(String,Properties), + Show(Identifier,Properties), Text(String), } -#[derive(Debug)] +#[derive(Debug,Clone)] pub enum Value { Dictionary(HashMap), List(Vec), String(String), Int(i64), Float(f64), + Identifier(Identifier), +} + +#[derive(Debug,Clone)] +pub enum IdentifierValue { + Name(String), + Index(usize), +} + +pub type Identifier = Vec; +pub type Context = HashMap; + +pub trait MappedStructure { + fn get_value(&self, id: &Identifier) -> Option; +} + +impl MappedStructure for Context { + fn get_value(&self, id: &Identifier) -> Option { + if id.len() == 0 { + return None; + } + let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] { + self.get(s) + } else { + return None; + }; + if cur_val.is_none() { + return None; + } + + for idx in 1..id.len() { + if let IdentifierValue::Name(s) = &id[idx] && + let Some(Value::Dictionary(d)) = &cur_val { + cur_val.replace( if let Some(v) = d.get(s) { + v + } else { + return None; + }); + } + } + + return cur_val.cloned(); + } } impl Crimtag { @@ -157,7 +200,7 @@ impl Crimtag { } } - pub fn render_partial(&self, view: &str, input: &Value ) -> Result { + pub fn render_partial(&self, view: &str, input: &impl MappedStructure ) -> Result { if let Some(view) = self.views.get(view) { return view.process( input ) } else { @@ -165,14 +208,13 @@ impl Crimtag { } } - pub fn render(&self, view: &str, input: &Value ) -> Result { + pub fn render(&self, view: &str, input: &impl MappedStructure ) -> Result { if let Some(view) = self.views.get( view ) { - let output = view.process( input )?; + let mut output = view.process( input )?; if let Some(l) = &view.layout { self.render( l, &output ) } else { - if let Value::Dictionary(mut d) = output && - let Some(content) = d.remove("content") && + if let Some(content) = output.remove("content") && let Value::String(s) = content { Ok(s) @@ -187,38 +229,40 @@ impl Crimtag { } } -fn exec(input: &Value, tokens: &Vec, buf: &mut String) -> Result<(),ParseError> { +fn exec(input: &impl MappedStructure, tokens: &Vec, buf: &mut String) -> Result<(),ParseError> { for token in tokens { match token { - Token::Show(s,p) => { + Token::Show(ident,p) => { let format = if let Some(s) = p.get("format") { s } else { &"".to_string() }; - if let Value::Dictionary(d) = input && - let Some(value) = d.get(s) { - match value { - Value::Dictionary(_) => { - buf.push_str(""); - } - Value::List(_) => { - buf.push_str(""); - } - Value::String(s) => { - buf.push_str(s); - } - Value::Int(i) => { - buf.push_str(&i.to_string()); - } - Value::Float(f) => { - if format == "" { - buf.push_str(&f.to_string()); - } else { - buf.push_str(&f.to_string()); - } + if let Some(value) = input.get_value( &ident ) { + match value { + Value::Dictionary(_) => { + buf.push_str(""); + } + Value::List(_) => { + buf.push_str(""); + } + Value::String(s) => { + buf.push_str(s.as_str()); + } + Value::Int(i) => { + buf.push_str(&i.to_string()); + } + Value::Float(f) => { + if format == "" { + buf.push_str(&f.to_string()); + } else { + buf.push_str(&f.to_string()); } } + Value::Identifier(_) => { + // ??? + } + } } } Token::Text(s) => { @@ -254,14 +298,14 @@ mod tests { let mut ct = Crimtag::new(); if let Err(e) = ct.load_static(r#"Here is a sample [|view "index" theme="standard"|>Hi<|view|] -That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu +That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu -[|view "index" layout="simple"|>Whatever man!<|view|] +[|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|] Now with explicit outputs: [|view "complex"|> [|output "content"|> - Here's a [|show "name"|]. + Here's a [|show name|]. <|output|] [|output "sidebar"|> What's up world? @@ -274,13 +318,15 @@ Now with explicit outputs: } else { println!("It finished"); if let Ok(v) = ct.render( - "index", - &Value::Dictionary( - HashMap::from([ - ("hi".to_string(),Value::String("hi".to_string())) - ]) - ) - ) { + "person", + &Context::from([ + ("hi".to_string(),Value::String("hi".to_string())), + ("person".to_string(),Value::Dictionary(HashMap::from([ + ("first_name".to_string(), Value::String("Bob".to_string())), + ("last_name".to_string(), Value::String("Smith".to_string())), + ]))), + ]) + ) { println!("View result: {:?}", v ); } else { println!("Error?"); diff --git a/src/parser.rs b/src/parser.rs index 297230c..5f53557 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -274,7 +274,7 @@ impl Parser { SymbolType::StartFlat => { let subtb = self.parse_tag_body( ctx )?; if subtb.is_name(&SymbolType::Output) { - outputs.push(subtb.build_output(ctx)?); + outputs.push(subtb.build_output()?); } else { children.push(subtb.build()?); } @@ -437,16 +437,43 @@ impl Parser { } match ctx.cur().unwrap().symbol() { SymbolType::Literal(s) => { - tb.add_param( s.to_string() ); + tb.add_param( ParamValue::Literal(s.to_string()) ); + ctx.next(); + } + SymbolType::Token(_) => { + if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { + break; + } + tb.add_param( self.parse_identifier( ctx )? ); } _ => { break; } } - ctx.next(); } Ok(()) } + + fn parse_identifier(&self, ctx: &mut Context ) -> ParseResult { + let mut id = Identifier::new(); + + loop { + if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { + if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { + id.push( IdentifierValue::Name(s.to_string()) ); + } + } else { + return Err(ParseError::new( ctx.cur().unwrap().start().clone(), &format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); + } + + if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { + break; + } + ctx.next(); + } + + Ok(ParamValue::Identifier(id)) + } fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> ParseResult<()> { loop { @@ -483,13 +510,18 @@ enum TagType { struct TagBuilder<'a> { name: Option>, - params: Vec, + params: Vec, props: Properties, children: Vec, tag_type: TagType, start_pos: Position, } +enum ParamValue { + Literal(String), + Identifier(Identifier), +} + impl<'a> TagBuilder<'a> { pub fn new(start: &Symbol) -> TagBuilder<'a> { TagBuilder { @@ -538,7 +570,7 @@ impl<'a> TagBuilder<'a> { self.name = Some(name); } - pub fn add_param(&mut self, param: String) { + pub fn add_param(&mut self, param: ParamValue) { self.params.push( param ); } @@ -565,8 +597,13 @@ impl<'a> TagBuilder<'a> { pub fn build_view(mut self, ctx: &Context, outputs: Vec) -> ParseResult { if let Some(sym) = self.name { if *sym.symbol() == SymbolType::View { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(ParseError::new(Position::none(), "Expected string literal for view name.")); + }; Ok(View { - name: self.params.swap_remove(0), + name: name, source: ctx.source(), outputs: outputs, //theme: Option @@ -580,11 +617,16 @@ impl<'a> TagBuilder<'a> { } } - pub fn build_output(mut self, ctx: &Context) -> ParseResult { + pub fn build_output(mut self) -> ParseResult { if let Some(sym) = self.name { if *sym.symbol() == SymbolType::Output { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(ParseError::new(Position::none(), "Expected string literal for output name.")); + }; Ok(Output { - name: self.params.swap_remove(0), + name: name, code: self.children, }) } else { @@ -599,7 +641,12 @@ impl<'a> TagBuilder<'a> { if let Some(sym) = self.name { match sym.symbol() { SymbolType::Show => { - Ok(Token::Show(self.params.swap_remove(0), self.props)) + let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { + id + } else { + return Err(ParseError::new(Position::none(), "Identifier for show variable name.")); + }; + Ok(Token::Show(id, self.props)) } _ => { Err(ParseError { -- cgit v1.2.3