Skip to content

Commit 8868a7d

Browse files
hhugoclaude
andcommitted
Absorb Parse_with_comments into Extended_ast, simplify Translation_unit
Move the parsing pipeline (warnings, hash-bang, tokens, Source.t creation) from Parse_with_comments into Extended_ast: - Extended_ast.parse is the full parsing pipeline - Extended_ast.parse_toplevel dispatches Use_file vs Repl_file - Extended_ast.Parsed.t replaces Parse_with_comments.with_comments - Extended_ast.Warning50 replaces Parse_with_comments.Warning50 - Delete Parse_with_comments.ml/.mli Simplify Translation_unit: remove std_fg/std_parsed parameters from format and parse_and_format, use Extended_ast.equivalent for AST validation. Translation_unit no longer references Std_ast directly. Rewrite printast tool to use the unified Extended_ast.parse API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7706073 commit 8868a7d

9 files changed

Lines changed: 248 additions & 324 deletions

File tree

lib/Extended_ast.ml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,146 @@ module Asttypes = struct
425425
let is_recursive = function Recursive -> true | Nonrecursive -> false
426426
end
427427

428+
module Parsed = struct
429+
type 'a t =
430+
{ast: 'a; comments: Cmt.t list; prefix: string; source: Source.t}
431+
end
432+
433+
exception Warning50 of (Location.t * Warnings.t) list
434+
435+
module W = struct
436+
type t = int
437+
438+
let in_lexer : t list = [1; 2; 3; 14; 29]
439+
440+
let disable x = -abs x
441+
442+
let enable x = abs x
443+
444+
let to_string x =
445+
String.concat ~sep:"" (List.map ~f:(Format.sprintf "%+d") x)
446+
end
447+
448+
let tokens lexbuf =
449+
let rec loop acc =
450+
match Lexer.token_with_comments lexbuf with
451+
(* The location in lexbuf are invalid for comments *)
452+
| COMMENT (_, loc) as tok -> loop ((tok, loc) :: acc)
453+
| DOCSTRING ds as tok -> loop ((tok, Docstrings.docstring_loc ds) :: acc)
454+
| tok -> (
455+
let loc = Migrate_ast.Location.of_lexbuf lexbuf in
456+
let acc = (tok, loc) :: acc in
457+
match tok with EOF -> List.rev acc | _ -> loop acc )
458+
in
459+
loop []
460+
461+
let fresh_lexbuf source =
462+
let lexbuf = Lexing.from_string source in
463+
Location.init_info lexbuf !Location.input_name ;
464+
let hash_bang =
465+
Lexer.skip_hash_bang lexbuf ;
466+
let len = lexbuf.lex_last_pos in
467+
String.sub source ~pos:0 ~len
468+
in
469+
(lexbuf, hash_bang)
470+
471+
let split_hash_bang source =
472+
let lexbuf = Lexing.from_string source in
473+
Location.init_info lexbuf !Location.input_name ;
474+
Lexer.skip_hash_bang lexbuf ;
475+
let len = lexbuf.lex_last_pos in
476+
let hash_bang = String.sub source ~pos:0 ~len in
477+
let rest = String.sub source ~pos:len ~len:(String.length source - len) in
478+
(rest, hash_bang)
479+
480+
let collect_comments () =
481+
List.map (Lexer.comments ()) ~f:(function
482+
| `Comment txt, loc -> Cmt.create_comment txt loc
483+
| `Docstring txt, loc -> Cmt.create_docstring txt loc )
484+
485+
let parse_ocaml ?(disable_w50 = false) ?(disable_deprecated = false) fg
486+
(conf : Conf.t) ~input_name ~source =
487+
let warnings =
488+
if conf.opr_opts.quiet.v then List.map ~f:W.disable W.in_lexer else []
489+
in
490+
let warnings = if disable_w50 then warnings else W.enable 50 :: warnings in
491+
ignore @@ Warnings.parse_options false (W.to_string warnings) ;
492+
let w50 = ref [] in
493+
let t =
494+
let source, hash_bang = split_hash_bang source in
495+
Warning.with_warning_filter
496+
~filter_warning:(fun loc warn ->
497+
if
498+
Warning.is_unexpected_docstring warn
499+
&& conf.opr_opts.comment_check.v
500+
then (
501+
w50 := (loc, warn) :: !w50 ;
502+
false )
503+
else not conf.opr_opts.quiet.v )
504+
~filter_alert:(fun _loc alert ->
505+
if Warning.is_deprecated_alert alert && disable_deprecated then false
506+
else not conf.opr_opts.quiet.v )
507+
~f:(fun () ->
508+
let ocaml_version = conf.opr_opts.ocaml_version.v in
509+
let preserve_beginend =
510+
Poly.(conf.fmt_opts.exp_grouping.v = `Preserve)
511+
in
512+
let prefer_let_puns =
513+
match conf.fmt_opts.letop_punning.v with
514+
| `Always -> Some true
515+
| `Never -> Some false
516+
| `Preserve -> None
517+
in
518+
let ast =
519+
Parse.ast fg ~ocaml_version ~preserve_beginend ~prefer_let_puns
520+
~input_name source
521+
in
522+
let comments = collect_comments () in
523+
Warnings.check_fatal () ;
524+
let tokens =
525+
let lexbuf, _ = fresh_lexbuf source in
526+
tokens lexbuf
527+
in
528+
let source = Source.create ~text:source ~tokens in
529+
{Parsed.ast; comments; prefix= hash_bang; source} )
530+
in
531+
match List.rev !w50 with [] -> t | w50 -> raise (Warning50 w50)
532+
533+
let parse (type a) ?disable_w50 ?disable_deprecated (fg : a t) conf
534+
~input_name ~source : a Parsed.t =
535+
match fg with
536+
| Documentation ->
537+
let pos = {Lexing.dummy_pos with pos_fname= input_name} in
538+
let ast = Docstring.parse_file pos source in
539+
{ Parsed.ast
540+
; comments= []
541+
; prefix= ""
542+
; source= Source.create ~text:source ~tokens:[] }
543+
| fg ->
544+
parse_ocaml ?disable_w50 ?disable_deprecated fg conf ~input_name
545+
~source
546+
547+
(** [is_repl_block x] returns whether [x] is a list of REPL phrases and
548+
outputs of the form:
549+
550+
{v
551+
# let this is = some phrase;;
552+
this is some output
553+
v} *)
554+
let is_repl_block x =
555+
String.length x >= 2 && Char.equal x.[0] '#' && Char.is_whitespace x.[1]
556+
557+
let parse_toplevel ?disable_w50 ?disable_deprecated (conf : Conf.t)
558+
~input_name ~source =
559+
if is_repl_block source && conf.fmt_opts.parse_toplevel_phrases.v then
560+
Either.Second
561+
(parse ?disable_w50 ?disable_deprecated Repl_file conf ~input_name
562+
~source )
563+
else
564+
First
565+
(parse ?disable_w50 ?disable_deprecated Use_file conf ~input_name
566+
~source )
567+
428568
type std_value = Std_value : 'a Std_ast.t * 'a -> std_value
429569

430570
let get_std (type a) (fg : a t) (v : a) : std_value option =

lib/Extended_ast.mli

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,6 @@ type any_t = Any : 'a t -> any_t [@@unboxed]
3939

4040
val of_syntax : Syntax.t -> any_t
4141

42-
module Parse : sig
43-
val ast :
44-
'a t
45-
-> ocaml_version:Ocaml_version.t
46-
-> preserve_beginend:bool
47-
-> prefer_let_puns:bool option
48-
-> input_name:string
49-
-> string
50-
-> 'a
51-
end
52-
5342
val map : 'a t -> Ast_mapper.mapper -> 'a -> 'a
5443

5544
module Printast : sig
@@ -66,6 +55,36 @@ module Asttypes : sig
6655
val is_recursive : rec_flag -> bool
6756
end
6857

58+
module Parsed : sig
59+
type 'a t =
60+
{ast: 'a; comments: Cmt.t list; prefix: string; source: Source.t}
61+
end
62+
63+
exception Warning50 of (Location.t * Warnings.t) list
64+
65+
val parse :
66+
?disable_w50:bool
67+
-> ?disable_deprecated:bool
68+
-> 'a t
69+
-> Conf.t
70+
-> input_name:string
71+
-> source:string
72+
-> 'a Parsed.t
73+
(** Parse source with warning handling, hash-bang detection, and token
74+
collection. For paired fragment types, also parses with the standard
75+
parser. *)
76+
77+
val parse_toplevel :
78+
?disable_w50:bool
79+
-> ?disable_deprecated:bool
80+
-> Conf.t
81+
-> input_name:string
82+
-> source:string
83+
-> ( (use_file, Std_parsetree.toplevel_phrase list) paired Parsed.t
84+
, repl_file Parsed.t )
85+
Either.t
86+
(** Parse source as toplevel phrases or REPL phrases depending on content. *)
87+
6988
type std_value = Std_value : 'a Std_ast.t * 'a -> std_value
7089

7190
val get_std : 'a t -> 'a -> std_value option

lib/Fmt_ast.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5144,13 +5144,13 @@ let fmt_code ~debug =
51445144
let warn = fmt_opts.parse_toplevel_phrases.v in
51455145
let input_name = !Location.input_name in
51465146
match
5147-
Parse_with_comments.parse_toplevel ~disable_deprecated:true conf
5148-
~input_name ~source:s
5147+
Extended_ast.parse_toplevel ~disable_deprecated:true conf ~input_name
5148+
~source:s
51495149
with
5150-
| Either.First {ast; comments; source; prefix= _} ->
5150+
| Either.First {Parsed.ast; comments; source; prefix= _} ->
51515151
fmt_parse_result conf ~debug Use_file ast source comments ~set_margin
51525152
~fmt_code
5153-
| Second {ast; comments; source; prefix= _} ->
5153+
| Second {Parsed.ast; comments; source; prefix= _} ->
51545154
fmt_parse_result conf ~debug Repl_file ast source comments
51555155
~set_margin ~fmt_code
51565156
| exception Syntaxerr.Error (Expecting (_, x)) when warn ->

lib/Normalize_extended_ast.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ let normalize_parse_result ~normalize_cmt ast_kind ast comments =
5454
let normalize_code ~normalize_cmt conf (m : Ast_mapper.mapper) txt =
5555
let input_name = "<output>" in
5656
let normalize_cmt = normalize_cmt conf in
57-
match Parse_with_comments.parse_toplevel conf ~input_name ~source:txt with
58-
| First {ast; comments; _} ->
57+
match Extended_ast.parse_toplevel conf ~input_name ~source:txt with
58+
| First {Parsed.ast; comments; _} ->
5959
normalize_parse_result ~normalize_cmt Use_file (map Use_file m ast)
6060
comments
61-
| Second {ast; comments; _} ->
61+
| Second {Parsed.ast; comments; _} ->
6262
normalize_parse_result ~normalize_cmt Repl_file
6363
(List.map ~f:(m.repl_phrase m) ast)
6464
comments

lib/Parse_with_comments.ml

Lines changed: 0 additions & 137 deletions
This file was deleted.

0 commit comments

Comments
 (0)