Example Programs

Every program below runs as shown: tools/vios_manual.py examples evaluates each Forth block of this chapter in order on a host build of VIOS, so the programs are checked whenever the manual is rebuilt. Type them at the console, paste them into the Workspace and use do it (Ctrl+D), or save them in a module with the Browser so they survive a reboot (see Modules and Persistence).

Classics: FizzBuzz, a prime sieve, recursion

Plain Forth: loops, conditionals, data space and recursion.

: FIZZBUZZ ( n -- )
  1+ 1 ?DO
    I 15 MOD 0= IF ." FizzBuzz "
    ELSE I 3 MOD 0= IF ." Fizz "
    ELSE I 5 MOD 0= IF ." Buzz "
    ELSE I . THEN THEN THEN
  LOOP CR ;
20 FIZZBUZZ
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz

The sieve of Eratosthenes keeps one flag byte per number in a buffer made with CREATE and ALLOT. The inner loop steps by the prime with +LOOP; J is the outer loop's index.

200 CONSTANT LIMIT
CREATE FLAGS LIMIT ALLOT
: SIEVE ( -- )
  FLAGS LIMIT 1 FILL
  LIMIT 2 DO
    FLAGS I + C@ IF
      I DUP * LIMIT < IF
        LIMIT I DUP * DO 0 FLAGS I + C! J +LOOP
      THEN
    THEN
  LOOP ;
: PRIMES ( -- ) SIEVE LIMIT 2 DO FLAGS I + C@ IF I . THEN LOOP CR ;
PRIMES

RECURSE calls the word being defined. Cells are 24 bits on the board, so 10 FACTORIAL (3628800) is the largest factorial that fits; 11 FACTORIAL wraps around.

: FACTORIAL ( n -- n! ) DUP 2 < IF DROP 1 ELSE DUP 1- RECURSE * THEN ;
: FIB ( n -- fib ) DUP 2 < IF EXIT THEN DUP 1- RECURSE SWAP 2 - RECURSE + ;
10 FACTORIAL .  20 FIB . CR
3628800 6765

A pipeline filter: cat | nl

This program is the Forth version of the Unix command cat poem.txt | nl. One task reads a file and prints it; a second task numbers the lines it receives on its KEY input. PIPELINE joins the two with a pipe, so neither word knows about the other: CAT-POEM just prints, NL just reads KEY until it returns -1 (end of input).

\ Build a small file to read back, line by line.
CREATE POEM 128 ALLOT   VARIABLE POEM-LEN
: +BYTE ( c -- ) POEM POEM-LEN @ + C!  1 POEM-LEN +! ;
: +LINE ( addr u -- ) 0 DO DUP I + C@ +BYTE LOOP DROP  13 +BYTE 10 +BYTE ;
S" Roses are red," +LINE
S" Violets are blue," +LINE
S" Forth on the Agon" +LINE
S" Is waiting for you." +LINE
S" /VIOS/POEM.TXT" POEM POEM-LEN @ FILE-WRITE DROP

\ Producer: copy a file to standard output.
CREATE CAT-BUF 64 ALLOT
VARIABLE CAT-H
: CAT-POEM ( -- )
  S" /VIOS/POEM.TXT" 1 OPEN CAT-H !
  BEGIN CAT-BUF 64 CAT-H @ READ  DUP 0 > WHILE  CAT-BUF SWAP TYPE  REPEAT
  DROP  CAT-H @ CLOSE DROP ;

\ Filter: number each line of standard input.
VARIABLE LINE#   VARIABLE FRESH
: NL ( -- )
  1 LINE# !  TRUE FRESH !
  BEGIN KEY DUP -1 <> WHILE
    FRESH @ IF  LINE# @ .  1 LINE# +!  FALSE FRESH !  THEN
    DUP EMIT  10 = IF TRUE FRESH ! THEN
  REPEAT DROP ;

' CAT-POEM ' NL PIPELINE 2DROP
1 Roses are red,
2 Violets are blue,
3 Forth on the Agon
4 Is waiting for you.

The numbered lines appear on the console as soon as it is idle. Both tasks end on their own: when CAT-POEM returns, VIOS closes the pipe's write end that the task owned, NL's KEY returns -1 and NL returns too. Any word that reads KEY until -1 and prints its result is a filter you can put on the right-hand side of PIPELINE; any word that prints can go on the left. The poem is built in memory first because a S" string ends at the end of its line.

A background clock

A task that counts seconds with MS and a word that shows the time. The task sleeps between ticks, so it costs almost nothing, and the console stays free while it runs. Because the task only updates a variable, it never prints in the middle of your typing.

VARIABLE SECONDS
: CLOCK ( -- ) BEGIN 1000 MS  1 SECONDS +!  AGAIN ;
: .2 ( n -- ) 10 /MOD  48 + EMIT  48 + EMIT ;
: .TIME ( -- ) SECONDS @ 60 /MOD  .2 ." :" .2 CR ;

' CLOCK SPAWN CONSTANT CLOCK-ID
2100 MS
.TIME
CLOCK-ID TASK-STATE .
CLOCK-ID TASK-KILL
00:02
2

TASK-STATE shows 2 (sleeping): the clock spends its life in MS. In a real program you would not kill it; put CLOCK in a module and name a word AUTORUN that spawns it, and the clock starts at every boot (see Tasks and Preemption):

: AUTORUN ( -- ) ' CLOCK SPAWN DROP ;

Two details: .2 prints a number from 0 to 99 as two digits with no trailing space (48 is the character code of 0), and 60 /MOD leaves the remainder (seconds) below the quotient (minutes).

A note-taker

NOTE adds a line to /VIOS/NOTES.TXT, NOTES shows them all, and FORGET-NOTES deletes the file. SD files opened for writing are always truncated, so to append, the program reads the whole file with FILE-READ, adds the line in memory and writes it back with FILE-WRITE. That limits the notebook to 4096 bytes, which is plenty for notes.

CREATE NOTE-BUF 4096 ALLOT
VARIABLE NOTE-LEN
: NOTES-FILE ( -- addr u ) S" /VIOS/NOTES.TXT" ;
: READ-NOTES ( -- )
  NOTES-FILE NOTE-BUF 4096 FILE-READ  IF DROP 0 THEN  NOTE-LEN ! ;
: WRITE-NOTES ( -- )
  NOTES-FILE NOTE-BUF NOTE-LEN @ FILE-WRITE  IF ." note not saved: " FS-ERROR THEN ;
: ADD-BYTE ( c -- ) NOTE-BUF NOTE-LEN @ + C!  1 NOTE-LEN +! ;
: NOTE ( addr u -- )
  READ-NOTES
  DUP NOTE-LEN @ + 4094 > IF 2DROP ." notebook full" CR EXIT THEN
  >R  NOTE-BUF NOTE-LEN @ +  R@ MOVE  R> NOTE-LEN +!
  13 ADD-BYTE  10 ADD-BYTE  WRITE-NOTES ;
: NOTES ( -- ) READ-NOTES  NOTE-BUF NOTE-LEN @ TYPE ;
: FORGET-NOTES ( -- ) NOTES-FILE FILE-REMOVE DROP ;

S" buy milk" NOTE
S" back up the SD card" NOTE
NOTES
FORGET-NOTES
buy milk
back up the SD card

A missing file is not an error here: FILE-READ returns length 0 and ior 1, and READ-NOTES turns that into an empty notebook. The notes survive a restart because they are a file; the words do not, unless you keep them in a module or in /VIOS/USER.FTH.

A jukebox

Three tunes stored on the ESP and a task that plays them one after another. The tunes are played by the ESP; the task only starts each one and then polls SOUND? every 50 ms until the voices are quiet.

: LOAD-TUNES ( -- )
  S" t120 l4 eefg gfed ccde e.d8d2 eefg gfed ccde d.c8c2" 0 TUNE
  S" t100 l8 o4 ccggaag4 ffeeddc4" 1 TUNE
  S" t180 w1 l16 o5 cegcegceg>c4" 2 TUNE ;
: WAIT-QUIET ( -- ) BEGIN 50 MS SOUND? 0= UNTIL ;
: PLAYLIST ( -- ) 3 0 DO  I 0 PLAY  WAIT-QUIET  300 MS  LOOP ;

LOAD-TUNES
' PLAYLIST SPAWN CONSTANT DJ
500 MS
DJ TASK-KILL  -1 STOP

PLAYLIST runs as a task, so the console stays usable while the music plays; DJ TASK-KILL -1 STOP ends both the playlist and the current tune (killing the task alone would let the current tune finish on the ESP). Remove the last line to hear all three. To play one tune in a loop instead, end its text with *.

A counter window

A window with a number from 0 to 100, a progress bar that follows it and three buttons. It shows the usual shape of a GUI program in VIOS: one word builds the window inside UI-BEGIN ... UI-COMMIT, one word reacts to a single event, and a loop takes the waiting events every 20 ms until the window is closed.

  • Window 2 shows surface 1; widget ids 10 to 16 are used (any ids below 40 are free for programs).
  • The root box (10) stacks a label, the progress bar and a row box (13) with one empty row between them; the row box lays out the three buttons side by side.
  • N>S turns a number into text, since the label needs a string.
  • CHANDLE gets one decoded event ( kind id arg ) from UI-NEXT: a button press (UI-EV-ACTIVATE) changes the value; a close request for the window (UI-EV-WIN whose low byte is 2, sent for Ctrl+W or Alt+F3) ends the loop.
  • CSTEP handles every event that is waiting and returns; COUNTER repeats it.
\ Counter: a value from 0 to 100, a progress bar and three buttons.
2 CONSTANT CWIN   1 CONSTANT CSURF
VARIABLE CVAL   VARIABLE CDONE
CREATE NBUF 8 ALLOT   VARIABLE NLEN
: N>S ( u -- addr len )            \ unsigned decimal into NBUF
  8 NLEN !
  BEGIN 10 /MOD SWAP 48 +  -1 NLEN +!  NBUF NLEN @ + C!  DUP 0= UNTIL DROP
  NBUF NLEN @ +  8 NLEN @ - ;
: CSHOW ( -- )  CVAL @ N>S 11 UI-WTEXT   12 CVAL @ UI-WVALUE ;
: COUNTER-OPEN ( -- )
  UI-BEGIN
  CSURF 30 5 48 UI-SURFACE
  CWIN CSURF 200 64 30 5 UI-VISIBLE UI-DECOR OR UI-SHADOW OR UI-WINDOW
  S" Counter" CWIN UI-TITLE
  10 CWIN UI-BOX UI-NONE 0 0 0 0 UI-WF-FILL UI-WIDGET   10 UI-COLUMN 1 UI-LAYOUT
  11 CWIN UI-LABEL 10 0 0 0 0 0 UI-WIDGET      11 1 0 UI-WSIZE
  12 CWIN UI-PROGRESS 10 0 0 0 0 0 UI-WIDGET   12 1 0 UI-WSIZE
  13 CWIN UI-BOX 10 0 0 0 0 0 UI-WIDGET        13 1 0 UI-WSIZE   13 UI-ROW 1 UI-LAYOUT
  14 CWIN UI-BUTTON 13 0 0 0 0 0 UI-WIDGET     S" -10" 14 UI-WTEXT
  15 CWIN UI-BUTTON 13 0 0 0 0 0 UI-WIDGET     S" +10" 15 UI-WTEXT
  16 CWIN UI-BUTTON 13 0 0 0 0 0 UI-WIDGET     S" &Reset" 16 UI-WTEXT
  0 CVAL !  CSHOW
  CWIN UI-FOCUS
  UI-COMMIT ;
: CBUMP ( n -- )  CVAL @ +  0 MAX 100 MIN  CVAL !  CSHOW ;
: CHANDLE ( kind id arg -- )
  ROT DUP UI-EV-ACTIVATE = IF
    DROP DROP                                    ( id )
    DUP 14 = IF -10 CBUMP THEN
    DUP 15 = IF 10 CBUMP THEN
    16 = IF CVAL @ NEGATE CBUMP THEN  EXIT
  THEN
  UI-EV-WIN = IF                                 ( id arg )
    255 AND 2 =  SWAP CWIN =  AND IF TRUE CDONE ! THEN  EXIT
  THEN
  2DROP ;
: CSTEP ( -- )  BEGIN UI-NEXT WHILE CHANDLE REPEAT ;
: COUNTER ( -- )
  COUNTER-OPEN  FALSE CDONE !
  BEGIN CSTEP 20 MS  CDONE @ UNTIL
  CWIN UI-CLOSE ;

Start it from the console. Tab moves between the buttons, Space or Enter presses one, Ctrl+W closes the window and returns to the prompt:

COUNTER
The counter window after pressing +10 three times
The counter window after pressing +10 three times

A settings form with a menu and an animation

A small form that changes the GUI itself: two radio buttons choose the style, a check box sets the window snap, and choosing a row of the list slides the window to the left, the centre or the right with a tween. It also defines two commands: Quit with the accelerator Ctrl+Q (listed on the Turbo Vision status line because it has the UI-STATUS flag) and Defaults, both in a pane menu opened with Shift+F10.

  • The radios share the parent box 22, so they form one group: selecting one clears the other, and each selection sends UI-EV-CHANGE with value 1.
  • The check box sends UI-EV-CHANGE with its new value.
  • The list sends UI-EV-SELECT with the selected row; FMOVE animates the window's x from where it is to the new place with UI-OUT easing.
  • Commands arrive as UI-EV-COMMAND with the command id in arg. Ctrl+Q works whichever window has the focus, because accelerators are global.
  • Instead of taking the event apart on the stack, EVENT? stores it in three variables and IS? compares kind and id.
  • Commands outlive windows, so SETTINGS disables its two commands when it ends; otherwise Ctrl+Q would keep sending events that nobody reads.
\ Settings: radios switch the style, a check box sets the snap, a list moves the window.
3 CONSTANT FWIN   2 CONSTANT FSURF
100 CONSTANT CMD-QUIT   101 CONSTANT CMD-DEFAULTS
VARIABLE FDONE   VARIABLE FX
VARIABLE KIND   VARIABLE WHO   VARIABLE ARG
: EVENT? ( -- flag )  UI-NEXT DUP IF >R ARG ! WHO ! KIND ! R> THEN ;
: IS? ( kind id -- flag )  WHO @ =  SWAP KIND @ =  AND ;
: FCOMMANDS ( -- )
  S" &Quit" CMD-QUIT UI-ENABLED UI-STATUS OR 0 UI-CTRL 113 UI-COMMAND
  S" &Defaults" CMD-DEFAULTS UI-ENABLED 0 0 0 UI-COMMAND
  CMD-DEFAULTS UI-SEPARATOR CMD-QUIT 3 S" Settings" 1 UI-MENU
  FWIN 1 UI-POPUP ;
: FDEFAULTS ( -- )  23 1 UI-WVALUE 0 UI-STYLE  25 1 UI-WVALUE 8 UI-SNAP ;
: FORM-OPEN ( -- )
  UI-BEGIN
  FSURF 34 10 48 UI-SURFACE
  288 FX !
  FWIN FSURF 288 48 34 10 UI-VISIBLE UI-DECOR OR UI-SHADOW OR UI-WINDOW
  S" Settings" FWIN UI-TITLE
  20 FWIN UI-BOX UI-NONE 0 0 0 0 UI-WF-FILL UI-WIDGET   20 UI-COLUMN 0 UI-LAYOUT
  21 FWIN UI-LABEL 20 0 0 0 0 0 UI-WIDGET   21 1 0 UI-WSIZE   S" Style" 21 UI-WTEXT
  22 FWIN UI-BOX 20 0 0 0 0 0 UI-WIDGET     22 2 0 UI-WSIZE   22 UI-COLUMN 0 UI-LAYOUT
  23 FWIN UI-RADIO 22 0 0 0 0 0 UI-WIDGET   S" S&malltalk" 23 UI-WTEXT
  24 FWIN UI-RADIO 22 0 0 0 0 0 UI-WIDGET   S" &Turbo Vision" 24 UI-WTEXT
  25 FWIN UI-CHECK 20 0 0 0 0 0 UI-WIDGET   25 1 0 UI-WSIZE   S" Snap windows to &8 px" 25 UI-WTEXT
  26 FWIN UI-LABEL 20 0 0 0 0 0 UI-WIDGET   26 1 0 UI-WSIZE   S" Position" 26 UI-WTEXT
  27 FWIN UI-LIST 20 0 0 0 0 0 UI-WIDGET    27 0 1 UI-WSIZE
  27 3 UI-ITEMS  S" Left" 27 0 UI-ITEM  S" Centre" 27 1 UI-ITEM  S" Right" 27 2 UI-ITEM  27 2 UI-WVALUE
  28 FWIN UI-BOX 20 0 0 0 0 0 UI-WIDGET     28 1 0 UI-WSIZE   28 UI-ROW 2 UI-LAYOUT
  29 FWIN UI-BUTTON 28 0 0 0 0 0 UI-WIDGET  S" &Close" 29 UI-WTEXT
  23 1 UI-WVALUE  25 1 UI-WVALUE
  FCOMMANDS
  FWIN UI-FOCUS
  UI-COMMIT ;
: FMOVE ( index -- )               \ slide the window to column 2, 19 or 36
  136 * 16 +  >R
  UI-TWEEN-WINDOW FWIN UI-X  FX @ R@  400 UI-OUT UI-ANIMATE
  R> FX ! ;
: FHANDLE ( -- )
  UI-EV-CHANGE 23 IS? IF 0 UI-STYLE THEN
  UI-EV-CHANGE 24 IS? IF 1 UI-STYLE THEN
  UI-EV-CHANGE 25 IS? IF ARG @ IF 8 ELSE 1 THEN UI-SNAP THEN
  UI-EV-SELECT 27 IS? IF ARG @ FMOVE THEN
  UI-EV-ACTIVATE 29 IS? IF TRUE FDONE ! THEN
  KIND @ UI-EV-COMMAND = IF
    ARG @ CMD-QUIT = IF TRUE FDONE ! THEN
    ARG @ CMD-DEFAULTS = IF FDEFAULTS THEN
  THEN
  UI-EV-WIN FWIN IS?  ARG @ 255 AND 2 =  AND IF TRUE FDONE ! THEN ;
: FSTEP ( -- )  BEGIN EVENT? WHILE FHANDLE REPEAT ;
: SETTINGS ( -- )
  FORM-OPEN  FALSE FDONE !
  BEGIN FSTEP 20 MS  FDONE @ UNTIL
  CMD-QUIT 0 UI-STATE  CMD-DEFAULTS 0 UI-STATE      \ disable Ctrl+Q again
  FWIN UI-CLOSE ;
SETTINGS
The settings form after switching to Turbo Vision
The settings form after switching to Turbo Vision

Both programs leave the window ids, surfaces and widget ids of the other untouched, so you can load them together; run one loop at a time.