From 11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Fri, 5 Jun 2026 11:28:07 -0700 Subject: Started on the parser. Thinking about some tweaks. - Creating a LookaheadIterator as a general class that could be used. - Wrap symbols in a symbol struct that tracks symbol start and end position for error reporting. - Make a struct for position in a file as well. --- src/lexer.rs | 14 ++- src/lib.rs | 30 ++++--- src/parser.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+), 14 deletions(-) create mode 100644 src/parser.rs diff --git a/src/lexer.rs b/src/lexer.rs index e80e461..94825b2 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -2,7 +2,12 @@ use std::iter::Iterator; //use core::error::Error; use std::str::CharIndices; -#[derive(Debug)] +#[derive(Debug,Copy,Clone)] +pub enum ErrorType { + UnexpectedChar(char), +} + +#[derive(Debug,Copy,Clone)] pub enum Symbol<'a> { StartFlat, StartPoint, @@ -15,7 +20,7 @@ pub enum Symbol<'a> { Token(&'a str), Literal(&'a str), Text(&'a str), - Error{ line: u32, row: u32, what: String }, + Error{ line: u32, row: u32, what: ErrorType }, EOS, } @@ -104,7 +109,7 @@ impl<'a> Lexer<'a> { } } - fn error( &self, what: String ) -> Option> { + fn error( &self, what: ErrorType ) -> Option> { Some(Symbol::Error { line: self.line, row: self.row, @@ -162,6 +167,7 @@ impl<'a> Lexer<'a> { return self.parse_literal_str(); } Some('=') => { + self.next(); return Some(Symbol::Equals); } _ => {} @@ -186,7 +192,7 @@ impl<'a> Lexer<'a> { fn parse_literal_str(&mut self) -> Option> { if let Some(chr) = self.cur() && chr != '"' { - return self.error(format!("Expected '\"' but found '{}'", chr)); + return self.error( ErrorType::UnexpectedChar(chr) ); } let start = self.peek_index(); while self.next().is_some_and(|chr| chr != '"') { } diff --git a/src/lib.rs b/src/lib.rs index 16bef07..0f6d01d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,13 @@ use std::path::{PathBuf,Path}; use std::collections::HashMap; use core::error::Error; use std::time::SystemTime; +use std::fs::File; +use std::io::Read; mod lexer; +mod parser; -pub struct Tx { +pub struct Crimtag { fragments: HashMap, sources: Vec, } @@ -40,23 +43,22 @@ enum Token { Fragment(String,Properties,Vec), Section(String,Properties,Vec), Text(String), - } struct State { } -impl Tx { +impl Crimtag { pub fn new() -> Self { Self { fragments: HashMap::new(), - sources: vec![Frament::Static], + sources: vec![FragmentSource::Static], } } fn id_source(&mut self, src: &FragmentSource ) -> usize { for idx in 0..self.sources.len() { - if self.sources[idx] == src { + if self.sources[idx] == *src { return idx; } } @@ -77,16 +79,18 @@ impl Tx { SystemTime::now() } } else { - SystemTime.now() + SystemTime::now() }; - let mut file = File::open( path )?; + let mut p = parser::Parser::new(); + p.parse( self, &String::from_utf8( std::fs::read( path )? )? ); - let mut ll = lexer::Lexer( &String::from_utf8( file.read()? )? ); Ok(()) } pub fn load_static(&mut self, data: &str) -> Result<(),Box::> { + let mut p = parser::Parser::new(); + p.parse( self, &data ); Ok(()) } @@ -102,15 +106,21 @@ mod tests { #[test] fn lexing() { - let data = r#"Leading comment: [|fragment "basic"|>Hello there <|fragment|] Trailing text"#; + let data = r#"Leading comment: [|fragment "basic" something="yeup"|>Hello there <|fragment|] Trailing text"#; let ll = lexer::Lexer::new( &data ); for sym in ll { if let lexer::Symbol::Error{line,row,what} = sym { - println!("Error {}:{}: {}", line, row, what ); + println!("Error {}:{}: {:?}", line, row, what ); break; } else { println!("Symbol: {:?}", sym ); } } } + + #[test] + fn parsing() { + let mut ct = Crimtag::new(); + ct.load_static(r#"Here is a sample [|frament "index" theme="standard"|>Hi<|fragment|] That was fun!"#); + } } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..54b77ac --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,280 @@ +use crate::lexer::*; +use crate::*; + +use core::error::Error; +use std::fmt; + +#[derive(Debug)] +struct ParseError { + line: u32, + row: u32, + what: String, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error parsing input: {}", "yeah") + } +} + +impl Error for ParseError { } + +struct Context<'a> { + cur: [Option>;2], + icur: usize, + ll: Lexer<'a>, +} + +impl<'a> Context<'a> { + pub fn new( mut ll: Lexer<'a> ) -> Self { + let cur = [ll.next(), ll.next()]; + Self { + cur, + icur: 0, + ll + } + } + + 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 struct Parser { +} + +/** + * input: input tag + * | input tag_pair + * | input text + * | + * ; + * + * tag: '[|' 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: 'fragment' 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: 'fragment' literal props + * | 'section' literal + * | 'output' literal + * ; + * + * literal: '"' [^"]* '"' + * ; + * + * props: props token '=' literal + * | + * ; + */ +impl Parser { + pub fn new() -> Self { + Self { + } + } + + fn lex_error(&self, error: &Symbol) -> Result<(),Box> { + if let Symbol::Error{line,row,what} = error { + Err(Box::new(ParseError { + line: *line, + row: *row, + what: format!("What: {:?}", *what) + })) + } else { + Err(Box::new(ParseError { + line: 0, row: 0, what: "Not an error?".to_string() + })) + } + } + + pub fn parse(&mut self, crim: &mut Crimtag, src: &str ) -> Result<(),Box> { + let mut ll = Lexer::new( src ); + let mut ctx = Context::new( ll ); + + // Parse the root of the file, the input context + self.p_input( &mut ctx )?; + + Ok(()) + } + + fn p_input(&mut self, ctx: &mut Context ) -> Result<(), Box> { + loop { + if ctx.cur().is_none() { + break; + } + match ctx.cur() { + Some(Symbol::Text(_)) => { /* Skip top level text */ } + Some(Symbol::StartFlat) => { + self.parse_tag( ctx ); + } + Some(Symbol::Error{..}) => { + return self.lex_error( &ctx.cur().unwrap() ); + } + _ => { + + } + } + ctx.next(); + } + Ok(()) + } + + fn parse_tag(&mut self, ctx: &mut Context) -> Result<(), Box> { + let mut tb = TagBuilder::new(); + + let start_sym = ctx.cur().unwrap(); + + if let Some(Symbol::Token(s)) = ctx.next() { + tb.set_name( s.to_string() ); + } else { + return Err(Box::new(ParseError{line: 0, row: 0, what: "Unexpecetd symbol".to_string()})); + } + + self.parse_tag_params( ctx, &mut tb )?; + self.parse_tag_props( ctx, &mut tb )?; + + Ok(()) + } + + fn parse_tag_params(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box> { + loop { + if let Some(Symbol::Token(s)) = ctx.cur() { + if matches!(ctx.peek(), Some(Symbol::Equals)) { + // Done with parameters + break; + } + else { + tb.add_param( s.to_string() ); + } + } + ctx.next(); + } + Ok(()) + } + + fn parse_tag_props(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box> { + loop { + if let Some(Symbol::Token(s)) = ctx.cur() { + if matches!(ctx.peek(), Some(Symbol::Equals)) { + ctx.next(); + if let Some(Symbol::Literal(lv)) = ctx.next() { + tb.add_prop( s.to_string(), lv.to_string() ); + } else { + return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected quoted literal string".to_string()})); + } + } else { + return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected = ".to_string()})); + } + } else { + break; + } + ctx.next(); + } + Ok(()) + } +} + +enum TagType { + Unknown, + Unary, + BinaryOpen, + BinaryClose, +} + +struct TagBuilder { + name: String, + params: Vec, + props: Vec<(String,String)>, + tag_type: TagType, +} + +impl TagBuilder { + pub fn new() -> TagBuilder { + TagBuilder { + name: String::new(), + params: Vec::new(), + props: Vec::new(), + tag_type: TagType::Unknown, + } + } + + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + pub fn add_param(&mut self, param: String) { + self.params.push( param ); + } + + pub fn add_prop(&mut self, key: String, value: String) { + self.props.push( (key, value) ); + } + + pub fn set_type(&mut self, tag_type: TagType) { + self.tag_type = tag_type; + } +} -- cgit v1.2.3