Solo Forth development history in 2015-12

Description of the page content

Solo Forth development history in 2015-12.

Tags:

2015-12-04

Moved >order to the kernel.

Started adapting the tape words from Afera.

2015-12-14

Added a new control structured, adapted from Pygmy Forth:

( associative-case: )

  \ Credits:
  \
  \ Code adapted from Pygmy Forth's `case:`

need create:

: associative-case: ( "name" -- )
  create:
  does> ( n -- ) ( n pfa ) cell+  \ move past `lit`
  begin   2dup @ dup 0= >r  ( n a n n')
          =  r> or  0= ( n a f )
  while   ( n a )  [ 3 cells ] literal +  \ no match
  repeat  nip cell+ perform  ;

  \ doc{

  \ associative-case: ( "name" -- )
  \
  \ Create an associative case definition "name":
  \ `name  ( i*x n -- j*x )`.

  \ Usage example:

  \ ----
  \ : red       ." red"  ;
  \ : blue      ." blue"  ;
  \ : orange    ." orange"  ;
  \ : pink      ." pink"  ;
  \ : black     ." black"  ;
  \
  \ associative-case: color  ( n -- )
  \   7 red  12 blue  472 orange  15 pink  0 black  ;
  \
  \ 7 color cr  472 color cr  3000 color cr
  \ ----

  \ _n_ for default must be 0 and the default pair must be
  \ last.  Numbers can be in any order except 0 must be last.
  \ An actual zero or a no match causes the default to be
  \ executed.  Numbers can't be constants.

  \ }doc

For clarity, renamed the current case: (adapted from F83) to positional-case:.

Added time and date support, to some extent. No real clock, but only the system frames counter. The date is not updated anyway.

( set-date get-date )

create (date)  1 c,  1 c,  2016 ,
  \ day, month, year

: get-date  ( -- day month year )
  (date) c@
  [ (date) 1+ ] literal c@
  [ (date) 2+ ] literal @  ;

  \ doc{
  \
  \ get-date  ( -- day month year )
  \
  \ Get the current date. The default date is 2016-01-01. It
  \ can be changed with `set-date`. The date is not updated by
  \ the system.
  \
  \ }doc

: set-date  ( day month year -- )
  [ (date) 2+ ] literal !
  [ (date) 1+ ] literal c!
  (date) !  ;

  \ doc{
  \
  \ set-date  ( day month year -- )
  \
  \ Set the current date. The default date is 2016-01-01. It
  \ can be fetch with `get-date`. The date is not updated by
  \ the system.
  \
  \ }doc

( set-time get-time reset-time )

need frames@  need frames!  need m+  need alias  need ud*

: get-time  ( -- second minute hour )
  frames@ 50 um/mod nip s>d   ( sec . )
          60 um/mod s>d       ( sec min . )
          60 um/mod           ( sec min hour )  ;

  \ doc{
  \
  \ get-time  ( -- second minute hour )
  \
  \ Return the current time.
  \
  \ The system doesn't have an actual clock. The system frames
  \ counter is used instead. It is increased by the interrupts
  \ routine every 20th ms. The counter is a 24-bit value, so
  \ its maximum is $FFF ticks of 20 ms (5592 minutes, 93
  \ hours), then it starts again from zero.
  \
  \ }doc

: set-time  ( second minute hour -- )
  3600 um*  rot 60 * m+  rot m+  ( seconds )
  50. ud* frames!  ;

  \ doc{
  \
  \ set-time  ( second minute hour -- )
  \
  \ Set the current time. See `get-time`.
  \
  \ }doc

' reset-frames alias reset-time  ( -- )

  \ doc{
  \
  \ reset-time  ( -- )
  \
  \ Reset the current time to 00:00:00. See `get-time`.
  \
  \ }doc

( .time .system-time .date .system-date .time&date time&date )

  \ XXX TODO document

need get-time  need get-date

: .00  ( n -- )  s>d <# # # #> type  ;
: .0000  ( n -- )  s>d <# # # # # #> type  ;

: .time  ( second minute hour -- )
  .00 ':' emit .00 ':' emit .00  ;

: .system-time  ( -- )  get-time .time  ;

: .date  ( day month year -- )
  .0000 '-' emit .00 '-' emit .00  ;

: .system-date  ( -- )  get-date  .date  ;

: .time&date  ( second minute hour day month year -- )
  .date 'T' emit .time  ;

: time&date  ( -- second minute hour day month year )
  get-time get-date  ;

  \ doc{
  \
  \ time&date  ( -- second minute hour day month year )
  \
  \ Return the current time and date: second (0..59), minute
  \ (0..59), hour (0..23), day (1..31), month (1..12) and year
  \ (e.g., 2016).
  \
  \ See: `get-time`, `get-date`, `set-time`, `set-date`.
  \
  \ Standard: Forth-94 (FACILITY EXT), Forth-201 (FACILITY
  \ EXT).
  \
  \ }doc

2015-12-15

Rewrote under+ in Z80.

Added #spaces:

( #spaces )

need under+

: #spaces  ( ca len -- +n )
  0 rot rot  0 do  count bl = under+  loop  drop abs  ;
  \ Count spaces in a string.

  \ Credits:
  \ Code improved from:
  \ http://forth.sourceforge.net/mirror/comus/index.html

Started implementing a new more versatile method to manage the input buffer, in order to implement evaluate. The new method under development is being written from scratch but inspired by some features of eForth, hForth, Z88 CamelForth and other Forth systems.

2015-12-16

Added some unusual stack operations:

( nup drup dip )

code nup  ( x1 x2 -- x1 x1 x2 )
  E1 c,  D1 c,  D5 c,  C3 c, pushhlde ,
    \ pop hl
    \ pop de
    \ push de
    \ jp pushhlde
  end-code
  \ Also called `under`.

code drup  ( x1 x2 -- x1 x1 )
  D1 c,  E1 c,  E5 c,  E5 c,  jpnext
    \ pop de
    \ pop hl
    \ push hl
    \ push hl
    \ jp next
  end-code

code dip  ( x1 x2 -- x2 x2 )
  E1 c, D1 c, E5 c, E5 c,  jpnext
    \ pop hl
    \ pop de
    \ push hl
    \ push hl
    \ jp next
  end-code

First working version of the new input buffer method. Everything seems to work fine. evaluate has been implemented by the first time. Also nesting of different sources works, for example evaluating a string that loads a block that evaluates a string...

The first change is source doesn't return tib #tib # anymore but a string saved in a new double variable called input-buffer:

; ----------------------------------------------
  _two_variable_header input_buffer_,'INPUT-BUFFER'

; doc{
;
; input-buffer  ( -- a )
;
; A double-cell variable that holds the address and length of
; the current input buffer.
;
; }doc

  dw 0 ; len
  dw 0 ; address

; ----------------------------------------------
  _colon_header source_,'SOURCE'

; doc{
;
; source  ( -- ca len )
;
; ----
; : source  ( -- ca len )
;   blk @ ?dup if  block b/buf exit  then
;   input-buffer 2@  ;
; ----
;
; Standard: Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

  dw blk_,fetch_,question_dup_
  dw zero_branch_,source.other
  ; disk block
  dw block_,b_slash_buf_,exit_
source.other:
  dw input_buffer_,two_fetch_
  dw exit_

Next, some new words set the current source:

; ----------------------------------------------
  _colon_header set_source_,'SET-SOURCE'

; doc{
;
; set-source  ( ca len -- )
;
; Set the memory zone _ca len_ as the current source by pointing
; the input buffer to it.
;
; ----
; : set-source  ( ca len -- )
;   input-buffer 2!  >in off  ;
; ----
;
; }doc

  dw input_buffer_,two_store_
  dw to_in_,off_
  dw exit_

; ----------------------------------------------
  _colon_header string_to_source_,'STRING>SOURCE'

; doc{
;
; string>source  ( ca len -- )
;
; Set the string _ca len_ as the current source.
;
; ----
; : string>source  ( ca len -- )
;   blk off
;   [ ' source-id >body ] literal on
;   set-source  ;
; ----
; }doc

  dw blk_,off_
  _literal source_id_pfa
  dw on_
  dw set_source_
  dw exit_

; ----------------------------------------------
  _colon_header terminal_to_source_,'TERMINAL>SOURCE'

; doc{
;
; terminal>source  ( -- )
;
; Set the terminal as the current source.
;
; ----
; : terminal>source  ( -- )
;   blk off
;   [ ' source-id >body ] literal off
;   tib #tib @ set-source  ;
; ----
;
; }doc

  dw blk_,off_
  _literal source_id_pfa
  dw off_
  dw tib_,number_tib_,fetch_,set_source_
  dw exit_

; ----------------------------------------------
  _colon_header block_to_source_,'BLOCK>SOURCE'

; doc{
;
; block>source  ( +n -- )
;
; Set block _+n_ as the current source.
;
; ----
; : block>source  ( +n -- )
;   blk !  >in off  ;
; ----
;
; }doc

  dw blk_,store_,to_in_,off_
  dw exit_

Next, two words to nest and unnest sources:

; ----------------------------------------------
  _colon_header nest_source_,'NEST-SOURCE',compile_only

; doc{
;
; nest-source  ( R: -- source-sys )
;
; _source-sys_ describe the current source specification for
; later use by `unnest-source`.

; ----
; : nest-source  ( R: -- source-sys )
;   r>
;   source 2>r
;   source-id >r
;   >in @ >r
;   blk @ >r
;   #tib @ >@
;   >r  ;
; ----

; }doc

  dw from_r_ ; save the return address
  dw source_,two_to_r_
  dw source_id_,to_r_
  dw to_in_,fetch_,to_r_
  dw blk_,fetch_,to_r_
  dw number_tib_,fetch_,to_r_
  dw to_r_ ; restore the return address
  dw exit_

; ----------------------------------------------
  _colon_header unnest_source_,'UNNEST-SOURCE',compile_only

; doc{
;
; unnest-source  ( R: source-sys -- )
;
; Restore the source specification described by _source-sys_.

; ----
; : unnest-source  ( R: source-sys -- )
;   r>
;   r> #tib !
;   r> blk !
;   r> >in !
;   r> [ ' source-id >body ] literal !
;   2r> input-buffer 2!
;   >r  ;
; ----

; }doc

  dw from_r_ ; save the return address
  dw from_r_,number_tib_,store_
  dw from_r_,blk_,store_
  dw from_r_,to_in_,store_
  dw from_r_
  _literal source_id_pfa
  dw store_
  dw two_from_r_,input_buffer_,two_store_
  dw to_r_ ; restore the return address
  dw exit_

Finally, some words must be updated after the new method: quit, query, load, -->, (load). And evaluate can be defined:

: evaluate  ( i*x ca len -- j*x )
  nest-source string>source interpret unnest-source  ;

Factored out the part of (MODE32-EMIT) that prints User Defined Graphics to a new word called emit-udg. No speed penalty when executing emit, but executing emit-udg directly is faster because scroll and character checking are omitted.

2015-12-17

Finished implementing the standard do loop, adapting the code Spectrum Forth-83. A benchmark showed they are even faster than the original fig-Forth version:

( do-bench )

  \ 2015-12-17

need bench

32767 0 2constant range

: forth-83-do  ( -- )  bench  range do83  loop83  marque  ;

: forth-79-do  ( -- )  bench  range do  loop  marque  ;

: forth-83-i  ( -- )
  bench  range do83  i83 drop  loop83  marque  ;

: forth-79-i  ( -- )  bench  range do  i drop  loop  marque  ;

: forth-83-+loop  ( -- )
  bench  range do83  2 +loop83  marque  ;

: forth-79-+loop  ( -- )  bench  range do  2 +loop  marque  ;

: do-bench  ( -- )
  forth-83-do forth-79-do
  forth-83-i forth-79-i
  forth-83-+loop forth-79-+loop  ;

  \           Frames by 32767 iterations
  \           --------------------------
  \ Bench     fig-Forth  Forth-83
  \ --------  ---------  --------
  \ loop            143       109
  \ i               264       258
  \ +loop           108        97

  \ Note: 1 frame = 50th of second

Beside, the new code is only 83 bytes larger than the old one, and includes ?do.

The few loops already compiled in the kernel had to be adapted: the fig-Forth loops compiled the address of do at loop. The Forth-83 loops do the other way around.

The old code:

; ----------------------------------------------
  _code_alias_header unloop_,'UNLOOP',,two_r_drop_

; doc{
;
; unloop  ( -- ) ( R: loop-sys -- )
;
; Discard the loop-control parameters for the current nesting
; level. An `unloop` is required for each nesting level before
; the definition may be exited with `exit`. An ambiguous
; condition exists if the loop-control parameters are
; unavailable.
;
; In Solo Forth, the stack effect is:
;
; unloop  ( -- ) ( R: n1 n2 -- )
;
;   n1 = loop index
;   n2 = loop limit
;
; Standard: Forth-94.
;
; }doc

; ----------------------------------------------
  _code_header paren_do_,'(DO)'

  ; Credits:
  ; Code from CP/M fig-Forth 1.1g.

  exx                           ; 04t 01b
  pop de                        ; 10t 01b
  pop bc                        ; 10t 01b
  ld hl,(return_stack_pointer)  ; 20t 03b
  dec hl                        ; 06t 01b
  ld (hl),b                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),c                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),d                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),e                     ; 07t 01b
  ld (return_stack_pointer),hl  ; 16t 03b
  exx                           ; 04t 01b
                                ;116t 18b TOTAL
  _jp_next

; ----------------------------------------------
  _colon_header plus_loop_,'+LOOP',immediate+compile_only

; doc{
;
; +loop  ( n -- )
;
; Add the  signed  increment  _n_ to the  loop  index  using
; the convention for `+`,  and compare the total to the limit.
; Return execution to the corresponding `do` until the new index
; is equal to or greater than the limit (n>0),  or until the new
; index is less  than the limit (n<0).   Upon the exiting from
; the  loop, discard  the  loop control  parameters,  continuing
; execution ahead.   Index and  limit are  signed  integers in
; the  range {-32,768..32,767}.
;
; Standard: fig-Forth, Forth-79.
;
; }doc

  dw compile_,paren_plus_loop_
  dw backward_resolve_
  dw exit_

; ----------------------------------------------
  _colon_header loop_,'LOOP',immediate+compile_only

; XXX TODO documentation
; doc{
;
; loop  ( -- )
;
; Increment the `do-loop` index by one, terminating the loop  if
; the new index is equal to or greater than  the  limit.
;
; Standard: fig-Forth, Forth-79.
;
; }doc

  dw compile_,paren_loop_
  dw backward_resolve_
  dw exit_

; ----------------------------------------------
  _colon_header do_,'DO',immediate+compile_only

; doc{
;
; do  ( n1 n2 -- )
;
; Standard: fig-Forth, Forth-79.
;
; }doc

  dw compile_,paren_do_
  dw backward_mark_
  dw exit_

; ----------------------------------------------
  _code_alias_header i_,'I',,r_fetch_

; doc{
;
; i  ( -- x ) ( R: loop-sys -- loop-sys )
;
; Return a copy of the current (innermost) loop index.
;
; }doc

; ----------------------------------------------
  _code_header exhaust_,'EXHAUST'

; doc{
;
; exhaust  ( -- ) ( R: n1 n2 -- n2 n2 )
;
;   n1 = loop limit
;   n2 = loop index
;
; Force termination of a do-loop at the next opportunity by
; setting the loop limit equal to the current value of the
; index. The index itself remains unchanged, and execution
; proceeds normally until `loop` or `+loop` is encountered.
;
; Note: This is the equivalent of fig-Forth's `leave`.
;
; }doc

  ; XXX TODO rename to `leave-next`.

  ld hl,(return_stack_pointer)
  ld e,(hl)
  inc hl
  ld d,(hl)
  inc hl
  ld (hl),e
  inc hl
  ld (hl),d
  _jp_next

; ----------------------------------------------
  _code_header question_exhaust_,'?EXHAUST'

; doc{
;
; ?exhaust  ( f -- ) ( R: n1 n2 -- n1 n2 | n2 n2 )
;
; n1 = loop limit
; n2 = loop index
;
; If _f_ is not false, force termination of a do-loop at the
; next opportunity by setting the loop limit equal to the
; current value of the index. The index itself remains
; unchanged, and execution proceeds normally until `loop` or
; `+loop` is encountered.
;
; }doc

  ; XXX TODO rename to `?leave-next`.

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

; ----------------------------------------------
  _code_header paren_plus_loop_,'(+LOOP)'

  pop de
  jp paren_loop.step_in_de

; ----------------------------------------------
  _code_header paren_loop_,'(LOOP)'

; doc{
;
; (loop)  ( n -- )
;
; Add the  signed  increment  _n_ to the  loop  index  using
; the convention for `+`,  and compare the total to the limit.
; Return execution to the corresponding `do` until the new index
; is equal to or greater than the limit (n>0),  or until the new
; index is less  than the limit (n<0).   Upon the exiting from
; the  loop, discard  the  loop control  parameters,  continuing
; execution ahead.   Index and  limit are  signed  integers in
; the  range {-32,768..32,767}.
;
; }doc

  ; Credits:
  ;
  ; This code is from Abersoft Forth.  It's the same code used
  ; in `(+loop)` in fig-Forth 1.1g.  The author of Abersoft
  ; Forth used it to write `(loop)` and wrote `(+loop)` with a
  ; simple call to it, what saves code.

  ld de,0x0001
paren_loop.step_in_de:
  ld hl,(return_stack_pointer)
  ld a,(hl)
  add a,e
  ld (hl),a
  ld e,a
  inc hl
  ld a,(hl)
  adc a,d
  ld (hl),a
  inc hl ; (hl) = limit
  inc d
  dec d
  ld d,a ; de = new index
  jp m,paren_loop.negative_step

  ; increment>0
  ld a,e
  sub (hl)
  ld a,d
  inc hl
  sbc a,(hl)
  jp paren_loop.end

paren_loop.negative_step:
  ; increment<0
  ld a,(hl) ; limit-index
  sub e
  inc hl
  ld a,(hl)
  sbc a,d ; a<0?

paren_loop.end:
  jp m,branch_pfa ; loop again if a<0
  ; done, discard loop parameters
  inc hl
  ld (return_stack_pointer),hl
  ; skip branch offset
  inc bc
  inc bc
  _jp_next

The new code, not fully documented yet:

; ----------------------------------------------
  _code_header unloop_,'UNLOOP'

; doc{
;
; unloop  ( -- ) ( R: loop-sys -- )
;
; Discard the loop-control parameters for the current nesting
; level. An `unloop` is required for each nesting level before
; the definition may be exited with `exit`. An ambiguous
; condition exists if the loop-control parameters are
; unavailable.
;
; Standard: Forth-94 (CORE), Forth-2012 (CORE).
;
; }doc

  ld hl,(return_stack_pointer)
  ld de,cell*3
  add hl,de
  ld (return_stack_pointer),hl
  _jp_next

; ----------------------------------------------
  _code_header paren_question_do_,'(?DO)'

  ; Credits:
  ; Code based on Spectrum Forth-83.

  pop hl
  pop de
  and a
  sbc hl,de
  jp z,branch_pfa
  add hl,de  ; reverse the substraction
  jp paren_do.de_hl

; ----------------------------------------------
  _colon_header question_do_,'?DO',immediate+compile_only

; doc{
;
; ?do  ( n1 n2 -- )
;
; Standard: Forth-83, Forth-94, Forth-2012.
;
; ----
; : ?do  ( n1 n2 -- )
;   postpone (?do) >mark  ;  immediate compile-only
; ----
;
; }doc

  dw compile_,paren_question_do_
  dw forward_mark_
  dw exit_

  _colon_header do_,'DO',immediate+compile_only

; doc{
;
; do  ( n1 n2 -- )
;
; Standard: Forth-83, Forth-94, Forth-2012.
;
; ----
; : do  ( n1 n2 -- )
;   postpone (do) >mark  ;  immediate compile-only
; ----
;
; }doc

  dw compile_,paren_do_
  dw forward_mark_
  dw exit_

; ----------------------------------------------
  _colon_header loop_,'LOOP',immediate+compile_only

; doc{
;
; loop  ( -- )
;
; Increment the `do-loop` index by one, terminating the loop  if
; the new index is equal to or greater than  the  limit.
;
; Standard: Forth-83, Forth-94, Forth-2012.
;
; }doc

  dw compile_,paren_loop_
  dw forward_resolve_
  dw exit_

; ----------------------------------------------
  _colon_header plus_loop_,'+LOOP',immediate+compile_only

; doc{
;
; loop  ( -- )
;
; Increment the `do-loop` index by one, terminating the loop  if
; the new index is equal to or greater than  the  limit.
;
; Standard: Forth-83, Forth-94, Forth-2012.
;
; }doc

  dw compile_,paren_plus_loop_
  dw forward_resolve_
  dw exit_

; ----------------------------------------------
  _code_header paren_do_,'(DO)'

  ; Credits:
  ; Code from Spectrum Forth-83.

  pop hl                        ; 10t 01b ; init
  pop de                        ; 10t 01b ; limit
paren_do.de_hl:
  push hl                       ; 11t 01b
  ld hl,(return_stack_pointer)  ; 20t 03b
  dec hl                        ; 06t 01b
  ld (hl),d                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),e                     ; 07t 01b ; push limit on return stack
  inc bc
  inc bc                        ; skip branch address
  dec hl                        ; 06t 01b
  ld (hl),b                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),c                     ; 07t 01b ; push IP on return stack
  ex (sp),hl                    ; hl=init, (sp)=rp@
  and a
  sbc hl,de                     ; hl=init-limit
  ld a,h
  xor 0x80                      ; flip most significant bit
  ld d,a
  ld e,l
  pop hl                        ; hl=rp@
  dec hl
  ld (hl),d                     ; 07t 01b
  dec hl                        ; 06t 01b
  ld (hl),e                     ; 07t 01b ; push (init-limit) xor 0x8000 on return stack
  ld (return_stack_pointer),hl  ; 16t 03b
                                ;116t 18b TOTAL
  _jp_next


; ----------------------------------------------
  _code_header i_,'I'

  ld hl,(return_stack_pointer)

  ; Note: `j` and `k`jump here (body of `i` + 3), with updated
  ; hl.

  ld e,(hl)
  inc hl
  ld d,(hl) ; de= current index, wich is: (index-limit) xor 0x8000
  inc hl
  inc hl
  inc hl
  ld a,(hl)
  add a,e   ; read limit and add to index
  ld e,a
  inc hl
  ld a,(hl)
  adc a,d
  xor 0x80  ; flip most significant bit, getting true index value
  ld d,a
  push de ; result
  _jp_next

; ----------------------------------------------
  _code_header question_leave_,'?LEAVE'

; doc{
;
; ?leave  ( f -- ) ( R: loop-sys -- | loop-sys )
;
; }doc

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

; ----------------------------------------------
  _code_header leave_,'LEAVE'

  ld hl,(return_stack_pointer)
  inc hl
  inc hl
  ld c,(hl)
  inc hl
  ld b,(hl) ; bc = start address
  inc hl
  inc hl
  inc hl
  ld (return_stack_pointer),hl ; write updated return stack pointer (6 was added)
  dec bc
  dec bc ; point IP to forward branch address
  jp branch_pfa

; ----------------------------------------------
  _code_header paren_plus_loop_,'(+LOOP)'

  ld hl,(return_stack_pointer)
  ld e,(hl)
  inc hl
  ld d,(hl)   ; de = current index
  ex (sp),hl  ; hl = the increment value

  and a
  adc hl,de   ; add increment to index
  ; Note: `and a` and `adc` are used because `add` does not affect the po flag
  jp po,paren_plus_loop.continue

  ; If overflow, then boundary between limit-1 and limit is
  ; crossed, terminate loop.

  pop hl ; return stack pointer
  ld de,0x0005
  add hl,de
  ld (return_stack_pointer),hl
  ; Increment return stack pointer by 5 (1 increment already done)
  _jp_next

paren_plus_loop.continue:
  ld d,h
  ld e,l ; de = updated index
  pop hl ; return stack pointer

paren_loop.continue:

  ld (hl),d
  dec hl
  ld (hl),e  ; update the index on the return stack
  inc hl
  inc hl
  ld c,(hl)
  inc hl
  ld b,(hl) ; IP = start address, repeat loop
  _jp_next

; ----------------------------------------------
  _code_header paren_loop_,'(LOOP)'

  ld hl,(return_stack_pointer)
  ld e,(hl)
  inc hl
  ld d,(hl)   ; de = index value
  inc de      ; increment the index
  ld a,d
  xor 0x80
  or e        ; index=0x8000?
  jp nz,paren_loop.continue

  ; limit reached
  ld de,0x0005
  add hl,de ; increment RP by 5 (1 already done)
  ld (return_stack_pointer),hl
  _jp_next

In the library, j and k needed a little rewriting, because the loops use three cells of the return stack of the return stack, and i must calculate the actual index. i', j' and k' also need a little change.

Added support for semantic versioning. So far the development version was the build time in ISO format. Start of version 0.1.0.

2015-12-18

Fixed udg! and .s for the same reason: with a negative number the Forth-83 +loop doesn't work the same way than the fig-Forth version.

Benchmarked emit-udg: 0.97 the execution time of emit.

2015-12-19

Moved break-key? to the kernel, else it could not be used by located. Converted it to a deferred word with its default behaviour default-break-key?, so it can be deactivated or customized.

Moved set-order to the kernel, and added a copy to the root vocabulary, the minimum search order, after standard Forth.

Added find-name-in to the library, simply : find-name-in ( ca len wid -- nfa | 0 ) @ find-name-from ;

2015-12-20

Made set-order standard; so far parameter zero was not recognized.

; ----------------------------------------------
  _colon_header set_order_,'SET-ORDER'

; doc{
;
; set-order  ( -1 | 0 | widn..wid1 n -- )
;
; Set the search order to the word lists identified by
; _widn..wid1_. Subsequently, word list _wid1_ will be
; searched first, and word list _widn_ searched last. If _n_
; is zero, empty the search order. If _n_ is minus one, set
; the search order to the implementation-defined minimum
; search order.
;
; Standard: Forth-94 (SEARCH), Forth-2012 (SEARCH).
;
; }doc

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

if 0 ; XXX OLD -- 14 cells
  dw dup_
  _literal -1
  dw equals_
  dw zero_branch_,set_order.do_set_order
  dw drop_,only_,exit_
set_order.do_set_order
  dw question_dup_
  dw zero_branch_,minus_order_pfa
  dw paren_set_order_
  dw exit_
endif

  ; XXX NEW -- 12 cells
  dw question_dup_
  dw zero_branch_,minus_order_pfa ; 0
  dw dup_
  _literal -1
  dw equals_
  dw zero_branch_,paren_set_order_pfa ; <>-1
  dw drop_,only_ ; -1
  dw exit_

Changed the notation in the sources: _xt_, _xtp_ and _nt_ instead of _cfa_, _cfap_ and _nfa_.

First changes to implement DTC as an alternative. Adapted all header macros with conditional compilation.

Additional memory required by a DTC kernel (supposition)
Word type Number Bytes
code 145 -290
colon 248 +248
variable 42 +42
user variable 20 +20
constant 38 +38
deferred 12 -36
TOTAL +22

Wrote >defer ( xt -- a ), a common factor of defer! and defer@, which must be diferent for DCT and ITC.

Wrote the standard word find, just in case in could be useful for old programs.

: find  ( ca -- ca 0 | xt 1 | xt -1 )
  dup count find-name dup
  if  nip name>immediate? 1 or negate  then  ;

2015-12-21

Added more random number generators and wrote a benchmark to compare them all.

Benchmarked three versions of ud/mod (one shared by fig-Forth and Gforth, and two variants from Z88 CamelForth). The modified version from Z88 CamelForth is the winner, but the speed difference is almost unremarkable, only two bytes are saved, and the code needs -rot to be moved to the kernel. The fig-Forth version is kept, until -rot is needed by more words.

Added polarity:

( polarity )

  \ Credits:
  \
  \ Code adapted from Z88 CamelForth.

code polarity  ( n -- -1 | 0 | 1 )
  D1 c, 78 02 + c,  B0 03 + c,  CA c, ' false >body ,
    \ pop de
    \ ld a,d
    \ or e
    \ jp z,false_pfa

  CB c, 10 03 + c,  ED c, 62 c,
    \ rl d ; set carry if DE -ve
    \ sbc hl,hl ; HL=0 if DE +ve, or -1 if DE -ve

  78 05 + c,  F6 c, 01 c,  68 07 + c,  C3 c, pushhl ,
    \ ld a,l
    \ or 1
    \ ld l,a ; HL=1 or -1
    \ jp push_hl
  end-code

Removed some old unused words from the kernel, already commented out: the original fig-Forth ;s and its correspondent versions of exit and ?exit.

Added two new control structures:

( 0exit -exit )

code 0exit  ( f -- ) ( R: nest-sys | -- nest-sys | )

  E1 c,                 \ pop hl
  78 04 + c,            \ ld a,h
  B0 05 + c,            \ or l
  CA c, ' exit >body ,  \ jp z,exit_pfa
  jpnext                \ jp next

  end-code

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

code -exit  ( n -- ) ( R: nest-sys | -- nest-sys | )

  E1 c,                   \ pop hl
  CB c, 7C c,             \ bit 7,h ; negative?
  C2 c, ' exit >body ,    \ jp nz,exit_pfa
  jpnext                  \ jp next

  end-code

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

Added ?do to the words that required it. They were already marked. So far they used an if wrapper.

Fixed decode-special (part of the decode tool) after the Forth-83 version of do loop: now the branch address is after do or ?do; also added -branch to it, in case it is already defined during the compilation of decode-special.

2015-12-22

Substituted the d* with an alternative definition, from DX-Forth, which is slighty faster.

Added pixels, needed to finish the random number generator benchmark:

( pixels )

  \ Credits:
  \ Original code written by Juan Antonio Paz,
  \ published on Microhobby, issue 170 (1988-05), page 21:
  \ http://microhobby.org/numero170.htm
  \ http://microhobby.speccy.cz/mhf/170/MH170_21.jpg

need z80-asm

code pixels  ( -- u )

  \ Return the number of pixels set on the screen.

  exx
  4000 hl ldp#  l b ld  l c ld
  begin  \ byte
    08 d ld#
    begin  \ bit
      m rrc  cy if  bc incp  then  d dec
    z until
    hl incp  h a ld  58 cp#
  z until
  bc push
  exx
  jpnext
  end-code

Factored 0.r and 0d.r to print numbers without a trailing blank.

Wrote % and u% to calculate percentages:

( % u% )

: %  ( n1 n2 -- n3 )  100 swap */  ;
  \ _n1_ is percentage _n3_ of _n2_

: u%  ( u1 u2 -- u3 )  >r 100 um* r> um/mod nip  ;
  \ _u1_ is percentage _u3_ of _u2_

First version of the random number generator benchmark:

( rng-bench )

  \ Random number generator benchmark

need set-pixel  need bench{  need pixels  need u%

256 192 * constant #pixels
  \ number of pixels of the screen

defer rng  ( n -- 0..n-1 )

: pixels%.  ( u -- )  #pixels u% 0.r ." %"  ;
  \ Print _u_ pixels as a percentage.

: pixels.  ( u -- )
  dup u. ." pixels (" %pixels ." )"  ;
  \ Print _u_ as the number of pixels.

: rng-bench  ( ca len xt -- )
  ['] rng defer!  cls  bench{
  #pixels 0 do  256 rng 193 rng set-pixel  loop  frames@ 2>r
  pixels >r  ." Code: " type cr  r> pixels. cr  2r> bench.
  key drop  ;
  \ Do the RNG benchmark using _xt_;
  \ print title _ca len_ before the results.

Renamed and factored the words used to make benchmarks. Now they are more versatile.

( bench{ }bench }bench. bench. )

  \ Code adapted from:
  \ Forth Dimensions Volume XVII number 4 page 11.

  \ System-dependent timing routines.

need reset-frames  need frames@

: bench{  ( -- )  reset-frames  ;
  \ start timing

: }bench  ( -- d )  frames@ ;
  \ stop timing

: bench.  ( d -- )
  2dup d. ." frames (" 50 m/ nip . ." s) " cr  ;
  \ print the result _d_

: }bench.  ( -- )  }bench bench.  ;
  \ stop timing and print the result

Rewrote 3dup in Z80 and extracted it from the assembler.

( 3dup )

code 3dup  ( x1 x2 x3 -- x1 x2 x3 x1 x2 x3 )
  D9 c,
    \ exx
  C1 c,  D1 c,  E1 c,
    \ pop bc
    \ pop de
    \ pop hl
  E5 c,  D5 c,  C5 c,
    \ push hl
    \ push de
    \ push bc
  E5 c,  D5 c,  C5 c,
    \ push hl
    \ push de
    \ push bc
  D9 c,
    \ exx
  jpnext  end-code

exit  \ slow and smaller version:

: 3dup  ( x1 x2 x3 -- x1 x2 x3 x1 x2 x3 )  dup 2over rot  ;

Some new double-cell operators:

( dxor dor dand )

  \ Credits:
  \
  \ Code written by Everett F. Carter, published on Forth
  \ Dimensions (volume 16, number 2, page 17, 1994-08).

need -rot

[unneeded] dxor
?\  : dxor  ( d1 d2 -- d3 )  rot xor -rot xor swap  ;

[unneeded] dor
?\  : dor  ( d1 d2 -- d3 )  rot or -rot or swap  ;

[unneeded] dand
?\  : dand  ( d1 d2 -- d3 )  rot and -rot and swap  ;

New double benchmark for random number generators: the second benchmark counts the cycles until no pixel has changed.

( rng-benchmark )

  \ Random number generator benchmark

need set-pixel  need bench{  need pixels  need u%  need 3dup

256 192 * constant #pixels
  \ number of pixels of the screen

defer rng  ( n -- 0..n-1 )

: pixels%.  ( u -- )  #pixels u% 0.r ." %"  ;
  \ Print _u_ pixels as a percentage of the maximum
  \ number of pixels.

: .pixels  ( u -- )  dup u. ." pixels (" pixels%. ." )"  ;
  \ Print _u_ as the number of pixels.

: .origin  ( ca len -- )  ." Code: " type  ;

: fill-screen  ( -- )
  #pixels 0 do  256 rng 193 rng set-pixel  loop  ;
  \ Fill the screen with random pixels.

: (rng-benchmark)  ( -- d )  bench{ fill-screen }bench  ;
  \ Do the current RNG benchmark and return its result.

: .result  ( ca len d -- )
  2>r pixels >r  .origin cr  r> .pixels cr  2r> bench.  ;
  \ Calculate and print the result of the benchmark.
  \ _d_ is the time in frames; _ca len_ is the title.

: init-rng  ( xt -- )  ['] rng defer! page  ;

: wait  ( -- )  key drop  ;

-->

( rng-benchmark )

: rng-benchmark1  ( ca len xt -- )
  init-rng (rng-benchmark) .result wait  ;
  \ Do the RNG basic benchmark for _xt_ with title _ca len_:
  \ one single cycle of 49152 random pixels.

variable cycles

: .cycles  ( -- )
  cycles ?  s" cycles" cycles @ 1 = + type  ;
  \ Print the number of cycles.

: signal  ( -- )  cycles @ %111 and border  ;
  \ Change the border color according to the current count
  \ of cycles, just to show that the benchmark is running.

: rng-benchmark+  ( ca len xt -- )
  init-rng  -1 cycles !  bench{
  0 begin   signal  1 cycles +!
            fill-screen pixels tuck =  until  drop
  }bench  0 border  .result .cycles  wait  ;
  \ Do the RNG extended benchmark for _xt_ with title _ca len_:
  \ as many cycles of 49152 random pixels as required, until
  \ the number of pixels doesn't change.

: rng-benchmark  ( ca len xt -- )
  3dup rng-benchmark1 rng-benchmark+  ;
  \ Do the RNG basic and extended benchmarks for _xt_ with
  \ title _ca len_:

( every-random )

need rng-benchmark  need +thru

1 11 +thru

ace-rng-benchmark
dx-rng-benchmark
g-rng-benchmark
jer-rng-benchmark
jml-rng-benchmark
lb-rng-benchmark
lina-rng-benchmark
sf83-rng-benchmark
tt-rng-benchmark
v-rng-benchmark

2015-12-23

Fixed du/mod, by Wil Baden, published on Forth Dimensions (volume 19, number 6, page 34, 1998-04). One code line was missing:

: du/mod  ( ud1 ud2 -- ud3 ud4 )

  ?dup 0= if
    \ there is a leading zero "digit" in divisor
    >r  0 r@ um/mod  r> swap >r  um/mod  0 swap r>  exit
  then

  normalize-divisor dup >r rot rot 2>r
  1 swap lshift tum*
    \ normalize divisor and dividend

  dup  r@ = if   -1  else  2dup  r@ um/mod nip  then
    \ guess leading "digit" of quotient

  2r@  rot dup >r  tum*  t-
    \ multiply divisor by trial quot and substract from
    \ dividend

  dup 0< if  r> 1-  2r@  rot >r  0 t+
    \ if negative, decrement quot and add to dividend

    dup 0< if  r> 1-  2r@  rot >r  0 t+  then
    \ if still negative, do it one more time

  then

  r> 2r> 2drop  1 r>  rot >r  lshift tum/  r> 0  ;
    \ undo nurmalization of dividend to get remainder

  \ Double unsigned divide with remainder.  Given a dividend
  \ _ud1_ and a divisor _ud2_, return remainder _ud3_ and
  \ quotient _ud4_.

New version of synonym, without create does>:

( synonym )

need alias

: synonym  ( "newname" "oldname" -- )
  parse-name nextname ' dup >r alias
  r> >name dup immediate?     if  immediate     then
               compile-only?  if  compile-only  then  ;

exit

  \ XXX OLD -- first version
  \ XXX FIXME -- `' newname` would not work fine

  \ Credits:
  \
  \ This code of `synonym` is adapted from the example provided
  \ in the Forth-2012 documentation, and improved with
  \ `compile-only?`.

: synonym  ( "newname" "oldname" -- )
  create immediate
    hide ' , reveal
  does>  ( -- )
   ( pfa ) @ ( xt ) dup >name dup ( xt nt nt )
   compile-only? executing? and -14 ?throw
   immediate? executing? or
   if  execute  else  compile,  then  ;

Compiled all system variables into one block, and changed their prefix "sys-" to "os-", also for seed. Formerly their definitions where scattered and repeated, with conditional compilation.

( os-chars os-chans os-seed os-frames os-udg )

[unneeded] os-chars    ?\ 23606 constant os-chars
  \ address of the font (characters 32..127)

[unneeded] os-chans    ?\ 23631 constant os-chans
  \ address of the channels

[unneeded] os-seed     ?\ 23670 constant os-seed
  \ seed of the random number generator

[unneeded] os-frames   ?\ 23672 constant os-frames
  \ 24-bit counter of frames (1 frame = 20 ms)

[unneeded] os-udg      ?\ 23675 constant os-udg
  \ address of the User Defined Graphics (characters 128..255)

Factored save-mode from bye, and restore-mode from warm.

Worked on the tape support.

Fixed some bugs that made the DTC version crash: in macro _defer_header and words .version and noop. Also made execute and defer faster and smaller with DTC. Finally, the DTC version boots, but there are more bugs: it doesn't recognize words. But there's a suprise: The DTC version saves 35 bytes more than expected; the reasen is the deferred definitions were not included in the draft calculations.

Factored finish-code from ; to be reused in ;code.

2015-12-24

Fixed header,: so far it created a default code field specific for ITC. Modified : and create accordingly: now they create their own DTC code fields, with the new word code-field,. DTC works: The kernel can be assembled with ITC or DTC just changing a directive.

Did some benchmarks, already adapted from Forth Dimensions volume 17, number 4, page 11 (1995-11). The execution times of DTC are 80-90% of ITC.


  \ BYTE Magazine Benchmark
  \
  \ Times Frames (1 frame = 50th of second)
  \ ----- -----------------------------------
  \       ITC           DTC
  \       -----         -----
  \ 00010  6397          5216
  \ 00100 63970 (1.00)  52159 (0.81)

  \ Interface Age Benchmark
  \
  \ Times Frames (1 frame = 50th of second)
  \ ----- -----------------------------------
  \       ITC           DTC
  \       ------------  ------------
  \ 05000 80091 (1.00)  72445 (0.90)

  \ Vector Loop Benchmarks
  \
  \ Benchmark     Frames (1 frame = 50th of second)
  \ ---------     -----------------------------------
  \               ITC           DTC
  \               ------        -------------
  \ Vector noop    10919 (1.0)    9033 (0.82)
  \ Vector +       58650 (1.0)   47462 (0.80)
  \ Vector *      107770 (1.0)   91611 (0.85)
  \ Vector /      149002 (1.0)  127495 (0.85)
  \ Vector */     178854 (1.0)  154480 (0.86)

The used memory didn't change significantly: After executing cold, DTC has 32625 bytes free, and ITC has 32598. After compiling all three benchmarks, the amounts are 17255 and 17275 respectively.

2015-12-25

Started an alternative version of the assembler, with a modified syntax.

Finished an improved combined version of the RNG benchmark. It shows the execution time of one cycle, plus the number of cycles. An additional simpler variant is provided, to show the result of the first cycle:

( rng-benchmark )

  \ Random number generator benchmark

need set-pixel  need bench{  need pixels  need u%  need 3dup

256 192 * constant #pixels
  \ number of pixels of the screen

defer rng  ( n -- 0..n-1 )

: pixels%.  ( u -- )  #pixels u% 0.r ." %"  ;
  \ Print _u_ pixels as a percentage of the maximum
  \ number of pixels.

: .pixels  ( u -- )  dup u. ." pixels (" pixels%. ." )"  ;
  \ Print _u_ as the number of pixels.

: .title  ( ca len -- )  ." Code: " type  ;

variable cycles

defer .cycles  ( -- )

: (.cycles)  ( -- )
  cycles ?  s" cycles" cycles @ 1 = + type  ;
  \ Print the number of cycles.

: .time  ( d -- )  bench. ." per cycle" cr  ;

: .result  ( ca len d -- )
  2>r pixels >r  .title cr  r> .pixels cr
  2r> .time .cycles   ;
  \ Calculate and print the result of the benchmark.
  \ _d_ is the time in frames; _ca len_ is the title.

-->

( rng-benchmark )

: fill-screen  ( -- )
  #pixels 0 do  256 rng 193 rng set-pixel  loop  ;
  \ Fill the screen with random pixels.

: signal  ( -- )  cycles @ %111 and border  ;
  \ Change the border color according to the current count
  \ of cycles, just to show that the benchmark is running.

: (rng-benchmark)  ( -- d )
  1 cycles +!  signal  bench{ fill-screen }bench  ;
  \ Do the RNG benchmark of ony cycle and return its result.

: wait  ( -- )  key drop  ;

: finish  ( ca len d -- )  0 border  .result  wait  ;
  \ Finish the benchmark.
  \ _d_ is the time in frames; _ca len_ is the title.

: init  ( xt1 xt2 -- )
  ['] .cycles defer!  ['] rng defer!  page  -1 cycles !  ;

defer finish?  ( i*x -- j*x f )
  \ Finish the benchmark?

: new-pixels?  ( n1 -- n2 f )  pixels tuck =  ;
  \ Are there new pixels on the screen, comparing the previous
  \ count _n1_ with the new count _n2_?

' new-pixels? ' finish? defer!

-->

( rng-benchmark )

defer rng-benchmark  ( ca len xt -- )
  \ Do a RNG benchmark for the `random` word _xt_ with title
  \ _ca len_.

: rng-benchmark2  ( ca len xt -- )
  ['] (.cycles) init
  0 begin   (rng-benchmark) 2>r
            finish? dup 0= if  2rdrop  then
  until     drop 2r> finish  ;
  \ Do a double RNG benchmark for the `random` word _xt_ with
  \ title _ca len_: The time required to complete one cycle
  \ (49152 random pixels), plus the number of cycles required
  \ until the number of pixels doesn't change.

  \ The best `random` words need several cycles. In such cases
  \ it's useful a simpler test to show only the pixels set at
  \ the end of the first cycle:

: (.cycle)  ( -- )  ." First cycle only"  ;

: rng-benchmark1  ( ca len xt -- )
  ['] (.cycle) init
  (rng-benchmark) .result wait  ;
  \ Do a one-cycle RNG benchmark for `random` word _xt_ with
  \ title _ca len_: Only the time required to complete one
  \ cycle (49152 random pixels).

' rng-benchmark2 ' rng-benchmark defer!  \ default

2015-12-26

New version of alias, adapted to DTC:

( deferred? alias )

  \ XXX NEW -- DTC

: deferred?  ( xt -- wf )  c@ $C3 =  ;
  \ Is _xt_ a deferred word?
  \ The code of a deferred word starts with a Z80 jump ($C3)
  \ to the word it's associated.

: alias  ( xt "name" -- )
  dup deferred? if  1+ @  then  defer latest name> defer!  ;

  \ doc{
  \
  \ alias  ( xt "name" -- )
  \
  \ Create an alias _name_ that will execute _xt_.
  \
  \ If _xt_ is a deferred word, the alias will point to the
  \ word it's associated to.
  \
  \ }doc

( code? code-alias? defer-alias code-alias alias )

  \ XXX OLD -- ITC

: code?  ( xt -- wf )  dup >body swap @ =  ;
  \ Is _xt_ a word created by `code`?

: code-alias?  ( xt -- wf )  @ dup body> @ =  ;
  \ Is _xt_ a word created by `code-alias` or `code`?

: defer-alias  ( xt "name" -- )
  defer latest name> defer!  ;
  \ Create a deferred word _name_ that executes _xt_.

: code-alias  ( xt "name" -- )
  @ header reveal latest name> !  ;
  \ Create a code word that executes the code pointed by _xt_.

: alias  ( xt "name" -- )
  dup code-alias? if  code-alias exit  then  defer-alias  ;

  \ doc{
  \
  \ alias  ( xt "name" -- )
  \
  \ Create an alias _name_ that will execute _xt_.  If _xt_
  \ is a primitive word, the address hold in _name_'s own xt
  \ will be the same than the address hold in _xt_. Otherwise
  \ _name_ will be a deferred word executing _xt_.
  \
  \ }doc

Checked and fixed some scroll and clear screen words written in assembler.

Wrote >code. Some assembly words in the library calculated jump points using >body, but it doesn't work with DTC. For the same reason, an additional label with the "_code" prefix is created for code words in the kernel; it's the same address of the body, but the code is clearer. All this is temporary, until the ITC option will be removed from the kernel.

; ----------------------------------------------
if 0 ; ITC
  _code_alias_header to_code_,'>CODE',,two_plus_
else ; DTC
  _code_alias_header to_code_,'>CODE',immediate,noop_
endif

; doc{
;
; >code  ( xt -- a )
;
; Convert _xt_ of a primitive word to the actual address where
; the Z80 code is.
;
; This word is an abstraction: On ITC Solo Forth, `>code` is
; equivalent to `>body`; on DTC, it does nothing.
;
; }doc

Started a better implementation of wordlist and vocabulary, after eForth. The current implementation does not allow to associate wordlists to names.

2015-12-29

Added a second sqrt:

( sqrt )

  \ Integer square root by Newton's method

  \ Credits:
  \
  \ Adapted from Sinclair QL's Computer One Forth.

: sqrt  ( n1 -- n2 )
  dup 0< -24 ?throw  \ invalid numeric argument
  dup
  if  dup 2/  20 0
      do      2dup / + 2/
      loop    swap drop
  then  ;