/*
   Copyright (C) 2004 - 2016 by Guillaume Melquiond <guillaume.melquiond@inria.fr>
   Part of the Gappa tool https://gappa.gitlabpages.inria.fr/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the CeCILL Free Software License Agreement or
   under the terms of the GNU General Public License version.

   See the COPYING and COPYING.GPL files for more details.
*/

#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include "backends/backend.hpp"
#include "backends/coq_common.hpp"
#include "numbers/interval_utility.hpp"
#include "numbers/real.hpp"
#include "parser/ast.hpp"
#include "proofs/proof_graph.hpp"
#include "utils.hpp"

#define GAPPADEF "Gappa.Gappa_definitions."
#define COQRDEF "Reals.Rdefinitions."

extern std::string get_real_split(number const &f, int &exp, bool &zero, bool);

namespace coq {

static char const *theorem_defs[][2] = {
  { "subset", "$gpred_bnd.$t $x $1i $i $" },
  { "subset_l", "$gpred_bnd.$t $x $1i $l $" },
  { "subset_r", "$gpred_bnd.$t $x $1i $u $" },
  { "abs_subset", "$gpred_abs.$t $x $1i $i $" },
  { "rel_subset", "$gpred_rel.$t $x $y $1i $i $" },
  { "lin_subset", "$gpred_lin.$t $x $y $1i $i $" },
  { "fix_subset", "$gpred_fixflt.$t $x $1c $c $" },
  { "flt_subset", "$gpred_fixflt.$t $x $1c $c $" },

  { "intersect", "$gpred_bnd.$t $x $1i $2i $i $" },
  { "intersect_hb", "$gpred_bnd.$t $x $1u $2i $i $" },
  { "intersect_bh", "$gpred_bnd.$t $x $2l $1i $i $" },
  { "intersect_hh", "$gpred_bnd.$t $x $1u $2l $i $" },
  { "intersect_aa", "$gpred_abs.$t $x $1i $2i $i $" },
  { "intersect_rr", "$gpred_rel.$t $x $y $1i $2i $i $" },
  { "intersect_ll", "$gpred_lin.$t $x $y $1i $2i $i $" },
  { "intersect_rr0", "$gpred_rel.$t $1x $1y $1i $2i $i $" },
  { "intersect_ll0", "$gpred_lin.$t $1x $1y $1i $2i $i $" },
  { "absurd_intersect", "$gpred_bnd.$t $1x $1i $2i $" },
  { "absurd_intersect_hb", "$gpred_bnd.$t $1x $1u $2i $" },
  { "absurd_intersect_bh", "$gpred_bnd.$t $1x $1i $2l $" },
  { "absurd_intersect_aa", "$gpred_abs.$t $1x $1i $2i $" },

  { "bnd_of_abs", "$gpred_abs.$t $1x $1i $i $" },
  { "abs_of_bnd_p", "$gpred_abs.$t $1x $1i $i $" },
  { "abs_of_bnd_o", "$gpred_abs.$t $1x $1i $i $" },
  { "abs_of_bnd_n", "$gpred_abs.$t $1x $1i $i $" },
  { "abs_of_uabs", "$gpred_abs.$t $x $1i $" },
  { "uabs_of_abs", "$gpred_abs.$t $1x $i $1p" },
  { "bnd_of_bnd_abs_p", "$gpred_abs.$t $1x $1i $2i $i $" },
  { "bnd_of_bnd_abs_n", "$gpred_abs.$t $1x $1i $2i $i $" },

  { "constant1", "$gpred_bnd.$t _ $i $" },
  { "constant2", "$gpred_bnd.$t _ $i $" },
  { "constant10", "$gpred_bnd.$t _ $i $" },

  { "neg", "$gpred_bnd.$t $1x $1i $i $" },
  { "add", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "sub", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_pp", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_po", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_pn", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_op", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_oo", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_on", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_np", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_no", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "mul_nn", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_pp", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_pn", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_op", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_on", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_np", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "div_nn", "$gpred_bnd.$t $1x $2x $1i $2i $i $" },
  { "square", "$gpred_bnd.$t $1x $1i $i $" },
  { "sqrt", "$gpred_bnd.sqrtG $1x $1i $i $" },
  { "square_rev", "$gpred_bnd.$t $x $1i $i $" },

  { "sub_refl", "$gpred_bnd.$t _ $i $" },
  { "div_refl", "$gpred_bnd.$t $1x $i $" },
  { "sub_of_eql", "$gpred_bnd.$t $1x $1y $i $" },
  { "eql_of_cst", "$gpred_bnd.$t $1x $2x $1i $2i $" },

  { "neg_a", "$gpred_abs.$t $1x $i $1p" },
  { "abs_a", "$gpred_abs.$t $1x $i $1p" },
  { "add_aa_p", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "add_aa_o", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "add_aa_n", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "sub_aa_p", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "sub_aa_o", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "sub_aa_n", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "mul_aa", "$gpred_abs.$t $1x $2x $1i $2i $i $" },
  { "div_aa", "$gpred_abs.$t $1x $2x $1i $2i $i $" },

  { "add_rr_left", "$gpred_rel.$t $1x $1y $2x $2y $1i $2i $3i $i $" },
  { "add_rr_right", "$gpred_rel.$t $1x $1y $2x $2y $1i $2i $3i $i $" },
  { "sub_rr_left", "$gpred_rel.$t $1x $1y $2x $2y $1i $2i $3i $i $" },
  { "sub_rr_right", "$gpred_rel.$t $1x $1y $2x $2y $1i $2i $3i $i $" },
  { "mul_rr", "$gpred_rel.$t $1x $1y $2x $2y $1i $2i $i $" },
  { "div_rr", "$gpred_rel.div_rr' $1x $1y $2x $2y $1i $2i $i $" },
  { "inv_r", "$gpred_rel.inv_r' $1x $1y _ $1i $i $" },
  { "compose", "$gpred_rel.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_swap", "$gpred_rel.$t $1x $y $2x $3x $1i $2i $i $" },

  { "abs_fix", "$gpred_fixflt.$t $1x $1c $c $" },
  { "neg_fix", "$gpred_fixflt.$t $1x $1c $c $" },
  { "add_fix", "$gpred_fixflt.$t $1x $2x $1c $2c $c $" },
  { "sub_fix", "$gpred_fixflt.$t $1x $2x $1c $2c $c $" },
  { "mul_fix", "$gpred_fixflt.$t $1x $2x $1c $2c $c $" },
  { "abs_flt", "$gpred_fixflt.$t $1x $1c $c $" },
  { "neg_flt", "$gpred_fixflt.$t $1x $1c $c $" },
  { "mul_flt", "$gpred_fixflt.$t $1x $2x $1c $2c $c $" },
  { "add_flt_lin", "$gpred_fixflt.$t $1x $2x $1c $2c $3i $c $" },
  { "sub_flt_lin", "$gpred_fixflt.$t $1x $2x $1c $2c $3i $c $" },
  { "add_flt_lin_rev", "$gpred_fixflt.$t $1x $2x $1c $2c $3i $c $" },
  { "sub_flt_lin_rev", "$gpred_fixflt.$t $1x $2x $1c $2c $3i $c $" },

  { "neg_nzr", "$gpred_nzr.$t $1x $1p" },
  { "mul_nzr", "$gpred_nzr.$t $1x $2x $!" },
  { "div_nzr", "$gpred_nzr.$t $1x $2x $!" },
  { "div_nzr_rev_l", "$gpred_nzr.$t $x _ $1p" },
  { "div_nzr_rev_r", "$gpred_nzr.$t _ $x $1p" },
  { "nzr_of_abs", "$gpred_nzr.$t $x $1i $" },
  { "nzr_of_nzr_rel", "$gpred_nzr.$t $x $1x $2i $" },
  { "nzr_of_nzr_rel_rev", "$gpred_nzr.$t $1x $x $2i $!" },

  { "rel_refl", "$gpred_rel.$t $x $i $" },
  { "bnd_of_nzr_rel", "$gpred_rel.$t $2x $1x $i $!" },
  { "bnd_of_rel", "$gpred_rel.$t $1x $1y $i $" },
  { "rel_of_nzr_bnd", "$gpred_rel.$t $x $1x $2i $!" },
  { "error_of_rel_pp", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_po", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_pn", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_op", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_oo", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_on", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_np", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_no", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "error_of_rel_nn", "$gpred_rel.$t $1x $2x $1i $2i $i $" },
  { "bnd_of_bnd_rel_p", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "bnd_of_bnd_rel_o", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "bnd_of_bnd_rel_n", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "bnd_of_rel_bnd_p", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "bnd_of_rel_bnd_o", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "bnd_of_rel_bnd_n", "$gpred_rel.$t $2x $2y $1i $2i $i $" },
  { "lin_of_lin_rel1_p", "$gpred_rel.$t $1x $y $2x $1i $2i $i $" },
  { "lin_of_lin_rel1_o", "$gpred_rel.$t $1x $y $2x $1i $2i $i $" },
  { "lin_of_lin_rel1_n", "$gpred_rel.$t $1x $y $2x $1i $2i $i $" },
  { "lin_of_lin_rel2_p", "$gpred_rel.$t $x $1y $y $1i $2i $i $" },
  { "lin_of_lin_rel2_o", "$gpred_rel.$t $x $1y $y $1i $2i $i $" },
  { "lin_of_lin_rel2_n", "$gpred_rel.$t $x $1y $y $1i $2i $i $" },
  { "lin_of_lin_rel3_p", "$gpred_rel.$t $1x $y $x $1i $2i $i $" },
  { "lin_of_lin_rel3_o", "$gpred_rel.$t $1x $y $x $1i $2i $i $" },
  { "lin_of_lin_rel3_n", "$gpred_rel.$t $1x $y $x $1i $2i $i $" },

  { "lin_of_bnd_div", "$gpred_lin.$t $x $y $i $2!" },
  { "bnd_div_of_lin", "$gpred_lin.$t $2x $2y $2i $!" },
  { "bnd_of_lin", "$gpred_lin.$t $1x $1y $i $" },
  { "lin_of_rel", "$gpred_lin.$t $x $y $1i $i $" },
  { "lin_refl", "$gpred_lin.$t $x $i $" },
  { "add_ll", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "sub_ll", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "minus_lin", "$gpred_lin.$t $1x $y $1i $i $" },
  { "mul_ll_pp", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_po", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_pn", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_op", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_oo", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_on", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_np", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_no", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "mul_ll_nn", "$gpred_lin.$t $1x $2x $1y $2y $1i $2i $i $" },
  { "compose_lin_pp", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_po", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_pn", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_op", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_oo", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_on", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_np", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_no", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "compose_lin_nn", "$gpred_lin.$t $1x $1y $2y $1i $2i $i $" },
  { "lin_of_lin_rel_pp", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_po", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_pn", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_op", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_oo", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_on", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_np", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_no", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },
  { "lin_of_lin_rel_nn", "$gpred_lin.$t $1x $2x $y $1i $2i $i $" },

  { "fix_of_singleton_bnd", "$gpred_fixflt.$t $x $1i $c $" },
  { "flt_of_singleton_bnd", "$gpred_fixflt.$t $x $1i $c $" },
  { "fix_of_flt_bnd", "$gpred_fixflt.$t $1x $2i $c $1c $" },
  { "flt_of_fix_bnd", "$gpred_fixflt.$t $1x $2i $1c $c $" },

  { "fix_of_float", "$gfloat.$t _ _ _ _ $c $" },
  { "flt_of_float", "$gfloat.$t _ _ _ $c _ $" },
  { "float_of_fix_flt", "$gfloat.$t _ $1x $1c _ $2c _ $" },
  { "fix_float_of_fix", "$gfloat.$t _ _ _ $1c $c $1x $" },
  { "flt_float_of_flt", "$gfloat.$t _ _ _ $1c $c $1x $" },

  { "float_round_dn", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_round_up", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_round_zr", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_round_ne", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_round_na", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_enforce", "$gfloat.$t _ _ _ _ $1i $i $" },
  { "float_absolute_dn", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_up", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_zr", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_ne", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_na", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_wide_ne", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_absolute_inv_dn", "$gfloat.$t _ _ _ $1i $i $" },
  { "float_absolute_inv_up", "$gfloat.$t _ _ _ $1i $i $" },
  { "float_absolute_inv_ne", "$gfloat.$t _ _ _ $1i $i $" },
  { "float_absolute_inv_na", "$gfloat.$t _ _ _ $1i $i $" },
  { "float_relative_dn", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_relative_up", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_relative_zr", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_relative_ne", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_relative_na", "$gfloat.$t _ _ $1x $1i $i $" },
  { "float_relative_inv_ne", "$gfloat.$t _ _ $y $1i $i $" },
  { "float_relative_inv_na", "$gfloat.$t _ _ $y $1i $i $" },
  { "rel_of_fix_float_dn", "$gfloat.$t _ _ $1c $1x $i $" },
  { "rel_of_fix_float_up", "$gfloat.$t _ _ $1c $1x $i $" },
  { "rel_of_fix_float_zr", "$gfloat.$t _ _ $1c $1x $i $" },
  { "rel_of_fix_float_ne", "$gfloat.$t _ _ $1c $1x $i $" },
  { "rel_of_fix_float_na", "$gfloat.$t _ _ $1c $1x $i $" },
  { "lin_float_error_of_lin_abs_ne", "$gfloat.$t _ _ $1x $2x $1i $2i $i $" },
  { "lin_float_error_of_lin_abs_na", "$gfloat.$t _ _ $1x $2x $1i $2i $i $" },

  { "flt_of_floatx", "$gfloat.$t _ _ $c _ $" },
  { "floatx_of_flt", "$gfloat.$t _ $1x $1c _ $" },
  { "fix_floatx_of_fix", "$gfloat.$t _ _ $1c $c $1x $" },
  { "flt_floatx_of_flt", "$gfloat.$t _ _ $1c $c $1x $" },

  { "floatx_round_dn", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_round_up", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_round_zr", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_round_ne", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_round_na", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_enforce", "$gfloat.$t _ _ _ $1i $i $" },
  { "floatx_absolute_ne", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_absolute_na", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_absolute_wide_ne", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_absolute_wide_na", "$gfloat.$t _ $1x $1i $i $" },
  { "floatx_relative_ne", "$gfloat.$t _ $y $i $" },
  { "floatx_relative_na", "$gfloat.$t _ $y $i $" },
  { "floatx_relative_aw", "$gfloat.$t _ $y $i $" },
  { "lin_floatx_round_of_lin_flt_ne", "$gfloat.$t _ $1x $2x $1i $i $" },
  { "lin_floatx_error_of_lin_flt_wide_ne", "$gfloat.$t _ $1x $2x $1i $i $" },

  { "fix_of_fixed", "$gfixed.$t _ _ _ $c $" },
  { "fixed_of_fix", "$gfixed.$t _ $1x $1c _ $" },
  { "fix_fixed_of_fix", "$gfixed.$t _ _ $1c $c $1x $" },
  { "flt_fixed_of_flt", "$gfixed.$t _ _ $1c $c $1x $" },
  { "bnd_of_bnd_fix", "$gfixed.$t $1x $2c $1i $i $" },

  { "fixed_round_dn", "$gfixed.$t _ $1x $1i $i $" },
  { "fixed_round_up", "$gfixed.$t _ $1x $1i $i $" },
  { "fixed_round_zr", "$gfixed.$t _ $1x $1i $i $" },
  { "fixed_round_ne", "$gfixed.$t _ $1x $1i $i $" },
  { "fixed_round_na", "$gfixed.$t _ $1x $1i $i $" },
  { "fixed_error_dn", "$gfixed.$t _ _ $i $" },
  { "fixed_error_up", "$gfixed.$t _ _ $i $" },
  { "fixed_error_zr", "$gfixed.$t _ _ $i $" },
  { "fixed_error_ne", "$gfixed.$t _ _ $i $" },
  { "fixed_error_na", "$gfixed.$t _ _ $i $" },

  { "bnd_rewrite", "$grewriting.$t $1x $1y $i $2!" },
  { "abs_rewrite", "$grewriting.$t $1x $1y $i $2!" },
  { "fix_rewrite", "$grewriting.$t $1x $1y $c $2!" },
  { "flt_rewrite", "$grewriting.$t $1x $1y $c $2!" },
  { "nzr_rewrite", "$grewriting.$t $1x $1y $2!" },
  { "rel_rewrite_1", "$grewriting.$t $1x $1y $y $i $2!" },
  { "rel_rewrite_2", "$grewriting.$t $1x $1y $x $i $2!" },
  { "lin_rewrite_1", "$grewriting.$t $1x $1y $y $i $2!" },
  { "lin_rewrite_2", "$grewriting.$t $1x $1y $x $i $2!" },
  { "eql_trans", "$grewriting.$t $x $1y $y $2!" },

  { "opp_mibs", "$grewriting.$t _ _ $i $1p" },
  { "opp_xibu", "$grewriting.$t $x $i $1p" },
  { "add_xals", "$grewriting.$t _ _ _ $i $1p" },
  { "add_xars", "$grewriting.$t _ _ _ $i $1p" },
  { "sub_xals", "$grewriting.$t _ _ _ $i $1p" },
  { "sub_xars", "$grewriting.$t _ _ _ $i $1p" },
  { "mul_xals", "$grewriting.$t _ _ _ $i $1p" },
  { "mul_xars", "$grewriting.$t _ _ _ $i $1p" },
  { "add_mibs", "$grewriting.$t _ _ _ _ $i $1p" },
  { "add_fils", "$grewriting.$t _ _ _ $i $1p" },
  { "add_firs", "$grewriting.$t _ _ _ $i $1p" },
  { "add_filq", "$grewriting.add_filq' _ _ _ $i $1!" },
  { "add_firq", "$grewriting.add_firq' _ _ _ $i $1!" },
  { "add_xilu", "$grewriting.$t $x _ $i $1p" },
  { "add_xiru", "$grewriting.$t _ $x $i $1p" },
  { "sub_mibs", "$grewriting.$t _ _ _ _ $i $1p" },
  { "sub_fils", "$grewriting.$t _ _ _ $i $1p" },
  { "sub_firs", "$grewriting.$t _ _ _ $i $1p" },
  { "sub_filq", "$grewriting.sub_filq' _ _ _ $i $1!" },
  { "sub_firq", "$grewriting.sub_firq' _ _ _ $i $1!" },
  { "mul_fils", "$grewriting.$t _ _ _ $i $1p" },
  { "mul_firs", "$grewriting.$t _ _ _ $i $1p" },
  { "mul_mals", "$grewriting.$t _ _ _ _ $i $1p" },
  { "mul_mars", "$grewriting.$t _ _ _ _ $i $1p" },
  { "mul_mabs", "$grewriting.$t _ _ _ _ $i $1p" },
  { "mul_mibs", "$grewriting.$t _ _ _ _ $i $1p" },
  { "mul_xilu", "$grewriting.$t $x _ $i $2!" },
  { "mul_xiru", "$grewriting.$t _ $x $i $2!" },
  { "err_xalq", "$grewriting.$t _ $2x $3x $i $!" },
  { "sqrt_mibs", "$grewriting.$t $1x $2x $1i $2i $3i $" },
  { "sqrt_mibq", "$grewriting.$t $1x $2x $1i $2i $3i $" },
  { "sqrt_xibu", "$grewriting.$t $x $1i $2i $" },
  { "square_sqrt", "$grewriting.$t $1x $2i $2p _" },
  { "val_xebs", "$grewriting.$t $x _ $i $1p" },
  { "val_xabs", "$grewriting.$t _ $x $i $1p" },
  { "div_mibq", "$grewriting.$t _ $1x $2x $3x $i $4!" },
  { "div_xals", "$grewriting.div_xals' _ _ _ $i $1!" },
  { "div_fil", "$grewriting.$t $1x $2x $i $!" },
  { "div_fir", "$grewriting.$t $2x $1x $i $!" },
  { "div_firs", "$grewriting.div_firs' _ _ _ $i $1!" },
  { "div_xilu", "$grewriting.$t $x _ $i $2!" },
  { "div_xiru", "$grewriting.div_xiru' $1x $x $i $2!" },
  { "err_xabq", "$grewriting.$t _ $1x $i $2!" },
  { "err_fabq", "$grewriting.$t _ $1x $i $2!" },
  { "addf_9", "$grewriting.$t _ $1x $i $3!" },
  { "addf_10", "$grewriting.$t $1x _ $i $3!" },
  { "addf_11", "$grewriting.$t _ $1x $i $3!" },
  { "addf_12", "$grewriting.$t $1x _ $i $3!" },

  { "opp_fibq", "$grewriting.$t $1x $1y $i $1p" },
  { "mul_filq", "$grewriting.$t _ $1x $1y $i $1p" },
  { "mul_firq", "$grewriting.$t $1x _ $1y $i $1p" },
  { "div_firq", "$grewriting.$t $1x _ $1y $i $1p" },

  { "opp_fibm", "$grewriting.$t $1x $1y $i $1p" },
  { "add_mibm", "$grewriting.$t _ _ _ _ $y $i $1p" },
  { "add_film", "$grewriting.$t _ _ _ $y $i $1p" },
  { "add_firm", "$grewriting.$t _ _ _ $y $i $1p" },
  { "sub_mibm", "$grewriting.$t _ _ _ _ $y $i $1p" },
  { "sub_film", "$grewriting.$t _ _ _ $y $i $1p" },
  { "sub_firm", "$grewriting.$t _ _ _ $y $i $1p" },
  { "sub_xalm", "$grewriting.$t _ _ _ $y $i $1p" },
  { "mul_film", "$grewriting.$t _ $1x $1y $i $1p" },
  { "mul_firm", "$grewriting.$t $1x _ $1y $i $1p" },
  { "mul_fism", "$grewriting.$t _ $1x $1y $i $1p" },
  { "mul_fitm", "$grewriting.$t $1x _ $1y $i $1p" },
  { "div_firm", "$grewriting.$t $1x _ $1y $i $1p" },
  { "val_xabm", "$grewriting.$t _ $x $y $i $1p" },

  { "opp_fibe", "$grewriting.$t $1x $1y $1p" },
  { "add_file", "$grewriting.$t _ $1x $1y $1p" },
  { "add_fire", "$grewriting.$t $1x _ $1y $1p" },
  { "sub_file", "$grewriting.$t _ $1x $1y $1p" },
  { "sub_fire", "$grewriting.$t $1x _ $1y $1p" },
  { "mul_file", "$grewriting.$t _ $1x $1y $1p" },
  { "mul_fire", "$grewriting.$t $1x _ $1y $1p" },
  { "div_file", "$grewriting.$t _ $1x $1y $1p" },
  { "div_fire", "$grewriting.$t $1x _ $1y $1p" },

  { "lin_of_rel2", "$grewriting.$t $1x $y $i $1p" },
  { "rel_of_lin", "$grewriting.$t $x $y $i $1p" },
};

theorem_map theorems;

RUN_ONCE(fill_theorem_map)
{
  for (auto p: theorem_defs) {
    auto v = theorems.insert(theorem_map::value_type(p[0], p[1]));
    assert(v.second);
  }
};

bool fqn = false;
bool vernac = true;

static char const *qualify(char const *path)
{
  if (fqn) return path;
  else return "";
}

std::string convert_rounding(std::string const &name)
{
  std::string::size_type p0 = name.find(',');
  if (p0 == std::string::npos) return name;
  std::string prefix = name.substr(0, p0);
  std::string::size_type p1 = name.find(',', p0 + 1);
  std::string mode;
  if (p1 != std::string::npos && p1 == p0 + 3) {
    mode = name.substr(p0 + 1, 2);
    mode[0] = std::toupper(mode[0]);
    mode[1] = std::toupper(mode[1]);
  }
  std::ostringstream res;
  if (prefix == "rounding_fixed")
  {
    assert (p1 != std::string::npos && p1 == p0 + 3);
    res << '(' << qualify("Gappa.Gappa_fixed.") << "rounding_fixed "
        << qualify("Gappa.Gappa_round_def.") << "rnd" << mode
        << " (" << name.substr(p1 + 1) << ")%Z)";
    return res.str();
  }
  if (prefix == "rounding_float")
  {
    assert (p1 != std::string::npos && p1 == p0 + 3);
    std::string::size_type p2 = name.find(',', p1 + 1);
    assert(p2 != std::string::npos);
    res << '(' << qualify("Gappa.Gappa_float.") << "rounding_float "
        << qualify("Gappa.Gappa_round_def.") << "rnd" << mode
        << " (" << name.substr(p1 + 1, p2 - p1 - 1) << ")%positive ("
        << name.substr(p2 + 1) << ")%Z)";
    return res.str();
  }
  if (prefix == "rounding_floatx")
  {
    assert (p1 != std::string::npos && p1 == p0 + 3);
    res << '(' << qualify("Gappa.Gappa_float.") << "rounding_floatx "
        << qualify("Gappa.Gappa_round_def.") << "rnd" << mode
        << " (" << name.substr(p1 + 1) << ")%positive)";
    return res.str();
  }
  assert(false);
  return res.str();
}

std::string convert_name(std::string const &name)
{
  std::string::size_type p0 = name.find(',');
  if (p0 == std::string::npos) return name;
  std::ostringstream res;
  res << name.substr(0, p0);
  do {
    std::string::size_type p1 = p0 + 1;
    p0 = name.find(',', p1);
    std::string s(name, p1, p0 == std::string::npos ? p0 : p0 - p1);
    assert(std::isalpha(s[0]));
    res << '_' << s;
  } while (p0 != std::string::npos);
  return res.str();
}

static id_cache< std::string > displayed_floats;

std::string display(number const &f)
{
  std::ostringstream s;
  bool zero;
  int exp;
  s << '(' << get_real_split(f, exp, zero, false);
  s << ") (" << (zero ? 0 : exp) << ')';
  std::string const &s_ = s.str();
  int f_id = displayed_floats.find(s_);
  std::string name = composite('f', f_id);
  if (f_id < 0) return name;
  *out << (vernac ? "Definition " : "let ") << name << " := "
       << qualify(GAPPADEF) << "Float2 " << s_
       << (vernac ? ".\n" : " in\n");
  return name;
}

static id_cache< std::string > displayed_intervals;

std::string display(interval const &i)
{
  std::ostringstream s;
  s << display(lower(i)) << ' ' << display(upper(i));
  std::string const &s_ = s.str();
  int i_id = displayed_intervals.find(s_);
  std::string name = composite('i', i_id);
  if (i_id < 0) return name;
  *out << (vernac ? "Definition " : "let ") << name << " := "
       << qualify(GAPPADEF) << "makepairF " << s_
       << (vernac ? ".\n" : " in\n");
  return name;
}

static id_cache< ast_real const * > displayed_reals;

std::ostream *out_vars;

std::string display(ast_real const *r)
{
  if (hidden_real const *h = boost::get< hidden_real const >(r))
    r = h->real;
  int r_id = displayed_reals.find(r);
  std::string name = r->name ? '_' + r->name->name : composite('r', r_id);
  if (r_id < 0)
    return name;
  if (boost::get< undefined_real const >(r)) {
    *out_vars << (vernac ? "Variable " : " (") << name << " : "
              << qualify(COQRDEF) << 'R' << (vernac ? ".\n" : ")");
    return name;
  }
  auto_flush plouf;
  plouf << (vernac ? "Definition " : "let ") << name << " := ";
  if (ast_number const *const *nn = boost::get< ast_number const *const >(r))
  {
    ast_number const &n = **nn;
    std::string m = (n.mantissa.size() > 0 && n.mantissa[0] == '+') ? n.mantissa.substr(1) : n.mantissa;
    if (n.base == 0)
      plouf << qualify("Gappa.Gappa_pred_bnd.") << "Float1 0";
    else if (n.exponent == 0)
      plouf << qualify("Gappa.Gappa_pred_bnd.") << "Float1 (" << m << ')';
    else
      plouf << qualify(GAPPADEF) << "float" << n.base << "R ("
            << qualify(GAPPADEF) << "Float" << n.base
            << " (" << m << ") (" << n.exponent << "))";
  }
  else if (real_op const *o = boost::get< real_op const >(r))
  {
    static char const op[] = "X-XX+-*/XX";
    if (o->type == UOP_RND)
    {
      std::string description = o->fun->description();
      plouf << convert_rounding(description);
      for (ast_real const *i: o->ops)
        plouf << ' ' << display(i);
    }
    else if (fqn)
    {
      char const *s;
      switch (o->type) {
      case BOP_ADD: s = COQRDEF "Rplus"; break;
      case BOP_SUB: s = COQRDEF "Rminus"; break;
      case BOP_MUL: s = COQRDEF "Rmult"; break;
      case BOP_DIV: s = COQRDEF "Rdiv"; break;
      case UOP_ABS: s = "Reals.Rbasic_fun.Rabs"; break;
      case UOP_SQRT: s = "Reals.R_sqrt.sqrt"; break;
      case UOP_NEG: s = COQRDEF "Ropp"; break;
      default: assert(false);
      }
      plouf << s << ' ' << display(o->ops[0]);
      if (o->ops.size() == 2) plouf << ' ' << display(o->ops[1]);
    }
    else if (o->ops.size() == 1)
    {
      std::string s(1, op[o->type]);
      if (o->type == UOP_ABS) s = "Rabs";
      else if (o->type == UOP_SQRT) s = "sqrt";
      plouf << '(' << s << ' ' << display(o->ops[0]) << ")%R";
    }
    else
    {
      plouf << '(' << display(o->ops[0]) << ' ' << op[o->type] << ' '
            << display(o->ops[1]) << ")%R";
    }
  }
  else
    assert(false);
  plouf << (vernac ? ".\n" : " in\n");
  return name;
}

static id_cache<property> displayed_properties;

std::string display(property const &p)
{
  if (p.null()) return "False";
  int p_id = displayed_properties.find(p);
  std::string name = composite('p', p_id);
  if (p_id < 0) return name;
  std::ostringstream s;
  predicate_type t = p.real.pred();
  ast_real const *real = p.real.real();
  if (p.real.pred_bnd())
  {
    interval const &bnd = p.bnd();
    if (lower(bnd) == number::neg_inf) {
      assert(t == PRED_BND);
      if (fqn)
        s << COQRDEF "Rle " << display(real) << ' ' << display(upper(bnd));
      else
        s << '(' << display(real) << " <= " << display(upper(bnd)) << ")%R";
    } else if (upper(bnd) == number::pos_inf) {
      assert(t == PRED_BND);
      if (fqn)
        s << COQRDEF "Rle " << display(lower(bnd)) << ' ' << display(real);
      else
        s << '(' << display(lower(bnd)) << " <= " << display(real) << ")%R";
    }
    else
    {
      switch (t) {
      case PRED_BND:
        s << qualify(GAPPADEF) << "BND " << display(real) << ' ' << display(bnd);
        break;
      case PRED_ABS:
        s << qualify(GAPPADEF) << "ABS " << display(real) << ' ' << display(bnd);
        break;
      case PRED_REL:
        s << qualify(GAPPADEF) << "REL " << display(real) << ' '
          << display(p.real.real2()) << ' ' << display(bnd);
        break;
      case PRED_LIN:
        s << qualify(GAPPADEF) << "LIN " << display(real) << ' '
          << display(p.real.real2()) << ' ' << display(bnd);
        break;
      default:
        assert(false);
      }
    }
  }
  else
  {
    switch (t) {
    case PRED_FIX:
      s << qualify(GAPPADEF) << "FIX " << display(real) << " (" << p.cst() << ')';
      break;
    case PRED_FLT:
      s << qualify(GAPPADEF) << "FLT " << display(real) << " (" << p.cst() << ')';
      break;
    case PRED_EQL:
      s << display(real) << " = " << display(p.real.real2());
      break;
    case PRED_NZR:
      s << qualify(GAPPADEF) << "NZR " << display(real);
      break;
    default:
      assert(false);
    }
  }
  *out << (vernac ? "Definition " : "let ") << name << " := "
       << s.str() << (vernac ? ". (* " : " in (* ") << dump_property(p) << " *)\n";
  return name;
}

static property const &fetch(property const &p)
{
  if (p.real.pred_bnd() && !is_defined(p.bnd())) {
    undefined_map::const_iterator i = instances->find(p.real);
    assert(i != instances->end());
    return i->second;
  }
  return p;
}

static id_cache<property_tree> displayed_trees;

std::string display(property_tree const &t)
{
  if (t.empty()) return "False";
  if (t.atom && t.conjunction) return display(fetch(*t.atom));
  int t_id = displayed_trees.find(t);
  std::string name = composite('s', t_id);
  if (t_id < 0) return name;
  auto_flush plouf;
  plouf << (vernac ? "Definition " : "let ") << name << " := ";
  if (!t.left) {
    assert(!t.conjunction);
    plouf << "not " << display(fetch(*t.atom));
  } else {
    plouf << display(*t.left) << (t.conjunction ? " /\\ " : " \\/ ")
      << display(*t.right);
  }
  plouf << (vernac ? ".\n" : " in\n");
  return name;
}

static void apply_hypothesis(auto_flush &plouf, predicated_real const &hr,
  int hn, property_map const *pmap, int *num)
{
  plouf << 'h';
  if (pmap) {
    property_map::const_iterator pki = pmap->find(hr);
    assert(pki != pmap->end());
    plouf << pki->second.first;
  } else plouf << (num ? num[hn] : hn);
}

void apply_theorem(auto_flush &plouf, std::string const &th,
  property const &res, property const *hyp, property_map const *pmap, int *num)
{
  theorem_map::const_iterator it = theorems.find(th);
  if (it == theorems.end())
  {
    plouf << "UNKNOWN_" << th;
    return;
  }
  int max = -1;
  for (char const *p = it->second.c_str(); *p; ++p)
  {
    char const *q = strchr(p, '$');
    if (!q) { plouf << p; return; }
    plouf.write(p, q - p);
    p = q + 1;
    property const *h = &res;
    if (*p >= '1' && *p <= '9')
    {
      int n = *(p++) - '1';
      h = &hyp[n];
      if (max < n) max = n;
    }
    switch (*p) {
      case 'g':
        q = strchr(p + 1, '.');
        assert(q);
        if (fqn) {
          plouf << "Gappa.Gappa_";
          plouf.write(p + 1, q - p);
        }
        p = q;
        break;
      case 't': plouf << th; break;
      case 'i': plouf << display(h->bnd()); break;
      case 'l': plouf << display(lower(h->bnd())); break;
      case 'u': plouf << display(upper(h->bnd())); break;
      case 'c': plouf << '(' << h->cst() << ')'; break;
      case 'x': plouf << display(h->real.real()); break;
      case 'y': plouf << display(h->real.real2()); break;
      case 'p':
        apply_hypothesis(plouf, h->real, h - hyp, pmap, num);
        break;
      case '!':
        for (int n = 0; n <= max; ++n) {
          apply_hypothesis(plouf, hyp[n].real, n, pmap, num);
          if (n != max) plouf << ' ';
        }
        break;
      case '\0':
        for (int n = 0; n <= max; ++n) {
          apply_hypothesis(plouf, hyp[n].real, n, pmap, num);
          plouf << ' ';
        }
        plouf << '_';
        return;
      default:
        plouf << '$';
    }
  }
}

std::string display(theorem_node const *t)
{
  static int t_id = 0;
  std::string name = composite('t', ++t_id);
  auto_flush plouf;
  if (vernac)
  {
    plouf << "Lemma " << name << " : ";
    for (property const &i: t->hyp) plouf << display(i) << " -> ";
    plouf << display(t->res) << ".\nProof.\n";
    int nb_hyps = t->hyp.size();
    if (nb_hyps)
    {
      plouf << " intros";
      for (int i = 0; i < nb_hyps; ++i) plouf << " h" << i;
      plouf << ".\n";
    }
    plouf << " refine (";
  }
  else
  {
    plouf << "let " << name;
    int num_hyp = 0;
    for (property const &i: t->hyp) {
      plouf << " (h" << num_hyp++ << " : " << display(i) << ')';
    }
    plouf << " : " <<  display(t->res) << " := ";
  }

  apply_theorem(plouf, convert_name(t->name), t->res, &*t->hyp.begin());
  plouf << (vernac ? ") ; finalize.\nQed.\n" : " in\n");
  return name;
}

std::string subset_name(property const &p1, property const &p2)
{
  assert(p1.implies(p2));
  if (p2.implies(p1)) return std::string();
  char const *prefix = "", *suffix = "";
  switch (p1.real.pred()) {
  case PRED_BND:
    if (lower(p2.bnd()) == number::neg_inf)
      suffix = "_r";
    else if (upper(p2.bnd()) == number::pos_inf)
      suffix = "_l";
    break;
  case PRED_ABS: prefix = "abs_"; break;
  case PRED_REL: prefix = "rel_"; break;
  case PRED_LIN: prefix = "lin_"; break;
  case PRED_FIX: prefix = "fix_"; break;
  case PRED_FLT: prefix = "flt_"; break;
  case PRED_EQL:
  case PRED_NZR:
  case PRED_NUL: assert(false);
  }
  return std::string(prefix) + "subset" + suffix;
}

void invoke_lemma(auto_flush &plouf, property_vect const &hyp, property_map const &pmap)
{
  for (property const &j: hyp)
  {
    property_map::const_iterator pki = pmap.find(j.real);
    assert(pki != pmap.end());
    int h = pki->second.first;
    std::string sn = subset_name(*pki->second.second, j);
    if (sn.empty()) {
      if (vernac) plouf << " exact h" << h << '.';
      else plouf << " h" << h;
    } else {
      plouf << (vernac ? " refine (" : " (");
      apply_theorem(plouf, sn, j, pki->second.second, &pmap);
      plouf << (vernac ? ") ; finalize." : ")");
    }
  }
  if (vernac) plouf << '\n';
}

/**
 * Instantiate node @a m as "h @a num_hyp" with the hypotheses of node @a n.
 */
static void pose_hypothesis(auto_flush &plouf, int num_hyp, node const *m, node const *n)
{
  plouf << (vernac ? " assert (h" : "  let h") << num_hyp << " := ";
  int i = 0;
  graph_t *g = n->graph;
  for (; g != m->graph; g = g->get_father()) ++i;
  if (m->type == LOGIC && !static_cast<logic_node const *>(m)->before) {
    plouf << 'h' << i;
  } else {
    plouf << display(m);
    for (; g; g = g->get_father()) plouf << " h" << (i++);
  }
  plouf << (vernac ? ").\n" : " in\n");
}

typedef std::map<ast_real const *, int> real_map;

static int find_real(real_map &rm, ast_real const *r)
{
  real_map::const_iterator i = rm.find(r);
  if (i == rm.end()) {
    int j = rm.size();
    rm[r] = j;
    return j;
  }
  return i->second;
}

static void reify(auto_flush &plouf, real_map &rm, property const &p)
{
  plouf << qualify("Gappa.Gappa_tree.");
  switch (p.real.pred()) {
  case PRED_BND: {
    interval const &b = p.bnd();
    int r = find_real(rm, p.real.real());
    if (is_bounded(b))
      plouf << "Abnd " << r << "%nat " << display(b);
    else if (lower(b) == number::neg_inf)
      plouf << "Aleq " << r << "%nat " << display(upper(b));
    else
      plouf << "Ageq " << r << "%nat " << display(lower(b));
    break; }
  case PRED_ABS:
    plouf << "Aabs " << find_real(rm, p.real.real()) << "%nat " << display(p.bnd());
    break;
  case PRED_REL:
    plouf << "Arel " << find_real(rm, p.real.real()) << "%nat " << find_real(rm, p.real.real2()) << "%nat " << display(p.bnd());
    break;
  case PRED_LIN:
    plouf << "Alin " << find_real(rm, p.real.real()) << "%nat " << find_real(rm, p.real.real2()) << "%nat " << display(p.bnd());
    break;
  case PRED_FIX:
    plouf << "Afix " << find_real(rm, p.real.real()) << "%nat (" << p.cst() << ")%Z";
    break;
  case PRED_FLT:
    plouf << "Aflt " << find_real(rm, p.real.real()) << "%nat " << p.cst() << "%positive";
    break;
  case PRED_NZR:
    plouf << "Anzr " << find_real(rm, p.real.real()) << "%nat";
    break;
  case PRED_EQL:
    plouf << "Aeql " << find_real(rm, p.real.real()) << "%nat " << find_real(rm, p.real.real2()) << "%nat";
    break;
  case PRED_NUL:
    assert(false);
  }
}

static void reify(auto_flush &plouf, real_map &rm, property_tree const &t)
{
  assert(!t.empty());
  plouf << '(' << qualify("Gappa.Gappa_tree.");
  if (t.left) {
    plouf << "Ttree " << (t.conjunction ? "true" : "false") << ' ';
    reify(plouf, rm, *t.left);
    plouf << ' ';
    reify(plouf, rm, *t.right);
  } else {
    plouf << "Tatom " << (t.conjunction ? "true" : "false") << " (";
    reify(plouf, rm, fetch(*t.atom));
    plouf << ')';
  }
    plouf << ')';
}

static void simplification(auto_flush &plouf, property_tree const &before, property_tree const &after, property const &mod, int num_hyp)
{
  real_map rm;
  if (vernac) plouf << " refine (simplify ";
  else plouf << "  Gappa.Gappa_tree.simplify ";
  reify(plouf, rm, before);
  plouf << ' ';
  if (after.empty()) plouf << qualify("Gappa.Gappa_tree.") << "Tfalse";
  else reify(plouf, rm, after);
  plouf << " (";
  reify(plouf, rm, mod);
  plouf << ") ";
  std::vector<ast_real const *> inv(rm.size(), NULL);
  for (auto const &i: rm) {
    inv[i.second] = i.first;
  }
  for (ast_real const *i: inv) {
    plouf << "(List.cons " << display(i) << ' ';
  }
  plouf << "List.nil" << std::string(rm.size(), ')')
     << " h" << num_hyp << " h" << (num_hyp - 1) << " _";
  if (vernac) plouf << ") ; finalize.\n";
}

static void select(auto_flush &plouf, int idx, int num_hyp)
{
  plouf << (vernac ? " exact (" : "  ");
  if (idx == -1)
    plouf << "RIneq.Rlt_le _ _ (RIneq.Rnot_le_lt _ _ h" << num_hyp << ')';
  else if (idx)
    plouf << "proj" << idx << " h" << num_hyp;
  else
    plouf << 'h' << num_hyp;
  if (vernac) plouf << ").\n";
}

static id_cache<node const *> displayed_nodes;

std::string display(node const *n)
{
  assert(n);
  int n_id = displayed_nodes.find(n);
  std::string name = composite('l', n_id);
  if (n_id < 0) return name;
  auto_flush plouf;
  if (vernac) plouf << "Lemma " << name << " : ";
  else plouf << "let " << name << " : ";
  int num_hyp = 0;
  for (graph_t *g = n->graph; g; g = g->get_father())
  {
    plouf << display(g->get_hypotheses()) << " -> ";
    ++num_hyp;
  }
  logic_node const *ln = NULL;
  if (n->type == LOGIC) {
    ln = static_cast<logic_node const *>(n);
    assert(ln->before);
    plouf << display(ln->tree);
  } else {
    property const &n_res = n->get_result();
    if (n_res.null())
      plouf << "False";
    else
      plouf << display(n_res) << " (* " << dump_property(n_res) << " *)";
  }
  plouf << (vernac ? ".\nProof.\n" : " :=\n");
  if (num_hyp) {
    plouf << (vernac ? " intros" : " fun");
    for (int i = 0; i < num_hyp; ++i) plouf << " h" << i;
    plouf << (vernac ? ".\n" : " =>\n");
  }
  switch (n->type) {
  case LOGIC: {
    pose_hypothesis(plouf, num_hyp, ln->before, n);
    if (ln->modifier) {
      pose_hypothesis(plouf, ++num_hyp, ln->modifier, n);
      simplification(plouf, ln->before->tree, ln->tree, ln->modifier->get_result(), num_hyp);
    } else {
      assert(ln->before->tree.conjunction);
      select(plouf, ln->index, num_hyp);
    }
    break; }
  case LOGICP: {
    logicp_node const *ln = static_cast<logicp_node const *>(n);
    assert(ln->before && (ln->before->tree.conjunction || ln->before->tree.atom));
    pose_hypothesis(plouf, num_hyp, ln->before, n);
    select(plouf, ln->index, num_hyp);
    break; }
  case MODUS: {
    property_map pmap;
    node_vect const &pred = n->get_subproofs();
    for (node const *m: pred) {
      pose_hypothesis(plouf, num_hyp, m, n);
      property const &res = m->get_result();
      pmap[res.real] = std::make_pair(num_hyp++, &res);
    }
    modus_node const *mn = static_cast<modus_node const *>(n);
    assert(mn->target);
    plouf << (vernac ? " apply " : "  ") << display(mn->target);
    if (vernac) plouf << '.';
    invoke_lemma(plouf, mn->target->hyp, pmap);
    break; }
  case INTERSECTION: {
    property hyps[2];
    node_vect const &pred = n->get_subproofs();
    int num[2];
    char const *suffix = "", *prefix = "";
    property const &n_res = n->get_result();
    if (n_res.null()) prefix = "absurd_";
    for (int i = 0; i < 2; ++i)
    {
      node *m = pred[i];
      property const &res = m->get_result();
      hyps[i] = res;
      switch (res.real.pred()) {
        case PRED_BND:
          if (is_bounded(res.bnd())) break;
          if (!i) suffix = "_hb";
          else if (suffix[0]) suffix = "_hh";
          else suffix = "_bh";
          break;
        case PRED_ABS:
          suffix = "_aa";
          break;
        case PRED_REL:
          suffix = res.real == n_res.real ? "_rr" : "_rr0";
          break;
        case PRED_LIN:
          suffix = res.real == n_res.real ? "_ll" : "_ll0";
          break;
        default:
          assert(false);
      }
      pose_hypothesis(plouf, num_hyp, m, n);
      num[i] = num_hyp++;
    }
    if (vernac) {
      plouf << " apply " << prefix << "intersect" << suffix << " with"
               " (1 := h" << num[0] << ") (2 := h" << num[1] << ")."
               " finalize.\n";
    } else {
      plouf << "  ";
      apply_theorem(plouf, std::string(prefix) + "intersect" + suffix,
                    n_res, hyps, NULL, num);
    }
    break; }
  case UNION: {
    node_vect const &pred = n->get_subproofs();
    property const &n_res = n->get_result();
    assert(pred.size() >= 2);
    for (node const *m: pred)
    {
      assert(m->graph->father == n->graph);
      property const &p = *m->graph->get_hypotheses().atom;
      if (upper(p.bnd()) < number::pos_inf) {
        std::string rcase = display(p.real.real());
        if (lower(p.bnd()) > number::neg_inf) {
          if (vernac)
            plouf << " apply (union' " << rcase << ' '
                  << display(p.bnd()) << ").\n";
          else
            plouf << " (Gappa.Gappa_pred_bnd.union' " << rcase << ' '
                  << display(p.bnd()) << ' ' << display(n_res);
        } else {
          if (vernac)
            plouf << " apply (union " << rcase << ' '
                  << display(upper(p.bnd())) << ").\n";
          else
            plouf << "  Gappa.Gappa_pred_bnd.union " << rcase << ' '
                  << display(upper(p.bnd())) << ' ' << display(n_res);
        }
      }
      plouf << (vernac ? " intro h" : " (fun h") << num_hyp
            << (vernac ? ". (* " : " => (* ") << p.bnd() << " *)\n";
      property const &res = m->get_result();
      if (res.null()) {
        if (n_res.null())
          plouf << (vernac ? " apply (" : "   (");
        else
          plouf << (vernac ? " elim (" : " False_ind _ (");
      } else {
        // not a contradictory result
        std::string sn = subset_name(res, n_res);
        if (!sn.empty()) {
          assert(vernac);
          plouf << " apply " << sn << " with ";
          switch (res.real.pred()) {
          case PRED_FIX: plouf << res.cst() << "%Z"; break;
          case PRED_FLT: plouf << res.cst() << "%positive"; break;
          default: plouf << display(res.bnd());
          }
          plouf << ". 2: finalize.\n";
        }
        plouf << (vernac ? " apply (" : " (");
      }
      plouf << display(m) << " h" << num_hyp;
      for (int j = 0; j != num_hyp; ++j) plouf << " h" << j;
      plouf << (vernac ? ").\n" : "))\n");
    }
    if (!vernac)
      plouf << ' ' << std::string(pred.size() - 2, ')');
    break; }
  }
  plouf << (vernac ? "Qed.\n" : " in\n");
  return name;
}

}
