diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lexer.rs | 27 | ||||
| -rw-r--r-- | src/lib.rs | 132 | ||||
| -rw-r--r-- | 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> { | |||
| 20 | Output, | 20 | Output, |
| 21 | Show, | 21 | Show, |
| 22 | Equals, | 22 | Equals, |
| 23 | Period, | ||
| 23 | Token(&'a str), | 24 | Token(&'a str), |
| 24 | Literal(&'a str), | 25 | Literal(&'a str), |
| 25 | Text(&'a str), | 26 | Text(&'a str), |
| @@ -78,13 +79,20 @@ pub struct Lexer<'a> { | |||
| 78 | pos: Position, | 79 | pos: Position, |
| 79 | } | 80 | } |
| 80 | 81 | ||
| 81 | fn is_valid_token_char( c: char ) -> bool { | 82 | fn is_valid_token_char( c: char, first: bool ) -> bool { |
| 82 | if c.is_whitespace() { | 83 | if c.is_whitespace() { |
| 83 | return false; | 84 | return false; |
| 84 | } | 85 | } |
| 85 | match c { | 86 | if first { |
| 86 | 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' => true, | 87 | match c { |
| 87 | _ => false | 88 | 'a'..'z' | 'A'..'Z' | '_' => true, |
| 89 | _ => false | ||
| 90 | } | ||
| 91 | } else { | ||
| 92 | match c { | ||
| 93 | 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' => true, | ||
| 94 | _ => false | ||
| 95 | } | ||
| 88 | } | 96 | } |
| 89 | } | 97 | } |
| 90 | 98 | ||
| @@ -214,13 +222,18 @@ impl<'a> Lexer<'a> { | |||
| 214 | self.next(); | 222 | self.next(); |
| 215 | return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); | 223 | return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); |
| 216 | } | 224 | } |
| 225 | Some('.') => { | ||
| 226 | self.next(); | ||
| 227 | return Some(Symbol::new(SymbolType::Period, self.pos, self.pos)); | ||
| 228 | } | ||
| 217 | _ => {} | 229 | _ => {} |
| 218 | } | 230 | } |
| 219 | let start = self.cur_index(); | 231 | let start = self.cur_index(); |
| 220 | let start_pos = self.pos.clone(); | 232 | let start_pos = self.pos.clone(); |
| 221 | 233 | ||
| 222 | while self.next().is_some_and(|ch| is_valid_token_char(ch) ) && | 234 | let mut first = true; |
| 223 | !self.is_end_tag() {} | 235 | while self.next().is_some_and(|ch| is_valid_token_char(ch, first) ) && |
| 236 | !self.is_end_tag() { first = false; } | ||
| 224 | let end = self.cur_index(); | 237 | let end = self.cur_index(); |
| 225 | let end_pos = self.pos.clone(); | 238 | let end_pos = self.pos.clone(); |
| 226 | let s = &self.data[start..end]; | 239 | let s = &self.data[start..end]; |
| @@ -38,14 +38,14 @@ struct View { | |||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | impl View { | 40 | impl View { |
| 41 | fn process(&self, input: &Value) -> Result<Value, ParseError> { | 41 | fn process(&self, input: &impl MappedStructure) -> Result<Context, ParseError> { |
| 42 | let mut out_vars = HashMap::new(); | 42 | let mut out_vars = Context::new(); |
| 43 | for output in &self.outputs { | 43 | for output in &self.outputs { |
| 44 | let mut buf = String::new(); | 44 | let mut buf = String::new(); |
| 45 | exec( input, &output.code, &mut buf )?; | 45 | exec( input, &output.code, &mut buf )?; |
| 46 | out_vars.insert( output.name.clone(), Value::String(buf) ); | 46 | out_vars.insert( output.name.clone(), Value::String(buf) ); |
| 47 | } | 47 | } |
| 48 | Ok(Value::Dictionary(out_vars)) | 48 | Ok(out_vars) |
| 49 | } | 49 | } |
| 50 | } | 50 | } |
| 51 | 51 | ||
| @@ -59,17 +59,60 @@ type Properties = HashMap<String, String>; | |||
| 59 | 59 | ||
| 60 | #[derive(Debug)] | 60 | #[derive(Debug)] |
| 61 | enum Token { | 61 | enum Token { |
| 62 | Show(String,Properties), | 62 | Show(Identifier,Properties), |
| 63 | Text(String), | 63 | Text(String), |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | #[derive(Debug)] | 66 | #[derive(Debug,Clone)] |
| 67 | pub enum Value { | 67 | pub enum Value { |
| 68 | Dictionary(HashMap<String,Value>), | 68 | Dictionary(HashMap<String,Value>), |
| 69 | List(Vec<Value>), | 69 | List(Vec<Value>), |
| 70 | String(String), | 70 | String(String), |
| 71 | Int(i64), | 71 | Int(i64), |
| 72 | Float(f64), | 72 | Float(f64), |
| 73 | Identifier(Identifier), | ||
| 74 | } | ||
| 75 | |||
| 76 | #[derive(Debug,Clone)] | ||
| 77 | pub enum IdentifierValue { | ||
| 78 | Name(String), | ||
| 79 | Index(usize), | ||
| 80 | } | ||
| 81 | |||
| 82 | pub type Identifier = Vec<IdentifierValue>; | ||
| 83 | pub type Context = HashMap<String,Value>; | ||
| 84 | |||
| 85 | pub trait MappedStructure { | ||
| 86 | fn get_value(&self, id: &Identifier) -> Option<Value>; | ||
| 87 | } | ||
| 88 | |||
| 89 | impl MappedStructure for Context { | ||
| 90 | fn get_value(&self, id: &Identifier) -> Option<Value> { | ||
| 91 | if id.len() == 0 { | ||
| 92 | return None; | ||
| 93 | } | ||
| 94 | let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] { | ||
| 95 | self.get(s) | ||
| 96 | } else { | ||
| 97 | return None; | ||
| 98 | }; | ||
| 99 | if cur_val.is_none() { | ||
| 100 | return None; | ||
| 101 | } | ||
| 102 | |||
| 103 | for idx in 1..id.len() { | ||
| 104 | if let IdentifierValue::Name(s) = &id[idx] && | ||
| 105 | let Some(Value::Dictionary(d)) = &cur_val { | ||
| 106 | cur_val.replace( if let Some(v) = d.get(s) { | ||
| 107 | v | ||
| 108 | } else { | ||
| 109 | return None; | ||
| 110 | }); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | return cur_val.cloned(); | ||
| 115 | } | ||
| 73 | } | 116 | } |
| 74 | 117 | ||
| 75 | impl Crimtag { | 118 | impl Crimtag { |
| @@ -157,7 +200,7 @@ impl Crimtag { | |||
| 157 | } | 200 | } |
| 158 | } | 201 | } |
| 159 | 202 | ||
| 160 | pub fn render_partial(&self, view: &str, input: &Value ) -> Result<Value,ParseError> { | 203 | pub fn render_partial(&self, view: &str, input: &impl MappedStructure ) -> Result<Context,ParseError> { |
| 161 | if let Some(view) = self.views.get(view) { | 204 | if let Some(view) = self.views.get(view) { |
| 162 | return view.process( input ) | 205 | return view.process( input ) |
| 163 | } else { | 206 | } else { |
| @@ -165,14 +208,13 @@ impl Crimtag { | |||
| 165 | } | 208 | } |
| 166 | } | 209 | } |
| 167 | 210 | ||
| 168 | pub fn render(&self, view: &str, input: &Value ) -> Result<String,ParseError> { | 211 | pub fn render(&self, view: &str, input: &impl MappedStructure ) -> Result<String,ParseError> { |
| 169 | if let Some(view) = self.views.get( view ) { | 212 | if let Some(view) = self.views.get( view ) { |
| 170 | let output = view.process( input )?; | 213 | let mut output = view.process( input )?; |
| 171 | if let Some(l) = &view.layout { | 214 | if let Some(l) = &view.layout { |
| 172 | self.render( l, &output ) | 215 | self.render( l, &output ) |
| 173 | } else { | 216 | } else { |
| 174 | if let Value::Dictionary(mut d) = output && | 217 | if let Some(content) = output.remove("content") && |
| 175 | let Some(content) = d.remove("content") && | ||
| 176 | let Value::String(s) = content { | 218 | let Value::String(s) = content { |
| 177 | 219 | ||
| 178 | Ok(s) | 220 | Ok(s) |
| @@ -187,38 +229,40 @@ impl Crimtag { | |||
| 187 | } | 229 | } |
| 188 | } | 230 | } |
| 189 | 231 | ||
| 190 | fn exec(input: &Value, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> { | 232 | fn exec(input: &impl MappedStructure, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> { |
| 191 | for token in tokens { | 233 | for token in tokens { |
| 192 | match token { | 234 | match token { |
| 193 | Token::Show(s,p) => { | 235 | Token::Show(ident,p) => { |
| 194 | let format = if let Some(s) = p.get("format") { | 236 | let format = if let Some(s) = p.get("format") { |
| 195 | s | 237 | s |
| 196 | } else { | 238 | } else { |
| 197 | &"".to_string() | 239 | &"".to_string() |
| 198 | }; | 240 | }; |
| 199 | if let Value::Dictionary(d) = input && | 241 | if let Some(value) = input.get_value( &ident ) { |
| 200 | let Some(value) = d.get(s) { | 242 | match value { |
| 201 | match value { | 243 | Value::Dictionary(_) => { |
| 202 | Value::Dictionary(_) => { | 244 | buf.push_str("<dictionary>"); |
| 203 | buf.push_str("<dictionary>"); | 245 | } |
| 204 | } | 246 | Value::List(_) => { |
| 205 | Value::List(_) => { | 247 | buf.push_str("<list>"); |
| 206 | buf.push_str("<list>"); | 248 | } |
| 207 | } | 249 | Value::String(s) => { |
| 208 | Value::String(s) => { | 250 | buf.push_str(s.as_str()); |
| 209 | buf.push_str(s); | 251 | } |
| 210 | } | 252 | Value::Int(i) => { |
| 211 | Value::Int(i) => { | 253 | buf.push_str(&i.to_string()); |
| 212 | buf.push_str(&i.to_string()); | 254 | } |
| 213 | } | 255 | Value::Float(f) => { |
| 214 | Value::Float(f) => { | 256 | if format == "" { |
| 215 | if format == "" { | 257 | buf.push_str(&f.to_string()); |
| 216 | buf.push_str(&f.to_string()); | 258 | } else { |
| 217 | } else { | 259 | buf.push_str(&f.to_string()); |
| 218 | buf.push_str(&f.to_string()); | ||
| 219 | } | ||
| 220 | } | 260 | } |
| 221 | } | 261 | } |
| 262 | Value::Identifier(_) => { | ||
| 263 | // ??? | ||
| 264 | } | ||
| 265 | } | ||
| 222 | } | 266 | } |
| 223 | } | 267 | } |
| 224 | Token::Text(s) => { | 268 | Token::Text(s) => { |
| @@ -254,14 +298,14 @@ mod tests { | |||
| 254 | let mut ct = Crimtag::new(); | 298 | let mut ct = Crimtag::new(); |
| 255 | if let Err(e) = ct.load_static(r#"Here is a sample | 299 | if let Err(e) = ct.load_static(r#"Here is a sample |
| 256 | [|view "index" theme="standard"|><html><body>Hi</body></html><|view|] | 300 | [|view "index" theme="standard"|><html><body>Hi</body></html><|view|] |
| 257 | That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu | 301 | That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu |
| 258 | 302 | ||
| 259 | [|view "index" layout="simple"|>Whatever man!<|view|] | 303 | [|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|] |
| 260 | 304 | ||
| 261 | Now with explicit outputs: | 305 | Now with explicit outputs: |
| 262 | [|view "complex"|> | 306 | [|view "complex"|> |
| 263 | [|output "content"|> | 307 | [|output "content"|> |
| 264 | Here's a [|show "name"|]. | 308 | Here's a [|show name|]. |
| 265 | <|output|] | 309 | <|output|] |
| 266 | [|output "sidebar"|> | 310 | [|output "sidebar"|> |
| 267 | What's up world? | 311 | What's up world? |
| @@ -274,13 +318,15 @@ Now with explicit outputs: | |||
| 274 | } else { | 318 | } else { |
| 275 | println!("It finished"); | 319 | println!("It finished"); |
| 276 | if let Ok(v) = ct.render( | 320 | if let Ok(v) = ct.render( |
| 277 | "index", | 321 | "person", |
| 278 | &Value::Dictionary( | 322 | &Context::from([ |
| 279 | HashMap::from([ | 323 | ("hi".to_string(),Value::String("hi".to_string())), |
| 280 | ("hi".to_string(),Value::String("hi".to_string())) | 324 | ("person".to_string(),Value::Dictionary(HashMap::from([ |
| 281 | ]) | 325 | ("first_name".to_string(), Value::String("Bob".to_string())), |
| 282 | ) | 326 | ("last_name".to_string(), Value::String("Smith".to_string())), |
| 283 | ) { | 327 | ]))), |
| 328 | ]) | ||
| 329 | ) { | ||
| 284 | println!("View result: {:?}", v ); | 330 | println!("View result: {:?}", v ); |
| 285 | } else { | 331 | } else { |
| 286 | println!("Error?"); | 332 | 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 { | |||
| 274 | SymbolType::StartFlat => { | 274 | SymbolType::StartFlat => { |
| 275 | let subtb = self.parse_tag_body( ctx )?; | 275 | let subtb = self.parse_tag_body( ctx )?; |
| 276 | if subtb.is_name(&SymbolType::Output) { | 276 | if subtb.is_name(&SymbolType::Output) { |
| 277 | outputs.push(subtb.build_output(ctx)?); | 277 | outputs.push(subtb.build_output()?); |
| 278 | } else { | 278 | } else { |
| 279 | children.push(subtb.build()?); | 279 | children.push(subtb.build()?); |
| 280 | } | 280 | } |
| @@ -437,16 +437,43 @@ impl Parser { | |||
| 437 | } | 437 | } |
| 438 | match ctx.cur().unwrap().symbol() { | 438 | match ctx.cur().unwrap().symbol() { |
| 439 | SymbolType::Literal(s) => { | 439 | SymbolType::Literal(s) => { |
| 440 | tb.add_param( s.to_string() ); | 440 | tb.add_param( ParamValue::Literal(s.to_string()) ); |
| 441 | ctx.next(); | ||
| 442 | } | ||
| 443 | SymbolType::Token(_) => { | ||
| 444 | if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { | ||
| 445 | break; | ||
| 446 | } | ||
| 447 | tb.add_param( self.parse_identifier( ctx )? ); | ||
| 441 | } | 448 | } |
| 442 | _ => { | 449 | _ => { |
| 443 | break; | 450 | break; |
| 444 | } | 451 | } |
| 445 | } | 452 | } |
| 446 | ctx.next(); | ||
| 447 | } | 453 | } |
| 448 | Ok(()) | 454 | Ok(()) |
| 449 | } | 455 | } |
| 456 | |||
| 457 | fn parse_identifier(&self, ctx: &mut Context ) -> ParseResult<ParamValue> { | ||
| 458 | let mut id = Identifier::new(); | ||
| 459 | |||
| 460 | loop { | ||
| 461 | if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { | ||
| 462 | if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { | ||
| 463 | id.push( IdentifierValue::Name(s.to_string()) ); | ||
| 464 | } | ||
| 465 | } else { | ||
| 466 | return Err(ParseError::new( ctx.cur().unwrap().start().clone(), &format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); | ||
| 467 | } | ||
| 468 | |||
| 469 | if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { | ||
| 470 | break; | ||
| 471 | } | ||
| 472 | ctx.next(); | ||
| 473 | } | ||
| 474 | |||
| 475 | Ok(ParamValue::Identifier(id)) | ||
| 476 | } | ||
| 450 | 477 | ||
| 451 | fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> ParseResult<()> { | 478 | fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> ParseResult<()> { |
| 452 | loop { | 479 | loop { |
| @@ -483,13 +510,18 @@ enum TagType { | |||
| 483 | 510 | ||
| 484 | struct TagBuilder<'a> { | 511 | struct TagBuilder<'a> { |
| 485 | name: Option<Symbol<'a>>, | 512 | name: Option<Symbol<'a>>, |
| 486 | params: Vec<String>, | 513 | params: Vec<ParamValue>, |
| 487 | props: Properties, | 514 | props: Properties, |
| 488 | children: Vec<Token>, | 515 | children: Vec<Token>, |
| 489 | tag_type: TagType, | 516 | tag_type: TagType, |
| 490 | start_pos: Position, | 517 | start_pos: Position, |
| 491 | } | 518 | } |
| 492 | 519 | ||
| 520 | enum ParamValue { | ||
| 521 | Literal(String), | ||
| 522 | Identifier(Identifier), | ||
| 523 | } | ||
| 524 | |||
| 493 | impl<'a> TagBuilder<'a> { | 525 | impl<'a> TagBuilder<'a> { |
| 494 | pub fn new(start: &Symbol) -> TagBuilder<'a> { | 526 | pub fn new(start: &Symbol) -> TagBuilder<'a> { |
| 495 | TagBuilder { | 527 | TagBuilder { |
| @@ -538,7 +570,7 @@ impl<'a> TagBuilder<'a> { | |||
| 538 | self.name = Some(name); | 570 | self.name = Some(name); |
| 539 | } | 571 | } |
| 540 | 572 | ||
| 541 | pub fn add_param(&mut self, param: String) { | 573 | pub fn add_param(&mut self, param: ParamValue) { |
| 542 | self.params.push( param ); | 574 | self.params.push( param ); |
| 543 | } | 575 | } |
| 544 | 576 | ||
| @@ -565,8 +597,13 @@ impl<'a> TagBuilder<'a> { | |||
| 565 | pub fn build_view(mut self, ctx: &Context, outputs: Vec<Output>) -> ParseResult<View> { | 597 | pub fn build_view(mut self, ctx: &Context, outputs: Vec<Output>) -> ParseResult<View> { |
| 566 | if let Some(sym) = self.name { | 598 | if let Some(sym) = self.name { |
| 567 | if *sym.symbol() == SymbolType::View { | 599 | if *sym.symbol() == SymbolType::View { |
| 600 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 601 | s.to_string() | ||
| 602 | } else { | ||
| 603 | return Err(ParseError::new(Position::none(), "Expected string literal for view name.")); | ||
| 604 | }; | ||
| 568 | Ok(View { | 605 | Ok(View { |
| 569 | name: self.params.swap_remove(0), | 606 | name: name, |
| 570 | source: ctx.source(), | 607 | source: ctx.source(), |
| 571 | outputs: outputs, | 608 | outputs: outputs, |
| 572 | //theme: Option<String> | 609 | //theme: Option<String> |
| @@ -580,11 +617,16 @@ impl<'a> TagBuilder<'a> { | |||
| 580 | } | 617 | } |
| 581 | } | 618 | } |
| 582 | 619 | ||
| 583 | pub fn build_output(mut self, ctx: &Context) -> ParseResult<Output> { | 620 | pub fn build_output(mut self) -> ParseResult<Output> { |
| 584 | if let Some(sym) = self.name { | 621 | if let Some(sym) = self.name { |
| 585 | if *sym.symbol() == SymbolType::Output { | 622 | if *sym.symbol() == SymbolType::Output { |
| 623 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 624 | s.to_string() | ||
| 625 | } else { | ||
| 626 | return Err(ParseError::new(Position::none(), "Expected string literal for output name.")); | ||
| 627 | }; | ||
| 586 | Ok(Output { | 628 | Ok(Output { |
| 587 | name: self.params.swap_remove(0), | 629 | name: name, |
| 588 | code: self.children, | 630 | code: self.children, |
| 589 | }) | 631 | }) |
| 590 | } else { | 632 | } else { |
| @@ -599,7 +641,12 @@ impl<'a> TagBuilder<'a> { | |||
| 599 | if let Some(sym) = self.name { | 641 | if let Some(sym) = self.name { |
| 600 | match sym.symbol() { | 642 | match sym.symbol() { |
| 601 | SymbolType::Show => { | 643 | SymbolType::Show => { |
| 602 | Ok(Token::Show(self.params.swap_remove(0), self.props)) | 644 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { |
| 645 | id | ||
| 646 | } else { | ||
| 647 | return Err(ParseError::new(Position::none(), "Identifier for show variable name.")); | ||
| 648 | }; | ||
| 649 | Ok(Token::Show(id, self.props)) | ||
| 603 | } | 650 | } |
| 604 | _ => { | 651 | _ => { |
| 605 | Err(ParseError { | 652 | Err(ParseError { |
