[comp.lang.icon] Holiday TidBit, part 2 of 3

goer@ellis.uchicago.edu (Richard L. Goerwitz) (05/29/91)

---- Cut Here and feed the following to sh ----
#!/bin/sh
# this is yahtz.02 (part 2 of a multipart archive)
# do not concatenate these parts, unpack them in order with /bin/sh
# file iolib.icn continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 2; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping iolib.icn'
else
echo 'x - continuing file iolib.icn'
sed 's/^X//' << 'SHAR_EOF' >> 'iolib.icn' &&
X#  suggesting things like letting drive specifications appear in DOS
X#  TERMCAP environment variables, and for finding several bugs (e.g.
X#  the lack of support for %2 and %3 in cm).  Although he is loathe
X#  to accept this credit, I think he deserves it.
X#
X#########################################################################
X#
X#  Contents:
X#
X#  setname(term)
X#	Use only if you wish to initialize itermlib for a terminal
X#  other than what your current environment specifies.  "Term" is the
X#  name of the termcap entry to use.  Normally this initialization is
X#  done automatically, and need not concern the user.
X#
X#  getval(id)
X#	Works something like tgetnum, tgetflag, and tgetstr.  In the
X#  spirit of Icon, all three have been collapsed into one routine.
X#  Integer valued caps are returned as integers, strings as strings,
X#  and flags as records (if a flag is set, then type(flag) will return
X#  "true").  Absence of a given capability is signalled by procedure
X#  failure.
X#
X#  igoto(cm,destcol,destline) - NB:  default 1 offset (*not* zero)!
X#	Analogous to tgoto.  "Cm" is the cursor movement command for
X#  the current terminal, as obtained via getval("cm").  Igoto()
X#  returns a string which, when output via iputs, will cause the
X#  cursor to move to column "destcol" and line "destline."  Column and
X#  line are always calculated using a *one* offset.  This is far more
X#  Iconish than the normal zero offset used by tgoto.  If you want to
X#  go to the first square on your screen, then include in your program
X#  "iputs(igoto(getval("cm"),1,1))."
X#
X#  iputs(cp,affcnt)
X#	Equivalent to tputs.  "Cp" is a string obtained via getval(),
X#  or, in the case of "cm," via igoto(getval("cm"),x,y).  Affcnt is a
X#  count of affected lines.  It is completely irrelevant for most
X#  modern terminals, and is supplied here merely for the sake of
X#  backward compatibility with itlib, a UNIX-only version of these
X#  routines (one which handles padding on archaic terminals).
X#
X##########################################################################
X#
X#  Notes for MS-DOS users:
X#
X#	There are two basic reasons for using the I/O routines
X#  contained in this package.  First, by using a set of generalized
X#  routines, your code will become much more readable.  Secondly, by
X#  using a high level interface, you can avoid the cardinal
X#  programming error of hard coding things like screen length and
X#  escape codes into your programs.
X#
X#	To use this collection of programs, you must do two things.
X#  First, you must add the line "device=ansi.sys" (or the name of some
X#  other driver, like zansi.sys, nansi.sys, or nnansi.sys [=new
X#  nansi.sys]) to your config.sys file.  Secondly, you must add two
X#  lines to your autoexec.bat file: 1) "set TERM=ansi-mono" and 2)
X#  "set TERMCAP=\location\termcap."  The purpose of setting the TERM
X#  variable is to tell this program what driver you are using.  If you
X#  have a color system, you could use "ansi-color" instead of
X#  "ansi-mono," although for compatibility with a broader range of
X#  users, it would perhaps be better to stick with mono.  The purpose
X#  of setting TERMCAP is to make it possible to determine where the
X#  termcap database file is located.  The termcap file (which should
X#  have been packed with this library as termcap.dos) is a short
X#  database of all the escape sequences used by the various terminal
X#  drivers.  Set TERMCAP so that it reflects the location of this file
X#  (which should be renamed as termcap, for the sake of consistency
X#  across UNIX and MS-DOS spectra).  If desired, you can also try
X#  using termcap2.dos.  Certain games work a lot better using this
X#  alternate file.  To try it out, rename it to termcap, and set
X#  the environment variable TERMCAP to its location.
X#
X#	Although the authors make no pretense of providing here a
X#  complete introduction to the format of the termcap database file,
X#  it will be useful, we believe, to explain a few basic facts about
X#  how to use this program in conjunction with it.  If, say, you want
X#  to clear the screen, add the line,
X#
X#	iputs(getval("cl"))
X#
X#  to your program.  The function iputs() outputs screen control
X#  sequences.  Getval retrieves a specific sequence from the termcap
X#  file.  The string "cl" is the symbol used in the termcap file to
X#  mark the code used to clear the screen.  By executing the
X#  expression "iputs(getval("cl"))," you are 1) looking up the "cl"
X#  (clear) code in the termcap database entry for your terminal, and
X#  the 2) outputting that sequence to the screen.
X#
X#	Some other useful termcap symbols are "ce" (clear to end of
X#  line), "ho" (go to the top left square on the screen), "so" (begin
X#  standout mode), and "se" (end standout mode).  To output a
X#  boldfaced string, str, to the screen, you would write -
X#
X#	iputs(getval("so"))
X#	writes(str)
X#	iputs(getval("se"))
X#
X#  You can also write "writes(getval("so") || str || getval("se")),
X#  but this would make reimplementation for UNIX terminals that
X#  require padding rather difficult.
X#
X#	It is also heartily to be recommended that MS-DOS programmers
X#  try not to assume that everyone will be using a 25-line screen.
X#  Most terminals are 24-line.  Some 43.  Some have variable window
X#  sizes.  If you want to put a status line on, say, the 2nd-to-last
X#  line of the screen, then determine what that line is by executing
X#  "getval("li")."  The termcap database holds not only string-valued
X#  sequences, but numeric ones as well.  The value of "li" tells you
X#  how many lines the terminal has (compare "co," which will tell you
X#  how many columns).  To go to the beginning of the second-to-last
X#  line on the screen, type in:
X#
X#	iputs(igoto(getval("cm"), 1, getval("li")-1))
X#
X#  The "cm" capability is a special capability, and needs to be output
X#  via igoto(cm,x,y), where cm is the sequence telling your computer
X#  to move the cursor to a specified spot, x is the column, and y is
X#  the row.  The expression "getval("li")-1" will return the number of
X#  the second-to-last line on your screen.
X#
X##########################################################################
X#
X#  Requires: UNIX or MS-DOS, co-expressions
X#
X#  See also: itlib.icn, iscreen.icn
X#
X##########################################################################
X
X
Xglobal tc_table, isDOS
Xrecord true()
X
X
Xprocedure check_features()
X
X    initial {
X
X	if find("UNIX",&features) then
X	    isDOS := &null
X	else if find("MS-DOS", &features) then
X	    isDOS := 1
X	else stop("check_features:  OS not (yet?) supported.")
X
X	find("expressi",&features) |
X	    er("check_features","co-expressions not implemented - &$#!",1)
X    }
X
X    return
X
Xend
X
X
X
Xprocedure setname(name)
X
X    # Sets current terminal type to "name" and builds a new termcap
X    # capability database (residing in tc_table).  Fails if unable to
X    # find a termcap entry for terminal type "name."  If you want it
X    # to terminate with an error message under these circumstances,
X    # comment out "| fail" below, and uncomment the er() line.
X
X    #tc_table is global
X    
X    check_features()
X
X    tc_table := table()
X    tc_table := maketc_table(getentry(name)) | fail
X    # er("setname","no termcap entry found for "||name,3)
X    return "successfully reset for terminal " || name
X
Xend
X
X
X
Xprocedure getname()
X
X    # Getname() first checks to be sure we're running under DOS or
X    # UNIX, and, if so, tries to figure out what the current terminal
X    # type is, checking successively the value of the environment
X    # variable TERM, and then (under UNIX) the output of "tset -".
X    # Terminates with an error message if the terminal type cannot be
X    # ascertained.  DOS defaults to "mono."
X
X    local term, tset_output
X
X    check_features()
X
X    if \isDOS then {
X        term := getenv("TERM") | "mono"
X    }
X    else {
X	if not (term := getenv("TERM")) then {
X	    tset_output := open("/bin/tset -","pr") |
X		er("getname","can't find tset command",1)
X	    term := !tset_output
X	    close(tset_output)
X	}
X    }
X
X    return \term |
X	er("getname","can't seem to determine your terminal type",1)
X
Xend
X
X
X
Xprocedure er(func,msg,errnum)
X
X    # short error processing utility
X    write(&errout,func,":  ",msg)
X    exit(errnum)
X
Xend
X
X
X
Xprocedure getentry(name, termcap_string)
X
X    # "Name" designates the current terminal type.  Getentry() scans
X    # the current environment for the variable TERMCAP.  If the
X    # TERMCAP string represents a termcap entry for a terminal of type
X    # "name," then getentry() returns the TERMCAP string.  Otherwise,
X    # getentry() will check to see if TERMCAP is a file name.  If so,
X    # getentry() will scan that file for an entry corresponding to
X    # "name."  If the TERMCAP string does not designate a filename,
X    # getentry() will scan the termcap file for the correct entry.
X    # Whatever the input file, if an entry for terminal "name" is
X    # found, getentry() returns that entry.  Otherwise, getentry()
X    # fails.
X
X    local isFILE, f, getline, line, nm, ent1, ent2
X    static slash, termcap_names
X    initial {
X	if \isDOS then {
X	    slash := "\\"
X	    termcap_names := ["termcap","termcap.dos","termcap2.dos"]
X	}
X	else {
X	    slash := "/"
X	    termcap_names := ["/etc/termcap"]
X	}
X    }
X
X
X    # You can force getentry() to use a specific termcap file by cal-
X    # ling it with a second argument - the name of the termcap file
X    # to use instead of the regular one, or the one specified in the
X    # termcap environment variable.
X    /termcap_string := getenv("TERMCAP")
X
X    if \isDOS then {
X	if \termcap_string then {
X	    if termcap_string ? (
X		 not ((tab(any(&letters)), match(":")) | match(slash)),
X		 pos(1) | tab(find("|")+1), =name)
X	    then return termcap_string
X	    else isFILE := 1
X	}
X    }
X    else {
X	if \termcap_string then {
X	    if termcap_string ? (
X	        not match(slash), pos(1) | tab(find("|")+1), =name)
X	    then return termcap_string
X	    else isFILE := 1
X	}
X    }
X
X    # The logic here probably isn't clear.  The idea is to try to use
X    # the termcap environment variable successively as 1) a termcap en-
X    # try and then 2) as a termcap file.  If neither works, 3) go to
X    # the /etc/termcap file.  The else clause here does 2 and, if ne-
X    # cessary, 3.  The "\termcap_string ? (not match..." expression
X    # handles 1.
X
X    if \isFILE			# if find(slash, \termcap_string)
X    then f := open(termcap_string)
X    /f := open(!termcap_names) |
X	er("getentry","I can't access your termcap file.  Read iolib.icn.",1)
X    
X    getline := create read_file(f)
X    
X    while line := @getline do {
X	if line ? (pos(1) | tab(find("|")+1), =name, any(':|')) then {
X	    entry := ""
X	    while (\line | @getline) ? {
X		if entry ||:= 1(tab(find(":")+1), pos(0))
X		then {
X		    close(f)
X		    # if entry ends in tc= then add in the named tc entry
X		    entry ?:= tab(find("tc=")) ||
X		    # recursively fetch the new termcap entry
X			(move(3), getentry(tab(find(":"))) ?
X			 # remove the name field from the new entry
X			 (tab(find(":")+1), tab(0)))
X		    return entry
X		}
X		else {
X		    \line := &null # must precede the next line
X		    entry ||:= trim(trim(tab(0),'\\'),':')
X		}
X	    }
X	}
X    }
X
X    close(f)
X    er("getentry","can't find and/or process your termcap entry",3)
X 
Xend
X
X
X
Xprocedure read_file(f)
X
X    # Suspends all non #-initial lines in the file f.
X    # Removes leading tabs and spaces from lines before suspending
X    # them.
X
X    local line
X
X    \f | er("read_tcap_file","no valid termcap file found",3)
X    while line := read(f) do {
X	match("#",line) & next
X	line ?:= (tab(many('\t ')) | &null, tab(0))
X	suspend line
X    }
X
X    fail
X
Xend
X
X
X
Xprocedure maketc_table(entry)
X
X    # Maketc_table(s) (where s is a valid termcap entry for some
X    # terminal-type): Returns a table in which the keys are termcap
X    # capability designators, and the values are the entries in
X    # "entry" for those designators.
X
X    local k, v
X
X    /entry & er("maketc_table","no entry given",8)
X    if entry[-1] ~== ":" then entry ||:= ":"
X    
X    /tc_table := table()
X
X    entry ? {
X
X	tab(find(":")+1)	# tab past initial (name) field
X
X	while tab((find(":")+1) \ 1) ? {
X	    &subject == "" & next
X	    if k := 1(move(2), ="=") then {
X		# Get rid of null padding information.  Iolib can't
X		# handle it (unlike itlib.icn).  Leave star in.  It
X		# indicates a real dinosaur terminal, and will later
X		# prompt an abort.
X		str := ="*" | ""; tab(many(&digits))
X		tc_table[k] := Decode(str || tab(find(":")))
X	    }
X	    else if k := 1(move(2), ="#")
X	    then tc_table[k] := integer(tab(find(":")))
X	    else if k := 1(tab(find(":")), pos(-1))
X	    then tc_table[k] := true()
X	    else er("maketc_table", "your termcap file has a bad entry",3)
X	}
X    }
X
X    return tc_table
X
Xend
X
X
X
Xprocedure getval(id)
X
X    /tc_table := maketc_table(getentry(getname())) |
X	er("getval","can't make a table for your terminal",4)
X
X    return \tc_table[id] | fail
X	# er("getval","the current terminal doesn't support "||id,7)
X
Xend
X
X
X
Xprocedure Decode(s)
X
X    # Does things like turn ^ plus a letter into a genuine control
X    # character.
X
X    new_s := ""
X
X    s ? {
X
X	while new_s ||:= tab(upto('\\^')) do {
X	    chr := move(1)
X	    if chr == "\\" then {
X		new_s ||:= {
X		    case chr2 := move(1) of {
X			"\\" : "\\"
X			"^"  : "^"
X			"E"  : "\e"
X			"b"  : "\b"
X			"f"  : "\f"
X			"n"  : "\n"
X			"r"  : "\r"
X			"t"  : "\t"
X			default : {
X			    if any(&digits,chr2) then {
X				char(integer("8r"||chr2||move(2 to 0 by -1))) |
X				    er("Decode","bad termcap entry",3)
X			    }
X			   else chr2
X			}
X		    }
X		}
X	    }
X	    else new_s ||:= char(ord(map(move(1),&lcase,&ucase)) - 64)
X	}
X	new_s ||:= tab(0)
X    }
X
X    return new_s
X
Xend
X
X
X
Xprocedure igoto(cm,col,line)
X
X    local colline, range, increment, padding, str, outstr, chr, x, y
X
X    if col > (tc_table["co"]) | line > (tc_table["li"]) then {
X	colline := string(\col) || "," || string(\line) | string(\col|line)
X	range := "(" || tc_table["co"]-1 || "," || tc_table["li"]-1 || ")"
X	er("igoto",colline || " out of range " || (\range|""),9)
X    } 
X
X    # Use the Iconish 1;1 upper left corner & not the C-ish 0 offsets
X    increment := -1
X    outstr := ""
X    
X    cm ? {
X	while outstr ||:= tab(find("%")) do {
X	    tab(match("%"))
X	    if padding := integer(tab(any('23')))
X	    then chr := (="d" | "d")
X	    else chr := move(1)
X	    if case \chr of {
X		"." :  outstr ||:= char(line + increment)
X		"+" :  outstr ||:= char(line + ord(move(1)) + increment)
X		"d" :  {
X		    str := string(line + increment)
X		    outstr ||:= right(str, \padding, "0") | str
X		}
X	    }
X	    then line :=: col
X	    else {
X		case chr of {
X		    "n" :  line := ixor(line,96) & col := ixor(col,96)
X		    "i" :  increment := 0
X		    "r" :  line :=: col
X		    "%" :  outstr ||:= "%"
X		    "B" :  line := ior(ishift(line / 10, 4), line % 10)
X		    ">" :  {
X			x := move(1); y := move(1)
X			line > ord(x) & line +:= ord(y)
X			&null
X		    }
X		} | er("goto","bad termcap entry",5)
X	    }
X	}
X    return outstr || tab(0)
X    }
X
Xend
X
X
X
Xprocedure iputs(cp, affcnt)
X
X    # Writes cp to the screen.  Use this instead of writes() for
X    # compatibility with itlib (a UNIX-only version which can handle
X    # albeit inelegantly) terminals that need padding.
X
X    static num_chars
X    initial num_chars := &digits ++ '.'
X
X    type(cp) == "string" |
X	er("iputs","you can't iputs() a non-string value!",10)
X
X    cp ? {
X	if tab(many(num_chars)) & ="*" then
X	    stop("iputs:  iolib can't use terminals that require padding.")
X	writes(tab(0))
X    }
X
X    return
X
Xend
SHAR_EOF
echo 'File iolib.icn is complete' &&
true || echo 'restore of iolib.icn failed'
rm -f _shar_wnt_.tmp
fi
# ============= termcap.dos ==============
if test -f 'termcap.dos' -a X"$1" != X"-c"; then
	echo 'x - skipping termcap.dos (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting termcap.dos (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'termcap.dos' &&
Xansi|color|ansi-color|ibm|ibmpc|ANSI.SYS color:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[H\E[2J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:ti=\E[0;44m:te=\E[0m:\
X	:so=\E[1;35;44m:se=\E[0;44m:\
X	:us=\E[1;31;44m:ue=\E[0;44m:\
X	:mb=\E[5m:md=\E[1m:me=\E[0;44m:
Xmono|ansi-mono|ANSI.SYS:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[H\E[2J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
X	:mb=\E[5m:md=\E[1m:me=\E[m:
Xnnansi-mono|NNANSI.SYS:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[2J:cd=\E[J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:so=\E[7m:se=\E[2m:\
X	:us=\E[4m:ue=\E[24m:\
X	:mb=\E[5m:md=\E[1m:mh=\E[2m:mr=\E[7m:me=\E[m:\
X	:al=\E[L:dl=\E[M:ic=\E[@:dc=\E[P:
Xnnansi|nnansi-color|NNANSI.SYS color:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[2J:cd=\E[J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:ti=\E[0;44m:te=\E[0m:\
X	:so=\E[1;35;44m:se=\E[2;37m:\
X	:us=\E[4m:ue=\E[24m:\
X	:mb=\E[5m:md=\E[1m:mh=\E[2m:mr=\E[7m:me=\E[0;44m:\
X	:al=\E[L:dl=\E[M:ic=\E[@:dc=\E[P:
Xnansi-mono|zansi-mono|N/ZANSI.SYS:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[2J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:ti=\E[0m:te=\E[0m:\
X	:so=\E[7;35m:se=\E[0m:\
X	:us=\E[1;31m:ue=\E[0m:\
X	:mb=\E[5m:md=\E[1m:mr=\E[7m:me=\E[m:\
X	:al=\E[L:dl=\E[M:ic=\E[@:dc=\E[P:
Xnansi|zansi|nansi-color|zansi-color|N/ZANSI.SYS color:\
X	:co#80:li#25:bs:pt:bl=^G:le=^H:do=^J:\
X	:cl=\E[2J:ce=\E[K:\
X	:ho=\E[H:cm=\E[%i%d;%dH:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:ti=\E[0;44m:te=\E[0m:\
X	:so=\E[1;35;44m:se=\E[0;44m:\
X	:us=\E[1;31;44m:ue=\E[0;44m:\
X	:mb=\E[5m:md=\E[1m:mr=\E[7m:me=\E[0;44m:\
X	:al=\E[L:dl=\E[M:ic=\E[@:dc=\E[P:
XAX|ANSI X3.64|full ANSI X3.64 (1977) standard:\
X	:co#80:li#25:bs:pt:am:mi:bl=^G:le=^H:\
X	:cl=\E[2J:ce=\E[K:cd=\E[J:\
X	:ho=\E[H:cm=\E[%i%d;%dH:cs=\E[%i%d;%dr:\
X	:up=\E[A:do=\E[B:le=\E[C:ri=\E[D:nd=\E[C:\
X	:UP=\E[%dA:DO=\E[%dB:LE=\E[%dC:RI=\E[%dD:\
X	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
X	:mb=\E[5m:md=\E[1m:mr=\E[7m:me=\E[m:as=^N:ae=^O:\
X	:ku=\E[A:kd=\E[B:kl=\E[C:kr=\E[D:kb=^H:\
X	:kn#4:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\
X	:im=\E[4h:ei=\E[4l:al=\E[L:dl=\E[M:ic=\E[@:dc=\E[P:sf=\ED:sr=\EM:
SHAR_EOF
true || echo 'restore of termcap.dos failed'
rm -f _shar_wnt_.tmp
fi
# ============= Makefile.dist ==============
if test -f 'Makefile.dist' -a X"$1" != X"-c"; then
	echo 'x - skipping Makefile.dist (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting Makefile.dist (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Makefile.dist' &&
X# Don't change this unless you're sure of what you're doing.
SHAR_EOF
true || echo 'restore of Makefile.dist failed'
fi
echo 'End of  part 2'
echo 'File Makefile.dist is continued in part 3'
echo 3 > _shar_seq_.tmp
exit 0
-- 

   -Richard L. Goerwitz              goer%sophist@uchicago.bitnet
   goer@sophist.uchicago.edu         rutgers!oddjob!gide!sophist!goer