Solo Forth development history in 2016-04
Description of the page content
Solo Forth development history in 2016-04.
2016-04-02
Factored default-header
from nextname-header
and added it to cold
in order to restore the default behaviour after a cold restart. This fixes a rare but possible bug, detected during the development of indexer
.
Started the indexer
tool.
Factored new-needed-word
from needed
. This change was needed for indexer
.
2016-04-03
Made need
and related words deferred. Factored new-needed-word
from needed
. These changes were needed for indexer
.
defer reneeded ( ca len -- )
\ doc{
\
\ reneeded ( ca len -- )
\
\ Load the first block whose header contains the string _ca
\ len_ (surrounded by spaces). If not found, throw an
\ exception -268 ("required, but not located").
\
\ This is a deferred word whose default behaviour is
\ `locate-reneeded`.
\
\ }doc
: locate-reneeded ( ca len -- ) located ?located load ;
\ doc{
\
\ locate-reneeded ( ca len -- )
\
\ Locate the first block whose header contains the string _ca
\ len_ (but surrounded by spaces), and load it. If not found,
\ throw an exception -268 ("required, but not located").
\
\ This is the default behaviour of the deferred word
\ `reneeded`.
\
\ }doc
defer reneed ( "name" -- )
\ doc{
\
\ reneed ( ca len -- )
\
\ Load the first block whose header contains "name" (but
\ surrounded by spaces).
\
\ This is a deferred word whose default behaviour is
\ `locate-reneed`.
\
\ }doc
: locate-reneed ( "name" -- )
parse-name save-string reneeded ;
\ doc{
\
\ locate-reneed ( ca len -- )
\
\ Locate the first block whose header contains "name" (but
\ surrounded by spaces), and load it. If not found, throw an
\ exception -268 ("required, but not located").
\
\ This is the default behaviour of the deferred word
\ `reneed`.
\
\ }doc
-->
( needed-word [needed] [unneeded] )
2variable needed-word
\ XXX TODO -- make `[needed]` and `[unneeded]` optional.
: [needed] ( "name" -- wf )
parse-name needed-word 2@ 2dup or
if compare 0= exit then 2drop 2drop true ; immediate
\ doc{
\
\ [needed] ( "name" -- wf )
\
\ Is "name" the needed word specified by the last execution
\ of `need` or `needed`?
\
\ }doc
: [unneeded] ( "name" -- wf )
postpone [needed] 0= ; immediate
\ doc{
\
\ [unneeded] ( "name" -- wf )
\
\ Is "name" different than the needed word specified by the
\ last execution of `need` or `needed`?
\
\ }doc
: new-needed-word ( ca len -- ca len )
-trailing -leading save-string 2dup needed-word 2! ;
-->
( needed need )
defer needed ( ca len -- )
\ doc{
\
\ needed ( ca len -- )
\
\ If the string _ca len_ is not the name of a word found in
\ the current search order, load the first block of the
\ library where "name" is included in the block header
\ (but surrounded by spaces).
\ If not found, throw an
\ exception -268 ("required, but not located").
\
\ This is a deferred word whose default behaviour is
\ `locate-needed`.
\
\ }doc
: locate-needed ( ca len -- )
needed-word 2@ 2>r new-needed-word 2dup undefined?
if locate-reneeded else 2drop then 2r> needed-word 2! ;
\ doc{
\
\ locate-needed ( ca len -- )
\
\ If the string _ca len_ is not the name of a word found in
\ the current search order, locate the first block of the
\ library where "name" is included in the block header (but
\ surrounded by spaces), and load it. If not found, throw an
\ exception -268 ("required, but not located").
\
\ This is the default behaviour of the deferred word
\ `needed`.
\
\ }doc
defer need ( "name" -- )
\ doc{
\
\ need ( "name" -- )
\
\ If "name" is not found in the current search order, locate
\ the first block of the library where "name" is included is
\ the block header (but surrounded by spaces), and load it.
\ If not found, throw an exception -268 ("required, but not
\ located").
\
\ This is a deferred word whose default behaviour is
\ `locate-need`.
\
\ }doc
: locate-need ( "name" -- ) parse-name needed ;
\ doc{
\
\ locate-need ( "name" -- )
\
\ If "name" is not found in the current search order, locate
\ the first block of the library where "name" is included is
\ the block header (but surrounded by spaces), and load it.
\ If not found, throw an exception -268 ("required, but not
\ located").
\
\ This is the default behaviour of the deferred word `need`.
\
\ }doc
: set-located-need ( -- )
['] locate-reneeded ['] reneeded defer!
['] locate-reneed ['] reneed defer!
['] locate-need ['] need defer!
['] locate-needed ['] needed defer! ;
\ doc{
\
\ set-located-need ( -- )
\
\ Set the default behaviour of `need`, `needed`, `reneed` and
\ `reneeded`: Use `locate` for searching the library.
\
\ The alternative, provided by the optional `indexer` tool,
\ is set by `set-indexed-need`.
\
\ }doc
set-located-need
Added string/
, useful for a check done by indexer
:
( string/ )
code string/ ( ca1 len1 len2 -- ca2 len2 )
\ ; T B
\ ; -- --
D9 c, C1 c, D1 c, E1 c,
\ exx ; save IP ; 04 01
\ pop bc ; len2 ; 10 01
\ pop de ; len1 ; 10 01
\ pop hl ; ca1 ; 10 01
19 c, A7 c, ED c, 42 c,
\ add hl,de ; 11 01
\ and a ; cy=0 ; 04 01
\ sbc hl,bc ; hl=ca2 ; 15 02
E5 c, C5 c,
\ push hl ; 11 01
\ push bc ; 11 01
D9 c, jpnext
\ exx ; restore IP ; 04 01
\ jp (ix) ; 08 02
\ ; -- --
\ ; 98 13 Total
end-code
\ doc{
\
\ string/ ( ca1 len1 len2 -- ca2 len2 )
\
\ Return the _len2_ ending characters of string _ca1 len1_.
\
\ }doc
exit
\ Slower version, 1 byte shorter
\
\ Credit: code from Galope.
: string/ ( ca1 len1 len2 -- ca2 len2 )
>r + r@ - r> ;
\ Return the _len2_ ending characters of string _ca1 len1_.
Splitted and renamed the error codes files, in order to make sure they are included in the right order.
Finished the indexer
tool:
( indexer )
only forth definitions
need loader need s= need alias need string/
need get-order need set-order
wordlist constant index-wordlist
\ Words of the blocks index.
: search-index ( ca len -- 0 | xt 1 | xt -1 )
index-wordlist search-wordlist ;
\ Search the index for word _ca len_.
: name-indexed? ( ca len -- f )
search-index 0<> dup if nip then ;
\ Is word _ca len_ in the index?
variable indexed-block
: (index-name) ( ca len -- )
2dup name-indexed? if 2drop exit then
nextname indexed-block @ loader ;
\ Add word _ca len_ to the blocks index, if not done before.
\ The current word list is supposed to be `index-wordlist`.
-->
( indexer )
wordlist dup constant indexer-wordlist set-current
\ Words to parse the block index lines.
: ( ( "ccc<space><paren><space|eof>" -- )
begin parse-name 2dup s" )" s= 0=
while (index-name) repeat 2drop ;
\ Parse and index the names until the next right paren name.
' ( alias .(
: \ ( "ccc<space><backslash><space|eof>" -- )
begin parse-name 2dup s" \" s= 0=
while (index-name) repeat 2drop ;
\ Parse and index the names until the next backslash name.
-->
( indexer )
forth-wordlist set-current
: index-reneeded ( ca len -- )
search-index 0= #-277 ?throw execute ;
\ doc{
\
\ index-reneeded ( ca len-- )
\
\ Search the index word list for word _ca len_. If found,
\ execute it, causing its associated block be loaded. If not
\ found, throw an exception -277 ("required, but not
\ indexed").
\
\ This is an alternative behaviour of the deferred word
\ `reneeded`.
\
\ }doc
: index-reneed ( "name" -- ) parse-name index-reneeded ;
\ doc{
\
\ index-reneed ( "name" -- )
\
\ Search the index word list for word "name". If found,
\ execute it, causing its associated block be loaded. If not
\ found, throw an exception -277 ("required, but not
\ indexed").
\
\ This is an alternative behaviour of the deferred word
\ `reneed`.
\
\ }doc
: index-needed ( ca len -- )
needed-word 2@ 2>r new-needed-word 2dup undefined?
if index-reneeded else 2drop then 2r> needed-word 2! ;
\ doc{
\
\ index-needed ( ca len -- )
\
\ If word _ca len_ is found in the current search order, do
\ nothing. Otherwise search the index word list for it. If
\ found, execute it, causing its associated block be loaded.
\ If not found, throw an exception -277 ("required, but not
\ indexed").
\
\ This is an alternative behaviour of the deferred word
\ `needed`.
\
\ }doc
: index-need ( "name" -- ) parse-name index-needed ;
\ doc{
\
\ index-need ( "name" -- )
\
\ If word "name" is found in the current search order, do
\ nothing. Otherwise search the index word list for it. If
\ found, execute it, causing its associated block be loaded.
\ If not found, throw an exception -277 ("required, but not
\ indexed").
\
\ This is an alternative behaviour of the deferred word
\ `need`.
\
\ }doc
: set-indexed-need ( -- )
['] index-reneeded ['] reneeded defer!
['] index-reneed ['] reneed defer!
['] index-need ['] need defer!
['] index-needed ['] needed defer! ;
\ doc{
\
\ set-indexed-need ( -- )
\
\ Set the alternative behaviour of `need`, `needed`, `reneed`
\ and `reneeded`: Use the library index created by `indexer`.
\ In fact `indexer` executes `set-indexed-need` after
\ creating the index.
\
\ The default behaviour can be restored by
\ `set-located-need`.
\
\ }doc
-->
( indexer )
: valid-block-header? ( ca len -- f )
-trailing dup 0= #-278 ?throw
2 string/ 2dup s" )" s= >r s" \" s= r> or ;
\ Is block header _ca len_ valid?
\ Valid block headers end with " )" or " /".
\ If it's empty, throw error -278 in order to quit
\ the indexing.
: index-block-header ( ca len -- )
2dup valid-block-header? if evaluate exit then 2drop ;
\ Index block header _ca len_, if it's valid.
: index-block ( +n -- )
dup indexed-block ! 0 swap line>string index-block-header ;
\ Index block _+n_.
: (indexer) ( -- )
last-locatable @ 1+ first-locatable @
?do i index-block loop ;
\ Create the blocks index.
: indexer ( -- )
get-current get-order
index-wordlist set-current indexer-wordlist 1 set-order
['] (indexer) catch dup #-278 <> swap ?throw
set-order set-current set-indexed-need ;
\ doc{
\
\ indexer ( -- )
\ Create the blocks index and activate it. The the current
\ word list and the current search order are preserved.
\
\ }doc
indexer
improves the default behaviour of need
, needed
, reneed
and reneeded
: It creates a word list from the names that are on the index (header) line of every searchable block, ignoring duplicates. These words will load the block they belong to. This way, after indexing all of the disk blocks only once, need
will search the word list and execute the word found, instead of searching all of the blocks every time.
At the time of writing, indexing the whole library (677 blocks) takes less than one minute and uses 3600 bytes of dictionary space.
Finished rewriting the file headers of the library modules that are based on other people's code, based on the documentation and credits of the original versions.
2016-04-05
Added cell/
to the library.
Fixed a recently introduced bug in jppushl
.
Added the pseudo-random number generator from IsForth.
Fixed the maximum y coordinate of (random-coords)
, part of rng-benchmark
.
Added d10*
to the library.
2016-04-07
Added bits
, a generic version of pixels
:
( bits )
\ Credit:
\
\ Based on a pixels counter 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 bits ( ca len -- u )
0 hl ldp# \ init bit count
exx \ save IP and count
de pop hl pop \ memory zone
begin cr .s
d a ld e or nz if cr .s
08 b ld# \ bits per byte
begin m rrc cy if exx hl incp exx then step
hl incp de decp \ next byte
2swap again then
\ Note: `2swap` is needed because `begin again` and `if
\ then` are not nested.
exx jppushhl end-code
\ doc{
\
\ bits ( ca len -- u )
\
\ Count the number of bits set in memory zone _ca len_.
\
\ }doc
2016-04-08
Added a new 8-bit crnd
, adapted from libzx Sebastian Mihai's libzx library. But the results of Joe Wingbermuehle's code are better.
( opt2-libzx-crnd )
\ Credit:
\ Original code from the ZX Spectrum libzx library,
\ written by Sebastian Mihai, 2016
\ 2016-04-09: Adapted to Solo Forth. Optimized and modified
\ the original code.
need z80-asm need os-seed need random-pix-benchmark
variable rom-pointer rom-pointer off os-seed off
code opt2-libzx-crnd ( -- b )
\ Gets an 8-bit random number.
\ It is computed using a combination of:
\ - the last returned random number
\ - a byte from ROM, in increasing order
\ - current values of various registers
\ - a flat incremented value
bc push af push
\ save Forth IP and the AF register
\ 1) advance ROM pointer
rom-pointer hl ftp hl incp
h a ld %00111111 and a h ld hl rom-pointer stp
\ ld hl,(romPointer)
\ inc hl
\ ld a, h
\ and %00111111
\ ld h, a ; H := H mod %00111111
\ ; essentially, HL := HL mod 16384, to make sure
\ ; HL points at a ROM location
\ ld (romPointer), hl ; save new location
\ 2) compute the random number
bc pop c rlc b rlc os-seed fta
\ pop bc ; BC := AF
\ rlc c
\ rlc b
\ ld a, (lastRandomNumber)
47 add# b add c add d add e add h add l add
\ add a, 47
\ add a, b ; current register values are "pretty random"
\ add a, c ; so add them in the mix
\ add a, d
\ add a, e
\ add a, h
\ add a, l
rom-pointer hl ldp# m add
\ ld hl, romPointer
\ add a, (hl) ; the contents of the ROM are "pretty random"
\ ; so add it in the mix
os-seed sta
\ ld (lastRandomNumber), a ; save this number
bc pop pusha jp end-code
: libzx-random-pix-benchmark ( -- )
rom-pointer off os-seed off s" libzx opt2 (8 bit)"
['] opt2-libzx-crnd random-pix-benchmark ;
libzx-random-pix-benchmark \ XXX TMP --
( opt1-libzx-crnd )
\ Credit:
\ Original code from the ZX Spectrum libzx library,
\ written by Sebastian Mihai, 2016
\ 2016-04-09: Adapted to Solo Forth. Optimized the original
\ code.
need z80-asm need os-seed need random-pix-benchmark
variable rom-pointer 3 rom-pointer ! 33 os-seed c!
code opt1-libzx-crnd ( -- b )
\ Gets an 8-bit random number.
\ It is computed using a combination of:
\ - the last returned random number
\ - a byte from ROM, in increasing order
\ - current values of various registers
\ - a flat incremented value
bc push af push
\ save Forth IP and the AF register
\ 1) advance ROM pointer
rom-pointer bc ftp 3 hl ldp# bc addp
\ ld bc,(romPointer)
\ ld hl,3
\ add hl,bc ; HL := ROM pointer advanced by 3
h a ld %00111111 and a h ld hl rom-pointer stp
\ ld a, h
\ and %00111111
\ ld h, a ; H := H mod %00111111
\ ; essentially, HL := HL mod 16384, to make sure
\ ; HL points at a ROM location
\ ld (romPointer), hl ; save new location
\ 2) compute the random number
bc pop c rlc b rlc os-seed fta
\ pop bc ; BC := AF
\ rlc c
\ rlc b
\ ld a, (lastRandomNumber)
47 add# b add c add d add e add h add l add
\ add a, 47
\ add a, b ; current register values are "pretty random"
\ add a, c ; so add them in the mix
\ add a, d
\ add a, e
\ add a, h
\ add a, l
rom-pointer hl ldp# m add
\ ld hl, romPointer
\ add a, (hl) ; the contents of the ROM are "pretty random"
\ ; so add it in the mix
os-seed sta
\ ld (lastRandomNumber), a ; save this number
bc pop pusha jp end-code
: libzx-random-pix-benchmark ( -- )
3 rom-pointer ! 33 os-seed c!
s" libzx opt1 (8 bit)"
['] opt1-libzx-crnd random-pix-benchmark ;
( libzx-crnd )
\ Credit:
\ Original code from the ZX Spectrum libzx library,
\ written by Sebastian Mihai, 2016
\ 2016-04-09: Adapted to Solo Forth.
need z80-asm need os-seed need random-pix-benchmark
variable rom-pointer 3 rom-pointer ! 33 os-seed c!
code libzx-crnd ( -- b )
\ Gets an 8-bit random number.
\ It is computed using a combination of:
\ - the last returned random number
\ - a byte from ROM, in increasing order
\ - current values of various registers
\ - a flat incremented value
bc push af push
\ save Forth IP and the AF register
\ 1) advance ROM pointer
rom-pointer hl ldp#
m c ld hl incp m b ld 3 hl ldp# bc addp
\ XXX TODO -- simpler
\ XXX REMARK -- original code is not optimized
\ ld hl, romPointer
\ ld c, (hl)
\ inc hl
\ ld b, (hl) ; BC := word (romPointer)
\ ld hl, 3
\ add hl, bc ; HL := ROM pointer advanced by 3
h a ld %00111111 and a h ld hl rom-pointer stp
\ ld a, h
\ and %00111111
\ ld h, a ; H := H mod %00111111
\ ; essentially, HL := HL mod 16384, to make sure
\ ; HL points at a ROM location
\ ld (romPointer), hl ; save new location
\ 2) compute the random number
bc pop c rlc b rlc os-seed fta
\ pop bc ; BC := AF
\ rlc c
\ rlc b
\ ld a, (lastRandomNumber)
47 add# b add c add d add e add h add l add
\ add a, 47
\ add a, b ; current register values are "pretty random"
\ add a, c ; so add them in the mix
\ add a, d
\ add a, e
\ add a, h
\ add a, l
rom-pointer hl ldp# m add
\ ld hl, romPointer
\ add a, (hl) ; the contents of the ROM are "pretty random"
\ ; so add it in the mix
os-seed ldp# m a ld
\ ld hl, lastRandomNumber
\ ld (hl), a ; save this number
\ XXX REMARK -- original code is not optimized
bc pop 0 h ld# a l ld jppushhl end-code
: libzx-random-pix-benchmark ( -- )
3 rom-pointer ! 33 os-seed c!
s" libzx (8 bit)" ['] libzx-crnd random-pix-benchmark ;
2016-04-09
Released version 0.3.0.
Splitted the assembler library module into three files: z80-asm assembler, z80-asm-comma assembler and common tools.
Fixed the title of two 8-bit pseudo-random number generator benchmarks.
Fixed the name of the base-execute
library module.
Released version 0.3.1.
Fixed, improved and finished the arguments
implementation of locals.
Released version 0.3.2.
2016-04-10
Fixed the tape support. There were two bugs: 1) The parameter passed to the ROM routine in the HL register was wrong. 2) The status of the display was not saved and restored, and the system crashed at the end because of the message "Start tape, then press any key" printed by the ROM routine (which will be avoided in the next version).
( write-tape-file read-tape-file )
17 constant /tape-header
\ bytes per tape header
create tape-header /tape-header 2 * allot
\ save (=new) and load (=old) headers
10 constant /tape-filename \ filename max length
: tape-filetype ( -- ca ) tape-header ;
: tape-filename ( -- ca ) tape-header 1+ ;
: tape-length ( -- a ) tape-header 11 + ;
: tape-start ( -- a ) tape-header 13 + ;
3 tape-filetype c! \ "code" filetype by default
-->
( write-tape-file read-tape-file )
code (tape) ( n -- )
E1 c, C5 c, 78 05 + c,
\ pop hl
\ push bc ; save Forth IP
\ ld a,l ; 0=save, 1=load, 2=verify
DD c, 21 c, tape-header , 2A c, tape-start ,
\ ld ix,tape_header
\ ld hl,(tape_start)
32 c, 5C74 , CD c, 075A ,
\ ld (5C74),A ; T_ADDR system variable
\ call 075A ; SA_ALL ROM routine
C1 c, DD c, 21 c, next ,
\ pop bc ; restore Forth IP
\ ld ix,next ; restore the address of Forth next
jpnext end-code
\ doc{
\
\ (tape) ( n -- )
\
\ Tape primitive that uses the data stored at `tape-header`
\ to save (n=0), load (n=1) or verify (n=2) a tape file.
\
\ }doc
-->
( write-tape-file read-tape-file )
: -tape-filename ( -- ) tape-filename /tape-filename blank ;
\ doc{
\
\ -tape-filename ( -- )
\
\ Blank the filename of the tape header.
\
\ }doc
: set-tape-filename ( ca len -- )
-tape-filename /tape-filename min
tape-filename swap cmove ;
\ doc{
\
\ set-tape-filename ( ca len -- )
\
\ Store a filename into the tape header.
\
\ }doc
: any-tape-filename ( -- ) 255 tape-filename c! ;
\ Configure the tape header to load any filename,
\ by replacing the first char of the filename with 255.
-->
( write-tape-file read-tape-file )
need save-display need restore-display
: transfer-tape-file ( ca len n -- )
save-display
>r tape-length ! tape-start ! r> (tape)
restore-display ;
\ doc{
\
\ : transfer-tape-file ( ca len n -- )
\
\ Read or write the tape file whose filename and filetype
\ have been already set in `tape-header`. _n_ is 0 for
\ writing or 1 for reading; _ca len_ is the memory zone.
\
\ }doc
: read-tape-file ( ca1 len1 ca2 len2 -- )
dup if set-tape-filename
else 2drop any-tape-filename
then 1 transfer-tape-file ;
\ doc{
\
\ read-tape-file ( ca1 len1 ca2 len2 -- )
\
\ Read a tape file _ca2 len2_ (_len2_ is zero if filename is
\ unspecified) into a memory region _ca1 len1_.
\
\ _ca1_ is zero if the address must be taken from the file
\ header instead, which is the address the file was saved
\ from. _len1_ is zero if is unspecified.
\
\ }doc
: write-tape-file ( ca1 len1 ca2 len2 -- )
set-tape-filename 0 transfer-tape-file ;
\ doc{
\
\ write-tape-file ( ca1 len1 ca2 len2 -- )
\
\ Write a memory region _ca1 len1_ into a tape file _ca2
\ len2_.
\
\ }doc
New words were needed for the tape support. They are defined in its own library module, graphics.display.fsb:
( nonfull-display full-display save-display restore-display )
: nonfull-display ( -- ) 2 23659 c! ;
\ doc{
\
\ nonfull-display ( -- )
\
\ Set the nonfull screen mode: 2 lines in the lower screen
\ and 22 lines in the upper main screen, which is the default
\ configuration in BASIC.
\
\ }doc
\ Note: 23659 is the system variable DF_SZ (lines in the
\ lower screen).
: full-display ( -- ) 0 23659 c! ;
\ doc{
\
\ full-display ( -- )
\
\ Set the full screen mode: no lines in the lower screen,
\ thus 24 lines in the upper main screen, which is the
\ default configuration in Solo Forth.
\
\ }doc
\ Note: 23659 is the system variable DF_SZ (lines in the
\ lower screen).
: save-display ( -- ) ( R: -- col row )
r> xy 2>r >r save-mode nonfull-display ;
\ doc{
\
\ save-display ( -- ) ( R: -- col row )
\
\ Save the status of the display. This word is intended to
\ be used before calling a ROM routine that uses the display.
\ The display can be restored to its previous status with
\ `restore-display`.
\
\ }doc
: restore-display ( -- ) ( R: col row -- )
display full-display restore-mode r> 2r> at-xy >r ;
\ doc{
\
\ restore-display ( -- ) ( R: col row -- )
\
\ Restore the status of the display, saved by `save-display`.
\ Intended to be used after calling a ROM routine that uses
\ the display.
\
\ }doc
Released version 0.4.0.
Improved the tape support: The message "Start tape, then press any key" does not appear anymore. Instead, writing to tape starts inmmediately. New version of the code:
( write-tape-file read-tape-file )
17 constant /tape-header
\ bytes per tape header
create tape-header /tape-header 2 * allot
\ save (=new) and load (=old) headers
10 constant /tape-filename \ filename max length
: tape-filetype ( -- ca ) tape-header ;
: tape-filename ( -- ca ) tape-header 1+ ;
: tape-length ( -- a ) tape-header 11 + ;
: tape-start ( -- a ) tape-header 13 + ;
3 tape-filetype c! \ "code" filetype by default
-->
( write-tape-file read-tape-file )
: -tape-filename ( -- ) tape-filename /tape-filename blank ;
\ doc{
\
\ -tape-filename ( -- )
\
\ Blank the filename of the tape header.
\
\ }doc
: set-tape-filename ( ca len -- )
-tape-filename /tape-filename min
tape-filename swap cmove ;
\ doc{
\
\ set-tape-filename ( ca len -- )
\
\ Store a filename into the tape header.
\
\ }doc
: any-tape-filename ( -- ) 255 tape-filename c! ;
\ doc{
\
\ any-tape-filename ( -- )
\
\ Configure the tape header to load any filename,
\ by replacing the first char of the filename with 255.
\
\ }doc
: set-tape-memory ( ca len -- )
tape-length ! tape-start ! ;
\ doc{
\
\ set-tape-memory ( ca len -- )
\
\ Configure the tape header with the memomy zone _ca len_ (to
\ be read or written).
\
\ }doc
-->
( write-tape-file read-tape-file )
code (read-tape-file) ( -- )
C5 c, DD c, 21 c, tape-header , 2A c, tape-start ,
\ push bc ; save Forth IP
\ ld ix,tape_header
\ ld hl,(tape_start)
3E c, 01 c, 32 c, 5C74 , CD c, 075A ,
\ ld a,1 ; 1=load
\ ld (5C74),A ; T_ADDR system variable
\ call 075A ; SA_ALL ROM routine
C1 c, DD c, 21 c, next ,
\ pop bc ; restore Forth IP
\ ld ix,next ; restore the address of Forth `next`
jpnext end-code
: read-tape-file ( ca1 len1 ca2 len2 -- )
dup if set-tape-filename else 2drop any-tape-filename
then set-tape-memory (read-tape-file) ;
\ doc{
\
\ read-tape-file ( ca1 len1 ca2 len2 -- )
\
\ Read a tape file _ca2 len2_ (_len2_ is zero if filename is
\ unspecified) into a memory region _ca1 len1_.
\
\ _ca1_ is zero if the address must be taken from the file
\ header instead, which is the address the file was saved
\ from. _len1_ is zero if is unspecified.
\
\ }doc
-->
( write-tape-file read-tape-file )
code (write-tape-file) ( -- )
C5 c, DD c, 21 c, tape-header ,
\ push bc ; save Forth IP
\ ld ix,tape_header
A8 07 + c, 32 c, 5C74 ,
\ xor a ; 0=save
\ ld (5C74),a ; T_ADDR system variable
21 c, here 0A + , E5 c,
\ ld hl,return_from_ROM
\ push hl ; simulate a call
2A c, tape-start , E5 c,
\ ld hl,(tape_start) ; start of data
\ push hl ; needed by entry point $0984,
\ ; because it's done at the main entry point $0970
C3 c, 0984 ,
\ jp $0984 ; alternative entry point to SA_ALL, after the save message
\ ; note: `jp` is used, but it works as a `call`,
\ ; because the return address has been pushed
\ return_from_ROM:
C1 c, DD c, 21 c, next ,
\ pop bc ; restore Forth IP
\ ld ix,next ; restore address of Forth `next`
jpnext end-code
: write-tape-file ( ca1 len1 ca2 len2 -- )
set-tape-filename set-tape-memory (write-tape-file) ;
\ doc{
\
\ write-tape-file ( ca1 len1 ca2 len2 -- )
\
\ Write a memory region _ca1 len1_ into a tape file _ca2
\ len2_.
\
\ }doc
2016-04-11
Released version 0.4.1.
Moved macro
to its own module, since its definition is identical in both versions of the assembler, and can be useful without an assembler:
( macro )
need get-order
get-order get-current
only forth definitions also assembler
: macro ( "name" -- ) : asm ;
: endm ( -- ) end-asm postpone ; ; immediate
set-current set-order
Started plusd-in
, plusd-out
plusd-in,
, plusd-out,
.
Removed a wrong and unnecessary Z80 instruction from end-calc
.
Checked the implementation of floating point based on the ROM calculator.
2016-04-12
Started f.
and f,
.
2016-04-13
Finished f.
and f,
.
Wrote .fs
, f*
, -frot
.
Fixed fover
and frot
.
Made calc end-calc
independent from the assemblers and moved it to the floating point module. Formerly it was defined in both assemblers.
Fixed execute-hl
, which was not adapted from ITC yet, and did not save the Forth IP. It will be made independent from the assemblers too. The version for the second assembler has been renamed with a trailing comma.
macro execute-hl ( -- )
0000 bc stp |mark \ save the Forth IP
0000 bc ldp# |mark \ point IP to phony_compiled_word
jphl \ execute the xt in HL
>resolve \ phony_compiled_word
here cell+ , \ point to the phony xt following
0000 bc ldp# |resolve \ restore the Forth IP
endm
\ Compile an `execute` with the xt hold in HL.
The fixing of execute-hl
made all floating point logical operators work.
The version numbering has been modified, converting the optional pre-release field release candidate to simple pre-release.
Factored the most common ROM calculator commands into assembler macros, in order to make the floating point more legible and easier to maintain.
Wrote first working versions of fmax
and fmin
.
Wrote a program to patch the source of the BASIC loader with the current load and entry addresses compiled in the kernel. So far those addresses were hardcoded in the source of the loader. Now the loader is automatically updated by Makefile when needed.
Wrote f0>
, which was missing.
Fixed the problem with non-integer floating point numbers: the reason was the alternate registers set is used by the calculator, and the Forth IP was saved and restored with exx
in calc
and end-calc
. Now the stack is used instead.
2016-04-14
Restored the file exceptions.fsb from the repository. It was removed from version 0.3.0+20160409 by mistake. Updated the headers and the documentation. Renamed (.throw)
to .throw-message
and msg-scr
to error-messages-block
. Fixed error>ordinal
(codes below -255 were displaced by one).
Fixed the file error_codes.0256.system.fsb, which still contained also the standard errors.
Documented some sound words.
Renamed many library files. The goal is to build a logical hierarchy, easy to search and extend, without using subdirectories, in order to make it easy to select and link only the files needed by the application. The task is not finished: Math, system, media, DOS and other library files still have temporary names.
2016-04-15
Benchmarked the current primitive m+
and an alternative high level definition, which results 1.44 slower but saves 4 bytes.
Added j
and k
, which were missing since before version 0.1.0, after converting the fig-Forth do
to Forth-83:
; ----------------------------------------------
_code_header i_,'I'
; Credit:
; Code adapted from Spectrum Forth-83.
ld hl,(return_stack_pointer)
i.hl: ; entry point for `j` and `k`
ld e,(hl)
inc hl
ld d,(hl) ; de= current index, wich is: (index-limit) xor $8000
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 $80 ; flip most significant bit, getting true index value
ld d,a
push de ; result
_jp_next
; ----------------------------------------------
_code_header j_,'J'
ld hl,(return_stack_pointer)
ld de,3*cell
add hl,de
jp i.hl
; ----------------------------------------------
_code_header k_,'K'
ld hl,(return_stack_pointer)
ld de,6*cell
add hl,de
jp i.hl
But they better be in the library. A constant in the kernel is enough to move them:
; ----------------------------------------------
_constant_header paren_i,'(I)'
; doc{
;
; (i) ( -- a )
;
; A constant that holds the address of the machine code entry
; point of `i` that calculates the `do` index from the address
; pointed by the HL register. Used by `j` and `k`.
;
; }doc
dw i.hl
The new versions in the library, documented:
( j )
code j ( -- n|u ) ( R: do-sys1 do-sys2 -- do-sys1 do-sys2 )
2A c, rp , 11 c, 3 cells , 19 c, C3 c, (i) ,
\ ld hl,(return_stack_pointer)
\ ld de,3*cell
\ add hl,de
\ jp i.hl
end-code
\ doc{
\
\ j ( -- n|u ) ( R: loop-sys1 loop-sys2 -- loop-sys1 loop-sys2 )
\
\ Return a copy _n|u_ of the next-outer loop index.
\
\ Origin: Forth-83 (Required word set), Forth-94 (CORE),
\ Forth-2012 (CORE). Note: `k` was also in Forth-79
\ (Reference word set), but returned a signed number.
\
\ }doc
( k )
code k ( -- n|u )
( R: loop-sys1..loop-sys3 -- loop-sys1..loop-sys3 )
2A c, rp , 11 c, 6 cells , 19 c, C3 c, (i) ,
\ ld hl,(return_stack_pointer)
\ ld de,6*cell
\ add hl,de
\ jp i.hl
end-code
\ doc{
\
\ k ( -- n|u ) ( R: loop-sys1..loop-sys3 -- loop-sys1..loop-sys3 )
\
\ Return a copy _n|u_ of the second outer loop index.
\
\ Origin: Forth-83 (Controlled reference words). Note: `k`
\ was also in Forth-79 (Reference word set), but returned a
\ signed number.
\
\ }doc
associative-list
, based on code by Wil Baden (published on Forth Dimensions, volume 17, number 4, page 36, 1995-11), had been adapted and included in 2015-11, but now it has been improved with different types of data. A strange bug was discovered during the chanegs, related to (;code)
. Using the new variable last
, which is updated by header,
, instead of using fig-Forth's latest
, which fetchs current
, solves the problem.
Fixed decode-compile
(from the decode
utility), which had not been converted from ITC to DTC.
Fixed dump
(the loop printed one byte more than requested). Improved ascii-type
(now also characters above 127 are printed as dots, not masked).
Removed the old user variable #emit
.
After fixing the problem of (;code)
, it was possible to finish associative-list
:
( associative-list item? item create-entry )
: associative-list ( "name" -- ) wordlist constant ;
\ Create a new associative list "name".
: item? ( ca len wid -- false | xt true )
search-wordlist 0<> ;
\ Is _ca len_ an item of associative list _wid_?
\ If so return its _xt_ and _true_, else return _false_.
: item ( ca len wid -- i*x )
item? 0= #-13 ?throw execute ;
\ If _ca len_ is an item of associative list _wid_, return
\ its value _i*x_; else throw exception -13, "undefined
\ word".
: create-entry ( i*x wid xt "name" -- )
get-current >r swap set-current
create execute
r> set-current ;
\ Create an entry "name" in associative list _wid_,
\ using _xt_ to store its value _i*x_.
-->
( entry char-entry double-entry string-entry )
: entry ( x wid "name" -- )
['] , create-entry does> ( -- x ) ( pfa ) @ ;
\ Create a cell entry "name" in associative list
\ _wid_, with value _x_.
: char-entry ( c wid "name" -- )
['] c, create-entry does> ( -- c ) ( pfa ) c@ ;
\ Create a character entry "name" in associative list
\ _wid_, with value _c_.
: double-entry ( dx wid "name" -- )
['] 2, create-entry does> ( -- dx ) ( pfa ) 2@ ;
\ Create a double-cell entry "name" in associative list
\ _wid_, with value _dx_.
: string-entry ( ca len wid "name" -- )
['] s, create-entry does> ( -- ca len ) ( pfa ) count ;
\ Create a string entry "name" in associative list
\ _wid_, with value _ca len_.
( items )
need alias need wordlist-words
' wordlist-words alias items ( wid -- )
\ List items of associative list _wid_.
( associative-list-demo )
need associative-list need items
associative-list stuff
1887 stuff entry year
char E stuff char-entry letter
s" Saluton" stuff string-entry hello
314159. stuff double-entry pi
cr .( Keys:) cr stuff items cr
cr .( Values: ) cr
s" year" stuff item . cr
s" letter" stuff item emit cr
s" hello" stuff item type cr
s" pi" stuff item d. cr
2016-04-16
Fixed two minor problems in Makefile and Makefile.pasmo.
Revised for
and dfor
.
Finished times
:
( times )
variable times-xt \ the _xt_ executed by `times`
: times ( i*x n -- j*x )
rp@ @ dup cell+ rp@ ! @ times-xt !
0 ?do times-xt perform loop ; compile-only
\ doc{
\
\ times ( i*x n -- j*x )
\
\ Repeat the next compiled instruction _n_ times. If _n_ is
\ zero, continue executing the following instruction.
\
\ `times` is useful to implement complicated math operations,
\ like shifts, multiply, divide and square root, from
\ appropriate math step instructions. It is also useful in
\ repeating auto-indexing memory instructions.
\
\ Usage example:
\
\ ----
\ : blink ( -- ) 7 0 do i border loop 0 border ;
\ : blinking ( -- ) 100 times blink ." Done" cr ;
\ ----
\
\ }doc
Improved Makefile in order to patch the +3DOS BASIC loader after the method used for G+DOS.
2016-04-17
Added -!
:
( -! )
code -! ( n a -- )
E1 c, D1 c,
\ pop hl ; address
\ pop de ; number
7E c, 90 03 + c, 70 07 + c, 23 c,
\ ld a,(hl)
\ sub a,e
\ ld (hl),a
\ inc hl
7E c, 98 02 + c, 70 07 + c,
\ ld a,(hl)
\ sbc a,d
\ ld (hl),a
jpnext end-code
Fixed >name
. So far it never returned when the search failed, because the ending condition was wrong. Beside, it has been rewritten for clarity, and it's 2 bytes shorter:
_code_header to_name_,'>NAME'
; doc{
;
; >name ( xt -- nt | 0 )
;
; Try to find the name token _nt_ of the word represented by
; execution token _xt_. Return 0 if it fails.
;
; This word searches all headers, from the oldest to the newest,
; for the first one whose _xtp_ (xt pointer) contains _xt_.
;
; Origin: Gforth.
;
; }doc
; XXX TODO -- Search backwards from `last @`?
call bank.system ; page in the memory bank
pop de ; xt
push bc ; save Forth IP
; save the names pointer for later:
ld a,(names_pointer)
ld (to_name.names_pointer_low_byte),a
ld a,(names_pointer+1)
ld (to_name.names_pointer_high_byte),a
ld b,0
ld hl, names_bank_address
to_name.begin:
; compare HL with the names pointer
; if they are equals, there are no more names to check
to_name.names_pointer_high_byte equ $+1
ld a,0
cp h
jr nz,to_name.check_xt
to_name.names_pointer_low_byte equ $+1
ld a,0
sub l
jr nz,to_name.check_xt
; no words left
; a=0
ld h,a
ld l,a ; hl = 0
jr to_name.end
to_name.check_xt
; Check if the xt pointed by HL is the one searched for
; hl = xtp
ld a,(hl) ; low byte of xt
inc hl
cp e ; equal?
jr nz,to_name.next ; not equal
ld a,(hl) ; high byte of xt
cp d ; equal?
jr nz,to_name.next ; not equal
; xt found
; hl = xtp+1
inc hl ; lfa
inc hl
inc hl ; nfa
to_name.end:
; hl = nt or zero
call bank.default ; page in the default memory bank
pop bc ; restore Forth IP
_jp_pushhl
to_name.next:
; hl = xtp+1
inc hl ; lfa
inc hl
inc hl ; nfa
ld a,(hl) ; name field byte length
and word_length_mask ; name length
ld c,a ; name length
inc c ; plus the length byte
add hl,bc ; xtp of the next word
jr to_name.begin
Moved !s
and c!s
to the library (src/lib/memory.bank.fsb). They are not needed in the kernel:
( !s c!s )
: !s ( x a -- ) system-bank ! default-bank ;
\ doc{
\
\ !s ( x a -- )
\
\ Store _x_ into address _a_ of the system bank.
\
\ }doc
: c!s ( c ca -- ) system-bank c! default-bank ;
\ doc{
\
\ c!s ( c ca -- )
\
\ Store _c_ into address _ca_ of the system bank.
\
\ }doc
Added (source-id)
, which returns the address of the value returned by source-id
. This makes the code clearer, makes it simpler to manipulate source-id
and saves one byte.
Moved recurse
to the library and updated the library modules that need it.
Improved alias
. Now aliases have the same execution token of the original word:
( alias )
need name>> need !s
: alias ( xt "name" -- ) header reveal latest name>> !s ;
name>>
had to bee added, to get the xtp from a nt:
( >>link name>> )
[unneeded] >>link dup ?\ need alias
?\ ' cell+ alias >>link ( xtp -- lfa )
[unneeded] name>>
?\ : name>> ( nt -- xtp ) [ 2 cells ] literal - ;
Finally, the old definition of alias
was renamed to deferred
:
( deferred )
: deferred ( xt "name" -- ) defer latest name> defer! ;
\ doc{
\
\ deferred ( xt "name" -- )
\
\ Create a deferred word _name_ that will execute _xt_. The
\ effect is the same than `defer name xt ' name defer!`.
\
\ }doc
2016-04-18
Wrote realias
, to reconfigure aliases:
: realias ( xt "name" -- )
defined dup 0= #-13 ?throw name>> !s ;
\ doc{
\
\ realias ( xt "name" -- )
\
\ Set the alias _name_ to execute _xt_.
\
\ See `alias`.
\
\ }doc
Updated the definition of aliases in the kernel. They were created as deferred words, in conformity with the old version of alias
. Now they are actual aliases. The assembler macros _header
and _alias_header
have been modified. Aliases don't have a code field, so they save the 3 bytes of the Z80 instruction jp
used by deferred words. 48 bytes have been saved in the kernel.
_header: macro _base_label,_name,_flags,_xt
; In dictionary:
if nul _xt
_base_label: ; execution token
else
_base_label equ _xt ; execution token of an alias
endif
local _dp_backup
local _name_address
local _address_after_name
_dp_backup: equ $
; In memory bank:
org np
_base_label##xtp:
if nul _xt
dw _base_label ; execution token pointer
else
dw _xt ; execution token pointer of an alias
endif
_base_label##lfa: ; link field address
dw nt_of_the_previous_word ; link field
_base_label##nt: ; name token
; Length byte with optional immediate bit:
if nul _flags
db _address_after_name-_name_address
else
db _address_after_name-_name_address+_flags
endif
_name_address: equ $
db _name ; name field
_address_after_name: equ $
np: defl $ ; new value of the names pointer
; Update the names pointer:
org names_pointer
dw np ; overwrite names_pointer
dw np ; overwrite names_pointer_init_value
; In dictionary:
org _dp_backup
nt_of_the_previous_word: defl _base_label##nt
endm
_alias_header: macro _base_label,_name,_flags,_xt
_header _base_label,_name,_flags,_xt
endm
Discovered the bug of the floating-point module: .fs
added a zero to the floating-point stack for every number in it which is non-integer and less than 1. The reason is there's a bug in the PRINT-FP ROM routine, called "unbalaced stack error". It's documented in the disassembly:
; the branch was here when 'int x' was found to be zero as in say 0.5.
; The zero has been fetched from the calculator stack but not deleted and
; this should occur now. This omission leaves the stack unbalanced and while
; that causes no problems with a simple PRINT statement, it will if str$ is
; being used in an expression e.g. 2 + STR$ 0.5 gives the result 0.5
; instead of the expected result 20.5.
; credit Tony Stratton, 1982.
; A DEFB 02 delete is required immediately on using the calculator.
;; PF-SMALL
L2E24: RST 28H ;; FP-CALC int x = 0.
L2E25: DEFB $E2 ;;get-mem-2 int x = 0, x-int x.
DEFB $38 ;;end-calc
Fixed ftrunc
. So far its behaviour was done by fround
. fround
and floor
are not defined yet.
( fround floor ftrunc )
need calc need f0= need f0< need fdup need fnegate
: fround ( F: r1 -- r2 ) ;
\ XXX TODO --
\ doc{
\
\ fround ( r1 -- r2 )
\
\ Round _r1_ to an integral value using the "round to
\ nearest" rule, giving _r2_.
\
\ Origin: Forth-2012 (FLOATING).
\
\ }doc
: floor ( F: r1 -- r2 ) ;
\ XXX TODO --
\ doc{
\
\ floor ( F: r1 -- r2 )
\
\ Round _r1_ to an integral value using the "round toward
\ negative infinity" rule, giving _r2_.
\
\ Origin: Forth-2012 (FLOATING).
\
\ }doc
code ftrunc ( F: r1 -- r2 )
calc 3A c, end-calc jpnext end-code
\ doc{
\
\ floor ( F: r1 -- r2 )
\
\ Round _r1_ to an integral value using the "round toward
\ zero" rule, giving _r2_.
\
\ Origin: Forth-2012 (FLOATING).
\
\ }doc
\ Example from the documentation of Forth-2012:
\ : ftrunc ( F: r1 -- r2 )
\ fdup f0= 0= if
\ fdup f0< if fnegate floor fnegate else floor then
\ then ;
\ From Gforth:
\
\ : ftrunc ( F: r1 -- r2 ) f>d d>f ;
Renamed the math library modules with the common root "math.", and extracted the code of the ROM calculator to its own file, in order to reuse it.
Improved the ROM calculator support and the floating-point support. The ROM calculator module has been rewritten. Now it uses ordinary words, in its own word list, including if else then
. The floating-point support has been modified and improved accordingly.
Added floating-point floor
and ROM calculator int
.
2016-04-19
Fixed circular prerequisite of f>s
.
Fixed fmax
.
2016-04-20
Fixed fconstant
.
Wrote calculator-command>flag
and rewrote f=
, f<>
, f<
, f<=
, f>
and f>=
after it, because calling the equivalents command of the ROM calculator directly always returned a true flag; the details of the debugging are noted in the ROM calculator module.
( calculator-command>flag )
need calculator need f>flag need call-xt
: calculator-command>flag ( b -- )
$C5 c, $06 c, c,
\ push bc ; save the Forth IP
\ ld b,command
calculator $3B c,
\ `fp-calc-2` calculator command, which executes the
\ calculator command stored in the b register.
[ calculator-wordlist >order ] end-calculator [ previous ]
$C1 c,
\ pop bc ; restore the Forth IP
['] f>flag call-xt
[ also assembler ] jpnext [ previous ] ;
\ doc{
\
\ calculator-command>flag ( b -- )
\
\ Compile the assembler instructions needed to execute the
\ _b_ command of the ROM calculator and to return the
\ floating-point result as a flag on the data stack.
\
\ }doc
( f= f<> )
need calculator-command>flag
code f= ( -- f ) ( F: r1 r2 -- )
0E calculator-command>flag end-code
\ `nos-eql` calculator command
code f<> ( -- f ) ( F: r1 r2 -- )
0B calculator-command>flag end-code
\ `nos-neql` calculator command
\ code f~ \ XXX TODO
( f< f<= f> f>= )
need calculator-command>flag
code f< ( -- f ) ( F: r1 r2 -- )
0D calculator-command>flag end-code
\ `no-less` calculator command
code f<= ( -- f ) ( F: r1 r2 -- )
09 calculator-command>flag end-code
\ `no-l-eql` calculator command
code f> ( -- f ) ( F: r1 r2 -- )
0C calculator-command>flag end-code
\ `no-grtr` calculator command
code f>= ( -- f ) ( F: r1 r2 -- )
0A calculator-command>flag end-code
\ `no-gr-eql` calculator command
The calculator's if
compiled the "jump-true" calculator command, which does a branch when calculator TOS is not zero, the opposite of what it should be. 0branch
has been added to fix this, after the names used in the kernel:
( calculator )
: ?branch ( -- ) $00 c, ;
\ Compile the `jump-true` ROM calculator command.
: 0branch ( -- ) 0= ?branch ;
\ Compile the ROM calculator commands to do a branch if
\ the TOS of the calculator stack is zero.
: branch ( -- ) $33 c, ;
\ Compile the `jump` ROM calculator command.
-->
( calculator )
: >mark ( -- a ) here 0 c, ;
\ Compile space for the displacement of a ROM calculator
\ forward branch which will later be resolved by
\ `>resolve`.
\
\ Typically used after either `branch` or
\ `?branch`.
: from-here ( a -- n )
here [ also forth ] swap - [ previous ] ;
\ Calculate the displacement _n_ from the current data-space
\ pointer to address _a_.
: >resolve ( a -- )
[ also forth ] dup [ previous ] from-here
[ also forth ] swap [ previous ] c! ;
\ Resolve a ROM calculator forward branch by placing the
\ displacement to the current position into the space
\ compiled by `>mark`.
' here alias <mark ( -- a )
\ Leave the address of the current data-space pointer as the
\ destination of a ROM calculator backward branch which will
\ later be resolved by `<resolve`.
\
\ Typically used before either `branch` or `?branch`.
: <resolve ( a -- ) from-here c, ;
\ Resolve a ROM calculator backward branch by compiling the
\ displacement from the current position to address _a_,
\ which was left by `<mark`.
: if ( -- a ) 0branch >mark ;
: else ( a1 -- a2 )
branch >mark [ also forth ] swap [ previous ] >resolve ;
' >resolve alias then ( a -- )
The bug of if
affected fmax
and fmin
, buy these words had to be modified also because they used the calculator's operators >
and <
directly, what causes problems (the details are in the source of the calculator module). calculator-command
was factored from calculator-command>flag
for this:
( calculator-command )
need calculator
: calculator-command ( b -- )
$C5 c, $06 c, c,
\ push bc ; save the Forth IP
\ ld b,command
calculator $3B c,
\ `fp-calc-2` calculator command, which executes the
\ calculator command stored in the b register.
[ calculator-wordlist >order ] end-calculator [ previous ]
$C1 c, ;
\ pop bc ; restore the Forth IP
\ doc{
\
\ calculator-command ( b -- )
\
\ Compile the assembler instructions needed to execute the
\ _b_ command of the ROM calculator.
\
\ }doc
( calculator-command>flag )
need calculator-command need f>flag need call-xt
: calculator-command>flag ( b -- )
calculator-command ['] f>flag call-xt
[ also assembler ] jpnext [ previous ] ;
\ doc{
\
\ calculator-command>flag ( b -- )
\
\ Compile the assembler instructions needed to execute the
\ _b_ command of the ROM calculator and to return the
\ floating-point result as a flag on the data stack.
\
\ }doc
( fmax )
need calculator need calculator-command
code fmax ( F: r1 r2 -- r1|r2 )
calculator 2dup end-calculator
0C calculator-command ( F: r1 r2 rf -- )
\ `no-grtr` ROM calculator command
calculator
if drop ( F: r1 )
else swap drop ( F: r2 )
then
end-calculator jpnext end-code
\ XXX OLD -- Original, simpler version. The problem is the
\ calculator's `>`. See the calculator module for details of
\ the problem.
\ code fmax ( F: r1 r2 -- r1|r2 )
\ calculator
\ 2dup > ( F: r1 r2 rf -- )
\ if drop ( F: r1 )
\ else swap drop ( F: r2 )
\ then
\ end-calculator jpnext end-code
( fmin )
need calculator need calculator-command
code fmin ( F: r1 r2 -- r1|r2 )
calculator 2dup end-calculator
0D calculator-command ( F: r1 r2 rf -- )
\ `no-less` ROM calculator command
calculator
if drop ( F: r1 )
else swap drop ( F: r2 )
then
end-calculator jpnext end-code
\ XXX OLD -- Original, simpler version. The problem is the
\ calculator's `<`. See the calculator module for details of
\ the problem.
\ code fmin ( F: r1 r2 -- r1|r2 )
\ calculator
\ 2dup < ( F: r1 r2 rf -- )
\ if drop ( F: r1 )
\ else swap drop ( F: r2 )
\ then
\ end-calculator jpnext end-code
Improved upper
and system-bank
: two simples changes save same bytes and T-cycles. For example:
; XXX OLD
; T B
; call bank.system ; 17 03
; _jp_next ; 08 02
; XXX NEW
; T B
push ix ; 15 02 ; make next `ret` jump to `next`
Added wait-for-key
, with a routine used by key
:
; ----------------------------------------------
_code_header wait_for_key_,'WAIT-FOR-KEY'
; doc{
;
; wait-for-key ( -- )
;
; Wait for a valid key and discard it.
;
; }doc
; XXX TODO -- make it compatible with the future multitasking
push ix ; make next `ret` jump to `next`
wait_for_key:
; output: A = key code
bit 5,(iy+1) ; FLAGS system variable: new key available?
jr z,wait_for_key
ld a,(sys_last_k) ; return the key in register A
res 5,(iy+1) ; no new key available anymore
ret
Replaced the current definition of dnegate
with a faster one that was commented out and needs only one additional byte. Added conditional assembling depending on the assembly label size_optimization
.
2016-04-21
Fixed, finished and improved the implementation of user
and moved it to the library:
( ?user uallot ucreate user 2user )
: ?user ( -- )
udp @ dup /user > #-279 ?throw \ user area overflow?
0< #-280 ?throw ; \ user area underflow?
\ doc{
\
\ ?user ( -- )
\
\ Throw an exception if the user area pointer is out of bounds.
\
\ }doc
: uallot ( n -- ) udp +! ?user ;
\ doc{
\
\ uallot ( n -- )
\
\ If _n_ is greater than zero, reserve _n_ address units of
\ user data space. If _n_ is less than zero, release _n_
\ address units of user data space. If _n_ is zero, leave the
\ user data-space pointer unchanged. An exception is thrown
\ if the user-data pointer is out of bounds after the
\ operation.
\
\ }doc
: ucreate ( "name" -- ) udp @ (user) ;
\ doc{
\
\ ucreate ( "name" -- )
\
\ Create a header _name_ which points to the first available
\ offset within the user area. Execution of _name_ leaves
\ its absolute user area storage address. No user space is
\ allocated. See: `uallot`, `user`.
\
\ }doc
: user ( "name" -- ) ucreate cell uallot ;
\ doc{
\
\ user ( n "name" -- )
\
\ Create a user variable _name_ in the first available offset
\ within the user area. Execution of _name_ leaves its
\ absolute user area storage address.
\
\ }doc
: 2user ( "name" -- ) ucreate [ 2 cells ] literal uallot ;
\ doc{
\
\ 2user ( "name" -- )
\
\ Create a user double variable _name_ in the first available
\ offset within the user area. Execution of _name_ leaves
\ its absolute user area storage address.
\
\ }doc
Only (user)
is in the kernel:
_colon_header paren_user_,'(USER)'
; doc{
;
; (user) ( +n "name" -- )
;
; Create a user variable _name_. _+n_ is the offset within the
; user area where the value for _name_ is stored. Execution of
; _name_ leaves its absolute user area storage address.
;
; This is a factor of `user`. It works like `user` in:
; fig-Forth, Forth-79 (Reference Word Set) and Forth-83
; (Uncontrolled Reference Words).
;
; }doc
; XXX TODO -- rename, maybe to `+user`?
dw c_constant_
dw paren_semicolon_code_
do_user:
pop hl
ld e,(hl)
ld d,$00 ; de = index of the user variable
ld hl,(user_variables_pointer)
add hl,de ; hl= address of the user variable
_jp_pushhl
Added fround
:
: fround ( F: r1 -- r2 ) fdup fsgn fhalf f* f+ ftrunc ;
\ doc{
\
\ fround ( r1 -- r2 )
\
\ Round _r1_ to an integral value using the "round to
\ nearest" rule, giving _r2_.
\
\ Origin: Forth-94 (FLOATING), Forth-2012 (FLOATING).
\
\ }doc
Added f~
, f~abs
, f~rel
, f~relabs
and f==
:
( f~abs f~rel f~relabs f== )
\ Credit:
\
\ Most of this code is based on the words `f~`, `f~abs` and
\ `f~rel` implemented in Gforth 0.7.3. Parts have been
\ factored and adapted.
need frot need f- need fabs need fswap need f< need fover
need f+ need f* need fp@ need float- need float need s=
need fdrop need fsgn
: f~abs ( -- f ) ( F: r1 r2 r3 -- )
frot frot f- fabs fswap f< ;
\ doc{
\
\ f~abs ( -- f ) ( F: r1 r2 r3 -- )
\
\ Approximate equality with absolute error: `|r1-r2|<r3`.
\
\ Flag _f_ is true if the absolute value of _r1-r2_ is less
\ than _r3_.
\
\ Origin: Gforth.
\
\ }doc
: f~rel ( -- f ) ( F: r1 r2 r3 -- )
frot frot fover fabs fover fabs f+
frot frot f- fabs frot frot f* f< ;
\ doc{
\
\ f~rel ( -- f ) ( F: r1 r2 r3 -- )
\
\ Approximate equality with relative error:
\ `|r1-r2|<r3*|r1+r2|`.
\
\ Flag _f_ is true if the absolute value of _r1-r2_ is less
\ than the value of _r3_ times the sum of the absolute values
\ of _r1_ and _r2_.
\
\ }doc
: f~relabs ( -- f ) ( F: r1 r2 r3 -- ) fabs f~rel ;
\ XXX TODO -- better name
\ doc{
\
\ f~relabs ( -- f ) ( F: r1 r2 r3 -- )
\
\ Approximate equality with relative error:
\ `|r1-r2|<|r3|*|r1+r2|`.
\
\ Flag _f_ is true if the absolute value of _r1-r2_ is less
\ than the absolute value of _r3_ times the sum of the
\ absolute values of _r1_ and _r2_.
\
\ }doc
: f== ( -- f ) ( F: r1 r2 -- )
fp@ dup float- float tuck s= fdrop fdrop ;
\ doc{
\
\ f== ( -- f ) ( F: r1 r2 -- )
\
\ Exact bitwise equality.
\
\ Are _r1_ and _r2_ exactly identical? Flag _f_ is true if
\ the bitwise comparison of _r1_ and _r2_ is succesful.
\
\ }doc
( f~ )
need f~abs need f== need f~relabs
' f~abs ,
here ' f== ,
' f~relabs ,
constant (f~) \ execution table of `f~`
: f~ ( -- f ) ( F: r1 r2 r3 -- )
fdup fsgn f>s cells (f~) + perform ;
\ doc{
\
\ f~ ( -- f ) ( F: r1 r2 r3 -- )
\
\ Medley for comparing _r1_ and _r2_ for equality:
\
\ - _r3_>0: `f~abs`;
\ - _r3_=0: `f==`;
\ - _r3_<0: `f~relabs`.
\
\ See: `f~abs`, `f==`, `f~rel`,`f~relabs`.
\
\ Origin: Forth-94 (FLOATING EXT), Forth-2012 (FLOATING EXT).
\
\ }doc
2016-04-22
Released version 0.5.0.
2016-04-23
Improve the Pong game: the ball rolls.
Revise and improve the module that defines UDG and the module that prints them at graphic coordinates.
Add emit-0udg
to the kernel, a useful alternative to emit-udg
.
Improve type
with ?do
: faster and smaller.
Add c-!
:
code c-! ( c ca -- )
hl pop de pop m a ld e sub a m ld jpnext end-code
\ doc{
\
\ c-! ( c ca - )
\
\ Subtract _c_ from the char at _ca_
\
\ }doc
Fix c+!
.
2016-04-24
Add 0udg[
and udg[
, which make it possible to define several UDG in compact form.
( 0udg[ udg[ )
need os-udg
variable first-udg
variable current-udg
variable current-scan
[defined] binary ?\ : binary ( -- ) 2 base ! ;
: 0udg[ ( b -- )
dup first-udg ! current-udg ! current-scan off binary ;
\ doc{
\
\ 0udg[ ( b -- )
\
\ Start a set of UDG definitions, from UDG number _b_
\ (0..255).
\
\ See: `udg[`, `|`, `||`, `||]`.
\
\ }doc
: udg[ ( c -- ) 128 - 0udg[ ;
\ doc{
\
\ udg[ ( c -- )
\
\ Start a set of UDG definitions, from UDG character _c_
\ (128..255).
\
\ See: `0udg[`, `|`, `||`, `||]`.
\
\ Usage example:
\
\ ----
\ 140 udg[ \ define UDG 140..144
\
\ 00111100 | 00111100 | 00111100 | 00111100 | 00111100 ||
\ 01111110 | 01111110 | 01111110 | 01111110 | 01011110 ||
\ 11111111 | 11111111 | 11111111 | 10111111 | 10111111 ||
\ 11111111 | 11111111 | 10111111 | 10111111 | 11111111 ||
\ 11111111 | 10111111 | 10111111 | 11111111 | 11111111 ||
\ 11001111 | 11011111 | 11111111 | 11111111 | 11111111 ||
\ 01111110 | 01111110 | 01111110 | 01111110 | 01111110 ||
\ 00111100 | 00111100 | 00111100 | 00111100 | 00111100 ||]
\ ----
\
\ }doc
: | ( b -- )
os-udg @ current-udg @ 8 * current-scan @ + + c!
1 current-udg +! ;
\ doc{
\
\ | ( b -- )
\
\ Store scan _b_ into the current UDG being defined.
\
\ See: `0udg[`, `udg[`, `||`, `||]`.
\
\ }doc
: || ( b -- )
| 1 current-scan +! first-udg @ current-udg ! ;
\ doc{
\
\ || ( b -- )
\
\ Store scan _b_ into the current UDG being defined and start
\ a new row of scans.
\
\ See: `0udg[`, `udg[`, `|`, `||]`.
\
\ }doc
: ||] ( b -- ) || decimal ;
\ doc{
\
\ ||] ( b -- )
\
\ Store scan _b_ into the current UDG being defined and stop
\ defining UDGs.
\
\ See: `0udg[`, `udg[`, `|`, `||`.
\
\ }doc
Fix dump
: nothing was printed when the length was less than 8.
Add support for 2literal
to decode
.
Improve 2,
and 2literal
.
Fix 2lit
: it returned the low and high parts in reverse order.
Add some words to compile constants as literals:
( ]l ]2l exec eval )
need [if]
[needed] ]l [if]
: ]l ( x -- ) ] postpone literal ; immediate compile-only
\ doc{
\
\ ]l ( x -- )
\
\ A short form of the idiom `] literal`.
\
\ }doc
[then]
[needed] ]2l [if]
: ]2l ( xd -- ) ] postpone 2literal ; immediate compile-only
\ doc{
\
\ ]2l ( xd -- )
\
\ A short form of the idiom `] 2literal`.
\
\ }doc
[then]
[needed] exec [if]
: exec ( "name" -- i*x )
parse-name find-name ?dup 0= #-13 ?throw name> execute ;
\ doc{
\
\ exec ( "name" -- i*x )
\
\ Parse, find and execute "name".
\
\ }doc
[then]
[needed] eval [if]
: eval ( i*x "name" -- j*x ) parse-name evaluate ;
\ doc{
\
\ exec ( i*x "name" -- j*x )
\
\ Parse and evaluate "name".
\
\ This is a common factor of `[const]`, `[2const]` and
\ `[cconst]`.
\
\ }doc
[then]
( [const] [2const] [cconst] )
need eval need [if]
[needed] [const] [if]
: [const] ( "name" -- )
eval postpone literal ; immediate compile-only
\ doc{
\
\ [const] ( "name" -- )
\
\ Evaluate "name". Then compile the single-cell value left on
\ the stack.
\
\ This word is intented to compile constants as literals, in
\ order to gain execution speed.
\
\ Usage example:
\
\ ----
\ 48 constant zx
\ : test ( -- ) [const] zx . ;
\ ----
\
\ }doc
[then]
[needed] [2const] [if]
: [2const] ( "name" -- )
eval postpone 2literal ; immediate compile-only
\ doc{
\
\ [2const] ( "name" -- )
\
\ Evaluate "name". Then compile the double-cell value left on
\ the stack.
\
\ This word is intented to compile double-cell constants as
\ literals, in order to gain execution speed.
\
\ Usage example:
\
\ ----
\ 48. 2constant zx
\ : test ( -- ) [2const] zx d. ;
\ ----
\
\ }doc
[then]
[needed] [cconst] [if]
: [cconst] ( "name" -- )
eval postpone cliteral ; immediate compile-only
\ doc{
\
\ [cconst] ( "name" -- )
\
\ Evaluate "name". Then compile the char left
\ on the stack.
\
\ This word is intented to compile char constants as literals, in
\ order to gain execution speed.
\
\ Usage example:
\
\ ----
\ 48 cconstant zx
\ : test ( -- ) [cconst] zx emit ;
\ ----
\
\ }doc
[then]
Move get-default-bank
and set-default-bank
to the library.
Move :noname
to the library.
Add lastxt
and latestxt
.
Fix :noname
: now it updates lastxt
and last
.
Fix recurse
: now it works in words created with :noname
.
Move nextname
, nextname-header
and nextname-string
to the library.
Move char
and [char]
to the library.
By the first time, the unused data space after cold
is more than 32 KiB: 32774 B.
2016-04-25
First working version of marker
.
Clearer exception messages: a carriage return is printed before the offending word and before the message; default messages show no "Exception" text, and use the same backslash as the text messages.
2016-04-26
Improve located
: when the user press the break key, throw exception -28 (user interrupt); formerly the ordinary -268 (required, but not located) was thrown by the calling word.
Rename old fig-Forth latest
to current-latest
and move it to the library. Write new latest
after Gforth. Change the few cases that depend on the old behaviour.
2016-04-27
Add char-in-string?
and char-position?
:
( char-in-string? char-position? )
need -rot need [if]
[needed] char-in-string? [if]
: char-in-string? ( ca len c -- f )
-rot bounds
?do dup i c@ = if drop true unloop exit then loop
drop false ;
\ doc{
\
\ char-in-string? ( ca len c -- f )
\
\ Is char _c_ in string _ca len_?
\
\ }doc
[then]
[needed] char-position? [if]
: char-position? ( ca len c -- +n true | false )
-rot 0 ?do
2dup i + c@ = if 2drop i true unloop exit then
loop 2drop false ;
\ doc{
\
\ char-position? ( ca len c -- +n true | false )
\
\ If char _c_ is in string _ca len_, return its first
\ position _+n_ and _true_; else return _false_.
\
\ }doc
[then]
Move ascii-char?
and control-char?
to module "chars.fsb". Move ascii-type
to module "printing.type.fsb" and rename it to type-ascii
.
Replace bs
, used by dump
, with backspace
, which is part of the library.
Add /!
, *!
, 2/!
, 2*!
:
( /! *! 2/! 2*! )
[unneeded] /!
?\ : /! ( n a -- ) tuck @ swap / swap ! ;
\ doc{
\
\ /! ( n a -- )
\
\ Divide _n_ by the single-cell number at _a_ and store
\ the quotient in _a_
\
\ }doc
[unneeded] *!
?\ : *! ( n a -- ) tuck @ swap * swap ! ;
\ doc{
\
\ *! ( n|u a -- )
\
\ Multiply _n|u_ by the single-cell number at _a_ and store
\ the product in _a_
\
\ }doc
[unneeded] 2*!
?\ : 2*! ( a -- ) dup @ 2* swap ! ;
\ doc{
\
\ 2*! ( a -- )
\
\ Do a `2*` shift to the single-cell number at _a_.
\
\ See `2*`.
\
\ }doc
[unneeded] 2/! ?exit
need 2/
: 2/! ( a -- ) dup @ 2/ swap ! ;
\ doc{
\
\ 2/! ( a -- )
\
\ Do a `2/` shift to the single-cell number at _a_.
\
\ See `2/`.
\
\ }doc
Release version 0.6.0.
Add sgn
and <=>
:
( sgn <=> )
: sgn ( n -- -1|0|1 )
dup 0= ?exit 0< ?dup ?exit 1 ;
\ doc{
\
\ sgn ( n -- -1|0|1 )
\
\ If _n_ is zero, return zero.
\ If _n_ is negative, return negative one.
\ If _n_ is positive, return positive one.
\
\ }doc
[unneeded] <=> ?exit
: <=> ( n1 n2 -- -1|0|1 ) swap - sgn ;
\ doc{
\
\ <=> ( n1 n2 -- -1|0|1 )
\
\ If _n1_ equals _n2_, return zero.
\ If _n1_ is less than _n2_, return negative one.
\ If _n1_ is greater than _n2_, return positive one.
\
\ }doc
Add either
and neither
:
( either neither )
\ Credit:
\
\ Code from IsForth (version 1.23b).
need [if]
[needed] either [if]
: either ( n1|u1 n2|u2 n3|u3 -- f )
-rot over = -rot = or ;
\ doc{
\
\ either ( n1|u1 n2|u2 n3|u3 -- f )
\
\ Return _true_ if _n1|u1_ equals either _n2|u2_ or _n3|u3_;
\ else return _false_.
\
\ }doc
[then]
[needed] neither [if]
: neither ( n1|u1 n2|u2 n3|u3 -- f )
-rot over <> -rot <> and ;
\ doc{
\
\ neither ( n1|u1 n2|u2 n3|u3 -- f )
\
\ Return _true_ if _n1|u1_ is not equal to either _n2|u2_ or
\ _n3|u3_; else return _false_.
\
\ }doc
[then]
Improve the documentation of some optional control structures. Rename nextcase
to repeatcase
, and other>
to othercase>
.
Factor .error-word
from error
, in order to reuse it in the new optional warnings
control, which is moved to the library:
( warnings )
need user need search-wordlist
user warnings \ flag
\ doc{
\
\ warnings ( -- a )
\
\ User variable that holds a flag. If it's zero, no warning
\ is shown when a compiled word is not unique in the
\ `current` vocabulary.
\
\ }doc
: no-warnings? ( -- f ) warnings @ 0= ;
\ doc{
\
\ no-warnings? ( -- f )
\
\ Are the warnings deactivated?
\
\ }doc
: not-redefined? ( ca len -- ca len xt false | ca len true )
2dup get-current search-wordlist 0= ;
\ doc{
\
\ not-redefined? ( ca len -- ca len xt false | ca len true )
\
\ Is the word name _ca len_ not yet defined in the
\ current compilation word list?
\
\ }doc
: ?warn ( ca len -- ca len | ca len xt )
no-warnings? if unnest exit ( ca len ) then
not-redefined? if unnest then
( ca len | ca len xt ) ;
\ doc{
\
\ ?warn ( ca len -- ca len | ca len xt )
\
\ A common factor of `warn.throw`, `warn.message` and
\ `warn-throw`. Check if a warning about the redefinition of
\ the word name _ca len_ is needed. If no warning is needed,
\ unnest the calling definition and return _ca len_. If a
\ warning is needed, return _ca len_ and the _xt_ of the word
\ found in the current compilation wordlist.
\
\ See `warn.throw`, `warn-throw`, `warn.message`.
\
\ }doc
( warn.throw warn.message )
need warnings need [if]
[needed] warn.throw [if]
: warn.throw ( ca len -- ca len )
?warn ( ca len xt ) drop 2drop .error-word #-257 .throw ;
\ doc{
\
\ warn.throw ( ca len -- ca len )
\
\ Alternative behaviour for the deferred word `warn`. If the
\ contents of the user variable `warnings` is not zero and
\ the word name _ca len_ is already defined in the current
\ compilation word list, print throw error #-257, without
\ actually throwing an error.
\
\ See `warn-throw`, `warn.message`.
\
\ }doc
' warn.throw ' warn defer! warnings on
[then]
[needed] warn.message [if]
: warn.message ( ca len -- ca len )
?warn ( ca len xt ) ." redefined " >name .name ;
\ doc{
\
\ warn.message ( ca len -- ca len )
\
\ Alternative behaviour for the deferred word `warn`. If the
\ contents of the user variable `warnings` is not zero and
\ the word name _ca len_ is already defined in the current
\ compilation word list, print a warning message.
\
\ See `warn.throw`, `warn-throw`.
\
\ }doc
' warn.message ' warn defer! warnings on
[then]
( warn-throw )
need warnings need [if]
[needed] warn-throw [if]
: warn-throw ( ca len -- ca len )
?warn ( ca len xt ) #-257 throw ;
\ doc{
\
\ warn-throw ( ca len -- ca len )
\
\ Alternative behaviour for the deferred word `warn`. If the
\ contents of the user variable `warnings` is not zero and
\ the word name _ca len_ is already defined in the current
\ compilation word list, throw error #-257 instead of
\ printing a warning message.
\
\ See `warn.throw`, `warn.message`.
\
\ }doc
' warn-throw ' warn defer! warnings on
[then]
2016-04-28
Factored .unused
from greeting
:
; ----------------------------------------------
_colon_header greeting_,'GREETING'
; doc{
;
; greeting ( -- )
;
; Print the boot message.
;
; }doc
dw paren_dot_quote_
_string "Solo Forth\rVersion "
dw dot_version_
dw paren_dot_quote_
_string "\rBy Marcos Cruz\r(programandala.net), 2015, 2016\r"
dw dot_unused_
dw exit_
; ----------------------------------------------
_colon_header dot_unused_,'.UNUSED'
; doc{
;
; .unused ( -- )
;
; Display the amount of space remaining in the region addressed
; by `here`, in address units.
;
; ----
; : .unused ( -- )
; unused u. ." B free" ;
; ----
;
; }doc
dw unused_,u_dot_
dw paren_dot_quote_
_string "B free"
dw exit_
First working versions of type-left
, type-center
and type-right
:
( drop-type padding-spaces type-left )
[unneeded] drop-type
?\ : drop-type ( ca len x -- ) drop type ; exit
: padding-spaces ( len1 len2 -- )
swap - 0 max spaces ;
\ doc{
\
\ : padding-spaces ( len1 len2 -- )
\
\ If _len2_ minus _len1_ is a positive number, print that
\ number of spaces; else do nothing.
\
\ }doc
[needed] padding-spaces ?exit
: type-left ( ca len1 len2 -- )
2dup 2>r min type 2r> padding-spaces ;
\ doc{
\
\ type-left ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the left of a field of _len2_
\ characters.
\
\ }doc
( type-right )
need padding-spaces need drop-type need <=>
: type-right-crop ( ca len1 len2 -- )
over swap - /string type ;
\ doc{
\
\ type-right-crop ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the right of a field of _len2_
\ characters, which is shorter than the string.
\
\ }doc
: type-right-fit ( ca len1 len2 -- )
2dup 2>r padding-spaces 2r> min type ;
\ doc{
\
\ type-right-fit ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the right of a field of _len2_
\ characters, which is longer than the string.
\
\ }doc
' type-right-fit ,
here ' drop-type ,
' type-right-crop ,
constant type-right-cases
\ Execution table of `type-right`.
: type-right ( ca len1 len2 -- )
2dup <=> cells type-right-cases + perform ;
\ doc{
\
\ type-right ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the right of a field of _len2_
\ characters.
\
\ }doc
( type-center )
need drop-type need <=>
: type-center-fit ( ca len1 len2 -- )
over - 2 /mod dup >r + spaces type r> spaces ;
\ doc{
\
\ type-center-fit ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the center of a field of _len2_
\ characters, which is longer than the string.
\
\ }doc
: type-center-crop ( ca len1 len2 -- )
over swap - 2 /mod dup >r + /string r> - type ;
\ doc{
\
\ type-center-crop ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the center of a field of _len2_
\ characters, which is shorter than the string.
\
\ }doc
' type-center-fit ,
here ' drop-type ,
' type-center-crop ,
constant type-center-cases
\ Execution table of `type-center`.
: type-center ( ca len1 len2 -- )
2dup <=> cells type-center-cases + perform ;
\ doc{
\
\ type-center ( ca len1 len2 -- )
\
\ Type string _ca len1_ at the center of a field of _len2_
\ characters.
\
\ }doc
Add 0max
:
( 0max )
\ Credit:
\ Idea taken from IsForth.
code 0max ( n -- n | 0 )
E1 c,
\ pop hl
CB c, 10 05 + c,
\ rl h ; negative?
DA c, ' false ,
\ jp c,false_
CB c, 18 05 + c,
\ rr h
jppushhl
\ jp push_hl
end-code
\ doc{
\
\ 0max ( n -- n | 0 )
\
\ If _n_ is negative, return 0; else return _n_.
\ This is a faster alternative to the idiom `0 max`.
\
\ }doc
Rename set
to storer
. Though this usage of set
was mentioned in Forth-79 and Forth-83, it's seldom used, while set
is a comus name to define a set of data, a more useful feature which will be implemented. storer
is an obvious name for the old set
. Beside, it has been improved with double-cell operators, faster and shorter:
( storer )
: storer ( x a "name" -- )
create 2,
does> ( -- ) ( pfa ) 2@ ! ;
\ doc{
\
\ storer ( x a "name" -- )
\
\ Define a word "name" which, when executed, will cause the
\ value _x_ to be stored at _a_.
\
\ Origin: word `set` found in Forth-79 (Reference Word Set)
\ and Forth-83 (Appendix B. Uncontrolled Reference Words).
\
\ }doc
Write ccase
, ?ccase
and ccase0
:
\ -----------------------------------------------------------
\ Description
\ `ccase` is a positional control structure that uses the
\ position of a character in a string as key, and has a
\ mandatory default option at the end. Data space used
\ (without requirements): 69 bytes.
\
\ `ccase0` is a simpler variant of `ccase` that uses the
\ first compiled option as a mandatory default option. Data
\ space used (without requirements): 44 bytes.
\
\ `?ccase` is a simpler variant of `ccase` with no default
\ option. Data space used (without requirements): 34 bytes.
Simplify type-right
.
2016-04-29
Add ruler
:
( ruler )
: ruler ( c len -- ca len )
dup allocate-string swap 2dup 2>r rot fill 2r> ;
\ doc{
\
\ ruler ( c len -- ca len )
\
\ Return a string _ca len_ of characters _c_.
\
\ }doc
Add lineload
:
: lineblock>source ( n1 n2 -- ) blk ! c/l * >in ! ;
\ doc{
\
\ lineblock>source ( n1 n2 -- )
\
\ Set block _n2_ as the current source, starting from its
\ line _n1_.
\
\ }doc
: lineload ( n1 n2 -- )
dup 0= #-259 ?throw
nest-source lineblock>source interpret unnest-source ;
\ doc{
\
\ lineload ( n1 n2 -- )
\
\ Begin interpretation at line _n1_ of block _n2_.
\
\ Origin: Forth-83 (Uncontrolled Reference Words).
\
\ }doc
Add split
and join
:
( split join )
code split ( x -- b1 b2 )
E1 c,
\ pop hl
16 c, 00 c, 58 05 + c,
\ ld d,0
\ ld e,l
68 04 + c, 26 c, 00 c,
\ ld l,h
\ ld h,0
C3 c, pushhlde ,
\ jp push_hlde
end-code
\ doc{
\
\ split ( x -- b1 b2 )
\
\ Get _b1_ and _b2_ from the 2 bytes which compose _x_: _b1_
\ is the high-order byte and _b2_ is the low-order byte.
\
\ See `join`.
\
\ Origin: IsForth.
\
\ }doc
code join ( b1 b2 -- x )
D1 c, 60 03 + c, D1 c, 68 03 + c,
\ pop de
\ ld h,e
\ pop de
\ ld l,e
jppushhl
\ jp push_hl
end-code
\ doc{
\
\ join ( b1 b2 -- x )
\
\ _b1_ is the low-order byte of _x_, and _b2_ is the
\ high-order byte of _x_.
\
\ See `split`.
\
\ Origin: IsForth.
\
\ }doc
During the development of a system benchmark, a non-parsing method to redefine an alias was needed, as an alternative to realias
. alias!
, analogous to the standard word defer!
, is a common factor of alias
and realias
.
( alias! alias realias )
need name>> need !s
: alias! ( xt nt -- ) name>> !s ;
\ doc{
\
\ alias! ( xt nt -- )
\
\ Set the alias _nt_ to execute _xt_.
\
\ See `alias`, `realias`.
\
\ }doc
: alias ( xt "name" -- ) header reveal latest alias! ;
\ doc{
\
\ alias ( xt "name" -- )
\
\ Create an alias _name_ that will execute _xt_.
\
\ Aliases have the execution token _xt_ of the original word,
\ but don't inherit its precedence (set by `immediate`) and
\ restricted (set by `compile-only`) attributes.
\
\ See `realias`, `alias!`.
\
\ Origin: Gforth.
\
\ }doc
: realias ( xt "name" -- )
defined dup 0= #-13 ?throw alias! ;
\ doc{
\
\ realias ( xt "name" -- )
\
\ Set the alias _name_ to execute _xt_.
\
\ See `alias`, `alias!`.
\
\ }doc
Replace the current version of u<
, which was adapted from DZX-Forth, with a version adapted from Z88 CamelForth, which is faster (0.96 relative speed).