package MlFront_Thunk

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file ThunkBundle.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
(* DEV NOTE: The [2] in [asset_file2], etc. is the DkAssets specification version. *)

type t = {
  bundle_id : ThunkLexers.Ranges.range_plus option * string;
      (** Until the deprecated version JSON object is removed (ie. thunk.json),
          the location is only provided for a ["MODULE@VERSION"] JSON string. *)
  listing : asset_listing;
  files : (compare_free_rangeplus * asset_file2) list;
}

and asset_listing = { origins : asset_origin2 list }

and asset_origin2 = {
  origin_name : string;
  origin_mirrors : string * string list;
}

and asset_file2 = {
  file_path : string;
  file_checksum : asset_checksum;
  file_origin : string option;
  file_sz : ThunkLexers.Ranges.range_plus * int64;
}

and asset_checksum = {
  checksum_sha256 : (ThunkLexers.Ranges.range_plus * string) option;
  checksum_sha1 : (ThunkLexers.Ranges.range_plus * string) option;
}

and compare_free_rangeplus =
  (ThunkLexers.Ranges.range_plus[@compare fun _a _b -> 0])

module Parsers = struct
  open ThunkParsers.JsonParsers

  (** Logical representation of a CST *)
  module Bundle = struct
    type nonrec t = t
  end

  module Parser = struct
    (** Extend the JSON parser to also parse the contents of the JSON. *)
    module MakeCExtended
        (CJson' :
          ThunkParsers.JsonParsers.Parser.CJSON with type attr = AttrForFmlib.t) =
    struct
      open CJson'

      let cast_asset_checksum j =
        let* checksum_sha256 = getmember_opt "sha256" j in
        let* checksum_sha1 = getmember_opt "sha1" j in
        let* checksum_sha256 = if_some checksum_sha256 pair_attr in
        let* checksum_sha1 = if_some checksum_sha1 pair_attr in
        let* checksum_sha256 = if_some_snd checksum_sha256 cast_string in
        let* checksum_sha1 = if_some_snd checksum_sha1 cast_string in
        return { checksum_sha256; checksum_sha1 }

      let cast_asset_file2 j =
        let* file_path = getmember "path" j in
        let* file_checksum = getmember "checksum" j in
        let* file_origin = getmember_opt "origin" j in
        let* file_sz = getmember "size" j in
        let* file_path = cast_string file_path in
        let* file_checksum = cast_asset_checksum file_checksum in
        let* file_origin = if_some file_origin cast_string in
        let* file_sz =
          let* loc, _ = pair_attr file_sz in
          let* x = cast_int64 file_sz in
          return (loc, x)
        in

        return { file_origin; file_path; file_checksum; file_sz }

      let cast_asset_origin2 j =
        let* origin_name = getmember "name" j in
        let* mirrors_range, origin_mirrors = getmemberwithjson "mirrors" j in
        let* origin_name = cast_string origin_name in
        let* origin_mirrors = cast_array origin_mirrors in
        let* origin_mirrors = for_all origin_mirrors cast_string in
        let mirrors_range = ThunkLexers.Ranges.display_range mirrors_range in
        match origin_mirrors with
        | [] ->
            fail
              (ThunkParsers.Results.Semantic.create mirrors_range
                 (Printf.sprintf "No mirrors for origin %s" origin_name))
        | first :: rest ->
            return { origin_name; origin_mirrors = (first, rest) }

      let cast_asset_listing j =
        let* origins = getmember "origins" j in
        let* origins = cast_array origins in
        let* origins = for_all origins cast_asset_origin2 in
        return { origins }

      let cast_listing_unencrypted j =
        let* listing_name = getmember "name" j in
        let* listing_version = getmember "version" j in
        let* listing_name = cast_string listing_name in
        let* listing_version = cast_string listing_version in
        return (None, Printf.sprintf "%s@%s" listing_name listing_version)

      let cast_bundle j =
        let* state = get in
        let* listing_unencrypted = getmember_opt "listing_unencrypted" j in
        let* listing = getmember_opt "listing" j in
        let* files = getmember "assets" j in
        let* bundle_id =
          match
            ( listing_unencrypted,
              ThunkParsers.Results.State.allow_deprecated_toplevel_moduleid
                state )
          with
          | Some listing_unencrypted, true ->
              cast_listing_unencrypted listing_unencrypted
          | _ ->
              let* id = getmember "id" j in
              let* idrange, _ = pair_attr id in
              let* id = cast_string id in
              return (Some idrange, id)
        in
        let* listing = if_some listing cast_asset_listing in
        let* files = cast_array files in
        let* files =
          for_all files (fun json ->
              let* filerange, _ = pair_attr json in
              let* file = cast_asset_file2 json in
              return (filerange, file))
        in
        return
          {
            bundle_id;
            listing = Option.value ~default:{ origins = [] } listing;
            files;
          }
    end

    module CJson = Parser.MakeCJson (Bundle) (AttrForFmlib) (JsonForFmlib)

    include CJson.Parser
    (** Include parsing functions to get the final value, etc. *)

    module CExtended = struct
      open CJson
      include MakeCExtended (CJson)

      let bundle () : Bundle.t t =
        let* j = json () in
        cast_bundle j
    end

    let parse sourcestate : t = CJson.make sourcestate (CExtended.bundle ())
  end

  (** The complete parser *)
  module PL = struct
    open Fmlib_parse
    open ThunkLexers.JsonLexer
    open ThunkParsers

    include
      Parse_with_lexer.Make_utf8 (Results.State) (Token) (Bundle)
        (Results.Semantic)
        (Lexer)
        (Parser)

    let start sourcestate rangeplus : t =
      make
        (Lexer.start_at
           ?pos:(Option.map ThunkLexers.Ranges.start_of_range_plus rangeplus)
           ())
        (Parser.parse sourcestate)
  end
end

let asset_file2_id_yojson ~canonical
    {
      file_origin;
      file_path : string;
      file_checksum = { checksum_sha256; checksum_sha1 };
      file_sz;
    } : YojsonI.Safe.t =
  let checksum =
    match (checksum_sha256, checksum_sha1) with
    | Some (_range256, sha256), Some (_range1, sha1) ->
        `Assoc [ ("sha256", `String sha256); ("sha1", `String sha1) ]
    | Some (_range256, sha256), None -> `Assoc [ ("sha256", `String sha256) ]
    | None, Some (_range1, sha1) -> `Assoc [ ("sha1", `String sha1) ]
    | None, None -> `Assoc []
  in
  let origin =
    if canonical then []
    else
      match file_origin with
      | Some origin -> [ ("origin", `String origin) ]
      | None -> []
  in
  `Assoc
    ((("checksum", checksum) :: origin)
    @ [
        ("path", `String file_path);
        ("size", `Intlit (Int64.to_string (snd file_sz)));
      ])

let origins_yojson ~canonical { origin_name; origin_mirrors } =
  (* this is not part of canonical JSON.
     bonus: fields can be ordered naturally *)
  assert (not canonical);
  let mirrors =
    `List
      (let first, rest = origin_mirrors in
       `String first :: List.map (fun url -> `String url) rest)
  in
  `Assoc [ ("name", `String origin_name); ("mirrors", mirrors) ]

let yojson ~canonical =
  let compare_file
      { file_origin = _; file_path = p1; file_checksum = _; file_sz = _ }
      { file_origin = _; file_path = p2; file_checksum = _; file_sz = _ } =
    String.compare p1 p2
  in
  function
  | { bundle_id = _range, bundle_id; listing = { origins }; files } ->
      let listing =
        if canonical then []
        else
          [
            ( "listing",
              `Assoc
                [
                  ( "origins",
                    `List (List.map (origins_yojson ~canonical) origins) );
                ] );
          ]
      in
      `Assoc
        ([
           ( "assets",
             `List
               (List.map
                  (asset_file2_id_yojson ~canonical)
                  (List.sort compare_file (List.map snd files))) );
           ("id", `String bundle_id);
         ]
        @ listing)

let pp ppf t =
  let json = yojson ~canonical:false t in
  YojsonI.Safe.pretty_print ~std:true ppf json

let canonicalize_asset_file file =
  YojsonI.Safe.to_string ~std:true (asset_file2_id_yojson ~canonical:true file)

let canonical_asset_file_id file =
  Digestif.SHA256.to_hex
  @@ Digestif.SHA256.digest_string (canonicalize_asset_file file)

let canonicalize bundle =
  YojsonI.Safe.to_string ~std:true (yojson ~canonical:true bundle)

let canonical_id bundle =
  Digestif.SHA256.to_hex @@ Digestif.SHA256.digest_string (canonicalize bundle)

let compare a1 a2 = String.compare (canonicalize a1) (canonicalize a2)

module InternalUse = struct
  let parse_string
      (module ResultObserver : ThunkParsers.Results.OBSERVER_RESULT) rangeplus s
      : (t, _) result =
    let sourcestate = ThunkParsers.Results.State.create_with_source s in
    let parser = Parsers.PL.start sourcestate rangeplus in
    let source, parser =
      let module Subparse = ThunkParsers.MakeSubparse (Parsers.PL) in
      Subparse.parse sourcestate rangeplus parser s
    in
    let module H = ResultObserver.Make (Parsers.PL) in
    H.observe ~cant_do:"parse the bundle" ~source sourcestate parser

  module CAssetExtensions
      (CJson :
        ThunkParsers.JsonParsers.Parser.CJSON
          with type attr = ThunkLexers.Ranges.range_plus) =
  struct
    module CExtended = Parsers.Parser.MakeCExtended (CJson)

    let cast_bundle : CJson.json -> t CJson.t = CExtended.cast_bundle
  end
end