1use crate::error::Error;
10use crate::syntax::{Position, VarName};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Token {
15 kind: TokenKind,
16 at: Position,
17}
18
19impl Token {
20 #[must_use]
22 pub fn kind(&self) -> &TokenKind {
23 &self.kind
24 }
25
26 #[must_use]
28 pub fn at(&self) -> Position {
29 self.at
30 }
31
32 fn new(kind: TokenKind, at: Position) -> Self {
33 Self { kind, at }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum TokenKind {
40 Ident(VarName),
42 KwLet,
44 KwIn,
46 KwFix,
48 Lambda,
50 Dot,
52 Equals,
54 LParen,
56 RParen,
58}
59
60impl std::fmt::Display for TokenKind {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 Self::Ident(name) => write!(f, "identifier {:?}", name.as_str()),
64 Self::KwLet => f.write_str("keyword `let`"),
65 Self::KwIn => f.write_str("keyword `in`"),
66 Self::KwFix => f.write_str("keyword `fix`"),
67 Self::Lambda => f.write_str("`\\`"),
68 Self::Dot => f.write_str("`.`"),
69 Self::Equals => f.write_str("`=`"),
70 Self::LParen => f.write_str("`(`"),
71 Self::RParen => f.write_str("`)`"),
72 }
73 }
74}
75
76enum Step {
78 End,
79 Byte(u8),
80}
81
82fn peek(src: &[u8], pos: usize) -> Step {
83 src.get(pos).copied().map_or(Step::End, Step::Byte)
84}
85
86pub fn lex(src: &str) -> Result<Vec<Token>, Error> {
105 step(src.as_bytes(), 0, Vec::new())
106}
107
108fn step(src: &[u8], pos: usize, acc: Vec<Token>) -> Result<Vec<Token>, Error> {
109 match peek(src, pos) {
110 Step::End => Ok(acc),
111 Step::Byte(b) => take_token(src, pos, acc, b),
112 }
113}
114
115fn take_token(src: &[u8], pos: usize, acc: Vec<Token>, b: u8) -> Result<Vec<Token>, Error> {
116 match b {
117 b' ' | b'\t' | b'\n' | b'\r' => step(src, pos + 1, acc),
118 b'\\' => emit(src, pos + 1, acc, Token::new(TokenKind::Lambda, pos.into())),
119 b'.' => emit(src, pos + 1, acc, Token::new(TokenKind::Dot, pos.into())),
120 b'=' => emit(src, pos + 1, acc, Token::new(TokenKind::Equals, pos.into())),
121 b'(' => emit(src, pos + 1, acc, Token::new(TokenKind::LParen, pos.into())),
122 b')' => emit(src, pos + 1, acc, Token::new(TokenKind::RParen, pos.into())),
123 other if is_ident_start(other) => read_ident(src, pos, acc),
124 other => Err(Error::UnexpectedChar {
125 at: pos.into(),
126 ch: char::from(other),
127 }),
128 }
129}
130
131fn emit(src: &[u8], next_pos: usize, acc: Vec<Token>, token: Token) -> Result<Vec<Token>, Error> {
132 step(src, next_pos, push(acc, token))
133}
134
135fn push(acc: Vec<Token>, token: Token) -> Vec<Token> {
136 acc.into_iter().chain(std::iter::once(token)).collect()
137}
138
139fn read_ident(src: &[u8], start: usize, acc: Vec<Token>) -> Result<Vec<Token>, Error> {
140 let end = scan_ident(src, start);
141 let slice = src.get(start..end).unwrap_or(&[]);
142 let token = classify_ident(slice, start);
143 step(src, end, push(acc, token))
144}
145
146fn scan_ident(src: &[u8], pos: usize) -> usize {
147 src.get(pos)
148 .copied()
149 .filter(|b| is_ident_continue(*b))
150 .map_or(pos, |_| scan_ident(src, pos + 1))
151}
152
153fn classify_ident(slice: &[u8], start: usize) -> Token {
154 let at = Position::from(start);
155 match slice {
156 b"let" => Token::new(TokenKind::KwLet, at),
157 b"in" => Token::new(TokenKind::KwIn, at),
158 b"fix" => Token::new(TokenKind::KwFix, at),
159 bytes => Token::new(
160 TokenKind::Ident(VarName::from(
161 std::str::from_utf8(bytes).unwrap_or_default(),
162 )),
163 at,
164 ),
165 }
166}
167
168fn is_ident_start(b: u8) -> bool {
169 b.is_ascii_alphabetic() || b == b'_'
170}
171
172fn is_ident_continue(b: u8) -> bool {
173 b.is_ascii_alphanumeric() || b == b'_'
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn lex_identity_lambda() -> Result<(), Error> {
182 let tokens = lex("\\x. x")?;
183 let kinds: Vec<TokenKind> = tokens.iter().map(|t| t.kind().clone()).collect();
184 let expected = vec![
185 TokenKind::Lambda,
186 TokenKind::Ident(VarName::from("x")),
187 TokenKind::Dot,
188 TokenKind::Ident(VarName::from("x")),
189 ];
190 (kinds == expected)
191 .then_some(())
192 .ok_or(Error::UnexpectedEnd {
193 expected: "identity tokenization",
194 })
195 }
196
197 #[test]
198 fn lex_let_keyword() -> Result<(), Error> {
199 let tokens = lex("let x = y in x")?;
200 let kinds: Vec<TokenKind> = tokens.iter().map(|t| t.kind().clone()).collect();
201 let expected = vec![
202 TokenKind::KwLet,
203 TokenKind::Ident(VarName::from("x")),
204 TokenKind::Equals,
205 TokenKind::Ident(VarName::from("y")),
206 TokenKind::KwIn,
207 TokenKind::Ident(VarName::from("x")),
208 ];
209 (kinds == expected)
210 .then_some(())
211 .ok_or(Error::UnexpectedEnd {
212 expected: "let tokenization",
213 })
214 }
215
216 #[test]
217 fn lex_rejects_non_ascii() -> Result<(), Error> {
218 let result = lex("λx. x");
219 match result {
220 Err(Error::UnexpectedChar { .. }) => Ok(()),
221 Err(other) => Err(other),
222 Ok(_) => Err(Error::UnexpectedEnd {
223 expected: "rejection of non-ASCII source",
224 }),
225 }
226 }
227}