Skip to main content

jaq_core/load/
lex.rs

1//! Lexing.
2
3use alloc::vec::Vec;
4
5/// Component of a string potentially containing escape sequences.
6///
7/// `S` is a type of strings (without escape sequences), and
8/// `F` is a type of interpolated filters.
9#[derive(Clone, Debug)]
10pub enum StrPart<S, T> {
11    /// string without escape sequences
12    Str(S),
13    /// interpolated term (`\(...)`)
14    ///
15    /// Here, the contained term `T` must be of the shape
16    /// `Token(s, Tok::Block(...))` such that the first character of `s` is '('.
17    Term(T),
18    /// escaped character (e.g. `\n`, `t`, `\u0041`)
19    Char(char),
20}
21
22/// Token (tree) generic over string type `S`.
23///
24/// If the contained `Tok` is of the shape:
25/// * `Tok::Block(...)`, then `S` must start with `'('`, `'['`, or `'{'`.
26/// * `Tok::Var`, then `S` must start with `'$'`.
27#[derive(Debug)]
28pub struct Token<S>(pub S, pub Tok<S>);
29
30/// Type of token, generic over string type `S`.
31///
32/// This data structure should normally not be constructed manually.
33/// It is exposed mostly for fuzzing.
34#[derive(Debug)]
35pub enum Tok<S> {
36    /// keywords such as `def`, but also identifiers such as `map`, `f::g`
37    Word,
38    /// variables such as `$x`
39    Var,
40    /// formatters such as `@csv`
41    Fmt,
42    /// number
43    Num,
44    /// (interpolated) string, surrounded by opening and closing '"'
45    Str(Vec<StrPart<S, Token<S>>>),
46    /// symbol such as `.`, `;`, `-`, `|`, or `+=`
47    Sym,
48    /// delimited tokens, e.g. `(...)` or `[...]`
49    Block(Vec<Token<S>>),
50}
51
52/// Type of character that we expected.
53///
54/// Each variant is annoted with jq programs that trigger it.
55#[derive(Clone, Debug)]
56#[non_exhaustive]
57pub enum Expect<S> {
58    /// `0e`, `0.`
59    Digit,
60    /// `$`, `@`
61    Ident,
62    /// `(`, `[`, `{`
63    Delim(S),
64    /// `"\a"`
65    Escape,
66    /// `"\ux"`, `"\uD800"`
67    Unicode,
68    /// `&`, `§`, `💣`
69    Token,
70}
71
72impl Expect<&str> {
73    /// Return human-readable description of what we expected.
74    pub fn as_str(&self) -> &'static str {
75        match self {
76            Self::Digit => "digit",
77            Self::Ident => "identifier",
78            Self::Delim("(") => "closing parenthesis",
79            Self::Delim("[") => "closing bracket",
80            Self::Delim("{") => "closing brace",
81            Self::Delim("\"") => "closing quote",
82            Self::Delim(_) => panic!(),
83            Self::Escape => "string escape sequence",
84            Self::Unicode => "4-digit hexadecimal UTF-8 code point",
85            Self::Token => "token",
86        }
87    }
88}
89
90/// Lexer error, storing what we expected and what we got instead.
91pub type Error<S> = (Expect<S>, S);
92
93/// Lexer for jq files.
94pub struct Lexer<S> {
95    /// remaining lexer input
96    ///
97    /// When replacing `i` by a value `i'`, `i'` must be a suffix of `i`.
98    /// That is, there is some `n` such that `core::ptr::eq(&i[n..], i')`.
99    i: S,
100    /// errors accumulated during lexing
101    e: Vec<Error<S>>,
102}
103
104impl<'a> Lexer<&'a str> {
105    /// Initialise a new lexer for the given input.
106    #[must_use]
107    pub fn new(i: &'a str) -> Self {
108        let e = Vec::new();
109        Self { i, e }
110    }
111
112    /// Lex, returning the resulting tokens and errors.
113    pub fn lex(mut self) -> Result<Vec<Token<&'a str>>, Vec<Error<&'a str>>> {
114        let tokens = self.tokens();
115        self.space();
116        if !self.i.is_empty() {
117            self.e.push((Expect::Token, self.i));
118        }
119
120        if self.e.is_empty() {
121            Ok(tokens)
122        } else {
123            Err(self.e)
124        }
125    }
126
127    fn next(&mut self) -> Option<char> {
128        let mut chars = self.i.chars();
129        let c = chars.next()?;
130        self.i = chars.as_str();
131        Some(c)
132    }
133
134    fn take(&mut self, len: usize) -> &'a str {
135        let (head, tail) = self.i.split_at(len);
136        self.i = tail;
137        head
138    }
139
140    fn trim(&mut self, f: impl FnMut(char) -> bool) {
141        self.i = self.i.trim_start_matches(f);
142    }
143
144    fn consumed(&mut self, skip: usize, f: impl FnOnce(&mut Self)) -> &'a str {
145        self.with_consumed(|l| {
146            l.i = &l.i[skip..];
147            f(l);
148        })
149        .0
150    }
151
152    fn with_consumed<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (&'a str, T) {
153        let start = self.i;
154        let y = f(self);
155        (&start[..start.len() - self.i.len()], y)
156    }
157
158    /// Whitespace and comments.
159    fn space(&mut self) {
160        loop {
161            self.i = self.i.trim_start();
162            match self.i.strip_prefix('#') {
163                Some(comment) => self.i = comment,
164                None => break,
165            }
166            // ignore all lines that end with an odd number of backslashes
167            loop {
168                let no_newline = || (self.i, &self.i[self.i.len()..]);
169                let (before, after) = self.i.split_once('\n').unwrap_or_else(no_newline);
170                let before = before.strip_suffix('\r').unwrap_or(before);
171                self.i = after;
172                // does the line end with an even number of backslashes?
173                if before.chars().rev().take_while(|c| *c == '\\').count() % 2 == 0 {
174                    break;
175                }
176            }
177        }
178    }
179
180    fn mod_then_ident(&mut self) {
181        self.ident0();
182        if let Some(rest) = self.i.strip_prefix("::") {
183            self.i = rest.strip_prefix(['@', '$']).unwrap_or(rest);
184            self.ident1();
185        }
186    }
187
188    /// Lex a sequence matching `[a-zA-Z0-9_]*`.
189    fn ident0(&mut self) {
190        self.trim(|c: char| c.is_ascii_alphanumeric() || c == '_');
191    }
192
193    /// Lex a sequence matching `[a-zA-Z_][a-zA-Z0-9_]*`.
194    fn ident1(&mut self) {
195        let first = |c: char| c.is_ascii_alphabetic() || c == '_';
196        if let Some(rest) = self.i.strip_prefix(first) {
197            self.i = rest;
198            self.ident0();
199        } else {
200            self.e.push((Expect::Ident, self.i));
201        }
202    }
203
204    /// Lex a non-empty digit sequence.
205    fn digits1(&mut self) {
206        if let Some(rest) = self.i.strip_prefix(|c: char| c.is_ascii_digit()) {
207            self.i = rest.trim_start_matches(|c: char| c.is_ascii_digit());
208        } else {
209            self.e.push((Expect::Digit, self.i));
210        }
211    }
212
213    /// Decimal with optional exponent.
214    fn num(&mut self) {
215        self.trim(|c| c.is_ascii_digit());
216        if let Some(i) = self.i.strip_prefix('.') {
217            self.i = i;
218            self.digits1();
219        }
220        if let Some(i) = self.i.strip_prefix(['e', 'E']) {
221            self.i = i.strip_prefix(['+', '-']).unwrap_or(i);
222            self.digits1();
223        }
224    }
225
226    fn escape(&mut self) -> Option<StrPart<&'a str, Token<&'a str>>> {
227        let mut chars = self.i.chars();
228        let part = match chars.next() {
229            Some(c @ ('\\' | '/' | '"')) => StrPart::Char(c),
230            Some('b') => StrPart::Char('\x08'),
231            Some('f') => StrPart::Char('\x0C'),
232            Some('n') => StrPart::Char('\n'),
233            Some('r') => StrPart::Char('\r'),
234            Some('t') => StrPart::Char('\t'),
235            Some('u') => {
236                let err_at = |lex: &mut Self, pos| {
237                    lex.i = pos;
238                    lex.e.push((Expect::Unicode, lex.i));
239                    None
240                };
241                let mut hex = 0;
242                let start_i = chars.as_str();
243                for _ in 0..4 {
244                    let cur_i = chars.as_str();
245                    if let Some(digit) = chars.next().and_then(|c| c.to_digit(16)) {
246                        hex = (hex << 4) + digit;
247                    } else {
248                        return err_at(self, cur_i);
249                    }
250                }
251                match char::from_u32(hex) {
252                    None => return err_at(self, start_i),
253                    Some(c) => StrPart::Char(c),
254                }
255            }
256            Some('(') => {
257                let (full, block) = self.with_consumed(Self::block);
258                return Some(StrPart::Term(Token(full, block)));
259            }
260            Some(_) | None => {
261                self.e.push((Expect::Escape, self.i));
262                return None;
263            }
264        };
265
266        self.i = chars.as_str();
267        Some(part)
268    }
269
270    /// Lex a (possibly interpolated) string.
271    ///
272    /// The input string has to start with '"'.
273    fn str(&mut self) -> Tok<&'a str> {
274        let start = self.take(1);
275        assert_eq!(start, "\"");
276        let mut parts = Vec::new();
277
278        loop {
279            let s = self.consumed(0, |lex| lex.trim(|c| c != '\\' && c != '"'));
280            if !s.is_empty() {
281                parts.push(StrPart::Str(s));
282            }
283            match self.next() {
284                Some('"') => break,
285                Some('\\') => self.escape().map(|part| parts.push(part)),
286                // SAFETY: due to `lex.trim()`
287                Some(_) => unreachable!(),
288                None => {
289                    self.e.push((Expect::Delim(start), self.i));
290                    break;
291                }
292            };
293        }
294        Tok::Str(parts)
295    }
296
297    fn token(&mut self) -> Option<Token<&'a str>> {
298        self.space();
299
300        let hd_op = |c| "|=!<>+-*/%".contains(c);
301        let tl_op = |c| hd_op(c) && c != '-';
302
303        let mut chars = self.i.chars();
304        let (s, tok) = match chars.next()? {
305            'a'..='z' | 'A'..='Z' | '_' => (self.consumed(1, Self::mod_then_ident), Tok::Word),
306            '$' => (self.consumed(1, Self::ident1), Tok::Var),
307            '@' => (self.consumed(1, Self::ident1), Tok::Fmt),
308            '0'..='9' => (self.consumed(1, Self::num), Tok::Num),
309            c if hd_op(c) => (self.consumed(1, |lex| lex.trim(tl_op)), Tok::Sym),
310            '.' => match chars.next() {
311                Some('.') => (self.take(2), Tok::Sym),
312                Some('a'..='z' | 'A'..='Z' | '_') => (self.consumed(2, Self::ident0), Tok::Sym),
313                _ => (self.take(1), Tok::Sym),
314            },
315            ':' | ';' | ',' | '?' => (self.take(1), Tok::Sym),
316            '"' => self.with_consumed(Self::str),
317            '(' | '[' | '{' => self.with_consumed(Self::block),
318            _ => return None,
319        };
320        Some(Token(s, tok))
321    }
322
323    fn tokens(&mut self) -> Vec<Token<&'a str>> {
324        core::iter::from_fn(|| self.token()).collect()
325    }
326
327    /// Lex a sequence of tokens that is surrounded by parentheses, curly braces, or brackets.
328    ///
329    /// The input string has to start with either '(', '[', or '{'.
330    fn block(&mut self) -> Tok<&'a str> {
331        let open = self.take(1);
332        let close = match open {
333            "(" => ')',
334            "[" => ']',
335            "{" => '}',
336            _ => panic!(),
337        };
338        let mut tokens = self.tokens();
339
340        self.space();
341        if let Some(rest) = self.i.strip_prefix(close) {
342            tokens.push(Token(&self.i[..1], Tok::Sym));
343            self.i = rest;
344        } else {
345            self.e.push((Expect::Delim(open), self.i));
346        }
347        Tok::Block(tokens)
348    }
349}
350
351impl<'a> Token<&'a str> {
352    /// Return the string slice corresponding to an optional token.
353    ///
354    /// If the token is not present, return an empty string slice starting at the end of `code`.
355    pub fn opt_as_str(found: Option<&Self>, code: &'a str) -> &'a str {
356        found.map_or(&code[code.len()..], |found| found.as_str())
357    }
358
359    /// Return the string slice corresponding to the token.
360    pub fn as_str(&self) -> &'a str {
361        self.0
362    }
363}