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
open Lang
open Error
open Util.Source
module type KEY = sig
type t
val to_string : t -> string
val to_anchor : t -> string
val parse : Source.t -> t list
val compare : t -> t -> int
end
module type VALUE = sig
type t
val render : t list -> string
end
module type INIT = sig
type key
type value
val init : El.spec -> Pl.spec -> (key * value) list
end
module type STORE = sig
type key
type value
type t
val cardinal : t -> int
val add : key -> value -> t -> t
val find_opt : t -> key -> value option
val use : t -> key -> unit
val used : t -> key -> bool
val unused : t -> key list
val empty : t
val init : El.spec -> Pl.spec -> t
end
module Make_store
(K : KEY)
(V : VALUE)
(I : INIT with type key = K.t and type value = V.t) :
STORE with type key = K.t and type value = V.t = struct
module M = Map.Make (K)
type key = K.t
type value = V.t
type entry = { mutable used : bool; data : V.t }
type t = entry M.t
let cardinal (sto : t) : int = M.cardinal sto
let add (key : K.t) (data : V.t) (sto : t) : t =
M.add key { used = false; data } sto
let find_opt (sto : t) (key : K.t) : V.t option =
match M.find_opt key sto with Some entry -> Some entry.data | None -> None
let use (sto : t) (key : K.t) : unit =
let entry = M.find key sto in
entry.used <- true
let used (sto : t) (key : K.t) : bool =
let entry = M.find key sto in
entry.used
let unused (sto : t) : K.t list =
M.fold
(fun key entry keys_unused ->
if entry.used then keys_unused else key :: keys_unused)
sto []
|> List.rev
let empty : t = M.empty
let init (spec_el : El.spec) (spec_pl : Pl.spec) : t =
I.init spec_el spec_pl
|> List.fold_left (fun sto (key, data) -> add key data sto) empty
end
let prefix_source =
"ifdef::backend-html5[]\n"
^ ".Click to view the specification source\n[%collapsible]\n====\n"
^ "[source,watsup]\n----\n"
let suffix_source = "\n----\n====\n\n[.empty]\n--\n\n\n--\n\n" ^ "endif::[]"
let prefix_prose = "****\n"
let suffix_prose = "\n****"
module type ANCHOR = sig
val name : string
val prefix : string
val suffix : string
end
module type SPLICER = sig
include ANCHOR
type key
type value
val init : El.spec -> Pl.spec -> unit
val splice : Source.t -> string
val warn_unused : unit -> unit
end
module Make
(K : KEY)
(V : VALUE)
(I : INIT with type key = K.t and type value = V.t)
(A : ANCHOR) : SPLICER with type key = K.t and type value = V.t = struct
include A
type key = K.t
type value = V.t
module S = Make_store (K) (V) (I)
let sto = ref S.empty
let init (spec_el : El.spec) (spec_pl : Pl.spec) : unit =
sto := S.init spec_el spec_pl
let parse (source : Source.t) : K.t list = K.parse source
let render (keys : K.t list) : string =
let keys, values =
keys
|> List.filter_map (fun key ->
let value_opt = S.find_opt !sto key in
match value_opt with
| Some value -> Some (key, value)
| None ->
warn no_region
(Format.asprintf "%s splice key not found: %s" name
(K.to_string key));
None)
|> List.split
in
let =
if header then
(keys
|> List.filter_map (fun key ->
if not (S.used !sto key) then Some ("[[" ^ K.to_anchor key ^ "]]")
else None)
|> String.concat "\n")
^ "\n"
else ""
in
List.iter (S.use !sto) keys;
headers ^ prefix ^ V.render values ^ suffix
let splice (source : Source.t) : string =
let keys = parse source in
render keys
let warn_unused () : unit =
let keys_unused = S.unused !sto in
let count_unused = List.length keys_unused in
let total = S.cardinal !sto in
let percentage =
if total = 0 then 0.0
else float_of_int count_unused /. float_of_int total *. 100.0
in
Format.asprintf "unused %d %s splices out of %d (%.2f%%)" count_unused name
total percentage
|> warn no_region;
let s =
keys_unused
|> List.mapi (fun idx key -> (idx, key))
|> List.fold_left
(fun s (idx, key) ->
let s =
if idx mod 5 = 0 && idx > 0 then (
warn no_region ("\t" ^ s);
"")
else s
in
let s = s ^ K.to_string key in
s ^ if idx mod 5 < 4 && idx < count_unused - 1 then ", " else "")
""
in
warn no_region ("\t" ^ s)
end