Yaml encoder/decoder for OCaml jsont codecs
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Jsont.Repr 7open Yamlrw 8 9(* YAML format *) 10 11type yaml_format = Block | Flow | Layout 12 13(* Decoder *) 14 15type decoder = { 16 parser : Parser.t; 17 file : string; 18 locs : bool; 19 _layout : bool; (* For future layout preservation *) 20 max_depth : int; 21 max_nodes : int; 22 mutable node_count : int; 23 mutable current : Event.spanned option; 24 _anchors : (string, Jsont.json) Hashtbl.t; (* For future anchor resolution *) 25 meta_none : Jsont.Meta.t; 26} 27 28let make_decoder ?(locs = false) ?(layout = false) ?(file = "-") 29 ?(max_depth = 100) ?(max_nodes = 10_000_000) parser = 30 let meta_none = Jsont.Meta.make (Jsont.Textloc.(set_file none) file) in 31 { 32 parser; 33 file; 34 locs; 35 _layout = layout; 36 max_depth; 37 max_nodes; 38 node_count = 0; 39 current = None; 40 _anchors = Hashtbl.create 16; 41 meta_none; 42 } 43 44(* Decoder helpers *) 45 46(* Local helper to reduce Jsont.Error.msgf boilerplate *) 47let err_msg meta fmt = Jsont.Error.msgf meta fmt 48let err_msg_none fmt = Jsont.Error.msgf Jsont.Meta.none fmt 49 50let check_depth d ~nest = 51 if nest > d.max_depth then 52 err_msg_none "Maximum nesting depth %d exceeded" d.max_depth 53 54let check_nodes d = 55 d.node_count <- d.node_count + 1; 56 if d.node_count > d.max_nodes then 57 err_msg_none "Maximum node count %d exceeded" d.max_nodes 58 59let meta_of_span d span = 60 if not d.locs then d.meta_none 61 else 62 let start = span.Span.start and stop = span.Span.stop in 63 let first_byte = start.Position.index in 64 let last_byte = max first_byte (stop.Position.index - 1) in 65 (* line_pos is (line_number, byte_position_of_line_start) *) 66 let first_line = 67 (start.Position.line, start.Position.index - start.Position.column + 1) 68 in 69 (* Handle case where stop is at the start of a new line (column 1) 70 This happens when the span includes a trailing newline. 71 The last_byte is on the previous line, so we need to calculate 72 the line start position based on last_byte, not stop. *) 73 let last_line = 74 if stop.Position.column = 1 && stop.Position.line > start.Position.line then 75 (* last_byte is on the previous line (stop.line - 1) 76 We need to estimate where that line starts. Since we don't have 77 the full text, we can't calculate it exactly, but we can use: 78 last_byte - (estimated_column - 1) 79 For now, we'll use the same line as start if they're close, 80 or just report it as the previous line. *) 81 let last_line_num = stop.Position.line - 1 in 82 (* Estimate: assume last_byte is somewhere on the previous line. 83 We'll use the byte position minus a reasonable offset. 84 This is approximate but better than wrapping to the next line. *) 85 if last_line_num = start.Position.line then 86 (* Same line as start - use start's line position *) 87 first_line 88 else 89 (* Different line - estimate line start as last_byte minus some offset 90 Since we subtracted 1 from stop.index to get last_byte, and stop.column was 1, 91 last_byte should be the newline character on the previous line. 92 The line likely started much earlier, but we'll estimate conservatively. *) 93 (last_line_num, last_byte) 94 else 95 (stop.Position.line, stop.Position.index - stop.Position.column + 1) 96 in 97 let textloc = 98 Jsont.Textloc.make ~file:d.file ~first_byte ~last_byte ~first_line 99 ~last_line 100 in 101 Jsont.Meta.make textloc 102 103let next_event d = 104 d.current <- Parser.next d.parser; 105 d.current 106 107let peek_event d = 108 match d.current with Some _ -> d.current | None -> next_event d 109 110let skip_event d = d.current <- None 111 112let _expect_event d pred name = 113 match peek_event d with 114 | Some ev when pred ev.Event.event -> 115 skip_event d; 116 ev 117 | Some ev -> 118 let span = ev.Event.span in 119 let meta = meta_of_span d span in 120 err_msg meta "Expected %s but found %a" name Event.pp ev.Event.event 121 | None -> err_msg_none "Expected %s but reached end of stream" name 122 123(* Error helpers *) 124 125let _err_expected_scalar d ev = 126 let meta = meta_of_span d ev.Event.span in 127 err_msg meta "Expected scalar but found %a" Event.pp ev.Event.event 128 129let err_type_mismatch d span t ~fnd = 130 let open Jsont.Repr in 131 let meta = meta_of_span d span in 132 err_msg meta "Expected %s but found %s" (kinded_sort t) fnd 133 134(* YAML scalar resolution *) 135 136let is_null_scalar s = 137 s = "" || s = "~" || s = "null" || s = "Null" || s = "NULL" 138 139let bool_of_scalar_opt s = 140 match s with 141 | "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" -> 142 Some true 143 | "false" | "False" | "FALSE" | "no" | "No" | "NO" | "off" | "Off" | "OFF" -> 144 Some false 145 | _ -> None 146 147let float_of_scalar_opt s = 148 (* Handle YAML special floats *) 149 match s with 150 | ".inf" | ".Inf" | ".INF" -> Some Float.infinity 151 | "+.inf" | "+.Inf" | "+.INF" -> Some Float.infinity 152 | "-.inf" | "-.Inf" | "-.INF" -> Some Float.neg_infinity 153 | ".nan" | ".NaN" | ".NAN" -> Some Float.nan 154 | _ -> ( 155 (* Try parsing as number, allowing underscores *) 156 let s' = String.concat "" (String.split_on_char '_' s) in 157 (* Try int first (supports 0o, 0x, 0b) then float *) 158 match int_of_string_opt s' with 159 | Some i -> Some (float_of_int i) 160 | None -> float_of_string_opt s') 161 162let _int_of_scalar_opt s = 163 (* Handle hex, octal, and regular integers with underscores *) 164 let s' = String.concat "" (String.split_on_char '_' s) in 165 int_of_string_opt s' 166 167(* Decode a scalar value according to expected type *) 168let rec decode_scalar_as : type a. 169 decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a = 170 fun d ev value style t -> 171 check_nodes d; 172 let meta = meta_of_span d ev.Event.span in 173 match t with 174 | Null map -> 175 if is_null_scalar value then map.dec meta () 176 else err_type_mismatch d ev.span t ~fnd:("scalar " ^ value) 177 | Bool map -> ( 178 match bool_of_scalar_opt value with 179 | Some b -> map.dec meta b 180 | None -> 181 (* For explicitly quoted strings, fail *) 182 if style <> `Plain then 183 err_type_mismatch d ev.span t ~fnd:("string " ^ value) 184 else err_type_mismatch d ev.span t ~fnd:("scalar " ^ value)) 185 | Number map -> ( 186 if 187 (* Handle null -> nan mapping like jsont *) 188 is_null_scalar value 189 then map.dec meta Float.nan 190 else 191 match float_of_scalar_opt value with 192 | Some f -> map.dec meta f 193 | None -> err_type_mismatch d ev.span t ~fnd:("scalar " ^ value)) 194 | String map -> 195 (* Don't decode null values as strings - they should fail so outer combinators 196 like 'option' or 'any' can handle them properly. 197 BUT: quoted strings should always be treated as strings, even if they 198 look like null (e.g., "" or "null") *) 199 if style = `Plain && is_null_scalar value then 200 err_type_mismatch d ev.span t ~fnd:"null" 201 else 202 (* Strings accept quoted scalars or non-null plain scalars *) 203 map.dec meta value 204 | Map m -> 205 (* Handle Map combinators (e.g., from Jsont.option) *) 206 m.dec (decode_scalar_as d ev value style m.dom) 207 | Rec lazy_t -> 208 (* Handle recursive types *) 209 decode_scalar_as d ev value style (Lazy.force lazy_t) 210 | _ -> err_type_mismatch d ev.span t ~fnd:"scalar" 211 212(* Forward declaration for mutual recursion *) 213let rec decode : type a. decoder -> nest:int -> a t -> a = 214 fun d ~nest t -> 215 check_depth d ~nest; 216 match peek_event d with 217 | None -> err_msg_none "Unexpected end of YAML stream" 218 | Some ev -> ( 219 match (ev.Event.event, t) with 220 (* Scalar events *) 221 | Event.Scalar { value; style; anchor; _ }, _ -> 222 skip_event d; 223 let result = decode_scalar d ~nest ev value style t in 224 (* Store anchor if present - TODO: implement anchor storage *) 225 (match anchor with 226 | Some _name -> 227 (* We need generic JSON for anchors - decode as json and convert back *) 228 () 229 | None -> ()); 230 result 231 (* Alias *) 232 | Event.Alias { anchor }, _ -> 233 skip_event d; 234 decode_alias d ev anchor t 235 (* Map combinator - must come before specific event matches *) 236 | _, Map m -> m.dec (decode d ~nest m.dom) 237 (* Recursive types - must come before specific event matches *) 238 | _, Rec lazy_t -> decode d ~nest (Lazy.force lazy_t) 239 (* Sequence -> Array *) 240 | Event.Sequence_start _, Array map -> decode_array d ~nest ev map 241 | Event.Sequence_start _, Any map -> decode_any_sequence d ~nest ev t map 242 | Event.Sequence_start _, _ -> 243 err_type_mismatch d ev.span t ~fnd:"sequence" 244 (* Mapping -> Object *) 245 | Event.Mapping_start _, Object map -> decode_object d ~nest ev map 246 | Event.Mapping_start _, Any map -> decode_any_mapping d ~nest ev t map 247 | Event.Mapping_start _, _ -> err_type_mismatch d ev.span t ~fnd:"mapping" 248 (* Unexpected events *) 249 | Event.Sequence_end, _ -> 250 err_msg (meta_of_span d ev.span) "Unexpected sequence end" 251 | Event.Mapping_end, _ -> 252 err_msg (meta_of_span d ev.span) "Unexpected mapping end" 253 | Event.Document_start _, _ -> 254 err_msg (meta_of_span d ev.span) "Unexpected document start" 255 | Event.Document_end _, _ -> 256 err_msg (meta_of_span d ev.span) "Unexpected document end" 257 | Event.Stream_start _, _ -> 258 err_msg (meta_of_span d ev.span) "Unexpected stream start" 259 | Event.Stream_end, _ -> 260 err_msg (meta_of_span d ev.span) "Unexpected stream end") 261 262and decode_scalar : type a. 263 decoder -> nest:int -> Event.spanned -> string -> Scalar_style.t -> a t -> a 264 = 265 fun d ~nest ev value style t -> 266 match t with 267 | Any map -> decode_any_scalar d ev value style t map 268 | Map m -> m.dec (decode_scalar d ~nest ev value style m.dom) 269 | Rec lazy_t -> decode_scalar d ~nest ev value style (Lazy.force lazy_t) 270 | _ -> decode_scalar_as d ev value style t 271 272and decode_any_scalar : type a. 273 decoder -> 274 Event.spanned -> 275 string -> 276 Scalar_style.t -> 277 a t -> 278 a any_map -> 279 a = 280 fun d ev value style t map -> 281 check_nodes d; 282 let meta = meta_of_span d ev.span in 283 let type_err fnd = Jsont.Repr.type_error meta t ~fnd in 284 (* Determine which decoder to use based on scalar content *) 285 if is_null_scalar value then 286 match map.dec_null with 287 | Some t' -> decode_scalar_as d ev value style t' 288 | None -> type_err Jsont.Sort.Null 289 else if style = `Plain then 290 (* Try bool, then number, then string *) 291 match bool_of_scalar_opt value with 292 | Some _ -> ( 293 match map.dec_bool with 294 | Some t' -> decode_scalar_as d ev value style t' 295 | None -> ( 296 match map.dec_string with 297 | Some t' -> decode_scalar_as d ev value style t' 298 | None -> type_err Jsont.Sort.Bool)) 299 | None -> ( 300 match float_of_scalar_opt value with 301 | Some _ -> ( 302 match map.dec_number with 303 | Some t' -> decode_scalar_as d ev value style t' 304 | None -> ( 305 match map.dec_string with 306 | Some t' -> decode_scalar_as d ev value style t' 307 | None -> type_err Jsont.Sort.Number)) 308 | None -> ( 309 (* Plain scalar that's not bool/number -> string *) 310 match map.dec_string with 311 | Some t' -> decode_scalar_as d ev value style t' 312 | None -> type_err Jsont.Sort.String)) 313 else 314 (* Quoted scalars are strings *) 315 match map.dec_string with 316 | Some t' -> decode_scalar_as d ev value style t' 317 | None -> type_err Jsont.Sort.String 318 319and decode_alias : type a. decoder -> Event.spanned -> string -> a t -> a = 320 fun d ev anchor t -> 321 check_nodes d; 322 match Hashtbl.find_opt d._anchors anchor with 323 | None -> 324 let meta = meta_of_span d ev.span in 325 err_msg meta "Unknown anchor: %s" anchor 326 | Some json_value -> 327 (* Decode the stored JSON value through the type *) 328 let t' = Jsont.Repr.unsafe_to_t t in 329 match Jsont.Json.decode' t' json_value with 330 | Ok v -> v 331 | Error e -> raise (Jsont.Error e) 332 333and decode_array : type a elt b. 334 decoder -> nest:int -> Event.spanned -> (a, elt, b) array_map -> a = 335 fun d ~nest start_ev array_map -> 336 skip_event d; 337 (* consume Sequence_start *) 338 check_nodes d; 339 let meta = meta_of_span d start_ev.span in 340 let builder = ref (array_map.dec_empty ()) in 341 let idx = ref 0 in 342 let rec loop () = 343 match peek_event d with 344 | Some { Event.event = Event.Sequence_end; span } -> 345 skip_event d; 346 let end_meta = meta_of_span d span in 347 array_map.dec_finish end_meta !idx !builder 348 | Some _ -> 349 let i = !idx in 350 (try 351 if array_map.dec_skip i !builder then begin 352 (* Skip this element by decoding as ignore *) 353 let _ : unit = 354 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) 355 in 356 () 357 end 358 else begin 359 let elt = decode d ~nest:(nest + 1) array_map.elt in 360 builder := array_map.dec_add i elt !builder 361 end 362 with Jsont.Error e -> 363 Jsont.Repr.error_push_array meta array_map (i, Jsont.Meta.none) e); 364 incr idx; 365 loop () 366 | None -> err_msg meta "Unclosed sequence" 367 in 368 loop () 369 370and decode_any_sequence : type a. 371 decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a = 372 fun d ~nest ev t map -> 373 match map.dec_array with 374 | Some t' -> ( 375 (* The t' decoder might be wrapped (e.g., Map for option types) 376 Directly decode the array and let the wrapper handle it *) 377 match t' with 378 | Array array_map -> decode_array d ~nest ev array_map 379 | _ -> 380 (* For wrapped types like Map (Array ...), use full decode *) 381 decode d ~nest t') 382 | None -> 383 Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Array 384 385and decode_object : type o. 386 decoder -> nest:int -> Event.spanned -> (o, o) object_map -> o = 387 fun d ~nest start_ev map -> 388 skip_event d; 389 (* consume Mapping_start *) 390 check_nodes d; 391 let meta = meta_of_span d start_ev.span in 392 let dict = 393 decode_object_members d ~nest meta map String_map.empty Dict.empty 394 in 395 let dict = Dict.add object_meta_arg meta dict in 396 apply_dict map.dec dict 397 398and decode_object_members : type o. 399 decoder -> 400 nest:int -> 401 Jsont.Meta.t -> 402 (o, o) object_map -> 403 mem_dec String_map.t -> 404 Dict.t -> 405 Dict.t = 406 fun d ~nest obj_meta map mem_miss dict -> 407 (* Merge expected member decoders *) 408 let u _ _ _ = assert false in 409 let mem_miss = String_map.union u mem_miss map.mem_decs in 410 match map.shape with 411 | Object_basic umems -> 412 decode_object_basic d ~nest obj_meta map umems mem_miss dict 413 | Object_cases (umems_opt, cases) -> 414 (* Wrap umems_opt to hide existential types *) 415 let umems = Unknown_mems umems_opt in 416 decode_object_cases d ~nest obj_meta map umems cases mem_miss [] dict 417 418and decode_object_basic : type o mems builder. 419 decoder -> 420 nest:int -> 421 Jsont.Meta.t -> 422 (o, o) object_map -> 423 (o, mems, builder) unknown_mems -> 424 mem_dec String_map.t -> 425 Dict.t -> 426 Dict.t = 427 fun d ~nest obj_meta object_map umems mem_miss dict -> 428 let ubuilder = 429 ref 430 (match umems with 431 | Unknown_skip | Unknown_error -> Obj.magic () 432 | Unknown_keep (mmap, _) -> mmap.dec_empty ()) 433 in 434 let mem_miss = ref mem_miss in 435 let dict = ref dict in 436 let rec loop () = 437 match peek_event d with 438 | Some { Event.event = Event.Mapping_end; _ } -> 439 skip_event d; 440 (* Finalize *) 441 finish_object obj_meta object_map umems !ubuilder !mem_miss !dict 442 | Some ev -> 443 (* Expect a scalar key *) 444 let name, name_meta = decode_mapping_key d ev in 445 (* Look up member decoder *) 446 (match String_map.find_opt name object_map.mem_decs with 447 | Some (Mem_dec mem) -> ( 448 mem_miss := String_map.remove name !mem_miss; 449 try 450 let v = decode d ~nest:(nest + 1) mem.type' in 451 dict := Dict.add mem.id v !dict 452 with Jsont.Error e -> 453 Jsont.Repr.error_push_object obj_meta object_map (name, name_meta) 454 e) 455 | None -> ( 456 (* Unknown member *) 457 match umems with 458 | Unknown_skip -> 459 let _ : unit = 460 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) 461 in 462 () 463 | Unknown_error -> 464 Jsont.Repr.unexpected_mems_error obj_meta object_map 465 ~fnd:[ (name, name_meta) ] 466 | Unknown_keep (mmap, _) -> ( 467 try 468 let v = decode d ~nest:(nest + 1) mmap.mems_type in 469 ubuilder := mmap.dec_add name_meta name v !ubuilder 470 with Jsont.Error e -> 471 Jsont.Repr.error_push_object obj_meta object_map 472 (name, name_meta) e))); 473 loop () 474 | None -> err_msg obj_meta "Unclosed mapping" 475 in 476 loop () 477 478and finish_object : type o mems builder. 479 Jsont.Meta.t -> 480 (o, o) object_map -> 481 (o, mems, builder) unknown_mems -> 482 builder -> 483 mem_dec String_map.t -> 484 Dict.t -> 485 Dict.t = 486 fun meta map umems ubuilder mem_miss dict -> 487 let open Jsont.Repr in 488 let dict = Dict.add object_meta_arg meta dict in 489 let dict = 490 match umems with 491 | Unknown_skip | Unknown_error -> dict 492 | Unknown_keep (mmap, _) -> 493 Dict.add mmap.id (mmap.dec_finish meta ubuilder) dict 494 in 495 (* Check for missing required members *) 496 let add_default _ (Mem_dec mem_map) dict = 497 match mem_map.dec_absent with 498 | Some v -> Dict.add mem_map.id v dict 499 | None -> raise Exit 500 in 501 try String_map.fold add_default mem_miss dict 502 with Exit -> 503 let no_default _ (Mem_dec mm) = Option.is_none mm.dec_absent in 504 let exp = String_map.filter no_default mem_miss in 505 missing_mems_error meta map ~exp ~fnd:[] 506 507and decode_object_cases : type o cases tag. 508 decoder -> 509 nest:int -> 510 Jsont.Meta.t -> 511 (o, o) object_map -> 512 unknown_mems_option -> 513 (o, cases, tag) object_cases -> 514 mem_dec String_map.t -> 515 (Jsont.name * Jsont.json) list -> 516 Dict.t -> 517 Dict.t = 518 fun d ~nest obj_meta object_map umems cases mem_miss delayed dict -> 519 match peek_event d with 520 | Some { Event.event = Event.Mapping_end; _ } -> ( 521 skip_event d; 522 (* No tag found - use dec_absent if available *) 523 match cases.tag.dec_absent with 524 | Some tag -> 525 decode_with_case_tag d ~nest obj_meta object_map umems cases tag 526 mem_miss delayed dict 527 | None -> 528 (* Missing required case tag *) 529 let exp = String_map.singleton cases.tag.name (Mem_dec cases.tag) in 530 let fnd = List.map (fun ((n, _), _) -> n) delayed in 531 Jsont.Repr.missing_mems_error obj_meta object_map ~exp ~fnd) 532 | Some ev -> 533 let name, name_meta = decode_mapping_key d ev in 534 if String.equal name cases.tag.name then begin 535 (* Found the case tag *) 536 let tag = decode d ~nest:(nest + 1) cases.tag.type' in 537 decode_with_case_tag d ~nest obj_meta object_map umems cases tag 538 mem_miss delayed dict 539 end 540 else begin 541 (* Not the case tag - check if known member or delay *) 542 match String_map.find_opt name object_map.mem_decs with 543 | Some (Mem_dec mem) -> ( 544 let mem_miss = String_map.remove name mem_miss in 545 try 546 let v = decode d ~nest:(nest + 1) mem.type' in 547 let dict = Dict.add mem.id v dict in 548 decode_object_cases d ~nest obj_meta object_map umems cases 549 mem_miss delayed dict 550 with Jsont.Error e -> 551 Jsont.Repr.error_push_object obj_meta object_map (name, name_meta) 552 e) 553 | None -> 554 (* Unknown member - decode as generic JSON and delay *) 555 let v = 556 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.json) 557 in 558 let delayed = ((name, name_meta), v) :: delayed in 559 decode_object_cases d ~nest obj_meta object_map umems cases 560 mem_miss delayed dict 561 end 562 | None -> err_msg obj_meta "Unclosed mapping" 563 564and decode_with_case_tag : type o cases tag. 565 decoder -> 566 nest:int -> 567 Jsont.Meta.t -> 568 (o, o) object_map -> 569 unknown_mems_option -> 570 (o, cases, tag) object_cases -> 571 tag -> 572 mem_dec String_map.t -> 573 (Jsont.name * Jsont.json) list -> 574 Dict.t -> 575 Dict.t = 576 fun d ~nest obj_meta map umems cases tag mem_miss delayed dict -> 577 let open Jsont.Repr in 578 let eq_tag (Case c) = cases.tag_compare c.tag tag = 0 in 579 match List.find_opt eq_tag cases.cases with 580 | None -> unexpected_case_tag_error obj_meta map cases tag 581 | Some (Case case) -> 582 (* Continue decoding with the case's object map *) 583 let case_dict = 584 decode_case_remaining d ~nest obj_meta case.object_map umems mem_miss 585 delayed dict 586 in 587 let case_value = apply_dict case.object_map.dec case_dict in 588 Dict.add cases.id (case.dec case_value) dict 589 590and decode_case_remaining : type o. 591 decoder -> 592 nest:int -> 593 Jsont.Meta.t -> 594 (o, o) object_map -> 595 unknown_mems_option -> 596 mem_dec String_map.t -> 597 (Jsont.name * Jsont.json) list -> 598 Dict.t -> 599 Dict.t = 600 fun d ~nest obj_meta case_map _umems mem_miss delayed dict -> 601 (* First, process delayed members against the case map *) 602 let u _ _ _ = assert false in 603 let mem_miss = String_map.union u mem_miss case_map.mem_decs in 604 let dict, mem_miss = 605 List.fold_left 606 (fun (dict, mem_miss) ((name, meta), json_value) -> 607 match String_map.find_opt name case_map.mem_decs with 608 | Some (Mem_dec mem) -> ( 609 let t' = Jsont.Repr.unsafe_to_t mem.type' in 610 match Jsont.Json.decode' t' json_value with 611 | Ok v -> 612 let dict = Dict.add mem.id v dict in 613 let mem_miss = String_map.remove name mem_miss in 614 (dict, mem_miss) 615 | Error e -> 616 Jsont.Repr.error_push_object obj_meta case_map (name, meta) e) 617 | None -> 618 (* Unknown for case too - skip them *) 619 (dict, mem_miss)) 620 (dict, mem_miss) delayed 621 in 622 (* Then continue reading remaining members using case's own unknown handling *) 623 match case_map.shape with 624 | Object_basic case_umems -> 625 decode_object_basic d ~nest obj_meta case_map case_umems mem_miss dict 626 | Object_cases _ -> 627 (* Nested cases shouldn't happen - use skip for safety *) 628 decode_object_basic d ~nest obj_meta case_map Unknown_skip mem_miss dict 629 630and decode_any_mapping : type a. 631 decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a = 632 fun d ~nest ev t map -> 633 match map.dec_object with 634 | Some t' -> decode d ~nest t' 635 | None -> 636 Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Object 637 638and decode_mapping_key : decoder -> Event.spanned -> string * Jsont.Meta.t = 639 fun d ev -> 640 match ev.Event.event with 641 | Event.Scalar { value; _ } -> 642 skip_event d; 643 let meta = meta_of_span d ev.span in 644 (value, meta) 645 | _ -> 646 let meta = meta_of_span d ev.span in 647 err_msg meta "Mapping keys must be scalars (strings), found %a" Event.pp 648 ev.event 649 650(* Skip stream/document wrappers *) 651let skip_to_content d = 652 let rec loop () = 653 match peek_event d with 654 | Some { Event.event = Event.Stream_start _; _ } -> 655 skip_event d; 656 loop () 657 | Some { Event.event = Event.Document_start _; _ } -> 658 skip_event d; 659 loop () 660 | _ -> () 661 in 662 loop () 663 664let skip_end_wrappers d = 665 let rec loop () = 666 match peek_event d with 667 | Some { Event.event = Event.Document_end _; _ } -> 668 skip_event d; 669 loop () 670 | Some { Event.event = Event.Stream_end; _ } -> 671 skip_event d; 672 loop () 673 | None -> () 674 | Some ev -> 675 let meta = meta_of_span d ev.span in 676 err_msg meta "Expected end of document but found %a" Event.pp ev.event 677 in 678 loop () 679 680(* Skip to the end of the current document after an error *) 681let skip_to_document_end d = 682 let rec loop depth = 683 match peek_event d with 684 | None -> () 685 | Some { Event.event = Event.Stream_end; _ } -> () 686 | Some { Event.event = Event.Document_end _; _ } -> 687 skip_event d; 688 if depth = 0 then () else loop (depth - 1) 689 | Some { Event.event = Event.Document_start _; _ } -> 690 skip_event d; 691 loop (depth + 1) 692 | Some _ -> 693 skip_event d; 694 loop depth 695 in 696 loop 0 697 698(* Public decode API *) 699 700(* Decode all documents from a multi-document YAML stream *) 701let decode_all' ?(layout = false) ?(locs = false) ?(file = "-") 702 ?(max_depth = 100) ?(max_nodes = 10_000_000) t reader = 703 let parser = Parser.of_reader reader in 704 let d = make_decoder ~layout ~locs ~file ~max_depth ~max_nodes parser in 705 let t' = Jsont.Repr.of_t t in 706 let rec next_doc () = 707 match peek_event d with 708 | None -> Seq.Nil 709 | Some { Event.event = Event.Stream_end; _ } -> 710 skip_event d; 711 Seq.Nil 712 | Some _ -> ( 713 try 714 skip_to_content d; 715 (* Reset node count for each document *) 716 d.node_count <- 0; 717 let v = decode d ~nest:0 t' in 718 (* Skip document end marker if present *) 719 (match peek_event d with 720 | Some { Event.event = Event.Document_end _; _ } -> skip_event d 721 | _ -> ()); 722 Seq.Cons (Ok v, next_doc) 723 with 724 | Jsont.Error e -> 725 skip_to_document_end d; 726 Seq.Cons (Error e, next_doc) 727 | Error.Yamlrw_error err -> 728 skip_to_document_end d; 729 let msg = Error.to_string err in 730 let e = Jsont.(Error.make_msg Error.Context.empty Meta.none msg) in 731 Seq.Cons (Error e, next_doc)) 732 in 733 next_doc 734 735let decode_all ?layout ?locs ?file ?max_depth ?max_nodes t reader = 736 decode_all' ?layout ?locs ?file ?max_depth ?max_nodes t reader 737 |> Seq.map (Result.map_error Jsont.Error.to_string) 738 739let decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader = 740 let parser = Parser.of_reader reader in 741 let d = make_decoder ?layout ?locs ?file ?max_depth ?max_nodes parser in 742 try 743 skip_to_content d; 744 let t' = Jsont.Repr.of_t t in 745 let v = decode d ~nest:0 t' in 746 skip_end_wrappers d; 747 Ok v 748 with 749 | Jsont.Error e -> Error e 750 | Error.Yamlrw_error err -> 751 let msg = Error.to_string err in 752 Error Jsont.(Error.make_msg Error.Context.empty Meta.none msg) 753 754let decode ?layout ?locs ?file ?max_depth ?max_nodes t reader = 755 Result.map_error Jsont.Error.to_string 756 (decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader) 757 758(* Encoder *) 759 760type encoder = { 761 emitter : Emitter.t; 762 format : yaml_format; 763 _indent : int; (* Stored for future use in custom formatting *) 764 explicit_doc : bool; 765 scalar_style : Scalar_style.t; 766} 767 768let make_encoder ?(format = Block) ?(indent = 2) ?(explicit_doc = false) 769 ?(scalar_style = `Any) emitter = 770 { emitter; format; _indent = indent; explicit_doc; scalar_style } 771 772let layout_style_of_format = function 773 | Block -> `Block 774 | Flow -> `Flow 775 | Layout -> `Any 776 777(* Choose appropriate scalar style for a string *) 778let choose_scalar_style ~preferred s = 779 if preferred <> `Any then preferred 780 else if String.contains s '\n' then `Literal 781 else if String.length s > 80 then `Folded 782 else `Plain 783 784(* Helper to create scalar events with common defaults *) 785let scalar_event ?(anchor = None) ?(tag = None) ~value ~style () = 786 Event.Scalar 787 { 788 anchor; 789 tag; 790 value; 791 plain_implicit = true; 792 quoted_implicit = true; 793 style; 794 } 795 796(* Helper to emit events *) 797let emit e = Emitter.emit e.emitter 798 799(* Encode null *) 800let encode_null e _meta = emit e (scalar_event ~value:"null" ~style:`Plain ()) 801 802(* Encode boolean *) 803let encode_bool e _meta b = 804 emit e (scalar_event ~value:(if b then "true" else "false") ~style:`Plain ()) 805 806(* Encode number *) 807let encode_number e _meta f = 808 let value = 809 match Float.classify_float f with 810 | FP_nan -> ".nan" 811 | FP_infinite -> if f > 0.0 then ".inf" else "-.inf" 812 | _ -> 813 if Float.is_integer f && Float.abs f < 1e15 then Printf.sprintf "%.0f" f 814 else Printf.sprintf "%g" f 815 in 816 emit e (scalar_event ~value ~style:`Plain ()) 817 818(* Encode string *) 819let encode_string e _meta s = 820 let style = choose_scalar_style ~preferred:e.scalar_style s in 821 emit e (scalar_event ~value:s ~style ()) 822 823let rec encode : type a. encoder -> a t -> a -> unit = 824 fun e t v -> 825 match t with 826 | Null map -> 827 let meta = map.enc_meta v in 828 let () = map.enc v in 829 encode_null e meta 830 | Bool map -> 831 let meta = map.enc_meta v in 832 let b = map.enc v in 833 encode_bool e meta b 834 | Number map -> 835 let meta = map.enc_meta v in 836 let f = map.enc v in 837 encode_number e meta f 838 | String map -> 839 let meta = map.enc_meta v in 840 let s = map.enc v in 841 encode_string e meta s 842 | Array map -> encode_array e map v 843 | Object map -> encode_object e map v 844 | Any map -> 845 let t' = map.enc v in 846 encode e t' v 847 | Map m -> encode e m.dom (m.enc v) 848 | Rec lazy_t -> encode e (Lazy.force lazy_t) v 849 850and encode_array : type a elt b. encoder -> (a, elt, b) array_map -> a -> unit = 851 fun e map v -> 852 let style = layout_style_of_format e.format in 853 emit e 854 (Event.Sequence_start { anchor = None; tag = None; implicit = true; style }); 855 let _ = 856 map.enc 857 (fun () _idx elt -> 858 encode e map.elt elt; 859 ()) 860 () v 861 in 862 emit e Event.Sequence_end 863 864and encode_object : type o. encoder -> (o, o) object_map -> o -> unit = 865 fun e map v -> 866 let style = layout_style_of_format e.format in 867 emit e (Event.Mapping_start { anchor = None; tag = None; implicit = true; style }); 868 (* Encode each member *) 869 List.iter 870 (fun (Mem_enc mem) -> 871 let mem_v = mem.enc v in 872 if not (mem.enc_omit mem_v) then begin 873 (* Emit key *) 874 emit e (scalar_event ~value:mem.name ~style:`Plain ()); 875 (* Emit value *) 876 encode e mem.type' mem_v 877 end) 878 map.mem_encs; 879 (* Handle case objects *) 880 (match map.shape with 881 | Object_basic _ -> () 882 | Object_cases (_, cases) -> 883 let (Case_value (case_map, case_v)) = cases.enc_case (cases.enc v) in 884 (* Emit case tag *) 885 if not (cases.tag.enc_omit case_map.tag) then begin 886 emit e (scalar_event ~value:cases.tag.name ~style:`Plain ()); 887 encode e cases.tag.type' case_map.tag 888 end; 889 (* Emit case members *) 890 List.iter 891 (fun (Mem_enc mem) -> 892 let mem_v = mem.enc case_v in 893 if not (mem.enc_omit mem_v) then begin 894 emit e (scalar_event ~value:mem.name ~style:`Plain ()); 895 encode e mem.type' mem_v 896 end) 897 case_map.object_map.mem_encs); 898 emit e Event.Mapping_end 899 900(* Public encode API *) 901 902let encode' ?buf:_ ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer = 903 let config = 904 { 905 Emitter.default_config with 906 indent = Option.value ~default:2 indent; 907 layout_style = (match format with Some Flow -> `Flow | _ -> `Block); 908 } 909 in 910 let emitter = Emitter.of_writer ~config writer in 911 let e = make_encoder ?format ?indent ?explicit_doc ?scalar_style emitter in 912 try 913 emit e (Event.Stream_start { encoding = `Utf8 }); 914 emit e (Event.Document_start { version = None; implicit = not e.explicit_doc }); 915 let t' = Jsont.Repr.of_t t in 916 encode e t' v; 917 emit e (Event.Document_end { implicit = not e.explicit_doc }); 918 emit e Event.Stream_end; 919 if eod then Emitter.flush e.emitter; 920 Ok () 921 with 922 | Jsont.Error err -> Error err 923 | Error.Yamlrw_error err -> 924 let msg = Error.to_string err in 925 Error Jsont.(Error.make_msg Error.Context.empty Meta.none msg) 926 927let encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer = 928 Result.map_error Jsont.Error.to_string 929 (encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer) 930 931(* Recode *) 932 933let recode ?layout ?locs ?file ?max_depth ?max_nodes ?buf ?format ?indent 934 ?explicit_doc ?scalar_style t reader writer ~eod = 935 let format = 936 match (layout, format) with Some true, None -> Some Layout | _, f -> f 937 in 938 let layout = 939 match (layout, format) with None, Some Layout -> Some true | l, _ -> l 940 in 941 match decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader with 942 | Ok v -> 943 encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer 944 | Error e -> Error (Jsont.Error.to_string e)