type t = Merge of string * string | Chunk of Grammar.symbol list let describe = function | Merge (a, b) -> Printf.sprintf "merge %s,%s" a b | Chunk seq -> Printf.sprintf "chunk (%s)" (String.concat " " (List.map Grammar.symbol_to_string seq)) let starts_with seq lst = let m = List.length seq in let rec go i seq lst = if i = 0 then true else match (seq, lst) with | [], _ -> true | _, [] -> false | x :: xs, y :: ys -> Grammar.equal_symbol x y && go (i - 1) xs ys in List.length lst >= m && go m seq lst let drop n lst = let rec go n lst = if n <= 0 then lst else match lst with [] -> [] | _ :: r -> go (n - 1) r in go n lst let replace_all seq name rhs = let m = List.length seq in let rec go acc lst count = match lst with | _ when starts_with seq lst -> go (Grammar.Nonterm name :: acc) (drop m lst) (count + 1) | [] -> (List.rev acc, count) | x :: rest -> go (x :: acc) rest count in go [] rhs 0 let replace_first seq name rhs = let m = List.length seq in let rec go acc lst = match lst with | _ when starts_with seq lst -> (List.rev_append acc (Grammar.Nonterm name :: drop m lst), 1) | [] -> (List.rev acc, 0) | x :: rest -> go (x :: acc) rest in go [] rhs let fresh_name (g : Grammar.t) _base = let existing = Grammar.nonterminals g in let candidates = [ "X"; "Y"; "Z"; "W"; "V"; "U"; "P"; "Q"; "R" ] in match List.find_opt (fun c -> not (List.mem c existing)) candidates with | Some c -> c | None -> let rec loop i = let c = "X" ^ string_of_int i in if List.mem c existing then loop (i + 1) else c in loop 1 let apply_merge (g : Grammar.t) a b = let survivor, removed = if String.equal a g.Grammar.start then (a, b) else if String.equal b g.Grammar.start then (b, a) else if String.compare a b <= 0 then (a, b) else (b, a) in let map_sym = function | Grammar.Nonterm x when String.equal x removed -> Grammar.Nonterm survivor | s -> s in let prods = List.map (fun (p : Grammar.production) -> let lhs = if String.equal p.lhs removed then survivor else p.lhs in { p with lhs; rhs = List.map map_sym p.rhs }) g.Grammar.productions in Grammar.copy_with g prods let apply_chunk ?(occurrence = Scoring.All_occurrences) (g : Grammar.t) seq = if List.length seq < 2 then g else begin let name = fresh_name g "X" in let replace = match occurrence with | Scoring.All_occurrences -> replace_all | Scoring.First_occurrence -> replace_first in let total = ref 0 in let prods = List.map (fun (p : Grammar.production) -> let rhs, n = replace seq name p.rhs in total := !total + n; { p with rhs }) g.Grammar.productions in let newp = { Grammar.lhs = name; rhs = seq; count = float_of_int (max 1 !total); } in Grammar.copy_with g (prods @ [ newp ]) end let apply ?occurrence (g : Grammar.t) = function | Merge (a, b) -> apply_merge g a b | Chunk seq -> apply_chunk ?occurrence g seq let candidates (g : Grammar.t) (config : Scoring.config) = let nts = Grammar.nonterminals g in let merges = let rec pairs = function | [] -> [] | x :: rest -> List.map (fun y -> Merge (x, y)) rest @ pairs rest in pairs nts in let counts = Hashtbl.create 256 in let order = ref [] in List.iter (fun (p : Grammar.production) -> let arr = Array.of_list p.rhs in let n = Array.length arr in for len = 2 to min config.Scoring.max_chunk n do for i = 0 to n - len do let sub = Array.to_list (Array.sub arr i len) in match Hashtbl.find_opt counts sub with | Some c -> Hashtbl.replace counts sub (c + 1) | None -> Hashtbl.replace counts sub 1; order := sub :: !order done done) g.Grammar.productions; let chunks = List.rev !order |> List.filter (fun k -> Hashtbl.find counts k >= 1) |> List.map (fun seq -> Chunk seq) in merges @ chunks type change = { before_nts : string list; after_nts : string list; before_prods : Grammar.production list; after_prods : Grammar.production list; } let change_of (before : Grammar.t) t (after : Grammar.t) = match t with | Merge (a, b) -> let survivor = if String.equal a before.Grammar.start then a else if String.equal b before.Grammar.start then b else if String.compare a b <= 0 then a else b in { before_nts = [ a; b ]; after_nts = [ survivor ]; before_prods = List.filter (fun p -> String.equal p.Grammar.lhs a || String.equal p.Grammar.lhs b) before.Grammar.productions; after_prods = List.filter (fun p -> String.equal p.Grammar.lhs survivor) after.Grammar.productions; } | Chunk seq -> let name = fresh_name before "X" in let affected_before = List.filter (fun p -> starts_with seq p.Grammar.rhs) before.Grammar.productions in let affected_after = List.filter (fun p -> String.equal p.Grammar.lhs name || List.exists (function | Grammar.Nonterm x -> String.equal x name | Grammar.Term _ -> false) p.Grammar.rhs) after.Grammar.productions in { before_nts = name :: List.map (fun p -> p.Grammar.lhs) affected_before; after_nts = [ name ]; before_prods = affected_before; after_prods = affected_after; }