1use std::path::PathBuf;
6
7use color_eyre::eyre::eyre;
8use llmy_types::error::LLMYError;
9use tree_sitter::{Language as TsLanguage, Node, Parser, Tree};
10
11use crate::model::{CallableKind, Language, LineSpan, ModuleKind, StateKind};
12
13unsafe extern "C" {
14 fn tree_sitter_move_on_aptos() -> *const std::ffi::c_void;
15 fn tree_sitter_move() -> *const std::ffi::c_void;
16}
17
18#[derive(Debug, Clone)]
20pub struct SourceFile {
21 pub relative: PathBuf,
22 pub content: String,
23}
24
25#[derive(Debug, Clone)]
27pub struct RawCallSite {
28 pub text: String,
30 pub name: String,
32 pub qualifier: Option<String>,
34 pub line: usize,
35}
36
37#[derive(Debug, Clone)]
40pub struct RawStateRef {
41 pub name: String,
42 pub write: bool,
43 pub line: usize,
44}
45
46#[derive(Debug, Clone)]
47pub struct RawCallable {
48 pub name: String,
49 pub kind: CallableKind,
50 pub signature: String,
51 pub span: LineSpan,
52 pub calls: Vec<RawCallSite>,
53 pub state_refs: Vec<RawStateRef>,
54}
55
56#[derive(Debug, Clone)]
57pub struct RawState {
58 pub name: String,
59 pub kind: StateKind,
60 pub type_text: String,
61 pub span: LineSpan,
62}
63
64#[derive(Debug, Clone)]
65pub struct RawModule {
66 pub name: String,
67 pub kind: ModuleKind,
68 pub span: LineSpan,
69 pub parents: Vec<String>,
71 pub callables: Vec<RawCallable>,
72 pub states: Vec<RawState>,
73}
74
75#[derive(Debug, Clone)]
77pub struct FileExtraction {
78 pub file: PathBuf,
79 pub language: Language,
80 pub modules: Vec<RawModule>,
81 pub parse_errors: usize,
84}
85
86pub struct GrammarSet;
88
89impl GrammarSet {
90 pub fn language_of(language: Language) -> TsLanguage {
91 match language {
92 Language::Solidity => tree_sitter_solidity::LANGUAGE.into(),
93 Language::Rust => tree_sitter_rust::LANGUAGE.into(),
94 Language::MoveAptos => {
95 let ptr = unsafe { tree_sitter_move_on_aptos() };
96 unsafe { TsLanguage::from_raw(ptr.cast()) }
97 }
98 Language::MoveSui => {
99 let ptr = unsafe { tree_sitter_move() };
100 unsafe { TsLanguage::from_raw(ptr.cast()) }
101 }
102 }
103 }
104
105 pub fn parse(language: Language, content: &str) -> Result<Tree, LLMYError> {
106 let mut parser = Parser::new();
107 parser
108 .set_language(&Self::language_of(language))
109 .map_err(|e| eyre!("failed to load {} grammar: {}", language.render(), e))?;
110 parser
111 .parse(content, None)
112 .ok_or_else(|| eyre!("{} parse returned no tree", language.render()).into())
113 }
114
115 pub fn count_errors(node: Node<'_>) -> usize {
116 let mut count = 0;
117 let mut cursor = node.walk();
118 let mut stack = vec![node];
119 while let Some(current) = stack.pop() {
120 if current.is_error() || current.is_missing() {
121 count += 1;
122 }
123 for child in current.children(&mut cursor) {
124 stack.push(child);
125 }
126 }
127 count
128 }
129
130 pub fn dump(node: Node<'_>, source: &str, depth: usize) -> String {
133 let mut out = String::new();
134 if node.is_named() {
135 let text = node.text_of(source);
136 let short: String = text
137 .chars()
138 .take(40)
139 .collect::<String>()
140 .replace('\n', "\\n");
141 out.push_str(&format!(
142 "{}{} [{}..{}] {:?}\n",
143 " ".repeat(depth),
144 node.kind(),
145 node.start_position().row + 1,
146 node.end_position().row + 1,
147 short
148 ));
149 }
150 let mut cursor = node.walk();
151 for child in node.children(&mut cursor) {
152 out.push_str(&Self::dump(child, source, depth + 1));
153 }
154 out
155 }
156}
157
158pub trait NodeUtil<'tree> {
160 fn text_of(&self, source: &str) -> String;
161 fn line_span(&self) -> LineSpan;
162 fn field_text(&self, field: &str, source: &str) -> Option<String>;
163 fn child_of_kind(&self, kind: &str) -> Option<Node<'tree>>;
164 fn children_of_kind(&self, kind: &str) -> Vec<Node<'tree>>;
165 fn descendants_of_kinds(&self, kinds: &[&str], enter_matches: bool) -> Vec<Node<'tree>>;
169 fn first_identifier(&self, source: &str) -> Option<String>;
171 fn signature_head(&self, body_kinds: &[&str], source: &str) -> String;
174}
175
176impl<'tree> NodeUtil<'tree> for Node<'tree> {
177 fn text_of(&self, source: &str) -> String {
178 source
179 .get(self.start_byte()..self.end_byte())
180 .unwrap_or_default()
181 .to_string()
182 }
183
184 fn line_span(&self) -> LineSpan {
185 LineSpan {
186 start_line: self.start_position().row + 1,
187 end_line: self.end_position().row + 1,
188 }
189 }
190
191 fn field_text(&self, field: &str, source: &str) -> Option<String> {
192 self.child_by_field_name(field.as_bytes())
193 .map(|node| node.text_of(source))
194 }
195
196 fn child_of_kind(&self, kind: &str) -> Option<Node<'tree>> {
197 let mut cursor = self.walk();
198 self.children(&mut cursor).find(|c| c.kind() == kind)
199 }
200
201 fn children_of_kind(&self, kind: &str) -> Vec<Node<'tree>> {
202 let mut cursor = self.walk();
203 self.children(&mut cursor)
204 .filter(|c| c.kind() == kind)
205 .collect()
206 }
207
208 fn descendants_of_kinds(&self, kinds: &[&str], enter_matches: bool) -> Vec<Node<'tree>> {
209 let mut out = vec![];
210 let mut stack = vec![*self];
211 while let Some(current) = stack.pop() {
212 let matched = kinds.contains(¤t.kind());
213 if matched && current.id() != self.id() {
214 out.push(current);
215 if !enter_matches {
216 continue;
217 }
218 }
219 let mut cursor = current.walk();
220 let mut children: Vec<_> = current.children(&mut cursor).collect();
221 children.reverse();
222 stack.extend(children);
223 }
224 out.sort_by_key(|node| node.start_byte());
226 out
227 }
228
229 fn first_identifier(&self, source: &str) -> Option<String> {
230 if self.kind().contains("identifier") {
231 return Some(self.text_of(source));
232 }
233 let mut stack = vec![*self];
234 let mut found: Vec<(usize, String)> = vec![];
235 while let Some(current) = stack.pop() {
236 if current.kind().contains("identifier") {
237 found.push((current.start_byte(), current.text_of(source)));
238 continue;
239 }
240 let mut cursor = current.walk();
241 for child in current.children(&mut cursor) {
242 stack.push(child);
243 }
244 }
245 found.sort();
246 found.into_iter().next().map(|(_, text)| text)
247 }
248
249 fn signature_head(&self, body_kinds: &[&str], source: &str) -> String {
250 let end = {
251 let mut cursor = self.walk();
252 self.children(&mut cursor)
253 .find(|c| body_kinds.contains(&c.kind()))
254 .map(|body| body.start_byte())
255 .unwrap_or(self.end_byte())
256 };
257 let head = source.get(self.start_byte()..end).unwrap_or_default();
258 head.split_whitespace().collect::<Vec<_>>().join(" ")
259 }
260}