Solo Forth development history in 2015-11

Description of the page content

Solo Forth development history in 2015-11.

Tags:

2015-11-01

Wrote a double-number version of the for loop.

( dfor dstep di )

: (dstep)  ( R: x ud -- x ud' )

  \ doc{
  \
  \ (dstep)  ( R: x ud -- x ud' | x )
  \
  \ The run-time procedure compiled by `dstep`.
  \
  \ If the loop index _ud_ is zero, discard it and continue
  \ execution after the loop. Otherwise decrement the loop
  \ index and continue execution at the beginning of the loop.
  \
  \ }doc

  r>  \ save the return address
  2r> 2dup or  \ is the index zero?
  if    -1. d+ 2>r
    \ decrement the index
  else  2drop  [ 2 cells ] literal +
    \ discard the index and skip the branch offset
  then  >r  ;
    \ restore the return address

: dfor  ( ud -- )  postpone 2>r <mark  ; immediate compile-only

  \ doc{
  \
  \ dfor
  \
  \ Compilation: ( R: -- dest )
  \ Run-time: ( ud -- )
  \
  \ Start of a `dfor dstep` loop, that will iterate _ud+1_
  \ times, starting with _du_ and ending with 0.
  \
  \ The current value of the index can be retrieved with `di`.
  \
  \ }doc

: dstep  ( -- )
  postpone (dstep) postpone branch <resolve
  ; immediate compile-only

  \ doc{
  \
  \ dstep
  \
  \ Compilation: ( dest -- )
  \
  \ Append the run-time semantics given below to the current
  \ definition. Resolve the destination of `dfor`.
  \
  \ Run-time:    ( R: ud -- ud' )
  \
  \ 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

: di  ( -- ud )  ( R: x ud -- x ud )
  r> 2r@ rot >r  ;

  \ doc{
  \
  \ di  ( -- ud )  ( R: x ud -- x ud )
  \
  \ Return the current index _ud_ of a `dfor` loop.
  \
  \ }doc

Finished two versions of rshift; and added a new version of lshift, adapted from Z88 CamelForth. Then benchmarked them all.

( lshift )

need z80-asm

  \ Credits:
  \ Code adapted from Z88 CamelForth.

  \ 16 bytes used

code lshift  ( x1 u -- x2 )

  exx
  bc pop  \ C = loop counter
  c b ld
  hl pop  \ hi 8 bits ignored!
  b inc  ahead 0 unresolved !
  begin  hl addp  0 unresolved @ >relresolve  step
  hl push
  exx
  jpnext

  end-code

( lshift )

  \ Credits:
  \ Code adapted from DZX-Forth.

  \ This version is smaller (13 bytes used) but slower (169%
  \ the execution time of the version adapted from Z88 CamelForth).

code lshift  ( x1 u -- x2 )

  D1 c,           \ pop de
  E1 c,           \ pop hl
  1C c,           \ inc e
  here            \ begin:
  1D c,           \ dec e
  CA c, pushhl ,  \ jp z,push_hl
  29 c,           \ add hl,hl
  C3 c, ,         \ jp begin

  end-code

( rshift )

need z80-asm

  \ Credits:
  \ Code adapted from Z88 CamelForth.

  \ 19 bytes used

code rshift  ( x1 u -- x2 )

  exx
  bc pop  \ C = loop counter
  c b ld
  hl pop  \ hi 8 bits ignored!

  b inc  ahead 0 unresolved !

  begin  h srl  l rr  0 unresolved @ >relresolve  step
  hl push
  exx
  jpnext

  end-code

( rshift )

  \ Credits:
  \ Code adapted from DZX-Forth.

  \ This version is smaller (16 bytes used) but slower (133%
  \ the execution time of the version adapted from Z88 CamelForth).

code rshift  ( x1 u -- x2 )
  D1 c,           \ pop de
  E1 c,           \ pop hl
  1C c,           \ inc e
  here            \ begin:
  1D c,           \ dec e
  CA c, pushhl ,  \ jp z,push_hl
  CB c, 3C c,     \ srl h
  CB c, 1D c,     \ rr l
  C3 c, ,         \ jp begin
  end-code


A simple benchmark showed the versions adapted from Z88 CamelForth are much faster:

( rshift-bench lshift-bench )

need frames@  need frames0

: rshift-bench  ( n -- )
  frames0  0
  do  128 255 rshift drop   loop
  frames@ cr d.  ;

: lshift-bench  ( n -- )
  frames0  0
  do  128 255 lshift drop   loop
  frames@ cr d.  ;

  \ Times Frames (1 frame = 50th of second)
  \ ----- -----------------------------------
  \       rshift         lshift
  \       -------------- --------------
  \        Z88  DZX    %  Z88  DZX    %
  \       ---- ---- ---- ---- ---- ----
  \ 10000 1203 1609 133% 1016 1723 169%
  \ 30000 3607 4826 133% 3048 5170 169%

  \ Z88 = code adapted from Z88 CamelForth
  \ DZX = code adapted from DZX-Forth

2015-11-02

Started implementing a standard 1024-byte disk buffer, instead of the current 512-byte one. Both systems will be used, with conditional compilation, until benchmarked and tested.

The current blk holds the sequential number of a 512-byte disk sector (0..1600 in G+DOS) and two such sectors form an actual Forth block or screen; but with the new standard method blk will hold the number of a 1024-byte Forth disk block (0..800 in G+DOS). Some calculations must be modified or moved.

Renamed b/scr to rec/blk and created b/rec, both after F83. Renamed scr/disk to blk/disk.

Fixed free-buffer.

2015-11-03

Finished the implementation of a 1024-byte disk buffer.

2015-11-04

Did some benchmarks: the new 1024-byte buffer is slower: locate needs 136% the original time; compiling from disk blocks needs 115% the original time. The new buffer is 512 bytes bigger, but the used space is 436 bytes, because the code required by a 1024-byte buffer is simpler and several words and calculations can be removed. An speed improvement may achieved by unfactoring some useful words (for example, the word that transfer one disk sector), but it's not a good idea.

( dummy-needed )

( buffer-benchmark-1 )

  2 load need frames0
  frames0

  need dummy-needed need dummy-needed need dummy-needed
  need dummy-needed need dummy-needed need dummy-needed
  need dummy-needed need dummy-needed need dummy-needed
  need dummy-needed need dummy-needed need dummy-needed
  need dummy-needed need dummy-needed need dummy-needed
  need dummy-needed

  frames@ cr .( Frames ) d. cr

  \ Benchmark: Locate and load 16 times empty screen #457.

  \ Frames (1 frame = 50th of second)
  \ -----------------------------------
  \ 512-byte buffer 1024-byte buffer
  \ ---------------- ----------------
  \             6323       8621 (136%)

( buffer-benchmark-2 )

  2 load  need frames0  warnings off  frames0

  need list  need dump  need wdump  need decode
  need life  need hanoi  need tt need siderator  need pong
  need doer  need a!  need defer  need value  need editor
  need case  need times  need dtimes  need for

  frames@ cr .( Frames ) d. cr

  \             512-byte buffer 1024-byte buffer
  \            ---------------- ----------------
  \ Frames                20960     24310 (115%)
  \ Bytes free            33242     32777 (-465)

  \ 1 frame = 50th of second

Many things are simpler with a 1024-byte buffer. For example, copy is part of a classic line editor:

  \ XXX OLD -- version for 512-byte buffer

: copy  ( n1 n2 -- )
  rec/blk * swap rec/blk * rec/blk over + swap
  do
    dup i block cell- ! 1+ update
  loop  drop flush  ;
  \ Copy screen _n1_ to screen _n2_.

  \ XXX NEW -- version for 1024-byte buffer

: copy  ( n1 n2 -- )
  swap block cell- ! update save-buffers  ;
  \ Copy screen _n1_ to screen _n2_.

Finally, removed the code required to use a 512-byte buffer and made some optimizations. Now the result of buffer benchmark 2 is slightly better: 23862 frames (112% the speed of a 512-byte buffer).

Removed read-buffer and write-buffer, which were an unuseful layer above read-block and write-block. Converted buffer-data to a constant (it was a run-time simple calculation).

Removed the old unused versions of +D, already commented out: from Abersoft Forth and Z80 fig-Forth 1.1g (which had a bug). The version from Z88 CamelForth is faster and smaller.

2015-11-05

Fixed header: the name is not converted to uppercase in the input buffer anymore, but after being moved to the name field.

Added the benchmarks from Forth Dimensions Volume XVII number 4 page 11.

2015-11-06

Renamed unless to -if; wrote -while and -until:

( -if -while -until )

  \ Equivalent to `0= if`, but faster.

: -if  ( f -- )
  postpone ?branch >mark 2  ; immediate compile-only

  \ XXX TODO Alternative for when compiler security is removed:
  \ : unless  ( f -- )
  \   postpone ?branch >mark  ; immediate compile-only

: -while  ( f -- )
  postpone -if 2+  ; immediate compile-only

  \ XXX TODO Alternative for when compiler security is removed:
  \ : -while  ( f -- )
  \   postpone -if  ; immediate compile-only

: -until  ( f -- )
  1 ?pairs postpone ?branch <resolve  ; immediate compile-only

  \ XXX TODO Alternative for when compiler security is removed:
  \ : -until  ( f -- )
  \   postpone ?branch <resolve  ; immediate compile-only

2015-11-07

Added retry to the library. It does an unconditional branch to the start of the word.

( retry )

  \ Credits:
  \ Code of `retry` from an article by Richard Astle in
  \ Forth Dimensions 17/4 p. 36 (1995-11).

: retry  ( -- )
  latest name> >body 1 postpone again
  ; immediate compile-only

  \ XXX TODO -- remove `1` when compiler security is disabled

: ?retry  ( run-time: f -- )
  postpone if  postpone retry  postpone then
  ; immediate compile-only

2015-11-08

Added d*, by Wil Baden:

( d* )

  \ Credits:
  \ Code by Wil Baden, published on Forth Dimensions
  \ (volume 19, number 6, page 33, 1998-04).

: d*  ( d1 d2 -- d3 )
  >r swap >r            ( d1lo d2lo ) ( R: d2hi d1hi )
  2dup um* 2swap        ( d1lo*d2lo d1lo d2lo )
  r> * swap r> * + +  ; ( d1*d2 ) ( R: )

Added also du/mod, by the same author and from the same number of Forth Dimensions; but it depends on d2*, not defined yet.

Reorganized the library sections that contained code related to double numbers.

2015-11-09

Adapted module: from Galope:

( begin-module: begin-module public private end-module )

  \ Credits:
  \ Code adapted and modified from Galope.

need get-order  need get-current  need wordlist  need >order
need set-current

  \ Inner words

get-order get-current

wordlist dup set-current  >order

variable current-wid  variable module-wid

: (module)  ( -- wid )
  get-current current-wid !
  wordlist dup module-wid ! dup >order  ;

set-current

  \ Interface words

: public  ( -- )  current-wid @ set-current  ;
  \ Public definitions follow.

: private  ( -- )  module-wid @ set-current  ;
  \ Private definitions follow.

: begin-module:  ( "name" -- )  (module) constant private  ;
  \ Start a named module.
  \ Private definitions follow.

: begin-module  ( -- )  (module) drop private  ;
  \ Start an anonymous module.
  \ Private definitions follow.

: end-module  ( -- )  public previous  ;
  \ End a module.

set-order

  \ Description and usage.

  \ Modules hide the internal implementation and leave visible the
  \ words of the outer interface. Example:

  \ begin-module: my_module
  \   \ Inner/helper words.
  \ public
  \   \ Interface words,
  \   \ compiled in the outer vocabulary,
  \   \ thus seen from the extern.
  \ private
  \   \ Inner/helper words again.
  \ public
  \   \ Interface words again. And so on.
  \ end-module

  \ As an alternative, the word 'begin-module' starts an unnamed module.

Wrote swapped, based on code found on Forth Dimensions:

( swapped )

  \ Credits:
  \ Adapted from code written by Sam Suan Chen, published
  \ on Forth Dimensions (volume 6, number 6, page 9, 1985-03).

: swapped  ( i*x n1 n2 -- j*x )
  >r 1+ cells sp@ +     ( i*x a1 ) ( R: n2 )
  r> 2+ cells sp@ +     ( i*x a1 a2 )
  over @ over @         ( i*x a1 a2 x1 x2 )
  >r swap !  r> swap !  ;

  \ Usage example:
  \
  \   ( 1 2 3 4 5 )  1 4 swapped  ( 4 2 3 1 5 )

  \ Original code by Sam Suan Chen,
  \ with an equivalent usage example:

  \ : xyswap  ( i*x n -- j*x )
  \   16 /mod >r dup + sp@ + sp@ r> dup + +
  \   over @ over @
  \   >r swap !  r> swap !  ;
  \
  \   ( 1 2 3 4 5 )  $25 xyswap  ( 4 2 3 1 5 )

Benchmarked the current d* and an alternative definition that happened to be bigger and slower:

( d* )

  \ Credits:
  \ Code by Wil Baden, published on Forth Dimensions
  \ (volume 19, number 6, page 33, 1998-04).

  \ This implementation uses 30 bytes.

: d*  ( d1 d2 -- d3 )
  >r swap >r            ( d1lo d2lo ) ( R: d2hi d1hi )
  2dup um* 2swap        ( d1lo*d2lo d1lo d2lo )
  r> * swap r> * + +  ; ( d1*d2 ) ( R: )

  \ Alternative implementation.
  \
  \ Credits:
  \ Adapted from code written by Robert L. Smith,
  \ published on Forth Dimensions (volume 4, number 1, page 3).
  \
  \ This implementation uses 36 bytes and is 5% slower.
  \
  \ : d*  ( d1 d2 -- d3 )
  \  over 4 pick um*  5 roll 3 roll * +  2swap * +  ;

Wrote m+:

( m+ )

  \ Credits:
  \ Code adapted from Z88 CamelForth.

need z80-asm

code m+  ( d1 n -- d2 )
  exx     \ save Forth IP
  bc pop  \ n
  de pop  \ d1 hi cell
  hl pop  \ d1 lo cell
  bc addp  hl push
  cy if  de inc  then  de push
  exx      \ restore Forth IP
  jpnext
  end-code

  \ doc{
  \
  \ m+  ( d1|ud1 n -- d2|ud2 )
  \
  \ Add _n_ to _d1|ud1_, giving the sum _d2|ud2_.
  \
  \ Standard: Forth-94 (DOUBLE) Forth-2012 (DOUBLE).
  \
  \ }doc

First working version of the Pong demo game.

Moved 2/ to the library.

2015-11-10

First changes to make also a +3DOS version, from the same sources.

2015-11-11

Wrote any-of, another addon for the case structure, based on any?, already ported from F83:

( any-of )

need any?

: (any-of)  ( x0 x1..xn n -- x0 x0 | x0 0 )
  dup 1+ pick >r any? r> tuck and  ;

: any-of  ( Compilation: -- of-sys )
          ( Run-time: x0 x1..xn n -- | x0 )
  postpone (any-of) postpone of  ; immediate compile-only

  \ Usage example:

  \ : test  ( n -- )
  \   case
  \     1 of  ." one"  endof
  \     2 7 10 3 any-of  ." two, seven or ten"  endof
  \     6 of  ." six"  endof
  \   endcase  ;

( any? )

  \ Credits:
  \ Code from F83.

need roll

variable (any?)

: any?  ( x0 x1..xn n -- f )
  \ Is any _x1..xn_ equal to _x0_?
  dup 1+ roll (any?) !  0 swap 0 do swap (any?) @ = or loop  ;

Improved the Pong game with delay counters and Z80 halts.

Started adapting the Dijkstra Guarded Command Control Structures, by M. Edward Borasky, 1996-08-03, originally published in Forth Dimensions volume 18, number 4 (1996-11), pages 5-14: Towards a Discipline of ANS Forth Programming, and later already adapted to hForth v0.9.9 by Wonyong Koh.

2015-11-12

Started removing the fig-Forth compiler security from control structures. They are inconvenient to build new structures. Beside, 62 bytes will be saved in the kernel.

Modified interpret: Now it uses an execution table to determine what to do with a number. First, two new words are required:

if 1 ; interpret_numbers_with_execution_table ; XXX NEW

  _variable_header numbers_execution_table_,'NUMBERS-EXECUTION-TABLE'

  dw two_literal_     ; compiling a 2-cell number
  dw literal_         ; compiling a 1-cell number
  dw not_understood_  ; error
  dw 0                ; interpreting a 1-cell number: do nothing
  dw 0                ; interpreting a 2-cell number: do nothing

  _colon_header not_understood_,'NOT-UNDERSTOOD'

  _literal error.not_understood
  dw throw_

endif

Then, interpret can be simplified:

if 1 ; interpret_numbers_with_execution_table ; XXX NEW

  dw parsed_name_,two_fetch_
  ; try to convert the text to a number
  ; ( ca len )
  dw number_question_ ; is it a number?  ( 0 | n 1 | d 2 )
  dw compiling_question_,question_negate_  ; ( d -2 | n -1 | 0 | n 1 | d 2 )
  dw cells_
  _literal numbers_execution_table_pfa+(2*cell)
  dw plus_,perform_
  dw branch_,interpret.begin ; repeat

else ; XXX OLD

  dw parsed_name_,two_fetch_
  ; ( ca len )
  dw number_question_ ; is it a number?
  ; ( 0 | n 1 | d 2 )
  dw zero_equals_
  _question_throw error.not_understood ; error if not
  dw dpl_,fetch_,zero_less_than_ ; single number?
  dw question_branch_,interpret.16bit_number

  ; decimal point detected, so it's a double, 32-bit, number
  dw compiling_question_
  dw zero_branch_,interpret.begin
  dw two_literal_
  dw branch_,interpret.begin

interpret.16bit_number:
  dw compiling_question_
  dw zero_branch_,interpret.begin
  dw literal_

  dw branch_,interpret.begin ; repeat

endif

The new method is only 1% faster, and saves only 4 bytes. Its main advantage is the code is simpler and more versatile (the execution table may be modified by the application).

Tried a similar method for the interpretion of words. It's only 1% faster and needs 24 bytes more:

if 1 ; table_interpret ; XXX NEW

  _colon_header compilation_only_,'COMPILATION-ONLY'

  _literal error.compilation_only
  dw throw_

  _variable_header interpret_execution_table_,'INTERPRET-EXECUTION-TABLE'

  dw execute_           ; compiling an immediate and compile-only word
  dw compile_comma_     ; compiling a compile-only word
  dw execute_           ; compiling an immediate word
  dw compile_comma_     ; compiling an ordinary word
interpret_execution_table.0
  dw 0                  ; not used
  dw execute_           ; interpreting an ordinary word
  dw execute_           ; interpreting an immediate word
  dw compilation_only_  ; interpreting a compile-only word
  dw compilation_only_  ; interpreting an immediate and compile-only word

endif

; ----------------------------------------------
  _colon_header interpret_,'INTERPRET'

; doc{
;
; interpret  ( -- )
;
; The outer text interpreter which sequentially executes or
; compiles text from the input stream (terminal or disk)
; depending on `state`. if the word name cannot be found after a
; search of the `context` search order it is converted to a
; number according to the current `base`.  That also failing, an
; error message echoing the name with a "?" will be given.
;
; }doc

interpret.begin:

  dw question_stack_

if 1 ; table_interpret ; XXX NEW

  dw parse_name_  ; ( ca len )
  dw dup_ ; end of stream?
  dw zero_branch_,interpret.end ; if so, finish

  dw find_name_ ; ( nfa | 0 )
  dw question_dup_ ; found?
  dw zero_branch_,interpret.word_not_found

  ; Found ( nfa )
  dw dup_,from_name_,swap_  ; ( cfa nfa )
  dw dup_,immediate_question_
  dw swap_,compile_only_question_,two_star_
  dw plus_,abs_ ; ( cfa +n )
  dw one_plus_ ; adjust the index
  dw compiling_question_,question_negate_ ; (cfa n )
  dw cells_
  _literal interpret_execution_table.0
  dw plus_,perform_
  dw branch_,interpret.begin

else ; XXX OLD

  dw parse_name_  ; ( ca len )

  dw dup_ ; end of stream?
  dw zero_branch_,interpret.end ; if so, finish

  dw find_name_ ; ( nfa | 0 )
  dw question_dup_ ; found?

  dw zero_branch_,interpret.word_not_found

  ; Found ( nfa )
  dw dup_,compile_only_question_,executing_question_, and_
  ; executing a compile-only word?
  _literal -14
  dw question_throw_ ; is so, throw error -14

  ; ( nfa )
  dw dup_,from_name_  ; ( nfa cfa )
  dw swap_,immediate_question_,zero_equals_ ; ( cfa non-immediate? )
  dw compiling_question_ ; ( nfa non-immediate? compiling? )
  dw and_ ; compiling a non-immediate word?
  dw zero_branch_,interpret.execute ; else, execute it

  ; Compiling a non-immediate word  ( cfa )
  dw compile_comma_
  dw branch_,interpret.begin

interpret.execute:
  ; Executing or immediate ( cfa )
  dw execute_
  dw branch_,interpret.begin

endif

The combination of both execution tables and their associated code is easy. The final solution needs only 5 bytes more than the original interpret without execution tables. The speed gain is only 1%, but the code is much versatile.

; ----------------------------------------------
  _variable_header interpret_table_,'INTERPRET-TABLE'

                        ; compiling...
  dw execute_           ; ...immediate and compile-only word
  dw compile_comma_     ; ...compile-only word
  dw execute_           ; ...immediate word
  dw compile_comma_     ; ...ordinary word
  dw two_literal_       ; ...2-cell number
  dw literal_           ; ...1-cell number

interpret_table.0:
  dw not_understood_    ; not a number (error)

                        ; interpreting...
  dw 0                  ; ...1-cell number (do nothing)
  dw 0                  ; ...2-cell number (do nothing)
  dw execute_           ; ...ordinary word
  dw execute_           ; ...immediate word
  dw compilation_only_  ; ...compile-only word (error)
  dw compilation_only_  ; ...immediate and compile-only word (error)

; ----------------------------------------------
  _colon_header interpret_,'INTERPRET'

; doc{
;
; interpret  ( -- )
;
; The outer text interpreter which sequentially executes or
; compiles text from the input stream (terminal or disk)
; depending on `state`. if the word name cannot be found after a
; search of the `context` search order it is converted to a
; number according to the current `base`.  That also failing, an
; error message echoing the name with a "?" will be given.
;
; }doc

interpret.begin:

  dw question_stack_

  dw parse_name_  ; ( ca len )
  dw dup_ ; end of stream?
  dw zero_branch_,interpret.end ; if so, finish

  dw find_name_ ; ( nfa | 0 )
  dw question_dup_ ; found?
  dw zero_branch_,interpret.word_not_found

  ; Word found
  ; ( nfa )
  dw dup_,from_name_,swap_  ; ( cfa nfa )
  dw dup_,immediate_question_
  dw swap_,compile_only_question_,two_star_
  dw plus_,abs_ ; ( cfa +n )
  _literal 3
  dw plus_ ; adjust the table index

interpret.do_it:
  ; ( +n )
  ; Execute element _+n_ of `interpret-table`,
  ; depending on `state`.
  dw compiling_question_,question_negate_,cells_
  _literal interpret_table.0
  dw plus_,perform_
  dw branch_,interpret.begin

interpret.word_not_found:
  dw parsed_name_,two_fetch_ ; ( ca len )
  dw number_question_ ; is it a number?  ( 0 | n 1 | d 2 )
  dw branch_,interpret.do_it

interpret.end:
  dw two_drop_
  dw semicolon_s_

2015-11-13

Found the bug that caused stack underflow when compiler security was deactivated in the kernel: a swap was missing in the new version of else!

Removed the fig-Forth compiler security checks from the kernel and the library. This makes the kernel 74 bytes smaller. The library words that included compiler security are -if, -while, -until, retry, the unused original case from Abersoft Forth Disassembled (a version without compiler security was already written, beside two smaller versions).

Only the assembler still uses compiler security, with ?pairs, but at the moment it seems a good idea. ?pairs has been moved from the kernel to the library.

After removing the compiler security from the kernel, the implementation of the Dijkstra Guarded Command Control Structures works fine:

( {if if} if> |if| )

  \ Dijkstra Guarded Command Control Structures

  \ Credits:
  \ Adapted from:
  \ Dijkstra Guarded Command Control Structures
  \ M. Edward Borasky, 1996-08-03
  \ Listing in "Towards a Discipline of ANS Forth Programming"
  \ Originally published in Forth Dimensions XVIII, No.4, pp5-14
  \ Adapted to hForth v0.9.9 by Wonyong Koh

need cs-roll

: {if  ( -- 0 )  0  ; immediate compile-only
  \ start a conditional
  \ put counter on stack

: if>
  \ ( count -- count+1 )
  \ ( c: -- orig1 )
  1+ >r postpone if  r> ; immediate compile-only
  \ right-arrow for {if ... if}

: |if|
  \ ( count -- count )
  \ ( c: orig ... orig1 -- orig ... orig2 )
  >r postpone ahead \ new orig
  1 cs-roll postpone then \ resolve old orig
  r>  ; immediate compile-only
  \ bar for {if ... if}

: if} \ end of conditional
  \ ( count -- )
  \ ( c: orig1 ... orign -- )
  >r  postpone ahead
  1 cs-roll postpone then \ resolve old orig
  -22 postpone literal postpone throw
    \ 'control structure mismatch'
  r> 0 do  postpone then  loop  ; immediate compile-only
  \ end a conditional
  \ XXX TODO use `?do` when available

( {do do} do> |do| )

  \ Dijkstra Guarded Command Control Structures

  \ Credits:
  \ Adapted from:
  \ Dijkstra Guarded Command Control Structures
  \ M. Edward Borasky, 1996-08-03
  \ Listing in "Towards a Discipline of ANS Forth Programming"
  \ Originally published in Forth Dimensions XVIII, No.4, pp5-14
  \ Adapted to hForth v0.9.9 by Wonyong Koh

need cs-pick  need cs-roll

: {do  ( c: -- dest )  postpone begin  ; immediate compile-only
  \ start a loop

: do>  ( c: dest -- orig1 dest )
  postpone if  1 cs-roll  ; immediate compile-only
  \ right arrow for {do ... od}

: |do|  ( c: orig1 dest -- dest )
  0 cs-pick postpone again \ resolve a copy of dest
  1 cs-roll postpone then \ resolve old orig
  ; immediate compile-only
  \ bar for {do ... do}

: do}  ( c: orig dest -- )
  \ end of loop
  postpone again \ resolve dest
  postpone then \ resolve orig
  ; immediate compile-only
  \ end a loop

Modified interpret to save 6 bytes, using name>immediate?:

  ; Word found
  ; ( nfa )
if 0 ; XXX OLD
  dw dup_,from_name_,swap_                  ; ( cfa nfa )
  dw dup_,immediate_question_               ; ( cfa nfa f1 )
  dw swap_,compile_only_question_,two_star_ ; ( cfa f1 n2 )
else ; XXX NEW
  dw dup_,name_to_immediate_question_       ; ( nfa cfa f1 )
  dw rot_,compile_only_question_,two_star_  ; ( cfa f1 n2 )
endif

Removed from the kernel the unused alternative of ." that compiled slit and type instead of (."). Every compiled string needed one more cell.

Added environment?, with only the Forth-2012 queries, not the obsolescent word set queries of Forth-94.

Added some words written by Wil Baden and published on Forth Dimensions (volume 18, number 5, page 27): sqrt, q2* and dsqrt.

Wrote d2*, and d2/, converting the 8080 code of DZX-Forth to Z80.

2015-11-14

Included a simple implementation of local variables:

( local )

  \ This is a simple solution to use an ordinary variable as
  \ local, saving its current value on the return stack and
  \ restoring it at the end.

  \ Credits:
  \ Original code by Henning Hanseng, published on
  \ Forth Dimensions 9/5 p. 6 (1988-01).

: restore-local  ( -- )  ( R: a x -- )  2r> swap !  ;
  \ a = address of a variable
  \ x = its original value
  \ Restore variable address and value.

: local  ( a0 -- )  ( R: a1 -- a0 x pfa a1 )
  \ a0 = address of a variable
  \ x = its current value
  \ a1 = return address
  \ pfa = pfa of `restore-local`
  r> swap                     \ save top return address
  dup @ 2>r                   \ save variable address and value
  ['] restore-local >body >r  \ force exit via `restore-local`
  >r  ;                       \ restore top return address

  \ Usage example:
  \
  \ variable v
  \ 1 v !  v ?  \ default value
  \ : test  ( -- )
  \   v local
  \   v ?  1887 v !  v ?  ;
  \ v ?  \ default value

Renamed -if, -while and -until:

( 0if 0while 0until )

: 0if  ( f -- )
  postpone ?branch >mark  ; immediate compile-only

: 0while  ( f -- )
  postpone 0if  postpone swap  ; immediate compile-only
  \ XXX TODO use cs-swap

: 0until  ( f -- )
  postpone ?branch <resolve  ; immediate compile-only

Their former names are better for checking negative numbers, after cmForth.

Factored the main code of header to the new word (header), that accepts a string. This is more versatile.

Wrote a new alternative implementation of local variables:

( create-anon anon +anon n>anon )

  \ 2015-11-14

  \ Anonymous variables.

  \ Credits:
  \
  \ Adapted, modified, improved and commented from original
  \ code written by Leonard Morgenstern, published on Forth
  \ Dimensions 6/1 p. 33 (1984-05).

variable (anon)  ( -- a )
  \ cfa of the latest anonymous variable.

: create-anon  ( -- )
  here (anon) !
  [ (anon) body> @ ] literal compile, 0 ,  ;
  \ Create a new anonymous variable.  `(anon)` is used to get
  \ and compile the cfa executed by all variables.

: anon  ( Compilation: -- ) ( Run-time: -- a )
  (anon) @
  compiling? if  compile,  else  execute  then  ; immediate
  \ Current anonymous variable (first cell),
  \ equivalent to `0 +anon`.

: +anon  ( Compilation:  n -- ) ( Run-time: -- )
  cells (anon) @ execute +
  compiling? if  postpone literal  then ; immediate
  \ Current anonymous variable (cell _n_, first is 0).

: n>anon  ( x1..xn n -- )
  cells postpone anon swap bounds do  i !  cell +loop  ;
  \ Store the given _n_ cells into the current anonymous
  \ variable.

  \ Usage example:

  \ create-anon 5 cells allot
  \
  \ : test
  \   400 300 200 100 000  5 n>anon
  \   anon ?          \ prints 0
  \   123 anon !
  \   anon ?          \ prints 123
  \   [ 2 ] +anon ?   \ prints 200
  \   555 [ 2 ] +anon !
  \   [ 2 ] +anon ?   \ prints 555
  \   ;

Added two alternative case structures:

( cases: )

  \ `cases:` structure, alternative to the standard `case`.
  \ It saves space, but is slower.

  \ 2015-11-14

  \ Credits:
  \
  \ Adapted, renamed and commented from code written by Dan
  \ Lerner, published on Forth Dimensions (volume 3, number 6,
  \ page 189, 1982-03).

  \ 109 bytes used

: cases:  ( "name" -- orig 0 )
  create >mark 0
  does>  ( selector -- ) ( selector pfa )
    true rot rot dup  ( true selector pfa pfa )
    cell+ swap @   ( true selector pfa+2 options )
    0 do  ( true selector a )
      2dup @ =  ( true selector a f )
      if    dup cell+ perform
            2>r 0= 2r>  ( false selector a )  exhaust
      else  cell+ cell+  then
   loop  ( true selector a | false selector a )
   rot if  perform  else  drop  then  drop  ;

  \ doc{
  \
  \ cases:  ( "name" -- orig 0 )
  \
  \ Define a `cases:` structure "name", built as an array of
  \ pairs (value and associated vector).
  \

  \ Usage example:
  \
  \ ----
  \ : say10     ." dek"  ;
  \ : say100    ." cent" ;
  \ : say1000   ." mil"  ;
  \ : sayother  ." alia" ;
  \
  \ cases: say  ( n -- )
  \     10 case>  say10
  \    100 case>  say100
  \   1000 case>  say1000
  \        other> sayother
  \
  \ 10 say  100 say  1000 say  1001 say
  \ ----
  \
  \ }doc

: case>  ( orig counter selector "name" -- orig counter' )
  , ' compile, 1+  ;

  \ doc{
  \
  \ case>  ( orig counter selector "name" -- orig counter' )
  \
  \ Compile an option into a `cases:` structure. The given
  \ _selector_ will cause the word "name" to be executed.
  \
  \ See `cases:` for an usage example.
  \
  \ }doc

: other>  ( orig counter "name" -- )  ' compile, swap !  ;

  \ doc{
  \
  \ other>  ( orig counter "name" -- )
  \
  \ Compile the default option of a `cases:` to be the word
  \ "name" . This must be the last option of the structure and
  \ is mandatory.  When no default action is required, `other>
  \ noop` can be used.
  \
  \ See `cases:` for an usage example.
  \
  \ }doc

( case )  \ baden-case )

  \ An alternative `case` structure that makes any
  \ calculation easier.

  \ 2015-11-14

  \ Credits:
  \
  \ Adapted and modified from code written by Wil Baden,
  \ published on Forth Dimensions (volume 8, number 5, page 29,
  \ 1987-01).

  \ XXX TODO rename, avoid standard names: `case`, `of`,
  \ `endof`.

defer case  ( n -- n n )  ' dup ' case defer!
defer othercase  ( n -- )  ' drop ' othercase defer!

: of  ( n f -- )
  postpone if  postpone drop  ; immediate compile-only

: endof  ( n f -- )
  postpone exit  postpone then  ; immediate compile-only  ;s

  \ Usage example

  \ ----
  \ : say0 ." nul"  ;
  \ : say1 ." unu"  ;
  \ : say2 ." du"  ;
  \ : say-other ." alia"  ;

  \ : test  ( n -- )
  \   case 0 = of  say0  endof
  \   case 1 = of  say1  endof
  \   case 2 = of  say2  endof
  \            othercase say-other  ;
  \ ----


Benchmarked all case structures in the library:

  \                   Bytes used            Speed (3)
  \                   --------------------- --------------
  \ Structure         Code (1)  Example (2) Frames Seconds
  \ ---------         --------- ----------- ------ -------
  \ case (7)           48       62          1365   27
  \ eforth-case (8)    54       62          1366   27
  \ 94-doc-case (6)    54       62          1365   27
  \ abersoft-case (5)  64       62          1365   27
  \ case: (4)          21       12           823   16
  \ options[ (9)      166       24          3627   72
  \ cases: (10)       109       18          3155   63
  \ baden-case (11)    18       56          1472   29
  \ baden-case (12)    36       56          1472   29
  \ baden-case (13)     0       50          1353   27

  \ (1) Bytes used by the compilation of the structure's code.
  \
  \ (2) Bytes used by the tested example: a structure with
  \ three options plus default, that execute a `noop`.
  \
  \ (3) For 32767 iterations with parameter 0..3. One system
  \ frame is 20 ms.

  \ (4) A port of F83's `case:`. It is more specific than the
  \ other structures: it lacks a default option and its
  \ argument is positional.
  \
  \ (5) Eaker/Forth-94 `case` of Abersoft Forth, but with
  \ compiler security removed.
  \
  \ (6) Eaker/Forth-94 `case` copied from the Forth-94
  \ documentation.
  \
  \ (7) Eaker/Forth-94 `case` of eForth, with a little
  \ simplification.
  \
  \ (8) Eaker/Forth-94 `case` of eForth.
  \
  \ (9) A port of IsForth's `case:`.
  \
  \ (10) A port of a structure written by Dan Lerner, published
  \ on Forth Dimensions (volume 3, number 6, page 189,
  \ 1982-03).
  \
  \ (11) "Ultimate CASE Statement", written by Wil Baden,
  \ published on Forth Dimensions (volume 8, number 5, page 29,
  \ 1987-01).
  \
  \ (12) The same "Ultimate CASE Statement", by Wil Baden, with
  \ two syntactic sugar words added: `endof` and `othercase`.
  \
  \ (13) The same "Ultimate CASE Statement", by Wil Baden,
  \ emulated with standard words. This is a bit faster
  \ because, without the syntactic sugar definitions, one `dup`
  \ and two `drop` are saved.

2015-11-15

Implemented the Noble arrays.

( 1array ) \ noble-arrays )

  \ Toolset for one- and two-dimensional arrays in ANS Forth

  \ ---------------------------------------------------
  \ (c) Copyright 2001 Julian V. Noble. \
  \ Permission is granted by the author to \
  \ use this software for any application pro- \
  \ vided this copyright notice is preserved. \
  \ ---------------------------------------------------

  \ ...........................................................
  \ References:
  \
  \ http://forth.sourceforge.net/techniques/arrays-jvn/index-v.txt
  \ http://forth.sourceforge.net/techniques/
  \ http://www.phys.virginia.edu/classes/551.jvn.fall01/arrays.f

  \ ...........................................................
  \ Implementation:

  \ words for 1-dimensional arrays

need <=

: long ; immediate

: 1array ( len #bytes/datum --) \ ( #b len data ...)
  create 2dup , , * allot ;

: _len ( base_addr -- len)  cell+ @ ;
  \ determine length of an array

: } ( base_adr indx -- adr[indx] )
  over _len over <= over 0< or #-272 ?throw
    \ #-272 = index out of range
  over @ * + cell+ cell+ ;

( 2array ) \ noble-arrays )

  \ words for 2-dimensional arrays

need 1array

: wide ; immediate

: 2array ( hgt wid data_size --) \ ( wid #b len data ...)
  create >r tuck , ( wid hgt)
  r@ , * dup , r> * allot ;

: }} ( base_adr m n -- adr[m,n] ) \ data stored row-wise
  2>r cell+ dup cell- @
  r> * r> + ( base_adr+cell m+n*w)
  } ;

  \ ...........................................................
  \ Usage examples:

  \ 20 long 2 floats 1array a{
  \   \ complex vector
  \
  \ 20 long 20 wide 1 floats 2array m{{
  \   \ real matrix
  \
  \ 20 long 1 cells 1array irow{
  \   \ single-length, integer-valued vector
  \
  \ a{ i }         ( -- adr[a_i] )
  \ m{{ i j }}     ( -- adr[m_ij] )
  \ irow{{ i j }}  ( -- adr[irow_i] )

Included Leo Wong's array:

( array ato ) \ wong-arrays )

  \ One-dimensional arrays by Leo Wong

  \ ...........................................................
  \ References:

  \ http://forth.sourceforge.net/techniques/arrays-lw/index-v.txt
  \ http://forth.sourceforge.net/techniques/

  \ ...........................................................
  \ Description:

  \ This approach is unique in having no proponents. It is part
  \ of Flight, a Forth scripting language. Several Flight
  \ programs can be found by Googling comp.lang.forth. In
  \ conformance with the design goals of Flight, bounds
  \ checking is included.

  \ ...........................................................
  \ Main idea:

  \ Treat arrays like values.

    \ \ Example of use:
    \ 4 array bar
    \ 10 0 ato bar 20 1 ato bar 30 2 ato bar 40 3 ato bar

    \ 3 bar .
    \ 0 bar .
    \ 123 3 ato bar
    \ 3 bar .
    \ 1 3 +ato bar
    \ 3 bar .

  \ Note that as in most Forth numerical arguments go before:

    \ 1 3 +ato bar NOT: 1 +ato 3 bar

  \ This approach can be extended to different-sized data
  \ (including strings) and 2 or more dimensions by having
  \ their own versions of array array> ato +ato. A Flight
  \ scripter would not be expected to implement these words.

  \ ...........................................................
  \ Sample implementation:

need within

: array>  ( n 'array -- a )
  2dup @ 0 swap within 0= #-272 ?throw
    \ #-272 = array index out of range
  cell+ swap cells +  ;
  \ if in range, return element address, else abort

: array  ( n "name" -- )
  create dup , cells allot
  does> ( n -- x ) ( n pfa ) array> @  ;
  \ define array

: (ato)  ( x n array -- )  >body array> !  ;
  \ store to array

: ato  ( x n "name" -- )
  compiling?  if    postpone ['] postpone (ato)
              else  ' (ato)  then ; immediate
  \ store to array
  \ usage: x n ato <name>

: (+ato)  ( x n array -- )  >body array> +!  ;
  \ add to array

: +ato  ( n "name" -- )
  compiling? if    postpone ['] postpone (+ato)
             else  ' (+ato)  then  ; immediate
  \ add to array

Added a new switch structure:

( link@ link, )

  \ 2015-11-15

  \ Credits:
  \
  \ Code written after the description by Rick VanNorman,
  \ published on Forth Dimensions (volume 20, number 3, pages
  \ 19..22, 1998-09).

defer link@  ( node1 -- node2 )
  ' @ ' link@ defer!
  \ Fetch the linked list node _node_ is pointing to.

: link,  ( node -- )  here over @ , swap !  ;
  \ Create a new linked list node, pointing to _node_.

( :switch )

  \ 2015-11-15

  \ Credits:
  \
  \ Original code by Rick VanNorman, published on Forth
  \ Dimensions (volume 20, number 3, pages 19..22, 1998-09).

need link@  need link,

: switcher  ( i*x n head -- j*x )
  dup cell+ @ >r  \ save default cfa
  begin  link@ ?dup while  ( n a )
    2dup cell+ @ = if   \ match
      nip cell+ cell+ perform  r> drop exit
    then
  repeat  r> execute  ;
  \ Search the linked list from its _head_ for a match to the
  \ value _n_. If a match is found, discard _n_ and execute the
  \ associated matched cfa. If no match is found, leave _n_ on
  \ the stack and execute the default cfa.

: :switch  ( cfa "name" -- a )
  create  >mark swap ,
  does>  ( n -- )  ( n pfa ) switcher  ;
  \ Create a code switch whose default behaviour is given by
  \ _cfa_. Leave the address of the head of its list on the
  \ stack.

: <switch  ( head cfa n -- head )
  2 pick link,  , ,  ;
  \ Define a new clause to execute _cfa_ when the key _n_
  \ is matched.

-->

( :switch )

: [switch  ( "name1" "name2" -- head )
  create  >mark ' ,
  does>  ( n -- )  ( n pfa ) switcher  ;
  \ Define a new switch "name1" with its default behaviour
  \ "name2". The head of the switch is left on the stack for
  \ defining clauses.

: [+switch  ( "name" -- head )  ' >body  ;
  \ Leave the head of the given switch "name", for clauses to
  \ append to.

: switch]  ( head -- )  drop  ;
  \ Discard the switch head from the stack.
  \ Used after defining clauses.

: runs  ( head n "name" -- )  ' swap <switch  ;
  \ Associate the cfa of "name" to clause _n_ of switch _head_.

;s

  \ XXX TODO -- `run:`

: run:  ( head n "text<;>" -- )
  :noname [char] ; parse evaluate postpone ; ( cfa )
  swap >switch  ;

( :switch-test ) \ without syntactic sugar

: one    ( -- )  ." unu " ;
: two    ( -- )  ." du "  ;
: three  ( -- )  ." tri "  ;
: many   ( n -- )  . ." is too much! "  ;

' many :switch numbers
  \ `many` is the default behaviour of the new switch `numbers`

  ' one   1 <switch
  ' two   2 <switch
  ' three 3 <switch  drop

cr 1 numbers 2 numbers 3 numbers 4 numbers

' numbers >body  :noname  ." kvar "  ; 4 <switch drop
  \ add a new clause for the number 4

cr 1 numbers 2 numbers 3 numbers 4 numbers

-->

( :switch-test ) \ with syntactic sugar

[switch sugar-numbers many
  1 runs one
  2 runs two
  3 runs three
switch]

cr 1 sugar-numbers 2 sugar-numbers
   3 sugar-numbers 4 sugar-numbers

: four  ( -- )  ." kvar "  ;

[+switch sugar-numbers
  4 runs four
switch]
  \ add a new clause for the number 4

cr 1 sugar-numbers 2 sugar-numbers
   3 sugar-numbers 4 sugar-numbers

Added a leapy year calculation:

( leapy-year? )

  \ 2015-11-15

  \ Credits:
  \
  \ Code written by Wil Baden, published on Forth Dimensions
  \ (volume 8, number 5, page 31, 1987-01).

: leapy-year?  ( n -- f )
  dup 400 mod 0= if  drop true   exit  then
  dup 100 mod 0= if  drop false  exit  then
        4 mod 0= if       false  exit  then
  false  ;

  \ Alternative.

  \ need baden-case
  \
  \ : leapy-year?  ( n -- f )
  \   case 400 mod 0= of  true   endof
  \   case 100 mod 0= of  false  endof
  \   case   4 mod 0= of  true   endof
  \   othercase false  ;

2015-11-16

Improved header: The new double variable next-name is checked for a name string. This feature was inspired by Gforth's nextname. A double variable is simpler and saves memory in this case.

  _colon_header header_,'HEADER'

; doc{
;
; header  ( "name" | -- )

; Create a dictionary header with its code field pointing to its
; parameter field.
;
; The double variable `next-name` may hold a string to be used
; as the name.  If its length is zero, "name" is parsed from the
; stream as usual.

; ----
; : header  ( "name" | -- )
;   next-name 2@
;   dup if    next-name off
;       else  2drop parse-name  then  (header)  ;
; ----

; }doc

  dw next_name_,two_fetch_
  dw dup_
  dw zero_branch_,header.parse_name
  dw next_name_,off_
  dw branch_,header.end
header.parse_name
  dw two_drop_,parse_name_
header.end
  dw paren_header_
  dw semicolon_s_

Confirmed the recent implementation of wordlist works fine. (vocabulary) was factored from vocabulary to implement wordlist, though it could be simpler to write vocabulary based on wordlist. Beside, the association of existing word lists to names is not solved yet. For example, order hangs the system when a word list is in the search order, because >name is supposed to find the associated name field, and does no check the end of the name fields bank...

; ----------------------------------------------
  _colon_header paren_vocabulary_,'(VOCABULARY)'

; doc{
;
; (vocabulary)  ( -- )
;
; Create the parameter field of a vocabulary or word list just created.
;
; ----
; : (vocabulary)  ( -- )
;   0 ,  here voc-link @ , voc-link !  ;
; ----
;
; }doc

  _literal 0
  dw comma_                   ; nfa of the latest word defined in the vocabulary
  dw here_                    ; new contents of vocabulary link
  dw voc_link_,fetch_,comma_  ; compile the previous contents of `voc-link`
  dw voc_link_,store_         ; update `voc-link`
  dw semicolon_s_

; ----------------------------------------------
  _colon_header vocabulary_,'VOCABULARY'

; doc{
;
; vocabulary  ( "name" -- )
;
; Create a vocabulary with the parsed "name" as its name. The
; run-time efect of `name` is to replace `context`, the top
; vocabulary in the search order.
;
; ----
; : vocabulary  ( "name" -- )
;   create  (vocabulary)
;   does>   ( -- )  ( pfa ) context !  ;
; ----
; }doc

  dw create_,paren_vocabulary_
  dw paren_semicolon_code_
do_vocabulary:
  call do_does
  ; The next words are to be executed when the vocabulary is invoked.
  dw context_,store_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header wordlist_,'WORDLIST'

; doc{
;
; wordlist  ( -- wid )
;
; : wordlist ( -- wid )
;   :noname  (vocabulary)  postpone ;  >body  ;
;
; }doc

  dw colon_no_name_,paren_vocabulary_,semicolon_,to_body_
  dw semicolon_s_

Made : non-immediate.

2015-11-17

Wrote an alternative method to set a string as the name of the next word to be defined. The new approach (and the word names) is adapted from Gforth:

; ----------------------------------------------
  _two_variable_header nextname_string_,'NEXTNAME-STRING'

; doc{
;
; next-name  ( -- a )
;
; A double variable that may hold the address and length of a
; name to be used by the next defining word.  This variable is
; set by `nextname`.
;
; }doc

  dw 0,0

if 1 ; nextname

  ; XXX NEW -- This alternative was inspired by Gforth.

; ----------------------------------------------
  _defer_header header_,'HEADER',,input_stream_header_

; ----------------------------------------------
  _colon_header input_stream_header_,'INPUT-STREAM-HEADER'

; doc{
;
; header  ( "name" -- )

; Create a dictionary header "header" with its code field
; pointing to its parameter field.

; ----
; : input-stream-header  ( "name" | -- )
;   parse-name header,  ;
; ----

; }doc

  dw parse_name_,header_comma_
  dw semicolon_s_

; ----------------------------------------------
  _colon_header nextname_header_,'NEXTNAME-HEADER'

; doc{
;
; nextname-header  ( -- )

; Create a dictionary header with its code field pointing to its
; parameter field, using the name string set by `nextname`.

; ----
; : nextname-header  ( -- )
;   nextname-string 2@ header,
;   ['] input-stream-header ['] header defer!  ;
; ----

; }doc

  dw nextname_string_,two_fetch_,header_comma_
  _literal input_stream_header_
  _literal header_
  dw defer_store_ ; restore the default behaviour of `header`
  dw semicolon_s_

; ----------------------------------------------
  _colon_header nextname_,'NEXTNAME'

; doc{
;
; nextname  ( ca len -- )
;
; ----
; : nextname  ( ca len -- )
;   nextname-string 2!
;   ['] nextname-header ['] header defer!  ;
; ----
;
; }doc

  dw nextname_string_,two_store_
  _literal nextname_header_
  _literal header_
  dw defer_store_ ; change the default behaviour of `header`
  dw semicolon_s_

else

  ; XXX OLD

; ----------------------------------------------
  _colon_header header_,'HEADER'

; doc{
;
; header  ( "name" | -- )

; Create a dictionary header with its code field pointing to its
; parameter field.
;
; The double variable `next-name` may hold a string to be used
; as the name.  If its length is zero, "name" is parsed from the
; stream as usual.

; ----
; : header  ( "name" | -- )
;   nextname-string 2@
;   dup if    nextname-string off
;       else  2drop parse-name  then  header,  ;
; ----

; }doc

  dw nextname_string_,two_fetch_
  dw dup_
  dw zero_branch_,header.parse_name
  dw nextname_string_,off_
  dw branch_,header.end
header.parse_name
  dw two_drop_,parse_name_
header.end
  dw header_comma_
  dw semicolon_s_

endif

Some benchmarking revealed both methods are equally fast.

2015-11-18

Started writting an alternative implementation of the heap, using a bitmap, after an example provided by Javier Gil in his book Introducción a Forth.

2015-11-19

Factored >digit from # and rewrote it in Z80. Faster, smaller and more versatile.

Modernized two old names that still remained from Abersoft Forth: Forth-79's u/mod to um/mod, after Forth-94 and Forth-2012; fig-Forth's m/mod to ud/mod, after common usage.

Converted sign to modern Forth. The fig-Forth definition was : sign ( n d -- d ) rot 0< if [char] - hold then ; instead of simply : sign ( n -- ) 0< if [char] - hold then ;.

2015-11-22

Added two simple words to the library: create: (from CP/M-volksForth 3.80a) and swap-current (from lpForth). Modified F83's case: to use create:.

Added also nuf? and aborted?:

( nuf? )

  \ Credits:
  \
  \ Code adapted from lpForth and Forth Dimensions (volume 10,
  \ number 1, page 29).

need aborted?

[defined] 'cr' ?\ 13 constant 'cr' \ code of carriage return

: nuf?  ( -- f )  'cr' aborted?  ;

  \ XXX OLD -- Classic definition:
  \
  \ : nuf?  ( -- f )  key? dup if  key 2drop key 'cr' = then  ;

  \ doc{
  \
  \ nuf?  ( -- f )
  \
  \ If no key is pressed return _false_.  If a key is pressed,
  \ discard it and wait for a second key. Then return _true_ if
  \ it's a carriage return, else return _false_.

  \ Usage example:
  \
  \ : listing  ( -- )
  \   begin  ." bla " nuf?  until  ." Aborted"  ;
  \
  \ }doc

( aborted? )

: aborted?  ( c -- f )
  key? dup  if    key 2drop key =
            else  nip  then  ;

  \ doc{
  \
  \ aborted?  ( c -- f )
  \
  \ If no key is pressed return _false_.  If a key is pressed,
  \ discard it and wait for a second key. Ther return _true_ if
  \ it's _c_, else return _false_.

  \ Usage example:
  \
  \ : listing  ( -- )
  \   begin  ." bla "  bl aborted?  until  ." Aborted"  ;
  \
  \ }doc

Added 3drop and 4drop:

( 3drop 4drop )

code 3drop  ( x1 x2 x3 -- )
  E1 c,  E1 c,  E1 c,
    \ pop hl
    \ pop hl
    \ pop hl
  jpnext  end-code

code 4drop  ( x1 x2 x3 x4 -- )
  E1 c,  E1 c,  E1 c,  E1 c,
    \ pop hl
    \ pop hl
    \ pop hl
    \ pop hl
  jpnext  end-code

Added negative-check versions of branch and conditionals:

( -branch )

code -branch  ( f -- )

  E1 c,
    \ pop hl
  CB c, 7C c,
    \ bit 7,h ; negative?
  CA c, ' branch >body ,
    \ jp z,branch_pfa ; if not, branch
  03 c, 03 c,
    \ inc bc
    \ inc bc ; skip the inline branch address
  jpnext
  end-code

  \ doc{
  \
  \ -branch  ( f -- )
  \
  \ A run-time procedure to branch conditionally. If  _f_ on
  \ stack is negative, the following in-line address is copied
  \ to IP to branch forward or  backward.
  \
  \ Compiled by `-if`, `-while` and `-until`.
  \
  \ }doc

( -if -while -until )

  \ XXX TODO Compilation stack notation.

need -branch  need cs-swap

: -if  ( f -- )
  postpone -branch >mark  ; immediate compile-only

  \ doc{
  \
  \ -if  ( f -- )
  \
  \ Faster and smaller alternative to the idiom `0< if`.
  \
  \ }doc

: -while  ( f -- )
  postpone -if  postpone cs-swap  ; immediate compile-only

  \ doc{
  \
  \ -while  ( f -- )
  \
  \ Faster and smaller alternative to the idiom `0< while`.
  \
  \ }doc

: -until  ( f -- )
  postpone -branch <resolve  ; immediate compile-only

  \ doc{
  \
  \ -until  ( f -- )
  \
  \ Faster and smaller alternative to the idiom `0< until`.
  \
  \ }doc

2015-11-23

Added the variable limit to implement an upper memory limit. This way data can be allocated outside the dictionary.

Fixed accept: useless control keys were not filtered out.

Made max a bit faster with a little optimization of the assembler.

Factored the "ok" prompt out from quit: created the defered word ok and its default vector .ok. This way the some useful customizations are possible.

Finished the first version of the character translation table. So far the translations were hardcoded in xkey, the temporary version of key used by accept.

2015-11-24

Substituted the assembler code of um/mod (from Abersoft Forth's u/mod, a word from Forth-79) with the version of Z88 CamelForth. It's 18 bytes shorter and c. 25% faster. Beside, an obscure bug of the Abersoft Forth version caused wrong results with certain values.

  _code_header u_m_slash_mod_,'UM/MOD'

; doc{
;
; um/mod ( ud u1 -- u2 u3 )
;
; Divide _ud_ by _u1_, giving the quotient _u3_ and the
; remainder _u2_.  All values and arithmetic are unsigned.
;
; Standard: Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

  ; Credits:
  ;
  ; Code adapted from Z88 CamelForth.

  exx
  pop bc      ; BC = divisor
  pop hl      ; HLDE = dividend
  pop de
  ld a,16     ; loop counter
  sla e
  rl d        ; hi bit DE -> carry
u_m_slash_mod.do:
  adc hl,hl   ; rot left w/ carry
  jr nc,u_m_slash_mod.3
  ; case 1: 17 bit, cy:HL = 1xxxx
  or a        ; we know we can subtract
  sbc hl,bc
  or a        ; clear cy to indicate sub ok
  jr u_m_slash_mod.4
  ; case 2: 16 bit, cy:HL = 0xxxx
u_m_slash_mod.3:
  sbc hl,bc   ; try the subtract
  jr nc,u_m_slash_mod.4 ; if no cy, subtract ok
  add hl,bc   ; else cancel the subtract
  scf         ;   and set cy to indicate
u_m_slash_mod.4:
  rl e        ; rotate result bit into DE,
  rl d        ; and next bit of DE into cy
  dec a
  jr nz,u_m_slash_mod.do
  ; now have complemented quotient in DE,
  ; and remainder in HL
  ld a,d
  cpl
  ld b,a
  ld a,e
  cpl
  ld c,a
  push hl     ; remainder
  push bc     ; quotient
  exx
  _jp_next

if 0 ; XXX OLD

  ; Credits:
  ;
  ; Code from Abersoft Forth.
  ; The original word was called `u/mod`, from Forth-79.

; XXX FIXME -- This word, has a bug that affects `line>string`, used
; by `message`, and other words that use it: `*/mod`, `mod` and
; `/mod`, with certain negative values, return different values
; in Abersoft Forth and other Forth systems that have been
; tested (some of them are fig-Forth).

  ld hl,0x0004
  add hl,sp
  ld e,(hl)
  ld (hl),c
  inc hl
  ld d,(hl)
  ld (hl),b
  pop bc
  pop hl
  ld a,l
  sub c
  ld a,h
  sbc a,b
  jr c,l60a0h
  ld hl,0xFFFF
  ld de,0xFFFF
  jr l60c0h
l60a0h:
  ld a,0x10
l60a2h:
  add hl,hl
  rla
  ex de,hl
  add hl,hl
  jr nc,l60aah
  inc de
  and a
l60aah:
  ex de,hl
  rra
  push af
  jr nc,l60b4h
  and l
  sbc hl,bc
  jr l60bbh
l60b4h:
  and a
  sbc hl,bc
  jr nc,l60bbh
  add hl,bc
  dec de
l60bbh:
  inc de
  pop af
  dec a
  jr nz,l60a2h
l60c0h:
  pop bc
  push hl
  push de
  _jp_next

endif

Moved get-current and set-current to the kernel. This costs only 8 bytes, because all current @ and current ! used in the kernel can be substituted.

Removed the old unused alternative versions of throw and documented the current version:

  _colon_header throw_,'THROW'

  ; Credits:
  ; Code from DZX-Forth.
  ; Comments from MPE Forth for TiniARM.

; doc{
;
; throw  ( n -- )

; Standard: Forth-94 (EXCEPTION), Forth-2012 (EXCEPTION).

; ----
; : throw  ( n -- )
;   ?dup if
;     catcher @ ?dup 0=   \ no catcher?
;     if  error  then     \ `error` does not return
;     rp!                 \ restore previous return stack
;     r> catcher !        ( n )  \ restore previous catcher
;     r> swap >r          ( saved-sp ) ( R: n )
;     sp! drop r>         ( n )  \ restore stack
;     \ Return to the caller of `catch` because return stack is
;     \ restored to the state that existed when `catch` began
;     \ execution.
;   then  ;
; ----

; }doc

  dw question_dup_
  dw zero_branch_,throw.end
  dw catcher_,fetch_,question_dup_  ; catcher?
  dw question_branch_,throw.catcher ; if so, branch
  dw error_ ; no return from `error`.

throw.catcher:
  dw rp_store_                ; restore previous return stack
  dw from_r_,catcher_,store_  ; ( n )  restore previous catcher
  dw from_r_,swap_,to_r_      ; ( saved-sp ) ( R: n )
  dw sp_store_,drop_,from_r_  ; ( n )  restore stack

  ; Return to the caller of `catch` because return stack is
  ; restored to the state that existed when `catch` began
  ; execution.

throw.end:
  dw semicolon_s_

Substituted the code of 2swap with a faster (0.89 the execution time) and smaller version, adapted from Z88 CamelForth:

  _code_header two_swap_,'2SWAP'

; doc{
;
; 2swap  ( x1 x2 x3 x4 -- x3 x4 x1 x2 )
;
; }doc

if 0 ; XXX OLD

  ; Credits:
  ; Code from DZX-Forth.

                ; T   B
                ; --- --
  pop hl        ;  10 01
  pop de        ;  10 01
  ex (sp),hl    ;  19 01
  push hl       ;  11 01
  ld hl,5       ;  10 03
  add hl,sp     ;  11 01
  ld a,(hl)     ;  07 01
  ld (hl),d     ;  07 01
  ld d,a        ;  04 01
  dec hl        ;  06 01
  ld a,(hl)     ;  07 01
  ld (hl),e     ;  07 01
  ld e,a        ;  04 01
  pop hl        ;  10 01
  jp push_hlde  ;  10 03
                ;  11 00 push de
                ;  11 00 push hl
                ; --- --
                ; 155 19 TOTAL

else ; XXX NEW

  ; Credits:
  ; Code adapted from Z88 CamelForth

                      ; T   B
                      ; --- --
  exx                 ;  04 01
  pop hl  ; hl'=x4    ;  10 01
  pop de  ; de'=x3    ;  10 01
  exx                 ;  04 01
  pop hl  ; hl=x2     ;  10 01
  pop de  ; de=x1     ;  10 01
  exx                 ;  04 01
  push de ; x3        ;  11 01
  push hl ; x4        ;  11 01
  exx                 ;  04 01
  jp push_hlde        ;  10 03
                      ;  11 00 push de
                      ;  11 00 push hl
                      ; --- --
                      ; 110 13 TOTAL

endif

Compared the current code of dnegate with the code of Spectrum Forth-83. The current code is faster and only 1 byte bigger:

  _code_header d_negate_,'DNEGATE'

; doc{
;
; dnegate  ( d1 -- d2 )
;
; Negate _d1_, giving its arithmetic inverse _d2_.
;
; Standard: Forth-79 (Required word set), Forth-83 (Required
; word set), Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

if 0

  ; Credits:
  ; Code from Abersoft Forth, also found in White Lightning.

  ; XXX TODO optimize?

                                                ; T   B
                                                ; --- --
  pop hl      ; high part                       ;  10 01
  pop de      ; low part                        ;  10 01
  sub a       ; zero                            ;  04 01
  sub e       ; two's complement to carry       ;  04 01
  ld e,a                                        ;  04 01
  ld a,0x00                                     ;  07 02
  sbc a,d     ; two's complement with carry     ;  04 01
  ld d,a                                        ;  04 01
  ld a,0x00                                     ;  07 02
  sbc a,l     ; two's complement with carry     ;  04 01
  ld l,a                                        ;  04 01
  ld a,0x00                                     ;  07 02
  sbc a,h     ; two's complement with carry     ;  04 01
  ld h,a                                        ;  04 01
  jp push_hlde                                  ;  10 03
                                                ;  11 00 push de
                                                ;  11 00 push hl
                                                ; --- --
                                                ; 109 20

else

  ; Credits:
  ; Code from Spectrum Forth-83.
  ;
  ; Comparison with the Abersoft Forth version:
  ; Speed: 1.04
  ; Size:  -1

                                                ; T   B
                                                ; --- --
  pop     hl        ; high part                 ;  10 01
  pop     de        ; low part                  ;  10 01
  push    hl        ; save copy                 ;  11 01
  ld      hl,0x0000                             ;  10 03
  and     a                                     ;  04 01
  sbc     hl,de     ; hl = negated low part     ;  15 02
  pop     de        ; high part                 ;  10 01
  push    hl        ; negated low part          ;  11 01
  ld      hl,0x0000                             ;  10 03
  sbc     hl,de     ; hl = negated high part    ;  15 02
  jp push_hl                                    ;  10 03
                                                ;  11 00 push hl
                                                ; --- --
                                                ; 127 19

endif

2015-11-25

Reorganized the code of ;s, exit and ?exit. Removed the old ;s. It may be redefined as an alias of exit, but exit and ?exit can be used instead.

if 0 ; XXX OLD

; ----------------------------------------------
  _code_header semicolon_s_,';S'

; doc{
;
; ;s  ( -- )
;
; Return execution to the calling definition.  Unnest one level.
;
; It is used to stop interpretation of a screen. It is also the
; run-time word compiled at the end of a colon-definition which
; returns execution to the calling procedure.

; Standard: fig-Forth.
;
; }doc

  ld hl,(return_stack_pointer)
  ld c,(hl)
  inc hl
  ld b,(hl)
  inc hl
  ld (return_stack_pointer),hl
  _jp_next

; ----------------------------------------------
  _colon_header exit_,'EXIT',compile_only

; doc{
;
; exit  ( -- ) ( R: nest-sys -- )

; Return control to the calling definition, specified by
; _nest-sys_.
;
; Before executing `exit` within a do-loop, a program shall
; discard the loop-control parameters by executing `unloop`.
;
; Standard: Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

  dw r_drop_
  dw exit_

; ----------------------------------------------
  _colon_header question_exit_,'?EXIT',compile_only

; doc{
;
; ?exit  ( f -- ) ( R: a | -- a | )
;
; If _f_ is non-zero, return control to the calling definition,
; specified by the address on the return stack.
;
; `?exit` is not intended to be used within a do-loop. Use `if
; unloop exit then` instead.
;
; }doc

  dw question_branch_,exit_pfa
  dw exit_

else ; XXX NEW

; ----------------------------------------------
  _code_header exit_,'EXIT'

; doc{
;
; exit  ( -- ) ( R: nest-sys -- )

; Return control to the calling definition, specified by
; _nest-sys_.
;
; Before executing `exit` within a do-loop, a program shall
; discard the loop-control parameters by executing `unloop`.
;
; In Solo Forth `exit` can be used in interpretation mode to
; stop the interpretation of a block.
;
; Standard: Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

  ld hl,(return_stack_pointer)
  ld c,(hl)
  inc hl
  ld b,(hl)
  inc hl
  ld (return_stack_pointer),hl
  _jp_next

; ----------------------------------------------
  _code_header question_exit_,'?EXIT'

; doc{
;
; ?exit  ( f -- ) ( R: nest-sys | -- nest-sys | )
;
; If _f_ is non-zero, return control to the calling definition,
; specified by _nest-sys_.
;
; `?exit` is not intended to be used within a do-loop. Use `if
; unloop exit then` instead.
;
; In Solo Forth `?exit` can be used in interpretation mode to
; stop the interpretation of a block.
;
; }doc

  pop hl
  ld a,h
  or l
  jp nz,exit_pfa
  _jp_next