src/HOL/Tools/Sledgehammer/sledgehammer_isar_proof.ML
author blanchet
Mon, 03 Feb 2014 13:35:50 +0100
changeset 56623 765555d6a0b2
parent 56622 f0187a12b8f2
child 56624 d4a033f07800
permissions -rw-r--r--
refactor relabeling code
blanchet@56544
     1
(*  Title:      HOL/Tools/Sledgehammer/sledgehammer_isar_proof.ML
smolkas@51278
     2
    Author:     Jasmin Blanchette, TU Muenchen
smolkas@51278
     3
    Author:     Steffen Juilf Smolka, TU Muenchen
smolkas@51278
     4
smolkas@51278
     5
Basic data structures for representing and basic methods
smolkas@51278
     6
for dealing with Isar proof texts.
smolkas@51278
     7
*)
smolkas@51278
     8
blanchet@56544
     9
signature SLEDGEHAMMER_ISAR_PROOF =
smolkas@51274
    10
sig
wenzelm@52376
    11
  type label = string * int
blanchet@56158
    12
  type facts = label list * string list (* local and global facts *)
smolkas@51283
    13
smolkas@52315
    14
  datatype isar_qualifier = Show | Then
smolkas@51283
    15
blanchet@56560
    16
  datatype proof_method =
blanchet@56599
    17
    Metis_Method of string option * string option |
blanchet@56599
    18
    Simp_Method |
blanchet@56599
    19
    Simp_Size_Method |
blanchet@56599
    20
    Auto_Method |
blanchet@56599
    21
    Fastforce_Method |
blanchet@56599
    22
    Force_Method |
blanchet@56599
    23
    Arith_Method |
blanchet@56599
    24
    Blast_Method |
blanchet@56599
    25
    Meson_Method |
blanchet@56599
    26
    Algebra_Method
blanchet@56560
    27
smolkas@53591
    28
  datatype isar_proof =
blanchet@56042
    29
    Proof of (string * typ) list * (label * term) list * isar_step list
smolkas@52316
    30
  and isar_step =
smolkas@51283
    31
    Let of term * term |
blanchet@56622
    32
    Prove of isar_qualifier list * (string * typ) list * label * term * isar_proof list
blanchet@56622
    33
      * facts * proof_method list
smolkas@53729
    34
smolkas@52316
    35
  val no_label : label
smolkas@51283
    36
smolkas@53729
    37
  val label_ord : label * label -> order
blanchet@53135
    38
  val string_of_label : label -> string
smolkas@53729
    39
blanchet@56564
    40
  val string_of_proof_method : proof_method -> string
blanchet@56564
    41
blanchet@56602
    42
  val steps_of_isar_proof : isar_proof -> isar_step list
smolkas@52316
    43
blanchet@56565
    44
  val label_of_isar_step : isar_step -> label option
blanchet@56621
    45
  val facts_of_isar_step : isar_step -> facts
blanchet@56621
    46
  val proof_methods_of_isar_step : isar_step -> proof_method list
smolkas@52316
    47
blanchet@56107
    48
  val fold_isar_steps : (isar_step -> 'a -> 'a) -> isar_step list -> 'a -> 'a
blanchet@56554
    49
  val map_isar_steps : (isar_step -> isar_step) -> isar_proof -> isar_proof
blanchet@56554
    50
  val add_isar_steps : isar_step list -> int -> int
smolkas@53591
    51
blanchet@56554
    52
  structure Canonical_Label_Tab : TABLE
smolkas@53693
    53
smolkas@53693
    54
  val canonical_label_ord : (label * label) -> order
blanchet@56562
    55
blanchet@56562
    56
  val chain_isar_proof : isar_proof -> isar_proof
blanchet@56562
    57
  val kill_useless_labels_in_isar_proof : isar_proof -> isar_proof
blanchet@56555
    58
  val relabel_isar_proof_canonically : isar_proof -> isar_proof
blanchet@56562
    59
  val relabel_isar_proof_finally : isar_proof -> isar_proof
smolkas@53693
    60
blanchet@56599
    61
  val string_of_isar_proof : Proof.context -> int -> int ->
blanchet@56586
    62
    (label -> proof_method list -> string) -> isar_proof -> string
blanchet@55877
    63
end;
smolkas@51274
    64
blanchet@56544
    65
structure Sledgehammer_Isar_Proof : SLEDGEHAMMER_ISAR_PROOF =
smolkas@51274
    66
struct
smolkas@51274
    67
blanchet@56553
    68
open ATP_Util
blanchet@56553
    69
open ATP_Proof
blanchet@56553
    70
open ATP_Problem_Generate
blanchet@56553
    71
open ATP_Proof_Reconstruct
blanchet@56553
    72
open Sledgehammer_Util
blanchet@56553
    73
open Sledgehammer_Reconstructor
blanchet@56553
    74
open Sledgehammer_Isar_Annotate
blanchet@56553
    75
smolkas@51274
    76
type label = string * int
blanchet@55907
    77
type facts = label list * string list (* local and global facts *)
smolkas@51274
    78
smolkas@52315
    79
datatype isar_qualifier = Show | Then
smolkas@51274
    80
blanchet@56560
    81
datatype proof_method =
blanchet@56599
    82
  Metis_Method of string option * string option |
blanchet@56599
    83
  Simp_Method |
blanchet@56599
    84
  Simp_Size_Method |
blanchet@56599
    85
  Auto_Method |
blanchet@56599
    86
  Fastforce_Method |
blanchet@56599
    87
  Force_Method |
blanchet@56599
    88
  Arith_Method |
blanchet@56599
    89
  Blast_Method |
blanchet@56599
    90
  Meson_Method |
blanchet@56599
    91
  Algebra_Method
blanchet@56560
    92
smolkas@53591
    93
datatype isar_proof =
blanchet@56042
    94
  Proof of (string * typ) list * (label * term) list * isar_step list
smolkas@52316
    95
and isar_step =
smolkas@51274
    96
  Let of term * term |
blanchet@56622
    97
  Prove of isar_qualifier list * (string * typ) list * label * term * isar_proof list
blanchet@56622
    98
    * facts * proof_method list
smolkas@53729
    99
smolkas@52316
   100
val no_label = ("", ~1)
smolkas@51274
   101
smolkas@53729
   102
val label_ord = pairself swap #> prod_ord int_ord fast_string_ord
smolkas@53693
   103
blanchet@53135
   104
fun string_of_label (s, num) = s ^ string_of_int num
smolkas@51274
   105
blanchet@56564
   106
fun string_of_proof_method meth =
blanchet@56560
   107
  (case meth of
blanchet@56599
   108
    Metis_Method (NONE, NONE) => "metis"
blanchet@56599
   109
  | Metis_Method (type_enc_opt, lam_trans_opt) =>
blanchet@56599
   110
    "metis (" ^ commas (map_filter I [type_enc_opt, lam_trans_opt]) ^ ")"
blanchet@56560
   111
  | Simp_Method => "simp"
blanchet@56599
   112
  | Simp_Size_Method => "simp add: size_ne_size_imp_ne"
blanchet@56560
   113
  | Auto_Method => "auto"
blanchet@56560
   114
  | Fastforce_Method => "fastforce"
blanchet@56560
   115
  | Force_Method => "force"
blanchet@56560
   116
  | Arith_Method => "arith"
blanchet@56560
   117
  | Blast_Method => "blast"
blanchet@56561
   118
  | Meson_Method => "meson"
blanchet@56561
   119
  | Algebra_Method => "algebra")
blanchet@56560
   120
blanchet@56602
   121
fun steps_of_isar_proof (Proof (_, _, steps)) = steps
smolkas@52315
   122
blanchet@56622
   123
fun label_of_isar_step (Prove (_, _, l, _, _, _, _)) = SOME l
blanchet@56565
   124
  | label_of_isar_step _ = NONE
smolkas@52315
   125
blanchet@56623
   126
fun subproofs_of_isar_step (Prove (_, _, _, _, subs, _, _)) = subs
blanchet@56623
   127
  | subproofs_of_isar_step _ = []
smolkas@53591
   128
blanchet@56622
   129
fun facts_of_isar_step (Prove (_, _, _, _, _, facts, _)) = facts
blanchet@56621
   130
  | facts_of_isar_step _ = ([], [])
blanchet@56621
   131
blanchet@56622
   132
fun proof_methods_of_isar_step (Prove (_, _, _, _, _, _, meths)) = meths
blanchet@56621
   133
  | proof_methods_of_isar_step _ = []
smolkas@52316
   134
blanchet@56554
   135
fun fold_isar_step f step =
blanchet@56623
   136
  fold (steps_of_isar_proof #> fold_isar_steps f) (subproofs_of_isar_step step) #> f step
blanchet@56554
   137
and fold_isar_steps f = fold (fold_isar_step f)
smolkas@53591
   138
blanchet@56107
   139
fun map_isar_steps f =
smolkas@53729
   140
  let
blanchet@56554
   141
    fun map_proof (Proof (fix, assms, steps)) = Proof (fix, assms, map map_step steps)
blanchet@56554
   142
    and map_step (step as Let _) = f step
blanchet@56623
   143
      | map_step (Prove (qs, xs, l, t, subs, facts, meths)) =
blanchet@56623
   144
        f (Prove (qs, xs, l, t, map map_proof subs, facts, meths))
blanchet@56554
   145
  in map_proof end
smolkas@53729
   146
blanchet@56554
   147
val add_isar_steps = fold_isar_steps (fn Prove _ => Integer.add 1 | _ => I)
smolkas@53693
   148
blanchet@56553
   149
(* canonical proof labels: 1, 2, 3, ... in post traversal order *)
smolkas@53694
   150
fun canonical_label_ord (((_, i1), (_, i2)) : label * label) = int_ord (i1, i2)
smolkas@53693
   151
blanchet@56554
   152
structure Canonical_Label_Tab = Table(
smolkas@53693
   153
  type key = label
smolkas@53693
   154
  val ord = canonical_label_ord)
smolkas@53693
   155
blanchet@56562
   156
fun chain_qs_lfs NONE lfs = ([], lfs)
blanchet@56562
   157
  | chain_qs_lfs (SOME l0) lfs =
blanchet@56562
   158
    if member (op =) lfs l0 then ([Then], lfs |> remove (op =) l0) else ([], lfs)
blanchet@56623
   159
fun chain_isar_step lbl (Prove (qs, xs, l, t, subs, (lfs, gfs), meths)) =
blanchet@56562
   160
    let val (qs', lfs) = chain_qs_lfs lbl lfs in
blanchet@56623
   161
      Prove (qs' @ qs, xs, l, t, map chain_isar_proof subs, (lfs, gfs), meths)
blanchet@56562
   162
    end
blanchet@56562
   163
  | chain_isar_step _ step = step
blanchet@56562
   164
and chain_isar_steps _ [] = []
blanchet@56562
   165
  | chain_isar_steps (prev as SOME _) (i :: is) =
blanchet@56565
   166
    chain_isar_step prev i :: chain_isar_steps (label_of_isar_step i) is
blanchet@56565
   167
  | chain_isar_steps _ (i :: is) = i :: chain_isar_steps (label_of_isar_step i) is
blanchet@56562
   168
and chain_isar_proof (Proof (fix, assms, steps)) =
blanchet@56562
   169
  Proof (fix, assms, chain_isar_steps (try (List.last #> fst) assms) steps)
blanchet@56562
   170
blanchet@56562
   171
fun kill_useless_labels_in_isar_proof proof =
blanchet@56562
   172
  let
blanchet@56562
   173
    val used_ls =
blanchet@56621
   174
      fold_isar_steps (facts_of_isar_step #> fst #> union (op =)) (steps_of_isar_proof proof) []
blanchet@56562
   175
blanchet@56562
   176
    fun kill_label l = if member (op =) used_ls l then l else no_label
blanchet@56562
   177
blanchet@56623
   178
    fun kill_step (Prove (qs, xs, l, t, subs, facts, meths)) =
blanchet@56623
   179
        Prove (qs, xs, kill_label l, t, map kill_proof subs, facts, meths)
blanchet@56562
   180
      | kill_step step = step
blanchet@56562
   181
    and kill_proof (Proof (fix, assms, steps)) =
blanchet@56562
   182
      Proof (fix, map (apfst kill_label) assms, map kill_step steps)
blanchet@56562
   183
  in
blanchet@56562
   184
    kill_proof proof
blanchet@56562
   185
  end
blanchet@56562
   186
blanchet@56555
   187
fun relabel_isar_proof_canonically proof =
smolkas@53693
   188
  let
smolkas@53693
   189
    fun next_label l (next, subst) =
blanchet@55907
   190
      let val l' = ("", next) in (l', (next + 1, (l, l') :: subst)) end
smolkas@53693
   191
blanchet@56623
   192
    fun relabel_step (Prove (qs, fix, l, t, subs, (lfs, gfs), meths)) (accum as (_, subst)) =
blanchet@55907
   193
        let
blanchet@56623
   194
          val lfs' = maps (the_list o AList.lookup (op =) subst) lfs
blanchet@56623
   195
          val ((subs', l'), accum') = accum
blanchet@56623
   196
            |> fold_map relabel_proof subs
blanchet@56623
   197
            ||>> next_label l
blanchet@55907
   198
        in
blanchet@56623
   199
          (Prove (qs, fix, l', t, subs', (lfs', gfs), meths), accum')
blanchet@55907
   200
        end
blanchet@56623
   201
      | relabel_step step accum = (step, accum)
blanchet@56623
   202
    and relabel_proof (Proof (fix, assms, steps)) =
blanchet@56623
   203
      fold_map (fn (l, t) => next_label l #> apfst (rpair t)) assms
blanchet@56623
   204
      ##>> fold_map relabel_step steps
blanchet@56623
   205
      #>> (fn (assms, steps) => Proof (fix, assms, steps))
smolkas@53693
   206
  in
blanchet@56621
   207
    fst (relabel_proof proof (0, []))
smolkas@53693
   208
  end
smolkas@53693
   209
blanchet@56562
   210
val assume_prefix = "a"
blanchet@56562
   211
val have_prefix = "f"
blanchet@56562
   212
blanchet@56562
   213
val relabel_isar_proof_finally =
blanchet@56562
   214
  let
blanchet@56623
   215
    fun next_label depth prefix l (accum as (next, subst)) =
blanchet@56562
   216
      if l = no_label then
blanchet@56623
   217
        (l, accum)
blanchet@56562
   218
      else
blanchet@56562
   219
        let val l' = (replicate_string (depth + 1) prefix, next) in
blanchet@56623
   220
          (l', (next + 1, (l, l') :: subst))
blanchet@56562
   221
        end
blanchet@56562
   222
blanchet@56623
   223
    fun relabel_step depth (Prove (qs, xs, l, t, subs, (lfs, gfs), meths)) (accum as (_, subst)) =
blanchet@56562
   224
        let
blanchet@56623
   225
          val lfs' = maps (the_list o AList.lookup (op =) subst) lfs
blanchet@56623
   226
          val (l', accum' as (next', subst')) = next_label depth have_prefix l accum
blanchet@56623
   227
          val subs' = map (relabel_proof subst' (depth + 1)) subs
blanchet@56562
   228
        in
blanchet@56623
   229
          (Prove (qs, xs, l', t, subs', (lfs', gfs), meths), accum')
blanchet@56562
   230
        end
blanchet@56623
   231
      | relabel_step _ step accum = (step, accum)
blanchet@56562
   232
    and relabel_proof subst depth (Proof (fix, assms, steps)) =
blanchet@56623
   233
      (1, subst)
blanchet@56623
   234
      |> fold_map (fn (l, t) => next_label depth assume_prefix l #> apfst (rpair t)) assms
blanchet@56623
   235
      ||>> fold_map (relabel_step depth) steps
blanchet@56623
   236
      |> (fn ((assms, steps), _) => Proof (fix, assms, steps))
blanchet@56562
   237
  in
blanchet@56562
   238
    relabel_proof [] 0
blanchet@56562
   239
  end
blanchet@56562
   240
blanchet@56553
   241
val indent_size = 2
blanchet@56553
   242
blanchet@56599
   243
fun string_of_isar_proof ctxt i n comment_of proof =
blanchet@56553
   244
  let
blanchet@56553
   245
    (* Make sure only type constraints inserted by the type annotation code are printed. *)
blanchet@56553
   246
    val ctxt =
blanchet@56553
   247
      ctxt |> Config.put show_markup false
blanchet@56553
   248
           |> Config.put Printer.show_type_emphasis false
blanchet@56553
   249
           |> Config.put show_types false
blanchet@56553
   250
           |> Config.put show_sorts false
blanchet@56553
   251
           |> Config.put show_consts false
blanchet@56553
   252
blanchet@56553
   253
    val register_fixes = map Free #> fold Variable.auto_fixes
blanchet@56553
   254
blanchet@56558
   255
    fun add_str s' = apfst (suffix s')
blanchet@56553
   256
blanchet@56553
   257
    fun of_indent ind = replicate_string (ind * indent_size) " "
blanchet@56553
   258
    fun of_moreover ind = of_indent ind ^ "moreover\n"
blanchet@56553
   259
    fun of_label l = if l = no_label then "" else string_of_label l ^ ": "
blanchet@56553
   260
blanchet@56553
   261
    fun of_obtain qs nr =
blanchet@56553
   262
      (if nr > 1 orelse (nr = 1 andalso member (op =) qs Then) then "ultimately "
blanchet@56553
   263
       else if nr = 1 orelse member (op =) qs Then then "then "
blanchet@56553
   264
       else "") ^ "obtain"
blanchet@56553
   265
blanchet@56553
   266
    fun of_show_have qs = if member (op =) qs Show then "show" else "have"
blanchet@56553
   267
    fun of_thus_hence qs = if member (op =) qs Show then "thus" else "hence"
blanchet@56553
   268
blanchet@56553
   269
    fun of_have qs nr =
blanchet@56553
   270
      if nr > 1 orelse (nr = 1 andalso member (op =) qs Then) then "ultimately " ^ of_show_have qs
blanchet@56553
   271
      else if nr = 1 orelse member (op =) qs Then then of_thus_hence qs
blanchet@56553
   272
      else of_show_have qs
blanchet@56553
   273
blanchet@56553
   274
    fun add_term term (s, ctxt) =
blanchet@56553
   275
      (s ^ (term
blanchet@56553
   276
            |> singleton (Syntax.uncheck_terms ctxt)
blanchet@56555
   277
            |> annotate_types_in_term ctxt
blanchet@56553
   278
            |> with_vanilla_print_mode (Syntax.unparse_term ctxt #> Pretty.string_of)
blanchet@56553
   279
            |> simplify_spaces
blanchet@56553
   280
            |> maybe_quote),
blanchet@56553
   281
       ctxt |> Variable.auto_fixes term)
blanchet@56553
   282
blanchet@56553
   283
    fun with_facts none _ [] [] = none
blanchet@56553
   284
      | with_facts _ some ls ss = some (space_implode " " (map string_of_label ls @ ss))
blanchet@56553
   285
blanchet@56553
   286
    val using_facts = with_facts "" (enclose "using " " ")
blanchet@56599
   287
blanchet@56599
   288
    fun maybe_paren s = s |> String.isSubstring " " s ? enclose "(" ")"
blanchet@56599
   289
    fun by_facts meth ls ss =
blanchet@56599
   290
      "by " ^ with_facts (maybe_paren meth) (enclose ("(" ^ meth ^ " ") ")") ls ss
blanchet@56599
   291
blanchet@56599
   292
    fun is_direct_method (Metis_Method _) = true
blanchet@56599
   293
      | is_direct_method Meson_Method = true
blanchet@56599
   294
      | is_direct_method _ = false
blanchet@56553
   295
blanchet@56553
   296
    (* Local facts are always passed via "using", which affects "meson" and "metis". This is
blanchet@56553
   297
       arguably stylistically superior, because it emphasises the structure of the proof. It is also
blanchet@56553
   298
       more robust w.r.t. preplay: Preplay is performed before chaining of local facts with "hence"
blanchet@56553
   299
       and "thus" is introduced. See also "tac_of_method" in "Sledgehammer_Isar_Preplay". *)
blanchet@56599
   300
    fun of_method ls ss meth =
blanchet@56623
   301
      let val direct = is_direct_method meth in
blanchet@56623
   302
        using_facts ls (if direct then [] else ss) ^
blanchet@56623
   303
        by_facts (string_of_proof_method meth) [] (if direct then ss else [])
blanchet@56623
   304
      end
blanchet@56553
   305
blanchet@56553
   306
    fun of_free (s, T) =
blanchet@56553
   307
      maybe_quote s ^ " :: " ^
blanchet@56553
   308
      maybe_quote (simplify_spaces (with_vanilla_print_mode (Syntax.string_of_typ ctxt) T))
blanchet@56553
   309
blanchet@56553
   310
    fun add_frees xs (s, ctxt) =
blanchet@56553
   311
      (s ^ space_implode " and " (map of_free xs), ctxt |> register_fixes xs)
blanchet@56553
   312
blanchet@56553
   313
    fun add_fix _ [] = I
blanchet@56623
   314
      | add_fix ind xs = add_str (of_indent ind ^ "fix ") #> add_frees xs #> add_str "\n"
blanchet@56553
   315
blanchet@56553
   316
    fun add_assm ind (l, t) =
blanchet@56623
   317
      add_str (of_indent ind ^ "assume " ^ of_label l) #> add_term t #> add_str "\n"
blanchet@56553
   318
blanchet@56553
   319
    fun of_subproof ind ctxt proof =
blanchet@56553
   320
      let
blanchet@56553
   321
        val ind = ind + 1
blanchet@56553
   322
        val s = of_proof ind ctxt proof
blanchet@56553
   323
        val prefix = "{ "
blanchet@56553
   324
        val suffix = " }"
blanchet@56553
   325
      in
blanchet@56553
   326
        replicate_string (ind * indent_size - size prefix) " " ^ prefix ^
blanchet@56553
   327
        String.extract (s, ind * indent_size, SOME (size s - ind * indent_size - 1)) ^
blanchet@56553
   328
        suffix ^ "\n"
blanchet@56553
   329
      end
blanchet@56553
   330
    and of_subproofs _ _ _ [] = ""
blanchet@56623
   331
      | of_subproofs ind ctxt qs subs =
blanchet@56553
   332
        (if member (op =) qs Then then of_moreover ind else "") ^
blanchet@56623
   333
        space_implode (of_moreover ind) (map (of_subproof ind ctxt) subs)
blanchet@56623
   334
    and add_step_pre ind qs subs (s, ctxt) =
blanchet@56623
   335
      (s ^ of_subproofs ind ctxt qs subs ^ of_indent ind, ctxt)
blanchet@56553
   336
    and add_step ind (Let (t1, t2)) =
blanchet@56558
   337
        add_str (of_indent ind ^ "let ")
blanchet@56623
   338
        #> add_term t1 #> add_str " = " #> add_term t2 #> add_str "\n"
blanchet@56623
   339
      | add_step ind (Prove (qs, xs, l, t, subs, (ls, ss), meths as meth :: _)) =
blanchet@56623
   340
        add_step_pre ind qs subs
blanchet@56553
   341
        #> (case xs of
blanchet@56623
   342
             [] => add_str (of_have qs (length subs) ^ " ")
blanchet@56623
   343
           | _ => add_str (of_obtain qs (length subs) ^ " ") #> add_frees xs #> add_str " where ")
blanchet@56559
   344
        #> add_str (of_label l)
blanchet@56559
   345
        #> add_term t
blanchet@56559
   346
        #> add_str (" " ^
blanchet@56560
   347
             of_method (sort_distinct label_ord ls) (sort_distinct string_ord ss) meth ^
blanchet@56586
   348
             (case comment_of l meths of
blanchet@56564
   349
               "" => ""
blanchet@56564
   350
             | comment => " (* " ^ comment ^ " *)") ^ "\n")
blanchet@56553
   351
    and add_steps ind = fold (add_step ind)
blanchet@56553
   352
    and of_proof ind ctxt (Proof (xs, assms, steps)) =
blanchet@56623
   353
      ("", ctxt)
blanchet@56623
   354
      |> add_fix ind xs
blanchet@56623
   355
      |> fold (add_assm ind) assms
blanchet@56623
   356
      |> add_steps ind steps
blanchet@56623
   357
      |> fst
blanchet@56553
   358
  in
blanchet@56553
   359
    (* One-step Metis proofs are pointless; better use the one-liner directly. *)
blanchet@56553
   360
    (case proof of
blanchet@56553
   361
      Proof ([], [], []) => "" (* degenerate case: the conjecture is "True" with Z3 *)
blanchet@56622
   362
    | Proof ([], [], [Prove (_, [], _, _, [], _, Metis_Method _ :: _)]) => ""
blanchet@56553
   363
    | _ =>
blanchet@56553
   364
      (if i <> 1 then "prefer " ^ string_of_int i ^ "\n" else "") ^
blanchet@56553
   365
      of_indent 0 ^ "proof -\n" ^ of_proof 1 ctxt proof ^
blanchet@56553
   366
      of_indent 0 ^ (if n <> 1 then "next" else "qed"))
blanchet@56553
   367
  end
blanchet@56553
   368
blanchet@55877
   369
end;