use std::cell::Cell;
use std::os::raw::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr::NonNull;
use std::time::Duration;
use std::{fmt, mem, ptr};
use regex_cursor::Cursor;
use crate::grammar::IncompatibleGrammarError;
use crate::tree::{SyntaxTreeData, Tree};
use crate::{Grammar, Input, IntoInput, Point, Range};
enum ParserData {}
#[clippy::msrv = "1.76.0"]
thread_local! {
static PARSER_CACHE: Cell<Option<RawParser>> = const { Cell::new(None) };
}
struct RawParser {
ptr: NonNull<ParserData>,
}
impl Drop for RawParser {
fn drop(&mut self) {
unsafe { ts_parser_delete(self.ptr) }
}
}
pub struct Parser {
ptr: NonNull<ParserData>,
}
impl Parser {
#[must_use]
pub fn new() -> Parser {
let ptr = match PARSER_CACHE.take() {
Some(cached) => {
let ptr = cached.ptr;
mem::forget(cached);
ptr
}
None => unsafe { ts_parser_new() },
};
Parser { ptr }
}
pub fn set_grammar(&mut self, grammar: Grammar) -> Result<(), IncompatibleGrammarError> {
if unsafe { ts_parser_set_language(self.ptr, grammar) } {
Ok(())
} else {
Err(IncompatibleGrammarError {
abi_version: grammar.abi_version(),
})
}
}
pub fn set_timeout(&mut self, duration: Duration) {
#[allow(deprecated)]
unsafe {
ts_parser_set_timeout_micros(self.ptr, duration.as_micros().try_into().unwrap());
}
}
pub fn set_included_ranges(&mut self, ranges: &[Range]) -> Result<(), InvalidRangesError> {
let success = unsafe {
ts_parser_set_included_ranges(self.ptr, ranges.as_ptr(), ranges.len() as u32)
};
if success {
Ok(())
} else {
Err(InvalidRangesError)
}
}
#[must_use]
pub fn parse<I: Input>(
&mut self,
input: impl IntoInput<Input = I>,
old_tree: Option<&Tree>,
) -> Option<Tree> {
let mut input = input.into_input();
unsafe extern "C" fn read<C: Input>(
payload: NonNull<c_void>,
byte_index: u32,
_position: Point,
bytes_read: *mut u32,
) -> *const u8 {
let cursor = catch_unwind(AssertUnwindSafe(move || {
let input: &mut C = payload.cast().as_mut();
let cursor = input.cursor_at(byte_index);
let slice = cursor.chunk();
let offset: u32 = cursor.offset().try_into().unwrap();
let len: u32 = slice.len().try_into().unwrap();
(byte_index - offset, slice.as_ptr(), len)
}));
match cursor {
Ok((chunk_offset, ptr, len)) if chunk_offset < len => {
*bytes_read = len - chunk_offset;
ptr.add(chunk_offset as usize)
}
_ => {
*bytes_read = 0;
ptr::null()
}
}
}
let input = ParserInputRaw {
payload: NonNull::from(&mut input).cast(),
read: read::<I>,
encoding: InputEncoding::Utf8,
decode: None,
};
unsafe {
let old_tree = old_tree.map(|tree| tree.as_raw());
let new_tree = ts_parser_parse(self.ptr, old_tree, input);
new_tree.map(|raw| Tree::from_raw(raw))
}
}
}
impl Default for Parser {
fn default() -> Self {
Self::new()
}
}
unsafe impl Sync for Parser {}
unsafe impl Send for Parser {}
impl Drop for Parser {
fn drop(&mut self) {
PARSER_CACHE.set(Some(RawParser { ptr: self.ptr }));
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidRangesError;
impl fmt::Display for InvalidRangesError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "include ranges overlap or are not sorted",)
}
}
impl std::error::Error for InvalidRangesError {}
type TreeSitterReadFn = unsafe extern "C" fn(
payload: NonNull<c_void>,
byte_index: u32,
position: Point,
bytes_read: *mut u32,
) -> *const u8;
type DecodeInputFn =
unsafe extern "C" fn(string: *const u8, length: u32, code_point: *const i32) -> u32;
#[repr(C)]
#[derive(Debug)]
pub struct ParserInputRaw {
pub payload: NonNull<c_void>,
pub read: TreeSitterReadFn,
pub encoding: InputEncoding,
pub decode: Option<DecodeInputFn>,
}
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub enum InputEncoding {
Utf8,
Utf16LE,
Utf16BE,
Custom,
}
#[allow(unused)]
#[repr(C)]
#[derive(Debug)]
struct ParseState {
payload: NonNull<c_void>,
current_byte_offset: u32,
has_error: bool,
}
#[allow(unused)]
type ProgressCallback = unsafe extern "C" fn(state: NonNull<ParseState>) -> bool;
#[allow(unused)]
#[repr(C)]
#[derive(Debug, Default)]
struct ParseOptions {
payload: Option<NonNull<c_void>>,
progress_callback: Option<ProgressCallback>,
}
extern "C" {
fn ts_parser_new() -> NonNull<ParserData>;
fn ts_parser_delete(parser: NonNull<ParserData>);
fn ts_parser_set_language(parser: NonNull<ParserData>, language: Grammar) -> bool;
fn ts_parser_set_included_ranges(
parser: NonNull<ParserData>,
ranges: *const Range,
count: u32,
) -> bool;
fn ts_parser_parse(
parser: NonNull<ParserData>,
old_tree: Option<NonNull<SyntaxTreeData>>,
input: ParserInputRaw,
) -> Option<NonNull<SyntaxTreeData>>;
#[deprecated = "use ts_parser_parse_with_options and pass in a calback instead, this will be removed in 0.26"]
fn ts_parser_set_timeout_micros(self_: NonNull<ParserData>, timeout_micros: u64);
#[allow(unused)]
fn ts_parser_parse_with_options(
parser: NonNull<ParserData>,
old_tree: Option<NonNull<SyntaxTreeData>>,
input: ParserInputRaw,
parse_options: ParseOptions,
) -> Option<NonNull<SyntaxTreeData>>;
}