Pure OCaml Yaml 1.2 reader and writer using Bytesrw
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Loader - converts parser events to YAML data structures *)
7
8(** Stack frame for building nested structures *)
9type frame =
10 | Sequence_frame of {
11 anchor : string option;
12 tag : string option;
13 implicit : bool;
14 style : Layout_style.t;
15 items : Yaml.t list;
16 }
17 | Mapping_frame of {
18 anchor : string option;
19 tag : string option;
20 implicit : bool;
21 style : Layout_style.t;
22 pairs : (Yaml.t * Yaml.t) list;
23 pending_key : Yaml.t option;
24 }
25
26type state = {
27 mutable stack : frame list;
28 mutable current : Yaml.t option;
29 mutable documents : Document.t list;
30 mutable doc_version : (int * int) option;
31 mutable doc_implicit_start : bool;
32}
33
34let create_state () = {
35 stack = [];
36 current = None;
37 documents = [];
38 doc_version = None;
39 doc_implicit_start = true;
40}
41
42(** Process a single event *)
43let rec process_event state (ev : Event.spanned) =
44 match ev.event with
45 | Event.Stream_start _ -> ()
46
47 | Event.Stream_end -> ()
48
49 | Event.Document_start { version; implicit } ->
50 state.doc_version <- version;
51 state.doc_implicit_start <- implicit
52
53 | Event.Document_end { implicit } ->
54 let doc = Document.make
55 ?version:state.doc_version
56 ~implicit_start:state.doc_implicit_start
57 ~implicit_end:implicit
58 state.current
59 in
60 state.documents <- doc :: state.documents;
61 state.current <- None;
62 state.doc_version <- None;
63 state.doc_implicit_start <- true
64
65 | Event.Alias { anchor } ->
66 let node : Yaml.t = `Alias anchor in
67 add_node state node
68
69 | Event.Scalar { anchor; tag; value; plain_implicit; quoted_implicit; style } ->
70 let scalar = Scalar.make
71 ?anchor ?tag
72 ~plain_implicit ~quoted_implicit
73 ~style value
74 in
75 let node : Yaml.t = `Scalar scalar in
76 add_node state node
77
78 | Event.Sequence_start { anchor; tag; implicit; style } ->
79 let frame = Sequence_frame {
80 anchor; tag; implicit; style;
81 items = [];
82 } in
83 state.stack <- frame :: state.stack
84
85 | Event.Sequence_end ->
86 (match state.stack with
87 | Sequence_frame { anchor; tag; implicit; style; items } :: rest ->
88 let seq = Sequence.make ?anchor ?tag ~implicit ~style (List.rev items) in
89 let node : Yaml.t = `A seq in
90 state.stack <- rest;
91 add_node state node
92 | _ -> Error.raise (Invalid_state "unexpected sequence end"))
93
94 | Event.Mapping_start { anchor; tag; implicit; style } ->
95 let frame = Mapping_frame {
96 anchor; tag; implicit; style;
97 pairs = [];
98 pending_key = None;
99 } in
100 state.stack <- frame :: state.stack
101
102 | Event.Mapping_end ->
103 (match state.stack with
104 | Mapping_frame { anchor; tag; implicit; style; pairs; pending_key = None } :: rest ->
105 let map = Mapping.make ?anchor ?tag ~implicit ~style (List.rev pairs) in
106 let node : Yaml.t = `O map in
107 state.stack <- rest;
108 add_node state node
109 | Mapping_frame { pending_key = Some _; _ } :: _ ->
110 Error.raise (Invalid_state "mapping ended with pending key")
111 | _ -> Error.raise (Invalid_state "unexpected mapping end"))
112
113(** Add a node to current context *)
114and add_node state node =
115 match state.stack with
116 | [] ->
117 state.current <- Some node
118
119 | Sequence_frame f :: rest ->
120 state.stack <- Sequence_frame { f with items = node :: f.items } :: rest
121
122 | Mapping_frame f :: rest ->
123 (match f.pending_key with
124 | None ->
125 (* This is a key *)
126 state.stack <- Mapping_frame { f with pending_key = Some node } :: rest
127 | Some key ->
128 (* This is a value *)
129 state.stack <- Mapping_frame {
130 f with
131 pairs = (key, node) :: f.pairs;
132 pending_key = None;
133 } :: rest)
134
135(** Internal: parse all documents from a parser *)
136let parse_all_documents parser =
137 let state = create_state () in
138 Parser.iter (process_event state) parser;
139 List.rev state.documents
140
141(** Internal: extract single document or raise *)
142let single_document_or_error docs ~empty =
143 match docs with
144 | [] -> empty
145 | [doc] -> doc
146 | _ -> Error.raise Multiple_documents
147
148(** Load single document as Value.
149
150 @param resolve_aliases Whether to resolve aliases (default true)
151 @param max_nodes Maximum nodes during alias expansion (default 10M)
152 @param max_depth Maximum alias nesting depth (default 100)
153*)
154let value_of_string
155 ?(resolve_aliases = true)
156 ?(max_nodes = Yaml.default_max_alias_nodes)
157 ?(max_depth = Yaml.default_max_alias_depth)
158 s =
159 let docs = parse_all_documents (Parser.of_string s) in
160 let doc = single_document_or_error docs ~empty:(Document.make None) in
161 match Document.root doc with
162 | None -> `Null
163 | Some yaml ->
164 Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
165
166(** Load single document as Yaml.
167
168 @param resolve_aliases Whether to resolve aliases (default false for Yaml.t)
169 @param max_nodes Maximum nodes during alias expansion (default 10M)
170 @param max_depth Maximum alias nesting depth (default 100)
171*)
172let yaml_of_string
173 ?(resolve_aliases = false)
174 ?(max_nodes = Yaml.default_max_alias_nodes)
175 ?(max_depth = Yaml.default_max_alias_depth)
176 s =
177 let docs = parse_all_documents (Parser.of_string s) in
178 let doc = single_document_or_error docs ~empty:(Document.make None) in
179 match Document.root doc with
180 | None -> `Scalar (Scalar.make "")
181 | Some yaml ->
182 if resolve_aliases then
183 Yaml.resolve_aliases ~max_nodes ~max_depth yaml
184 else
185 yaml
186
187(** Load all documents *)
188let documents_of_string s =
189 parse_all_documents (Parser.of_string s)
190
191(** {2 Reader-based loading} *)
192
193(** Load single document as Value from a Bytes.Reader.
194
195 @param resolve_aliases Whether to resolve aliases (default true)
196 @param max_nodes Maximum nodes during alias expansion (default 10M)
197 @param max_depth Maximum alias nesting depth (default 100)
198*)
199let value_of_reader
200 ?(resolve_aliases = true)
201 ?(max_nodes = Yaml.default_max_alias_nodes)
202 ?(max_depth = Yaml.default_max_alias_depth)
203 reader =
204 let docs = parse_all_documents (Parser.of_reader reader) in
205 let doc = single_document_or_error docs ~empty:(Document.make None) in
206 match Document.root doc with
207 | None -> `Null
208 | Some yaml ->
209 Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
210
211(** Load single document as Yaml from a Bytes.Reader.
212
213 @param resolve_aliases Whether to resolve aliases (default false for Yaml.t)
214 @param max_nodes Maximum nodes during alias expansion (default 10M)
215 @param max_depth Maximum alias nesting depth (default 100)
216*)
217let yaml_of_reader
218 ?(resolve_aliases = false)
219 ?(max_nodes = Yaml.default_max_alias_nodes)
220 ?(max_depth = Yaml.default_max_alias_depth)
221 reader =
222 let docs = parse_all_documents (Parser.of_reader reader) in
223 let doc = single_document_or_error docs ~empty:(Document.make None) in
224 match Document.root doc with
225 | None -> `Scalar (Scalar.make "")
226 | Some yaml ->
227 if resolve_aliases then
228 Yaml.resolve_aliases ~max_nodes ~max_depth yaml
229 else
230 yaml
231
232(** Load all documents from a Bytes.Reader *)
233let documents_of_reader reader =
234 parse_all_documents (Parser.of_reader reader)
235
236(** {2 Parser-function based loading}
237
238 These functions accept a [unit -> Event.spanned option] function
239 instead of a [Parser.t], allowing them to work with any event source
240 (e.g., streaming parsers). *)
241
242(** Generic document loader using event source function *)
243let load_generic_fn extract next_event =
244 let state = create_state () in
245 let rec loop () =
246 match next_event () with
247 | None -> None
248 | Some ev ->
249 process_event state ev;
250 match ev.event with
251 | Event.Document_end _ ->
252 (match state.documents with
253 | doc :: _ ->
254 state.documents <- [];
255 Some (extract doc)
256 | [] -> None)
257 | Event.Stream_end -> None
258 | _ -> loop ()
259 in
260 loop ()
261
262(** Generic document loader - extracts common pattern from load_* functions *)
263let load_generic extract parser =
264 load_generic_fn extract (fun () -> Parser.next parser)
265
266(** Load single Value from parser.
267
268 @param resolve_aliases Whether to resolve aliases (default true)
269 @param max_nodes Maximum nodes during alias expansion (default 10M)
270 @param max_depth Maximum alias nesting depth (default 100)
271*)
272let load_value
273 ?(resolve_aliases = true)
274 ?(max_nodes = Yaml.default_max_alias_nodes)
275 ?(max_depth = Yaml.default_max_alias_depth)
276 parser =
277 load_generic (fun doc ->
278 match Document.root doc with
279 | None -> `Null
280 | Some yaml ->
281 Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
282 ) parser
283
284(** Load single Yaml from parser *)
285let load_yaml parser =
286 load_generic (fun doc ->
287 Document.root doc |> Option.value ~default:(`Scalar (Scalar.make ""))
288 ) parser
289
290(** Load single Document from parser *)
291let load_document parser =
292 load_generic Fun.id parser
293
294(** Iterate over documents *)
295let iter_documents f parser =
296 let rec loop () =
297 match load_document parser with
298 | None -> ()
299 | Some doc -> f doc; loop ()
300 in
301 loop ()
302
303(** Fold over documents *)
304let fold_documents f init parser =
305 let rec loop acc =
306 match load_document parser with
307 | None -> acc
308 | Some doc -> loop (f acc doc)
309 in
310 loop init
311
312(** Load single Value from event source.
313
314 @param resolve_aliases Whether to resolve aliases (default true)
315 @param max_nodes Maximum nodes during alias expansion (default 10M)
316 @param max_depth Maximum alias nesting depth (default 100)
317*)
318let value_of_parser
319 ?(resolve_aliases = true)
320 ?(max_nodes = Yaml.default_max_alias_nodes)
321 ?(max_depth = Yaml.default_max_alias_depth)
322 next_event =
323 match load_generic_fn (fun doc ->
324 match Document.root doc with
325 | None -> `Null
326 | Some yaml ->
327 Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
328 ) next_event with
329 | Some v -> v
330 | None -> `Null
331
332(** Load single Yaml from event source.
333
334 @param resolve_aliases Whether to resolve aliases (default false)
335 @param max_nodes Maximum nodes during alias expansion (default 10M)
336 @param max_depth Maximum alias nesting depth (default 100)
337*)
338let yaml_of_parser
339 ?(resolve_aliases = false)
340 ?(max_nodes = Yaml.default_max_alias_nodes)
341 ?(max_depth = Yaml.default_max_alias_depth)
342 next_event =
343 match load_generic_fn (fun doc ->
344 match Document.root doc with
345 | None -> `Scalar (Scalar.make "")
346 | Some yaml ->
347 if resolve_aliases then
348 Yaml.resolve_aliases ~max_nodes ~max_depth yaml
349 else
350 yaml
351 ) next_event with
352 | Some v -> v
353 | None -> `Scalar (Scalar.make "")
354
355(** Load single Document from event source *)
356let document_of_parser next_event =
357 load_generic_fn Fun.id next_event
358
359(** Load all documents from event source *)
360let documents_of_parser next_event =
361 let state = create_state () in
362 let rec loop () =
363 match next_event () with
364 | None -> List.rev state.documents
365 | Some ev ->
366 process_event state ev;
367 loop ()
368 in
369 loop ()
370
371(** Iterate over documents from event source *)
372let iter_documents_parser f next_event =
373 let rec loop () =
374 match document_of_parser next_event with
375 | None -> ()
376 | Some doc -> f doc; loop ()
377 in
378 loop ()
379
380(** Fold over documents from event source *)
381let fold_documents_parser f init next_event =
382 let rec loop acc =
383 match document_of_parser next_event with
384 | None -> acc
385 | Some doc -> loop (f acc doc)
386 in
387 loop init