(2025-04-14) What I have learned from porting DRACONDI onto the TI-74S
----------------------------------------------------------------------
To be honest, I don't remember the last time before the last week when I
wrote anything worthwhile in any BASIC dialect. As I already mentioned in my
previous post, my programming journey started from an RPN-programmable
calculator and then I jumped straight to C and JS. I missed out on Pascal
for the most part (although I read about it a lot) and surely missed out on
any BASIC environments except for some Mobile BASIC on the Nokia 3100 and
VBScript (sic) although I had enough literature to pick it up on the first
opportunity. And, of course, once I learned C and C-like syntax, I never
since thought of going back to the world of mandatory line numbers and
having $ in any string-related things. However, if I wanted to hop on a bike
I never had in the childhood, I needed to finally face this and learn the
TI-74's BASIC dialect in order to write anything useful and stick to my
initial plan, and this was exactly what I did when I wrote my previous post.
I must admit, the experience hasn't been as terrible as I thought it would
be. The machine does a nice job with compacting the code and helping you
with all the FN-shortcuts and line autonumbering (using the NUM keyword), as
well as reusing the previously typed lines with the PB function. In short, I
didn't feel uncomfortable at all when typing in the program and editing it
on the fly whenever necessary, which is a very important aspect in case
you're stuck with this computer only. The language is not without its quirks
(which I'm going to mention here), but it surely contains enough features to
get the job done, and let me remind you that those were the times when most
of the TI-74's direct handheld competitors couldn't even display lowercase
letters, let alone offer you string manipulation functions. Here, I can type
everything in lowercase and the machine will auto-convert it to uppercase,
except for the string literals (in quotes, comments or DATA statements)
where the original case is surely preserved. Very convenient. Some reviewers
also say that you can't touch-type on the TI-74 while you can on the CC-40,
and I can tell that they are incorrect: even with as little practice as I
have with this machine, I am already very close to touch-typing on it.
That's exactly because of its size: yes, it's bulkier than your average
modern smartphone but this is why its keyboard is not too small to be able
to type relatively fast, and when I get used to the FN-shortcuts... oh man,
I'm gonna master typing on this.
Now, I already knew which algorithm I was going to implement first. Of course
it would be DRACONDI. So, what would a DRACONDI port look like here? Well,
first we need some place to enter and store the two keyphrases, then an
entry point where we could choose and call an operation, accepting user
input (plaintext or ciphertext) if necessary, and, of course, we need the
subroutines to perform the actual keyed alphabet generation, encryption and
decryption. For the obvious reasons, I started with the alphabet keying
part. I decided to store the keyphrases in two DATA statements in the
beginning of the program:
100 DATA firstkeyphrase
110 DATA secondkeyphrase
I wanted to write the key generation subroutine right after these lines, but
here's the first quirk of the TI-74 BASIC dialect. This dialect supports two
types of subroutines: old-school numbered subroutines (GOSUB/RETURN) and
named subroutines with parameters (CALL/SUB/SUBEND). To make the code
clearer, I firmly decided to only use named subroutines. However, the TI-74
requires you to put all named subroutines (that the manual calls
"subprograms") after the main program code. Quoting the manual: "Subprograms
appear after the main program. If a SUB statement is encountered in a main
program, it terminates as if a STOP statement had been executed. Only
remarks and END statements may appear between the SUBEND of one subprogram
and the SUB of the next subprogram". That's why I had to plan my subroutines
in advance, numbering the lines in 1000s, so please don't pay attention to
the final line numbers at this point (they were generated after the RENUMBER
command). I decided to put the encryption and decryption functions first and
the last one would be the alphabet keying function. But I wrote it first in
the timeline, and here's what it looks like:
620 SUB KEYGEN(P$,KA$)
630 KA$=""
640 L=LEN(P$)
650 FOR I=1 TO L
660 C$=SEG$(P$,I,1)
670 IF POS(KA$,C$,1)>0 THEN 680 ELSE 710
680 CK=ASC(C$)-72:IF CK>25 THEN CK=CK-26
690 C$=CHR$(97+CK)
700 GOTO 670
710 KA$=KA$&C$
720 NEXT I
730 KA$=KA$&"abcdefghijklmnopqrstuvwxyz"
740 KR$="":L2=LEN(KA$)
750 FOR I=1 TO L2
760 C$=SEG$(KA$,I,1)
770 IF POS(KR$,C$,1)=0 THEN KR$=KR$&C$
780 NEXT I
790 L=L-INT(L/26)*26
800 KA$=SEG$(KR$,L+1,26)&SEG$(KR$,1,L)
810 SUBEND
If you already are familiar with the DRACONDI's alphabet keying scheme, you
might have an understanding of what's going on here, but yeah, I had to cope
with several more things: string operations in TI-74 are quite slow, all
character positions in the strings are 1-based, and... this BASIC dialect
doesn't have any modulo operation. Like, really? Come on, they even added
XOR here, but no way to get a remainder! This is why the line 790 contains a
construct that involves integer division, all just to get the value mod 26.
This has been my biggest disappointment in this particular programming
language so far. Maybe I don't know something but I haven't found anything
regarding modulo in the official programming manual.
On a positive side though, I had learned another important fact: all
"subprogram" parameters are passed by reference. That's why we can't use the
RETURN statement in such functions but we can directly modify the parameter
variables instead. If you need to be absolutely sure that the original
parameter variable remains intact, you can pass its value by enclosing it in
parentheses, thus creating an "ephemeral" reference for the subprogram call.
Overall, I think this mechanic was pretty advanced for the time as well,
considering most people just viewed TI-74 as an advanced programmable
calculator as opposed to what it really was.
With all that in mind, let's take a look at the encryption function:
320 SUB DRENC(M$,KA1$,KA2$,RES$)
330 OFFS=INT(26*RND)
340 RES$=SEG$(KA1$,OFFS+1,1)
350 L=LEN(M$):SK=1
360 FOR I=1 TO L
370 C$=SEG$(M$,I,1)
380 CI=POS(KA1$,C$,1)+OFFS
390 IF CI>26 THEN CI=CI-26
400 CT$=SEG$(KA1$,CI,1):RES$=RES$&CT$
410 SK=SK+1
420 IF SK=5 THEN 430 ELSE 440
430 SK=0:RES$=RES$&" "
440 OFFS=POS(KA2$,CT$,1)+I-1
450 IF OFFS>25 THEN OFFS=OFFS-26*INT(OFFS/26)
460 NEXT I
470 SUBEND
I think this is pretty straightforward and fully follows the algorithm,
except for the one bit with the SK variable. Well, the third rule of the
compliant DRACONDI software implementations states the following: "The
encryption part shall sanitize the message and output the ciphertext in
5-letter groups separated by a whitespace". Since all messages are
hand-entered, the sanitization part is on the operator, but the 5-letter
grouping part still needs to be implemented. Yes, I imposed this rule myself
and must suffer for this. Well, not really, it's a piece of simple
conditional spacing logic. Now, let's review the decryption function:
480 SUB DRDEC(M$,KA1$,KA2$,RES$)
490 OFFS=POS(KA1$,SEG$(M$,1,1),1)-1
500 M$=SEG$(M$,2,1024)
510 RES$="":L=LEN(M$):RI=0
520 FOR I=1 TO L
530 C$=SEG$(M$,I,1):IF C$=" " THEN 600
540 RI=RI+1
550 CI=POS(KA1$,C$,1)-OFFS
560 IF CI<1 THEN CI=CI+26
570 RES$=RES$&SEG$(KA1$,CI,1)
580 OFFS=POS(KA2$,C$,1)+RI-1
590 IF OFFS>25 THEN OFFS=OFFS-26*INT(OFFS/26)
600 NEXT I
610 SUBEND
This one is even simpler, but yeah, we could use the built-in LEN call as
opposed to hardcoding 1024 when calling SEG$ to get the rest of the
ciphertext, but the real line limit is much lower than that so it makes no
sense doing an extra call. Because of the rule #4 ("The decryption part
shall sanitize the ciphertext before processing, removing any whitespace and
non-letter characters from the input"), where, again, we only care about the
whitespace part of it, we have to use two counter variables here: I and RI
("real index") which only increments after we have checked for a whitespace.
Other than that, this is a step-by-step translation of the DRACONDI
decryption algorithm.
Finally, once I had all the subroutines in place, I proceeded with writing
the rest of the main code. Here's what I came up with:
100 DATA firstkeyphrase
110 DATA secondkeyphrase
120 PRINT "Initializing keys..."
130 RANDOMIZE
140 READ P1$,P2$
150 CALL KEYGEN(P1$,KA1$)
160 CALL KEYGEN(P2$,KA2$)
170 LINPUT "Operation (k/e/d): ";OP$
180 IF OP$="k" THEN 190 ELSE 220
190 PRINT "KA1: ";KA1$:PAUSE
200 PRINT "KA2: ";KA2$:PAUSE
210 GOTO 170
220 IF OP$="e" THEN 230 ELSE 270
230 LINPUT "Msg: ";MSG$:IF MSG$="" THEN 170
240 CALL DRENC(MSG$,KA1$,KA2$,RES$)
250 PRINT "Enc: ";RES$:PAUSE
260 GOTO 230
270 IF OP$="d" THEN 280 ELSE 170
280 LINPUT "Msg: ";MSG$:IF MSG$="" THEN 170
290 CALL DRDEC(MSG$,KA1$,KA2$,RES$)
300 PRINT "Dec: ";RES$:PAUSE
310 GOTO 280
So, after defining the keyphrases with DATA statements, we call RANDOMIZE
(remember that we use RND in the encryption routine), then read the
keyphrases and call the KEYGEN routine twice for each, saving the resulting
alphabets into KA1$ and KA2$ variables. This process, by the way, takes a
bit longer than I expected, maybe there's some room for optimization in the
KEYGEN routine but I'm not sure. Then we ask the user for an operation to
perform by pressing the corresponding key and Enter: yes, I know, I could
have used CALL KEY() to not have the user press Enter, but this is more
compact as it doesn't require a separate PRINT statement for the prompt. If
the user chooses "k", we just display the keyed alphabets in sequence (and,
by the way, the prefix line and the alphabet form a perfect 31-character
string to fit into the display). If the user chooses "e" or "d", we prompt
for the plaintext or ciphertext respectively and then display the
encryption/decryption result. Both encryption and decryption operations are
in a loop until the user enters an empty message (just pressing Enter), at
which point the program returns to the main operation selector. That's it,
no more fancy stuff going on here.
You can enter all the above parts in any order and get a working DRACONDI
program on your TI-74 too, but just in case, the full listing is also
provided on my Codeberg ([1]). When checking with FRE(1), the internal
memory taken by the program with my own test keyphrases is 1373 bytes out of
7710 available in total, that's how good the TI's internal tokenizer is.
Here are my main takeaways from this coding experience:
* named subroutines/functions aka "subprograms" rock as they make the code
much cleaner than in most handheld BASIC dialects of the time;
* the requirement to place them strictly after the main code looks like a
strange design decision though;
* all their parameters are passed by reference unless explicitly
parenthesized, which rocks too;
* on the other hand, the absence of a modulo operator really sucks as it
makes no sense at all to not have it in any computer with such capabilities;
* typing in relatively long programs is much more convenient than I thought
it would be;
* some keyboard shortcuts are more useful than the others;
* an internal tokenizer auto-converts all non-literals in the program to
uppercase and also gets rid of all unnecessary whitespaces;
* you do have some choice on how to organize your text input and output in
the program;
* you also have a rich choice of string operations here but keep in mind that
they are rather slow and all character positions are 1-based;
* you can store multiple programs in the same space (just be careful not to
use the RENUMBER command if you don't want all your programs compacted into
a continuous line sequence) and pass line numbers to the RUN command and
line ranges to the DELETE command if required;
* the program runtime doesn't have a scrollable output buffer, so you have to
call PAUSE after printing every significant line of text.
All in all, I think this experience has been invaluable and pretty exciting.
On the first day, when I only had implemented the alphabet keying routine
and still was testing just that piece to make sure it worked as expected,
for a brief moment, I felt that very kind of childish joy that I felt 23+
years ago when I got my first MK-52 program working. It was a "yes, I can
figure this out; yes, it can do what I tell it to do" kind of moment. It was
completely unlike the present day when you've got a ton of video tutorials
and instructional materials on any (modern) stuff you want to learn to
program in: a manual booklet and a handful of examples is everything you
usually had back then. And, despite the manual booklet has been read in a
digitized form, I've experienced absolutely the same emotions with the
TI-74S. So, I'm definitely going to continue exploring this machine and
adapt a bunch of other stuff to this BASIC dialect. Including, but not
limited to, a pretty surreal thing that I'm going to describe in detail on
the next week.
--- Luxferre ---
[1]:
https://codeberg.org/luxferre/dracondi/src/branch/main/dracondi.b74