///|
type Parser
///|
extern "c" fn ts_parser_new() -> Parser = "moonbit_ts_parser_new"
///|
/// Create a new parser.
pub fn Parser::new() -> Parser {
ts_parser_new()
}
///|
pub fn parser(language : Language) -> Parser raise LanguageError {
let parser = Parser::new()
parser.set_language(language)
return parser
}
///|
#borrow(parser)
extern "c" fn ts_parser_language(parser : Parser) -> Language = "moonbit_ts_parser_language"
///|
#borrow(language)
extern "c" fn ts_language_is_null(language : Language) -> Bool = "moonbit_c_is_null"
///|
/// Get the parser's current language.
pub fn Parser::language(self : Parser) -> Language? {
let language = ts_parser_language(self)
if ts_language_is_null(language) {
None
} else {
Some(language)
}
}
///|
#borrow(parser)
extern "c" fn ts_parser_set_language(
parser : Parser,
language : Language,
) -> Bool = "moonbit_ts_parser_set_language"
///|
suberror LanguageError {
VersionMismatch(
language_abi_version~ : Int,
library_version~ : Int,
min_compatible_version~ : Int
)
} derive(Show)
///|
pub impl ToJson for LanguageError with to_json(self) -> Json {
match self {
VersionMismatch(
language_abi_version~,
library_version~,
min_compatible_version~
) =>
{
"language_abi_version": language_abi_version.to_json(),
"library_version": library_version.to_json(),
"min_compatible_version": min_compatible_version.to_json(),
}
}
}
///|
/// Set the language that the parser should use for parsing.
///
/// Returns a boolean indicating whether or not the language was successfully
/// assigned. True means assignment succeeded. False means there was a version
/// mismatch: the language was generated with an incompatible version of the
/// Tree-sitter CLI. Check the language's ABI version using
/// `Language::abi_version` and compare it to this library's `LANGUAGE_VERSION`
/// and `MIN_COMPATIBLE_LANGUAGE_VERSION` constants.
pub fn Parser::set_language(
self : Parser,
language : Language,
) -> Unit raise LanguageError {
let succeed = ts_parser_set_language(self, language)
if not(succeed) {
raise VersionMismatch(
language_abi_version=language.abi_version(),
library_version=LANGUAGE_VERSION,
min_compatible_version=MIN_COMPATIBLE_LANGUAGE_VERSION,
)
}
}
///|
/// The latest ABI version that is supported by the current version of the
/// library. When Languages are generated by the Tree-sitter CLI, they are
/// assigned an ABI version number that corresponds to the current CLI version.
/// The Tree-sitter library is generally backwards-compatible with languages
/// generated using older CLI versions, but is not forwards-compatible.
pub const LANGUAGE_VERSION = 15
///|
///
/// The earliest ABI version that is supported by the current version of the
/// library.
pub const MIN_COMPATIBLE_LANGUAGE_VERSION = 13
///|
#borrow(parser, ranges)
extern "c" fn ts_parser_set_included_ranges(
parser : Parser,
ranges : FixedArray[UInt],
) -> Bool = "moonbit_ts_parser_set_included_ranges"
///|
/// Set the ranges of text that the parser should include when parsing.
///
/// By default, the parser will always include entire documents. This function
/// allows you to parse only a *portion* of a document but still return a syntax
/// tree whose ranges match up with the document as a whole. You can also pass
/// multiple disjoint ranges.
///
/// The second and third parameters specify the location and length of an array
/// of ranges.
///
/// If `count` is zero, then the entire document will be parsed. Otherwise,
/// the given ranges must be ordered from earliest to latest in the document,
/// and they must not overlap. That is, the following must hold for all:
///
/// `i < count - 1`: `ranges[i].end_byte <= ranges[i + 1].start_byte`
///
/// If this requirement is not satisfied, the operation will fail, the ranges
/// will not be assigned, and this function will return `false`. On success,
/// this function returns `true`
pub fn Parser::set_included_ranges(
self : Parser,
ranges : Array[Range],
) -> Bool {
let flatten_ranges = FixedArray::make(ranges.length() * 6, 0U)
for i, range in ranges {
flatten_ranges[i * 6] = range.0[0]
flatten_ranges[i * 6 + 1] = range.0[1]
flatten_ranges[i * 6 + 2] = range.0[2]
flatten_ranges[i * 6 + 3] = range.0[3]
flatten_ranges[i * 6 + 4] = range.0[4]
flatten_ranges[i * 6 + 5] = range.0[5]
}
return ts_parser_set_included_ranges(self, flatten_ranges)
}
///|
#borrow(parser)
extern "c" fn ts_parser_included_ranges(parser : Parser) -> FixedArray[UInt] = "moonbit_ts_parser_included_ranges"
///|
/// Get the ranges of text that the parser will include when parsing.
pub fn Parser::included_ranges(self : Parser) -> Array[Range] {
let flatten_ranges = ts_parser_included_ranges(self)
Array::makei(flatten_ranges.length() / 6, fn(i) {
ts_range_new(
ts_point_new(flatten_ranges[i * 6], flatten_ranges[i * 6 + 1]),
ts_point_new(flatten_ranges[i * 6 + 2], flatten_ranges[i * 6 + 3]),
flatten_ranges[i * 6 + 4],
flatten_ranges[i * 6 + 5],
)
})
}
///|
#borrow(parser, old_tree)
extern "c" fn ts_parser_parse(
parser : Parser,
old_tree : TSTree,
input : (UInt, Point, FixedArray[UInt]) -> Bytes,
encoding : InputEncoding,
decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
) -> TSTree = "moonbit_ts_parser_parse"
///|
/// The state of a parse operation.
pub struct ParseState {
current_byte_offset : Int
has_error : Bool
}
///|
/// Options for parsing.
struct ParseOptions {
progress_callback : (ParseState) -> Bool
}
///|
/// Create new parse options with the given progress callback.
pub fn ParseOptions::new(
progress_callback : (ParseState) -> Bool,
) -> ParseOptions {
ParseOptions::{ progress_callback, }
}
///|
#borrow(parser, old_tree)
extern "c" fn ts_parser_parse_with_options(
parser : Parser,
old_tree : TSTree,
input : (UInt, Point, FixedArray[UInt]) -> Bytes,
encoding : UInt,
decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int],
progress_callback : (UInt, Bool) -> Bool,
) -> TSTree = "moonbit_ts_parser_parse_with_options"
///|
/// Use the parser to parse some source code and create a syntax tree.
///
/// If you are parsing this document for the first time, pass `None` for the
/// `old_tree` parameter. Otherwise, if you have already parsed an earlier
/// version of this document and the document has since been edited, pass the
/// previous syntax tree so that the unchanged parts of it can be reused.
/// This will save time and memory. For this to work correctly, you must have
/// already edited the old syntax tree using the `Tree::edit` function in a
/// way that exactly matches the source code changes.
///
/// The `Input` parameter lets you specify how to read the text. It has the
/// following fields:
/// 1. `read`: A function to retrieve a chunk of text at a given byte offset
/// and (row, column) position. The function should return a pointer to the
/// text and write its length to the `bytes_read` pointer. The parser does
/// not take ownership of this buffer; it just borrows it until it has
/// finished reading it. The function should write a zero value to the
/// `bytes_read` pointer to indicate the end of the document.
/// 2. `decode`: A function to decode the text. This is only used if the
/// encoding is `Custom`.
/// Additionally, you can pass `InputEncoding::UTF8` or `InputEncoding::UTF16`
/// to the `Input` parameter to specify the encoding of the text.
///
/// This function returns a syntax tree on success, and `None` on failure. There
/// are four possible reasons for failure:
/// 1. The parser does not have a language assigned. Check for this using the
/// `Parser::language` function.
/// 2. Parsing was cancelled due to a timeout that was set by an earlier call to
/// the `Parser::set_timeout_micros` function. You can resume parsing from
/// where the parser left out by calling `Parser::parse` again with the
/// same arguments. Or you can start parsing from scratch by first calling
/// `Parser::reset`.
/// 3. Parsing was cancelled using a cancellation flag that was set by an
/// earlier call to `Parser::set_cancellation_flag`. You can resume parsing
/// from where the parser left out by calling `Parser::parse` again with
/// the same arguments.
/// 4. Parsing was cancelled due to the progress callback returning true. This callback
/// is passed as the `options` argument inside the `ParseOptions` struct.
pub fn[Encoding : DecodeFunction] Parser::parse(
self : Parser,
old_tree? : Tree,
input : Input[Encoding],
options? : ParseOptions,
) -> Tree raise ParseError {
let encoding = input.decode.encoding()
let decode : FuncRef[(@c.Pointer[Byte], UInt, @c.Pointer[Int]) -> Int] = match encoding {
Custom =>
fn(bytes, length, code_point) {
let buffer = @buffer.new()
for i in 0.. {
code_point.store(result.code_point.to_int())
result.bytes_read
}
None => -1
}
}
_ => fn(_bytes, _length, _code_point) { 0 }
}
let mut stage : BytesView = []
let text = StringBuilder::new()
let mut chunk = None
let read : (UInt, Point, FixedArray[UInt]) -> Bytes = fn(
offset : UInt,
point : Point,
range : FixedArray[UInt],
) {
let bytes_view = (input.read)(offset, point)
stage = [..stage, ..bytes_view]
loop Encoding::decode(stage) {
Some(result) => {
text.write_char(result.code_point)
stage = stage[result.bytes_read:]
if stage.length() == 0 {
break
} else {
continue Encoding::decode(stage)
}
}
None => ()
}
let bytes_start = bytes_view.start_offset()
let bytes_length = bytes_view.length()
range[0] = int_to_uint(bytes_start)
range[1] = int_to_uint(bytes_length)
let bytes_data = bytes_view.data()
if chunk is Some(chunk) && physical_equal(chunk, bytes_data) {
return bytes_data
}
if bytes_length != 0 {
chunk = Some(bytes_data)
} else {
chunk = None
}
return bytes_data
}
let old_ts_tree = match old_tree {
None => ts_tree_null()
Some(tree) => tree.tree
}
guard options is Some(options) else {
let tree = ts_parser_parse(self, old_ts_tree, read, encoding, decode).to_option()
{ tree: self.raise_parse_error(tree), text: text.to_string() }
}
let tree = ts_parser_parse_with_options(
self,
old_ts_tree,
read,
encoding.to_uint(),
decode,
fn(current_byte_offset, has_error) {
let current_byte_offset = uint_to_int(current_byte_offset)
(options.progress_callback)({ current_byte_offset, has_error })
},
).to_option()
{ tree: self.raise_parse_error(tree), text: text.to_string() }
}
///|
#borrow(parser, old_tree, string)
extern "c" fn ts_parser_parse_bytes(
parser : Parser,
old_tree : TSTree,
string : Bytes,
) -> TSTree = "moonbit_ts_parser_parse_string"
///|
#borrow(parser, old_tree, string)
extern "c" fn ts_parser_parse_bytes_encoding(
parser : Parser,
old_tree : TSTree,
string : Bytes,
encoding : UInt,
) -> TSTree = "moonbit_ts_parser_parse_string_encoding"
///|
/// Use the parser to parse some source code stored in one contiguous buffer
/// with a given encoding. The first three parameters work the same as in the
/// `parse` method. The final parameter indicates whether the text is encoded as
/// UTF8 or UTF16.
pub fn Parser::parse_bytes(
self : Parser,
old_tree? : Tree,
bytes : Bytes,
encoding~ : InputEncoding,
) -> Tree raise ParseError {
let old_ts_tree = match old_tree {
None => ts_tree_null()
Some(tree) => tree.tree
}
let tree = ts_parser_parse_bytes_encoding(
self,
old_ts_tree,
bytes,
encoding.to_uint(),
).to_option()
let text : StringView = match encoding {
UTF8 => @utf8.decode_lossy(bytes)
UTF16LE => @utf16.decode_lossy(bytes, endianness=Little)
UTF16BE => @utf16.decode_lossy(bytes, endianness=Big)
Custom => @utf8.decode_lossy(bytes)
}
{ tree: self.raise_parse_error(tree), text }
}
///|
pub suberror ParseError {
MissingLanguage
Cancelled
} derive(Show)
///|
fn Parser::raise_parse_error(
self : Parser,
tree : TSTree?,
) -> TSTree raise ParseError {
match tree {
None =>
match self.language() {
None => raise MissingLanguage
Some(_) => raise Cancelled
}
Some(tree) => return tree
}
}
///|
/// Use the parser to parse some source code stored in one contiguous string buffer.
/// The first two parameters are the same as in the `parse` function. The final
/// parameter is the string to parse.
pub fn Parser::parse_string(
self : Parser,
old_tree? : Tree,
string : StringView,
) -> Tree raise ParseError {
let old_ts_tree = match old_tree {
None => ts_tree_null()
Some(tree) => tree.tree
}
let tree = ts_parser_parse_bytes(self, old_ts_tree, @utf8.encode(string)).to_option()
{ tree: self.raise_parse_error(tree), text: string }
}
///|
#borrow(parser)
extern "c" fn ts_parser_reset(parser : Parser) = "moonbit_ts_parser_reset"
///|
/// Instruct the parser to start the next parse from the beginning.
///
/// If the parser previously failed because of a timeout or a cancellation, then
/// by default, it will resume where it left off on the next call to
/// `parse` or other parsing functions. If you don't want to resume,
/// and instead intend to use this parser to parse some other document, you must
/// call `reset` first.
pub fn Parser::reset(self : Parser) -> Unit {
ts_parser_reset(self)
}
///|
pub enum LogType {
Parse
Lex
}
///|
fn LogType::of_uint(value : UInt) -> LogType {
match value {
0 => Parse
1 => Lex
value => abort("Invalid log type: \{value}")
}
}
///|
struct Logger((UInt, Bytes) -> Unit)
///|
pub fn Logger::new(log : (LogType, StringView) -> Unit) -> Logger {
fn(log_type, message) {
log(LogType::of_uint(log_type), @utf8.decode_lossy(message))
}
}
///|
#borrow(parser)
extern "c" fn ts_parser_set_logger(parser : Parser, logger : Logger) = "moonbit_ts_parser_set_logger"
///|
/// Set the logger that a parser should use during parsing.
///
/// The parser does not take ownership over the logger payload. If a logger was
/// previously assigned, the caller is responsible for releasing any memory
/// owned by the previous logger.
pub fn Parser::set_logger(self : Parser, logger : Logger) -> Unit {
ts_parser_set_logger(self, logger)
}
///|
#borrow(parser)
extern "c" fn ts_parser_logger(parser : Parser) -> Logger = "moonbit_ts_parser_logger"
///|
/// Get the parser's current logger.
pub fn Parser::logger(self : Parser) -> Logger {
ts_parser_logger(self)
}