/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author(s): Stanislas Polu, Jesse Michael Han

REPL implementation to interact with Lean through stdio at a specific
declaration.
-/
import tactic
import data.string.basic
import all
import util.io
import util.tactic
import basic.table
import tools.shrink_proof
import tools.try_finish

section main

setup_tactic_parser

meta structure LeanREPLRequest : Type :=
(cmd : string)
(sid: string)
(tsid: string)
(tac: string)
(name: string)
(open_ns: string)
(term: string)

meta structure LeanREPLResponse : Type :=
(sid : option string)
(tsid : option string)
(tactic_state : option string)
(error: option string)
(proof_steps : list (string × string))

meta structure parent : Type :=
(tsid : string)
(action : string)

meta structure LeanREPLState : Type :=
(state : dict string (dict string (tactic_state × option parent)))
(next_sid : ℕ)

namespace LeanREPLState

meta def insert_ts (σ : LeanREPLState) (sid) (tsid) (ts) (parent : option parent): LeanREPLState :=
  ⟨dict.insert sid (dict.insert tsid (ts, parent) (σ.1.get_default (dict.empty) sid)) σ.1, σ.2⟩

meta def get_ts_parents (σ : LeanREPLState) (sid) (tsid) : option (tactic_state × option parent) :=
  (σ.1.get_default (dict.empty) sid).get tsid

meta def get_ts (σ : LeanREPLState) (sid) (tsid) : option tactic_state :=
  option.map prod.fst $ σ.get_ts_parents sid tsid

meta def get_next_tsid (σ : LeanREPLState) (sid) : string := (format! "{(σ.1.get_default (dict.empty) sid).size}").to_string

meta def erase_search (σ : LeanREPLState) (sid) : LeanREPLState := ⟨σ.1.erase sid, σ.2⟩

meta def get_next_sid (σ : LeanREPLState) : string := (format! "{σ.2}").to_string

meta def incr_next_sid (σ : LeanREPLState) : LeanREPLState := ⟨σ.1, σ.2+1⟩

end LeanREPLState

meta instance : has_from_json LeanREPLRequest := ⟨λ msg, match msg with
  | (json.array [json.of_string cmd, json.array args]) := match cmd with
    | "run_tac" := match json.array args with
      | (json.array [json.of_string sid, json.of_string tsid, json.of_string tac]) := pure ⟨cmd, sid, tsid, tac, "", "", ""⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "conjecture_set" := match json.array args with
      | (json.array [json.of_string sid, json.of_string tsid, json.of_string term]) := pure ⟨cmd, sid, tsid, "", "", "", term⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "conjecture_assume" := match json.array args with
      | (json.array [json.of_string sid, json.of_string tsid, json.of_string term]) := pure ⟨cmd, sid, tsid, "", "", "", term⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "init_search" := match json.array args with
      | (json.array [json.of_string name, json.of_string open_ns]) := pure ⟨cmd, "", "", "", name, open_ns, ""⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "clear_search" := match json.array args with
      | (json.array [json.of_string sid]) := pure ⟨cmd, sid, "" , "", "", "", ""⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "shrink_proof" := match json.array args with
      | (json.array [json.of_string sid, json.of_string tsid]) := do
        pure ⟨cmd, sid, tsid , "", "", "", ""⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | "try_finish" := match json.array args with
      | (json.array [json.of_string sid, json.of_string tsid]) := do
        pure ⟨cmd, sid, tsid , "", "", "", ""⟩
      | exc := tactic.fail format!"request_parsing_error: cmd={cmd} data={exc}"
      end
    | exc := tactic.fail format!"request_parsing_error: data={exc}"
    end
  | exc := tactic.fail format!"request_parsing_error: data={exc}"
  end
⟩

@[reducible]
meta def LeanREPL := state_t LeanREPLState io

meta def LeanREPL.forever (x : LeanREPL unit) : LeanREPL unit := do
  σ₀ ← get,
  state_t.lift $ io.iterate σ₀ $ λ σ, do {
    (_, σ') ← x.run σ,
    return (some σ')
  },
  state_t.lift $ io.fail' $ format! "[LeanREPL.forever] unreachable code"

meta def record_ts {m} [monad m] (sid: string) (ts : tactic_state) (parent : option parent) : (state_t LeanREPLState m) string := do {
  σ ← get,
  let tsid := σ.get_next_tsid sid,
  modify $ λ σ, σ.insert_ts sid tsid ts parent,
  pure tsid
}

meta def LeanREPLResponse.to_json: LeanREPLResponse → json
| ⟨sid, tsid, ts, err, steps⟩ :=
    json.object [
      ⟨"search_id", match sid with
        | none := json.null
        | some sid := json.of_string sid
        end⟩,
      ⟨"tactic_state_id", match tsid with
        | none := json.null
        | some tsid := json.of_string tsid
        end⟩,
      ⟨"tactic_state", match ts with
        | none := json.null
        | some ts := json.of_string ts
        end⟩,
      ⟨"error", match err with
        | none := json.null
        | some err := json.of_string err
        end⟩,
      ⟨"proof_steps", json.array (steps.map $ λ ⟨ts_str, action⟩, json.array [json.of_string ts_str, json.of_string action])⟩
    ]

meta instance : has_to_format LeanREPLResponse :=
⟨has_to_format.to_format ∘ LeanREPLResponse.to_json⟩

meta def parse_theorem_name (nm: string) : tactic name :=
do lean.parser.run_with_input ident nm

meta def parse_open_namespace (open_ns: string) : tactic (list name) :=
do lean.parser.run_with_input (many ident) open_ns

meta def handle_init_search
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
   σ ← get,
   -- Parse declaration name.
   decl_name ← state_t.lift $ io.run_tactic'' $ do {
     parse_theorem_name req.name
   },
   -- Parse open namespaces.
   decl_open_ns ← state_t.lift $ io.run_tactic'' $ do {
     parse_open_namespace req.open_ns
   },
   -- Check that the declaration is a theorem.
   is_theorem ← state_t.lift $ io.run_tactic'' $ do {
     tactic.is_theorem decl_name
   } <|> pure ff,
   match is_theorem with
   -- The declaration is not a theorem, return an error.
   | ff := do {
     let err := format! "not_a_theorem: name={req.name} open_ns={req.open_ns}",
     pure ⟨none, none, none, some err.to_string, []⟩
   }
   -- The declaration is a theorem, set the env with open namespaces to it and
   -- generate a new tactic state.
   | tt := do {
     ts ← state_t.lift $ io.run_tactic'' $ do {
       env ← tactic.get_env,
       decl ← env.get decl_name,
       let g := decl.type,
       tactic.set_goal_to g,
       lean_file ← env.decl_olean decl_name,
       tactic.set_env_core $ environment.for_decl_of_imported_module lean_file decl_name,
       add_open_namespaces decl_open_ns,
       tactic.read
     },
     let sid := σ.get_next_sid,
     modify $ λ σ, σ.incr_next_sid,
     tsid ← record_ts sid ts none,
     ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts,
     pure $ ⟨sid, tsid, ts_str, none, []⟩
   }
   end
}

meta def handle_clear_search
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
   -- Simply remove the table associated with the provided search id from the state.
   modify $ λ σ, σ.erase_search req.sid,
   pure $ ⟨req.sid, none, none, none, []⟩
}

meta def finalize_proof
  (req : LeanREPLRequest)
  (ts': tactic_state) : LeanREPL LeanREPLResponse := do {
  σ ← get,
  -- Retrieve the tactic state at index 0 to extract the top-level goal metavariable.
  match σ.get_ts req.sid "0" with
  | none := do {
    let err := format! "unexpected_unknown_tsid_0: search_id={req.sid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  | (some ts₀) := do {
    result ← (state_t.lift ∘ io.run_tactic'') $ do {
      -- Set to tactic state index 0 to retrieve the meta-variable for the top goal.
      tactic.write ts₀,
      [g] ← tactic.get_goals,
      tgt ← tactic.infer_type g,
      tactic.write ts',
      pf ← tactic.get_assignment g >>= tactic.instantiate_mvars,
      tactic.capture' (validate_proof tgt pf)
    },
    match result with
    | (interaction_monad.result.success r s') := do {
      tsid ← record_ts req.sid ts' (some ⟨req.tsid, req.tac⟩),
      ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts',
      pure $ ⟨req.sid, tsid, ts_str, none, []⟩
    }
    | (interaction_monad.result.exception f p s') := do {
      let msg := (f.get_or_else (λ _, format.of_string "n/a")) (),
      let err := format! "proof_validation_failed: msg={msg}",
      pure ⟨none, none, none, some err.to_string, []⟩
    }
    end
  }
  end
}

meta def handle_conjecture
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
  σ ← get,
  match (σ.get_ts req.sid req.tsid) with
  | none := do {
    let err := format! "unknown_id: search_id={req.sid} tactic_state_id={req.tsid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  | (some ts) := do {
    let conj_str := req.term,
    -- Use `have` to introduce the new assumption
    result_with_string ← state_t.lift $ io.run_tactic'' $ do {
      tactic.write ts,
      conj_name ← tactic.get_unused_name "h",
      let tac_str := format! "have {conj_name} : {conj_str}",
      get_tac_and_capture_result tac_str.to_string 5000 <|> do {
          let msg : format := format!"parse_itactic failed on `{req.tac}`",
          interaction_monad.mk_exception msg none <$> tactic.read
      }
    },
    match result_with_string with
    -- `have` was successful.
    | interaction_monad.result.success _ ts' := do {
        -- Narrow the tactic state to the assumption only
        ts_narrowed ← (state_t.lift ∘ io.run_tactic'') $ do {
          tactic.write ts',
          g ← list.head <$> tactic.get_goals,
          tactic.set_goals [g],
          -- We need to revert all hypotheses, otherwise proof finalization will complain with
          -- unknown variables.
          tactic.revert_all,
          tactic.read
        },
        -- Create a new search id, this is required so that the final check are only run on the
        -- "narrowed" tactic state (tactic state of the conjecture only).
        let sid := σ.get_next_sid,
        modify $ λ σ, σ.incr_next_sid,

        tsid ← record_ts sid ts_narrowed none,
        ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts_narrowed,
        pure $ ⟨sid, tsid, ts_str, none, []⟩
    }
    | interaction_monad.result.exception fn pos ts' := do {
      state_t.lift $ do {
        let msg := (fn.get_or_else (λ _, format.of_string "n/a")) (),
        let err := format! "conjecture_set_have_failed: pos={pos} msg={msg}",
        pure ⟨none, none, none, some err.to_string, []⟩
      }
    }
    end
  }
  end
}

meta def collect_proof_steps_aux (σ : LeanREPLState) (sid : string) : Π (tsid : string), io (list (tactic_state × string × tactic_state))
| tsid2 := do
  match σ.get_ts_parents sid tsid2 with
  | none := io.fail "collect_proof_steps: invalid tsid"
  | (some ⟨ts2, parent⟩) :=
    match parent with
    | none := if tsid2 = "0" then pure [] else io.fail "no parent"
    | (some ⟨tsid1, action⟩) :=
      match σ.get_ts sid tsid1 with
      | none := io.fail "parent doesn't exist"
      | (some ts1) := do {
        rest ← collect_proof_steps_aux tsid1,
        pure (⟨ts1, action, ts2⟩ :: rest)
      }
      end
    end
  end

meta def collect_proof_steps (σ : LeanREPLState) (sid tsid : string) : io (list (tactic_state × string × tactic_state)) := do
  rev_steps ← collect_proof_steps_aux σ sid tsid,
  pure $ list.reverse rev_steps

meta def handle_shrink_proof
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
  σ ← get,
  match (σ.get_ts req.sid req.tsid) with
  | none := do {
    let err := format! "unknown_id: search_id={req.sid} tactic_state_id={req.tsid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  | (some ts_final) := do {
    ts_str ← state_t.lift $ io.run_tactic'' $ postprocess_tactic_state ts_final,
    state_t.lift $ io.run_tac ts_final tactic.done,
    steps ← state_t.lift $ collect_proof_steps σ req.sid req.tsid,
    new_steps ← state_t.lift (shrink_proof steps),
    new_steps ← state_t.lift $ new_steps.mmap $ λ ⟨ts1, action, _⟩, do {
      ts1_str ← io.run_tactic'' $ postprocess_tactic_state ts1,
      pure (ts1_str, action)
    },
    pure ⟨req.sid, req.tsid, ts_str, none, new_steps⟩
  }
  end
  }

meta def handle_try_finish
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
  σ ← get,
  match (σ.get_ts req.sid req.tsid) with
  | none := do {
    let err := format! "unknown_id: search_id={req.sid} tactic_state_id={req.tsid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  | (some ts) := do {
    possible_action ← state_t.lift $ try_finish ts,
    match possible_action with
    | none := do {
      let err := format! "try_finish_failed: search_id={req.sid} tactic_state_id={req.tsid}",
      pure ⟨none, none, none, some err.to_string, []⟩
    }
    | some (action, ts') := do {
      -- TODO: refactor so that finalizing a proof is a separate top-level call
      goals ← state_t.lift $ io.run_tac ts' tactic.get_goals,
      if goals.empty then do {
        r ← finalize_proof { req with tac := action } ts',
        match r.error with
        | none := do {
          ts_str ← state_t.lift $ io.run_tactic'' $ postprocess_tactic_state ts',
          pure { r with proof_steps := [(action, ts_str)] }
        }
        | some err := pure r
        end
      } else do {
      tsid ← record_ts req.sid ts' (some ⟨req.tsid, action⟩),
      ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts',
      pure $ ⟨req.sid, tsid, ts_str, none, [(action, ts_str)]⟩
    }
    }
    end
  }
  end
}

meta def handle_assume
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
  σ ← get,
  match (σ.get_ts req.sid req.tsid) with
  | none := do {
    let err := format! "unknown_id: search_id={req.sid} tactic_state_id={req.tsid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  | (some ts) := do {
    let conj_str := req.term,
    -- Use `have` to introduce the new assumption
    result_with_string ← state_t.lift $ io.run_tactic'' $ do {
      tactic.write ts,
      conj_name ← tactic.get_unused_name "h",
      let tac_str := format! "have {conj_name} : {conj_str}",
      get_tac_and_capture_result tac_str.to_string 5000 <|> do {
          let msg : format := format!"parse_itactic failed on `{req.tac}`",
          interaction_monad.mk_exception msg none <$> tactic.read
      }
    },
    match result_with_string with
    -- `have` was successful.
    | interaction_monad.result.success _ ts' := do {
        -- Narrow the tactic state to the initial goal with assumption.
        ts_assumed ← (state_t.lift ∘ io.run_tactic'') $ do {
          tactic.write ts',
          (g1 :: gs) ← tactic.get_goals,
          tactic.set_goals gs,
          -- We need to revert all hypotheses, otherwise proof finalization will complain with
          -- unknown variables.
          tactic.revert_all,
          tactic.read
        },
        -- Create a new search id, this is required so that the final check are only run on the
        -- "assumed" tactic state (tactic state with additional assumption only).
        let sid := σ.get_next_sid,
        modify $ λ σ, σ.incr_next_sid,

        tsid ← record_ts sid ts_assumed (some ⟨req.tsid, req.tac⟩),
        ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts_assumed,
        pure $ ⟨sid, tsid, ts_str, none, []⟩
    }
    | interaction_monad.result.exception fn pos ts' := do {
      state_t.lift $ do {
        let msg := (fn.get_or_else (λ _, format.of_string "n/a")) (),
        let err := format! "conjecture_assume_have_failed: pos={pos} msg={msg}",
        pure ⟨none, none, none, some err.to_string, []⟩
      }
    }
    end
  }
  end
}

meta def handle_parse_failed
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
    -- A little hack that use `req` to pass error message
    let err := format! "parse_failed: data={req.sid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }

meta def handle_run_tac
  (req : LeanREPLRequest)
  : LeanREPL LeanREPLResponse := do {
  σ ← get,
  match (σ.get_ts req.sid req.tsid) with
  -- Received an unknown search id, return an error.
  | none := do {
    let err := format! "unknown_id: search_id={req.sid} tactic_state_id={req.tsid}",
    pure ⟨none, none, none, some err.to_string, []⟩
  }
  -- The tactic state was retrieved from the state.
  | (some ts) := do {
    -- Set the tactic state and try to apply the tactic.
    result_with_string ← state_t.lift $ io.run_tactic'' $ do {
      tactic.write ts,
      get_tac_and_capture_result req.tac 5000 <|> do {
          let msg : format := format!"parse_itactic failed on `{req.tac}`",
          interaction_monad.mk_exception msg none <$> tactic.read
      }
    },
    match result_with_string with
    -- The tactic application was successful.
    | interaction_monad.result.success _ ts' := do {
        n ← (state_t.lift ∘ io.run_tactic'') $ do {
          tactic.write ts',
          tactic.num_goals
        },
        -- monad_lift $ io.run_tactic'' $ tactic.trace format! "REMAINING SUBGOALS: {n}",
        match n with
        -- There is no more subgoals, check that the produced proof is valid.
        | 0 := do {
          finalize_proof req ts'
        }
        -- There are remaining subgoals, return the updated tactic state.
        | n := do {
          tsid ← record_ts req.sid ts' (some ⟨req.tsid, req.tac⟩),
          ts_str ← (state_t.lift ∘ io.run_tactic'') $ postprocess_tactic_state ts',
          pure $ ⟨req.sid, tsid, ts_str, none, []⟩
        }
        end
      }
    -- The tactic application failed, potentially return an error with the failure message.
    | interaction_monad.result.exception fn pos ts' := do {
        -- Some tactics such as linarith fail but result in a tactic state with no goals. Check if
        -- that's the case and finalize the proof, otherwise error.
        n ← (state_t.lift ∘ io.run_tactic'') $ do {
          tactic.write ts',
          tactic.num_goals
        },
        -- monad_lift $ io.run_tactic'' $ tactic.trace format! "REMAINING SUBGOALS: {n}",
        match n with
        -- There is no more subgoals, check that the produced proof is valid.
        | 0 := do {
          finalize_proof req ts'
        }
        -- There are remaining subgoals, return the error.
        | _ := do {
          state_t.lift $ do {
            let msg := (fn.get_or_else (λ _, format.of_string "n/a")) (),
            let err := format! "gen_tac_and_capture_res_failed: pos={pos} msg={msg}",
            pure ⟨none, none, none, some err.to_string, []⟩
          }
        }
        end
      }
    end
  }
  end
}

meta def handle_request (req : LeanREPLRequest) : LeanREPL LeanREPLResponse :=
match req.cmd with
| "run_tac" := handle_run_tac req
| "init_search" := handle_init_search req
| "clear_search" := handle_clear_search req
| "conjecture_set" := handle_conjecture req
| "conjecture_assume" := handle_assume req
| "shrink_proof" := handle_shrink_proof req
| "try_finish" := handle_try_finish req
| "parse_failed" := handle_parse_failed req
| exc := state_t.lift $ io.fail' format! "[fatal] unknown_command: cmd={exc}"
end

meta def parse_request (msg : string) : io LeanREPLRequest := do {
  match json.parse msg with
  | (some json_msg) := io.run_tactic'' $ has_from_json.from_json json_msg
  | none := pure ⟨"parse_failed", msg, "", "", "", "", ""⟩
  end
}

meta def loop : LeanREPL unit := do {
  req ← (state_t.lift $ io.get_line >>= parse_request),
  res ← handle_request req,
  state_t.lift $ io.put_str_ln' $ format! "{(json.unparse ∘ LeanREPLResponse.to_json) res}"
}

meta def main : io unit := do {
  state_t.run loop.forever ⟨dict.empty, 0⟩ $> ()
}

end main


example : Nat := Id.run do
  for _ in [1:10] do
    assert! true
    if false then return 0
  return 0


import tactic.replacer

open tactic

def_replacer sneaky

example : true :=
begin
  success_if_fail { sneaky },
  trivial
end

@[sneaky] meta def sneaky' : tactic unit := `[skip]

example : true :=
begin
  sneaky,
  guard_target true,
  trivial
end

@[sneaky] meta def sneaky'' := `[trivial]

example : true :=
begin
  sneaky
end

@[sneaky] meta def sneaky''' (old : tactic unit) := old >> `[trivial]

example : true ∧ true :=
begin
  split,
  sneaky
end

def_replacer transform : ℕ → tactic ℕ

run_cmd success_if_fail (transform 1)

@[transform] meta def transform' (n : ℕ) : tactic ℕ :=
return (n+1)

run_cmd do n ← transform 2, guard (n = 3)

@[transform] meta def transform'' (n : ℕ) : tactic ℕ :=
return (n * n)

run_cmd do n ← transform 2, guard (n = 4)

@[transform] meta def transform''' (n : ℕ) (old : tactic ℕ) : tactic ℕ :=
do n' ← old, return (n' * n')

run_cmd do n ← transform 2, guard (n = 16)


structure Foo where
  foo : Nat

example (f : Foo) : f
                   --^ insert: .
                    --^ textDocument/completion


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jannis Limperg
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.meta.tactic
import Mathlib.Lean3Lib.init.meta.type_context
import Mathlib.Lean3Lib.init.meta.rewrite_tactic
import Mathlib.Lean3Lib.init.meta.simp_tactic
import Mathlib.Lean3Lib.init.meta.smt.congruence_closure
import Mathlib.Lean3Lib.init.control.combinators
import Mathlib.Lean3Lib.init.meta.interactive_base
import Mathlib.Lean3Lib.init.meta.derive
import Mathlib.Lean3Lib.init.meta.match_tactic
import Mathlib.Lean3Lib.init.meta.congr_tactic
import Mathlib.Lean3Lib.init.meta.case_tag

universes l u v

namespace Mathlib

namespace tactic

/- allows metavars -/

/- allow metavars and no subgoals -/

/- doesn't allows metavars -/

/- Auxiliary version of i_to_expr for apply-like tactics.
   This is a workaround for comment
      https://github.com/leanprover/lean/issues/1342#issuecomment-307912291
   at issue #1342.

   In interactive mode, given a tactic

        apply f

   we want the apply tactic to create all metavariables. The following
   definition will return `@f` for `f`. That is, it will **not** create
   metavariables for implicit arguments.

   Before we added `i_to_expr_for_apply`, the tactic

       apply le_antisymm

   would first elaborate `le_antisymm`, and create

       @le_antisymm ?m_1 ?m_2 ?m_3 ?m_4

   The type class resolution problem
        ?m_2 : weak_order ?m_1
   by the elaborator since ?m_1 is not assigned yet, and the problem is
   discarded.

   Then, we would invoke `apply_core`, which would create two
   new metavariables for the explicit arguments, and try to unify the resulting
   type with the current target. After the unification,
   the metavariables ?m_1, ?m_3 and ?m_4 are assigned, but we lost
   the information about the pending type class resolution problem.

   With `i_to_expr_for_apply`, `le_antisymm` is elaborate into `@le_antisymm`,
   the apply_core tactic creates all metavariables, and solves the ones that
   can be solved by type class resolution.

   Another possible fix: we modify the elaborator to return pending
   type class resolution problems, and store them in the tactic_state.
-/

namespace interactive

/--
itactic: parse a nested "interactive" tactic. That is, parse
  `{` tactic `}`
-/
/--
If the current goal is a Pi/forall `∀ x : t, u` (resp. `let x := t in u`) then `intro` puts `x : t` (resp. `x := t`) in the local context. The new subgoal target is `u`.

If the goal is an arrow `t → u`, then it puts `h : t` in the local context and the new goal target is `u`.

If the goal is neither a Pi/forall nor begins with a let binder, the tactic `intro` applies the tactic `whnf` until an introduction can be applied or the goal is not head reducible. In the latter case, the tactic fails.
-/
/--
Similar to `intro` tactic. The tactic `intros` will keep introducing new hypotheses until the goal target is not a Pi/forall or let binder.

The variant `intros h₁ ... hₙ` introduces `n` new hypotheses using the given identifiers to name them.
-/
/--
The tactic `introv` allows the user to automatically introduce the variables of a theorem and explicitly name the hypotheses involved. The given names are used to name non-dependent hypotheses.

Examples:
```
example : ∀ a b : nat, a = b → b = a :=
begin
  introv h,
  exact h.symm
end
```
The state after `introv h` is
```
a b : ℕ,
h : a = b
⊢ b = a
```

```
example : ∀ a b : nat, a = b → ∀ c, b = c → a = c :=
begin
  introv h₁ h₂,
  exact h₁.trans h₂
end
```
The state after `introv h₁ h₂` is
```
a b : ℕ,
h₁ : a = b,
c : ℕ,
h₂ : b = c
⊢ a = c
```
-/
/-- Parse a current name and new name for `rename`. -/
/-- Parse the arguments of `rename`. -/
/--
Rename one or more local hypotheses. The renamings are given as follows:

```
rename x y             -- rename x to y
rename x → y           -- ditto
rename [x y, a b]      -- rename x to y and a to b
rename [x → y, a → b]  -- ditto
```

Note that if there are multiple hypotheses called `x` in the context, then
`rename x y` will rename *all* of them. If you want to rename only one, use
`dedup` first.
-/
/--
The `apply` tactic tries to match the current goal against the conclusion of the type of term. The argument term should be a term well-formed in the local context of the main goal. If it succeeds, then the tactic returns as many subgoals as the number of premises that have not been fixed by type inference or type class resolution. Non-dependent premises are added before dependent ones.

The `apply` tactic uses higher-order pattern matching, type class resolution, and first-order unification with dependent types.
-/
/--
Similar to the `apply` tactic, but does not reorder goals.
-/
/--
Similar to the `apply` tactic, but only creates subgoals for non-dependent premises that have not been fixed by type inference or type class resolution.
-/
/--
Similar to the `apply` tactic, but allows the user to provide a `apply_cfg` configuration object.
-/
/--
Similar to the `apply` tactic, but uses matching instead of unification.
`apply_match t` is equivalent to `apply_with t {unify := ff}`
-/
/--
This tactic tries to close the main goal `... ⊢ t` by generating a term of type `t` using type class resolution.
-/
/--
This tactic behaves like `exact`, but with a big difference: the user can put underscores `_` in the expression as placeholders for holes that need to be filled, and `refine` will generate as many subgoals as there are holes.

Note that some holes may be implicit. The type of each hole must either be synthesized by the system or declared by an explicit type ascription like `(_ : nat → Prop)`.
-/
/--
This tactic looks in the local context for a hypothesis whose type is equal to the goal target. If it finds one, it uses it to prove the goal, and otherwise it fails.
-/
/-- Try to apply `assumption` to all goals. -/
/--
`change u` replaces the target `t` of the main goal to `u` provided that `t` is well formed with respect to the local context of the main goal and `t` and `u` are definitionally equal.

`change u at h` will change a local hypothesis to `u`.

`change t with u at h1 h2 ...` will replace `t` with `u` in all the supplied hypotheses (or `*`), or in the goal if no `at` clause is specified, provided that `t` and `u` are definitionally equal.
-/
/--
This tactic provides an exact proof term to solve the main goal. If `t` is the goal and `p` is a term of type `u` then `exact p` succeeds if and only if `t` and `u` can be unified.
-/
/--
Like `exact`, but takes a list of terms and checks that all goals are discharged after the tactic.
-/
/--
A synonym for `exact` that allows writing `have/suffices/show ..., from ...` in tactic mode.
-/
/--
`revert h₁ ... hₙ` applies to any goal with hypotheses `h₁` ... `hₙ`. It moves the hypotheses and their dependencies to the target of the goal. This tactic is the inverse of `intro`.
-/
/- Version of to_expr that tries to bypass the elaborator if `p` is just a constant or local constant.
   This is not an optimization, by skipping the elaborator we make sure that no unwanted resolution is used.
   Example: the elaborator will force any unassigned ?A that must have be an instance of (has_one ?A) to nat.
   Remark: another benefit is that auxiliary temporary metavariables do not appear in error messages. -/

-- accepts the same content as `pexpr_list_or_texpr`, but with correct goal info pos annotations

/--
`rewrite e` applies identity `e` as a rewrite rule to the target of the main goal. If `e` is preceded by left arrow (`←` or `<-`), the rewrite is applied in the reverse direction. If `e` is a defined constant, then the equational lemmas associated with `e` are used. This provides a convenient way to unfold `e`.

`rewrite [e₁, ..., eₙ]` applies the given rules sequentially.

`rewrite e at l` rewrites `e` at location(s) `l`, where `l` is either `*` or a list of hypotheses in the local context. In the latter case, a turnstile `⊢` or `|-` can also be used, to signify the target of the goal.
-/
/--
An abbreviation for `rewrite`.
-/
/--
`rewrite` followed by `assumption`.
-/
/--
A variant of `rewrite` that uses the unifier more aggressively, unfolding semireducible definitions.
-/
/--
An abbreviation for `erewrite`.
-/
/--
Returns the unique names of all hypotheses (local constants) in the context.
-/
/--
Returns all hypotheses (local constants) from the context except those whose
unique names are in `hyp_uids`.
-/
/--
Apply `t` to the main goal and revert any new hypothesis in the generated goals.
If `t` is a supported tactic or chain of supported tactics (e.g. `induction`,
`cases`, `apply`, `constructor`), the generated goals are also tagged with case
tags. You can then use `case` to focus such tagged goals.

Two typical uses of `with_cases`:

1. Applying a custom eliminator:

   ```
   lemma my_nat_rec :
     ∀ n {P : ℕ → Prop} (zero : P 0) (succ : ∀ n, P n → P (n + 1)), P n := ...

   example (n : ℕ) : n = n :=
   begin
     with_cases { apply my_nat_rec n },
     case zero { refl },
     case succ : m ih { refl }
   end
   ```

2. Enabling the use of `case` after a chain of case-splitting tactics:

   ```
   example (n m : ℕ) : unit :=
   begin
     with_cases { cases n; induction m },
     case nat.zero nat.zero { exact () },
     case nat.zero nat.succ : k { exact () },
     case nat.succ nat.zero : i { exact () },
     case nat.succ nat.succ : k i ih_i { exact () }
   end
   ```
-/
/--
`generalize : e = x` replaces all occurrences of `e` in the target with a new hypothesis `x` of the same type.

`generalize h : e = x` in addition registers the hypothesis `h : e = x`.
-/
/--
  Updates the tags of new subgoals produced by `cases` or `induction`. `in_tag`
  is the initial tag, i.e. the tag of the goal on which `cases`/`induction` was
  applied. `rs` should contain, for each subgoal, the constructor name
  associated with that goal and the hypotheses that were introduced.
-/
/--
Assuming `x` is a variable in the local context with an inductive type, `induction x` applies induction on `x` to the main goal, producing one goal for each constructor of the inductive type, in which the target is replaced by a general instance of that constructor and an inductive hypothesis is added for each recursive argument to the constructor. If the type of an element in the local context depends on `x`, that element is reverted and reintroduced afterward, so that the inductive hypothesis incorporates that hypothesis as well.

For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `induction n` produces one goal with hypothesis `h : P 0` and target `Q 0`, and one goal with hypotheses `h : P (nat.succ a)` and `ih₁ : P a → Q a` and target `Q (nat.succ a)`. Here the names `a` and `ih₁` ire chosen automatically.

`induction e`, where `e` is an expression instead of a variable, generalizes `e` in the goal, and then performs induction on the resulting variable.

`induction e with y₁ ... yₙ`, where `e` is a variable or an expression, specifies that the sequence of names `y₁ ... yₙ` should be used for the arguments to the constructors and inductive hypotheses, including implicit arguments. If the list does not include enough names for all of the arguments, additional names are generated automatically. If too many names are given, the extra ones are ignored. Underscores can be used in the list, in which case the corresponding names are generated automatically. Note that for long sequences of names, the `case` tactic provides a more convenient naming mechanism.

`induction e using r` allows the user to specify the principle of induction that should be used. Here `r` should be a theorem whose result type must be of the form `C t`, where `C` is a bound variable and `t` is a (possibly empty) sequence of bound variables

`induction e generalizing z₁ ... zₙ`, where `z₁ ... zₙ` are variables in the local context, generalizes over `z₁ ... zₙ` before applying the induction but then introduces them in each goal. In other words, the net effect is that each inductive hypothesis is generalized.

`induction h : t` will introduce an equality of the form `h : t = C x y`, asserting that the input term is equal to the current constructor case, to the context.
-/
/--
Focuses on a goal ('case') generated by `induction`, `cases` or `with_cases`.

The goal is selected by giving one or more names which must match exactly one
goal. A goal is matched if the given names are a suffix of its goal tag.
Additionally, each name in the sequence can be abbreviated to a suffix of the
corresponding name in the goal tag. Thus, a goal with tag
```
nat.zero, list.nil
```
can be selected with any of these invocations (among others):
```
case nat.zero list.nil {...}
case nat.zero nil      {...}
case zero     nil      {...}
case          nil      {...}
```

Additionally, the form
```
case C : N₀ ... Nₙ {...}
```
can be used to rename hypotheses introduced by the preceding
`cases`/`induction`/`with_cases`, using the names `Nᵢ`. For example:
```
example (xs : list ℕ) : xs = xs :=
begin
  induction xs,
  case nil { reflexivity },
  case cons : x xs ih {
    -- x : ℕ, xs : list ℕ, ih : xs = xs
    reflexivity }
end
```

Note that this renaming functionality only work reliably *directly after* an
`induction`/`cases`/`with_cases`. If you need to perform additional work after
an `induction` or `cases` (e.g. introduce hypotheses in all goals), use
`with_cases`.
-/
/-
TODO `case` could be generalised to work with zero names as well. The form

  case : x y z { ... }

would select the first goal (or the first goal with a case tag), renaming
hypotheses to `x, y, z`. The renaming functionality would be available only if
the goal has a case tag.
-/

/--
Assuming `x` is a variable in the local context with an inductive type, `destruct x` splits the main goal, producing one goal for each constructor of the inductive type, in which `x` is assumed to be a general instance of that constructor. In contrast to `cases`, the local context is unchanged, i.e. no elements are reverted or introduced.

For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `destruct n` produces one goal with target `n = 0 → Q n`, and one goal with target `∀ (a : ℕ), (λ (w : ℕ), n = w → Q n) (nat.succ a)`. Here the name `a` is chosen automatically.
-/
/--
Assuming `x` is a variable in the local context with an inductive type, `cases x` splits the main goal, producing one goal for each constructor of the inductive type, in which the target is replaced by a general instance of that constructor. If the type of an element in the local context depends on `x`, that element is reverted and reintroduced afterward, so that the case split affects that hypothesis as well.

For example, given `n : nat` and a goal with a hypothesis `h : P n` and target `Q n`, `cases n` produces one goal with hypothesis `h : P 0` and target `Q 0`, and one goal with hypothesis `h : P (nat.succ a)` and target `Q (nat.succ a)`. Here the name `a` is chosen automatically.

`cases e`, where `e` is an expression instead of a variable, generalizes `e` in the goal, and then cases on the resulting variable.

`cases e with y₁ ... yₙ`, where `e` is a variable or an expression, specifies that the sequence of names `y₁ ... yₙ` should be used for the arguments to the constructors, including implicit arguments. If the list does not include enough names for all of the arguments, additional names are generated automatically. If too many names are given, the extra ones are ignored. Underscores can be used in the list, in which case the corresponding names are generated automatically.

`cases h : e`, where `e` is a variable or an expression, performs cases on `e` as above, but also adds a hypothesis `h : e = ...` to each hypothesis, where `...` is the constructor instance for that particular case.
-/
/--
`cases_matching p` applies the `cases` tactic to a hypothesis `h : type` if `type` matches the pattern `p`.
`cases_matching [p_1, ..., p_n]` applies the `cases` tactic to a hypothesis `h : type` if `type` matches one of the given patterns.
`cases_matching* p` more efficient and compact version of `focus1 { repeat { cases_matching p } }`. It is more efficient because the pattern is compiled once.

Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_matching* [_ ∨ _, _ ∧ _]
```
-/
/-- Shorthand for `cases_matching` -/
/--
`cases_type I` applies the `cases` tactic to a hypothesis `h : (I ...)`
`cases_type I_1 ... I_n` applies the `cases` tactic to a hypothesis `h : (I_1 ...)` or ... or `h : (I_n ...)`
`cases_type* I` is shorthand for `focus1 { repeat { cases_type I } }`
`cases_type! I` only applies `cases` if the number of resulting subgoals is <= 1.

Example: The following tactic destructs all conjunctions and disjunctions in the current goal.
```
cases_type* or and
```
-/
/--
Tries to solve the current goal using a canonical proof of `true`, or the `reflexivity` tactic, or the `contradiction` tactic.
-/
/--
Closes the main goal using `sorry`.
-/
/--
Closes the main goal using `sorry`.
-/
/--
The contradiction tactic attempts to find in the current local context a hypothesis that is equivalent to an empty inductive type (e.g. `false`), a hypothesis of the form `c_1 ... = c_2 ...` where `c_1` and `c_2` are distinct constructors, or two contradictory hypotheses.
-/
/--
`iterate { t }` repeatedly applies tactic `t` until `t` fails. `iterate { t }` always succeeds.

`iterate n { t }` applies `t` `n` times.
-/
/--
`repeat { t }` applies `t` to each goal. If the application succeeds,
the tactic is applied recursively to all the generated subgoals until it eventually fails.
The recursion stops in a subgoal when the tactic has failed to make progress.
The tactic `repeat { t }` never fails.
-/
/--
`try { t }` tries to apply tactic `t`, but succeeds whether or not `t` succeeds.
-/
/--
A do-nothing tactic that always succeeds.
-/
/--
`solve1 { t }` applies the tactic `t` to the main goal and fails if it is not solved.
-/
/--
`abstract id { t }` tries to use tactic `t` to solve the main goal. If it succeeds, it abstracts the goal as an independent definition or theorem with name `id`. If `id` is omitted, a name is generated automatically.
-/
/--
`all_goals { t }` applies the tactic `t` to every goal, and succeeds if each application succeeds.
-/
/--
`any_goals { t }` applies the tactic `t` to every goal, and succeeds if at least one application succeeds.
-/
/--
`focus { t }` temporarily hides all goals other than the first, applies `t`, and then restores the other goals. It fails if there are no goals.
-/
/--
Assuming the target of the goal is a Pi or a let, `assume h : t` unifies the type of the binder with `t` and introduces it with name `h`, just like `intro h`. If `h` is absent, the tactic uses the name `this`. If `t` is omitted, it will be inferred.

`assume (h₁ : t₁) ... (hₙ : tₙ)` introduces multiple hypotheses. Any of the types may be omitted, but the names must be present.
-/
/--
`have h : t := p` adds the hypothesis `h : t` to the current goal if `p` a term of type `t`. If `t` is omitted, it will be inferred.

`have h : t` adds the hypothesis `h : t` to the current goal and opens a new subgoal with target `t`. The new subgoal becomes the main goal. If `t` is omitted, it will be replaced by a fresh metavariable.

If `h` is omitted, the name `this` is used.
-/
/--
`let h : t := p` adds the hypothesis `h : t := p` to the current goal if `p` a term of type `t`. If `t` is omitted, it will be inferred.

`let h : t` adds the hypothesis `h : t := ?M` to the current goal and opens a new subgoal `?M : t`. The new subgoal becomes the main goal. If `t` is omitted, it will be replaced by a fresh metavariable.

If `h` is omitted, the name `this` is used.
-/
/--
`suffices h : t` is the same as `have h : t, tactic.swap`. In other words, it adds the hypothesis `h : t` to the current goal and opens a new subgoal with target `t`.
-/
/--
This tactic displays the current state in the tracing buffer.
-/
/--
`trace a` displays `a` in the tracing buffer.
-/
/--
`existsi e` will instantiate an existential quantifier in the target with `e` and leave the instantiated body as the new target. More generally, it applies to any inductive type with one constructor and at least two arguments, applying the constructor with `e` as the first argument and leaving the remaining arguments as goals.

`existsi [e₁, ..., eₙ]` iteratively does the same for each expression in the list.
-/
/--
This tactic applies to a goal such that its conclusion is an inductive type (say `I`). It tries to apply each constructor of `I` until it succeeds.
-/
/--
Similar to `constructor`, but only non-dependent premises are added as new goals.
-/
/--
Applies the first constructor when the type of the target is an inductive data type with two constructors.
-/
/--
Applies the second constructor when the type of the target is an inductive data type with two constructors.
-/
/--
Applies the constructor when the type of the target is an inductive data type with one constructor.
-/
/--
Replaces the target of the main goal by `false`.
-/
/--
The `injection` tactic is based on the fact that constructors of inductive data types are injections. That means that if `c` is a constructor of an inductive datatype, and if `(c t₁)` and `(c t₂)` are two terms that are equal then  `t₁` and `t₂` are equal too.

If `q` is a proof of a statement of conclusion `t₁ = t₂`, then injection applies injectivity to derive the equality of all arguments of `t₁` and `t₂` placed in the same positions. For example, from `(a::b) = (c::d)` we derive `a=c` and `b=d`. To use this tactic `t₁` and `t₂` should be constructor applications of the same constructor.

Given `h : a::b = c::d`, the tactic `injection h` adds two new hypothesis with types `a = c` and `b = d` to the main goal. The tactic `injection h with h₁ h₂` uses the names `h₁` and `h₂` to name the new hypotheses.
-/
/--
`injections with h₁ ... hₙ` iteratively applies `injection` to hypotheses using the names `h₁ ... hₙ`.
-/
end interactive

/-- Decode a list of `simp_arg_type` into lists for each type.

  This is a backwards-compatibility version of `decode_simp_arg_list_with_symm`.
  This version fails when an argument of the form `simp_arg_type.symm_expr`
  is included, so that `simp`-like tactics that do not (yet) support backwards rewriting
  should properly report an error but function normally on other inputs.
-/
/-- Decode a list of `simp_arg_type` into lists for each type.

  This is the newer version of `decode_simp_arg_list`,
  and has a new name for backwards compatibility.
  This version indicates the direction of a `simp` lemma by including a `bool` with the `pexpr`.
-/
namespace interactive

/--
The `simp` tactic uses lemmas and hypotheses to simplify the main goal target or non-dependent hypotheses. It has many variants.

`simp` simplifies the main goal target using lemmas tagged with the attribute `[simp]`.

`simp [h₁ h₂ ... hₙ]` simplifies the main goal target using the lemmas tagged with the attribute `[simp]` and the given `hᵢ`'s, where the `hᵢ`'s are expressions. If `hᵢ` is preceded by left arrow (`←` or `<-`), the simplification is performed in the reverse direction. If an `hᵢ` is a defined constant `f`, then the equational lemmas associated with `f` are used. This provides a convenient way to unfold `f`.

`simp [*]` simplifies the main goal target using the lemmas tagged with the attribute `[simp]` and all hypotheses.

`simp *` is a shorthand for `simp [*]`.

`simp only [h₁ h₂ ... hₙ]` is like `simp [h₁ h₂ ... hₙ]` but does not use `[simp]` lemmas

`simp [-id_1, ... -id_n]` simplifies the main goal target using the lemmas tagged with the attribute `[simp]`, but removes the ones named `idᵢ`.

`simp at h₁ h₂ ... hₙ` simplifies the non-dependent hypotheses `h₁ : T₁` ... `hₙ : Tₙ`. The tactic fails if the target or another hypothesis depends on one of them. The token `⊢` or `|-` can be added to the list to include the target.

`simp at *` simplifies all the hypotheses and the target.

`simp * at *` simplifies target and all (non-dependent propositional) hypotheses using the other hypotheses.

`simp with attr₁ ... attrₙ` simplifies the main goal target using the lemmas tagged with any of the attributes `[attr₁]`, ..., `[attrₙ]` or `[simp]`.
-/
/--
Just construct the simp set and trace it. Used for debugging.
-/
/--
`simp_intros h₁ h₂ ... hₙ` is similar to `intros h₁ h₂ ... hₙ` except that each hypothesis is simplified as it is introduced, and each introduced hypothesis is used to simplify later ones and the final target.

As with `simp`, a list of simplification lemmas can be provided. The modifiers `only` and `with` behave as with `simp`.
-/
/--
`dsimp` is similar to `simp`, except that it only uses definitional equalities.
-/
/--
This tactic applies to a goal whose target has the form `t ~ u` where `~` is a reflexive relation, that is, a relation which has a reflexivity lemma tagged with the attribute `[refl]`. The tactic checks whether `t` and `u` are definitionally equal and then solves the goal.
-/
/--
Shorter name for the tactic `reflexivity`.
-/
/--
This tactic applies to a goal whose target has the form `t ~ u` where `~` is a symmetric relation, that is, a relation which has a symmetry lemma tagged with the attribute `[symm]`. It replaces the target with `u ~ t`.
-/
/--
This tactic applies to a goal whose target has the form `t ~ u` where `~` is a transitive relation, that is, a relation which has a transitivity lemma tagged with the attribute `[trans]`.

`transitivity s` replaces the goal with the two subgoals `t ~ s` and `s ~ u`. If `s` is omitted, then a metavariable is used instead.
-/
/--
Proves a goal with target `s = t` when `s` and `t` are equal up to the associativity and commutativity of their binary operations.
-/
/--
An abbreviation for `ac_reflexivity`.
-/
/--
Tries to prove the main goal using congruence closure.
-/
/--
Given hypothesis `h : x = t` or `h : t = x`, where `x` is a local constant, `subst h` substitutes `x` by `t` everywhere in the main goal and then clears `h`.
-/
/--
Apply `subst` to all hypotheses of the form `h : x = t` or `h : t = x`.
-/
/--
`clear h₁ ... hₙ` tries to clear each hypothesis `hᵢ` from the local context.
-/
/--
Similar to `unfold`, but only uses definitional equalities.
-/
/--
Similar to `dunfold`, but performs a raw delta reduction, rather than using an equation associated with the defined constants.
-/
/--
This tactic unfolds all structure projections.
-/
end interactive

structure unfold_config extends simp_config where

namespace interactive

/--
Given defined constants `e₁ ... eₙ`, `unfold e₁ ... eₙ` iteratively unfolds all occurrences in the target of the main goal, using equational lemmas associated with the definitions.

As with `simp`, the `at` modifier can be used to specify locations for the unfolding.
-/
/--
Similar to `unfold`, but does not iterate the unfolding.
-/
/--
If the target of the main goal is an `opt_param`, assigns the default value.
-/
/--
If the target of the main goal is an `auto_param`, executes the associated tactic.
-/
/--
Fails if the given tactic succeeds.
-/
/--
Succeeds if the given tactic fails.
-/
/--
`guard_target t` fails if the target of the main goal is not `t`.
We use this tactic for writing tests.
-/
/--
`guard_hyp h : t` fails if the hypothesis `h` does not have type `t`.
We use this tactic for writing tests.
-/
/--
`match_target t` fails if target does not match pattern `t`.
-/
/--
`by_cases p` splits the main goal into two cases, assuming `h : p` in the first branch, and
`h : ¬ p` in the second branch. You can specify the name of the new hypothesis using the syntax
`by_cases h : p`.
-/
/--
Apply function extensionality and introduce new hypotheses.
The tactic `funext` will keep applying new the `funext` lemma until the goal target is not reducible to
```
  |-  ((fun x, ...) = (fun x, ...))
```
The variant `funext h₁ ... hₙ` applies `funext` `n` times, and uses the given identifiers to name the new hypotheses.
-/
/--
If the target of the main goal is a proposition `p`, `by_contradiction` reduces the goal to proving `false` using the additional hypothesis `h : ¬ p`. `by_contradiction h` can be used to name the hypothesis `h : ¬ p`.

This tactic will attempt to use decidability of `p` if available, and will otherwise fall back on classical reasoning.
-/
/--
If the target of the main goal is a proposition `p`, `by_contra` reduces the goal to proving `false` using the additional hypothesis `h : ¬ p`. `by_contra h` can be used to name the hypothesis `h : ¬ p`.

This tactic will attempt to use decidability of `p` if available, and will otherwise fall back on classical reasoning.
-/
/--
Type check the given expression, and trace its type.
-/
/--
Fail if there are unsolved goals.
-/
/--
`show t` finds the first goal whose target unifies with `t`. It makes that the main goal, performs the unification, and replaces the target with the unified version of `t`.
-/
/--
The tactic `specialize h a₁ ... aₙ` works on local hypothesis `h`. The premises of this hypothesis, either universal quantifications or non-dependent implications, are instantiated by concrete terms coming either from arguments `a₁` ... `aₙ`. The tactic adds a new hypothesis with the same name `h := h a₁ ... aₙ` and tries to clear the previous one.
-/
end interactive

end tactic

/- See add_interactive -/

/--
Copy a list of meta definitions in the current namespace to tactic.interactive.

This command is useful when we want to update tactic.interactive without closing the current namespace.
-/
/--
Renames hypotheses with the same name.
-/
namespace tactic

/- Helper tactic for `mk_inj_eq -/

/- Auxiliary tactic for proving `I.C.inj_eq` lemmas.
   These lemmas are automatically generated by the equation compiler.
   Example:
   ```
   list.cons.inj_eq : forall h1 h2 t1 t2, (h1::t1 = h2::t2) = (h1 = h2 ∧ t1 = t2) :=
   by mk_inj_eq
   ```
-/

end tactic

/- Define inj_eq lemmas for inductive datatypes that were declared before `mk_inj_eq` -/

theorem sum.inl.inj_eq {α : Type u} (β : Type v) (a₁ : α) (a₂ : α) :
    sum.inl a₁ = sum.inl a₂ = (a₁ = a₂) :=
  propext
    { mp := fun (h : sum.inl a₁ = sum.inl a₂) => sum.inl.inj h,
      mpr :=
        fun (ᾰ : a₁ = a₂) =>
          (fun (val val_1 : α) (e_2 : val = val_1) => congr_arg sum.inl e_2) a₁ a₂ ᾰ }

theorem sum.inr.inj_eq (α : Type u) {β : Type v} (b₁ : β) (b₂ : β) :
    sum.inr b₁ = sum.inr b₂ = (b₁ = b₂) :=
  sorry

theorem psum.inl.inj_eq {α : Sort u} (β : Sort v) (a₁ : α) (a₂ : α) :
    psum.inl a₁ = psum.inl a₂ = (a₁ = a₂) :=
  propext
    { mp := fun (h : psum.inl a₁ = psum.inl a₂) => psum.inl.inj h,
      mpr :=
        fun (ᾰ : a₁ = a₂) =>
          (fun (val val_1 : α) (e_2 : val = val_1) => congr_arg psum.inl e_2) a₁ a₂ ᾰ }

theorem psum.inr.inj_eq (α : Sort u) {β : Sort v} (b₁ : β) (b₂ : β) :
    psum.inr b₁ = psum.inr b₂ = (b₁ = b₂) :=
  sorry

theorem sigma.mk.inj_eq {α : Type u} {β : α → Type v} (a₁ : α) (b₁ : β a₁) (a₂ : α) (b₂ : β a₂) :
    sigma.mk a₁ b₁ = sigma.mk a₂ b₂ = (a₁ = a₂ ∧ b₁ == b₂) :=
  sorry

theorem psigma.mk.inj_eq {α : Sort u} {β : α → Sort v} (a₁ : α) (b₁ : β a₁) (a₂ : α) (b₂ : β a₂) :
    psigma.mk a₁ b₁ = psigma.mk a₂ b₂ = (a₁ = a₂ ∧ b₁ == b₂) :=
  sorry

theorem subtype.mk.inj_eq {α : Sort u} {p : α → Prop} (a₁ : α) (h₁ : p a₁) (a₂ : α) (h₂ : p a₂) :
    { val := a₁, property := h₁ } = { val := a₂, property := h₂ } = (a₁ = a₂) :=
  sorry

theorem option.some.inj_eq {α : Type u} (a₁ : α) (a₂ : α) : some a₁ = some a₂ = (a₁ = a₂) :=
  propext
    { mp := fun (h : some a₁ = some a₂) => option.some.inj h,
      mpr :=
        fun (ᾰ : a₁ = a₂) =>
          (fun (val val_1 : α) (e_1 : val = val_1) => congr_arg some e_1) a₁ a₂ ᾰ }

theorem list.cons.inj_eq {α : Type u} (h₁ : α) (t₁ : List α) (h₂ : α) (t₂ : List α) :
    h₁ :: t₁ = h₂ :: t₂ = (h₁ = h₂ ∧ t₁ = t₂) :=
  sorry

theorem nat.succ.inj_eq (n₁ : ℕ) (n₂ : ℕ) : Nat.succ n₁ = Nat.succ n₂ = (n₁ = n₂) :=
  propext
    { mp := fun (h : Nat.succ n₁ = Nat.succ n₂) => nat.succ.inj h,
      mpr :=
        fun (ᾰ : n₁ = n₂) => (fun (n n_1 : ℕ) (e_1 : n = n_1) => congr_arg Nat.succ e_1) n₁ n₂ ᾰ }

end Mathlib


/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jared Roesch, Sebastian Ullrich

The Except monad transformer.
-/
prelude
import Init.Control.Basic
import Init.Control.Id
import Init.Coe

namespace Except
variable {ε : Type u}

@[always_inline, inline]
protected def pure (a : α) : Except ε α :=
  Except.ok a

@[always_inline, inline]
protected def map (f : α → β) : Except ε α → Except ε β
  | Except.error err => Except.error err
  | Except.ok v => Except.ok <| f v

@[simp] theorem map_id : Except.map (ε := ε) (α := α) (β := α) id = id := by
  apply funext
  intro e
  simp [Except.map]; cases e <;> rfl

@[always_inline, inline]
protected def mapError (f : ε → ε') : Except ε α → Except ε' α
  | Except.error err => Except.error <| f err
  | Except.ok v      => Except.ok v

@[always_inline, inline]
protected def bind (ma : Except ε α) (f : α → Except ε β) : Except ε β :=
  match ma with
  | Except.error err => Except.error err
  | Except.ok v      => f v

/-- Returns true if the value is `Except.ok`, false otherwise. -/
@[always_inline, inline]
protected def toBool : Except ε α → Bool
  | Except.ok _    => true
  | Except.error _ => false

abbrev isOk : Except ε α → Bool := Except.toBool

@[always_inline, inline]
protected def toOption : Except ε α → Option α
  | Except.ok a    => some a
  | Except.error _ => none

@[always_inline, inline]
protected def tryCatch (ma : Except ε α) (handle : ε → Except ε α) : Except ε α :=
  match ma with
  | Except.ok a    => Except.ok a
  | Except.error e => handle e

def orElseLazy (x : Except ε α) (y : Unit → Except ε α) : Except ε α :=
  match x with
  | Except.ok a    => Except.ok a
  | Except.error _ => y ()

@[always_inline]
instance : Monad (Except ε) where
  pure := Except.pure
  bind := Except.bind
  map  := Except.map

end Except

def ExceptT (ε : Type u) (m : Type u → Type v) (α : Type u) : Type v :=
  m (Except ε α)

@[always_inline, inline]
def ExceptT.mk {ε : Type u} {m : Type u → Type v} {α : Type u} (x : m (Except ε α)) : ExceptT ε m α := x

@[always_inline, inline]
def ExceptT.run {ε : Type u} {m : Type u → Type v} {α : Type u} (x : ExceptT ε m α) : m (Except ε α) := x

namespace ExceptT

variable {ε : Type u} {m : Type u → Type v} [Monad m]

@[always_inline, inline]
protected def pure {α : Type u} (a : α) : ExceptT ε m α :=
  ExceptT.mk <| pure (Except.ok a)

@[always_inline, inline]
protected def bindCont {α β : Type u} (f : α → ExceptT ε m β) : Except ε α → m (Except ε β)
  | Except.ok a    => f a
  | Except.error e => pure (Except.error e)

@[always_inline, inline]
protected def bind {α β : Type u} (ma : ExceptT ε m α) (f : α → ExceptT ε m β) : ExceptT ε m β :=
  ExceptT.mk <| ma >>= ExceptT.bindCont f

@[always_inline, inline]
protected def map {α β : Type u} (f : α → β) (x : ExceptT ε m α) : ExceptT ε m β :=
  ExceptT.mk <| x >>= fun a => match a with
    | (Except.ok a)    => pure <| Except.ok (f a)
    | (Except.error e) => pure <| Except.error e

@[always_inline, inline]
protected def lift {α : Type u} (t : m α) : ExceptT ε m α :=
  ExceptT.mk <| Except.ok <$> t

@[always_inline]
instance : MonadLift (Except ε) (ExceptT ε m) := ⟨fun e => ExceptT.mk <| pure e⟩
instance : MonadLift m (ExceptT ε m) := ⟨ExceptT.lift⟩

@[always_inline, inline]
protected def tryCatch {α : Type u} (ma : ExceptT ε m α) (handle : ε → ExceptT ε m α) : ExceptT ε m α :=
  ExceptT.mk <| ma >>= fun res => match res with
   | Except.ok a    => pure (Except.ok a)
   | Except.error e => (handle e)

instance : MonadFunctor m (ExceptT ε m) := ⟨fun f x => f x⟩

@[always_inline]
instance : Monad (ExceptT ε m) where
  pure := ExceptT.pure
  bind := ExceptT.bind
  map  := ExceptT.map

@[always_inline, inline]
protected def adapt {ε' α : Type u} (f : ε → ε') : ExceptT ε m α → ExceptT ε' m α := fun x =>
  ExceptT.mk <| Except.mapError f <$> x

end ExceptT

@[always_inline]
instance (m : Type u → Type v) (ε₁ : Type u) (ε₂ : Type u) [Monad m] [MonadExceptOf ε₁ m] : MonadExceptOf ε₁ (ExceptT ε₂ m) where
  throw e := ExceptT.mk <| throwThe ε₁ e
  tryCatch x handle := ExceptT.mk <| tryCatchThe ε₁ x handle

@[always_inline]
instance (m : Type u → Type v) (ε : Type u) [Monad m] : MonadExceptOf ε (ExceptT ε m) where
  throw e := ExceptT.mk <| pure (Except.error e)
  tryCatch := ExceptT.tryCatch

instance [Monad m] [Inhabited ε] : Inhabited (ExceptT ε m α) where
  default := throw default

instance (ε) : MonadExceptOf ε (Except ε) where
  throw    := Except.error
  tryCatch := Except.tryCatch

namespace MonadExcept
variable {ε : Type u} {m : Type v → Type w}

/-- Alternative orelse operator that allows to select which exception should be used.
    The default is to use the first exception since the standard `orelse` uses the second. -/
@[always_inline, inline]
def orelse' [MonadExcept ε m] {α : Type v} (t₁ t₂ : m α) (useFirstEx := true) : m α :=
  tryCatch t₁ fun e₁ => tryCatch t₂ fun e₂ => throw (if useFirstEx then e₁ else e₂)

end MonadExcept

@[always_inline, inline]
def observing {ε α : Type u} {m : Type u → Type v} [Monad m] [MonadExcept ε m] (x : m α) : m (Except ε α) :=
  tryCatch (do let a ← x; pure (Except.ok a)) (fun ex => pure (Except.error ex))

def liftExcept [MonadExceptOf ε m] [Pure m] : Except ε α → m α
  | Except.ok a    => pure a
  | Except.error e => throw e

instance (ε : Type u) (m : Type u → Type v) [Monad m] : MonadControl m (ExceptT ε m) where
  stM        := Except ε
  liftWith f := liftM <| f fun x => x.run
  restoreM x := x

class MonadFinally (m : Type u → Type v) where
  /-- `tryFinally' x f` runs `x` and then the "finally" computation `f`.
  When `x` succeeds with `a : α`, `f (some a)` is returned. If `x` fails
  for `m`'s definition of failure, `f none` is returned. Hence `tryFinally'`
  can be thought of as performing the same role as a `finally` block in
  an imperative programming language. -/
  tryFinally' {α β} : m α → (Option α → m β) → m (α × β)

export MonadFinally (tryFinally')

/-- Execute `x` and then execute `finalizer` even if `x` threw an exception -/
@[always_inline, inline]
def tryFinally {m : Type u → Type v} {α β : Type u} [MonadFinally m] [Functor m] (x : m α) (finalizer : m β) : m α :=
  let y := tryFinally' x (fun _ => finalizer)
  (·.1) <$> y

@[always_inline]
instance Id.finally : MonadFinally Id where
  tryFinally' := fun x h =>
   let a := x
   let b := h (some x)
   pure (a, b)

@[always_inline]
instance ExceptT.finally {m : Type u → Type v} {ε : Type u} [MonadFinally m] [Monad m] : MonadFinally (ExceptT ε m) where
  tryFinally' := fun x h => ExceptT.mk do
    let r ← tryFinally' x fun e? => match e? with
        | some (.ok a) => h (some a)
        | _            => h none
    match r with
    | (.ok a,    .ok b)    => pure (.ok (a, b))
    | (_,        .error e) => pure (.error e)  -- second error has precedence
    | (.error e, _)        => pure (.error e)


/-
Copyright (c) 2019 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import control.traversable.basic
import tactic.simpa

open interactive interactive.types lean.parser

private meta def loc.to_string_aux : option name → string
| none := "⊢"
| (some x) := to_string x

/-- pretty print a `loc` -/
meta def loc.to_string : loc → string
| (loc.ns []) := ""
| (loc.ns [none]) := ""
| (loc.ns ls) := string.join $ list.intersperse " " (" at" :: ls.map loc.to_string_aux)
| loc.wildcard := " at *"

/-- shift `pos` `n` columns to the left -/
meta def pos.move_left (p : pos) (n : ℕ) : pos :=
{ line := p.line, column := p.column - n }

namespace tactic

open list

/-- parse structure instance of the shape `{ field1 := value1, .. , field2 := value2 }` -/
meta def struct_inst : lean.parser pexpr :=
do tk "{",
   ls ← sep_by (skip_info (tk ","))
     ( sum.inl <$> (tk ".." *> texpr) <|>
       sum.inr <$> (prod.mk <$> ident <* tk ":=" <*> texpr)),
   tk "}",
   let (srcs,fields) := partition_map id ls,
   let (names,values) := unzip fields,
   pure $ pexpr.mk_structure_instance
     { field_names := names,
       field_values := values,
       sources := srcs }

/-- pretty print structure instance -/
meta def struct.to_tactic_format (e : pexpr) : tactic format :=
do r ← e.get_structure_instance_info,
   fs ← mzip_with (λ n v,
     do v ← to_expr v >>= pp,
        pure $ format!"{n} := {v}" )
     r.field_names r.field_values,
   let ss := r.sources.map (λ s, format!" .. {s}"),
   let x : format := format.join $ list.intersperse ", " (fs ++ ss),
   pure format!" {{{x}}"

/-- Attribute containing a table that accumulates multiple `squeeze_simp` suggestions -/
@[user_attribute]
private meta def squeeze_loc_attr :
  user_attribute unit (option (list (pos × string × list simp_arg_type × string))) :=
{ name := `_squeeze_loc,
  parser := fail "this attribute should not be used",
  descr := "table to accumulate multiple `squeeze_simp` suggestions" }

/-- dummy declaration used as target of `squeeze_loc` attribute -/
def squeeze_loc_attr_carrier := ()

run_cmd squeeze_loc_attr.set ``squeeze_loc_attr_carrier none tt

/-- Format a list of arguments for use with `simp` and friends. This omits the
list entirely if it is empty. -/
meta def render_simp_arg_list : list simp_arg_type → tactic format
| [] := pure ""
| args := (++) " " <$> to_line_wrap_format <$> args.mmap pp

/-- Emit a suggestion to the user. If inside a `squeeze_scope` block,
the suggestions emitted through `mk_suggestion` will be aggregated so that
every tactic that makes a suggestion can consider multiple execution of the
same invocation.
If `at_pos` is true, make the suggestion at `p` instead of the current position. -/
meta def mk_suggestion (p : pos) (pre post : string) (args : list simp_arg_type)
  (at_pos := ff) : tactic unit :=
do xs ← squeeze_loc_attr.get_param ``squeeze_loc_attr_carrier,
   match xs with
   | none := do
     args ← render_simp_arg_list args,
     if at_pos then
       @scope_trace _ p.line p.column $
         λ _, _root_.trace sformat!"{pre}{args}{post}" (pure () : tactic unit)
     else
       trace sformat!"{pre}{args}{post}"
   | some xs := do
     squeeze_loc_attr.set ``squeeze_loc_attr_carrier ((p,pre,args,post) :: xs) ff
   end

local postfix `?`:9001 := optional

/-- translate a `pexpr` into a `simp` configuration -/
meta def parse_config : option pexpr → tactic (simp_config_ext × format)
| none := pure ({}, "")
| (some cfg) :=
  do e ← to_expr ``(%%cfg : simp_config_ext),
     fmt ← has_to_tactic_format.to_tactic_format cfg,
     prod.mk <$> eval_expr simp_config_ext e
             <*> struct.to_tactic_format cfg

/-- translate a `pexpr` into a `dsimp` configuration -/
meta def parse_dsimp_config : option pexpr → tactic (dsimp_config × format)
| none := pure ({}, "")
| (some cfg) :=
  do e ← to_expr ``(%%cfg : simp_config_ext),
     fmt ← has_to_tactic_format.to_tactic_format cfg,
     prod.mk <$> eval_expr dsimp_config e
             <*> struct.to_tactic_format cfg

/-- `same_result proof tac` runs tactic `tac` and checks if the proof
produced by `tac` is equivalent to `proof`. -/
meta def same_result (pr : proof_state) (tac : tactic unit) : tactic bool :=
do s ← get_proof_state_after tac,
   pure $ some pr = s

private meta def filter_simp_set_aux
  (tac : bool → list simp_arg_type → tactic unit)
  (args : list simp_arg_type) (pr : proof_state) :
  list simp_arg_type → list simp_arg_type →
  list simp_arg_type → tactic (list simp_arg_type × list simp_arg_type)
| [] ys ds := pure (ys.reverse, ds.reverse)
| (x :: xs) ys ds :=
  do b ← same_result pr (tac tt (args ++ xs ++ ys)),
     if b
       then filter_simp_set_aux xs ys (x:: ds)
       else filter_simp_set_aux xs (x :: ys) ds

declare_trace squeeze.deleted

/--
`filter_simp_set g call_simp user_args simp_args` returns `args'` such that, when calling
`call_simp tt /- only -/ args'` on the goal `g` (`g` is a meta var) we end up in the same
state as if we had called `call_simp ff (user_args ++ simp_args)` and removing any one
element of `args'` changes the resulting proof.
-/
meta def filter_simp_set
  (tac : bool → list simp_arg_type → tactic unit)
  (user_args simp_args : list simp_arg_type) : tactic (list simp_arg_type) :=
do some s ← get_proof_state_after (tac ff (user_args ++ simp_args)),
   (simp_args', _)  ← filter_simp_set_aux tac user_args s simp_args [] [],
   (user_args', ds) ← filter_simp_set_aux tac simp_args' s user_args [] [],
   when (is_trace_enabled_for `squeeze.deleted = tt ∧ ¬ ds.empty)
     trace!"deleting provided arguments {ds}",
   pure (user_args' ++ simp_args')

/-- make a `simp_arg_type` that references the name given as an argument -/
meta def name.to_simp_args (n : name) : tactic simp_arg_type :=
do e ← resolve_name' n, pure $ simp_arg_type.expr e

/-- tactic combinator to create a `simp`-like tactic that minimizes its
argument list.

 * `slow`: adds all rfl-lemmas from the environment to the initial list (this is a slower but more
           accurate strategy)
 * `no_dflt`: did the user use the `only` keyword?
 * `args`:    list of `simp` arguments
 * `tac`:     how to invoke the underlying `simp` tactic

-/
meta def squeeze_simp_core
  (slow no_dflt : bool) (args : list simp_arg_type)
  (tac : Π (no_dflt : bool) (args : list simp_arg_type), tactic unit)
  (mk_suggestion : list simp_arg_type → tactic unit) : tactic unit :=
do v ← target >>= mk_meta_var,
   args ← if slow then do
     simp_set ← attribute.get_instances `simp,
     simp_set ← simp_set.mfilter $ has_attribute' `_refl_lemma,
     simp_set ← simp_set.mmap $ resolve_name' >=> pure ∘ simp_arg_type.expr,
     pure $ args ++ simp_set
   else pure args,
   g ← retrieve $ do
   { g ← main_goal,
     tac no_dflt args,
     instantiate_mvars g },
   let vs := g.list_constant,
   vs ← vs.mfilter is_simp_lemma,
   vs ← vs.mmap strip_prefix,
   vs ← vs.to_list.mmap name.to_simp_args,
   with_local_goals' [v] (filter_simp_set tac args vs)
     >>= mk_suggestion,
   tac no_dflt args

namespace interactive

attribute [derive decidable_eq] simp_arg_type

/-- Turn a `simp_arg_type` into a string. -/
meta instance simp_arg_type.has_to_string : has_to_string simp_arg_type :=
⟨λ a, match a with
| simp_arg_type.all_hyps := "*"
| (simp_arg_type.except n) := "-" ++ to_string n
| (simp_arg_type.expr e) := to_string e
| (simp_arg_type.symm_expr e) := "←" ++ to_string e
end⟩

/-- combinator meant to aggregate the suggestions issued by multiple calls
of `squeeze_simp` (due, for instance, to `;`).

Can be used as:

```lean
example {α β} (xs ys : list α) (f : α → β) :
  (xs ++ ys.tail).map f = xs.map f ∧ (xs.tail.map f).length = xs.length :=
begin
  have : xs = ys, admit,
  squeeze_scope
  { split; squeeze_simp, -- `squeeze_simp` is run twice, the first one requires
                         -- `list.map_append` and the second one
                         -- `[list.length_map, list.length_tail]`
                         -- prints only one message and combine the suggestions:
                         -- > Try this: simp only [list.length_map, list.length_tail, list.map_append]
    squeeze_simp [this]  -- `squeeze_simp` is run only once
                         -- prints:
                         -- > Try this: simp only [this]
 },
end
```

-/
meta def squeeze_scope (tac : itactic) : tactic unit :=
do none ← squeeze_loc_attr.get_param ``squeeze_loc_attr_carrier | pure (),
   squeeze_loc_attr.set ``squeeze_loc_attr_carrier (some []) ff,
   finally tac $ do
     some xs ← squeeze_loc_attr.get_param ``squeeze_loc_attr_carrier | fail "invalid state",
     let m := native.rb_lmap.of_list xs,
     squeeze_loc_attr.set ``squeeze_loc_attr_carrier none ff,
     m.to_list.reverse.mmap' $ λ ⟨p,suggs⟩, do
       { let ⟨pre,_,post⟩ := suggs.head,
         let suggs : list (list simp_arg_type) := suggs.map $ prod.fst ∘ prod.snd,
         mk_suggestion p pre post (suggs.foldl list.union []) tt, pure () }

/--
`squeeze_simp`, `squeeze_simpa` and `squeeze_dsimp` perform the same
task with the difference that `squeeze_simp` relates to `simp` while
`squeeze_simpa` relates to `simpa` and `squeeze_dsimp` relates to
`dsimp`. The following applies to `squeeze_simp`, `squeeze_simpa` and
`squeeze_dsimp`.

`squeeze_simp` behaves like `simp` (including all its arguments)
and prints a `simp only` invocation to skip the search through the
`simp` lemma list.

For instance, the following is easily solved with `simp`:

```lean
example : 0 + 1 = 1 + 0 := by simp
```

To guide the proof search and speed it up, we may replace `simp`
with `squeeze_simp`:

```lean
example : 0 + 1 = 1 + 0 := by squeeze_simp
-- prints:
-- Try this: simp only [add_zero, eq_self_iff_true, zero_add]
```

`squeeze_simp` suggests a replacement which we can use instead of
`squeeze_simp`.

```lean
example : 0 + 1 = 1 + 0 := by simp only [add_zero, eq_self_iff_true, zero_add]
```

`squeeze_simp only` prints nothing as it already skips the `simp` list.

This tactic is useful for speeding up the compilation of a complete file.
Steps:

   1. search and replace ` simp` with ` squeeze_simp` (the space helps avoid the
      replacement of `simp` in `@[simp]`) throughout the file.
   2. Starting at the beginning of the file, go to each printout in turn, copy
      the suggestion in place of `squeeze_simp`.
   3. after all the suggestions were applied, search and replace `squeeze_simp` with
      `simp` to remove the occurrences of `squeeze_simp` that did not produce a suggestion.

Known limitation(s):
  * in cases where `squeeze_simp` is used after a `;` (e.g. `cases x; squeeze_simp`),
    `squeeze_simp` will produce as many suggestions as the number of goals it is applied to.
    It is likely that none of the suggestion is a good replacement but they can all be
    combined by concatenating their list of lemmas. `squeeze_scope` can be used to
    combine the suggestions: `by squeeze_scope { cases x; squeeze_simp }`
  * sometimes, `simp` lemmas are also `_refl_lemma` and they can be used without appearing in the
    resulting proof. `squeeze_simp` won't know to try that lemma unless it is called as
    `squeeze_simp?`
-/
meta def squeeze_simp
  (key : parse cur_pos)
  (slow_and_accurate : parse (tk "?")?)
  (use_iota_eqn : parse (tk "!")?) (no_dflt : parse only_flag) (hs : parse simp_arg_list)
  (attr_names : parse with_ident_list) (locat : parse location)
  (cfg : parse struct_inst?) : tactic unit :=
do (cfg',c) ← parse_config cfg,
   squeeze_simp_core slow_and_accurate.is_some no_dflt hs
     (λ l_no_dft l_args, simp use_iota_eqn none l_no_dft l_args attr_names locat cfg')
     (λ args,
        let use_iota_eqn := if use_iota_eqn.is_some then "!" else "",
            attrs := if attr_names.empty then ""
                     else string.join (list.intersperse " " (" with" :: attr_names.map to_string)),
            loc := loc.to_string locat in
        mk_suggestion (key.move_left 1)
          sformat!"Try this: simp{use_iota_eqn} only"
          sformat!"{attrs}{loc}{c}" args)

/-- see `squeeze_simp` -/
meta def squeeze_simpa
  (key : parse cur_pos)
  (slow_and_accurate : parse (tk "?")?)
  (use_iota_eqn : parse (tk "!")?) (no_dflt : parse only_flag) (hs : parse simp_arg_list)
  (attr_names : parse with_ident_list) (tgt : parse (tk "using" *> texpr)?)
  (cfg : parse struct_inst?) : tactic unit :=
do (cfg',c) ← parse_config cfg,
   tgt' ← traverse (λ t, do t ← to_expr t >>= pp,
                            pure format!" using {t}") tgt,
   squeeze_simp_core slow_and_accurate.is_some no_dflt hs
     (λ l_no_dft l_args, simpa use_iota_eqn none l_no_dft l_args attr_names tgt cfg')
     (λ args,
        let use_iota_eqn := if use_iota_eqn.is_some then "!" else "",
            attrs := if attr_names.empty then ""
                     else string.join (list.intersperse " " (" with" :: attr_names.map to_string)),
            tgt' := tgt'.get_or_else "" in
        mk_suggestion (key.move_left 1)
          sformat!"Try this: simpa{use_iota_eqn} only"
          sformat!"{attrs}{tgt'}{c}" args)

/-- `squeeze_dsimp` behaves like `dsimp` (including all its arguments)
and prints a `dsimp only` invocation to skip the search through the
`simp` lemma list. See the doc string of `squeeze_simp` for examples.
 -/
meta def squeeze_dsimp
  (key : parse cur_pos)
  (slow_and_accurate : parse (tk "?")?)
  (use_iota_eqn : parse (tk "!")?)
  (no_dflt : parse only_flag) (hs : parse simp_arg_list)
  (attr_names : parse with_ident_list) (locat : parse location)
  (cfg : parse struct_inst?) : tactic unit :=
do (cfg',c) ← parse_dsimp_config cfg,
   squeeze_simp_core slow_and_accurate.is_some no_dflt hs
     (λ l_no_dft l_args, dsimp l_no_dft l_args attr_names locat cfg')
     (λ args,
        let use_iota_eqn := if use_iota_eqn.is_some then "!" else "",
            attrs := if attr_names.empty then ""
                     else string.join (list.intersperse " " (" with" :: attr_names.map to_string)),
            loc := loc.to_string locat in
        mk_suggestion (key.move_left 1)
          sformat!"Try this: dsimp{use_iota_eqn} only"
          sformat!"{attrs}{loc}{c}" args)

end interactive
end tactic

open tactic.interactive
add_tactic_doc
{ name       := "squeeze_simp / squeeze_simpa / squeeze_dsimp / squeeze_scope",
  category   := doc_category.tactic,
  decl_names :=
   [``squeeze_simp,
    ``squeeze_dsimp,
    ``squeeze_simpa,
    ``squeeze_scope],
  tags       := ["simplification", "Try this"],
  inherit_description_from := ``squeeze_simp }


/-
Copyright (c) 2021 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import Lean

/-!
# Defines `#time` command.

Time the elaboration of a command, and print the result (in milliseconds).
-/

section
open Lean Elab Command

syntax (name := timeCmd)  "#time " command : command

/--
Time the elaboration of a command, and print the result (in milliseconds).

Example usage:
```
set_option maxRecDepth 100000 in
#time example : (List.range 500).length = 500 := rfl
```
-/
@[command_elab timeCmd] def timeCmdElab : CommandElab
  | `(#time%$tk $stx:command) => do
    let start ← IO.monoMsNow
    elabCommand stx
    logInfoAt tk m!"time: {(← IO.monoMsNow) - start}ms"
  | _ => throwUnsupportedSyntax

end


import LeanForeign.S
import Lean
def main : IO Unit := do
  IO.println (mkS 10 20 "hello").addXY
  IO.println (mkS 10 20 "hello").string
  appendToGlobalS "foo"
  appendToGlobalS "bla"
  getGlobalString >>= IO.println
  updateGlobalS (mkS 0 0 "world")
  getGlobalString >>= IO.println
  pure ()

open Lean Elab Command Term Meta

-- syntax (name := mc1) "#mc1" : command
-- @[commandElab mc1]
-- def mc1Impl : CommandElab

elab "#findCElab " c:command : command => do
  let macroRes ← liftMacroM <| expandMacroImpl? (←getEnv) c
  -- because of MonadLift, IO monad also available
  let _c ← (pure: _ → IO _) Lean.Syntax.missing
  let _a ← liftIO do
      IO.println "a"
      let _b := 12
      (pure: _) Lean.Syntax.missing
  match macroRes with
  | some (name, _) => logInfo s!"Next step is a macro: {name.toString}"
  | none =>
    let kind := c.raw.getKind
    let elabs := commandElabAttribute.getEntries (←getEnv) kind
    match elabs with
    | [] => logInfo s!"There is no elaborators for your syntax, lools like its bad"
    | _ => logInfo s!"your syntx may be elaborated by: {elabs.map (fun el => el.declName.toString)}"

#findCElab def lala := 12
#findCElab example : 1 = 1 := rfl

def divide (x :Float ) (y :Float):ExceptT String Id Float :=
  if y ==0 then
    throw "can't divide by zero"
  else
    pure (x /y)
#eval divide 8 0

def lt (x : Except String Float):
  StateT Nat (Except String) Float := (monadLift : _) x

#print lt

syntax (name := myterm1) "myterm 1" : term

def mytermValues := [1,3]

@[termElab myterm1]
def myTerm1Impl : TermElab := fun stx type? =>
  mkAppM ``List.get! #[mkConst ``mytermValues, mkNatLit 0]
#eval myterm 1

def sss := "∀a b, a → b → a ∧ b"
elab "myterm 2" : term => do
  let env ← getEnv
  let _a ← (pure:_ → IO _) "a"
  let parsedSyntax ← match Lean.Parser.runParserCategory env `term sss with
                      | Except.ok stx => pure stx
                      | Except.error errmsg => throwError errmsg
  logInfo s!"{parsedSyntax}"
  let prop ← elabTerm parsedSyntax none-- (mkConst `Lean.Prop)
  logInfo s!"hi:{prop}"
  pure prop
  -- logInfo s!"{prop}"
  -- mkAppM ``List.get! #[mkConst ``mytermValues, mkNatLit 1]

def hahna: myterm 2 := fun {a b : Prop}(ha:a) (hb:b) => And.intro ha hb -- (⟨ha,hb⟩:a∧b)


macro x:ident noWs "(" ys:term,* ")" : term => `($x $ys*)

#check id(1)

macro "foo" &"only" : tactic => `(tactic| trivial)

example : True := by foo only


import for_mathlib.snake_lemma3
import for_mathlib.les_homology
import for_mathlib.snake_lemma_naturality

noncomputable theory

open category_theory category_theory.limits

namespace category_theory

section

local attribute [-instance] category_theory.prod

@[elab_as_eliminator]
lemma preorder_prod_induction {C D : Type*} [preorder C] [preorder D]
  {motive : Π ⦃i j : C × D⦄ (f : i ⟶ j), Prop}
  (comp : ∀ {i j k : C × D} (f : i ⟶ j) (g : j ⟶ k), motive f → motive g → motive (f ≫ g))
  (H1 : ∀ (i : C) {j k : D} (f : j ≤ k), @motive (i,j) (i,k) (hom_of_le $ ⟨le_rfl, f⟩))
  (H2 : ∀ {i j : C} (k : D) (f : i ≤ j), @motive (i,k) (j,k) (hom_of_le $ ⟨f, le_rfl⟩))
  ⦃i j : C × D⦄ (f : i ⟶ j) : motive f :=
begin
  cases i with i1 i2, cases j with j1 j2,
  convert comp _ _ (H1 i1 f.le.2) (H2 j2 f.le.1),
end

end

variables {C D : Type*} [category C] [category D]

@[elab_as_eliminator]
lemma prod_induction
  {motive : Π ⦃i j : C × D⦄ (f : i ⟶ j), Prop}
  (comp : ∀ {i j k : C × D} (f : i ⟶ j) (g : j ⟶ k), motive f → motive g → motive (f ≫ g))
  (H1 : ∀ (i : C) {j k : D} (f : j ⟶ k), @motive (i,j) (i,k) (𝟙 i, f))
  (H2 : ∀ {i j : C} (k : D) (f : i ⟶ j), @motive (i,k) (j,k) (f, 𝟙 k))
  ⦃i j : C × D⦄ (f : i ⟶ j) : motive f :=
begin
  let f1 : (i.1, i.2) ⟶ (i.1, j.2) := (𝟙 i.1, f.2),
  let f2 : (i.1, j.2) ⟶ (j.1, j.2) := (f.1, 𝟙 j.2),
  have hf : f = f1 ≫ f2,
  { ext; simp only [prod_comp_fst, prod_comp_snd, category.id_comp, category.comp_id], },
  rw hf, cases i, cases j,
  apply comp; apply_assumption,
end

@[elab_as_eliminator]
lemma fin_induction (n : ℕ)
  {motive : Π ⦃i j : fin (n+1)⦄ (f : i ≤ j), Prop}
  (id : ∀ i, motive (le_refl i))
  (comp : ∀ {i j k : fin (n+1)} (f : i ≤ j) (g : j ≤ k), motive f → motive g → motive (f.trans g : i ≤ k))
  (Hsucc : ∀ (i : fin n), @motive i.cast_succ i.succ (le_of_lt $ by { rw fin.cast_succ_lt_iff_succ_le }))
  ⦃i j : fin (n+1)⦄ (f : i ≤ j) : motive f :=
begin
  revert f,
  refine fin.induction_on j _ _; clear j,
  { intro f, have hi : i = 0, { erw eq_bot_iff, exact f }, subst i, convert id _, },
  { intros j IH f,
    obtain (hij|rfl|hij) := lt_trichotomy i j.succ,
    { rw ← fin.le_cast_succ_iff at hij,
      convert comp _ _ (IH hij) (Hsucc j), },
    { convert id _, },
    { exact (f.not_lt hij).elim } }
end

end category_theory

variables {C 𝓐 : Type*} [category C] [category 𝓐] [abelian 𝓐]

namespace homological_complex

variables {ι : Type*} {c : complex_shape ι}

local notation x `⟶[`D`]` y := D.map (snake_diagram.hom x y)

def cast_horizontal (i : fin 4) (j : fin 2) : snake_diagram := (i,j.cast_succ)
def cast_vertical (i : fin 3) (j : fin 3) : snake_diagram := (i.cast_succ,j)
def succ_horizontal (i : fin 4) (j : fin 2) : snake_diagram := (i, j.succ)
def succ_vertical (i : fin 3) (j : fin 3) : snake_diagram := (i.succ,j)
def to_succ_horizontal (i : fin 4) (j : fin 2) :
  cast_horizontal i j ⟶ succ_horizontal i j := snake_diagram.hom _ _
def to_succ_vertical ( i : fin 3) (j : fin 3) :
  cast_vertical i j ⟶ succ_vertical i j := snake_diagram.hom _ _

lemma snake_diagram_induction
  {motive : Π ⦃i j : snake_diagram⦄ (f : i ⟶ j), Prop}
  (id : ∀ i : snake_diagram, motive (𝟙 i))
  (comp : ∀ (i j k : snake_diagram) (f : i ⟶ j) (g : j ⟶ k),
    motive f → motive g → motive (f ≫ g))
  (succ_horizontal : ∀ (i : fin 4) (j : fin 2),
    motive (to_succ_horizontal i j))
  (succ_vertical : ∀ (i : fin 3) (j : fin 3),
    motive (to_succ_vertical i j)) ⦃i j : snake_diagram⦄ (f : i ⟶ j) : motive f :=
begin
  apply category_theory.preorder_prod_induction comp; clear f i j,
  { intros i,
    refine @category_theory.fin_induction 2
      (λ j k f, motive (hom_of_le $ (⟨le_refl i, f⟩ : (i,j) ≤ (i,k)))) _ _ _,
    { intros j, convert id _, },
    { intros i' j k f g hf hg, convert comp _ _ _ _ _ hf hg, },
    { intros j, convert succ_horizontal i j } },
  { intros i j k, revert i j,
    refine @category_theory.fin_induction 3
      (λ i j f, motive (hom_of_le $ (⟨f, le_refl k⟩ : (i,k) ≤ (j,k)))) _ _ _,
    { intros j, convert id _, },
    { intros i' j k f g hf hg, convert comp _ _ _ _ _ hf hg, },
    { intros i, convert succ_vertical i k } },
end

variables
  {X Y Z : C ⥤ homological_complex 𝓐 c} (f : X ⟶ Y) (g : Y ⟶ Z)
  (H : ∀ c i, short_exact ((f.app c).f i) ((g.app c).f i))
  {c₁ c₂ : C} (φ : c₁ ⟶ c₂) (i j : ι) (hij : c.rel i j)

def mk_snake_diagram_nat_trans_app : Π (e : snake_diagram),
  (snake (f.app c₁) (g.app c₁) (H _) i j hij).snake_diagram.obj e ⟶
  (snake (f.app c₂) (g.app c₂) (H _) i j hij).snake_diagram.obj e
| ⟨⟨0,_⟩,⟨0,_⟩⟩ := (homology_functor _ _ i).map (X.map φ)
| ⟨⟨0,_⟩,⟨1,_⟩⟩ := (homology_functor _ _ i).map (Y.map φ)
| ⟨⟨0,_⟩,⟨2,_⟩⟩ := (homology_functor _ _ i).map (Z.map φ)
| ⟨⟨1,_⟩,⟨0,_⟩⟩ := (mod_boundaries_functor _).map (X.map φ)
| ⟨⟨1,_⟩,⟨1,_⟩⟩ := (mod_boundaries_functor _).map (Y.map φ)
| ⟨⟨1,_⟩,⟨2,_⟩⟩ := (mod_boundaries_functor _).map (Z.map φ)
| ⟨⟨2,_⟩,⟨0,_⟩⟩ := (cycles_functor _ _ _).map (X.map φ)
| ⟨⟨2,_⟩,⟨1,_⟩⟩ := (cycles_functor _ _ _).map (Y.map φ)
| ⟨⟨2,_⟩,⟨2,_⟩⟩ := (cycles_functor _ _ _).map (Z.map φ)
| ⟨⟨3,_⟩,⟨0,_⟩⟩ := (homology_functor _ _ j).map (X.map φ)
| ⟨⟨3,_⟩,⟨1,_⟩⟩ := (homology_functor _ _ j).map (Y.map φ)
| ⟨⟨3,_⟩,⟨2,_⟩⟩ := (homology_functor _ _ j).map (Z.map φ)
| _ := 0 -- impossible case
.

def mk_snake_diagram_nat_trans_hor :
  ∀ (a : fin 4) (b : fin 2),
  (snake (f.app c₁) (g.app c₁) (H _) i j hij).snake_diagram.map (to_succ_horizontal a b) ≫
    mk_snake_diagram_nat_trans_app f g H φ i j hij (succ_horizontal a b) =
    mk_snake_diagram_nat_trans_app f g H φ i j hij (cast_horizontal a b) ≫
    (snake (f.app c₂) (g.app c₂) (H _) i j hij).snake_diagram.map (to_succ_horizontal a b)
| ⟨0,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_f0, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨0,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_g0, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨0,_⟩ ⟨n+2,h⟩ := by { exfalso, rw [nat.succ_lt_succ_iff, nat.succ_lt_succ_iff] at h, exact nat.not_lt_zero n h }
| ⟨1,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_f1, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨1,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_g1, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨1,_⟩ ⟨n+2,h⟩ := by { exfalso, rw [nat.succ_lt_succ_iff, nat.succ_lt_succ_iff] at h, exact nat.not_lt_zero n h }
| ⟨2,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_f2, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨2,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_g2, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨2,_⟩ ⟨n+2,h⟩ := by { exfalso, rw [nat.succ_lt_succ_iff, nat.succ_lt_succ_iff] at h, exact nat.not_lt_zero n h }
| ⟨3,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_f3, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨3,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_g3, ← category_theory.functor.map_comp] }, rw nat_trans.naturality, }
| ⟨3,_⟩ ⟨n+2,h⟩ := by { exfalso, rw [nat.succ_lt_succ_iff, nat.succ_lt_succ_iff] at h, exact nat.not_lt_zero n h }
| ⟨n+4,h⟩ _   := by { exfalso, repeat { rw [nat.succ_lt_succ_iff] at h }, exact nat.not_lt_zero n h }
.

def mk_snake_diagram_nat_trans_ver :
  ∀ (a b : fin 3),
  (snake (f.app c₁) (g.app c₁) (H _) i j hij).snake_diagram.map (to_succ_vertical a b) ≫
    mk_snake_diagram_nat_trans_app f g H φ i j hij (succ_vertical a b) =
    mk_snake_diagram_nat_trans_app f g H φ i j hij (cast_vertical a b) ≫
    (snake (f.app c₂) (g.app c₂) (H _) i j hij).snake_diagram.map (to_succ_vertical a b)
| ⟨0,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_a0] }, erw nat_trans.naturality, refl }
| ⟨0,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_b0] }, erw nat_trans.naturality, refl }
| ⟨0,_⟩ ⟨2,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_c0] }, erw nat_trans.naturality, refl }
| ⟨0,_⟩ ⟨n+3,h⟩ := by { exfalso, repeat { rw [nat.succ_lt_succ_iff] at h }, exact nat.not_lt_zero n h }
| ⟨1,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_a1] }, erw nat_trans.naturality, refl }
| ⟨1,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_b1] }, erw nat_trans.naturality, refl }
| ⟨1,_⟩ ⟨2,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_c1] }, erw nat_trans.naturality, refl }
| ⟨1,_⟩ ⟨n+3,h⟩ := by { exfalso, repeat { rw [nat.succ_lt_succ_iff] at h }, exact nat.not_lt_zero n h }
| ⟨2,_⟩ ⟨0,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_a2] }, erw nat_trans.naturality, refl }
| ⟨2,_⟩ ⟨1,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_b2] }, erw nat_trans.naturality, refl }
| ⟨2,_⟩ ⟨2,_⟩ := by { repeat { erw [snake_diagram.mk_functor_map_c2] }, erw nat_trans.naturality, refl }
| ⟨2,_⟩ ⟨n+3,h⟩ := by { exfalso, repeat { rw [nat.succ_lt_succ_iff] at h }, exact nat.not_lt_zero n h }
| ⟨n+3,h⟩ _   := by { exfalso, repeat { rw [nat.succ_lt_succ_iff] at h }, exact nat.not_lt_zero n h }
.

-- TODO: Make a general construction, similar to `snake_diagram.mk_functor`
def mk_snake_diagram_nat_trans :
  (snake (f.app c₁) (g.app c₁) (H _) i j hij).snake_diagram ⟶
  (snake (f.app c₂) (g.app c₂) (H _) i j hij).snake_diagram :=
{ app := λ e, mk_snake_diagram_nat_trans_app f g H φ i j hij e,
  naturality' := begin
    apply snake_diagram_induction,
    { intro, simp only [category_theory.functor.map_id, category.id_comp, category.comp_id] },
    { intros i j k f g h1 h2, simp only [functor.map_comp, category.assoc, h2, reassoc_of h1] },
    { exact mk_snake_diagram_nat_trans_hor f g H φ i j hij },
    { exact mk_snake_diagram_nat_trans_ver f g H φ i j hij },
  end }

lemma δ_natural :
  δ (f.app c₁) (g.app c₁) (H _) i j hij ≫ (homology_functor _ _ j).map (X.map φ) =
    (homology_functor _ _ i).map (Z.map φ) ≫ δ (f.app c₂) (g.app c₂) (H _) i j hij :=
begin
  let η := mk_snake_diagram_nat_trans f g H φ i j hij,
  apply (snake_lemma.δ_natural η _ _).symm,
end

end homological_complex


import Lean.Elab

structure Bar where

structure Foo where
  foo₁ : Nat
  foo₂ : Nat
  bar  : Bar

def mkFoo₁ : Foo := {
--v textDocument/definition
  foo₁ := 1
--v textDocument/declaration
  foo₂ := 2
--v textDocument/typeDefinition
  bar := ⟨⟩
}

structure HandWrittenStruct where
  n : Nat

-- def HandWrittenStruct.n := fun | mk n => n

          --v textDocument/definition
def hws : HandWrittenStruct := {
--v textDocument/definition
  n := 3
}

            --v textDocument/declaration
def mkFoo₂ := mkFoo₁

syntax (name := elabTest) "test" : term

@[termElab elabTest] def elabElabTest : Lean.Elab.Term.TermElab := fun _ _ => do
  let stx ← `(2)
  Lean.Elab.Term.elabTerm stx none

     --v textDocument/declaration
#check test
     --^ textDocument/definition

def Baz (α : Type) := α

#check fun (b : Baz Nat) => b
                          --^ textDocument/typeDefinition

example : Nat :=
  let a := 1
--v textDocument/definition
  a + b
    --^ textDocument/definition
where
  b := 2


import Lib.Meta
import Lib.Tactic

namespace Lean.Elab.Tactic

open Lean.Elab.Tactic
open Lean Lean.Meta
open Lean.Elab
open Lean.Elab.Term
open Lean.PrettyPrinter.Delaborator.TopDownAnalyze
open Lean.Elab.Tactic

-- initialize registerTrace
initialize registerTraceClass `select.match.failure
initialize registerTraceClass `select.match.attempt
initialize registerTraceClass `select.match.success
initialize registerTraceClass `select.skipped

inductive Selector where
  | hyp (name : Name) (pattern : Expr)
  | goal (pattern : Expr)

def headZeta1 : Expr → Expr
| .mdata _ e _ => headZeta1 e
| .letE v t e e' _ => instantiateBVar [e] e'
| e => e

def clearLDecl (l : LocalDecl) : TacticM (Array Name) :=
withMainContext do
  -- println!"before"
  -- print_vars![mainGoal]
  let some l' ← tryTactic? <| liftMetaTactic1' (revert . #[l.fvarId])
    | pure #[]
  -- println!"reverted"
  let tgt ← getMainTarget
  -- println!"main target"
  liftMetaTactic1 (change . <| headZeta1 tgt)
  let l' := l'[1:].toArray
  -- println!"after"
  l'.mapM (λ v => do return (← getLocalDecl v).userName)

open Lean (Meta.ppExpr)
open Lean.Meta (ppExpr)

def elabHypSelector : Selector → SearchTacticM δ (Option (LocalDecl × LocalDecl))
| .hyp tag pat => do
  let lctx ← getLCtx
  let ls := λ _ : Unit =>
    lctx.decls.toList.filterMap <| Option.map (·.userName)
  let some h := (← SearchT.pick lctx.decls.toList) | failure
  trace[select.match.attempt]" trying {h.userName} with {← Meta.ppExpr pat}"
  unless ¬ h.isAuxDecl do
    trace[select.skipped]"skipping {h.userName} ({h.fvarId.name}) "
    failure
  let t := h.type
  unless (← liftM <| isDefEqAssigning t pat) do
    trace[select.match.failure]" tried {h.userName} with {← Meta.ppExpr pat}"
    failure
  trace[select.match.success]" tried {h.userName} with {← Meta.ppExpr pat}"
  liftMetaTactic1 (define . tag h.type <| mkFVar h.fvarId)
  let var ← liftMetaTactic1' (intro . tag)
  let var ← withMainContext (getLocalDecl var)
  return some (var, h)
| .goal pat => do
  let g ← getMainTarget
  unless (← liftM <| isDefEqAssigning g pat) do
    trace[select.match.failure]" tried goal with {← Meta.ppExpr pat}"
    failure
  return none

-- #exit

def elabHypSelectors (sel : Array Selector) (tac : Option Syntax) :
  SearchTacticM δ (Array (LocalDecl × LocalDecl)) := do
let mappings ← sel.filterMapM elabHypSelector
match tac with
| none => pure ()
| some tac => evalTactic tac
return mappings

def elabSelectors (sel : Array Selector) (tac : Option Syntax) :
  TacticM (Array (LocalDecl × LocalDecl)) := do
elabHypSelectors sel tac |>.run

declare_syntax_cat asm_select
declare_syntax_cat asm_selectors

syntax (name := hyp_selector)
  colGt withPosition("hyp " ident " : " colGt term) ppLine : asm_select
syntax (name := goal_selector)
  colGt withPosition("goal " " : " colGt term) ppLine : asm_select
syntax (name := many_selectors) (asm_select)+ : asm_selectors
syntax (name := one_selector) group(ident " : " colGt term) : asm_selectors

syntax withPosition("select " asm_selectors (colGt tacticSeq)?) : tactic

elab "have! " d:haveDecl : tactic => do
  let Hid := d[0][0][0].getId
  let decl ← getLocalDeclFromUserName Hid
  evalTactic (← `(tactic| have $d:haveDecl))
  liftMetaTactic1 (clear . decl.fvarId)

elab "have? " d:haveDecl : tactic => do
  evalTactic (← `(tactic| have $d:haveDecl))
  let Hid := d[0][0][0].getId
  withMainContext do
  let ldecl ← getLocalDeclFromUserName Hid
  for h in ← getLCtx do
    if h.fvarId != ldecl.fvarId && (← isDefEq h.type ldecl.type) then
      throwError "Local decl {h.userName} already has type {ldecl.type}"

def parseSelector (xs : Syntax) : TacticM (Array Selector) := do
if xs.getKind == ``one_selector then
  let xs := xs[0]
  let tag := xs[0].getId
  let colon := xs[1]
  let term := xs[2]
  let pat ← Term.elabTerm term none
  return #[ Selector.hyp tag pat ]
else
  let mut r := #[]
  for x in xs[0].getArgs do
    if x.getKind == ``goal_selector then
      let kw    := x[0]
      let colon := x[1]
      let term  := x[2]
      let pat ← Term.elabTerm term none
      r := r.push <| Selector.goal pat
    else
      let kw    := x[0]
      let tag   := x[1].getId
      let colon := x[2]
      let term  := x[3]
      let pat ← Term.elabTerm term none
      r := r.push <| Selector.hyp tag pat
  return r

elab_rules : tactic
| `(tactic| select $xs $[$tac:tacticSeq]?) =>
Lean.MonadQuotation.withFreshMacroScope do
withMainContext do
    let sels ← parseSelector xs
    let hs ← elabSelectors sels tac
    if tac.isSome then
      withMainContext do
      let mut vs := #[]
      for (v, _) in hs.reverse do
        let vs' ← clearLDecl v
        vs := vs ++ vs'.reverse
      withMainContext do
      for v in vs.reverse do
        discard <| liftMetaTactic1' (intro . v)

example (a b c x y z : Nat)
  (a₀ : a ≤ b)
  (a₁ : b ≤ c)
  (h₁ : y ≤ z)
  (h₂ : x ≤ z)
  (h₀ : x ≤ y)
  -- (hh : x ≤ w)
  (h₃ : z ≤ w)
 : x ≤ w := by
-- repeat
repeat
  select
    hyp h : ?x ≤ ?y
    hyp h' : ?y ≤ ?z
    -- goal : ?x ≤ ?z
    have? h₃ := Nat.le_trans h h'
skip
admit
-- have : h = h := rfl
-- have : h' = h' := rfl
  -- clear h
  -- revert h'
  -- have? h₄ := rfl
-- intro
-- select h'' : _ ≤ ?x

-- skip