ci - command interpreter

#include <stdio.h>
#include <ci.h>

ci (prompt,file,depth,list,helppath,cmdfpath);
char *prompt,*helppath,*cmdfpath;
FILE *file;
int depth;
CIENTRY *list;

Ci allows the user to type instructions which execute commands and
which display and assign values to variables. The commands which
are executed correspond to procedures supplied by the user’s
program; the variables correspond to variables in the program. The
most basic facilities of ci are easily learned and used, and it
provides other, more sophisticated, features which make it suitable
for complex command-driven programs.

Ci has a basic loop which consists of printing the string prompt,
reading a line from its current input file, and executing the
specified instructions. The format and meaning of these
instructions is as follows:

command [ arg ... ]
Execute the procedure which corresponds to the named command.
The list of arguments will be passed to this procedure. See
details below.

variable [ param ... ]
Display the value of the named variable. Some variables may
require parameters; see details below.

variable = [ param ... ] value
Assign the given value to the named variable. The equals sign
(=) may appear anywhere in the line.

List the names of commands which begin with "command".

List the names and values of variables which begin with

Recursively call ci, with the new procedure reading its
commands from "commandfile".

help topic
Print help message related to topic; or, if topic matches more
than one help message name, list the names of matching topics.
If no topic is specified, a standard help message is displayed
which describes how to use ci. If the topic is "*", a list of
help topics is produced.

instruction >filename
Execute the given instruction,placing the output in the
specified file rather than on the terminal. If more than one
instruction appears on the input line, >filename must appear
at the end of the input line and applies to the entire line.

Fork a shell; if a command is specified, execute it and have
the shell exit. The shell invoked is that specified by the
environment variable SHELL. If SHELL is not defined the
standard shell is used.

Ignore this instruction.

^instruction ("^" character followed by instruction)
Exit from this instance of ci, then execute the given
instruction. Used when one ci is running as a subroutine of
another -- the child exits, and the parent executes the given

@instruction ("@" character followed by instruction)
Exit from this instance of ci, execute the given instruction,
then return to this instance of ci. Used when one ci is
running as a subroutine of another -- the child exits, the
parent executes the given instruction, and the child is re-

^D (control-D)
Exit from ci.

Several instructions may appear on the same line, separated by
semicolons. In addition, ci allows abbreviation of command names,
variable names, and help topics to any unique prefix.

Prompt is the string which is printed to tell the user to type a
command line to ci. During execution of command files, the prompt
will be echoed along with each line read from the file; both will
be indented to indicate that this command was read from a command

File is the file to be used for input. Normally, the standard
input (stdin) is used; if you specify 0, ci will assume you are
using the standard input.

Depth is used for indenting the prompt; this should usually be 0
when you execute ci, but, when it calls itself recursively for
command files, this will be non-zero.

List is an array of the entry descriptors for commands and
variables; you declare this to be of type CIENTRY (see below), and
use the macros described below to describe the entries.

Helppath is a list of directories containing help files, separated
by colons. If you specify a single directory, that’s fine; ci will
assume all help files lie in that directory. When the user asks
for help on some topic, ci looks for a file in one of the help
directories which has a matching name. The contents of the file
will simply be typed out to the user. If you specify 0 for
helppath, ci will assume that no help files exist.

Cmdfpath is a list of directories containing command files,
separated by colons. This is useful for libraries of prepared
command files. The current directory should be included in the
list; this is best done by indicating a null directory name (i.e.
a colon as the first character of the path, or two consecutive
colons within the path). If you specify 0 (which you will probably
do most of the time), ci will assume that all command file names
are evaluated with respect to the current directory only. Absolute
pathnames, of course, are always valid.

The parameter list is a list of objects of type CIENTRY. These
objects, defined by a set of macros, consist of a name which is a
character string, a pointer to a value, and a type. You declare
the list in this manner:
CIENTRY list[] = {

The macros which define entries are described below.

A command is a procedure provided by your program, which can be
executed by a user by typing its name and, optionally, a list of
arguments. You specify a command by providing the procedure, which
must take a character string as an argument, and by placing an
entry into the CIENTRY list:
mycommand (arglist)
char *arglist;
char *p;/* recommended for parsing */
int arg1,arg2;
p = arglist;
arg1 = intarg (&p,0,...);/* see intarg(3) */
arg2 = intarg (&p,0,...);
CIENTRY list[] =
CICMD ("munch",mycommand),

The user can then type "munch 3 4", and myproc will be executed
with arglist equal to "3 4". The parsing sequence shown above
(using intarg(3)), will assign 3 to arg1 and 4 to arg2. If the
user were to type "munch" with no arguments, he would be prompted
for arg1 and arg2 as described in intarg(3).

Ci knows how to manipulate several kinds of simple variables. To
use these, you declare a variable of the appropriate type, and
place an entry into the CIENTRY list. The types of variables known
to ci correspond to the macros which you place into the list:

CIINT ("name", variable)
This specifies a variable of type int. "Name" is the name of
the variable as it will appear to the user who is executing
the program.

CILONG ("name", variable)
CISHORT ("name", variable)
These specify variables of type long (actually, long int), and
short (actually, short int), respectively.

CIOCT ("name", variable)
CIHEX ("name", variable)
These specify unsigned int variables, whose values will be
shown and interpreted as octal and hexadecimal integers,
respectively. Thus, the value of an octal variable might be
07773; the value of a hexadecimal variable might be 0xabc.

CIDOUBLE ("name", variable)
CIFLOAT ("name", variable)
These indicate floating-point variables of types double and
float, respectively.

CIBOOL ("name", variable)
This indicates a variable of type int, whose value will be
either "yes" (i.e. non-zero), or "no" (i.e. zero).

CISTRING ("name", variable)
This indicates a variable of type "char *" or "char []", which
will be treated as a character string. For ci to work
properly, this should not contain garbage when you call ci.

Here is an example of two variables and how they might be used:

int i;
char s[100];
CIENTRY list[] =
CIINT ("number",i),
CISTRING ("string",s),

Here is an excerpt of a dialogue with the program containing the
above statements (lines typed by the user are indicated by

number 3
s=Hello, mom!
string Hello, mom!
number 3
number 4
number 4
string Hello, mom!

In addition to the simple variables described above, ci can
manipulate "clustered variables", which consist of a variable and
some descriptive information about it. The descriptive information
for a variable of type X (int, float, etc.) is exactly the
information in the parameter list of the routine called "getX"
(getint(3), getfloat(3), etc.). It typically includes some
description of the legal values for the variable, and a prompt
string printed to remind the user what this variable means.

To use a clustered variable involves two steps: you must declare
the variable itself, together with its description; and you must
insert the proper declaration into the CIENTRY list.

To declare a clustered "int" variable, use this macro:
CINT (sname, vname, min, max, "prompt");
This macro appears just like any other declaration, but must be
outside of any procedures (i.e. global). It will create an int
variable called vname, which you may refer to in other parts of
your program; it also declares a structure called sname which
contains the description of vname. The description consists of
three values: min, the minimum allowable value for vname; max, the
maximum allowed value; and prompt, the prompt string for assigning
a value to vname.

The corresponding entry of the CIENTRY list would be:
CICINT ("name", sname)
where sname is the same as sname in the CINT macro.

A clustered variable differs from a simple variable in two ways.
When a user tries to assign a value to a clustered variable, the
new value is checked for legality. If it is legal, it is assigned;
otherwise, a message is printed and the user can type another
value. Also, the user may type "name=", omitting the value, and
will be prompted for the value to be assigned.

Here are the clustered types known to ci:

CINT (sname, vname, min, max, "prompt");
CICINT ("name", sname)
Declares a clustered int variable. The legal range of values
is [min..max]. The variable will be called vname. As
indicated above, CINT is a declaration, and CICINT is the
corresponding entry in the CIENTRY list.

CLONG (sname, vname, min, max, "prompt");
CICLONG ("name", sname)
CSHORT (sname, vname, min, max, "prompt");
CICSHORT ("name", sname)
These define long and short clustered variables, respectively.
CLONG and CSHORT are the declarations; CICLONG and CICSHORT
are the entries for the CIENTRY list.

COCT (sname, vname, min, max, "prompt");
CICOCT ("name", sname)
CHEX (sname, vname, min, max, "prompt");
CICHEX ("name", sname)
These define unsigned int clustered variables whose values are
interpreted as octal or hexadecimal numbers, respectively.
COCT and CHEX are declarations; CICOCT and CICHEX are

CDOUBLE (sname, vname, min, max, "prompt");
CICDOUBLE ("name", sname)
CFLOAT (sname, vname, min, max, "prompt");
CICFLOAT ("name", sname)
These define floating-point variables (double and float,
respectively). CDOUBLE and CFLOAT are declarations; CICDOUBLE

CBOOL (sname, vname, "prompt");
CICBOOL ("name", sname)
Defines an int variable whose value is interpreted as "yes"
(non-zero) or "no" (zero).

CCHR (sname, vname, "legals", "prompt");
CICCHR ("name", sname)
Defines an int variable whose value corresponds to a single
character within the string legals. The value will be printed
as the character indexed by the current value of the variable
(i.e. legals[vname]), and, when assigning a value to it, the
user types a character. The index of that character within
legals will then be assigned to vname.

CSTRING (sname, vname, length, "prompt");
CICSTRING ("name", sname)
These define a variable which is a character array of length
length. It will be treated as a character string.

CSTAB (sname, vname, table, "prompt");
CICSTAB ("name", sname)
CSEARCH (sname, vname, table, "prompt");
CICSEARCH ("name", sname)
These define a variable of type int, which corresponds to one
of the strings in the string array table. The table is
declared as for getstab(3) or getsearch(3), respectively, and
the corresponding routine is actually used for assigning a
value to the variable. The value is displayed as the string
it indexes (e.g. table[vname]), and, to assign a value, the
user types a string which matches an entry of the table.

Here is an example using two clustered variables:

CINT (si, i, 1, 10, "What’s your favorite number?");
CSTRING (sname, name, 100, "What’s your name?");
CIENTRY list[] =
CICINT ("favorite",si),
CICSTRING ("name",sname),

This might be part of a dialogue with the program containing the
above declarations (lines typed by the user are indicated in

favorite 7
name=Humpty Dumpty
name Humpty Dumpty
32 out of range. What’s your favorite number? (1 to 10) [7] 4
favorite 4
What’s your favorite number? (1 to 10) [4] 8
favorite 8
What’s your name? [Humpty Dumpty] Minnie Mouse
name Minnie Mouse
favorite 8
name Minnie Mouse

Most users, for most programs, will find clustered variables to be
preferable to simple variables.

Ci allows you to specify any type of variable you want -- an
ordered pair, a character font, a buffer of a color TV display, a
strange plotter, a robot arm, a file, the color of the pajama tops
worn by three hippopotami in the CS lounge, absolutely anything at

There is, however, a catch. You have to write the procedure that
manipulates the variable.

This type of variable is called a procedure variable. It consists
of a procedure which you must provide, and an entry on the CIENTRY
list which looks like this:
CIPROC ("name", procname)
where procname is the name of your procedure.

Your procedure will be called with two parameters:
proc (mode,arglist)
CIMODE mode;
char *arglist;
The first parameter, mode, indicates what ci is trying to do; the
second, arglist is the list of parameters and values typed by the

The mode parameter may have one of three values:

ci is trying to assign a value to the variable; i.e. the user
typed "name=" or "name=value" or something like that.

ci is trying to display the value of the variable; i.e. the
user typed "name".

ci is trying to do a one-line printed display of the variable
in the format "name<TAB><TAB>value". This is normally
performed when the user types "*=", and you should do this
following a CISET.

Typically, the procedure will use a switch statement to deal with
the three cases. If the value can be displayed by printing it on
one line, the CISET and CIPEEK cases may be the same. This is
true, for example, for an ordered pair of integers; it is not true,
say, for a variable which represents a color picture (to display
this may involve writing it onto a color TV monitor).

Here is an example of a procedure variable which represents an
ordered pair:

int x,y;
xy (mode,arg)
CIMODE mode;
char *arg;
char *p; /* for parsing */
switch (mode) {
case CISET:
p = arg;
x = intarg (&p,0,"X coordinate?",-100,100,x);
y = intarg (&p,0,"Y coordinate?",-100,100,y);
/* now, fall through to display the value */
case CISHOW:
case CIPEEK:
printf ("pointx %dy %d0,x,y);
CIENTRY list[] =
CIPROC ("point",xy),

Here is an example of dialogue with the program containing the
above code (lines typed by the user are indicated by italics):

point=3 5
point x 3 y 5
X coordinate? (-100 to 100) [3] 72
Y coordinate? (-100 to 100) [5] 39
point x 72 y 39
p= 287
287 out of range. X coordinate? (-100 to 100) [72] 28
Y coordinate? (-100 to 100) [39] 29
point x 28 y 29
point x 28 y 29

Note that some kinds of variables may require parameters just to be
displayed; you will receive a (possibly null) argument list every
time your procedure variable is called, and may parse arguments for
all three activities specified by mode.

On occasion, you may want to have several procedure variables which
require the exact same code for their processing. For example, you
may have sixteen different robot arms that you want the user to
treat as variables; or have several windows on the color TV screen
that you want to treat as variables. In such cases, it would be a
shame to have to create several procedure variables, each with
exactly the same code. To eliminate this duplication, ci provides
a facility called the class.

A class is a collection of procedure variables which share the same
code. Each variable, however, is distinguished by its own data.
The entries on the CIENTRY list look like this:
CICLASS ("name1",var1,classname),
CICLASS ("name2",var2,classname),
and so on, one entry for each variable. Var1 and var2 are the
names of the data areas for the variables; they might be declared
like this:
typedef struct { int field1; ... } DATAAREA;
DATAAREA var1, var2;
Classname is the name of the procedure which is used by these
variables for displaying and assigning a value.

The procedure will be called with four parameters. Continuing the
above example, the procedure might begin like this:
classname (mode,arglist,varptr,varname)
CIMODE mode;
char *arglist, *varptr, *varname;
. . .
p = (DATAAREA *) varptr;
. . . p->field1 . . .
In this example, note that the first two parameters are just the
same as the first two parameters for a procedure variable. They
have exactly the same meaning. The third parameter is a pointer to
the data area for the variable being displayed or assigned to.
This value must be of type "char *" for C’s type-checking to work
properly, so you will want to coerce it by a type-cast to be a
pointer to the proper type. Note also that var1 and var2 are
DATAAREAs, not (DATAAREA *)s. In general, whatever type of object
you declare in the CICLASS macro, the parameter passed to the
procedure will be a pointer to that type of object. The fourth
(last) parameter passed to the procedure will be the name of the
variable being processed, in a character string.

Here is an example of two ordered pairs represented by two class

typedef struct {int x,y;} ORDPAIR;
ORDPAIR startp,endp;
ordproc (mode,arg,cdata,name)
CIMODE mode;
char *arg, *cdata, *name;
char *p;
ORDPAIR *data;
data = (ORDPAIR *) cdata;
switch (mode) {
case CISET:
p = arg;
data->x = intarg (&p,0,"X coordinate?",-100,100,data->x);
data->y = intarg (&p,0,"Y coordinate?",-100,100,data->y);
case CISHOW:
case CIPEEK:
printf ("%sx %dy %d0,name,data->x,data->y);
CIENTRY list[] =
CICLASS ("start",startp,ordproc),
CICLASS ("end",endp,ordproc),

Here is an example of dialogue with the program containing the
above code (lines typed by the user are indicated by italics):

start = 3 5
startx 3y 5
end = 6 10
endx 6y 10
start =
X coordinate? (-100 to 100) [3] 72
Y coordinate? (-100 to 100) [5] 39
startx 72y 39

If you use del(3) to trap interrupts, you will receive a bonus from
ci. If you hit DEL during the execution of a command, that command
may trap it (by DELRETURN, etc.); if the command ignores it, ci
will deal with it when the command is finished executing.

If the interrupt occurred while ci was reading from the standard
input, it will just print "Interrupt ignored".

If, however, the interrupt occurred during a command file, ci will
INTERRUPT: Abort or breakpoint? [abort]
and wait for you to type something. If you type "a", or "abort",
or just a carriage return, ci will abort the command file (i.e.
pretend it just encountered the end of the file). If you type "b",
or "breakpoint", or something like that, then ci will recursively
call itself, with the new ci taking input from the standard input
(e.g. terminal). The prompt will be "Breakpoint for prompt",
where prompt is the prompt for the interrupted command file. When
you exit from the new ci, the command file will be resumed as if
nothing had happened.

Ci uses six external variables (declared in the file <ci.h>) which
you may also use in your program.

int cidepth;
This variable is the current depth of nesting of invocations
of ci. It is automatically updated by ci to have the proper

FILE *ciinput;
This variable is the current input file for ci. You can read
lines from this file within your commands and variables, if
you want to read from the same place that ci is reading from.

char cinext[];
Normally the null string. If you place a ci instruction in
this string, it will be executed before ci reads any new input

char ciprev[];
Normally the null string. When an instance of ci (the
"parent") contains a command which invokes a new ci with a
different entry list (the "child"), the string ciprev must be
used to enable the "@" instruction to function. In the
parent, immediately after the ci() invocation for the child,
place the statement: ’strcpy (ciprev,"childname");’ where
"childname" is the command the user would type to invoke the
child instance of ci.

int ciexit;
If you put a non-zero value into this variable, ci will exit
(i.e. return) when the current command (or procedure
variable) is finished. This allows you to write a command
which causes ci to exit.

int ciquiet;
This word contains several bits which govern the output
produced by ci.

The bits for ciquiet are also declared in the macro file. If a bit
is 0, the output will be produced; if it is 1, the output is

print a message when ci resumes after a shell command (i.e.

automatically display the new value of a variable when that
variable has a new value assigned. This effectively performs
a CIPEEK on a variable after a CISET. The automatic display
is never performed for variable procedures or class variables.
CISETPEEK is only used when the input to ci is coming from the
terminal. For command files, see CICMDFPEEK below.

echo on the terminal each line which is read from a command

echo the prompt (indented) on the terminal before each line
read from a command file.

print an end-of-file message on the terminal after executing a
command file.

display new value of a variable after assigning; used when
input is from command file. See CISETPEEK, above.

indent commands to reflect nesting of ci invocations.

Certain bits in ciquiet control the processing of input rather than
output. If these bits are 0, all special input symbols are
processed; if 1, the corresponding special input character is
treated just like ordinary data with no special meaning to ci:

If 1, semicolons (;) are data characters; if 0, semicolons
separate multiple instructions on a single input line.

If 1, right angle-brackets (>) are data characters; if 0,
">filename" instructs ci to place output into the named file.

If 1, an equals sign (=) is a data character unless it appears
immediately after a variable name; if 0, any equals sign
indicates that the instruction is assigning a value to a

/usr/cs/lib/ default help file

Name of shell to invoke for "!" instruction. If not defined,
"sh" is used.


intarg(3), shortarg(3), longarg(3), octarg(3), hexarg(3),
doublearg(3), floatarg(3), chrarg(3), boolarg(3), strarg(3),
stabarg(3), searcharg(3), del(3)

"Depth" argument is now a no-op, since its function is subsumed by
the "cidepth" global variable. The argument has been retained for
backward compatibility; its value is ignored.