summaryrefslogtreecommitdiff
path: root/crimtag/src/lexer.rs
diff options
context:
space:
mode:
authoreichlan <eichlan@jotunn.office.penny-arcade.com>2026-06-23 15:31:51 -0700
committereichlan <eichlan@jotunn.office.penny-arcade.com>2026-06-23 15:31:51 -0700
commit062048ef6e3e060bcf841ea2ec92e026f09d8da0 (patch)
tree22f7dad062b16f25fcfa1a28af13ac2937348ef0 /crimtag/src/lexer.rs
parent7c299c86bbac76ec2ac199d68eb4dda093a64e3a (diff)
downloadcrimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.gz
crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.bz2
crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.tar.xz
crimtag-062048ef6e3e060bcf841ea2ec92e026f09d8da0.zip
Roorganized into workspace and packages.
This is to accomidate a new proc_macro package, which can't have anything else in it.
Diffstat (limited to 'crimtag/src/lexer.rs')
-rw-r--r--crimtag/src/lexer.rs368
1 files changed, 368 insertions, 0 deletions
diff --git a/crimtag/src/lexer.rs b/crimtag/src/lexer.rs
new file mode 100644
index 0000000..d4d39e9
--- /dev/null
+++ b/crimtag/src/lexer.rs
@@ -0,0 +1,368 @@
1use std::iter::Iterator;
2//use core::error::Error;
3use std::str::CharIndices;
4use std::fmt;
5
6use crate::Position;
7
8#[derive(Debug,Copy,Clone,PartialEq,Eq)]
9pub enum ErrorType {
10 UnexpectedChar(char),
11}
12
13#[derive(Debug,Copy,Clone,PartialEq,Eq)]
14pub enum SymbolType<'a> {
15 StartFlat,
16 StartPoint,
17 EndFlat,
18 EndPoint,
19
20 View,
21 Output,
22 Show,
23 Loop,
24 If,
25 ElIf,
26 Else,
27
28 Sharp,
29 Equals,
30 Period,
31 Token(&'a str),
32 Literal(&'a str),
33 Text(&'a str),
34 Error{ what: ErrorType },
35}
36
37#[derive(Copy,Clone)]
38pub struct Symbol<'a> {
39 symbol: SymbolType<'a>,
40 start: Position,
41 end: Position,
42}
43
44impl<'a> fmt::Debug for Symbol<'a> {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "{:?} @ {:?}-{:?}", self.symbol, self.start, self.end )
47 }
48}
49
50impl<'a> Symbol<'a> {
51 pub fn new( symbol: SymbolType<'a>, start: Position, end: Position ) -> Self {
52 Self {
53 symbol, start, end,
54 }
55 }
56
57 pub fn check_type<T: Fn( &SymbolType ) -> bool>(&self, f: T ) -> bool {
58 f( &self.symbol )
59 }
60
61 pub fn symbol(&self) -> &SymbolType<'a> {
62 &self.symbol
63 }
64
65 pub fn start(&self) -> &Position {
66 &self.start
67 }
68
69 /*
70 pub fn end(&self) -> &Position {
71 &self.end
72 }
73 */
74}
75
76#[derive(Debug)]
77enum Mode {
78 Text,
79 InTag,
80}
81
82pub struct Lexer<'a> {
83 data: &'a str,
84 chars: CharIndices<'a>,
85 cur: [Option<(usize, char)>;2],
86 icur: usize,
87 mode: Mode,
88 pos: Position,
89}
90
91fn is_valid_token_char( c: char, first: bool ) -> bool {
92 if c.is_whitespace() {
93 return false;
94 }
95 if first {
96 match c {
97 'a'..'z' | 'A'..'Z' | '_' => true,
98 _ => false
99 }
100 } else {
101 match c {
102 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' => true,
103 _ => false
104 }
105 }
106}
107
108impl<'a> Lexer<'a> {
109 pub fn new( data: &'a str ) -> Lexer<'a> {
110 let mut chars = data.char_indices();
111 let cur = chars.next();
112 let cur2 = chars.next();
113 //println!(" - cur: {:?}, peek: {:?}", cur, cur2 );
114 Lexer {
115 data,
116 chars,
117 cur: [cur, cur2],
118 icur: 0,
119 mode: Mode::Text,
120 pos: Position::new(1,1),
121 }
122 }
123
124 fn next(&mut self) -> Option<char> {
125 self.cur[self.icur] = self.chars.next();
126 self.icur = (self.icur+1)%2;
127 //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() );
128 if let Some((_,chr)) = self.cur[self.icur] {
129 if chr == '\n' {
130 self.pos.column = 1;
131 self.pos.line += 1;
132 } else {
133 self.pos.column += 1;
134 }
135 Some(chr)
136 } else {
137 None
138 }
139 }
140
141 fn cur(&self) -> Option<char> {
142 if let Some((_, chr)) = self.cur[self.icur] {
143 Some(chr)
144 } else {
145 None
146 }
147 }
148
149 fn cur_index(&self) -> usize {
150 if let Some((idx, _)) = self.cur[self.icur] {
151 idx
152 } else {
153 self.chars.offset()
154 }
155 }
156
157 fn peek(&self) -> Option<char> {
158 if let Some((_,chr)) = self.cur[(self.icur+1)%2] {
159 Some(chr)
160 } else {
161 None
162 }
163 }
164
165 fn peek_index(&self) -> usize {
166 if let Some((idx, _)) = self.cur[(self.icur+1)%2] {
167 idx
168 } else {
169 self.chars.offset()
170 }
171 }
172
173 fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> {
174 Some(Symbol::new( SymbolType::Error {
175 what: what
176 }, self.pos, self.pos ))
177 }
178
179 fn skip_ws( &mut self ) {
180 while self.cur().is_some_and(|x|x.is_whitespace()) {
181 self.next();
182 }
183 }
184
185 fn next_symbol(&mut self) -> Option<Symbol<'a>> {
186 // If we hit the end then we're already done.
187 if self.cur().is_none() {
188 return None;
189 }
190
191 match self.mode {
192 Mode::Text => {
193 if let Some(sym) = self.parse_start_tag() {
194 Some(sym)
195 } else {
196 self.parse_text()
197 }
198 }
199 Mode::InTag => {
200 if let Some(sym) = self.parse_end_tag() {
201 Some(sym)
202 } else {
203 self.parse_token()
204 }
205 }
206 }
207 }
208
209 fn parse_text(&mut self) -> Option<Symbol<'a>> {
210 let start = self.cur_index();
211 let start_pos = self.pos.clone();
212 while self.next().is_some() && !self.is_start_tag() { }
213 let end = self.cur_index();
214 let end_pos = self.pos.clone();
215 let s = &self.data[start..end];
216 //println!(" text: >>>{}<<<", s);
217 if start == end {
218 None
219 } else {
220 Some(Symbol::new( SymbolType::Text(s), start_pos, end_pos ))
221 }
222 }
223
224 fn parse_token(&mut self) -> Option<Symbol<'a>> {
225 self.skip_ws();
226 match self.cur() {
227 Some('"') => {
228 return self.parse_literal_str();
229 }
230 Some('#') => {
231 self.next();
232 return Some(Symbol::new(SymbolType::Sharp, self.pos, self.pos));
233 }
234 Some('=') => {
235 self.next();
236 return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos));
237 }
238 Some('.') => {
239 self.next();
240 return Some(Symbol::new(SymbolType::Period, self.pos, self.pos));
241 }
242 _ => {}
243 }
244 let start = self.cur_index();
245 let start_pos = self.pos.clone();
246
247 let mut first = true;
248 while self.next().is_some_and(|ch| is_valid_token_char(ch, first) ) &&
249 !self.is_end_tag() { first = false; }
250 let end = self.cur_index();
251 let end_pos = self.pos.clone();
252 let s = &self.data[start..end];
253 if start == end {
254 None
255 } else {
256 Some(Symbol::new( match s {
257 "view" => SymbolType::View,
258 "show" => SymbolType::Show,
259 "output" => SymbolType::Output,
260 "loop" => SymbolType::Loop,
261 "if" => SymbolType::If,
262 "elif" => SymbolType::ElIf,
263 "else" => SymbolType::Else,
264 _ => SymbolType::Token(s)
265 }, start_pos, end_pos ))
266 }
267 }
268
269 fn parse_literal_str(&mut self) -> Option<Symbol<'a>> {
270 if let Some(chr) = self.cur() && chr != '"' {
271 return self.error( ErrorType::UnexpectedChar(chr) );
272 }
273 let start = self.peek_index();
274 let start_pos = self.pos.clone();
275 while self.next().is_some_and(|chr| chr != '"') { }
276 let end = self.cur_index();
277 let end_pos = self.pos.clone();
278 self.next();
279 let s = &self.data[start..end];
280 Some(Symbol::new(SymbolType::Literal(s), start_pos, end_pos ))
281 }
282
283 fn is_start_tag(&mut self) -> bool {
284 if let Some(cur) = self.cur() && (cur == '[' || cur == '<') &&
285 let Some(peek) = self.peek() && peek == '|' {
286 true
287 } else {
288 false
289 }
290 }
291
292 fn parse_start_tag(&mut self) -> Option<Symbol<'a>> {
293 let start_pos = self.pos.clone();
294 match self.cur() {
295 Some('[') => {
296 if let Some(p) = self.peek() && p == '|' {
297 self.next();
298 let end_pos = self.pos.clone();
299 self.next();
300 self.mode = Mode::InTag;
301 Some(Symbol::new( SymbolType::StartFlat, start_pos, end_pos ) )
302 } else {
303 None
304 }
305 }
306 Some('<') => {
307 if let Some(p) = self.peek() && p == '|' {
308 self.next();
309 let end_pos = self.pos.clone();
310 self.next();
311 self.mode = Mode::InTag;
312 Some(Symbol::new(SymbolType::StartPoint, start_pos, end_pos ) )
313 } else {
314 None
315 }
316 }
317 _ => {
318 None
319 }
320 }
321 }
322
323 fn is_end_tag(&mut self) -> bool {
324 if let Some(cur) = self.cur() && cur == '|' &&
325 let Some(peek) = self.peek() && (peek == '>' || peek == ']') {
326 true
327 } else {
328 false
329 }
330 }
331
332 fn parse_end_tag(&mut self) -> Option<Symbol<'a>> {
333 self.skip_ws();
334 let start_pos = self.pos.clone();
335 if let Some(chr) = self.cur() && chr == '|' {
336 match self.peek() {
337 Some(']') => {
338 self.next();
339 let end_pos = self.pos.clone();
340 self.next();
341 self.mode = Mode::Text;
342 Some(Symbol::new( SymbolType::EndFlat, start_pos, end_pos))
343 }
344 Some('>') => {
345 self.next();
346 let end_pos = self.pos.clone();
347 self.next();
348 self.mode = Mode::Text;
349 Some(Symbol::new( SymbolType::EndPoint, start_pos, end_pos))
350 }
351 _ => {
352 None
353 }
354 }
355 } else {
356 None
357 }
358 }
359}
360
361impl<'a> Iterator for Lexer<'a> {
362 type Item = Symbol<'a>;
363
364 fn next(&mut self) -> Option<Self::Item> {
365 self.next_symbol()
366 }
367}
368