LOCS: Mi lenguaje de dibujo desarrollado a los 9 años.

LOCS en acción
El lenguaje Logo creado por Seymour Papert in 1967 fue bastante famoso en los ochentas como una herramienta para enseñar programación a los niños. Ahora en 2024, ya me doy cuenta que era fabuloso para hacer bonitos dibujos en la pantalla y aprender trigonometría, y algunos niños muy afortunados tuvieron una tortuga robótica real dibujando en papel, pero no era muy bueno para aprender programación porque pocas computadora caseras venían con Logo, era caro, y las implementaciones diferían mucho en su apoyo para construcciones de programación importantes. (KingOfCoders me informa que las computadoras Amstrad CPC464 (1984) venían con Dr. Logo ¡qué bien!)
Por ejemplo, existe una implementación de Logo para Commodore 64 que tiene soporte para sprites, pero no es portable a otras versiones. Lo aprendí de la manera difícil cuando quería probar el juego de Caza del Dragón para Logo C64 publicando en la revista Mi computer magazine (el volumen completo contiene tutoriales de Logo que usé para aprender Logo), pero Dr. Logo solo sacaba mensajes de error que yo no comprendía.
De cualquier forma, estaba fascinado con Logo, y siendo un niño audaz decidí hacer un lenguaje Logo para la computadora de estudiantes diseñada por mi padre, y lo haría en código máquina Z80 a los 9 años. De hecho, habría olvidado esto por completo si no fuera por el programa escrito a mano que se guardó en el folder de clases que mi padre todavía conserva y encontré las páginas este domingo 10 de marzo de 2024, casi 36 años después. ¡No podía creerlo!
Estas son fotos de las tres páginas que componen el programa ¡y la página final contiene el manual! Puedes ver que el código máquina es algo legible porque cada instrucción esta separada. Eesto significa que escribí el programa directamente en la computadora educativa y después copié a mano el listado de código máquina en mi cuaderno.
LOCS código máquina Z80 escrito a mano, página 1 LOCS código máquina Z80 escrito a mano, página 2 LOCS código máquina Z80 escrito a mano, página 3
Es afortunado que puse una fecha en la página: Viernes, 9 de septiembre de 1988. Significa que fue escrito después de mi primer juego en código máquina Z80, así que es mi segundo programa grande en código máquina.
LOCS significa Lenguaje de Oscar Computadora eStudiantil. OTEK viene de las iniciales de mi padre (Oscar Toledo Esteva) que usábamos como un tipo de nombre de compañía.
Las órdenes de mi lenguaje eran bastante simples: BORRA limpia la pantalla, TORTUGA centra la "tortuga", hay cuatro órdenes para dibujar (SM arriba, AM abajo, DM derecha, IM izquierda), y finalmente tenemos la orden PT (Pone Tortuga) para poner la tortuga en cualquier posición de la pantalla.

Haciéndolo funcionar

Este programa depende completamente de tener un teclado, así que esta vez no lo portare a Colecovision para ahorrar tiempo. Podría asignar una función a cada tecla del keypad pero requeriría un enorme trozo de código y terminaría reescribiendo el programa completamente.
De cualquier forma, aquí vamos a analizarlo. Comenzaremos preparando una capa simple de traslación para MSX.

    ;
	; 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
Este programa llama la ROM de la computadora educativa. Son cinco funciones:
  1. L0100, pone dirección del VDP.
  2. L0169, copia un mensaje a VRAM.
  3. L04CC, limpia la pantalla.
  4. L04F5, lee el teclado.
  5. RST $10, lee un número hexadecimal en HL (4 dígitos).
También depende de que la rejila de la pantalla esté disponible en la dirección VRAM $3c00 (en MSX esto es $1800 por defecto) así que mi capa de traslación para L04CC escribe el registro 2 del VDP para cambiarlo.
El programa empieza así:

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
Pone el apuntador de pila a la dirección inicial para la computadora estudiantil de 2K de RAM, e inmediatamente el código es parcheado antes de mostrar el título del programa.

L8114:	CALL L8075
	LD HL,$3C01
	RET

L8075:	LD HL,$3D50
	LD (L86F0),HL
	CALL L04CC
	RET
El parche pone la "tortuga" en la posición $3d50 (el centro de la pantala), esta posición se salva en la dirección L86F0, entonces limpia la pantalla llamando L04CC, y carga HL con $3c01 para apuntar a la parte superior izquierda de la pantalla. Nota que no es $3c00 sino $3c01 ya que mi TV Sony Trinitron de la época se "comía" la primera columna.
Probablemente me tomó media hora descubrir lo que parchee con toneladas de bytes zero sobreescritos en la hoja (instrucciones NOP), y era simplemente un mensaje diciendo "32 BYTES". Creo que Dr. Logo mostraba el total de bytes disponibles para el programa, y creo que decir 32 bytes era mi broma debido a que no había tal cosa como gestión de memoria.

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
El bucle principal comienza por llamar L81C1, que es obviamente un parche porque termina poniendo HL a $3EC1 (el área de órdenes en la pantalla), pero primero redefine el caracter # para ser un bloque sólido. La redefinición se hace en las tres áreas del VRAM porque la computadora educativa estaba fija en el modo 2 del VDP, pero el MSX comienza en el modo 0, así que solo el primer bucle es necesario pero mantuve todo.
La siguiente llamada salta a L807F para dibujar la "tortuga" en la posición actual. Un asterisco. Me sorprende que ni siquiera traté de dibujar una tortuga. Al final de L807F llama a L0100 para apuntar a la dirección de VRAM del área de órdenes.
Ahora muestra el mensaje "GRAFICO" y en la siguiente línea ($3ee1 en VRAM) pone un signo > para esperar el teclado. Note el parche L8063 porque no me cupo INC HL para salvar la dirección actual de VRAM en L86F2.
No hay cursor, solo espera el teclado e ilustra la tecla entrada en la pantalla (L86F2 contiene la dirección actual en VRAM), y salva la tecla también en el buffer de RAM localizado en L8700. Moví antes la comparación CP $0D que espera por la tecla Enter, para que no muestre basura en MSX, ya que en la computadora educativa ese caracter no estaba definido así que ilustrado era invisible.
No maneja la tecla de retroceso, así que hay que pensar todo antes de teclear.

A sus órdenes

En este punto comienza a procesar las órdenes del lenguaje:

L8068:
        LD A,(L8700)
	CP $42
	JR NZ,L808F
	CALL L04CC
	JP L8028
La primera orden implementada es "BORRA", pero en ese momento no sabía como comparar palabras completas, así que solamente checa la primera letra "B" (ASCII $42), llama a L04CC y salta atrás a 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
La segunda orden es "TORTUGA" (T). Sólo obtiene la posición actual de la tortuga, pone un caracter de espacio para borrarla, la centra de nuevo en la pantalla, y la dibuja de nuevo (aparentemente olvidé que tenía la subrutina de dibujar la tortuga en L807F), o que L8028 ya dibuja la tortuga.

L80BF:
        CP $53		
        JR NZ,L80E6
        LD A,(L8700+3)
        SUB $30
        LD B,A
L80C9:
        LD HL,(L86F0)
        CALL L0100
        LD A,$23
        OUT (VDP),A
        LD DE,$0020
        SBC HL,DE
        LD (L86F0),HL
        DJNZ L80C9
        CALL L80B0
        CALL L807F
        JP L8028
La tercera orden es "SM" (S). Espera el tamaño en el cuarto byte como un dígito, así que necesita teclearse exactamente SM seguido por un espacio y luego un dígito.
Convierte el dígito a un valor entre 0 y 9 y lo guarda en B como contador. Toma la posición actual de la tortuga y dibuja un bloque, desplaza el apuntador, y repite. Termina limpiando el área de órdenes, redibujando la tortuga y saltando al bucle principal.
Nota que nunca válida el tamaño, cero dibujará 256 bloques.
¿Dije "bloque"? Correcto, yo no sabía nada acerca de pixeles, así que si no tienes pixeles, usas bloques ¿correcto? De verdad era muy audaz.

L80E6:
        CP $44
        JR NZ,L811B
        LD A,(L8700+3)
        SUB $30
        LD B,A
L80F0:
        LD HL,(L86F0)
        CALL L0100
        LD A,$23
        OUT (VDP),A
        LD DE,$0001
        ADD HL,DE
        NOP
        LD (L86F0),HL
        DJNZ L80F0
        CALL L80B0
        CALL L807F
        JP L8028
La cuarta orden es "DM" (D). Es exactamente el mismo código, solo cambié el offset de desplazamiento para ir a la derecha.

L811B:
        CP $41
        JR NZ,L8142
        LD A,(L8700+3)
        SUB $30
        LD B,A
L8125:
        LD HL,(L86F0)
        CALL L0100
        LD A,$23
        OUT (VDP),A
        LD DE,$0020
        ADD HL,DE
        NOP
        LD (L86F0),HL
        DJNZ L8125
        CALL L80B0
        CALL L807F
        JP L8028

L8142:
        CP $49
        JR NZ,L8169
        LD A,(L8700+3)
        SUB $30
        LD B,A
L814C:
        LD HL,(L86F0)
        CALL L0100
        LD A,$23
        OUT (VDP),A
        LD DE,$0001
        SBC HL,DE
        LD (L86F0),HL
        DJNZ L814C      
        CALL L80B0 
        CALL L807F
        JP L8028
La quinta orden es "AM" (A) para ir abajo, y la sexta orden "IM" (I) para ir a la izquierda. De nuevo, solo cambié los offsets de desplazamiento. Pude ahorrar toneladas de bytes con una subrutina genérica de dibujo a la que se provee un offset, pero todavía esta aprendiendo, y probablemente aterrado de manipular tantos registros.

L8169:
        CP $50
        JR NZ,L8194-1
        LD HL,$3EE5
        LD (L87F0),HL
        CALL READ_WORD  ; Modified from RST $10
        LD D,H
        LD E,L
        LD HL,(L86F0)
        CALL L0100
        LD A,$20
        OUT (VDP),A
        LD HL,$3C00
        ADD HL,DE
        LD (L86F0),HL
        CALL L0100
        LD A,$2A
        OUT (VDP),A
        CALL L80B0
        JP L8028
La séptima orden es "PT", pone la posición para el cursor interno leyendo una palabra hexadecimal en HL y la copia en DE. Borra la tortuga de la pantalla, y la reposiciona en la posición de DE más $3c00 para ponerla en la posición correcta de la VRAM. Casi el mismo código de la orden TORTUGA, y de nuevo olvidé la subrutina de redibujar la tortuga.
Por cierto hay un pequeño bug aquí. En el código máquina calculé mal el salto relativo JR NZ y salta un byte antes de la dirección correcta, como el código $81 es una instrucción ADD no afecta y funciona bien ($41 en 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
Finalmente, mi programa LOCS termina con un mensaje de error para una orden no reconocida. Hace un gran retardo, borra el mensaje, y vuelve al bucle principal.
Hice un pequeño video mostrando mi programa LOCS en acción.

Descargas

Puedes descargar el programa LOCS original (495 bytes) y la versión adaptada a MSX (589 bytes con capa de traslación), junto con el código ensamblador para ambos.
Puedes ensamblarlo con mi nuevo ensamblador Gasm80 (disponible de https://github.com/nanochess/gasm80) o con TNIasm v0.45.

Epilogue

Para ser un lenguaje complete necesitaría variables, expresiones, y al menos un bucle condicional. Pero no importa ¡HTML es llamado un lenguaje y no tiene esto!
Yo no entendía exactamente como funcionaban los ángulos así que que no hay ninguno en mi programa. El uso de bloques en lugar de pixeles fue bastante atrevido, así como la falta de validación de la entrada del usuario, pero puedo comprender que estaba muy entusiasta (¡detectar órdenes con una sola letra!) y empíricamente descubrí el principio de "mejor tenerlo funcionando que no tener nada".
A través de los años seguí trabajando en compiladores e intérpretes para varios lenguajes yendo de BASIC a Pascal y luego a C, tal vez después escriba sobre esto. Pero no fue sino hasta 2014 que hice de nuevo mi propio lenguaje: IntyBASIC, un dialecto de BASIC con sentencias dedicadas para consolas Intellivision, y más reciente CVBasic.

Ligas relacionadas

Última modificación: 19-mar-2024