1414 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515 *)
1616
17- open Mdx.Migrate_ast
1817open Mdx.Compat
1918
2019module 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
3031end
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
6176end
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 "
0 commit comments