ephemeris_documents/json/
lexer.rs1use crate::error::{JsonError, JsonErrorKind};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub(super) enum TokenKind {
15 OpenBrace,
16 CloseBrace,
17 OpenBracket,
18 CloseBracket,
19 Colon,
20 Comma,
21 String(Option<String>),
24 Identifier(String),
26 Number,
27 Whitespace,
28 LineComment,
29 BlockComment,
30}
31
32impl TokenKind {
33 pub(super) const fn is_trivia(&self) -> bool {
34 matches!(
35 self,
36 Self::Whitespace | Self::LineComment | Self::BlockComment
37 )
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub(super) struct Token<'a> {
43 pub(super) kind: TokenKind,
44 pub(super) start: usize,
45 pub(super) text: &'a str,
46}
47
48pub(super) struct Lexer<'a> {
49 source: &'a str,
50 position: usize,
51}
52
53impl<'a> Lexer<'a> {
54 pub(super) fn tokenize(source: &'a str) -> Result<Vec<Token<'a>>, JsonError> {
59 let mut lexer = Self {
60 source,
61 position: 0,
62 };
63 let mut tokens = Vec::new();
64 while let Some(token) = lexer.token()? {
65 tokens.push(token);
66 }
67 Ok(tokens)
68 }
69
70 pub(super) const fn is_line_terminator(character: char) -> bool {
71 matches!(character, '\n' | '\r' | '\u{2028}' | '\u{2029}')
72 }
73
74 const fn is_whitespace(character: char) -> bool {
75 matches!(
76 character,
77 '\t' | '\u{b}' | '\u{c}' | ' ' | '\u{a0}' | '\u{1680}' | '\u{2000}'
78 ..='\u{200a}' | '\u{202f}' | '\u{205f}' | '\u{3000}' | '\u{feff}'
79 )
80 }
81
82 fn is_identifier_start(character: char) -> bool {
83 character.is_alphabetic() || character == '$' || character == '_'
84 }
85
86 fn is_identifier_part(character: char) -> bool {
87 character.is_alphanumeric() || matches!(character, '$' | '_' | '\u{200c}' | '\u{200d}')
88 }
89
90 const fn error(offset: usize, kind: JsonErrorKind) -> JsonError {
91 JsonError { offset, kind }
92 }
93
94 fn rest(&self) -> &'a str {
95 self.source.get(self.position..).unwrap_or_default()
96 }
97
98 fn peek(&self) -> Option<char> {
99 self.rest().chars().next()
100 }
101
102 fn peek_at(&self, index: usize) -> Option<char> {
103 self.rest().chars().nth(index)
104 }
105
106 fn bump(&mut self) -> Option<char> {
107 let character = self.peek()?;
108 self.position = self.position.saturating_add(character.len_utf8());
109 Some(character)
110 }
111
112 fn eat(&mut self, expected: char) -> bool {
113 let found = self.peek() == Some(expected);
114 if found {
115 self.bump();
116 }
117 found
118 }
119
120 fn eat_while(&mut self, accept: impl Fn(char) -> bool) -> usize {
122 let mut count = 0usize;
123 while self.peek().is_some_and(&accept) {
124 self.bump();
125 count = count.saturating_add(1);
126 }
127 count
128 }
129
130 fn token(&mut self) -> Result<Option<Token<'a>>, JsonError> {
131 let start = self.position;
132 let Some(first) = self.peek() else {
133 return Ok(None);
134 };
135 let kind = match first {
136 '{' => self.punctuation(TokenKind::OpenBrace),
137 '}' => self.punctuation(TokenKind::CloseBrace),
138 '[' => self.punctuation(TokenKind::OpenBracket),
139 ']' => self.punctuation(TokenKind::CloseBracket),
140 ':' => self.punctuation(TokenKind::Colon),
141 ',' => self.punctuation(TokenKind::Comma),
142 '"' | '\'' => TokenKind::String(self.string(first)?),
143 '/' => self.comment()?,
144 '+' | '-' | '.' | '0'..='9' => self.number()?,
145 '\\' => TokenKind::Identifier(self.identifier()?),
146 character if Self::is_identifier_start(character) => {
147 TokenKind::Identifier(self.identifier()?)
148 }
149 character if Self::is_whitespace(character) || Self::is_line_terminator(character) => {
150 self.eat_while(|next| Self::is_whitespace(next) || Self::is_line_terminator(next));
151 TokenKind::Whitespace
152 }
153 found => {
154 return Err(Self::error(
155 start,
156 JsonErrorKind::UnexpectedCharacter { found },
157 ));
158 }
159 };
160 let text = self.source.get(start..self.position).unwrap_or_default();
161 Ok(Some(Token { kind, start, text }))
162 }
163
164 fn punctuation(&mut self, kind: TokenKind) -> TokenKind {
165 self.bump();
166 kind
167 }
168
169 fn comment(&mut self) -> Result<TokenKind, JsonError> {
170 let start = self.position;
171 match self.peek_at(1) {
172 Some('/') => {
173 self.eat_while(|next| !Self::is_line_terminator(next));
174 Ok(TokenKind::LineComment)
175 }
176 Some('*') => {
177 let body = self.rest().get(2..).unwrap_or_default();
178 let Some(end) = body.find("*/") else {
179 return Err(Self::error(start, JsonErrorKind::UnterminatedComment));
180 };
181 self.position = self
182 .position
183 .saturating_add(2)
184 .saturating_add(end)
185 .saturating_add(2);
186 Ok(TokenKind::BlockComment)
187 }
188 _ => Err(Self::error(
189 start,
190 JsonErrorKind::UnexpectedCharacter { found: '/' },
191 )),
192 }
193 }
194
195 fn string(&mut self, quote: char) -> Result<Option<String>, JsonError> {
196 let start = self.position;
197 self.bump();
198 let mut decoded = Some(String::new());
199 loop {
200 match self.bump() {
201 None | Some('\n' | '\r') => {
202 return Err(Self::error(start, JsonErrorKind::UnterminatedString));
203 }
204 Some(character) if character == quote => return Ok(decoded),
205 Some('\\') => self.escape(start, &mut decoded)?,
206 Some(character) => Self::push(&mut decoded, character),
207 }
208 }
209 }
210
211 fn push(decoded: &mut Option<String>, character: char) {
212 if let Some(text) = decoded {
213 text.push(character);
214 }
215 }
216
217 fn escape(
219 &mut self,
220 string_start: usize,
221 decoded: &mut Option<String>,
222 ) -> Result<(), JsonError> {
223 let backslash = self.position.saturating_sub(1);
224 let invalid = || Self::error(backslash, JsonErrorKind::InvalidEscape);
225 let Some(character) = self.bump() else {
226 return Err(Self::error(string_start, JsonErrorKind::UnterminatedString));
227 };
228 let value = match character {
229 'b' => '\u{8}',
230 'f' => '\u{c}',
231 'n' => '\n',
232 'r' => '\r',
233 't' => '\t',
234 'v' => '\u{b}',
235 '0' if self.peek().is_some_and(|next| next.is_ascii_digit()) => {
236 return Err(invalid());
237 }
238 '0' => '\0',
239 '1'..='9' => return Err(invalid()),
240 'x' => {
241 let code = self.hex_digits(2).ok_or_else(invalid)?;
242 char::from_u32(code).ok_or_else(invalid)?
243 }
244 'u' => {
245 let code = self.hex_digits(4).ok_or_else(invalid)?;
246 let Some(value) = self
247 .surrogate_pair(code)
248 .map_or_else(|| char::from_u32(code), Some)
249 else {
250 *decoded = None;
251 return Ok(());
252 };
253 value
254 }
255 '\r' => {
256 self.eat('\n');
257 return Ok(());
258 }
259 '\n' | '\u{2028}' | '\u{2029}' => return Ok(()),
260 other => other,
261 };
262 Self::push(decoded, value);
263 Ok(())
264 }
265
266 fn surrogate_pair(&mut self, high: u32) -> Option<char> {
268 if !(0xD800..=0xDBFF).contains(&high) {
269 return None;
270 }
271 let saved = self.position;
272 if self.eat('\\')
273 && self.eat('u')
274 && let Some(low) = self.hex_digits(4)
275 && (0xDC00..=0xDFFF).contains(&low)
276 {
277 let offset = high.saturating_sub(0xD800).checked_shl(10)?;
278 let code = offset
279 .saturating_add(low.saturating_sub(0xDC00))
280 .saturating_add(0x1_0000);
281 return char::from_u32(code);
282 }
283 self.position = saved;
284 None
285 }
286
287 fn hex_digits(&mut self, count: usize) -> Option<u32> {
288 let mut value = 0u32;
289 for _ in 0..count {
290 let digit = self.peek()?.to_digit(16)?;
291 self.bump();
292 value = value.checked_mul(16)?.checked_add(digit)?;
293 }
294 Some(value)
295 }
296
297 fn identifier(&mut self) -> Result<String, JsonError> {
298 let mut name = String::new();
299 loop {
300 let first = name.is_empty();
301 let accepts = |character: char| {
302 if first {
303 Self::is_identifier_start(character)
304 } else {
305 Self::is_identifier_part(character)
306 }
307 };
308 match self.peek() {
309 Some('\\') => {
310 let backslash = self.position;
311 self.bump();
312 let invalid = || Self::error(backslash, JsonErrorKind::InvalidEscape);
313 if !self.eat('u') {
314 return Err(invalid());
315 }
316 let code = self.hex_digits(4).ok_or_else(invalid)?;
317 let character = char::from_u32(code)
318 .filter(|&character| accepts(character))
319 .ok_or_else(invalid)?;
320 name.push(character);
321 }
322 Some(character) if accepts(character) => {
323 self.bump();
324 name.push(character);
325 }
326 _ => return Ok(name),
327 }
328 }
329 }
330
331 fn number(&mut self) -> Result<TokenKind, JsonError> {
332 let start = self.position;
333 let invalid = || Self::error(start, JsonErrorKind::InvalidNumber);
334 if matches!(self.peek(), Some('+' | '-')) {
335 self.bump();
336 }
337 match self.peek() {
338 Some('I' | 'N') => {
339 let word_start = self.position;
340 self.eat_while(|next| next.is_ascii_alphabetic());
341 let word = self.source.get(word_start..self.position);
342 if !matches!(word, Some("Infinity" | "NaN")) {
343 return Err(invalid());
344 }
345 }
346 Some('0') if matches!(self.peek_at(1), Some('x' | 'X')) => {
347 self.bump();
348 self.bump();
349 if self.eat_while(|next| next.is_ascii_hexdigit()) == 0 {
350 return Err(invalid());
351 }
352 }
353 _ => {
354 if !self.decimal() {
355 return Err(invalid());
356 }
357 }
358 }
359 if self
360 .peek()
361 .is_some_and(|next| Self::is_identifier_part(next) || next == '\\')
362 {
363 return Err(invalid());
364 }
365 Ok(TokenKind::Number)
366 }
367
368 fn decimal(&mut self) -> bool {
371 let leading_zero = self.peek() == Some('0');
372 let integer = self.eat_while(|next| next.is_ascii_digit());
373 if leading_zero && integer > 1 {
374 return false;
375 }
376 let fraction = if self.eat('.') {
377 self.eat_while(|next| next.is_ascii_digit())
378 } else {
379 0
380 };
381 if integer == 0 && fraction == 0 {
382 return false;
383 }
384 if matches!(self.peek(), Some('e' | 'E')) {
385 self.bump();
386 if matches!(self.peek(), Some('+' | '-')) {
387 self.bump();
388 }
389 if self.eat_while(|next| next.is_ascii_digit()) == 0 {
390 return false;
391 }
392 }
393 true
394 }
395}