-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
patch.asm
944 lines (891 loc) · 23.7 KB
/
patch.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
;
; Title: BBC Basic for AGON
; Author: Dean Belfield
; Created: 12/05/2023
; Last Updated: 15/11/2023
;
; Modinfo:
; 11/07/2023: Fixed *BYE for ADL mode
; 15/11/2023: Improved OSLOAD_TXT; now handles LF terminated files, files with no trailing LF or CR/LF at end
.ASSUME ADL = 1
INCLUDE "equs.inc"
INCLUDE "macros.inc"
INCLUDE "mos_api.inc" ; In MOS/src
SEGMENT CODE
XDEF OSWRCH
XDEF OSLINE
XDEF ESCSET
XDEF PUTIME
XDEF GETIME
XDEF PUTCSR
XDEF GETCSR
XDEF OSRDCH
XDEF PROMPT
XDEF OSKEY
XDEF TRAP
XDEF LTRAP
XDEF OSINIT
XDEF OSCLI
XDEF OSBPUT
XDEF OSBGET
XDEF OSSTAT
XDEF OSSHUT
XDEF OSOPEN
XDEF OSCALL
XDEF GETPTR
XDEF PUTPTR
XDEF GETEXT
XDEF GETIMS
XDEF RESET
XDEF OSLOAD
XDEF OSSAVE
XDEF EXPR_W2
XDEF STAR_VERSION
XREF _end ; In init.asm
XREF ASC_TO_NUMBER
XREF RAM_START
XREF RAM_END
XREF FLAGS
XREF ESCAPE
XREF USER
XREF RAM_Top
XREF EXTERR
XREF COUNT0
XREF EXPRI
XREF COMMA
XREF XEQ
XREF NXT
XREF NULLTOCR
XREF CRLF
XREF CSTR_FNAME
XREF CSTR_LINE
XREF CSTR_FINDCH
XREF CSTR_ENDSWITH
XREF CSTR_CAT
XREF FINDL
XREF OUT_
XREF ERROR_
XREF ONEDIT
XREF TELL
XREF OSWRCHPT
XREF OSWRCHCH
XREF OSWRCHFH
XREF LISTON
XREF LISTIT
XREF PAGE_
XREF ONEDIT1
XREF CLEAN
XREF NEWIT
XREF BAD
XREF VBLANK_INIT
XREF VBLANK_STOP
XREF KEYDOWN
XREF KEYASCII
XREF WIDTH
XREF ASSEM
; OSLINE: Invoke the line editor
;
OSLINE: LD E, 1 ; Default is to clear the buffer
; Entry point to line editor that does not clear the buffer
;
OSLINE1: PUSH IY
PUSH HL ; Buffer address
LD BC, 256 ; Buffer length
MOSCALL mos_editline ; Call the MOS line editor
POP HL ; Pop the address
POP IY
PUSH AF ; Stack the return value (key pressed)
CALL NULLTOCR ; Turn the 0 character to a CR
CALL CRLF ; Display CRLF
POP AF
CP 1Bh ; Check if ESC terminated the input
JP Z, LTRAP1 ; Yes, so do the ESC thing
LD A, (FLAGS) ; Otherwise
RES 7, A ; Clear the escape flag
LD (FLAGS), A
CALL WAIT_VBLANK ; Wait a frame
XOR A ; Return A = 0
LD (KEYDOWN), A
LD (KEYASCII), A
RET
; PUTIME: set current time to DE:HL, in centiseconds.
;
PUTIME: PUSH IX
MOSCALL mos_sysvars
LD (IX + sysvar_time + 0), L
LD (IX + sysvar_time + 1), H
LD (IX + sysvar_time + 2), E
LD (IX + sysvar_time + 3), D
POP IX
RET
; GETIME: return current time in DE:HL, in centiseconds
;
GETIME: PUSH IX
MOSCALL mos_sysvars
LD L, (IX + sysvar_time + 0)
LD H, (IX + sysvar_time + 1)
LD E, (IX + sysvar_time + 2)
LD D, (IX + sysvar_time + 3)
POP IX
RET
; PUTCSR: move to cursor to x=DE, y=HL
;
PUTCSR: LD A, 1Fh ; TAB
RST.LIL 10h
LD A, E ; X
RST.LIL 10h
LD A, L ; Y
RST.LIL 10h
RET
; GETCSR: return cursor position in x=DE, y=HL
;
GETCSR: PUSH IX ; Get the system vars in IX
MOSCALL mos_sysvars ; Reset the semaphore
RES 0, (IX+sysvar_vpd_pflags)
VDU 23
VDU 0
VDU vdp_cursor
$$: BIT 0, (IX+sysvar_vpd_pflags)
JR Z, $B ; Wait for the result
LD D, 0
LD H, D
LD E, (IX + sysvar_cursorX)
LD L, (IX + sysvar_cursorY)
POP IX
RET
; PROMPT: output the input prompt
;
PROMPT: LD A,'>'
JP OSWRCH
; OSWRCH: Write a character out to the ESP32 VDU handler via the MOS
; A: Character to write
;
OSWRCH: PUSH HL
LD HL, LISTON ; Fetch the LISTON variable
BIT 3, (HL) ; Check whether we are in *EDIT mode
JR NZ, OSWRCH_BUFFER ; Yes, so just output to buffer
;
LD HL, (OSWRCHCH) ; L: Channel #
DEC L ; If it is 1
JR Z, OSWRCH_FILE ; Then we are outputting to a file
;
POP HL ; Otherwise
RST.LIL 10h ; Output the character to MOS
RET
;
OSWRCH_BUFFER: LD HL, (OSWRCHPT) ; Fetch the pointer buffer
LD (HL), A ; Echo the character into the buffer
INC HL ; Increment pointer
LD (OSWRCHPT), HL ; Write pointer back
POP HL
RET
;
OSWRCH_FILE: PUSH DE
LD E, H ; Filehandle to E
CALL OSBPUT ; Write the byte out
POP DE
POP HL
RET
; OSRDCH: Read a character in from the ESP32 keyboard handler
; This is only called in GETS (eval.asm)
;
OSRDCH: MOSCALL mos_getkey ; Read keyboard
CP 1Bh
JR Z, LTRAP1
RET
;OSKEY - Read key with time-limit, test for ESCape.
;Main function is carried out in user patch.
; Inputs: HL = time limit (centiseconds)
; Outputs: Carry reset if time-out
; If carry set A = character
; Destroys: A,H,L,F
;
OSKEY: CALL READKEY ; Read the keyboard
JR Z, $F ; Skip if we have a key
LD A, H ; Check loop counter
OR L
RET Z ; Return, we've not got a key at this point
CALL WAIT_VBLANK ; Wait a frame
DEC HL ; Decrement
JR OSKEY ; And loop
;
$$: LD HL, KEYDOWN ; We have a key, so
LD (HL), 0 ; clear the keydown flag
CP 1BH ; If we are not pressing ESC,
SCF ; then flag we've got a character
RET NZ
;
; ESCSET
; Set the escape flag (bit 7 of FLAGS = 1) if escape is enabled (bit 6 of FLAGS = 0)
;
ESCSET: PUSH HL
LD HL,FLAGS ; Pointer to FLAGS
BIT 6,(HL) ; If bit 6 is set, then
JR NZ,ESCDIS ; escape is disabled, so skip
SET 7,(HL) ; Set bit 7, the escape flag
ESCDIS: POP HL
RET
;
; ESCTEST
; Test for ESC key
;
ESCTEST: CALL READKEY ; Read the keyboard
RET NZ ; Skip if no key is pressed
CP 1BH ; If ESC pressed then
JR Z,ESCSET ; jump to the escape set routine
RET
; Read the keyboard
; Returns:
; - A: ASCII of the pressed key
; - F: Z if the key is pressed, otherwise NZ
;
READKEY: LD A, (KEYDOWN) ; Get key down
DEC A ; Set Z flag if keydown is 1
LD A, (KEYASCII) ; Get key ASCII value
RET
;
; TRAP
; This is called whenever BASIC needs to check for ESC
;
TRAP: CALL ESCTEST ; Read keyboard, test for ESC, set FLAGS
;
LTRAP: LD A,(FLAGS) ; Get FLAGS
OR A ; This checks for bit 7; if it is not set then the result will
RET P ; be positive (bit 7 is the sign bit in Z80), so return
LTRAP1: LD HL,FLAGS ; Escape is pressed at this point, so
RES 7,(HL) ; Clear the escape pressed flag and
JP ESCAPE ; Jump to the ESCAPE error routine in exec.asm
;OSINIT - Initialise RAM mapping etc.
;If BASIC is entered by BBCBASIC FILENAME then file
;FILENAME.BBC is automatically CHAINed.
; Outputs: DE = initial value of HIMEM (top of RAM)
; HL = initial value of PAGE (user program)
; Z-flag reset indicates AUTO-RUN.
; Destroys: A,D,E,H,L,F
;
OSINIT: CALL VBLANK_INIT
XOR A
LD HL, USER
LD DE, RAM_Top
LD E, A ; Page boundary
RET
;
;OSCLI - Process a MOS command
;
OSCLI: CALL SKIPSP
CP CR
RET Z
CP '|'
RET Z
EX DE,HL
LD HL,COMDS
OSCLI0: LD A,(DE)
CALL UPPRC
CP (HL)
JR Z,OSCLI2
JR C,OSCLI6
OSCLI1: BIT 7,(HL)
INC HL
JR Z,OSCLI1
INC HL
INC HL
JR OSCLI0
;
OSCLI2: PUSH DE
OSCLI3: INC DE
INC HL
LD A,(DE)
CALL UPPRC
CP '.' ; ABBREVIATED?
JR Z,OSCLI4
XOR (HL)
JR Z,OSCLI3
CP 80H
JR Z,OSCLI4
POP DE
JR OSCLI1
;
OSCLI4: POP AF
INC DE
OSCLI5: BIT 7,(HL)
INC HL
JR Z,OSCLI5
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
PUSH HL
EX DE,HL
JP SKIPSP
;
OSCLI6: EX DE, HL ; HL: Buffer for command
LD DE, ACCS ; Buffer for command string is ACCS (the string accumulator)
PUSH DE ; Store buffer address
CALL CSTR_LINE ; Fetch the line
POP HL ; HL: Pointer to command string in ACCS
PUSH IY
MOSCALL mos_oscli ; Returns OSCLI error in A
POP IY
OR A ; 0 means MOS returned OK
RET Z ; So don't do anything
JP OSERROR ; Otherwise it's a MOS error
HUH: LD A,254 ; Bad command error
CALL EXTERR
DB "Bad command"
DEFB 0
SKIPSP: LD A,(HL)
CP ' '
RET NZ
INC HL
JR SKIPSP
UPPRC: AND 7FH
CP '`'
RET C
AND 5FH ; CONVERT TO UPPER CASE
RET
; Each command has bit 7 of the last character set, and is followed by the address of the handler
; These must be in alphabetical order
;
COMDS: DB 'AS','M'+80h ; ASM
DW STAR_ASM
DB 'BY','E'+80h ; BYE
DW STAR_BYE
DB 'EDI','T'+80h ; EDIT
DW STAR_EDIT
DB 'F','X'+80h ; FX
DW STAR_FX
DB 'VERSIO','N'+80h ; VERSION
DW STAR_VERSION
DB FFh
; *ASM string
;
STAR_ASM: PUSH IY ; Stack the BASIC pointer
PUSH HL ; HL = IY
POP IY
CALL ASSEM ; Invoke the assembler
POP IY
RET
; *BYE
;
STAR_BYE: CALL VBLANK_STOP ; Restore MOS interrupts
LD HL, 0 ; The return value
JP _end ; Jump back to the end routine in init.asm
; *VERSION
;
STAR_VERSION: CALL TELL ; Output the welcome message
DB "BBC BASIC (Agon ADL) Version 1.03\n\r",0
RET
; *EDIT linenum
;
STAR_EDIT: CALL ASC_TO_NUMBER ; DE: Line number to edit
EX DE, HL ; HL: Line number
CALL FINDL ; HL: Address in RAM of tokenised line
LD A, 41 ; F:NZ If the line is not found
JP NZ, ERROR_ ; Do error 41: No such line in that case
;
; Use LISTIT to output the line to the ACCS buffer
;
INC HL ; Skip the length byte
LD E, (HL) ; Fetch the line number
INC HL
LD D, (HL)
INC HL
LD IX, ACCS ; Pointer to where the copy is to be stored
LD (OSWRCHPT), IX
LD IX, LISTON ; Pointer to LISTON variable in RAM
LD A, (IX) ; Store that variable
PUSH AF
LD (IX), 09h ; Set to echo to buffer
CALL LISTIT
POP AF
LD (IX), A ; Restore the original LISTON variable
LD HL, ACCS ; HL: ACCS
LD E, L ; E: 0 - Don't clear the buffer; ACCS is on a page boundary so L is 0
CALL OSLINE1 ; Invoke the editor
JP ONEDIT ; Jump back to the BASIC loop just after the normal line edit
; OSCLI FX n
;
STAR_FX: CALL ASC_TO_NUMBER
LD C, E ; C: Save FX #
CALL ASC_TO_NUMBER
LD A, D ; Is first parameter > 255?
OR A
JR Z, STAR_FX1 ; Yes, so skip next bit
EX DE, HL ; Parameter is 16-bit
JR STAR_FX2
;
STAR_FX1: LD B, E ; B: Save First parameter
CALL ASC_TO_NUMBER ; Fetch second parameter
LD L, B ; L: First parameter
LD H, E ; H: Second parameter
;
STAR_FX2: LD A, C ; A: FX #, and fall through to OSBYTE
;
; OSBYTE
; A: FX #
; L: First parameter
; H: Second parameter
;
OSBYTE: CP 0BH ; *FX 11, n: Keyboard auto-repeat delay
JR Z, OSBYTE_0B
CP 0CH ; *FX 12, n: Keyboard auto-repeat rate
JR Z, OSBYTE_0C
CP 13H ; *FX 19: Wait for vblank
JR Z, OSBYTE_13
CP 76H ; *FX 118, n: Set keyboard LED
JP Z, OSBYTE_76
CP A0H
JP Z, OSBYTE_A0
JP HUH ; Anything else trips an error
; OSBYTE 0x0B (FX 11,n): Keyboard auto-repeat delay
; Parameters:
; - HL: Repeat delay
;
OSBYTE_0B: VDU 23
VDU 0
VDU vdp_keystate
VDU L
VDU H
VDU 0
VDU 0
VDU 255
RET
; OSBYTE 0x0C (FX 12,n): Keyboard auto-repeat rate
; Parameters:
; - HL: Repeat rate
;
OSBYTE_0C: VDU 23
VDU 0
VDU vdp_keystate
VDU 0
VDU 0
VDU L
VDU H
VDU 255
RET
; OSBYTE 0x13 (FX 19): Wait for vertical blank interrupt
;
OSBYTE_13: CALL WAIT_VBLANK
LD L, 0 ; Returns 0
JP COUNT0
;
WAIT_VBLANK: PUSH IX ; Wait for VBLANK interrupt
MOSCALL mos_sysvars ; Fetch pointer to system variables
LD A, (IX + sysvar_time + 0)
$$: CP A, (IX + sysvar_time + 0)
JR Z, $B
POP IX
RET
; OSBYTE 0x76 (FX 118,n): Set Keyboard LED
; Parameters:
; - L: LED (Bit 0: Scroll Lock, Bit 1: Caps Lock, Bit 2: Num Lock)
;
OSBYTE_76: VDU 23
VDU 0
VDU vdp_keystate
VDU 0
VDU 0
VDU 0
VDU 0
VDU L
RET
; OSBYTE 0xA0: Fetch system variable
; Parameters:
; - L: The system variable to fetch
;
OSBYTE_A0: PUSH IX
MOSCALL mos_sysvars ; Fetch pointer to system variables
LD BC, 0
LD C, L ; BCU = L
ADD IX, BC ; Add to IX
LD L, (IX + 0) ; Fetch the return value
POP IX
JP COUNT0
;OSLOAD - Load an area of memory from a file.
; Inputs: HL addresses filename (CR terminated)
; DE = address at which to load
; BC = maximum allowed size (bytes)
; Outputs: Carry reset indicates no room for file.
; Destroys: A,B,C,D,E,H,L,F
;
OSLOAD: PUSH BC ; Stack the size
PUSH DE ; Stack the load address
LD DE, ACCS ; Buffer address for filename
CALL CSTR_FNAME ; Fetch filename from MOS into buffer
LD HL, ACCS ; HL: Filename
CALL EXT_DEFAULT ; Tack on the extension .BBC if not specified
CALL EXT_HANDLER ; Get the default handler
POP DE ; Restore the load address
POP BC ; Restore the size
OR A
JR Z, OSLOAD_BBC
;
; Load the file in as a text file
;
OSLOAD_TXT: XOR A ; Set file attributes to read
CALL OSOPEN ; Open the file
LD E, A ; The filehandle
OR A
LD A, 4 ; File not found error
JR Z, OSERROR ; Jump to error handler
CALL NEWIT ; Call NEW to clear the program space
;
OSLOAD_TXT1: LD HL, ACCS ; Where the input is going to be stored
;
; First skip any whitespace (indents) at the beginning of the input
;
$$: CALL OSBGET ; Read the byte into A
JR C, OSLOAD_TXT3 ; Is it EOF?
CP LF ; Is it LF?
JR Z, OSLOAD_TXT3 ; Yes, so skip to the next line
CP 21h ; Is it less than or equal to ASCII space?
JR C, $B ; Yes, so keep looping
LD (HL), A ; Store the first character
INC L
;
; Now read the rest of the line in
;
OSLOAD_TXT2: CALL OSBGET ; Read the byte into A
JR C, OSLOAD_TXT4 ; Is it EOF?
CP 20h ; Skip if not an ASCII character
JR C, $F
LD (HL), A ; Store in the input buffer
INC L ; Increment the buffer pointer
JP Z, BAD ; If the buffer is full (wrapped to 0) then jump to Bad Program error
$$: CP LF ; Check for LF
JR NZ, OSLOAD_TXT2 ; If not, then loop to read the rest of the characters in
;
; Finally, handle EOL/EOF
;
OSLOAD_TXT3: LD (HL), CR ; Store a CR for BBC BASIC
LD A, L ; Check for minimum line length
CP 2 ; If it is 2 characters or less (including CR)
JR C, $F ; Then don't bother entering it
PUSH DE ; Preserve the filehandle
CALL ONEDIT1 ; Enter the line in memory
CALL C,CLEAN ; If a new line has been entered, then call CLEAN to set TOP and write &FFFF end of program marker
POP DE
$$: CALL OSSTAT ; End of file?
JR NZ, OSLOAD_TXT1 ; No, so loop
CALL OSSHUT ; Close the file
SCF ; Flag to BASIC that we're good
RET
;
; Special case for BASIC programs with no blank line at the end
;
OSLOAD_TXT4: CP 20h ; Skip if not an ASCII character
JR C, $F
LD (HL), A ; Store the character
INC L
JP Z, BAD
$$: JR OSLOAD_TXT3
;
; Load the file in as a tokenised binary blob
;
OSLOAD_BBC: MOSCALL mos_load ; Call LOAD in MOS
RET NC ; If load returns with carry reset - NO ROOM
OR A ; If there is no error (A=0)
SCF ; Need to set carry indicating there was room
RET Z ; Return
;
OSERROR: PUSH AF ; Handle the MOS error
LD HL, ACCS ; Address of the buffer
LD BC, 256 ; Length of the buffer
LD E, A ; The error code
MOSCALL mos_getError ; Copy the error message into the buffer
POP AF
PUSH HL ; Stack the address of the error (now in ACCS)
ADD A, 127 ; Add 127 to the error code (MOS errors start at 128, and are trappable)
JP EXTERR ; Trigger an external error
;OSSAVE - Save an area of memory to a file.
; Inputs: HL addresses filename (term CR)
; DE = start address of data to save
; BC = length of data to save (bytes)
; Destroys: A,B,C,D,E,H,L,F
;
OSSAVE: PUSH BC ; Stack the size
PUSH DE ; Stack the save address
LD DE, ACCS ; Buffer address for filename
CALL CSTR_FNAME ; Fetch filename from MOS into buffer
LD HL, ACCS ; HL: Filename
CALL EXT_DEFAULT ; Tack on the extension .BBC if not specified
CALL EXT_HANDLER ; Get the default handler
POP DE ; Restore the save address
POP BC ; Restore the size
OR A ; Is the extension .BBC
JR Z, OSSAVE_BBC ; Yes, so use that
;
; Save the file out as a text file
;
OSSAVE_TXT: LD A, (OSWRCHCH) ; Stack the current channel
PUSH AF
XOR A
INC A ; Make sure C is clear, A is 1, for OPENOUT
LD (OSWRCHCH), A
CALL OSOPEN ; Open the file
LD (OSWRCHFH), A ; Store the file handle for OSWRCH
LD IX, LISTON ; Required for LISTIT
LD HL, (PAGE_) ; Get start of program area
EXX
LD BC, 0 ; Set the initial indent counters
EXX
OSSAVE_TXT1: LD A, (HL) ; Check for end of program marker
OR A
JR Z, OSSAVE_TXT2
INC HL ; Skip the length byte
LD DE, 0 ; Clear DE to ensure we get a 16-bit line number
LD E, (HL) ; Get the line number
INC HL
LD D, (HL)
INC HL
CALL LISTIT ; List the line
JR OSSAVE_TXT1
OSSAVE_TXT2: LD A, (OSWRCHFH) ; Get the file handle
LD E, A
CALL OSSHUT ; Close it
POP AF ; Restore the channel
LD (OSWRCHCH), A
RET
;
; Save the file out as a tokenised binary blob
;
OSSAVE_BBC: MOSCALL mos_save ; Call SAVE in MOS
OR A ; If there is no error (A=0)
RET Z ; Just return
JR OSERROR ; Trip an error
; Check if an extension is specified in the filename
; Add a default if not specified
; HL: Filename (CSTR format)
;
EXT_DEFAULT: PUSH HL ; Stack the filename pointer
LD C, '.' ; Search for dot (marks start of extension)
CALL CSTR_FINDCH
OR A ; Check for end of string marker
JR NZ, $F ; No, so skip as we have an extension at this point
LD DE, EXT_LOOKUP ; Get the first (default extension)
CALL CSTR_CAT ; Concat it to string pointed to by HL
$$: POP HL ; Restore the filename pointer
RET
; Check if an extension is valid and, if so, provide a pointer to a handler
; HL: Filename (CSTR format)
; Returns:
; A: Filename extension type (0=BBC tokenised, 1=ASCII untokenised)
;
EXT_HANDLER: PUSH HL ; Stack the filename pointer
LD C, '.' ; Find the '.'
CALL CSTR_FINDCH
LD DE, EXT_LOOKUP ; The lookup table
;
EXT_HANDLER_1: PUSH HL ; Stack the pointer to the extension
CALL CSTR_ENDSWITH ; Check whether the string ends with the entry in the lookup
POP HL ; Restore the pointer to the extension
JR Z, EXT_HANDLER_2 ; We have a match!
;
$$: LD A, (DE) ; Skip to the end of the entry in the lookup
INC DE
OR A
JR NZ, $B
INC DE ; Skip the file extension # byte
;
LD A, (DE) ; Are we at the end of the table?
OR A
JR NZ, EXT_HANDLER_1 ; No, so loop
;
LD A,204 ; Throw a "Bad name" error
CALL EXTERR
DB "Bad name", 0
;
EXT_HANDLER_2: INC DE ; Skip to the file extension # byte
LD A, (DE)
POP HL ; Restore the filename pointer
RET
;
; Extension lookup table
; CSTR, TYPE
; - 0: BBC (tokenised BBC BASIC for Z80 format)
; - 1: Human readable plain text
;
EXT_LOOKUP: DB '.BBC', 0, 0 ; First entry is the default extension
DB '.TXT', 0, 1
DB '.ASC', 0, 1
DB '.BAS', 0, 1
DB 0 ; End of table
;OSCALL - Intercept page &FF calls and provide an alternative address
;
;&FFF7: OSCLI Execute *command.
;&FFF4: OSBYTE Various byte-wide functions.
;&FFF1: OSWORD Various control block functions.
;&FFEE: OSWRCH Write character to output stream.
;&FFE7: OSNEWL Write NewLine to output stream.
;&FFE3: OSASCI Write character or NewLine to output stream.
;&FFE0: OSRDCH Wait for character from input stream.
;&FFDD: OSFILE Perform actions on whole files or directories.
;&FFDA: OSARGS Read and write information on open files or filing systems.
;&FFD7: OSBGET Read a byte from an a channel.
;&FFD4: OSBPUT Write a byte to a channel.
;&FFD1: OSGBPB Read and write blocks of data.
;&FFCE: OSFIND Open or close a file.
;
OSCALL: LD HL, OSCALL_TABLE
OSCALL_1: LD A, (HL)
INC HL
CP FFh
RET Z
CP A, IYL
JR Z, OSCALL_2
RET NC
INC HL
INC HL
INC HL
JR OSCALL_1
OSCALL_2: LD IY,(HL)
RET
OSCALL_TABLE: DB D4h
DW24 OSBPUT
DB D7h
DW24 OSBGET
DB EEh
DW24 OSWRCH
DB F4h
DW24 OSBYTE
DB F7h
DW24 OSCLI
DB FFh
; OSOPEN
; HL: Pointer to path
; F: C Z
; x x OPENIN
; OPENOUT
; x OPENUP
; Returns:
; A: Filehandle, 0 if cannot open
;
OSOPEN: LD C, fa_read
JR Z, $F
LD C, fa_write | fa_open_append
JR C, $F
LD C, fa_write | fa_create_always
$$: MOSCALL mos_fopen
RET
;OSSHUT - Close disk file(s).
; E = file channel
; If E=0 all files are closed (except SPOOL)
; Destroys: A,B,C,D,E,H,L,F
;
OSSHUT: PUSH BC
LD C, E
MOSCALL mos_fclose
POP BC
RET
; OSBGET - Read a byte from a random disk file.
; E = file channel
; Returns
; A = byte read
; Carry set if LAST BYTE of file
; Destroys: A,B,C,F
;
OSBGET: PUSH BC
LD C, E
MOSCALL mos_fgetc
POP BC
RET
; OSBPUT - Write a byte to a random disk file.
; E = file channel
; A = byte to write
; Destroys: A,B,C,F
;
OSBPUT: PUSH BC
LD C, E
LD B, A
MOSCALL mos_fputc
POP BC
RET
; OSSTAT - Read file status
; E = file channel
; Returns
; F: Z flag set - EOF
; A: If Z then A = 0
; Destroys: A,D,E,H,L,F
;
OSSTAT: PUSH BC
LD C, E
MOSCALL mos_feof
POP BC
CP 1
RET
; GETPTR - Return file pointer.
; E = file channel
; Returns:
; DEHL = pointer (0-&7FFFFF)
; Destroys: A,B,C,D,E,H,L,F
;
GETPTR: PUSH IY
LD C, E
MOSCALL mos_getfil ; HLU: Pointer to FIL structure
PUSH HL
POP IY ; IYU: Pointer to FIL structure
LD L, (IY + FIL.fptr + 0)
LD H, (IY + FIL.fptr + 1)
LD E, (IY + FIL.fptr + 2)
LD D, (IY + FIL.fptr + 3)
POP IY
RET
; PUTPTR - Update file pointer.
; A = file channel
; DEHL = new pointer (0-&7FFFFF)
; Destroys: A,B,C,D,E,H,L,F
;
PUTPTR: PUSH IY
LD C, A ; C: Filehandle
PUSH HL
LD HL, 2
ADD HL, SP
LD (HL), E ; 3rd byte of DWORD set to E
POP HL
LD E, D ; 4th byte passed as E
MOSCALL mos_flseek
POP IY
RET
; GETEXT - Find file size.
; E = file channel
; Returns:
; DEHL = file size (0-&800000)
; Destroys: A,B,C,D,E,H,L,F
;
GETEXT: PUSH IY
LD C, E
MOSCALL mos_getfil ; HLU: Pointer to FIL structure
PUSH HL
POP IY ; IYU: Pointer to FIL structure
LD L, (IY + FIL.obj.objsize + 0)
LD H, (IY + FIL.obj.objsize + 1)
LD E, (IY + FIL.obj.objsize + 2)
LD D, (IY + FIL.obj.objsize + 3)
POP IY
RET
; GETIMS - Get time from RTC
;
GETIMS: PUSH IY
LD HL, ACCS ; Where to store the time string
MOSCALL mos_getrtc
LD DE, ACCS ; DE: pointer to start of string accumulator
LD E, A ; E: now points to the end of the string
POP IY
RET
; Get two word values from EXPR in DE, HL
; IY: Pointer to expression string
; Returns:
; DE: P1
; HL: P2
;
EXPR_W2: CALL EXPRI ; Get first parameter
EXX
PUSH HL
CALL COMMA
CALL EXPRI ; Get second parameter
EXX
POP DE
RET
; Stuff not implemented yet
;
RESET: RET