1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum TokenKind {
6 Int(i64),
8 BigInt(String),
12 Float(f64),
13 Str(String),
14 InterpStr(Vec<(bool, String)>), Bool(bool),
16 Ident(String),
18 Module,
20 Depends,
21 Exposes,
22 Intent,
23 Type,
24 Record,
25 Fn,
26 Effects,
27 Decision,
28 Verify,
29 Match,
30 Arrow, FatArrow, Eq, Neq, Lte, Gte, Assign, Bang, Question, Lt, Gt, Plus, Minus, Star, Slash, Dot, Colon, Comma, LParen, RParen, LBracket, RBracket, LBrace, RBrace, Indent,
57 Dedent,
58 Newline,
59 Eof,
60}
61
62impl fmt::Display for TokenKind {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 TokenKind::Int(n) => write!(f, "integer '{}'", n),
66 TokenKind::BigInt(s) => write!(f, "integer '{}'", s),
67 TokenKind::Float(n) => write!(f, "float '{}'", n),
68 TokenKind::Str(s) => write!(f, "string \"{}\"", s),
69 TokenKind::InterpStr(_) => write!(f, "interpolated string"),
70 TokenKind::Bool(b) => write!(f, "'{}'", b),
71 TokenKind::Ident(s) => write!(f, "'{}'", s),
72 TokenKind::Module => write!(f, "'module'"),
73 TokenKind::Depends => write!(f, "'depends'"),
74 TokenKind::Exposes => write!(f, "'exposes'"),
75 TokenKind::Intent => write!(f, "'intent'"),
76 TokenKind::Type => write!(f, "'type'"),
77 TokenKind::Record => write!(f, "'record'"),
78 TokenKind::Fn => write!(f, "'fn'"),
79 TokenKind::Effects => write!(f, "'effects'"),
80 TokenKind::Decision => write!(f, "'decision'"),
81 TokenKind::Verify => write!(f, "'verify'"),
82 TokenKind::Match => write!(f, "'match'"),
83 TokenKind::Arrow => write!(f, "'->'"),
84 TokenKind::FatArrow => write!(f, "'=>'"),
85 TokenKind::Eq => write!(f, "'=='"),
86 TokenKind::Neq => write!(f, "'!='"),
87 TokenKind::Lte => write!(f, "'<='"),
88 TokenKind::Gte => write!(f, "'>='"),
89 TokenKind::Assign => write!(f, "'='"),
90 TokenKind::Bang => write!(f, "'!'"),
91 TokenKind::Question => write!(f, "'?'"),
92 TokenKind::Lt => write!(f, "'<'"),
93 TokenKind::Gt => write!(f, "'>'"),
94 TokenKind::Plus => write!(f, "'+'"),
95 TokenKind::Minus => write!(f, "'-'"),
96 TokenKind::Star => write!(f, "'*'"),
97 TokenKind::Slash => write!(f, "'/'"),
98 TokenKind::Dot => write!(f, "'.'"),
99 TokenKind::Colon => write!(f, "':'"),
100 TokenKind::Comma => write!(f, "','"),
101 TokenKind::LParen => write!(f, "'('"),
102 TokenKind::RParen => write!(f, "')'"),
103 TokenKind::LBracket => write!(f, "'['"),
104 TokenKind::RBracket => write!(f, "']'"),
105 TokenKind::LBrace => write!(f, "'{{'"),
106 TokenKind::RBrace => write!(f, "'}}'"),
107 TokenKind::Indent => write!(f, "indentation"),
108 TokenKind::Dedent => write!(f, "end of block"),
109 TokenKind::Newline => write!(f, "end of line"),
110 TokenKind::Eof => write!(f, "end of file"),
111 }
112 }
113}
114
115#[derive(Debug, Clone)]
116pub struct Token {
117 pub kind: TokenKind,
118 pub line: usize,
119 pub col: usize,
120}
121
122#[derive(Debug, Error)]
123pub enum LexerError {
124 #[error("error[{line}:{col}]: {msg}")]
125 Error {
126 msg: String,
127 line: usize,
128 col: usize,
129 },
130}
131
132fn keyword(s: &str) -> Option<TokenKind> {
133 match s {
134 "module" => Some(TokenKind::Module),
135 "depends" => Some(TokenKind::Depends),
136 "exposes" => Some(TokenKind::Exposes),
137 "intent" => Some(TokenKind::Intent),
138 "type" => Some(TokenKind::Type),
139 "record" => Some(TokenKind::Record),
140 "fn" => Some(TokenKind::Fn),
141 "effects" => Some(TokenKind::Effects),
142 "decision" => Some(TokenKind::Decision),
143 "verify" => Some(TokenKind::Verify),
144 "match" => Some(TokenKind::Match),
145 "true" => Some(TokenKind::Bool(true)),
146 "false" => Some(TokenKind::Bool(false)),
147 _ => None,
148 }
149}
150
151pub struct Lexer {
152 chars: Vec<char>,
153 pos: usize,
154 line: usize,
155 col: usize,
156 indent_stack: Vec<usize>,
157 at_line_start: bool,
158}
159
160impl Lexer {
161 pub fn new(source: &str) -> Self {
162 Lexer {
163 chars: source.chars().collect(),
164 pos: 0,
165 line: 1,
166 col: 1,
167 indent_stack: vec![0],
168 at_line_start: true,
169 }
170 }
171
172 fn error(&self, msg: impl Into<String>) -> LexerError {
173 LexerError::Error {
174 msg: msg.into(),
175 line: self.line,
176 col: self.col,
177 }
178 }
179
180 fn peek(&self, offset: usize) -> Option<char> {
181 self.chars.get(self.pos + offset).copied()
182 }
183
184 fn current(&self) -> Option<char> {
185 self.chars.get(self.pos).copied()
186 }
187
188 fn advance(&mut self) -> Option<char> {
189 let ch = self.chars.get(self.pos).copied()?;
190 self.pos += 1;
191 if ch == '\n' {
192 self.line += 1;
193 self.col = 1;
194 } else {
195 self.col += 1;
196 }
197 Some(ch)
198 }
199
200 pub fn tokenize(&mut self) -> Result<Vec<Token>, LexerError> {
201 let mut tokens = Vec::new();
202
203 while self.pos < self.chars.len() {
204 self.scan_token(&mut tokens)?;
205 }
206
207 while self.indent_stack.len() > 1 {
209 self.indent_stack.pop();
210 tokens.push(Token {
211 kind: TokenKind::Dedent,
212 line: self.line,
213 col: self.col,
214 });
215 }
216
217 tokens.push(Token {
218 kind: TokenKind::Eof,
219 line: self.line,
220 col: self.col,
221 });
222
223 Ok(tokens)
224 }
225
226 fn scan_token(&mut self, tokens: &mut Vec<Token>) -> Result<(), LexerError> {
227 if self.at_line_start {
228 self.handle_indentation(tokens)?;
229 if self.pos >= self.chars.len() {
230 return Ok(());
231 }
232 }
233
234 let ch = match self.current() {
235 Some(c) => c,
236 None => return Ok(()),
237 };
238
239 if ch == ' ' {
241 self.advance();
242 return Ok(());
243 }
244
245 if ch == '\n' {
247 let line = self.line;
248 let col = self.col;
249 self.advance();
250
251 let last_is_structural = tokens
252 .last()
253 .map(|t| {
254 matches!(
255 t.kind,
256 TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
257 )
258 })
259 .unwrap_or(true);
260
261 if !tokens.is_empty() && !last_is_structural {
262 tokens.push(Token {
263 kind: TokenKind::Newline,
264 line,
265 col,
266 });
267 }
268 self.at_line_start = true;
269 return Ok(());
270 }
271
272 if ch == '\r' {
274 self.advance();
275 return Ok(());
276 }
277
278 if ch == '/' && self.peek(1) == Some('/') {
280 self.skip_comment();
281 return Ok(());
282 }
283
284 if ch == '"' {
286 let tok = self.scan_string()?;
287 tokens.push(tok);
288 return Ok(());
289 }
290
291 if ch.is_ascii_digit() {
293 let tok = self.scan_number()?;
294 tokens.push(tok);
295 return Ok(());
296 }
297
298 if ch.is_alphabetic() || ch == '_' {
300 let tok = self.scan_identifier();
301 tokens.push(tok);
302 return Ok(());
303 }
304
305 let tok = self.scan_operator()?;
307 tokens.push(tok);
308 Ok(())
309 }
310
311 fn handle_indentation(&mut self, tokens: &mut Vec<Token>) -> Result<(), LexerError> {
312 self.at_line_start = false;
313 let mut indent = 0;
314
315 while self.pos < self.chars.len() && self.chars[self.pos] == ' ' {
316 indent += 1;
317 self.pos += 1;
318 self.col += 1;
319 }
320
321 if self.pos < self.chars.len() {
323 let ch = self.chars[self.pos];
324 if ch == '\n' || ch == '\r' {
325 return Ok(());
326 }
327 if ch == '/' && self.pos + 1 < self.chars.len() && self.chars[self.pos + 1] == '/' {
328 return Ok(());
329 }
330 } else {
331 return Ok(());
332 }
333
334 let current = *self.indent_stack.last().unwrap();
335 let line = self.line;
336
337 if indent > current {
338 self.indent_stack.push(indent);
339 tokens.push(Token {
340 kind: TokenKind::Indent,
341 line,
342 col: 1,
343 });
344 } else if indent < current {
345 while self.indent_stack.len() > 1 && *self.indent_stack.last().unwrap() > indent {
346 self.indent_stack.pop();
347 tokens.push(Token {
348 kind: TokenKind::Dedent,
349 line,
350 col: 1,
351 });
352 }
353 if *self.indent_stack.last().unwrap() != indent {
354 return Err(self.error(format!(
355 "Invalid indentation level: {indent}. Aver uses \
356 significant indentation with one consistent step per \
357 block — every line in the same block dedents back to \
358 a previously-opened indent level. Common cause: a \
359 wrapped `fn` signature or a multi-line argument list \
360 (Aver doesn't support either — keep each declaration \
361 on a single line, or split the body into a named \
362 helper function)."
363 )));
364 }
365 }
366
367 Ok(())
368 }
369
370 fn skip_comment(&mut self) {
371 while self.pos < self.chars.len() && self.chars[self.pos] != '\n' {
372 self.advance();
373 }
374 }
375
376 fn scan_string(&mut self) -> Result<Token, LexerError> {
377 let line = self.line;
378 let col = self.col;
379 self.advance(); let mut parts: Vec<(bool, String)> = Vec::new(); let mut current = String::new();
383 let mut has_interp = false;
384
385 loop {
386 match self.current() {
387 None => return Err(self.error("Unterminated string literal")),
388 Some('"') => {
389 self.advance();
390 break;
391 }
392 Some('{') => {
393 if self.chars.get(self.pos + 1).copied() == Some('{') {
395 current.push('{');
396 self.advance(); self.advance(); } else {
399 has_interp = true;
400 if !current.is_empty() {
401 parts.push((false, current.clone()));
402 current.clear();
403 }
404 self.advance(); let mut expr_text = String::new();
406 let mut depth = 1usize;
407 while self.pos < self.chars.len() && depth > 0 {
408 match self.chars[self.pos] {
409 '{' => {
410 depth += 1;
411 expr_text.push('{');
412 self.advance();
413 }
414 '}' => {
415 depth -= 1;
416 if depth > 0 {
417 expr_text.push('}');
418 }
419 self.advance();
420 }
421 c => {
422 expr_text.push(c);
423 self.advance();
424 }
425 }
426 }
427 parts.push((true, expr_text));
428 }
429 }
430 Some('}') => {
431 if self.chars.get(self.pos + 1).copied() == Some('}') {
433 current.push('}');
434 self.advance(); self.advance(); } else {
437 current.push('}');
438 self.advance();
439 }
440 }
441 Some('\\') => {
442 self.advance();
443 match self.advance() {
444 Some('b') => current.push('\u{0008}'),
445 Some('f') => current.push('\u{000C}'),
446 Some('n') => current.push('\n'),
447 Some('t') => current.push('\t'),
448 Some('r') => current.push('\r'),
449 Some('"') => current.push('"'),
450 Some('\\') => current.push('\\'),
451 Some(c) => current.push(c),
452 None => return Err(self.error("Unterminated string literal")),
453 }
454 }
455 Some('\n') => return Err(self.error("Unterminated string literal")),
456 Some(c) => {
457 current.push(c);
458 self.advance();
459 }
460 }
461 }
462
463 if !current.is_empty() {
464 parts.push((false, current));
465 }
466
467 if has_interp {
468 Ok(Token {
469 kind: TokenKind::InterpStr(parts),
470 line,
471 col,
472 })
473 } else {
474 let plain = parts.into_iter().map(|(_, s)| s).collect::<String>();
475 Ok(Token {
476 kind: TokenKind::Str(plain),
477 line,
478 col,
479 })
480 }
481 }
482
483 fn scan_number(&mut self) -> Result<Token, LexerError> {
484 let line = self.line;
485 let col = self.col;
486 let mut num_str = String::new();
487 let mut is_float = false;
488
489 while let Some(c) = self.current() {
490 if c.is_ascii_digit() {
491 num_str.push(c);
492 self.advance();
493 } else {
494 break;
495 }
496 }
497
498 if self.current() == Some('.') && self.peek(1).map(|c| c.is_ascii_digit()).unwrap_or(false)
499 {
500 is_float = true;
501 num_str.push('.');
502 self.advance(); while let Some(c) = self.current() {
504 if c.is_ascii_digit() {
505 num_str.push(c);
506 self.advance();
507 } else {
508 break;
509 }
510 }
511 }
512
513 if is_float {
514 let f: f64 = num_str
515 .parse()
516 .map_err(|_| self.error("Invalid floating-point number"))?;
517 Ok(Token {
518 kind: TokenKind::Float(f),
519 line,
520 col,
521 })
522 } else {
523 match num_str.parse::<i64>() {
532 Ok(i) => Ok(Token {
533 kind: TokenKind::Int(i),
534 line,
535 col,
536 }),
537 Err(e)
538 if matches!(
539 e.kind(),
540 std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
541 ) =>
542 {
543 Ok(Token {
544 kind: TokenKind::BigInt(num_str),
545 line,
546 col,
547 })
548 }
549 Err(_) => Err(self.error("Invalid integer literal")),
550 }
551 }
552 }
553
554 fn scan_identifier(&mut self) -> Token {
555 let line = self.line;
556 let col = self.col;
557 let mut ident = String::new();
558
559 while let Some(c) = self.current() {
560 if c.is_alphanumeric() || c == '_' {
561 ident.push(c);
562 self.advance();
563 } else {
564 break;
565 }
566 }
567
568 let kind = keyword(&ident).unwrap_or(TokenKind::Ident(ident));
569 Token { kind, line, col }
570 }
571
572 fn scan_operator(&mut self) -> Result<Token, LexerError> {
573 let line = self.line;
574 let col = self.col;
575 let ch = self.advance().unwrap();
576
577 let kind = match ch {
578 '-' if self.current() == Some('>') => {
579 self.advance();
580 TokenKind::Arrow
581 }
582 '=' if self.current() == Some('>') => {
583 self.advance();
584 TokenKind::FatArrow
585 }
586 '=' if self.current() == Some('=') => {
587 self.advance();
588 TokenKind::Eq
589 }
590 '!' if self.current() == Some('=') => {
591 self.advance();
592 TokenKind::Neq
593 }
594 '<' if self.current() == Some('=') => {
595 self.advance();
596 TokenKind::Lte
597 }
598 '>' if self.current() == Some('=') => {
599 self.advance();
600 TokenKind::Gte
601 }
602 '=' => TokenKind::Assign,
603 '<' => TokenKind::Lt,
604 '>' => TokenKind::Gt,
605 '+' => TokenKind::Plus,
606 '-' => TokenKind::Minus,
607 '*' => TokenKind::Star,
608 '/' => TokenKind::Slash,
609 '!' => TokenKind::Bang,
610 '?' => TokenKind::Question,
611 '.' => TokenKind::Dot,
612 ':' => TokenKind::Colon,
613 ',' => TokenKind::Comma,
614 '(' => TokenKind::LParen,
615 ')' => TokenKind::RParen,
616 '[' => TokenKind::LBracket,
617 ']' => TokenKind::RBracket,
618 '{' => TokenKind::LBrace,
619 '}' => TokenKind::RBrace,
620 other => return Err(self.error(format!("Unknown character: {:?}", other))),
621 };
622
623 Ok(Token { kind, line, col })
624 }
625}