TCT Basic

There are several interpreters for the Basic programming language for 2650-based computers. One of my earliest exercises was to reverse-engineer the source code for TCT Basic. This software was written for Pipbug-based machines, which use a serial terminal for input and output.

(C) 1978

In order to run TCT Basic on my P1 computer, I had to adapt the software to use the built-in character-display and keyboard interface. And for that I needed the source code.

Using the DASMx disassembler I managed to piece together the inner workings of TCT Basic. For example, it obfuscates all strings (using a simple Rotate Left), and uses a fancy pseudo-code language internally. More about this, and more, in the Notes below.

With the source code available, it was relatively easy to modify TCT Basic so that it can run on the P1 computer.

However, TCT Basic does not contain its own text editor. In order to run Basic programs, I needed an editor. So after all this work I decided to get a Disk Operating System working first, to get an editor that could save files to floppy disks, and then adapt TCT Basic to work with floppy disk files.

I hope to return to this peculiar Basic interpreter later…


Here is some information on the internals of TCT Basic.

Compact indirect addressing hints

The code often uses indirect addresses when another instruction
close by already used that using absolute addressing:

CODE loda,r0 ADDR
     strr,r0 *CODE+1

All in all, this shaves about 115 bytes from the program. Using relative addressing saves one execution cycle, but the indirection costs an extra two cycles. In effect, we spend one extra execution cycle tosave one byte of memory. One drawback is reduced readbility of the code.

Pseudo instructions

The interpreter works by executing a list of pseudo instructions. The 3 upper bits determine the kind of pseudo

  • bit 80 = jump to pseudo subroutine at the address given in the next two bytes, then continue with next pseudo.
  • bit 40 = unconditional jump to pseudo at the address given in the next two bytes. The address is actually the address-1.
  • bit 20 = jump if not match string, where the string follows the pseudo. The last char in string is marked by a high bit. On match continue with pseudo just after the string.
  • none = execute subroute at this address, then continue with next pseudo.

Obfuscated strings

Some of the internal strings of the interpreter are obfuscated by a simple Rotate Left. Since the end-of-string is again indicated by a high bit 80, the last characters can be recognised because they are odd whereas other characters are even (bit 80 rotates to position 01).

For example, the string at address StrCopyright decodes to

(C) 1978

Numbers and variables

Variables A-Z are always numeric. They are stored in a table starting at 06F0 to 07C0. Numbers take up 8 bytes, and are internally stored as 10-digit floating point numbers. The exponent can be 2 digits (-99 .. +99). Both the mantissa and the exponent are store in BCD-notation.

es Ex ms 12 34 56 78 90 = 0.1234567890 * 10Ex
When es=00 the exponent is positive.
When es=F0 the exponent is negative.
When ms=00 the number is positive.
When ms=F0 the number is negative.

The routines from TN094 appear to be used literally. The pointers PtrA, PtrB (and its SignPtr) and PtrR must be filled, after which TN094_DADD, TN094_DSUB, TN094_DMUL or TN094_DDIV can be called. PtrA, PtrB and PtrR are typically filled by references to the ValStack.

Miscellaneous internal storage

BooleanResult: false=0, true=non-zero; is also used to determine if output is
to screen (false) or tape (true).

ForStack: each For-statement takes 18 bytes:
00 = offset of the variable
01,02 = Basic Program offset to where NEXT should start
03..10 = to value
11..18 = step value
The stack is h'4C' deep, just enough for a nesting depth of four for-loops.

DoUntilStack: each DO-statement takes 2 bytes: the offset where to jump to on UNTIL. The stack is h'20' deep, for a depth of 16 DO-UNTIL statements.

ValueStack: The ValueStack is used while evaluating expressions. The ValueStack is not checked for maximum depth. It is possible to overwrite the stack and crash the program. For example, evaluating this expression results in a crash:

PRINT 1+2*(1+2*(1+2*(1+2*(1+2*(1+2*(3+4))))))

This starts to overwrite the variable store, which is located just after the ValueStack.

Program code and string storage

Program code (the Basic program) is stored at 1800 onwards. On NEW, the bytes 0D FE 0D are stored: empty line, followed by the EndOfCode marker, followed by another empty line.

After the code all strings are stored. Each string starts with a two-byte BCD number of the string variable, followed by the string itself, and terminated by an h'0D' EndOfLine marker. The last string is followed by the h'FF' EndOfStrings marker.

Correction to the code

  • At address h'14A1' byte h'78' should be h'79'
    This is probably due to a typo in the pseudocode table. In practice it is immaterial, although it inserts an unnecessary REDD,R0 whenever the SIZE command is executed.
  • At address h'1224' byte h'86' should be h'26'
    Seems to be a transcription error.