1use crate::ast::{Binder, BinderKind, DeclKind, Declaration, TypeExpr};
4use crate::lexer::{Lexer, Token};
5use crate::parser::type_expr::{parse_type, ParseError};
6use std::path::Path;
7
8pub fn parse_declarations(source: &str, file: &str) -> Vec<Declaration> {
10 parse_declarations_with_path(source, file, None)
11}
12
13pub fn parse_declarations_with_path(
14 source: &str,
15 file: &str,
16 module_hint: Option<&str>,
17) -> Vec<Declaration> {
18 let mut decls = Vec::new();
19 let mut namespace_stack: Vec<String> = Vec::new();
20 let mut section_depth: usize = 0;
21
22 let lines: Vec<&str> = source.lines().collect();
24 let mut i = 0usize;
25
26 while i < lines.len() {
27 let raw = lines[i];
28 let line = strip_line_comment(raw).trim();
29
30 if line.is_empty() {
31 i += 1;
32 continue;
33 }
34
35 if line.starts_with("/-") && !line.contains("-/") {
37 i += 1;
38 while i < lines.len() && !lines[i].contains("-/") {
39 i += 1;
40 }
41 i += 1;
42 continue;
43 }
44
45 if let Some(rest) = line.strip_prefix("namespace ") {
46 let name = rest.split_whitespace().next().unwrap_or("").trim();
47 if !name.is_empty() && name != "_root_" {
48 for part in name.split('.') {
49 if !part.is_empty() {
50 namespace_stack.push(part.to_string());
51 }
52 }
53 }
54 i += 1;
55 continue;
56 }
57 if line == "end" || line.starts_with("end ") {
58 let name = line.strip_prefix("end").unwrap_or("").trim();
60 if name.is_empty() {
61 if section_depth > 0 {
62 section_depth -= 1;
63 } else if !namespace_stack.is_empty() {
64 namespace_stack.pop();
65 }
66 } else if namespace_stack.last().map(|s| s.as_str()) == Some(name)
67 || namespace_stack
68 .iter()
69 .rev()
70 .take(name.matches('.').count() + 1)
71 .cloned()
72 .collect::<Vec<_>>()
73 .into_iter()
74 .rev()
75 .collect::<Vec<_>>()
76 .join(".")
77 == name
78 {
79 let parts = name.split('.').filter(|p| !p.is_empty()).count();
80 for _ in 0..parts.max(1) {
81 namespace_stack.pop();
82 }
83 } else if section_depth > 0 {
84 section_depth -= 1;
85 } else {
86 namespace_stack.pop();
88 }
89 i += 1;
90 continue;
91 }
92 if line.starts_with("section") {
93 section_depth += 1;
94 i += 1;
95 continue;
96 }
97
98 let (attrs, decl_line, start_line_idx) = collect_decl_start(&lines, i);
100 if let Some((kind, after_kw)) = match_decl_keyword(decl_line) {
101 if let Some((decl, end_i)) = extract_one_decl_with_end(
102 &lines,
103 start_line_idx,
104 kind,
105 after_kw,
106 attrs,
107 file,
108 &namespace_stack,
109 module_hint,
110 ) {
111 decls.push(decl);
112 i = end_i;
113 continue;
114 }
115 }
116
117 i += 1;
118 }
119
120 decls
121}
122
123fn strip_line_comment(line: &str) -> &str {
124 if let Some(idx) = find_line_comment(line) {
126 &line[..idx]
127 } else {
128 line
129 }
130}
131
132fn find_line_comment(line: &str) -> Option<usize> {
133 let bytes = line.as_bytes();
134 let mut i = 0;
135 let mut in_str = false;
136 while i + 1 < bytes.len() {
137 let c = bytes[i];
138 if in_str {
139 if c == b'\\' {
140 i += 2;
141 continue;
142 }
143 if c == b'"' {
144 in_str = false;
145 }
146 i += 1;
147 continue;
148 }
149 if c == b'"' {
150 in_str = true;
151 i += 1;
152 continue;
153 }
154 if c == b'-' && bytes[i + 1] == b'-' {
155 return Some(i);
156 }
157 i += 1;
158 }
159 None
160}
161
162fn collect_decl_start<'a>(lines: &[&'a str], i: usize) -> (Vec<String>, &'a str, usize) {
163 let mut attrs = Vec::new();
164 let mut j = i;
165 while j < lines.len() {
167 let t = strip_line_comment(lines[j]).trim();
168 if t.starts_with("@[") && !contains_decl_keyword(t) {
169 attrs.push(t.to_string());
170 j += 1;
171 continue;
172 }
173 break;
174 }
175 if j >= lines.len() {
176 return (attrs, "", i);
177 }
178 let t = strip_line_comment(lines[j]).trim();
179 let (more_attrs, rest) = split_leading_attrs(t);
181 attrs.extend(more_attrs);
182 (attrs, rest, j)
183}
184
185fn split_leading_attrs(s: &str) -> (Vec<String>, &str) {
186 let mut attrs = Vec::new();
187 let mut rest = s;
188 while rest.starts_with('@') {
189 if let Some(end) = find_matching_bracket(rest, 1) {
190 attrs.push(rest[..=end].to_string());
192 rest = rest[end + 1..].trim_start();
193 } else {
194 break;
195 }
196 }
197 (attrs, rest)
198}
199
200fn find_matching_bracket(s: &str, open_idx: usize) -> Option<usize> {
201 let bytes = s.as_bytes();
203 if open_idx >= bytes.len() || bytes[open_idx] != b'[' {
204 return None;
205 }
206 let mut depth = 0i32;
207 let mut i = open_idx;
208 while i < bytes.len() {
209 match bytes[i] {
210 b'[' => depth += 1,
211 b']' => {
212 depth -= 1;
213 if depth == 0 {
214 return Some(i);
215 }
216 }
217 _ => {}
218 }
219 i += 1;
220 }
221 None
222}
223
224fn contains_decl_keyword(s: &str) -> bool {
225 s.contains("theorem ") || s.contains("lemma ") || s.contains("axiom ")
226}
227
228fn match_decl_keyword(s: &str) -> Option<(DeclKind, &str)> {
229 let s = s.trim();
230 let mut rest = s;
232 loop {
233 let trimmed = rest.trim_start();
234 if let Some(r) = strip_modifier(trimmed) {
235 rest = r;
236 continue;
237 }
238 break;
239 }
240 let rest = rest.trim_start();
241 for (kw, kind) in [
242 ("theorem ", DeclKind::Theorem),
243 ("lemma ", DeclKind::Lemma),
244 ("axiom ", DeclKind::Axiom),
245 ] {
246 if let Some(after) = rest.strip_prefix(kw) {
247 return Some((kind, after.trim_start()));
248 }
249 }
250 None
251}
252
253fn strip_modifier(s: &str) -> Option<&str> {
254 for m in [
255 "protected ",
256 "private ",
257 "noncomputable ",
258 "public ",
259 "unsafe ",
260 "partial ",
261 ] {
262 if let Some(r) = s.strip_prefix(m) {
263 return Some(r);
264 }
265 }
266 None
267}
268
269fn extract_one_decl_with_end(
270 lines: &[&str],
271 start: usize,
272 kind: DeclKind,
273 after_kw: &str,
274 attrs: Vec<String>,
275 file: &str,
276 namespace_stack: &[String],
277 module_hint: Option<&str>,
278) -> Option<(Declaration, usize)> {
279 let mut buf = after_kw.to_string();
281 let mut end = start;
282 let mut depth_paren = count_balance(&buf, '(', ')');
283 let mut depth_brace = count_balance(&buf, '{', '}');
284 let mut depth_brack = count_balance(&buf, '[', ']');
285 let seen_assign = buf.contains(":=");
286
287 if !seen_assign {
288 let mut j = start + 1;
289 while j < lines.len() {
290 let t = strip_line_comment(lines[j]);
291 let trimmed = t.trim();
292 if depth_paren <= 0
294 && depth_brace <= 0
295 && depth_brack <= 0
296 && looks_like_new_decl(trimmed)
297 && buf.contains(':')
298 {
299 break;
300 }
301 buf.push('\n');
302 buf.push_str(t);
303 depth_paren += count_balance(t, '(', ')');
304 depth_brace += count_balance(t, '{', '}');
305 depth_brack += count_balance(t, '[', ']');
306 end = j;
307 if t.contains(":=") {
308 break;
309 }
310 if depth_paren <= 0
312 && depth_brace <= 0
313 && depth_brack <= 0
314 && (trimmed.starts_with("where") || trimmed.contains(" where "))
315 && buf.contains(':')
316 {
317 break;
318 }
319 j += 1;
320 if j > start + 80 {
322 break;
323 }
324 }
325 }
326
327 let (name, binders_src, type_src) = split_name_binders_type(&buf)?;
329 if name.is_empty() {
330 return None;
331 }
332
333 let binders = parse_binders_src(&binders_src);
334 let type_surface = collapse_ws(&type_src);
335 if type_surface.is_empty() {
336 return None;
337 }
338
339 let ty = match parse_type(&type_surface) {
340 Ok(t) => t,
341 Err(_) => {
342 TypeExpr::Raw(type_surface.clone())
344 }
345 };
346
347 let full_name = if namespace_stack.is_empty() {
348 name.clone()
349 } else {
350 format!("{}.{}", namespace_stack.join("."), name)
351 };
352
353 let module = module_hint.map(|s| s.to_string()).or_else(|| {
354 Path::new(file)
355 .file_stem()
356 .map(|s| s.to_string_lossy().into_owned())
357 });
358
359 let decl = Declaration {
360 kind,
361 name,
362 full_name,
363 binders,
364 ty,
365 type_surface,
366 file: file.to_string(),
367 line: start + 1,
368 module,
369 namespace_path: namespace_stack.to_vec(),
370 attributes: attrs,
371 };
372
373 Some((decl, end + 1))
374}
375
376fn looks_like_new_decl(trimmed: &str) -> bool {
377 let t = trimmed.trim_start_matches('@');
378 if trimmed.starts_with("@[") && !contains_decl_keyword(trimmed) {
380 return true;
381 }
382 for p in [
383 "theorem ",
384 "lemma ",
385 "axiom ",
386 "def ",
387 "instance ",
388 "class ",
389 "structure ",
390 "inductive ",
391 "namespace ",
392 "end ",
393 "section ",
394 "variable ",
395 "example ",
396 "abbrev ",
397 "opaque ",
398 "mutual ",
399 ] {
400 if t.starts_with(p) || t == "end" {
401 return true;
402 }
403 }
404 for m in ["protected ", "private ", "noncomputable ", "public "] {
406 if let Some(r) = t.strip_prefix(m) {
407 return looks_like_new_decl(r);
408 }
409 }
410 false
411}
412
413fn count_balance(s: &str, open: char, close: char) -> i32 {
414 let mut d = 0i32;
415 for c in s.chars() {
416 if c == open {
417 d += 1;
418 } else if c == close {
419 d -= 1;
420 }
421 }
422 d
423}
424
425fn split_name_binders_type(buf: &str) -> Option<(String, String, String)> {
427 let mut s = buf;
429 if let Some(idx) = find_top_level(s, ":=") {
430 s = &s[..idx];
431 }
432 if let Some(idx) = find_top_level_word(s, "where") {
433 s = &s[..idx];
434 }
435 if let Some(idx) = find_top_level_eqns(s) {
437 s = &s[..idx];
438 }
439 let s = s.trim();
440
441 let mut lx = Lexer::new(s);
443 let name_tok = lx.next_token();
444 let name = match name_tok {
445 Token::Ident(n) => n,
446 _ => return None,
447 };
448 let name_end = lx.position();
450 let rest = s[name_end..].trim_start();
452 let (binders_src, type_src) = split_binders_and_type(rest)?;
453 Some((name, binders_src, type_src))
454}
455
456fn split_binders_and_type(rest: &str) -> Option<(String, String)> {
457 let chars: Vec<char> = rest.chars().collect();
461 let mut i = 0usize;
462
463 while i < chars.len() {
465 match chars[i] {
466 c if c.is_whitespace() => i += 1,
467 '(' => {
468 let mut depth_p = 1i32;
469 i += 1;
470 while i < chars.len() && depth_p > 0 {
471 match chars[i] {
472 '(' => depth_p += 1,
473 ')' => depth_p -= 1,
474 _ => {}
475 }
476 i += 1;
477 }
478 }
479 '{' => {
480 let mut depth_b = 1i32;
481 i += 1;
482 while i < chars.len() && depth_b > 0 {
483 match chars[i] {
484 '{' => depth_b += 1,
485 '}' => depth_b -= 1,
486 _ => {}
487 }
488 i += 1;
489 }
490 }
491 '[' => {
492 let mut depth_k = 1i32;
493 i += 1;
494 while i < chars.len() && depth_k > 0 {
495 match chars[i] {
496 '[' => depth_k += 1,
497 ']' => depth_k -= 1,
498 _ => {}
499 }
500 i += 1;
501 }
502 }
503 '⦃' => {
504 let mut depth_s = 1i32;
505 i += 1;
506 while i < chars.len() && depth_s > 0 {
507 if chars[i] == '⦃' {
508 depth_s += 1;
509 } else if chars[i] == '⦄' {
510 depth_s -= 1;
511 }
512 i += 1;
513 }
514 }
515 ':' => {
516 let binders = rest[..char_byte_index(rest, i)].trim().to_string();
518 let ty = rest[char_byte_index(rest, i) + 1..].trim().to_string();
519 return Some((binders, ty));
520 }
521 c if is_name_start(c) => {
523 i += 1;
525 while i < chars.len() && is_name_continue(chars[i]) {
526 i += 1;
527 }
528 }
529 _ => {
530 break;
532 }
533 }
534 }
535
536 if let Some(idx) = find_top_level(rest, ":") {
538 if rest[idx..].starts_with(":=") {
540 return None;
541 }
542 let binders = rest[..idx].trim().to_string();
543 let ty = rest[idx + 1..].trim().to_string();
544 Some((binders, ty))
545 } else {
546 None
547 }
548}
549
550fn char_byte_index(s: &str, char_idx: usize) -> usize {
551 s.char_indices()
552 .nth(char_idx)
553 .map(|(i, _)| i)
554 .unwrap_or(s.len())
555}
556
557fn is_name_start(c: char) -> bool {
558 c.is_alphabetic() || c == '_' || c == '«'
559}
560
561fn is_name_continue(c: char) -> bool {
562 is_name_start(c) || c.is_ascii_digit() || c == '\'' || c == '?' || c == '»'
563}
564
565fn find_top_level(s: &str, pat: &str) -> Option<usize> {
566 let mut depth_p = 0i32;
567 let mut depth_b = 0i32;
568 let mut depth_k = 0i32;
569 let bytes = s.as_bytes();
570 let mut i = 0usize;
571 while i < bytes.len() {
572 let c = s[i..].chars().next()?;
573 match c {
574 '(' => depth_p += 1,
575 ')' => depth_p -= 1,
576 '{' => depth_b += 1,
577 '}' => depth_b -= 1,
578 '[' => depth_k += 1,
579 ']' => depth_k -= 1,
580 _ => {}
581 }
582 if depth_p == 0
583 && depth_b == 0
584 && depth_k == 0
585 && s[i..].starts_with(pat)
586 {
587 if pat == ":" && s[i..].starts_with(":=") {
589 i += c.len_utf8();
590 continue;
591 }
592 return Some(i);
593 }
594 i += c.len_utf8();
595 }
596 None
597}
598
599fn find_top_level_eqns(s: &str) -> Option<usize> {
601 let mut depth_p = 0i32;
602 let mut depth_b = 0i32;
603 let mut depth_k = 0i32;
604 let mut i = 0usize;
605 let mut seen_type_colon = false;
607 while i < s.len() {
608 let c = s[i..].chars().next()?;
609 match c {
610 '(' => depth_p += 1,
611 ')' => depth_p -= 1,
612 '{' => depth_b += 1,
613 '}' => depth_b -= 1,
614 '[' => depth_k += 1,
615 ']' => depth_k -= 1,
616 ':' if depth_p == 0 && depth_b == 0 && depth_k == 0 && !s[i..].starts_with(":=") => {
617 seen_type_colon = true;
618 }
619 '|' if seen_type_colon && depth_p == 0 && depth_b == 0 && depth_k == 0 => {
620 let rest = &s[i..];
622 if rest.starts_with("||") || rest.starts_with("|>") {
623 i += c.len_utf8();
624 continue;
625 }
626 if rest.contains("=>") || rest.contains('↦') {
628 let before_ok = i == 0
630 || s[..i]
631 .chars()
632 .next_back()
633 .map(|ch| ch.is_whitespace())
634 .unwrap_or(true);
635 if before_ok {
636 return Some(i);
637 }
638 }
639 }
640 _ => {}
641 }
642 i += c.len_utf8();
643 }
644 None
645}
646
647fn find_top_level_word(s: &str, word: &str) -> Option<usize> {
648 let mut depth_p = 0i32;
649 let mut depth_b = 0i32;
650 let mut depth_k = 0i32;
651 let mut i = 0usize;
652 while i < s.len() {
653 let c = s[i..].chars().next()?;
654 match c {
655 '(' => depth_p += 1,
656 ')' => depth_p -= 1,
657 '{' => depth_b += 1,
658 '}' => depth_b -= 1,
659 '[' => depth_k += 1,
660 ']' => depth_k -= 1,
661 _ => {}
662 }
663 if depth_p == 0 && depth_b == 0 && depth_k == 0 && s[i..].starts_with(word) {
664 let before_ok = i == 0
665 || s[..i]
666 .chars()
667 .next_back()
668 .map(|ch| !ch.is_alphanumeric() && ch != '_')
669 .unwrap_or(true);
670 let after = i + word.len();
671 let after_ok = after >= s.len()
672 || s[after..]
673 .chars()
674 .next()
675 .map(|ch| !ch.is_alphanumeric() && ch != '_')
676 .unwrap_or(true);
677 if before_ok && after_ok {
678 return Some(i);
679 }
680 }
681 i += c.len_utf8();
682 }
683 None
684}
685
686fn parse_binders_src(src: &str) -> Vec<Binder> {
687 let src = src.trim();
688 if src.is_empty() {
689 return Vec::new();
690 }
691 let mut binders = Vec::new();
693 let mut rest = src;
694 while !rest.is_empty() {
695 let rest_trim = rest.trim_start();
696 if rest_trim.is_empty() {
697 break;
698 }
699 let (b, consumed) = match rest_trim.chars().next() {
700 Some('(') => parse_one_group(rest_trim, BinderKind::Default, '(', ')'),
701 Some('{') => parse_one_group(rest_trim, BinderKind::Implicit, '{', '}'),
702 Some('[') => parse_one_group(rest_trim, BinderKind::Instance, '[', ']'),
703 Some('⦃') => parse_one_group_strict(rest_trim),
704 _ => {
705 let mut lx = Lexer::new(rest_trim);
707 match lx.next_token() {
708 Token::Ident(n) => {
709 let c = lx.position();
710 binders.push(Binder {
711 kind: BinderKind::Default,
712 names: vec![n],
713 ty: None,
714 });
715 rest = &rest_trim[c..];
716 continue;
717 }
718 _ => break,
719 }
720 }
721 };
722 if let Some(b) = b {
723 binders.push(b);
724 }
725 if consumed == 0 {
726 break;
727 }
728 rest = &rest_trim[consumed..];
729 }
730 binders
731}
732
733fn parse_one_group(
734 s: &str,
735 kind: BinderKind,
736 open: char,
737 close: char,
738) -> (Option<Binder>, usize) {
739 if !s.starts_with(open) {
740 return (None, 0);
741 }
742 let mut depth = 0i32;
743 let mut end = 0usize;
744 for (idx, c) in s.char_indices() {
745 if c == open {
746 depth += 1;
747 } else if c == close {
748 depth -= 1;
749 if depth == 0 {
750 end = idx + c.len_utf8();
751 break;
752 }
753 }
754 }
755 if end == 0 {
756 return (None, 0);
757 }
758 let inner = &s[open.len_utf8()..end - close.len_utf8()];
759 let binder = parse_binder_inner(kind, inner);
760 (Some(binder), end)
761}
762
763fn parse_one_group_strict(s: &str) -> (Option<Binder>, usize) {
764 if !s.starts_with('⦃') {
765 return (None, 0);
766 }
767 let mut depth = 0i32;
768 let mut end = 0usize;
769 for (idx, c) in s.char_indices() {
770 if c == '⦃' {
771 depth += 1;
772 } else if c == '⦄' {
773 depth -= 1;
774 if depth == 0 {
775 end = idx + c.len_utf8();
776 break;
777 }
778 }
779 }
780 if end == 0 {
781 return (None, 0);
782 }
783 let inner = &s['⦃'.len_utf8()..end - '⦄'.len_utf8()];
784 (Some(parse_binder_inner(BinderKind::StrictImplicit, inner)), end)
785}
786
787fn parse_binder_inner(kind: BinderKind, inner: &str) -> Binder {
788 let inner = inner.trim();
789 if let Some(colon) = find_top_level(inner, ":") {
790 let names_part = inner[..colon].trim();
791 let ty_part = inner[colon + 1..].trim();
792 let names = if names_part.is_empty() {
793 vec![]
794 } else {
795 names_part
796 .split_whitespace()
797 .map(|s| s.to_string())
798 .collect()
799 };
800 let ty = parse_type(ty_part).ok().map(Box::new);
801 Binder { kind, names, ty }
802 } else {
803 if inner.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '\'' || c == ' ' || c == '.') {
805 let names: Vec<_> = inner.split_whitespace().map(|s| s.to_string()).collect();
806 if names.len() > 1 && names[0].chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
808 {
809 Binder {
810 kind,
811 names: vec![],
812 ty: parse_type(inner).ok().map(Box::new),
813 }
814 } else {
815 Binder {
816 kind,
817 names,
818 ty: None,
819 }
820 }
821 } else {
822 Binder {
823 kind,
824 names: vec![],
825 ty: parse_type(inner).ok().map(Box::new),
826 }
827 }
828 }
829}
830
831fn collapse_ws(s: &str) -> String {
832 s.split_whitespace().collect::<Vec<_>>().join(" ")
833}
834
835#[allow(dead_code)]
836fn _parse_error_unused(_: ParseError) {}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841
842 #[test]
843 fn parse_simple_theorems() {
844 let src = r#"
845namespace Nat
846
847theorem add_comm (n m : Nat) : n + m = m + n := sorry
848
849@[simp] theorem add_zero (n : Nat) : n + 0 = n := rfl
850
851lemma foo : True := trivial
852
853axiom choice {α : Sort u} : Nonempty α → α
854
855end Nat
856"#;
857 let decls = parse_declarations(src, "Test.lean");
858 assert!(decls.len() >= 4, "got {:?}", decls.iter().map(|d| &d.name).collect::<Vec<_>>());
859 let add_comm = decls.iter().find(|d| d.name == "add_comm").unwrap();
860 assert_eq!(add_comm.kind, DeclKind::Theorem);
861 assert!(add_comm.full_name.contains("add_comm"));
862 assert!(matches!(&add_comm.ty, TypeExpr::BinOp { op, .. } if op == "="));
863 let ax = decls.iter().find(|d| d.name == "choice").unwrap();
864 assert_eq!(ax.kind, DeclKind::Axiom);
865 }
866
867 #[test]
868 fn parse_multiline_type() {
869 let src = r#"
870theorem multi (n : Nat)
871 (m : Nat) :
872 n + m = m + n := by
873 sorry
874"#;
875 let decls = parse_declarations(src, "M.lean");
876 assert_eq!(decls.len(), 1);
877 assert!(decls[0].type_surface.contains('='));
878 }
879}