diff options
| author | Mike Buland <mike@xagasoft.com> | 2026-06-12 14:33:34 -0700 |
|---|---|---|
| committer | Mike Buland <mike@xagasoft.com> | 2026-06-12 14:33:34 -0700 |
| commit | 3c9a65f0a576397024c76bcc33950796ec3297ec (patch) | |
| tree | 3b6e7d10f7a5507dbe7736e443ad85bcdf78a1fe | |
| parent | 9083d5843f5a39fafacde81242b817021f9cb818 (diff) | |
| download | crimtag-3c9a65f0a576397024c76bcc33950796ec3297ec.tar.gz crimtag-3c9a65f0a576397024c76bcc33950796ec3297ec.tar.bz2 crimtag-3c9a65f0a576397024c76bcc33950796ec3297ec.tar.xz crimtag-3c9a65f0a576397024c76bcc33950796ec3297ec.zip | |
if/elif/else and loop work fully.
Complex identifiers also work now.
| -rw-r--r-- | src/lib.rs | 43 | ||||
| -rw-r--r-- | src/parser.rs | 432 |
2 files changed, 217 insertions, 258 deletions
| @@ -171,6 +171,7 @@ impl Crimtag { | |||
| 171 | 171 | ||
| 172 | pub fn render(&self, view: &str, input: &impl MappedStructure ) -> CrimResult<String> { | 172 | pub fn render(&self, view: &str, input: &impl MappedStructure ) -> CrimResult<String> { |
| 173 | if let Some(view) = self.views.get( view ) { | 173 | if let Some(view) = self.views.get( view ) { |
| 174 | //println!("::Token tree::\n{:?}", view ); | ||
| 174 | let mut output = view.process( input )?; | 175 | let mut output = view.process( input )?; |
| 175 | if let Some(l) = &view.layout { | 176 | if let Some(l) = &view.layout { |
| 176 | self.render( l, &output ) | 177 | self.render( l, &output ) |
| @@ -203,12 +204,14 @@ fn exec(input: &impl MappedStructure, tokens: &Vec<Token>, buf: &mut String) -> | |||
| 203 | } | 204 | } |
| 204 | } | 205 | } |
| 205 | } | 206 | } |
| 206 | Token::If(ident,code) => { | 207 | Token::If(ident,code,other) => { |
| 207 | if let Some(value) = input.get_value( &ident ) && | 208 | if let Some(value) = input.get_value( &ident ) && |
| 208 | let Value::Bool(b) = value && | 209 | let Value::Bool(b) = value && |
| 209 | b { | 210 | b { |
| 210 | 211 | ||
| 211 | exec(input, code, buf)? | 212 | exec(input, code, buf)? |
| 213 | } else { | ||
| 214 | exec(input, other, buf)? | ||
| 212 | } | 215 | } |
| 213 | } | 216 | } |
| 214 | Token::Show(ident,p) => { | 217 | Token::Show(ident,p) => { |
| @@ -322,16 +325,13 @@ Now with explicit outputs: | |||
| 322 | } | 325 | } |
| 323 | 326 | ||
| 324 | #[test] | 327 | #[test] |
| 325 | fn loops() { | 328 | fn loops() -> Result<(),CrimError> { |
| 326 | let mut ct = Crimtag::new(); | 329 | let mut ct = Crimtag::new(); |
| 327 | if let Err(e) = ct.load_static(r#"Looping code: | 330 | ct.load_static(r#"Looping code: |
| 328 | [|view "index"|>We will enumerate people here:[|loop people|> | 331 | [|view "index"|>We will enumerate people here:[|loop people|> |
| 329 | - [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|if|] [|loop tags|> [|show tag|]<|loop|] <|loop|] | 332 | - [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|else|> **title redacted** <|if|] [|loop tags|> [|show tag|]<|loop|] <|loop|] |
| 330 | <|view|] | 333 | <|view|] |
| 331 | "#) { | 334 | "#)?; |
| 332 | println!("Error: {:?}", e ); | ||
| 333 | return; | ||
| 334 | } | ||
| 335 | 335 | ||
| 336 | let ctx = Context::from([ | 336 | let ctx = Context::from([ |
| 337 | ("people".to_string(), vec![ | 337 | ("people".to_string(), vec![ |
| @@ -361,9 +361,28 @@ Now with explicit outputs: | |||
| 361 | ].into(), | 361 | ].into(), |
| 362 | ].into()), | 362 | ].into()), |
| 363 | ]); | 363 | ]); |
| 364 | match ct.render("index", &ctx) { | 364 | |
| 365 | Ok(s) => println!("View result: {}", s ), | 365 | println!("View result: {}", ct.render("index", &ctx)? ); |
| 366 | Err(e) => println!("Error: {:?}", e ), | 366 | |
| 367 | } | 367 | Ok(()) |
| 368 | } | ||
| 369 | |||
| 370 | #[test] | ||
| 371 | fn conditional() -> Result<(),CrimError> { | ||
| 372 | let mut ct = Crimtag::new(); | ||
| 373 | ct.load_static(r#"Hi there | ||
| 374 | [|view "index"|> | ||
| 375 | Color: [|if is_red|> red <|elif is_blue |> blue <|else|> green <|if|] | ||
| 376 | <|view|]"#)?; | ||
| 377 | |||
| 378 | let ctx = Context::from([ | ||
| 379 | ("is_red".into(), false.into()), | ||
| 380 | ("is_blue".into(), false.into()), | ||
| 381 | ("color".into(), "purple".into()), | ||
| 382 | ]); | ||
| 383 | |||
| 384 | println!("View result: {}", ct.render("index", &ctx)? ); | ||
| 385 | |||
| 386 | Ok(()) | ||
| 368 | } | 387 | } |
| 369 | } | 388 | } |
diff --git a/src/parser.rs b/src/parser.rs index b681fce..fff00b1 100644 --- a/src/parser.rs +++ b/src/parser.rs | |||
| @@ -199,7 +199,7 @@ impl Parser { | |||
| 199 | } | 199 | } |
| 200 | 200 | ||
| 201 | fn p_input(&self, ctx: &mut Context ) -> CrimResult<Vec<View>> { | 201 | fn p_input(&self, ctx: &mut Context ) -> CrimResult<Vec<View>> { |
| 202 | let mut children = Vec::new(); | 202 | let mut tags = Vec::new(); |
| 203 | loop { | 203 | loop { |
| 204 | if ctx.cur().is_none() { | 204 | if ctx.cur().is_none() { |
| 205 | break; | 205 | break; |
| @@ -207,21 +207,41 @@ impl Parser { | |||
| 207 | match ctx.cur().unwrap().symbol() { | 207 | match ctx.cur().unwrap().symbol() { |
| 208 | SymbolType::Text(_) => { /* Skip top level text */ } | 208 | SymbolType::Text(_) => { /* Skip top level text */ } |
| 209 | SymbolType::StartFlat => { | 209 | SymbolType::StartFlat => { |
| 210 | children.push( self.parse_tag_view( ctx )? ); | 210 | let tb = self.parse_tag( ctx )?; |
| 211 | let tb = self.parse_tag_set( ctx, tb )?; | ||
| 212 | tags.push( tb ); | ||
| 211 | } | 213 | } |
| 212 | SymbolType::Error{..} => { | 214 | SymbolType::Error{..} => { |
| 213 | self.lex_error( &ctx.cur().unwrap() )?; | 215 | self.lex_error( &ctx.cur().unwrap() )?; |
| 214 | } | 216 | } |
| 215 | _ => { | 217 | _ => { |
| 216 | 218 | return Err(CrimError::parse( | |
| 219 | *ctx.cur().unwrap().start(), | ||
| 220 | format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() ) | ||
| 221 | )); | ||
| 217 | } | 222 | } |
| 218 | } | 223 | } |
| 219 | ctx.next(); | 224 | ctx.next(); |
| 220 | } | 225 | } |
| 221 | Ok(children) | 226 | |
| 227 | let mut views = Vec::new(); | ||
| 228 | for tag in tags { | ||
| 229 | let start_pos = tag.start_pos(); | ||
| 230 | let name = tag.name().clone(); | ||
| 231 | if tag.is_name(&SymbolType::View) && | ||
| 232 | let BuildResult::View(view) = tag.build( ctx )? { | ||
| 233 | views.push( view ); | ||
| 234 | } else { | ||
| 235 | return Err(CrimError::parse( | ||
| 236 | start_pos, | ||
| 237 | format!("Expected view at root, found {:?}", name ) | ||
| 238 | )); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | Ok(views) | ||
| 222 | } | 242 | } |
| 223 | 243 | ||
| 224 | fn parse_tag(&self, ctx: &mut Context) -> CrimResult<TagBuilder> { | 244 | fn parse_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult<TagBuilder<'a>> { |
| 225 | let start_sym = ctx.cur().unwrap(); | 245 | let start_sym = ctx.cur().unwrap(); |
| 226 | let mut tb = TagBuilder::new(&start_sym); | 246 | let mut tb = TagBuilder::new(&start_sym); |
| 227 | 247 | ||
| @@ -255,7 +275,7 @@ impl Parser { | |||
| 255 | if let Some(end_sym) = ctx.cur() { | 275 | if let Some(end_sym) = ctx.cur() { |
| 256 | match end_sym.symbol() { | 276 | match end_sym.symbol() { |
| 257 | SymbolType::EndPoint | SymbolType::EndFlat => { | 277 | SymbolType::EndPoint | SymbolType::EndFlat => { |
| 258 | tb.set_end( end_sym )?; | 278 | tb.set_end( &end_sym )?; |
| 259 | } | 279 | } |
| 260 | _ => { | 280 | _ => { |
| 261 | return Err(CrimError::parse( | 281 | return Err(CrimError::parse( |
| @@ -271,12 +291,9 @@ impl Parser { | |||
| 271 | Ok(tb) | 291 | Ok(tb) |
| 272 | } | 292 | } |
| 273 | 293 | ||
| 274 | fn parse_tag_set<'a>(&self, ctx: &mut Context; base: &mut TagBuilder) -> CrimResult<Token> { | 294 | fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult<TagBuilder<'a>> { |
| 275 | if base.is_unary() { | 295 | if base.is_unary() { |
| 276 | return Ok(base.build()); | 296 | return Ok(base); |
| 277 | } | ||
| 278 | if !base.is_multinary_open() { | ||
| 279 | return Err(CrimError::broken("Found close/mid tag instead of opening")); | ||
| 280 | } | 297 | } |
| 281 | 298 | ||
| 282 | loop { | 299 | loop { |
| @@ -285,226 +302,39 @@ impl Parser { | |||
| 285 | } | 302 | } |
| 286 | match ctx.cur().unwrap().symbol() { | 303 | match ctx.cur().unwrap().symbol() { |
| 287 | SymbolType::Text(s) => { | 304 | SymbolType::Text(s) => { |
| 288 | base.add_child(Token::Text(s.to_string())); | 305 | base.add_child(Entry::Token(Token::Text(s.to_string()))); |
| 289 | ctx.next(); | 306 | ctx.next(); |
| 290 | } | 307 | } |
| 291 | SymbolType::StartFlat | SymbolType::StartPoint => { | 308 | SymbolType::StartFlat | SymbolType::StartPoint => { |
| 292 | let mut tb = self.parse_tag( ctx )?; | 309 | let tb = self.parse_tag( ctx )?; |
| 293 | 310 | ||
| 294 | match tb.tag_type() { | 311 | match tb.tag_type() { |
| 295 | TagType::MultinaryOpen => { | 312 | TagType::MultinaryOpen => { |
| 296 | base.add_child( parse_tag_set( ctx, tb )? ); | 313 | base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); |
| 297 | } | 314 | } |
| 298 | TagType::MultinaryMid => { | 315 | TagType::MultinaryMid => { |
| 299 | base.add_chain( parse_tag_set( ctx, tb )? ); | 316 | base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); |
| 317 | return Ok(base); | ||
| 300 | } | 318 | } |
| 301 | TagType::MultinaryClose => { | 319 | TagType::MultinaryClose => { |
| 302 | if (tb.is_name(SymbolToken::If) && | 320 | if (tb.is_name(&SymbolType::If) && |
| 303 | (base.is_name(SymbolToken::Elif) || | 321 | (base.is_name(&SymbolType::ElIf) || |
| 304 | base.is_name(SymbolToken::Else))) || | 322 | base.is_name(&SymbolType::Else))) || |
| 305 | tb.is_name(base.name().symbol()) { | 323 | tb.is_name(base.name().unwrap().symbol()) { |
| 306 | 324 | return Ok(base); | |
| 307 | } else { | 325 | } else { |
| 308 | return Err(CrimError::parse( | 326 | return Err(CrimError::parse( |
| 309 | *tb.name().start(), | 327 | *tb.name().unwrap().start(), |
| 310 | format!("Found {:?} looking for close of {:?}", tb.name().symbol(), base.name().symbol()) | 328 | format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol()) |
| 311 | )); | 329 | )); |
| 312 | } | 330 | } |
| 313 | } | 331 | } |
| 314 | } | 332 | TagType::Unary => { |
| 315 | } | 333 | base.add_child(Entry::TagBuilder(tb)); |
| 316 | SymbolType::Error{..} => { | 334 | } |
| 317 | return self.lex_error( &ctx.cur().unwrap() ); | 335 | TagType::Unknown => { |
| 318 | } | 336 | return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else.")); |
| 319 | _ => { | ||
| 320 | return Err(CrimError::parse( | ||
| 321 | *ctx.cur().unwrap().start(), | ||
| 322 | format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), | ||
| 323 | )); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | fn parse_tag_view(&self, ctx: &mut Context) -> CrimResult<View> { | ||
| 330 | let tb = self.parse_open_tag( ctx )?; | ||
| 331 | if !tb.is_name( &SymbolType::View ) { | ||
| 332 | return Err(CrimError::parse(tb.start_pos(), | ||
| 333 | "Unexpected tag type, only view allowed at root".into() | ||
| 334 | )); | ||
| 335 | } | ||
| 336 | |||
| 337 | if tb.is_unary() { | ||
| 338 | return tb.build_view(ctx, Vec::new()); | ||
| 339 | } | ||
| 340 | let mut children = Vec::new(); | ||
| 341 | let mut outputs = Vec::new(); | ||
| 342 | |||
| 343 | //println!("--parse-tag-view-- tag parsed, next token: {:?}", ctx.cur() ); | ||
| 344 | loop { | ||
| 345 | if ctx.cur().is_none() { | ||
| 346 | return Err(CrimError::eos(format!("close tag for {:?}", tb.name()).as_str())) | ||
| 347 | } | ||
| 348 | match ctx.cur().unwrap().symbol() { | ||
| 349 | SymbolType::Text(s) => { | ||
| 350 | children.push( Token::Text(s.to_string()) ); | ||
| 351 | ctx.next(); | ||
| 352 | } | ||
| 353 | SymbolType::StartFlat => { | ||
| 354 | let subtb = self.parse_tag( ctx )?; | ||
| 355 | if subtb.is_name(&SymbolType::Output) { | ||
| 356 | outputs.push(subtb.build_output()?); | ||
| 357 | } else { | ||
| 358 | children.push(subtb.build()?); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | SymbolType::StartPoint => { | ||
| 362 | let end = self.parse_end_tag( ctx )?; | ||
| 363 | //println!("End tag: {:?}, open tag: {:?}", end, tb.name() ); | ||
| 364 | if tb.is_name(end.symbol()) { | ||
| 365 | // They match, time to decide what we're doing. | ||
| 366 | let any_outputs = outputs.len() > 0; | ||
| 367 | let all_text = children.iter().all( | ||
| 368 | |t| matches!(t, Token::Text(..)) | ||
| 369 | ); | ||
| 370 | if !any_outputs { | ||
| 371 | // Create an implicit output called "content" | ||
| 372 | outputs.push(Output{ | ||
| 373 | name: "content".to_string(), | ||
| 374 | code: children, | ||
| 375 | }); | ||
| 376 | } else { | ||
| 377 | if !all_text { | ||
| 378 | return Err(CrimError::parse( | ||
| 379 | *end.start(), | ||
| 380 | "You cannot mix non-output and output tags in a view.".to_string() | ||
| 381 | )); | ||
| 382 | } | ||
| 383 | } | 337 | } |
| 384 | return tb.build_view(ctx,outputs); | ||
| 385 | } else { | ||
| 386 | // They don't match, complain. | ||
| 387 | return Err(CrimError::parse( | ||
| 388 | *end.start(), | ||
| 389 | "Mismatched open and closing tags.".to_string() | ||
| 390 | )); | ||
| 391 | } | ||
| 392 | } | ||
| 393 | SymbolType::Error{..} => { | ||
| 394 | return self.lex_error( &ctx.cur().unwrap() ); | ||
| 395 | } | ||
| 396 | _ => { | ||
| 397 | return Err(CrimError::parse( | ||
| 398 | *ctx.cur().unwrap().start(), | ||
| 399 | format!("Unexpected tag in view root: {:?}", ctx.cur().unwrap().symbol()) | ||
| 400 | )); | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | fn parse_open_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult<TagBuilder<'a>> { | ||
| 407 | let start_sym = ctx.cur().unwrap(); | ||
| 408 | let mut tb = TagBuilder::new(&start_sym); | ||
| 409 | |||
| 410 | if ctx.next().is_some() { | ||
| 411 | match ctx.cur().unwrap().symbol() { | ||
| 412 | SymbolType::View | SymbolType::Show | SymbolType::Loop | | ||
| 413 | SymbolType::If | SymbolType::ElIf | SymbolType::Else | | ||
| 414 | SymbolType::Output => { | ||
| 415 | let name_sym = ctx.cur().unwrap(); | ||
| 416 | ctx.next(); | ||
| 417 | tb.set_name( name_sym ); | ||
| 418 | } | ||
| 419 | _ => { | ||
| 420 | return Err(CrimError::parse( | ||
| 421 | *ctx.cur().unwrap().start(), | ||
| 422 | "Unexpected symbol".to_string() | ||
| 423 | )); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } else { | ||
| 427 | return Err(CrimError::eos("tag type")); | ||
| 428 | } | ||
| 429 | |||
| 430 | self.parse_tag_params( ctx, &mut tb )?; | ||
| 431 | self.parse_tag_props( ctx, &mut tb )?; | ||
| 432 | |||
| 433 | if let Some(end_sym) = ctx.cur() { | ||
| 434 | match end_sym.symbol() { | ||
| 435 | SymbolType::EndPoint => { | ||
| 436 | tb.set_type( TagType::MultinaryOpen ); | ||
| 437 | } | ||
| 438 | SymbolType::EndFlat => { | ||
| 439 | tb.set_type( TagType::Unary ); | ||
| 440 | } | ||
| 441 | _ => { | ||
| 442 | return Err(CrimError::parse( | ||
| 443 | *end_sym.start(), | ||
| 444 | format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) | ||
| 445 | )); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | ctx.next(); | ||
| 451 | |||
| 452 | Ok(tb) | ||
| 453 | } | ||
| 454 | |||
| 455 | fn parse_end_tag<'a>(&self, ctx: &mut Context<'a> ) -> CrimResult<Symbol<'a>> { | ||
| 456 | if !ctx.cur().is("end tag",|s| *s == SymbolType::StartPoint )? { | ||
| 457 | return Err(CrimError::parse( | ||
| 458 | *ctx.cur().unwrap().start(), | ||
| 459 | "Invalid end tag?".to_string() | ||
| 460 | )); | ||
| 461 | } | ||
| 462 | if !ctx.next().is_valid_tag_name()? { | ||
| 463 | return Err(CrimError::parse( | ||
| 464 | *ctx.cur().unwrap().start(), | ||
| 465 | "Invalid tag name".to_string() | ||
| 466 | )); | ||
| 467 | } | ||
| 468 | let name = ctx.cur().unwrap(); | ||
| 469 | if !ctx.next().is("end of end tag", |s| *s == SymbolType::EndFlat )? { | ||
| 470 | return Err(CrimError::parse( | ||
| 471 | *ctx.cur().unwrap().start(), | ||
| 472 | "Tag should be <| |] style end tag.".to_string(), | ||
| 473 | )); | ||
| 474 | } | ||
| 475 | ctx.next(); | ||
| 476 | Ok(name) | ||
| 477 | } | ||
| 478 | |||
| 479 | fn parse_tag_body<'a>(&self, ctx: &mut Context<'a> ) -> CrimResult<TagBuilder<'a>> { | ||
| 480 | let mut tb = self.parse_open_tag( ctx )?; | ||
| 481 | if tb.is_unary() { | ||
| 482 | return Ok(tb); | ||
| 483 | } | ||
| 484 | |||
| 485 | loop { | ||
| 486 | if ctx.cur().is_none() { | ||
| 487 | return Err(CrimError::eos(format!("close tag for {:?}", tb.name()).as_str())); | ||
| 488 | } | ||
| 489 | match ctx.cur().unwrap().symbol() { | ||
| 490 | SymbolType::Text(s) => { | ||
| 491 | tb.add_child(Token:: Text(s.to_string())); | ||
| 492 | ctx.next(); | ||
| 493 | } | ||
| 494 | SymbolType::StartFlat => { | ||
| 495 | tb.add_child( self.parse_tag_body( ctx )?.build()? ); | ||
| 496 | } | ||
| 497 | SymbolType::StartPoint => { | ||
| 498 | let end = self.parse_end_tag( ctx )?; | ||
| 499 | if tb.is_name(end.symbol()) { | ||
| 500 | // They match, end. | ||
| 501 | return Ok(tb); | ||
| 502 | } else { | ||
| 503 | // They don't match, complain. | ||
| 504 | return Err(CrimError::parse( | ||
| 505 | *end.start(), | ||
| 506 | "Mismatched open and closing tags.".to_string() | ||
| 507 | )); | ||
| 508 | } | 338 | } |
| 509 | } | 339 | } |
| 510 | SymbolType::Error{..} => { | 340 | SymbolType::Error{..} => { |
| @@ -600,14 +430,87 @@ enum TagType { | |||
| 600 | MultinaryClose, | 430 | MultinaryClose, |
| 601 | } | 431 | } |
| 602 | 432 | ||
| 433 | #[derive(Debug)] | ||
| 434 | enum Entry<'a>{ | ||
| 435 | Token(Token), | ||
| 436 | TagBuilder(TagBuilder<'a>), | ||
| 437 | } | ||
| 438 | |||
| 439 | type EntryList<'a> = Vec<Entry<'a>>; | ||
| 440 | |||
| 441 | trait EntryListConverter { | ||
| 442 | fn has_outputs(&self) -> bool; | ||
| 443 | fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult<Vec<Output>>; | ||
| 444 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>>; | ||
| 445 | } | ||
| 446 | |||
| 447 | impl<'a> EntryListConverter for EntryList<'a> { | ||
| 448 | fn has_outputs(&self) -> bool { | ||
| 449 | self.iter().any(|e| | ||
| 450 | if let Entry::TagBuilder(tb) = e | ||
| 451 | && tb.is_name(&SymbolType::Output) { | ||
| 452 | true | ||
| 453 | } else { | ||
| 454 | false | ||
| 455 | } | ||
| 456 | ) | ||
| 457 | } | ||
| 458 | |||
| 459 | fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult<Vec<Output>> { | ||
| 460 | let mut outputs = Vec::new(); | ||
| 461 | |||
| 462 | if self.has_outputs() { | ||
| 463 | // We have an explicit output, we cant have anything else | ||
| 464 | for e in self.drain(..) { | ||
| 465 | let tb = if let Entry::TagBuilder(tb) = e { | ||
| 466 | tb | ||
| 467 | } else { | ||
| 468 | continue; | ||
| 469 | }; | ||
| 470 | if let BuildResult::Output(out) = tb.build(ctx)? { | ||
| 471 | outputs.push( out ); | ||
| 472 | } | ||
| 473 | } | ||
| 474 | } else { | ||
| 475 | // No outputs, so we create one implicit output | ||
| 476 | outputs.push(Output { | ||
| 477 | name: "content".into(), | ||
| 478 | code: self.to_tokens(ctx)? | ||
| 479 | }); | ||
| 480 | } | ||
| 481 | |||
| 482 | Ok(outputs) | ||
| 483 | } | ||
| 484 | |||
| 485 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>> { | ||
| 486 | let mut tokens : Vec<Token> = Vec::new(); | ||
| 487 | for e in self.drain(..) { | ||
| 488 | match e { | ||
| 489 | Entry::Token(token) => { | ||
| 490 | tokens.push( token ); | ||
| 491 | } | ||
| 492 | Entry::TagBuilder(tb) => { | ||
| 493 | if let BuildResult::Token(token) = tb.build(ctx)? { | ||
| 494 | tokens.push( token ); | ||
| 495 | } else { | ||
| 496 | return Err(CrimError::broken( Position::none(), "Non-token result built.")); | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
| 500 | } | ||
| 501 | Ok(tokens) | ||
| 502 | } | ||
| 503 | } | ||
| 504 | |||
| 505 | #[derive(Debug)] | ||
| 603 | struct TagBuilder<'a> { | 506 | struct TagBuilder<'a> { |
| 604 | name: Option<Symbol<'a>>, | 507 | name: Option<Symbol<'a>>, |
| 605 | params: Vec<ParamValue>, | 508 | params: Vec<ParamValue>, |
| 606 | props: Properties, | 509 | props: Properties, |
| 607 | children: Vec<Token>, | 510 | children: Vec<Entry<'a>>, |
| 608 | tag_type: TagType, | 511 | tag_type: TagType, |
| 609 | start: Symbol<'a>, | 512 | start: Symbol<'a>, |
| 610 | chain: Vec<Token>, | 513 | chain: Vec<Entry<'a>>, |
| 611 | } | 514 | } |
| 612 | 515 | ||
| 613 | #[derive(Debug)] | 516 | #[derive(Debug)] |
| @@ -636,29 +539,30 @@ impl<'a> TagBuilder<'a> { | |||
| 636 | } | 539 | } |
| 637 | 540 | ||
| 638 | pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { | 541 | pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { |
| 639 | if start.symbol() == SymbolType::StartFlat { | 542 | if *self.start.symbol() == SymbolType::StartFlat { |
| 640 | if end.symbol() == SymbolType::EndFlat { | 543 | if *end.symbol() == SymbolType::EndFlat { |
| 641 | self.tag_type = TagType::Unary; | 544 | self.tag_type = TagType::Unary; |
| 642 | } else if end.symbol() == SymbolType::EndPoint { | 545 | } else if *end.symbol() == SymbolType::EndPoint { |
| 643 | self.tag_type = TagType::MultinaryOpen; | 546 | self.tag_type = TagType::MultinaryOpen; |
| 644 | } else { | 547 | } else { |
| 645 | return Err(CrimError::broken( *self.end.start(), "Invalid bracket token type.")); | 548 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); |
| 646 | } | 549 | } |
| 647 | } else if start.symbol() == SymbolType::StartPoint { | 550 | } else if *self.start.symbol() == SymbolType::StartPoint { |
| 648 | if end.symbol() == SymbolType::EndFlat { | 551 | if *end.symbol() == SymbolType::EndFlat { |
| 649 | self.tag_type = TagType::MultinaryClose; | 552 | self.tag_type = TagType::MultinaryClose; |
| 650 | } else if end.symbol() == SymbolType::EndPoint { | 553 | } else if *end.symbol() == SymbolType::EndPoint { |
| 651 | self.tag_type = TagType::MultinaryMid; | 554 | self.tag_type = TagType::MultinaryMid; |
| 652 | } else { | 555 | } else { |
| 653 | return Err(CrimError::broken( *self.end.start(), "Invalid bracket token type.")); | 556 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); |
| 654 | } | 557 | } |
| 655 | } else { | 558 | } else { |
| 656 | return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); | 559 | return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); |
| 657 | } | 560 | } |
| 561 | Ok(()) | ||
| 658 | } | 562 | } |
| 659 | 563 | ||
| 660 | pub fn start_pos(&self) -> Position { | 564 | pub fn start_pos(&self) -> Position { |
| 661 | self.start.start() | 565 | *self.start.start() |
| 662 | } | 566 | } |
| 663 | 567 | ||
| 664 | pub fn is_unary(&self) -> bool { | 568 | pub fn is_unary(&self) -> bool { |
| @@ -670,18 +574,20 @@ impl<'a> TagBuilder<'a> { | |||
| 670 | } | 574 | } |
| 671 | 575 | ||
| 672 | pub fn can_have_params(&self) -> bool { | 576 | pub fn can_have_params(&self) -> bool { |
| 673 | match self.name.symbol() { | 577 | match self.name.unwrap().symbol() { |
| 674 | SymbolType::View | SymbolType::Output | SymbolType::Loop => true, | 578 | SymbolType::View | SymbolType::Output | SymbolType::Loop => true, |
| 675 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | 579 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | |
| 676 | SymbolType::Else => false, | 580 | SymbolType::Else => false, |
| 581 | _ => false, | ||
| 677 | } | 582 | } |
| 678 | } | 583 | } |
| 679 | 584 | ||
| 680 | pub fn can_have_expr(&self) -> bool { | 585 | pub fn can_have_expr(&self) -> bool { |
| 681 | match self.name.symbol() { | 586 | match self.name.unwrap().symbol() { |
| 682 | SymbolType::View | SymbolType::Output | SymbolType::Loop => false, | 587 | SymbolType::View | SymbolType::Output | SymbolType::Loop => false, |
| 683 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | 588 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | |
| 684 | SymbolType::Else => true, | 589 | SymbolType::Else => true, |
| 590 | _ => false, | ||
| 685 | } | 591 | } |
| 686 | } | 592 | } |
| 687 | 593 | ||
| @@ -726,14 +632,18 @@ impl<'a> TagBuilder<'a> { | |||
| 726 | self.props.insert( key, value ); | 632 | self.props.insert( key, value ); |
| 727 | } | 633 | } |
| 728 | 634 | ||
| 729 | pub fn add_child(&mut self, token: Token) { | 635 | pub fn add_child(&mut self, tb: Entry<'a>) { |
| 730 | self.children.push( token ); | 636 | self.children.push( tb ); |
| 731 | } | 637 | } |
| 732 | 638 | ||
| 733 | pub fn append_children(&mut self, children: &mut Vec::<Token>) { | 639 | pub fn append_children(&mut self, children: &mut Vec::<Entry<'a>>) { |
| 734 | self.children.append( children ); | 640 | self.children.append( children ); |
| 735 | } | 641 | } |
| 736 | 642 | ||
| 643 | pub fn add_chain(&mut self, tb: Entry<'a>) { | ||
| 644 | self.chain.push( tb ); | ||
| 645 | } | ||
| 646 | |||
| 737 | pub fn set_type(&mut self, tag_type: TagType) { | 647 | pub fn set_type(&mut self, tag_type: TagType) { |
| 738 | self.tag_type = tag_type; | 648 | self.tag_type = tag_type; |
| 739 | } | 649 | } |
| @@ -742,7 +652,7 @@ impl<'a> TagBuilder<'a> { | |||
| 742 | self.tag_type | 652 | self.tag_type |
| 743 | } | 653 | } |
| 744 | 654 | ||
| 745 | pub fn build(mut self) -> CrimResult<BuildResult> { | 655 | pub fn build(mut self, ctx: &mut Context ) -> CrimResult<BuildResult> { |
| 746 | if let Some(sym) = self.name { | 656 | if let Some(sym) = self.name { |
| 747 | match sym.symbol() { | 657 | match sym.symbol() { |
| 748 | SymbolType::View => { | 658 | SymbolType::View => { |
| @@ -754,7 +664,7 @@ impl<'a> TagBuilder<'a> { | |||
| 754 | Ok(BuildResult::View(View { | 664 | Ok(BuildResult::View(View { |
| 755 | name: name, | 665 | name: name, |
| 756 | source: ctx.source(), | 666 | source: ctx.source(), |
| 757 | outputs: outputs, | 667 | outputs: self.children.to_outputs(ctx)?, |
| 758 | //theme: Option<String> | 668 | //theme: Option<String> |
| 759 | layout: self.props.get("layout").cloned(), | 669 | layout: self.props.get("layout").cloned(), |
| 760 | })) | 670 | })) |
| @@ -767,7 +677,7 @@ impl<'a> TagBuilder<'a> { | |||
| 767 | }; | 677 | }; |
| 768 | Ok(BuildResult::Output(Output { | 678 | Ok(BuildResult::Output(Output { |
| 769 | name: name, | 679 | name: name, |
| 770 | code: self.children, | 680 | code: self.children.to_tokens(ctx)?, |
| 771 | })) | 681 | })) |
| 772 | } | 682 | } |
| 773 | SymbolType::Loop => { | 683 | SymbolType::Loop => { |
| @@ -780,7 +690,9 @@ impl<'a> TagBuilder<'a> { | |||
| 780 | "Identifier for loop variable name.".into()) | 690 | "Identifier for loop variable name.".into()) |
| 781 | ); | 691 | ); |
| 782 | }; | 692 | }; |
| 783 | Ok(BuildResult::Token(Token::Loop(id, self.children))) | 693 | Ok(BuildResult::Token( |
| 694 | Token::Loop(id, self.children.to_tokens(ctx)?) | ||
| 695 | )) | ||
| 784 | } | 696 | } |
| 785 | SymbolType::Show => { | 697 | SymbolType::Show => { |
| 786 | let id = if let ParamValue::Identifier(id) | 698 | let id = if let ParamValue::Identifier(id) |
| @@ -804,17 +716,25 @@ impl<'a> TagBuilder<'a> { | |||
| 804 | "Identifier for if variable name.".into()) | 716 | "Identifier for if variable name.".into()) |
| 805 | ); | 717 | ); |
| 806 | }; | 718 | }; |
| 807 | if self.chain.len() == 1 && | 719 | |
| 808 | let Token::If(id, mut children, chain) = self.chain[0] && | 720 | let is_else = if self.chain.len() == 1 && |
| 809 | id.len() == 0 { | 721 | let Entry::TagBuilder(tb) = &self.chain[0] && |
| 722 | tb.is_name(&SymbolType::Else) && | ||
| 723 | tb.params.len() == 0 { | ||
| 724 | true | ||
| 725 | } else { | ||
| 726 | false | ||
| 727 | }; | ||
| 728 | |||
| 729 | if is_else && | ||
| 730 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 810 | self.chain.clear(); | 731 | self.chain.clear(); |
| 811 | self.chain.append(children); | 732 | self.chain.append(&mut tb.children); |
| 812 | } | 733 | } |
| 813 | Ok(BuildResult::Token(Token::If(id, self.children, self.chain))) | 734 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) |
| 814 | } | 735 | } |
| 815 | SymbolType::ElIf => { | 736 | SymbolType::ElIf => { |
| 816 | let id = if let ParamValue::Identifier(id) | 737 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { |
| 817 | = self.params.swap_remove(0) { | ||
| 818 | id | 738 | id |
| 819 | } else { | 739 | } else { |
| 820 | return Err(CrimError::parse( | 740 | return Err(CrimError::parse( |
| @@ -822,17 +742,37 @@ impl<'a> TagBuilder<'a> { | |||
| 822 | "Identifier for elif variable name.".into()) | 742 | "Identifier for elif variable name.".into()) |
| 823 | ); | 743 | ); |
| 824 | }; | 744 | }; |
| 825 | Ok(BuildResult::Token(Token::If(Identifier::new(), self.children, self.chain))) | 745 | |
| 746 | // this is copied from if, since they're the same this | ||
| 747 | // should be encapsulated and moved into a function... | ||
| 748 | // ...but I can't do that right now. | ||
| 749 | let is_else = if self.chain.len() == 1 && | ||
| 750 | let Entry::TagBuilder(tb) = &self.chain[0] && | ||
| 751 | tb.is_name(&SymbolType::Else) && | ||
| 752 | tb.params.len() == 0 { | ||
| 753 | true | ||
| 754 | } else { | ||
| 755 | false | ||
| 756 | }; | ||
| 757 | |||
| 758 | if is_else && | ||
| 759 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 760 | self.chain.clear(); | ||
| 761 | self.chain.append(&mut tb.children); | ||
| 762 | } | ||
| 763 | |||
| 764 | |||
| 765 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 826 | } | 766 | } |
| 827 | SymbolType::Else => { | 767 | SymbolType::Else => { |
| 828 | Ok(BuildResult::Token(Token::If(Identifier::new(), self.children, self.chain))) | 768 | Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) |
| 829 | } | 769 | } |
| 830 | _ => { | 770 | _ => { |
| 831 | Err(CrimError::parse( self.start_pos, "Bad tag type".into())) | 771 | Err(CrimError::parse( self.start_pos(), "Bad tag type".into())) |
| 832 | } | 772 | } |
| 833 | } | 773 | } |
| 834 | } else { | 774 | } else { |
| 835 | Err(CrimError::parse(self.start_pos, "Bad tag type".into())) | 775 | Err(CrimError::parse(self.start_pos(), "Bad tag type".into())) |
| 836 | } | 776 | } |
| 837 | } | 777 | } |
| 838 | } | 778 | } |
