There is another trick that no one else has mentioned yet, useful for most "classic" microprocesor architectures:You take your favorite modern assembler (Gnu's gas, for example) and implement all of the classic instructions as macros that generate bytes in memory. This way you get the major assembler features (offset calculations, pseudo-ops and directives, macros, binary file handling, editor support) of the modern assembler "for free."
Also, in the process of writing the macros, you probably learn a lot about the target...
I wonder if there is a "blank" gas target that doesn't define any opcodes that might conflict?
I did an 8085 assembler as a set of Macro-10 (DEC PDP-10 Assembler) macros, back in college.
Of course it's a bit sucky when you type in a Forth implementation, assemble with you homemade assembler, and run it on your homemade emulator, and you have to figure out which piece has the bug that preventing things from working right :-(
And it doesn't work so well on newer CPUs that got creative with their operand syntax - it's easy to have separate macros for MOV, MVI, LSI, LDA, STA, LHLD, etc, but not if they've all been collapsed into "MOVE", with the opcode varying depending on exact format of the operand(s)