-
Loïc Correnson authoredLoïc Correnson authored
data.ml 21.76 KiB
(**************************************************************************)
(* *)
(* This file is part of Frama-C. *)
(* *)
(* Copyright (C) 2007-2020 *)
(* CEA (Commissariat à l'énergie atomique et aux énergies *)
(* alternatives) *)
(* *)
(* you can redistribute it and/or modify it under the terms of the GNU *)
(* Lesser General Public License as published by the Free Software *)
(* Foundation, version 2.1. *)
(* *)
(* It 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 Lesser General Public License for more details. *)
(* *)
(* See the GNU Lesser General Public License version 2.1 *)
(* for more details (enclosed in the file licenses/LGPLv2.1). *)
(* *)
(**************************************************************************)
open Package
module Js = Yojson.Basic
module Ju = Yojson.Basic.Util
(* -------------------------------------------------------------------------- *)
(* --- Data Encoding --- *)
(* -------------------------------------------------------------------------- *)
type json = Js.t
let pretty = Js.pretty_print ~std:false
module type S =
sig
type t
val jtype : jtype
val of_json : json -> t
val to_json : t -> json
end
exception InputError of string
let failure ?json msg =
let add_json msg =
let msg = match json with
| None -> msg
| Some json ->
Format.asprintf "@[%s:@ %s@]" msg (Js.pretty_to_string json)
in
raise(InputError(msg))
in
Pretty_utils.ksfprintf add_json msg
let failure_from_type_error msg json =
failure ~json "%s" msg
let package = Package.package ~name:"data" ~title:"Informations" ()
(* -------------------------------------------------------------------------- *)
(* --- Declared Type --- *)
(* -------------------------------------------------------------------------- *)
let derived ~package ~id jtype =
let module Md = Markdown in
begin
declare ~package ~name:(Derived.safe id).name
~descr:(Md.plain "Safe decoder for" @ Md.code id.name)
(D_safe(id,jtype)) ;
declare ~package ~name:(Derived.loose id).name
~descr:(Md.plain "Loose decoder for" @ Md.code id.name)
(D_loose(id,jtype)) ;
declare ~package ~name:(Derived.order id).name
~descr:(Md.plain "Natural order for" @ Md.code id.name)
(D_order(id,jtype)) ;
end
let declare ~package ~name ?descr jtype =
let id = declare_id ~package ~name ?descr (D_type jtype) in
derived ~package ~id jtype ; Jdata id
(* -------------------------------------------------------------------------- *)
(* --- Option --- *)
(* -------------------------------------------------------------------------- *)
module Joption(A : S) : S with type t = A.t option =
struct
type t = A.t option
let nullable = try ignore (A.of_json `Null) ; true with _ -> false
let jtype = Joption (if not nullable then A.jtype else Jtuple [A.jtype])
let to_json = function
| None -> `Null
| Some v -> if nullable then `List [A.to_json v] else A.to_json v
let of_json = function
| `Null -> None
| `List [js] when nullable -> Some (A.of_json js)
| js -> Some (A.of_json js)
end
(* -------------------------------------------------------------------------- *)
(* --- Tuples --- *)
(* -------------------------------------------------------------------------- *)
module Jpair(A : S)(B : S) : S with type t = A.t * B.t =
struct
type t = A.t * B.t
let jtype = Jtuple [A.jtype;B.jtype]
let to_json (x,y) = `List [ A.to_json x ; B.to_json y ]
let of_json = function
| `List [ ja ; jb ] -> A.of_json ja , B.of_json jb
| js -> failure ~json:js "Expected list with 2 elements"
end
module Jtriple(A : S)(B : S)(C : S) : S with type t = A.t * B.t * C.t =
struct
type t = A.t * B.t * C.t
let jtype = Jtuple [A.jtype;B.jtype;C.jtype]
let to_json (x,y,z) = `List [ A.to_json x ; B.to_json y ; C.to_json z ]
let of_json = function
| `List [ ja ; jb ; jc ] -> A.of_json ja , B.of_json jb , C.of_json jc
| js -> failure ~json:js "Expected list with 3 elements"
end
(* -------------------------------------------------------------------------- *)
(* --- Lists --- *)
(* -------------------------------------------------------------------------- *)
module Jlist(A : S) : S with type t = A.t list =
struct
type t = A.t list
let jtype = Jlist A.jtype
let to_json xs = `List (List.map A.to_json xs)
let of_json js = List.map A.of_json (Ju.to_list js)
end
module Jalist(A : S) : S with type t = A.t list =
struct
type t = A.t list
let jtype = Jarray A.jtype
let to_json xs = `List (List.map A.to_json xs)
let of_json js = List.map A.of_json (Ju.to_list js)
end
(* -------------------------------------------------------------------------- *)
(* --- Arrays --- *)
(* -------------------------------------------------------------------------- *)
module Jarray(A : S) : S with type t = A.t array =
struct
type t = A.t array
let jtype = Jarray A.jtype
let to_json xs = `List (List.map A.to_json (Array.to_list xs))
let of_json js = Array.of_list @@ List.map A.of_json (Ju.to_list js)
end
(* -------------------------------------------------------------------------- *)
(* --- Atomic Types --- *)
(* -------------------------------------------------------------------------- *)
module Junit : S with type t = unit =
struct
type t = unit
let jtype = Jnull
let of_json _js = ()
let to_json () = `Null
end
module Jany : S with type t = json =
struct
type t = json
let jtype = Jany
let of_json js = js
let to_json js = js
end
module Jbool : S with type t = bool =
struct
type t = bool
let jtype = Jboolean
let of_json = Ju.to_bool
let to_json b = `Bool b
end
module Jint : S with type t = int =
struct
type t = int
let jtype = Jnumber
let of_json = Ju.to_int
let to_json n = `Int n
end
module Jfloat : S with type t = float =
struct
type t = float
let jtype = Jnumber
let of_json = Ju.to_number
let to_json v = `Float v
end
module Jstring : S with type t = string =
struct
type t = string
let jtype = Jstring
let of_json = Ju.to_string
let to_json s = `String s
end
module Jalpha : S with type t = string =
struct
type t = string
let jtype = Jalpha
let of_json = Ju.to_string
let to_json s = `String s
end
(* -------------------------------------------------------------------------- *)
(* --- Text Datatypes --- *)
(* -------------------------------------------------------------------------- *)
module Jmarkdown : S with type t = Markdown.text =
struct
type t = Markdown.text
let jtype =
let descr = Markdown.plain "Markdown (inlined) text." in
declare ~package ~name:"markdown" ~descr Jstring
let of_json js = Markdown.plain (Ju.to_string js)
let to_json txt = `String (Pretty_utils.to_string Markdown.pp_text txt)
end
module Jtext =
struct
include Jany
let jtype =
let descr = Markdown.plain
"Rich text format uses `[tag; …text ]` to apply \
the tag `tag` to the enclosed text. \
Empty tag `\"\"` can also used to simply group text together." in
let jdef = Junion [ Jnull; Jstring; Jlist Jself ] in
declare ~package ~name:"text" ~descr jdef
end
(* -------------------------------------------------------------------------- *)
(* --- Functional API --- *)
(* -------------------------------------------------------------------------- *)
type 'a data = (module S with type t = 'a)
let junit : unit data = (module Junit)
let jany : json data = (module Jany)
let jbool : bool data = (module Jbool)
let jint : int data = (module Jint)
let jfloat : float data = (module Jfloat)
let jstring : string data = (module Jstring)
let jalpha : string data = (module Jalpha)
let jkey ~kind =
let module JkeyKind =
struct
include Jstring
let jtype = Jkey kind
end in
(module JkeyKind : S with type t = string)
let jindex ~kind =
let module JindexKind =
struct
include Jint
let jtype = Jindex kind
end in
(module JindexKind : S with type t = int)
let joption (type a) (d : a data) : a option data =
let module A = Joption(val d) in
(module A : S with type t = a option)
let jlist (type a) (d : a data) : a list data =
let module A = Jlist(val d) in
(module A : S with type t = a list)
let jalist (type a) (d : a data) : a list data =
let module A = Jalist(val d) in
(module A : S with type t = a list)
let jarray (type a) (d : a data) : a array data =
let module A = Jarray(val d) in
(module A : S with type t = a array)
(* -------------------------------------------------------------------------- *)
(* --- Records --- *)
(* -------------------------------------------------------------------------- *)
module Fmap = Map.Make(String)
module Record =
struct
type 'a record = json Fmap.t
type ('r,'a) field = {
member : 'r record -> bool ;
getter : 'r record -> 'a ;
setter : 'r record -> 'a -> 'r record ;
}
type 'a signature = {
mutable fields : fieldInfo list ;
mutable default : 'a record ;
mutable published : bool ;
}
module type S =
sig
type r
include S with type t = r record
val default : t
val has : (r,'a) field -> t -> bool
val get : (r,'a) field -> t -> 'a
val set : (r,'a) field -> 'a -> t -> t
end
let signature () = {
published = false ;
fields = [] ;
default = Fmap.empty ;
}
let not_published s =
if s.published then
raise (Invalid_argument "Server.Data.Record: already published")
let check_field_name s name =
begin
if List.exists (fun f -> f.Package.fd_name = name) s.fields then
(let msg = Printf.sprintf "Server.Data.Record: duplicate field %S" name
in raise (Invalid_argument msg));
if not (Str.string_match (Str.regexp "[a-zA-Z0-9 _-]+$") name 0) then
(let msg = Printf.sprintf
"Server.Data.Record: invalid characters for field %S" name in
raise (Invalid_argument msg));
end
let field (type a r) (s : r signature)
~name ~descr ?default (d : a data) : (r,a) field =
not_published s ;
check_field_name s name ;
let module D = (val d) in
begin match default with
| None -> ()
| Some v -> s.default <- Fmap.add name (D.to_json v) s.default
end ;
let field = Package.{
fd_name = name ;
fd_type = D.jtype ;
fd_descr = descr ;
} in
s.fields <- field :: s.fields ;
let member r = Fmap.mem name r in
let getter r = D.of_json (Fmap.find name r) in
let setter r v = Fmap.add name (D.to_json v) r in
{ member ; getter ; setter }
let option (type a r) (s : r signature)
~name ~descr (d : a data) : (r,a option) field =
not_published s ;
check_field_name s name ;
let module D = (val d) in
let field = Package.{
fd_name = name ;
fd_type = Joption D.jtype ;
fd_descr = descr ;
} in
s.fields <- field :: s.fields ;
let member r = Fmap.mem name r in
let getter r =
try Some (D.of_json (Fmap.find name r)) with Not_found -> None in
let setter r = function
| None -> Fmap.remove name r
| Some v -> Fmap.add name (D.to_json v) r in
{ member ; getter ; setter }
let publish (type r) ~package ~name ?(descr=[]) (s : r signature) =
not_published s ;
let module M =
struct
type nonrec r = r
type t = r record
let jtype =
let fields = List.rev s.fields in
let id = Package.declare_id ~package ~name ~descr (D_record fields) in
derived ~package ~id (Jrecord (List.map Package.field fields)) ;
Jdata id
let default = s.default
let has fd r = fd.member r
let get fd r = fd.getter r
let set fd v r = fd.setter r v
let of_json js =
List.fold_left
(fun r (fd,js) -> Fmap.add fd js r)
default (Ju.to_assoc js)
let to_json r : json =
`Assoc (Fmap.fold (fun fd js fds -> (fd,js) :: fds) r [])
end in
begin
s.default <- Fmap.empty ;
s.fields <- [] ;
s.published <- true ;
(module M : S with type r = r)
end
end
(* -------------------------------------------------------------------------- *)
(* --- Enums --- *)
(* -------------------------------------------------------------------------- *)
module Tag =
struct
type t = Package.tagInfo
let jtype = declare ~package ~name:"tag"
~descr:(Markdown.plain "Enum Tag Description")
(Jrecord [
"name",Jalpha ;
"label",Jmarkdown.jtype ;
"descr",Jmarkdown.jtype ;
])
let to_json tg = `Assoc Package.[
"name", `String tg.tg_name ;
"label", Jmarkdown.to_json tg.tg_label ;
"descr" , Jmarkdown.to_json tg.tg_descr ;
]
let of_json js = Package.{
tg_name = Ju.member "name" js |> Ju.to_string ;
tg_label = Ju.member "label" js |> Jmarkdown.of_json ;
tg_descr = Ju.member "descr" js |> Jmarkdown.of_json ;
}
end
module Enum =
struct
type 'a dictionary = {
values : (string,'a option) Hashtbl.t ;
vindex : ('a,string) Hashtbl.t ;
mutable syntax : Markdown.text ;
mutable published : (package * string) option ;
mutable tags : tagInfo list ;
mutable prefix : tagInfo list ;
mutable lookup : ('a -> string) option ;
}
type 'a tag = string
type 'a prefix = 'a dictionary * string
let tag_name tg = tg
let tag_label a = function
| None -> Markdown.plain (String.(capitalize_ascii (lowercase_ascii a)))
| Some lbl -> lbl
let dictionary () = {
published = None ;
values = Hashtbl.create 0 ;
vindex = Hashtbl.create 0 ;
syntax = [] ;
prefix = [] ;
tags = [] ;
lookup = None ;
}
let tag ~name ?label ~descr ?value (d : 'a dictionary) : 'a tag =
if Hashtbl.mem d.values name then
( let msg = Printf.sprintf "Server.Data.Enum: duplicate tag %S" name in
raise (Invalid_argument msg) );
let tg = Package.{
tg_name = name ;
tg_label = tag_label name label ;
tg_descr = descr ;
} in
d.tags <- tg :: d.tags ;
Hashtbl.add d.values name value ;
begin match value with
| None -> ()
| Some v -> Hashtbl.add d.vindex v name
end ; name
let add ~name ?label ~descr ?value (d : 'a dictionary) : unit =
ignore (tag ~name ?label ~descr ?value d)
let find (d : 'a dictionary) (tg : 'a tag) : 'a =
match Hashtbl.find d.values tg with
| Some v -> v
| None -> raise Not_found
let find_tag (d : 'a dictionary) name : 'a tag =
if Hashtbl.mem d.values name then name else
raise Not_found
let lookup_index lookup vindex v =
match lookup with
| None -> Hashtbl.find vindex v
| Some f -> try f v with Not_found -> Hashtbl.find vindex v
let lookup (d : 'a dictionary) (v: 'a) : 'a tag =
lookup_index d.lookup d.vindex v
let set_lookup (d : 'a dictionary) (tag : 'a -> 'a tag) =
d.lookup <- Some tag
let instance_name = Printf.sprintf "%s_%s"
let instance (_,prefix) = instance_name prefix
let prefix ~name ?(var="*") ?label ~descr (d : 'a dictionary) =
let tg = Package.{
tg_name = instance_name name var ;
tg_label = tag_label (name ^ ".") label ;
tg_descr = descr ;
} in
d.prefix <- tg :: d.prefix ; d , name
let extends ~name ?label ~descr ?value ((d,prefix) : 'a prefix) : 'a tag =
let name = tag ~name:(instance_name prefix name) ?label ~descr ?value d in
( match d.published with
| None -> ()
| Some (package,name) ->
Package.update ~package ~name (D_enum (List.rev d.tags))
) ; name
let to_json name lookup vindex v =
`String begin
try lookup_index lookup vindex v
with Not_found ->
failure "[%s] Value not found" name
end
let of_json name values js =
let tag = Ju.to_string js in
match Hashtbl.find values tag with
| Some v -> v
| None ->
failure "[%s] No registered value for tag '%s" name tag
| exception Not_found ->
failure "[%s] Not registered tag '%s" name tag
let tags d = List.rev d.tags
let publish (type a) ~package ~name ~descr (d : a dictionary) =
( match d.published with
| None -> ()
| Some _ ->
let msg = "Server.Data.Enums: already published" in
raise (Invalid_argument msg) );
let module M =
struct
type t = a
let jtype =
let enums = D_enum (List.rev d.tags) in
let id = Package.declare_id ~package ~name ~descr enums in
derived ~package ~id (Jenum id) ; Jdata id
let of_json = of_json name d.values
let to_json = to_json name d.lookup d.vindex
end in
begin
d.published <- Some (package,name) ;
(module M : S with type t = a)
end
end
(* -------------------------------------------------------------------------- *)
(* --- Index --- *)
(* -------------------------------------------------------------------------- *)
module type Info =
sig
val name: string
end
(** Simplified [Map.S] *)
module type Map =
sig
type 'a t
type key
val empty : 'a t
val add : key -> 'a -> 'a t -> 'a t
val find : key -> 'a t -> 'a
end
module type Index =
sig
include S
val get : t -> int
val find : int -> t
val clear : unit -> unit
end
module INDEXER(M : Map)(I : Info) :
sig
type index
val create : unit -> index
val clear : index -> unit
val get : index -> M.key -> int
val find : index -> int -> M.key
val to_json : index -> M.key -> json
val of_json : index -> json -> M.key
end =
struct
type index = {
mutable kid : int ;
mutable index : int M.t ;
lookup : (int,M.key) Hashtbl.t ;
}
let create () = {
kid = 0 ;
index = M.empty ;
lookup = Hashtbl.create 0 ;
}
let clear m =
begin
m.kid <- 0 ;
m.index <- M.empty ;
Hashtbl.clear m.lookup ;
end
let get m a =
try M.find a m.index
with Not_found ->
let id = m.kid in
m.kid <- succ id ;
m.index <- M.add a id m.index ;
Hashtbl.add m.lookup id a ; id
let find m id = Hashtbl.find m.lookup id
let to_json m a = `Int (get m a)
let of_json m js =
let id = Ju.to_int js in
try find m id
with Not_found ->
failure "[%s] No registered id #%d" I.name id
end
module Static(M : Map)(I : Info)
: Index with type t = M.key =
struct
module INDEX = INDEXER(M)(I)
let index = INDEX.create ()
let clear () = INDEX.clear index
let get = INDEX.get index
let find = INDEX.find index
include
(struct
type t = M.key
let jtype = Jindex I.name
let of_json = INDEX.of_json index
let to_json = INDEX.to_json index
end)
end
module Index(M : Map)(I : Info)
: Index with type t = M.key =
struct
module INDEX = INDEXER(M)(I)
module TYPE : Datatype.S with type t = INDEX.index =
Datatype.Make
(struct
type t = INDEX.index
include Datatype.Undefined
let reprs = [INDEX.create()]
let name = "Server.Data.Index.Type." ^ I.name
let mem_project = Datatype.never_any_project
end)
module STATE = State_builder.Ref(TYPE)
(struct
let name = "Server.Data.Index.State." ^ I.name
let dependencies = []
let default = INDEX.create
end)
let index () = STATE.get ()
let clear () = INDEX.clear (index())
let get a = INDEX.get (index()) a
let find id = INDEX.find (index()) id
include
(struct
type t = M.key
let jtype = Jindex I.name
let of_json js = INDEX.of_json (index()) js
let to_json v = INDEX.to_json (index()) v
end)
end
module type IdentifiedType =
sig
type t
val id : t -> int
end
module Identified(A : IdentifiedType)(I : Info) : Index with type t = A.t =
struct
type index = (int,A.t) Hashtbl.t
module TYPE : Datatype.S with type t = index =
Datatype.Make
(struct
type t = index
include Datatype.Undefined
let reprs = [Hashtbl.create 0]
let name = "Server.Data.Identified.Type." ^ I.name
let mem_project = Datatype.never_any_project
end)
module STATE = State_builder.Ref(TYPE)
(struct
let name = "Server.Data.Identified.State." ^ I.name
let dependencies = []
let default () = Hashtbl.create 0
end)
let lookup () = STATE.get ()
let clear () = Hashtbl.clear (lookup())
let get = A.id
let find id = Hashtbl.find (lookup()) id
include
(struct
type t = A.t
let jtype = Jindex I.name
let to_json a = `Int (get a)
let of_json js =
let k = Ju.to_int js in
try find k
with Not_found -> failure "[%s] No registered id #%d" I.name k
end)
end
(* -------------------------------------------------------------------------- *)