use std::cell::Cell;
use std::ops::ControlFlow;
use std::os::raw::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr::NonNull;
use std::time::{Duration, Instant};
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_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 raw_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());
ts_parser_parse(self.ptr, old_tree, raw_input).map(|raw| Tree::from_raw(raw))
}
}
#[must_use]
pub fn parse_with_options<I: Input>(
&mut self,
input: impl IntoInput<Input = I>,
old_tree: Option<&Tree>,
mut options: ParseOptions<'_>,
) -> 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 raw_input = ParserInputRaw {
payload: NonNull::from(&mut input).cast(),
read: read::<I>,
encoding: InputEncoding::Utf8,
decode: None,
};
unsafe extern "C" fn progress_cb(raw_state: NonNull<RawParseState>) -> bool {
let raw_ref = raw_state.as_ref();
let cb: *mut &mut dyn FnMut(&ParseState) -> ControlFlow<()> =
raw_ref.payload.as_ptr().cast();
let public_state = ParseState {
current_byte_offset: raw_ref.current_byte_offset,
has_error: raw_ref.has_error,
};
(*cb)(&public_state).is_break()
}
let raw_options = RawParseOptions {
payload: unsafe {
Some(NonNull::new_unchecked(
ptr::addr_of_mut!(options.callback).cast(),
))
},
progress_callback: Some(progress_cb),
};
unsafe {
let old_tree = old_tree.map(|tree| tree.as_raw());
ts_parser_parse_with_options(self.ptr, old_tree, raw_input, raw_options)
.map(|raw| Tree::from_raw(raw))
}
}
#[must_use]
pub fn parse_with_timeout<I: Input>(
&mut self,
input: impl IntoInput<Input = I>,
old_tree: Option<&Tree>,
timeout: Duration,
) -> Option<Tree> {
let deadline = Instant::now() + timeout;
let mut check = |_: &ParseState| {
if Instant::now() >= deadline {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
};
self.parse_with_options(input, old_tree, ParseOptions::new(&mut check))
}
}
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, Clone, Copy)]
pub struct ParseState {
pub current_byte_offset: u32,
pub has_error: bool,
}
pub struct ParseOptions<'a> {
callback: &'a mut dyn FnMut(&ParseState) -> ControlFlow<()>,
}
impl<'a> ParseOptions<'a> {
pub fn new(callback: &'a mut impl FnMut(&ParseState) -> ControlFlow<()>) -> ParseOptions<'a> {
ParseOptions { callback }
}
}
#[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,
}
#[repr(C)]
#[derive(Debug)]
struct RawParseState {
payload: NonNull<c_void>,
current_byte_offset: u32,
has_error: bool,
}
type ProgressCallback = unsafe extern "C" fn(state: NonNull<RawParseState>) -> bool;
#[repr(C)]
#[derive(Debug, Default)]
struct RawParseOptions {
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>>;
fn ts_parser_parse_with_options(
parser: NonNull<ParserData>,
old_tree: Option<NonNull<SyntaxTreeData>>,
input: ParserInputRaw,
parse_options: RawParseOptions,
) -> Option<NonNull<SyntaxTreeData>>;
}