############################################################################
#
#	File:     pt.icn
#
#	Subject:  Program to produce parse table generator
#
#	Author:   Deeporn H. Beardsley
#
#	Date:     December 10, 1988
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  See pt.man for a description of functionality as well as input and
#  output format.
#
############################################################################

#**********************************************************************
#*                                                                    *
#* Main procedure as well as                                          * 
#*      a routine to generate production table, nonterminal, terminal *
#*      and epsilon sets from the input grammar                       *
#**********************************************************************
#
#  1.  Data structures:-
#
#       E.g.  Grammar:-
#               
#               A -> ( B )
#               A -> B , C
#               A -> a
#               B -> ( C )
#               B -> C , A
#               B -> b
#               C -> ( A )
#               C -> A , B
#               C -> c
#
#       prod_table		           prod
#               __________________         _____  _____  _____  
#               |     |          |     num | 1 |  | 2 |  | 3 |
#               | "A" |    ------|-->[     |---| ,|---| ,|---| ]
#               |     |          |     rhs |_|_|  |_|_|  |_|_|
#               |     |          |           |      |      v  
#               |     |          |           |      v      ["a"]
#               |     |          |           v      ["B",",","C"]
#               |     |          |           ["(","B",")"]
#               |_____|__________|         _____  _____  _____  
#               |     |          |     num | 4 |  | 5 |  | 6 |
#               | "B" |    ------|-->[     |---| ,|---| ,|---| ]
#               |     |          |     rhs |_|_|  |_|_|  |_|_|
#               |     |          |           |      |      v  
#               |     |          |           |      v      ["b"]
#               |     |          |           v      ["C",",","A"]
#               |     |          |           ["(","C",")"]
#               |_____|__________|         _____  _____  _____  
#               |     |          |     num | 7 |  | 8 |  | 9 |
#               | "C" |    ------|-->[     |---| ,|---| ,|---| ]
#               |     |          |     rhs |_|_|  |_|_|  |_|_|
#               |     |          |           |      |      v  
#               |     |          |           |      v      ["c"]
#               |     |          |           v      ["A",",","B"]
#               |     |          |           ["(","A",")"]
#               ------------------
#
#               __________________
#       firsts  | "A" |    ------|-->("(", "a", "b", "c")
#               |-----|----------|
#               | "B" |    ------|-->("(", "a", "b", "c")
#               |-----|----------|
#               | "C" |    ------|-->("(", "a", "b", "c")
#               ------------------
#
#               _______
#       NTs     |  ---|-->("A", "B", "C")
#               -------
#
#               _______
#       Ts      |  ---|-->("(", "a", "b", "c")
#               -------
#
#  2.  Algorithm:-
#
#       get_productions() -- build productions table (& NT, T 
#          		     and epsilon sets):-
#               open grammar file or from stdin
#               while can get an input line, i.e. production, do
#                 get LHS token and use it as entry value to table
#                   (very first LHS token is start symbol of grammar)
#                   (enter token in nonterminal, NT, set)
#                 get each RHS token & form a list, put this list 
#                   in the list, i.e.assigned value, of the table
#                   (enter each RHS token in terminal, T, set)
#                   (if first RHS token is epsilon
#                      enter LHS token in the epsilon set)
#               (T is the difference of T and NT)
#               close grammar file
#
#**********************************************************************
global prod_table, NTs, Ts, firsts, stateL, itemL
global StartSymbol, start, eoi, epsilon
global erratta			# to list all items in a state (debugging)
record prod(num, rhs)           # assigned values for prod_table
record arc(From, To)            # firsts computation -- closure
record item(prodN, lhs, rhs1, rhs2, NextI)
record state(C_Set, I_Set, goto)
procedure main(opt_list)
  local opt

  start := "START"              # start symbol for augmented grammar
  eoi := "EOI"                  # end-of-input token (constant)
  epsilon := "EPSILON"          # epsilon token (constant)
  prod_table := table()         # productions
  NTs := set()                  # non-terminals
  Ts := set()                   # terminals
  firsts := table()             # nonterminals only; first(T) = {T}
  get_firsts(get_productions())
  if /StartSymbol then exit(0)	# input file empty
  write_prods()
  if opt := (!opt_list == "-nt") then
    write_NTs()
  if opt := (!opt_list == "-t") then
    write_Ts()
  if opt := (!opt_list == "-f") then
    write_firsts()
  if opt := (!opt_list == "-e") then
    erratta := 1  
  else
    erratta := 0  
  stateL := list()              # not popped, only for referencing
  itemL := list()               # not popped, only for referencing
  state0()                      # closure of start production
  gotos()                       # sets if items
  p_table()                     # output parse table
end
 
procedure get_productions()
  local Epsilon_Set, LHS, first_RHS_token, grammarFile, line, prods, temp_list
  local token, ws

  prods := 0                    # for enumeration of productions
  ws := ' \t'
  Epsilon_Set := set()          # NT's that have epsilon production
  grammarFile := (open("grammar") | &input)
  while line := read(grammarFile) do {
    first_RHS_token := &null    # to detect epsilon production
    temp_list := []             # RHS of production--list of tokens
    line ? {
      tab(many(ws))
      LHS := tab(upto(ws))      # LHS of production--nonterminal
      /firsts[LHS] := set()     
      /StartSymbol := LHS       # start symbol for unaug. grammar
      insert(NTs, LHS)          # collect nonterminals
      tab(many(ws)); tab(match("->")); tab(many(ws))
      while put(temp_list, token := tab(upto(ws))) do {
        /first_RHS_token := token
        insert(Ts, token)       # put all RHS tokens into T set for now
        tab(many(ws))
      }
      token := tab(0)		# get last RHS non-ws token
      if *token > 0 then {
        put(temp_list, token)
        /first_RHS_token := token
        insert(Ts, token)
      }
      Ts --:= NTs               # set of terminals
      delete(Ts, epsilon)	# EPSILON is not a terminal
      /prod_table[LHS] := []
      put(prod_table[LHS], prod(prods +:=1, temp_list))
    }
    if first_RHS_token == epsilon then
      insert(Epsilon_Set, LHS)
  }
  if not (grammarFile === &input) then 
    close(grammarFile)
  return Epsilon_Set
end
#**********************************************************************
#*                                                                    *
#* Routines to generate first sets                                    *
#**********************************************************************
#  1.  Data structures:-
#       (see also data structures in mainProds.icn)
#
#               __________________
#       needs   | "A" |    ------|-->[B]
#               |-----|----------|
#               | "B" |    ------|-->[C]
#               |-----|----------|
#               | "C" |    ------|-->[A]
#               ------------------
#
#       has_all_1st
#               _______
#               |  ---|-->("A", "C")
#               -------
#
#
#       G    |-----------------------| 
#            |  __________________   v 
#            |  | "A" |    ------|-->(B)<--------|
#            |  |-----|----------|               |
#            |--|---  |      ----|-->"A"         |
#               |-----|----------|               |
#               | "B" |    ------|-->(C)<-----|  |
#               |-----|----------|            |  |
#               | (C) |    ------|-->"B"      |  |
#               |-----|----------|            |  |
#               | "C" |    ------|-->(A)<--|  |  |
#               |-----|----------|         |  |  |
#               | (A) |    ------|-->"C"   |  |  |
#               ------------------         |  |  |
#                                          |  |  |
#       closure_table                      |  |  |
#               __________________         |  |  |
#               | "A" |    ------|-->( ----| ,| ,| ) 
#               |-----|----------|
#               | "B" |    ------|-->( as above    )
#               |-----|----------|
#               | "C" |    ------|-->( as above    )
#               ------------------
#
#       (Note: G table: the entry values (B) and (C) should be analogous
#                       to that of '(A)'.)
#
#  2.  Algorithms:-
#
#       2.1  Firsts sets (note: A is nonterminal & 
#                               beta is a string of symbols):-
#                         For definition, see Aho, et al, Compilers...
#                               Addison-Wesley, 1986, p.188)
#               for each production A -> beta (use production table above)
#                 loop1
#                   case next RHS token, B, is
#                     epsilon    :  do nothing, break from loop1
#                     terminal   :  insert it in first(A), break from loop1
#                     nonterminal:  put B in needs[A] table
#                                   if B in epsilon set & last RHS token
#                                     insert A in epsilon set
#                                     break from loop1
#                                   loop1
#               collect has_all_1st set (NTs whose first is fully defined
#                       i.e. NTs not entry value of needs table)
#               Loop2 (fill_firsts)
#                 for each NT B in each needs[A]
#                   if B is in has_all_1st
#                     insert all elements of first(B) in first(A)
#                     delete B from needs[A]
#                 if needs[A] is empty 
#                   insert A in has_all_1st
#                 if *has_all_1st set equal to *NTs set
#                   exit loop2
#                 if *has_all_1st set not equal to *NTs set
#                   if *has_all_1st not changed from beginning of loop2
#                   (i.e. circular dependency e.g.
#                       needs[X] = [Y]
#                       needs[Y] = [Z]
#                       needs[Z] = [X])
#                       find closure of each A
#                       find a set of A's whose closure sets are same
#                         pool their firsts together
#                         add pooled firsts to first set of each A
#                       goto loop2
#
#
#               This algorithm is implemented by the following procedures:-
#
#                 get_firsts(Epsilon_Set) -- compute first sets of all
#                    NTs, given the NTs that have epsilon productions.
#
#                 fill_firsts(needs) -- given the needs table that says
#                    which first set contains the elements of other
#                    first set(s), complete computation of first sets.
#
#                 buildgraph(tempL) -- given the productions in tempL,
#                    build table G above.
#
#                 closure(G, S1, S2) -- given the productions in tempL,
#                    the entry value S1 and its closure set S2, build 
#                    closure_table.
#
#                 addnode(n, t) -- given table t ( G, actually), and
#                    1. entry value of n, enter its assigned value in
#                       in table t to be a set (empty, for now) 
#                    2. use t[n] (in 1) as the entry value, enter its
#                       assigned value in table t to be "n".
#
#                 closed_loop(G, SS, closure_table, tempL_i) -- given
#                    table G, closure_table and a nonterminal tempL_i
#                    that still needs its firsts completed, return the
#                    set SS of nonterminals if each and every of these
#                    nonterminals has identical closure set.
#
#                 finish_firsts(closed_set) -- given the set closed_set
#                    of nonterminals where every member of of the set
#                    has identical closure set, pool the elements 
#                    (terminals) from their so-far known firsts sets
#                    together and reenter this pooled value into their
#                    firsts sets (firsts table).
#
#       2.2  Note that buildgraph(), closure() and addnode()
#                 are either exactly or essentially the same as
#                 given in class (by R. Griswold).
#
#**********************************************************************
 
procedure get_firsts(Epsilon_Set)
  local needs, prods, i, j, k, token

  needs := table()
  prods := sort(prod_table, 3)
  every i := 1 to *prods by 2 do                # production(s) of a NT
    every j := 1 to *prods[i+1] do              # RHS of each production
      every k := 1 to *prods[i+1][j].rhs do     #  and each token
        if ((token := prods[i+1][j].rhs[k]) == epsilon) then
          break                                 # did in get_productions
        else if member(Ts, token) then {        # leading token on RHS
          insert(firsts[prods[i]], token)       # e.g. A -> ( B )
          break
        }
        else { #if member(NTs, token) then      #      A -> B a C
          /needs[prods[i]] := [] 
          put(needs[prods[i]], token)
          if not (member(Epsilon_Set, token)) then # not B -> EPSILON
            break
          if k = *prods[i+1][j].rhs then   # all RHS tokens are NTs &
            insert(Epsilon_Set, prods[i])  # each has epsilon production
        }
  fill_firsts(needs)    # do firsts that contain firsts of other NT(s)
  every insert(firsts[!Epsilon_Set], epsilon) # add epsilon last
end
 
procedure fill_firsts(needs)
  local G, L, NTy, SS, closed_set, closure_table, has_all_1st, i, lhs
  local new_temp, rhs, size_has_all_1st, ss, ss_table, tempL, x

  closure_table := table()
  has_all_1st := copy(NTs)              # set of NTs whose firsts fully defined
  tempL := sort(needs, 3)
  every i := 1 to *tempL by 2 do
    delete(has_all_1st, tempL[i])
  repeat {
    ss := ""
    ss_table := table()
    size_has_all_1st := *has_all_1st
    new_temp := list()
    while lhs := pop(tempL) do {
      rhs := pop(tempL)
      L := list()
      while NTy := pop(rhs) do
        if NTy ~== lhs then
          if member(has_all_1st, NTy) then
            firsts[lhs] ++:= firsts[NTy]
          else
            put(L, NTy)
      if *L = 0 then
        insert(has_all_1st, lhs)
      else {
        put(new_temp, lhs)
        put(new_temp, L)
      }
    }
    tempL := new_temp
    if *has_all_1st = *NTs then
      break
    if size_has_all_1st = *has_all_1st then {
      G := buildgraph(tempL)
      every i := 1 to *tempL by 2 do 
        closure_table[tempL[i]] := closure(G, tempL[i])
      every i := 1 to *tempL by 2 do {
        closed_set := set()
        SS := set([tempL[i]])
        every x := !closure_table[tempL[i]] do
          insert(SS, G[x])
        closed_set := closed_loop(G,SS,closure_table,tempL[i])
        if \closed_set then {
          finish_firsts(closed_set) 
          every insert(has_all_1st, !closed_set)
          break
        }
      }
    }
  }
  return
end
 
procedure buildgraph(tempL)     # modified from the original version 
  local arclist, nodetable, x, i

  arclist := []                 # by Ralph Griswold
  nodetable := table()
  every i := 1 to *tempL by 2 do {
    every x := !tempL[i+1] do {
     addnode(tempL[i], nodetable)
     addnode(x, nodetable)
     put(arclist, arc(tempL[i], x))
    }
  }
  while x := get(arclist) do
    insert(nodetable[x.From], nodetable[x.To])
  return nodetable
end
 
procedure closure(G, S1, S2)    # modified from the original version 
  local S

  /S2 := set([G[S1]])           # by Ralph Griswold
  every S := !(G[S1]) do
    if not member(S2, S) then {
      insert(S2, S)
      closure(G, G[S], S2)
    }
  return S2
end
 
procedure addnode(n, t)         # author: Ralph Griswold 
  local S

  if /t[n] then {
    S := set()
    t[n] := S
    t[S] := n
  }
  return
end
 
procedure closed_loop(G, SS, closure_table, tempL_i)
  local S, x, y

  delete(SS, tempL_i)
  every x := !SS do {
    S := set()
    every y := !closure_table[x] do
      insert(S, G[y])
    delete(S, tempL_i)
    if *S ~= *SS then fail
    every y := !S do
      if not member(SS, y) then fail
  }
  return insert(SS, tempL_i)
end 
 
procedure finish_firsts(closed_set)
  local S, x

  S := set()
  every x := !closed_set do
    every insert(S, !firsts[x]) 
  every x := !closed_set do
    every insert(firsts[x], !S)
end
#**********************************************************************
#*                                                                    *
#* Routines to generate states                                        *
#**********************************************************************
#
#  1.  Data structures:-
#
#       E.g. Augmented grammar:-
#       
#               START -> S              (production 0)
#               S -> ( S )              (production 1)
#               S -> ( )                (production 2)
#
#             Item is a record of 5 fields:-
#                 Example of an item: itemL[1] is [START->.S , $] 
#                      prodN     represents the production number
#                      lhs       represents the nonterminal at the
#                                left hand side of the production
#                      rhs1      represents the list of tokens seen so 
#                                far (i.e. left of the dot in item)
#                      rhs2      represents the list of tokens yet to be
#                                seen (i.e. right of the dot in item)
#                      NextI     represents the next input symbol
#                                (the end of input symbol $ is 
#                                represented by EOI.)
#             
#             
#				  item             
#                                _________       _________
#                           prodN|   0   |       |   1   |
#                                |-------|       |-------|
#                           lhs  |"START"|       |  "S"  |
#               _______          |-------|       |-------|     
#       itemL   |  ---|-->[ rhs1 |    ---|---| , |  -----|---| , ...  ]
#               -------          |-------|   |   |-------|   | 
#                           rhs2 |    ---|-| |   |  -----|-| |
#                                |-------| | |   |-------| | | 
#                           NextI| "EOI" | | |   | "EOI" | | | 
#                                --------- | |   --------- | | 
#                                          | |             | | 
#                                          | |             | |    
#                                          | v             | v
#                                          | []            | []
#                                          |               |
#                                          v               v
#                                          ["S"]           ["(", "S", ")"]
#
#				 state
#                                _______         
#                           C_Set|  ---|-----|
#               _______          |-----|     |
#       stateL  |  ---|-->[ I_Set|  ---|---| |  , ...  ]
#               -------          |-----|   | | 
#                           goto |  ---|-| | |
#                                ------- | | |
#                                        | | v
#                                        | | (1, 2, 3)
#                                        | v        
#                                        | (1)   
#                                        v        
#                                        __________________    
#               	   	         | "A" |    5     |
#			                 |-----|----------|
#			                 | "B" |    2     |
#			                 |-----|----------|
#			                 | "C" |    3     |
#			                 ------------------
#
#
#       (Note: 1.  The above 2 lists:-
#                    -- are not to be popped
#                    -- new elements are put in the back
#                    -- index represents the identity of the element
#                    -- no duplicate elements in either list
#	       2.  The state record:-
#			I_Set represents J in function goto(I,x) in 
#		 	  Compiler, Aho, et al, Addison-Wesley, 1986,
#			  p. 232.
#			C_Set represents the closure if I_Set.
#			goto is part of the goto table and the shift 
#			  actions of the final parse table.)
#              3.  The 1 in C_Set and I_Set in the diagrams above refer 
#                       the same (physical) element.
#
#  2.  Algorithms:-
#
#         state0() -- create itemL[1] and stateL[1] as well as its
#                       closure.
#
#         item_num(P_num, N_lhs, N_rhs1, N_rhs2, NI) --
#                     if the item with the values given in the
#                       argument list already exists in itemL list,
#                       it returns the index of the item in the list,
#                     if not, it builds a new item and put it at the 
#                       end of the list and returns the new index.
#
# 	  prod_equal(prod1, prod2) --  prod1 and prod2 are lists of
#		      strings; fail if they are not the same.
#
# 	  state_closure(st) -- given the item set (I_set of the state 
#		      st), set the value of C_Set of st to the closure
#		      of this item set.  For definition of closure, 
#                     see Aho, et al, Compilers..., Addison-Wesley, 
#		      1986, pp. 222-224)
#		      
# 	  new_item(st,O_itm) -- given the state st and an item O_itm,
#		      suppose the item has the following configuration:-
#			     [A -> B.CD,x]
#		      where CD is a string of terminal and nonterminal
#		      tokens.  If C is a nonterminal, 
# 		        for each C -> E in the grammar, and 
#			for each y in first(Dx), add the new item
#			     [C -> .E,y] 
#			to the C_Set of st.
#
# 	  all_firsts(itm) -- given an item itm and suupose it has the
#		      following configuration:-
#			     [A -> B.CD,x]
#		      where D is a string of terminal and nonterminal
#		      tokens.  The procedure returns first(Dx).
#
# 	  gotos() -- For definition of goto operation, see Aho, et al,
#                    Compilers..., Addison-Wesley, 1986, pp. 224-227)
#		     The C = {closure({[S'->S]})} is set up by the
#		            state0()
#		     call in the main procedure.
#	
#		     It also compiles the goto table.  The errata part
#		     (last section of the code in this procedure) is
#		     for debugging purposes and is left intact for now.
#		      
# 	  moved_item(itm) -- given the item itm and suppose it has the
#		      following configuration:-
#			     [A -> B.CD,x]
#		      where D is a string of terminal and nonterminal
#		      tokens.  The procedure builds a new item:-
#			     [A -> BC.D,x]
#		      It then looks up itemL to see if it already is
#		      in it.  If so, it'll return its index in the list,
#		      else, it'll put it in the back of the list and
#		      return this new index.  (This is done by calling
#		      item_num()).
#		      
# 	  exists_I_Set(test) -- given the I_Set test, look in the stateL
#		     list and see if any state does contain similar
#		     I_Set, if so, return its index to the stateL list,
#		     else fail.
#		      
#	  set_equal(set1, set2) -- set1 and set2 are sets of integers;
#		      return set1 if the two sets have the same elements
#		      else fail.  (It is used strictly in comparison of
#		      I_Sets).
#
#
#**********************************************************************
 
procedure state0()
  local itm, st

  itm := item_num(0, start, [], [StartSymbol], eoi)
  st := state(set(), set([itm]), table())
  put(stateL, st)
  state_closure(st)     # closure on initial state
end
 
procedure item_num(P_num, N_lhs, N_rhs1, N_rhs2, NI)
  local itm, i

  itm := item(P_num, N_lhs, N_rhs1, N_rhs2, NI)
  every i := 1 to *itemL do {
    if itm.prodN ~== itemL[i].prodN then next
    if itm.lhs ~== itemL[i].lhs then next
    if not prod_equal(itm.rhs1, itemL[i].rhs1) then next
    if not prod_equal(itm.rhs2, itemL[i].rhs2) then next
    if itm.NextI == itemL[i].NextI then return i
  }
  put(itemL, itm)
  return *itemL
end
 
procedure prod_equal(prod1, prod2)      # compare 2 lists of strings
  local i

  if *prod1 ~= *prod2 then fail
  every i := 1 to *prod1 do
    if prod1[i] ~== prod2[i] then fail
  return
end
 
procedure state_closure(st)
  local addset, more_set, i

  st.C_Set := copy(st.I_Set)
  addset := copy(st.C_Set)
  while *addset > 0 do {
    more_set := set()
    every i := !addset do
      if (itemL[i].rhs2[1] ~== epsilon) then
        if member(NTs, itemL[i].rhs2[1]) then
          more_set ++:= new_item(st,itemL[i])
    addset := more_set
  }
end
 
procedure new_item(st,O_itm)
  local N_Lhs, N_Rhs1, N_prod, NxtInput, T_itm, i, rtn_set
  rtn_set := set()
  NxtInput := all_firsts(O_itm)
  N_Lhs := O_itm.rhs2[1]
  N_Rhs1 := []
  every N_prod := !prod_table[N_Lhs] do
    every i := !NxtInput do {
      T_itm := item_num(N_prod.num, N_Lhs, N_Rhs1, N_prod.rhs, i)
      if not member(st.C_Set, T_itm) then {
        insert(st.C_Set, T_itm)
        insert(rtn_set, T_itm)
      }
    }
  return rtn_set
end
 
procedure all_firsts(itm)
  local rtn_set, i

  if *itm.rhs2 = 1 then
    return set([itm.NextI])
  rtn_set := set()
  every i := 2 to *itm.rhs2 do
    if member(Ts, itm.rhs2[i]) then 
      return insert(rtn_set, itm.rhs2[i])
    else {
      rtn_set ++:= firsts[itm.rhs2[i]]
      if not member(firsts[itm.rhs2[i]], epsilon) then
        return rtn_set
    }
  return insert(rtn_set, itm.NextI)
end
 
procedure gotos()
  local New_I_Set, gost, i, i_num, j, j_num, looked_at, scan, st, st_num, x
  st_num := 1
  repeat{
    looked_at := set()
    scan := sort(stateL[st_num].C_Set)
    every i := 1 to *scan do {
      i_num := scan[i]
      if member(looked_at, i_num) then next
      insert(looked_at, i_num)
      x := itemL[i_num].rhs2[1]         # next LHS
      if ((*itemL[i_num].rhs2 = 0) | (x == epsilon)) then next
      New_I_Set := set([moved_item(itemL[i_num])])
      every j := i+1 to *scan do {
        j_num := scan[j]
        if not member(looked_at, j_num) then
          if (x == itemL[j_num].rhs2[1]) then {
            insert(New_I_Set, moved_item(itemL[j_num]))
            insert(looked_at, j_num)
          }
      }
      if gost := exists_I_Set(New_I_Set) then 
        stateL[st_num].goto[x] := gost    #add into goto
      else { # add a new state
        st := state(set(), New_I_Set, table())
        put(stateL, st)
        state_closure(st)
        stateL[st_num].goto[x] := *stateL    #add into goto
      }
    }
    if erratta=1 then {
      write("--------------------------------")
      write("State ", st_num-1)
      write_state(stateL[st_num])
    }
    st_num +:= 1
    if st_num > *stateL then {
      if erratta=1 then
        write("--------------------------------")
      return stateL
    }
  }
end
 
procedure moved_item(itm)
  local N_Rhs1, N_Rhs2, i

  N_Rhs1 := copy(itm.rhs1)
  put(N_Rhs1, itm.rhs2[1])
  N_Rhs2 := list()
  every i := 2 to *itm.rhs2 do
    put(N_Rhs2, itm.rhs2[i])
  return item_num(itm.prodN, itm.lhs, N_Rhs1, N_Rhs2, itm.NextI)
end
 
procedure exists_I_Set(test)
  local st

  every st := 1 to *stateL do
    if set_equal(test, stateL[st].I_Set) then return st
  fail
end
 
procedure set_equal(set1, set2)         
  local i

   if *set1 ~= *set2 then fail
   every i := !set2 do
     if not member(set1, i) then fail
   return set1
end
#**********************************************************************
#*                                                                    *
#* Miscellaneous write routines                                       *
#**********************************************************************
#	The following are write routines; some for optional output
#	while others are for debugging purposes. 
# 
#	    write_item(itm) -- write the contents if item itm.
#	    write_state(st) -- write the contents of state st.
#	    write_tbl_list(out) -- (for debugging purposes only).
#	    write_prods()-- write the enmnerated grammar productions.
#	    write_NTs() -- write the set of nonterminals.
#	    write_Ts() -- write the set of terminals.
#	    write_firsts() -- write the first sets of each nonterminal.
#	    write_needs(L) -- write the list of all nonterminals and the
#			      associated nonterminals whose first sets 
#			      it still needs to compute its own first
#			      set.
# 
#**********************************************************************
 
procedure write_item(itm)
  local i

  writes("[(",itm.prodN,") ",itm.lhs," ->")
  every i := !itm.rhs1 do writes(" ",i)
  writes(" .")
  every i := !itm.rhs2 do writes(" ",i)
  writes(", ",itm.NextI,"]\n")
end
 
procedure write_state(st)
  local i, tgoto

  write("I_Set")
  every i := ! st.I_Set do {
    writes("Item ", i, " ")
    write_item(itemL[i])
  }
  write()
  write("C_Set")
  every i := ! st.C_Set do {
    writes("Item ", i, " ")
    write_item(itemL[i])
  }
  tgoto := sort(st.goto, 3)
  write()
  write("Gotos")
  every i := 1 to *tgoto by 2 do
    write("Goto state ", tgoto[i+1]-1, " on ", tgoto[i])
end
 
procedure write_tbl_list(out)
  local i, j

  every i := 1 to *out by 2 do {
    writes(out[i], ", [")
    every j := *out[i+1] do {
      if j ~= 1 then
        writes(", ")
      writes(out[i+1][j])
    }
    writes("]\n")
  }
end
 
procedure write_prods()
  local i, j, k, prods

  prods := sort(prod_table, 3)
  every i := 1 to *prods by 2 do 
    every j := 1 to *prods[i+1] do {
      writes(right(string(prods[i+1][j].num),3," "),":  ")
      writes(prods[i], " ->")
      every k := 1 to *prods[i+1][j].rhs do
        writes(" ", prods[i+1][j].rhs[k])
      writes("\n")
    }
end
 
procedure write_NTs()
  local temp_list

  temp_list := sort(NTs)
  write("\n")
  write("nonterminal sets are:")
  every write(|pop(temp_list))
end
 
procedure write_Ts()
  local temp_list

  temp_list := sort(Ts)
  write("\n")
  write("terminal sets are:")
  every write(|pop(temp_list))
end
 
procedure write_firsts()
  local temp_list, i, j, first_list

  temp_list := sort(firsts, 3)
  write("\nfirst sets:::::")
  every i := 1 to *temp_list by 2 do {
    writes(temp_list[i], ": ")
    first_list := sort(temp_list[i+1])
    every j := 1 to *first_list do
      writes(" ", pop(first_list))
    writes("\n\n")
  }
end
 
procedure write_needs(L)
  local i, temp

  write("tempL : ")
  every i := 1 to *L by 2 do {
    writes(L[i], " ")
    temp := copy(L[i+1])
    every writes(|pop(temp))
    writes("\n")
  }
end
#**********************************************************************
#*                                                                    *
#* Output the parse table routines                                    *
#**********************************************************************
#
#	p_table() -- output parse table: tablulated (vertical and
#		     horizontal lines, etc.) if the width is within
#		     80 characters long else a listing.
#
#	outline(size, out, st_num, T_list, NT_list) -- print the header;
#		     used in table form.
#
#	border(size, T_list, NT_list, col) -- draw a horizontal line
#		     for the table form, given the table size that tells
#		     the length of each token given the lists of 
#		     terminals and nonterminals.  If the line is the 
#		     last line of the table, col given is "-", else it 
#		     is "-". 
#
#	outstate(st, out, T_list, NT_list) -- print the shift, reduce
#		     and goto for state st from information given in
#		     out, and the lists of terminals and nonterminals;
#		     used to output the parse table in the listing form.
#
#**********************************************************************
 
procedure p_table()
  local NT_list, T_list, action, gs, i, itm, msize, out, s, size, st_num, tsize

  T_list := sort(Ts)
  put(T_list, eoi)
  NT_list := sort(NTs)
  size := table()
  out := table()
  if *stateL < 1000 then msize := 4
  else if *stateL < 10000 then msize := 5
  else msize := 6
  tsize := 7
  every s := !T_list do {
    size[s] := *s
    size[s] <:= msize
    tsize +:= size[s] + 3
    out[s] := s
  }
  every s := !NT_list do {
    size[s] := *s
    size[s] <:= msize
    tsize +:= size[s] + 3
    out[s] := s
  }
  write()
  write()
  write("PARSE TABLE")
  write()
  if tsize <= 80 then {
    outline(size, out, 0, T_list, NT_list)
    border(size, T_list, NT_list, "+")
  }
  every st_num := 1 to *stateL do {
    out := table()
    gs := sort(stateL[st_num].goto,3)
    every i := 1 to * gs by 2 do {  # do the shifts and gotos
      if member(Ts, gs[i]) then
        out[gs[i]] := "S" || string(gs[i+1]-1)	# shift (action table)
      else
        out[gs[i]] := string(gs[i+1]-1)		# for goto table
    }
    every itm := itemL[!stateL[st_num].C_Set] do {
      if ((*itm.rhs2 = 0) | (itm.rhs2[1] == epsilon))  then {
        if itm.prodN = 0 then
          action := "ACC"			# accept state
        else
          action := "R" || string(itm.prodN)	# reduce (action table)
        if /out[itm.NextI] then
          out[itm.NextI] := action
        else { # conflict
          write(&errout, "Conflict on state ", st_num-1, " symbol ",
           itm.NextI, " between ", action, " and ", out[itm.NextI])
          write(&errout, "  ", out[itm.NextI], " takes presidence")
        }
      }
    }
    if tsize <= 80 then
      outline(size, out, st_num, T_list, NT_list)
    else
      outstate(st_num, out, T_list, NT_list)
  }
end
 
procedure outline(size, out, st_num, T_list, NT_list)
  local s

  if st_num = 0 then
    writes("State")
  else
    writes(right(string(st_num-1),5," "))
  writes(" ||")
  every s := !T_list do {
    /out[s] := ""
    writes(" ", center(out[s],size[s]," "), " |")
  }
  writes("|")
  every s := !NT_list do {
    /out[s] := ""
    writes(" ", center(out[s],size[s]," "), " |")
  }
  write()
  if st_num < * stateL then
    border(size, T_list, NT_list, "+")
  else
    border(size, T_list, NT_list, "-")
end
 
procedure border(size, T_list, NT_list, col)
  local s

  writes("------", col, col)
  every s := !T_list do
    writes("-", center("",size[s],"-"),"-", col)
  writes(col)
  every s := !NT_list do
    writes("-",center("",size[s],"-"), "-", col)
  writes("\n")
end
 
procedure outstate(st, out, T_list, NT_list)
  local s

  write()
  write("Actions for state ", st-1)
  every s := !T_list do
    if \out[s] then
      if out[s][1] == "R" then
        write("   On ", s, " reduce by production ", out[s][2:0])
      else if out[s][1] == "A" then
	write("   On ", s, " ACCEPT")
      else
        write("   On ", s, " shift to state ", out[s][2:0])
  every s := !NT_list do
    if \out[s] then
      write("   On ", s, " Goto ", out[s])
  write()
end

