Implementing Macro Processor in C

A macro processor is a system program that expands macros in a source file before the code is assembled or compiled. A macro is a named block of assembly statements; every time its name appears in the program the processor substitutes the full block in place of the call. This eliminates repetition and makes large assembly programs easier to maintain.

This C implementation reads an assembly program from MACIN.TXT, detects MACRO / MEND delimiters to populate a Macro Definition Table (MDT), and then expands every macro call by inline-substituting the stored body. The expanded output is written to MACOUT.TXT and the raw MDT is saved to MDT.TXT.

C Code

#include <stdio.h>
#include <conio.h>
#include <string.h>

/* MDT entry: one line inside a macro body */
struct mdt {
    char lab[10];   /* label field  */
    char opc[10];   /* opcode field */
    char oper[10];  /* operand field */
} d[10];            /* up to 10 lines per macro body */

void main() {
    char label[10], opcode[10], operand[10];
    char macroname[10];   /* name of the most recently defined macro */
    int  i, lines;        /* lines = number of statements in macro body */
    FILE *f1, *f2, *f3;
    clrscr();

    f1 = fopen("MACIN.txt",  "r");   /* input assembly program          */
    f2 = fopen("MACOUT.txt", "w");   /* expanded output program         */
    f3 = fopen("MDT.txt",    "w");   /* macro definition table (debug)  */

    /* Read the first token triple from the input file */
    fscanf(f1, "%s %s %s", label, opcode, operand);

    /* Process until END directive is reached */
    while (strcmp(opcode, "END") != 0) {

        if (strcmp(opcode, "MACRO") == 0) {
            /* -----------------------------------------------
               Macro definition found:
               label holds the macro name; body follows until MEND
               ----------------------------------------------- */
            strcpy(macroname, label);
            fscanf(f1, "%s%s%s", label, opcode, operand);
            lines = 0;

            while (strcmp(opcode, "MEND") != 0) {
                /* Store each body line in MDT array and MDT file */
                fprintf(f3, "%s\t%s\t%s\n", label, opcode, operand);
                strcpy(d[lines].lab,  label);
                strcpy(d[lines].opc,  opcode);
                strcpy(d[lines].oper, operand);
                fscanf(f1, "%s %s %s", label, opcode, operand);
                lines++;   /* count statements in the macro body */
            }

        } else if (strcmp(opcode, macroname) == 0) {
            /* -----------------------------------------------
               Macro call found:
               expand by writing all stored body lines to output
               ----------------------------------------------- */
            printf("lines=%d\n", lines);
            for (i = 0; i < lines; i++) {
                fprintf(f2, "%s\t%s\t%s\n", d[i].lab, d[i].opc, d[i].oper);
                printf("DLAB=%s\nDOPC=%s\nDOPER=%s\n",
                       d[i].lab, d[i].opc, d[i].oper);
            }

        } else {
            /* Not a macro definition or call: copy line as-is */
            fprintf(f2, "%s\t%s\t%s\n", label, opcode, operand);
        }

        /* Advance to the next line */
        fscanf(f1, "%s%s%s", label, opcode, operand);
    }

    /* Write the final END line to the output */
    fprintf(f2, "%s\t%s\t%s\n", label, opcode, operand);

    fclose(f1);
    fclose(f2);
    fclose(f3);
    printf("FINISHED");
    getch();
}

Input File (MACIN.TXT)

CALC   START  1000
SUM    MACRO  **
**     LDA    #5
**     ADD    #10
**     STA    2000
**     MEND   **
**     LDA    LENGTH
**     COMP   ZERO
**     JEQ    LOOP
**     SUM    **
LENGTH WORD   S
ZERO   WORD   S
LOOP   SUM    **
**     END    **

How the Code Works

  1. File setup: Three files are opened — MACIN.TXT for reading the source, MACOUT.TXT for writing the expanded output, and MDT.TXT for logging the macro definition table.
  2. Macro definition detection: When the parser sees MACRO in the opcode field, the label on that line is saved as the macro name. Every subsequent line up to MEND is stored in the d[] MDT array and also written to MDT.TXT. The variable lines counts how many body statements were stored.
  3. Macro call expansion: When the opcode matches macroname, the processor iterates over all lines entries in d[] and writes each one to MACOUT.TXT, effectively inlining the macro body at every call site.
  4. Pass-through lines: Any line that is neither a macro definition nor a macro call is copied unchanged to MACOUT.TXT.
  5. Termination: The loop exits when END is encountered; the END line itself is then appended to the output.

Output Files

Macro Definition Table (MDT.TXT)

**	LDA	#5
**	ADD	#10
**	STA	2000

Expanded Output (MACOUT.TXT)

CALC	START	1000
**	LDA	LENGTH
**	COMP	ZERO
**	JEQ	LOOP
**	LDA	#5
**	ADD	#10
**	STA	2000
LENGTH	WORD	S
ZERO	WORD	S
**	LDA	#5
**	ADD	#10
**	STA	2000
**	END	**

Output Explanation

  1. CALC START 1000 is not a macro definition or call, so it is copied verbatim to the output.
  2. The SUM MACRO block is consumed during definition and does not appear in the output. Its three body lines (LDA #5, ADD #10, STA 2000) are stored in the MDT.
  3. Lines LDA LENGTH, COMP ZERO, JEQ LOOP are regular instructions; they are passed through unchanged.
  4. First SUM call (line 10): The macro is expanded inline — the three MDT lines are inserted, replacing the single SUM ** call.
  5. LENGTH WORD S and ZERO WORD S are data declarations; they are copied unchanged.
  6. Second SUM call (LOOP SUM **): The same three MDT lines are expanded again, demonstrating that a macro can be called any number of times.
  7. END is appended as the final line of the expanded program.

See Also

Conclusion

This single-pass macro processor captures the essential idea behind all macro systems: define once, expand everywhere. The MDT array plays the same role as the Macro Definition Table in a full assembler, and the expansion loop mirrors what production assemblers do when they encounter macro calls. Pairing this processor with the multi-pass assembler gives you a complete front-to-back pipeline: the macro processor runs first to eliminate all macro calls, and the assembler then translates the expanded source into object code.

4 thoughts on “Implementing Macro Processor in C”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.