home *** CD-ROM | disk | FTP | other *** search
- ########################################################################
- #
- # File: iolib.icn
- #
- # Subject: Procedures for termlib-type tools for MS-DOS and UNIX
- #
- # Author: Richard L. Goerwitz (with help from Norman Azadian)
- #
- # Date: January 17, 1992
- #
- ########################################################################
- #
- # Version: 1.13
- #
- ###########################################################################
- #
- # The following library represents a series of rough functional
- # equivalents to the standard UNIX low-level termcap routines. It is
- # not meant as an exact termlib clone. Nor is it enhanced to take
- # care of magic cookie terminals, terminals that use \D in their
- # termcap entries, or archaic terminals that require padding. This
- # library is geared mainly for use with ANSI and VT-100 devices.
- # Note that this file may, in most instances, be used in place of the
- # older UNIX-only itlib.icn file. It essentially replaces the DOS-
- # only itlibdos routines. For DOS users not familiar with the whole
- # notion of generalized screen I/O, I've included extra documentation
- # below. Please read it.
- #
- # The sole disadvantage of this over the old itlib routines is that
- # iolib.icn cannot deal with archaic or arcane UNIX terminals and/or
- # odd system file arrangements. Note that because these routines
- # ignore padding, they can (unlike itlib.icn) be run on the NeXT and
- # other systems which fail to implement the -g option of the stty
- # command. Iolib.icn is also simpler and faster than itlib.icn.
- #
- # I want to thank Norman Azadian for suggesting the whole idea of
- # combining itlib.icn and itlibdos.icn into one distribution, for
- # suggesting things like letting drive specifications appear in DOS
- # TERMCAP environment variables, and for finding several bugs (e.g.
- # the lack of support for %2 and %3 in cm). Although he is loathe
- # to accept this credit, I think he deserves it.
- #
- #########################################################################
- #
- # Contents:
- #
- # setname(term)
- # Use only if you wish to initialize itermlib for a terminal
- # other than what your current environment specifies. "Term" is the
- # name of the termcap entry to use. Normally this initialization is
- # done automatically, and need not concern the user.
- #
- # getval(id)
- # Works something like tgetnum, tgetflag, and tgetstr. In the
- # spirit of Icon, all three have been collapsed into one routine.
- # Integer valued caps are returned as integers, strings as strings,
- # and flags as records (if a flag is set, then type(flag) will return
- # "true"). Absence of a given capability is signalled by procedure
- # failure.
- #
- # igoto(cm,destcol,destline) - NB: default 1 offset (*not* zero)!
- # Analogous to tgoto. "Cm" is the cursor movement command for
- # the current terminal, as obtained via getval("cm"). Igoto()
- # returns a string which, when output via iputs, will cause the
- # cursor to move to column "destcol" and line "destline." Column and
- # line are always calculated using a *one* offset. This is far more
- # Iconish than the normal zero offset used by tgoto. If you want to
- # go to the first square on your screen, then include in your program
- # "iputs(igoto(getval("cm"),1,1))."
- #
- # iputs(cp,affcnt)
- # Equivalent to tputs. "Cp" is a string obtained via getval(),
- # or, in the case of "cm," via igoto(getval("cm"),x,y). Affcnt is a
- # count of affected lines. It is completely irrelevant for most
- # modern terminals, and is supplied here merely for the sake of
- # backward compatibility with itlib, a UNIX-only version of these
- # routines (one which handles padding on archaic terminals).
- #
- ##########################################################################
- #
- # Notes for MS-DOS users:
- #
- # There are two basic reasons for using the I/O routines
- # contained in this package. First, by using a set of generalized
- # routines, your code will become much more readable. Secondly, by
- # using a high level interface, you can avoid the cardinal
- # programming error of hard coding things like screen length and
- # escape codes into your programs.
- #
- # To use this collection of programs, you must do two things.
- # First, you must add the line "device=ansi.sys" (or the name of some
- # other driver, like zansi.sys, nansi.sys, or nnansi.sys [=new
- # nansi.sys]) to your config.sys file. Secondly, you must add two
- # lines to your autoexec.bat file: 1) "set TERM=ansi-mono" and 2)
- # "set TERMCAP=\location\termcap." The purpose of setting the TERM
- # variable is to tell this program what driver you are using. If you
- # have a color system, you could use "ansi-color" instead of
- # "ansi-mono," although for compatibility with a broader range of
- # users, it would perhaps be better to stick with mono. The purpose
- # of setting TERMCAP is to make it possible to determine where the
- # termcap database file is located. The termcap file (which should
- # have been packed with this library as termcap.dos) is a short
- # database of all the escape sequences used by the various terminal
- # drivers. Set TERMCAP so that it reflects the location of this file
- # (which should be renamed as termcap, for the sake of consistency
- # across UNIX and MS-DOS spectra). If desired, you can also try
- # using termcap2.dos. Certain games work a lot better using this
- # alternate file. To try it out, rename it to termcap, and set
- # the environment variable TERMCAP to its location.
- #
- # Although the authors make no pretense of providing here a
- # complete introduction to the format of the termcap database file,
- # it will be useful, we believe, to explain a few basic facts about
- # how to use this program in conjunction with it. If, say, you want
- # to clear the screen, add the line,
- #
- # iputs(getval("cl"))
- #
- # to your program. The function iputs() outputs screen control
- # sequences. Getval retrieves a specific sequence from the termcap
- # file. The string "cl" is the symbol used in the termcap file to
- # mark the code used to clear the screen. By executing the
- # expression "iputs(getval("cl"))," you are 1) looking up the "cl"
- # (clear) code in the termcap database entry for your terminal, and
- # the 2) outputting that sequence to the screen.
- #
- # Some other useful termcap symbols are "ce" (clear to end of
- # line), "ho" (go to the top left square on the screen), "so" (begin
- # standout mode), and "se" (end standout mode). To output a
- # boldfaced string, str, to the screen, you would write -
- #
- # iputs(getval("so"))
- # writes(str)
- # iputs(getval("se"))
- #
- # You can also write "writes(getval("so") || str || getval("se")),
- # but this would make reimplementation for UNIX terminals that
- # require padding rather difficult.
- #
- # It is also heartily to be recommended that MS-DOS programmers
- # try not to assume that everyone will be using a 25-line screen.
- # Most terminals are 24-line. Some 43. Some have variable window
- # sizes. If you want to put a status line on, say, the 2nd-to-last
- # line of the screen, then determine what that line is by executing
- # "getval("li")." The termcap database holds not only string-valued
- # sequences, but numeric ones as well. The value of "li" tells you
- # how many lines the terminal has (compare "co," which will tell you
- # how many columns). To go to the beginning of the second-to-last
- # line on the screen, type in:
- #
- # iputs(igoto(getval("cm"), 1, getval("li")-1))
- #
- # The "cm" capability is a special capability, and needs to be output
- # via igoto(cm,x,y), where cm is the sequence telling your computer
- # to move the cursor to a specified spot, x is the column, and y is
- # the row. The expression "getval("li")-1" will return the number of
- # the second-to-last line on your screen.
- #
- ##########################################################################
- #
- # Requires: UNIX or MS-DOS, co-expressions
- #
- # See also: itlib.icn, iscreen.icn
- #
- ##########################################################################
-
-
- global tc_table, isDOS
- record true()
-
-
- procedure check_features()
-
- initial {
-
- if find("UNIX",&features) then
- isDOS := &null
- else if find("MS-DOS", &features) then
- isDOS := 1
- else stop("check_features: OS not (yet?) supported.")
-
- find("expressi",&features) |
- er("check_features","co-expressions not implemented - &$#!",1)
- }
-
- return
-
- end
-
-
-
- procedure setname(name)
-
- # Sets current terminal type to "name" and builds a new termcap
- # capability database (residing in tc_table). Fails if unable to
- # find a termcap entry for terminal type "name." If you want it
- # to terminate with an error message under these circumstances,
- # comment out "| fail" below, and uncomment the er() line.
-
- #tc_table is global
-
- check_features()
-
- tc_table := table()
- tc_table := maketc_table(getentry(name)) | fail
- # er("setname","no termcap entry found for "||name,3)
- return "successfully reset for terminal " || name
-
- end
-
-
-
- procedure getname()
-
- # Getname() first checks to be sure we're running under DOS or
- # UNIX, and, if so, tries to figure out what the current terminal
- # type is, checking successively the value of the environment
- # variable TERM, and then (under UNIX) the output of "tset -".
- # Terminates with an error message if the terminal type cannot be
- # ascertained. DOS defaults to "mono."
-
- local term, tset_output
-
- check_features()
-
- if \isDOS then {
- term := getenv("TERM") | "mono"
- }
- else {
- if not (term := getenv("TERM")) then {
- tset_output := open("/bin/tset -","pr") |
- er("getname","can't find tset command",1)
- term := !tset_output
- close(tset_output)
- }
- }
-
- return \term |
- er("getname","can't seem to determine your terminal type",1)
-
- end
-
-
-
- procedure er(func,msg,errnum)
-
- # short error processing utility
- write(&errout,func,": ",msg)
- exit(errnum)
-
- end
-
-
-
- procedure getentry(name, termcap_string)
-
- # "Name" designates the current terminal type. Getentry() scans
- # the current environment for the variable TERMCAP. If the
- # TERMCAP string represents a termcap entry for a terminal of type
- # "name," then getentry() returns the TERMCAP string. Otherwise,
- # getentry() will check to see if TERMCAP is a file name. If so,
- # getentry() will scan that file for an entry corresponding to
- # "name." If the TERMCAP string does not designate a filename,
- # getentry() will scan the termcap file for the correct entry.
- # Whatever the input file, if an entry for terminal "name" is
- # found, getentry() returns that entry. Otherwise, getentry()
- # fails.
-
- local isFILE, f, getline, line, nm, ent1, ent2, entry
- static slash, termcap_names
- initial {
- if \isDOS then {
- slash := "\\"
- termcap_names := ["termcap","termcap.dos","termcap2.dos"]
- }
- else {
- slash := "/"
- termcap_names := ["/etc/termcap"]
- }
- }
-
-
- # You can force getentry() to use a specific termcap file by cal-
- # ling it with a second argument - the name of the termcap file
- # to use instead of the regular one, or the one specified in the
- # termcap environment variable.
- /termcap_string := getenv("TERMCAP")
-
- if \isDOS then {
- if \termcap_string then {
- if termcap_string ? (
- not ((tab(any(&letters)), match(":")) | match(slash)),
- pos(1) | tab(find("|")+1), =name)
- then {
- # if entry ends in tc= then add in the named tc entry
- termcap_string ?:= tab(find("tc=")) ||
- # Recursively fetch the new termcap entry w/ name trimmed.
- # Note that on the next time through name won't match the
- # termcap environment variable, so getentry() will look for
- # a termcap file.
- (move(3), getentry(tab(find(":"))) ?
- (tab(find(":")+1), tab(0)))
- return termcap_string
- }
- else isFILE := 1
- }
- }
- else {
- if \termcap_string then {
- if termcap_string ? (
- not match(slash), pos(1) | tab(find("|")+1), =name)
- then {
- # if entry ends in tc= then add in the named tc entry
- termcap_string ?:= tab(find("tc=")) ||
- # Recursively fetch the new termcap entry w/ name trimmed.
- (move(3), getentry(tab(find(":")), "/etc/termcap") ?
- (tab(find(":")+1), tab(0)))
- return termcap_string
- }
- else isFILE := 1
- }
- }
-
- # The logic here probably isn't clear. The idea is to try to use
- # the termcap environment variable successively as 1) a termcap en-
- # try and then 2) as a termcap file. If neither works, 3) go to
- # the /etc/termcap file. The else clause here does 2 and, if ne-
- # cessary, 3. The "\termcap_string ? (not match..." expression
- # handles 1.
-
- if \isFILE # if find(slash, \termcap_string)
- then f := open(\termcap_string)
- /f := open(!termcap_names) |
- er("getentry","I can't access your termcap file. Read iolib.icn.",1)
-
- getline := create read_file(f)
-
- while line := @getline do {
- if line ? (pos(1) | tab(find("|")+1), =name, any(':|')) then {
- entry := ""
- while (\line | @getline) ? {
- if entry ||:= 1(tab(find(":")+1), pos(0))
- then {
- close(f)
- # if entry ends in tc= then add in the named tc entry
- entry ?:= tab(find("tc=")) ||
- # recursively fetch the new termcap entry
- (move(3), getentry(tab(find(":"))) ?
- # remove the name field from the new entry
- (tab(find(":")+1), tab(0)))
- return entry
- }
- else {
- \line := &null # must precede the next line
- entry ||:= trim(trim(tab(0),'\\'),':')
- }
- }
- }
- }
-
- close(f)
- er("getentry","can't find and/or process your termcap entry",3)
-
- end
-
-
-
- procedure read_file(f)
-
- # Suspends all non #-initial lines in the file f.
- # Removes leading tabs and spaces from lines before suspending
- # them.
-
- local line
-
- \f | er("read_tcap_file","no valid termcap file found",3)
- while line := read(f) do {
- match("#",line) & next
- line ?:= (tab(many('\t ')) | &null, tab(0))
- suspend line
- }
-
- fail
-
- end
-
-
-
- procedure maketc_table(entry)
-
- # Maketc_table(s) (where s is a valid termcap entry for some
- # terminal-type): Returns a table in which the keys are termcap
- # capability designators, and the values are the entries in
- # "entry" for those designators.
-
- local k, v, str, decoded_value
-
- /entry & er("maketc_table","no entry given",8)
- if entry[-1] ~== ":" then entry ||:= ":"
-
- /tc_table := table()
-
- entry ? {
-
- tab(find(":")+1) # tab past initial (name) field
-
- while tab((find(":")+1) \ 1) ? {
- &subject == "" & next
- if k := 1(move(2), ="=") then {
- # Get rid of null padding information. Iolib can't
- # handle it (unlike itlib.icn). Leave star in. It
- # indicates a real dinosaur terminal, and will later
- # prompt an abort.
- str := ="*" | ""; tab(many(&digits))
- decoded_value := Decode(str || tab(find(":")))
- }
- else if k := 1(move(2), ="#")
- then decoded_value := integer(tab(find(":")))
- else if k := 1(tab(find(":")), pos(-1))
- then decoded_value := true()
- else er("maketc_table", "your termcap file has a bad entry",3)
- /tc_table[k] := decoded_value
- &null
- }
- }
-
- return tc_table
-
- end
-
-
-
- procedure getval(id)
-
- /tc_table := maketc_table(getentry(getname())) |
- er("getval","can't make a table for your terminal",4)
-
- return \tc_table[id] | fail
- # er("getval","the current terminal doesn't support "||id,7)
-
- end
-
-
-
- procedure Decode(s)
-
- # Does things like turn ^ plus a letter into a genuine control
- # character.
-
- local new_s, chr, chr2
-
- new_s := ""
-
- s ? {
-
- while new_s ||:= tab(upto('\\^')) do {
- chr := move(1)
- if chr == "\\" then {
- new_s ||:= {
- case chr2 := move(1) of {
- "\\" : "\\"
- "^" : "^"
- "E" : "\e"
- "b" : "\b"
- "f" : "\f"
- "n" : "\n"
- "r" : "\r"
- "t" : "\t"
- default : {
- if any(&digits,chr2) then {
- char(integer("8r"||chr2||move(2 to 0 by -1))) |
- er("Decode","bad termcap entry",3)
- }
- else chr2
- }
- }
- }
- }
- else new_s ||:= char(ord(map(move(1),&lcase,&ucase)) - 64)
- }
- new_s ||:= tab(0)
- }
-
- return new_s
-
- end
-
-
-
- procedure igoto(cm,col,line)
-
- local colline, range, increment, padding, str, outstr, chr, x, y
-
- if \col > (tc_table["co"]) | \line > (tc_table["li"]) then {
- colline := string(\col) || "," || string(\line) | string(\col|line)
- range := "(" || tc_table["co"]-1 || "," || tc_table["li"]-1 || ")"
- er("igoto",colline || " out of range " || (\range|""),9)
- }
-
- # Use the Iconish 1;1 upper left corner & not the C-ish 0 offsets
- increment := -1
- outstr := ""
-
- cm ? {
- while outstr ||:= tab(find("%")) do {
- tab(match("%"))
- if padding := integer(tab(any('23')))
- then chr := (="d" | "d")
- else chr := move(1)
- if case \chr of {
- "." : outstr ||:= char(line + increment)
- "+" : outstr ||:= char(line + ord(move(1)) + increment)
- "d" : {
- str := string(line + increment)
- outstr ||:= right(str, \padding, "0") | str
- }
- }
- then line :=: col
- else {
- case chr of {
- "n" : line := ixor(line,96) & col := ixor(col,96)
- "i" : increment := 0
- "r" : line :=: col
- "%" : outstr ||:= "%"
- "B" : line := ior(ishift(line / 10, 4), line % 10)
- ">" : {
- x := move(1); y := move(1)
- line > ord(x) & line +:= ord(y)
- &null
- }
- } | er("goto","bad termcap entry",5)
- }
- }
- return outstr || tab(0)
- }
-
- end
-
-
-
- procedure iputs(cp, affcnt)
-
- # Writes cp to the screen. Use this instead of writes() for
- # compatibility with itlib (a UNIX-only version which can handle
- # albeit inelegantly) terminals that need padding.
-
- static num_chars
- initial num_chars := &digits ++ '.'
-
- type(cp) == "string" |
- er("iputs","you can't iputs() a non-string value!",10)
-
- cp ? {
- if tab(many(num_chars)) & ="*" then
- stop("iputs: iolib can't use terminals that require padding.")
- writes(tab(0))
- }
-
- return
-
- end
-