From 062048ef6e3e060bcf841ea2ec92e026f09d8da0 Mon Sep 17 00:00:00 2001 From: eichlan Date: Tue, 23 Jun 2026 15:31:51 -0700 Subject: Roorganized into workspace and packages. This is to accomidate a new proc_macro package, which can't have anything else in it. --- crimtag/src/parser.rs | 785 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 crimtag/src/parser.rs (limited to 'crimtag/src/parser.rs') 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 @@ +use crate::lexer::*; +use crate::*; + +struct Context<'a> { + cur: [Option>;2], + icur: usize, + ll: Lexer<'a>, + source: usize, +} + +impl<'a> Context<'a> { + pub fn new( mut ll: Lexer<'a>, source: usize ) -> Self { + let cur = [ll.next(), ll.next()]; + //println!(" - cur: {:?}, peek: {:?}", cur[0], cur[1] ); + Self { + cur, + icur: 0, + ll, + source, + } + } + + pub fn next(&mut self) -> Option> { + self.cur[self.icur] = self.ll.next(); + self.icur = (self.icur+1)%2; + //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); + self.cur[self.icur] + } + + pub fn cur(&self) -> Option> { + self.cur[self.icur] + } + + pub fn peek(&self) -> Option> { + self.cur[(self.icur+1)%2] + } + + /* + pub fn check_unwrapped bool>(&self, context: &str, f: T) -> CrimResult { + if self.cur().is_none() || self.peek().is_none() { + Err(CrimError::eos( context )) + } else { + Ok(f( self.cur().unwrap().symbol(), self.peek().unwrap().symbol())) + } + } + */ + + pub fn source(&self) -> usize { + self.source + } +} + +trait SymbolHelper { + fn is bool>(&self, context: &str, f: T ) -> CrimResult; + #[allow(dead_code)] + fn is_valid_tag_name(&self) -> CrimResult; +} + +impl<'a> SymbolHelper for Option> { + fn is bool>(&self, context: &str, f: T ) -> CrimResult { + if let Some(st) = self { + Ok(f( &st.symbol() )) + } else { + Err(CrimError::eos( context )) + } + } + + fn is_valid_tag_name(&self) -> CrimResult { + if let Some(st) = self { + Ok(match st.symbol() { + SymbolType::Token(_) | + SymbolType::View | + SymbolType::Output | + SymbolType::Loop | + SymbolType::If | + SymbolType::ElIf | + SymbolType::Else | + SymbolType::Show => true, + _ => false + }) + } else { + Err(CrimError::parse( + Position::none(), + "Unexpeceted end of stream.".to_string() + )) + } + } +} + +pub struct Parser { +} + +/** + * input: input complete_tag + * | input text + * | + * ; + * + * tag: unary_tag + * | multinary_open_tag + * | multinary_mid_tag + * | multinary_close_tag + * ; + * + * unary_tag: '[|' tag_guts '|]' + * ; + * + * | '[|' tag_guts '|>' + * | '<|' tag_guts '|>' + * | '<|' tag_guts '|]' + * ; + * + * tag_pair: open_tag tag_body close_tag + * ; + * + * tag_body: tag_body tag + * | tag_body tag_pair + * | tag_body text + * | + * ; + * + * open_tag: '[|' tag_guts '|>' + * ; + * + * close_tag: '<|' token '|]' + * ; + * + * tag_guts: 'view' literal props + * | 'section' literal + * | 'output' literal + * ; + * + * literal: '"' [^"]* '"' + * ; + * + * props: props token '=' literal + * | + * ; + * + * disambiguate (tm): + * + * input: input tag + * | input text + * | + * ; + * + * tag: open_tag_base unary_tag + * | open_tag_base binary_tag + * ; + * + * open_tag_base: '[|' tag_guts + * ; + * + * unary_tag: '|]' + * ; + * + * binary_tag: '|>' tag_body close_tag + * ; + * + * tag_body: tag_body tag + * | tag_body text + * | + * ; + * + * close_tag: '<|' token '|]' + * ; + * + * tag_guts: 'view' literal props + * | 'section' literal + * | 'output' literal + * ; + * + * literal: '"' [^"]* '"' + * ; + * + * props: props token '=' literal + * | + * ; + */ +impl Parser { + pub fn new() -> Self { + Self { + } + } + + fn lex_error(&self, error: &Symbol) -> CrimResult { + if let SymbolType::Error{what} = error.symbol() { + Err(CrimError::parse( *error.start(), + format!("What: {:?}", *what) + )) + } else { + Err(CrimError::parse( Position::none(), "Not an error?".into())) + } + } + + pub fn parse(&self, src: &str, source: usize ) -> CrimResult> { + let ll = Lexer::new( src ); + let mut ctx = Context::new( ll, source ); + + // Parse the root of the file, the input context + self.p_input( &mut ctx ) + } + + fn p_input(&self, ctx: &mut Context ) -> CrimResult> { + let mut tags = Vec::new(); + loop { + if ctx.cur().is_none() { + break; + } + match ctx.cur().unwrap().symbol() { + SymbolType::Text(_) => { /* Skip top level text */ } + SymbolType::StartFlat => { + let tb = self.parse_tag( ctx )?; + let tb = self.parse_tag_set( ctx, tb )?; + tags.push( tb ); + } + SymbolType::Error{..} => { + self.lex_error( &ctx.cur().unwrap() )?; + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() ) + )); + } + } + ctx.next(); + } + + let mut views = Vec::new(); + for tag in tags { + let start_pos = tag.start_pos(); + let name = tag.name().clone(); + if tag.is_name(&SymbolType::View) && + let BuildResult::View(view) = tag.build( ctx )? { + views.push( view ); + } else { + return Err(CrimError::parse( + start_pos, + format!("Expected view at root, found {:?}", name ) + )); + } + } + Ok(views) + } + + fn parse_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult> { + let start_sym = ctx.cur().unwrap(); + let mut tb = TagBuilder::new(&start_sym); + + if ctx.next().is_some() { + match ctx.cur().unwrap().symbol() { + SymbolType::View | SymbolType::Show | SymbolType::Loop | + SymbolType::If | SymbolType::ElIf | SymbolType::Else | + SymbolType::Output => { + let name_sym = ctx.cur().unwrap(); + ctx.next(); + tb.set_name( name_sym ); + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + "Unexpected symbol".to_string() + )); + } + } + } else { + return Err(CrimError::eos("tag type")); + } + + if tb.can_have_params() || tb.can_have_expr() { + self.parse_tag_params( ctx, &mut tb )?; + } + if tb.can_have_props() { + self.parse_tag_props( ctx, &mut tb )?; + } + + if let Some(end_sym) = ctx.cur() { + match end_sym.symbol() { + SymbolType::EndPoint | SymbolType::EndFlat => { + tb.set_end( &end_sym )?; + } + _ => { + return Err(CrimError::parse( + *end_sym.start(), + format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) + )); + } + } + } + + ctx.next(); + + Ok(tb) + } + + fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult> { + if base.is_unary() { + return Ok(base); + } + + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos(format!("close tag for {:?}", base.name()).as_str())); + } + match ctx.cur().unwrap().symbol() { + SymbolType::Text(s) => { + base.add_child(Entry::Token(Token::Text(s.to_string()))); + ctx.next(); + } + SymbolType::StartFlat | SymbolType::StartPoint => { + let tb = self.parse_tag( ctx )?; + + match tb.tag_type() { + TagType::MultinaryOpen => { + base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); + } + TagType::MultinaryMid => { + base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); + return Ok(base); + } + TagType::MultinaryClose => { + if (tb.is_name(&SymbolType::If) && + (base.is_name(&SymbolType::ElIf) || + base.is_name(&SymbolType::Else))) || + tb.is_name(base.name().unwrap().symbol()) { + return Ok(base); + } else { + return Err(CrimError::parse( + *tb.name().unwrap().start(), + format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol()) + )); + } + } + TagType::Unary => { + base.add_child(Entry::TagBuilder(tb)); + } + TagType::Unknown => { + return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else.")); + } + } + } + SymbolType::Error{..} => { + return self.lex_error( &ctx.cur().unwrap() ); + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), + )); + } + } + } + } + + fn parse_tag_params(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos("tag parameters")); + } + match ctx.cur().unwrap().symbol() { + SymbolType::Literal(s) => { + tb.add_param( ParamValue::Literal(s.to_string()) ); + ctx.next(); + } + SymbolType::Token(_) | SymbolType::Sharp => { + if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { + break; + } + tb.add_param( self.parse_identifier( ctx )? ); + } + _ => { + break; + } + } + } + Ok(()) + } + + fn parse_identifier(&self, ctx: &mut Context ) -> CrimResult { + let mut id = Identifier::new(); + + if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? { + id.push( IdentifierValue::Root ); + ctx.next(); + } + 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(CrimError::parse( 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 ) -> CrimResult<()> { + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos("tag properties")); + } + if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { + if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) { + ctx.next(); + if let Some(sym) = ctx.next() && + let SymbolType::Literal(lv) = sym.symbol() { + tb.add_prop( s.to_string(), lv.to_string() ); + } else { + return Err(CrimError::parse(Position::none(),"Expected quoted literal string".to_string())); + } + } else { + break; + } + } else { + break; + } + ctx.next(); + } + Ok(()) + } +} + +#[derive(PartialEq,Copy,Clone,Debug)] +enum TagType { + Unknown, + Unary, + MultinaryOpen, + MultinaryMid, + MultinaryClose, +} + +#[derive(Debug)] +enum Entry<'a>{ + Token(Token), + TagBuilder(TagBuilder<'a>), +} + +type EntryList<'a> = Vec>; + +trait EntryListConverter { + fn has_outputs(&self) -> bool; + fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult>; + fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult>; +} + +impl<'a> EntryListConverter for EntryList<'a> { + fn has_outputs(&self) -> bool { + self.iter().any(|e| + if let Entry::TagBuilder(tb) = e + && tb.is_name(&SymbolType::Output) { + true + } else { + false + } + ) + } + + fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult> { + let mut outputs = Vec::new(); + + if self.has_outputs() { + // We have an explicit output, we cant have anything else + for e in self.drain(..) { + let tb = if let Entry::TagBuilder(tb) = e { + tb + } else { + continue; + }; + if let BuildResult::Output(out) = tb.build(ctx)? { + outputs.push( out ); + } + } + } else { + // No outputs, so we create one implicit output + outputs.push(Output { + name: "content".into(), + code: self.to_tokens(ctx)? + }); + } + + Ok(outputs) + } + + fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult> { + let mut tokens : Vec = Vec::new(); + for e in self.drain(..) { + match e { + Entry::Token(token) => { + tokens.push( token ); + } + Entry::TagBuilder(tb) => { + if let BuildResult::Token(token) = tb.build(ctx)? { + tokens.push( token ); + } else { + return Err(CrimError::broken( Position::none(), "Non-token result built.")); + } + } + } + } + Ok(tokens) + } +} + +#[derive(Debug)] +struct TagBuilder<'a> { + name: Option>, + params: Vec, + props: Properties, + children: Vec>, + tag_type: TagType, + start: Symbol<'a>, + chain: Vec>, +} + +#[derive(Debug)] +enum ParamValue { + Literal(String), + Identifier(Identifier), +} + +enum BuildResult { + View(View), + Output(Output), + Token(Token), +} + +impl<'a> TagBuilder<'a> { + pub fn new(start: &Symbol<'a>) -> TagBuilder<'a> { + TagBuilder { + name: None, + params: Vec::new(), + props: Properties::new(), + children: Vec::new(), + tag_type: TagType::Unknown, + start: start.clone(), + chain: Vec::new(), + } + } + + pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { + if *self.start.symbol() == SymbolType::StartFlat { + if *end.symbol() == SymbolType::EndFlat { + self.tag_type = TagType::Unary; + } else if *end.symbol() == SymbolType::EndPoint { + self.tag_type = TagType::MultinaryOpen; + } else { + return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); + } + } else if *self.start.symbol() == SymbolType::StartPoint { + if *end.symbol() == SymbolType::EndFlat { + self.tag_type = TagType::MultinaryClose; + } else if *end.symbol() == SymbolType::EndPoint { + self.tag_type = TagType::MultinaryMid; + } else { + return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); + } + } else { + return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); + } + Ok(()) + } + + pub fn start_pos(&self) -> Position { + *self.start.start() + } + + pub fn is_unary(&self) -> bool { + self.tag_type == TagType::Unary + } +/* + pub fn is_multinary_open(&self) -> bool { + self.tag_type == TagType::MultinaryOpen + } +*/ + pub fn can_have_params(&self) -> bool { + match self.name.unwrap().symbol() { + SymbolType::View | SymbolType::Output | SymbolType::Loop => true, + SymbolType::Show | SymbolType::If | SymbolType::ElIf | + SymbolType::Else => false, + _ => false, + } + } + + pub fn can_have_expr(&self) -> bool { + match self.name.unwrap().symbol() { + SymbolType::View | SymbolType::Output | SymbolType::Loop => false, + SymbolType::Show | SymbolType::If | SymbolType::ElIf | + SymbolType::Else => true, + _ => false, + } + } + + pub fn can_have_props(&self) -> bool { + true + } +/* + pub fn can_have_children(&self) -> bool { + if self.tag_type == TagType::MultinaryOpen || + self.tag_type == TagType::MultinaryMid { + true + } else { + false + } + } +*/ + pub fn is_name(&self, name: &SymbolType<'a>) -> bool { + if let Some(a) = self.name { + a.symbol() == name + } else { + false + } + } + + pub fn name(&self) -> Option> { + self.name + } +/* + pub fn params(&self) -> &Vec { + &self.params + } +*/ + pub fn set_name(&mut self, name: Symbol<'a>) { + self.name = Some(name); + } + + pub fn add_param(&mut self, param: ParamValue) { + self.params.push( param ); + } + + pub fn add_prop(&mut self, key: String, value: String) { + self.props.insert( key, value ); + } + + pub fn add_child(&mut self, tb: Entry<'a>) { + self.children.push( tb ); + } +/* + pub fn append_children(&mut self, children: &mut Vec::>) { + self.children.append( children ); + } +*/ + pub fn add_chain(&mut self, tb: Entry<'a>) { + self.chain.push( tb ); + } +/* + pub fn set_type(&mut self, tag_type: TagType) { + self.tag_type = tag_type; + } +*/ + pub fn tag_type(&self) -> TagType { + self.tag_type + } + + pub fn build(mut self, ctx: &mut Context ) -> CrimResult { + if let Some(sym) = self.name { + match sym.symbol() { + SymbolType::View => { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string())); + }; + Ok(BuildResult::View(View { + name: name, + source: ctx.source(), + outputs: self.children.to_outputs(ctx)?, + //theme: Option + layout: self.props.get("layout").cloned(), + })) + } + SymbolType::Output => { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into())); + }; + Ok(BuildResult::Output(Output { + name: name, + code: self.children.to_tokens(ctx)?, + })) + } + SymbolType::Loop => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for loop variable name.".into()) + ); + }; + Ok(BuildResult::Token( + Token::Loop(id, self.children.to_tokens(ctx)?) + )) + } + SymbolType::Show => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for show variable name.".into()) + ); + }; + Ok(BuildResult::Token(Token::Show(id, self.props))) + } + SymbolType::If => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for if variable name.".into()) + ); + }; + + let is_else = if self.chain.len() == 1 && + let Entry::TagBuilder(tb) = &self.chain[0] && + tb.is_name(&SymbolType::Else) && + tb.params.len() == 0 { + true + } else { + false + }; + + if is_else && + let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { + self.chain.clear(); + self.chain.append(&mut tb.children); + } + Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + SymbolType::ElIf => { + let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for elif variable name.".into()) + ); + }; + + // this is copied from if, since they're the same this + // should be encapsulated and moved into a function... + // ...but I can't do that right now. + let is_else = if self.chain.len() == 1 && + let Entry::TagBuilder(tb) = &self.chain[0] && + tb.is_name(&SymbolType::Else) && + tb.params.len() == 0 { + true + } else { + false + }; + + if is_else && + let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { + self.chain.clear(); + self.chain.append(&mut tb.children); + } + + + Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + SymbolType::Else => { + Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + _ => { + Err(CrimError::parse( self.start_pos(), "Bad tag type".into())) + } + } + } else { + Err(CrimError::parse(self.start_pos(), "Bad tag type".into())) + } + } +} -- cgit v1.2.3