(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) (* to deal in the Software without restriction, including without limitation *) (* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) (* and/or sell copies of the Software, and to permit persons to whom the *) (* Software is furnished to do so, subject to the following conditions: *) (* *) (* The above copyright notice and this permission notice shall be included *) (* in all copies or substantial portions of the Software. *) (* *) (* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) (* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) (* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) (* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) (* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) (* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) open Encoding type integer_extended = [ Binary_size.integer | `Int32 | `Int64 ] type field_descr = | Named_field of string * Kind.t * layout | Anonymous_field of Kind.t * layout | Dynamic_size_field of string option * int * Binary_size.unsigned_integer | Optional_field of string and layout = | Zero_width | Int of integer_extended | Bool | RangedInt of int * int | RangedFloat of float * float | Float | Bytes | String | Enum of Binary_size.integer * string | Seq of layout * int option (* For arrays and lists *) | Ref of string | Padding and fields = field_descr list and toplevel_encoding = | Obj of { fields : fields } | Cases of { kind : Kind.t ; tag_size : Binary_size.tag_size ; cases : (int * string option * fields) list } | Int_enum of { size : Binary_size.integer ; cases : (int * string) list } and description = { title : string ; description : string option } type t = { toplevel: toplevel_encoding ; fields: (description * toplevel_encoding) list ; } module Printer_ast = struct type table = { headers : string list ; body : string list list } type t = | Table of table | Union of Binary_size.tag_size * (description * table) list let pp_size ppf = function | `Fixed size -> Format.fprintf ppf "%d byte%s" size (if size = 1 then "" else "s") | `Variable -> Format.fprintf ppf "Variable" | `Dynamic -> Format.fprintf ppf "Determined from data" let pp_int ppf (int : integer_extended) = Format.fprintf ppf "%s" begin match int with | `Int16 -> "signed 16-bit integer" | `Int31 -> "signed 31-bit integer" | `Uint30 -> "unsigned 30-bit integer" | `Int32 -> "signed 32-bit integer" | `Int64 -> "signed 64-bit integer" | `Int8 -> "signed 8-bit integer" | `Uint16 -> "unsigned 16-bit integer" | `Uint8 -> "unsigned 8-bit integer" end let rec pp_layout ppf = function | Zero_width -> Format.fprintf ppf "placeholder (not actually present in the encoding)" | Int integer -> Format.fprintf ppf "%a" pp_int integer | Bool -> Format.fprintf ppf "boolean (0 for false, 255 for true)" | RangedInt (minimum, maximum) when minimum <= 0 -> Format.fprintf ppf "%a in the range %d to %d" pp_int ((Binary_size.range_to_size ~minimum ~maximum) :> integer_extended) minimum maximum | RangedInt (minimum, maximum) (* when minimum > 0 *) -> Format.fprintf ppf "%a in the range %d to %d (shifted by %d)" pp_int ((Binary_size.range_to_size ~minimum ~maximum) :> integer_extended) minimum maximum minimum | RangedFloat (minimum, maximum) -> Format.fprintf ppf "double-precision floating-point number, in the range %f to %f" minimum maximum | Float -> Format.fprintf ppf "double-precision floating-point number" | Bytes -> Format.fprintf ppf "bytes" | String -> Format.fprintf ppf "bytes" | Ref reference -> Format.fprintf ppf "$%s" reference | Padding -> Format.fprintf ppf "padding" | Enum (size, reference) -> Format.fprintf ppf "%a encoding an enumeration (see %s)" pp_int (size :> integer_extended) reference | Seq (data, len) -> Format.fprintf ppf "sequence of " ; begin match len with | None -> () | Some len -> Format.fprintf ppf "at most %d " len end ; begin match data with | Ref reference -> Format.fprintf ppf "$%s" reference | _ -> pp_layout ppf data end let pp_tag_size ppf tag = Format.fprintf ppf "%s" @@ match tag with | `Uint8 -> "8-bit" | `Uint16 -> "16-bit" let field_descr () = let reference = ref 0 in let string_of_layout = Format.asprintf "%a" pp_layout in let anon_num () = let value = !reference in reference := value + 1; string_of_int value in function | Named_field (name, kind, desc) -> [ name ; Format.asprintf "%a" pp_size kind ; string_of_layout desc ] | Dynamic_size_field (Some name, 1, size) -> [ Format.asprintf "# bytes in field \"%s\"" name ; Format.asprintf "%a" pp_size (`Fixed (Binary_size.integer_to_size size)) ; string_of_layout (Int (size :> integer_extended)) ] | Dynamic_size_field (None, 1, size) -> [ Format.asprintf "# bytes in next field" ; Format.asprintf "%a" pp_size (`Fixed (Binary_size.integer_to_size size)) ; string_of_layout (Int (size :> integer_extended)) ] | Dynamic_size_field (_, i, size) -> [ Format.asprintf "# bytes in next %d fields" i ; Format.asprintf "%a" pp_size (`Fixed (Binary_size.integer_to_size size)) ; string_of_layout (Int (size :> integer_extended)) ] | Anonymous_field (kind, desc) -> [ "Unnamed field " ^ anon_num () ; Format.asprintf "%a" pp_size kind ; string_of_layout desc ] | Optional_field name -> [ Format.asprintf "? presence of field \"%s\"" name ; Format.asprintf "%a" pp_size (`Fixed 1) ; string_of_layout Bool ] let binary_table_headers = [ "Name" ; "Size" ; "Contents" ] let enum_headers = [ "Case number" ; "Encoded string" ] let toplevel (descr, encoding) = match encoding with | Obj { fields } -> descr, Table { headers = binary_table_headers ; body = List.map (field_descr ()) fields } | Cases { kind ; tag_size ; cases } -> { title = Format.asprintf "%s (%a, %a tag)" descr.title pp_size kind pp_tag_size tag_size ; description = descr.description}, Union (tag_size, List.map (fun (tag, name, fields) -> { title = begin match name with | Some name -> Format.asprintf "%s (tag %d)" name tag | None -> Format.asprintf "Tag %d" tag end; description = None }, { headers = binary_table_headers ; body = List.map (field_descr ()) fields }) cases) | Int_enum { size ; cases } -> { title = Format.asprintf "%s (Enumeration: %a):" descr.title pp_int (size :> integer_extended) ; description = descr.description }, Table { headers = enum_headers ; body = List.map (fun (num, str) -> [ string_of_int num ; str ]) cases } end module Printer = struct let rec pad char ppf = function | 0 -> () | n -> Format.pp_print_char ppf char ; pad char ppf (n - 1) let pp_title level ppf title = let char = if level = 1 then '*' else if level = 2 then '=' else '`' in let sub = String.map (fun _ -> char) title in Format.fprintf ppf "%s@ %s@\n@\n" title sub let pp_table ppf { Printer_ast.headers ; body } = let max_widths = List.fold_left (List.map2 (fun len str -> max (String.length str) len)) (List.map String.length headers) body in let pp_row pad_char ppf = Format.fprintf ppf "|%a" (fun ppf -> List.iter2 (fun width str -> Format.fprintf ppf " %s%a |" str (pad pad_char) (width - (String.length str))) max_widths) in let pp_line c ppf = Format.fprintf ppf "+%a" (fun ppf -> List.iter2 (fun width _str -> Format.fprintf ppf "%a+" (pad c) (width + 2)) max_widths) in Format.fprintf ppf "%a@\n%a@\n%a@\n%a@\n@\n" (pp_line '-') headers (pp_row ' ') headers (pp_line '=') headers (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "@\n") (fun ppf s -> Format.fprintf ppf "%a@\n%a" (pp_row ' ') s (pp_line '-') s)) body let pp_option_nl ppf = Option.iter ~f:(Format.fprintf ppf "%s@\n@\n") let pp_toplevel ppf = function | Printer_ast.Table table -> pp_table ppf table | Union (_tag_size, tables) -> Format.fprintf ppf "%a" (fun ppf -> Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "@\n") (fun ppf (descr, table) -> Format.fprintf ppf "%a%a%a" (pp_title 2) descr.title pp_option_nl descr.description pp_table table) ppf) tables let pp ppf { toplevel; fields } = let _, toplevel = Printer_ast.toplevel ({ title = "" ; description = None}, toplevel) in Format.fprintf ppf "%a@\n%a" pp_toplevel toplevel (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "@\n") (fun ppf (descr, toplevel) -> Format.fprintf ppf "%a%a%a" (pp_title 1) descr.title pp_option_nl descr.description pp_toplevel toplevel)) (List.map Printer_ast.toplevel fields) end module Encoding = struct let description_encoding = conv (fun { title ; description } -> (title, description)) (fun (title, description) -> { title ; description }) (obj2 (req "title" string) (opt "description" string)) let integer_cases = [ ("Int16", `Int16) ; ("Int8", `Int8) ; ("Uint16", `Uint16) ; ("Uint8", `Uint8) ] let integer_encoding : Binary_size.integer encoding = string_enum integer_cases let integer_extended_encoding = string_enum (("Int64", `Int64) :: ("Int32", `Int32) :: integer_cases) let layout_encoding = mu "layout" (fun layout -> union [ case ~title:"Zero_width" (Tag 0) (obj1 (req "kind" (constant "Zero_width"))) (function | Zero_width -> Some () | _ -> None) (fun () -> Zero_width) ; case ~title:"Int" (Tag 1) (obj2 (req "size" integer_extended_encoding) (req "kind" (constant "Int"))) (function | Int integer -> Some (integer, ()) | _ -> None) (fun (integer, _)-> Int integer) ; case ~title:"Bool" (Tag 2) (obj1 (req "kind" (constant "Bool"))) (function | Bool -> Some () | _ -> None) (fun () -> Bool) ; case ~title:"RangedInt" (Tag 3) (obj3 (req "min" int31) (req "max" int31) (req "kind" (constant "RangedInt"))) (function | RangedInt (min, max) -> Some (min, max, ()) | _ -> None) (fun (min, max, _) -> RangedInt (min, max)) ; case ~title:"RangedFloat" (Tag 4) (obj3 (req "min" float) (req "max" float) (req "kind" (constant "RangedFloat"))) (function | RangedFloat (min, max) -> Some (min, max, ()) | _ -> None) (fun (min, max, ()) -> RangedFloat (min, max)) ; case ~title:"Float" (Tag 5) (obj1 (req "kind" (constant "Float"))) (function | Float -> Some () | _ -> None) (fun () -> Float) ; case ~title:"Bytes" (Tag 6) (obj1 (req "kind" (constant "Bytes"))) (function | Bytes -> Some () | _ -> None) (fun () -> Bytes) ; case ~title:"String" (Tag 7) (obj1 (req "kind" (constant "String"))) (function | String -> Some () | _ -> None) (fun () -> String) ; case ~title:"Enum" (Tag 8) (obj3 (req "size" integer_encoding) (req "reference" string) (req "kind" (constant "Enum"))) (function | Enum (size, cases) -> Some (size, cases, ()) | _ -> None) (fun (size, cases, _) -> Enum (size, cases)) ; case ~title:"Seq" (Tag 9) (obj3 (req "layout" layout) (req "kind" (constant "Seq")) (opt "max_length" int31)) (function | Seq (layout, len) -> Some (layout, (), len) | _ -> None) (fun (layout, (), len) -> Seq (layout, len)) ; case ~title:"Ref" (Tag 10) (obj2 (req "name" string) (req "kind" (constant "Ref"))) (function | Ref layout -> Some (layout, ()) | _ -> None) (fun (name, ()) -> Ref name) ; case ~title:"Padding" (Tag 11) (obj1 (req "kind" (constant "Padding"))) (function | Padding -> Some () | _ -> None) (fun () -> Padding) ; ]) let kind_enum_cases = (fun () -> [ case ~title:"Dynamic" (Tag 0) (obj1 (req "kind" (constant "Dynamic"))) (function `Dynamic -> Some () | _ -> None) (fun () -> `Dynamic) ; case ~title:"Variable" (Tag 1) (obj1 (req "kind" (constant "Variable"))) (function `Variable -> Some () | _ -> None) (fun () -> `Variable) ]) let kind_enum_encoding = def "schema.kind.enum" @@ union (kind_enum_cases ()) let kind_t_encoding = def "schema.kind" @@ union ((case ~title:"Fixed" (Tag 2) (obj2 (req "size" int31) (req "kind" (constant "Float"))) (function `Fixed n -> Some (n, ()) | _ -> None) (fun (n, _) -> `Fixed n)) :: (kind_enum_cases ())) let unsigned_integer_encoding = string_enum [("Uint30", `Uint30) ; ("Uint16", `Uint16) ; ("Uint8", `Uint8) ] let field_descr_encoding = let dynamic_layout_encoding = dynamic_size layout_encoding in def "schema.field" @@ union [ case ~title:"Named_field" (Tag 0) (obj4 (req "name" string) (req "layout" dynamic_layout_encoding) (req "data_kind" kind_t_encoding) (req "kind" (constant "named"))) (function Named_field (name, kind, layout) -> Some (name, layout, kind, ()) | _ -> None) (fun (name, kind, layout, _) -> Named_field (name, layout, kind)) ; case ~title:"Anonymous_field" (Tag 1) (obj3 (req "layout" dynamic_layout_encoding) (req "kind" (constant "anon")) (req "data_kind" kind_t_encoding)) (function Anonymous_field (kind, layout) -> Some (layout, (), kind) | _ -> None) (fun (kind, _, layout) -> Anonymous_field (layout, kind)) ; case ~title:"Dynamic_field" (Tag 2) (obj4 (req "kind" (constant "dyn")) (opt "name" string) (req "num_fields" int31) (req "size" unsigned_integer_encoding)) (function Dynamic_size_field (name, i, size) -> Some ((), name, i, size) | _ -> None) (fun ((), name, i, size) -> Dynamic_size_field (name, i, size)) ; case ~title:"Optional_field" (Tag 3) (obj2 (req "kind" (constant "option_indicator")) (req "name" string)) (function Optional_field s -> Some ((), s) | _ -> None) (fun ((), s) -> Optional_field s) ] let tag_size_encoding = string_enum [("Uint16", `Uint16) ; ("Uint8", `Uint8) ] let binary_description_encoding = union [ case ~title:"Obj" (Tag 0) (obj1 (req "fields" (list (dynamic_size field_descr_encoding)))) (function | Obj { fields } -> Some (fields) | _ -> None) (fun (fields) -> Obj { fields }) ; case ~title:"Cases" (Tag 1) (obj3 (req "tag_size" tag_size_encoding) (req "kind" (dynamic_size kind_t_encoding)) (req "cases" (list (def "union case" @@ conv (fun (tag, name, fields) -> (tag, fields, name)) (fun (tag, fields, name) -> (tag, name, fields)) @@ obj3 (req "tag" int31) (req "fields" (list (dynamic_size field_descr_encoding))) (opt "name" string))))) (function | Cases { kind ; tag_size ; cases } -> Some (tag_size, kind, cases) | _ -> None) (fun (tag_size, kind, cases) -> Cases { kind ; tag_size ; cases }) ; case ~title:"Int_enum" (Tag 2) (obj2 (req "size" integer_encoding) (req "cases" (list (tup2 int31 string)))) (function Int_enum { size ; cases } -> Some (size, cases) | _ -> None) (fun (size, cases) -> Int_enum { size ; cases }) ] let encoding = conv (fun { toplevel ; fields } -> (toplevel, fields)) (fun (toplevel, fields) -> { toplevel ; fields }) @@ obj2 (req "toplevel" binary_description_encoding) (req "fields" (list (obj2 (req "description" description_encoding) (req "encoding" binary_description_encoding)))) end let encoding = Encoding.encoding let pp = Printer.pp