Hello, this is my first post on this forum.
Welcome!
- What can I change to make the code more idiomatic, concise, maintainable?
The code should be easy to read which means that;
1. On a small project like this you should keep everything in a single file.
2. Commenting the code so that, ideally, someone who doesn't even know assembly can still follow along what the code does. I say "ideally" because I rarely do that myself.
3. Everything that carlos12 already said, using proper indentation for code and comments, etc.
4. This might sound strange but you should avoid using macros if possible. This applies especially if other people are supposed to read and maintain the code. Not only do macros "redefine" the language (forcing people to constantly look up what they do until it's internalized) but they also add abstraction that make it harder to optimize the code.
5. Name variables so you can infer their size directly by reading the instruction using them.
For example, something like this;
6. Optimize the code. Again, this might sound strange but I often find optimized code to be easier to read, probably because it's more concise.
- Are the code and data organized well for performance?
No. In this case it doesn't really matter, but as an example, you should in general avoid jumps whenever possible - the very first instruction in this program jumps over some data that could have been located at the end after all the code.
- Am I wasting code memory? What should I do to use fewer instructions?
It's hard to give specific advice so...
Find a good instruction set reference and learn everything there is to know about each of the instructions - what they do and how they affect the flags. Using the flags properly is crucial for writing efficient code. As far as 8086/8088 code goes, smaller is always faster (with a few exceptions - namely the multiply and divide instructions) so you should learn the size of the instructions as well.
The very first thing you should learn as a new beginner is the jump instructions, specifically which jcc:s are signed vs unsigned and when to use which.
BTW, my head hurts after reading this;
Code:
; If the high bit of the keyboard code is clear ("make"), the key is pressed.
; If it is set ("break"), the key is released.
; ah = key press state. 0 is released, 1 is pressed.
mov ah,1
test al,080h
jge >L2
mov ah,0
Using JGE like this is... not common.
JGE jumps when the sign flag equals the overflow flag. The TEST instruction (as well as AND, OR and XOR) always clear the overflow flag so this means that JGE will always jump when the sign flag is cleared in the result of the TEST instruction. In other words, I would have used JNS instead.
On a side note, those four instructions can be replaced with this;
- Where should I be using more macros?
Nowhere, at least in this program. Macros are great for readability when you're using conditional assembly. For example, in XUB we use a lot of conditional assembly to emulate instructions not available on older processors, or to avoid bugs in some processors. I would not use macros for simple instruction sequences like, for example, your dos_return macro.
Finally, seeing this made me dry heave;
I assume that's a built-in macro in A86? I wouldn't use macros like that because "mov sreg, imm" is not valid code and it might fool people into thinking it is.
The code for that actually looks like this;
Code:
push ax
mov ax, 0
mov es, ax
pop ax
It has to preserve the flags so that's why it does "mov ax, 0" instead of using the more efficient "xor ax, ax" or "sub ax, ax". This is a perfect example of how macros can make code less efficient.