Historia de DZX-Forth en 2015-02

Descripción del contenido de la página

Historia del desarrollo en 2015-02 de DZX-Forth, un Forth para ZX Spectrum +3e.

Etiquetas:
Esta página muestra apuntes tomados durante el desarrollo de DZX-Forth en 2015-02.

2015-02-01

Nuevo cambio de nombre: fileblocks pasa a ser resize-block-file. El objetivo de todos los cambios de nombre es que éstos sean lo más regulares posible para facilitar recordar la función de las palabras. En este caso el cambio era claro: desde el estándar Forth-94 existe una palabra resize-file para cambiar el tamaño de cualquier tipo de fichero. Otras palabras del grupo de ficheros de bloques aún deben ser renombradas con este mismo criterio; salvo las que tienen un nombre que forma parte de un estándar.

DZX-Forth

Nueva palabra latest-word, para conservar la cadena de la última palabra que fue recibida en el flujo de entrada antes de cualquier error. Además se imprime cuando el error fue el -13 (palabra no definida). DX-Forth ya tiene la palabra pwa (parsed word address), originalmente oculta, que guarda la dirección de la cadena contada de cada resultado de word, pero para utilizar la cadena tras un error era necesario salvarla, pues lógicamente si usamos pwa en la línea de comandos para obtener su contenido se modifica su contenido consigo misma...

La definición de la nueva palabra latest-word y los pequeños cambios necesarios en error:

error_.other:
  dw pwa_,count_
  dw lit_,latest_word_.value,two_store_
  dw paren_dot_quote_
  _dcs ' exception = '
  dw dup_,dot_
  dw lit_,-13 ; undefined word
  dw equals_
  dw branch_if_false_,error_.end
  dw latest_word_,type_
error_.end:
  dw paren_abort_
;  dw paren_exit_ ; XXX OLD -- not needed

; LATEST-WORD  ( -- ca len )
; Return the latest word parsed before any error.

  _header _public,'LATEST-WORD'
latest_word_:
  call do_two_constant
latest_word_.value:
  ds 2*cell

2015-02-09

Extraigo del fichero de DX-Forth el código relativo a los ficheros de código fuente con formato de texto (include y demás palabras relacionadas), para examinarlo y documentarlo, antes de poder incluirlo en el núcleo. Hay algunas palabras que hacen la misma función que otras del módulo de ficheros de bloques que está incluido en el núcleo. Se pueden unificar algunas de ellas. También hay que unificar la gestión general de los números de fichero con la tabla de descripción de ficheros de bloques, que también es usada por los ficheros fuente con formato de texto.

El objetivo es que las palabras necesarias para usar ficheros de código fuente en formato de texto estén en el núcleo (aunque pueda desactivarse con compilación condicional), aunque no cuesta nada ofrecerlas también en un fichero de bloques, como ahora. Lo mismo se aplica al soporte para ficheros de bloques, que está en el núcleo: hace falta una implementación alternativa en un fichero fuente. De este modo el sistema será muy flexible, y podrá usar ambos tipos de ficheros fuente tanto si fue compilado para ello como si no.

2015-02-12

Continúo el examen y la documentación del código de carga de ficheros fuente en formato de texto. Hasta que este código esté en el núcleo, aunque sea como opción, el desarrollo estará frenado. Cuando DZX-Forth pueda interpretar ficheros de texto estándar, más cómodos de editar que los incómodos ficheros de bloques, será posible implementar muchas funcionalidades nuevas.

2015-02-13

DZX-Forth

Para poder editar, modificar y probar con comodidad el código de include y palabras relacionadas, escribo un conversor en Vim que convierte el fichero de texto actual en un fichero de bloques de Forth. La única precaución a la hora de escribir el código es que no haya líneas más largas de 63 caracteres, y que las líneas formen grupos de 16. A pesar de estas restricciones, este método provisional permite usar Vim para editar las fuentes y cargarlas después en DZX-Forth.

2015-02-14

DZX-Forth

Hasta ahora DZX-Forth se cargaba en el emulador desde un fichero TAP, mientras el fichero DSK con la imagen de disquete de ZX Spectrum +3 contenía solo los ficheros de bloques de Forth, con las fuentes opcionales, que aún solo servían para hacer pruebas. Pero el objetivo era que todo el sistema estuviera contenido en la imagen de disquete. Por fin, tras leer aquí y allá en el manual de GNU make y hacer algunas pruebas, he podido reescribir el fichero Makefile para que todos los ficheros de DZX-Forth estén en el disquete:

# DZX-Forth Makefile

# By Marcos Cruz (programandala.net)
# http://programandala.net/en.program.dzx-forth.html

################################################################
# Requirements

# Pasmo (by Julián Albo)
#   http://pasmo.speccy.org/

# tap2dsk from Taptools (by John Elliott)
#   http://www.seasip.info/ZX/unix.html

# bin2code (by MetalBrain)
#   http://metalbrain.speccy.org/link-eng.htm

# bas2tap (by Martijn van der Heide)
#   Utilities section of
#   http://worldofspectrum.org

################################################################
# Change history

# See at the end of the file.

################################################################
# TODO

# - Versions with/out floating support, using Pasmo's command
#   line to set the labels.

################################################################
# Config

VPATH = src:doc:bin
MAKEFLAGS = --no-print-directory

.PHONY : all
all : dsk doc

.ONESHELL:

################################################################
# Documentation

dzx-forth.html : dzx-forth.adoc
  asciidoctor doc/dzx-forth.adoc -o doc/dzx-forth.html

dzx-forth_glossary.html : dzx-forth_glossary.adoc
  asciidoctor doc/dzx-forth_glossary.adoc -o doc/dzx-forth_glossary.html

.PHONY : doc
doc:
  @make dzx-forth.html dzx-forth_glossary.html

################################################################
# Program

# ----------------------------------------------
# Disk BASIC loader


dzx-forth_loader.bas : dzx-forth_loader.bas.raw dzx-forth_kernel.tap
  @make dzx-forth_kernel.tap
  ./_tools/patch_the_loader.fs

# Note: <./_tools_patch_the_loader.fs> called above is a Forth
# program that patches the DZX-Forth BASIC loader with the load
# and start address of the kernel.

dzx-forth_loader.tap : dzx-forth_loader.bas
  bas2tap -a10 -sDISK src/dzx-forth_loader.bas bin/dzx-forth_loader.tap

# ----------------------------------------------
# Kernel

dzx-forth_kernel.tap : dzx-forth.z80s
  pasmo -I src --name "DZXFORTH.B" --tap \
    src/dzx-forth.z80s bin/dzx-forth_kernel.tap \
    src/dzx-forth_symbols.z80s

# ----------------------------------------------
# TAP file

# Note: The TAP file is created only in order to create the DSK
# file from it. DZX-Forth can be loaded into a ZX Spectrum
# emulator using the TAP file, but the system itself has no tape
# support.

forth_block_files = $(wildcard src/*.fb)

dzx-forth_block_files.tap : $(forth_block_files)
  cd src ; \
  for file in $$(ls -1 *.fb); do \
    bin2code $$file $$file.tap; \
  done; \
  cat *.fb.tap > ../bin/dzx-forth_block_files.tap ; \
  rm -f *.fb.tap ; \
  cd - > /dev/null

dzx-forth.tap : dzx-forth_loader.tap dzx-forth_kernel.tap dzx-forth_block_files.tap
  cat \
    bin/dzx-forth_loader.tap \
    bin/dzx-forth_kernel.tap \
    bin/dzx-forth_block_files.tap \
    > bin/dzx-forth.tap

.PHONY : tap
tap:
  @make dzx-forth.tap

# ----------------------------------------------
# DSK disk image

dzx-forth_720k.dsk : dzx-forth.tap
  tap2dsk -720 -label DZXForth bin/dzx-forth.tap bin/dzx-forth_720k.dsk

.PHONY : dsk
dsk:
  @make dzx-forth_720k.dsk

################################################################
# Change history

# 2014-12-28: First draft.
#
# 2014-12-30: First working version: z80s>tap>dsk, adoc>html;
# 180 and 720 KB disks.
#
# 2015-01-05: Requirements.
#
# 2015-01-13: 720 KB disk commented out.
#
# 2015-01-14: Fixed the symbols filename. Added <bin> dir.
#
# 2015-01-20: <tools.fb> is included into the disk image, with
# <bin2code>.
#
# 2015-01-24: 720 KB disk used, to hold all block files.
# <mkp3fs> is used in order to copy the block files without a
# +3DOS header.
#
# 2015-02-14: Rewritten: the DSK includes not only the Forth
# block files but also the loader and the kernel; no TAP needed
# anymore, though a whole TAP is created as part of the process.
# <tap2dsk> has to be used instead of <mkp3fs>.

Y esta es una transcripción de la operación:

$ make dsk
pasmo -I src --name "DZXFORTH.B" --tap \
  src/dzx-forth.z80s bin/dzx-forth_kernel.tap \
  src/dzx-forth_symbols.z80s
make[2]: 'bin/dzx-forth_kernel.tap' is up to date.
bas2tap -a10 -sDISK src/dzx-forth_loader.bas bin/dzx-forth_loader.tap

BAS2TAP v2.4 by Martijn van der Heide of ThunderWare Research Center

Creating output file bin/dzx-forth_loader.tap
Done! Listing contains 5 lines.
cd src ; \
for file in $(ls -1 *.fb); do \
  bin2code $file $file.tap; \
done; \
cat *.fb.tap > ../bin/dzx-forth_block_files.tap ; \
rm -f *.fb.tap ; \
cd - > /dev/null
cat \
  bin/dzx-forth_loader.tap \
  bin/dzx-forth_kernel.tap \
  bin/dzx-forth_block_files.tap \
  > bin/dzx-forth.tap
tap2dsk -720 -label DZXForth bin/dzx-forth.tap bin/dzx-forth_720k.dsk
Writing DISK    .
Writing DZXFORTH.B
Writing ASM-DXF .FB
Writing ASMCOND .FB
Writing ASMTEST .FB
Writing BLK2TXT .FB
Writing BREAKGO .FB
Writing DOSLIB  .FB
Writing DXFORTH .FB
Writing EDITOR  .FB
Writing EXTEND  .FB
Writing FILES   .FB
Writing LOCALS  .FB
Writing MISC    .FB
Writing MISER   .FB
Writing MULTI   .FB
Writing NEWAPP  .FB
Writing OVERLAY .FB
Writing STKCHK  .FB
Writing TOOLS   .FB
Writing TOOLSALL.FB
Writing UG      .FB
Writing WORDS   .FB

Así pues, DZX-Forth arranca por primera vez desde disquete. Con esta mejora queda cerrada la rama A-02 del desarrollo, con la versión A-02-20150214, e inicio la A-03, cuyo principal objetivo es implementar el soporte para ficheros fuente en formato de texto.

2015-02-15

Hay una pega con el sistema empleado en el nuevo fichero Makefile: al pasar los ficheros por el formato TAP la longitud de sus nombres queda limitada a diez caracteres, mientras que en el disquete pueden tener 12, incluyendo el punto de la extensión.

2015-02-16

DZX-Forth

Pruebas con la compilación desde ficheros de bloques, antes de unificar las tablas de control de ficheros.

Por otra parte, en DX-Forth hay dos palabras que hacen lo mismo: (name) y n>count (un alias de la primera introducido en DX-Forth 4.09). Dejo solo una, con el nombre estándar name>string.

2015-02-17

Hay un problema extraño: El valor devuelto por #screens se conserva entre diferentes aperturas de ficheros de bloques, al parecer cuando el segundo fichero abierto tiene menos bloques que el primeor. Primeras pruebas para encontrar el origen del fallo.

2015-02-18

La palabra read-line aún no está adaptada a +3DOS. Para empezar la adaptación tomo del código de DX-Forth su versión en Forth, para documentarlo y adaptarlo mejor. Lo que hace esta palabra más compleja de lo habitual es que +3DOS, como CP/M, guarda los ficheros en bloques de 128 octetos y rellena el espacio sobrante final con el carácter de control 26. Por ello no solo hay que detectar la presencia de una posible combinación de final de línea, sino también de dicho carácter.

La versión de alto nivel de read-line, que está en los comentarios del código de DX-Forth, es la siguiente:

: READ-LINE  ( ca len1 fid -- len2 flag ior )
  >r over swap r> read-file ?dup if  exit  then
  2dup over + swap ?do
    i dup c@ $1A = if
      rot - fh file-size drop rwp! leave
    then
    c@ eol? ?dup if
      i + >r over + r> swap - dup
      0<> rwp@ d+ rwp! i swap - -1 0 unloop exit
    then
  loop  nip dup 0<> 0  ;

Las palabras rwp@ y rwp! sirven respectivamente para recuperar y almacenar el puntero del fichero actual en su tabla de control, pero fueron anuladas en DZX-Forth, pues la forma en que funciona +3DOS las hace innecesarias. Para adaptar read-line tengo primero que entenderla a fondo, y para ello es mejor anotar lo que hay en la pila en cada paso. De ese modo es posible experimentar con más seguridad hasta escribir el código definitivo. De momento el resultado es el siguiente:

2variable read-line-position
variable read-line-fid

: READ-LINE  ( ca1 len1 fid -- len2 flag ior )
  \ ca1   = address of the first char
  \ len1  = max number of chars to be read
  \ fid   = file id of the file to be read
  \ len1' = number of chars actually read
  \ len2  = number of chars actually read, without line terminators
  \ flag  = succesful?
  \ ior   = input/output report
  \ ca1'  = address of the currently examined char, in the loop
  \ eol   = type of EOL char (1=LF, 2=CR)

  dup read-line-fid !
  dup file-position  ( ca1 len1 fid ud ior )
  ?dup if  >r 2drop 2drop 0 0 r> exit  then
  ( ca1 len1 fid ud )
  read-line-position 2! ( ca1 len1 fid )
  >r  ( ca1 len1 )
  over ( ca1 len1 ca1 )
  swap ( ca1 ca1 len1 )
  r> ( ca1 ca1 len1 fid )
  read-file ( ca1 len1' ior ) ?dup if  exit  then
  \ no error
  ( ca1 len1' )
  2dup ( ca1 len1' ca1 len1' )
  bounds  ( ca1 len1' len1'+ca ca1 )
  ?do
    ( ca1 len1' )
    i ( ca1 len1' ca1' )
    dup c@ $1A = if  \ CTRL-Z?
      \ CTRL-Z found
      rot ( len1' ca1' ca1 )
      -   ( len1' len2 )
      read-line-fid @ file-size drop \ XXX TODO manage the ior instead?
\     ( len1' len2 d )
      read-line-fid @ reposition-file drop \ XXX TODO manage the ior instead?
      ( len1' len2 )
      leave  \ leave the loop
    then
    ( ca1 len1' ca1' )
    c@ eol? ?dup if
      \ EOL found
      ( ca1 len1' eol )

      \ ca1    = address of the first char
      \ len1' = number of chars actually read
      \ len2  = number of chars actually read, without line terminators
      \ flag  = succesful?
      \ ior   = input/output report
      \ ca1'   = address of the currently examined char, in the loop
      \ eol   = type of EOL char (1=LF, 2=CR)

[ true ] [if]

      \ This works fine with Unix-format texts (LF as EOL) and
      \ DOS-format texts (CR and LF as EOL). It can not work with
      \ Mac-format texts (CR as EOL).

      \ XXX TODO : maybe this calculation could be done directly with
      \ eol?:

      ( ca1 len1' eol )
      i         ( ca1 len1' eol ca1' )
      +         ( ca1 len1' eol+ca1' )
      >r        ( ca1 len1' )
      over +    ( ca1 len1'+ca1 )
      r>        ( ca1 len1'+ca1 eol+ca1' )
      swap      ( ca1 eol+ca1' len1'+ca1 )
      -         ( ca1 x )
      dup 0<>   ( ca1 x flag ) \ handle buffer > 32K

[else]

      \ Simpler version.

      \ XXX FIXME 

      \ This works fine with Unix-format texts (LF as EOL)
      \ but fails at the second line of DOS-format texts
      \ (CR and LF as EOL).

      ( ca1 len1' eol )
      + s>d

[then]

      ( ca1 d )

      read-line-fid @ file-position drop  \ XXX TODO manage the ior instead?
      d+
      read-line-fid @ reposition-file  \ XXX TODO manage the ior instead?

      drop i swap - -1 0 unloop exit
    then
  loop
  ( ca1 len2 | len1' len2 )
  nip   ( len2 )
  dup   ( len2 len2 )
  0<>   ( len2 len2<>0 )
  0     ( len2 len2<>0 0 )
  ;

El trabajo no ha concluido, pero este primer modelo funciona. Probablemente aún sera posible simplificar el cálculo de actualización del puntero.

Las palabras (read-file) y (write-file), componentes respectivamente de las palabras estándar read-file y write-file, fueron escritas para facilitar el acceso que permite +3DOS a la lectura o grabación con una página de memoria a elegir en el cuarto superior de la memoria, en el rango de direcciones 0xC000..0xFFFF. Por ello es mejor renombrarlas como page-read-file y page-write-file.

Por último, >ior ya no es necesaria y puede borrarse. Además, los números de error de +3DOS son ahora convertidos en números negativos, entre -1000 y -1036, para seguir el estándar.

2015-02-19

Primera versión funcional de read-line en el núcleo, terminada a falta de algunos detalles:

; READ-LINE  ( ca1 len1 fid -- len2 flag ior )

  _header _public,'READ-LINE'
read_line_:

  ; ca1   = address of the first char
  ; ca1'  = address of the currently examined char, in the loop
  ; ca2   = ca1'+eol = address after the end of line
  ; ca3   = ca1+len1' = address after the last char actually read
  ; eol   = type of EOL char (1=LF, 2=CR)
  ; flag  = succesful?
  ; ior   = input/output report
  ; len1' = number of chars actually read
  ; len2  = number of chars actually read, without line terminators
  ; n     = offset from the current file position to the start of
  ;         the next line

  call do_colon

  dw dup_,lit_,read_line_.fid,store_
  dw to_r_,over_,swap_,from_r_ ; ( ca1 ca1 len1 fid )
  dw read_file_ ; ( ca1 len1' ior )
  dw question_dup_
  dw branch_if_zero_,read_line_.ready
  ; 'read-file' error
  dw to_r_,nip_,zero_,from_r_
  dw paren_exit_

read_line_.ready:
  ; ( ca1 len1' )
  dw two_dup_,bounds_
  dw paren_question_do_,read_line_.end_of_loop

read_line_.do:
  ; ( ca1 len1' )
  dw i_,dup_,c_fetch_ ; current char
  dw c_lit_
  db ctrl_z_char
  dw equals_ ; CTRL-Z?
  dw branch_if_false_,read_line_.check_eol

  ; CTRL-Z found
  ; ( ca1 len1' ca1')
  dw rot_,minus_  ; ( len1' len2 )
  dw lit_
read_line_.fid:
  ds cell ; the file id is stored here at the start
  dw file_size_,drop_ ; XXX TODO manage the ior
  ; XXX TODO faster with 'dup' and 'rot'?:
  dw lit_,read_line_.fid,fetch_
  dw reposition_file_,drop_  ; XXX TODO manage the ior
  ; ( len1' len2 )
  dw paren_leave_,read_line_.do-cell ; leave the loop

read_line_.check_eol:
  ; ( ca1 len1' ca1' )
  dw c_fetch_,eol_question_,question_dup_
  dw branch_if_zero_,read_line_.loop
  ; EOL found
  ; ( ca1 len1' eol )
  dw i_,plus_,to_r_
  dw over_,plus_
  dw from_r_ ; ( ca1 ca3 ca2 )
  dw swap_,minus_ ; ( ca1 n )
  dw dup_,zero_not_equals_  ; handle buffer > 32K XXX ?

  ; Update the file position to the start of the next line
  ; ( ca1 d )
  dw lit_,read_line_.fid,fetch_
  dw file_position_,drop_ ; XXX TODO manage the ior
  dw d_plus_
  dw lit_,read_line_.fid,fetch_
  dw reposition_file_,drop_ ; XXX TODO manage the ior

  ; ( ca1 )
  dw i_,swap_,minus_    ; ( len2 )
  dw true_,zero_        ; ( flag ior )
  ; ( len2 flag ior )
  dw unloop_,paren_exit_

read_line_.loop:
  dw paren_loop_,read_line_.do
read_line_.end_of_loop:
  ; ( ca1 len2 | len1' len2 )
  dw nip_,dup_,zero_not_equals_,zero_
  ; ( len2 flag ior )
  dw paren_exit_

La versión que sirvió de modelo para las pruebas y la implementación:

variable read-line-fid
: READ-LINE  ( ca1 len1 fid -- len2 flag ior )
  dup read-line-fid !
  >r over swap ( ca1 ca1 len1 )
  r> ( ca1 ca1 len1 fid )
  read-file ( ca1 len1' ior )
  ?dup if  >r nip 0 r> exit  then ( ca1 len1' )
  2dup bounds
  ?do ( ca1 len1' )
    i ( ca1 len1' ca1' )
    dup c@ $1A = if  \ CTRL-Z?
      \ CTRL-Z found
      rot ( len1' ca1' ca1 )
      -   ( len1' len2 )
      read-line-fid @ file-size drop
      read-line-fid @ reposition-file drop
      ( len1' len2 )
      leave  \ leave the loop
    then
    ( ca1 len1' ca1' )
    c@ eol? ?dup if
      \ EOL found
      ( ca1 len1' eol )
      i +             ( ca1 len1' eol+ca' )
      >r over + r>    ( ca1 ca3 ca2 )
      swap - dup 0<>  ( ca1 d ) \ original method
      read-line-fid @ file-position drop  d+
      read-line-fid @ reposition-file drop
      i swap - -1 0 unloop exit
    then
  loop ( ca1 len2 | len1' len2 )
  nip dup 0<> 0  ( len2 len2<>0 )
  ;

DZX-ForthDZX-Forth

2015-02-20

Hasta ahora para manejar los nombres de fichero se usaba básicamente el mismo código de DX-Forth, con algunos cambios:

; FILENAME-BUFFERS ( -- a )
; filename buffer pointers

  _header _hidden,'FILENAME-BUFFERS'
filename_buffers_:
  call do_create
filename_buffers_.next:
  dw filename_buffer.next ; address of the filename buffer to be used next
filename_buffers_.last:
  dw filename_buffer.last ; address of the filename buffer used last

filename_buffer.next: ds filename_buffer_size
filename_buffer.last: ds filename_buffer_size

; >FILENAME-BUFFER  ( ca len -- ca2 )
;
;   (filenamesize-1) min filename-buffers @ packed 0 affix
;   filename-buffers 2@ swap filename-buffers 2!

; Convert the string ca len to a 0xFF-terminated counted string
; ca2 in the filename buffer.  A maximum of two filenames can
; exist in the buffer at one time.  ca2 returns the counted
; string, whereas ca2 + 1 returns the 0xFF-terminated string.

; Note: The returned string resides in a transient region which may
; be overwritten by subsequent operations.

; XXX TODO return ca2+1? this is what +3DOS needs

; XXX TODO better yet: simply add a trailing 0xFF and use the circular string buffer

  _header _public,'>FILENAME-BUFFER'
to_filename_buffer_:

  call do_colon
  dw c_lit_
  db filename_size
  dw min_
  dw filename_buffers_,fetch_
  dw packed_
  dw c_lit_
  db 0xFF
  dw affix_  ; trailing 0xFF
  dw filename_buffers_,two_fetch_
  dw swap_
  dw filename_buffers_,two_store_
  dw paren_exit_

La nueva versión simplificada devuelve la dirección que +3DOS necesita, y usa el almacén circular de cadenas:

; >FILENAME  ( ca len -- ca2 )

; Convert the string ca len to a 0xFF-terminated string
; at ca2 in the string buffer.

  _header _public,'>FILENAME'
to_filename_:

  call do_colon
  dw c_lit_
  db filename_size
  dw min_
  dw char_plus_ ; for the trailing 0xFF
  dw save_string_
  dw two_dup_,plus_,char_minus_ ; address of the last char
  dw c_lit_
  db 0xFF
  dw swap_,c_store_  ; trailing 0xFF
  dw drop_
  dw paren_exit_

2015-02-21

Nueva versión, más sencilla y rápida, de control>bl, usada por (evaluate); no tenía utilidad eliminar solo ciertos caracteres de control, y es más rápido eliminarlos todos. Además, la palabra ya no es un vector redireccionable.

; CONTROL>BL  ( ca len -- ca len )
  _header _public,'CONTROL>BL'
control_to_bl_:
  ; XXX TODO -- other method faster than pop-push?:
  pop de
  pop hl
  push hl
  push de
paren_control_to_bl_.begin:
  ; hl = pointer to current char
  ; de = remaining chars
  ld a,d
  or e
  jp z,next
  dec de
  ld a,(hl)
  cp space_char
  jp nc, paren_control_to_bl_.repeat
  ld (hl),sp_char
paren_control_to_bl_.repeat:
  inc hl
  jp paren_control_to_bl_.begin

Problema: La rutina de lectura de ficheros de +3DOS devuelve un error de final de fichero cuando el número de octetos solicitado sobrepasa el final del fichero, lo cual es lógico pero impide que la palabra read-file se comporte según el estándar: debe devolver el número de octetos leídos, sin código de error. Para corregir este problema hay que comprobar la posición del puntero del fichero antes de cada lectura.

2015-02-23

Confirmo que file-size la mayoría de las veces devuelve valores incorrectos, mucho mayores que el tamaño real, y a menudo además los mismos valores de llamadas previas. Este es el motivo de algunos errores de lectura de ficheros de bloques, y de que read-line falle en el código de include. Para investigar el problema y encontrar el patrón de error escribo una pequeña palabra:

: .size  ( ca len -- )
  \ Show the size of a closed file.
  r/o open-file throw
  dup file-size throw d.
  close-file throw  ;

También aprovecho para escribir flush-drive, que estaba pendiente, y que permite acceder a una de las rutinas de +3DOS; pero no hace ninguna diferencia al ser usada entre las llamadas a file-size:

; FLUSH-DRIVE  ( c -- )
;
; Write any pending headers, data directory entries for the given drive.  This
; word ensures that the disk is up to date. It can be used at any time, even
; when files are open.
;
; c = drive (ASCII 'A'..'P')

  _header _public,'FLUSH-DRIVE'
flush_drive_:
  pop hl
  ld a,l
  ld ix,dos_flush
  call do_dos
  jp nc,dos_error_.a
  jp next

Para hacer alguna prueba más, escribo una palabra para la acceder a la rutina DOS HEF REF de +3DOS:

; BASIC-FILE-HEADER  ( fid -- a )

; Point at the BASIC header data for the given file, in RAM page 7.

; The header data area is 8 bytes long and may be used by the caller for any
; purpose whatsoever. It is available even if the file does not have a header;
; however, only files with a header and opened with write access will have the
; header data recorded on disk.

; Note that +3 BASIC uses these 8 bytes.  If creating a file that will
; subsequently be LOADed within BASIC, then those bytes should be filled with
; the relevant values.

  _header _public,'BASIC-FILE-HEADER'
basic_file_header_:
  call save_ip
  pop hl
  ld b,l  ; fid
  ld ix,dos_ref_head
  call dos
  call restore_ip
  jp nc,dos_error_.a
  push ix
  jp next

Finalmente recordé que en la documentación de ZX Spectrum +3e se decía que el nuevo sistema operativo corregía algunos fallos de la ROM original. Arranqué DZX-Forth en ZX Spectrum +3e, para probar, y file-size funcionó perfectamente. Todos los tamaños de fichero devueltos eran los correctos, tanto de ficheros con cabecera de +3DOS como sin ella.

Si efectivamente la ZX Spectrum +3 original, como parece, tiene este fallo tan grave en +3DOS, no hay más remedio que escribir DZX-Forth en y para ZX Spectrum +3e. El objetivo inicial era que DZX-Forth fuera compatible con ambas máquinas, y que las muchas prestaciones adicionales de ZX Spectrum +3e estuvieran disponibles con ensamblado condicional del núcleo o con ficheros fuente adicionales. El fallo de file-size en +3 hara imposible la carga de ficheros fuente en la mayoría de los casos.

Esta palabra estaba también pendiente y, debido a lo dicho, es conveniente tenerla ya definida:

; IDEDOS?  ( -- wf )
;
; Is IDEDOS present?

  _header _public,'IDEDOS?'
idedos_question_:
  ld ix,dos_version
  call do_dos
  jp m,true_
  jp false_

2015-02-24

He encontrado la confirmación del problema de file-size, en la documentación de ZX Spectrum +3e:

The following changes have been made to v1.37 of the +3e ROMs since the previous public release (v1.36):

  • Fixed a long-standing +3DOS uninitialised variable bug, present since the original +3, which caused the filesize of files opened in headerless mode to be miscalculated. The most obvious manifestation of this bug was incorrect (too large) filesizes when files were copied from hard disk to any location.

Eso significa que, definitivamente, DZX-Forth será desarrollado solo para ZX Spectrum +3e, con soporte para +3DOS, IDEDOS y, posteriormente, también para ResiDOS.