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.
545 lines
14 KiB
545 lines
14 KiB
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"
|
|
|
|
|