diff options
| author | eichlan <eichlan@jotunn.office.penny-arcade.com> | 2026-06-23 15:31:51 -0700 |
|---|---|---|
| committer | eichlan <eichlan@jotunn.office.penny-arcade.com> | 2026-06-23 15:31:51 -0700 |
| commit | 062048ef6e3e060bcf841ea2ec92e026f09d8da0 (patch) | |
| tree | 22f7dad062b16f25fcfa1a28af13ac2937348ef0 /crimtag/src/parser.rs | |
| parent | 7c299c86bbac76ec2ac199d68eb4dda093a64e3a (diff) | |
| download | crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.gz crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.bz2 crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.xz crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.zip | |
Roorganized into workspace and packages.
This is to accomidate a new proc_macro package, which can't have
anything else in it.
Diffstat (limited to 'crimtag/src/parser.rs')
| -rw-r--r-- | crimtag/src/parser.rs | 785 |
1 files changed, 785 insertions, 0 deletions
diff --git a/crimtag/src/parser.rs b/crimtag/src/parser.rs new file mode 100644 index 0000000..4f7ff6a --- /dev/null +++ b/crimtag/src/parser.rs | |||
| @@ -0,0 +1,785 @@ | |||
| 1 | use crate::lexer::*; | ||
| 2 | use crate::*; | ||
| 3 | |||
| 4 | struct Context<'a> { | ||
| 5 | cur: [Option<Symbol<'a>>;2], | ||
| 6 | icur: usize, | ||
| 7 | ll: Lexer<'a>, | ||
| 8 | source: usize, | ||
| 9 | } | ||
| 10 | |||
| 11 | impl<'a> Context<'a> { | ||
| 12 | pub fn new( mut ll: Lexer<'a>, source: usize ) -> Self { | ||
| 13 | let cur = [ll.next(), ll.next()]; | ||
| 14 | //println!(" - cur: {:?}, peek: {:?}", cur[0], cur[1] ); | ||
| 15 | Self { | ||
| 16 | cur, | ||
| 17 | icur: 0, | ||
| 18 | ll, | ||
| 19 | source, | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn next(&mut self) -> Option<Symbol<'a>> { | ||
| 24 | self.cur[self.icur] = self.ll.next(); | ||
| 25 | self.icur = (self.icur+1)%2; | ||
| 26 | //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); | ||
| 27 | self.cur[self.icur] | ||
| 28 | } | ||
| 29 | |||
| 30 | pub fn cur(&self) -> Option<Symbol<'a>> { | ||
| 31 | self.cur[self.icur] | ||
| 32 | } | ||
| 33 | |||
| 34 | pub fn peek(&self) -> Option<Symbol<'a>> { | ||
| 35 | self.cur[(self.icur+1)%2] | ||
| 36 | } | ||
| 37 | |||
| 38 | /* | ||
| 39 | pub fn check_unwrapped<T: Fn( &SymbolType, &SymbolType ) -> bool>(&self, context: &str, f: T) -> CrimResult<bool> { | ||
| 40 | if self.cur().is_none() || self.peek().is_none() { | ||
| 41 | Err(CrimError::eos( context )) | ||
| 42 | } else { | ||
| 43 | Ok(f( self.cur().unwrap().symbol(), self.peek().unwrap().symbol())) | ||
| 44 | } | ||
| 45 | } | ||
| 46 | */ | ||
| 47 | |||
| 48 | pub fn source(&self) -> usize { | ||
| 49 | self.source | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | trait SymbolHelper { | ||
| 54 | fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool>; | ||
| 55 | #[allow(dead_code)] | ||
| 56 | fn is_valid_tag_name(&self) -> CrimResult<bool>; | ||
| 57 | } | ||
| 58 | |||
| 59 | impl<'a> SymbolHelper for Option<Symbol<'a>> { | ||
| 60 | fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool> { | ||
| 61 | if let Some(st) = self { | ||
| 62 | Ok(f( &st.symbol() )) | ||
| 63 | } else { | ||
| 64 | Err(CrimError::eos( context )) | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | fn is_valid_tag_name(&self) -> CrimResult<bool> { | ||
| 69 | if let Some(st) = self { | ||
| 70 | Ok(match st.symbol() { | ||
| 71 | SymbolType::Token(_) | | ||
| 72 | SymbolType::View | | ||
| 73 | SymbolType::Output | | ||
| 74 | SymbolType::Loop | | ||
| 75 | SymbolType::If | | ||
| 76 | SymbolType::ElIf | | ||
| 77 | SymbolType::Else | | ||
| 78 | SymbolType::Show => true, | ||
| 79 | _ => false | ||
| 80 | }) | ||
| 81 | } else { | ||
| 82 | Err(CrimError::parse( | ||
| 83 | Position::none(), | ||
| 84 | "Unexpeceted end of stream.".to_string() | ||
| 85 | )) | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | pub struct Parser { | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * input: input complete_tag | ||
| 95 | * | input text | ||
| 96 | * | | ||
| 97 | * ; | ||
| 98 | * | ||
| 99 | * tag: unary_tag | ||
| 100 | * | multinary_open_tag | ||
| 101 | * | multinary_mid_tag | ||
| 102 | * | multinary_close_tag | ||
| 103 | * ; | ||
| 104 | * | ||
| 105 | * unary_tag: '[|' tag_guts '|]' | ||
| 106 | * ; | ||
| 107 | * | ||
| 108 | * | '[|' tag_guts '|>' | ||
| 109 | * | '<|' tag_guts '|>' | ||
| 110 | * | '<|' tag_guts '|]' | ||
| 111 | * ; | ||
| 112 | * | ||
| 113 | * tag_pair: open_tag tag_body close_tag | ||
| 114 | * ; | ||
| 115 | * | ||
| 116 | * tag_body: tag_body tag | ||
| 117 | * | tag_body tag_pair | ||
| 118 | * | tag_body text | ||
| 119 | * | | ||
| 120 | * ; | ||
| 121 | * | ||
| 122 | * open_tag: '[|' tag_guts '|>' | ||
| 123 | * ; | ||
| 124 | * | ||
| 125 | * close_tag: '<|' token '|]' | ||
| 126 | * ; | ||
| 127 | * | ||
| 128 | * tag_guts: 'view' literal props | ||
| 129 | * | 'section' literal | ||
| 130 | * | 'output' literal | ||
| 131 | * ; | ||
| 132 | * | ||
| 133 | * literal: '"' [^"]* '"' | ||
| 134 | * ; | ||
| 135 | * | ||
| 136 | * props: props token '=' literal | ||
| 137 | * | | ||
| 138 | * ; | ||
| 139 | * | ||
| 140 | * disambiguate (tm): | ||
| 141 | * | ||
| 142 | * input: input tag | ||
| 143 | * | input text | ||
| 144 | * | | ||
| 145 | * ; | ||
| 146 | * | ||
| 147 | * tag: open_tag_base unary_tag | ||
| 148 | * | open_tag_base binary_tag | ||
| 149 | * ; | ||
| 150 | * | ||
| 151 | * open_tag_base: '[|' tag_guts | ||
| 152 | * ; | ||
| 153 | * | ||
| 154 | * unary_tag: '|]' | ||
| 155 | * ; | ||
| 156 | * | ||
| 157 | * binary_tag: '|>' tag_body close_tag | ||
| 158 | * ; | ||
| 159 | * | ||
| 160 | * tag_body: tag_body tag | ||
| 161 | * | tag_body text | ||
| 162 | * | | ||
| 163 | * ; | ||
| 164 | * | ||
| 165 | * close_tag: '<|' token '|]' | ||
| 166 | * ; | ||
| 167 | * | ||
| 168 | * tag_guts: 'view' literal props | ||
| 169 | * | 'section' literal | ||
| 170 | * | 'output' literal | ||
| 171 | * ; | ||
| 172 | * | ||
| 173 | * literal: '"' [^"]* '"' | ||
| 174 | * ; | ||
| 175 | * | ||
| 176 | * props: props token '=' literal | ||
| 177 | * | | ||
| 178 | * ; | ||
| 179 | */ | ||
| 180 | impl Parser { | ||
| 181 | pub fn new() -> Self { | ||
| 182 | Self { | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | fn lex_error<T>(&self, error: &Symbol) -> CrimResult<T> { | ||
| 187 | if let SymbolType::Error{what} = error.symbol() { | ||
| 188 | Err(CrimError::parse( *error.start(), | ||
| 189 | format!("What: {:?}", *what) | ||
| 190 | )) | ||
| 191 | } else { | ||
| 192 | Err(CrimError::parse( Position::none(), "Not an error?".into())) | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | pub fn parse(&self, src: &str, source: usize ) -> CrimResult<Vec<View>> { | ||
| 197 | let ll = Lexer::new( src ); | ||
| 198 | let mut ctx = Context::new( ll, source ); | ||
| 199 | |||
| 200 | // Parse the root of the file, the input context | ||
| 201 | self.p_input( &mut ctx ) | ||
| 202 | } | ||
| 203 | |||
| 204 | fn p_input(&self, ctx: &mut Context ) -> CrimResult<Vec<View>> { | ||
| 205 | let mut tags = Vec::new(); | ||
| 206 | loop { | ||
| 207 | if ctx.cur().is_none() { | ||
| 208 | break; | ||
| 209 | } | ||
| 210 | match ctx.cur().unwrap().symbol() { | ||
| 211 | SymbolType::Text(_) => { /* Skip top level text */ } | ||
| 212 | SymbolType::StartFlat => { | ||
| 213 | let tb = self.parse_tag( ctx )?; | ||
| 214 | let tb = self.parse_tag_set( ctx, tb )?; | ||
| 215 | tags.push( tb ); | ||
| 216 | } | ||
| 217 | SymbolType::Error{..} => { | ||
| 218 | self.lex_error( &ctx.cur().unwrap() )?; | ||
| 219 | } | ||
| 220 | _ => { | ||
| 221 | return Err(CrimError::parse( | ||
| 222 | *ctx.cur().unwrap().start(), | ||
| 223 | format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() ) | ||
| 224 | )); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | ctx.next(); | ||
| 228 | } | ||
| 229 | |||
| 230 | let mut views = Vec::new(); | ||
| 231 | for tag in tags { | ||
| 232 | let start_pos = tag.start_pos(); | ||
| 233 | let name = tag.name().clone(); | ||
| 234 | if tag.is_name(&SymbolType::View) && | ||
| 235 | let BuildResult::View(view) = tag.build( ctx )? { | ||
| 236 | views.push( view ); | ||
| 237 | } else { | ||
| 238 | return Err(CrimError::parse( | ||
| 239 | start_pos, | ||
| 240 | format!("Expected view at root, found {:?}", name ) | ||
| 241 | )); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | Ok(views) | ||
| 245 | } | ||
| 246 | |||
| 247 | fn parse_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult<TagBuilder<'a>> { | ||
| 248 | let start_sym = ctx.cur().unwrap(); | ||
| 249 | let mut tb = TagBuilder::new(&start_sym); | ||
| 250 | |||
| 251 | if ctx.next().is_some() { | ||
| 252 | match ctx.cur().unwrap().symbol() { | ||
| 253 | SymbolType::View | SymbolType::Show | SymbolType::Loop | | ||
| 254 | SymbolType::If | SymbolType::ElIf | SymbolType::Else | | ||
| 255 | SymbolType::Output => { | ||
| 256 | let name_sym = ctx.cur().unwrap(); | ||
| 257 | ctx.next(); | ||
| 258 | tb.set_name( name_sym ); | ||
| 259 | } | ||
| 260 | _ => { | ||
| 261 | return Err(CrimError::parse( | ||
| 262 | *ctx.cur().unwrap().start(), | ||
| 263 | "Unexpected symbol".to_string() | ||
| 264 | )); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } else { | ||
| 268 | return Err(CrimError::eos("tag type")); | ||
| 269 | } | ||
| 270 | |||
| 271 | if tb.can_have_params() || tb.can_have_expr() { | ||
| 272 | self.parse_tag_params( ctx, &mut tb )?; | ||
| 273 | } | ||
| 274 | if tb.can_have_props() { | ||
| 275 | self.parse_tag_props( ctx, &mut tb )?; | ||
| 276 | } | ||
| 277 | |||
| 278 | if let Some(end_sym) = ctx.cur() { | ||
| 279 | match end_sym.symbol() { | ||
| 280 | SymbolType::EndPoint | SymbolType::EndFlat => { | ||
| 281 | tb.set_end( &end_sym )?; | ||
| 282 | } | ||
| 283 | _ => { | ||
| 284 | return Err(CrimError::parse( | ||
| 285 | *end_sym.start(), | ||
| 286 | format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) | ||
| 287 | )); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | ctx.next(); | ||
| 293 | |||
| 294 | Ok(tb) | ||
| 295 | } | ||
| 296 | |||
| 297 | fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult<TagBuilder<'a>> { | ||
| 298 | if base.is_unary() { | ||
| 299 | return Ok(base); | ||
| 300 | } | ||
| 301 | |||
| 302 | loop { | ||
| 303 | if ctx.cur().is_none() { | ||
| 304 | return Err(CrimError::eos(format!("close tag for {:?}", base.name()).as_str())); | ||
| 305 | } | ||
| 306 | match ctx.cur().unwrap().symbol() { | ||
| 307 | SymbolType::Text(s) => { | ||
| 308 | base.add_child(Entry::Token(Token::Text(s.to_string()))); | ||
| 309 | ctx.next(); | ||
| 310 | } | ||
| 311 | SymbolType::StartFlat | SymbolType::StartPoint => { | ||
| 312 | let tb = self.parse_tag( ctx )?; | ||
| 313 | |||
| 314 | match tb.tag_type() { | ||
| 315 | TagType::MultinaryOpen => { | ||
| 316 | base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); | ||
| 317 | } | ||
| 318 | TagType::MultinaryMid => { | ||
| 319 | base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); | ||
| 320 | return Ok(base); | ||
| 321 | } | ||
| 322 | TagType::MultinaryClose => { | ||
| 323 | if (tb.is_name(&SymbolType::If) && | ||
| 324 | (base.is_name(&SymbolType::ElIf) || | ||
| 325 | base.is_name(&SymbolType::Else))) || | ||
| 326 | tb.is_name(base.name().unwrap().symbol()) { | ||
| 327 | return Ok(base); | ||
| 328 | } else { | ||
| 329 | return Err(CrimError::parse( | ||
| 330 | *tb.name().unwrap().start(), | ||
| 331 | format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol()) | ||
| 332 | )); | ||
| 333 | } | ||
| 334 | } | ||
| 335 | TagType::Unary => { | ||
| 336 | base.add_child(Entry::TagBuilder(tb)); | ||
| 337 | } | ||
| 338 | TagType::Unknown => { | ||
| 339 | return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else.")); | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | SymbolType::Error{..} => { | ||
| 344 | return self.lex_error( &ctx.cur().unwrap() ); | ||
| 345 | } | ||
| 346 | _ => { | ||
| 347 | return Err(CrimError::parse( | ||
| 348 | *ctx.cur().unwrap().start(), | ||
| 349 | format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), | ||
| 350 | )); | ||
| 351 | } | ||
| 352 | } | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | fn parse_tag_params(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { | ||
| 357 | loop { | ||
| 358 | if ctx.cur().is_none() { | ||
| 359 | return Err(CrimError::eos("tag parameters")); | ||
| 360 | } | ||
| 361 | match ctx.cur().unwrap().symbol() { | ||
| 362 | SymbolType::Literal(s) => { | ||
| 363 | tb.add_param( ParamValue::Literal(s.to_string()) ); | ||
| 364 | ctx.next(); | ||
| 365 | } | ||
| 366 | SymbolType::Token(_) | SymbolType::Sharp => { | ||
| 367 | if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { | ||
| 368 | break; | ||
| 369 | } | ||
| 370 | tb.add_param( self.parse_identifier( ctx )? ); | ||
| 371 | } | ||
| 372 | _ => { | ||
| 373 | break; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | } | ||
| 377 | Ok(()) | ||
| 378 | } | ||
| 379 | |||
| 380 | fn parse_identifier(&self, ctx: &mut Context ) -> CrimResult<ParamValue> { | ||
| 381 | let mut id = Identifier::new(); | ||
| 382 | |||
| 383 | if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? { | ||
| 384 | id.push( IdentifierValue::Root ); | ||
| 385 | ctx.next(); | ||
| 386 | } | ||
| 387 | loop { | ||
| 388 | if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { | ||
| 389 | if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { | ||
| 390 | id.push( IdentifierValue::Name(s.to_string()) ); | ||
| 391 | } | ||
| 392 | } else { | ||
| 393 | return Err(CrimError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); | ||
| 394 | } | ||
| 395 | |||
| 396 | if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { | ||
| 397 | break; | ||
| 398 | } | ||
| 399 | ctx.next(); | ||
| 400 | } | ||
| 401 | |||
| 402 | Ok(ParamValue::Identifier(id)) | ||
| 403 | } | ||
| 404 | |||
| 405 | fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { | ||
| 406 | loop { | ||
| 407 | if ctx.cur().is_none() { | ||
| 408 | return Err(CrimError::eos("tag properties")); | ||
| 409 | } | ||
| 410 | if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { | ||
| 411 | if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) { | ||
| 412 | ctx.next(); | ||
| 413 | if let Some(sym) = ctx.next() && | ||
| 414 | let SymbolType::Literal(lv) = sym.symbol() { | ||
| 415 | tb.add_prop( s.to_string(), lv.to_string() ); | ||
| 416 | } else { | ||
| 417 | return Err(CrimError::parse(Position::none(),"Expected quoted literal string".to_string())); | ||
| 418 | } | ||
| 419 | } else { | ||
| 420 | break; | ||
| 421 | } | ||
| 422 | } else { | ||
| 423 | break; | ||
| 424 | } | ||
| 425 | ctx.next(); | ||
| 426 | } | ||
| 427 | Ok(()) | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | #[derive(PartialEq,Copy,Clone,Debug)] | ||
| 432 | enum TagType { | ||
| 433 | Unknown, | ||
| 434 | Unary, | ||
| 435 | MultinaryOpen, | ||
| 436 | MultinaryMid, | ||
| 437 | MultinaryClose, | ||
| 438 | } | ||
| 439 | |||
| 440 | #[derive(Debug)] | ||
| 441 | enum Entry<'a>{ | ||
| 442 | Token(Token), | ||
| 443 | TagBuilder(TagBuilder<'a>), | ||
| 444 | } | ||
| 445 | |||
| 446 | type EntryList<'a> = Vec<Entry<'a>>; | ||
| 447 | |||
| 448 | trait EntryListConverter { | ||
| 449 | fn has_outputs(&self) -> bool; | ||
| 450 | fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult<Vec<Output>>; | ||
| 451 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>>; | ||
| 452 | } | ||
| 453 | |||
| 454 | impl<'a> EntryListConverter for EntryList<'a> { | ||
| 455 | fn has_outputs(&self) -> bool { | ||
| 456 | self.iter().any(|e| | ||
| 457 | if let Entry::TagBuilder(tb) = e | ||
| 458 | && tb.is_name(&SymbolType::Output) { | ||
| 459 | true | ||
| 460 | } else { | ||
| 461 | false | ||
| 462 | } | ||
| 463 | ) | ||
| 464 | } | ||
| 465 | |||
| 466 | fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult<Vec<Output>> { | ||
| 467 | let mut outputs = Vec::new(); | ||
| 468 | |||
| 469 | if self.has_outputs() { | ||
| 470 | // We have an explicit output, we cant have anything else | ||
| 471 | for e in self.drain(..) { | ||
| 472 | let tb = if let Entry::TagBuilder(tb) = e { | ||
| 473 | tb | ||
| 474 | } else { | ||
| 475 | continue; | ||
| 476 | }; | ||
| 477 | if let BuildResult::Output(out) = tb.build(ctx)? { | ||
| 478 | outputs.push( out ); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | } else { | ||
| 482 | // No outputs, so we create one implicit output | ||
| 483 | outputs.push(Output { | ||
| 484 | name: "content".into(), | ||
| 485 | code: self.to_tokens(ctx)? | ||
| 486 | }); | ||
| 487 | } | ||
| 488 | |||
| 489 | Ok(outputs) | ||
| 490 | } | ||
| 491 | |||
| 492 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>> { | ||
| 493 | let mut tokens : Vec<Token> = Vec::new(); | ||
| 494 | for e in self.drain(..) { | ||
| 495 | match e { | ||
| 496 | Entry::Token(token) => { | ||
| 497 | tokens.push( token ); | ||
| 498 | } | ||
| 499 | Entry::TagBuilder(tb) => { | ||
| 500 | if let BuildResult::Token(token) = tb.build(ctx)? { | ||
| 501 | tokens.push( token ); | ||
| 502 | } else { | ||
| 503 | return Err(CrimError::broken( Position::none(), "Non-token result built.")); | ||
| 504 | } | ||
| 505 | } | ||
| 506 | } | ||
| 507 | } | ||
| 508 | Ok(tokens) | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | #[derive(Debug)] | ||
| 513 | struct TagBuilder<'a> { | ||
| 514 | name: Option<Symbol<'a>>, | ||
| 515 | params: Vec<ParamValue>, | ||
| 516 | props: Properties, | ||
| 517 | children: Vec<Entry<'a>>, | ||
| 518 | tag_type: TagType, | ||
| 519 | start: Symbol<'a>, | ||
| 520 | chain: Vec<Entry<'a>>, | ||
| 521 | } | ||
| 522 | |||
| 523 | #[derive(Debug)] | ||
| 524 | enum ParamValue { | ||
| 525 | Literal(String), | ||
| 526 | Identifier(Identifier), | ||
| 527 | } | ||
| 528 | |||
| 529 | enum BuildResult { | ||
| 530 | View(View), | ||
| 531 | Output(Output), | ||
| 532 | Token(Token), | ||
| 533 | } | ||
| 534 | |||
| 535 | impl<'a> TagBuilder<'a> { | ||
| 536 | pub fn new(start: &Symbol<'a>) -> TagBuilder<'a> { | ||
| 537 | TagBuilder { | ||
| 538 | name: None, | ||
| 539 | params: Vec::new(), | ||
| 540 | props: Properties::new(), | ||
| 541 | children: Vec::new(), | ||
| 542 | tag_type: TagType::Unknown, | ||
| 543 | start: start.clone(), | ||
| 544 | chain: Vec::new(), | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { | ||
| 549 | if *self.start.symbol() == SymbolType::StartFlat { | ||
| 550 | if *end.symbol() == SymbolType::EndFlat { | ||
| 551 | self.tag_type = TagType::Unary; | ||
| 552 | } else if *end.symbol() == SymbolType::EndPoint { | ||
| 553 | self.tag_type = TagType::MultinaryOpen; | ||
| 554 | } else { | ||
| 555 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); | ||
| 556 | } | ||
| 557 | } else if *self.start.symbol() == SymbolType::StartPoint { | ||
| 558 | if *end.symbol() == SymbolType::EndFlat { | ||
| 559 | self.tag_type = TagType::MultinaryClose; | ||
| 560 | } else if *end.symbol() == SymbolType::EndPoint { | ||
| 561 | self.tag_type = TagType::MultinaryMid; | ||
| 562 | } else { | ||
| 563 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); | ||
| 564 | } | ||
| 565 | } else { | ||
| 566 | return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); | ||
| 567 | } | ||
| 568 | Ok(()) | ||
| 569 | } | ||
| 570 | |||
| 571 | pub fn start_pos(&self) -> Position { | ||
| 572 | *self.start.start() | ||
| 573 | } | ||
| 574 | |||
| 575 | pub fn is_unary(&self) -> bool { | ||
| 576 | self.tag_type == TagType::Unary | ||
| 577 | } | ||
| 578 | /* | ||
| 579 | pub fn is_multinary_open(&self) -> bool { | ||
| 580 | self.tag_type == TagType::MultinaryOpen | ||
| 581 | } | ||
| 582 | */ | ||
| 583 | pub fn can_have_params(&self) -> bool { | ||
| 584 | match self.name.unwrap().symbol() { | ||
| 585 | SymbolType::View | SymbolType::Output | SymbolType::Loop => true, | ||
| 586 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | ||
| 587 | SymbolType::Else => false, | ||
| 588 | _ => false, | ||
| 589 | } | ||
| 590 | } | ||
| 591 | |||
| 592 | pub fn can_have_expr(&self) -> bool { | ||
| 593 | match self.name.unwrap().symbol() { | ||
| 594 | SymbolType::View | SymbolType::Output | SymbolType::Loop => false, | ||
| 595 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | ||
| 596 | SymbolType::Else => true, | ||
| 597 | _ => false, | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | pub fn can_have_props(&self) -> bool { | ||
| 602 | true | ||
| 603 | } | ||
| 604 | /* | ||
| 605 | pub fn can_have_children(&self) -> bool { | ||
| 606 | if self.tag_type == TagType::MultinaryOpen || | ||
| 607 | self.tag_type == TagType::MultinaryMid { | ||
| 608 | true | ||
| 609 | } else { | ||
| 610 | false | ||
| 611 | } | ||
| 612 | } | ||
| 613 | */ | ||
| 614 | pub fn is_name(&self, name: &SymbolType<'a>) -> bool { | ||
| 615 | if let Some(a) = self.name { | ||
| 616 | a.symbol() == name | ||
| 617 | } else { | ||
| 618 | false | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | pub fn name(&self) -> Option<Symbol<'a>> { | ||
| 623 | self.name | ||
| 624 | } | ||
| 625 | /* | ||
| 626 | pub fn params(&self) -> &Vec<ParamValue> { | ||
| 627 | &self.params | ||
| 628 | } | ||
| 629 | */ | ||
| 630 | pub fn set_name(&mut self, name: Symbol<'a>) { | ||
| 631 | self.name = Some(name); | ||
| 632 | } | ||
| 633 | |||
| 634 | pub fn add_param(&mut self, param: ParamValue) { | ||
| 635 | self.params.push( param ); | ||
| 636 | } | ||
| 637 | |||
| 638 | pub fn add_prop(&mut self, key: String, value: String) { | ||
| 639 | self.props.insert( key, value ); | ||
| 640 | } | ||
| 641 | |||
| 642 | pub fn add_child(&mut self, tb: Entry<'a>) { | ||
| 643 | self.children.push( tb ); | ||
| 644 | } | ||
| 645 | /* | ||
| 646 | pub fn append_children(&mut self, children: &mut Vec::<Entry<'a>>) { | ||
| 647 | self.children.append( children ); | ||
| 648 | } | ||
| 649 | */ | ||
| 650 | pub fn add_chain(&mut self, tb: Entry<'a>) { | ||
| 651 | self.chain.push( tb ); | ||
| 652 | } | ||
| 653 | /* | ||
| 654 | pub fn set_type(&mut self, tag_type: TagType) { | ||
| 655 | self.tag_type = tag_type; | ||
| 656 | } | ||
| 657 | */ | ||
| 658 | pub fn tag_type(&self) -> TagType { | ||
| 659 | self.tag_type | ||
| 660 | } | ||
| 661 | |||
| 662 | pub fn build(mut self, ctx: &mut Context ) -> CrimResult<BuildResult> { | ||
| 663 | if let Some(sym) = self.name { | ||
| 664 | match sym.symbol() { | ||
| 665 | SymbolType::View => { | ||
| 666 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 667 | s.to_string() | ||
| 668 | } else { | ||
| 669 | return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string())); | ||
| 670 | }; | ||
| 671 | Ok(BuildResult::View(View { | ||
| 672 | name: name, | ||
| 673 | source: ctx.source(), | ||
| 674 | outputs: self.children.to_outputs(ctx)?, | ||
| 675 | //theme: Option<String> | ||
| 676 | layout: self.props.get("layout").cloned(), | ||
| 677 | })) | ||
| 678 | } | ||
| 679 | SymbolType::Output => { | ||
| 680 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 681 | s.to_string() | ||
| 682 | } else { | ||
| 683 | return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into())); | ||
| 684 | }; | ||
| 685 | Ok(BuildResult::Output(Output { | ||
| 686 | name: name, | ||
| 687 | code: self.children.to_tokens(ctx)?, | ||
| 688 | })) | ||
| 689 | } | ||
| 690 | SymbolType::Loop => { | ||
| 691 | let id = if let ParamValue::Identifier(id) | ||
| 692 | = self.params.swap_remove(0) { | ||
| 693 | id | ||
| 694 | } else { | ||
| 695 | return Err(CrimError::parse( | ||
| 696 | Position::none(), | ||
| 697 | "Identifier for loop variable name.".into()) | ||
| 698 | ); | ||
| 699 | }; | ||
| 700 | Ok(BuildResult::Token( | ||
| 701 | Token::Loop(id, self.children.to_tokens(ctx)?) | ||
| 702 | )) | ||
| 703 | } | ||
| 704 | SymbolType::Show => { | ||
| 705 | let id = if let ParamValue::Identifier(id) | ||
| 706 | = self.params.swap_remove(0) { | ||
| 707 | id | ||
| 708 | } else { | ||
| 709 | return Err(CrimError::parse( | ||
| 710 | Position::none(), | ||
| 711 | "Identifier for show variable name.".into()) | ||
| 712 | ); | ||
| 713 | }; | ||
| 714 | Ok(BuildResult::Token(Token::Show(id, self.props))) | ||
| 715 | } | ||
| 716 | SymbolType::If => { | ||
| 717 | let id = if let ParamValue::Identifier(id) | ||
| 718 | = self.params.swap_remove(0) { | ||
| 719 | id | ||
| 720 | } else { | ||
| 721 | return Err(CrimError::parse( | ||
| 722 | Position::none(), | ||
| 723 | "Identifier for if variable name.".into()) | ||
| 724 | ); | ||
| 725 | }; | ||
| 726 | |||
| 727 | let is_else = if self.chain.len() == 1 && | ||
| 728 | let Entry::TagBuilder(tb) = &self.chain[0] && | ||
| 729 | tb.is_name(&SymbolType::Else) && | ||
| 730 | tb.params.len() == 0 { | ||
| 731 | true | ||
| 732 | } else { | ||
| 733 | false | ||
| 734 | }; | ||
| 735 | |||
| 736 | if is_else && | ||
| 737 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 738 | self.chain.clear(); | ||
| 739 | self.chain.append(&mut tb.children); | ||
| 740 | } | ||
| 741 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 742 | } | ||
| 743 | SymbolType::ElIf => { | ||
| 744 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { | ||
| 745 | id | ||
| 746 | } else { | ||
| 747 | return Err(CrimError::parse( | ||
| 748 | Position::none(), | ||
| 749 | "Identifier for elif variable name.".into()) | ||
| 750 | ); | ||
| 751 | }; | ||
| 752 | |||
| 753 | // this is copied from if, since they're the same this | ||
| 754 | // should be encapsulated and moved into a function... | ||
| 755 | // ...but I can't do that right now. | ||
| 756 | let is_else = if self.chain.len() == 1 && | ||
| 757 | let Entry::TagBuilder(tb) = &self.chain[0] && | ||
| 758 | tb.is_name(&SymbolType::Else) && | ||
| 759 | tb.params.len() == 0 { | ||
| 760 | true | ||
| 761 | } else { | ||
| 762 | false | ||
| 763 | }; | ||
| 764 | |||
| 765 | if is_else && | ||
| 766 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 767 | self.chain.clear(); | ||
| 768 | self.chain.append(&mut tb.children); | ||
| 769 | } | ||
| 770 | |||
| 771 | |||
| 772 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 773 | } | ||
| 774 | SymbolType::Else => { | ||
| 775 | Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 776 | } | ||
| 777 | _ => { | ||
| 778 | Err(CrimError::parse( self.start_pos(), "Bad tag type".into())) | ||
| 779 | } | ||
| 780 | } | ||
| 781 | } else { | ||
| 782 | Err(CrimError::parse(self.start_pos(), "Bad tag type".into())) | ||
| 783 | } | ||
| 784 | } | ||
| 785 | } | ||
