← Rutvik Ghughal / exhibits

Two-Pass Assembler

A faithful recreation of the two-pass assembler I wrote in C in 2019 — source on GitHub. This page runs the same two-pass algorithm ported line-by-line to JavaScript: on the original input.asm it reproduces symTable.txt, opTable.txt and output.o byte for byte. Edit the program and reassemble.

input.asm

Pass 1 — Symbol table (symTable.txt)

LabelAddress

Pass 1 — Opcode table (opTable.txt)

MnemonicOpcode

Pass 2 — Machine code (output.o)


  

How the two passes work

Pass 1 — read, measure, remember

The source is scanned token by token (the C original literally loops on fscanf("%s")). A location counter adds each instruction's size in memory — 4 for MOV, 2 for AND, 0 for HLT… When a label definition like L1: appears, its current address is written to the symbol table. Every distinct mnemonic also gets logged in the opcode table with its 4-bit encoding. No code is emitted yet — pass 1 exists so that forward references (JMP L2 before L2: is defined) can be resolved.

Pass 2 — read again, emit

The same source is scanned a second time, now emitting binary: 4-bit opcode, 5-bit register fields, 16-bit hex literals. Label operands are looked up in the pass-1 symbol table and replaced by their addresses. Two quirks of the original ISA are preserved: LOOP Ln is a macro that expands to SUB R31, 0001H + JNZ Ln, and MUL Rn implies R1 as its first operand.

MnemonicOpcodeSizeNotes

Operand grammar, as in 2019: registers R0–R31, hex literals as four digits plus H (e.g. 0003H), labels L<number> (colon when defined, bare when referenced). START/END frame the program and assemble to nothing. SUB and JNZ exist in the instruction set but typically appear only through LOOP's expansion.