This is the implementation of the second stage (compound) assembler.
The compound assembler implements the complete syntax for the final assembly language but it does not implement all of the error checking and debug info that the full assembler does.
For example the ims
instruction takes a 16-bit argument which can be a decimal number. This assembler accepts a decimal number of any size and just silently truncates it to the low 16 bits. The final assembler verifies that the value is in range.
The assembler implements compound opcodes using templates. A template is modifiable data that contains assembly instructions.
For example, the template for xor looks like this:
=opcode_xor_template
or ra r1 r2 ; or ra <src1> <src2>
and rb r1 r2 ; and rb <src1> <src2>
sub r0 ra rb ; sub <dest> ra rb
When the assembler sees an "xor" instruction, it parses a destination register and two mix-type source arguments. It then replaces bytes in the template:
- Byte 9 (initially
r0
) is replaced with the destination; - Bytes 2 and 6 (initially
r1
) are replaced with the first source; - Bytes 3 and 7 (initially
r2
) are replaced with the second source.
The template is then emitted in full, completing the xor instruction.
Note that the template is not copied. The replacements are done in-place in the actual data. The new values are simply left in the template until they are replaced by the next "xor" instruction.
It is interesting that, when this assembler program is itself assembled, the values in this template are actually generated by the previous stage assembler. This would have security and correctness implications if we were not ourselves bootstrapping the previous stage assembler! (The final stage assembler doesn't work this way; it provides its own values for all its templates.)