/-
Copyright (c) 2020 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import data.bool
import meta.rb_map
import tactic.core

/-!
# list_unused_decls

`#list_unused_decls` is a command used for theory development.
When writing a new theory one often tries
multiple variations of the same definitions: `foo`, `foo'`, `foo₂`,
`foo₃`, etc. Once the main definition or theorem has been written,
it's time to clean up and the file can contain a lot of dead code.
Mark the main declarations with `@[main_declaration]` and
`#list_unused_decls` will show the declarations in the file
that are not needed to define the main declarations.

Some of the so-called "unused" declarations may turn out to be useful
after all. The oversight can be corrected by marking those as
`@[main_declaration]`. `#list_unused_decls` will revise the list of
unused declarations. By default, the list of unused declarations will
not include any dependency of the main declarations.

The `@[main_declaration]` attribute should be removed before submitting
code to mathlib as it is merely a tool for cleaning up a module.
-/

namespace tactic

/-- Attribute `main_declaration` is used to mark declarations that are featured
in the current file.  Then, the `#list_unused_decls` command can be used to
list the declaration present in the file that are not used by the main
declarations of the file. -/
@[user_attribute]
meta def main_declaration_attr : user_attribute :=
{ name := `main_declaration,
  descr := "tag essential declarations to help identify unused definitions" }

/-- `update_unsed_decls_list n m` removes from the map of unneeded declarations those
referenced by declaration named `n` which is considerred to be a
main declaration -/
private meta def update_unsed_decls_list :
  name → name_map declaration → tactic (name_map declaration)
| n m :=
  do d ← get_decl n,
     if m.contains n then do
       let m := m.erase n,
       let ns := d.value.list_constant.union d.type.list_constant,
       ns.mfold m update_unsed_decls_list
     else pure m

/-- In the current file, list all the declaration that are not marked as `@[main_declaration]` and
that are not referenced by such declarations -/
meta def all_unused (fs : list (option string)) : tactic (name_map declaration) :=
do ds ← get_decls_from fs,
   ls ← ds.keys.mfilter (succeeds ∘ user_attribute.get_param_untyped main_declaration_attr),
   ds ← ls.mfoldl (flip update_unsed_decls_list) ds,
   ds.mfilter $ λ n d, do
     e ← get_env,
     return $ !d.is_auto_or_internal e

/-- expecting a string literal (e.g. `"src/tactic/find_unused.lean"`)
-/
meta def parse_file_name (fn : pexpr) : tactic (option string) :=
some <$> (to_expr fn >>= eval_expr string) <|> fail "expecting: \"src/dir/file-name\""

setup_tactic_parser

/-- The command `#list_unused_decls` lists the declarations that that
are not used the main features of the present file. The main features
of a file are taken as the declaration tagged with
`@[main_declaration]`.

A list of files can be given to `#list_unused_decls` as follows:

```lean
#list_unused_decls ["src/tactic/core.lean","src/tactic/interactive.lean"]
```

They are given in a list that contains file names written as Lean
strings. With a list of files, the declarations from all those files
in addition to the declarations above `#list_unused_decls` in the
current file will be considered and their interdependencies will be
analyzed to see which declarations are unused by declarations marked
as `@[main_declaration]`. The files listed must be imported by the
current file. The path of the file names is expected to be relative to
the root of the project (i.e. the location of `leanpkg.toml` when it
is present).

Neither `#list_unused_decls` nor `@[main_declaration]` should appear
in a finished mathlib development. -/
@[user_command]
meta def unused_decls_cmd (_ : parse $ tk "#list_unused_decls") : lean.parser unit :=
do fs ← pexpr_list,
   show tactic unit, from
   do fs ← fs.mmap parse_file_name,
      ds ← all_unused $ none :: fs,
      ds.to_list.mmap' $ λ ⟨n,_⟩, trace!"#print {n}"

add_tactic_doc
{ name                     := "#list_unused_decls",
  category                 := doc_category.cmd,
  decl_names               := [`tactic.unused_decls_cmd],
  tags                     := ["debugging"] }

end tactic


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

/-!
# `simp?` tactic

The `simp?` tactic is a simple wrapper around the simp with trace behavior implemented in core.
-/
namespace Std.Tactic
open Lean Elab Parser Tactic

/-- The common arguments of `simp?` and `simp?!`. -/
syntax simpTraceArgsRest := (config)? (discharger)? (&" only")? (simpArgs)? (ppSpace location)?

/--
`simp?` takes the same arguments as `simp`, but reports an equivalent call to `simp only`
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
```
example (x : Nat) : (if True then x + 2 else 3) = x + 2 := by
  simp? -- prints "Try this: simp only [ite_true]"
```

This command can also be used in `simp_all` and `dsimp`.
-/
syntax (name := simpTrace) "simp?" "!"? simpTraceArgsRest : tactic

@[inherit_doc simpTrace]
macro tk:"simp?!" rest:simpTraceArgsRest : tactic => `(tactic| simp?%$tk ! $rest)

macro_rules
  | `(tactic| simp?%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]? $(loc)?) =>
    `(tactic| set_option tactic.simp.trace true in
      simp%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]? $(loc)?)
  | `(tactic| simp?%$tk ! $(config)? $(discharger)? $[only%$o]? $[[$args,*]]? $(loc)?) =>
    `(tactic| set_option tactic.simp.trace true in
      simp!%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]? $(loc)?)

/-- The common arguments of `simp_all?` and `simp_all?!`. -/
syntax simpAllTraceArgsRest := (config)? (discharger)? (&" only")? (dsimpArgs)?

@[inherit_doc simpTrace]
syntax (name := simpAllTrace) "simp_all?" "!"? simpAllTraceArgsRest : tactic

@[inherit_doc simpTrace]
macro tk:"simp_all?!" rest:simpAllTraceArgsRest : tactic => `(tactic| simp_all?%$tk ! $rest)

macro_rules
  | `(tactic| simp_all?%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]?) =>
    `(tactic| set_option tactic.simp.trace true in
      simp_all%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]?)
  | `(tactic| simp_all?%$tk ! $(config)? $(discharger)? $[only%$o]? $[[$args,*]]?) =>
    `(tactic| set_option tactic.simp.trace true in
      simp_all!%$tk $(config)? $(discharger)? $[only%$o]? $[[$args,*]]?)

/-- The common arguments of `dsimp?` and `dsimp?!`. -/
syntax dsimpTraceArgsRest := (config)? (&" only")? (dsimpArgs)? (ppSpace location)?

@[inherit_doc simpTrace]
syntax (name := dsimpTrace) "dsimp?" "!"? dsimpTraceArgsRest : tactic

@[inherit_doc simpTrace]
macro tk:"dsimp?!" rest:dsimpTraceArgsRest : tactic => `(tactic| dsimp?%$tk ! $rest)

macro_rules
  | `(tactic| dsimp?%$tk $(config)? $[only%$o]? $[[$args,*]]? $(loc)?) =>
    `(tactic| set_option tactic.simp.trace true in
      dsimp%$tk $(config)? $[only%$o]? $[[$args,*]]? $(loc)?)
  | `(tactic| dsimp?%$tk ! $(config)? $[only%$o]? $[[$args,*]]? $(loc)?) =>
    `(tactic| set_option tactic.simp.trace true in
      dsimp!%$tk $(config)? $[only%$o]? $[[$args,*]]? $(loc)?)


/-
File: signature_recover_public_key_ec_mul_inner_soundness.lean

Autogenerated file.
-/
import starkware.cairo.lean.semantics.soundness.hoare
import .signature_recover_public_key_code
import ..signature_recover_public_key_spec
import .signature_recover_public_key_fast_ec_add_soundness
import .signature_recover_public_key_ec_double_soundness
open tactic

open starkware.cairo.common.cairo_secp.ec
open starkware.cairo.common.cairo_secp.bigint
open starkware.cairo.common.cairo_secp.field

variables {F : Type} [field F] [decidable_eq F] [prelude_hyps F]
variable  mem : F → F
variable  σ : register_state F

/- starkware.cairo.common.cairo_secp.ec.ec_mul_inner autogenerated soundness theorem -/

theorem auto_sound_ec_mul_inner
    -- arguments
    (range_check_ptr : F) (point : EcPoint F) (scalar m : F)
    -- code is in memory at σ.pc
    (h_mem : mem_at mem code_ec_mul_inner σ.pc)
    -- all dependencies are in memory
    (h_mem_4 : mem_at mem code_nondet_bigint3 (σ.pc  - 460))
    (h_mem_5 : mem_at mem code_unreduced_mul (σ.pc  - 448))
    (h_mem_6 : mem_at mem code_unreduced_sqr (σ.pc  - 428))
    (h_mem_7 : mem_at mem code_verify_zero (σ.pc  - 412))
    (h_mem_12 : mem_at mem code_compute_doubling_slope (σ.pc  - 284))
    (h_mem_13 : mem_at mem code_compute_slope (σ.pc  - 240))
    (h_mem_14 : mem_at mem code_ec_double (σ.pc  - 216))
    (h_mem_15 : mem_at mem code_fast_ec_add (σ.pc  - 143))
    -- input arguments on the stack
    (hin_range_check_ptr : range_check_ptr = mem (σ.fp - 11))
    (hin_point : point = cast_EcPoint mem (σ.fp - 10))
    (hin_scalar : scalar = mem (σ.fp - 4))
    (hin_m : m = mem (σ.fp - 3))
    -- conclusion
  : ensures_ret mem σ (λ κ τ,
      ∃ μ ≤ κ, rc_ensures mem (rc_bound F) μ (mem (σ.fp - 11)) (mem $ τ.ap - 13)
        (spec_ec_mul_inner mem κ range_check_ptr point scalar m (mem (τ.ap - 13)) (cast_EcPoint mem (τ.ap - 12)) (cast_EcPoint mem (τ.ap - 6)))) :=
begin
  apply ensures_of_ensuresb, intro νbound,
  revert σ range_check_ptr point scalar m h_mem h_mem_4 h_mem_5 h_mem_6 h_mem_7 h_mem_12 h_mem_13 h_mem_14 h_mem_15 hin_range_check_ptr hin_point hin_scalar hin_m,
  induction νbound with νbound νih,
  { intros, intros n nlt, apply absurd nlt (nat.not_lt_zero _) },
  intros σ range_check_ptr point scalar m h_mem h_mem_4 h_mem_5 h_mem_6 h_mem_7 h_mem_12 h_mem_13 h_mem_14 h_mem_15 hin_range_check_ptr hin_point hin_scalar hin_m,
  dsimp at νih,
  have h_mem_rec := h_mem,
  unpack_memory code_ec_mul_inner at h_mem with ⟨hpc0, hpc1, hpc2, hpc3, hpc4, hpc5, hpc6, hpc7, hpc8, hpc9, hpc10, hpc11, hpc12, hpc13, hpc14, hpc15, hpc16, hpc17, hpc18, hpc19, hpc20, hpc21, hpc22, hpc23, hpc24, hpc25, hpc26, hpc27, hpc28, hpc29, hpc30, hpc31, hpc32, hpc33, hpc34, hpc35, hpc36, hpc37, hpc38, hpc39, hpc40, hpc41, hpc42, hpc43, hpc44, hpc45, hpc46, hpc47, hpc48, hpc49, hpc50, hpc51, hpc52, hpc53, hpc54, hpc55, hpc56, hpc57, hpc58, hpc59, hpc60, hpc61, hpc62, hpc63, hpc64, hpc65, hpc66, hpc67, hpc68, hpc69, hpc70, hpc71, hpc72, hpc73, hpc74, hpc75, hpc76, hpc77, hpc78, hpc79, hpc80, hpc81, hpc82, hpc83, hpc84, hpc85, hpc86, hpc87, hpc88, hpc89, hpc90, hpc91, hpc92, hpc93, hpc94, hpc95, hpc96, hpc97, hpc98, hpc99, hpc100⟩,
  -- if statement
  step_jnz hpc0 hpc1 with hcond hcond,
  {
    -- if: positive branch
    have a0 : m = 0, {
      try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m] },
      try { dsimp [cast_EcPoint, cast_BigInt3] },
      try { arith_simps }, try { simp only [hcond] },
      try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },
    },
    try { dsimp at a0 }, try { arith_simps at a0 },
    clear hcond,
    -- assert eq
    step_assert_eq hpc2 hpc3 with temp0,
    have a2: scalar = 0, {
      apply assert_eq_reduction temp0,
      try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m] },
      try { dsimp [cast_EcPoint, cast_BigInt3] },
      try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },
    },
    try { dsimp at a2 }, try { arith_simps at a2 },
    clear temp0,
    -- let
    generalize' hl_rev_ZERO_POINT: ({
      x := { d0 := 0, d1 := 0, d2 := 0 },
      y := { d0 := 0, d1 := 0, d2 := 0 }
    } : EcPoint F) = ZERO_POINT,
    have hl_ZERO_POINT := hl_rev_ZERO_POINT.symm, clear hl_rev_ZERO_POINT,
    try { dsimp at hl_ZERO_POINT }, try { arith_simps at hl_ZERO_POINT },
    -- return
    step_assert_eq hpc4 with hret0,
    step_assert_eq hpc5 with hret1,
    step_assert_eq hpc6 with hret2,
    step_assert_eq hpc7 with hret3,
    step_assert_eq hpc8 with hret4,
    step_assert_eq hpc9 with hret5,
    step_assert_eq hpc10 with hret6,
    step_assert_eq hpc11 hpc12 with hret7,
    step_assert_eq hpc13 hpc14 with hret8,
    step_assert_eq hpc15 hpc16 with hret9,
    step_assert_eq hpc17 hpc18 with hret10,
    step_assert_eq hpc19 hpc20 with hret11,
    step_assert_eq hpc21 hpc22 with hret12,
    step_ret hpc23,
    -- finish
    step_done, use_only [rfl, rfl],
    -- range check condition
    use_only (0+0), split,
    linarith [],
    split,
    { arith_simps, try { simp only [hret0 ,hret1 ,hret2 ,hret3 ,hret4 ,hret5 ,hret6 ,hret7 ,hret8 ,hret9 ,hret10 ,hret11 ,hret12] },
      try { arith_simps, refl <|> norm_cast }, try { refl } },
    intro rc_h_range_check_ptr, repeat { rw [add_assoc] at rc_h_range_check_ptr },
    have rc_h_range_check_ptr' := range_checked_add_right rc_h_range_check_ptr,
    -- Final Proof
    -- user-provided reduction
    suffices auto_spec: auto_spec_ec_mul_inner mem _ range_check_ptr point scalar m _ _ _,
    { apply sound_ec_mul_inner, apply auto_spec },
    -- prove the auto generated assertion
    dsimp [auto_spec_ec_mul_inner],
    try { norm_num1 }, try { arith_simps },
    left,
    use_only [a0],
    use_only [a2],
    use_only [ZERO_POINT, hl_ZERO_POINT],
    try { split, linarith },
    try { ensures_simps; try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, hl_ZERO_POINT] }, },
    try { dsimp [cast_EcPoint, cast_BigInt3] },
    try { arith_simps }, try { simp only [hret0, hret1, hret2, hret3, hret4, hret5, hret6, hret7, hret8, hret9, hret10, hret11, hret12] },
    try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },
  },
  {
    -- if: negative branch
    have a0 : m ≠ 0, {
      try { simp only [ne.def] },
      try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m] },
      try { dsimp [cast_EcPoint, cast_BigInt3] },
      try { arith_simps }, try { simp only [hcond] },
      try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },
    },
    try { dsimp at a0 }, try { arith_simps at a0 },
    clear hcond,
    -- ap += 6
    step_advance_ap hpc24 hpc25,
    -- function call
    step_assert_eq hpc26 with arg0,
    step_assert_eq hpc27 with arg1,
    step_assert_eq hpc28 with arg2,
    step_assert_eq hpc29 with arg3,
    step_assert_eq hpc30 with arg4,
    step_assert_eq hpc31 with arg5,
    step_assert_eq hpc32 with arg6,
    step_sub hpc33 (auto_sound_ec_double mem _ range_check_ptr point _ _ _ _ _ _ _ _),
    { rw hpc34, norm_num2, exact h_mem_14 },
    { rw hpc34, norm_num2, exact h_mem_4 },
    { rw hpc34, norm_num2, exact h_mem_5 },
    { rw hpc34, norm_num2, exact h_mem_6 },
    { rw hpc34, norm_num2, exact h_mem_7 },
    { rw hpc34, norm_num2, exact h_mem_12 },
    { try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m] },
      try { dsimp [cast_EcPoint, cast_BigInt3] },
      try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6] },
      try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
    { try { ext } ; {
        try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
    intros κ_call35 ap35 h_call35,
    rcases h_call35 with ⟨rc_m35, rc_mle35, hl_range_check_ptr₁, h_call35⟩,
    generalize' hr_rev_range_check_ptr₁: mem (ap35 - 7) = range_check_ptr₁,
    have htv_range_check_ptr₁ := hr_rev_range_check_ptr₁.symm, clear hr_rev_range_check_ptr₁,
    generalize' hr_rev_double_point: cast_EcPoint mem (ap35 - 6) = double_point,
    simp only [hr_rev_double_point] at h_call35,
    have htv_double_point := hr_rev_double_point.symm, clear hr_rev_double_point,
    try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6] at hl_range_check_ptr₁ },
    rw [←htv_range_check_ptr₁, ←hin_range_check_ptr] at hl_range_check_ptr₁,
    try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6] at h_call35 },
    rw [hin_range_check_ptr] at h_call35,
    clear arg0 arg1 arg2 arg3 arg4 arg5 arg6,
    -- jnz
    apply of_register_state,
    intros regstate35 regstate35eq,
    have regstateapeq_a35 := congr_arg register_state.ap regstate35eq,
    try { dsimp at regstateapeq_a35 },
    step_jnz hpc35 hpc36 with a35 a35,
    {
      -- jnz: positive branch
      rw ←regstateapeq_a35 at a35,
      -- tail recursive function call
      step_assert_eq hpc37 with arg0,
      step_assert_eq hpc38 with arg1,
      step_assert_eq hpc39 with arg2,
      step_assert_eq hpc40 with arg3,
      step_assert_eq hpc41 with arg4,
      step_assert_eq hpc42 with arg5,
      step_assert_eq hpc43 with arg6,
      step_assert_eq hpc44 hpc45 with arg7,
      step_assert_eq hpc46 hpc47 with arg8,
      have h_δ37_c0 : ∀ x : F, x / (2 : ℤ) = x * (-1809251394333065606848661391547535052811553607665798349986546028067936010240 : ℤ),
      { intro x,  apply div_eq_mul_inv', apply PRIME.int_cast_mul_eq_one, rw [PRIME], try { simp_int_casts }, norm_num1 },
      have h_δ37_c0_fz : ∀ x : F, x / 2 = x / (2 : ℤ), { intro x, norm_cast },
      step_rec_sub hpc48 (νih _ range_check_ptr₁ double_point (scalar / (2 : ℤ)) (m - 1) _ _ _ _ _ _ _ _ _ _ _ _ _),
      { rw hpc49, norm_num, exact h_mem_rec },
      { rw hpc49, norm_num2, exact h_mem_4 },
      { rw hpc49, norm_num2, exact h_mem_5 },
      { rw hpc49, norm_num2, exact h_mem_6 },
      { rw hpc49, norm_num2, exact h_mem_7 },
      { rw hpc49, norm_num2, exact h_mem_12 },
      { rw hpc49, norm_num2, exact h_mem_13 },
      { rw hpc49, norm_num2, exact h_mem_14 },
      { rw hpc49, norm_num2, exact h_mem_15 },
      { try { simp only [h_δ37_c0_fz, h_δ37_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      { try { simp only [h_δ37_c0_fz, h_δ37_c0] }, try { ext } ; {
          try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
          try { dsimp [cast_EcPoint, cast_BigInt3] },
          try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8] },
          try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
      { try { simp only [h_δ37_c0_fz, h_δ37_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      { try { simp only [h_δ37_c0_fz, h_δ37_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      intros κ_call50 ap50 h_call50,
      rcases h_call50 with ⟨rc_m50, rc_mle50, hl_range_check_ptr₂, h_call50⟩,
      step_ret hpc50,
      generalize' hr_rev_range_check_ptr₂: mem (ap50 - 13) = range_check_ptr₂,
      have htv_range_check_ptr₂ := hr_rev_range_check_ptr₂.symm, clear hr_rev_range_check_ptr₂,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8] at hl_range_check_ptr₂ },
      rw [←htv_range_check_ptr₂, ←htv_range_check_ptr₁] at hl_range_check_ptr₂,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8] at h_call50 },
      rw [←htv_range_check_ptr₁, hl_range_check_ptr₁, hin_range_check_ptr] at h_call50,
      clear arg0 arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8,
      -- finish
      step_done, use_only [rfl, rfl],
      -- range check condition
      use_only (rc_m35+rc_m50+0+0), split,
      linarith [rc_mle35, rc_mle50],
      split,
      { arith_simps,
        rw [←htv_range_check_ptr₂, hl_range_check_ptr₂, hl_range_check_ptr₁, hin_range_check_ptr],
        try { arith_simps, refl <|> norm_cast }, try { refl } },
      intro rc_h_range_check_ptr, repeat { rw [add_assoc] at rc_h_range_check_ptr },
      have rc_h_range_check_ptr' := range_checked_add_right rc_h_range_check_ptr,
      -- Final Proof
      -- user-provided reduction
      suffices auto_spec: auto_spec_ec_mul_inner mem _ range_check_ptr point scalar m _ _ _,
      { apply sound_ec_mul_inner, apply auto_spec },
      -- prove the auto generated assertion
      dsimp [auto_spec_ec_mul_inner],
      try { norm_num1 }, try { arith_simps },
      right,
      use_only [a0],
      use_only [κ_call35],
      use_only [range_check_ptr₁],
      use_only [double_point],
      have rc_h_range_check_ptr₁ := range_checked_offset' rc_h_range_check_ptr,
      have rc_h_range_check_ptr₁' := range_checked_add_right rc_h_range_check_ptr₁, try { norm_cast at rc_h_range_check_ptr₁' },
      have spec35 := h_call35 rc_h_range_check_ptr',
      rw [←hin_range_check_ptr, ←htv_range_check_ptr₁] at spec35,
      try { dsimp at spec35, arith_simps at spec35 },
      use_only [spec35],
      use_only (mem regstate35.ap),
      left,
      use_only [a35],
      use_only [κ_call50],
      have rc_h_range_check_ptr₂ := range_checked_offset' rc_h_range_check_ptr₁,
      have rc_h_range_check_ptr₂' := range_checked_add_right rc_h_range_check_ptr₂, try { norm_cast at rc_h_range_check_ptr₂' },
      have spec50 := h_call50 rc_h_range_check_ptr₁',
      rw [←hin_range_check_ptr, ←hl_range_check_ptr₁] at spec50,
      try { dsimp at spec50, arith_simps at spec50 },
      use_only [spec50],
      try { linarith },
    },
    {
      -- jnz: negative branch
      rw ←regstateapeq_a35 at a35,
      -- recursive function call
      step_assert_eq hpc51 hpc52 with arg0,
      step_assert_eq hpc53 with arg1,
      step_assert_eq hpc54 with arg2,
      step_assert_eq hpc55 with arg3,
      step_assert_eq hpc56 with arg4,
      step_assert_eq hpc57 with arg5,
      step_assert_eq hpc58 with arg6,
      step_assert_eq hpc59 with arg7,
      step_assert_eq hpc60 hpc61 with arg8,
      step_assert_eq hpc62 hpc63 with arg9,
      have h_δ51_c0 : ∀ x : F, x / (2 : ℤ) = x * (-1809251394333065606848661391547535052811553607665798349986546028067936010240 : ℤ),
      { intro x,  apply div_eq_mul_inv', apply PRIME.int_cast_mul_eq_one, rw [PRIME], try { simp_int_casts }, norm_num1 },
      have h_δ51_c0_fz : ∀ x : F, x / 2 = x / (2 : ℤ), { intro x, norm_cast },
      step_rec_sub hpc64 (νih _ range_check_ptr₁ double_point ((scalar - 1) / (2 : ℤ)) (m - 1) _ _ _ _ _ _ _ _ _ _ _ _ _),
      { rw hpc65, norm_num, exact h_mem_rec },
      { rw hpc65, norm_num2, exact h_mem_4 },
      { rw hpc65, norm_num2, exact h_mem_5 },
      { rw hpc65, norm_num2, exact h_mem_6 },
      { rw hpc65, norm_num2, exact h_mem_7 },
      { rw hpc65, norm_num2, exact h_mem_12 },
      { rw hpc65, norm_num2, exact h_mem_13 },
      { rw hpc65, norm_num2, exact h_mem_14 },
      { rw hpc65, norm_num2, exact h_mem_15 },
      { try { simp only [h_δ51_c0_fz, h_δ51_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      { try { simp only [h_δ51_c0_fz, h_δ51_c0] }, try { ext } ; {
          try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
          try { dsimp [cast_EcPoint, cast_BigInt3] },
          try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9] },
          try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
      { try { simp only [h_δ51_c0_fz, h_δ51_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      { try { simp only [h_δ51_c0_fz, h_δ51_c0] }, try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      intros κ_call66 ap66 h_call66,
      rcases h_call66 with ⟨rc_m66, rc_mle66, hl_range_check_ptr₂, h_call66⟩,
      generalize' hr_rev_range_check_ptr₂: mem (ap66 - 13) = range_check_ptr₂,
      have htv_range_check_ptr₂ := hr_rev_range_check_ptr₂.symm, clear hr_rev_range_check_ptr₂,
      generalize' hr_rev_inner_pow2: cast_EcPoint mem (ap66 - 12) = inner_pow2,
      simp only [hr_rev_inner_pow2] at h_call66,
      have htv_inner_pow2 := hr_rev_inner_pow2.symm, clear hr_rev_inner_pow2,
      generalize' hr_rev_inner_res: cast_EcPoint mem (ap66 - 6) = inner_res,
      simp only [hr_rev_inner_res] at h_call66,
      have htv_inner_res := hr_rev_inner_res.symm, clear hr_rev_inner_res,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8 ,arg9] at hl_range_check_ptr₂ },
      rw [←htv_range_check_ptr₂, ←htv_range_check_ptr₁] at hl_range_check_ptr₂,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8 ,arg9] at h_call66 },
      rw [←htv_range_check_ptr₁, hl_range_check_ptr₁, hin_range_check_ptr] at h_call66,
      clear arg0 arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9,
      -- local var
      step_assert_eq hpc66 with temp0,
      step_assert_eq hpc67 with temp1,
      step_assert_eq hpc68 with temp2,
      step_assert_eq hpc69 with temp3,
      step_assert_eq hpc70 with temp4,
      step_assert_eq hpc71 with temp5,
      have lc_inner_pow2: inner_pow2 = cast_EcPoint mem σ.fp, {
        try { ext } ; {
          try { simp only [htv_inner_pow2] },
          try { dsimp [cast_EcPoint, cast_BigInt3] },
          try { arith_simps }, try { simp only [temp0, temp1, temp2, temp3, temp4, temp5] },
          try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
      clear temp0 temp1 temp2 temp3 temp4 temp5,
      -- function call
      step_assert_eq hpc72 with arg0,
      step_assert_eq hpc73 with arg1,
      step_assert_eq hpc74 with arg2,
      step_assert_eq hpc75 with arg3,
      step_assert_eq hpc76 with arg4,
      step_assert_eq hpc77 with arg5,
      step_assert_eq hpc78 with arg6,
      step_assert_eq hpc79 with arg7,
      step_assert_eq hpc80 with arg8,
      step_assert_eq hpc81 with arg9,
      step_assert_eq hpc82 with arg10,
      step_assert_eq hpc83 with arg11,
      step_assert_eq hpc84 with arg12,
      step_sub hpc85 (auto_sound_fast_ec_add mem _ range_check_ptr₂ point inner_res _ _ _ _ _ _ _ _ _),
      { rw hpc86, norm_num2, exact h_mem_15 },
      { rw hpc86, norm_num2, exact h_mem_4 },
      { rw hpc86, norm_num2, exact h_mem_5 },
      { rw hpc86, norm_num2, exact h_mem_6 },
      { rw hpc86, norm_num2, exact h_mem_7 },
      { rw hpc86, norm_num2, exact h_mem_13 },
      { try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point, htv_range_check_ptr₂, htv_inner_pow2, htv_inner_res, lc_inner_pow2] },
        try { dsimp [cast_EcPoint, cast_BigInt3] },
        try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12] },
        try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } }, },
      { try { ext } ; {
          try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point, htv_range_check_ptr₂, htv_inner_pow2, htv_inner_res, lc_inner_pow2] },
          try { dsimp [cast_EcPoint, cast_BigInt3] },
          try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12] },
          try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
      { try { ext } ; {
          try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point, htv_range_check_ptr₂, htv_inner_pow2, htv_inner_res, lc_inner_pow2] },
          try { dsimp [cast_EcPoint, cast_BigInt3] },
          try { arith_simps }, try { simp only [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12] },
          try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },}, },
      intros κ_call87 ap87 h_call87,
      rcases h_call87 with ⟨rc_m87, rc_mle87, hl_range_check_ptr₃, h_call87⟩,
      generalize' hr_rev_range_check_ptr₃: mem (ap87 - 7) = range_check_ptr₃,
      have htv_range_check_ptr₃ := hr_rev_range_check_ptr₃.symm, clear hr_rev_range_check_ptr₃,
      generalize' hr_rev_res: cast_EcPoint mem (ap87 - 6) = res,
      simp only [hr_rev_res] at h_call87,
      have htv_res := hr_rev_res.symm, clear hr_rev_res,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8 ,arg9 ,arg10 ,arg11 ,arg12] at hl_range_check_ptr₃ },
      rw [←htv_range_check_ptr₃, ←htv_range_check_ptr₂] at hl_range_check_ptr₃,
      try { simp only [arg0 ,arg1 ,arg2 ,arg3 ,arg4 ,arg5 ,arg6 ,arg7 ,arg8 ,arg9 ,arg10 ,arg11 ,arg12] at h_call87 },
      rw [←htv_range_check_ptr₂, hl_range_check_ptr₂, hl_range_check_ptr₁, hin_range_check_ptr] at h_call87,
      clear arg0 arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 arg11 arg12,
      -- return
      step_assert_eq hpc87 with hret0,
      step_assert_eq hpc88 with hret1,
      step_assert_eq hpc89 with hret2,
      step_assert_eq hpc90 with hret3,
      step_assert_eq hpc91 with hret4,
      step_assert_eq hpc92 with hret5,
      step_assert_eq hpc93 with hret6,
      step_assert_eq hpc94 with hret7,
      step_assert_eq hpc95 with hret8,
      step_assert_eq hpc96 with hret9,
      step_assert_eq hpc97 with hret10,
      step_assert_eq hpc98 with hret11,
      step_assert_eq hpc99 with hret12,
      step_ret hpc100,
      -- finish
      step_done, use_only [rfl, rfl],
      -- range check condition
      use_only (rc_m35+rc_m66+rc_m87+0+0), split,
      linarith [rc_mle35, rc_mle66, rc_mle87],
      split,
      { arith_simps, try { simp only [hret0 ,hret1 ,hret2 ,hret3 ,hret4 ,hret5 ,hret6 ,hret7 ,hret8 ,hret9 ,hret10 ,hret11 ,hret12] },
        rw [←htv_range_check_ptr₃, hl_range_check_ptr₃, hl_range_check_ptr₂, hl_range_check_ptr₁, hin_range_check_ptr],
        try { arith_simps, refl <|> norm_cast }, try { refl } },
      intro rc_h_range_check_ptr, repeat { rw [add_assoc] at rc_h_range_check_ptr },
      have rc_h_range_check_ptr' := range_checked_add_right rc_h_range_check_ptr,
      -- Final Proof
      -- user-provided reduction
      suffices auto_spec: auto_spec_ec_mul_inner mem _ range_check_ptr point scalar m _ _ _,
      { apply sound_ec_mul_inner, apply auto_spec },
      -- prove the auto generated assertion
      dsimp [auto_spec_ec_mul_inner],
      try { norm_num1 }, try { arith_simps },
      right,
      use_only [a0],
      use_only [κ_call35],
      use_only [range_check_ptr₁],
      use_only [double_point],
      have rc_h_range_check_ptr₁ := range_checked_offset' rc_h_range_check_ptr,
      have rc_h_range_check_ptr₁' := range_checked_add_right rc_h_range_check_ptr₁, try { norm_cast at rc_h_range_check_ptr₁' },
      have spec35 := h_call35 rc_h_range_check_ptr',
      rw [←hin_range_check_ptr, ←htv_range_check_ptr₁] at spec35,
      try { dsimp at spec35, arith_simps at spec35 },
      use_only [spec35],
      use_only (mem regstate35.ap),
      right,
      use_only [a35],
      use_only [κ_call66],
      use_only [range_check_ptr₂],
      use_only [inner_pow2],
      use_only [inner_res],
      have rc_h_range_check_ptr₂ := range_checked_offset' rc_h_range_check_ptr₁,
      have rc_h_range_check_ptr₂' := range_checked_add_right rc_h_range_check_ptr₂, try { norm_cast at rc_h_range_check_ptr₂' },
      have spec66 := h_call66 rc_h_range_check_ptr₁',
      rw [←hin_range_check_ptr, ←hl_range_check_ptr₁, ←htv_range_check_ptr₂] at spec66,
      try { dsimp at spec66, arith_simps at spec66 },
      use_only [spec66],
      use_only [κ_call87],
      use_only [range_check_ptr₃],
      use_only [res],
      have rc_h_range_check_ptr₃ := range_checked_offset' rc_h_range_check_ptr₂,
      have rc_h_range_check_ptr₃' := range_checked_add_right rc_h_range_check_ptr₃, try { norm_cast at rc_h_range_check_ptr₃' },
      have spec87 := h_call87 rc_h_range_check_ptr₂',
      rw [←hin_range_check_ptr, ←hl_range_check_ptr₁, ←hl_range_check_ptr₂, ←htv_range_check_ptr₃] at spec87,
      try { dsimp at spec87, arith_simps at spec87 },
      use_only [spec87],
      try { split, linarith },
      try { ensures_simps; try { simp only [add_neg_eq_sub, hin_range_check_ptr, hin_point, hin_scalar, hin_m, htv_range_check_ptr₁, htv_double_point, htv_range_check_ptr₂, htv_inner_pow2, htv_inner_res, lc_inner_pow2, htv_range_check_ptr₃, htv_res] }, },
      try { dsimp [cast_EcPoint, cast_BigInt3] },
      try { arith_simps }, try { simp only [hret0, hret1, hret2, hret3, hret4, hret5, hret6, hret7, hret8, hret9, hret10, hret11, hret12] },
      try { arith_simps; try { split }; triv <|> refl <|> simp <|> abel; try { norm_num } },
    }
  }
end


import LeanCodePrompts.Translate
import LeanCodePrompts.Utils

open Lean Meta Elab

def translateWithDataM (s: String)(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 5)(temp : JsonNumber := ⟨2, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) :
  TermElabM ((Option (Expr × (Array String) )) × Array String) := do
  let js ←
    getCodeJson s numSim numKW includeFixed queryNum temp scoreBound matchBound
  let output ← GPT.jsonToExprStrArray js
  let output := output.toList.eraseDups.toArray
  let res ← arrayToExpr? output
  return (res, output)

def translateWithDataCore (s: String)(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 5)(temp : JsonNumber := ⟨2, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) :
  CoreM ((Option (Expr × (Array String) )) × Array String) :=
    (translateWithDataM s
      numSim numKW includeFixed
        queryNum temp scoreBound matchBound).run'.run'

def checkTranslatedThmsM(type: String := "thm")(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 5)(temp : JsonNumber := ⟨2, 1⟩) : TermElabM Json := do
  elabLog s!"Writing to file: {type}-elab-{numSim}-{numKW}-{includeFixed}-{queryNum}-{temp.mantissa}.json"
  let promptsFile ← reroutePath <| System.mkFilePath ["data",
    s!"prompts-{type}-{numSim}-{numKW}-{includeFixed}-{queryNum}-{temp.mantissa}.jsonl"]
  let h ← IO.FS.Handle.mk promptsFile IO.FS.Mode.append Bool.false
  let file ← reroutePath <| System.mkFilePath [s!"data/{type}-prompts.txt"]
  let prompts ←  IO.FS.lines file
  let prompts :=
      prompts.map <| fun s => s.replace "<br>" "\n"
  let mut count := 0
  let mut elaborated := 0
  let mut elabPairs: Array (String × String × (Array String)) := #[]
  let mut failed : Array String := #[]
  for prompt in prompts do
    trace[Translate.info] m!"{prompt}"
    IO.println ""
    IO.println prompt
    let (res?, outputs) ←
        translateWithDataM prompt
          numSim numKW includeFixed queryNum temp
    let fullPrompt := (← logs 1).head!
    let js := Json.mkObj [("text", Json.str prompt), ("fullPrompt", Json.str fullPrompt)]
    h.putStrLn <| js.pretty 10000
    count := count + 1
    match res? with
    | some (e, thms) =>
      elabLog "success"
      let v ← e.view
      elabLog s!"theorem {v}"
      IO.println s!"theorem {v}"
      elaborated := elaborated + 1
      elabPairs := elabPairs.push (prompt, v, thms)
    | none =>
      elabLog "failed to elaborate"
      IO.println "failed to elaborate"
      failed := failed.push prompt
      elabLog s!"outputs: {outputs}"
    elabLog s!"total : {count}"
    elabLog s!"elaborated: {elaborated}"
    IO.println s!"total : {count}"
    IO.println s!"elaborated: {elaborated}"
    IO.sleep 20000

  let js :=
    Json.mkObj
      [("total-prompts", count),
        ("elaborated", elaborated),
        ("number-similar-sentences", numSim),
       ("number-keyword-sentences", numKW),
       ("include-fixed", includeFixed),
       ("query-number", queryNum),
       ("temperature", Json.num temp),
       ("elaborated-prompts",
        Json.arr <| ←  elabPairs.mapM <|
          fun (p, s, thms) => do
            return Json.mkObj [
            ("prompt", p), ("theorem", s),
            ("all-elabs", Json.arr <| thms.map (Json.str)),
            ("comments", ""), ("correct", Json.null),
            ("some-correct", Json.null)
            ]),
        ("failures", Json.arr <| failed.map (Json.str))
            ]
  return js

def checkTranslatedThmsCore(type: String := "thm")(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 5)(temp : JsonNumber := ⟨2, 1⟩) : CoreM Json :=
    (checkTranslatedThmsM type
      numSim numKW includeFixed queryNum temp).run'.run'

def parsedThmsPrompt : IO (Array String) := do
  let file ← reroutePath <| System.mkFilePath ["data/parsed_thms.txt"]
  IO.FS.lines file

def elabThmSplit(start? size?: Option Nat := none) : TermElabM ((Array String) × (Array String)) := do
  let deps ← parsedThmsPrompt
  let deps := deps.toList.drop (start?.getD 0)
  let deps := deps.take (size?.getD (deps.length))
  let deps := deps.toArray
  let mut succ: Array String := Array.empty
  let mut fail: Array String := Array.empty
  let mut count := start?.getD 0
  let succFile ← reroutePath <| System.mkFilePath ["data/elab_thms.txt"]
  let h ← IO.FS.Handle.mk succFile IO.FS.Mode.append Bool.false
  IO.println s!"total: {deps.size}"
  for thm in deps do
    IO.println s!"parsing theorem {thm}"
    let chk ←  hasElab thm (some 25)
    count := count + 1
    if chk then
      succ := succ.push thm
      h.putStrLn thm
    else
      fail := fail.push thm
    IO.println s!"parsed: {count}"
    IO.println s!"elaborated: {succ.size}"
  return (succ, fail)

def elabThmSplitCore(start? size?: Option Nat := none) : CoreM ((Array String) × (Array String)) :=
  (elabThmSplit start? size?).run'.run'

def outputFromCompletionsM (s: String) :
  TermElabM (String) := do
  let output ← jsonStringToExprStrArray s
  let output := output ++ (output.map (fun s => ": " ++ s))
  let output := output.toList.eraseDups.toArray
  -- IO.println s!"output: {output}"
  let res? ← arrayToExpr? output
  let js : Json ←  match res? with
  | some (thm, elabs) => do
    let thm ←  thm.view
    pure <| Json.mkObj [("success", Bool.true), ("theorem", thm),
            ("all-elabs", Json.arr <| elabs.map (Json.str))]
  | none => pure <| Json.mkObj [("success", Bool.false)]
  return js.pretty 10000

def outputFromCompletionsCore (s: String) : CoreM String :=
  (outputFromCompletionsM s).run'.run'


import Lean

open Lean
open Lean.Elab
open Lean.Elab.Term

def getCtors (c : Name) : TermElabM (List Name) := do
let env ← getEnv;
(match env.find? c with
| some (ConstantInfo.inductInfo val) =>
  pure val.ctors
| _ => pure [])

def elabAnonCtor (args : Array Syntax) (τ : Expr) : TermElabM Expr :=
  match τ.getAppFn with
  | Expr.const C _ _ => do
    let ctors ← getCtors C;
    (match ctors with
    | [c] => do
      let stx ← `($(Lean.mkIdent c) $args*);
      elabTerm stx τ
-- error handling
    | _ => unreachable!)
  | _ => unreachable!

elab "foo⟨" args:term,* "⟩" : term <= τ => do
  elabAnonCtor args τ

example : Nat × Nat := foo⟨1, 2⟩


/-
## Undefined Behavior

This file models undefined behavior, the runtime condition of reaching a state
for which semantics are undefined. This captures errors that are dependent on
runtime data, eg. division by zero.

Undefined behavior is interpreted in the exception monad, with generic string-
based error messages. An execution that runs into UB is stopped; determinism
is preserved.

TODO: Consider an UB interpreter directly into results from a proof of non-UB
-/

import MLIR.Semantics.Fitree

-- The monad for UB records exceptions based on strings
abbrev UBT := ExceptT String

inductive UBE: Type → Type :=
  | UB {α: Type} [Inhabited α]: Option String → UBE α
  | Unhandled {α: Type} [Inhabited α]: UBE α

@[simp_itree]
def UBE.handle {E}: UBE ~> UBT (Fitree E) := fun _ e =>
  match e with
  | Unhandled => throw "<unhandled>"
  | UB none => throw "<UB>"
  | UB (some msg) => throw s!"<UB: {msg}>"

@[simp_itree]
def UBE.handle! {E}: UBE ~> Fitree E := fun _ e =>
  match e with
  | Unhandled => panic! "<unhandled>"
  | UB none => panic! "<UB>"
  | UB (some msg) => panic! s!"<UB: {msg}>"

def raiseUB (msg: String) {E α} [Member UBE E] [Inhabited α]: Fitree E α :=
  Fitree.trigger <| UBE.UB (some msg)

def interpUB (t: Fitree UBE R): UBT (Fitree Void1) R :=
  t.interpExcept UBE.handle

def interpUB! (t: Fitree UBE R): Fitree Void1 R :=
  t.interp UBE.handle!

def interpUB' {E} (t: Fitree (UBE +' E) R): UBT (Fitree E) R :=
  t.interpExcept (Fitree.case UBE.handle Fitree.liftHandler)

def interpUB'! {E} (t: Fitree (UBE +' E) R): Fitree E R :=
  t.interp (Fitree.case UBE.handle! (fun T => @Fitree.trigger E E T _))

/-
### Reduction theorems
-/

@[simp] theorem interpUB_ret:
  interpUB (Fitree.ret r) = Fitree.ret (Except.ok r) := rfl

@[simp] theorem interpUB_Ret:
  interpUB (Fitree.Ret r) = Fitree.ret (Except.ok r) := rfl

theorem interpUB_bind (k: T → Fitree UBE R):
  interpUB (Fitree.bind t k) =
  Fitree.bind (interpUB t) (fun x =>
    match x with
    | .error ε => Fitree.ret (.error ε)
    | .ok x => interpUB (k x)) := by
  -- Can't reuse `Fitree.interpExcept_bind` because the match statements are
  -- considered different by isDefEq for some reason
  induction t with
  | Ret _ => rfl
  | Vis _ _ ih =>
      simp [interpUB, Fitree.interpExcept] at *
      simp [Fitree.interp, Fitree.bind, Bind.bind]
      simp [ExceptT.bind, ExceptT.mk, ExceptT.bindCont]
      have fequal2 α β (f g: α → β) x y: f = g → x = y → f x = g y :=
        fun h₁ h₂ => by simp [h₁, h₂]
      apply fequal2; rfl; funext x
      cases x <;> simp [ih]

@[simp] theorem interpUB'_Vis_right:
  interpUB' (Fitree.Vis (Sum.inr e) k) =
  Fitree.Vis e (fun x => interpUB' (k x)) := rfl

@[simp] theorem interpUB'_ret:
  @interpUB' _ E (Fitree.ret r) = Fitree.ret (Except.ok r) := rfl

theorem interpUB'_bind (k: T → Fitree (UBE +' E) R):
  interpUB' (Fitree.bind t k) =
  Fitree.bind (interpUB' t) (fun x =>
    match x with
    | .error ε => Fitree.ret (.error ε)
    | .ok x => interpUB' (k x)) := by
  -- Can't reuse `Fitree.interpExcept_bind` because the match statements are
  -- considered different by isDefEq for some reason
  induction t with
  | Ret _ => rfl
  | Vis _ _ ih =>
      simp [interpUB', Fitree.interpExcept] at *
      simp [Fitree.interp, Fitree.bind, Bind.bind]
      simp [ExceptT.bind, ExceptT.mk, ExceptT.bindCont]
      have fequal2 α β (f g: α → β) x y: f = g → x = y → f x = g y :=
        fun h₁ h₂ => by simp [h₁, h₂]
      apply fequal2; rfl; funext x
      cases x <;> simp [ih]


/-
Copyright (c) 2020 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import tactic.core

/-!
# The `simp_rw` tactic

This module defines a tactic `simp_rw` which functions as a mix of `simp` and
`rw`. Like `rw`, it applies each rewrite rule in the given order, but like
`simp` it repeatedly applies these rules and also under binders like `∀ x, ...`,
`∃ x, ...` and `λ x, ...`.

## Implementation notes

The tactic works by taking each rewrite rule in turn and applying `simp only` to
it. Arguments to `simp_rw` are of the format used by `rw` and are translated to
their equivalents for `simp`.
-/

namespace tactic.interactive
open interactive interactive.types tactic

/--
`simp_rw` functions as a mix of `simp` and `rw`. Like `rw`, it applies each
rewrite rule in the given order, but like `simp` it repeatedly applies these
rules and also under binders like `∀ x, ...`, `∃ x, ...` and `λ x, ...`.

Usage:
  - `simp_rw [lemma_1, ..., lemma_n]` will rewrite the goal by applying the
    lemmas in that order. A lemma preceded by `←` is applied in the reverse direction.
  - `simp_rw [lemma_1, ..., lemma_n] at h₁ ... hₙ` will rewrite the given hypotheses.
  - `simp_rw [...] at ⊢ h₁ ... hₙ` rewrites the goal as well as the given hypotheses.
  - `simp_rw [...] at *` rewrites in the whole context: all hypotheses and the goal.

Lemmas passed to `simp_rw` must be expressions that are valid arguments to `simp`.

For example, neither `simp` nor `rw` can solve the following, but `simp_rw` can:
```lean
example {α β : Type} {f : α → β} {t : set β} :
  (∀ s, f '' s ⊆ t) = ∀ s : set α, ∀ x ∈ s, x ∈ f ⁻¹' t :=
by simp_rw [set.image_subset_iff, set.subset_def]
```
-/
meta def simp_rw (q : parse rw_rules) (l : parse location) : tactic unit :=
q.rules.mmap' (λ rule, do
  let simp_arg := if rule.symm
    then simp_arg_type.symm_expr rule.rule
    else simp_arg_type.expr rule.rule,
  save_info rule.pos,
  simp none none tt [simp_arg] [] l) -- equivalent to `simp only [rule] at l`

add_tactic_doc
{ name       := "simp_rw",
  category   := doc_category.tactic,
  decl_names := [`tactic.interactive.simp_rw],
  tags       := ["simplification"] }

end tactic.interactive


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.function
import Mathlib.Lean3Lib.init.data.option.basic
import Mathlib.Lean3Lib.init.util
import Mathlib.Lean3Lib.init.control.combinators
import Mathlib.Lean3Lib.init.control.monad
import Mathlib.Lean3Lib.init.control.alternative
import Mathlib.Lean3Lib.init.control.monad_fail
import Mathlib.Lean3Lib.init.data.nat.div
import Mathlib.Lean3Lib.init.meta.exceptional
import Mathlib.Lean3Lib.init.meta.format
import Mathlib.Lean3Lib.init.meta.environment
import Mathlib.Lean3Lib.init.meta.pexpr
import Mathlib.Lean3Lib.init.data.repr
import Mathlib.Lean3Lib.init.data.string.basic
import Mathlib.Lean3Lib.init.meta.interaction_monad
import Mathlib.Lean3Lib.init.classical

universes l

namespace Mathlib

infixl:2 " >>=[tactic] " => Mathlib.interaction_monad_bind

infixl:2 " >>[tactic] " => Mathlib.interaction_monad_seq

namespace tactic_state

/-- Format the given tactic state. If `target_lhs_only` is true and the target
    is of the form `lhs ~ rhs`, where `~` is a simplification relation,
    then only the `lhs` is displayed.

    Remark: the parameter `target_lhs_only` is a temporary hack used to implement
    the `conv` monad. It will be removed in the future. -/
/-- Format expression with respect to the main goal in the tactic state.
   If the tactic state does not contain any goals, then format expression
   using an empty local context. -/
end tactic_state

/-- `tactic` is the monad for building tactics.
    You use this to:
    - View and modify the local goals and hypotheses in the prover's state.
    - Invoke type checking and elaboration of terms.
    - View and modify the environment.
    - Build new tactics out of existing ones such as `simp` and `rewrite`.
-/
namespace tactic

end tactic

namespace tactic_result

end tactic_result

namespace interactive

/-- Typeclass for custom interaction monads, which provides
    the information required to convert an interactive-mode
    construction to a `tactic` which can actually be executed.

    Given a `[monad m]`, `execute_with` explains how to turn a `begin ... end`
    block, or a `by ...` statement into a `tactic α` which can actually be
    executed. The `inhabited` first argument facilitates the passing of an
    optional configuration parameter `config`, using the syntax:
    ```
    begin [custom_monad] with config,
        ...
    end
    ```
-/
/-- Default `executor` instance for `tactic`s themselves -/
end interactive

namespace tactic

/-- Does nothing. -/
/--
`try_core t` acts like `t`, but succeeds even if `t` fails. It returns the
result of `t` if `t` succeeded and `none` otherwise.
-/
/--
`try t` acts like `t`, but succeeds even if `t` fails.
-/
/--
`fail_if_success t` acts like `t`, but succeeds if `t` fails and fails if `t`
succeeds. Changes made by `t` to the `tactic_state` are preserved only if `t`
succeeds.
-/
/--
`success_if_fail t` acts like `t`, but succeeds if `t` fails and fails if `t`
succeeds. Changes made by `t` to the `tactic_state` are preserved only if `t`
succeeds.
-/
/--
`iterate_at_most n t` iterates `t` `n` times or until `t` fails, returning the
result of each successful iteration.
-/
/--
`iterate_at_most' n t` repeats `t` `n` times or until `t` fails.
-/
/--
`iterate_exactly n t` iterates `t` `n` times, returning the result of
each iteration. If any iteration fails, the whole tactic fails.
-/
/--
`iterate_exactly' n t` executes `t` `n` times. If any iteration fails, the whole
tactic fails.
-/
/--
`iterate t` repeats `t` 100.000 times or until `t` fails, returning the
result of each iteration.
-/
/--
`iterate' t` repeats `t` 100.000 times or until `t` fails.
-/
/-- Decorate t's exceptions with msg. -/
/-- Set the tactic_state. -/
/-- Get the tactic_state. -/
/--
`capture t` acts like `t`, but succeeds with a result containing either the returned value
or the exception.
Changes made by `t` to the `tactic_state` are preserved in both cases.

The result can be used to inspect the error message, or passed to `unwrap` to rethrow the
failure later.
-/
/--
`unwrap r` unwraps a result previously obtained using `capture`.

If the previous result was a success, this produces its wrapped value.
If the previous result was an exception, this "rethrows" the exception as if it came
from where it originated.

`do r ← capture t, unwrap r` is identical to `t`, but allows for intermediate tactics to be inserted.
-/
/--
`resume r` continues execution from a result previously obtained using `capture`.

This is like `unwrap`, but the `tactic_state` is rolled back to point of capture even upon success.
-/
end tactic

namespace tactic

/-- A parameter representing how aggressively definitions should be unfolded when trying to decide if two terms match, unify or are definitionally equal.
By default, theorem declarations are never unfolded.
- `all` will unfold everything, including macros and theorems. Except projection macros.
- `semireducible` will unfold everything except theorems and definitions tagged as irreducible.
- `instances` will unfold all class instance definitions and definitions tagged with reducible.
- `reducible` will only unfold definitions tagged with the `reducible` attribute.
- `none` will never unfold anything.
[NOTE] You are not allowed to tag a definition with more than one of `reducible`, `irreducible`, `semireducible` attributes.
[NOTE] there is a config flag `m_unfold_lemmas`that will make it unfold theorems.
 -/
inductive transparency
where
| all : transparency
| semireducible : transparency
| instances : transparency
| reducible : transparency
| none : transparency

/-- (eval_expr α e) evaluates 'e' IF 'e' has type 'α'. -/
/-- Return the partial term/proof constructed so far. Note that the resultant expression
   may contain variables that are not declarate in the current main goal. -/
/-- Display the partial term/proof constructed so far. This tactic is *not* equivalent to
   `do { r ← result, s ← read, return (format_expr s r) }` because this one will format the result with respect
   to the current goal, and trace_result will do it with respect to the initial goal. -/
/-- Return target type of the main goal. Fail if tactic_state does not have any goal left. -/
/-- Clear the given local constant. The tactic fails if the given expression is not a local constant. -/
/-- `revert_lst : list expr → tactic nat` is the reverse of `intron`. It takes a local constant `c` and puts it back as bound by a `pi` or `elet` of the main target.
If there are other local constants that depend on `c`, these are also reverted. Because of this, the `nat` that is returned is the actual number of reverted local constants.
Example: with `x : ℕ, h : P(x) ⊢ T(x)`, `revert_lst [x]` returns `2` and produces the state ` ⊢ Π x, P(x) → T(x)`.
 -/
/-- Return `e` in weak head normal form with respect to the given transparency setting.
    If `unfold_ginductive` is `tt`, then nested and/or mutually recursive inductive datatype constructors
    and types are unfolded. Recall that nested and mutually recursive inductive datatype declarations
    are compiled into primitive datatypes accepted by the Kernel. -/
/-- (head) eta expand the given expression. `f : α → β` head-eta-expands to `λ a, f a`. If `f` isn't a function then it just returns `f`.  -/
/-- (head) beta reduction. `(λ x, B) c` reduces to `B[x/c]`. -/
/-- (head) zeta reduction. Reduction of let bindings at the head of the expression. `let x : a := b in c` reduces to `c[x/b]`. -/
/-- Zeta reduction. Reduction of let bindings. `let x : a := b in c` reduces to `c[x/b]`. -/
/-- (head) eta reduction. `(λ x, f x)` reduces to `f`. -/
/-- Succeeds if `t` and `s` can be unified using the given transparency setting. -/
/-- Similar to `unify`, but it treats metavariables as constants. -/
/-- Infer the type of the given expression.
   Remark: transparency does not affect type inference -/
/-- Get the `local_const` expr for the given `name`. -/
/-- Resolve a name using the current local context, environment, aliases, etc. -/
/-- Return the hypothesis in the main goal. Fail if tactic_state does not have any goal left. -/
/-- Get a fresh name that is guaranteed to not be in use in the local context.
    If `n` is provided and `n` is not in use, then `n` is returned.
    Otherwise a number `i` is appended to give `"n_i"`.
-/
/--  Helper tactic for creating simple applications where some arguments are inferred using
    type inference.

    Example, given
    ```
        rel.{l_1 l_2} : Pi (α : Type.{l_1}) (β : α -> Type.{l_2}), (Pi x : α, β x) -> (Pi x : α, β x) -> , Prop
        nat     : Type
        real    : Type
        vec.{l} : Pi (α : Type l) (n : nat), Type.{l1}
        f g     : Pi (n : nat), vec real n
    ```
    then
    ```
    mk_app_core semireducible "rel" [f, g]
    ```
    returns the application
    ```
    rel.{1 2} nat (fun n : nat, vec real n) f g
    ```

    The unification constraints due to type inference are solved using the transparency `md`.
-/
/-- Similar to `mk_app`, but allows to specify which arguments are explicit/implicit.
   Example, given `(a b : nat)` then
   ```
   mk_mapp "ite" [some (a > b), none, none, some a, some b]
   ```
   returns the application
   ```
   @ite.{1} (a > b) (nat.decidable_gt a b) nat a b
   ```
-/
/-- (mk_congr_arg h₁ h₂) is a more efficient version of (mk_app `congr_arg [h₁, h₂]) -/
/-- (mk_congr_fun h₁ h₂) is a more efficient version of (mk_app `congr_fun [h₁, h₂]) -/
/-- (mk_congr h₁ h₂) is a more efficient version of (mk_app `congr [h₁, h₂]) -/
/-- (mk_eq_refl h) is a more efficient version of (mk_app `eq.refl [h]) -/
/-- (mk_eq_symm h) is a more efficient version of (mk_app `eq.symm [h]) -/
/-- (mk_eq_trans h₁ h₂) is a more efficient version of (mk_app `eq.trans [h₁, h₂]) -/
/-- (mk_eq_mp h₁ h₂) is a more efficient version of (mk_app `eq.mp [h₁, h₂]) -/
/-- (mk_eq_mpr h₁ h₂) is a more efficient version of (mk_app `eq.mpr [h₁, h₂]) -/
/- Given a local constant t, if t has type (lhs = rhs) apply substitution.
   Otherwise, try to find a local constant that has type of the form (t = t') or (t' = t).
   The tactic fails if the given expression is not a local constant. -/

/-- Close the current goal using `e`. Fail if the type of `e` is not definitionally equal to
    the target type. -/
/-- Elaborate the given quoted expression with respect to the current main goal.
    Note that this means that any implicit arguments for the given `pexpr` will be applied with fresh metavariables.
    If `allow_mvars` is tt, then metavariables are tolerated and become new goals if `subgoals` is tt. -/
/-- Return true if the given expression is a type class. -/
/-- Try to create an instance of the given type class. -/
/-- Change the target of the main goal.
   The input expression must be definitionally equal to the current target.
   If `check` is `ff`, then the tactic does not check whether `e`
   is definitionally equal to the current target. If it is not,
   then the error will only be detected by the kernel type checker. -/
/-- `assert_core H T`, adds a new goal for T, and change target to `T -> target`. -/
/-- `assertv_core H T P`, change target to (T -> target) if P has type T. -/
/-- `define_core H T`, adds a new goal for T, and change target to  `let H : T := ?M in target` in the current goal. -/
/-- `definev_core H T P`, change target to `let H : T := P in target` if P has type T. -/
/-- Rotate goals to the left. That is, `rotate_left 1` takes the main goal and puts it to the back of the subgoal list. -/
/-- Gets a list of metavariables, one for each goal. -/
/-- Replace the current list of goals with the given one. Each expr in the list should be a metavariable. Any assigned metavariables will be ignored.-/
/-- How to order the new goals made from an `apply` tactic.
Supposing we were applying `e : ∀ (a:α) (p : P(a)), Q`
- `non_dep_first` would produce goals `⊢ P(?m)`, `⊢ α`. It puts the P goal at the front because none of the arguments after `p` in `e` depend on `p`. It doesn't matter what the result `Q` depends on.
- `non_dep_only` would produce goal `⊢ P(?m)`.
- `all` would produce goals `⊢ α`, `⊢ P(?m)`.
-/
inductive new_goals
where
| non_dep_first : new_goals
| non_dep_only : new_goals
| all : new_goals

/-- Configuration options for the `apply` tactic.
- `md` sets how aggressively definitions are unfolded.
- `new_goals` is the strategy for ordering new goals.
- `instances` if `tt`, then `apply` tries to synthesize unresolved `[...]` arguments using type class resolution.
- `auto_param` if `tt`, then `apply` tries to synthesize unresolved `(h : p . tac_id)` arguments using tactic `tac_id`.
- `opt_param` if `tt`, then `apply` tries to synthesize unresolved `(a : t := v)` arguments by setting them to `v`.
- `unify` if `tt`, then `apply` is free to assign existing metavariables in the goal when solving unification constraints.
   For example, in the goal `|- ?x < succ 0`, the tactic `apply succ_lt_succ` succeeds with the default configuration,
   but `apply_with succ_lt_succ {unify := ff}` doesn't since it would require Lean to assign `?x` to `succ ?y` where
   `?y` is a fresh metavariable.
-/
structure apply_cfg
where
  md : transparency
  approx : Bool
  new_goals : new_goals
  instances : Bool
  auto_param : Bool
  opt_param : Bool
  unify : Bool

/-- Apply the expression `e` to the main goal, the unification is performed using the transparency mode in `cfg`.
    Supposing `e : Π (a₁:α₁) ... (aₙ:αₙ), P(a₁,...,aₙ)` and the target is `Q`, `apply` will attempt to unify `Q` with `P(?a₁,...?aₙ)`.
    All of the metavariables that are not assigned are added as new metavariables.
    If `cfg.approx` is `tt`, then fallback to first-order unification, and approximate context during unification.
    `cfg.new_goals` specifies which unassigned metavariables become new goals, and their order.
    If `cfg.instances` is `tt`, then use type class resolution to instantiate unassigned meta-variables.
    The fields `cfg.auto_param` and `cfg.opt_param` are ignored by this tactic (See `tactic.apply`).
    It returns a list of all introduced meta variables and the parameter name associated with them, even the assigned ones. -/
/- Create a fresh meta universe variable. -/

/- Create a fresh meta-variable with the given type.
   The scope of the new meta-variable is the local context of the main goal. -/

/-- Return the value assigned to the given universe meta-variable.
   Fail if argument is not an universe meta-variable or if it is not assigned. -/
/-- Return the value assigned to the given meta-variable.
   Fail if argument is not a meta-variable or if it is not assigned. -/
/-- Return true if the given meta-variable is assigned.
    Fail if argument is not a meta-variable. -/
/-- Make a name that is guaranteed to be unique. Eg `_fresh.1001.4667`. These will be different for each run of the tactic.  -/
/-- Induction on `h` using recursor `rec`, names for the new hypotheses
   are retrieved from `ns`. If `ns` does not have sufficient names, then use the internal binder names
   in the recursor.
   It returns for each new goal the name of the constructor (if `rec_name` is a builtin recursor),
   a list of new hypotheses, and a list of substitutions for hypotheses
   depending on `h`. The substitutions map internal names to their replacement terms. If the
   replacement is again a hypothesis the user name stays the same. The internal names are only valid
   in the original goal, not in the type context of the new goal.
   Remark: if `rec_name` is not a builtin recursor, we use parameter names of `rec_name` instead of
   constructor names.

   If `rec` is none, then the type of `h` is inferred, if it is of the form `C ...`, tactic uses `C.rec` -/
/-- Apply `cases_on` recursor, names for the new hypotheses are retrieved from `ns`.
   `h` must be a local constant. It returns for each new goal the name of the constructor, a list of new hypotheses, and a list of
   substitutions for hypotheses depending on `h`. The number of new goals may be smaller than the
   number of constructors. Some goals may be discarded when the indices to not match.
   See `induction` for information on the list of substitutions.

   The `cases` tactic is implemented using this one, and it relaxes the restriction of `h`.

   Note: There is one "new hypothesis" for every constructor argument. These are
   usually local constants, but due to dependent pattern matching, they can also
   be arbitrary terms. -/
/-- Similar to cases tactic, but does not revert/intro/clear hypotheses. -/
/-- Generalizes the target with respect to `e`.  -/
/-- instantiate assigned metavariables in the given expression -/
/-- Add the given declaration to the environment -/
/--
Changes the environment to the `new_env`.
The new environment does not need to be a descendant of the old one.
Use with care.
-/
/-- Changes the environment to the `new_env`. `new_env` needs to be a descendant from the current environment. -/
/-- `doc_string env d k` returns the doc string for `d` (if available) -/
/-- Set the docstring for the given declaration. -/
/--
Create an auxiliary definition with name `c` where `type` and `value` may contain local constants and
meta-variables. This function collects all dependencies (universe parameters, universe metavariables,
local constants (aka hypotheses) and metavariables).
It updates the environment in the tactic_state, and returns an expression of the form

          (c.{l_1 ... l_n} a_1 ... a_m)

where l_i's and a_j's are the collected dependencies.
-/
/-- Returns a list of all top-level (`/-! ... -/`) docstrings in the active module and imported ones.
The returned object is a list of modules, indexed by `(some filename)` for imported modules
and `none` for the active one, where each module in the list is paired with a list
of `(position_in_file, docstring)` pairs. -/
/-- Returns a list of docstrings in the active module. An entry in the list can be either:
- a top-level (`/-! ... -/`) docstring, represented as `(none, docstring)`
- a declaration-specific (`/-- ... -/`) docstring, represented as `(some decl_name, docstring)` -/
/-- Set attribute `attr_name` for constant `c_name` with the given priority.
   If the priority is none, then use default -/
/-- `unset_attribute attr_name c_name` -/
/-- `has_attribute attr_name c_name` succeeds if the declaration `decl_name`
   has the attribute `attr_name`. The result is the priority and whether or not
   the attribute is persistent. -/
/-- `copy_attribute attr_name c_name p d_name` copy attribute `attr_name` from
   `src` to `tgt` if it is defined for `src`; make it persistent if `p` is `tt`;
   if `p` is `none`, the copied attribute is made persistent iff it is persistent on `src`  -/
/-- Name of the declaration currently being elaborated. -/
/-- `save_type_info e ref` save (typeof e) at position associated with ref -/
/-- Return list of currently open namespaces -/
/-- Return tt iff `t` "occurs" in `e`. The occurrence checking is performed using
    keyed matching with the given transparency setting.

    We say `t` occurs in `e` by keyed matching iff there is a subterm `s`
    s.t. `t` and `s` have the same head, and `is_def_eq t s md`

    The main idea is to minimize the number of `is_def_eq` checks
    performed. -/
/-- Abstracts all occurrences of the term `t` in `e` using keyed matching.
    If `unify` is `ff`, then matching is used instead of unification.
    That is, metavariables occurring in `e` are not assigned. -/
/-- Blocks the execution of the current thread for at least `msecs` milliseconds.
    This tactic is used mainly for debugging purposes. -/
/-- Type check `e` with respect to the current goal.
    Fails if `e` is not type correct. -/
/-- A `tag` is a list of `names`. These are attached to goals to help tactics track them.-/
def tag :=
  List name


import data.equiv.nat
import computability.encoding
import computability.turing_machine
import data.polynomial.basic
import data.polynomial.eval
import data.finset.basic
-- import measure_theory.measurable_space_def
import .distribution_ensemble

/-!
# UC Protocols

This file defines protocols as they are understood in the
[Universal Composability Security framework](https://eprint.iacr.org/2000/067.pdf).

-/

-- noncomputable theory

def ste := list bool

structure message :=
(import_tokens : ℕ)
(content : list (bool))

def pair (m1 m2 : message) : message := sorry -- computes a list encoding a pair of lists
def unpair_left (m : message) : message := sorry -- computes the left of a pair encoding
def unpair_right (m : message) : message := sorry -- computes the right of a pair encoding

def encode (n : ℕ) : message := sorry -- encodes a nat as a message
def decode (m : message) : ℕ := sorry -- decodes a nat from a message

inductive incoming_information : Type
| input : incoming_information
| suroutine_output : incoming_information
| backdoor : incoming_information

/-- As defined in section 2.1 -/
structure machine :=
  (ident : ℕ)
  (initial_state : ensemble ste) -- An ensemble of initial states (representing randomness in the machine execution)
  (callers : finset (ℕ)) -- Ids of machines that input to this machine
  (subroutines : finset (ℕ)) -- Ids of machines that give subroutine output to this machine
  (backdoor : finset (ℕ)) -- Ids of machines that backdoor to this machine
  -- given a starting state and a message from ID, return a new state and an outgoing message, or optionally halt
  (program : ste → ℕ → message → option (ste × ℕ × message))
    -- TODO add condition for polytime halting
  (environment_output : option (ste → bool))
    -- Optional function for environement machines to run on the environment's state when it halts to determine its output variable

instance : decidable_eq machine := sorry

-- /-- As defined in section 2.1 -/
-- structure protocol :=
--   (ids : finset ℕ)
--   (μ : ℕ → machine)
--   -- ids correspond to ids
--   (ids_match : ∀ i ∈ ids, (μ i).id = i)
--   -- Callers match up with subroutines
--   (callers_have_subroutines : ∀ i j ∈ ids, i ∈ (μ j).callers ↔ j ∈ (μ i).subroutines)

/-- As defined in section 2.1 -/
structure protocol :=
  (machines : finset machine)
  -- Callers match up with subroutines
  (callers_have_subroutines :
    ∀ (i j : machine), i ∈ machines → i.ident ∈ j.callers ↔ j.ident ∈ i.subroutines)

def protocol.ids (π : protocol) : finset ℕ :=
  π.machines.image machine.ident

/--
As defined in section 2.1, the main machines of a protocol are those who have callers whose ids
are not in the protocol,
-/
def is_main_machine (π : protocol) (μ : machine) : Prop :=
  μ.ident ∈ π.ids ∧ ∃ m ∈ μ.callers, m ∉ π.ids

-- TODO seems like a bug in the typeclass inference system tha tthis needs to be defined
instance (π : protocol) : decidable_pred (is_main_machine π) := begin
  rw decidable_pred,
  intro a,
  apply and.decidable,
end

/--
As defined in section 2.1, the main machines of a protocol are those who have callers whose ids
are not in the protocol,
-/
def main_machines (π : protocol) : finset machine :=
  π.machines.filter (is_main_machine π)

/-- ... and the ids of these machines not in the protocol are "external" -/
def external_ids (π : protocol) : finset ℕ :=
  (finset.bUnion (π.machines) (λ μ, μ.callers)) \ π.ids

/-- As defined in 2.2.1 -/
def execution (π : protocol) (𝓐 : machine) (𝓔 : machine) :
  -- (initial_states : ℕ → ste) -- randomness initialization for the machines in the protocol (including input for environment)
  -- (environment_id_zero : 𝓔.id = 0) -- environment machine has id 0
  -- (adversary_id_one : 𝓐.id = 1) -- adversaty machine has id 1
  -- (h0 : 0 ∉ 𝓟.ids) (h1 : 1 ∉ 𝓟.ids) -- 𝓟 does not have 0 or 1 in its id list
  -- (h0' : 0 ∉ external_ids 𝓟) (h1' : 1 ∉ external_ids 𝓟) -- or in its external ids
  ensemble bool :=
sorry

/--
See definition on page 42.
An environment is balanced if, at any point in time during the execution, the overall import of
the inputs given to the adversary is at least the sum of the imports of all the other inputs given
to all the other ITIs in the system so far
-/
def balanced (𝓔 : machine) : Prop :=
sorry

/-- As defined in 2.2.1 Definition 1 -/
def emulates (π ϕ : protocol) : Prop :=
  ∀ (𝓐 : machine), ∃ (𝓢 : machine), ∀ (𝓔 : machine),
    balanced 𝓔 → (execution π 𝓐 𝓔 ≈ₛ execution π 𝓢 𝓔)

def dummy_machine (ident : ℕ) (forwards_to : ℕ) (callers : finset ℕ) : machine :=
{ ident := ident,
  initial_state := λ n,
  { val := λ s, if s = [] then 1 else 0, -- todo replace with const added to pmf.lean
    property :=
    begin
      apply has_sum_ite_eq,
    end }, -- doesn't matter, stateless
  callers := callers,
  subroutines := {forwards_to},
  backdoor := ∅,
  program := λ st idx msg,
    if idx = forwards_to
      then some ⟨st, decode (unpair_left msg), unpair_right msg⟩
      else some ⟨st, forwards_to, pair msg (encode idx)⟩,
  environment_output := none }

/--
Per section 2.2.2, a functionality is described with an ideal protocol which is a protocol with a
machine for the functionality and a bunch of dummy machines which call the functionality by
forwarding messages
-/
def ideal_protocol (m : machine) : protocol :=
{ machines := finset.cons (m : machine)
    (finset.image (λ i, dummy_machine i m.ident m.callers) m.callers)
    (by {
      simp only [not_exists, finset.mem_image],
      intros x hx,
      sorry,
    }),
  callers_have_subroutines := sorry }

-- /-- Per section 2.3 -/
-- def subroutine_protocol (𝓟 : protocol) (s : finset ℕ) : protocol :=
-- { ids := 𝓟.ids \ s,
--   μ := 𝓟.μ,
--   ids_match := begin
--     intros i hi,
--     rw finset.mem_sdiff at hi,
--     exact 𝓟.ids_match i hi.left,
--   end,
--   callers_have_subroutines := begin
--     intros i hi j hj,
--     rw finset.mem_sdiff at hi hj,
--     apply 𝓟.callers_have_subroutines,
--     exact hi.left,
--     exact hj.left,
--   end }

/-- Per section 2.3 -/
def subroutine (ϕ π : protocol) : Prop := ϕ.machines ⊆ π.machines

instance : has_subset (protocol) := ⟨λ ϕ π, subroutine ϕ π⟩

/-- Per section 2.3 -/
def compatible (π ϕ : protocol) : Prop :=
∀ μ ∈ π.machines, ∃! μ' ∈ ϕ.machines, ((μ : machine).ident) = (μ'.ident) ∧ μ.callers = μ'.callers
-- Fails without the type ascription, post to forum to figure out whats wrong
-- Is it that lean doesn't know what the type of a member of a list of machines is?
-- I can accept that there might be different has_mem instances for a type, but the infoview
-- indicates it knows μ is a machine

def identity_compatible (π ρ ϕ : protocol) :=
disjoint (π.ids) (ρ.ids \ ϕ.ids)

/-- Per section 2.3 -/
def composed (ρ ϕ π : protocol) (hϕρ : ϕ ⊆ ρ) (hπϕ : compatible π ϕ)
  (h : identity_compatible π ρ ϕ) : protocol :=
{ machines := (ρ.machines \ ϕ.machines) ∪ π.machines,
  callers_have_subroutines := begin
    sorry,
    --follows from compatibility
  end }

/-- Section 2.3 theorem 3 -/
theorem composition_theorem (ρ ϕ π : protocol) (hϕρ : ϕ ⊆ ρ) (hπϕ : compatible π ϕ)
  (h : identity_compatible π ρ ϕ) (h_emulate : emulates π ϕ) :
  emulates (composed ρ ϕ π hϕρ hπϕ h) ρ :=
begin
  sorry,
end


/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.Lean3Lib.data.buffer.parser
import Mathlib.tactic.core
import Mathlib.PostPort

namespace Mathlib

/-!
# The `alias` command

This file defines an `alias` command, which can be used to create copies
of a theorem or definition with different names.

Syntax:

```lean
/-- doc string -/

alias my_theorem ← alias1 alias2 ...
```

This produces defs or theorems of the form:

```lean
/-- doc string -/
/-- doc string -/
namespace tactic.alias

/--
The `alias` command can be used to create copies
of a theorem or definition with different names.

Syntax:

```lean
/-- doc string -/
/-- doc string -/
/-- doc string -/
end Mathlib