[comp.sources.amiga] v89i019: nro - nroff-like text formatter, Part01/02

page@swan.ulowell.edu (Bob Page) (02/03/89)

Submitted-by: U211344@HNYKUN11.BITNET (Olaf 'Rhialto' Seibert)
Posting-number: Volume 89, Issue 19
Archive-name: applications/nro.1

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	nro.n
#	nrocmd.c
# This archive created: Mon Jan 30 20:00:32 1989
cat << \SHAR_EOF > nro.n
.tm Entering macro file
.so an
.tm Entering main file
.TH NRO 1 "AM*GA Programmer's Manual" "KosmoSoft" "Version 1.5"
.de cmd
!sp $1;!ne 2;!ti -5;!bo "$0
.en
.de fun
.sp $1;.ne 2;.ti -5;.bo "$0
.en
.SH NAME
nro - text formatter
.SH SYNOPSIS
.in +5;.ta +0;.ti -5
.bo "nro@t[-n] [+n] [-pxx] [-v] [-bx] [-rnx] [-rdx] [-rpx]
.bo "[-mmfile] ifile ... [>ofile]
.in -5;.ta
.SH DESCRIPTION
.ul "NRO
is a text processor and formatter based on the design
provided in
.bo ""Software Tools"
by Kernighan and Plauger. The text and commands found in the
.cu "ifile(s)
are processed to generate formatted text. The output may be directed into a
file or to the printer, otherwise, the output will appear at the user
console.

The
.ul "+n
option causes the output to start with page
.ul "n.
The
.ul "-n
option causes the output to stop after page
.ul "n.

The
.ul "-v
option prints the version number to the console. Specifying a higher
numerical argument makes
.bo "NRO
more and more verbose.

The
.ul "-p
option causes the output to be shifted to the right by
.ul "xx
spaces. This has the same effect as the
.bo "@.po
command.

The
.ul "-b
option with argument
.ul "x
controls if backspaces appear in the output text when underlining or
overstriking. This has the same effect as the
.bo "@.bs
command with the same argument.

The
.ul "-r
options allow you to specify how much memory is allocated for macro
definitions and related things. The
.ul "-rn
option lets you specify how many macros you may define while your text is
being formatted. Default is 512. The
.ul "-rd
option lets you specify how much memory is reserved for keeping the
contents of your macro definitions. Default is 20480 characters. The
.ul "-rp
option lets you specify how much characters may be pushed back into the
input, when processing a macro evaluation. Default is 1024 characters.
.br
If any of these options is present they must be located before the first
file to be processed to have any effect.

The
.ul "-m
option processes the file
.ul "mfile
for macro definitions. Note that files processed in this way should contain
only macro definitions, no immediate output should be generated from this
file.
.PP
Commands typically are distinguished by a period in column one of the input
followed by a two character abbreviation for the command funtion. The
abbreviation may then be followed by an optional numeric or character
argument.
.br
The numeric argument may be an absolute value such as setting the right
margin to a particular column, or the argument may be preceded by an
operator to indicate that the parameter should be modified relative to a
previous setting. You may use the operators +, -, /, *, % (mod), | (bitwise
or), & (bitwise and), = (equal), < (less than) and > (greater than) to
calculate values.
.ul "No
normal operator precedence is taken into account; the expression is
evaluated strictly from left to right. You may use parentheses to group
sub-expressions together. Note that all operators are dyadic, even the -.
To generate a negative value, use a form like 0-5. A leading operator in an
expression is not used in the evaluation. Instead, it has the effect of
altering a previously set value. The + operator increases the set value
with the given value, the - operator decreases the set value with the given
value, the * operator multiplies the set value with the given value, and
the / operator divides the set value by the given value. A leading operator
within parentheses is ignored.
.PP
Commands that have effect on a given number of input lines, can also take a
text argument, if it is preceded by a double quote character `"'. This
behaves exactly as if you gave the number 1, and followed the command with
the given text argument.
.br
More text or commands may follow a command if they are seperated by a
semicolon `;', unless the first command must be the last one on an input
line.

.tm ...commands
The following commands are recognized:
.in +5;.ta +0;.cc !
!*---------------------------*
!cmd .bo
@tcauses the following lines of text to appear in boldface. The optional
argument specifies the number of lines to be typed in boldface. An argument
of zero explicitly turns boldface off, while a negative argument turns it
on indefinitely. When overstrike is being used to produce the boldface,
boldface and underlining are mutually exclusive features, and the
appearance of a boldface command will cause any underlining to cease.
!*---------------------------*
!cmd .bp
@tcauses succeeding text to appear at the top of a new page. The optional
argument specifies the page number for the new page. The initial value is
one and the default value is one more than the previous page number.
!*---------------------------*
!cmd .br
@tcauses succeeding text to start on a new line at the current left margin.
There is no argument for this command.
!*---------------------------*
!cmd .bs
@tenables or disables the appearance of backspaces in the output text. A
value of
!cu "zero
doesn't use backspaces at all, but utilizes standard ISO printer codes.
The Commodore Amiga console and printer devices support these directly, but
on other systems you need a post-processor to convert these standard ISO
codes into, for example, Epson printer codes. If you don't want this,
underlining and boldface can be done by inserting character - backspace -
character combinations into the output. This is fine for devices which
properly recognize the backspace character. Many printers, however, do not
recognize backspaces, so the option is provided to overprint one line
buffer with another. The first line buffer is terminated with just a
carriage return rather than the carriage return - linefeed combination.
!br;An argument of
!cu "2
to the backspace command removes backspaces from the output. Even with
printers which do recognize backspaces, this usually is faster. An argument
of
!cu "1
puts them into the output. The default is to use Commodore Amiga (ISO)
control sequences.
!*---------------------------*
!cmd .cc
@tchanges the
!ul "NRO
command character to that specified by the character argument. If no
argument is provided, the default is a period.
!*---------------------------*
!cmd .ce
@tcauses the next line of text to appear centered on the output. This
automatically generates a break. The optional argument specifies if more
than one line is to be centered. An argument of zero explicitly turns
centering off, while a negative argument turns it on indefinitely.
!*---------------------------*
!cmd .cu
@tcauses the next line(s) of text to be continuously underlined. Unlike the
underline command (see
!bo ".ul)
which underlines only alphanumerics, continuous underlining underlines all
printable characters. The optional argument specifies the number of lines
of text to underlined. An argument of zero explicitly turns continuous
underline off, while a negative argument turns it on indefinitely. Any
normal underlining command currently in effect will be terminated.
!*---------------------------*
!cmd .de
@tcauses all text and commands following to be used to define a macro. The
definition is terminated by a line with
!bo ".en
as the first three characters. The rest of that line is ignored.
!br;The first argument following the
!bo ".de
command becomes the name of the new command.
!br;It should be noted that upper and lower case arguments are
considered different. Thus, the commands
!bo ".PP
and
!bo ".pp
could define two different macros. Care should be exercised since existing
commands and macros may be redefined. A macro may contain up to ten
arguments. In the macro definition, the placement of arguments is
designated by the two character sequences, $0, $1, ... $9.
!br
When the macro is invoked, each argument of the macro command line is
substituted for its corresponding designator in the expansion. The first
argument of the macro command is substituted for the $0 in the expansion,
the second argument for the $1, and so forth. Arguments are typically
strings which do not contain blanks or tabs. If an argument is to contain
blanks, then it should be surrounded by either single or double quotes.
!br
A macro name may be at most ten characters long. If more are supplied, the
results are unpredictable. To get things like $4 in the macro text, use a
double $$, i.e. $$4.
!*---------------------------*
!cmd .ef
@tspecifies the text for the footer on even numbered pages. The format is
the same as for the footer command (see
!bo ".fo).
!*---------------------------*
!cmd .eh
@tspecifies the text for the header on even numbered pages. The format is
the same as for the footer command (see
!bo ".fo).
!*---------------------------*
!cmd .el
@tSee
!bo ".if
and
!bo ".ie.
The
!bo ".el
command reverses the truth of the condition remembered by the last
!bo ".ie.
command, and if the result is true, it accepts the input on the remainder
of the line, just like the
!bo ".if
command. Multi-line else-parts are thus also possible. You may have
multiple
!bo ".el
request following a single
!bo ".ie
command. Since every
!bo ".el
reverses the remembered condition, you have if effect multiple 'then-' and
'else-parts', just divided into bits and pieces.
!*---------------------------*
!cmd .ev
@tenvironment switch. If a numerical argument is given, the number of the
current environment is pushed on a special environment number stack, and
the named environment is made current. If no argument is supplied, the
previous environment number that was in effect will be restored. As a
variation of this, the command
!bo ".ev -
will restore the previous environment and
!ul "discard
the current environment. This is useful if you don't need the particular
environment anymore. At a new invocation of this environment it will have
default values again.
!br
The environment is the set of values that determine the appearance of
formatted output. There are ten environments available, and up to nineteen
environment numbers can be pushed. Because a stack of previous environment
numbers is maintained, always return to a previous environment by a
!bo ".ev
command without numeric argument.
!br
The following commands affect values that are in the environment:
!bo 2
.ls .ti .in .rm .ce .ul .cu .it .ta .ju .nj .bo .fi .nf .pn .pc .cc and
!bo ".c2.
!br
Also in the environment are the last remembered conditional,
!bo ".ie,
and collected partial output lines.
!*---------------------------*
!cmd .en
@tdesignates the end of a macro definition.
!*---------------------------*
!cmd .fi
@tcauses the input text to be rearranged or filled to obtain the maximum
word count possible between the previously set left and right margins. No
argument is expected.
!*---------------------------*
!cmd .fo
@tspecifies text to be used for a footer. The footer text contains three
strings seperated by a delimiter character. The first non-blank character
following the command is designated as the delimiter. The first text string
is left justified to the current indentation value (specified by
!bo ".in).
The second string is centered between the current indentation value and the
current right margin value (specified by
!bo ".rm).
The third string is right justified to the current right margin value. The
absence of footer text will result in the footer being printed as one blank
line. The presence of the page number character (set by
!bo ".pc)
in the footer text results in the current page number being inserted at
that position. Multiple occurrances of the page number character are
allowed. This command must be the last command on an input line.
!*---------------------------*
!cmd .he
@tspecifies text to be used for a header. The format is the same as for the
footer (see
!bo ".fo).
!*---------------------------*
!cmd .ie
@tSame as
!bo ".if,
but remembers the resulting condition, that can be used by subsequent
!bo ".el
requests. Only one level of conditions is remembered: they cannot be
nested. Every occurrence of a
!bo ".ie
request overrides the result as remembered by the previous one.
!*---------------------------*
!cmd .if
@tConditional acceptance of input. The request is followed by a
conditional, which may take several forms. Depending on the condition,
subsequent input may be accepted or ignored. The following forms of
condition are valid:
!in +9;!ti -4;!nj
!bo ".if [!]<letter>
anything
!ti -4;!bo ".if [!]<expression>
anything
!ti -4;!bo ".if [!]<delimiter> <string1> <delimiter> <string2> <delimiter>
anything
!in -9;!ju
The following <letter>s may be used:
!bo "o:
Current page number is odd;
!bo "e:
Current page number is even;
!bo "n:
Formatter is
!ul "NRO,
which is always true;
!bo "t:
Formatter is TRO(FF), which is always false.
!br
An <expression> is said to be true if it is > 0.
!br
The last form is a string comparison, which is true if <string1> and
<string2> are exactly equal. Any <delimiter> may be used as long as it
doesn't make the conditional look like one of the other forms.
!br
A not-symbol `!' may immediately precede the condition to reverse its
truth.
!br
Normally, the accepted or ignored input affected is only the rest of the
current input line. A multi-line `then-part' must begin with the opening
delimiter @@{ and the last line must end with the closing delimiter @@}. It
may also be useful to conceal the newline following the opening delimiter
if it is at the end of an input line. Because the delimiters are deleted
from the input, make sure that the line is not empty without them, because
this would cause a break. It is sufficient to place them on a line with a
comment command to avoid this.
!*---------------------------*
!cmd .in
@tindents the left margin to the column value specified by the argument.
The default left margin is set to zero. This command performs a break.
!*---------------------------*
!cmd .it
@tcauses the following lines of text to appear in italics. The optional
argument specifies the number of lines to be typed in italics. An argument
of zero explicitly turns italics off, while a negative argument turns it on
indefinitely. This command is
!bo "not
functional when the Commodore Amiga (ISO) command sequences are not being
used, since it can't be emulated by overstriking printlines.
!*---------------------------*
!cmd .ju
@tcauses blanks to be inserted between words in a line of output in order
to align or justify the right margin. The default is to justify. No
argument is expected.
!*---------------------------*
!cmd .ls
@tsets the line spacing to the value specified by the argument. The default
is for single spacing.
!*---------------------------*
!cmd .m1
@tspecifies the number of lines in the header margin. This is the space
from the physical top of page to and including the header text. A value of
zero causes the header to not be printed. A value of one causes the header
to appear at the physical top of page. Larger argument values cause the
appropriate number of blank lines to appear before the header is printed.
!*---------------------------*
!cmd .m2
@tspecifies the number of blank lines to be printed between the header line
and the first line of the processed text.
!*---------------------------*
!cmd .m3
@tspecifies the number of blank lines to be printed between the last line
of processed text and the footer line.
!*---------------------------*
!cmd .m4
@tspecifies the number of lines in the footer margin. This command affects
the footer the same way the
!bo ".m1
command affects the header.
!*---------------------------*
!cmd .ne
@tspecifies a number of lines which should not be broken across a page
boundary. If the number of lines remaining on a page is less than the value
needed, then a new output page is started.
!*---------------------------*
!cmd .nf
@tspecifies that succeeding text should be printed without rearrangement,
or with no fill. The default is to justify. No argument is expected.
!*---------------------------*
!cmd .nj
@tspecifies that no attempt should be made to align or justify the right
margin. No argument is expected.
!*---------------------------*
!cmd .nr
@tcauses the value of a number register to be set or modified. A total of
twenty-six number registers are available designated @@na through @@nz
(either upper or lower case is allowed). When the sequence @@nc is imbedded
in the text, the current value of number register c replaces the sequence,
thus, such things as paragraph numbering can be accomplished with relative
ease.
!*---------------------------*
!cmd .of
@tspecifies the text for the footer on odd numbered pages. The format is
the same as the footer command (see
!bo ".fo).
!*---------------------------*
!cmd .oh
@tspecifies the text for the header on odd numbered pages. The format is
the same as the footer command (see
!bo ".fo).
!*---------------------------*
!cmd .pc
@tspecifies the page number character to be used in headers and footers.
The occurrance of this character in the header or footer text results in
the current page number being printed. The default for this character is
the hash mark `#'.
!*---------------------------*
!cmd .pl
@tspecifies the page lenght or the number of lines per output page. The
default is 66.
!*---------------------------*
!cmd .pn
@tspecifies the way page numbering is done. You may specify the following
values: 0 uses normal arabic page numbers, 1 generates lowercase roman
numbers, and 2 specifies uppercase roman numbers. Default is arabic page
numbering.
!*---------------------------*
!cmd .po
@tspecifies a page offset value. This allows the formatted text to be
shifted to the right by the number of spaces specified. This feature may
also be invoked by a switch on the command line. All horizontal positions
(like tabstops, indentations) are adjusted accordingly.
!*---------------------------*
!cmd .rm
@tsets the column value for the right margin. The default is 80.
!*---------------------------*
!cmd .so
@tcauses input to be retrieved from the file specified by the command's
character string argument. The contents of the new file are inserted into
the output stream until an EOF is detected. Processing of the original file
is then resumed. File nesting is allowed up to a reasonable level (four).
!*---------------------------*
!cmd .sp
@tspecifies a number of blank lines to be output before printing the next
line of text. This automatically generates a break. You cannot move
upwards.
!*---------------------------*
!cmd .ta
@ttab settings. This command allows you to set tabstops. It may have
optional numeric arguments. When issued without arguments, all tabstops are
removed. When issued with one or more numeric arguments, tabstops are set
at the specified number of spaces from the left hand side of the paper. An
argument may optionally be preceded with a `+' to indicate that a distance
from the previously mentioned tab is meant instead of an absolute position.
A `+' sign before the first argument indicates a distance from the current
indent.
!*---------------------------*
!cmd .ti
@ttemporarily alters the indentation or left margin value for a single
succeeding line of output text. This command performs a break. If an input
line starts with spaces, this has the effect that a break is generated, and
a temporary indent for the number of spaces is set.
!*---------------------------*
!cmd .tm
@twrites a message to the standard error output, so you see it in your
console window. This command must be the last one on an input line. When
used with the no-break command character, this `no-break' gets another
meaning: don't break the formatted output if it also is printed on the
console. The command is ignored instead.
!*---------------------------*
!cmd .ul
@tunderlines the alphanumeric text in the following line(s). The optional
argument specifies the number of lines to be underlined. An argument of
zero explicitly turns underlining off, while a negative argument turns it
on indefinitely. When overstrike is being used to produce the underline,
underlining and boldface are mutually exclusive features, and the
appearance of an underline command cancels any existing boldface
operations.
!*---------------------------*
!cmd .un
@tundefines a previously defined macro, and all macros that were defined
later. If this macro was a redefinition of an older macro with the same
name, the old definition will be available again. Thus, macros are defined
in a stack-like fashion.
!cmd .*
!cmd ." 0
@tBoth of these serve as a way to insert comments in the input text. They
have absolutely no effect on the output.
!*---------------------------*

!cc
.in -5;.PP
.tm End of commands
Instead of having the command immediately at the beginning of a line, you
may also begin a line with a command character, then some blanks, and then
again a command character, this time followed immediately by the command.
There exists even a second command character, called the
.bo "no break command character,
`'', which suppresses the break normally generated by some commands. This
no break command character can only be used in this way. If the first
non-blank character is
.ul "not
a command character, it is considered to be text. An example illustrates
this.

.RS 5;.nf
@.    .sp   @@" This is a space command,
@.    this is some normal text,
@.    'sp 3 @@" and this spaces without a break.
.RE;.fi
.PP
When a line does not begin with the command character, its words are placed
one by one on an output line. As soon as a word doesn't fit, the collected
line is printed, possibly after justification. The word is then placed on
the next output line. If a line is about to be printed at the top of a
page, the page header, if any, is printed first. If a line is on the last
usable line of a page, the page footer, if any, is printed next, and the
page is ejected.
.br
If you want to begin an input text line with an instance of the command
character, you may precede it with the escape character.
.PP
A number of translations can be performed on the input, even before it is
interpreted as either text or commands. Such a translation takes place
where an escape character `@@' is seen in the input text. The single
charachter following the escape character determines the effect.

.tm ...functions
The following functions are currently implemented:
.in +5
.fun @@@@
@tis a single @@.
.*---------------------------*
.fun @@e
@tis replaced by the current value of the escape character. Currently,
there is no way to change it.
.*---------------------------*
.fun @@n
@tfollowed by a single letter, is replaced by the contents of the
designated number register.
.*---------------------------*
.fun @@t
@tis replaced with a tab character, to be used in conjunction with the
.bo "@.ta
command. You may also use a normal tab character if you wish. If a tab is
encountered, the necessary space is generated by non-breakable spaces. Any
spaces on the output line before the tab are also made non-breakable. This
is to avoid justification spoiling the tab.
.br
Tabs beyond the current right margin or beyond the last tab stop are
ignored, except that they separate words.
.*---------------------------*
.fun @@X(expression)
@tis replaced with the character with character code value `expression'.
.*---------------------------*
.fun @@(space)
@tis replaced by a non-breakable space. It behaves just like any printable
character, you just don't see it. This space won't be modified by
justification, and it will be underlined by continous underline.
.*---------------------------*
.fun @@(newline)
@tis deleted from the input. If a @@ is placed at the end of an input line,
it will appear as if the next input line actually is following the contents
of the current line. This is called `concealing the newline'.
.*---------------------------*
.fun @@.
@twhere . stands for the current command character, produces the command
character. It will, however, not be recognized as such. This provides a way
to start your input line with a command character that in fact is a text
line. This only works if the command character is different from any other
valid character following the escape character, and does not work for
.bo "@.en
commands that end a macro definition.
.*---------------------------*
.fun @@{
.fun @@} 0
@tThese two delimiters were already mentioned in the section about the
.bo "@.if
and
.bo "@.el
commands. They serve to delimit the lines you want to be affected by these
commands. Normally, when the condition is false, the
.bo "@.if
skips input until it finds the end of the current line. If it sees the @@{
following the condition, it sets a flag to indicate to the low-level file
reader, that it must count these delimiters. All input is then skipped
until the matching closing delimiter @@} is found. So, the
.bo "@.if
command never 'sees' anything of it. Also in this case, the
.bo "@.if
command skips the rest of the line remaining after the closing @@}.
.br
On the other hand, if the condition evaluates to be true, all and any
opening and closing delimiters are ignored completely. The command
following the condition is executed, and also of course any commands on
following lines. Since a command (or text) is expected on the same line as
the
.bo "@.if
command, omission of a command makes it look like there is an empty line,
and this generates an unexpected break. Therefore, you should conceal the
newline at the end of such a line. (See @@(newline).)
.*---------------------------*
.fun @@"
@tThis is yet another way to insert a comment. Note that text may precede
the comment function. All text from the comment function until the end of
the line will be ignored. Note that spaces between the last word on a line
and the comment are not deleted, and this may effect your layout in some
(as of yet unimplemented) cirumstances.

.in -5
Any unrecognized character that follows the @@ will be left in the input.
.PP
Macro expansion is accomplished in the following way. There exists an input
push-back buffer. If any input is needed, this push-back buffer is examined
first. If a character is present, the request is satisfied by taking the
last pushed back character. Only if the push back buffer is empty, a
character is read from the input file. At any time that
.ul "NRO
reads a character that is unexpected, it is pushed back, in the hope that
it can be used later.
.br
When a macro is to be expanded, its body is pushed back, while at the same
time subsituting the arguments. Normally when an instance of the escape
character is to be pushed back, it is `guarded' against re-substitution
when it is read back, by doubling it, so anything pushed back will be read
back exactly the same. This is not done
.ul "only
for macro bodies, to allow resubstitution to occur in them. This means that
the body of a macro is interpreted twice, once at definition time, and once
at application time. The macro arguments are interpreted only once, so that
a double escape character in an argument will show up in the resulting text
as a single escape character, instead of disappearing. This seems to be the
most intuitive approach. It will disallow some more complex applications
but avoids needing four @@'s in an argument if you want only one.
.tm ...examples
.SH EXAMPLES
.SS "Macros:
You may even define a macro that defines a macro, in the following way:
.br;.RS +5;.nf
@.de keep
@.de $0
$1 $2 $3 $4 $5 $6 $7 $8 $9
@@@@.en
@.en
.fi;.RE
This macro defines a macro with a name designated by the first argument,
and containing the text designated by the remaining arguments. Remember
that you can always enclose an argument with quotes.
.br
Note that you must use two escape characters to make the
.bo "@.en
part of the macro body. This is because macro definitions are handled
slightly different from normal input lines. In the definition of `keep'
there will be a line with
.bo "@@.en,
and at the time of definition of the inner macro the second escape
character will be stripped off, so that the end of the inner macro
definition is recognized.
.br
Another way to to it, is to change the command character temporarily while
defining the outer macro.

.SS "Conditionals:
Here are a few examples of correct use of the
.bo "@.if, .ie
and
.bo "@.el
commands:
.br;.RS +5;.nf

@.if @@na Register A is greater than zero!

@.if @@na @@{.firstcommand
@.	   .secondcommand
@.	   .lastcommand @@}

@.if @@na @@{@@
@.	   .firstcommand
@.	   .secondcommand
@.	   .lastcommand @@}

@.ie @@na @@{.firstcommand
@.	   .secondcommand
@.	   .lastcommand @@}
@.el Register A is NOT greater than zero!!
@.el Maybe a bit late, but register A IS greater than zero!!
.fi;.RE
.tm ...undocumented features
.SH "UNDOCUMENTED FEATURES"
.PP
<censored>
SHAR_EOF
cat << \SHAR_EOF > nrocmd.c
/*
 *      Command processor for NRO text processor
 *
 *      Originally by Stephen L. Browning, 5723 North Parker Avenue
 *      Indianapolis, Indiana 46220
 *
 *      Transformed beyond immediate recognition, and
 *      adapted for Amiga by Olaf Seibert, KosmoSoft
 *
 *      Vossendijk 149-1 (study)   Beek 5 (home)
 *      5634 TN  Nijmegen          5815 CS  Merselo
 *      The Netherlands            The Netherlands
 *      Phone:
 *             (...-31)80561045     (...-31)4786205
 *          or 080-561045           04786-205
 *
 *      This program is NOT in the public domain. It may, however
 *      be distributed only at no charge, and this notice must be
 *      included unaltered.
 */

#include <stdio.h>
#include "nro.h"
#include "nroxtrn.h"

uchar *skipbl();
uchar *skipwd();

/*
 *      Communicating the current file through a global
 *      variable is a bit of a kludge. We could as well use
 *      it everywhere instead of passing it as a parameter
 *      all the time. But that would not be very `structured',
 *      whatever that may be.
 */

comand(word)
uchar *word;
{
        int ct, val;
        int spval;
        uchar argtyp, chr;
        uchar *macexp;

        val = getcmdwrd(word, infile);
        if (word[0] == env.c2chr) {
                env.dontbrk = TRUE;
                word++;
        } else if (word[0] == env.cmdchr) {
                word++;
        } else if (val > 0) {
                putbak(' ');
                pbstr(word);
                return;
        }

        ct = comtyp(word, &macexp);

        if (ct == UNKNOWN) {
                error("nro: unrecognized command %s\n", word);
                env.dontbrk = FALSE;
                return;
        }
        if (! (ct & NOARGS))
                val = getval(&argtyp, infile);  /* Eat more of line */

        switch (ct & ~NOARGS) {
        case BO: /* Bold face */
                set(&env.boval, val, argtyp, 1, -1, HUGE);
                if (env.boval)  env.reqmode |= FXBO;
                else            env.reqmode &= ~FXBO;
                if (dc.bsflg != BSAMIGA)
                        env.cuval = env.ulval = 0;
                break;
        case BP: /* Begin page */
                if (pg.lineno > 0) space(pg.plval);
                set(&pg.curpag, val, argtyp, pg.curpag+1, -HUGE, HUGE);
                pg.newpag = pg.curpag;
                break;
        case BR: /* Break */
                dobrk();
                break;
        case BS: /* Backspaces in output: 0=No, Amiga; 1=Yes; 2=Use CR */
                set(&dc.bsflg, val, argtyp, 1, 0, 2);
                break;
        case C2: /* No-break command character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.c2chr = C2CHAR;
                else { env.c2chr = chr; ct = 0; }
                break;
        case CC: /* Command character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.cmdchr = CMDCHAR;
                else { env.cmdchr = chr; ct = 0; }
                break;
        case CE: /* Center */
                dobrk();
                set(&env.ceval, val, argtyp, 1, -1, HUGE);
                break;
        case COMMENT:
                while (chr = ngetc(infile), isnteol(chr));
                break;
        case CU: /* Continuous underline */
                set(&env.cuval, val, argtyp, 1, -1, HUGE);
                if (env.cuval)  env.reqmode |= FXUL;
                else            env.reqmode &= ~FXUL;
                if (dc.bsflg != BSAMIGA)
                        env.ulval = env.boval = 0;
                break;
        case DE: /* Define macro */
                defmac(word, infile);
                break;
        case EF: /* Even footer */
                gettl(infile, pg.efoot, NULL, &pg.eflim[0], NULL);
                break;
        case EH: /* Even header */
                gettl(infile, pg.ehead, NULL, &pg.ehlim[0], NULL);
                break;
        case EL: /* Else */
                doel(infile);
                break;
        case EN: /* End macro definition */
                error("nro: missing .de command\n");
                break;
        case EV: /* Environment switch */
                if (isdigit(argtyp)) {  /* Supplied argument: push */
                        if (dc.envsp >= ENVSTACK-1) {
                                error("nro: cannot push environment.\n"); break;
                        }
                        spval = dc.envstack[dc.envsp];  /* Current environment */
                        dc.envstack[++dc.envsp] = val;  /* Save number on stack*/
                        storenv(spval);                                 /* Save current envir. */
                        loadenv(val);                                   /* Get new one         */
                } else {        /* Pop an environment */
                        if ((int) dc.envsp <= 0) {
                                error("*** nro: cannot pop environment.\n"); break;
                        }
                        spval = dc.envstack[dc.envsp];  /* Current environment */
                        val = dc.envstack[--dc.envsp];  /* Number of old env   */
                        if (argtyp == '-') freenv(spval);/* Dump current environ*/
                        else storenv(spval);                    /* ..or save it        */
                        loadenv(val);                                   /* Get old one back    */
                }
                break;
        case FI: /* Fill */
                dobrk();
                env.fill = YES;
                break;
        case FO: /* Footer */
                gettl(infile, pg.efoot, pg.ofoot, &pg.eflim[0], &pg.oflim[0]);
                break;
        case HE: /* Header */
                gettl(infile, pg.ehead, pg.ohead, &pg.ehlim[0], &pg.ohlim[0]);
                break;
        case IE:
        case IF:
                doieif(infile, ct & ~NOARGS);
                break;
        case IN: /* Indenting */
                dobrk();
                set(&env.inval, val, argtyp, 0, 0, env.tmval-1);
                env.tival = env.inval;
                break;
        case IT: /* Italic face */
                set(&env.itval, val, argtyp, 1, -1, HUGE);
                if (env.itval)  env.reqmode |= FXIT;
                else            env.reqmode &= ~FXIT;
                if (dc.bsflg != BSAMIGA)
                        error("nro: italics cannot be done by overstrike :-)\n");
                break;
        case JU: /* Justify */
                env.juval = YES;
                break;
        case LS: /* Line spacing */
                set(&env.lsval, val, argtyp, 1, 1, HUGE);
                break;
        case M1: /* Set topmost margin */
                set(&pg.m1val, val, argtyp, 2, 0, HUGE);
                break;
        case M2: /* Set second top margin */
                set(&pg.m2val, val, argtyp, 2, 0, HUGE);
                break;
        case M3: /* Set first bottom margin */
                set(&pg.m3val, val, argtyp, 2, 0, HUGE);
                pg.bottom = pg.plval - pg.m4val - pg.m3val;
                break;
        case M4: /* Set bottom-most margin */
                set(&pg.m4val, val, argtyp, 2, 0, HUGE);
                pg.bottom = pg.plval - pg.m4val - pg.m3val;
                break;
        case MACRO: /* Macro expansion */
                maceval(macexp, infile);
                break;
        case NE: /* Need n lines */
                /* dobrk(); */
                if ((pg.bottom-pg.lineno+1) < (val*env.lsval)) space(pg.plval);
                break;
        case NF: /* No fill */
                dobrk();
                env.fill = NO;
                break;
        case NJ: /* No justify */
                env.juval = NO;
                break;
        case NR: /* Set number register */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (!isalpha(chr)) {
                        error("nro: invalid or missing number register name\n");
                } else {
                        ct = tolower(chr) - 'a';
                        val = getval(&chr, infile);
                        set(&dc.nr[ct], val, chr, 0, -HUGE, HUGE);
                }
                ct = 0;
                break;
        case OF: /* Odd footer */
                gettl(infile, pg.ofoot, NULL, &pg.oflim[0], NULL);
                break;
        case OH: /* Odd header */
                gettl(infile, pg.ohead, NULL, &pg.ohlim[0], NULL);
                break;
        case PC: /* Page number character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.pgchr = EOS;
                else { env.pgchr = chr; ct = 0; }
                break;
        case PL: /* Page length */
                set(&pg.plval, val, argtyp, PAGELEN,
                        pg.m1val+pg.m2val+pg.m3val+pg.m4val+1, HUGE);
                pg.bottom = pg.plval - pg.m3val - pg.m4val;
                break;
        case PN: /* Page numbering mode */
                set(&env.pnflg, val, argtyp, PNARABIC, PNARABIC, PNUROMAN);
                break;
        case PO: /* Page offset */
                set(&pg.offset, val, argtyp, 0, 0, HUGE);
                break;
        case RM: /* Right margin */
                set(&env.rmval, val, argtyp, PAGEWIDTH, env.tival+1, HUGE);
                env.tmval = env.rmval;
                break;
        case SO: /* Source file */
                if (getcmdwrd(word, infile) == EOF) break;
                skipeol(infile);
                ct = NOARGS;
                if (dc.flevel+1 >= NFILES) {
                        error("nro: .so commands nested too deeply\n");
                        break;
                }
                if ((sofile[dc.flevel+1] = fopen(word, "r")) == NULL) {
                        error("nro: unable to open %s\n", word);
                        break;
                }
                if (verbose > 2) error("nro: processing file '%s'\n", word);
                sopbb[dc.flevel] = mac.pbb;     /* Stack push back stacks */
                mac.pbb = mac.ppb + 1;
                dc.flevel++;
                infile = sofile[dc.flevel];
                break;
        case SP: /* Space */
                set(&spval, val, argtyp, 1, 0, HUGE);
                space(spval * env.lsval);
                break;
        case TA: /* Tab settings */
                tabs(infile);
                break;
        case TI: /* Temporary indent */
                dobrk();
                set(&env.tival, val, argtyp, 0, 0, env.tmval);
                break;
        case TM: /* Terminal Message */
                fflush(stdout);
                if (env.dontbrk == FALSE || pout != stdout) {
                        while (val = ngetc(infile), isnteol(val) && val != EOF)
                                putc(val, stderr);
                        putc('\n', stderr);
                        fflush(stderr);
                } else ct = 0; /* Skip rest of command line */
                break;
        case UL: /* Underline */
                set(&env.ulval, val, argtyp, -1, 1, HUGE);
                if (env.ulval)  env.reqmode |= FXUL;
                else            env.reqmode &= ~FXUL;
                if (dc.bsflg != BSAMIGA)
                        env.cuval = env.boval = 0;
                break;
        case UN: /* Undefine macro */
                undefmac(word, infile);
                break;
        }
        env.dontbrk = FALSE;

        if (argtyp != STRINGTYP && !(ct & NOARGS))      skipeol(infile);

        return;
}


/*
 *      Decodes nro command and returns its associated
 *      value.
 */

comtyp(line, macdef)
uchar *line;
uchar **macdef;
{
        uchar c1, c2;

        /*
         *      First check to see if the command is a macro.
         *      If it is, return expansion in macdef.
         *      Note that upper and lower case characters are handled
         *      differently for macro names, but not for normal command names.
         */

        if ((*macdef = getmac(line)) != NULL) {
                return MACRO | NOARGS;
        }

        c1 = tolower(*line++);
        c2 = tolower(*line  );

        switch (c1) {
        case '"':
        case '*':
                return COMMENT | NOARGS;
        case 'b':
                if              (c2 == 'o') return BO;
                else if (c2 == 'p') return BP;
                else if (c2 == 'r') return BR;
                else if (c2 == 's') return BS;
                break;
        case 'c':
                if              (c2 == '2') return C2 | NOARGS;
                else if (c2 == 'c') return CC | NOARGS;
                else if (c2 == 'e') return CE;
                else if (c2 == 'u') return CU;
                break;
        case 'd':
                if      (c2 == 'e') return DE | NOARGS;
                break;
        case 'e':
                if              (c2 == 'f') return EF | NOARGS;
                else if (c2 == 'h') return EH | NOARGS;
                else if (c2 == 'l') return EL | NOARGS;
                else if (c2 == 'n') return EN;
                else if (c2 == 'v') return EV;
                break;
        case 'f':
                if              (c2 == 'i') return FI;
                else if (c2 == 'o') return FO | NOARGS;
                break;
        case 'h':
                if      (c2 == 'e') return HE | NOARGS;
                break;
        case 'i':
                if              (c2 == 'e') return IE | NOARGS;
                else if (c2 == 'f') return IF | NOARGS;
                else if (c2 == 'n') return IN;
                else if (c2 == 't') return IT;
                break;
        case 'j':
                if      (c2 == 'u') return JU;
                break;
        case 'l':
                if      (c2 == 's') return LS;
                break;
        case 'm':
                if              (c2 == '1') return M1;
                else if (c2 == '2') return M2;
                else if (c2 == '3') return M3;
                else if (c2 == '4') return M4;
                break;
        case 'n':
                if              (c2 == 'e') return NE;
                else if (c2 == 'f') return NF;
                else if (c2 == 'j') return NJ;
                else if (c2 == 'r') return NR | NOARGS;
                break;
        case 'o':
                if              (c2 == 'f') return OF | NOARGS;
                else if (c2 == 'h') return OH | NOARGS;
                break;
        case 'p':
                if              (c2 == 'c') return PC | NOARGS;
                else if (c2 == 'l') return PL;
                else if (c2 == 'n') return PN;
                else if (c2 == 'o') return PO;
                break;
        case 'r':
                if      (c2 == 'm') return RM;
                break;
        case 's':
                if              (c2 == 'o') return SO | NOARGS;
                else if (c2 == 'p') return SP;
                break;
        case 't':
                if      (c2 == 'a') return TA | NOARGS;
                else if (c2 == 'i') return TI;
                else if (c2 == 'm') return TM | NOARGS;
                break;
        case 'u':
                if              (c2 == 'l') return UL;
                else if (c2 == 'n') return UN | NOARGS;
        }
        return UNKNOWN;
}


/*
 *      Retrieves optional argument following nro command.
 *      returns positive integer value with sign (if any)
 *      saved in character addressed by pargtyp.
 *      In case of a string, puts in a quote.
 *      It won't read the character it doesn't understand,
 *      i.e. semicolons and newlines, and garbage.
 */

getval(pargtyp, infile)
uchar *pargtyp;
register FILE *infile;
{
        register uchar chr, operator = '+';
        short digit;
        register int val, cumval;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        *pargtyp = chr;
        if (chr == '+' || chr == '-' || chr == '/' || chr == '*')
                chr = ngetc(infile);
        else if (chr == STRINGTYP)
                return 1;

        cumval = 0;

again:
        val = 0;

        if (chr == '(') {       /* Parenthesized subexpression */
                val = getval(&digit, infile); /* Dummy argtype pointer */
                chr = ngetc(infile);
                if (chr != ')')
                        error("nro: missing close parenthesis in expression\n");
                else
                        chr = ngetc(infile); /* Get next operator or anything */
                if (*pargtyp == '(') *pargtyp = '0';
        } else {                        /* Try to collect a number from the input */
                while ((digit = atod(chr)) >= 0) {
                        val = 10 * val + digit;
                        chr = ngetc(infile);
                }
        }

        /* Check if we need to evaluate an operator */
        if (operator) {
                switch (operator) {
                case '+': cumval += val; break;
                case '-': cumval -= val; break;
                case '*': cumval *= val; break;
                case '/':
                        if (val != 0) cumval /= val;
                        else error("nro: division by zero\n");
                        break;
                case '%':
                        if (val != 0) cumval %= val;
                        else error("nro: modulo by zero\n");
                        break;
                case '<': cumval = cumval < val;  break;
                case '=': cumval = cumval == val; break;
                case '>': cumval = cumval > val;  break;
                case '&': cumval = cumval & val; break;
                case '|': cumval = cumval | val; break;
                }
                operator = '\0';
        }

        /* See if there is more to come */
        switch (chr) {
        case '+': case '-': case '*': case '/': case '%':
        case '<': case '=': case '>':
        case '&': case '|':
                operator = chr;
                chr = ngetc(infile);
                goto again;
        default:
                putbak(chr);    /* Put back what we can't interpret */

                return cumval;
        }
}


/*
 *      Convert string to decimal.
 *      processes only positive values.
 */

ctod(p)
uchar *p;
{
        int val, d;

        val = 0;
        while (*p != EOS) {
                d = atod(*p++);
                if (d == -1) return val;
                val = 10 * val + d;
        }
        return val;
}


/*
 *      Convert ascii character to decimal.
 */

atod(c)
uchar c;
{
        return ((c < '0') || (c > '9')) ? -1 : c-'0';
}


/*
 *      Get non-blank word from the input file.
 *      Returns the number of spaces skipped before the word,
 *      or EOF on end of file.
 *      It won't read past a newline or semicolon,
 *      but will skip the first space following the word.
 */

getcmdwrd(to, infile)
register uchar *to;
register FILE *infile;
{
        register short chr;
        int skipped = 0;
        short length = 0;


        chr = ngetc(infile);
        if (chr == EOF) {
                *to = EOS;
                return EOF;
        }

        /* Skip spaces */

        while (isspace(chr)) {
                chr = ngetc(infile);
                skipped++;
        }

        while (isntspace(chr) && isnteol(chr) && chr != EOF &&
                        chr != MORETXT && length < MAXWORD-3) {
                *to++ = chr;
                length++;
                chr = ngetc(infile);
        }

        if (iseol(chr) || chr == MORETXT) putbak(chr);

        *to = EOS;
        if (length >= MAXWORD-3) error("nro: command word buffer overflow\n");

        return skipped;
}


/*
 *      Skip the rest of the current input line
 */

skipeol(infile)
FILE *infile;
{
        int chr;

        while ((chr = ngetc(infile)) != '\n' && chr != MORETXT &&
                chr != EOF);
}


/*
 *      Process an IF or IE request
 */

doieif(infile, request)
FILE *infile;
int request;
{
        uchar *strp;
        short negation;
        int chr, val;
        uchar delim;
        uchar string[MAXWORD];

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');
        if (chr == '!') {
                negation = TRUE;
                chr = ngetc(infile);
        } else
                negation = FALSE;

        switch (chr) {
        case 'e':       /* Even page number */
                val = (pg.curpag % 2) == 0;
                break;
        case 'o':       /* Odd page number */
                val = (pg.curpag % 2) != 0;
                break;
        case 'n':       /* Nro(ff) is formatter */
                val = TRUE; break;
        case 't':       /* Troff certainly not */
                val = FALSE; break;
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
        case '+': case '-': case '(':
                /* Must be an expression */
                putbak(chr);
                chr = getval(&string[0], infile);
                val = 0;
                set(&val, chr, string[0], 0, -HUGE, HUGE);
                val = val > 0;
                break;
        default:        /* String comparison */
                delim  = chr;
                strp   = string;        /* Collect first string */
                while ((chr = ngetc(infile)) != delim &&
                                strp < string + sizeof(string) - 2)
                        *strp++ = chr;
                *strp = EOS;
                val = TRUE;

                strp = string;  /* Compare with second string */
                while ((chr = ngetc(infile)) != delim) {
                        if (*strp++ != chr) val = FALSE;
                }
                if (*strp != EOS) val = FALSE;
                break;
        }
        if (negation) val = !val;
        if (request == IE) env.lastie = val;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        if (val) {     /* Condition is true. Don't skip any text */
                if (chr != BEGIF) putbak(chr);
        } else { /* Need to skip some text, maybe even very much */
                if (chr == BEGIF)          /* Skip until end of line */
                        dc.iflvl = 1;
                while (isnteol(chr)) chr = ngetc(infile);
        }
}


/*
 *      Process an EL request
 */

doel(infile)
FILE *infile;
{
        int chr, val;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        /* Toggle last remembered condition */
        val = env.lastie = !env.lastie;

        if (val) {     /* Condition is true. Don't skip any text */
                if (chr != BEGIF) putbak(chr);
        } else { /* Need to skip some text, maybe even very much */
                if (chr == BEGIF)          /* Skip until end of line */
                        dc.iflvl = 1;
                while (isnteol(chr)) chr = ngetc(infile);
        }
}


/*
 *      Process a TA request
 */

tabs(infile)
FILE *infile;
{
        int chr, val, i, j;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        if (iseol(chr) || chr == EOF) { /* No arguments */
                for (i=0; i<MAXTAB; i++)
                        env.tabstop[i] = NOTAB;
                return;
        }

        i = env.inval;

        for (;;) {      /* Collect the values */
                putbak(chr);
                val = getval((uchar *)&chr, infile);

                if (*(uchar *)&chr == '+') {    /* Saves a temporary uchar :-) */
                        val += i;
                }

                /* Find where to insert the tab */
                i=0;
                while (i<MAXTAB && env.tabstop[i] < val) i++;

                if (i < MAXTAB && env.tabstop[i] != val)  {
                        /* Insert the tab */
                        for (j=MAXTAB-1; j > i; j--)
                                env.tabstop[j] = env.tabstop[j-1];

                        env.tabstop[i] = val;
                }
                i = val;

                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr) || chr == MORETXT || chr == EOF) break;
        }
}



/*
 *      Loadenv - get an environment
 */

loadenv(number)
int number;
{
        struct environ *envp;
        short freeit = env.dontbrk;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp) {     /* It't there. Copy it. */
                env = *envp;
                if (freeit) {           /* We may free the saved image of it, */
                        freenv(number); /* if we are tight on memory.         */
                }
        } else {        /* It isn't. Just use default values. */
                initenv();
        }

        return OK;
}

/*
 *      Storenv - save an environment
 */

storenv(number)
int number;
{
        struct environ *envp;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp == NULL) {     /* Never saw this guy before */
                if ((envp = (struct environ *)malloc(sizeof(*envp))) == NULL) {
                        error("*** nro: cannot allocate environment #%d\n", number);
                        return ERR;
                } else if (verbose > 2)
                        error("nro: allocated environment #%d\n", number);
                environ[number] = envp;
        }

        *envp = env;

        return OK;
}


/*
 *      Freenv - throw an environment away
 */

freenv(number)
int number;
{
        struct environ *envp;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp) {
                free(envp);
                environ[number] = NULL;
                if (verbose > 2) error("nro: freed environment #%d\n", number);
        }

        return OK;
}


/*
 *      End current filled line
 */

dobrk()
{
        if (!env.dontbrk) {             /* Breaks may be disabled by using .'xx */
                if (env.outp > 0) {
#ifdef CPM
                        env.outbuf[env.outp++] = '\r';
                        env.outbuf[env.outp++] = '\n';
                        env.outbuf[env.outp  ] = EOS;
#else  CPM
                        env.outbuf[env.outp++] = '\n';
                        env.outbuf[env.outp  ] = EOS;
#endif CPM
                        if (env.ceval != 0)     /* Centering */
                                center(env.outbuf);
                        put(env.outbuf);
                }
                env.outp = 0;
                env.outw = 0;
                env.outwds = 0;
        }
}



/*
 *      Define a macro
 */

defmac(word, infile)
uchar *word;
FILE *infile;
{
        uchar line[MAXLINE];

        getcmdwrd(word, infile);
        skipeol(infile);

        if (word[0] == EOS && verbose)
                error("nro: appending to macro definition\n");

        while (getlin(line, infile) != EOF) {
                if (line[0] == env.cmdchr && line[1] == 'e' && line[2] == 'n')
                        break;
                if (putmac(word, line) == ERR) {
                        error("*** nro: macro definition table full\n");
                }
                word[0] = EOS;
        }
}


/*
 *      Put macro definition into table.
 *      If name == "", concatenate this definition to the previous one.
 */

putmac(name, def)
uchar *name;
uchar *def;
{
        int lenofname, lenofdef;

        lenofname = strlen(name);
        lenofdef = strlen(def);

        if ((lenofname && mac.lastp >= mac.mxmdef) ||
                (mac.emb + lenofname + lenofdef + 2 > &mac.mb[mac.macbuf])) {
                return ERR;
        }
        if (lenofname) {
                ++mac.lastp;
                mac.mnames[mac.lastp] = mac.emb;
                strcpy(mac.emb, name);
                mac.emb += lenofname + 1;
        } else
                mac.emb--;

        strcpy(mac.emb, def);
        mac.emb += lenofdef + 1;
        return OK;
}



/*
 *      Get macro definition from table
 */

uchar *getmac(name)
uchar *name;
{
        register int i;
        register uchar *name1, *mname;


        for (i = mac.lastp; i > 0; --i) {       /*V1.5*/
                name1 = name;
                mname = mac.mnames[i];
                while (*(name1++) == *(mname++)) {
                        if (name1[-1] == EOS)   return mname;
                }
        }
        return NULL;
}


/*
 *      Delete macro definition from table
 */

undefmac(word, infile)
uchar *word;
FILE *infile;
{
        register int i;
        register uchar *name, *mname;

        getcmdwrd(word, infile);
        skipeol(infile);

        for (i = mac.lastp; i >= 0; --i) {
                name = word;
                mname = mac.mnames[i];
                while (*(name++) == *(mname++)) {
                        if (name[-1] == EOS) {
                                mac.lastp = i-1;
                                mac.emb = mac.mnames[i];
                                return OK;
                        }
                }
        }
        return ERR;
}



/*
 *      Evaluate macro expansion for re-evaluation
 */

maceval(macdef, infile)
register uchar *macdef;
FILE *infile;
{
        uchar *argp[10];
        int i;
        uchar c;
        uchar line[MAXLINE];
        register uchar *linep = line;

        *linep++ = EOS;
        getlin(linep, infile);
        /*
         *       Initialize argp array to substitute empty string
         *       string for any undefined argument
         */
        for (i=0; i<10; ++i) argp[i] = line;

        for (i=0; i<10; ++i) {
                linep = skipbl(linep);
                if (iseol(*linep) || *linep == MORETXT || *linep == EOS) break;
                if (*linep == '\'' || *linep == '"') {
                        c = *linep++;
                        argp[i] = linep;
                        while (*linep != c && isnteol(*linep) && *linep != EOS)
                                linep++;
                        *linep++ = EOS;
                } else {
                        argp[i] = linep;
                        linep = skipwd(linep);
                        *linep++ = EOS;
                }
        }

        /* First push back any remaining text or commands */

        if (*linep == MORETXT) pbstr(linep+1);

        /* Now push back macro body */

        for (i=strlen(macdef)-1; i>=0; --i) {
                /* Need we substitute an argument? */
                if (i > 0 && macdef[i-1] == '$') {
                        if (!isdigit(macdef[i]) || macdef[i-2] == '$') {
                                /* Re-evaluate escape characters from macro body */
                                putbak(macdef[i] | NOGUARD);
                        } else {
                                /* but not of any arguments */
                                pbstr(argp[macdef[i]-'0']);
                                --i;
                        }
                } else {
                        putbak(macdef[i] | NOGUARD);
                }
        }
}


/*
 *      Push back string into input stream for re-evaluation
 */

pbstr(p)
register uchar p[];
{
        register int i;

        for (i=strlen(p)-1; i>=0; --i) {
                putbak(p[i]);
        }
}



/*
 *      Push character back into input stream
 */

putbak(c)
int c;
{
        if (mac.ppb < mac.pbb) {
                mac.ppb = mac.pbb;
                *mac.ppb = c;
        } else {
                if (mac.ppb >= mac.pbbend-2) {
                        error("*** nro: push back buffer overflow\n");
                }
                *++mac.ppb = c;
        }

        /* Avoid evaluating escaped escape characters twice */
        if (c == ESCCHAR)
                *++mac.ppb = ESCCHAR;
}



/*
 *      Get header or footer title
 */

gettl(infile, line1, line2, limit1, limit2)
FILE *infile;
uchar *line1, *line2;
int limit1[], limit2[];
{
        uchar c;

        while (c = ngetc(infile), isspace(c));

        line1[0] = c;
        if (isnteol(c)) getlin(line1+1, infile);
        limit1[LEFT] = env.inval;
        limit1[RIGHT] = env.rmval;
        if (line2) {
                strcpy(line2, line1);
                limit2[LEFT] = limit1[LEFT];
                limit2[RIGHT] = limit1[RIGHT];
        }
}



/*
 *      Set parameter and check range
 */

set(param, val, type, defval, minval, maxval)
int *param;
int val;
uchar type;
int defval, minval, maxval;
{
        switch(type) {
        case '+':
                *param += val; break;
        case '-':
                *param -= val; break;
        case '*':
                *param *= val; break;
        case '/':
                if (val != 0) *param /= val;
                else error ("nro: division by zero modification\n");
                break;
        case '%':
                if (val != 0) *param %= val;
                else error ("nro: modulo by zero modification\n");
                break;
        default:
                *param = isdigit(type)? val : defval;
                break;
        }
        if (*param < minval)
                *param = minval;
        else if (*param > maxval)
                *param = maxval;
}



/*
 *      Skip blanks and tabs in character buffer.
 *      return pointer to first non-space char.
 */

uchar *skipbl(p)
uchar *p;
{
        while (isspace(*p)) ++p;
        return p;
}


/*
 *      Skip over word and punctuation
 */

uchar *skipwd(p)
uchar *p;
{
        while (isntspace(*p) && isnteol(*p) && *p != EOS)
                ++p;
        return p;
}



/*
 *      Space vertically n lines
 */

space(n)
int n;
{
        dobrk();
        if (pg.lineno > pg.bottom) return;
        if (pg.lineno == 0) phead();
        skip(min(n, pg.bottom+1-pg.lineno));
        pg.lineno += n;
        if (pg.lineno > pg.bottom) pfoot();
}

SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.