Source file ast_constructors.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
open Ast.Impl

module type Intf = sig
  (** Functions to help constructing the elements of a {!doc}.

      E.g.,

      {[
        let open Omd.Ctor in
        let para =
          p ~attrs:[ ("class", "my-para") ] [ txt "Content of"; em "this"; txt "paragraph" ]
        in
        [ blockquote [ para; hr; p [ txt "Content of second paragraph" ] ] ]
      ]}

      Produces

      {v
<blockquote>
<p class="my-para">Content of<em>this</em>paragraph</p>
<hr />
<p>Content of second paragraph</p>
</blockquote>
      v}

      The optional [attrs] argument always defaults to an empty list, and can
      generally be omitted. *)

  (** {3 Constructors for inline elements}  *)

  val empty : attributes inline
  (** [empty] is an empty inline element. *)

  val txt : ?attrs:attributes -> string -> attributes inline
  (** [txt ~attrs s] is {{!Text} [Text (attrs, s)]}. *)

  val em : ?attrs:attributes -> string -> attributes inline
  (** [em ~attrs s] is {{!Emph} [Emph (attrs, txt s)]}. See {!txt}. *)

  val strong : ?attrs:attributes -> string -> attributes inline
  (** [strong ~attrs s] is {{!Strong} [Strong (attrs, txt s)]}. See {!txt}. *)

  val code : ?attrs:attributes -> string -> attributes inline
  (** [code ~attrs s] is {{!Code} [Code (attrs, s)]}. *)

  val br : attributes inline
  (** [br] is {{!Hard_break}[Hard_break []]}. *)

  val nl : attributes inline
  (** [nl] is {{!Soft_break}[Soft_break []]}. *)

  val a :
       ?attrs:attributes
    -> ?title:string
    -> url:string
    -> string
    -> attributes inline
  (** [a ~attrs ~title ~url label] is a link around the text of [label],
      pointing to the [url], with the optional title [title] and additional [attrs].
      See {!Link}. *)

  val img :
       ?attrs:attributes
    -> ?title:string
    -> alt:string
    -> string
    -> attributes inline
  (** [img ~attrs ~title ~alt src] is an image from the given [src] that has the
      [alt] text as a fallback, with the optional title [title] and additional
      [attrs].  See {!Image}. *)

  val html : string -> attributes inline
  (** [html s] is an inline HTML string. See {!Html}. *)

  (** {3 Constructors for block-level elements} *)

  val p : ?attrs:attributes -> attributes inline list -> attributes block
  (** [p ~attrs inlines] is a pragraph block holding the given [inline]
      elements. See {!Paragraph}. *)

  val ul :
       ?attrs:attributes
    -> ?spacing:list_spacing
    -> attributes block list list
    -> attributes block
  (** [ul ~attrs ~spacing items] is an unordered list with the specified [spacing], listing
      the given [items]. Each item is a list of block elements.

      - [spacing] defaults to {!Loose}.

      E.g.,

      {[
        ul ~spacing:Tight
          [ [ p [ txt "Item 1" ] ]
          ; [ p [ txt "Item 2" ] ]
          ; [ p [ txt "Item 3" ] ]
          ]
      ]}

      See {!List} and {!Bullet}. *)

  val ol :
       ?attrs:attributes
    -> ?start:int
    -> ?char:[ `Dot | `Paren ]
    -> ?spacing:list_spacing
    -> attributes block list list
    -> attributes block
  (** [ol ~attrs ~start ~char ~spacing items] is like {!ul}, but constructs an ordered list,
      where [start] is the number to start enumerating from, and [char] indicates the
      character following the number in the enumeration.

      - [char] can be either [`Dot] indicating ['.'] or [`Paren] indicating [')'], and
        defaults to [`Dot].
      - [start] defaults to [1].

      See {!List} and {!Ordered}. *)

  val blockquote :
    ?attrs:attributes -> attributes block list -> attributes block
  (** [blockquote ~attrs blocks] is a blockquote element containing the given
      [blocks]. See {!Blockquote}. *)

  val hr : attributes block
  (** [hr] is {{!Thematic_break} [Thematic_break []]}. *)

  val h : ?attrs:attributes -> int -> attributes inline list -> attributes block
  (** [h ~attrs level inlines] is a heading of the given [level] comprised of
      the [inlines]. See {!Heading}. *)

  val code_bl : ?attrs:attributes -> ?lang:string -> string -> attributes block
  (** [code_bl ~attrs ~lang code] is a code block labeled with language [lang].

      - [lang] defaults to being absent.

      See {!Code_block} *)

  val html_bl : ?attrs:attributes -> string -> attributes block
  (** [html_bl ~attrs html] is a block-level element of raw HTML. See {!Html_block}. *)

  type 'attr ctor_def_elt =
    { term : 'attr inline list
    ; defs : 'attr inline list list
    }
  (** Type for the items given to {!dl} definition lists. It is isomorphic to {!def_elt}. *)

  val dl : ?attrs:attributes -> attributes ctor_def_elt list -> attributes block
  (** [dl ~attrs elements] is a definition list of the given [elements]. See
      {!Definition_list}.

      E.g.,

      {[
        dl
          [ { term = [ txt "def term 1" ]
            ; defs =
                [ [ txt "definition 1.1" ]
                ; [ txt "definition 1.2" ]
                ; [ txt "definition 1.3" ]
                ]
            }
          ; { term = [ txt "def term 2" ]
            ; defs =
                [ [ txt "definition 2.1" ]
                ; [ txt "definition 2.2" ]
                ; [ txt "definition 2.3" ]
                ]
            }
          ]
      ]} *)
end

module Impl : Intf = struct
  let concat elems = Concat ([], elems)
  let empty = concat []
  let txt ?(attrs = []) s = Text (attrs, s)
  let em ?(attrs = []) s = Emph (attrs, txt s)
  let strong ?(attrs = []) s = Strong (attrs, txt s)
  let code ?(attrs = []) s = Code (attrs, s)
  let br = Hard_break []
  let nl = Soft_break []

  let a ?(attrs = []) ?title ~url label =
    Link (attrs, { label = txt label; destination = url; title })

  let img ?(attrs = []) ?title ~alt src =
    Image (attrs, { label = txt alt; destination = src; title })

  (* Note that attributes are not actually supported Html nodes currently. *)
  let html s = Html ([], s)

  (* Block constructors *)

  let p ?(attrs = []) inlines = Paragraph (attrs, concat inlines)

  let ul ?(attrs = []) ?(spacing = Loose) items =
    List (attrs, Bullet '-', spacing, items)

  let ol ?(attrs = []) ?(start = 1) ?(char = `Dot) ?(spacing = Loose) items =
    let c = match char with `Dot -> '.' | `Paren -> ')' in
    List (attrs, Ordered (start, c), spacing, items)

  let blockquote ?(attrs = []) blocks = Blockquote (attrs, blocks)
  let hr = Thematic_break []
  let h ?(attrs = []) level inlines = Heading (attrs, level, concat inlines)
  let code_bl ?(attrs = []) ?(lang = "") s = Code_block (attrs, lang, s)
  let html_bl ?(attrs = []) s = Html_block (attrs, s)

  type 'attr ctor_def_elt =
    { term : 'attr inline list
    ; defs : 'attr inline list list
    }

  let dl ?(attrs = []) (items : 'attr ctor_def_elt list) =
    let def_elt_of_pair { term; defs } : 'attr def_elt =
      let term = concat term in
      let defs = List.map concat defs in
      { term; defs }
    in
    let def_elts = List.map def_elt_of_pair items in
    Definition_list (attrs, def_elts)
end