BEGIN es un palabra inmediata, es decir, es ejecutada durante la compilación. Durante la compilación, el BEGIN simplementa deja en el stack la dirección de la primera celda libre en memoria, donde se deja la palabra construida. Esa es la misma celda que ocupara la primera instrucción dentro del bloque.
Cuando la compilación encuentra el AGAIN (tambien inmediata), este toma la dirección en el stack y la graba en la celda libre, a continuación de la última instrucción del bloque. En J1s, una dirección es también un salto a esa dirección.
Ejemplo
: x begin 1 + again ;
compila en
>see x
c0e 8001 ; cargar 1 en el stack
c0f 6203 ; sumar
c10 c0e ; salto a la primer instrucción
c11 608c ; Retorno (agregada por default a toda palabra)
c12 0 ; Marca de fin.
BEGIN - UNTIL
Este es un ciclo que se repite mientras UNTIL (también inmediata) vea un cero en el tope del stack. Su implementación es la misma del BEGIN - AGAIN, pero con un salto condicional:
compila en
c18 8001 ; Cargar 1 en el stack
c19 6203 ; Sumar
c1a 2c18 ; Salto condicional si stack == 0 a c18.
c1b 608c ; Retorno (agregado por default)
c1c 0 ; Marca de fin.
Balanceo
La parte interesante es asegurarse de que los BEGIN - AGAIN/UNTIL esten bien anidados.
Las celdas son de 16 bits, pero las direcciones sólo tiene 12 bits. Podemos aprovechar esos 4 bits marcar las direcciones que dejamos en el stack. Para ello usaremos la siguiente asignación:
0x2000 constant sys-if
0x3000 constant sys-do
Entonces, BEGIN coloca en el stack el valor 0x1xxx, donde xxx es la dirección donde ocurre el BEGIN.
Cuando la compilación encuentra el AGAIN/UNTIL, en el stack debe haber una celda con el valor 1 en los bits 15-12. Si tiene cualquier otro valor, hay algo mal anidado y se aborta la compilación.
Recuerde que todo esto pasa durante la compilación de una palabra, no durante la ejecución.
Implementación
: make-load ( n -- n ) 0x7FFF invert + ;
\ Convierte una dirección en salto condicional
: make-branch ( n -- n ) 0x2000 or ;
\ Convierte un valor en un sys-valor
: make-sys ( addr n -- sys-n ) or ;
\ Revisa el sys-valor en el stack;
: sys-check ( n sys-code -- n ) over 0x7000 and = ;
\ make-clean borra los bits 15-12 (definido en el kernel)
: begin sys-begin here make-sys ; immediate
: until sys-begin sys-check
if make-clean make-branch ,
else ." UNTIL desbalanceado" quit then ; immediate
: again sys-begin sys-check
if make-clean ,
else ." AGAIN desbalanceado" quit then ; immediate