Source file complete.icn
############################################################################
#
#	File:     complete.icn
#
#	Subject:  Procedure to complete partial input string
#
#	Author:   Richard L. Goerwitz
#
#	Date:     August 14, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Version:  1.7
#
############################################################################
#
#	complete(s,st)	completes a s relative to a set or list of strings, st.
#			Put differently, complete() lets you supply a
#			partial string, s, and get back those strings in st
#			that s is either equal to or a	substring of.
#
############################################################################
#
#  Lots of command interfaces allow completion of partial input.
#  Complete() simply represents my personal sentiments about how this
#  might best be done in Icon.  If you strip away the profuse comments
#  below, you end up with only about thirty lines of actual source
#  code.
#
#  I have arranged things so that only that portion of an automaton
#  which is needed to complete a given string is actually created and
#  stored.  Storing automata for later use naturally makes complete()
#  eat up more memory.  The performance gains can make it worth the
#  trouble, though.  If, for some reason, there comes a time when it
#  is advisable to reclaim the space occupied by complete's static
#  structures, you can just call it without arguments.  This
#  "resets" complete() and forces an immediate garbage collection.
#  
# Example code:
#
#      commands := ["run","stop","quit","save","load","continue"]
#      while line := read(&input) do {
#          cmds := list()
#          every put(cmds, complete(line, commands))
#          case *cmds of {
#              0 : input_error(line)
#              1 : do_command(cmds[1])
#              default : display_possible_completions(cmds)
#          }
#          etc...
#
#  More Iconish methods might include displaying successive
#  alternatives each time the user presses the tab key (this would,
#  however, require using the nonportable getch() routine).  Another
#  method might be to use the first string suspended by complete().
#
#  NOTE: This entire shebang could be replaced with a slightly slower
#  and much smaller program suggested to me by Jerry Nowlin and Bob
#  Alexander.
#
#      procedure terscompl(s, st)
#          suspend match(s, p := !st) & p
#      end
#
#  This program will work fine for lists with just a few members, and
#  also for cases where s is fairly large.  It will also use much less
#  memory.
#
############################################################################

procedure complete(s,st)

    local dfstn, c, l, old_chr, chr, newtbl, str, strset
    static t
    initial t := table()

    # No-arg invocation wipes out static structures & causes an
    # immediate garbage collection.
    if /s & /st then {
	t := table()
	collect()		# do it NOW
	fail
    }
    type(st) == ("list"|"set") |
	stop("error (complete):  list or set expected for arg2")

    # Seriously, all that's being done here is that possible states
    # are being represented by sets containing possible completions of
    # s relative to st.  Each time a character is snarfed from s, we
    # check to see what strings in st might represent possible
    # completions, and store these in yet another set.  At some
    # point, we either run into a character in s that makes comple-
    # tion impossible (fail), or we run out of characters in s (in
    # which case we succeed, & suspend each of the possible
    # completions).

    # Store any sets we have to create in a static structure for later
    # re-use.
    /t[st] := table()

    # We'll call the table entry for the current set dfstn.  (It really
    # does enable us to do things deterministically.)
    dfstn := t[st]

    # Snarf one character at a time from s.
    every c := !s do {

	# The state we're in is represented by the set of all possible
	# completions before c was read.  If we haven't yet seen char
	# c in this state, run through the current-possible-completion
	# set, popping off the first character of each possible
	# completion, and then construct a table which uses these
	# initial chars as keys, and makes the completions that are
	# possible for each of these characters into the values for
	# those keys.
	if /dfstn[st] then {

	    # To get strings that start with the same char together,
	    # sort the current string set (st).
	    l := sort(st)
	    newtbl := table()
	    old_chr := ""
	    # Now pop off each member of the sorted string set.  Use
	    # first characters as keys, and then divvy up the full strings
	    # into sets of strings having the same initial letter.
	    every str := !l do {
		str ? { chr := move(1) | next; str := tab(0) }
		if old_chr ~==:= chr then {
		    strset := set([str])
		    insert(newtbl, chr, strset)
		}
		else insert(strset, str)
	    }
	    insert(dfstn, st, newtbl)
	}

	# What we've done essentially is to create a table in which
	# the keys represent labeled arcs out of the current state,
	# and the values represent possible completion sets for those
	# paths.  What we need to do now is store that table in dfstn
	# as the value of the current state-set (i.e. the current
	# range of possible completions).  Once stored, we can then
	# see if there is any arc from the current state (dfstn[st])
	# with the label c (dfstn[st][c]).  If so, its value becomes
	# the new current state (st), and we cycle around again for
	# yet another c.
	st := \dfstn[st][c] | fail
	if *st = 1 & match(s,!st)
	then break
    }

    # Eventually we run out of characters in c.  The current state
    # (i.e. the set of possible completions) can simply be suspended
    # one element at a time, with s prefixed to each element.  If, for
    # instance, st had contained ["hello","help","hear"] at the outset
    # and s was equal to "hel", we would now be suspending "hel" ||
    # !set(["lo","p"]).
    suspend s || !st

end

This page produced by UniDoc on 2021/04/15 @ 23:59:45.