Interior comments, fragment comments, get_siblings + doc_gen comment support

Parser: read_value/read_list now capture Comment nodes inside lists
when ~comments:true. Module-level _preserve_comments ref threads the
flag through the recursive descent without changing signatures.

Pretty printer: has_interior_comments (recursive) forces multi-line
when any nested list contains comments. Comment nodes inside lists
emit as indented comment lines.

Edit tools: separate_comments strips interior comments recursively
via strip_interior_comments before passing to tree-tools (paths stay
correct). extract_fragment_comments parses new source with comments,
attaches leading comments to the target position in the comment map.

sx_get_siblings: injects comments for top-level siblings.

sx_doc_gen: parses with comments, tracks preceding Comment node,
includes cleaned comment text in generated component documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 17:00:56 +00:00
parent 033b2cb304
commit 38556af423
2 changed files with 193 additions and 57 deletions

View File

@@ -110,7 +110,35 @@ let try_number str =
| Some n -> Some (Number n)
| None -> None
(* Module-level flag — when true, comments are captured as Comment nodes.
Set by parse_all ~comments:true, reset after. *)
let _preserve_comments = ref false
(* Collect consecutive comment lines into a Comment node *)
let collect_comment_node s =
let lines = ref [] in
let rec go () =
skip_whitespace s;
if not (at_end s) && s.src.[s.pos] = ';' then begin
lines := read_comment s :: !lines;
go ()
end
in
go ();
Comment (String.concat "\n" (List.rev !lines))
let rec read_value s : value =
(* In comment-preserving mode, check for comments first *)
if !_preserve_comments then begin
skip_whitespace s;
if not (at_end s) && s.src.[s.pos] = ';' then
collect_comment_node s
else
read_value_core s
end else
read_value_core s
and read_value_core s : value =
skip_whitespace_and_comments s;
if at_end s then begin
let line = ref 1 in
@@ -184,14 +212,29 @@ and read_list s close_char =
advance s; (* skip opening paren/bracket *)
let items = ref [] in
let rec go () =
skip_whitespace_and_comments s;
if at_end s then raise (Parse_error "Unterminated list");
if s.src.[s.pos] = close_char then begin
advance s;
List (List.rev !items)
if !_preserve_comments then begin
skip_whitespace s;
if at_end s then raise (Parse_error "Unterminated list");
if s.src.[s.pos] = close_char then begin
advance s;
List (List.rev !items)
end else if s.src.[s.pos] = ';' then begin
items := collect_comment_node s :: !items;
go ()
end else begin
items := read_value_core s :: !items;
go ()
end
end else begin
items := read_value s :: !items;
go ()
skip_whitespace_and_comments s;
if at_end s then raise (Parse_error "Unterminated list");
if s.src.[s.pos] = close_char then begin
advance s;
List (List.rev !items)
end else begin
items := read_value s :: !items;
go ()
end
end
in go ()
@@ -220,42 +263,25 @@ and read_dict s =
(** Parse a string into a list of SX values.
When [~comments:true], top-level ;; comments are preserved as [Comment]
nodes in the result list. Default is [false] (strip comments). *)
When [~comments:true], comments are preserved as [Comment] nodes —
both at top level and inside lists. Default is [false] (strip). *)
let parse_all ?(comments=false) src =
_preserve_comments := comments;
let s = make_state src in
let results = ref [] in
let rec go () =
if comments then begin
skip_whitespace s;
if at_end s then List.rev !results
else if s.src.[s.pos] = ';' then begin
(* Collect consecutive comment lines into one Comment node *)
let lines = ref [] in
let rec collect () =
skip_whitespace s;
if not (at_end s) && s.src.[s.pos] = ';' then begin
lines := read_comment s :: !lines;
collect ()
end
in
collect ();
let text = String.concat "\n" (List.rev !lines) in
results := Comment text :: !results;
go ()
end else begin
results := read_value s :: !results;
go ()
end
end else begin
skip_whitespace_and_comments s;
if at_end s then List.rev !results
else begin
results := read_value s :: !results;
go ()
end
if !_preserve_comments then skip_whitespace s
else skip_whitespace_and_comments s;
if at_end s then (
_preserve_comments := false;
List.rev !results
) else begin
results := read_value s :: !results;
go ()
end
in go ()
in
try go ()
with e -> _preserve_comments := false; raise e
(** Parse a file into a list of SX values. *)
let parse_file ?(comments=false) path =