diff options
| author | Mike Buland <mike@xagasoft.com> | 2026-06-05 14:46:35 -0700 |
|---|---|---|
| committer | Mike Buland <mike@xagasoft.com> | 2026-06-05 14:46:35 -0700 |
| commit | 3dd9ca69340512e42867b43db689e494bada2000 (patch) | |
| tree | 8834a0736873139302c8555bc5a7768cd739f74b | |
| parent | 11f5bc0c5a8bcfb6ac8fef8abc33297c4addc81b (diff) | |
| download | crimtag-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.rs | 105 | ||||
| -rw-r--r-- | src/lib.rs | 24 | ||||
| -rw-r--r-- | src/parser.rs | 56 |
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; |
| 3 | use std::str::CharIndices; | 3 | use std::str::CharIndices; |
| 4 | 4 | ||
| 5 | use crate::Position; | ||
| 6 | |||
| 5 | #[derive(Debug,Copy,Clone)] | 7 | #[derive(Debug,Copy,Clone)] |
| 6 | pub enum ErrorType { | 8 | pub enum ErrorType { |
| 7 | UnexpectedChar(char), | 9 | UnexpectedChar(char), |
| 8 | } | 10 | } |
| 9 | 11 | ||
| 10 | #[derive(Debug,Copy,Clone)] | 12 | #[derive(Debug,Copy,Clone)] |
| 11 | pub enum Symbol<'a> { | 13 | pub 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)] | ||
| 30 | pub struct Symbol<'a> { | ||
| 31 | symbol: SymbolType<'a>, | ||
| 32 | start: Position, | ||
| 33 | end: Position, | ||
| 34 | } | ||
| 35 | |||
| 36 | impl<'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 | |||
| 27 | enum Mode { | 60 | enum 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 | ||
| 42 | fn is_valid_token_char( c: char ) -> bool { | 74 | fn 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 |
| @@ -8,6 +8,20 @@ use std::io::Read; | |||
| 8 | mod lexer; | 8 | mod lexer; |
| 9 | mod parser; | 9 | mod parser; |
| 10 | 10 | ||
| 11 | #[derive(Debug,Copy,Clone)] | ||
| 12 | pub struct Position { | ||
| 13 | pub line: u32, | ||
| 14 | pub column: u32, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl Position { | ||
| 18 | pub fn new( line: u32, column: u32 ) -> Self { | ||
| 19 | Self { | ||
| 20 | line, column, | ||
| 21 | } | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 11 | pub struct Crimtag { | 25 | pub 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 { | |||
| 104 | mod tests { | 118 | mod 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|] | ||
| 142 | That 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)] |
| 8 | struct ParseError { | 8 | struct 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; |
