Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::fmt;
use std::path::{Path, PathBuf};
use std::ptr::NonNull;

use libloading::{Library, Symbol};

/// supported TS versions, WARNING: update when updating vendored c sources
pub const MIN_COMPATIBLE_ABI_VERSION: u32 = 13;
pub const ABI_VERSION: u32 = 14;

// opaque pointer
enum GrammarData {}

#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Grammar {
    ptr: NonNull<GrammarData>,
}

unsafe impl Send for Grammar {}
unsafe impl Sync for Grammar {}

impl std::fmt::Debug for Grammar {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Grammar").finish_non_exhaustive()
    }
}

impl Grammar {
    /// Loads a shared library containg a tree sitter grammar with name `name`
    // from `library_path`.
    ///
    /// # Safety
    ///
    /// `library_path` must be a valid tree sitter grammar
    pub unsafe fn new(name: &str, library_path: &Path) -> Result<Grammar, Error> {
        let library = unsafe {
            Library::new(library_path).map_err(|err| Error::DlOpen {
                err,
                path: library_path.to_owned(),
            })?
        };
        let language_fn_name = format!("tree_sitter_{}", name.replace('-', "_"));
        let grammar = unsafe {
            let language_fn: Symbol<unsafe extern "C" fn() -> NonNull<GrammarData>> = library
                .get(language_fn_name.as_bytes())
                .map_err(|err| Error::DlSym {
                    err,
                    symbol: name.to_owned(),
                })?;
            Grammar { ptr: language_fn() }
        };
        let version = grammar.version();
        if (MIN_COMPATIBLE_ABI_VERSION..=ABI_VERSION).contains(&version) {
            std::mem::forget(library);
            Ok(grammar)
        } else {
            Err(Error::IncompatibleVersion { version })
        }
    }
    pub fn version(self) -> u32 {
        unsafe { ts_language_version(self) }
    }
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("Error opening dynamic library {path:?}")]
    DlOpen {
        #[source]
        err: libloading::Error,
        path: PathBuf,
    },
    #[error("Failed to load symbol {symbol}")]
    DlSym {
        #[source]
        err: libloading::Error,
        symbol: String,
    },
    #[error("Tried to load grammar with incompatible ABI {version}.")]
    IncompatibleVersion { version: u32 },
}

/// An error that occurred when trying to assign an incompatible [`Grammar`] to
/// a [`Parser`].
#[derive(Debug, PartialEq, Eq)]
pub struct IncompatibleGrammarError {
    version: u32,
}

impl fmt::Display for IncompatibleGrammarError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Tried to load grammar with incompatible ABI {}.",
            self.version,
        )
    }
}
impl std::error::Error for IncompatibleGrammarError {}

extern "C" {
    /// Get the ABI version number for this language. This version number
    /// is used to ensure that languages were generated by a compatible version of
    /// Tree-sitter. See also [`ts_parser_set_language`].
    pub fn ts_language_version(grammar: Grammar) -> u32;
}