Solo Forth development history in 2015-08

Description of the page content

Solo Forth development history in 2015-08.

Tags:

2015-08-11

Fixed a silly bug in parsed that crashed the system.

Checked the search order words, based on F83 and already implemented in the kernel. Removed their old versions from the library disk.

First draft of get-order and set-order, in the library disk.

Fixed next,, pushhl,, pushde, and fetchhl,: they were defined as constants.

Fixed message: error>line was used in error instead of message, therefore message printed a wrong message number, because it was already converted to its line.

Fixed perform: the position of the check was wrong and the fetched cfa got corrupted.

Finished, documented and checked case: and associative:, adapted from F83.

2015-08-12

New: lshift.

Change: the code of 2/, adapted from DZX-Forth, was too complex, because its origin is DX-Forth, that uses instructions compatible with the 8080.

  _code_header two_slash_,'2/'

; doc{
;
; 2/  ( x1 -- x2 )  \ ANS Forth
;
; _x2_ is the result of shifting _x1_ one bit toward the
; least-significant bit, leaving the most-significant bit
; unchanged.
;
; This is the same as `s>d 2 fm/mod swap drop`. It is not the same
; as `2 /`, nor is it the same as `1 rshift`.
;
; }doc

  ; [Code from DZX-Forth. Documentation partly based on lina
  ; ciforth.]

  pop hl
  call sra_hl
  jp push_hl

  ; Shift right arithmetic

sra_hl:
  ld a,h
  rlca
  rrca
sra_hl.1:
  ; cy=sign bit of hl
  ; a=h
  rra
  ld h,a
  ld a,l
  rra
  ld l,a
  ret

Beside, the routine is not used yet by other words (DZX-Forth does). The new code is adapted from Spectrum Forth-83:

  pop hl
  sra h
  rr l
  jp push_hl

The Abersoft Forth case is removed from the kernel. It was already in the library disk, and other versions will be added:

; ----------------------------------------------
  _colon_header case_,'CASE',immediate

  dw question_comp_
  dw csp_,fetch_,store_csp_
  dw c_lit_
  db 0x04
  dw semicolon_s_

; ----------------------------------------------
  _colon_header of_,'OF',immediate

  dw c_lit_
  db 0x04
  dw question_pairs_
  dw compile_,over_
  dw compile_,equals_
  dw compile_,zero_branch_
  dw forward_mark_
  dw compile_,drop_
  dw c_lit_
  db 0x05
  dw semicolon_s_

; ----------------------------------------------
  _colon_header endof_,'ENDOF',immediate

  dw c_lit_
  db 0x05
  dw question_pairs_
  dw compile_,branch_
  dw forward_mark_
  dw swap_
  dw two_ ; error checking number
  dw then_
  dw c_lit_
  db 0x04
  dw semicolon_s_

; ----------------------------------------------
  _colon_header endcase_,'ENDCASE',immediate

  dw c_lit_
  db 0x04
  dw question_pairs_
  dw compile_,drop_
endcase.compile_then: ; begin
  dw sp_fetch_,csp_,fetch_,not_equals_ ; while
  dw question_branch_,endcase.end
  dw two_,then_
  dw branch_,endcase.compile_then ; repeat
endcase.end:
  dw csp_,store_
  dw semicolon_s_

The Abersoft Forth version of case includes security checks during compilation and needs 112 bytes of dictionary. The ANS Forth version (adapted from the ANS Forth documentation) does no compilation check and needs only 56 bytes.

Improvement: now the fig-Forth compiler security checks are optional, with conditional compilation in the kernel.

Fixed cold: Since the search order was implemented, and root and assembler were created, cold didn't restored the original lastest definitions of the vocabularies.

Improved abort: Now it does not show the greeting message (cold does it instead), does not check the stack (it was useless) and does not change the search order or current. It simply does what ANS Forth says.

Renamed ?terminal (the original fig-Forth name) to break-key? (clearer) and moved it off the kernel to the library disk.

Removed old unused config options of the kernel and their related code:

Added ahead and modified else with it.

Moved label to the forth vocabulary.

2015-08-13

First draft of ANS Forth version of key and key?. First changes to convert the original key to an alternative version with additional features.

New: >number, from DZX-Forth.

Converted at to ANS-Forth at-xy.

Fixed border: it didn't update the BORDCR system variable, what caused G+DOS could not restore the border color (G+DOS, by default, changes the border color during disk operations).

  _code_header border_,'BORDER'

  pop hl
  ld a,l
  out(0xFE),a

  ; The system variable that holds the attributes of the lower
  ; part of the screen, unnecessary in Solo Forth, must be
  ; updated.  The reason is G+DOS, after disk operations that
  ; make the border change, restores the border color with the
  ; value of this system variable.  We use the border color as
  ; paper and set a a contrast ink (black or white), to make
  ; sure the lower part of the screen is usable after returning
  ; to BASIC.

  cp 4 ; cy = dark color (0..3)?
  ld a,7 ; white ink
  jr c,border.end
  xor a ; black ink

border.end:
  add hl,hl
  add hl,hl
  add hl,hl ; paper
  ld l,a  ; ink
  ld (sys_bordcr),hl
  _jp_next

Removed #whiles and all related code, an approach to implement multiple while in control structures, already deactivated with conditional compilation. It was a bad idea that introduced new problems.

Added du.r, u.r, and du. to the library disk. They probably will be moved to the kernel.

2015-08-14

Improved dnegate: saved one byte!

Fixed a recent bug in decode: sp0 sp! was used when quitting, instead of sp0 @ sp!!

Checked the for step structure, that was adapted from Z88 CamelForth:

; ----------------------------------------------
  _code_header paren_step_,'(STEP)'

  ld hl,(return_stack_pointer)
  ld e,(hl)
  inc hl
  ld d,(hl) ; de = loop index
  dec de
  ld (hl),d
  dec hl
  ld (hl),e ; update the loop index
  ld a,d
  or e ; zero?
  jp nz,branch_pfa ; loop again if not zero
  ; done, discard loop index
  inc hl
  inc hl
  ld (return_stack_pointer),hl
  ; skip branch offset
  inc bc
  inc bc
  _jp_next

; ----------------------------------------------
  _colon_header for_,'FOR',immediate

  dw compile_,to_r_,backward_mark_
if fig_compiler_security?
  dw c_lit_
  db 6 ; error checking number
endif
  dw semicolon_s_

; ----------------------------------------------
  _colon_header step_,'STEP',immediate

if fig_compiler_security?
  dw c_lit_
  db 6 ; error checking number
  dw question_pairs_
endif
  dw compile_,paren_step_,backward_resolve_
  dw semicolon_s_

Moved it off the kernel to the library disk, improved it (after Gforth: now the index is checked before decrementing it), removed the fig-Forth compiler security checks and documented it:

( for step )

  \ [Code adapted from Z88 CamelForth. Modified to do the check
  \ before decrementing the index.]

code (step)  ( R: n -- n' )

  \ doc{
  \
  \ (step)
  \
  \ The run-time procedure compiled by `step`.
  \
  \ Run-time:    ( R: u -- u' )
  \
  \ If the loop index is zero, discard the loop parameters and
  \ continue execution after the loop. Otherwise decrement the
  \ loop index and continue execution at the beginning of the
  \ loop.
  \
  \ }doc

  2A c, rp ,
    \ ld hl,(return_stack_pointer)
  5E c, 23 c, 56 c,
    \ ld e,(hl)
    \ inc hl
    \ ld d,(hl) ; de = loop index
  7A c, B3 c,
    \ ld a,d
    \ or e ; z=already zero?
  1B c, 72 c, 2B c, 73 c,
    \ dec de
    \ ld (hl),d
    \ dec hl
    \ ld (hl),e ; update the loop index
  C2 c, ' branch cfa>pfa ,
    \ jp nz,branch_pfa ; loop again if not zero
    \ ; done, discard loop index:
  23 c, 23 c, 22 c, rp ,
    \ inc hl
    \ inc hl
    \ ld (return_stack_pointer),hl
    \ ; skip branch offset and jump to next
  03 c, 03 c, next,
    \ inc bc
    \ inc bc
    \ jp next

  end-code

: for  ( n -- )  postpone >r <mark  ; immediate

  \ doc{
  \
  \ for  Compilation: ( R: -- dest ) Run-time:    ( u -- )
  \
  \ Start of a `for step` loop, that will iterate _u+1_ times,
  \ starting with _u_ and ending with 0.
  \
  \ The current value of the index can be retrieved with `i`.a
  \
  \ }doc

: step  ( -- )  postpone (step) <resolve  ; immediate

  \ doc{
  \
  \ step
  \
  \ Compilation: ( dest -- )
  \
  \ Append the run-time semantics given below to the current
  \ definition. Resolve the destination of `for`.
  \
  \ Run-time:    ( R: u -- u' )
  \
  \ If the loop index is zero, discard the loop parameters and
  \ continue execution after the loop. Otherwise decrement the
  \ loop index and continue execution at the beginning of the
  \ loop.
  \
  \ }doc

Compared the current code of D+, from Abersoft Forth, with an alternative from Z80 fig-Forth 1.1g, that is faster and smaller and will be used instead:

  _code_header d_plus_,'D+'

  ; Code from Abersoft Forth

                ;  t  B
                ;  -- --
  ld hl,0x0006  ;  10 03
  add hl,sp     ;  11 01
  ld e,(hl)     ;  07 01
  ld (hl),c     ;  07 01
  inc hl        ;  06 01
  ld d,(hl)     ;  07 01
  ld (hl),b     ;  07 01
  pop bc        ;  10 01
  pop hl        ;  10 01
  add hl,de     ;  11 01
  ex de,hl      ;  04 01
  pop hl        ;  10 01
  adc hl,bc     ;  15 01
  pop bc        ;  10 01
  jp push_hlde  ;  10 03
                ;  11    ; push de
                ;  11    ; push hl
                ; --- --
                ; 157 19 TOTALS
if 0

; Alternative from fig-Forth 1.1g

              ;                           t  B
              ;                           -- --
  exx         ; save ip                   04 01
  pop bc      ; (bc)<--d2h                10 01
  pop hl      ; (hl)<--d2l                10 01
  pop af      ; (af)<--d1h                10 01
  pop de      ; (de)<--d1l                10 01
  push  af    ; (s1)<--d1h                11 01
  add hl,de   ; (hl)<--d2l+d1l=d3l        11 01
  ex  de,hl   ; (de)<--d3l                04 01
  pop hl      ; (hl)<--d1h                10 01
  adc hl,bc   ; (hl)<--d1h+d2h+carry=d3h  15 02
  push  de    ; (s2)<--d3l                11 01
  push  hl    ; (s1)<--d3h                11 01
  exx         ; restore ip                04 01
  jp_next     ;                           08 02 ; jp (ix)
              ;                          --- --
              ;                          134 15 TOTALS
endif

First changes to make true -1 instead of 1. Conditional compilation is used at the moment.

Added under+ and k to the library, after Gforth.

Renamed gover to overwrite. The less cryptic names the better; names don't waste dictionary space.

Fixed and improved emitted (the equivalent of the ZX Spectrum SCREEN$() function). Faster and configurable for any charset or set of user defined graphics. It will be moved to the library disk, maybe adapted to the Forth assembler.

; ----------------------------------------------
  _code_header emitted_,'EMITTED'

  ; [Code adapted and modified from the ZX Spectrum ROM routine
  ; S-SCRN$-S at 0x2535.]

; doc{
;
; emitted  ( col row -- n | 0 )
;
; Return the ordinal number _n_ (first is 1) of the character
; printed at the given screen coordinates, or 0 if no character
; can be recognized on that position of the screen.
;
; This word must be configured by `emitted-charset` and
; `#emitted-chars`, that set the address of the first character
; and the number of characters to compare with. By default the
; printable ASCII chars of the ROM charset are used.
;
; The result _n_ is the ordinal number (first is 1) of the
; recognized char in the specified charset. Example: with the
; default configuration, a recognized space char would return 1;
; a "!" char, 2; a "A", 34...
;
; This word is meant to be used with user defined graphics.
;
; }doc

  ; XXX TODO improve the result number?
  ;
  ; XXX TODO move to the disk
  ;
  ; XXX TODO rename?: `ocr`, `recognized`, `on-xy`, `xy-char`?
  ; The reason is a name clash with the fig-Forth `out` counter,
  ; that was going to be called `emitted` or `#emitted`.

  pop de ; row
  pop hl ; col
  push bc ; save the Forth IP
  ld b,l ; column
  ld c,e ; row
  ld hl,(emitted_charset_pfa) ; address of first printable char in the charset
  ld a,c  ; row
  rrca
  rrca
  rrca ; multiply by 0x20
  and  %11100000
  xor  b ; combine with column (0x00..0x1F)
  ld  e,a ; low byte of top row = 0x20 * (line mod 8) + column
  ld  a,c  ; row is copied to a again
  and  0x18
  xor  0x40
  ld  d,a ; high byte of top row = 64 + 8*int (line/8)
  ; de = screen address
  ld a,(hash_emitted_chars_pfa) ; number of chars in the charset
  ld b,a

emitted.do:
  push  bc  ; save the characters count
  push  de  ; save the screen pointer
  push  hl  ; save the character set pointer (bitmap start)
  ld  a,(de)  ; get first scan of screen character
  xor  (hl)  ; match with scan from character set
  jp z,emitted.match  ; jump if direct match found
  ; if inverse, a=0xFF
  inc  a  ; inverse? (if inverse, a=0)
  jp  nz,emitted.next_char  ; jump if inverse match not found
  ; inverse match
  dec  a  ; restore 0xFF
emitted.match:
  ld  c,a  ; inverse mask (0x00 or 0xFF)
  ld  b,0x07  ; count 7 more character rows
emitted.scans:
  inc  d  ; next screen scan (add 0x100)
  inc  hl  ; next bitmap address
  ld  a,(de)  ; screen scan
  xor  (hl)  ; will give 0x00 or 0xFF (inverse)
  xor  c  ; inverse mask to include the inverse status
  jp  nz,emitted.next_char  ; jump if no match
  djnz  emitted.scans  ; jump back till all scans done

  ; character match
  pop  bc  ; discard character set pointer
  pop  bc  ; discard screen pointer
  pop  bc  ; final count
  ld a,(hash_emitted_chars_pfa) ; number of chars in the charset
  sub  b ; ordinal number of the matched character (1 is the first)
  ld l,a
  jp emitted.end

emitted.next_char:
  pop  hl  ; restore character set pointer
  ld  de,0x0008  ; move it on 8 bytes
  add  hl,de  ; to the next character in the set
  pop  de  ; restore the screen pointer
  pop  bc  ; restore the counter
  djnz  emitted.do  ; loop back for the 96 characters
  ; no match
  ld l,b ; zero

emitted.end:
  pop bc ; restore the Forth IP
  ld h,0
  jp push_hl

; ----------------------------------------------
  _variable_header emitted_charset_,'EMITTED-CHARSET'

; doc{
;
; emitted-charset  ( -- a )
;
; Variable that holds the address of the first printable char in
; the charset used by `emitted`. By default it contains 0x3D00, the
; address of the space char in the ROM charset.
;
; }doc

  dw 0x3D00 ; address of the space in the ROM charset

; ----------------------------------------------
  _variable_header hash_emitted_chars_,'#EMITTED-CHARS'

; doc{
;
; #emitted-charset  ( -- a )
;
; Variable that holds the number of printable chars in the
; charset used by `emitted`. By default it contais 0x5F, the
; number of printable ASCII chars in the ROM charset.
;
; }doc

  dw 0x5F ; printable ASCII chars in the ROM charset


Fixed an old bug in the new, faster and smaller, definition of stream-end. The old definition was still in use because of the bug.

Renamed #buff to buffers.

Factor flush to save-buffers.

Removed the useless user variable fence from the kernel, and the useless forget from the library.

Converted the user variables tib and tib to a constant and a variable, after ANS Forth.

Added ANS Forth's source to the library.

Changed the disk block interface. So far the fig-Forth r/w was used:

  _colon_header read_write_,'R/W'

; doc{
;
; r/w  ( a block wf -- )
;
; The disk read-write linkage. _a_ specifies the source or
; destination block buffer, _block_ is the sequential number of
; the referenced block; and _wf_ is a flag: false=write,
; true=read.  `r/w` determines the location on mass storage,
; performs the read-write and performs any error checking.
;
; }doc

  _literal 0x45
  dw swap_
if true=1
  dw minus_
else
  dw plus_
endif
  dw lit_,read_write_sector_command,c_store_
  dw block_to_sector_
  dw paren_read_write_
  dw semicolon_s_

paren_read_write_:

  ; Headerless word with the low level code of `R/W`.

  ; ( a sector -- )
  ; sector (high byte) = track 0..79, +128 if side 1
  ;        (low byte)   = sector 1..10

  dw paren_read_write_pfa ; code field
paren_read_write_pfa:
  pop de ; d = track 0..79, +128 if side 1
         ; e = sector 1..10
  pop ix ; address
  push bc ; save the Forth IP
  ld a,2 ; drive ; XXX TMP
  rst 8 ; G+DOS hook
read_write_sector_command:
  ; G+DOS command already patched:
  db 0x44 ; 0x44 = read ; 0x45 = write
  pop bc ; restore the Forth IP
  ld ix,next
  _jp_next

The new interface is clearer, but it needs 7 more bytes:

; ----------------------------------------------
  _colon_header read_block_,'READ-BLOCK'

; doc{
;
; read-block  ( a n -- )
;
; Read disk block _n_ to buffer _a_.
;
; }doc

  _literal 0x44 ; G+DOS command to read a disk sector
  dw transfer_block_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header write_block_,'WRITE-BLOCK'

; doc{
;
; write-block  ( a n -- )
;
; Write buffer _a_ to disk block _n_.
;
; }doc

  _literal 0x45 ; G+DOS command to write a disk sector
  dw transfer_block_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header transfer_block_,'TRANSFER-BLOCK'

; doc{
;
; transfer-block  ( a n b -- )
;
; The disk read-write linkage.
;
; a = source or destination block buffer
; n = sequential number of the referenced disk block
; b = G+DOS command to read or write a sector
;
; }doc

  dw lit_,read_write_sector_command,c_store_
  dw block_to_sector_
  dw paren_transfer_block_
  dw semicolon_s_

paren_transfer_block_:
  ; Headerless word with the low level code of `transfer-block`.
  dw paren_transfer_block_pfa ; code field

  ; ( a sector -- )
  ; sector (high byte) = track 0..79, +128 if side 1
  ;        (low byte)   = sector 1..10
paren_transfer_block_pfa:
  pop de ; d = track 0..79, +128 if side 1
         ; e = sector 1..10
  pop ix ; address
  push bc ; save the Forth IP
  ld a,2 ; drive ; XXX TMP
  rst 8 ; G+DOS hook
read_write_sector_command:
  ; G+DOS command already patched:
  db 0x44 ; 0x44 = read ; 0x45 = write
  pop bc ; restore the Forth IP
  ld ix,next
  _jp_next


2015-08-15

Currently Solo Forth uses two 512-byte disk buffers. I thought using only one buffer would make many things simpler, so I implemented alternative code and tried it with conditional compilation:

; ----------------------------------------------
  _colon_header update_,'UPDATE'

; doc{
;
; update  ( -- )  \ ANS-Forth
;
; Mark the most recently referenced block (pointed to by `prev`) as
; altered. The block will subsequently be transferred automatically to
; disk should its buffer be required for storage of a different block.
;
; }doc

  ; XXX TODO move to the disk?

if buffers=1

  dw first_,fetch_
  dw lit_,0x8000,or_
  dw first_,store_

else

  dw prev_,fetch_,fetch_
  dw lit_,0x8000,or_
  dw prev_,fetch_,store_

endif

  dw semicolon_s_

if buffers=1
; ----------------------------------------------
  _colon_header updated_question_,'UPDATED?'

; doc{
;
; updated?  ( -- f )
;
; Is the current disk buffer marked as modified?
;
; }doc

  dw first_,fetch_,zero_less_than_

endif

; ----------------------------------------------
  _colon_header empty_buffers_,'EMPTY-BUFFERS'

if buffers=1

; doc{
;
; empty-buffers  ( -- )
;
; Unassign all block buffers. Do not transfer the contents of
; any updated block to mass storage.
;
; }doc

  dw lit_,0x7FFF,first_,store_

else

; doc{
;
; empty-buffers  ( -- )
;
; Unassign all block buffers. Do not transfer the contents of
; any updated block to mass storage. All buffers are filled with
; blanks.
;
; }doc

  dw first_,limit_,over_,minus_,blank_
  dw limit_,first_
  dw paren_do_
empty_buffers.do:
  dw lit_,0x7FFF,i_,store_
  ; Store a null word after the end of the buffer:
  dw i_,lit_,data_bytes_per_buffer+2,plus_,stream_end_
  dw lit_,total_bytes_per_buffer
  dw paren_plus_loop_,empty_buffers.do

endif

  dw semicolon_s_

if buffers=1
; ----------------------------------------------
  _colon_header block_number_,'BLOCK-NUMBER'

; doc{
;
; block-number  ( n1 -- n2 )
;
; Remove the update bit from the block number _n1_.
;
; }doc

  _literal 0x7FFF
  dw and_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header free_buffer_,'FREE-BUFFER'

; doc{
;
; free-buffer  ( n1 n2 -- )
;
; Free the disk buffer, currently assigned to block _n2_,
; and assign it to block _n1_.
;
; }doc

  dw dup_,zero_less_than_ ; updated?
  dw zero_branch_,free_buffer.not_updated
  dw block_number_,first_,cell_plus_,swap_,write_block_
  dw branch_,free_buffer.end
free_buffer.not_updated:
  dw drop_
free_buffer.end:
  dw first_,store_
  dw semicolon_s_

endif

; ----------------------------------------------
  _colon_header buffer_,'BUFFER'

; doc{
;
; buffer  ( n -- a )
;
; Obtain the next block buffer and assign it to block _n_.   If the
; contents of the buffer were marked as updated, it is written to the
; disk.  The block _n_ is not read from the disk.  The address left on
; stack is the first cell in the buffer for data storage.
;
; }doc

if buffers=1 ; XXX NEW

  dw first_,fetch_ ; current block assigned to the buffer
  dw two_dup_,block_number_,equals_
  dw zero_branch_,buffer.not_equals
  ; The requested block is the one already in the buffer.
  dw two_drop_
  dw branch_,buffer.end
buffer.not_equals:
  dw free_buffer_
buffer.end:
  dw first_,cell_plus_ ; first cell of data in the buffer
  dw semicolon_s_

else ; XXX OLD

  dw use_,fetch_
  dw dup_,to_r_
buffer.begin:
  ; XXX NOTE: `+buf` won't work if single buffer
  dw plus_buf_
  dw zero_branch_,buffer.begin ; until
  dw use_,store_
  dw r_fetch_,fetch_
  dw zero_less_than_ ; updated?
  dw zero_branch_,buffer.end
  ; The buffer was updated, it must be saved to disk.
  dw r_fetch_,two_plus_ ; first cell of data in the buffer
  dw r_fetch_,fetch_
  dw lit_,0x7FFF,and_ ; discard the update bit, leave the block number
if 1 ; new_read_write?
  dw write_block_
else
  dw false_,read_write_ ; write
endif
buffer.end:
  dw r_fetch_,store_
  dw r_fetch_,prev_,store_
  dw from_r_,two_plus_ ; first cell of data in the buffer
  dw semicolon_s_

endif

; ----------------------------------------------
  _colon_header block_,'BLOCK'

; doc{
;
; block  ( n -- a )

; Leave the memory address _a_ of the block buffer containing
; block _n_. If the block is not already in memory, it is
; transferred from disk to which ever buffer was least recently
; written. If the block occupying that buffer has been marked as
; updated, it is rewritten to disc before block _n_ is read into
; the buffer.
;
; }doc

if buffers=1

  dw dup_,first_,fetch_,block_number_,equals_
  dw zero_branch_,block.not_equals
  dw drop_
  dw branch_,block.end
block.not_equals:
  dw save_buffers_,first_,two_dup_,store_,cell_plus_,swap_,read_block_
block.end:
  dw first_,cell_plus_
  dw semicolon_s_

else

  dw to_r_
  dw prev_,fetch_ ; most recently accessed buffer
  dw dup_,fetch_ ; its block number (bit 15, the update indicator, may be set)
  dw r_fetch_,minus_ ; compare to the required block
  dw two_star_ ; discard the left most bit, which is the update indicator
  dw zero_branch_,block.end
  ; Block number _n_ is not the previously referenced.
  ; Prepare disk access.
block.begin:
  ; Scan the buffers and look for a buffer which might contain
  ; block _n_ already.
  dw plus_buf_,zero_equals_ ; advance a buffer
  dw zero_branch_,branch_destination_0x71E5
  ; This buffer is pointed to by `prev`, all buffers scanned.
  dw drop_ ; discard the buffer address
  dw r_fetch_,buffer_ ; find the disk sector, update the sector if necessary
  dw dup_,r_fetch_
if 1 ; new_read_write?
  dw read_block_
else
  dw true_,read_write_ ; read one sector from the disk
endif
  dw two_minus_ ; backup to the buffer address of block n
branch_destination_0x71E5:
  dw dup_,fetch_ ; beginning address of the buffer, with a block number in it
  dw r_fetch_,minus_ ; compare to the block number _n_
  dw two_star_ ; discard the left most bit, with is the update indicator
  dw question_branch_,block.begin ; until
  dw dup_,prev_,store_ ; store the buffer address in `prev`
block.end:
  dw r_drop_
  dw two_plus_ ; get the address where data begin
  dw semicolon_s_

endif

; ----------------------------------------------
  _colon_header save_buffers_,'SAVE-BUFFERS'

; doc{
;
; save-buffers  ( -- )  \ ANS Forth
;
; Transfer the contents of each updated block buffer to disk.
; Mark all buffers as unmodified.
;
; }doc

if buffers=1

  dw updated_question_,zero_equals_ ; not updated?
  dw question_exit_ ; exit if not updated
  ; Updated
  dw first_,first_,fetch_,block_number_,two_dup_,write_block_
  dw swap_,store_
  dw semicolon_s_

else

  ; XXX TODO -- Check. It seems this original version is not ANS
  ; Forth compliant, because it modifies the buffers.

  dw buffers_,one_plus_,zero_
  dw paren_do_
save_buffers.do:
  dw zero_,buffer_,drop_
  dw paren_loop_,save_buffers.do ; loop
  dw semicolon_s_

endif


A simple benchmark revealed using one single buffer was faster:

Loading times in system frames (20 ms)
512-byte buffers Benchmark 1 Benchmark 2
1 431 392
2 492 475
3 473 439
4 476 445
10 491 471

Removed the conditional compilation: no way back to several buffers. Eventually I will try a 1 KiB buffer, but I think it will be slower, because a G+DOS disk sector is 512 bytes long.

Removed all unnecesary words (+buff, prev, use). After some factoring and renaming, the Forth code (the original version written before including it into the kernel) looks like this:

: block-number  ( x -- n )  7FFF and  ;
  \ Convert the buffer id _x_ to its block number _n_
  \ by removing the update bit.

: buffer-data   ( -- a )  disk-buffer cell+ ;
  \ First data address of the disk buffer.

: buffer-id     ( -- x )  disk-buffer @ ;
  \ Id of the disk buffer.

: buffer-block  ( -- n )  buffer-id block-number ;
  \ Block associated to the disk buffer.

: write-buffer  ( n -- )  buffer-data swap write-block  ;
: read-buffer  ( n -- )  buffer-data swap read-block  ;

: free-buffer  ( n1 n2 -- )
  \  Free the disk buffer, currently assigned to block _n2_,
  \  and assign it to block _n1_.
  \  XXX TODO rewrite
  dup 0< \ updated?
  if    block-number write-buffer
  else  drop
  then  disk-buffer !  ;

: buffer  ( n -- a )
  buffer-id 2dup block-number = ( n n0 f )
  if    2drop
  else  free-buffer
  then  buffer-data  ;

: updated? ( -- f )  buffer-id 0<  ;

: save-buffers ( -- )

  updated? 0= ?exit \ exit if not updated
  buffer-block dup write-buffer  disk-buffer !  ;

: block ( n --- a )
  dup buffer-block =
  if    drop
  else  save-buffers  dup read-buffer  disk-buffer !
  then  buffer-data  ;

New result of benchmark 1: 443 frames, a bit slower, because of the factoring, but stil faster than the 2-buffer method. The free memory is 33910 bytes, more than ever before, and growing (finally all words regarded as optional will be moved off kernel).

2015-08-16

Serious problems with the Pasmo assembler. The other day version 0.5.3 suddenly started to change some addresses of jumps and calls in the binary, though the labels were fine in the symbols file created by the assembler! I switched to version 0.5.4 beta2 and the problem disappeared, but after a while then the Forth system started crashing at the start. After a lot of debugging, I conclude the problem is the assembler.

2015-08-17

Converted the 8000-line source code to GNU binutils, with the help of Vim.

Had some problems with the conversion of the _header macro. Asked for help in the World of Spectrum forum. The problem seemed to vanish but not really...

2015-08-18

After a lot of breakpoints and step by step execution with the Fuse's monitor/debugger, I found the bug that crashed the system at the start: the output channel was not open, and the rst 0x10 in (emit) failed. Can not guess when and how this bug was introduced. The problems with Pasmo made this bug difficult to find out, because the error conditions seemed to change every time. It has been easier with GNU binutils.

2015-08-19

Discovered how to to compile the length of strings in a macro, with GNU binutils. Only local labels work:

; string_length.z80s
;
; GNU binutils test
;
; Marcos Cruz (programandala.net)
;
; 2015-08-19

  .text

  db message_0_end-$-1 ; works, compiles 7
  db "message"
message_0_end: defl $

_macro_message: macro

  db _macro_message_end-$-1 ; works, compiles 13
  db "macro message"
_macro_message_end: defl $

  endm

_any_message: macro _string

  db _any_message_end-$-1 ; works only the first time
  db "\_string"
_any_message_end: defl $

  endm

_any_message_with_local: macro _string

  db 0f-$-1
  db "\_string"
0:

  endm

  _macro_message ; works

  _any_message "any" ; works, compiles 3
  _any_message "message" ; fails, compiles 0xFF
  _any_message "fails" ; fails, compiles 0xFF
  ; Several more tries confirmed that only the first call to
  ; `_any_message` works.

  _any_message_with_local "0any" ; works, compiles 4
  _any_message_with_local "0message" ; works, compiles 8
  _any_message_with_local "0works" ; works, compiles 6

New problem: the Z80 assembler of GNU binutils does not escape special chars in strings. The documentation says: A backslash `\` is an ordinary character for the Z80 assembler. Two word names in Solo Forth are just a null character. With Pasmo, "\x00" worked. Other names contain double quotes. With Pasmo, single or double quotes can be used for strings, but GNU binutils allows double quotes only...

The solution is to write wrapper macros for the _header macro, that accept a name in three parts (a prefix string, a char and a suffix string). Example:

_code_header_with_special_name: .macro _base_label,_name0,_name1,_name2,_immediate=0

  _header_with_special_name \_base_label,"\_name0",\_name1,"\_name2",\_immediate
  dw \_base_label\()pfa ; code field
  \_base_label\()pfa: ; parameter field address

  .endm

The new macro _header_with_special_name does the same than _header except the name is compiled this way:

  ; Name field
  db 0f-$-1+\_immediate
  db "\_name0",\_name1,"\_name2"
0:

Solo Forth runs again!

Fixed \: it worked only while loading a block.

; doc{
;
; \  ( -- )
;
; Discard the rest of the parse area.
;
; }doc

  dw blk_,fetch_
  dw question_branch_,backslash.loading
  ; Interpreting
  dw span_,fetch_,to_in_,store_
  dw semicolon_s_

backslash.loading:
  ; Loading
  dw to_in_,fetch_,c_slash_l_,mod_
  dw c_slash_l_,swap_,minus_
  dw to_in_,plus_store_
  dw semicolon_s_

Problems: something gets broken after loading from disk: the next word to be executed executes the input string in the circular string buffer instead.

2015-08-20

The field names of new definitions are not compiled. It seems it has something to do with the assembler sections, but I can not find out the problem.

Back to Pasmo. Updated its last source with a Vim diff. Everything seems to work fine.

bye still resets the system. Can not guess when this bug was introduced.

Nevertheless the effect changes: system freeze, 48K reset, 128K reset... No pattern yet.

2015-08-21

The problem vanished after compiling with Pasmo 0.54beta2: bye works fine. But then I changed back to Pasmo 0.53, compiled and bye still works too. The binaries produced by both versions are identical. I don't understand.

Fixed plot.

First drafts for circle.

2015-08-22

Converted dp to an ordinary variable. In fig-Forth it was an user variable.

Adapted Spectrum Forth-83's draw:

( rdraw )

  \ [Code adapted from Spectrum Forth-83's `DRAW`.]

require fasmo  \ assembler

code rdraw  ( x y -- )

  \ Draw a line.
  \ x y = relative coordinates from the current position

  \ XXX TODO -- use the whole screen

  hl pop  de pop  bc push
  de bc mvp

  \ hl = y
  \ bc = x
  1 e ld
  7 b bit  \ negative x?
  nz if  c a mv  neg  -1 e ld  a c mv  then  \ negative x

  l b mv   \ y
  1 d ld
  7 h bit  \ negative y?
  nz if  b a mv  neg  -1 d ld  a b mv  then  \ negative y

  24BA call \ alternative entry to the DRAW-LINE ROM routine

  bc pop

  next,  end-code

Fixed cls (a factor of page): it didn't set the graphics coordinates to zero.

Adapted Abersoft Forth's draw:

( adraw )

  \ [Code adapted from Abersoft Forth's `DRAW`.]

require plot

2variable x1  2variable incx  2variable y1  2variable incy

  \ Note: 23677 = system variable COORDX
  \       23678 = system variable COORDY

: adraw  ( x y -- )

  \ x y = absolute coordinates

  23678 c@ ( y0 ) dup 0 swap y1 2! - dup abs rot
  \ ( +-ydiff ydiff x )
  23677 c@ ( x0 ) dup 0 swap x1 2! - dup abs rot
  \ ( +-ydiff +-xdiff xdiff ydiff )
  max >r dup 0<  \ negative xdiff?
  if    abs 0 swap r@ m/mod dnegate
  else  0 swap r@ m/mod  then
  incx 2! drop dup 0<  \ negative ydiff?
  if    abs 0 swap r@ m/mod dnegate
  else  0 swap r@ m/mod  then
  incy 2! drop r> 1+ 0
  do  x1 @ y1 @ plot
      x1 2@ incx 2@ d+ x1 2!
      y1 2@ incy 2@ d+ y1 2!  loop  ;

Until rdraw is fixed to use the whole screen, adraw and rdraw can not be used together without additional code.

Discovered, while debugging adraw, that the new code of d+, included some days ago, has a bug: The calculation 0 0 0 1 d+ should leave 0 1 on the stack, but it leaves 0 -1. Restored the original Abersoft Forth version of d+.

Converted the code of plotted? from hex opcodes to assembler, and modified it to return -1 instead of 1.

Updated the assembler after Fasmo, the Z80 assembler for Gforth, based on the same code.

2015-08-23

Removed ?comp from >mark, <mark, >resolve and <resolve, otherwise they can not work in assembler definitions.

Moved the assembler words from the kernel to the library, including the assembler vocabulary, and modified the actual assembler accordingly. But it didn't work because the assembler itselfs requires some library low-level words that are compiled with code end-code and , c,... Restored the previous situation: the kernel provides the assembler vocabulary and all core words required to code in Z80 by hand. Library words that require the actual assembler must do require z80-asm.

Fixed warm. The problem was the same as cold's: somehow channel 2 was not open.

Moved update and flush to the library.

Removed the graphic word emmitted from the kernel. It must be adapted to the Forth assembler.

Converted dp into an user variable again, after asking about it in comp.lang.forth.

Added cconstant, and used it in user instead of constant.

; ----------------------------------------------
  _colon_header c_constant_,'CCONSTANT'

  dw create_,c_comma_
  dw paren_semicolon_code_
do_constant:
  inc de    ; de=pfa
  ex de,hl  ; hl=pfa
  jp c_fetch.hl

2015-08-25

Added another sound effect to the library, and also the music words from Spectrum Forth-83. Not tested yet.

Fixed a strange new bug that was introduced two days ago: the system crashes after pressing the Enter key. It was necessary to update the version of the source for GNU binutils, comparing it with the current version for Pasmo. The stupid mistake was I had written the user variable index of dp in decimal, but with hex notation.

Rewrote part of the error message system: Renamed warning to warnings. Converted the msg-scr constant to a user variable and made message to print text messages only if msg-scr is not zero. Wrote warning to print warnings depending on warnings. Modified header and error accordingly. This way, warnings can be turned off completely, error messages are independent from warnings, and messages can be used for other purposes, even by different tasks. Eventually, throw and catch will be implemented.

; ----------------------------------------------
  _colon_header warning_,'WARNING'

; doc{
;
; warning  ( n -- )
;
; }doc

  dw warnings_,fetch_
  dw question_branch_,message_pfa
  dw drop_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header message_,'MESSAGE'

; doc{
;
; message  ( n -- )
;
; }doc

  dw msg_scr_,fetch_
  dw zero_branch_,message.number_only
  dw error_to_line_,msg_scr_,fetch_,dot_line_,space_
  dw semicolon_s_

message.number_only:
  dw paren_dot_quote_
  _string 'MSG # '
  dw base_,fetch_,swap_,decimal_,dot_,base_,store_
  dw semicolon_s_

Now scr is updated also by (load). The new library word reload uses it, what is useful during debugging.

Fix: moved asm to the forth vocabulary.

Moved !csp and ?csp from code end-code to asm end-asm, since the later structure is included in the former one, and the check is useful also when asm end-asm are used directly.

Finished the code conversion of two new sound effects added to the library:

( laser-gun )

  \ Laser gun sound for ZX Spectrum 48K.
  \ Author of the original code: Álvaro Corredor Lanas.
  \ Published in Microhobby, issue 126 (1987), page 7:
  \ http://microhobby.org/numero126.htm
  \ http://microhobby.speccy.cz/mhf/126/MH126_07.jpg

require z80-asm

code laser-gun  ( -- )
  bc push
  5 b ld#
  0500 hl ldp#
  <mark   0001 de ldp#
          hl push  03B5 call  hl pop
          0010 de ldp#  de subp
          jrnz
  bc pop  next ix ldp#  next,
  end-code

( white-noise )

  \ White noise for ZX Spectrum 48K.
  \ Author of the original code: Ricardo Serral Wigge.
  \ Published in Microhobby, issue 125 (1987), page 26:
  \ http://microhobby.org/numero125.htm
  \ http://microhobby.speccy.cz/mhf/125/MH125_26.jpg

  \ The original code was called "explosion" and had a fixed
  \ duration of 768 sample bytes, thus equivalent to `768
  \ white-noise`.

require z80-asm

code white-noise  ( u -- )

  \ u = duration in number of sample bytes

  de pop
  bc push  \ save the Forth IP
  de bc ldp  0000 hl ldp#  \ bc=duration, hl=start of ROM

  5C48 fta  a sra  a sra  a sra  07 and#  a d ld
    \ d = border color (in bits 0-2)

  <mark   m e ld  hl incp  bc decp  bc push
          08 b ld#  \ bit counter
          <mark   e a ld  10 and#  e rl  d or  FE out  \ beep
                  djnz
          bc pop  c a ld  b or
          jrnz

  bc pop  next,  \ restore the Forth IP and go next

  end-code

Added versatile alternatives to plot:

( set-pixel )

  \ Set a pixel without changing the color attributes.

  \ Author of the original code: José Manuel Lazo.
  \ Published in Microhobby, issue 85 (1986-07), page 24:
  \ http://microhobby.org/numero085.htm
  \ http://microhobby.speccy.cz/mhf/085/MH085_24.jpg

require pixel-add  require z80-asm

code set-pixel  ( xc yc -- )

  hl pop  de pop  bc push
  l b ld  e c ld  pixel-add call
  a b ld  b inc  1 a ld#
  <mark  a rrc  djnz
  m or  a m ld  \ combine pixel with byte in the screen
  bc pop  next,

  end-code

( reset-pixel )

  \ Reset a pixel without changing the color attributes.

  \ Based on code written by José Manuel Lazo,
  \ published in Microhobby, issue 85 (1986-07), page 24:
  \ http://microhobby.org/numero085.htm
  \ http://microhobby.speccy.cz/mhf/085/MH085_24.jpg

require pixel-add  require z80-asm

code reset-pixel  ( xc yc -- )

  hl pop  de pop  bc push
  l b ld  e c ld  pixel-add call
  a b ld  b inc  1 a ld#
  <mark  a rrc  djnz
  cpl  m and  a m ld  \ combine pixel with byte in the screen
  bc pop  next,

  end-code

( toggle-pixel )

  \ Toggle a pixel without changing the color attributes.

  \ Based on code written by José Manuel Lazo,
  \ published in Microhobby, issue 85 (1986-07), page 24:
  \ http://microhobby.org/numero085.htm
  \ http://microhobby.speccy.cz/mhf/085/MH085_24.jpg

require pixel-add  require z80-asm

code toggle-pixel  ( xc yc -- )

  hl pop  de pop  bc push
  l b ld  e c ld  pixel-add call
  a b ld  b inc  1 a ld#
  <mark  a rrc  djnz
  m xor  a m ld  \ combine pixel with byte in the screen
  bc pop  next,

  end-code

Renamed plotted? to pixel?.

Added d>s to the kernel.

2015-08-26

Finished the conversion of some 128K sound effects, and the word play to play them.

( waves shoot helicopter train )

  \ `waves` and `shoot` are
  \ adapted from code written by Juan José Ruiz,
  \ published in Microhobby, issue 139 (1987-07), page 7:
  \ http://microhobby.org/numero139.htm
  \ http://microhobby.speccy.cz/mhf/139/MH139_07.jpg

create waves  ( -- a )
  0 c,  0 c,  0 c,  0 c,  0 c,  0 c,  7 c,
  71 c,  20 c,  20 c,  20 c,  0 c,  38 c,  14 c,

create shoot  ( -- a )
  10 c,  0 c,  177 c,  0 c,  191 c,  0 c,  31 c,
  71 c,  20 c,  20 c,  20 c,  92 c,  28 c,  3 c,

  \ `helicopter` and `train` are
  \ adapted from code written by José Ángel Martín,
  \ published in Microhobby, issue 172 (1988-09), page 22:
  \ http://microhobby.org/numero172.htm
  \ http://microhobby.speccy.cz/mhf/172/MH172_22.jpg

create helicopter  ( -- a )
  200 c,  15 c,  200 c,  15 c,  200 c,  15 c,  0 c,
  7 c, 23 c,  23 c,  23 c,  255 c,  1 c,  12 c,

create train  ( -- a )
  100 c,  120 c,  48 c,  97 c,  12 c,  200 c,  55 c,
  15 c,  9 c,  11 c,  55 c,  180 c,  4 c,  8 c,

( play )

  \ Code inspired by the article
  \ "Las posibilidades sonoras del 128 K",
  \ written by Juan José Rosado Recio,
  \ published in Microhobby, issue 147 (1987-10), page 24:
  \ http://microhobby.org/numero147.htm
  \ http://microhobby.speccy.cz/mhf/147/MH147_24.jpg

require !p

[defined] sound-register-port
?\ 65533 constant sound-register-port
[defined] sound-write-port
?\ 49149 constant sound-write-port

: play  ( a -- )
  \ a = address of a table with 14 bytes
  14 0 do
    i sound-register-port !p  dup c@ sound-write-port !p 1+
  loop  drop  ;

Renamed next, to jpnext, clearer.

Removed pushhl,, pushhlde, and fetchhl, from the kernel. The word definitions that used them have been updated (example, C3 c, pushhl , instead of pushhl,.

2015-08-27

Included all G+DOS modules from Afera and started the first changes in the code.

Added some graphic effects to the library.

2015-08-28

Finished two of the new graphic effects:

( horizontal-curtain )

  \ [Code adapted from a routine written by Alejandro Mora,
  \ published in Microhobby, issue 128 (1987-05), page 7:
  \ http://microhobby.org/numero128.htm
  \ http://microhobby.speccy.cz/mhf/128/MH128_07.jpg]

require z80-asm

code horizontal-curtain  ( b -- )

  \ Wash the screen with the given color
  \ attribute _b_ from the top and bottom
  \ rows to the middle.

  de pop  bc push

  e a ld  5800 de ldp#  5AFF hl ldp#
  0C b ld#
  begin   bc push  20 b ld#
          begin   a m ld  de stap
                  bc push  02 b ld#
                  begin bc push  FF b ld#  begin  step
                        bc pop  step
                  bc pop  de incp  hl decp  step
          bc pop  step

  bc pop  jpnext

  end-code

( vertical-curtain )

  \ [Code adapted from a routine written by Alejandro Mora,
  \ published in Microhobby, issue 128 (1987-05), page 7:
  \ http://microhobby.org/numero128.htm
  \ http://microhobby.speccy.cz/mhf/128/MH128_07.jpg]

require z80-asm

code vertical-curtain  ( b -- )

  \ Wash the screen with the given color
  \ attribute _b_ from the left and right
  \ columns to the middle.

  \ b = color attribute

  \ XXX FIXME crash!

  de pop  bc push

  e a ld  5800 de ldp#  5AFF hl ldp#  10 b ld#
  begin   bc push  18 b ld#  de push  hl push
          begin   a m ld  de stap  bc push  02 b ld#
                  begin   bc push  FF b ld#  begin  step
                          bc pop  step
                  20 b ld#
                  begin  de incp  hl decp  step
                  bc pop  step

          hl pop  de pop  bc pop  de incp  hl decp  step

  bc pop  jpnext  end-code

Added a new sound effect:

( ambulance )

  \ Ambulance sound for ZX Spectrum 48K.
  \ Author of the original code: Líder Software.
  \ Published in Microhobby, issue 142 (1987-09), page 7:
  \ http://microhobby.org/numero142.htm
  \ http://microhobby.speccy.cz/mhf/142/MH142_07.jpg

require z80-asm

code ambulance  ( n -- )

  \ n = times

  de pop  bc push  e b ld

  begin   bc push  0320 hl ldp#  000A de ldp#
          <mark   hl push
                  03B5 call  \ ROM beeper
                  hl pop  hl decp
                  h a ld  l or
                  jrnz
          bc pop
          step

  bc pop  next ix ldp#  jpnext

  end-code

2015-08-30

Fixed wrong Z80 instructions in <file and >file.

Added pusha to the kernel and improved three words in the library with it.

Fixed file?: it still returned 1 as a true flag and didn't restored the border color.

Added u.s to the library, a variant of .s that uses u..

Renamed cat and acat as wcat and wacat; added cat and acat to call them with a default wildcard to list all files.

2015-08-31

Modified ior>error: now the offset for G+DOS errors is 31 instead of 100. The G+DOS error messages have been added to the library, and they are printed when msg-scr points to the correspondent screen.

Finished the definitions of set-order and get-order. At the moment get-order uses #vocs instead of the actual number of vocs in the search order. Beside, set-order is not ANS Forth compliant because of the different words included in the minimum search order.

: (get-order)  ( n -- widn..wid1 n)
  \ Do get the search order.
  1- -1 swap do  context i cells + @  -1 +loop  ;

: get-order  ( -- 0 | widn..wid1 n)
  \ Get the search order.
  #vocs ?dup if  (get-order)  then  #vocs  ;

: (set-order)  ( widn..wid1 n -- )
  \ Do set a search order.
  0 do  context i cells + !  loop  ;

: set-order  ( -1 | 0 | widn..wid1 n -- )
  \ Set a search order.
  dup -1 =  if  drop only exit  then  -order
  ?dup if  (set-order)  then  ;

Started adapting the FZX driver written by Andrew Owen and Einar Saukas. This way arbitrary sized and proportional fonts will be supported, as an option to the default printing routines provided by the ROM.

Factored >relmark with just-here in the assembler, because several times >relmark was used to resolve a branch, not to mark its origin.

Added rld to the assembler.

Moved defer and (defer) to the kernel, in order to make emit and cr defered words.