(* 'let' names nets (scoped vars) 'fun' names ports (argument vars) digraph { compound=true; node [shape=record]; edge [decorate=true]; rankdir="LR"; subgraph G { label="let rec fact n = if n = 0 then 1. else float n *. fact (n - 1)"; cluster=true; labeljust="l"; "1."; "if/then/else" [label="{{<1>if|<2>then|<3>else}|}"]; "1"; "0"; minus [label="{{<1>|<2>}|-}"]; float_mult [label="{{<1>|<2>}|*.}"]; "float" [label="{<1>|float}"]; "(=)" [label="{{<1>|<2>}|=}"]; "fun n" [label="{fun|<1>n}"]; "factorial" [label="{<1>|factorial}"]; "1." -> "if/then/else":2 [label="float"]; "fun n":1 -> minus:1 [lhead="cluster_minus" label="int"]; "fun n":1 -> "(=)":2 [label="int"]; "fun n":1 -> "float":1 [label="int"]; "if/then/else" -> "->":o [label="float"]; "1" -> minus:2 [lhead="cluster_minus" label="int"]; "0" -> "(=)":1 [label="int"]; minus:o -> "factorial":1 [label="int"]; float_mult:o -> "if/then/else":3 [label="float"]; "float":o -> float_mult:1 [label="float"]; "(=)":o -> "if/then/else":1 [label="bool"]; "factorial":o -> float_mult:2 [label="float"]; } } *) open Merlin_kernel open Merlin_analysis open Merlin_specific open Merlin_utils open Ocaml_parsing open Ocaml_typing let _ = Logs.set_reporter (Logs_fmt.reporter ()); Logs.set_level (Some Debug) module F = struct include Fmt let bar ppf () = string ppf "|" end module Log = (val Logs.src_log (Logs.Src.create "dot_of_tast" ~doc:"dot_of_tast.ml logger") : Logs.LOG) open Graph module DotAttributes = struct include Graphviz.DotAttributes (* the dot spec probably added records after ocamlgraph/src/graphviz.ml was written *) type edge = [ Graphviz.DotAttributes.edge | `HeadportRecord of string | `TailportRecord of string ] end module Gr = struct open DotAttributes module Vertex = struct type t = { name : string; attr : DotAttributes.vertex list; subgraph : DotAttributes.subgraph option; } let compare a b = String.compare a.name b.name let hash { name; _ } = Hashtbl.hash name let equal a b = compare a b = 0 let label a = a.name let default = { name = ""; attr = []; subgraph = None } end module Edge = struct type t = { name : string; attr : DotAttributes.edge list } let compare a b = String.compare a.name b.name let default = { name = ""; attr = [] } end module G = Imperative.Digraph.ConcreteLabeled (Vertex) (Edge) module B = Builder.I (G) module V = G.V module E = G.E type t = G.t * graph list let iter_vertex a (b, _) = G.iter_vertex a b let iter_edges_e a (b, _) = G.iter_edges_e a b let add_edge_e (a, _) b = G.add_edge_e a b let vertex_name v = F.str "\"%s\"" (String.escaped (Vertex.label v)) let graph_attributes (_g, attr) = attr let default_vertex_attributes _ = [ `Shape `Record ] let vertex_attributes (a : V.t) = a.attr let default_edge_attributes _ = [ `Decorate true ] let edge_attributes ((_src, { name; attr; _ }, _dst) : E.t) = if String.equal name "" then attr else `Label name :: attr let get_subgraph (v : G.V.t) : Graphviz.DotAttributes.subgraph option = v.subgraph let create ?(size = 32) attr = (B.G.create ~size (), attr) end module Dot = struct module X = struct include Gr end open Format module EN = struct type color = int type color_with_transparency = int32 let color_to_color_with_transparency color = Int32.add (Int32.shift_left (Int32.of_int color) 8) 0xFFl let fprint_color ppf color = fprintf ppf "\"#%06X\"" color let fprint_color_with_transparency ppf color = fprintf ppf "\"#%08lX\"" color let fprint_string ppf s = fprintf ppf "\"%s\"" s (* let s' = String.escaped s in if s' = s && s <> "" then fprintf ppf "%s" s else fprintf ppf "\"%s\"" s'*) let fprint_string_user ppf s = (* let s = String.escaped s in*) fprintf ppf "\"%s\"" s let fprint_htmlstring_user ppf s = fprintf ppf "<%s>" s let fprint_square_not_empty printer ppf = function | [] -> () | l -> fprintf ppf " [%a]" printer l type arrow_style = [ `None | `Normal | `Onormal | `Inv | `Dot | `Odot | `Invdot | `Invodot ] let fprint_arrow_style ppf = function | `None -> fprintf ppf "none" | `Normal -> fprintf ppf "normal" | `Onormal -> fprintf ppf "onormal" | `Inv -> fprintf ppf "inv" | `Dot -> fprintf ppf "dot" | `Odot -> fprintf ppf "odot" | `Invdot -> fprintf ppf "invdot" | `Invodot -> fprintf ppf "invodot" let fprint_dir ppf = function | `TopToBottom -> fprintf ppf "TB" | `BottomToTop -> fprintf ppf "BT" | `LeftToRight -> fprintf ppf "LR" | `RightToLeft -> fprintf ppf "RL" type symbseq = COMMA | SEMI let fprint_symbseq ppf = function | COMMA -> pp_print_string ppf "," | SEMI -> pp_print_string ppf ";" module Attributes = struct module CommonAttributes = struct include Graphviz.CommonAttributes (** Pretty-print. *) let fprint_orientation ppf = function | `Portrait -> fprintf ppf "portrait" | `Landscape -> fprintf ppf "landscape" let fprint_graph ppf = function | `Center b -> fprintf ppf "center=%i" (if b then 1 else 0) | `Fontcolor a -> fprintf ppf "fontcolor=%a" fprint_color a | `Fontname s -> fprintf ppf "fontname=%a" fprint_string s | `Fontsize i -> fprintf ppf "fontsize=%i" i | `Label s -> fprintf ppf "label=%a" fprint_string_user s | `HtmlLabel s -> fprintf ppf "label=%a" fprint_htmlstring_user s | `Orientation a -> fprintf ppf "orientation=%a" fprint_orientation a | `Page (x, y) -> fprintf ppf "page=\"%f,%f\"" x y | `Pagedir a -> fprintf ppf "pagedir=%a" fprint_dir a | `Size (x, y) -> fprintf ppf "size=\"%f,%f\"" x y | `OrderingOut -> fprintf ppf "ordering=out" let fprint_shape ppf = function | `Ellipse -> fprintf ppf "ellipse" | `Box -> fprintf ppf "box" | `Circle -> fprintf ppf "circle" | `Doublecircle -> fprintf ppf "doublecircle" | `Diamond -> fprintf ppf "diamond" | `Plaintext -> fprintf ppf "plaintext" | `Record -> fprintf ppf "record" | `Egg -> fprintf ppf "egg" | `House -> fprintf ppf "house" | `Invhouse -> fprintf ppf "invhouse" | `Trapezium -> fprintf ppf "trapezium" | `Invtrapezium -> fprintf ppf "invtrapezium" | `Triangle -> fprintf ppf "triangle" | `Invtriangle -> fprintf ppf "invtriangle" | `Oval -> fprintf ppf "oval" | `Assembly -> fprintf ppf "assembly" | `Box3d -> fprintf ppf "box3d" | `Cds -> fprintf ppf "cds" | `Component -> fprintf ppf "component" | `Doubleoctagon -> fprintf ppf "doubleoctagon" | `Fivepoverhang -> fprintf ppf "fivepoverhang" | `Folder -> fprintf ppf "folder" | `Insulator -> fprintf ppf "insulator" | `Larrow -> fprintf ppf "larrow" | `Lpromoter -> fprintf ppf "lpromoter" | `Mcircle -> fprintf ppf "mcircle" | `Mdiamond -> fprintf ppf "mdiamond" | `Msquare -> fprintf ppf " msquare" | `Note -> fprintf ppf "note" | `Noverhang -> fprintf ppf "noverhang" | `Parallelogram -> fprintf ppf "parallelogram" | `Primersite -> fprintf ppf "primersite" | `Promoter -> fprintf ppf "promoter" | `Proteasesite -> fprintf ppf "proteasesite" | `Proteinstab -> fprintf ppf "proteinstab" | `Rarrow -> fprintf ppf "rarrow" | `Restrictionsite -> fprintf ppf "restrictionsite" | `Ribosite -> fprintf ppf "ribosite" | `Rnastab -> fprintf ppf "rnastab" | `Rpromoter -> fprintf ppf "rpromoter" | `Signature -> fprintf ppf "signature" | `Star -> fprintf ppf "star" | `Tab -> fprintf ppf "tab" | `Terminator -> fprintf ppf "terminator" | `Threepoverhang -> fprintf ppf "threepoverhang" | `Tripleoctagon -> fprintf ppf "tripleoctagon" | `Underline -> fprintf ppf "underline" | `Utr -> fprintf ppf "utr" | `Polygon (i, f) -> fprintf ppf "polygon, sides=%i, skew=%f" i f let rec fprint_string_list ppf = function | [] -> () | [ hd ] -> fprintf ppf "%s" hd | hd :: tl -> fprintf ppf "%s,%a" hd fprint_string_list tl let node_style_str = function | `Rounded -> "rounded" | `Filled -> "filled" | `Solid -> "solid" | `Dashed -> "dashed" | `Dotted -> "dotted" | `Bold -> "bold" | `Invis -> "invis" let fprint_style_list sep ppf a = fprintf ppf "style=\"%a\"%a@ " fprint_string_list (List.map node_style_str a) fprint_symbseq sep let fprint_vertex ppf = function | `Color a -> fprintf ppf "color=%a" fprint_color a | `ColorWithTransparency a -> fprintf ppf "color=%a" fprint_color_with_transparency a | `Fontcolor a -> fprintf ppf "fontcolor=%a" fprint_color a | `Fontname s -> fprintf ppf "fontname=%a" fprint_string s | `Fontsize i -> fprintf ppf "fontsize=%i" i | `Height f -> fprintf ppf "height=%f" f | `Label s -> fprintf ppf "label=%a" fprint_string_user s | `HtmlLabel s -> fprintf ppf "label=%a" fprint_htmlstring_user s | `Orientation f -> fprintf ppf "orientation=%f" f | `Penwidth f -> fprintf ppf "penwidth=%f" f | `Peripheries i -> fprintf ppf "peripheries=%i" i | `Regular b -> fprintf ppf "regular=%b" b | `Shape a -> fprintf ppf "shape=%a" fprint_shape a | `Style _ -> assert false | `Width f -> fprintf ppf "width=%f" f let fprint_arrow_direction ppf = function | `Forward -> fprintf ppf "forward" | `Back -> fprintf ppf "back" | `Both -> fprintf ppf "both" | `None -> fprintf ppf "none" let fprint_edge ppf = function | `Color a -> fprintf ppf "color=%a" fprint_color a | `ColorWithTransparency a -> fprintf ppf "color=%a" fprint_color_with_transparency a | `Decorate b -> fprintf ppf "decorate=%b" b | `Dir a -> fprintf ppf "dir=%a" fprint_arrow_direction a | `Fontcolor a -> fprintf ppf "fontcolor=%a" fprint_color a | `Fontname s -> fprintf ppf "fontname=%a" fprint_string s | `Fontsize i -> fprintf ppf "fontsize=%i" i | `Label s -> fprintf ppf "label=%a" fprint_string_user s | `HtmlLabel s -> fprintf ppf "label=%a" fprint_htmlstring_user s | `Labelfontcolor a -> fprintf ppf "labelfontcolor=%a" fprint_color a | `Labelfontname s -> fprintf ppf "labelfontname=\"%s\"" s (* (String.escaped s) *) | `Labelfontsize i -> fprintf ppf "labelfontsize=%i" i | `Penwidth f -> fprintf ppf "penwidth=%f" f | `Style _ -> assert false let rec filter_style al sl l = match l with | [] -> (al, sl) | `Style s :: l -> filter_style al (s :: sl) l | a :: l -> filter_style (a :: al) sl l (** [fprint_graph_attribute printer ppf list] pretty prints a list of attributes on the formatter [ppf], using the printer [printer] for each attribute. The list appears between brackets and attributes are speparated by ",". If the list is empty, nothing is printed. *) let fprint_attributes fprint_style_list fprint_attribute sep ppf list = if list <> [] then ( let list, styles = filter_style [] [] list in let rec fprint_attributes_rec ppf = function | [] -> () | hd :: tl -> fprintf ppf "%a%a@ " fprint_attribute hd fprint_symbseq sep; fprint_attributes_rec ppf tl in fprintf ppf "@[%a" fprint_attributes_rec list; if styles <> [] then fprint_style_list sep ppf styles; fprintf ppf "@]") end include DotAttributes (** {4 Pretty-print of attributes} *) let rec fprint_string_list ppf = function | [] -> () | [ hd ] -> fprintf ppf "%s" hd | hd :: tl -> fprintf ppf "%s,%a" hd fprint_string_list tl let fprint_ratio ppf = function | `Float f -> fprintf ppf "%f" f | `Fill -> fprintf ppf "fill" | `Compress -> fprintf ppf "compress" | `Auto -> fprintf ppf "auto" let fprint_graph ppf = function | #CommonAttributes.graph as att -> CommonAttributes.fprint_graph ppf att | `Bgcolor a -> fprintf ppf "bgcolor=%a" fprint_color a | `BgcolorWithTransparency a -> fprintf ppf "bgcolor=%a" fprint_color_with_transparency a | `Comment s -> fprintf ppf "comment=%a" fprint_string s | `Concentrate b -> fprintf ppf "concentrate=%b" b | `Fontpath s -> fprintf ppf "fontpath=%a" fprint_string s | `Layers s -> fprintf ppf "layers=%a" fprint_string_list s | `Margin f -> fprintf ppf "margin=%f" f | `Mclimit f -> fprintf ppf "mclimit=%f" f | `Nodesep f -> fprintf ppf "nodesep=%f" f | `Nslimit i -> fprintf ppf "nslimit=%i" i | `Nslimit1 i -> fprintf ppf "nslimit1=%i" i | `Ranksep f -> fprintf ppf "ranksep=%f" f | `Quantum f -> fprintf ppf "quantum=%f" f | `Rankdir a -> fprintf ppf "rankdir=%a" fprint_dir a | `Ratio a -> fprintf ppf "ratio=%a" fprint_ratio a | `Samplepoints i -> fprintf ppf "samplepoints=%i" i | `Url s -> fprintf ppf "URL=\"%s\"" s (*(String.escaped s)*) let fprint_vertex ppf = function | #CommonAttributes.vertex as att -> CommonAttributes.fprint_vertex ppf att | `Comment s -> fprintf ppf "comment=%a" fprint_string s | `Distortion f -> fprintf ppf "distortion=%f" f | `Fillcolor a -> fprintf ppf "fillcolor=%a" fprint_color a | `FillcolorWithTransparency a -> fprintf ppf "fillcolor=%a" fprint_color_with_transparency a | `Fixedsize b -> fprintf ppf "fixedsize=%b" b | `Layer s -> fprintf ppf "layer=%a" fprint_string s | `Url s -> fprintf ppf "URL=\"%s\"" s (*(String.escaped s)*) | `Z f -> fprintf ppf "z=%f" f let fprint_port ppf = function | `N -> fprintf ppf "n" | `NE -> fprintf ppf "ne" | `E -> fprintf ppf "e" | `SE -> fprintf ppf "se" | `S -> fprintf ppf "s" | `SW -> fprintf ppf "sw" | `W -> fprintf ppf "w" | `NW -> fprintf ppf "nw" let fprint_edge ppf = function | #CommonAttributes.edge as att -> CommonAttributes.fprint_edge ppf att | `Arrowhead a -> fprintf ppf "arrowhead=%a" fprint_arrow_style a | `Arrowsize f -> fprintf ppf "arrowsize=%f" f | `Arrowtail a -> fprintf ppf "arrowtail=%a" fprint_arrow_style a | `Comment s -> fprintf ppf "comment=%a" fprint_string s | `Constraint b -> fprintf ppf "constraint=%b" b | `Headlabel s -> fprintf ppf "headlabel=%a" fprint_string s | `Headport a -> fprintf ppf "headport=%a" fprint_port a | `HeadportRecord a -> fprintf ppf "headport=%a" fprint_string a | `Headurl s -> fprintf ppf "headURL=%a" fprint_string s | `Labelangle f -> fprintf ppf "labelangle=%f" f | `Labeldistance f -> fprintf ppf "labeldistance=%f" f | `Labelfloat b -> fprintf ppf "labelfloat=%b" b | `Layer s -> fprintf ppf "layer=%a" fprint_string s | `Minlen i -> fprintf ppf "minlen=%i" i | `Samehead s -> fprintf ppf "samehead=%a" fprint_string s | `Sametail s -> fprintf ppf "sametail=%a" fprint_string s | `Taillabel s -> fprintf ppf "taillabel=%a" fprint_string s | `Tailport a -> fprintf ppf "tailport=%a" fprint_port a | `TailportRecord a -> fprintf ppf "tailport=%a" fprint_string a | `Tailurl s -> fprintf ppf "tailURL=%a" fprint_string s | `Weight i -> fprintf ppf "weight=%i" i let fprint_vertex_list = CommonAttributes.fprint_attributes CommonAttributes.fprint_style_list fprint_vertex let fprint_edge_list = CommonAttributes.fprint_attributes CommonAttributes.fprint_style_list fprint_edge end let name = "dot" let opening = "digraph" let edge_arrow = "->" end open Format (** {3 Common attributes} *) type color = int type color_with_transparency = int32 let color_to_color_with_transparency color = Int32.add (Int32.shift_left (Int32.of_int color) 8) 0xFFl let fprint_color ppf color = fprintf ppf "\"#%06X\"" color let fprint_color_with_transparency ppf color = fprintf ppf "\"#%08lX\"" color let fprint_string ppf s = fprintf ppf "\"%s\"" s (* let s' = String.escaped s in if s' = s && s <> "" then fprintf ppf "%s" s else fprintf ppf "\"%s\"" s'*) let fprint_string_user ppf s = (* let s = String.escaped s in*) fprintf ppf "\"%s\"" s let fprint_htmlstring_user ppf s = fprintf ppf "<%s>" s let fprint_square_not_empty printer ppf = function | [] -> () | l -> fprintf ppf " [%a]" printer l type arrow_style = [ `None | `Normal | `Onormal | `Inv | `Dot | `Odot | `Invdot | `Invodot ] let fprint_arrow_style ppf = function | `None -> fprintf ppf "none" | `Normal -> fprintf ppf "normal" | `Onormal -> fprintf ppf "onormal" | `Inv -> fprintf ppf "inv" | `Dot -> fprintf ppf "dot" | `Odot -> fprintf ppf "odot" | `Invdot -> fprintf ppf "invdot" | `Invodot -> fprintf ppf "invodot" let fprint_dir ppf = function | `TopToBottom -> fprintf ppf "TB" | `BottomToTop -> fprintf ppf "BT" | `LeftToRight -> fprintf ppf "LR" | `RightToLeft -> fprintf ppf "RL" type symbseq = COMMA | SEMI let fprint_symbseq ppf = function | COMMA -> pp_print_string ppf "," | SEMI -> pp_print_string ppf ";" (* had to copy out of ocamlgraph/src/graphviz.ml because people don't make shit extensible *) let command = ref EN.name let set_command cmd = command := cmd exception Error of string let handle_error f arg = try f arg with Error msg -> Printf.eprintf "%s: %s failure\n %s\n" Sys.argv.(0) EN.name msg; flush stderr; exit 2 (** [fprint_graph_attributes ppf list] pretty prints a list of graph attributes on the formatter [ppf]. Attributes are separated by a ";". *) let fprint_graph_attributes ppf list = List.iter (function | att -> fprintf ppf "%a;@ " EN.Attributes.fprint_graph att) list (** [fprint_graph ppf graph] pretty prints the graph [graph] in the CGL language on the formatter [ppf]. *) let fprint_graph ppf graph = let module SG = Map.Make (String) in let subgraphs = ref SG.empty in (* Printing nodes. *) let print_nodes ppf = let default_node_attributes = X.default_vertex_attributes graph in if default_node_attributes <> [] then fprintf ppf "node%a;@ " (fprint_square_not_empty (EN.Attributes.fprint_vertex_list COMMA)) default_node_attributes; X.iter_vertex (function | node -> (match X.get_subgraph node with | None -> () | Some sg -> let sg, nodes = if SG.mem sg.EN.Attributes.sg_name !subgraphs then SG.find sg.EN.Attributes.sg_name !subgraphs else (sg, []) in subgraphs := SG.add sg.EN.Attributes.sg_name (sg, node :: nodes) !subgraphs); fprintf ppf "%s%a;@ " (X.vertex_name node) (fprint_square_not_empty (EN.Attributes.fprint_vertex_list COMMA)) (X.vertex_attributes node)) graph in (* Printing subgraphs *) let rec print_nested_subgraphs ppf = function | [] -> () (* no more work to do, so terminate *) | name :: worklist -> let sg, nodes = SG.find name !subgraphs in let children = SG.filter (fun _ (sg, _) -> sg.EN.Attributes.sg_parent = Some name) !subgraphs in fprintf ppf "@[subgraph cluster_%s { %a%t@ %t };@]@\n" name (EN.Attributes.fprint_vertex_list SEMI) sg.EN.Attributes.sg_attributes (fun ppf -> List.iter (fun n -> fprintf ppf "%s;" (X.vertex_name n)) nodes) (fun ppf -> print_nested_subgraphs ppf (List.map fst (SG.bindings children))); print_nested_subgraphs ppf worklist in let print_subgraphs ppf = let root_worklist = SG.filter (fun _ (sg, _) -> sg.EN.Attributes.sg_parent = None) !subgraphs in print_nested_subgraphs ppf (List.map fst (SG.bindings root_worklist)) in (* Printing edges *) let print_edges ppf = let default_edge_attributes = X.default_edge_attributes graph in if default_edge_attributes <> [] then fprintf ppf "edge%a;@ " (fprint_square_not_empty (EN.Attributes.fprint_edge_list COMMA)) default_edge_attributes; X.iter_edges_e (function | edge -> fprintf ppf "%s %s %s%a;@ " (X.vertex_name (X.E.src edge)) EN.edge_arrow (X.vertex_name (X.E.dst edge)) (fprint_square_not_empty (EN.Attributes.fprint_edge_list COMMA)) (X.edge_attributes edge)) graph in fprintf ppf "@[%s G {@ @[ %a" EN.opening fprint_graph_attributes (X.graph_attributes graph); fprintf ppf "%t@ " print_nodes; fprintf ppf "%t@ " print_subgraphs; fprintf ppf "%t@ " print_edges; fprintf ppf "@]}@;@]" (** [output_graph oc graph] pretty prints the graph [graph] in the dot language on the channel [oc]. *) let output_graph oc graph = let ppf = formatter_of_out_channel oc in fprint_graph ppf graph; pp_print_flush ppf () end module Pmapper = struct type t = Ast_mapper.mapper let default = Ast_mapper.default_mapper let remove_attributes : t = { default with attributes = (fun _m _alist -> []) } end (* Stolen from ocaml/typing/printtyped.ml cause it's not exposed!! *) module Pp = struct open Asttypes open Format open Lexing open Location open Typedtree module Path = Ocaml_typing.Path let rec fmt_longident_aux f x = match x with | Longident.Lident s -> fprintf f "%s" s | Longident.Ldot (y, s) -> fprintf f "%a.%s" fmt_longident_aux y s | Longident.Lapply (y, z) -> fprintf f "%a(%a)" fmt_longident_aux y fmt_longident_aux z let fmt_longident f x = fprintf f "\"%a\"" fmt_longident_aux x.txt let fmt_ident = Ident.print let fmt_modname f = function | None -> fprintf f "_" | Some id -> Ident.print f id let rec fmt_path f x = match x with | Path.Pident s -> fprintf f "%a" fmt_ident s | Path.Pdot (y, s) -> fprintf f "%a.%s" fmt_path y s | Path.Papply (y, z) -> fprintf f "%a(%a)" fmt_path y fmt_path z let fmt_partial f = function | Partial -> F.pf f "partial" | Total -> F.pf f "total" let fmt_position f l = if l.pos_lnum = -1 then fprintf f "%s[%d]" l.pos_fname l.pos_cnum else fprintf f "%s[%d,%d+%d]" l.pos_fname l.pos_lnum l.pos_bol (l.pos_cnum - l.pos_bol) let fmt_location f loc = fprintf f "(%a..%a)" fmt_position loc.loc_start fmt_position loc.loc_end; if loc.loc_ghost then fprintf f " ghost" end let str_of_exp (exp : Typedtree.expression) = Fmt.str "%a" Pprintast.expression Pmapper.( remove_attributes.expr remove_attributes (Untypeast.untype_expression exp)) open Typedtree let str_of_pat (pat : value general_pattern) = Fmt.str "%a" Pprintast.pattern Pmapper.( remove_attributes.pat remove_attributes (Untypeast.untype_pattern pat)) let str_of_ident = Ident.name let unique_str_of_ident = Ident.unique_name let unique_str_of_ident_pat x = match x.pat_desc with | Tpat_var (s, _) -> F.str "%a" Pp.fmt_ident s | _ -> str_of_pat x let pp_value_binding_list ppf (vbl : Typedtree.value_binding list) = Fmt.pf ppf "%a" (Fmt.parens (Fmt.list (fun ppf (ident, _loc, _ty) -> Ident.print ppf ident))) (Typedtree.let_bound_idents_full vbl) let unique_node_count = ref 0 let vertex_of_exp (exp : Typedtree.expression) : Gr.Vertex.t = (* primary isn't well defined here, it's whatever i choosen for src nodes atm *) match exp.exp_desc with | Texp_ident (path, _longident, _value_desc) -> { name = F.str "%a" Pp.fmt_path path; attr = [ `Label (F.str "{{}|%s}" (str_of_exp exp)) ]; subgraph = None; } | Texp_construct _ -> { Gr.Vertex.default with name = F.str "_Texp_construct/%d" !unique_node_count; attr = [ `Label (F.str "{%s}" (str_of_exp exp)) ]; } | Texp_constant _ -> { Gr.Vertex.default with name = F.str "_Texp_constant/%d" !unique_node_count; attr = [ `Label (F.str "{<0>|%s}" (str_of_exp exp)) ]; } | Texp_variant _ -> { Gr.Vertex.default with name = str_of_exp exp } | Texp_apply (exp, args) -> let cnt = ref 0 in let label = F.str "{{%a}|%s}" (F.list ~sep:F.bar (fun ppf -> function | Asttypes.Nolabel, _exp -> F.pf ppf "<%d>" !cnt; cnt := !cnt + 1 | Asttypes.(Labelled s | Optional s), _exp -> F.pf ppf "<%s>" s)) args (str_of_exp exp) in { Gr.Vertex.default with name = str_of_exp exp; attr = [ `Label label ]; } | Texp_let (_, vbl, _) -> { Gr.Vertex.default with name = Fmt.str "let %a" pp_value_binding_list vbl; } | Texp_ifthenelse (_, _, _) -> Gr.Vertex.default | _ -> { Gr.Vertex.default with name = Fmt.str "???? (%s)" (str_of_exp exp); } let labeled_vertex name label = { Gr.Vertex.default with name; attr = [ `Label label ] } let dst_stk : (Gr.V.t * int) Stack.t = Stack.create () let expr_iterator g (iter : Tast_iterator.iterator) (exp : Typedtree.expression) = let extra = function | Typedtree.Texp_constraint cty -> iter.typ iter cty | Texp_coerce (cty1, cty2) -> Option.iter (iter.typ iter) cty1; iter.typ iter cty2 | Texp_newtype _ -> () | Texp_newtype' _ -> () | Texp_poly cto -> Option.iter (iter.typ iter) cto in List.iter (fun (e, _, _) -> extra e) exp.exp_extra; iter.env iter exp.exp_env; let srcport = "out" in incr unique_node_count; let src = let open Typedtree in match exp.exp_desc with | Texp_apply (_exp, list) -> let src = vertex_of_exp exp in (* Tast_iterator.default_iterator.expr iter exp *) (* skip _exp *) List.fold_left (fun i (arg_label, expopt) -> Option.iter (fun ee -> Stack.push (src, i) dst_stk; iter.expr iter ee; Stack.pop dst_stk |> ignore) expopt; match arg_label with Asttypes.Nolabel -> i + 1 | _ -> i) 0 list |> ignore; src | Texp_ifthenelse (e0, e1, e2) -> let src = match e2 with | Some _ -> { Gr.Vertex.default with (* TODO make unique (with loc?) *) name = "if/then/else"; attr = [ `Label "{{<0>if|<1>then|<2>else}|}" ]; } | None -> { Gr.Vertex.default with name = "if/then"; attr = [ `Label "{{<1>if|<2>then}|}" ]; } in Stack.push (src, 0) dst_stk; iter.expr iter e0; Stack.pop dst_stk |> ignore; Stack.push (src, 1) dst_stk; iter.expr iter e1; Stack.pop dst_stk |> ignore; Option.iter (fun e' -> Stack.push (src, 2) dst_stk; iter.expr iter e'; Stack.pop dst_stk |> ignore) e2; src | Texp_function { arg_label = _; param; cases; partial = _ } -> let src = labeled_vertex (unique_str_of_ident param) (F.str "{{function %s}|{%s}}" (unique_str_of_ident param) (fst (List.fold_left (fun (s, i) { c_lhs; c_guard; _ } -> ( F.str "%s|<%d>%s%s" s i (str_of_pat c_lhs) (Option.fold ~none:"" ~some:(fun _ -> " with TODO c_guard") c_guard), i + 1 )) ("", 0) cases)) (* -> *)) in List.iteri (fun i { c_lhs; c_rhs; _ } -> Stack.push (src, i) dst_stk; iter.expr iter c_rhs; Gr.add_edge_e g ( src, Gr.Edge. { name = ""; attr = [ `HeadportRecord "in"; `TailportRecord (F.str "%d" i); ]; }, labeled_vertex (unique_str_of_ident_pat c_lhs) "{|LOL}" ); Stack.pop_opt dst_stk |> ignore) cases; src | _ -> let src = vertex_of_exp exp in Stack.push (src, 0) dst_stk; Tast_iterator.default_iterator.expr iter exp; Stack.pop_opt dst_stk |> ignore; src in match Stack.top_opt dst_stk with | Some (dst, dstport) -> Log.debug (fun m -> m "G.add_edge %s:%s %s:%s" (Gr.Vertex.label src) srcport (Gr.Vertex.label dst) (F.str "%d" dstport)); Gr.add_edge_e g ( src, Gr.Edge. { name = ""; attr = [ `HeadportRecord (F.str "%d" dstport); `TailportRecord srcport; ]; }, dst ) | None -> () let graph_of_impl (s : Typedtree.structure) : Gr.t = let g = Gr.create [ `Rankdir `LeftToRight ] in let iterator = { Tast_iterator.default_iterator with expr = expr_iterator g } in iterator.structure iterator s; g (* Stolen from ocaml/typing/printtyped.ml cause it's not exposed!! *) module P = struct open Asttypes open Format open Lexing open Location open Typedtree module Path = Ocaml_typing.Path let rec fmt_longident_aux f x = match x with | Longident.Lident s -> fprintf f "%s" s | Longident.Ldot (y, s) -> fprintf f "%a.%s" fmt_longident_aux y s | Longident.Lapply (y, z) -> fprintf f "%a(%a)" fmt_longident_aux y fmt_longident_aux z let fmt_longident f x = fprintf f "%a" fmt_longident_aux x.txt let fmt_ident = Ident.print let fmt_modname f = function | None -> fprintf f "_" | Some id -> Ident.print f id let rec fmt_path f x = match x with | Path.Pident s -> fprintf f "%a" fmt_ident s | Path.Pdot (y, s) -> fprintf f "%a.%s" fmt_path y s | Path.Papply (y, z) -> fprintf f "%a(%a)" fmt_path y fmt_path z let fmt_partial f = function | Partial -> F.pf f "partial" | Total -> F.pf f "total" let fmt_position f l = if l.pos_lnum = -1 then fprintf f "%s[%d]" l.pos_fname l.pos_cnum else fprintf f "%s[%d,%d+%d]" l.pos_fname l.pos_lnum l.pos_bol (l.pos_cnum - l.pos_bol) let fmt_location f loc = fprintf f "(%a..%a)" fmt_position loc.loc_start fmt_position loc.loc_end; if loc.loc_ghost then fprintf f " ghost" let fmt_constant f x = match x with | Asttypes.Const_int i -> fprintf f "%d" i | Const_char c -> fprintf f "%02x" (Char.code c) | Const_string (s, strloc, None) -> fprintf f "(%S,%a,None)" s fmt_location strloc | Const_string (s, strloc, Some delim) -> fprintf f "(%S,%a,Some %S)" s fmt_location strloc delim | Const_float s -> fprintf f "%s" s | Const_int32 i -> fprintf f "%ld" i | Const_int64 i -> fprintf f "%Ld" i | Const_nativeint i -> fprintf f "%nd" i let string_of_node = function | Browse_raw.Dummy -> "Dummy" | Pattern _ -> "Pattern" | Expression _ -> "Expression" | Case _ -> "case" | Class_expr _ -> "class_expr" | Class_structure _ -> "class_structure" | Class_field _ -> "class_field" | Class_field_kind _ -> "class_field_kind" | Module_expr _ -> "module_expr" | Module_type_constraint _ -> "module_type_constraint" | Structure _ -> "structure" | Structure_item _ -> "structure_item" | Module_binding _ -> "module_binding" | Value_binding _ -> "value_binding" | Module_type _ -> "module_type" | Signature _ -> "signature" | Signature_item _ -> "signature_item" | Module_declaration _ -> "module_declaration" | Module_type_declaration _ -> "module_type_declaration" | With_constraint _ -> "with_constraint" | Core_type _ -> "core_type" | Package_type _ -> "package_type" | Row_field _ -> "row_field" | Value_description _ -> "value_description" | Type_declaration _ -> "type_declaration" | Type_kind _ -> "type_kind" | Type_extension _ -> "type_extension" | Extension_constructor _ -> "extension_constructor" | Label_declaration _ -> "label_declaration" | Constructor_declaration _ -> "constructor_declaration" | Class_type _ -> "class_type" | Class_signature _ -> "class_signature" | Class_type_field _ -> "class_type_field" | Class_declaration _ -> "class_declaration" | Class_description _ -> "class_description" | Class_type_declaration _ -> "class_type_declaration" | Method_call _ -> "method_call" | Record_field _ -> "record_field" | Module_binding_name _ -> "module_binding_name" | Module_declaration_name _ -> "module_declaration_name" | Module_type_declaration_name _ -> "module_type_declaration_name" | Open_description _ -> "open_description" | Open_declaration _ -> "open_declaration" | Include_description _ -> "include_description" | Include_declaration _ -> "include_declaration" let string_of_texp = function | Texp_ident _ -> "Texp_ident" | Texp_constant _ -> "Texp_constant" | Texp_apply _ -> "Texp_apply" | Texp_instvar _ -> "Texp_instvar" | Texp_let _ -> "Texp_let" | Texp_function _ -> "Texp_function" | Texp_match _ -> "Texp_match" | Texp_try _ -> "Texp_try" | Texp_tuple _ -> "Texp_tuple" | Texp_construct _ -> "Texp_construct" | Texp_variant _ -> "Texp_variant" | Texp_record _ -> "Texp_record" | Texp_field _ -> "Texp_field" | Texp_setfield _ -> "Texp_setfield" | Texp_array _ -> "Texp_array" | Texp_ifthenelse _ -> "Texp_ifthenelse" | Texp_sequence _ -> "Texp_sequence" | Texp_while _ -> "Texp_while" | Texp_for _ -> "Texp_for" | Texp_send _ -> "Texp_send" | Texp_new _ -> "Texp_new" | Texp_setinstvar _ -> "Texp_setinstvar" | Texp_override _ -> "Texp_override" | Texp_letmodule _ -> "Texp_letmodule" | Texp_letexception _ -> "Texp_letexception" | Texp_assert _ -> "Texp_assert" | Texp_lazy _ -> "Texp_lazy" | Texp_object _ -> "Texp_object" | Texp_pack _ -> "Texp_pack" | Texp_letop _ -> "Texp_letop" | Texp_unreachable -> "Texp_unreachable" | Texp_extension_constructor _ -> "Texp_extension_constructor" | Texp_open _ -> "Texp_open" | Texp_hole -> "Texp_hole" let str_of_exp (exp : Typedtree.expression) = Fmt.str "%a" Pprintast.expression Pmapper.( remove_attributes.expr remove_attributes (Untypeast.untype_expression exp)) let str_of_pat (pat : 'a general_pattern) = Fmt.str "%a" Pprintast.pattern Pmapper.( remove_attributes.pat remove_attributes (Untypeast.untype_pattern pat)) end let add_edge g ?(srcport = "tl") ?(dstport = "hd") ?(edgename = "") src dst = Log.debug (fun m -> m "G.add_edge %s:%s %s:%s %s" (Gr.Vertex.label src) srcport (Gr.Vertex.label dst) dstport edgename); Gr.add_edge_e g ( src, { Gr.Edge.name = edgename; attr = [ `HeadportRecord dstport; `TailportRecord srcport ]; }, dst ) let make_vertex ?(attr = []) ?(label : string option) ?subgraph name = Log.debug (fun m -> m "G.make_vertex %s [`Label %s]" name (Option.value label ~default:"")); let attr = match label with Some s -> `Label s :: attr | None -> attr in Gr.Vertex.{ name; attr; subgraph } let ident_counter = ref 0 let rec add_expression g ~dst ~dstport ~edgename { exp_desc; _ } = let unique_ident = F.str "/%d" !ident_counter in ident_counter := !ident_counter + 1; match exp_desc with | Texp_ident (p, _, _) -> add_edge g (make_vertex ~label: (F.str "{%s (ident)|}" (Printtyp.string_of_path p)) (Ident.unique_name (Ocaml_typing.Path.head p))) ~dstport dst | Texp_constant c -> add_edge g (make_vertex ~label:(F.str "{%a (constant)|}" P.fmt_constant c) unique_ident) ~dstport dst | Texp_apply (exp0, args) -> let rec srcportlist = String.concat "|" (List.mapi (fun i _ -> F.str "<%d>" i) args) in let src = make_vertex ~label: (F.str "{{%s}|%s (apply)|}" srcportlist (P.str_of_exp exp0)) unique_ident in add_edge g src dst ~dstport ~edgename; List.iteri (fun i (arg_label, exp1) -> match exp1 with | Some e' -> add_expression g ~dst:src ~dstport:(Fmt.str "%d" i) ~edgename:(Printtyp.string_of_label arg_label) e' | None -> ()) args | Texp_sequence (exp0, exp1) -> let src = make_vertex ~label:(F.str "{{<0>|<1>}|(sequence)|}") unique_ident in add_edge g src dst ~dstport ~edgename; add_expression g ~dst:src ~dstport:"0" ~edgename:"0" exp0; add_expression g ~dst:src ~dstport:"1" ~edgename:"1" exp1 | Texp_ifthenelse (exp0, exp1, exp2) -> let src = make_vertex ~label: (F.str "{{if|then%s}|}" (Option.fold ~none:"" ~some:(fun _ -> "|else") exp2)) unique_ident in add_edge g src dst ~dstport ~edgename; add_expression g ~dst:src ~dstport:"if" ~edgename:"if" exp0; add_expression g ~dst:src ~dstport:"then" ~edgename:"then" exp1; Option.iter (add_expression g ~dst:src ~dstport:"else" ~edgename:"else") exp2 | Texp_let (_rec_flag, _value_binding_list, exp) -> let src = make_vertex ~label:(F.str "{{}|(let)|}") unique_ident in add_edge g src dst ~dstport ~edgename; add_expression g ~dst:src ~dstport:"hd" exp ~edgename | Texp_function { arg_label; param; cases; _ } -> let rec srcportlist = String.concat "|" (List.mapi (fun i _ -> F.str "<%d>" i) cases) in let src = make_vertex ~label: (F.str "{{%s}|fun %s:%s|}" srcportlist (Printtyp.string_of_label arg_label) (Ident.name param)) (Ident.unique_name param) in add_edge g src dst ~edgename; List.iter (fun { c_lhs; c_guard; c_rhs } -> let srcportlist = match c_guard with Some _ -> "|" | None -> "" in let src = make_vertex ~label: (F.str "{{%s}|%a with %s|}" srcportlist Printpat.top_pretty c_lhs (Ident.name param)) (Ident.unique_name param) in Option.iter (fun exp -> add_expression g ~dst:src ~dstport:"with" exp ~edgename) c_guard; add_edge g src dst ~edgename; add_expression g ~dst:src ~dstport:"hd" ~edgename:(Printtyp.string_of_label arg_label) c_rhs) cases | Texp_match (exp0, cases, _partial) -> let rec dstportlist = String.concat "|" (List.mapi (fun i { c_lhs; c_guard; _ } -> F.str "<%d>%s%s" i (P.str_of_pat c_lhs) (Option.fold ~none:"" ~some:(fun _ -> F.str "|with" i) c_guard)) cases) in let src = make_vertex ~label:(F.str "{{with|%s}|(match)|}" dstportlist) unique_ident in add_edge g src dst ~dstport ~edgename; add_expression g ~dst:src ~dstport:"match" ~edgename exp0; List.iteri (fun i { c_guard; c_rhs; _ } -> Option.iter (fun exp_guard -> add_expression g ~dst:src ~dstport:(F.str "with%d" i) ~edgename exp_guard) c_guard; add_expression g ~dst:src ~dstport:(F.str "%d" i) ~edgename c_rhs) cases | Texp_tuple tl -> let rec dstportlist = String.concat "|" (List.mapi (fun i _ -> F.str "<%d>%d" i i) tl) in let src = make_vertex ~label:(F.str "{{%s}|(tuple)|}" dstportlist) unique_ident in add_edge g src dst ~dstport ~edgename; List.iteri (fun i e -> add_expression g ~dst:src ~dstport:(F.str "%d" i) ~edgename e) tl | Texp_construct (li, _constr_desc, exp_list) -> let rec dstportlist = String.concat "|" (List.mapi (fun i _ -> F.str "<%d>%d" i i) exp_list) in let src = make_vertex ~label: (F.str "{{%s}|%a (construct)|}" dstportlist P.fmt_longident li) unique_ident in add_edge g src dst ~dstport ~edgename; List.iteri (fun i e -> add_expression g ~dst:src ~dstport:(F.str "%d" i) ~edgename e) exp_list | e -> let src = make_vertex ~label:(F.str "{%s|}" (P.string_of_texp e)) unique_ident in add_edge g src dst and add_node_edges g t dst : unit = let unique_ident = F.str "/%d" !ident_counter in ident_counter := !ident_counter + 1; match Browse_tree.(t.t_node) with | Expression e -> add_expression g ~dst ~dstport:"hd" ~edgename:"" e | node -> let src_name, src_ident = match node with | Pattern p -> let fmt, printer = Std.Format.to_string () in Printpat.top_pretty fmt p; (printer (), unique_ident) | e -> (P.string_of_node e, unique_ident) in let src = make_vertex src_ident ~label:(F.str "{|%s|}" src_name) in add_edge g src dst; graph_of_browse g src (Lazy.force t.t_children) and graph_of_browse g dst (tl : Browse_tree.t list) = match tl with | t :: ts -> add_node_edges g t dst; graph_of_browse g dst ts | [] -> () let merlin_parse str : Gr.t = let config, _command_args = Mconfig.parse_arguments ~wd:(Sys.getcwd ()) ~warning:(fun _ -> ()) (List.map snd []) [] Mconfig.initial [] in File_id.with_cache @@ fun () -> let source = Msource.make str in let pipeline = Mpipeline.make config source in Mpipeline.with_pipeline pipeline @@ fun () -> Log.info (fun m -> m "merlin_parse..."); let typer = Mpipeline.typer_result pipeline in let browse = Mbrowse.of_typedtree (Mtyper.get_typedtree typer) in Log.info (fun m -> m "Mbrowse.print %s" (Mbrowse.print () browse)); let g = Gr.create [ `Rankdir `LeftToRight ] in let dst = make_vertex "root" ~attr:[ `Label (F.str "{|root|}") ] in graph_of_browse g dst (Lazy.force (Browse_tree.of_browse browse).t_children); g (* match typedtree with | `Implementation t -> Log.info (fun m -> m "Untyped:@ %a" Pprintast.structure Pmapper.( remove_attributes.structure remove_attributes (Untypeast.untype_structure t))); Log.info (fun m -> m "Printtyped:@ %a" Ocaml_typing.Printtyped.implementation t); graph_of_impl t | `Interface _ -> Gr.create [] *) let dot_of_tast ?(fname = "x.dot") str : unit = let oc = open_out fname in Dot.output_graph oc (merlin_parse str); close_out oc