(* * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) * * SPDX-License-Identifier: BSD-2-Clause *) (* interface to Norbert Schirmer's Hoare Package code *) structure HPfninfo = struct type t = {fname : string, inparams : (string * typ * int Absyn.ctype option) list, outparams : (string * typ * int Absyn.ctype) list, body : Absyn.ext_decl HoarePackage.bodykind option, spec : (string * Element.context) list, mod_annotated_protop : bool, recursivep : bool} end signature HPINTER = sig type csenv = ProgramAnalysis.csenv type fninfo = HPfninfo.t val globalsN : string val asm_to_string : (term -> string) -> ('typ,term,'fact) Element.ctxt -> string val mk_fninfo : theory -> csenv -> (string -> bool) -> Absyn.ext_decl list -> fninfo list val make_function_definitions : string -> (* name of locale where definitions will live *) csenv -> (* result of ProgramAnalysis over declarations *) typ list -> (* type arguments to state type operator *) thm list -> (* theorems defining names of functions *) fninfo list -> (* result of mk_fninfo above *) (Absyn.ext_decl list -> Proof.context -> (string * term * term) list) -> (* compilation/translation function *) string -> (* name of globals locale *) (typ,'a,'b) Element.ctxt list -> (* globals living in the globals locale *) theory -> (string * theory) end structure HPInter : HPINTER = struct type fninfo = HPfninfo.t type csenv = ProgramAnalysis.csenv (* make function information - perform a preliminary analysis on the abstract syntax to extract function names, parameters and specifications *) fun mk_fninfo thy csenv includeP decllist : fninfo list = let open Absyn NameGeneration (* don't fold top-level declarations into the table again: the program analysis that built the csenv in the first place will have already grabbed this *) fun toInclude (FnDefn((_, fname), _, _, _)) = includeP (node fname) | toInclude _ = false val table = List.foldl (fn (d, tab) => if toInclude d then ProgramAnalysis.update_hpfninfo0 d tab else tab) (ProgramAnalysis.get_hpfninfo csenv) decllist fun parse_spec fname fspec = case fspec of fnspec spec => let val toklist = Token.explode (Thy_Header.get_keywords thy) Position.none (node spec) (* it might be nice to remember where the spec came from so we could put in a reasonable pos'n above *) val filtered = List.filter Token.is_proper toklist val eof = Token.stopper val nameproplist = valOf (Scan.read eof HoarePackage.proc_specs filtered) handle Option => raise Fail ("Failed to scan spec for "^fname) fun nprop2Assume (name, prop) = ((Binding.name name, []), [(prop, [])]) in [("", Element.Assumes (map nprop2Assume nameproplist))] end | fn_modifies slist => let val mgoal = Modifies_Proofs.gen_modify_goalstring csenv fname slist in [("", Element.Assumes [((Binding.name (fname ^ "_modifies"), []), [(mgoal, [])])])] end | didnt_translate => [] | gcc_attribs _ => [] | relspec _ => [] (* fixme *) fun parse_specs fname fspecs = List.concat (map (parse_spec fname) fspecs) fun mk_ret rettype = if rettype = Void then [] else [(MString.dest (return_var_name rettype), CalculateState.ctype_to_typ (thy, rettype), rettype)] fun to_param vi = let open CalculateState ProgramAnalysis val cty = get_vi_type vi val typ = ctype_to_typ (thy,cty) in (MString.dest (get_mname vi), typ, SOME cty) end fun foldthis (_, d) acc = case d of FnDefn((_ (* retty *),fname_w), _ (* inparams *), prepost, _ (* body *)) => let open HoarePackage CalculateState ProgramAnalysis val fname = node fname_w val rettype = valOf (get_rettype fname csenv) in {fname = fname, inparams = map to_param (valOf (get_params fname csenv)) handle Option => raise Fail ("mk_fninfo: No function information for "^ fname), outparams = mk_ret rettype, recursivep = is_recursivefn csenv fname, body = SOME (BodyTerm d), spec = parse_specs fname prepost, mod_annotated_protop = false} :: acc end | Decl d0 => let in case node d0 of ExtFnDecl { name, specs, params = _, rettype = _ } => let open CalculateState ProgramAnalysis val fname = node name val params = map to_param (valOf (get_params fname csenv)) handle Option => raise Fail ("mk_fninfo: No function information for "^ fname) val rettype = valOf (get_rettype fname csenv) val _ = Feedback.informStr (0, "Added external decl for " ^ fname ^ " with " ^ Int.toString (length params) ^ " args.") val spec = parse_specs fname specs val mod_annotated_protop = isSome (get_modifies csenv fname) val body = NONE in {fname = fname, inparams = params, recursivep = false, outparams = mk_ret rettype, body = body, spec = spec, mod_annotated_protop = mod_annotated_protop} :: acc end | _ => acc end in Symtab.fold foldthis table [] end fun extract_element_asms e = case e of Element.Assumes asms => map (fn (nm, [(t,_)]) => (nm, t) | _ => raise Fail "extract_element_term: malformed Assumes") asms | _ => raise Fail "extract_element_term: malformed element" fun mkSpecFunction thy gloc cse styargs (f : fninfo) = let open TermsTypes val {fname,...} = f val state_ty = hd styargs val ctxt = thy2ctxt thy val mods = fname |> ProgramAnalysis.get_modifies cse |> valOf |> Binaryset.listItems val s = Free("s", state_ty) val t = Free("t", state_ty) val relbody = Modifies_Proofs.gen_modify_body thy state_ty s t mods val collect_t = mk_collect_t (mk_prod_ty (state_ty, state_ty)) fun typedterm t = Syntax.string_of_term ctxt t ^ " :: " ^ Syntax.string_of_typ ctxt (type_of t) fun appi f l = let fun recurse i l = case l of [] => () | h::t => (f i h; recurse (i + 1) t) in recurse 0 l end fun parse_spec thy (_, e) = let val lnm_strs = extract_element_asms e fun innermap thy (((speclocalename_b,_), tstr)) = let val speclocalename = Binding.name_of speclocalename_b (* jump into an lthy corresponding to globloc to parse the tstr; this lthy is dropped and not used again *) val lthy = Named_Target.init [] gloc thy val tm = Syntax.read_term lthy tstr val (vs, body) = strip_forall tm val PQopt = case body of Const(@{const_name "hoarep"}, _) $ _ (* gamma *) $ _ $_ $ P $ _ $ Q $ _ => if String.isSuffix "modifies" speclocalename then NONE else SOME (P,Q) | _ => let val (f, args) = strip_comb tm val cstr = case f of Const(s, _) => "const: "^s | Free(s, _) => "free: "^s | _ => "huh?" in raise Fail ("parse_spec: bad specification " ^ speclocalename ^ " : " ^ Syntax.string_of_term ctxt t ^ "\n(" ^ cstr ^ " applied to " ^ Int.toString (length args) ^ " arguments)") end in case PQopt of NONE => NONE | SOME (P0, Q0) => let val _ = appi (fn n => fn v => Feedback.informStr(0, "var#"^Int.toString n^" "^typedterm v)) vs val elty0 = dest_set_type (type_of P0) val sigma = valOf (TermsTypes.match_type_frees{pattern = elty0, m = state_ty}) handle Option => raise Fail "Couldn't match state types" val ptype = Syntax.string_of_typ ctxt fun foldthis (ty1, ty2) acc = (ptype ty1 ^ " |-> " ^ ptype ty2) :: acc val sigma_elts0 = TypTable.fold foldthis sigma [] val sigma_elts = String.concatWith ", " sigma_elts0 val _ = Feedback.informStr(0, "Sigma = " ^ sigma_elts) val P1 = subst_frees sigma P0 val Q1 = subst_frees sigma Q0 val vs = map (subst_frees sigma) vs val s = variant vs s val t = variant vs t val P2 = List.foldr (fn (v, t) => unbeta{bv=v,body=t}) P1 vs val Q2 = List.foldr (fn (v, t) => unbeta{bv=v,body=t}) Q1 vs val _ = Feedback.informStr(0, "P2 : " ^ typedterm P2) val _ = Feedback.informStr(0, "Q2 : " ^ typedterm Q2) val P = list_mk_comb(P2, vs) val Q = list_mk_comb(Q2, vs) val _ = Feedback.informStr(0, "P : " ^ typedterm P) val _ = Feedback.informStr(0, "Q : " ^ typedterm Q) val _ = Feedback.informStr(0, "s : " ^ typedterm s) val _ = Feedback.informStr(0, "t : " ^ typedterm t) val body_imp = list_mk_forall(vs, mk_imp(mk_IN(s,P), mk_IN(t,Q))) val Pexists = list_mk_exists(vs, mk_IN(s,P)) val res = collect_t $ list_mk_pabs([s,t], mk_conj(body_imp, Pexists)) val _ = Feedback.informStr(0, "Type of PQ combo: " ^ ptype (type_of res)) in SOME res end end in List.mapPartial (innermap thy) lnm_strs end val mod_spec = collect_t $ list_mk_pabs([s,t], relbody) val _ = Feedback.informStr(0, "modstr : " ^ typedterm mod_spec) val otherspecs = List.concat (map (parse_spec thy) (#spec f)) val fbody = if null otherspecs then mk_Spec(styargs, mod_spec) else let val _ = appi (fn n => fn t => Feedback.informStr(0, "spec#" ^ Int.toString n ^ ": " ^ typedterm t)) otherspecs val spec_t = List.foldl mk_inter mod_spec otherspecs in Const(@{const_name "guarded_spec_body"}, @{typ "strictc_errortype"} --> type_of spec_t --> mk_com_ty styargs) $ @{const "ImpossibleSpec"} $ spec_t end in (fname, fbody, fbody) end (* compile bodies - turn a list of AST values into a term list *) fun compile_fninfos globloc cse styargs compfn lthy (fninfo : fninfo list) = let open HoarePackage fun partition (acc as (asts,specs)) fns = case fns of [] => (List.rev asts, specs) | {body = SOME (BodyTerm x),...} :: t => partition (x::asts,specs) t | (i as {mod_annotated_protop = true, ...}) :: t => partition (asts,i::specs) t | _ :: t => partition acc t val (asts,toSpec) = partition ([],[]) fninfo in compfn asts lthy @ map (mkSpecFunction (Proof_Context.theory_of lthy) globloc cse styargs) toSpec end fun rhs_extras rhs = Term.fold_aterms (fn v as Free _ => insert (op aconv) v | _ => I) rhs []; fun extract_fixes elems = let open Element fun fix2term (n,tyopt,_) = case tyopt of NONE => NONE | SOME ty => SOME (Free(Binding.name_of n,ty)) fun recurse elems = case elems of [] => [] | Fixes fs::rest => List.mapPartial fix2term fs @ recurse rest | _ :: rest => recurse rest in recurse elems end fun create_bodyloc_elems globs thy (name, body_t, body_stripped_t) = let val rhs_vars0 = rhs_extras body_t (* Library.subtract has arguments in wrong order - sheesh! *) val rhs_vars = subtract (op aconv) globs rhs_vars0 fun mk_rhs(nm, vars, t) = let val t0 = TermsTypes.list_mk_abs (vars, t) in (nm, Term.map_types (Sign.certify_typ thy) t0) end val eqt_stripped = mk_rhs(name ^ "_body", [], body_stripped_t) val eqt = mk_rhs(name ^ (if null rhs_vars then "_body" else "_invs_body"), rhs_vars, body_t) fun eqn_to_definfo (nm, t) = let val b = Binding.name nm in ((b, NoSyn), ((Thm.def_binding b, []), t)) end val elems = if null rhs_vars then [eqn_to_definfo eqt] else [eqn_to_definfo eqt_stripped, eqn_to_definfo eqt] type lthy_def_info = (binding * mixfix) * (Attrib.binding * term) in (elems : lthy_def_info list, (name, if null rhs_vars then body_t else body_stripped_t)) end val globalsN = "_global_addresses" fun add_syntax (name,recp,inpars,outpars) thy = let open HoarePackage val name = suffix HoarePackage.proc_deco name val pars = map (fn par => (In,varname par)) inpars@ map (fn par => (Out,varname par)) outpars val thy_decl = thy |> Context.theory_map (add_params Morphism.identity name pars) |> Context.theory_map (add_state_kind Morphism.identity name HoarePackage.Record) |> (if recp then Context.theory_map (add_recursive Morphism.identity name) else (fn x => x)) in thy_decl end fun asm_to_string tmw e = case e of Element.Assumes asms => let fun asm_element pfx ((nm, _ (*attrs*)), ttmlist) = pfx ^ Binding.name_of nm ^ ": " ^ commas (map (tmw o #1) ttmlist) ^ "\n" in "Assumes:" ^ (if length asms = 1 then " " ^ String.concat (map (asm_element "") asms) else "\n" ^ String.concat (map (asm_element " ") asms)) end | Element.Fixes stysynlist => "Fixes: " ^ commas (map (Binding.name_of o #1) stysynlist) ^ "\n" | _ => "??\n" ; (* The following is modelled on the code in HoarePackage.procedures_definition *) fun make_function_definitions localename (cse : ProgramAnalysis.csenv) (styargs : typ list) (nmdefs : thm list) (fninfo : fninfo list) compfn globloc globals_elems thy = let open HoarePackage val localename_b = Binding.name localename val thy = thy |> Context.theory_map (HoarePackage.set_default_state_kind HoarePackage.Record) val name_pars = map (fn {fname,inparams,outparams,recursivep,...} => (fname, recursivep, map #1 inparams, map #1 outparams)) fninfo val name_specs = map (fn {fname,spec,...} => (fname,spec)) fninfo val thy = List.foldr (uncurry add_syntax) thy name_pars val lthy = Named_Target.init [] globloc thy val name_body = compile_fninfos globloc cse styargs compfn lthy fninfo val _ = Feedback.informStr (0, "Translated all functions") val globs = extract_fixes globals_elems val body_elems = map (create_bodyloc_elems globs thy) name_body fun add_body_defs ((elems, _ (* name-body *)), lthy) = let fun foldthis (e, lthy) = let val _ = Feedback.informStr (0, "Adding body_def for " ^ Binding.name_of (#1 (#1 e))) in lthy |> Local_Theory.begin_nested |> snd |> Local_Theory.define e |> #2 |> Local_Theory.end_nested end in List.foldl foldthis lthy elems end val lthy = List.foldl add_body_defs lthy body_elems val thy = Local_Theory.exit_global lthy (* set up _impl by defining \ *) val thy = if List.exists (isSome o #body) fninfo then let val lthy = Named_Target.init [] globloc thy fun get_body fni = let val nm = #fname fni in case (#body fni, #mod_annotated_protop fni) of (NONE, false) => NONE | _ => let val realnm = Consts.intern (Proof_Context.consts_of lthy) (nm ^ "_body") in SOME (Syntax.check_term lthy (Const(realnm, dummyT))) end end (* name for thm, (proc constant, body constant) *) val (_, lthy) = StaticFun.define_tree_and_thms_with_defs @{binding \} (map (suffix HoarePackage.implementationN o #fname) fninfo) nmdefs (map get_body fninfo) @{term "id :: int => int"} lthy in Local_Theory.exit_global lthy end else thy val globloc_expr = ([(globloc, (("", false), (Expression.Named [], [])))], []) (* three levels of folding - woohoo *) fun add_spec_locales ((name,specs), (localemap, thy)) = let (* add any spec locales *) fun foldthis ((_, e), (localemap, thy)) = let val lnm_strs = extract_element_asms e fun innerfold (((speclocalename_b,_), tstr), (localemap, thy)) = let val speclocalename = Binding.name_of speclocalename_b (* jump into an lthy corresponding to globloc to parse the tstr; this lthy is dropped and not used again *) val lthy = Named_Target.init [] globloc thy val t = Syntax.read_term lthy tstr val _ = Feedback.informStr (0, "Adding spec locale "^speclocalename^ (if speclocalename = "" then "" else " ")^ "for function "^name ^ " (" ^ tstr ^ ")") val e' = Element.Assumes [((speclocalename_b,[]), [(TermsTypes.mk_prop t, [])])] val speclocalename_b = Binding.map_name (fn s => if s = "" then suffix HoarePackage.specL name else s) speclocalename_b val (locname, ctxt') = Expression.add_locale speclocalename_b speclocalename_b [] globloc_expr [e'] thy (* localename is the name we wanted, locname is the name Isabelle gives back to us. This will be properly qualified yadda yadda *) in (Symtab.update (speclocalename, locname) localemap, Local_Theory.exit_global ctxt') end in List.foldl innerfold (localemap,thy) lnm_strs end val (speclocnames,thy) = List.foldl foldthis (localemap, thy) specs in (speclocnames, thy) end val (_ (* specloc_names *), thy) = List.foldl add_spec_locales (Symtab.empty, thy) name_specs val (globloc, ctxt) = Expression.add_locale localename_b localename_b [] globloc_expr [] thy in (globloc, Local_Theory.exit_global ctxt) end end (* struct *)