IS-FORTH

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 30 Mar 2022 23:40

Último mensaje de la página anterior:

Elurdio escribió:···
Examinando lo hecho para el Jupiter Ace con más detenimiento, veo que NEXT podría ser también útil en el ISF.
···

He estado estudiando como trabaja la parte compilante de los LEAVE en el ISF.

Hay una posición de memoria (hex 022C de 2 bytes) reservada para esta labor. La llamaré (L)

Cada vez que se abre un DO se ponen en la pila tres números: el valor de HERE, la posición del último LEAVE del Bucle anidado anterior (si estamos en el más externo, valdrá 0) y el código 2.

Cada vez que encuentra un LEAVE mira el valor de (L). Si vale 0 apunta la dirección de LEAVE en (L). Si es distinto de 0, quiere decir que tiene el valor de un LEAVE anterior guardado. Entonces lo que se hace es poner en el Operand Field del LEAVE que está en la dirección guardada en (L) el valor de la dirección de este nuevo LEAVE. Luego apuntamos en (L) la dirección de este nuevo LEAVE. Lo que estamos haciendo es que cada LEAVE apunte al siguiente LEAVE y así sucesivamente. El último LEAVE apuntará al final del BUCLE (al LOOP que le corresponde). Así cuando se ejecuta un LEAVE, éste saltará al siguiente, y éste al siguiente, y así hasta que el último salta al LOOP correspondiente.

Si se abre un nuevo DO mientras no se ha cerrado el anterior (un bucle anidado) entonces se ponen en la pila los tres números comentados antes. En este caso el segundo número tendrá la dirección del último LEAVE del bucle anidado inmediatamente exterior a éste. Ahora que ya lo tenemos "a salvo" podemos poner (L) a cero y seguir como en el caso anterior.

Y así sucesivamente hasta que encontramos el primer LOOP. Este cerrará el LEAVE apuntado por (L) y entonces saca de la pila el valor que se salvó del último LEAVE del bucle anterior y lo coloca en (L) y continuamos procesando...

etc.

Visto lo cual, y otras cosas que no comento aquí, de momento dejo aparcado esto, pues es mucho lío para tan poco aporte de NEXT al ISF.

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 31 Mar 2022 22:30

Haciendo pruebas, por un lado viendo que ponen en la pila al compilar palabras con bucles DO/LOOP anidados y sin anidar, sin LEAVE, con LEAVE, con varios LEAVE, etc., usando .S y HERE dentro de palabras compilantes inmediatas que voy insertando en diferentes puntos de las palabras de prueba y por otro lado, examinando los Parameter Fields de dichas palabras con DUMP (la versión del manual de IS-FORTH corregida) creo que tengo información suficiente para hacer mi propio DO/LOOP compatible con LEAVE y que incorpore NEXT.

Resumo lo que he averiguado: (salvo que se indique expresamente, todos los CFAs están en Hexadecimal)

En tiempo de compilación:

DO coloca en la pila tres números (el primero es el último en entrar o TOP):
  • Código 2
  • LLA
  • HERE
LLA es el Last Leave Addres o la dirección del último LEAVE que se compiló antes de compilarse este DO. Si no hay ninguno, vale 0

Dentro de un mismo bucle DO/LOOP los LEAVE están linkados de modo que el primero apunta al segundo, el segundo al tercero y así hasta el último que apunta al LOOP.

Cada LEAVE compila el CFA=F299 de BRANCH (salto incondicional) seguido de un Operand Field (OP) de 2 bytes con la dirección a la que ha de saltar.

DO coloca en la pila de Retornos o Return Stack (RS) el contador y el límite que toma de la pila. Compila su CFA=E346

LOOP compila 2 run-times seguidos. Primero compila el CFA=E55F que llamo RUN-LOOP seguido de un OP de 2 bytes con la dirección justo después del CFA que compiló el DO correspondiente. Este RUN-LOOP corresponde al run-time que incrementa el índice y comprueba si se ha de repetir el bucle. Si se ha de repetir salta a la dirección almacenada en su OP. Si terminó continúa después del OP. Allí compila el CFA=DDE4 que llamo END-LOOP pues es el que se encarga de terminar el bucle quitando el límite y el índice del RS.

Cuando dije que el último LEAVE apunta al LOOP correspondiente, en realidad apunta al END-LOOP correspondiente.

El NEXT que voy a introducir es equivalente a un LEAVE salvo que NO termina el Bucle. De ahí que el NEXT debe saltar al RUN-LOOP en vez de al END-LOOP

Hay una variable de memoria del sistema (no sé como se llama) que corresponde a la posición de memoria hex =022C (2 bytes). Por la pruebas que hice que comenté al principio del post sospeché la existencia de tal variable y la encontré desensamblando LEAVE "por encima" (por inspección se ve enseguida)

LEAVE ni pone ni quita números en la pila durante la compilación. Lo cual me dejó perplejo. No entendía como podía calcular los saltos. Pensando en como hacerlo sin guardar nada en la pila se me ocurrió una manera que pensé era la que debían usar ISF. Aunque no era la que usaba, es la que voy a utilizar para el NEXT. De este modo NEXT no pondrá nada en la pila durante la compilación y por consiguiente no interferirá con LEAVE. Esto me permitirá seguir usando LOOP para la compilación. La idea es crear una palabra inmediata nueva que incluya a LOOP en su definición. Así LOOP se encargará de los LEAVE y ya añadiré lo que falta para los NEXT. Algo así como esto:

Código: Seleccionar todo

: MILOOP
  ['] LOOP EXECUTE
  <código para tratar NEXT>
;
IMMEDIATE

continuará...

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 12 Abr 2022 16:56

El método que voy a hacer para NEXT es que, a diferencia de los LEAVE donde cada uno apunta al que le sigue y el último apunta al LOOP, todos los NEXT apuntarán al DO y éste apuntará al LOOP.

Así no tengo necesidad de poner nada en la pila durante la compilación ni de usar ninguna variable de almacenamiento temporal. Por contra, tengo que alterar tanto DO como LOOP para que se manejen con los NEXT.

Como ya comenté, haré unos DO/LOOP modificados (de momento los llamaré NDO/NLOOP) que lo primero que harán es ejecutar el DO/LOOP estándar y a continuación las modificaciones pertinentes. Así mato dos pájaros de un tiro. Por un lado DO/LOOP realiza la compilación de los posibles LEAVE y por otro lado compilan sus Run-Times originales, con lo que NO hay necesidad de crear ninguna versión código máquina de los mismos.

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 12 Abr 2022 22:53

Empezamos con el DO. La versión modificada la llamo NDO

Código: Seleccionar todo

: NDO
 ['] DO EXECUTE
 COMPILE BRANCH ROT 8 + DUP , ROT ROT
 COMPILE BRANCH 2 ALLOT
;
IMMEDIATE


['] DO compila el CFA de DO como un "literal". Cuando NDO se ejecute se pondrá en la pila el CFA de DO que EXECUTE ejecutará. En conjunto es equivalente a un DO estándar. Pero de esta manera puedo añadir más cosas durante la compilación.

Queremos añadir un salto incondicional que apunte al LOOP. Ahora bien, cuando se ejecute el DO tiene que continuar justo después de éste salto. O sea, NO ha de saltar al LOOP sino continuar con lo que hay en el cuerpo del bucle. Para ello ponemos antes otro salto incondicional que apunta justo después del salto incondicional que apunta al LOOP. Veámoslo con un esquema.

nloop2.png
nloop2.png (35.4 KiB) Visto 607 veces

Leyenda: Cada casilla son 2 bytes
s.i. = salto incondicional (BRANCH)
addr. = dirección de memoria

El DO al ejecutarse deja en la pila 3 números como ya expliqué en un post anterior. El más profundo es la dirección del byte justo después del DO. Pero como vamos a añadir 2 saltos incondicionales, hemos de modificarlo para que apunte al final de NDO. Hemos de sumarle 8 (cada salto incondicional son 4 bytes, 2 del CFA del s.i. y 2 de la dirección a donde saltar).

COMPILE BRANCH compila el CFA del salto incondicional. Va seguido de la dirección a la que ha de saltar.

El primer salto es solo para "evitar" el segundo salto (el que apunta al LOOP). La dirección a saltar coincide con la que acabamos de calcular. En el código está mezclada la modificación de la dirección en la 3ª posición de la pila (que colocó el DO) y el colocar una copia del mismo en el "addr." del primer s.i.. Todo esto lo ejecuta la línea:

COMPILE BRANCH ROT 8 + DUP , ROT ROT

Para el segundo salto incondicional reservamos 2 bytes de espacio para que más adelante NLOOP coloque allí la dirección dónde se encuentra el RUN-Time de LOOP:

COMPILE BRANCH 2 ALLOT

El DO y el primer salto incondicional solo se ejecutan una vez. El segundo salto incondicional se ejecuta cada vez que se ejecute un NEXT como ya veremos más adelante.

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 12 Abr 2022 23:33

Vamos ahora con el LOOP. La versión modificada la llamo NLOOP

Código: Seleccionar todo

: NLOOP
 2 PICK ROT ROT
 ['] LOOP EXECUTE
 2- HERE 6 - SWAP !
;
IMMEDIATE


['] LOOP EXECUTE esto es similar al caso del DO del post anterior, pero con LOOP. Tras ejecutarse el LOOP se habrán sacado de la pila los tres números que dejó DO. El salto que calcula LOOP en caso de repetición es el adecuado para NDO pues ya habíamos modificado el valor del tercer número de la pila que dejó DO.

LOOP se compila en dos secciones (como ya expliqué anteriormente). Primero compila su Run-Loop seguido de la dirección dónde ha de saltar cuando se repite el bucle. Luego compila el End-Loop que termina el bucle. Esto se ve en el esquema del post anterior.

Hemos de añadir al LOOP estándar las instrucciones para que coloque en el Operand Field del segundo salto incondicional de NDO la dirección donde se encuentra el Run-Loop. Para ello necesitamos la dirección que calculamos entonces. Pero LOOP la ha quitado de la pila. Tenemos dos opciones:

  • Hacemos una copia antes que se ejecute LOOP
  • La obtemos del Operand Field de Run-Loop

He optado por la primera opción. Eso es lo que hace la primera línea:

2 PICK ROT ROT hace una copia del tercer número de la pila, de modo que ahora también está en la cuarta posición.

La última línea calcula la posición del CFA de Run-Loop y lo guarda en el Operand Field del segundo salto incondicional del NDO

2- HERE 6 - SWAP !

Avatar de Usuario
carlosjuliopr
Mensajes: 553
Registrado: 20 Ago 2012 22:13
Ubicación: Puerto Rico
Agradecido : 225 veces
Agradecimiento recibido: 108 veces

Re: IS-FORTH

Mensajepor carlosjuliopr » 13 Abr 2022 01:35

interesante tema !!!
"We need to build computers for the masses, not the classes",Jack Tramiel -cocbm1

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 13 Abr 2022 12:29

En cuanto a mi elección de la "primera opción" para disponer de la dirección del final del NDO cuando se ejecuta NLOOP que hice en el post anterior, tras pensarlo un poco, creo más adecuado la "segunda opción".

Aunque tras varias pruebas no hay aparentemente ningún problema, pues detecta el caso que se use NLOOP sin ningún NDO previo, no tiene mucho sentido obtener una copia de un valor que puede que no exista. Ya que es precisamente LOOP el que se encarga de comprobar que exista un DO previo pendiente de cerrar y se está ejecutando "después" de crear la copia del tercer número de la pila.

Así que, con la "segunda opción" NLOOP queda así:

Código: Seleccionar todo

: NLOOP 
['] LOOP EXECUTE
HERE 4 - @
2- HERE 6 - SWAP !
;
IMMEDIATE


HERE 4 - @ aquí ponemos en la pila el valor del Operand Field de Run-Loop que es la dirección donde acaba NDO. La siguiente línea no cambia con respecto a la versión anterior.
-------------------------------------------
Respecto al nombre de NDO/NLOOP, se puede cambiar el nombre de NDO a DO y de NLOOP a LOOP y así no podemos cometer el error de usar una mezcla de antiguas y nuevas versiones (DO/NLOOP o NDO/LOOP) pues o todas son las nuevas o todas son las originales.

Además la velocidad de ejecución es prácticamente la misma, pues la única diferencia es que cuando se ejecuta NDO hace un salto incondicional que DO no hace, pero como NDO se ejecuta una sola vez, el sobrecosto es mínimo.

Por lo que sale a cuenta, si se va a utilizar NEXT, definir las nuevas versiones con el mismo nombre de las originales.

OJO: Si usamos los mismos nombres DO/LOOP para la versión nueva, cada vez que cargamos la nueva versión habiendo ya sido cargada antes, la palabra DO se va haciendo más grande cada vez. Se van encadenando saltos incondicionales cada vez que se carga. Funcionar funcionan, pero no es lo óptimo. Así, procurar cargarlas solo una vez si tienen los nombres originales. Este problema no se da si mantenemos nombres diferentes, como NDO/NLOOP por ejemplo. Pero entonces hemos de ir con cuidado de no mezclar nuevos con viejos...

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 13 Abr 2022 17:04

Voy a definir unas palabras que necesitaré para NEXT

INTERNAL_DEPTH devuelve cuantos números hay en la pila de datos pero solo tiene en cuenta los números que se han introducido en la pila desde que se comenzó con la definición de una palabra. Es como una "pila interna". Si se usa fuera de una definición devolverá lo mismo que el DEPTH normal. Normalmente estos números serán los que ponen en la pila DO/IF/BEGIN/ etc. durante su compilación.

Código: Seleccionar todo

: INTERNAL_DEPTH ( - n)
 DEPTH [ BASE @ >R HEX BDFF R> BASE ! ] LITERAL CSP @ - 2/ - 
;

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 13 Abr 2022 22:29

NDO_ADR busca en la "pila interna" (entre los números que se han colocado en la pila desde que se comenzó la definición de una palabra) el primer bloque de tres números que comienza con el número 2 y devuelve el tercero (que será la dirección del final del NDO en curso). Si no lo encuentra da error.

Código: Seleccionar todo

: NDO_ADR ( - addr.)
 INTERNAL_DEPTH DUP 3 <
 ABORT" **NEXT control error." 1- 1 SWAP 1
 DO
  I PICK 2 =
  IF
   DROP I 1+ PICK 0 LEAVE
  THEN
 LOOP
 ABORT" ***NEXT control error."
;

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 13 Abr 2022 23:37

Vamos ya con NEXT

Como ya hemos dicho, cuando se ejecuta NEXT se salta directamente al LOOP sin ejecutar ninguna palabra entre el NEXT y el LOOP. Entonces LOOP actualiza el contador y, según el resultado, repite o no el bucle.

Hay que tener en cuenta que:

  1. Pueden haber varios NEXT en un mismo bucle
  2. Pueden estar dentro de otras estructuras (IF/BEGIN/etc.)

El punto 2 implica que cuando se compila un NEXT el código "2" del NDO correspondiente puede estar más abajo en la pila, no necesariamente el primero como es de esperar cuando se compila un THEN/ELSE/REPEAT/UNTIL/LOOP. De ahí la necesidad de la palabra NDO_ADR para "buscarlo".

La compilación de un NEXT será:

  • Comprueba que existe un código 2 en la pila "interna". Si no lo hay da error, pues significa que no hay ningún NDO abierto
  • Compila un salto incondicional al salto incondicional que hay al final del NDO. La dirección de ese s.i. la calcula a partir de la dirección que NDO dejó en la pila (el tercero de los 3 números que pone)
  • NEXT ni quita ni pone nada en la pila

Código: Seleccionar todo

: NEXT
 NDO_ADR 4 - COMPILE BRANCH ,
;
IMMEDIATE


NDO_ADR se encarga tanto de comprobar que hay un NDO abierto como de poner en la pila la dirección del primer byte justo después del final del NDO.

Si le restamos 4 a esta dirección estamos apuntando al segundo s.i. (como se ve en el esquema de unos post atrás), el que salta al LOOP (concretamente al Run-Loop). Con la "," compilamos esta dirección para que BRANCH salte allí cuando se ejecute el NEXT

Veamos un ejemplo tan sencillo como tonto, pero que muestra el funcionamiento de dos NEXT en un mismo bucle anidado dentro de otro bucle con un NEXT.

Código: Seleccionar todo

: TEST
20 10
NDO
 I 2 MOD 1 =
 IF
  NEXT
 THEN
 CR I .
 10 0
 NDO
  I 2 MOD 0 =
  IF
   NEXT
  THEN
  I 7 =
  IF
   NEXT
  THEN
  I .
 NLOOP
NLOOP
;


Si lo ejecutamos obtenemos el resultado esperado:

test1_nloop.png
test1_nloop.png (8.08 KiB) Visto 551 veces


Solo recordar que NDO/NLOOP es compatible con LEAVE, por lo que se pueden usar conjuntamente NEXTs y LEAVEs dentro de estos bucles.

Dado que normalmente la ejecución de un NEXT se decide con un IF/THEN puede ser útil disponer de ?NEXT que espera un flag en la pila y si es cierto ejecuta un NEXT y si no continúa normalmente.

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 14 Abr 2022 12:11

?NEXT espera en la pila un flag. Si es verdadero se ejecuta un NEXT. Si es falso sigue con la siguiente palabra.

Es equivalente a:

Código: Seleccionar todo

IF
 NEXT
THEN


Su definición es:

Código: Seleccionar todo

: ?NEXT ( flag - )
NDO_ADR 4 - COMPILE NOT COMPILE ?BRANCH ,
;
IMMEDIATE


Como se ve, hemos compilado un salto condicional (?BRANCH) que espera un flag en la pila y hace el salto si es FALSO. Como quiero que el salto lo haga is es VERDADERO, compilamos antes un NOT para invertirlo.

Si no compilamos el NOT, pues se comportaría al revés. A gusto del consumidor...

El ejemplo tonto de antes queda así con ?NEXT:

Código: Seleccionar todo

: TEST2
20 10
NDO
 I 2 MOD 1 =
 ?NEXT
 CR I .
 10 0
 NDO
  I 2 MOD 0=
  ?NEXT
  I 7 =
  ?NEXT
  I .
 NLOOP
NLOOP
;

Elurdio
Mensajes: 510
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 115 veces
Agradecimiento recibido: 95 veces

Re: IS-FORTH

Mensajepor Elurdio » 14 Abr 2022 16:26

Por último, ya para acabar, pongo aquí las definiciones de unas cuantas palabras que me han sido útiles:

Código: Seleccionar todo

: DUMP
    CR   
    8 0 DO
      DUP HEX
      0 <# # # # #S #> TYPE 2 SPACES
      DUP 8 0 DO
        DUP
        C@ 0 <# # #S #> TYPE SPACE 1+
      LOOP DECIMAL  DROP
      8 0 DO
        DUP C@ 127 AND
        DUP 32 < IF
         DROP 32
        THEN EMIT 1+
      LOOP
      CR
    LOOP DECIMAL
;
: H. BASE @ HEX SWAP U. BASE ! ;
: N. BASE @ DECIMAL SWAP U. BASE ! ;
: ?BASE BASE @ DECIMAL DUP . BASE ! ;
: >H BASE @ 32 WORD HEX NUMBER
 SWAP BASE ! ;
: >N BASE @ 32 WORD DECIMAL NUMBER SWAP BASE ! ;   


NOTA: Cuando digo "decimal" me refiero a un número entero en base 10.

DUMP la versión corregida del volcador hexadecimal del manual del IS-FORTH
H. toma un número de la pila y lo imprime como número hexadecimal (unsigned) sin afectar a la base actualmente en uso.
N. toma un número de la pila y lo imprime como número decimal (unsigned) sin afectar a la base actualmente en uso.
?BASE imprime la base actual como número decimal sin afectar a la base actualmente en uso.
>H toma el número hexadecimal que le sigue y lo coloca en la pila. NO afecta a la base actual.
>N toma el número decimal que le sigue y lo coloca en la pila. NO afecta a la base actual.


Volver a “Software Enterprise”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado