Thanks for the thoughts.
The Macro and Include logic is mostly the same, but Includes don't get extra parameters, and macros can only be chained, not nested. Also the implementation for both is coded separately.
I'm reasonably confident that the macro code, as I implemented it, works OK and should meet most Macro usage requirements and allow at least what Microsoft's MACRO assembler would have been capable of. Hopefully no one would want to create large, complex, macros since it stores the macro text in the assembler memory where it competes with labels for space.
And while I recognise it's for a worldwide user base of 1 person, I'm writing this for fun and challenge - It's like building a ship in a bottle - Hence why I have been adding features I don't presently desire. On the other hand, the PC cross assembler might actually be useful to others in the future. As
@Svenska noted previously, there are z80 assemblers available under Linux, but using WSL to run an assembler bothers me, and is mostly why I wrote my own to run under Windows 10,11, 12??? or whatever comes next. So I'll rewrite my PC assembler to match the z80 assembler when I'm done
That way the code can be assembled on either system. So that much of the effort is not wasted at least.
The other value to me is that I still write assembly commercially, so learning new structures and programming techniques and ideas is still valuable to me. Writing an assembler is just an autodidactic approach to learning more about assembler.
The macro store/define/generation function uses the system parser to strip out comments, unnecessary whitespace, unused operators and all characters below $20 (ASCII space) including tabs, which are replaced only with a space when relevant to the syntax of an argument. This ensures that when a macro is called, I only need to track which term (if any) causes an error, but it all shows up correctly for the line that calls it. The intent there was to minimise the memory requirements.
It seems like the macro function does what is necessary, and can even pass parameters. Using system labels isn't preferred, but then an assembler by nature restricts what you can do. I don't reserve register names, so labels like HL, DE etc are perfectly acceptable, which allow even register names to be passed to the macro if necessary, by assigning the register opcode token value to the register.
Using predefined argument names comes from DOS/UNIX type shells, where command line arguments are referenced by an argument indicator - eg, ARGV, ARGC, %1, %2 etc. Using text arguments that are reserved just means code can't use those reserved words, but there's not a lot of reserved words in the assembler. The list was kept intentionally short. And ARG isn't a label that someone is all that likely to use. Even if they did, it would generate an easily found error rather than accepting it and allowing the collision. It seems an elegant way to allow in-line parameters without needing to define them, which creates a different kind of rigidity.
As long as you're using global labels for your macro arguments, then pleas for naming the arguments will go on deaf ears.
I may be using global labels for macro arguments, but that doesn't mean I don't support local labels for the macros also. That code is already in place ( though it gives me ideas to improve it ). But having a reference to he position of the arguments is a bit of a system constraint. Doing so saves memory over other options and still allows local labels.
One problem with the ARG1, ARG2, ... scheme is that you can not insert an argument (easily) in the macro, since it pushes all of the others and the macro will have to be gone through. Also, with enough ARG1, ARG2, etc. you find they blur together and lose their meaning.
This is actually quite easily done, if the programmer is going to the trouble of declaring local variables anyway, since it's not that different from other macros - just the syntax and structure look different.
Code:
MACRO MYNAME
LOCAL ; Establish local precedence over the label list.
EQU X,ARG1
EQU Y,ARG2
; Do something here with X and Y.
MEND
Would easily be extended like this;
Code:
MACRO MYNAME
LOCAL ; Establish local precedence over the label list.
EQU X, ARG1
EQU X1,ARG2
EQU Y, ARG3
EQU Y1,ARG4
; Do something here with X, X1 and Y, Y1...
CLEAR X ; Remove all the labels after X, including X.
GLOBAL ; Restore the global labels back to use.
MEND
All that is needed is to reassign the arguments.
When you consider it, it's not that different from defining a normal macro except the declaration is explicit rather than implicit.
So once the macro goes as far as establishing local labels, and they get defined ( which a Macro parser has to do anyway ) then you can add, remove, change etc, without any more difficulty than would be expected.
One thing I note - I'm going to have to write some decent documentation explaining all of that. Going local removes the global table. What I need is another command to partially re-attach the global table after making the local declarations. I can't leave them there by default, because of collisions, but I can make it possible to attach them later, which is more trivial than may seem obvious.
And again, the more I think about it, creating a very flexible system is only half the battle. Documenting that flexibility is going to be the next challenge.
While my assembler is very small, I think it punches way above it's weight for features and function
Though I need to pull down a real CP/M system some time and try it out to see how well it assembles on a 4Mhz z80 instead of an emulator.
This. If you are going to use it yourself, then make it work the way you want it to work. But it's still helpful to have experience with different assemblers so that you can know what kind of things are possible. Macros is one area with a very wide variability, and it can be very tricky to make a useful macro system if you don't have a lot of experience with writing code to parse text.
It's not that I don't have a lot of experience writing code to parse text... Actually, that's probably incorrect because I don't have a lot of experience, but I've written a few parsers at least.
The challenges come when I choose a parsing methodology is that once I've settled on a strategy, if I add or change things later, it becomes constrained by earlier decision.
Hence why knowing as much as I could before I set out seemed like a good idea. Instead I took the Indiana Jones route and made it up as I went