The Logo language created by Seymour Papert in 1967 was pretty famous in the eighties as a tool to teach programming to kids. Now in 2024, I can see it was great to make nice drawings on the screen and learn trigonometry, and some very fortunate kids had a real robotic turtle drawing on paper, but it wasn't so good for teaching programming because few home computers came with Logo, it was expensive, and its implementations differed greatly in their support of important programming constructs (KingOfCoders informs me that the Amstrad CPC464 (1984) computers came with Dr. Logo, nice!)
For example, there is at least one Logo language for Commodore 64 that implemented support for sprites, but it wasn’t portable to other versions. I learned this the hard way as I wanted to play a “Hunt the Dragon” game made for C64 Logo published in the Mi computer magazine (the whole volume contains Logo tutorials that I used to learn Logo), but Dr. Logo just kept throwing me error messages that I didn’t understand.
Anyway, I was in love with Logo, and being the bold kid I was, I decided to make a Logo language for the student's computer developed by my father, and I would code it in Z80 machine code at age 9. Furthermore, I would have forgotten this completely if it wasn’t because the handwritten program was saved away in the class folder that my father still kept and I found the pages the last Sunday, March 10, 2024, almost 36 years later. I almost couldn't believe it!
These are pictures of the three pages composing the program, and the final page contains the manual! You can see the machine code is somewhat readable because each instruction is neatly separated, and this means I coded directly on the educational computer, and later I copied the machine code listing in my notebook.
I’m glad I put a date on the page: Friday, September 9, 1988. It means it was written after my first Z80 machine code game, so this is my second big machine code program.
LOCS means Lenguaje de Oscar Computadora eStudiantil (or Oscar's language for students' computers). OTEK comes from my father's initials (Oscar Toledo Esteva) which we used as a kind of company name.
My language commands are pretty simple: BORRA clears the screen, TORTUGA centers the "turtle", there are four commands to draw (SM up, AM down, DM right, IM left), and finally we have the PT command (Pone Tortuga) to place the turtle anywhere on the screen.
Making it to work
This program depends heavily on having a keyboard, so this time I won’t port it to Colecovision to save me time. I could assign a function to each key in the keypad but it would require a big code chunk and I would end up rewriting the program completely.
Anyway, here we go analyzing it. We’ll start by preparing a simple MSX translation layer.
;
; LOCS
;
; by Oscar Toledo G.
; (c) Copyright 1988 Oscar Toledo G.
;
; Creation date: Sep/09/1988. I was age 9.
;
;
; Layer to adapt it to MSX computers.
;
WRTVDP: EQU $0047 ; Set VDP address HL.
WRTVRM: EQU $004D ; Write byte A to VRAM address HL.
FILVRM: EQU $0056 ; Fill VRAM address HL with A repeated BC times.
SETWRT: EQU $0053 ; Set write address HL for VDP.
CHGET: EQU $009F ; Read character from keyboard into A.
VDP: EQU $98 ; Base VDP port.
; MSX Cartridge header
ORG $4000
DB "AB"
DW START
DW 0
DW 0
DW 0
DW 0
;
; Set a VDP address to write.
;
L0100: JP SETWRT
;
; Copy immediate data to VRAM.
;
L0169: EX (SP),HL
.1: LD A,(HL)
OR A
JR Z,.2
OUT (VDP),a
INC HL
JR .1
.2: EX (SP),HL
RET
;
; Clear the screen.
; Set screen buffer to VRAM $3c00.
;
L04CC:
LD BC,$0F02 ; VDP register 2 = $3c00.
CALL WRTVDP
LD HL,$3C00 ; Clear the screen.
LD BC,$0300 ; 32x24 grid.
LD A,$20 ; Space character.
JP FILVRM ; Fill VRAM.
;
; Read the keyboard.
;
L04F5:
CALL CHGET ; Read keyboard.
CP $61 ; Is it lowercase?
RET C
CP $7B
RET NC ; No, return.
SUB $20 ; Yes, make it uppercase.
RET
;
; Read a hexadecimal number in HL.
;
READ_WORD:
CALL READ_BYTE ; Read a byte.
READ_BYTE:
CALL READ_HEX ; Read a hexadecimal digit.
READ_HEX:
CALL L04F5 ; Read the keyboard.
PUSH HL
LD HL,(L87F0) ; Get cursor address.
CALL WRTVRM ; Display key on the screen.
INC HL
LD (L87F0),HL ; Update cursor address.
POP HL
SUB $30 ; Convert ASCII 0-9 to 0-9.
CP $0A
JR C,.1
SUB $07 ; Convert ASCII A-F to 10-15.
.1: ADD HL,HL ; Shift HL to the left 4 bits.
ADD HL,HL
ADD HL,HL
ADD HL,HL
OR L ; Add hexadecimal digit.
LD L,A
RET
This program calls the ROM of the educational computer. It uses five functions:
L0100, Set VDP address.
L0169, Copy a message to VRAM.
L04CC, Clear the screen.
L04F5, Read the keyboard.
RST $10, Read a hexadecimal number into HL (4 digits).
It depends also on the screen grid to be available at the VRAM address $3c00 (the MSX default is $1800) so in my translation layer for L04CC it writes the VDP register 2 to change it.
The program starts like this:
START:
LD SP,L87F0
CALL L8114
CALL L0100
CALL L0169
DB "LOCS OTEK",0
IF 0
LD HL,$3C41
CALL L0100
CALL L0169
DB "32 BYTES",0
ELSE
DB 0,0,0
DB 0,0,0
DB 0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0
ENDIF
It sets the stack pointer at the initial address for the 2K RAM students computer, and immediately the code is patched before showing the program title.
L8114: CALL L8075
LD HL,$3C01
RET
L8075: LD HL,$3D50
LD (L86F0),HL
CALL L04CC
RET
The patch sets the "turtle" position to $3D50 (the center of the screen), this position is saved in the L86F0 address, then it clears the screen calling L04CC, and then loads HL with $3C01 to point to the top left of the screen. Notice it isn't exactly $3C00 but $3c01 because my Sony Trinitron TV of the time "ate" the first column.
It took me probably half an hour to discover what I patched with tons of handwritten zero bytes (NOP instructions), and it was simply an extra message saying "32 BYTES". I think that Dr. Logo showed the total bytes available for the program, and I think that saying 32 bytes was my joke because there isn't such thing as a memory manager.
L8028:
CALL L81C1
CALL L807F
CALL L0169
DB "GRAFICO",0
LD HL,$3EE1
CALL L8063
LD A,$3E
OUT (VDP),A
LD (L86F2),HL
LD DE,L8700
L8049:
CALL L04F5
LD (DE),A
INC DE
CP $0D
JR Z,L8068
LD HL,(L86F2)
PUSH AF
CALL L0100
POP AF
OUT (VDP),A
INC HL
LD (L86F2),HL
JP L8049
L8063:
CALL L0100
INC HL
RET
L807F:
PUSH HL
LD HL,(L86F0)
CALL L0100
LD A,$2A
OUT (VDP),A
POP HL
CALL L0100
RET
L81C1:
LD HL,$0118 ; Redefine the # character
CALL L0100 ; as a solid block.
LD B,$08
L81C9:
LD A,$FF
OUT (VDP),A
DJNZ L81C9
L81CF:
LD HL,$0918
CALL L0100
LD B,$08
L81D7:
LD A,$FF
OUT (VDP),A
DJNZ L81D7
LD HL,$1118
CALL L0100
LD B,$08
L81E5:
LD A,$FF
OUT (VDP),A
DJNZ L81E5
LD HL,$3EC1 ; Point to command area.
RET
The main loop starts by calling L81C1, obviously a patch, because it ends setting HL to $3EC1 (the command area on the screen), but first it redefines the # character to be a solid block. The redefinition is done in three areas of VRAM because the educational computer was hardcoded to VDP mode 2, but the MSX starts in mode 0, so only the first loop is necessary but I kept it all.
The next call jumps to L807F to draw the "turtle" in the current position. An asterisk, I'm surprised I didn't even try to draw a turtle. At the end of the L807F it calls L0100 to point to the VRAM address for the command area.
Now it shows a "GRAFICO" message (akin to a READY message), and in the next row ($3EE1 in VRAM) it puts a > sign to wait for the keyboard. Notice the L8063 patch because I couldn't fit an INC HL to save the current address in VRAM at L86F2.
There's no cursor, it simply waits for the keyboard and displays the typed key on the screen (L86F2 contains the current address in VRAM), and saves the key also in the RAM buffer located at L8700. I've moved early the CP $0D comparison waiting for the Enter key, so it doesn't show trash in MSX, because in the educational computer that character wasn't defined so when displayed it was invisible.
It doesn't handle the backspace key, so you have to think before typing anything.
On your command
At this point, it starts processing the language commands:
The first implemented command is "BORRA", but at the time I didn't know how to compare whole words, so it simply checks for the first letter "B" (ASCII $42), calls L04CC and jumps back to L8028.
L808F:
CP $54
JR NZ,L80BF
LD HL,(L86F0)
CALL L0100
LD A,$20
OUT (VDP),A
LD HL,$3D50
LD (L86F0),HL
CALL L0100
LD A,$2A
OUT (VDP),A
CALL L80B0
JP L8028
L80B0:
LD HL,$3EC1
L80B3:
CALL L0100
LD B,$40
L80B8:
LD A,$20
OUT (VDP),A
DJNZ L80B8
RET
The second command is "TORTUGA" (T). It simply gets the current position of the turtle, puts a space character to erase it, centers it again on the screen, and draws it again. Apparently I forgot I had a turtle drawing subroutine at L807F, or that the L8028 jump already draws the turtle.
The third command is "SM" (S). It expects the length in the fourth byte as a digit, so you need to type exactly SM followed by a space and the digit.
It converts the digit to a value between 0-9 and saves it into B as counter. It takes the current position of the turtle and draws a block, displaces the pointer, and repeats. It ends clearing the command area, redrawing the turtle, and jumping back to the main loop.
Notice it never validates the length, zero will draw 256 blocks.
Did I say block? Right, I didn't know anything about pixels, so when you don't have pixels, you have blocks, right? A very bold kid.
The fifth command is "AM" (A) for going down, and the sixth command "IM" (I) for going to left. Again, I only changed the displacement offsets. I could have saved tons of bytes by having a generic drawing subroutine where you could provide an offset, but I was still learning, and probably afraid of juggling with registers.
The seventh command is "PT". It sets a position for the internal cursor by reading a hexadecimal word into HL that is saved right away into DE. It deletes the turtle from the screen, and repositions it at the DE position added $3c00 to put it into the right VRAM address. Almost the same code as the TORTUGA command, and again I forgot I had a turtle redraw subroutine.
By the way, there is a tiny bug here. In the machine code I calculated wrongly the relative jump JR NZ and it jumps one byte before the right address, as the opcode $81 is an ADD instruction, it doesn't affect and it works just right ($41 in MSX, LD B,C)
L8194:
LD HL,$3C01
CALL L0100
CALL L0169
DB "* ERROR *",0
LD B,$05
L81A9:
PUSH BC
LD BC,$0000
L81AD:
DEC BC
LD A,B
OR C
JR NZ,L81AD
POP BC
DJNZ L81A9
LD HL,$3C00
CALL L80B3
CALL L80B0
JP L8028
Finally, my LOCS program ends with an error message for an unrecognized command. It does a big delay, erases it, and returns to the main loop.
I've made a tiny video showing my LOCS program in action.
Downloads
You can download the original LOCS program (495 bytes), and the MSX adapted version (589 bytes with the translation layer), along with the original assembler code for both.
Download locs.zip (4.33 kb) containing assembler source code and binaries.
To be a complete language it would need variables, expressions, and at least a conditional loop. But doesn't matter, HTML is called a language and doesn't have this!
I didn't understand exactly how angles worked at the time so there are none at all in my program. The use of blocks instead of pixels was crazy, also the lack of validation of the user's typing, but I can understand I was pretty enthusiastic (detecting commands with one single letter!) and I empirically discovered the principle of "it is better to have it working than having nothing".
Over the years I kept working on compilers and interpreters for several languages going from BASIC to Pascal and then C, maybe later I'll talk about this. But it wasn't until 2014 that I made again my own language: IntyBASIC, a dialect of BASIC with dedicated statements for Intellivision consoles, and more recently CVBasic.
Related links
bootLogo, a Logo language I made in 509 bytes of x86 machine code (boot sector).