Preserve ;; comments through MCP tree edit round-trips
Parser gains Comment(string) AST variant and ~comments:true mode that captures top-level ;; lines instead of discarding them. All MCP edit tools (replace_node, insert_child, delete_node, wrap_node, rename_symbol, replace_by_pattern, insert_near, rename_across, pretty_print, write_file) now preserve comments: separate before tree-tools operate (so index paths stay correct), re-interleave after editing, emit in pretty_print_file. Default parse path (evaluator, runtime, compiler) is unchanged — comments are still stripped unless explicitly requested. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,23 @@ let skip_whitespace_and_comments s =
|
||||
| _ -> ()
|
||||
in go ()
|
||||
|
||||
(* Skip whitespace only — leaves comments for capture *)
|
||||
let skip_whitespace s =
|
||||
let rec go () =
|
||||
if at_end s then ()
|
||||
else match s.src.[s.pos] with
|
||||
| ' ' | '\t' | '\n' | '\r' -> advance s; go ()
|
||||
| _ -> ()
|
||||
in go ()
|
||||
|
||||
(* Read a comment line starting at ';', returns text after ";;" (or ";") *)
|
||||
let read_comment s =
|
||||
let start = s.pos in
|
||||
while s.pos < s.len && s.src.[s.pos] <> '\n' do advance s done;
|
||||
let text = String.sub s.src start (s.pos - start) in
|
||||
if s.pos < s.len then advance s;
|
||||
text
|
||||
|
||||
(* Character classification — matches spec/parser.sx ident-start/ident-char.
|
||||
ident-start: a-z A-Z _ ~ * + - > < = / ! ? &
|
||||
ident-char: ident-start plus 0-9 . : / # , *)
|
||||
@@ -202,23 +219,48 @@ and read_dict s =
|
||||
in go ()
|
||||
|
||||
|
||||
(** Parse a string into a list of SX values. *)
|
||||
let parse_all src =
|
||||
(** 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). *)
|
||||
let parse_all ?(comments=false) src =
|
||||
let s = make_state src in
|
||||
let results = ref [] in
|
||||
let rec go () =
|
||||
skip_whitespace_and_comments s;
|
||||
if at_end s then List.rev !results
|
||||
else begin
|
||||
results := read_value s :: !results;
|
||||
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
|
||||
end
|
||||
in go ()
|
||||
|
||||
(** Parse a file into a list of SX values. *)
|
||||
let parse_file path =
|
||||
let parse_file ?(comments=false) path =
|
||||
let ic = open_in path in
|
||||
let n = in_channel_length ic in
|
||||
let src = really_input_string ic n in
|
||||
close_in ic;
|
||||
parse_all src
|
||||
parse_all ~comments src
|
||||
|
||||
Reference in New Issue
Block a user