Skip to main content

clickhouse_lexer_sys/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::ffi::{c_char, c_void};
5use std::fmt;
6use std::str::Utf8Error;
7
8/// The C API of `upstream/clickhouse_lexer.h`, as compiled by this crate.
9pub mod ffi {
10    use std::ffi::{c_char, c_int, c_uchar, c_void};
11
12    extern "C" {
13        /// Size in bytes of the lexer state `clickhouse_lexer_create` initializes.
14        pub static clickhouse_lexer_size: usize;
15        /// Placement-constructs a lexer over `[begin, end)` into `ptr`.
16        pub fn clickhouse_lexer_create(
17            ptr: *mut c_void,
18            begin: *const c_char,
19            end: *const c_char,
20            max_query_size: usize,
21        );
22        /// Returns the next token's type and writes its bounds to the out-pointers.
23        pub fn clickhouse_lexer_next_token(
24            ptr: *mut c_void,
25            out_token_begin: *mut *const c_char,
26            out_token_end: *mut *const c_char,
27        ) -> c_uchar;
28        /// Whether the type is neither whitespace nor a comment.
29        pub fn clickhouse_lexer_token_is_significant(token: c_uchar) -> c_int;
30        /// Whether the type is one of the error types.
31        pub fn clickhouse_lexer_token_is_error(token: c_uchar) -> c_int;
32        /// Whether the type is `EndOfStream`.
33        pub fn clickhouse_lexer_token_is_end(token: c_uchar) -> c_int;
34    }
35}
36
37include!(concat!(env!("OUT_DIR"), "/generated.rs"));
38
39impl TokenKind {
40    /// Neither whitespace nor a comment.
41    pub fn is_significant(self) -> bool {
42        !matches!(self, TokenKind::Whitespace | TokenKind::Comment)
43    }
44
45    /// One of the `Error*` kinds, which end lexing.
46    pub fn is_error(self) -> bool {
47        self > TokenKind::EndOfStream
48    }
49
50    /// `EndOfStream`.
51    pub fn is_end(self) -> bool {
52        self == TokenKind::EndOfStream
53    }
54}
55
56impl fmt::Display for TokenKind {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.write_str(self.name())
59    }
60}
61
62/// A token of the input: its kind and the byte range it covers.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub struct Token<'a> {
65    /// The token's kind.
66    pub kind: TokenKind,
67    /// Byte offset into the input where the token starts.
68    pub begin: usize,
69    /// Byte offset into the input where the token ends (exclusive).
70    pub end: usize,
71    /// The bytes of the input the token covers.
72    pub text: &'a [u8],
73}
74
75impl<'a> Token<'a> {
76    /// The token text as a string slice, if it is valid UTF-8.
77    pub fn text_str(&self) -> Result<&'a str, Utf8Error> {
78        std::str::from_utf8(self.text)
79    }
80}
81
82/// ClickHouse's lexer over one input.
83///
84/// Iterating yields the tokens that tile the input from the start, whitespace and
85/// comments included. Iteration ends after the first error token, which is yielded,
86/// or at the end of the input, where `EndOfStream` is not yielded.
87pub struct Lexer<'a> {
88    input: &'a [u8],
89    // u64 slots keep the pointer fields of the C++ Lexer aligned.
90    state: Box<[u64]>,
91    previous_end: usize,
92    done: bool,
93}
94
95impl<'a> Lexer<'a> {
96    /// A lexer over `input` with no query size limit.
97    pub fn new(input: &'a (impl AsRef<[u8]> + ?Sized)) -> Self {
98        Self::with_max_query_size(input, 0)
99    }
100
101    /// A lexer over `input` that reports `ErrorMaxQuerySizeExceeded` once a token ends
102    /// past `max_query_size` bytes; `0` means unlimited.
103    pub fn with_max_query_size(
104        input: &'a (impl AsRef<[u8]> + ?Sized),
105        max_query_size: usize,
106    ) -> Self {
107        let input = input.as_ref();
108        // SAFETY: a plain `size_t` the C++ side initializes statically.
109        let size = unsafe { ffi::clickhouse_lexer_size };
110        let mut state = vec![0u64; size.div_ceil(8)].into_boxed_slice();
111        let begin = input.as_ptr().cast::<c_char>();
112        // SAFETY: `state` holds `clickhouse_lexer_size` bytes, and `input` outlives
113        // `self`, so the pointers stored in the state stay valid.
114        unsafe {
115            ffi::clickhouse_lexer_create(
116                state.as_mut_ptr().cast::<c_void>(),
117                begin,
118                begin.add(input.len()),
119                max_query_size,
120            );
121        }
122        Self {
123            input,
124            state,
125            previous_end: 0,
126            done: false,
127        }
128    }
129
130    /// The input being lexed.
131    pub fn input(&self) -> &'a [u8] {
132        self.input
133    }
134
135    /// The next token straight from the lexer, including `EndOfStream`.
136    ///
137    /// Unlike iteration this never stops: after an error or the end of the input the
138    /// lexer keeps returning the same token.
139    pub fn next_token(&mut self) -> Token<'a> {
140        let mut begin = std::ptr::null();
141        let mut end = std::ptr::null();
142        // SAFETY: the state was initialized by `clickhouse_lexer_create` over `input`.
143        let raw = unsafe {
144            ffi::clickhouse_lexer_next_token(
145                self.state.as_mut_ptr().cast::<c_void>(),
146                &mut begin,
147                &mut end,
148            )
149        };
150        let kind = TokenKind::from_raw(raw)
151            .unwrap_or_else(|| panic!("ClickHouse lexer returned unknown token type {raw}"));
152        let begin = self.offset(begin);
153        let end = self.offset(end);
154        Token {
155            kind,
156            begin,
157            end,
158            text: &self.input[begin..end],
159        }
160    }
161
162    /// Byte offset of a pointer the lexer returned, which always lies inside the input.
163    fn offset(&self, ptr: *const c_char) -> usize {
164        let offset = (ptr as usize).wrapping_sub(self.input.as_ptr() as usize);
165        assert!(
166            offset <= self.input.len(),
167            "ClickHouse lexer returned a pointer outside the input"
168        );
169        offset
170    }
171}
172
173impl<'a> Iterator for Lexer<'a> {
174    type Item = Token<'a>;
175
176    fn next(&mut self) -> Option<Token<'a>> {
177        if self.done {
178            return None;
179        }
180        let token = self.next_token();
181        if token.kind.is_end() {
182            self.done = true;
183            return None;
184        }
185        if token.kind.is_error() {
186            self.done = true;
187            return Some(token);
188        }
189        // Non-error tokens tile the input, so each one must advance.
190        assert!(
191            token.end > self.previous_end,
192            "ClickHouse lexer stopped advancing at byte {} of {}",
193            token.end,
194            self.input.len()
195        );
196        self.previous_end = token.end;
197        Some(token)
198    }
199}
200
201impl fmt::Debug for Lexer<'_> {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        f.debug_struct("Lexer")
204            .field("input_len", &self.input.len())
205            .field("previous_end", &self.previous_end)
206            .field("done", &self.done)
207            .finish()
208    }
209}