1#![allow(unsafe_code)]
15
16use lean_rs::Obj;
17use lean_rs::abi::structure::{ctor_tag, take_ctor_objects};
18use lean_rs::abi::traits::{TryFromLean, conversion_error};
19use lean_rs::error::LeanResult;
20use lean_rs_sys::ctor::lean_ctor_get_uint8;
21
22use crate::host::elaboration::LeanElabFailure;
23
24#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct TermInfoNode {
32 pub start_line: u32,
34 pub start_column: u32,
36 pub end_line: u32,
38 pub end_column: u32,
40 pub expr_str: String,
42 pub type_str: String,
45 pub expected_type_str: Option<String>,
48}
49
50impl<'lean> TryFromLean<'lean> for TermInfoNode {
51 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
52 let [sl, sc, el, ec, expr, ty, exp_ty] = take_ctor_objects::<7>(obj, 0, "TermInfoNode")?;
53 Ok(Self {
54 start_line: u32::try_from_lean(sl)?,
55 start_column: u32::try_from_lean(sc)?,
56 end_line: u32::try_from_lean(el)?,
57 end_column: u32::try_from_lean(ec)?,
58 expr_str: String::try_from_lean(expr)?,
59 type_str: String::try_from_lean(ty)?,
60 expected_type_str: Option::<String>::try_from_lean(exp_ty)?,
61 })
62 }
63}
64
65#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct TacticInfoNode {
74 pub start_line: u32,
76 pub start_column: u32,
78 pub end_line: u32,
80 pub end_column: u32,
82 pub goals_before: Vec<String>,
84 pub goals_after: Vec<String>,
86}
87
88impl<'lean> TryFromLean<'lean> for TacticInfoNode {
89 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
90 let [sl, sc, el, ec, gb, ga] = take_ctor_objects::<6>(obj, 0, "TacticInfoNode")?;
91 Ok(Self {
92 start_line: u32::try_from_lean(sl)?,
93 start_column: u32::try_from_lean(sc)?,
94 end_line: u32::try_from_lean(el)?,
95 end_column: u32::try_from_lean(ec)?,
96 goals_before: Vec::<String>::try_from_lean(gb)?,
97 goals_after: Vec::<String>::try_from_lean(ga)?,
98 })
99 }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct NameRefNode {
111 pub start_line: u32,
113 pub start_column: u32,
115 pub end_line: u32,
117 pub end_column: u32,
119 pub name: String,
122 pub is_binder: bool,
124}
125
126impl<'lean> TryFromLean<'lean> for NameRefNode {
127 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
128 let tag = ctor_tag(&obj)?;
133 if tag != 0 {
134 return Err(conversion_error(format!(
135 "expected Lean NameRefNode ctor (tag 0), found tag {tag}"
136 )));
137 }
138 let ptr = obj.as_raw_borrowed();
139 let is_binder_byte = unsafe { lean_ctor_get_uint8(ptr, 0) };
142 let is_binder = match is_binder_byte {
143 0 => false,
144 1 => true,
145 other => {
146 return Err(conversion_error(format!(
147 "Lean NameRefNode.isBinder byte {other} is not in {{0, 1}}"
148 )));
149 }
150 };
151 let [sl, sc, el, ec, nm] = take_ctor_objects::<5>(obj, 0, "NameRefNode")?;
152 Ok(Self {
153 start_line: u32::try_from_lean(sl)?,
154 start_column: u32::try_from_lean(sc)?,
155 end_line: u32::try_from_lean(el)?,
156 end_column: u32::try_from_lean(ec)?,
157 name: String::try_from_lean(nm)?,
158 is_binder,
159 })
160 }
161}
162
163#[derive(Clone, Debug, Eq, PartialEq)]
169pub struct CommandInfoNode {
170 pub start_line: u32,
172 pub start_column: u32,
174 pub end_line: u32,
176 pub end_column: u32,
178 pub decl_name: Option<String>,
180}
181
182impl<'lean> TryFromLean<'lean> for CommandInfoNode {
183 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
184 let [sl, sc, el, ec, dn] = take_ctor_objects::<5>(obj, 0, "CommandInfoNode")?;
185 Ok(Self {
186 start_line: u32::try_from_lean(sl)?,
187 start_column: u32::try_from_lean(sc)?,
188 end_line: u32::try_from_lean(el)?,
189 end_column: u32::try_from_lean(ec)?,
190 decl_name: Option::<String>::try_from_lean(dn)?,
191 })
192 }
193}
194
195#[derive(Clone, Debug)]
203pub struct ProcessedFile {
204 pub commands: Vec<CommandInfoNode>,
206 pub terms: Vec<TermInfoNode>,
208 pub tactics: Vec<TacticInfoNode>,
210 pub names: Vec<NameRefNode>,
212 pub diagnostics: LeanElabFailure,
216}
217
218impl ProcessedFile {
219 #[must_use]
224 pub fn term_at(&self, line: u32, column: u32) -> Option<&TermInfoNode> {
225 smallest_containing(&self.terms, line, column, |n| {
226 (n.start_line, n.start_column, n.end_line, n.end_column)
227 })
228 }
229
230 #[must_use]
233 pub fn tactic_at(&self, line: u32, column: u32) -> Option<&TacticInfoNode> {
234 smallest_containing(&self.tactics, line, column, |n| {
235 (n.start_line, n.start_column, n.end_line, n.end_column)
236 })
237 }
238
239 #[must_use]
244 pub fn references_of<'a>(&'a self, name: &str) -> Vec<&'a NameRefNode> {
245 self.names.iter().filter(|n| n.name == name).collect()
246 }
247}
248
249impl<'lean> TryFromLean<'lean> for ProcessedFile {
250 fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
251 let [commands, terms, tactics, names, diagnostics] = take_ctor_objects::<5>(obj, 0, "ProcessedFile")?;
252 Ok(Self {
253 commands: Vec::<CommandInfoNode>::try_from_lean(commands)?,
254 terms: Vec::<TermInfoNode>::try_from_lean(terms)?,
255 tactics: Vec::<TacticInfoNode>::try_from_lean(tactics)?,
256 names: Vec::<NameRefNode>::try_from_lean(names)?,
257 diagnostics: LeanElabFailure::try_from_lean(diagnostics)?,
258 })
259 }
260}
261
262fn smallest_containing<T>(
263 nodes: &[T],
264 line: u32,
265 column: u32,
266 range: impl Fn(&T) -> (u32, u32, u32, u32),
267) -> Option<&T> {
268 let mut best: Option<(&T, u64)> = None;
269 for node in nodes {
270 let (sl, sc, el, ec) = range(node);
271 if !range_contains(sl, sc, el, ec, line, column) {
272 continue;
273 }
274 let area = range_area(sl, sc, el, ec);
275 match best {
276 None => best = Some((node, area)),
277 Some((_, best_area)) if area < best_area => best = Some((node, area)),
278 _ => {}
279 }
280 }
281 best.map(|(node, _)| node)
282}
283
284fn range_contains(sl: u32, sc: u32, el: u32, ec: u32, line: u32, column: u32) -> bool {
285 if line < sl || line > el {
286 return false;
287 }
288 if line == sl && column < sc {
289 return false;
290 }
291 if line == el && column > ec {
292 return false;
293 }
294 true
295}
296
297fn range_area(sl: u32, sc: u32, el: u32, ec: u32) -> u64 {
298 let line_span = u64::from(el.saturating_sub(sl));
301 let col_span = u64::from(ec).saturating_sub(u64::from(sc));
302 line_span.saturating_mul(1_000_000).saturating_add(col_span)
303}