############################################################################
#
#	Name:	 slrtbls.icn
#
#	Title:	 slr table generation routines
#
#	Author:	 Richard L. Goerwitz
#
#	Version: 1.20
#
############################################################################
#
#  Contains make_slr_tables(grammar, atbl, gtbl, noconflict,
#  like_yacc), where grammar is an ib_grammar record (as returned by
#  ibreader), where atbl and gtbl are initialized (default &null) hash
#  tables, and where noconflict is a switch that, if nonnull, directs
#  the resolver to abort on unresolvable conflicts.  Returns &null if
#  successful in filling out atbl and gtbl.  If likeyacc is nonnull,
#  make_slr_tables will resolve reduce/reduce conflicts by order of
#  occurrence in the grammar, just like YACC.  Shift/reduce conflicts
#  will be resolved in favor of shift.
#
#  The reason for the noconflict switch is that there are parsers that
#  can accept tables with multiple action entries, i.e.  parsers that
#  can use tables generated by ambiguous grammars.
#
#  In this routine's case, success is identified with creating a
#  standard SLR action and goto table.  Note that both tables end up
#  as tables of tables, with symbols being the primary or first key,
#  and state numbers being the second.  This is the reverse of the
#  usual arrangement, but turns out to save a lot of space.  Atbl
#  values are of the form "s2.3", "r4<A>10", "a", etc.  The string
#  "s2.3" means "shift the current lookahead token, and enter state 2
#  via rule 3."  By way of contrast, "r4<A>10" means "reduce by rule
#  number 4, which has A as its LHS symbol and 10 RHS symbols."  A
#  single "a" means "accept."

#  Atbl entries may contain more than one action.  The actions are
#  simply concatenated: "s2.3r4<A>10a".  Conflicts may be resolved
#  later by associativity or precedence, if available.  Unresolvable
#  conflicts only cause error termination if the 5th and final
#  argument is nonnull (see above on "noconflict").
#
#  Gtbl entries are simpler than atble entries, consisting of a single
#  integer.
#
############################################################################
#
#  Links: follow, slritems, iohno
#
############################################################################

# declared in ibreader.icn
# record ib_grammar(start, rules, tbl)

#link follow, slritems, iohno#, ximage

#
# make_slr_tables
#
procedure make_slr_tables(grammar, atbl, gtbl, noconflict, like_yacc)

    local start_symbol, st, C, i, augmented_start_symbol, item,
	symbol, new_item_list, j, action

    # Initialize start symbol and rule list/set (either is okay).
    start_symbol := grammar.start
    st := grammar.rules

    # Number the rules, and then construct the canonical LR(0) item sets.
    every i := 1 to *st do st[i].no := i
    C := make_slr_item_sets(start_symbol, st)

    # Now, go through each item in each item set in C filling out the
    # action (atbl) and goto table (gtbl) as we go.
    #
    augmented_start_symbol := "`_" || start_symbol || "_'"
    every i := 1 to *C do {
        every item := !C[i] do {
	    # if the dot's *not* at the end of the production...
	    if symbol := item.RHS[item.POS] then {
		# if were looking at a terminal, enter a shift action
		if type(symbol) == "integer" then {
		    if symbol = -2 then next   # Never shift epsilon!
		    new_item_list := slr_goto(C[i], symbol, st)
		    every j := 1 to *C do {
			if equivalent_item_lists(new_item_list, C[j]) then {
			    action := "s" || j || "." || item.no
			    resolve(st, atbl, symbol, i, action,
				    noconflict, like_yacc)
			    break next
			}
		    }
		# if we're looking at a nonterminal, add action to gtbl
		} else {
		    new_item_list := slr_goto(C[i], symbol, st)
		    every j := 1 to *C do {
			if equivalent_item_lists(new_item_list, C[j]) then {
			    /gtbl[symbol] := table()
			    /gtbl[symbol][i] := j |
				gtbl[symbol][i] =:= j |
				iohno(80, image(symbol), ".", image(i), ":", j)
			    break next
			}
		    }
		}
	    # ...else if the dot *is* at the end of the production
	    } else {
		if item.LHS == augmented_start_symbol then {
		    action := "a"
		    # 0 = EOF 
		    resolve(st, atbl, 0, i, action, noconflict, like_yacc)
		} else {
		    # add a reduce for every symbol in FOLLOW(item.LHS)
		    every symbol := !FOLLOW(start_symbol, st, item.LHS) do {
			# RHS size is 0 for epsilon.
			if item.RHS[1] === -2 then {
			    action := "r" || item.no || "<" || item.LHS ||
				">0"
			} else
			    action := "r" || item.no || "<" || item.LHS ||
			        ">" || *item.RHS
			resolve(st, atbl, symbol, i, action,
				noconflict, like_yacc)
		    }
		}
	    }
	}
    }

    return

end


#
# resolve: list|set x table x string|integer, integer, anything, anything
#	      						-> string
#          (st, tbl, symbol, state, action, noconflict, like_yacc)
#							-> new_action_list
#
#     Add action to action table, resolving conflicts by precedence
#     and associativity, if need be.  If noconflict is nonnull, abort
#     on unresolvable conflicts.  Fails on shift/shift "conflicts," or
#     if an identical action is already present in the table entry to
#     be modified.  If like_yacc is nonnull, resolve reduce/reduce
#     conflicts by their order of occurrence in the grammar; resolve
#     shift/reduce conflicts in favor of shift.
#
procedure resolve(st, tbl, symbol, state, action, noconflict, like_yacc)

    local actions, chr, a, ruleno, p, newp

    /tbl[symbol] := table()
    /tbl[symbol][state] := ""
    
    # If this action is already present, then don't re-enter it.  Just
    # fail.
    #
    tbl[symbol][state] ? {
	while a := tab(any('sra')) do {
	    a ||:= tab(upto('.<'))
	    a ||:= { (="<" || tab(find(">")+1)) | ="." }
	    a ||:= tab(many(&digits))
	    if a == action then fail
	}
    }

    # Get rule number for the new action specified as arg 5, and
    # fetch its source production.
    action ? {
	case move(1) of {
	    "s": ruleno := (tab(find(".")+1), tab(many(&digits)))
	    "r": ruleno := 1(tab(find("<")), move(1))
	    "a": return tbl[symbol][state] := action || tbl[symbol][state]
	} | iohno(70, tbl[symbol][state])
	(newp := !st).no = ruleno |
	    iohno(72, tbl[symbol][state])
    }

    # Resolve any conflicts that might be present.
    #
    actions := ""
    tbl[symbol][state] ? {
	while a := tab(any('sra')) do {
	    # Snip out the old action, and put it into a.
	    a ||:= tab(upto('.<'))
	    a ||:= { (="<" || tab(find(">")+1)) | ="." }
	    a ||:= tab(many(&digits))
	    #
	    # Get the old action's rule number, and use it to fetch
	    # the full production that it is keyed to.
	    #
	    a ? {
		case move(1) of {
		    "s": ruleno := (tab(find(".")+1), tab(many(&digits)))
		    "r": ruleno := 1(tab(find("<")), move(1))
		    "a": return tbl[symbol][state] := a || actions || action
		} | iohno(70, tbl[symbol][state])
		# Go through rule list; find the one whose number is ruleno.
		(p := !st).no = ruleno |
		    iohno(71, tbl[symbol][state])
	    }

	    # Check precedences to see if we can resolve the conflict
	    # this way.
	    #
	    if \newp.prec > \p.prec then
		# discard the old action, a
		return tbl[symbol][state] := actions || action || tab(0)
	    else if \newp.prec < \p.prec then
		# discard the new action, action
		return tbl[symbol][state] := actions || a || tab(0)
	    else {
		#
		# If, however, both precedences are the same (i.e.
		# newp.prec === p.prec), then we must check the
		# associativities.  Right implies shift; left, reduce.
		# If there is no associativity, then we have a
		# conflict.  Nonassociative ("n") implies error.
		#
		case action[1] of {
		    default: iohno(70, tbl[symbol][state])	
		    # case "a" is handled above; look for "s" & "r"
		    "s" : {
			if a[1] == "s" then fail  # no shift/shift "conflict"
			else if a[1] == "r" then {
			    newp.assoc === p.assoc | {
				iohno(40, "state " || state || "; token " ||
				      symbol || "; rules " || newp.no ||
				      "," || p.no)
			    }
			    case newp.assoc of {
				"n"  : iohno(41, production_2_string(newp))
				&null: { # no associativity given
				    if \noconflict & /like_yacc then
					iohno(46, "state " || state ||
					      "; token " || symbol ||
					      "; rules " || newp.no ||
					      "," || p.no)
				    else {
					write(&errout, "warning: shift/reduce",
					      " conflict in state " || state ||
					      "; token " || symbol ||
					      "; rules " || newp.no ||
					      "," || p.no)
					if \like_yacc then {
					    write(&errout, "resolving in _
						            favor of shift.")
					    return tbl[symbol][state] :=
					       actions || action || tab(0)
					} else {
					    write(&errout, "creating multi-_
							action table entry")
					    return tbl[symbol][state] :=
					       actions || action || a || tab(0)
					}
				    }
				}
				"l"  : { # left associative
				    # discard new action, action
				    return tbl[symbol][state] := 
					actions || a || tab(0)
				}
				"r"  : { # right associative
				    # remove old action, a
				    return tbl[symbol][state] := 
					actions || action || tab(0)
				}
			    }
			}
		    }
		    "r" : {
			if a[1] == "r" then {
			    #
			    # If conflicts in general, and reduce-reduce
			    # conflicts in specific are not okay...
			    #
			    if \noconflict & /like_yacc then {
				# ...abort, otherwise...
				iohno(42, "state " || state || "; token " ||
				      symbol || "; " || "; rules " ||
				      newp.no || "," || p.no)
			    } else {
				#
				# ...flag reduce-reduce conficts, and
				# then resolve them by their order of
				# occurrence in the grammar.
				#
				write(&errout, "warning: reduce/reduce",
				      " conflict in state ", state,
				      "; token ", symbol, "; rules ",
				      newp.no, ",", p.no)
				if \like_yacc then {
				    write(&errout, "resolving by order of _
				          occurrence in the grammar")
				    if newp.no > p.no
				    # discard later production (newp)
				    then return return tbl[symbol][state] := 
					actions || a || tab(0)
				    # discard later production (old p)
				    else return tbl[symbol][state] := 
					actions || action || tab(0)
				} else {
				    #
				    # If conflicts ok, but we aren't supposed
				    # to resolve reduce-reduce conflicts by
				    # order of rule occurrence:
				    #
				    write(&errout, "creating multi-action _
					table entry")
				    return tbl[symbol][state] :=
					actions || action || a || tab(0)
				}
			    }
			} else {
			    # associativities must be the same for both rules:
			    newp.assoc === p.assoc | {
				iohno(40, "state " || state || "; token " ||
				      symbol || "; rules " || newp.no ||
				      "," || p.no)
			    }
			    case newp.assoc of {
				"n"  : iohno(41, production_2_string(newp))
				&null: {
				    if \noconflict & /like_yacc then
					iohno(46, "state " || state ||
					      "; token " || symbol ||
					      "; rules " || newp.no ||
					      "," || p.no)
				    else {
					write(&errout, "warning: shift/reduce",
					      " conflict in state " || state ||
					      "; token " || symbol ||
					      "; rules " || newp.no ||
					      "," || p.no)
					if \like_yacc then {
					    write(&errout, "resolving in _
						            favor of shift.")
					    return tbl[symbol][state] :=
					       actions || a || tab(0)
					} else {
					    write(&errout, "creating multi-_
							action table entry")
					    return tbl[symbol][state] :=
					       actions || action || a || tab(0)
					}
				    }
				}
				"r"  : {
				    # discard new action, action
				    return tbl[symbol][state] :=
					actions || a || tab(0)
				}
				"l"  : {
				    # remove old action, a
				    return tbl[symbol][state] :=
					actions || action || tab(0)
				}
			    }
			}
		    }
		}
	    }
	}
    }

    return tbl[symbol][state] ||:= action

end
