Remove unreachable rules from a grammar in OCaml
I'm new to Ocaml and for a homework assignment I have to write a function filter_reachable that takes a grammar and returns a reduced grammar with the unreachable rules removed. The only modules I'm allowed to use are Pervasives and List modules and no other modules.
The idea I have is to first make a list of all reachable nonterminals. Then a rule is reachable if it's nonterminal part is in the list of all reachable nonterminals. All the rules that are reachable then placed into a new list and paired with the start symbol to create the reduced grammar.
My solution is below. However it d开发者_高级运维oesn't work and I don't understand why. Can anyone help me fix it?
let rec member x s=
match s with
[]->false
| y::ys-> (x = y) || member x ys
(*the type of a symbol*)
type ('nonterminal, 'terminal) symbol =
| N of 'nonterminal
| T of 'terminal
let rec get_nont sl=
match sl with
|[]->[]
|h::t->match h with
|N x-> x::get_nont t
|T y-> get_nont t
let rec get_rea_nont (n,r) =
n::(match r with
|[]->[]
|h::t->match h with
| a,b -> if a=n then (get_nont b)@(get_rea_nont (n,t))
else get_rea_nont(n,t)
| _-> [])
let rec fil (st,rl)=
let x = get_rea_nont(st,rl) in
(match rl with
|[]-> []
|h::t-> match h with
|a,b -> if (member a x) then h::fil(st,t)
else fil(st,t)
|_->[]
|_->[]
)
let rec filter(st,rl)=
(st,fil(st,rl))
Imagine the second last recursive call to fil (st, rl)
. In this call rl
has just one rule in it. Your code is going to try to discover whether the nonterminal of the rule is reachable by just looking at this one rule. This isn't going to work unless the nonterminal is the start symbol.
Generally speaking I'd say you have to carry around quite a bit more context. I don't want to give too much away, but this is basically a classic directed graph traversal problem. Each grammar rule is a node in the graph. Edges go from a rule R to the other rules defining the nonterminals that appear in the RHS of R. This famous graph traversal algorithm generally works with a list of "visited" nodes. You need this because the graph can have cycles.
Here is a grammar with cycles:
A ::= B | B '+' A
B :: = 'x' | 'y' | '(' A ')'
Some remarks on your code:
Instead of
member
, you could useList.mem
The pattern matching in
get_nont
can be combined:let rec get_nont sl = match sl with | [] -> [] | N x :: tl -> x :: get_nont tl | T _ :: tl -> get_nont tl;;
In functional programming, currying is used to define functions with more than one argument. See Scope, Currying, and Lists,section "Curried functions".
Use the power of pattern matching, e.g., demonstrated for
get_rea_nont
:let rec get_rea_nont_old n r = n :: (match r with | []->[] | (a, b) :: t when a = n -> (get_nont b) @ (get_rea_nont_old n t) | _ :: t -> get_rea_nont_old n t );;
Try modularize your code, e.g., for
get_rea_nont
:- First filter elements being equal to
n
(seeList.filter
). - For the filtered elements, apply the function
get_nont
(seeList.map
) - Consider
List.flatten
to get the expected result.
Thus, ...
let get_rea_nont n r = let filtered = List.filter (fun (a, _) -> a = n) r in let nonterminals = List.map (fun (_, b) -> get_nont b) filtered in n :: (List.flatten nonterminals);;
- First filter elements being equal to
精彩评论