############################################################################
#
#	File:     utrim.icn
#
#	Subject:  Program to remove unneeded procs from ucode
#
#	Author:   Gregg M. Townsend
#
#	Date:     August 7, 1993
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  Usage:  utrim [-s | -v] file...
#
#  Utrim alters a set of uncode files comprising a complete Icon program
#  by removing unreferenced procedures.  The resulting files are smaller,
#  and they produce a smaller icode file.
#
#  The basename of each command argument is used to find a pair of
#  .u1 and .u2 files; each pair is renamed to .u1o and .u2o and
#  replaced by new .u1 and .u2 files.
#
#  -s invokes silent mode; -v invokes verbose mode.
#
#  Warning: utrim may break programs that use string invocation.
#
############################################################################
#
#  Links: options
#
############################################################################

link options

record prc(name, size, calls, need)	# proc record
record lcl(name, flags)			# local record

global pnames, ptable			# proc names and table


# main procedure

procedure main(args)
   local opts, fname, name, need

   # process options
   opts := options(args, "sv")
   if *args = 0 then
      stop("usage: ", &progname, " [-s | -v] file.u1 ...")
   every !args ?:= tab(upto('.'))

   # scan .u1 files to decide what's needed
   pnames := set()
   ptable := table()
   every scan1(!args)
   if /ptable["main"] then
      stop(&progname, ": no main procedure")
   dependencies()
   report(opts)

   # write new .u1 and .u2 files
   every fname := !args || (".u1" | ".u2") do {
      remove(fname || "o")
      rename(fname, fname || "o") | stop("can't rename ", fname)
      }
   every filter1(!args)
   every filter2(!args)
end


#  scan1(fname) -- read .u1 file, add proc names and refs to ptable

procedure scan1(fname)
   local u1, line, i, name, flags, curr, locals
   u1 := open(fname || ".u1") | stop(&progname, ": can't open", fname || ".u1")
   while line := read(u1) do line ? {
      if ="proc " then {
         # new proc: make table entry
         name := tab(0)
         insert(pnames, name)
         ptable[name] := curr := prc(name, 0, set())
         locals := []
         }
      else if ="\tlocal\t" then {
         # new local: remember its name
         i := tab(many(&digits))
         =","
         flags := tab(upto(','))
         =","
         name := tab(0)
         put(locals, lcl(name, flags))
         }
      else if ="\tvar\t" then {
         # ref to "local": note as needed if it's a global
         i := tab(0) + 1
         if locals[i].flags = 0 then
            insert(curr.calls, locals[i].name)
         }
      curr.size +:= 1		# tally number of lines
      }
   close(u1)
   return
end


#  dependencies() -- mark procs called directly or indirectly from main proc

procedure dependencies()
   local need, p

   need := ["main"]
   while name := get(need) do
      if (p := \ptable[name]) & (/p.need := 1) then
         every put(need, !p.calls)
   return
end


#  report(opts) -- write reports as selected by command options

procedure report(opts)
   local name, p, ptrim, ltrim, ltotal

   ltotal := ltrim := ptrim := 0
   every name := !sort(pnames) do {
      p := ptable[name]
      ltotal +:= p.size
      if /p.need then {
         ltrim +:= p.size
         ptrim +:= 1
         }
      if /opts["v"] then
         next
      writes(right(p.size, 6))
      writes(if \p.need then " * " else "   ")
      writes(left(p.name, 16))
      every writes(" ", !sort(p.calls))
      write()
      }
   if /opts["s"] then
      write(&errout, "Trimming ", ptrim, "/", *pnames, " procedures (",
         (100 * ptrim + 5) / *pnames, "%), ", ltrim, "/", ltotal, " lines (",
         (100 * ltrim + 5) / ltotal, "%)")
   return
end


#  filter1(fname) -- filter .u1o file to make new .u1 file
#
#  For each proc body, copy only if marked as needed in ptable.

procedure filter1(fname)
   local old, new, line

   old := open(fname||".u1o") | stop(&progname, ": can't open", fname||".u1o")
   new := open(fname||".u1","w") | stop(&progname,": can't write",fname||".u1")

   while line := read(old) do line ?
      if ="proc " & /ptable[tab(0)].need then		    # check new proc
         until (line ? ="\tend") | not (line := read(old))  # skip to proc end
      else
         write(new, line)
   close(old)
   close(new)
   return
end


#  filter2(fname) -- filter .u2o file to make new .u2 file
#
#  Copy header verbatim; read list of globals, remove procs trimmed from .u1,
#  and write new (renumbered) global list.

procedure filter2(fname)
   local old, new, line, n, glist, flags, name, args, p

   old := open(fname||".u2o") | stop(&progname, ": can't open ", fname||".u2o")
   new := open(fname||".u2","w") | stop(&progname,": can't write ",fname||".u2")

   write(new, read(old)) | stop(&progname, ": empty ", fname || ".u2o")
   while (line := read(old)) & not (line ? ="global") do
      write(new, line)

   glist := []
   while line := read(old) do line ? {
      ="\t"
      tab(many(&digits))
      p := &pos
      =","
      flags := tab(upto(','))
      =","
      name := tab(upto(','))
      if flags = 5 & /(\ptable[name]).need then
         next
      tab(p)
      put(glist, tab(0))
      }
   write(new, "global\t", *glist)
   every write(new, "\t", 0 to *glist - 1, get(glist))

   close(old)
   close(new)
   return
end
