diff options
| author | Mike Buland <mike@xagasoft.com> | 2026-06-05 11:28:07 -0700 |
|---|---|---|
| committer | Mike Buland <mike@xagasoft.com> | 2026-06-05 11:28:07 -0700 |
| commit | 11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b (patch) | |
| tree | c5d08d320c1cd280ce9ba1f59c0385808a036e67 | |
| parent | 414681bc8b8ccbff81c0878fbf3ff193f7f55e0f (diff) | |
| download | crimtag-11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b.tar.gz crimtag-11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b.tar.bz2 crimtag-11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b.tar.xz crimtag-11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b.zip | |
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.
| -rw-r--r-- | src/lexer.rs | 14 | ||||
| -rw-r--r-- | src/lib.rs | 30 | ||||
| -rw-r--r-- | src/parser.rs | 280 |
3 files changed, 310 insertions, 14 deletions
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; | |||
| 2 | //use core::error::Error; | 2 | //use core::error::Error; |
| 3 | use std::str::CharIndices; | 3 | use std::str::CharIndices; |
| 4 | 4 | ||
| 5 | #[derive(Debug)] | 5 | #[derive(Debug,Copy,Clone)] |
| 6 | pub enum ErrorType { | ||
| 7 | UnexpectedChar(char), | ||
| 8 | } | ||
| 9 | |||
| 10 | #[derive(Debug,Copy,Clone)] | ||
| 6 | pub enum Symbol<'a> { | 11 | pub enum Symbol<'a> { |
| 7 | StartFlat, | 12 | StartFlat, |
| 8 | StartPoint, | 13 | StartPoint, |
| @@ -15,7 +20,7 @@ pub enum Symbol<'a> { | |||
| 15 | Token(&'a str), | 20 | Token(&'a str), |
| 16 | Literal(&'a str), | 21 | Literal(&'a str), |
| 17 | Text(&'a str), | 22 | Text(&'a str), |
| 18 | Error{ line: u32, row: u32, what: String }, | 23 | Error{ line: u32, row: u32, what: ErrorType }, |
| 19 | EOS, | 24 | EOS, |
| 20 | } | 25 | } |
| 21 | 26 | ||
| @@ -104,7 +109,7 @@ impl<'a> Lexer<'a> { | |||
| 104 | } | 109 | } |
| 105 | } | 110 | } |
| 106 | 111 | ||
| 107 | fn error( &self, what: String ) -> Option<Symbol<'a>> { | 112 | fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> { |
| 108 | Some(Symbol::Error { | 113 | Some(Symbol::Error { |
| 109 | line: self.line, | 114 | line: self.line, |
| 110 | row: self.row, | 115 | row: self.row, |
| @@ -162,6 +167,7 @@ impl<'a> Lexer<'a> { | |||
| 162 | return self.parse_literal_str(); | 167 | return self.parse_literal_str(); |
| 163 | } | 168 | } |
| 164 | Some('=') => { | 169 | Some('=') => { |
| 170 | self.next(); | ||
| 165 | return Some(Symbol::Equals); | 171 | return Some(Symbol::Equals); |
| 166 | } | 172 | } |
| 167 | _ => {} | 173 | _ => {} |
| @@ -186,7 +192,7 @@ impl<'a> Lexer<'a> { | |||
| 186 | 192 | ||
| 187 | fn parse_literal_str(&mut self) -> Option<Symbol<'a>> { | 193 | fn parse_literal_str(&mut self) -> Option<Symbol<'a>> { |
| 188 | if let Some(chr) = self.cur() && chr != '"' { | 194 | if let Some(chr) = self.cur() && chr != '"' { |
| 189 | return self.error(format!("Expected '\"' but found '{}'", chr)); | 195 | return self.error( ErrorType::UnexpectedChar(chr) ); |
| 190 | } | 196 | } |
| 191 | let start = self.peek_index(); | 197 | let start = self.peek_index(); |
| 192 | while self.next().is_some_and(|chr| chr != '"') { } | 198 | while self.next().is_some_and(|chr| chr != '"') { } |
| @@ -2,10 +2,13 @@ use std::path::{PathBuf,Path}; | |||
| 2 | use std::collections::HashMap; | 2 | use std::collections::HashMap; |
| 3 | use core::error::Error; | 3 | use core::error::Error; |
| 4 | use std::time::SystemTime; | 4 | use std::time::SystemTime; |
| 5 | use std::fs::File; | ||
| 6 | use std::io::Read; | ||
| 5 | 7 | ||
| 6 | mod lexer; | 8 | mod lexer; |
| 9 | mod parser; | ||
| 7 | 10 | ||
| 8 | pub struct Tx { | 11 | pub struct Crimtag { |
| 9 | fragments: HashMap<String, Fragment>, | 12 | fragments: HashMap<String, Fragment>, |
| 10 | sources: Vec<FragmentSource>, | 13 | sources: Vec<FragmentSource>, |
| 11 | } | 14 | } |
| @@ -40,23 +43,22 @@ enum Token { | |||
| 40 | Fragment(String,Properties,Vec<Token>), | 43 | Fragment(String,Properties,Vec<Token>), |
| 41 | Section(String,Properties,Vec<Token>), | 44 | Section(String,Properties,Vec<Token>), |
| 42 | Text(String), | 45 | Text(String), |
| 43 | |||
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | struct State { | 48 | struct State { |
| 47 | } | 49 | } |
| 48 | 50 | ||
| 49 | impl Tx { | 51 | impl Crimtag { |
| 50 | pub fn new() -> Self { | 52 | pub fn new() -> Self { |
| 51 | Self { | 53 | Self { |
| 52 | fragments: HashMap::new(), | 54 | fragments: HashMap::new(), |
| 53 | sources: vec![Frament::Static], | 55 | sources: vec![FragmentSource::Static], |
| 54 | } | 56 | } |
| 55 | } | 57 | } |
| 56 | 58 | ||
| 57 | fn id_source(&mut self, src: &FragmentSource ) -> usize { | 59 | fn id_source(&mut self, src: &FragmentSource ) -> usize { |
| 58 | for idx in 0..self.sources.len() { | 60 | for idx in 0..self.sources.len() { |
| 59 | if self.sources[idx] == src { | 61 | if self.sources[idx] == *src { |
| 60 | return idx; | 62 | return idx; |
| 61 | } | 63 | } |
| 62 | } | 64 | } |
| @@ -77,16 +79,18 @@ impl Tx { | |||
| 77 | SystemTime::now() | 79 | SystemTime::now() |
| 78 | } | 80 | } |
| 79 | } else { | 81 | } else { |
| 80 | SystemTime.now() | 82 | SystemTime::now() |
| 81 | }; | 83 | }; |
| 82 | 84 | ||
| 83 | let mut file = File::open( path )?; | 85 | let mut p = parser::Parser::new(); |
| 86 | p.parse( self, &String::from_utf8( std::fs::read( path )? )? ); | ||
| 84 | 87 | ||
| 85 | let mut ll = lexer::Lexer( &String::from_utf8( file.read()? )? ); | ||
| 86 | Ok(()) | 88 | Ok(()) |
| 87 | } | 89 | } |
| 88 | 90 | ||
| 89 | pub fn load_static(&mut self, data: &str) -> Result<(),Box::<dyn Error>> { | 91 | pub fn load_static(&mut self, data: &str) -> Result<(),Box::<dyn Error>> { |
| 92 | let mut p = parser::Parser::new(); | ||
| 93 | p.parse( self, &data ); | ||
| 90 | 94 | ||
| 91 | Ok(()) | 95 | Ok(()) |
| 92 | } | 96 | } |
| @@ -102,15 +106,21 @@ mod tests { | |||
| 102 | 106 | ||
| 103 | #[test] | 107 | #[test] |
| 104 | fn lexing() { | 108 | fn lexing() { |
| 105 | let data = r#"Leading comment: [|fragment "basic"|>Hello there <|fragment|] Trailing text"#; | 109 | let data = r#"Leading comment: [|fragment "basic" something="yeup"|>Hello there <|fragment|] Trailing text"#; |
| 106 | let ll = lexer::Lexer::new( &data ); | 110 | let ll = lexer::Lexer::new( &data ); |
| 107 | for sym in ll { | 111 | for sym in ll { |
| 108 | if let lexer::Symbol::Error{line,row,what} = sym { | 112 | if let lexer::Symbol::Error{line,row,what} = sym { |
| 109 | println!("Error {}:{}: {}", line, row, what ); | 113 | println!("Error {}:{}: {:?}", line, row, what ); |
| 110 | break; | 114 | break; |
| 111 | } else { | 115 | } else { |
| 112 | println!("Symbol: {:?}", sym ); | 116 | println!("Symbol: {:?}", sym ); |
| 113 | } | 117 | } |
| 114 | } | 118 | } |
| 115 | } | 119 | } |
| 120 | |||
| 121 | #[test] | ||
| 122 | fn parsing() { | ||
| 123 | let mut ct = Crimtag::new(); | ||
| 124 | ct.load_static(r#"Here is a sample [|frament "index" theme="standard"|><html><body>Hi</body></html><|fragment|] That was fun!"#); | ||
| 125 | } | ||
| 116 | } | 126 | } |
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 @@ | |||
| 1 | use crate::lexer::*; | ||
| 2 | use crate::*; | ||
| 3 | |||
| 4 | use core::error::Error; | ||
| 5 | use std::fmt; | ||
| 6 | |||
| 7 | #[derive(Debug)] | ||
| 8 | struct ParseError { | ||
| 9 | line: u32, | ||
| 10 | row: u32, | ||
| 11 | what: String, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl fmt::Display for ParseError { | ||
| 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 16 | write!(f, "Error parsing input: {}", "yeah") | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | impl Error for ParseError { } | ||
| 21 | |||
| 22 | struct Context<'a> { | ||
| 23 | cur: [Option<Symbol<'a>>;2], | ||
| 24 | icur: usize, | ||
| 25 | ll: Lexer<'a>, | ||
| 26 | } | ||
| 27 | |||
| 28 | impl<'a> Context<'a> { | ||
| 29 | pub fn new( mut ll: Lexer<'a> ) -> Self { | ||
| 30 | let cur = [ll.next(), ll.next()]; | ||
| 31 | Self { | ||
| 32 | cur, | ||
| 33 | icur: 0, | ||
| 34 | ll | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | pub fn next(&mut self) -> Option<Symbol<'a>> { | ||
| 39 | self.cur[self.icur] = self.ll.next(); | ||
| 40 | self.icur = (self.icur+1)%2; | ||
| 41 | println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); | ||
| 42 | self.cur[self.icur] | ||
| 43 | } | ||
| 44 | |||
| 45 | pub fn cur(&self) -> Option<Symbol<'a>> { | ||
| 46 | self.cur[self.icur] | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn peek(&self) -> Option<Symbol<'a>> { | ||
| 50 | self.cur[(self.icur+1)%2] | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | pub struct Parser { | ||
| 55 | } | ||
| 56 | |||
| 57 | /** | ||
| 58 | * input: input tag | ||
| 59 | * | input tag_pair | ||
| 60 | * | input text | ||
| 61 | * | | ||
| 62 | * ; | ||
| 63 | * | ||
| 64 | * tag: '[|' tag_guts '|]' | ||
| 65 | * ; | ||
| 66 | * | ||
| 67 | * tag_pair: open_tag tag_body close_tag | ||
| 68 | * ; | ||
| 69 | * | ||
| 70 | * tag_body: tag_body tag | ||
| 71 | * | tag_body tag_pair | ||
| 72 | * | tag_body text | ||
| 73 | * | | ||
| 74 | * ; | ||
| 75 | * | ||
| 76 | * open_tag: '[|' tag_guts '|>' | ||
| 77 | * ; | ||
| 78 | * | ||
| 79 | * close_tag: '<|' token '|]' | ||
| 80 | * ; | ||
| 81 | * | ||
| 82 | * tag_guts: 'fragment' literal props | ||
| 83 | * | 'section' literal | ||
| 84 | * | 'output' literal | ||
| 85 | * ; | ||
| 86 | * | ||
| 87 | * literal: '"' [^"]* '"' | ||
| 88 | * ; | ||
| 89 | * | ||
| 90 | * props: props token '=' literal | ||
| 91 | * | | ||
| 92 | * ; | ||
| 93 | * | ||
| 94 | * disambiguate (tm): | ||
| 95 | * | ||
| 96 | * input: input tag | ||
| 97 | * | input text | ||
| 98 | * | | ||
| 99 | * ; | ||
| 100 | * | ||
| 101 | * tag: open_tag_base unary_tag | ||
| 102 | * | open_tag_base binary_tag | ||
| 103 | * ; | ||
| 104 | * | ||
| 105 | * open_tag_base: '[|' tag_guts | ||
| 106 | * ; | ||
| 107 | * | ||
| 108 | * unary_tag: '|]' | ||
| 109 | * ; | ||
| 110 | * | ||
| 111 | * binary_tag: '|>' tag_body close_tag | ||
| 112 | * ; | ||
| 113 | * | ||
| 114 | * tag_body: tag_body tag | ||
| 115 | * | tag_body text | ||
| 116 | * | | ||
| 117 | * ; | ||
| 118 | * | ||
| 119 | * close_tag: '<|' token '|]' | ||
| 120 | * ; | ||
| 121 | * | ||
| 122 | * tag_guts: 'fragment' literal props | ||
| 123 | * | 'section' literal | ||
| 124 | * | 'output' literal | ||
| 125 | * ; | ||
| 126 | * | ||
| 127 | * literal: '"' [^"]* '"' | ||
| 128 | * ; | ||
| 129 | * | ||
| 130 | * props: props token '=' literal | ||
| 131 | * | | ||
| 132 | * ; | ||
| 133 | */ | ||
| 134 | impl Parser { | ||
| 135 | pub fn new() -> Self { | ||
| 136 | Self { | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | fn lex_error(&self, error: &Symbol) -> Result<(),Box<dyn Error>> { | ||
| 141 | if let Symbol::Error{line,row,what} = error { | ||
| 142 | Err(Box::new(ParseError { | ||
| 143 | line: *line, | ||
| 144 | row: *row, | ||
| 145 | what: format!("What: {:?}", *what) | ||
| 146 | })) | ||
| 147 | } else { | ||
| 148 | Err(Box::new(ParseError { | ||
| 149 | line: 0, row: 0, what: "Not an error?".to_string() | ||
| 150 | })) | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | pub fn parse(&mut self, crim: &mut Crimtag, src: &str ) -> Result<(),Box<dyn Error>> { | ||
| 155 | let mut ll = Lexer::new( src ); | ||
| 156 | let mut ctx = Context::new( ll ); | ||
| 157 | |||
| 158 | // Parse the root of the file, the input context | ||
| 159 | self.p_input( &mut ctx )?; | ||
| 160 | |||
| 161 | Ok(()) | ||
| 162 | } | ||
| 163 | |||
| 164 | fn p_input(&mut self, ctx: &mut Context ) -> Result<(), Box<dyn Error>> { | ||
| 165 | loop { | ||
| 166 | if ctx.cur().is_none() { | ||
| 167 | break; | ||
| 168 | } | ||
| 169 | match ctx.cur() { | ||
| 170 | Some(Symbol::Text(_)) => { /* Skip top level text */ } | ||
| 171 | Some(Symbol::StartFlat) => { | ||
| 172 | self.parse_tag( ctx ); | ||
| 173 | } | ||
| 174 | Some(Symbol::Error{..}) => { | ||
| 175 | return self.lex_error( &ctx.cur().unwrap() ); | ||
| 176 | } | ||
| 177 | _ => { | ||
| 178 | |||
| 179 | } | ||
| 180 | } | ||
| 181 | ctx.next(); | ||
| 182 | } | ||
| 183 | Ok(()) | ||
| 184 | } | ||
| 185 | |||
| 186 | fn parse_tag(&mut self, ctx: &mut Context) -> Result<(), Box<dyn Error>> { | ||
| 187 | let mut tb = TagBuilder::new(); | ||
| 188 | |||
| 189 | let start_sym = ctx.cur().unwrap(); | ||
| 190 | |||
| 191 | if let Some(Symbol::Token(s)) = ctx.next() { | ||
| 192 | tb.set_name( s.to_string() ); | ||
| 193 | } else { | ||
| 194 | return Err(Box::new(ParseError{line: 0, row: 0, what: "Unexpecetd symbol".to_string()})); | ||
| 195 | } | ||
| 196 | |||
| 197 | self.parse_tag_params( ctx, &mut tb )?; | ||
| 198 | self.parse_tag_props( ctx, &mut tb )?; | ||
| 199 | |||
| 200 | Ok(()) | ||
| 201 | } | ||
| 202 | |||
| 203 | fn parse_tag_params(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> { | ||
| 204 | loop { | ||
| 205 | if let Some(Symbol::Token(s)) = ctx.cur() { | ||
| 206 | if matches!(ctx.peek(), Some(Symbol::Equals)) { | ||
| 207 | // Done with parameters | ||
| 208 | break; | ||
| 209 | } | ||
| 210 | else { | ||
| 211 | tb.add_param( s.to_string() ); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | ctx.next(); | ||
| 215 | } | ||
| 216 | Ok(()) | ||
| 217 | } | ||
| 218 | |||
| 219 | fn parse_tag_props(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> { | ||
| 220 | loop { | ||
| 221 | if let Some(Symbol::Token(s)) = ctx.cur() { | ||
| 222 | if matches!(ctx.peek(), Some(Symbol::Equals)) { | ||
| 223 | ctx.next(); | ||
| 224 | if let Some(Symbol::Literal(lv)) = ctx.next() { | ||
| 225 | tb.add_prop( s.to_string(), lv.to_string() ); | ||
| 226 | } else { | ||
| 227 | return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected quoted literal string".to_string()})); | ||
| 228 | } | ||
| 229 | } else { | ||
| 230 | return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected = ".to_string()})); | ||
| 231 | } | ||
| 232 | } else { | ||
| 233 | break; | ||
| 234 | } | ||
| 235 | ctx.next(); | ||
| 236 | } | ||
| 237 | Ok(()) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | enum TagType { | ||
| 242 | Unknown, | ||
| 243 | Unary, | ||
| 244 | BinaryOpen, | ||
| 245 | BinaryClose, | ||
| 246 | } | ||
| 247 | |||
| 248 | struct TagBuilder { | ||
| 249 | name: String, | ||
| 250 | params: Vec<String>, | ||
| 251 | props: Vec<(String,String)>, | ||
| 252 | tag_type: TagType, | ||
| 253 | } | ||
| 254 | |||
| 255 | impl TagBuilder { | ||
| 256 | pub fn new() -> TagBuilder { | ||
| 257 | TagBuilder { | ||
| 258 | name: String::new(), | ||
| 259 | params: Vec::new(), | ||
| 260 | props: Vec::new(), | ||
| 261 | tag_type: TagType::Unknown, | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | pub fn set_name(&mut self, name: String) { | ||
| 266 | self.name = name; | ||
| 267 | } | ||
| 268 | |||
| 269 | pub fn add_param(&mut self, param: String) { | ||
| 270 | self.params.push( param ); | ||
| 271 | } | ||
| 272 | |||
| 273 | pub fn add_prop(&mut self, key: String, value: String) { | ||
| 274 | self.props.push( (key, value) ); | ||
| 275 | } | ||
| 276 | |||
| 277 | pub fn set_type(&mut self, tag_type: TagType) { | ||
| 278 | self.tag_type = tag_type; | ||
| 279 | } | ||
| 280 | } | ||
