Skip to content

Commit e0a4def

Browse files
authored
Merge pull request #155 from Julow/fix-parts-parsing
Fix parts parsing
2 parents 39f0b57 + a4470a6 commit e0a4def

File tree

5 files changed

+104
-265
lines changed

5 files changed

+104
-265
lines changed

bin/test/main.ml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,6 @@ let run_toplevel_tests ?root c ppf tests t =
182182
) tests;
183183
Block.pp_footer ppf ()
184184

185-
let trim l =
186-
let rec aux = function
187-
| [] -> []
188-
| h::t -> if String.trim h = "" then aux t else String.trim h :: t
189-
in
190-
let no_head = aux l in
191-
let no_tail = List.rev (aux (List.rev no_head)) in
192-
no_tail
193-
194185
type file = { first: Mdx_top.Part.file; current: Mdx_top.Part.file }
195186

196187
let files: (string, file) Hashtbl.t = Hashtbl.create 8
@@ -228,8 +219,8 @@ let update_block_with_file ppf t file part =
228219
(match part with None -> "" | Some p -> p)
229220
file
230221
| Some lines ->
231-
let lines = trim lines in
232222
let contents = String.concat "\n" lines in
223+
let contents = String.trim contents in
233224
Output.pp ppf (`Output contents);
234225
Block.pp_footer ppf ()
235226

lib/top/part.ml

Lines changed: 72 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -14,253 +14,78 @@
1414
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515
*)
1616

17-
open Mdx.Migrate_ast
1817
open Mdx.Compat
1918

2019
module Part = struct
2120

2221
type t =
2322
{ name: string;
23+
sep_indent: string; (** Whitespaces before the [@@@part] separator *)
2424
body: string; }
2525

26-
let v ~name ~body = { name; body }
26+
let v ~name ~sep_indent ~body = { name; sep_indent; body }
2727
let name {name;_} = name
28+
let sep_indent {sep_indent;_} = sep_indent
2829
let body {body;_} = body
2930

3031
end
3132

32-
module Lexbuf = struct
33-
34-
open Lexing
35-
36-
type t = {
37-
contents: string;
38-
lexbuf : lexbuf;
39-
}
40-
41-
let initial_pos name = {
42-
pos_fname = name;
43-
pos_lnum = 1;
44-
pos_bol = 0;
45-
pos_cnum = 0;
46-
}
47-
48-
let v ~fname contents =
49-
let lexbuf = Lexing.from_string contents in
50-
lexbuf.lex_curr_p <- initial_pos fname;
51-
Location.input_name := fname;
52-
{ contents; lexbuf }
53-
54-
let of_file fname =
55-
let ic = open_in fname in
56-
let len = in_channel_length ic in
57-
let result = really_input_string ic len in
58-
close_in_noerr ic;
59-
v ~fname result
33+
(** Remove empty strings at the beginning of a list *)
34+
let rec remove_empty_heads = function
35+
| "" :: tl -> remove_empty_heads tl
36+
| l -> l
37+
38+
let trim_empty_rev l =
39+
remove_empty_heads (List.rev (remove_empty_heads l))
40+
41+
module Parse_parts =
42+
struct
43+
44+
let part_statement_re =
45+
let open Re in
46+
let ws = rep space in
47+
compile @@ whole_string @@ seq [
48+
group ws; str "[@@@"; ws; str "part"; ws;
49+
str "\""; group (rep1 any); str "\"";
50+
ws; str "]"; ws; opt (str ";;"); ws;
51+
]
52+
53+
let next_part ~name ~sep_indent = fun lines_rev ->
54+
let body = String.concat "\n" (trim_empty_rev lines_rev) in
55+
Part.v ~name ~sep_indent ~body
56+
57+
let next_part_of_groups groups =
58+
let sep_indent = Re.Group.get groups 1 in
59+
let name = Re.Group.get groups 2 in
60+
next_part ~name ~sep_indent
61+
62+
let rec parse_parts input make_part lines =
63+
match input_line input with
64+
| exception End_of_file -> [make_part lines]
65+
| line ->
66+
match Re.exec_opt part_statement_re line with
67+
| None -> parse_parts input make_part (line :: lines)
68+
| Some groups ->
69+
let next_part = next_part_of_groups groups in
70+
make_part lines :: parse_parts input next_part []
71+
72+
let of_file name =
73+
let input = open_in name in
74+
parse_parts input (next_part ~name:"" ~sep_indent:"") []
6075

6176
end
6277

63-
module Phrase = struct
64-
65-
open Lexing
66-
open Parsetree
67-
68-
type kind = Code | Part of string
69-
70-
exception Cannot_parse_payload of Location.t
71-
72-
let string_of_location
73-
{Location.loc_start = {pos_fname; pos_lnum; pos_bol; pos_cnum};_}
74-
=
75-
Printf.sprintf "%s, line %d, col %d" pos_fname pos_lnum (pos_cnum - pos_bol)
76-
77-
let payload_constants loc = function
78-
| PStr [{pstr_desc = Pstr_eval (expr, _); _}] ->
79-
let one {pexp_loc; pexp_desc; _} = match pexp_desc with
80-
| Pexp_apply ({pexp_desc = Pexp_ident ident; _},
81-
[Asttypes.Nolabel, {pexp_desc = Pexp_constant const; _}]) ->
82-
(pexp_loc, Some ident, const)
83-
| Pexp_constant const -> (pexp_loc, None, const)
84-
| _ -> raise (Cannot_parse_payload pexp_loc)
85-
in
86-
let rec consts = function
87-
| {pexp_desc=Pexp_sequence(e, rest); _} -> one e :: consts rest
88-
| e -> [one e]
89-
in
90-
consts expr
91-
| PStr [] -> []
92-
| _ -> raise (Cannot_parse_payload loc)
93-
94-
let payload_strings loc = function
95-
| PStr [] -> []
96-
| x ->
97-
let aux = function
98-
| _, Some {Location.txt = Longident.Lident "ocaml"; _},
99-
Pconst_string (str, _) -> (`OCaml, str)
100-
| _, None, Pconst_string (str, _) -> (`Raw, str)
101-
| loc, _, _ -> raise (Cannot_parse_payload loc)
102-
in
103-
List.map aux (payload_constants loc x)
104-
105-
let kind_impl = function
106-
| {pstr_desc = Pstr_attribute (name, payload); pstr_loc}
107-
when name.Asttypes.txt = "part" ->
108-
begin match payload_strings pstr_loc payload with
109-
| [`Raw, part] -> Part part
110-
| _ ->
111-
prerr_endline
112-
(string_of_location pstr_loc ^ ": cannot parse [@@@part] payload");
113-
Code
114-
| exception (Cannot_parse_payload loc) ->
115-
prerr_endline
116-
(string_of_location loc ^ ": cannot parse [@@@part] payload");
117-
Code
118-
end
119-
| _ -> Code
120-
121-
let kind_intf = function
122-
| {psig_desc = Psig_attribute (name, payload); psig_loc}
123-
when name.Asttypes.txt = "part" ->
124-
begin match payload_strings psig_loc payload with
125-
| [`Raw, part] -> Part part
126-
| _ ->
127-
prerr_endline
128-
(string_of_location psig_loc ^ ": cannot parse [@@@part] payload");
129-
Code
130-
| exception (Cannot_parse_payload loc) ->
131-
prerr_endline
132-
(string_of_location loc ^ ": cannot parse [@@@part] payload");
133-
Code
134-
end
135-
| _ -> Code
136-
137-
138-
(* by default, [structure_item] locations do not contain the [;;] token,
139-
so here we try to extend the location when this is needed. *)
140-
let shift_semi_semi doc loc =
141-
let str = doc.Lexbuf.contents in
142-
let stop = loc.pos_cnum in
143-
let rec aux n =
144-
if n+1 >= String.length str then loc
145-
else match str.[n], str.[n+1] with
146-
| '\n', _ -> aux (n+1)
147-
| ';', ';' -> { loc with pos_cnum = n + 2 }
148-
| _, _ -> loc
149-
in
150-
aux stop
151-
152-
let body_impl doc s =
153-
let start = match s with
154-
| s::_ -> Some s.pstr_loc.loc_start.pos_cnum
155-
| _ -> None
156-
in
157-
let stop = match List.rev s with
158-
| s::_ -> Some (shift_semi_semi doc s.pstr_loc.loc_end).pos_cnum
159-
| _ -> None
160-
in
161-
match start, stop with
162-
| Some start, Some stop ->
163-
String.sub doc.Lexbuf.contents start (stop - start)
164-
| _ -> ""
78+
type file = Part.t list
16579

166-
let body_intf doc s =
167-
let start = match s with
168-
| s::_ -> Some s.psig_loc.loc_start.pos_cnum
169-
| _ -> None
170-
in
171-
let stop = match List.rev s with
172-
| s::_ -> Some (shift_semi_semi doc s.psig_loc.loc_end).pos_cnum
173-
| _ -> None
174-
in
175-
match start, stop with
176-
| Some start, Some stop ->
177-
String.sub doc.Lexbuf.contents start (stop - start)
178-
| _ -> ""
80+
let read file = Parse_parts.of_file file
17981

180-
let parts ~body doc phrases =
181-
let rec aux parts part strs = function
182-
| (s, Code) :: rest -> aux parts part (s :: strs) rest
183-
| (_, Part name) :: rest ->
184-
let body = body doc (List.rev strs) in
185-
let parts = Part.v ~name:part ~body :: parts in
186-
aux parts name [] rest
187-
| [] ->
188-
let parts =
189-
if part <> "" || strs <> [] then
190-
let body = body doc (List.rev strs) in
191-
Part.v ~name:part ~body :: parts
192-
else
193-
if List.length parts = 0 then
194-
[Part.v ~name:"" ~body:""]
195-
else
196-
parts
197-
in
198-
List.rev parts
199-
in
200-
aux [] "" [] phrases
201-
202-
let handle_syntax_error e =
203-
#if OCAML_MAJOR >= 4 && OCAML_MINOR >= 8
204-
(* The function is now Parse.prepare_error, but it is not
205-
exposed; luckily enough, it is register to print the
206-
exception. *)
207-
Fmt.failwith "Cannot parse: %s" (Printexc.to_string (Syntaxerr.Error e))
208-
#else
209-
Fmt.failwith "Cannot parse: %a" Syntaxerr.report_error e
210-
#endif
211-
212-
let read_impl doc =
213-
try
214-
let strs = Parse.implementation doc.Lexbuf.lexbuf in
215-
List.map (fun x -> x, kind_impl x) strs
216-
with Syntaxerr.Error e ->
217-
handle_syntax_error e
218-
219-
let read_intf doc =
220-
try
221-
let strs = Parse.interface doc.Lexbuf.lexbuf in
222-
List.map (fun x -> x, kind_intf x) strs
223-
with Syntaxerr.Error e ->
224-
handle_syntax_error e
225-
226-
end
227-
228-
type file =
229-
| Parts of Part.t list
230-
| Body of (exn * string)
231-
232-
let read_impl lexbuf =
233-
Phrase.(parts ~body:body_impl lexbuf (read_impl lexbuf))
234-
235-
let read_intf lexbuf =
236-
Phrase.(parts ~body:body_intf lexbuf (read_intf lexbuf))
237-
238-
let read file =
239-
let lexbuf = Lexbuf.of_file file in
240-
let read = match Filename.extension file with
241-
| ".ml" -> read_impl
242-
| ".mli" -> read_intf
243-
| s -> Fmt.failwith "unknown extension: %s" s
244-
in
245-
try
246-
lexbuf
247-
|> read
248-
|> fun x -> Parts x
249-
with e ->
250-
Body (e, lexbuf.Lexbuf.contents)
251-
252-
let err_parse_error (e, _) =
253-
Fmt.failwith "Parse error: %a" Fmt.exn e
254-
255-
let find file ~part = match file, part with
256-
| Body (_, s), None -> Some [s]
257-
| Body b, _ -> err_parse_error b
258-
| Parts parts, Some part ->
259-
(match List.find_opt (fun p -> String.equal (Part.name p) part) parts with
82+
let find file ~part = match part with
83+
| Some part ->
84+
(match List.find_opt (fun p -> String.equal (Part.name p) part) file with
26085
| Some p -> Some [Part.body p]
26186
| None -> None )
262-
| Parts parts, None ->
263-
List.fold_left (fun acc p -> Part.body p :: [""] @ acc) [] parts
87+
| None ->
88+
List.fold_left (fun acc p -> Part.body p :: [""] @ acc) [] file
26489
|> List.rev
26590
|> fun x -> Some x
26691

@@ -270,27 +95,23 @@ let rec replace_or_append part_name body = function
27095
| p :: tl ->
27196
p :: replace_or_append part_name body tl
27297
| [] ->
273-
[{ name = part_name; body }]
274-
275-
let replace file ~part ~lines = match file, part with
276-
| Body (e, _), None -> Body (e, String.concat "\n" lines)
277-
| Body b , _ -> err_parse_error b
278-
| Parts parts, _ ->
279-
let part = match part with None -> "" | Some p -> p in
280-
let parts = replace_or_append part (String.concat "\n" lines) parts in
281-
Parts parts
282-
283-
let contents = function
284-
| Body (_, s) -> String.trim s ^ "\n"
285-
| Parts parts ->
286-
let lines =
287-
List.fold_left (fun acc p ->
288-
let body = Part.body p in
289-
match Part.name p with
290-
| "" -> body :: acc
291-
| n -> body :: ("\n[@@@part \"" ^ n ^ "\"] ;;\n") :: acc
292-
) [] parts
293-
in
294-
let lines = List.rev lines in
295-
let lines = String.concat "\n" lines in
296-
String.trim lines ^ "\n"
98+
[{ name = part_name; sep_indent = ""; body }]
99+
100+
let replace file ~part ~lines =
101+
let part = match part with None -> "" | Some p -> p in
102+
replace_or_append part (String.concat "\n" lines) file
103+
104+
let contents file =
105+
let lines =
106+
List.fold_left (fun acc p ->
107+
let body = Part.body p in
108+
match Part.name p with
109+
| "" -> body :: acc
110+
| n ->
111+
let indent = Part.sep_indent p in
112+
body :: ("\n" ^ indent ^ "[@@@part \"" ^ n ^ "\"] ;;\n") :: acc
113+
) [] file
114+
in
115+
let lines = List.rev lines in
116+
let lines = String.concat "\n" lines in
117+
String.trim lines ^ "\n"

test/sync_to_ml.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ let () =
4040
()
4141
```
4242

43+
```ocaml file=sync_to_ml.mli,part=1
44+
module A =
45+
struct
46+
```
47+
48+
```ocaml file=sync_to_ml.mli,part=2
49+
type t = Some of int | Many
50+
```
51+
4352
```ocaml file=sync_to_ml.mli,part=3
44-
type t = Some of int | Many
53+
end
4554
```

0 commit comments

Comments
 (0)