(*
This file is part of the dpll(t) engine sammy
Copyright (C) 2004  The University of Iowa
Michael Decoster
                                                                                
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
                                                                                
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
                                                                                
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*)
                                                                                

open Theory_db

open Formula

open Basic_expression

type ite_wrapper =
    I of exprtype * exprtype list

(** should be raised if making an and expression and are given an empty list*)
exception And_must_have_at_least_one_argument

(** should be raised if an uninterpreted function is attempted to be built as an interpreted function*)
exception Not_interpreted_function

(** should be raised if an uninterpreted predicate is attempted to be built as an interpreted predicate*)
exception Not_interpreted_predicate



let debug = Global.debug

let pred_db=Sys_state.pred_db

let term_db=Term_db.term_db

let theory=Sys_state.cstore#theory

let append_list = List.rev_append


(* makes a functional predicate object *)
(* sym : function name string
   terms: list of parameters (as exprtypes)
*)
let make_function_pred (sym:string) (terms:exprtype list) =
  let new_id = theory#appFunction (Theory_db.get_function_id sym) (term_ids terms) in
  let temp = expr_to_string (FUN(sym,terms)) in
  add_pred new_id temp;
  new_id



let make_and_expr exprl =
      match exprl with
        [] -> raise And_must_have_at_least_one_argument
      | expr::[] -> expr
      | _ ->
          let (merged_freelists,merged_exist_lists,merged_universal_lists)=
                List.fold_left 
                    (fun (free_acc,exist_acc,universal_acc) expr -> 
                         let E(_,expr_vars,_,_,_)=expr in 
                          (Formula.merge_free_lists expr_vars.freevars free_acc),(Formula.merge_free_lists expr_vars.existential_bound_vars exist_acc),(Formula.merge_free_lists expr_vars.universal_bound_vars universal_acc)
                    ) ([],[],[]) exprl
          in
          let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
          if merged_freelists=[] then
            E(Some(make_abs_pred()),merged_expr_vars,bool_type_id,AND(exprl),Escjava_label.empty_label)
          else
            E(None,merged_expr_vars,bool_type_id,AND(exprl),Escjava_label.empty_label)

let make_iff_expr exprl =
    match exprl with
      expr1::expr2::[] -> 
              let E(id1,expr_vars1,_,_,_)=expr1 in
              let E(id2,expr_vars2,_,_,_)=expr2 in
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                  let id=make_abs_pred()in
                    E(Some id,merged_expr_vars,bool_type_id,IFF(expr1,expr2),Escjava_label.empty_label)
                else
                  E(None,merged_expr_vars,bool_type_id,IFF(expr1,expr2),Escjava_label.empty_label)
    | expr1::tail ->
        let iff_list= List.fold_left
          (fun acc expr2 -> 
              let E(id1,expr_vars1,_,_,_)=expr1 in
              let E(id2,expr_vars2,_,_,_)=expr2 in
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                  let id=make_abs_pred() in
                    E(Some id,merged_expr_vars,bool_type_id,IFF(expr1,expr2),Escjava_label.empty_label)::acc
                else
                  E(None,merged_expr_vars,bool_type_id,IFF(expr1,expr2),Escjava_label.empty_label)::acc
          ) [] tail
        in
        make_and_expr iff_list

let make_not_expr expr1 =
      match expr1 with
        E(Some id1,{freevars=[]},_,PRED(name,exprl),label) ->
            E(Some(Global.compl id1),empty_expression_variables,bool_type_id,PRED(name,exprl),label)
      | E(Some id1,({freevars=[]} as expr_vars),_,expr,_)->
            let not_id=make_abs_pred() in
              E(Some not_id,Formula.reverse_variable_polarity expr_vars,bool_type_id,NOT(expr1),Escjava_label.empty_label)
      | E(None,expr_vars,_,_,_) ->
            E(None,reverse_variable_polarity expr_vars,bool_type_id,NOT(expr1),Escjava_label.empty_label)

let make_or_expr exprl =
      let (merged_freelists,merged_exist_lists,merged_universal_lists)=
            List.fold_left 
                (fun (free_acc,exist_acc,universal_acc) expr -> 
                     let E(_,expr_vars,_,_,_)=expr in 
                      (Formula.merge_free_lists expr_vars.freevars free_acc),(Formula.merge_free_lists expr_vars.existential_bound_vars exist_acc),(Formula.merge_free_lists expr_vars.universal_bound_vars universal_acc)
                ) ([],[],[]) exprl
      in
      let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
      if merged_freelists=[] then
        E(Some(make_abs_pred()),merged_expr_vars,bool_type_id,OR(exprl),Escjava_label.empty_label)
      else
        E(None,merged_expr_vars,bool_type_id,OR(exprl),Escjava_label.empty_label)

let make_impl_expr expr1 expr2 =
      let not_a=make_not_expr expr1 in
        make_or_expr [not_a;expr2] 

let make_xor_expr exprl =
    let head::tail=exprl in
      List.fold_left 
          (fun expr1 expr2 -> 
              let E(_,expr_vars1,_,_,_)=expr1 in
              let E(_,expr_vars2,_,_,_)=expr2 in

              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                  let id=make_abs_pred() in
                    E(Some id,merged_expr_vars,bool_type_id,XOR(expr1,expr2),Escjava_label.empty_label)
                else
                  E(None,merged_expr_vars,bool_type_id,XOR(expr1,expr2),Escjava_label.empty_label)
          ) head tail


let make_ite_expr expr1 expr2 expr3 =
      let guard_expr1=set_expr_guard expr1 true in
      let not1=make_not_expr guard_expr1 in
      let impl1=make_impl_expr guard_expr1 expr2 in
      let impl2=make_impl_expr not1 expr3 in
        make_and_expr [impl1;impl2]

let make_equality_expr exprl label =
  let eq_list=
    (match exprl with
      expr1::expr2::[] -> 
              let E(id1,expr_vars1,_,_,_)=expr1 in
              let E(id2,expr_vars2,_,_,_)=expr2 in

              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                begin
                  let Some e_id1=id1 in
                  let Some e_id2=id2 in
                  let id=theory#create_equals e_id1 e_id2 in
                  let name="(= " ^exprtype_to_string expr1 ^ " " ^ exprtype_to_string expr2 ^")" in
                    add_pred id name;
                    let accumulated_labels=gather_labels_from_exprlist [expr1;expr2] in
                    [E(Some id,merged_expr_vars,bool_type_id,ID(id,name,accumulated_labels),label)]
                end
                else
                  [E(None,merged_expr_vars,bool_type_id,REL(EQ,expr1,expr2),label)]
    | expr1::tail ->
        List.fold_left
          (fun acc expr2 -> 
              let E(id1,expr_vars1,_,_,_)=expr1 in
              let E(id2,expr_vars2,_,_,_)=expr2 in

              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                begin
                  let Some e_id1=id1 in
                  let Some e_id2=id2 in
                  let id=theory#create_equals e_id1 e_id2 in
                  let name="(= " ^exprtype_to_string expr1 ^ " " ^ exprtype_to_string expr2 ^")" in
                    add_pred id name;
                    let accumulated_labels=gather_labels_from_exprlist [expr1;expr2] in
                    E(Some id,merged_expr_vars,bool_type_id,ID(id,name,accumulated_labels),label)::acc
                end
                else
                  E(None,merged_expr_vars,bool_type_id,REL(EQ,expr1,expr2),label)::acc
          ) [] tail
    )
  in
  make_and_expr eq_list

let make_disequality_expr exprl label=
  let rec make_disequality_expr' exprl acc_disequalities =
      match exprl with
        expr1::[] -> acc_disequalities
      | expr1::tail -> 
        let neq_list= List.fold_left
          (fun acc expr2 -> 
              let E(id1,expr_vars1,_,_,_)=expr1 in
              let E(id2,expr_vars2,_,_,_)=expr2 in

              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
                if merged_freelists=[] then
                begin
                  let Some e_id1=id1 in
                  let Some e_id2=id2 in
                  let id=theory#create_equals e_id1 e_id2 in
                  let name="(= " ^exprtype_to_string expr1 ^ " " ^ exprtype_to_string expr2 ^")" in
                    add_pred id name;
                    let accumulated_labels=gather_labels_from_exprlist [expr1;expr2] in
                    (make_not_expr (E(Some id,merged_expr_vars,bool_type_id,ID(id,name,accumulated_labels),label)))::acc
                end
                else
                  E(None,merged_expr_vars,bool_type_id,REL(NEQ,expr1,expr2),label)::acc
          ) [] tail
        in
        make_disequality_expr' tail neq_list
  in
  let neq_list=make_disequality_expr' exprl [] in
    make_and_expr neq_list 
          

let build_predicate_expr id_name ite_exprl label=
  let (terms,ite_defs)=
      List.fold_left (fun acc ite_term ->
                            let (terml,ite_defl)= acc in
                            let I(term,new_ite_defl)=ite_term in
                            (term::terml,(append_list new_ite_defl ite_defl))
                     ) ([],[]) (List.rev ite_exprl) 
  in
(*  List.iter (fun exprtype -> Printf.printf "build_pred_expr_term=";Formula.print_exprtype exprtype; Printf.printf "\n") terms;Printf.printf "\n";*)
  if(List.for_all (function E(Some(_),_,_,_,_) -> true
                                |E(_,_,_,_,_) -> false
                  ) terms)
  then 
    let arg_ids=List.map (fun (E(Some id,_,_,_,_)) ->
                          if is_pred id then
                            mark_interpreted id;
                          id) terms
    in
    let pid = make_function_pred id_name terms in
(*    let predicate=E(Some pid,{freevars=[]},bool_type_id,PRED(id_name,[])) in*)
    let predicate=E(Some pid,empty_expression_variables,bool_type_id,PRED(id_name,terms),label) in
      (*Printf.printf "predicate_true=";Formula.print_exprtype predicate; Printf.printf "\n";*)
      make_and_expr (predicate ::ite_defs)
  else
    let freelists=
        List.map (function E(_,{freevars=freelist},_,_,_)-> freelist) terms 
    in
    let merged_free_list=List.fold_left Formula.merge_free_lists [] freelists in
    let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
    let predicate=E(None,expr_vars,bool_type_id,PRED(id_name,terms),label) in
      (*Printf.printf "predicate_false=";Formula.print_exprtype predicate; Printf.printf "\n";*)
      make_and_expr (predicate ::ite_defs)


let build_function_expr id_name op_id ite_exprl = 
  (*Send in [] for proc_exprl and make sure to reverse the list for
    unproc_exprl.  This will make the list of expressions evaluate
    from back to front in a tail recursive manner.
  *)
  let rec build_func_expr id_name op_id proc_exprl unproc_exprl ite_defs=
    (match unproc_exprl with
      [] -> 
        if(List.for_all (function E(Some(_),_,_,_,_) -> true
                                      |E(_,_,_,_,_) -> false
                        ) proc_exprl)
        then 
          let arg_ids=List.map (fun (E(Some id,_,_,_,_)) ->
                                if is_pred id then
                                  mark_interpreted id;
                                id) proc_exprl
          in
          let function_id=theory#appFunction op_id arg_ids in
          let (_,_,return_sort)=get_fun_sorts id_name in
          let sort_id=return_sort#get_id in
           term_db#add_term sort_id function_id;
           I(E(Some function_id,Formula.empty_expression_variables,sort_id,FUN(id_name,proc_exprl),Escjava_label.empty_label),ite_defs) 
        else
          let freelists=
            List.map (function E(_,{freevars=freelist},_,_,_)-> freelist) proc_exprl 
          in
          let merged_free_list=List.fold_left Formula.merge_free_lists [] freelists in
          let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
          let (_,_,return_sort)=get_fun_sorts id_name in
          let sort_id=return_sort#get_id in
          let term=E(None, expr_vars,sort_id,FUN(id_name,proc_exprl),Escjava_label.empty_label) in
            term_db#add_template term;
            I(term,ite_defs)
    | I(h,defs)::t -> 
      let accumulated_defs=append_list defs ite_defs in
        build_func_expr id_name op_id (h::proc_exprl) t accumulated_defs
    )
  in
  build_func_expr id_name op_id [] (List.rev ite_exprl) []


let is_interpreted_predicate pred_name = 
      match pred_name with 
        ">" -> true
      | "<" -> true
      | ">=" -> true
      | "<=" -> true
      | _ -> false

let build_interpreted_predicate id_name ite_exprl label = 
  (*Send in [] for proc_exprl and make sure to reverse the list for
    unproc_exprl.  This will make the list of expressions evaluate
    from back to front in a tail recursive manner.
  *)
  let (terms,ite_defs)=List.fold_left 
                            (fun acc ite_expr -> 
                                  let (terml,ite_defs)=acc in
                                  let I(term,new_ite_defs)=ite_expr in
                                    (term::terml,append_list ite_defs new_ite_defs)
                            ) ([],[]) (List.rev ite_exprl)
  in
  let expr=match id_name,terms with
    ">", [term1;term2] ->
        (match term1,term2 with
          E(Some id1,({freevars=[]} as expr_vars1),tid1,_,_), E(Some id2,({freevars=[]} as expr_vars2),tid2,_,_)-> 
            let rel_id=theory#create_greater_than id1 id2 in
            let name="(> (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              add_pred rel_id name;
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some rel_id,merged_expr_vars,bool_type_id,ID(rel_id,name,accumulated_labels),label)
        | E(_,expr_vars1,tid1,_,_),E(_,expr_vars2,tid2,_,_) ->
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              E(None,merged_expr_vars,bool_type_id,REL(GT,term1,term2),label)
        )
  | "<", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,({freevars=[]} as expr_vars1),tid1,_,_), E(Some id2,({freevars=[]} as expr_vars2),tid2,_,_)-> 
            let rel_id=theory#create_less_than id1 id2 in
            let name="(< (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              add_pred rel_id name;
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some rel_id,merged_expr_vars,bool_type_id,ID(rel_id,name,accumulated_labels),label)
        | E(_,expr_vars1,tid1,_,_),E(_,expr_vars2,tid2,_,_) ->
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              E(None,merged_expr_vars,bool_type_id,REL(LT,term1,term2),label)
        )
  | ">=", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,({freevars=[]} as expr_vars1),tid1,_,_), E(Some id2,({freevars=[]} as expr_vars2),tid2,_,_)-> 
            let rel_id=theory#create_greater_than_or_equals id1 id2 in
            let name="(>= (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              add_pred rel_id name;
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some rel_id,merged_expr_vars,bool_type_id,ID(rel_id,name,accumulated_labels),label)
        | E(_,expr_vars1,tid1,_,_),E(_,expr_vars2,tid2,_,_) ->
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              E(None,merged_expr_vars,bool_type_id,REL(GTE,term1,term2),label)
        )
  | "<=", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,({freevars=[]} as expr_vars1),tid1,_,_), E(Some id2,({freevars=[]} as expr_vars2),tid2,_,_)-> 
            let rel_id=theory#create_less_than_or_equals id1 id2 in
            let name="(<= (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              add_pred rel_id name;
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some rel_id,merged_expr_vars,bool_type_id,ID(rel_id,name,accumulated_labels),label)
        | E(_,expr_vars1,tid1,_,_),E(_,expr_vars2,tid2,_,_) ->
              let merged_freelists=Formula.merge_free_lists expr_vars1.freevars expr_vars2.freevars in
              let merged_exist_lists=Formula.merge_free_lists expr_vars1.existential_bound_vars expr_vars2.existential_bound_vars in
              let merged_universal_lists=Formula.merge_free_lists expr_vars1.universal_bound_vars expr_vars2.universal_bound_vars in
              let merged_expr_vars=Formula.new_expr_vars merged_freelists merged_exist_lists merged_universal_lists in
              E(None,merged_expr_vars,bool_type_id,REL(LTE,term1,term2),label)
        )
  | _ -> raise Not_interpreted_predicate
  in
  make_and_expr (expr::ite_defs)


let is_interpreted_function fun_name = 
      match fun_name with 
        "+" -> true
      | "-" -> true
      | "~" -> true
      | "*" -> true
      | "/" -> true
      | ".Real_Real_select" -> true
      | ".Real_Real_store" -> true
      | ".Real_Int_select" -> true
      | ".Real_Int_store" -> true
      | ".Int_Real_select" -> true
      | ".Int_Real_store" -> true
      | ".Int_Int_select" -> true
      | ".Int_Int_store" -> true
      | _ -> false

let build_interpreted_function id_name ite_exprl = 
  (*Send in [] for proc_exprl and make sure to reverse the list for
    unproc_exprl.  This will make the list of expressions evaluate
    from back to front in a tail recursive manner.
  *)
  let (terms,ite_defs)=List.fold_left 
                            (fun acc ite_expr -> 
                                  let (terml,ite_defs)=acc in
                                  let I(term,new_ite_defs)=ite_expr in
                                    (term::terml,append_list ite_defs new_ite_defs)
                            ) ([],[]) (List.rev ite_exprl)
  in
  let expr=match id_name,terms with
    "+", [term1;term2] ->
        (match term1,term2 with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let binop_id=theory#appPlus id1 id2 in
            let name="(+ (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some binop_id,empty_expression_variables,tid1,ID(binop_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,BINOP(PLUS,term1,term2),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | "-", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let binop_id=theory#appMinus id1 id2 in
            let name="(- (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some binop_id,empty_expression_variables,tid1,ID(binop_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,BINOP(MINUS,term1,term2),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | "~", [term1] 
  | "-", [term1] -> 
        (match term1 with
          E(Some id1,{freevars=[]},tid1,_,_) -> 
              let uminus_id=theory#appUnaryMinus id1 in
              let name="(- (" ^ (exprtype_to_string term1) ^") )" in
              let accumulated_labels=gather_labels_from_expr term1 in
                E(Some uminus_id,empty_expression_variables,tid1,ID(uminus_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(None,expr_vars1,tid1,_,_) ->
              let term=E(None,expr_vars1,tid1,UMINUS(term1),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | "*", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let binop_id=theory#appMult id1 id2 in
            let name="(* (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some binop_id,empty_expression_variables,tid1,ID(binop_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,BINOP(MULT,term1,term2),Escjava_label.empty_label) in
                term_db#add_template term;
                term

        )
  | "/", [term1;term2] -> 
        (match term1,term2 with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let binop_id=theory#appDiv id1 id2 in
            let name="(/ (" ^ (exprtype_to_string term1) ^") ("^ (exprtype_to_string term2)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [term1;term2] in
              E(Some binop_id,empty_expression_variables,tid1,ID(binop_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,BINOP(DIV,term1,term2),Escjava_label.empty_label) in
                term_db#add_template term;
                term

        )
  | ".Real_Real_store", [arr;index;element] ->
        (match arr,index,element with
          E(Some id1,{freevars=[]},tid1,_,_),E(Some id2,{freevars=[]},tid2,_,_),E(Some id3,{freevars=[]},tid3,_,_) ->
              let update_id= 
(**)              theory#decl_interpreted_function
(**)                "ARRAY_WRITE" [Global.id_string id1;Global.id_string id2;Global.id_string id3]
              in
              let name="(real_real_store ("^(exprtype_to_string arr)^") ("^(exprtype_to_string index)^") ("^(exprtype_to_string element)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index;element] in
                E(Some update_id,empty_expression_variables,tid1,ID(update_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_),E(_,{freevars=freelist3},tid3,_,_) ->
            let merged_free_list=Formula.merge_free_lists (Formula.merge_free_lists freelist1 freelist2) freelist3 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,FUN("ARRAY_WRITE",[arr;index;element]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | ".Real_Real_select", [arr;index] ->
        (match arr,index with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let id=
(**)              theory#decl_interpreted_function 
(**)                "ARRAY_LOOKUP" [Global.id_string id1;Global.id_string id2]
            in
            let name="("^(exprtype_to_string arr)^"["^(exprtype_to_string index)^"])" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index] in
              E(Some(id),empty_expression_variables,theory#realType,ID(id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,theory#realType,FUN("ARRAY_LOOKUP",[arr;index]),Escjava_label.empty_label) in
                term_db#add_template term;
                term

        )
  | ".Real_Int_store", [arr;index;element] ->
        (match arr,index,element with
          E(Some id1,{freevars=[]},tid1,_,_),E(Some id2,{freevars=[]},tid2,_,_),E(Some id3,{freevars=[]},tid3,_,_) ->
              let update_id= 
(**)              theory#decl_interpreted_function
(**)                "ARRAY_WRITE" [Global.id_string id1;Global.id_string id2;Global.id_string id3]
              in
              let name="(.Real_Int_store ("^(exprtype_to_string arr)^") ("^(exprtype_to_string index)^") ("^(exprtype_to_string element)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index;element] in
                E(Some update_id,empty_expression_variables,tid1,ID(update_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_),E(_,{freevars=freelist3},tid3,_,_) ->
            let merged_free_list=Formula.merge_free_lists (Formula.merge_free_lists freelist1 freelist2) freelist3 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,FUN("ARRAY_WRITE",[arr;index;element]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | ".Real_Int_select", [arr;index] ->
        (match arr,index with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let id=
(**)              theory#decl_interpreted_function 
(**)                "ARRAY_LOOKUP" [Global.id_string id1;Global.id_string id2]
            in
            let name="("^(exprtype_to_string arr)^"["^(exprtype_to_string index)^"])" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index] in
              E(Some(id),empty_expression_variables,theory#intType,ID(id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,theory#intType,FUN("ARRAY_LOOKUP",[arr;index]),Escjava_label.empty_label) in
                term_db#add_template term;
                term

        )
  | ".Int_Real_store", [arr;index;element] ->
        (match arr,index,element with
          E(Some id1,{freevars=[]},tid1,_,_),E(Some id2,{freevars=[]},tid2,_,_),E(Some id3,{freevars=[]},tid3,_,_) ->
              let update_id= 
(**)              theory#decl_interpreted_function
(**)                "ARRAY_WRITE" [Global.id_string id1;Global.id_string id2;Global.id_string id3]
              in
              let name="(.Int_Real_store ("^(exprtype_to_string arr)^") ("^(exprtype_to_string index)^") ("^(exprtype_to_string element)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index;element] in
                E(Some update_id,empty_expression_variables,tid1,ID(update_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_),E(_,{freevars=freelist3},tid3,_,_) ->
            let merged_free_list=Formula.merge_free_lists (Formula.merge_free_lists freelist1 freelist2) freelist3 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,FUN("ARRAY_WRITE",[arr;index;element]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | ".Int_Real_select", [arr;index] ->
        (match arr,index with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let id=
(**)              theory#decl_interpreted_function 
(**)                "ARRAY_LOOKUP" [Global.id_string id1;Global.id_string id2]
            in
            let name="("^(exprtype_to_string arr)^"["^(exprtype_to_string index)^"])" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index] in
              E(Some(id),empty_expression_variables,theory#realType,ID(id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,theory#realType,FUN("ARRAY_LOOKUP",[arr;index]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | ".Int_Int_store", [arr;index;element] ->
        (match arr,index,element with
          E(Some id1,{freevars=[]},tid1,_,_),E(Some id2,{freevars=[]},tid2,_,_),E(Some id3,{freevars=[]},tid3,_,_) ->
              let update_id= 
(**)              theory#decl_interpreted_function
(**)                "ARRAY_WRITE" [Global.id_string id1;Global.id_string id2;Global.id_string id3]
              in
              let name="(.Int_Int_store ("^(exprtype_to_string arr)^") ("^(exprtype_to_string index)^") ("^(exprtype_to_string element)^") )" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index;element] in
                E(Some update_id,empty_expression_variables,tid1,ID(update_id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_),E(_,{freevars=freelist3},tid3,_,_) ->
            let merged_free_list=Formula.merge_free_lists (Formula.merge_free_lists freelist1 freelist2) freelist3 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,tid1,FUN("ARRAY_WRITE",[arr;index;element]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | ".Int_Int_select", [arr;index] ->
        (match arr,index with
          E(Some id1,{freevars=[]},tid1,_,_), E(Some id2,{freevars=[]},tid2,_,_)-> 
            let id=
(**)              theory#decl_interpreted_function 
(**)                "ARRAY_LOOKUP" [Global.id_string id1;Global.id_string id2]
            in
            let name="("^(exprtype_to_string arr)^"["^(exprtype_to_string index)^"])" in
              let accumulated_labels=gather_labels_from_exprlist [arr;index] in
              E(Some(id),empty_expression_variables,theory#intType,ID(id,name,accumulated_labels),Escjava_label.empty_label)
        | E(_,{freevars=freelist1},tid1,_,_),E(_,{freevars=freelist2},tid2,_,_) ->
            let merged_free_list=Formula.merge_free_lists freelist1 freelist2 in
            let expr_vars=Formula.new_expr_vars merged_free_list [] [] in
              let term=E(None,expr_vars,theory#intType,FUN("ARRAY_LOOKUP",[arr;index]),Escjava_label.empty_label) in
                term_db#add_template term;
                term
        )
  | _ -> raise Not_interpreted_function
  in
  I(expr,ite_defs)
