Historia de DZX-Forth en 2015-01

Descripción del contenido de la página

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

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

2015-01-06

Soporte en interpret para números binarios con prefijo «%». Nueva palabra binary.

Encontrado y solucionado el fallo en interpret, que provocaba error de palabra no reconocida tras la ejecución de la palabra reconocida.

state? es renombrada como compiling? y reescrita para que sea más rápida. Nueva palabra executing?, complementaria de compiling?.

La antigua palabra not es eliminada; era la versión de Forth-79 (equivalente a 0=), no la de Forth-83 (complemento a uno).

compile, es convertida en un alias de , (coma), pues las comprobaciones que hacía ya no son necesarias.

Corregido fallo en la comprobación de la pila de retorno en la nueva versión de ?stack.

Eliminada la palabra cls en favor de page.

2015-01-10

Corregida la incoherencia en los nombres de las etiquetas de exit y (exit).

Corregido un fallo en ?comp: había desaparecido la llamada a 0= y por ello hacía lo mismo que ?exec.

Nuevo: cursor (provisional); -cursor para borrarlo.

Sigue habiendo un problema al crear nuevas palabras, no importa con qué palabra. La máquina se reinicia en (exit), habiendo llegado desde interpret.

2015-01-11

Nuevo: etiqueta para ensamblado condicional con que ahorrar memoria a cambio de reducir la velocidad.

Cambio: la palabra wfind es renombrada como wordlist-find, y convertida en pública.

Cambio: w>name cambia a wordlist>name.

2015-01-12

Corregido un error en la definición de unused, que provocaba el cuelgue; esto afectaba a todas las palabras que usan allot.

Corregido un error en .s.

Corregido un error en ctoggle, que provocaba que quedara la máscara en la pila.

Cambio: @execute ha sido modificada para hacer una ejecución incondicional. Su antigua función la hace ahora la nueva palabra @?execute: ejecuta la dirección solo si no es cero. Esto es más coherente con y más flexible.

Todo parece funcionar bien. Inicio de la versión A-01.

Primeros cambios para implementar el manejo de discos con +3DOS.

2015-01-13

DZX-ForthDZX-ForthDZX-Forth

Primeras palabras que funcionan llamando a +3DOS: set-user, get-user y restore-user (las originales de DX-Forth tenían nombres más crípticos).

Nuevas palabras: tib, y #tib a pesar de estar obsoletas en Forth actual (se pueden omitir con una etiqueta de configuración); dos-version, reescrita para +3DOS y renombrada (dosver en DX-Forth); dzx-forth devuelve su versión en el mismo formato que dosver; dos-error llama a throw con el código de error a partir de 1000; unused-disk (nombre provisional), devuelve el espacio libre en una unidad de disco; set-drive y get-drive.

interpret reconoce caracteres, con el formato de Forth-2012 (entre comillas simples).

Primeras modificaciones para crear una tabla de traducción de teclado que permita traducir cualquier código.

2015-01-14

DZX-Forth

Tabla de traducción de teclado implementada. La nueva palabra decode hace la traducción, llamada por key, que ha sido factorizada en (key) para hacerla configurable por la aplicación, como se pretende con todas las palabras de entrada y salida. La traducción de teclado permite hacer cualquier cambio de un código por otro, sin limitación: todo el rango de 0x00 a 0xFF. Se provee una tabla de traducción que permite acceder con solo la tecla Symbol Shift a los ocho signos ASCII que en Sinclair BASIC necesitan el modo extendido.

Inicio de la versión A-02.

DZX-Forth

Nuevas palabras: defer! y defer@, de Forth-2012:

; DEFER!  ( xt1 xt2 -- )  2012

  _header _public,'DEFER!'
defer_store_:
  call do_colon
  if defer_uses_body
    dw to_body_
  else ; XXX TODO -- not used yet
    dw one_plus_
  endif
  dw store_
  dw paren_exit_

; DEFER@  ( xt -- )  2012

  _header _public,'DEFER@'
defer_fetch_:
  ; XXX TODO faster with ix?
  pop de ; xt
  if defer_uses_body
    inc de ; address after the 'call' opcode
    inc de
    inc de ; pfa
  else ; XXX TODO -- not used yet
    inc de ; address after the 'jp' opcode
  endif
  ld a,(de)
  ld l,a
  inc de
  ld a,(de)
  ld h,a
  jp push_hl

Reescrita la palabra alias, de Gforth, para hacerla independiente de aka. aka es menos versátil porque espera los dos parámetros en el flujo de entrada. Esto mismo hace synonym en el estándar Forth-2012, pero en orden inverso más claro (primero el nombre nuevo y después el existente). aka será convertida más adelante en synonym.

; ALIAS  ( xt "newname" -- )  Gforth

; Note: alias doesn't copy the precedence bit of the xt

  _header _public,'ALIAS'
alias_:
  call do_colon
  dw header_
  dw c_lit_
  db alias_mask
  dw xnfa             ; set the alias mask of the header
  dw paren_exit_

Revisadas las palabras de conversión entre minúsculas y mayúsculas. Había un fallo en una.

Restaurado el comportamiento estándar de . (punto), que DX-Forth modificó para imprimir el número sin signo cuando la base no fuera decimal.

Nueva palabra: hex., tomada de Gforth, salvo que no imprime el prefijo «$»:

; HEX.  ( n -- )  Gforth
; Note: taken from Gforth, except "$" prefix.
  _header _public,'HEX.'
hex_dot_:
  call do_colon
  dw base_,fetch_
  dw swap_,hex_,u_dot_
  dw base_,store_
  dw paren_exit_

save es renombrada como save-system.

DZX-Forth

Creada la primera palabra de +3DOS para manejo de ficheros: file-position:

; FILE-POSITION  ( fid -- ud ior )

  _header _public,'FILE-POSITION'
file_position_:
  pop hl
  push bc
  ld b,l
  ld ix,dos_get_position
  call dos
  pop bc
  jr nc,file_position_.error

  ; ehl = position
  push hl
  ld d,0
  push de
  xor a ; no error
  jp push_a

file_position_.error
  ; a = error code
  push hl ; fake value
  push hl ; fake value
  call convert_dos_error_code
  jp push_hl ; error code

(emit) es modificada para poder ser llamada en alto o bajo nivel:

; (EMIT)  ( c -- )

  _header _public,'(EMIT)'
paren_emit_:
  pop hl
paren_emit_.l:
  ld a,l
paren_emit_.a:
  call emit_a
  jp pause_

emit_l:
  ld a,l
emit_a:
  rst 0x10
  ld a,0xff
  ld(sys_scr_ct),a ; no "scroll?" message
  ret

; EMIT  ( c -- )

  _header _public,'EMIT'
emit_:
  call fetch_execute_
  dw paren_emit_

at-xt y home (esta creada hace poco) son por ello corregidas. De hecho no funcionaban bien debido a la versión anterior de (emit):

; AT-XY  ( u1 u2 -- )
;   position cursor at col u1, row u2

  _header _public,'AT-XY'
at_xy_:

  if fast_emit

    ld a,at_char
    call emit_a
    pop hl
    call emit_l
    pop hl
    call emit_l
    jp next

  else

    call do_colon
  at_xy_.0:
    dw c_lit_
    db at_char
    dw emit_,emit_,emit_
    dw paren_exit_

  endif

; HOME  ( -- )  comus
;   position cursor at col 0, row 0

  _header _public,'HOME'
home_:

  if fast_emit

    if size_optimization

      ld hl,0
      push hl
      push hl
      jr at_xy_

    else

      ld a,at_char
      call emit_a
      xor a
      call emit_a
      xor a
      call emit_a
      jp next

    endif

  else

    call do_colon
    dw zero_,dup_
    dw branch_,at_xy_.0

  endif

Palabras para los colores, el brillo y la intermitencia:

DZX-Forth
; COLOR  ( +n c -- )
; Print control char c, char +n and update the permanent attributes.

; This word was inspired by the 'COLOR' defining word from Lennart
; Benchsop's Spectrum Forth-83 (1988).

; XXX TODO don't print the codes, just change the variables...

  _header _public,'COLOR'
color_:
  pop hl
color_.l:
  call emit_l
  pop hl
  call emit_l
  ld a,(sys_attr_t)
  ld (sys_attr_p),a
  jp next

; PAPER  ( n -- )
  _header _public,'PAPER'
paper_:
  ld l,paper_char
  _jump color_.l

; INK  ( n -- )
  _header _public,'INK'
ink_:
  ld l,ink_char
  _jump color_.l

; BRIGHT  ( n -- )
  _header _public,'BRIGHT'
bright_:
  ld l,bright_char
  _jump color_.l

; FLASH  ( n -- )
  _header _public,'FLASH'
flash_:
  ld l,flash_char
  _jump color_.l

Y una palabra para poner los colores predeterminados, quitar el brillo, la intermitencia y demás. Entre los posibles nombres, tanto de palabras que tiene DX-Forth para gestionar el vídeo en CP/M como de palabras propias de Sinclair BASIC o habituales en los Forth de ZX Spectrum, finalmente (o momentáneamente) se llama «normal»:

; NORMAL  ( -- )
  _header _public,'NORMAL'
normal_:
  call do_colon
  dw c_lit_
  db 7
  dw dup_,border_,paper_
  dw zero_,ink_
  dw zero_,flash_
  dw zero_,inverse_
  dw zero_,overprint_
  dw paren_exit_

2015-01-15

Programación de la primera palabra de acceso a disco, create-file... El manejo de ficheros apenas está comentado en DX-Forth, lo que dificulta la tarea. Probablemente la mayor parte del código original sea prescindible, pues las rutinas de +3DOS se ocupan de casi todo lo necesario. De momento, create-file no funciona bien si el método de acceso es distintode r/w.

; (CREATE-FILE)  ( ca fam fid -- fid ior )

  _header _public,'(CREATE-FILE)'
paren_create_file_:

  ; XXX INFORMER
  ; call do_colon
  ; dw drop_,drop_
  ; dw paren_exit_

  ; XXX INFORMER
  ; jp next

  pop de  ; e=fid
  pop hl
  ld d,l  ; fam
  pop hl  ; ca

  ; XXX INFORMER
  ; ld a,l
  ; ld (sys_seed),a
  ; ld a,h
  ; ld (sys_seed+1),a

  push de ; save fid (actually, fid + 256*fam)
  push bc ; save Forth IP

  ld b,e  ; fid
  ld c,d  ; fam
  ld d,2  ; create action: new file without header
  ld e,4  ; open action: erase and create
  ld ix,dos_open
  call dos

  pop bc  ; restore Forth IP
  ; (sp) = fid + 256*fam
  _jump_nc paren_create_file_.error
  ; no error
  ; (sp) = fid + 256*fam
  pop hl      ; l = fid
  ld h,0
  push hl     ; fid
  jp false_   ; no error

paren_create_file_.error
  ; (sp) = fid + 256*fam
  ; a = error code
  call convert_dos_error_code
  ; hl = error code
  jp push_hl

  ; ; XXX TMP
; filename
  ; ds filename_size

  ; ; XXX TMP
  ; ; ( a u -- a2 )
  ; _header _public,'n>f'
; n_to_f_
  ; ; Put a 0xFF at the end of the filename
  ; ; XXX experimental

  ; call do_colon
  ; dw c_lit_
  ; db 15
  ; dw min_
  ; dw plus_
  ; dw c_lit_
  ; db 0xFF
  ; dw swap_,c_store_
  ; dw lit_,filename
  ; dw paren_exit_

; CREATE-FILE  ( ca u fam -- fid ior )

  _header _public,'CREATE-FILE'
create_file_:

  call do_colon
  dw to_r_,to_fname_,one_plus_,from_r_
  ; ( ca2 fam )
  dw get_file_handle_,question_dup_
  dw branch_if_zero_,create_file_.no_file_handle
  ;  ( ca2 fam fid )
  dw paren_create_file_
  dw paren_exit_

create_file_.no_file_handle
  ;  ( ca2 fam )
  dw drop_ ; leave only ca2, as an undefined fid
  dw c_lit_
  db 4 ; XXX TODO -- adapt this error code
  dw to_ior_
  dw paren_exit_

Nueva palabra: close-file.

; CLOSE-FILE  ( fid -- ior )

  _header _public,'CLOSE-FILE'
close_file_:
  pop hl
  push bc ; save Forth IP
  ld b,l  ; fid
  ld ix,dos_close
  call dos
  pop bc  ; restore Forth IP

back_from_dos:
  jp c,false_   ; no error
  ; a = error code
  call convert_dos_error_code
  ; hl = error code
  jp push_hl

2015-01-16

Nueva palabra: delete-file. La rutina DOS_DELETE de +3DOS no está documentada en el manual publicado en wos, pero su dirección y parámetros son fácilmente adivinables:

; (DELETE-FILE)  ( ca -- ior )
; ca = address of filename, with a trailing 0xFF

  _header _public,'(DELETE-FILE)'
paren_delete_file_:
  pop hl
  ld ix,dos_delete
  call do_dos
  jr back_from_dos

; DELETE-FILE  ( ca u -- ior )
; ca u = filename

  _header _public,'DELETE-FILE'
delete_file_:
  call do_colon
  dw to_fname_,one_plus_
  dw paren_delete_file_
  dw paren_exit_

Nuevas palabras: cell, 4 y float.

El soporte para ficheros de bloques se convierte en opcional, con compilación condicional. Desactivar esta funcionalidad ahorra 1743 octetos. (Hacer lo mismo con el soporte para punto flotante ahorra 2336 octetos).

La palabra write-file ya funciona. Aún falta un método para añadir la cabecera a un fichero, al abrirlo, por lo que de momento los ficheros creados desde Forth no pueden ser cargados en memoria por Sinclair BASIC.

; (WRITE-FILE)  ( ca len fid +n -- ior )
;
; +n = RAM bank paged at 0xC000..0xFFFF

  _header _public,'(WRITE-FILE)'
parent_write_file_:
  pop hl ; l = bank
  ld a,l
paren_write_file_.bank_a:
  call save_ip
  ld c,a
  pop hl
  ld b,l ; fid
  pop de ; len
  pop hl ; ca
  ld ix,dos_write
  call dos
  call restore_ip
  _jump back_from_dos

; WRITE-FILE  ( a u fid -- ior )

  _header _public,'WRITE-FILE'
write_file_:
  ld a,(sys_bankm)
  and %111 ; current page for 0xC000..0xFFFF
  _jump paren_write_file_.bank_a

La palabra UNNEST ha sido renombrada como RDROP, que el el nombre genérico y común de la operación. Sin embargo UNNEST ha sido recreada después como un alias, pues conviene que sus entradas en el glosario sean diferentes.

2015-01-17

Ya funciona WRITE-LINE también. Esta ha sido muy fácil, porque solo llama dos veces a WRITE-FILE. He añadido la posibilidad de configurar la cadena que se usa como final de línea. Esto permite crear ficheros de texto para otros sistemas operativos.

; NEW-LINE  ( -- ca len )  ; XXX NEW
; After Gforth's 'newline', but configurable
; String used by 'WRITE-LINE' as end of line

  _header _public,'NEW-LINE'
new_line_:
  call do_two_constant
  dw new_line_.string,1 ; address and length
new_line_.string:
  db cr_char ; default

; WRITE-LINE  ( ca u fileid -- ior )
;     dup >r write-file ?dup if  r> rdrop exit  then
;     new-line r> write-file
  _header _public,'WRITE-LINE'
write_line_:
  call do_colon
  dw dup_,to_r_
  dw write_file_
  dw question_dup_
  dw branch_if_0_,write_line_.end_of_line
  dw r_drop_
  dw paren_exit_
write_line_.end_of_line:
  dw new_line_
  dw from_r_
  dw write_file_
  dw paren_exit_

Y read-file:

; (READ-FILE)  ( ca len fid +n -- ior )
;
; +n = RAM bank paged at 0xC000..0xFFFF

  _header _public,'(READ-FILE)'
parent_read_file_:
  pop hl ; l = bank
  ld a,l
paren_read_file_.bank_a:
  call save_ip
  ld c,a
  pop hl
  ld b,l  ; fid
  pop de  ; len
  pop hl  ; ca
  push de ; len
  ld ix,dos_read
  call dos
  pop hl ; len
  call restore_ip
  jr c,paren_read_file_.no_error

  ; error
  ; hl = len
  ; a = error code
  ; de = number of bytes remaining unread
  push af
  call hl_minus_de_to_hl
  ; hl = bytes actually read
  pop af
  cp 21 ; is it "bad parameter"? XXX TODO label
  jr z, paren_read_file_.no_error
  push hl
  call convert_dos_error_code
  jp push_a

paren_read_file_.no_error:
  ; no error
  ; hl = len
  push hl
  jp false_

; READ-FILE  ( a u1 fid -- u2 ior )

  _header _public,'READ-FILE'
read_file_:
  ld a,(sys_bankm) ; XXX TODO factor out if size_optimization
  and %111 ; current page for 0xC000..0xFFFF
  _jump paren_read_file_.bank_a

Y open-file, que de momento creo con una copia del código de create-file, pues basta con cambiar los parámetros de llamada a +3DOS. El siguiente paso será extraer el código común y crear con él una palabra.

; (OPEN-FILE)  ( ca fam fid -- fid ior )

  _header _public,'(OPEN-FILE)'
paren_open_file_:

  pop de  ; e=fid
  pop hl
  ld d,l  ; fam
  pop hl  ; ca
  push de ; save fid (actually, fid + 256*fam)
  push bc ; save Forth IP
  ld b,e  ; fid
  ld c,d  ; fam
  ld d,0  ; create action: error, file does not exist
  ld e,1  ; open action: position after the header ; XXX TODO configurable
  ld ix,dos_open
  call dos
  pop bc  ; restore Forth IP
  ; (sp) = fid + 256*fam
  _jump_nc paren_open_file_.error
  ; no error
  ; (sp) = fid + 256*fam
  pop hl      ; l = fid
  ld h,0
  push hl     ; fid
  jp false_   ; no error

paren_open_file_.error
  ; (sp) = fid + 256*fam
  ; a = error code
  call convert_dos_error_code
  ; hl = error code
  jp push_hl

; OPEN-FILE  ( ca len fam -- fid ior )

  _header _public,'OPEN-FILE'
open_file_:

  call do_colon
  dw to_r_,to_fname_,one_plus_,from_r_
  ; ( ca2 fam )
  ;dw get_file_handle_
  dw two_ ; XXX TMP
  dw question_dup_
  dw branch_if_zero_,open_file_.no_file_handle
  ;  ( ca2 fam fid )
  dw paren_open_file_
  dw paren_exit_
open_file_.no_file_handle
  ;  ( ca2 fam )
  dw drop_ ; leave only ca2, as an undefined fid
  dw c_lit_
  db 4 ; XXX TODO -- adapt this error code
  dw to_ior_
  if  debug?
    dw paren_dot_quote_
    dcs 'open_file error'
  endif
  dw paren_exit_

Todo funciona pero, aparte de las muchas palabras aún pendientes relacionadas con ficheros, hay dos cuestiones importantes por resolver:

Hago una limpieza de todo el código antiguo relacionado con manejo de ficheros. Esto ahorra alrededor de un kibiocteto.

Renombro token como parse-name, que es el que usan Gforth y el estándar Forth 2012 para esa función.

2015-01-21

La palabra de DX-Forth getfh podría reescribirse de forma más sencilla, pues +3DOS hace innecesario mantener bloques de información sobre los ficheros abiertos. Como siempre en estos casos, le doy un nuevo nombre menos críptico y más homogéneo con el resto: file-id. Hice primero varios prototipos en Gforth:
#! /usr/bin/env gforth

\ 2015-01-21: Prototypes for DZX-Forth's file-ids.

15              constant max-file-id
max-file-id 1+  constant max-file-ids

create file-id-table  max-file-ids allot

file-id-table max-file-ids erase

\ First version

: file-id  ( -- fid true | false )
  file-id-table max-file-ids 0
  do
    dup i + dup c@ 0=
    if    0xFF swap c!  drop i true  unloop exit
    then  drop
  loop  drop false
  ;

\ Second version

: file-id  ( -- fid true | false )
  file-id-table max-file-ids bounds
  do
    i c@ 0=
    if  0xFF i c!  i file-id-table - true  unloop exit  then
  loop  false
  ;

\ Third version

: file-id  ( -- fid true | false )
  max-file-id
  begin
    dup file-id-table + dup  ( fid a a )
    c@
    if    drop 1- dup 0<
    else  0xFF swap c!  true exit  then
  until  drop false
  ;

La tercera versión del prototipo, implementada en DZX-Forth:
; FILE-ID  ( -- fid true | false )
  _header _public,'FILE-ID'
file_id_:
  call do_colon
  dw max_fcb_
file_id_.begin:
  dw dup_
  dw file_id_table_,plus_,dup_,c_fetch_
  dw branch_if_0_,file_id_.found
  dw drop_,one_minus_,dup_,zero_less_
  ; XXX TODO use '0>=' when available, and save one branch
  dw branch_if_0_,file_id_.begin
  dw branch_,file_id_.begin
file_id_.found:
  dw c_lit_
  db 0xFF
  dw swap_,c_store_
  dw true_
file_id_.end:
  dw paren_exit_

2015-01-23

Nueva palabra s/r, para representar el método de acceso de ficheros con lectura compartida, propio de +3DOS.

; S/R  ( -- fam )
;
; shared-read file access method

  _header _public,'S/R'
shared_read_:
  call do_byte_constant
  db 5

Había un problema con create-file. Hago cada prueba siguiente tras un reinicio de la máquina, para estar seguro de que no hay números de fichero en uso u otras condiciones:

  s" zx1" r/o create-file . . 1030 270 ( 30=access denied, file in use)

  s" zx2" w/o create-file . . 0 15 ( 0=no error)

  s" zx3" r/w create-file . . 0 15 ( 0=no error)

  s" zx4" s/r create-file . . 1030 1295 ( 30=access denied, file in use)

El resultado prueba que +3DOS no puede crear ficheros nuevos como de solo lectura, de lectura compartida. Esto habrá que solucionarlo cambiando el modo de acceso y restableciéndolo tras la creación del fichero...

Implementada la palabra reposition-file:

; REPOSITION-FILE  ( ud fid -- ior )
;
; ud = file pointer
;
; Note: the most significant byte of ud is ignored, because the file pointer
; range is 0x000000..0xFFFFFF (0..16777215).

  _header _public,'REPOSITION-FILE'
reposition_file_:
  pop hl
  ld d,l  ; fid
  pop hl  ; high 16-bit part of the file pointer
  ld e,l  ; actual most significant byte of the file pointer
  pop hl  ; low 16-bit part of the file pointer
  push bc ; save the Forth IP
  ld b,d  ; fid
  ld ix,dos_set_position
  call dos
  pop bc  ; restore the Forth IP
  jp back_from_dos

Implementada la palabra abandon-file, para facilitar el acceso a la rutina correspondiente de +3DOS:

; ABANDON-FILE  ( fid -- ior )

  _header _public,'ABANDON-FILE'
abandon_file_:
  pop hl
  push bc ; save the Forth IP
  ld b,l  ; fid
  ld ix,dos_abandon
  call dos
  pop bc  ; restore the Forth IP
  _jump back_from_dos

Nueva palabra headed para modificar el método de acceso a los ficheros y permitir usar o no cabeceras de +3DOS:

; HEADED  ( fam1 -- fam2 )

  _header _public,'HEADED'
headed_:
  pop hl
  set 7,l
  push hl
  jp next

Las palabras (create-file) y (open-file) han sido modificadas para modificar sus acciones dependiendo de si headed ha sido ejecutado, así como para compartir código que tenían en común.

2015-01-24

Nueva versión de file-position, con un par de instrucciones menos y con la parte final, tras el regreso de la rutina de +3DOS, reutilizable:

; FILE-POSITION  ( fid -- ud ior )
;
; ud = file pointer
;
; Note: the most significant byte of ud is zero, because the file pointer range
; is 0x000000..0xFFFFFF (0..16777215).

  _header _public,'FILE-POSITION'
file_position_:
  pop hl
  push bc ; save the Forth IP
  ld b,l ; fid
  ld ix,dos_get_position

dos_ehl:

  ; Call a DOS routine that returns a number in EHL and, as usual, an error
  ; code in A. Leave EHL on the stack as a double number, and then A.

  ; Input:
  ;   b    = file number
  ;   ix   = DOS routine
  ;   (sp) = Forth IP
  ; Output stack:
  ;   ( d ior )
  ;   Where d is the content of registers ehl

  call dos
  pop bc ; restore the Forth IP
  ; ehl = result
  push hl ; low part
  ld d,0
  push de ; high part
  jp back_from_dos

La palabra file-size es implementada así, aprovechando el código reutilizable de file-position:

; FILE-SIZE  ( fid -- ud ior )

  _header _public,'FILE-SIZE'
file_size_:

  pop hl
  push bc ; save the Forth IP
  ld b,l ; fid
  ld ix,dos_get_eof
  jp dos_ehl

DZX-Forth

Primeras pruebas con las palabras de gestión de ficheros de bloques: open-block-file y block funcionan, pero todo el módulo necesita ser comprobado y probablemente simplificado.

Corregido un error en close-file, que dejaba en la pila el supuesto error 1000 en lugar de 0. Este error fue introducido al añadir el código que libera el identificador de fichero. La operación add hl,de modificaba la bandera de acarreo, que era comprobada al entrar en la rutina back_from_dos. Con unos cambios, queda así:

; CLOSE-FILE  ( fid -- ior )

  _header _public,'CLOSE-FILE'
close_file_:
  pop hl
  push hl ; save the fid
  push bc ; save the Forth IP
  ld b,l  ; fid
  ld ix,dos_close
  call dos
  pop bc  ; restore the Forth IP
  pop de  ; restore the fid
  _jump_nc back_from_dos.error
  ; no error; mark the file id as free
  ld hl,file_id_table
  add hl,de
  ld (hl),0
  jp false_

Primera versión de read-byte y write-byte, para acceder a las llamadas correspondientes de +3DOS:

; READ-BYTE  ( fid -- b ior )

; XXX FIXME -- No EOF error: bytes 229 are read after the end of file (the byte
; used to format a disk).

  _header _public,'READ-BYTE'
read_byte_:
  pop hl
  push bc ; save the Forth IP
  ld b,l ; fid
  ld ix,dos_byte_read
  call dos
  ld d,0
  ld e,c  ; byte
  pop bc  ; restore the Forth IP
  push de ; byte
  jp nc,dos_error_.a
  jp zero_

; WRITE-BYTE  ( b fid -- ior )

  _header _public,'WRITE-BYTE'
write_byte_:
  pop hl
  pop de
  push bc ; save the Forth IP
  ld b,l ; fid
  ld c,e ; byte
  ld ix,dos_byte_write
  call dos
  pop bc  ; restore the Forth IP
  jp back_from_dos

Corregido un error en (read-file): el número de error de +3DOS no era convertido al formato del sistema.

Más cambios de nombres, para seguir unificándolos de acuerdo al criterio elegido: zbuf para a ser filename-buffers; lastfile se convierte en last-filename; >fname se llama ahora >filename-buffer.

Modifico la palabra filetype? y la renombro como filetype: ahora devuelve la cadena, no solo su longitud, lo cual es más versátil. Para adaptar las palabras que usaban la versión antigua basta añadir un nip.

Elimino la palabra .lastfile, que solo se usaba en operaciones interactivas relacionadas con ficheros de bloques, que serán eliminadas también.

Escribo rename-file:

; (RENAME-FILE)  ( ca1 ca2 -- ior )
;
; ca1 = current filename, as a 0xFF-terminated string
; ca2 = new filename, as a 0xFF-terminated string

  _header _public,'(RENAME-FILE)'
paren_rename_file_:
  pop de
  pop hl
  ld ix,dos_rename
  call do_dos
  jp back_from_dos

; RENAME-FILE  ( ca1 len1 ca2 len2 -- ior )
;
; ca1 len1 = current name
; ca2 len2 = new name

  _header _public,'RENAME-FILE'
rename_file_:
  call do_colon
  dw to_filename_buffer_,one_plus_,to_r_
  dw to_filename_buffer_,one_plus_,from_r_
  dw paren_rename_file_
  dw paren_exit_

Dado que +3DOS necesita la dirección del comienzo del nombre de fichero, terminado en 0xFF, la palabra >filename-buffer siempre necesita ir seguida de 1+. Será conveniente factorizar >filename-buffer para evitar 1+ en cada llamada.

Por otra parte, hay un problema pendiente: La dirección de la cadena devuelta por s" y otras palabras similares siempre es la misma, hay un único tampón para textos:

s" current" s" new" .s

Resultado en pantalla:

<4> 25733 7 25733 3  ok

Por supuesto, no hay problema cuando las cadenas son compiladas en el diccionario, dentro de una palabra:

: files  s" current" s" new" ;
files .s

Resultado en pantalla:

<4> -18721 7 -18711 3  ok

Esta limitación impide de momento usar s" más de una vez en modo de interpretación (por ejemplo: s" old" s" new" rename-file) sin guardar antes la primera cadena en lugar seguro, por ejemplo así:

create filename1 16 allot
s" current" filename1 place
filename1 count s" new" rename-file

La solución será implementar un tampón circular de cadenas de texto integrado en el propio sistema, de longitud configurable. En su día escribí dos herramientas de este tipo en Forth: sbuffer y csb2.

Otro asunto pendiente: +string necesita unos cambios para recibir las cadenas en orden inverso en la pila, que es más lógico, pero en cualquier caso puede optimizarse haciendo que el código se modifique a sí mismo. La versión actual procedende de DX-Forth es:

; +STRING  ( ca1 len1 ca2 len2 -- ca2 len3 )
;
; Append the string ca1 len1 to the end of string ca2 len2
; returning the resulting string ca2 len3.  It is the programmer's
; responsibility to ensure sufficient room is available at ca2
; to hold both strings.
;
; Note: uses temp 1-3.
;
;   2swap swap 2over + 2 pick cmove +
;
; XXX TODO rename and change the order of parameters,
; or keep this word and do a wrapper with the parameters swapped.

  _header _public,'+STRING'
plus_string_:
  ld l,c
  ld h,b
  ld (t3),hl    ; bsave
  pop de
  pop hl
  ld (t2),hl    ; ca2
  add hl,de
  ex de,hl
  pop bc
  add hl,bc
  ld (t1),hl    ; len3
  pop hl
  call move_block_downwards
  ld hl,(t3)    ; bsave
  ld c,l
  ld b,h
  ld hl,t1
  jp two_fetch_.hl

t1: ds cell
t2: ds cell
t3: ds cell

Tras unas modificaciones el código es más rápido, compacto y legible:

  _header _public,'+STRING'
plus_string_:

  ; Save the Forth IP
  ld l,c
  ld h,b
  ld (plus_string_.ip),hl

  ; Concatenate the strings
  pop de
  pop hl
  ld (plus_string_.address),hl ; save ca2
  add hl,de
  ex de,hl
  pop bc
  add hl,bc
  ld (plus_string_.length),hl ; save len3
  pop hl
  call move_block_downwards

  ; Restore the Forth IP
plus_string_.ip: equ $+1
  ld bc,0

  ; Return the string
plus_string_.address: equ $+1
  ld hl,0
  push hl
plus_string_.length: equ $+1
  ld hl,0
  if size_optimization?
    jp push_hl
  else
    push hl
    jp next
  endif

2015-01-26

El código relacionado con los números de coma flotante que aún era parcheado durante la ejecución (la inicialización; la identificación en el mensaje de arranque; y la comprobación de números en interpret) ha sido modificado para usar directamente el ensamblado condicional.

La palabra (-fp) es eliminada.

Corrijo un error recientemente introducido en last-filename.

Hay un problema en interpret: la pila se vacía durante la interpretación de un bloque. Esto impide usar load. Creo puntos nuevos deduración para rastrear el error, y una macro para facilitar la tarea:

_debug: macro message,flag
  local show
  show: defl false
  if not nul flag
    show: defl flag
  endif
  if debug? or show
    dw cr_,paren_dot_quote_
    _dcs message
    dw cr_,dot_s_
    dw cr_,paren_dot_quote_
    _dcs "Press any key"
    dw key_,drop_,cr_
  endif
  endm

Tras rastrear el código paso a paso descubro que las palabras de división no funcionan, no devuelven el resultado. Tras un nuevo rastreo, descubro el error en el código de más bajo nivel, en las rutinas matemáticas de división escritas en Z80: la causa era una confusión entre algunas de las etiquetas push_de y push_de_hl tras su conversión desde DX-Forth. Tras buscar las etiquetas en todo el código, corrijo también las palabras d2* y d2/, que sufrían el mismo problema aunque aún no había sido detectado.

DZX-Forth

Tras estas últimas correcciones, load funciona por fin. Ya es posible compilar código fuente de los ficheros de bloques, aunque hay una pega: las palabras de DX-Forth que fueron renombradas o eliminadas. Para las pruebas es necesario recrearlas como sinónimos o palabras inoperantes; más adelante podré reunirlas en un solo fichero para servir como capa de adaptación para cargar fuentes de DX-Forth. En todo caso las fuentes que DX-Forth incluye como ficheros de bloques serán adaptadas finalmente.

2015-01-28

DZX-Forth

Primera versión de una capa de compatibilidad para poder cargar fuentes de DX-Forth. Contiene alias del las palabras renombradas, y versiones inútiles o equivalentes de las que han cambiado de función o han sido eliminadas.

.( DX-Forth layer for DZX-Forth - block 1) cr
' .wid alias .voc
' >filename-buffer alias >fname
' compiling? alias state?
' cs0 alias cf0
' current-block-file alias loadfile
\ ' current-stream-file alias fname
' dos-version alias dosver
' file-id alias getfh
' filename-buffers alias zbuf
' last-filename alias lastfile
' load-file alias fload
' noop alias -caps immediate
' noop alias caps immediate
\ variable sys
-->
.( DX-Forth layer for DZX-Forth - block 2) cr
' noop alias system immediate
' noop alias application immediate
' page alias cls
' parse-filename alias getfilename
' parse-name alias token
' save-system alias save
\ ' set-fcb alias setfcb
' wordlist-find alias wfind
' wordlist>name alias w>name
: filetype?  ( -- len )  filetype nip  ;
' noop alias >drive
: path drop s" xxx" 0 ;
' open-block-file alias open
' here alias sys-vec \ fake
' drop alias remember

DZX-Forth

Tras lograr cargar el fichero de bloques tools.fb, que incluye la definición de las palabras necesarias para cargar ficheros fuente en formato normal, ya es posible usar include. Pero el primer intento falla. Hay que revisar todo el código de estas palabra para encontrar el motivo.

2015-01-29

Los ficheros de bloques lina (la versión de ciforth para Linux) tienen una peculiaridad: caracteres de salto de línea. Esto permite combinar las ventajas del formato de bloques (fácil implementación y rapidez de uso) con las ventajas de los ficheros de texto convencionales (edición más fácil y cómoda). Por otra parte, Gforth, a la hora de interpretar código fuente, trata los caracteres de final de línea, así como los tabuladores, como espacios, lo cual es muy flexible pues permite usar diferentes tipos de texto como código fuente.

Para implementar ambas características en DZX-Forth he escrito una palabra que traduce los caracteres de control citados a espacios, y que es llamada por (evaluate):

; (CONTROL>BL)  ( ca len -- ca len )
; 
; Change all end-of-word control chars (CR, LF, TAB)
; to spaces in the given text.

  _header _public,'(CONTROL>BL)'
paren_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 cr_char
  jp z, paren_control_to_bl_.space
paren_control_to_bl_.check_tab:
  cp 9 ; standard TAB char (not ZX Spectrum TAB char)
  jp z, paren_control_to_bl_.space
paren_control_to_bl_.check_lf:
  cp lf_char
  jp nz, paren_control_to_bl_.repeat
paren_control_to_bl_.space:
  ld (hl),sp_char
paren_control_to_bl_.repeat:
  inc hl
  jp paren_control_to_bl_.begin

; CONTROL>BL  ( ca len -- ca len )
; 
; Convert end-of-word control chars (CR, LF, TAB) to spaces.

  _header _public,'CONTROL>BL'
control_to_bl_:
  call fetch_execute_
  dw paren_control_to_bl_

Sin embargo este método tiene una desventaja: corrompe posibles caracteres de control empotrados en cadenas de caracteres. Estos caracteres no son habituales ni recomendables en un código fuente, pero aun así esa capa de traducción no parece una buena solución. Un método alternativo, más lento, sería añadir la detección de los caracteres de control al algoritmo de (interpret). En cualquier caso este comportamiento debe ser opcional.

Un pequeño cambio: sustituyo cada dp @ por here, pues es más lógico aunque sea más lento. Más adelante se podrá elegir uno u otro código según la opción de ensamblado size_optimization?.

Tras escribir un prototipo de tampón circular para cadenas de texto y probarlo en Gforth, lo traslado en DZX-Forth en alto nivel, pero más adelante la traduciré a ensamblador:

csb_size:   equ 512
csb_unused: dw cbs_size
csb0:       ds cbs_size

; CSB-UNUSED  ( -- len )
  _header _public,'CSB-UNUSED'
csb_unused_:
  ld hl,(csb_unused)
  jp push_hl

; CSB0  ( -- a )
  _header _public,'CSB0'
csb0_:
  ld hl,csb0
  jp push_hl

; ?CSB  ( len -- )
; Make sure there's room for the given characters.
  _header _hidden,'?CSB'
question_csb_:
  call do_colon
  dw dup_,lit_,csb_unused,fetch_,greater_
  dw branch_if_false_,question_csb_.enough
  ; not enough space; reset the pointer
  dw lit_,cbs_size
  dw lit_,csb_unused,store_
question_csb_.enough:
  dw negate_,lit_,csb_unused,plus_store_
  dw paren_exit_

; STRING-ALLOCATE  ( len -- ca )
  _header _hidden,'STRING-ALLOCATE'
string_allocate_:
  call do_colon
  dw question_csb_
  dw csb0_,csb_unused_,plus_
  dw paren_exit_

; SAVE-STRING  ( ca1 len1 -- ca2 len1 )
  _header _public,'SAVE-STRING'
save_string_:
  call do_colon
  dw dup_,string_allocate_,swap_
  dw two_dup_,two_to_r_
  dw move_,two_from_r_
  dw paren_exit_

; SAVE-COUNTED-STRING  ( ca1 len1 -- ca2 )
  _header _public,'SAVE-COUNTED-STRING'
save_counted_string_:
  call do_colon
  dw dup_,one_plus_,string_allocate_
  dw dup_,to_r_,place_,from_r_
  dw paren_exit_

Los cambios necesarios para que parse y word y palabras relacionadas usen el tampón circular de texto son casi triviales. Por ahora esta característica es opcional, con ensamblado condicional, pero finalmente el sistema antiguo será eliminado, pues el tampón circular de texto no tiene ningún inconveniente: en caso necesario se puede reducir su tamaño hasta un límite en que funcionaría igual que el sistema anterior, como si el tampón no fuera circular.

2015-01-31

Primeros cambios para usar los códigos de error estándar a través de throw, en lugar de imprimir mensajes con abort". Por ejemplo, de esto:

; ?COMP  ( -- )
;   executing? abort" compilation only'

  _header _public,'?COMP'
question_comp_:
  call do_colon
  dw executing?
  dw paren_abort_quote_
  _dcs 'compilation only'
  dw paren_exit_

A esto:

; ?COMP  ( -- )
;   executing? -14 and throw

  _header _public,'?COMP'
question_comp_:
  call do_colon
  dw executing?
  dw lit_,-14 ; interpreting a compile-only word
  dw and_,throw_
  dw paren_exit_

El uso de throw permite atrapar los errores. Además se ahorra el espacio ocupado por los mensajes, aunque más adelante se implementará un sistema opcional para que throw los muestre.

Mejora en header: se distinguen los casos de que la longitud del nombre sea cero y de que sea mayor de lo permitido.