Produce formatted listings of BBC BASIC programs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bbc-lst2/LST2.bas.txt

561 lines
22 KiB

NOTE: This is a formatted listing of the program, and won't run like this.
10 REM This program provides a formatted listing of a BASIC program,
11 REM in particular, indenting loops and splitting multiple statements
12 REM out onto separate lines.
13 REM Since it calls into the BASIC ROM, it requires BASIC 2.
14
20 REM It lives at &08D0-0AFF, which is normally used for the following:
21 REM 800-8FF: sound workspace & buffers, printer buffer, envelope storage
22 REM 900-9FF: envelope storage, RS423/speech/cassette output buffers
23 REM A00-AFF: cassette/RS423 input buffers
24
30 REM Run this program to assemble it, and save the generated machine code.
31
40 REM Enter LIST commands as normal, but with a trailing period e.g.
41 REM LIST 100,200.
42 REM LIST ,200.
43 REM LIST 100,.
44 REM LIST.
45
50 REM NOTE: Page references in the source code are for "The BBC Micro
51 REM Compendium", by Jeremy Ruston.
52
100 REM === VARIABLES =======================================================
101
102 REM We borrow some zero-page locations normally used by BASIC (p170).
103
110 REM When when we're parsing the input command, this points into
111 REM the keyboard buffer (&0700-07FF), then we switch to the program code
112 REM when we start printing the listing.
113 pinput = &4B
: REM and &4C
114
120 REM This points into the program code being listed (only while
121 REM we're parsing the input command).
122 pcode = &3B
: REM and &3C
123 REM After parsing the input command, we use these bytes as temp storage.
124 temp1 = &3B
: temp2 = &3C
125
130 REM This holds the current line# being listed.
131 curr_lineno = &2A
: REM and &2B
132
140 REM This holds the last line# to be listed.
141 last_lineno = &2E
: REM and &2F
142
150 REM This holds the current indent.
151 REM The first 5 columns are used for the line#, followed by a space,
152 REM then the code itself starts at col.6
153 curr_indent = &30
154
160 REM This flags if we are in a REM statement.
161 in_rem = &31
162
170 REM This flags if we are in a double-quoted string.
171 in_quotes = &32
172
180 REM This flags if we are in BASIC code (0 means we are in assembly code).
181 in_basic = &33
182
190 REM This flags if we are in an assembly label.
191 in_asm_label = &34
192
200 REM This flags if the current line has not been indented yet.
201 indent_pending = &35
202
900 osbyte = &FFF4
: oswrch = &FFEE
: osnewl = &FFE7
910
1000 FOR opt = 0 TO 3 STEP 3
1010 BASE = &08D0
: P% = BASE
1020 [ OPT opt
1030
1100 \ === MAIN ENTRY POINT ====================================================
1101
1102 \ Adding a trailing "." to a LIST command will generate a syntax error,
1103 \ so we replace the default BRK error handler with ours, so that we can
1104 \ check for this.
1105
1110 \ install our custom BRK handler
1120 LDA #(brkHandler MOD 256)
1130 STA &0202
1140 LDA #(brkHandler DIV 256)
1150 STA &0203
1160
1170 \ beep, then exit
1180 LDA #7
1190 JMP oswrch
1200
1500 \ === CUSTOM ERROR HANDLER ================================================
1501
1502 \ We check for our special LIST command here.
1503
1510 .brkHandler
1520 \ check for a "Bad program"
1530 JSR &BE6F
1540
1550 \ if the error was not "Syntax error" (error code 16), then jump to
1551 \ the normal BRK handler
1560 LDY #0
1570 LDA (&FD),Y
1580 CMP #16
1590 BEQ P%+5
1600 .defaultBRK
1610 JMP &B402
1620
2000 \ initialize our variables
2001 \ PINPUT <- &0700
2002 \ PCODE <- PAGE
2003 \ CURR_INDENT <- 6
2004 \ IN_BASIC <- &FF
2005 \ LAST_LINENO <- &7FFF (in case a line# isn't explicitly set)
2010 STY pinput
2020 STY pcode
2030 LDA &18
2040 STA pcode+1
2050 LDX #7
2060 STX pinput+1
2070 DEX
2080 STX curr_indent
2090 DEY
2100 STY in_basic
2110 STY last_lineno
2120 LDY #&7F
2130 STY last_lineno+1
2140
2150 \ if the next non-space byte in the keyboard buffer is not the LIST keyword,
2151 \ then jump to the normal BRK handler
2160 JSR skipSpaces
2170 CMP #&C9
2180 BNE defaultBRK
2190
2200 \ if the next non-space byte is a ".", then skip parsing line numbers (the user
2201 \ wants to "LIST." the entire program)
2210 JSR skipSpaces
2220 CMP #&2C
2230 BEQ parseEndLineNo
2240
2250 \ if the byte is &8D (pseudo-keyword for a line#), then decode the line#
2251 \ into CURR_LINENO
2260 CMP #&8D
2270 BNE endParseInput
2280 JSR decodeLineNo
2290
2300 \ find the start of the specified line in the program code
2301 \ NOTE - Program lines are stored as follows
2302 \ &0D
2303 \ line# (MSB, LSB)
2304 \ line length (1 byte, the count includes the 3 leading bytes)
2305 \ The end of the program is stored as
2306 \ &0D &FF
2310 .checkNextLine
2320 \ check if we've found the first line to be listed (i.e. the line# of
2321 \ the line pointed to by PCODE is >= CURR_LINENO)
2330 LDY #2
2340 LDA (pcode),Y \ this is the LSB of the current line#
2350 SEC
2360 SBC curr_lineno
2370 DEY
2380 LDA (pcode),Y \ this is the MSB of the current line#
2390 BPL P%+5 \ negative line# MSB = end-of-program marker
2400 JMP done
2410 SBC curr_lineno+1
2420 BPL foundFirstLine
2430 \ nope - move to the next line
2440 LDY #3
2450 LDA (pcode),Y \ this is the line length
2460 CLC
2470 ADC pcode
2480 STA pcode
2490 BCC P%+4
2500 INC pcode+1
2510 JMP checkNextLine
2520
2530 .foundFirstLine
2540 \ if the next non-space byte in the keyboard buffer is ",", then parse
2541 \ the end line#
2550 JSR skipSpaces
2560 CMP #&2C
2570 BEQ parseEndLineNo
2580
2590 \ if the byte is not ".", then jump to the normal BRK handler
2600 CMP #&2E
2610 BNE defaultBRK
2620
2630 \ the input was of the form "LIST 12345.", so set the last line#
2631 \ to be the same as the first line#
2640 LDA curr_lineno
2650 STA last_lineno
2660 LDA curr_lineno+1
2670 STA last_lineno+1
2680 JMP parseEOL
2690
2700 .parseEndLineNo
2710 \ if the next non-space byte in the keyboard buffer is &8D (pseudo-keyword
2711 \ for a line#), then decode the line# into LAST_LINENO
2712 \ NOTE - While decodeLineNo stores the result in CURR_LINENO (thus
2713 \ corrupting it), the main loop extracts the line# each time it starts
2714 \ a new line of program code, and so will restore it when it starts
2715 \ processing the first line.
2720 JSR skipSpaces
2730 CMP #&8D
2740 BNE endParseInput
2750 JSR decodeLineNo
2760 STA last_lineno+1
2770 LDA curr_lineno
2780 STA last_lineno
2790 JSR skipSpaces
2800
2810 .endParseInput
2820 \ check for the trailing "."
2830 CMP #&2E
2840 BEQ P%+5
2850 JMP defaultBRK
2860
2870 .parseEOL
2880 \ check for the terminating CR
2890 JSR skipSpaces
2900 CMP #&0D
2910 BEQ P%+5
2920 JMP defaultBRK
2930
2940 \ start taking input from the program code i.e. PINPUT <- PCODE
2950 LDA pcode
2960 STA pinput
2970 LDA pcode+1
2980 STA pinput+1
2990
5000 \ === MAIN LOOP ===========================================================
5001
5002 \ We now start printing the listing, by stepping through each byte of
5003 \ the program code, deciding how to print it out, until we go past LAST_LINENO.
5004
5010 .processNextByte
5020 \ check if we're at the start of a line in the program code
5030 JSR getNextByte
5040 CMP #&0D
5050 BNE printCode
5060
5070 \ yup - print a newline, save the line# in CURR_LINENO
5080 JSR osnewl
5090 JSR getNextByte
5100 STA curr_lineno+1
5110 JSR getNextByte
5120 STA curr_lineno
5130
5140 \ skip over the line length byte
5150 JSR getNextByte
5160
5170 \ check if we're done (CURR_LINENO > LAST_LINENO)
5180 LDA last_lineno
5190 SEC
5200 SBC curr_lineno
5210 LDA last_lineno+1
5220 SBC curr_lineno+1
5230 BMI done
5240
5390 \ print the current line# (with a field width of 5)
5400 JSR &9923
5410
5420 \ print a space
5430 LDA #&20
5440 JSR oswrch
5450
5460 .startNextLine
5470 \ initialize for the next line of program code, then loop back for the next byte
5471 \ IN_REM <- 0
5472 \ IN_QUOTES <- 0
5473 \ IN_ASM_LABEL <- 0
5474 \ INDENT_PENDING <- &FF
5480 LDX #0
5490 STX in_rem
5500 STX in_quotes
5510 STX in_asm_label
5520 DEX
5530 STX indent_pending
5540 BMI processNextByte
5550
5560 .done
5570 \ we're all done - warm-start BASIC
5580 JSR osnewl
5590 JMP &8AF3
5600
5670 \ we now print out the next byte of program code (in A)
5671
5680 .printCode
5690
5700 \ if we have a line# (pseudo-keyword &8D), then decode and print it out
5710 CMP #&8D
5720 BNE P%+11
5730 JSR decodeLineNo
5740 JSR &991F \ this prints the IAC as a 16-bit number
5750 JMP processNextByte
5760
5770 \ if we have a double-quote, then toggle the IN_QUOTES flag
5780 CMP #&22
5790 BNE P%+10
5800 LDA in_quotes
5810 EOR #&FF
5820 STA in_quotes
5830 LDA #&22
5840
5850 \ if we are currently in a quoted string or a REM statement,
5851 \ then output the current byte verbatim
5860 BIT in_rem
5870 BMI P%+6
5880 BIT in_quotes
5890 BPL P%+5
5900 JMP outputByte
5910
5920 \ if we have a colon, then print a newline, indent, print a colon
5930 CMP #&3A
5940 BNE P%+18
5950 JSR osnewl
5960 LDX #5
5970 JSR indent
5980 LDA #&3A
5990 JSR oswrch
6000 JMP startNextLine
6010
6020 \ if we are currently in assembly code, then jump to handle that
6030 BIT in_basic
6040 BPL checkAssembly
6050
6060 \ if we have a REM token, then update the flag
6070 CMP #&F4
6080 BNE P%+6
6090 LDX #&FF
6100 STX in_rem
6110
6120 \ check if we have a NEXT or UNTIL token
6130 CMP #&ED
6140 BEQ P%+6
6150 CMP #&FD
6160 BNE postNextUntil
6170 \ yup - decrease the current level of indentation
6180 JSR dedent
6190 \ if we have a NEXT token, and the next program byte is a ",", then dedent again
6191 \ NOTE - We peek ahead at the program bytes, and don't consume them.
6200 CMP #&FD
6210 BEQ postNextUntil
6220 LDY #0
6230 .checkForComma
6240 LDA (pinput),Y
6250 INY
6260 CMP #&2C
6270 BNE P%+5
6280 JSR dedent
6290 \ if we don't have a colon or the start of the next program line,
6291 \ then loop back to check for another ","
6300 CMP #&3A
6310 BEQ P%+6
6320 CMP #&0D
6330 BNE checkForComma
6340 LDA #&ED \ restore the NEXT token
6350
6360 .postNextUntil
6370 \ if the byte is "[" (start assembly code), then indent to col.24, flag that
6371 \ we are not in BASIC code
6380 CMP #&5B
6390 BNE P%+11
6400 LDX #24
6410 JSR indent
6420 LDX #0
6430 STX in_basic
6440
6450 \ if we need to indent, then make it so
6460 BIT indent_pending
6470 BPL P%+7
6480 LDX curr_indent
6490 JSR indent
6500
6510 \ if we have a FOR or REPEAT token, then increase the indent
6520 CMP #&E3
6530 BEQ P%+6
6540 CMP #&F5
6550 BNE outputByte
6560 INC curr_indent
6570 INC curr_indent
6580 BNE outputByte
6590
6600 .checkAssembly
6610
6620 \ if we haven't indented yet, and we have a ".", then flag that we are
6621 \ in an assembly label
6630 LDY indent_pending \ remember this flag
6631 STY temp2
6640 BPL P%+10
6650 CMP #&2E
6660 BNE P%+6
6670 LDX #&FF
6680 STX in_asm_label
6690
6700 \ if we haven't indented yet, and are in a label, and we've found
6701 \ the end of the label, then update the "in label" flag
6710 BIT indent_pending
6720 BPL postAsmPrefix
6730 BIT in_asm_label
6740 BPL P%+10
6750 CMP #&20
6760 BNE postAsmPrefix
6770 LDX #0
6780 STX in_asm_label
6790
6800 \ if we have something other than a space, then indent to col.24
6810 CMP #&20
6820 BEQ postAsmPrefix
6830 LDX #24
6840 JSR indent
6850
6860 .postAsmPrefix
6870 \ if we have a "\" (start of comment), and it's not stand-alone,
6871 \ then show it at col.50
6880 CMP #&5C
6890 BNE P%+11
6891 BIT temp2
6892 BMI P%+7
6894 LDX #50
6895 JSR indent
6920
6930 \ if we have a "]" (end of assembly code), then flag that we are in BASIC code
6940 CMP #&5D
6950 BNE outputByte
6960 LDX #&FF
6970 STX in_basic
6980
6990 .outputByte
7000 JSR &B50E \ this prints the character or token in A
7010 JMP processNextByte
7020
8000 \ === SUPPORT ROUTINES ====================================================
8010
8020 \ get the next byte from PINPUT (keyboard buffer or program code)
8030
8040 .getNextByte
8050 LDY #0
8060 LDA (pinput),Y
8070 INC pinput
8080 BNE P%+4
8090 INC pinput+1
8100
8110 \ check for Escape
8120 BIT &FF
8130 BPL P%+14
8140 BRK
8150 EQUB &17
8160 EQUB &0A
: EQUB &0D
: EQUS "Escape."
: EQUB 0
8170
8180 RTS
8190
8200 \ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8201
8202 \ print spaces until the cursor is at the column specified in X
8210
8220 .indent
8230 PHA
8240 \ NOTE - PCODE is only used when parsing the keyboard input,
8241 \ and we re-use that byte here.
8250 STX pcode
8260
8270 \ get the current text cursor position
8280 LDA #&86
8290 JSR osbyte
8300
8310 \ subtract the requested column position to get the number of spaces
8311 \ we need to print (this won't work properly if the indent is so large,
8312 \ it wraps).
8320 TXA
8330 SEC
8340 SBC pcode
8350 BPL indentDone
8360
8370 \ print the spaces
8380 TAX
8390 LDA #&20
8400 JSR oswrch
8410 INX
8420 BNE P%-4
8430
8440 .indentDone
8450 \ flag that the current line has been indented
8460 LDA #0
8470 STA indent_pending
8480
8490 PLA
8500 RTS
8510
8520 \ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8521
8522 \ get the next non-space byte from PINPUT (keyboard buffer or program code)
8530
8540 .skipSpaces
8550 JSR getNextByte
8560 CMP #&20
8570 BEQ skipSpaces
8580
8590 RTS
8600
8610 \ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8611
8612 \ decode a line number in the 3 bytes at PINPUT, and store it in CURR_LINENO.
8613 \ Adapted from the BBC BASIC ROM &97EB (p334). See p174 for details
8614 \ on how these are encoded.
8620
8630 .decodeLineNo
8640 JSR getNextByte
8650 ASL A
8660 ASL A
8670 TAX
8680 JSR getNextByte
8690 STA curr_lineno
8700 JSR getNextByte
8710 STA curr_lineno+1
8720 TXA
8730 AND #&C0
8740 EOR curr_lineno
8750 STA curr_lineno
8760 TXA
8770 ASL A
8780 ASL A
8790 EOR curr_lineno+1
8800 STA curr_lineno+1
8810
8820 RTS
8830
8840 \ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8841
8842 \ decrease the level of indentation (to a minimum of 6)
8850
8860 .dedent
8870 DEC curr_indent
8880 DEC curr_indent
8890
8900 LDX #6
8910 CPX curr_indent
8920 BCC P%+4
8930 STX curr_indent
8940
8950 RTS `
8960
10000 ]
: NEXT opt
10010 PRINT
10020 PRINT "To save the generated machine code:"
10030 PRINT " *SAVE lst2 " + STR$~(BASE) + " " + STR$~(P%)
10040 PRINT
10050 PRINT "To activate it now:"
10060 PRINT " CALL &" + STR$~(BASE)
10070 PRINT "Or to reload it at a later time:"
10080 PRINT " *lst2"
>*SP.