Development history of ForthCoupe

Description of the page content

Development history of the ForthCoupe project, a Forth for the SAM Coupé computer.



First tinkering with the source of SamForth-B. But the disassembling is not mature for the fork yet.


SamForth-B is finally forked. First version of a more clear BASIC loader.

First simple changes:


Unknown zones deleted:

  defs 80

  defb 0xd6
  defb 0x45

  defs 18

The following duplicated routine is deleted too:

  call jsvin_rom_routine
  defw rst_10_rom_routine

As well as its vector:

jp_print_a_xxx_duplicated: ; xxx -- not used; the calls are direct
  jp print_a_xxx_duplicated

The jump vectors are deleted: jp jp_* are converted to jp -; call jp_* to call -. Same with sub_* labels in the vectors table.

93 call + ret#! pairs are converted to a single #!jp.

Fix (also in SamForth2z80dasm): the jp_u_dot_header_end is renamed to jp_u_dot_code_field.

VLIST renamed to WORDS!

All scattered code is moved to the corresponding Forth words.

The call_rom_address_in_iy routine is deleted. It's a useless remain from SamForth-A.

All editor code and words are removed.

BASE is standarized: it does not change the base any more but returns its address.

The temp1 and temp2 variables are removed. They were used only in the following case:

  ld hl,(here_fvar)
  ld (temp1_fvar),hl
  ld hl,(latest_fvar)
  ld (temp2_fvar),hl
  ld hl,(temp1_fvar)
  ld (here_fvar),hl
  ld hl,(temp2_fvar)
  ld (latest_fvar),hl
  ld a,0x00
  ld (state_fvar),a

This way it's shorter and faster:

  ld hl,(here_fvar)
  ld (saved_here),hl
  ld hl,(latest_fvar)
  ld (saved_latest),hl
saved_here: equ $+1
  ld hl,0
  ld (here_fvar),hl
saved_latest: equ $+1
  ld hl,0
  ld (latest_fvar),hl
  ld a,0x00
  ld (state_fvar),a

The same is done with tmpstk_fvar.

Some Z80 global optimizations: ld a,0x00 converted to xor a; all unconditional relative jumps are converted to absolute jumps (the relative conditional jumps will be examined later, as their speed depends on the check); cp 0x00 converted to and a; a push - pop pair is changed to use direct register loading.

The storage of the address returned by HERE is moved to the code of the HERE word, and pointed by the here symbol. Also the chere_fvar, latest_cfvar and clate_cfvar system variables are converted the same way.

PUSH-DE is renamed PUSH-DE-HL (HL to TOS); POP-DE is renamed POP-HL-DE (TOS to HL);

Removed the errsp_fvar and stkend_fvar variables. The addresses of the return stack can be used directly in the code instead.

New standard word STATE.

The calls to link_l used the HL register, while L was enough and faster. But an additional entry point link_a makes the calls even faster; the A register was already used by link_l anyway.

New common use word BOUNDS.

#S doesn't work. A lot of checks done.

Unfortunately, the following simple alternative to the error message printing routine can not work — because the message table must be paged in too.

  ld de,error_messages
  call jsvin_rom_routine
  defw jpomsg_rom_routine
  jr warm_restart

An alternative routine has been written. It's 7 bytes longer than the SamForth's original, but saves 11 ending bytes in the message table and its code will be factored and reused:

  ld hl,error_messages
  dec a
  jr z,print_message
  ex af,af'
  ld a,(hl)
  inc hl
  and 0x80
  jr z,skip_message
  ex af,af'
  jr next_message
  ld a,(hl)
  inc hl
  bit 7,a
  push af
  res 7,a
  call print_a
  pop af
  jr z,print_message
  jr warm_restart


New standard word DEPTH.

#S fixed. The problem was a 0BRANCH jumped to a ret that had been combined with a preceding call as part of the global optimizations. All 0BRANCH jumps have been marked; also in SamForth disassembled.

Now (FIND) ignores case. A little modification was needed:

  ; de = address of the name to be searched for
  ; b = its length
  ; hl = address of a word's name in the dictionary
  dec hl ; point to the word name length
length_of_name_searched_for: equ $+1
  ld c,0x00
  ld a,(hl)
  res 7,a ; compile-only bit
  res 6,a ; precedence bit
  cp c ; lengths are different?
  jr nz,different_name_lengths
  ; lengths are equal
  ld a,(hl) ; fetch the byte with its special bits
  ld (leng_fvar),a
  push hl
  ld de,(ip_fvar)
  ld b,(hl)
  res 7,b ; compile-only bit
  res 6,b ; precedence bit
  inc hl
  ; de = address of current char of the name to be searched for
  ; b = its remaining length
  ; hl = address of the current char of a word's name in the dictionary
  ld c,(hl)
  ld a,(de)
  ; convert the char in the A register to uppercase if needed
  cp "a"
  jr c,compare_chars
  cp "z"+1
  jr nc,compare_chars
  and %01011111 ; convert to uppercase
  cp c
  jp nz,different_names

Some useless variables removed.


First draft for the new integrated init: it will search for free memory pages, page them in and copy the code to its execution location.

The print_a subroutine is moved to EMIT. The standard word SPACE is created with the the print_a_space subroutine.

The SamForth-B's ASCII word is state-smart, what is inconvenient:

  ld hl,(ip_fvar)
  inc hl
  ld a,space_char
  cp (hl)
  jr z,skip_space_before_char

  ld a,(hl)
  ld (ip_fvar),hl
  ld l,a
  ld h,0x00
  call push_hl
  ld a,(state)
  bit 7,a ; compiling?
  ret z

  call pop_hl
  ld b,h
  ld c,l
  ld de,lit_code_field
  call compile_call_de
  ld (hl),c
  inc hl
  ld (hl),b
  inc hl
  ld (here),hl

It has a bug: the interpreter continues its work right after the char: ASCII A will put the code of "A" on the stack; ASCII ANT will do the same but then will cause an error because the word "NT" can not be found in the dictionary. Beside, some optimization can be done. Anyway the code has to be splitted into the standard words CHAR and [CHAR].


Symbols have been created for all system variables and special char codes used in the code.

All words in dictionary are converted to lowercase. They will be shown in lowercase here too.

The code of (find) is adapted.

create had a problem: it didn't add the default behaviour to the new word. Now it does the same than variable.

New error added to the code that create headers: "Attempt to use zero-lenght string as a name" (after Gforth).

soff renamed silence. New common use word: perform.

smode_fvar removed. No need to store the current screen mode, because there's already a system variable for that.

Fixed an error introduced by me two days ago: Moving and removing Forth variables caused the basic loader poked them wrongly before doing a warm restart.

New structured version of the BASIC loader, but not fully rewritten yet:

   10 REM ForthCoupe's loader
   20 REM Copyright (C) 2013 Marcos Cruz (
   30 REM ForthCoupe is free software
      published under the terms of the GNU General Public License,
      as published by the Free Software Foundation,
      either version 3 of the license or (at your option) any later version.
      See "".
   40 MODE 3
   50 LOAD "d2:fc.bin"CODE 65536
      REM xxx temporary code location
   60 LET entry=50000
      REM address of the code machine routine that starts ForthCoupe
      REM ForthCoupe variables

   80 LET fvars=&10000,svblk_fvar=fvars+6,slen_fvar=fvars+8
      REM xxx todo -- finish
      REM Cold start

  100 LABEL cold
      ON ERROR GO TO errorTrap
  110 PRINT "ForthCoupe"'"Copyright (C) 2013 Marcos Cruz ("
  120 coldSysVars
  130 pokeColdEnterRoutine
  140 GO TO start
      REM Warm start

  160 LABEL warm
      ON ERROR GO TO errorTrap
  170 REM warmSysVars
      REM xxx -- not needed?
  180 pokeWarmEnterRoutine
      REM Start

  200 LABEL start
  210 POKE SVAR 520,0
      REM &5c08, xxx -- clear keyboard buffer?
  220 CALL entry
      REM Command dispatch

  240 REM xxx todo -- rewrite
  250 ON PEEK svblk_fvar
        GO TO warm
        GO TO bye
  260 GO TO warm
      REM Bye

  280 LABEL b
      REM Disk operations

  300 REM xxx todo -- rewrite or write in Z80
  310 DEF PROC nop
      END PROC
  320 DEF PROC directory
        DIR PEEK &5A07 "*.F*"
      END PROC
  330 DEF PROC fload
        LET b$=MEM$(&10226 TO &1022f),a$=TRUNC$ b$ + ".FS",b=(PEEK &1015D -1)*16384+DPEEK &10188
        LOAD A$ CODE b
        LET a=(PEEK 19314*16384)+(DPEEK 19315-32768)
        DPOKE slen_fvar,a
      END PROC
  340 DEF PROC fsave
        LET b$=MEM$(&10226 TO &1022f),a=(PEEK &1015D-1)*16384+DPEEK &10188b=DPEEK slen_fvar
        LET a$=TRUNC$ b$ + ".FS"
        SAVE A$ CODE a,b
      END PROC
  350 DEF PROC bload
        LET b$=MEM$(&10226 TO &1022f),a$=TRUNC$ b$ + ".FC",b=49152+DPEEK &10188
        IF DPEEK &10188 >=32768
          LET b= (PEEK &1018E-1)*16384+DPEEK &10188
        END IF
        LOAD a$ CODE b
      END PROC
  360 DEF PROC bsave
        LET b$=MEM$(&10226 TO &1022f),a=49152+DPEEK &10188,b=DPEEK slen_fvar
        IF DPEEK &10188 >=32768
          LET a= (PEEK &1018E-1)*16384+DPEEK &10188
        END IF
        LET a$=TRUNC$ b$ + ".FC"
        SAVE a$ CODE a,b
      END PROC
  370 DEF PROC dload
        LET b$=MEM$(&10226 TO &1022f),a$=TRUNC$ b$ + ".FD",b=49152+DPEEK &10188
        LOAD A$ CODE b
      END PROC
  380 DEF PROC dsave
        LET b$=MEM$(&10226 TO &1022F),a=49152+DPEEK &10188,b=DPEEK slen_fvar,a$=TRUNC$ b$+ ".FD"
        SAVE A$ CODE a,b
      END PROC
      REM Machine code routines

  400 REM xxx todo -- do these tasks (mainly memory paging to enter and return) in ForthCoupe instead
  410 DEF PROC pokeColdEnterRoutine
        POKE entry,&ed,&73,0,&f0,62,2,211,250,195,0,64
      END PROC
  420 DEF PROC pokeWarmEnterRoutine
        POKE entry,&ed,&73,0,&f0,62,2,211,250,195,3,64
      END PROC
  430 DEF PROC pokeReturnRoutine
        POKE entry+20,&ed,&7b,0,&f0,62,31,211,250,205,&66,1,201
      END PROC
      REM Error trap

  450 LABEL errorTrap
      IF error>83
        PRINT "DOS";
      END IF
      PRINT " error ";error
  460 GO TO warm
      REM Config

  480 DEF PROC coldSysVars
  490   REM POKE &5A44,1
        REM fat pixels
  500   POKE &5ABA,1
        REM "in quotes" flag
  510   POKE &5A34,2
        REM blocks
  520   REM DPOKE &5C7D,16384
        REM address of chr$ 169
  530   REM POKE &5C6A,8
        rem caps lock
  540 END PROC
  550 DEF PROC keys
  560   DEF KEYCODE 209,"goto warm"
        REM F9, usually boot
  570   KEY 140+49,91
        REM symbol+8=left bracket
  580   KEY 140+58,93
        REM symbol+9=right bracket
  590   KEY 140+70,64
        REM symbol+a=at sign
  600   KEY 140+37,35
        REM symbol+n=number sign
  610 END PROC
      REM Meta

  630 DEF PROC s
        SAVE OVER "fc" LINE 40
      END PROC


The flags_fvar variable has been removed. Its second bit was set by the init, but never checked or changed. Its bit 7 was sen by link when linking to stream 3, and checked by cls in order to do nothing. Too slow and complex. Now link simply stores the linked stream into the code of cls. The IY was used some times to set or check the bits of flags_fvar. Now it's free.

New drafts and timings of the alternative data stack code.

New common use words: sp0, sp@ sp!, rp0, rp@ and rp!.

The old endif is removed (it can be defined in Forth if needed); then is prefered.

New word: spaces.

Fixed: references to tib and pad start and end labels.

New words: tib, on and off.

Improved: fence holds its data.


Timings of three versions of the data stack code. Faster versions without checks. New words to toggle them: fast and slow (after the Jupiter ACE's Forth).

Now a Pasmo's macro creates the headers:

_end_of_dictionary_: equ 0xffff

new_header_format: equ false
old_header_format: equ not new_header_format

  macro header,  label,name,flags,previous

    ; Create a word header in the dictionary

    ; label = label-format name
    ; name = string with the actual name
    ; flags = byte with the precedence and compile-only bits combined
    ; previous = label-format name of the previous word in the dictionary

    local end_of_name

    if new_header_format

        defb flags
        if defined _##previous##_
          defw _##previous##_
          defw previous##_name_field
        defb end_of_name-$-1
        defm name
      end_of_name: equ $

    else ; SamForth's header format:

        if defined _##previous##_
          defw _##previous##_
          defw previous##_name_field+1
        defb end_of_name-$-1 or flags
        defm name
      end_of_name: equ $



The source has been converted with the following Vim's commands:

:'b,'ms@\(.\+\)_link_field:\n\s\+defw\s\(\S\{-}\)\(_name\)\?\n_name_field:\n\s\+defb\s0x\([0-9a-f]\)\([0-9a-f]\)\s*\n_name:\n\s\+defm \(\S\+\)\s*\n_code_field:\s*$@  header ,,{},\r@

Example of an old hard-coded header:

  defw beep_name
  defb 0x02
  defm '."'

And its new version:

  header dot_quote,'."',0,beep

Nevertheless there's a problem with Pasmo. The macro does not create the symbols when the "label" parameter is a Z80 command, e.g. "out", "in", "and"... Those are Forth words. The author of Pasmo has been contacted. Meanwhile, alternative label names are used ("__out", "__in", "__and"...).

New dp word, a variable that stores the address returned by here.

Some simple comparation words has been changed. Example, the original code of 0<:

  header zero_less,"0<",0,zero_equals

  call pop_hl
  bit 7,h
  ld hl,0x0000
  jr z,l5eddh
  inc hl
  jp push_hl

Now it's a bit faster and beside it returns -1 as true, not 1 (the system is being converted to use -1 as true flag):

  header zero_less,"0<",0,zero_equals

  call pop_hl
  bit 7,h
  ld hl,0x0000
  jp z,push_hl
  dec hl
  jp push_hl

The codes for the structure had been extracted from the original code and converted to symbols:

do_structure_id:          equ 0x02
begin_structure_id:       equ 0x03
if_structure_id:          equ 0x04
builds_structure_id:      equ 0x09

Now their checks are a bit faster too.

( fixed: it wasn't immediate.









Some changes and updates in comments.


Some typos.


Some little fixes in the layout.


The header macro is improved, using ideas from DZX-Forth: no need to use the previous word as a parameter. It's calculated.

_previous_nfa: defl 0

  macro header,  label,name,flags

    ; Create a word header in the dictionary

    ; label = label-format name
    ; name = string with the actual name
    ; flags = byte with the precedence and compile-only bits combined

    label##_flags_field:  defb flags
    label##_lfa:          defw _previous_nfa
    _previous_nfa:        defl $
    label##_nfa:          defb label##_cfa-$-1,name


The new header format is made definitive. The old header alternative code is removed.


Some fixes in comments.


Forked to Couplement Forth.


Some stack comments completed.


After the changes in Couplement Forth: Now all changes of state are done with a call to [ or ]; fix in the insert mode flag of the command input.


Fixed the labels of >r and r>. Additional header labels without the 'cfa' suffix; all 'cfa' suffix are removed from the labels. Fix: wrong label in quit.

Related pages

[Abandoned project:] A Forth for the SAM Coupé computer.
SamForth disassembled
Disassembling of SamForth.
SamForth documentation
Edited documentation of SamForth, a Forth system for the SAM Coupé computer.