File size: 25,763 Bytes
a10b189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveDataTypeable #-}

{-|
Module      : Assertica.Core.AST
Description : Core internal abstract syntax tree for the proof language
Copyright   : (c) 2026 Ahmad Ali Parr
License     : BSL-1.0

The AST is the trusted representation layer upon which all other agents depend.
It maintains strong separation between Terms, Types, Proofs, Propositions, and Assertions.

DESIGN PRINCIPLES:
1. Each node type (Term, Type, Proof, Proposition, Assertion) is distinct
2. No untyped collapse into a generic tree
3. Supports dependent types via explicit binding structures
4. Uses explicit binders for clarity (not de Bruijn indices initially)
5. All constructors have explicit inputs and clear semantic meaning

INVARIANTS (see Assertica.Core.Invariants):
- All bound variables must be in scope
- All type ascriptions must be well-formed
- No circular references in type definitions
- Module-qualified names must be properly formed
-}

module Assertica.Core.AST
  ( -- * Variables and Names
    Var (..)
  , varName
  , Binder (..)
  , QName (..)
  
    -- * Terms (computational values)
  , Term (..)
  , Pattern (..)
  , Clause (..)
  
    -- * Types (sort/kind layer)
  , Type (..)
  , TypeLevel (..)
  
    -- * Propositions (logical claims)
  , Proposition (..)
  
    -- * Proofs (witnesses for propositions)
  , Proof (..)
  
    -- * Assertions (proof obligations)
  , Assertion (..)
  , Assertion' (..)
  
    -- * Constants
  , Constant (..)
  , PrimitiveOp (..)
  
    -- * Utilities
  , freeVarsInTerm
  , freeVarsInType
  , freeVarsInProof
  , substituteTerm
  , substituteType
  , substituteProof
  , prettyTerm
  , prettyType
  , prettyProof
  , prettyProposition
  , prettyAssertion
  , prettyPattern
  ) where

import Data.List (intercalate)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import Data.Maybe (mapMaybe)
import GHC.Generics (Generic)
import Data.Data (Data)
import Data.Typeable (Typeable)

-- ============================================================================
-- VARIABLES AND NAMES
-- ============================================================================

{-| A variable in the AST. Uses explicit string names for clarity.
We maintain a scope-aware structure that tracks binding depth.
-}
data Var = Var
  { varName :: Text        -- ^ The name of the variable
  , varId   :: Int         -- ^ Unique identifier (for internal tracking)
  }
  deriving (Eq, Ord, Show, Generic, Typeable, Data)

{-| A binder introduces a variable with an optional type annotation.
Used in lambda abstractions, dependent functions, let-bindings, etc.
-}
data Binder = Binder
  { binderVar  :: Var     -- ^ The bound variable
  , binderType :: Maybe Type  -- ^ Optional type annotation
  }
  deriving (Eq, Show, Generic, Typeable, Data)

{-| Qualified names for modules and global definitions.
Format: module.name or just name for unqualified.
-}
data QName = QName
  { qnameModule :: [Text]  -- ^ Module path (e.g., ["Assertica", "Prelude"])
  , qnameLocal  :: Text    -- ^ Local name within the module
  }
  deriving (Eq, Ord, Show, Generic, Typeable, Data)

-- ============================================================================
-- TYPES
-- ============================================================================

{-| Type universe levels for predicativity (if supported).
For now, we have Type and Type at level n.
-}
data TypeLevel
  = Type0      -- ^ The base type universe
  | TypeN Int  -- ^ Type at level n (for universes)
  deriving (Eq, Show, Generic, Typeable, Data)

{-| Types represent the sorts/kinds of terms.
Supports dependent types via explicit quantification.
-}
data Type
  = TyVar Var                           -- ^ Type variable
  | TyConst QName [Type]                -- ^ Named type with arguments (e.g., List Int)
  | TyFun Type Type                     -- ^ Non-dependent function type: a -> b
  | TyForall Binder Type                -- ^ Dependent function type: forall x : A . B
  | TyUniverse TypeLevel                -- ^ Universe level (Type, Type 1, etc.)
  | TyApp Type Type                     -- ^ Type application (for partially applied types)
  | TyEq Term Term                      -- ^ Equality type: a = b
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- TERMS
-- ============================================================================

{-| Patterns for case expressions and pattern matching.
-}
data Pattern
  = PatVar Var                 -- ^ Bind a variable
  | PatConstr QName [Pattern]  -- ^ Match a data constructor
  | PatWildcard                -- ^ Match anything (no binding)
  | PatLit String              -- ^ Match a literal (placeholder)
  deriving (Eq, Show, Generic, Typeable, Data)

{-| A clause in a case expression.
Pattern -> Term when condition holds.
-}
data Clause = Clause
  { clausePattern :: Pattern
  , clauseBody    :: Term
  }
  deriving (Eq, Show, Generic, Typeable, Data)

{-| Terms are computational values in the proof language.
This includes functions, data, and computed values.
-}
data Term
  = -- Basic constructs
    Var Var                              -- ^ Variable
  | Const Constant                       -- ^ Constant (primitive ops, proof constants)
  
  -- Functions
  | Lam Binder Term                      -- ^ Lambda abstraction: λx . e
  | App Term Term                        -- ^ Application: f x
  
  -- Data
  | Constr QName [Term]                  -- ^ Data constructor application
  | Case Term [Clause] (Maybe Term)      -- ^ Case expression with default
  
  -- Let-binding
  | Let Binder Term Term                 -- ^ let x = e1 in e2
  
  -- Type ascription
  | Ann Term Type                        -- ^ Term annotated with type: e : T
  
  -- Dependent function
  | Forall Binder Term                   -- ^ Dependent function: forall x : A . body
  
  -- Proofs (opaque in this layer, verified by Agent 2B)
  | ProofTerm Proof                      -- ^ A proof term as evidence
  
  -- Propositions embedded as terms (for higher-order reasoning)
  | Prop Proposition                     -- ^ A proposition as a term
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- PROPOSITIONS
-- ============================================================================

{-| Propositions are logical claims about terms.
These represent the types of proof terms.
-}
data Proposition
  = -- Basic
    Eq Term Term                         -- ^ Equality: a = b
  | Top                                  -- ^ True (trivially provable)
  | Bot                                  -- ^ False (refutable)
  
  -- Logical connectives
  | And Proposition Proposition          -- ^ Conjunction
  | Or Proposition Proposition           -- ^ Disjunction
  | Impl Proposition Proposition         -- ^ Implication: P -> Q
  | Not Proposition                      -- ^ Negation: not P
  
  -- Quantification (over terms)
  | Forall' Binder Proposition           -- ^ Universal: forall x : A . P x
  | Exists Binder Proposition            -- ^ Existential: exists x : A . P x
  
  -- Type predicates
  | HasType Term Type                    -- ^ Type judgment: e : T
  | IsTypeCorrect Term                   -- ^ Term is type-correct (well-formed in context)
  
  -- Named propositions (constraints, lemmas)
  | Named QName                          -- ^ Reference to a named proposition
  
  -- Typed propositions
  | Typed Proposition Type               -- ^ Proposition with explicit type
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- PROOFS
-- ============================================================================

{-| Proof terms are witnesses for propositions.
These are largely opaque at this layer; Agent 2B's equality checker will verify them.
-}
data Proof
  = -- Basic proofs
    Refl Term                            -- ^ Proof by reflexivity: x = x
  | Symm Proof                           -- ^ Symmetry: from P prove symmetric P
  | Trans Proof Proof                    -- ^ Transitivity: compose proofs
  
  -- Structural proofs
  | Intro Binder Proof                   -- ^ Introduce a hypothesis (implication intro)
  | Elim Proof Proof                     -- ^ Eliminate a hypothesis (modus ponens)
  
  -- Data/constructor proofs
  | Constr' QName [Proof]                -- ^ Proof constructor (e.g., conjunction pair)
  | Case' Proof [Clause] (Maybe Proof)   -- ^ Case analysis on a proof
  
  -- Application
  | App' Proof Term                      -- ^ Apply proof to term (instantiation)
  
  -- Opaque proof (verified by the type checker or proof kernel)
  | Opaque QName Proposition             -- ^ Trusted proof (e.g., from an oracle or axiom)
  
  -- Conversion (type-aware)
  | Conv Proof Type Type                 -- ^ Proof with implicit type conversion
  
  -- Annotation
  | ProofAnn Proof Proposition           -- ^ Proof explicitly typed with proposition
  
  -- Variables in proofs
  | ProofVar Var                         -- ^ Proof variable
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- CONSTANTS
-- ============================================================================

{-| Primitive operations supported by the language.
-}
data PrimitiveOp
  = OpAdd | OpSub | OpMul | OpDiv       -- ^ Arithmetic
  | OpEq | OpNeq | OpLt | OpLe | OpGt | OpGe  -- ^ Comparison
  | OpAnd | OpOr | OpNot                -- ^ Logical
  | OpIfThenElse                        -- ^ Conditional
  deriving (Eq, Show, Generic, Typeable, Data)

{-| Constants in the language.
-}
data Constant
  = PrimOp PrimitiveOp                   -- ^ Built-in operation
  | IntLit Integer                       -- ^ Integer literal
  | BoolLit Bool                         -- ^ Boolean literal
  | StringLit String                     -- ^ String literal
  | UnitConst                            -- ^ Unit value
  | ProofConst QName                     -- ^ Named proof constant (axiom, lemma)
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- ASSERTIONS
-- ============================================================================

{-| An assertion is a top-level proof obligation in a module.
-}
data Assertion = Assertion
  { assertionName  :: QName              -- ^ Name of the assertion
  , assertionProp  :: Proposition        -- ^ The proposition to prove
  , assertionProof :: Maybe Proof        -- ^ Optional proof (Nothing = unsolved)
  }
  deriving (Eq, Show, Generic, Typeable, Data)

{-| Simplified assertion for contexts where we only care about the proof obligation.
-}
data Assertion' = Assertion'
  { assertion'Prop  :: Proposition
  , assertion'Proof :: Maybe Proof
  }
  deriving (Eq, Show, Generic, Typeable, Data)

-- ============================================================================
-- UTILITIES: FREE VARIABLE COLLECTION
-- ============================================================================

{-| Collect all free variables in a term.
A variable is free if it is not bound by a lambda, forall, or let.
-}
freeVarsInTerm :: Term -> Set Var
freeVarsInTerm term = go Set.empty term
  where
    go bound = \case
      Var v -> if Set.member v bound then Set.empty else Set.singleton v
      Const _ -> Set.empty
      Lam binder body ->
        let bound' = Set.insert (binderVar binder) bound
        in go bound' body
      App f x -> Set.union (go bound f) (go bound x)
      Constr _ args -> Set.unions (map (go bound) args)
      Case e clauses def ->
        let fv = go bound e
            fv_clauses = Set.unions [freeVarsInClause bound c | c <- clauses]
            fv_def = maybe Set.empty (go bound) def
        in Set.unions [fv, fv_clauses, fv_def]
      Let binder e1 e2 ->
        let fv1 = go bound e1
            bound' = Set.insert (binderVar binder) bound
            fv2 = go bound' e2
        in Set.union fv1 fv2
      Ann e ty ->
        Set.union (go bound e) (freeVarsInType bound ty)
      Forall binder body ->
        let bound' = Set.insert (binderVar binder) bound
        in go bound' body
      ProofTerm p -> freeVarsInProof bound p
      Prop prop -> freeVarsInProposition bound prop

freeVarsInClause :: Set Var -> Clause -> Set Var
freeVarsInClause bound (Clause pat body) =
  let boundVars = boundVarsInPattern pat
      bound' = Set.union bound boundVars
  in go bound' body
  where
    go = freeVarsInTerm

boundVarsInPattern :: Pattern -> Set Var
boundVarsInPattern = \case
  PatVar v -> Set.singleton v
  PatConstr _ pats -> Set.unions (map boundVarsInPattern pats)
  PatWildcard -> Set.empty
  PatLit _ -> Set.empty

{-| Collect free variables in a type.
-}
freeVarsInType :: Set Var -> Type -> Set Var
freeVarsInType bound = go
  where
    go = \case
      TyVar v -> if Set.member v bound then Set.empty else Set.singleton v
      TyConst _ args -> Set.unions (map go args)
      TyFun a b -> Set.union (go a) (go b)
      TyForall binder ty ->
        let bound' = Set.insert (binderVar binder) bound
        in freeVarsInType bound' ty
      TyUniverse _ -> Set.empty
      TyApp a b -> Set.union (go a) (go b)
      TyEq e1 e2 -> Set.union (freeVarsInTerm e1) (freeVarsInTerm e2)

{-| Collect free variables in a proof.
-}
freeVarsInProof :: Set Var -> Proof -> Set Var
freeVarsInProof bound = go
  where
    go = \case
      Refl e -> freeVarsInTerm e
      Symm p -> go p
      Trans p1 p2 -> Set.union (go p1) (go p2)
      Intro binder p ->
        let bound' = Set.insert (binderVar binder) bound
        in freeVarsInProof bound' p
      Elim p1 p2 -> Set.union (go p1) (go p2)
      Constr' _ proofs -> Set.unions (map go proofs)
      Case' p clauses def ->
        let fv = go p
            fv_clauses = Set.unions [freeVarsInProofClause bound c | c <- clauses]
            fv_def = maybe Set.empty go def
        in Set.unions [fv, fv_clauses, fv_def]
      App' p e -> Set.union (go p) (freeVarsInTerm e)
      Opaque _ _ -> Set.empty
      Conv p _ _ -> go p
      ProofAnn p _ -> go p
      ProofVar v -> if Set.member v bound then Set.empty else Set.singleton v

freeVarsInProofClause :: Set Var -> Clause -> Set Var
freeVarsInProofClause bound (Clause pat body) =
  let boundVars = boundVarsInPattern pat
      bound' = Set.union bound boundVars
  in freeVarsInProof bound' (extractProofFromTerm body)
  where
    extractProofFromTerm (ProofTerm p) = p
    extractProofFromTerm _ = Refl (Const UnitConst)

{-| Collect free variables in a proposition.
-}
freeVarsInProposition :: Set Var -> Proposition -> Set Var
freeVarsInProposition bound = go
  where
    go = \case
      Eq e1 e2 -> Set.union (freeVarsInTerm e1) (freeVarsInTerm e2)
      Top -> Set.empty
      Bot -> Set.empty
      And p1 p2 -> Set.union (go p1) (go p2)
      Or p1 p2 -> Set.union (go p1) (go p2)
      Impl p1 p2 -> Set.union (go p1) (go p2)
      Not p -> go p
      Forall' binder prop ->
        let bound' = Set.insert (binderVar binder) bound
        in freeVarsInProposition bound' prop
      Exists binder prop ->
        let bound' = Set.insert (binderVar binder) bound
        in freeVarsInProposition bound' prop
      HasType e ty -> Set.union (freeVarsInTerm e) (freeVarsInType bound ty)
      IsTypeCorrect e -> freeVarsInTerm e
      Named _ -> Set.empty
      Typed p _ -> go p

-- ============================================================================
-- UTILITIES: SUBSTITUTION
-- ============================================================================

{-| Substitute a variable with a term in a term.
Respects bounding and avoids capture.
-}
substituteTerm :: Var -> Term -> Term -> Term
substituteTerm var replacement = go
  where
    go = \case
      Var v -> if v == var then replacement else Var v
      Const c -> Const c
      Lam binder body ->
        if binderVar binder == var
        then Lam binder body
        else Lam binder (go body)
      App f x -> App (go f) (go x)
      Constr n args -> Constr n (map go args)
      Case e clauses def ->
        Case (go e)
             [Clause pat (goInClause pat body) | Clause pat body <- clauses]
             (fmap go def)
      Let binder e1 e2 ->
        let e1' = go e1
            e2' = if binderVar binder == var then e2 else go e2
        in Let binder e1' e2'
      Ann e ty -> Ann (go e) ty
      Forall binder body ->
        if binderVar binder == var
        then Forall binder body
        else Forall binder (go body)
      ProofTerm p -> ProofTerm (substituteProof var replacement p)
      Prop prop -> Prop (substituteProposition var replacement prop)
    
    goInClause pat body =
      if any (== var) (Set.toList (boundVarsInPattern pat))
      then body
      else go body

{-| Substitute in a type.
-}
substituteType :: Var -> Term -> Type -> Type
substituteType var replacement = go
  where
    go = \case
      TyVar v -> TyVar v
      TyConst n args -> TyConst n (map go args)
      TyFun a b -> TyFun (go a) (go b)
      TyForall binder ty ->
        if binderVar binder == var
        then TyForall binder ty
        else TyForall binder (go ty)
      TyUniverse u -> TyUniverse u
      TyApp a b -> TyApp (go a) (go b)
      TyEq e1 e2 -> TyEq (substituteTerm var replacement e1) (substituteTerm var replacement e2)

{-| Substitute in a proof.
-}
substituteProof :: Var -> Term -> Proof -> Proof
substituteProof var replacement = go
  where
    go = \case
      Refl e -> Refl (substituteTerm var replacement e)
      Symm p -> Symm (go p)
      Trans p1 p2 -> Trans (go p1) (go p2)
      Intro binder p ->
        if binderVar binder == var
        then Intro binder p
        else Intro binder (go p)
      Elim p1 p2 -> Elim (go p1) (go p2)
      Constr' n ps -> Constr' n (map go ps)
      Case' p clauses def ->
        Case' (go p)
              [Clause pat (body) | Clause pat body <- clauses]
              (fmap go def)
      App' p e -> App' (go p) (substituteTerm var replacement e)
      Opaque n prop -> Opaque n (substituteProposition var replacement prop)
      Conv p t1 t2 -> Conv (go p) (substituteType var replacement t1) (substituteType var replacement t2)
      ProofAnn p prop -> ProofAnn (go p) (substituteProposition var replacement prop)
      ProofVar v -> ProofVar v

{-| Substitute in a proposition.
-}
substituteProposition :: Var -> Term -> Proposition -> Proposition
substituteProposition var replacement = go
  where
    go = \case
      Eq e1 e2 -> Eq (substituteTerm var replacement e1) (substituteTerm var replacement e2)
      Top -> Top
      Bot -> Bot
      And p1 p2 -> And (go p1) (go p2)
      Or p1 p2 -> Or (go p1) (go p2)
      Impl p1 p2 -> Impl (go p1) (go p2)
      Not p -> Not (go p)
      Forall' binder prop ->
        if binderVar binder == var
        then Forall' binder prop
        else Forall' binder (go prop)
      Exists binder prop ->
        if binderVar binder == var
        then Exists binder prop
        else Exists binder (go prop)
      HasType e ty -> HasType (substituteTerm var replacement e) (substituteType var replacement ty)
      IsTypeCorrect e -> IsTypeCorrect (substituteTerm var replacement e)
      Named n -> Named n
      Typed p ty -> Typed (go p) (substituteType var replacement ty)

-- ============================================================================
-- UTILITIES: PRETTY-PRINTING
-- ============================================================================

{-| Pretty-print a term for display/debugging.
-}
prettyTerm :: Term -> String
prettyTerm = \case
  Var (Var name _) -> T.unpack name
  Const c -> prettyConstant c
  Lam (Binder (Var name _) mty) body ->
    let ty_str = case mty of
          Nothing -> ""
          Just ty -> " : " ++ prettyType ty
    in "λ" ++ T.unpack name ++ ty_str ++ ". " ++ prettyTerm body
  App f x -> "(" ++ prettyTerm f ++ " " ++ prettyTerm x ++ ")"
  Constr (QName mod name) args ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in mod_str ++ T.unpack name ++ if null args then "" else " " ++ unwords (map prettyTerm args)
  Case e clauses def ->
    "case " ++ prettyTerm e ++ " of { " ++
    intercalate "; " [prettyPattern p ++ " -> " ++ prettyTerm b | Clause p b <- clauses] ++
    (case def of Nothing -> ""; Just d -> "; _ -> " ++ prettyTerm d) ++ " }"
  Let (Binder (Var name _) _) e1 e2 ->
    "let " ++ T.unpack name ++ " = " ++ prettyTerm e1 ++ " in " ++ prettyTerm e2
  Ann e ty -> "(" ++ prettyTerm e ++ " : " ++ prettyType ty ++ ")"
  Forall (Binder (Var name _) mty) body ->
    let ty_str = case mty of
          Nothing -> ""
          Just ty -> " : " ++ prettyType ty
    in "∀ " ++ T.unpack name ++ ty_str ++ ". " ++ prettyTerm body
  ProofTerm p -> prettyProof p
  Prop prop -> prettyProposition prop

prettyPattern :: Pattern -> String
prettyPattern = \case
  PatVar (Var name _) -> T.unpack name
  PatConstr (QName mod name) pats ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in mod_str ++ T.unpack name ++ if null pats then "" else " " ++ unwords (map prettyPattern pats)
  PatWildcard -> "_"
  PatLit s -> s

{-| Pretty-print a type for display/debugging.
-}
prettyType :: Type -> String
prettyType = \case
  TyVar (Var name _) -> T.unpack name
  TyConst (QName mod name) args ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
        args_str = if null args then "" else " " ++ unwords (map prettyType args)
    in mod_str ++ T.unpack name ++ args_str
  TyFun a b -> "(" ++ prettyType a ++ " → " ++ prettyType b ++ ")"
  TyForall (Binder (Var name _) mty) body ->
    let ty_str = case mty of
          Nothing -> ""
          Just ty -> " : " ++ prettyType ty
    in "∀ " ++ T.unpack name ++ ty_str ++ ". " ++ prettyType body
  TyUniverse Type0 -> "Type"
  TyUniverse (TypeN n) -> "Type " ++ show n
  TyApp a b -> "(" ++ prettyType a ++ " " ++ prettyType b ++ ")"
  TyEq e1 e2 -> "(" ++ prettyTerm e1 ++ " = " ++ prettyTerm e2 ++ ")"

{-| Pretty-print a proposition.
-}
prettyProposition :: Proposition -> String
prettyProposition = \case
  Eq e1 e2 -> "(" ++ prettyTerm e1 ++ " = " ++ prettyTerm e2 ++ ")"
  Top -> "⊤"
  Bot -> "⊥"
  And p1 p2 -> "(" ++ prettyProposition p1 ++ " ∧ " ++ prettyProposition p2 ++ ")"
  Or p1 p2 -> "(" ++ prettyProposition p1 ++ " ∨ " ++ prettyProposition p2 ++ ")"
  Impl p1 p2 -> "(" ++ prettyProposition p1 ++ " → " ++ prettyProposition p2 ++ ")"
  Not p -> "¬(" ++ prettyProposition p ++ ")"
  Forall' (Binder (Var name _) mty) prop ->
    let ty_str = case mty of
          Nothing -> ""
          Just ty -> " : " ++ prettyType ty
    in "∀ " ++ T.unpack name ++ ty_str ++ ". " ++ prettyProposition prop
  Exists (Binder (Var name _) mty) prop ->
    let ty_str = case mty of
          Nothing -> ""
          Just ty -> " : " ++ prettyType ty
    in "∃ " ++ T.unpack name ++ ty_str ++ ". " ++ prettyProposition prop
  HasType e ty -> "(" ++ prettyTerm e ++ " : " ++ prettyType ty ++ ")"
  IsTypeCorrect e -> "WF(" ++ prettyTerm e ++ ")"
  Named (QName mod name) ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in mod_str ++ T.unpack name
  Typed p ty -> "(" ++ prettyProposition p ++ " : " ++ prettyType ty ++ ")"

{-| Pretty-print a proof.
-}
prettyProof :: Proof -> String
prettyProof = \case
  Refl e -> "refl(" ++ prettyTerm e ++ ")"
  Symm p -> "symm(" ++ prettyProof p ++ ")"
  Trans p1 p2 -> "trans(" ++ prettyProof p1 ++ ", " ++ prettyProof p2 ++ ")"
  Intro (Binder (Var name _) _) p -> "λ" ++ T.unpack name ++ ". " ++ prettyProof p
  Elim p1 p2 -> "(" ++ prettyProof p1 ++ " " ++ prettyProof p2 ++ ")"
  Constr' (QName mod name) ps ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in mod_str ++ T.unpack name ++ if null ps then "" else " " ++ unwords (map prettyProof ps)
  Case' p clauses def ->
    "case " ++ prettyProof p ++ " of { " ++
    intercalate "; " [prettyPattern pat ++ " -> " ++ prettyTerm body | Clause pat body <- clauses] ++
    (case def of Nothing -> ""; Just d -> "; _ -> " ++ prettyProof d) ++ " }"
  App' p e -> "(" ++ prettyProof p ++ " " ++ prettyTerm e ++ ")"
  Opaque (QName mod name) _ ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in "opaque[" ++ mod_str ++ T.unpack name ++ "]"
  Conv p t1 t2 -> "conv(" ++ prettyProof p ++ " : " ++ prettyType t1 ++ " → " ++ prettyType t2 ++ ")"
  ProofAnn p prop -> "(" ++ prettyProof p ++ " : " ++ prettyProposition prop ++ ")"
  ProofVar (Var name _) -> T.unpack name

prettyConstant :: Constant -> String
prettyConstant = \case
  PrimOp op -> "op[" ++ show op ++ "]"
  IntLit n -> show n
  BoolLit b -> show b
  StringLit s -> show s
  UnitConst -> "()"
  ProofConst (QName mod name) ->
    let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
    in mod_str ++ T.unpack name

{-| Pretty-print an assertion.
-}
prettyAssertion :: Assertion -> String
prettyAssertion (Assertion (QName mod name) prop proof) =
  let mod_str = if null mod then "" else intercalate "." (map T.unpack mod) ++ "."
      proof_str = case proof of
        Nothing -> " [unproven]"
        Just p -> " := " ++ prettyProof p
  in "assert " ++ mod_str ++ T.unpack name ++ " : " ++ prettyProposition prop ++ proof_str