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 | Array map ->
205 (* Treat null as an empty array for convenience *)
206 if is_null_scalar value then
207 let end_meta = meta_of_span d ev.Event.span in
208 map.dec_finish end_meta 0 (map.dec_empty ())
209 else
210 err_type_mismatch d ev.span t ~fnd:"scalar"
211 | Object map ->
212 (* Treat null as an empty object for convenience *)
213 if is_null_scalar value then
214 (* Build a dict with all default values from absent members *)
215 let add_default _ (Mem_dec mem_map) dict =
216 match mem_map.dec_absent with
217 | Some v -> Dict.add mem_map.id v dict
218 | None ->
219 (* Required field without default - error *)
220 let exp = String_map.singleton mem_map.name (Mem_dec mem_map) in
221 missing_mems_error meta map ~exp ~fnd:[]
222 in
223 let dict = String_map.fold add_default map.mem_decs Dict.empty in
224 let dict = Dict.add object_meta_arg meta dict in
225 apply_dict map.dec dict
226 else
227 err_type_mismatch d ev.span t ~fnd:"scalar"
228 | Map m ->
229 (* Handle Map combinators (e.g., from Jsont.option) *)
230 m.dec (decode_scalar_as d ev value style m.dom)
231 | Rec lazy_t ->
232 (* Handle recursive types *)
233 decode_scalar_as d ev value style (Lazy.force lazy_t)
234 | _ -> err_type_mismatch d ev.span t ~fnd:"scalar"
235
236(* Forward declaration for mutual recursion *)
237let rec decode : type a. decoder -> nest:int -> a t -> a =
238 fun d ~nest t ->
239 check_depth d ~nest;
240 match peek_event d with
241 | None -> err_msg_none "Unexpected end of YAML stream"
242 | Some ev -> (
243 match (ev.Event.event, t) with
244 (* Scalar events *)
245 | Event.Scalar { value; style; anchor; _ }, _ ->
246 skip_event d;
247 let result = decode_scalar d ~nest ev value style t in
248 (* Store anchor if present - TODO: implement anchor storage *)
249 (match anchor with
250 | Some _name ->
251 (* We need generic JSON for anchors - decode as json and convert back *)
252 ()
253 | None -> ());
254 result
255 (* Alias *)
256 | Event.Alias { anchor }, _ ->
257 skip_event d;
258 decode_alias d ev anchor t
259 (* Map combinator - must come before specific event matches *)
260 | _, Map m -> m.dec (decode d ~nest m.dom)
261 (* Recursive types - must come before specific event matches *)
262 | _, Rec lazy_t -> decode d ~nest (Lazy.force lazy_t)
263 (* Sequence -> Array *)
264 | Event.Sequence_start _, Array map -> decode_array d ~nest ev map
265 | Event.Sequence_start _, Any map -> decode_any_sequence d ~nest ev t map
266 | Event.Sequence_start _, _ ->
267 err_type_mismatch d ev.span t ~fnd:"sequence"
268 (* Mapping -> Object *)
269 | Event.Mapping_start _, Object map -> decode_object d ~nest ev map
270 | Event.Mapping_start _, Any map -> decode_any_mapping d ~nest ev t map
271 | Event.Mapping_start _, _ -> err_type_mismatch d ev.span t ~fnd:"mapping"
272 (* Unexpected events *)
273 | Event.Sequence_end, _ ->
274 err_msg (meta_of_span d ev.span) "Unexpected sequence end"
275 | Event.Mapping_end, _ ->
276 err_msg (meta_of_span d ev.span) "Unexpected mapping end"
277 | Event.Document_start _, _ ->
278 err_msg (meta_of_span d ev.span) "Unexpected document start"
279 | Event.Document_end _, _ ->
280 err_msg (meta_of_span d ev.span) "Unexpected document end"
281 | Event.Stream_start _, _ ->
282 err_msg (meta_of_span d ev.span) "Unexpected stream start"
283 | Event.Stream_end, _ ->
284 err_msg (meta_of_span d ev.span) "Unexpected stream end")
285
286and decode_scalar : type a.
287 decoder -> nest:int -> Event.spanned -> string -> Scalar_style.t -> a t -> a
288 =
289 fun d ~nest ev value style t ->
290 match t with
291 | Any map -> decode_any_scalar d ev value style t map
292 | Map m -> m.dec (decode_scalar d ~nest ev value style m.dom)
293 | Rec lazy_t -> decode_scalar d ~nest ev value style (Lazy.force lazy_t)
294 | _ -> decode_scalar_as d ev value style t
295
296and decode_any_scalar : type a.
297 decoder ->
298 Event.spanned ->
299 string ->
300 Scalar_style.t ->
301 a t ->
302 a any_map ->
303 a =
304 fun d ev value style t map ->
305 check_nodes d;
306 let meta = meta_of_span d ev.span in
307 let type_err fnd = Jsont.Repr.type_error meta t ~fnd in
308 (* Determine which decoder to use based on scalar content *)
309 if is_null_scalar value then
310 match map.dec_null with
311 | Some t' -> decode_scalar_as d ev value style t'
312 | None -> type_err Jsont.Sort.Null
313 else if style = `Plain then
314 (* Try bool, then number, then string *)
315 match bool_of_scalar_opt value with
316 | Some _ -> (
317 match map.dec_bool with
318 | Some t' -> decode_scalar_as d ev value style t'
319 | None -> (
320 match map.dec_string with
321 | Some t' -> decode_scalar_as d ev value style t'
322 | None -> type_err Jsont.Sort.Bool))
323 | None -> (
324 match float_of_scalar_opt value with
325 | Some _ -> (
326 match map.dec_number with
327 | Some t' -> decode_scalar_as d ev value style t'
328 | None -> (
329 match map.dec_string with
330 | Some t' -> decode_scalar_as d ev value style t'
331 | None -> type_err Jsont.Sort.Number))
332 | None -> (
333 (* Plain scalar that's not bool/number -> string *)
334 match map.dec_string with
335 | Some t' -> decode_scalar_as d ev value style t'
336 | None -> type_err Jsont.Sort.String))
337 else
338 (* Quoted scalars are strings *)
339 match map.dec_string with
340 | Some t' -> decode_scalar_as d ev value style t'
341 | None -> type_err Jsont.Sort.String
342
343and decode_alias : type a. decoder -> Event.spanned -> string -> a t -> a =
344 fun d ev anchor t ->
345 check_nodes d;
346 match Hashtbl.find_opt d._anchors anchor with
347 | None ->
348 let meta = meta_of_span d ev.span in
349 err_msg meta "Unknown anchor: %s" anchor
350 | Some json_value ->
351 (* Decode the stored JSON value through the type *)
352 let t' = Jsont.Repr.unsafe_to_t t in
353 match Jsont.Json.decode' t' json_value with
354 | Ok v -> v
355 | Error e -> raise (Jsont.Error e)
356
357and decode_array : type a elt b.
358 decoder -> nest:int -> Event.spanned -> (a, elt, b) array_map -> a =
359 fun d ~nest start_ev array_map ->
360 skip_event d;
361 (* consume Sequence_start *)
362 check_nodes d;
363 let meta = meta_of_span d start_ev.span in
364 let builder = ref (array_map.dec_empty ()) in
365 let idx = ref 0 in
366 let rec loop () =
367 match peek_event d with
368 | Some { Event.event = Event.Sequence_end; span } ->
369 skip_event d;
370 let end_meta = meta_of_span d span in
371 array_map.dec_finish end_meta !idx !builder
372 | Some _ ->
373 let i = !idx in
374 (try
375 if array_map.dec_skip i !builder then begin
376 (* Skip this element by decoding as ignore *)
377 let _ : unit =
378 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore)
379 in
380 ()
381 end
382 else begin
383 let elt = decode d ~nest:(nest + 1) array_map.elt in
384 builder := array_map.dec_add i elt !builder
385 end
386 with Jsont.Error e ->
387 Jsont.Repr.error_push_array meta array_map (i, Jsont.Meta.none) e);
388 incr idx;
389 loop ()
390 | None -> err_msg meta "Unclosed sequence"
391 in
392 loop ()
393
394and decode_any_sequence : type a.
395 decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
396 fun d ~nest ev t map ->
397 match map.dec_array with
398 | Some t' -> (
399 (* The t' decoder might be wrapped (e.g., Map for option types)
400 Directly decode the array and let the wrapper handle it *)
401 match t' with
402 | Array array_map -> decode_array d ~nest ev array_map
403 | _ ->
404 (* For wrapped types like Map (Array ...), use full decode *)
405 decode d ~nest t')
406 | None ->
407 Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Array
408
409and decode_object : type o.
410 decoder -> nest:int -> Event.spanned -> (o, o) object_map -> o =
411 fun d ~nest start_ev map ->
412 skip_event d;
413 (* consume Mapping_start *)
414 check_nodes d;
415 let meta = meta_of_span d start_ev.span in
416 let dict =
417 decode_object_members d ~nest meta map String_map.empty Dict.empty
418 in
419 let dict = Dict.add object_meta_arg meta dict in
420 apply_dict map.dec dict
421
422and decode_object_members : type o.
423 decoder ->
424 nest:int ->
425 Jsont.Meta.t ->
426 (o, o) object_map ->
427 mem_dec String_map.t ->
428 Dict.t ->
429 Dict.t =
430 fun d ~nest obj_meta map mem_miss dict ->
431 (* Merge expected member decoders *)
432 let u _ _ _ = assert false in
433 let mem_miss = String_map.union u mem_miss map.mem_decs in
434 match map.shape with
435 | Object_basic umems ->
436 decode_object_basic d ~nest obj_meta map umems mem_miss dict
437 | Object_cases (umems_opt, cases) ->
438 (* Wrap umems_opt to hide existential types *)
439 let umems = Unknown_mems umems_opt in
440 decode_object_cases d ~nest obj_meta map umems cases mem_miss [] dict
441
442and decode_object_basic : type o mems builder.
443 decoder ->
444 nest:int ->
445 Jsont.Meta.t ->
446 (o, o) object_map ->
447 (o, mems, builder) unknown_mems ->
448 mem_dec String_map.t ->
449 Dict.t ->
450 Dict.t =
451 fun d ~nest obj_meta object_map umems mem_miss dict ->
452 let ubuilder =
453 ref
454 (match umems with
455 | Unknown_skip | Unknown_error -> Obj.magic ()
456 | Unknown_keep (mmap, _) -> mmap.dec_empty ())
457 in
458 let mem_miss = ref mem_miss in
459 let dict = ref dict in
460 let rec loop () =
461 match peek_event d with
462 | Some { Event.event = Event.Mapping_end; _ } ->
463 skip_event d;
464 (* Finalize *)
465 finish_object obj_meta object_map umems !ubuilder !mem_miss !dict
466 | Some ev ->
467 (* Expect a scalar key *)
468 let name, name_meta = decode_mapping_key d ev in
469 (* Look up member decoder *)
470 (match String_map.find_opt name object_map.mem_decs with
471 | Some (Mem_dec mem) -> (
472 mem_miss := String_map.remove name !mem_miss;
473 try
474 let v = decode d ~nest:(nest + 1) mem.type' in
475 dict := Dict.add mem.id v !dict
476 with Jsont.Error e ->
477 Jsont.Repr.error_push_object obj_meta object_map (name, name_meta)
478 e)
479 | None -> (
480 (* Unknown member *)
481 match umems with
482 | Unknown_skip ->
483 let _ : unit =
484 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore)
485 in
486 ()
487 | Unknown_error ->
488 Jsont.Repr.unexpected_mems_error obj_meta object_map
489 ~fnd:[ (name, name_meta) ]
490 | Unknown_keep (mmap, _) -> (
491 try
492 let v = decode d ~nest:(nest + 1) mmap.mems_type in
493 ubuilder := mmap.dec_add name_meta name v !ubuilder
494 with Jsont.Error e ->
495 Jsont.Repr.error_push_object obj_meta object_map
496 (name, name_meta) e)));
497 loop ()
498 | None -> err_msg obj_meta "Unclosed mapping"
499 in
500 loop ()
501
502and finish_object : type o mems builder.
503 Jsont.Meta.t ->
504 (o, o) object_map ->
505 (o, mems, builder) unknown_mems ->
506 builder ->
507 mem_dec String_map.t ->
508 Dict.t ->
509 Dict.t =
510 fun meta map umems ubuilder mem_miss dict ->
511 let open Jsont.Repr in
512 let dict = Dict.add object_meta_arg meta dict in
513 let dict =
514 match umems with
515 | Unknown_skip | Unknown_error -> dict
516 | Unknown_keep (mmap, _) ->
517 Dict.add mmap.id (mmap.dec_finish meta ubuilder) dict
518 in
519 (* Check for missing required members *)
520 let add_default _ (Mem_dec mem_map) dict =
521 match mem_map.dec_absent with
522 | Some v -> Dict.add mem_map.id v dict
523 | None -> raise Exit
524 in
525 try String_map.fold add_default mem_miss dict
526 with Exit ->
527 let no_default _ (Mem_dec mm) = Option.is_none mm.dec_absent in
528 let exp = String_map.filter no_default mem_miss in
529 missing_mems_error meta map ~exp ~fnd:[]
530
531and decode_object_cases : type o cases tag.
532 decoder ->
533 nest:int ->
534 Jsont.Meta.t ->
535 (o, o) object_map ->
536 unknown_mems_option ->
537 (o, cases, tag) object_cases ->
538 mem_dec String_map.t ->
539 (Jsont.name * Jsont.json) list ->
540 Dict.t ->
541 Dict.t =
542 fun d ~nest obj_meta object_map umems cases mem_miss delayed dict ->
543 match peek_event d with
544 | Some { Event.event = Event.Mapping_end; _ } -> (
545 skip_event d;
546 (* No tag found - use dec_absent if available *)
547 match cases.tag.dec_absent with
548 | Some tag ->
549 decode_with_case_tag d ~nest obj_meta object_map umems cases tag
550 mem_miss delayed dict
551 | None ->
552 (* Missing required case tag *)
553 let exp = String_map.singleton cases.tag.name (Mem_dec cases.tag) in
554 let fnd = List.map (fun ((n, _), _) -> n) delayed in
555 Jsont.Repr.missing_mems_error obj_meta object_map ~exp ~fnd)
556 | Some ev ->
557 let name, name_meta = decode_mapping_key d ev in
558 if String.equal name cases.tag.name then begin
559 (* Found the case tag *)
560 let tag = decode d ~nest:(nest + 1) cases.tag.type' in
561 decode_with_case_tag d ~nest obj_meta object_map umems cases tag
562 mem_miss delayed dict
563 end
564 else begin
565 (* Not the case tag - check if known member or delay *)
566 match String_map.find_opt name object_map.mem_decs with
567 | Some (Mem_dec mem) -> (
568 let mem_miss = String_map.remove name mem_miss in
569 try
570 let v = decode d ~nest:(nest + 1) mem.type' in
571 let dict = Dict.add mem.id v dict in
572 decode_object_cases d ~nest obj_meta object_map umems cases
573 mem_miss delayed dict
574 with Jsont.Error e ->
575 Jsont.Repr.error_push_object obj_meta object_map (name, name_meta)
576 e)
577 | None ->
578 (* Unknown member - decode as generic JSON and delay *)
579 let v =
580 decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.json)
581 in
582 let delayed = ((name, name_meta), v) :: delayed in
583 decode_object_cases d ~nest obj_meta object_map umems cases
584 mem_miss delayed dict
585 end
586 | None -> err_msg obj_meta "Unclosed mapping"
587
588and decode_with_case_tag : type o cases tag.
589 decoder ->
590 nest:int ->
591 Jsont.Meta.t ->
592 (o, o) object_map ->
593 unknown_mems_option ->
594 (o, cases, tag) object_cases ->
595 tag ->
596 mem_dec String_map.t ->
597 (Jsont.name * Jsont.json) list ->
598 Dict.t ->
599 Dict.t =
600 fun d ~nest obj_meta map umems cases tag mem_miss delayed dict ->
601 let open Jsont.Repr in
602 let eq_tag (Case c) = cases.tag_compare c.tag tag = 0 in
603 match List.find_opt eq_tag cases.cases with
604 | None -> unexpected_case_tag_error obj_meta map cases tag
605 | Some (Case case) ->
606 (* Continue decoding with the case's object map *)
607 let case_dict =
608 decode_case_remaining d ~nest obj_meta case.object_map umems mem_miss
609 delayed dict
610 in
611 let case_value = apply_dict case.object_map.dec case_dict in
612 Dict.add cases.id (case.dec case_value) dict
613
614and decode_case_remaining : type o.
615 decoder ->
616 nest:int ->
617 Jsont.Meta.t ->
618 (o, o) object_map ->
619 unknown_mems_option ->
620 mem_dec String_map.t ->
621 (Jsont.name * Jsont.json) list ->
622 Dict.t ->
623 Dict.t =
624 fun d ~nest obj_meta case_map _umems mem_miss delayed dict ->
625 (* First, process delayed members against the case map *)
626 let u _ _ _ = assert false in
627 let mem_miss = String_map.union u mem_miss case_map.mem_decs in
628 let dict, mem_miss =
629 List.fold_left
630 (fun (dict, mem_miss) ((name, meta), json_value) ->
631 match String_map.find_opt name case_map.mem_decs with
632 | Some (Mem_dec mem) -> (
633 let t' = Jsont.Repr.unsafe_to_t mem.type' in
634 match Jsont.Json.decode' t' json_value with
635 | Ok v ->
636 let dict = Dict.add mem.id v dict in
637 let mem_miss = String_map.remove name mem_miss in
638 (dict, mem_miss)
639 | Error e ->
640 Jsont.Repr.error_push_object obj_meta case_map (name, meta) e)
641 | None ->
642 (* Unknown for case too - skip them *)
643 (dict, mem_miss))
644 (dict, mem_miss) delayed
645 in
646 (* Then continue reading remaining members using case's own unknown handling *)
647 match case_map.shape with
648 | Object_basic case_umems ->
649 decode_object_basic d ~nest obj_meta case_map case_umems mem_miss dict
650 | Object_cases _ ->
651 (* Nested cases shouldn't happen - use skip for safety *)
652 decode_object_basic d ~nest obj_meta case_map Unknown_skip mem_miss dict
653
654and decode_any_mapping : type a.
655 decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
656 fun d ~nest ev t map ->
657 match map.dec_object with
658 | Some t' -> decode d ~nest t'
659 | None ->
660 Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Object
661
662and decode_mapping_key : decoder -> Event.spanned -> string * Jsont.Meta.t =
663 fun d ev ->
664 match ev.Event.event with
665 | Event.Scalar { value; _ } ->
666 skip_event d;
667 let meta = meta_of_span d ev.span in
668 (value, meta)
669 | _ ->
670 let meta = meta_of_span d ev.span in
671 err_msg meta "Mapping keys must be scalars (strings), found %a" Event.pp
672 ev.event
673
674(* Skip stream/document wrappers *)
675let skip_to_content d =
676 let rec loop () =
677 match peek_event d with
678 | Some { Event.event = Event.Stream_start _; _ } ->
679 skip_event d;
680 loop ()
681 | Some { Event.event = Event.Document_start _; _ } ->
682 skip_event d;
683 loop ()
684 | _ -> ()
685 in
686 loop ()
687
688let skip_end_wrappers d =
689 let rec loop () =
690 match peek_event d with
691 | Some { Event.event = Event.Document_end _; _ } ->
692 skip_event d;
693 loop ()
694 | Some { Event.event = Event.Stream_end; _ } ->
695 skip_event d;
696 loop ()
697 | None -> ()
698 | Some ev ->
699 let meta = meta_of_span d ev.span in
700 err_msg meta "Expected end of document but found %a" Event.pp ev.event
701 in
702 loop ()
703
704(* Skip to the end of the current document after an error *)
705let skip_to_document_end d =
706 let rec loop depth =
707 match peek_event d with
708 | None -> ()
709 | Some { Event.event = Event.Stream_end; _ } -> ()
710 | Some { Event.event = Event.Document_end _; _ } ->
711 skip_event d;
712 if depth = 0 then () else loop (depth - 1)
713 | Some { Event.event = Event.Document_start _; _ } ->
714 skip_event d;
715 loop (depth + 1)
716 | Some _ ->
717 skip_event d;
718 loop depth
719 in
720 loop 0
721
722(* Public decode API *)
723
724(* Decode all documents from a multi-document YAML stream *)
725let decode_all' ?(layout = false) ?(locs = false) ?(file = "-")
726 ?(max_depth = 100) ?(max_nodes = 10_000_000) t reader =
727 let parser = Parser.of_reader reader in
728 let d = make_decoder ~layout ~locs ~file ~max_depth ~max_nodes parser in
729 let t' = Jsont.Repr.of_t t in
730 let rec next_doc () =
731 match peek_event d with
732 | None -> Seq.Nil
733 | Some { Event.event = Event.Stream_end; _ } ->
734 skip_event d;
735 Seq.Nil
736 | Some _ -> (
737 try
738 skip_to_content d;
739 (* Reset node count for each document *)
740 d.node_count <- 0;
741 let v = decode d ~nest:0 t' in
742 (* Skip document end marker if present *)
743 (match peek_event d with
744 | Some { Event.event = Event.Document_end _; _ } -> skip_event d
745 | _ -> ());
746 Seq.Cons (Ok v, next_doc)
747 with
748 | Jsont.Error e ->
749 skip_to_document_end d;
750 Seq.Cons (Error e, next_doc)
751 | Error.Yamlrw_error err ->
752 skip_to_document_end d;
753 let msg = Error.to_string err in
754 let e = Jsont.(Error.make_msg Error.Context.empty Meta.none msg) in
755 Seq.Cons (Error e, next_doc))
756 in
757 next_doc
758
759let decode_all ?layout ?locs ?file ?max_depth ?max_nodes t reader =
760 decode_all' ?layout ?locs ?file ?max_depth ?max_nodes t reader
761 |> Seq.map (Result.map_error Jsont.Error.to_string)
762
763let decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader =
764 let parser = Parser.of_reader reader in
765 let d = make_decoder ?layout ?locs ?file ?max_depth ?max_nodes parser in
766 try
767 skip_to_content d;
768 let t' = Jsont.Repr.of_t t in
769 let v = decode d ~nest:0 t' in
770 skip_end_wrappers d;
771 Ok v
772 with
773 | Jsont.Error e -> Error e
774 | Error.Yamlrw_error err ->
775 let msg = Error.to_string err in
776 Error Jsont.(Error.make_msg Error.Context.empty Meta.none msg)
777
778let decode ?layout ?locs ?file ?max_depth ?max_nodes t reader =
779 Result.map_error Jsont.Error.to_string
780 (decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader)
781
782(* Encoder *)
783
784type encoder = {
785 emitter : Emitter.t;
786 format : yaml_format;
787 _indent : int; (* Stored for future use in custom formatting *)
788 explicit_doc : bool;
789 scalar_style : Scalar_style.t;
790}
791
792let make_encoder ?(format = Block) ?(indent = 2) ?(explicit_doc = false)
793 ?(scalar_style = `Any) emitter =
794 { emitter; format; _indent = indent; explicit_doc; scalar_style }
795
796let layout_style_of_format = function
797 | Block -> `Block
798 | Flow -> `Flow
799 | Layout -> `Any
800
801(* Choose appropriate scalar style for a string *)
802let choose_scalar_style ~preferred s =
803 if preferred <> `Any then preferred
804 else if String.contains s '\n' then `Literal
805 else if String.length s > 80 then `Folded
806 else `Plain
807
808(* Helper to create scalar events with common defaults *)
809let scalar_event ?(anchor = None) ?(tag = None) ~value ~style () =
810 Event.Scalar
811 {
812 anchor;
813 tag;
814 value;
815 plain_implicit = true;
816 quoted_implicit = true;
817 style;
818 }
819
820(* Helper to emit events *)
821let emit e = Emitter.emit e.emitter
822
823(* Encode null *)
824let encode_null e _meta = emit e (scalar_event ~value:"null" ~style:`Plain ())
825
826(* Encode boolean *)
827let encode_bool e _meta b =
828 emit e (scalar_event ~value:(if b then "true" else "false") ~style:`Plain ())
829
830(* Encode number *)
831let encode_number e _meta f =
832 let value =
833 match Float.classify_float f with
834 | FP_nan -> ".nan"
835 | FP_infinite -> if f > 0.0 then ".inf" else "-.inf"
836 | _ ->
837 if Float.is_integer f && Float.abs f < 1e15 then Printf.sprintf "%.0f" f
838 else Printf.sprintf "%g" f
839 in
840 emit e (scalar_event ~value ~style:`Plain ())
841
842(* Encode string *)
843let encode_string e _meta s =
844 let style = choose_scalar_style ~preferred:e.scalar_style s in
845 emit e (scalar_event ~value:s ~style ())
846
847let rec encode : type a. encoder -> a t -> a -> unit =
848 fun e t v ->
849 match t with
850 | Null map ->
851 let meta = map.enc_meta v in
852 let () = map.enc v in
853 encode_null e meta
854 | Bool map ->
855 let meta = map.enc_meta v in
856 let b = map.enc v in
857 encode_bool e meta b
858 | Number map ->
859 let meta = map.enc_meta v in
860 let f = map.enc v in
861 encode_number e meta f
862 | String map ->
863 let meta = map.enc_meta v in
864 let s = map.enc v in
865 encode_string e meta s
866 | Array map -> encode_array e map v
867 | Object map -> encode_object e map v
868 | Any map ->
869 let t' = map.enc v in
870 encode e t' v
871 | Map m -> encode e m.dom (m.enc v)
872 | Rec lazy_t -> encode e (Lazy.force lazy_t) v
873
874and encode_array : type a elt b. encoder -> (a, elt, b) array_map -> a -> unit =
875 fun e map v ->
876 let style = layout_style_of_format e.format in
877 emit e
878 (Event.Sequence_start { anchor = None; tag = None; implicit = true; style });
879 let _ =
880 map.enc
881 (fun () _idx elt ->
882 encode e map.elt elt;
883 ())
884 () v
885 in
886 emit e Event.Sequence_end
887
888and encode_object : type o. encoder -> (o, o) object_map -> o -> unit =
889 fun e map v ->
890 let style = layout_style_of_format e.format in
891 emit e (Event.Mapping_start { anchor = None; tag = None; implicit = true; style });
892 (* Encode each member *)
893 List.iter
894 (fun (Mem_enc mem) ->
895 let mem_v = mem.enc v in
896 if not (mem.enc_omit mem_v) then begin
897 (* Emit key *)
898 emit e (scalar_event ~value:mem.name ~style:`Plain ());
899 (* Emit value *)
900 encode e mem.type' mem_v
901 end)
902 map.mem_encs;
903 (* Handle case objects *)
904 (match map.shape with
905 | Object_basic _ -> ()
906 | Object_cases (_, cases) ->
907 let (Case_value (case_map, case_v)) = cases.enc_case (cases.enc v) in
908 (* Emit case tag *)
909 if not (cases.tag.enc_omit case_map.tag) then begin
910 emit e (scalar_event ~value:cases.tag.name ~style:`Plain ());
911 encode e cases.tag.type' case_map.tag
912 end;
913 (* Emit case members *)
914 List.iter
915 (fun (Mem_enc mem) ->
916 let mem_v = mem.enc case_v in
917 if not (mem.enc_omit mem_v) then begin
918 emit e (scalar_event ~value:mem.name ~style:`Plain ());
919 encode e mem.type' mem_v
920 end)
921 case_map.object_map.mem_encs);
922 emit e Event.Mapping_end
923
924(* Public encode API *)
925
926let encode' ?buf:_ ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer =
927 let config =
928 {
929 Emitter.default_config with
930 indent = Option.value ~default:2 indent;
931 layout_style = (match format with Some Flow -> `Flow | _ -> `Block);
932 }
933 in
934 let emitter = Emitter.of_writer ~config writer in
935 let e = make_encoder ?format ?indent ?explicit_doc ?scalar_style emitter in
936 try
937 emit e (Event.Stream_start { encoding = `Utf8 });
938 emit e (Event.Document_start { version = None; implicit = not e.explicit_doc });
939 let t' = Jsont.Repr.of_t t in
940 encode e t' v;
941 emit e (Event.Document_end { implicit = not e.explicit_doc });
942 emit e Event.Stream_end;
943 if eod then Emitter.flush e.emitter;
944 Ok ()
945 with
946 | Jsont.Error err -> Error err
947 | Error.Yamlrw_error err ->
948 let msg = Error.to_string err in
949 Error Jsont.(Error.make_msg Error.Context.empty Meta.none msg)
950
951let encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer =
952 Result.map_error Jsont.Error.to_string
953 (encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer)
954
955(* Recode *)
956
957let recode ?layout ?locs ?file ?max_depth ?max_nodes ?buf ?format ?indent
958 ?explicit_doc ?scalar_style t reader writer ~eod =
959 let format =
960 match (layout, format) with Some true, None -> Some Layout | _, f -> f
961 in
962 let layout =
963 match (layout, format) with None, Some Layout -> Some true | l, _ -> l
964 in
965 match decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader with
966 | Ok v ->
967 encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer
968 | Error e -> Error (Jsont.Error.to_string e)