use quarb::{AstAdapter, NodeId, Value};
mod ast_cache;
pub use ast_cache::Cache;
thread_local! {
static CACHE: std::cell::RefCell<Option<Cache>> = const { std::cell::RefCell::new(None) };
}
pub fn set_cache(cache: Option<Cache>) {
CACHE.with(|c| *c.borrow_mut() = cache);
}
#[derive(Debug, thiserror::Error)]
pub enum CodeError {
#[error("code: {0}")]
Io(#[from] std::io::Error),
#[error("code: no grammar for extension {0:?}")]
Language(String),
#[error("code: parse produced no tree")]
Parse,
}
#[derive(Debug, PartialEq)]
pub(crate) struct Node {
pub(crate) kind: &'static str,
pub(crate) field: Option<&'static str>,
pub(crate) parent: Option<NodeId>,
pub(crate) children: Vec<NodeId>,
pub(crate) start: usize,
pub(crate) end: usize,
pub(crate) start_line: usize,
pub(crate) end_line: usize,
pub(crate) fields: Vec<(&'static str, usize)>,
}
pub struct CodeAdapter {
source: String,
nodes: Vec<Node>,
}
fn language(ext: &str) -> Option<tree_sitter::Language> {
match ext {
"rs" => Some(tree_sitter_rust::LANGUAGE.into()),
"py" => Some(tree_sitter_python::LANGUAGE.into()),
"js" | "mjs" | "cjs" | "jsx" => Some(tree_sitter_javascript::LANGUAGE.into()),
"c" | "h" => Some(tree_sitter_c::LANGUAGE.into()),
_ => None,
}
}
pub fn supported(ext: &str) -> bool {
language(ext).is_some()
}
impl CodeAdapter {
pub fn parse(text: &str, ext: &str) -> Result<Self, CodeError> {
if let Some(cache) = CACHE.with(|c| c.borrow().clone())
&& let Some(lang) = language(ext)
{
let tag = ast_cache::lang_tag(ext);
if tag != 0 {
let hash = quarb::sha256(text.as_bytes());
if let Some(nodes) = ast_cache::load(&cache, tag, &lang, &hash, text) {
return Ok(CodeAdapter {
source: text.to_string(),
nodes,
});
}
let adapter = Self::parse_raw(text, ext)?;
ast_cache::store(&cache, &adapter.nodes, tag, &lang, &hash, text.len() as u64);
return Ok(adapter);
}
}
Self::parse_raw(text, ext)
}
fn parse_raw(text: &str, ext: &str) -> Result<Self, CodeError> {
let lang = language(ext).ok_or_else(|| CodeError::Language(ext.to_string()))?;
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&lang)
.map_err(|_| CodeError::Language(ext.to_string()))?;
let tree = parser.parse(text, None).ok_or(CodeError::Parse)?;
let mut nodes = Vec::new();
build(&mut nodes, tree.root_node());
Ok(CodeAdapter {
source: text.to_string(),
nodes,
})
}
pub fn open(path: &std::path::Path) -> Result<Self, CodeError> {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_ascii_lowercase();
let text = std::fs::read_to_string(path)?;
Self::parse(&text, &ext)
}
pub fn locator(&self, node: NodeId) -> String {
let mut parts = Vec::new();
let mut cur = Some(node);
while let Some(n) = cur {
let nd = &self.nodes[n.0 as usize];
if nd.parent.is_some() {
parts.push(format!("{}:{}", nd.kind, nd.start_line));
}
cur = nd.parent;
}
parts.reverse();
format!("/{}", parts.join("/"))
}
fn text_of(&self, n: &Node) -> &str {
&self.source[n.start.min(self.source.len())..n.end.min(self.source.len())]
}
}
fn build(nodes: &mut Vec<Node>, root: tree_sitter::Node<'_>) {
let mut stack: Vec<(tree_sitter::Node<'_>, Option<NodeId>, Option<&'static str>)> =
vec![(root, None, None)];
while let Some((ts, parent, field)) = stack.pop() {
let id = NodeId(nodes.len() as u64);
nodes.push(Node {
kind: ts.kind(),
field,
parent,
children: Vec::new(),
start: ts.start_byte(),
end: ts.end_byte(),
start_line: ts.start_position().row + 1,
end_line: ts.end_position().row + 1,
fields: Vec::new(),
});
if let Some(p) = parent {
let pnode = &mut nodes[p.0 as usize];
if let Some(f) = field {
pnode.fields.push((f, pnode.children.len()));
}
pnode.children.push(id);
}
let mut cursor = ts.walk();
let mut kids = Vec::new();
if cursor.goto_first_child() {
loop {
let child = cursor.node();
if child.is_named() {
kids.push((child, cursor.field_name()));
}
if !cursor.goto_next_sibling() {
break;
}
}
}
for (child, f) in kids.into_iter().rev() {
stack.push((child, Some(id), f));
}
}
}
impl AstAdapter for CodeAdapter {
fn root(&self) -> NodeId {
NodeId(0)
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.nodes[node.0 as usize].children.clone()
}
fn name(&self, node: NodeId) -> Option<String> {
let n = &self.nodes[node.0 as usize];
n.parent.map(|_| n.kind.to_string())
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.nodes[node.0 as usize].parent
}
fn traits(&self, node: NodeId) -> Vec<String> {
vec![self.nodes[node.0 as usize].kind.to_string()]
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
let n = &self.nodes[node.0 as usize];
let (_, idx) = n.fields.iter().find(|(f, _)| *f == name)?;
let child = &self.nodes[n.children[*idx].0 as usize];
Some(Value::Str(self.text_of(child).to_string()))
}
fn default_value(&self, node: NodeId) -> Option<Value> {
Some(Value::Str(
self.text_of(&self.nodes[node.0 as usize]).to_string(),
))
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
let n = &self.nodes[node.0 as usize];
match key {
"kind" => Some(Value::Str(n.kind.to_string())),
"field" => n.field.map(|f| Value::Str(f.to_string())),
"start-line" => Some(Value::Int(n.start_line as i64)),
"end-line" => Some(Value::Int(n.end_line as i64)),
"n-children" => Some(Value::Int(n.children.len() as i64)),
_ => None,
}
}
}