···
102
-
(** Skip blanks (spaces/tabs) and return whether tabs were found *)
102
+
(** Skip blanks (spaces/tabs) and return (found_tabs, found_spaces) *)
let skip_blanks_check_tabs t =
let found_tab = ref false in
105
+
let found_space = ref false in
while Input.next_is_blank t.input do
106
-
if Input.peek t.input = Some '\t' then found_tab := true;
107
+
(match Input.peek t.input with
108
+
| Some '\t' -> found_tab := true
109
+
| Some ' ' -> found_space := true
ignore (Input.next t.input)
113
+
(!found_tab, !found_space)
(** Skip whitespace and comments, return true if at newline *)
let rec skip_to_next_token t =
···
Input.consume_break t.input;
(* Allow simple keys after line breaks in flow context *)
t.allow_simple_key <- true;
147
+
(* After line break in flow, check for tabs at start of line (Y79Y/03)
148
+
Tabs are not allowed as indentation - if tab is first char and results
149
+
in a column less than flow_indent, it's an error *)
150
+
if Input.next_is (( = ) '\t') t.input then begin
151
+
(* Tab at start of line in flow context - skip tabs and check position *)
152
+
let start_mark = Input.mark t.input in
153
+
while Input.next_is (( = ) '\t') t.input do
154
+
ignore (Input.next t.input)
156
+
(* If only tabs were used (no spaces) and column < flow_indent, error *)
157
+
if not (Input.next_is_break t.input) && not (Input.is_eof t.input) &&
158
+
column t < t.flow_indent then
159
+
Error.raise_at start_mark Invalid_flow_indentation
ignore (Input.next t.input);
···
(* Check for document boundary *)
if Input.at_document_boundary t.input then
Error.raise_at start Unclosed_single_quote;
435
+
(* Check indentation: continuation must be > block indent (QB6E, DK95) *)
436
+
let col = column t in
437
+
let indent = current_indent t in
438
+
if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then
439
+
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
(* Count empty lines (consecutive line breaks) *)
let empty_lines = ref 0 in
while Input.next_is_break t.input do
···
ignore (Input.next t.input)
if Input.at_document_boundary t.input then
426
-
Error.raise_at start Unclosed_single_quote
449
+
Error.raise_at start Unclosed_single_quote;
450
+
(* Check indentation after each empty line too *)
451
+
let col = column t in
452
+
let indent = current_indent t in
453
+
if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then
454
+
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar")
(* Apply folding rules *)
if !empty_lines > 0 then begin
···
(* Count consecutive line breaks (empty lines) *)
let empty_lines = ref 0 in
let continue = ref true in
583
+
let started_with_tab = ref false in
585
+
(* Track if we start with a tab (for DK95/01 check) *)
586
+
if Input.next_is (( = ) '\t') t.input then started_with_tab := true;
(* Skip blanks (spaces/tabs) on the line *)
while Input.next_is_blank t.input do
ignore (Input.next t.input)
···
(* Check if we hit another line break (empty line) *)
if Input.next_is_break t.input then begin
Input.consume_break t.input;
595
+
started_with_tab := false (* Reset for next line *)
(* Check for document boundary - this terminates the quoted string *)
if Input.at_document_boundary t.input then
Error.raise_at start Unclosed_double_quote;
602
+
(* Check indentation: continuation must be > block indent (QB6E, DK95)
603
+
Note: must be strictly greater than block indent, not just equal *)
604
+
let col = column t in
605
+
let indent = current_indent t in
606
+
let start_col = start.column in
607
+
(* DK95/01: if continuation started with tabs and column < start column, error *)
608
+
if not (Input.is_eof t.input) && !started_with_tab && col < start_col then
609
+
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
610
+
if not (Input.is_eof t.input) && col <= indent && indent >= 0 then
611
+
Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar");
(* Per YAML spec: single break = space, break + empty lines = newlines *)
if !empty_lines > 0 then begin
(* Empty lines: output N newlines where N = number of empty lines *)
···
let buf = Buffer.create 256 in
let trailing_breaks = Buffer.create 16 in
let leading_blank = ref false in (* Was the previous line "more indented"? *)
820
+
let max_empty_line_indent = ref 0 in (* Track max indent of empty lines before first content *)
(* Skip to content indentation, skipping empty lines.
Returns the number of spaces actually skipped (important for detecting dedentation). *)
···
match Input.peek_nth t.input (!idx) with
| None | Some '\n' | Some '\r' ->
(* Line has only spaces - empty line *)
889
+
(* Track max indent of empty lines for later validation *)
890
+
if !idx > !max_empty_line_indent then
891
+
max_empty_line_indent := !idx;
while Input.next_is (( = ) ' ') t.input do
ignore (Input.next t.input)
···
(* Has content (including tabs which are content, not indentation) *)
901
+
end else if Input.next_is (( = ) '\t') t.input then begin
902
+
(* Tab at start of line in implicit indent mode - this is an error (Y79Y)
903
+
because tabs cannot be used as indentation in YAML *)
904
+
Error.raise_at (Input.mark t.input) Tab_in_indentation
856
-
(* Not at break or space - could be tab (content) or other *)
906
+
(* Not at break or space - other content character *)
···
if line_indent <= base_level then
false (* No content - first line not indented enough *)
940
+
(* Validate: first content line must be indented at least as much as
941
+
the maximum indent seen on empty lines before it (5LLU, S98Z, W9L4) *)
942
+
if line_indent < !max_empty_line_indent && line_indent > base_level then
943
+
Error.raise_at (Input.mark t.input)
944
+
(Invalid_block_scalar_header "wrongly indented line in block scalar");
content_indent := line_indent;
···
while Input.next_is_digit t.input do
minor := !minor * 10 + (Char.code (Input.next_exn t.input) - Char.code '0')
1069
+
(* Validate: only whitespace and comments allowed before line break (MUS6) *)
1070
+
skip_whitespace_and_comment t;
1071
+
if not (Input.next_is_break t.input) && not (Input.is_eof t.input) then
1072
+
Error.raise_at (Input.mark t.input) (Invalid_directive "expected comment or line break after version");
let span = Span.make ~start ~stop:(Input.mark t.input) in
Token.Version_directive { major = !major; minor = !minor }, span
···
ignore (Input.next t.input);
(* Check for tabs after - : pattern like -\t- is invalid *)
1210
-
let found_tabs = skip_blanks_check_tabs t in
1269
+
let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in
(* If we found tabs and next char is - followed by whitespace, error *)
match Input.peek t.input with
···
ignore (Input.next t.input);
(* Check for tabs after ? : pattern like ?\t- or ?\tkey is invalid *)
1251
-
let found_tabs = skip_blanks_check_tabs t in
1310
+
let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in
if found_tabs && t.flow_level = 0 then begin
(* In block context, tabs after ? are not allowed *)
Error.raise_at start Tab_in_indentation
···
let start = Input.mark t.input in
ignore (Input.next t.input);
1347
-
(* Check for tabs after : : pattern like :\t- is invalid in block context *)
1348
-
let found_tabs = skip_blanks_check_tabs t in
1349
-
if found_tabs && t.flow_level = 0 then begin
1350
-
(* In block context, tabs after : followed by indicator are not allowed *)
1406
+
(* Check for tabs after : : patterns like :\t- or :\tkey: are invalid in block context (Y79Y/09)
1407
+
However, :\t bar (tab followed by space then content) is valid (6BCT) *)
1408
+
let (found_tabs, found_spaces) = skip_blanks_check_tabs t in
1409
+
if found_tabs && not found_spaces && t.flow_level = 0 then begin
1410
+
(* In block context, tabs-only after : followed by indicator or alphanumeric are not allowed *)
match Input.peek t.input with
1413
+
Error.raise_at start Tab_in_indentation
1414
+
| Some c when (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ->
1415
+
(* Tab-only followed by alphanumeric - likely a key, which is invalid *)
Error.raise_at start Tab_in_indentation