(*
 *)

val empty = "empty";
fun emptyP x = x=empty;

datatype Grammar = Gram of
    (string list) *                     (* Non Terminals *)
    (string list) *                     (* Terminals     *)
    (string * (string list)) list *     (* Productions   *)
    string;                             (* Start symbol  *)


val g1 = Gram(["Expr","Term","Factor","Expr'","Term'"]
             ,["ident","+","-","*","div","(",")","num"]
             ,[("Expr",["Term","Expr'"])
              ,("Expr'",["+","Term","Expr'"])
              ,("Expr'",["-","Term","Expr'"])
              ,("Expr'",[])
              ,("Term",["Factor","Term'"])
              ,("Term'",["*","Factor","Term'"])
              ,("Term'",["div","Factor","Term'"])
              ,("Term'",[])
              ,("Factor",["(","Expr",")"])
              ,("Factor",["num"])
              ,("Factor",["ident"])
              ]
             ,"Expr");


(* ********* Operations on Grammars *********************** *)

fun termP (Gram(nts,ts,ps,s)) x = List.exists (fn y => y=x) ts;

fun nontermP (Gram(nts,ts,ps,s)) x = List.exists (fn y => y=x) nts;

fun rhssFor (Gram(nts,ts,ps,start)) symbol =
let fun test (rhs,lhs) = rhs=symbol
    fun lhs (r,l) = l
in List.map lhs (List.filter test ps) end;


(* ******************* Operations on Tables ************************ *)
(* a table is    (string * ref item) list                            *)
(* We can update the reference to indicate updating the table        *)


(* Create a table with all slots initialized *)
fun init symbols initf =
  let fun f x = (x,ref (initf x))
  in List.map f symbols end;


(* Update the slot at "s" with the function "updatef"  *)
(* returns true if it makes a change, false otherwise  *)

fun update s updatef pairs =
 case List.find (fn (y,r) => s=y) pairs of
   SOME(_,r) => let val old = !r
                    val new = updatef old
                in if new=old
                      then false
                      else (r := new; true) end
 | NONE => false



(* ***** Create a Table for nullable symbols from a grammar **** *)

fun nullTable (gram as(Gram(nts,ts,ps,start))) =
let val allsymbols = nts @ ts
    val table = init allsymbols (fn _ => false)
    val changed = ref true
    fun onePass [] = false  (* returns true if it makes a change *)
      | onePass (x::xs) =
         let val rhss = rhssFor gram x
             fun nullify b = List.exists (nullableRhs table) rhss
             val b1 = update x nullify table
             val b2 = onePass xs
         in b1 orelse b2 end
in while (!changed) do
         (changed := false; changed := onePass allsymbols);
   table
end

and nullable table s =
  case List.find (fn (y,r) => y=s) table of
    NONE => false
  | SOME(_,r) => !r

and nullableRhs table [] = true
  | nullableRhs table (x::xs) =
     (nullable table x) andalso (nullableRhs table xs);

val NullT = nullTable g1;


(* ************ normalizing lists of strings ************ *)

val filter = List.filter;

fun sort' comp [] ans = ans
  | sort' comp [x] ans = x :: ans
  | sort' comp (x::xs) ans =
     let fun LE x y = case comp(x,y) of GREATER => false | _ => true
         fun GT x y = case comp(x,y) of GREATER => true | _ => false
     val small = filter (GT x) xs
         val big = filter (LE x) xs
     in sort' comp small (x::(sort' comp big ans)) end;

fun nub [] = []
  | nub [x] = [x]
  | nub (x::y::xs) = if x=y then nub (x::xs) else x::(nub (y::xs));

fun norm x = nub (sort' String.compare x [])

(* ********************************************************* *)


fun firstTable (gram as(Gram(nts,ts,ps,start))) =
let val termTable = init ts (fn x => [x])
    val nontermTable = init nts (fn x => [])
    val table = termTable @ nontermTable
    val changed = ref true
    fun onePass [] = false  (* returns true if it makes a change *)
      | onePass (x::xs) =
         let val rhss = rhssFor gram x
             fun first old =
                 let val listOflists = map (firstRhs gram table) rhss
                     val new =  List.concat listOflists
                 in norm(old @ new) end
             val b1 = update x first table
             val b2 = onePass xs
         in b1 orelse b2 end

in  while (!changed) do
          (changed := false; changed := onePass nts);
   table
end

and first table s =
 case List.find (fn (y,r) => y=s) table of
    NONE => []
  | SOME(_,r) => !r

and firstRhs gram table [] = [empty]
  | firstRhs gram table [x] = first table x
  | firstRhs gram table (x::xs) =
      let val temp = first table x
      in case List.find emptyP temp of
           NONE => temp
         | SOME _ => temp @ firstRhs gram table xs
      end;

val FirstT = firstTable g1;






(* 
*)