(* CFG - Manipulation of Context-Free Grammars Copyright (C) 2000-2003 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *) (* $Id: cfg_impl.ml,v 1.8 2006/01/16 23:57:58 mottl Exp $ *) 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 and 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 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_info = NTMap.fold (coll_live_info live_nts) nonlive_nts live_info in if live_info = new_live_info 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 root_nts = NTMap.empty 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 and 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 || reached_nts = NTSet.empty then levels else let ts, new_reached_nts = NTSet.fold (bnd_descend_nt gr) reached_nts (TSet.empty, NTSet.empty) in bnd_descend ((ts, new_reached_nts) :: 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 ts = TSet.empty && nts = NTMap.empty 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 and good_nts = NTMap.fold (fun nt _ -> NTMap.remove nt) bad_nts nts in if nts == good_nts then if ts == good_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, nts) :: levels -> let init = [(ts, NTMap.empty)], NTMap.empty in fst (List.fold_left (bnd_ascend gr) init levels) end