#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use std::ffi::{c_char, c_void};
use std::fmt;
use std::str::Utf8Error;
pub mod ffi {
use std::ffi::{c_char, c_int, c_uchar, c_void};
extern "C" {
pub static clickhouse_lexer_size: usize;
pub fn clickhouse_lexer_create(
ptr: *mut c_void,
begin: *const c_char,
end: *const c_char,
max_query_size: usize,
);
pub fn clickhouse_lexer_next_token(
ptr: *mut c_void,
out_token_begin: *mut *const c_char,
out_token_end: *mut *const c_char,
) -> c_uchar;
pub fn clickhouse_lexer_token_is_significant(token: c_uchar) -> c_int;
pub fn clickhouse_lexer_token_is_error(token: c_uchar) -> c_int;
pub fn clickhouse_lexer_token_is_end(token: c_uchar) -> c_int;
}
}
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
impl TokenKind {
pub fn is_significant(self) -> bool {
!matches!(self, TokenKind::Whitespace | TokenKind::Comment)
}
pub fn is_error(self) -> bool {
self > TokenKind::EndOfStream
}
pub fn is_end(self) -> bool {
self == TokenKind::EndOfStream
}
}
impl fmt::Display for TokenKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Token<'a> {
pub kind: TokenKind,
pub begin: usize,
pub end: usize,
pub text: &'a [u8],
}
impl<'a> Token<'a> {
pub fn text_str(&self) -> Result<&'a str, Utf8Error> {
std::str::from_utf8(self.text)
}
}
pub struct Lexer<'a> {
input: &'a [u8],
state: Box<[u64]>,
previous_end: usize,
done: bool,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a (impl AsRef<[u8]> + ?Sized)) -> Self {
Self::with_max_query_size(input, 0)
}
pub fn with_max_query_size(
input: &'a (impl AsRef<[u8]> + ?Sized),
max_query_size: usize,
) -> Self {
let input = input.as_ref();
let size = unsafe { ffi::clickhouse_lexer_size };
let mut state = vec![0u64; size.div_ceil(8)].into_boxed_slice();
let begin = input.as_ptr().cast::<c_char>();
unsafe {
ffi::clickhouse_lexer_create(
state.as_mut_ptr().cast::<c_void>(),
begin,
begin.add(input.len()),
max_query_size,
);
}
Self {
input,
state,
previous_end: 0,
done: false,
}
}
pub fn input(&self) -> &'a [u8] {
self.input
}
pub fn next_token(&mut self) -> Token<'a> {
let mut begin = std::ptr::null();
let mut end = std::ptr::null();
let raw = unsafe {
ffi::clickhouse_lexer_next_token(
self.state.as_mut_ptr().cast::<c_void>(),
&mut begin,
&mut end,
)
};
let kind = TokenKind::from_raw(raw)
.unwrap_or_else(|| panic!("ClickHouse lexer returned unknown token type {raw}"));
let begin = self.offset(begin);
let end = self.offset(end);
Token {
kind,
begin,
end,
text: &self.input[begin..end],
}
}
fn offset(&self, ptr: *const c_char) -> usize {
let offset = (ptr as usize).wrapping_sub(self.input.as_ptr() as usize);
assert!(
offset <= self.input.len(),
"ClickHouse lexer returned a pointer outside the input"
);
offset
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Token<'a>> {
if self.done {
return None;
}
let token = self.next_token();
if token.kind.is_end() {
self.done = true;
return None;
}
if token.kind.is_error() {
self.done = true;
return Some(token);
}
assert!(
token.end > self.previous_end,
"ClickHouse lexer stopped advancing at byte {} of {}",
token.end,
self.input.len()
);
self.previous_end = token.end;
Some(token)
}
}
impl fmt::Debug for Lexer<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Lexer")
.field("input_len", &self.input.len())
.field("previous_end", &self.previous_end)
.field("done", &self.done)
.finish()
}
}