summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Buland <mike@xagasoft.com>2026-06-05 14:46:35 -0700
committerMike Buland <mike@xagasoft.com>2026-06-05 14:46:35 -0700
commit3dd9ca69340512e42867b43db689e494bada2000 (patch)
tree8834a0736873139302c8555bc5a7768cd739f74b
parent11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b (diff)
downloadcrimtag-3dd9ca69340512e42867b43db689e494bada2000.tar.gz
crimtag-3dd9ca69340512e42867b43db689e494bada2000.tar.bz2
crimtag-3dd9ca69340512e42867b43db689e494bada2000.tar.xz
crimtag-3dd9ca69340512e42867b43db689e494bada2000.zip
Symbols have a wrapper with positional data now.
It required some weird changes, I'm not sure I'm happy with them all yet, but I"m getting closer. It parses all basics, now we need to build an AST so it's actually useful :-P
-rw-r--r--src/lexer.rs105
-rw-r--r--src/lib.rs24
-rw-r--r--src/parser.rs56
3 files changed, 133 insertions, 52 deletions
diff --git a/src/lexer.rs b/src/lexer.rs
index 94825b2..04be350 100644
--- a/src/lexer.rs
+++ b/src/lexer.rs
@@ -2,13 +2,15 @@ use std::iter::Iterator;
2//use core::error::Error; 2//use core::error::Error;
3use std::str::CharIndices; 3use std::str::CharIndices;
4 4
5use crate::Position;
6
5#[derive(Debug,Copy,Clone)] 7#[derive(Debug,Copy,Clone)]
6pub enum ErrorType { 8pub enum ErrorType {
7 UnexpectedChar(char), 9 UnexpectedChar(char),
8} 10}
9 11
10#[derive(Debug,Copy,Clone)] 12#[derive(Debug,Copy,Clone)]
11pub enum Symbol<'a> { 13pub enum SymbolType<'a> {
12 StartFlat, 14 StartFlat,
13 StartPoint, 15 StartPoint,
14 EndFlat, 16 EndFlat,
@@ -20,10 +22,41 @@ pub enum Symbol<'a> {
20 Token(&'a str), 22 Token(&'a str),
21 Literal(&'a str), 23 Literal(&'a str),
22 Text(&'a str), 24 Text(&'a str),
23 Error{ line: u32, row: u32, what: ErrorType }, 25 Error{ what: ErrorType },
24 EOS, 26 EOS,
25} 27}
26 28
29#[derive(Debug,Copy,Clone)]
30pub struct Symbol<'a> {
31 symbol: SymbolType<'a>,
32 start: Position,
33 end: Position,
34}
35
36impl<'a> Symbol<'a> {
37 pub fn new( symbol: SymbolType<'a>, start: Position, end: Position ) -> Self {
38 Self {
39 symbol, start, end,
40 }
41 }
42
43 pub fn check_type<T: Fn( &SymbolType ) -> bool>(&self, f: T ) -> bool {
44 f( &self.symbol )
45 }
46
47 pub fn symbol(&self) -> &SymbolType<'a> {
48 &self.symbol
49 }
50
51 pub fn start(&self) -> &Position {
52 &self.start
53 }
54
55 pub fn end(&self) -> &Position {
56 &self.end
57 }
58}
59
27enum Mode { 60enum Mode {
28 Text, 61 Text,
29 InTag, 62 InTag,
@@ -35,8 +68,7 @@ pub struct Lexer<'a> {
35 cur: [Option<(usize, char)>;2], 68 cur: [Option<(usize, char)>;2],
36 icur: usize, 69 icur: usize,
37 mode: Mode, 70 mode: Mode,
38 line: u32, 71 pos: Position,
39 row: u32,
40} 72}
41 73
42fn is_valid_token_char( c: char ) -> bool { 74fn is_valid_token_char( c: char ) -> bool {
@@ -61,8 +93,7 @@ impl<'a> Lexer<'a> {
61 cur: [cur, cur2], 93 cur: [cur, cur2],
62 icur: 0, 94 icur: 0,
63 mode: Mode::Text, 95 mode: Mode::Text,
64 line: 0, 96 pos: Position::new(1,1),
65 row: 0,
66 } 97 }
67 } 98 }
68 99
@@ -71,6 +102,12 @@ impl<'a> Lexer<'a> {
71 self.icur = (self.icur+1)%2; 102 self.icur = (self.icur+1)%2;
72 //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); 103 //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() );
73 if let Some((_,chr)) = self.cur[self.icur] { 104 if let Some((_,chr)) = self.cur[self.icur] {
105 if chr == '\n' {
106 self.pos.column = 1;
107 self.pos.line += 1;
108 } else {
109 self.pos.column += 1;
110 }
74 Some(chr) 111 Some(chr)
75 } else { 112 } else {
76 None 113 None
@@ -110,11 +147,9 @@ impl<'a> Lexer<'a> {
110 } 147 }
111 148
112 fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> { 149 fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> {
113 Some(Symbol::Error { 150 Some(Symbol::new( SymbolType::Error {
114 line: self.line,
115 row: self.row,
116 what: what 151 what: what
117 }) 152 }, self.pos, self.pos ))
118 } 153 }
119 154
120 fn skip_ws( &mut self ) { 155 fn skip_ws( &mut self ) {
@@ -149,14 +184,16 @@ impl<'a> Lexer<'a> {
149 184
150 fn parse_text(&mut self) -> Option<Symbol<'a>> { 185 fn parse_text(&mut self) -> Option<Symbol<'a>> {
151 let start = self.cur_index(); 186 let start = self.cur_index();
187 let start_pos = self.pos.clone();
152 while self.next().is_some() && !self.is_start_tag() { } 188 while self.next().is_some() && !self.is_start_tag() { }
153 let end = self.cur_index(); 189 let end = self.cur_index();
190 let end_pos = self.pos.clone();
154 let s = &self.data[start..end]; 191 let s = &self.data[start..end];
155 //println!(" text: >>>{}<<<", s); 192 //println!(" text: >>>{}<<<", s);
156 if start == end { 193 if start == end {
157 None 194 None
158 } else { 195 } else {
159 Some(Symbol::Text(s)) 196 Some(Symbol::new( SymbolType::Text(s), start_pos, end_pos ))
160 } 197 }
161 } 198 }
162 199
@@ -168,25 +205,27 @@ impl<'a> Lexer<'a> {
168 } 205 }
169 Some('=') => { 206 Some('=') => {
170 self.next(); 207 self.next();
171 return Some(Symbol::Equals); 208 return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos));
172 } 209 }
173 _ => {} 210 _ => {}
174 } 211 }
175 let start = self.cur_index(); 212 let start = self.cur_index();
213 let start_pos = self.pos.clone();
176 214
177 while self.next().is_some_and(|ch| is_valid_token_char(ch) ) && 215 while self.next().is_some_and(|ch| is_valid_token_char(ch) ) &&
178 !self.is_end_tag() {} 216 !self.is_end_tag() {}
179 let end = self.cur_index(); 217 let end = self.cur_index();
218 let end_pos = self.pos.clone();
180 let s = &self.data[start..end]; 219 let s = &self.data[start..end];
181 if start == end { 220 if start == end {
182 None 221 None
183 } else { 222 } else {
184 Some(match s { 223 Some(Symbol::new( match s {
185 "fragment" => Symbol::Fragment, 224 "fragment" => SymbolType::Fragment,
186 "section" => Symbol::Section, 225 "section" => SymbolType::Section,
187 "output" => Symbol::Output, 226 "output" => SymbolType::Output,
188 _ => Symbol::Token(s) 227 _ => SymbolType::Token(s)
189 }) 228 }, start_pos, end_pos ))
190 } 229 }
191 } 230 }
192 231
@@ -195,11 +234,13 @@ impl<'a> Lexer<'a> {
195 return self.error( ErrorType::UnexpectedChar(chr) ); 234 return self.error( ErrorType::UnexpectedChar(chr) );
196 } 235 }
197 let start = self.peek_index(); 236 let start = self.peek_index();
237 let start_pos = self.pos.clone();
198 while self.next().is_some_and(|chr| chr != '"') { } 238 while self.next().is_some_and(|chr| chr != '"') { }
199 let end = self.cur_index(); 239 let end = self.cur_index();
240 let end_pos = self.pos.clone();
200 self.next(); 241 self.next();
201 let s = &self.data[start..end]; 242 let s = &self.data[start..end];
202 Some(Symbol::Literal(s)) 243 Some(Symbol::new(SymbolType::Literal(s), start_pos, end_pos ))
203 } 244 }
204 245
205 fn is_start_tag(&mut self) -> bool { 246 fn is_start_tag(&mut self) -> bool {
@@ -212,21 +253,26 @@ impl<'a> Lexer<'a> {
212 } 253 }
213 254
214 fn parse_start_tag(&mut self) -> Option<Symbol<'a>> { 255 fn parse_start_tag(&mut self) -> Option<Symbol<'a>> {
256 let start_pos = self.pos.clone();
215 match self.cur() { 257 match self.cur() {
216 Some('[') => { 258 Some('[') => {
217 if let Some(p) = self.peek() && p == '|' { 259 if let Some(p) = self.peek() && p == '|' {
218 self.next(); self.next(); 260 self.next();
261 let end_pos = self.pos.clone();
262 self.next();
219 self.mode = Mode::InTag; 263 self.mode = Mode::InTag;
220 Some(Symbol::StartFlat) 264 Some(Symbol::new( SymbolType::StartFlat, start_pos, end_pos ) )
221 } else { 265 } else {
222 None 266 None
223 } 267 }
224 } 268 }
225 Some('<') => { 269 Some('<') => {
226 if let Some(p) = self.peek() && p == '|' { 270 if let Some(p) = self.peek() && p == '|' {
227 self.next(); self.next(); 271 self.next();
272 let end_pos = self.pos.clone();
273 self.next();
228 self.mode = Mode::InTag; 274 self.mode = Mode::InTag;
229 Some(Symbol::StartPoint) 275 Some(Symbol::new(SymbolType::StartPoint, start_pos, end_pos ) )
230 } else { 276 } else {
231 None 277 None
232 } 278 }
@@ -247,17 +293,22 @@ impl<'a> Lexer<'a> {
247 } 293 }
248 294
249 fn parse_end_tag(&mut self) -> Option<Symbol<'a>> { 295 fn parse_end_tag(&mut self) -> Option<Symbol<'a>> {
296 let start_pos = self.pos.clone();
250 if let Some(chr) = self.cur() && chr == '|' { 297 if let Some(chr) = self.cur() && chr == '|' {
251 match self.peek() { 298 match self.peek() {
252 Some(']') => { 299 Some(']') => {
253 self.next(); self.next(); 300 self.next();
301 let end_pos = self.pos.clone();
302 self.next();
254 self.mode = Mode::Text; 303 self.mode = Mode::Text;
255 Some(Symbol::EndFlat) 304 Some(Symbol::new( SymbolType::EndFlat, start_pos, end_pos))
256 } 305 }
257 Some('>') => { 306 Some('>') => {
258 self.next(); self.next(); 307 self.next();
308 let end_pos = self.pos.clone();
309 self.next();
259 self.mode = Mode::Text; 310 self.mode = Mode::Text;
260 Some(Symbol::EndPoint) 311 Some(Symbol::new( SymbolType::EndPoint, start_pos, end_pos))
261 } 312 }
262 _ => { 313 _ => {
263 None 314 None
diff --git a/src/lib.rs b/src/lib.rs
index 0f6d01d..7d0e7f2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,6 +8,20 @@ use std::io::Read;
8mod lexer; 8mod lexer;
9mod parser; 9mod parser;
10 10
11#[derive(Debug,Copy,Clone)]
12pub struct Position {
13 pub line: u32,
14 pub column: u32,
15}
16
17impl Position {
18 pub fn new( line: u32, column: u32 ) -> Self {
19 Self {
20 line, column,
21 }
22 }
23}
24
11pub struct Crimtag { 25pub struct Crimtag {
12 fragments: HashMap<String, Fragment>, 26 fragments: HashMap<String, Fragment>,
13 sources: Vec<FragmentSource>, 27 sources: Vec<FragmentSource>,
@@ -104,13 +118,15 @@ impl Crimtag {
104mod tests { 118mod tests {
105 use super::*; 119 use super::*;
106 120
121 use crate::lexer::*;
122
107 #[test] 123 #[test]
108 fn lexing() { 124 fn lexing() {
109 let data = r#"Leading comment: [|fragment "basic" something="yeup"|>Hello there <|fragment|] Trailing text"#; 125 let data = r#"Leading comment: [|fragment "basic" something="yeup"|>Hello there <|fragment|] Trailing text"#;
110 let ll = lexer::Lexer::new( &data ); 126 let ll = lexer::Lexer::new( &data );
111 for sym in ll { 127 for sym in ll {
112 if let lexer::Symbol::Error{line,row,what} = sym { 128 if let SymbolType::Error{what} = sym.symbol() {
113 println!("Error {}:{}: {:?}", line, row, what ); 129 println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what );
114 break; 130 break;
115 } else { 131 } else {
116 println!("Symbol: {:?}", sym ); 132 println!("Symbol: {:?}", sym );
@@ -121,6 +137,8 @@ mod tests {
121 #[test] 137 #[test]
122 fn parsing() { 138 fn parsing() {
123 let mut ct = Crimtag::new(); 139 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!"#); 140 ct.load_static(r#"Here is a sample
141[|frament "index" theme="standard"|><html><body>Hi</body></html><|fragment|]
142That was fun!"#);
125 } 143 }
126} 144}
diff --git a/src/parser.rs b/src/parser.rs
index 54b77ac..e5ea1f5 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -6,8 +6,7 @@ use std::fmt;
6 6
7#[derive(Debug)] 7#[derive(Debug)]
8struct ParseError { 8struct ParseError {
9 line: u32, 9 position: Position,
10 row: u32,
11 what: String, 10 what: String,
12} 11}
13 12
@@ -138,15 +137,15 @@ impl Parser {
138 } 137 }
139 138
140 fn lex_error(&self, error: &Symbol) -> Result<(),Box<dyn Error>> { 139 fn lex_error(&self, error: &Symbol) -> Result<(),Box<dyn Error>> {
141 if let Symbol::Error{line,row,what} = error { 140 if let SymbolType::Error{what} = error.symbol() {
142 Err(Box::new(ParseError { 141 Err(Box::new(ParseError {
143 line: *line, 142 position: error.start().clone(),
144 row: *row,
145 what: format!("What: {:?}", *what) 143 what: format!("What: {:?}", *what)
146 })) 144 }))
147 } else { 145 } else {
148 Err(Box::new(ParseError { 146 Err(Box::new(ParseError {
149 line: 0, row: 0, what: "Not an error?".to_string() 147 position: Position::new(0,0),
148 what: "Not an error?".to_string(),
150 })) 149 }))
151 } 150 }
152 } 151 }
@@ -166,12 +165,12 @@ impl Parser {
166 if ctx.cur().is_none() { 165 if ctx.cur().is_none() {
167 break; 166 break;
168 } 167 }
169 match ctx.cur() { 168 match ctx.cur().unwrap().symbol() {
170 Some(Symbol::Text(_)) => { /* Skip top level text */ } 169 SymbolType::Text(_) => { /* Skip top level text */ }
171 Some(Symbol::StartFlat) => { 170 SymbolType::StartFlat => {
172 self.parse_tag( ctx ); 171 self.parse_tag( ctx );
173 } 172 }
174 Some(Symbol::Error{..}) => { 173 SymbolType::Error{..} => {
175 return self.lex_error( &ctx.cur().unwrap() ); 174 return self.lex_error( &ctx.cur().unwrap() );
176 } 175 }
177 _ => { 176 _ => {
@@ -188,10 +187,14 @@ impl Parser {
188 187
189 let start_sym = ctx.cur().unwrap(); 188 let start_sym = ctx.cur().unwrap();
190 189
191 if let Some(Symbol::Token(s)) = ctx.next() { 190 if ctx.next().is_some() {
192 tb.set_name( s.to_string() ); 191 if let SymbolType::Token(s) = ctx.next().unwrap().symbol() {
192 tb.set_name( s.to_string() );
193 } else {
194 return Err(Box::new(ParseError{position: ctx.cur().unwrap().start().clone(), what: "Unexpecetd symbol".to_string()}));
195 }
193 } else { 196 } else {
194 return Err(Box::new(ParseError{line: 0, row: 0, what: "Unexpecetd symbol".to_string()})); 197 return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
195 } 198 }
196 199
197 self.parse_tag_params( ctx, &mut tb )?; 200 self.parse_tag_params( ctx, &mut tb )?;
@@ -202,10 +205,15 @@ impl Parser {
202 205
203 fn parse_tag_params(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> { 206 fn parse_tag_params(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> {
204 loop { 207 loop {
205 if let Some(Symbol::Token(s)) = ctx.cur() { 208 if ctx.cur().is_none() {
206 if matches!(ctx.peek(), Some(Symbol::Equals)) { 209
207 // Done with parameters 210 return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
208 break; 211 }
212 if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() {
213 if let Some(p) = ctx.peek() {
214 if p.check_type(|t| matches!(t, SymbolType::Equals)) {
215 break;
216 }
209 } 217 }
210 else { 218 else {
211 tb.add_param( s.to_string() ); 219 tb.add_param( s.to_string() );
@@ -218,16 +226,20 @@ impl Parser {
218 226
219 fn parse_tag_props(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> { 227 fn parse_tag_props(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> {
220 loop { 228 loop {
221 if let Some(Symbol::Token(s)) = ctx.cur() { 229 if ctx.cur().is_none() {
222 if matches!(ctx.peek(), Some(Symbol::Equals)) { 230 return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
231 }
232 if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() {
233 if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) {
223 ctx.next(); 234 ctx.next();
224 if let Some(Symbol::Literal(lv)) = ctx.next() { 235 if let Some(sym) = ctx.next() &&
236 let SymbolType::Literal(lv) = sym.symbol() {
225 tb.add_prop( s.to_string(), lv.to_string() ); 237 tb.add_prop( s.to_string(), lv.to_string() );
226 } else { 238 } else {
227 return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected quoted literal string".to_string()})); 239 return Err(Box::new(ParseError{position: Position::new(0,0), what: "Expected quoted literal string".to_string()}));
228 } 240 }
229 } else { 241 } else {
230 return Err(Box::new(ParseError{line: 0, row: 0, what: "Expected = ".to_string()})); 242 return Err(Box::new(ParseError{position:Position::new(0,0), what: "Expected = ".to_string()}));
231 } 243 }
232 } else { 244 } else {
233 break; 245 break;