|Z88 Developers' Notes|
24. BBC BASIC and the in-line assembler
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 program 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 xBASIC's Workspace
In its Z88 incarnation, BASIC occupies a memory map with the following
$0000 - $1FFF Operating system use (and occasional application stack) $2000 - $3FFF BASIC program/workspace $4000 - $BFFF (additional 32K of program/workspace, expanded Z88) $C000 - $FFFF BBC BASIC interpreter applicationBASIC's program/workspace is arranged in the following manner:
+-------------------+ $FFFF | 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 | +-------------------+ $0000HIMEM, 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.
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) 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.
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 contiguous 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!
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 assembler 20 DIM code 50 30 P%=code 40 [ 50 ld bc, 50 60 ret 70 ]When 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:
45 OPT 0A number given by any combination of the following bit settings may follow OPT:
BIT 0 = 1 (1) give a listing BIT 1 = 1 (2) report errors BIT 2 = 1 (4) place assembled code starting at O% rather than P%The 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:
100 DIM code 50 110 P%=&C000: O%=code 130 [ 140 OPT 6 150 .codestart 160 dec a 170 cp (hl) 180 jp codestart .. 200 ]then 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.
Comments may be inserted in the assembler source by preceeding them with either semicolon or backslash, viz:
42 ; This is a comment 55 \ and so is thisNote, 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:
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,15 20 .loop1 ld b,30 30 .loop2 40 call misc 50 djnz loop2 60 call wrn1 70 dec c 80 jr nz, loop1When 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:
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 = 62 . . 40 [ . . 56 cp maxsize . . 80 ]The 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:
100 DIM code 100 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%Two rough edges in the assembler regarding labels are:
DEFB &12 ; sets up a byte of storage and initialises it to 12H (18 decimal) 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 ; byteThe 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.:
.pointer DEFM "This string is null-terminated" DEFB 0Contrast the BASIC string indirection which when used thus:
$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) [ OPT pass ld (savearea),hl ld (savearea+2),de ld (savearea+4),bc ] =passthen including:
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) IF arg>255 THEN [OPT pass: RST &20: DEFW arg] :=pass [OPT pass: RST &20: DEFB arg] :=passNote 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.
Finally, to call the machine code program once it has been assembled, do:
a = USR(code)which returns the contents of the HL main and alternate register pairs set at termination (H most significant; L least).
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 type 2 bytes parameter x addressThe parameter type byte may be any of:
0 byte, eg. ?x 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 addressUsing system calls from BBC BASIC
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 registers 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... ... retThe 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.
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
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
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
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
1180 FOR pass=0 TO 2 STEP 2
1190 P%=code1200 [
1290 LD HL,errhan \ address of error handler 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 routine1340 .exit
1350 LD HL,(oerr) \ address of old error handler 1360 LD A,(obou) \ old call level1370 LD B,0
1380 OPT FNsys(OS_Erh) \ restore old error handler 1390 LD SP,(bstk) \ install BASIC stack pointer 1400 RET \ return to BBC BASIC interpreter1410
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 program 1490 RET \ Fc = 0, Fz = 01500 .err1
1510 CP rc_quit \ KILL request?1520 JR NZ,err2
1530 LD HL,(oerr) \ re-install old error handler 1540 LD A,(obou) \ old call level1550 OPT FNsys(OS_Erh)
1560 LD SP,(bstk) \ install BASIC stack pointer1570 LD HL,(oerr)
1580 LD A, rc_quit \ reload A with RC_QUIT 1590 OR A \ Fz = 0 1400 SCF \ Fc = 1 1410 JP (HL) \ jump to BASIC's error handler1420
1430 .err2 \ write error message if possible 1440 OR A \ Fc = 01450 RET 1460
1470 .bstk DEFW 0 \ storage for BASIC stack pointer 1480 .obou DEFB 0 \ storage for old call level 1490 .oerr DEFW 0 \ storage for old error handler address1500
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$
|Miscellaneous useful information||BBC BASIC & in-line assembler||Z88 Hardware|