(** This module is used to extract the pure ASTs to various theorem provers. It defines utilities and helpers to make the work as easy as possible: we try to factorize as much as possible the different extractions to the backends we target. *) open Errors open Pure open TranslateCore module C = Contexts module RegionVarId = T.RegionVarId (** The local logger *) let log = L.pure_to_extract_log type extraction_ctx = { type_context : C.type_context; fun_context : C.fun_context; } (** Extraction context. Note that the extraction context contains information coming from the CFIM AST (not only the pure AST). This is useful for naming, for instance: we use the region information to generate the names of the backward functions, etc. *) type region_group_info = { id : RegionGroupId.id; (** The id of the region group. Note that a simple way of generating unique names for backward functions is to use the region group ids. *) region_names : string option list; (** The names of the region variables included in this group. Note that names are not always available... *) } module StringSet = Collections.MakeSet (Collections.OrderedString) module StringMap = Collections.MakeMap (Collections.OrderedString) type name = Identifiers.name type name_formatter = { bool_name : string; char_name : string; int_name : integer_type -> string; str_name : string; type_name : name -> string; (** Provided a basename, compute a type name. *) fun_name : A.fun_id -> name -> int -> region_group_info option -> string; (** Inputs: - function id: this is especially useful to identify whether the function is an assumed function or a local function - function basename - number of region groups - region group information in case of a backward function (`None` if forward function) *) var_basename : StringSet.t -> ty -> string; (** Generates a variable basename. Inputs: - the set of names used in the context so far - the type of the variable (can be useful for heuristics, in order not to always use "x" for instance, whenever naming anonymous variables) Note that once the formatter generated a basename, we add an index if necessary to prevent name clashes: the burden of name clashes checks is thus on the caller's side. *) } (** A name formatter's role is to come up with name suggestions. For instance, provided some information about a function (its basename, information about the region group, etc.) it should come up with an appropriate name for the forward/backward function. It can of course apply many transformations, like changing to camel case/ snake case, adding prefixes/suffixes, etc. *) let compute_type_def_name (fmt : name_formatter) (def : type_def) : string = fmt.type_name def.name (** A helper function: generates a function suffix from a region group information. TODO: move all those helpers. *) let default_fun_suffix (num_region_groups : int) (rg : region_group_info option) : string = (* There are several cases: - [rg] is `Some`: this is a forward function: - if there are no region groups (i.e., no backward functions) we don't add any suffix - if there are region gruops, we add "_fwd" - [rg] is `None`: this is a backward function: - this function has one region group: we add "_back" - this function has several backward function: we add "_back" and an additional suffix to identify the precise backward function *) match rg with | None -> if num_region_groups = 0 then "" else "_fwd" | Some rg -> assert (num_region_groups > 0); if num_region_groups = 1 then (* Exactly one backward function *) "_back" else if (* Several region groups/backward functions: - if all the regions in the group have names, we use those names - otherwise we use an index *) List.for_all Option.is_some rg.region_names then (* Concatenate the region names *) "_back" ^ String.concat "" (List.map Option.get rg.region_names) else (* Use the region index *) "_back" ^ RegionGroupId.to_string rg.id (** Extract information from a function, and give this information to a [name_formatter] to generate a function's name. Note that we need region information coming from CFIM (when generating the name for a backward function, we try to use the names of the regions to *) let compute_fun_def_name (ctx : extraction_ctx) (fmt : name_formatter) (fun_id : A.fun_id) (rg_id : RegionGroupId.id option) : string = (* Lookup the function CFIM signature (we need the region information) *) let sg = CfimAstUtils.lookup_fun_sig fun_id ctx.fun_context.fun_defs in let basename = CfimAstUtils.lookup_fun_name fun_id ctx.fun_context.fun_defs in (* Compute the regions information *) let num_region_groups = List.length sg.regions_hierarchy in let rg_info = match rg_id with | None -> None | Some rg_id -> let rg = RegionGroupId.nth sg.regions_hierarchy rg_id in let regions = List.map (fun rid -> RegionVarId.nth sg.region_params rid) rg.regions in let region_names = List.map (fun (r : T.region_var) -> r.name) regions in Some { id = rg.id; region_names } in fmt.fun_name fun_id basename num_region_groups rg_info (** We use identifiers to look for name clashes *) type id = | FunId of A.fun_id * RegionGroupId.id option | TypeId of type_id | VarId of VarId.id | UnknownId (** Used for stored various strings like keywords, definitions which should always be in context, etc. and which can't be linked to one of the above. *) [@@deriving show, ord] module IdOrderedType = struct type t = id let compare = compare_id let to_string = show_id let pp_t = pp_id let show_t = show_id end module IdMap = Collections.MakeMap (IdOrderedType) type names_map = { id_to_name : string IdMap.t; name_to_id : id StringMap.t; (** The name to id map is used to look for name clashes, and generate nice debugging messages: if there is a name clash, it is useful to know precisely which identifiers are mapped to the same name... *) names_set : StringSet.t; } (** The names map stores the mappings from names to identifiers and vice-versa. We use it for lookups (during the translation) and to check for name clashes. *) let names_map_add (id : id) (name : string) (nm : names_map) : names_map = (* Sanity check: no clashes *) assert (not (StringSet.mem name nm.names_set)); (* Insert *) let id_to_name = IdMap.add id name nm.id_to_name in let name_to_id = StringMap.add name id nm.name_to_id in let names_set = StringSet.add name nm.names_set in { id_to_name; name_to_id; names_set } let names_map_find (id : id) (nm : names_map) : string = IdMap.find id nm.id_to_name let names_map_find_function (id : A.fun_id) (rg : RegionGroupId.id option) (nm : names_map) : string = names_map_find (FunId (id, rg)) nm let names_map_find_local_function (id : FunDefId.id) (rg : RegionGroupId.id option) (nm : names_map) : string = names_map_find_function (A.Local id) rg nm let names_map_find_type (id : type_id) (nm : names_map) : string = assert (id <> Tuple); names_map_find (TypeId id) nm let names_map_find_local_type (id : TypeDefId.id) (nm : names_map) : string = names_map_find_type (AdtId id) nm (** Make a basename unique (by adding an index). We do this in an inefficient manner (by testing all indices starting from 0) but it shouldn't be a bottleneck. [add_index]: concatenates a given index to the variable's basename. *) let var_basename_to_unique (names_set : StringSet.t) (add_index : int -> string) : string = let rec gen (i : int) : string = let s = add_index i in if StringSet.mem s names_set then gen (i + 1) else s in gen 0