Reverse engineering PDP-11 BASIC: Part 10
In this post I'll be looking at how the SAVE, OLD and DELETE BASIC commands work.
For context and a list of other posts on this topic, see the PDP-11 BASIC reverse engineering project page.
The SAVE command
The PDP-11 BASIC Programming Manual (section 5.3) tells us that the SAVE command will punch the user's BASIC program on the low-speed or high-speed punch, depending on the user's response to the initial dialog questions.
The SAVE code is incredibly simple:
002060 012767 MOV #1, 13676 002066 000167 JMP 2440
The value 1 is moved into address 13676, indicating that output should be directed to a storage device rather than the TTY. Then control jumps to address 2440, which is the location of the LIST command. So basically SAVE works by directing the LIST output to a paper tape rather than the screen.
The LIST command was described in Part 9 and you might recall that when output is directed to a storage device (by testing the value at memory address 13676), 100 (in octal, which is 64 in decimal) zero bytes are written to the end of the program code.
Interestingly, after running a LIST command, basic will display "READY" and this "READY" will also be written to tape because all output is directed to tape until the value at 13676 is cleared. Therefore, it was expected that the user, once the tape punch was complete, would tear off the final bytes (representing the printing of "READY" to tape) after the 64 zero bytes.
The OLD command
Turning now to the OLD command, the PDP-11 BASIC Programming Manual (section 5.4) tells us that the OLD command is used to read user programs that were previously saved on paper tape. The paper tape with the program is placed into the paper tape reader and then the user types OLD. When the user presses the RETURN key the tape is loaded and, presuming the code loads without error, READY will be displayed by BASIC.
Any BASIC programs already stored in memory will be cleared by loading a program using OLD, but code loaded for use with the EXF function will not be impacted.
Here's the code of the OLD command handler:
002072 012767 MOV #1, 13674 002100 016705 MOV 13662, R5 002104 005205 INC R5 002106 005067 CLR 13666 002112 005067 CLR 13702 002116 005067 CLR 13660 002122 000167 JMP 2762
The value 1 is moved into address 13674, indicating that input should be taken from a storage device rather than the TTY. In other words, OLD works by using the same code for interpreting characters that have been typed by a user into BASIC commands, except instead of the user typing the characters into the TTY they are read from the paper tape instead.
Before the code is read in from paper tape, a few state variables are reset:
002100 016705 MOV 13662, R5 002104 005205 INC R5 002106 005067 CLR 13666 002112 005067 CLR 13702 002116 005067 CLR 13660
Memory address 13662 contains the first byte of available program code storage space, and R5 tracks the extent of the code in the progam code storage space. Setting R5 to the value of at the address 13662 effectively clears the program code storage area. The first byte in the progam code storage area is always a linefeed character, so R5 is incremented to point at the first available usable byte in the program code storage area.
Memory address 13666 is used when programs are running to record the beginning of the memory used for storing program execution state. This is also cleared.
Memory address 13702 contains a flag that is used to indicate whether execution has been interrupted by the user pressing Ctrl-P. This is cleared to indicate that execution has not been interrupted.
Finally, memory address 13660 is used to maintain the currently executing line number of the currently executing stored program. This is also cleared since we are about to load new code.
002122 000167 JMP 2762
Finally, control jumps to the beginning of the syntax parsing loop to start reading in code. The code will be read from tape, as opposed to input from the user, due to the non-zero value in the memory address 13674.
The DELETE command
The DELETE command is used to delete a line, or lines, from a program. Line numbers are specified in the same format as for the LIST command, but an argument must be specified to prevent accidental deletion of an entire program.
Here are some example uses:
DELETE 10 ; this will delete line 10 from the program DELETE 10, 100 ; this will delete lines from 10 1o 100 DELETE 100, ; this will delete all lines from line 100 to the ; end of the program DELETE ,500 ; this will delete all lines from the beginning ; of the program to line 500
Here is the code the the DELETE command handler:
002670 104516 TRAP 116 002672 104506 TRAP 106 002674 016701 MOV 13662, R1 002700 005704 TST R4 002702 001001 BNE 2706 002704 010304 MOV R3, R4 002706 010446 MOV R4, -(SP) 002710 010346 MOV R3, -(SP) 002712 104502 TRAP 102 002714 020105 CMP R1, R5 002716 103012 BCC 2744 002720 010146 MOV R1, -(SP) 002722 104410 TRAP 10 002724 012601 MOV (SP)+, R1 002726 020016 CMP R0, (SP) 002730 002770 BLT 2712 002732 020066 CMP R0, 2(SP) 002736 003002 BGT 2744 002740 104476 TRAP 76 002742 000764 BR 2714 002744 022626 CMP (SP)+, (SP)+ 002746 000461 BR 3112
Let's see how that one works.
002670 104516 TRAP 116
There is a TRAP 116 at the beginning of most of the BASIC command handlers. I'm still working out exactly what this does but it definitely relates to the management of program runtime state.
002672 104506 TRAP 106
TRAP 106 parses the line range specified in the command line. I described this in detail in Part 9. Briefly, the starting line number (or single line number) is returned in R3 and the ending line number is returned in line 4. Either or both registers may be returned with the value zero, if one or the other line values are not specified.
002674 016701 MOV 13662, R1
The memory address of the start of the program storage area, stored at location 13662, is moved into R1.
002700 005704 TST R4 002702 001001 BNE 2706
The value of R4 is tested. This will contain the ending line number for deletion, or zero if not specified. If the value in R4 is not zero, jump to 2706.
002704 010304 MOV R3, R4
Otherwise, i.e. in the case where R4 is zero, we copy R3 into R4.
002706 010446 MOV R4, -(SP) 002710 010346 MOV R3, -(SP)
Now, both R4 and R3 are pushed onto the stack.
002712 104502 TRAP 102
TRAP 102 is then used to move to the next line in the program.
However, remember that a few instructions ago we moved the memory address of the beginning of the program area into R1. The first character in the program storage area is a linefeed so the first time around the loop this will move to the first line in the program.
002714 020105 CMP R1, R5 002716 103012 BCC 2744
R1 will contain the current location in the program and R5 contains the memory address of the end of the program code, so we now check whether we have reached the end of the program. If yes (i.e. if R1 is greater than R5), branch to address 2744.
002720 010146 MOV R1, -(SP) 002722 104410 TRAP 10 002724 012601 MOV (SP)+, R1
Otherwise, push R1 onto the stack and then use TRAP 10 to get the number of the current line in the program code. TRAP 10 will change the value of R1, so R1 is then popped from the stack to restore its value. The result of TRAP 10 will be that R0 will contain the numeric value of the line number of the current line.
002726 020016 CMP R0, (SP) 002730 002770 BLT 2712
Now the value in R0 (the current line number) is compared to the value at the top of the stack (which is R3, the starting line number for deletion). If the line number in R0 is less than the line numer at the top of the stack, we branch up to 2712 and move on to the next line. This will continue until we have seeked forward to the first line to be deleted, at which point R1 will point at the first line to be deleted in the program code area.
002732 020066 CMP R0, 2(SP) 002736 003002 BGT 2744
Otherwise, the current line number (in R0) is greater than the starting line number (at the top of the stack) so we now compare R0 to the next value down in the stack (SP+2), which contains the ending line number for deletion. If the value in R0 is greater than the ending line number that means we have finished deleting lines, so we branch to 2744.
002740 104476 TRAP 76 002742 000764 BR 2714
If neither of the preceding two tests led to a branch, that means we are currently pointing at a line that is to be deleted. Therefore, TRAP 76 is invoked to delete the line from the program code, after which control branches back to the top of the loop (address 2714) to continue to the next line.
Note that we don't need to seek forwards after deleting a line because, after TRAP 76, R1 will point at the next line after the deleted line, so we just need to check whether the new line now pointed to by R1 also needs to be deleted, or are we finished deleting lines. TRAP 76 is described in detail in Part 7.
002744 022626 CMP (SP)+, (SP)+ 002746 000461 BR 3112
Finally, after all relevant lines have been deleted, we pop two values off the stack and discard them, then branch to 3112, which is back to the main syntax parsing loop.
Although the BASIC commands described in this post can be used in deferred mode, they aren't usually. The remaining commands require a much more in-depth consideration of the management of runtime program state, so I'm looking at that right now.
Thanks for sticking with this series, I hope you're finding it interesting!