Jupiter Ace Forth: Utilidades

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 18 Feb 2022 11:41

Último mensaje de la página anterior:

Elurdio escribió:···El número se ha de dar en el momento de la compilación, de ahí el uso de los corchetes. No se puede usar ningún número que estuviera en la pila antes de empezar la definición de una palabra, pues el JA usa un sistema de comprobación que no lo permite (el JA comprueba que la pila queda igual al final de la compilación que antes, si no es así, da error #5)···


Explico en detalle las comprobaciones que hace el JA si usamos palabras compilantes "primitivas" (DO/LOOP, BEGIN/UNTIL, etc.) cuando definimos una nueva palabra:

Veámoslo con un ejemplo:

Supongamos que queremos definir esto:

Código: Seleccionar todo

: BUCLE 5 0 DO I . LOOP ;

¿Que hace el JA durante la compilación de esta palabra tras pulsar el ENTER?

Para verlo sin que se altere la definición, lo haremos así:

Código: Seleccionar todo

: BUCLE [ .S ] 5 0 DO [ .S ] I . LOOP [ .S ] ;

.S es una palabra que nos muestra el contenido de la pila sin modificarla (ver manual del JA pág 143). Tras pulsar ENTER nos saldrá en pantalla (con VIS activado):

Código: Seleccionar todo

: bucle [ .s 10 ] 5 0 do [ .s 10 21313 3 ] i . loop [ .s 10 ] ;

Así vemos que (haciendo esto con la pila vacía y comprobando que sigue vacía después del ENTER) :

  • ":" planta un 10 en la pila
  • "DO" planta el valor de HERE (*) y un 3
  • "LOOP" usa el valor del HERE tras DO y quita el 3 que plantó DO
  • ";" quita el 10 que plantó ":"

Deducimos que el JA comprueba que:

  • El 10 que planta al principio de la definición sigue estando al final de la misma.
  • Cuando se compila el LOOP tiene que haber un 3 en la pila.

Para otras palabras compilantes como :BEGIN, IF/ELSE y WHILE los códigos son 1, 2 y 4 respectivamente.
Todo esto permite al JA comprobar que las compilantes están pareadas (no hay DOs sin LOOPs, etc) y bien anidadas.

Para qué nos sirve saber esto:

  • Si al programar ULOOP hubiera seguido este protocolo entonces el JA se encargaría de comprobar que está todo bien (pareado/anidado).
  • Podría usar ULOOP con DO (no necesitaría el UDO)
  • Podemos engañar al JA para que nuestras compilantes puedan tomar números de la pila (**).
  • Etc.

(*) Que 21313 es el valor del HERE en ese momento, se comprueba de esta manera:

Código: Seleccionar todo

: bucle [ .s ] 5 0 [ here .s drop ] do [ here .s drop ] i . loop [ .s ] ;
que dará este resultado
: bucle [ .s 10 ] 5 0 [ here .s 10 21311 drop ] do [ here .s 10 21313 3 21313 drop ] i . loop [ .s 10 ] ;

(**) Podemos, por ejemplo, utilizar números de la pila (que estaban antes de empezar la definición) siempre y cuando nos aseguremos que antes que se compile el ";" haya un 10 en la pila... etc.

NOTA: Al preparar este post me he dado cuenta que LEAVE no se comprueba, por lo que ULEAVE no es es necesaria. Se puede usar LEAVE en un bucle UDO/ULOOP.
-------------------------------------------------------------------
He aquí una modificación de ULOOP que se ajusta a lo expuesto. Esta versión funciona con DO sin problemas.

Código: Seleccionar todo

2 COMPILER ULOOP
 3 =
 IF
  HERE - ,
 ELSE
  5 ERROR
 THEN
RUNS>
 ULOOPMC
;

ERROR toma un número n de la pila, imprime ERROR n en pantalla y realiza un ABORT.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 20 Feb 2022 00:42

Otra palabra compilante: NEXT

NEXT se utiliza dentro de un bucle DO/ULOOP. Lo que hace es saltarse todas las palabras que le siguen e ir directamente al ULOOP.

Ejemplo:

Código: Seleccionar todo

: BUCLE
 5 0
 DO
  I DUP + I 3
  =
  IF
   DROP NEXT
  THEN
  .
 ULOOP
;
Imprrmirá: 0 2 4 8 saltando directamente a ULOOP cuando I=3 sin imprimir el 6.


Un uso interesante es la combinación LEAVE NEXT pues lleva directamente a terminar el bucle sin ejecutar más palabras.

Esta compilante de usuario se ajusta al protocolo JA y usa el código 5. La parte compilante es más compleja que las vistas hasta ahora por dos motivos:
  • Puede haber más de un NEXT en un mismo bucle DO/ULOOP
  • El NEXT puede estar dentro de otras estructuras compilantes que no estarán aún cerradas cuando se compile éste.

Veamos otro ejemplo:

Código: Seleccionar todo

: BUCLE
 5 0
 DO
  I DUP + I 3
  =
  IF
   0 0=
   IF
    DROP NEXT
   THEN
  THEN
  .
  BEGIN
   I . NEXT
  UNTIL
  128 .
  ULOOP
;

Imprimirá: 0 0 2 1 4 2 8 4 El UNTIL no se ejecuta nunca (si lo hiciera daría error pues la pila estaría vacía) y el 128 no se imprime nunca.

Usaremos el ULOOP compilante con protocolo JA y modificamos su parte compilante para que pueda manerjar los NEXT. Dado que tanto NEXT como este ULOOP se ajustan al protocolo JA podemos usar el DO, no necesitamos el UDO.

NEXT durante la compilación tiene que:

  1. Encontrar un código 3 en la pila. Si no encuentra ninguno significa que está fuera de un bucle y dará error #5 y se aborta la definición
  2. El primer código 3 encontrado tendrá debajo la dirección de memoria que apunta después del DO que lo puso. Entonces NEXT tiene que poner debajo de esa dirección su código 5 y debajo de éste la dirección de memoria del byte después del NEXT.
  3. A continuación compilará un 0 (con ",") para que más tarde ULOOP ponga allí los bytes que hay desde el NEXT hasta el ULOOP.

Si hubiesen varios NEXT en un mismo bucle se irían compilando uno delante del otro y todos debajo del 3/HERE del DO:

(top pila)
·
·
3
HERE que puso el DO
5
HERE del 2º NEXT
5
HERE del 1º NEXT
·
·
·
10 (final pila de compilación)

Para el paso 1 he definido la palabra SEARCH>10 que devuelve la posición del primer 3 en la pila. Si no encuentra ninguno antes de encontrar un 10 (recordemos que el JA planta un 10 en la pila al comenzar la definición de una palabra) entonces devuelve un 0.

Para el paso 2 he definido la palabra ENCRUST que intercala un número en la posición de la pila que se le indique. Utiliza la palabra ROLLD (*) que equivale al ROLL pero en sentido contrario (mueve la primera posición de la pila a la indicada y sube el resto una posición).

El paso 3 es solo hacer: 0 ,

NEXT

Código: Seleccionar todo

2 COMPILER NEXT
 3 SEARCH>10 DUP 0=
 IF
  5 ERROR
 THEN
 1+ DUP >R 5 SWAP
 ENCRUST HERE R> 1+ ENCRUST
 0 ,
RUNS>
 DUP @ + R> DROP
 >R
;



Durante la ejecución, NEXT pone en la pila al dirección de su OF (operand field) que son 2 bytes justo a continuación, dónde ULOOP almacenó durante la compilación el salto desde aquí (NEXT) hasta el ULOOP. Con este dato realiza el salto a ULOOP. Luego el ULOOP se ejecuta normalmente, aumentado el contador y viendo si se ha de repetir o no el bucle...

ULOOP durante la compilación tiene que:

  1. Comprobar que la primera posición de la pila es un código 3 (del DO pendiente de cerrar). Si no ERROR #5 y se aborta la definición.
  2. Eliminar de la pila el código 3 encontrado (cerrar el bucle) y usando la siguiente posición (que contiene la dirección del bytes después del DO) calcular el desplazamiento relativo para saltar hasta el DO desde el ULOOP cuando se repite el bucle y almacenarlo a continuación del ULOOP.
  3. Comprobar si existen uno o más NEXT a continuación. Por cada uno que encuentre tiene que eliminar el código 5 encontrado (cerrar el NEXT) y usando la siguiente posición en la pila (que contiene la dirección del byte después del NEXT) calcular el desplazamiento relativo para saltar desde el NEXT hasta el ULOOP y almacenarlo a continuación del NEXT.

Los pasos 1 y 2 son los mismos que ya hacía antes de existir NEXT.
El paso 3 utiliza un bucle BEGIN/WHILE/REPEAT para detectar los NEXTs. Este bucle se detiene en cuanto encuentra un código distinto de 5. Si hubiera otro DO/LOOP con NEXTs también, quedarían separados por al menos un código distinto de 5 y no se mezclarían.

ULOOP

Código: Seleccionar todo

2 COMPILER ULOOP
 3 = 0=
 IF
  5 ERROR
 THEN
 HERE - ,
 BEGIN
  DUP 5 =
 WHILE
  DROP DUP HERE SWAP -
  4 - SWAP !
 REPEAT
RUNS>
 ULOOPMC
;


El funcionamiento de ULOOP durante la ejecución es el mismo que antes. No cambia con la introducción del NEXT.

Programa Completo:

Código: Seleccionar todo

DECIMAL

BASE C@ DECIMAL 16 BASE C!
CREATE 2*
 DF C, CB C, 23 C, CB C, 12 C,
 D7 C, FD C, E9 C,
BASE C! 2* DUP 2- !

BASE C@ DECIMAL 16 BASE C!
CREATE ULOOPMC
 21 C, 04 C, 00 C, 39 C, 34 C,
 20 C, 04 C, 23 C, 34 C, 18 C,
 01 C, 23 C, 23 C, 5E C, 23 C,
 56 C, EB C, F1 C, C1 C, D1 C,
 D5 C, C5 C, F5 C, B7 C, ED C,
 52 C, 28 C, 0F C, 38 C, 0D C,
 DF C, EB C, 4E C, 23 C, 46 C,
 2B C, 09 C, C1 C, D1 C, E5 C,
 C5 C, FD C, E9 C, C1 C, D1 C,
 E1 C, E1 C, D5 C, C5 C, DF C,
 FD C, E9 C,
BASE C! ULOOPMC DUP 2- !


: DEPTH
 15419 @ HERE 12 +
 - 2 /
;
 
: ERROR
 15421 C! ABORT
;
 
: ROLLD
 ( n- )
 DUP 2 <
 IF
  DROP EXIT
 THEN
 DUP DEPTH 2- >
 IF
  2 ERROR
 THEN
 SWAP >R DEPTH OVER -
 2* HERE + 12 +
 DUP >R OVER 1- 2*
 + DUP ROT 1- 0
 DO
  DUP 2- SWAP OVER @
  SWAP !
 LOOP
 DROP R> R> SWAP !
;
 
: ENCRUST
 ( n,i- )
 1+ ROLLD
;
 
: SEARCH>10
 ( n-n)
 DEPTH 1+ 2
 DO
  I PICK 10 =
  IF
   DROP 0 LEAVE
  ELSE
   I PICK OVER =
   IF
    DROP I 1- LEAVE
   THEN
  THEN
 LOOP
;
 
2 COMPILER ULOOP
 3 = 0=
 IF
  5 ERROR
 THEN
 HERE - ,
 BEGIN
  DUP 5 =
 WHILE
  DROP DUP HERE SWAP -
  4 - SWAP !
 REPEAT
RUNS>
 ULOOPMC
;
 
2 COMPILER NEXT
 3 SEARCH>10 DUP 0=
 IF
  5 ERROR
 THEN
 1+ DUP >R 5 SWAP
 ENCRUST HERE R> 1+ ENCRUST
 0 ,
RUNS>
 DUP @ + R> DROP
 >R
;
 


(*) La definición de ROLLD la he hecho toda en FORTH y me ha salido muy lenta con el enfoque que he usado. Pero como se va a usar solo durante la compilación, no importa.
Ahora bien, si se piensa utilizar el ROLLD a menudo para otros propósitos, sería interesante optimizarla o, mejor incluso, hacerla en código máquina.
Para que se vea lo lenta que es, la comparo con la primitiva ROLL que en el fondo es la mismo, solo cambia el sentido de la rotación:

Tiempo en segundos para 1000 vueltas: (FAST/SLOW)
10 ROLLD: 26.9/45.1
10 ROLL : 0.49/0.67

Estamos hablando de unas 60 veces más rápido ROLL que ROLLD.
Adjuntos
uloopnext.TZX
(652 Bytes) Descargado 11 veces

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 20 Feb 2022 10:32

Visto lo muy rápido que es ROLL respecto a mi ROLLD, se podía haber hecho una versión más sencilla sabiendo que:

n ROLLD es equivalente a repetir n-1 veces n ROLL

El equivalente sería:

Código: Seleccionar todo

: ROLLD
 ( n- )
 DUP 2 <
 IF
  DROP EXIT
 THEN
 DUP DEPTH 2- >
 IF
  2 ERROR
 THEN
 DUP 1+ SWAP 1- 0
 DO
  DUP ROLL SWAP
 LOOP
 DROP
 ;


los tiempos en segundos para mil vueltas para 10 ROLLD
Antes: 26.9/45.1
Ahora: 13.4/20.3

NOTA: Válido para n pequeño, pues uno es O(n) y el que usa ROLL es O(n^2)

EDITO: (23-2-2022) Hablando de DEPTH en un hilo posterior a éste, he visto que si se cambia en la definición de DEPTH el 2 / por la palabra 2/ en código máquina, aumenta mucho la velocidad de DEPTH. De hecho, probando la nueva versión de DEPTH con ésta de ROLLD:

los tiempos en segundos para mil vueltas para 10 ROLLD
Antes: 13.4/20.3
Ahora: 9.1/13.6
Por lo que se ve el Overhead que causaba el DEPTH.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 20 Feb 2022 23:37

ROLLD en código máquina (c.m.)

He hecho la versión c.m. de ROLLD "fusilando" todo lo posible el c.m. de ROLL de la ROM del JA. De ahí que los tiempos de ejecución sean "casi clavados". Lo único que con n=1 se me colgaba y por no hacer el debug, opté por comprobar el caso n=1 y si es así, no hacer nada (1 ROLLD deja la pila igual).

Como tengo por costumbre, pongo el fuente para el assembler/disassembler de Boldfield (ADB) (*)

Código: Seleccionar todo

code rolld
     \ call NN      \ h 084E ))    { BC = stack(1)}
     \ or a         \
     \ ld hl,NN     \ h 1 ))       { Si n=1 no hace nada}
     \ sbc hl,bc    \
     \ jr z,DIS     \ ~ b
     \ dec bc       \
     \ sla c        \              { Comprueba BC no sea 0 o negativo y}
     \ rl b         \              { hace BC = 2*BC}
     \ inc bc       \
     \ inc bc       \
     \ jr c,DIS     \ ~ a          { Error 7}
     \ ld hl,(NN)   \ h 3C3B ))    { SPARE: byte después Top of Stack}
     \ sbc hl,bc    \              { HL=addr. byte donde rotar}
     \ ex de,hl     \
     \ ld hl,(NN)   \ h 3C37 ))    { HL=HERE}
     \ sbc hl,de    \
     \ jp nc,NN     \ h 04D7 ))    { Error 2}
     \ rst24        \              { DE=stack(1)}
     \ push de      \
     \ ld hl,(NN)   \ h 3C3B ))    { SPARE: byte después Top of Stack}
     \ ld d,h       \
     \ ld e,l       \
     \ inc de       \
     \ dec hl       \
     \ dec bc       \
     \ dec bc       \
     \ lddr         \
     \ pop de       \              { Guardo stack(1) en punto rotacion }
     \ inc hl       \
     \ ld(hl),e     \
     \ inc hl       \
     \ ld(hl),d     \
     \ ld hl,(NN)   \ h 3C3B ))    { Actualizo SPARE}
     \ inc hl       \
     \ inc hl       \
     \ ld(NN),hl    \ h 3C3B ))
 ` b \ jp(iy)       \
 ` a \ rst32        \
      07
endc



Tiempo en segundos para 1000 vueltas: (FAST/SLOW)
10 ROLLD: 0.50/0.68
10 ROLL : 0.49/0.67

(*) Al principio los tecleaba directamente en el JA (EightyOne) con el ADB cargado y tal. Pero hace ya un tiempo que lo tecleo en el bloc de notas de Windows y luego lo cargo con el cargador de ficheros fuentes ("cargadorf", el que se autoborra, cargado encima del ADB). Mucho más cómodo y rápido así.
Adjuntos
rolldmc.TZX
(163 Bytes) Descargado 11 veces

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 22 Feb 2022 13:01

Al poco de redescubrir el JA tras encontrar casualmente un emulador para el iPad, me fijé como primer objetivo añadir al diccionario por defecto las palabras del FORTH-79 de las que carece el JA Forth. El mismo manual menciona unas cuantas (pág-176):

Algunas se definen en algún punto del JA y otras no. Iré comentando las que hice. Empezamos con:

' (tecla 7 + tecla Symbol Shift) en el teclado del JA
Se pronuncia "tick". Según el manual del JA devuelve la dirección del PF (Parameter Field) de la palabra que le sigue, muy similar a FIND que devuelve el CFA: (pag-122 del manual)

Código: Seleccionar todo

: '
FIND 2+
;

Pues como ya sabemos, el PF de cualquier palabra de usuario empieza justo después del CF (Code Field) que ocupa 2 bytes, de ahí el +2
Ahora bien, en FORTH-79 es algo diferente:
  • Modo ejecución: Coincide con lo dicho en el JA. Devuelve el PFA (dirección del PF = PF Address = PFA) de la palabra que le sigue.
  • Modo compilación: Compila el PFA de la palabra que le sigue como número (internamente es un LITERAL).
Tick es una palabra que uso muchísimo, pero la verdad, solo en modo ejecución. Hasta la fecha no la he usado nunca dentro de la definición de una palabra, por lo que entiendo que el manual del JA la defina así.

Veamos la diferencia con un ejemplo:

Versión JA:

Código: Seleccionar todo

: TEST ' DIVR ;
se genera la palabra TEST con la definición tal cual se escribió. Si ahora hacemos:

Código: Seleccionar todo

TEST HEX
nos dejará en la pila el PFA de HEX y luego se ejecutará DIVR.

Versión F-79:

Código: Seleccionar todo

: TEST ' DIVR ;
se genera la palabra TEST siguiente:

Código: Seleccionar todo

: TEST n ;
donde n es el valor del PFA de DIVR. Si ahora hacemos:

Código: Seleccionar todo

TEST HEX
nos dejará en la pila el PFA de DIVR y luego se ejecutará HEX.

Como se ve, el comportamiento de la palabra TEST es diferente según la versión que usemos.

Veamos su definición ajustada al Forth-79:

Código: Seleccionar todo

: '
 FIND DUP 0=
 IF
  13 ERROR
 THEN
 2+ STATE
 IF
  4113 , ,
 THEN
;
IMMEDIATE

  • ERROR toma un número n de la pila, imprime en pantalla ERROR n y realiza un ABORT.
  • STATE devuelve 1 si estamos en modo compilación, 0 en caso contrario. Lo que hace es comprobar el valor del bit número 6 del byte del sistema FLAGS (#3C3E/15422) (pag 144 del manual del JA)
  • 4113 es #1011, el CF de LITERAL.

Código: Seleccionar todo

: ERROR
 15421 C! ABORT
;
 
: STATE
 15422 C@ 64 AND
;


NOTA: STATE es también una de las palabras del FORTH-79 de las que carece el Jupiter Ace.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 22 Feb 2022 23:40

-TRAILING es otra palabra del FORTH-79 de la que el JA carece. En el manual del JA (MJA) página 100 se propone como ejercicio su definición.

Según FORTH-79:

-TRAILING
( addr,n1 - addr,n2)
Adjust the character count n1 of a text string beginning at
addr to exclude trailing blanks, i.e., the characters at
addr+n2 to addr+n1-1 are blanks. An error condition exists if
n1 is negative.

Código: Seleccionar todo

: -TRAILING
 ( addr,n1 - addr,n2)
 DUP 0<
 IF
  15 ERROR
 THEN
 OVER OVER + SWAP -1
 DO
  1- DUP C@ 32 =
  0=
  IF
   LEAVE
  THEN
 LOOP
 OVER - 1+
;

  • Funciona empezando a mirar por el final de la cadena de caracteres y retrocediendo hasta encontrar el primero distinto de espacio.
  • Esta definición NO sirve si la palabra empieza en la primera posición de memoria (#0000) pero eso es zona ROM.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 23 Feb 2022 11:02

En la última actualización del Jupiter Ace Archive han incluido una copia del artículo Forth' Demo two que Gil Filbey escribió en la revista Computer Age en 1980.
Me ha hecho gracia la inclusión de un programa con la calculadora programable HP-29C que fue precisamente mi primer contacto con la programación allá por 1979 . La "C" era porque disponía de memoria Continua, o sea, que los programas y datos no se borraban al apagar la calculadora como era habitual por aquellas fechas.
Siempre he pensado que mi atracción por FORTH viene de mi contacto previo con las calculadoras HP y su lógica RPN.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 23 Feb 2022 17:06

CMOVE y MOVE son otras palabras de Forth-79 de las que carece el JA. En la página 100 del manual del JA propone como ejercicio su definición en Forth.

Sirven para mover bloques de memoria medidos en bytes (CMOVE) o palabras de 16 bits (MOVE). Según Forth-79:

CMOVE
(addr1,addr2,n - )
Move n bytes beginning at address addr1 to addr2. The
contents of addr1 is moved first proceeding toward high
memory. If n is zero nothing is moved.


En Forth-79 hay otra palabra <CMOVE que hace lo mismo pero al revés, de memoria alta hacia baja.

Como quiera que MOVE no es más que un caso especial de CMOVE, nos centramos en CMOVE.

CMOVE programada en FORTH es adecuada para bloques pequeños. Si éstos son muy grandes (decenas de miles de bytes) la cosa es muy lenta y es mejor programarlas en código máquina. En cualquier caso hay que tener en cuenta (ya se indica en el manual) que si los bloques se solapan, hay que escoger el sentido del movimiento de los bytes adecuadamente para no sobreescribirlos antes de moverlos.

En mi caso opté por crear dos palabras programadas en código máquina: DWMOVE y UPMOVE moviendo la primera los bytes en orden descendente y la segunda en orden ascendente. Luego defino CMOVE en Forth que es la que en función del posible solapamiento decide cual de las dos usar y calcula el principio y el fin adecuados. Este CMOVE viene a ser una "unificación" del CMOVE y <CMOVE del Forth-79 en una sola palabra CMOVE

Código: Seleccionar todo

: CMOVE
 (addr1,addr2,n - )
 ?DUP 0=
 IF
  DROP DROP DROP EXIT
 THEN
 ROT ROT OVER OVER U<
 IF
  3 PICK 1- + SWAP
  3 PICK 1- + SWAP
  ROT DWMOVE
 ELSE
  ROT UPMOVE
 THEN
;


Las palabras en código máquina:
DWMOVE

Código: Seleccionar todo

4E9B DF          RST $18
4E9C D5          PUSH DE
4E9D C1          POP BC
4E9E DF          RST $18
4E9F D5          PUSH DE
4EA0 DF          RST $18
4EA1 EB          EX DE,HL
4EA2 D1          POP DE
4EA3 ED B8       LDDR
4EA5 FD E9       JP IY

UPMOVE

Código: Seleccionar todo

4EB4 DF          RST $18
4EB5 D5          PUSH DE
4EB6 C1          POP BC
4EB7 DF          RST $18
4EB8 D5          PUSH DE
4EB9 DF          RST $18
4EBA EB          EX DE,HL
4EBB D1          POP DE
4EBC ED B0       LDIR
4EBE FD E9       JP IY

Ambas rutinas esperan en la pila la dirección inicial, la dirección final y el número de bytes a mover. Luego utilizan la instrucción LDDR o LDIR respectivamente que se encargan de mover los bloques de memoria.
RST 24 (o RST 18 en Hexadecimal) lee el primer número de la pila y lo coloca en el registro DE.
LDDR/LDIR esperan:
HL=Inicio
DE=Final
BC=Número de bytes a mover
JP IY equivale a JP(IY) que es como terminan las rutinas en c.m. en el JA.

Si se opta por programar DWMOVE y UPMOVE en FORTH, cuidado si se usa un bucle DO/LOOP para recorrer uno de los bloques (con las direcciones de memoria de un bloque como índice), pues estos tratan los límites como números con signo. En general no pasa nada si el bloque que se recorre con DO/LOOP está íntegramente dentro de zona "negativa" o "positiva". El problema es si este bloque empieza en zona positiva y acaba en zona negativa. Solo se moverá un bytel. Para que esto no suceda habría que descomponer el bloque DO/LOOPizado en dos partes, una íntegramente en zona positiva y la otra íntegramente en zona negativa.

En cuanto a los tiempos, si van a mover 20.000 bytes y eso no significa disponer de 40.000 o más bytes de memoria, puede ser algo tan simple como mover el bloque 2 bytes más abajo por la razón que sea o hacer 200 movimientos de 100 bytes:

En FORTH: tarda del orden de 10/20 segundos. (FAST/SLOW)
En c.m.: tarda menos de una décima de segundo.

Este es uno de los pocos casos en FORTH en que el uso del c.m. es casi obligatorio.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 23 Feb 2022 22:43

Elurdio escribió:···Este es uno de los pocos casos en FORTH en que el uso del c.m. es casi obligatorio.

Otro caso también carne de código máquina es FILL

JA manual pag 130 escribió:A related word (again not available on the Ace) is FILL (address, n, byte –) which
fills the memory starting at the given address with n copies of the given byte


Se aplica la misma precaución comentada en el post anterior si se programa en FORTH recorriendo el bloque con DO/LOOP.

Esta fue una de las primeras palabras que quise tener en código máquina para mi FORTH desde que retomé el tema hace escasos meses. Como tenía muy olvidado el assembler del Z80, opté por buscar entre la abundante información del Jupiter Ace Archive y lo encontré aquí:

Ace User. Newslleter of the Jupiter Ace User's Club
no. 4: Winter 1983 pag-17 (G.R. Yorke)

nota AceUser-4-1983.jpg
nota AceUser-4-1983.jpg (47.35 KiB) Visto 883 veces

Incluía también el código para un CMOVE como el que hice pero 100% código máquina, aunque no funcionó bien (o lo copié mal) por lo que me decidí a repasar Z80 assembler. De todos modos el FILL que tengo puesto es éste de Yorke.

FILL
(addr,n,byte –)

Código: Seleccionar todo

44B6 DF          RST $18
44B7 7B          LD A,E
44B8 DF          RST $18
44B9 D5          PUSH DE
44BA C1          POP BC
44BB DF          RST $18
44BC EB          EX DE,HL
44BD 0B          DEC BC
44BE CB 78       BIT 7,B
44C0 20 04       JR NZ,$44C6
44C2 77          LD (HL),A
44C3 23          INC HL
44C4 18 F7       JR $44BD
44C6 FD E9       JP IY


NOTA: Tal como dice el autor de la rutina, si el número de veces a copiar es cero o negativo, no hace nada, por lo que el valor máximo de n es 32761.

EDIT: (24-2-2022) Disponiendo de UPMOVE se podría programar FILL así:
Se coloca el byte a repetir en la primera posición y entonces se aplica UPMOVE al bloque con esta dirección inicial y como dirección final la misma más uno y tamaño las veces que se iba a repetir menos uno.

Código: Seleccionar todo

: FILL
 ( addr,n,c- )
 OVER 0=
 IF
  DROP DROP DROP EXIT
 THEN
 OVER 1 =
 IF
  SWAP DROP SWAP C! EXIT
 THEN
 3 PICK C! OVER 1+
 SWAP 1- UPMOVE
;

  • Los casos n=0 y n=1 los trata por separado
  • Permite n mayor que 32671 (n unsigned)
  • A pesar del overhead del código en FORTH, es más rápida que la versión anterior para n grande.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 23 Feb 2022 23:42

Otras palabras del FORTH-79 de las que carece el JA y mencionadas en el manual son:

COUNT Se define y explica en el manual (pag-99)
DEPTH Devuelve la cantidad de números que hay en la pila.
( - n)

Código: Seleccionar todo

: DEPTH
 15419 @ HERE 12 +
 - 2 /
;

  • 15419 dirección de memoria dónde se almacena la dirección del primer byte pasada la parte superior de la pila. V. sistema SPARE
  • La pila empieza 12 bytes después del final del diccionario (HERE).
  • Cada número ocupa dos bytes de la pila.

NOTA: La parte más lenta de DEPTH es precisamente la división por 2. Si en lugar de usar la división genérica por el número 2, utilizamos la palabra 2/ en código máquina (que hace una división concreta por dos, mucho más rápida que la genérica), la velocidad de ejecución de DEPTH se multiplica por 5 (400%)

2/
(n - n/2)

Código: Seleccionar todo

4145 DF          RST $18
4146 CB 2A       SRA D
4148 CB 1B       RR E
414A D7          RST $10
414B FD E9       JP IY

RST$18 (RST 24 en decimal) DE = primer número de la pila
RST$10 (RST 16 en decimal) coloca en la pila el valor de DE
JP IY (equivale a JP (IY) en notación estándar) Fin de rutina.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 25 Feb 2022 11:54

Otra palabra del Forth-79 de la que carece el JA:
COMPILE
Aparte de incluirla en la lista, el manual del JA no habla para nada de ella.
Según Forth-79:

COMPILE
When a word containing COMPILE executes, the 16-bit value
following the compilation address of COMPILE is copied
(compiled) into the dictionary. i.e., COMPILE DUP will copy
the compilation address of DUP.
COMPILE [ 0 , ] will copy zero.

La he definido así:

Código: Seleccionar todo

2 COMPILER COMPILE
RUNS>
 @ ,
;


Esta palabra se comporta cara a la edición/listado como otras por el estilo (veáse 2LITERAL en el manual pags 137-139): Al listarla/editarla la palabra que seguía a COMPILE al definir la palabra ha desaparecido. Si se edita la palabra hay que volver a escribirla. Normalmente se pone un recordatorio dentro de un comentario entre paréntesis.

Ejemplo:

Código: Seleccionar todo

: TEST ( DUP) SWAP COMPILE DUP * ;


Si la listamos veremos

Código: Seleccionar todo

: TEST
 ( DUP)
 SWAP COMPILE *
;


Esto es así, porque la dirección de compilación de DUP (su CFA) es ahora el OF (operand field) de COMPILE y no se muestra. Esto es necesario porque si no, cuando se mueve la palabra TEST (al usar LOAD o REDEFINIR palabras) el JA sabe que esos dos bytes no se pueden modificar, son datos de COMPILE. Por contra, como el JA no sabe que significan, los ignora y no los lista.
El problema es si editamos la palabra, pues el JA la descompila y, por lo anteriormente dicho, ignora el OF de COMPILE. Si ahora le damos al ENTER sin hacer nada, la nueva palabra sería:

Código: Seleccionar todo

: TEST
 ( DUP)
 SWAP COMPILE 
;


COMPILE tomaría el "*" que viene después de ella y lo convertiría en su OF. Es por eso que antes de pulsar ENTER hay que volver a escribir el DUP a continuación del COMPILE. De ahí el recordatorio entre paréntesis.

El Jupiter Ace, debido a su capacidad de descompilar palabras de su diccionario para poderlas listar/editar/redefinir/etc., es muy particular en varios aspectos y éste es uno de ellos.
Ya hablaré en otro post de esta gran diferencia del Ace Forth con respecto a otros. Sus ventajas, inconvenientes y "alternativas".

Aplicaciones de COMPILE
No tenía muy claro para que podría servir en la práctica esta palabra. Encontré un ejemplo en Starting Forth de Leo Brodie (1ª edición pág 301). Allí la utiliza para mostrar como definiríamos DO si no dispusiéramos de él:

Código: Seleccionar todo

: DO COMPILE 2>R HERE ; IMMEDIATE

2>R es la palabra que hace lo que se espera de DO, o sea, poner en la pila de retornos el límite y el índice del bucle. Aquí se ve que esta manera de hacerlo en el JA no va bien:
  • Cuando listamos una palabra que contiene este DO nos mostraría 2>R en su lugar
  • Si editamos la palabra que contiene el DO, tenemos que borrar el 2>R y escribir DO de nuevo, a mano

Esta manera de definir DO funciona muy bien en sistemas de Forth que NO pueden listar/editar/redefinir palabras en el diccionario (lo normal en Forth) pero no en el Jupiter Ace. Como dije antes, ya hablaré de este tema en otro post/hilo más adelante.

En Ace Forth esta palabra DO se definiría con COMPILER/RUNS>

Código: Seleccionar todo

2 COMPILER DO
 HERE
RUNS>
 2>R
;

y no daría los problemas de la versión con COMPILE.

  • Si se edita/lista la palabra que contiene este DO se muestra el DO
  • Si se edita la palabra que lo contiene no hay que modificar/escribir nada, ya se encarga ella de todo.

NOTA: A diferencia de 2LITERAL (y similares) que no contiene instrucciones en la parte compilante de su definición (la zona comprendida entre el nombre y RUNS>) el DO definido por nosotros tiene ahí HERE que coloca un número en la pila, por lo que no tenemos que escribirlo nosotros a mano.

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 25 Feb 2022 15:32

Otra palabra del Forth-79 de la que carece el JA:
[COMPILE]
Al igual que la anterior, aunque la incluye en la lista, el manual del JA no habla para nada de ella.
Según Forth-79:

[COMPILE]
Used in a colon-definition in the form:
[COMPILE] <name>
Forces compilation of the following word. This allows
compilation of an IMMEDIATE word when it would otherwise be
executed.

Sirve para forzar la compilación de palabras que, por ser inmediatas, se ejecutarían durante la compilación.

La defino así:

Código: Seleccionar todo

: [COMPILE]
 FIND DUP 0=
 IF
  13 ERROR
 THEN
 ,
;
IMMEDIATE


Un ejemplo muy interesante de aplicación de [COMPILE] es éste (*)

En un post anterior hablé de la palabra ' (tick) que en su versión Forth-79 se comporta diferente según la situación:

  • En ejecución devuelve el PFA de la palabra que le sigue en el búfer de entrada
  • En compilación compila como número el PFA de la palabra que le sigue en el búfer de entrada

Mientras que en su versión JA, en modo compilación simplemente se compila.

Que la versión Forth-79 se comporte como la JA al compilar lo podemos conseguir mediante [COMPILE]

Ej:

Código: Seleccionar todo

: TEST
 [COMPILE] ' DUP
;


Si ejecuto TEST HEX me pondrá en la pila dos copias del PFA de la palabra HEX tal como haría la definición de ' del manual del JA.

Si no hubiéramos precedido ' con [COMPILE] en la definición de TEST el resultado sería otro:
TEST HEX nos pondría una copia del PFA de DUP en la pila y luego se ejecutaría HEX

En resumen, gracias a [COMPILE] podemos escoger en cada momento como queremos que se comporte ' en modo compilación: O según Forth-79 o según la versión JA.

(*) ver Starting Forth de Leo Brodie Apéndice 3-2 (1ª edición)

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 27 Feb 2022 23:36

Detección de varias teclas pulsadas a la vez

Buscando información para detectar combinaciones de teclas simultáneas para "activar" funciones de una rutina de interrupción propia, he dado con esto que me parece interesante tratar aquí.

He leído en Ace Users 3 (1983) (páginas 12 y 13) un método para leer pulsaciones de teclas independientemente de si están pulsadas otras a la vez. Muy útil en juegos. Pues como ya sabemos, la palabra INKEY del Ace Forth devuelve cero si hay varias teclas pulsadas a la vez (salvo teclas auxiliares).

Listado adaptado para generar READ y KEY al cargarlo con assembler/disassembler de Boldfield (ADB).
(afortunadamente el documento escaneado está OCR'ed y lo he podido copiar. He tenido que hacer algunas correciones: unos que eran eles, ceros que eran oes, etc.)

Código: Seleccionar todo


code read
 h
 43 53 7A 78 63 61 73 64
 66 67 71 77 65 72 74 31
 32 33 34 35 30 39 38 37
 36 70 6F 69 75 79 0D 6C
 6B 6A 68 20 6D 6E 62 76
 FE FD FB F7 EF DF BF 7F
endc

decimal

4076 find read !    { la convierte en tipo CREATE}

code key
 h
 DF D5 DF E1 E5 7B 06 08
 0E 05 16 01 BE 28 0B CB
 22 23 0D 20 F7 10 F1 E1
 E7 00 E1 3E 08 90 4F 06
 00 09 01 28 00 09 46 0E
 FE ED 78 2F E6 1F A2 11
 00 00 28 01 13 D7 FD E9
endc

decimal


READ es una palabra tipo CREATE que contiene en su PF los datos relevantes de la tabla del artículo:
Key_table.jpg
Key_table.jpg (32.75 KiB) Visto 800 veces


Los datos son los High bytes de las direcciones de los ocho puertos a leer (el Low bytes es fijo = FE) y los códigos ASCII de los caracteres que identifican cada una de las cuarenta teclas (en hexadecimal).

La palabra KEY es la rutina en código máquina que espera en la pila el código ascii del carácter correspondiente de la tecla a comprobar y la dirección del PF de READ (lo que deja READ en la pila al ejecutarse, pues es de tipo CREATE) y devuelve un flag si la tecla correspondiente está pulsada o no. Y la respuesta es independiente de la situación de otras teclas.

He aquí le desensamblado de KEY:

Código: Seleccionar todo

KEY
3C5B DF          RST $18
3C5C D5          PUSH DE
3C5D DF          RST $18
3C5E E1          POP HL
3C5F E5          PUSH HL
3C60 7B          LD A,E
3C61 06 08       LD B,$08
3C63 0E 05       LD C,$05
3C65 16 01       LD D,$01
3C67 BE          CP (HL)
3C68 28 0B       JR Z,$3C75
3C6A CB 22       SLA D
3C6C 23          INC HL
3C6D 0D          DEC C
3C6E 20 F7       JR NZ,$3C67
3C70 10 F1       DJNZ $3C63
3C72 E1          POP HL
3C73 E7          RST $20
3C74 00          NOP
3C75 E1          POP HL
3C76 3E 08       LD A,$08
3C78 90          SUB B
3C79 4F          LD C,A
3C7A 06 00       LD B,$00
3C7C 09          ADD HL,BC
3C7D 01 28 00    LD BC,$0028
3C80 09          ADD HL,BC
3C81 46          LD B,(HL)
3C82 0E FE       LD C,$FE
3C84 ED 78       IN A,(C)
3C86 2F          CPL
3C87 E6 1F       AND $1F
3C89 A2          AND D
3C8A 11 00 00    LD DE,$0000
3C8D 28 01       JR Z,$3C90
3C8F 13          INC DE
3C90 D7          RST $10
3C91 FD E9       JP IY


Ejemplo de uso:

Código: Seleccionar todo

: TEST
 ASCII 5 READ KEY
 IF
  LEFT
 THEN
 ASCII 0 READ KEY
 IF
  FIRE
 THEN
;


MULTIPLE KEY PRESSES Garry Knight
One feature of my latest game, ALIENS"', is the ability
of the program to detect multiple key presses. Here's
how it's done. As it says on page 154 of the Ace manual,
each half-row of keys corresponds to it unique Z80 port
address, with the outer-most key on each row correspondlag to bit 0 of the data read in from that port. The
assignment of keys to ports is fairly straightforward,
except on the bottom row, where SYMBOL-SHIFT has been
assigned to the port for the left-hand half-row, and the
'v' key has accordingly been moved right. Fig. I shows
the logical layout of the keys with respect to their port
addresses.
So, to find out whether a particular key is being pressed
you can either read the port; and perform some simple logic
on the number returned, or you can type in the routine
listed below which does it all for you in machine code.
The way you use it is as follows. Suppose you want to
execute the word LEFT if the '5' key is being pressed, and
execute the word FIRE if the '0' key is being pressed, no
matter which other keys are being pressed at the time.
Simply define a word which includes:
ASCII 5 READ KEY IF LEFT THEN ASCII 0 READ KEY IF FIRE THEN
So, to detect number keys, use the ascii code of the number,
for letter keys use the ascii code for the lower-case letter on the key.
You can detect the ENTER, SPACE, SHIFT
and SYMBOL SHIFT keys with 13, 32, ASCII C and ASCII S.
Note that these last two are upper-case. READ KEY leaves
a true (I) or false (0) value on the stack, if anybody
would like the ZX Spectrum version of this routine please
send a SAL to CarryKnight, - 30A Stanton house, lhia,ne Street,
Loudon, SE13 91)1.


Lo he probado ejecutando esta palabra:

Código: Seleccionar todo

: TESTaz
 CLS
 BEGIN
  5 0 AT 123 97
  DO
   I DUP READ KEY
   IF
    128 + 
   THEN
   EMIT
  LOOP
  0
 UNTIL
;
 

Que solo comprueba las letras de la a la z. Las muestra en pantalla y si está pulsada lo hace en video inverso.
Así, por ejemplo, esto es lo que se ve si se mantienen pulsadas simultáneamente las teclas "a", "c", "g"y "u".
key_test.jpg
key_test.jpg (7.6 KiB) Visto 800 veces


Tiene algunas limitaciones no respondiendo a cualquier número de teclas pulsadas a la vez, pero para dos/tres simultáneas parece que reconoce todos lo casos (no los he probado todos...)
Adjuntos
readkey.TZX
(212 Bytes) Descargado 10 veces

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 28 Feb 2022 16:35

CHANGENAME
VLIST3 (Supone que solo hay el vocabulario FORTH)

Estas palabras fueron una de mis primeras utilidades. CHANGENAME es útil cuando tienes palabras repetidas o con el mismo nombre que quieres borrar/redefinir/editar.

Un ejemplo: Si has estado haciendo muchas pruebas con una misma palabra modificándola y probándola pero sin redefinirla cada vez, acabas con un montón de copias al final del diccionario y si quieres borrarlas no te queda otra que hacer otros tantos forgets.

Con VLIST3 obtienes un listado vertical del diccionario. Una palabra por línea. Si se llena la pantalla se espera a que pulses una tecla.
Muestra el nombre de cada palabra precedido por su NLFA (Name Length Field Address/Campo de la Longitud del Nombre). Este número es único, al igual que el CFA, no pueden haber dos palabras con el mismo valor del NLFA.

vlist3_ej1.jpg
vlist3_ej1.jpg (37.03 KiB) Visto 776 veces


Supongamos que te interesa conservar el TEST original (NLFA=20002). Nos interesa borrar el anterior (NLFA=20017). Entonces hacemos un BREAK (SHIFT+SPACE) para mantener a la vista el listado y hacemos:

20017 CHANGENAME

La máquina pedirá confirmación, monstrando en pantalla el NLFA y el nombre que le corresponde. Si le decimos que sí, nos cambiará el primer carácter del nombre por el carácter © con lo que ese TEST nos quedará renombrado a ©EST y el tamaño de la palabra se mantiene igual. Vemos con un VLIST3 que todo ha ido bien:

vlist3_ej2.jpg
vlist3_ej2.jpg (54.34 KiB) Visto 776 veces

Solo nos resta hacer:

FORGET ©EST

También va bien para redefinir una palabra que tiene el mismo nombre que una que está más arriba en el diccionario, etc.
Ej, si tienes dos palabras con el mismo nombre y quieres editar/renombrar la segunda (la que estás más abajo y por tanto inaccesible con REDEFINE (pues este accede a la primera ocurrencia del nombre). Aplicas CHANGENAME a la segunda, la editas (y/o le cambias el nombre) y la redefines.
Etc.

VLIST3 no es solo una palabra auxiliar de CHANGENAME, sino que es muy útil por sí misma. Sirve para obtener el CFA de palabras que tienen el mismo nombre. Con FIND solo puedes acceder a la primera. Con VLIST3 puedes ver el de cualquiera del diccionario. Recuerda que el CFA=NLFA+1.
Es por eso que mis programas Extractores de Fuente/Diccionario/Palabras usan el CFA en la pila, para poder aplicarlos a palabras con nombre repetido, usando VLIST3 (u otros medios) para obtenerlos.

Código: Seleccionar todo

DECIMAL

: CLKEY
 BEGIN
  INKEY 0=
 UNTIL
;
 
: RKEY
 CLKEY
 BEGIN
  INKEY ?DUP
 UNTIL
 CLKEY
;
 
: Y/N
 BEGIN
  RKEY DUP ASCII y =
  IF
   DROP 2
  ELSE
   ASCII n =
  THEN
  ?DUP
 UNTIL
 1-
;
 
: VNAME2
 DUP U. SPACE DUP 2-
 @ SWAP DUP 4 -
 SWAP C@ 63 AND OVER
 SWAP -
 DO
  I C@ 127 AND EMIT
 LOOP
 CR
;
 
: CHANGENAME
 DUP VNAME2 DROP ." (y/n): "
 Y/N
 IF
  DUP C@ 63 AND -
  4 - DUP C@ 127
  >
  IF
   255 ( ASCII © + 128)
  ELSE
   127 ( ASCII ©)
  THEN
  SWAP C!
 ELSE
  DROP
 THEN
;
 

: VLIST3
 0 CR CURRENT @ @
 BEGIN
  VNAME2 SWAP 1+ DUP 22
  =
  IF
   BEGIN
    INKEY
   UNTIL
   DROP 0
  THEN
  SWAP DUP 15434 U<
 UNTIL
 DROP DROP
;
Adjuntos
vlist3.TZX
(448 Bytes) Descargado 10 veces

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 03 Mar 2022 16:54

Hasta ahora he estado usando para programar en ensamblador el Asembler/Disasembler de Boldfiel. Pero me voy a pasar al TASM 2.9.

Por ejemplo, para crear la palabra TEST en Ace Forth que me ejecute el código máquina que previamente he generado con TASM en el fichero MC.bin, hago esto:

CREATE TEST n ALLOT

Donde n es el tamaño exacto en bytes del fichero MC.bin

Luego, para cargar MC.bin en el PF de TEST:

TEST U.

Que me da la dirección de memoria del inicio del PF de TEST.

EightyOne>File>Load Memory Block y allí pongo el fichero MC.bin y la dirección del PF de TEST.

Por último, para convertir TEST en una primitiva de usuario:

TEST DUP 2- !

Hecho!

Esto último podría hacerse con la palabra CREATE>MC así:

CREATE>MC TEST

Código: Seleccionar todo

: CREATE>MC
 FIND DUP 2+ SWAP  !
;

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 04 Mar 2022 16:22

+ULOOP en código máquina y compatible con NEXT

Los límites se tratan Sin Signo. El incremento es Con Signo.

Timings: Slow/Fast segundos para 1000 vueltas (incluido el tiempo de colocar el incremento en la pila)
+ULOOP 0.82/0.55
+LOOP 0.33/0.22 Este es el +LOOP original del JA (con signo tanto en límites como en incremento)

Código: Seleccionar todo

2 COMPILER +ULOOP
 3 = 0=
 IF
  5 ERROR
 THEN
 HERE - ,
 BEGIN
  DUP 5 =
 WHILE
  DROP DUP HERE SWAP -
  4 - SWAP !
 REPEAT
RUNS>
 SWAP ULOOPMC+
;


ULOOPMC+

Código: Seleccionar todo

   .list
        call $84E                ; BC=STACK(0)= incremento
        ld   hl,4     
        add  hl,sp               ; HL = SP+4 apunta al índice actual del bucle
        ld   e,(hl)   
        inc  hl       
        ld   d,(hl)         
        ex   de,hl               ; HL = índice del bucle 
        add  hl,bc   
        ld   a,b                 ; A = B (has bit7 the increment sign)
        ex   de,hl     
        ld   (hl),d     
        dec  hl       
        ld   (hl),e              ; actualizado el nuevo índice del bucle
        inc  hl       
        inc  hl           
        ld   c,(hl)   
        inc  hl       
        ld   b,(hl)              ; BC = Límite del Bucle
        ld   h,b       
        ld   l,c                 ; HL = Límite del Bucle
        bit  7,a                 ; ¿Es incremento negativo? Salta si no es negativo
        jr   z,plbl              ; HL apunta limite del bucle = SP+6 ; DE = indice actual
        or   a                   ; Borro el Carry flag
        sbc  hl,de               ; Resto al límite el valor actual del índice
        jr   z,clbl              ; Si iguales se acabó el bucle
        jr   nc,clbl             ; Si límite mayor que índice, fin también
        jr   qlbl
plbl:
        or   a                   ; Borro el Carry flag
        sbc  hl,de               ; Resto al límite el valor actual del índice
        jr   z,clbl              ; Si iguales se acabó el bucle
        jr   c,clbl              ; Si límite menor que índice, fin también
qlbl:
        rst  18h
        ex   de,hl               ; HL = número de la Pila de datos (lo saca)
        ld   c,(hl)              ; que apunta a donde está el desplazamiento
        inc  hl                  ; negativo hacia el principio del bucle
        ld   b,(hl)              ; BC = (HL)
        dec  hl       
        add  hl,bc               ; HL = HL + (HL)
        pop  bc       
        pop  de       
        push hl     
        push bc                  ; (SP+2)=HL sin alterar el resto del R.stack
        jp   (iy)                ; se repite el bucle
clbl:
        pop  bc       
        pop  de       
        pop  hl       
        pop  hl       
        push de     
        push bc                 ; Elmina SP+4 y SP+6 del R.stack
        rst  18h                ; Elimina el número en D.stack (DROP)
        jp   (iy)               ; Se termina el bucle y pasa a siguiente word
   .end


Incluyo aquí el Pack completo: ULOOP +ULOOP y NEXT
Utilizan el DO LEAVE I I' y J normales.
Adjuntos
uloopnext.TZX
(825 Bytes) Descargado 13 veces

Elurdio
Mensajes: 410
Registrado: 07 Dic 2021 21:33
Ubicación: Barcelona
Agradecido : 90 veces
Agradecimiento recibido: 84 veces

Re: Jupiter Ace Forth: Utilidades

Mensajepor Elurdio » 05 Mar 2022 23:29

Template for TAP files

Hoy me he estado peleando con el TASM 2.9 mientras intentaba compilar el ejemplo que viene como template para hacer ficheros .tap para el JA del Jupiter Ace Archive

No sé muy bien por qué, pero he tenido que desactivar la directiva #ifndef ACE_INC que viene en los dos "include files" necesarios. Si no siempre me daba error de "imbalanced conditional".

Luego de compilar finalmente sin errores el "example.asm" me genera un fichero "example.tap" que si lo cargo en el JA (emulador) vacío funciona la mar de bien pero no así si ya hay un diccionario antes de cargar "example.tap". Entonces a la que hagas algo (un simple VLIST basta), se cuelga.

Tras examinar detenidamente el fuente, veo que hay un bug:
imag1.jpg
imag1.jpg (28.42 KiB) Visto 1410 veces

Falta restar 3 al cáculo del Word Length Field (lo he añadido marcado en rojo). Pues tal como lo han hecho están incluyendo el tamaño del nombre de la siguiente palabra (HEX) en el tamaño de ésta (BELL). El Word Length Field es el tamaño de la palabra sin incluir el tamaño del nombre. Aquí no solo lo incluyen, sino que encima es el de la palabra que le sigue...

Tras recompilar con la modificación indicada, ya se puede cargar sobre un diccionario preexistente sin problemas.
EDIT: (9&13-3-2022) Solución genérica: Cambiar solo la línea del Word Length Field por la que pongo aquí. De hecho es como lo hace con la siguiente palabra del ejemplo (HEX).

Código: Seleccionar todo

; DING -----------------------
; FORTH dictionary word header
BELLNAME: .BYTE  "BEL",'L' | INVERSE  ; Word Name (last letter inverse)   
          .WORD  BELLEND - $          ; Word Lenght Field
          .WORD  $3C49                ; Link Field
BELLLNK:  .BYTE  $ - BELLNAME - 4     ; Name Lenght Field
          .WORD  $ + 2                ; Code Field Address
; --- BELL code ---
   ·
   ·
   ·
          AsmEnd               ; End of word
BELLEND:


Volver a “Jupiter Ace”

¿Quién está conectado?

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