Pure OCaml Yaml 1.2 reader and writer using Bytesrw

more

+120 -99
bin/yamlcat.ml
···
| `Float f ->
if Float.is_integer f && Float.abs f < 1e15 then
Buffer.add_string buf (Printf.sprintf "%.0f" f)
-
else
-
Buffer.add_string buf (Printf.sprintf "%g" f)
+
else Buffer.add_string buf (Printf.sprintf "%g" f)
| `String s -> Buffer.add_string buf (Printf.sprintf "%S" s)
| `A items ->
Buffer.add_char buf '[';
-
List.iteri (fun i item ->
-
if i > 0 then Buffer.add_string buf ", ";
-
json_to_string buf item
-
) items;
+
List.iteri
+
(fun i item ->
+
if i > 0 then Buffer.add_string buf ", ";
+
json_to_string buf item)
+
items;
Buffer.add_char buf ']'
| `O pairs ->
Buffer.add_char buf '{';
-
List.iteri (fun i (k, v) ->
-
if i > 0 then Buffer.add_string buf ", ";
-
Buffer.add_string buf (Printf.sprintf "%S: " k);
-
json_to_string buf v
-
) pairs;
+
List.iteri
+
(fun i (k, v) ->
+
if i > 0 then Buffer.add_string buf ", ";
+
Buffer.add_string buf (Printf.sprintf "%S: " k);
+
json_to_string buf v)
+
pairs;
Buffer.add_char buf '}'
let value_to_json v =
···
| Yaml ->
(* Convert through Value to apply tag-based type coercion *)
let first = ref true in
-
List.iter (fun (doc : Yamlrw.document) ->
-
if not !first then print_string "---\n";
-
first := false;
-
match doc.root with
-
| None -> print_endline ""
-
| Some yaml ->
-
let value = Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in
-
print_string (Yamlrw.to_string value)
-
) documents
+
List.iter
+
(fun (doc : Yamlrw.document) ->
+
if not !first then print_string "---\n";
+
first := false;
+
match doc.root with
+
| None -> print_endline ""
+
| Some yaml ->
+
let value =
+
Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml
+
in
+
print_string (Yamlrw.to_string value))
+
documents
| Flow ->
(* Convert through Value to apply tag-based type coercion *)
let first = ref true in
-
List.iter (fun (doc : Yamlrw.document) ->
-
if not !first then print_string "---\n";
-
first := false;
-
match doc.root with
-
| None -> print_endline ""
-
| Some yaml ->
-
let value = Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in
-
print_string (Yamlrw.to_string ~layout_style:`Flow value)
-
) documents
+
List.iter
+
(fun (doc : Yamlrw.document) ->
+
if not !first then print_string "---\n";
+
first := false;
+
match doc.root with
+
| None -> print_endline ""
+
| Some yaml ->
+
let value =
+
Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml
+
in
+
print_string (Yamlrw.to_string ~layout_style:`Flow value))
+
documents
| Json ->
let first = ref true in
-
List.iter (fun (doc : Yamlrw.document) ->
-
match doc.root with
-
| None -> ()
-
| Some yaml ->
-
if not !first then print_endline "---";
-
first := false;
-
let value = Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in
-
print_endline (value_to_json value)
-
) documents
+
List.iter
+
(fun (doc : Yamlrw.document) ->
+
match doc.root with
+
| None -> ()
+
| Some yaml ->
+
if not !first then print_endline "---";
+
first := false;
+
let value =
+
Yamlrw.to_json ~resolve_aliases ~max_nodes ~max_depth yaml
+
in
+
print_endline (value_to_json value))
+
documents
| Debug ->
-
List.iteri (fun i (doc : Yamlrw.document) ->
-
Format.printf "Document %d:@." (i + 1);
-
(* Convert back to Document.t for printing *)
-
let doc' : Yamlrw.Document.t = {
-
Yamlrw.Document.version = doc.version;
-
Yamlrw.Document.tags = doc.tags;
-
Yamlrw.Document.root = (doc.root :> Yamlrw.Yaml.t option);
-
Yamlrw.Document.implicit_start = doc.implicit_start;
-
Yamlrw.Document.implicit_end = doc.implicit_end;
-
} in
-
Format.printf "%a@." Yamlrw.Document.pp doc'
-
) documents
-
with
-
| Yamlrw.Yamlrw_error e ->
-
Printf.eprintf "Error: %s\n" (Yamlrw.Error.to_string e);
-
exit 1
+
List.iteri
+
(fun i (doc : Yamlrw.document) ->
+
Format.printf "Document %d:@." (i + 1);
+
(* Convert back to Document.t for printing *)
+
let doc' : Yamlrw.Document.t =
+
{
+
Yamlrw.Document.version = doc.version;
+
Yamlrw.Document.tags = doc.tags;
+
Yamlrw.Document.root = (doc.root :> Yamlrw.Yaml.t option);
+
Yamlrw.Document.implicit_start = doc.implicit_start;
+
Yamlrw.Document.implicit_end = doc.implicit_end;
+
}
+
in
+
Format.printf "%a@." Yamlrw.Document.pp doc')
+
documents
+
with Yamlrw.Yamlrw_error e ->
+
Printf.eprintf "Error: %s\n" (Yamlrw.Error.to_string e);
+
exit 1
let process_file ~format ~resolve_aliases ~max_nodes ~max_depth filename =
let content =
-
if filename = "-" then
-
In_channel.input_all In_channel.stdin
-
else
-
In_channel.with_open_text filename In_channel.input_all
+
if filename = "-" then In_channel.input_all In_channel.stdin
+
else In_channel.with_open_text filename In_channel.input_all
in
process_string ~format ~resolve_aliases ~max_nodes ~max_depth content
let run format _all resolve_aliases max_nodes max_depth files =
-
let files = if files = [] then ["-"] else files in
+
let files = if files = [] then [ "-" ] else files in
List.iter (process_file ~format ~resolve_aliases ~max_nodes ~max_depth) files;
`Ok ()
···
let format_arg =
let doc = "Output format: yaml (default), json, flow, or debug." in
-
let formats = [
-
("yaml", Yaml);
-
("json", Json);
-
("flow", Flow);
-
("debug", Debug);
-
] in
-
Arg.(value & opt (enum formats) Yaml & info ["format"; "f"] ~docv:"FORMAT" ~doc)
+
let formats =
+
[ ("yaml", Yaml); ("json", Json); ("flow", Flow); ("debug", Debug) ]
+
in
+
Arg.(
+
value & opt (enum formats) Yaml & info [ "format"; "f" ] ~docv:"FORMAT" ~doc)
let json_arg =
let doc = "Output as JSON (shorthand for --format=json)." in
-
Arg.(value & flag & info ["json"] ~doc)
+
Arg.(value & flag & info [ "json" ] ~doc)
let flow_arg =
let doc = "Output in flow style (shorthand for --format=flow)." in
-
Arg.(value & flag & info ["flow"] ~doc)
+
Arg.(value & flag & info [ "flow" ] ~doc)
let debug_arg =
let doc = "Output internal representation (shorthand for --format=debug)." in
-
Arg.(value & flag & info ["debug"] ~doc)
+
Arg.(value & flag & info [ "debug" ] ~doc)
let all_arg =
let doc = "Output all documents (for multi-document YAML)." in
-
Arg.(value & flag & info ["all"; "a"] ~doc)
+
Arg.(value & flag & info [ "all"; "a" ] ~doc)
let no_resolve_aliases_arg =
let doc = "Don't resolve aliases (keep them as references)." in
-
Arg.(value & flag & info ["no-resolve-aliases"] ~doc)
+
Arg.(value & flag & info [ "no-resolve-aliases" ] ~doc)
let max_nodes_arg =
-
let doc = "Maximum number of nodes during alias expansion (default: 10000000). \
-
Protection against billion laughs attack." in
-
Arg.(value & opt int Yamlrw.default_max_alias_nodes & info ["max-nodes"] ~docv:"N" ~doc)
+
let doc =
+
"Maximum number of nodes during alias expansion (default: 10000000). \
+
Protection against billion laughs attack."
+
in
+
Arg.(
+
value
+
& opt int Yamlrw.default_max_alias_nodes
+
& info [ "max-nodes" ] ~docv:"N" ~doc)
let max_depth_arg =
-
let doc = "Maximum alias nesting depth (default: 100). \
-
Protection against deeply nested alias chains." in
-
Arg.(value & opt int Yamlrw.default_max_alias_depth & info ["max-depth"] ~docv:"N" ~doc)
+
let doc =
+
"Maximum alias nesting depth (default: 100). Protection against deeply \
+
nested alias chains."
+
in
+
Arg.(
+
value
+
& opt int Yamlrw.default_max_alias_depth
+
& info [ "max-depth" ] ~docv:"N" ~doc)
let files_arg =
let doc = "YAML file(s) to process. Use '-' for stdin." in
Arg.(value & pos_all file [] & info [] ~docv:"FILE" ~doc)
let combined_format format json flow debug =
-
if json then Json
-
else if flow then Flow
-
else if debug then Debug
-
else format
+
if json then Json else if flow then Flow else if debug then Debug else format
let term =
let combine format json flow debug all no_resolve max_nodes max_depth files =
···
let resolve_aliases = not no_resolve in
run format all resolve_aliases max_nodes max_depth files
in
-
Term.(ret (const combine $ format_arg $ json_arg $ flow_arg $ debug_arg $
-
all_arg $ no_resolve_aliases_arg $ max_nodes_arg $ max_depth_arg $ files_arg))
+
Term.(
+
ret
+
(const combine $ format_arg $ json_arg $ flow_arg $ debug_arg $ all_arg
+
$ no_resolve_aliases_arg $ max_nodes_arg $ max_depth_arg $ files_arg))
let info =
let doc = "Parse and reprint YAML files" in
-
let man = [
-
`S Manpage.s_description;
-
`P "$(tname) parses YAML files and reprints them in various formats. \
-
It can be used to validate YAML, convert between styles, or convert to JSON.";
-
`S Manpage.s_examples;
-
`P "Parse and reprint a YAML file:";
-
`Pre " $(tname) config.yaml";
-
`P "Convert YAML to JSON:";
-
`Pre " $(tname) --json config.yaml";
-
`P "Process multi-document YAML:";
-
`Pre " $(tname) --all multi.yaml";
-
`P "Limit alias expansion (protection against malicious YAML):";
-
`Pre " $(tname) --max-nodes 1000 --max-depth 10 untrusted.yaml";
-
`S Manpage.s_bugs;
-
`P "Report bugs at https://github.com/avsm/ocaml-yaml/issues";
-
] in
+
let man =
+
[
+
`S Manpage.s_description;
+
`P
+
"$(tname) parses YAML files and reprints them in various formats. It \
+
can be used to validate YAML, convert between styles, or convert to \
+
JSON.";
+
`S Manpage.s_examples;
+
`P "Parse and reprint a YAML file:";
+
`Pre " $(tname) config.yaml";
+
`P "Convert YAML to JSON:";
+
`Pre " $(tname) --json config.yaml";
+
`P "Process multi-document YAML:";
+
`Pre " $(tname) --all multi.yaml";
+
`P "Limit alias expansion (protection against malicious YAML):";
+
`Pre " $(tname) --max-nodes 1000 --max-depth 10 untrusted.yaml";
+
`S Manpage.s_bugs;
+
`P "Report bugs at https://github.com/avsm/ocaml-yaml/issues";
+
]
+
in
Cmd.info "yamlcat" ~version:"0.1.0" ~doc ~man
let () = exit (Cmd.eval (Cmd.v info term))
+1
dune
···
; Root dune file
; Ignore third_party directory (for fetched dependency sources)
+
(data_only_dirs third_party)
+6 -12
lib/char_class.ml
···
(** Hexadecimal digit *)
let is_hex c =
-
(c >= '0' && c <= '9') ||
-
(c >= 'a' && c <= 'f') ||
-
(c >= 'A' && c <= 'F')
+
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
(** Alphabetic character *)
-
let is_alpha c =
-
(c >= 'a' && c <= 'z') ||
-
(c >= 'A' && c <= 'Z')
+
let is_alpha c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
(** Alphanumeric character *)
let is_alnum c = is_alpha c || is_digit c
···
(** YAML indicator characters *)
let is_indicator c =
match c with
-
| '-' | '?' | ':' | ',' | '[' | ']' | '{' | '}'
-
| '#' | '&' | '*' | '!' | '|' | '>' | '\'' | '"'
-
| '%' | '@' | '`' -> true
+
| '-' | '?' | ':' | ',' | '[' | ']' | '{' | '}' | '#' | '&' | '*' | '!' | '|'
+
| '>' | '\'' | '"' | '%' | '@' | '`' ->
+
true
| _ -> false
(** Flow context indicator characters *)
let is_flow_indicator c =
-
match c with
-
| ',' | '[' | ']' | '{' | '}' -> true
-
| _ -> false
+
match c with ',' | '[' | ']' | '{' | '}' -> true | _ -> false
+5 -19
lib/chomping.ml
···
(** Block scalar chomping indicators *)
type t =
-
| Strip (** Remove final line break and trailing empty lines *)
+
| Strip (** Remove final line break and trailing empty lines *)
| Clip (** Keep final line break, remove trailing empty lines (default) *)
| Keep (** Keep final line break and trailing empty lines *)
-
let to_string = function
-
| Strip -> "strip"
-
| Clip -> "clip"
-
| Keep -> "keep"
-
-
let pp fmt t =
-
Format.pp_print_string fmt (to_string t)
-
-
let of_char = function
-
| '-' -> Some Strip
-
| '+' -> Some Keep
-
| _ -> None
-
-
let to_char = function
-
| Strip -> Some '-'
-
| Clip -> None
-
| Keep -> Some '+'
-
+
let to_string = function Strip -> "strip" | Clip -> "clip" | Keep -> "keep"
+
let pp fmt t = Format.pp_print_string fmt (to_string t)
+
let of_char = function '-' -> Some Strip | '+' -> Some Keep | _ -> None
+
let to_char = function Strip -> Some '-' | Clip -> None | Keep -> Some '+'
let equal a b = a = b
+16 -20
lib/document.ml
···
implicit_end : bool;
}
-
let make
-
?(version : (int * int) option)
-
?(tags : (string * string) list = [])
-
?(implicit_start = true)
-
?(implicit_end = true)
-
root =
+
let make ?(version : (int * int) option) ?(tags : (string * string) list = [])
+
?(implicit_start = true) ?(implicit_end = true) root =
{ version; tags; root; implicit_start; implicit_end }
let version t = t.version
···
let root t = t.root
let implicit_start t = t.implicit_start
let implicit_end t = t.implicit_end
-
let with_version version t = { t with version = Some version }
let with_tags tags t = { t with tags }
let with_root root t = { t with root = Some root }
···
let pp fmt t =
Format.fprintf fmt "@[<v 2>document(@,";
(match t.version with
-
| Some (maj, min) -> Format.fprintf fmt "version=%d.%d,@ " maj min
-
| None -> ());
+
| Some (maj, min) -> Format.fprintf fmt "version=%d.%d,@ " maj min
+
| None -> ());
if t.tags <> [] then begin
Format.fprintf fmt "tags=[";
-
List.iteri (fun i (h, p) ->
-
if i > 0 then Format.fprintf fmt ", ";
-
Format.fprintf fmt "%s -> %s" h p
-
) t.tags;
+
List.iteri
+
(fun i (h, p) ->
+
if i > 0 then Format.fprintf fmt ", ";
+
Format.fprintf fmt "%s -> %s" h p)
+
t.tags;
Format.fprintf fmt "],@ "
end;
Format.fprintf fmt "implicit_start=%b,@ " t.implicit_start;
Format.fprintf fmt "implicit_end=%b,@ " t.implicit_end;
(match t.root with
-
| Some root -> Format.fprintf fmt "root=%a" Yaml.pp root
-
| None -> Format.fprintf fmt "root=<empty>");
+
| Some root -> Format.fprintf fmt "root=%a" Yaml.pp root
+
| None -> Format.fprintf fmt "root=<empty>");
Format.fprintf fmt "@]@,)"
let equal a b =
-
Option.equal ( = ) a.version b.version &&
-
List.equal ( = ) a.tags b.tags &&
-
Option.equal Yaml.equal a.root b.root &&
-
a.implicit_start = b.implicit_start &&
-
a.implicit_end = b.implicit_end
+
Option.equal ( = ) a.version b.version
+
&& List.equal ( = ) a.tags b.tags
+
&& Option.equal Yaml.equal a.root b.root
+
&& a.implicit_start = b.implicit_start
+
&& a.implicit_end = b.implicit_end
+63 -107
lib/eio/yamlrw_eio.ml
···
(** Yamlrw Eio - Streaming YAML parsing and emitting with Eio
-
This module provides Eio-compatible streaming YAML parsing and emitting.
-
It uses bytesrw adapters to convert Eio sources/sinks to the standard
-
YAML scanner/parser/emitter, eliminating code duplication. *)
+
This module provides Eio-compatible streaming YAML parsing and emitting. It
+
uses bytesrw adapters to convert Eio sources/sinks to the standard YAML
+
scanner/parser/emitter, eliminating code duplication. *)
open Yamlrw
···
Scanner.of_input input
(** Create a parser from an Eio flow *)
-
let parser_of_flow flow =
-
Parser.of_scanner (scanner_of_flow flow)
+
let parser_of_flow flow = Parser.of_scanner (scanner_of_flow flow)
(** Parse a JSON-compatible value from an Eio flow.
@param resolve_aliases Whether to expand aliases (default: true)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100) *)
-
let value
-
?(resolve_aliases = true)
+
let value ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
flow =
+
?(max_depth = Yaml.default_max_alias_depth) flow =
let parser = parser_of_flow flow in
-
Loader.value_of_parser
-
~resolve_aliases ~max_nodes ~max_depth
-
(fun () -> Parser.next parser)
+
Loader.value_of_parser ~resolve_aliases ~max_nodes ~max_depth (fun () ->
+
Parser.next parser)
(** Parse a full YAML value from an Eio flow.
···
@param resolve_aliases Whether to expand aliases (default: false)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100) *)
-
let yaml
-
?(resolve_aliases = false)
+
let yaml ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
flow =
+
?(max_depth = Yaml.default_max_alias_depth) flow =
let parser = parser_of_flow flow in
-
Loader.yaml_of_parser
-
~resolve_aliases ~max_nodes ~max_depth
-
(fun () -> Parser.next parser)
+
Loader.yaml_of_parser ~resolve_aliases ~max_nodes ~max_depth (fun () ->
+
Parser.next parser)
(** Parse multiple YAML documents from an Eio flow. *)
let documents flow =
···
(** {2 Event-Based Streaming} *)
+
type event_reader = { parser : Parser.t }
(** A streaming event reader backed by a flow *)
-
type event_reader = {
-
parser : Parser.t;
-
}
-
(** Create an event reader from an Eio flow.
-
This reads data incrementally as events are requested. *)
-
let event_reader flow =
-
{ parser = parser_of_flow flow }
+
(** Create an event reader from an Eio flow. This reads data incrementally as
+
events are requested. *)
+
let event_reader flow = { parser = parser_of_flow flow }
-
(** Get the next event from an event reader.
-
Returns [None] when parsing is complete. *)
-
let next_event reader =
-
Parser.next reader.parser
+
(** Get the next event from an event reader. Returns [None] when parsing is
+
complete. *)
+
let next_event reader = Parser.next reader.parser
(** Iterate over all events from a flow.
···
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any) *)
-
let value
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
flow
-
(v : value) =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let value ?(encoding = `Utf8) ?(scalar_style = `Any) ?(layout_style = `Any)
+
flow (v : value) =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytesrw_eio.bytes_writer_of_flow flow in
Serialize.value_to_writer ~config writer v
···
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any) *)
-
let yaml
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
flow
-
(v : yaml) =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let yaml ?(encoding = `Utf8) ?(scalar_style = `Any) ?(layout_style = `Any)
+
flow (v : yaml) =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytesrw_eio.bytes_writer_of_flow flow in
Serialize.yaml_to_writer ~config writer v
···
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
@param resolve_aliases Whether to expand aliases (default: true) *)
-
let documents
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
-
flow
-
docs =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let documents ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) flow docs =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytesrw_eio.bytes_writer_of_flow flow in
Serialize.documents_to_writer ~config ~resolve_aliases writer docs
(** {2 Event-Based Streaming} *)
+
type event_writer = { emitter : Emitter.t }
(** A streaming event writer that writes directly to a flow *)
-
type event_writer = {
-
emitter : Emitter.t;
-
}
-
(** Create an event writer that writes directly to a flow.
-
Events are written incrementally as they are emitted.
+
(** Create an event writer that writes directly to a flow. Events are written
+
incrementally as they are emitted.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any) *)
-
let event_writer
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
flow =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let event_writer ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) flow =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytesrw_eio.bytes_writer_of_flow flow in
{ emitter = Emitter.of_writer ~config writer }
(** Emit a single event to the writer. *)
-
let emit ew ev =
-
Emitter.emit ew.emitter ev
+
let emit ew ev = Emitter.emit ew.emitter ev
(** Flush the writer by sending end-of-data. *)
-
let flush ew =
-
Emitter.flush ew.emitter
+
let flush ew = Emitter.flush ew.emitter
(** Emit events from a list to a flow. *)
let emit_all flow events =
···
(** {1 Convenience Functions} *)
(** Read a value from a file path *)
-
let of_file
-
?(resolve_aliases = true)
+
let of_file ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
~fs
-
path =
+
?(max_depth = Yaml.default_max_alias_depth) ~fs path =
Eio.Path.with_open_in Eio.Path.(fs / path) @@ fun flow ->
Read.value ~resolve_aliases ~max_nodes ~max_depth flow
(** Read full YAML from a file path *)
-
let yaml_of_file
-
?(resolve_aliases = false)
+
let yaml_of_file ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
~fs
-
path =
+
?(max_depth = Yaml.default_max_alias_depth) ~fs path =
Eio.Path.with_open_in Eio.Path.(fs / path) @@ fun flow ->
Read.yaml ~resolve_aliases ~max_nodes ~max_depth flow
(** Read documents from a file path *)
let documents_of_file ~fs path =
-
Eio.Path.with_open_in Eio.Path.(fs / path) @@ fun flow ->
-
Read.documents flow
+
Eio.Path.with_open_in Eio.Path.(fs / path) @@ fun flow -> Read.documents flow
(** Write a value to a file path *)
-
let to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
~fs
-
path
-
v =
-
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path) @@ fun flow ->
-
Write.value ~encoding ~scalar_style ~layout_style flow v
+
let to_file ?(encoding = `Utf8) ?(scalar_style = `Any) ?(layout_style = `Any)
+
~fs path v =
+
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path)
+
@@ fun flow -> Write.value ~encoding ~scalar_style ~layout_style flow v
(** Write full YAML to a file path *)
-
let yaml_to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
~fs
-
path
-
v =
-
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path) @@ fun flow ->
-
Write.yaml ~encoding ~scalar_style ~layout_style flow v
+
let yaml_to_file ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ~fs path v =
+
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path)
+
@@ fun flow -> Write.yaml ~encoding ~scalar_style ~layout_style flow v
(** Write documents to a file path *)
-
let documents_to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
-
~fs
-
path
-
docs =
-
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path) @@ fun flow ->
-
Write.documents ~encoding ~scalar_style ~layout_style ~resolve_aliases flow docs
+
let documents_to_file ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) ~fs path docs =
+
Eio.Path.with_open_out ~create:(`Or_truncate 0o644) Eio.Path.(fs / path)
+
@@ fun flow ->
+
Write.documents ~encoding ~scalar_style ~layout_style ~resolve_aliases flow
+
docs
+66 -52
lib/eio/yamlrw_eio.mli
···
(** Yamlrw Eio - Streaming YAML parsing and emitting with Eio
-
This library provides Eio-based streaming support for YAML parsing
-
and emitting. It uses bytesrw adapters that read/write directly to
-
Eio flows, with bytesrw handling internal buffering.
+
This library provides Eio-based streaming support for YAML parsing and
+
emitting. It uses bytesrw adapters that read/write directly to Eio flows,
+
with bytesrw handling internal buffering.
{2 Quick Start}
···
Eio_main.run @@ fun env ->
let fs = Eio.Stdenv.fs env in
Eio.Path.with_open_out Eio.Path.(fs / "output.yaml") @@ fun flow ->
-
Yaml_eio.Write.value flow (`O [("name", `String "test")])
+
Yaml_eio.Write.value flow (`O [ ("name", `String "test") ])
]}
Stream events incrementally:
···
Eio_main.run @@ fun env ->
let fs = Eio.Stdenv.fs env in
Eio.Path.with_open_in Eio.Path.(fs / "data.yaml") @@ fun flow ->
-
Yaml_eio.Read.iter_events (fun event span ->
-
Format.printf "Event at %a@." Yamlrw.Span.pp span
-
) flow
+
Yaml_eio.Read.iter_events
+
(fun event span -> Format.printf "Event at %a@." Yamlrw.Span.pp span)
+
flow
]}
{2 Streaming Architecture}
This library uses bytesrw for direct I/O with Eio flows:
-
- {b Reading}: Data is read directly from the flow as the
-
parser requests it. Bytesrw handles internal buffering.
+
- {b Reading}: Data is read directly from the flow as the parser requests
+
it. Bytesrw handles internal buffering.
-
- {b Writing}: Output is written directly to the flow.
-
Bytesrw handles chunking and buffering. *)
+
- {b Writing}: Output is written directly to the flow. Bytesrw handles
+
chunking and buffering. *)
(** {1 Types} *)
···
module Read : sig
(** Parse YAML from Eio flows.
-
All functions read data incrementally from the underlying flow,
-
without loading the entire file into memory first. *)
+
All functions read data incrementally from the underlying flow, without
+
loading the entire file into memory first. *)
(** {2 High-Level Parsing} *)
···
?resolve_aliases:bool ->
?max_nodes:int ->
?max_depth:int ->
-
_ Eio.Flow.source -> value
+
_ Eio.Flow.source ->
+
value
(** Parse a JSON-compatible value from an Eio flow.
@param resolve_aliases Whether to expand aliases (default: true)
···
?resolve_aliases:bool ->
?max_nodes:int ->
?max_depth:int ->
-
_ Eio.Flow.source -> yaml
+
_ Eio.Flow.source ->
+
yaml
(** Parse a full YAML value from an Eio flow.
By default, aliases are NOT resolved, preserving the document structure.
···
(** {2 Event-Based Streaming} *)
type event_reader
-
(** A streaming event reader backed by a flow.
-
Events are parsed incrementally as requested. *)
+
(** A streaming event reader backed by a flow. Events are parsed incrementally
+
as requested. *)
val event_reader : _ Eio.Flow.source -> event_reader
(** Create an event reader from an Eio flow. *)
val next_event : event_reader -> Yamlrw.Event.spanned option
-
(** Get the next event from an event reader.
-
Returns [None] when parsing is complete. *)
+
(** Get the next event from an event reader. Returns [None] when parsing is
+
complete. *)
val iter_events :
-
(event -> Yamlrw.Span.t -> unit) ->
-
_ Eio.Flow.source -> unit
+
(event -> Yamlrw.Span.t -> unit) -> _ Eio.Flow.source -> unit
(** Iterate over all events from a flow. *)
-
val fold_events :
-
('a -> event -> 'a) -> 'a ->
-
_ Eio.Flow.source -> 'a
+
val fold_events : ('a -> event -> 'a) -> 'a -> _ Eio.Flow.source -> 'a
(** Fold over all events from a flow. *)
-
val iter_documents :
-
(document -> unit) ->
-
_ Eio.Flow.source -> unit
+
val iter_documents : (document -> unit) -> _ Eio.Flow.source -> unit
(** Iterate over documents from a flow, calling [f] for each document. *)
-
val fold_documents :
-
('a -> document -> 'a) -> 'a ->
-
_ Eio.Flow.source -> 'a
+
val fold_documents : ('a -> document -> 'a) -> 'a -> _ Eio.Flow.source -> 'a
(** Fold over documents from a flow. *)
end
···
?encoding:Yamlrw.Encoding.t ->
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
-
_ Eio.Flow.sink -> value -> unit
+
_ Eio.Flow.sink ->
+
value ->
+
unit
(** Write a JSON-compatible value to an Eio flow.
@param encoding Output encoding (default: UTF-8)
···
?encoding:Yamlrw.Encoding.t ->
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
-
_ Eio.Flow.sink -> yaml -> unit
+
_ Eio.Flow.sink ->
+
yaml ->
+
unit
(** Write a full YAML value to an Eio flow.
@param encoding Output encoding (default: UTF-8)
···
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
?resolve_aliases:bool ->
-
_ Eio.Flow.sink -> document list -> unit
+
_ Eio.Flow.sink ->
+
document list ->
+
unit
(** Write multiple YAML documents to an Eio flow.
@param encoding Output encoding (default: UTF-8)
···
(** {2 Event-Based Streaming} *)
type event_writer
-
(** A streaming event writer backed by a flow.
-
Events are written incrementally to the underlying flow. *)
+
(** A streaming event writer backed by a flow. Events are written
+
incrementally to the underlying flow. *)
val event_writer :
?encoding:Yamlrw.Encoding.t ->
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
-
_ Eio.Flow.sink -> event_writer
-
(** Create an event writer that writes directly to a flow.
-
Events are written incrementally as they are emitted.
+
_ Eio.Flow.sink ->
+
event_writer
+
(** Create an event writer that writes directly to a flow. Events are written
+
incrementally as they are emitted.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
···
?max_nodes:int ->
?max_depth:int ->
fs:_ Eio.Path.t ->
-
string -> value
+
string ->
+
value
(** Read a value from a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
val yaml_of_file :
?resolve_aliases:bool ->
?max_nodes:int ->
?max_depth:int ->
fs:_ Eio.Path.t ->
-
string -> yaml
+
string ->
+
yaml
(** Read full YAML from a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
-
val documents_of_file :
-
fs:_ Eio.Path.t ->
-
string -> document list
+
val documents_of_file : fs:_ Eio.Path.t -> string -> document list
(** Read documents from a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
val to_file :
?encoding:Yamlrw.Encoding.t ->
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
fs:_ Eio.Path.t ->
-
string -> value -> unit
+
string ->
+
value ->
+
unit
(** Write a value to a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
val yaml_to_file :
?encoding:Yamlrw.Encoding.t ->
?scalar_style:Yamlrw.Scalar_style.t ->
?layout_style:Yamlrw.Layout_style.t ->
fs:_ Eio.Path.t ->
-
string -> yaml -> unit
+
string ->
+
yaml ->
+
unit
(** Write full YAML to a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
val documents_to_file :
?encoding:Yamlrw.Encoding.t ->
···
?layout_style:Yamlrw.Layout_style.t ->
?resolve_aliases:bool ->
fs:_ Eio.Path.t ->
-
string -> document list -> unit
+
string ->
+
document list ->
+
unit
(** Write documents to a file path.
-
@param fs The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+
@param fs
+
The filesystem path (e.g., [Eio.Stdenv.fs env] or [Eio.Stdenv.cwd env]) *)
+209 -211
lib/emitter.ml
···
(** Emitter - converts YAML data structures to string output
-
The emitter can write to either a Buffer (default) or directly to a
-
bytesrw Bytes.Writer for streaming output. *)
+
The emitter can write to either a Buffer (default) or directly to a bytesrw
+
Bytes.Writer for streaming output. *)
type config = {
encoding : Encoding.t;
···
canonical : bool;
}
-
let default_config = {
-
encoding = `Utf8;
-
scalar_style = `Any;
-
layout_style = `Any;
-
indent = 2;
-
width = 80;
-
canonical = false;
-
}
+
let default_config =
+
{
+
encoding = `Utf8;
+
scalar_style = `Any;
+
layout_style = `Any;
+
indent = 2;
+
width = 80;
+
canonical = false;
+
}
type state =
| Initial
| Stream_started
| Document_started
-
| In_block_sequence of int (* indent level *)
+
| In_block_sequence of int (* indent level *)
| In_block_mapping_key of int
| In_block_mapping_value of int
-
| In_block_mapping_first_key of int (* first key after "- ", no indent needed *)
+
| In_block_mapping_first_key of
+
int (* first key after "- ", no indent needed *)
| In_flow_sequence
| In_flow_mapping_key
| In_flow_mapping_value
···
| Stream_ended
(** Output sink - either a Buffer or a bytesrw Writer *)
-
type sink =
-
| Buffer_sink of Buffer.t
-
| Writer_sink of Bytesrw.Bytes.Writer.t
+
type sink = Buffer_sink of Buffer.t | Writer_sink of Bytesrw.Bytes.Writer.t
type t = {
config : config;
···
mutable need_separator : bool;
}
-
let create ?(config = default_config) () = {
-
config;
-
sink = Buffer_sink (Buffer.create 1024);
-
state = Initial;
-
states = [];
-
indent = 0;
-
flow_level = 0;
-
need_separator = false;
-
}
+
let create ?(config = default_config) () =
+
{
+
config;
+
sink = Buffer_sink (Buffer.create 1024);
+
state = Initial;
+
states = [];
+
indent = 0;
+
flow_level = 0;
+
need_separator = false;
+
}
(** Create an emitter that writes directly to a Bytes.Writer *)
-
let of_writer ?(config = default_config) writer = {
-
config;
-
sink = Writer_sink writer;
-
state = Initial;
-
states = [];
-
indent = 0;
-
flow_level = 0;
-
need_separator = false;
-
}
+
let of_writer ?(config = default_config) writer =
+
{
+
config;
+
sink = Writer_sink writer;
+
state = Initial;
+
states = [];
+
indent = 0;
+
flow_level = 0;
+
need_separator = false;
+
}
let contents t =
match t.sink with
| Buffer_sink buf -> Buffer.contents buf
-
| Writer_sink _ -> "" (* No accumulated content for writer sink *)
+
| Writer_sink _ -> "" (* No accumulated content for writer sink *)
let reset t =
(match t.sink with
-
| Buffer_sink buf -> Buffer.clear buf
-
| Writer_sink _ -> ());
+
| Buffer_sink buf -> Buffer.clear buf
+
| Writer_sink _ -> ());
t.state <- Initial;
t.states <- [];
t.indent <- 0;
···
let write_indent t =
if t.indent <= 8 then
-
for _ = 1 to t.indent do write_char t ' ' done
-
else
-
write t (String.make t.indent ' ')
+
for _ = 1 to t.indent do
+
write_char t ' '
+
done
+
else write t (String.make t.indent ' ')
-
let write_newline t =
-
write_char t '\n'
+
let write_newline t = write_char t '\n'
let push_state t s =
t.states <- t.state :: t.states;
···
| s :: rest ->
t.state <- s;
t.states <- rest
-
| [] ->
-
t.state <- Stream_ended
+
| [] -> t.state <- Stream_ended
-
(** Escape a string for double-quoted output.
-
Uses a buffer to batch writes instead of character-by-character. *)
+
(** Escape a string for double-quoted output. Uses a buffer to batch writes
+
instead of character-by-character. *)
let escape_double_quoted value =
let len = String.length value in
(* Check if any escaping is needed *)
···
done;
if not !needs_escape then value
else begin
-
let buf = Buffer.create (len + len / 4) in
+
let buf = Buffer.create (len + (len / 4)) in
for i = 0 to len - 1 do
match value.[i] with
| '"' -> Buffer.add_string buf "\\\""
···
| '\n' -> Buffer.add_string buf "\\n"
| '\r' -> Buffer.add_string buf "\\r"
| '\t' -> Buffer.add_string buf "\\t"
-
| c when c < ' ' -> Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c))
+
| c when c < ' ' ->
+
Buffer.add_string buf (Printf.sprintf "\\x%02x" (Char.code c))
| c -> Buffer.add_char buf c
done;
Buffer.contents buf
···
if not (String.contains value '\'') then value
else begin
let len = String.length value in
-
let buf = Buffer.create (len + len / 8) in
+
let buf = Buffer.create (len + (len / 8)) in
for i = 0 to len - 1 do
let c = value.[i] in
-
if c = '\'' then Buffer.add_string buf "''"
-
else Buffer.add_char buf c
+
if c = '\'' then Buffer.add_string buf "''" else Buffer.add_char buf c
done;
Buffer.contents buf
end
(** Write scalar with appropriate quoting *)
let write_scalar t ?(style = `Any) value =
-
match (match style with `Any -> Quoting.choose_style value | s -> s) with
-
| `Plain | `Any ->
-
write t value
+
match match style with `Any -> Quoting.choose_style value | s -> s with
+
| `Plain | `Any -> write t value
| `Single_quoted ->
write_char t '\'';
write t (escape_single_quoted value);
···
| `Literal ->
write t "|";
write_newline t;
-
String.split_on_char '\n' value |> List.iter (fun line ->
-
write_indent t;
-
write t line;
-
write_newline t
-
)
+
String.split_on_char '\n' value
+
|> List.iter (fun line ->
+
write_indent t;
+
write t line;
+
write_newline t)
| `Folded ->
write t ">";
write_newline t;
-
String.split_on_char '\n' value |> List.iter (fun line ->
-
write_indent t;
-
write t line;
-
write_newline t
-
)
+
String.split_on_char '\n' value
+
|> List.iter (fun line ->
+
write_indent t;
+
write t line;
+
write_newline t)
(** Write anchor if present *)
let write_anchor t anchor =
···
let emit t (ev : Event.t) =
match ev with
-
| Event.Stream_start _ ->
-
t.state <- Stream_started
-
-
| Event.Stream_end ->
-
t.state <- Stream_ended
-
+
| Event.Stream_start _ -> t.state <- Stream_started
+
| Event.Stream_end -> t.state <- Stream_ended
| Event.Document_start { version; implicit } ->
if not implicit then begin
(match version with
-
| Some (maj, min) ->
-
write t (Printf.sprintf "%%YAML %d.%d\n" maj min)
-
| None -> ());
+
| Some (maj, min) -> write t (Printf.sprintf "%%YAML %d.%d\n" maj min)
+
| None -> ());
write t "---";
write_newline t
end;
t.state <- Document_started
-
| Event.Document_end { implicit } ->
if not implicit then begin
write t "...";
write_newline t
end;
t.state <- Document_ended
-
| Event.Alias { anchor } ->
if t.flow_level > 0 then begin
if t.need_separator then write t ", ";
t.need_separator <- true;
write_char t '*';
write t anchor
-
end else begin
-
(match t.state with
-
| In_block_sequence _ ->
-
write_indent t;
-
write t "- *";
-
write t anchor;
-
write_newline t
-
| In_block_mapping_key _ ->
-
write_indent t;
-
write_char t '*';
-
write t anchor;
-
write t ": ";
-
t.state <- In_block_mapping_value t.indent
-
| In_block_mapping_value indent ->
-
write_char t '*';
-
write t anchor;
-
write_newline t;
-
t.state <- In_block_mapping_key indent
-
| _ ->
-
write_char t '*';
-
write t anchor;
-
write_newline t)
+
end
+
else begin
+
match t.state with
+
| In_block_sequence _ ->
+
write_indent t;
+
write t "- *";
+
write t anchor;
+
write_newline t
+
| In_block_mapping_key _ ->
+
write_indent t;
+
write_char t '*';
+
write t anchor;
+
write t ": ";
+
t.state <- In_block_mapping_value t.indent
+
| In_block_mapping_value indent ->
+
write_char t '*';
+
write t anchor;
+
write_newline t;
+
t.state <- In_block_mapping_key indent
+
| _ ->
+
write_char t '*';
+
write t anchor;
+
write_newline t
end
-
| Event.Scalar { anchor; tag; value; plain_implicit; style; _ } ->
if t.flow_level > 0 then begin
-
(match t.state with
-
| In_flow_mapping_key ->
-
if t.need_separator then write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit:plain_implicit tag;
-
write_scalar t ~style value;
-
write t ": ";
-
t.need_separator <- false;
-
t.state <- In_flow_mapping_value
-
| In_flow_mapping_value ->
-
if t.need_separator then begin
-
(* We just finished a nested structure (array/mapping),
+
match t.state with
+
| In_flow_mapping_key ->
+
if t.need_separator then write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write t ": ";
+
t.need_separator <- false;
+
t.state <- In_flow_mapping_value
+
| In_flow_mapping_value ->
+
if t.need_separator then begin
+
(* We just finished a nested structure (array/mapping),
so this scalar is the next key, not a value *)
-
write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit:plain_implicit tag;
-
write_scalar t ~style value;
-
write t ": ";
-
t.need_separator <- false;
-
t.state <- In_flow_mapping_value
-
end else begin
-
(* Normal value scalar *)
-
write_anchor t anchor;
-
write_tag t ~implicit:plain_implicit tag;
-
write_scalar t ~style value;
-
t.need_separator <- true;
-
t.state <- In_flow_mapping_key
-
end
-
| _ ->
-
if t.need_separator then write t ", ";
-
t.need_separator <- true;
-
write_anchor t anchor;
-
write_tag t ~implicit:plain_implicit tag;
-
write_scalar t ~style value)
-
end else begin
+
write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write t ": ";
+
t.need_separator <- false;
+
t.state <- In_flow_mapping_value
+
end
+
else begin
+
(* Normal value scalar *)
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
t.need_separator <- true;
+
t.state <- In_flow_mapping_key
+
end
+
| _ ->
+
if t.need_separator then write t ", ";
+
t.need_separator <- true;
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value
+
end
+
else begin
match t.state with
| In_block_sequence _ ->
write_indent t;
···
write_scalar t ~style value;
write_newline t
end
-
| Event.Sequence_start { anchor; tag; implicit; style } ->
let use_flow = style = `Flow || t.flow_level > 0 in
if t.flow_level > 0 then begin
-
(match t.state with
-
| In_flow_mapping_key ->
-
if t.need_separator then write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '[';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_mapping_value; (* After ] we'll be in value position but sequence handles it *)
-
t.state <- In_flow_sequence
-
| In_flow_mapping_value ->
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '[';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_mapping_key;
-
t.state <- In_flow_sequence
-
| _ ->
-
if t.need_separator then write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '[';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_sequence)
-
end else begin
+
match t.state with
+
| In_flow_mapping_key ->
+
if t.need_separator then write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_value;
+
(* After ] we'll be in value position but sequence handles it *)
+
t.state <- In_flow_sequence
+
| In_flow_mapping_value ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_key;
+
t.state <- In_flow_sequence
+
| _ ->
+
if t.need_separator then write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_sequence
+
end
+
else begin
match t.state with
| In_block_sequence _ ->
write_indent t;
···
t.flow_level <- t.flow_level + 1;
t.need_separator <- false;
push_state t In_flow_sequence
-
end else begin
+
end
+
else begin
write_newline t;
push_state t (In_block_sequence t.indent);
t.indent <- t.indent + t.config.indent
···
(* Save key state to return to after flow sequence *)
t.state <- In_block_mapping_key indent;
push_state t In_flow_sequence
-
end else begin
+
end
+
else begin
write_newline t;
(* Save key state to return to after nested sequence *)
t.state <- In_block_mapping_key indent;
···
t.flow_level <- t.flow_level + 1;
t.need_separator <- false;
push_state t In_flow_sequence
-
end else begin
+
end
+
else begin
push_state t (In_block_sequence t.indent);
t.state <- In_block_sequence t.indent
end
end
-
| Event.Sequence_end ->
if t.flow_level > 0 then begin
write_char t ']';
···
t.need_separator <- true;
pop_state t;
(* Write newline if returning to block context *)
-
(match t.state with
-
| In_block_mapping_key _ | In_block_sequence _ -> write_newline t
-
| _ -> ())
-
end else begin
+
match t.state with
+
| In_block_mapping_key _ | In_block_sequence _ -> write_newline t
+
| _ -> ()
+
end
+
else begin
t.indent <- t.indent - t.config.indent;
pop_state t
end
-
| Event.Mapping_start { anchor; tag; implicit; style } ->
let use_flow = style = `Flow || t.flow_level > 0 in
if t.flow_level > 0 then begin
-
(match t.state with
-
| In_flow_mapping_key ->
-
if t.need_separator then write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '{';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_mapping_value;
-
t.state <- In_flow_mapping_key
-
| In_flow_mapping_value ->
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '{';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_mapping_key;
-
t.state <- In_flow_mapping_key
-
| _ ->
-
if t.need_separator then write t ", ";
-
write_anchor t anchor;
-
write_tag t ~implicit tag;
-
write_char t '{';
-
t.flow_level <- t.flow_level + 1;
-
t.need_separator <- false;
-
push_state t In_flow_mapping_key)
-
end else begin
+
match t.state with
+
| In_flow_mapping_key ->
+
if t.need_separator then write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_value;
+
t.state <- In_flow_mapping_key
+
| In_flow_mapping_value ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_key;
+
t.state <- In_flow_mapping_key
+
| _ ->
+
if t.need_separator then write t ", ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_key
+
end
+
else begin
match t.state with
| In_block_sequence _ ->
write_indent t;
···
t.flow_level <- t.flow_level + 1;
t.need_separator <- false;
push_state t In_flow_mapping_key
-
end else begin
+
end
+
else begin
(* Don't write newline - first key goes on same line as "- " *)
push_state t (In_block_sequence t.indent);
t.indent <- t.indent + t.config.indent;
···
(* Save key state to return to after flow mapping *)
t.state <- In_block_mapping_key indent;
push_state t In_flow_mapping_key
-
end else begin
+
end
+
else begin
write_newline t;
(* Save key state to return to after nested mapping *)
t.state <- In_block_mapping_key indent;
···
t.flow_level <- t.flow_level + 1;
t.need_separator <- false;
push_state t In_flow_mapping_key
-
end else begin
+
end
+
else begin
push_state t (In_block_mapping_key t.indent);
t.state <- In_block_mapping_key t.indent
end
end
-
| Event.Mapping_end ->
if t.flow_level > 0 then begin
write_char t '}';
···
t.need_separator <- true;
pop_state t;
(* Write newline if returning to block context *)
-
(match t.state with
-
| In_block_mapping_key _ | In_block_sequence _ -> write_newline t
-
| _ -> ())
-
end else begin
+
match t.state with
+
| In_block_mapping_key _ | In_block_sequence _ -> write_newline t
+
| _ -> ()
+
end
+
else begin
t.indent <- t.indent - t.config.indent;
pop_state t
end
-
(** Access to the underlying buffer for advanced use.
-
Returns None if emitter is writing to a Writer instead of Buffer. *)
+
(** Access to the underlying buffer for advanced use. Returns None if emitter is
+
writing to a Writer instead of Buffer. *)
let buffer t =
-
match t.sink with
-
| Buffer_sink buf -> Some buf
-
| Writer_sink _ -> None
+
match t.sink with Buffer_sink buf -> Some buf | Writer_sink _ -> None
(** Get config *)
let config t = t.config
(** Check if emitter is writing to a Writer *)
let is_streaming t =
-
match t.sink with
-
| Writer_sink _ -> true
-
| Buffer_sink _ -> false
+
match t.sink with Writer_sink _ -> true | Buffer_sink _ -> false
(** Flush the writer sink (no-op for buffer sink) *)
let flush t =
+12 -20
lib/encoding.ml
···
(** Character encoding detection and handling *)
-
type t = [
-
| `Utf8
-
| `Utf16be
-
| `Utf16le
-
| `Utf32be
-
| `Utf32le
-
]
+
type t = [ `Utf8 | `Utf16be | `Utf16le | `Utf32be | `Utf32le ]
let to_string = function
| `Utf8 -> "UTF-8"
···
| `Utf32be -> "UTF-32BE"
| `Utf32le -> "UTF-32LE"
-
let pp fmt t =
-
Format.pp_print_string fmt (to_string t)
+
let pp fmt t = Format.pp_print_string fmt (to_string t)
-
(** Detect encoding from BOM or first bytes.
-
Returns (encoding, bom_length) *)
+
(** Detect encoding from BOM or first bytes. Returns (encoding, bom_length) *)
let detect s =
let len = String.length s in
if len = 0 then (`Utf8, 0)
···
let b3 = if len > 3 then Char.code s.[3] else 0 in
match (b0, b1, b2, b3) with
(* BOM patterns *)
-
| (0xEF, 0xBB, 0xBF, _) -> (`Utf8, 3)
-
| (0xFE, 0xFF, _, _) -> (`Utf16be, 2)
-
| (0xFF, 0xFE, 0x00, 0x00) -> (`Utf32le, 4)
-
| (0xFF, 0xFE, _, _) -> (`Utf16le, 2)
-
| (0x00, 0x00, 0xFE, 0xFF) -> (`Utf32be, 4)
+
| 0xEF, 0xBB, 0xBF, _ -> (`Utf8, 3)
+
| 0xFE, 0xFF, _, _ -> (`Utf16be, 2)
+
| 0xFF, 0xFE, 0x00, 0x00 -> (`Utf32le, 4)
+
| 0xFF, 0xFE, _, _ -> (`Utf16le, 2)
+
| 0x00, 0x00, 0xFE, 0xFF -> (`Utf32be, 4)
(* Content pattern detection (no BOM) *)
-
| (0x00, 0x00, 0x00, b3) when b3 <> 0x00 -> (`Utf32be, 0)
-
| (b0, 0x00, 0x00, 0x00) when b0 <> 0x00 -> (`Utf32le, 0)
-
| (0x00, b1, _, _) when b1 <> 0x00 -> (`Utf16be, 0)
-
| (b0, 0x00, _, _) when b0 <> 0x00 -> (`Utf16le, 0)
+
| 0x00, 0x00, 0x00, b3 when b3 <> 0x00 -> (`Utf32be, 0)
+
| b0, 0x00, 0x00, 0x00 when b0 <> 0x00 -> (`Utf32le, 0)
+
| 0x00, b1, _, _ when b1 <> 0x00 -> (`Utf16be, 0)
+
| b0, 0x00, _, _ when b0 <> 0x00 -> (`Utf16le, 0)
| _ -> (`Utf8, 0)
let equal a b = a = b
+135 -105
lib/error.ml
···
Comprehensive error reporting for YAML parsing and emission.
-
This module provides detailed error types that correspond to various
-
failure modes in YAML processing, as specified in the
+
This module provides detailed error types that correspond to various failure
+
modes in YAML processing, as specified in the
{{:https://yaml.org/spec/1.2.2/}YAML 1.2.2 specification}.
Each error includes:
···
- A context stack showing where the error occurred
- Optional source text for error display
-
See also {{:https://yaml.org/spec/1.2.2/#31-processes}Section 3.1 (Processes)}
-
for background on the YAML processing model. *)
+
See also
+
{{:https://yaml.org/spec/1.2.2/#31-processes}Section 3.1 (Processes)} for
+
background on the YAML processing model. *)
(** {2 Error Classification}
···
(* Scanner errors - see {{:https://yaml.org/spec/1.2.2/#51-character-set}Section 5.1} *)
| Unexpected_character of char
(** Invalid character in input. See
-
{{:https://yaml.org/spec/1.2.2/#51-character-set}Section 5.1 (Character Set)}. *)
-
| Unexpected_eof
-
(** Premature end of input. *)
+
{{:https://yaml.org/spec/1.2.2/#51-character-set}Section 5.1
+
(Character Set)}. *)
+
| Unexpected_eof (** Premature end of input. *)
| Invalid_escape_sequence of string
(** Invalid escape in double-quoted string. See
-
{{:https://yaml.org/spec/1.2.2/#57-escaped-characters}Section 5.7 (Escaped Characters)}. *)
+
{{:https://yaml.org/spec/1.2.2/#57-escaped-characters}Section 5.7
+
(Escaped Characters)}. *)
| Invalid_unicode_escape of string
(** Invalid Unicode escape sequence (\uXXXX or \UXXXXXXXX). *)
| Invalid_hex_escape of string
(** Invalid hexadecimal escape sequence (\xXX). *)
| Invalid_tag of string
(** Malformed tag syntax. See
-
{{:https://yaml.org/spec/1.2.2/#681-node-tags}Section 6.8.1 (Node Tags)}. *)
+
{{:https://yaml.org/spec/1.2.2/#681-node-tags}Section 6.8.1 (Node
+
Tags)}. *)
| Invalid_anchor of string
(** Malformed anchor name. See
-
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
| Invalid_alias of string
(** Malformed alias reference. See
-
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
| Invalid_comment
(** Comment not properly separated from content. See
-
{{:https://yaml.org/spec/1.2.2/#62-comments}Section 6.2 (Comments)}. *)
+
{{:https://yaml.org/spec/1.2.2/#62-comments}Section 6.2 (Comments)}.
+
*)
| Unclosed_single_quote
(** Unterminated single-quoted scalar. See
-
{{:https://yaml.org/spec/1.2.2/#72-single-quoted-style}Section 7.2 (Single-Quoted Style)}. *)
+
{{:https://yaml.org/spec/1.2.2/#72-single-quoted-style}Section 7.2
+
(Single-Quoted Style)}. *)
| Unclosed_double_quote
(** Unterminated double-quoted scalar. See
-
{{:https://yaml.org/spec/1.2.2/#73-double-quoted-style}Section 7.3 (Double-Quoted Style)}. *)
+
{{:https://yaml.org/spec/1.2.2/#73-double-quoted-style}Section 7.3
+
(Double-Quoted Style)}. *)
| Unclosed_flow_sequence
(** Missing closing bracket \] for flow sequence. See
-
{{:https://yaml.org/spec/1.2.2/#742-flow-sequences}Section 7.4.2 (Flow Sequences)}. *)
+
{{:https://yaml.org/spec/1.2.2/#742-flow-sequences}Section 7.4.2 (Flow
+
Sequences)}. *)
| Unclosed_flow_mapping
(** Missing closing brace \} for flow mapping. See
-
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow Mappings)}. *)
+
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow
+
Mappings)}. *)
| Invalid_indentation of int * int
(** Incorrect indentation level (expected, got). See
-
{{:https://yaml.org/spec/1.2.2/#61-indentation-spaces}Section 6.1 (Indentation Spaces)}. *)
+
{{:https://yaml.org/spec/1.2.2/#61-indentation-spaces}Section 6.1
+
(Indentation Spaces)}. *)
| Invalid_flow_indentation
(** Content in flow collection must be indented. See
-
{{:https://yaml.org/spec/1.2.2/#74-flow-styles}Section 7.4 (Flow Styles)}. *)
+
{{:https://yaml.org/spec/1.2.2/#74-flow-styles}Section 7.4 (Flow
+
Styles)}. *)
| Tab_in_indentation
(** Tab character used for indentation (only spaces allowed). See
-
{{:https://yaml.org/spec/1.2.2/#61-indentation-spaces}Section 6.1 (Indentation Spaces)}. *)
+
{{:https://yaml.org/spec/1.2.2/#61-indentation-spaces}Section 6.1
+
(Indentation Spaces)}. *)
| Invalid_block_scalar_header of string
(** Malformed block scalar header (| or >). See
-
{{:https://yaml.org/spec/1.2.2/#81-block-scalar-styles}Section 8.1 (Block Scalar Styles)}. *)
+
{{:https://yaml.org/spec/1.2.2/#81-block-scalar-styles}Section 8.1
+
(Block Scalar Styles)}. *)
| Invalid_quoted_scalar_indentation of string
(** Incorrect indentation in quoted scalar. *)
| Invalid_directive of string
(** Malformed directive. See
-
{{:https://yaml.org/spec/1.2.2/#68-directives}Section 6.8 (Directives)}. *)
+
{{:https://yaml.org/spec/1.2.2/#68-directives}Section 6.8
+
(Directives)}. *)
| Invalid_yaml_version of string
(** Unsupported YAML version in %YAML directive. See
-
{{:https://yaml.org/spec/1.2.2/#681-yaml-directives}Section 6.8.1 (YAML Directives)}. *)
+
{{:https://yaml.org/spec/1.2.2/#681-yaml-directives}Section 6.8.1
+
(YAML Directives)}. *)
| Invalid_tag_directive of string
(** Malformed %TAG directive. See
-
{{:https://yaml.org/spec/1.2.2/#682-tag-directives}Section 6.8.2 (TAG Directives)}. *)
+
{{:https://yaml.org/spec/1.2.2/#682-tag-directives}Section 6.8.2 (TAG
+
Directives)}. *)
| Reserved_directive of string
(** Reserved directive name. See
-
{{:https://yaml.org/spec/1.2.2/#683-reserved-directives}Section 6.8.3 (Reserved Directives)}. *)
+
{{:https://yaml.org/spec/1.2.2/#683-reserved-directives}Section 6.8.3
+
(Reserved Directives)}. *)
| Illegal_flow_key_line
(** Key and colon must be on same line in flow context. See
-
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow Mappings)}. *)
+
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow
+
Mappings)}. *)
| Block_sequence_disallowed
(** Block sequence entries not allowed in this context. See
-
{{:https://yaml.org/spec/1.2.2/#82-block-collection-styles}Section 8.2 (Block Collection Styles)}. *)
-
+
{{:https://yaml.org/spec/1.2.2/#82-block-collection-styles}Section 8.2
+
(Block Collection Styles)}. *)
(* Parser errors - see {{:https://yaml.org/spec/1.2.2/#3-processing-yaml-information}Section 3 (Processing)} *)
-
| Unexpected_token of string
-
(** Unexpected token in event stream. *)
+
| Unexpected_token of string (** Unexpected token in event stream. *)
| Expected_document_start
(** Expected document start marker (---). See
-
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2 (Document Markers)}. *)
+
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2
+
(Document Markers)}. *)
| Expected_document_end
(** Expected document end marker (...). See
-
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2 (Document Markers)}. *)
+
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2
+
(Document Markers)}. *)
| Expected_block_entry
(** Expected block sequence entry marker (-). See
-
{{:https://yaml.org/spec/1.2.2/#821-block-sequences}Section 8.2.1 (Block Sequences)}. *)
+
{{:https://yaml.org/spec/1.2.2/#821-block-sequences}Section 8.2.1
+
(Block Sequences)}. *)
| Expected_key
(** Expected mapping key. See
-
{{:https://yaml.org/spec/1.2.2/#822-block-mappings}Section 8.2.2 (Block Mappings)}. *)
+
{{:https://yaml.org/spec/1.2.2/#822-block-mappings}Section 8.2.2
+
(Block Mappings)}. *)
| Expected_value
(** Expected mapping value after colon. See
-
{{:https://yaml.org/spec/1.2.2/#822-block-mappings}Section 8.2.2 (Block Mappings)}. *)
-
| Expected_node
-
(** Expected a YAML node. *)
-
| Expected_scalar
-
(** Expected a scalar value. *)
+
{{:https://yaml.org/spec/1.2.2/#822-block-mappings}Section 8.2.2
+
(Block Mappings)}. *)
+
| Expected_node (** Expected a YAML node. *)
+
| Expected_scalar (** Expected a scalar value. *)
| Expected_sequence_end
(** Expected closing bracket \] for flow sequence. See
-
{{:https://yaml.org/spec/1.2.2/#742-flow-sequences}Section 7.4.2 (Flow Sequences)}. *)
+
{{:https://yaml.org/spec/1.2.2/#742-flow-sequences}Section 7.4.2 (Flow
+
Sequences)}. *)
| Expected_mapping_end
(** Expected closing brace \} for flow mapping. See
-
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow Mappings)}. *)
+
{{:https://yaml.org/spec/1.2.2/#743-flow-mappings}Section 7.4.3 (Flow
+
Mappings)}. *)
| Duplicate_anchor of string
(** Anchor name defined multiple times. See
-
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
| Undefined_alias of string
(** Alias references non-existent anchor. See
-
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
| Alias_cycle of string
(** Circular reference in alias chain. See
-
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
| Multiple_documents
(** Multiple documents found when single document expected. See
-
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2 (Document Markers)}. *)
+
{{:https://yaml.org/spec/1.2.2/#912-document-markers}Section 9.1.2
+
(Document Markers)}. *)
| Mapping_key_too_long
(** Mapping key exceeds maximum length (1024 characters). *)
-
(* Loader errors - see {{:https://yaml.org/spec/1.2.2/#31-processes}Section 3.1 (Processes)} *)
| Invalid_scalar_conversion of string * string
-
(** Cannot convert scalar value to target type (value, target type).
-
See {{:https://yaml.org/spec/1.2.2/#103-core-schema}Section 10.3 (Core Schema)}. *)
+
(** Cannot convert scalar value to target type (value, target type). See
+
{{:https://yaml.org/spec/1.2.2/#103-core-schema}Section 10.3 (Core
+
Schema)}. *)
| Type_mismatch of string * string
(** Value has wrong type for operation (expected, got). *)
| Unresolved_alias of string
-
(** Alias encountered during conversion but not resolved.
-
See {{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2 (Anchors and Aliases)}. *)
-
| Key_not_found of string
-
(** Mapping key not found. *)
+
(** Alias encountered during conversion but not resolved. See
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section
+
3.2.2.2 (Anchors and Aliases)}. *)
+
| Key_not_found of string (** Mapping key not found. *)
| Alias_expansion_node_limit of int
-
(** Alias expansion exceeded maximum node count (protection against billion laughs attack).
-
See {{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1 (Processes)}.
+
(** Alias expansion exceeded maximum node count (protection against
+
billion laughs attack). See
+
{{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1
+
(Processes)}.
-
The "billion laughs attack" (also known as an XML bomb) is a denial-of-service
-
attack where a small YAML document expands to enormous size through recursive
-
alias expansion. This limit prevents such attacks. *)
+
The "billion laughs attack" (also known as an XML bomb) is a
+
denial-of-service attack where a small YAML document expands to
+
enormous size through recursive alias expansion. This limit prevents
+
such attacks. *)
| Alias_expansion_depth_limit of int
-
(** Alias expansion exceeded maximum nesting depth (protection against deeply nested aliases).
-
See {{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1 (Processes)}. *)
-
+
(** Alias expansion exceeded maximum nesting depth (protection against
+
deeply nested aliases). See
+
{{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1
+
(Processes)}. *)
(* Emitter errors *)
| Invalid_encoding of string
(** Invalid character encoding specified. See
-
{{:https://yaml.org/spec/1.2.2/#51-character-set}Section 5.1 (Character Set)}. *)
+
{{:https://yaml.org/spec/1.2.2/#51-character-set}Section 5.1
+
(Character Set)}. *)
| Scalar_contains_invalid_chars of string
(** Scalar contains characters invalid for chosen style. *)
-
| Anchor_not_set
-
(** Attempted to emit alias before anchor was defined. *)
+
| Anchor_not_set (** Attempted to emit alias before anchor was defined. *)
| Invalid_state of string
(** Emitter in invalid state for requested operation. *)
-
(* Generic *)
-
| Custom of string
-
(** Custom error message. *)
+
| Custom of string (** Custom error message. *)
-
(** {2 Error Value}
-
-
Full error information including classification, location, and context. *)
type t = {
-
kind : kind;
-
(** The specific error classification. *)
+
kind : kind; (** The specific error classification. *)
span : Span.t option;
(** Source location where the error occurred (if available). *)
context : string list;
···
source : string option;
(** Source text for displaying the error in context. *)
}
+
(** {2 Error Value}
+
Full error information including classification, location, and context. *)
+
+
exception Yamlrw_error of t
(** {2 Exception}
The main exception type raised by all yamlrw operations.
-
All parsing, loading, and emitting errors are reported by raising
-
this exception with detailed error information. *)
-
exception Yamlrw_error of t
+
All parsing, loading, and emitting errors are reported by raising this
+
exception with detailed error information. *)
let () =
Printexc.register_printer (function
| Yamlrw_error e ->
-
let loc = match e.span with
+
let loc =
+
match e.span with
| None -> ""
| Some span -> " at " ^ Span.to_string span
in
-
Some (Printf.sprintf "Yamlrw_error: %s%s"
-
(match e.kind with Custom s -> s | _ -> "error") loc)
+
Some
+
(Printf.sprintf "Yamlrw_error: %s%s"
+
(match e.kind with Custom s -> s | _ -> "error")
+
loc)
| _ -> None)
(** {2 Error Construction} *)
···
@param context Context stack (defaults to empty)
@param source Source text
@param kind Error classification *)
-
let make ?span ?(context=[]) ?source kind =
-
{ kind; span; context; source }
+
let make ?span ?(context = []) ?source kind = { kind; span; context; source }
(** [raise ?span ?context ?source kind] constructs and raises an error.
···
@param span Source span
@param kind Error classification
@raise Yamlrw_error *)
-
let raise_span span kind =
-
raise ~span kind
+
let raise_span span kind = raise ~span kind
-
(** [with_context ctx f] executes [f ()] and adds [ctx] to any raised error's context.
+
(** [with_context ctx f] executes [f ()] and adds [ctx] to any raised error's
+
context.
This is useful for tracking the processing path through nested structures.
@param ctx Context description (e.g., "parsing mapping key")
@param f Function to execute *)
let with_context ctx f =
-
try f () with
-
| Yamlrw_error e ->
-
Stdlib.raise (Yamlrw_error { e with context = ctx :: e.context })
+
try f ()
+
with Yamlrw_error e ->
+
Stdlib.raise (Yamlrw_error { e with context = ctx :: e.context })
(** {2 Error Formatting} *)
···
| Invalid_tag s -> Printf.sprintf "invalid tag: %s" s
| Invalid_anchor s -> Printf.sprintf "invalid anchor: %s" s
| Invalid_alias s -> Printf.sprintf "invalid alias: %s" s
-
| Invalid_comment -> "comments must be separated from other tokens by whitespace"
+
| Invalid_comment ->
+
"comments must be separated from other tokens by whitespace"
| Unclosed_single_quote -> "unclosed single quote"
| Unclosed_double_quote -> "unclosed double quote"
| Unclosed_flow_sequence -> "unclosed flow sequence '['"
···
| Tab_in_indentation -> "tab character in indentation"
| Invalid_block_scalar_header s ->
Printf.sprintf "invalid block scalar header: %s" s
-
| Invalid_quoted_scalar_indentation s ->
-
Printf.sprintf "%s" s
+
| Invalid_quoted_scalar_indentation s -> Printf.sprintf "%s" s
| Invalid_directive s -> Printf.sprintf "invalid directive: %s" s
| Invalid_yaml_version s -> Printf.sprintf "invalid YAML version: %s" s
| Invalid_tag_directive s -> Printf.sprintf "invalid TAG directive: %s" s
| Reserved_directive s -> Printf.sprintf "reserved directive: %s" s
-
| Illegal_flow_key_line -> "key and ':' must be on the same line in flow context"
-
| Block_sequence_disallowed -> "block sequence entries are not allowed in this context"
+
| Illegal_flow_key_line ->
+
"key and ':' must be on the same line in flow context"
+
| Block_sequence_disallowed ->
+
"block sequence entries are not allowed in this context"
| Unexpected_token s -> Printf.sprintf "unexpected token: %s" s
| Expected_document_start -> "expected document start '---'"
| Expected_document_end -> "expected document end '...'"
···
Includes error kind, source location (if available), and context stack. *)
let to_string t =
-
let loc = match t.span with
-
| None -> ""
-
| Some span -> " at " ^ Span.to_string span
+
let loc =
+
match t.span with None -> "" | Some span -> " at " ^ Span.to_string span
in
-
let ctx = match t.context with
+
let ctx =
+
match t.context with
| [] -> ""
| ctxs -> " (in " ^ String.concat " > " (List.rev ctxs) ^ ")"
in
kind_to_string t.kind ^ loc ^ ctx
(** [pp fmt t] pretty-prints an error to a formatter. *)
-
let pp fmt t =
-
Format.fprintf fmt "Yamlrw error: %s" (to_string t)
+
let pp fmt t = Format.fprintf fmt "Yamlrw error: %s" (to_string t)
(** [pp_with_source ~source fmt t] pretty-prints an error with source context.
-
Shows the error message followed by the relevant source line with
-
a caret (^) pointing to the error location.
+
Shows the error message followed by the relevant source line with a caret
+
(^) pointing to the error location.
@param source The source text
@param fmt Output formatter
@param t The error to display *)
let pp_with_source ~source fmt t =
-
let extract_line source line_num =
-
let lines = String.split_on_char '\n' source in
-
if line_num >= 1 && line_num <= List.length lines then
-
Some (List.nth lines (line_num - 1))
-
else
-
None
+
let extract_line source line_num =
+
let lines = String.split_on_char '\n' source in
+
if line_num >= 1 && line_num <= List.length lines then
+
Some (List.nth lines (line_num - 1))
+
else None
in
pp fmt t;
match t.span with
| None -> ()
-
| Some span ->
+
| Some span -> (
match extract_line source span.start.line with
| None -> ()
| Some line ->
Format.fprintf fmt "\n %d | %s\n" span.start.line line;
let padding = String.make (span.start.column - 1) ' ' in
-
Format.fprintf fmt " | %s^" padding
+
Format.fprintf fmt " | %s^" padding)
+10 -18
lib/event.ml
···
type t =
| Stream_start of { encoding : Encoding.t }
| Stream_end
-
| Document_start of {
-
version : (int * int) option;
-
implicit : bool;
-
}
+
| Document_start of { version : (int * int) option; implicit : bool }
| Document_end of { implicit : bool }
| Alias of { anchor : string }
| Scalar of {
···
}
| Mapping_end
-
type spanned = {
-
event : t;
-
span : Span.t;
-
}
+
type spanned = { event : t; span : Span.t }
let pp_opt_str = Option.value ~default:"none"
let pp fmt = function
| Stream_start { encoding } ->
Format.fprintf fmt "stream-start(%a)" Encoding.pp encoding
-
| Stream_end ->
-
Format.fprintf fmt "stream-end"
+
| Stream_end -> Format.fprintf fmt "stream-end"
| Document_start { version; implicit } ->
-
let version_str = match version with
+
let version_str =
+
match version with
| None -> "none"
| Some (maj, min) -> Printf.sprintf "%d.%d" maj min
in
-
Format.fprintf fmt "document-start(version=%s, implicit=%b)" version_str implicit
+
Format.fprintf fmt "document-start(version=%s, implicit=%b)" version_str
+
implicit
| Document_end { implicit } ->
Format.fprintf fmt "document-end(implicit=%b)" implicit
-
| Alias { anchor } ->
-
Format.fprintf fmt "alias(%s)" anchor
+
| Alias { anchor } -> Format.fprintf fmt "alias(%s)" anchor
| Scalar { anchor; tag; value; style; _ } ->
Format.fprintf fmt "scalar(anchor=%s, tag=%s, style=%a, value=%S)"
(pp_opt_str anchor) (pp_opt_str tag) Scalar_style.pp style value
| Sequence_start { anchor; tag; implicit; style } ->
Format.fprintf fmt "sequence-start(anchor=%s, tag=%s, implicit=%b, style=%a)"
(pp_opt_str anchor) (pp_opt_str tag) implicit Layout_style.pp style
-
| Sequence_end ->
-
Format.fprintf fmt "sequence-end"
+
| Sequence_end -> Format.fprintf fmt "sequence-end"
| Mapping_start { anchor; tag; implicit; style } ->
Format.fprintf fmt "mapping-start(anchor=%s, tag=%s, implicit=%b, style=%a)"
(pp_opt_str anchor) (pp_opt_str tag) implicit Layout_style.pp style
-
| Mapping_end ->
-
Format.fprintf fmt "mapping-end"
+
| Mapping_end -> Format.fprintf fmt "mapping-end"
let pp_spanned fmt { event; span } =
Format.fprintf fmt "%a at %a" pp event Span.pp span
+50 -71
lib/input.ml
···
(** Character input source with lookahead, based on Bytes.Reader.t
-
This module wraps a bytesrw [Bytes.Reader.t] to provide character-by-character
-
access with lookahead for the YAML scanner. Uses bytesrw's sniff and push_back
-
for efficient lookahead without excessive copying.
+
This module wraps a bytesrw [Bytes.Reader.t] to provide
+
character-by-character access with lookahead for the YAML scanner. Uses
+
bytesrw's sniff and push_back for efficient lookahead without excessive
+
copying.
The same input type works with any reader source: strings, files, channels,
or streaming sources like Eio. *)
open Bytesrw
-
(** Re-export character classification *)
include Char_class
+
(** Re-export character classification *)
type t = {
reader : Bytes.Reader.t;
-
mutable current_slice : Bytes.Slice.t option; (** Current slice being consumed *)
-
mutable slice_pos : int; (** Position within current slice *)
-
mutable position : Position.t; (** Line/column tracking *)
+
mutable current_slice : Bytes.Slice.t option;
+
(** Current slice being consumed *)
+
mutable slice_pos : int; (** Position within current slice *)
+
mutable position : Position.t; (** Line/column tracking *)
}
(** Ensure we have a current slice. Returns true if data available. *)
···
if Bytes.Slice.is_eod slice then begin
t.current_slice <- None;
false
-
end else begin
+
end
+
else begin
t.current_slice <- Some slice;
t.slice_pos <- 0;
true
···
(** Create input from a Bytes.Reader.t *)
let of_reader ?(initial_position = Position.initial) reader =
-
let t = {
-
reader;
-
current_slice = None;
-
slice_pos = 0;
-
position = initial_position;
-
} in
+
let t =
+
{ reader; current_slice = None; slice_pos = 0; position = initial_position }
+
in
(* Use sniff for BOM detection - this is exactly what sniff is for *)
let sample = Bytes.Reader.sniff 4 t.reader in
let bom_len =
-
if String.length sample >= 3 &&
-
sample.[0] = '\xEF' &&
-
sample.[1] = '\xBB' &&
-
sample.[2] = '\xBF'
-
then 3 (* UTF-8 BOM *)
+
if
+
String.length sample >= 3
+
&& sample.[0] = '\xEF'
+
&& sample.[1] = '\xBB'
+
&& sample.[2] = '\xBF'
+
then 3 (* UTF-8 BOM *)
else 0
in
(* Skip BOM if present *)
-
if bom_len > 0 then
-
Bytes.Reader.skip bom_len t.reader;
+
if bom_len > 0 then Bytes.Reader.skip bom_len t.reader;
t
(** Create input from a string *)
···
of_reader reader
let position t = t.position
-
-
let is_eof t =
-
not (ensure_slice t)
-
-
let peek t =
-
if ensure_slice t then
-
peek_current t
-
else
-
None
+
let is_eof t = not (ensure_slice t)
+
let peek t = if ensure_slice t then peek_current t else None
let peek_exn t =
match peek t with
···
let sample_offset = n - slice_remaining in
if sample_offset < String.length sample then
Some sample.[sample_offset]
-
else
-
None
+
else None
end
-
| None ->
-
if n < String.length sample then
-
Some sample.[n]
-
else
-
None
+
| None -> if n < String.length sample then Some sample.[n] else None
end
(** Peek at up to n characters as a string *)
···
let needed_from_reader = n - slice_remaining in
let sample = Bytes.Reader.sniff needed_from_reader t.reader in
let buf = Buffer.create n in
-
Buffer.add_subbytes buf slice_bytes (slice_first + t.slice_pos) slice_remaining;
+
Buffer.add_subbytes buf slice_bytes
+
(slice_first + t.slice_pos)
+
slice_remaining;
Buffer.add_string buf sample;
Buffer.contents buf
end
-
| None ->
-
if ensure_slice t then
-
peek_string t n
-
else
-
""
+
| None -> if ensure_slice t then peek_string t n else ""
end
(** Consume next character *)
···
t.slice_pos <- t.slice_pos + 1;
t.position <- Position.advance_char c t.position;
(* Check if we've exhausted this slice *)
-
if t.slice_pos >= Bytes.Slice.length slice then
-
t.current_slice <- None;
+
if t.slice_pos >= Bytes.Slice.length slice then t.current_slice <- None;
Some c
| None -> None
-
end else
-
None
+
end
+
else None
let next_exn t =
match next t with
···
let skip_while t pred =
let rec loop () =
match peek t with
-
| Some c when pred c -> ignore (next t); loop ()
+
| Some c when pred c ->
+
ignore (next t);
+
loop ()
| _ -> ()
in
loop ()
(** Check if next char satisfies predicate *)
-
let next_is pred t =
-
match peek t with
-
| None -> false
-
| Some c -> pred c
+
let next_is pred t = match peek t with None -> false | Some c -> pred c
let next_is_break t = next_is is_break t
let next_is_blank t = next_is is_blank t
···
if len < 3 then false
else
let prefix = String.sub s 0 3 in
-
(prefix = "---" || prefix = "...") &&
-
(len = 3 || is_whitespace s.[3])
+
(prefix = "---" || prefix = "...") && (len = 3 || is_whitespace s.[3])
end
(** Consume line break, handling \r\n as single break *)
let consume_break t =
match peek t with
-
| Some '\r' ->
+
| Some '\r' -> (
ignore (next t);
-
(match peek t with
-
| Some '\n' -> ignore (next t)
-
| _ -> ())
-
| Some '\n' ->
-
ignore (next t)
+
match peek t with Some '\n' -> ignore (next t) | _ -> ())
+
| Some '\n' -> ignore (next t)
| _ -> ()
(** Get remaining content from current position *)
···
let buf = Buffer.create 256 in
(* Add current slice remainder *)
(match t.current_slice with
-
| Some slice ->
-
let bytes = Bytes.Slice.bytes slice in
-
let first = Bytes.Slice.first slice in
-
let remaining = Bytes.Slice.length slice - t.slice_pos in
-
if remaining > 0 then
-
Buffer.add_subbytes buf bytes (first + t.slice_pos) remaining
-
| None -> ());
+
| Some slice ->
+
let bytes = Bytes.Slice.bytes slice in
+
let first = Bytes.Slice.first slice in
+
let remaining = Bytes.Slice.length slice - t.slice_pos in
+
if remaining > 0 then
+
Buffer.add_subbytes buf bytes (first + t.slice_pos) remaining
+
| None -> ());
(* Add remaining from reader *)
Bytes.Reader.add_to_buffer buf t.reader;
Buffer.contents buf
···
Some (Stdlib.Bytes.get bytes (first + t.slice_pos - 1))
| _ -> None
-
(** Get a sample of the source for encoding detection.
-
Uses sniff to peek without consuming. *)
+
(** Get a sample of the source for encoding detection. Uses sniff to peek
+
without consuming. *)
let source t =
(* First check current slice *)
match t.current_slice with
···
Bytes.Reader.sniff 4 t.reader
(** Get the byte position in the underlying stream *)
-
let byte_pos t =
-
Bytes.Reader.pos t.reader
+
let byte_pos t = Bytes.Reader.pos t.reader
+7 -18
lib/layout_style.ml
···
(** Collection layout styles *)
-
type t = [
-
| `Any (** Let emitter choose *)
-
| `Block (** Indentation-based *)
-
| `Flow (** Inline with brackets *)
-
]
+
type t =
+
[ `Any (** Let emitter choose *)
+
| `Block (** Indentation-based *)
+
| `Flow (** Inline with brackets *) ]
-
let to_string = function
-
| `Any -> "any"
-
| `Block -> "block"
-
| `Flow -> "flow"
-
-
let pp fmt t =
-
Format.pp_print_string fmt (to_string t)
-
+
let to_string = function `Any -> "any" | `Block -> "block" | `Flow -> "flow"
+
let pp fmt t = Format.pp_print_string fmt (to_string t)
let equal a b = a = b
let compare a b =
-
let to_int = function
-
| `Any -> 0
-
| `Block -> 1
-
| `Flow -> 2
-
in
+
let to_int = function `Any -> 0 | `Block -> 1 | `Flow -> 2 in
Int.compare (to_int a) (to_int b)
+138 -162
lib/loader.ml
···
mutable doc_implicit_start : bool;
}
-
let create_state () = {
-
stack = [];
-
current = None;
-
documents = [];
-
doc_version = None;
-
doc_implicit_start = true;
-
}
+
let create_state () =
+
{
+
stack = [];
+
current = None;
+
documents = [];
+
doc_version = None;
+
doc_implicit_start = true;
+
}
(** Process a single event *)
let rec process_event state (ev : Event.spanned) =
match ev.event with
| Event.Stream_start _ -> ()
-
| Event.Stream_end -> ()
-
| Event.Document_start { version; implicit } ->
state.doc_version <- version;
state.doc_implicit_start <- implicit
-
| Event.Document_end { implicit } ->
-
let doc = Document.make
-
?version:state.doc_version
-
~implicit_start:state.doc_implicit_start
-
~implicit_end:implicit
-
state.current
+
let doc =
+
Document.make ?version:state.doc_version
+
~implicit_start:state.doc_implicit_start ~implicit_end:implicit
+
state.current
in
state.documents <- doc :: state.documents;
state.current <- None;
state.doc_version <- None;
state.doc_implicit_start <- true
-
| Event.Alias { anchor } ->
let node : Yaml.t = `Alias anchor in
add_node state node
-
-
| Event.Scalar { anchor; tag; value; plain_implicit; quoted_implicit; style } ->
-
let scalar = Scalar.make
-
?anchor ?tag
-
~plain_implicit ~quoted_implicit
-
~style value
+
| Event.Scalar { anchor; tag; value; plain_implicit; quoted_implicit; style }
+
->
+
let scalar =
+
Scalar.make ?anchor ?tag ~plain_implicit ~quoted_implicit ~style value
in
let node : Yaml.t = `Scalar scalar in
add_node state node
-
| Event.Sequence_start { anchor; tag; implicit; style } ->
-
let frame = Sequence_frame {
-
anchor; tag; implicit; style;
-
items = [];
-
} in
+
let frame = Sequence_frame { anchor; tag; implicit; style; items = [] } in
state.stack <- frame :: state.stack
-
-
| Event.Sequence_end ->
-
(match state.stack with
-
| Sequence_frame { anchor; tag; implicit; style; items } :: rest ->
-
let seq = Sequence.make ?anchor ?tag ~implicit ~style (List.rev items) in
-
let node : Yaml.t = `A seq in
-
state.stack <- rest;
-
add_node state node
-
| _ -> Error.raise (Invalid_state "unexpected sequence end"))
-
+
| Event.Sequence_end -> (
+
match state.stack with
+
| Sequence_frame { anchor; tag; implicit; style; items } :: rest ->
+
let seq =
+
Sequence.make ?anchor ?tag ~implicit ~style (List.rev items)
+
in
+
let node : Yaml.t = `A seq in
+
state.stack <- rest;
+
add_node state node
+
| _ -> Error.raise (Invalid_state "unexpected sequence end"))
| Event.Mapping_start { anchor; tag; implicit; style } ->
-
let frame = Mapping_frame {
-
anchor; tag; implicit; style;
-
pairs = [];
-
pending_key = None;
-
} in
+
let frame =
+
Mapping_frame
+
{ anchor; tag; implicit; style; pairs = []; pending_key = None }
+
in
state.stack <- frame :: state.stack
-
-
| Event.Mapping_end ->
-
(match state.stack with
-
| Mapping_frame { anchor; tag; implicit; style; pairs; pending_key = None } :: rest ->
-
let map = Mapping.make ?anchor ?tag ~implicit ~style (List.rev pairs) in
-
let node : Yaml.t = `O map in
-
state.stack <- rest;
-
add_node state node
-
| Mapping_frame { pending_key = Some _; _ } :: _ ->
-
Error.raise (Invalid_state "mapping ended with pending key")
-
| _ -> Error.raise (Invalid_state "unexpected mapping end"))
+
| Event.Mapping_end -> (
+
match state.stack with
+
| Mapping_frame
+
{ anchor; tag; implicit; style; pairs; pending_key = None }
+
:: rest ->
+
let map =
+
Mapping.make ?anchor ?tag ~implicit ~style (List.rev pairs)
+
in
+
let node : Yaml.t = `O map in
+
state.stack <- rest;
+
add_node state node
+
| Mapping_frame { pending_key = Some _; _ } :: _ ->
+
Error.raise (Invalid_state "mapping ended with pending key")
+
| _ -> Error.raise (Invalid_state "unexpected mapping end"))
(** Add a node to current context *)
and add_node state node =
match state.stack with
-
| [] ->
-
state.current <- Some node
-
+
| [] -> state.current <- Some node
| Sequence_frame f :: rest ->
state.stack <- Sequence_frame { f with items = node :: f.items } :: rest
-
-
| Mapping_frame f :: rest ->
-
(match f.pending_key with
-
| None ->
-
(* This is a key *)
-
state.stack <- Mapping_frame { f with pending_key = Some node } :: rest
-
| Some key ->
-
(* This is a value *)
-
state.stack <- Mapping_frame {
-
f with
-
pairs = (key, node) :: f.pairs;
-
pending_key = None;
-
} :: rest)
+
| Mapping_frame f :: rest -> (
+
match f.pending_key with
+
| None ->
+
(* This is a key *)
+
state.stack <-
+
Mapping_frame { f with pending_key = Some node } :: rest
+
| Some key ->
+
(* This is a value *)
+
state.stack <-
+
Mapping_frame
+
{ f with pairs = (key, node) :: f.pairs; pending_key = None }
+
:: rest)
(** Internal: parse all documents from a parser *)
let parse_all_documents parser =
···
@param resolve_aliases Whether to resolve aliases (default true)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let value_of_string
-
?(resolve_aliases = true)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let value_of_string ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
s =
+
?(max_depth = Yaml.default_max_alias_depth) s =
let docs = parse_all_documents (Parser.of_string s) in
let doc = single_document_or_error docs ~empty:(Document.make None) in
match Document.root doc with
| None -> `Null
| Some yaml ->
-
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
+
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth
+
yaml
(** Load single document as Yaml.
@param resolve_aliases Whether to resolve aliases (default false for Yaml.t)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let yaml_of_string
-
?(resolve_aliases = false)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let yaml_of_string ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
s =
+
?(max_depth = Yaml.default_max_alias_depth) s =
let docs = parse_all_documents (Parser.of_string s) in
let doc = single_document_or_error docs ~empty:(Document.make None) in
match Document.root doc with
| None -> `Scalar (Scalar.make "")
| Some yaml ->
-
if resolve_aliases then
-
Yaml.resolve_aliases ~max_nodes ~max_depth yaml
-
else
-
yaml
+
if resolve_aliases then Yaml.resolve_aliases ~max_nodes ~max_depth yaml
+
else yaml
(** Load all documents *)
let documents_of_string s =
···
@param resolve_aliases Whether to resolve aliases (default true)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let value_of_reader
-
?(resolve_aliases = true)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let value_of_reader ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
reader =
+
?(max_depth = Yaml.default_max_alias_depth) reader =
let docs = parse_all_documents (Parser.of_reader reader) in
let doc = single_document_or_error docs ~empty:(Document.make None) in
match Document.root doc with
| None -> `Null
| Some yaml ->
-
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
+
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth
+
yaml
(** Load single document as Yaml from a Bytes.Reader.
@param resolve_aliases Whether to resolve aliases (default false for Yaml.t)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let yaml_of_reader
-
?(resolve_aliases = false)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let yaml_of_reader ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
reader =
+
?(max_depth = Yaml.default_max_alias_depth) reader =
let docs = parse_all_documents (Parser.of_reader reader) in
let doc = single_document_or_error docs ~empty:(Document.make None) in
match Document.root doc with
| None -> `Scalar (Scalar.make "")
| Some yaml ->
-
if resolve_aliases then
-
Yaml.resolve_aliases ~max_nodes ~max_depth yaml
-
else
-
yaml
+
if resolve_aliases then Yaml.resolve_aliases ~max_nodes ~max_depth yaml
+
else yaml
(** Load all documents from a Bytes.Reader *)
let documents_of_reader reader =
···
let rec loop () =
match next_event () with
| None -> None
-
| Some ev ->
+
| Some ev -> (
process_event state ev;
match ev.event with
-
| Event.Document_end _ ->
-
(match state.documents with
-
| doc :: _ ->
-
state.documents <- [];
-
Some (extract doc)
-
| [] -> None)
+
| Event.Document_end _ -> (
+
match state.documents with
+
| doc :: _ ->
+
state.documents <- [];
+
Some (extract doc)
+
| [] -> None)
| Event.Stream_end -> None
-
| _ -> loop ()
+
| _ -> loop ())
in
loop ()
···
@param resolve_aliases Whether to resolve aliases (default true)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let load_value
-
?(resolve_aliases = true)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let load_value ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
parser =
-
load_generic (fun doc ->
-
match Document.root doc with
-
| None -> `Null
-
| Some yaml ->
-
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
-
) parser
+
?(max_depth = Yaml.default_max_alias_depth) parser =
+
load_generic
+
(fun doc ->
+
match Document.root doc with
+
| None -> `Null
+
| Some yaml ->
+
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes
+
~max_depth yaml)
+
parser
(** Load single Yaml from parser *)
let load_yaml parser =
-
load_generic (fun doc ->
-
Document.root doc |> Option.value ~default:(`Scalar (Scalar.make ""))
-
) parser
+
load_generic
+
(fun doc ->
+
Document.root doc |> Option.value ~default:(`Scalar (Scalar.make "")))
+
parser
(** Load single Document from parser *)
-
let load_document parser =
-
load_generic Fun.id parser
+
let load_document parser = load_generic Fun.id parser
(** Iterate over documents *)
let iter_documents f parser =
let rec loop () =
match load_document parser with
| None -> ()
-
| Some doc -> f doc; loop ()
+
| Some doc ->
+
f doc;
+
loop ()
in
loop ()
(** Fold over documents *)
let fold_documents f init parser =
let rec loop acc =
-
match load_document parser with
-
| None -> acc
-
| Some doc -> loop (f acc doc)
+
match load_document parser with None -> acc | Some doc -> loop (f acc doc)
in
loop init
+
(** Load single Value from event source.
@param resolve_aliases Whether to resolve aliases (default true)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let value_of_parser
-
?(resolve_aliases = true)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let value_of_parser ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
next_event =
-
match load_generic_fn (fun doc ->
-
match Document.root doc with
-
| None -> `Null
-
| Some yaml ->
-
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml
-
) next_event with
+
?(max_depth = Yaml.default_max_alias_depth) next_event =
+
match
+
load_generic_fn
+
(fun doc ->
+
match Document.root doc with
+
| None -> `Null
+
| Some yaml ->
+
Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes
+
~max_depth yaml)
+
next_event
+
with
| Some v -> v
| None -> `Null
···
@param resolve_aliases Whether to resolve aliases (default false)
@param max_nodes Maximum nodes during alias expansion (default 10M)
-
@param max_depth Maximum alias nesting depth (default 100)
-
*)
-
let yaml_of_parser
-
?(resolve_aliases = false)
+
@param max_depth Maximum alias nesting depth (default 100) *)
+
let yaml_of_parser ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
next_event =
-
match load_generic_fn (fun doc ->
-
match Document.root doc with
-
| None -> `Scalar (Scalar.make "")
-
| Some yaml ->
-
if resolve_aliases then
-
Yaml.resolve_aliases ~max_nodes ~max_depth yaml
-
else
-
yaml
-
) next_event with
+
?(max_depth = Yaml.default_max_alias_depth) next_event =
+
match
+
load_generic_fn
+
(fun doc ->
+
match Document.root doc with
+
| None -> `Scalar (Scalar.make "")
+
| Some yaml ->
+
if resolve_aliases then
+
Yaml.resolve_aliases ~max_nodes ~max_depth yaml
+
else yaml)
+
next_event
+
with
| Some v -> v
| None -> `Scalar (Scalar.make "")
(** Load single Document from event source *)
-
let document_of_parser next_event =
-
load_generic_fn Fun.id next_event
+
let document_of_parser next_event = load_generic_fn Fun.id next_event
(** Load all documents from event source *)
let documents_of_parser next_event =
···
let rec loop () =
match document_of_parser next_event with
| None -> ()
-
| Some doc -> f doc; loop ()
+
| Some doc ->
+
f doc;
+
loop ()
in
loop ()
+38 -41
lib/mapping.ml
···
members : ('k * 'v) list;
}
-
let make
-
?(anchor : string option)
-
?(tag : string option)
-
?(implicit = true)
-
?(style = `Any)
-
members =
+
let make ?(anchor : string option) ?(tag : string option) ?(implicit = true)
+
?(style = `Any) members =
{ anchor; tag; implicit; style; members }
let members t = t.members
···
let tag t = t.tag
let implicit t = t.implicit
let style t = t.style
-
let with_anchor anchor t = { t with anchor = Some anchor }
let with_tag tag t = { t with tag = Some tag }
let with_style style t = { t with style }
-
let map_keys f t = { t with members = List.map (fun (k, v) -> (f k, v)) t.members }
-
let map_values f t = { t with members = List.map (fun (k, v) -> (k, f v)) t.members }
-
let map f t = { t with members = List.map (fun (k, v) -> f k v) t.members }
+
let map_keys f t =
+
{ t with members = List.map (fun (k, v) -> (f k, v)) t.members }
-
let length t = List.length t.members
+
let map_values f t =
+
{ t with members = List.map (fun (k, v) -> (k, f v)) t.members }
+
let map f t = { t with members = List.map (fun (k, v) -> f k v) t.members }
+
let length t = List.length t.members
let is_empty t = t.members = []
let find pred t =
List.find_opt (fun (k, _) -> pred k) t.members |> Option.map snd
-
let find_key pred t =
-
List.find_opt (fun (k, _) -> pred k) t.members
-
-
let mem pred t =
-
List.exists (fun (k, _) -> pred k) t.members
-
+
let find_key pred t = List.find_opt (fun (k, _) -> pred k) t.members
+
let mem pred t = List.exists (fun (k, _) -> pred k) t.members
let keys t = List.map fst t.members
-
let values t = List.map snd t.members
-
let iter f t = List.iter (fun (k, v) -> f k v) t.members
-
let fold f init t = List.fold_left (fun acc (k, v) -> f acc k v) init t.members
let pp pp_key pp_val fmt t =
···
Option.iter (Format.fprintf fmt "tag=%s,@ ") t.tag;
Format.fprintf fmt "style=%a,@ " Layout_style.pp t.style;
Format.fprintf fmt "members={@,";
-
List.iteri (fun i (k, v) ->
-
if i > 0 then Format.fprintf fmt ",@ ";
-
Format.fprintf fmt "@[<hv 2>%a:@ %a@]" pp_key k pp_val v
-
) t.members;
+
List.iteri
+
(fun i (k, v) ->
+
if i > 0 then Format.fprintf fmt ",@ ";
+
Format.fprintf fmt "@[<hv 2>%a:@ %a@]" pp_key k pp_val v)
+
t.members;
Format.fprintf fmt "@]@,})"
let equal eq_k eq_v a b =
-
Option.equal String.equal a.anchor b.anchor &&
-
Option.equal String.equal a.tag b.tag &&
-
a.implicit = b.implicit &&
-
Layout_style.equal a.style b.style &&
-
List.equal (fun (k1, v1) (k2, v2) -> eq_k k1 k2 && eq_v v1 v2) a.members b.members
+
Option.equal String.equal a.anchor b.anchor
+
&& Option.equal String.equal a.tag b.tag
+
&& a.implicit = b.implicit
+
&& Layout_style.equal a.style b.style
+
&& List.equal
+
(fun (k1, v1) (k2, v2) -> eq_k k1 k2 && eq_v v1 v2)
+
a.members b.members
let compare cmp_k cmp_v a b =
let c = Option.compare String.compare a.anchor b.anchor in
-
if c <> 0 then c else
-
let c = Option.compare String.compare a.tag b.tag in
-
if c <> 0 then c else
-
let c = Bool.compare a.implicit b.implicit in
-
if c <> 0 then c else
-
let c = Layout_style.compare a.style b.style in
-
if c <> 0 then c else
-
let cmp_pair (k1, v1) (k2, v2) =
-
let c = cmp_k k1 k2 in
-
if c <> 0 then c else cmp_v v1 v2
-
in
-
List.compare cmp_pair a.members b.members
+
if c <> 0 then c
+
else
+
let c = Option.compare String.compare a.tag b.tag in
+
if c <> 0 then c
+
else
+
let c = Bool.compare a.implicit b.implicit in
+
if c <> 0 then c
+
else
+
let c = Layout_style.compare a.style b.style in
+
if c <> 0 then c
+
else
+
let cmp_pair (k1, v1) (k2, v2) =
+
let c = cmp_k k1 k2 in
+
if c <> 0 then c else cmp_v v1 v2
+
in
+
List.compare cmp_pair a.members b.members
+266 -305
lib/parser.ml
···
| Stream_start
| Implicit_document_start
| Document_content
-
| Document_content_done (* After parsing a node, check for unexpected content *)
+
| Document_content_done
+
(* After parsing a node, check for unexpected content *)
| Document_end
| Block_sequence_first_entry
| Block_sequence_entry
···
mutable tag_directives : (string * string) list;
mutable current_token : Token.spanned option;
mutable finished : bool;
-
mutable explicit_doc_end : bool; (** True if last doc ended with explicit ... *)
-
mutable stream_start : bool; (** True if we haven't emitted any documents yet *)
+
mutable explicit_doc_end : bool;
+
(** True if last doc ended with explicit ... *)
+
mutable stream_start : bool;
+
(** True if we haven't emitted any documents yet *)
}
-
let create scanner = {
-
scanner;
-
state = Stream_start;
-
states = [];
-
version = None;
-
tag_directives = [
-
("!", "!");
-
("!!", "tag:yaml.org,2002:");
-
];
-
current_token = None;
-
finished = false;
-
explicit_doc_end = false;
-
stream_start = true;
-
}
+
let create scanner =
+
{
+
scanner;
+
state = Stream_start;
+
states = [];
+
version = None;
+
tag_directives = [ ("!", "!"); ("!!", "tag:yaml.org,2002:") ];
+
current_token = None;
+
finished = false;
+
explicit_doc_end = false;
+
stream_start = true;
+
}
let of_string s = create (Scanner.of_string s)
let of_scanner = create
···
let current_token t =
match t.current_token with
| Some tok -> tok
-
| None ->
+
| None -> (
let tok = Scanner.next t.scanner in
t.current_token <- tok;
-
match tok with
-
| Some tok -> tok
-
| None -> Error.raise Unexpected_eof
+
match tok with Some tok -> tok | None -> Error.raise Unexpected_eof)
(** Peek at current token *)
let peek_token t =
···
t.current_token
(** Skip current token *)
-
let skip_token t =
-
t.current_token <- None
+
let skip_token t = t.current_token <- None
(** Check if current token matches predicate *)
let check t pred =
-
match peek_token t with
-
| Some tok -> pred tok.token
-
| None -> false
+
match peek_token t with Some tok -> pred tok.token | None -> false
+
(** Push state onto stack *)
-
let push_state t s =
-
t.states <- s :: t.states
+
let push_state t s = t.states <- s :: t.states
(** Pop state from stack *)
let pop_state t =
···
(** Process directives at document start *)
let process_directives t =
t.version <- None;
-
t.tag_directives <- [("!", "!"); ("!!", "tag:yaml.org,2002:")];
+
t.tag_directives <- [ ("!", "!"); ("!!", "tag:yaml.org,2002:") ];
-
while check t (function
-
| Token.Version_directive _ | Token.Tag_directive _ -> true
-
| _ -> false)
+
while
+
check t (function
+
| Token.Version_directive _ | Token.Tag_directive _ -> true
+
| _ -> false)
do
let tok = current_token t in
skip_token t;
match tok.token with
| Token.Version_directive { major; minor } ->
if t.version <> None then
-
Error.raise_span tok.span (Invalid_yaml_version "duplicate YAML directive");
+
Error.raise_span tok.span
+
(Invalid_yaml_version "duplicate YAML directive");
t.version <- Some (major, minor)
| Token.Tag_directive { handle; prefix } ->
(* Skip empty tag directives (these are reserved/unknown directives that were ignored) *)
-
if handle = "" && prefix = "" then
-
() (* Ignore reserved directives *)
+
if handle = "" && prefix = "" then () (* Ignore reserved directives *)
else begin
-
if List.mem_assoc handle t.tag_directives &&
-
handle <> "!" && handle <> "!!" then
-
Error.raise_span tok.span (Invalid_tag_directive ("duplicate tag handle: " ^ handle));
+
if
+
List.mem_assoc handle t.tag_directives
+
&& handle <> "!" && handle <> "!!"
+
then
+
Error.raise_span tok.span
+
(Invalid_tag_directive ("duplicate tag handle: " ^ handle));
t.tag_directives <- (handle, prefix) :: t.tag_directives
end
| _ -> ()
···
let anchor = ref None in
let tag = ref None in
-
while check t (function
-
| Token.Anchor _ | Token.Tag _ -> true
-
| _ -> false)
+
while
+
check t (function Token.Anchor _ | Token.Tag _ -> true | _ -> false)
do
let tok = current_token t in
skip_token t;
···
(** Empty scalar event *)
let empty_scalar_event ~anchor ~tag span =
-
Event.Scalar {
-
anchor;
-
tag;
-
value = "";
-
plain_implicit = tag = None;
-
quoted_implicit = false;
-
style = `Plain;
-
}, span
+
( Event.Scalar
+
{
+
anchor;
+
tag;
+
value = "";
+
plain_implicit = tag = None;
+
quoted_implicit = false;
+
style = `Plain;
+
},
+
span )
(** Parse stream start *)
let parse_stream_start t =
···
match tok.token with
| Token.Stream_start encoding ->
t.state <- Implicit_document_start;
-
Event.Stream_start { encoding }, tok.span
-
| _ ->
-
Error.raise_span tok.span (Unexpected_token "expected stream start")
+
(Event.Stream_start { encoding }, tok.span)
+
| _ -> Error.raise_span tok.span (Unexpected_token "expected stream start")
(** Parse document start (implicit or explicit) *)
let parse_document_start t ~implicit =
···
if not implicit then begin
let tok = current_token t in
match tok.token with
-
| Token.Document_start ->
-
skip_token t
-
| _ ->
-
Error.raise_span tok.span Expected_document_start
+
| Token.Document_start -> skip_token t
+
| _ -> Error.raise_span tok.span Expected_document_start
end;
-
let span = match peek_token t with
+
let span =
+
match peek_token t with
| Some tok -> tok.span
| None -> Span.point Position.initial
in
···
t.stream_start <- false;
push_state t Document_end;
t.state <- Document_content;
-
Event.Document_start { version = t.version; implicit }, span
+
(Event.Document_start { version = t.version; implicit }, span)
(** Parse document end *)
let parse_document_end t =
-
let implicit = not (check t (function Token.Document_end -> true | _ -> false)) in
-
let span = match peek_token t with
+
let implicit =
+
not (check t (function Token.Document_end -> true | _ -> false))
+
in
+
let span =
+
match peek_token t with
| Some tok -> tok.span
| None -> Span.point Position.initial
in
···
(* Track if this document ended explicitly with ... *)
t.explicit_doc_end <- not implicit;
t.state <- Implicit_document_start;
-
Event.Document_end { implicit }, span
+
(Event.Document_end { implicit }, span)
(** Parse node in various contexts *)
let parse_node t ~block ~indentless =
···
| Token.Alias name ->
skip_token t;
t.state <- pop_state t;
-
Event.Alias { anchor = name }, tok.span
-
-
| Token.Anchor _ | Token.Tag _ ->
+
(Event.Alias { anchor = name }, tok.span)
+
| Token.Anchor _ | Token.Tag _ -> (
let anchor, tag = parse_properties t in
let tok = current_token t in
-
(match tok.token with
-
| Token.Block_entry when indentless ->
-
t.state <- Indentless_sequence_entry;
-
Event.Sequence_start {
-
anchor; tag;
-
implicit = tag = None;
-
style = `Block;
-
}, tok.span
-
-
| Token.Block_sequence_start when block ->
-
t.state <- Block_sequence_first_entry;
-
skip_token t;
-
Event.Sequence_start {
-
anchor; tag;
-
implicit = tag = None;
-
style = `Block;
-
}, tok.span
-
-
| Token.Block_mapping_start when block ->
-
t.state <- Block_mapping_first_key;
-
skip_token t;
-
Event.Mapping_start {
-
anchor; tag;
-
implicit = tag = None;
-
style = `Block;
-
}, tok.span
-
-
| Token.Flow_sequence_start ->
-
t.state <- Flow_sequence_first_entry;
-
skip_token t;
-
Event.Sequence_start {
-
anchor; tag;
-
implicit = tag = None;
-
style = `Flow;
-
}, tok.span
-
-
| Token.Flow_mapping_start ->
-
t.state <- Flow_mapping_first_key;
-
skip_token t;
-
Event.Mapping_start {
-
anchor; tag;
-
implicit = tag = None;
-
style = `Flow;
-
}, tok.span
-
-
| Token.Scalar { style; value } ->
-
skip_token t;
-
t.state <- pop_state t;
-
let plain_implicit = tag = None && style = `Plain in
-
let quoted_implicit = tag = None && style <> `Plain in
-
Event.Scalar {
-
anchor; tag; value;
-
plain_implicit; quoted_implicit; style;
-
}, tok.span
-
-
| _ ->
-
(* Empty node *)
-
t.state <- pop_state t;
-
empty_scalar_event ~anchor ~tag tok.span)
-
+
match tok.token with
+
| Token.Block_entry when indentless ->
+
t.state <- Indentless_sequence_entry;
+
( Event.Sequence_start
+
{ anchor; tag; implicit = tag = None; style = `Block },
+
tok.span )
+
| Token.Block_sequence_start when block ->
+
t.state <- Block_sequence_first_entry;
+
skip_token t;
+
( Event.Sequence_start
+
{ anchor; tag; implicit = tag = None; style = `Block },
+
tok.span )
+
| Token.Block_mapping_start when block ->
+
t.state <- Block_mapping_first_key;
+
skip_token t;
+
( Event.Mapping_start
+
{ anchor; tag; implicit = tag = None; style = `Block },
+
tok.span )
+
| Token.Flow_sequence_start ->
+
t.state <- Flow_sequence_first_entry;
+
skip_token t;
+
( Event.Sequence_start
+
{ anchor; tag; implicit = tag = None; style = `Flow },
+
tok.span )
+
| Token.Flow_mapping_start ->
+
t.state <- Flow_mapping_first_key;
+
skip_token t;
+
( Event.Mapping_start
+
{ anchor; tag; implicit = tag = None; style = `Flow },
+
tok.span )
+
| Token.Scalar { style; value } ->
+
skip_token t;
+
t.state <- pop_state t;
+
let plain_implicit = tag = None && style = `Plain in
+
let quoted_implicit = tag = None && style <> `Plain in
+
( Event.Scalar
+
{ anchor; tag; value; plain_implicit; quoted_implicit; style },
+
tok.span )
+
| _ ->
+
(* Empty node *)
+
t.state <- pop_state t;
+
empty_scalar_event ~anchor ~tag tok.span)
| Token.Block_sequence_start when block ->
t.state <- Block_sequence_first_entry;
skip_token t;
-
Event.Sequence_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Block;
-
}, tok.span
-
+
( Event.Sequence_start
+
{ anchor = None; tag = None; implicit = true; style = `Block },
+
tok.span )
| Token.Block_mapping_start when block ->
t.state <- Block_mapping_first_key;
skip_token t;
-
Event.Mapping_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Block;
-
}, tok.span
-
+
( Event.Mapping_start
+
{ anchor = None; tag = None; implicit = true; style = `Block },
+
tok.span )
| Token.Flow_sequence_start ->
t.state <- Flow_sequence_first_entry;
skip_token t;
-
Event.Sequence_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Flow;
-
}, tok.span
-
+
( Event.Sequence_start
+
{ anchor = None; tag = None; implicit = true; style = `Flow },
+
tok.span )
| Token.Flow_mapping_start ->
t.state <- Flow_mapping_first_key;
skip_token t;
-
Event.Mapping_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Flow;
-
}, tok.span
-
+
( Event.Mapping_start
+
{ anchor = None; tag = None; implicit = true; style = `Flow },
+
tok.span )
| Token.Block_entry when indentless ->
t.state <- Indentless_sequence_entry;
-
Event.Sequence_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Block;
-
}, tok.span
-
+
( Event.Sequence_start
+
{ anchor = None; tag = None; implicit = true; style = `Block },
+
tok.span )
| Token.Scalar { style; value } ->
skip_token t;
t.state <- pop_state t;
let plain_implicit = style = `Plain in
let quoted_implicit = style <> `Plain in
-
Event.Scalar {
-
anchor = None; tag = None; value;
-
plain_implicit; quoted_implicit; style;
-
}, tok.span
-
+
( Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value;
+
plain_implicit;
+
quoted_implicit;
+
style;
+
},
+
tok.span )
| _ ->
(* Empty node *)
t.state <- pop_state t;
···
match tok.token with
| Token.Block_entry ->
skip_token t;
-
if check t (function
-
| Token.Block_entry | Token.Block_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Block_entry | Token.Block_end -> true
+
| _ -> false)
then begin
t.state <- Block_sequence_entry;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Block_sequence_entry;
parse_node t ~block:true ~indentless:false
end
| Token.Block_end ->
skip_token t;
t.state <- pop_state t;
-
Event.Sequence_end, tok.span
-
| _ ->
-
Error.raise_span tok.span Expected_block_entry
+
(Event.Sequence_end, tok.span)
+
| _ -> Error.raise_span tok.span Expected_block_entry
(** Parse block mapping key *)
let parse_block_mapping_key t =
···
match tok.token with
| Token.Key ->
skip_token t;
-
if check t (function
-
| Token.Key | Token.Value | Token.Block_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Key | Token.Value | Token.Block_end -> true
+
| _ -> false)
then begin
t.state <- Block_mapping_value;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Block_mapping_value;
parse_node t ~block:true ~indentless:true
end
···
| Token.Block_end ->
skip_token t;
t.state <- pop_state t;
-
Event.Mapping_end, tok.span
-
| _ ->
-
Error.raise_span tok.span Expected_key
+
(Event.Mapping_end, tok.span)
+
| _ -> Error.raise_span tok.span Expected_key
(** Parse block mapping value *)
let parse_block_mapping_value t =
···
match tok.token with
| Token.Value ->
skip_token t;
-
if check t (function
-
| Token.Key | Token.Value | Token.Block_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Key | Token.Value | Token.Block_end -> true
+
| _ -> false)
then begin
t.state <- Block_mapping_key;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Block_mapping_key;
parse_node t ~block:true ~indentless:true
end
···
match tok.token with
| Token.Block_entry ->
skip_token t;
-
if check t (function
-
| Token.Block_entry | Token.Key | Token.Value | Token.Block_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Block_entry | Token.Key | Token.Value | Token.Block_end ->
+
true
+
| _ -> false)
then begin
t.state <- Indentless_sequence_entry;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Indentless_sequence_entry;
parse_node t ~block:true ~indentless:false
end
| _ ->
t.state <- pop_state t;
-
Event.Sequence_end, tok.span
+
(Event.Sequence_end, tok.span)
(** Parse flow sequence *)
let rec parse_flow_sequence_entry t ~first =
···
| Token.Flow_sequence_end ->
skip_token t;
t.state <- pop_state t;
-
Event.Sequence_end, tok.span
+
(Event.Sequence_end, tok.span)
| Token.Flow_entry when not first ->
skip_token t;
parse_flow_sequence_entry_internal t
-
| _ when first ->
-
parse_flow_sequence_entry_internal t
-
| _ ->
-
Error.raise_span tok.span Expected_sequence_end
+
| _ when first -> parse_flow_sequence_entry_internal t
+
| _ -> Error.raise_span tok.span Expected_sequence_end
and parse_flow_sequence_entry_internal t =
let tok = current_token t in
···
(* Trailing comma case - don't emit empty scalar, just go back to sequence entry state *)
skip_token t;
t.state <- pop_state t;
-
Event.Sequence_end, tok.span
+
(Event.Sequence_end, tok.span)
| Token.Flow_entry ->
(* Double comma or comma after comma - invalid *)
-
Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow sequence")
+
Error.raise_span tok.span
+
(Unexpected_token "unexpected ',' in flow sequence")
| Token.Key ->
skip_token t;
t.state <- Flow_sequence_entry_mapping_key;
-
Event.Mapping_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Flow;
-
}, tok.span
+
( Event.Mapping_start
+
{ anchor = None; tag = None; implicit = true; style = `Flow },
+
tok.span )
| Token.Value ->
(* Implicit empty key mapping: [ : value ] *)
t.state <- Flow_sequence_entry_mapping_key;
-
Event.Mapping_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style = `Flow;
-
}, tok.span
+
( Event.Mapping_start
+
{ anchor = None; tag = None; implicit = true; style = `Flow },
+
tok.span )
| _ ->
push_state t Flow_sequence_entry;
parse_node t ~block:false ~indentless:false
···
(** Parse flow sequence entry mapping *)
let parse_flow_sequence_entry_mapping_key t =
let tok = current_token t in
-
if check t (function
-
| Token.Value | Token.Flow_entry | Token.Flow_sequence_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Value | Token.Flow_entry | Token.Flow_sequence_end -> true
+
| _ -> false)
then begin
t.state <- Flow_sequence_entry_mapping_value;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Flow_sequence_entry_mapping_value;
parse_node t ~block:false ~indentless:false
end
···
match tok.token with
| Token.Value ->
skip_token t;
-
if check t (function
-
| Token.Flow_entry | Token.Flow_sequence_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Flow_entry | Token.Flow_sequence_end -> true
+
| _ -> false)
then begin
t.state <- Flow_sequence_entry_mapping_end;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Flow_sequence_entry_mapping_end;
parse_node t ~block:false ~indentless:false
end
···
let parse_flow_sequence_entry_mapping_end t =
let tok = current_token t in
t.state <- Flow_sequence_entry;
-
Event.Mapping_end, tok.span
+
(Event.Mapping_end, tok.span)
(** Parse flow mapping *)
let rec parse_flow_mapping_key t ~first =
···
| Token.Flow_mapping_end ->
skip_token t;
t.state <- pop_state t;
-
Event.Mapping_end, tok.span
+
(Event.Mapping_end, tok.span)
| Token.Flow_entry when not first ->
skip_token t;
parse_flow_mapping_key_internal t
-
| _ when first ->
-
parse_flow_mapping_key_internal t
-
| _ ->
-
Error.raise_span tok.span Expected_mapping_end
+
| _ when first -> parse_flow_mapping_key_internal t
+
| _ -> Error.raise_span tok.span Expected_mapping_end
and parse_flow_mapping_key_internal t =
let tok = current_token t in
···
(* Trailing comma case - don't emit empty scalar, just return to key state *)
skip_token t;
t.state <- pop_state t;
-
Event.Mapping_end, tok.span
+
(Event.Mapping_end, tok.span)
| Token.Flow_entry ->
(* Double comma or comma after comma - invalid *)
-
Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow mapping")
+
Error.raise_span tok.span
+
(Unexpected_token "unexpected ',' in flow mapping")
| Token.Key ->
skip_token t;
-
if check t (function
-
| Token.Value | Token.Flow_entry | Token.Flow_mapping_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Value | Token.Flow_entry | Token.Flow_mapping_end -> true
+
| _ -> false)
then begin
t.state <- Flow_mapping_value;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Flow_mapping_value;
parse_node t ~block:false ~indentless:false
end
···
if empty then begin
t.state <- Flow_mapping_key;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else
+
end
+
else
match tok.token with
| Token.Value ->
skip_token t;
-
if check t (function
-
| Token.Flow_entry | Token.Flow_mapping_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Flow_entry | Token.Flow_mapping_end -> true
+
| _ -> false)
then begin
t.state <- Flow_mapping_key;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
push_state t Flow_mapping_key;
parse_node t ~block:false ~indentless:false
end
···
(** Main state machine dispatcher *)
let rec parse t =
match t.state with
-
| Stream_start ->
-
parse_stream_start t
-
-
| Implicit_document_start ->
+
| Stream_start -> parse_stream_start t
+
| Implicit_document_start -> (
(* Skip any document end markers before checking what's next *)
while check t (function Token.Document_end -> true | _ -> false) do
-
t.explicit_doc_end <- true; (* Seeing ... counts as explicit end *)
+
t.explicit_doc_end <- true;
+
(* Seeing ... counts as explicit end *)
skip_token t
done;
let tok = current_token t in
-
(match tok.token with
-
| Token.Stream_end ->
-
skip_token t;
-
t.state <- End;
-
t.finished <- true;
-
Event.Stream_end, tok.span
-
| Token.Version_directive _ | Token.Tag_directive _ ->
-
(* Directives are only allowed at stream start or after explicit ... (MUS6/01) *)
-
if not t.stream_start && not t.explicit_doc_end then
-
Error.raise_span tok.span (Invalid_directive "directives require explicit document end '...' before them");
-
parse_document_start t ~implicit:false
-
| Token.Document_start ->
-
parse_document_start t ~implicit:false
-
(* These tokens are invalid at document start - they indicate leftover junk *)
-
| Token.Flow_sequence_end | Token.Flow_mapping_end | Token.Flow_entry
-
| Token.Block_end | Token.Value ->
-
Error.raise_span tok.span (Unexpected_token "unexpected token at document start")
-
| _ ->
-
parse_document_start t ~implicit:true)
+
match tok.token with
+
| Token.Stream_end ->
+
skip_token t;
+
t.state <- End;
+
t.finished <- true;
+
(Event.Stream_end, tok.span)
+
| Token.Version_directive _ | Token.Tag_directive _ ->
+
(* Directives are only allowed at stream start or after explicit ... (MUS6/01) *)
+
if (not t.stream_start) && not t.explicit_doc_end then
+
Error.raise_span tok.span
+
(Invalid_directive
+
"directives require explicit document end '...' before them");
+
parse_document_start t ~implicit:false
+
| Token.Document_start -> parse_document_start t ~implicit:false
+
(* These tokens are invalid at document start - they indicate leftover junk *)
+
| Token.Flow_sequence_end | Token.Flow_mapping_end | Token.Flow_entry
+
| Token.Block_end | Token.Value ->
+
Error.raise_span tok.span
+
(Unexpected_token "unexpected token at document start")
+
| _ -> parse_document_start t ~implicit:true)
| Document_content ->
-
if check t (function
-
| Token.Version_directive _ | Token.Tag_directive _
-
| Token.Document_start | Token.Document_end | Token.Stream_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Version_directive _ | Token.Tag_directive _
+
| Token.Document_start | Token.Document_end | Token.Stream_end ->
+
true
+
| _ -> false)
then begin
let tok = current_token t in
t.state <- pop_state t;
empty_scalar_event ~anchor:None ~tag:None tok.span
-
end else begin
+
end
+
else begin
(* Push Document_content_done so we return there after parsing the node.
This allows us to check for unexpected content after the node. *)
push_state t Document_content_done;
parse_node t ~block:true ~indentless:false
end
-
| Document_content_done ->
(* After parsing a node in document content, check for unexpected content *)
-
if check t (function
-
| Token.Version_directive _ | Token.Tag_directive _
-
| Token.Document_start | Token.Document_end | Token.Stream_end -> true
-
| _ -> false)
+
if
+
check t (function
+
| Token.Version_directive _ | Token.Tag_directive _
+
| Token.Document_start | Token.Document_end | Token.Stream_end ->
+
true
+
| _ -> false)
then begin
(* Valid document boundary - continue to Document_end *)
t.state <- pop_state t;
-
parse t (* Continue to emit the next event *)
-
end else begin
+
parse t (* Continue to emit the next event *)
+
end
+
else begin
(* Unexpected content after document value - this is an error (KS4U, BS4K) *)
let tok = current_token t in
Error.raise_span tok.span
(Unexpected_token "content not allowed after document value")
end
-
-
| Document_end ->
-
parse_document_end t
+
| Document_end -> parse_document_end t
| Block_sequence_first_entry ->
t.state <- Block_sequence_entry;
parse_block_sequence_entry t
-
-
| Block_sequence_entry ->
-
parse_block_sequence_entry t
-
-
| Indentless_sequence_entry ->
-
parse_indentless_sequence_entry t
-
+
| Block_sequence_entry -> parse_block_sequence_entry t
+
| Indentless_sequence_entry -> parse_indentless_sequence_entry t
| Block_mapping_first_key ->
t.state <- Block_mapping_key;
parse_block_mapping_key t
-
-
| Block_mapping_key ->
-
parse_block_mapping_key t
-
-
| Block_mapping_value ->
-
parse_block_mapping_value t
-
-
| Flow_sequence_first_entry ->
-
parse_flow_sequence_entry t ~first:true
-
-
| Flow_sequence_entry ->
-
parse_flow_sequence_entry t ~first:false
-
-
| Flow_sequence_entry_mapping_key ->
-
parse_flow_sequence_entry_mapping_key t
-
+
| Block_mapping_key -> parse_block_mapping_key t
+
| Block_mapping_value -> parse_block_mapping_value t
+
| Flow_sequence_first_entry -> parse_flow_sequence_entry t ~first:true
+
| Flow_sequence_entry -> parse_flow_sequence_entry t ~first:false
+
| Flow_sequence_entry_mapping_key -> parse_flow_sequence_entry_mapping_key t
| Flow_sequence_entry_mapping_value ->
parse_flow_sequence_entry_mapping_value t
-
-
| Flow_sequence_entry_mapping_end ->
-
parse_flow_sequence_entry_mapping_end t
-
-
| Flow_mapping_first_key ->
-
parse_flow_mapping_key t ~first:true
-
-
| Flow_mapping_key ->
-
parse_flow_mapping_key t ~first:false
-
-
| Flow_mapping_value ->
-
parse_flow_mapping_value t ~empty:false
+
| Flow_sequence_entry_mapping_end -> parse_flow_sequence_entry_mapping_end t
+
| Flow_mapping_first_key -> parse_flow_mapping_key t ~first:true
+
| Flow_mapping_key -> parse_flow_mapping_key t ~first:false
+
| Flow_mapping_value -> parse_flow_mapping_value t ~empty:false
| End ->
let span = Span.point Position.initial in
t.finished <- true;
-
Event.Stream_end, span
+
(Event.Stream_end, span)
(** Get next event *)
let next t =
···
let rec loop () =
match next t with
| None -> ()
-
| Some ev -> f ev; loop ()
+
| Some ev ->
+
f ev;
+
loop ()
in
loop ()
(** Fold over all events *)
let fold f init t =
let rec loop acc =
-
match next t with
-
| None -> acc
-
| Some ev -> loop (f acc ev)
+
match next t with None -> acc | Some ev -> loop (f acc ev)
in
loop init
(** Convert to list *)
-
let to_list t =
-
fold (fun acc ev -> ev :: acc) [] t |> List.rev
+
let to_list t = fold (fun acc ev -> ev :: acc) [] t |> List.rev
+11 -28
lib/position.ml
···
(** Position tracking for source locations *)
type t = {
-
index : int; (** Byte offset from start *)
-
line : int; (** 1-indexed line number *)
+
index : int; (** Byte offset from start *)
+
line : int; (** 1-indexed line number *)
column : int; (** 1-indexed column number *)
}
let initial = { index = 0; line = 1; column = 1 }
-
-
let advance_byte t =
-
{ t with index = t.index + 1; column = t.column + 1 }
-
-
let advance_line t =
-
{ index = t.index + 1; line = t.line + 1; column = 1 }
-
-
let advance_char c t =
-
if c = '\n' then advance_line t
-
else advance_byte t
+
let advance_byte t = { t with index = t.index + 1; column = t.column + 1 }
+
let advance_line t = { index = t.index + 1; line = t.line + 1; column = 1 }
+
let advance_char c t = if c = '\n' then advance_line t else advance_byte t
let advance_utf8 uchar t =
let len = Uchar.utf_8_byte_length uchar in
let code = Uchar.to_int uchar in
if code = 0x0A (* LF *) then
{ index = t.index + len; line = t.line + 1; column = 1 }
-
else
-
{ t with index = t.index + len; column = t.column + 1 }
+
else { t with index = t.index + len; column = t.column + 1 }
-
let advance_bytes n t =
-
{ t with index = t.index + n; column = t.column + n }
-
-
let pp fmt t =
-
Format.fprintf fmt "line %d, column %d" t.line t.column
-
-
let to_string t =
-
Format.asprintf "%a" pp t
-
-
let compare a b =
-
Int.compare a.index b.index
-
-
let equal a b =
-
a.index = b.index
+
let advance_bytes n t = { t with index = t.index + n; column = t.column + n }
+
let pp fmt t = Format.fprintf fmt "line %d, column %d" t.line t.column
+
let to_string t = Format.asprintf "%a" pp t
+
let compare a b = Int.compare a.index b.index
+
let equal a b = a.index = b.index
+31 -27
lib/quoting.ml
···
(** YAML scalar quoting detection *)
-
(** Check if a string value needs quoting in YAML output.
-
Returns true if the string:
+
(** Check if a string value needs quoting in YAML output. Returns true if the
+
string:
- Is empty
- Starts with an indicator character
- Is a reserved word (null, true, false, yes, no, etc.)
···
else
let first = s.[0] in
(* Check first character for indicators *)
-
if first = '-' || first = '?' || first = ':' || first = ',' ||
-
first = '[' || first = ']' || first = '{' || first = '}' ||
-
first = '#' || first = '&' || first = '*' || first = '!' ||
-
first = '|' || first = '>' || first = '\'' || first = '"' ||
-
first = '%' || first = '@' || first = '`' || first = ' ' then
-
true
+
if
+
first = '-' || first = '?' || first = ':' || first = ',' || first = '['
+
|| first = ']' || first = '{' || first = '}' || first = '#' || first = '&'
+
|| first = '*' || first = '!' || first = '|' || first = '>'
+
|| first = '\'' || first = '"' || first = '%' || first = '@'
+
|| first = '`' || first = ' '
+
then true
else
(* Check for reserved/special values *)
let lower = String.lowercase_ascii s in
-
if lower = "null" || lower = "true" || lower = "false" ||
-
lower = "yes" || lower = "no" || lower = "on" || lower = "off" ||
-
lower = "~" || lower = ".inf" || lower = "-.inf" || lower = ".nan" then
-
true
+
if
+
lower = "null" || lower = "true" || lower = "false" || lower = "yes"
+
|| lower = "no" || lower = "on" || lower = "off" || lower = "~"
+
|| lower = ".inf" || lower = "-.inf" || lower = ".nan"
+
then true
else
(* Check for problematic characters *)
try
-
String.iter (fun c ->
-
if c = ':' || c = '#' || c = '\n' || c = '\r' then
-
raise Exit
-
) s;
+
String.iter
+
(fun c ->
+
if c = ':' || c = '#' || c = '\n' || c = '\r' then raise Exit)
+
s;
(* Check if it looks like a number *)
-
(try ignore (Float.of_string s); true with _ -> false)
+
try
+
ignore (Float.of_string s);
+
true
+
with _ -> false
with Exit -> true
-
(** Check if a string requires double quotes (vs single quotes).
-
Returns true if the string contains characters that need escape sequences. *)
+
(** Check if a string requires double quotes (vs single quotes). Returns true if
+
the string contains characters that need escape sequences. *)
let needs_double_quotes s =
try
-
String.iter (fun c ->
-
if c = '\n' || c = '\r' || c = '\t' || c = '\\' ||
-
c < ' ' || c = '"' then
-
raise Exit
-
) s;
+
String.iter
+
(fun c ->
+
if c = '\n' || c = '\r' || c = '\t' || c = '\\' || c < ' ' || c = '"'
+
then raise Exit)
+
s;
false
with Exit -> true
(** Choose the appropriate quoting style for a string value *)
let choose_style s =
match (needs_double_quotes s, needs_quoting s) with
-
| (true, _) -> `Double_quoted
-
| (_, true) -> `Single_quoted
+
| true, _ -> `Double_quoted
+
| _, true -> `Single_quoted
| _ -> `Plain
-
+22 -24
lib/scalar.ml
···
style : Scalar_style.t;
}
-
let make
-
?(anchor : string option)
-
?(tag : string option)
-
?(plain_implicit = true)
-
?(quoted_implicit = false)
-
?(style = `Plain)
-
value =
+
let make ?(anchor : string option) ?(tag : string option)
+
?(plain_implicit = true) ?(quoted_implicit = false) ?(style = `Plain) value
+
=
{ anchor; tag; value; plain_implicit; quoted_implicit; style }
let value t = t.value
···
let style t = t.style
let plain_implicit t = t.plain_implicit
let quoted_implicit t = t.quoted_implicit
-
let with_anchor anchor t = { t with anchor = Some anchor }
let with_tag tag t = { t with tag = Some tag }
let with_style style t = { t with style }
···
Format.fprintf fmt ", style=%a)" Scalar_style.pp t.style
let equal a b =
-
Option.equal String.equal a.anchor b.anchor &&
-
Option.equal String.equal a.tag b.tag &&
-
String.equal a.value b.value &&
-
a.plain_implicit = b.plain_implicit &&
-
a.quoted_implicit = b.quoted_implicit &&
-
Scalar_style.equal a.style b.style
+
Option.equal String.equal a.anchor b.anchor
+
&& Option.equal String.equal a.tag b.tag
+
&& String.equal a.value b.value
+
&& a.plain_implicit = b.plain_implicit
+
&& a.quoted_implicit = b.quoted_implicit
+
&& Scalar_style.equal a.style b.style
let compare a b =
let c = Option.compare String.compare a.anchor b.anchor in
-
if c <> 0 then c else
-
let c = Option.compare String.compare a.tag b.tag in
-
if c <> 0 then c else
-
let c = String.compare a.value b.value in
-
if c <> 0 then c else
-
let c = Bool.compare a.plain_implicit b.plain_implicit in
-
if c <> 0 then c else
-
let c = Bool.compare a.quoted_implicit b.quoted_implicit in
-
if c <> 0 then c else
-
Scalar_style.compare a.style b.style
+
if c <> 0 then c
+
else
+
let c = Option.compare String.compare a.tag b.tag in
+
if c <> 0 then c
+
else
+
let c = String.compare a.value b.value in
+
if c <> 0 then c
+
else
+
let c = Bool.compare a.plain_implicit b.plain_implicit in
+
if c <> 0 then c
+
else
+
let c = Bool.compare a.quoted_implicit b.quoted_implicit in
+
if c <> 0 then c else Scalar_style.compare a.style b.style
+8 -11
lib/scalar_style.ml
···
(** Scalar formatting styles *)
-
type t = [
-
| `Any (** Let emitter choose *)
-
| `Plain (** Unquoted: foo *)
-
| `Single_quoted (** 'foo' *)
-
| `Double_quoted (** "foo" *)
-
| `Literal (** | block *)
-
| `Folded (** > block *)
-
]
+
type t =
+
[ `Any (** Let emitter choose *)
+
| `Plain (** Unquoted: foo *)
+
| `Single_quoted (** 'foo' *)
+
| `Double_quoted (** "foo" *)
+
| `Literal (** | block *)
+
| `Folded (** > block *) ]
let to_string = function
| `Any -> "any"
···
| `Literal -> "literal"
| `Folded -> "folded"
-
let pp fmt t =
-
Format.pp_print_string fmt (to_string t)
-
+
let pp fmt t = Format.pp_print_string fmt (to_string t)
let equal a b = a = b
let compare a b =
+536 -399
lib/scanner.ml
···
(** YAML tokenizer/scanner with lookahead for ambiguity resolution *)
-
(** Simple key tracking for mapping key disambiguation *)
type simple_key = {
sk_possible : bool;
sk_required : bool;
sk_token_number : int;
sk_position : Position.t;
}
+
(** Simple key tracking for mapping key disambiguation *)
-
(** Indent level tracking *)
type indent = {
indent : int;
needs_block_end : bool;
}
+
(** Indent level tracking *)
type t = {
input : Input.t;
···
mutable stream_started : bool;
mutable stream_ended : bool;
mutable indent_stack : indent list; (** Stack of indentation levels *)
-
mutable flow_level : int; (** Nesting depth in \[\] or \{\} *)
-
mutable flow_indent : int; (** Column where outermost flow collection started *)
-
mutable simple_keys : simple_key option list; (** Per flow-level simple key tracking *)
+
mutable flow_level : int; (** Nesting depth in \[\] or \{\} *)
+
mutable flow_indent : int;
+
(** Column where outermost flow collection started *)
+
mutable simple_keys : simple_key option list;
+
(** Per flow-level simple key tracking *)
mutable allow_simple_key : bool;
-
mutable leading_whitespace : bool; (** True when at start of line (only whitespace seen) *)
-
mutable document_has_content : bool; (** True if we've emitted content tokens in current document *)
-
mutable adjacent_value_allowed_at : Position.t option; (** Position where adjacent : is allowed *)
-
mutable flow_mapping_stack : bool list; (** Stack of whether each flow level is a mapping *)
+
mutable leading_whitespace : bool;
+
(** True when at start of line (only whitespace seen) *)
+
mutable document_has_content : bool;
+
(** True if we've emitted content tokens in current document *)
+
mutable adjacent_value_allowed_at : Position.t option;
+
(** Position where adjacent : is allowed *)
+
mutable flow_mapping_stack : bool list;
+
(** Stack of whether each flow level is a mapping *)
}
let create input =
···
indent_stack = [];
flow_level = 0;
flow_indent = 0;
-
simple_keys = [None]; (* One entry for the base level *)
+
simple_keys = [ None ];
+
(* One entry for the base level *)
allow_simple_key = true;
-
leading_whitespace = true; (* Start at beginning of stream *)
+
leading_whitespace = true;
+
(* Start at beginning of stream *)
document_has_content = false;
adjacent_value_allowed_at = None;
flow_mapping_stack = [];
···
let of_string s = create (Input.of_string s)
let of_input = create
let of_reader r = create (Input.of_reader r)
-
let position t = Input.position t.input
(** Add a token to the queue *)
···
(** Get current indent level *)
let current_indent t =
-
match t.indent_stack with
-
| [] -> -1
-
| { indent; _ } :: _ -> indent
+
match t.indent_stack with [] -> -1 | { indent; _ } :: _ -> indent
-
(** Skip whitespace to end of line, checking for valid comments.
-
Returns true if any whitespace (including tabs) was found before a comment. *)
+
(** Skip whitespace to end of line, checking for valid comments. Returns true if
+
any whitespace (including tabs) was found before a comment. *)
let skip_whitespace_and_comment t =
let has_whitespace = ref false in
(* Skip blanks (spaces and tabs) *)
···
Error.raise_at (Input.mark t.input) Invalid_comment
end;
(* Skip to end of line *)
-
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
while (not (Input.is_eof t.input)) && not (Input.next_is_break t.input) do
ignore (Input.next t.input)
done
end
···
let found_space = ref false in
while Input.next_is_blank t.input do
(match Input.peek t.input with
-
| Some '\t' -> found_tab := true
-
| Some ' ' -> found_space := true
-
| _ -> ());
+
| Some '\t' -> found_tab := true
+
| Some ' ' -> found_space := true
+
| _ -> ());
ignore (Input.next t.input)
done;
(!found_tab, !found_space)
···
let rec skip_to_next_token t =
(* Check for tabs used as indentation in block context *)
(match Input.peek t.input with
-
| Some '\t' when t.flow_level = 0 && t.leading_whitespace &&
-
(column t - 1) < current_indent t ->
-
(* Tab found in indentation zone - this is invalid *)
-
(* Skip to end of line to check if line has content *)
-
let start_pos = Input.mark t.input in
-
while Input.next_is_blank t.input do
-
ignore (Input.next t.input)
-
done;
-
(* If we have content on this line with a tab, raise error *)
-
if not (Input.next_is_break t.input) && not (Input.is_eof t.input) then
-
Error.raise_at start_pos Tab_in_indentation
-
| _ -> ());
+
| Some '\t'
+
when t.flow_level = 0 && t.leading_whitespace
+
&& column t - 1 < current_indent t ->
+
(* Tab found in indentation zone - this is invalid *)
+
(* Skip to end of line to check if line has content *)
+
let start_pos = Input.mark t.input in
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
(* If we have content on this line with a tab, raise error *)
+
if (not (Input.next_is_break t.input)) && not (Input.is_eof t.input) then
+
Error.raise_at start_pos Tab_in_indentation
+
| _ -> ());
(* Skip blanks and validate comments *)
skip_whitespace_and_comment t;
···
ignore (Input.next t.input)
done;
(* If only tabs were used (no spaces) and column < flow_indent, error *)
-
if not (Input.next_is_break t.input) && not (Input.is_eof t.input) &&
-
column t < t.flow_indent then
-
Error.raise_at start_mark Invalid_flow_indentation
+
if
+
(not (Input.next_is_break t.input))
+
&& (not (Input.is_eof t.input))
+
&& column t < t.flow_indent
+
then Error.raise_at start_mark Invalid_flow_indentation
end;
skip_to_next_token t
-
end else begin
+
end
+
else begin
ignore (Input.next t.input);
skip_to_next_token t
end
···
if t.flow_level = 0 && col > current_indent t then begin
t.indent_stack <- { indent = col; needs_block_end = true } :: t.indent_stack;
true
-
end else
-
false
+
end
+
else false
(** Unroll indentation to given column *)
let unroll_indent t col =
-
while t.flow_level = 0 &&
-
match t.indent_stack with
-
| { indent; needs_block_end = true; _ } :: _ when indent > col -> true
-
| _ -> false
+
while
+
t.flow_level = 0
+
&&
+
match t.indent_stack with
+
| { indent; needs_block_end = true; _ } :: _ when indent > col -> true
+
| _ -> false
do
match t.indent_stack with
| { indent = _; needs_block_end = true; _ } :: rest ->
···
(* A simple key is required only if we're in a block context,
at the current indentation level, AND the current indent needs a block end.
This matches saphyr's logic and prevents false positives for values. *)
-
let required = t.flow_level = 0 &&
-
match t.indent_stack with
-
| { indent; needs_block_end = true; _ } :: _ ->
-
indent = column t
-
| _ -> false
+
let required =
+
t.flow_level = 0
+
&&
+
match t.indent_stack with
+
| { indent; needs_block_end = true; _ } :: _ -> indent = column t
+
| _ -> false
in
-
let sk = {
-
sk_possible = true;
-
sk_required = required;
-
sk_token_number = t.token_number;
-
sk_position = Input.position t.input;
-
} in
+
let sk =
+
{
+
sk_possible = true;
+
sk_required = required;
+
sk_token_number = t.token_number;
+
sk_position = Input.position t.input;
+
}
+
in
(* Remove any existing simple key at current level *)
-
t.simple_keys <- (
-
match t.simple_keys with
+
t.simple_keys <-
+
(match t.simple_keys with
| _ :: rest -> Some sk :: rest
-
| [] -> [Some sk]
-
)
+
| [] -> [ Some sk ])
end
(** Remove simple key at current level *)
···
(** Stale simple keys that span too many tokens *)
let stale_simple_keys t =
-
t.simple_keys <- List.map (fun sk_opt ->
-
match sk_opt with
-
| Some sk when sk.sk_possible &&
-
(Input.position t.input).line > sk.sk_position.line &&
-
t.flow_level = 0 ->
-
if sk.sk_required then
-
Error.raise_at sk.sk_position Expected_key;
-
None
-
| _ -> sk_opt
-
) t.simple_keys
+
t.simple_keys <-
+
List.map
+
(fun sk_opt ->
+
match sk_opt with
+
| Some sk
+
when sk.sk_possible
+
&& (Input.position t.input).line > sk.sk_position.line
+
&& t.flow_level = 0 ->
+
if sk.sk_required then Error.raise_at sk.sk_position Expected_key;
+
None
+
| _ -> sk_opt)
+
t.simple_keys
(** Read anchor or alias name *)
let scan_anchor_alias t =
···
This matches the saphyr implementation: is_yaml_non_space && !is_flow *)
while
match Input.peek t.input with
-
| Some c when not (Input.is_whitespace c) &&
-
not (Input.is_flow_indicator c) &&
-
c <> '\x00' ->
+
| Some c
+
when (not (Input.is_whitespace c))
+
&& (not (Input.is_flow_indicator c))
+
&& c <> '\x00' ->
Buffer.add_char buf c;
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
let name = Buffer.contents buf in
if String.length name = 0 then
Error.raise_at start (Invalid_anchor "empty anchor name");
···
let buf = Buffer.create 16 in
(* Expect ! *)
(match Input.peek t.input with
-
| Some '!' ->
-
Buffer.add_char buf '!';
-
ignore (Input.next t.input)
-
| _ -> Error.raise_at start (Invalid_tag "expected '!'"));
+
| Some '!' ->
+
Buffer.add_char buf '!';
+
ignore (Input.next t.input)
+
| _ -> Error.raise_at start (Invalid_tag "expected '!'"));
(* Read word chars *)
while
match Input.peek t.input with
···
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
(* Check for secondary ! *)
(match Input.peek t.input with
-
| Some '!' ->
-
Buffer.add_char buf '!';
-
ignore (Input.next t.input)
-
| _ -> ());
+
| Some '!' ->
+
Buffer.add_char buf '!';
+
ignore (Input.next t.input)
+
| _ -> ());
Buffer.contents buf
(** Scan tag suffix (after handle) *)
···
in
let hex_val c =
match c with
-
| '0'..'9' -> Char.code c - Char.code '0'
-
| 'A'..'F' -> Char.code c - Char.code 'A' + 10
-
| 'a'..'f' -> Char.code c - Char.code 'a' + 10
+
| '0' .. '9' -> Char.code c - Char.code '0'
+
| 'A' .. 'F' -> Char.code c - Char.code 'A' + 10
+
| 'a' .. 'f' -> Char.code c - Char.code 'a' + 10
| _ -> 0
in
let buf = Buffer.create 32 in
while
match Input.peek t.input with
-
| Some '%' ->
+
| Some '%' -> (
(* Percent-encoded character *)
ignore (Input.next t.input);
-
(match Input.peek t.input, Input.peek_nth t.input 1 with
-
| Some c1, Some c2 when is_hex_digit c1 && is_hex_digit c2 ->
-
ignore (Input.next t.input);
-
ignore (Input.next t.input);
-
let code = (hex_val c1) * 16 + (hex_val c2) in
-
Buffer.add_char buf (Char.chr code);
-
true
-
| _ ->
-
(* Invalid percent encoding - keep the % *)
-
Buffer.add_char buf '%';
-
true)
-
| Some c when not (Input.is_whitespace c) &&
-
not (Input.is_flow_indicator c) ->
+
match (Input.peek t.input, Input.peek_nth t.input 1) with
+
| Some c1, Some c2 when is_hex_digit c1 && is_hex_digit c2 ->
+
ignore (Input.next t.input);
+
ignore (Input.next t.input);
+
let code = (hex_val c1 * 16) + hex_val c2 in
+
Buffer.add_char buf (Char.chr code);
+
true
+
| _ ->
+
(* Invalid percent encoding - keep the % *)
+
Buffer.add_char buf '%';
+
true)
+
| Some c
+
when (not (Input.is_whitespace c)) && not (Input.is_flow_indicator c) ->
Buffer.add_char buf c;
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
Buffer.contents buf
(** Scan a tag *)
let scan_tag t =
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume ! *)
+
ignore (Input.next t.input);
+
(* consume ! *)
let handle, suffix =
match Input.peek t.input with
| Some '<' ->
···
Buffer.add_char buf c;
ignore (Input.next t.input);
true
-
| None -> Error.raise_at (Input.mark t.input) (Invalid_tag "unclosed verbatim tag")
-
do () done;
-
ignore (Input.next t.input); (* consume > *)
+
| None ->
+
Error.raise_at (Input.mark t.input)
+
(Invalid_tag "unclosed verbatim tag")
+
do
+
()
+
done;
+
ignore (Input.next t.input);
+
(* consume > *)
("", Buffer.contents buf)
| Some c when Input.is_whitespace c || Input.is_flow_indicator c ->
(* Non-specific tag: ! *)
("!", "")
| Some '!' ->
(* Secondary handle: !! *)
-
ignore (Input.next t.input); (* consume second ! *)
+
ignore (Input.next t.input);
+
(* consume second ! *)
let suffix = scan_tag_suffix t in
("!!", suffix)
-
| _ ->
+
| _ -> (
(* Primary handle or just suffix: !foo or !e!foo *)
(* Read alphanumeric characters *)
let buf = Buffer.create 16 in
···
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
(* Check if next character is ! - if so, this is a named handle *)
-
(match Input.peek t.input with
-
| Some '!' ->
-
(* Named handle like !e! *)
-
ignore (Input.next t.input);
-
let handle_name = Buffer.contents buf in
-
let suffix = scan_tag_suffix t in
-
("!" ^ handle_name ^ "!", suffix)
-
| _ ->
-
(* Just ! followed by suffix *)
-
("!", Buffer.contents buf ^ scan_tag_suffix t))
+
match Input.peek t.input with
+
| Some '!' ->
+
(* Named handle like !e! *)
+
ignore (Input.next t.input);
+
let handle_name = Buffer.contents buf in
+
let suffix = scan_tag_suffix t in
+
("!" ^ handle_name ^ "!", suffix)
+
| _ ->
+
(* Just ! followed by suffix *)
+
("!", Buffer.contents buf ^ scan_tag_suffix t))
in
(* Validate that tag is followed by whitespace, break, or (in flow) flow indicator *)
(match Input.peek t.input with
-
| None -> () (* EOF is ok *)
-
| Some c when Input.is_whitespace c || Input.is_break c -> ()
-
| Some c when t.flow_level > 0 && Input.is_flow_indicator c -> ()
-
| _ -> Error.raise_at start (Invalid_tag "expected whitespace or line break after tag"));
+
| None -> () (* EOF is ok *)
+
| Some c when Input.is_whitespace c || Input.is_break c -> ()
+
| Some c when t.flow_level > 0 && Input.is_flow_indicator c -> ()
+
| _ ->
+
Error.raise_at start
+
(Invalid_tag "expected whitespace or line break after tag"));
let span = Span.make ~start ~stop:(Input.mark t.input) in
(handle, suffix, span)
(** Scan single-quoted scalar *)
let scan_single_quoted t =
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume opening single-quote *)
+
ignore (Input.next t.input);
+
(* consume opening single-quote *)
let buf = Buffer.create 64 in
-
let whitespace = Buffer.create 16 in (* Track trailing whitespace *)
+
let whitespace = Buffer.create 16 in
+
(* Track trailing whitespace *)
let flush_whitespace () =
if Buffer.length whitespace > 0 then begin
···
let rec loop () =
match Input.peek t.input with
| None -> Error.raise_at start Unclosed_single_quote
-
| Some '\'' ->
+
| Some '\'' -> (
ignore (Input.next t.input);
(* Check for escaped quote ('') *)
-
(match Input.peek t.input with
-
| Some '\'' ->
-
flush_whitespace ();
-
Buffer.add_char buf '\'';
-
ignore (Input.next t.input);
-
loop ()
-
| _ ->
-
(* End of string - flush any trailing whitespace *)
-
flush_whitespace ())
+
match Input.peek t.input with
+
| Some '\'' ->
+
flush_whitespace ();
+
Buffer.add_char buf '\'';
+
ignore (Input.next t.input);
+
loop ()
+
| _ ->
+
(* End of string - flush any trailing whitespace *)
+
flush_whitespace ())
| Some ' ' | Some '\t' ->
(* Track whitespace - don't add to buf yet *)
Buffer.add_char whitespace (Option.get (Input.peek t.input));
···
(* Check indentation: continuation must be > block indent (QB6E, DK95) *)
let col = column t in
let indent = current_indent t in
-
if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then
-
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
+
if
+
(not (Input.is_eof t.input))
+
&& (not (Input.next_is_break t.input))
+
&& col <= indent && indent >= 0
+
then
+
Error.raise_at (Input.mark t.input)
+
(Invalid_quoted_scalar_indentation
+
"invalid indentation in quoted scalar");
(* Count empty lines (consecutive line breaks) *)
let empty_lines = ref 0 in
while Input.next_is_break t.input do
···
(* Check indentation after each empty line too *)
let col = column t in
let indent = current_indent t in
-
if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then
-
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar")
+
if
+
(not (Input.is_eof t.input))
+
&& (not (Input.next_is_break t.input))
+
&& col <= indent && indent >= 0
+
then
+
Error.raise_at (Input.mark t.input)
+
(Invalid_quoted_scalar_indentation
+
"invalid indentation in quoted scalar")
done;
(* Apply folding rules *)
if !empty_lines > 0 then begin
···
for _ = 1 to !empty_lines do
Buffer.add_char buf '\n'
done
-
end else
+
end
+
else
(* Single break: fold to space (even at start of string) *)
Buffer.add_char buf ' ';
loop ()
···
| Some c when Input.is_hex c ->
Buffer.add_char buf c;
ignore (Input.next t.input)
-
| _ ->
-
Error.raise_at start (Invalid_hex_escape (Buffer.contents buf))
+
| _ -> Error.raise_at start (Invalid_hex_escape (Buffer.contents buf))
done;
let code = int_of_string ("0x" ^ Buffer.contents buf) in
-
if code <= 0x7F then
-
String.make 1 (Char.chr code)
+
if code <= 0x7F then String.make 1 (Char.chr code)
else if code <= 0x7FF then
let b1 = 0xC0 lor (code lsr 6) in
let b2 = 0x80 lor (code land 0x3F) in
···
let b1 = 0xE0 lor (code lsr 12) in
let b2 = 0x80 lor ((code lsr 6) land 0x3F) in
let b3 = 0x80 lor (code land 0x3F) in
-
String.init 3 (fun i -> Char.chr (match i with 0 -> b1 | 1 -> b2 | _ -> b3))
+
String.init 3 (fun i ->
+
Char.chr (match i with 0 -> b1 | 1 -> b2 | _ -> b3))
else
let b1 = 0xF0 lor (code lsr 18) in
let b2 = 0x80 lor ((code lsr 12) land 0x3F) in
let b3 = 0x80 lor ((code lsr 6) land 0x3F) in
let b4 = 0x80 lor (code land 0x3F) in
-
String.init 4 (fun i -> Char.chr (match i with 0 -> b1 | 1 -> b2 | 2 -> b3 | _ -> b4))
+
String.init 4 (fun i ->
+
Char.chr (match i with 0 -> b1 | 1 -> b2 | 2 -> b3 | _ -> b4))
(** Scan double-quoted scalar *)
let scan_double_quoted t =
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume opening double-quote *)
+
ignore (Input.next t.input);
+
(* consume opening double-quote *)
let buf = Buffer.create 64 in
-
let whitespace = Buffer.create 16 in (* Track pending whitespace *)
+
let whitespace = Buffer.create 16 in
+
(* Track pending whitespace *)
let flush_whitespace () =
if Buffer.length whitespace > 0 then begin
···
(* Flush trailing whitespace before closing quote to preserve it *)
flush_whitespace ();
ignore (Input.next t.input)
-
| Some ' ' | Some '\t' as c_opt ->
+
| (Some ' ' | Some '\t') as c_opt ->
(* Track whitespace - don't add to buf yet *)
let c = match c_opt with Some c -> c | None -> assert false in
Buffer.add_char whitespace c;
···
loop ()
| Some '\\' ->
(* Escape sequence - this is non-whitespace content *)
-
flush_whitespace (); (* Commit any pending whitespace *)
+
flush_whitespace ();
+
(* Commit any pending whitespace *)
ignore (Input.next t.input);
(match Input.peek t.input with
-
| None -> Error.raise_at start (Invalid_escape_sequence "\\<EOF>")
-
| Some '0' -> Buffer.add_char buf '\x00'; ignore (Input.next t.input)
-
| Some 'a' -> Buffer.add_char buf '\x07'; ignore (Input.next t.input)
-
| Some 'b' -> Buffer.add_char buf '\x08'; ignore (Input.next t.input)
-
| Some 't' | Some '\t' -> Buffer.add_char buf '\t'; ignore (Input.next t.input)
-
| Some 'n' -> Buffer.add_char buf '\n'; ignore (Input.next t.input)
-
| Some 'v' -> Buffer.add_char buf '\x0B'; ignore (Input.next t.input)
-
| Some 'f' -> Buffer.add_char buf '\x0C'; ignore (Input.next t.input)
-
| Some 'r' -> Buffer.add_char buf '\r'; ignore (Input.next t.input)
-
| Some 'e' -> Buffer.add_char buf '\x1B'; ignore (Input.next t.input)
-
| Some ' ' -> Buffer.add_char buf ' '; ignore (Input.next t.input)
-
| Some '"' -> Buffer.add_char buf '"'; ignore (Input.next t.input)
-
| Some '/' -> Buffer.add_char buf '/'; ignore (Input.next t.input)
-
| Some '\\' -> Buffer.add_char buf '\\'; ignore (Input.next t.input)
-
| Some 'N' -> Buffer.add_string buf "\xC2\x85"; ignore (Input.next t.input) (* NEL *)
-
| Some '_' -> Buffer.add_string buf "\xC2\xA0"; ignore (Input.next t.input) (* NBSP *)
-
| Some 'L' -> Buffer.add_string buf "\xE2\x80\xA8"; ignore (Input.next t.input) (* LS *)
-
| Some 'P' -> Buffer.add_string buf "\xE2\x80\xA9"; ignore (Input.next t.input) (* PS *)
-
| Some 'x' ->
-
ignore (Input.next t.input);
-
Buffer.add_string buf (decode_hex t 2)
-
| Some 'u' ->
-
ignore (Input.next t.input);
-
Buffer.add_string buf (decode_hex t 4)
-
| Some 'U' ->
-
ignore (Input.next t.input);
-
Buffer.add_string buf (decode_hex t 8)
-
| Some '\n' | Some '\r' ->
-
(* Line continuation escape *)
-
Input.consume_break t.input;
-
while Input.next_is_blank t.input do
-
ignore (Input.next t.input)
-
done
-
| Some c ->
-
Error.raise_at (Input.mark t.input)
-
(Invalid_escape_sequence (Printf.sprintf "\\%c" c)));
+
| None -> Error.raise_at start (Invalid_escape_sequence "\\<EOF>")
+
| Some '0' ->
+
Buffer.add_char buf '\x00';
+
ignore (Input.next t.input)
+
| Some 'a' ->
+
Buffer.add_char buf '\x07';
+
ignore (Input.next t.input)
+
| Some 'b' ->
+
Buffer.add_char buf '\x08';
+
ignore (Input.next t.input)
+
| Some 't' | Some '\t' ->
+
Buffer.add_char buf '\t';
+
ignore (Input.next t.input)
+
| Some 'n' ->
+
Buffer.add_char buf '\n';
+
ignore (Input.next t.input)
+
| Some 'v' ->
+
Buffer.add_char buf '\x0B';
+
ignore (Input.next t.input)
+
| Some 'f' ->
+
Buffer.add_char buf '\x0C';
+
ignore (Input.next t.input)
+
| Some 'r' ->
+
Buffer.add_char buf '\r';
+
ignore (Input.next t.input)
+
| Some 'e' ->
+
Buffer.add_char buf '\x1B';
+
ignore (Input.next t.input)
+
| Some ' ' ->
+
Buffer.add_char buf ' ';
+
ignore (Input.next t.input)
+
| Some '"' ->
+
Buffer.add_char buf '"';
+
ignore (Input.next t.input)
+
| Some '/' ->
+
Buffer.add_char buf '/';
+
ignore (Input.next t.input)
+
| Some '\\' ->
+
Buffer.add_char buf '\\';
+
ignore (Input.next t.input)
+
| Some 'N' ->
+
Buffer.add_string buf "\xC2\x85";
+
ignore (Input.next t.input) (* NEL *)
+
| Some '_' ->
+
Buffer.add_string buf "\xC2\xA0";
+
ignore (Input.next t.input) (* NBSP *)
+
| Some 'L' ->
+
Buffer.add_string buf "\xE2\x80\xA8";
+
ignore (Input.next t.input) (* LS *)
+
| Some 'P' ->
+
Buffer.add_string buf "\xE2\x80\xA9";
+
ignore (Input.next t.input) (* PS *)
+
| Some 'x' ->
+
ignore (Input.next t.input);
+
Buffer.add_string buf (decode_hex t 2)
+
| Some 'u' ->
+
ignore (Input.next t.input);
+
Buffer.add_string buf (decode_hex t 4)
+
| Some 'U' ->
+
ignore (Input.next t.input);
+
Buffer.add_string buf (decode_hex t 8)
+
| Some '\n' | Some '\r' ->
+
(* Line continuation escape *)
+
Input.consume_break t.input;
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done
+
| Some c ->
+
Error.raise_at (Input.mark t.input)
+
(Invalid_escape_sequence (Printf.sprintf "\\%c" c)));
loop ()
| Some '\n' | Some '\r' ->
(* Line break: discard any pending trailing whitespace *)
···
if Input.next_is_break t.input then begin
Input.consume_break t.input;
incr empty_lines;
-
started_with_tab := false (* Reset for next line *)
-
end else
-
continue := false
+
started_with_tab := false (* Reset for next line *)
+
end
+
else continue := false
done;
(* Check for document boundary - this terminates the quoted string *)
if Input.at_document_boundary t.input then
···
let indent = current_indent t in
let start_col = start.column in
(* DK95/01: if continuation started with tabs and column < start column, error *)
-
if not (Input.is_eof t.input) && !started_with_tab && col < start_col then
-
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
-
if not (Input.is_eof t.input) && col <= indent && indent >= 0 then
-
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
+
if (not (Input.is_eof t.input)) && !started_with_tab && col < start_col
+
then
+
Error.raise_at (Input.mark t.input)
+
(Invalid_quoted_scalar_indentation
+
"invalid indentation in quoted scalar");
+
if (not (Input.is_eof t.input)) && col <= indent && indent >= 0 then
+
Error.raise_at (Input.mark t.input)
+
(Invalid_quoted_scalar_indentation
+
"invalid indentation in quoted scalar");
(* Per YAML spec: single break = space, break + empty lines = newlines *)
if !empty_lines > 0 then begin
(* Empty lines: output N newlines where N = number of empty lines *)
for _ = 1 to !empty_lines do
Buffer.add_char buf '\n'
done
-
end else
+
end
+
else
(* Single break folds to space *)
Buffer.add_char buf ' ';
loop ()
| Some c ->
(* Non-whitespace character *)
-
flush_whitespace (); (* Commit any pending whitespace *)
+
flush_whitespace ();
+
(* Commit any pending whitespace *)
Buffer.add_char buf c;
ignore (Input.next t.input);
loop ()
···
(** Check if character can appear in plain scalar at this position *)
let can_continue_plain t c ~in_flow =
match c with
-
| ':' ->
+
| ':' -> (
(* : is OK if not followed by whitespace or flow indicator *)
-
(match Input.peek_nth t.input 1 with
-
| None -> true
-
| Some c2 when Input.is_whitespace c2 -> false
-
| Some c2 when in_flow && Input.is_flow_indicator c2 -> false
-
| _ -> true)
-
| '#' ->
+
match Input.peek_nth t.input 1 with
+
| None -> true
+
| Some c2 when Input.is_whitespace c2 -> false
+
| Some c2 when in_flow && Input.is_flow_indicator c2 -> false
+
| _ -> true)
+
| '#' -> (
(* # is a comment indicator only if preceded by whitespace *)
(* Check the previous character to determine if this is a comment *)
-
(match Input.peek_back t.input with
-
| None -> true (* At start - can't be comment indicator, allow it *)
-
| Some c when Input.is_whitespace c -> false (* Preceded by whitespace - comment *)
-
| Some c when Input.is_break c -> false (* At start of line - comment *)
-
| _ -> true) (* Not preceded by whitespace - part of scalar *)
+
match Input.peek_back t.input with
+
| None -> true (* At start - can't be comment indicator, allow it *)
+
| Some c when Input.is_whitespace c ->
+
false (* Preceded by whitespace - comment *)
+
| Some c when Input.is_break c -> false (* At start of line - comment *)
+
| _ -> true (* Not preceded by whitespace - part of scalar *))
| c when in_flow && Input.is_flow_indicator c -> false
| _ when Input.is_break c -> false
| _ -> true
···
let indent = current_indent t in
(* In flow context, scalars must be indented more than the current block indent.
This ensures that content at block indent or less ends the flow context. *)
-
if in_flow && (column t - 1) < indent then
+
if in_flow && column t - 1 < indent then
Error.raise_at start Invalid_flow_indentation;
let buf = Buffer.create 64 in
let spaces = Buffer.create 16 in
-
let whitespace = Buffer.create 16 in (* Track whitespace within a line *)
+
let whitespace = Buffer.create 16 in
+
(* Track whitespace within a line *)
let leading_blanks = ref false in
let rec scan_line () =
···
if Buffer.length spaces > 0 then begin
if !leading_blanks then begin
(* Fold line break *)
-
if Buffer.contents spaces = "\n" then
-
Buffer.add_char buf ' '
+
if Buffer.contents spaces = "\n" then Buffer.add_char buf ' '
else begin
(* Multiple breaks - preserve all but first *)
let s = Buffer.contents spaces in
Buffer.add_substring buf s 1 (String.length s - 1)
end
-
end else
-
Buffer.add_buffer buf spaces;
+
end
+
else Buffer.add_buffer buf spaces;
Buffer.clear spaces
end;
(* Add any pending whitespace from within the line *)
···
if !leading_blanks then begin
(* We already had a break - this is an additional break (empty line) *)
Buffer.add_char spaces '\n'
-
end else begin
+
end
+
else begin
(* First line break *)
Buffer.clear spaces;
Buffer.add_char spaces '\n';
···
(* However, allow empty lines (line breaks) to continue even if dedented *)
if Input.next_is_break t.input then
scan_lines () (* Empty line - continue *)
-
else if not in_flow && col <= indent then
-
() (* Stop - dedented or at parent level in block context *)
-
else if Input.at_document_boundary t.input then
-
() (* Stop - document boundary *)
-
else
-
scan_lines ()
+
else if (not in_flow) && col <= indent then ()
+
(* Stop - dedented or at parent level in block context *)
+
else if Input.at_document_boundary t.input then ()
+
(* Stop - document boundary *)
+
else scan_lines ()
end
in
···
let len = String.length value in
let rec find_end i =
if i < 0 then 0
-
else match value.[i] with
-
| ' ' | '\t' -> find_end (i - 1)
-
| _ -> i + 1
+
else match value.[i] with ' ' | '\t' -> find_end (i - 1) | _ -> i + 1
in
let end_pos = find_end (len - 1) in
String.sub value 0 end_pos
···
(** Scan block scalar (literal | or folded >) *)
let scan_block_scalar t literal =
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume | or > *)
+
ignore (Input.next t.input);
+
+
(* consume | or > *)
(* Parse header: optional indentation indicator and chomping *)
let explicit_indent = ref None in
···
(* First character of header *)
(match Input.peek t.input with
-
| Some c when Input.is_digit c && c <> '0' ->
-
explicit_indent := Some (Char.code c - Char.code '0');
-
ignore (Input.next t.input)
-
| Some '-' -> chomping := Chomping.Strip; ignore (Input.next t.input)
-
| Some '+' -> chomping := Chomping.Keep; ignore (Input.next t.input)
-
| _ -> ());
+
| Some c when Input.is_digit c && c <> '0' ->
+
explicit_indent := Some (Char.code c - Char.code '0');
+
ignore (Input.next t.input)
+
| Some '-' ->
+
chomping := Chomping.Strip;
+
ignore (Input.next t.input)
+
| Some '+' ->
+
chomping := Chomping.Keep;
+
ignore (Input.next t.input)
+
| _ -> ());
(* Second character of header *)
(match Input.peek t.input with
-
| Some c when Input.is_digit c && c <> '0' && !explicit_indent = None ->
-
explicit_indent := Some (Char.code c - Char.code '0');
-
ignore (Input.next t.input)
-
| Some '-' when !chomping = Chomping.Clip ->
-
chomping := Chomping.Strip; ignore (Input.next t.input)
-
| Some '+' when !chomping = Chomping.Clip ->
-
chomping := Chomping.Keep; ignore (Input.next t.input)
-
| _ -> ());
+
| Some c when Input.is_digit c && c <> '0' && !explicit_indent = None ->
+
explicit_indent := Some (Char.code c - Char.code '0');
+
ignore (Input.next t.input)
+
| Some '-' when !chomping = Chomping.Clip ->
+
chomping := Chomping.Strip;
+
ignore (Input.next t.input)
+
| Some '+' when !chomping = Chomping.Clip ->
+
chomping := Chomping.Keep;
+
ignore (Input.next t.input)
+
| _ -> ());
(* Skip whitespace and optional comment *)
skip_whitespace_and_comment t;
(* Consume line break *)
-
if Input.next_is_break t.input then
-
Input.consume_break t.input
+
if Input.next_is_break t.input then Input.consume_break t.input
else if not (Input.is_eof t.input) then
Error.raise_at (Input.mark t.input)
(Invalid_block_scalar_header "expected newline after header");
···
let base_indent = current_indent t in
(* base_indent is the indent level from the stack, -1 if empty.
It's used directly for comparisons in implicit indent case. *)
-
let content_indent = ref (
-
match !explicit_indent with
-
| Some n ->
-
(* Explicit indent: base_indent is 1-indexed column, convert to 0-indexed.
+
let content_indent =
+
ref
+
(match !explicit_indent with
+
| Some n ->
+
(* Explicit indent: base_indent is 1-indexed column, convert to 0-indexed.
content_indent = (base_indent - 1) + n, but at least n for document level. *)
-
let base_level = max 0 (base_indent - 1) in
-
base_level + n
-
| None -> 0 (* Will be determined by first non-empty line *)
-
) in
+
let base_level = max 0 (base_indent - 1) in
+
base_level + n
+
| None -> 0 (* Will be determined by first non-empty line *))
+
in
let buf = Buffer.create 256 in
let trailing_breaks = Buffer.create 16 in
-
let leading_blank = ref false in (* Was the previous line "more indented"? *)
-
let max_empty_line_indent = ref 0 in (* Track max indent of empty lines before first content *)
+
let leading_blank = ref false in
+
(* Was the previous line "more indented"? *)
+
let max_empty_line_indent = ref 0 in
+
(* Track max indent of empty lines before first content *)
(* Skip to content indentation, skipping empty lines.
Returns the number of spaces actually skipped (important for detecting dedentation). *)
···
if !content_indent > 0 then begin
(* Explicit indent - skip up to content_indent spaces *)
let spaces_skipped = ref 0 in
-
while !spaces_skipped < !content_indent && Input.next_is (( = ) ' ') t.input do
+
while
+
!spaces_skipped < !content_indent && Input.next_is (( = ) ' ') t.input
+
do
incr spaces_skipped;
ignore (Input.next t.input)
done;
···
Buffer.add_char trailing_breaks '\n';
Input.consume_break t.input;
skip_to_content_indent ()
-
end else if !spaces_skipped < !content_indent then begin
+
end
+
else if !spaces_skipped < !content_indent then begin
(* Line starts with fewer spaces than content_indent - dedented *)
!spaces_skipped
-
end else if Input.next_is_blank t.input then begin
+
end
+
else if Input.next_is_blank t.input then begin
(* Line has spaces/tabs beyond content_indent - could be whitespace content or empty line.
For literal scalars, whitespace-only lines ARE content (not empty).
For folded scalars, whitespace-only lines that are "more indented" are preserved. *)
···
else begin
(* Folded: check if rest is only blanks *)
let idx = ref 0 in
-
while match Input.peek_nth t.input !idx with
-
| Some c when Input.is_blank c -> incr idx; true
-
| _ -> false
-
do () done;
-
match Input.peek_nth t.input (!idx) with
+
while
+
match Input.peek_nth t.input !idx with
+
| Some c when Input.is_blank c ->
+
incr idx;
+
true
+
| _ -> false
+
do
+
()
+
done;
+
match Input.peek_nth t.input !idx with
| None | Some '\n' | Some '\r' ->
(* Empty/whitespace-only line in folded - skip spaces *)
while Input.next_is_blank t.input do
···
(* Has non-whitespace content *)
!content_indent
end
-
end else
-
!content_indent
-
end else begin
+
end
+
else !content_indent
+
end
+
else begin
(* Implicit indent - skip empty lines without consuming spaces.
Note: Only SPACES count as indentation. Tabs are content, not indentation.
So we only check for spaces when determining if a line is "empty". *)
···
Buffer.add_char trailing_breaks '\n';
Input.consume_break t.input;
skip_to_content_indent ()
-
end else if Input.next_is (( = ) ' ') t.input then begin
+
end
+
else if Input.next_is (( = ) ' ') t.input then begin
(* Check if line is empty (only spaces before break) *)
let idx = ref 0 in
-
while match Input.peek_nth t.input !idx with
-
| Some ' ' -> incr idx; true
-
| _ -> false
-
do () done;
-
match Input.peek_nth t.input (!idx) with
+
while
+
match Input.peek_nth t.input !idx with
+
| Some ' ' ->
+
incr idx;
+
true
+
| _ -> false
+
do
+
()
+
done;
+
match Input.peek_nth t.input !idx with
| None | Some '\n' | Some '\r' ->
(* Line has only spaces - empty line *)
(* Track max indent of empty lines for later validation *)
-
if !idx > !max_empty_line_indent then
-
max_empty_line_indent := !idx;
+
if !idx > !max_empty_line_indent then max_empty_line_indent := !idx;
while Input.next_is (( = ) ' ') t.input do
ignore (Input.next t.input)
done;
···
| _ ->
(* Has content (including tabs which are content, not indentation) *)
0
-
end else if Input.next_is (( = ) '\t') t.input then begin
+
end
+
else if Input.next_is (( = ) '\t') t.input then begin
(* Tab at start of line in implicit indent mode - this is an error (Y79Y)
because tabs cannot be used as indentation in YAML *)
Error.raise_at (Input.mark t.input) Tab_in_indentation
-
end else
+
end
+
else
(* Not at break or space - other content character *)
0
end
···
let should_process =
if !content_indent = 0 then begin
(* For implicit indent, content must be more indented than base_level. *)
-
if line_indent <= base_level then
-
false (* No content - first line not indented enough *)
+
if line_indent <= base_level then false
+
(* No content - first line not indented enough *)
else begin
(* Validate: first content line must be indented at least as much as
the maximum indent seen on empty lines before it (5LLU, S98Z, W9L4) *)
-
if line_indent < !max_empty_line_indent && line_indent > base_level then
+
if line_indent < !max_empty_line_indent && line_indent > base_level
+
then
Error.raise_at (Input.mark t.input)
-
(Invalid_block_scalar_header "wrongly indented line in block scalar");
+
(Invalid_block_scalar_header
+
"wrongly indented line in block scalar");
content_indent := line_indent;
true
end
-
end else if line_indent < !content_indent then
-
false (* Dedented - done with content *)
-
else
-
true
+
end
+
else if line_indent < !content_indent then false
+
(* Dedented - done with content *)
+
else true
in
if should_process then begin
···
For folded scalars, lines that start with any whitespace (space or tab) after the
content indentation are "more indented" and preserve breaks.
Note: we check Input.next_is_blank BEFORE reading content to see if content starts with whitespace. *)
-
let trailing_blank = line_indent > !content_indent || Input.next_is_blank t.input in
+
let trailing_blank =
+
line_indent > !content_indent || Input.next_is_blank t.input
+
in
(* Add trailing breaks to buffer *)
if Buffer.length buf > 0 then begin
if Buffer.length trailing_breaks > 0 then begin
-
if literal then
-
Buffer.add_buffer buf trailing_breaks
+
if literal then Buffer.add_buffer buf trailing_breaks
else begin
(* Folded scalar: fold only if both previous and current lines are not more-indented *)
-
if not !leading_blank && not trailing_blank then begin
+
if (not !leading_blank) && not trailing_blank then begin
let breaks = Buffer.contents trailing_breaks in
-
if String.length breaks = 1 then
-
Buffer.add_char buf ' '
-
else
-
Buffer.add_substring buf breaks 1 (String.length breaks - 1)
-
end else begin
+
if String.length breaks = 1 then Buffer.add_char buf ' '
+
else Buffer.add_substring buf breaks 1 (String.length breaks - 1)
+
end
+
else begin
(* Preserve breaks for more-indented lines *)
Buffer.add_buffer buf trailing_breaks
end
end
-
end else if not literal then
-
Buffer.add_char buf ' '
-
end else
-
Buffer.add_buffer buf trailing_breaks;
+
end
+
else if not literal then Buffer.add_char buf ' '
+
end
+
else Buffer.add_buffer buf trailing_breaks;
Buffer.clear trailing_breaks;
(* Add extra indentation for literal or more-indented folded lines *)
(* On the first line (when determining content_indent), we've already consumed all spaces,
so we should NOT add any back. On subsequent lines, we add only the spaces beyond content_indent. *)
-
if not first_line && (literal || (!extra_spaces > 0 && not literal)) then begin
+
if (not first_line) && (literal || (!extra_spaces > 0 && not literal))
+
then begin
for _ = 1 to !extra_spaces do
Buffer.add_char buf ' '
done
end;
(* Read line content *)
-
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
while
+
(not (Input.is_eof t.input)) && not (Input.next_is_break t.input)
+
do
Buffer.add_char buf (Input.next_exn t.input)
done;
···
| Chomping.Strip -> content
| Chomping.Clip ->
if String.length content > 0 then content ^ "\n" else content
-
| Chomping.Keep ->
-
content ^ Buffer.contents trailing_breaks
+
| Chomping.Keep -> content ^ Buffer.contents trailing_breaks
in
let span = Span.make ~start ~stop:(Input.mark t.input) in
···
(** Scan directive (after %) *)
let scan_directive t =
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume % *)
+
ignore (Input.next t.input);
+
+
(* consume % *)
(* Read directive name *)
let name_buf = Buffer.create 16 in
···
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
let name = Buffer.contents name_buf in
(* Skip blanks *)
···
let minor = ref 0 in
(* Read major version *)
while Input.next_is_digit t.input do
-
major := !major * 10 + (Char.code (Input.next_exn t.input) - Char.code '0')
+
major :=
+
(!major * 10) + (Char.code (Input.next_exn t.input) - Char.code '0')
done;
(* Expect . *)
(match Input.peek t.input with
-
| Some '.' -> ignore (Input.next t.input)
-
| _ -> Error.raise_at (Input.mark t.input) (Invalid_yaml_version "expected '.'"));
+
| Some '.' -> ignore (Input.next t.input)
+
| _ ->
+
Error.raise_at (Input.mark t.input)
+
(Invalid_yaml_version "expected '.'"));
(* Read minor version *)
while Input.next_is_digit t.input do
-
minor := !minor * 10 + (Char.code (Input.next_exn t.input) - Char.code '0')
+
minor :=
+
(!minor * 10) + (Char.code (Input.next_exn t.input) - Char.code '0')
done;
(* Validate: only whitespace and comments allowed before line break (MUS6) *)
skip_whitespace_and_comment t;
-
if not (Input.next_is_break t.input) && not (Input.is_eof t.input) then
-
Error.raise_at (Input.mark t.input) (Invalid_directive "expected comment or line break after version");
+
if (not (Input.next_is_break t.input)) && not (Input.is_eof t.input) then
+
Error.raise_at (Input.mark t.input)
+
(Invalid_directive "expected comment or line break after version");
let span = Span.make ~start ~stop:(Input.mark t.input) in
-
Token.Version_directive { major = !major; minor = !minor }, span
-
+
(Token.Version_directive { major = !major; minor = !minor }, span)
| "TAG" ->
(* Tag directive: %TAG !foo! tag:example.com,2000: *)
let handle = scan_tag_handle t in
···
ignore (Input.next t.input);
true
| _ -> false
-
do () done;
+
do
+
()
+
done;
let prefix = Buffer.contents prefix_buf in
let span = Span.make ~start ~stop:(Input.mark t.input) in
-
Token.Tag_directive { handle; prefix }, span
-
+
(Token.Tag_directive { handle; prefix }, span)
| _ ->
(* Reserved/Unknown directive - skip to end of line and ignore *)
(* Per YAML spec, reserved directives should be ignored with a warning *)
-
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
while (not (Input.is_eof t.input)) && not (Input.next_is_break t.input) do
ignore (Input.next t.input)
done;
let span = Span.make ~start ~stop:(Input.mark t.input) in
(* Return an empty tag directive token to indicate directive was processed but ignored *)
-
Token.Tag_directive { handle = ""; prefix = "" }, span
+
(Token.Tag_directive { handle = ""; prefix = "" }, span)
(** Fetch the next token(s) into the queue *)
let rec fetch_next_token t =
···
(* We're about to process actual content, not leading whitespace *)
t.leading_whitespace <- false;
-
if Input.is_eof t.input then
-
fetch_stream_end t
-
else if Input.at_document_boundary t.input then
-
fetch_document_indicator t
+
if Input.is_eof t.input then fetch_stream_end t
+
else if Input.at_document_boundary t.input then fetch_document_indicator t
else begin
match Input.peek t.input with
| None -> fetch_stream_end t
-
| Some '%' when (Input.position t.input).column = 1 ->
-
fetch_directive t
+
| Some '%' when (Input.position t.input).column = 1 -> fetch_directive t
| Some '[' -> fetch_flow_collection_start t Token.Flow_sequence_start
| Some '{' -> fetch_flow_collection_start t Token.Flow_mapping_start
| Some ']' -> fetch_flow_collection_end t Token.Flow_sequence_end
···
| Some ',' -> fetch_flow_entry t
| Some '-' when t.flow_level = 0 && check_block_entry t ->
fetch_block_entry t
-
| Some '?' when check_key t ->
-
fetch_key t
-
| Some ':' when check_value t ->
-
fetch_value t
+
| Some '?' when check_key t -> fetch_key t
+
| Some ':' when check_value t -> fetch_value t
| Some '*' -> fetch_alias t
| Some '&' -> fetch_anchor t
| Some '!' -> fetch_tag t
···
| Some '>' when t.flow_level = 0 -> fetch_block_scalar t false
| Some '\'' -> fetch_single_quoted t
| Some '"' -> fetch_double_quoted t
-
| Some '-' when can_start_plain t ->
-
fetch_plain_scalar t
-
| Some '?' when can_start_plain t ->
-
fetch_plain_scalar t
-
| Some ':' when can_start_plain t ->
-
fetch_plain_scalar t
-
| Some c when can_start_plain_char c t ->
-
fetch_plain_scalar t
-
| Some c ->
-
Error.raise_at (Input.mark t.input) (Unexpected_character c)
+
| Some '-' when can_start_plain t -> fetch_plain_scalar t
+
| Some '?' when can_start_plain t -> fetch_plain_scalar t
+
| Some ':' when can_start_plain t -> fetch_plain_scalar t
+
| Some c when can_start_plain_char c t -> fetch_plain_scalar t
+
| Some c -> Error.raise_at (Input.mark t.input) (Unexpected_character c)
end
and fetch_stream_end t =
···
let indicator = Input.peek_string t.input 3 in
Input.skip t.input 3;
let span = Span.make ~start ~stop:(Input.mark t.input) in
-
let token = if indicator = "---" then Token.Document_start else Token.Document_end in
+
let token =
+
if indicator = "---" then Token.Document_start else Token.Document_end
+
in
(* Reset document content flag after document end marker *)
if indicator = "..." then begin
t.document_has_content <- false;
(* After document end marker, skip whitespace and check for end of line or comment *)
-
while Input.next_is_blank t.input do ignore (Input.next t.input) done;
-
(match Input.peek t.input with
-
| None -> () (* EOF is ok *)
-
| Some c when Input.is_break c -> ()
-
| Some '#' -> () (* Comment is ok *)
-
| _ -> Error.raise_at start (Invalid_directive "content not allowed after document end marker on same line"))
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
match Input.peek t.input with
+
| None -> () (* EOF is ok *)
+
| Some c when Input.is_break c -> ()
+
| Some '#' -> () (* Comment is ok *)
+
| _ ->
+
Error.raise_at start
+
(Invalid_directive
+
"content not allowed after document end marker on same line")
end;
emit t span token
···
If we've emitted content in the current document, we need a document end marker first *)
if t.document_has_content then
Error.raise_at (Input.mark t.input)
-
(Unexpected_token "directives must be separated from document content by document end marker (...)");
+
(Unexpected_token
+
"directives must be separated from document content by document end \
+
marker (...)");
unroll_indent t (-1);
remove_simple_key t;
t.allow_simple_key <- false;
···
and fetch_flow_collection_start t token_type =
save_simple_key t;
(* Record indent of outermost flow collection *)
-
if t.flow_level = 0 then
-
t.flow_indent <- column t;
+
if t.flow_level = 0 then t.flow_indent <- column t;
t.flow_level <- t.flow_level + 1;
(* Track whether this is a mapping or sequence *)
-
let is_mapping = (token_type = Token.Flow_mapping_start) in
+
let is_mapping = token_type = Token.Flow_mapping_start in
t.flow_mapping_stack <- is_mapping :: t.flow_mapping_stack;
t.allow_simple_key <- true;
t.simple_keys <- None :: t.simple_keys;
···
and fetch_flow_collection_end t token_type =
remove_simple_key t;
t.flow_level <- t.flow_level - 1;
-
t.flow_mapping_stack <- (match t.flow_mapping_stack with _ :: rest -> rest | [] -> []);
+
t.flow_mapping_stack <-
+
(match t.flow_mapping_stack with _ :: rest -> rest | [] -> []);
t.simple_keys <- (match t.simple_keys with _ :: rest -> rest | [] -> []);
t.allow_simple_key <- false;
let start = Input.mark t.input in
···
ignore (Input.next t.input);
(* Check for tabs after - : pattern like -\t- is invalid *)
-
let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in
+
let found_tabs, _found_spaces = skip_blanks_check_tabs t in
if found_tabs then begin
(* If we found tabs and next char is - followed by whitespace, error *)
match Input.peek t.input with
-
| Some '-' ->
-
(match Input.peek_nth t.input 1 with
-
| None -> Error.raise_at start Tab_in_indentation
-
| Some c when Input.is_whitespace c ->
-
Error.raise_at start Tab_in_indentation
-
| Some _ -> ())
+
| Some '-' -> (
+
match Input.peek_nth t.input 1 with
+
| None -> Error.raise_at start Tab_in_indentation
+
| Some c when Input.is_whitespace c ->
+
Error.raise_at start Tab_in_indentation
+
| Some _ -> ())
| _ -> ()
end;
···
and check_key t =
(* ? followed by whitespace or flow indicator in both block and flow *)
match Input.peek_nth t.input 1 with
-
| None -> true
-
| Some c ->
-
Input.is_whitespace c ||
-
(t.flow_level > 0 && Input.is_flow_indicator c)
+
| None -> true
+
| Some c ->
+
Input.is_whitespace c || (t.flow_level > 0 && Input.is_flow_indicator c)
and fetch_key t =
if t.flow_level = 0 then begin
···
ignore (Input.next t.input);
(* Check for tabs after ? : pattern like ?\t- or ?\tkey is invalid *)
-
let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in
+
let found_tabs, _found_spaces = skip_blanks_check_tabs t in
if found_tabs && t.flow_level = 0 then begin
(* In block context, tabs after ? are not allowed *)
Error.raise_at start Tab_in_indentation
···
(* : followed by whitespace in block, or whitespace/flow indicator in flow, or adjacent value *)
match Input.peek_nth t.input 1 with
| None -> true
-
| Some c ->
-
Input.is_whitespace c ||
-
(t.flow_level > 0 && Input.is_flow_indicator c) ||
+
| Some c -> (
+
Input.is_whitespace c
+
|| (t.flow_level > 0 && Input.is_flow_indicator c)
+
||
(* Allow adjacent values in flow context at designated positions *)
-
(t.flow_level > 0 &&
-
match t.adjacent_value_allowed_at with
-
| Some pos -> pos.Position.line = (Input.position t.input).Position.line &&
-
pos.Position.column = (Input.position t.input).Position.column
-
| None -> false)
+
t.flow_level > 0
+
&&
+
match t.adjacent_value_allowed_at with
+
| Some pos ->
+
pos.Position.line = (Input.position t.input).Position.line
+
&& pos.Position.column = (Input.position t.input).Position.column
+
| None -> false)
and fetch_value t =
let start = Input.mark t.input in
···
| Some sk :: _ when sk.sk_possible ->
(* In implicit flow mapping (inside a flow sequence), key and : must be on the same line.
In explicit flow mapping { }, key and : can span lines. *)
-
let is_implicit_flow_mapping = match t.flow_mapping_stack with
-
| false :: _ -> true (* false = we're in a sequence, so any mapping is implicit *)
+
let is_implicit_flow_mapping =
+
match t.flow_mapping_stack with
+
| false :: _ ->
+
true (* false = we're in a sequence, so any mapping is implicit *)
| _ -> false
in
-
if is_implicit_flow_mapping && sk.sk_position.line < (Input.position t.input).line then
-
Error.raise_at start Illegal_flow_key_line;
+
if
+
is_implicit_flow_mapping
+
&& sk.sk_position.line < (Input.position t.input).line
+
then Error.raise_at start Illegal_flow_key_line;
(* Insert KEY token before the simple key value *)
let key_span = Span.point sk.sk_position in
let key_token = { Token.token = Token.Key; span = key_span } in
···
let tokens = Queue.to_seq t.tokens |> Array.of_seq in
Queue.clear t.tokens;
let insert_pos = sk.sk_token_number - t.tokens_taken in
-
Array.iteri (fun i tok ->
-
if i = insert_pos then Queue.add key_token t.tokens;
-
Queue.add tok t.tokens
-
) tokens;
-
if insert_pos >= Array.length tokens then
-
Queue.add key_token t.tokens;
+
Array.iteri
+
(fun i tok ->
+
if i = insert_pos then Queue.add key_token t.tokens;
+
Queue.add tok t.tokens)
+
tokens;
+
if insert_pos >= Array.length tokens then Queue.add key_token t.tokens;
t.token_number <- t.token_number + 1;
(* Roll indent for implicit block mapping *)
if t.flow_level = 0 then begin
···
let bm_token = { Token.token = Token.Block_mapping_start; span } in
let tokens = Queue.to_seq t.tokens |> Array.of_seq in
Queue.clear t.tokens;
-
Array.iteri (fun i tok ->
-
if i = insert_pos then Queue.add bm_token t.tokens;
-
Queue.add tok t.tokens
-
) tokens;
+
Array.iteri
+
(fun i tok ->
+
if i = insert_pos then Queue.add bm_token t.tokens;
+
Queue.add tok t.tokens)
+
tokens;
if insert_pos >= Array.length tokens then
Queue.add bm_token t.tokens;
t.token_number <- t.token_number + 1
end
end;
-
t.simple_keys <- None :: (List.tl t.simple_keys);
+
t.simple_keys <- None :: List.tl t.simple_keys;
true
| _ ->
(* No simple key - this is a complex value (or empty key) *)
···
remove_simple_key t;
(* In block context without simple key, allow simple keys for compact mappings like ": moon: white"
In flow context or after using a simple key, disallow simple keys *)
-
t.allow_simple_key <- (not used_simple_key) && (t.flow_level = 0);
+
t.allow_simple_key <- (not used_simple_key) && t.flow_level = 0;
t.document_has_content <- true;
let start = Input.mark t.input in
ignore (Input.next t.input);
(* Check for tabs after : : patterns like :\t- or :\tkey: are invalid in block context (Y79Y/09)
However, :\t bar (tab followed by space then content) is valid (6BCT) *)
-
let (found_tabs, found_spaces) = skip_blanks_check_tabs t in
-
if found_tabs && not found_spaces && t.flow_level = 0 then begin
+
let found_tabs, found_spaces = skip_blanks_check_tabs t in
+
if found_tabs && (not found_spaces) && t.flow_level = 0 then begin
(* In block context, tabs-only after : followed by indicator or alphanumeric are not allowed *)
match Input.peek t.input with
-
| Some ('-' | '?') ->
-
Error.raise_at start Tab_in_indentation
-
| Some c when (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ->
+
| Some ('-' | '?') -> Error.raise_at start Tab_in_indentation
+
| Some c
+
when (c >= 'a' && c <= 'z')
+
|| (c >= 'A' && c <= 'Z')
+
|| (c >= '0' && c <= '9') ->
(* Tab-only followed by alphanumeric - likely a key, which is invalid *)
Error.raise_at start Tab_in_indentation
| _ -> ()
···
t.allow_simple_key <- false;
t.document_has_content <- true;
let start = Input.mark t.input in
-
ignore (Input.next t.input); (* consume * or & *)
+
ignore (Input.next t.input);
+
(* consume * or & *)
let name, span = scan_anchor_alias t in
let span = Span.make ~start ~stop:span.stop in
let token = if is_alias then Token.Alias name else Token.Anchor name in
···
match Input.peek_nth t.input 1 with
| None -> false
| Some c ->
-
not (Input.is_whitespace c) &&
-
(t.flow_level = 0 || not (Input.is_flow_indicator c))
+
(not (Input.is_whitespace c))
+
&& (t.flow_level = 0 || not (Input.is_flow_indicator c))
and can_start_plain_char c _t =
(* Characters that can start a plain scalar *)
···
(* If the plain scalar ended after crossing a line break (leading_blanks = true),
allow simple keys. This is important because the scanner already consumed the
line break and leading whitespace when checking for continuation. *)
-
if ended_with_linebreak then
-
t.allow_simple_key <- true;
+
if ended_with_linebreak then t.allow_simple_key <- true;
emit t span (Token.Scalar { style = `Plain; value })
(** Check if we need more tokens to resolve simple keys *)
···
else if Queue.is_empty t.tokens then true
else
(* Check if any simple key could affect the first queued token *)
-
List.exists (function
-
| Some sk when sk.sk_possible ->
-
sk.sk_token_number >= t.tokens_taken
-
| _ -> false
-
) t.simple_keys
+
List.exists
+
(function
+
| Some sk when sk.sk_possible -> sk.sk_token_number >= t.tokens_taken
+
| _ -> false)
+
t.simple_keys
(** Ensure we have enough tokens to return one safely *)
let ensure_tokens t =
···
(** Get next token *)
let next t =
ensure_tokens t;
-
if Queue.is_empty t.tokens then
-
None
+
if Queue.is_empty t.tokens then None
else begin
t.tokens_taken <- t.tokens_taken + 1;
Some (Queue.pop t.tokens)
···
let rec loop () =
match next t with
| None -> ()
-
| Some tok -> f tok; loop ()
+
| Some tok ->
+
f tok;
+
loop ()
in
loop ()
(** Fold over all tokens *)
let fold f init t =
let rec loop acc =
-
match next t with
-
| None -> acc
-
| Some tok -> loop (f acc tok)
+
match next t with None -> acc | Some tok -> loop (f acc tok)
in
loop init
(** Convert to list *)
-
let to_list t =
-
fold (fun acc tok -> tok :: acc) [] t |> List.rev
+
let to_list t = fold (fun acc tok -> tok :: acc) [] t |> List.rev
+20 -28
lib/sequence.ml
···
members : 'a list;
}
-
let make
-
?(anchor : string option)
-
?(tag : string option)
-
?(implicit = true)
-
?(style = `Any)
-
members =
+
let make ?(anchor : string option) ?(tag : string option) ?(implicit = true)
+
?(style = `Any) members =
{ anchor; tag; implicit; style; members }
let members t = t.members
···
let tag t = t.tag
let implicit t = t.implicit
let style t = t.style
-
let with_anchor anchor t = { t with anchor = Some anchor }
let with_tag tag t = { t with tag = Some tag }
let with_style style t = { t with style }
-
let map f t = { t with members = List.map f t.members }
-
let length t = List.length t.members
-
let is_empty t = t.members = []
-
let nth t n = List.nth t.members n
-
let nth_opt t n = List.nth_opt t.members n
-
let iter f t = List.iter f t.members
-
let fold f init t = List.fold_left f init t.members
let pp pp_elem fmt t =
···
Option.iter (Format.fprintf fmt "tag=%s,@ ") t.tag;
Format.fprintf fmt "style=%a,@ " Layout_style.pp t.style;
Format.fprintf fmt "members=[@,%a@]@,)"
-
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") pp_elem)
+
(Format.pp_print_list
+
~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ")
+
pp_elem)
t.members
let equal eq a b =
-
Option.equal String.equal a.anchor b.anchor &&
-
Option.equal String.equal a.tag b.tag &&
-
a.implicit = b.implicit &&
-
Layout_style.equal a.style b.style &&
-
List.equal eq a.members b.members
+
Option.equal String.equal a.anchor b.anchor
+
&& Option.equal String.equal a.tag b.tag
+
&& a.implicit = b.implicit
+
&& Layout_style.equal a.style b.style
+
&& List.equal eq a.members b.members
let compare cmp a b =
let c = Option.compare String.compare a.anchor b.anchor in
-
if c <> 0 then c else
-
let c = Option.compare String.compare a.tag b.tag in
-
if c <> 0 then c else
-
let c = Bool.compare a.implicit b.implicit in
-
if c <> 0 then c else
-
let c = Layout_style.compare a.style b.style in
-
if c <> 0 then c else
-
List.compare cmp a.members b.members
+
if c <> 0 then c
+
else
+
let c = Option.compare String.compare a.tag b.tag in
+
if c <> 0 then c
+
else
+
let c = Bool.compare a.implicit b.implicit in
+
if c <> 0 then c
+
else
+
let c = Layout_style.compare a.style b.style in
+
if c <> 0 then c else List.compare cmp a.members b.members
+166 -139
lib/serialize.ml
···
(** {1 Internal Helpers} *)
-
(** Emit a YAML node using an emit function.
-
This is the core implementation used by both Emitter.t and function-based APIs. *)
+
(** Emit a YAML node using an emit function. This is the core implementation
+
used by both Emitter.t and function-based APIs. *)
let rec emit_yaml_node_impl ~emit (yaml : Yaml.t) =
match yaml with
| `Scalar s ->
-
emit (Event.Scalar {
-
anchor = Scalar.anchor s;
-
tag = Scalar.tag s;
-
value = Scalar.value s;
-
plain_implicit = Scalar.plain_implicit s;
-
quoted_implicit = Scalar.quoted_implicit s;
-
style = Scalar.style s;
-
})
-
-
| `Alias name ->
-
emit (Event.Alias { anchor = name })
-
+
emit
+
(Event.Scalar
+
{
+
anchor = Scalar.anchor s;
+
tag = Scalar.tag s;
+
value = Scalar.value s;
+
plain_implicit = Scalar.plain_implicit s;
+
quoted_implicit = Scalar.quoted_implicit s;
+
style = Scalar.style s;
+
})
+
| `Alias name -> emit (Event.Alias { anchor = name })
| `A seq ->
let members = Sequence.members seq in
(* Force flow style for empty sequences *)
let style = if members = [] then `Flow else Sequence.style seq in
-
emit (Event.Sequence_start {
-
anchor = Sequence.anchor seq;
-
tag = Sequence.tag seq;
-
implicit = Sequence.implicit seq;
-
style;
-
});
+
emit
+
(Event.Sequence_start
+
{
+
anchor = Sequence.anchor seq;
+
tag = Sequence.tag seq;
+
implicit = Sequence.implicit seq;
+
style;
+
});
List.iter (emit_yaml_node_impl ~emit) members;
emit Event.Sequence_end
-
| `O map ->
let members = Mapping.members map in
(* Force flow style for empty mappings *)
let style = if members = [] then `Flow else Mapping.style map in
-
emit (Event.Mapping_start {
-
anchor = Mapping.anchor map;
-
tag = Mapping.tag map;
-
implicit = Mapping.implicit map;
-
style;
-
});
-
List.iter (fun (k, v) ->
-
emit_yaml_node_impl ~emit k;
-
emit_yaml_node_impl ~emit v
-
) members;
+
emit
+
(Event.Mapping_start
+
{
+
anchor = Mapping.anchor map;
+
tag = Mapping.tag map;
+
implicit = Mapping.implicit map;
+
style;
+
});
+
List.iter
+
(fun (k, v) ->
+
emit_yaml_node_impl ~emit k;
+
emit_yaml_node_impl ~emit v)
+
members;
emit Event.Mapping_end
-
(** Emit a Value node using an emit function.
-
This is the core implementation used by both Emitter.t and function-based APIs. *)
+
(** Emit a Value node using an emit function. This is the core implementation
+
used by both Emitter.t and function-based APIs. *)
let rec emit_value_node_impl ~emit ~config (value : Value.t) =
match value with
| `Null ->
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value = "null";
-
plain_implicit = true; quoted_implicit = false;
-
style = `Plain;
-
})
-
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = "null";
+
plain_implicit = true;
+
quoted_implicit = false;
+
style = `Plain;
+
})
| `Bool b ->
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value = if b then "true" else "false";
-
plain_implicit = true; quoted_implicit = false;
-
style = `Plain;
-
})
-
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = (if b then "true" else "false");
+
plain_implicit = true;
+
quoted_implicit = false;
+
style = `Plain;
+
})
| `Float f ->
let value =
match Float.classify_float f with
···
| _ ->
if Float.is_integer f && Float.abs f < 1e15 then
Printf.sprintf "%.0f" f
-
else
-
Printf.sprintf "%g" f
+
else Printf.sprintf "%g" f
in
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value;
-
plain_implicit = true; quoted_implicit = false;
-
style = `Plain;
-
})
-
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value;
+
plain_implicit = true;
+
quoted_implicit = false;
+
style = `Plain;
+
})
| `String s ->
let style = Quoting.choose_style s in
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value = s;
-
plain_implicit = style = `Plain;
-
quoted_implicit = style <> `Plain;
-
style;
-
})
-
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = s;
+
plain_implicit = style = `Plain;
+
quoted_implicit = style <> `Plain;
+
style;
+
})
| `A items ->
(* Force flow style for empty sequences, otherwise use config *)
let style =
if items = [] || config.Emitter.layout_style = `Flow then `Flow else `Block
in
-
emit (Event.Sequence_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style;
-
});
+
emit
+
(Event.Sequence_start
+
{ anchor = None; tag = None; implicit = true; style });
List.iter (emit_value_node_impl ~emit ~config) items;
emit Event.Sequence_end
-
| `O pairs ->
(* Force flow style for empty mappings, otherwise use config *)
let style =
if pairs = [] || config.Emitter.layout_style = `Flow then `Flow else `Block
in
-
emit (Event.Mapping_start {
-
anchor = None; tag = None;
-
implicit = true;
-
style;
-
});
-
List.iter (fun (k, v) ->
-
let style = Quoting.choose_style k in
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value = k;
-
plain_implicit = style = `Plain;
-
quoted_implicit = style <> `Plain;
-
style;
-
});
-
emit_value_node_impl ~emit ~config v
-
) pairs;
+
emit
+
(Event.Mapping_start
+
{ anchor = None; tag = None; implicit = true; style });
+
List.iter
+
(fun (k, v) ->
+
let style = Quoting.choose_style k in
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = k;
+
plain_implicit = style = `Plain;
+
quoted_implicit = style <> `Plain;
+
style;
+
});
+
emit_value_node_impl ~emit ~config v)
+
pairs;
emit Event.Mapping_end
(** Strip anchors from a YAML tree (used when resolving aliases for output) *)
···
| `Scalar s ->
if Option.is_none (Scalar.anchor s) then yaml
else
-
`Scalar (Scalar.make
-
?tag:(Scalar.tag s)
-
~plain_implicit:(Scalar.plain_implicit s)
-
~quoted_implicit:(Scalar.quoted_implicit s)
-
~style:(Scalar.style s)
-
(Scalar.value s))
+
`Scalar
+
(Scalar.make ?tag:(Scalar.tag s)
+
~plain_implicit:(Scalar.plain_implicit s)
+
~quoted_implicit:(Scalar.quoted_implicit s) ~style:(Scalar.style s)
+
(Scalar.value s))
| `Alias _ -> yaml
| `A seq ->
-
`A (Sequence.make
-
?tag:(Sequence.tag seq)
-
~implicit:(Sequence.implicit seq)
-
~style:(Sequence.style seq)
-
(List.map strip_anchors (Sequence.members seq)))
+
`A
+
(Sequence.make ?tag:(Sequence.tag seq) ~implicit:(Sequence.implicit seq)
+
~style:(Sequence.style seq)
+
(List.map strip_anchors (Sequence.members seq)))
| `O map ->
-
`O (Mapping.make
-
?tag:(Mapping.tag map)
-
~implicit:(Mapping.implicit map)
-
~style:(Mapping.style map)
-
(List.map (fun (k, v) -> (strip_anchors k, strip_anchors v)) (Mapping.members map)))
+
`O
+
(Mapping.make ?tag:(Mapping.tag map) ~implicit:(Mapping.implicit map)
+
~style:(Mapping.style map)
+
(List.map
+
(fun (k, v) -> (strip_anchors k, strip_anchors v))
+
(Mapping.members map)))
(** Emit a document using an emit function *)
let emit_document_impl ?(resolve_aliases = true) ~emit doc =
-
emit (Event.Document_start {
-
version = Document.version doc;
-
implicit = Document.implicit_start doc;
-
});
+
emit
+
(Event.Document_start
+
{
+
version = Document.version doc;
+
implicit = Document.implicit_start doc;
+
});
(match Document.root doc with
-
| Some yaml ->
-
let yaml = if resolve_aliases then
-
yaml |> Yaml.resolve_aliases |> strip_anchors
-
else yaml in
-
emit_yaml_node_impl ~emit yaml
-
| None ->
-
emit (Event.Scalar {
-
anchor = None; tag = None;
-
value = "";
-
plain_implicit = true; quoted_implicit = false;
-
style = `Plain;
-
}));
+
| Some yaml ->
+
let yaml =
+
if resolve_aliases then yaml |> Yaml.resolve_aliases |> strip_anchors
+
else yaml
+
in
+
emit_yaml_node_impl ~emit yaml
+
| None ->
+
emit
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = "";
+
plain_implicit = true;
+
quoted_implicit = false;
+
style = `Plain;
+
}));
emit (Event.Document_end { implicit = Document.implicit_end doc })
(** {1 Emitter.t-based API} *)
(** Emit a YAML node to an emitter *)
-
let emit_yaml_node t yaml =
-
emit_yaml_node_impl ~emit:(Emitter.emit t) yaml
+
let emit_yaml_node t yaml = emit_yaml_node_impl ~emit:(Emitter.emit t) yaml
(** Emit a complete YAML document to an emitter *)
let emit_yaml t yaml =
···
(** Serialize documents to a buffer.
@param config Emitter configuration (default: {!Emitter.default_config})
-
@param resolve_aliases Whether to resolve aliases before emission (default: true)
+
@param resolve_aliases
+
Whether to resolve aliases before emission (default: true)
@param buffer Optional buffer to append to; creates new one if not provided
@return The buffer containing serialized YAML *)
-
let documents_to_buffer ?(config = Emitter.default_config) ?(resolve_aliases = true) ?buffer documents =
+
let documents_to_buffer ?(config = Emitter.default_config)
+
?(resolve_aliases = true) ?buffer documents =
let buf = Option.value buffer ~default:(Buffer.create 1024) in
let t = Emitter.create ~config () in
Emitter.emit t (Event.Stream_start { encoding = config.encoding });
···
(** Serialize documents to a string.
@param config Emitter configuration (default: {!Emitter.default_config})
-
@param resolve_aliases Whether to resolve aliases before emission (default: true) *)
-
let documents_to_string ?(config = Emitter.default_config) ?(resolve_aliases = true) documents =
+
@param resolve_aliases
+
Whether to resolve aliases before emission (default: true) *)
+
let documents_to_string ?(config = Emitter.default_config)
+
?(resolve_aliases = true) documents =
Buffer.contents (documents_to_buffer ~config ~resolve_aliases documents)
(** {1 Writer-based API}
-
These functions write directly to a bytesrw [Bytes.Writer.t],
-
enabling true streaming output without intermediate string allocation.
-
Uses the emitter's native Writer support for efficiency. *)
+
These functions write directly to a bytesrw [Bytes.Writer.t], enabling true
+
streaming output without intermediate string allocation. Uses the emitter's
+
native Writer support for efficiency. *)
(** Serialize a Value directly to a Bytes.Writer.
@param config Emitter configuration (default: {!Emitter.default_config})
-
@param eod Whether to write end-of-data after serialization (default: true) *)
-
let value_to_writer ?(config = Emitter.default_config) ?(eod = true) writer value =
+
@param eod Whether to write end-of-data after serialization (default: true)
+
*)
+
let value_to_writer ?(config = Emitter.default_config) ?(eod = true) writer
+
value =
let t = Emitter.of_writer ~config writer in
emit_value t value;
if eod then Emitter.flush t
···
(** Serialize a Yaml.t directly to a Bytes.Writer.
@param config Emitter configuration (default: {!Emitter.default_config})
-
@param eod Whether to write end-of-data after serialization (default: true) *)
-
let yaml_to_writer ?(config = Emitter.default_config) ?(eod = true) writer yaml =
+
@param eod Whether to write end-of-data after serialization (default: true)
+
*)
+
let yaml_to_writer ?(config = Emitter.default_config) ?(eod = true) writer yaml
+
=
let t = Emitter.of_writer ~config writer in
emit_yaml t yaml;
if eod then Emitter.flush t
···
(** Serialize documents directly to a Bytes.Writer.
@param config Emitter configuration (default: {!Emitter.default_config})
-
@param resolve_aliases Whether to resolve aliases before emission (default: true)
-
@param eod Whether to write end-of-data after serialization (default: true) *)
-
let documents_to_writer ?(config = Emitter.default_config) ?(resolve_aliases = true) ?(eod = true) writer documents =
+
@param resolve_aliases
+
Whether to resolve aliases before emission (default: true)
+
@param eod Whether to write end-of-data after serialization (default: true)
+
*)
+
let documents_to_writer ?(config = Emitter.default_config)
+
?(resolve_aliases = true) ?(eod = true) writer documents =
let t = Emitter.of_writer ~config writer in
Emitter.emit t (Event.Stream_start { encoding = config.encoding });
List.iter (emit_document ~resolve_aliases t) documents;
···
(** {1 Function-based API}
-
These functions accept an emit function [Event.t -> unit] instead of
-
an {!Emitter.t}, allowing them to work with any event sink
-
(e.g., streaming writers, custom processors). *)
+
These functions accept an emit function [Event.t -> unit] instead of an
+
{!Emitter.t}, allowing them to work with any event sink (e.g., streaming
+
writers, custom processors). *)
(** Emit a YAML node using an emitter function *)
-
let emit_yaml_node_fn ~emitter yaml =
-
emit_yaml_node_impl ~emit:emitter yaml
+
let emit_yaml_node_fn ~emitter yaml = emit_yaml_node_impl ~emit:emitter yaml
(** Emit a complete YAML stream using an emitter function *)
let emit_yaml_fn ~emitter ~config yaml =
+10 -16
lib/span.ml
···
(** Source spans representing ranges in input *)
-
type t = {
-
start : Position.t;
-
stop : Position.t;
-
}
+
type t = { start : Position.t; stop : Position.t }
let make ~start ~stop = { start; stop }
-
let point pos = { start = pos; stop = pos }
let merge a b =
-
let start = if Position.compare a.start b.start <= 0 then a.start else b.start in
+
let start =
+
if Position.compare a.start b.start <= 0 then a.start else b.start
+
in
let stop = if Position.compare a.stop b.stop >= 0 then a.stop else b.stop in
{ start; stop }
-
let extend span pos =
-
{ span with stop = pos }
+
let extend span pos = { span with stop = pos }
let pp fmt t =
if t.start.line = t.stop.line then
-
Format.fprintf fmt "line %d, columns %d-%d"
-
t.start.line t.start.column t.stop.column
-
else
-
Format.fprintf fmt "lines %d-%d" t.start.line t.stop.line
+
Format.fprintf fmt "line %d, columns %d-%d" t.start.line t.start.column
+
t.stop.column
+
else Format.fprintf fmt "lines %d-%d" t.start.line t.stop.line
-
let to_string t =
-
Format.asprintf "%a" pp t
+
let to_string t = Format.asprintf "%a" pp t
let compare a b =
let c = Position.compare a.start b.start in
if c <> 0 then c else Position.compare a.stop b.stop
-
let equal a b =
-
Position.equal a.start b.start && Position.equal a.stop b.stop
+
let equal a b = Position.equal a.start b.start && Position.equal a.stop b.stop
+12 -14
lib/tag.ml
···
| 0 -> None
| _ when s.[0] <> '!' -> None
| 1 -> Some { handle = "!"; suffix = "" }
-
| _ ->
+
| _ -> (
match s.[1] with
-
| '!' -> (* !! handle *)
+
| '!' ->
+
(* !! handle *)
Some { handle = "!!"; suffix = String.sub s 2 (len - 2) }
-
| '<' -> (* Verbatim tag !<...> *)
+
| '<' ->
+
(* Verbatim tag !<...> *)
if len > 2 && s.[len - 1] = '>' then
Some { handle = "!"; suffix = String.sub s 2 (len - 3) }
-
else
-
None
-
| _ -> (* Primary handle or local tag *)
-
Some { handle = "!"; suffix = String.sub s 1 (len - 1) }
+
else None
+
| _ ->
+
(* Primary handle or local tag *)
+
Some { handle = "!"; suffix = String.sub s 1 (len - 1) })
let to_string t =
-
if t.handle = "!" && t.suffix = "" then "!"
-
else t.handle ^ t.suffix
+
if t.handle = "!" && t.suffix = "" then "!" else t.handle ^ t.suffix
let to_uri t =
match t.handle with
···
| "!" -> "!" ^ t.suffix
| h -> h ^ t.suffix
-
let pp fmt t =
-
Format.pp_print_string fmt (to_string t)
-
-
let equal a b =
-
String.equal a.handle b.handle && String.equal a.suffix b.suffix
+
let pp fmt t = Format.pp_print_string fmt (to_string t)
+
let equal a b = String.equal a.handle b.handle && String.equal a.suffix b.suffix
let compare a b =
let c = String.compare a.handle b.handle in
+30 -51
lib/token.ml
···
| Stream_end
| Version_directive of { major : int; minor : int }
| Tag_directive of { handle : string; prefix : string }
-
| Document_start (** --- *)
-
| Document_end (** ... *)
+
| Document_start (** --- *)
+
| Document_end (** ... *)
| Block_sequence_start
| Block_mapping_start
-
| Block_entry (** [-] *)
-
| Block_end (** implicit, from dedent *)
+
| Block_entry (** [-] *)
+
| Block_end (** implicit, from dedent *)
| Flow_sequence_start (** \[ *)
-
| Flow_sequence_end (** \] *)
-
| Flow_mapping_start (** \{ *)
-
| Flow_mapping_end (** \} *)
-
| Flow_entry (** [,] *)
-
| Key (** ? or implicit key *)
-
| Value (** : *)
+
| Flow_sequence_end (** \] *)
+
| Flow_mapping_start (** \{ *)
+
| Flow_mapping_end (** \} *)
+
| Flow_entry (** [,] *)
+
| Key (** ? or implicit key *)
+
| Value (** : *)
| Anchor of string (** &name *)
-
| Alias of string (** *name *)
+
| Alias of string (** *name *)
| Tag of { handle : string; suffix : string }
| Scalar of { style : Scalar_style.t; value : string }
-
type spanned = {
-
token : t;
-
span : Span.t;
-
}
+
type spanned = { token : t; span : Span.t }
let pp_token fmt = function
-
| Stream_start enc ->
-
Format.fprintf fmt "STREAM-START(%a)" Encoding.pp enc
-
| Stream_end ->
-
Format.fprintf fmt "STREAM-END"
+
| Stream_start enc -> Format.fprintf fmt "STREAM-START(%a)" Encoding.pp enc
+
| Stream_end -> Format.fprintf fmt "STREAM-END"
| Version_directive { major; minor } ->
Format.fprintf fmt "VERSION-DIRECTIVE(%d.%d)" major minor
| Tag_directive { handle; prefix } ->
Format.fprintf fmt "TAG-DIRECTIVE(%s, %s)" handle prefix
-
| Document_start ->
-
Format.fprintf fmt "DOCUMENT-START"
-
| Document_end ->
-
Format.fprintf fmt "DOCUMENT-END"
-
| Block_sequence_start ->
-
Format.fprintf fmt "BLOCK-SEQUENCE-START"
-
| Block_mapping_start ->
-
Format.fprintf fmt "BLOCK-MAPPING-START"
-
| Block_entry ->
-
Format.fprintf fmt "BLOCK-ENTRY"
-
| Block_end ->
-
Format.fprintf fmt "BLOCK-END"
-
| Flow_sequence_start ->
-
Format.fprintf fmt "FLOW-SEQUENCE-START"
-
| Flow_sequence_end ->
-
Format.fprintf fmt "FLOW-SEQUENCE-END"
-
| Flow_mapping_start ->
-
Format.fprintf fmt "FLOW-MAPPING-START"
-
| Flow_mapping_end ->
-
Format.fprintf fmt "FLOW-MAPPING-END"
-
| Flow_entry ->
-
Format.fprintf fmt "FLOW-ENTRY"
-
| Key ->
-
Format.fprintf fmt "KEY"
-
| Value ->
-
Format.fprintf fmt "VALUE"
-
| Anchor name ->
-
Format.fprintf fmt "ANCHOR(%s)" name
-
| Alias name ->
-
Format.fprintf fmt "ALIAS(%s)" name
-
| Tag { handle; suffix } ->
-
Format.fprintf fmt "TAG(%s, %s)" handle suffix
+
| Document_start -> Format.fprintf fmt "DOCUMENT-START"
+
| Document_end -> Format.fprintf fmt "DOCUMENT-END"
+
| Block_sequence_start -> Format.fprintf fmt "BLOCK-SEQUENCE-START"
+
| Block_mapping_start -> Format.fprintf fmt "BLOCK-MAPPING-START"
+
| Block_entry -> Format.fprintf fmt "BLOCK-ENTRY"
+
| Block_end -> Format.fprintf fmt "BLOCK-END"
+
| Flow_sequence_start -> Format.fprintf fmt "FLOW-SEQUENCE-START"
+
| Flow_sequence_end -> Format.fprintf fmt "FLOW-SEQUENCE-END"
+
| Flow_mapping_start -> Format.fprintf fmt "FLOW-MAPPING-START"
+
| Flow_mapping_end -> Format.fprintf fmt "FLOW-MAPPING-END"
+
| Flow_entry -> Format.fprintf fmt "FLOW-ENTRY"
+
| Key -> Format.fprintf fmt "KEY"
+
| Value -> Format.fprintf fmt "VALUE"
+
| Anchor name -> Format.fprintf fmt "ANCHOR(%s)" name
+
| Alias name -> Format.fprintf fmt "ALIAS(%s)" name
+
| Tag { handle; suffix } -> Format.fprintf fmt "TAG(%s, %s)" handle suffix
| Scalar { style; value } ->
Format.fprintf fmt "SCALAR(%a, %S)" Scalar_style.pp style value
+38 -66
lib/unix/yamlrw_unix.ml
···
(** Yamlrw Unix - Channel and file I/O for YAML
-
This module provides channel and file operations for parsing
-
and emitting YAML using bytesrw for efficient streaming I/O. *)
+
This module provides channel and file operations for parsing and emitting
+
YAML using bytesrw for efficient streaming I/O. *)
open Bytesrw
open Yamlrw
···
(** {1 Channel Input} *)
-
let value_of_channel
-
?(resolve_aliases = true)
+
let value_of_channel ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
ic =
+
?(max_depth = Yaml.default_max_alias_depth) ic =
let reader = Bytes.Reader.of_in_channel ic in
Loader.value_of_reader ~resolve_aliases ~max_nodes ~max_depth reader
-
let yaml_of_channel
-
?(resolve_aliases = false)
+
let yaml_of_channel ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
ic =
+
?(max_depth = Yaml.default_max_alias_depth) ic =
let reader = Bytes.Reader.of_in_channel ic in
Loader.yaml_of_reader ~resolve_aliases ~max_nodes ~max_depth reader
···
(** {1 Channel Output} *)
-
let value_to_channel
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
oc
-
(v : value) =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let value_to_channel ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) oc (v : value) =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytes.Writer.of_out_channel oc in
Serialize.value_to_writer ~config writer v
-
let yaml_to_channel
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
oc
-
(v : yaml) =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let yaml_to_channel ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) oc (v : yaml) =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytes.Writer.of_out_channel oc in
Serialize.yaml_to_writer ~config writer v
-
let documents_to_channel
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
-
oc
-
docs =
-
let config = { Emitter.default_config with encoding; scalar_style; layout_style } in
+
let documents_to_channel ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) oc docs =
+
let config =
+
{ Emitter.default_config with encoding; scalar_style; layout_style }
+
in
let writer = Bytes.Writer.of_out_channel oc in
Serialize.documents_to_writer ~config ~resolve_aliases writer docs
(** {1 File Input} *)
-
let value_of_file
-
?(resolve_aliases = true)
+
let value_of_file ?(resolve_aliases = true)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
path =
+
?(max_depth = Yaml.default_max_alias_depth) path =
In_channel.with_open_bin path (fun ic ->
-
value_of_channel ~resolve_aliases ~max_nodes ~max_depth ic)
+
value_of_channel ~resolve_aliases ~max_nodes ~max_depth ic)
-
let yaml_of_file
-
?(resolve_aliases = false)
+
let yaml_of_file ?(resolve_aliases = false)
?(max_nodes = Yaml.default_max_alias_nodes)
-
?(max_depth = Yaml.default_max_alias_depth)
-
path =
+
?(max_depth = Yaml.default_max_alias_depth) path =
In_channel.with_open_bin path (fun ic ->
-
yaml_of_channel ~resolve_aliases ~max_nodes ~max_depth ic)
+
yaml_of_channel ~resolve_aliases ~max_nodes ~max_depth ic)
-
let documents_of_file path =
-
In_channel.with_open_bin path documents_of_channel
+
let documents_of_file path = In_channel.with_open_bin path documents_of_channel
(** {1 File Output} *)
-
let value_to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
path
-
v =
+
let value_to_file ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) path v =
Out_channel.with_open_bin path (fun oc ->
-
value_to_channel ~encoding ~scalar_style ~layout_style oc v)
+
value_to_channel ~encoding ~scalar_style ~layout_style oc v)
-
let yaml_to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
path
-
v =
+
let yaml_to_file ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) path v =
Out_channel.with_open_bin path (fun oc ->
-
yaml_to_channel ~encoding ~scalar_style ~layout_style oc v)
+
yaml_to_channel ~encoding ~scalar_style ~layout_style oc v)
-
let documents_to_file
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
-
path
-
docs =
+
let documents_to_file ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) path docs =
Out_channel.with_open_bin path (fun oc ->
-
documents_to_channel ~encoding ~scalar_style ~layout_style ~resolve_aliases oc docs)
+
documents_to_channel ~encoding ~scalar_style ~layout_style
+
~resolve_aliases oc docs)
+4 -12
lib/unix/yamlrw_unix.mli
···
(** Yamlrw Unix - Channel and file I/O for YAML
-
This module provides channel and file operations for parsing
-
and emitting YAML using bytesrw for efficient streaming I/O. *)
+
This module provides channel and file operations for parsing and emitting
+
YAML using bytesrw for efficient streaming I/O. *)
(** {1 Types} *)
···
(** {1 File Input} *)
val value_of_file :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
string ->
-
value
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> string -> value
(** Parse a JSON-compatible value from a file. *)
val yaml_of_file :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
string ->
-
yaml
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> string -> yaml
(** Parse a full YAML value from a file. *)
val documents_of_file : string -> document list
+16 -20
lib/value.ml
···
(** JSON-compatible YAML value representation *)
-
type t = [
-
| `Null
+
type t =
+
[ `Null
| `Bool of bool
| `Float of float
| `String of string
| `A of t list
-
| `O of (string * t) list
-
]
+
| `O of (string * t) list ]
(* Type equality is ensured by structural compatibility with Yamlrw.value *)
···
let int n : t = `Float (Float.of_int n)
let float f : t = `Float f
let string s : t = `String s
-
let list f xs : t = `A (List.map f xs)
let obj pairs : t = `O pairs
···
| `O pairs -> List.exists (fun (k, _) -> k = key) pairs
| _ -> false
-
let find key = function
-
| `O pairs -> List.assoc_opt key pairs
-
| _ -> None
+
let find key = function `O pairs -> List.assoc_opt key pairs | _ -> None
let get key v =
-
match find key v with
-
| Some v -> v
-
| None -> Error.raise (Key_not_found key)
+
match find key v with Some v -> v | None -> Error.raise (Key_not_found key)
let keys = function
| `O pairs -> List.map fst pairs
···
(** Combinators *)
let combine v1 v2 =
-
match v1, v2 with
+
match (v1, v2) with
| `O o1, `O o2 -> `O (o1 @ o2)
| v1, _ -> Error.raise (Type_mismatch ("object", type_name v1))
···
| `Float f ->
if Float.is_integer f && Float.abs f < 1e15 then
Format.fprintf fmt "%.0f" f
-
else
-
Format.fprintf fmt "%g" f
+
else Format.fprintf fmt "%g" f
| `String s -> Format.fprintf fmt "%S" s
| `A [] -> Format.pp_print_string fmt "[]"
| `A items ->
Format.fprintf fmt "@[<hv 2>[@,%a@]@,]"
-
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") pp)
+
(Format.pp_print_list
+
~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ")
+
pp)
items
| `O [] -> Format.pp_print_string fmt "{}"
| `O pairs ->
Format.fprintf fmt "@[<hv 2>{@,%a@]@,}"
-
(Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ")
+
(Format.pp_print_list
+
~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ")
(fun fmt (k, v) -> Format.fprintf fmt "@[<hv 2>%S:@ %a@]" k pp v))
pairs
(** Equality and comparison *)
let rec equal (a : t) (b : t) =
-
match a, b with
+
match (a, b) with
| `Null, `Null -> true
| `Bool a, `Bool b -> a = b
| `Float a, `Float b -> Float.equal a b
| `String a, `String b -> String.equal a b
| `A a, `A b -> List.equal equal a b
| `O a, `O b ->
-
List.length a = List.length b &&
-
List.for_all2 (fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2) a b
+
List.length a = List.length b
+
&& List.for_all2 (fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2) a b
| _ -> false
let rec compare (a : t) (b : t) =
-
match a, b with
+
match (a, b) with
| `Null, `Null -> 0
| `Null, _ -> -1
| _, `Null -> 1
+115 -102
lib/yaml.ml
···
(** Full YAML representation with anchors, tags, and aliases *)
-
type t = [
-
| `Scalar of Scalar.t
+
type t =
+
[ `Scalar of Scalar.t
| `Alias of string
| `A of t Sequence.t
-
| `O of (t, t) Mapping.t
-
]
+
| `O of (t, t) Mapping.t ]
(** Pretty printing *)
···
(** Equality *)
let rec equal (a : t) (b : t) =
-
match a, b with
+
match (a, b) with
| `Scalar a, `Scalar b -> Scalar.equal a b
| `Alias a, `Alias b -> String.equal a b
| `A a, `A b -> Sequence.equal equal a b
···
| `Bool false -> `Scalar (Scalar.make "false")
| `Float f ->
let s =
-
if Float.is_integer f && Float.abs f < 1e15 then
-
Printf.sprintf "%.0f" f
-
else
-
Printf.sprintf "%g" f
+
if Float.is_integer f && Float.abs f < 1e15 then Printf.sprintf "%.0f" f
+
else Printf.sprintf "%g" f
in
`Scalar (Scalar.make s)
-
| `String s ->
-
`Scalar (Scalar.make s ~style:`Double_quoted)
-
| `A items ->
-
`A (Sequence.make (List.map of_value items))
+
| `String s -> `Scalar (Scalar.make s ~style:`Double_quoted)
+
| `A items -> `A (Sequence.make (List.map of_value items))
| `O pairs ->
-
`O (Mapping.make (List.map (fun (k, v) ->
-
(`Scalar (Scalar.make k), of_value v)
-
) pairs))
+
`O
+
(Mapping.make
+
(List.map
+
(fun (k, v) -> (`Scalar (Scalar.make k), of_value v))
+
pairs))
-
(** Default limits for alias expansion (protection against billion laughs attack) *)
+
(** Default limits for alias expansion (protection against billion laughs
+
attack) *)
let default_max_alias_nodes = 10_000_000
+
let default_max_alias_depth = 100
(** Resolve aliases by replacing them with referenced nodes.
-
Processes the tree in document order so that aliases resolve to the
-
anchor value that was defined at the point the alias was encountered.
+
Processes the tree in document order so that aliases resolve to the anchor
+
value that was defined at the point the alias was encountered.
-
See {{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2
-
(Anchors and Aliases)} of the YAML 1.2.2 specification for details on
-
how anchors and aliases work in YAML.
+
See
+
{{:https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases}Section 3.2.2.2
+
(Anchors and Aliases)} of the YAML 1.2.2 specification for details on how
+
anchors and aliases work in YAML.
-
This implements protection against the "billion laughs attack"
-
(see {{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1 (Processes)})
-
by limiting both the total number of nodes and the nesting depth during expansion.
+
This implements protection against the "billion laughs attack" (see
+
{{:https://yaml.org/spec/1.2.2/#321-processes}Section 3.2.1 (Processes)}) by
+
limiting both the total number of nodes and the nesting depth during
+
expansion.
-
@param max_nodes Maximum number of nodes to create during expansion (default 10M)
-
@param max_depth Maximum depth of alias-within-alias resolution (default 100)
-
@raise Error.Yamlrw_error with {!type:Error.kind} [Alias_expansion_node_limit] if max_nodes is exceeded
-
@raise Error.Yamlrw_error with {!type:Error.kind} [Alias_expansion_depth_limit] if max_depth is exceeded
-
*)
-
let resolve_aliases ?(max_nodes = default_max_alias_nodes) ?(max_depth = default_max_alias_depth) (root : t) : t =
+
@param max_nodes
+
Maximum number of nodes to create during expansion (default 10M)
+
@param max_depth
+
Maximum depth of alias-within-alias resolution (default 100)
+
@raise Error.Yamlrw_error
+
with {!type:Error.kind} [Alias_expansion_node_limit] if max_nodes is
+
exceeded
+
@raise Error.Yamlrw_error
+
with {!type:Error.kind} [Alias_expansion_depth_limit] if max_depth is
+
exceeded *)
+
let resolve_aliases ?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) (root : t) : t =
let anchors = Hashtbl.create 16 in
let node_count = ref 0 in
···
need expansion if it was registered before those anchors existed *)
resolve ~depth:(depth + 1) target
| None -> Error.raise (Undefined_alias name)
-
(* Single pass: process in document order, registering anchors and resolving aliases *)
and resolve ~depth (v : t) : t =
check_node_limit ();
···
(* Register anchor after we have the resolved node *)
Option.iter (fun name -> register_anchor name v) (Scalar.anchor s);
v
-
| `Alias name ->
-
expand_alias ~depth name
+
| `Alias name -> expand_alias ~depth name
| `A seq ->
(* First resolve all members in order *)
-
let resolved_members = List.map (resolve ~depth) (Sequence.members seq) in
-
let resolved = `A (Sequence.make
-
?anchor:(Sequence.anchor seq)
-
?tag:(Sequence.tag seq)
-
~implicit:(Sequence.implicit seq)
-
~style:(Sequence.style seq)
-
resolved_members) in
+
let resolved_members =
+
List.map (resolve ~depth) (Sequence.members seq)
+
in
+
let resolved =
+
`A
+
(Sequence.make ?anchor:(Sequence.anchor seq) ?tag:(Sequence.tag seq)
+
~implicit:(Sequence.implicit seq) ~style:(Sequence.style seq)
+
resolved_members)
+
in
(* Register anchor with resolved node *)
Option.iter (fun name -> register_anchor name resolved) (Sequence.anchor seq);
resolved
| `O map ->
(* Process key-value pairs in document order *)
-
let resolved_pairs = List.map (fun (k, v) ->
-
let resolved_k = resolve ~depth k in
-
let resolved_v = resolve ~depth v in
-
(resolved_k, resolved_v)
-
) (Mapping.members map) in
-
let resolved = `O (Mapping.make
-
?anchor:(Mapping.anchor map)
-
?tag:(Mapping.tag map)
-
~implicit:(Mapping.implicit map)
-
~style:(Mapping.style map)
-
resolved_pairs) in
+
let resolved_pairs =
+
List.map
+
(fun (k, v) ->
+
let resolved_k = resolve ~depth k in
+
let resolved_v = resolve ~depth v in
+
(resolved_k, resolved_v))
+
(Mapping.members map)
+
in
+
let resolved =
+
`O
+
(Mapping.make ?anchor:(Mapping.anchor map) ?tag:(Mapping.tag map)
+
~implicit:(Mapping.implicit map) ~style:(Mapping.style map)
+
resolved_pairs)
+
in
(* Register anchor with resolved node *)
Option.iter (fun name -> register_anchor name resolved) (Mapping.anchor map);
resolved
···
(* If explicitly tagged, respect the tag *)
match tag with
-
| Some "tag:yaml.org,2002:null" | Some "!!null" ->
-
`Null
-
| Some "tag:yaml.org,2002:bool" | Some "!!bool" ->
-
(match String.lowercase_ascii value with
-
| "true" | "yes" | "on" -> `Bool true
-
| "false" | "no" | "off" -> `Bool false
-
| _ -> Error.raise (Invalid_scalar_conversion (value, "bool")))
-
| Some "tag:yaml.org,2002:int" | Some "!!int" ->
-
(try `Float (Float.of_string value)
-
with _ -> Error.raise (Invalid_scalar_conversion (value, "int")))
-
| Some "tag:yaml.org,2002:float" | Some "!!float" ->
-
(try `Float (Float.of_string value)
-
with _ -> Error.raise (Invalid_scalar_conversion (value, "float")))
-
| Some "tag:yaml.org,2002:str" | Some "!!str" ->
-
`String value
+
| Some "tag:yaml.org,2002:null" | Some "!!null" -> `Null
+
| Some "tag:yaml.org,2002:bool" | Some "!!bool" -> (
+
match String.lowercase_ascii value with
+
| "true" | "yes" | "on" -> `Bool true
+
| "false" | "no" | "off" -> `Bool false
+
| _ -> Error.raise (Invalid_scalar_conversion (value, "bool")))
+
| Some "tag:yaml.org,2002:int" | Some "!!int" -> (
+
try `Float (Float.of_string value)
+
with _ -> Error.raise (Invalid_scalar_conversion (value, "int")))
+
| Some "tag:yaml.org,2002:float" | Some "!!float" -> (
+
try `Float (Float.of_string value)
+
with _ -> Error.raise (Invalid_scalar_conversion (value, "float")))
+
| Some "tag:yaml.org,2002:str" | Some "!!str" -> `String value
| Some _ ->
(* Unknown tag - treat as string *)
`String value
| None ->
(* Implicit type resolution for plain scalars *)
-
if style <> `Plain then
-
`String value
-
else
-
infer_scalar_type value
+
if style <> `Plain then `String value else infer_scalar_type value
(** Infer type from plain scalar value *)
and infer_scalar_type value =
···
else if (first = '-' || first = '+') && len >= 2 then
let second = value.[1] in
(* After sign, must be digit or dot-digit (for +.5, -.5) *)
-
second >= '0' && second <= '9' ||
-
(second = '.' && len >= 3 && value.[2] >= '0' && value.[2] <= '9')
+
(second >= '0' && second <= '9')
+
|| (second = '.' && len >= 3 && value.[2] >= '0' && value.[2] <= '9')
else false
in
(* Try integer/float *)
···
| _ ->
(* Decimal with leading zero or octal in YAML 1.1 *)
Some (`Float (Float.of_string value))
-
else
-
Some (`Float (Float.of_string value))
+
else Some (`Float (Float.of_string value))
with _ -> None
else None
in
···
OCaml accepts "nan", "inf", "infinity" which are NOT valid YAML floats.
YAML requires the leading dot: .nan, .inf, -.inf
See: https://github.com/avsm/ocaml-yaml/issues/82 *)
-
if String.length value >= 2 && value.[0] = '.' &&
-
value.[1] >= '0' && value.[1] <= '9' then
-
try `Float (Float.of_string value)
-
with _ -> `String value
-
else
-
`String value
+
if
+
String.length value >= 2
+
&& value.[0] = '.'
+
&& value.[1] >= '0'
+
&& value.[1] <= '9'
+
then try `Float (Float.of_string value) with _ -> `String value
+
else `String value
(** Convert to JSON-compatible Value.
-
Converts a full YAML representation to a simplified JSON-compatible value type.
-
This process implements the representation graph to serialization tree conversion
-
described in {{:https://yaml.org/spec/1.2.2/#32-processes}Section 3.2 (Processes)}
-
of the YAML 1.2.2 specification.
+
Converts a full YAML representation to a simplified JSON-compatible value
+
type. This process implements the representation graph to serialization tree
+
conversion described in
+
{{:https://yaml.org/spec/1.2.2/#32-processes}Section 3.2 (Processes)} of the
+
YAML 1.2.2 specification.
-
See also {{:https://yaml.org/spec/1.2.2/#10212-json-schema}Section 10.2.1.2
-
(JSON Schema)} for the tag resolution used during conversion.
+
See also
+
{{:https://yaml.org/spec/1.2.2/#10212-json-schema}Section 10.2.1.2 (JSON
+
Schema)} for the tag resolution used during conversion.
-
@param resolve_aliases_first Whether to resolve aliases before conversion (default true)
+
@param resolve_aliases_first
+
Whether to resolve aliases before conversion (default true)
@param max_nodes Maximum nodes during alias expansion (default 10M)
@param max_depth Maximum alias nesting depth (default 100)
-
@raise Error.Yamlrw_error with {!type:Error.kind} [Unresolved_alias] if resolve_aliases_first is false and an alias is encountered
-
*)
-
let to_value
-
?(resolve_aliases_first = true)
+
@raise Error.Yamlrw_error
+
with {!type:Error.kind} [Unresolved_alias] if resolve_aliases_first is
+
false and an alias is encountered *)
+
let to_value ?(resolve_aliases_first = true)
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
(v : t) : Value.t =
-
let v = if resolve_aliases_first then resolve_aliases ~max_nodes ~max_depth v else v in
+
?(max_depth = default_max_alias_depth) (v : t) : Value.t =
+
let v =
+
if resolve_aliases_first then resolve_aliases ~max_nodes ~max_depth v else v
+
in
let rec convert (v : t) : Value.t =
match v with
| `Scalar s -> scalar_to_value s
| `Alias name -> Error.raise (Unresolved_alias name)
| `A seq -> `A (List.map convert (Sequence.members seq))
| `O map ->
-
`O (List.map (fun (k, v) ->
-
let key = match k with
-
| `Scalar s -> Scalar.value s
-
| _ -> Error.raise (Type_mismatch ("string key", "complex key"))
-
in
-
(key, convert v)
-
) (Mapping.members map))
+
`O
+
(List.map
+
(fun (k, v) ->
+
let key =
+
match k with
+
| `Scalar s -> Scalar.value s
+
| _ ->
+
Error.raise (Type_mismatch ("string key", "complex key"))
+
in
+
(key, convert v))
+
(Mapping.members map))
in
convert v
+183 -253
lib/yamlrw.ml
···
exception Yamlrw_error = Error.Yamlrw_error
-
(** {2 Core Types} *)
+
type value =
+
[ `Null (** YAML null, ~, or empty values *)
+
| `Bool of bool (** YAML booleans (true, false, yes, no, on, off) *)
+
| `Float of float (** All YAML numbers (integers stored as floats) *)
+
| `String of string (** YAML strings *)
+
| `A of value list (** YAML sequences/arrays *)
+
| `O of (string * value) list (** YAML mappings/objects with string keys *)
+
]
(** JSON-compatible YAML representation. Use this for simple data interchange.
This type is structurally equivalent to {!Value.t} and compatible with the
-
ezjsonm representation. For additional operations, see {!Value} and {!Util}. *)
-
type value = [
-
| `Null (** YAML null, ~, or empty values *)
-
| `Bool of bool (** YAML booleans (true, false, yes, no, on, off) *)
-
| `Float of float (** All YAML numbers (integers stored as floats) *)
-
| `String of string (** YAML strings *)
-
| `A of value list (** YAML sequences/arrays *)
-
| `O of (string * value) list (** YAML mappings/objects with string keys *)
-
]
+
ezjsonm representation. For additional operations, see {!Value} and {!Util}.
+
*)
+
type yaml =
+
[ `Scalar of Scalar.t (** YAML scalar value with style and metadata *)
+
| `Alias of string (** Alias reference to an anchored node *)
+
| `A of yaml Sequence.t (** YAML sequence with style and metadata *)
+
| `O of (yaml, yaml) Mapping.t (** YAML mapping with style and metadata *)
+
]
(** Full YAML representation preserving anchors, tags, and aliases.
This type is structurally equivalent to {!Yaml.t}. Use this when you need
···
type tags for custom types, scalar styles (plain, quoted, literal, folded),
and collection styles (block vs flow).
-
For additional operations, see {!Yaml}, {!Scalar}, {!Sequence}, and {!Mapping}. *)
-
type yaml = [
-
| `Scalar of Scalar.t (** YAML scalar value with style and metadata *)
-
| `Alias of string (** Alias reference to an anchored node *)
-
| `A of yaml Sequence.t (** YAML sequence with style and metadata *)
-
| `O of (yaml, yaml) Mapping.t (** YAML mapping with style and metadata *)
-
]
+
For additional operations, see {!Yaml}, {!Scalar}, {!Sequence}, and
+
{!Mapping}. *)
+
type document = {
+
version : (int * int) option;
+
(** Optional YAML version directive (e.g., (1, 2) for YAML 1.2) *)
+
tags : (string * string) list;
+
(** TAG directives mapping handles to prefixes *)
+
root : yaml option; (** Root content of the document *)
+
implicit_start : bool;
+
(** Whether the document start marker (---) is implicit *)
+
implicit_end : bool; (** Whether the document end marker (...) is implicit *)
+
}
(** A YAML document with directives and metadata.
This type is structurally equivalent to {!Document.t}. A YAML stream can
contain multiple documents, each separated by document markers.
For additional operations, see {!Document}. *)
-
type document = {
-
version : (int * int) option; (** Optional YAML version directive (e.g., (1, 2) for YAML 1.2) *)
-
tags : (string * string) list; (** TAG directives mapping handles to prefixes *)
-
root : yaml option; (** Root content of the document *)
-
implicit_start : bool; (** Whether the document start marker (---) is implicit *)
-
implicit_end : bool; (** Whether the document end marker (...) is implicit *)
-
}
-
(** {2 Character Encoding} *)
module Encoding = Encoding
-
(** {2 Parsing} *)
···
(** Default maximum alias nesting depth (100). *)
let default_max_alias_depth = Yaml.default_max_alias_depth
-
let of_string
-
?(resolve_aliases = true)
-
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
s : value =
-
(Loader.value_of_string ~resolve_aliases ~max_nodes ~max_depth s :> value)
(** Parse a YAML string into a JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100)
@raise Yamlrw_error on parse error or if multiple documents found *)
+
let of_string ?(resolve_aliases = true) ?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) s : value =
+
(Loader.value_of_string ~resolve_aliases ~max_nodes ~max_depth s :> value)
-
let yaml_of_string
-
?(resolve_aliases = false)
-
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
s : yaml =
-
(Loader.yaml_of_string ~resolve_aliases ~max_nodes ~max_depth s :> yaml)
(** Parse a YAML string preserving full YAML metadata (anchors, tags, etc).
By default, aliases are NOT resolved, preserving the document structure.
···
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100)
@raise Yamlrw_error on parse error or if multiple documents found *)
+
let yaml_of_string ?(resolve_aliases = false)
+
?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) s : yaml =
+
(Loader.yaml_of_string ~resolve_aliases ~max_nodes ~max_depth s :> yaml)
-
let documents_of_string s : document list =
-
let docs = Loader.documents_of_string s in
-
List.map (fun (d : Document.t) : document -> {
-
version = d.version;
-
tags = d.tags;
-
root = (d.root :> yaml option);
-
implicit_start = d.implicit_start;
-
implicit_end = d.implicit_end;
-
}) docs
(** Parse a multi-document YAML stream.
-
Use this when your YAML input contains multiple documents separated
-
by document markers (---).
+
Use this when your YAML input contains multiple documents separated by
+
document markers (---).
@raise Yamlrw_error on parse error *)
-
+
let documents_of_string s : document list =
+
let docs = Loader.documents_of_string s in
+
List.map
+
(fun (d : Document.t) : document ->
+
{
+
version = d.version;
+
tags = d.tags;
+
root = (d.root :> yaml option);
+
implicit_start = d.implicit_start;
+
implicit_end = d.implicit_end;
+
})
+
docs
(** {2 Formatting Styles} *)
module Scalar_style = Scalar_style
-
module Layout_style = Layout_style
-
(** {2 Serialization} *)
let make_config ~encoding ~scalar_style ~layout_style =
{ Emitter.default_config with encoding; scalar_style; layout_style }
-
let to_buffer
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?buffer
-
(value : value) =
-
let config = make_config ~encoding ~scalar_style ~layout_style in
-
Serialize.value_to_buffer ~config ?buffer (value :> Value.t)
(** Serialize a value to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
+
let to_buffer ?(encoding = `Utf8) ?(scalar_style = `Any) ?(layout_style = `Any)
+
?buffer (value : value) =
+
let config = make_config ~encoding ~scalar_style ~layout_style in
+
Serialize.value_to_buffer ~config ?buffer (value :> Value.t)
-
let to_string
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
(value : value) =
-
Buffer.contents (to_buffer ~encoding ~scalar_style ~layout_style value)
(** Serialize a value to a YAML string.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any) *)
+
let to_string ?(encoding = `Utf8) ?(scalar_style = `Any) ?(layout_style = `Any)
+
(value : value) =
+
Buffer.contents (to_buffer ~encoding ~scalar_style ~layout_style value)
-
let yaml_to_buffer
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?buffer
-
(yaml : yaml) =
-
let config = make_config ~encoding ~scalar_style ~layout_style in
-
Serialize.yaml_to_buffer ~config ?buffer (yaml :> Yaml.t)
(** Serialize a full YAML value to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
+
let yaml_to_buffer ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?buffer (yaml : yaml) =
+
let config = make_config ~encoding ~scalar_style ~layout_style in
+
Serialize.yaml_to_buffer ~config ?buffer (yaml :> Yaml.t)
-
let yaml_to_string
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
(yaml : yaml) =
-
Buffer.contents (yaml_to_buffer ~encoding ~scalar_style ~layout_style yaml)
(** Serialize a full YAML value to a string.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any) *)
+
let yaml_to_string ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) (yaml : yaml) =
+
Buffer.contents (yaml_to_buffer ~encoding ~scalar_style ~layout_style yaml)
-
let documents_to_buffer
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
-
?buffer
-
(documents : document list) =
-
let config = make_config ~encoding ~scalar_style ~layout_style in
-
let docs' = List.map (fun (d : document) : Document.t -> {
-
Document.version = d.version;
-
Document.tags = d.tags;
-
Document.root = (d.root :> Yaml.t option);
-
Document.implicit_start = d.implicit_start;
-
Document.implicit_end = d.implicit_end;
-
}) documents in
-
Serialize.documents_to_buffer ~config ~resolve_aliases ?buffer docs'
(** Serialize multiple documents to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
@param resolve_aliases Whether to expand aliases (default: true)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
-
-
let documents_to_string
-
?(encoding = `Utf8)
-
?(scalar_style = `Any)
-
?(layout_style = `Any)
-
?(resolve_aliases = true)
+
let documents_to_buffer ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) ?buffer
(documents : document list) =
-
Buffer.contents (documents_to_buffer ~encoding ~scalar_style ~layout_style ~resolve_aliases documents)
+
let config = make_config ~encoding ~scalar_style ~layout_style in
+
let docs' =
+
List.map
+
(fun (d : document) : Document.t ->
+
{
+
Document.version = d.version;
+
Document.tags = d.tags;
+
Document.root = (d.root :> Yaml.t option);
+
Document.implicit_start = d.implicit_start;
+
Document.implicit_end = d.implicit_end;
+
})
+
documents
+
in
+
Serialize.documents_to_buffer ~config ~resolve_aliases ?buffer docs'
+
(** Serialize multiple documents to a YAML stream.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
@param resolve_aliases Whether to expand aliases (default: true) *)
+
let documents_to_string ?(encoding = `Utf8) ?(scalar_style = `Any)
+
?(layout_style = `Any) ?(resolve_aliases = true) (documents : document list)
+
=
+
Buffer.contents
+
(documents_to_buffer ~encoding ~scalar_style ~layout_style ~resolve_aliases
+
documents)
(** {2 Buffer Parsing} *)
-
let of_buffer
-
?(resolve_aliases = true)
-
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
buffer : value =
-
of_string ~resolve_aliases ~max_nodes ~max_depth (Buffer.contents buffer)
(** Parse YAML from a buffer into a JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100)
@raise Yamlrw_error on parse error or if multiple documents found *)
+
let of_buffer ?(resolve_aliases = true) ?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) buffer : value =
+
of_string ~resolve_aliases ~max_nodes ~max_depth (Buffer.contents buffer)
-
let yaml_of_buffer
-
?(resolve_aliases = false)
-
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
buffer : yaml =
-
yaml_of_string ~resolve_aliases ~max_nodes ~max_depth (Buffer.contents buffer)
(** Parse YAML from a buffer preserving full YAML metadata.
@param resolve_aliases Whether to expand aliases (default: false)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100)
@raise Yamlrw_error on parse error or if multiple documents found *)
+
let yaml_of_buffer ?(resolve_aliases = false)
+
?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) buffer : yaml =
+
yaml_of_string ~resolve_aliases ~max_nodes ~max_depth (Buffer.contents buffer)
-
let documents_of_buffer buffer : document list =
-
documents_of_string (Buffer.contents buffer)
(** Parse a multi-document YAML stream from a buffer.
@raise Yamlrw_error on parse error *)
-
+
let documents_of_buffer buffer : document list =
+
documents_of_string (Buffer.contents buffer)
(** {2 Conversion} *)
-
let to_json
-
?(resolve_aliases = true)
-
?(max_nodes = default_max_alias_nodes)
-
?(max_depth = default_max_alias_depth)
-
(yaml : yaml) : value =
-
(Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth (yaml :> Yaml.t) :> value)
(** Convert full YAML to JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
@param max_nodes Maximum nodes during alias expansion (default: 10M)
@param max_depth Maximum alias nesting depth (default: 100)
@raise Yamlrw_error if alias limits exceeded or complex keys found *)
+
let to_json ?(resolve_aliases = true) ?(max_nodes = default_max_alias_nodes)
+
?(max_depth = default_max_alias_depth) (yaml : yaml) : value =
+
(Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth
+
(yaml :> Yaml.t)
+
:> value)
-
let of_json (value : value) : yaml =
-
(Yaml.of_value (value :> Value.t) :> yaml)
(** Convert JSON-compatible value to full YAML representation. *)
-
+
let of_json (value : value) : yaml = (Yaml.of_value (value :> Value.t) :> yaml)
(** {2 Pretty Printing & Equality} *)
+
(** Pretty-print a value. *)
let pp = Value.pp
-
(** Pretty-print a value. *)
-
let equal = Value.equal
(** Test equality of two values. *)
-
+
let equal = Value.equal
(** {2 Util - Value Combinators} *)
module Util = struct
(** Combinators for working with {!type:value} values.
-
This module provides constructors, accessors, and transformations
-
for JSON-compatible YAML values. *)
+
This module provides constructors, accessors, and transformations for
+
JSON-compatible YAML values. *)
type t = Value.t
···
let get_string v = match v with `String s -> s | _ -> type_error "string" v
let get_list v = match v with `A l -> l | _ -> type_error "list" v
let get_obj v = match v with `O o -> o | _ -> type_error "object" v
-
-
let get_int v =
-
match as_int v with
-
| Some i -> i
-
| None -> type_error "int" v
+
let get_int v = match as_int v with Some i -> i | None -> type_error "int" v
(** {3 Object Operations} *)
···
| `O pairs -> List.exists (fun (k, _) -> k = key) pairs
| _ -> false
-
let find key = function
-
| `O pairs -> List.assoc_opt key pairs
-
| _ -> None
+
let find key = function `O pairs -> List.assoc_opt key pairs | _ -> None
+
let get key v = match find key v with Some v -> v | None -> raise Not_found
-
let get key v =
-
match find key v with
-
| Some v -> v
-
| None -> raise Not_found
+
let keys v =
+
match v with `O pairs -> List.map fst pairs | _ -> type_error "object" v
-
let keys v = match v with
-
| `O pairs -> List.map fst pairs
-
| _ -> type_error "object" v
-
-
let values v = match v with
-
| `O pairs -> List.map snd pairs
-
| _ -> type_error "object" v
+
let values v =
+
match v with `O pairs -> List.map snd pairs | _ -> type_error "object" v
let update key value = function
| `O pairs ->
let rec go = function
-
| [] -> [(key, value)]
+
| [] -> [ (key, value) ]
| (k, _) :: rest when k = key -> (key, value) :: rest
| kv :: rest -> kv :: go rest
in
···
| v -> type_error "object" v
let combine v1 v2 =
-
match v1, v2 with
+
match (v1, v2) with
| `O o1, `O o2 -> `O (o1 @ o2)
| `O _, _ -> type_error "object" v2
| _, _ -> type_error "object" v1
(** {3 List Operations} *)
-
let map f = function
-
| `A l -> `A (List.map f l)
-
| v -> type_error "list" v
-
-
let mapi f = function
-
| `A l -> `A (List.mapi f l)
-
| v -> type_error "list" v
+
let map f = function `A l -> `A (List.map f l) | v -> type_error "list" v
+
let mapi f = function `A l -> `A (List.mapi f l) | v -> type_error "list" v
let filter pred = function
| `A l -> `A (List.filter pred l)
···
| `A l -> List.fold_left f init l
| v -> type_error "list" v
-
let nth n = function
-
| `A l -> List.nth_opt l n
-
| _ -> None
-
-
let length = function
-
| `A l -> List.length l
-
| `O o -> List.length o
-
| _ -> 0
+
let nth n = function `A l -> List.nth_opt l n | _ -> None
+
let length = function `A l -> List.length l | `O o -> List.length o | _ -> 0
let flatten = function
-
| `A l ->
-
`A (List.concat_map (function `A inner -> inner | v -> [v]) l)
+
| `A l -> `A (List.concat_map (function `A inner -> inner | v -> [ v ]) l)
| v -> type_error "list" v
(** {3 Path Operations} *)
···
let rec get_path path v =
match path with
| [] -> Some v
-
| key :: rest ->
-
match find key v with
-
| Some child -> get_path rest child
-
| None -> None
+
| key :: rest -> (
+
match find key v with Some child -> get_path rest child | None -> None)
let get_path_exn path v =
-
match get_path path v with
-
| Some v -> v
-
| None -> raise Not_found
+
match get_path path v with Some v -> v | None -> raise Not_found
(** {3 Iteration} *)
···
| `O pairs -> List.iter (fun (k, v) -> f k v) pairs
| v -> type_error "object" v
-
let iter_list f = function
-
| `A l -> List.iter f l
-
| v -> type_error "list" v
+
let iter_list f = function `A l -> List.iter f l | v -> type_error "list" v
let fold_obj f init = function
| `O pairs -> List.fold_left (fun acc (k, v) -> f acc k v) init pairs
···
(** {3 Conversion Helpers} *)
let to_bool ?default v =
-
match as_bool v, default with
+
match (as_bool v, default) with
| Some b, _ -> b
| None, Some d -> d
| None, None -> type_error "bool" v
let to_int ?default v =
-
match as_int v, default with
+
match (as_int v, default) with
| Some i, _ -> i
| None, Some d -> d
| None, None -> type_error "int" v
let to_float ?default v =
-
match as_float v, default with
+
match (as_float v, default) with
| Some f, _ -> f
| None, Some d -> d
| None, None -> type_error "float" v
let to_string ?default v =
-
match as_string v, default with
+
match (as_string v, default) with
| Some s, _ -> s
| None, Some d -> d
| None, None -> type_error "string" v
let to_list ?default v =
-
match as_list v, default with
+
match (as_list v, default) with
| Some l, _ -> l
| None, Some d -> d
| None, None -> type_error "list" v
end
-
(** {2 Stream - Low-Level Event API} *)
···
type position = Position.t
(** A position in the source (line, column, byte offset). *)
-
(** Result of parsing an event. *)
type event_result = {
event : event;
start_pos : position;
end_pos : position;
}
+
(** Result of parsing an event. *)
(** {3 Parsing} *)
type parser = Parser.t
(** A streaming YAML parser. *)
-
let parser s = Parser.of_string s
(** Create a parser from a string. *)
+
let parser s = Parser.of_string s
+
(** Get the next event from the parser. Returns [None] when parsing is
+
complete. *)
let next p =
match Parser.next p with
| Some { event; span } ->
-
Some {
-
event;
-
start_pos = span.start;
-
end_pos = span.stop;
-
}
+
Some { event; start_pos = span.start; end_pos = span.stop }
| None -> None
-
(** Get the next event from the parser.
-
Returns [None] when parsing is complete. *)
+
(** Iterate over all events from the parser. *)
let iter f p =
let rec go () =
match next p with
···
| None -> ()
in
go ()
-
(** Iterate over all events from the parser. *)
+
(** Fold over all events from the parser. *)
let fold f init p =
let rec go acc =
match Parser.next p with
···
| None -> acc
in
go init
-
(** Fold over all events from the parser. *)
(** {3 Emitting} *)
type emitter = Emitter.t
(** A streaming YAML emitter. *)
-
let emitter ?len:_ () = Emitter.create ()
(** Create a new emitter. *)
+
let emitter ?len:_ () = Emitter.create ()
-
let contents e = Emitter.contents e
(** Get the emitted YAML string. *)
+
let contents e = Emitter.contents e
-
let emit e ev = Emitter.emit e ev
(** Emit an event. *)
+
let emit e ev = Emitter.emit e ev
(** {3 Event Emission Helpers} *)
let stream_start e enc =
Emitter.emit e (Event.Stream_start { encoding = enc })
-
let stream_end e =
-
Emitter.emit e Event.Stream_end
+
let stream_end e = Emitter.emit e Event.Stream_end
let document_start e ?version ?(implicit = true) () =
-
let version = match version with
+
let version =
+
match version with
| Some `V1_1 -> Some (1, 1)
| Some `V1_2 -> Some (1, 2)
| None -> None
···
Emitter.emit e (Event.Document_end { implicit })
let scalar e ?anchor ?tag ?(style = `Any) value =
-
Emitter.emit e (Event.Scalar {
-
anchor;
-
tag;
-
value;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style;
-
})
+
Emitter.emit e
+
(Event.Scalar
+
{
+
anchor;
+
tag;
+
value;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style;
+
})
-
let alias e name =
-
Emitter.emit e (Event.Alias { anchor = name })
+
let alias e name = Emitter.emit e (Event.Alias { anchor = name })
let sequence_start e ?anchor ?tag ?(style = `Any) () =
-
Emitter.emit e (Event.Sequence_start {
-
anchor;
-
tag;
-
implicit = true;
-
style;
-
})
+
Emitter.emit e
+
(Event.Sequence_start { anchor; tag; implicit = true; style })
-
let sequence_end e =
-
Emitter.emit e Event.Sequence_end
+
let sequence_end e = Emitter.emit e Event.Sequence_end
let mapping_start e ?anchor ?tag ?(style = `Any) () =
-
Emitter.emit e (Event.Mapping_start {
-
anchor;
-
tag;
-
implicit = true;
-
style;
-
})
+
Emitter.emit e (Event.Mapping_start { anchor; tag; implicit = true; style })
-
let mapping_end e =
-
Emitter.emit e Event.Mapping_end
+
let mapping_end e = Emitter.emit e Event.Mapping_end
end
-
(** {2 Internal Modules} *)
-
(** These modules are exposed for advanced use cases requiring
-
fine-grained control over parsing, emission, or data structures.
+
(** These modules are exposed for advanced use cases requiring fine-grained
+
control over parsing, emission, or data structures.
For typical usage, prefer the top-level functions and {!Util}. *)
-
(** Source position tracking. *)
module Position = Position
+
(** Source position tracking. *)
+
module Span = Span
(** Source span (range of positions). *)
-
module Span = Span
+
module Chomping = Chomping
(** Block scalar chomping modes. *)
-
module Chomping = Chomping
-
(** YAML type tags. *)
module Tag = Tag
+
(** YAML type tags. *)
-
(** JSON-compatible value type and operations. *)
module Value = Value
+
(** JSON-compatible value type and operations. *)
-
(** YAML scalar with metadata. *)
module Scalar = Scalar
+
(** YAML scalar with metadata. *)
+
module Sequence = Sequence
(** YAML sequence with metadata. *)
-
module Sequence = Sequence
+
module Mapping = Mapping
(** YAML mapping with metadata. *)
-
module Mapping = Mapping
-
(** Full YAML value type. *)
module Yaml = Yaml
+
(** Full YAML value type. *)
-
(** YAML document with directives. *)
module Document = Document
+
(** YAML document with directives. *)
-
(** Lexical tokens. *)
module Token = Token
+
(** Lexical tokens. *)
+
module Scanner = Scanner
(** Lexical scanner. *)
-
module Scanner = Scanner
-
(** Parser events. *)
module Event = Event
+
(** Parser events. *)
+
module Parser = Parser
(** Event-based parser. *)
-
module Parser = Parser
+
module Loader = Loader
(** Document loader. *)
-
module Loader = Loader
-
(** Event-based emitter. *)
module Emitter = Emitter
+
(** Event-based emitter. *)
-
(** Input stream utilities. *)
module Input = Input
+
(** Input stream utilities. *)
+
module Serialize = Serialize
(** Buffer serialization utilities. *)
-
module Serialize = Serialize
+96 -90
lib/yamlrw.mli
···
let age = Yamlrw.Util.(get_int (get "age" value)) in
]} *)
-
(** {2 Error Handling} *)
module Error = Error
···
exception Yamlrw_error of Error.t
(** Raised on parse or emit errors. *)
-
(** {2 Core Types} *)
-
type value = [
-
| `Null (** YAML null, ~, or empty values *)
-
| `Bool of bool (** YAML booleans (true, false, yes, no, on, off) *)
-
| `Float of float (** All YAML numbers (integers stored as floats) *)
-
| `String of string (** YAML strings *)
-
| `A of value list (** YAML sequences/arrays *)
-
| `O of (string * value) list (** YAML mappings/objects with string keys *)
-
]
+
type value =
+
[ `Null (** YAML null, ~, or empty values *)
+
| `Bool of bool (** YAML booleans (true, false, yes, no, on, off) *)
+
| `Float of float (** All YAML numbers (integers stored as floats) *)
+
| `String of string (** YAML strings *)
+
| `A of value list (** YAML sequences/arrays *)
+
| `O of (string * value) list (** YAML mappings/objects with string keys *)
+
]
(** JSON-compatible YAML representation. Use this for simple data interchange.
This type is structurally equivalent to {!Value.t} and compatible with the
-
ezjsonm representation. For additional operations, see {!Value} and {!Util}. *)
+
ezjsonm representation. For additional operations, see {!Value} and {!Util}.
+
*)
-
type yaml = [
-
| `Scalar of Scalar.t (** YAML scalar value with style and metadata *)
-
| `Alias of string (** Alias reference to an anchored node *)
-
| `A of yaml Sequence.t (** YAML sequence with style and metadata *)
-
| `O of (yaml, yaml) Mapping.t (** YAML mapping with style and metadata *)
-
]
+
type yaml =
+
[ `Scalar of Scalar.t (** YAML scalar value with style and metadata *)
+
| `Alias of string (** Alias reference to an anchored node *)
+
| `A of yaml Sequence.t (** YAML sequence with style and metadata *)
+
| `O of (yaml, yaml) Mapping.t (** YAML mapping with style and metadata *)
+
]
(** Full YAML representation preserving anchors, tags, and aliases.
This type is structurally equivalent to {!Yaml.t}. Use this when you need
···
type tags for custom types, scalar styles (plain, quoted, literal, folded),
and collection styles (block vs flow).
-
For additional operations, see {!Yaml}, {!Scalar}, {!Sequence}, and {!Mapping}. *)
+
For additional operations, see {!Yaml}, {!Scalar}, {!Sequence}, and
+
{!Mapping}. *)
type document = {
-
version : (int * int) option; (** Optional YAML version directive (e.g., (1, 2) for YAML 1.2) *)
-
tags : (string * string) list; (** TAG directives mapping handles to prefixes *)
-
root : yaml option; (** Root content of the document *)
-
implicit_start : bool; (** Whether the document start marker (---) is implicit *)
-
implicit_end : bool; (** Whether the document end marker (...) is implicit *)
+
version : (int * int) option;
+
(** Optional YAML version directive (e.g., (1, 2) for YAML 1.2) *)
+
tags : (string * string) list;
+
(** TAG directives mapping handles to prefixes *)
+
root : yaml option; (** Root content of the document *)
+
implicit_start : bool;
+
(** Whether the document start marker (---) is implicit *)
+
implicit_end : bool; (** Whether the document end marker (...) is implicit *)
}
(** A YAML document with directives and metadata.
···
contain multiple documents, each separated by document markers.
For additional operations, see {!Document}. *)
-
(** {2 Character Encoding} *)
module Encoding = Encoding
-
(** {2 Parsing} *)
type version = [ `V1_1 | `V1_2 ]
···
(** Default maximum alias nesting depth (100). *)
val of_string :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
string -> value
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> string -> value
(** Parse a YAML string into a JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
···
@raise Yamlrw_error on parse error or if multiple documents found *)
val yaml_of_string :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
string -> yaml
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> string -> yaml
(** Parse a YAML string preserving full YAML metadata (anchors, tags, etc).
By default, aliases are NOT resolved, preserving the document structure.
···
val documents_of_string : string -> document list
(** Parse a multi-document YAML stream.
-
Use this when your YAML input contains multiple documents separated
-
by document markers (---).
+
Use this when your YAML input contains multiple documents separated by
+
document markers (---).
@raise Yamlrw_error on parse error *)
-
(** {2 Formatting Styles} *)
module Scalar_style = Scalar_style
-
module Layout_style = Layout_style
-
(** {2 Serialization} *)
···
?scalar_style:Scalar_style.t ->
?layout_style:Layout_style.t ->
?buffer:Buffer.t ->
-
value -> Buffer.t
+
value ->
+
Buffer.t
(** Serialize a value to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
val to_string :
?encoding:Encoding.t ->
?scalar_style:Scalar_style.t ->
?layout_style:Layout_style.t ->
-
value -> string
+
value ->
+
string
(** Serialize a value to a YAML string.
@param encoding Output encoding (default: UTF-8)
···
?scalar_style:Scalar_style.t ->
?layout_style:Layout_style.t ->
?buffer:Buffer.t ->
-
yaml -> Buffer.t
+
yaml ->
+
Buffer.t
(** Serialize a full YAML value to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
val yaml_to_string :
?encoding:Encoding.t ->
?scalar_style:Scalar_style.t ->
?layout_style:Layout_style.t ->
-
yaml -> string
+
yaml ->
+
string
(** Serialize a full YAML value to a string.
@param encoding Output encoding (default: UTF-8)
···
?layout_style:Layout_style.t ->
?resolve_aliases:bool ->
?buffer:Buffer.t ->
-
document list -> Buffer.t
+
document list ->
+
Buffer.t
(** Serialize multiple documents to a buffer.
@param encoding Output encoding (default: UTF-8)
@param scalar_style Preferred scalar style (default: Any)
@param layout_style Preferred layout style (default: Any)
@param resolve_aliases Whether to expand aliases (default: true)
-
@param buffer Optional buffer to append to (allocates new one if not provided)
+
@param buffer
+
Optional buffer to append to (allocates new one if not provided)
@return The buffer containing the serialized YAML *)
val documents_to_string :
···
?scalar_style:Scalar_style.t ->
?layout_style:Layout_style.t ->
?resolve_aliases:bool ->
-
document list -> string
+
document list ->
+
string
(** Serialize multiple documents to a YAML stream.
@param encoding Output encoding (default: UTF-8)
···
(** {2 Buffer Parsing} *)
val of_buffer :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
Buffer.t -> value
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> Buffer.t -> value
(** Parse YAML from a buffer into a JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
···
@raise Yamlrw_error on parse error or if multiple documents found *)
val yaml_of_buffer :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
Buffer.t -> yaml
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> Buffer.t -> yaml
(** Parse YAML from a buffer preserving full YAML metadata.
@param resolve_aliases Whether to expand aliases (default: false)
···
@raise Yamlrw_error on parse error *)
-
(** {2 Conversion} *)
val to_json :
-
?resolve_aliases:bool ->
-
?max_nodes:int ->
-
?max_depth:int ->
-
yaml -> value
+
?resolve_aliases:bool -> ?max_nodes:int -> ?max_depth:int -> yaml -> value
(** Convert full YAML to JSON-compatible value.
@param resolve_aliases Whether to expand aliases (default: true)
···
val of_json : value -> yaml
(** Convert JSON-compatible value to full YAML representation. *)
-
(** {2 Pretty Printing & Equality} *)
val pp : Format.formatter -> value -> unit
···
val equal : value -> value -> bool
(** Test equality of two values. *)
-
(** {2 Util - Value Combinators}
Combinators for working with {!type:value} values.
-
This module provides constructors, accessors, and transformations
-
for JSON-compatible YAML values. *)
+
This module provides constructors, accessors, and transformations for
+
JSON-compatible YAML values. *)
module Util : sig
type t = Value.t
···
(** {3 Object Operations} *)
val mem : string -> t -> bool
-
(** [mem key obj] checks if [key] exists in object [obj].
-
Returns [false] if [obj] is not an object. *)
+
(** [mem key obj] checks if [key] exists in object [obj]. Returns [false] if
+
[obj] is not an object. *)
val find : string -> t -> t option
-
(** [find key obj] looks up [key] in object [obj].
-
Returns [None] if key not found or if [obj] is not an object. *)
+
(** [find key obj] looks up [key] in object [obj]. Returns [None] if key not
+
found or if [obj] is not an object. *)
val get : string -> t -> t
-
(** [get key obj] looks up [key] in object [obj].
-
Raises [Not_found] if key not found. *)
+
(** [get key obj] looks up [key] in object [obj]. Raises [Not_found] if key
+
not found. *)
val keys : t -> string list
(** Get all keys from an object.
···
@raise Type_error if not an object *)
val update : string -> t -> t -> t
-
(** [update key value obj] sets [key] to [value] in [obj].
-
Adds the key if it doesn't exist.
+
(** [update key value obj] sets [key] to [value] in [obj]. Adds the key if it
+
doesn't exist.
@raise Type_error if [obj] is not an object *)
val remove : string -> t -> t
···
@raise Type_error if [obj] is not an object *)
val combine : t -> t -> t
-
(** [combine obj1 obj2] merges two objects, with [obj2] values taking precedence.
+
(** [combine obj1 obj2] merges two objects, with [obj2] values taking
+
precedence.
@raise Type_error if either argument is not an object *)
(** {3 List Operations} *)
···
@raise Type_error if [lst] is not a list *)
val nth : int -> t -> t option
-
(** [nth n lst] gets element at index [n].
-
Returns [None] if [lst] is not a list or index out of bounds. *)
+
(** [nth n lst] gets element at index [n]. Returns [None] if [lst] is not a
+
list or index out of bounds. *)
val length : t -> int
(** Get the length of a list or object. Returns 0 for other types. *)
val flatten : t -> t
-
(** Flatten a list of lists into a single list.
-
Non-list elements are kept as-is.
+
(** Flatten a list of lists into a single list. Non-list elements are kept
+
as-is.
@raise Type_error if not a list *)
(** {3 Path Operations} *)
val get_path : string list -> t -> t option
-
(** [get_path ["a"; "b"; "c"] obj] looks up nested path [obj.a.b.c].
-
Returns [None] if any key is not found. *)
+
(** [get_path ["a"; "b"; "c"] obj] looks up nested path [obj.a.b.c]. Returns
+
[None] if any key is not found. *)
val get_path_exn : string list -> t -> t
(** Like {!get_path} but raises [Not_found] if path not found. *)
···
@raise Type_error if type doesn't match and no default provided *)
end
-
(** {2 Stream - Low-Level Event API}
Low-level streaming API for event-based YAML processing.
···
- Fine-grained control over YAML emission *)
module Stream : sig
-
(** {3 Event Types} *)
type event = Event.t
···
(** Create a parser from a string. *)
val next : parser -> event_result option
-
(** Get the next event from the parser.
-
Returns [None] when parsing is complete. *)
+
(** Get the next event from the parser. Returns [None] when parsing is
+
complete. *)
val iter : (event -> position -> position -> unit) -> parser -> unit
(** [iter f parser] calls [f event start_pos end_pos] for each event. *)
···
val stream_end : emitter -> unit
(** Emit a stream end event. *)
-
val document_start : emitter -> ?version:version -> ?implicit:bool -> unit -> unit
+
val document_start :
+
emitter -> ?version:version -> ?implicit:bool -> unit -> unit
(** Emit a document start event.
@param version YAML version directive
@param implicit Whether start marker is implicit (default: true) *)
···
(** Emit a document end event.
@param implicit Whether end marker is implicit (default: true) *)
-
val scalar : emitter -> ?anchor:string -> ?tag:string -> ?style:Scalar_style.t -> string -> unit
+
val scalar :
+
emitter ->
+
?anchor:string ->
+
?tag:string ->
+
?style:Scalar_style.t ->
+
string ->
+
unit
(** Emit a scalar value.
@param anchor Optional anchor name
@param tag Optional type tag
···
val alias : emitter -> string -> unit
(** Emit an alias reference. *)
-
val sequence_start : emitter -> ?anchor:string -> ?tag:string -> ?style:Layout_style.t -> unit -> unit
+
val sequence_start :
+
emitter ->
+
?anchor:string ->
+
?tag:string ->
+
?style:Layout_style.t ->
+
unit ->
+
unit
(** Emit a sequence start event.
@param anchor Optional anchor name
@param tag Optional type tag
···
val sequence_end : emitter -> unit
(** Emit a sequence end event. *)
-
val mapping_start : emitter -> ?anchor:string -> ?tag:string -> ?style:Layout_style.t -> unit -> unit
+
val mapping_start :
+
emitter ->
+
?anchor:string ->
+
?tag:string ->
+
?style:Layout_style.t ->
+
unit ->
+
unit
(** Emit a mapping start event.
@param anchor Optional anchor name
@param tag Optional type tag
···
(** Emit a mapping end event. *)
end
-
(** {2 Internal Modules}
-
These modules are exposed for advanced use cases requiring
-
fine-grained control over parsing, emission, or data structures.
+
These modules are exposed for advanced use cases requiring fine-grained
+
control over parsing, emission, or data structures.
For typical usage, prefer the top-level functions and {!Util}. *)
+17 -8
tests/dune
···
; Alias to run the full YAML test suite and generate HTML report
; Requires yaml-test-suite to be cloned to tests/yaml-test-suite
+
(rule
(alias yaml-test-suite)
-
(deps (source_tree yaml-test-suite))
+
(deps
+
(source_tree yaml-test-suite))
(targets yaml-test-results.html)
(action
-
(run %{exe:run_all_tests.exe}
-
--test-suite-path %{workspace_root}/tests/yaml-test-suite
-
--html yaml-test-results.html)))
+
(run
+
%{exe:run_all_tests.exe}
+
--test-suite-path
+
%{workspace_root}/tests/yaml-test-suite
+
--html
+
yaml-test-results.html)))
(rule
(alias yaml-test-suite-eio)
-
(deps (source_tree yaml-test-suite))
+
(deps
+
(source_tree yaml-test-suite))
(targets yaml-test-results-eio.html)
(action
-
(run %{exe:run_all_tests_eio.exe}
-
--test-suite-path %{workspace_root}/tests/yaml-test-suite
-
--html yaml-test-results-eio.html)))
+
(run
+
%{exe:run_all_tests_eio.exe}
+
--test-suite-path
+
%{workspace_root}/tests/yaml-test-suite
+
--html
+
yaml-test-results-eio.html)))
+209 -144
tests/run_all_tests.ml
···
(* HTML escape function *)
let html_escape s =
let buf = Buffer.create (String.length s) in
-
String.iter (function
-
| '<' -> Buffer.add_string buf "&lt;"
-
| '>' -> Buffer.add_string buf "&gt;"
-
| '&' -> Buffer.add_string buf "&amp;"
-
| '"' -> Buffer.add_string buf "&quot;"
-
| c -> Buffer.add_char buf c
-
) s;
+
String.iter
+
(function
+
| '<' -> Buffer.add_string buf "&lt;"
+
| '>' -> Buffer.add_string buf "&gt;"
+
| '&' -> Buffer.add_string buf "&amp;"
+
| '"' -> Buffer.add_string buf "&quot;"
+
| c -> Buffer.add_char buf c)
+
s;
Buffer.contents buf
let normalize_tree s =
···
name : string;
yaml : string;
is_error_test : bool;
-
status : [`Pass | `Fail of string | `Skip];
+
status : [ `Pass | `Fail of string | `Skip ];
output : string;
-
json_status : [`Pass | `Fail of string | `Skip];
+
json_status : [ `Pass | `Fail of string | `Skip ];
json_expected : string;
json_actual : string;
}
···
This handles formatting differences and object key ordering. *)
JC.compare_json_strings expected actual
-
let run_json_test (test : TL.test_case) : [`Pass | `Fail of string | `Skip] * string =
+
let run_json_test (test : TL.test_case) :
+
[ `Pass | `Fail of string | `Skip ] * string =
match test.json with
| None -> (`Skip, "")
-
| Some expected_json ->
+
| Some expected_json -> (
if test.fail then
(* Error tests shouldn't have JSON comparison *)
(`Skip, "")
···
try
(* Handle multi-document YAML by using documents_of_string *)
let docs = Loader.documents_of_string test.yaml in
-
let values = List.filter_map (fun doc ->
-
match Document.root doc with
-
| None -> None
-
| Some yaml -> Some (Yaml.to_value ~resolve_aliases_first:true yaml)
-
) docs in
-
let actual_json = match values with
-
| [] -> "" (* Empty document produces empty JSON *)
-
| [v] -> JF.to_json v
+
let values =
+
List.filter_map
+
(fun doc ->
+
match Document.root doc with
+
| None -> None
+
| Some yaml ->
+
Some (Yaml.to_value ~resolve_aliases_first:true yaml))
+
docs
+
in
+
let actual_json =
+
match values with
+
| [] -> "" (* Empty document produces empty JSON *)
+
| [ v ] -> JF.to_json v
| vs -> JF.documents_to_json vs
in
-
if compare_json expected_json actual_json then
-
(`Pass, actual_json)
-
else
-
(`Fail "JSON mismatch", actual_json)
+
if compare_json expected_json actual_json then (`Pass, actual_json)
+
else (`Fail "JSON mismatch", actual_json)
with
| Yamlrw_error e ->
(`Fail (Format.asprintf "Parse error: %a" Error.pp e), "")
| exn ->
(`Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn)), "")
+
)
let run_test (test : TL.test_case) : test_result =
let json_status, json_actual = run_json_test test in
-
let base = {
-
id = test.id;
-
name = test.name;
-
yaml = test.yaml;
-
is_error_test = test.fail;
-
status = `Skip;
-
output = "";
-
json_status;
-
json_expected = Option.value ~default:"" test.json;
-
json_actual;
-
} in
+
let base =
+
{
+
id = test.id;
+
name = test.name;
+
yaml = test.yaml;
+
is_error_test = test.fail;
+
status = `Skip;
+
output = "";
+
json_status;
+
json_expected = Option.value ~default:"" test.json;
+
json_actual;
+
}
+
in
if test.fail then begin
try
let parser = Parser.of_string test.yaml in
let events = Parser.to_list parser in
let tree = TF.of_spanned_events events in
-
{ base with
-
status = `Fail "Expected parsing to fail";
-
output = tree;
-
}
+
{ base with status = `Fail "Expected parsing to fail"; output = tree }
with
| Yamlrw_error e ->
-
{ base with
-
status = `Pass;
-
output = Format.asprintf "%a" Error.pp e;
-
}
-
| exn ->
-
{ base with
-
status = `Pass;
-
output = Printexc.to_string exn;
-
}
+
{ base with status = `Pass; output = Format.asprintf "%a" Error.pp e }
+
| exn -> { base with status = `Pass; output = Printexc.to_string exn }
end
else begin
match test.tree with
-
| None ->
+
| None -> (
(* No expected tree - check if json indicates expected success *)
-
(match test.json with
-
| Some _ ->
-
(* Has json output, so should parse successfully *)
-
(try
-
let parser = Parser.of_string test.yaml in
-
let events = Parser.to_list parser in
-
let tree = TF.of_spanned_events events in
-
{ base with status = `Pass; output = tree }
-
with exn ->
-
{ base with
-
status = `Fail (Printf.sprintf "Should parse but got: %s" (Printexc.to_string exn));
-
output = Printexc.to_string exn;
-
})
-
| None ->
-
(* No tree, no json, no fail - ambiguous edge case, skip *)
-
{ base with status = `Skip; output = "(no expected tree or json)" })
-
| Some expected ->
+
match test.json with
+
| Some _ -> (
+
(* Has json output, so should parse successfully *)
+
try
+
let parser = Parser.of_string test.yaml in
+
let events = Parser.to_list parser in
+
let tree = TF.of_spanned_events events in
+
{ base with status = `Pass; output = tree }
+
with exn ->
+
{
+
base with
+
status =
+
`Fail
+
(Printf.sprintf "Should parse but got: %s"
+
(Printexc.to_string exn));
+
output = Printexc.to_string exn;
+
})
+
| None ->
+
(* No tree, no json, no fail - ambiguous edge case, skip *)
+
{ base with status = `Skip; output = "(no expected tree or json)" })
+
| Some expected -> (
try
let parser = Parser.of_string test.yaml in
let events = Parser.to_list parser in
···
if expected_norm = actual_norm then
{ base with status = `Pass; output = actual }
else
-
{ base with
+
{
+
base with
status = `Fail (Printf.sprintf "Tree mismatch");
-
output = Printf.sprintf "Expected:\n%s\n\nActual:\n%s" expected_norm actual_norm;
+
output =
+
Printf.sprintf "Expected:\n%s\n\nActual:\n%s" expected_norm
+
actual_norm;
}
with exn ->
-
{ base with
-
status = `Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn));
+
{
+
base with
+
status =
+
`Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn));
output = Printexc.to_string exn;
-
}
+
})
end
let status_class = function
···
let generate_html results output_file =
let oc = open_out output_file in
-
let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in
-
let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in
-
let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in
+
let pass_count =
+
List.length (List.filter (fun r -> r.status = `Pass) results)
+
in
+
let fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let skip_count =
+
List.length (List.filter (fun r -> r.status = `Skip) results)
+
in
let total = List.length results in
-
let json_pass_count = List.length (List.filter (fun r -> r.json_status = `Pass) results) in
-
let json_fail_count = List.length (List.filter (fun r -> match r.json_status with `Fail _ -> true | _ -> false) results) in
-
let json_skip_count = List.length (List.filter (fun r -> r.json_status = `Skip) results) in
+
let json_pass_count =
+
List.length (List.filter (fun r -> r.json_status = `Pass) results)
+
in
+
let json_fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.json_status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let json_skip_count =
+
List.length (List.filter (fun r -> r.json_status = `Skip) results)
+
in
-
Printf.fprintf oc {|<!DOCTYPE html>
+
Printf.fprintf oc
+
{|<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
···
<input type="text" class="search" placeholder="Search by ID or name...">
</div>
<div class="tests">
-
|} pass_count fail_count skip_count total json_pass_count json_fail_count json_skip_count;
+
|}
+
pass_count fail_count skip_count total json_pass_count json_fail_count
+
json_skip_count;
-
List.iter (fun result ->
-
let error_badge = if result.is_error_test then
-
{|<span class="badge error-test">Error Test</span>|}
-
else "" in
-
let json_badge = Printf.sprintf {|<span class="badge %s" style="margin-left: 4px;">JSON: %s</span>|}
-
(status_class result.json_status) (status_text result.json_status) in
-
let json_section = if result.json_expected <> "" || result.json_actual <> "" then
-
Printf.sprintf {|
+
List.iter
+
(fun result ->
+
let error_badge =
+
if result.is_error_test then
+
{|<span class="badge error-test">Error Test</span>|}
+
else ""
+
in
+
let json_badge =
+
Printf.sprintf
+
{|<span class="badge %s" style="margin-left: 4px;">JSON: %s</span>|}
+
(status_class result.json_status)
+
(status_text result.json_status)
+
in
+
let json_section =
+
if result.json_expected <> "" || result.json_actual <> "" then
+
Printf.sprintf
+
{|
<div class="section">
<div class="section-title">Expected JSON</div>
<pre>%s</pre>
···
<div class="section-title">Actual JSON</div>
<pre>%s</pre>
</div>|}
-
(html_escape result.json_expected)
-
(html_escape result.json_actual)
-
else "" in
-
Printf.fprintf oc {| <div class="test" data-status="%s" data-json-status="%s" data-id="%s" data-name="%s">
+
(html_escape result.json_expected)
+
(html_escape result.json_actual)
+
else ""
+
in
+
Printf.fprintf oc
+
{| <div class="test" data-status="%s" data-json-status="%s" data-id="%s" data-name="%s">
<div class="test-header" onclick="this.parentElement.classList.toggle('expanded')">
<span class="expand-icon">▶</span>
<span class="badge %s">%s</span>
···
</div>
</div>
|}
-
(status_class result.status)
-
(status_class result.json_status)
-
(html_escape result.id)
-
(html_escape (String.lowercase_ascii result.name))
-
(status_class result.status)
-
(status_text result.status)
-
json_badge
-
(html_escape result.id)
-
(html_escape result.name)
-
error_badge
-
(html_escape result.yaml)
-
(html_escape result.output)
-
json_section
-
) results;
+
(status_class result.status)
+
(status_class result.json_status)
+
(html_escape result.id)
+
(html_escape (String.lowercase_ascii result.name))
+
(status_class result.status)
+
(status_text result.status)
+
json_badge (html_escape result.id) (html_escape result.name) error_badge
+
(html_escape result.yaml)
+
(html_escape result.output)
+
json_section)
+
results;
-
Printf.fprintf oc {| </div>
+
Printf.fprintf oc
+
{| </div>
</div>
<script>
document.querySelectorAll('.filter-btn').forEach(btn => {
···
let html_output = ref None in
let show_skipped = ref false in
let test_suite_path_ref = ref test_suite_path in
-
let args = [
-
"--html", Arg.String (fun s -> html_output := Some s),
-
"<file> Generate HTML report to file";
-
"--show-skipped", Arg.Set show_skipped,
-
" Show details of skipped tests";
-
"--test-suite-path", Arg.Set_string test_suite_path_ref,
-
"<path> Path to yaml-test-suite directory";
-
] in
-
Arg.parse args (fun _ -> ()) "Usage: run_all_tests [--html <file>] [--show-skipped] [--test-suite-path <path>]";
+
let args =
+
[
+
( "--html",
+
Arg.String (fun s -> html_output := Some s),
+
"<file> Generate HTML report to file" );
+
("--show-skipped", Arg.Set show_skipped, " Show details of skipped tests");
+
( "--test-suite-path",
+
Arg.Set_string test_suite_path_ref,
+
"<path> Path to yaml-test-suite directory" );
+
]
+
in
+
Arg.parse args
+
(fun _ -> ())
+
"Usage: run_all_tests [--html <file>] [--show-skipped] [--test-suite-path \
+
<path>]";
let all_tests = TL.load_directory !test_suite_path_ref in
Printf.printf "Total tests loaded: %d\n%!" (List.length all_tests);
let results = List.map run_test all_tests in
-
let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in
-
let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in
-
let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in
+
let pass_count =
+
List.length (List.filter (fun r -> r.status = `Pass) results)
+
in
+
let fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let skip_count =
+
List.length (List.filter (fun r -> r.status = `Skip) results)
+
in
-
let json_pass_count = List.length (List.filter (fun r -> r.json_status = `Pass) results) in
-
let json_fail_count = List.length (List.filter (fun r -> match r.json_status with `Fail _ -> true | _ -> false) results) in
-
let json_skip_count = List.length (List.filter (fun r -> r.json_status = `Skip) results) in
+
let json_pass_count =
+
List.length (List.filter (fun r -> r.json_status = `Pass) results)
+
in
+
let json_fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.json_status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let json_skip_count =
+
List.length (List.filter (fun r -> r.json_status = `Skip) results)
+
in
-
Printf.printf "\nEvent Tree Results: %d pass, %d fail, %d skip (total: %d)\n%!"
-
pass_count fail_count skip_count (pass_count + fail_count + skip_count);
+
Printf.printf
+
"\nEvent Tree Results: %d pass, %d fail, %d skip (total: %d)\n%!" pass_count
+
fail_count skip_count
+
(pass_count + fail_count + skip_count);
-
Printf.printf "JSON Results: %d pass, %d fail, %d skip\n%!"
-
json_pass_count json_fail_count json_skip_count;
+
Printf.printf "JSON Results: %d pass, %d fail, %d skip\n%!" json_pass_count
+
json_fail_count json_skip_count;
if fail_count > 0 then begin
Printf.printf "\nFailing event tree tests:\n";
-
List.iter (fun r ->
-
match r.status with
-
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
-
| _ -> ()
-
) results
+
List.iter
+
(fun r ->
+
match r.status with
+
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
+
| _ -> ())
+
results
end;
if json_fail_count > 0 then begin
Printf.printf "\nFailing JSON tests:\n";
-
List.iter (fun r ->
-
match r.json_status with
-
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
-
| _ -> ()
-
) results
+
List.iter
+
(fun r ->
+
match r.json_status with
+
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
+
| _ -> ())
+
results
end;
if !show_skipped && skip_count > 0 then begin
Printf.printf "\nSkipped tests (no expected tree):\n";
-
List.iter (fun r ->
-
if r.status = `Skip then begin
-
Printf.printf " %s: %s\n" r.id r.name;
-
Printf.printf " YAML (%d chars): %S\n" (String.length r.yaml)
-
(if String.length r.yaml <= 60 then r.yaml
-
else String.sub r.yaml 0 60 ^ "...")
-
end
-
) results
+
List.iter
+
(fun r ->
+
if r.status = `Skip then begin
+
Printf.printf " %s: %s\n" r.id r.name;
+
Printf.printf " YAML (%d chars): %S\n" (String.length r.yaml)
+
(if String.length r.yaml <= 60 then r.yaml
+
else String.sub r.yaml 0 60 ^ "...")
+
end)
+
results
end;
(match !html_output with
···
| None -> ());
(* Exit with non-zero code if any tests failed *)
-
if fail_count > 0 || json_fail_count > 0 then
-
exit 1
+
if fail_count > 0 || json_fail_count > 0 then exit 1
+224 -162
tests/run_all_tests_eio.ml
···
(* HTML escape function *)
let html_escape s =
let buf = Buffer.create (String.length s) in
-
String.iter (function
-
| '<' -> Buffer.add_string buf "&lt;"
-
| '>' -> Buffer.add_string buf "&gt;"
-
| '&' -> Buffer.add_string buf "&amp;"
-
| '"' -> Buffer.add_string buf "&quot;"
-
| c -> Buffer.add_char buf c
-
) s;
+
String.iter
+
(function
+
| '<' -> Buffer.add_string buf "&lt;"
+
| '>' -> Buffer.add_string buf "&gt;"
+
| '&' -> Buffer.add_string buf "&amp;"
+
| '"' -> Buffer.add_string buf "&quot;"
+
| c -> Buffer.add_char buf c)
+
s;
Buffer.contents buf
let normalize_tree s =
···
name : string;
yaml : string;
is_error_test : bool;
-
status : [`Pass | `Fail of string | `Skip];
+
status : [ `Pass | `Fail of string | `Skip ];
output : string;
-
json_status : [`Pass | `Fail of string | `Skip];
+
json_status : [ `Pass | `Fail of string | `Skip ];
json_expected : string;
json_actual : string;
}
-
let compare_json expected actual =
-
JC.compare_json_strings expected actual
+
let compare_json expected actual = JC.compare_json_strings expected actual
-
let run_json_test (test : TL.test_case) : [`Pass | `Fail of string | `Skip] * string =
+
let run_json_test (test : TL.test_case) :
+
[ `Pass | `Fail of string | `Skip ] * string =
match test.json with
| None -> (`Skip, "")
-
| Some expected_json ->
-
if test.fail then
-
(`Skip, "")
+
| Some expected_json -> (
+
if test.fail then (`Skip, "")
else
try
let docs = Loader.documents_of_string test.yaml in
-
let values = List.filter_map (fun doc ->
-
match Document.root doc with
-
| None -> None
-
| Some yaml -> Some (Yaml.to_value ~resolve_aliases_first:true yaml)
-
) docs in
-
let actual_json = match values with
+
let values =
+
List.filter_map
+
(fun doc ->
+
match Document.root doc with
+
| None -> None
+
| Some yaml ->
+
Some (Yaml.to_value ~resolve_aliases_first:true yaml))
+
docs
+
in
+
let actual_json =
+
match values with
| [] -> ""
-
| [v] -> JF.to_json v
+
| [ v ] -> JF.to_json v
| vs -> JF.documents_to_json vs
in
-
if compare_json expected_json actual_json then
-
(`Pass, actual_json)
-
else
-
(`Fail "JSON mismatch", actual_json)
+
if compare_json expected_json actual_json then (`Pass, actual_json)
+
else (`Fail "JSON mismatch", actual_json)
with
| Yamlrw_error e ->
(`Fail (Format.asprintf "Parse error: %a" Error.pp e), "")
| exn ->
(`Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn)), "")
+
)
let run_test (test : TL.test_case) : test_result =
let json_status, json_actual = run_json_test test in
-
let base = {
-
id = test.id;
-
name = test.name;
-
yaml = test.yaml;
-
is_error_test = test.fail;
-
status = `Skip;
-
output = "";
-
json_status;
-
json_expected = Option.value ~default:"" test.json;
-
json_actual;
-
} in
+
let base =
+
{
+
id = test.id;
+
name = test.name;
+
yaml = test.yaml;
+
is_error_test = test.fail;
+
status = `Skip;
+
output = "";
+
json_status;
+
json_expected = Option.value ~default:"" test.json;
+
json_actual;
+
}
+
in
if test.fail then begin
try
let parser = Parser.of_string test.yaml in
let events = Parser.to_list parser in
let tree = TF.of_spanned_events events in
-
{ base with
-
status = `Fail "Expected parsing to fail";
-
output = tree;
-
}
+
{ base with status = `Fail "Expected parsing to fail"; output = tree }
with
| Yamlrw_error e ->
-
{ base with
-
status = `Pass;
-
output = Format.asprintf "%a" Error.pp e;
-
}
-
| exn ->
-
{ base with
-
status = `Pass;
-
output = Printexc.to_string exn;
-
}
+
{ base with status = `Pass; output = Format.asprintf "%a" Error.pp e }
+
| exn -> { base with status = `Pass; output = Printexc.to_string exn }
end
else begin
match test.tree with
-
| None ->
-
(match test.json with
-
| Some _ ->
-
(try
-
let parser = Parser.of_string test.yaml in
-
let events = Parser.to_list parser in
-
let tree = TF.of_spanned_events events in
-
{ base with status = `Pass; output = tree }
-
with exn ->
-
{ base with
-
status = `Fail (Printf.sprintf "Should parse but got: %s" (Printexc.to_string exn));
-
output = Printexc.to_string exn;
-
})
-
| None ->
-
{ base with status = `Skip; output = "(no expected tree or json)" })
-
| Some expected ->
+
| None -> (
+
match test.json with
+
| Some _ -> (
+
try
+
let parser = Parser.of_string test.yaml in
+
let events = Parser.to_list parser in
+
let tree = TF.of_spanned_events events in
+
{ base with status = `Pass; output = tree }
+
with exn ->
+
{
+
base with
+
status =
+
`Fail
+
(Printf.sprintf "Should parse but got: %s"
+
(Printexc.to_string exn));
+
output = Printexc.to_string exn;
+
})
+
| None ->
+
{ base with status = `Skip; output = "(no expected tree or json)" })
+
| Some expected -> (
try
let parser = Parser.of_string test.yaml in
let events = Parser.to_list parser in
···
if expected_norm = actual_norm then
{ base with status = `Pass; output = actual }
else
-
{ base with
+
{
+
base with
status = `Fail (Printf.sprintf "Tree mismatch");
-
output = Printf.sprintf "Expected:\n%s\n\nActual:\n%s" expected_norm actual_norm;
+
output =
+
Printf.sprintf "Expected:\n%s\n\nActual:\n%s" expected_norm
+
actual_norm;
}
with exn ->
-
{ base with
-
status = `Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn));
+
{
+
base with
+
status =
+
`Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn));
output = Printexc.to_string exn;
-
}
+
})
end
(* Run tests in parallel using Eio fibers *)
-
let run_tests_parallel tests =
-
Eio.Fiber.List.map run_test tests
+
let run_tests_parallel tests = Eio.Fiber.List.map run_test tests
let status_class = function
| `Pass -> "pass"
···
| `Skip -> "SKIP"
let generate_html ~fs results output_file =
-
let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in
-
let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in
-
let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in
+
let pass_count =
+
List.length (List.filter (fun r -> r.status = `Pass) results)
+
in
+
let fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let skip_count =
+
List.length (List.filter (fun r -> r.status = `Skip) results)
+
in
let total = List.length results in
-
let json_pass_count = List.length (List.filter (fun r -> r.json_status = `Pass) results) in
-
let json_fail_count = List.length (List.filter (fun r -> match r.json_status with `Fail _ -> true | _ -> false) results) in
-
let json_skip_count = List.length (List.filter (fun r -> r.json_status = `Skip) results) in
+
let json_pass_count =
+
List.length (List.filter (fun r -> r.json_status = `Pass) results)
+
in
+
let json_fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.json_status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let json_skip_count =
+
List.length (List.filter (fun r -> r.json_status = `Skip) results)
+
in
let buf = Buffer.create 65536 in
-
Printf.bprintf buf {|<!DOCTYPE html>
+
Printf.bprintf buf
+
{|<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
···
<input type="text" class="search" placeholder="Search by ID or name...">
</div>
<div class="tests">
-
|} pass_count fail_count skip_count total json_pass_count json_fail_count json_skip_count;
+
|}
+
pass_count fail_count skip_count total json_pass_count json_fail_count
+
json_skip_count;
-
List.iter (fun result ->
-
let error_badge = if result.is_error_test then
-
{|<span class="badge error-test">Error Test</span>|}
-
else "" in
-
let json_badge = Printf.sprintf {|<span class="badge %s" style="margin-left: 4px;">JSON: %s</span>|}
-
(status_class result.json_status) (status_text result.json_status) in
-
let json_section = if result.json_expected <> "" || result.json_actual <> "" then
-
Printf.sprintf {|
+
List.iter
+
(fun result ->
+
let error_badge =
+
if result.is_error_test then
+
{|<span class="badge error-test">Error Test</span>|}
+
else ""
+
in
+
let json_badge =
+
Printf.sprintf
+
{|<span class="badge %s" style="margin-left: 4px;">JSON: %s</span>|}
+
(status_class result.json_status)
+
(status_text result.json_status)
+
in
+
let json_section =
+
if result.json_expected <> "" || result.json_actual <> "" then
+
Printf.sprintf
+
{|
<div class="section">
<div class="section-title">Expected JSON</div>
<pre>%s</pre>
···
<div class="section-title">Actual JSON</div>
<pre>%s</pre>
</div>|}
-
(html_escape result.json_expected)
-
(html_escape result.json_actual)
-
else "" in
-
Printf.bprintf buf {| <div class="test" data-status="%s" data-json-status="%s" data-id="%s" data-name="%s">
+
(html_escape result.json_expected)
+
(html_escape result.json_actual)
+
else ""
+
in
+
Printf.bprintf buf
+
{| <div class="test" data-status="%s" data-json-status="%s" data-id="%s" data-name="%s">
<div class="test-header" onclick="this.parentElement.classList.toggle('expanded')">
<span class="expand-icon">▶</span>
<span class="badge %s">%s</span>
···
</div>
</div>
|}
-
(status_class result.status)
-
(status_class result.json_status)
-
(html_escape result.id)
-
(html_escape (String.lowercase_ascii result.name))
-
(status_class result.status)
-
(status_text result.status)
-
json_badge
-
(html_escape result.id)
-
(html_escape result.name)
-
error_badge
-
(html_escape result.yaml)
-
(html_escape result.output)
-
json_section
-
) results;
+
(status_class result.status)
+
(status_class result.json_status)
+
(html_escape result.id)
+
(html_escape (String.lowercase_ascii result.name))
+
(status_class result.status)
+
(status_text result.status)
+
json_badge (html_escape result.id) (html_escape result.name) error_badge
+
(html_escape result.yaml)
+
(html_escape result.output)
+
json_section)
+
results;
-
Printf.bprintf buf {| </div>
+
Printf.bprintf buf
+
{| </div>
</div>
<script>
document.querySelectorAll('.filter-btn').forEach(btn => {
···
let show_skipped = ref false in
let sequential = ref false in
let test_suite_path_ref = ref test_suite_path in
-
let args = [
-
"--html", Arg.String (fun s -> html_output := Some s),
-
"<file> Generate HTML report to file";
-
"--show-skipped", Arg.Set show_skipped,
-
" Show details of skipped tests";
-
"--sequential", Arg.Set sequential,
-
" Run tests sequentially instead of in parallel";
-
"--test-suite-path", Arg.Set_string test_suite_path_ref,
-
"<path> Path to yaml-test-suite directory";
-
] in
-
Arg.parse args (fun _ -> ()) "Usage: run_all_tests_eio [--html <file>] [--show-skipped] [--sequential] [--test-suite-path <path>]";
+
let args =
+
[
+
( "--html",
+
Arg.String (fun s -> html_output := Some s),
+
"<file> Generate HTML report to file" );
+
("--show-skipped", Arg.Set show_skipped, " Show details of skipped tests");
+
( "--sequential",
+
Arg.Set sequential,
+
" Run tests sequentially instead of in parallel" );
+
( "--test-suite-path",
+
Arg.Set_string test_suite_path_ref,
+
"<path> Path to yaml-test-suite directory" );
+
]
+
in
+
Arg.parse args
+
(fun _ -> ())
+
"Usage: run_all_tests_eio [--html <file>] [--show-skipped] [--sequential] \
+
[--test-suite-path <path>]";
Eio_main.run @@ fun env ->
(* Use fs (full filesystem) rather than cwd (sandboxed) to allow ".." navigation *)
let fs = Eio.Stdenv.fs env in
(* Get the absolute path to the test suite *)
-
let test_suite_abs = if Filename.is_relative !test_suite_path_ref then
-
Filename.concat (Sys.getcwd ()) !test_suite_path_ref
-
else
-
!test_suite_path_ref
+
let test_suite_abs =
+
if Filename.is_relative !test_suite_path_ref then
+
Filename.concat (Sys.getcwd ()) !test_suite_path_ref
+
else !test_suite_path_ref
in
let start_time = Unix.gettimeofday () in
(* Load tests using Eio (parallel by default) *)
-
let all_tests = if !sequential then
-
TL.load_directory ~fs test_suite_abs
-
else
-
TL.load_directory_parallel ~fs test_suite_abs
+
let all_tests =
+
if !sequential then TL.load_directory ~fs test_suite_abs
+
else TL.load_directory_parallel ~fs test_suite_abs
in
let load_time = Unix.gettimeofday () in
-
Printf.printf "Loaded %d tests in %.3fs\n%!" (List.length all_tests) (load_time -. start_time);
+
Printf.printf "Loaded %d tests in %.3fs\n%!" (List.length all_tests)
+
(load_time -. start_time);
(* Run tests (parallel or sequential based on flag) *)
-
let results = if !sequential then
-
List.map run_test all_tests
-
else
-
run_tests_parallel all_tests
+
let results =
+
if !sequential then List.map run_test all_tests
+
else run_tests_parallel all_tests
in
let run_time = Unix.gettimeofday () in
Printf.printf "Ran tests in %.3fs\n%!" (run_time -. load_time);
-
let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in
-
let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in
-
let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in
+
let pass_count =
+
List.length (List.filter (fun r -> r.status = `Pass) results)
+
in
+
let fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let skip_count =
+
List.length (List.filter (fun r -> r.status = `Skip) results)
+
in
-
let json_pass_count = List.length (List.filter (fun r -> r.json_status = `Pass) results) in
-
let json_fail_count = List.length (List.filter (fun r -> match r.json_status with `Fail _ -> true | _ -> false) results) in
-
let json_skip_count = List.length (List.filter (fun r -> r.json_status = `Skip) results) in
+
let json_pass_count =
+
List.length (List.filter (fun r -> r.json_status = `Pass) results)
+
in
+
let json_fail_count =
+
List.length
+
(List.filter
+
(fun r -> match r.json_status with `Fail _ -> true | _ -> false)
+
results)
+
in
+
let json_skip_count =
+
List.length (List.filter (fun r -> r.json_status = `Skip) results)
+
in
-
Printf.printf "\nEvent Tree Results: %d pass, %d fail, %d skip (total: %d)\n%!"
-
pass_count fail_count skip_count (pass_count + fail_count + skip_count);
+
Printf.printf
+
"\nEvent Tree Results: %d pass, %d fail, %d skip (total: %d)\n%!" pass_count
+
fail_count skip_count
+
(pass_count + fail_count + skip_count);
-
Printf.printf "JSON Results: %d pass, %d fail, %d skip\n%!"
-
json_pass_count json_fail_count json_skip_count;
+
Printf.printf "JSON Results: %d pass, %d fail, %d skip\n%!" json_pass_count
+
json_fail_count json_skip_count;
if fail_count > 0 then begin
Printf.printf "\nFailing event tree tests:\n";
-
List.iter (fun r ->
-
match r.status with
-
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
-
| _ -> ()
-
) results
+
List.iter
+
(fun r ->
+
match r.status with
+
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
+
| _ -> ())
+
results
end;
if json_fail_count > 0 then begin
Printf.printf "\nFailing JSON tests:\n";
-
List.iter (fun r ->
-
match r.json_status with
-
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
-
| _ -> ()
-
) results
+
List.iter
+
(fun r ->
+
match r.json_status with
+
| `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg
+
| _ -> ())
+
results
end;
if !show_skipped && skip_count > 0 then begin
Printf.printf "\nSkipped tests (no expected tree):\n";
-
List.iter (fun r ->
-
if r.status = `Skip then begin
-
Printf.printf " %s: %s\n" r.id r.name;
-
Printf.printf " YAML (%d chars): %S\n" (String.length r.yaml)
-
(if String.length r.yaml <= 60 then r.yaml
-
else String.sub r.yaml 0 60 ^ "...")
-
end
-
) results
+
List.iter
+
(fun r ->
+
if r.status = `Skip then begin
+
Printf.printf " %s: %s\n" r.id r.name;
+
Printf.printf " YAML (%d chars): %S\n" (String.length r.yaml)
+
(if String.length r.yaml <= 60 then r.yaml
+
else String.sub r.yaml 0 60 ^ "...")
+
end)
+
results
end;
let total_time = Unix.gettimeofday () in
···
| None -> ());
(* Exit with non-zero code if any tests failed *)
-
if fail_count > 0 || json_fail_count > 0 then
-
exit 1
+
if fail_count > 0 || json_fail_count > 0 then exit 1
+6 -1
tests/test_suite_lib/dune
···
(library
(name test_suite_lib)
-
(modules test_suite_loader_generic test_suite_loader tree_format json_format json_compare)
+
(modules
+
test_suite_loader_generic
+
test_suite_loader
+
tree_format
+
json_format
+
json_compare)
(libraries yamlrw jsonm))
(library
+29 -27
tests/test_suite_lib/json_compare.ml
···
| Object of (string * json) list
let rec equal a b =
-
match a, b with
+
match (a, b) with
| Null, Null -> true
| Bool a, Bool b -> a = b
| Float a, Float b -> Float.equal a b
···
| Array a, Array b -> List.equal equal a b
| Object a, Object b ->
(* Compare objects as sets of key-value pairs (order independent) *)
-
let sorted_a = List.sort (fun (k1, _) (k2, _) -> String.compare k1 k2) a in
-
let sorted_b = List.sort (fun (k1, _) (k2, _) -> String.compare k1 k2) b in
-
List.length sorted_a = List.length sorted_b &&
-
List.for_all2 (fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2) sorted_a sorted_b
+
let sorted_a =
+
List.sort (fun (k1, _) (k2, _) -> String.compare k1 k2) a
+
in
+
let sorted_b =
+
List.sort (fun (k1, _) (k2, _) -> String.compare k1 k2) b
+
in
+
List.length sorted_a = List.length sorted_b
+
&& List.for_all2
+
(fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2)
+
sorted_a sorted_b
| _ -> false
(* Parse JSON string using jsonm *)
···
and parse_array acc =
match Jsonm.decode decoder with
| `Lexeme `Ae -> Ok (Array (List.rev acc))
-
| `Lexeme _ as lex ->
+
| `Lexeme _ as lex -> (
(* Push back and parse value *)
let result = parse_value_with_lex lex in
-
(match result with
-
| Ok v -> parse_array (v :: acc)
-
| Error _ as e -> e)
+
match result with Ok v -> parse_array (v :: acc) | Error _ as e -> e)
| `Error e -> Error (Format.asprintf "%a" Jsonm.pp_error e)
| `End -> Error "unexpected end in array"
| `Await -> Error "unexpected await"
and parse_object acc =
match Jsonm.decode decoder with
| `Lexeme `Oe -> Ok (Object (List.rev acc))
-
| `Lexeme (`Name key) ->
-
(match parse_value () with
-
| Ok v -> parse_object ((key, v) :: acc)
-
| Error _ as e -> e)
+
| `Lexeme (`Name key) -> (
+
match parse_value () with
+
| Ok v -> parse_object ((key, v) :: acc)
+
| Error _ as e -> e)
| `Lexeme _ -> Error "expected object key"
| `Error e -> Error (Format.asprintf "%a" Jsonm.pp_error e)
| `End -> Error "unexpected end in object"
···
and parse_array acc =
match Jsonm.decode decoder with
| `Lexeme `Ae -> Some (Array (List.rev acc))
-
| `Lexeme _ as lex ->
-
(match parse_value_with_lex lex with
-
| Some v -> parse_array (v :: acc)
-
| None -> None)
+
| `Lexeme _ as lex -> (
+
match parse_value_with_lex lex with
+
| Some v -> parse_array (v :: acc)
+
| None -> None)
| _ -> None
and parse_object acc =
match Jsonm.decode decoder with
| `Lexeme `Oe -> Some (Object (List.rev acc))
-
| `Lexeme (`Name key) ->
-
(match parse_value () with
-
| Some v -> parse_object ((key, v) :: acc)
-
| None -> None)
+
| `Lexeme (`Name key) -> (
+
match parse_value () with
+
| Some v -> parse_object ((key, v) :: acc)
+
| None -> None)
| _ -> None
and parse_value_with_lex lex =
match lex with
···
(* Handle empty strings *)
let expected_trimmed = String.trim expected in
let actual_trimmed = String.trim actual in
-
if expected_trimmed = "" && actual_trimmed = "" then
-
true
-
else if expected_trimmed = "" || actual_trimmed = "" then
-
false
+
if expected_trimmed = "" && actual_trimmed = "" then true
+
else if expected_trimmed = "" || actual_trimmed = "" then false
else
(* Parse as potentially multiple JSON values *)
let expected_values = parse_json_multi expected in
let actual_values = parse_json_multi actual in
-
List.length expected_values = List.length actual_values &&
-
List.for_all2 equal expected_values actual_values
+
List.length expected_values = List.length actual_values
+
&& List.for_all2 equal expected_values actual_values
+37 -27
tests/test_suite_lib/json_format.ml
···
let escape_string s =
let buf = Buffer.create (String.length s * 2) in
Buffer.add_char buf '"';
-
String.iter (fun c ->
-
match c with
-
| '"' -> Buffer.add_string buf "\\\""
-
| '\\' -> Buffer.add_string buf "\\\\"
-
| '\n' -> Buffer.add_string buf "\\n"
-
| '\r' -> Buffer.add_string buf "\\r"
-
| '\t' -> Buffer.add_string buf "\\t"
-
| '\x08' -> Buffer.add_string buf "\\b"
-
| '\x0c' -> Buffer.add_string buf "\\f"
-
| c when Char.code c < 32 ->
-
Buffer.add_string buf (Printf.sprintf "\\u%04x" (Char.code c))
-
| c -> Buffer.add_char buf c
-
) s;
+
String.iter
+
(fun c ->
+
match c with
+
| '"' -> Buffer.add_string buf "\\\""
+
| '\\' -> Buffer.add_string buf "\\\\"
+
| '\n' -> Buffer.add_string buf "\\n"
+
| '\r' -> Buffer.add_string buf "\\r"
+
| '\t' -> Buffer.add_string buf "\\t"
+
| '\x08' -> Buffer.add_string buf "\\b"
+
| '\x0c' -> Buffer.add_string buf "\\f"
+
| c when Char.code c < 32 ->
+
Buffer.add_string buf (Printf.sprintf "\\u%04x" (Char.code c))
+
| c -> Buffer.add_char buf c)
+
s;
Buffer.add_char buf '"';
Buffer.contents buf
-
let rec format_value ?(indent=0) (v : Value.t) =
+
let rec format_value ?(indent = 0) (v : Value.t) =
let spaces n = String.make n ' ' in
match v with
| `Null -> "null"
| `Bool true -> "true"
| `Bool false -> "false"
| `Float f ->
-
if Float.is_nan f then "null" (* JSON doesn't support NaN *)
-
else if f = Float.infinity || f = Float.neg_infinity then "null" (* JSON doesn't support Inf *)
+
if Float.is_nan f then "null" (* JSON doesn't support NaN *)
+
else if f = Float.infinity || f = Float.neg_infinity then "null"
+
(* JSON doesn't support Inf *)
else if Float.is_integer f && Float.abs f < 1e15 then
Printf.sprintf "%.0f" f
else
(* Try to match yaml-test-suite's number formatting *)
let s = Printf.sprintf "%g" f in
(* Ensure we have a decimal point for floats *)
-
if String.contains s '.' || String.contains s 'e' || String.contains s 'E' then s
+
if
+
String.contains s '.' || String.contains s 'e'
+
|| String.contains s 'E'
+
then s
else s ^ ".0"
| `String s -> escape_string s
| `A [] -> "[]"
| `A items ->
let inner_indent = indent + 2 in
-
let formatted_items = List.map (fun item ->
-
spaces inner_indent ^ format_value ~indent:inner_indent item
-
) items in
+
let formatted_items =
+
List.map
+
(fun item ->
+
spaces inner_indent ^ format_value ~indent:inner_indent item)
+
items
+
in
"[\n" ^ String.concat ",\n" formatted_items ^ "\n" ^ spaces indent ^ "]"
| `O [] -> "{}"
| `O pairs ->
let inner_indent = indent + 2 in
-
let formatted_pairs = List.map (fun (k, v) ->
-
let key = escape_string k in
-
let value = format_value ~indent:inner_indent v in
-
spaces inner_indent ^ key ^ ": " ^ value
-
) pairs in
+
let formatted_pairs =
+
List.map
+
(fun (k, v) ->
+
let key = escape_string k in
+
let value = format_value ~indent:inner_indent v in
+
spaces inner_indent ^ key ^ ": " ^ value)
+
pairs
+
in
"{\n" ^ String.concat ",\n" formatted_pairs ^ "\n" ^ spaces indent ^ "}"
-
let to_json (v : Value.t) : string =
-
format_value v
+
let to_json (v : Value.t) : string = format_value v
(* Format multiple documents (for multi-doc YAML) *)
let documents_to_json (docs : Value.t list) : string =
+5 -10
tests/test_suite_lib/test_suite_loader.ml
···
Some s
with _ -> None
-
let file_exists () path =
-
Sys.file_exists path
-
-
let is_directory () path =
-
Sys.file_exists path && Sys.is_directory path
-
-
let read_dir () path =
-
Array.to_list (Sys.readdir path)
+
let file_exists () path = Sys.file_exists path
+
let is_directory () path = Sys.file_exists path && Sys.is_directory path
+
let read_dir () path = Array.to_list (Sys.readdir path)
end
+
module Loader = Test_suite_loader_generic.Make (Sync_io)
(** Internal loader module *)
-
module Loader = Test_suite_loader_generic.Make(Sync_io)
-
(** Re-export test_case type from loader *)
type test_case = Loader.test_case = {
id : string;
name : string;
···
json : string option;
fail : bool;
}
+
(** Re-export test_case type from loader *)
(** Load tests without needing to pass a context *)
let load_directory path : test_case list = Loader.load_directory () path
+15 -15
tests/test_suite_lib/test_suite_loader_eio.ml
···
module Generic = Test_suite_lib.Test_suite_loader_generic
(** Eio file I/O implementation *)
-
module Eio_io : Generic.FILE_IO with type ctx = Eio.Fs.dir_ty Eio.Path.t = struct
+
module Eio_io : Generic.FILE_IO with type ctx = Eio.Fs.dir_ty Eio.Path.t =
+
struct
type ctx = Eio.Fs.dir_ty Eio.Path.t
let read_file fs path =
-
try
-
Some (Eio.Path.load Eio.Path.(fs / path))
-
with _ -> None
+
try Some (Eio.Path.load Eio.Path.(fs / path)) with _ -> None
let file_exists fs path =
match Eio.Path.kind ~follow:true Eio.Path.(fs / path) with
···
| _ -> false
| exception _ -> false
-
let read_dir fs path =
-
Eio.Path.read_dir Eio.Path.(fs / path)
+
let read_dir fs path = Eio.Path.read_dir Eio.Path.(fs / path)
end
+
module Loader = Generic.Make (Eio_io)
(** Internal loader module *)
-
module Loader = Generic.Make(Eio_io)
-
(** Re-export test_case type from loader *)
type test_case = Loader.test_case = {
id : string;
name : string;
···
json : string option;
fail : bool;
}
+
(** Re-export test_case type from loader *)
(** Load tests with Eio filesystem context *)
let load_directory ~fs path : test_case list = Loader.load_directory fs path
···
if not (Eio_io.is_directory fs test_suite_path) then []
else
let entries = Eio_io.read_dir fs test_suite_path in
-
let test_ids = entries
+
let test_ids =
+
entries
|> List.filter (fun e ->
-
Eio_io.is_directory fs (Filename.concat test_suite_path e) &&
-
String.length e >= 4 &&
-
e.[0] >= '0' && e.[0] <= 'Z')
+
Eio_io.is_directory fs (Filename.concat test_suite_path e)
+
&& String.length e >= 4
+
&& e.[0] >= '0'
+
&& e.[0] <= 'Z')
|> List.sort String.compare
in
(* Load each test ID in parallel using fibers *)
-
Eio.Fiber.List.map (fun test_id ->
-
Loader.load_test_id fs test_suite_path test_id
-
) test_ids
+
Eio.Fiber.List.map
+
(fun test_id -> Loader.load_test_id fs test_suite_path test_id)
+
test_ids
|> List.concat
+40 -31
tests/test_suite_lib/test_suite_loader_generic.ml
···
(** Generic test suite loader - parameterized by file I/O operations *)
-
(** Test case representation *)
type test_case = {
id : string;
name : string;
···
json : string option;
fail : bool;
}
+
(** Test case representation *)
(** Module type for file I/O operations *)
module type FILE_IO = sig
-
(** Context type for file operations (unit for sync, ~fs for Eio) *)
type ctx
+
(** Context type for file operations (unit for sync, ~fs for Eio) *)
+
val read_file : ctx -> string -> string option
(** Read a file, returning None if it doesn't exist or can't be read *)
-
val read_file : ctx -> string -> string option
-
(** Check if a path exists and is a regular file *)
val file_exists : ctx -> string -> bool
+
(** Check if a path exists and is a regular file *)
-
(** Check if a path exists and is a directory *)
val is_directory : ctx -> string -> bool
+
(** Check if a path exists and is a directory *)
+
val read_dir : ctx -> string -> string list
(** List directory entries *)
-
val read_dir : ctx -> string -> string list
end
(** Create a test loader from file I/O operations *)
···
}
let read_file_required ctx path =
-
match IO.read_file ctx path with
-
| Some s -> s
-
| None -> ""
+
match IO.read_file ctx path with Some s -> s | None -> ""
(** Load a single test from a directory *)
let load_test_dir ctx base_id dir_path =
···
(* Must have in.yaml to be a valid test *)
if not (IO.file_exists ctx yaml_file) then None
else
-
let name = match IO.read_file ctx name_file with
+
let name =
+
match IO.read_file ctx name_file with
| Some s -> String.trim s
| None -> base_id
in
···
let fail = IO.file_exists ctx error_file in
Some { id = base_id; name; yaml; tree; json; fail }
-
(** Load tests from a test ID directory (may have subdirectories for variants) *)
+
(** Load tests from a test ID directory (may have subdirectories for variants)
+
*)
let load_test_id ctx test_suite_path test_id =
let dir_path = Filename.concat test_suite_path test_id in
if not (IO.is_directory ctx dir_path) then []
else
let entries = IO.read_dir ctx dir_path in
(* Check if this directory has variant subdirectories (00, 01, etc.) *)
-
let has_variants = List.exists (fun e ->
-
let subdir = Filename.concat dir_path e in
-
IO.is_directory ctx subdir &&
-
String.length e >= 2 &&
-
e.[0] >= '0' && e.[0] <= '9'
-
) entries in
+
let has_variants =
+
List.exists
+
(fun e ->
+
let subdir = Filename.concat dir_path e in
+
IO.is_directory ctx subdir
+
&& String.length e >= 2
+
&& e.[0] >= '0'
+
&& e.[0] <= '9')
+
entries
+
in
if has_variants then
(* Load each variant subdirectory *)
-
let variants = entries
+
let variants =
+
entries
|> List.filter (fun e ->
let subdir = Filename.concat dir_path e in
-
IO.is_directory ctx subdir &&
-
String.length e >= 2 &&
-
e.[0] >= '0' && e.[0] <= '9')
+
IO.is_directory ctx subdir
+
&& String.length e >= 2
+
&& e.[0] >= '0'
+
&& e.[0] <= '9')
|> List.sort String.compare
in
-
List.filter_map (fun variant ->
-
let variant_path = Filename.concat dir_path variant in
-
let variant_id = Printf.sprintf "%s:%s" test_id variant in
-
load_test_dir ctx variant_id variant_path
-
) variants
+
List.filter_map
+
(fun variant ->
+
let variant_path = Filename.concat dir_path variant in
+
let variant_id = Printf.sprintf "%s:%s" test_id variant in
+
load_test_dir ctx variant_id variant_path)
+
variants
else
(* Single test in this directory *)
match load_test_dir ctx test_id dir_path with
-
| Some t -> [t]
+
| Some t -> [ t ]
| None -> []
(** Load all tests from a test suite directory *)
···
if not (IO.is_directory ctx test_suite_path) then []
else
let entries = IO.read_dir ctx test_suite_path in
-
let test_ids = entries
+
let test_ids =
+
entries
|> List.filter (fun e ->
-
IO.is_directory ctx (Filename.concat test_suite_path e) &&
-
String.length e >= 4 &&
-
e.[0] >= '0' && e.[0] <= 'Z')
+
IO.is_directory ctx (Filename.concat test_suite_path e)
+
&& String.length e >= 4
+
&& e.[0] >= '0'
+
&& e.[0] <= 'Z')
|> List.sort String.compare
in
List.concat_map (load_test_id ctx test_suite_path) test_ids
+27 -28
tests/test_suite_lib/tree_format.ml
···
let escape_string s =
let buf = Buffer.create (String.length s * 2) in
-
String.iter (fun c ->
-
match c with
-
| '\n' -> Buffer.add_string buf "\\n"
-
| '\t' -> Buffer.add_string buf "\\t"
-
| '\r' -> Buffer.add_string buf "\\r"
-
| '\\' -> Buffer.add_string buf "\\\\"
-
| '\x00' -> Buffer.add_string buf "\\0"
-
| '\x07' -> Buffer.add_string buf "\\a"
-
| '\x08' -> Buffer.add_string buf "\\b"
-
| '\x0b' -> Buffer.add_string buf "\\v"
-
| '\x0c' -> Buffer.add_string buf "\\f"
-
| '\x1b' -> Buffer.add_string buf "\\e"
-
| '\xa0' -> Buffer.add_string buf "\\_"
-
| c -> Buffer.add_char buf c
-
) s;
+
String.iter
+
(fun c ->
+
match c with
+
| '\n' -> Buffer.add_string buf "\\n"
+
| '\t' -> Buffer.add_string buf "\\t"
+
| '\r' -> Buffer.add_string buf "\\r"
+
| '\\' -> Buffer.add_string buf "\\\\"
+
| '\x00' -> Buffer.add_string buf "\\0"
+
| '\x07' -> Buffer.add_string buf "\\a"
+
| '\x08' -> Buffer.add_string buf "\\b"
+
| '\x0b' -> Buffer.add_string buf "\\v"
+
| '\x0c' -> Buffer.add_string buf "\\f"
+
| '\x1b' -> Buffer.add_string buf "\\e"
+
| '\xa0' -> Buffer.add_string buf "\\_"
+
| c -> Buffer.add_char buf c)
+
s;
Buffer.contents buf
let style_char = function
···
| Event.Stream_start _ -> "+STR"
| Event.Stream_end -> "-STR"
| Event.Document_start { implicit; _ } ->
-
if implicit then "+DOC"
-
else "+DOC ---"
-
| Event.Document_end { implicit } ->
-
if implicit then "-DOC"
-
else "-DOC ..."
+
if implicit then "+DOC" else "+DOC ---"
+
| Event.Document_end { implicit } -> if implicit then "-DOC" else "-DOC ..."
| Event.Mapping_start { anchor; tag; style; _ } ->
let anchor_str = match anchor with Some a -> " &" ^ a | None -> "" in
let tag_str = match tag with Some t -> " <" ^ t ^ ">" | None -> "" in
···
let anchor_str = match anchor with Some a -> " &" ^ a | None -> "" in
let tag_str = match tag with Some t -> " <" ^ t ^ ">" | None -> "" in
let style_c = style_char style in
-
Printf.sprintf "=VAL%s%s %c%s" anchor_str tag_str style_c (escape_string value)
-
| Event.Alias { anchor } ->
-
Printf.sprintf "=ALI *%s" anchor
+
Printf.sprintf "=VAL%s%s %c%s" anchor_str tag_str style_c
+
(escape_string value)
+
| Event.Alias { anchor } -> Printf.sprintf "=ALI *%s" anchor
let of_spanned_events events =
let buf = Buffer.create 256 in
-
List.iter (fun (e : Event.spanned) ->
-
let line = format_event e in
-
Buffer.add_string buf line;
-
Buffer.add_char buf '\n'
-
) events;
+
List.iter
+
(fun (e : Event.spanned) ->
+
let line = format_event e in
+
Buffer.add_string buf line;
+
Buffer.add_char buf '\n')
+
events;
Buffer.contents buf
+147 -112
tests/test_yamlrw.ml
···
Alcotest.(check int) "token count" 8 (List.length token_types);
(* Stream_start, Block_mapping_start, Key, Scalar, Value, Scalar, Block_end, Stream_end *)
match token_types with
-
| Token.Stream_start _ :: Token.Block_mapping_start :: Token.Key ::
-
Token.Scalar { value = "hello"; _ } :: Token.Value ::
-
Token.Scalar { value = "world"; _ } :: Token.Block_end :: Token.Stream_end :: [] ->
+
| [
+
Token.Stream_start _;
+
Token.Block_mapping_start;
+
Token.Key;
+
Token.Scalar { value = "hello"; _ };
+
Token.Value;
+
Token.Scalar { value = "world"; _ };
+
Token.Block_end;
+
Token.Stream_end;
+
] ->
()
-
| _ ->
-
Alcotest.fail "unexpected token sequence"
+
| _ -> Alcotest.fail "unexpected token sequence"
let test_scanner_sequence () =
let scanner = Scanner.of_string "- one\n- two\n- three" in
···
let test_scanner_flow () =
let scanner = Scanner.of_string "[1, 2, 3]" in
let tokens = Scanner.to_list scanner in
-
let has_flow_start = List.exists (fun (t : Token.spanned) ->
-
match t.token with Token.Flow_sequence_start -> true | _ -> false
-
) tokens in
+
let has_flow_start =
+
List.exists
+
(fun (t : Token.spanned) ->
+
match t.token with Token.Flow_sequence_start -> true | _ -> false)
+
tokens
+
in
Alcotest.(check bool) "has flow sequence start" true has_flow_start
-
let scanner_tests = [
-
"simple mapping", `Quick, test_scanner_simple;
-
"sequence", `Quick, test_scanner_sequence;
-
"flow sequence", `Quick, test_scanner_flow;
-
]
+
let scanner_tests =
+
[
+
("simple mapping", `Quick, test_scanner_simple);
+
("sequence", `Quick, test_scanner_sequence);
+
("flow sequence", `Quick, test_scanner_flow);
+
]
(** Parser tests *)
···
let parser = Parser.of_string "key: value" in
let events = Parser.to_list parser in
Alcotest.(check bool) "has events" true (List.length events > 0);
-
let has_stream_start = List.exists (fun (e : Event.spanned) ->
-
match e.event with Event.Stream_start _ -> true | _ -> false
-
) events in
+
let has_stream_start =
+
List.exists
+
(fun (e : Event.spanned) ->
+
match e.event with Event.Stream_start _ -> true | _ -> false)
+
events
+
in
Alcotest.(check bool) "has stream start" true has_stream_start
let test_parser_sequence_events () =
let parser = Parser.of_string "- a\n- b" in
let events = Parser.to_list parser in
-
let has_seq_start = List.exists (fun (e : Event.spanned) ->
-
match e.event with Event.Sequence_start _ -> true | _ -> false
-
) events in
+
let has_seq_start =
+
List.exists
+
(fun (e : Event.spanned) ->
+
match e.event with Event.Sequence_start _ -> true | _ -> false)
+
events
+
in
Alcotest.(check bool) "has sequence start" true has_seq_start
-
let parser_tests = [
-
"parse events", `Quick, test_parser_events;
-
"sequence events", `Quick, test_parser_sequence_events;
-
]
+
let parser_tests =
+
[
+
("parse events", `Quick, test_parser_events);
+
("sequence events", `Quick, test_parser_sequence_events);
+
]
(** Value parsing tests *)
···
check_value "float" (`Float 3.14) (of_string "3.14")
let test_parse_string () =
-
check_value "plain" (`String "hello") (of_string "hello world" |> function `String s -> `String (String.sub s 0 5) | v -> v);
+
check_value "plain" (`String "hello")
+
( of_string "hello world" |> function
+
| `String s -> `String (String.sub s 0 5)
+
| v -> v );
check_value "quoted" (`String "hello") (of_string {|"hello"|})
let test_parse_sequence () =
let result = of_string "- one\n- two\n- three" in
match result with
-
| `A [_; _; _] -> ()
+
| `A [ _; _; _ ] -> ()
| _ -> Alcotest.fail "expected sequence with 3 elements"
let test_parse_mapping () =
···
|} in
let result = of_string yaml in
match result with
-
| `O [("person", `O _)] -> ()
+
| `O [ ("person", `O _) ] -> ()
| _ -> Alcotest.fail "expected nested structure"
let test_parse_flow_sequence () =
let result = of_string "[1, 2, 3]" in
match result with
-
| `A [`Float 1.0; `Float 2.0; `Float 3.0] -> ()
+
| `A [ `Float 1.0; `Float 2.0; `Float 3.0 ] -> ()
| _ -> Alcotest.fail "expected flow sequence [1, 2, 3]"
let test_parse_flow_mapping () =
let result = of_string "{a: 1, b: 2}" in
match result with
-
| `O [("a", `Float 1.0); ("b", `Float 2.0)] -> ()
+
| `O [ ("a", `Float 1.0); ("b", `Float 2.0) ] -> ()
| _ -> Alcotest.fail "expected flow mapping {a: 1, b: 2}"
let test_parse_flow_mapping_trailing_comma () =
let result = of_string "{ a: 1, }" in
match result with
-
| `O [("a", `Float 1.0)] -> ()
+
| `O [ ("a", `Float 1.0) ] -> ()
| `O pairs ->
-
Alcotest.failf "expected 1 pair but got %d pairs (trailing comma should not create empty entry)"
+
Alcotest.failf
+
"expected 1 pair but got %d pairs (trailing comma should not create \
+
empty entry)"
(List.length pairs)
| _ -> Alcotest.fail "expected flow mapping with 1 pair"
-
let value_tests = [
-
"parse null", `Quick, test_parse_null;
-
"parse bool", `Quick, test_parse_bool;
-
"parse number", `Quick, test_parse_number;
-
"parse string", `Quick, test_parse_string;
-
"parse sequence", `Quick, test_parse_sequence;
-
"parse mapping", `Quick, test_parse_mapping;
-
"parse nested", `Quick, test_parse_nested;
-
"parse flow sequence", `Quick, test_parse_flow_sequence;
-
"parse flow mapping", `Quick, test_parse_flow_mapping;
-
"flow mapping trailing comma", `Quick, test_parse_flow_mapping_trailing_comma;
-
]
+
let value_tests =
+
[
+
("parse null", `Quick, test_parse_null);
+
("parse bool", `Quick, test_parse_bool);
+
("parse number", `Quick, test_parse_number);
+
("parse string", `Quick, test_parse_string);
+
("parse sequence", `Quick, test_parse_sequence);
+
("parse mapping", `Quick, test_parse_mapping);
+
("parse nested", `Quick, test_parse_nested);
+
("parse flow sequence", `Quick, test_parse_flow_sequence);
+
("parse flow mapping", `Quick, test_parse_flow_mapping);
+
( "flow mapping trailing comma",
+
`Quick,
+
test_parse_flow_mapping_trailing_comma );
+
]
(** Emitter tests *)
···
Alcotest.(check bool) "contains null" true (String.length result > 0)
let starts_with prefix s =
-
String.length s >= String.length prefix &&
-
String.sub s 0 (String.length prefix) = prefix
+
String.length s >= String.length prefix
+
&& String.sub s 0 (String.length prefix) = prefix
let test_emit_mapping () =
-
let value = `O [("name", `String "Alice"); ("age", `Float 30.0)] in
+
let value = `O [ ("name", `String "Alice"); ("age", `Float 30.0) ] in
let result = to_string value in
let trimmed = String.trim result in
-
Alcotest.(check bool) "contains name" true (starts_with "name" trimmed || starts_with "\"name\"" trimmed)
+
Alcotest.(check bool)
+
"contains name" true
+
(starts_with "name" trimmed || starts_with "\"name\"" trimmed)
let test_roundtrip_simple () =
let yaml = "name: Alice" in
···
()
| _ -> Alcotest.fail "roundtrip failed"
-
let emitter_tests = [
-
"emit null", `Quick, test_emit_null;
-
"emit mapping", `Quick, test_emit_mapping;
-
"roundtrip simple", `Quick, test_roundtrip_simple;
-
"roundtrip sequence", `Quick, test_roundtrip_sequence;
-
]
+
let emitter_tests =
+
[
+
("emit null", `Quick, test_emit_null);
+
("emit mapping", `Quick, test_emit_mapping);
+
("roundtrip simple", `Quick, test_roundtrip_simple);
+
("roundtrip sequence", `Quick, test_roundtrip_sequence);
+
]
(** YAML-specific tests *)
···
| _ -> Alcotest.fail "expected scalar with anchor"
let test_yaml_alias () =
-
let yaml = {|
+
let yaml =
+
{|
defaults: &defaults
timeout: 30
production:
<<: *defaults
port: 8080
-
|} in
+
|}
+
in
(* Just check it parses without error *)
let _ = yaml_of_string yaml in
()
-
let yaml_tests = [
-
"yaml anchor", `Quick, test_yaml_anchor;
-
"yaml alias", `Quick, test_yaml_alias;
-
]
+
let yaml_tests =
+
[
+
("yaml anchor", `Quick, test_yaml_anchor);
+
("yaml alias", `Quick, test_yaml_alias);
+
]
(** Multiline scalar tests *)
···
|} in
let result = of_string yaml in
match result with
-
| `O [("description", `String _)] -> ()
+
| `O [ ("description", `String _) ] -> ()
| _ -> Alcotest.fail "expected mapping with literal block"
let test_folded_block () =
···
|} in
let result = of_string yaml in
match result with
-
| `O [("description", `String _)] -> ()
+
| `O [ ("description", `String _) ] -> ()
| _ -> Alcotest.fail "expected mapping with folded block"
-
let multiline_tests = [
-
"literal block", `Quick, test_literal_block;
-
"folded block", `Quick, test_folded_block;
-
]
+
let multiline_tests =
+
[
+
("literal block", `Quick, test_literal_block);
+
("folded block", `Quick, test_folded_block);
+
]
(** Error handling tests *)
···
try
let _ = of_string "key: [unclosed" in
Alcotest.fail "expected error"
-
with
-
| Yamlrw_error e ->
-
Alcotest.(check bool) "has span" true (e.span <> None)
+
with Yamlrw_error e -> Alcotest.(check bool) "has span" true (e.span <> None)
-
let error_tests = [
-
"error position", `Quick, test_error_position;
-
]
+
let error_tests = [ ("error position", `Quick, test_error_position) ]
(** Alias expansion limit tests (billion laughs protection) *)
let test_node_limit () =
(* Small bomb that would expand to 9^4 = 6561 nodes *)
-
let yaml = {|
+
let yaml =
+
{|
a: &a [1,2,3,4,5,6,7,8,9]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
-
|} in
+
|}
+
in
(* Should fail with a small node limit *)
try
let _ = of_string ~max_nodes:100 yaml in
Alcotest.fail "expected node limit error"
-
with
-
| Yamlrw_error e ->
-
(match e.Error.kind with
-
| Error.Alias_expansion_node_limit _ -> ()
-
| _ -> Alcotest.fail "expected Alias_expansion_node_limit error")
+
with Yamlrw_error e -> (
+
match e.Error.kind with
+
| Error.Alias_expansion_node_limit _ -> ()
+
| _ -> Alcotest.fail "expected Alias_expansion_node_limit error")
let test_depth_limit () =
(* Create deeply nested alias chain:
*e -> [*d,*d] -> [*c,*c] -> [*b,*b] -> [*a,*a] -> [x,y,z]
Each alias resolution increases depth by 1 *)
-
let yaml = {|
+
let yaml =
+
{|
a: &a [x, y, z]
b: &b [*a, *a]
c: &c [*b, *b]
d: &d [*c, *c]
e: &e [*d, *d]
result: *e
-
|} in
+
|}
+
in
(* Should fail with a small depth limit (depth 3 means max 3 alias hops) *)
try
let _ = of_string ~max_depth:3 yaml in
Alcotest.fail "expected depth limit error"
-
with
-
| Yamlrw_error e ->
-
(match e.Error.kind with
-
| Error.Alias_expansion_depth_limit _ -> ()
-
| _ -> Alcotest.fail ("expected Alias_expansion_depth_limit error, got: " ^ Error.kind_to_string e.Error.kind))
+
with Yamlrw_error e -> (
+
match e.Error.kind with
+
| Error.Alias_expansion_depth_limit _ -> ()
+
| _ ->
+
Alcotest.fail
+
("expected Alias_expansion_depth_limit error, got: "
+
^ Error.kind_to_string e.Error.kind))
let test_normal_aliases_work () =
(* Normal alias usage should work fine *)
-
let yaml = {|
+
let yaml =
+
{|
defaults: &defaults
timeout: 30
retries: 3
production:
<<: *defaults
port: 8080
-
|} in
+
|}
+
in
let result = of_string yaml in
-
match result with
-
| `O _ -> ()
-
| _ -> Alcotest.fail "expected mapping"
+
match result with `O _ -> () | _ -> Alcotest.fail "expected mapping"
let test_resolve_aliases_false () =
(* With resolve_aliases=false, aliases should remain unresolved *)
···
let result = yaml_of_string ~resolve_aliases:false yaml in
(* Check that alias is preserved *)
match result with
-
| `O map ->
+
| `O map -> (
let pairs = Mapping.members map in
-
(match List.assoc_opt (`Scalar (Scalar.make "b")) pairs with
-
| Some (`Alias "anchor") -> ()
-
| _ -> Alcotest.fail "expected alias to be preserved")
+
match List.assoc_opt (`Scalar (Scalar.make "b")) pairs with
+
| Some (`Alias "anchor") -> ()
+
| _ -> Alcotest.fail "expected alias to be preserved")
| _ -> Alcotest.fail "expected mapping"
-
let alias_limit_tests = [
-
"node limit", `Quick, test_node_limit;
-
"depth limit", `Quick, test_depth_limit;
-
"normal aliases work", `Quick, test_normal_aliases_work;
-
"resolve_aliases false", `Quick, test_resolve_aliases_false;
-
]
+
let alias_limit_tests =
+
[
+
("node limit", `Quick, test_node_limit);
+
("depth limit", `Quick, test_depth_limit);
+
("normal aliases work", `Quick, test_normal_aliases_work);
+
("resolve_aliases false", `Quick, test_resolve_aliases_false);
+
]
(** Bug fix regression tests
These tests verify that issues fixed in ocaml-yaml don't occur in ocaml-yamlrw *)
···
let test_parse_special_floats () =
let inf_result = of_string ".inf" in
(match inf_result with
-
| `Float f when Float.is_inf f && f > 0.0 -> ()
+
| `Float f when Float.is_infinite f && f > 0.0 -> ()
| _ -> Alcotest.fail "expected positive infinity");
let neg_inf_result = of_string "-.inf" in
(match neg_inf_result with
-
| `Float f when Float.is_inf f && f < 0.0 -> ()
+
| `Float f when Float.is_infinite f && f < 0.0 -> ()
| _ -> Alcotest.fail "expected negative infinity");
let nan_result = of_string ".nan" in
(match nan_result with
···
(** Run all tests *)
let () =
-
Alcotest.run "yamlrw" [
-
"scanner", scanner_tests;
-
"parser", parser_tests;
-
"value", value_tests;
-
"emitter", emitter_tests;
-
"yaml", yaml_tests;
-
"multiline", multiline_tests;
-
"errors", error_tests;
-
"alias_limits", alias_limit_tests;
-
"bugfix_regression", bugfix_regression_tests;
-
]
+
Alcotest.run "yamlrw"
+
[
+
("scanner", scanner_tests);
+
("parser", parser_tests);
+
("value", value_tests);
+
("emitter", emitter_tests);
+
("yaml", yaml_tests);
+
("multiline", multiline_tests);
+
("errors", error_tests);
+
("alias_limits", alias_limit_tests);
+
("bugfix_regression", bugfix_regression_tests);
+
]