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(** YAML parser - converts tokens to semantic events via state machine *)
7
8(** Parser states *)
9type state =
10 | Stream_start
11 | Implicit_document_start
12 | Document_start
13 | Document_content
14 | Document_content_done (* After parsing a node, check for unexpected content *)
15 | Document_end
16 | Block_node
17 | Block_node_or_indentless_sequence
18 | Flow_node
19 | Block_sequence_first_entry
20 | Block_sequence_entry
21 | Indentless_sequence_entry
22 | Block_mapping_first_key
23 | Block_mapping_key
24 | Block_mapping_value
25 | Flow_sequence_first_entry
26 | Flow_sequence_entry
27 | Flow_sequence_entry_mapping_key
28 | Flow_sequence_entry_mapping_value
29 | Flow_sequence_entry_mapping_end
30 | Flow_mapping_first_key
31 | Flow_mapping_key
32 | Flow_mapping_value
33 | Flow_mapping_empty_value
34 | End
35
36type t = {
37 scanner : Scanner.t;
38 mutable state : state;
39 mutable states : state list; (** State stack *)
40 mutable marks : Span.t list; (** Mark stack for span tracking *)
41 mutable version : (int * int) option;
42 mutable tag_directives : (string * string) list;
43 mutable current_token : Token.spanned option;
44 mutable finished : bool;
45 mutable explicit_doc_end : bool; (** True if last doc ended with explicit ... *)
46 mutable stream_start : bool; (** True if we haven't emitted any documents yet *)
47}
48
49let create scanner = {
50 scanner;
51 state = Stream_start;
52 states = [];
53 marks = [];
54 version = None;
55 tag_directives = [
56 ("!", "!");
57 ("!!", "tag:yaml.org,2002:");
58 ];
59 current_token = None;
60 finished = false;
61 explicit_doc_end = false;
62 stream_start = true;
63}
64
65let of_string s = create (Scanner.of_string s)
66let of_scanner = create
67let of_input i = create (Scanner.of_input i)
68let of_reader r = create (Scanner.of_reader r)
69
70(** Get current token, fetching if needed *)
71let current_token t =
72 match t.current_token with
73 | Some tok -> tok
74 | None ->
75 let tok = Scanner.next t.scanner in
76 t.current_token <- tok;
77 match tok with
78 | Some tok -> tok
79 | None -> Error.raise Unexpected_eof
80
81(** Peek at current token *)
82let peek_token t =
83 match t.current_token with
84 | Some _ -> t.current_token
85 | None ->
86 t.current_token <- Scanner.next t.scanner;
87 t.current_token
88
89(** Skip current token *)
90let skip_token t =
91 t.current_token <- None
92
93(** Check if current token matches *)
94let check t pred =
95 match peek_token t with
96 | Some tok -> pred tok.token
97 | None -> false
98
99(** Check for specific token *)
100let check_token t token_match =
101 check t token_match
102
103(** Push state onto stack *)
104let push_state t s =
105 t.states <- s :: t.states
106
107(** Pop state from stack *)
108let pop_state t =
109 match t.states with
110 | s :: rest ->
111 t.states <- rest;
112 s
113 | [] -> End
114
115(** Resolve a tag *)
116let resolve_tag t ~handle ~suffix =
117 if handle = "" then
118 (* Verbatim tag - suffix is already the full URI *)
119 suffix
120 else
121 match List.assoc_opt handle t.tag_directives with
122 | Some prefix -> prefix ^ suffix
123 | None when handle = "!" -> "!" ^ suffix
124 | None -> Error.raise (Invalid_tag (handle ^ suffix))
125
126(** Process directives at document start *)
127let process_directives t =
128 t.version <- None;
129 t.tag_directives <- [("!", "!"); ("!!", "tag:yaml.org,2002:")];
130
131 while check t (function
132 | Token.Version_directive _ | Token.Tag_directive _ -> true
133 | _ -> false)
134 do
135 let tok = current_token t in
136 skip_token t;
137 match tok.token with
138 | Token.Version_directive { major; minor } ->
139 if t.version <> None then
140 Error.raise_span tok.span (Invalid_yaml_version "duplicate YAML directive");
141 t.version <- Some (major, minor)
142 | Token.Tag_directive { handle; prefix } ->
143 (* Skip empty tag directives (these are reserved/unknown directives that were ignored) *)
144 if handle = "" && prefix = "" then
145 () (* Ignore reserved directives *)
146 else begin
147 if List.mem_assoc handle t.tag_directives &&
148 handle <> "!" && handle <> "!!" then
149 Error.raise_span tok.span (Invalid_tag_directive ("duplicate tag handle: " ^ handle));
150 t.tag_directives <- (handle, prefix) :: t.tag_directives
151 end
152 | _ -> ()
153 done
154
155(** Parse anchor and/or tag properties *)
156let parse_properties t =
157 let anchor = ref None in
158 let tag = ref None in
159
160 while check t (function
161 | Token.Anchor _ | Token.Tag _ -> true
162 | _ -> false)
163 do
164 let tok = current_token t in
165 skip_token t;
166 match tok.token with
167 | Token.Anchor name ->
168 if Option.is_some !anchor then
169 Error.raise_span tok.span (Duplicate_anchor name);
170 anchor := Some name
171 | Token.Tag { handle; suffix } ->
172 if Option.is_some !tag then
173 Error.raise_span tok.span (Invalid_tag "duplicate tag");
174 let resolved =
175 if handle = "" && suffix = "" then None
176 else if handle = "!" && suffix = "" then Some "!"
177 else Some (resolve_tag t ~handle ~suffix)
178 in
179 tag := resolved
180 | _ -> ()
181 done;
182 (!anchor, !tag)
183
184(** Empty scalar event *)
185let empty_scalar_event ~anchor ~tag span =
186 Event.Scalar {
187 anchor;
188 tag;
189 value = "";
190 plain_implicit = tag = None;
191 quoted_implicit = false;
192 style = `Plain;
193 }, span
194
195(** Parse stream start *)
196let parse_stream_start t =
197 let tok = current_token t in
198 skip_token t;
199 match tok.token with
200 | Token.Stream_start encoding ->
201 t.state <- Implicit_document_start;
202 Event.Stream_start { encoding }, tok.span
203 | _ ->
204 Error.raise_span tok.span (Unexpected_token "expected stream start")
205
206(** Parse document start (implicit or explicit) *)
207let parse_document_start t ~implicit =
208 process_directives t;
209
210 if not implicit then begin
211 let tok = current_token t in
212 match tok.token with
213 | Token.Document_start ->
214 skip_token t
215 | _ ->
216 Error.raise_span tok.span Expected_document_start
217 end;
218
219 let span = match peek_token t with
220 | Some tok -> tok.span
221 | None -> Span.point Position.initial
222 in
223
224 (* After first document, stream_start is false *)
225 t.stream_start <- false;
226 push_state t Document_end;
227 t.state <- Document_content;
228 Event.Document_start { version = t.version; implicit }, span
229
230(** Parse document end *)
231let parse_document_end t =
232 let implicit = not (check t (function Token.Document_end -> true | _ -> false)) in
233 let span = match peek_token t with
234 | Some tok -> tok.span
235 | None -> Span.point Position.initial
236 in
237
238 if not implicit then skip_token t;
239
240 (* Track if this document ended explicitly with ... *)
241 t.explicit_doc_end <- not implicit;
242 t.state <- Implicit_document_start;
243 Event.Document_end { implicit }, span
244
245(** Parse node in various contexts *)
246let parse_node t ~block ~indentless =
247 let tok = current_token t in
248 match tok.token with
249 | Token.Alias name ->
250 skip_token t;
251 t.state <- pop_state t;
252 Event.Alias { anchor = name }, tok.span
253
254 | Token.Anchor _ | Token.Tag _ ->
255 let anchor, tag = parse_properties t in
256 let tok = current_token t in
257 (match tok.token with
258 | Token.Block_entry when indentless ->
259 t.state <- Indentless_sequence_entry;
260 Event.Sequence_start {
261 anchor; tag;
262 implicit = tag = None;
263 style = `Block;
264 }, tok.span
265
266 | Token.Block_sequence_start when block ->
267 t.state <- Block_sequence_first_entry;
268 skip_token t;
269 Event.Sequence_start {
270 anchor; tag;
271 implicit = tag = None;
272 style = `Block;
273 }, tok.span
274
275 | Token.Block_mapping_start when block ->
276 t.state <- Block_mapping_first_key;
277 skip_token t;
278 Event.Mapping_start {
279 anchor; tag;
280 implicit = tag = None;
281 style = `Block;
282 }, tok.span
283
284 | Token.Flow_sequence_start ->
285 t.state <- Flow_sequence_first_entry;
286 skip_token t;
287 Event.Sequence_start {
288 anchor; tag;
289 implicit = tag = None;
290 style = `Flow;
291 }, tok.span
292
293 | Token.Flow_mapping_start ->
294 t.state <- Flow_mapping_first_key;
295 skip_token t;
296 Event.Mapping_start {
297 anchor; tag;
298 implicit = tag = None;
299 style = `Flow;
300 }, tok.span
301
302 | Token.Scalar { style; value } ->
303 skip_token t;
304 t.state <- pop_state t;
305 let plain_implicit = tag = None && style = `Plain in
306 let quoted_implicit = tag = None && style <> `Plain in
307 Event.Scalar {
308 anchor; tag; value;
309 plain_implicit; quoted_implicit; style;
310 }, tok.span
311
312 | _ ->
313 (* Empty node *)
314 t.state <- pop_state t;
315 empty_scalar_event ~anchor ~tag tok.span)
316
317 | Token.Block_sequence_start when block ->
318 t.state <- Block_sequence_first_entry;
319 skip_token t;
320 Event.Sequence_start {
321 anchor = None; tag = None;
322 implicit = true;
323 style = `Block;
324 }, tok.span
325
326 | Token.Block_mapping_start when block ->
327 t.state <- Block_mapping_first_key;
328 skip_token t;
329 Event.Mapping_start {
330 anchor = None; tag = None;
331 implicit = true;
332 style = `Block;
333 }, tok.span
334
335 | Token.Flow_sequence_start ->
336 t.state <- Flow_sequence_first_entry;
337 skip_token t;
338 Event.Sequence_start {
339 anchor = None; tag = None;
340 implicit = true;
341 style = `Flow;
342 }, tok.span
343
344 | Token.Flow_mapping_start ->
345 t.state <- Flow_mapping_first_key;
346 skip_token t;
347 Event.Mapping_start {
348 anchor = None; tag = None;
349 implicit = true;
350 style = `Flow;
351 }, tok.span
352
353 | Token.Block_entry when indentless ->
354 t.state <- Indentless_sequence_entry;
355 Event.Sequence_start {
356 anchor = None; tag = None;
357 implicit = true;
358 style = `Block;
359 }, tok.span
360
361 | Token.Scalar { style; value } ->
362 skip_token t;
363 t.state <- pop_state t;
364 let plain_implicit = style = `Plain in
365 let quoted_implicit = style <> `Plain in
366 Event.Scalar {
367 anchor = None; tag = None; value;
368 plain_implicit; quoted_implicit; style;
369 }, tok.span
370
371 | _ ->
372 (* Empty node *)
373 t.state <- pop_state t;
374 empty_scalar_event ~anchor:None ~tag:None tok.span
375
376(** Parse block sequence entry *)
377let parse_block_sequence_entry t =
378 let tok = current_token t in
379 match tok.token with
380 | Token.Block_entry ->
381 skip_token t;
382 if check t (function
383 | Token.Block_entry | Token.Block_end -> true
384 | _ -> false)
385 then begin
386 t.state <- Block_sequence_entry;
387 empty_scalar_event ~anchor:None ~tag:None tok.span
388 end else begin
389 push_state t Block_sequence_entry;
390 parse_node t ~block:true ~indentless:false
391 end
392 | Token.Block_end ->
393 skip_token t;
394 t.state <- pop_state t;
395 Event.Sequence_end, tok.span
396 | _ ->
397 Error.raise_span tok.span Expected_block_entry
398
399(** Parse block mapping key *)
400let parse_block_mapping_key t =
401 let tok = current_token t in
402 match tok.token with
403 | Token.Key ->
404 skip_token t;
405 if check t (function
406 | Token.Key | Token.Value | Token.Block_end -> true
407 | _ -> false)
408 then begin
409 t.state <- Block_mapping_value;
410 empty_scalar_event ~anchor:None ~tag:None tok.span
411 end else begin
412 push_state t Block_mapping_value;
413 parse_node t ~block:true ~indentless:true
414 end
415 (* Handle value without explicit key - key is empty/null *)
416 | Token.Value ->
417 t.state <- Block_mapping_value;
418 empty_scalar_event ~anchor:None ~tag:None tok.span
419 | Token.Block_end ->
420 skip_token t;
421 t.state <- pop_state t;
422 Event.Mapping_end, tok.span
423 | _ ->
424 Error.raise_span tok.span Expected_key
425
426(** Parse block mapping value *)
427let parse_block_mapping_value t =
428 let tok = current_token t in
429 match tok.token with
430 | Token.Value ->
431 skip_token t;
432 if check t (function
433 | Token.Key | Token.Value | Token.Block_end -> true
434 | _ -> false)
435 then begin
436 t.state <- Block_mapping_key;
437 empty_scalar_event ~anchor:None ~tag:None tok.span
438 end else begin
439 push_state t Block_mapping_key;
440 parse_node t ~block:true ~indentless:true
441 end
442 | _ ->
443 (* Implicit empty value *)
444 t.state <- Block_mapping_key;
445 empty_scalar_event ~anchor:None ~tag:None tok.span
446
447(** Parse indentless sequence entry *)
448let parse_indentless_sequence_entry t =
449 let tok = current_token t in
450 match tok.token with
451 | Token.Block_entry ->
452 skip_token t;
453 if check t (function
454 | Token.Block_entry | Token.Key | Token.Value | Token.Block_end -> true
455 | _ -> false)
456 then begin
457 t.state <- Indentless_sequence_entry;
458 empty_scalar_event ~anchor:None ~tag:None tok.span
459 end else begin
460 push_state t Indentless_sequence_entry;
461 parse_node t ~block:true ~indentless:false
462 end
463 | _ ->
464 t.state <- pop_state t;
465 Event.Sequence_end, tok.span
466
467(** Parse flow sequence *)
468let rec parse_flow_sequence_entry t ~first =
469 let tok = current_token t in
470 match tok.token with
471 | Token.Flow_sequence_end ->
472 skip_token t;
473 t.state <- pop_state t;
474 Event.Sequence_end, tok.span
475 | Token.Flow_entry when not first ->
476 skip_token t;
477 parse_flow_sequence_entry_internal t
478 | _ when first ->
479 parse_flow_sequence_entry_internal t
480 | _ ->
481 Error.raise_span tok.span Expected_sequence_end
482
483and parse_flow_sequence_entry_internal t =
484 let tok = current_token t in
485 match tok.token with
486 | Token.Flow_sequence_end ->
487 (* Trailing comma case - don't emit empty scalar, just go back to sequence entry state *)
488 skip_token t;
489 t.state <- pop_state t;
490 Event.Sequence_end, tok.span
491 | Token.Flow_entry ->
492 (* Double comma or comma after comma - invalid *)
493 Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow sequence")
494 | Token.Key ->
495 skip_token t;
496 t.state <- Flow_sequence_entry_mapping_key;
497 Event.Mapping_start {
498 anchor = None; tag = None;
499 implicit = true;
500 style = `Flow;
501 }, tok.span
502 | Token.Value ->
503 (* Implicit empty key mapping: [ : value ] *)
504 t.state <- Flow_sequence_entry_mapping_key;
505 Event.Mapping_start {
506 anchor = None; tag = None;
507 implicit = true;
508 style = `Flow;
509 }, tok.span
510 | _ ->
511 push_state t Flow_sequence_entry;
512 parse_node t ~block:false ~indentless:false
513
514(** Parse flow sequence entry mapping *)
515let parse_flow_sequence_entry_mapping_key t =
516 let tok = current_token t in
517 if check t (function
518 | Token.Value | Token.Flow_entry | Token.Flow_sequence_end -> true
519 | _ -> false)
520 then begin
521 t.state <- Flow_sequence_entry_mapping_value;
522 empty_scalar_event ~anchor:None ~tag:None tok.span
523 end else begin
524 push_state t Flow_sequence_entry_mapping_value;
525 parse_node t ~block:false ~indentless:false
526 end
527
528let parse_flow_sequence_entry_mapping_value t =
529 let tok = current_token t in
530 match tok.token with
531 | Token.Value ->
532 skip_token t;
533 if check t (function
534 | Token.Flow_entry | Token.Flow_sequence_end -> true
535 | _ -> false)
536 then begin
537 t.state <- Flow_sequence_entry_mapping_end;
538 empty_scalar_event ~anchor:None ~tag:None tok.span
539 end else begin
540 push_state t Flow_sequence_entry_mapping_end;
541 parse_node t ~block:false ~indentless:false
542 end
543 | _ ->
544 t.state <- Flow_sequence_entry_mapping_end;
545 empty_scalar_event ~anchor:None ~tag:None tok.span
546
547let parse_flow_sequence_entry_mapping_end t =
548 let tok = current_token t in
549 t.state <- Flow_sequence_entry;
550 Event.Mapping_end, tok.span
551
552(** Parse flow mapping *)
553let rec parse_flow_mapping_key t ~first =
554 let tok = current_token t in
555 match tok.token with
556 | Token.Flow_mapping_end ->
557 skip_token t;
558 t.state <- pop_state t;
559 Event.Mapping_end, tok.span
560 | Token.Flow_entry when not first ->
561 skip_token t;
562 parse_flow_mapping_key_internal t
563 | _ when first ->
564 parse_flow_mapping_key_internal t
565 | _ ->
566 Error.raise_span tok.span Expected_mapping_end
567
568and parse_flow_mapping_key_internal t =
569 let tok = current_token t in
570 match tok.token with
571 | Token.Flow_mapping_end ->
572 (* Trailing comma case - don't emit empty scalar, just return to key state *)
573 skip_token t;
574 t.state <- pop_state t;
575 Event.Mapping_end, tok.span
576 | Token.Flow_entry ->
577 (* Double comma or comma after comma - invalid *)
578 Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow mapping")
579 | Token.Key ->
580 skip_token t;
581 if check t (function
582 | Token.Value | Token.Flow_entry | Token.Flow_mapping_end -> true
583 | _ -> false)
584 then begin
585 t.state <- Flow_mapping_value;
586 empty_scalar_event ~anchor:None ~tag:None tok.span
587 end else begin
588 push_state t Flow_mapping_value;
589 parse_node t ~block:false ~indentless:false
590 end
591 | _ ->
592 push_state t Flow_mapping_value;
593 parse_node t ~block:false ~indentless:false
594
595let parse_flow_mapping_value t ~empty =
596 let tok = current_token t in
597 if empty then begin
598 t.state <- Flow_mapping_key;
599 empty_scalar_event ~anchor:None ~tag:None tok.span
600 end else
601 match tok.token with
602 | Token.Value ->
603 skip_token t;
604 if check t (function
605 | Token.Flow_entry | Token.Flow_mapping_end -> true
606 | _ -> false)
607 then begin
608 t.state <- Flow_mapping_key;
609 empty_scalar_event ~anchor:None ~tag:None tok.span
610 end else begin
611 push_state t Flow_mapping_key;
612 parse_node t ~block:false ~indentless:false
613 end
614 | _ ->
615 t.state <- Flow_mapping_key;
616 empty_scalar_event ~anchor:None ~tag:None tok.span
617
618(** Main state machine dispatcher *)
619let rec parse t =
620 match t.state with
621 | Stream_start ->
622 parse_stream_start t
623
624 | Implicit_document_start ->
625 (* Skip any document end markers before checking what's next *)
626 while check t (function Token.Document_end -> true | _ -> false) do
627 t.explicit_doc_end <- true; (* Seeing ... counts as explicit end *)
628 skip_token t
629 done;
630
631 let tok = current_token t in
632 (match tok.token with
633 | Token.Stream_end ->
634 skip_token t;
635 t.state <- End;
636 t.finished <- true;
637 Event.Stream_end, tok.span
638 | Token.Version_directive _ | Token.Tag_directive _ ->
639 (* Directives are only allowed at stream start or after explicit ... (MUS6/01) *)
640 if not t.stream_start && not t.explicit_doc_end then
641 Error.raise_span tok.span (Invalid_directive "directives require explicit document end '...' before them");
642 parse_document_start t ~implicit:false
643 | Token.Document_start ->
644 parse_document_start t ~implicit:false
645 (* These tokens are invalid at document start - they indicate leftover junk *)
646 | Token.Flow_sequence_end | Token.Flow_mapping_end | Token.Flow_entry
647 | Token.Block_end | Token.Value ->
648 Error.raise_span tok.span (Unexpected_token "unexpected token at document start")
649 | _ ->
650 parse_document_start t ~implicit:true)
651
652 | Document_start ->
653 parse_document_start t ~implicit:false
654
655 | Document_content ->
656 if check t (function
657 | Token.Version_directive _ | Token.Tag_directive _
658 | Token.Document_start | Token.Document_end | Token.Stream_end -> true
659 | _ -> false)
660 then begin
661 let tok = current_token t in
662 t.state <- pop_state t;
663 empty_scalar_event ~anchor:None ~tag:None tok.span
664 end else begin
665 (* Push Document_content_done so we return there after parsing the node.
666 This allows us to check for unexpected content after the node. *)
667 push_state t Document_content_done;
668 parse_node t ~block:true ~indentless:false
669 end
670
671 | Document_content_done ->
672 (* After parsing a node in document content, check for unexpected content *)
673 if check t (function
674 | Token.Version_directive _ | Token.Tag_directive _
675 | Token.Document_start | Token.Document_end | Token.Stream_end -> true
676 | _ -> false)
677 then begin
678 (* Valid document boundary - continue to Document_end *)
679 t.state <- pop_state t;
680 parse t (* Continue to emit the next event *)
681 end else begin
682 (* Unexpected content after document value - this is an error (KS4U, BS4K) *)
683 let tok = current_token t in
684 Error.raise_span tok.span
685 (Unexpected_token "content not allowed after document value")
686 end
687
688 | Document_end ->
689 parse_document_end t
690
691 | Block_node ->
692 parse_node t ~block:true ~indentless:false
693
694 | Block_node_or_indentless_sequence ->
695 parse_node t ~block:true ~indentless:true
696
697 | Flow_node ->
698 parse_node t ~block:false ~indentless:false
699
700 | Block_sequence_first_entry ->
701 t.state <- Block_sequence_entry;
702 parse_block_sequence_entry t
703
704 | Block_sequence_entry ->
705 parse_block_sequence_entry t
706
707 | Indentless_sequence_entry ->
708 parse_indentless_sequence_entry t
709
710 | Block_mapping_first_key ->
711 t.state <- Block_mapping_key;
712 parse_block_mapping_key t
713
714 | Block_mapping_key ->
715 parse_block_mapping_key t
716
717 | Block_mapping_value ->
718 parse_block_mapping_value t
719
720 | Flow_sequence_first_entry ->
721 parse_flow_sequence_entry t ~first:true
722
723 | Flow_sequence_entry ->
724 parse_flow_sequence_entry t ~first:false
725
726 | Flow_sequence_entry_mapping_key ->
727 parse_flow_sequence_entry_mapping_key t
728
729 | Flow_sequence_entry_mapping_value ->
730 parse_flow_sequence_entry_mapping_value t
731
732 | Flow_sequence_entry_mapping_end ->
733 parse_flow_sequence_entry_mapping_end t
734
735 | Flow_mapping_first_key ->
736 parse_flow_mapping_key t ~first:true
737
738 | Flow_mapping_key ->
739 parse_flow_mapping_key t ~first:false
740
741 | Flow_mapping_value ->
742 parse_flow_mapping_value t ~empty:false
743
744 | Flow_mapping_empty_value ->
745 parse_flow_mapping_value t ~empty:true
746
747 | End ->
748 let span = Span.point Position.initial in
749 t.finished <- true;
750 Event.Stream_end, span
751
752(** Get next event *)
753let next t =
754 if t.finished then None
755 else begin
756 let event, span = parse t in
757 Some { Event.event; span }
758 end
759
760(** Peek at next event *)
761let peek t =
762 (* Parser is not easily peekable without full state save/restore *)
763 (* For now, we don't support peek - could add caching if needed *)
764 if t.finished then None
765 else
766 (* Just call next and the caller will have to deal with it *)
767 next t
768
769(** Iterate over all events *)
770let iter f t =
771 let rec loop () =
772 match next t with
773 | None -> ()
774 | Some ev -> f ev; loop ()
775 in
776 loop ()
777
778(** Fold over all events *)
779let fold f init t =
780 let rec loop acc =
781 match next t with
782 | None -> acc
783 | Some ev -> loop (f acc ev)
784 in
785 loop init
786
787(** Convert to list *)
788let to_list t =
789 fold (fun acc ev -> ev :: acc) [] t |> List.rev