use core::ffi::{c_char, c_void};
use core::fmt::Write;
use core::ptr;
use crate::ffi::{
TSInput, TSInputEncoding, TSInputEncodingUTF8, TSLanguage, TSLogger, TSParseOptions,
TSParseState, TSPoint, TSRange, TSStateId, TSSymbol, TREE_SITTER_LANGUAGE_VERSION,
TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION,
};
use super::alloc::{free, malloc};
use super::error_costs::ERROR_COST_PER_SKIPPED_TREE;
use super::language::{
language_full, language_table_entry, language_table_entry_from_index, TableEntry,
};
use super::length::length_zero;
use super::lexer::{
lexer_delete, lexer_included_ranges, lexer_included_ranges_slice, lexer_new, lexer_reset,
lexer_set_included_ranges, lexer_set_input, Lexer,
};
use super::reduce_action::ReduceActionSet;
use super::stack::{stack_clear, stack_delete, stack_new, Stack, StackVersion};
use super::subtree::{
subtree_array_prepare_scratch, subtree_pool_delete, subtree_pool_new,
subtree_pool_prepare_for_parse, subtree_pool_retain_arena, subtree_publish, Subtree,
SubtreeArray, SubtreePool, NULL_SUBTREE,
};
use super::tree::TSTree;
use super::utils::Array;
use super::utils::{ptr_mut, ptr_ref};
extern "C" {
fn fprintf(f: *mut c_void, fmt: *const i8, ...) -> i32;
fn fputs(s: *const i8, f: *mut c_void) -> i32;
fn fputc(c: i32, f: *mut c_void) -> i32;
#[cfg(not(target_os = "windows"))]
fn fdopen(fd: i32, mode: *const i8) -> *mut c_void;
fn fclose(f: *mut c_void) -> i32;
}
const MAX_VERSION_COUNT: u32 = 6;
const MAX_VERSION_COUNT_OVERFLOW: u32 = 4;
const MAX_SUMMARY_DEPTH: u32 = 16;
const MAX_COST_DIFFERENCE: u32 = 18 * ERROR_COST_PER_SKIPPED_TREE;
const OP_COUNT_PER_PARSER_CALLBACK_CHECK: u32 = 100;
struct TokenCache {
token: Subtree,
last_external_token: Subtree,
byte_index: u32,
}
#[derive(Clone, Copy)]
struct GotoRow {
start: u32,
length: u32,
}
#[derive(Clone, Copy)]
struct GotoEntry {
symbol: TSSymbol,
state: TSStateId,
}
#[derive(Clone, Copy)]
struct ActionEntry {
symbol: TSSymbol,
action_index: u16,
}
struct ActionTable {
rows: Array<GotoRow>,
entries: Array<ActionEntry>,
large_state_count: u32,
token_count: u32,
}
impl ActionTable {
const fn new() -> Self {
Self {
rows: Array::new(),
entries: Array::new(),
large_state_count: 0,
token_count: 0,
}
}
unsafe fn clear(&mut self) {
self.rows.clear();
self.entries.clear();
self.large_state_count = 0;
self.token_count = 0;
}
unsafe fn delete(&mut self) {
if !self.rows.contents.is_null() {
self.rows.delete();
}
if !self.entries.contents.is_null() {
self.entries.delete();
}
}
unsafe fn rebuild(&mut self, language: *const TSLanguage) {
self.clear();
if language.is_null() {
return;
}
let language = language_full(language);
self.large_state_count = language.large_state_count;
self.token_count = language.token_count;
self.rows
.reserve(language.state_count - language.large_state_count);
for state in language.large_state_count..language.state_count {
let table_index = *language
.small_parse_table_map
.add((state - language.large_state_count) as usize);
self.append_small_row(language.small_parse_table.add(table_index as usize));
}
}
unsafe fn append_small_row(&mut self, mut data: *const u16) {
let row_start = self.entries.size;
let group_count = u32::from(*data);
data = data.add(1);
for _ in 0..group_count {
let action_index = *data;
let group_symbol_count = u32::from(*data.add(1));
data = data.add(2);
for _ in 0..group_symbol_count {
let symbol = *data;
if u32::from(symbol) < self.token_count {
self.entries.push(ActionEntry {
symbol,
action_index,
});
}
data = data.add(1);
}
}
let row_length = self.entries.size - row_start;
let row =
&mut self.entries.as_mut_slice()[row_start as usize..(row_start + row_length) as usize];
for i in 1..row.len() {
let entry = row[i];
let mut j = i;
while j > 0 && row[j - 1].symbol > entry.symbol {
row[j] = row[j - 1];
j -= 1;
}
row[j] = entry;
}
self.rows.push(GotoRow {
start: row_start,
length: row_length,
});
}
#[inline]
unsafe fn lookup(&self, state: TSStateId, symbol: TSSymbol) -> Option<u16> {
if u32::from(state) < self.large_state_count || u32::from(symbol) >= self.token_count {
return None;
}
let row_index = u32::from(state) - self.large_state_count;
let row = self.rows.get_unchecked(row_index);
let entries =
&self.entries.as_slice()[row.start as usize..(row.start + row.length) as usize];
if entries.len() <= 8 {
return Some(
entries
.iter()
.find(|entry| entry.symbol == symbol)
.map_or(0, |entry| entry.action_index),
);
}
Some(
entries
.binary_search_by_key(&symbol, |entry| entry.symbol)
.map_or(0, |index| entries[index].action_index),
)
}
}
struct GotoTable {
rows: Array<GotoRow>,
entries: Array<GotoEntry>,
large_state_count: u32,
}
impl GotoTable {
const fn new() -> Self {
Self {
rows: Array::new(),
entries: Array::new(),
large_state_count: 0,
}
}
unsafe fn clear(&mut self) {
self.rows.clear();
self.entries.clear();
self.large_state_count = 0;
}
unsafe fn delete(&mut self) {
if !self.rows.contents.is_null() {
self.rows.delete();
}
if !self.entries.contents.is_null() {
self.entries.delete();
}
}
unsafe fn rebuild(&mut self, language: *const TSLanguage) {
self.clear();
if language.is_null() {
return;
}
let language = language_full(language);
self.large_state_count = language.large_state_count;
self.rows
.reserve(language.state_count - language.large_state_count);
for state in language.large_state_count..language.state_count {
let table_index = *language
.small_parse_table_map
.add((state - language.large_state_count) as usize);
self.append_small_row(
language.small_parse_table.add(table_index as usize),
language.token_count,
language.symbol_count,
);
}
}
unsafe fn append_small_row(
&mut self,
mut data: *const u16,
token_count: u32,
symbol_count: u32,
) {
let row_start = self.entries.size;
let group_count = u32::from(*data);
data = data.add(1);
for _ in 0..group_count {
let section_value = *data;
let group_symbol_count = u32::from(*data.add(1));
data = data.add(2);
for _ in 0..group_symbol_count {
let symbol = *data;
if u32::from(symbol) >= token_count && u32::from(symbol) < symbol_count {
self.entries.push(GotoEntry {
symbol,
state: section_value,
});
}
data = data.add(1);
}
}
let row_length = self.entries.size - row_start;
let row =
&mut self.entries.as_mut_slice()[row_start as usize..(row_start + row_length) as usize];
for i in 1..row.len() {
let entry = row[i];
let mut j = i;
while j > 0 && row[j - 1].symbol > entry.symbol {
row[j] = row[j - 1];
j -= 1;
}
row[j] = entry;
}
self.rows.push(GotoRow {
start: row_start,
length: row_length,
});
}
#[inline]
unsafe fn lookup(&self, state: TSStateId, symbol: TSSymbol) -> Option<TSStateId> {
if u32::from(state) < self.large_state_count {
return None;
}
let row_index = u32::from(state) - self.large_state_count;
let row = self.rows.get_unchecked(row_index);
let entries =
&self.entries.as_slice()[row.start as usize..(row.start + row.length) as usize];
if entries.len() <= 8 {
return Some(
entries
.iter()
.find(|entry| entry.symbol == symbol)
.map_or(0, |entry| entry.state),
);
}
Some(
entries
.binary_search_by_key(&symbol, |entry| entry.symbol)
.map_or(0, |index| entries[index].state),
)
}
}
#[cfg(test)]
mod goto_table_tests {
use super::{ActionTable, GotoTable};
#[test]
fn projects_and_sorts_terminal_action_entries() {
unsafe {
let compressed_row = [4, 7, 2, 1, 2, 42, 3, 12, 9, 10, 99, 2, 15, 11, 77, 1, 3];
let mut table = ActionTable::new();
table.large_state_count = 4;
table.token_count = 10;
table.append_small_row(compressed_row.as_ptr());
assert_eq!(table.lookup(3, 1), None);
assert_eq!(table.lookup(4, 1), Some(7));
assert_eq!(table.lookup(4, 2), Some(7));
assert_eq!(table.lookup(4, 3), Some(77));
assert_eq!(table.lookup(4, 9), Some(42));
assert_eq!(table.lookup(4, 4), Some(0));
assert_eq!(table.lookup(4, 10), None);
table.delete();
}
}
#[test]
fn projects_and_sorts_nonterminal_entries() {
unsafe {
let compressed_row = [4, 7, 2, 1, 2, 42, 3, 12, 9, 10, 99, 2, 15, 11, 77, 1, 13];
let mut table = GotoTable::new();
table.large_state_count = 4;
table.append_small_row(compressed_row.as_ptr(), 10, 14);
assert_eq!(table.lookup(3, 10), None);
assert_eq!(table.lookup(4, 10), Some(42));
assert_eq!(table.lookup(4, 11), Some(99));
assert_eq!(table.lookup(4, 12), Some(42));
assert_eq!(table.lookup(4, 13), Some(77));
assert_eq!(table.lookup(4, 14), Some(0));
table.delete();
}
}
#[test]
fn searches_large_sparse_rows() {
unsafe {
let compressed_row = [1, 5, 9, 18, 10, 17, 11, 16, 12, 15, 13, 14];
let mut table = GotoTable::new();
table.large_state_count = 8;
table.append_small_row(compressed_row.as_ptr(), 10, 19);
for symbol in 10..=18 {
assert_eq!(table.lookup(8, symbol), Some(5));
}
assert_eq!(table.lookup(8, 9), Some(0));
table.delete();
}
}
}
#[derive(Clone, Copy)]
struct ErrorStatus {
cost: u32,
node_count: u32,
dynamic_precedence: i32,
is_in_error: bool,
}
#[derive(PartialEq, Eq)]
enum ErrorComparison {
TakeLeft,
PreferLeft,
None,
PreferRight,
TakeRight,
}
struct TSStringInput {
string: *const c_char,
length: u32,
}
pub struct TSParser {
lexer: Lexer,
stack: *mut Stack,
tree_pool: SubtreePool,
language: *const TSLanguage,
reduce_actions: ReduceActionSet,
finished_tree: Subtree,
trailing_extras: SubtreeArray,
trailing_extras2: SubtreeArray,
scratch_trees: SubtreeArray,
token_cache: TokenCache,
goto_table: GotoTable,
action_table: ActionTable,
external_scanner_payload: *mut c_void,
dot_graph_file: *mut c_void,
accept_count: u32,
operation_count: u32,
parse_options: TSParseOptions,
parse_state: TSParseState,
canceled_balancing: bool,
has_error: bool,
}
#[inline]
fn parse_options_none() -> TSParseOptions {
TSParseOptions {
payload: ptr::null_mut(),
progress_callback: None,
}
}
#[inline]
const fn parse_state_empty() -> TSParseState {
TSParseState {
payload: ptr::null_mut(),
current_byte_offset: 0,
has_error: false,
}
}
unsafe extern "C" fn ts_string_input_read(
payload: *mut c_void,
byte: u32,
_point: TSPoint,
length: *mut u32,
) -> *const c_char {
let input = ptr_ref(payload.cast::<TSStringInput>());
if byte >= input.length {
*length = 0;
c"".as_ptr()
} else {
*length = input.length - byte;
input.string.add(byte as usize)
}
}
mod logging;
use logging::{parser_log, parser_log_stack, parser_log_tree};
#[inline]
unsafe fn parser_table_entry(
self_: &TSParser,
state: TSStateId,
symbol: TSSymbol,
result: &mut TableEntry,
) {
if let Some(action_index) = self_.action_table.lookup(state, symbol) {
language_table_entry_from_index(self_.language, action_index, result);
} else {
language_table_entry(self_.language, state, symbol, result);
}
}
mod advance;
use advance::{parser_advance, parser_condense_stack};
mod lexing;
use lexing::{
parser_external_scanner_create, parser_external_scanner_destroy, parser_set_cached_token,
};
mod actions;
mod recovery;
mod balancing;
use balancing::parser_balance_subtree;
unsafe fn parser_has_outstanding_parse(self_: &TSParser) -> bool {
self_.canceled_balancing
|| !self_.external_scanner_payload.is_null()
|| ptr_ref(self_.stack).head(0).state() != 1
|| ptr_mut(self_.stack).node_count_since_error(0) != 0
}
unsafe fn parser_take_finished_tree(self_: &mut TSParser) -> *mut TSTree {
subtree_publish(self_.tree_pool.arena());
let arena = subtree_pool_retain_arena(&mut self_.tree_pool);
let result = TSTree::new(
self_.finished_tree,
arena,
self_.language,
lexer_included_ranges_slice(&self_.lexer),
);
self_.finished_tree = NULL_SUBTREE;
result
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_new() -> *mut TSParser {
let self_ = malloc(core::mem::size_of::<TSParser>()).cast::<TSParser>();
ptr::write(
self_,
TSParser {
lexer: lexer_new(),
stack: ptr::null_mut(),
tree_pool: subtree_pool_new(32),
language: ptr::null(),
reduce_actions: Array::new(),
finished_tree: NULL_SUBTREE,
trailing_extras: SubtreeArray::new(),
trailing_extras2: SubtreeArray::new(),
scratch_trees: SubtreeArray::new(),
token_cache: TokenCache {
token: NULL_SUBTREE,
last_external_token: NULL_SUBTREE,
byte_index: 0,
},
goto_table: GotoTable::new(),
action_table: ActionTable::new(),
external_scanner_payload: ptr::null_mut(),
dot_graph_file: ptr::null_mut(),
accept_count: 0,
operation_count: 0,
parse_options: parse_options_none(),
parse_state: parse_state_empty(),
canceled_balancing: false,
has_error: false,
},
);
let parser = ptr_mut(self_);
parser.reduce_actions.reserve(4);
parser.stack = stack_new(&mut parser.tree_pool);
parser_set_cached_token(parser, 0, NULL_SUBTREE, NULL_SUBTREE);
self_
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_delete(self_: *mut TSParser) {
if self_.is_null() {
return;
}
ts_parser_reset(self_);
let parser = ptr_mut(self_);
stack_delete(ptr_mut(parser.stack));
if !parser.reduce_actions.contents.is_null() {
parser.reduce_actions.delete();
}
lexer_delete(&mut parser.lexer);
parser_set_cached_token(parser, 0, NULL_SUBTREE, NULL_SUBTREE);
subtree_pool_delete(&mut parser.tree_pool);
parser.trailing_extras.delete();
parser.trailing_extras2.delete();
parser.scratch_trees.delete();
parser.goto_table.delete();
parser.action_table.delete();
free(self_.cast::<c_void>());
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_language(self_: *const TSParser) -> *const TSLanguage {
let parser = ptr_ref(self_);
parser.language
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_set_language(
self_: *mut TSParser,
language: *const TSLanguage,
) -> bool {
ts_parser_reset(self_);
let parser = ptr_mut(self_);
parser.language = ptr::null();
parser.goto_table.clear();
parser.action_table.clear();
if !language.is_null() {
let language_data = language_full(language);
if language_data.abi_version > TREE_SITTER_LANGUAGE_VERSION
|| language_data.abi_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION
{
return false;
}
}
parser.language = language;
parser.goto_table.rebuild(language);
parser.action_table.rebuild(language);
true
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_logger(self_: *const TSParser) -> TSLogger {
let parser = ptr_ref(self_);
ptr::read(&parser.lexer.logger)
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_set_logger(self_: *mut TSParser, logger: TSLogger) {
let parser = ptr_mut(self_);
parser.lexer.logger = logger;
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_print_dot_graphs(self_: *mut TSParser, fd: i32) {
let parser = ptr_mut(self_);
if !parser.dot_graph_file.is_null() {
fclose(parser.dot_graph_file);
}
if fd >= 0 {
#[cfg(target_os = "windows")]
{
extern "C" {
fn _fdopen(fd: i32, mode: *const i8) -> *mut c_void;
}
parser.dot_graph_file = _fdopen(fd, c"a".as_ptr().cast::<i8>());
}
#[cfg(not(target_os = "windows"))]
{
parser.dot_graph_file = fdopen(fd, c"a".as_ptr().cast::<i8>());
}
} else {
parser.dot_graph_file = ptr::null_mut();
}
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_set_included_ranges(
self_: *mut TSParser,
ranges: *const TSRange,
count: u32,
) -> bool {
let parser = ptr_mut(self_);
lexer_set_included_ranges(&mut parser.lexer, ranges, count)
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_included_ranges(
self_: *const TSParser,
count: *mut u32,
) -> *const TSRange {
let parser = ptr_ref(self_);
lexer_included_ranges(&parser.lexer, count)
}
#[no_mangle]
pub unsafe extern "C" fn ts_parser_reset(self_: *mut TSParser) {
let parser = ptr_mut(self_);
parser_external_scanner_destroy(parser);
lexer_reset(&mut parser.lexer, length_zero());
stack_clear(ptr_mut(parser.stack));
parser.trailing_extras.clear();
parser.trailing_extras2.clear();
parser.scratch_trees.clear();
parser_set_cached_token(parser, 0, NULL_SUBTREE, NULL_SUBTREE);
if !parser.finished_tree.is_null() {
parser.finished_tree.release(&mut parser.tree_pool);
parser.finished_tree = NULL_SUBTREE;
}
parser.accept_count = 0;
parser.has_error = false;
parser.canceled_balancing = false;
parser.parse_options = parse_options_none();
parser.parse_state = parse_state_empty();
}
#[no_mangle]
pub unsafe extern "C-unwind" fn ts_parser_parse(
self_: *mut TSParser,
old_tree: *const TSTree,
input: TSInput,
) -> *mut TSTree {
let _ = old_tree;
let parser = ptr_mut(self_);
if parser.language.is_null() || input.read.is_none() {
return ptr::null_mut();
}
lexer_set_input(&mut parser.lexer, input);
parser.operation_count = 0;
if parser_has_outstanding_parse(parser) {
parser_log(parser, |_, log| log.write_str("resume_parsing"));
if parser.canceled_balancing {
debug_assert!(!parser.finished_tree.is_null());
if !parser_balance_subtree(parser) {
parser.canceled_balancing = true;
return ptr::null_mut();
}
parser.canceled_balancing = false;
parser_log(parser, |_, log| log.write_str("done"));
parser_log_tree(parser, parser.finished_tree);
let result = parser_take_finished_tree(parser);
ts_parser_reset(self_);
return result;
}
} else {
subtree_pool_prepare_for_parse(&mut parser.tree_pool);
subtree_array_prepare_scratch(&mut parser.tree_pool, &mut parser.scratch_trees);
parser_external_scanner_create(parser);
parser_log(parser, |_, log| log.write_str("new_parse"));
}
let mut last_position: u32 = 0;
let mut version_count: StackVersion;
loop {
let mut version: StackVersion = 0;
loop {
version_count = ptr_ref(parser.stack).version_count();
if version >= version_count {
break;
}
while ptr_ref(parser.stack).head(version).is_active() {
parser_log(parser, |context, log| {
let stack = ptr_ref(context.stack);
let head = stack.head(version);
write!(
log,
"process version:{version}, version_count:{}, state:{}, row:{}, col:{}",
stack.version_count(),
i32::from(head.state()),
head.position().extent.row,
head.position().extent.column
)
});
if !parser_advance(parser, version) {
return ptr::null_mut();
}
parser_log_stack(parser);
let position = ptr_ref(parser.stack).head(version).position().bytes;
if position > last_position || (version > 0 && position == last_position) {
last_position = position;
break;
}
}
version += 1;
}
let min_error_cost = parser_condense_stack(parser);
if !parser.finished_tree.is_null()
&& parser.finished_tree.error_cost(parser.tree_pool.arena()) < min_error_cost
{
stack_clear(ptr_mut(parser.stack));
break;
}
if version_count == 0 {
break;
}
}
debug_assert!(!parser.finished_tree.is_null());
if !parser_balance_subtree(parser) {
parser.canceled_balancing = true;
return ptr::null_mut();
}
parser.canceled_balancing = false;
parser_log(parser, |_, log| log.write_str("done"));
parser_log_tree(parser, parser.finished_tree);
let result = parser_take_finished_tree(parser);
ts_parser_reset(self_);
result
}
#[no_mangle]
pub unsafe extern "C-unwind" fn ts_parser_parse_with_options(
self_: *mut TSParser,
old_tree: *const TSTree,
input: TSInput,
parse_options: TSParseOptions,
) -> *mut TSTree {
{
let parser = ptr_mut(self_);
parser.parse_options = parse_options;
parser.parse_state.payload = parse_options.payload;
}
let result = ts_parser_parse(self_, old_tree, input);
let parser = ptr_mut(self_);
parser.parse_options = parse_options_none();
result
}
#[no_mangle]
pub unsafe extern "C-unwind" fn ts_parser_parse_string(
self_: *mut TSParser,
old_tree: *const TSTree,
string: *const i8,
length: u32,
) -> *mut TSTree {
ts_parser_parse_string_encoding(self_, old_tree, string, length, TSInputEncodingUTF8)
}
#[no_mangle]
pub unsafe extern "C-unwind" fn ts_parser_parse_string_encoding(
self_: *mut TSParser,
old_tree: *const TSTree,
string: *const i8,
length: u32,
encoding: TSInputEncoding,
) -> *mut TSTree {
let input = TSStringInput {
string: string.cast::<c_char>(),
length,
};
ts_parser_parse(
self_,
old_tree,
TSInput {
payload: core::ptr::addr_of!(input) as *mut c_void,
read: Some(ts_string_input_read),
encoding,
decode: None,
},
)
}