Skip to main content

rlean_search/
ast.rs

1//! Abstract syntax for Lean 4 types and declarations.
2//!
3//! The goal is a useful fragment of Lean surface types: binders, arrows,
4//! applications, common infix operators, quantifiers, and search holes.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// XML / schema namespace for rlean-search documents.
10pub const RLEAN_NS: &str = "http://github.com/createyourpersonalaccount/rlean-search";
11
12/// Kind of searchable declaration.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum DeclKind {
16    Theorem,
17    Lemma,
18    Axiom,
19}
20
21impl DeclKind {
22    pub fn as_str(self) -> &'static str {
23        match self {
24            DeclKind::Theorem => "theorem",
25            DeclKind::Lemma => "lemma",
26            DeclKind::Axiom => "axiom",
27        }
28    }
29
30    pub fn parse(s: &str) -> Option<Self> {
31        match s {
32            "theorem" => Some(DeclKind::Theorem),
33            "lemma" => Some(DeclKind::Lemma),
34            "axiom" => Some(DeclKind::Axiom),
35            _ => None,
36        }
37    }
38}
39
40impl fmt::Display for DeclKind {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(self.as_str())
43    }
44}
45
46/// Explicit binder kind in Lean surface syntax.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum BinderKind {
50    /// `(x : T)` default
51    Default,
52    /// `{x : T}` implicit
53    Implicit,
54    /// `[x : T]` instance-implicit
55    Instance,
56    /// `⦃x : T⦄` strict-implicit
57    StrictImplicit,
58}
59
60/// A binder group: `(a b : Nat)` or `{α : Type u}`.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct Binder {
63    pub kind: BinderKind,
64    pub names: Vec<String>,
65    pub ty: Option<Box<TypeExpr>>,
66}
67
68/// Surface type expression used for indexing and pattern matching.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub enum TypeExpr {
71    /// Anonymous hole `_` (search only, or explicit underscore in source).
72    Hole,
73    /// Named hole `?a` (search patterns; also metavariable-like tokens).
74    NamedHole(String),
75    /// Identifier / constant, e.g. `Nat`, `List`, `add_comm`.
76    Ident(String),
77    /// Numeric literal.
78    NatLit(String),
79    /// String / char literal (kept raw).
80    Literal(String),
81    /// Function application `f a` (left-associative chain folded as nested apps).
82    App(Box<TypeExpr>, Box<TypeExpr>),
83    /// Infix binary operator: `a + b`, `x = y`, `P ∧ Q`, etc.
84    BinOp {
85        op: String,
86        left: Box<TypeExpr>,
87        right: Box<TypeExpr>,
88    },
89    /// Unary prefix operator: `¬P`, `-n`, `⁻¹` is usually postfix — see `Postfix`.
90    UnaryOp {
91        op: String,
92        arg: Box<TypeExpr>,
93    },
94    /// Postfix operator: `a⁻¹`, `f'`.
95    Postfix {
96        arg: Box<TypeExpr>,
97        op: String,
98    },
99    /// `A → B` / `A -> B`
100    Arrow(Box<TypeExpr>, Box<TypeExpr>),
101    /// `∀ binders, body` / `forall`
102    Forall {
103        binders: Vec<Binder>,
104        body: Box<TypeExpr>,
105    },
106    /// `∃ binders, body` / `exists`
107    Exists {
108        binders: Vec<Binder>,
109        body: Box<TypeExpr>,
110    },
111    /// `fun binders => body` / `λ`
112    Lambda {
113        binders: Vec<Binder>,
114        body: Box<TypeExpr>,
115    },
116    /// Explicit binder-typed term used in Pi: `(x : A) → B`
117    Pi {
118        binder: Binder,
119        body: Box<TypeExpr>,
120    },
121    /// Projection `e.field` / `e.1`
122    Proj {
123        base: Box<TypeExpr>,
124        field: String,
125    },
126    /// Universe / sort: `Prop`, `Type`, `Type u`, `Sort u`, `Type*`, `Sort _`
127    Sort {
128        name: String,
129        level: Option<Box<TypeExpr>>,
130    },
131    /// Explicit list / structure sugar kept as raw for robustness.
132    Raw(String),
133}
134
135impl TypeExpr {
136    /// Fold a function and argument list into nested `App` nodes.
137    pub fn apps(f: TypeExpr, args: impl IntoIterator<Item = TypeExpr>) -> TypeExpr {
138        args.into_iter()
139            .fold(f, |acc, a| TypeExpr::App(Box::new(acc), Box::new(a)))
140    }
141
142    /// Collect free identifiers (rough; for indexing).
143    pub fn idents(&self) -> Vec<&str> {
144        let mut out = Vec::new();
145        self.collect_idents(&mut out);
146        out
147    }
148
149    fn collect_idents<'a>(&'a self, out: &mut Vec<&'a str>) {
150        match self {
151            TypeExpr::Ident(s) => out.push(s),
152            TypeExpr::App(f, a) => {
153                f.collect_idents(out);
154                a.collect_idents(out);
155            }
156            TypeExpr::BinOp { left, right, .. } => {
157                left.collect_idents(out);
158                right.collect_idents(out);
159            }
160            TypeExpr::UnaryOp { arg, .. } | TypeExpr::Postfix { arg, .. } => {
161                arg.collect_idents(out);
162            }
163            TypeExpr::Arrow(a, b) => {
164                a.collect_idents(out);
165                b.collect_idents(out);
166            }
167            TypeExpr::Forall { binders, body }
168            | TypeExpr::Exists { binders, body }
169            | TypeExpr::Lambda { binders, body } => {
170                for b in binders {
171                    if let Some(ty) = &b.ty {
172                        ty.collect_idents(out);
173                    }
174                }
175                body.collect_idents(out);
176            }
177            TypeExpr::Pi { binder, body } => {
178                if let Some(ty) = &binder.ty {
179                    ty.collect_idents(out);
180                }
181                body.collect_idents(out);
182            }
183            TypeExpr::Proj { base, .. } => base.collect_idents(out),
184            TypeExpr::Sort { level: Some(l), .. } => l.collect_idents(out),
185            _ => {}
186        }
187    }
188
189    /// Operators appearing in the expression (for inverted index).
190    pub fn operators(&self) -> Vec<&str> {
191        let mut out = Vec::new();
192        self.collect_ops(&mut out);
193        out
194    }
195
196    fn collect_ops<'a>(&'a self, out: &mut Vec<&'a str>) {
197        match self {
198            TypeExpr::BinOp { op, left, right } => {
199                out.push(op.as_str());
200                left.collect_ops(out);
201                right.collect_ops(out);
202            }
203            TypeExpr::UnaryOp { op, arg } => {
204                out.push(op.as_str());
205                arg.collect_ops(out);
206            }
207            TypeExpr::Postfix { op, arg } => {
208                out.push(op.as_str());
209                arg.collect_ops(out);
210            }
211            TypeExpr::App(f, a) => {
212                f.collect_ops(out);
213                a.collect_ops(out);
214            }
215            TypeExpr::Arrow(a, b) => {
216                out.push("→");
217                a.collect_ops(out);
218                b.collect_ops(out);
219            }
220            TypeExpr::Forall { binders, body } => {
221                out.push("∀");
222                for b in binders {
223                    if let Some(ty) = &b.ty {
224                        ty.collect_ops(out);
225                    }
226                }
227                body.collect_ops(out);
228            }
229            TypeExpr::Exists { binders, body } => {
230                out.push("∃");
231                for b in binders {
232                    if let Some(ty) = &b.ty {
233                        ty.collect_ops(out);
234                    }
235                }
236                body.collect_ops(out);
237            }
238            TypeExpr::Lambda { binders, body } => {
239                for b in binders {
240                    if let Some(ty) = &b.ty {
241                        ty.collect_ops(out);
242                    }
243                }
244                body.collect_ops(out);
245            }
246            TypeExpr::Pi { binder, body } => {
247                out.push("→");
248                if let Some(ty) = &binder.ty {
249                    ty.collect_ops(out);
250                }
251                body.collect_ops(out);
252            }
253            TypeExpr::Proj { base, .. } => base.collect_ops(out),
254            TypeExpr::Sort { level: Some(l), .. } => l.collect_ops(out),
255            _ => {}
256        }
257    }
258
259    /// Strip outer binders / arrows to obtain the main conclusion.
260    ///
261    /// `∀ x, P → Q → R` concludes with `R`.
262    pub fn conclusion(&self) -> &TypeExpr {
263        match self {
264            TypeExpr::Forall { body, .. }
265            | TypeExpr::Exists { body, .. }
266            | TypeExpr::Lambda { body, .. }
267            | TypeExpr::Pi { body, .. } => body.conclusion(),
268            TypeExpr::Arrow(_, right) => right.conclusion(),
269            other => other,
270        }
271    }
272
273    /// Head symbol for inverted indexing (operator or leading ident).
274    pub fn head_key(&self) -> String {
275        match self.conclusion() {
276            TypeExpr::BinOp { op, .. } => format!("op:{op}"),
277            TypeExpr::UnaryOp { op, .. } => format!("uop:{op}"),
278            TypeExpr::Postfix { op, .. } => format!("pop:{op}"),
279            TypeExpr::Ident(s) => format!("id:{s}"),
280            TypeExpr::App(f, _) => match f.as_ref() {
281                TypeExpr::Ident(s) => format!("id:{s}"),
282                TypeExpr::App(ff, _) => match ff.as_ref() {
283                    TypeExpr::Ident(s) => format!("id:{s}"),
284                    _ => "app".into(),
285                },
286                _ => "app".into(),
287            },
288            TypeExpr::Arrow(_, _) => "op:→".into(),
289            TypeExpr::Forall { .. } => "op:∀".into(),
290            TypeExpr::Exists { .. } => "op:∃".into(),
291            TypeExpr::Sort { name, .. } => format!("sort:{name}"),
292            TypeExpr::NatLit(_) => "lit:nat".into(),
293            TypeExpr::Hole | TypeExpr::NamedHole(_) => "hole".into(),
294            _ => "other".into(),
295        }
296    }
297
298    /// Pretty-print a compact surface form (for display / cache).
299    pub fn surface(&self) -> String {
300        match self {
301            TypeExpr::Hole => "_".into(),
302            TypeExpr::NamedHole(n) => format!("?{n}"),
303            TypeExpr::Ident(s) => s.clone(),
304            TypeExpr::NatLit(n) => n.clone(),
305            TypeExpr::Literal(s) => s.clone(),
306            TypeExpr::App(f, a) => {
307                let fa = f.surface();
308                let aa = match a.as_ref() {
309                    TypeExpr::App(_, _)
310                    | TypeExpr::BinOp { .. }
311                    | TypeExpr::Arrow(_, _)
312                    | TypeExpr::Forall { .. }
313                    | TypeExpr::Exists { .. }
314                    | TypeExpr::Lambda { .. }
315                    | TypeExpr::Pi { .. } => format!("({})", a.surface()),
316                    _ => a.surface(),
317                };
318                format!("{fa} {aa}")
319            }
320            TypeExpr::BinOp { op, left, right } => {
321                format!("({} {} {})", left.surface(), op, right.surface())
322            }
323            TypeExpr::UnaryOp { op, arg } => format!("{op}{}", arg.surface()),
324            TypeExpr::Postfix { arg, op } => format!("{}{op}", arg.surface()),
325            TypeExpr::Arrow(a, b) => format!("({} → {})", a.surface(), b.surface()),
326            TypeExpr::Forall { binders, body } => {
327                format!("(∀ {}, {})", format_binders(binders), body.surface())
328            }
329            TypeExpr::Exists { binders, body } => {
330                format!("(∃ {}, {})", format_binders(binders), body.surface())
331            }
332            TypeExpr::Lambda { binders, body } => {
333                format!("(fun {} => {})", format_binders(binders), body.surface())
334            }
335            TypeExpr::Pi { binder, body } => {
336                format!("({} → {})", format_binder(binder), body.surface())
337            }
338            TypeExpr::Proj { base, field } => format!("{}.{}", base.surface(), field),
339            TypeExpr::Sort { name, level } => match level {
340                Some(l) => format!("{name} {}", l.surface()),
341                None => name.clone(),
342            },
343            TypeExpr::Raw(s) => s.clone(),
344        }
345    }
346}
347
348fn format_binders(binders: &[Binder]) -> String {
349    binders
350        .iter()
351        .map(format_binder)
352        .collect::<Vec<_>>()
353        .join(" ")
354}
355
356fn format_binder(b: &Binder) -> String {
357    let names = b.names.join(" ");
358    let inner = match &b.ty {
359        Some(ty) => format!("{names} : {}", ty.surface()),
360        None => names,
361    };
362    match b.kind {
363        BinderKind::Default => format!("({inner})"),
364        BinderKind::Implicit => format!("{{{inner}}}"),
365        BinderKind::Instance => format!("[{inner}]"),
366        BinderKind::StrictImplicit => format!("⦃{inner}⦄"),
367    }
368}
369
370/// A parsed declaration ready for indexing / XML export.
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
372pub struct Declaration {
373    pub kind: DeclKind,
374    pub name: String,
375    /// Fully qualified-ish name if namespace known: `Nat.add_comm`.
376    pub full_name: String,
377    pub binders: Vec<Binder>,
378    pub ty: TypeExpr,
379    /// Original type surface text (as in source, trimmed).
380    pub type_surface: String,
381    pub file: String,
382    pub line: usize,
383    pub module: Option<String>,
384    pub namespace_path: Vec<String>,
385    pub attributes: Vec<String>,
386}
387
388impl Declaration {
389    /// Type including explicit binders as Pi/forall-like arrow chain for matching.
390    pub fn effective_type(&self) -> TypeExpr {
391        if self.binders.is_empty() {
392            return self.ty.clone();
393        }
394        // Represent leading binders as a Forall wrapping the stated type.
395        TypeExpr::Forall {
396            binders: self.binders.clone(),
397            body: Box::new(self.ty.clone()),
398        }
399    }
400}
401
402/// One indexed Lean source package / lake root.
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct PackageInfo {
405    pub name: String,
406    pub root: String,
407    pub src_dirs: Vec<String>,
408    pub lean_libs: Vec<String>,
409}
410
411/// Full in-memory / on-disk index document.
412#[derive(Debug, Clone, Default, Serialize, Deserialize)]
413pub struct IndexDocument {
414    pub schema: String,
415    pub created_at: String,
416    pub packages: Vec<PackageInfo>,
417    pub declarations: Vec<Declaration>,
418    /// Source fingerprint for cache validation.
419    pub source_hash: String,
420}
421
422impl IndexDocument {
423    pub fn new() -> Self {
424        Self {
425            schema: RLEAN_NS.to_string(),
426            created_at: chrono::Utc::now().to_rfc3339(),
427            packages: Vec::new(),
428            declarations: Vec::new(),
429            source_hash: String::new(),
430        }
431    }
432}