import Lean
new_frontend
open Lean

partial def mkTower : Nat → Expr
| 0   => mkConst `a
| n+1 => mkApp2 (mkConst `f) (mkTower n) (mkTower n)

partial def depth : Expr → MonadCacheT Expr Nat CoreM Nat
| e =>
  checkCache e fun e =>
    match e with
    | Expr.const c [] _ => pure 1
    | Expr.app f a _    => do pure $ Nat.max (← depth f) (← depth a) + 1
    | _                 => pure 0

#eval (depth (mkTower 100)).run

partial def visit : Expr → MonadCacheT Expr Expr CoreM Expr
| e =>
  checkCache e fun e =>
    match e with
    | Expr.const `a [] _ => pure $ mkConst `b
    | Expr.app f a _     => e.updateApp! <$> visit f <*> visit a
    | _                  => pure e

#eval (visit (mkTower 4)).run

def tst : CoreM Nat := do
let e ← (visit (mkTower 100)).run; (depth e).run

#eval tst

partial def visitNoCache : Expr → CoreM Expr
| e =>
  match e with
  | Expr.const `a [] _ => pure $ mkConst `b
  | Expr.app f a _     => e.updateApp! <$> visitNoCache f <*> visitNoCache a
  | _                  => pure e

-- The following is super slow
-- #eval do e ← visitNoCache (mkTower 30); (depth e).run

def displayConsts (e : Expr) : CoreM Unit :=
e.forEach fun e => match e with
  | Expr.const c _ _ => do IO.println c
  | _ => pure ()

def tst2 : CoreM Unit := do
let e ← (visit (mkTower 100)).run; displayConsts e

#eval tst2
