diff options
| author | Mike Buland <mike@xagasoft.com> | 2026-06-09 14:28:36 -0700 |
|---|---|---|
| committer | Mike Buland <mike@xagasoft.com> | 2026-06-09 14:28:36 -0700 |
| commit | 5d014484a4a34146bc13a578e81ed0a0080d4697 (patch) | |
| tree | e97a598b46260d1ee0849758b462fd29cd5abb32 | |
| parent | 61b54e8f6b177f33a1428ab6a3622503a63cc1d6 (diff) | |
| download | crimtag-5d014484a4a34146bc13a578e81ed0a0080d4697.tar.gz crimtag-5d014484a4a34146bc13a578e81ed0a0080d4697.tar.bz2 crimtag-5d014484a4a34146bc13a578e81ed0a0080d4697.tar.xz crimtag-5d014484a4a34146bc13a578e81ed0a0080d4697.zip | |
Loops seem to work!?
| -rw-r--r-- | src/lexer.rs | 2 | ||||
| -rw-r--r-- | src/lib.rs | 55 | ||||
| -rw-r--r-- | src/parser.rs | 11 |
3 files changed, 66 insertions, 2 deletions
diff --git a/src/lexer.rs b/src/lexer.rs index 2e43914..7228866 100644 --- a/src/lexer.rs +++ b/src/lexer.rs | |||
| @@ -19,6 +19,7 @@ pub enum SymbolType<'a> { | |||
| 19 | View, | 19 | View, |
| 20 | Output, | 20 | Output, |
| 21 | Show, | 21 | Show, |
| 22 | Loop, | ||
| 22 | Equals, | 23 | Equals, |
| 23 | Period, | 24 | Period, |
| 24 | Token(&'a str), | 25 | Token(&'a str), |
| @@ -244,6 +245,7 @@ impl<'a> Lexer<'a> { | |||
| 244 | "view" => SymbolType::View, | 245 | "view" => SymbolType::View, |
| 245 | "show" => SymbolType::Show, | 246 | "show" => SymbolType::Show, |
| 246 | "output" => SymbolType::Output, | 247 | "output" => SymbolType::Output, |
| 248 | "loop" => SymbolType::Loop, | ||
| 247 | _ => SymbolType::Token(s) | 249 | _ => SymbolType::Token(s) |
| 248 | }, start_pos, end_pos )) | 250 | }, start_pos, end_pos )) |
| 249 | } | 251 | } |
| @@ -59,13 +59,14 @@ type Properties = HashMap<String, String>; | |||
| 59 | 59 | ||
| 60 | #[derive(Debug)] | 60 | #[derive(Debug)] |
| 61 | enum Token { | 61 | enum Token { |
| 62 | Loop(Identifier, Vec<Token>), | ||
| 62 | Show(Identifier,Properties), | 63 | Show(Identifier,Properties), |
| 63 | Text(String), | 64 | Text(String), |
| 64 | } | 65 | } |
| 65 | 66 | ||
| 66 | #[derive(Debug,Clone)] | 67 | #[derive(Debug,Clone)] |
| 67 | pub enum Value { | 68 | pub enum Value { |
| 68 | Dictionary(HashMap<String,Value>), | 69 | Dictionary(Context), |
| 69 | List(Vec<Value>), | 70 | List(Vec<Value>), |
| 70 | String(String), | 71 | String(String), |
| 71 | Int(i64), | 72 | Int(i64), |
| @@ -232,6 +233,16 @@ impl Crimtag { | |||
| 232 | fn exec(input: &impl MappedStructure, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> { | 233 | fn exec(input: &impl MappedStructure, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> { |
| 233 | for token in tokens { | 234 | for token in tokens { |
| 234 | match token { | 235 | match token { |
| 236 | Token::Loop(ident,code) => { | ||
| 237 | if let Some(value) = input.get_value( &ident ) && | ||
| 238 | let Value::List(list) = value { | ||
| 239 | for rec in &list { | ||
| 240 | if let Value::Dictionary(d) = rec { | ||
| 241 | exec( d, code, buf )? | ||
| 242 | } | ||
| 243 | } | ||
| 244 | } | ||
| 245 | } | ||
| 235 | Token::Show(ident,p) => { | 246 | Token::Show(ident,p) => { |
| 236 | let format = if let Some(s) = p.get("format") { | 247 | let format = if let Some(s) = p.get("format") { |
| 237 | s | 248 | s |
| @@ -333,4 +344,46 @@ Now with explicit outputs: | |||
| 333 | } | 344 | } |
| 334 | } | 345 | } |
| 335 | } | 346 | } |
| 347 | |||
| 348 | #[test] | ||
| 349 | fn loops() { | ||
| 350 | let mut ct = Crimtag::new(); | ||
| 351 | if let Err(e) = ct.load_static(r#"Looping code: | ||
| 352 | [|view "index"|>We will enumerate people here:[|loop people|> | ||
| 353 | - [|show last_name|], [|show first_name|]: [|show title|]<|loop|] | ||
| 354 | <|view|] | ||
| 355 | "#) { | ||
| 356 | println!("Error: {:?}", e ); | ||
| 357 | return; | ||
| 358 | } | ||
| 359 | |||
| 360 | let ctx = Context::from([ | ||
| 361 | ("people".to_string(), crate::Value::List(vec![ | ||
| 362 | crate::Value::Dictionary(Context::from([ | ||
| 363 | ("first_name".to_string(), Value::String("Joe".to_string())), | ||
| 364 | ("last_name".to_string(), Value::String("Smith".to_string())), | ||
| 365 | ("title".to_string(), Value::String("CEO".to_string())), | ||
| 366 | ])), | ||
| 367 | crate::Value::Dictionary(Context::from([ | ||
| 368 | ("first_name".to_string(), Value::String("Chris".to_string())), | ||
| 369 | ("last_name".to_string(), Value::String("Perkens".to_string())), | ||
| 370 | ("title".to_string(), Value::String("Baconeer".to_string())), | ||
| 371 | ])), | ||
| 372 | crate::Value::Dictionary(Context::from([ | ||
| 373 | ("first_name".to_string(), Value::String("Will".to_string())), | ||
| 374 | ("last_name".to_string(), Value::String("Power".to_string())), | ||
| 375 | ("title".to_string(), Value::String("CFO".to_string())), | ||
| 376 | ])), | ||
| 377 | crate::Value::Dictionary(Context::from([ | ||
| 378 | ("first_name".to_string(), Value::String("Justin".to_string())), | ||
| 379 | ("last_name".to_string(), Value::String("Time".to_string())), | ||
| 380 | ("title".to_string(), Value::String("CIO".to_string())), | ||
| 381 | ])), | ||
| 382 | ])), | ||
| 383 | ]); | ||
| 384 | match ct.render("index", &ctx) { | ||
| 385 | Ok(s) => println!("View result: {}", s ), | ||
| 386 | Err(e) => println!("Error: {:?}", e ), | ||
| 387 | } | ||
| 388 | } | ||
| 336 | } | 389 | } |
diff --git a/src/parser.rs b/src/parser.rs index 5f53557..6e479e4 100644 --- a/src/parser.rs +++ b/src/parser.rs | |||
| @@ -103,6 +103,7 @@ impl<'a> SymbolHelper for Option<Symbol<'a>> { | |||
| 103 | SymbolType::Token(_) | | 103 | SymbolType::Token(_) | |
| 104 | SymbolType::View | | 104 | SymbolType::View | |
| 105 | SymbolType::Output | | 105 | SymbolType::Output | |
| 106 | SymbolType::Loop | | ||
| 106 | SymbolType::Show => true, | 107 | SymbolType::Show => true, |
| 107 | _ => false | 108 | _ => false |
| 108 | }) | 109 | }) |
| @@ -327,7 +328,7 @@ impl Parser { | |||
| 327 | 328 | ||
| 328 | if ctx.next().is_some() { | 329 | if ctx.next().is_some() { |
| 329 | match ctx.cur().unwrap().symbol() { | 330 | match ctx.cur().unwrap().symbol() { |
| 330 | SymbolType::View | SymbolType::Show | | 331 | SymbolType::View | SymbolType::Show | SymbolType::Loop | |
| 331 | SymbolType::Output => { | 332 | SymbolType::Output => { |
| 332 | let name_sym = ctx.cur().unwrap(); | 333 | let name_sym = ctx.cur().unwrap(); |
| 333 | ctx.next(); | 334 | ctx.next(); |
| @@ -640,6 +641,14 @@ impl<'a> TagBuilder<'a> { | |||
| 640 | pub fn build(mut self) -> ParseResult<Token> { | 641 | pub fn build(mut self) -> ParseResult<Token> { |
| 641 | if let Some(sym) = self.name { | 642 | if let Some(sym) = self.name { |
| 642 | match sym.symbol() { | 643 | match sym.symbol() { |
| 644 | SymbolType::Loop => { | ||
| 645 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { | ||
| 646 | id | ||
| 647 | } else { | ||
| 648 | return Err(ParseError::new(Position::none(), "Identifier for loop variable name.")); | ||
| 649 | }; | ||
| 650 | Ok(Token::Loop(id, self.children)) | ||
| 651 | } | ||
| 643 | SymbolType::Show => { | 652 | SymbolType::Show => { |
| 644 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { | 653 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { |
| 645 | id | 654 | id |
