1use crate::ast::{Binder, BinderKind, TypeExpr};
4use crate::lexer::{Lexer, Token};
5use thiserror::Error;
6
7#[derive(Debug, Error, Clone)]
8pub enum ParseError {
9 #[error("unexpected token {found} (expected {expected})")]
10 Unexpected {
11 expected: String,
12 found: String,
13 },
14 #[error("unexpected end of input while parsing {context}")]
15 Eof { context: String },
16 #[error("parse error: {0}")]
17 Message(String),
18}
19
20pub type Result<T> = std::result::Result<T, ParseError>;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct SearchPattern {
25 pub conclusion_only: bool,
27 pub expr: TypeExpr,
28}
29
30pub fn parse_type(input: &str) -> Result<TypeExpr> {
31 let mut p = Parser::new(input);
32 let t = p.parse_type()?;
33 Ok(t)
35}
36
37pub fn parse_search_pattern(input: &str) -> Result<SearchPattern> {
38 let trimmed = input.trim();
39 let (conclusion_only, rest) = if let Some(r) = trimmed.strip_prefix("|-") {
40 (true, r.trim())
41 } else if let Some(r) = trimmed.strip_prefix("⊢") {
42 (true, r.trim())
43 } else {
44 (false, trimmed)
45 };
46 let expr = parse_type(rest)?;
47 Ok(SearchPattern {
48 conclusion_only,
49 expr,
50 })
51}
52
53struct Parser {
54 tokens: Vec<Token>,
55 pos: usize,
56}
57
58impl Parser {
59 fn new(input: &str) -> Self {
60 let tokens = Lexer::tokenize(input);
61 Self { tokens, pos: 0 }
62 }
63
64 fn peek(&self) -> &Token {
65 self.tokens.get(self.pos).unwrap_or(&Token::Eof)
66 }
67
68 fn bump(&mut self) -> Token {
69 let t = self.peek().clone();
70 if !matches!(t, Token::Eof) {
71 self.pos += 1;
72 }
73 t
74 }
75
76 fn expect(&mut self, expected: &Token) -> Result<()> {
77 let found = self.bump();
78 if &found == expected {
79 Ok(())
80 } else {
81 Err(ParseError::Unexpected {
82 expected: expected.to_string(),
83 found: found.to_string(),
84 })
85 }
86 }
87
88 fn parse_type(&mut self) -> Result<TypeExpr> {
89 self.parse_arrow()
90 }
91
92 fn parse_arrow(&mut self) -> Result<TypeExpr> {
94 if matches!(
96 self.peek(),
97 Token::LParen | Token::LBrace | Token::LBracket | Token::LStrict
98 ) {
99 if let Some(binder) = self.try_parse_leading_pi_binder()? {
101 if matches!(self.peek(), Token::Arrow) {
102 self.bump();
103 let body = self.parse_arrow()?;
104 return Ok(TypeExpr::Pi {
105 binder,
106 body: Box::new(body),
107 });
108 }
109 return Ok(TypeExpr::Raw(format_binder_raw(&binder_fallback_name(
116 &binder,
117 ))));
118 }
119 }
120
121 let left = self.parse_bin_expr(0)?;
122 if matches!(self.peek(), Token::Arrow) {
123 self.bump();
124 let right = self.parse_arrow()?;
125 Ok(TypeExpr::Arrow(Box::new(left), Box::new(right)))
126 } else {
127 Ok(left)
128 }
129 }
130
131 fn parse_bin_expr(&mut self, min_prec: u8) -> Result<TypeExpr> {
133 let mut left = self.parse_app()?;
134
135 loop {
136 let op = match self.peek() {
137 Token::Op(s) if is_infix_op(s) => s.clone(),
138 _ => break,
140 };
141 let prec = op_prec(&op);
142 if prec < min_prec {
143 break;
144 }
145 self.bump();
146 let next_min = if is_right_assoc(&op) { prec } else { prec + 1 };
148 let right = self.parse_bin_expr(next_min)?;
149 left = TypeExpr::BinOp {
150 op,
151 left: Box::new(left),
152 right: Box::new(right),
153 };
154 }
155 Ok(left)
156 }
157
158 fn parse_app(&mut self) -> Result<TypeExpr> {
159 if let Token::Op(s) = self.peek() {
161 if s == "¬" || s == "-" {
162 let op = s.clone();
163 self.bump();
164 let arg = self.parse_app()?;
165 return Ok(TypeExpr::UnaryOp {
166 op,
167 arg: Box::new(arg),
168 });
169 }
170 }
171
172 let mut expr = self.parse_atomic()?;
173
174 loop {
176 if let Token::Op(s) = self.peek() {
177 if s == "⁻¹" || s == "'" {
178 let op = s.clone();
179 self.bump();
180 expr = TypeExpr::Postfix {
181 arg: Box::new(expr),
182 op,
183 };
184 continue;
185 }
186 }
187 if matches!(self.peek(), Token::Dot) {
188 self.bump();
189 match self.bump() {
190 Token::Ident(f) => {
191 expr = TypeExpr::Proj {
192 base: Box::new(expr),
193 field: f,
194 };
195 }
196 Token::Nat(n) => {
197 expr = TypeExpr::Proj {
198 base: Box::new(expr),
199 field: n,
200 };
201 }
202 Token::Op(s) if s == ".." => {
203 expr = TypeExpr::Postfix {
205 arg: Box::new(expr),
206 op: "..".into(),
207 };
208 }
209 other => {
210 return Err(ParseError::Unexpected {
211 expected: "field name".into(),
212 found: other.to_string(),
213 });
214 }
215 }
216 continue;
217 }
218 break;
219 }
220
221 while is_atomic_start(self.peek()) {
223 if let Token::Op(s) = self.peek() {
225 if is_infix_op(s) {
226 break;
227 }
228 }
229 if matches!(self.peek(), Token::Arrow | Token::Comma | Token::Assign) {
230 break;
231 }
232 let arg = self.parse_atomic()?;
233 let mut arg = arg;
235 loop {
236 if let Token::Op(s) = self.peek() {
237 if s == "⁻¹" || s == "'" {
238 let op = s.clone();
239 self.bump();
240 arg = TypeExpr::Postfix {
241 arg: Box::new(arg),
242 op,
243 };
244 continue;
245 }
246 }
247 if matches!(self.peek(), Token::Dot) {
248 self.bump();
249 match self.bump() {
250 Token::Ident(f) => {
251 arg = TypeExpr::Proj {
252 base: Box::new(arg),
253 field: f,
254 };
255 }
256 Token::Nat(n) => {
257 arg = TypeExpr::Proj {
258 base: Box::new(arg),
259 field: n,
260 };
261 }
262 _ => break,
263 }
264 continue;
265 }
266 break;
267 }
268 expr = TypeExpr::App(Box::new(expr), Box::new(arg));
269 }
270
271 Ok(expr)
272 }
273
274 fn parse_atomic(&mut self) -> Result<TypeExpr> {
275 match self.peek().clone() {
276 Token::Underscore => {
277 self.bump();
278 Ok(TypeExpr::Hole)
279 }
280 Token::NamedHole(n) => {
281 self.bump();
282 Ok(TypeExpr::NamedHole(n))
283 }
284 Token::Nat(n) => {
285 self.bump();
286 Ok(TypeExpr::NatLit(n))
287 }
288 Token::Literal(s) => {
289 self.bump();
290 Ok(TypeExpr::Literal(s))
291 }
292 Token::Ident(s) => {
293 self.bump();
294 if s == "Prop" {
296 return Ok(TypeExpr::Sort {
297 name: "Prop".into(),
298 level: None,
299 });
300 }
301 if s == "Type" || s == "Sort" {
302 let level = if is_atomic_start(self.peek())
303 && !matches!(self.peek(), Token::Op(_))
304 {
305 if matches!(self.peek(), Token::Op(op) if op == "*") {
307 self.bump();
308 Some(Box::new(TypeExpr::Ident("*".into())))
309 } else if matches!(
310 self.peek(),
311 Token::Ident(_)
312 | Token::Underscore
313 | Token::NamedHole(_)
314 | Token::Nat(_)
315 | Token::LParen
316 ) {
317 match self.peek() {
319 Token::Ident(_)
320 | Token::Underscore
321 | Token::NamedHole(_)
322 | Token::Nat(_) => Some(Box::new(self.parse_level_atom()?)),
323 Token::LParen => Some(Box::new(self.parse_atomic()?)),
324 _ => None,
325 }
326 } else {
327 None
328 }
329 } else {
330 None
331 };
332 return Ok(TypeExpr::Sort { name: s, level });
333 }
334 Ok(TypeExpr::Ident(s))
335 }
336 Token::Forall => {
337 self.bump();
338 self.parse_quantifier(true)
339 }
340 Token::Exists => {
341 self.bump();
342 self.parse_quantifier(false)
343 }
344 Token::Fun => {
345 self.bump();
346 let binders = self.parse_binder_list_until_mapsto()?;
347 if matches!(self.peek(), Token::MapsTo) {
348 self.bump();
349 } else if matches!(self.peek(), Token::Arrow) {
350 self.bump();
352 } else {
353 }
355 let body = self.parse_type()?;
356 Ok(TypeExpr::Lambda {
357 binders,
358 body: Box::new(body),
359 })
360 }
361 Token::LParen => {
362 self.bump();
363 if matches!(self.peek(), Token::RParen) {
365 self.bump();
366 return Ok(TypeExpr::Raw("()".into()));
367 }
368 let inner = self.parse_type()?;
370 let inner = if matches!(self.peek(), Token::Colon) {
372 self.bump();
373 let ty = self.parse_type()?;
374 TypeExpr::App(
375 Box::new(TypeExpr::App(
376 Box::new(TypeExpr::Ident("ascribe".into())),
377 Box::new(inner),
378 )),
379 Box::new(ty),
380 )
381 } else {
382 inner
383 };
384 if matches!(self.peek(), Token::Comma) {
385 let mut parts = vec![inner];
386 while matches!(self.peek(), Token::Comma) {
387 self.bump();
388 parts.push(self.parse_type()?);
389 }
390 self.expect(&Token::RParen)?;
391 let s = parts
392 .iter()
393 .map(|p| p.surface())
394 .collect::<Vec<_>>()
395 .join(", ");
396 return Ok(TypeExpr::Raw(format!("({s})")));
397 }
398 self.expect(&Token::RParen)?;
399 Ok(inner)
400 }
401 Token::LBrace => {
402 let binders = self.parse_brace_binder_group(BinderKind::Implicit)?;
404 Ok(TypeExpr::Raw(format_binders_surface(&binders)))
405 }
406 Token::LBracket => {
407 self.bump();
409 if matches!(self.peek(), Token::RBracket) {
410 self.bump();
411 return Ok(TypeExpr::Ident("[]".into()));
412 }
413 let inner = self.parse_type()?;
414 let inner = if matches!(self.peek(), Token::Colon) {
416 self.bump();
417 let ty = self.parse_type()?;
418 TypeExpr::App(
419 Box::new(TypeExpr::App(
420 Box::new(TypeExpr::Ident("ascribe".into())),
421 Box::new(inner),
422 )),
423 Box::new(ty),
424 )
425 } else {
426 inner
427 };
428 self.expect(&Token::RBracket)?;
429 Ok(TypeExpr::App(
430 Box::new(TypeExpr::Ident("inst".into())),
431 Box::new(inner),
432 ))
433 }
434 Token::Pipe => {
435 self.bump();
437 let inner = self.parse_bin_expr(0)?;
438 if matches!(self.peek(), Token::Pipe) {
439 self.bump();
440 Ok(TypeExpr::App(
441 Box::new(TypeExpr::Ident("abs".into())),
442 Box::new(inner),
443 ))
444 } else {
445 Ok(TypeExpr::UnaryOp {
446 op: "|".into(),
447 arg: Box::new(inner),
448 })
449 }
450 }
451 Token::LStrict => {
452 let binders = self.parse_brace_binder_group(BinderKind::StrictImplicit)?;
453 Ok(TypeExpr::Raw(format_binders_surface(&binders)))
454 }
455 Token::Op(s) if s == "∑" || s == "∏" || s == "∫" => {
456 let op = s.clone();
458 self.bump();
459 let arg = if is_atomic_start(self.peek()) {
461 self.parse_app()?
462 } else {
463 TypeExpr::Hole
464 };
465 Ok(TypeExpr::UnaryOp {
466 op,
467 arg: Box::new(arg),
468 })
469 }
470 other => Err(ParseError::Unexpected {
471 expected: "type expression".into(),
472 found: other.to_string(),
473 }),
474 }
475 }
476
477 fn parse_level_atom(&mut self) -> Result<TypeExpr> {
478 match self.bump() {
479 Token::Ident(s) => Ok(TypeExpr::Ident(s)),
480 Token::Nat(n) => Ok(TypeExpr::NatLit(n)),
481 Token::Underscore => Ok(TypeExpr::Hole),
482 Token::NamedHole(n) => Ok(TypeExpr::NamedHole(n)),
483 other => Err(ParseError::Unexpected {
484 expected: "universe level".into(),
485 found: other.to_string(),
486 }),
487 }
488 }
489
490 fn parse_quantifier(&mut self, is_forall: bool) -> Result<TypeExpr> {
491 let binders = self.parse_quantifier_binders()?;
492 if matches!(self.peek(), Token::Comma) {
494 self.bump();
495 }
496 let body = self.parse_type()?;
497 if is_forall {
498 Ok(TypeExpr::Forall {
499 binders,
500 body: Box::new(body),
501 })
502 } else {
503 Ok(TypeExpr::Exists {
504 binders,
505 body: Box::new(body),
506 })
507 }
508 }
509
510 fn parse_quantifier_binders(&mut self) -> Result<Vec<Binder>> {
511 let mut binders = Vec::new();
512 loop {
514 match self.peek() {
515 Token::LParen | Token::LBrace | Token::LBracket | Token::LStrict => {
516 binders.push(self.parse_one_binder_group()?);
517 }
518 Token::Ident(_) | Token::Underscore => {
519 let mut names = Vec::new();
521 while matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
522 match self.bump() {
523 Token::Ident(n) => names.push(n),
524 Token::Underscore => names.push("_".into()),
525 _ => unreachable!(),
526 }
527 if matches!(
529 self.peek(),
530 Token::Colon
531 | Token::Comma
532 | Token::LParen
533 | Token::LBrace
534 | Token::LBracket
535 | Token::LStrict
536 ) {
537 break;
538 }
539 if matches!(self.peek(), Token::Op(_) | Token::Arrow) {
541 break;
542 }
543 }
544 let ty = if matches!(self.peek(), Token::Colon) {
545 self.bump();
546 Some(Box::new(self.parse_bin_expr(0)?))
547 } else {
548 None
549 };
550 binders.push(Binder {
551 kind: BinderKind::Default,
552 names,
553 ty,
554 });
555 if matches!(self.peek(), Token::Comma) {
557 break;
558 }
559 if !matches!(
560 self.peek(),
561 Token::LParen
562 | Token::LBrace
563 | Token::LBracket
564 | Token::LStrict
565 | Token::Ident(_)
566 | Token::Underscore
567 ) {
568 break;
569 }
570 }
571 Token::Comma => break,
572 _ => break,
573 }
574 if matches!(self.peek(), Token::Comma) {
575 break;
576 }
577 }
578 if binders.is_empty() {
579 return Err(ParseError::Message(
580 "expected binders after quantifier".into(),
581 ));
582 }
583 Ok(binders)
584 }
585
586 fn parse_binder_list_until_mapsto(&mut self) -> Result<Vec<Binder>> {
587 let mut binders = Vec::new();
588 while !matches!(
589 self.peek(),
590 Token::MapsTo | Token::Arrow | Token::Eof | Token::Comma
591 ) {
592 if matches!(
593 self.peek(),
594 Token::LParen | Token::LBrace | Token::LBracket | Token::LStrict
595 ) {
596 binders.push(self.parse_one_binder_group()?);
597 } else if matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
598 let mut names = Vec::new();
599 while matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
600 match self.bump() {
601 Token::Ident(n) => names.push(n),
602 Token::Underscore => names.push("_".into()),
603 _ => unreachable!(),
604 }
605 if matches!(self.peek(), Token::Colon) {
606 break;
607 }
608 if matches!(self.peek(), Token::MapsTo | Token::Arrow) {
609 break;
610 }
611 if matches!(
612 self.peek(),
613 Token::LParen | Token::LBrace | Token::LBracket | Token::LStrict
614 ) {
615 break;
616 }
617 }
618 let ty = if matches!(self.peek(), Token::Colon) {
619 self.bump();
620 Some(Box::new(self.parse_bin_expr(0)?))
621 } else {
622 None
623 };
624 binders.push(Binder {
625 kind: BinderKind::Default,
626 names,
627 ty,
628 });
629 } else {
630 break;
631 }
632 }
633 Ok(binders)
634 }
635
636 fn parse_one_binder_group(&mut self) -> Result<Binder> {
637 match self.peek() {
638 Token::LParen => self.parse_paren_binder(BinderKind::Default),
639 Token::LBrace => self.parse_brace_binder_group(BinderKind::Implicit).map(|mut v| {
640 v.pop().unwrap_or(Binder {
641 kind: BinderKind::Implicit,
642 names: vec!["_".into()],
643 ty: None,
644 })
645 }),
646 Token::LBracket => self.parse_brace_binder_group(BinderKind::Instance).map(|mut v| {
647 v.pop().unwrap_or(Binder {
648 kind: BinderKind::Instance,
649 names: vec!["_".into()],
650 ty: None,
651 })
652 }),
653 Token::LStrict => self
654 .parse_brace_binder_group(BinderKind::StrictImplicit)
655 .map(|mut v| {
656 v.pop().unwrap_or(Binder {
657 kind: BinderKind::StrictImplicit,
658 names: vec!["_".into()],
659 ty: None,
660 })
661 }),
662 _ => Err(ParseError::Message("expected binder group".into())),
663 }
664 }
665
666 fn parse_paren_binder(&mut self, kind: BinderKind) -> Result<Binder> {
667 self.expect(&Token::LParen)?;
668 let mut names = Vec::new();
670 while matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
671 match self.bump() {
672 Token::Ident(n) => names.push(n),
673 Token::Underscore => names.push("_".into()),
674 _ => unreachable!(),
675 }
676 if matches!(self.peek(), Token::Colon | Token::RParen) {
677 break;
678 }
679 }
680 let ty = if matches!(self.peek(), Token::Colon) {
681 self.bump();
682 Some(Box::new(self.parse_type()?))
683 } else {
684 None
685 };
686 self.expect(&Token::RParen)?;
687 if names.is_empty() {
688 if let Some(t) = ty {
690 return Ok(Binder {
691 kind,
692 names: vec!["_".into()],
693 ty: Some(t),
694 });
695 }
696 }
697 Ok(Binder { kind, names, ty })
698 }
699
700 fn parse_brace_binder_group(&mut self, kind: BinderKind) -> Result<Vec<Binder>> {
701 let (open, close) = match kind {
702 BinderKind::Implicit => (Token::LBrace, Token::RBrace),
703 BinderKind::Instance => (Token::LBracket, Token::RBracket),
704 BinderKind::StrictImplicit => (Token::LStrict, Token::RStrict),
705 BinderKind::Default => (Token::LParen, Token::RParen),
706 };
707 self.expect(&open)?;
708 let mut binders = Vec::new();
709 let mut names = Vec::new();
712 let start_pos = self.pos;
713 while matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
714 match self.bump() {
715 Token::Ident(n) => names.push(n),
716 Token::Underscore => names.push("_".into()),
717 _ => unreachable!(),
718 }
719 if matches!(self.peek(), Token::Colon) {
720 break;
721 }
722 if matches!(
724 self.peek(),
725 Token::RBrace | Token::RBracket | Token::RStrict | Token::RParen
726 ) {
727 break;
728 }
729 }
731 if matches!(self.peek(), Token::Colon) {
732 self.bump();
733 let ty = self.parse_type()?;
734 binders.push(Binder {
735 kind,
736 names,
737 ty: Some(Box::new(ty)),
738 });
739 } else {
740 self.pos = start_pos;
743 let ty = self.parse_type()?;
744 binders.push(Binder {
745 kind,
746 names: vec![],
747 ty: Some(Box::new(ty)),
748 });
749 }
750 self.expect(&close)?;
752 Ok(binders)
753 }
754
755 fn try_parse_leading_pi_binder(&mut self) -> Result<Option<Binder>> {
757 let save = self.pos;
758 let kind = match self.peek() {
759 Token::LParen => BinderKind::Default,
760 Token::LBrace => BinderKind::Implicit,
761 Token::LBracket => BinderKind::Instance,
762 Token::LStrict => BinderKind::StrictImplicit,
763 _ => return Ok(None),
764 };
765 let open_tok = self.bump();
767 let mut names = Vec::new();
768 while matches!(self.peek(), Token::Ident(_) | Token::Underscore) {
769 match self.bump() {
770 Token::Ident(n) => names.push(n),
771 Token::Underscore => names.push("_".into()),
772 _ => unreachable!(),
773 }
774 if matches!(self.peek(), Token::Colon) {
775 break;
776 }
777 if !matches!(self.peek(), Token::Ident(_) | Token::Underscore | Token::Colon)
779 {
780 break;
781 }
782 }
783 if names.is_empty() || !matches!(self.peek(), Token::Colon) {
784 self.pos = save;
785 return Ok(None);
786 }
787 self.bump(); let ty = match self.parse_type() {
789 Ok(t) => t,
790 Err(_) => {
791 self.pos = save;
792 return Ok(None);
793 }
794 };
795 let close_ok = match kind {
796 BinderKind::Default => matches!(self.peek(), Token::RParen),
797 BinderKind::Implicit => matches!(self.peek(), Token::RBrace),
798 BinderKind::Instance => matches!(self.peek(), Token::RBracket),
799 BinderKind::StrictImplicit => matches!(self.peek(), Token::RStrict),
800 };
801 if !close_ok {
802 self.pos = save;
803 return Ok(None);
804 }
805 self.bump();
806 if matches!(self.peek(), Token::Arrow) {
808 Ok(Some(Binder {
809 kind,
810 names,
811 ty: Some(Box::new(ty)),
812 }))
813 } else {
814 let _ = open_tok;
816 self.pos = save;
817 Ok(None)
818 }
819 }
820}
821
822fn is_atomic_start(tok: &Token) -> bool {
823 match tok {
824 Token::Ident(_)
825 | Token::Nat(_)
826 | Token::Literal(_)
827 | Token::Underscore
828 | Token::NamedHole(_)
829 | Token::LParen
830 | Token::LBrace
831 | Token::LBracket
832 | Token::LStrict
833 | Token::Forall
834 | Token::Exists
835 | Token::Fun
836 | Token::Pipe => true,
837 Token::Op(s) => s == "∑" || s == "∏" || s == "∫" || s == "¬" || s == "-",
838 _ => false,
839 }
840}
841
842fn is_infix_op(s: &str) -> bool {
843 matches!(
844 s,
845 "=" | "≠"
846 | "<"
847 | ">"
848 | "≤"
849 | "≥"
850 | "+"
851 | "-"
852 | "*"
853 | "/"
854 | "%"
855 | "^"
856 | "∧"
857 | "∨"
858 | "↔"
859 | "∘"
860 | "∈"
861 | "∉"
862 | "⊆"
863 | "⊂"
864 | "∪"
865 | "∩"
866 | "++"
867 | "::"
868 | "|>"
869 | "<|"
870 | "|>."
871 | "$"
872 | "≈"
873 | "≃"
874 | "≅"
875 | "≡"
876 | "⋅"
877 | "•"
878 | "⋆"
879 | "▸"
880 | "∥"
881 | "∣"
882 | "→"
883 ) || s == "→"
884}
885
886fn op_prec(op: &str) -> u8 {
887 match op {
888 "$" | "<|" | "|>" | "|>." => 1,
889 "↔" => 2,
890 "∨" => 3,
891 "∧" => 4,
892 "=" | "≠" | "<" | ">" | "≤" | "≥" | "∈" | "∉" | "⊆" | "⊂" | "≈" | "≃" | "≅" | "≡"
893 | "∥" | "∣" => 5,
894 "∪" | "++" => 6,
895 "∩" => 7,
896 "::" => 8,
897 "+" | "-" => 9,
898 "*" | "/" | "%" | "⋅" | "•" | "⋆" => 10,
899 "∘" => 11,
900 "^" => 12,
901 "▸" => 13,
902 _ => 5,
903 }
904}
905
906fn is_right_assoc(op: &str) -> bool {
907 matches!(op, "^" | "↔" | "::" | "$" | "∘")
908}
909
910fn format_binders_surface(binders: &[Binder]) -> String {
911 binders
912 .iter()
913 .map(|b| {
914 let names = b.names.join(" ");
915 let core = match &b.ty {
916 Some(t) if names.is_empty() => t.surface(),
917 Some(t) => format!("{names} : {}", t.surface()),
918 None => names,
919 };
920 match b.kind {
921 BinderKind::Default => format!("({core})"),
922 BinderKind::Implicit => format!("{{{core}}}"),
923 BinderKind::Instance => format!("[{core}]"),
924 BinderKind::StrictImplicit => format!("⦃{core}⦄"),
925 }
926 })
927 .collect::<Vec<_>>()
928 .join(" ")
929}
930
931fn format_binder_raw(s: &str) -> String {
932 s.to_string()
933}
934
935fn binder_fallback_name(b: &Binder) -> String {
936 format_binders_surface(std::slice::from_ref(b))
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942
943 #[test]
944 fn parse_eq_add() {
945 let t = parse_type("n + m = m + n").unwrap();
946 assert!(matches!(&t, TypeExpr::BinOp { op, .. } if op == "="));
947 assert_eq!(t.conclusion().head_key(), "op:=");
948 }
949
950 #[test]
951 fn parse_forall() {
952 let t = parse_type("∀ (n m : Nat), n + m = m + n").unwrap();
953 match t {
954 TypeExpr::Forall { binders, body } => {
955 assert_eq!(binders[0].names, vec!["n", "m"]);
956 assert!(matches!(body.as_ref(), TypeExpr::BinOp { op, .. } if op == "="));
957 }
958 _ => panic!("expected forall"),
959 }
960 }
961
962 #[test]
963 fn parse_arrow_chain() {
964 let t = parse_type("Nat → Nat → Prop").unwrap();
965 match t {
966 TypeExpr::Arrow(a, b) => {
967 assert!(matches!(a.as_ref(), TypeExpr::Ident(s) if s == "Nat"));
968 assert!(matches!(b.as_ref(), TypeExpr::Arrow(_, _)));
969 }
970 _ => panic!("expected arrow"),
971 }
972 }
973
974 #[test]
975 fn parse_pi_binder() {
976 let t = parse_type("(n : Nat) → n + 0 = n").unwrap();
977 assert!(matches!(t, TypeExpr::Pi { .. }));
978 }
979
980 #[test]
981 fn parse_holes_and_named() {
982 let t = parse_type("?a - ?a = 0").unwrap();
983 match t {
984 TypeExpr::BinOp { op, left, right } if op == "=" => {
985 assert!(matches!(right.as_ref(), TypeExpr::NatLit(n) if n == "0"));
986 match left.as_ref() {
987 TypeExpr::BinOp { op, left, right } if op == "-" => {
988 assert!(matches!(left.as_ref(), TypeExpr::NamedHole(a) if a == "a"));
989 assert!(matches!(right.as_ref(), TypeExpr::NamedHole(a) if a == "a"));
990 }
991 _ => panic!("expected subtraction"),
992 }
993 }
994 _ => panic!("expected eq"),
995 }
996 }
997
998 #[test]
999 fn parse_search_turnstile() {
1000 let p = parse_search_pattern("|- tsum _ = _ * tsum _").unwrap();
1001 assert!(p.conclusion_only);
1002 assert!(matches!(p.expr, TypeExpr::BinOp { op, .. } if op == "="));
1003 }
1004
1005 #[test]
1006 fn parse_iff_and() {
1007 let t = parse_type("p ∧ q ↔ q ∧ p").unwrap();
1008 assert!(matches!(t, TypeExpr::BinOp { op, .. } if op == "↔"));
1009 }
1010}