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.