I vaguely remember you mentioned something about using
"Forth" language (or I may be imagining things 😵💫 ) for some of
your troubleshooting. Would you please elaborate on that?
I use Forth for all of my hobby stuff. I'm more productive in that language than in other languages. It comes in various implementations. The most common was the FIG implementations that were indirect threaded. Most high performance Forths are inline/call threaded. Most people don't like it because it uses a stack for parameter passing and the programmer has to maintain it or it will crash. Unlike most languages Forth is more like a tool box. It does only a few special things for you. But then, you have full control over the interpreter/compiler. Most other languages, you don't think to much about the difference between run time and compile time. To expand it efficiently you may have to think about such things. If it doesn't have something you want and you can define what you want, you are not blocked from adding it at the lowest level, if you like. Not everyone uses it this way. Most never get beyond just using it as a programming language. That is too bad, it was made to be expanded to be the application. It is hard to explain how one expands it to be the application.
It is like when I write an assembler, I don't exactly write a separate assembler, I expand Forth to be the particular assembler I need. I often write single pass assemblers that self patch the addresses as it goes along. When one needs a forward location, one just defines a forward reference. It is designed to keep a linked list of where is is used. When it finally reaches the location of its address, it patches itself by backing through the linked list. From there on the address just acts like a constant value. No second pass is needed. For assembler target that can have address phase errors, it is no different than other assemblers, except it is up to you to tell the assembler how you want that address handle as either a local reference or a long addresses.
I find that I compile the extension at the time needed. The Forth base compiler is quite fast and things like compiling the assembler and the code to be assembled is done at the same time. There is little reason to keep a compiled version of the assembler. If I need to add a feature to the assembler it is still just all source. I recompile it and the final assembled code at the same time. There still is not enough time to get a cup of coffee ( I'm not a coffee drinker anyway ). You hit the return and before you can get up to do anything the prompt is reminding you that it is ready for the next thing to do.
Also, for simulators, I do the same. I find there is always some feature I need to add to a simulator for each project. The simulator is never really separate, it is Forth expanded to be the simulator and then expanded to be the simulation. It is hard to make the distinction of where one ends and the other begins. An example would be like a 4004 project I recently completed. I needed to add a ASR33 simulation. Was it part of the simulator or part of the 4004 simulation? It was both and an extension of Forth. I used the Forth keyboard and display functions to be the ASR33 keyboard and printout. The file I/O was the tape reader and punch. It was Forth that filled in for the needed parts. Since the particular 4004 code that I was simulating was bit banging the serial TTY, it was part of the 4004 simulation as it need to know how fast a baud signal was in 4004 execution cycles but was still just an extension for Forth.You just don't use other languages like that.
Sorry for the winded text but you asked for it.