1use logos::Logos;
8
9use crate::token::{Span, Spanned, Token};
10
11pub fn tokenize(input: &str) -> Result<Vec<Spanned>, LexError> {
21 let raw = raw_tokenize(input)?;
22 Ok(insert_layout(input, &raw))
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct LexError {
28 pub offset: usize,
30 pub text: String,
32}
33
34impl std::fmt::Display for LexError {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(
37 f,
38 "unrecognized token at byte {}: {:?}",
39 self.offset, self.text
40 )
41 }
42}
43
44impl std::error::Error for LexError {}
45
46fn raw_tokenize(input: &str) -> Result<Vec<Spanned>, LexError> {
48 let mut tokens = Vec::new();
49 let mut lexer = Token::lexer(input);
50
51 while let Some(result) = lexer.next() {
52 let span = lexer.span();
53 if let Ok(token) = result {
54 tokens.push(Spanned {
55 token,
56 span: Span {
57 start: span.start,
58 end: span.end,
59 },
60 });
61 } else {
62 let slice = &input[span.clone()];
64 if slice.contains('\n') || slice.contains('\r') {
65 continue;
67 }
68 return Err(LexError {
69 offset: span.start,
70 text: slice.to_string(),
71 });
72 }
73 }
74
75 tokens.push(Spanned {
76 token: Token::Eof,
77 span: Span {
78 start: input.len(),
79 end: input.len(),
80 },
81 });
82
83 Ok(tokens)
84}
85
86fn insert_layout(input: &str, raw: &[Spanned]) -> Vec<Spanned> {
97 if raw.is_empty() {
98 return vec![];
99 }
100
101 let mut result = Vec::with_capacity(raw.len());
102 let mut indent_stack: Vec<usize> = vec![0]; let mut cursor = Cursor::new();
104 let mut prev_line = 0;
105 let mut prev_end = 0;
106
107 for spanned in raw {
108 cursor.advance_to(input, spanned.span.start);
109 let cur_line = cursor.line;
110 let cur_col = cursor.column();
111
112 if cur_line > prev_line {
114 let current_indent = *indent_stack.last().unwrap_or(&0);
115
116 match cur_col.cmp(¤t_indent) {
117 std::cmp::Ordering::Greater => {
118 let prev_is_layout = result.last().is_some_and(|s: &Spanned| {
120 matches!(s.token, Token::Let | Token::Where | Token::Do | Token::Of)
121 });
122 if prev_is_layout {
123 indent_stack.push(cur_col);
124 result.push(Spanned {
125 token: Token::Indent,
126 span: Span {
127 start: spanned.span.start,
128 end: spanned.span.start,
129 },
130 });
131 }
132 }
133 std::cmp::Ordering::Less => {
134 while indent_stack.len() > 1 && *indent_stack.last().unwrap_or(&0) > cur_col {
136 indent_stack.pop();
137 result.push(Spanned {
138 token: Token::Dedent,
139 span: Span {
140 start: spanned.span.start,
141 end: spanned.span.start,
142 },
143 });
144 }
145 }
146 std::cmp::Ordering::Equal => {
147 if indent_stack.len() > 1 {
150 result.push(Spanned {
151 token: Token::Newline,
152 span: Span {
153 start: spanned.span.start,
154 end: spanned.span.start,
155 },
156 });
157 }
158 }
159 }
160 }
161
162 result.push(spanned.clone());
163 prev_line = cur_line;
164 prev_end = spanned.span.end;
165 }
166
167 while indent_stack.len() > 1 {
169 indent_stack.pop();
170 result.push(Spanned {
171 token: Token::Dedent,
172 span: Span {
173 start: prev_end,
174 end: prev_end,
175 },
176 });
177 }
178
179 result
180}
181
182struct Cursor {
189 offset: usize,
191 line: usize,
193 line_start: usize,
195}
196
197impl Cursor {
198 const fn new() -> Self {
199 Self {
200 offset: 0,
201 line: 0,
202 line_start: 0,
203 }
204 }
205
206 fn advance_to(&mut self, input: &str, target: usize) {
211 let target = target.max(self.offset).min(input.len());
212 for (index, byte) in input.as_bytes()[self.offset..target].iter().enumerate() {
213 if *byte == b'\n' {
214 self.line += 1;
215 self.line_start = self.offset + index + 1;
216 }
217 }
218 self.offset = target;
219 }
220
221 const fn column(&self) -> usize {
223 self.offset - self.line_start
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn simple_expression() {
233 let tokens = tokenize("1 + 2").unwrap_or_default();
234 assert_eq!(tokens[0].token, Token::Int(1));
235 assert_eq!(tokens[1].token, Token::Plus);
236 assert_eq!(tokens[2].token, Token::Int(2));
237 assert_eq!(tokens[3].token, Token::Eof);
238 }
239
240 #[test]
241 fn keywords_recognized() {
242 let tokens = tokenize("let x = 1 in x").unwrap_or_default();
243 assert_eq!(tokens[0].token, Token::Let);
244 assert_eq!(tokens[1].token, Token::Ident("x".into()));
245 assert_eq!(tokens[2].token, Token::Eq);
246 assert_eq!(tokens[3].token, Token::Int(1));
247 assert_eq!(tokens[4].token, Token::In);
248 }
249
250 #[test]
251 fn string_literal() {
252 let tokens = tokenize(r#""hello world""#).unwrap_or_default();
253 assert_eq!(tokens[0].token, Token::Str("hello world".into()));
254 }
255
256 #[test]
257 fn operators() {
258 let tokens = tokenize("a -> b && c || d").unwrap_or_default();
259 assert_eq!(tokens[0].token, Token::Ident("a".into()));
260 assert_eq!(tokens[1].token, Token::Arrow);
261 assert_eq!(tokens[2].token, Token::Ident("b".into()));
262 assert_eq!(tokens[3].token, Token::AndAnd);
263 assert_eq!(tokens[5].token, Token::OrOr);
264 }
265
266 #[test]
267 fn layout_let_block() {
268 let input = "let\n x = 1\n y = 2\nin x";
269 let tokens = tokenize(input).unwrap_or_default();
270 let kinds: Vec<&Token> = tokens.iter().map(|s| &s.token).collect();
271 assert!(kinds.contains(&&Token::Indent));
274 assert!(kinds.contains(&&Token::Newline));
275 assert!(kinds.contains(&&Token::Dedent));
276 }
277
278 #[test]
279 fn comprehension_tokens() {
280 let tokens = tokenize("[ a | a <- xs, a > 0 ]").unwrap_or_default();
281 assert_eq!(tokens[0].token, Token::LBracket);
282 assert_eq!(tokens[1].token, Token::Ident("a".into()));
283 assert_eq!(tokens[2].token, Token::Pipe);
284 assert_eq!(tokens[3].token, Token::Ident("a".into()));
285 assert_eq!(tokens[4].token, Token::LeftArrow);
286 }
287
288 #[test]
289 fn comment_skipped() {
290 let tokens = tokenize("x -- this is a comment\ny").unwrap_or_default();
291 let idents: Vec<&str> = tokens
292 .iter()
293 .filter_map(|s| {
294 if let Token::Ident(ref name) = s.token {
295 Some(name.as_str())
296 } else {
297 None
298 }
299 })
300 .collect();
301 assert_eq!(idents, vec!["x", "y"]);
302 }
303
304 #[test]
305 fn float_literal() {
306 let tokens = tokenize("3.125").unwrap_or_default();
307 assert!(matches!(tokens[0].token, Token::Float(f) if (f - 3.125).abs() < f64::EPSILON));
308 }
309
310 #[test]
311 fn hex_literal() {
312 let tokens = tokenize("0xFF").unwrap_or_default();
313 assert_eq!(tokens[0].token, Token::Int(255));
314 }
315
316 #[test]
317 fn upper_ident() {
318 let tokens = tokenize("True Nothing MyType").unwrap_or_default();
319 assert_eq!(tokens[0].token, Token::True);
320 assert_eq!(tokens[1].token, Token::Nothing);
321 assert_eq!(tokens[2].token, Token::UpperIdent("MyType".into()));
322 }
323
324 #[test]
325 fn lambda_tokens() {
326 let tokens = tokenize("\\x -> x + 1").unwrap_or_default();
327 assert_eq!(tokens[0].token, Token::Backslash);
328 assert_eq!(tokens[1].token, Token::Ident("x".into()));
329 assert_eq!(tokens[2].token, Token::Arrow);
330 }
331
332 #[test]
333 fn edge_traversal() {
334 let tokens = tokenize("doc -> layers -> annotations").unwrap_or_default();
335 assert_eq!(tokens[0].token, Token::Ident("doc".into()));
336 assert_eq!(tokens[1].token, Token::Arrow);
337 assert_eq!(tokens[2].token, Token::Ident("layers".into()));
338 assert_eq!(tokens[3].token, Token::Arrow);
339 assert_eq!(tokens[4].token, Token::Ident("annotations".into()));
340 }
341}