Tiny PILOT for tiny 6502 computers by Nicholas Vrtis Presented in MICRO Sept. 1979 - issue 16, page 41 with I/O modifications for KIM-1 and I: L: and P: statement additions by Bob Applegate, 1980 Source code hand-entered, this command guide, plus X: and Z: statement additions and L: modification by Dave Hassler, 2023 *** EDITOR *** Command Function @ Start execution of the PILOT program in memory ^ Move edit pointer to start of program / Display next line of the program % Pad to end of line with delete characters b/s Backspace to correct typing errors (current line) c/r Carriage return to indicate end of statement line (maximum 125 characters per line) *** STATEMENTS *** A: ACCEPT - Input up to 40 characters into ANSWER/CHRS field. ?: Accept NAME - Input up to 40 characters into the NAME and ANSWER/CHRS fields simultaneously (see X: statement). C: COMPUTE - Performs arithmetic on variables named A through Z. NOTE: allowed operations are: +, -, and =; and range limit is +/- 999. (We *told* you it was tiny!) C:$=x will place result x in ANSWER field instead of a variable (see M: statement). E: EXIT FROM SUBROUTINE - Return to address saved by prior USE statement. I:x INPUT - Input a positive number 0-9999 into variable x, which can be A-Z. (BA) J:x JUMP - Jump to label n for next line. L:x LEAP TO ML ROUTINE - Calls machine language routine at address stored in special ML-calling variable x, which can be A - C. (BA/DH) M: MATCH - Compare text to last input from terminal (in CHRS), set flag to Y if equal, N if not equal. P:x PSEUDO-RANDOM NUMBER - Puts a pseudo-random number 0-99 into variable x, which can be A-Z. (BA) R: REMARK - Program remarks, not executed. S: STOP - Stop program and return to editor. Must be at end of a program; otherwise, the interpreter crashes. T: TYPE - Display following text on the terminal. $x will display the contents of a variable. U:x USE SUBROUTINE - Saves the address of the start of the next line and then jumps to n. X: TRANSFER STRING - Moves the NAME string ('?') to a temporary storage location called STRING; moves whatever was in STRING into NAME and CHRS (the A: acceptance/“answer” area). Z: ZAP MEMORY - Resets all variables and user RAM to a 'cold start' state. To use, in the editor go to the first line of program memory with '^', type 'Z' then . Execute with the '@'. (DH) *** CONDITIONALS, LABELS, VARIABLES *** N,Y NO and YES CONDITIONALS - May precede any statement. I.e., YJ: means jump only if the MATCH flag is Y. NT: means print following text only if MATCH flag is N. *x LABEL - May precede any statement or conditional. x = single B-Z. Acts as a destination for JUMP or USE. A and * alone are reserved for system use. $x Variable marker. In TYPE, causes variable contents to be displayed or prepares them to be matched. $? indicates the NAME field. C:$=x puts a variable into the ANSWER area as a string so it can be MATCHed. Variables are a single letter. While the numerical content of a variable may be from -9999 to 9999, C: will only operate in the range -999 to 999. *** EXAMPLES AND NOTES *** A: A:ENVELOPE (user input) This can be compared with the M: statement. Input is stored in the ANSWER/CHRS area backwards from $2A to $03. 40 characters, max. ?: ?:PENELOPE (user input) A string variable that may be printed with '$?'. Called NAME, this appears to be a holdover from PILOT-73, with educational programs in mind. It is stored in the NAME area from $2B-$52. 40 characters, max. It is also stored in the ANSWER/CHRS area, simultaneously. It is, essentially, the *one* string variable available, but can only be assigned by user input, not from within a program. And, it is also stored backward, as with ANSWER. Can (should?) be used with the X: statement. C: C:X=Z+Z+Z+Z this is X = 4 x Z C:X=5-2+6-12 this will leave X with the value -3 C:X=A this will leave X with value of A It's very similar to BASIC. Operation is strict left-to-right; parentheses not allowed. $ is a special case: C:$=X M:12 YJ:Z Here, whatever is in X gets put in ANSWER area as ASCII and is matched with the string '12'. If it is (Y), jump to module Z; if not, continue on. *** Does not work for string variable '?'; for that, see the X: statement below. E: Simply place at the end of a subroutine. The equivalent of BASIC's 'RETURN'. *ZC:A=B+C E: Performs the calculation, then returns to the line right after the U:Z statement that called the subroutine. I: I:x User is asked for input, which can be a positive integer 0-9999, and assigned to variable x. J:x J:A means jump to last ACCEPT. J:* means restart from the beginning. This also means letters B through Z are available for labels. L:x The variables referenced are not the numeric variables! Addresses *must* be pre-set in Z-Pg locations $A0 to $A5 (two 8-bit hex bytes, little endian, variables A-C available; see source) before program execution. To use: A) load the desired ML routine into memory. *** be aware that the X: statement occupies $1780-$17A2 B) load PILOT. Prior to execution, set $A0 and $A1 to the start of the user ML program, little-endian. This equates to ML-jump variable A. C) execute PILOT at $0200. D) load user program (via TTY). '@' to run program. The called ML routine must end with an RTS to come back to the PILOT interpreter. Store locations $A0 and $A1 with #$00 and #$01 as described above, and L:A will JSR to $0100. M: M:QUIT,HALT,STO Will look at the last input to the ANSWER/CHRS area (A: X: or ?:) and try to match the first word only with the parameters. If QUIT or HALT or STOP was entered, the flag will be set to Y. However, if STOMACH or QUITE is the first word, they will match, also. Further, the following will set the flag to N: T: WHAT'S YOUR FAVORITE COLOR? A:I LIKE BLUE M:BLUE YT:I AGREE THAT BLUE IS BEST. One (albeit complicated) way out of this is to use conditionals and cascade M: statements: T: WHAT'S YOUR FAVORITE COLOR? A:I LIKE BLUE M:BLUE NM:I LIKE NM:I LIKE BLUE YT:I AGREE THAT BLUE IS BEST. To match variables, use C:$=x This will place the ASCII values of the variable in the ANSWER/CHRS area. When the program sees M:123, it's matching against a literal 3-character string of '123'. You can also say M:$x to match against the content of a variable. P:x Will place a pseudo-random decimal number from 0-99 in variable x. R: A BASIC 'REM' statement; however, a '/' in the statement will get misinterpreted and result in the rest of the R: line being output. Do not use '/' in R: S: The marks the end of the program and an exit back to the editor. Without it, the program will hang/crash. When S: is reached execution stops and the program pointer is reset to the first line. T: Will print any string <126 characters long. Quotes around the string are not needed. To print out the contents of NAME (?:) or a variable, preface it with $. E.g., T:YOU HAVE $X "MARBLES" LEFT, $?. will print out YOU HAVE 12 "MARBLES" LEFT, STAN. assuming X=12 and ?=STAN. U:x Saves a return address to the next line and then jumps to the label *x. X: This transfers the contents of the ?: variable (NAME) into the STRING area. Using X: allows for an "additional" string variable that can also be tested against anything specified for the M: statement. E.g. ?: DAVE (user input) A:BOB (user input) M:BOB YT:IT'S 'BOB' X: X: M:DAVE YT:IT'S DAVE S: Will result in 'IT'S BOB' and 'IT'S DAVE' being printed. The two X: statements place NAME into STRING, then STRING into NAME and ANSWER/CHRS, the latter of which M: draws from for tests. See the included TELLMEDUDE.PIL program for an example of use. Z: Zap RAM. In this version, it stores $00 from $00-$EE on zero page, then fills $0600-$13FF with 00. It's kind of an "all or nothing" approach to clearing user RAM. When tucking away machine language routines, don't use this after you put the ML in memory, unless you've hidden the routine in the bottom of the stack at $100, or in RIOT RAM at $17A3. Tiny PILOT does clear out the variable space when you start a program with '@'. Z: is needed when you want to load in another program; otherwise, the new one will merge with the old one, creating problems; worse, the variables don’t get cleared. I'm sure it could be improved to only clear to the end-of-program, but that would likely take up more space. ================================================================ COMMENTS and ERRATA You must have CAPS LOCK on. Tiny PILOT (like KB9, Apple BASIC, and Tiny BASIC) can print out lower case letters, but won't take them as input. There is no "immediate mode" as in most other classic high-level languages. The editor is very simple. You cannot insert a line of code in between lines; you can only replace it with a line of equal or shorter length by typing over. If the replacement is shorter, end the line with '%' instead of . That will pad the remaining space with $80, which will not print and is ignored. Backspace (08) works within a line, but frankly, I would not use the editor for anything other than sketching out ideas or very small programs. Better to just use Mousepad or something for program writing and upload that via serial terminal. You can break Tiny PILOT; there is absolutely no error checking. Stuff like not assigning a variable a value and then trying to print it, or forgetting to put a ':' after a program statement letter will break it. J:* is like typing GOTO 1 in BASIC; notice that there's no variable with it. It would be useful for allowing the user (or another user) to start fresh automatically without having to restart the program. Makes sense in a lower-grades educational/classroom environment. The one reserved label is *A. J:A will jump back to the last A: statement. There's no cold start with Tiny PILOT -- your program in user RAM is still there. You can even go do other stuff, reload Tiny PILOT, and as long as your program from $0600 to its end was undisturbed, it'll run. Loading programs is easy; saving programs is a bit of a pain. Today (2023), simply upload an ASCII text file to the interpreter. 10 ms character and 150 ms line delays seem to work OK. Loading from cassette/other device can be done by breaking out of Tiny PILOT and using the KIM-1 routine at $1873. But saving.... Two ways I can see: 1) Line by line from the editor. Open a text capture file on the serial terminal emulator, type a '^' to go to the beginning of your program, then start slowly tapping '/' to list it out line by line. You'll have to clean it up a bit in post. 2) Find the end-of-user-program address. You could break out of Tiny PILOT, fire up a monitor and go look for the end. When there's no more ASCII from $0600 (the start of user RAM), that's the end. Do count the final 00, though, then go one past that and put that address into $17F7-8 and save to PTP or tape. Of course, you can avoid all of these saving business woes by writing your programs offline on Mousepad or Notepad++ or whatever. I tweaked Bob Applegate's L: statement so Tiny PILOT could be burned into an EPROM. Bob's original approach was to used self-modifying code in a brute-force way that would have made my then-14-year-old self say "Wow!" Considering that Bob was just 17 when he wrote and submitted this to MICRO in 1980 makes it all that much more fun. With the addition of the X: statement, I had to limit Bob's ML calling routine to just three instances, variables A-C. That should be plenty. If you don't want to call outside ML routines, locations $A0-$A5 are available for Zero Page use. You'll need to alter any ML routine that uses Zero Page inside of that range. Otherwise, Zero Page is pretty locked up. This brings up the topic of where to put ML. In addition to our old favorites in RIOT RAM (barring $1780-A2, where X: resides) and the bottom of the stack on Page 1, the area of user RAM from the end of the PILOT program up to $13FF is usable, but just don't use the Z: statement or you'll wipe it out. Another thing one could do is make a PTP of user memory from $0600 to the end of your program and ML, then tack on the bytes needed for the JSR in a PTP line for $A0-1 (or wherever). Then it's all self-contained and requires no extra work by the user. As for the pseudo-random number routine, it's Jim Butterfield's from page 172 in "The First Book of KIM," which Bob gratefully acknowledged in his original article for MICRO and then tweaked for decimal output. The results seem to be weighted toward the larger end of the range. Also, the first dozen results are not very random, as it takes a while for all the bytes in the routine to roll over. I think Bob's I: statement for entering numerical variables is genius. It really amplifies the utility and possibilities of Tiny PILOT. Damn good code, too. Adding in my own Z: statement and a tiny welcome message at start up to Bob's three commands and KIM-1 I/O added 259 bytes to Nicholas's Tiny PILOT as presented in MICRO #16. It sits at exactly 1024 bytes in main memory. That's pretty tiny! In an unexpanded PAL-1 or microKIM, when loaded at $0200, there's space for 3.5 K's worth of program text (the X: routine takes up 31 bytes at $1780). As for this program and PILOT, itself, this version by Nicholas Vrtis appears to be based upon PILOT-73, which was the defacto standard on minicomputers in the '70s. I think it's a pared down version of '73. One interesting note is that Tiny PILOT shares a lot of code with Larry Kheriaty's ultra-tiny WADUZITDO in the September 1978 issue of BYTE -- right down to using the same labels in the source. Both programs must have been made from PILOT-73. In the article from MICRO #16, Nicholas goes into some detail describing how the program works, section by section. It's a good read and very informative. I recommend it, in addition to his well-commented source code (which I typed in by hand off a printout ... the complete '80s experience!). I know I certainly learned a lot about how to build a high-level language interpreter. I only have two real gripes: the M: statement only works on the first word input and gives up without checking the rest of the string; and the only way to operate on a string, other than printing the NAME buffer, is a simple M: via the X: statement. I have recently learned that most full PILOTs can do these things, with multiple strings, so all hope is not lost, if someone wants to find some source code and have at it. I found a copy of PILOT for the Commodore 64 on the web, and it's quite robust ... at 13K. After stripping out the turtle and sprite graphics, I bet it would fit in a 4-6K space ... hmmm ... (since first writing this, I adapted Mike Tinglof's VIC and PET PILOT to work under KIM-1 BASIC 9. It's called PAL PILOT, and a full package with documentation is on my website at www.vanportmedia.com/PAL-1 -- DHH) No, Tiny PILOT -- or regular, full-sized PILOT for that matter -- is no mathematical champ. Tiny PILOT is limited to only addition and subtraction, and the only comparator is =; although, it's easy enough to do multiplication and division with those, plus use a loop to test for < and > when values are between 0 and 999. E.g.: I:G ; a number to test C:I=105 ; limit of loop (could be a variable, too) *RC:$=I ; move ASCII of I to $ M:$G ; match I ($) against (ASCII of) G YJ:V ; if = then branch to V label, because G is NC:I=I-1 ; inside the loop. If <>, dec I NM:0 ; test if I ($) is ASCII '0' (end of loop is 1) YJ:W ; could also match with '-' for 105 to 0 test NJ:R ; yes, done, branch to W; no, loop back *VT:$G is less than 106. J:Z *WT:$G is greater than 105. *ZS: ; end of program Still, not much to work with. But what PILOT excels at interactive dialog. PILOT stands for Programmed Inquiry, Learning, or Teaching, and was written by Dr. John Starkweather in 1968 at UCSF. It was designed to be a vehicle for computer-aided instruction (CAI) in classrooms, and it's wonderful for that. Super-easy for a teacher to use to create an interactive script that can guide a student through a lesson or exercise. At least, back then. Or, perhaps, today, as a retro adventure game? Maybe something like the "Choose Your Own Adventure" books from the '70s and '80s? Who knows? However you use it ... Have Fun! -- Dave Hassler, 9 Feb 23