Header Bar

Rebol Quick Start

Basic Coding Structures, Concepts, and Apps, Demonstrated Using R2
By: Nick Antonaccio
Updated 12-20-2015
See these short examples for an even quicker intro to Rebol code
Ask questions at http://rebolforum.com

Contents:

1. What is Rebol?
2. Installing and Using Rebol
2.1 Using the Rebol Console
2.2 Using Rebol's Built In Code Editor
3. Rebol Fundamentals
3.1 Functions
3.2 Data Persistence - Rebol Requires No Database System
3.3 Variables
3.4 Conditional Evaluations
3.5 Using Text Strings and Concatenation
3.6 Lists
3.7 Loops
3.8 User Interfaces
3.9 User Created Functions
3.10 Handling Errors
3.11 The rebol.r Startup File
4. Some Rote GUI, File, and Data List Examples in Rebol
4.1 Basic GUI Examples
4.2 Some GUI Examples Using Lists and Loops
4.3 Building a Basic CRUD (create, read, update, delete) Contacts App
5. A Few More Complete App Examples
5.1 Tip Calculator
5.2 Days Between 2 Dates
5.3 Image Effects
5.4 Send Emails
5.5 Tile Game
5.6 Generic Calculator
5.7 Coin Flip
5.8 Additional Example Apps to Study
6. Graphics
6.1 Rebol "Draw" Basics
6.2 A Few Short Paint Apps
7. More About Series - Adding More Features to the Contacts App
7.1 Built in Help
8. A Few More Short Apps Using Series and Loop Constructs
8.1 Image Slideshow
8.2 Bar Chart
8.3 Paypal Report
8.4 Tric Trac Game
8.5 Shopping List and To-Do List
8.6 Calendar
8.7 Pig Latin Generator
8.8 Marching Alien Logo Army
8.9 Catch Game
8.10 Guitar Chord and Scale Diagrammer
8.11 Additional Series Apps to Study
9. Creating Web and Mobile Applications using Rebol CGI
9.1 An HTML Crash Course
9.2 A Standard CGI Template to Memorize
10. Example CGI Applications
10.1 Generic CGI App, With HTML Form
10.2 CGI Photo Album
10.3 CGI Text Chat
10.4 A Generic Drop Down List Application
10.5 jsLinb and Sigma IDE
10.6 Summary
11. Why Rebol? Dialects and Other Uniquely Productive Features
11.1 Basic Language Features
11.2 Dialects
11.3 Remarkable Capability, Lightweight Tooling and Reduced Code Complexity
12. Learning More

1. What is Rebol?

Rebol ("Relative Expression Based Object Language") aims to be the most productive, readable, and concise programming language available. Its main goal is to reduce code complexity and to remove the bloated infrastructure found in typical software development stacks. Rebol is much simpler to use and dramatically more compact than even tools like Python, Ruby, Lua, Clojure, Javascript, Visual Basic, etc. Various full distributions of the Rebol interpreter run on Windows, Mac, Linux, Android, Raspberry Pi, and more than 40 legacy platforms. Typical distributions which include graphics, GUI, network, sound, database, math, compression, email, CGI, security, and other features weigh in at less than 1 megabyte. Core language distributions are much smaller. Rebol has been used in a wide variety of commercial production environments for more than 16 years. The newest versions of Rebol are completely free and open source (Apache license).

This tutorial explains everything needed to create 20+ interesting example applications with Rebol. The first section explains basic Rebol syntax, code constructs, and how to use the Rebol interpreter. The second half of the tutorial explains exactly how the code of each example application works, in line-by-line detail. It takes less than an hour to complete (~55 printed pages), and requires absolutely no previous programming experience. By the end of the text, you'll have a usable working knowledge of Rebol - enough to create many varieties of useful apps (don't worry, though, Rebol is much deeper than the topics covered here).

2. Installing and Using Rebol

Download and install REBOL/View (not REBOL/Core) from http://www.rebol.com/download-view.html.

You can use any text editor (Windows Notepad, Notepad++, Textmate, Emacs, etc.) to write code.

All Rebol programs must begin with the header "REBOL []". Save the following code to a text file with a name ending in .r (i.e., myapp.r):

REBOL []
alert "Hello World"
print "Welcome to the Console"
; This is a comment, a human readable note ignored by Rebol
halt

If you've installed the Rebol interpreter, any code file with the .r extension will run just like an executable program. For example, in Windows, you could save the program above to your desktop as "myapp.r", click the myapp.r icon with your mouse, and it will run. Rebol programs work the same way on every operating system.

2.1 Using the Rebol Console

You can paste snippets of code directly into the Rebol interpreter console:

  1. Click the Rebol program icon (in Windows, it's found by default on the desktop, or in Start -> Programs -> Rebol)
  2. Click the "Console" button in the Rebol program.
  3. Type or paste your code at the flashing prompt.

When you paste code into the console, there's no need to include the REBOL[] header. A one-liner can be pasted into the console like this:

alert "Hello World"

You can run saved code files by typing "do" in the Rebol console, followed by the path of the file. Use the percent sign to refer to the file system of your local device. You can also load scripts from other locations and sources:

do %myapp.r
do http://site.com/myapp.r
do ftp://user:pass@site.com/public_html/myapp.r
do read clipboard://

DO THIS: Click the Rebol program icon. On the main Rebol "Viewtop" screen, click "User" -> uncheck "Open Desktop on Startup" -> Click "Save", and Rebol will automatically open to the console every time it starts.

2.2 Using Rebol's Built In Code Editor

Type the following code into the Rebol console, and a built-in mini text editor program will pop up:

editor none

You can use this program to edit code (or any other text files). Press the [F5] key on your keyboard, and the code file you're editing will be saved and executed by the Rebol interpreter. This has the same effect as saving your script and clicking its icon with the mouse.

Most of the simple code examples in this text can be pasted directly into the Rebol console. Scripts which accept user input, or which otherwise wait for human interactivity, are generally best saved to a file and executed with 'do, or by clicking the saved file's icon, pressing the F5 key in the Rebol editor, etc.

3. Rebol Fundamentals

3.1 Functions

Functions are words which perform actions upon data "arguments" (or "parameters"). The 'print function prints some text to the Rebol console. In the following code, 'print is the function, "Hello World" is the argument. Try pasting this example into the Rebol console:

print "Hello World"

The 'alert function pops up a message dialogue displaying its text argument:

alert "Hello World"

The 'halt function doesn't require any argument. It just stops the program and displays the console:

halt

The 'request-text function pops up a dialog to fetch text input from the user:

request-text

In Rebol, some functions have optional "refinements", which allow the function to accept a variety of potentially useful parameters. Here are some refinements of the 'request-text function. Try them in the console:

request-text/title "Name:"
request-text/title/default "Password:" "Secret"

3.1.1 Return Values

The 'now function returns, or outputs, the current date and time:

now
now/date
now/time
now/time/precise

When working in the console, the return values of functions are automatically printed. When working in a script file, you can print the return value of a function to the console:

REBOL[]       ; header, because this will be run from a script file
print now
halt          ; this stops the interpreter from closing after printing

This example uses the 'alert function to display the output of the 'request-text function, right back to the user:

alert request-text

Note that before the 'alert function above can complete its operation, a value first needs to be returned from the 'request-text function. Because of that requirement, when this code runs, the alert function is held up temporarily, and the first thing the user sees is the 'request-text popup.

Using the output value of one function as the input argument of another function is a routine way of composing code in Rebol.

3.2 Data Persistence - Rebol Requires No Database System

To save text data to a hard drive, thumb drive, or other storage medium, use the 'write function. The 'write function takes two arguments: a file location, and the data to be written to that file. Local file names are preceded by the percent symbol (%):

write %mydata "This is some text that I want to save for later."

To retrieve the text later, use the 'read function. You can use the 'print function to print formatted data read from a file, or the 'probe function to view the raw data without any formatting:

print read %mydata

probe read %mydata

You can read and write to servers at a network/Internet URL, in the same way you read and write to local drives on your computer:

write ftp://user:pass@site.com/www/mydata.txt "text saved to a server"

read http://site.com/mydata.txt

To read and write binary file data like images, sounds, videos, etc., use the /binary refinement:

read/binary http://rebol.com/bay.jpg

To store complex structured data types, in any format which is natively understood by Rebol, use the 'save function:

save %mycontacts ["John" "Bill" "Jane" "Ron" "Sue"]

save/png %myimage.png logo.gif

To load saved structured data, use the "load" function:

probe load %mycontacts

editor load %myimage.png

editor load http://rebol.com/bay.jpg

editor load %/c/WINDOWS/Media/tada.wav

You'll see that Rebol provides all the required abilities to search, sort, compare, combine, add, remove, change, load, save, and otherwise manipulate persistent data. Together, all these features provide the same capabilities as database systems such as MySQL, SQLite, etc., with a shorter and more consistent learning curve, and more fine grained control of data processing algorithms, all in a tiny, 100% portable tool set. The functions learned, and the set of skills used to manage persistent data structures in Rebol are also used to tackle many other sorts of problem domains in the language (access to network socket connections, email accounts, graphic displays, console and IO interactions, etc.). This single set of skills is much more broadly applicable in Rebol, than for example SQL is in other languages (SQL is a domain specific language which can only be applied to processing information in database operations). This consistently applied concept is a basic feature which helps Rebol maintain simplicity. The issue of managing concurrent access to data by multiple client users is easily solved in Rebol.

3.3 Variables

Variables are word labels (some series of characters) assigned to represent data in a program. In Rebol, word labels are created with the colon symbol. Try pasting these examples into the Rebol console:

name: "John"
alert name

Variables can be changed to hold different values (the data they refer to is variable). The code above alerts "John". The code below alerts "Bill":

name: "Bill"
alert name

Note that Rebol is case insensitive. The interpreter treats lower case and capital letters in identifiers the same:

name: "George"
print name
print Name
print NAME
print NaMe

You can assign a variable word label to text returned by requestor functions. This code alerts whatever name the user types in:

name: request-text/title "Name:"
alert name

You can assign a label to data read from a file:

data: read %mydata
alert data

You can assign a label to any block of code, and evaluate (run) that code with the 'do function:

mycode: [print "Hello" halt]
do mycode

3.4 Conditional Evaluations

Most programs make use of "if/then" evaluations: if (this) is true [do this]:

if now/time = 8:00am [alert "Time to wake up!"]
if account < $0 [alert "Your account is overdrawn."]

"Either" is like if/else evaluations in other languages: if (this) is true [do this] [otherwise do this]. Notice that each separate action block is indented, so that you can clearly see two 2 differentiated actions:

pass: request-text/title "Enter Password:"
either pass = "secret" [
    alert "Welcome back."
] [
    alert "Incorrect password."
]

"Case" can be used to choose between a variety of actions, depending on the situation:

name: "john"
case [
    find name "a" [alert {Your name contains the letter "a"}]
    find name "e" [alert {Your name contains the letter "e"}]
    find name "i" [alert {Your name contains the letter "i"}]
    find name "o" [alert {Your name contains the letter "o"}]
    find name "u" [alert {Your name contains the letter "u"}]
    true [alert {Your name doesn't contain any vowels!}]
]

There are many other conditional evaluation structures in Rebol, but these are enough to handle most situations.

3.5 Using Text Strings and Concatenation

{Curly braces} can be used instead of quotes, to enclose strings of text which contain quote characters, or multi-line strings:

alert {She said "Hi"}

print {
    line 1
    line 2
    line 3
}

The 'trim function provides a number of refinements which are helpful in removing white space from strings:

print trim {
    line 1
    line 2
    line 3
}

print trim/all {
    line 1
    line 2
    line 3
}

The "^/" character is used to represent a carriage return:

print "line 1^/line 2^/line 3"

The "prin" function is used to print text without a carriage return:

prin "no carriage return ... "
prin "you can add a carriage return manually ... "
prin newline                        ; 'newline is the same as "^/"

"Concatenation" is the process of joining together multiple pieces of data. Rebol uses the 'rejoin function to concatenate text:

alert rejoin ["Current Date and Time: " now]

Most programs make use of rejoined values which are represented by variable labels, typically obtained from user input, resulting from conditional operations, resulting from various potential program states, or other data items which change dynamically during the operation of the program. Notice that rejoined items can be placed on multiple lines and indented for readability:

name: request-text/title "Your Name:"
birthday: request-date
clr: request-color
fl: request-file
alert rejoin [
    (uppercase name) ", your birthday, color and file choices were: "
    birthday ", " clr ", " fl 
    " -- and the current period of day is:"
    either now/time > 12:00pm [" afternoon"][" morning"]
]

You can 'rejoin strings of code, and run that code using the 'do function (this is a simple form of "metaprogramming"):

code: request-text/title/default {Enter some code:} {alert "hello"}
do rejoin [{print "Here's your running code..." } code]

3.6 Lists

Most computer applications deal with some sort of data list(s): lists of coordinates in a graphics program, lists of items (and perhaps their prices, sizes, shipping dates, etc.) in an inventory program, lists of names (and their account numbers, passwords, transaction details, etc.) in a financial program, lists of files in a utility program, emails in an email program, etc. Rebol's main list structure is called the "series". There are a number of functions built into Rebol which allow you to add, remove, change, search, sort, compare, combine, load, save, and otherwise manipulate series data. Manipulating characters within strings of text (lists of characters), graphics on screen in a game (list of images and screen locations in a draw block), pieces of data being transferred between network ports, etc. all require knowledge of only that one simple set of functions.

The basic code structure used to store series of data items in Rebol is called the "block". Just put square brackets around a list of items, and assign it a variable label:

names: ["John" "Dave" "Jane" "Bob" "Sue"]
codes: [2804 9439 2386 9823 4217]
dates: [21-dec-2013 1-oct-2012 1-jan-2014]
files: [%employees %vendors %contractors %events]

You can pick, find, and sort items from the list using simple functions and constructs:

print pick names 3          ; these two lines do
print names/3               ; exactly the same thing
print find names "Dave"
print sort names
print sort codes
print sort dates

Notice in the sort examples above that each type of data is automatically sorted appropriately (names alphabetically, numbers ordinally, dates chronologically).

Save and load blocks of data using the 'save and 'load functions:

names: ["John" "Dave" "Jane" "Bob" "Sue"]
save %mynames names
loaded-names: load %mynames
probe loaded-names

The 'parse function can be used to split strings into lists of data:

parse/all "John Smith, 123 Street Rd., Cityville, PA, 12345" ","

Mastering series manipulation is a core skill required for Rebol proficiency. These functions will become thoroughly familiar: pick, find, at, index?, length?, append, remove, insert, extract, copy, replace, select, sort, reverse, head, next, back, last, tail, skip, change, poke, clear, join, intersect, difference, exclude, union, unique, empty?, write, read, save, load.

3.7 Loops

Loops are used to do something to or with each consecutive item in a list. 'Foreach is the most commonly used type of loop in Rebol:

names: ["John" "Dave" "Jane" "Bob" "Sue"]
foreach name names [alert name]

The variable labels in loops can be set to any arbitrary word. For example, this example does the exact same thing as the code above:

n: ["John" "Dave" "Jane" "Bob" "Sue"]
foreach i n [alert i]

Very often, you'll check each item in a list for a condition, and do something appropriate based on the evaluation of each item:

names: ["John" "Dave" "Jane" "Bob" "Sue"]
foreach name names [
    if find name "j" [alert name]
]

dates: [21-dec-2013 1-oct-2012 1-jan-2014]
foreach date dates [
    if date > 31-dec-2013 [alert form date]
]

You can structure data in a list to form rows and columns. The following foreach loop labels every 3 consecutive pieces of data in the 'mycontacts list as a name, address, and phone value. Notice that "" is used as an empty place holder:

mycontacts: [
    "John Smith" "123 Tomline Lane Forest Hills, NJ" "555-1234"
    "Paul Thompson" "234 Georgetown Pl. Peanut Grove, AL" "555-2345"
    "Jim Persee" "345 Pickles Pike Orange Grove, FL" "555-3456"
    "George Jones" "456 Topforge Court Mountain Creek, CO" ""
    "Tim Paulson" "" "555-5678"
]
foreach [name address phone] mycontacts [
    print name
]

You can use the 'extract function, instead of a 'foreach loop, as a shortcut to get columns of data:

mycontacts: [
    "John Smith" "123 Tomline Lane Forest Hills, NJ" "555-1234"
    "Paul Thompson" "234 Georgetown Pl. Peanut Grove, AL" "555-2345"
    "Jim Persee" "345 Pickles Pike Orange Grove, FL" "555-3456"
    "George Jones" "456 Topforge Court Mountain Creek, CO" ""
    "Tim Paulson" "" "555-5678"
]
probe extract mycontacts 3          ; every 3 items 
                                    ; (the 1st column of above data)
probe extract/index mycontacts 3 3  ; every 3 items, starting with 3rd
                                    ; (the 3rd column of above data)

You can use 'repeat and 'for loops much like 'foreach, to count through items in a list. This example sets a counter variable 'i to count from 1 to the length of the names block, incrementing the variable by 1 each time through the loop:

names: ["John" "Dave" "Jane" "Bob" "Sue"]
repeat i (length? names) [
    alert rejoin ["Name #" i ": " pick names i]
]

names: ["John" "Dave" "Jane" "Bob" "Sue"]
for i 1 (length? names) 1 [
    alert rejoin [
        "Current Name #" i ": " pick names i
        " Next Name: " pick names (i + 1)
    ]
]

A 'forever loop just repeats forever, until a 'break function is encountered:

count: 99
forever [
    print rejoin [count " bottles of beer on the wall"]
    count: count - 1
    if count = 0 [break]
]

3.8 User Interfaces

3.8.1 Console Apps

Utility scripts often print a quick (non-graphic) display of data to the Rebol console, or perhaps require a brief answer to a question asked at the console text prompt. You can use the 'ask and 'print functions to do simple console i/o. Save this code to a file and run it:

REBOL []
user: ask "Username (iwu83):  "
pass: ask "Password (zqo72):  "
either (user = "iwu83") and (pass = "zqo72") [
    print rejoin [newpage "Welcome back"]
] [
    print rejoin [newpage "Incorrect Password"]
    halt
]
print "You're in..."
halt

3.8.2 GUI Apps

For most "apps", a GUI (graphic user interface) screen is used to input and output data and to control program flow. Buttons, text fields, drop-down selectors, and other "widgets" allow the user to operate the program. Rebol provides a simple built-in code dialect for creating interactive GUIs. The words "view layout" create a window layout. A block holds widget descriptions:

view layout [
    btn
    field
    text "Rebol is really pretty easy to program."
    text-list
    check
]

You can adjust the visual characteristics of any widget in a screen layout by following each widget with appropriate modifiers:

view layout [
    btn red "Click Me"
    field 400 "Enter some text here"
    text font-size 16 "Rebol is really pretty easy to program." purple
    text-list 400x300 "line 1" "line 2" "another line"
    check yellow
]

You can have widgets perform functions, or any block of code, when clicked or otherwise activated. Just put the functions inside another set of brackets after the widget. This is how you get your GUIs to 'do something':

view layout [
    btn "click me" [alert "You clicked the button."]
]

The word "value" refers to data contained in a currently activated widget:

view layout [
    text "Some action examples.  Try using each widget:"
    button red "Click Me" [
        alert "You clicked the red button."
    ]
    field 400 "Type some text here, then press [Enter] on your keyboard" [
        alert value
    ]
    text-list 400x300 "Select this line" "Then this line" "Now this one" [
        alert value
    ]
    check yellow [alert "You clicked the yellow check box."]
    btn "Quit" [quit]    
]

You can refer to data contained in other widgets using the path ("/") syntax. Assign a variable label to any widgets which you refer to:

view layout [
    a: area                                    ; an area widget labeled 'a
    btn "Show" [alert a/text]
    btn "Save" [write %somedatafile.txt a/text]
    btn "Load" [a/text: read %somedatafile.txt  show a]
]

view layout [
    t: text-list data [1 2 3]                  ; a text-list labeled 't
    btn "Show Selected" [alert t/picked]
]

You can create a new widget definition, based on any existing widget type, but with its own special properties and actions, using the 'style word:

view layout [
    style question-btn btn tan 100x40 "Set me" [face/text: request-text]
    question-btn
    question-btn    ; these buttons have looks and actions defined above
]

You can build layouts dynamically using loops, and then display the resulting constructed GUI code. Together with 'style, this is enables some very powerful, concise, and malleable coding capabilities:

gui: [style b button [alert join "My color is: " face/color]]
foreach color [red green blue] [append gui 'b append gui color]
view layout gui

You'll need to learn a lot more about creating GUI screens if you want to create user apps with Rebol, but you can get a lot done with just the basics demonstrated above. The examples in the second half of this text will explain and demonstrate by rote many common GUI techniques.

3.8.3 Server and CGI Web Apps

Rebol has network and data/code transfer features built in, which allow you to easily create client-server applications. Client apps can be written in Rebol, HTML/CSS/Javascript, or any other language which can connect using standard network protocols (HTTP://, TCP, UDP, etc.). Rebol also provides native "CGI" capabilities, so that server scripts can run on a typical Apache host or any other web server stack, and accept input from HTML web forms, Ajax requests, etc., then print HTML or formatted data responses to appear in the user's web browser:

REBOL [title: "HTML Form Server"]
port: open/lines tcp://:80   browse join read join dns:// read dns:// "?"
forever [
    p: port/1 attempt [
        probe decode-cgi replace next find p/1 "?" " HTTP/1.1" ""
        write-io p h:  {HTTP/1.0 200 OK^/Content-type: text/html^/^/
        <HTML><BODY>WATCH THE REBOL CONSOLE!<FORM action="localhost">
            Name:<input type="text" name="name"><input type="submit">
        </FORM></BODY></HTML>} length? h
    ] close p
]

Try running the app above on your PC, type some text into the form which appears in your PC's browser. Then, try opening the displayed IP address using the browser on your mobile phone, iPad, or any other Internet connected device (that address will look something like 192.168.1.4? - be sure to include the question mark, as it's displayed in your PC's browser). You will see all submitted data printed in the Rebol console, regardless of which network connected device was used to submit the data.

A complete section at the end of this text is dedicated to creating CGI applications (stand-alone network applications are covered in Quick Start, Part 2). This allows you to create useful web based data management apps, with Rebol, which can be accessed on mobile devices, or any platform which has a browser.

3.8.4 Faceless Apps

Certain types of utility scripts may not require any interaction with a user. For example, apps that run in the background to perform scheduled file maintenance, to upload routine data backups, to check and update the current system time, etc., don't necessarily need to show you that they're running. Rebol scripts can run in memory without any front end display.

3.9 User Created Functions

You can create your own functions in Rebol using the "func" code structure. New functions are assigned a word label with a colon symbol. The label(s) of data arguments which the function will process are listed in a block, then the calculations or other actions which the function performs are included in a following block.

Here's a function called 'triple, which multiplies the number 3 times any given number argument (called 'x here):

triple: func [x] [
    print 3 * x
]

Now you can apply the 'triple function to any argument value, as if that function was a native function word built into the Rebol language:

triple 4
triple 5
triple 6
alert "I just tripled the numbers 4 (12), 5 (15), and 6 (18)"

Think of the 'x argument (parameter) in the function definition above just like some program's command line option - it represents some variable data that you send to the program to do some work with. In the 'triple function, the argument represents whatever number is multiplied by 3, and 'x is the variable placeholder used to represent that parameter value.

The 'does structure is a shortcut for 'func, when no data parameters are needed:

cls: does [prin newpage]
cls

3.9.1 Return Values

The last value in a Rebol function definition is treated as its "return" value. The 'check function below takes a series of string values as its parameter (labeled 'list here), and checks to see if it contains any bad words (in this example, bad words are specified by the characters "--"). This function starts out by setting the variable 'answer to "safe", then uses a 'foreach loop and an 'if condition to see if "--" is found in any of the strings in the series. If at any point the bad characters are found, the 'answer variable is set to "unsafe". At the end of the function the 'answer variable is returned. The function is then run on both the names1 and names2 lists, and the user is alerted with the returned results:

check: func [list] [
    answer: "safe"
    foreach l list [
        if find l "--" [answer: "unsafe"]
    ]
    answer
]
names1: ["Joe" "Dan" "Sh--" "Bill"]
names2: ["Paul" "Tom" "Mike" "John"]
alert rejoin ["The list " names1 " is " check names1]
alert rejoin ["The list " names2 " is " check names2]

3.9.2 Libraries

You can save collections of useful functions ("libraries") to a file, and import them with 'do. This saves you from having to retype or paste the code of commonly used function definitions into each new program. Save just the 'check function code above to a file named "myfunctions.r" - be sure to include the rebol[] header when saving code to a file:

REBOL []
check: func [list] [
    answer: "safe"
    foreach l list [
        if find l "--" [answer: "unsafe"]
    ]
    answer
]

The program from the previous section then looks like this:

REBOL []
do %myfunctions.r
names1: ["Joe" "Dan" "Sh--" "Bill"]
names2: ["Paul" "Tom" "Mike" "John"]
alert rejoin ["The list " names1 " is " check names1]
alert rejoin ["The list " names2 " is " check names2]

Imported files can be found on the local file system (i.e., on your hard drive, a thumb drive, etc.), or at a network/Internet URL. For example, if you save the "myfunctions.r" file on your web site server, you could import it like this:

do http://site.com/myfunctions.r

You can actually include any code you want in imported files (not just function definitions). You could put the entire program above into a file on your web site and run it by "do"ing the URL of the file. Importing a file with 'do is exactly the same as copying and pasting the contents of the file into your code, and then executing it.

3.10 Handling Errors

The 'error? and 'try functions can be used together to detect runtime errors in Rebol code. Use an 'if or other conditional evaluation to handle any error events:

if error? try [0 / 0] [alert "Dividing by zero is an error"]

You can examine error properties by assigning a label to the result of the 'try function, using the 'disarm function:

if error? err: try [0 / 0] [probe err: disarm :err]

The 'attempt function can also be used to handle errors:

attempt [1 / 1]
attempt [0 / 0]

3.11 The rebol.r Startup File

A few useful features can be added to the Rebol editor by running this line when the Rebol console is started:

do http://re-bol.com/e

Save that code to a file named "rebol.r", in the same folder as the Rebol interpreter, and it will be executed every time Rebol starts. Be sure to add the Rebol header to the beginning of the rebol.r script. You can wrap the line above in an attempt[] function, to avoid a stopping error in cases when no Internet connection is available:

REBOL [title: "My Startup Options"]
attempt [do http://re-bol.com/e]

It's even better practice to keep a local copy of any scripts which you'll run regularly:

REBOL [title: "My Startup Options"]
if not exists? %e [write %e read http://re-bol.com/e]
do %e

The 'do-thru function makes the above process even easier by automatically caching a local copy of any executed files:

REBOL [title: "My Startup Options"]
attempt [do-thru http://re-bol.com/e]

; next time this line is run, the local copy of the file will be used

You can add any other code to your rebol.r file, to initialize options such as email and other account settings, or to run entire scripts, to automate any common startup routines or other custom initialization code. Try adding the following lines, with your email account info edited

REBOL [title: "My Startup Options"]
system/schemes/default/host:   "smtp.youremail.com"
system/schemes/pop/host:       "pop.youremail.com"
system/schemes/default/user:   "username"
system/schemes/default/pass:   "password"
system/user/email:             yourname@youremail.com
attempt [do-thru http://re-bol.com/e]

To move all your personalized settings to another machine, just copy your rebol.r file. You can also use multiple copies of the Rebol interpreter running on a single machine, all with different rebol.r startup files. Just place each interpreter in a different folder, anywhere on your hard drive, SD card, etc.

4. Some Rote GUI, File, and Data List Examples in Rebol

This section demonstrates a variety of code patterns which are commonly used to create apps in Rebol. They make use of functions, variables, string concatenation, lists, loops, conditional evaluations, user interfaces, and other code structures which you've been introduced to already. The examples may seen randomly selected or trivial - just paste or type each of them into a script file, by rote, and then run the code. You'll use variations of each snippet in the 20+ example apps which make up the rest of the tutorial. The first examples focus on performing basic interactions with a user interface, then progress towards handling more interesting data manipulation operations. Pay particular attention to the use of lists and loops, as the examples advance.

4.1 Basic GUI Examples

Create a window with a button:

rebol []
view layout [
    btn "Click Me"
]

When the button is clicked, do something:

rebol []
view layout [
    btn "Click Me" [alert "I've been clicked!"]
]

Here's a window with a button and a text entry field labeled 'f:

rebol []
view layout [
    btn "Click Me"
    f: field
]

When the button is clicked, alert the user with the text currently in the 'f field. Try typing something in the field, then click the button:

rebol []
view layout [
    f: field "Type here, then click the button"
    btn "Click Me" [alert f/text]
]

When the button is clicked, write the contents of the field to the file mytext.txt, then alert the user that the file has been saved:

rebol []
view layout [
    f: field "Type here, then click the button"
    btn "Click Me" [
        write %mytext.txt f/text
        alert "Saved"
    ]
]

Add another button to read the file contents back into the field. Now you can close the program, run it again, and retrieve the saved text. Note that any time you change anything in a GUI, you must update the display with the 'show function:

rebol []
view layout [
    f: field 
    btn "Save" [
        write %mytext.txt f/text
        alert "Saved"
    ]
    btn "Load" [
        f/text: read %mytext.txt
        show f
    ]
]

Here's the exact same program as above, except with a text area widget, instead of a one-line field. This forms a simple text editor:

rebol []
view layout [
    a: area
    btn "Save" [
        write %mytext.txt a/text
        alert "Saved"
    ]
    btn "Load" [
        a/text: read %mytext.txt
        show a
    ]
]

Here the text is appended to mylist.txt. Instead of overwriting the file contents each time, each new write operation adds an additional line to the file, creating a log. The 'rejoin function joins together the text and a newline (carriage return), so that each entry appears on a different line in the saved log file:

rebol []
view layout [
    f: field "Enter some lines here..."
    btn "Save" [
        write/append %mylist.txt rejoin [f/text newline]
        alert "Saved"
    ]
    a: area "The contents of the file will appear here when loaded..."
    btn "Load" [
        a/text: read %mylist.txt
        show a
    ]
]

4.2 Some GUI Examples Using Lists and Loops

Now here are some examples which display lists in a GUI layout, using the 'text-list widget:

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
view layout [
    text-list data mylist
]

This example uses the 'extract function to create a new list containing just the names from the original list, which is then displayed in the text-list widget:

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
view layout [
    t: text-list data (extract mylist 2)
]

This example does the same thing as above, using a 'foreach loop technique. First, a blank 'names list is created using "copy []", then items are added to this list using 'foreach and 'append:

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
names: copy []
foreach [n c] mylist [append names n]
view layout [
    t: text-list data names
]

The clicked item in a text-list widget is referred to with "/picked":

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
view layout [
    t: text-list data (extract mylist 2) [alert t/picked]
]

The following example does the exact same thing as the code above, but using the 'foreach loop technique to collect alternate names from the 'mylist block, instead of 'extract. We'll use this code pattern going forward, to help familiarize the use of 'foreach, since it is very common in all types of Rebol apps:

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
names: copy []
foreach [n c] mylist [append names n]
view layout [
    t: text-list data names [alert t/picked]
]

The "index" of an item in a list is its numerical position in the list. See how an item's index can be used together with 'find, to coordinate the matching of values in separate lists:

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
names: copy []
foreach [n c] mylist [append names n]
view layout [
    t: text-list data names [alert form index? find mylist t/picked]
]

You can select the next item in a list by adding 1 to the current item's index. In the 'mylist example below, each number value is always found at the next index location, immediately following every name (i.e., "Jane" is at position 5 in the example list, her number 2386, is at position 6). In code, that's thought of as 1 + (Jane's index position):

rebol []
mylist: ["John" 2804 "Dave" 9439 "Jane" 2386 "Bob" 9823 "Sue" 4217]
names: copy []
foreach [n c] mylist [append names n]
view layout [
    t: text-list data names [
        alert form pick mylist (1 + index? find mylist t/picked)
    ]
]

4.3 Building a Basic CRUD (create, read, update, delete) Contacts App

Here's the same idea as above, but with more data fields for each name. Name, Address, and Phone values can be found at consecutive Name, Name + 1, and Name + 2 positions:

rebol []
mycontacts: [
    "John Smith" "123 Tomline Lane Forest Hills, NJ" "555-1234"
    "Paul Thompson" "234 Georgetown Pl. Peanut Grove, AL" "555-2345"
    "Jim Persee" "345 Pickles Pike Orange Grove, FL" "555-3456"
    "George Jones" "456 Topforge Court Mountain Creek, CO" ""
    "Tim Paulson" "" "555-5678"
]
names: copy []
foreach [name address phone] mycontacts [append names name]
view layout [
    t: text-list data names [
        alert rejoin [
            "Name: "
            t/picked
            " Address: "
            pick mycontacts (1 + index? find mycontacts t/picked)
            " Phone: "
            pick mycontacts (2 + index? find mycontacts t/picked)
        ]
    ]
]

Instead of alerting some rejoined text, display each data item in a text entry field:

rebol []
mycontacts: [
    "John Smith" "123 Tomline Lane Forest Hills, NJ" "555-1234"
    "Paul Thompson" "234 Georgetown Pl. Peanut Grove, AL" "555-2345"
    "Jim Persee" "345 Pickles Pike Orange Grove, FL" "555-3456"
    "George Jones" "456 Topforge Court Mountain Creek, CO" ""
    "Tim Paulson" "" "555-5678"
]
names: copy []
foreach [name address phone] mycontacts [append names name]
view g: layout [
    t: text-list data names [
        n/text: t/picked
        a/text: pick mycontacts (1 + index? find mycontacts t/picked)
        p/text: pick mycontacts (2 + index? find mycontacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
]

Add items to a block (data displayed in a text-list) by entering text into field widgets:

rebol []
contacts: copy []
names: copy []
foreach [name address phone] contacts [append names name]
view g: layout [
    t: text-list data [] [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        names: copy []
        foreach [name address phone] contacts [append names name]
        t/data: copy names
        show t
    ]
]

This example uses a function to encapsulate duplicated code (which doesn't shorten the whole program much here, but if the duplicated code was, for example, 20 lines long and used in 5 different places, the whole program would be about 100 lines shorter):

rebol []
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
    copy names
]
contacts: copy []
view g: layout [
    t: text-list data [] [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        t/data: extract-names
        show t
    ]
]

Load a list from file using 'load %filename, and assign it a variable label. Save a list to file using 'save %filename [list]. Create an empty file using write/append %filename "" (if the file already exists, nothing gets written):

rebol [title: "Contacts"]
write/append %contacts ""
contacts: load %contacts
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
    copy names
]
extract-names
view g: layout [
    t: text-list data names [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    across
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        t/data: extract-names
        show t
    ]
    btn "Save" [save %contacts contacts]
]

Notice that the title "Contacts" was added to the program header above, which is displayed in the title bar of the GUI window.

You could create a wide variety of basic "CRUD" (create, read, update, delete) apps using variations of the code above. So far, this program actually only creates and reads, but we'll add more to it shortly.

5. A Few More Complete App Examples

5.1 Tip Calculator

This app calculates the total amount, including tip, to pay for a restaurant meal:

  1. A window layout is created. It contains 3 text field widgets, labeled 'f,'t, and 'x.
  2. The 'f and 't fields contain some default money and tip rate values ($9 and .2 (20 percent)).
  3. When a value is entered into the 't field by the user, the 'x field's text is set to the value computed by multiplying the 'f field's money value, times the 't field's decimal value + 1 (i.e., the total to pay for a $9 bill is ($9 times 1.2)). The display of the 'x widget is updated with the 'show function:
REBOL [title: "Tip Calculator"]
view layout [
    f: field "$9"
    t: field ".2" [
        x/text: (to-money f/text) * (1 + (to-decimal t/text))
        show x
    ]
    x: field
]

5.2 Days Between 2 Dates

This app displays the number of days between any 2 selected dates:

  1. A window layout is created. It contains 2 buttons, a text widget displaying the text "Days Between:", and a text entry field labeled 'f.
  2. When the first button is clicked, it's face text is set to a date requested from the user. The selected date value is also assigned the variable label 's.
  3. When the second button is clicked, it's face text is set to another date requested from the user. That value is also assigned the variable label 'e. The text of the 'f field is then set to the difference between the 2 dates (e - s), and the display is updated with the 'show function.
REBOL [title: "Days Between"]
view layout [
    btn 200 "Start" [face/text: s: request-date]
    btn 200 "End" [
        face/text: e: request-date
        f/text: e - s
        show f
    ]
    text "Days Between:"
    f: field
]

5.3 Image Effects

This app allows users to apply selected effects to an image:

  1. A window layout is created. It contains an image widget labeled 'pic, and a text-list.
  2. The data in the text list is a variety of built-in effects which Rebol natively knows how to apply to images.
  3. When a value is selected from the text list, the /effect property of the 'pic image is set to that selected value, and the image is updated with the 'show function.
REBOL [title: "Image Effects"]
view layout [
    pic: image load http://re-bol.com/palms.jpg
    text-list data [
        "Invert" "Grayscale" "Emboss" "Blur" "Sharpen" "Flip 1x1"
        "Rotate 90" "Tint 83" "Contrast 66" "Luma 150" "None"
    ][
        pic/effect: to-block value
        show pic
    ]
]

5.4 Send Emails

This program allows users to send email messages.

  1. The program starts out by setting 5 required system variables: the user's POP and SMTP mail server settings, account username and password, and email address. These values must be edited to contain proper email account settings, in order for emails to actually be sent by the program.
  2. A window layout is created. It contains a text entry field labeled 'f, a multiline text area labeled 'a, a "Send" button, and 2 text widgets to tell the user what to type in the field and the area widgets ("Email:" and "Message:").
  3. Users type in the recipient's email address, and a message to send. When the "Send" button is clicked, the 'send function is executed. The parameters of the send function are the email address gotten from the text typed into the 'f field, and the multiline text typed into the 'a area. After the email has been sent, the user is alerted with the text "Sent".
REBOL [title: "Send Email"]
system/schemes/pop/host: "pop.server1" 
system/schemes/default/host: "smtp.server1" 
system/schemes/default/user: "username"
system/schemes/default/pass: "password1" 
system/user/email: you@site.com
view layout [
    text "Email:"
    f: field "joe@site.com" 
    text "Message:"
    a: area
    btn "Send" [
        send to-email f/text a/text
        alert "Sent"
    ]
]

Try editing this program so that it allows the user to set the default email settings without having to edit any program code (you can find a few such working examples at http://re-bol.com/examples.txt)

5.5 Tile Game

The code below creates a playable tile game. Click any tile piece to move it into the empty space. Rearrange the tiles in ascending order:

  1. A GUI window layout is created.
  2. The 'style word, in Rebol's GUI dialect, is used to create a new red 'box object labeled 't. Whenever a 't widget is clicked by the user, three actions occur: the label 'x is set to the box's current 'offset (coordinate position), the box's coordinate position is set to the 'e widget's position, and the 'e widget's position is changed to the 'x position. These three actions together effectively swap the positions of the clicked box and the 'e widget (as you'll see below, the widget labeled 'e is just a plain box widget, which appears as a blank space in the window layout).
  3. The 'across word, in Rebol's GUI dialect, is used to place each consecutive widget in the window across the screen (instead of one below another, each on new lines, which is the default behavior you've seen so far in GUI layouts).
  4. The 'return word, in Rebol's GUI dialect, is used to start a new "line" of widgets (similar to how a carriage return is used to start a new line in a text document).
  5. A bunch of 't box widgets are added to lines in the window layout, along with a final box widget labeled 'e. Any time you click a red box, its position is swapped with the empty box.
REBOL [title: "Tile Game"] 
view layout [ 
     style t box red [
         x: face/offset
         face/offset: e/offset 
         e/offset: x
     ] 
     across 
     t "8"  t "7"  t "6"  return 
     t "5"  t "4"  t "3"  return 
     t "2"  t" 1"  e: box 
]

Here's a compact version of the code above, with each section of code compacted onto a single line. It's still quite readable:

view layout [ 
     style t box red [x: face/offset face/offset: e/offset e/offset: x] 
     across t"8" t"7" t"6" return t"5" t"4" t"3" return t"2" t"1" e: box
]

5.6 Generic Calculator

Below is a calculator app. Most of the code should make sense.

  1. A GUI window is created.
  2. The 'across word is used to place each consecutive widget in the window across the screen.
  3. The window layout includes a field labeled 'f. It is 225x50 pixels in size, and the font size of text displayed in it is 25.
  4. The 'style word is used to create a new button object called 'b, which is 50x50 pixels in size, and which appends the current button's face text to the 'f field widget's text, whenever one of the 'b buttons is clicked.
  5. A bunch of 'b buttons are added to the window layout, each displaying a different number or mathematical operator. The 'return word is used to start new lines of buttons.
  6. The "=" button attempts to set the 'f field's text to whatever results in "do"ing (evaluating) the current mathematical expression displayed in the 'f field.
REBOL [title: "Calculator"]
view layout [ 
     across 
     f: field 225x50 font-size 25  return 
     style b btn 50x50 [append f/text face/text show f] 
     b "1"  b "2"  b "3"  b " + "  return 
     b "4"  b "5"  b "6"  b " - "  return 
     b "7"  b "8"  b "9"  b " * "  return 
     b "0"  b "."  b " / "  b "=" [ 
         attempt [f/text: form do f/text show f] 
     ] 
]

Try building this same app in any other programming language, and you'll see that there is absolutely nothing simpler than Rebol's dialect approach to building windowed programs. The idea of using derived GUI widget objects, variable labels, series functions, and evaluated expressions, however, is universal to creating a similar calculator application using any programming language/tool.

5.7 Coin Flip

Clicking the button in this app randomly flips between a heads and tails coin image:

  1. Two images are loaded from specified URLs on the Internet. The label 'h is assigned to the loaded heads image. The label 't is assigned to the loaded tails image.
  2. The 'random function with the /seed refinement is used to initiate Rebol's random number generator, using the current time (a generally unique value), so that unique random values can be generated with the 'random function. Any time you want to generate random numbers, text, or other random values, this stock line of code should be included in your program.
  3. A window layout is created and labeled 'g. It contains an image widget labeled 'i which initially displays the 'h image (the coin head image), a text field entry widget labeled 'f, and a button displaying the text "Flip".
  4. When the button is clicked, it sets the 'f field text to the first item in a randomly ordered list of the 2 text strings "Heads" and "Tails".
  5. Next, an 'either conditional evaluation is performed. If the text in the 'f field is "Heads" (that random state was determined in the previous step), then the image value of the 'i widget is set to the 'h image (the heads image). Otherwise (i.e., if the text in the 'f field is "Tails"), then the image in the 'i widget is set to 't (the tails image).
  6. The display (labeled 'g) is updated using the 'show function.
REBOL [title: "Coin Flip"]
h: load http://re-bol.com/heads.jpg
t: load http://re-bol.com/tails.jpg
random/seed now
view g: layout [
    i: image h
    f: field
    btn "Flip" [
        f/text: first random ["Heads" "Tails"]
        either f/text = "Heads" [i/image: h] [i/image: t] 
        show g
    ]
]

5.8 Additional Example Apps to Study

Here are a few more short app examples. See if you can follow the basic flow of code in each program. Pay attention to which pieces of code are variable labels, GUI layout widgets, conditional evaluations, etc.

A web page editor:

REBOL [title: "Web Page Editor"]
view layout [
    f: field 600 "ftp://user:pass@site.com/public_html/page.html"
    a: area 600x350 
    across 
    btn "Load" [a/text: read to-url f/text  show a]
    btn "Save" [write to-url f/text a/text  alert "Saved"]
]

Here's an app to quiz users on basic addition math facts. Try changing it to quiz subtraction, multiplication, and division on selected ranges of random numbers (i.e., subtraction on numbers 1 - 100 or multiplication on numbers 1 - 12):

REBOL [title: "Math Test"]
random/seed now
x: func [] [rejoin [random 10 " + " random 20]]
view layout [
    f1: field x
    text "Answer:"
    f2: field [
        alert either f2/text = form do f1/text ["Yes!"]["No"]
        f1/text: x  show f1  focus f2
    ]
]

Try changing this one to generate insults:

REBOL [title: "Compliment Generator"]
random/seed now
view layout [
    x: area "brilliant rare unique talented exceptional"
    y: area "genius champion winner success achiever"
    btn "Compliment" [
        alert rejoin [
            "You're a "
            first random parse x/text none " "
            first random parse y/text none "!"
        ]
    ] 
]

A useful tool for web developers, which allows you to list, select, edit, and save any file in any folder on your FTP server:

REBOL [title: "FTP Tool"]
view  layout [
    f: field 600 "ftp://user:pass@site.com/public_html/" [
        either dir? to-url value [
            t/data: sort read to-url value 
            show t
        ][
            editor to-url value
        ]
    ]
    t: text-list 600x400 [editor to-url join f/text value]
]

6. Graphics

6.1 Rebol "Draw" Basics

To draw graphics, put a box widget into a GUI layout, followed by an 'effect block containing 1 or more 'draw commands:

rebol []
view layout [
    box 400x400 effect [
        draw [
            line 10x39 322x211
        ]
    ]
]

The draw commands are contained inside a BLOCK:

rebol []
view layout [
    box 400x400 black effect [
        draw [
            line 0x400 400x50
            circle 250x250 100
            box 100x20 300x380
            curve 50x50 300x50 50x300 300x300
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

So you could, for example, use a foreach loop to add items to the screen. We'll append draw commands to the /effect/draw block of the object labeled 'b (in this case, the box widget being drawn upon):

rebol [title: "Drawing Animation"]
shapes: [
    [line 0x400 400x50]
    [circle 250x250 100]
    [box 100x20 300x380]
    [curve 50x50 300x50 50x300 300x300]
    [spline closed 3 20x20 200x70 150x200]
    [polygon 20x20 200x70 150x200 50x300]
]
view layout [
    b: box 400x400 black effect [draw []]
    btn "Animate Drawing" [
        foreach g shapes [
            append b/effect/draw g
            show b
            wait 1
        ]
    ]
]

Or use widgets to make changes to the data in the draw block. This code changes the 'x and 'y values of the 'pos variable (the circle's position), then updates the 'b display, using the 'show function:

rebol [title: "Animated Circle"]
pos: 200x200
view layout [
    b: box 400x400 black effect [
        draw [circle pos 20]
    ]
    across
    btn "Up"    [pos/y: pos/y - 10 show b]
    btn "Down"  [pos/y: pos/y + 10 show b]
    btn "Right" [pos/x: pos/x + 10 show b]
    btn "Left"  [pos/x: pos/x - 10 show b]
]

Here's the same example again using cursor key controls:

rebol [title: "Keyboard Controlled Animated Circle"]
pos: 200x200
view layout [
    b: box 400x400 black effect [
        draw [circle pos 20]
    ]
    across
    key keycode [up]    [pos/y: pos/y - 10 show b]
    key keycode [down]  [pos/y: pos/y + 10 show b]
    key keycode [right] [pos/x: pos/x + 10 show b]
    key keycode [left]  [pos/x: pos/x - 10 show b]
]

Color can be added to shapes in the draw block using the "pen" command. Shapes can be filled with color, with images, and with other graphic elements using the "fill-pen" command. The thickness of drawn lines is set with the "line-width" command:

rebol []
view layout [
    box 400x400 black effect [
        draw [
            pen red
            line 0x400 400x50
            pen white
            box 100x20 300x380
            fill-pen green
            circle 250x250 100
            pen blue
            fill-pen orange
            line-width 5
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

6.2 A Few Short Paint Apps

Making a paint program is as simple as adding items to a draw block, using any sort of controlled input from a user:

rebol [title: "Paint Lines"]
view layout [
    b: box black 400x400 effect [draw []]
    text "line start point:"
    f1: field "0x400"
    text "line end point:"
    f2: field "400x50"
    btn "Add line" [
        append b/effect/draw compose [
            line (to-pair f1/text) (to-pair f2/text)
        ]
        show b
    ]
]

Here's a colorful paint example that uses key controls:

rebol [title: "Colorful Etch a Painting"]
pos: 200x200
paint: func [] [
    append b/effect/draw compose [
        pen (random 255.255.255)
        circle (pos) 2
    ]
    show b
]
view layout [
    b: box 400x400 black effect [
        draw [circle pos 10]
    ]
    across
    key keycode [up]    [pos/y: pos/y - 4  paint]
    key keycode [down]  [pos/y: pos/y + 4  paint]
    key keycode [right] [pos/x: pos/x + 4  paint]
    key keycode [left]  [pos/x: pos/x - 4  paint]
]

This example allows you to paint with the mouse, erase, and save created images. It makes use of 'feel and 'engage event management properties, which will be covered in Quick Start, Part 2:

REBOL [title: "Paint"]
view layout [
    s: area 500x400 white feel [
        engage: func [f a e] [
            if a = 'over [append s/effect/draw e/offset  show s]
            if a = 'up [append s/effect/draw 'line]
        ]
    ] effect [draw [line]]
    btn "Clear" [s/effect/draw: copy [line] show s]
    btn "Save" [save/png request-file/only/file %myimage.png to-image s]
]

As you can see, drawing graphics in Rebol is all about managing that 'draw block (inside the 'effect block, attached to some widget in a GUI) by using series functions, adjusting variable values, etc. In that way, graphics programming is a lot like creating CRUD apps similar to the contacts example you saw earlier.

7. More About Series - Adding More Features to the Contacts App

Learning how to do anything more in Rebol requires understanding more about how to use blocks ("series") to store and manipulate data. Let's take the contacts example from earlier:

rebol []
write/append %contacts ""
contacts: load %contacts
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
]
extract-names
view g: layout [
    t: text-list data names [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        extract-names
        t/data: copy names
        show t
    ]
    btn "Save" [save %contacts contacts]
]

If you want to remove a contact, you'll need a function to remove 3 data items from the saved list (name, address, and phone). Let's add a button which does that - it uses the 'remove/part function:

rebol []
write/append %contacts ""
contacts: load %contacts
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
    copy names
]
view g: layout [
    t: text-list data extract-names [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    across
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        t/data: extract-names
        show t
    ]
    btn "Remove" [
        remove/part (find contacts t/picked) 3
        t/data: extract-names
        show t
    ]
    btn "Save" [save %contacts contacts]
]

Now let's add a button to sort the entries by name. You can use the 'sort/skip function/refinement to perform that operation:

rebol []
write/append %contacts ""
contacts: load %contacts
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
    copy names
]
view g: layout [
    t: text-list data extract-names [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    across
    btn "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        t/data: extract-names  show t
    ]
    btn "Remove" [
        remove/part (find contacts t/picked) 3
        t/data: extract-names  show t
    ]
    btn "Sort" [
        sort/skip contacts 3
        t/data: extract-names  show t
    ]
    btn "Save" [save %contacts contacts]
]

We can sort in the opposite order using the 'sort function with the /skip and /reverse refinements. Notice that in each one of our new buttons, we're using the extract-names function. If we hadn't created that function, there certainly would have been a lot of duplicate code in this program:

rebol []
write/append %contacts ""
contacts: load %contacts
extract-names: func [] [
    names: copy []
    foreach [name address phone] contacts [append names name]
    copy names
]
view g: layout [
    t: text-list data extract-names [
        n/text: copy t/picked
        a/text: copy pick contacts (1 + index? find contacts t/picked)
        p/text: copy pick contacts (2 + index? find contacts t/picked)
        show g
    ]
    text "Name:"
    n: field
    text "Address:"
    a: field
    text "Phone:"
    p: field
    btn 100 "Add" [
        append contacts reduce [copy n/text copy a/text copy p/text]
        t/data: extract-names  show t
    ]
    btn 100 "Remove" [
        remove/part (find contacts t/picked) 3
        t/data: extract-names  show t
    ]
    btn 100 "Sort Ascending" [
        sort/skip contacts 3
        t/data: extract-names  show t
    ]
    btn 100 "Sort Descending" [
        sort/skip/reverse contacts 3
        t/data: extract-names  show t
    ]
    btn 100 "Save" [save %contacts contacts]
]

7.1 Built in Help

So where do you learn about all these useful series operations? Well, Rebol can provide information about using any function, with the 'help function, and you can get a list of all available functions with the 'what function. To get a start, try running this script:

REBOL []
help pick
help find
help at
help index?
help length?
help append
help remove
help insert
help extract
help copy
help replace
help select
help sort
help reverse
help head
help next
help back
help last
help tail
help skip
help change
help poke
help clear
help join
help intersect
help difference
help exclude
help union
help unique
help empty?
help write
help read
help save
help load
halt

Rebolers use the 'help function regularly when writing code. Information about using Rebol's built in GUI dialect (called "VID") can be found by examining the objects in SVV:

REBOL []
? svv
probe extract svv/vid-styles 2
? svv/vid-styles/button
? svv/vid-words
? svv/facet-words

Run the program below to create a Rebol word reference manual. The results are saved in a file named help.txt, and displayed using Rebol's built in text editor. Using the editor, you can rename the file and save it anywhere on your hard drive, make notes and write in your own examples, etc., for future reference:

REBOL [Title: "Quick Manual"]
print "This will take a minute..."  wait 2
echo %words.txt what echo off   ; "echo" saves console activity to a file
echo %help.txt
foreach line read/lines %words.txt [
    word: first to-block line
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do compose [help (to-word word)]
]
echo off
x: read %help.txt
write %help.txt "VID STYLES (GUI WIDGETS):^/^/"
foreach i extract svv/vid-styles 2 [write/append %help.txt join i newline ]
write/append %help.txt "^/^/LAYOUT WORDS:^/^/" 
foreach i svv/vid-words [write/append %help.txt join i newline]
b: copy [] 
foreach i svv/facet-words [
    if (not function? :i) [append b join to-string i "^/"]
]
write/append %help.txt rejoin [
    "^/^/STYLE FACETS (ATTRIBUTES):^/^/" b "^/^/SPECIAL STYLE FACETS:^/^/"
]
y: copy ""
foreach i (extract svv/vid-styles 2) [
    z: select svv/vid-styles i
    ; additional facets are held in a "words" block:
    if z/words [
        append y join i ": "
        foreach q z/words [if not (function? :q) [append y join q " "]]
        append y newline
    ]
]
write/append %help.txt rejoin [
    y "^/^/CORE FUNCTIONS:^/^/" at x 4
]
editor %help.txt

7.1.1 The Word-Broswer.r Script

The program below presents nicely formatted help text with clickable examples that clarify how all Rebol's functions work. It covers the overwhelming majority of the Rebol language, and everything is organized into helpful categories, and cross referenced with other related functions, so you can quickly find and use the code you need. It's an indispensable tool which will help you learn all about the core language functions:

do http://re-bol.com/wordbrowser.r

You can download the wordbrowser app in this zip file: http://re-bol.com/wordbrowser.zip.

8. A Few More Short Apps Using Series and Loop Constructs

Some examples in this section may get a bit complicated if you're just starting out with Rebol. Try not to get stuck on any one example. Read through everything and absorb as much as you can. Then go back and read the whole thing again. If you're interested in pursuing coding with Rebol, just continue to digest a few more tutorials (and books, examples on rebol.org, etc.) and it will all start to sink in quickly. You'll see most of these code patterns and concepts repeated over and over again in all sorts of examples.

8.1 Image Slideshow

This app displays all the images in the current folder, with a 3 second pause between each:

  1. The list of files in the current directory is read, and assigned the variable label 'files.
  2. A 'forever loop is started.
  3. Inside each repeat of the 'forever loop, a 'foreach loop is executed. For each file in the folder, if the suffix of the file is found in the given list of file extensions (png, jpg, gif, bmp), then any currently displayed window layout is unviewed, and a new window layout displaying the current file image is displayed. If the user clicks the image with the mouse, the program is quit. Otherwise the program waits 3 seconds and then continues with the loops.
REBOL [title: "Image Slideshow"]
files: read %./
forever [
    foreach file files [
        if find [%.png %.jpg %.gif %.bmp] suffix? file [
            unview 
            view/new center-face layout [
                image file [quit]
            ]
            wait 3
        ]
    ]
]

8.2 Bar Chart

This little app demonstrates how to visually display a list of numerical values in a graphic bar chart:

  1. In this example, a block (list) of months and numerical values is created, labeled 'd.
  2. An empty block labeled 'gui is created.
  3. A 'foreach loop is used to go through each month and value pair in the 'd list. For each pair of month and value items in the 'd list, some GUI layout code is appended to the 'gui block. The appended code consists of a button, sized according to the computation (value * 10), and displaying the month text. The 'compose function is used to evaluate the parenthesized size and month values. For example, if the selected month and value items are "March" and 13, the items added to the 'gui block are [btn 130 "March"].
  4. The constructed 'gui layout block is displayed. The 'center-face word is used to center the window layout on screen.
REBOL [title: "Bar Chart"] 
d: ["March" 13 "April" 9 "May" 21 "June" 29 "July" 10] 
gui: copy []
foreach [month value] d [
    append gui compose [btn (value * 10) (month)]
]
view center-face layout gui

Here's a compact version of the code above, using shorter variable labels:

d: ["March" 13 "April" 9 "May" 21 "June" 29 "July" 10] 
g: [] foreach [m v] d [append g reduce ['btn m v * 10]] view layout g

8.3 Paypal Report

This example downloads a Paypal account file from the web, and computes a sum of all sales in the "Gross Sales" column:

  1. A variable label 'sum is set to represent an initial value of $0.
  2. A variable label 'x is set to represent a block of line data read from the given URL.
  3. The first line in the 'x data block is a bunch of column headers (titles for each column of data). We don't want to use that line in our computations, so a new variable 'y is set to represent all but the first line of 'x values (i.e., 'y is set to start "at" the second line in 'x).
  4. A foreach loop is started, to run through each 'line in the 'y block.
  5. The data on each line of the Paypal file is in "CSV" or "comma separated value" format, which means that each value on each line of the file is separated by a comma character. The 'parse function separates each line into a block of values, labeled 'values here.
  6. The "Gross Sales" value is found in column number 8 on each line, so we'll add the current 'sum value to the money value picked from item 8 in the 'values block. By doing this for each line, the final 'sum value will be the total of all individual Gross Sales values.
  7. Alert the sum to the user.
REBOL [title: "Paypal Reports"]    
sum: $0
x: read/lines http://re-bol.com/Download.csv
y: at x 2
foreach line y [
    values: parse/all line ","
    sum: sum + to-money pick values 8
]
alert join "Total Gross Sales: " sum

Here's a more compact version of the exact same script, which condenses more operations onto each line:

sum: $0
foreach line at read/lines http://re-bol.com/Download.csv 2 [
    sum: sum + to-money pick (parse/all line ",") 8
]
alert join "Total Gross Sales: " sum

Here's a variation which prints all transactions which occur between midnight and noon:

foreach line at read/lines http://re-bol.com/Download.csv 2 [
    time: to-time second row: parse/all line ","
    if (time >= 0:00am) and (time <= 12:00pm) [print row/8]
]

8.4 Tric Trac Game

The point of the Tric-Trac board game is to roll two dice, and match the rolled number with any combination of available numbers on the board. For example, if you roll a 6 with the dice, you can match with the numbers 5 + 1, or 4 + 2, or 3 + 2 + 1, etc., on the board. Once a number on the board has been selected, it cannot be used again for the rest of the game. The game is over when you have no possible combinations of number buttons to match the rolled value. To determine your final score, sum the remaining unselected numbers on the board. The goal is to get the lowest final score (you can play against yourself or take turns playing against others). Here's how the code works in this simple Tric-Trac game simulation:

  1. The "random/seed" function is used to seed Rebol's random number generator with the current time, so that unique random numbers can be generated with the 'random function.
  2. A new block labeled 'g is created. To start out, that block contains 2 lines of GUI dialect code: the 'across layout word, and a button which alerts the user with a random value between 1 and 12 (to simulate a roll of the dice).
  3. A 'repeat loop is used to add GUI code to the 'g block. That code consists of a text widget displaying a number 1 - 12, and a check widget.
  4. The 'g block layout code is displayed in a centered GUI layout window.
REBOL [title: "Tric Trac"]
random/seed now
g: [
    across 
    btn "Roll" [alert form 1 + random 11]
] 
repeat i 12 [
    append g compose [text (mold i) check]
]
view center-face layout g

Here's a compact version which could be entered as a 1-liner:

random/seed now g: [across btn "Roll" [alert form 1 + random 11]] 
    repeat i 12 [append g reduce ['text mold i 'check]] view layout g

A version of this game with many more features is available at http://www.rebol.org/view-script.r?script=trictrac.r

8.5 Shopping List and To-Do List

This app keeps a shopping list of items to buy. It checks to make sure that you're not adding duplicate items, and allows you to save and load your list.

  1. A window layout is created. It contains five widgets: a text list 500x400 pixels in size, labeled 't, a text entry field 500 pixels wide, labeled 'f, and three buttons.
  2. When the user enters an item in the 'f field, the program checks to see if the entered text is already found in the list. If a duplicate entry is not found in the list, the item is added to the data block displayed by the 't widget (t/data). The display is updated with the 'show function, and the cursor is placed back in 'f field, with the 'focus function.
  3. If the user clicks the "Remove Selected" button, the picked item is removed from the text list, and the display is updated.
  4. If the user clicks the "Save List" button, the data in the text list is saved to the file %shopping.txt, and the user is alerted with the text "Saved".
  5. If the user clicks the "Load List" button, the text list is set to display the data loaded from the %shopping.txt file, and the display is updated.
REBOL [title: "Shopping List"]
view center-face layout [
    t: text-list 500x400
    f: field 500 "(Enter Items Here)" [
        if not find t/data f/text [
            append t/data copy f/text
        ] 
        show t 
        focus f
    ]
    across
    btn "Remove Selected" [remove find t/data t/picked  show t]
    btn "Save List" [save %shopping.txt t/data  alert "Saved"]
    btn "Load List" [t/data: load %shopping.txt  show t]
]

Here's a more compact "To-Do List" version:

view layout [
    t: text-list [remove find t/data value  show t]
    field [append t/data copy value show t clear-face face]
    btn "Save" [save %todo.txt t/data  alert "Saved"]
    btn "Load" [t/data: load %todo.txt  show t]
]

And here's a slightly more complicated To-Do List variation of the program above, which adds some features. It provides the option to save and load lists with different file names, the ability to move items up or down in the list order, and the ability to save a screenshot of the program to a .png image, and view that image in a web browser. Try to pick out the series functions and variable labels, and see if you can follow the general program flow:

REBOL [title: "To Do"]
view/new gui: layout [
    t: text-list 500x400
    across
    f: field [if not find t/data v: value [append t/data copy v] focus f]
    btn "Del"  [remove find t/data t/picked]
    btn "Up"   [attempt [move/to z: find t/data t/picked (index? z) - 1]]
    btn "Down" [attempt [move/to z: find t/data t/picked (index? z) + 1]]
    btn "Load" [attempt [t/data: load request-file/file/only %todo.txt]]
    btn "Save" [attempt [
        save request-file/save/file/only %todo.txt t/data
        alert "Saved"
    ]]
    btn "Print" [save/png %todo.png to-image gui  browse %todo.png]
] 
forever [wait .1  show gui  if not viewed? gui [quit]]

8.6 Calendar

Here's a little personal calendar app which allows you to save events for specific days:

  1. A new file "mycal" is created to save the calendar event data. Remember, the /append refinement of the 'write function adds data to a file (instead of erasing the existing contents). If the file doesn't already exist, the 'write function creates a new empty file (i.e., with nothing ("") added to it). If the file already exists, nothing ("") is added to it, so it stays the same.
  2. A 'forever loop is started.
  3. A 'request-date function is used to get a date selected by the user. The result of that function is labeled 'd. If the user selects no date ('none), then the program quits.
  4. A window layout is created and centered on screen (with the 'center-face GUI dialect word).
  5. The selected date is displayed in bold text at the top of the window.
  6. A text area widget labeled 'a is added to the layout. It contains the data "select"ed (found after) the 'd date value, in the "mycal" file (if the selected date isn't found in the mycal file, nothing is displayed).
  7. A button widget is added to the layout. When it's clicked, the date ('d) and text contained in the area labeled 'a, are saved to the mycal file, and then the current layout window is unviewed.
  8. The 'forever loop starts over.
REBOL [title: "Calendar"]    
write/append %mycal "" 
forever [ 
     if none = d: request-date [quit] 
     view center-face layout [ 
         text bold form d 
         a: area form select (l: load %mycal) d 
         btn "Save" [ 
             save %mycal head insert l reduce [d a/text] 
             unview 
         ] 
     ] 
]

8.7 Pig Latin Generator

This console program allows the user to enter a phrase, which is converted to Pig Latin:

  1. The user is asked for a string of text from the user. The ^/ characters at the end of the quoted text are used to print a carriage return (they're the same thing as a "newline"). The label 'p is assigned to the text entered by the user.
  2. The 'parse function is used to split apart strings at specified characters. Here, the phrase labeled 'p above, is split apart at each space. The result of the 'parse function is a block of text strings (here, the individual words of the phrase entered by the user). That list of words is assigned the label 's.
  3. A foreach loop is used to go through each word in the 's block of words. During each repeat of the foreach loop, the individual words from the list are labeled 'w.
  4. During each repeat of the foreach loop, each individual word is split apart at the vowels. That block of characters is labeled 'x.
  5. The Pig Latin output is created by concatenating several pieces of text: the part of the word before the first vowel, and the part of the word beginning with the first vowel. A conditional 'either evaluation is used to determine which portion of the given word should be used in each one of the Pig Latin halves of the word. Another 'either evaluation is used to determine which ending characters should be used to form the second word in the Pig Latin text (based on whether or not non-vowel characters exist before the first part of the original word). For example, if the word starts with a consonant letter, the second half of the Pig Latin pair ends in "ay". Otherwise, it ends in "hay". Note that the 'prin function is used to print each Pig Latin word. The 'prin function does the same thing as 'print, except it doesn't attach a newline character to the end of each printed item (in this case, the allows each of the output words to be printed next to one another, instead of on separate lines).
  6. A blank line is printed, and the 'halt function is used to keep the program from closing, so that the user can read the printed results.
REBOL [title: "Pig Latin"]
p: ask "Type some words to convert to Pig Latin:  ^/^/"
s: parse p " "
foreach w s [
    x: parse w "aeiou"
    prin rejoin [
        either q: find/match w x/1 [q] [w] 
        x/1 
        either q ["ay"]["hay"]
        " "
    ]
]
print newline
halt

Here's a one-liner version:

foreach t parse ask"PIG: """[x: parse t"aeiou"prin rejoin[either m:
    find/match t x/1[m][t]x/1 either m["ay"]["hay"]" "]]

8.8 Marching Alien Logo Army

Here's an example that moves an army of Rebol logo graphics across the screen:

  1. A block of starting position coordinates is defined, labeled 'poss.
  2. A 'build-block function is defined. It builds a block of draw commands from the block of existing position coordinates, moving each image 8 pixels from the existing position. In this function, the 'poss block values are first copied over to the variable label 'existing-poss, and a new blank 'poss block is created. A foreach loop is used to fill the new 'poss block with coordinates values that are moved 8 pixels to the right (+ 8x0) from their locations in the existing poss block. Then a new 'logos block is created, and a foreach loop is used to fill it with draw commands composed from the generated coordinates in the new 'poss block.
  3. The 'build-block function is run once before showing the window display, to create a 'logos block of draw commands. The function definition above just defines what the 'build-block function does. In order to run that code, you must actually execute the 'build-block function (just like any other function (request-text, now, etc.), the function is executed by just typing its name).
  4. A window layout is created. It contains a draw block displaying all the graphics commands in the 'logos block.
  5. The window layout also contains a button widget. When the user clicks the button, a block of code is looped 10 times. Inside the loop, the 'build-block function is executed, the display of the 'b box is updated with the 'show function, so that it displays the new graphics commands in the generated 'logos block, with the images shifted over 8 pixels to the right. The loop is paused for .2 seconds during each of the 10 iterations.
REBOL [title: "Marching Alien Logo Army"]
poss: [
    10x30 120x30 230x30 340x30 450x30 560x30
    10x60 120x60 230x60 340x60 450x60 560x60 
]
build-block: func [] [
    existing-poss: copy poss
    poss: copy []
    foreach pos existing-poss [append poss pos + 8x0]
    logos: copy []
    foreach pos poss [append logos compose [image (pos) logo.gif]]
]
build-block
view layout [
    b: box 800x300 aqua effect [draw logos]
    btn "Move 10 clicks"[
        loop 10 [
            build-block
            show b
            wait .2
        ]
    ]
]

8.9 Catch Game

In this game the player tries to catch falling Rebol logo images, using cursor keys to move. Speed increases as the game progresses. Game play ends when a falling logo hits the floor. Final score is the amount of time the game was played. Scores are saved along with each player's name, sorted from lowest to highest score, and displayed at the end of the game. Take a look at the GUI window elements, conditional evaluations of computed variable values inside a forever loop, score list management operations, persistent data storage, etc.:

  1. The "random/seed" function is used to seed Rebol's random number generator with the current time, so that unique random numbers can be generated with the 'random function.
  2. The variable label 'start is set to hold the time when the program was started.
  3. The variable 'speed is set to an initial value of 2.
  4. A window layout is created. The /new refinement of the 'view function is used to create a window, without showing it yet. The layout is labeled 'g. The layout is 600x440 pixels in size. An image widget containing the built in 'logo.gif image is placed at coordinate 280x20. That widget is labeled 'a. A blue button widget sized 50x20 pixels is placed at coordinate 280x420. That widget is labeled 'b. Keyboard handlers for the left and right arrow keys are set up. If the left key is pressed, the position of the blue 'b button is shifted 10 pixels to the left. If the right key is pressed, the 'b button's coordinate is shifted 10 pixels to the right.
  5. A forever loop is started.
  6. The position of the 'a image is shifted down. The distance of the shift is determined by the 'speed variable. The 'as-pair function is used to create a coordinate in which the y (vertical) value is the rounded 'speed value. That incremented coordinate value is added to the current coordinate of the 'a widget. The end result of this calculation is that the widget is moved down 'speed number of pixels.
  7. A conditional 'if evaluation is used to check whether the 'a and 'b widgets are overlapping (i.e., the player piece and the falling image are touching/colliding - if so, the falling piece is being caught). If so, the position of the 'a widget is set to a random x coordinate at the top of the screen. This starts dropping a "new" logo image, once the previous falling image has been caught (there's really only one logo image in the entire game - it's just repositioned). The x value of the random coordinate is somewhere between 1-500 pixels from the left side of the window, and the y value is 20 pixels from the top of the window. The speed value is also incremented by .1 every time a falling logo is caught. This means that every 10 catches, the speed increases by one full rounded pixel value.
  8. An 'if evaluation is used to check whether the y value of the 'a widget's coordinate is greater than 440 (i.e., the image is touching the floor). If so, game play is over, so the 'break function is used to end the 'forever loop.
  9. If game play hasn't been stopped above, the program waits .01 seconds, shows all updates to the window layout (labeled 'g), and continues with another repeat of the 'forever loop.
  10. When game play ends, the score is saved, and high scores are displayed in sorted order from lowest to highest. To do this, first a "scores" file is created, if it doesn't already exist. Next, any existing scores are loaded from the file. Then the score list is appended with 2 values: the current score (which is the current time minus the start time), and the player's name (gotten using the 'request-text function). The score list is sorted by score, using the 'skip refinement (every 2 items in the list contains a score and a name (score, name, score, name, etc.)), and that sorted list is saved to the scores file. The 'request-list function is used to display all the sorted scores.
REBOL [title: "Catch Game"]
random/seed now   
start: now/time
speed: 2
view/new center-face g: layout [
    size 600x440 
    at 280x20   a: image logo.gif
    at 280x420  b: btn 50x20 blue
    key keycode [left]  [b/offset: b/offset - 10x0]
    key keycode [right] [b/offset: b/offset + 10x0]
]
forever [
    a/offset: a/offset + (as-pair 0 round speed)
    if overlap? a b [
        a/offset: as-pair (random 500) 20
        speed: speed + .1
    ]
    if a/offset/y > 440 [break]
    wait .01
    show g
]
write/append %scores ""
scores: load %scores
append scores now/time - start 
append scores request-text/title "Name:"
save %scores sort/skip scores 2 
request-list "High Scores:" scores

8.10 Guitar Chord and Scale Diagrammer

This program allows the user to quickly create and save images of guitar fretboard diagrams for chords and/or scales. When the program starts, enter the number of frets you want in your diagram (the default is 5 frets, but you could use 3 or 4 for smaller diagrams, or more for full fretboard scale diagrams). Click any fret on any string to add a dot (finger position). Click any added dot to remove it (change it back to an empty position). Right-Click any fret on any string to add a character of your choice (you could use this feature to add finger numbers, root note labels, interval labels, etc.). Click the title text ("Chord Name") to give the chord or scale a name. Right-Click the title to save the diagram to a .png image (the default image file name is the title text entered above).

  1. The program starts by getting a number of frets from the user. The 'request-text function is used, with the refinements /title and /default (a default number of 5 is presented to the user). The user's answer is labeled 'f.
  2. A block of GUI dialect code is labeled 'g. The word 'backdrop sets the background color of the window layout to white. The word 'across is used arrange the widgets across the screen. The word 'space is used to set the space between widgets to 0 in both the horizontal and vertical direction (there's usually a default space buffer between widgets - here it's set to 0x0). The word 'origin is used to set the position of the first widget in the layout to 0x0.
  3. The 'style word is use to create a new widget labeled 's, which is a 20x20 pixel box, displaying the character "|" on its face, with a font size of 20, a font color of black, and a font shadow offset of 0x0 (no shadow). There are two actions set up for the 's widget. The first block runs whenever the widget is left-clicked with the mouse (just like the normal action block you've seen in all the other examples so far). The second block runs whenever the widget is right-clicked with the mouse. When any 's widget is left-clicked, the face text is set to either "O" or "|". The 'either conditional evaluation is used to determine if the current face text is "|". If so, the face text is set to "O", if not the face text is set to "|". If any 's widget is right-clicked, the face text is set to some text requested from the user.
  4. A text widget labeled 't is added to the layout. It is 120x24 pixels in size, with bold, centered text, and a font size of 18. It is assigned 2 alternate action blocks, to react to left and right clicks of the mouse. If the text widget is left clicked, the face text is set to some text requested from the user. The 'request-text function is used, with the /default refinement, and the default text that appears in the requester is the current face text on the widget. If the text widget is right clicked, an attempt is made to save the 'p layout to an image file. The 'save function with the /png refinement takes 2 parameters: the name of the file to save to, and the data to save. In this case, the name of the file is gotten from the user, with the 'request-file function. The /save /only and /file refinements of the 'request-text function bring up a save dialogue which allows for only one file selection, with a default file name (in this case, the text of the 't widget joined with the characters ".png"). The image data is created using the 'to-image function, with the current layout (labeled 'p) as it its parameter.
  5. The 'loop function is used to run a block of code 'f number of times. Remember, the 'f label was set at the beginning of the program - it's the number of frets to include in the layout. Here, the text answer gotten from the user needs to be converted to an integer number. That's accomplished using the 'to-integer function. The looped block appends a number of 's widgets and a 'return word (newline), to the 'g layout block above.
  6. The 'g layout code block is viewed, and that layout is labeled 'p. To be clear, the label 'g refers to the GUI dialect code block used to create the layout. The label 'p refers to the actual current layout (which internally consists of lots of low level objects, functions, draw commands, etc.). That low level, currently drawn layout is what's required as a parameter of the 'to-image function.
REBOL [title: "Guitar Chord and Scale Diagrammer"]
f: request-text/title/default "Number of frets in each diagram:" "5"
g: [
    backdrop white
    across  space 0x0  origin 0x0
    style s box 20x20 "|" font-size 20 font-color black shadow 0x0 [
        face/text: either "|" = face/text ["O"] ["|"] 
    ] [
        face/text: request-text
    ]
    t: text 120x24 bold center font-size 18 "Chord Name" [
        face/text: request-text/default face/text
    ] [ attempt [
        save/png request-file/save/only/file join t/text ".png" to-image p
    ] ]
    return
]
loop to-integer f [append g [s s s s s s return]]
view center-face p: layout g

Here's a version of the program above, with an added HTML layout feature. Press the "p" key on your keyboard to create an HTML layout (web page) of any selected diagrams you've created. The HTML layout can be printed, emailed, uploaded to a web site, etc. You'll learn more about HTML in the next section of this tutorial. Come back afterward and see if you can follow the 'layout-html function more clearly:

REBOL [title: "Guitar Chord and Scale Diagrammer"]
f: request-text/title/default "Number of frets in each diagram:" "5"
layout-html: func [] [
    filelist: sort request-file/filter/title 
        ["*.png"] "Select image(s) to print:" ""
    html: copy "<html><body>"
    foreach file filelist [
        append html rejoin [
            {<img src="file:///} to-local-file file {"> &nbsp; &nbsp; }
        ]
    ]
    append html [</body></html>]
    write %chords.html trim/auto html
    browse %chords.html 
]
g: [
    at 100x100 key #"p" [layout-html]
    backdrop white
    across  origin 0x0  space 0x0
    style s box 20x20 "|" font-size 20 font-color black shadow 0x0 [
        face/text: either "|" = face/text ["O"] ["|"] 
    ] [
        face/text: request-text
    ]
    t: text 120x24 bold center font-size 18 "Chord Name" [
        face/text: request-text/default face/text
    ] [ attempt [
        save/png request-file/save/only/file join t/text ".png" to-image p
    ] ]
    return
]
loop to-integer f [append g [s s s s s s return]]
view center-face p: layout g

The apps in this section clearly demonstrate that lists and loop code structures are important additions to concepts such as variables, functions, persistent data storage, and other basic coding principles.

8.11 Additional Series Apps to Study

Here are a few more short app examples which make use of series constructs. See if you can follow the basic flow of code in each program.

A full screen presentation (try to guess which system variable holds the size of the screen). Note that the slide layouts are stored in a nested block of blocks:

REBOL [title: "Simple Presentation"]
slides: [
    [
        at 0x0 box system/view/screen-face/size white [unview]
        at 20x20 h1 blue "Slide 1"
        box black 2000x2
        text "This slide takes up the full screen."
        text "Adding images is easy:"
        image logo.gif
        image stop.gif
        image info.gif
        image exclamation.gif
        text "Click anywhere on the screen for next slide..."
        box black 2000x2
    ]
    [
        at 0x0 box system/view/screen-face/size effect [
            gradient 1x1 tan brown
        ] [unview]
        at 20x20 h1 blue "Slide 2"
        box black 2000x2
        text "Gradients and color effects are easy in Rebol:"
        box effect [gradient 123.23.56 254.0.12]
        box effect [gradient blue gold/2]
        text "Click anywhere on the screen to close..."
        box black 2000x2
    ]
]
foreach slide slides [
    view/options center-face layout slide 'no-title
]

A simple but fully functional retail cash register app:

REBOL [title: "Minimal Cash Register"]
view gui: layout [
    style fld field 80
    across
    text "Cashier:"   cashier: fld 
    text "Item:"      item: fld 
    text "Price:"     price: fld [
        if error? try [to-money price/text] [alert "Price error" return]
        append a/text reduce [mold item/text tab price/text newline]
        item/text: copy "" price/text: copy ""
        sum: 0
        foreach [item price] load a/text [sum: sum + to-money price]
        subtotal/text: form sum
        tax/text: form sum * .06
        total/text: form sum * 1.06 
        focus item
        show gui
    ]
    return
    a: area 600x300
    return
    text "Subtotal:"   subtotal: fld 
    text "Tax:"        tax: fld 
    text "Total:"      total: fld
    btn "Save" [
        items: replace/all (mold load a/text) newline " "
        write/append %sales.txt rejoin [
            items newline cashier/text newline now/date newline
        ]
        clear-fields gui
        a/text: copy ""             
        show gui             
    ]
]

A report program which calculates the daily sales total for the program above:

REBOL [title: "Daily Sales Total"]
sales: read/lines %sales.txt
sum: $0
foreach [items cashier date] sales [
    if now/date = to-date date [
        foreach [item price] load items [
            sum: sum + to-money price
        ]
    ]
]
alert rejoin ["Total sales today: " sum]

Below are two different versions of the same group chat program. The first runs in the text console. The second uses a GUI window to allow the user to input text and view messages. The messages are just stored in a text file on a web server (to use this program, you need to know a valid web site FTP URL, and use correct username and password info to write the file):

REBOL [title: "Group Chat (Console)"]
url: ftp://user:pass@site.com/public_html/chat
write/append url ""
name: copy ask "^LName:  "
forever [
    notes: copy read url
    message: ask rejoin [newpage notes "Message:  "]
    if message = "erase" [write url ""]
    if message <> "" [
        write/append url rejoin [
            now " (" name "):  " message "^/^/"
        ]
    ]
]

REBOL [title: "Group Chat (GUI)"]
url: ftp://user:pass@site.com/public_html/chat
write/append url ""
name: request-text/title "Name:"
view gui: layout [
    a: area 600x300
    text "New Message:"
    message: field 600 [
        if message/text = "erase" [write url ""]
        if message/text <> "" [
            write/append url rejoin [
                now " (" name "):  " message/text "^/^/"
            ]
        ]
        a/text: copy read url  focus message  show gui 
    ]
]

The Rebocalc app by Carl Sassenrath demonstrates how to create a tiny spreadsheet and this article explains and extends its features. This discussion at RebolForum.com produced the example below, which demonstrates an even simpler way to implement 'worksheet' features which run quickly even with hundreds of thousands of data values:

REBOL [title: "Work Sheet"]
sum: func [fld] [attempt [
    s: 0  foreach n to-block copy fld/text [s: s + (to-decimal n)]  s
]]
average: func [fld] [attempt [
    (sum fld) / (length? to-block copy fld/text)
]]
maxi: func [fld] [
    attempt [first maximum-of to-block copy fld/text
]]
compound: does [attempt [
    ((1 + (to-decimal rt/text)) ** to-integer yrs/text) *
    to-decimal p/text
]]
random/seed now
x: copy y: copy z: copy ""
loop 10000 [
     append x join random 10000 newline
     append y join random 10000 newline
     append z join random 10000 newline
]
view center-face layout [
     style col area 80x350
     style fld field 80
     style txt text 80
     across
     txt "Sum:"      txt "Average:" txt "Maximum:"     return
     a: col form x   b: col form y    c: col form z    return
     txt "Sum:"      txt "Average:" txt "Maximum:"     return
     fld [face/text: sum a      show face]
     fld [face/text: average b  show face]
     fld [face/text: maxi c     show face]             return
     txt                                               return
     txt txt "Principal:"  p:   fld "10000"            return
     txt txt "Years:"      yrs: fld "10"               return
     txt txt "Rate:"       rt:  fld ".07"              return
     txt txt "TOTAL:" fld [face/text: compound show face]
]

This version adds improved error handling and input validation, column sorting, data save and load capability, and other features:

REBOL [title: "Work Sheet"]
random/seed now
x: copy y: copy z: copy ""
loop 50000 [
    insert tail x join random 50000 newline
    insert tail y join random 50000 newline
    insert tail z join random 50000 newline
]
sum: func [fld] [
    s: 0  
    foreach num to-block copy fld/text [
        if error? try [
            s: s + (to-decimal num)
        ] [
            return join "Error: " num
        ]
    ] 
    s
]
average: func [fld] [
    attempt [
        the-sum: sum fld
        if attempt [find the-sum "error"] [return the-sum]
        the-sum / (length? to-block copy fld/text)
    ]
]
maxi: func [fld] [
    attempt [
        first maximum-of to-block copy fld/text
    ]
]
compound: func [f1 f2 f3] [
    attempt [
        (
            (1 + to-decimal f3/text) ** to-integer f2/text
        ) * to-decimal f1/text
    ]
]
ascend: false
srt: func [fld] [
    st: copy ""
    ascend: not ascend
    data: to-block copy fld/text
    sorted: copy either ascend [sort data] [sort/reverse data]
    foreach i sorted [insert tail st join i "^/"] 
    head st
]
view center-face gui: layout [
    style col area  100x250
    style fld field 100
    style txt text  100
    across
    txt "Sum:"      [set-face a srt a]
    txt "Average:"  [set-face b srt b]
    txt "Maximum:"  [set-face c srt c]                 return 
    a: col form x   b: col form y     c: col form z    return
    txt "SUM:"      txt "AVERAGE:"    txt "MAXIMUM:"   return 
    fld [face/text: sum a      show face]
    fld [face/text: average b  show face]   
    fld [face/text: maxi c     show face]              return
    txt                                                return
    txt txt "Principal:"  p:   fld "10000"             return
    txt txt "Years:"      yrs: fld "10"                return
    txt txt "Rate:"       rt:  fld ".07"               return
    txt txt "TOTAL:" fld [face/text: compound p yrs rt show face] return
    txt txt btn "Save" [
        write %worksheet.dat rejoin [
            mold a/text mold b/text mold c/text
            mold p/text mold yrs/text mold rt/text
        ]
    ]
    btn "Load" [
        if error? try [dat: load %worksheet.dat] [return]
        set-face a dat/1 set-face b dat/2 set-face c dat/3
        p/text: dat/4 yrs/text: dat/5 rt/text: dat/6
        show gui
    ]
]

To explore the potential capabilities of this app, you could, for example, add the ability to read data values from a Rebol CGI app running on a web server, make the results available to multiple users on a local network (via a TCP connection), send emails to a group of people whenever a set of calculations satisfies a given set of conditions, insert this app in a live full screen presentation, etc. Doing those things is simple with Rebol. Not so with spreadsheets.

Take a look through http://re-bol.com/short_rebol_examples.r now. Many of the apps there should be understandable at this point - in fact most are simplifications or more condensed versions of the scripts you've seen in this text so far.

9. Creating Web and Mobile Applications using Rebol CGI

In order to create "CGI" applications for a web site, you need a web server, and the Rebol interpreter must be uploaded and installed on your web server. This project contains the Uniform WAMP miniserver, with Rebol all set up and ready to run CGI scripts on Windows PCs. You can use any other LAMP or WAMP Apache web server package to run CGI scripts on your local PC, or use inexpensive shared hosting services like Hostgator or Lunarpages to run scripts on fast managed servers.

9.1 An HTML Crash Course

In order to create any sort of web app, you need to understand a bit about HTML. HTML is the layout language used to format text and GUI elements on all web pages. HTML is not a programming language - it doesn't have facilities to process or manipulate data. It's simply a markup format that allows you to shape the visual appearance of text, images, and other items on pages viewed in a browser.

In HTML, items on a web page are enclosed between starting and ending "tags":

<STARTING TAG>Some item to be included on a web page</ENDING TAG>

There are tags to effect the layout in many ways. To bold some text, for example, surround it in opening and closing "strong" tags:

<STRONG>some bolded text</STRONG>

The code above appears on a web page as: some bolded text.

To italicize text, surround it in < i > and < / i > tags:

<i>some italicized text</i>

That appears on a web page as: some italicized text.

To create a table with three rows of data, do the following:

<TABLE border=1>
    <TR><TD>First Row</TD></TR>
    <TR><TD>Second Row</TD></TR>
    <TR><TD>Third Row</TD></TR>
</TABLE>

Notice that every

<opening tag>

in HTML code is followed by a corresponding

</closing tag>

Some tags surround all of the page, some tags surround portions of the page, and they're often nested inside one another to create more complex designs.

A minimal format to create a web page is shown below. Notice that the title is nested between "head" tags, and the entire document is nested within "HTML" tags. The page content seen in the user's browser is surrounded by "body" tags:

<HTML>
    <HEAD>
        <TITLE>Page title</TITLE>
    </HEAD>
    <BODY>
        A bunch of text and <i>HTML formatting</i> goes here...
    </BODY>
</HTML>

If you save the above code to a text file on your hard drive named "yourpage.html", then click to open it, you'll see your browser open to a page entitled "Page title", with the text "A bunch of text and HTML formatting goes here...". Upload that file to your web server (typically in the ".../WWW/" or ".../HTDOCS/" folder), and surf to http://yoursite.com/yourpage.html , and you'll see the page appear in your browser. If you're running a web server program on your local PC, then you'll point your browser to http://localhost/yourpage.html or http://127.0.0.1/yourpage.html .

All web pages work that way. This tutorial is in fact just an HTML document stored in a folder on the author's web server (and the domain re-bol.com is registered to point to a folder on that web server). Click View -> Source in your browser, and you'll see the HTML tags that were used to format this document (try doing that now).

9.1.1 HTML Forms and Server Scripts - the Basic CGI Model

The following HTML example contains a "form" tag inside the standard HTML "head" and "body" tags. Then, nested inside the form tags are a INPUT TYPE="TEXT" (text input field) tag, and a INPUT TYPE="SUBMIT" (submit button) tag. Note that the form tag contains an ACTION="http://yoursite.com/your_rebol_script.cgi" link, which points to a Rebol CGI script URL:

<HTML>
    <HEAD><TITLE>Data Entry Form</TITLE></HEAD>
    <BODY>
        <FORM ACTION="http://yoursite.com/your_rebol_script.cgi">
            <INPUT TYPE="TEXT" NAME="username" SIZE="25">
            <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
    </BODY>
</HTML>

Forms can contain tags for a variety of input types: multi-line text areas, drop down selection boxes, check boxes, etc. See http://www.w3schools.com/html/html_forms.asp for more information about form tags.

You can use the data entered into any form by pointing the action address to the URL at which a specific rebol script is located. For example, 'FORM ACTION="http://yoursite.com/your_rebol_script.cgi"' in the form above could point to the URL of the following CGI script, which is saved as a text file on your web server. When a web site visitor clicks the submit button in the above form, the entered data is sent to the following program, which in turn does some processing, and prints output directly back to the user's web browser. NOTE: Remember that in Rebol curly brackets are the SAME AS QUOTES. Curly brackets are used in all the following examples, because they allow for multiline content and they help improve readability by clearly showing where strings begin and end:

#!/home/your_user_path/rebol -cs
REBOL []
print {content-type: text/html^/}  
submitted: decode-cgi raw: read-cgi

print rejoin [
    {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}
    {Hello } submitted/2 {!}
    {</BODY></HTML>}
]

In order for the above code to actually run on your web server, a working Rebol interpreter must be installed in the path designated by "/home/your_user_path/rebol -cs". For example, if the Rebol interpreter is in the same path as the script, that line should read "#!./rebol -cs".

The first 4 lines of the above script are basically stock code. Include them at the top of every Rebol CGI script. Notice the decode-cgi read-cgi line - it's the key to retrieving data submitted by HTML forms. In the code above, the decoded data is assigned the variable name "submitted". The submitted form data can be manipulated however desired, and output is then returned to the user via the "print" function. That's important to understand: all data printed by a Rebol CGI program appears directly in the user's web browser (i.e., to the web visitor who entered data into the HTML form). The printed data is typically laid out with HTML formatting, so that it appears as a nicely formed web page in the user's browser.

Any normal Rebol code can be included in a CGI script. You can perform any type of data storage, retrieval, organization, and manipulation that can occur in any other Rebol program. The CGI interface just allows your Rebol code to run online on your web server, and for data to be input/output via web pages which are also stored on the web server, accessible by any visitor's browser.

9.2 A Standard CGI Template to Memorize

Stand-alone CGI programs typically print an initial HTML form to obtain data from the user. In the initial printed form, the action address typically points back to the same URL address as the script itself. The script examines the submitted data, and if it's empty (i.e., no data has been submitted), the program prints the initial HTML form. Otherwise, it manipulates the submitted data in way(s) you choose and then prints some output to the user's web browser in the form of a new HTML page. Here's a basic example of that process, using the code above:

#!/home/your_user_path/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: decode-cgi raw: read-cgi

; The 4 lines above are the standard Rebol CGI headers.
; The line below prints the standard HTML, head and body
; tags to the visitor's browser:

print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}

; Next, determine if any data has been submitted.
; Print the initial form if empty.  Otherwise, process 
; and print out some HTML using the submitted data.  
; Finally, print the standard closing "body" and "html"
; tags, which were opened above:

either empty? submitted [
    print {
        <FORM METHOD="POST">
        <INPUT TYPE="TEXT" NAME="username" SIZE="25">
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
        </BODY></HTML>
    }
] [ 
    print rejoin [{Hello } submitted/2 {!}]
    print {</BODY></HTML>}
]

Using the above standard outline, you can include any required HTML form(s), along with all executable code and data required to make a complete CGI program, all in one script file. Just memorize or copy/paste that boilerplate outline to get started writing CGI scripts.

10. Example CGI Applications

10.1 Generic CGI App, With HTML Form

Here is a basic CGI template that prints a form for user data entry. Learn a bit more about HTML forms, and process the submitted/x variables, and you can create some very powerful web apps. A demo of this script is available at http://guitarz.org/rebol3:

#!./rebol -cs
REBOL [title: "Generic CGI Application, With HTML Form"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}
submitted: decode-cgi read-cgi
either empty? submitted [
    print {
        <FORM METHOD="POST">
        <INPUT TYPE="TEXT" NAME="username" SIZE="25">
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
        </BODY></HTML>
    }
] [ 
    print rejoin [{Hello } submitted/2 {!}]
    print {</BODY></HTML>}
]

10.2 CGI Photo Album

Here's a simple CGI program that displays all photos in the current folder on a web site, using a foreach loop. A demo of this script is available at http://guitarz.org/rebol3/photos.cgi:

#!./rebol -cs
REBOL [title: "CGI Photo Album"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Photos</TITLE></HEAD><BODY>}
folder: read %./
count: 0
foreach file folder [
    foreach ext [".jpg" ".gif" ".png" ".bmp"] [
        if find file ext [
            print [<BR> <CENTER>]
            print rejoin [{<img src="} file {">}]
            print [</CENTER>]
            count: count + 1
        ]
    ]
]
print join {<BR>Total Images: } count
print {</BODY></HTML>}

10.3 CGI Text Chat

Here's a short web chat app. Users can type the word "erase" in the name field to delete all former texts. A demo of this script is available at http://guitarz.org/rebol3/chat.cgi:

#!./rebol -cs
REBOL [title: "Group Chat"]
print {content-type: text/html^/}
url: %./chat.txt
write/append url ""
submitted: decode-cgi read-cgi
if submitted/2 = "erase" [write url ""]
if submitted/2 <> none [
    write/append url rejoin [
        now " (" submitted/2 "):  " submitted/4 "^/^/"
    ]
]
notes: copy read url
print rejoin [
    "<pre>" notes "</pre>"
    {<FORM METHOD="POST">
        Name:<br>
        <input type=text size="65" name="username"><br>
        Message:<br>
        <textarea name=message rows=5 cols=50></textarea><br>
        <input type="submit" name="submit" value="Submit">
    </FORM>}
]

10.4 A Generic Drop Down List Application

The following example demonstrates how to automatically build lists of days, months, times, and data read from a file, using dynamic loops (foreach, for, etc.). The items are selectable from drop down lists in the printed HTML form. A demo of this script is available at http://guitarz.org/rebol3/drop.cgi::

#!./rebol3 -cs
REBOL [title: "Dropdown Lists"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Dropdown Lists</TITLE></HEAD><BODY>}
submitted: decode-cgi read-cgi
if not empty? submitted [
    print rejoin [{NAME SELECTED: } submitted/2 {<BR><BR>}]
    selected: rejoin [
        {TIME/DATE SELECTED: }
        submitted/4 { } submitted/6 {, } submitted/8
    ]
    print selected
    quit
]
; If no data has been submitted, print the initial form:
print {<FORM METHOD="POST">SELECT A NAME:  <BR> <BR>}
names: read/lines %users.txt
print {<select NAME="names">}
foreach name names [prin rejoin [{<option>} name]]
print {</option> </select> <br> <br>}
print { SELECT A DATE AND TIME: }
print rejoin [{(today's date is } now/date {)} <BR><BR>]
print {<select NAME="month">}
foreach m system/locale/months [prin rejoin [{<option>} m]]
print {</option> </select>}
print {<select NAME="date">} 
for daysinmonth 1 31 1 [prin rejoin [{<option>} daysinmonth]]
print {</option> </select>}
print {<select NAME="time">}
times: ["10am" "11am" "12pm" "1pm" "1:30pm" "4:15pm" "7:30pm"]
foreach time times [prin rejoin [{<option>} time]]
print {</option> </select><br> <br>}
print {<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit"></FORM>}

The "users.txt" file used in the above example may look something like this:

nick
john
jim
bob

10.5 jsLinb and Sigma IDE

The tutorial at http://re-bol.com/jslinb explains how to build complex and powerful HTML/CSS/Javascript client user interfaces (GUIs) to Rebol CGI server applications, using the totally free (LGPL licensed) jsLinb library and Sigma Visual IDE. You can use those tools to create professional looking web sites and web applications which run in virtually any web browser (from Firefox 1.5 and IE6, to any modern browser), on just about any mobile device or desktop OS in existence. The HTML5 apps created with that tool can be packaged as mobile apps and distributed to every popular app store. jsLinb/Sigma together with Rebol CGI scripts, provide a complete and simple to use tool set, which enable you to create powerful data management applications that run basically anywhere, on the web, mobile devices, any desktop PC, etc. It takes just a few hours to learn. Be sure to check it out!

10.6 Summary

As with the CRUD, graphics, and other basic code examples you saw earlier, you'll notice that creating CGI apps is all about using variables, functions, series operations, loops, conditional evaluations and other similar language structures and approaches to managing data. With a little creative experimentation, you can accomplish quite a few useful and varied computing tasks with Rebol, using only the code patterns you've seen up to this point. Don't worry though, Rebol has many more powerful features and capabilities - you've only just scratched the surface.

11. Why Rebol? Dialects and Other Uniquely Productive Features

Rebol was created to be the simplest, most productive, and least bloated software development tool available anywhere. Although marketing efforts by Rebol Technologies were not a great commercial success, the technical achievements embodied by Rebol have been an unmatched elsewhere. A detailed objective explanation of this topic is available here.

11.1 Basic Language Features

Below is a quick list of features which enable Rebol to remain remarkably light weight and productive compared other tools:

  • Many built in data types which are automatically recognized and handled appropriately by the interpreter. Not just numbers and text, but dates, times, money values, coordinate pair values, IP addresses, etc.
  • A complete lack of all unnecessary syntax (semicolons, commas, indentation, quotes, etc.), so that code and data can be read more naturally, concisely, and without clutter, and so that less processing power, network bandwidth, and storage space is required to handle data and code of every kind.
  • The ubiquitous "series" structure, which is used to manage lists of all types: strings, arrays, linked lists, data in network ports, file system listings, values returned from database systems, graphic screen layouts, Internet protocols such as TCP, UDP, HTTP, FTP, email, etc., and code patterns themselves, are all treated the same way, as series.
  • The unique 'parse function and dialect, which can be used in dealing with tasks typically handled by Regular Expressions, to perform complex searches and pattern matching algorithms in structured and unstructured data. 'parse code can easily make use of existing functions, and can itself be composed into new functions. 'parse code is also much more human readable than regex - and it performs faster. Parse alone is a reason that some developers choose to use Rebol. It provides a simple and powerful solution to many of the most common data processing activities for which computer systems are implemented.
  • Native ability to parse data formats such as .csv, HTML, XML, etc., using dedicated high level functions (as well as series functions and lower level 'parse rules).
  • Simple data persistence features such as read, write, save, and load which, along with all the standard series functions, eliminate the need for 3rd party database systems in many cases (see http://re-bol.com/rebol-multi-client-databases.html).
  • Built-in low level, and high level, native abilities to draw graphics, display and manipulate images, attach events, timers, etc. to interface elements, etc. And of course, an even higher level built-in GUI dialect which is built from those capabilities.
  • Useful built-in help, source, and other introspection features for all available functions, language constructs, and interpreter internals.
  • Unmatched tiny binary size and ease of deployment.
  • No third party browser, library, or server incompatibility issues to deal with. User interfaces, IO, and all infrastructure requirements are all completely contained in the single Rebol interpreter binary.
  • The ability to compose and manipulate data and code constructs with equal freedom. Rebol's code=data nature is a metaprogramming facet which fits naturally with dialect design, and generally helps to enable a powerfully malleable language structure.

Natural, native, easy interactivity is designed between all these fundamental computing capabilities, from the ground up. All of the basic computing features which universally make up all types of computing activity (graphics, networking, structured data management, user interfaces, etc.) are built into the fabric of the Rebol language, and are easily composable.

Developer productivity is made a priority in Rebol, at every level and in every facet of the language and tooling design, using simple mental models, language design and syntax choices (even such basic decisions as using square brackets, because they require fewer key strokes). The end result is a dramatically noticeable lack of friction, pain, and code size/complexity, compared to the work routines typical of nearly every other software development stack in common use (yes, even compared to Python, Ruby, Lua, Clojure, Javascript, etc.).

11.2 Dialects

In addition to the basic features above, Rebol was created from the ground up to create domain specific "dialect" syntax structures (DSLs, or domain specific languages). When combined with all of Rebol's basic language features, dialects provide a way to simplify the code needed to achieve any conceivable computing task, in ways which are unheard of in other programming languages.

For example, Rebol's built in GUI dialect ("VID") provides benefits which OOP and other approaches claim as their very reason for being. The simple code below creates and instantiates a new type of button object, with properties inherited from an already existing button object structure:

view layout [
    style newbtn btn tan 400x50 [alert "clicked"]
    newbtn "button 1"
    newbtn "button 2"
]

That code is really as concise as could possibly be created for the task. The dialect gives users control over salient properties of the underlying code structure (constructed of typical object definitions, methods, properties, etc.), using a succinct syntax which is much more direct and clear to work with. The idea that composing functionality via higher layers of language structure, in this way, is a core design concept in Rebol.

It's important to note that Rebol's native and 3rd party GUI dialects are all so simple to read and write, if fact, that it's typically much faster to layout screens using them, than a visual drag and drop ("WYSIWYG") tool in a graphical IDE. *The code itself*, without any heavy third party tools used to aid complex code creation, provides the simplest possible solution to the problem. It's important to understand that dialect code can be created and manipulated using basic Rebol language structures. The idea that such complex layers of computation can be *composed via layers of concise language constructs*, as opposed to layers of brittle functions, objects, libraries, and other traditional "machine centric" code structures - or even worse, layers of heavy tooling which can't be easily integrated in code - is an unusual technique which most programming languages do not yet use to encapsulate functionality.

You can find existing Rebol dialects for 2D and 3D graphics and animation, tools which aid in the layout of HTML, CSS, PDF and other formats, graphic chart layout, tools which aid in code composition, manipulation and compilation of other languages (check out the powerful Rebol Flash dialect), complex console printing and interaction, and many other domains. Designers of dialects can create specialized code structures to drastically reduce the complexity of syntax patterns needed to handle many other domain specific problems.

Rebol is still ripe for creating dialects related to any new conceivable problem domains which may come in the future. Dialects are powerful not just because they provide another way to leverage the power of language, but because that method is so intuitive for the human mind to grasp and work with naturally. The dialect approach is also infinitely variable according to taste, style, and the unique needs encountered in any domain of work (in the same way that natural language dialects have been implemented in virtually every domain of real life work). The ability to create languages which are structured as needed or desired to complete any specific category of tasks, is the key to dramatic productivity, when compared to other contrived and complex methods which programming languages have used up until this point.

11.3 Remarkable Capability, Lightweight Tooling and Reduced Code Complexity

The broad and deep capability embodied by the tiny Rebol interpreter is a testament to just how orders of magnitude of size and complexity can be reduced by implementing the language solutions which Rebol makes available. Python, Ruby, Lua, Clojure, Javascript and the popular frameworks implemented in those languages are behemoths when compared to Rebol. You will find that Rebol examples nearly universally consist of only a small fraction of the code volume needed in any other language, even in frameworks intended to "boost productivity" in more bloated environments. Deploying Rebol apps is dramatically fast and simple, and upgrade difficulties, reliance on third party tools, and other maintenance chores are reduced to virtually nothing compared to other tools.

See what Atronix Engineering has accomplished, and watch this video by Atronix's founder to understand how Rebol's productive capabilities have enabled their large commercial projects.

You may also want to see the study on the well respected redmonk.com site which ranks Rebol as *the most productive* of all general purpose programming languages.

Rebol has been used in production environments for more then a decade and a half, and it represents a uniquely successful, readable and writable approach which has been sorely missed by the designers of nearly every other programming language. More than any other development tool, Rebol was created to be a virtual language toolkit, enabling simple language interfaces to be created which can handle complex coding problems easily. Its other "nice" features, the single unified series data/code structure, consistent and unified native interfaces to data sources, the readable 'parse syntax, numerous native data types, etc., all work together with simple code constructs which dialects enable, to deal with even the most complex programming endeavors, easily and more concisely than can be achieved by any other paradigm.

12. Learning More

See http://re-bol.com/rebol_quick_start_part2.html for part 2 of this text.

To learn more about Rebol, see http://business-programming.com for a full 850 page book that documents everything you need to know to create many types of applications with Rebol (over 100 complete working programs are included in that text), along with much deeper coverage of a huge assortment of useful Rebol techniques and topics.

http://learnrebol.com to learn about the newest version of Rebol (Saphir/Atronix R3 and R3-GUI), which is open source and available for Android mobile platforms, Raspberry Pi, as well as desktop and server environments.

There are many other stand-alone tutorials linked in the documents above, which will help you dig deeper into particular capabilities. For example, if you want to learn how to create rich web and mobile apps, and all varieties of client-server apps, see http://re-bol.com/jslinb and http://re-bol.com/rebol-multi-client-databases.html.

An updated list of documentation links is maintained at rebolforum.com. You can go to that forum to ask questions of users in the Rebol community.W