From 33fc04950eaf753e3d864a9c42111e11fb1fff7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Wed, 24 Jun 2020 16:46:45 +0200
Subject: [PATCH] [ivette] natural orders

---
 ivette/api/dive.ts                           |  7 ++
 ivette/api/kernel/ast.ts                     |  5 ++
 ivette/api/kernel/data.ts                    | 10 +++
 ivette/api/kernel/project.ts                 | 14 ++++
 ivette/api/kernel/properties.ts              |  6 ++
 ivette/api/kernel/services.ts                | 20 ++++-
 ivette/api/server_tsc.ml                     | 84 +++++++++++++++++---
 ivette/src/dome/src/renderer/data/compare.ts | 18 ++++-
 src/plugins/dive/server_interface.ml         |  4 +-
 src/plugins/server/data.ml                   |  3 +-
 src/plugins/server/data.mli                  |  1 +
 src/plugins/server/package.ml                |  2 +-
 12 files changed, 155 insertions(+), 19 deletions(-)

diff --git a/ivette/api/dive.ts b/ivette/api/dive.ts
index de058d27793..cb3badb52ff 100644
--- a/ivette/api/dive.ts
+++ b/ivette/api/dive.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 
 /** The name of variable of the program */
@@ -30,6 +32,11 @@ export const jVariableName: Json.Loose<variableName> =
   Json.jTry(jVariableNameSafe);
 
 /** Natural order for `variableName` */
+export const byVariableName: Compare.Order<variableName> =
+  Compare.byFields({
+    funName: Compare.defined(Compare.alpha),
+    varName: Compare.alpha,
+  });
 
 /** Retrieve the whole graph */
 export const graph: Server.GetRequest<null,Json.json> = {
diff --git a/ivette/api/kernel/ast.ts b/ivette/api/kernel/ast.ts
index b6016940b3d..18f6889d903 100644
--- a/ivette/api/kernel/ast.ts
+++ b/ivette/api/kernel/ast.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 import { byTag } from 'api/kernel/data';
 import { byText } from 'api/kernel/data';
@@ -56,6 +58,8 @@ export const jMarkerKindSafe: Json.Safe<markerKind> =
 export const jMarkerKind: Json.Loose<markerKind> = Json.jEnum(markerKind);
 
 /** Natural order for `markerKind` */
+export const byMarkerKind: Compare.Order<markerKind> =
+  Compare.byEnym(markerKind);
 
 /** Registered tags for the above type. */
 export const markerKindTags: Server.GetRequest<null,tag[]> = {
@@ -138,6 +142,7 @@ export const jMarker: Json.Loose<marker> =
   );
 
 /** Natural order for `marker` */
+export const byMarker: Compare.Order<marker> = Compare.structural;
 
 /** Collect all functions in the AST */
 export const getFunctions: Server.GetRequest<null,Json.Key<'#fct'>[]> = {
diff --git a/ivette/api/kernel/data.ts b/ivette/api/kernel/data.ts
index 9ea5ff79642..151f9226057 100644
--- a/ivette/api/kernel/data.ts
+++ b/ivette/api/kernel/data.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 
 /** Markdown (inlined) text. */
@@ -21,6 +23,7 @@ export const jMarkdownSafe: Json.Safe<markdown> =
 export const jMarkdown: Json.Loose<markdown> = Json.jString;
 
 /** Natural order for `markdown` */
+export const byMarkdown: Compare.Order<markdown> = Compare.primitive;
 
 /** 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. */
 export type text = null | string | text[];
@@ -37,6 +40,7 @@ export const jText: Json.Loose<text> =
   );
 
 /** Natural order for `text` */
+export const byText: Compare.Order<text> = Compare.structural;
 
 /** Enum Tag Description */
 export type tag = { name: string, label: markdown, descr: markdown };
@@ -53,5 +57,11 @@ export const jTagSafe: Json.Safe<tag> =
 export const jTag: Json.Loose<tag> = Json.jTry(jTagSafe);
 
 /** Natural order for `tag` */
+export const byTag: Compare.Order<tag> =
+  Compare.byFields({
+    name: Compare.primitive,
+    label: byMarkdown,
+    descr: byMarkdown,
+  });
 
 /* ------------------------------------- */
diff --git a/ivette/api/kernel/project.ts b/ivette/api/kernel/project.ts
index 5335e9e6cc8..9af041819f0 100644
--- a/ivette/api/kernel/project.ts
+++ b/ivette/api/kernel/project.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 
 /** Project informations */
@@ -27,6 +29,12 @@ export const jProjectInfo: Json.Loose<projectInfo> =
   Json.jTry(jProjectInfoSafe);
 
 /** Natural order for `projectInfo` */
+export const byProjectInfo: Compare.Order<projectInfo> =
+  Compare.byFields({
+    id: Compare.primitive,
+    name: Compare.primitive,
+    current: Compare.primitive,
+  });
 
 /** Request to be executed on the specified project. */
 export type projectRequest =
@@ -45,6 +53,12 @@ export const jProjectRequest: Json.Loose<projectRequest> =
   Json.jTry(jProjectRequestSafe);
 
 /** Natural order for `projectRequest` */
+export const byProjectRequest: Compare.Order<projectRequest> =
+  Compare.byFields({
+    project: Compare.primitive,
+    request: Compare.primitive,
+    data: Compare.structural,
+  });
 
 /** Returns the current project */
 export const getCurrent: Server.GetRequest<null,projectInfo> = {
diff --git a/ivette/api/kernel/properties.ts b/ivette/api/kernel/properties.ts
index 696f28775e0..a17f096b215 100644
--- a/ivette/api/kernel/properties.ts
+++ b/ivette/api/kernel/properties.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 import { byTag } from 'api/kernel/data';
 import { jTag } from 'api/kernel/data';
@@ -98,6 +100,7 @@ export const jPropKindSafe: Json.Safe<propKind> =
 export const jPropKind: Json.Loose<propKind> = Json.jEnum(propKind);
 
 /** Natural order for `propKind` */
+export const byPropKind: Compare.Order<propKind> = Compare.byEnym(propKind);
 
 /** Registered tags for the above type. */
 export const propKindTags: Server.GetRequest<null,tag[]> = {
@@ -141,6 +144,8 @@ export const jPropStatusSafe: Json.Safe<propStatus> =
 export const jPropStatus: Json.Loose<propStatus> = Json.jEnum(propStatus);
 
 /** Natural order for `propStatus` */
+export const byPropStatus: Compare.Order<propStatus> =
+  Compare.byEnym(propStatus);
 
 /** Registered tags for the above type. */
 export const propStatusTags: Server.GetRequest<null,tag[]> = {
@@ -198,6 +203,7 @@ export const jAlarmsSafe: Json.Safe<alarms> =
 export const jAlarms: Json.Loose<alarms> = Json.jEnum(alarms);
 
 /** Natural order for `alarms` */
+export const byAlarms: Compare.Order<alarms> = Compare.byEnym(alarms);
 
 /** Registered tags for the above type. */
 export const alarmsTags: Server.GetRequest<null,tag[]> = {
diff --git a/ivette/api/kernel/services.ts b/ivette/api/kernel/services.ts
index 56348fad9fc..ed4ed06e0f9 100644
--- a/ivette/api/kernel/services.ts
+++ b/ivette/api/kernel/services.ts
@@ -7,7 +7,9 @@
 */
 
 import * as Json from 'dome/data/json';
+import * as Compare from 'dome/data/compare';
 import * as Server from 'frama-c/server';
+import * as State from 'frama-c/states';
 
 import { byTag } from 'api/kernel/data';
 import { jTag } from 'api/kernel/data';
@@ -55,6 +57,13 @@ export const jSourceSafe: Json.Safe<source> =
 export const jSource: Json.Loose<source> = Json.jTry(jSourceSafe);
 
 /** Natural order for `source` */
+export const bySource: Compare.Order<source> =
+  Compare.byFields({
+    dir: Compare.primitive,
+    base: Compare.primitive,
+    file: Compare.primitive,
+    line: Compare.primitive,
+  });
 
 /** Log messages categories. */
 export enum logkind {
@@ -80,6 +89,7 @@ export const jLogkindSafe: Json.Safe<logkind> =
 export const jLogkind: Json.Loose<logkind> = Json.jEnum(logkind);
 
 /** Natural order for `logkind` */
+export const byLogkind: Compare.Order<logkind> = Compare.byEnym(logkind);
 
 /** Registered tags for the above type. */
 export const logkindTags: Server.GetRequest<null,tag[]> = {
@@ -106,7 +116,7 @@ export interface log {
 /** Safe decoder for `log` */
 export const jLogSafe: Json.Safe<log> =
   Json.jObject({
-    kind: Json.jFail(Json.jEnum(logkind),'kernel.services.logkind expected'),
+    kind: jLogkindSafe,
     plugin: Json.jFail(Json.jString,'String expected'),
     message: Json.jFail(Json.jString,'String expected'),
     category: Json.jString,
@@ -117,6 +127,14 @@ export const jLogSafe: Json.Safe<log> =
 export const jLog: Json.Loose<log> = Json.jTry(jLogSafe);
 
 /** Natural order for `log` */
+export const byLog: Compare.Order<log> =
+  Compare.byFields({
+    kind: byLogkind,
+    plugin: Compare.primitive,
+    message: Compare.primitive,
+    category: Compare.defined(Compare.primitive),
+    source: Compare.defined(bySource),
+  });
 
 /** Turn logs monitoring on/off */
 export const setLogs: Server.SetRequest<boolean,null> = {
diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml
index 378dfc49d94..48c3b15877f 100644
--- a/ivette/api/server_tsc.ml
+++ b/ivette/api/server_tsc.ml
@@ -39,7 +39,7 @@ let keywords = [
   "let"; "package"; "private"; "protected"; "public"; "static"; "yield"; "any";
   "boolean"; "constructor"; "declare"; "get"; "module"; "require"; "number";
   "set"; "string"; "symbol"; "type"; "from"; "of";
-  "Json";
+  "Json"; "Compare"; "Server"; "State";
 ]
 
 let pp_descr = Md.pp_text ?page:None
@@ -205,6 +205,49 @@ let typeOfParam = function
     let field fd = fd.Pkg.fd_name , fd.Pkg.fd_type in
     Jrecord (List.map field fjs)
 
+(* -------------------------------------------------------------------------- *)
+(* --- Jtype Order                                                        --- *)
+(* -------------------------------------------------------------------------- *)
+
+let makeOrder ~self ~names fmt js =
+  let open Pkg in
+  let rec pp fmt = function
+    | Jnull -> Format.pp_print_string fmt "Compare.equal"
+    | Jalpha -> Format.pp_print_string fmt "Compare.alpha"
+    | Jnumber | Jstring | Jboolean | Jkey _ | Jindex _
+      -> Format.pp_print_string fmt "Compare.primitive"
+    | Jself -> jcall names fmt (Pkg.Derived.order self)
+    | Jdata id -> jcall names fmt (Pkg.Derived.order id)
+    | Joption js ->
+      Format.fprintf fmt "@[<hov 2>Compare.defined(@,%a)@]" pp js
+    | Jany | Junion _ -> (* Can not find a better solution *)
+      Format.fprintf fmt "Compare.structural"
+    | Jenum id ->
+      Format.fprintf fmt "@[<hov 2>Compare.byEnym(@,%a)@]" (jcall names) id
+    | Jlist js | Jarray js ->
+      Format.fprintf fmt "@[<hov 2>Compare.array(@,%a)@]" pp js
+    | Jtuple jts ->
+      let name = match List.length jts with
+        | 2 -> "pair"
+        | 3 -> "triple"
+        | 4 -> "tuple4"
+        | 5 -> "tuple5"
+        | n -> Self.abort "No comparison for %d-tuples" n in
+      Format.fprintf fmt "@[<hv 0>@[<hv 2>Compare.%s(" name ;
+      List.iter (fun js -> Format.fprintf fmt "@,%a," pp js) jts ;
+      Format.fprintf fmt "@]@,)@]" ;
+    | Jrecord jfs ->
+      Format.fprintf fmt "@[<hv 0>@[<hv 2>Compare.byFields({" ;
+      List.iter
+        (fun (fd,js) -> Format.fprintf fmt "@ @[<hov 2>%s: %a,@]" fd pp js) jfs ;
+      Format.fprintf fmt "@]@ })@]" ;
+    | Jdict(kd,js) ->
+      let jtype fmt js = makeJtype ~names fmt js in
+      Format.fprintf fmt
+        "@[<hov 2>Compare.dictionary<@,Json.dict<'#%s'@,%a>>(@,%a)@]"
+        kd jtype js pp js
+  in pp fmt js
+
 (* -------------------------------------------------------------------------- *)
 (* --- Declaration Generator                                              --- *)
 (* -------------------------------------------------------------------------- *)
@@ -216,8 +259,10 @@ let makeDeclaration fmt names d =
   let self = d.d_ident in
   let jtype = makeJtype ~self ~names in
   match d.d_kind with
+
   | D_type js ->
-    Format.fprintf fmt "@[<hv 2>export type %s =@ %a;@]@\n" self.name jtype js ;
+    Format.fprintf fmt "@[<hv 2>export type %s =@ %a;@]@\n" self.name jtype js
+
   | D_record fjs ->
     Format.fprintf fmt "export interface %s {@\n" self.name ;
     List.iter
@@ -229,7 +274,8 @@ let makeDeclaration fmt names d =
          | _ ->
            Format.fprintf fmt "  @[<hov 2>%s: %a;@]@\n" fd jtype js
       ) fjs ;
-    Format.fprintf fmt "}@\n" ;
+    Format.fprintf fmt "}@\n"
+
   | D_enum tgs ->
     Format.fprintf fmt "export enum %s {@\n" self.name ;
     List.iter
@@ -237,11 +283,13 @@ let makeDeclaration fmt names d =
          makeDescr ~indent:"  " fmt doc ;
          Format.fprintf fmt "  %s = '%s';@\n" tag tag ;
       ) tgs ;
-    Format.fprintf fmt "}@\n" ;
+    Format.fprintf fmt "}@\n"
+
   | D_signal ->
     Format.fprintf fmt "export const %s: Server.Signal = {@\n" self.name ;
     Format.fprintf fmt "  name: '%s',@\n" (Pkg.name_of_ident d.d_ident) ;
-    Format.fprintf fmt "};@\n" ;
+    Format.fprintf fmt "};@\n"
+
   | D_request rq ->
     let kind = name_of_kind rq.rq_kind in
     let prefix = String.capitalize_ascii (String.lowercase_ascii kind) in
@@ -255,7 +303,8 @@ let makeDeclaration fmt names d =
     Format.fprintf fmt "  name:   '%s',@\n" (Pkg.name_of_ident d.d_ident) ;
     Format.fprintf fmt "  input:  %a,@\n" makeParam input ;
     Format.fprintf fmt "  output: %a,@\n" makeParam output ;
-    Format.fprintf fmt "};@\n" ;
+    Format.fprintf fmt "};@\n"
+
   | D_value js ->
     Format.fprintf fmt
       "@[<hov 2>export const %s: State.Value<@,%a@,> = {@]@\n"
@@ -264,7 +313,8 @@ let makeDeclaration fmt names d =
       (jcall names) (Pkg.Derived.signal self) ;
     Format.fprintf fmt "  getter: %a,@\n"
       (jcall names) (Pkg.Derived.getter self) ;
-    Format.fprintf fmt "};@\n" ;
+    Format.fprintf fmt "};@\n"
+
   | D_state js ->
     Format.fprintf fmt
       "@[<hov 2>export const %s: State.State<@,%a@,> = {@]@\n"
@@ -275,7 +325,8 @@ let makeDeclaration fmt names d =
       (jcall names) (Pkg.Derived.getter self) ;
     Format.fprintf fmt "  setter: %a,@\n"
       (jcall names) (Pkg.Derived.setter self) ;
-    Format.fprintf fmt "};@\n" ;
+    Format.fprintf fmt "};@\n"
+
   | D_array kd ->
     let data = Pkg.Derived.data self in
     Format.fprintf fmt
@@ -287,18 +338,25 @@ let makeDeclaration fmt names d =
       (jcall names) (Pkg.Derived.fetch self) ;
     Format.fprintf fmt "  reload: %a,@\n"
       (jcall names) (Pkg.Derived.reload self) ;
-    Format.fprintf fmt "};@\n" ;
+    Format.fprintf fmt "};@\n"
+
   | D_safe(id,js) ->
     Format.fprintf fmt
       "@[<hov 2>export const %s: Json.Safe<@,%a@,> =@ %a;@]\n"
       self.name (jcall names) id
-      (makeRootDecoder ~safe:true ~self:id ~names) js ;
+      (makeRootDecoder ~safe:true ~self:id ~names) js
+
   | D_loose(id,js) ->
     Format.fprintf fmt
       "@[<hov 2>export const %s: Json.Loose<@,%a@,> =@ %a;@]\n"
       self.name (jcall names) id
-      (makeRootDecoder ~safe:false ~self:id ~names) js ;
-  | D_order _ -> ()
+      (makeRootDecoder ~safe:false ~self:id ~names) js
+
+  | D_order(id,js) ->
+    Format.fprintf fmt
+      "@[<hov 2>export const %s: Compare.Order<@,%a@,> =@ %a;@]\n"
+      self.name (jcall names) id
+      (makeOrder ~self:id ~names) js
 
 (* -------------------------------------------------------------------------- *)
 (* --- Package Generator                                                  --- *)
@@ -316,7 +374,9 @@ let makePackage pkg name fmt =
     Format.fprintf fmt "*/@\n@." ;
     let names = Pkg.resolve ~keywords pkg in
     Format.fprintf fmt "import * as Json from 'dome/data/json';@\n" ;
+    Format.fprintf fmt "import * as Compare from 'dome/data/compare';@\n" ;
     Format.fprintf fmt "import * as Server from 'frama-c/server';@\n" ;
+    Format.fprintf fmt "import * as State from 'frama-c/states';@\n" ;
     Format.pp_print_newline fmt () ;
     Pkg.IdMap.iter
       (fun id name ->
diff --git a/ivette/src/dome/src/renderer/data/compare.ts b/ivette/src/dome/src/renderer/data/compare.ts
index 5f643b4cc85..9419679dd6f 100644
--- a/ivette/src/dome/src/renderer/data/compare.ts
+++ b/ivette/src/dome/src/renderer/data/compare.ts
@@ -131,8 +131,24 @@ export function array<A>(order: Order<A>): Order<A[]> {
   };
 }
 
+/** Order by dictionary order.
+    Can be used directly with an enum type declaration.
+ */
+export function byEnum<A extends string>(d: { [key: string]: A }): Order<A> {
+  const ranks: { [index: string]: number } = {};
+  const values = Object.keys(d);
+  const wildcard = values.length;
+  values.forEach((C, k) => ranks[C] = k);
+  return (x: A, y: A) => {
+    if (x === y) return 0;
+    const rx = ranks[x] ?? wildcard;
+    const ry = ranks[y] ?? wildcard;
+    return rx - ry;
+  };
+}
+
 /** Order string enumeration constants.
-    `enums(v1,...,vN)` will order constant following the order of arguments.
+    `byRank(v1,...,vN)` will order constant following the order of arguments.
     Non-listed constants appear at the end, or at the rank specified by `'*'`. */
 export function byRank(...args: string[]): Order<string> {
   const ranks: { [index: string]: number } = {};
diff --git a/src/plugins/dive/server_interface.ml b/src/plugins/dive/server_interface.ml
index 51f5267c7a9..65179a2d791 100644
--- a/src/plugins/dive/server_interface.ml
+++ b/src/plugins/dive/server_interface.ml
@@ -59,12 +59,12 @@ struct
   let fun_field = Data.Record.option signature
       ~name:"funName"
       ~descr:(Markdown.plain "owner function for a local variable")
-      (module Data.Jstring)
+      (module Data.Jalpha)
 
   let var_field = Data.Record.field signature
       ~name:"varName"
       ~descr:(Markdown.plain "variable name")
-      (module Data.Jstring)
+      (module Data.Jalpha)
 
   type t = Cil_types.varinfo
 
diff --git a/src/plugins/server/data.ml b/src/plugins/server/data.ml
index 75833d9b461..e6ad81cbbf9 100644
--- a/src/plugins/server/data.ml
+++ b/src/plugins/server/data.ml
@@ -549,8 +549,7 @@ struct
       let jtype =
         let enums = D_enum (List.rev d.tags) in
         let id = Package.declare_id ~package ~name ~descr enums in
-        let js = Jenum id in
-        derived ~package ~id js ; js
+        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
diff --git a/src/plugins/server/data.mli b/src/plugins/server/data.mli
index 6134493c968..6ba578402a2 100644
--- a/src/plugins/server/data.mli
+++ b/src/plugins/server/data.mli
@@ -80,6 +80,7 @@ module Jbool : S with type t = bool
 module Jint : S with type t = int
 module Jfloat : S with type t = float
 module Jstring : S with type t = string
+module Jalpha : S with type t = string
 module Jtext : S with type t = json (** Rich text encoding, see [Jbuffer]. *)
 module Jmarkdown : S with type t = Markdown.text
 
diff --git a/src/plugins/server/package.ml b/src/plugins/server/package.ml
index 837861f6bb1..b0361dcbd90 100644
--- a/src/plugins/server/package.ml
+++ b/src/plugins/server/package.ml
@@ -176,7 +176,7 @@ type jtype =
   | Junion of jtype list
   | Jrecord of (string * jtype) list
   | Jdata of ident
-  | Jenum of ident (* data that is an enum *)
+  | Jenum of ident (* enum type declaration *)
   | Jself (* for (simply) recursive types *)
 
 (* -------------------------------------------------------------------------- *)
-- 
GitLab