Pure OCaml Yaml 1.2 reader and writer using Bytesrw
at main 4.9 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** JSON-compatible YAML value representation *) 7 8type t = 9 [ `Null 10 | `Bool of bool 11 | `Float of float 12 | `String of string 13 | `A of t list 14 | `O of (string * t) list ] 15 16(* Type equality is ensured by structural compatibility with Yamlrw.value *) 17 18(** Constructors *) 19 20let null : t = `Null 21let bool b : t = `Bool b 22let int n : t = `Float (Float.of_int n) 23let float f : t = `Float f 24let string s : t = `String s 25let list f xs : t = `A (List.map f xs) 26let obj pairs : t = `O pairs 27 28(** Type name for error messages *) 29let type_name : t -> string = function 30 | `Null -> "null" 31 | `Bool _ -> "bool" 32 | `Float _ -> "float" 33 | `String _ -> "string" 34 | `A _ -> "array" 35 | `O _ -> "object" 36 37(** Safe accessors (return option) *) 38 39let as_null = function `Null -> Some () | _ -> None 40let as_bool = function `Bool b -> Some b | _ -> None 41let as_float = function `Float f -> Some f | _ -> None 42let as_string = function `String s -> Some s | _ -> None 43let as_list = function `A l -> Some l | _ -> None 44let as_assoc = function `O o -> Some o | _ -> None 45 46let as_int = function 47 | `Float f -> 48 let i = Float.to_int f in 49 if Float.equal (Float.of_int i) f then Some i else None 50 | _ -> None 51 52(** Unsafe accessors (raise on type mismatch) *) 53 54let unwrap_or_type_error expected_type extractor v = 55 match extractor v with 56 | Some x -> x 57 | None -> Error.raise (Type_mismatch (expected_type, type_name v)) 58 59let to_null v = unwrap_or_type_error "null" as_null v 60let to_bool v = unwrap_or_type_error "bool" as_bool v 61let to_float v = unwrap_or_type_error "float" as_float v 62let to_string v = unwrap_or_type_error "string" as_string v 63let to_list v = unwrap_or_type_error "array" as_list v 64let to_assoc v = unwrap_or_type_error "object" as_assoc v 65let to_int v = unwrap_or_type_error "int" as_int v 66 67(** Object access *) 68 69let mem key = function 70 | `O pairs -> List.exists (fun (k, _) -> k = key) pairs 71 | _ -> false 72 73let find key = function `O pairs -> List.assoc_opt key pairs | _ -> None 74 75let get key v = 76 match find key v with Some v -> v | None -> Error.raise (Key_not_found key) 77 78let keys = function 79 | `O pairs -> List.map fst pairs 80 | v -> Error.raise (Type_mismatch ("object", type_name v)) 81 82let values = function 83 | `O pairs -> List.map snd pairs 84 | v -> Error.raise (Type_mismatch ("object", type_name v)) 85 86(** Combinators *) 87 88let combine v1 v2 = 89 match (v1, v2) with 90 | `O o1, `O o2 -> `O (o1 @ o2) 91 | v1, _ -> Error.raise (Type_mismatch ("object", type_name v1)) 92 93let map f = function 94 | `A l -> `A (List.map f l) 95 | v -> Error.raise (Type_mismatch ("array", type_name v)) 96 97let filter pred = function 98 | `A l -> `A (List.filter pred l) 99 | v -> Error.raise (Type_mismatch ("array", type_name v)) 100 101(** Pretty printing *) 102 103let rec pp fmt (v : t) = 104 match v with 105 | `Null -> Format.pp_print_string fmt "null" 106 | `Bool b -> Format.pp_print_bool fmt b 107 | `Float f -> 108 if Float.is_integer f && Float.abs f < 1e15 then 109 Format.fprintf fmt "%.0f" f 110 else Format.fprintf fmt "%g" f 111 | `String s -> Format.fprintf fmt "%S" s 112 | `A [] -> Format.pp_print_string fmt "[]" 113 | `A items -> 114 Format.fprintf fmt "@[<hv 2>[@,%a@]@,]" 115 (Format.pp_print_list 116 ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") 117 pp) 118 items 119 | `O [] -> Format.pp_print_string fmt "{}" 120 | `O pairs -> 121 Format.fprintf fmt "@[<hv 2>{@,%a@]@,}" 122 (Format.pp_print_list 123 ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") 124 (fun fmt (k, v) -> Format.fprintf fmt "@[<hv 2>%S:@ %a@]" k pp v)) 125 pairs 126 127(** Equality and comparison *) 128 129let rec equal (a : t) (b : t) = 130 match (a, b) with 131 | `Null, `Null -> true 132 | `Bool a, `Bool b -> a = b 133 | `Float a, `Float b -> Float.equal a b 134 | `String a, `String b -> String.equal a b 135 | `A a, `A b -> List.equal equal a b 136 | `O a, `O b -> 137 List.length a = List.length b 138 && List.for_all2 (fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2) a b 139 | _ -> false 140 141let rec compare (a : t) (b : t) = 142 match (a, b) with 143 | `Null, `Null -> 0 144 | `Null, _ -> -1 145 | _, `Null -> 1 146 | `Bool a, `Bool b -> Bool.compare a b 147 | `Bool _, _ -> -1 148 | _, `Bool _ -> 1 149 | `Float a, `Float b -> Float.compare a b 150 | `Float _, _ -> -1 151 | _, `Float _ -> 1 152 | `String a, `String b -> String.compare a b 153 | `String _, _ -> -1 154 | _, `String _ -> 1 155 | `A a, `A b -> List.compare compare a b 156 | `A _, _ -> -1 157 | _, `A _ -> 1 158 | `O a, `O b -> 159 let cmp_pair (k1, v1) (k2, v2) = 160 let c = String.compare k1 k2 in 161 if c <> 0 then c else compare v1 v2 162 in 163 List.compare cmp_pair a b