I came up with much of my metacompiler design before I had much of an idea as to how other metacompilers worked, and I seem to have ended up with some unusual design features.
- The target image is built in dictionary space, rather than in a separate chunk of address space. Once the metacompiler is loaded, anything added to the dictionary ends up in the target image.
- Instead of having memory operations for accessing target space, I have separate operations for working with addresses instead of data (applying a displacement to convert target pointers to host pointers and back).
- The metacompiler searches the target image dictionary directly, rather than having a separate host wordlist for target names. There used to be a post-build step that ran through the dictionary altering host pointers to target, but that was too hacky to keep and wouldn't've worked when building a target of a difference cell width than the host.
At one point I started over with a new implementation as a Linux-hosted system, instead of continuing as a standalone implementation. I used (32-bit) Jonesforth as the initial bootstrap host, which was fairly dreadful, and once I had the system up and working enough, and the target system converted to conform closely enough to the ANS spec, I added support for building from (64-bit) gforth. Supporting three host environments (including self-hosting) required some work to either paper over the differences between the host environments or to stop doing unportable things, but is far better than requiring Jonesforth as the only host system.
Adding support for a DTC target as well as the original ITC target, especially with respect to DOES>, involved some cleverness. The target configuration file is loaded before the metacompiler, since the metacompiler needs to refer to it during build, but it includes definitions which need to refer to parts of the metacompiler. So there are several places where, in the middle of a definition, we leave compilation mode to use a word that's part of the target configuration, and that word is basically a literal string and a call to INTERPRET, and the code that gets interpreted re-enters the compiler to add something to the word currently being defined.
I am hoping at some point to add support for 65816 and possibly 6502 standalone targets, producing ROMmable code, but that will need to wait until I finish getting my '816 system working.