1use super::diagnostics::{Result, TranslationError, type_error, unsupported};
12use super::lexer::{Source, Token, TokenCursor, TokenKind, describe, is_js_space, tokenize_source};
13use super::surface::{
14 BinaryOp, SBinder, SComparison, SCtor, SData, SEffect, SExpr, SField, SFn, SItem, SMain,
15 SModule, SNode, SParam, SPattern, SPatternNode, SProgram, SProp, SPropNode, ShowStyle,
16};
17use super::types::{BOOL, INT, NAT, STRING, Type, UNIT};
18use super::{Language, Span};
19
20mod expressions;
21mod proofs;
22
23const ITEM_KEYWORDS: [&str; 28] = [
24 "namespace",
25 "end",
26 "inductive",
27 "def",
28 "theorem",
29 "lemma",
30 "structure",
31 "class",
32 "instance",
33 "abbrev",
34 "open",
35 "section",
36 "variable",
37 "universe",
38 "import",
39 "mutual",
40 "partial",
41 "private",
42 "protected",
43 "noncomputable",
44 "set_option",
45 "example",
46 "axiom",
47 "opaque",
48 "macro",
49 "syntax",
50 "notation",
51 "attribute",
52];
53
54fn is_item_keyword(value: &str) -> bool {
55 ITEM_KEYWORDS.contains(&value)
56}
57
58fn builtin_type(name: &str) -> Option<Type> {
59 match name {
60 "Nat" => Some(NAT),
61 "Int" => Some(INT),
62 "Bool" => Some(BOOL),
63 "String" => Some(STRING),
64 "Unit" => Some(UNIT),
65 _ => None,
66 }
67}
68
69fn is_unsupported_type(name: &str) -> bool {
71 let fixed = name
72 .strip_prefix('U')
73 .unwrap_or(name)
74 .strip_prefix("Int")
75 .is_some_and(|bits| matches!(bits, "8" | "16" | "32" | "64"));
76 fixed
77 || matches!(
78 name,
79 "USize" | "Float" | "Char" | "List" | "Array" | "Option" | "Prop" | "Type" | "Sort"
80 )
81}
82
83struct Level {
84 ops: &'static [&'static str],
85 prec: u32,
86 op: Option<BinaryOp>,
87 right: bool,
88}
89
90const BINARY: [Level; 6] = [
91 Level {
92 ops: &["||"],
93 prec: 30,
94 op: Some(BinaryOp::Or),
95 right: false,
96 },
97 Level {
98 ops: &["&&"],
99 prec: 35,
100 op: Some(BinaryOp::And),
101 right: false,
102 },
103 Level {
104 ops: &["==", "!=", "=", "≠", "<", "<=", "≤", ">", ">=", "≥"],
105 prec: 50,
106 op: None,
107 right: false,
108 },
109 Level {
110 ops: &["++"],
111 prec: 65,
112 op: Some(BinaryOp::Concat),
113 right: true,
114 },
115 Level {
116 ops: &["+", "-"],
117 prec: 65,
118 op: None,
119 right: false,
120 },
121 Level {
122 ops: &["*", "/", "%"],
123 prec: 70,
124 op: None,
125 right: false,
126 },
127];
128
129fn operator_name(op: &str) -> Option<BinaryOp> {
130 Some(match op {
131 "==" | "=" => BinaryOp::Eq,
132 "!=" | "≠" => BinaryOp::Ne,
133 "<" => BinaryOp::Lt,
134 "<=" | "≤" => BinaryOp::Le,
135 ">" => BinaryOp::Gt,
136 ">=" | "≥" => BinaryOp::Ge,
137 "+" => BinaryOp::Add,
138 "-" => BinaryOp::Sub,
139 "*" => BinaryOp::Mul,
140 "/" => BinaryOp::Div,
141 "%" => BinaryOp::Rem,
142 _ => return None,
143 })
144}
145
146type Relation = fn(SComparison) -> SPropNode;
147
148fn prop_relation(op: &str) -> Option<Relation> {
149 Some(match op {
150 "=" => SPropNode::Eq,
151 "≠" => SPropNode::Ne,
152 "<" => SPropNode::Lt,
153 "≤" | "<=" => SPropNode::Le,
154 ">" => SPropNode::Gt,
155 "≥" | ">=" => SPropNode::Ge,
156 _ => return None,
157 })
158}
159
160pub fn parse_lean(source: &str) -> Result<SProgram> {
165 let source = Source::new(source);
166 let mut tokens = tokenize_source(&source, Language::Lean)?.tokens;
167 annotate_layout(&mut tokens, source.units());
168 LeanParser::new(tokens, &source).file()
169}
170
171pub fn annotate_layout(tokens: &mut [Token], source: &[u16]) {
173 let mut line = 0;
174 let mut line_start = 0;
175 let mut cursor = 0;
176 let mut previous_line = None;
177 for token in tokens {
178 while cursor < token.start {
179 if source.get(cursor) == Some(&u16::from(b'\n')) {
180 line += 1;
181 line_start = cursor + 1;
182 }
183 cursor += 1;
184 }
185 token.line = line;
186 token.col = token.start - line_start;
187 token.first = previous_line != Some(line);
188 previous_line = Some(line);
189 }
190}
191
192#[must_use]
194pub fn span(from: &Token, to: &Token) -> Span {
195 let reach = if to.kind == TokenKind::Eof {
196 from.end
197 } else {
198 to.start
199 };
200 Span::new(from.start, from.end.max(reach))
201}
202
203fn column(token: &Token) -> i64 {
205 i64::try_from(token.col).unwrap_or(i64::MAX)
206}
207
208fn syntax_at(message: String, token: &Token) -> TranslationError {
209 TranslationError::syntax(message, Some(token.span()))
210}
211
212fn js_trimmed(units: &[u16]) -> String {
214 let start = units
215 .iter()
216 .position(|&unit| !is_js_space(unit))
217 .unwrap_or(units.len());
218 let end = units
219 .iter()
220 .rposition(|&unit| !is_js_space(unit))
221 .map_or(start, |index| index + 1);
222 String::from_utf16_lossy(&units[start..end.max(start)])
223}
224
225const fn expr(node: SNode, at: Span) -> SExpr {
226 SExpr::new(node, Some(at))
227}
228
229const fn pattern(node: SPatternNode, at: Option<Span>) -> SPattern {
230 SPattern { node, span: at }
231}
232
233struct PatternHead {
235 path: Vec<String>,
236 dot: bool,
237}
238
239enum Definition {
240 Fn(Box<SFn>),
241 Main(SMain, Span),
242}
243
244struct LeanParser<'a> {
245 cursor: TokenCursor,
246 source: &'a Source,
247 bounds: Vec<i64>,
248}
249
250impl<'a> LeanParser<'a> {
251 fn new(tokens: Vec<Token>, source: &'a Source) -> Self {
252 Self {
253 cursor: TokenCursor::new(tokens, Language::Lean),
254 source,
255 bounds: vec![-1],
256 }
257 }
258
259 fn bound(&self) -> i64 {
260 self.bounds.last().copied().unwrap_or(-1)
261 }
262
263 fn with_bound<T>(
264 &mut self,
265 column: i64,
266 parse: impl FnOnce(&mut Self) -> Result<T>,
267 ) -> Result<T> {
268 self.bounds.push(column);
269 let result = parse(self);
270 self.bounds.pop();
271 result
272 }
273
274 fn blocked(&self, token: &Token) -> bool {
276 if token.kind == TokenKind::Eof {
277 return true;
278 }
279 if token.col == 0
280 && token.first
281 && token.kind == TokenKind::Identifier
282 && is_item_keyword(&token.value)
283 {
284 return true;
285 }
286 token.first && column(token) <= self.bound()
287 }
288
289 fn blocked_next(&self) -> bool {
290 self.blocked(self.cursor.peek())
291 }
292
293 fn outdented(&self, at: i64) -> bool {
295 let token = self.cursor.peek();
296 token.kind == TokenKind::Eof || (token.first && column(token) < at)
297 }
298
299 fn fail(&self, message: &str) -> TranslationError {
300 Self::fail_at(message, self.cursor.peek())
301 }
302
303 fn fail_at(message: &str, token: &Token) -> TranslationError {
304 syntax_at(format!("{message} but found {}", describe(token)), token)
305 }
306
307 fn peek(&self) -> Token {
308 self.cursor.peek().clone()
309 }
310
311 fn span_to_next(&self, from: &Token) -> Span {
312 span(from, self.cursor.peek())
313 }
314
315 fn file(mut self) -> Result<SProgram> {
316 let mut items = Vec::new();
317 let mut main = None;
318 self.items(&mut main, &mut items, &[])?;
319 Ok(SProgram {
320 language: Language::Lean,
321 items,
322 main,
323 })
324 }
325
326 fn items(
327 &mut self,
328 main: &mut Option<SMain>,
329 items: &mut Vec<SItem>,
330 path: &[String],
331 ) -> Result<()> {
332 while !self.cursor.at_end() {
333 let token = self.peek();
334 if self.cursor.is("end") {
335 let Some(open) = path.last() else {
336 return Err(self.fail("unmatched end"));
337 };
338 self.cursor.advance();
339 let name = self.qualified_name()?.join(".");
340 if name != *open {
341 return Err(syntax_at(
342 format!("end {name} closes namespace {open}"),
343 &token,
344 ));
345 }
346 return Ok(());
347 }
348 if self.cursor.is("namespace") {
349 self.cursor.advance();
350 let mut segments = self.qualified_name()?;
351 let module_span = self.span_to_next(&token);
352 if segments.len() != 1 {
353 return Err(unsupported(
354 "dotted namespace",
355 "use one namespace per level",
356 Some(span(&token, &token)),
357 ));
358 }
359 let name = segments.remove(0);
360 let mut module = SModule {
361 name: name.clone(),
362 items: Vec::new(),
363 span: Some(module_span),
364 };
365 let mut inner = path.to_vec();
366 inner.push(name);
367 self.items(main, &mut module.items, &inner)?;
368 items.push(SItem::Module(module));
369 continue;
370 }
371 if self.cursor.is("inductive") {
372 items.push(SItem::Data(self.inductive()?));
373 continue;
374 }
375 if self.cursor.is("def") {
376 match self.definition(path)? {
377 Definition::Main(program_main, at) => {
378 if !path.is_empty() {
379 return Err(unsupported(
380 "namespaced main",
381 "main must be declared at the top level",
382 Some(at),
383 ));
384 }
385 if main.is_some() {
386 return Err(type_error("duplicate main", Some(at)));
387 }
388 *main = Some(program_main);
389 }
390 Definition::Fn(item) => items.push(SItem::Fn(*item)),
391 }
392 continue;
393 }
394 if self.cursor.is("theorem") {
395 items.push(SItem::Theorem(self.theorem()?));
396 continue;
397 }
398 if token.kind == TokenKind::Identifier && is_item_keyword(&token.value) {
399 return Err(unsupported(
400 &format!("Lean {} declaration", token.value),
401 "outside the portable core",
402 Some(span(&token, &token)),
403 ));
404 }
405 return Err(self.fail("expected a declaration"));
406 }
407 if let Some(open) = path.last() {
408 return Err(syntax_at(
409 format!("namespace {open} is not closed"),
410 self.cursor.peek(),
411 ));
412 }
413 Ok(())
414 }
415
416 fn qualified_name(&mut self) -> Result<Vec<String>> {
417 let token = self.cursor.identifier(Some("name"))?;
418 Ok(token.value.split('.').map(str::to_owned).collect())
419 }
420
421 fn inductive(&mut self) -> Result<SData> {
422 let start = self.cursor.advance();
423 let name = self.cursor.identifier(Some("inductive"))?.value;
424 if name.contains('.') {
425 return Err(unsupported(
426 "qualified inductive name",
427 "declare inductives inside their namespace",
428 Some(span(&start, &start)),
429 ));
430 }
431 if !self.cursor.is("where") {
432 if self.cursor.is("(") || self.cursor.is("{") {
433 return Err(unsupported(
434 "parameterised inductive",
435 "type parameters are outside the portable core",
436 Some(self.span_to_next(&start)),
437 ));
438 }
439 self.cursor.expect("where", Some("inductive"))?;
440 }
441 self.cursor.advance();
442 let mut ctors = Vec::new();
443 while self.cursor.is("|") {
444 let bar = self.cursor.advance();
445 let ctor_name = self.cursor.identifier(Some("constructor"))?.value;
446 let mut fields = Vec::new();
447 while self.cursor.is("(") {
448 fields.extend(self.binder_group()?.into_iter().map(|(name, ty)| SField {
449 name: Some(name),
450 ty,
451 rocq_type: None,
452 span: None,
453 }));
454 }
455 if self.cursor.eat(":").is_some() {
456 let mut types = self.arrow_type()?;
457 let result = types.pop();
458 let constructs_self = matches!(
459 &result,
460 Some(Type::Named { path, .. }) if path.join(".") == name
461 );
462 if !constructs_self {
463 return Err(unsupported(
464 "indexed constructor",
465 &format!("{ctor_name} must construct {name}"),
466 Some(self.span_to_next(&bar)),
467 ));
468 }
469 fields.extend(types.into_iter().map(|ty| SField {
470 name: None,
471 ty,
472 rocq_type: None,
473 span: None,
474 }));
475 }
476 ctors.push(SCtor {
477 name: ctor_name,
478 fields,
479 });
480 }
481 if self.cursor.is("deriving") {
482 self.cursor.advance();
483 self.cursor.identifier(Some("deriving"))?;
484 while self.cursor.eat(",").is_some() {
485 self.cursor.identifier(Some("deriving"))?;
486 }
487 }
488 Ok(SData {
489 name,
490 ctors,
491 span: Some(self.span_to_next(&start)),
492 generated: false,
493 })
494 }
495
496 fn binder_group(&mut self) -> Result<Vec<(String, Type)>> {
497 let open = self.cursor.expect("(", Some("binder"))?;
498 let mut names = Vec::new();
499 while self.cursor.is_kind(TokenKind::Identifier) {
500 names.push(self.cursor.advance().value);
501 }
502 if names.is_empty() {
503 return Err(self.fail("expected binder names"));
504 }
505 self.cursor.expect(":", Some("binder"))?;
506 let ty = self.ty()?;
507 self.cursor.expect(")", Some("binder"))?;
508 if self.cursor.is("{") || self.cursor.is("[") {
509 return Err(unsupported(
510 "implicit binder",
511 "implicit and instance binders are outside the portable core",
512 Some(self.span_to_next(&open)),
513 ));
514 }
515 Ok(names.into_iter().map(|name| (name, ty.clone())).collect())
516 }
517
518 fn binders(&mut self) -> Result<Vec<(String, Type)>> {
519 let mut result = Vec::new();
520 while self.cursor.is("(") || self.cursor.is("{") || self.cursor.is("[") {
521 if !self.cursor.is("(") {
522 let token = self.cursor.peek();
523 return Err(unsupported(
524 "implicit binder",
525 "implicit and instance binders are outside the portable core",
526 Some(span(token, token)),
527 ));
528 }
529 result.extend(self.binder_group()?);
530 }
531 Ok(result)
532 }
533
534 fn arrow_type(&mut self) -> Result<Vec<Type>> {
535 let mut types = vec![self.ty()?];
536 while self.cursor.eat("→").is_some() || self.cursor.eat("->").is_some() {
537 types.push(self.ty()?);
538 }
539 Ok(types)
540 }
541
542 fn ty(&mut self) -> Result<Type> {
543 let token = self.peek();
544 if self.cursor.eat("(").is_some() {
545 let mut types = self.arrow_type()?;
546 self.cursor.expect(")", Some("type"))?;
547 if types.len() != 1 {
548 return Err(unsupported(
549 "function type",
550 "higher-order values are outside the portable core",
551 Some(span(&token, &token)),
552 ));
553 }
554 return Ok(types.remove(0));
555 }
556 let name = self.cursor.identifier(Some("type"))?.value;
557 if let Some(builtin) = builtin_type(&name) {
558 return Ok(builtin);
559 }
560 if is_unsupported_type(&name) {
561 return Err(unsupported(
562 &format!("Lean type {name}"),
563 "outside the portable core",
564 Some(span(&token, &token)),
565 ));
566 }
567 let next = self.cursor.peek();
568 if !self.blocked(next)
569 && next.kind == TokenKind::Identifier
570 && !["where", ":=", "with", "deriving"].contains(&next.value.as_str())
571 {
572 return Err(unsupported(
573 "type application",
574 &format!("{name} {} is outside the portable core", next.value),
575 Some(span(&token, next)),
576 ));
577 }
578 Ok(Type::Named {
579 path: name.split('.').map(str::to_owned).collect(),
580 span: Some(span(&token, &token)),
581 })
582 }
583
584 fn definition(&mut self, path: &[String]) -> Result<Definition> {
585 let start = self.cursor.advance();
586 let name_token = self.cursor.identifier(Some("def"))?;
587 let name = name_token.value.clone();
588 if name.contains('.') {
589 return Err(unsupported(
590 "qualified definition name",
591 "declare definitions inside their namespace",
592 Some(span(&name_token, &name_token)),
593 ));
594 }
595 let params: Vec<SParam> = self
596 .binders()?
597 .into_iter()
598 .map(|(name, ty)| param(name, ty))
599 .collect();
600 self.cursor.expect(":", Some("def"))?;
601 if name == "main" && path.is_empty() && self.cursor.is("IO") {
602 return self.main(&start);
603 }
604 let mut types = self.arrow_type()?;
605 let ret = types.pop();
606 let extra: Vec<SParam> = types
607 .into_iter()
608 .enumerate()
609 .map(|(index, ty)| param(format!("ml_arg{index}"), ty))
610 .collect();
611 if self.cursor.eat(":=").is_some() {
612 if !extra.is_empty() {
613 return Err(unsupported(
614 "function-valued definition body",
615 "use binders or equations for arrow types",
616 Some(self.span_to_next(&start)),
617 ));
618 }
619 let body = self.with_bound(0, Self::expr)?;
620 self.end_decl(&start, "termination_by")?;
621 return Ok(Definition::Fn(Box::new(SFn {
622 name,
623 params,
624 ret,
625 body,
626 span: Some(self.span_to_next(&start)),
627 generated: false,
628 })));
629 }
630 if !self.cursor.is("|") {
631 return Err(self.fail("expected := or equations"));
632 }
633 if extra.is_empty() {
634 return Err(unsupported(
635 "equations without arrow arguments",
636 "use match",
637 Some(self.span_to_next(&start)),
638 ));
639 }
640 let rows = self.alternatives(extra.len())?;
641 let body = expr(
642 SNode::Match {
643 scrutinees: extra.iter().map(|param| SExpr::name(¶m.name)).collect(),
644 rows,
645 },
646 self.span_to_next(&start),
647 );
648 self.end_decl(&start, "termination_by")?;
649 Ok(Definition::Fn(Box::new(SFn {
650 name,
651 params: params.into_iter().chain(extra).collect(),
652 ret,
653 body,
654 span: Some(self.span_to_next(&start)),
655 generated: false,
656 })))
657 }
658
659 fn end_decl(&self, start: &Token, keyword: &str) -> Result<()> {
660 if self.cursor.is(keyword) || self.cursor.is("decreasing_by") {
661 return Err(unsupported(
662 &format!("Lean {}", self.cursor.peek().value),
663 "explicit termination arguments are outside the portable core",
664 Some(self.span_to_next(start)),
665 ));
666 }
667 if !self.blocked_next() {
668 return Err(self.fail("expected the end of the declaration"));
669 }
670 Ok(())
671 }
672
673 fn main(&mut self, start: &Token) -> Result<Definition> {
674 self.cursor.expect("IO", Some("main"))?;
675 self.cursor.expect("Unit", Some("main"))?;
676 self.cursor.expect(":=", Some("main"))?;
677 self.cursor.expect("do", Some("main"))?;
678 let mut effects = Vec::new();
679 let first = self.peek();
680 let effect_column = if first.first { column(&first) } else { -1 };
681 if effect_column <= 0 {
682 return Err(unsupported(
683 "inline do block",
684 "write one effect per line",
685 Some(span(&first, &first)),
686 ));
687 }
688 while !self.cursor.at_end()
689 && column(self.cursor.peek()) == effect_column
690 && self.cursor.peek().first
691 {
692 let before = self.cursor.index;
693 effects.push(self.with_bound(effect_column, Self::effect)?);
694 if self.cursor.index == before {
695 return Err(self.fail("expected an effect"));
696 }
697 }
698 self.end_decl(start, "where")?;
699 let at = self.span_to_next(start);
700 Ok(Definition::Main(
701 SMain {
702 effects,
703 span: Some(at),
704 sequential_async: false,
705 },
706 at,
707 ))
708 }
709
710 fn effect(&mut self) -> Result<SEffect> {
711 let token = self.peek();
712 if self.cursor.is("IO.println") {
713 self.cursor.advance();
714 let expr = self.atom()?;
715 return Ok(SEffect::Print {
716 expr,
717 style: ShowStyle::Lean,
718 span: Some(self.span_to_next(&token)),
719 });
720 }
721 if self.cursor.is("let") {
722 self.cursor.advance();
723 let name = self.cursor.identifier(Some("let"))?.value;
724 let ty = if self.cursor.eat(":").is_some() {
725 Some(self.ty()?)
726 } else {
727 None
728 };
729 self.cursor.expect(":=", Some("let"))?;
730 let value = self.expr()?;
731 return Ok(SEffect::Let {
732 name,
733 ty,
734 value,
735 constant: false,
736 span: Some(self.span_to_next(&token)),
737 });
738 }
739 Err(unsupported(
740 "Lean do element",
741 &format!("{} is outside the portable effect model", describe(&token)),
742 Some(span(&token, &token)),
743 ))
744 }
745}
746
747fn plain_application(head: SExpr, args: Vec<SExpr>, range: Span) -> Result<SExpr> {
749 if args.is_empty() {
750 return Ok(head);
751 }
752 if !matches!(head.node, SNode::Name { .. } | SNode::DotCtor { .. }) {
753 return Err(unsupported(
754 "higher-order application",
755 "only named functions can be applied",
756 Some(range),
757 ));
758 }
759 Ok(expr(
760 SNode::App {
761 func: Box::new(head),
762 args,
763 },
764 range,
765 ))
766}
767
768const fn connective(node: SPropNode) -> SProp {
769 SProp { node, span: None }
770}
771
772fn bind_or_ctor(head: PatternHead, token: &Token) -> SPattern {
773 let name = head.path.into_iter().next().unwrap_or_default();
774 pattern(SPatternNode::BindOrCtor { name }, Some(span(token, token)))
775}
776
777const fn param(name: String, ty: Type) -> SParam {
778 SParam {
779 name,
780 default_value: None,
781 ty: Some(ty),
782 span: None,
783 guard: None,
784 rocq_type: None,
785 }
786}
787
788const fn binder(name: String, ty: Type) -> SBinder {
789 SBinder {
790 name,
791 ty: Some(ty),
792 rocq_type: None,
793 span: None,
794 }
795}