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
- 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. - 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 advanceslcby the instruction’s length (loi). For directives likeCONST,DB, andDWit allocates data space. Forward references (a symbol used before it is defined) are first added as typeIDNand resolved toLABELwhen the definition is seen. - 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 toOUTPUTNEW.txt. - Helper functions:
searchmot(),searchpot(), andsearchst()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 fromadd_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
- Column layout: Each object-code line is
location_counter opcode operand_address. All values are decimal. - Program starts at 2000 because of
START 2000in the ALP. Each instruction occupies 2 bytes, so successive instructions appear at 2000, 2002, 2004, … - Symbol resolution:
Nwas allocated at address 2033, so every instruction that referencesN(e.g.,READ Nat 2000 andSUB Nat 2022) carries address 2033 in the third column. - Labels in jumps:
JZ OUTERat 2024 andJMP LOOPat 2026 use the addresses assigned toOUTER(2024) andLOOP(2008) respectively — both resolved in Pass 1. - Constants:
ZEROandONEareCONSTdirectives placed in the data segment afterENDP; their location counter values are 0 in the output because they are handled as immediate constants rather than relocatable code addresses.
See Also
- Implementing Macro Processor in C
- Implementing Absolute Loader in C++
- Implementing Code Generator in C++
- Implementing Lexical Analyser in C++
- Implementation of Bottom-Up (Shift-Reduce) Parsing in C++
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.
I got some more errors while am running this program.
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..