Compare commits

...

5 Commits

Author SHA1 Message Date
Pacman Ghost 58319bf85b Added a formatted listing of the source code. 2 years ago
Pacman Ghost e160551f0f Left-align stand-alone assembly comments with the code. 2 years ago
Pacman Ghost a42b2efdd1 Changed how line numbers are printed out. 2 years ago
Pacman Ghost c9b6b8a14d Moved assembly comments farther to the right. 2 years ago
Pacman Ghost 979c2343ce Added the original source code. 2 years ago
  1. 545
      LST2.bas
  2. 561
      LST2.bas.txt
  3. BIN
      LST2.ssd
  4. 57
      README.md

@ -0,0 +1,545 @@
10REM This program provides a formatted listing of a BASIC program,
11REM in particular, indenting loops and splitting multiple statements
12REM out onto separate lines.
13REM Since it calls into the BASIC ROM, it requires BASIC 2.
14
20REM It lives at &08D0-0AFF, which is normally used for the following:
21REM 800-8FF: sound workspace & buffers, printer buffer, envelope storage
22REM 900-9FF: envelope storage, RS423/speech/cassette output buffers
23REM A00-AFF: cassette/RS423 input buffers
24
30REM Run this program to assemble it, and save the generated machine code.
31
40REM Enter LIST commands as normal, but with a trailing period e.g.
41REM LIST 100,200.
42REM LIST ,200.
43REM LIST 100,.
44REM LIST.
45
50REM NOTE: Page references in the source code are for "The BBC Micro
51REM Compendium", by Jeremy Ruston.
52
100REM === VARIABLES =======================================================
101
102REM We borrow some zero-page locations normally used by BASIC (p170).
103
110REM When when we're parsing the input command, this points into
111REM the keyboard buffer (&0700-07FF), then we switch to the program code
112REM when we start printing the listing.
113pinput = &4B : REM and &4C
114
120REM This points into the program code being listed (only while
121REM we're parsing the input command).
122pcode = &3B : REM and &3C
123REM After parsing the input command, we use these bytes as temp storage.
124temp1 = &3B : temp2 = &3C
125
130REM This holds the current line# being listed.
131curr_lineno = &2A : REM and &2B
132
140REM This holds the last line# to be listed.
141last_lineno = &2E : REM and &2F
142
150REM This holds the current indent.
151REM The first 5 columns are used for the line#, followed by a space,
152REM then the code itself starts at col.6
153curr_indent = &30
154
160REM This flags if we are in a REM statement.
161in_rem = &31
162
170REM This flags if we are in a double-quoted string.
171in_quotes = &32
172
180REM This flags if we are in BASIC code (0 means we are in assembly code).
181in_basic = &33
182
190REM This flags if we are in an assembly label.
191in_asm_label = &34
192
200REM This flags if the current line has not been indented yet.
201indent_pending = &35
202
900osbyte = &FFF4 : oswrch = &FFEE : osnewl = &FFE7
910
1000FOR opt = 0 TO 3 STEP 3
1010BASE = &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
1120LDA #(brkHandler MOD 256)
1130STA &0202
1140LDA #(brkHandler DIV 256)
1150STA &0203
1160
1170\ beep, then exit
1180LDA #7
1190JMP 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"
1530JSR &BE6F
1540
1550\ if the error was not "Syntax error" (error code 16), then jump to
1551\ the normal BRK handler
1560LDY #0
1570LDA (&FD),Y
1580CMP #16
1590BEQ P%+5
1600.defaultBRK
1610JMP &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)
2010STY pinput
2020STY pcode
2030LDA &18
2040STA pcode+1
2050LDX #7
2060STX pinput+1
2070DEX
2080STX curr_indent
2090DEY
2100STY in_basic
2110STY last_lineno
2120LDY #&7F
2130STY 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
2160JSR skipSpaces
2170CMP #&C9
2180BNE 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)
2210JSR skipSpaces
2220CMP #&2C
2230BEQ parseEndLineNo
2240
2250\ if the byte is &8D (pseudo-keyword for a line#), then decode the line#
2251\ into CURR_LINENO
2260CMP #&8D
2270BNE endParseInput
2280JSR 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)
2330LDY #2
2340LDA (pcode),Y \ this is the LSB of the current line#
2350SEC
2360SBC curr_lineno
2370DEY
2380LDA (pcode),Y \ this is the MSB of the current line#
2390BPL P%+5 \ negative line# MSB = end-of-program marker
2400JMP done
2410SBC curr_lineno+1
2420BPL foundFirstLine
2430\ nope - move to the next line
2440LDY #3
2450LDA (pcode),Y \ this is the line length
2460CLC
2470ADC pcode
2480STA pcode
2490BCC P%+4
2500INC pcode+1
2510JMP checkNextLine
2520
2530.foundFirstLine
2540\ if the next non-space byte in the keyboard buffer is ",", then parse
2541\ the end line#
2550JSR skipSpaces
2560CMP #&2C
2570BEQ parseEndLineNo
2580
2590\ if the byte is not ".", then jump to the normal BRK handler
2600CMP #&2E
2610BNE 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#
2640LDA curr_lineno
2650STA last_lineno
2660LDA curr_lineno+1
2670STA last_lineno+1
2680JMP 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.
2720JSR skipSpaces
2730CMP #&8D
2740BNE endParseInput
2750JSR decodeLineNo
2760STA last_lineno+1
2770LDA curr_lineno
2780STA last_lineno
2790JSR skipSpaces
2800
2810.endParseInput
2820\ check for the trailing "."
2830CMP #&2E
2840BEQ P%+5
2850JMP defaultBRK
2860
2870.parseEOL
2880\ check for the terminating CR
2890JSR skipSpaces
2900CMP #&0D
2910BEQ P%+5
2920JMP defaultBRK
2930
2940\ start taking input from the program code i.e. PINPUT <- PCODE
2950LDA pcode
2960STA pinput
2970LDA pcode+1
2980STA 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
5030JSR getNextByte
5040CMP #&0D
5050BNE printCode
5060
5070\ yup - print a newline, save the line# in CURR_LINENO
5080JSR osnewl
5090JSR getNextByte
5100STA curr_lineno+1
5110JSR getNextByte
5120STA curr_lineno
5130
5140\ skip over the line length byte
5150JSR getNextByte
5160
5170\ check if we're done (CURR_LINENO > LAST_LINENO)
5180LDA last_lineno
5190SEC
5200SBC curr_lineno
5210LDA last_lineno+1
5220SBC curr_lineno+1
5230BMI done
5240
5390\ print the current line# (with a field width of 5)
5400JSR &9923
5410
5420\ print a space
5430LDA #&20
5440JSR 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
5480LDX #0
5490STX in_rem
5500STX in_quotes
5510STX in_asm_label
5520DEX
5530STX indent_pending
5540BMI processNextByte
5550
5560.done
5570\ we're all done - warm-start BASIC
5580JSR osnewl
5590JMP &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
5710CMP #&8D
5720BNE P%+11
5730JSR decodeLineNo
5740JSR &991F \ this prints the IAC as a 16-bit number
5750JMP processNextByte
5760
5770\ if we have a double-quote, then toggle the IN_QUOTES flag
5780CMP #&22
5790BNE P%+10
5800LDA in_quotes
5810EOR #&FF
5820STA in_quotes
5830LDA #&22
5840
5850\ if we are currently in a quoted string or a REM statement,
5851\ then output the current byte verbatim
5860BIT in_rem
5870BMI P%+6
5880BIT in_quotes
5890BPL P%+5
5900JMP outputByte
5910
5920\ if we have a colon, then print a newline, indent, print a colon
5930CMP #&3A
5940BNE P%+18
5950JSR osnewl
5960LDX #5
5970JSR indent
5980LDA #&3A
5990JSR oswrch
6000JMP startNextLine
6010
6020\ if we are currently in assembly code, then jump to handle that
6030BIT in_basic
6040BPL checkAssembly
6050
6060\ if we have a REM token, then update the flag
6070CMP #&F4
6080BNE P%+6
6090LDX #&FF
6100STX in_rem
6110
6120\ check if we have a NEXT or UNTIL token
6130CMP #&ED
6140BEQ P%+6
6150CMP #&FD
6160BNE postNextUntil
6170\ yup - decrease the current level of indentation
6180JSR 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.
6200CMP #&FD
6210BEQ postNextUntil
6220LDY #0
6230.checkForComma
6240LDA (pinput),Y
6250INY
6260CMP #&2C
6270BNE P%+5
6280JSR 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 ","
6300CMP #&3A
6310BEQ P%+6
6320CMP #&0D
6330BNE checkForComma
6340LDA #&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
6380CMP #&5B
6390BNE P%+11
6400LDX #24
6410JSR indent
6420LDX #0
6430STX in_basic
6440
6450\ if we need to indent, then make it so
6460BIT indent_pending
6470BPL P%+7
6480LDX curr_indent
6490JSR indent
6500
6510\ if we have a FOR or REPEAT token, then increase the indent
6520CMP #&E3
6530BEQ P%+6
6540CMP #&F5
6550BNE outputByte
6560INC curr_indent
6570INC curr_indent
6580BNE 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
6630LDY indent_pending \ remember this flag
6631STY temp2
6640BPL P%+10
6650CMP #&2E
6660BNE P%+6
6670LDX #&FF
6680STX 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
6710BIT indent_pending
6720BPL postAsmPrefix
6730BIT in_asm_label
6740BPL P%+10
6750CMP #&20
6760BNE postAsmPrefix
6770LDX #0
6780STX in_asm_label
6790
6800\ if we have something other than a space, then indent to col.24
6810CMP #&20
6820BEQ postAsmPrefix
6830LDX #24
6840JSR 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
6880CMP #&5C
6890BNE P%+11
6891BIT temp2
6892BMI P%+7
6894LDX #50
6895JSR indent
6920
6930\ if we have a "]" (end of assembly code), then flag that we are in BASIC code
6940CMP #&5D
6950BNE outputByte
6960LDX #&FF
6970STX in_basic
6980
6990.outputByte
7000JSR &B50E \ this prints the character or token in A
7010JMP processNextByte
7020
8000\ === SUPPORT ROUTINES ====================================================
8010
8020\ get the next byte from PINPUT (keyboard buffer or program code)
8030
8040.getNextByte
8050LDY #0
8060LDA (pinput),Y
8070INC pinput
8080BNE P%+4
8090INC pinput+1
8100
8110\ check for Escape
8120BIT &FF
8130BPL P%+14
8140BRK
8150EQUB &17
8160EQUB &0A : EQUB &0D : EQUS "Escape." : EQUB 0
8170
8180RTS
8190
8200\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8201
8202\ print spaces until the cursor is at the column specified in X
8210
8220.indent
8230PHA
8240\ NOTE - PCODE is only used when parsing the keyboard input,
8241\ and we re-use that byte here.
8250STX pcode
8260
8270\ get the current text cursor position
8280LDA #&86
8290JSR 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).
8320TXA
8330SEC
8340SBC pcode
8350BPL indentDone
8360
8370\ print the spaces
8380TAX
8390LDA #&20
8400JSR oswrch
8410INX
8420BNE P%-4
8430
8440.indentDone
8450\ flag that the current line has been indented
8460LDA #0
8470STA indent_pending
8480
8490PLA
8500RTS
8510
8520\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8521
8522\ get the next non-space byte from PINPUT (keyboard buffer or program code)
8530
8540.skipSpaces
8550JSR getNextByte
8560CMP #&20
8570BEQ skipSpaces
8580
8590RTS
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
8640JSR getNextByte
8650ASL A
8660ASL A
8670TAX
8680JSR getNextByte
8690STA curr_lineno
8700JSR getNextByte
8710STA curr_lineno+1
8720TXA
8730AND #&C0
8740EOR curr_lineno
8750STA curr_lineno
8760TXA
8770ASL A
8780ASL A
8790EOR curr_lineno+1
8800STA curr_lineno+1
8810
8820RTS
8830
8840\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8841
8842\ decrease the level of indentation (to a minimum of 6)
8850
8860.dedent
8870DEC curr_indent
8880DEC curr_indent
8890
8900LDX #6
8910CPX curr_indent
8920BCC P%+4
8930STX curr_indent
8940
8950RTS `
8960
10000] : NEXT opt
10010PRINT
10020PRINT "To save the generated machine code:"
10030PRINT " *SAVE lst2 " + STR$~(BASE) + " " + STR$~(P%)
10040PRINT
10050PRINT "To activate it now:"
10060PRINT " CALL &" + STR$~(BASE)
10070PRINT "Or to reload it at a later time:"
10080PRINT " *lst2"

@ -0,0 +1,561 @@
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.

Binary file not shown.

@ -0,0 +1,57 @@
This program provides a formatted listing of a BBC BASIC program, by splitting multiple statements out onto separate lines, indenting loops, etc. Assembly language also gets nicely formatted.
I was doing a lot of work on the BBC Micro in the late 80's, and recently managed to retrieve a lot of it from floppy discs. The discs were in relatively good condition, given that they were nearly 40 years old (!), but it was still a challenge reading them (they were shedding so much oxide, we had to clean the disk heads after each use :-)).
This program was incredibly useful, but we were only able to get a binary executable off the floppies, not the source code, so I disassembled it, annotated it, and have made it available here.
Working on the BBC Micro was not easy, since you only had, at best, 24&frac34;KB of memory (!) (5&frac34;KB if you were using hi-res graphics), so saving every byte of memory possible was really important. Since BASIC is an interpreted language, and each line had a 3-byte overhead, you could save quite a few bytes by cramming as many statements onto each line as possible, separated by a colon, thus saving 2 bytes for each statement.
Of course, this made the code very difficult to read, so I wrote this listing formatter, which converts something like this:
```
807DEFPROCsp LOCALT%,I%,P%,S$:PROCblip(100):REPEATUNTILNOTINKEY-99:REPEAT:PRINTTAB(0,24)SPC10f$B$bk$Y$"Press SPACE "bb$SPC10;:PROCfx21:T%=INKEY500:IFT%=esc T%=FNesc(T%):GOTO810:ELSEIFT%=32ORT%<>-1ORRND(500)>1GOTO810
809P%=msgs+1:FORI%=1TORND(?msgs):S$=$P%:P%=P%+LENS$+1:NEXT:prvb=prvb+1:PRINTTAB(0,24)CHR$(128+RND(7))SPC38;:I%=1:REPEAT:PRINTTAB(1,24)MID$(STRING$(39," ")+"Ancient proverb #"+STR$prvb+": "+S$+STRING$(50," "),I%,38);:I%=I%+1:UNTILI%=70+LENS$ORNOTINKEY20
810UNTILT%=32:PRINTTAB(0,24)SPC39;:ENDPROC
```
into this:
```
807 DEFPROCsp LOCALT%,I%,P%,S$
:PROCblip(100)
:REPEATUNTILNOTINKEY-99
:REPEAT
: PRINTTAB(0,24)SPC10f$B$bk$Y$"Press SPACE "bb$SPC10;
: PROCfx21
: T%=INKEY500
: IFT%=esc T%=FNesc(T%)
: GOTO810
: ELSEIFT%=32ORT%<>-1ORRND(500)>1GOTO810
809 P%=msgs+1
: FORI%=1TORND(?msgs)
: S$=$P%
: P%=P%+LENS$+1
: NEXT
: prvb=prvb+1
: PRINTTAB(0,24)CHR$(128+RND(7))SPC38;
: I%=1
: REPEAT
: PRINTTAB(1,24)MID$(STRING$(39," ")+"Ancient proverb #"+STR$prvb+": "+S$+STRING$(50," "),I%,38);
: I%=I%+1
: UNTILI%=70+LENS$ORNOTINKEY20
810 UNTILT%=32
:PRINTTAB(0,24)SPC39;
:ENDPROC
```
To get the fully authentic Beeb experience, you can type the program in from the listing :-), but as a convenience, a `.ssd` file is provided with the source code, together with a binary executable.
Load the program into memory like this:
```
*LST2
```
And then enter LIST commands as normal, but with a trailing period e.g.
```
LIST 100,200.
LIST ,200.
LIST 100,.
LIST.
```
Loading…
Cancel
Save