My agentic slop goes here. Not intended for anyone else!

sync

+43
yaml/ocaml-yamle/TODO.md
···
+
# Yamle Implementation Progress
+
+
## Phase 1: Foundation
+
- [x] Project structure and dune files
+
- [ ] Position module - location tracking
+
- [ ] Span module - source ranges
+
- [ ] Error module - exception with position info
+
- [ ] Encoding module - UTF-8/16 detection
+
+
## Phase 2: Styles and Input
+
- [ ] Scalar_style module
+
- [ ] Layout_style module
+
- [ ] Chomping module
+
- [ ] Input module - character source abstraction
+
+
## Phase 3: Scanner (Lexer)
+
- [ ] Token module - token types
+
- [ ] Scanner module - tokenizer with lookahead
+
+
## Phase 4: Parser
+
- [ ] Event module - parser events
+
- [ ] Parser module - state machine
+
+
## Phase 5: Data Structures
+
- [ ] Value module - JSON-compatible representation
+
- [ ] Tag module - YAML tags
+
- [ ] Scalar module - scalar with metadata
+
- [ ] Sequence module - sequence with metadata
+
- [ ] Mapping module - mapping with metadata
+
- [ ] Yaml module - full YAML representation
+
- [ ] Document module - document wrapper
+
+
## Phase 6: Loader and Emitter
+
- [ ] Loader module - events to data structures
+
- [ ] Emitter module - data structures to YAML string
+
+
## Phase 7: Top-Level API
+
- [ ] Yamle module - main API
+
- [ ] Stream submodule - streaming interface
+
+
## Phase 8: Testing
+
- [ ] Unit tests for each module
+
- [ ] Integration tests with YAML test suite
+8
yaml/ocaml-yamle/bin/dune
···
+
(executable
+
(name yamlcat)
+
(public_name yamlcat)
+
(libraries yamle))
+
+
(executable
+
(name test_emit)
+
(libraries yamle))
+14
yaml/ocaml-yamle/bin/test_emit.ml
···
+
let () =
+
let yaml = {|
+
name: Alice
+
age: 30
+
hobbies:
+
- reading
+
- coding
+
|} in
+
let v = Yamle.of_string yaml in
+
print_endline "=== Using to_string (YAML output) ===";
+
print_endline (Yamle.to_string v);
+
print_endline "";
+
print_endline "=== Using pp (JSON-like) ===";
+
Format.printf "%a@." Yamle.pp v
+105
yaml/ocaml-yamle/bin/yamlcat.ml
···
+
(** yamlcat - parse and reprint YAML files *)
+
+
let usage () =
+
Printf.eprintf "Usage: %s [OPTIONS] [FILE...]\n" Sys.argv.(0);
+
Printf.eprintf "\n";
+
Printf.eprintf "Parse YAML files and reprint them.\n";
+
Printf.eprintf "If no files are given, reads from stdin.\n";
+
Printf.eprintf "\n";
+
Printf.eprintf "Options:\n";
+
Printf.eprintf " --json Output as JSON format\n";
+
Printf.eprintf " --flow Output YAML in flow style\n";
+
Printf.eprintf " --debug Output internal representation (for debugging)\n";
+
Printf.eprintf " --help Show this help message\n";
+
exit 1
+
+
type output_format = Yaml | Json | Flow | Debug
+
+
let rec json_to_string buf (v : Yamle.value) =
+
match v with
+
| `Null -> Buffer.add_string buf "null"
+
| `Bool b -> Buffer.add_string buf (if b then "true" else "false")
+
| `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)
+
| `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;
+
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;
+
Buffer.add_char buf '}'
+
+
let value_to_json v =
+
let buf = Buffer.create 256 in
+
json_to_string buf v;
+
Buffer.contents buf
+
+
let process_string ~format content =
+
try
+
match format with
+
| Yaml ->
+
let value = Yamle.of_string content in
+
print_string (Yamle.to_string value)
+
| Flow ->
+
let value = Yamle.of_string content in
+
print_string (Yamle.to_string ~layout_style:Yamle.Layout_style.Flow value)
+
| Json ->
+
let value = Yamle.of_string content in
+
print_endline (value_to_json value)
+
| Debug ->
+
let yaml = Yamle.yaml_of_string content in
+
Format.printf "%a@." Yamle.pp_yaml yaml
+
with
+
| Yamle.Yamle_error e ->
+
Printf.eprintf "Error: %s\n" (Yamle.Error.to_string e);
+
exit 1
+
+
let process_file ~format filename =
+
let content =
+
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 content
+
+
let () =
+
let files = ref [] in
+
let format = ref Yaml in
+
let show_help = ref false in
+
+
(* Parse arguments *)
+
let args = Array.to_list Sys.argv |> List.tl in
+
List.iter (fun arg ->
+
match arg with
+
| "--help" | "-h" -> show_help := true
+
| "--json" -> format := Json
+
| "--flow" -> format := Flow
+
| "--debug" -> format := Debug
+
| s when String.length s > 0 && s.[0] = '-' ->
+
Printf.eprintf "Unknown option: %s\n" s;
+
usage ()
+
| filename -> files := filename :: !files
+
) args;
+
+
if !show_help then usage ();
+
+
let files = List.rev !files in
+
+
if files = [] then
+
(* Read from stdin *)
+
process_file ~format:!format "-"
+
else
+
List.iter (process_file ~format:!format) files
+19
yaml/ocaml-yamle/dune-project
···
+
(lang dune 3.0)
+
(name yamle)
+
(version 0.1.0)
+
+
(generate_opam_files true)
+
+
(source (github ocaml/yamle))
+
(license ISC)
+
(authors "Yamle Authors")
+
(maintainers "yamle@example.com")
+
+
(package
+
(name yamle)
+
(synopsis "Pure OCaml YAML 1.2 parser and emitter")
+
(description "A pure OCaml implementation of YAML 1.2 parsing and emission, with no C dependencies.")
+
(depends
+
(ocaml (>= 4.14.0))
+
(dune (>= 3.0))
+
(alcotest :with-test)))
+26
yaml/ocaml-yamle/lib/chomping.ml
···
+
(** Block scalar chomping indicators *)
+
+
type t =
+
| 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 equal a b = a = b
+54
yaml/ocaml-yamle/lib/document.ml
···
+
(** YAML document with directives and content *)
+
+
type t = {
+
version : (int * int) option;
+
tags : (string * string) list;
+
root : Yaml.t option;
+
implicit_start : bool;
+
implicit_end : bool;
+
}
+
+
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 tags t = t.tags
+
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 -> ());
+
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;
+
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>");
+
Format.fprintf fmt "@]@,)"
+
+
let equal a b =
+
Option.equal (fun (a1, a2) (b1, b2) -> a1 = b1 && a2 = b2) a.version b.version &&
+
List.equal (fun (h1, p1) (h2, p2) -> h1 = h2 && p1 = p2) a.tags b.tags &&
+
Option.equal Yaml.equal a.root b.root &&
+
a.implicit_start = b.implicit_start &&
+
a.implicit_end = b.implicit_end
+26
yaml/ocaml-yamle/lib/dune
···
+
(library
+
(name yamle)
+
(public_name yamle)
+
(modules
+
position
+
span
+
error
+
encoding
+
scalar_style
+
layout_style
+
chomping
+
input
+
token
+
scanner
+
event
+
parser
+
value
+
tag
+
scalar
+
sequence
+
mapping
+
yaml
+
document
+
loader
+
emitter
+
yamle))
+701
yaml/ocaml-yamle/lib/emitter.ml
···
+
(** Emitter - converts YAML data structures to string output *)
+
+
type config = {
+
encoding : Encoding.t;
+
scalar_style : Scalar_style.t;
+
layout_style : Layout_style.t;
+
indent : int;
+
width : int;
+
canonical : bool;
+
}
+
+
let default_config = {
+
encoding = Encoding.Utf8;
+
scalar_style = Scalar_style.Any;
+
layout_style = 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_mapping_key of int
+
| In_block_mapping_value of int
+
| In_block_mapping_first_key of int (* first key after "- ", no indent needed *)
+
| In_flow_sequence
+
| In_flow_mapping_key
+
| In_flow_mapping_value
+
| Document_ended
+
| Stream_ended
+
+
type t = {
+
config : config;
+
buffer : Buffer.t;
+
mutable state : state;
+
mutable states : state list;
+
mutable indent : int;
+
mutable flow_level : int;
+
mutable need_separator : bool;
+
}
+
+
let create ?(config = default_config) () = {
+
config;
+
buffer = Buffer.create 1024;
+
state = Initial;
+
states = [];
+
indent = 0;
+
flow_level = 0;
+
need_separator = false;
+
}
+
+
let contents t = Buffer.contents t.buffer
+
+
let reset t =
+
Buffer.clear t.buffer;
+
t.state <- Initial;
+
t.states <- [];
+
t.indent <- 0;
+
t.flow_level <- 0;
+
t.need_separator <- false
+
+
(** Output helpers *)
+
+
let write t s = Buffer.add_string t.buffer s
+
let write_char t c = Buffer.add_char t.buffer c
+
+
let write_indent t =
+
for _ = 1 to t.indent do
+
write_char t ' '
+
done
+
+
let write_newline t =
+
write_char t '\n'
+
+
let push_state t s =
+
t.states <- t.state :: t.states;
+
t.state <- s
+
+
let pop_state t =
+
match t.states with
+
| s :: rest ->
+
t.state <- s;
+
t.states <- rest
+
| [] ->
+
t.state <- Stream_ended
+
+
(** Check if string needs quoting *)
+
let needs_quoting s =
+
if String.length s = 0 then true
+
else
+
let first = s.[0] in
+
(* Check first character *)
+
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 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
+
else
+
(* Check for characters that need quoting *)
+
try
+
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)
+
with Exit -> true
+
+
(** Check if string contains characters requiring double quotes *)
+
let needs_double_quotes s =
+
try
+
String.iter (fun c ->
+
if c = '\n' || c = '\r' || c = '\t' || c = '\\' ||
+
c < ' ' || c = '"' then
+
raise Exit
+
) s;
+
false
+
with Exit -> true
+
+
(** Write scalar with appropriate quoting *)
+
let write_scalar t ?(style = Scalar_style.Any) value =
+
let effective_style =
+
if style = Scalar_style.Any then
+
if needs_double_quotes value then Scalar_style.Double_quoted
+
else if needs_quoting value then Scalar_style.Single_quoted
+
else Scalar_style.Plain
+
else style
+
in
+
match effective_style with
+
| Scalar_style.Plain | Scalar_style.Any ->
+
write t value
+
+
| Scalar_style.Single_quoted ->
+
write_char t '\'';
+
String.iter (fun c ->
+
if c = '\'' then write t "''"
+
else write_char t c
+
) value;
+
write_char t '\''
+
+
| Scalar_style.Double_quoted ->
+
write_char t '"';
+
String.iter (fun c ->
+
match c with
+
| '"' -> write t "\\\""
+
| '\\' -> write t "\\\\"
+
| '\n' -> write t "\\n"
+
| '\r' -> write t "\\r"
+
| '\t' -> write t "\\t"
+
| c when c < ' ' -> write t (Printf.sprintf "\\x%02x" (Char.code c))
+
| c -> write_char t c
+
) value;
+
write_char t '"'
+
+
| Scalar_style.Literal ->
+
write t "|";
+
write_newline t;
+
let lines = String.split_on_char '\n' value in
+
List.iter (fun line ->
+
write_indent t;
+
write t line;
+
write_newline t
+
) lines
+
+
| Scalar_style.Folded ->
+
write t ">";
+
write_newline t;
+
let lines = String.split_on_char '\n' value in
+
List.iter (fun line ->
+
write_indent t;
+
write t line;
+
write_newline t
+
) lines
+
+
(** Write anchor if present *)
+
let write_anchor t anchor =
+
match anchor with
+
| Some name ->
+
write_char t '&';
+
write t name;
+
write_char t ' '
+
| None -> ()
+
+
(** Write tag if present and not implicit *)
+
let write_tag t ~implicit tag =
+
if not implicit then
+
match tag with
+
| Some tag_str ->
+
write_char t '!';
+
write t tag_str;
+
write_char t ' '
+
| None -> ()
+
+
(** Emit events *)
+
+
let emit t (ev : Event.t) =
+
match ev with
+
| 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 -> ());
+
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
+
+
| 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 ->
+
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
+
| _ ->
+
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 t "- ";
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write_newline t
+
| In_block_mapping_key indent ->
+
write_indent t;
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write_char t ':';
+
t.state <- In_block_mapping_value indent
+
| In_block_mapping_first_key indent ->
+
(* First key after "- ", no indent needed *)
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write_char t ':';
+
t.state <- In_block_mapping_value indent
+
| In_block_mapping_value indent ->
+
write_char t ' ';
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write_newline t;
+
t.state <- In_block_mapping_key indent
+
| _ ->
+
write_anchor t anchor;
+
write_tag t ~implicit:plain_implicit tag;
+
write_scalar t ~style value;
+
write_newline t
+
end
+
+
| Event.Sequence_start { anchor; tag; implicit; style } ->
+
let use_flow = style = Layout_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_block_sequence _ ->
+
write_indent t;
+
write t "- ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_sequence
+
end else begin
+
write_newline t;
+
push_state t (In_block_sequence t.indent);
+
t.indent <- t.indent + t.config.indent
+
end
+
| In_block_mapping_key indent ->
+
write_indent t;
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write t ":";
+
write_newline t;
+
push_state t (In_block_mapping_key indent);
+
t.indent <- t.indent + t.config.indent;
+
t.state <- In_block_sequence t.indent
+
| In_block_mapping_first_key indent ->
+
(* First key after "- " with sequence value - no indent *)
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write t ":";
+
write_newline t;
+
push_state t (In_block_mapping_key indent);
+
t.indent <- t.indent + t.config.indent;
+
t.state <- In_block_sequence t.indent
+
| In_block_mapping_value indent ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
(* 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
+
write_newline t;
+
(* Save key state to return to after nested sequence *)
+
t.state <- In_block_mapping_key indent;
+
push_state t (In_block_sequence (t.indent + t.config.indent));
+
t.indent <- t.indent + t.config.indent
+
end
+
| _ ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '[';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_sequence
+
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.flow_level <- t.flow_level - 1;
+
t.need_separator <- true;
+
pop_state 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 = Layout_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_block_sequence _ ->
+
write_indent t;
+
write t "- ";
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_key
+
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;
+
t.state <- In_block_mapping_first_key t.indent
+
end
+
| In_block_mapping_key indent ->
+
write_indent t;
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write t ":";
+
write_newline t;
+
push_state t (In_block_mapping_key indent);
+
t.indent <- t.indent + t.config.indent;
+
t.state <- In_block_mapping_key t.indent
+
| In_block_mapping_first_key indent ->
+
(* First key after "- " with mapping value - no indent *)
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
write t ":";
+
write_newline t;
+
push_state t (In_block_mapping_key indent);
+
t.indent <- t.indent + t.config.indent;
+
t.state <- In_block_mapping_key t.indent
+
| In_block_mapping_value indent ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
(* 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
+
write_newline t;
+
(* Save key state to return to after nested mapping *)
+
t.state <- In_block_mapping_key indent;
+
push_state t (In_block_mapping_key (t.indent + t.config.indent));
+
t.indent <- t.indent + t.config.indent
+
end
+
| _ ->
+
write_anchor t anchor;
+
write_tag t ~implicit tag;
+
if use_flow then begin
+
write_char t '{';
+
t.flow_level <- t.flow_level + 1;
+
t.need_separator <- false;
+
push_state t In_flow_mapping_key
+
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.flow_level <- t.flow_level - 1;
+
t.need_separator <- true;
+
pop_state t
+
end else begin
+
t.indent <- t.indent - t.config.indent;
+
pop_state t
+
end
+
+
(** High-level emission *)
+
+
let rec emit_yaml_node t (yaml : Yaml.t) =
+
match yaml with
+
| `Scalar s ->
+
emit t (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 t (Event.Alias { anchor = name })
+
+
| `A seq ->
+
emit t (Event.Sequence_start {
+
anchor = Sequence.anchor seq;
+
tag = Sequence.tag seq;
+
implicit = Sequence.implicit seq;
+
style = Sequence.style seq;
+
});
+
List.iter (emit_yaml_node t) (Sequence.members seq);
+
emit t Event.Sequence_end
+
+
| `O map ->
+
emit t (Event.Mapping_start {
+
anchor = Mapping.anchor map;
+
tag = Mapping.tag map;
+
implicit = Mapping.implicit map;
+
style = Mapping.style map;
+
});
+
List.iter (fun (k, v) ->
+
emit_yaml_node t k;
+
emit_yaml_node t v
+
) (Mapping.members map);
+
emit t Event.Mapping_end
+
+
let emit_yaml t yaml =
+
emit t (Event.Stream_start { encoding = t.config.encoding });
+
emit t (Event.Document_start { version = None; implicit = true });
+
emit_yaml_node t yaml;
+
emit t (Event.Document_end { implicit = true });
+
emit t Event.Stream_end
+
+
let rec emit_value_node t (value : Value.t) =
+
match value with
+
| `Null ->
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value = "null";
+
plain_implicit = true; quoted_implicit = false;
+
style = Scalar_style.Plain;
+
})
+
+
| `Bool b ->
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value = if b then "true" else "false";
+
plain_implicit = true; quoted_implicit = false;
+
style = Scalar_style.Plain;
+
})
+
+
| `Float f ->
+
let value =
+
match Float.classify_float f with
+
| FP_nan -> ".nan"
+
| FP_infinite -> if f > 0.0 then ".inf" else "-.inf"
+
| _ ->
+
if Float.is_integer f && Float.abs f < 1e15 then
+
Printf.sprintf "%.0f" f
+
else
+
Printf.sprintf "%g" f
+
in
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value;
+
plain_implicit = true; quoted_implicit = false;
+
style = Scalar_style.Plain;
+
})
+
+
| `String s ->
+
let style =
+
if needs_double_quotes s then Scalar_style.Double_quoted
+
else if needs_quoting s then Scalar_style.Single_quoted
+
else Scalar_style.Plain
+
in
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value = s;
+
plain_implicit = style = Scalar_style.Plain;
+
quoted_implicit = style <> Scalar_style.Plain;
+
style;
+
})
+
+
| `A items ->
+
let style =
+
if t.config.layout_style = Layout_style.Flow then Layout_style.Flow
+
else Layout_style.Block
+
in
+
emit t (Event.Sequence_start {
+
anchor = None; tag = None;
+
implicit = true;
+
style;
+
});
+
List.iter (emit_value_node t) items;
+
emit t Event.Sequence_end
+
+
| `O pairs ->
+
let style =
+
if t.config.layout_style = Layout_style.Flow then Layout_style.Flow
+
else Layout_style.Block
+
in
+
emit t (Event.Mapping_start {
+
anchor = None; tag = None;
+
implicit = true;
+
style;
+
});
+
List.iter (fun (k, v) ->
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value = k;
+
plain_implicit = not (needs_quoting k);
+
quoted_implicit = needs_quoting k;
+
style = if needs_quoting k then Scalar_style.Double_quoted else Scalar_style.Plain;
+
});
+
emit_value_node t v
+
) pairs;
+
emit t Event.Mapping_end
+
+
let emit_value t value =
+
emit t (Event.Stream_start { encoding = t.config.encoding });
+
emit t (Event.Document_start { version = None; implicit = true });
+
emit_value_node t value;
+
emit t (Event.Document_end { implicit = true });
+
emit t Event.Stream_end
+
+
let emit_document t doc =
+
emit t (Event.Document_start {
+
version = Document.version doc;
+
implicit = Document.implicit_start doc;
+
});
+
(match Document.root doc with
+
| Some yaml -> emit_yaml_node t yaml
+
| None ->
+
emit t (Event.Scalar {
+
anchor = None; tag = None;
+
value = "";
+
plain_implicit = true; quoted_implicit = false;
+
style = Scalar_style.Plain;
+
}));
+
emit t (Event.Document_end { implicit = Document.implicit_end doc })
+
+
(** Convenience functions *)
+
+
let value_to_string ?(config = default_config) value =
+
let t = create ~config () in
+
emit_value t value;
+
contents t
+
+
let yaml_to_string ?(config = default_config) yaml =
+
let t = create ~config () in
+
emit_yaml t yaml;
+
contents t
+54
yaml/ocaml-yamle/lib/encoding.ml
···
+
(** Character encoding detection and handling *)
+
+
type t =
+
| Utf8
+
| Utf16be
+
| Utf16le
+
| Utf32be
+
| Utf32le
+
+
let to_string = function
+
| Utf8 -> "UTF-8"
+
| Utf16be -> "UTF-16BE"
+
| Utf16le -> "UTF-16LE"
+
| Utf32be -> "UTF-32BE"
+
| Utf32le -> "UTF-32LE"
+
+
let pp fmt t =
+
Format.pp_print_string fmt (to_string t)
+
+
(** 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)
+
else
+
let b0 = Char.code s.[0] in
+
let b1 = if len > 1 then Char.code s.[1] else 0 in
+
let b2 = if len > 2 then Char.code s.[2] else 0 in
+
let b3 = if len > 3 then Char.code s.[3] else 0 in
+
(* Check for BOM first *)
+
if b0 = 0xEF && b1 = 0xBB && b2 = 0xBF then
+
(Utf8, 3)
+
else if b0 = 0xFE && b1 = 0xFF then
+
(Utf16be, 2)
+
else if b0 = 0xFF && b1 = 0xFE then
+
if b2 = 0x00 && b3 = 0x00 then
+
(Utf32le, 4)
+
else
+
(Utf16le, 2)
+
else if b0 = 0x00 && b1 = 0x00 && b2 = 0xFE && b3 = 0xFF then
+
(Utf32be, 4)
+
(* No BOM - detect from content pattern *)
+
else if b0 = 0x00 && b1 = 0x00 && b2 = 0x00 && b3 <> 0x00 then
+
(Utf32be, 0)
+
else if b0 <> 0x00 && b1 = 0x00 && b2 = 0x00 && b3 = 0x00 then
+
(Utf32le, 0)
+
else if b0 = 0x00 && b1 <> 0x00 then
+
(Utf16be, 0)
+
else if b0 <> 0x00 && b1 = 0x00 then
+
(Utf16le, 0)
+
else
+
(Utf8, 0)
+
+
let equal a b = a = b
+179
yaml/ocaml-yamle/lib/error.ml
···
+
(** Error handling with position information *)
+
+
(** Error classification *)
+
type kind =
+
(* Scanner errors *)
+
| Unexpected_character of char
+
| Unexpected_eof
+
| Invalid_escape_sequence of string
+
| Invalid_unicode_escape of string
+
| Invalid_hex_escape of string
+
| Invalid_tag of string
+
| Invalid_anchor of string
+
| Invalid_alias of string
+
| Unclosed_single_quote
+
| Unclosed_double_quote
+
| Unclosed_flow_sequence
+
| Unclosed_flow_mapping
+
| Invalid_indentation of int * int (** expected, got *)
+
| Tab_in_indentation
+
| Invalid_block_scalar_header of string
+
| Invalid_directive of string
+
| Invalid_yaml_version of string
+
| Invalid_tag_directive of string
+
| Reserved_directive of string
+
+
(* Parser errors *)
+
| Unexpected_token of string
+
| Expected_document_start
+
| Expected_document_end
+
| Expected_block_entry
+
| Expected_key
+
| Expected_value
+
| Expected_node
+
| Expected_scalar
+
| Expected_sequence_end
+
| Expected_mapping_end
+
| Duplicate_anchor of string
+
| Undefined_alias of string
+
| Alias_cycle of string
+
| Multiple_documents
+
| Mapping_key_too_long
+
+
(* Loader errors *)
+
| Invalid_scalar_conversion of string * string (** value, target type *)
+
| Type_mismatch of string * string (** expected, got *)
+
| Unresolved_alias of string
+
| Key_not_found of string
+
+
(* Emitter errors *)
+
| Invalid_encoding of string
+
| Scalar_contains_invalid_chars of string
+
| Anchor_not_set
+
| Invalid_state of string
+
+
(* Generic *)
+
| Custom of string
+
+
(** Full error with location *)
+
type t = {
+
kind : kind;
+
span : Span.t option;
+
context : string list;
+
source : string option;
+
}
+
+
(** The exception raised by yamle *)
+
exception Yamle_error of t
+
+
let () =
+
Printexc.register_printer (function
+
| Yamle_error e ->
+
let loc = match e.span with
+
| None -> ""
+
| Some span -> " at " ^ Span.to_string span
+
in
+
Some (Printf.sprintf "Yamle_error: %s%s"
+
(match e.kind with Custom s -> s | _ -> "error") loc)
+
| _ -> None)
+
+
let make ?span ?(context=[]) ?source kind =
+
{ kind; span; context; source }
+
+
let raise ?span ?context ?source kind =
+
Stdlib.raise (Yamle_error (make ?span ?context ?source kind))
+
+
let raise_at pos kind =
+
let span = Span.point pos in
+
raise ~span kind
+
+
let raise_span span kind =
+
raise ~span kind
+
+
let with_context ctx f =
+
try f () with
+
| Yamle_error e ->
+
Stdlib.raise (Yamle_error { e with context = ctx :: e.context })
+
+
let kind_to_string = function
+
| Unexpected_character c -> Printf.sprintf "unexpected character %C" c
+
| Unexpected_eof -> "unexpected end of input"
+
| Invalid_escape_sequence s -> Printf.sprintf "invalid escape sequence: %s" s
+
| Invalid_unicode_escape s -> Printf.sprintf "invalid unicode escape: %s" s
+
| Invalid_hex_escape s -> Printf.sprintf "invalid hex escape: %s" s
+
| 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
+
| Unclosed_single_quote -> "unclosed single quote"
+
| Unclosed_double_quote -> "unclosed double quote"
+
| Unclosed_flow_sequence -> "unclosed flow sequence '['"
+
| Unclosed_flow_mapping -> "unclosed flow mapping '{'"
+
| Invalid_indentation (expected, got) ->
+
Printf.sprintf "invalid indentation: expected %d, got %d" expected got
+
| Tab_in_indentation -> "tab character in indentation"
+
| Invalid_block_scalar_header s ->
+
Printf.sprintf "invalid block scalar header: %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
+
| Unexpected_token s -> Printf.sprintf "unexpected token: %s" s
+
| Expected_document_start -> "expected document start '---'"
+
| Expected_document_end -> "expected document end '...'"
+
| Expected_block_entry -> "expected block entry '-'"
+
| Expected_key -> "expected mapping key"
+
| Expected_value -> "expected mapping value"
+
| Expected_node -> "expected node"
+
| Expected_scalar -> "expected scalar"
+
| Expected_sequence_end -> "expected sequence end ']'"
+
| Expected_mapping_end -> "expected mapping end '}'"
+
| Duplicate_anchor s -> Printf.sprintf "duplicate anchor: &%s" s
+
| Undefined_alias s -> Printf.sprintf "undefined alias: *%s" s
+
| Alias_cycle s -> Printf.sprintf "alias cycle detected: *%s" s
+
| Multiple_documents -> "multiple documents found when single expected"
+
| Mapping_key_too_long -> "mapping key too long (max 1024 characters)"
+
| Invalid_scalar_conversion (value, typ) ->
+
Printf.sprintf "cannot convert %S to %s" value typ
+
| Type_mismatch (expected, got) ->
+
Printf.sprintf "type mismatch: expected %s, got %s" expected got
+
| Unresolved_alias s -> Printf.sprintf "unresolved alias: *%s" s
+
| Key_not_found s -> Printf.sprintf "key not found: %s" s
+
| Invalid_encoding s -> Printf.sprintf "invalid encoding: %s" s
+
| Scalar_contains_invalid_chars s ->
+
Printf.sprintf "scalar contains invalid characters: %s" s
+
| Anchor_not_set -> "anchor not set"
+
| Invalid_state s -> Printf.sprintf "invalid state: %s" s
+
| Custom s -> s
+
+
let to_string t =
+
let loc = match t.span with
+
| None -> ""
+
| Some span -> " at " ^ Span.to_string span
+
in
+
let ctx = match t.context with
+
| [] -> ""
+
| ctxs -> " (in " ^ String.concat " > " (List.rev ctxs) ^ ")"
+
in
+
kind_to_string t.kind ^ loc ^ ctx
+
+
let pp fmt t =
+
Format.fprintf fmt "Yamle error: %s" (to_string 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 pp_with_source ~source fmt t =
+
pp fmt t;
+
match t.span with
+
| None -> ()
+
| 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
+77
yaml/ocaml-yamle/lib/event.ml
···
+
(** YAML parser events *)
+
+
type t =
+
| Stream_start of { encoding : Encoding.t }
+
| Stream_end
+
| Document_start of {
+
version : (int * int) option;
+
implicit : bool;
+
}
+
| Document_end of { implicit : bool }
+
| Alias of { anchor : string }
+
| Scalar of {
+
anchor : string option;
+
tag : string option;
+
value : string;
+
plain_implicit : bool;
+
quoted_implicit : bool;
+
style : Scalar_style.t;
+
}
+
| Sequence_start of {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
}
+
| Sequence_end
+
| Mapping_start of {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
}
+
| Mapping_end
+
+
type spanned = {
+
event : t;
+
span : Span.t;
+
}
+
+
let pp fmt = function
+
| Stream_start { encoding } ->
+
Format.fprintf fmt "stream-start(%a)" Encoding.pp encoding
+
| Stream_end ->
+
Format.fprintf fmt "stream-end"
+
| Document_start { version; implicit } ->
+
Format.fprintf fmt "document-start(version=%s, implicit=%b)"
+
(match version with None -> "none" | Some (maj, min) -> Printf.sprintf "%d.%d" maj min)
+
implicit
+
| Document_end { implicit } ->
+
Format.fprintf fmt "document-end(implicit=%b)" implicit
+
| 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)"
+
(Option.value anchor ~default:"none")
+
(Option.value tag ~default:"none")
+
Scalar_style.pp style
+
value
+
| Sequence_start { anchor; tag; implicit; style } ->
+
Format.fprintf fmt "sequence-start(anchor=%s, tag=%s, implicit=%b, style=%a)"
+
(Option.value anchor ~default:"none")
+
(Option.value tag ~default:"none")
+
implicit
+
Layout_style.pp style
+
| 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)"
+
(Option.value anchor ~default:"none")
+
(Option.value tag ~default:"none")
+
implicit
+
Layout_style.pp style
+
| Mapping_end ->
+
Format.fprintf fmt "mapping-end"
+
+
let pp_spanned fmt { event; span } =
+
Format.fprintf fmt "%a at %a" pp event Span.pp span
+146
yaml/ocaml-yamle/lib/input.ml
···
+
(** Character input source with lookahead *)
+
+
type t = {
+
source : string;
+
mutable pos : int; (** Current byte position *)
+
mutable position : Position.t; (** Line/column tracking *)
+
length : int;
+
}
+
+
let of_string source =
+
let encoding, bom_len = Encoding.detect source in
+
(* For now, we only support UTF-8. Skip BOM if present. *)
+
ignore encoding;
+
{
+
source;
+
pos = bom_len;
+
position = Position.initial;
+
length = String.length source;
+
}
+
+
let position t = t.position
+
+
let is_eof t = t.pos >= t.length
+
+
let peek t =
+
if t.pos >= t.length then None
+
else Some t.source.[t.pos]
+
+
let peek_exn t =
+
if t.pos >= t.length then
+
Error.raise_at t.position Unexpected_eof
+
else
+
t.source.[t.pos]
+
+
let peek_nth t n =
+
let idx = t.pos + n in
+
if idx >= t.length then None
+
else Some t.source.[idx]
+
+
let peek_string t n =
+
if t.pos + n > t.length then
+
String.sub t.source t.pos (t.length - t.pos)
+
else
+
String.sub t.source t.pos n
+
+
let next t =
+
if t.pos >= t.length then None
+
else begin
+
let c = t.source.[t.pos] in
+
t.pos <- t.pos + 1;
+
t.position <- Position.advance_char c t.position;
+
Some c
+
end
+
+
let next_exn t =
+
match next t with
+
| Some c -> c
+
| None -> Error.raise_at t.position Unexpected_eof
+
+
let skip t n =
+
for _ = 1 to n do
+
ignore (next t)
+
done
+
+
let skip_while t pred =
+
while not (is_eof t) && pred (Option.get (peek t)) do
+
ignore (next t)
+
done
+
+
(** Character classification *)
+
+
let is_break c = c = '\n' || c = '\r'
+
+
let is_blank c = c = ' ' || c = '\t'
+
+
let is_whitespace c = is_break c || is_blank c
+
+
let is_digit c = c >= '0' && c <= '9'
+
+
let is_hex c =
+
(c >= '0' && c <= '9') ||
+
(c >= 'a' && c <= 'f') ||
+
(c >= 'A' && c <= 'F')
+
+
let is_alpha c =
+
(c >= 'a' && c <= 'z') ||
+
(c >= 'A' && c <= 'Z')
+
+
let is_alnum c = is_alpha c || is_digit c
+
+
(** YAML indicator characters *)
+
let is_indicator c =
+
match c with
+
| '-' | '?' | ':' | ',' | '[' | ']' | '{' | '}'
+
| '#' | '&' | '*' | '!' | '|' | '>' | '\'' | '"'
+
| '%' | '@' | '`' -> true
+
| _ -> false
+
+
(** Characters that cannot start a plain scalar *)
+
let is_flow_indicator c =
+
match c with
+
| ',' | '[' | ']' | '{' | '}' -> true
+
| _ -> false
+
+
(** Check if next char satisfies predicate *)
+
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
+
let next_is_whitespace t = next_is is_whitespace t
+
let next_is_digit t = next_is is_digit t
+
let next_is_hex t = next_is is_hex t
+
let next_is_alpha t = next_is is_alpha t
+
let next_is_indicator t = next_is is_indicator t
+
+
(** Check if at document boundary (--- or ...) *)
+
let at_document_boundary t =
+
if t.position.column <> 1 then false
+
else
+
let s = peek_string t 4 in
+
let prefix = String.sub s 0 (min 3 (String.length s)) in
+
(prefix = "---" || prefix = "...") &&
+
(String.length s < 4 || is_whitespace s.[3] || String.length s = 3)
+
+
(** Consume line break, handling \r\n as single break *)
+
let consume_break t =
+
match peek t with
+
| Some '\r' ->
+
ignore (next t);
+
(match peek t with
+
| Some '\n' -> ignore (next t)
+
| _ -> ())
+
| Some '\n' ->
+
ignore (next t)
+
| _ -> ()
+
+
(** Get remaining content from current position *)
+
let remaining t =
+
if t.pos >= t.length then ""
+
else String.sub t.source t.pos (t.length - t.pos)
+
+
(** Mark current position for span creation *)
+
let mark t = t.position
+24
yaml/ocaml-yamle/lib/layout_style.ml
···
+
(** Collection layout styles *)
+
+
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 equal a b = a = b
+
+
let compare a b =
+
let to_int = function
+
| Any -> 0
+
| Block -> 1
+
| Flow -> 2
+
in
+
Int.compare (to_int a) (to_int b)
+243
yaml/ocaml-yamle/lib/loader.ml
···
+
(** Loader - converts parser events to YAML data structures *)
+
+
(** Stack frame for building nested structures *)
+
type frame =
+
| Sequence_frame of {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
items : Yaml.t list;
+
}
+
| Mapping_frame of {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
pairs : (Yaml.t * Yaml.t) list;
+
pending_key : Yaml.t option;
+
}
+
+
type state = {
+
mutable stack : frame list;
+
mutable current : Yaml.t option;
+
mutable documents : Document.t list;
+
mutable doc_version : (int * int) option;
+
mutable doc_implicit_start : bool;
+
}
+
+
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
+
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
+
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
+
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.Mapping_start { anchor; tag; implicit; style } ->
+
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"))
+
+
(** Add a node to current context *)
+
and add_node state node =
+
match state.stack with
+
| [] ->
+
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)
+
+
(** Load single document as Value *)
+
let value_of_string s =
+
let parser = Parser.of_string s in
+
let state = create_state () in
+
Parser.iter (process_event state) parser;
+
match state.documents with
+
| [] -> `Null
+
| [doc] ->
+
(match Document.root doc with
+
| None -> `Null
+
| Some yaml -> Yaml.to_value yaml)
+
| _ -> Error.raise Multiple_documents
+
+
(** Load single document as Yaml *)
+
let yaml_of_string s =
+
let parser = Parser.of_string s in
+
let state = create_state () in
+
Parser.iter (process_event state) parser;
+
match state.documents with
+
| [] -> `Scalar (Scalar.make "")
+
| [doc] ->
+
(match Document.root doc with
+
| None -> `Scalar (Scalar.make "")
+
| Some yaml -> yaml)
+
| _ -> Error.raise Multiple_documents
+
+
(** Load all documents *)
+
let documents_of_string s =
+
let parser = Parser.of_string s in
+
let state = create_state () in
+
Parser.iter (process_event state) parser;
+
List.rev state.documents
+
+
(** Load single Value from parser *)
+
let load_value parser =
+
let state = create_state () in
+
let rec loop () =
+
match Parser.next parser with
+
| None -> None
+
| Some ev ->
+
process_event state ev;
+
match ev.event with
+
| Event.Document_end _ ->
+
(match state.documents with
+
| doc :: _ ->
+
state.documents <- [];
+
Some (match Document.root doc with
+
| None -> `Null
+
| Some yaml -> Yaml.to_value yaml)
+
| [] -> None)
+
| Event.Stream_end -> None
+
| _ -> loop ()
+
in
+
loop ()
+
+
(** Load single Yaml from parser *)
+
let load_yaml parser =
+
let state = create_state () in
+
let rec loop () =
+
match Parser.next parser with
+
| None -> None
+
| Some ev ->
+
process_event state ev;
+
match ev.event with
+
| Event.Document_end _ ->
+
(match state.documents with
+
| doc :: _ ->
+
state.documents <- [];
+
Some (match Document.root doc with
+
| None -> `Scalar (Scalar.make "")
+
| Some yaml -> yaml)
+
| [] -> None)
+
| Event.Stream_end -> None
+
| _ -> loop ()
+
in
+
loop ()
+
+
(** Load single Document from parser *)
+
let load_document parser =
+
let state = create_state () in
+
let rec loop () =
+
match Parser.next parser with
+
| None -> None
+
| Some ev ->
+
process_event state ev;
+
match ev.event with
+
| Event.Document_end _ ->
+
(match state.documents with
+
| doc :: _ ->
+
state.documents <- [];
+
Some doc
+
| [] -> None)
+
| Event.Stream_end -> None
+
| _ -> loop ()
+
in
+
loop ()
+
+
(** Iterate over documents *)
+
let iter_documents f parser =
+
let rec loop () =
+
match load_document parser with
+
| None -> ()
+
| 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)
+
in
+
loop init
+92
yaml/ocaml-yamle/lib/mapping.ml
···
+
(** YAML mapping (object) values with metadata *)
+
+
type ('k, 'v) t = {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
members : ('k * 'v) list;
+
}
+
+
let make
+
?(anchor : string option)
+
?(tag : string option)
+
?(implicit = true)
+
?(style = Layout_style.Any)
+
members =
+
{ anchor; tag; implicit; style; members }
+
+
let members t = t.members
+
let anchor t = t.anchor
+
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 length t = List.length t.members
+
+
let is_empty t = t.members = []
+
+
let find pred t =
+
match List.find_opt (fun (k, _) -> pred k) t.members with
+
| Some (_, v) -> Some v
+
| None -> None
+
+
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 =
+
Format.fprintf fmt "@[<hv 2>mapping(@,";
+
(match t.anchor with
+
| Some a -> Format.fprintf fmt "anchor=%s,@ " a
+
| None -> ());
+
(match t.tag with
+
| Some tag -> Format.fprintf fmt "tag=%s,@ " tag
+
| None -> ());
+
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;
+
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
+
+
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
+711
yaml/ocaml-yamle/lib/parser.ml
···
+
(** YAML parser - converts tokens to semantic events via state machine *)
+
+
(** Parser states *)
+
type state =
+
| Stream_start
+
| Implicit_document_start
+
| Document_start
+
| Document_content
+
| Document_end
+
| Block_node
+
| Block_node_or_indentless_sequence
+
| Flow_node
+
| Block_sequence_first_entry
+
| Block_sequence_entry
+
| Indentless_sequence_entry
+
| Block_mapping_first_key
+
| Block_mapping_key
+
| Block_mapping_value
+
| Flow_sequence_first_entry
+
| Flow_sequence_entry
+
| Flow_sequence_entry_mapping_key
+
| Flow_sequence_entry_mapping_value
+
| Flow_sequence_entry_mapping_end
+
| Flow_mapping_first_key
+
| Flow_mapping_key
+
| Flow_mapping_value
+
| Flow_mapping_empty_value
+
| End
+
+
type t = {
+
scanner : Scanner.t;
+
mutable state : state;
+
mutable states : state list; (** State stack *)
+
mutable marks : Span.t list; (** Mark stack for span tracking *)
+
mutable version : (int * int) option;
+
mutable tag_directives : (string * string) list;
+
mutable current_token : Token.spanned option;
+
mutable finished : bool;
+
}
+
+
let create scanner = {
+
scanner;
+
state = Stream_start;
+
states = [];
+
marks = [];
+
version = None;
+
tag_directives = [
+
("!", "!");
+
("!!", "tag:yaml.org,2002:");
+
];
+
current_token = None;
+
finished = false;
+
}
+
+
let of_string s = create (Scanner.of_string s)
+
+
(** Get current token, fetching if needed *)
+
let current_token t =
+
match t.current_token with
+
| Some tok -> tok
+
| None ->
+
let tok = Scanner.next t.scanner in
+
t.current_token <- tok;
+
match tok with
+
| Some tok -> tok
+
| None -> Error.raise Unexpected_eof
+
+
(** Peek at current token *)
+
let peek_token t =
+
match t.current_token with
+
| Some _ -> t.current_token
+
| None ->
+
t.current_token <- Scanner.next t.scanner;
+
t.current_token
+
+
(** Skip current token *)
+
let skip_token t =
+
t.current_token <- None
+
+
(** Check if current token matches *)
+
let check t pred =
+
match peek_token t with
+
| Some tok -> pred tok.token
+
| None -> false
+
+
(** Check for specific token *)
+
let check_token t token_match =
+
check t token_match
+
+
(** Push state onto stack *)
+
let push_state t s =
+
t.states <- s :: t.states
+
+
(** Pop state from stack *)
+
let pop_state t =
+
match t.states with
+
| s :: rest ->
+
t.states <- rest;
+
s
+
| [] -> End
+
+
(** Resolve a tag *)
+
let resolve_tag t ~handle ~suffix =
+
match List.assoc_opt handle t.tag_directives with
+
| Some prefix -> prefix ^ suffix
+
| None when handle = "!" -> "!" ^ suffix
+
| None -> Error.raise (Invalid_tag (handle ^ suffix))
+
+
(** Process directives at document start *)
+
let process_directives t =
+
t.version <- None;
+
t.tag_directives <- [("!", "!"); ("!!", "tag:yaml.org,2002:")];
+
+
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");
+
t.version <- Some (major, minor)
+
| Token.Tag_directive { handle; prefix } ->
+
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
+
| _ -> ()
+
done
+
+
(** Parse anchor and/or tag properties *)
+
let parse_properties t =
+
let anchor = ref None in
+
let tag = ref None in
+
+
while check t (function
+
| Token.Anchor _ | Token.Tag _ -> true
+
| _ -> false)
+
do
+
let tok = current_token t in
+
skip_token t;
+
match tok.token with
+
| Token.Anchor name ->
+
if !anchor <> None then
+
Error.raise_span tok.span (Duplicate_anchor name);
+
anchor := Some name
+
| Token.Tag { handle; suffix } ->
+
if !tag <> None then
+
Error.raise_span tok.span (Invalid_tag "duplicate tag");
+
let resolved =
+
if handle = "" && suffix = "" then None
+
else if handle = "!" && suffix = "" then Some "!"
+
else Some (resolve_tag t ~handle ~suffix)
+
in
+
tag := resolved
+
| _ -> ()
+
done;
+
(!anchor, !tag)
+
+
(** Empty scalar event *)
+
let empty_scalar_event ~anchor ~tag span =
+
Event.Scalar {
+
anchor;
+
tag;
+
value = "";
+
plain_implicit = tag = None;
+
quoted_implicit = false;
+
style = Scalar_style.Plain;
+
}, span
+
+
(** Parse stream start *)
+
let parse_stream_start t =
+
let tok = current_token t in
+
skip_token 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")
+
+
(** Parse document start (implicit or explicit) *)
+
let parse_document_start t ~implicit =
+
process_directives t;
+
+
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
+
end;
+
+
let span = match peek_token t with
+
| Some tok -> tok.span
+
| None -> Span.point Position.initial
+
in
+
+
push_state t Document_end;
+
t.state <- Document_content;
+
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
+
| Some tok -> tok.span
+
| None -> Span.point Position.initial
+
in
+
+
if not implicit then skip_token t;
+
+
t.state <- Implicit_document_start;
+
Event.Document_end { implicit }, span
+
+
(** Parse node in various contexts *)
+
let parse_node t ~block ~indentless =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Alias name ->
+
skip_token t;
+
t.state <- pop_state t;
+
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 = Layout_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 = Layout_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 = Layout_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 = Layout_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 = Layout_style.Flow;
+
}, tok.span
+
+
| Token.Scalar { style; value } ->
+
skip_token t;
+
t.state <- pop_state t;
+
let plain_implicit = tag = None && style = Scalar_style.Plain in
+
let quoted_implicit = tag = None && style <> Scalar_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 = Layout_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 = Layout_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 = Layout_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 = Layout_style.Flow;
+
}, tok.span
+
+
| Token.Block_entry when indentless ->
+
t.state <- Indentless_sequence_entry;
+
Event.Sequence_start {
+
anchor = None; tag = None;
+
implicit = true;
+
style = Layout_style.Block;
+
}, tok.span
+
+
| Token.Scalar { style; value } ->
+
skip_token t;
+
t.state <- pop_state t;
+
let plain_implicit = style = Scalar_style.Plain in
+
let quoted_implicit = style <> Scalar_style.Plain in
+
Event.Scalar {
+
anchor = None; tag = None; value;
+
plain_implicit; quoted_implicit; style;
+
}, tok.span
+
+
| _ ->
+
(* Empty node *)
+
t.state <- pop_state t;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
+
(** Parse block sequence entry *)
+
let parse_block_sequence_entry t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Block_entry ->
+
skip_token t;
+
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
+
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
+
+
(** Parse block mapping key *)
+
let parse_block_mapping_key t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Key ->
+
skip_token t;
+
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
+
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
+
+
(** Parse block mapping value *)
+
let parse_block_mapping_value t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Value ->
+
skip_token t;
+
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
+
push_state t Block_mapping_key;
+
parse_node t ~block:true ~indentless:true
+
end
+
| _ ->
+
(* Implicit empty value *)
+
t.state <- Block_mapping_key;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
+
(** Parse indentless sequence entry *)
+
let parse_indentless_sequence_entry t =
+
let tok = current_token t in
+
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)
+
then begin
+
t.state <- Indentless_sequence_entry;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
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
+
+
(** Parse flow sequence *)
+
let rec parse_flow_sequence_entry t ~first =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Flow_sequence_end ->
+
skip_token t;
+
t.state <- pop_state t;
+
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
+
+
and parse_flow_sequence_entry_internal t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Flow_sequence_end ->
+
t.state <- Flow_sequence_entry;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
| Token.Key ->
+
skip_token t;
+
push_state t Flow_sequence_entry_mapping_end;
+
t.state <- Flow_sequence_entry_mapping_key;
+
Event.Mapping_start {
+
anchor = None; tag = None;
+
implicit = true;
+
style = Layout_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)
+
then begin
+
t.state <- Flow_sequence_entry_mapping_value;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
end else begin
+
push_state t Flow_sequence_entry_mapping_value;
+
parse_node t ~block:false ~indentless:false
+
end
+
+
let parse_flow_sequence_entry_mapping_value t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Value ->
+
skip_token t;
+
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
+
push_state t Flow_sequence_entry_mapping_end;
+
parse_node t ~block:false ~indentless:false
+
end
+
| _ ->
+
t.state <- Flow_sequence_entry_mapping_end;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
+
let parse_flow_sequence_entry_mapping_end t =
+
let tok = current_token t in
+
t.state <- Flow_sequence_entry;
+
Event.Mapping_end, tok.span
+
+
(** Parse flow mapping *)
+
let rec parse_flow_mapping_key t ~first =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Flow_mapping_end ->
+
skip_token t;
+
t.state <- pop_state t;
+
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
+
+
and parse_flow_mapping_key_internal t =
+
let tok = current_token t in
+
match tok.token with
+
| Token.Flow_mapping_end ->
+
t.state <- Flow_mapping_key;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
| Token.Key ->
+
skip_token t;
+
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
+
push_state t Flow_mapping_value;
+
parse_node t ~block:false ~indentless:false
+
end
+
| _ ->
+
push_state t Flow_mapping_value;
+
parse_node t ~block:false ~indentless:false
+
+
let parse_flow_mapping_value t ~empty =
+
let tok = current_token t in
+
if empty then begin
+
t.state <- Flow_mapping_key;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
end else
+
match tok.token with
+
| Token.Value ->
+
skip_token t;
+
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
+
push_state t Flow_mapping_key;
+
parse_node t ~block:false ~indentless:false
+
end
+
| _ ->
+
t.state <- Flow_mapping_key;
+
empty_scalar_event ~anchor:None ~tag:None tok.span
+
+
(** Main state machine dispatcher *)
+
let parse t =
+
match t.state with
+
| Stream_start ->
+
parse_stream_start t
+
+
| Implicit_document_start ->
+
if check t (function
+
| Token.Version_directive _ | Token.Tag_directive _
+
| Token.Document_start | Token.Stream_end -> true
+
| _ -> false)
+
then begin
+
if check t (function Token.Stream_end -> true | _ -> false) then begin
+
let tok = current_token t in
+
skip_token t;
+
t.state <- End;
+
t.finished <- true;
+
Event.Stream_end, tok.span
+
end else begin
+
parse_document_start t ~implicit:false
+
end
+
end else
+
parse_document_start t ~implicit:true
+
+
| Document_start ->
+
parse_document_start t ~implicit:false
+
+
| Document_content ->
+
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
+
parse_node t ~block:true ~indentless:false
+
+
| Document_end ->
+
parse_document_end t
+
+
| Block_node ->
+
parse_node t ~block:true ~indentless:false
+
+
| Block_node_or_indentless_sequence ->
+
parse_node t ~block:true ~indentless:true
+
+
| Flow_node ->
+
parse_node t ~block:false ~indentless:false
+
+
| 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_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
+
+
| 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_mapping_empty_value ->
+
parse_flow_mapping_value t ~empty:true
+
+
| End ->
+
let span = Span.point Position.initial in
+
t.finished <- true;
+
Event.Stream_end, span
+
+
(** Get next event *)
+
let next t =
+
if t.finished then None
+
else begin
+
let event, span = parse t in
+
Some { Event.event; span }
+
end
+
+
(** Peek at next event *)
+
let peek t =
+
(* Parser is not easily peekable without full state save/restore *)
+
(* For now, we don't support peek - could add caching if needed *)
+
if t.finished then None
+
else
+
(* Just call next and the caller will have to deal with it *)
+
next t
+
+
(** Iterate over all events *)
+
let iter f t =
+
let rec loop () =
+
match next t with
+
| None -> ()
+
| 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)
+
in
+
loop init
+
+
(** Convert to list *)
+
let to_list t =
+
fold (fun acc ev -> ev :: acc) [] t |> List.rev
+42
yaml/ocaml-yamle/lib/position.ml
···
+
(** Position tracking for source locations *)
+
+
type t = {
+
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_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 }
+
+
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
+61
yaml/ocaml-yamle/lib/scalar.ml
···
+
(** YAML scalar values with metadata *)
+
+
type t = {
+
anchor : string option;
+
tag : string option;
+
value : string;
+
plain_implicit : bool;
+
quoted_implicit : bool;
+
style : Scalar_style.t;
+
}
+
+
let make
+
?(anchor : string option)
+
?(tag : string option)
+
?(plain_implicit = true)
+
?(quoted_implicit = false)
+
?(style = Scalar_style.Plain)
+
value =
+
{ anchor; tag; value; plain_implicit; quoted_implicit; style }
+
+
let value t = t.value
+
let anchor t = t.anchor
+
let tag t = t.tag
+
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 }
+
+
let pp fmt t =
+
Format.fprintf fmt "scalar(%S" t.value;
+
(match t.anchor with
+
| Some a -> Format.fprintf fmt ", anchor=%s" a
+
| None -> ());
+
(match t.tag with
+
| Some tag -> Format.fprintf fmt ", tag=%s" tag
+
| None -> ());
+
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
+
+
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
+33
yaml/ocaml-yamle/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 *)
+
+
let to_string = function
+
| Any -> "any"
+
| Plain -> "plain"
+
| Single_quoted -> "single-quoted"
+
| Double_quoted -> "double-quoted"
+
| Literal -> "literal"
+
| Folded -> "folded"
+
+
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
+
| Plain -> 1
+
| Single_quoted -> 2
+
| Double_quoted -> 3
+
| Literal -> 4
+
| Folded -> 5
+
in
+
Int.compare (to_int a) (to_int b)
+1046
yaml/ocaml-yamle/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;
+
}
+
+
(** Indent level tracking *)
+
type indent = {
+
indent : int;
+
needs_block_end : bool;
+
sequence : bool; (** true if this is a sequence indent *)
+
}
+
+
type t = {
+
input : Input.t;
+
mutable tokens : Token.spanned Queue.t;
+
mutable token_number : int;
+
mutable tokens_taken : int;
+
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 simple_keys : simple_key option list; (** Per flow-level simple key tracking *)
+
mutable allow_simple_key : bool;
+
}
+
+
let create input =
+
{
+
input;
+
tokens = Queue.create ();
+
token_number = 0;
+
tokens_taken = 0;
+
stream_started = false;
+
stream_ended = false;
+
indent_stack = [];
+
flow_level = 0;
+
simple_keys = [None]; (* One entry for the base level *)
+
allow_simple_key = true;
+
}
+
+
let of_string s = create (Input.of_string s)
+
+
let position t = Input.position t.input
+
+
(** Add a token to the queue *)
+
let emit t span token =
+
Queue.add { Token.token; span } t.tokens;
+
t.token_number <- t.token_number + 1
+
+
(** Get current column (1-indexed) *)
+
let column t = (Input.position t.input).column
+
+
(** Get current indent level *)
+
let current_indent t =
+
match t.indent_stack with
+
| [] -> 0
+
| { indent; _ } :: _ -> indent
+
+
(** Skip whitespace and comments, return true if at newline *)
+
let rec skip_to_next_token t =
+
(* Skip blanks *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
(* Skip comment *)
+
if Input.next_is (( = ) '#') t.input then begin
+
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
ignore (Input.next t.input)
+
done
+
end;
+
(* Skip line break in block context *)
+
if t.flow_level = 0 && Input.next_is_break t.input then begin
+
Input.consume_break t.input;
+
t.allow_simple_key <- true;
+
skip_to_next_token t
+
end
+
else if t.flow_level > 0 && Input.next_is_whitespace t.input then begin
+
ignore (Input.next t.input);
+
skip_to_next_token t
+
end
+
+
(** Roll the indentation level *)
+
let roll_indent t col ~sequence =
+
if t.flow_level = 0 && col > current_indent t then begin
+
t.indent_stack <- { indent = col; needs_block_end = true; sequence } :: t.indent_stack;
+
true
+
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
+
do
+
match t.indent_stack with
+
| { indent = _; needs_block_end = true; _ } :: rest ->
+
let pos = Input.position t.input in
+
let span = Span.point pos in
+
emit t span Token.Block_end;
+
t.indent_stack <- rest
+
| _ -> ()
+
done
+
+
(** Save a potential simple key *)
+
let save_simple_key t =
+
if t.allow_simple_key then begin
+
(* A simple key is required only if we're in a block context,
+
at the current indentation level, AND we have an active indent *)
+
let required = t.flow_level = 0 &&
+
t.indent_stack <> [] &&
+
current_indent t = column t - 1 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
+
| _ :: rest -> Some sk :: rest
+
| [] -> [Some sk]
+
)
+
end
+
+
(** Remove simple key at current level *)
+
let remove_simple_key t =
+
match t.simple_keys with
+
| Some sk :: _rest when sk.sk_required ->
+
Error.raise_at sk.sk_position Expected_key
+
| _ :: rest -> t.simple_keys <- None :: rest
+
| [] -> ()
+
+
(** 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
+
+
(** Read anchor or alias name *)
+
let scan_anchor_alias t =
+
let start = Input.mark t.input in
+
let buf = Buffer.create 16 in
+
while
+
match Input.peek t.input with
+
| Some c when Input.is_alnum c || c = '_' || c = '-' ->
+
Buffer.add_char buf c;
+
ignore (Input.next t.input);
+
true
+
| _ -> false
+
do () done;
+
let name = Buffer.contents buf in
+
if String.length name = 0 then
+
Error.raise_at start (Invalid_anchor "empty anchor name");
+
(name, Span.make ~start ~stop:(Input.mark t.input))
+
+
(** Scan tag handle *)
+
let scan_tag_handle t =
+
let start = Input.mark t.input in
+
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 '!'"));
+
(* Read word chars *)
+
while
+
match Input.peek t.input with
+
| Some c when Input.is_alnum c || c = '-' ->
+
Buffer.add_char buf c;
+
ignore (Input.next t.input);
+
true
+
| _ -> false
+
do () done;
+
(* Check for secondary ! *)
+
(match Input.peek t.input with
+
| Some '!' ->
+
Buffer.add_char buf '!';
+
ignore (Input.next t.input)
+
| _ -> ());
+
Buffer.contents buf
+
+
(** Scan tag suffix (after handle) *)
+
let scan_tag_suffix t =
+
let buf = Buffer.create 32 in
+
while
+
match Input.peek t.input with
+
| 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;
+
Buffer.contents buf
+
+
(** Scan a tag *)
+
let scan_tag t =
+
let start = Input.mark t.input in
+
ignore (Input.next t.input); (* consume ! *)
+
let handle, suffix =
+
match Input.peek t.input with
+
| Some '<' ->
+
(* Verbatim tag: !<...> *)
+
ignore (Input.next t.input);
+
let buf = Buffer.create 32 in
+
while
+
match Input.peek t.input with
+
| Some '>' -> false
+
| Some c ->
+
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 > *)
+
("!", Buffer.contents buf)
+
| Some c when Input.is_whitespace c || Input.is_flow_indicator c ->
+
(* Non-specific tag: ! *)
+
("!", "")
+
| Some '!' ->
+
(* Secondary handle *)
+
let handle = scan_tag_handle t in
+
let suffix = scan_tag_suffix t in
+
(handle, suffix)
+
| _ ->
+
(* Primary handle or just suffix *)
+
let first_part = scan_tag_suffix t in
+
if String.length first_part > 0 && first_part.[String.length first_part - 1] = '!' then
+
let suffix = scan_tag_suffix t in
+
(first_part, suffix)
+
else
+
("!", first_part)
+
in
+
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 *)
+
let buf = Buffer.create 64 in
+
let rec loop () =
+
match Input.peek t.input with
+
| None -> Error.raise_at start Unclosed_single_quote
+
| Some '\'' ->
+
ignore (Input.next t.input);
+
(* Check for escaped quote ('') *)
+
(match Input.peek t.input with
+
| Some '\'' ->
+
Buffer.add_char buf '\'';
+
ignore (Input.next t.input);
+
loop ()
+
| _ -> ())
+
| Some '\n' | Some '\r' ->
+
Input.consume_break t.input;
+
(* Fold line break to space unless at start of content *)
+
if Buffer.length buf > 0 then
+
Buffer.add_char buf ' ';
+
(* Skip leading whitespace on next line *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
loop ()
+
| Some c ->
+
Buffer.add_char buf c;
+
ignore (Input.next t.input);
+
loop ()
+
in
+
loop ();
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
(Buffer.contents buf, span)
+
+
(** Decode hex escape of given length *)
+
let decode_hex t len =
+
let start = Input.mark t.input in
+
let buf = Buffer.create len in
+
for _ = 1 to len do
+
match Input.peek t.input with
+
| 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))
+
done;
+
let code = int_of_string ("0x" ^ Buffer.contents buf) in
+
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
+
String.init 2 (fun i -> Char.chr (if i = 0 then b1 else b2))
+
else if code <= 0xFFFF then
+
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))
+
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))
+
+
(** 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 *)
+
let buf = Buffer.create 64 in
+
let rec loop () =
+
match Input.peek t.input with
+
| None -> Error.raise_at start Unclosed_double_quote
+
| Some '"' ->
+
ignore (Input.next t.input)
+
| Some '\\' ->
+
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 *)
+
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' ->
+
Input.consume_break t.input;
+
(* Fold to space *)
+
Buffer.add_char buf ' ';
+
(* Skip leading whitespace *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
loop ()
+
| Some c ->
+
Buffer.add_char buf c;
+
ignore (Input.next t.input);
+
loop ()
+
in
+
loop ();
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
(Buffer.contents buf, span)
+
+
(** 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)
+
| '#' ->
+
(* # is OK if not preceded by whitespace (checked at call site) *)
+
false
+
| c when in_flow && Input.is_flow_indicator c -> false
+
| _ when Input.is_break c -> false
+
| _ -> true
+
+
(** Scan plain scalar *)
+
let scan_plain_scalar t =
+
let start = Input.mark t.input in
+
let in_flow = t.flow_level > 0 in
+
let indent = current_indent t in
+
let buf = Buffer.create 64 in
+
let spaces = Buffer.create 16 in
+
let leading_blanks = ref false in
+
+
let rec scan_line () =
+
match Input.peek t.input with
+
| None -> ()
+
| Some c when can_continue_plain t c ~in_flow ->
+
(* Check for # preceded by space *)
+
if c = '#' && Buffer.length buf > 0 then
+
() (* Stop - # after content *)
+
else begin
+
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 ' '
+
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;
+
Buffer.clear spaces
+
end;
+
Buffer.add_char buf c;
+
ignore (Input.next t.input);
+
leading_blanks := false;
+
scan_line ()
+
end
+
| _ -> ()
+
in
+
+
let rec scan_lines () =
+
scan_line ();
+
(* Check for line continuation *)
+
if not in_flow && Input.next_is_break t.input then begin
+
(* Save whitespace *)
+
Buffer.clear spaces;
+
Buffer.add_char spaces '\n';
+
Input.consume_break t.input;
+
(* Line break in block context allows simple key *)
+
t.allow_simple_key <- true;
+
(* Skip leading blanks *)
+
while Input.next_is_blank t.input do
+
Buffer.add_char spaces (Option.get (Input.next t.input))
+
done;
+
let col = (Input.position t.input).column in
+
(* Check indentation - stop if we're at or before the containing block's indent *)
+
if not in_flow && col <= indent then
+
() (* Stop - dedented or at parent level *)
+
else if Input.at_document_boundary t.input then
+
() (* Stop - document boundary *)
+
else begin
+
leading_blanks := true;
+
scan_lines ()
+
end
+
end
+
in
+
+
scan_lines ();
+
let value = Buffer.contents buf in
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
(value, span)
+
+
(** 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 > *)
+
+
(* Parse header: optional indentation indicator and chomping *)
+
let explicit_indent = ref None in
+
let chomping = ref Chomping.Clip 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)
+
| _ -> ());
+
+
(* 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)
+
| _ -> ());
+
+
(* Skip to end of line *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
+
(* Optional comment *)
+
if Input.next_is (( = ) '#') t.input then begin
+
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
ignore (Input.next t.input)
+
done
+
end;
+
+
(* Consume line break *)
+
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
+
let content_indent = ref (
+
match !explicit_indent with
+
| Some n -> base_indent + 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
+
+
(* Read content *)
+
let rec read_lines () =
+
(* Skip empty lines, collecting breaks *)
+
while Input.next_is_break t.input ||
+
(Input.next_is_blank t.input &&
+
match Input.peek_nth t.input 1 with
+
| Some c when Input.is_break c -> true
+
| None -> true
+
| _ -> false)
+
do
+
if Input.next_is_blank t.input then begin
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done
+
end;
+
if Input.next_is_break t.input then begin
+
Buffer.add_char trailing_breaks '\n';
+
Input.consume_break t.input
+
end
+
done;
+
+
(* Check if we're at content *)
+
if Input.is_eof t.input then ()
+
else if Input.at_document_boundary t.input then ()
+
else begin
+
(* Count leading spaces *)
+
let line_indent = ref 0 in
+
while Input.next_is (( = ) ' ') t.input do
+
incr line_indent;
+
ignore (Input.next t.input)
+
done;
+
+
(* Determine content indent from first content line *)
+
if !content_indent = 0 then begin
+
if !line_indent <= base_indent then begin
+
(* No content - restore position conceptually *)
+
()
+
end else
+
content_indent := !line_indent
+
end;
+
+
if !line_indent < !content_indent then begin
+
(* Dedented - done with content *)
+
()
+
end else begin
+
(* 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
+
else 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
+
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 *)
+
if literal then begin
+
for _ = !content_indent + 1 to !line_indent 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
+
Buffer.add_char buf (Input.next_exn t.input)
+
done;
+
+
(* Record trailing break *)
+
if Input.next_is_break t.input then begin
+
Buffer.add_char trailing_breaks '\n';
+
Input.consume_break t.input
+
end;
+
+
read_lines ()
+
end
+
end
+
in
+
+
read_lines ();
+
+
(* Apply chomping *)
+
let value =
+
let content = Buffer.contents buf in
+
match !chomping with
+
| Chomping.Strip -> content
+
| Chomping.Clip ->
+
if String.length content > 0 then content ^ "\n" else content
+
| Chomping.Keep ->
+
content ^ Buffer.contents trailing_breaks
+
in
+
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
let style = if literal then Scalar_style.Literal else Scalar_style.Folded in
+
(value, style, span)
+
+
(** Scan directive (after %) *)
+
let scan_directive t =
+
let start = Input.mark t.input in
+
ignore (Input.next t.input); (* consume % *)
+
+
(* Read directive name *)
+
let name_buf = Buffer.create 16 in
+
while
+
match Input.peek t.input with
+
| Some c when Input.is_alnum c || c = '-' ->
+
Buffer.add_char name_buf c;
+
ignore (Input.next t.input);
+
true
+
| _ -> false
+
do () done;
+
let name = Buffer.contents name_buf in
+
+
(* Skip blanks *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
+
match name with
+
| "YAML" ->
+
(* Version directive: %YAML 1.2 *)
+
let major = ref 0 in
+
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')
+
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 '.'"));
+
(* Read minor version *)
+
while Input.next_is_digit t.input do
+
minor := !minor * 10 + (Char.code (Input.next_exn t.input) - Char.code '0')
+
done;
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
Token.Version_directive { major = !major; minor = !minor }, span
+
+
| "TAG" ->
+
(* Tag directive: %TAG !foo! tag:example.com,2000: *)
+
let handle = scan_tag_handle t in
+
(* Skip blanks *)
+
while Input.next_is_blank t.input do
+
ignore (Input.next t.input)
+
done;
+
(* Read prefix *)
+
let prefix_buf = Buffer.create 32 in
+
while
+
match Input.peek t.input with
+
| Some c when not (Input.is_whitespace c) ->
+
Buffer.add_char prefix_buf c;
+
ignore (Input.next t.input);
+
true
+
| _ -> false
+
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
+
+
| _ when String.length name > 0 && name.[0] >= 'A' && name.[0] <= 'Z' ->
+
(* Reserved directive *)
+
Error.raise_span span (Reserved_directive name)
+
+
| _ ->
+
(* Unknown directive - skip to end of line *)
+
while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do
+
ignore (Input.next t.input)
+
done;
+
Error.raise_span span (Invalid_directive name)
+
+
(** Fetch the next token(s) into the queue *)
+
let rec fetch_next_token t =
+
skip_to_next_token t;
+
stale_simple_keys t;
+
let col = column t in
+
(* Unroll indents that are deeper than current column.
+
Note: we use col, not col-1, to allow entries at the same level. *)
+
unroll_indent t col;
+
+
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 '[' -> 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_collection_end t Token.Flow_mapping_end
+
| Some ',' -> fetch_flow_entry t
+
| Some '-' when t.flow_level = 0 && check_block_entry t ->
+
fetch_block_entry t
+
| Some '?' when t.flow_level = 0 && 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 true
+
| 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)
+
end
+
+
and fetch_stream_end t =
+
if not t.stream_ended then begin
+
unroll_indent t (-1);
+
remove_simple_key t;
+
t.allow_simple_key <- false;
+
t.stream_ended <- true;
+
let span = Span.point (Input.mark t.input) in
+
emit t span Token.Stream_end
+
end
+
+
and fetch_document_indicator t =
+
unroll_indent t (-1);
+
remove_simple_key t;
+
t.allow_simple_key <- false;
+
let start = Input.mark t.input in
+
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
+
emit t span token
+
+
and fetch_directive t =
+
unroll_indent t (-1);
+
remove_simple_key t;
+
t.allow_simple_key <- false;
+
let token, span = scan_directive t in
+
emit t span token
+
+
and fetch_flow_collection_start t token_type =
+
save_simple_key t;
+
t.flow_level <- t.flow_level + 1;
+
t.allow_simple_key <- true;
+
t.simple_keys <- None :: t.simple_keys;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span token_type
+
+
and fetch_flow_collection_end t token_type =
+
remove_simple_key t;
+
t.flow_level <- t.flow_level - 1;
+
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);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span token_type
+
+
and fetch_flow_entry t =
+
remove_simple_key t;
+
t.allow_simple_key <- true;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span Token.Flow_entry
+
+
and check_block_entry t =
+
(* - followed by whitespace or EOF *)
+
match Input.peek_nth t.input 1 with
+
| None -> true
+
| Some c -> Input.is_whitespace c
+
+
and fetch_block_entry t =
+
if t.flow_level = 0 then begin
+
if not t.allow_simple_key then
+
Error.raise_at (Input.mark t.input) Expected_block_entry;
+
let col = column t in
+
if roll_indent t col ~sequence:true then begin
+
let span = Span.point (Input.mark t.input) in
+
emit t span Token.Block_sequence_start
+
end
+
end;
+
remove_simple_key t;
+
t.allow_simple_key <- true;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span Token.Block_entry
+
+
and check_key t =
+
(* ? followed by whitespace in block, any in flow *)
+
if t.flow_level > 0 then true
+
else match Input.peek_nth t.input 1 with
+
| None -> true
+
| Some c -> Input.is_whitespace c
+
+
and fetch_key t =
+
if t.flow_level = 0 then begin
+
if not t.allow_simple_key then
+
Error.raise_at (Input.mark t.input) Expected_key;
+
let col = column t in
+
if roll_indent t col ~sequence:false then begin
+
let span = Span.point (Input.mark t.input) in
+
emit t span Token.Block_mapping_start
+
end
+
end;
+
remove_simple_key t;
+
t.allow_simple_key <- t.flow_level = 0;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span Token.Key
+
+
and check_value t =
+
(* : followed by whitespace in block, or flow indicator in flow *)
+
if t.flow_level > 0 then true
+
else match Input.peek_nth t.input 1 with
+
| None -> true
+
| Some c -> Input.is_whitespace c
+
+
and fetch_value t =
+
(* Check for simple key *)
+
(match t.simple_keys with
+
| Some sk :: _ when sk.sk_possible ->
+
(* 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
+
(* We need to insert at the right position *)
+
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;
+
t.token_number <- t.token_number + 1;
+
(* Roll indent for implicit block mapping *)
+
if t.flow_level = 0 then begin
+
let col = sk.sk_position.column in
+
if roll_indent t col ~sequence:false then begin
+
let span = Span.point sk.sk_position in
+
(* Insert block mapping start before key *)
+
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;
+
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)
+
| _ ->
+
(* No simple key - this is a complex value *)
+
if t.flow_level = 0 then begin
+
if not t.allow_simple_key then
+
Error.raise_at (Input.mark t.input) Expected_key;
+
let col = column t in
+
if roll_indent t col ~sequence:false then begin
+
let span = Span.point (Input.mark t.input) in
+
emit t span Token.Block_mapping_start
+
end
+
end);
+
remove_simple_key t;
+
t.allow_simple_key <- t.flow_level = 0;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input);
+
let span = Span.make ~start ~stop:(Input.mark t.input) in
+
emit t span Token.Value
+
+
and fetch_alias t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input); (* consume * *)
+
let name, span = scan_anchor_alias t in
+
let span = Span.make ~start ~stop:span.stop in
+
emit t span (Token.Alias name)
+
+
and fetch_anchor t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let start = Input.mark t.input in
+
ignore (Input.next t.input); (* consume & *)
+
let name, span = scan_anchor_alias t in
+
let span = Span.make ~start ~stop:span.stop in
+
emit t span (Token.Anchor name)
+
+
and fetch_tag t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let handle, suffix, span = scan_tag t in
+
emit t span (Token.Tag { handle; suffix })
+
+
and fetch_block_scalar t literal =
+
remove_simple_key t;
+
t.allow_simple_key <- true;
+
let value, style, span = scan_block_scalar t literal in
+
emit t span (Token.Scalar { style; value })
+
+
and fetch_single_quoted t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let value, span = scan_single_quoted t in
+
emit t span (Token.Scalar { style = Scalar_style.Single_quoted; value })
+
+
and fetch_double_quoted t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let value, span = scan_double_quoted t in
+
emit t span (Token.Scalar { style = Scalar_style.Double_quoted; value })
+
+
and can_start_plain t =
+
(* Check if - ? : can start a plain scalar *)
+
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))
+
+
and can_start_plain_char c _t =
+
(* Characters that can start a plain scalar *)
+
if Input.is_whitespace c then false
+
else if Input.is_indicator c then false
+
else true
+
+
and fetch_plain_scalar t =
+
save_simple_key t;
+
t.allow_simple_key <- false;
+
let value, span = scan_plain_scalar t in
+
emit t span (Token.Scalar { style = Scalar_style.Plain; value })
+
+
(** Check if we need more tokens to resolve simple keys *)
+
let need_more_tokens t =
+
if t.stream_ended then false
+
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
+
+
(** Ensure we have enough tokens to return one safely *)
+
let ensure_tokens t =
+
if not t.stream_started then begin
+
t.stream_started <- true;
+
let span = Span.point (Input.position t.input) in
+
let encoding, _ = Encoding.detect t.input.source in
+
emit t span (Token.Stream_start encoding)
+
end;
+
while need_more_tokens t do
+
fetch_next_token t
+
done
+
+
(** Get next token *)
+
let next t =
+
ensure_tokens t;
+
if Queue.is_empty t.tokens then
+
None
+
else begin
+
t.tokens_taken <- t.tokens_taken + 1;
+
Some (Queue.pop t.tokens)
+
end
+
+
(** Peek at next token *)
+
let peek t =
+
ensure_tokens t;
+
Queue.peek_opt t.tokens
+
+
(** Iterate over all tokens *)
+
let iter f t =
+
let rec loop () =
+
match next t with
+
| None -> ()
+
| 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)
+
in
+
loop init
+
+
(** Convert to list *)
+
let to_list t =
+
fold (fun acc tok -> tok :: acc) [] t |> List.rev
+72
yaml/ocaml-yamle/lib/sequence.ml
···
+
(** YAML sequence (array) values with metadata *)
+
+
type 'a t = {
+
anchor : string option;
+
tag : string option;
+
implicit : bool;
+
style : Layout_style.t;
+
members : 'a list;
+
}
+
+
let make
+
?(anchor : string option)
+
?(tag : string option)
+
?(implicit = true)
+
?(style = Layout_style.Any)
+
members =
+
{ anchor; tag; implicit; style; members }
+
+
let members t = t.members
+
let anchor t = t.anchor
+
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 =
+
Format.fprintf fmt "@[<hv 2>sequence(@,";
+
(match t.anchor with
+
| Some a -> Format.fprintf fmt "anchor=%s,@ " a
+
| None -> ());
+
(match t.tag with
+
| Some tag -> Format.fprintf fmt "tag=%s,@ " tag
+
| None -> ());
+
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)
+
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
+
+
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
+35
yaml/ocaml-yamle/lib/span.ml
···
+
(** Source spans representing ranges in input *)
+
+
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 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 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
+
+
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
+70
yaml/ocaml-yamle/lib/tag.ml
···
+
(** YAML tags for type information *)
+
+
type t = {
+
handle : string; (** e.g., "!" or "!!" or "!foo!" *)
+
suffix : string; (** e.g., "str", "int", "custom/type" *)
+
}
+
+
let make ~handle ~suffix = { handle; suffix }
+
+
let of_string s =
+
if String.length s = 0 then None
+
else if s.[0] <> '!' then None
+
else
+
(* Find the suffix after the handle *)
+
let len = String.length s in
+
if len = 1 then Some { handle = "!"; suffix = "" }
+
else if s.[1] = '!' then
+
(* !! handle *)
+
Some { handle = "!!"; suffix = String.sub s 2 (len - 2) }
+
else if s.[1] = '<' then
+
(* Verbatim tag !<...> *)
+
if len > 2 && s.[len - 1] = '>' then
+
Some { handle = "!"; suffix = String.sub s 2 (len - 3) }
+
else
+
None
+
else
+
(* 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
+
+
let to_uri t =
+
match t.handle with
+
| "!!" -> "tag:yaml.org,2002:" ^ t.suffix
+
| "!" -> "!" ^ 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 compare a b =
+
let c = String.compare a.handle b.handle in
+
if c <> 0 then c else String.compare a.suffix b.suffix
+
+
(** Standard tags *)
+
+
let null = { handle = "!!"; suffix = "null" }
+
let bool = { handle = "!!"; suffix = "bool" }
+
let int = { handle = "!!"; suffix = "int" }
+
let float = { handle = "!!"; suffix = "float" }
+
let str = { handle = "!!"; suffix = "str" }
+
let seq = { handle = "!!"; suffix = "seq" }
+
let map = { handle = "!!"; suffix = "map" }
+
let binary = { handle = "!!"; suffix = "binary" }
+
let timestamp = { handle = "!!"; suffix = "timestamp" }
+
+
(** Check if tag matches a standard type *)
+
+
let is_null t = equal t null || (t.handle = "!" && t.suffix = "")
+
let is_bool t = equal t bool
+
let is_int t = equal t int
+
let is_float t = equal t float
+
let is_str t = equal t str
+
let is_seq t = equal t seq
+
let is_map t = equal t map
+78
yaml/ocaml-yamle/lib/token.ml
···
+
(** YAML token types produced by the scanner *)
+
+
type t =
+
| Stream_start of Encoding.t
+
| Stream_end
+
| Version_directive of { major : int; minor : int }
+
| Tag_directive of { handle : string; prefix : string }
+
| Document_start (** --- *)
+
| Document_end (** ... *)
+
| Block_sequence_start
+
| Block_mapping_start
+
| 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 (** : *)
+
| Anchor 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;
+
}
+
+
let pp_token fmt = function
+
| 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
+
| Scalar { style; value } ->
+
Format.fprintf fmt "SCALAR(%a, %S)" Scalar_style.pp style value
+
+
let pp fmt t = pp_token fmt t
+
+
let pp_spanned fmt { token; span } =
+
Format.fprintf fmt "%a at %a" pp_token token Span.pp span
+182
yaml/ocaml-yamle/lib/value.ml
···
+
(** JSON-compatible YAML value representation *)
+
+
type t = [
+
| `Null
+
| `Bool of bool
+
| `Float of float
+
| `String of string
+
| `A of t list
+
| `O of (string * t) list
+
]
+
+
(** Constructors *)
+
+
let null : t = `Null
+
let bool b : t = `Bool b
+
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
+
+
(** Type name for error messages *)
+
let type_name : t -> string = function
+
| `Null -> "null"
+
| `Bool _ -> "bool"
+
| `Float _ -> "float"
+
| `String _ -> "string"
+
| `A _ -> "array"
+
| `O _ -> "object"
+
+
(** Safe accessors (return option) *)
+
+
let as_null = function `Null -> Some () | _ -> None
+
let as_bool = function `Bool b -> Some b | _ -> None
+
let as_float = function `Float f -> Some f | _ -> None
+
let as_string = function `String s -> Some s | _ -> None
+
let as_list = function `A l -> Some l | _ -> None
+
let as_assoc = function `O o -> Some o | _ -> None
+
+
let as_int = function
+
| `Float f ->
+
let i = Float.to_int f in
+
if Float.equal (Float.of_int i) f then Some i else None
+
| _ -> None
+
+
(** Unsafe accessors (raise on type mismatch) *)
+
+
let to_null v =
+
match as_null v with
+
| Some () -> ()
+
| None -> Error.raise (Type_mismatch ("null", type_name v))
+
+
let to_bool v =
+
match as_bool v with
+
| Some b -> b
+
| None -> Error.raise (Type_mismatch ("bool", type_name v))
+
+
let to_float v =
+
match as_float v with
+
| Some f -> f
+
| None -> Error.raise (Type_mismatch ("float", type_name v))
+
+
let to_string v =
+
match as_string v with
+
| Some s -> s
+
| None -> Error.raise (Type_mismatch ("string", type_name v))
+
+
let to_list v =
+
match as_list v with
+
| Some l -> l
+
| None -> Error.raise (Type_mismatch ("array", type_name v))
+
+
let to_assoc v =
+
match as_assoc v with
+
| Some o -> o
+
| None -> Error.raise (Type_mismatch ("object", type_name v))
+
+
let to_int v =
+
match as_int v with
+
| Some i -> i
+
| None -> Error.raise (Type_mismatch ("int", type_name v))
+
+
(** Object access *)
+
+
let mem key = function
+
| `O pairs -> List.exists (fun (k, _) -> k = key) pairs
+
| _ -> false
+
+
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)
+
+
let keys = function
+
| `O pairs -> List.map fst pairs
+
| v -> Error.raise (Type_mismatch ("object", type_name v))
+
+
let values = function
+
| `O pairs -> List.map snd pairs
+
| v -> Error.raise (Type_mismatch ("object", type_name v))
+
+
(** Combinators *)
+
+
let combine v1 v2 =
+
match v1, v2 with
+
| `O o1, `O o2 -> `O (o1 @ o2)
+
| v1, _ -> Error.raise (Type_mismatch ("object", type_name v1))
+
+
let map f = function
+
| `A l -> `A (List.map f l)
+
| v -> Error.raise (Type_mismatch ("array", type_name v))
+
+
let filter pred = function
+
| `A l -> `A (List.filter pred l)
+
| v -> Error.raise (Type_mismatch ("array", type_name v))
+
+
(** Pretty printing *)
+
+
let rec pp fmt (v : t) =
+
match v with
+
| `Null -> Format.pp_print_string fmt "null"
+
| `Bool b -> Format.pp_print_bool fmt b
+
| `Float f ->
+
if Float.is_integer f && Float.abs f < 1e15 then
+
Format.fprintf fmt "%.0f" 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)
+
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 ",@ ")
+
(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
+
| `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
+
| _ -> false
+
+
let rec compare (a : t) (b : t) =
+
match a, b with
+
| `Null, `Null -> 0
+
| `Null, _ -> -1
+
| _, `Null -> 1
+
| `Bool a, `Bool b -> Bool.compare a b
+
| `Bool _, _ -> -1
+
| _, `Bool _ -> 1
+
| `Float a, `Float b -> Float.compare a b
+
| `Float _, _ -> -1
+
| _, `Float _ -> 1
+
| `String a, `String b -> String.compare a b
+
| `String _, _ -> -1
+
| _, `String _ -> 1
+
| `A a, `A b -> List.compare compare a b
+
| `A _, _ -> -1
+
| _, `A _ -> 1
+
| `O a, `O b ->
+
let cmp_pair (k1, v1) (k2, v2) =
+
let c = String.compare k1 k2 in
+
if c <> 0 then c else compare v1 v2
+
in
+
List.compare cmp_pair a b
+224
yaml/ocaml-yamle/lib/yaml.ml
···
+
(** Full YAML representation with anchors, tags, and aliases *)
+
+
type t = [
+
| `Scalar of Scalar.t
+
| `Alias of string
+
| `A of t Sequence.t
+
| `O of (t, t) Mapping.t
+
]
+
+
(** Pretty printing *)
+
+
let rec pp fmt (v : t) =
+
match v with
+
| `Scalar s -> Scalar.pp fmt s
+
| `Alias name -> Format.fprintf fmt "*%s" name
+
| `A seq -> Sequence.pp pp fmt seq
+
| `O map -> Mapping.pp pp pp fmt map
+
+
(** Equality *)
+
+
let rec equal (a : t) (b : t) =
+
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
+
| `O a, `O b -> Mapping.equal equal equal a b
+
| _ -> false
+
+
(** Construct from JSON-compatible Value *)
+
+
let rec of_value (v : Value.t) : t =
+
match v with
+
| `Null -> `Scalar (Scalar.make "null")
+
| `Bool true -> `Scalar (Scalar.make "true")
+
| `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
+
in
+
`Scalar (Scalar.make s)
+
| `String s ->
+
`Scalar (Scalar.make s ~style:Scalar_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))
+
+
(** Convert to JSON-compatible Value *)
+
+
let rec to_value (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 to_value (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, to_value v)
+
) (Mapping.members map))
+
+
(** Convert scalar to JSON value based on content *)
+
and scalar_to_value s =
+
let value = Scalar.value s in
+
let tag = Scalar.tag s in
+
let style = Scalar.style s in
+
+
(* 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 _ ->
+
(* Unknown tag - treat as string *)
+
`String value
+
| None ->
+
(* Implicit type resolution for plain scalars *)
+
if style <> Scalar_style.Plain then
+
`String value
+
else
+
infer_scalar_type value
+
+
(** Infer type from plain scalar value *)
+
and infer_scalar_type value =
+
let lower = String.lowercase_ascii value in
+
(* Null *)
+
if value = "" || lower = "null" || lower = "~" then
+
`Null
+
(* Boolean *)
+
else if lower = "true" || lower = "yes" || lower = "on" then
+
`Bool true
+
else if lower = "false" || lower = "no" || lower = "off" then
+
`Bool false
+
(* Special floats *)
+
else if lower = ".inf" || lower = "+.inf" then
+
`Float Float.infinity
+
else if lower = "-.inf" then
+
`Float Float.neg_infinity
+
else if lower = ".nan" then
+
`Float Float.nan
+
(* Try numeric *)
+
else
+
try_parse_number value
+
+
(** Try to parse as number *)
+
and try_parse_number value =
+
(* Try integer first *)
+
let try_int () =
+
if String.length value > 0 then
+
let first = value.[0] in
+
if first = '-' || first = '+' || (first >= '0' && first <= '9') then
+
try
+
(* Handle octal: 0o prefix or leading 0 *)
+
if String.length value > 2 && value.[0] = '0' then
+
match value.[1] with
+
| 'x' | 'X' ->
+
(* Hex *)
+
Some (`Float (Float.of_int (int_of_string value)))
+
| 'o' | 'O' ->
+
(* Octal *)
+
Some (`Float (Float.of_int (int_of_string value)))
+
| 'b' | 'B' ->
+
(* Binary *)
+
Some (`Float (Float.of_int (int_of_string value)))
+
| _ ->
+
(* Decimal with leading zero or octal in YAML 1.1 *)
+
Some (`Float (Float.of_string value))
+
else
+
Some (`Float (Float.of_string value))
+
with _ -> None
+
else None
+
else None
+
in
+
match try_int () with
+
| Some v -> v
+
| None ->
+
(* Try float *)
+
try
+
let f = Float.of_string value in
+
`Float f
+
with _ ->
+
(* Not a number - it's a string *)
+
`String value
+
+
(** Resolve aliases by replacing them with referenced nodes *)
+
+
let resolve_aliases (root : t) : t =
+
let anchors = Hashtbl.create 16 in
+
+
(* First pass: collect all anchors *)
+
let rec collect (v : t) =
+
match v with
+
| `Scalar s ->
+
(match Scalar.anchor s with
+
| Some name -> Hashtbl.replace anchors name v
+
| None -> ())
+
| `Alias _ -> ()
+
| `A seq ->
+
(match Sequence.anchor seq with
+
| Some name -> Hashtbl.replace anchors name v
+
| None -> ());
+
List.iter collect (Sequence.members seq)
+
| `O map ->
+
(match Mapping.anchor map with
+
| Some name -> Hashtbl.replace anchors name v
+
| None -> ());
+
List.iter (fun (k, v) -> collect k; collect v) (Mapping.members map)
+
in
+
collect root;
+
+
(* Second pass: resolve aliases *)
+
let rec resolve (v : t) : t =
+
match v with
+
| `Scalar _ -> v
+
| `Alias name ->
+
(match Hashtbl.find_opt anchors name with
+
| Some target -> resolve target
+
| None -> Error.raise (Undefined_alias name))
+
| `A seq ->
+
`A (Sequence.map resolve seq)
+
| `O map ->
+
`O (Mapping.make
+
?anchor:(Mapping.anchor map)
+
?tag:(Mapping.tag map)
+
~implicit:(Mapping.implicit map)
+
~style:(Mapping.style map)
+
(List.map (fun (k, v) -> (resolve k, resolve v)) (Mapping.members map)))
+
in
+
resolve root
+
+
(** Get anchor from any node *)
+
let anchor (v : t) =
+
match v with
+
| `Scalar s -> Scalar.anchor s
+
| `Alias _ -> None
+
| `A seq -> Sequence.anchor seq
+
| `O map -> Mapping.anchor map
+
+
(** Get tag from any node *)
+
let tag (v : t) =
+
match v with
+
| `Scalar s -> Scalar.tag s
+
| `Alias _ -> None
+
| `A seq -> Sequence.tag seq
+
| `O map -> Mapping.tag map
+149
yaml/ocaml-yamle/lib/yamle.ml
···
+
type value = Value.t
+
type yaml = Yaml.t
+
+
type version = [ `V1_1 | `V1_2 ]
+
+
type encoding = Encoding.t
+
type scalar_style = Scalar_style.t
+
type layout_style = Layout_style.t
+
+
(** {1 Error handling} *)
+
+
type error = Error.t
+
exception Yamle_error = Error.Yamle_error
+
+
(** {1 JSON-compatible parsing} *)
+
+
let of_string s = Loader.value_of_string s
+
+
(** {1 JSON-compatible emission} *)
+
+
let to_string
+
?(encoding = Encoding.Utf8)
+
?(scalar_style = Scalar_style.Any)
+
?(layout_style = Layout_style.Any)
+
value =
+
let config = {
+
Emitter.default_config with
+
encoding;
+
scalar_style;
+
layout_style;
+
} in
+
Emitter.value_to_string ~config value
+
+
(** {1 YAML-specific parsing} *)
+
+
let yaml_of_string s = Loader.yaml_of_string s
+
+
(** {1 YAML-specific emission} *)
+
+
let yaml_to_string
+
?(encoding = Encoding.Utf8)
+
?(scalar_style = Scalar_style.Any)
+
?(layout_style = Layout_style.Any)
+
yaml =
+
let config = {
+
Emitter.default_config with
+
encoding;
+
scalar_style;
+
layout_style;
+
} in
+
Emitter.yaml_to_string ~config yaml
+
+
(** {1 Conversion} *)
+
+
let to_json yaml = Yaml.to_value yaml
+
+
let of_json value = Yaml.of_value value
+
+
(** {1 Pretty printing} *)
+
+
let pp = Value.pp
+
let pp_yaml = Yaml.pp
+
let equal = Value.equal
+
let equal_yaml = Yaml.equal
+
+
(** {1 Nested modules} *)
+
+
module Error = Error
+
module Position = Position
+
module Span = Span
+
module Encoding = Encoding
+
module Input = Input
+
module Scalar_style = Scalar_style
+
module Layout_style = Layout_style
+
module Chomping = Chomping
+
module Token = Token
+
module Scanner = Scanner
+
module Event = Event
+
module Parser = Parser
+
module Tag = Tag
+
module Value = Value
+
module Scalar = Scalar
+
module Sequence = Sequence
+
module Mapping = Mapping
+
module Yaml = Yaml
+
module Document = Document
+
module Loader = Loader
+
module Emitter = Emitter
+
+
(** {1 Streaming interface} *)
+
+
module Stream = struct
+
type parser = Parser.t
+
type emitter = Emitter.t
+
+
let parser s = Parser.of_string s
+
+
let do_parse p = Parser.next p
+
+
let emitter ?len:_ () = Emitter.create ()
+
+
let emit e ev = Emitter.emit e ev
+
+
let emitter_buf e = Emitter.contents e
+
+
(** Convenience event emitters *)
+
+
let stream_start e enc =
+
Emitter.emit e (Event.Stream_start { encoding = enc })
+
+
let stream_end e =
+
Emitter.emit e Event.Stream_end
+
+
let document_start ?version ?(implicit = true) e =
+
let version = match version with
+
| Some `V1_1 -> Some (1, 1)
+
| Some `V1_2 -> Some (1, 2)
+
| None -> None
+
in
+
Emitter.emit e (Event.Document_start { version; implicit })
+
+
let document_end ?(implicit = true) e =
+
Emitter.emit e (Event.Document_end { implicit })
+
+
let scalar s e =
+
Emitter.emit e (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;
+
})
+
+
let alias e name =
+
Emitter.emit e (Event.Alias { anchor = name })
+
+
let sequence_start ?anchor ?tag ?(implicit = true) ?(style = Layout_style.Any) e =
+
Emitter.emit e (Event.Sequence_start { anchor; tag; implicit; style })
+
+
let sequence_end e =
+
Emitter.emit e Event.Sequence_end
+
+
let mapping_start ?anchor ?tag ?(implicit = true) ?(style = Layout_style.Any) e =
+
Emitter.emit e (Event.Mapping_start { anchor; tag; implicit; style })
+
+
let mapping_end e =
+
Emitter.emit e Event.Mapping_end
+
end
+7
yaml/ocaml-yamle/tests/cram/dune
···
+
(cram
+
(deps
+
(package yamle)
+
../yaml/seq.yml
+
../yaml/cohttp.yml
+
../yaml/linuxkit.yml
+
../yaml/yaml-1.2.yml))
+161
yaml/ocaml-yamle/tests/cram/yamlcat.t
···
+
Test yamlcat with simple YAML
+
+
$ echo 'hello: world' | yamlcat
+
hello: world
+
+
$ echo 'name: Alice
+
> age: 30' | yamlcat
+
name: Alice
+
age: 30
+
+
Test nested mappings
+
+
$ echo 'server:
+
> host: localhost
+
> port: 8080
+
> database:
+
> name: mydb' | yamlcat
+
server:
+
host: localhost
+
port: 8080
+
database:
+
name: mydb
+
+
Test sequences
+
+
$ echo '- apple
+
> - banana
+
> - cherry' | yamlcat
+
- apple
+
- banana
+
- cherry
+
+
Test mapping with sequence value
+
+
$ echo 'fruits:
+
> - apple
+
> - banana' | yamlcat
+
fruits:
+
- apple
+
- banana
+
+
Test flow style output
+
+
$ echo 'name: Alice
+
> hobbies:
+
> - reading
+
> - coding' | yamlcat --flow
+
{name: Alice, hobbies: [reading, coding]}
+
+
Test JSON output
+
+
$ echo 'name: Alice
+
> age: 30' | yamlcat --json
+
{"name": "Alice", "age": 30}
+
+
Test seq.yml file (multiline plain scalar)
+
+
$ yamlcat ../yaml/seq.yml
+
- hello - whats - up
+
- foo
+
- bar
+
+
Test seq.yml roundtrip preserves data
+
+
$ yamlcat --json ../yaml/seq.yml
+
["hello - whats - up", "foo", "bar"]
+
+
Test cohttp.yml
+
+
$ yamlcat ../yaml/cohttp.yml
+
language: c
+
sudo: false
+
services:
+
- docker
+
install: 'wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh'
+
script: bash -ex ./.travis-docker.sh
+
env:
+
global:
+
- "EXTRA_REMOTES=\"https://github.com/mirage/mirage-dev.git\""
+
- "PINS=\"cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:.\""
+
matrix:
+
- "PACKAGE=\"cohttp\" DISTRO=\"alpine-3.5\" OCAML_VERSION=\"4.06.0\""
+
- "PACKAGE=\"cohttp-async\" DISTRO=\"alpine\" OCAML_VERSION=\"4.06.0\""
+
- "PACKAGE=\"cohttp-lwt\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\""
+
- "PACKAGE=\"cohttp-mirage\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\""
+
notifications:
+
webhooks:
+
urls:
+
- 'https://webhooks.gitter.im/e/6ee5059c7420709f4ad1'
+
on_success: change
+
on_failure: always
+
on_start: false
+
+
Test cohttp.yml roundtrip with JSON
+
+
$ yamlcat --json ../yaml/cohttp.yml
+
{"language": "c", "sudo": false, "services": ["docker"], "install": "wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh", "script": "bash -ex ./.travis-docker.sh", "env": {"global": ["EXTRA_REMOTES=\"https://github.com/mirage/mirage-dev.git\"", "PINS=\"cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:.\""], "matrix": ["PACKAGE=\"cohttp\" DISTRO=\"alpine-3.5\" OCAML_VERSION=\"4.06.0\"", "PACKAGE=\"cohttp-async\" DISTRO=\"alpine\" OCAML_VERSION=\"4.06.0\"", "PACKAGE=\"cohttp-lwt\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\"", "PACKAGE=\"cohttp-mirage\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\""]}, "notifications": {"webhooks": {"urls": ["https://webhooks.gitter.im/e/6ee5059c7420709f4ad1"], "on_success": "change", "on_failure": "always", "on_start": false}}}
+
+
Test special values
+
+
$ echo 'null_val: null
+
> bool_true: true
+
> bool_false: false
+
> number: 42
+
> float: 3.14' | yamlcat --json
+
{"null_val": null, "bool_true": true, "bool_false": false, "number": 42, "float": 3.14}
+
+
Test quoted strings
+
+
$ echo 'single: '"'"'hello world'"'"'
+
> double: "hello world"' | yamlcat
+
single: hello world
+
double: hello world
+
+
Test literal block scalar
+
+
$ echo 'text: |
+
> line one
+
> line two' | yamlcat --json
+
{"text": "line one\nline two\n"}
+
+
Test folded block scalar
+
+
$ echo 'text: >
+
> line one
+
> line two' | yamlcat --json
+
{"text": "line one line two\n"}
+
+
Test linuxkit.yml (sequences of mappings)
+
+
$ yamlcat ../yaml/linuxkit.yml | head -30
+
kernel:
+
image: 'linuxkit/kernel:4.9.40'
+
cmdline: console=tty0 console=ttyS0
+
init:
+
- 'linuxkit/init:906e174b3f2e07f97d6fd693a2e8518e98dafa58'
+
- 'linuxkit/runc:90e45f13e1d0a0983f36ef854621e3eac91cf541'
+
- 'linuxkit/containerd:7c986fb7df33bea73b5c8097b46989e46f49d875'
+
- 'linuxkit/ca-certificates:e44b0a66df5a102c0e220f0066b0d904710dcb10'
+
onboot:
+
- name: sysctl
+
image: 'linuxkit/sysctl:184c914d23a017062d7b53d7fc1dfaf47764bef6'
+
- name: dhcpcd
+
image: 'linuxkit/dhcpcd:f3f5413abb78fae9020e35bd4788fa93df4530b7'
+
command:
+
- /sbin/dhcpcd
+
- '--nobackground'
+
- '-f'
+
- /dhcpcd.conf
+
- '-1'
+
onshutdown:
+
- name: shutdown
+
image: 'busybox:latest'
+
command:
+
- /bin/echo
+
- so long and thanks for all the fish
+
services:
+
- name: getty
+
image: 'linuxkit/getty:2c841cdc34396e3fa8f25b62d112808f63f16df6'
+
env:
+
- INSECURE=true
+3
yaml/ocaml-yamle/tests/dune
···
+
(test
+
(name test_yamle)
+
(libraries yamle alcotest))
+262
yaml/ocaml-yamle/tests/test_yamle.ml
···
+
(** Tests for the Yamle library *)
+
+
open Yamle
+
+
(** Test helpers *)
+
+
let check_value msg expected actual =
+
Alcotest.(check bool) msg true (Value.equal expected actual)
+
+
let _check_string msg expected actual =
+
Alcotest.(check string) msg expected actual
+
+
(** Scanner tests *)
+
+
let test_scanner_simple () =
+
let scanner = Scanner.of_string "hello: world" in
+
let tokens = Scanner.to_list scanner in
+
let token_types = List.map (fun (t : Token.spanned) -> t.token) tokens in
+
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 :: [] ->
+
()
+
| _ ->
+
Alcotest.fail "unexpected token sequence"
+
+
let test_scanner_sequence () =
+
let scanner = Scanner.of_string "- one\n- two\n- three" in
+
let tokens = Scanner.to_list scanner in
+
Alcotest.(check bool) "has tokens" true (List.length tokens > 0)
+
+
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
+
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;
+
]
+
+
(** Parser tests *)
+
+
let test_parser_events () =
+
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
+
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
+
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;
+
]
+
+
(** Value parsing tests *)
+
+
let test_parse_null () =
+
check_value "null" `Null (of_string "null");
+
check_value "~" `Null (of_string "~");
+
check_value "empty" `Null (of_string "")
+
+
let test_parse_bool () =
+
check_value "true" (`Bool true) (of_string "true");
+
check_value "false" (`Bool false) (of_string "false");
+
check_value "yes" (`Bool true) (of_string "yes");
+
check_value "no" (`Bool false) (of_string "no")
+
+
let test_parse_number () =
+
check_value "integer" (`Float 42.0) (of_string "42");
+
check_value "negative" (`Float (-17.0)) (of_string "-17");
+
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 "quoted" (`String "hello") (of_string {|"hello"|})
+
+
let test_parse_sequence () =
+
let result = of_string "- one\n- two\n- three" in
+
match result with
+
| `A [_; _; _] -> ()
+
| _ -> Alcotest.fail "expected sequence with 3 elements"
+
+
let test_parse_mapping () =
+
let result = of_string "name: Alice\nage: 30" in
+
match result with
+
| `O pairs when List.length pairs = 2 -> ()
+
| _ -> Alcotest.fail "expected mapping with 2 pairs"
+
+
let test_parse_nested () =
+
let yaml = {|
+
person:
+
name: Bob
+
hobbies:
+
- reading
+
- coding
+
|} in
+
let result = of_string yaml in
+
match result with
+
| `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] -> ()
+
| _ -> 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)] -> ()
+
| _ -> Alcotest.fail "expected flow mapping {a: 1, b: 2}"
+
+
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;
+
]
+
+
(** Emitter tests *)
+
+
let test_emit_null () =
+
let result = to_string `Null in
+
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
+
+
let test_emit_mapping () =
+
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)
+
+
let test_roundtrip_simple () =
+
let yaml = "name: Alice" in
+
let value = of_string yaml in
+
let _ = to_string value in
+
(* Just check it doesn't crash *)
+
()
+
+
let test_roundtrip_sequence () =
+
let yaml = "- one\n- two\n- three" in
+
let value = of_string yaml in
+
match value with
+
| `A items when List.length items = 3 ->
+
let _ = to_string value 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;
+
]
+
+
(** YAML-specific tests *)
+
+
let test_yaml_anchor () =
+
let yaml = "&anchor hello" in
+
let result = yaml_of_string yaml in
+
match result with
+
| `Scalar s when Scalar.anchor s = Some "anchor" -> ()
+
| _ -> Alcotest.fail "expected scalar with anchor"
+
+
let test_yaml_alias () =
+
let yaml = {|
+
defaults: &defaults
+
timeout: 30
+
production:
+
<<: *defaults
+
port: 8080
+
|} 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;
+
]
+
+
(** Multiline scalar tests *)
+
+
let test_literal_block () =
+
let yaml = {|description: |
+
This is a
+
multi-line
+
description
+
|} in
+
let result = of_string yaml in
+
match result with
+
| `O [("description", `String _)] -> ()
+
| _ -> Alcotest.fail "expected mapping with literal block"
+
+
let test_folded_block () =
+
let yaml = {|description: >
+
This is a
+
folded
+
description
+
|} in
+
let result = of_string yaml in
+
match result with
+
| `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;
+
]
+
+
(** Error handling tests *)
+
+
let test_error_position () =
+
try
+
let _ = of_string "key: [unclosed" in
+
Alcotest.fail "expected error"
+
with
+
| Yamle_error e ->
+
Alcotest.(check bool) "has span" true (e.span <> None)
+
+
let error_tests = [
+
"error position", `Quick, test_error_position;
+
]
+
+
(** Run all tests *)
+
+
let () =
+
Alcotest.run "yamle" [
+
"scanner", scanner_tests;
+
"parser", parser_tests;
+
"value", value_tests;
+
"emitter", emitter_tests;
+
"yaml", yaml_tests;
+
"multiline", multiline_tests;
+
"errors", error_tests;
+
]
+24
yaml/ocaml-yamle/tests/yaml/anchor.yml
···
+
datetime: 2001-12-15T02:59:43.1Z
+
datetime_with_spaces: 2001-12-14 21:59:43.10 -5
+
date: 2002-12-14
+
+
# The !!binary tag indicates that a string is actually a base64-encoded
+
# representation of a binary blob.
+
gif_file: !!binary |
+
R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
+
OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
+
+f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
+
AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
+
+
# YAML also has a set type, which looks like this:
+
set:
+
? item1
+
? item2
+
? item3
+
+
# Like Python, sets are just maps with null values; the above is equivalent to:
+
set2:
+
item1: null
+
item2: null
+
item3: null
+
+23
yaml/ocaml-yamle/tests/yaml/cohttp.yml
···
+
language: c
+
sudo: false
+
services:
+
- docker
+
install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh
+
script: bash -ex ./.travis-docker.sh
+
env:
+
global:
+
- EXTRA_REMOTES="https://github.com/mirage/mirage-dev.git"
+
- PINS="cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:."
+
matrix:
+
- PACKAGE="cohttp" DISTRO="alpine-3.5" OCAML_VERSION="4.06.0"
+
- PACKAGE="cohttp-async" DISTRO="alpine" OCAML_VERSION="4.06.0"
+
- PACKAGE="cohttp-lwt" DISTRO="debian-unstable" OCAML_VERSION="4.03.0"
+
- PACKAGE="cohttp-mirage" DISTRO="debian-unstable" OCAML_VERSION="4.03.0"
+
notifications:
+
webhooks:
+
urls:
+
- https://webhooks.gitter.im/e/6ee5059c7420709f4ad1
+
on_success: change
+
on_failure: always
+
on_start: false
+
+59
yaml/ocaml-yamle/tests/yaml/linuxkit.yml
···
+
kernel:
+
image: linuxkit/kernel:4.9.40
+
cmdline: "console=tty0 console=ttyS0"
+
init:
+
- linuxkit/init:906e174b3f2e07f97d6fd693a2e8518e98dafa58
+
- linuxkit/runc:90e45f13e1d0a0983f36ef854621e3eac91cf541
+
- linuxkit/containerd:7c986fb7df33bea73b5c8097b46989e46f49d875
+
- linuxkit/ca-certificates:e44b0a66df5a102c0e220f0066b0d904710dcb10
+
onboot:
+
- name: sysctl
+
image: linuxkit/sysctl:184c914d23a017062d7b53d7fc1dfaf47764bef6
+
- name: dhcpcd
+
image: linuxkit/dhcpcd:f3f5413abb78fae9020e35bd4788fa93df4530b7
+
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
+
onshutdown:
+
- name: shutdown
+
image: busybox:latest
+
command: ["/bin/echo", "so long and thanks for all the fish"]
+
services:
+
- name: getty
+
image: linuxkit/getty:2c841cdc34396e3fa8f25b62d112808f63f16df6
+
env:
+
- INSECURE=true
+
- name: rngd
+
image: linuxkit/rngd:b2f4bdcb55aa88a25c86733e294628614504f383
+
- name: nginx
+
image: nginx:alpine
+
capabilities:
+
- CAP_NET_BIND_SERVICE
+
- CAP_CHOWN
+
- CAP_SETUID
+
- CAP_SETGID
+
- CAP_DAC_OVERRIDE
+
files:
+
- path: etc/containerd/config.toml
+
contents: |
+
state = "/run/containerd"
+
root = "/var/lib/containerd"
+
snapshotter = "io.containerd.snapshotter.v1.overlayfs"
+
differ = "io.containerd.differ.v1.base-diff"
+
subreaper = false
+
+
[grpc]
+
address = "/run/containerd/containerd.sock"
+
uid = 0
+
gid = 0
+
+
[debug]
+
address = "/run/containerd/debug.sock"
+
level = "info"
+
+
[metrics]
+
address = ":13337"
+
- path: etc/linuxkit-config
+
metadata: yaml
+
trust:
+
org:
+
- linuxkit
+
- library
+5
yaml/ocaml-yamle/tests/yaml/seq.yml
···
+
- hello
+
- whats
+
- up
+
- foo
+
- bar
+3
yaml/ocaml-yamle/tests/yaml/yaml-1.2.yml
···
+
- {"when the key is quoted":"space after colon can be omitted."}
+
- "quoted slashes \/ are allowed."
+
- {?"a key can be looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooger": "than 1024 when parsing is unambiguous before seeing the colon."}
+32
yaml/ocaml-yamle/yamle.opam
···
+
# This file is generated by dune, edit dune-project instead
+
opam-version: "2.0"
+
version: "0.1.0"
+
synopsis: "Pure OCaml YAML 1.2 parser and emitter"
+
description:
+
"A pure OCaml implementation of YAML 1.2 parsing and emission, with no C dependencies."
+
maintainer: ["yamle@example.com"]
+
authors: ["Yamle Authors"]
+
license: "ISC"
+
homepage: "https://github.com/ocaml/yamle"
+
bug-reports: "https://github.com/ocaml/yamle/issues"
+
depends: [
+
"ocaml" {>= "4.14.0"}
+
"dune" {>= "3.0" & >= "3.0"}
+
"alcotest" {with-test}
+
"odoc" {with-doc}
+
]
+
build: [
+
["dune" "subst"] {dev}
+
[
+
"dune"
+
"build"
+
"-p"
+
name
+
"-j"
+
jobs
+
"@install"
+
"@runtest" {with-test}
+
"@doc" {with-doc}
+
]
+
]
+
dev-repo: "git+https://github.com/ocaml/yamle.git"