1use quarb::{AstAdapter, NodeId, Value};
23
24mod ast_cache;
25pub use ast_cache::Cache;
26
27thread_local! {
28 static CACHE: std::cell::RefCell<Option<Cache>> = const { std::cell::RefCell::new(None) };
32}
33
34pub fn set_cache(cache: Option<Cache>) {
36 CACHE.with(|c| *c.borrow_mut() = cache);
37}
38
39#[derive(Debug, thiserror::Error)]
41pub enum CodeError {
42 #[error("code: {0}")]
43 Io(#[from] std::io::Error),
44 #[error("code: no grammar for extension {0:?}")]
45 Language(String),
46 #[error("code: parse produced no tree")]
47 Parse,
48}
49
50#[derive(Debug, PartialEq)]
51pub(crate) struct Node {
52 pub(crate) kind: &'static str,
53 pub(crate) field: Option<&'static str>,
54 pub(crate) parent: Option<NodeId>,
55 pub(crate) children: Vec<NodeId>,
56 pub(crate) start: usize,
58 pub(crate) end: usize,
59 pub(crate) start_line: usize,
60 pub(crate) end_line: usize,
61 pub(crate) fields: Vec<(&'static str, usize)>,
63}
64
65pub struct CodeAdapter {
67 source: String,
68 nodes: Vec<Node>,
69}
70
71fn language(ext: &str) -> Option<tree_sitter::Language> {
73 match ext {
74 "rs" => Some(tree_sitter_rust::LANGUAGE.into()),
75 "py" => Some(tree_sitter_python::LANGUAGE.into()),
76 "js" | "mjs" | "cjs" | "jsx" => Some(tree_sitter_javascript::LANGUAGE.into()),
77 "c" | "h" => Some(tree_sitter_c::LANGUAGE.into()),
78 _ => None,
79 }
80}
81
82pub fn supported(ext: &str) -> bool {
84 language(ext).is_some()
85}
86
87impl CodeAdapter {
88 pub fn parse(text: &str, ext: &str) -> Result<Self, CodeError> {
94 if let Some(cache) = CACHE.with(|c| c.borrow().clone())
95 && let Some(lang) = language(ext)
96 {
97 let tag = ast_cache::lang_tag(ext);
98 if tag != 0 {
99 let hash = quarb::sha256(text.as_bytes());
100 if let Some(nodes) = ast_cache::load(&cache, tag, &lang, &hash, text) {
101 return Ok(CodeAdapter {
102 source: text.to_string(),
103 nodes,
104 });
105 }
106 let adapter = Self::parse_raw(text, ext)?;
107 ast_cache::store(&cache, &adapter.nodes, tag, &lang, &hash, text.len() as u64);
108 return Ok(adapter);
109 }
110 }
111 Self::parse_raw(text, ext)
112 }
113
114 fn parse_raw(text: &str, ext: &str) -> Result<Self, CodeError> {
117 let lang = language(ext).ok_or_else(|| CodeError::Language(ext.to_string()))?;
118 let mut parser = tree_sitter::Parser::new();
119 parser
120 .set_language(&lang)
121 .map_err(|_| CodeError::Language(ext.to_string()))?;
122 let tree = parser.parse(text, None).ok_or(CodeError::Parse)?;
123 let mut nodes = Vec::new();
124 build(&mut nodes, tree.root_node());
125 Ok(CodeAdapter {
126 source: text.to_string(),
127 nodes,
128 })
129 }
130
131 pub fn open(path: &std::path::Path) -> Result<Self, CodeError> {
133 let ext = path
134 .extension()
135 .and_then(|e| e.to_str())
136 .unwrap_or("")
137 .to_ascii_lowercase();
138 let text = std::fs::read_to_string(path)?;
139 Self::parse(&text, &ext)
140 }
141
142 pub fn locator(&self, node: NodeId) -> String {
144 let mut parts = Vec::new();
145 let mut cur = Some(node);
146 while let Some(n) = cur {
147 let nd = &self.nodes[n.0 as usize];
148 if nd.parent.is_some() {
149 parts.push(format!("{}:{}", nd.kind, nd.start_line));
150 }
151 cur = nd.parent;
152 }
153 parts.reverse();
154 format!("/{}", parts.join("/"))
155 }
156
157 fn text_of(&self, n: &Node) -> &str {
158 &self.source[n.start.min(self.source.len())..n.end.min(self.source.len())]
159 }
160}
161
162fn build(nodes: &mut Vec<Node>, root: tree_sitter::Node<'_>) {
167 let mut stack: Vec<(tree_sitter::Node<'_>, Option<NodeId>, Option<&'static str>)> =
169 vec![(root, None, None)];
170 while let Some((ts, parent, field)) = stack.pop() {
171 let id = NodeId(nodes.len() as u64);
172 nodes.push(Node {
173 kind: ts.kind(),
174 field,
175 parent,
176 children: Vec::new(),
177 start: ts.start_byte(),
178 end: ts.end_byte(),
179 start_line: ts.start_position().row + 1,
180 end_line: ts.end_position().row + 1,
181 fields: Vec::new(),
182 });
183 if let Some(p) = parent {
186 let pnode = &mut nodes[p.0 as usize];
187 if let Some(f) = field {
188 pnode.fields.push((f, pnode.children.len()));
189 }
190 pnode.children.push(id);
191 }
192 let mut cursor = ts.walk();
196 let mut kids = Vec::new();
197 if cursor.goto_first_child() {
198 loop {
199 let child = cursor.node();
200 if child.is_named() {
201 kids.push((child, cursor.field_name()));
202 }
203 if !cursor.goto_next_sibling() {
204 break;
205 }
206 }
207 }
208 for (child, f) in kids.into_iter().rev() {
209 stack.push((child, Some(id), f));
210 }
211 }
212}
213
214impl AstAdapter for CodeAdapter {
215 fn root(&self) -> NodeId {
216 NodeId(0)
217 }
218
219 fn children(&self, node: NodeId) -> Vec<NodeId> {
220 self.nodes[node.0 as usize].children.clone()
221 }
222
223 fn name(&self, node: NodeId) -> Option<String> {
227 let n = &self.nodes[node.0 as usize];
228 n.parent.map(|_| n.kind.to_string())
229 }
230
231 fn parent(&self, node: NodeId) -> Option<NodeId> {
232 self.nodes[node.0 as usize].parent
233 }
234
235 fn traits(&self, node: NodeId) -> Vec<String> {
238 vec![self.nodes[node.0 as usize].kind.to_string()]
239 }
240
241 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
244 let n = &self.nodes[node.0 as usize];
245 let (_, idx) = n.fields.iter().find(|(f, _)| *f == name)?;
246 let child = &self.nodes[n.children[*idx].0 as usize];
247 Some(Value::Str(self.text_of(child).to_string()))
248 }
249
250 fn default_value(&self, node: NodeId) -> Option<Value> {
252 Some(Value::Str(
253 self.text_of(&self.nodes[node.0 as usize]).to_string(),
254 ))
255 }
256
257 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
258 let n = &self.nodes[node.0 as usize];
259 match key {
260 "kind" => Some(Value::Str(n.kind.to_string())),
261 "field" => n.field.map(|f| Value::Str(f.to_string())),
262 "start-line" => Some(Value::Int(n.start_line as i64)),
263 "end-line" => Some(Value::Int(n.end_line as i64)),
264 "n-children" => Some(Value::Int(n.children.len() as i64)),
265 _ => None,
266 }
267 }
268}