Starting Forth Reading Notes

I recently looked into the book Starting Forth by Leo Brodie. The examples have been written and tested in GForth. These are my reading notes.

I had actually started with Thinking Forth to learn about the origins of test-driven development and similar project management methods that are popular today, see this article. Then I realized that I could benefit more if I actually invested the time and effort to brush up my Forth and work through the manual Starting Forth by the same author, first.

Admittedly, some concepts are a little dated. For example, the idea to use integer arithmetics when contemporary machines know how to do floating point at least as fast. On the other hand, it is also fascinating to see how far you can get with 16-bit integers and a bit of creativity! The basic idea is to extend my tool chest of methods by studying a new and different programming language paradigm.

Chapter 1

These are very elementary and straightfoward problems:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\ Problem 1
: gift ( -- ) ." Bookends" ;
: giver ( -- ) ." Stephanie" ;
: thanks ( -- ) cr ." Dear " giver ." ," cr 8 spaces ." Thanks for the " gift ." ." ;

\ Problem 2
: ten.less ( n -- nred ) -10 + ;

\ Problem 3
\ The word 'thanks' is compiled fully with auxiliary words hard-coded in.

Chapter 2

The quizzies in this chapter are simple, but I needed to rethink things in terms of RPN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
\ Quizzie 2-a:
\ 1. a b + c *
\ 2. 3 a * b - 4 / c +
\ 3. a b * 200 /
\ 4. n 1+ n /
\ 5. x 7 * 5 + x *

\ 6. (a-b)/(a+b)
\ 7. a/(10b)

\ Quizzie 2-b:
\ 1.
: 2b1 ( c b a -- result ) * + ;
\ 2.
: 2b2 ( c a b -- result ) 4 * - 6 / + ;
\ 3.
: 2b3 ( a b -- result ) 8 * / ;
\ 4.
: 2b4 ( a b -- result ) * 200 / ;
\ 5. (requires 'dup word!)
: 2b5 ( a -- result ) dup 2 * 3 + * ;
\ 6. (requires 'swap' word!)
: 2b6 ( c a b -- result ) - swap / ;

\ Quizzie 2-c:
\ 1.
: 2c1 ( a b c -- c b a ) swap rot ;
\ 2.
: 2c2 ( a b -- a b a ) swap dup rot rot ;
\ 3.
: 2c3 ( a b c -- c a b ) rot rot ;
\ 4.
: 2c4 ( n -- result ) dup 1+ swap / ;
\ 5.
: 2c5 ( x -- result ) dup 7 * 5 + * ;
\ 6.
: 2c6 ( a b -- result ) over 9 * swap - * ;

The problems are not difficult, either. Still, they require consistently thinking in RPN at all stages and to think in terms of stack effects:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
\ Problem 1
\ dup dup has stack effect ( a b -- a b b b ).
\ 2dup has stack effect ( a b -- a b a b ).

\ Problem 2
: 2reverse swap 2swap swap ;

\ Problem 3
: 3dup ( n1 n2 n3 -- n1 n2 n3 n1 n2 n3 )
    rot dup 2swap swap dup 2swap dup 2swap rot rot swap ;
( The sample solution is simpler: )
: 3dup ( n1 n2 n3 -- n1 n2 n3 n1 n2 n3 )
    dup 2over rot ;

\ Problem 4
: c2p4 ( c a b -- result )
    over + * + ;

\ Problem 5
: c2p5 ( a b -- result )
    2dup - rot rot + / ;

\ Problem 6
: convicted-of ( -- 0 ) 0 ;
: homicide ( long -- longer ) 20 + ;
: arson ( long -- longer ) 10 + ;
: bookmaking ( long -- longer ) 2 + ;
: tax-evasion ( long -- longer ) 5 + ;
: will-serve ( years -- ) . ." years" ;

Chapter 4

There were no exercises in chapter three, so I continue right away with chapter four. Here we encounter Forth’s uncommon syntax for branching. The case-instruction is not introduced, unfortunately. Which means that some pieces of code end up being nested and long-winded if-then blocks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
\ Problem 1
\ -1 (true)
\ 0  (false)
\ -1 (true)

\ Problem 2
\ Well ... humpf ...

\ Problem 3
: card ( n -- ) 18 <
    if ." under age"
    else ." alcoholic beverages permitted"
    then ;

\ Problem 4
: sign.text ( x -- )
    dup >
    if ." positive"
    else dup 0<
        if ." negative"
        else ." zero"
        then
    then drop ;

\ Problem 5
\ Replacing 'do' with '?do' is the easiest solution. It is also possible with:
: stars ( n -- )
    dup 0= if drop else 0 do [char] * emit loop then ;

\ Problem 6
: within ( n low high -- flag )
    rot dup rot < rot rot <= and ;

\ Problem 7
: guess ( n m -- n |  )
    2dup over =
    if ." correct!" 2drop drop
    else over <
        if ." too large"
        else ." too small"
        then drop then ;

\ Problem 8
: speller ( n -- )
    dup 0= if ." zero" else
    dup < if ." minus " negate then
    dup 1 = if ." one" else
    dup 2 = if ." two" else
    dup 3 = if ." three" else
    dup 4 = if ." four" else
    ." out of range"
    then then then then then drop ;

\ Problem 9
: trap ( a low high -- a |  )
    3dup dup rot = rot rot = and if ." you got it!" 2drop drop else
    3dup swap 1+ swap within if ." between" else ." not between" then 2drop then ;

Chapter 5

The ideas in this chapter are probably the most dated. It is still interesting to see fixed-point arithmetics in this approach, but it is not really of practical use today. Anyway, the exercises are quite simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
\ Problem 1
: c5p1 ( a b c -- result ) */ negate ;

\ Problem 2
\ max max max .

\ Problem 4
: f>c ( f -- c ) 32 - 1000 1800 */ ;
: c>f ( c -- f ) 1800 1000 */ 32 + ;
: k>c ( k -- c ) 273 - ;
: c>k ( c -- k ) 273 + ;
: f>k ( f -- k ) f>c c>k ;
: k>f ( k -- f ) k>c c>f ;

\ Problem 3
\ 0 f>c .
\ 212 f>c .
\ -32 f>c .
\ 16 c>f .
\ 233 k>c .

Chapter 6

In this chapter things are finally getting challenging. We get a first glimpse of the instruction stack and also some non-trivial code examples to work with. I have adapted the Gforth versions of the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
( My own versions of compound and doubled. )
: r% ( total percent -- result ) 10 */ 5 + 10 / ;
: compount ( funds interest -- )
    21 1 do cr ." Year " i . ." Balance "
        >r dup r@ r% + dup . r> loop
    2drop ;
: doubled ( funds interest -- )
    over rot rot
    21 1 do cr ." Year " i . ." Balance "
        >r dup r@ r% + dup .
        2dup 2/ < if
             r> cr cr ." more than doubled in " i . ." years " leave
        then
    r> loop
    2drop drop ;

The problem set offers more involved challenges:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
\ Problem 1
\ (It's better to use '?do' here to handle the special case 0 stars!)
: stars ( n -- )
    0 ?do [char] * emit loop ;

\ Problem 2
: box ( width height -- )
    cr 0 do dup stars cr loop drop ;

\ Problem 3
: \stars ( n -- )
    cr 0 do i spaces 10 stars cr loop ;

\ Problem 4
: /stars ( n -- )
    cr 1- 0 swap do i spaces 10 stars cr -1 +loop ;

\ Problem 5
: /stars ( n -- )
    cr begin
        1- dup spaces 10 stars cr
    dup 0= until drop ;

\ Problem 6
: diamond-slice ( width #stars -- )
    swap over + spaces stars cr ;
: diamond ( height -- )
    dup 0 do dup i diamond-slice loop
    dup 1- 0 swap do dup i diamond-slice -1 +loop drop ;
: diamonds ( n -- )
    cr 0 ?do 10 diamond loop ;

\ Problem 7
: doubled-after ( funds interest -- )
    over rot rot
    0 1 do
        cr ." Year " i . ." Balance " >r dup r@ r% + dup .
        2dup 2/ < if
             r> cr cr ." more than doubled in " i . ." years " leave
        then
    r> loop
    2drop drop ;

\ Problem 8
: ** ( x n -- x^n )
    dup 0= if 2drop 1 else
        over swap begin 1- dup 0>
        while >r over * r>
        repeat drop swap drop
    then ;

Chapter 7

This chapter introduces some unusual concepts: The idea of a number formatter that is programmed with the full power of Forth! The first section about the binary representation of numbers is something most developers should be familiar with, but it is written very well and clear and thus deserves another read.

The code examples (needed for later) are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
\ Examples from chapter 7
hex
: .ph# ( d -- )
    <# # # # # 02d hold #s #> type ;
decimal
: .date ( d -- )
    <# # # # # [char] / hold # # [char] / hold # # #> type ;
: sextal ( b -- ) 6 base ! ;
: :00 # sextal # decimal [char] : hold ;
: sec ( s -- ) <# :00 :00 #s #> type ;
: .$ ( d -- )
    tuck dabs <# # # [char] . hold #s rot sign [char] $ hold #> type ;
: dr% ( dtotal percent -- dresult )
    10 m*/ 5 m+ 10 sm/rem nip 0 ;

The problems again involve the idea of fixed-point arithmetics. However, in this entire chapter there are notable deviations in the syntax for parsing numbers from this book when doing the problems with Gforth. The exercises are simple, but the fact that an overflow can occur in certain situations is something I was unaware of:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
\ Problem 1
\ Simplest solution: -1 1 rshift .
\ Requested solution with a loop:
: n-max ( -- )
    1 dup begin nip dup 1 lshift 1+ dup 0< until drop . ;

\ Problem 2
\ When the 'or' instruction is on its own: The flags may not be
\ canonical, i.e. different from -1 or 0. Thus, the flag bits would
\ not get treated correctly if one of them was the negation of the
\ other one. E.g. if art-loving was -2 and music-loving was 2 then
\ 'or' would correctly yield -2, but '+' would incorrectly yield
\ 0. When combined with the other 'and' things get even worse: An
\ addition may shift the flag bits and this could mess up the mask
\ that an 'and' relies on.

\ Problem 3
\ We need the function 'ms' for sleeping n milliseconds.
: rings ( -- )
    3 0 do ." BELL" cr 7 emit 1000 ms loop ;

\ Problem 4
\ a. Rewrite the definitions:
: df>c ( df -- dc ) 320. d- 10 18 m*/ ;
: dc>f ( dc -- df ) 18 10 m*/ 320. d+ ;
: dk>c ( dk -- dc ) 2730. d- ;
: dc>k ( dc -- dk ) 2730. d+ ;
: df>k ( df -- dk ) df>c dc>k ;
: dk>f ( dk -- df ) dk>c dc>f ;
\ b. Formatted print of a number:
: .deg ( d -- )
    tuck dabs <# # [char] . hold #s rot sign #> type ;
\ c. Example conversions:
\ 0. df>c .deg
\ 2120. df>c .deg
\ 200. df>c .deg
\ 160. dc>f .deg
\ -400. dc>f .deg
\ 1000. dk>c .deg
\ 1000. dk>f .deg
\ 2330. dk>c .deg
\ 2330. dk>f .deg

\ Problem 5
\ a. Routine that evaluates the quadratic equation:
: c7p5 ( x -- dresult )
    dup 20 m* rot dup m* 7 1 m*/ d+ 5 m+ ;
\ b. Maximum x that does not overflow the above equation:
\ 7x^2 + 20x + 5 <= 2^64-1
\ x <= sqrt(2^63/7-5/7+(10/7)^2)-10/7
\ Using Python:
\ from __future__ import division
\ import math
\ print(math.floor(math.sqrt(2**63/7-5/7+(10/7)**2)-10/7))
\ x <= 1147878292
\ This result can be verified using Gforth via:
\ 1147878292 c7p5 d. ( does not overflow )
\ 1147878293 c7p5 d. ( causes overflow )

\ Problem 6
decimal
: binary ( -- ) 2 base ! ;
: c7p6 ( -- )
    cr 17 0 do
        ." Decimal" decimal i 3 u.r 2 spaces
        ." Hex" hex i 3 u.r 2 spaces
        ." Binary" binary i 6 u.r cr
    loop decimal ;

\ Problem 7
\ It doesn't work on Gforth just like the other ways of parsing double
\ numbers. Maybe it works on some systems, but I really wonder how
\ come the standard has left such things open ...  on the other hand,
\ it could also simply mean that a word named '..' has been defined at
\ some point and, consequently, I don't learn much at all.

\ Problem 8
: ph-form # # # # [char] - hold ;
: c7p8 ( dph# -- )
    2dup 10000000. du< if
        <# ph-form #s #>
    else
        <# ph-form # # # [char] / hold #s #>
    then type ;

Chapter 8

The concept of arrays and memory management are well presented in this chapter. The code examples from this chapter are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
\ Examples from chapter 8:
0 constant reject
1 constant small
2 constant medium
3 constant large
4 constant extra-large
5 constant error
variable counts 5 cells allot

: reset ( -- )
    counts 6 cells erase ;
: counter ( counter# -- addr )
    cells counts + ;
: tally ( counter# -- )
    counter 1 swap +! ;
: category ( weight -- category )
    dup 18 < if reject      else
    dup 21 < if small       else
    dup 24 < if medium      else
    dup 27 < if large       else
    dup 30 < if extra-large else
                error
    then then then then then nip ;
: label ( category -- )
    case
        reject of ." reject " endof
        small  of ." small " endof
        medium of ." medium " endof
        large  of ." large " endof
        extra-large of ." extra-large " endof
        error of ." error " endof
    endcase ;
: eggsize ( size -- )
    category dup label tally ;
: report ( -- )
    page ." QUANTITY        SIZE " cr cr
    6 0 do i counter @ 5 u.r
        7 spaces
        i label cr
    loop ;

The highlight of the problem set is the TicTacToe game. For this one I have done a little more work and implemented error checking, alternate playing and verifying the victory conditions in Forth. Consequently, it grew a little longer, but it was an interesting exercise to write a non-trivial program in a stack-based language!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
\ Problem 1
variable pie# 0 pie# !
variable frozen-pie# 0 frozen-pie# !
: bake-pie ( -- ) 1 pie# +! ;
: eat-pie ( -- )
    pie# @ 0= if ." What pie?"
    else -1 pie# +! ." Thank you!" then ;
: freeze-pies ( -- )
    pie# dup @ frozen-pie# +! 0 swap ! ;

\ Problem 2
: .base ( -- )
    base @ dup decimal . base ! ;

\ Problem 3
variable places
: m. ( d -- )
    places @ 0= if d.
    else
        tuck dabs
        <# places @ 0 do # loop [char] . hold #s rot sign #>
        type
    then ;

\ Problem 4
0 constant red
1 constant blue
2 constant green
3 constant orange
variable pencil-case 4 cells allot

: pencils ( counter# -- addr )
    cells pencil-case + ;
: c8p4 ( -- )
    23 red pencils !
    15 blue pencils !
    12 green pencils !
    0 orange pencils ! ;

\ Problem 5
10 constant test#
variable plot-test test# cells allot

\ Requires 'random.fs' which exports the word 'random ( n -- 0..n-1)'.
s" random.fs" included
: c8p5-init ( -- )
    test# 0 do 71 random i cells plot-test + ! loop ;

: plot-line ( addr -- )
    @ 0 ?do [char] * emit loop ;
: plot ( addr size -- )
    cr 0 ?do i 1+ 3 u.r space dup i cells + plot-line cr loop drop ;
\ Test this using: c8p5-init plot-test test# plot

\ Problem 6
9 constant board-size
0 constant field-empty
1 constant field-X
255 constant field-O
variable board board-size allot
: tictactoe-start ( -- )
    board board-size erase ; tictactoe-start
: separator ( -- ) cr 11 0 do [char] - emit loop cr ;
: tictactoe-board-type ( -- )
    cr board-size 0 do
        board i + c@ space
        case
            field-empty of space endof
            field-X     of ." X" endof
            field-O     of ." O" endof
        endcase
        i 3 mod 2 = if
            i 1+ board-size <> if separator then
        else ."  |" then
    loop cr ;
: do-move ( field figure -- )
    swap 1- board + dup c@ if
        ." Error - field not empty!" 2drop
    else c!
    then ;
: X! ( field -- ) field-X do-move tictactoe-board-type ;
: O! ( field -- ) field-O do-move tictactoe-board-type ;

\ Addition: Check for end-game conditions and valid moves!
\ (I used local variables here, otherwise this would be a pain!)
0 constant column
1 constant row
2 constant diagonal1
3 constant diagonal2
variable next-mover
: tictactoe-start ( -- )
    board board-size erase 1 next-mover ! ; tictactoe-start
: ?correct-player ( current -- flag ) next-mover @ = ;
: next-player ( -- )
    next-mover @ field-X = if field-O else field-X then next-mover ! ;
: ?moves-left ( -- flag )
    0 board-size 0 do
        board i + c@ 0= if 1+ then
    loop 0<> ;
: get-next-field ( n m -- figure )
    + board + c@ ;
: ?check-line { n dir -- flag }
    dir case
        column    of 3 endof
        row       of 1 endof
        diagonal1 of 4 endof
        diagonal2 of 2 endof
    endcase
    n 0 get-next-field
    dup 0<> if
        over n get-next-field
        rot 2* n get-next-field
        over = rot rot = and if
            dir case
                column    of ." Column " n 1+ .  endof
                row       of ." Row " n 3 / 1+ . endof
                diagonal1 of ." Diagonal \"      endof
                diagonal2 of ." Diagonal /"      endof
            endcase ." completed." cr true
        else
            false
        then
    else
        2drop false
    then ;
: ?check-column ( n -- flag )
    column ?check-line ;
: ?check-row ( n -- flag )
    row ?check-line ;
: ?check-diagonal1 ( -- flag )
    0 diagonal1 ?check-line ;
: ?check-diagonal2 ( -- flag )
    2 diagonal2 ?check-line ;
: tictactoe-evaluate ( -- )
    ?moves-left if
        3 0 do
            i ?check-column
            i 3 * ?check-row
        loop
        ?check-diagonal1
        ?check-diagonal2
        or or or or or or or if
            ." GAME OVER -- congratulations!" cr tictactoe-start then
    else
        ." The game is a draw -- try harder next time!"
        cr tictactoe-start
    then ;
: do-move ( field figure -- )
    dup ?correct-player if
        swap 1- board + dup c@ if
            ." Error - field not empty!" 2drop
        else c! next-player then
    else ." Error - wrong player!" then ;
: X! ( field -- )
    field-X do-move tictactoe-board-type tictactoe-evaluate ;
: O! ( field -- )
    field-O do-move tictactoe-board-type tictactoe-evaluate ;

Chapter 9

This chapter goes very deep into the topics of memory, pointers and how the interpreter works. I found the presentation less clear than in previous chapters, though. It was not always apparent which words operated on which pointers and as these concepts are needed in later chapters it takes some trial and error to figure it out.

The problems only touched upon a small subset of the tools introduced in this chapter and were straightforward:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
\ Problem 1
: counts ( current n [word] -- total )
    ' rot rot 0 do over execute loop nip ;

\ Problem 2
\ here u.

\ Problem 3
\ pad here - u.
\ (On my Ubuntu Linux 10.04 LTS with Gforth 0.7.0 it was 104 bytes.)

\ Problem 4
\ These two are identical: `date .` and `' date >body .`.
\ For `base`, they are not the same -- it might not even work on some
\ systems as base is a user variable in a different location.

\ Problem 5
: word1 ( -- ) ." Hello, world." ;
: word2 ( -- ) 11 1 do r@ . loop ;
: word3 ( -- ) 50 0 do r@ 10 mod if else cr then [char] * emit loop ;
: nop ;
create code-array ' word1 , ' word2 , ' word3 , ' nop , ' nop , ' nop ,
: store-word ( n [word] -- )
    code-array swap cells + ' swap ! ;
: do-something ( n -- )
    assert( dup 0 > )
    assert( dup 7 < )
    1- code-array swap cells + @ execute cr ;

Chapter 10

This chapter was quite tough for me as the specification of strings is not consistent and things work different in Gforth. Furthermore, there is a bug with the >number word which expects 4 numbers on the stack, not just two as the documentation claims. I did not get this to work.

The examples from the chapter are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
\ Gforth version of non-portable string handling:
: "label"   ." Reject  Small   Medium  Large   Xtra lrgError   " ;
: label 8 * ['] "label" >body cell+ cell+ + 8 type space ;

\ Gforth version of portable string handling:
: "label"   c" Reject  Small   Medium  Large   Xtra lrgError   " ;
: label ( n -- ) 8 * "label" 1+ + 8 type  space ;
: label ( n -- ) 0 max 5 min label ;

( Form love letter )
: text ( delimiter -- )
    pad 258 bl fill word count pad swap move ;
create name 14 allot
create eyes 12 allot
create me   14 allot
: vitals
    [char] , text pad name 14 move
    [char] , text pad eyes 12 move
           1 text pad me   14 move ;
: letter   page
    ." Dear " name 14 -trailing type ." ,"
    cr ." I go to heaven whenever I see your deep "
    eyes 12 -trailing type space ." eyes. Can "
    cr ." you go to the movies Friday?"
    cr 30 spaces ." Love,"
    cr 30 spaces me 14 -trailing type
    cr ." P.S. Wear something " eyes 12 -trailing type
    space ." to show off those eyes!" ;

( Interactive session example )
: greet   cr ." What's your name?"
    tib 40 accept #tib ! 0 >in !
    1 text cr ." Hello, "
    pad 40 -trailing type ." , I speak Forth." ;

For the problem sets I have decided to deviate from the assignments and not use block-based file access at all. Instead, I looked up the words for file access in Gforth and solved the problems with standard files, instead. Consequently, I have skipped problem five:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
\ Problem 1
: "steve-jobs"
    c" Being the richest man in the cemetery doesn't matter to me ... Going to bed at night saying we've done something wonderful... that's what matters to me." ;
: setup-quote ( -- )
    "steve-jobs" count 3 block swap cmove ;
: change ( c1 c2 -- )
    1024 0 do
        2dup swap 3 block r@ + dup c@ rot = if c! else 2drop then
    loop 2drop ;
\ Testing can be done with: `3 block 1024 -trailing type`

\ Problem 2
\ Generate the fortunes with a loop of `fortune -n 64 -s >> fortunes.txt`
include random.fs
0 value fd-in
0 value fortune#
: "fortune-file" c" fortunes.txt" ;
: set-fortune# ( -- )
    "fortune-file" count r/o open-file throw to fd-in
    0 begin
        1+ pad 64 fd-in read-line throw nip 0=
    until
    to fortune# fd-in close-file throw ;
: fortune ( -- )
    set-fortune#
    "fortune-file" count r/o open-file throw to fd-in
    fortune# random begin
        1- dup pad 64 fd-in read-line throw
        rot 0<= swap 0= or
        dup if pad rot .s cr -trailing type cr else nip then
    until
    drop fd-in close-file throw ;

\ Problem 3
: "lunar-calendar"
    c" Rat Ox Tiger Rabbit Dragon Snake Horse Ram Monkey Cock Dog Boar " ;
: sword ( addr delimiter -- addr )
    begin
        swap 2dup c@ <>
    while
        1+ swap
    repeat 1+ nip ;
: skip-words ( addr n -- addr )
    0 ?do bl sword loop ;
: type-word ( addr -- )
    begin
        dup c@ dup emit bl <>
    while
        1+
    repeat drop ;
: .animal ( n -- )
    0 max 11 min "lunar-calendar" 1+ swap skip-words type-word ;
: juneeshee ( year -- )
    4 - 12 mod .animal ;
\ There is a serious bug in the `>number` macro which differs from
\ it's documentation in
\ http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Line-input-and-conversion.html#Line-input-and-conversion
: juneeshee ( -- )
    cr ." What is your year of birth? (YYYY) "
    4 0 do key dup emit pad r@ + c! loop
    base @ 10 base ! 0 0 pad 4 >number 2drop drop swap base !
    cr ." Your animal type is " juneeshee ;

\ Problem 4
\ I rather take the vitals from a file
\ `echo Alice,blue,Fred >> vitals.txt`
: "vitals-file" c" vitals.txt" ;
: .word ( addr -- addr )
    begin dup c@ dup dup [char] , <> swap bl <> and
    while emit 1+
    repeat drop 1+ ;
: letter ( address -- )
    page
    ." Dear " .word ." ,"
    cr ." I go to heaven whenever I see your deep "
    dup eyes 12 cmove .word space ." eyes. Can "
    cr ." you go to the movies Friday?"
    cr 30 spaces ." Love,"
    cr 30 spaces .word
    cr ." P.S. Wear something " eyes .word
    space ." to show off those eyes!" 2drop ;
: letters ( -- )
    "vitals-file" count r/o open-file throw to fd-in
    begin
        pad 1024 bl fill pad 1024 fd-in read-line throw
        swap 0> if pad letter key drop then
    0= until
    fd-in close-file throw ;

\ Problem 5
\ I'll skip this one as it uses blocks for disk access and I have
\ already written a couple of disk-access routines now that rely on
\ regular files (which seems to be more complicated).

Chapter 11

This is the last chapter with exercises. They require a thorough understanding of the concepts of compile-time vs. run-time and how compilation works. This is explained quite well, although the difference between a “defining word” and a “compiling word” is has not been explained well enough in my opinion.

The examples from this chapter are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
\ Examples from chapter 11:
: characters ( # -- )   create dup , allot does> dup cell+ swap @ ;
: string ( # -- )   create dup , allot
                    does>  2dup @ u< 0= abort" Range error " + cell+ ;
: erased ( # -- )   here over erase allot ;
: 0string ( # -- )   create erased does> + ;
: array ( #rows #cols -- )   create dup , * allot
                             does>  ( member: row col -- addr )
                               rot over @ * + + cell+ ;

\ Shapes, using a defining word
decimal
: star   [char] * emit ;
: .row   cr 8 0 do
        dup 128 and if star else space then
        1 lshift
    loop drop ;
: shape   create 8 0 do c, loop
          does>  dup 7 + do i c@ .row -1 +loop cr ;
hex 18 18 3c 5a 99 24 24 24 shape man
    81 42 24 18 18 24 24 81 shape equis
    aa aa fe fe 38 38 38 fe shape castle        
decimal

The problems themselves are not difficult, but I had problems figuring out the last one, because it took me some experimenting with the user variables from the earlier chapter before I understood how I can access the rest of the command line repeatedly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
\ Problem 1
: loaded-by ( addr n [word] -- )
    create 2, does> 2@ included ;

\ Problem 2
: based. ( n [word] -- )
    create , does> @ base @ swap base ! swap . base ! ;

\ Problem 3
: plural ( addr [word] -- )
    create , does> @ swap 0 ?do dup execute loop drop ;

\ Problem 4
: tourne postpone do ; immediate
: retourne postpone loop ; immediate
: tourne-francaise 10 0 tourne i . cr retourne ;

\ Problem 5
: loops ( n [instr] -- )
    >in @ swap 0 ?do
        dup >in ! 0 word count evaluate
    loop drop ;

Summary

I find it compelling to learn new concepts of programming. Stack-oriented languages have become unfashionable in recent years, although many elementary programming techniques are stack-oriented. The Postscript programming language is probably the language where most lines of codes have ever been written as every page rendered on a Postscript-printer is a program in that stack-oriented language, see e.g. this Postscript Tutorial for an introduction.

There are further resources on Forth online. There is the Gforth tutorial which explains the specifics of the GNU implementation. And there is a Python implementation that teaches how to implement a Forth interpreter and compiler from first principles.

Which books on older programming languages have you read recently? What is the most important lesson you learned from them?