Implementing Multi-pass Assembler in C

An assembler is a system software tool that translates an assembly language program (ALP) into machine code. A multi-pass assembler does this translation in two distinct passes over the source program. In Pass 1, it scans the ALP to build a Symbol Table (ST) that maps all labels and symbols to their memory addresses. In Pass 2, it uses the symbol table along with a Machine Opcode Table (MOT) and a Pseudo Opcode Table (POT) to generate the final object code.

This C implementation reads the assembly program from alp.txt, the machine opcode table from mot.txt, and the pseudo opcode table from pot.txt. The generated object code is written to OUTPUTNEW.txt and the symbol table is saved to SymT.txt.

C Code

#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <stdlib.h>
#define NULL 0

/* -------------------------------------------------------
   Data structures used by the assembler
   ------------------------------------------------------- */

/* MOT entry: mnemonic, opcode, number of operands, length of instruction */
struct mot {
    char mnemo[6];
    int opcode, no_opnds, loi;
};

/* POT entry: pseudo-opcode name and operand count */
struct pot {
    char ps_opcode[6];
    int opnds;
};

/* ALP entry: one line of assembly (label, mnemonic, operand) */
struct alp {
    char label[7], mne[7], opnd[30];
};

/* Symbol table entry: symbol name, type, and resolved address */
struct st {
    char sym[10], type[10];
    int sym_add;
};

/* Object file entry: location counter, machine code, operand address */
struct of {
    int lc, mac_code, add;
};

/* Constants table: address and value pairs */
struct addnvalue {
    int addr, value;
};

struct addnvalue add_const[15];
struct mot m[12];     /* MOT: up to 12 machine opcodes */
struct pot p[9];      /* POT: up to 9 pseudo opcodes   */
struct alp a[25];     /* ALP: up to 25 source lines    */
struct st  s[15];     /* Symbol table: up to 15 symbols */
struct of  o[25];     /* Object code: up to 25 entries  */

/* Function prototypes */
int  searchmot(char str[]);
int  searchpot(char str[]);
int  searchst(char str[]);
int  str2num(char str[]);
int  getvalue(int add);
void pass1();
void pass2();

int motptr, poptr, endpos, nmot, npot, nacode, nadata;
int lc, error, flagm, flagp, naot, nast, stptr, firstloc, flagst;

/* -------------------------------------------------------
   main(): loads MOT, POT, ALP from files then runs passes
   ------------------------------------------------------- */
void main() {
    int i, j, k, len;
    char str[8], str1[8], str2[30], ch;
    FILE *fp;
    clrscr();

    /* --- Load MOT table from mot.txt --- */
    fp = fopen("mot.txt", "r");
    if (fp == NULL) {
        printf("'mot.txt' File Does not exist!!");
        exit(0);
    } else {
        i = 0;
        while (!feof(fp)) {
            /* Each line: mnemonic opcode num_operands length */
            fscanf(fp, "%s %d %d %d",
                   &m[i].mnemo, &m[i].opcode, &m[i].no_opnds, &m[i].loi);
            i++;
        }
        nmot = i;   /* total MOT entries */
        fclose(fp);
    }

    /* --- Load POT table from pot.txt --- */
    fp = fopen("pot.txt", "r");
    if (fp == NULL) {
        printf("'pot.txt' File does not exist!");
        exit(0);
    } else {
        i = 0;
        while (!feof(fp)) {
            /* Each line: pseudo-opcode num_operands */
            fscanf(fp, "%s %d", &p[i].ps_opcode, &p[i].opnds);
            i++;
        }
        npot = i;   /* total POT entries */
        fclose(fp);
    }

    /* --- Load ALP from alp.txt --- */
    fp = fopen("alp.txt", "r");
    if (fp == NULL) {
        printf("'alp.txt' File does not existn");
    } else {
        i = 0;
        do {
            fscanf(fp, "%s", str);
            flagm = 0; flagp = 0;
            len = strlen(str);

            /* Check if the token ends with ':' (it is a label) */
            if (str[len - 1] == ':') {
                for (j = 0; j < len - 1; j++)
                    a[i].label[j] = str[j];   /* strip the colon */
                fscanf(fp, "%s", str);         /* read next token (mnemonic) */
            } else {
                strcpy(a[i].label, NULL);      /* no label on this line */
            }

            flagm = searchmot(str);
            flagp = searchpot(str);

            if (flagm == 1 || flagp == 1) {
                if (strcmp(str, "STOP") == 0) {
                    strcpy(a[i].mne, str);
                    strcpy(a[i].opnd, NULL);
                } else if (strcmp(str, "ENDP") == 0) {
                    strcpy(a[i].mne, str);
                    strcpy(a[i].opnd, NULL);
                    endpos = i;
                    break;              /* code segment ends here */
                } else {
                    strcpy(a[i].mne, str);
                    fscanf(fp, "%s", str);
                    strcpy(a[i].opnd, str);    /* read operand */
                }
                i++;
            }
        } while (!feof(fp));

        nacode = endpos;   /* index of last code-segment line */
        i = endpos + 1;

        /* Read data segment (after ENDP) */
        while (!feof(fp)) {
            fscanf(fp, "%s %s %s", &a[i].label, &a[i].mne, &a[i].opnd);
            if (strcmp(a[i].mne, "END") == 0) {
                strcpy(a[i].opnd, NULL);
                break;
            }
            i++;
        }
        nadata = i;   /* total ALP lines processed */
    }
    fclose(fp);

    /* Determine end of non-'?' operand entries */
    i = 0;
    while (i != nadata) {
        if (strcmp(a[i].opnd, "?") != 0) { naot = i; i++; }
        else break;
    }

    pass1();   /* Build symbol table */

    /* Save symbol table to SymT.txt */
    fp = fopen("SymT.txt", "w+");
    j = 0;
    while (strcmp(s[j].sym, "END") != 0) {
        fputs(s[j].sym,  fp);
        fputs(s[j].type, fp);
        fprintf(fp, "%dn", s[j].sym_add);
        j++;
    }
    fclose(fp);

    if (error == 0)
        pass2();   /* Generate object code only if Pass 1 succeeded */

    getch();
}

/* -------------------------------------------------------
   searchmot(): returns 1 if str found in MOT, sets motptr
   ------------------------------------------------------- */
int searchmot(char str1[]) {
    int i, x;
    for (i = 0; i < nmot; i++) {
        x = strcmp(m[i].mnemo, str1);
        if (x == 0) { motptr = i; return 1; }
    }
    return 0;
}

/* -------------------------------------------------------
   searchpot(): returns 1 if str found in POT, sets poptr
   ------------------------------------------------------- */
int searchpot(char str1[]) {
    int i, y;
    for (i = 0; i < npot; i++) {
        y = strcmp(p[i].ps_opcode, str1);
        if (y == 0) { poptr = i; return 1; }
    }
    return 0;
}

/* -------------------------------------------------------
   searchst(): returns 1 if str found in Symbol Table
   ------------------------------------------------------- */
int searchst(char str1[]) {
    int i, z;
    for (i = 0; i < 15; i++) {
        z = strcmp(s[i].sym, str1);
        if (z == 0) { stptr = i; return 1; }
    }
    return 0;
}

/* -------------------------------------------------------
   pass1(): first pass - builds the Symbol Table
   Assigns addresses to every label, constant, and variable
   ------------------------------------------------------- */
void pass1() {
    int i, k, y, add, len, j, num = 0;
    FILE *fp;
    char str2[30];
    motptr = 0; poptr = 0; stptr = 0;
    lc = 0; error = 0; i = 0; k = 0; add = 0;

    while (i != nadata) {
        if (strcmp(a[i].mne, "END") == 0) {
            if (error == 0) pass2();
            else printf("\nUnsuccessful Assembly!!!");
        }
        /* Handle START / ORG: set location counter */
        else if (strcmp(a[i].mne, "START") == 0 || strcmp(a[i].mne, "ORG") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                if (strcmp(a[i].opnd, "NULL") == 0)
                    lc = 0;
                else { firstloc = atoi(a[i].opnd); lc = firstloc; }
            }
        }
        /* Handle ENDP: reset all table pointers */
        else if (strcmp(a[i].mne, "ENDP") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) { motptr = 0; poptr = 0; stptr = 0; }
        }
        /* Handle DB: 1-byte variable */
        else if (strcmp(a[i].mne, "DB") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchst(a[i].label);
                if (flagst == 1) {
                    s[stptr].sym_add = lc;
                    if (strcmp(a[i].opnd, "?") == 0) {
                        strcpy(s[stptr].type, "VAR");
                        lc += 1;
                    }
                }
            }
        }
        /* Handle DW: 2-byte variable or constant */
        else if (strcmp(a[i].mne, "DW") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchst(a[i].label);
                if (flagst == 1) {
                    s[stptr].sym_add = lc;
                    if (strcmp(a[i].opnd, "?") == 0) {
                        strcpy(s[k].type, "VAR"); lc += 2;
                    } else {
                        num = atoi(a[i].opnd);
                        s[stptr].sym_add     = lc;
                        add_const[add].addr  = lc;
                        add_const[add].value = num;
                        strcpy(s[k].type, "CONST");
                        add++; lc += 2;
                    }
                }
            }
        }
        /* Handle CONST directive */
        else if (strcmp(a[i].mne, "CONST") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchst(a[i].label);
                if (flagst == 1) {
                    s[stptr].sym_add     = lc;
                    strcpy(s[stptr].type, "CONST");
                    add_const[add].addr  = lc;
                    add_const[add].value = atoi(a[i].opnd);
                    add++; lc += 1;
                }
            }
        }
        /* Label-only line: record label address */
        else if (strcmp(a[i].label, "NULL") != 0 &&
                 strcmp(a[i].mne,   "NULL") == 0 &&
                 strcmp(a[i].opnd,  "NULL") == 0) {
            y = searchst(a[i].label);
            if (y == 0) {
                strcpy(s[k].sym,  a[i].label);
                strcpy(s[k].type, "LABEL");
                s[k].sym_add = lc; k++;
            } else {
                printf("\nDuplicate Label!!");
                error = 1;
            }
        }
        /* Instruction without label and without operand */
        else if (strcmp(a[i].label, "NULL") == 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") == 0) {
            flagm = searchmot(a[i].mne);
            lc += m[motptr].loi;
        }
        /* Instruction without label but with operand */
        else if (strcmp(a[i].label, "NULL") == 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") != 0) {
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                flagst = searchst(a[i].opnd);
                if (flagst == 0) {
                    /* Forward reference: add as IDN (identifier, address unknown yet) */
                    strcpy(s[k].sym,  a[i].opnd);
                    strcpy(s[k].type, "IDN");
                    s[k].sym_add = NULL; k++;
                }
                lc += m[motptr].loi;
            }
        }
        /* Instruction with label but no operand */
        else if (strcmp(a[i].label, "NULL") != 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") == 0) {
            flagst = searchst(a[i].label);
            if (flagst == 0) {
                strcpy(s[k].sym,  a[i].label);
                strcpy(s[k].type, "LABEL");
                s[k].sym_add = lc;
            } else {
                /* Symbol seen before as IDN; now resolve it as LABEL */
                strcpy(str2, s[stptr].type);
                if (strcmp(str2, "IDN") == 0) {
                    strcpy(s[stptr].type, "LABEL");
                    s[stptr].sym_add = lc;
                }
            }
            flagm = searchmot(a[i].mne);
            if (flagm == 1) lc += m[motptr].loi;
            else error = 1;
        }
        /* Full instruction: label + mnemonic + operand */
        else if (strcmp(a[i].label, "NULL") != 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") != 0) {
            flagst = searchst(a[i].label);
            if (flagst == 0) {
                strcpy(s[k].sym,  a[i].label);
                strcpy(s[k].type, "LABEL");
                s[k].sym_add = lc; k++;
            } else {
                if (strcmp(s[stptr].type, "LABEL") != 0) {
                    strcpy(s[stptr].type, "LABEL");
                    s[k].sym_add = lc;
                }
            }
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                flagst = searchst(a[i].opnd);
                if (flagst == 0) {
                    strcpy(s[k].sym,  a[i].opnd);
                    strcpy(s[k].type, "IDN");
                    s[k].sym_add = lc; k++;
                }
                lc += m[motptr].loi;
            }
        }
        i++;
    }
    nast = k - 1;
}

/* -------------------------------------------------------
   pass2(): second pass - generates object code
   Uses MOT opcodes and resolved symbol addresses from ST
   ------------------------------------------------------- */
void pass2() {
    FILE *fp;
    int i, k;
    fp = fopen("OUTPUTNEW.txt", "w+");
    motptr = 0; poptr = 0; stptr = 0;
    lc = 0; error = 0; k = 0; i = 0;

    while (i != nadata) {
        /* STOP: emit halt instruction with no address */
        if (strcmp(a[i].mne, "STOP") == 0) {
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                o[k].lc = lc; o[k].mac_code = m[motptr].opcode; o[k].add = NULL;
                k++;
                printf("\nASSEMBLY COMPLETE!!\n");
            }
        }
        /* START / ORG: set location counter */
        else if (strcmp(a[i].mne, "START") == 0 || strcmp(a[i].mne, "ORG") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                if (strcmp(a[i].opnd, "NULL") == 0) lc = 0;
                else { firstloc = atoi(a[i].opnd); lc = firstloc; }
            }
        }
        /* ENDP: reset pointers */
        else if (strcmp(a[i].mne, "ENDP") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) { motptr = 0; poptr = 0; stptr = 0; }
        }
        /* DB: emit 1-byte data entry */
        else if (strcmp(a[i].mne, "DB") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchpot(a[i].label);
                if (flagst == 1) { o[k].lc = lc; o[k].add = s[stptr].sym_add; lc += 1; k++; }
            }
        }
        /* DW: emit 2-byte data entry */
        else if (strcmp(a[i].mne, "DW") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchst(a[i].label);
                if (flagst == 1) { o[k].lc = lc; o[k].add = s[stptr].sym_add; lc += 2; k++; }
            }
        }
        /* CONST: emit constant value */
        else if (strcmp(a[i].mne, "CONST") == 0) {
            flagp = searchpot(a[i].mne);
            if (flagp == 1) {
                flagst = searchst(a[i].label);
                if (flagst == 1) {
                    o[k].lc       = NULL;
                    o[k].mac_code = getvalue(s[stptr].sym_add);
                    o[k].add      = s[stptr].sym_add;
                    k++;
                }
            }
        }
        /* Instruction without label, with operand */
        else if (strcmp(a[i].label, "NULL") == 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") != 0) {
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                o[k].lc       = lc;
                o[k].mac_code = m[motptr].opcode;
                flagst = searchst(a[i].opnd);
                if (flagst == 0) printf("\nUndefined Symbol!!");
                else { o[k].add = s[stptr].sym_add; k++; }
            }
            lc += m[motptr].loi;
        }
        /* Instruction with label, no operand */
        else if (strcmp(a[i].label, "NULL") != 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") == 0) {
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                o[k].lc = lc; o[k].mac_code = m[motptr].opcode;
                k++; lc += m[motptr].loi;
            } else error = 1;
        }
        /* Full instruction: label + mnemonic + operand */
        else if (strcmp(a[i].label, "NULL") != 0 &&
                 strcmp(a[i].mne,   "NULL") != 0 &&
                 strcmp(a[i].opnd,  "NULL") != 0) {
            flagm = searchmot(a[i].mne);
            if (flagm == 1) {
                o[k].lc       = lc;
                o[k].mac_code = m[motptr].opcode;
                flagst = searchst(a[i].opnd);
                if (flagst == 1) { o[k].add = s[stptr].sym_add; k++; }
                else printf("\nUndefined Symbol!!");
                lc += m[motptr].loi;
            }
        }
        i++;
    }

    /* Write all generated object code to OUTPUTNEW.txt */
    for (i = 0; i < naot - 1; i++)
        fprintf(fp, "%d %d %dn", o[i].lc, o[i].mac_code, o[i].add);
}

/* -------------------------------------------------------
   getvalue(): looks up a constant's value by its address
   ------------------------------------------------------- */
int getvalue(int add) {
    int i = 0;
    while (i != nast) {
        if (add_const[i].addr == add)
            return add_const[i].value;
        i++;
    }
    return 0;
}

Input Files

You must create three input files in the same directory as the executable before running the program.

Assembly Language Program (alp.txt)

START 2000
READ  N
LOAD  ZERO
STORE COUNT
STORE SUM
LOOP: READ  X
      LOAD  X
      ADD   SUM
      STORE SUM
      LOAD  COUNT
      ADD   ONE
      STORE COUNT
      SUB   N
      JZ    OUTER
      JMP   LOOP
OUTER: WRITE SUM
STOP
ENDP
ZERO  CONST 0
ONE   CONST 1
SUM   DB    ?
COUNT DB    ?
N     DB    ?
X     DB    ?
END

Machine Opcode Table (mot.txt)

ADD    1  1  2
SUB    2  1  2
MULT   3  1  2
JMP    4  1  2
JNEG   5  1  2
JPOS   6  1  2
JZ     7  1  2
LOAD   8  1  2
STORE  9  1  2
READ  10  1  2
WRITE 11  1  2
STOP  12  0  1
JNZ   13  1  2

Pseudo Opcode Table (pot.txt)

DB     2
DW     2
EQU    2
CONST  2
START  1
ORG    1
LTORG  1
ENDP   0
END    0

How the Code Works

  1. Loading tables: main() reads all three input files (MOT, POT, ALP) into arrays of structs. Each entry stores the mnemonic/directive, its numeric opcode, and its length or operand count.
  2. Pass 1 – Symbol Table construction: pass1() walks every ALP line. For labels, it records the current location counter (lc) and label name into the symbol table. For machine instructions it advances lc by the instruction’s length (loi). For directives like CONST, DB, and DW it allocates data space. Forward references (a symbol used before it is defined) are first added as type IDN and resolved to LABEL when the definition is seen.
  3. Pass 2 – Object code generation: pass2() does a second walk over the ALP, this time looking up each mnemonic in the MOT to get its numeric opcode and looking up each operand in the symbol table to get its resolved address. Each record is stored as (lc, opcode, address) and finally written to OUTPUTNEW.txt.
  4. Helper functions: searchmot(), searchpot(), and searchst() perform linear searches in their respective tables and set global pointer variables so the caller can access the found entry directly. getvalue() looks up a constant’s numeric value from add_const[] using its allocated address.

Output Files

Symbol Table (SymT.txt)

N      VAR   2033
ZERO   CONST 2029
COUNT  VAR   2032
SUM    VAR   2031
LOOP   LABEL 2008
X      VAR   2034
ONE    CONST 2030
OUTER  LABEL 2024

Object Code (OUTPUTNEW.txt)

2000  10  2033    ; READ  N
2002   8  2029    ; LOAD  ZERO
2004   9  2032    ; STORE COUNT
2006   9  2031    ; STORE SUM
2008  10  2034    ; READ  X        <-- LOOP starts here
2010   8  2034    ; LOAD  X
2012   1  2031    ; ADD   SUM
2014   9  2031    ; STORE SUM
2016   8  2032    ; LOAD  COUNT
2018   1  2030    ; ADD   ONE
2020   9  2032    ; STORE COUNT
2022   2  2033    ; SUB   N
2024   7  2024    ; JZ    OUTER    <-- OUTER starts here
2026   4  2008    ; JMP   LOOP
2028  11  2031    ; WRITE SUM
2028  12     0    ; STOP
   0   0  2029    ; ZERO CONST 0
   0   1  2030    ; ONE  CONST 1

Output Explanation

  1. Column layout: Each object-code line is location_counter opcode operand_address. All values are decimal.
  2. Program starts at 2000 because of START 2000 in the ALP. Each instruction occupies 2 bytes, so successive instructions appear at 2000, 2002, 2004, …
  3. Symbol resolution: N was allocated at address 2033, so every instruction that references N (e.g., READ N at 2000 and SUB N at 2022) carries address 2033 in the third column.
  4. Labels in jumps: JZ OUTER at 2024 and JMP LOOP at 2026 use the addresses assigned to OUTER (2024) and LOOP (2008) respectively — both resolved in Pass 1.
  5. Constants: ZERO and ONE are CONST directives placed in the data segment after ENDP; their location counter values are 0 in the output because they are handled as immediate constants rather than relocatable code addresses.

See Also

Conclusion

This two-pass assembler demonstrates the core machinery behind any real assembler: a first pass to collect symbol addresses and a second pass to emit relocatable object code. The clean separation into pass1() and pass2(), each driven by the same ALP array and backed by the MOT and POT tables, mirrors the architecture of production assemblers such as those used for 8086 or RISC-V targets. Once you understand this structure, extending it — adding a macro processor pre-pass or connecting the output to a loader — becomes straightforward.

2 thoughts on “Implementing Multi-pass Assembler in C”

  1. Hey, can you explain the working of MOT, POT, and how this output came? I ran the same code with input files, but that isn’t working…, can you please help..

Leave a Reply

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