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.