Source file sexp_with_layout.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
(* packaging of annotated sexp functions *)

module List = struct
  let iter t ~f = List.iter f t
  let map t ~f = List.rev (List.rev_map f t)
end

include Type_with_layout

type pos = Src_pos.Relative.t =
  { row : int
  ; col : int
  }

let sexp_of_pos = Src_pos.Relative.sexp_of_t

module Lexer = struct
  let main = Lexer.main_with_layout
end

module Parser = Parser_with_layout

module Render = struct
  module Rel_pos = Src_pos.Relative
  module Abs_pos = Src_pos.Absolute

  type last_atom =
    { immed_after : Abs_pos.t
    ; unescaped : bool
    }

  type state =
    { mutable row_shift : Rel_pos.t
    ; mutable current : Abs_pos.t
    ; mutable last_atom : last_atom option
    ; mutable last_comment_row : int
    }

  (* the point of [immed_after_last_atom] is to prevent
     (A B C) from rendering as (A BBC) after we replace B with BB *)

  type 'a t = (char -> unit) -> state -> 'a

  let return a _putc _st = a
  let bind m ~f putc st = f (m putc st) putc st

  let run putc m =
    m
      putc
      { row_shift = Rel_pos.zero
      ; current = Abs_pos.origin
      ; last_atom = None
      ; last_comment_row = 0 (* before the file starts *)
      }
  ;;

  let emit_char putc st c =
    let { Abs_pos.col; row } = st.current in
    putc c;
    if c = '\n'
    then st.current <- { Abs_pos.row = 1 + row; col = 1 }
    else st.current <- { Abs_pos.row; col = 1 + col }
  ;;

  let emit_string putc st str =
    let n = String.length str in
    for i = 0 to n - 1 do
      emit_char putc st str.[i]
    done
  ;;

  let emit_chars putc st c ~n = emit_string putc st (String.make n c)

  let advance putc ~anchor st ~by:delta ~unescaped_atom ~line_comment =
    let new_pos = Abs_pos.add (Abs_pos.add anchor delta) st.row_shift in
    let need_to_leave_room_between_two_unescaped_atoms_lest_they_become_one =
      unescaped_atom
      &&
      match st.last_atom with
      | Some { immed_after; unescaped = prev_unescaped } ->
        new_pos = immed_after && prev_unescaped
      | None -> false
    in
    (* avoid joining subsequent items into a preceding line comment *)
    let need_to_clear_line_comment = new_pos.row = st.last_comment_row in
    let need_to_reposition =
      (not (Abs_pos.geq new_pos st.current))
      || need_to_clear_line_comment
      || need_to_leave_room_between_two_unescaped_atoms_lest_they_become_one
    in
    let row_delta, new_pos =
      if need_to_reposition
      then (
        (* repositioning heuristic: just move to the next fresh row *)
        let new_row = 1 + st.current.Abs_pos.row in
        let row_delta = new_row - new_pos.Abs_pos.row in
        row_delta, { Abs_pos.row = new_row; col = new_pos.Abs_pos.col })
      else 0, new_pos
    in
    (* advance to new_pos by emitting whitespace *)
    if new_pos.Abs_pos.row > st.current.Abs_pos.row
    then (
      let n = new_pos.Abs_pos.row - st.current.Abs_pos.row in
      emit_chars putc st '\n' ~n);
    if new_pos.Abs_pos.col > st.current.Abs_pos.col
    then (
      let n = new_pos.Abs_pos.col - st.current.Abs_pos.col in
      emit_chars putc st ' ' ~n);
    assert (new_pos = st.current);
    if line_comment then st.last_comment_row <- st.current.row;
    st.row_shift
    <- { st.row_shift with Rel_pos.row = st.row_shift.Rel_pos.row + row_delta }
  ;;

  let rec render_t putc ~anchor (st : state) t =
    match t with
    | Atom (delta, text, fmt_text) ->
      let fmt_text =
        match fmt_text with
        | None | Some "" -> Pre_sexp.mach_maybe_esc_str text
        | Some text -> text
      in
      let unescaped = fmt_text.[0] <> '"' in
      advance putc st ~by:delta ~anchor ~unescaped_atom:unescaped ~line_comment:false;
      emit_string putc st fmt_text;
      st.last_atom <- Some { immed_after = st.current; unescaped }
    | List (start_delta, tocs, end_delta) ->
      advance putc st ~by:start_delta ~anchor ~unescaped_atom:false ~line_comment:false;
      let child_anchor = Abs_pos.sub st.current st.row_shift in
      emit_char putc st '(';
      List.iter tocs ~f:(fun toc -> render_toc putc ~anchor:child_anchor st toc);
      advance putc st ~by:end_delta ~anchor ~unescaped_atom:false ~line_comment:false;
      emit_char putc st ')';
      ()

  and render_toc putc ~anchor st = function
    | Sexp t -> render_t putc ~anchor st t
    | Comment c -> render_c putc ~anchor st c

  and render_c putc ~anchor st = function
    | Plain_comment (delta, text) ->
      let line_comment = String.length text > 0 && text.[0] = ';' in
      advance putc st ~by:delta ~anchor ~unescaped_atom:false ~line_comment;
      emit_string putc st text
    | Sexp_comment (delta, cs, t) ->
      advance putc st ~by:delta ~anchor ~unescaped_atom:false ~line_comment:false;
      emit_string putc st "#;";
      List.iter cs ~f:(render_c putc ~anchor st);
      render_t putc ~anchor st t
  ;;

  let render asexp putc st = render_toc putc ~anchor:Abs_pos.origin st asexp
  let sexp = render
end

module Forget = struct
  (* In cps to prevent non-tail recursion.
     The polymorphism in the signature ensures that each function returns
     only through the continuation. *)
  module Cps : sig
    val forget_t : t -> (Type.t -> 'r) -> 'r
    val forget_toc : t_or_comment -> (Type.t option -> 'r) -> 'r
    val forget_tocs : t_or_comment list -> (Type.t list -> 'r) -> 'r
  end = struct
    let rec forget_t t k =
      match t with
      | Atom (_, x, _) -> k (Type.Atom x)
      | List (_, tocs, _) -> forget_tocs tocs (fun xs -> k (Type.List xs))

    and forget_tocs tocs k =
      match tocs with
      | [] -> k []
      | toc :: tocs ->
        forget_toc toc (function
          | None -> forget_tocs tocs k
          | Some x -> forget_tocs tocs (fun xs -> k (x :: xs)))

    and forget_toc toc k =
      match toc with
      | Comment _ -> k None
      | Sexp t -> forget_t t (fun x -> k (Some x))
    ;;
  end

  let t x = Cps.forget_t x (fun y -> y)
  let t_or_comment x = Cps.forget_toc x (fun y -> y)
  let t_or_comments x = Cps.forget_tocs x (fun y -> y)
end