| Z88 Developers' Notes | ||
|---|---|---|
| Previous | Contents | Next |
24. BBC BASIC and the in-line assembler
BASIC Summary
The Z80 in-line assembler is not covered explicitly in the Z88 User
Guide, so a short explanation is provided here. The definitive source of
information about the assembler, and BBC BASIC in general, is the BBC BASIC
(Z80) manual by Richard Russell, which is available from M-Tec. Before
moving on to the assembler properly, we cover a few unusual features of
BBC BASIC. Some useful BASIC commands are:
LOAD "filename" loads a BBC BASIC programBASIC's Workspace
SAVE "filename" saves a BASIC program
NEW clears out BASIC workspace for a new program
OLD attempts to recover a program lost through NEW
RUN execute a BASIC program
LIST list out a BASIC program to the screen
RENUMBER [a,[b]] renumbers the lines of a BASIC program
DELETE a,b delete the lines between a and b
CALL x execute a machine code routine at address x
In its Z88 incarnation, BASIC occupies a memory map with the following
form:
$0000 - $1FFF Operating system use (and occasional application stack)BASIC's program/workspace is arranged in the following manner:
$2000 - $3FFF BASIC program/workspace
$4000 - $BFFF (additional 32K of program/workspace, expanded Z88)
$C000 - $FFFF BBC BASIC interpreter application
+-------------------+ $FFFFHIMEM, LOMEM and PAGE are pseudo variables whose values can be read and also set. TOP is a read-only pseudo-variable. If you intend to change the values of these pseudo-variables, then you must bear in mind that the "Unused Memory" area in BASIC's memory map will not necessarily be constant. If task switching occurs, this memory may be used for other purposes and when you return to BASIC its contents may have changed. Therefore any memory which you want to stay constant through task switching must either be in or below this heap. This memory is readily allocated within the heap using the DIM statement, explained below. LOMEM, the start of the heap, defaults to the value of TOP, the end of the program, although it is possible to increase LOMEM, thus providing some safe memory between the end of the program and the start of the variables. The value of PAGE is always set on a page (256 byte) boundary, ie. the less significant byte is ignored.
| BASIC interpreter |
. .
. .
HIMEM +-------------------+ $BFFF or $3FFF
| Stack |
+-------------------+ Current limit of the stack
. . (Stack expands downwards)
. Unused memory .
. .
+-------------------+ Current limit of the heap
| Heap | (Heap expands upwards)
LOMEM +-------------------+
. .
. .
TOP +-------------------+
| Program |
PAGE +-------------------+ $2300
| Workspace for |
| interpreter |
+-------------------+ $2000
| Operating system |
| system usage |
+-------------------+ $0000
By changing the value of PAGE, it is possible to have more than one
program resident at once, within the same instantiation of BASIC. The difficulty
with this technique on the Z88 is the inconstancy of the unused memory.
It is possible to set LOMEM to some high value, but extreme care is required,
because whenever a program is RUN, LOMEM is reset to TOP. TOP, however,
is not reset when PAGE is changed unless part of the program is altered
or OLD is used. If you attempt this technique, keep in mind that whenever
task switching could occur, all the memory used for programs should be
allocated to BASIC and therefore safe.
Numbers and Indirection Operators
BBC BASIC provides the '&' symbol to prefix a hexadecimal number, as distinct from the more usual '$' used in these notes, which is used in BBC BASIC for string indirection. For printing an expression in hex form, prefix it with '~', eg.
PRINT &AF gives 175while
PRINT ~27 gives 1BThe indirection operators provided by BBC BASIC are used to find the contents of a cell or cells at a given address. The ? (query) operator represents byte indirection, so:
?pointerrepresents the byte addressed by 'pointer'. This can be used either on the right or left of an expression; the statement:
?address = contentssets the contents of the byte-sized cell addressed by 'address' to the value 'contents'. In most BASIC's this would have been written as:
POKE address,contentswhereas the statement
contents = ?addressis equivalent to
contents = PEEK(address)The ! ('pling') operator represents word (32bit and not 16bit) indirection, so the expression:
!pointerrepresents the value of the 32bit word, the address of whose first byte is 'pointer'. Thus, !a (on the righthand side of an expression) is equivalent to:
a?0 + 256*a?1 + 256*256*a?2 + 256*256*256+a?3Notice that the least significant byte comes first; this is generally true for the Z88 system, an extension to four bytes of the standard two byte Z80 order.
The query and pling operators may also be used in a dyadic context, which is often more natural (cf. array indexing, which actually works similarly)
base?offset equivalent of ?(base+offset)In this case 'base' must be a simple variable, but 'offset' may be any expression. Thus "!x" will not work. Remember also that the operators in a dyadic context are symmetric, so a!1 addresses a word starting at a+1 and NOT a+4, as some people might expect. Finally, note that ! and ? have the highest level of priority in an expression, equal to unary plus, minus and the logical NOT, above binary arithmetical, relational and logical operators, so for example a?1^2 will be interpreted as (a?1)^2.
base!offset equivalent of !(base+offset)
The operator $ ('dollar') implements string indirection: $a refers to a string which begins at address 'a' and is terminated by carriage return (CR). Thus:
$a = "hello"sets up successive bytes, starting at address 'a', with these five letters and a 13.
Note that the maximum length of a string in BBC BASIC is 255 characters. This is not because the string storage uses a length byte - it does not - but simply for convenience of internal manipulation on an 8bit machine.
Space for a machine code routine, strings and memory to be used with indirection operators may conveniently be reserved using a special form of the DIM statement, without any brackets. The BASIC statement:
DIM code 255reserves a block of 256 continuous bytes of memory and sets the variable 'code' to point to the start of this block. The elements of the block therefore start at address 'code' and finish at 'code+255'. This is quite distinct from the statement:
DIM code(255)which reserves space for 256 floating point variables - which will be considerably more than 256 bytes, actually 256*5 bytes!
The Assembler
We can now move to the assembler itself. Some oddities worth mentioning are that the brackets around the port number for the IN and OUT Z80 instructions mnemonics are optional (ie. OUT 5,A and OUT (5),a are equivalent). The instruction IN F,(C) is not accepted, but the source code IN (HL),(C) produces the equivalent object code. It is conventional (but not necessary) to use lower case for labels and manifests, as this avoids lexical pitfalls and improves readability. It is also important to put spaces between the instruction mnemonic and its operands.
Assembler source may simply be placed within the BASIC program, surrounded by square brackets. The assembler uses the BASIC variable P% as a program counter, which advances as the assembler moves through the source code (note that P%, as with any BASIC variable with a % suffix, is a 4-byte signed (2. complement) integer rather than a floating point variable). The user must, therefore, set P% to the desired start point for the machine code output before invoking the assembler. The program might look like this:
10 REM Trivial example of how to use Z80 assemblerWhen this BASIC program is RUN, it assembles the two-line assembler program into the first four bytes of the reserved memory, but does not execute the code itself. As the BASIC is RUN, an assembly listing is provided. This may be surpressed by using option flags, set by using the assembler directive OPT <n> at the start of the code; 0 will supress a listing, ie. insert line 45 with:
20 DIM code 50
30 P%=code
40 [
50 ld bc, 50
60 ret
70 ]
45 OPT 0A number given by any combination of the following bit settings may follow OPT:
BIT 0 = 1 (1) give a listingThe above options may be combined. The last option means that the code is actually placed starting at O%, but labels have values as if the code started at P% (see below for details of labels declarations). This allows one to assemble code into on space which is designed to fit somewhere else. For instance, in the following code fragment:
BIT 1 = 1 (2) report errors
BIT 2 = 1 (4) place assembled code starting at O% rather than P%
100 DIM code 50then although the code will actually go into the 'code' array, the label 'codestart' has the value $C000, and so the address in the JP statement will appear as such. This facility could be used to assemble code that will ultimately appear in an application card.
110 P%=&C000: O%=code
130 [
140 OPT 6
150 .codestart
160 dec a
170 cp (hl)
180 jp codestart
..
200 ]
Comments may be inserted in the assembler source by preceeding them with either semicolon or backslash, viz:
42 ; This is a commentNote, however, that a comment ends at the end of a BASIC statement. This will normally be the end of the line, but a colon will have the same effect. Hence any characters after a colon will be regarded as a new assembler statement:
55 \ and so is this
54; The following will be regarded as an assembler statement: RST 0This practice is, of cource, very confusing and is not recommended.
Labels may be used in the assembler code; simply precede each label with a full stop (the full stop is NOT part of the label). A label may or may not be followed by an assembler statement on the same line, but if it is, then at least one space must be left between them, eg.:
10 ld c,15When the assembler encounters a label, it sets a BASIC variable of that name to the current value of P%. Assembler labels and BASIC variables are thus interchangeable; so the assembler code could use:
20 .loop1 ld b,30
30 .loop2
40 call misc
50 djnz loop2
60 call wrn1
70 dec c
80 jr nz, loop1
JP codeto jump back to the very start of the program (beginning of the allocated area by the DIM statement). Also, this allows BASIC variables to be used to define manifest constants for use in the assembler listing:
5 maxsize = 62The assembler simply passes once through the source from start to finish, and so will not know the values of labels before they are defined. It would be inconvenient to have to define every label before it is used, so the way around this problem is to make two passes through the code. The first will, in general, encounter errors, so set OPT 0 to suppress their reporting. This pass will set up all the labels correctly, so that a second pass (with OPT 2, or OPT 3 if a listing is desired, to make sure there are no 'genuine' errors) will complete the assembly process. For example:
.
.
40 [
.
.
56 cp maxsize
.
.
80 ]
100 DIM code 100Two rough edges in the assembler regarding labels are:
110 FOR pass%=0 TO 2 STEP 2
120 P%=code
130 [
140 OPT pass%
150 ld bc,13
160 jr label
170 ld bc,26
180 .label
190 ret
200 ]
210 NEXT pass%
DEFB &12 ; sets up a byte of storage and initialises it to 12H (18 decimal)The DEFM directive does not introduce any magic characters, so if you want a string to be null or carriage return terminated, you must explicitly append the terminator byte(s), eg.:
DEFW 16385 ; sets up a 16bit word of storage and initialises it to 123, less
; significant byte first, eg.: 1,64
DEFM "hey" ; sets up space initialised with this string, one character per
; byte
.pointerContrast the BASIC string indirection which when used thus:
DEFM "This string is null-terminated"
DEFB 0
$pointer = "This string is CR-terminated"will automatically carriage-return (13) terminate the string.
Unfortunately, there is no define-storage directive. A second DIM statement may be used, or small spaces, one could use DEFM with a dummy string. This may conveniently be done as follows:
DEFM STRING$(100,"A")This demonstrates a useful consequence of the close intertwining of the assembler with BASIC: the arguments to assembler operands and pseudo-operands may include many forms of BASIC expressions (though brackets may lead to ambiguity as they often indicate an extra level of indirection in Z80 assembler). Two other handy incidences of this are the use of ASC"x" as a character constant, viz:
ld a, ASC"Q"and the use of user-defined functions to provide macro and conditional assembly facilities. Suppose we are using the (non-local) variable 'pass' to represent the current assembler option. Then:
OPT passwill have no effect. Taking this a stage further, if the user-defined function 'macro' always evaluates to 'pass', then:
OPT FNmacro(arguments...)will have no effect, except that it will execute the body of the function, if any. For instance, suppose we define:
DEF FNsave_regs(savearea)then including:
[
OPT pass
ld (savearea),hl
ld (savearea+2),de
ld (savearea+4),bc
]
=pass
OPT FNsave_regs(space)in the main code would reproduce the above three lines of code with 'savearea' set to 'space'. Another useful example is a macro to automatically generate a DEFB or a DEFW depending on the size of an operating system call code. This would look like this:
DEF FNcall_oz(arg)Note that an OPT must reappear in the function and that closing square bracket in the function body does not exit assembler mode in the main program.
IF arg>255 THEN [OPT pass: RST &20: DEFW arg] :=pass
[OPT pass: RST &20: DEFB arg] :=pass
Finally, to call the machine code program once it has been assembled, do:
CALL codeor
a = USR(code)which returns the contents of the HL H'L' register pairs (forming a 32bit value) set at termination (HL most significant; H'L' least significant).
There is a mechanism for initialising the contents of registers from the CALL or USR statements: the registers A, F, B, C, D, E, H, L on entry are set to the values of the BASIC variables A%, F%, B%, C%, D%, E%, H% and L% respectively.
The CALL statement also allows the user to set up a parameter block on entry by appending the required parameters to the CALL statement, ie.:
CALL code, par1, par2, par3, ... parxOn entry IY is identical to the calling address, and IX will point to a parameter block with the following format:
1 byte number of parameters... ...
1 byte parameter 1 type
2 bytes parameter 1 address
1 byte parameter 2 type
2 bytes parameter 2 address
1 byte parameter x typeThe parameter type byte may be any of:
2 bytes parameter x address
0 byte, eg. ?xUsing system calls from BBC BASIC
4 32bit word, eg. !x or x%
5 Floating point (40bit), eg. x
128 String eg. "Hello"
129 Four byte string descriptor containing the current length of the
string, the number of bytes allocated to the string and its address
When using system calls from BBC BASIC there are various important things to bear in mind. The main restart code itself needs to page memory banks in and out of the address space; for instance one of the first things it does is bind bank 0 (which contains the vector table for the OZ calls) to segment 3; but it expects the stack to perform properly when it has done so. So in general, any program, the BASIC interpreter included, should not place its stack where it is liable to be paged out. To be safe, it should be in the bottom 8K of the logical address space, which is never paged out. The BBC BASIC application stack has to be fairly large as it is used for parameter-passing during BASIC execution, and so cannot be placed in the bottom 8K by default. It is in a very vulnerable position, typically at the top of segment 2, and so it is advisable to select a safer stack if any system calls are to be used. This may reliably be done by loading the stack pointer from the location $1FFE. For example, if the main user code starts at 'main' then the program as a whole might look like:
exx ; use alternate registersThe use of the alternate register set avoids corrupting the main set, thus allowing parameter passing with HL (by using the BASIC variables H% and L%), but this may be dispensed with, if the contents of HL are not important. The old stack pointer is pushed onto the new stack so it can be re-called at the end. The old stack pointer could also be saved in a static memory location and this technique is used in some of the other examples.
ld hl,0 ; to preserve hl
add hl,sp ; get current stack pointer in HL
ld sp,(&1FFE) ; load new (safe) stack pointer
push hl ; preserve old stack pointer on new stack
exx ; back to main registers
call main ; execute main machine code
exx ; back to alternate registers
pop hl ; get old BASIC stack pointer
ld sp,hl ; and restore it
exx ; back to main registers
ret ; return to BBC BASIC interpreter
.main ... ; main machine code...
...
ret
Saving and loading of machine code in BBC BASIC
The simplest method of loading a machine code is having it stored in DATA statements as byte opcode sequenses. However, this emplies a lot of work, converting an assembled code into DATA statements (you could make a BASIC program that generates a simple text file that later could could be typed in by CLI). Another way is simply to have the in-line assembler source included together with the rest of the BBC BASIC program. However, large assembler source tends to use much memory, especially if it is well-commented. It is usually sufficient with small assembler routines.
When you use pre-compiled code ready to execute, you face another obstacle; you must always place the code at the same location. Using DIM will probably not return the same address of allocated memory, so it is needed to move down HIMEM to ensure a static position for your code. If you have the source in-line then it is no problem, since you always can re-compile for a specifically allocated memory area.
The two BASIC procedures below implements a simple interface to both load and save machine code in binary files. Both procedures use a small machine code routine to save/load the code in BASIC's memory. To ensure compact BASIC procedures, the machine code have been put into DATA statements, placed inside the procedures. The mnemonic assembler source are found below the procedures. Both procedures locate the machine code routine at the bottom of the stack, beginning at $1800. This area is not touched by BBC BASIC and is therefore safe. However, when BBC BASIC has been pre-empted the bottom of the stack might have changed. Please note that both routines null-terminate the filename strings.
65515 DEF PROC_lbytes(f$,addr%)
65516 LOCAL b%,i% 65517 f$=f$+CHR$0
65518 RESTORE 65520: FOR i%=0 TO 66: READ b%: ?(&1800+i%)=b%:
NEXT i%
65519 CALL &1800,f$,addr%
65520 DATA &21,0,0,&39,&ED,&7B,&FE,&1F,&E5,&CD,&0F,&18,&E1,&F9,&C9
65521 DATA &DD,&E5,&FD,&E1,&FD,&6E,2,&FD,&66,3,&23,&23,&5E,&23,&56,&62
65522 DATA &6B,1,2,0,&3E,1,&E7,9,&60,&38,&15,&FD,&6E,5,&FD,&66,6
65523 DATA
&5E,&23,&56,&21,0,0,1,&FF,&FF,&E7,&45,&E7,9,&62,&C9,&E7,9,&4A,&C9
65524 ENDPROC
65525 DEF PROC_sbytes(f$,addr%,length%)
65526 LOCAL b%,i% 65527 f$=f$+CHR$0
65528 RESTORE 65530: FOR i%=0 TO 73: READ b%: ?(&1800+i%)=b%:
NEXT i%
65529 CALL &1800,f$,addr%,length%
65530 DATA &21,0,0,&39,&ED,&7B,&FE,&1F,&E5,&CD,&0F,&18,&E1,&F9,&C9
65531 DATA &DD,&E5,&FD,&E1,&FD,&6E,2,&FD,&66,3,&23,&23,&5E,&23,&56,&62
65532 DATA &6B,1,2,0,&3E,2,&E7,9,&60,&38,&1C,&FD,&6E,5,&FD,&66,6
65533 DATA &5E,&23,&56,&FD,&6E,8,&FD,&66,9,&4E,&23,&46,&EB,&11,0,0
65534 DATA &E7,&45,&E7,9,&62,&C9,&E7,9,&4A,&C9
65535 ENDPROC
MODULE Lbytes
include "fileio.def" ; standard file I/O definitions;****************************************************************************
include "error.def" ; error code definitions
ORG $1800 ; subroutine resided at $1800
; (IX+0) number of parameters: 2
; (IX+1) type of 1st parameter: 129 (mov. string)
; (IX+2,3) address of movable string: xx
; (IX+4) type of 2nd parameter: 4 (32bit integer)
; (IX+5,6) address of 2nd parameter: xx
; The movable string parameter block:
; (xx+0) current length
; (xx+1) max. length
; (xx+2,3) start address of string
.InitLbytes ld hl,0
add hl,sp ; get current BASIC stack pointer
ld sp,($1FFE) ; install safe stack pointer
push hl ; preserve BASIC stack pointer
call Lbytes
pop hl
ld sp,hl ; restore BASIC stack
ret ; return to BASIC interpreter
.Lbytes push ix
pop iy ; iy points at CALL parameter block
ld l,(iy+2)
ld h,(iy+3) ; get pointer to moveable string par. block
inc hl
inc hl ; point at string start address pointer
ld e,(hl)
inc hl
ld d,(hl)
ld h,d ; HL & DE = pointer to start of filename
ld l,e
ld bc,2 ; local pointer, C = 2 byte scratch buffer
ld a, OP_IN
call_oz(GN_Opf)
jr c, file_err ; Ups, file couldn't be opened
ld l,(iy+5) ; IX = file handle
ld h,(iy+6)
ld e,(hl)
inc hl
ld d,(hl) ; start address to load machine code
ld hl,0
ld bc, $FFFF ; max. file length (probably much snaller!)
call_oz(OS_Mv) ; load file image at (DE) onwards...
call_oz(GN_Cl) ; close file
ret
.file_err call_oz(GN_Err) ; display error box...
ret
MODULE Sbytes
include "fileio.def" ; standard file I/O definitions;****************************************************************************
include "error.def" ; error code definitions
ORG $1800 ; subroutine resided at $1800
; 1st parameter is the string pointer;
; 2nd parameter is the start address integer
; 3rd parameter is length of memory block
; (IX+0) number of parameters: 2;
; (IX+1) type of 1st parameter: 129 (mov. string)
; (IX+2,3) address of movable string: xx
; (IX+4) type of 2nd parameter: 4 (32bit integer)
; (IX+5,6) address of 2nd parameter: xx
; (IX+7) type of 3rd parameter: 4 (32bit integer)
; (IX+8,9) address of 3rd parameter: xx
; The movable string parameter block:;
; (xx+0) current length
; (xx+1) max. length
; (xx+2,3) start address of string
.InitSbytes ld hl,0
add hl,sp ; get current BASIC stack pointer
ld sp,($1FFE) ; install safe stack pointer
push hl ; preserve BASIC stack pointer
call Sbytes
pop hl
ld sp,hl ; restore BASIC stack
ret ; return to BASIC interpreter
.Sbytes push ix
pop iy ; iy points at CALL parameter block
ld l,(iy+2)
ld h,(iy+3) ; get pointer to moveable string par. block
inc hl
inc hl ; point at string start address pointer
ld e,(hl)
inc hl
ld d,(hl)
ld h,d ; HL & DE = pointer to start of filename
ld l,e
ld bc,2 ; local pointer, C = 2 byte scratch buffer
ld a, OP_OUT
call_oz(GN_Opf)
jr c, file_err ; Ups, file couldn't be created
ld l,(iy+5) ; IX = file handle
ld h,(iy+6) ; HL = pointer to pointer to start address
ld e,(hl)
inc hl
ld d,(hl) ; DE = start addr. of memory block to save
ld l,(iy+8)
ld h,(iy+9) ; HL = pointer to pointer to length
ld c,(hl)
inc hl
ld b,(hl) ; BC = length of memory block to save
ex de,hl
ld de,0
call_oz(OS_Mv) ; save memory block from (HL) onwards...
call_oz(GN_Cl) ; close file
ret
.file_err call_oz(GN_Err) ; display error box...
ret
Using relocatable code in BBC BASIC
Using machine code in BBC BASIC is best suited with allocating dynamic memory with DIM and then storing (assembling) the code to that area. If you don't assemble but have code ready, eg. loaded directly with PROC_lbytes, it is necessary that your machine code is relocatable, ie. contains no absolute address references (the machine code assumes that it is located at a certain ORG position in memory). You can relocatable code by omitting CALL and JP instructions and only use JR and DJNZ (jump relative) instruction. However, your program cannot be very large since relative jumps only range +/- 128 bytes in either direction from the instruction.
You can make truly relocatable machine code with the Z80asm application
that is part of the Z88 Assembler Workbench. By using the '-R' option a
small header is generated together with your code. The header contains
a relocater routine and a relocation table. When your code is executed,
it is automatically relocated to the current position in memory (just once).
Subsequent calls to the code will just execute your code and not the relocater.
With this option you can always store your machine code utilities with
PROC_sbytes and at a later time allocate space with DIM for your
code, and just load it into appropriate BBC BASIC memory. The code may
be placed anywhere (in RAM).
Example program in BBC BASIC's assembler
We present here a short example program in BASIC. The error handler copes with pre-emption, responding to RC_QUIT by calling BASIC's own error handler. This will not close files, filters wildcards, or memory however, so if you use these features you must modify the error handler to close these things first before calling BASIC. The program here does something which BASIC cannot normally do, which is to read the update date of a file. When the program is run it assembles the code and asks for a filename. It attempts to open the file for DOR access, indicating failure with a system error box, and then reads the update date. Finally, having released the DOR handle, the program displays the explicit filename, expanded by GN_Opf, and the update date.
This listing can be 'loaded' by CLI. Mark a block for the program only in column A below, and save it as a text file. Then execute eg. from the FILER. .J
1000 DIM code 512 \ space for program
1010 REM
1020 GN_Esp=&4C09 \ return pointer to system error message
1040 GN_Nln=&2E09 \ carriage return, linefeed to std. output
1050 GN_Sop=&3A09 \ output string to std. output
1060 GN_Opf=&6009 \ open file
1070 OS_Erh=&75 \ install error handler
1080 OS_Esc=&6F \ examine special condition
1090 GN_Err=&4A09 \ standard system error box
1100 GN_Sdo=&0E09 \ date and time to standard output
1110 OS_Dor=&87 \ DOR interface
1120 dr_rd=&09 \ read DOR record
1130 dr_fre=&05 \ free DOR handle
1140 op_dor=&06 \ open file for DOR access
1150 rc_quit=&67 \ KILL request error code
1160 rc_esc=&01 \ escape detection error code
1170
1180 FOR pass=0 TO 2 STEP 2
1190 P%=code1200 [
1290 LD HL,errhan \ address of error handler1340 .exit
1300 OPT FNsys(OS_Erh) \ install new error handler
1310 LD (obou),A \ save old error handler call level
1320 LD (oerr),HL \ save old error handler address
1330 CALL main \ call main routine
1350 LD HL,(oerr) \ address of old error handler1370 LD B,0
1360 LD A,(obou) \ old call level
1380 OPT FNsys(OS_Erh) \ restore old error handler1410
1390 LD SP,(bstk) \ install BASIC stack pointer
1400 RET \ return to BBC BASIC interpreter
1440 CP rc_esc \ ESC pressed?1450 JR NZ,err1
1460 OPT FNsys(OS_Esc) \ acknowledge ESC1470 LD A,rc_esc
1480 OR A \ return rc_esc back to main program1500 .err1
1490 RET \ Fc = 0, Fz = 0
1510 CP rc_quit \ KILL request?1520 JR NZ,err2
1530 LD HL,(oerr) \ re-install old error handler1550 OPT FNsys(OS_Erh)
1540 LD A,(obou) \ old call level
1560 LD SP,(bstk) \ install BASIC stack pointer1570 LD HL,(oerr)
1580 LD A, rc_quit \ reload A with RC_QUIT1420
1590 OR A \ Fz = 0
1400 SCF \ Fc = 1
1410 JP (HL) \ jump to BASIC's error handler
1430 .err2 \ write error message if possible1450 RET 1460
1440 OR A \ Fc = 0
1470 .bstk DEFW 0 \ storage for BASIC stack pointer1500
1480 .obou DEFB 0 \ storage for old call level
1490 .oerr DEFW 0 \ storage for old error handler address
1530 LD HL,scratch_1 \ holds address of file to open
1540 LD DE,scratch_2 \ explicit name buffer
1550 LD C,40 \ size of explicit name buffer
1560 LD B,0 \ HL string pointer is local
1570 LD a, op_dor \ get DOR handle
1580 OPT FNsys(GN_Opf) \ open...
1590 JR NC,opened_OK1600 OPT FNsys(GN_Err) \ report error in standard window
1890 A$=A$+CHR$0 \ null-terminate filename string1900 $scratch_1=A$
| Previous | Contents | Next |
| Miscellaneous useful information | BBC BASIC & in-line assembler | Z88 Hardware |