My next assembler is written and I'm working on testing it - hopefully I will be posting it here soon. It will have 2 parts: one part (RUNASM.BA) written in BASIC, and another part (ASM.CO) written in assembly. The reason is that for some tasks, it makes more sense to use BASIC, and there is no (easy/documented) way to call into the BASIC code from a machine language program, whereas there is a way to call machine language from BASIC. RUNASM.BA will be listed below.
I need RUNASM.BA to deal with getting input from the user, displaying messages to the user, and handling the input file being assembled. Most of the tasks will only be required once or twice - except #1 (line 100 - get the next line from the input file); and #9 (line 900 - used for testing/debugging). If the assembler is too slow, I will suspect task #1 first.
The main reason that I want to use BASIC to handle the input file is that, as well as files in RAM, it can read from cassette, the serial port, and ** a diskette drive ** if you have one and have set BASIC up to use it. (For example, there was available the "TANDY Portable Disk Drive" and a program called TS-DOS which could be hooked into BASIC - see the links on the left for more info.) Note, though, that for now I'm only testing with RAM files.
Aside: I figure I'd better assert copyright on this stuff, SO
All code below copyright (C) 2014 Clinton Reddekop.
You can redistribute and/or modify this code
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License (available at <http://www.gnu.org/licenses/gpl-3.0.html>) for details.
Now here's how this works. RUNASM.BA loops, repeatedly:
- calling ASM.CO
- checking a byte ("inst") in RAM which corresponds to a task that ASM.CO wants ASM.BA to perform
- performing the requested task
Each call into ASM.CO sets the A register to 0 (or 1 if there has been an error - don't worry about that yet...) and the HL register pair to any result needed from the previous task.
Here is RUNASM.BA:
5 ON ERROR GOTO6000
410 ?"error near input file line:";NM!
500 INPUT"input file";FN$
660 HL!=1:?"bounds error"
720 ?"LO =";L!,"HI =";H!
5000 ?"error bad instruction"
6010 ERROR ERR
7000 ?"assembler exit"
CO! is the location in ASM.CO which is called from BASIC
CO! + 3 is the address of the "inst" byte
CO! + 4 and CO! + 6 are the addresses of the (16-bit) lowest and highest addresses that the program being assembled will be written to
CO! + 8 is the (16-bit) line number of the line most recently read from the input file of the program being assembled
Here is the machine code that runs first when ASM.CO is called:
; BASIC calls always go through here ;
;if a is nonzero, restore to
; uncalled state
; save BASIC sp, use local stack ;
shld hlin ; save hl param
lhld hlin ; restore hl param
; tos has address to return to ;
; (on first entry this is main) ;
This switches from BASIC's stack to another one created for use in ASM.CO. The word on the top of the local stack is always the address of the code we want to run next; we get there via the 'ret' instruction.
Here is an example showing how ASM.CO tells BASIC to do something:
;func to print "success"
;modifies all regs
;stack usage 2 words
mvi a,8h ; instruction to
sta inst ; print "success"
It stores the instruction it wants performed in 'inst', pushes the 'printedsuccess' address onto the top of the local stack, and jumps to backtobasic (below). Having 'printedsuccess' on the top of the local stack means that when BASIC calls entry (above) again, it will return to 'printedsuccess'.
This is the backtobasic code:
; save local sp, use BASIC stack ;
It switches back to the BASIC stack and returns to BASIC, which will then handle the instruction in 'inst'.