Parser-ready tokens with indentation handled. Returns lex diagnostics alongside so the caller folds them into the parser's error stream. */
(source: &str)
| 53 | |
| 54 | /* Parser-ready tokens with indentation handled. Returns lex diagnostics alongside so the caller folds them into the parser's error stream. */ |
| 55 | pub fn lex(source: &str) -> (Vec<Token>, Vec<LexError>) { |
| 56 | let bytes = source.as_bytes(); |
| 57 | let len = source.len(); |
| 58 | let mut scanner = Scanner::new(bytes); |
| 59 | // Skip UTF-8 BOM so it doesn't fuse into the first identifier. |
| 60 | if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) { scanner.pos = 3; } |
| 61 | |
| 62 | if len > MAX_SOURCE_SIZE { |
| 63 | scanner.errors.push(LexError { |
| 64 | start: 0, end: 0, |
| 65 | msg: "source file exceeds maximum size (10 MiB)", |
| 66 | }); |
| 67 | return ( |
| 68 | alloc::vec![Token { kind: TokenType::Endmarker, line: 0, start: len, end: len }], |
| 69 | scanner.errors, |
| 70 | ); |
| 71 | } |
| 72 | |
| 73 | let mut raw: Vec<(TokenType, usize, usize, usize)> = Vec::new(); |
| 74 | while let Some(t) = scanner.next_token() { |
| 75 | raw.push(t); |
| 76 | } |
| 77 | // Drain dangling indents at EOF so blocks always close cleanly. |
| 78 | while scanner.indent_stack.pop().is_some() { |
| 79 | raw.push((TokenType::Dedent, scanner.line, len, len)); |
| 80 | } |
| 81 | raw.push((TokenType::Endmarker, scanner.line, len, len)); |
| 82 | |
| 83 | let mut tokens = Vec::with_capacity(raw.len()); |
| 84 | let mut ended = false; |
| 85 | for i in 0..raw.len() { |
| 86 | let (tok, line, start, end) = raw[i]; |
| 87 | if ended { break; } |
| 88 | if tok == TokenType::Endmarker { ended = true; } |
| 89 | |
| 90 | /* Soft keywords demote to Name by the next token, so `match(...)`, `case(...)`, `type(...)` parse as calls not statements. */ |
| 91 | let is_soft = matches!(tok, TokenType::Type | TokenType::Match | TokenType::Case); |
| 92 | let next_demotes = matches!( |
| 93 | raw.get(i + 1), |
| 94 | Some(&( |
| 95 | TokenType::Lpar |
| 96 | | TokenType::Colon |
| 97 | | TokenType::Equal |
| 98 | | TokenType::Comma |
| 99 | | TokenType::Rpar |
| 100 | | TokenType::Rsqb |
| 101 | | TokenType::Newline, |
| 102 | _, _, _, |
| 103 | )) | None |
| 104 | ); |
| 105 | let kind = if is_soft && next_demotes { TokenType::Name } else { tok }; |
| 106 | tokens.push(Token { kind, line, start, end }); |
| 107 | } |
| 108 | (tokens, scanner.errors) |
| 109 | } |
| 110 | |
| 111 | impl TokenType { |
| 112 | #[inline] |