https://github.com/EasyCrypt/easycrypt
Raw File
Tip revision: 32f0ee5d128b91f3c82e79fde23d61d207c08a4f authored by Adrien Koutsos on 25 August 2023, 16:20:56 UTC
[cost v2] printing of full mpath + first wrapped mpath in the parser
Tip revision: 32f0ee5
ecPath.ml
(* -------------------------------------------------------------------- *)
open EcUtils
open EcMaps
open EcSymbols
open EcIdent

(* -------------------------------------------------------------------- *)
(** {2 Path} *)

type path = {
  p_node : path_node;
  p_tag  : int
}

and path_node =
| Psymbol of symbol
| Pqname  of path * symbol

(* -------------------------------------------------------------------- *)
let p_equal   = ((==) : path -> path -> bool)
let p_hash    = fun p -> p.p_tag
let p_compare = fun p1 p2 -> p_hash p1 - p_hash p2

module Hspath = Why3.Hashcons.Make (struct
  type t = path

  let equal_node p1 p2 =
    match p1, p2 with
    | Psymbol id1, Psymbol id2 ->
        sym_equal id1 id2
    | Pqname (p1, id1), Pqname(p2, id2) ->
        sym_equal id1 id2 && p_equal p1 p2
    | _ -> false

  let equal p1 p2 = equal_node p1.p_node p2.p_node

  let hash p =
    match p.p_node with
    | Psymbol id    -> Hashtbl.hash id
    | Pqname (p,id) -> Why3.Hashcons.combine p.p_tag (Hashtbl.hash id)

  let tag n p = { p with p_tag = n }
end)

module Path = MakeMSH (struct
  type t  = path
  let tag = p_hash
end)

let rec p_ntr_compare (p1 : path) (p2 : path) =
  match p1.p_node, p2.p_node with
  | Psymbol _, Pqname  _ -> -1
  | Pqname  _, Psymbol _ -> +1

  | Psymbol x1, Psymbol x2 ->
       String.compare x1 x2

  | Pqname (p1, x1), Pqname (p2, x2) -> begin
      match p_ntr_compare p1 p2 with
      | 0 -> String.compare x1 x2
      | n -> n
    end

(* -------------------------------------------------------------------- *)
module Mp = Path.M
module Hp = Path.H

module Sp = struct
  include Path.S

  let ntr_elements s =
    List.ksort ~key:identity ~cmp:p_ntr_compare (elements s)
end

(* -------------------------------------------------------------------- *)
let mk_path node =
  Hspath.hashcons { p_node = node; p_tag = -1; }

let psymbol id   = mk_path (Psymbol id)
let pqname  p id = mk_path (Pqname(p,id))

let rec pappend p1 p2 =
  match p2.p_node with
  | Psymbol id -> pqname p1 id
  | Pqname(p2,id) -> pqname (pappend p1 p2) id

let pqoname p id =
  match p with
  | None   -> psymbol id
  | Some p -> pqname p id

(* -------------------------------------------------------------------- *)
let rec tostring p =
  match p.p_node with
  | Psymbol x    -> x
  | Pqname (p,x) -> Printf.sprintf "%s.%s" (tostring p) x

let tolist =
  let rec aux l p =
    match p.p_node with
    | Psymbol x     -> x :: l
    | Pqname (p, x) -> aux (x :: l) p in
  aux []

let toqsymbol (p : path) =
  match p.p_node with
  | Psymbol x     -> ([], x)
  | Pqname (p, x) -> (tolist p, x)

let fromqsymbol ((nm, x) : qsymbol) =
  pqoname
    (List.fold_left
       (fun p x -> Some (pqoname p x)) None nm)
    x

let basename p =
  match p.p_node with
  | Psymbol x     -> x
  | Pqname (_, x) -> x

let extend p s =
  List.fold_left pqname p s

let prefix p =
  match p.p_node with
  | Psymbol _ -> None
  | Pqname (p, _) -> Some p

let rec getprefix_r acc p q =
  match p_equal p q with
  | true  -> Some acc
  | false ->
      match q.p_node with
      | Psymbol _     -> None
      | Pqname (q, x) -> getprefix_r (x::acc) p q

let getprefix p q = getprefix_r [] p q

let isprefix p q =
  match getprefix p q with
  | None   -> false
  | Some _ -> true

let rec rootname p =
  match p.p_node with
  | Psymbol x     -> x
  | Pqname (p, _) -> rootname p

let rec p_size p =
  match p.p_node with
  | Psymbol _     -> 1
  | Pqname (p, _) -> 1 + (p_size p)


(* -------------------------------------------------------------------- *)
(** {2 Module path} *)

(* -------------------------------------------------------------------- *)
type wrap_k = [`Ext | `Cb]

type agk  = ident * wrap_k
type agks = agk list

(* -------------------------------------------------------------------- *)
(** top-level module path: an ident (for abstract modules) or a concrete path *)
type mpath_core_top = [`Local of ident | `Concrete of path]

(** a core module path *)
type mpath_core_node =
  | Top  of mpath_core_top

  | App  of mpath_core * mpath list
  (** application, [App(mc,args)] is [mc(args)]
      applications are strict and maximally grouped, i.e. there are no
      - [App (_, [])]
      - [App (App (_,_), _)] *)

  | Wrap of ident * wrap_k * mpath_core
  (** [Wrap (a, `Ext, mc)] delegates the evaluation of [mc] to agent [a].
      [Wrap (a, `Cb , mc)] restores [mc] evaluation to [a]'s caller. *)

(** hashconsed [mpath_core_node] *)
and mpath_core = { c_cnt : mpath_core_node; c_tag : int }

(** a module path:
    - [{ core; sub = None   }] is [core].
    - [{ core; sub = Some p }] is [core.p]. *)
and mpath = {
  core  : mpath_core;
  sub   : path option;
  m_tag : int;                   (* hashconsing *)
}

(* -------------------------------------------------------------------- *)
let m_equal     = ((==) : mpath      -> mpath      -> bool)
let mcore_equal = ((==) : mpath_core -> mpath_core -> bool)

(* -------------------------------------------------------------------- *)
let mpath_core_top_equal (mt1 : mpath_core_top) (mt2 : mpath_core_top) =
  match mt1, mt2 with
  | `Local id1, `Local id2 -> EcIdent.id_equal id1 id2
  | `Concrete p1, `Concrete p2 -> p_equal p1 p2
  | _, _ -> false

let mpath_core_top_hash (mt : mpath_core_top) =
  match mt with
  | `Local id -> EcIdent.id_hash id
  | `Concrete p -> p_hash p

(* -------------------------------------------------------------------- *)
let mcore_hash (p : mpath_core) = p.c_tag
let mcore_compare (p1 : mpath_core) (p2 : mpath_core) =
  mcore_hash p1 - mcore_hash p2

(* -------------------------------------------------------------------- *)
let m_hash    (p : mpath)               = p.m_tag
let m_compare (p1 : mpath) (p2 : mpath) = m_hash p1 - m_hash p2

(* -------------------------------------------------------------------- *)
(** resolved top-level module path *)
type mpath_top_r =
  [ | `Local of ident
    | `Concrete of path * path option ]

(* -------------------------------------------------------------------- *)
(** {3 Module core path and path hashconsing} *)

(** Hasconsing of [mpath_core].
    Can assume that strict sub-terms are already hashconsed
    (either [path], [mpath], or [mpath_core]). *)
module Hsmpath_core = Why3.Hashcons.Make (struct
  type t = mpath_core

  let equal (m1 : t) (m2 : t) =
    match m1.c_cnt, m2.c_cnt with
    | Top t1, Top t2 -> mpath_core_top_equal t1 t2
    | App (c1, args1), App (c2,args2) ->
      mcore_equal c1 c2 && List.length args1 = List.length args2 &&
      List.for_all2 m_equal args1 args2
    | Wrap (a1,k1,c1), Wrap (a2,k2,c2) ->
      id_equal a1 a2 && k1 = k2 && mcore_equal c1 c2

    | App _, Wrap _ | Wrap _, App _
    | (App _ | Wrap _), Top _ | Top _, (App _ | Wrap _) -> false

  let hash (m : t) =
    match m.c_cnt with
    | Top t -> mpath_core_top_hash t
    | App (c,args) ->
      let hash = mcore_hash c in
      Why3.Hashcons.combine_list m_hash hash args
    | Wrap (a,k,c) ->
      Why3.Hashcons.combine2 (id_hash a) (Hashtbl.hash k) (mcore_hash c)

  let tag n (p : t) = { p with c_tag = n }
end)

(** Hasconsing of [mpath].
    Same assumptions as [Hsmpath_core]. *)
module Hsmpath = Why3.Hashcons.Make (struct
  type t = mpath

  let equal (m1 : t) (m2 : t) =
    mcore_equal m1.core m2.core && oeq p_equal m1.sub m2.sub

  let hash (m : t) =
    let hash  = mcore_hash m.core in
    let hash' = Why3.Hashcons.combine_option p_hash m.sub in
    Why3.Hashcons.combine hash hash'

  let tag n (p : t) = { p with m_tag = n }
end)

(* -------------------------------------------------------------------- *)
module MPath = MakeMSH (struct
  type t  = mpath
  let tag = m_hash
end)

module MCPath = MakeMSH (struct
  type t  = mpath_core
  let tag = mcore_hash
end)

(* -------------------------------------------------------------------- *)
let mcore_top_ntr_compare (mt1 : mpath_core_top) (mt2 : mpath_core_top) =
  match mt1, mt2 with
  | `Local    _  , `Concrete _   -> -1
  | `Concrete _  , `Local    _   -> +1
  | `Local    id1, `Local    id2 -> id_ntr_compare id1 id2
  | `Concrete p1 , `Concrete p2  -> p_ntr_compare p1 p2

let rec mcore_ntr_compare (mc1 : mpath_core) (mc2 : mpath_core) =
  match mc1.c_cnt, mc2.c_cnt with
  | Top t1, Top t2 -> mcore_top_ntr_compare t1 t2

  | Top _           , (App _ | Wrap _) -> -1
  | (App _ | Wrap _), Top _            -> +1

  | Wrap _, App _  -> -1
  | App _ , Wrap _ -> +1

  | Wrap (a1,k1,c1), Wrap (a2,k2,c2) ->
    compare3
      (lazy (id_ntr_compare    a1 a2))
      (lazy (Stdlib.compare    k1 k2))
      (lazy (mcore_ntr_compare c1 c2))

  | App (c1,args1), App (c2,args2) ->
    match mcore_ntr_compare c1 c2 with
    | 0 -> List.compare m_ntr_compare args1 args2
    | n -> n

and m_ntr_compare (m1 : mpath) (m2 : mpath) =
  match mcore_ntr_compare m1.core m2.core with
  | 0 -> ocompare p_ntr_compare m1.sub m2.sub
  | n -> n

(* -------------------------------------------------------------------- *)
module MCm = MCPath.M
module HCm = MCPath.H

module Mm = MPath.M
module Hm = MPath.H

module Sm = struct
  include MPath.S

  let ntr_elements s =
    List.ksort ~key:identity ~cmp:m_ntr_compare (elements s)
end

(* -------------------------------------------------------------------- *)
(** {3 Module core path smart constructors} *)

let mcore (c_cnt : mpath_core_node) : mpath_core =
    Hsmpath_core.hashcons { c_cnt; c_tag = -1; }

let mcore_abs (id : EcIdent.t) : mpath_core =
  mcore (Top (`Local id))

let mcore_crt (p : path) : mpath_core =
  mcore (Top (`Concrete p))

let mcore_apply (c : mpath_core) (args : mpath list) : mpath_core =
  if args = [] then c  (* invariant: no empty application *)
  else
    let cnt = (* invariant: maximally grouped application *)
      match c.c_cnt with
      | App (c',args') -> App (c', args' @ args)
      | Wrap _ | Top _ -> App (c, args)
    in
    mcore cnt

let mcore_wrap (agk : agk) (c : mpath_core) : mpath_core =
  mcore (Wrap (fst agk, snd agk, c))

let mcore_wrap_l (agks : agks) (c : mpath_core) : mpath_core =
  List.fold_left ((^~) mcore_wrap) c (List.rev agks)

(* -------------------------------------------------------------------- *)
(** {3 Module path smart constructors} *)

let mk_mpath core sub = Hsmpath.hashcons { core; sub; m_tag = -1; }

let mpath (agks : agks) (p : mpath_top_r) (args : mpath list) : mpath =
  match p with
  | `Local id ->
    let core = mcore_wrap_l agks (mcore_apply (mcore_abs id) args) in
    mk_mpath core None

  | `Concrete (p, sub) ->
    let core = mcore_wrap_l agks (mcore_apply (mcore_crt p) args) in
    mk_mpath core sub

let mpath_abs agks id args = mpath agks (`Local id) args
let mident id = mpath_abs [] id []

let mpath_crt agks p args sub = mpath agks (`Concrete(p,sub)) args

let mqname (mp : mpath) (x : symbol) : mpath =
  match mp.sub with
  | None     -> mk_mpath mp.core (Some (psymbol x   ))
  | Some sub -> mk_mpath mp.core (Some (pqname sub x))

(* strips arguments of a [mpath_core] *)
let mcore_astrip ~keep_agks (c : mpath_core) : mpath_core =
  let rec doit c =
    match c.c_cnt with
    | Top _ -> c
    | App (c, args) -> doit c
    | Wrap (a,k,c) ->
      if keep_agks then mcore (Wrap (a,k,doit c)) else doit c
  in
  doit c

let mastrip ~keep_agks (mp : mpath) =
  mk_mpath (mcore_astrip ~keep_agks mp.core) mp.sub

let m_apply (mp : mpath) (args : mpath list) =
  mk_mpath (mcore_apply mp.core args) mp.sub

let m_functor ~keep_agks (mp : mpath) =
  mk_mpath (mcore_astrip ~keep_agks mp.core) None

let wrap (a : ident) (k : wrap_k) (m : mpath) : mpath =
  mk_mpath (mcore (Wrap (a, k, m.core))) m.sub

(* -------------------------------------------------------------------- *)
(** {3 Module resolution} *)

(** [resolves_core ragks core args] resolves the module [$(agks( core(args) )]
    where [agks = List.rev ragks] *)
let rec resolve_core
    (agks : agks) (core : mpath_core) (args : mpath list)
  : agks * mpath_core_top * mpath list
  =
  match core.c_cnt with
  (* TODO: cost: should this be [List.rev agks]? *)
  | Top top           -> (agks, top, args)
  | App (core, args') -> resolve_core agks core (args' @ args)
  | Wrap (ag', `Ext, mc) ->
    (** [$agks      ( ( $ag'(mc)                  ) (args) ) ->
         $agks      ( ( $ag'(mc ($cb_ag'(args)) ) )        ) ->
         ${agks,ag'}( mc ($cb_ag'(args))                   )   ] *)
    let args = List.map (wrap ag' `Cb) args in
    resolve_core ((ag', `Ext) :: agks) mc args

  | Wrap (ag', `Cb, mc) ->      (* nothing to do with arguments here *)
    resolve_core ((ag', `Cb) :: agks) mc args


(* -------------------------------------------------------------------- *)
let mtop_equal (mt1 : mpath_top_r) (mt2 : mpath_top_r) =
  match mt1, mt2 with
  | `Local id1, `Local id2 -> EcIdent.id_equal id1 id2
  | `Concrete(p1, o1), `Concrete(p2, o2) ->
    p_equal p1 p2 && oall2 p_equal o1 o2
  | _, _ -> false

(* -------------------------------------------------------------------- *)
let resolve { core; sub; } : agks * mpath_top_r * mpath list =
  let agks, top, args = resolve_core [] core [] in
  match top, sub with
  | `Local   _ , Some _   -> assert false (* abstract modules have no sub-modules *)
  | `Local top , None     -> agks, `Local top, args
  | `Concrete p, Some sub -> agks, `Concrete (p, Some sub), args
  | `Concrete p, None     -> agks, `Concrete (p,     None), args

(* -------------------------------------------------------------------- *)
(* TODO: cost: hashconsing *)
let margs (m : mpath) : mpath list  = let _, _, x = resolve m in x
let mtop  (m : mpath) : mpath_top_r = let _, x, _ = resolve m in x
let magks (m : mpath) : agks        = let x, _, _ = resolve m in x

(* -------------------------------------------------------------------- *)
(** {3 Module path utilities} *)

let rec mcore_is_local (mc : mpath_core) : bool =
  match mc.c_cnt with
  | Top  (`Local    _) -> true
  | Top  (`Concrete _) -> false
  | App  (c,_)         -> mcore_is_local c
  | Wrap (_,_,c)       -> mcore_is_local c

let m_is_local (mp : mpath) : bool = mcore_is_local mp.core

(* -------------------------------------------------------------------- *)
let rec mcore_is_concrete (mc : mpath_core) : bool =
  match mc.c_cnt with
  | Top  (`Local    _) -> false
  | Top  (`Concrete _) -> true
  | App  (c,_)         -> mcore_is_concrete c
  | Wrap (_,_,c)       -> mcore_is_concrete c

let m_is_concrete (mp : mpath) : bool =
  mcore_is_concrete mp.core

(* -------------------------------------------------------------------- *)
let rec mcore_get_ident (mc : mpath_core) : ident =
  match mc.c_cnt with
  | Top  (`Local    id) -> id
  | Top  (`Concrete _ ) -> assert false
  | App  (c,_)          -> mcore_get_ident c
  | Wrap (_,_,c)        -> mcore_get_ident c

let mget_ident mp = mcore_get_ident mp.core

(* -------------------------------------------------------------------- *)
let rec mcore_fv fv mc =
  match mc.c_cnt with
  | Top (`Local   id) -> EcIdent.fv_add id fv
  | Top (`Concrete _) -> fv
  | App (mc,args) ->
    List.fold_left m_fv (mcore_fv fv mc) args
  | Wrap (a, _, mc) ->
    mcore_fv (EcIdent.fv_add a fv) mc

and m_fv fv mp = mcore_fv fv mp.core

(* -------------------------------------------------------------------- *)
let rec pp_mcore fmt (mc : mpath_core) =
  let pp_args fmt args =
    assert (args <> []);
    Format.fprintf fmt "@[(%a)@]" (pp_list "," pp_m) args
  in
  match mc.c_cnt with
  | Top  (`Local    id) -> Format.fprintf fmt "%s" (EcIdent.tostring id)
  | Top  (`Concrete p ) -> Format.fprintf fmt "%s" (tostring p)
  | App  (c,args )      -> Format.fprintf fmt "%a%a" pp_mcore c pp_args args
  | Wrap (a, `Ext, c)   -> Format.fprintf fmt "$%a(%a)" EcIdent.pp_ident a pp_mcore c
  | Wrap (a, `Cb , c)   -> Format.fprintf fmt "$cb_%a(%a)" EcIdent.pp_ident a pp_mcore c

and pp_m fmt (mp : mpath) =
  let pp_sub fmt =
    match mp.sub with
    | None -> ()
    | Some sub -> Format.fprintf fmt ".%s" (tostring sub)
  in
  Format.fprintf fmt "%a%t" pp_mcore mp.core pp_sub

(* -------------------------------------------------------------------- *)
(** {2 Variable and procedure path} *)

type xpath = {
  x_top : mpath;
  x_sub : symbol;
  x_tag : int;
}

let x_equal   = ((==) : xpath -> xpath -> bool)
let x_hash    = fun p -> p.x_tag
let x_compare = fun p1 p2 -> x_hash p1 - x_hash p2

let x_compare_na x1 x2 =
  x_compare x1 x2 (* FIXME: doc says something about x_top being normalized *)

(* -------------------------------------------------------------------- *)
module Hsxpath = Why3.Hashcons.Make (struct
  type t = xpath

  let equal m1 m2 =
    m_equal m1.x_top m2.x_top && EcSymbols.sym_equal m1.x_sub m2.x_sub

  let hash m =
    Why3.Hashcons.combine (m_hash m.x_top) (Hashtbl.hash m.x_sub)

  let tag n p = { p with x_tag = n }
end)

(* -------------------------------------------------------------------- *)
module XPath = MakeMSH (struct
  type t  = xpath
  let tag = x_hash
end)

(* -------------------------------------------------------------------- *)
let x_ntr_compare (xp1 : xpath) (xp2 : xpath) =
  match m_ntr_compare xp1.x_top xp2.x_top with
  | 0 -> String.compare xp1.x_sub xp2.x_sub
  | n -> n

(* -------------------------------------------------------------------- *)
let xpath top sub =
  Hsxpath.hashcons { x_top = top; x_sub = sub; x_tag = -1; }

let x_fv fv xp = m_fv fv xp.x_top

(* TODO: cost: hashconsing seems broken here, try to add it back once
   all tests go through *)
let xastrip ~keep_agks x = { x with x_top = mastrip ~keep_agks x.x_top }
let xbasename xp = xp.x_sub

let pp_x fmt x = Format.fprintf fmt "%a.%s" pp_m x.x_top x.x_sub

(* -------------------------------------------------------------------- *)
module Mx = XPath.M
module Hx = XPath.H

module Sx = struct
  include XPath.S

  let ntr_elements s =
    List.ksort ~key:identity ~cmp:x_ntr_compare (elements s)
end

(* -------------------------------------------------------------------- *)
let rec m_tostring (m : mpath) = Format.asprintf "%a" pp_m m

let x_tostring x =
  Printf.sprintf "%s./%s"
    (m_tostring x.x_top) x.x_sub

(* -------------------------------------------------------------------- *)
type smsubst = {
  sms_crt : path Mp.t;
  sms_id  : mpath Mid.t;
  sms_ag  : ident Mid.t;
}

(* -------------------------------------------------------------------- *)
let sms_identity : smsubst =
  { sms_crt = Mp.empty; sms_id = Mid.empty; sms_ag = Mid.empty; }

(* -------------------------------------------------------------------- *)
let sms_is_identity (s : smsubst) =
  Mp.is_empty s.sms_crt && Mid.is_empty s.sms_id && Mid.is_empty s.sms_ag

(* -------------------------------------------------------------------- *)
let sms_bind_abs (x : ident) (mp : mpath) (s : smsubst) =
  { s with sms_id = Mid.add x mp s.sms_id }

(* -------------------------------------------------------------------- *)
let sms_bind_agent (x : ident) (y : ident) (s : smsubst) =
  { s with sms_ag = Mid.add x y s.sms_ag }

(* -------------------------------------------------------------------- *)
let p_subst (s : path Mp.t) =
  if Mp.is_empty s then identity else

  let doit (aux : path -> path) (p : path) =
    match p.p_node with
    | Psymbol _ -> p
    | Pqname(q, x) -> pqname (aux q) x in

  let p_subst (aux : path -> path) (p : path) =
    ofdfl (fun () -> doit aux p) (Mp.find_opt p s)

  in Hp.memo_rec 107 p_subst

(* -------------------------------------------------------------------- *)
let m_subst (s : smsubst) =
  let hc = HCm.create 127 in
  let h  =  Hm.create 127 in

  (* [doit_r] with memoisation *)
  let rec doit (mp : mpath) : mpath =
    try Hm.find h mp with
    | Not_found -> let r = doit_r mp in Hm.add h mp r; r

  (* [doit_core_r] with memoisation *)
  and doit_core (mc : mpath_core) : mpath =
    try HCm.find hc mc with
    | Not_found -> let r = doit_core_r mc in HCm.add hc mc r; r

  and doit_r (mp : mpath) : mpath =
    let m = doit_core mp.core in
    let sub = mp.sub |> omap (p_subst s.sms_crt) in
    match m.sub, sub with
    | Some m_sub, None   -> m
    | None      , _      -> mk_mpath m.core sub
    | Some _    , Some _ -> assert false
  (* impossible (abstract modules have no sub-modules) *)

  and doit_core_r (mc : mpath_core) : mpath =
    match mc.c_cnt with
    | Top (`Concrete p) ->
        let p' = p_subst s.sms_crt p in
        if p_equal p p' then mk_mpath mc None else mk_mpath (mcore_crt p') None

    | Top (`Local id) ->
      begin
        try Mid.find id s.sms_id with
        | Not_found -> mk_mpath mc None
      end

    | App (c,args) ->
      let m = doit_core c in
      let args = List.Smart.map doit args in
      begin
        match m.sub with
        | Some _ -> assert (args <> []); assert false
        (* impossible (abstract modules have no sub-modules) *)

        | None -> mk_mpath (mcore_apply m.core args) None
      end

    | Wrap (a,k,c) ->
      let a = Mid.find_def a a s.sms_ag in
      wrap a k (doit_core c)
  in
  doit

let m_subst (s : smsubst) =
  if sms_is_identity s then identity else m_subst s

(* -------------------------------------------------------------------- *)
let x_subst (s : smsubst) (xp : xpath) =
  xpath (m_subst s xp.x_top) xp.x_sub

let x_subst (s : smsubst) =
  if sms_is_identity s then identity else x_subst s
back to top