package cfg

  1. Overview
  2. Docs

Source file cfg_impl.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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
(*
   CFG - Manipulation of Context-Free Grammars

   Copyright (C) 2000-2017  Markus Mottl
   email: markus.mottl@gmail.com
   WWW:   http://www.ocaml.info

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this library; if not, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*)

open Cfg_intf

module Make (Spec_ : SPEC) : (CFG with module Spec = Spec_) = struct
  module MySet = Set.Make
  module MyMap = Map.Make

  module Spec = Spec_
  open Spec

  let compare_syms s1 s2 =
    match s1, s2 with
    | NT nt1, NT nt2 -> compare_nt nt1 nt2
    | T t1, T t2 -> compare_t t1 t2
    | NT _, T _ -> 1
    | T _, NT _ -> -1

  let rec compare_lists cf l1 l2 =
    match l1, l2 with
    | [], [] -> 0
    | _, [] -> 1
    | [], _ -> -1
    | h1::t1, h2::t2 ->
        let c = cf h1 h2 in
        if c = 0 then compare_lists cf t1 t2 else c

  let compare_prod (p1, sl1) (p2, sl2) =
    let c = compare_prod p1 p2 in
    if c = 0 then compare_lists compare_syms sl1 sl2 else c

  module TSet = MySet (struct type t = Spec.t let compare = compare_t end)
  module TMap = MyMap (struct type t = Spec.t let compare = compare_t end)
  module NTSet = MySet (struct type t = nt let compare = compare_nt end)
  module NTMap = MyMap (struct type t = nt let compare = compare_nt end)

  module ProdSet = MySet (struct type t = prod * symbol list
                                 let compare = compare_prod end)

  module ProdMap = MyMap (struct type t = prod * symbol list
                                 let compare = compare_prod end)

  type grammar = ProdSet.t NTMap.t
  type live_grammar = (int * int ProdMap.t) NTMap.t

  let empty = NTMap.empty

  let maybe_get_prods gr nt =
    try NTMap.find nt gr
    with Not_found -> ProdSet.empty

  let add_prod gr nt pr sl =
    if sl = [] then invalid_arg "Cfg.add_prod: symbol list empty!"
    else NTMap.add nt (ProdSet.add (pr, sl) (maybe_get_prods gr nt)) gr

  let remove_nt gr nt = NTMap.remove nt gr

  let union_aux k v acc =
    try NTMap.add k (ProdSet.union (NTMap.find k acc) v) acc
    with Not_found -> NTMap.add k v acc

  let union gr1 gr2 = NTMap.fold union_aux gr2 gr1

  let diff_aux k v acc =
    try
      let prods_diff = ProdSet.diff (NTMap.find k acc) v in
      if ProdSet.is_empty prods_diff then NTMap.remove k acc
      else NTMap.add k prods_diff acc
    with Not_found -> acc

  let diff gr1 gr2 = NTMap.fold diff_aux gr2 gr1

  let inter_aux k v (res, gr as acc) =
    try
      let new_gr = NTMap.remove k gr in
      let k_prods = NTMap.find k gr in
      let prods_inter = ProdSet.inter k_prods v in
      if ProdSet.is_empty prods_inter then res, new_gr
      else NTMap.add k prods_inter res, new_gr
    with Not_found -> acc

  let inter gr1 gr2 = fst (NTMap.fold inter_aux gr2 (NTMap.empty, gr1))


  (** PRUNE UNPRODUCTIVE *)

  let sym_derivable nts = function
    | NT nt -> NTMap.mem nt nts
    | _ -> true

  let prod_defined defined_nts (_, syms) =
    List.for_all (sym_derivable defined_nts) syms

  let remove_unproductive nt prods defined_nts =
    let productive_prods = ProdSet.filter (prod_defined defined_nts) prods in
    if ProdSet.is_empty productive_prods then NTMap.remove nt defined_nts
    else NTMap.add nt productive_prods defined_nts

  let rec prune_unproductive gr =
    let remaining_nts = NTMap.fold remove_unproductive gr gr in
    if NTMap.equal ProdSet.equal remaining_nts gr then gr
    else prune_unproductive remaining_nts


  (** PRUNE NONLIVE *)

  exception Found of int

  let rec calc_syms_deriv n live_nts = function
    | [] -> raise (Found n)
    | T _ :: syms -> calc_syms_deriv n live_nts syms
    | NT nt :: syms ->
        let new_d =
          try Some (max (NTMap.find nt live_nts + 1) n)
          with Not_found -> None in
        match new_d with
        | None -> ()
        | Some new_n -> calc_syms_deriv new_n live_nts syms

  let calc_prod_deriv live_nts (_, syms) = calc_syms_deriv 1 live_nts syms
  let calc_prods_deriv live_nts = ProdSet.iter (calc_prod_deriv live_nts)

  let coll_live_info old_live nt prods (live_nts, nonlive_nts as live_info) =
    try calc_prods_deriv old_live prods; live_info
    with Found n -> NTMap.add nt n live_nts, NTMap.remove nt nonlive_nts

  let rec split_live_info (live_nts, nonlive_nts as live_info) =
    let new_live_nts, new_nonlive_nts as new_live_info =
      NTMap.fold (coll_live_info live_nts) nonlive_nts live_info in
    if
      NTMap.equal (=) live_nts new_live_nts &&
      NTMap.equal ProdSet.equal nonlive_nts new_nonlive_nts
    then live_info
    else split_live_info new_live_info

  let deriv_prods live_nts (_, syms as prod) prods =
    try calc_syms_deriv 1 live_nts syms; prods
    with Found n -> ProdMap.add prod n prods

  let derive_nts gr live_nts nt n =
    let prods =
      ProdSet.fold (deriv_prods live_nts) (NTMap.find nt gr) ProdMap.empty in
    NTMap.add nt (n, prods)

  let prune_nonlive gr =
    let live_nts, _ = split_live_info (NTMap.empty, gr) in
    NTMap.fold (derive_nts gr live_nts) live_nts NTMap.empty


  (** PRUNE UNREACHABLE *)

  let coll_reachable_sym gr acc = function
    | T _ -> acc
    | NT nt ->
        try NTMap.add nt (NTMap.find nt gr) acc
        with Not_found -> acc

  let coll_reachable_prod gr (_, syms) acc =
    List.fold_left (coll_reachable_sym gr) acc syms

  let coll_reachable_prods gr prods =
    ProdSet.fold (coll_reachable_prod gr) prods NTMap.empty

  let coll_reachable_nt gr _ prods =
    NTMap.fold NTMap.add (coll_reachable_prods gr prods)

  let rec get_unreachable gr root_nts =
    if NTMap.is_empty root_nts then gr
    else
      let new_gr = NTMap.fold (fun k _ -> NTMap.remove k) root_nts gr in
      let reachable_nts =
        NTMap.fold (coll_reachable_nt new_gr) root_nts NTMap.empty in
      get_unreachable new_gr reachable_nts

  let prune_unreachable gr start_sym =
    let s_prods = NTMap.find start_sym gr in
    let no_s_gr = NTMap.remove start_sym gr in
    diff gr (get_unreachable no_s_gr (coll_reachable_prods no_s_gr s_prods))


  (** CONVERSION FUNCTIONS *)

  let unlive_prod pr _ = ProdSet.add pr
  let unlive_prods prods = ProdMap.fold unlive_prod prods ProdSet.empty
  let unlive_nts nt (_, prods) = NTMap.add nt (unlive_prods prods)
  let grammar_of_live gr = NTMap.fold unlive_nts gr NTMap.empty

  let prune_live_prods gr nt prod _ acc =
    if ProdSet.mem prod (NTMap.find nt gr) then acc
    else ProdMap.remove prod acc

  let prune_live_nts gr nt (d, dprods) =
    try NTMap.add nt (d, ProdMap.fold (prune_live_prods gr nt) dprods dprods)
    with Not_found -> NTMap.remove nt

  let prune_unreachable_live live_gr start_sym =
    let gr = prune_unreachable (grammar_of_live live_gr) start_sym in
    NTMap.fold (prune_live_nts gr) live_gr live_gr

  let make_live gr = prune_nonlive (prune_unproductive gr)
  let make_sane gr = prune_unreachable (grammar_of_live (make_live gr))
  let make_sane_live gr = prune_unreachable_live (make_live gr)

  let grammar_contents gr = gr
  let deriv_depth_info live_gr = live_gr

  let nts_in_sym acc = function NT nt -> NTSet.add nt acc | _ -> acc
  let nts_in_prod (_, syms) acc = List.fold_left nts_in_sym acc syms
  let nts_in_nt nt prods acc = ProdSet.fold nts_in_prod prods (NTSet.add nt acc)
  let nts_in_grammar gr = NTMap.fold nts_in_nt gr NTSet.empty

  let ts_in_sym acc = function T t -> TSet.add t acc | _ -> acc
  let ts_in_prod (_, syms) acc = List.fold_left ts_in_sym acc syms
  let ts_in_nt _ = ProdSet.fold ts_in_prod
  let ts_in_grammar gr = NTMap.fold ts_in_nt gr TSet.empty

  let prods_in_nt _ = ProdSet.union
  let prods_in_grammar gr = NTMap.fold prods_in_nt gr ProdSet.empty


  (** BOUNDED DERIVATION *)

  let collect_sym (ts, nts) = function
    | T t -> TSet.add t ts, nts
    | NT nt -> ts, NTSet.add nt nts

  let collect_syms (_, syms) acc = List.fold_left collect_sym acc syms

  let bnd_descend_nt gr nt = ProdSet.fold collect_syms (NTMap.find nt gr)

  let rec bnd_descend levels gr reached_nts n =
    if n <= 0 || NTSet.is_empty reached_nts then levels
    else
      let _, new_reached_nts as this_level =
        NTSet.fold (bnd_descend_nt gr) reached_nts (TSet.empty, NTSet.empty) in
      bnd_descend (this_level :: levels) gr new_reached_nts (n - 1)

  let bnd_ascend_prod kept_nts (_, syms as prod) prods =
    if List.for_all (sym_derivable kept_nts) syms then prods
    else ProdSet.remove prod prods

  let bnd_ascend_nt gr kept_nts nt nts =
    let prods = NTMap.find nt gr in
    let kept_prods = ProdSet.fold (bnd_ascend_prod kept_nts) prods prods in
    if ProdSet.is_empty kept_prods then nts
    else NTMap.add nt kept_prods nts

  let cleanup_sym (ts, nts) =
    if TSet.is_empty ts && NTMap.is_empty nts then raise Exit
    else function
      | T t -> TSet.remove t ts, nts
      | NT nt -> ts, NTMap.remove nt nts

  let cleanup_prod (_, syms) level = List.fold_left cleanup_sym level syms

  let rec cleanup_levels kept_nts = function
    | [] -> assert false
    | (ts, nts as level)::rest as levels ->
        try
          let (bad_ts, bad_nts) =
            NTMap.fold (fun _ -> ProdSet.fold cleanup_prod) kept_nts level in
          let good_ts = TSet.diff ts bad_ts in
          let good_nts = NTMap.fold (fun nt _ -> NTMap.remove nt) bad_nts nts in
          if NTMap.is_empty bad_nts then
            if TSet.is_empty bad_ts then levels
            else (good_ts, nts) :: rest
          else (good_ts, good_nts) :: cleanup_levels good_nts rest
        with Exit -> levels

  let bnd_ascend gr (levels, kept_nts) (ts, nts) =
    let new_kept_nts = NTSet.fold (bnd_ascend_nt gr kept_nts) nts NTMap.empty in
    (ts, new_kept_nts) :: cleanup_levels new_kept_nts levels, new_kept_nts

  let bounded_grammar gr start n =
    match bnd_descend [] gr (NTSet.singleton start) n with
    | [] -> []
    | (ts, _last_nts) :: levels ->
        let init = [(ts, NTMap.empty)], NTMap.empty in
        fst (List.fold_left (bnd_ascend gr) init levels)
end
OCaml

Innovation. Community. Security.