MC logo

CSc 231 Assignment 4

^  CSc 231 Perl Assignments

70 pts


Due: April 30

A macro processor reads a file containing macro definitions and uses, and replaces the uses with the definitions. A macro is a named replacement text, usually with parameters to substitute. For instance, if assignment four reads this input:

This is the first line.
!define frank=Mr. Frank #1 Stein
For more information, see @frank(N.),
@frank("Steiney"), Jr., or @frank(Furter), Esq.
It should output this:
This is the first line.
For more information, see Mr. Frank N. Stein,
Mr. Frank "Steiney" Stein, Jr., or Mr. Frank Furter Stein, Esq.
As you can see, the name frank is defined as the given string, and occurrences of @frank are replaced with the string. The @frank identifier is followed by an argument list. The arguments are separated by commas and replace items of the form #nnn in the macro definition, where nnn is the number of the parameter, starting from 1.

General Operation

Your program should do the following. Begin reading from either standard input, or, if a file name is given on the command line, open it and read from there instead. All output is to standard output, except error messages to STDOUT.

Operation is essentially line-oriented. Input lines starting with ! control the program. There are two types of these (see below). These are read by the program, obeyed, and do not appear in the output. All other lines should be printed on standard output, with any macro calls expanded.


Directive lines start with an ! as the first non-blank character. There are two directives: include and define. An include line follows the ! with the word include, then one or more spaces, then a file name. The program will read read from this file until EOF, then return to reading from the file which included it. If the file will not open, the program should print a message on standard output and continue processing the original file.

The second directive is define. The keyword follows the !, then one or more spaces, then the name of the macro, then one or more spaces, then an equal sign. The remainder of the line following the equal sign is the definition. This allows the macro definition to have spaces leading, trailing, or internal. The program remembers the definition for use on expansion lines. If the macro has already been defined, this new definition replaces it.

Macro names follow the C identifier rules: They must start with a letter or underscore, and proceed with letters, underscore, or digits. Names are case-sensitive.

If a line starts with ! as the first non-blank, but he directive name is not recognized, or the line does not have the right form, print an error message and discard the input line.


Any input line which does not start with ! is printed on standard output after macro expansion. Macro expansion follows these rules.
  1. Input is processed one line at a time. That means that a macro use, including all arguments, must be within a single line. There may be several macro uses on the same line, mixed with ordinary text.
  2. A macro use starts with the character @, followed immediately by a defined macro name, followed immediately by a list of arguments enclosed in parentheses. The argument list must be present, even if it is empty. Anything that sort of looks like a macro use, but does not follow these rules, is not a macro use. That means the text is simply output unchanged. This includes uses of undefined macro names, or when the parameter list or the @ is missing.
  3. Argument lists may contain nested parentheses, and they must be balanced. Arguments are separated by commas when those commas are not contained in nested parentheses. The purpose of this rule is to allow macro uses to be sent as parameters. For instance,
    sends for arguments to the macro @fred. Note that this rule depends on parenthesis nesting, even if the parens are not part of another invocation.

    Note that parameters may be empty, and may contain spaces.

  4. Macro uses are replaced by the macro definitions, with certain parameter replacements. Within the definition, the following parameter replacements occur:
    1. The construct #nnn, where nnn is a positive integer, is replaced with the nnn-th argument, numbering starting from 1. If there are not that many arguments, #nnn is replaced with the empty string.
    2. The construct #* is replaced with all the arguments, separated by commas.
    3. The construct #- is replaced with all the arguments but the first one, separated by commas.
  5. When each line is read in, macro expansion is applied repeatedly until no more uses exist. Since it is possible for a macro to recur infinitely, you may place a large limit on the number of replacements for any given line. This will keep your program from looping.
  6. Evaluation is from the outside in. Macros uses nested as arguments to other uses are treated as (unexpanded) text in the replacement of the outer macro. Then, they will be expanded when they appear. For instance,
    !define JOE=--#1--
    !define ALICE=::#1:#1::
    @ALICE(go @JOE(23))
    The macro @ALICE is expanded first, producing:
    ::go @JOE(23):go @JOE(23)::
    Then, each @JOE is expanded giving
    ::go --23--:go --23--::
  7. There is exactly one built-in macro which will be provided by directly by your program. The construct
    should be replaced with eb if a and b are the same string, and replaced with neb otherwise.
I will post some more examples later.


When your program is properly indented, commented, and works, submit over the web here.