[comp.lang.icon] I/O library, part 01 of 02

goer@quads.uchicago.edu (Richard L. Goerwitz) (09/04/90)

A few months ago, I posted a set of generalized I/O routines for Icon
running on Unix routines.  Since then, I've used various underhanded
means of testing them, such as porting Alan Corre's calendar program
to Unix, and sending out a little snake game.

The routines are now in fair shape.  What is more, I've ported them to
MS-DOS, so that people can now write programs for the one environment,
and expect them to run (and look pretty much the same) in the other.
I really hope that, by using these generalized routines, people will
be able to avoid the cardinal sin of hard-coding OS and terminal spe-
cific I/O routines into their source code.

This posting is long, but I've received a number of requests for the
package.  To be sure no one's mailer barf's on it, it's been split
into two parts.  This is part 1.

-Richard

(BTW:  The MS-DOS version is much less well-tested than the Unix ver-
sion, mainly because I don't use DOS much, and when I do it's usually
by way of a DOS emulator running as a task under Xenix.)

---- Cut Here and unpack ----
#!/bin/sh
# This is a shell archive (shar 3.24)
# made 08/31/1990 05:59 UTC by goer@sophist.uchicago.edu
# Source directory /u/richard/Itermlib
#
# existing files WILL be overwritten
# This format requires very little intelligence at unshar time.
# "echo" and "sed" will be needed.
#
# This is part 1 of a multipart archive                                    
# do not concatenate these parts, unpack them in order with /bin/sh        
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  12054 -r--r--r-- itlib.icn
#   4980 -r--r--r-- iscreen.icn
#  15452 -r--r--r-- itlibdos.icn
#   2391 -r--r--r-- termcap.dos
#
if test -r shar3_seq_.tmp; then
	echo "Must unpack archives in sequence!"
	next=`cat shar3_seq_.tmp`; echo "Please unpack part $next next"
	exit 1
fi
# ============= itlib.icn ==============
echo "x - extracting itlib.icn (Text)"
sed 's/^X//' << 'SHAR_EOF' > itlib.icn &&
X########################################################################
X#    
X#	Name:	itlib.icn
X#	
X#	Title:	Icon termlib-type tools
X#	
X#	Author:	Richard L. Goerwitz
X#
X#	Version: 1.12
X#
X########################################################################
X#
X#  Copyright (c) 1990, Richard L. Goerwitz, III
X#
X#  This software is intended for free and unrestricted distribution.
X#  I place only two conditions on its use:  1) That you clearly mark
X#  any additions or changes you make to the source code, and 2) that
X#  you do not delete this message therefrom.  In order to protect
X#  myself from spurious litigation, it must also be stated here that,
X#  because this is free software, I, Richard Goerwitz, make no claim
X#  about the applicability or fitness of this software for any
X#  purpose, and expressly disclaim any responsibility for any damages
X#  that might be incurred in conjunction with its use.
X#
X########################################################################
X#
X#  The following library represents a series of rough functional
X#  equivalents to the standard Unix low-level termcap routines.  They
X#  are not meant as exact termlib clones.  Nor are they enhanced to
X#  take care of magic cookie terminals, terminals that use \D in their
X#  termcap entries, or, in short, anything I felt would not affect my
X#  normal, day-to-day work with ANSI and vt100 terminals.
X#
X#  Requires:  A unix platform & co-expressions.  I have diffs for a
X#  MS-DOS version, if anyone wants them.
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 only relevant for terminals which
X#  specify proportional (starred) delays in their termcap entries.
X#
X#  Bugs:  I have not tested these routines on terminals that require
X#  padding.
X#
X##########################################################################
X#
X#  Requires: UNIX, co-expressions
X#
X#  See also: iscreen.icn (a set of companion utilities)
X#
X##########################################################################
X
X
Xglobal tc_table, tty_speed
Xrecord true()
X
X
Xprocedure check_features()
X
X    local in_params, line
X    # global tty_speed
X
X    initial {
X	find("unix",map(&features)) |
X	    er("check_features","unix system required",1)
X	find("o-expres",&features) |
X	    er("check_features","co-expressions not implemented - &$#!",1)
X	system("/bin/stty tabs") |
X	    er("check_features","can't set tabs option",1)
X    }
X
X    # clumsy, clumsy, clumsy, and probably won't work on all systems
X    in_params := open("/bin/stty 2>&1","pr") | fail
X    every line := !in_params do {
X	line ? {
X	    tab(find("speed")+5) &
X		tab(many(' ')) &
X		tty_speed := integer(tab(many(&digits)))
X	}
X    }
X    close(in_params)
X    return "term characteristics reset; features check out"
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 := 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 Unix, and,
X    # if so, tries to figure out what the current terminal type is,
X    # checking successively the value of the environment variable
X    # TERM, and then the output of "tset -".  Terminates with an error
X    # message if the terminal type cannot be ascertained.
X
X    local term, tset_output
X
X    check_features()
X
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    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)
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 /etc/termcap 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 termcap_string, f, getline, line, nm, ent1, ent2
X
X    termcap_string := getenv("TERMCAP")
X
X    if \termcap_string ? (not match("/"), pos(0) | tab(find("|")+1), =name)
X    then return termcap_string
X    else {
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 find("/",\termcap_string)
X	then f := open(termcap_string)
X	/f := open("/etc/termcap") |
X	    er("getentry","I can't access your /etc/termcap file",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 ||:= tab(-2)
X		    }
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
X	    &subject == "" & next
X	    if k := 1(move(2), ="=")
X	    then /tc_table[k] := decode(tab(find(":")))
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	    &null  # in case insertion fails due to duplicate entry
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	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, 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	    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, integer(tab(any('23'))), "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    local baud_rates, char_rates, i, delay, PC
X    static num_chars, char_times
X    # global tty_speed
X
X    initial {
X	num_chars := &digits ++ '.'
X	char_times := table()
X	baud_rates := [0,300,600,1200,1800,2400,4800,9600,19200]
X	char_rates := [0,333,166,83,55,41,20,10,5]
X	every i := 1 to *baud_rates do {
X	    char_times[baud_rates[i]] := char_rates[i]
X	}
X    }
X
X    type(cp) == "string" |
X	er("iputs","you can't iputs() a non-string value!",10)
X
X    cp ? {
X	delay := tab(many(num_chars))
X	if ="*" then {
X	    delay *:= \affcnt |
X		er("iputs","affected line count missing",6)
X	}
X	writes(tab(0))
X    }
X
X    if (\delay, tty_speed ~= 0) then {
X	PC := tc_table["pc"] | "\000"
X	char_time := char_times[tty_speed] | (return "speed error")
X	delay := (delay * char_time) + (char_time / 2)
X	every 1 to delay by 10
X	do writes(PC)
X    }
X
X    return
X
Xend
SHAR_EOF
# ============= iscreen.icn ==============
echo "x - extracting iscreen.icn (Text)"
sed 's/^X//' << 'SHAR_EOF' > iscreen.icn &&
X############################################################################
X#
X#	Name:	 iscreen.icn
X#
X#	Title:	 Icon screen functions
X#
X#	Author:	 Richard L. Goerwitz
X#
X#	Version: 1.11
X#
X############################################################################
X#
X#  Copyright (c) 1990, Richard L. Goerwitz, III
X#
X#  This software is intended for free and unrestricted distribution.
X#  I place only two conditions on its use:  1) That you clearly mark
X#  any additions or changes you make to the source code, and 2) that
X#  you do not delete this message from it.  In order to protect
X#  myself from spurious litigation, it must also be stated here that,
X#  because this is free software, I, Richard Goerwitz, make no claim
X#  about the applicability or fitness of this software for any
X#  purpose, and disclaim any responsibility for any damages that
X#  might be incurred in conjunction with its use.
X#
X############################################################################
X#  
X#      This file contains some rudimentary screen functions for use with
X#  itlib.icn (termlib-like routines for Icon).
X#
X#      clear()              - clears the screen (tries several methods)
X#      emphasize()          - initiates emphasized mode
X#      normal(mode)         - resets to normal mode; if mode is null,
X#        or "b," normal() assumes you were in emphasize mode,
X#        otherwise you are assumed to have been in underline mode
X#      message(s)           - displays message s on 2nd-to-last line
X#      underline()          - initiates underline mode
X#      status_line(s,s2,p)  - draws status line s on the 3rd-to-last
X#        screen line; if s is too short for the terminal, s2 is used;
X#        if p is nonnull then it either centers, left-, or right-justi-
X#        fies, depending on the value, "c," "l," or "r."
X#
X############################################################################
X#
X#  Requires: UNIX
X#
X#  Links: itlib.icn (or your OS-specific port of itlib)
X#
X#  See also: boldface.icn
X#
X############################################################################
X
X
Xprocedure clear()
X
X    # Clears the screen.  Tries several methods.
X
X    if not iputs(getval("cl"))
X    then iputs(igoto(getval("cm"),1,1))
X    if not iputs(getval("cd"))
X    then {
X	every i := 1 to getval("li") do {
X	    iputs(igoto(getval("cm"),1,i))
X	    iputs(getval("ce"))
X	}
X	iputs(igoto(getval("cm"),1,1))
X    }
X    return
X
Xend
X
X
X
Xprocedure emphasize()
X    
X    static bold_str, cookie_str
X    initial {
X	if bold_str := getval("so")
X	then cookie_str := repl(getval("bc") | "\b", getval("sg"))
X	else {
X	    if bold_str := getval("us")
X	    then cookie_str := repl(getval("bc") | "\b", getval("ug"))
X	}
X    }	    
X    
X    iputs(\bold_str)
X    iputs(\cookie_str)
X    return
X
Xend
X
X
X
Xprocedure underline()
X    
X    static underline_str, cookie_str
X    initial {
X	if underline_str := getval("us")
X	then cookie_str := repl(getval("bc") | "\b", getval("sg"))
X    }	    
X    
X    iputs(\underline_str)
X    iputs(\cookie_str)
X    return
X
Xend
X
X
X
Xprocedure normal(mode)
X
X    static UN_bold_str, bold_cookie_str,
X	UN_underline_str, underline_cookie_str
X    initial {
X
X	if UN_bold_str := getval("se") then
X	    bold_cookie_str := repl(getval("bc") | "\b", getval("sg"))
X	else {
X	    UN_bold_str := getval("ue")
X	    bold_cookie_str := repl(getval("bc")|"\b", getval("ug"))
X	}
X	if UN_underline_str := getval("ue") then
X	    underline_cookie_str := repl(getval("bc")|"\b", getval("ug"))
X    }	    
X    
X    if /mode | (mode == "b") then {
X	iputs(\UN_bold_str)
X	iputs(\bold_cookie_str)
X	return
X    }
X
X    iputs(\UN_underline_str)
X    iputs(\underline_cookie_str)
X    return
X
Xend
X
X
X
Xprocedure status_line(s,s2,p)
X
X    # Writes a status line on the terminal's third-to-last line
X    # The only necessary argument is s.  S2 (optional) is used
X    # for extra narrow screens.  In other words, by specifying
X    # s2 you give status_line an alternate, shorter status string
X    # to display, in case the terminal isn't wide enough to sup-
X    # port s.  If p is nonnull, then the status line is either
X    # centered (if equal to "c"), left justified ("l"), or right
X    # justified ("r").
X
X    local width
X
X    /s := ""
X    width := getval("co")
X    if *s > width then {
X	(*s2 < width, s := \s2) |
X	    er("status_line","Your terminal is too narrow.",4)
X    }
X    case \p of {
X	"c"    : s := center(s,width-1)
X	"l"    : s := left(s,width-1)
X	"r"    : s := right(s,width-1)
X	default: stop("status_line:  Unknown option "||string(p),4)
X    }
X
X    iputs(igoto(getval("cm"), 1, getval("li")-2))
X    emphasize(); writes(s); iputs(getval("ce"))
X    normal()
X    return
X
Xend
X
X
X
Xprocedure message(s)
X
X    # Display prompt s on the second-to-last line of the screen.
X    # I hate to use the last line, due to all the problems with
X    # automatic scrolling.
X
X    /s := ""
X    normal()
X    iputs(igoto(getval("cm"), 1, getval("li")-1))
X    writes(s[1:getval("co")] | s)
X    iputs(getval("ce"))
X    return
X
Xend
SHAR_EOF
# ============= itlibdos.icn ==============
echo "x - extracting itlibdos.icn (Text)"
sed 's/^X//' << 'SHAR_EOF' > itlibdos.icn &&
X########################################################################
X#    
X#	Name:	itlibdos.icn
X#	
X#	Title:	Icon termlib-type tools (MS-DOS version)
X#	
X#	Author:	Richard L. Goerwitz
X#
X#	Version: 1.3
X#
X########################################################################
X#
X#  Copyright (c) 1990, Richard L. Goerwitz, III
X#
X#  This software is intended for free and unrestricted distribution.
X#  I place only two conditions on its use:  1) That you clearly mark
X#  any additions or changes you make to the source code, and 2) that
X#  you do not delete this message therefrom.  In order to protect
X#  myself from spurious litigation, it must also be stated here that,
X#  because this is free software, I, Richard Goerwitz, make no claim
X#  about the applicability or fitness of this software for any
X#  purpose, and expressly disclaim any responsibility for any damages
X#  that might be incurred in conjunction with its use.
X#
X########################################################################
X#
X#  The following library represents a series of rough functional
X#  equivalents to the standard Unix low-level termcap routines.  They
X#  are not meant as exact termlib clones.  Nor are they enhanced to
X#  take care of magic cookie terminals, terminals that use \D in their
X#  termcap entries, or, in short, anything I felt would not affect my
X#  normal, day-to-day work with ANSI and vt100 terminals.
X#
X#  Requires:  An MS-DOS platform & co-expressions.  The MS-DOS version
X#  is a port of the Unix version.  Software you write for this library
X#  can be made to run under Unix simply by substituting the Unix ver-
X#  sion of this library.  See below for additional notes on how to use
X#  this MS-DOS port.
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 only relevant for terminals which
X#  specify proportional (starred) delays in their termcap entries.
X#
X#  Notes on the MS-DOS version:
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#	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, use "ansi-color" instead of "ansi-mono," and
X#  if you are using nansi or zansi instead of vanilla ansi, use one of
X#  these names instead of the "ansi" (e.g. "zansi-mono").  The purpose
X#  of setting TERMCAP is to make it possible to determine where the
X#  termcap file is located.  The termcap file (which should have been
X#  packed with this library as termcap.dos) is a short database of all
X#  the escape sequences used by the various terminal drivers.  Set
X#  TERMCAP so that it reflects the location of this file (which should
X#  be renamed as termcap, for the sake of consistency with the Unix
X#  version).  Naturally, you must change "\location\" above to reflect
X#  the correct path on your system.
X#	Although I make no pretense here of providing here a complete
X#  introduction to the format of the termcap database file, it will be
X#  useful, I think, to explain a few basic facts about how to use this
X#  program in conjunction with it.  If, say, you want to clear the
X#  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#	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 could write "iputs(getval("so") || str || getval("se")), but
X#  this would only work for DOS.  Some Unix terminals require padding,
X#  and iputs() handles them specially.  Normally you should not worry
X#  about Unix quirks under DOS.  It is in general wise, though, to
X#  separate out screen control sequences, and output them via iputs().
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#  Some 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: MS-DOS, coexpressions
X#
X#  See also: iscreen.icn (a set of companion utilities) 
X#
X##########################################################################
X
X
Xglobal tc_table
Xrecord true()
X
X
Xprocedure check_features()
X
X    local in_params, line
X    # global tty_speed
X
X    initial {
X	find("ms-dos",map(&features)) |
X	    er("check_features","unix system required",1)
X	find("o-expres",&features) |
X	    er("check_features","co-expressions not implemented - &$#!",1)
X    }
X
X    return "term characteristics reset; features check out"
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 := 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, and,
X    # if so, tries to figure out what the current terminal type is,
X    # checking the value of the environment variable TERM, and if this
X    # is unsuccessful, defaulting to "mono."
X
X    local term, tset_output
X
X    check_features()
X
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    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)
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 look through ./termcap 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 termcap_string, f, getline, line, nm, ent1, ent2
X
X    termcap_string := getenv("TERMCAP")
X
X    if \termcap_string ? (not match("\\"), pos(0) | tab(find("|")+1), =name)
X    then return termcap_string
X    else {
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 ./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 find("\\",\termcap_string)
X	then f := open(termcap_string)
X	/f := open("termcap") |
X	    er("getentry","I can't access your termcap file",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 ||:= tab(-2)
X		    }
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
SHAR_EOF
echo "End of  part 1"
echo "File itlibdos.icn is continued in part 2"
echo "2" > shar3_seq_.tmp
exit 0