#![allow(unsafe_code)]
use lean_rs::Obj;
use lean_rs::abi::structure::{ctor_tag, take_ctor_objects};
use lean_rs::abi::traits::{TryFromLean, conversion_error};
use lean_rs::error::LeanResult;
use lean_rs_sys::ctor::lean_ctor_get_uint8;
use crate::host::elaboration::LeanElabFailure;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TermInfoNode {
pub start_line: u32,
pub start_column: u32,
pub end_line: u32,
pub end_column: u32,
pub expr_str: String,
pub type_str: String,
pub expected_type_str: Option<String>,
}
impl<'lean> TryFromLean<'lean> for TermInfoNode {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
let [sl, sc, el, ec, expr, ty, exp_ty] = take_ctor_objects::<7>(obj, 0, "TermInfoNode")?;
Ok(Self {
start_line: u32::try_from_lean(sl)?,
start_column: u32::try_from_lean(sc)?,
end_line: u32::try_from_lean(el)?,
end_column: u32::try_from_lean(ec)?,
expr_str: String::try_from_lean(expr)?,
type_str: String::try_from_lean(ty)?,
expected_type_str: Option::<String>::try_from_lean(exp_ty)?,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TacticInfoNode {
pub start_line: u32,
pub start_column: u32,
pub end_line: u32,
pub end_column: u32,
pub goals_before: Vec<String>,
pub goals_after: Vec<String>,
}
impl<'lean> TryFromLean<'lean> for TacticInfoNode {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
let [sl, sc, el, ec, gb, ga] = take_ctor_objects::<6>(obj, 0, "TacticInfoNode")?;
Ok(Self {
start_line: u32::try_from_lean(sl)?,
start_column: u32::try_from_lean(sc)?,
end_line: u32::try_from_lean(el)?,
end_column: u32::try_from_lean(ec)?,
goals_before: Vec::<String>::try_from_lean(gb)?,
goals_after: Vec::<String>::try_from_lean(ga)?,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NameRefNode {
pub start_line: u32,
pub start_column: u32,
pub end_line: u32,
pub end_column: u32,
pub name: String,
pub is_binder: bool,
}
impl<'lean> TryFromLean<'lean> for NameRefNode {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
let tag = ctor_tag(&obj)?;
if tag != 0 {
return Err(conversion_error(format!(
"expected Lean NameRefNode ctor (tag 0), found tag {tag}"
)));
}
let ptr = obj.as_raw_borrowed();
let is_binder_byte = unsafe { lean_ctor_get_uint8(ptr, 0) };
let is_binder = match is_binder_byte {
0 => false,
1 => true,
other => {
return Err(conversion_error(format!(
"Lean NameRefNode.isBinder byte {other} is not in {{0, 1}}"
)));
}
};
let [sl, sc, el, ec, nm] = take_ctor_objects::<5>(obj, 0, "NameRefNode")?;
Ok(Self {
start_line: u32::try_from_lean(sl)?,
start_column: u32::try_from_lean(sc)?,
end_line: u32::try_from_lean(el)?,
end_column: u32::try_from_lean(ec)?,
name: String::try_from_lean(nm)?,
is_binder,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommandInfoNode {
pub start_line: u32,
pub start_column: u32,
pub end_line: u32,
pub end_column: u32,
pub decl_name: Option<String>,
}
impl<'lean> TryFromLean<'lean> for CommandInfoNode {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
let [sl, sc, el, ec, dn] = take_ctor_objects::<5>(obj, 0, "CommandInfoNode")?;
Ok(Self {
start_line: u32::try_from_lean(sl)?,
start_column: u32::try_from_lean(sc)?,
end_line: u32::try_from_lean(el)?,
end_column: u32::try_from_lean(ec)?,
decl_name: Option::<String>::try_from_lean(dn)?,
})
}
}
#[derive(Clone, Debug)]
pub struct ProcessedFile {
pub commands: Vec<CommandInfoNode>,
pub terms: Vec<TermInfoNode>,
pub tactics: Vec<TacticInfoNode>,
pub names: Vec<NameRefNode>,
pub diagnostics: LeanElabFailure,
}
impl ProcessedFile {
#[must_use]
pub fn term_at(&self, line: u32, column: u32) -> Option<&TermInfoNode> {
smallest_containing(&self.terms, line, column, |n| {
(n.start_line, n.start_column, n.end_line, n.end_column)
})
}
#[must_use]
pub fn tactic_at(&self, line: u32, column: u32) -> Option<&TacticInfoNode> {
smallest_containing(&self.tactics, line, column, |n| {
(n.start_line, n.start_column, n.end_line, n.end_column)
})
}
#[must_use]
pub fn references_of<'a>(&'a self, name: &str) -> Vec<&'a NameRefNode> {
self.names.iter().filter(|n| n.name == name).collect()
}
}
impl<'lean> TryFromLean<'lean> for ProcessedFile {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
let [commands, terms, tactics, names, diagnostics] = take_ctor_objects::<5>(obj, 0, "ProcessedFile")?;
Ok(Self {
commands: Vec::<CommandInfoNode>::try_from_lean(commands)?,
terms: Vec::<TermInfoNode>::try_from_lean(terms)?,
tactics: Vec::<TacticInfoNode>::try_from_lean(tactics)?,
names: Vec::<NameRefNode>::try_from_lean(names)?,
diagnostics: LeanElabFailure::try_from_lean(diagnostics)?,
})
}
}
fn smallest_containing<T>(
nodes: &[T],
line: u32,
column: u32,
range: impl Fn(&T) -> (u32, u32, u32, u32),
) -> Option<&T> {
let mut best: Option<(&T, u64)> = None;
for node in nodes {
let (sl, sc, el, ec) = range(node);
if !range_contains(sl, sc, el, ec, line, column) {
continue;
}
let area = range_area(sl, sc, el, ec);
match best {
None => best = Some((node, area)),
Some((_, best_area)) if area < best_area => best = Some((node, area)),
_ => {}
}
}
best.map(|(node, _)| node)
}
fn range_contains(sl: u32, sc: u32, el: u32, ec: u32, line: u32, column: u32) -> bool {
if line < sl || line > el {
return false;
}
if line == sl && column < sc {
return false;
}
if line == el && column > ec {
return false;
}
true
}
fn range_area(sl: u32, sc: u32, el: u32, ec: u32) -> u64 {
let line_span = u64::from(el.saturating_sub(sl));
let col_span = u64::from(ec).saturating_sub(u64::from(sc));
line_span.saturating_mul(1_000_000).saturating_add(col_span)
}