use std::path::PathBuf;
use color_eyre::eyre::eyre;
use llmy_types::error::LLMYError;
use tree_sitter::{Language as TsLanguage, Node, Parser, Tree};
use crate::model::{CallableKind, Language, LineSpan, ModuleKind, StateKind};
unsafe extern "C" {
fn tree_sitter_move_on_aptos() -> *const std::ffi::c_void;
fn tree_sitter_move() -> *const std::ffi::c_void;
}
#[derive(Debug, Clone)]
pub struct SourceFile {
pub relative: PathBuf,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct RawCallSite {
pub text: String,
pub name: String,
pub qualifier: Option<String>,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct RawStateRef {
pub name: String,
pub write: bool,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct RawCallable {
pub name: String,
pub kind: CallableKind,
pub signature: String,
pub span: LineSpan,
pub calls: Vec<RawCallSite>,
pub state_refs: Vec<RawStateRef>,
}
#[derive(Debug, Clone)]
pub struct RawState {
pub name: String,
pub kind: StateKind,
pub type_text: String,
pub span: LineSpan,
}
#[derive(Debug, Clone)]
pub struct RawModule {
pub name: String,
pub kind: ModuleKind,
pub span: LineSpan,
pub parents: Vec<String>,
pub callables: Vec<RawCallable>,
pub states: Vec<RawState>,
}
#[derive(Debug, Clone)]
pub struct FileExtraction {
pub file: PathBuf,
pub language: Language,
pub modules: Vec<RawModule>,
pub parse_errors: usize,
}
pub struct GrammarSet;
impl GrammarSet {
pub fn language_of(language: Language) -> TsLanguage {
match language {
Language::Solidity => tree_sitter_solidity::LANGUAGE.into(),
Language::Rust => tree_sitter_rust::LANGUAGE.into(),
Language::MoveAptos => {
let ptr = unsafe { tree_sitter_move_on_aptos() };
unsafe { TsLanguage::from_raw(ptr.cast()) }
}
Language::MoveSui => {
let ptr = unsafe { tree_sitter_move() };
unsafe { TsLanguage::from_raw(ptr.cast()) }
}
}
}
pub fn parse(language: Language, content: &str) -> Result<Tree, LLMYError> {
let mut parser = Parser::new();
parser
.set_language(&Self::language_of(language))
.map_err(|e| eyre!("failed to load {} grammar: {}", language.render(), e))?;
parser
.parse(content, None)
.ok_or_else(|| eyre!("{} parse returned no tree", language.render()).into())
}
pub fn count_errors(node: Node<'_>) -> usize {
let mut count = 0;
let mut cursor = node.walk();
let mut stack = vec![node];
while let Some(current) = stack.pop() {
if current.is_error() || current.is_missing() {
count += 1;
}
for child in current.children(&mut cursor) {
stack.push(child);
}
}
count
}
pub fn dump(node: Node<'_>, source: &str, depth: usize) -> String {
let mut out = String::new();
if node.is_named() {
let text = node.text_of(source);
let short: String = text
.chars()
.take(40)
.collect::<String>()
.replace('\n', "\\n");
out.push_str(&format!(
"{}{} [{}..{}] {:?}\n",
" ".repeat(depth),
node.kind(),
node.start_position().row + 1,
node.end_position().row + 1,
short
));
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
out.push_str(&Self::dump(child, source, depth + 1));
}
out
}
}
pub trait NodeUtil<'tree> {
fn text_of(&self, source: &str) -> String;
fn line_span(&self) -> LineSpan;
fn field_text(&self, field: &str, source: &str) -> Option<String>;
fn child_of_kind(&self, kind: &str) -> Option<Node<'tree>>;
fn children_of_kind(&self, kind: &str) -> Vec<Node<'tree>>;
fn descendants_of_kinds(&self, kinds: &[&str], enter_matches: bool) -> Vec<Node<'tree>>;
fn first_identifier(&self, source: &str) -> Option<String>;
fn signature_head(&self, body_kinds: &[&str], source: &str) -> String;
}
impl<'tree> NodeUtil<'tree> for Node<'tree> {
fn text_of(&self, source: &str) -> String {
source
.get(self.start_byte()..self.end_byte())
.unwrap_or_default()
.to_string()
}
fn line_span(&self) -> LineSpan {
LineSpan {
start_line: self.start_position().row + 1,
end_line: self.end_position().row + 1,
}
}
fn field_text(&self, field: &str, source: &str) -> Option<String> {
self.child_by_field_name(field.as_bytes())
.map(|node| node.text_of(source))
}
fn child_of_kind(&self, kind: &str) -> Option<Node<'tree>> {
let mut cursor = self.walk();
self.children(&mut cursor).find(|c| c.kind() == kind)
}
fn children_of_kind(&self, kind: &str) -> Vec<Node<'tree>> {
let mut cursor = self.walk();
self.children(&mut cursor)
.filter(|c| c.kind() == kind)
.collect()
}
fn descendants_of_kinds(&self, kinds: &[&str], enter_matches: bool) -> Vec<Node<'tree>> {
let mut out = vec![];
let mut stack = vec![*self];
while let Some(current) = stack.pop() {
let matched = kinds.contains(¤t.kind());
if matched && current.id() != self.id() {
out.push(current);
if !enter_matches {
continue;
}
}
let mut cursor = current.walk();
let mut children: Vec<_> = current.children(&mut cursor).collect();
children.reverse();
stack.extend(children);
}
out.sort_by_key(|node| node.start_byte());
out
}
fn first_identifier(&self, source: &str) -> Option<String> {
if self.kind().contains("identifier") {
return Some(self.text_of(source));
}
let mut stack = vec![*self];
let mut found: Vec<(usize, String)> = vec![];
while let Some(current) = stack.pop() {
if current.kind().contains("identifier") {
found.push((current.start_byte(), current.text_of(source)));
continue;
}
let mut cursor = current.walk();
for child in current.children(&mut cursor) {
stack.push(child);
}
}
found.sort();
found.into_iter().next().map(|(_, text)| text)
}
fn signature_head(&self, body_kinds: &[&str], source: &str) -> String {
let end = {
let mut cursor = self.walk();
self.children(&mut cursor)
.find(|c| body_kinds.contains(&c.kind()))
.map(|body| body.start_byte())
.unwrap_or(self.end_byte())
};
let head = source.get(self.start_byte()..end).unwrap_or_default();
head.split_whitespace().collect::<Vec<_>>().join(" ")
}
}