Полезная информация

Previous Page TOC Next Page Home


27 — Writing Your Own Macros

By Susan Peppard

Why Would Anyone Write a Macro?

If you work with macros every day, sooner or later you'll get the urge to write one. Sometimes it's a mild case of the disease: You're creating a document with mm, but you want paragraphs with a first-line indent and no extra space between paragraphs. Occasionally you want to do something more elaborate—like create a macro package for formatting that screen play.

Before you start, make sure you're familiar with the building blocks. troff provides you with the following: troff primitives (discussed in detail in Chapter 21, "Basic Formatting with troff/nroff"); escape sequences, such as \e and \^ (also discussed in detail in Chapter 21); other macros, either from a standard macro package, or ones you've written; number registers; and defined strings.

The next section reviews just what a macro is made of and introduces you to concepts that will be explained in detail later in the chapter.

Macro Review and Overview

With embedded troff primitives, you can format a page just about any way you want. The trouble is you have to reinvent the wheel every time you write a new document. And every time you format a first-level heading, you have to remember just what sequence of primitives you used to produce that centered 14-point Helvetica Bold heading. Then you have to type three or four troff requests, the heading itself, and another three or four requests to return to your normal body style. (This is practical only if you're being paid by the line.) It's a laborious process and one that makes it difficult—perhaps impossible—to maintain consistency over a set of files.

Good news: You can use macros to simplify formatting and ensure consistency.

Macros take advantage of one of the UNIX system's distinguishing characteristics—the ability to build complex processes from basic (primitive) units. A macro is nothing more than a series of troff requests, specified and named, that perform a special formatting task.


NOTE: The expression "troff request" is often used as a synonym for troff primitive. When I say "troff request," I mean any or all of the facilities provided by troff: primitives, escape sequences, strings, registers, and special characters.

Macros can be simple or complex, short or long, straightforward or cryptic. For example, a new paragraph macro might entail

.sp .5

.ti .5i

This produces spacing of half a line space (nroff) or half an em (troff) between paragraphs, and indents the first line of each paragraph half an inch.


NOTE: You can use just about any unit of measurement you want—inches, centimeters, points, picas—as long as you specify the units.

Macro names consist of a period followed by one or two characters. Traditionally, these characters are uppercase, to distinguish them from primitives. (The me package is the exception to this rule.) The paragraph macro above could be called .P or .PP or .XX.


NOTE: In general, macro names, like primitive names, are mnemonic; there's some relationship, however farfetched, between the macro name and its function. Thus .P or .PP would be reasonable names for a paragraph macro, and .XX wouldn't.

Macros are invoked in a text file by typing their names. (The period must be in the first position on the line.) Macros can also be invoked with an apostrophe (single quote) instead of a period as the first character. This delays processing of the macro until the current line has been filled.

A Heading Macro, Dissected and Explained

A fairly straightforward example of a macro is that centered heading I mentioned earlier. To create it, you need to provide spacing information before and after the heading, font information, size information, and position information (centering).

You could do this as follows:

.sp 2    \"space before the heading

.ce 99   \"turns on centering for the next 99 lines

.        \"to accommodate headings that might start 

.        \"out as 1 line and then get longer

.ft HB   \"changes the font to Helvetica Bold

.ps 14   \changes the point size to 14 points

.vs 16   \"changes vertical spacing to 16 points

first line of heading

second line of heading (optional)

third line of heading (optional)

.sp      \"space after the heading

.ce 0    \turns off centering

.ft      \"returns to default font

.ps      \returns to default point size

.vs      \"returns to default vertical space

That simple series of troff primitives illustrates several important points.

Most important, it is full of comments. Comments are identified by the sequence.\"

Note, however, that you can have as many spaces as you want between the initial period and the backslash. In this way, you can put a comment on a line by itself or you can add a comment at the end of a line of troff requests. You can use spaces to line up your comments so they're easy to read.

Another useful technique, illustrated in the sample above might be called generalization or thinking ahead. Instead of providing for a 1-line heading with a simple .ce, which centers the next line of text, the sample code turns centering on by requesting .ce 99 (which centers the next 99 lines of text). Most headings are not much longer than that. After the heading lines are specified, the code turns centering off with a .ce 0.

All of that code could be combined into a single pair of macros, called .H1 (for first-level heading) and .HE (for heading end), so that all you need type is

.H1

heading

.HE

A big improvement!

But wait. What if the heading came near the bottom of the page? There's nothing in the .H1 macro to prevent the heading from printing all by itself just before a page break. You need at least three lines of text after the heading. Fortunately there's a troff primitive trained for just this job—.ne.

.ne (for need) says "I need the number of lines specified right after me or else I'm going to start a new page." (This is similar to the "Keep with Next" feature of the better word processors and desktop publishing software.) Perfect. How many lines do you need? Three (so there will be at least three lines of text after the heading), plus one for the heading itself, two more for the spaces before the heading, and one last line for the space after the heading. So a real working version of the sample heading macro might have .ne 7 at the top.

This may seem like a lot of detail. (It gets worse.) If all you want to do is use a macro package to format documents, you may not want to learn how macros work. But, even if you have no intention of writing a macro yourself, it can be useful to understand how they work. (It can save you a lot of debugging time.) The more you know about the way macros are written, the easier it is to format a document.

What else might be done with a heading macro like .H1? Well, most often it would be combined with the .HE macro, so all you must type is

.H1 "heading"

Simple for the user, a bit harder for the macro writer.

To provide this kind of service, the .H1 macro would have to allow an argument. (An argument provides additional information for the macro or primitive—like the 7 specified for the .ne primitive. The section "Arguments," later in this chapter goes into more detail.)

An argument is coded in a special way. troff recognizes $1 as the first argument following the invocation of the macro. (This is a common UNIX convention.) There can be several (up to nine) arguments to a single macro (again, a common UNIX convention).

The code would look something like this:

.ne 7     \"need 7 spaces on page or start new page

.sp 2     \"space down 2 spaces before the heading

.ce 99    \"turn centering on

\f(HB\s+2\\$1\fP\s0

.         \"the font is Helvetica Bold, 2 points larger than 

.         \"the body type, the heading itself - $1 -and then return to

.         \"previous font and default point size

.ce 0     \"turn centering off

.sp       \"space down after the heading

NOTE: Don't worry about all the backslashes just yet. They are explained in the section "Arguments," too. For now, just concentrate on the code and the comments.

This macro is beginning to get complicated. (That means it's beginning to look like the kind of macro you'll see in a macro package.) But all it really says is the same old thing in a different way. UNIX is famous for providing 75 ways to do everything. troff code is no exception to this rule.

In the example above, the font change is accomplished by an escape sequence (\f(HB), instead of the .ft primitive. The point size is accomplished the same way (\s+2 instead of .ps), but note that a relative point size—the current point size plus 2—is specified. Next comes the heading itself, the first argument to .H1, specified as $1.

To return to the previous font, use the escape sequence \fP. In many cases, \f1 works just as well. \f1 returns you to the default body type. To return to your original point size, use \s0 (or \s-2). \s0 returns you to the default point size. Since you don't always know what this is, \s0 can be very useful.


TIP: When you use a heading macro, make a habit of surrounding the heading with quotation marks, even if it's a one-word heading. If you forget the quotes, your heading will be exactly one word long. troff simply disregards the rest of the line.

There's just one more concept you need: conditional execution (if statements). Details on conditional statements can be found later in this chapter in the section, "Conditional Statements." How would that work with the heading macro?

For one thing, you could change the macro name to plain .H and then use an argument to specify the heading level.

.H 1 "first-level heading"

.H 2 "second-level heading"

.

.

.

.H 7 "seventh-level heading"

And this is just what most macro packages do. They provide a general heading macro, and you supply the level and the text for the heading.

What would the code look like?

if \\$1 1 {     \"if the heading level is 1, do everything within the

.               \"curly braces; otherwise skip everything within them

.

.

.

\f(HB\s+2\\$fP\s0

.

.

.

}

Similarly,

if \\$1 2 {

for a second-level heading, and so on.

Number Registers

Number registers are locations that store values. They store only whole numbers which can, but need not, have units attached to them. There are three things you can do with a number register:

Number registers are used very frequently in macro definitions. They contain such information as line length, page offset, current font number, previous font number, current indent, current list item number, and so on.

For example, if you're formatting an automatic list (with mm), you would find the following information in number registers: current indent; current nesting level (That is, is the list a list-within-a-list?); item number; format of item number (that is, Arabic, uppercase roman numeral, lowercase roman numeral, uppercase alphabetic, or lowercase alphabetic).

Every time troff processes a .LI, the number registers that control these characteristics are interpolated. Some of them (the list item number, for example) are also incremented.

This information can be useful if you are formatting what I call a "discontinuous list" (a list that has ordinary text between two of the list items).

Before you insert the ordinary text, you must end the current list. When you want to continue the list, another .AL and .LI will start the list at 1. However, you want it to start at 5. If you know which number register stores this information, you can reset it.

To set a number register:

.nr a 0

.nr aa 0

.nr AA 1i

.nr b +1i

The units are optional—and very tricky. If you specify a unit of measurement (called a scaling factor) with a number register, the value is stored by troff in troff units (u), no matter what unit you specify. Thus, (for a 300dpi device) 1i is stored as 300u. When you add 1 to the register, you are adding 1 troff unit—unless you specify units. Note the following:

.nr x 21    \"has a value of 600u

.nr x +1    \"now has a value of 601u

.nr x 2i   \"has a value of 600u

.nr x +1   \"now has a value of 900u

You also have the option of specifying the increment/decrement to the register when you define it:

.nr b 10 1

.nr bb 0 2

Note that you do not specify whether 1 (in the first instance) or 2 (in the second instance) is to be an increment or a decrement. That is done when you interpolate the register.

To interpolate the contents of a number register:

\\na       \"one-character name

\\n(aa     \"two-character name

\\n+a      \"increments register b

\\n-(bb    \"decrements register bb

Number registers contain numbers. They are often used in arithmetic expressions:

.if \\na<1

.if \\na=\\nb

.if \\na+\\nb<\\nc

There is another arithmetic expression, common in troff, that looks unfinished:

.if \\na         \"if a is greater than 0

.if \\na-\\nb    \"if a minus b is greater than 0

.if !\\na        \"if a is not greater than 0

To increment or decrement a number register, use

.nr a \\na+1

.nr a \\na-1

(Note that you don't use an equal sign when you set a register.)

You can define a number register in terms of another number register (or two):

.nr z (\\nx+\\ny)

Toward the end of this chapter there are two tables of number registers predefined by troff. You will not want to use those names for your own number registers. If you are working with a macro package like ms or mm, however, you must also check the number registers used by your macro package because you don't want to overwrite the contents of the number register that numbers lists or stores indents.


Using Number Registers for Automatic Numbering

Every now and then you work on a document that cries out for automatic numbering. The examples that come to mind (because they're documents I've worked on) are storyboards for training materials. Each "board" represents a screen (in computer-based tutorials) or a viewgraph (in ordinary courses). Each board consists of graphics, text, and possibly animation or sound instructions.

I've found that you need to number the boards, both for your own convenience and to make things simple for your reviewers. I've also found that the order of the boards changes with depressing frequency.

If you explicitly number the boards, you have to explicitly change the numbers every time you switch 7 and 8 or 30 and 54. This is not fun and not an efficient way to spend your time.

You can use number registers to provide automatic numbers for the boards. (You can also write an awk program, but, if you don't know awk, this isn't an efficient solution either.)

To use number registers for automatic numbering, do the following:
  1. Select a number register that is not being used by troff or by your macro package. (For this example, I'm using vv.)

  2. Initialize the register at the top of your file: .nr vv 0.

  3. Whenever you want a number, interpolate vv : \n(vv+1. (Remember, you're doing this in your text file. You don't have to hide the register from troff.)

You can do this even more elegantly by defining an autoincrementing/decrementing number register:

.nr vv 0 1

The initial value in vv is 0; the autoincrement/decrement is 1. At this point, troff doesn't know whether you want the register contents to be incremented or decremented. You specify that when you interpolate the register.

\n+(vv

(The plus sign tells troff to increment the register.)

You can refine this to include a unit number, giving you compound folios, but this is practical only if you're using a macro package with chapter numbers (or some similar device like section or unit numbers) and you're using these numbers in your files.

Assuming you're using chapter numbers and the register for chapter numbers is cn, you can specify your board numbers like this:

.\n(cn-\n+(vv

If your chapter numbers are stored in a string called cn, do this:

\*(cn-\n+(vv

There is one disadvantage to using automatic numbering in this way. It's the same disadvantage you may have experienced with mm's automatic lists. When you look at your file, you have no idea what your current step (or board) is. And, if you have to refer to a previous step or board, you probably end up writing "Repeat Steps 1 through ???," printing your file, and inserting the correct numbers later.

Sometimes you need to remove registers. This is especially necessary if your macros use a large number of registers. It's a good idea to get into the habit of removing temporary registers as soon as you're done with them. To remove a register, use the .rr primitive:

.rr a     \"remove register a

Defined Strings

A defined string is a set of characters to which you assign a name. The string is always treated as a literal, and you cannot perform any arithmetic operation on it. You can, however, compare it to another string, or even compare the string "2" to the contents of a number register.

A string definition looks a lot like a macro definition:

.ds name value

.ds name "value that has a lot of separate words in it

.dsU U\s-1NIX\s0

.dsUU "UNIX Unleashed

String names consist of one or two characters. The names come from the same pool as macro names, so be careful to choose a unique name for your string. In the examples above, note that the .ds can (but does not have to be) followed by a space. Note also that you use only the opening quotation marks when your string consists of multiple words. (If you forget and include a closing quotation mark, it will be printed as part of the string).

To invoke the string:

\\*a      \"one-character name

\\*(aa    \"two-character name

Sometimes a string is a better choice than a number register. If you're dealing with alphabetic characters, a string may be your only choice.

Consider the following: You want to define something to hold the number of your current chapter. If you use a number register, you can increment these numbers very easily. You'll only have to set the value once, at the beginning of the book. Unless you have appendixes. If you have appendixes, you'll have to reset to 1 when you reach Appendix A, and then you'll have to translate that number into a letter.


NOTE: To use a number register for chapter numbers, use .af (alter format) to produce uppercase letters for your appendixes. .af recognizes the following formats:

1 Arabic numerals

i lowercase roman numerals

I uppercase roman numerals

a lowercase alphabetic characters

A uppercase alphabetic characters

To use the letter A in a compound page number (where the number register storing chapter numbers is cn), specify the following: .af cn A.

Perhaps a string would be simpler. You'll have to redefine the string at the beginning of each chapter, but you won't have to do any diddling.

Strings can be used as general purpose abbreviations, although this is not their primary purpose, nor even the best use of strings. A better use is to define a string containing the preliminary name of the product you are documenting. Then, when the marketing people finally decide to call their new brainchild "XYZZY Universal Widget," you don't have to do any searching or grepping to replace the temporary name. You can just redefine the string.

Define a string near the top of your file as the preliminary name of the product:

.ds Pn "Buzzy     \"code name for product

Remember that strings cannot share names with macros.

When the ugly duckling "Buzzy" becomes the swan "XYZZY Universal Widget," just change the definition:

.ds Pn "XYZZY Universal Widget     \"official name for product

Like macros, strings can have information appended. To add to a string, use the troff primitive .as. Although it's hard to imagine a use for this primitive, consider the following:

You are documenting three versions of the XYZZY Universal Widget in three separate documents. For the first document, you could add "Version 1.0" to the string:

.as Pn "(Version 1.0)

The other versions can be similarly identified in their documents as "Version 2.0" and "Version 3.0."


Listing Names of Existing Macros, Strings, and Number Registers

If you are using mm or ms and adding macros to either of these packages, you need to know what names (for macros, strings, and number registers) are available and what names have already been used.

To create a file called sortmac containing the macro names used in mm (assuming mm to be where it ought—namely in /usr/lib/tmac/tmac.m):

grep "^\.de" /usr/lib/tmac/tmac.m | sort | uniq > sortmac

(That code also assumes that you are executing grep from the directory in which you want sortmac to end up.)

Strings are listed pretty much the same way:

grep "^\.ds" /usr/lib/tmac/tmac.m | sort | uniq > sortstr

To list number registers defined in the mm macro package, execute the following sed script in the directory with the macros (/usr/lib/tmac):

sed -n -e 's/.*.nr *\(..\).*/\1/p' tmac.m | sort |uniq > $HOME/sortnum

The standard macro packages should all be in /usr/lib/tmac. The macro filenames are as follows:

tmac.m mm macros

tmac.s ms macros

tmac.e me macros (don't hold your breath looking for this one)

tmac.an man macros

Remember that troff and nroff—and each macro package—use predefined number registers, and these may not be set within the package.

Getting Started

To define a macro, you use the .de primitive and end the definition with two periods. A macro to indent the first line of a paragraph could be defined like this:

.dePX     \"macro to create indented paragraphs, no space between

.ti 3P

..

This is a very simple example. A "real" paragraph macro would check to make sure there was room for two or three lines and, if not, go to the next page. Nevertheless, this simple definition illustrates some important points. The macro name can consist of one or two characters. If you use a name that's already assigned to a macro, the definition in your text file overrides the definition in a macro package. The macro name follows the .de. It can be separated from the .de by a space, but a space is not necessary.


TIP: Although the space following the .de doesn't matter, consistency does. Some-day, you'll want to list and sort your macro definitions, and you can't sort them as easily unless you can rely on a space (or no space) between the .de and the macro name.

A macro definition can include troff primitives and other macros. A brief description of the macro is included on the definition line. This is crucial. You can forget more about macros in two weeks that you can learn in two years. Comment lavishly. And make sure you include a comment on the definition line to identify the macro. This helps when you grep for macro definitions and then sort them.

There is one more constraint on macro names: A macro cannot have the same name as a defined string. (Macros are perfectly happy sharing names with number registers, however.)


NOTE: By the way, there's no law against giving a macro the same names as a primitive. In fact, it sounds like an excellent April Fool. If you should be foolish enough to try this, bear in mind that the primitive will, for all intents and purposes, cease to exist. All that will remain will be your macro. So make it a good one.

If, instead of defining a new macro, you want to redefine an existing one, then you use the existing macro's name:

.deP

.ti 3P

..

If you redefine the .P macro, the old definition is no longer used (although it's still sitting there in the mm macro package). To return to the old definition, you must get rid of your new definition (delete it from the top of your file or delete the file containing the definition).

The benefit to writing a new macro with a new name is that the old definition is still usable. The drawback is that you're used to typing .P, so you'll probably forget to type .PX when you want to use your new macro.

In addition to defining a new macro and redefining an existing macro, you can remove a macro, and you can add to an existing macro.

Defining a Macro

To define a macro, use .de. You can end the definition, as shown above, with .., or you can use the delimiters of your choice, like this:

,deP!!

.ti 3P

!!

(I've never actually known anyone who used this form of the definition primitive, but it's there, if you want it. You know how UNIX always provides you with many roads to the same end.)

Once you have written your macro definition, you can add it to an existing macro package, add it to your own file of new macros, and source the file into your text files with .so, or just put the macro definition in your text file.


TIP: Creating an add-on file of macros is the least desirable way of incorporating your macros. If your current macro package needs that much help, someone should rewrite the package. The purpose of a macro package is to ensure a consistent look to documents prepared with the package. If everyone defines her or his own set of paragraph macros, this purpose is defeated.

Removing a Macro

To remove a macro, use the .rm primitive:

.rmP

Again, the space between the .rm and the macro name is optional.

This is not something that you do on a whim. Removing a macro requires serious, mature consideration. You might do it if you were experimenting with a better version of an existing macro—a list end macro (.LE) that left the right amount of space after it, for example. Your new, improved macro might be called .lE, or .Le. You could encourage people to use your new macro by removing .LE. (This is unlikely to be wise; you always forget to tell your department head who is working on a weekend on a crucial document, and—well, you can imagine the rest.) A safer way to use your new .Le might be to substitute the definition of .Le for .LE (after it's been tested and found to be truly superior), but to leave the .LE macro definition in the package and remove it at the end of the macro package file. (Even better, you could comment it out.)

Unless you are very knowledgeable about macros and are in charge of maintaining one or more macro packages, you will never remove a macro.

Renaming a Macro

To rename a macro, use the .rn primitive:

.rnP Pp

As usual, the space between the .rn and the macro name is optional. The space between the old name and the new name is not optional.

Renaming a macro is almost as serious as removing it. And it can be a great deal more complicated. For example, you might want to fix mm's list macro by adding some space after the .LE. You can do this by renaming. Here's what you do:

  1. Rename the .LE macro. (.rn LE Le)

  2. Define a new .LE.

    .deLE \"This is a new improved version of LE - adds space
    .Le
    .sp .5
    ..

  3. Invoke .LE as usual.

The new .LE (which is the old .LE plus a half-line space) takes the place of the old .LE.

You might think of using .rn so that you could include the .TH (table column heading) macro in a man file. (.TH is the basic title heading macro used in all man files.)

This seems to be a reasonable idea. If this sort of thing interests you, you can think through the process with me. (Otherwise, skip to "Adding to a Macro.")

The first thing to establish is the conditions for each of the .TH macros: When should .TH mean table heading, and when should it mean title?

That's easy to answer. You want the normal .TH to mean title all the time except following a .TS H. So when do you rename .TH—and which .TH do you rename? And, if you boldly put .rnTH Th in your file, to which .TH does it refer?

Think about that, and you'll begin to see that maybe .TH is not the ideal candidate for renaming.

Adding to a Macro

To add to a macro definition, use .am:

.amP

.ne 2     \"of course this isn't the right place for this request

..

(Yes, the space after the .am is optional.)

Adding to a macro, while not a task for beginners, is a lot more straightforward. .am is often used to collect information for a table of contents. Whenever the file has a .H (of any level, or of specified levels) you want to write that information into a data file which will be processed and turned into a TOC.

A Simple Example

Suppose you've found yourself in the unenviable position of typing a term paper for your child, spouse, or self. It's easy enough to double space the paper—just use .ls 2. But, if you're not using ms, you don't have an easy way of handling long quotes (which are supposed to be single spaced and indented from the left and right). What do you have to do every time you type a long quotation?

.in +1i

.ll -2i

.ls 1

And at the end of the quotation, you have to reverse that coding:

.in -1i

.ll +2i

.ls 2

Instead of typing those three lines, you could define a .Qb and a .Qe macro. Those two sets of three lines are the definitions. All you need to add is a .deQb (or .deQe) to start the macro definition and two dots to end it. If you want to refine the definition, you can add some space before and after the quotation and a .ne 2 so you don't get one line of the quotation at the bottom of page 5 and the other six lines on page 6:

.deQb

.sp

.ls 1

.ne 2

.in +1i

.ll -2i

..

.deOe

.br

.ls 2

.sp

.ne 2

.in -1i

.ll +2i

..

NOTE: There's no rule that says user-defined macros have to consist of an uppercase character followed by a lowercase character. It just makes things easier when you have guidelines.


troff Copy Mode

troff processes each file twice. The first time, called "copy mode," consists of copying without much interpretation. There is some interpretation, however. In copy mode, troff interprets the following immediately: the contents of number registers (\n); strings (\*); and arguments (\$1).

You do not want this to happen. troff will find \ns and \*s and \$1s in your macro package file—before the number register or string or argument has any meaningful contents. Fortunately, troff also interprets \\ as \, so you can "hide" these constructs by preceding them with an extra backslash. \\n copies as \n—which is what you want when the macro using that number register is invoked.

Note, however, that this rule does not apply to number registers invoked in your text file. When you invoke a number register in your text file, you want it interpreted then and there. So you don't use the extra backslash.

This seems simple. In fact, it is simple in theory. In practice, it's a horrible nuisance. A glance at a macro package like ms or mm will show you triple, even quad-ruple, backslashes. If you don't enjoy thinking through processes step by painful step, you will not enjoy this aspect of macro writing.

troff does not interpret ordinary escape sequences in copy mode. \h, \&, \d are all safe and do not have to be hidden.

During copy mode, troff eliminates comments following \".

Arguments

Macros, like other UNIX constructs, can take arguments. You specify an argument every time you type a heading after a .H 1 or a .NH. You specify arguments to primitives, too, like .sp .5 or .in +3P. In a macro definition, arguments are represented by \$1 through \$9. (Yes, you are limited to nine arguments.)

A couple of examples of arguments are:

.deCo     \"computer output (CW) font

\f(CW\\$1\fP

..

.dePi     \"paragraph indented amount specified by $1

.br

.ne 2

.ti \\$1

..

(Note that you must hide the argument (with the extra backslash) in order to survive copy mode.)

If you omit an argument, troff treats it as a null argument. In the case of the .Co macro, nothing at all would happen. In the case of the .Pi macro, the paragraph would not be indented. If you specify too many arguments (which would happen if you had .Co Press Enter in your file), troff merrily throws away the extras. You'd get "Press" in CW font; "Enter" would disappear. Use double quotation marks (.Co "Press Enter") to hide spaces from troff.

Conditional Statements

A conditional statement says, "Do this under certain (specified) conditions." It may add, "and under any other conditions, do that." You know the conditional statement as an "if" or an "if-else." The troff versions are .if (if) and .ie (if-else). The troff if has a different syntax from the shell if, but the principle is the same.

A simple if is coded like this:

.if condition simple-action

.if condition \{

complex-action

\}

The backslash-brace combinations delimit the actions to be taken when the condition is true.

The if-else works like this:

.ie condition simple-action

.el simple-action

.ie condition \{

complex-action

\}

.el \{

complex-action

\}

You use the conditional statement whenever you want to test for a condition. Is this an even page? Okay, then use the even-page footer. Are these files being nroffed (as opposed to troffed)? Okay, then make the next few lines bold instead of increasing the point size.

Believe it or not, troff has four built-in conditions to test for just those conditions:

o

current page is odd

e

current page is even

t

file is being formatted by troff

n

file is being formatted by nroff

The odd-even conditions simplify writing page header and footer macros. You can simply say:

.if o .tl '''%'     \"if odd - page no. on right

.if e .tl '%'''     \"if even - page no. on left

The single quotation marks delimit fields (left, center, and right). Thus, '''%' places the page number on the right side of the page and '%''' places it on the left side.

You could do the same thing with .ie:

.ie o .tl '''%'     \"if odd - page no. on right

.el .tl '%'''       \"else if even - page no. on left

The .if, even when it requires a seemingly endless list of conditions, is easier to use.

Suppose you are writing that heading macro discussed earlier in this chapter. You want to specify different spacing and different point sizes, depending on the level of the heading. You might start like this:

.deH

.if \\$1=1 \{

.bp

\s14\f(HB\\$1\fP\s0

.sp

\}

.if \\$1=2 \{

.sp 2

\s12\f(HB\\$1\fP\s0

.sp

|}

.

.

.

You can compare strings, but you use delimiters instead of an equal sign:

.if "\\$1"A"

.if '\\$2'Index'

TIP: The bell character, made by pressing Ctrl+G, is often used as a delimiter because it's not much use in a text file. It looks like ^G in a file, but don't be fooled. This is a non-printing character. Before you print out every macro file on your system, check them for ^Gs. Unless you want to spend a lot of time drawing little bells or printing ^G, try substituting another character for the bell before you print. (Try this on small portions of the file at a time.)

In addition to comparing numbers and strings, you can also test for inverse conditions. troff recognizes the exclamation mark (!) as the reverse of an expression, for example:

.if !o        \"same as .if e

.if !\\$1=0   \"if $1 is not equal to 0

.if !"\\$1""

(The last example above tests for a null argument.)

Be careful when you use !. It must precede the expression being reversed. For example, to check for an unequal condition, you must write .if !\\na=\\nb. You cannot write .if \\na!=\\nb.


Units of Measurement

troff allows you to use just about any units you want (except rods and kilometers):

i—inch p—point u—troff unit

c—centimeter m—em v—verticaspace

P—Pica n—en

Unfortunately, it is impossible to be 100 percent certain of the default units for any given primitive. For the most part, the troff default for horizontal measurements is the em and for vertical measurements is the line space. The nroff default for horizontal measurement is device-dependent, but it's usually 1/10 or 1/12 of an inch.

If you use arithmetic expressions, you will soon find that none of those defaults work the way they are supposed to. The culprit is the troff unit (u). A troff unit is about 1/300 of an inch (for a 300 dpi printer). Because this is a very much smaller unit than any of the others troff accepts, you can expect loony output from time to time. (Your text will print, but not on the paper.)

Always specify units.

If you want to divide 37 inches by 2, you are far safer doing the arithmetic in your head and specifying 18.5P than letting troff decide how to process 37P/2. troff will not do what you expect. troff will divide 37 picas by 2 ems. You will not like the result. If, in desperation, you try 37/2P, you will still not like the result because troff will divide 37 ems by 2 picas. You have to specify 37P/2u. The u acts as a sort of pacifier and lets troff perform the arithmetic correctly.

When you're unsure of the units, use troff units. It's sort of like adding backslashes. A few more will probably fix the problem.

Arithmetic and Logical Expressions

As you see, conditional statements are often combined with arithmetic expressions. You can also use logical expressions. troff understands all of the following:



+ - * /


plus, minus, multiplied by, divided by


%


modulo


> <


greater than, less than


>= <=


greater than or equal to, less than or equal to


=


equal (== is a synonym)


&


AND


:


OR

Unlike other UNIX programs, troff has no notion of precedence. An expression like \\$1+\\$2*\\$3-\\$4 is evaluated strictly from left to right. Thus, to troff, 2+3*5-10\2 equals 7.5. This is hard to get used to and easy to forget.

Always specify units.

Diversions

Diversions let you store text in a particular location (actually a macro that you define), from which the text can be retrieved when you need it. Diversions are used in the "keep" macros and in footnotes.

The diversion command is .di followed by the name of the macro in which the ensuing text is to be stored. A diversion is ended by .di on a line by itself.

Diverted text is processed (formatted) before it is stored, so when you want to print the stored text, all you have to do is specify the macro name. Since there is virtually no limit either to the number of diversions you can have in a file or to the length of any diversion, you can use diversions to store repeated text.


NOTE: Storing repeated text in a diversion isn't necessarily a good idea. You can avoid typing the repeated text just as easily by putting it in a file and reading that file into your text file.

For example, suppose the following text is repeated many, many times in your document:

.AL 1

,LI

Log in as root.

.LI

Invoke the UNIX system administrative menu by

typing \f(CWsysadm\fP and pressing Enter.

.P

The system administrative menu is displayed.

.LI

Select \f(CWEquine Systems\fP by highlighting

the line and pressing Enter.

.P

The Equine Systems menu is displayed

You could store this text in .Em (for Equine Menu) by prefacing it with .diEm and ending it with .di.

Note that your diversion contains an unterminated list. If this is likely to cause problems, add .LE to the diverted text.

To print the Equine Systems text, just put .Em in your file.

In addition to .di, there is a .da (diversion append) primitive that works like .am. .da is used to add text to an existing diversion. It can be used over and over, each time adding more text to the diversion. (To overwrite the text in a given diversion, just define it again with a .diEm.) The .am primitive can be used, like .am, to create TOC data.

You can even have a diversion within a diversion. The "inside" diversion can be used on its own, as well.

Traps

troff provides several kinds of traps: page traps (.wh and .ch); diversion traps (.dt); and input line traps (.it).

Page traps usually invoke macros. For example, when troff gets near the bottom of a page, the trap that produces the page footer is sprung. A simple illustration of this is the following.

Suppose you wanted to print the current date one inch from the bottom of every page in your document. Use the .wh primitive:

.deDa                   \"define date macro

\\n(mo/\\n(dy/18\\n(yr  \"set date

..

.wh 1i Da               \"set the trap

The order of the arguments is important.

To remove this kind of trap, invoke it with the position, but without the macro name: .wh 1i.

The .ch primitive changes a trap. If you wanted the date an inch from the bottom of the page on page 1 of your document, but an inch and a half from the bottom of the page on all subsequent pages, you could use .ch Da 1.5i.

(Note that the argument order is different.)

Diversion traps are set with the .dt primitive, for example:

.dt 1i Xx

This diversion trap, set within the diversion, invokes the .Xx macro when (if) the diversion comes within one inch of the bottom of the page.

Input text traps are set with the .it primitive. This trap is activated after a specified number of lines in your text file.

There is a fourth kind of trap, though it isn't usually thought of as a trap. This is the end macro (.em) primitive. .em is activated automatically at the end of your text file. It can be used to print overflow footnotes, TOCs, bibliographies, etc.

Environments

The .ev (environment) primitive gives you the ability to switch to a completely new and independent set of parameters, such as line length, point size, font, and so forth. It lets you return to your original set of parameters just as easily. This process is known as environment switching. The concept is used in page headers, for example, where the font and point size are always the same—and always different from the font and point size in the rest of the document.

Three environments are available: ev 0 (the normal, or default, environment); ev 1; and ev 2.

To switch from the normal environment, just enter .ev 1 or .ev 2 on a line by itself and specify the new parameters. These new parameters will be in effect until you specify a different environment. To return to your normal environment, use .ev or .ev 0.

You could use environment switching instead of writing the .Qb and .Qe macros. Here's how it would work:

.ev 1     \"long quote begins

.sp

.ls 1

.in +1i

.ll -2i

text of quotation

.sp

.ev

Environments are often used with diversions or with footnotes where the text is set in a smaller point size than body type. It is to accommodate diversions within diversions that the third environment is provided.

Debugging

Debugging macros is slow and often painful. If you have a version of troff that includes a trace option, use it—but be warned: It produces miles of paper. If you don't have a trace option, you can use the .tm primitive (for terminal message) to print the value of a number register at certain points in your file. The value is sent to standard error, which is probably your screen. Use .tm like this:

.tm Before calling the Xx macro, the value of xX is \n(xX.

.Xx

.tm After calling the Xx macro, the value of xX is \n(xX.

(Note that you don't hide the number register from copy mode because you put these lines right in your text file. Remember to delete them before the document goes to the printer.)

troff Output

Sometimes you have to look at troff output. It's not a pretty sight, but after the first few files, it begins to make sense. Here's the troff code produced by a file with two words in it: UNIX Unleashed.

(By the way, use troff -o > outputfile to produce this output.)

x T post

x res 720 1 1

x init

v0

p1

x font 1 R

x font 2 I

x font 3 B

x font 4 BI

x font 5 CW

x font 6 H

x font 7 HI

x font 8 HB

x font 9 S1

x font 10 S

s10

f1

H720

V120

cU

72N72I33Xw97U72n50l28e44as39h50e44dn120 0

x trailer

v7920

x stop

If you look hard, you can pick out the text in the long line. The numbers are horizontal motions reflecting the width of the letters. You can also see where the font positions are defined. The s10 on a line by itself is the point size. f1 is the font in position 1 (in this case, Times-Roman). The H and V numbers following the font definition specify the starting horizontal and vertical position on the page.

PostScript Output

PostScript output is a little easier to read, but the set-up lines are endless. Where UNIX Unleashed generates 24 lines of troff code, the same two words generate more than 800 lines of PostScript code. The significant lines are at the beginning and the end. The last 17 lines of the PostScript file are as follows:

setup

2 setdecoding

%%EndSetUp

%%Page: 1 1

/saveobj save def

mark

1 pagesetup

10 R f

(\255 1 \255)2 166 1 2797 490 t

(UNIX Uleashed) 1 695 1 720 960 t

cleartomark

showpage

saveobj restore

%%EndPage: 1 1\%%Trailer

done

%%Pages: 1

%%DocumentFonts: Times-Roman

Font and point size are specified as 10 R f (10 point Roman). Text is enclosed in parentheses (which makes it easy to find). The showpage is crucial. Every page in your document needs a showpage in the PostScript file. Occasionally, PostScript output is truncated and the last showpage is lost. No showpage means no printed page.

Hints for Creating a Macro Package

The following suggestions may be helpful. Most of them are very obvious, but, since I've made all these mistakes myself at one time or another, I pass on this advice:

Starting from scratch is necessary if you intend to sell your macro package. If you just want to provide a nice format for your group, use ms or mm as a basis. Remove all the macros you don't need and add the ones you do need (lists from mm, if you're using ms, boxes from ms, if you're using mm). Don't reinvent the wheel. Copy, steal, and plagiarize.

Make sure to include autoindexing and automatic generation of master and chapter TOCs.

Write a format script for your users to send their files to the printer, preferably one that will prompt for options if they aren't given on the command line.

Write—and use—a test file that includes all the difficult macros you can think of (lists, tables, headers and footers, etc.).

Try to enlist one or two reliable friends to pre-test your package.

You'll never be able to anticipate all the weird things users do to macro packages. Start with a reasonable selection. Save lists within tables within diversions within lists for later.

Don't replace your current macro package with the new one while people are working. Do it at night or after sufficient warning.

Make sure the old macro package is accessible to your users (but not easily accessible, or they won't use your new one).

Don't use PostScript shading if most of your documents are Xeroxed rather than typeset. Copiers wreak havoc on shading. Also, there's always one person in your group who doesn't use a PostScript printer.

Beyond Macro Packages

If you've gone to the trouble of creating an entire macro package, you want it to be easy to use, and you want it to do everything your users could possible desire. This means that you should provide users with a format script. Although actual programs for these tools are beyond the scope of this chapter, the following hints should get you started:

Predefined Number Registers (nroff/troff)

Table 26.1 lists the number registers that are predefined by troff. You can change the contents of these registers, but, whatever you do, don't use these names for your own number registers.

Register Name


Description


%

current page number

ct

character type (set by \w)

dl

(maximum) width of last completed

dn

height (vertical size) of last completed diversion

dw

current day of the week (1-7)

dy

current day of the month (1-31)

ln

output line number

mo

current month (1-12)

nl

vertical position of last printed baseline

sb

depth of string below baseline (generated by \w)

st

height of string above baseline (generated by \w)

yr

last 2 digits of current year

Predefined Read-Only Number Registers (nroff/troff)

Table 26.2 lists the read-only number registers that are predefined by troff. You cannot change the contents of these registers, but you can inspect them and use their contents in condition statements and arithmetic expressions.

Register Name


Description


$$

process id of troff or nroff

.$

number of arguments available at the current macro level

.a

post-line extra line-space most recently used in \x'N'

.A

set to 1 in troff if -a option used; always 1 in nroff

.b

emboldening level

.c

number of lines read from current input file

.d

current vertical place in current diversion; equal to n1 if no diversion

.f

current font number

.F

current input filename

.h

text baseline high-water mark on current page or diversion

.H

available horizontal resolution in basic (troff) units

.I

current indent

.j

current ad mode

.k

current output horizontal position

.l

current line length

.L

current ls value

.n

length of text portion on previous output line

.o

current page offset

.p

current page length

.R

number of unused number registers

.T

set to 1 in nroff, if -T option used; always 0 in troff

.s

current point size

.t

distance to the next trap

.u

equal to 1 in fill mode and 0 in no-fill mode

.v

current vertical line spacing

.V

available vertical resolution in basic (troff) units

.w

width of previous character

.x

reserved version-dependent register

.y

reserved version-dependent register

.z

name of current diversion

Summary

Writing one or two macros can be fun and can greatly simplify your life. Start small and easy—no number registers defined by other number registers, no renaming, and (if you can manage it) no traps or diversions. Writing macros helps to understand macro processing, which makes you a more valuable employee.

Writing an entire macro package is a long, difficult process, one that continues for months, even years, after you write that last macro, because someday some user will combine a couple of macros in ways you never dreamed of. Don't write a macro package unless you're prepared to maintain it, provide documentation and user support, and modify it.

Previous Page TOC Next Page Home