Skip to content
Juan Gonzalez-Gomez edited this page Nov 13, 2024 · 255 revisions

Log

2023-05-23

El lenguaje Forth siempre me ha atraido mucho, sobre todo porque está ligado a los niveles más bajos del software. He encontrado muy inspirador a este proyecto: AlexForth6809, de Alexandre Dumont en el que está implementando, desde 0, un intérprete de Forth. Lo interesante es que lo está documentado día a día, de manera que es muy fácil aprender su funcionamiento

Esa es justo la mejor manera de entender Forth: programarte tu propio intérprete. Y eso es justo lo que voy a hacer yo usando un RISC-V. La implementación es la excusa. Durante el camino quiero ir aprendiendo todos los conceptos de forth, que son muchos (y las siglas!!!)

Para practicar con el lenguaje se puede utilizar el Forth de GNU: gforth
Desde ubuntu se instala muy fácilmente así:

sudo apt install gforth

Un programa muy sencillo que podemos hacer en forth es el que realice la suma 1+1, y muestre el resultado en la pantalla. El programa en cuestión es este:

1 1 + .

Y desde el Gforth se prueba así:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
1 1 + . 2  ok

Observamos que a la derecha del . ha aparecido esto: 2 ok. El dos es el resultado de la suma y el ok es el mensaje de forth indicando que se han ejecutado las instrucciones correctamente

Este programa de Forth está formado por 4 palabras. Cada palabra hace una acción:

  • 1: Introduce el numero 1 en la pila
  • +: Lee de la pila los siguientes dos operandos, los suma y deja el resultado en la pila
  • .: Saca de la pila el último valor y lo imprime en la consola

Vamos a empezar a construir nuestro intérprete forth desde 0, para el RISC-V. Vamos a utilizar inicialmente el simulador RARs. Tiene la ventaja de que nos premite validar nuestro código, y depurarlo fácilmente. Otra ventaja es que dispone de Entrada/salida. El principal inconveniente es que NO NOS PERMITE MEZCLAR DATOS CON INSTRUCCIONES, por lo se tienen que hacer en segmetnos separados. En muchas implementaciones de forth el código máquina de la instrucciones está dentro del propio diccionario, junto a los datos. Esto ya lo solventaremos más adelante

Nuestro objetivo inicial será implementar el programa forth 1 1 + . directamente en esamblador, intentando imitar lo que el intérprete hace. El programa, en realidad es sencillo, basta con realizar 4 llamadas consecutivas. Una a la subrutina que implementa la palabra 1, a la que habrá que llamar 2 veces seguidas, luego llamar a la subrutina de suma y por último a la que implementa la palabra .. Y aquí acaba el programa.

En esta primera versión tendríamos el siguiente programa:

   .text
   jal do_1     #-- Ejecutar la palabra 1
   jal do_1     #-- Ejecutar la palabra 1 (otra vez)
   jal do_add   #-- Ejecutar la palabra +
   jal do_point #-- Ejecutar la palabra .
   #-- Terminar

La función do_1() mete el número 1 en la pila. Por ello tenemos que definir primero la pila. Vamos a utilizar una pila pequeña, inicialmente de 4 bytes, para poder depurar fácilmente. Esta pila metemos en el segmento de datos

   .data
   #-----------------------
   #-- PILA
   #----------------------	
	.space 4
stack:

En nuestra implementación en el rars usamos el modelo compacto de memoria, donde el código se sitúa a partir de la dirección 0 y los datos a partir de la 0x2000. Por tanto la pila ocupa las posiciones 0x2000, 0x2001, 0x2002 y 0x2003. Inicialmente el puntero de pila apunta a la dirección 0x2004. Este valor indica que LA PILA ESTÁ VACIA

Esto es lo que se hace nada más comenzar el programa: inicializar el puntero de pila. Este es el código:

  #-- Inicializar la pila
  la sp, stack

La función do_1 inserta el número 1 en la pila. Como es el primer elemento, se situará en la dirección 0x2003. Para ello, esta función debe realizar las siguientes operaciones:

  • Decrementar el puntero de pila (sp) en 1 byte
  • Almacenar el valor 1 en la dirección indicada por sp

Esta es la implementación en RISC-V:

#---------------
#-- Palabra 1	
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------

do_1:
        #-- Crear espacio en la pila para un byte
	addi sp,sp,-1
	
	#-- Guardar el 1 en la pila
	li t0, 1
	sb t0, 0(sp)
	
	#-- Hemos terminado
	ret

Ahora implementamos la operación de suma. Las operaciones que debe hacer son:

  • Obtener el siguiente elemento de la pila
  • Incrementar el puntero de pila en 1 byte
  • Obtener el siguiente elemento de la pila
  • Incrementar el puntero de pila en 1 byte
  • Realizar la suma de los elementos obtenidos
  • Depositar el resultado en la pila, para lo cual debe decrementar primero sp en 1

Esta es la implementacion:

#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:

	#-- Leer el primer elemento
	lb t0, 0(sp)
	addi sp,sp,1
	
	#-- Leer segundo elemento
	lb t1, 0(sp)
	addi sp,sp,1
	
	#-- Realizar la suma
	add t0, t0,t1
	
	#-- Guardar resultado en la pila
	addi sp,sp,-1
	sb t0, 0(sp)
	
	#-- Hemos terminado
	ret

Y ya por último, para imprimir el resultado basta con leer el elemento de la pila (y luego incrementar sp), y llamar al sistema operativo del simulador para imprimir el resultado. Esta es la implementación:

#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
	
	#-- Sacar el elemento de la pila
	lb t0, 0(sp)
	addi sp,sp,1
	
	#-- Imprimirlo
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	ret

Este es el programa final, que está almacenado el fichero 01-suma-1-1.s:

#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 1
#--
#--  Implementación en ensamblador del programa Forth:
#--  1 1 + .
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------

	#-- Servicios del sistema operativo
	.eqv PRINT_INT 1
	.eqv EXIT 10
	.eqv PRINT_STRING 4

	.text

	#-- Inicializar la pila
	la sp, stack

	#-- Programa Forth: 1 1 + .
	jal do_1
	jal do_1
	jal do_add
	jal do_point
			
	#-- Interprete de forth: Imprimir " ok"
	la a0, ok_msg
	li a7, PRINT_STRING
	ecall
	
	#-- Terminar
	li a7, EXIT
	ecall
		
#----------------------------------------------------------------
#-- Implementacion de las palabras primitivas
#----------------------------------------------------------------			
					
#---------------
#-- Palabra 1	
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------

do_1:
        #-- Crear espacio en la pila para un byte
	addi sp,sp,-1
	
	#-- Guardar el 1 en la pila
	li t0, 1
	sb t0, 0(sp)
	
	#-- Hemos terminado
	ret
	
#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:

	#-- Leer el primer elemento
	lb t0, 0(sp)
	addi sp,sp,1
	
	#-- Leer segundo elemento
	lb t1, 0(sp)
	addi sp,sp,1
	
	#-- Realizar la suma
	add t0, t0,t1
	
	#-- Guardar resultado en la pila
	addi sp,sp,-1
	sb t0, 0(sp)
	
	#-- Hemos terminado
	ret
	
#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
	
	#-- Sacar el elemento de la pila
	lb t0, 0(sp)
	addi sp,sp,1
	
	#-- Imprimirlo
	mv a0, t0
	li a7, PRINT_INT
	ecall
	
	ret
	
	
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------	
	.data
	
   #-----------------------
   #-- PILA
   #----------------------	
	.space 4
stack:

    #---- Mensajes del interprete
ok_msg: .string "  ok\n"

Este programa se prueba así desde la consola:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 01-suma_1_1.s 
2  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ 

Vemos que sale el mensaje 2 ok. ¡Ha funcionado!

Todavía no estamos ejecutando programas fuente en forth, sino que los estamos "compilando" a pelo e implementándolos directamente en ensamblador. Pero... ¡Ya podemos ejecutar programas forth! Siempre y cuando hayamos implementado previamente las palabras indicadas

Vamos a probar otro programa:

1 1 1 + + .

Este programa suma los tres unos y muestra el resultado en la consola (3). Lo probamos primero en gforth:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
1 1 1 + + . 3  ok

Para implementarlo con nuestro intérprete lo "compilados" a pelo, realizando las llamadas necesarias. En nuestro caso, el programa forth traducido sería este (el resto de la implementación será igual que en 01-suma-1-1.s:

	#-- Programa Forth: 1 1 1 + + .
	jal do_1
	jal do_1
	jal do_1
	jal do_add
	jal do_add
	jal do_point

El programa completo está en el fichero 02-suma-1-1-1.s. Esto es lo que ocurre al ejecutarlo:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 02-suma_1_1_1.s 
3  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ 

Refactorizando 01-suma-1-1.s

En las implementaciones de Forth hay muchas instrucciones que se repiten, y que tienen una semántica específica en el intérprete de Forth, por lo que se agrupan en MACROS. Vamos a definir macros para las diferentes partes

Por un lado tenemos las macros usadas para acceder al sistema operativo:

  • EXIT: Terminar
  • PRINT_T0: Imprimir el valor del registro t0
  • PRINT_STRING: Imprimir una cadena en la consola

Por otro lado están las macro usadas para la implementación de las palabras primitivas

  • PUSH_BYTE (valor): Meter valor en la pila
  • PUSH_T0: Meter el registro t0 en la pila
  • POP_T0: Recuperar el registro t0 de la pila

Las implementaciones de las primitivas están en el fichero primitives.s. Usando las macros definidas anteriormente, quedan así:

#----------------------------------------------------------------
#-- Implementacion de las palabras primitivas
#----------------------------------------------------------------			
	
		
	.globl do_1, do_add, do_point				
					
	.include "macros.h"
	
	.text			
#---------------
#-- Palabra 1	
#--
#-- Meter 1 en la pila (PUSH 1)
#---------------

do_1:
      
	#-- Guardar el 1 en la pila
	PUSH_BYTE (1)
	
	#-- Hemos terminado
	ret
	
#---------------
#-- Palabra +
#--
#-- Obtener los dos ultimos elementos de la pila,
#-- sumarlos y depositar el resultado en la pila
#---------------
do_add:

	#-- Leer el primer elemento en t1
	POP_T0
	mv t1,t0
	
	#-- Leer segundo elemento
	POP_T0
	
	#-- Realizar la suma
	add t0, t0,t1
	
	#-- Guardar resultado en la pila
	PUSH_T0
	
	#-- Hemos terminado
	ret
	
#-------------------------
#-- Palabra .
#--
#-- Sacar el ultimo elemento de la pila e
#-- imprimirlo
#-------------------
do_point:
	
	#-- Sacar el elemento de la pila
	POP_T0
	
	#-- Imprimirlo
	PRINT_T0
	
	ret

Lo interesante es que ahora el programa principal, donde está nuestro programa "Forth compilado", es mucho más sencillo:

#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 3
#--
#--  Implementación en ensamblador del programa Forth:
#--  1 1 + .
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------

	.include "macros.h"

	.text

	#-- Inicializar la pila
	la sp, stack

	#-- Programa Forth: 1 1 + .
	jal do_1
	jal do_1
	jal do_add
	jal do_point
			
	#-- Interprete de forth: Imprimir " ok"
	PRINT_STRING ("  ok\n")
	
	#-- Terminar
	EXIT
		
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------	
	.data
	
   #-----------------------
   #-- PILA
   #----------------------	
	.space 4
stack:

De esta forma, podemos ir definiendo TODAS las primitivas necesarias. En este enlace, hay una serie de artículos muy buenos sobre las implementaciones de Forth: http://www.bradrodriguez.com/papers/

En esta parte MOVING FORTH Part 5: The Z80 Primitives se explica una implementación para el Z80 de las primitivas. Todas las primitivas están definidas aquí: http://www.bradrodriguez.com/papers/camel80.txt

En esta tabla están resumidas: http://www.bradrodriguez.com/papers/glosslo.txt

Instrucciones de más alto nivel

Vamos a definir una nueva instrucción: add3, que suma los 3 elementos que haya en la pila. Esta es la definición en Forth:

: add3 + + ;

Ahora creamos un programa nuevo que usa esta nueva instrucción:

1 1 1 add3 .

El resultado en gforh es este:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: add3 + + ;  ok
1 1 1 add3 . 3  ok

Vamos a implementar ese programa en nuestro intérprete en ensamblador. Una posibilidad sería implementar add3 directamente como una primitiva. Pero dado que add3 en realidad lo que hace es llamar dos veces a la operación de suma, lo vamos a definir de esa forma

Add3 es ahora un subrutina intermedia, que llama a otras dos subrutinas. Por tanto, la direccion de retorno la tenemos que guardar en OTRA PILA (No es la pila de datos). Esta pila se llama rstack (return stack) y contiene las direcciones de retorno de las subrutinas superiores. Vamos a utilizar el registro s0 como puntero de esta pila

La definimos así:

	.data
	
   #-----------------------
   #-- PILA de Datos
   #----------------------	
	.space 4  #-- Tamaño 4 bytes
stack:

   #-----------------------
   #-- PILA de retorno
   #-- Elementos de 32 bits
   #-----------------------
   	.align 2  #-- Alinear a palabra
        .space 16  #-- Tamaño: 4 palabras
rstack:

El nuevo programa forth a ejecutar es este:

#-- Programa Forth: 1 1 1 add3 .
	jal do_1
	jal do_1
	jal do_1
	jal do_add3
	jal do_point

Para que funcione hay que inicializar primero la pila de retorno:

	.text

	#-- Inicializar la pila de datos
	la sp, stack
	
	#-- Inicializar la pila de retorno
	la s0, rstack

Y ahora hay que definir la función do_add3:

#--------------------------------
#-- Palabras de nivel superior	
#--------------------------------
do_add3:
	#-- Guardar direccion de retorno en la pila r
	addi s0,s0,-4
	sw ra, 0(s0)
	
	#-- Llamar a las palabras + +
	jal do_add
	jal do_add
	
	#-- Recuperar la direccion de retnor de la pila r
	lw ra, 0(s0)
	addi s0,s0,4

	#-- Devolver control
	ret	

El programa completo se encuentra en el fichero 04-add3-1-1-1.s. Y al ejecutarlo desde la consola esto es lo que se obtiene:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 02-suma_1_1_1.s 
3  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$

Refactorizando 04-add3-1-1-1.s

Vamos a hacer lo mismo que antes... definir macros para las instrucciones más comunes. También nos llevamos add3 al fichero higher_level.s

Estas son las nuevas macros definidas:

  • PUSH_RA: Almacenar dirección de retorno en rstack
	#-- Guardar direccion de retorno en rstack
	.macro PUSH_RA
	  addi s0,s0,-4
	  sw ra,0(s0)
	.end_macro
  • POP_RA: Recuperar la dirección de retorno de rstack
	#-- Repucerar direccion de retorno de rstack
	.macro POP_RA
	   lw ra,0(s0)
	   addi s0,s0,4
	.end_macro

El nuevo programa queda así:

#--------------------------------------------------------------------
#-- INTERPRETE DE FORTH. Version 5
#-- 
#--  Implementación en ensamblador del programa Forth:
#--  1 1 1 add3 .
#--
#-- Donde ahora add3 es una instrucción de nivel superior, definida
#--  a partir de palabras primitivas
#--
#-- El resultado se imprime en la consola del simulador
#-- Una vez completado, termina
#--------------------------------------------------------------------

	.include "macros.h"

	.text

	#-- Inicializar la pila de datos
	la sp, stack
	
	#-- Inicializar la pila de retorno
	la s0, rstack

	#-- Programa Forth: 1 1 1 add3 .
	jal do_1
	jal do_1
	jal do_1
	jal do_add3
	jal do_point
			
	#-- Interprete de forth: Imprimir " ok"
	PRINT_STRING ("  ok\n")
	
	#-- Terminar
	EXIT
		

		
		
#---------------------------------
#-- SEGMENTO DE DATOS
#---------------------------------	
	.data
	
   #-----------------------
   #-- PILA de Datos
   #----------------------	
	.space 4  #-- Tamaño 4 bytes
stack:

   #-----------------------
   #-- PILA de retorno
   #-- Elementos de 32 bits
   #-----------------------
   	.align 2  #-- Alinear a palabra
        .space 16  #-- Tamaño: 4 palabras
rstack:

Se encuentra en el fichero 05-add3-1-1-1.s

Hacia el intérprete

Con esta filosofía, si se implementan las funciones primitivas necesarioas y las de alto nivel, se puede tener un primer intérprete externo. Es lo que voy a intentar hacer. De momento este intérprete sólo ejecutará palabras de forth... no permitirá crear nuevas... Iré creando las cosas a medida que las vaya necesitando

Voy a ecar un vistazo al código del libro "Moving forth"....

La implementación comienza con este programa: http://www.bradrodriguez.com/papers/camel80.txt. El programa principal es este...

; CP/M ENTRY POINT
        org 100h
reset:  ld hl,(6h)   ; BDOS address, rounded down
        ld l,0       ;    = end of avail.mem (EM)
        dec h        ; EM-100h
        ld sp,hl     ;      = top of param stack
        inc h        ; EM
        push hl
        pop ix       ;      = top of return stack
        dec h        ; EM-200h
        dec h
        push hl
        pop iy       ;      = bottom of user area
        ld de,1      ; do reset if COLD returns
        jp COLD      ; enter top-level Forth word

Lo importante de este código es que la palabra de alto nivel que se invoca es COLD. Al probarla desde gforth efectivamente el intérprete se reinicia... y buscando información efectivamente sirva para reiniciar el sistema...

Voy a buscar la definición de COLD en el códig. En el listado anterior, de las primitivas NO está, porque claro, es una palabra de más alto nivel. Lo voy a buscar aquí: http://www.bradrodriguez.com/papers/camel80h.txt. Está definida justo al final:

;Z COLD     --      cold start Forth system
;   UINIT U0 #INIT CMOVE      init user area
;   80 COUNT INTERPRET       interpret CP/M cmd
;   ." Z80 CamelForth etc."
;   ABORT ;
    head COLD,4,COLD,docolon
        DW UINIT,U0,NINIT,CMOVE
        DW LIT,80h,COUNT,INTERPRET
        DW XSQUOTE
        DB 35,'Z80 CamelForth v1.01  25 Jan 1995'
        DB 0dh,0ah
        DW TYPE,ABORT       ; ABORT never returns

No soy capaz de entenderlo.... en fin... todavía me queda mucho por aprender...

Implementando más palabras primitivas

Como me queda mucho por aprender, voy a hacerlo al revés: partir de los más pequeño e ir entendiéndolas e implementándolas. Esta es la lista completa de palabras primitivas:

    TABLE 1.  GLOSSARY OF WORDS IN CAMEL80.AZM
    Words which are (usually) written in CODE.

NAME   stack in -- stack out          description

  Guide to stack diagrams:  R: = return stack,
  c = 8-bit character, flag = boolean (0 or -1), 
  n = signed 16-bit, u = unsigned 16-bit,
  d = signed 32-bit, ud = unsigned 32-bit,
  +n = unsigned 15-bit, x = any cell value, 
  i*x j*x = any number of cell values,
  a-addr = aligned adrs, c-addr = character adrs
  p-addr = I/O port adrs, sys = system-specific.
  Refer to ANS Forth document for more details.

               ANS Forth Core words
These are required words whose definitions are 
specified by the ANS Forth document.

!      x a-addr --           store cell in memory
+      n1/u1 n2/u2 -- n3/u3             add n1+n2
+!     n/u a-addr --           add cell to memory
-      n1/u1 n2/u2 -- n3/u3        subtract n1-n2
<      n1 n2 -- flag           test n1<n2, signed
=      x1 x2 -- flag                   test x1=x2
>      n1 n2 -- flag           test n1>n2, signed
>R     x --   R: -- x        push to return stack
?DUP   x -- 0 | x x                DUP if nonzero
@      a-addr -- x         fetch cell from memory
0<     n -- flag             true if TOS negative
0=     n/u -- flag           return true if TOS=0
1+     n1/u1 -- n2/u2                add 1 to TOS
1-     n1/u1 -- n2/u2         subtract 1 from TOS
2*     x1 -- x2             arithmetic left shift
2/     x1 -- x2            arithmetic right shift
AND    x1 x2 -- x3                    logical AND
CONSTANT   n --           define a Forth constant
C!     c c-addr --           store char in memory
C@     c-addr -- c         fetch char from memory
DROP   x --                     drop top of stack
DUP    x -- x x            duplicate top of stack
EMIT   c --           output character to console
EXECUTE   i*x xt -- j*x   execute Forth word 'xt'
EXIT   --                 exit a colon definition
FILL   c-addr u c --        fill memory with char
I      -- n   R: sys1 sys2 -- sys1 sys2
                     get the innermost loop index
INVERT x1 -- x2                 bitwise inversion
J      -- n   R: 4*sys -- 4*sys
                        get the second loop index
KEY    -- c           get character from keyboard
LSHIFT x1 u -- x2        logical L shift u places
NEGATE x1 -- x2                  two's complement
OR     x1 x2 -- x3                     logical OR
OVER   x1 x2 -- x1 x2 x1        per stack diagram
ROT    x1 x2 x3 -- x2 x3 x1     per stack diagram
RSHIFT x1 u -- x2        logical R shift u places
R>     -- x    R: x --      pop from return stack
R@     -- x    R: x -- x       fetch from rtn stk
SWAP   x1 x2 -- x2 x1          swap top two items
UM*    u1 u2 -- ud       unsigned 16x16->32 mult.
UM/MOD ud u1 -- u2 u3     unsigned 32/16->16 div.
UNLOOP --   R: sys1 sys2 --       drop loop parms
U<     u1 u2 -- flag         test u1<n2, unsigned
VARIABLE   --             define a Forth variable
XOR    x1 x2 -- x3                    logical XOR

               ANS Forth Extensions
These are optional words whose definitions are
specified by the ANS Forth document.

<>     x1 x2 -- flag               test not equal 
BYE    i*x --                      return to CP/M
CMOVE  c-addr1 c-addr2 u --      move from bottom
CMOVE> c-addr1 c-addr2 u --         move from top
KEY?   -- flag        return true if char waiting
M+     d1 n -- d2            add single to double
NIP    x1 x2 -- x2              per stack diagram
TUCK   x1 x2 -- x2 x1 x2        per stack diagram
U>     u1 u2 -- flag         test u1>u2, unsigned 

               Private Extensions
These are words which are unique to CamelForth.
Many of these are necessary to implement ANS
Forth words, but are not specified by the ANS
document.  Others are functions I find useful.

(do)   n1|u1 n2|u2 --  R: -- sys1 sys2
                             run-time code for DO
(loop) R: sys1 sys2 --  | sys1 sys2
                           run-time code for LOOP
(+loop)  n --   R: sys1 sys2 --  | sys1 sys2
                          run-time code for +LOOP
><     x1 -- x2                        swap bytes 
?branch  x --                  branch if TOS zero
BDOS   DE C -- A                   call CP/M BDOS
branch --                           branch always
lit    -- x         fetch inline literal to stack
PC!    c p-addr --            output char to port
PC@    p-addr -- c           input char from port
RP!    a-addr --         set return stack pointer
RP@    -- a-addr         get return stack pointer
SCAN   c-addr1 u1 c -- c-addr2 u2
                               find matching char
SKIP   c-addr1 u1 c -- c-addr2 u2
                              skip matching chars
SP!    a-addr --           set data stack pointer
SP@    -- a-addr           get data stack pointer
S=     c-addr1 c-addr2 u -- n      string compare
               n<0: s1<s2, n=0: s1=s2, n>0: s1>s2
USER   n --              define user variable 'n'

Voy a empezar por una muy necesaria lit. Con esta metemos un número en la pila. Tal y como estoy implementando el intérprete, el concept "literal" no tiene mucho sentido porque en realidad mis programas forth son llamadas a subrutinas.

Esta es la implementación:

#-----------------------------------
#-- Meter un literal en la pila
#-- a0: Literal a meter en la pila
#-----------------------------------	
do_lit:
	mv t0,a0
	PUSH_T0
	ret

Y así queda el programa principal 06-lit.s

#-- Programa Forth: 2 3 + .
	li a0, 2    
	jal do_lit #-- 2
	
	li a0, 3
	jal do_lit #-- 3
	
	jal do_add   #-- +
	jal do_point #-- .

La siguiente que voy a implementar es EMIT. En la implementación del Z80 lo tiene dividido en dos partes: Una genérica BDOS para hacer llamadas al sistema operativo, y luego otra que usa esa para imprimir el carácter.

Yo lo voy a hacer en una única función, por simplicidad

El programa en forth es este: 65 emit. Esto es lo que debe salir:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
65 emit A ok

He aprovecharo para definir macros TAMBIEN para las instrucciones FORTH, en vez de usar jal. El programa con el emit queda ahora así (07-emit.s)

#-- Programa Forth: 65 emit
	LIT(65)
	EMIT

Este es el resultado al probarlo en consola:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 07-emit.s 
A  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$

Con las nuevas macros ahora es mucho más fácil hacer los programas en Forth :-)

Este serie el programa para imprimir "HOLA":

72 emit 79 emit 76 emit 65 emit

Lo probamos en gforth:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
72 emit 79 emit 76 emit 65 emit HOLA ok

Este es el programa Forth en el RISC-V:

#-- Programa Forth: 
	LIT(72)
	EMIT
	LIT(79)
	EMIT
	LIT(76)
	EMIT
	LIT(65)
	EMIT

Y este el resultado de su ejecución:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ ./run.sh 08-emit-hola.s 
HOLA  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-23
$ 

Ya tengo la palabra KEY para leer un carácter del teclado, sin embargo funciona un poco distinto al del forth estándar porque el rars se comporta distinto. En forth, al llamar a key y que el usuario pulse la tecla NO SE HACE ECO de la tecla pulsada. Sin embargo en el RARs sí

También en el RARs, cuando se ejecuta en consola, para introducir 1 carácter hay que apretarlo y además pulsar ENTER

Pero por lo de más está funcionando...

Implementando store

Listo! Ya lo tengo implementado en el ejemplo 10-store.s. He aprovechado para hacer la pila de elementos de 32-bits, en vez de bytes, para poder almacenar direcciones y valores grandes

Otras cosas

Para aprender más forth voy a leer este libro:

https://www.forth.com/starting-forth/1-forth-stacks-dictionary/

Este programa borra la pantalla (con el código ansi de borrar la pantalla)

74 50 91 27 emit emit emit emit

Este es el mismo, pero puesto en orden:

27 emit 91 emit 50 emit 74 emit

Con este comando cambiamos el color a verde

27 emit 91 emit 51 emit 50 emit 109 emit

Y con este se hace home del cursor:

27 emit 91 emit 72 emit

El comando home lo podemos redefinir así:

: home 27 emit 91 emit 72 emit ;  ok

y el comando CLS como un borrado de pantalla y un home

: cls 27 emit 91 emit 50 emit 74 emit home ;

Este es para cambiar el color a verde

: green 27 emit 91 emit 51 emit 50 emit 109 emit ;

Lo metemos en un único programa para utilizarlo más fácilmente

( Definiciones para secuencias ANSI )

: home 27 emit 91 emit 72 emit ; 
: cls 27 emit 91 emit 50 emit 74 emit home ;
: green 27 emit 91 emit 51 emit 50 emit 109 emit ;

green
cls

2023-05-24

Sigo implementando palabras primitivas. Ya tengo todas las de comparación... Las que quedan de comparación no son primitiva, sino implementadas a partir de las otras. Lo implementaré más adelante... ahora me estoy centrando en la implementación en ensamblador

2023-05-25

Sigo con más implementaciones de palabras primitivas

He re-implementado la palabra LIT para introducir literales, y que sea más parecida a la implementación del Z80 de CamelForth. Ahora se hace en dos partes:

  • LIT --> Llamada a la funcion do_lit
  • DW(3) --> Macro que inserta el numero 3 en el código

En realidad, en el Rars no se puede insertar datos dentro del código, sin embargo he hecho un HACK temporal. El dato (de 20-bits como máximo) está en los 20-bits de mayor peso de a instrucción lui.

¿Cómo sabe LIT cómo acceder a la constante? LIT es una llamada a la subrutina do_lit, que hace el trabajo. Bien, en ella sabemos que ra apunta a la siguiente instrucción a LIT, es decir, que ra contiene la dirección del literal. Basta con leer esa información y luego incrementar ra en 4 antes de retornar para que se ejecute la siguiente instrucción del Thread Forth

Lo he implementado y FUNCIONA GENIAL!!!!

Ya tengo implementado también el Branch!!

Ya tengo implementado el qbranch! Ya se pueden hacer saltos condicionales....

2023-05-26

Ya tengo las instrucciones DO - LOOP! por lo que ya se pueden hacer bucles... de momento el código tiene que estar compilado Lo que hace DO es inicializar el bucle. Se leen de la pila el límite y el índice y se guardan en la pila R, que se usa de manera interna. Loop se encarga de incrementar el índice y repetir si indice < limite. Para realizar la repetición se lee de la siguiente literal la dirección de la primera instrucción del bucle (el DO NO forma parte del bucle, es sólo para la inicializacion. Es la dirección siguiente a DO la que hay que usar)

Para almacenar direcciones voy a hacer cambios. Para el branch voy a utilizar una literal diferente, que represente una dirección. Y por la limitación del RARs hay que guardara en dos instrucciones: lui y addi. La primera almacena los 20-bits de mayor peso. La siguiente los 12 de menor.

Esto lo hago porque necesito poder utilizar directamente las etiquetas del ensamblador para definir los saltos, o de lo contrario habría que estar calculando a pelo la dirección... lo que es tedioso y propenso a errores. Voy a intentar una solución universal para el rars... para el gnu asm será más fácil...

Empiezo por el ejemplo 51-branch.s en el que re-implementamos branch, pero usando las nuevas literales de direcciones, que llamaremos LIT_ADDR. Como argumento se le pasará LA ETIQUETA a donde realizar el salto... Voy a ver si lo consigo

Lo he solucionado con un apaño muy cutre, pero que funciona. La literal de salto, en vez de contener la dirección a la que saltar, contiene directamente la instrucción j con la etiqueta del salto. Branch en realidad no hace nada ahora... simplemente retorna y la siguiente instrucción es el jump

Ya está implementado el do-loop con este nuevo enfoque... es mucho más sencillo y funciona muy bien! ¡¡¡vaaaamos!!!

El comando +LOOP is igual a loop, pero incrementa el índice en n unidades, estando n en la pila. De momento lo voy a implementar de forma independiente a LOOP, pero hay mucho código que se podría reutilizar

Ya tengo implementadas TODAS las palabras primitivas para la implementación de LOOPs y BRANCHES....

Antes de seguir con la implementación de palabras primitivas, voy a implementar las palabras de prueba definidas en el archivo cameltst.azm. El camelforth está implementado (para z80) en este repositorio de github:

https://github.com/jamesbowman/camelforth-z80/blob/main/og/cameltst.azm

Ya tengo el DUMP !!!! Cómo mola!!!!

Ahora ejecutamos este programa forth:

0 64 DUMP

Esto es lo obtenemos al ejecutarlo:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$ ./run.sh 64-dump.s 

0000 17 21 00 00 13 01 01 02 17 24 00 00 13 04 84 03 
0010 EF 00 C0 3A 37 00 00 00 EF 00 40 3A 37 00 04 00 
0020 EF 00 40 77 17 25 00 00 13 05 05 03 93 08 40 00 
0030 73 00 00 00 93 08 A0 00 73 00 00 00 93 02 10 00 
 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$

Se ha hecho un volcado desde la dirección 0. Para comprobar que el volcado es correcto se puede compara con el código que hay en el fichero firmware.bin:

$ hd -n 64 firmware.bin 
00000000  17 21 00 00 13 01 01 02  17 24 00 00 13 04 84 03  |.!.......$......|
00000010  ef 00 c0 3a 37 00 00 00  ef 00 40 3a 37 00 04 00  |...:7.....@:7...|
00000020  ef 00 40 77 17 25 00 00  13 05 05 03 93 08 40 00  |..@w.%........@.|
00000030  73 00 00 00 93 08 a0 00  73 00 00 00 93 02 10 00  |s.......s.......|
00000040
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-05-26
$

¡Es lo mismo! (Aunque con formato diferente)

El intérprete ya empieza a funcionar.... aunque todavía sin modo interactivo

2023-05-28

Ya tengo implementados todos los ejemplos de test (fichero cameltst.azm) También tengo constantes (compiladas a mano) (docon)

2023-05-29

Ya tenemos variables compiladas a mano (dovar)

La implementación de la palabra UINIT ha sido complicada. Esta palabra devuelve la dirección de memoria donde están los valores iniciales de la zona de usuario: Fin del diccionario, dirección de la última palabra del diccionario, etc...

Hay que meterla en el segmento de datos, ya que tiene en total 9 palabras (de 4bytes). El problema está que hay que llamar a docreate... y como estamos en el segmento de dato el RARs NO ENSAMBLA... por ello hay que meter las instrucciones en ensamblador a pelo... También la dirección de docreate (dovar) tiene que estar fija... la hemos llevado a la parte inferior del segmento de codigo (en la dirección 4)

He empezado a implementar la palabra COLD, que es la primera que se ejecuta... para ello necesito implementar CMOVE.. así que voy a aprovechar para implementar las palabras relacionadas con el movimiento de bytes en la memoria

El FILL ya está operativo... y también el CMOVE

Ya tengo TYPE! Ya puedo imprimir cadenas literales!!!

2023-05-30

Estoy empezando a trabajar en las conversiones. Estaban ocurriendo bastantes fallos, hasta que me he dado cuenta de que en forth los números dobles ocupan 2 células en memoria! El orden de almacenamiento es primero la célula de menor peso, y luego la de mayor (la de mayor en el TOS). De momento voy a utilizar el riscv como si fuese sólo de 16.... así que simularemos los números dobles como dos células (cada una de 32-bits), pero la de mayor peso hay que asegurarse que siempre sea 0 ó -1 (todo a unos) si es negativo...

Cuando entienda bien el funcionamiento, lo podré implementar usando sólo 1 célula de 32-bits...

Así que ahora toca rehacer todas las operaciones con dobles con este nuevo enfoque...

2023-05-31

Implementadas las palabras de cambio de base HEX y DECIMAL

Joder... el RARs se me queda pequeño. Estoy usando el modelo de memoria pequeño, en el que el código se sitúa en la dirección 0. La dirección más alta es 0xFFC, es decir, 4KB...Y ya he llegado a ese límite....

Tendré que pasar a otro modelo... pero joder... tengo direcciones cableadas... va a ser complicado

ok... ya tengo una primera solución... usando saltos largos (meter en el registro t0 la dirección a la que saltar y llamar jalr)

Parece que está funcionando... el problema es que ahora las direcciones son mayores de 16-bits y la depuracińo se complica Tengo que asegurarme que todo está funcionando bien con las nuevas direcciones
Por otro lado, el problema del tamaño ya se ha solucionado... el programa actual ocupa más de 4K y ensambla sin problemas

Ya tengo implementada la palabra .S (DOTS). Madre mía... me ha gustado un huevo.. he tenido que solucionar muchísimos bugs... pero ya está....

En esta web está definido el estandar de forth: http://lars.nocrew.org/forth2012/
Me la tengo que leer para tener un conocimiento más profundo

Aquí hay un programa de test en forth... lo tengo que estudiar:

http://www.forth200x.org/tests/ttester.fs

2023-06-2

Estoy implementando la palabra INTERPRET y sus palabras de las que depende. Con la palabra NFA>CFA me encuentro con el problema de la alineación. NFA es un campo de bytes y tras él está la palabra (alinada) con la dirección del código (o directamente con el código, según que se use RARs o GNU...)

En cualquier caso, necesito una palabra forth para detectar si una dirección está alineada o no. Y si no está alinearla, obtener la direccion alineada... vamos... una palabra que convierta una dirección en su alineada (si ya está alineada se deja como está...)

Voy a apovechar para hacer este programa en gforth... y luego lo paso al risv-forth

Dese gforth se añade un archivo (por ejemplo align.fs) con este comando:

S" align.fs" included

Ya tengo el programa. Lo he dividido en dos palabras: ?align y align. La primera es para comprobar si una dirección está o no alineada, y la segunda es para calcular su dirección alineada (si ya estaba alineada se deja como está)

( addr -- flag )
( flag: -1: Dir alineada )
( flag:  0: Dir no alineada )
: ?ALIGN 

    ( Obtener los 2 bits de menor peso )
    ( Estos determinan si es una direccion alineada )
    ( o no:   bits == 00: Alineada )

  3
  and   ( Obtener los 2 bits de menor peso)
  0=    ( Es 0?)
  IF
    0 invert  ( Dir alineada: Dejar -1 )
  ELSE
    0  ( Dir no alineada: Dejar 0 )
  THEN
;


( addr1 -- addr2 )
( Convertir addr1 en una direccion alineada addr2 )
( Si ya esta alineada se quea como esta: addr2 = addr1 ) 
: align 
  dup ?align 
  invert IF 
    ( Sumar 4)
    4 +

    ( Poner a 0 los 2 bits de menor peso )
    3 invert
    and
  THEN 
;

Ahora lo voy a integrar en el riscv-forth

Está página está muy bien:

https://forth-standard.org/

2023-06-4

Ya tengo el primer ejemplo interactivo! De momento sólo lo he probado con números de un dígito. Tras el "ok" se imprime la pila, y se puede ver cómo efectivamente va a umentando al introducir números

El ejemplo es 153-quit.s

Este es un ejemplo de uso:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$ ./run.sh 153-quit.s 
Z80 CamelForth v1.01  25 Jan 1995
3
 ok 
3 
5
 ok 
3 5 
7
 ok 
3 5 7 
9
 ok 
3 5 7 9 

Ahora es cuando se empiezan a a ver los resultados, y la cosa se pone interesante

Tengo que empezar a meter palabras en el diccionario. La primera que voy a meter es bye, y probar a salir del modo interactivo...

Ya tengo el BYE en el diccionario! mi primera palabra interactiva!!! Y FUNCIONA!!!

Ejemplo: 154-bye.s

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$ ./run.sh 154-bye.s 
Z80 CamelForth v1.01  25 Jan 1995
5
 ok 
5 
7
 ok 
5 7 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-04
$

Es el momento de llevarse el diccionario a un fichero separado: dicctionary.s... Listo!!!

Ahora tengo que ir metiendo poco a poco todas las palabras en el diccionario. La siguiente será + para comprobar que está funcionando todo

2023-06-5

Ya he ejecutado la primera instrucción interpretada: La suma.... El ejemplo está en 156-plus.s

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-05
$ ./run.sh 156-plus.s 
Z80 CamelForth v1.01  25 Jan 1995
1 1 +
 ok 
2 
3 5 +
 ok 
2 8 
+
 ok 
10 
bye
 bye?
ok 

BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-05
$

2023-06-6

Estoy empezando con el diccionario... Lo primero que voy a hacer es crear una entrada del diccionario para la palabra ".S". Primero la creo "manualmente" y compruebo que funciona

$ ./run.sh 166-create.s 
Z80 CamelForth v1.01  25 Jan 1995
1 2 3
 ok 
.S
 1 2 3 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$

Ahora hago un volcado del diccionario:

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ ./run.sh 166-create.s 
Z80 CamelForth v1.01  25 Jan 1995
0x100100cd 0x100100c8 
00C8 C1 00 01 10 00 02 2E 53 E4 0F 40 00 00 00 00 00 
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$

A partir de la dirección 0x100100c8 está la nueva entrada correspondiente a .S

El siguiente paso es quitar la palabra .S e irla creando poco a poco, para comprobar que entiendo perfectamente todo. Con eso ya podemos crear la palabra CREATE

Listo! La palabra ".S" NO está en el diccionario, pero se mete en tiempo de ejecución en el ejemplo 163-create.s. Primero se mete y luego se pasa al modo interactivo para comprobarlo:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ ./run.sh 167-create.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100c8 
00C8 C1 00 01 10 00 02 2E 53 AC 0F 40 00 00 00 00 00 
 ok
1 2 3
 ok 
.S
 1 2 3 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-06
$ 

Llegó el momento de implementar : para crear definiciones interactivamente...

Voy a intentar primero que funcione esto:

: .. . ;

Es simplemente una llamada a la palabra `.`. 

Ya lo llevo bastante avanzado, pero tengo un probelma que resolver: Tengo que generar el código máquina de la instrucción li t0,direccion

Pero como direccion es de 32 bits, esto hay que descomponerlo a su vez en dos instrucciones: lui y addi... tengo que estudiarlo...
Tengo una solución, pero no es eficiente. De momento la voy a implementar y cuando funcione todo bien, lo voy mejorando... Este es el código para guardar un valor de 32-bits en un registros (Ejemplo con el valor 0x12345FEC

```asm
lui t1,0x12345  #-- 20-bits de mayor peso
lui t0,0xFEC    #-- 12-bits de menor peso
srli t0,t0,12   #-- Desplazar 12-bits a la derecha
or t1,t1,t0     #-- Unir

El siguiente problema es que hay que crear el código máquina de esas instrucciones directamente...

2023-06-7

La compilación no es trivial, se está complicando. Así que para depurar mejor, voy a empezar primero por implementar la palabra WORDS para visualizar todas las que hay en el diccionario

De momento las que hay se han compilado a mano. Este es el resultado (ejemplo 173-words.s)

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 172-words.s 
Z80 CamelForth v1.01  25 Jan 1995
.S . + BYE lit EXIT  ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ 

La palabra mínima sería la nula: que no hace nada, simplemente retorna. La definimos así:

: NULL ;

Si la implementamos directamente el resultado sería este:

do_null:
  DOCOLON
  EXIT

Y la estructura del diccionario así:

#-- Palabra 6
    .align 2
    .word link5
    .byte 0
lastword:
link6:
    .byte 4
    .ascii "NULL"
    .word do_null  #-- CFA

Primero vamos a completar la palabra CREATE que crea esta cabecera con un CFA en blanco. De hecho se puede meter guardar un valor especial para diferenciarlo (-1 por ejemplo)

Voy a hacer palabras de utilidad para mostrar información del diccionario sobre la palabra actual, y poder navegar fácilmente entre los diferentes campos, para hacer las pruebas

La primera que voy a implementar es .WINFO: Imprimir información de la palabra. Recibo como argumento la dirección de una palabra e imprime TODA su información

Ya está implementada la palabra .dotwinfo. Este es el resultado:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 173-dotwinfo.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100c8  Link: 0x100100c1 
0x100100cc  Inmd: 0 
0x100100cd  NLen: 2 
0x100100ce  Name: .S
0x100100d0  CFA:  0x00400f90 

 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ 

En el ejemplo 174-create.s se crear la palabra .. que simplemente llama a .S:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 174-create.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100c8  Link: 0x100100c1 
0x100100cc  Inmd: 0 
0x100100cd  NLen: 2 
0x100100ce  Name: .S
0x100100d0  CFA:  0x00400fa8 


0x100100d4  Link: 0x100100cd 
0x100100d8  Inmd: 0 
0x100100d9  NLen: 2 
0x100100da  Name: ..
0x100100dc  CFA:  0x00400fa8 

 ok
1 2 3 4
 ok 
..
 1 2 3 4 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ 

Todo parece funcionar bien, pero hay un bug... Si cambio el nombre y por ejemplo lo amplío a 3 bytes: ... algo peta... tengo que encontrar el error

Listo! ya se crean nombres de cualquier longitud y funciona... Este es el ejemplo que antes no funcionaba y ahora sí (175-create.s)

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 175-create.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100c8  Link: 0x100100c1 
0x100100cc  Inmd: 0 
0x100100cd  NLen: 2 
0x100100ce  Name: .S
0x100100d0  CFA:  0x00400fb0 

0x100100d4  Link: 0x100100cd 
0x100100d8  Inmd: 0 
0x100100d9  NLen: 3 
0x100100da  Name: ...
0x100100e0  CFA:  0x00400fb0 

0x100100e4 
 ok
1 2 3 4 5
 ok 
...
 1 2 3 4 5 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ 

He creado la palabra .WCODE para mostrar el código máquina de una palabra. Ejemplo: 176-dotwcode.s

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$ ./run.sh 176-dotwcode.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100d4  Link: 0x100100cd 
0x100100d8  Inmd: 0 
0x100100d9  NLen: 3 
0x100100da  Name: NOP
0x100100e0  CFA:  0x00401bd0 

0x00401bd0 : 0xffc40413 
0x00401bd4 : 0x00142023 
0x00401bd8 : 0x00042083 
0x00401bdc : 0x00440413 
0x00401be0 : 0x00008067 
 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-07
$

2023-06-08

Estoy empezando a implementar las variables. La coa de momento no funciona, hay que depurar... Estoy probando el ejemplo 186-variable.s. En el diccionario está metida la Variable A (Su dirección es la siguiente al campo CFA). Al ejecutar el ejemplo se muestra la información en la consola de la palabra A, y se pasa al modo interactivo:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 186-variable.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100f4  Link: 0x100100e9 
0x100100f8  Inmd: 0 
0x100100f9  NLen: 01
0x100100fa  Name: A
0x100100fc  CFA:  0x0040008c 

 ok 
A
 ok 
.S
 4200388 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ 

La dirección de la variale debería ser 0x10010100 (la siguiente a 0x100100fc) pero es 0x4017c4 (4200388)

Hay algo mal...

Igual pruebo a ejecutar directamente una llamada a dovar.. no se ... tengo que pensarlo

Ya tengo una primera solución. NO es muy óptima (todavía). Pero primero hay que hacer que funcione. La solución es meterlo en el código. En el código se almacena la variable y se llama a dovar. dovar lo que hace es leer la variable y meterla en la pila. Tiene mucha sobrecarga, pero de momento es la solución

De momento para que funcione la función dovar tiene que estar en la dirección fija 0x00400004

Esto es lo que ocurre al ejecuar el ejemplo anterior:

Z80 CamelForth v1.01  25 Jan 1995

0x100100f4  Link: 0x100100e9 
0x100100f8  Inmd: 0 
0x100100f9  NLen: 01
0x100100fa  Name: A
0x100100fc  CFA:  0x10010100 
0x10010100 : 0xffc40413 
0x10010104 : 0x00142023 
0x10010108 : 0x004002b7 
0x1001010c : 0x00428293 
0x10010110 : 0x000280e7 
0x10010114 : 0x00000000 
 ok
A
 ok 
.S
 268501268 ok 

La dirección que hay en la pila es ahora 0x10010114 (268501268) que efectivamente es la de la variable A

El siguiente paso es hacerlo automáticamente, desde un programa... Eso es lo que hace la palabra VARIABLE

En el ejemplo 187-variable-B.s ya está solucionado:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 187-variable-B.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100100f4  Link: 0x100100e9 
0x100100f8  Inmd: 0 
0x100100f9  NLen: 01
0x100100fa  Name: A
0x100100fc  CFA:  0x10010100 
0x10010100 : 0xffc40413 
0x10010104 : 0x00142023 
0x10010108 : 0x004002b7 
0x1001010c : 0x00428293 
0x10010110 : 0x000280e7 
0x10010114 : 0x00000000 

0x10010118  Link: 0x100100f9 
0x1001011c  Inmd: 0 
0x1001011d  NLen: 01
0x1001011e  Name: B
0x10010120  CFA:  0x10010124 
0x10010124 : 0xffc40413 
0x10010128 : 0x00142023 
0x1001012c : 0x004002b7 
0x10010130 : 0x00428293 
0x10010134 : 0x000280e7 
0x10010138 : 0x00000000 
 ok
B
 ok 
.S
 268501304 ok 
A
 ok 
.S
 268501304 268501268 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ 

He implementado la constante ESC (con valor 0xCAFE) de forma manual. Se prueba en este ejemplo: 188-constant.s

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ ./run.sh 189-constant.s 
Z80 CamelForth v1.01  25 Jan 1995

0x10010118  Link: 0x100100f9 
0x1001011c  Inmd: 0 
0x1001011d  NLen: 03
0x1001011e  Name: ESC
0x10010124  CFA:  0x10010128 
0x10010128 : 0xffc40413 
0x1001012c : 0x00142023 
0x10010130 : 0x004002b7 
0x10010134 : 0x01c28293 
0x10010138 : 0x000280e7 
0x1001013c : 0x0000cafe 
0x10010140 : 0x00000000 
 ok
ESC
 ok 
.S
 51966 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-08
$ 

El valor 51966 es 0xCAFE

2023-06-09

El objetivo de hoy es lograr generar la primera palabra compilada. Su definicion será esta:

: TEST -- ;

Donde la palabra -- está implementada de manera manual y lo que hace es pintar una línea de algunos caracteres. Es decir, TEST pinta una linea llamando a --. Lo que me interesa es el código de TEST que está formado por la llamada a docolon, el jal a línea y por último el exit

Primero lo implementamos todo manualmente y lo probamos

Ya tengo el ejemplo: 193-test.s. La palabra TEST está metida en el diccionario. Primero se hace un volcado de esta palabra del diccionario junto a su código máquina. Luego se pasa al modo interactivo y se comprueba que funciona: se dibuja una línea

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 193-test.s 
Z80 CamelForth v1.01  25 Jan 1995

0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 04
0x1001015e  Name: TEST
0x10010164  CFA:  0x00401e48 
0x00401e48 : 0xffc40413 
0x00401e4c : 0x00142023 
0x00401e50 : 0xfa5ff0ef 
0x00401e54 : 0x00042083 
0x00401e58 : 0x00440413 
0x00401e5c : 0x00008067 
0x00401e60 : 0xcafebaca 

 ok 
TEST
 -----
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ 

Para analizarlo mejor he repetido la ejecución pero desde el propio RARS. La salida es esta:

Z80 CamelForth v1.01  25 Jan 1995


0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 04
0x1001015e  Name: TEST
0x10010164  CFA:  0x00402070 
0x00402070 : 0xffc40413 
0x00402074 : 0x00142023 
0x00402078 : 0xfa5ff0ef 
0x0040207c : 0x00042083 
0x00402080 : 0x00440413 
0x00402084 : 0x00008067 
0x00402088 : 0xcafebaca 

 ok 
TEST
 -----
ok 
BYE
 
-- program is finished running (0) --

La rutina do_test está en la dirección 0x00402070. En esa dirección tenemos el siguiente código asm y máquina:

0xffc40413 addi s0,s0,-4
0x00142023 sw ra, 0(s0)
0xfa5ff0ef jal do_line
0x00042083 lw ra,0(s0)
0x00440413 addi s0,s0,4
0x00008067 ret

Si generásemos la rutina TEST nosotros, no podríamos meter exactamente ese código por el jal es relativo (y como la rutina generada estaría en la rama no habria sufientes bits para el desplazamiento relativo). Por tanto, de momento el jal hay que hacerlo absoluto. El siguiente paso sería modificar la rutina test, ó hacer otra que sea TEST2 pero que realice el salto absoluto

ok.. Ya tengo TEST2, que también he metido en el diccionario. La llamada a do_line se hace así:

    #-- Leer la direccion de la rutina line
    la t0, do_line

    #-- Saltar a esa rutina
    jalr ra,t0,0

Es el ejemplo 194-test2.s y funciona. Se imprime primero una linea y luego se ejecuta en modo interactivo. Funciona en ambos caso:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 194-test2.s 
Z80 CamelForth v1.01  25 Jan 1995

0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 05
0x1001015e  Name: TEST2
0x10010164  CFA:  0x00401e60 
0x00401e60 : 0xffc40413 
0x00401e64 : 0x00142023 
0x00401e68 : 0x00000297 
0x00401e6c : 0xf8c28293 
0x00401e70 : 0x000280e7 
0x00401e74 : 0x00042083 
0x00401e78 : 0x00440413 
-----
 ok
TEST2
 -----
ok 
TEST2
 -----
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ 

En el siguiente ejemplo vamos a llamar a do_line pero directamente usando su dirección: 0x40201C

El código es este:

    #-- Meter la direccion "cableada" de line en t0
    li t0, 0x40201C

    #-- Saltar a esa rutina
    jalr ra,t0,0

Y desde el RARS funciona bien:

Z80 CamelForth v1.01  25 Jan 1995


0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 05
0x1001015e  Name: TEST3
0x10010164  CFA:  0x004020a8 
0x004020a8 : 0xffc40413 
0x004020ac : 0x00142023 
0x004020b0 : 0x004022b7 
0x004020b4 : 0x01c28293 
0x004020b8 : 0x000280e7 
0x004020bc : 0x00042083 
0x004020c0 : 0x00440413 
-----
 ok
TEST3
 -----
ok 
BYE
 
-- program is finished running (0) --

Ahora pasamos a la siguiente versión: TEST4. La idea es ahora dividir la dirección en dos partes: Una alta de 20-bits y otra baja de 12-bits:

0x0040201c = 0x00402 y 0x01C

Cada trozo de dirección está en un registro diferente: t1 = 0x00402000 (parte alta). t0 = 0x01C (parte baja)

Esa dirección la calculamos con este código:

    lui t1, 0x00402    #-- t1= parte alta: 0x00402000
    li t0, 0x01C       #-- t0= parte baja: 0x0000001C

    #-- Creamos la direccion comple uniendo ambas con or
    or t0, t1, t0

    #-- Saltar a esa rutina
    jalr ra,t0,0

Comprobamos y funciona... (en el rars)

Z80 CamelForth v1.01  25 Jan 1995


0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 05
0x1001015e  Name: TEST4
0x10010164  CFA:  0x004020c8 
0x004020c8 : 0xffc40413 
0x004020cc : 0x00142023 
0x004020d0 : 0x00402337 
0x004020d4 : 0x01c00293 
0x004020d8 : 0x005362b3 
0x004020dc : 0x000280e7 
0x004020e0 : 0x00042083 
-----
 ok
TEST4
 -----
ok 
BYE
 
-- program is finished running (0) --

Este código tiene un problema. Al usar li para cargar los 12-bits de menor peso, se hace una extensión de signo (porque li es el realidad un andi). Con esta dirección no ocurre nada, pero si por ejemplo los 12-bits de menor peso tuviesen el bit más significativo a 1 (como por ejemplo con 0xF01), entonces lo trata como un número negativo y extiende el signo... el or entre t1 y t0 ya no funcionaría

Para evitar eso hay que cargar el valor en t0 usando dos instrucciones: lui y luego un shift a la derecha:

 lui t1, 0x00402    #-- t1= parte alta: 0x00402000
    lui t0, 0x01C      #-- t0= parte baja: 0x0000001C
    srli t0, t0, 12    #---    Desplazar 12 bits a la derecha

    #-- Creamos la direccion comple uniendo ambas con or
    or t0, t1, t0

Confirmamos que este código funciona:

Z80 CamelForth v1.01  25 Jan 1995


0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 05
0x1001015e  Name: TEST5
0x10010164  CFA:  0x004020ec 
0x004020ec : 0xffc40413 
0x004020f0 : 0x00142023 
0x004020f4 : 0x00402337 
0x004020f8 : 0x0001c2b7 
0x004020fc : 0x00c2d293 
0x00402100 : 0x005362b3 
0x00402104 : 0x000280e7 
-----
 ok
TEST5
 -----
ok 
BYE
 
-- program is finished running (0) --

Por tanto ESE ES EL CÓDIGO que hay que generar para realizar una llamada a subutina, y es por tanto el CODIGO que debe generar la rutina de compilación que añade una instrucción nueva de Forth

Es verdad que SON MUCHAS instrucciones para hacer un simple call.... Se podría optimizar, pero creo que la respuesta es que sería mejor implementar un intérprete de Forth de tipo DTC (como el de camelforth) en vez de un STC... ya que en el riscv generar las instrucciones de salto a suburtina (para direcciones absolutas) no es muy eficiente

Pero en cualquier caso, como lo estoy haciendo SDC, voy a seguir así aunque sea menos óptimo...

Por tanto, estas son las instrucciones (y su código máquina) que hay que añadir para realizar las llamadas a palabras forth:

(1) 0x00402337 lui t1, 0x00402
(2) 0x0001c2b7 lui t0, 0x01C
(3) 0x00c2d293 srli t0,t0, 12
(4) 0x005362b3 or t0,t1,t0
(5) 0x000280e7 jalr ra,0(s0)

De las 5, las dos primeras (1 y 2) hay que generarlas teniendo en cuenta la dirección de la subrutina a saltar (que supondremos que está en un registro). Las tres últimas (3, 4 y 5) son FIJAS. Por lo que se pueden copiar desde otra región de memoria igual que hemos hecho con docolon y exit

Por tanto, ahora el reto es generar la instrucción TEST6 automáticamente, creando el diccionario a pelo

Empezamos probando en la consola. La dirección de do_line es: 0x401dec. Lo voy a imprimir siempre al comienzo de los ejemplos por si cambiase...

VAAAAMOS!!! Ya tengo el ejemplo 198-test6.s que genera la palabra TEST6 y funciona!!!! De momento el ejemplo no está limpio, pero ha funcionado!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 198-test6.s 
Z80 CamelForth v1.01  25 Jan 1995
0x0040209c 

0x10010158  Link: 0x10010151 
0x1001015c  Inmd: 0 
0x1001015d  NLen: 05
0x1001015e  Name: TEST5
0x10010164  CFA:  0x0040216c 
0x0040216c : 0xffc40413 
0x00402170 : 0x00142023 
0x00402174 : 0x00402337 
0x00402178 : 0x0001c2b7 
0x0040217c : 0x00c2d293 
0x00402180 : 0x005362b3 
0x00402184 : 0x000280e7 

0x10010168  Link: 0x1001015d 
0x1001016c  Inmd: 0 
0x1001016d  NLen: 05
0x1001016e  Name: TEST6
0x10010174  CFA:  0x10010178 
0x10010178 : 0xffc40413 
0x1001017c : 0x00142023 
0x10010180 : 0x00402337 
0x10010184 : 0x0009c2b7 
0x10010188 : 0x00c2d293 
0x1001018c : 0x005362b3 
0x10010190 : 0x000280e7 
 ok
TEST6
 -----
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ 

Ahora ya sí que sí... con esto ya pasamos a otro nivel... voy a consolidarlo!

NOTA RECORDATORIO: Este link de sectorforth es MUY BUENO!! Mirar el código!!!

https://github.com/cesarblum/sectorforth

Ahora ya sé que la parte de las llamadas está ok... sin embargo todavía no he conseguido crear la primera palabra compilada automáticamente, desde la línea de comandos... Ni siquiera funciona la palabra nula: : NULL ;... se queda en un estado raro...

Así que toca depurar más profundamente... No tengo clara la estrategia... voy a probar primero a ver si se ejecuta correctamente el comando : (colon)... Primero lo depuro en compilación manual, y luego comparo con la automática...

Este es el resultado cuando llamo sólo a COLON, manualmente. Se crea una palabra nula, y en el diccionario vemos la entrada creada y el código máquina de DOCOLON

$ ./run.sh 200-dotdot.s 
Z80 CamelForth v1.01  25 Jan 1995

0x10010168  Link: 0x1001015d 
0x1001016c  Inmd: 0 
0x1001016d  NLen: 80
0x1001016e  Name: 
0x10010170  CFA:  0x10010174 
0x10010174 : 0xffc40413 
0x10010178 : 0x00142023 
0x1001017c : 0x00000000 
0x10010180 : 0x00000000 
0x10010184 : 0x00000000 
0x10010188 : 0x00000000 
 ok

Ahora voy a probar lo mismo pero ejecutando las instrucciones de depuración en el propio intérprete, a ver qué sale...

LISTO! Ya tengo el programa NULO funcionando! Había varios problemas, principalmente fallos míos de uso, porque tdoavía no lo entiendo del todo. El caso es que con el ejemplo 200-T.s hemos podido definir la palabra T en modo interactivo. Esta palabra No hace nada, sólo retorna. Este es el resultado:

$ ./run.sh 200-T.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: T ;
 ok 
WORDS
 T TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
T
 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$

El problema principal es que no tenía definida la palabra ; como INMEDIATA (el flag estaba a 0 en vez de 1). Lo bueno es que ya he comprendido lo que significa. Cuando NO es inmediato, si estamos en modo interpretación se ejecuta, pero si estamos en modo compilación se añade un salto a esa palabra en el diccionario. Peor, cuando es inmediato se ejecuta siempre, tanto en interpretación como en compilación. Y en compilación necesitamos que se ejecute el exit para que salir el modo compilación. Antes, al no ejecutarse, se metía su llamada pero nunca se salía del modo compilación, por lo que sucesivas ejecuciones de palabras no producian ningún efecto....

Otro problema que tenía es que estaba usando el nombre de palabra A, que YA ESTABA DEFINIDO EN EL DICCIONARIO, y por eso no se asignaba el nombre...

He estado haciendo más pruebas... y ¡ES LA LECHE! Ya puedo definir mis propias palabras!!!!!! Ahora es cuando es potente!!!!!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 200-T.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
1 2 3 4 5 
 ok 
.S
 1 2 3 4 5 ok 
: T .S ;
 ok 
T
 1 2 3 4 5 ok 
: T2 T ;
 ok 
T2
 1 2 3 4 5 ok 
: T3 T2 T2 T2 ;
 ok 
T3
 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ 

He definido palabras a varios niveles... unas llamando a otras... y funciona muy bien! Forth es la leche!

El siguiente problema a solucionar son los literales... que en compilación no funcionan, así, en modo interpretado 1 1 + . da como resultado 2. Sin embargo en modo compilado, basta con meter un único número para que pete:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ ./run.sh 200-T.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
1 1 + .
 2 ok 
: T 1 ;
 Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-09
$ 

2023-06-11

El objetivo de hoy es conseguir compilar palabras con literales. Como ejemplo, voy a trabajar en la palabra L1 que simplemente mete el número 1 en la pila:

: L1 1 ;

Con la versión actual ocurre esto:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 201-T.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: L1 1 ;
 Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$

Voy a comenzar añadiendo el literal '1' directamente en el diccionario (que en realidad es como si fuese una constante). Después tengo que hacer que se añadida desde un programa compilado automáticamente, y finalmente desde un programa compilado automáticamente de forma interactiva

He metido a pelo en el diccionario la palabra 1 como una constante. En esas circunstancias sí que funciona crear la palabra L1 a partir de la palabra 1... sin embargo, no funcionoa con 2 porque no está definida en el diccionario:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 201-T.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
: L1 1 ;
 ok 
L1 .
 1 ok 
: L2 2 ;
 Dobijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Bien, lo que hay que hacer es depurar el intérprete para que al detectar un número lo meta como si fuese una constante.. Pero primero voy a reproducir el ejemplo anterior creando el literal 2 desde un programa forth...

Ya está solucionado en el ejemplo 202-L2.s:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 202-L2.s 
Z80 CamelForth v1.01  25 Jan 1995

0x10010168  Link: 0x1001015d 
0x1001016c  Inmd: 0 
0x1001016d  NLen: 01
0x1001016e  Name: 1
0x10010170  CFA:  0x10010174 
0x10010174 : 0xffc40413 
0x10010178 : 0x00142023 
0x1001017c : 0x004002b7 
0x10010180 : 0x01c28293 
0x10010184 : 0x000280e7 
0x10010188 : 0x00000001 
0x1001018c : 0x00000000 
0x10010190 : 0x00000000 

0x1001018c  Link: 0x1001016d 
0x10010190  Inmd: 0 
0x10010191  NLen: 01
0x10010192  Name: 2
0x10010194  CFA:  0x10010198 
0x10010198 : 0xffc40413 
0x1001019c : 0x00142023 
0x100101a0 : 0x00400337 
0x100101a4 : 0x0001c2b7 
0x100101a8 : 0x00c2d293 
0x100101ac : 0x005362b3 
0x100101b0 : 0x000280e7 
0x100101b4 : 0x00000002 
0x100101c4  ok
: L2 2 ;
 ok 
L2 .
 2 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Bien, ya está todo listo... ahora hay que depurar el intérprete para añadir la literal...

LISTO!!!! Ya se meten las literales en modo compilación! No estaba implementada esta parte en la palabra LITERAL (estaba sólo la parte de interpretación). Ya lo he añadido... y con este ejemplo sencillo funciona!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 202-L3.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: L3 3 ;
 ok 
L3 .
 3 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Ahora hay que hacer pruebas más exhaustivas...

He encontrado un bug. En el modo compilacion no se pueden más de 1 literal. Funciona con 1, pero al meter 2 seguidas la segunda NO se inserta en la pila. En este ejemplo se puede ver el problema:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 204-Literal.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: T 2 3 4 ;
 ok 
T
 ok 
.S
 2 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Al ejecutar .S lo que debería haberse viesto es 2 3 4, sin embargo sólo está metido el 2... es decir, sólo se ha metido la primera literal, y luego es como si se hubiese ejecutado un exit para salir de la palabra...

Tengo que depurar el código generado por la palabra T... para ello voy a meter en el diccionario .LWINFO

Voy a desensamblar el código máquina generado y comprobar qué es lo que hace. Para desensamblar voy a utilizar esta herramienta web:

https://luplab.gitlab.io/rvcodecjs/

Los programas que voy a estudiar son los siguientes:

  • : P0 ;: Es el programa NULO
  • : P1 HI ;: Imprime un mensaje
  • : P2 HI HI ;: Imprime dos mensajes iguales
  • : P3 HI HI HI ;: Imprime tres mensajes iguales
  • : L1 2 ;: Mete una literal en la pila
  • : L2 2 2 ;: Mete dos literales en la pila
  • : L3 2 2 2 ;: Mete tres literales en la pila

Palabra P0

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 205-P0.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: P0 ;
 ok 
WORDS
 P0 .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
P0
 ok 
.WLINFO
 
0x100101a0  Link: 0x10010191 
0x100101a4  Inmd: 0 
0x100101a5  NLen: 02
0x100101a6  Name: P0
0x100101a8  CFA:  0x100101ac 
0x100101ac : 0xffc40413 
0x100101b0 : 0x00142023 
0x100101b4 : 0x00042083 
0x100101b8 : 0x00440413 
0x100101bc : 0x00008067 
0x100101c0 : 0x4c572e07 
0x100101c4 : 0x4f464e49 
0x100101c8 : 0x00000020 
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Vamos a analizar el código máquina de P0, desensamblando instrucción a instrucción

  • P0:
  • 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
  • 0x00142023 --> sw ra, 0(s0) (ra = x1)
  • 0x00042083 --> lw ra, 0(s0) #--- EXIT
  • 0x00440413 --> addi s0, s0, 4
  • 0x00008067 --> jalr zero, ra, 0

OK. Está formado por COLON y luego EXIT. Es decir, se mete la dirección de retorno en la pila. En exit se saca la dirección de retorno de la pila y se salta a ella

Palabra P1

Vamos a por P1. Funciona todo ok

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 206-P1.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
"HI
 HI!
ok 
: P1 "HI ;
 ok 
WORDS
 P1 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
P1
 HI!
ok 
.WLINFO
 
0x100101b0  Link: 0x100101a5 
0x100101b4  Inmd: 0 
0x100101b5  NLen: 02
0x100101b6  Name: P1
0x100101b8  CFA:  0x100101bc 
0x100101bc : 0xffc40413 
0x100101c0 : 0x00142023 
0x100101c4 : 0x00402337 
0x100101c8 : 0x001982b7 
0x100101cc : 0x00c2d293 
0x100101d0 : 0x005362b3 
0x100101d4 : 0x000280e7 
0x100101d8 : 0x00042083 
0x100101dc : 0x00440413 
0x100101e0 : 0x00008067 
0x100101e4 : 0x4c572e07 
0x100101e8 : 0x4f464e49 
0x100101ec : 0x00000020 
0x100101f0 : 0x00000000 
0x100101f4 : 0x00000000 
0x100101f8 : 0x00000000 
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 
  • P1:
  • 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
  • 0x00142023 --> sw ra, 0(s0) (ra = x1)
  • 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
  • 0x001982b7 --> lui t0, 0x198
  • 0x00c2d293 --> srli t0, t0, 12
  • 0x005362b3 --> or t0, t1, t0
  • 0x000280e7 --> jalr ra, t0, 0
  • 0x00042083 --> lw ra, 0(s0) #-- EXIT
  • 0x00440413 --> addi s0, s0, 4
  • 0x00008067 --> jalr zero, ra, 0

Palabra P2

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 207-P2.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: P2 "HI "HI ;
 ok 
P2
 HI!
HI!
ok 
.WLINFO
 
0x100101b0  Link: 0x100101a5 
0x100101b4  Inmd: 0 
0x100101b5  NLen: 02
0x100101b6  Name: P2
0x100101b8  CFA:  0x100101bc 
0x100101bc : 0xffc40413 
0x100101c0 : 0x00142023 
0x100101c4 : 0x00402337 
0x100101c8 : 0x001982b7 
0x100101cc : 0x00c2d293 
0x100101d0 : 0x005362b3 
0x100101d4 : 0x000280e7 
0x100101d8 : 0x00402337 
0x100101dc : 0x001982b7 
0x100101e0 : 0x00c2d293 
0x100101e4 : 0x005362b3 
0x100101e8 : 0x000280e7 
0x100101ec : 0x00042083 
0x100101f0 : 0x00440413 
0x100101f4 : 0x00008067 
0x100101f8 : 0x4c572e07 
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 
  • P2:
  • 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
  • 0x00142023 --> sw ra, 0(s0) (ra = x1)
  • 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
  • 0x001982b7 --> lui t0, 0x198
  • 0x00c2d293 --> srli t0, t0, 12
  • 0x005362b3 --> or t0, t1, t0
  • 0x000280e7 --> jalr ra, t0, 0
  • 0x00402337 --> lui t1, 0x402 #-- Execucion de HI
  • 0x001982b7 --> lui t0, 0x198
  • 0x00c2d293 --> srli t0, t0, 12
  • 0x005362b3 --> or t0, t1, t0
  • 0x000280e7 --> jalr ra, t0, 0
  • 0x00042083 --> lw ra, 0(s0) #-- EXIT
  • 0x00440413 --> addi s0, s0, 4
  • 0x00008067 --> jalr zero, ra, 0

P3 es más de lo mismo. El patrón queda claro... vamos ahora a ver qué pasa con los literales...

Palabra L1

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 208-test-literal.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: L1 2 ;
 ok 
L1
 ok 
.S
 2 ok 
.WLINFO
 
0x100101b0  Link: 0x100101a5 
0x100101b4  Inmd: 0 
0x100101b5  NLen: 02
0x100101b6  Name: L1
0x100101b8  CFA:  0x100101bc 
0x100101bc : 0xffc40413 
0x100101c0 : 0x00142023 
0x100101c4 : 0x00400337 
0x100101c8 : 0x0001c2b7 
0x100101cc : 0x00c2d293 
0x100101d0 : 0x005362b3 
0x100101d4 : 0x000280e7 
0x100101d8 : 0x00000002 
0x100101dc : 0x00042083 
0x100101e0 : 0x00440413 
0x100101e4 : 0x00008067 
0x100101e8 : 0x4c572e07 
0x100101ec : 0x4f464e49 
0x100101f0 : 0x00000020 
0x100101f4 : 0x00000000 
0x100101f8 : 0x00000000 
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 
  • L1
  • 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
  • 0x00142023 --> sw ra, 0(s0) (ra = x1)
  • 0x00402337 --> lui t1, 0x400 #-- Execucion de la literal
  • 0x0001c2b7 --> lui t0, 0x01c
  • 0x00c2d293 --> srli t0, t0, 12
  • 0x005362b3 --> or t0, t1, t0
  • 0x000280e7 --> jalr ra, t0, 0
  • 0x100101d8 --> literal: 0x00000002
  • 0x00042083 --> lw ra, 0(s0) #-- EXIT
  • 0x00440413 --> addi s0, s0, 4
  • 0x00008067 --> jalr zero, ra, 0

OK! Ahora ya puedo ver lo que ocurre. Se llama a docon2, pero ra se tiene que incrementar en 4 para saltar el literal. Así que he creado otra rutina: dolit que hace justo eso.... y ahora ya FUNCIONA!!!!

Esta es la prueba:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 208-test-literal.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 "HI .WLINFO 1 TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: L2 2 2 ;
 ok 
L2
 ok 
.S
 2 2 ok 
+ .
 4 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$

Aquí tengo una prueba más larga... joder... esto va tomando forma!!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 209-test.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: +1 1 + ;
 ok 
WORDS
 +1 "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
1 +1 .
 2 ok 
1 2 3 4 5 6 7 8
 ok 
.S
 1 2 3 4 5 6 7 8 ok 
+ .S
 1 2 3 4 5 6 15 ok 
+ .S
 1 2 3 4 5 21 ok 
+1
 ok 
.S
 1 2 3 4 5 22 ok 
+ + + + .S
 1 36 ok 
+ .
 37 ok 
.S
 ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

Palabra IF

Llega el momento de implementar el IF. Vamos a estudiar bien su funcionamiento e implementarlo de diferentes formas. Primero creamos un ejemplo mínimo para mostrar su funcionamiento, y lo probamos en gforth. Definimos ITRUE como una condición IF que se cumple siempre. Imprime el mensaje "HI". Definimos IFALSE como una condicón IF que NUNCA se cumple. No imprime ningún mensaje

: ITRUE -1 IF "HI THEN ;  ok
: IFALSE 0 IF "HI THEN ;  ok
ITRUE HI 
 ok
IFALSE  ok

Primero lo vamos a implementar directamente, para ver cómo debe ser el código máquina generado en ambos casos, y tener controladas las instrucciones privimitivas necesarias para su implementación. Después hay que ver cómo se hace para modificar la dirección de salto de la nueva palabra... vamos paso a paso...

El código para ejecutar IFALSE es el siguiente (compilado a mano):

    LIT(-1)

    #-- Si es falso no ejecutar (saltar)
    QBRANCH
    ADDR(end)

    QUOTEHI

end:

En el ejemplo completo se muestra la dirección donde comienza el programa main, y se hace un volcado del código máquina

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 210-itrue.s 
Z80 CamelForth v1.01  25 Jan 1995
0x004000c8 
HI!
0x004000c8 : 0x4e8000ef 
0x004000cc : 0xfffff037 
0x004000d0 : 0x638000ef 
0x004000d4 : 0x0080006f 
0x004000d8 : 0x16c020ef 
0x004000dc : 0x4d4000ef 
0x004000e0 : 0x00008037 
0x004000e4 : 0x7c1010ef 
 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$

El ensamblador de programa compilado a mano sería este:

jal do_lit
lui zero,%valor

jal do_qbranch
j %label

He hecho un ejemplo que crea el if a mano, y en el campo de salto se llama a do_hi (al final el IF no es más que un qbranch...). Sin embargo hay algún problema, porque peta. Esta es la salida:

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 211-if.s 
Z80 CamelForth v1.01  25 Jan 1995
0x00400760 
0x0040229c 
0x100101e0 

0x100101b4  Link: 0x100101a9 
0x100101b8  Inmd: 0 
0x100101b9  NLen: 05
0x100101ba  Name: ITRUE
0x100101c0  CFA:  0x100101c4 
0x100101c4 : 0xffc40413 
0x100101c8 : 0x00142023 
0x100101cc : 0x00400337 
0x100101d0 : 0x007602b7 
0x100101d4 : 0x00c2d293 
0x100101d8 : 0x005362b3 
0x100101dc : 0x000280e7 
0x100101e0 : 0x0040229c 
0x100101e4 : 0x00042083 
0x100101e8 : 0x00440413 
0x100101ec : 0x00008067 
0x100101f0 : 0x00000000 
0x100101f4 : 0x00000000 
0x100101f8 : 0x00000000 
0x100101fc : 0x00000000 
0x10010200 : 0x00000000 
 ok
WORDS
 ITRUE "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
0
 ok 
.S
 268501472 0 ok 
ITRUE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ 

El resultado esperado es que llamase a do_quotehi y que apareciese un "HI!" en la pantalla.... sin embargo peta... vamos a ver qué está pasando. Las dos primeras direcciones que aparecen son las siguientes:

  • do_qbranch: 0x00400760
  • do_quotehi: 0x0040229c

Vamos a desensamblar la palabra creada para saber lo que ocurre...

  • IF
  • 0xffc40413 --> addi s0, s0, -4 (s0 = 8) # COLON
  • 0x00142023 --> sw ra, 0(s0) (ra = x1)
  • 0x00400337 --> lui t1, 0x400 #-- Execucion de la literal
  • 0x007602b7 --> lui t0, 0x760
  • 0x00c2d293 --> srli t0, t0, 12
  • 0x005362b3 --> or t0, t1, t0
  • 0x000280e7 --> jalr ra, t0, 0
  • 0x0040229c --> literal: 0x0040229c
  • 0x00042083 --> lw ra, 0(s0) #-- EXIT
  • 0x00440413 --> addi s0, s0, 4
  • 0x00008067 --> jalr zero, ra, 0

ok, he encontrado el error. He hecho un do_qbranch2 nuevo. El anterior, do_qbranch, saltaba a la dirección indicada por ra, donde se supone que había un jump. Sin embargo, en qbranch2 se salta a la dirección que está almacenada en ra....

Ahora ya sale el HI! aunque se queda en bucle infinito (algún problema con la dirección de retorno...)

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-11
$ ./run.sh 211-if.s 
Z80 CamelForth v1.01  25 Jan 1995
0x00400048 
0x004022b8 
0x100101e0 

0x100101b4  Link: 0x100101a9 
0x100101b8  Inmd: 0 
0x100101b9  NLen: 05
0x100101ba  Name: ITRUE
0x100101c0  CFA:  0x100101c4 
0x100101c4 : 0xffc40413 
0x100101c8 : 0x00142023 
0x100101cc : 0x00400337 
0x100101d0 : 0x000482b7 
0x100101d4 : 0x00c2d293 
0x100101d8 : 0x005362b3 
0x100101dc : 0x000280e7 
0x100101e0 : 0x004022b8 
0x100101e4 : 0x00042083 
0x100101e8 : 0x00440413 
0x100101ec : 0x00008067 
0x100101f0 : 0x00000000 
0x100101f4 : 0x00000000 
0x100101f8 : 0x00000000 
0x100101fc : 0x00000000 
0x10010200 : 0x00000000 
 ok
0
 ok 
.S
 268501472 0 ok 
WORDS
 ITRUE "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
ITRUE
HI!
HI!
HI!
HI!
HI!
HI!
...

2023-06-12

IF

Seguimos depurando el IF... En realidad la cosa es un poco más complicada... para que funcione correctamente hay que integrar también el THEN, ya que, al fin y al cabo, el salto es a Then... PERO es un salto directo, es decir, que nunca retornará al IF inicial... Posiblemente haya que modificar la dirección de retorno de la pila para que vuelva NO al IF, sino al término del Then...

Ya funciona! Lo he probado con esta palabra FORTH compilada a mano:

: ITRUE IF "HI THEN ;

Es un poco diferente a la indicadas ayer. Esta cubre los dos casos. Según lo que haya en la pila, bien no imprime nada o bien imprime "HI!"

Este es el resultado al probarla en el ejemplo 212-itrue.s:

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 212-itrue.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100101b4  Link: 0x100101a9 
0x100101b8  Inmd: 0 
0x100101b9  NLen: 05
0x100101ba  Name: ITRUE
0x100101c0  CFA:  0x100101c4 
0x100101c4 : 0xffc40413 
0x100101c8 : 0x00142023 
0x100101cc : 0x00400337 
0x100101d0 : 0x000482b7 
0x100101d4 : 0x00c2d293 
0x100101d8 : 0x005362b3 
0x100101dc : 0x000280e7 
0x100101e0 : 0x100101f8 
0x100101e4 : 0x00402337 
0x100101e8 : 0x002902b7 
0x100101ec : 0x00c2d293 
0x100101f0 : 0x005362b3 
0x100101f4 : 0x000280e7 
0x100101f8 : 0x00042083 
0x100101fc : 0x00440413 
0x10010200 : 0x00008067 
0x10010204 : 0x00000000 
 ok
0 ITRUE
 ok 
-1 ITRUE
 HI!
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ 

Ahora tengo que hacer que funcione pero llamando a las palabras independientemente (porque ahora está todo en un único programa...)

ok, ya he refactorizado el código y tengo las subrutinas de IF y THEN por separado... Ahora es la prueba definitiva.... Qué pasa si lo hago en la línea de comandos? Vamos a probarlo... Primero hay que añadir IF y THEN en el diccionario (como inmediatos...)

FUNCIONA!!! VAAAAMOS!!!!! Ya puedo construir palabras con IF-THEN!!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 215-if-then.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: ?HI IF "HI THEN ;
 ok 
WORDS
 ?HI THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
0 ?HI
 ok 
1 ?HI
 HI!
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ 

Increible...

ELSE

Para probar el else voy a definir las palabras T (que imprime 'T') y F (que imprime 'F'), y ?BOOL que imprime T ó F en función del estado de la pila

obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
  ok
: T 84 emit ;  ok
  ok
T T ok
: F 70 emit ;  ok
  ok
F F ok
  ok
: ?BOOL IF T ELSE F THEN ;  ok
  ok
0 ?BOOL F ok
1 ?BOOL T ok
BYE 
obijuan@Hoth:~
$ 

LISTO!!!! ya lo tengo implementado!! Vaaamos!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 216-else.s 
Z80 CamelForth v1.01  25 Jan 1995

0x100101d0  Link: 0x100101c5 
0x100101d4  Inmd: 0 
0x100101d5  NLen: 05
0x100101d6  Name: ?BOOL
0x100101dc  CFA:  0x100101e0 
0x100101e0 : 0xffc40413 
0x100101e4 : 0x00142023 
0x100101e8 : 0x00400337 
0x100101ec : 0x000482b7 
0x100101f0 : 0x00c2d293 
0x100101f4 : 0x005362b3 
0x100101f8 : 0x000280e7 
0x100101fc : 0x1001022c 
0x10010200 : 0x00402337 
0x10010204 : 0x003582b7 
0x10010208 : 0x00c2d293 
0x1001020c : 0x005362b3 
0x10010210 : 0x000280e7 
0x10010214 : 0x00400337 
0x10010218 : 0x000642b7 
0x1001021c : 0x00c2d293 
0x10010220 : 0x005362b3 
0x10010224 : 0x000280e7 
0x10010228 : 0x10010240 
0x1001022c : 0x00402337 
0x10010230 : 0x0037c2b7 
0x10010234 : 0x00c2d293 
0x10010238 : 0x005362b3 
0x1001023c : 0x000280e7 
0x10010240 : 0x00042083 
0x10010244 : 0x00440413 
0x10010248 : 0x00008067 
0x1001024c : 0x00000000 
 ok
WORDS
 ?BOOL THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
0 ?BOOL
 F ok 
1 ?BOOL
 T ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ 

Ahora hay que meterlo en el diccionario y probarlo en el modo interactivo

LISTO!!! Ya lo tenemos!!!! FUNCIONA!!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 217-qbool.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
SPACE
  ok 
: T 84 EMIT SPACE ;
 ok 
T
 T ok 
: F 70 EMIT SPACE ;
 ok 
F
 F ok 
: ?BOOL IF T ELSE F THEN ;
 ok 
0 ?BOOL
 F ok 
1 ?BOOL
 T ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ 

DO...LOOP

Llega el momento de implementar el DO-LOOP... La prueba la vamos a hacer con la palabra -- que dibuja una linea de 10 caracteres. Primero lo probamos con el gforth:

obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: -- CR 10 0 DO 45 EMIT LOOP ;  ok
-- 
---------- ok
-- -- -- 
----------
----------
---------- ok
bye 
obijuan@Hoth:~
$ 

Y ahora seguimos el mismo proceso de siempre: Primero compilación manual. Luego al diccionario y por último compilación automática

Este me ha costado más, pero ya tengo una primra versión compilada manualmente que implementa el programa anterior:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ ./run.sh 220-do-loop.s 
Z80 CamelForth v1.01  25 Jan 1995
0 
0 

0x10010200  Link: 0x100101f5 
0x10010204  Inmd: 0 
0x10010205  NLen: 02
0x10010206  Name: --
0x10010208  CFA:  0x1001020c 
0x1001020c : 0xffc40413 
0x10010210 : 0x00142023 
0x10010214 : 0x00401337 
0x10010218 : 0x002642b7 
0x1001021c : 0x00c2d293 
0x10010220 : 0x005362b3 
0x10010224 : 0x000280e7 
0x10010228 : 0x00400337 
0x1001022c : 0x000342b7 
0x10010230 : 0x00c2d293 
0x10010234 : 0x005362b3 
0x10010238 : 0x000280e7 
0x1001023c : 0x00000005 
0x10010240 : 0x00400337 
0x10010244 : 0x000342b7 
0x10010248 : 0x00c2d293 
0x1001024c : 0x005362b3 
0x10010250 : 0x000280e7 
0x10010254 : 0x00000000 
0x10010258 : 0x00400337 
0x1001025c : 0x008082b7 
0x10010260 : 0x00c2d293 
0x10010264 : 0x005362b3 
0x10010268 : 0x000280e7 
0x1001026c : 0x00400337 
0x10010270 : 0x000342b7 
0x10010274 : 0x00c2d293 
0x10010278 : 0x005362b3 
0x1001027c : 0x000280e7 
0x10010280 : 0x0000002d 
0x10010284 : 0x00400337 
0x10010288 : 0x006b02b7 
0x1001028c : 0x00c2d293 
0x10010290 : 0x005362b3 
0x10010294 : 0x000280e7 
0x10010298 : 0x00400337 
0x1001029c : 0x0006c2b7 
0x100102a0 : 0x00c2d293 
0x100102a4 : 0x005362b3 
0x100102a8 : 0x000280e7 
0x100102ac : 0x1001026c 
0x100102b0 : 0x00042083 
0x100102b4 : 0x00440413 
0x100102b8 : 0x00008067 
0x100102bc : 0x00000000 
0x100102c0 : 0x00000000 
0x100102c4 : 0x00000000 
0x100102c8 : 0x00000000 
0x100102cc : 0x00000000 
0x100102d0 : 0x00000000 
0x100102d4 : 0x00000000 
0x100102d8 : 0x00000000 
0x100102dc : 0x00000000 
0x100102e0 : 0x00000000 
0x100102e4 : 0x00000000 
0x100102e8 : 0x00000000 
0x100102ec : 0x00000000 
0x100102f0 : 0x00000000 
 ok
WORDS
 -- SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
--
 
-----ok 
-- -- -- --
 
-----
-----
-----
-----ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-12
$ 

2023-06-13

DO...LOOP

Ya tengo factorizado el ejemplo 220-do-loop.s. Ya están implementadas las palabras DO y LOOP... Ahora toca el segundo paso: añadirlas al diccionario

Listo!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 221-do-loop.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: -- CR 10 0 DO 45 EMIT LOOP ;
 ok 
-- -- CR
 
----------
----------
ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ 

DOT-QUOTE (.")

El siguiente operando importante es ." para imprimir cadenas de caracteres en la consola... vamos a ver cómo lo implementamos...

Antes de implementar ." hay que implementar el operador estándar S": https://forth-standard.org/standard/core/Sq

Su misión es Añadir la cadena de caracteres al diccionario. En nuestro caso lo que se almacena es una llamada a XSQuote y a continuación los bytes de la cadena. Se trata de una cadena contadora. En tiempo de ejecución, XSQuote devuelve la dirección del primer carácter y el número total de caracteres de la cadena. Los pone en la pila

El S" ya lo tengo listo....

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 222-squote.s 
Z80 CamelForth v1.01  25 Jan 1995
0x10010263 

0x10010238  Link: 0x1001022d 
0x1001023c  Inmd: 0 
0x1001023d  NLen: 02
0x1001023e  Name: S"
0x10010240  CFA:  0x10010244 
0x10010244 : 0xffc40413 
0x10010248 : 0x00142023 
0x1001024c : 0x00400337 
0x10010250 : 0x000a02b7 
0x10010254 : 0x00c2d293 
0x10010258 : 0x005362b3 
0x1001025c : 0x000280e7 
0x10010260 : 0x20323102 
0x10010264 : 0x00042083 
0x10010268 : 0x00440413 
0x1001026c : 0x00008067 
0x10010270 : 0x00000000 
0x10010274 : 0x00000000 
0x10010278 : 0x00000000 
0x1001027c : 0x00000000 
0x10010280 : 0x00000000 
 ok
.S
 ok 
S"
 ok 
.S
 268501601 2 ok 
TYPE
 12ok 
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ 

En este ejemplo se compila la palabra S" (se le da el valor 12 desde el código). Vemos que en la pila no hay nada. Ahora ejecutamos S". En la pila se deposita la dirección de la cadena, y su longitud. Al ejecutar el comando TYPE (que está metido en el diccionario) se imprime la cadena "12"....

¡FUNCIONA!!!!!!

El siguiente paso es meterlo en el diccionario y usarlo en la construcción de otra palabra. Esto es lo que hacemos en este ejemplo:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 224-squote.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
.S
 ok 
: T S" 12" ;
 ok 
.S
 ok 
T
 ok 
.S
 268501613 2 ok 
TYPE
 12ok 
BYE

Se define la palabra de prueba T que no es más que una cadena de texto: "12". Al llamar a T se mete en la pila la dirección la cadena y su longitud. Si a continuación llamamos a TYPE entonces imprimimos la cadena...

Ahora ya podemos crear .". Como siempre. Primero compilado a mano, luego en el diccionario y finalmente en modo interactivo

LISTO! Ya está la palabra completa. OJO! Esta palabra, tal cual está definida en el CamelForth, NO ES INTERACTIVA. Es verdad que no tiene mucho sentido usarla en modo interactivo. Está pensada para usarse dentro de otras palabras.

En este ejemplo Definimos esta palabra de prueba T: : T ." HOLI!" ;

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 225-dotquote.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 ." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT ok 
: T ." HOLI!" ;
 ok 
T
 HOLI!ok 
BYE

Aquí definimos otra palabra T, que indica si el valor de la pila es VERDADERO o FALSE:

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 225-dotquote.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
1 1 + .
 2  ok
: T IF ." TRUE" ELSE ." FALSE" THEN CR ;
  ok
0 T
 FALSE
 ok
1 T
 TRUE
 ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ 

ANSI

Lo que queda ahora es ir sistemáticamente implementando TODAS las palabras que quedan... junto con sus programas de prueba...

Pero antes voy a aprovechar para hacerme algunos programas mios de pruebas, usando secuencias ANSI...

Las puedo practicas primero con gforth, y luego las pruebo en el rv-forth

Me metido las siguientes palabras: EESC (Emit ESC), HOME y CLS

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 226-esc.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS
 HOME CLS EESC ." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT  ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$

Si queremos definir el color verde, una forma sería así:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 226-esc.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
: GREEN EESC ." [32m" ;
  ok
GREEN
  ok
Esto sale en verde
 Esto?
sale?
en?
verde?
 ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$

COMENTARIOS

Para empezar a tener programas guardados en ficheros de texto y hacer "copy & paste" al intérprete, necesito primero implementar los comentarios

¡¡Ya tengo comentarios!!!!!!! Funcionan tanto en la compilacion como en el modo interpretado...

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ ./run.sh 227-paren.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
( Esto son comentarios )
  ok
( no hace falta poner espacio antes del ultimo)
  ok
: T ( palabra de prueba: -- ) ." TEST!" ;
  ok
T
 TEST! ok
T T
 TEST!TEST! ok
( vaaaaaamos!!!!)
  ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-13
$ 

2023-06-14

Ya sólo me quedan 19 palabras para completar el Camelforth! Esto ya empieza a tomar forma!!

RECURSE

La palabra RECURSE se utiliza para hacer recursividad en Forth. Lo que hace es añadir una llamada a la propia funcion que se está definiendo. Así por ejemplo si estamos definiendo la palabra T de esta forma: : T RECURSE ;, traducido a ensamblador lo que se genera es:

T: jal T

Es decir, una llamada a la propia función...

Este programa mete en la pila una cuenta recursiva 5 4 3 2 1 0...

: T DUP 0 > IF DUP 1- RECURSE THEN ;
5 T .S

El resultado devuelto es: <6> 5 4 3 2 1 0 ok

Este será el programa de pruebas para comprobar que RECURSE funciona

Primero comprobamos que recurse añadie la llamada a la propia función:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 255-recurse.s 
Z80 CamelForth v1.01  25 Jan 1995

0x1001028c  Link: 0x10010285 
0x10010290  Inmd: 0 
0x10010291  NLen: 01
0x10010292  Name: T
0x10010294  CFA:  0x10010298 
0x10010298 : 0xffc40413 
0x1001029c : 0x00142023 
0x100102a0 : 0x10010337 
0x100102a4 : 0x002982b7 
0x100102a8 : 0x00c2d293 
0x100102ac : 0x005362b3 
0x100102b0 : 0x000280e7 
0x100102b4 : 0x00042083 
0x100102b8 : 0x00440413 
0x100102bc : 0x00008067 
0x100102c0 : 0x00000000 
 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$

Ahora implementamos el programa de prueba, compilado a mano...

Listo.. con compilación manual ya está. Este es el resultado:

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 256-recurse.s 
Z80 CamelForth v1.01  25 Jan 1995
4 3 2 1 0 
 ok
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$

Ahora lo metemos en el diccionario y lo probamos en el modo interpretado

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 257-recurse.s 
Z80 CamelForth v1.01  25 Jan 1995
 ok
WORDS 
 RECURSE 1- > DUP ( HOME CLS EESC ." S" TYPE CR LOOP DO SPACE EMIT ELSE THEN IF "HI .WLINFO ONE TEST5 ; : ESC A WORDS NOP .S . + BYE lit EXIT  ok

  ok
: T DUP 0 > IF DUP 1- RECURSE THEN ;
  ok
5 T
  ok
.S
 5 4 3 2 1 0  ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ 

VAAAAAAAAMOS!!! FUNCIONA!!!!! UUUEEEEEEEEEE

POSTPONE

Este comando no lo llego a entender bien... me falta práctica... así que de momento lo dejo, será de los pocos que no implemente...

BEGIN

Empezamos por el Begin ... UNTIL... que es un bucle que se repite hasta que se cumpla una condición de terminación. En este ejemplo se tiene un contador que empieza en 5, y se va decrementando hasta llegar a 0, en ese momento se sale del bucle

: T BEGIN DUP . 1- DUP 0 = UNTIL ; 
5 T 5 4 3 2 1  ok

Ya funciona...

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 259-begin.s 
Z80 CamelForth v1.01  25 Jan 1995
0x100102dc 
0x100102c8  Link: 0x100102b9 
0x100102cc  Inmd: 0 
0x100102cd  NLen: 01
0x100102ce  Name: T
0x100102d0  CFA:  0x100102d4 
0x100102d4 : 0xffc40413 
0x100102d8 : 0x00142023 
0x100102dc : 0x00400337 
0x100102e0 : 0x004b02b7 
0x100102e4 : 0x00c2d293 
0x100102e8 : 0x005362b3 
0x100102ec : 0x000280e7 
0x100102f0 : 0x00401337 
0x100102f4 : 0x005b02b7 
0x100102f8 : 0x00c2d293 
0x100102fc : 0x005362b3 
0x10010300 : 0x000280e7 
0x10010304 : 0x00400337 
0x10010308 : 0x003542b7 
0x1001030c : 0x00c2d293 
0x10010310 : 0x005362b3 
0x10010314 : 0x000280e7 
0x10010318 : 0x00400337 
0x1001031c : 0x004b02b7 
0x10010320 : 0x00c2d293 
0x10010324 : 0x005362b3 
0x10010328 : 0x000280e7 
0x1001032c : 0x00400337 
0x10010330 : 0x000342b7 
0x10010334 : 0x00c2d293 
0x10010338 : 0x005362b3 
0x1001033c : 0x000280e7 
0x10010340 : 0x00000000 
0x10010344 : 0x00400337 
0x10010348 : 0x004342b7 
0x1001034c : 0x00c2d293 
0x10010350 : 0x005362b3 
0x10010354 : 0x000280e7 
0x10010358 : 0x00400337 
0x1001035c : 0x000482b7 
0x10010360 : 0x00c2d293 
0x10010364 : 0x005362b3 
0x10010368 : 0x000280e7 
0x1001036c : 0x100102dc 
0x10010370 : 0x00042083 
0x10010374 : 0x00440413 
0x10010378 : 0x00008067 
0x1001037c : 0x00000000 
 ok
5 T
 5 4 3 2 1  ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ 

Podemos ver cómo por haber hecho una implementación STC, el codigo es largo... la próxima implementación será DTC... el código máquina del forth será mucho más corto...

BEGIN...AGAIN

Esta es la estructura para hacer bucles infinitos. Est es un ejemplo de uno que imprime constantemente la letra 'A':

: INF BEGIN 65 EMIT AGAIN ;
obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 260-again.s 
Z80 CamelForth v1.01  25 Jan 1995
0x100102dc 
0x100102c8  Link: 0x100102b9 
0x100102cc  Inmd: 0 
0x100102cd  NLen: 01
0x100102ce  Name: T
0x100102d0  CFA:  0x100102d4 
0x100102d4 : 0xffc40413 
0x100102d8 : 0x00142023 
0x100102dc : 0x00400337 
0x100102e0 : 0x000342b7 
0x100102e4 : 0x00c2d293 
0x100102e8 : 0x005362b3 
0x100102ec : 0x000280e7 
0x100102f0 : 0x00000041 
0x100102f4 : 0x00400337 
0x100102f8 : 0x006842b7 
0x100102fc : 0x00c2d293 
0x10010300 : 0x005362b3 
0x10010304 : 0x000280e7 
0x10010308 : 0x00400337 
0x1001030c : 0x000642b7 
0x10010310 : 0x00c2d293 
0x10010314 : 0x005362b3 
0x10010318 : 0x000280e7 
0x1001031c : 0x100102dc 
0x10010320 : 0x00042083 
0x10010324 : 0x00440413 
0x10010328 : 0x00008067 
0x1001032c : 0x00000000 
 ok
T
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
...

BEGIN ... WHILE ... REPEAT

Este es nuestro programa de pruebas:

: T 1 BEGIN 65 EMIT DUP 5 < WHILE 66 EMIT 1+ REPEAT ;   ok
  ok
T ABABABABA ok

Y aquí está en funcionamiento, con compilado manual

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 261-while.s 
Z80 CamelForth v1.01  25 Jan 1995
0x100102dc 
0x100102c8  Link: 0x100102b9 
0x100102cc  Inmd: 0 
0x100102cd  NLen: 01
0x100102ce  Name: T
0x100102d0  CFA:  0x100102d4 
0x100102d4 : 0xffc40413 
0x100102d8 : 0x00142023 
0x100102dc : 0x00400337 
0x100102e0 : 0x000342b7 
0x100102e4 : 0x00c2d293 
0x100102e8 : 0x005362b3 
0x100102ec : 0x000280e7 
0x100102f0 : 0x00000041 
0x100102f4 : 0x00400337 
0x100102f8 : 0x006ec2b7 
0x100102fc : 0x00c2d293 
0x10010300 : 0x005362b3 
0x10010304 : 0x000280e7 
0x10010308 : 0x00400337 
0x1001030c : 0x004dc2b7 
0x10010310 : 0x00c2d293 
0x10010314 : 0x005362b3 
0x10010318 : 0x000280e7 
0x1001031c : 0x00400337 
0x10010320 : 0x000342b7 
0x10010324 : 0x00c2d293 
0x10010328 : 0x005362b3 
0x1001032c : 0x000280e7 
0x10010330 : 0x00000005 
0x10010334 : 0x00400337 
0x10010338 : 0x0048c2b7 
0x1001033c : 0x00c2d293 
0x10010340 : 0x005362b3 
0x10010344 : 0x000280e7 
0x10010348 : 0x00400337 
0x1001034c : 0x000482b7 
0x10010350 : 0x00c2d293 
0x10010354 : 0x005362b3 
0x10010358 : 0x000280e7 
0x1001035c : 0x100103b8 
0x10010360 : 0x00400337 
0x10010364 : 0x000342b7 
0x10010368 : 0x00c2d293 
0x1001036c : 0x005362b3 
0x10010370 : 0x000280e7 
0x10010374 : 0x00000042 
0x10010378 : 0x00400337 
0x1001037c : 0x006ec2b7 
0x10010380 : 0x00c2d293 
0x10010384 : 0x005362b3 
0x10010388 : 0x000280e7 
0x1001038c : 0x00400337 
0x10010390 : 0x003682b7 
0x10010394 : 0x00c2d293 
0x10010398 : 0x005362b3 
0x1001039c : 0x000280e7 
0x100103a0 : 0x00400337 
0x100103a4 : 0x000642b7 
0x100103a8 : 0x00c2d293 
0x100103ac : 0x005362b3 
0x100103b0 : 0x000280e7 
0x100103b4 : 0x100102dc 
0x100103b8 : 0x00042083 
0x100103bc : 0x00440413 
0x100103c0 : 0x00008067 
0x100103c4 : 0x00000000 
 ok
T
 ABABABABABA ok

  ok

LEAVE

Este es el ejemplo para LEAVE. Es un bucle que se tiene que repetir 100 veces (imprimir 100 As), pero cuando la variable llega a 5, se llama a LEAVE para salir... por lo que sólo se imprimen las primeras As (y no las 100)

: T 100 0 DO 65 EMIT I 5 = IF LEAVE THEN LOOP ; redefined T   ok
  ok
T AAAAAA ok

Ya está funcionando...

 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 262-leave.s 
Z80 CamelForth v1.01  25 Jan 1995
0x100103f4 
0x100102c8  Link: 0x100102b9 
0x100102cc  Inmd: 0 
0x100102cd  NLen: 01
0x100102ce  Name: T
0x100102d0  CFA:  0x100102d4 
0x100102d4 : 0xffc40413 
0x100102d8 : 0x00142023 
0x100102dc : 0x00400337 
0x100102e0 : 0x000342b7 
0x100102e4 : 0x00c2d293 
0x100102e8 : 0x005362b3 
0x100102ec : 0x000280e7 
0x100102f0 : 0x00000064 
0x100102f4 : 0x00400337 
0x100102f8 : 0x000342b7 
0x100102fc : 0x00c2d293 
0x10010300 : 0x005362b3 
0x10010304 : 0x000280e7 
0x10010308 : 0x00000000 
0x1001030c : 0x00400337 
0x10010310 : 0x0082c2b7 
0x10010314 : 0x00c2d293 
0x10010318 : 0x005362b3 
0x1001031c : 0x000280e7 
0x10010320 : 0x00400337 
0x10010324 : 0x000342b7 
0x10010328 : 0x00c2d293 
0x1001032c : 0x005362b3 
0x10010330 : 0x000280e7 
0x10010334 : 0x00000041 
0x10010338 : 0x00400337 
0x1001033c : 0x006d42b7 
0x10010340 : 0x00c2d293 
0x10010344 : 0x005362b3 
0x10010348 : 0x000280e7 
0x1001034c : 0x00400337 
0x10010350 : 0x008cc2b7 
0x10010354 : 0x00c2d293 
0x10010358 : 0x005362b3 
0x1001035c : 0x000280e7 
0x10010360 : 0x00400337 
0x10010364 : 0x000342b7 
0x10010368 : 0x00c2d293 
0x1001036c : 0x005362b3 
0x10010370 : 0x000280e7 
0x10010374 : 0x00000005 
0x10010378 : 0x00400337 
0x1001037c : 0x004482b7 
0x10010380 : 0x00c2d293 
0x10010384 : 0x005362b3 
0x10010388 : 0x000280e7 
0x1001038c : 0x00400337 
0x10010390 : 0x000482b7 
0x10010394 : 0x00c2d293 
0x10010398 : 0x005362b3 
0x1001039c : 0x000280e7 
0x100103a0 : 0x100103d0 
0x100103a4 : 0x00400337 
0x100103a8 : 0x008ec2b7 
0x100103ac : 0x00c2d293 
0x100103b0 : 0x005362b3 
0x100103b4 : 0x000280e7 
0x100103b8 : 0x00400337 
0x100103bc : 0x000642b7 
0x100103c0 : 0x00c2d293 
0x100103c4 : 0x005362b3 
0x100103c8 : 0x000280e7 
0x100103cc : 0x100103e8 
0x100103d0 : 0x00400337 
0x100103d4 : 0x0006c2b7 
0x100103d8 : 0x00c2d293 
0x100103dc : 0x005362b3 
0x100103e0 : 0x000280e7 
0x100103e4 : 0x10010320 
0x100103e8 : 0x00042083 
0x100103ec : 0x00440413 
0x100103f0 : 0x00008067 
0x100103f4 : 0x00000000 
 ok
T
 AAAAAA ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ 

+LOOP

Esta es la última instrucción!! (bueno, me queda POSTPONE, pero de momento lo dejo....)

: T 10 0 DO I . 2 +LOOP ;  ok
  ok
T 0 2 4 6 8  ok

LISO!! Ya está implementado!!!

obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ ./run.sh 263-plusloop.s 
Z80 CamelForth v1.01  25 Jan 1995
0x10010384 
0x100102c8  Link: 0x100102b9 
0x100102cc  Inmd: 0 
0x100102cd  NLen: 01
0x100102ce  Name: T
0x100102d0  CFA:  0x100102d4 
0x100102d4 : 0xffc40413 
0x100102d8 : 0x00142023 
0x100102dc : 0x00400337 
0x100102e0 : 0x000342b7 
0x100102e4 : 0x00c2d293 
0x100102e8 : 0x005362b3 
0x100102ec : 0x000280e7 
0x100102f0 : 0x0000000a 
0x100102f4 : 0x00400337 
0x100102f8 : 0x000342b7 
0x100102fc : 0x00c2d293 
0x10010300 : 0x005362b3 
0x10010304 : 0x000280e7 
0x10010308 : 0x00000000 
0x1001030c : 0x00400337 
0x10010310 : 0x008442b7 
0x10010314 : 0x00c2d293 
0x10010318 : 0x005362b3 
0x1001031c : 0x000280e7 
0x10010320 : 0x00400337 
0x10010324 : 0x008e42b7 
0x10010328 : 0x00c2d293 
0x1001032c : 0x005362b3 
0x10010330 : 0x000280e7 
0x10010334 : 0x00401337 
0x10010338 : 0x005dc2b7 
0x1001033c : 0x00c2d293 
0x10010340 : 0x005362b3 
0x10010344 : 0x000280e7 
0x10010348 : 0x00400337 
0x1001034c : 0x000342b7 
0x10010350 : 0x00c2d293 
0x10010354 : 0x005362b3 
0x10010358 : 0x000280e7 
0x1001035c : 0x00000002 
0x10010360 : 0x00400337 
0x10010364 : 0x000a02b7 
0x10010368 : 0x00c2d293 
0x1001036c : 0x005362b3 
0x10010370 : 0x000280e7 
0x10010374 : 0x10010320 
0x10010378 : 0x00042083 
0x1001037c : 0x00440413 
0x10010380 : 0x00008067 
0x10010384 : 0x00000000 
 ok
T
 0 2 4 6 8  ok
BYE
 obijuan@Hoth:~/Develop/Learn-forth/Log/2023-06-14
$ 

Diccionario

Ahora hay que meter todas las palabras en el diccionario... sin embargo lo voy a ir haciendo a la vez que aprendo más forth... voy a re-leer el libro de forth haciendo los ejemplos en el rv-forth...

https://www.forth.com/starting-forth/

2023-06-15

Es el momento de llevar lo que tengo hasta ahora a un repositorio nuevo, y empezar a organizarlo. El proyecto lo voy a llamar RiscvForth (RvForth para abreviar). Lo voy a separar en dos ficheros: primitivas y secundarios. El diccionario está en otro fichero separado. También necesito uno de inicialización (init?) que contenga la estructura inicial de todo en memoria

Esta primera versión es para el RARS-1.6, y es de tipo STC (Subrutine Threaded code)

Impresión de caracteres unicode

Para imprimir caracteres unicode desde forth. Primero nos vamos a la tabla de caracteres unicode. Hay muchas:

Vamos a fijarnos por ejemplo en la letra ñ. Su código unicode es U+00F1. Para decodificarlo podemos usar este programa en python:

"ñ".encode()

Que nos devuelve la cadena en bytes: b'\xc3\xb1'. Por tanto, la ñ se codifica como 0xC3B1

Ahora imprimimos esos dos caracteres en forth:

$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
hex  ok
c3 emit b1 emit ñ ok
bye

y tachan!!! Sale la ñ

Definimos la palabra UHLINE. Es el caracter unicode :

: UHLINE 226 EMIT 148 EMIT 128 EMIT ;

Si lo probamos en gforth, efectivamente sale el caracter de linea:

226 emit 148 emit 128 emit ─ ok

Sin embargo, al probarlo en el riscvForth salen caracteres raros. Tanto para la línea como para la ñ. Vamos a probarlo con la ñ por facilidad: (Metemos los números de la ñ en decimal, 0xc3=195, 0xb1=177

195 EMIT 177 EMIT
 ñ ok

Hay que solucionar este bug...

2023-06-16

Caracteres Unicode

Para solucionar el bug he probado este programa directamente en el RARs, para imprimir una cadena con la letra ñ:

	.data
msg:	.string "ñ"
msg2:    .byte 0xc3, 0xb1, 0  #-- ñ en bytes

	.text
	
	#-- Imprimir ñ como cadena
	la a0, msg
	li a7, 4
	ecall
	
	#-- Imprimir ñ como cadena, pero con bytes separados
	la a0, msg2
	li a7, 4
	ecall
	
	#-- Imprimir la ñ imprimiendo sus dos caracteres aislados
	li a0, 0xc3 
	li a7, 11
	ecall
	
	li a0, 0xb1
	li a7, 11
	ecall
	
	
	li a7, 10
	ecall

Y este es el resultado:

ñññ
-- program is finished running (0) --

Conclusión: para impriir caracteres unicode hay que hacerlo llamando al servicio de impresión de cadenas, y NO carácter a carácter

Así que voy a definir la palabra XEMIT para emitir todos los bytes del carácter unicode. En el forth estándar, se le pasa como parámetro el código unicode... sin embargo, en mi implementación voy a meter directamente los códigos ya traducidos. Para la ñ el número a enviar será el 0xc3b1 (enviando primero el c3 y luego el b1)

Para unificar voy a hacer pruebas también con el carácter de línea horizontal:

	.data
msg:	.string "─"
msg2:    .byte 0xe2, 0x94, 0x80, 0 

	.text
	
	#-- Imprimi como cadena
	la a0, msg
	li a7, 4
	ecall
	
	#-- Imprimir como cadena, pero con bytes separados
	la a0, msg2
	li a7, 4
	ecall
	
	
	li a7, 10
	ecall

Este es el resultado:

──
-- program is finished running (0) --

Los bytes los voy a imprimir empezando por el menos significativo. Así, para imprimir los siguienets caracteres hay que enviar los códigos indicados:

  • Letra ñ: 0x00b1c3
  • Caracter ─: 0x008094e2

Sin embargo, como con el RARs tengo el problema de no poder meter números junto con el código, la llamada a xemit la vamos a hacer metiendo el número como bytes. Dado que el unicode es de longitud variable, siepre el primer numero será un 0 (que indica el final), así, para impmrimir los caracteres anteriores las llamadas a xemit serán:

  • Letra ñ: 0 b1 c3 xemit
  • Caracter ─: 00 80 94 e2 xemit

¡Vamos a implementarlo!

¡Listo!

obijuan@Hoth:~/Develop/RiscvForth
$ ./run.sh prueba.s 
RiscvForth v0.1  15 Jun 2023
ñ─
 ok
obijuan@Hoth:~/Develop/RiscvForth
$ 

Esta es la nueva palabra UHLINE que imprime el caracter de línea horizontal:

: UHLINE 0 128 148 226 XEMIT ;

Lo probamos:

obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
: UHLINE 0 128 148 226 XEMIT ;
  ok
UHLINE
 ─ ok
BYE
 obijuan@Hoth:~/Develop/RiscvForth
$ 

¡FUNCIONA! uueeeeeeeee

La putada es que el XEMIT del riscvForth es diferente del estándar... y por tanto la palabra UHLINE hay que definirla de manera diferente en riscvForth y en gforth...

Estoy pensando que tal vez pueda modificar el comportamiento de emit de riscv para pueda imprimir tanto caracteres normales como unicode. Si en el gforth ejecuto este comando, se imprime la línea horizontal:

226 emit 148 emit 128 emit 0 emit ─ ok

(Nótese que he añadido el 0 al final)

Bien, puedo incluir una máquina de estados en el emit del riscvForth tal que permita enviar los dos tipos de caracteres: ASCII y unicode. El comportamiento sería algo así:

  • Inicialment estamos en modo ascii
  • Si el caracter recibido es menor a 128, se imprime como caracter ascii normal
  • Si el caracter recibido es mayor o igual a 128 se pasa a modo unicode y este caracter se guarda en la zona unicode (y no se emite nada todavia)
  • Si ahora llega el caracter 0, se gudarda en la zona unicode, se imprime como cadena y se retorna al modo ascii. Usamos el 0 para delimitar el fin del unicode
  • Si estando en modo unicode llega cualquier byte, se almacena en la zona unicode

Esto funcionará siempre y cuando en los caracteres unicodes a imprimir no haya 0s intermedios...

Creo que lo voy a implementar...

Siiiii..... Esto ya funciona enel riscvForth:

$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
226 EMIT 148 EMIT 128 EMIT 0 EMIT
 ─ ok
BYE
 obijuan@Hoth:~/Develop/RiscvForth
$ 

Por tanto, la siguiente definición de UHLINE debería funcionar en ambos forths:

: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;

Vamos a coprobarlo...

obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;  ok
UHLINE CR ─
 ok
BYE 
obijuan@Hoth:~
$ 
obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
  ok
UHLINE CR
 ─
 ok
BYE
 obijuan@Hoth:~/Develop/RiscvForth
$ 

Siiiiiii!!!!!!!

Líneas horizontales

Antes de seguir implementando más cosas en el RiscForth, voy a frikear un poco con Forth, para aprender. Voy a definir la palabra HLINE para dibujar una línea de x caracteres, donde x está en la pila. Lo hago primero en gforth y luego compruebo en rvForth

: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
10 HLINE

Este es el resultado:

obijuan@Hoth:~
$ gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;  ok
UHLINE ─ ok
: HLINE ( x -- ) 0 DO UHLINE LOOP ;  ok
10 HLINE ────────── ok
BYE 
obijuan@Hoth:~
$
obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
  ok
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
  ok
10 HLINE
 ────────── ok
BYE
 obijuan@Hoth:~/Develop/RiscvForth
$ 

Definimos la palabra HEADER:

: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLINE ( x -- ) 0 DO UHLINE LOOP ;
: HEADER ( addr u len -- ) CR DUP HLINE CR ROT ROT TYPE CR  HLINE CR ;
S"   HOLI-1" 30 HEADER CR
──────────────────────────────
  HOLI-1
──────────────────────────────

 ok

En el rvForth funciona, pero no S" no se puede usar directamente en el intérprete (todavía) sino compilado como parte de otra palabra. Usamos la palabra de prueba T: : T S" HOLI-1" 30 HEADER CR ;. Y ahora sí que funciona bien:

obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
: UHLINE 226 EMIT 148 EMIT 128 EMIT 0 EMIT ;
: HLI NE ( x -- ) 0 DO UHLINE LOOP ; ok

  ok
: HEADER ( addr u len -- ) CR DUP HLINE CR ROT ROT TYPE CR  HLINE CR ;
  ok
: T S"   HOLI-1" 30 HEADER CR ;
  ok
T
 
──────────────────────────────
  HOLI-1
──────────────────────────────

 ok
T
 
──────────────────────────────
  HOLI-1
──────────────────────────────

 ok

2023-06-17

Estoy implementando las variables, haciendo refactorización. He encontrado un problema con la implementacińo actual. Variables aisladas se pueden crear y usar con normalidad, pero si ahora queremos por ejemplo crear un array de 10 celdas no es posible. Esto es debido a que al reservar espacio, la variable debe estar justo al final del diccionario... pero en la implementación actual se meten las instrucciones de retorno... Hay que modificar el doval actual para que tras el jal sólo haya variables...

2023-06-19

Ya tengo las varibles y constantes implementadas. Para comprobar que está todo ok, voy a ir migrando el programa de test ttester.fs al rvForth. El programa completo está aquí: https://forth-standard.org/standard/testsuite

Voy a empezar por la palabra T{ y añadiendo sus dependencias. Lo voy a compilar a mano, más que meterlo en el modo interactivo...

LISTO!!!!!!

obijuan@Hoth:~/Develop/RiscvForth
$ ./riscvforth.sh 
RiscvForth v0.1  15 Jun 2023
 ok
T{ 1 2 -> }T
 WRONG NUMBER OF RESULTS ok
T{ 1 2 -> 1 2 }T
  ok
T{ 1 2 -> 1 1 }T
 INCORRECT RESULT  ok
T{ 1 1 + -> 2 }T
  ok
BYE
 obijuan@Hoth:~/Develop/RiscvForth
$ 

Forth REVOLUTIONS!

2024-10-29

A través de Mastodón encontré hace unas semanas esta implementación de Forth para RiscV:

https://github.com/jjyr/jonesforth_riscv

Pero lo más interesante NO es eso, SINO QUE ES UN TUTORIAL!!!. Lo que quiero hacer es reproducirlo para entenderlo bien, e implementarlo en mi riscv. A ver si consigo de una vez entenderlo todo bien

El tutorial está aquí:

https://github.com/jjyr/jonesforth_riscv/blob/master/jonesforth.S

Está hecho para un riscv64... El RARs lo permite... así que igual lo pruebo tal cual, adaptándolo al RARS, y luego lo migro a 32 bits para ejecutarlo en un RISCV de verdad

He encontrado este mismo forth para un risv32:

https://github.com/hansfbaier/jonesforth_riscv32

Aunque el tutorial que voy a hacer es para el RV64... lo voy a ir migrando a RV32 según lo hago

Siguiendo la idea del tutorial, vamos a realizar primero un programa que simplemente llame a 3 funciones. La última de ellas llama a EXIT y termina

Empezamos por un programa que simplemente llama a 3 subrutinas y termina. La cuarta subrutina llama directamente al servicio EXIT del sistema operativo

  • Fichero: 01-main.s
#-- JonesForth. Fundamentos de Forth
#-- En este primer ejemplo se llama a 3 subrutinas

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	.text
	
	#-- Ejecutar las subrutinas
	jal pa
	jal pb
	jal pc
	jal exit
	
	#-- Nunca se retorna
	
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
	#-- Imprimir caracter A
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	ret
	
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
	#-- Imprimir caracter B
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	ret
	
	
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	ret
	
#--------------------
#-- Terminar
#--------------------
exit:

    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
	li a7, EXIT
	ecall

Al ejecutarlo se obtiene lo esperado:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 01-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ 

La clave son estas llamadas a subrutina:

jal pa
jal pb
jal pc
jal exit

Así es como desarrollé el intérprete de forth en el rvforth... está muy bien, se pueden hacer las cosas muy fáciles.. pero me encontré con problemas... y sobre todo al final quedó un código oscuro cuando quería hacer que el propio forth compilase

El siguiente paso es meter esas direcciones en una tabla en memoria, y hacer que se llemen unas a otras. Para ello hay que añadir código al final de cada subrutina, para saltar a la siguiente. Ese salto se logra definiendo un puntero, s1, a la siguiente instruccuón

Este es el nuevo código:

  • Fichero: 02-main.s
#-- JonesForth. Fundamentos de Forth
#-- En este primer ejemplo se llama a 3 subrutinas

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	 .data
	 
 	  #-- Lista con las subrutinas a ejecutar
 	  #-- Es NUESTRO PROGRAMA!
prog: .word pa, pb, pc, exit   
      #--    1   2   3   4
  
	
	.text
	
	#-- Inicializar registro S1
	#-- Hacemos que apunte a la siguiente subrutina
	la s1, prog
	addi s1,s1,4  #-- S1 apunta a la instruccion 2
	
	#-- Ejecutar la primera instrucciones!
	jal pa
	
	#-- Nunca se retorna
	
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
	#-- Imprimir caracter A
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	#-- Grupo de instrucciones para saltar a la siguiente subrutina
	lw a0, 0(s1)    #-- a0: Direccion de la siguiente subrutina
	addi s1, s1, 4  #-- s1: Apunta a la siguiente 
	mv t0,a0        #lw t0, 0(a0)
	jalr t0         #-- Ejecutarla!
	
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
	#-- Imprimir caracter B
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	#-- Grupo de instrucciones para saltar a la siguiente subrutina
	lw a0, 0(s1)    #-- a0: Direccion de la siguiente subrutina
	addi s1, s1, 4  #-- s1: Apunta a la siguiente 
	mv t0,a0        #lw t0, 0(a0)
	jalr t0			#-- Ejecutarla!
	
	
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	#-- Grupo de instrucciones para saltar a la siguiente subrutina
	lw a0, 0(s1)    #-- a0: Direccion de la siguiente subrutina
	addi s1, s1, 4  #-- s1: Apunta a la siguiente
	mv t0,a0        #lw t0, 0(a0)
	jalr t0	        #-- Ejecutarla!
	
#--------------------
#-- Terminar
#--------------------
exit:
    #-- Terminar
	li a7, EXIT
	ecall

Al ejecutarlo se obtiene el mismo resultado que con el programa anterior:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 02-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$

La clave ahora es que el mismo programa de antes se puede representar mediante una lista de direcciones (etiquetas). Cada etiqueta es el nombre de la "instrucción" (Palabra) que se quiere ejecutar

prog: .word pa, pb, pc, exit

Para implementar cada una de las funciones lo hacemos en ensamblador, y hay que terminar con una serie de instrucciones que tienen el efecto de pasar a la siguiente instrucciones: ejecutar la siguiente instrucción

lw a0, 0(s1)    #-- a0: Direccion de la siguiente subrutina
addi s1, s1, 4  #-- s1: Apunta a la siguiente 
mv t0,a0        #lw t0, 0(a0)
jalr t0		#-- Ejecutarla!

Estas instrucciones la metemos en la macro NEXT

  • Fichero: 03-main.s
#-- JonesForth. Fundamentos de Forth

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	#-- Macro para ejecutar la siguiente instruccion
	.macro NEXT
	  lw a0, 0(s1)    #-- a0: Direccion de la siguiente subrutina
	  addi s1, s1, 4  #-- s1: Apunta a la siguiente
	  mv t0,a0        #lw t0, 0(a0)
	  jalr t0	      #-- Ejecutarla!
	.end_macro
	
	 .data
	 
 	  #-- Lista con las subrutinas a ejecutar
 	  #-- Es NUESTRO PROGRAMA!
prog: .word pa, pb, pc, exit   
      #--    1   2   3   4
  
	
	.text
	
	#-- Inicializar registro S1
	#-- Hacemos que apunte a la siguiente subrutina
	la s1, prog
	addi s1,s1,4  #-- S1 apunta a la instruccion 2
	
	#-- Ejecutar la primera instrucciones!
	jal pa
	
	#-- Nunca se retorna
	
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
pa:
	#-- Imprimir caracter A
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	#-- Ejecutar siguiente instruccion
	NEXT
	
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
pb:
	#-- Imprimir caracter B
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	NEXT
	
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	NEXT
	
#--------------------
#-- Terminar
#--------------------
exit:

    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
    li a7, EXIT
    ecall

La ejecución es exactamente igual, pero ahora las funciones se escriben de forma más clara

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 03-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ 

Las funciones que estamos escribiendo se llaman funciones primitivas (porque están implementadas en ensamblador). TODAS las funciones primitivas tienen que terminar con NEXT. El registro s1 es nuestro Puntero de instrucción (IP)

Esto es lo que se denomina direct threaded code (DTC): Código enhebrado directo.

Ahora imaginemos el programa anterior escrito en forth (omitimos la llamada al sistema exit)

: TEST pa pb pc ;

Una posible compilación de este programa sería precisamente como una lista de las direcciones de pa, pb y pc.. y la propia implementación de estas funciones primitivas en ensamblador. Esto es lo que hace que el compilador de forth sea fácil: Basta con recopilar las direcciones de las instrucciones primitivas. ¡Cualquiera puede hacer esta compilación! ¡Se puede hacer a mano facilmente!

La técnica DTC es muy útil para precisamente eso: si queremos ejecutar cualquier conjunto de funciones primitivas, escritras en ensablador PERO, si lo que queremos es poder ejecutar también código en Forth: es decir, ejecutar palabras primitivas (en asm) y también palabras definidas en forth, necesitamos incluir una doble indirección. Es la técnica que se conoce como ITC (Indirect Threaded Code)

En este caso la dirección a poner no es directamente la del código asm, sino que hay que ejecutar el intérprete que se encargará de ejecutar esas instrucciones. En el caso de código primitivo, NO HAY INTÉRPRETE. Por lo que la dirección del intérprete es el propio código. Por ello, para nostros la palabra PA lo que tiene es la dirección hacia su propio código. El código de PA está situado en la etiqueta code_PA

Así es como queda el programa

  • Fichero: 04-main.s
#-- JonesForth. Fundamentos de Forth

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	#-- Macro para ejecutar la siguiente instruccion
	.macro NEXT
	  lw a0, 0(s1)    #-- a0: Direccion del codeword de la palabra
	  addi s1, s1, 4  #-- s1: Apunta a la siguiente palabra
	  lw t0, 0(a0)    #-- Leer direccion del interprete
	  jalr t0	      #-- Ejecutar interprete
	.end_macro
	
	 .data
	 
 	  #-- Palabra definida en FORTH
TEST: .word PA, PB, PC, EX   
      #--    1   2   3   4
  
#-- Palabras primitivas
PA:   .word code_pa
PB:   .word code_pb
PC:   .word code_pc
EX:   .word code_ex
	
	.text
	
	#-- Inicializar registro S1
	#-- Hacemos que apunte a la siguiente subrutina
	la s1, TEST
	addi s1,s1,4  #-- S1 apunta a la instruccion 2
	
	#-- Ejecutar la primera instrucciones!
	jal code_pa
	
	#-- Nunca se retorna
	
#-------------------------------
#-- pa: Imprimir el caracter A
#-------------------------------
code_pa:
	#-- Imprimir caracter A
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	#-- Ejecutar siguiente instruccion
	NEXT
	
#-------------------------------
#-- pb: Imprimir el caracter B
#-------------------------------
code_pb:
	#-- Imprimir caracter B
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	NEXT
	
#-------------------------------
#-- pc: Imprimir el caracter C
#-------------------------------
code_pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	NEXT
	
#--------------------
#-- Terminar
#--------------------
code_ex:

    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
	li a7, EXIT
	ecall

El funcionamiento sigue siendo igual que antes:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 04-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$

Refactorizamos el código para juntar las partes de las palabras. Ahora, cada palabra primitiva tiene una parte en el segmento de datos (el puntero a su código), y el código en el segmento de código

  • Fichero: 05-main.s
#-- JonesForth. Fundamentos de Forth

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	#-- Macro para ejecutar la siguiente instruccion
	.macro NEXT
	  lw a0, 0(s1)    #-- a0: Direccion del codeword de la palabra
	  addi s1, s1, 4  #-- s1: Apunta a la siguiente palabra
	  lw t0, 0(a0)    #-- Leer direccion del interprete
	  jalr t0	      #-- Ejecutar interprete
	.end_macro
	
	 .data
	 
 	  #-- Palabra definida en FORTH
TEST: .word PA, PB, PC, EX   
      #--    1   2   3   4
  
	.text
	
	#-- Inicializar registro S1
	#-- Hacemos que apunte a la siguiente subrutina
	la s1, TEST
	addi s1,s1,4  #-- S1 apunta a la instruccion 2
	
	#-- Ejecutar la primera instrucciones!
	jal code_pa
	
	#-- Nunca se retorna  
  
  
#-------------------------------
#-- PA: Imprimir el caracter A
#-------------------------------
	  .data
PA:   .word code_pa
	  .text
code_pa:
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	NEXT



#-------------------------------
#-- PB: Imprimir el caracter B
#-------------------------------
	  .data
PB:   .word code_pb
	  .text
code_pb:
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	NEXT


#-------------------------------
#-- PC: Imprimir el caracter C
#-------------------------------
	  .data
PC:   .word code_pc
	  .text
code_pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	NEXT


#--------------------
#-- Terminar
#--------------------
	  .data
EX:   .word code_ex
	  .text
code_ex:

    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
	li a7, EXIT
	ecall

Mismo funcionamiento:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 05-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ 

Las palabras escritas en FORTH necesitan su propio intérprete. Una de las cosas que tiene que hacer este intérprete es gestionar la pila de retorno (pila R). S1 apunta a la siguiente instrucción, pero si la instrucción actual está en Forth, a su vez estará formada por otra lista de palabras, porque necesita usar S1 como puntero. Por ello hay que guardarlo en la pila

Utilizamos el registro fp para la pila R

El intérprete para instrucciones FORTH se llama DOCOL. Todas las palabras FORTH empiezan llamando a DOCOL (su interprete)

  • Fichero: 06-main.s
#-- JonesForth. Fundamentos de Forth

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	#-- Macro para ejecutar la siguiente instruccion
	.macro NEXT
	  lw a0, 0(s1)    #-- a0: Direccion del codeword de la palabra
	  addi s1, s1, 4  #-- s1: Apunta a la siguiente palabra
	  lw t0, 0(a0)    #-- Leer direccion del interprete
	  jalr t0	      #-- Ejecutar interprete
	.end_macro
	
	 .data
	 
 	  #-- Palabra definida en FORTH
TEST:   .word DOCOL		# codeword
	    .word PA
	    .word PB
	    .word PC
	    .word EX		

  	#-- Arranque del interprete
  	#-- Se indica la primera palabra a ejecutar
cold_start:
    .word TEST  #-- Caso especial: No tiene codeword

	.text
	
	#-- Inicializar la pila R
	la fp, return_stack_top 
	
	#-- Arrancar el interprete
	la s1, cold_start      	
	NEXT
	
	#-- Nunca se retorna  
  
  
#---------------------------------------
#- DOCOL: Interprete de palabras Forth 
#---------------------------------------
#-  a0: Direccion del codeword (interprete)

    .text
DOCOL:
    #-- Insertar s1 en la pila R
	addi fp, fp, -4  
	sw s1, 0(fp)

	#-- a0: que apunte a la siguiente palabra
	addi a0,a0, 4
	
	#-- s1: Apuntar a la siguiente palabra
	mv s1, a0
	
	#-- Ejecutar siguiente palabra
	NEXT
  
  
#-------------------------------
#-- PA: Imprimir el caracter A
#-------------------------------
	  .data
PA:   .word code_pa #-- codeword
	  .text
code_pa:
	li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	
	NEXT



#-------------------------------
#-- PB: Imprimir el caracter B
#-------------------------------
	  .data
PB:   .word code_pb
	  .text
code_pb:
	li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	
	NEXT


#-------------------------------
#-- PC: Imprimir el caracter C
#-------------------------------
	  .data
PC:   .word code_pc
	  .text
code_pc:
	#-- Imprimir caracter C
	li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	
	NEXT


#--------------------
#-- Terminar
#--------------------
	  .data
EX:   .word code_ex
	  .text
code_ex:

    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
	li a7, EXIT
	ecall
	
	.data
  	#-- PILA R
  	.space 40
return_stack_top:


	.data
	#-- Pila de datos
var_S0:
		

El resultado es el mismo:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 06-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ 

Modificamos el programa para añadir el diccionario. Cada palabra tiene una cabecera formada por un enlace a la siguiente palabra la longitud del nombre + flags, el nombre ASCII y luego el codeword. En el caso de las instrucciones primitivas, el codeword apunta al código en asm, que se pone a continuación. En el caso de las palabras FORTH el codeword es DOCOL y a continuación se ponen el resto de palabras que componen la palabra

  • Fichero: 07-main.s
#-- JonesForth. Fundamentos de Forth

	#-- Servicios del sistema operativo del RARs
	.eqv EXIT 10        #-- Terminar
	.eqv PRINT_CHAR 11  #-- Imprimir un caracter
	
	#-- Macro para ejecutar la siguiente instruccion
	.macro NEXT
	  lw a0, 0(s1)    #-- a0: Direccion del codeword de la palabra
	  addi s1, s1, 4  #-- s1: Apunta a la siguiente palabra
	  lw t0, 0(a0)    #-- Leer direccion del interprete
	  jalr t0	      #-- Ejecutar interprete
	.end_macro
	
	
	.text
	
	#-- Inicializar la pila R
	la fp, return_stack_top 
	
	#-- Arrancar el interprete
	la s1, cold_start      	
	NEXT
	
	#-- Nunca se retorna  
	
	.data
  	#-- Arranque del interprete
  	#-- Se indica la primera palabra a ejecutar
cold_start:
    .word TEST  #-- Caso especial: No tiene codeword	
	
#---------------------------------------------------
#-- Diccionario 
#---------------------------------------------------

#-- Ejemplo de palabra primitiva
    .data
name_DROP:
    .word 0  #-- NULL (Puntero a siguiente palabra)
    .byte 4  #-- Longitud del nombre + flags
    .ascii "DROP"  #-- Nombre de la palabra FORTH
    .align 2
DROP:
    .word code_DROP  #-- Codeword
    .text
code_DROP:  #-- Codigo en asm
    nop
    nop
    NEXT	


#----------------------------
#-- PA. Imprimir caracter A 
#----------------------------
    .data
name_PA:
    .word name_DROP  #-- NULL (Puntero a siguiente palabra)
    .byte 2          #-- Longitud del nombre + flags
    .ascii "PA"      #-- Nombre de la palabra FORTH
    .align 2
PA:
    .word code_PA  #-- Codeword
    .text
code_PA:  #-- Codigo en asm
    li a0, 'A'
	li a7, PRINT_CHAR
	ecall
	NEXT
	
	
#----------------------------
#-- PB. Imprimir caracter B 
#----------------------------
    .data
name_PB:
    .word name_PA  #-- NULL (Puntero a siguiente palabra)
    .byte 2          #-- Longitud del nombre + flags
    .ascii "PB"      #-- Nombre de la palabra FORTH
    .align 2
PB:
    .word code_PB  #-- Codeword
    .text
code_PB:  #-- Codigo en asm
    li a0, 'B'
	li a7, PRINT_CHAR
	ecall
	NEXT
	
#----------------------------
#-- PC. Imprimir caracter C 
#----------------------------
    .data
name_PC:
    .word name_PB  #-- NULL (Puntero a siguiente palabra)
    .byte 2          #-- Longitud del nombre + flags
    .ascii "PC"      #-- Nombre de la palabra FORTH
    .align 2
PC:
    .word code_PC  #-- Codeword
    .text
code_PC:  #-- Codigo en asm
    li a0, 'C'
	li a7, PRINT_CHAR
	ecall
	NEXT
	
#-------------------------------
#-- EX. Servicio EXIT del RARs 
#-------------------------------
    .data
name_EX:
    .word name_PC  #-- NULL (Puntero a siguiente palabra)
    .byte 2          #-- Longitud del nombre + flags
    .ascii "EX"      #-- Nombre de la palabra FORTH
    .align 2
EX:
    .word code_EX  #-- Codeword
    .text
code_EX:  #-- Codigo en asm
    li a0, '\n'
    li a7, PRINT_CHAR
    ecall
    
    #-- Terminar
	li a7, EXIT
	ecall
	
#--------------------------------------
#-- TEST. Definida en FORTH
#-------------------------------------	
	 .data
name_TEST:
     .word name_EX
     .byte 4
     .ascii "TEST"
     .align 2
TEST:
	 .word DOCOL  # Codeword
	 .word PA
	 .word PB
	 .word PC
	 .word EX
	 

#---------------------------------------
#- DOCOL: Interprete de palabras Forth 
#---------------------------------------
#-  a0: Direccion del codeword (interprete)

    .text
DOCOL:
    #-- Insertar s1 en la pila R
	addi fp, fp, -4  
	sw s1, 0(fp)

	#-- a0: que apunte a la siguiente palabra
	addi a0,a0, 4
	
	#-- s1: Apuntar a la siguiente palabra
	mv s1, a0
	
	#-- Ejecutar siguiente palabra
	NEXT



	#-- PILA R
	.data
  	.space 40
return_stack_top:

	#-- Pila de datos
	.data
var_S0:

El funcionamiento es el mismo que antes:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ ./run.sh 07-main.s 
ABC
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-10-29$ 

2024-10-30

Voy a escribir código rápido y sucio para organizar las ideas... quiero tenerlo todo claro, e ir pasándolo a limpio en otra página de la wiki: una especie de tutorial, que iré refinando a medida que aprenda cosas... En este pongo las cosas en bruto de momento

De momento estoy haciendo los ejemplos xx-test.s, empezando por el 01-test.s... Estoy escribiendo en los comentarios una especie de tutorial incremental. En cada nuevo programa de test se añade alguna mejora. Creo que se entiende muy muy bien. Esto lo escribiré en la wiki, pero de momento lo dejo en el código

Tras hacer todos los ejemplos de test, con sus tutoriales... tenemos en 21-test.s un MOTOR MINIMO DE FORTH

  • Fichero: 21-test.s
#-- Cambio/mejora: 
#-- En este ejemplo no se ha añadido nada nuevo en el interprete
#-- Lo que se hace es definir cuatro palabras que se recorren en 
#-- profundidad. TEST llama a TEST2 que llama a TEST3 que a su vez  
#-- llama a TEST4 y retorna. Se vuelve atrás hasta que se imprime A y B
#-- desde la primera palabra, tras ejecutar TEST
#--
#-- La cadena impresa es AB, y luego se termina OK
#--
#-- Llegados a estsa punto YA tenemos un intérprete de FORTH
#-- capaz de ejecutar palabras primitivas y palabras FORTH
#-- Aunque en realidad NO hay palabras definidas del propio FORTH
#-- todavía. Tenemos un MOTOR FORTH mínimo!!

    .include "so.s"

#---------------------------------------------------
#-- Definimos las palabras de nuestro sistema
#---------------------------------------------------
    .data

    #-- Primera Palabra de prueba a Ejecutar
    #-- Como es la primera es "especial". Su configuracion
    #-- es diferente a la de las demas
    #-- No tiene codeword propio
TEST:   
    .word TEST2  
    .word W1
    .word W2
    .word EX

TEST2:
    .word DOCOL
    .word TEST3
    .word EXIT

TEST3:
    .word DOCOL
    .word TEST4
    .word EXIT

TEST4:
    .word DOCOL
    .word EXIT

#------------------------
#-- W1: Imprimir A
#------------------------
    .data
W1: .word code_W1   #-- Codeword: Direccion codigo ejecutable
    #-- Implementacion de W1
    .text
code_W1:
   SO_PRINT_CHAR('A')
   NEXT     #-- Ejecutar siguiente instruccion

#-----------------------
# W2: Imprimir B 
#-----------------------
    .data
W2: .word code_W2   #-- Codeword
    .text
code_W2:
   SO_PRINT_CHAR('B')
   NEXT  

#-----------------------
# EX: Terminar
#-----------------------
    .data
EX: .word code_EX   #-- Codeword 
    .text
code_EX:
   SO_PRINT_CHAR('\n')
   SO_EXIT
   #-- Es una instruccion especial
   #-- Se termina, por lo que NO se llama a NEXT

#--------------------------------------------------------------------
# EXIT. Palabra que se tiene que ejecutar al final de la definicion
# de una palabra NO primitiva
#--------------------------------------------------------------------
    .data
EXIT: .word code_EXIT  #-- Codeword
    .text
code_EXIT:

    #-- Recuperar s1 de la pila
    lw s1, 0(fp)

    #-- Restaurar pila R
    addi fp,fp,4

    #-- Ejecutar siguiente instruccion!
    NEXT

#-----------------------
#-- DOCOL
#-- NO es una palabra de Forth. Es directamente codigo
#-- maquina que dice como ejecutar una palabra no primitiva
#-----------------------
.text
DOCOL:
   addi fp,fp,-4  #-- Generar espacio en la pila R
   sw s1, 0(fp)   #-- Almacenar s1 en la pila R

   #-- a0 apunta a DOCOL, cuando se empieza a ejecutar
   #-- Si le sumamos 4, apunta a la siguiente palabra
   #-- (Una palabra que empieza por DOCOL esta formada por varias
   #-- palabras. Es decir, que tras DOCOL hay una palabra seguro)
   #-- Hacemos que s1 apunte a esa siguiente palabra
   addi s1, a0, 4  #-- S1 apunta a la siguiente palabra tras DOCOL

   #-- Ejecutar la siguiente instruccion!
   NEXT

#-------------------------------------------
#-- Programa principal
#-- ARRANCA AQUI!!!!
#-------------------------------------------
    .text
    .global main
main:

    #-- Inicializar el puntero de pila R
	la fp, return_stack_top 

    #-- S1 apunta la primera palabra a ejecutar (W1)    
    la s1,TEST  #-- s1 apunta a la Variable con la palabra a ejecutar
                #-- s1 es el IP (Puntero de instruccion)

    #-- Ejecutar primera instruccion (W1)
    NEXT  #-- Ejecuta la instruccion apuntada por s1
          #-- y Apuntar a la siguiente

    #-- Nunca llega aqui!!!!!

    .data
    .space 40
return_stack_top:

RESUMEN

El conocimiento que tenemos hasta ahora se resume en muy poco código. Me dan ganas de escribir una cheat-sheet de Forth...

  • Definicion de palabras FORTH
        .data
WORD1:  .word DOCOL  #-- Codeword
        .word WORD2
        .word WORD3
        ...
        .word EXIT

Donde WORD2 y WORD3 son bien palabras primitivas o palabras FOTH. Da igual

  • Definición de palabras primitivas
    .data
W1: .word code_W1  #-- Codeword
    .text
code_word:
    #-- Instruccion asm 1
    #-- Instruccion asm 2
    #-- ....
    NEXT
  • MACRO NEXT
    lw  a0,0(s1)
    addi s1,s1,4
    lw t0,0(a0)
    jalr t0
  • DOCOL: Código en asm
      .text
DOCOL: addi fp,fp,-4
       sw s1,0(fp)
       add s1,a0,4
       NEXT
  • Palabra primitiva EXIT
    .data
EXIT: .word code_exit
    .text
code_exit:
    lw s1, 0(fp)
    addi fp,fp,4
    NEXT
  • Palabra inicial

Esta palabra se define de manera distinta al resto. No tiene DOCOL ni EXIT

    .data
TEST:  #-- No tiene codeword!!
    .word WORD1
    .word WORD2
    ...
  • Arranque del intérprete
    .text

    #-- Configurar pila R
    la fp, return_stack_top 

    #-- Comenzar ejecucion de palabra especial TEST
    la s1, TEST
    NEXT

Cheat Sheet

2024-10-31

Me apetece implementar la E/S, para que los programas empiecen a ser interactivos... Estoy implementando KEY. Para simplificar elimino el buffer de entrada y llamo directamente al servicio READ_CHAR del RARS. Dentro del simulador funciona muy bien: Cada vez que se aprieta una tecla se devuelve el control... PERO no es así cuando se ejecuta desde la consola de linux. Por cada caracter hay que darle al ENTER, lo cual es una mierda...

De momento puedo tirar con el simulador, pero voy a pensar en una solución para lo de la consola... Tal vez, antes de llamar al rars hay que configurar la consola para que devuelva el control al recibir un caracter... Existe algun comando linux para hacerlo?

He encontrado el comando, pero no funciona:

stty -icanon min 1 -echo

También he probado con la biblioteca que hice en C hace mucho: consola_io. Configura la consola para acceso directo, pero nada. Viendo el código veo que hay metido un timeout en select... vamos que la magia no está en la configuración de stdin, sino en el select...

Por ese camino nada... sólo me queda una cosa por probar.. utilizar la llamada al sistema read del rars, que es la misma que usa el forth... a ver si saco algo en claro...

Siiiiiiiiiiiiiii!! Joder... siiiiiiiiiiiiiiiiii!!!! Con el servidio READ (63) del RARS sí que funciona. Le he puesto que la cantidad máxima de caracteres a leer sea 1, y me devuelve el control... cojonudo!! Ya puedo seguir implementando KEY!

He tenido que cerrar el terminal y abrir uno nuevo y... el ejemplo 26 se ha vuelto bloqueante!!! Así que en realidad el comando stty sí que es necesario!!!. Lo he añadido en el run.sh

2024-11-01

Voy a empezar a ver la parte del compilador. La idea la verdad es que la explica Jones muy bien en su tutorial... Tenemos que convertir un trozo de texto como este:

: DOUBLE DUP + ; 

En una entrada en el diccionario como esta:

    Puntero a la siguiente palabra
	   ^
	   |
	+--|------+---+---+---+---+---+---+---+---+------------+------------+------------+------------+
	| LINK    | 6 | D | O | U | B | L | E | 0 | DOCOL      | DUP        | +          | EXIT       |
	+---------+---+---+---+---+---+---+---+---+------------+--|---------+------------+------------+
	   ^       len                         pad  codeword      |
	   |							  V
	  LATEST  				         Apunta a la codeword de DUP

Lo podemos representar así:

                                                                  +-------------------------------+
                                                         +------->|  Inst. Codigo maquina DOCOL   |
                                                         |        +-------------------------------+
        +---------+---+---+---+---+---+---+---+---+------|-----+------------+------------+------------+
	| LINK    | 6 | D | O | U | B | L | E | 0 | DOCOL      | DUP        | +          | EXIT       |
	+---------+---+---+---+---+---+---+---+---+------------+--|---------+------------+------------+
                                           +----------------------+
                                           |
                                           V
        +---------+---+---+---+---+---+------------+  ------------------------------------+
	| LINK    | 3 | D | U | P | 0 | Code_Dup   |  | Inst. codigo maquina DUP          |
	+---------+---+---+---+---+---+----|-------+  --^---------------------------------+
                                           |            |
                                           +------------+

Todo está en el segmento de datos, salvo los trozos separados que contienen el código máquina, que están en el segmento de código
En el caso de compilar una palabra Forth, el único código máquina que hay es el de DOCOL

Para continuar con la implementación del compilador hay que implementar primero las variables y constantes.
Ya tengo las variables...

2024-11-02

Toca implementar las constantes... vamos al lío!
Listo! Ya tengo todas las constantes del sistema implementadas. Faltan las llamadas al sistema, pero eso lo haré más adelante
Ya se podría implementar el compilador, pero prefiero implementar primero el intérprete, para probar directamente en la línea de comandos, y no tener que estar añadiendo casos de prueba en el propio código... así que... ¡vamos a por ello! Aquí es cuando las cosas se empiezan a poner muy interesantes

El intérprete básico ya está listo: palabra INTERPRET. Para terminar de funcionar se necesita la palabra QUIT que es la que ejecuta en un bucle infinito INTERPRET. Pero para implementar QUIT hay que implementar primero BRANCH

Listo! Ya está funcionando el primer intérprete! De momento hay pocas palabras en el diccionario por lo que se puede hacer poco

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-02$ ./run.sh 37-test.s 
1
2
HI
<3> 1 2 5
BYE

Es el momento de ampliar el diccionario

2024-11-03

La entrada no me funciona muy bien... La he implementando de una manera distinta a como estaba en el JoneForth, pero creo que ahora ya entiendo la implementación inicial... así que la voy a re-implementar!

Listo! Ahora ya funciona la entrada estándard! Los programas de prueba los puedo poner en ficheros externos. He creado test.f con pruebas iniciales

\\-- Probando DOTS
1 2 3 .S

\\-- Probando DROP
DROP DROP DROP .S

\\-- Probando  SWAP
5 6 .S SWAP .S DROP DROP .S

\\-- Prueba de DUP
5 .S DUP .S DROP DROP .S

\\-- Prueba de OVER
1 2 3 .S OVER .S DROP DROP DROP DROP .S 

Así es como se prueba:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-03$ ./run.sh 39-test.s < test.f
<3> 1 2 3
<0>
<2> 5 6
<2> 6 5
<0>
<1> 5
<2> 5 5
<0>
<3> 1 2 3
<4> 1 2 3 2
<0>

El programa de prueba va creciendo:

\\-- Probando DOTS
1 2 3 .S

\\-- Probando DROP
DROP DROP DROP .S

\\-- Probando  SWAP
5 6 .S SWAP .S DROP DROP .S

\\-- Prueba de DUP
5 .S DUP .S DROP DROP .S

\\-- Prueba de OVER
1 2 3 .S OVER .S DROP DROP DROP DROP .S 

\\-- Prueba de ROT
10 20 1 2 3 .S ROT .S DROP DROP DROP DROP DROP .S

\\-- Prueba de -ROT
10 20 1 2 3 .S -ROT .S DROP DROP DROP DROP DROP .S

\\-- Prueba de 2DROP
10 1 2 .S 2DROP .S DROP .S

\\-- Prueba de 2DUP
10 1 2 .S 2DUP .S DROP DROP DROP DROP DROP .S

\\-- Prueba de 2WAP
10 1 2 3 4 .S 2SWAP .S DROP DROP DROP DROP DROP .S

\\-- Prueba de ?DUP
10 2 .S ?DUP .S DROP DROP DROP .S  \\-- Hay duplicacion
10 0 .S ?DUP .S DROP DROP .S       \\-- No hay duplicacion

\\-- Prueba de 1+
5 .S 1+ .S DROP .S

\\-- Prueba de 1-
5 .S 1- .S DROP .S

\\-- Prueba de 4+
10 .S 4+ .S DROP .S

\\-- Prueba de 4-
12 .S 4- .S DROP .S

\\-- Prueba de 8+
8 .S 8+ .S DROP .S

\\-- Prueba de 8-
16 .S 8- .S DROP .S

\\-- Operacion +
1 1 .S + .S DROP .S

\\-- Operacion -
10 10 .S - .S DROP .S

\\-- Operacion *
7 3 .S * .S DROP .S

Y esta es su ejecución:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-03$ ./run.sh 40-test.s < test.f
<3> 1 2 3
<0>
<2> 5 6
<2> 6 5
<0>
<1> 5
<2> 5 5
<0>
<3> 1 2 3
<4> 1 2 3 2
<0>
<5> 10 20 1 2 3
<5> 10 20 2 3 1
<0>
<5> 10 20 1 2 3
<5> 10 20 3 1 2
<0>
<3> 10 1 2
<1> 10
<0>
<3> 10 1 2
<5> 10 1 2 1 2
<0>
<5> 10 1 2 3 4
<5> 10 3 4 1 2
<0>
<2> 10 2
<3> 10 2 2
<0>
<2> 10 0
<2> 10 0
<0>
<1> 5
<1> 6
<0>
<1> 5
<1> 4
<0>
<1> 10
<1> 14
<0>
<1> 12
<1> 8
<0>
<1> 8
<1> 16
<0>
<1> 16
<1> 8
<0>
<2> 1 1
<1> 2
<0>
<2> 10 10
<1> 0
<0>
<2> 7 3
<1> 21
<0>

Program terminated by calling exit

2024-11-04

Más palabras añadidas al diccionario, y pruebas en ficheros Forth externos

2024-11-05

Todas las variables y constantes añadidas

2024-11-06

El compilador ya empieza a funcionar!!!!

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-06$ ./run.sh 48-test.s 
: UNO 1 ;
: DOS UNO UNO + ;
: TRES DOS UNO + ;
: CUATRO TRES UNO + ;
CUATRO .S
<1> 4
BYE


Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-06$

Esto quiere decir que ya puedo empezar a probar programas en Forth!!! Todavía hay mucho que pulir y probar, pero pinta muy bien

2024-11-07

Ya está todo el fichero jonesforth.S implementado!

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ ./run.sh 49-test.s < test-05.fs 
<1> 49
<0>
<1> 268502696
<0>
<1> 4194304
<0>
<1> 128
<0>
<1> 32
<0>
<1> 31
<0>
<1> 1973
<0>
<1> 0
<0>
<1> 268502696
<0>
<1> 268502736
<0>
<1> 65
<0>
<2> 268502040 4
<0>
<1> 268502088
<0>
<1> 0
<0>
<1> 268502100
<0>
<1> 268502104
<0>
<0>
<0>
<0>
<1> 268501140
<0>
<1> 65
<0>
<1> 134068006
<0>
5<0>

Así que ahora ya pasamos a FORTH!!!

El interprete en ensamblador es el 50-test.s. La ampliacion en Foth está en el fichero jonesforth.fs. Los ficheros de prueba siguen a partir de test-06.fs

Para realizar las prueba usamos este comando:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ cat jonesforth.fs test-06.fs | ./run.sh 50-test.s 
<2> 10 5
<1> 2
<0>
<2> 10 3
<1> 1
<0>

Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-07$ 
  • Ya funciona el IF-THEN / IF-ELSE-THEN
  • Listo el BEGIN-UNTIL

2024-11-08

Tengo pendiente hacer una recopilación de bibliografía interesante de Forth... de momento pongo lo que voy usando cada día.

Este documento PDF está el libro "Introducción a Forth" de F.J Gil Chica, de la Universidad de Alicante:

http://www.disc.ua.es/~gil/forth.pdf

El texto está muy bien, y se aclaran un montón de conceptos que tenía un poco difusos. Lo interesante de estes libro es que implementa su propio Forth (en C), y luego lo extiende con el propio Forth. Esto es algo típico de los sistemas Forth: se crea un núcleo mínimo implementando a bajo nivel (típicamente en ensamblador del procesador a utilizar). Con este núcleo mínimo ya se tiene un intérprete y compilador minimos, con los que se puede extender el lenguaje para crear cosas más complejas

El Forth creado por el auto es no estándar, y lo ha llamado Lézar. El núcleo contiene lo siguiente:

  • El compilador
  • El intérprete
  • Palabras primitivas. En total tiene definidas 66
+ - * / mod and or = < > dup
drop swap >r r> : , create
does> immediate postpone [ ]
tick execute if else then
do loop +loop begin while
repeat I break exit key char
emit @ c@ ! c! c, date time
open-file close-file parse
read-line write-line channel
restart rebuild ." allot M N
. .s depth dd dc mark back

En el fichero lezar.f tiene definida la extensión del núcleo, implementado en FORTH. Estas son las palabras que contiene, 86:

cell cells rot -rot over nip
tuck 2dup 3dup 2drop ndrop
2swap cls plas 0= ?dup 0< 0>
>= <= != 1+ 1- 2* negate abs
^ fact << >> max min /mod
variable constant +! n->dd
[char] ESC[ at-xy cr cursor^
cursor_ cursor> <cursor
cursor^^ cursor__ cursor>>
<<cursor page <# # #s #>
hold sign type .r fill erase
copy 2c@ compare accept <-X
string strlen upcase? strup
lowcase? strlow strscan used
.time .date channels fr fw
fa q+ q- q* q/ mcd reduce q.

En total tenemos un vocabulario de 66 + 86 = 152 palabras. Esto es muy interesante porque se pueden tener diferentes extensiones según lo que se quiera hacer... así podríamos tener diferentes FORTHs para hacer diferentes cosas...

Voy a renombrar los nombres de los ficheros siguiendo con esta filosofía. El nuevo fichero con el nucleo mínimo lo voy a llamar kernel.s, y está implementado para el RISCV-32-bits. El fichero kernel-ext.fs es la extensión con el resto de palabras, ya definidas en Forth

Para lanzar el Forth con el kernel extendido se ejecuta este comando:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$ ./forth.sh 
.S
<0>
* 
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$

Se carga el kernel extendido y se pasa a modo intérprete. Para salir hay que apretar Ctrl-D

El script redirecciona el fichero kernel-ext.fs y activa la salida estándard normal:

#!/bin/sh
#-- Ejecutar el forth como interprete, incluyendo
#-- todas sus palabras del nucleo y de la extension
cat kernel-ext.fs $1 - | ./run_rars.sh kernel.s

Se le puede pasar opcionalmente como argumento el programa de test, que se cargará despues de haber cargado el kernel extendido:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-08$ ./forth.sh test-06.fs 
<2> 10 5
<1> 2
<0>
<2> 10 3
<1> 1
<0>



   <0>
<1> 73
<1> -73
<0>
<2> 1 0
<0>
:<0>
00AA<0>
A<0>
AB
<0>
B
<0>
A
<0>
B
<0>
AAAAA
<0>
AAAAAAAAAA
<0>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
<0>

2024-11-10

En la version 50 del nucleo hay un bug, que tengo que estudiar y solucionar. La versión 50 la dejamos como esta, y pasamos a la 51 para hacer las modificaciones necesarias para solucionarlo

El bug se reproduce así:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
PARSE ERROR
s A..Z )
                'A'
        THEN
        +
        EMIT
;
...

Aparece un Parse ERROR. Hay que descubrir de donde sale. El script forth carga el núcleo y su extensión. Así que el problema está en la extensión. Ponemos un BYE para salir, y detectar exactamente a partir de donde sale el PARSE ERROR

ok... había dejado una parte sin comentar... ya está solucionado y al ejecutar Forth se carga el kernel extendido y vuelve al intérprete:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
1 1 + .S
2 
BYE


Program terminated by calling exit

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$

En principio parece que funciona PERO... si leemos una variable entonces PETA:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
STATE
.S
26850172Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->45: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a

Simulation terminated due to errors.

Parece que el error esta bien en la lectura de STATE (que es la primera variable definida), o bien en la ejecucion de .S. Lo primero que vamos a hacer es repetir el experimento pero SIN cargar la extension del kernel. Es decir, directamente en el nucleo mínimo. Allí utilizaremos el .S primitivo, implementado en ensamblador:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
.S
<1> 268501720

BYE

Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10

Vemos que sí funciona. Parece que el error está en .S. Pero podría ser que la propia ejecución de STATE sea la que joda todo. Voy a renombrar .S como _.S para diferenciar al .S ejecutado en el nucleo mínimo del que se ejecuta en el nucleo externo

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
_.S
<1> 268501720
BYE


Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

Voy a cambiar el _.S a hexadecimal, así podemos comprobar si las direcciones de las variables están alineadas, y las podemos buscar en el simulador

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./run_rars.sh kernel.s
STATE
_.S
<1> 0x100102d8
BYE


Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

Ahora probamos con el nucleo extendido:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
STATE
_.S
<1> 0x100102d8
.S
26850172Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a

Simulation terminated due to errors.

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

La variable STATE funciona: deja el valor en la pila, y con _.S se lee correctamente. Sin embargo con .S PETA, así que el error parece estar ahí...

La implementación de .S depende de U. Así que vamos a probar con U., que imprime un numero sin signo

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
1
U.
1 
STATE
U.
268501720 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a

Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

Vemos que peta!!!!. Así que el error está en U. Parece que algo se imprime primero... Vamos a comprobarlo imprimiendo en hexadecimal con _.S

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
STATE
_.S
<1> 0x100102d8
U.
268501720 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a

Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

Efectivamente es el numero correcto en decimal:

In [1]: hex(268501720)
Out[1]: '0x100102d8'

Parece que U. funciona: imprime el numero correcto PERO al terminar hay un problema... Vamos a examinar el código de U.

Antes un experimento... el error se reproduce si se introduce un numero de 9 dígitos:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
123456789
U.
123456789 Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 90->50: Runtime exception at 0x00400024: Load address not aligned to word boundary 0x0000000a

Simulation terminated due to errors.
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

Si el numero es de 8 cifras, la cosa NO peta

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
12345678
U.
12345678 
*
Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

En la implementación de U. se utiliza RECURSION, mediante a la llamada RECURSE

El tamaño de la pila R es de 40 bytes... como cada posicion es de 4 bytes... nos da un tamaño de 10!!! Hay que hacer una pila R MAYOR! Vamos a probar...

La he aumentado una posición más (44 bytes) para comprobar....

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
123456789
U.
123456789 
BYE


Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

SIIIII!!!!! Ha funcionado!!!! Voy a meter un tamaño mayor.... por ejemplo de 20 posiciones: 80 bytes!

Ahora ya sí que se ejecuta el test-06.fs completo, sin dar errores:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-06.fs
5 10 2 3 10 1 


   73 )- 0 1 :00AAAAB
B
A
B
AAAAA
AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA
AAAAAAAAAA
B
AB
          A
FF 255 2 
3 
5 
       255
      4000
      -543
        23
12 0 
4 

VAAAAAAAAAAAMOS!!!!!!!

A partir de la versión 52 ya tenemos la capacidad de imprimir cadenas!

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-06.fs
5 10 2 3 10 1 


   73 )- 0 1 :00AAAAB
B
A
B
AAAAA
AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA
AAAAAAAAAA
B
AB
          A
FF 255 2 
3 
5 
       255
      4000
      -543
        23
12 0 
4 
4 268505744 
HOLA
HOLA2
VERSION: 52 
BYE


Program terminated by calling exit
obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ 

En la implementación de SEE (del decompilador) se produce un runtime error

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-07.fs

------------ VERSION 61 ------------------------------ 

* WORDS 
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT 

* DUMP 
10011714 -48 16  1 10  3 53 45 45  0  0 40  0 28  4  1 10 .....SEE..@.(...
10011724 68  4  1 10 -4  2  1 10 74  2  1 10 -18  2  1 10 h.......t.......
10011734 74  2  1 10 78  0  1 10  2  0  0  0 -38  C  1 10 t...x...........
10011744 -40  0  1 10 -50  1  1 10 -68  5  1 10 18  0  0  0 ................
10011754 -70  C  1 10 -50  0  1 10 74  2  1 10 -78  5  1 10 ........t.......
10011764 -2C -1 -1 -1 -6C  0  1 10 -5C  0  1 10 2C  9  1 10 ............,...
10011774 14  4  1 10 -80  8  1 10 -50  0  1 10 -78 13  1 10 ................
10011784 -80  8  1 10 -50  0  1 10 10 14  1 10 -68  5  1 10 ................

* CASE
Valor 0
Valor 1
Caso por defecto: 5 

: TEN 10 ;
SEE TEN
Error in /home/obijuan/Develop/Learn-forth/Log/2024-11-10/kernel.s line 905: Runtime exception at 0x004006e4: address out of range 0x00000000

Simulation terminated due to errors.

Hay que depurar SEE a ver qué ocurre... Tener un decompilador es muy interesante...

Para detectar el problema vamos a ir re-implementando SEE palabra por palabra, hasta que pete

De momento he encontrado un bug y ya no peta, pero no tengo claro si SEE está funcionando bien:

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh test-07.fs

------------ VERSION 61 ------------------------------ 

* WORDS 
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT 

* DUMP 
10011714 -48 16  1 10  3 53 45 45  0  0 40  0 28  4  1 10 .....SEE..@.(...
10011724 68  4  1 10 -4  2  1 10 74  2  1 10 -18  2  1 10 h.......t.......
10011734 74  2  1 10 78  0  1 10  2  0  0  0 -38  C  1 10 t...x...........
10011744 -40  0  1 10 -50  1  1 10 -68  5  1 10 18  0  0  0 ................
10011754 -70  C  1 10 -50  0  1 10 74  2  1 10 -78  5  1 10 ........t.......
10011764 -2C -1 -1 -1 -6C  0  1 10 -5C  0  1 10 2C  9  1 10 ............,...
10011774 14  4  1 10 -80  8  1 10 -50  0  1 10 -78 13  1 10 ................
10011784 -80  8  1 10 -50  0  1 10 10 14  1 10 -68  5  1 10 ................

* CASE
Valor 0
Valor 1
Caso por defecto: 5 

------- Testing SEE 
SEE TEN
: TEN 268501100 ;
HEX
SEE TEN
: TEN 1001006C ;

SIIIII!!!!! Ya lo tengo!!!! Había muchos 8s que había que sustituir por 4s en SEE. Todavía no descarto que haya más bugs, pero de momento TEN lo decompila de puta madre:

------------ VERSION 61 ------------------------------ 

* WORDS 
SEE CFA> ENDCASE ENDOF OF CASE DUMP FORGET WORDS ?IMMEDIATE ?HIDDEN ID. +TO TO VALUE VARIABLE CELLS ALLOT CONSTANT ." S" C, ALIGN ALIGNED DEPTH WITHIN ? U. . .R U.R UWIDTH .S U. HEX DECIMAL SPACES PICK TUCK NIP ( UNLESS REPEAT WHILE AGAIN UNTIL BEGIN ELSE THEN IF RECURSE [COMPILE] '.' '-' '0' 'A' '"' ')' '(' ';' ':' LITERAL NOT FALSE TRUE NEGATE SPACE CR BL '\n' MOD / BYE SYSCALL0 SYSCALL1 SYSCALL2 SYSCALL3 EXECUTE CHAR INTERPRET QUIT TELL LITSTRING 0BRANCH BRANCH ' HIDE HIDDEN IMMEDIATE ; : ] [ , CREATE >DFA >CFA FIND NUMBER WORD EMIT KEY DSP! DSP@ RSP! RSP! RSP@ R> >R F_LENMASK F_HIDDEN F_IMMED DOCOL R0 VERSION BASE S0 HERE LATEST STATE CMOVE C@C! C@ C! -! +! @ ! INVERT XOR OR AND 0>= 0<= 0> 0< 0<> 0= >= <= > < <> = /MOD * - + 8- 8+ 4- 4+ 1- 1+ ?DUP 2SWAP 2DUP 2DROP -ROT ROT OVER DUP SWAP DROP _.S LIT EXIT 

* DUMP 
10011714 -48 16  1 10  3 53 45 45  0  0 40  0 28  4  1 10 .....SEE..@.(...
10011724 68  4  1 10 -4  2  1 10 74  2  1 10 -18  2  1 10 h.......t.......
10011734 74  2  1 10 78  0  1 10  2  0  0  0 -38  C  1 10 t...x...........
10011744 -40  0  1 10 -50  1  1 10 -68  5  1 10 18  0  0  0 ................
10011754 -70  C  1 10 -50  0  1 10 74  2  1 10 -78  5  1 10 ........t.......
10011764 -2C -1 -1 -1 -6C  0  1 10 -5C  0  1 10 2C  9  1 10 ............,...
10011774 14  4  1 10 -80  8  1 10 -50  0  1 10 -78 13  1 10 ................
10011784 -80  8  1 10 -50  0  1 10 10 14  1 10 -68  5  1 10 ................

* CASE
Valor 0
Valor 1
Caso por defecto: 5 

------- Testing SEE 
SEE TEN
: TEN 10 ;

Ya tengo una primera versión lista. No están todas las palabras del kernel extendido. Faltan las relacionadas con las llamadas al sistema, y constantes relacioandas. Tampoco he metido las relacionadas con el código ensamblador. Esto es muy interesante... y sobre todo, muy importante para entender J1a forth...

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$ ./forth.sh 
JONESFORTH VERSION 63 
14927 CELLS REMAINING
OK 
BYE


Program terminated by calling exit

obijuan@JANEL:~/Develop/Learn-forth/Log/2024-11-10$

Desensamblador RISCV

Para practivar Forth voy a hacer un desensamblador del RISCV, así como herramientas de bajo nivel para trabajar con el RISCV

Comenzamos por la instrucción addi x1, x0, 1, que es de tipo I, y cuyo código máquina es: 0x00100093. El problema es partir de ese número y obtener los diferentes campos de la instrucción. El formato de las instrucciones de tipo I es:

| imm (12-bits) | rs1 (5) | func3 | rd (5) | opcode (7) |

2024-11-13

El tema de los ensambladores en Forth me resulta muy interesante desde que vi el del swapforth del procesador J1a... He encontrado este artículo que habla sobre el tema:

https://www.bradrodriguez.com/papers/tcjassem.txt

Así que he pensado en intentar hacer uno para el riscv.. Los avances los voy a ir poniendo en este log:

https://github.com/Obijuan/Learn-RISCV/wiki

🚧 TODO 🚧
(DEBUG)