(* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- *)
(*  Title:      Pure/IsaPlanner/term_lib.ML
    ID:		$Id: term_lib.ML,v 1.13 2005/09/15 15:16:59 wenzelm Exp $
    Author:     Lucas Dixon, University of Edinburgh
                lucasd@dai.ed.ac.uk
    Created:    17 Aug 2002
*)
(* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- *)
(*  DESCRIPTION:

    Additional code to work with terms.

*)
(* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- *)
signature TERM_LIB =
sig
    val fo_term_height : term -> int
    val ho_term_height : term -> int

    (* Matching/unification with exceptions handled *)
    val clean_match : theory -> term * term
                      -> ((indexname * (sort * typ)) list
                         * (indexname * (typ * term)) list) option
    val clean_unify : theory -> int -> term * term
                      -> ((indexname * (sort * typ)) list
                         * (indexname * (typ * term)) list) Seq.seq

    (* managing variables in terms, can doing conversions *)
    val bounds_to_frees : term -> term
    val bounds_to_frees_with_vars :
       (string * typ) list -> term -> term

    val change_bounds_to_frees : term -> term
    val change_frees_to_vars : term -> term
    val change_vars_to_frees : term -> term
    val loose_bnds_to_frees :
       (string * typ) list -> term -> term

    (* make all variables named uniquely *)
    val unique_namify : term -> term

		(* breasking up a term and putting it into a normal form
       independent of internal term context *)
    val cleaned_term_conc : term -> term
    val cleaned_term_parts : term -> term list * term
    val cterm_of_term : term -> cterm

    (* terms of theorems and subgoals *)
    val term_of_thm : thm -> term
    val get_prems_of_sg_term : term -> term list
    val triv_thm_from_string : string -> thm

    (* some common term manipulations *)
    val try_dest_Goal : term -> term
    val try_dest_Trueprop : term -> term

    val bot_left_leaf_of : term -> term
    val bot_left_leaf_noabs_of : term -> term

    (* term containing another term - an embedding check *)
    val term_contains : term -> term -> bool

    (* name-convertable checking - like ae-convertable, but allows for
       var's and free's to be mixed - and doesn't used buggy code. :-) *)
		val get_name_eq_member : term -> term list -> term option
    val name_eq_member : term -> term list -> bool
    val term_name_eq :
       term ->
       term ->
       (term * term) list ->
       (term * term) list option

     (* is the typ a function or is it atomic *)
     val is_fun_typ : typ -> bool
     val is_atomic_typ : typ -> bool

    (* variable analysis *)
    val is_some_kind_of_var : term -> bool
    val same_var_check :
       ''a -> ''b -> (''a * ''b) list -> (''a * ''b) list option
		val has_new_vars : term * term -> bool
    val has_new_typ_vars : term * term -> bool
   (* checks to see if the lhs -> rhs is a invalid rewrite rule *)
    val is_not_valid_rwrule : theory -> (term * term) -> bool

    (* get the frees in a term that are of atomic type, ie non-functions *)
    val atomic_frees_of_term : term -> (string * typ) list

    (* get used names in a theorem to avoid upon instantiation. *)
    val usednames_of_term : term -> string list
    val usednames_of_thm : thm -> string list

    (* Isar term skolemisationm and unsolemisation *)
    (* I think this is written also in IsarRTechn and also in somewhere else *)
    (* val skolemise_term : (string,typ) list -> term -> term *)
    val unskolemise_all_term :
        term ->
        (((string * typ) * string) list * term)

    (* given a string describing term then a string for its type, returns
       read term *)
    val readwty : string -> string -> term

    (* pretty stuff *)
    val print_term : term -> unit
    val pretty_print_sort : string list -> string
    val pretty_print_term : term -> string
    val pretty_print_type : typ -> string
    val pretty_print_typelist :
       typ list -> (typ -> string) -> string

    (* for debugging: quickly get a string of a term w.r.t the_context() *)
    val string_of_term : term -> string

    (* Pretty printing in a way that allows them to be parsed by ML.
       these are perhaps redundent, check the standard basis
       lib for generic versions for any datatype? *)
    val writesort : string list -> unit
    val writeterm : term -> unit
    val writetype : typ -> unit
  end


structure TermLib : TERM_LIB =
struct

(* Two kinds of depth measure for HOAS terms, a first order (flat) and a
   higher order one.
   Note: not stable of eta-contraction: embedding eta-expands term,
   thus we assume eta-expanded *)
fun fo_term_height (a $ b) =
    IsaPLib.max (1 + fo_term_height b, (fo_term_height a))
  | fo_term_height (Abs(_,_,t)) = fo_term_height t
  | fo_term_height _ = 0;

fun ho_term_height  (a $ b) =
    1 + (IsaPLib.max (ho_term_height b, ho_term_height a))
  | ho_term_height (Abs(_,_,t)) = ho_term_height t
  | ho_term_height _ = 0;


(* Higher order matching with exception handled *)
(* returns optional instantiation *)
fun clean_match thy (a as (pat, t)) =
  let val (tyenv, tenv) = Pattern.match thy a
  in SOME (Vartab.dest tyenv, Vartab.dest tenv)
  end handle Pattern.MATCH => NONE;
(* Higher order unification with exception handled, return the instantiations *)
(* given Signature, max var index, pat, tgt *)
(* returns Seq of instantiations *)
fun clean_unify sgn ix (a as (pat, tgt)) =
    let
      (* type info will be re-derived, maybe this can be cached
         for efficiency? *)
      val pat_ty = Term.type_of pat;
      val tgt_ty = Term.type_of tgt;
      (* is it OK to ignore the type instantiation info?
         or should I be using it? *)
      val typs_unify =
          SOME (Sign.typ_unify sgn (pat_ty, tgt_ty) (Term.Vartab.empty, ix))
            handle Type.TUNIFY => NONE;
    in
      case typs_unify of
        SOME (typinsttab, ix2) =>
        let
      (* is it right to throw away the flexes?
         or should I be using them somehow? *)
          fun mk_insts env =
            (Vartab.dest (Envir.type_env env),
             Envir.alist_of env);
          val initenv = Envir.Envir {asol = Vartab.empty,
                                     iTs = typinsttab, maxidx = ix2};
          val useq = (Unify.smash_unifiers (sgn,initenv,[a]))
	            handle UnequalLengths => Seq.empty
		               | Term.TERM _ => Seq.empty;
          fun clean_unify' useq () =
              (case (Seq.pull useq) of
                 NONE => NONE
               | SOME (h,t) => SOME (mk_insts h, Seq.make (clean_unify' t)))
	      handle UnequalLengths => NONE
                   | Term.TERM _ => NONE;
        in
          (Seq.make (clean_unify' useq))
        end
      | NONE => Seq.empty
    end;

fun asm_mk t = assume (cterm_of (the_context()) t);
fun asm_read s = (Thm.assume (read_cterm (the_context()) (s,propT)));


(* more pretty printing code for Isabelle terms etc *)


(* pretty_print_typelist l f = print a typelist.
   l = list of types to print : typ list
   f = function used to print a single type : typ -> string
*)
fun pretty_print_typelist [] f = ""
  | pretty_print_typelist [(h: typ)] (f : typ -> string) = (f h)
  | pretty_print_typelist ((h: typ) :: t) (f : typ -> string) =
      (f h) ^ ", " ^ (pretty_print_typelist t f);



(* pretty_print_sort s = print a sort
   s = sort to print : string list
*)
fun pretty_print_sort [] = ""
  | pretty_print_sort ([h])  = "\"" ^ h ^ "\""
  | pretty_print_sort (h :: t)  = "\"" ^ h ^ "\"," ^ (pretty_print_sort t);


(* pretty_print_type t = print a type
   t = type to print : type
*)
fun pretty_print_type (Type (n, l)) =
      "Type(\"" ^ n ^ "\", [" ^ (pretty_print_typelist l pretty_print_type) ^ "])"
  | pretty_print_type (TFree (n, s)) =
      "TFree(\"" ^ n ^ "\", [" ^ (pretty_print_sort s) ^ "])"
  | pretty_print_type (TVar ((n, i), s)) =
      "TVar( (\"" ^ n ^ "\", " ^ (string_of_int i) ^ "), [" ^ (pretty_print_sort s) ^ "])";


(* pretty_print_term t = print a term prints types and sorts too.
   t = term to print : term
*)
fun pretty_print_term (Const (s, t)) =
      "Const(\"" ^ s ^ "\", " ^ (pretty_print_type t) ^ ")"
  | pretty_print_term (Free (s, t)) =
      "Free(\"" ^ s ^ "\", " ^ (pretty_print_type t) ^ ")"
  | pretty_print_term (Var ((n, i), t)) =
      "Var( (\"" ^ n ^ "\"," ^ (string_of_int i) ^ "), " ^ (pretty_print_type t) ^ ")"
  | pretty_print_term (Bound i) =
      "Bound(" ^ (string_of_int i) ^ ")"
  | pretty_print_term (Abs (s, t, r)) =
      "Abs(\"" ^ s ^ "\"," ^ (pretty_print_type t) ^ ", \n  " ^ (pretty_print_term r) ^ ")"
  | pretty_print_term (op $ (t1, t2)) =
      "(" ^ (pretty_print_term t1) ^ ") $\n (" ^ (pretty_print_term t2) ^ ")";

(* Write the term out nicly instead of just creating a string for it *)
fun writeterm t = writeln (pretty_print_term t);
fun writetype t = writeln (pretty_print_type t);
fun writesort s = writeln (pretty_print_sort s);

fun cterm_of_term (t : term) = Thm.cterm_of (the_context()) t;
fun term_of_thm t = (Thm.prop_of t);

fun string_of_term t = Sign.string_of_term (the_context()) t;
fun print_term t = writeln (string_of_term t);

(* create a trivial HOL thm from anything... *)
fun triv_thm_from_string s = Thm.trivial (cterm_of (the_context()) (read s));

  (* Checks if vars could be the same - alpha convertable
  w.r.t. previous vars, a and b are assumed to be vars,
  free vars, but not bound vars,
  Note frees and vars must all have unique names. *)
  fun same_var_check a b L =
  let
    fun bterm_from t [] = NONE
      | bterm_from t ((a,b)::m) =
          if t = a then SOME b else bterm_from t m

    val bl_opt = bterm_from a L
  in
		case bterm_from a L of
			NONE => SOME ((a,b)::L)
		| SOME b2 => if b2 = b then SOME L else NONE
  end;

  (* FIXME: make more efficient, only require a single new var! *)
  (* check if the new term has any meta variables not in the old term *)
  fun has_new_vars (old, new) =
			(case IsaPLib.lrem (op =) (Term.term_vars old) (Term.term_vars new) of
				 [] => false
			 | (_::_) => true);
  (* check if the new term has any meta variables not in the old term *)
  fun has_new_typ_vars (old, new) =
			(case IsaPLib.lrem (op =) (Term.term_tvars old) (Term.term_tvars new) of
				 [] => false
			 | (_::_) => true);

(* This version avoids name conflicts that might be introduced by
unskolemisation, and returns a list of (string * Term.typ) * string,
where the outer string is the original name, and the inner string is
the new name, and the type is the type of the free variable that was
renamed. *)
local
  fun myadd (n,ty) (L as (renames, names)) =
      let val n' = Syntax.dest_skolem n in
        case (Library.find_first (fn (_,n2) => (n2 = n)) renames) of
          NONE =>
          let val renamedn = Term.variant names n' in
            (renamedn, (((renamedn, ty), n) :: renames,
                        renamedn :: names)) end
        | (SOME ((renamedn, _), _)) => (renamedn, L)
      end
      handle Fail _ => (n, L);

  fun unsk (L,f) (t1 $ t2) =
      let val (L', t1') = unsk (L, I) t1
      in unsk (L', (fn x => f (t1' $ x))) t2 end
    | unsk (L,f) (Abs(n,ty,t)) =
      unsk (L, (fn x => Abs(n,ty,x))) t
    | unsk (L, f) (Free (n,ty)) =
      let val (renamed_n, L') = myadd (n ,ty) L
       in (L', f (Free (renamed_n, ty))) end
    | unsk (L, f) l = (L, f l);
in
fun unskolemise_all_term t =
    let
      val names = Term.add_term_names (t,[])
      val ((renames,names'),t') = unsk (([], names),I) t
    in (renames,t') end;
end

(* true if the type t is a function *)
fun is_fun_typ (Type(s, l)) =
    if s = "fun" then true else false
  | is_fun_typ _ = false;

val is_atomic_typ = not o is_fun_typ;

(* get the frees in a term that are of atomic type, ie non-functions *)
val atomic_frees_of_term =
     List.filter (is_atomic_typ o snd)
     o map Term.dest_Free
     o Term.term_frees;

fun usednames_of_term t = Term.add_term_names (t,[]);
fun usednames_of_thm th =
    let val rep = Thm.rep_thm th
      val hyps = #hyps rep
      val (tpairl,tpairr) = Library.split_list (#tpairs rep)
      val prop = #prop rep
    in
      List.foldr Term.add_term_names [] (prop :: (tpairl @ (tpairr @ hyps)))
    end;

(* read in a string and a top-level type and this will give back a term *)
fun readwty tstr tystr =
  let val thy = the_context()
  in Sign.simple_read_term thy (Sign.read_typ (thy, K NONE) tystr) tstr end;

  (* first term is equal to the second in some name convertable
  way... Note: This differs from the aeconv in the Term.ML file of
  Isabelle in that it allows a var to be alpha-equiv to a free var.

  Also, the isabelle term.ML version of aeconv uses a
  function that it claims doesn't work! *)
  fun term_name_eq (Abs(_,ty1,t1)) (Abs(_,ty2,t2)) l =
    if ty1 = ty2 then term_name_eq t1 t2 l
    else NONE
  | term_name_eq (ah $ at) (bh $ bt) l =
    let
      val lopt = (term_name_eq ah bh l)
    in
      if isSome lopt then
	      term_name_eq at bt (valOf lopt)
      else
        NONE
    end
  | term_name_eq (Const(a,T)) (Const(b,U)) l =
    if a=b andalso T=U then SOME l
    else NONE
  | term_name_eq (a as Free(s1,ty1)) (b as Free(s2,ty2)) l =
    same_var_check a b l
  | term_name_eq (a as Free(s1,ty1)) (b as Var(n2,ty2)) l =
    same_var_check a b l
  | term_name_eq (a as Var(n1,ty1)) (b as Free(s2,ty2)) l =
    same_var_check a b l
  | term_name_eq (a as Var(n1,ty1)) (b as Var(n2,ty2)) l =
    same_var_check a b l
  | term_name_eq (Bound i) (Bound j) l =
    if i = j then SOME l else NONE
  | term_name_eq a b l = ((*writeln ("unchecked case:\n" ^ "a:\n" ^ (pretty_print_term a) ^ "\nb:\n" ^ (pretty_print_term b));*) NONE);

 (* checks to see if the term in a name-equivalent member of the list of terms. *)
  fun get_name_eq_member a [] = NONE
    | get_name_eq_member a (h :: t) =
        if isSome (term_name_eq a h []) then SOME h
				else get_name_eq_member a t;

  fun name_eq_member a [] = false
    | name_eq_member a (h :: t) =
        if isSome (term_name_eq a h []) then true
				else name_eq_member a t;

  (* true if term is a variable *)
  fun is_some_kind_of_var (Free(s, ty)) = true
    | is_some_kind_of_var (Var(i, ty)) = true
    | is_some_kind_of_var (Bound(i)) = true
    | is_some_kind_of_var _ = false;

    (* checks to see if the lhs -> rhs is a invalid rewrite rule *)
(* FIXME: we should really check that there is a subterm on the lhs
which embeds into the rhs, this would be much closer to the normal
notion of valid wave rule - ie there exists at least one case where it
is a valid wave rule... *)
	fun is_not_valid_rwrule thy (lhs, rhs) =
      Term.is_Var (Term.head_of lhs) (* if lhs is essentially just a var *)
      orelse has_new_vars (lhs,rhs)
      orelse has_new_typ_vars (lhs,rhs)
      orelse Pattern.matches_subterm thy (lhs, rhs);


  (* first term contains the second in some name convertable way... *)
  (* note: this is equivalent to an alpha-convertable emedding *)
  (* takes as args: term containing a, term contained b,
     (bound vars of a, bound vars of b),
     current alpha-conversion-pairs,
     returns: bool:can convert, new alpha-conversion table *)
  (* in bellow: we *don't* use: a loose notion that only requires
  variables to match variables, and doesn't worry about the actual
  pattern in the variables. *)
  fun term_contains1 (Bs, FVs) (Abs(s,ty,t)) (Abs(s2,ty2,t2)) =
			if ty = ty2 then
				term_contains1 ((SOME(s,s2,ty)::Bs), FVs) t t2
			else []

  | term_contains1 T t1 (Abs(s2,ty2,t2)) = []

  | term_contains1 (Bs, FVs) (Abs(s,ty,t)) t2 =
    term_contains1 (NONE::Bs, FVs) t t2

  | term_contains1 T (ah $ at) (bh $ bt) =
    (term_contains1 T ah (bh $ bt)) @
    (term_contains1 T at (bh $ bt)) @
    (List.concat (map (fn inT => (term_contains1 inT at bt))
               (term_contains1 T ah bh)))

  | term_contains1 T a (bh $ bt) = []

  | term_contains1 T (ah $ at) b =
		(term_contains1 T ah b) @ (term_contains1 T at b)

  | term_contains1 T a b =
  (* simple list table lookup to check if a named variable has been
  mapped to a variable, if not adds the mapping and return some new
  list mapping, if it is then it checks that the pair are mapped to
  each other, if so returns the current mapping list, else none. *)
		let
			fun bterm_from t [] = NONE
				| bterm_from t ((a,b)::m) =
					if t = a then SOME b else bterm_from t m

  (* check to see if, w.r.t. the variable mapping, two terms are leaf
  terms and are mapped to each other. Note constants are only mapped
  to the same constant. *)
			fun same_leaf_check (T as (Bs,FVs)) (Bound i) (Bound j) =
					let
						fun aux_chk (i1,i2) [] = false
							| aux_chk (0,0) ((SOME _) :: bnds) = true
							| aux_chk (i1,0) (NONE :: bnds) = false
							| aux_chk (i1,i2) ((SOME _) :: bnds) =
								aux_chk (i1 - 1,i2 - 1) bnds
							| aux_chk (i1,i2) (NONE :: bnds) =
								aux_chk (i1,i2 - 1) bnds
					in
						if (aux_chk (i,j) Bs) then [T]
						else []
					end
				| same_leaf_check (T as (Bs,(Fs,Vs)))
                          (a as (Free (an,aty)))
                          (b as (Free (bn,bty))) =
					(case bterm_from an Fs of
						 SOME b2n => if bn = b2n then [T]
												 else [] (* conflict of var name *)
					 | NONE => [(Bs,((an,bn)::Fs,Vs))])
				| same_leaf_check (T as (Bs,(Fs,Vs)))
                          (a as (Var (an,aty)))
                          (b as (Var (bn,bty))) =
					(case bterm_from an Vs of
						 SOME b2n => if bn = b2n then [T]
												 else [] (* conflict of var name *)
					 | NONE => [(Bs,(Fs,(an,bn)::Vs))])
				| same_leaf_check T (a as (Const _)) (b as (Const _)) =
					if a = b then [T] else []
				| same_leaf_check T _ _ = []

		in
			same_leaf_check T a b
		end;

  (* wrapper for term_contains1: checks if the term "a" contains in
  some embedded way, (w.r.t. name -convertable) the term "b" *)
  fun term_contains a b =
			case term_contains1 ([],([],[])) a b of
			  (_ :: _) => true
			| [] => false;

  (* change all bound variables to see ones with appropriate name and
  type *)
  (* naming convention is OK as we use 'variant' from term.ML *)
  (* Note "bounds_to_frees" defined below, its better and quicker, but
  keeps the quantifiers handing about, and changes all bounds, not
  just universally quantified ones. *)
  fun change_bounds_to_frees t =
    let
      val vars = strip_all_vars t
      val frees_names = map (fn Free(s,n) => s | _ => "") (term_frees t)
      val body = strip_all_body t

      fun bnds_to_frees [] _ acc = acc
        | bnds_to_frees ((name,ty)::more) names acc =
            let
	      val new_name = variant names name
	    in
	      bnds_to_frees more (new_name::names) (Free(new_name,ty)::acc)
	    end
    in
      (subst_bounds ((bnds_to_frees vars frees_names []), body))
    end;

(* a runtime-quick function which makes sure that every variable has a
unique name *)
fun unique_namify_aux (ntab,(Abs(s,ty,t))) =
    (case Symtab.lookup ntab s of
       NONE =>
       let
	 val (ntab2,t2) = unique_namify_aux (Symtab.update (s, s) ntab, t)
       in
	 (ntab2,Abs(s,ty,t2))
       end
     | SOME s2 =>
       let
	 val s_new = (Term.variant (Symtab.keys ntab) s)
	 val (ntab2,t2) =
	     unique_namify_aux (Symtab.update (s_new, s_new) ntab, t)
       in
	 (ntab2,Abs(s_new,ty,t2))
       end)
  | unique_namify_aux (ntab,(a $ b)) =
    let
      val (ntab1,t1) = unique_namify_aux (ntab,a)
      val (ntab2,t2) = unique_namify_aux (ntab1,b)		
    in
      (ntab2, t1$t2)
    end
  | unique_namify_aux (nt as (ntab,Const x)) = nt
  | unique_namify_aux (nt as (ntab,f as Free (s,ty))) =
    (case Symtab.lookup ntab s of
       NONE => (Symtab.update (s, s) ntab, f)
     | SOME _ => nt)
  | unique_namify_aux (nt as (ntab,v as Var ((s,i),ty))) =
    (case Symtab.lookup ntab s of
       NONE => (Symtab.update (s, s) ntab, v)
     | SOME _ => nt)
  | unique_namify_aux (nt as (ntab, Bound i)) = nt;
		
fun unique_namify t =
    #2 (unique_namify_aux (Symtab.empty, t));

(* a runtime-quick function which makes sure that every variable has a
unique name and also changes bound variables to free variables, used
for embedding checks, Note that this is a pretty naughty term
manipulation, which doesn't have necessary relation to the original
sematics of the term. This is really a trick for our embedding code. *)

fun bounds_to_frees_aux T (ntab,(Abs(s,ty,t))) =
    (case Symtab.lookup ntab s of
      NONE =>
      let
	val (ntab2,t2) =
          bounds_to_frees_aux ((s,ty)::T) (Symtab.update (s, s) ntab, t)
      in
	(ntab2,Abs(s,ty,t2))
      end
    | SOME s2 =>
      let
	val s_new = (Term.variant (Symtab.keys ntab) s)
	val (ntab2,t2) =
	  bounds_to_frees_aux ((s_new,ty)::T)
            (Symtab.update (s_new, s_new) ntab, t)
      in
	(ntab2,Abs(s_new,ty,t2))
      end)
  | bounds_to_frees_aux T (ntab,(a $ b)) =
    let
      val (ntab1,t1) = bounds_to_frees_aux T (ntab,a)
      val (ntab2,t2) = bounds_to_frees_aux T (ntab1,b)		
    in
      (ntab2, t1$t2)
    end
  | bounds_to_frees_aux T (nt as (ntab,Const x)) = nt
  | bounds_to_frees_aux T (nt as (ntab,f as Free (s,ty))) =
    (case Symtab.lookup ntab s of
      NONE => (Symtab.update (s, s) ntab, f)
    | SOME _ => nt)
  | bounds_to_frees_aux T (nt as (ntab,v as Var ((s,i),ty))) =
     (case Symtab.lookup ntab s of
      NONE => (Symtab.update (s, s) ntab, v)
    | SOME _ => nt)
  | bounds_to_frees_aux T (nt as (ntab, Bound i)) =
    let
      val (s,ty) = List.nth (T,i)
    in
      (ntab, Free (s,ty))
    end;


fun bounds_to_frees t =
    #2 (bounds_to_frees_aux [] (Symtab.empty,t));

fun bounds_to_frees_with_vars vars t =
    #2 (bounds_to_frees_aux vars (Symtab.empty,t));



(* loose bounds to frees *)
fun loose_bnds_to_frees_aux (bnds,vars) (Abs(s,ty,t)) =
    Abs(s,ty,loose_bnds_to_frees_aux (bnds + 1,vars) t)
  | loose_bnds_to_frees_aux (bnds,vars) (a $ b) =
    (loose_bnds_to_frees_aux (bnds,vars) a)
      $ (loose_bnds_to_frees_aux (bnds,vars) b)
  | loose_bnds_to_frees_aux (bnds,vars) (t as (Bound i)) =
    if (bnds <= i) then Free (List.nth (vars,i - bnds))
    else t
  | loose_bnds_to_frees_aux (bnds,vars) t = t;


fun loose_bnds_to_frees vars t =
    loose_bnds_to_frees_aux (0,vars) t;


  fun try_dest_Goal (Const("Goal", _) $ T) = T
    | try_dest_Goal T = T;

  fun try_dest_Trueprop (Const("Trueprop", _) $ T) = T
    | try_dest_Trueprop T = T;

  fun bot_left_leaf_of (l $ r) = bot_left_leaf_of l
    | bot_left_leaf_of (Abs(s,ty,t)) = bot_left_leaf_of t
    | bot_left_leaf_of x = x;

  fun bot_left_leaf_noabs_of (l $ r) = bot_left_leaf_noabs_of l
    | bot_left_leaf_noabs_of x = x;

(* cleaned up normal form version of the subgoal terms conclusion *)
fun cleaned_term_conc t =
    let
      val concl = Logic.strip_imp_concl (change_bounds_to_frees t)
    in
      (try_dest_Trueprop (try_dest_Goal concl))
    end;

(*   fun get_prems_of_sg_term t =
			map opt_dest_Trueprop (Logic.strip_imp_prems t); *)

fun get_prems_of_sg_term t =
		map try_dest_Trueprop (Logic.strip_assums_hyp t);


(* drop premices, clean bound var stuff, and make a trueprop... *)
  fun cleaned_term_parts t =
      let
				val t2 = (change_bounds_to_frees t)
        val concl = Logic.strip_imp_concl t2
				val prems = map try_dest_Trueprop (Logic.strip_imp_prems t2)
      in
				(prems, (try_dest_Trueprop (try_dest_Goal concl)))
      end;

  (* change free variables to vars and visa versa *)
  (* *** check naming is OK, can we just use the vasr old name? *)
  (* *** Check: Logic.varify and Logic.unvarify *)
  fun change_vars_to_frees (a$b) =
        (change_vars_to_frees a) $ (change_vars_to_frees b)
    | change_vars_to_frees (Abs(s,ty,t)) =
        (Abs(s,Type.varifyT ty,change_vars_to_frees t))
    | change_vars_to_frees (Var((s,i),ty)) = (Free(s,Type.unvarifyT ty))
    | change_vars_to_frees l = l;

  fun change_frees_to_vars (a$b) =
        (change_frees_to_vars a) $ (change_frees_to_vars b)
    | change_frees_to_vars (Abs(s,ty,t)) =
        (Abs(s,Type.varifyT ty,change_frees_to_vars t))
    | change_frees_to_vars (Free(s,ty)) = (Var((s,0),Type.varifyT ty))
    | change_frees_to_vars l = l;


end;
