Header Bar

Cross Platform App Development with Rebol 3 Saphir

The Easiest Way to Create Applications for Android, Desktop PCs, and Web, using Open Source R3
By: Nick Antonaccio
Updated: 10-11-2013
To ask questions, visit http://rebolforum.com
See http://re-bol.com/examples.r3 for some quick code examples.
See business-programming.com for a complete 850 page text about programming with R2.

Contents:

1. Introduction: The Current State of Software Development - Why R3?
1.1 Rebol On All Platforms
1.2 Bla Bla Bla, Another Language - I've Heard It All Before
2. Getting Started
2.1 Writing Code - You'll Need a Text Editor App
2.2 Other Helpful Apps
3. R3 GUI Basics
3.1 Views and Widgets on a Screen
3.2 More Widgets, and BUILT IN DOCUMENTATION
3.3 Pop-Up Message Boxes and Simple Requestors
3.4 GUI Actions
3.5 Getting and Setting GUI Data Values
3.6 Writing and Reading GUI Data to/from Files
3.7 Some Caveats About Displaying Data Values in Text Widgets: Data Types
3.8 A Little Note Pad App
3.9 Accessing Facets of a Widget
3.10 Saving to CSV Files - a "Contacts" App
3.11 More about Layout
3.12 More Actors
3.13 Custom Styles - The Calculator and Sliding Tile Puzzle Apps
3.14 Home-Made Requestors
3.15 Additional Essential Resources
3.16 A Telling Comparison
4. Getting Down to Business: Data Manipulation and Computation
4.1 Automatically Loading the GUI Library and other Scripts/Settings on Startup
4.2 Basics of Using REBOL Functions
4.3 Conditional Evaluations
4.4 Some More Useful Functions
5. Lists, Tables, and the "Foreach" Function
5.1 Managing Spreadsheet-Like Data
5.2 Some Simple List Algorithms (Count, Sum, Average, Max/Min)
5.3 Searching
5.4 Gathering Data, and the "Copy" Function
5.5 List Comparison Functions
5.6 Creating Lists From User Input - The Inventory Program
5.7 Three Useful Data Storage Programs: Inventory, Contacts, Schedule
5.8 Working With Tables of Data: Columns and Rows
5.9 Additional List/Block/Series Functions and Techniques
5.10 Sorting Lists and Tables of Data
5.11 CSV Files and the "Parse" Function
5.12 2 Paypal Report Programs, Analyzed
5.13 Some Perspective about Studying These Topics
6. Quick Review and Clarification
7. COMPLETE APPLICATION EXAMPLES
7.1 Math Test
7.2 Scheduler
7.3 Parts Database
7.4 Time Clock and Payroll Report
7.5 A Universal Report Generator, For Paypal and any other CSV Table Data
7.6 Catch Game
7.7 Text Table Examples
7.8 Contacts App With Text Table
7.9 FTP Examples
7.10 Web Page Editor
7.11 Currency Calculator
7.12 Reviewing and Using the Code You've Seen To Model New Applications
8. User Defined Functions and Imported Code Modules
8.1 "Do", "Does", and "Func"
8.2 Return Values
8.3 Scope
8.4 Function Documentation
8.5 Doing Imported Code
8.6 Separating Form and Function in GUIs - The Check Writer App
9. 2D Drawing, Graphics, and Animation
9.1 Basic Shapes
9.2 Animation
10. Creating Web Applications using REBOL CGI
10.1 An HTML Crash Course
10.2 A Standard CGI Template to Memorize
11. Example CGI Applications
11.1 Generic CGI App, With HTML Form
11.2 CGI Photo Album
11.3 CGI Text Chat
11.4 A Generic Drop Down List Application
11.5 Bulletin Board
11.6 Event Attendance
11.7 GET vs POST Example
12. Network Ports
12.1 Transferring Data and Files over a TCP Connection
12.2 Transferring Binary Files Through TCP Network Sockets
13. Parse (REBOL's Answer to Regular Expressions)
13.1 Using Parse to Load Speadsheet CSV Files and Other Structured Data
13.2 Using Parse's Pattern Matching Mode to Search Data
14. Objects
15. Organizing Efficient Data Structures and Algorithms
15.1 A Simple Loop Example
15.2 A Real Life Example: Checkout Register and Cashier Report System
16. More REBOL Language Fundamentals
16.1 Comments
16.2 Function Refinements
16.3 White Space and Indentation
16.4 Multi Line Strings, Quotes, and Concatenation
16.5 More About Variables
16.6 Data Types
16.7 Random Values
16.8 More About Reading, Writing, Loading, and Saving to and from Varied Sources
16.9 Understanding Return Values and the Order of Evaluation
16.10 More About Conditional Evaluations
16.11 More About Loops
16.12 More About Why/How Blocks are Useful
16.13 REBOL Strings
17. More Essential Topics
17.1 Built-In Help and Online Resources
17.2 "Compiling" REBOL Programs - Distributing Packaged Executable Files
17.3 Common REBOL Errors, and How to Fix Them
17.4 Creating Apps on Platforms That Don't Support GUI Interfaces
18. REAL WORLD CASE STUDIES - Learning To Think In Code

1. Introduction: The Current State of Software Development - Why R3?

Modern software development tools are overly complex. Creating even a small program that allows users to complete the most basic computing task typically requires the installation of a complex IDE, bloated SDK, and other supporting tools. Developers need a working knowledge of diverse chains of patched together tools, library APIs, database systems, etc. to create even the simplest application. For most developers, the thought of building mobile apps, desktop GUI programs, and web applications represents mastering a tremendous variety of incongruent work flow patterns, incompatible data formats, and inconsistent development methodologies that sap productivity at every turn. For most developers, this is just life.

We live in a time when ubiquitous inexpensive handheld wireless devices connect us all to an enormous world of useful data resources. Those devices enable critical data management, communications, business transactions, entertainment, and other practical benefits which deeply affect the nature and quality of our daily lives. The primary frustrations of device ownership have been eliminated by new generations of technology. Users don't need to know how networks work to connect to the Internet. Cameras, microphones, speakers, GPS and other sensors are built into every tablet, phone, netbook, and desktop PC, and they work transparently without having to install hardware, drivers, or OS patches. Tiny form factors and inexpensive cellular network connections enable new types of applications that weren't practical even on laptop machines from a few years past. The processing speeds of the least expensive modern phones and tablets run circles around the best desktop PCs of a decade ago. Deep data content of every sort, in every field of study and interest, is available instantly online to anyone around the globe. Mainstream hand held hardware is tiny, the interfaces are beautiful, standard formats exist for virtually every type of data (images, audio, video, structured tables of text and numeric data, etc.), and well known operating systems and software all function in a relatively uniform and familiar way, so that everything just "works" the way users expect. Being able to use the amazing programmable network-connected computing power in our pockets, and all around us, represents enormous cultural potential that has never before been available, and the ability to create custom applications which leverage that tremendous power will only continue to grow in importance for future generations.

This situation is fantastic for users of "apps", but for software developers, the current landscape of tools is a horrendously painful mess. The remnants of every legacy software and hardware solution that has led up to the current state of affairs, still haunts and infects the process of writing all sorts of code. Numerous attempts to standardize on different commercially motivated platforms and formats have led to the need to support a huge variety of data structures, language syntaxes, and tool sets. To even begin creating a "hello world" app for Android, you need to install hundreds of megabytes of software on a desktop computer: an integrated development environment, software development kit, device emulator, and any "productivity enhancing" tools that help ease the wildly complex process required to write even the simplest applications. Even for seasoned developers, it's all such a weighty endeavor. And if you intend to port your applications across mobile, desktop and web platforms, you need truly deep experience, skill, and lots of time. It's not uncommon to see teams of professional engineers devote their full time occupations to building and supporting a single application. Despite the fact that Android is an open platform, today's devices feel closed, or very least, extremely cumbersome for developers. The hurdles to begin learning are nearly impossible for all but full time developers. Gone are the days when even hobbyists could write useful custom applications for their personal and business computing devices. There was a time in our history when this activity was just as popular, accessible and even fun for users as running commercial apps. It's a shame, because never before has there been such a tremendous variety of knowledge and useful computing capability sitting at our fingertips.

1.1 Rebol On All Platforms

For more than a decade, a small group of developers coding in Rebol have known what it means to be truly productive, using a single simple tool to create applications of all types, for every popular platform that has come and gone (more than 40 hardware platforms and operating systems so far, and the web). All the core components that make modern computing possible - graphic user interfaces, network connectivity, manipulation of standard data formats, etc. are handled in Rebol using the simplest possible syntax, and implemented using a tiny interpreter that runs exactly the same way on every operating system. Rebol was designed from the ground up to eliminate complexity caused by patching together disparate tools, and it succeeds brilliantly in that regard. Rebol side-steps most of the mess that has evolved over the years in mainstream software development technology, and for a wide range of common development tasks, it just works, quickly, easily, and more simply than can be imagined by anyone mired in the mess of traditional programming tools. Rebol has a special ability to wrap new capabilities into simple language structures ("dialects", domain specific languages, or "DSL"s) which control multiple layers of computing functionality. It goes far beyond the abilities promised by old and rigid OOP approaches, and it has a proven record of reducing complexity in widely varied development tasks.

Recently, Rebol version 3 ("R3") was released by Carl Sassenrath as an open source project, and a version for Android named "Saphir" is forked and maintained by the Saphirion group. Free, open source R3 Saphir releases also exist for Windows, Mac, and Linux desktop operating systems, and R3 server versions enable easy and portable web development strategies. In most cases, Saphir R3 download sizes range between .5 - 1.5 megabytes, with GUI, networking, and all other required components included, and it's shockingly simple to learn and use. An entire development toolkit for R3 requires less than a minute to install (absolutely no hefty SDK or IDE installations required, even for Android development), and it works the exact same way on every platform, without any changes to code or workflow routine. Even people who spend the majority of their time doing things other than writing code, can learn to create powerful custom business and personal applications with it, quickly and easily, on any chosen platform. Despite it's extremely simple nature, R3 is not a hobbyist toy or limited learning environment. Rebol is a powerful and deep professional tool which has proven itself in numerous critical commercial projects, across a wide array of demanding industry environments, around the world, for more than a decade. Whether you want to create a quick schedule app or email program with special features for personal use, build business applications to handle inventory, sales, employee management, and other core operations for you or your clients, create niche commercial applications to sell to hobbyists and professionals around the world, build distributed apps to orgranize group activities for your local school, design web apps to manage membership routines for an online club, create hardware interface apps to control computerized machinery in your home or business, or develop systems to manage robotics at brand name manufacturing facilities, Rebol has been used to do it all, and more.

For most of it's life, Rebol 2 was a closed source commercial tool which thrived among only a small community of users who communicated privately. With the release of open source R3 and the new potential for Android development, in addition to more than 40 legacy platforms, and web development, Rebol has attracted a new group of coders interested in its practical and productive capabilities for creating software of all sorts. This book will show you how easy it is to get started.

1.2 Bla Bla Bla, Another Language - I've Heard It All Before

For years, developers have been hearing that some "new" language technology will supposedly cut their development time by orders of magnitude. That sort of talk is so cheap that it just falls on tired, deaf ears. This introductory section will provide a few short examples to substantiate some of the productive capabilities which Rebolers enjoy (nothing else even comes close to it's simplicity). Using R3, you can build a GUI "hello world" application for Android, Windows, Mac, and/or Linux as simply as this:

load-gui
view [text "Hello World!"]

The program above is much more than the typical text-based console "hello world" app. It's actually a complete windowed form, capable of displaying all sorts of other useful "graphic user interface" (GUI) widgets. Here's another simple program that displays some text entry fields, buttons, lists, and other common GUI widgets. Notice that every single widget word maps directly to something that appears on screen. There is no wasted syntax - you really don't even need to understand anything about "programming" to follow what this code does:

load-gui
view [
    field
    area
    check
    radio
    text-list
    text-table
    drop-down
    button
]

Here's the code for a little note pad app that allows you to create, load, edit, and save a text file. You could use it to store an editable to-do list, shopping list, notes and reminders, or other free form data. The first two lines are stock code that you'll see at the beginning of every example in this section. There's a text area widget and two buttons which load and save the notes.txt file. It's pretty easy to follow the code, even without any formal introduction to Rebol:

REBOL [title: "Tiny Note Editor"]
do %r3-gui.r3
view [
    a: area
    button "Load" on-action [attempt [set-face a read/string %notes.txt]]
    button "Save" on-action [write %notes.txt get-face a  alert "Saved"]
]

Here's a short program to calculate restaurant tips. Like every other app here, it can be run instantly on any Android phone, tablet, or device, or on any desktop/laptop PC, using the exact same code:

REBOL [title: "Tip Calculator"]
do %r3-gui.r3
view [
    f: field "49.99"
    t: field ".20" on-action [
        set-face x (to-decimal get-face f) * (1 + (to-decimal get-face t))
    ]
    x: title "Total: "
]

Here's an example that displays a block of data in graphic bar chart format. It consists of 2 stock header lines, 1 line of data to display, and 1 short line of actual code. Like every other R3 example in this text, all you need to create and run this application on any platform is a text editor and the tiny R3 interpreter. This app, along with the R3 interpreters for every popular OS platform, could be quickly emailed to friends or co-workers, and opened instantly on any device each user happens to have available:

REBOL [title: "Bar Chart"]
do %r3-gui.r3
d: ["March" 13 "April" 9 "May" 21 "June" 29 "July" 10]
g: [] foreach [m v] d [append g reduce ['button m v * 10]] view g

Network access and web protocols are built into R3 natively, to provide easy acces to all types of online data. Here's a variation of the above program, which displays a chart of live data read directly from a web URL (http://learnrebol.com/chartdata). Change the data at the web URL, run the app, and the bar chart displays the appropriate graphic adjustments:

REBOL [title: "Bar Chart - Live Online Data"]
do %r3-gui.r3
d: load http://learnrebol.com/chartdata
g: [] foreach [m v] d [append g reduce ['button m v * 10]] view g

Here's a grid display, typical of any app that involves managing tables of text, numbers, or other data. Rows can be added or removed, cells can be edited manually by the user, and the values are sortable and filterable by clicking column headers (months are automatically arranged chronologically, text alphabetically, numbers ordinally, etc.). Most of this example is just the data to be displayed. You don't really even need to understand anything about programming to follow this code:

REBOL [title: "List-View/Grid display"]
do %r3-gui.r3
view [
    text-table ["Text" 100 "Dates" 200 "Numbers"] [
        ["abcd"  1-jan-2013   44]
        ["bdac"  27-may-2013  4 ]
        ["dcab"  13-aug-2014  5 ]
    ]
]

R3 can be used to create full featured web applications. Here's a variation of the above program, which reads data created by an R3 web app running at http://learnrebol.com/griddata.cgi. Run it several times to see the updated data generated each time by the web app:

REBOL [title: "List-View/Grid display"]
do %r3-gui.r3
webdata: load to-string read http://learnrebol.com/griddata.cgi
view [text-table ["Text" 100 "Dates" 200 "Numbers"] (webdata)]

Here's the code for the web app that creates the random data displayed in the GUI grid above:

#!./rebol3 -cs
REBOL []
random/seed now/time
print {content-type: text/html^/}
data: copy {}
loop 100 [
    append data rejoin [
        "[" mold random "abcd" " " random now/date " " random 100 "]"
    ]
]
print data

Here's the code for another simple web app. It centers and displays all photos found in a given folder on a web server, along with the total count of all displayed images. A demo of this script is available at http://learnrebol.com/photos.cgi. Web apps like this can run on any computing device that has an Internet connection, even if R3 isn't installed on the device. All you need is a web browser (the code runs on the web server, and pushes out results for the browser to see):

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

Here's a little web chat app running at http://learnrebol.com/chat.cgi:

#!./rebol3 -cs
REBOL [title: "Group Chat"]
print {content-type: text/html^/}
url: %./chat.txt
submitted: parse (to string! read system/ports/input) "&="
foreach item submitted [replace/all item "+" " "]
if submitted/2 <> none [
    write/append url rejoin [
        now " (" submitted/2 "):  " submitted/4 "^/^/"
    ]
]
notes: dehex copy read/string 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>}
]

Here's a small graphic sliding tile game. You can imagine that no complex GUI builder tool was required to create this code. It's simple and readable enough that a text editor and the built in help facilities of Rebol are all you need. The actual layout code is 5 lines. Have you ever seen code this simple used to create a game for Android (or even a desktop machine)? No IDE, SDK, build scripts, etc. are needed either - just download the small R3 interpreter to your Android device, or your PC, click the edited plain text code file, and it runs the same on every platform, with graphics, touch events and all, without any changes to the code:

REBOL [title: "Sliding Tile Puzzle"]
do %r3-gui.r3
stylize [
    p: button [
        facets: [init-size: 60x60  max-size: 60x60]
        actors: [
            on-action: [
                t: reduce [face/gob/offset x/gob/offset]
                face/gob/offset: t/2  x/gob/offset: t/1
            ]
        ]
    ]
]
view/options [
    hgroup [ 
        p "8"   p "7"   p "6"   return
        p "5"   p "4"   p "3"   return
        p "2"   p "1"   x: box 60x60 white
    ]
] [bg-color: white]

While on the topic of games, it should be noted that R3 allows you to draw graphics and create animations very easily. Here's a quick example:

REBOL [title: "3D Box"]
do %r3-gui.r3
bck: make image! 400x220
view/no-wait [image bck] 
draw bck to-draw [
    fill-pen 200.100.90
    polygon 20x40 200x20 380x40 200x80
    fill-pen 200.130.110
    polygon 20x40 200x80 200x200 20x100
    fill-pen 100.80.50
    polygon 200x80 380x40 380x100 200x200
] copy []
do-events

Here's a complete arcade game with image animation, collision detection, keyboard event controls, score keeping, and more. Try to catch the falling fish. Be careful, it gets faster as you go!

REBOL [title: "Catch Game"]
do %requestors.r3
fish: load http://learnrebol.com/r3book/fish2.png
s: 0  p: 3  random/seed now/time
stylize [
    box: box [facets: [max-size: 50x10]]
    img: image [facets: [max-size: 50x20 min-size: 50x20]]
]
view/no-wait/options [
    t: text"ARROW KEYS" y: img 50x20 (fish) pad z: box blue
] [
    shortcut-keys: [
        left  [z/gob/offset/1: z/gob/offset/1 - 50 draw-face z]
        right [z/gob/offset/1: z/gob/offset/1 + 50 draw-face z]
    ] 
    min-hint: 600x440  bg-color: white
]
forever [
    wait .02
    y/gob/offset/2: y/gob/offset/2 + p draw-face y show-now y
    if inside? y/gob/offset (z/gob/offset - 49x0) (z/gob/offset + 49x10)[
        y/gob/offset: random 550x-20 s: s + 1 set-face t form s  p: p + .3
    ]
    if y/gob/offset/2 > 425 [alert join "Score: " s unview unview break]
]

Here's an R3 version of a program found in virtually every GUI instructional text - a basic calculator. Blink, and you'll miss the code for this one. There are no other files, layout templates, initialization scripts, or tools required to run this app on any platform. This is the entire, completely portable program. As you can imagine, with so little code, there's a short learning curve to fully understand how examples like this work. Compare this code to C++, Visual Basic, or even the simplest possible RFO Basic example (that last example was written by the author of this text to demonstrate the nearest comparably easy and productive Android development tool available) - and each of those examples runs only on a single operating system. Here's a minimal HTML5 example. It requires multiple pages of HTML, CSS and Javascript code. All those examples just scratch the surface of complexities found in other development environments:

REBOL [title: "Calculator"]
do %r3-gui.r3
stylize [
    btn: button [
        facets: [init-size: 50x50]
        actors: [on-action:[set-face f join get-face f get-face face]]
    ]
]
view [
    hgroup [
        f: field return
        btn "1"  btn "2"  btn "3"  btn " + "  return
        btn "4"  btn "5"  btn "6"  btn " - "  return
        btn "7"  btn "8"  btn "9"  btn " * "  return
        btn "0"  btn "."  btn " / "   btn "=" on-action [
            attempt [set-face f form do get-face f]
        ]
    ]
]

Below is a cash register application and separate reporting app that calculates and displays the total daily sales entered by any chosen checkout clerk. No database system, SQL code, third party libraries or other development tools/languages are required to create any part of this program. The user interface code, logic, data storage, and everything that make up these 2 apps are instantly portable to any OS that runs Rebol. If you're familiar with any other modern software development tools, the simple work flow, supporting tool set, and actual code required to create these 2 complete apps is likely unimaginable:

REBOL [title: "Minimal Cash Register"]
do %r3-gui.r3
stylize [fld: field [init-size: 80]]   
view [
    hgroup [
        text "Cashier:"   cashier: fld 
        text "Item:"      item: fld 
        text "Price:"     price: fld on-action [
            if error? try [to-money get-face price] [
                request "Error" "Price error" 
                return none
            ]
            set-face a rejoin [
                get-face a mold get-face item tab get-face price newline
            ]
            set-face item copy "" set-face price copy ""
            sum: 0
            foreach [item price] load get-face a [
                sum: sum + to-money price
            ]
            set-face subtotal form sum
            set-face tax form sum * .06
            set-face total form sum * 1.06 
            focus item
        ]
        return
        a: area 600x300
        return
        text "Subtotal:"   subtotal: fld 
        text "Tax:"        tax: fld 
        text "Total:"      total: fld
        button "Save" on-action [
            items: replace/all (mold load get-face a) newline " "
            write/append %sales.txt rejoin [
                items newline get-face cashier newline now/date newline
            ]
            set-face item copy "" set-face price copy "" 
            set-face a copy ""    set-face subtotal copy ""
            set-face tax copy ""  set-face total copy ""
        ]
    ]
]

REBOL [title: "Daily Cashier Report"]
do %r3-gui.r3
name: ask "Cashier:  "
sales: read/lines %sales.txt
sum: $0
foreach [items cashier date] sales [
    if ((now/date = to-date date) and (cashier = name)) [
        foreach [item price] load items [
            sum: sum + to-money price
        ]
    ]
]
alert rejoin ["Total sales today for " name ": " sum]

Here's the code for a generic app which allows users to enter data into GUI text fields, and click a button to save the fields to a standard CSV file. You can adjust this little program to create CSV files with any number of data fields, calculated fields, etc., which can be opened or imported by all sorts of data management programs (spreadsheets, financial applications, database systems, etc.). This simple example forms the basis for many types of business and personal data storage apps that can interact with all sorts of other software applications:

REBOL [title: "Data Entry to CSV File"]
do %r3-gui.r3
view [
    f: field 400
    a: area 400x400
    button "Submit" on-action [
        write/append %data.csv rejoin [
            mold get-face f ", " mold get-face a newline
        ]
    ]
]

To deal with data created by other applications, Rebol has a special ability to extract data from structured and unstructured text, using a unique "parse" function that elegantly replaces regular expressions (regex) found in most other programming languages. This program downloads a raw Paypal account file from the web, and displays 2 separate reports: all purchase totals made from the name "Saoud Gorn", and the sum of all gross account transactions:

REBOL [title: "Paypal Reports"]
do %r3-gui.r3
sum: $0
foreach line at parse read/lines http://re-bol.com/Download.csv  "^M" 2 [
    attempt [sum: sum + to-money pick row: parse/all line "," 8]
    if find row/4 "Saoud" [print rejoin [row/1 ", Saoud Gorn: " row/8]]
]
alert join "GROSS ACCOUNT TRANSACTIONS: " sum

As you can see, Rebol is particularly useful for building lite utilities and business applications with straightforward graphic user interfaces, data entry forms and data management capabilities, but it is not limited to such simple apps. Rebol is designed from the ground up, more than any other language, to build powerful language dialects (DSLs), and with versatile native networking, file manipulation, list management features, deep text parsing abilities, and other core language capabilities that are uniquely designed and truly different than standard language components found in every other development tool (regex, typical OO approaches to coding, etc.), Rebol has a special ability to dramatically reduce lines of code, and increase productivity in ways that you will not find elsewhere. It's even simple enough to be used by "average computer users" who spend the majority of their days doing things other than writing code. For making productive, customized use of today's powerful network connected computing devices, in our busy personal and professional lives, there really is nothing else like it.

This book covers all the basics of writing Rebol apps with graphic user interfaces ("GUIs") on the Android platform, but the code examples and patterns of writing and implementing apps are the same for any other operating system. If you want to run these programs on Windows, Mac, etc., it's simple. Just download the appropriate Rebol interpreter for your platform, and associate the ".r3" file extension with it (no install is required). To edit code, all you need is a good text editor, preferably one that can automatically execute scripts directly with the Rebol interpreter.

This tutorial also a contains a complete section about creating web applications with Rebol. You don't need any other programming background or special tools to get started.

2. Getting Started

You can download the newest open source releases of the "Saphir" version of REBOL 3 ("R3") for Android, Windows, Mac, and Linux at the web site of the Saphirion group:

http://development.saphirion.com/experimental/builds/

Saphirion's Android version of R3 can be downloaded by going to this link in your Android web browser:

http://development.saphirion.com/experimental/builds/android/r3-droid.apk

Run the downloaded file and accept the default install options. It only takes a few seconds (the file is tiny - just over 1 megabyte). After the installation, you'll see an app icon for "R3/Droid" on your Android device. You can click the icon to run the REBOL console. You'll use the console regularly to find help and source code listings for built in functions, examine system objects, and even run simple scripts directly from the console command line. Try typing or copying/pasting these example lines into the R3 console right now:

print "hello world"

cd %./sdcard/

list-dir

help

? system

help write

source load-gui

load-gui

? guie/dialect

x: to-string read http://development.saphirion.com/resources/r3-gui.r3

view [area (x)]

In order to write applications with GUIs ("graphic user interfaces" consisting of buttons, text fields, drop down selectors, etc.), you need to also download this file:

http://development.saphirion.com/resources/r3-gui.r3

You can go to that link in your Android browser, download the file, and copy it to the same folder as any R3 scripts you create - or just run the following line in the R3 interpreter console:

write %./sdcard/your_folder/r3-gui.r3 
    read http://development.saphirion.com/resources/r3-gui.r3

NOTE: For developers who want to contribute to the open source Saphir R3 project, the Apache 2 licensed code is available on Github:

https://github.com/saphirion/saphir

That link is not required for users of the R3 interpreter. To program in R3, just download the interpreter from Saphirion's web site.

2.1 Writing Code - You'll Need a Text Editor App

A good text editor app is essential for writing R3 scripts productively on Android. This author's current favorite Android text editor is "Jota", available for free in the Android app stores. You can find the Jota editor in Google Play at https://play.google.com/store/apps/details?id=jp.sblo.pandora.jota&hl=en. Any text editor will work, but Jota provides seamless integration with the Rebol interpreter, so that scripts can be run automatically from the editing environment. Jota also provides useful viewing and editing controls such as easy font sizing and word wrap options, fine navigation controls, undo/redo, an adjustable toolbar, and other features that make it a pleasant environment in which you can type code quickly.

It's suggested that you set up the Jota toolbar to include these buttons: Save, Open App, Word Wrap, Undo, Redo, Save As, Font-, Font+, Search, End, Home, Up, Down, Left, Right (click Menu -> Preferences -> Toolbar Settings to adjust the toolbar layout). This will provide quick access to many of the functions required while editing scripts.

Jota's "Open App" feature can also be found by clicking Menu -> File -> Open by Application. This feature allows you to run the currently edited R3 script file using the installed R3/Droid interpreter app. To enable this capability, be sure to save your scripts with file names that end in a .r3 extension (i.e., "myscript.r3"). Try pasting the following script into the Jota editor on your Android device:

load-gui
view [text "Hello World!"]

Save the code with a file name such as hello.r3 (any file name ending in ".r3"), then click Menu -> File -> Open by Application (or click the "Open App" toolbar button, if you've set up that button), and select "R3/Droid" (when prompted with "Complete Action Using:"). You'll see the program run, with a small GUI, and text displaying "Hello Android!". You can also run any script that ends in ".r3" by clicking its file icon in a file manager.

The simple routine of editing with Jota, saving, and opening with R3/Droid, can be performed in seconds, if you set up Jota's toolbar with the "save" and "open app" buttons readily available. The File -> History menu is another option that can be used to quickly move between recently edited scripts. You can switch between editor and R3 interpreter by holding down the "Home" button on your Android device. Together with the help system available in the R3/Droid console, this setup provides a fast and complete tool set to handle Android coding mechanics.

2.2 Other Helpful Apps

Several other useful Android tools include ES File Explorer and Wifi File Transfer. ES File explorer is a file manager that allows you to copy/move/delete/execute/etc files on your Android device. It has built in capabilities that allow you to save files to network shares and FTP servers, as well as transfer via email, text message, and other connections. Wifi File Transfer is the most efficient app this author has found to share files back and forth between desktop machines and Android devices. No cables or special network setup steps are required for either app, and both apps are free, fast and reliable.

Both tools above can be used to help install Rebol. Just send the R3 interpreter and any script files to your Android device(s), find them in the file explorer, and run (that way, no download from the web is required).

You can download desktop versions of Saphirion's R3 build from http://development.saphirion.com/experimental/builds/ , and edit/run scripts on your home computer, then use Wifi File Transfer or ES File Explorer to copy them to your Android device, edit, run, and send files back and forth between each platform quickly. Metapad (http://liquidninja.com/metapad/download.html) is this author's favorite editor for Rebol coding on Windows, but any of the other popular editors such as Notepad++, Textmate, or even Windows notepad, will work fine. These tools provide a tremendous productivity boost when sharing/moving work between your desktop and Android work environments.

3. R3 GUI Basics

In the early 1980's, Graphic User Interfaces (GUIs) changed the way people interacted with computers, and "windowed" screens led to a revolution in mainstream computing popularity. Clicking and dragging images in a graphic layout allowed even untrained users to quickly and intuitively operate computers, without having to memorize and type cryptic commands at a text console.

Modern mobile devices allow "touch" interaction with fingers instead of a mouse, but for developers, the basic concept has stayed primarily the same. The look, skin, and form factor of devices has evolved, but input, output, and event controls are still all enabled by users interacting with graphic images on a screen. Rebol's simple ability to create GUIs with only a few lines of code is unrivaled by any other software development tool. You'll be able to lay out functional user interfaces for the operating system of your choice (Android, Windows, Mac, etc.), using code (no complex IDE tools required), within minutes.

3.1 Views and Widgets on a Screen

Notice the "load-gui" function from the "hello world" example in the previous section:

load-gui
view [text "Hello World!"]

If you type "source load-gui" in the R3 console, you'll see that all it does is download and run the file at http://development.saphirion.com/resources/r3-gui.r3. It's recommended that you download and save that file in the same folder as the scripts you create, and use the following code change to enable GUI functionality in your R3 scripts:

do %r3-gui.r3
view [text "Hello World!"]

The "do %r3-gui.r3" line above does the exact same thing as the built-in load-gui function, without requiring the GUI library to be downloaded every time you run the script. This improves speed and eliminates the need for an Internet connection every time you run your script(s). You could also potentially paste the code at http://development.saphirion.com/resources/r3-gui.r3 directly into any of your scripts, if you want to eliminate the need for any additional imported library files (that's messy and not recommended in normal situations).

Notice the first line in the code above. Every R3 program must begin with the header "REBOL [ ]". Rebol is case-insensitive, so you can use any combination of lower or uppercase letters. You can add a title bar to your program header, like this:

REBOL [title: "Put your app title here"]

Notice also in the example above that the single word "view" is used to display a GUI layout. R3 includes a number of native GUI widgets: buttons, text entry fields, drop-down list selectors, listview data grids, etc. Just put the appropriate words between the square brackets, and the resulting widgets appear on screen. Indenting related lines of code is standard practice, but it's not required. In the example below, each line inside the "view []" brackets contains a separate widget, and those lines are all indented 4 spaces:

REBOL [title: "Widgets on Screen"]
do %r3-gui.r3
view [
    field
    area
    check
    radio
    text-list
    text-table
    drop-down
    button
]

You can adjust the size, color, data content, and other properties of a widget by including modifiers next to the widget word. On Android, the window layout is automatically sized to fit the screen, and widgets are skinned to look appropriate for the operating system. On desktop PCs, the GUI window automatically expands to fit resized widgets. Notice that the example below contains the exact same widgets as above, each just with some size, text, color or other data modifications added:

REBOL [title: "Modified Widgets With Data"]
do %r3-gui.r3
view [
    field 400 "Type Here"
    area 400x200 "Multi^/line^/text"
    check "option 1"  check "option 2"
    radio "choice 1"  radio "choice 2"
    text-list ["first choice" "second choice" "third choice"]
    text-table ["Text" 100 "Dates" 200 "Numbers"] [
        ["abcd"  1-jan-2013   44]
        ["bdac"  27-may-2013  4 ]
        ["dcab"  13-aug-2014  5 ]
    ]
    drop-down (system/locale/months)
    button red 100 "Click Me"
]

Adding images to a GUI layout is simple. This example displays an image which is downloaded from a web site. You could just as easily read it from a local file, or from a binary format included directly in the code of your program:

REBOL [title: "Image"]
do %r3-gui.r3
view [image (load http://rebol.com/view/bay.jpg)]

Creating useful user interfaces doesn't get any simpler than that.

3.2 More Widgets, and BUILT IN DOCUMENTATION

There are many more default widgets built into R3, and you can easily build your own. Here's a script that allows you to scroll through and view all the built-in widget styles available in R3:

REBOL [title: "View All Styles"]
do %r3-gui.r3
all-styles: find extract to-block guie/styles 2 'clicker
view [
    title "Pick a style:"
    text-list all-styles on-action [
        style-name: pick all-styles get-face face
        view/modal reduce [
            'title reform ["Example of a" style-name "style:"]
            style-name
        ]
    ]
]

You can see the facets (and all the other data/properties) of any widget using the "debug" keyword (use CTRL+C to stop the console scrolling output):

do %r3-gui.r3
view [text-list debug]

You can get further information about any facet using the "help" function ("?" is a synonym for "help"). Here's an example that displays the facet properties available in the text-list widget:

do %r3-gui.r3
help guie/styles/text-list/facets/init-size
? guie/styles/text-list/facets/init-size

You can get the source code for Rebol function words, using the "source" function:

source view
source make-window-layout

The help and documentation features built into the R3 console are the most important tools to get comfortable with while learning Saphirion's R3 GUI dialect. They provide an indispensable reference to help learn about every part of the Rebol language and object system. Rebolers regularly make use of these built in documentation capabilities, to clarify code syntax and to explore available features in the system. This help is available instantly whenever needed, just by opening the Rebol console. To fully learn the Rebol language, all you really need is the interpreter itself. A big part of the Rebol ethos is centered around this simple and self-sufficient approach to exploring the interpreter. Because the Rebol language is all about simplicity, you'll find that the built-in help features eliminate the need for any bloated IDE tools. The resulting work flow may seem totally foreign to developers who use Java and other tools with enormous APIs and libraries, but it truly is entirely comfortable and manageable with Rebol.

3.3 Pop-Up Message Boxes and Simple Requestors

The "alert" function allows you to display a pop-up text message:

do %r3-gui.r3
alert "This is a pop-up message!"

In it's simplest form, the "request" function operates like the "alert" function. The only difference is that you can change the title bar text:

do %r3-gui.r3
request "Title Text" "Notice the title text of this requestor"

The /ask refinement is helpful for getting responses to yes/no questions:

do %r3-gui.r3
either not request/ask "" "Do you like this?" [alert "No!"] [alert "Yes!"]

The /custom refinement let's you choose alternate text for the buttons:

do %r3-gui.r3
x: request/custom "Question" "What do you say?" ["Yay" "Boo"]
either x = false [alert "Boo!"] [alert "Yay!"]

This request/custom example is taken from the demo code, built into R3/Droid. You can put widgets, and entire GUI layouts and logic right in the requestor:

do %r3-gui.r3
site: http://development.saphirion.com/experimental/
request/custom "Downloading files..." [
    title "Loading game..."
    when [enter] on-action [
        game: load/all site/tile-game.r
        unview/all
        gui-metric/set 'unit-size 1x1
        do game
    ]
]["" "Close"]

A number of built-in widgets allow you to request useful information from the user:

do %r3-gui.r3
view [
    file-list %./
    color-picker
]

You'll see these requestor features explained more, and put to repeated use in examples throughout this text.

3.4 GUI Actions

GUI widgets are nice looking, but totally useless until they can perform some sort of action. To make a widget perform a function, whenever touched, clicked with a mouse, submitted with a keyboard, or otherwise activated, simply place the required function words in a block, between square brackets, following the text "on-action". This example alerts the user with a message whenever the button is clicked:

REBOL [title: "Button Click"]
do %r3-gui.r3
view [
    button "Click Me" on-action [alert "Clicked"]
]

Remember, indentation is not required in Rebol. The above example could be written on one line like this:

REBOL [title: "Button Click"]
do %r3-gui.r3
view [button "Click Me" on-action [alert "Clicked"]]

When more than one action is performed, for readability sake, they're each typically placed on separate lines in the action block:

REBOL [title: "Count"]
do %r3-gui.r3
view [
    button "Count" on-action [
        alert "1"
        alert "2"
        alert "3"
    ]
]

The code above could be written as follows, if desired:

REBOL [title: "Count"]
do %r3-gui.r3
view [button "Count" on-action [alert "1" alert "2" alert "3"]]

3.5 Getting and Setting GUI Data Values

The word "arg" is used to refer to the current/selected value in any widget. That makes it easy to perform some action with the text contained in field widgets, the items selected in text-list widgets, etc.:

REBOL [title: "Args"]
do %r3-gui.r3
view [
    field 280 "Type here, then press [ENTER]" on-action [alert arg]
    text-list [1 2 3] on-action [alert form arg]
    file-list %./ on-action [alert pick face/facets/table-data arg]
]

3.5.1 get-face

You can give any widget a "variable" label and refer to the data contained in it, using the "get-face" function. Label words are created with the colon symbol. When referring to labeled widgets, the label is used without a colon. Notice the "f" label in the code below. Clicking the button displays the text contained in the field labeled "f":

REBOL [title: "Widget Labels and Get-Face"]
do %r3-gui.r3
view [
    f: field "Type something..."
    button "Display Field Text" on-action [alert get-face f]
]

You've already seen that the word "arg" can be used to refer to a widget's own data. "Get-face face" does the same thing, as does "get-face ". This example does the same thing in all 3 ways:

REBOL [title: "Arg Alternatives"]
do %r3-gui.r3
view [
    f: field "Type something..." on-action [
        alert arg
        alert get-face face
        alert get-face f
    ]
]

3.5.2 set-face

You can change a widget's data using the "set-face" function:

REBOL [title: "Set-Face"]
do %r3-gui.r3
view [
    f: field
    button "Set Field Text" on-action [set-face f "tada!"]
]

3.6 Writing and Reading GUI Data to/from Files

You can write data from any widget, to a file, using the "write" function. In Rebol, file names are always preceded by the percent symbol (%). This example allows the user to type text into an area widget, and then save that text to a file named "temp.txt":

REBOL [title: "Write Widget Text to File"]
do %r3-gui.r3
view [
    a: area 
    button "Write" on-action [
        write %temp.txt get-face a
        alert "Saved"
    ]
]

You can read data from a file into a widget, using the "read" function. To read text as ASCII character data, the "/string" refinement is used. This example reads the text created by the code above, and displays it in an area widget:

REBOL [title: "Display Text Read From File"]
do %r3-gui.r3
view [
    a: area
    button "Read" on-action [set-face a read/string %temp.txt]
]

You can also use the "to-string" function to convert read data, to string data. This example loads the previous temp.txt file, and displays it in an area widget when the GUI is displayed:

REBOL [title: "Default Display of Text Read From File"]
do %r3-gui.r3
view [area (read/string %temp.txt)]

3.7 Some Caveats About Displaying Data Values in Text Widgets: Data Types

Unlike other programming languages, Rebol has a large number of native "data types". When the Rebol interpreter sees the value 9-dec-2013, it knows that's a date type (date! in Rebol parlance). Likewise, the values $23.44, 10:15am, 192.168.1.1, and %config.sys, for example, are money!, time!, tuple! (IP), and file! values. Those values are not simply strings of text to Rebol. The Rebol interpreter can automatically perform appropriate computations with data values that it understands. For example, try typing the following examples into the R3 console:

sort [10:00pm 12:00am 1:05pm]

9-dec-2013  -  8-mar-2012

There are dozens of these sorts of native data types. It's one of the many features that makes Rebol such a productive language.

GUI text fields are only able to display text ("string") values. When computing data values stored in a text field, be sure to convert them to data types recognized by Rebol, using a "to-" function. The example below demonstrates that a file name must be converted into a file! data type (from text gotten from the "f" face, using the "to-file" function):

REBOL [title: "Converting Text in GUI Fields to Data Types"]
do %r3-gui.r3
view [
    f: field "temp.txt"
    button "Read" on-action [print read (to-file get-face f)]
    button "Error" on-action [print read get-face f]
]

3.8 A Little Note Pad App

Using what you've seen so far, the following notepad app should be easy to follow. The layout consists of an area widget labeled "a", and two buttons. When the "Save" button is clicked, any text that the user has typed into the "a" text widget, is written to the notes.txt file. When the "Load" button is clicked, the text from the notes.txt file is read and displayed in the "a" text area widget. The read action is wrapped in an "attempt" block, in case the file doesn't exist yet. Such an occurrence would cause the program to crash with an error. The attempt function simply keeps the program from failing whenever those sorts of errors occur:

REBOL [title: "Tiny Note Editor"]
do %r3-gui.r3
view [
    a: area
    button "Load" on-action [attempt [set-face a read/string %notes.txt]]
    button "Save" on-action [write %notes.txt get-face a  alert "Saved"]
]

3.9 Accessing Facets of a Widget

You can access specific properties of any widget with the "get-face/field" refinement. This example displays the table data contained in a text list, when the widget is clicked:

REBOL [title: "Get a Specific Facet Value from a Face"]
do %r3-gui.r3
view [
    text-list [1 2] on-action [probe get-face/field face 'table-data]
]

You can do the same thing using the "get-facet" function (although the "get-face/field" method above is preferred):

REBOL [title: "Get a Specific Facet Value from a Face #2"]
do %r3-gui.r3
view [
    text-list [1 2] on-action [probe get-facet face 'table-data]
]

Blocks of multiple data items (such as those found in text-list and text-table widgets) can be saved using the "save" function. In the example below, the file-list widget displays the files in the current folder. When the button is clicked, that list, saved in the "table-data" facet, is saved to the file "myfiles":

REBOL [title: "Saving Data Blocks"]
do %r3-gui.r3
view [
    l: file-list %./
    button "Save File List" on-action  [
        save %myfiles get-face/field l 'table-data
        alert "Saved"
    ]
]

Blocks of saved data items can be loaded using the "load" function. Here, the file name list saved above is loaded and displayed when the button is clicked (the second line of button action code below is used to re-adjust the scroller widget size to the size of the loaded list):

REBOL [title: "Loading Data Blocks"]
do %r3-gui.r3
view [
    l: text-list
    button "Load File List" on-action [
        set-face/field l load %myfiles 'data
        set-face l/names/scr 100%
    ]
]

Here's the same data list as above, loaded and displayed when the GUI opens:

REBOL [title: "Loading Data Blocks on Startup"]
do %r3-gui.r3
view [text-list (load %myfiles)]

Rebol has a wide variety of unique list management and parsing features which allow complex data storage, retrieval, and manipulation, using only plain text files. You'll find that RDBMS (database) systems are not required for creating many types of applications in R3. This helps improve the initial learning curve, and also eases the porting process between platforms, for a wide variety of apps.

3.10 Saving to CSV Files - a "Contacts" App

CSV files are a common format used to save and exchange data between different applications and platforms. You'll typically see options to save and load (or import/export) to/from CSV files in spreadsheets, database apps, financial applications, and all sorts of other programs that deal with tables (columns and rows) of text and number data. If you download account information from Paypal and other commerce web sites, and you'll find that CSV format is the most prevalent formatting option.

Using what you've seen so far, you can create your own application that creates CSV files from data entered by a user. The CSV format basically requires each row of text to be placed on a new line of text, with each field of data enclosed in quotes, and separated by a common character (most often a comma). Knowing that, the following code example should make sense. It contains a field widget and text area widget to allow the user to enter data. When the button is clicked, the text in the fields is saved to a file named data.csv. The "rejoin" function simply joins all the pieces of data into a single string of text, and the "mold" function adds quotes around the text entered by the user.

REBOL [title: "Data Entry to CSV File"]
do %r3-gui.r3
view [
    f: field 400
    a: area 400x400
    button "Save Separate Data Fields to File" on-action [
        write/append %data.csv rejoin [
            mold get-face f  ", "  mold get-face a  newline
        ]
        alert "Saved"
    ]
]

Here's a slightly more robust version of the example above, with text labels that tell the user what data to enter. There's also a text area widget and an additional button which allows the user to view all the data that's been saved in the contacts file:

REBOL [title: "CSV File Example #2 - Contacts"]
do %r3-gui.r3
view [
    text "Name:"
    f1: field
    text "Phone:"
    f2: field
    text "Address:"
    f3: field
    button "Save to Contacts" on-action [
        write/append %cntcts.txt rejoin [
            mold get-face f1 ", " mold get-face f2 ", " mold get-face f3
            newline
        ]
        Alert "Saved"
    ]
    a1: area
    button "Load" on-action [set-face a1 to-string read %cntcts.txt]
]

By changing the text labels above each field, the code above could just as easily be used to create entry forms in an inventory app, cash register checkout program, or any other sort of application in which rows and columns of data to be entered and saved.

3.11 More about Layout

The default placement of widgets in an R3 GUI layout is vertical - new widgets are added below one another. Horizontal and vertical layout containers are used to arrange groups of items on screen in other useful ways. Notice the horizontal and vertical panels used in this example:

REBOL [title: "Vpanels and Hpanels"]
do %r3-gui.r3
view [
    vpanel [
        f1: field
        hpanel [
            button "Reset" on-action [set-face f1 ""]
            button "Clear" on-action [set-face f1 ""]
        ]
    ]
]

In an "hgroup" container, items are placed horizontally, and the word "return" is used to place any following widgets onto a new row:

REBOL [title: "Groups"]
do %r3-gui.r3
view [
    hgroup [
        f1: field
        return
        button "Reset" on-action [set-face f1 ""]
        button "Clear" on-action [set-face f1 ""]
    ]
]

Centering and resizing widgets in a group is simple:

REBOL [title: "Centering and Resizing"]
do %r3-gui.r3
view [
    hgroup [
        button
        button
        return
        button
        button
        button
        return
        button 80
        button 80
    ] options [pane-align: 'center]
]

Widgets can automatically be arranges into columns and rows, using the "pad" widget as a placeholder. This example arranges the widgets into rows of 2:

REBOL [title: "Rows and Columns"]
do %r3-gui.r3
view [
    hpanel 2 [
        text 50 "Part #:"
        field
        text 50 "SKU:"
        field
        pad
        button "Submit"
    ]
]

This example arranges the widgets into columns of 3:

REBOL [title: "Columns of 3"]
do %r3-gui.r3
view [
    vpanel 3 [
        text 50 "Part #:"
        field
        button "Submit"
        text 50 "SKU:"
        field
        button "Submit"
    ]
]

Notice that many layout and resizing options are handled automatically by Rebol (try resizing this grid, and notice the automatic sort and display features of the text-table widget):

do %r3-gui.r3
view [
    text-table ["1" 200 "2" 100 "3"][
        ["asdf" "a" "4"]
        ["sdfg" "b" "3"]
        ["dfgh" "c" "2"]
        ["fghj" "d" "1"]
    ] 
]

Because Android and other new devices come with a huge variety of screen resolutions, some provisions have been added to R3 to enable graceful handling of these options, with little code required for simple scripts, and additional detailed control options available to help build more complex applications. The "gui-metric" function is important to explore. Type "help gui-metric" in the R3 console to see its options. In the following code, take a look at how the gui-metric function is used, along with the "max-hint" option. This example layout fits nicely on a small screen phone in portrait mode:

REBOL [title: "Edit Downloaded File"]
do %r3-gui.r3 
gui-metric/set 'unit-size (gui-metric 'screen-dpi) / 96
view/options [
    hgroup [
        button "Load" on-action [
            set-face a to-string read http://rebol.com
        ]
        button "Open" on-action [
            view [
                text "File:"
                f: field
                button "Submit" on-action [
                    file: get-face f
                    set-face a (to-string read file)
                    unview
                ]
            ]
        ]
        return
        button "New" on-action [
            set-face a  ""
        ]
        button "Save-As" on-action [
            view [
                text "File:"
                f: field
                button "Submit" on-action [
                    file: get-face f
                    write file (get-face a)
                    unview
                ]
            ]
        ]
        button "Quit" on-action [
            unview/all
        ]
    ]
    a: area ""  on-key [
        do-actor/style face 'on-key arg 'area
        if arg/type = 'key [ 
            if arg/key = 'f5 [try load face/names/tb/state/value]    
        ]    
    ]
][
    max-hint: round/floor (gui-metric 'work-size) - gui-metric 'title-size
]

Here's an example that demonstrates the above technique more simply:

REBOL [title: "Sizes"]
do %r3-gui.r3
foreach sizer [48 96 128 256] [
    gui-metric/set 'unit-size (gui-metric 'screen-dpi) / sizer
    view/options [
        text (join "Sizer value: " sizer)
        button
        area (form sizer)
    ][
        max-hint: 
            round/floor (gui-metric 'work-size) - gui-metric 'title-size
    ]
]

There are a number of additional functions that can be used to query and change data contained in a layout:

REBOL [title: "Layout Functions"]
do %r3-gui.r3
alt-values: make object! [
    f1: "poiu"
    f2: "lkjh"
    f3: "mnbv"
]
view g: layout l: [
    f1: field "asdf"
    f2: field "qwer"
    f3: field "zxcv"
    button "Get All Values" on-action [probe get-layout g]
    button "Set All Values" on-action [set-layout g alt-values]
    button "Clear All Values" on-action [clear-layout g]
    button "Get Parent Layout" on-action [probe get-parent-layout g]
    button "Parse Layout" on-action [probe parse-layout l]
]

For more information about R3 layout and resizing, see http://development.saphirion.com/rebol/r3gui/layouts/index.shtml and http://development.saphirion.com/rebol/r3gui/resizing/index.shtml.

3.12 More Actors

Most widgets have one primary function, which is activated in code using the "on-action" keyword, as you've seen in previous examples. There are many other possibilities for interacting with widgets, such as dragging, waiting for changed data, resizing, etc. A list of other actors available for widgets is below:

on-make
    when face is first created to initialize special values
on-click
    when mouse button clicked on face
on-drag
    when dragging inside a face
on-drag-over
    when dragging and are over a target face
on-drop
    when drag has stopped or when a file is dropped
on-focus
    when we have been given or are losing focus
on-get
    to fetch state values
on-set
    when state values are set
on-clear
    when the state values are cleared
on-key
    when key has been pressed (for our focus)
on-move
    when mouse has moved
on-over
    when mouse passes over or away
on-reset
    when reset is needed
on-resize
    when size values have changed
on-update
    when face contents have changed
on-draw
    when system starts to draw the face (create DRAW block)
on-scroll
    when scrolling is needed
on-scroll-event
    when the mouse wheel is used
on-init
    when face and its parent pane are ready (including initial sizing)
on-attach
    when a face is attached from another face
on-attached
    when a face is triggered in the attach reaction chain

Much more information about faces, facets, actors, and other GUI options can be found at http://development.saphirion.com/rebol/r3gui/faces/index.shtml and http://development.saphirion.com/rebol/r3gui/actors/index.shtml. These texts provide more complete information about what you've learned here, about basic features of the R3 GUI dialect.

3.13 Custom Styles - The Calculator and Sliding Tile Puzzle Apps

"Styles" are the R3 synonym for "widgets" in other languages. The "stylize" function allows you to create your own widget variations, or even entirely new widget definitions, using draw commands. Default facets, actors, and other properties of a existing style can be set and the whole widget variation can be given a new name. Here, a "blue-button" style is created. Notice that after the blue-button style is defined, it can be used like any other built-in widget (other widgets could even be defined by creating a stylized variation of the new blue-button widget...):

REBOL [title: "Blue Button"]
do %r3-gui.r3
stylize [
    blue-button: button [
        facets: [bg-color: blue]
    ]
]
view [blue-button on-action [alert "You clicked the blue button"]]

3.13.1 Calculator

The example below is a simple GUI calculator program. Look at the "stylize" code.

  1. The "btn" style is set to a size of 50x50
  2. A default action is defined for each "btn" widget to perform (the face text of any clicked btn widget is appended to the face text of the field widget labeled "f").
  3. The GUI layout simply displays the field widget and a number of "btn" widgets.

Notice that the btn marked "=" tries to compute ("do") the expression displayed in the "f" field, and displays the result in the "f" field (its action code says "set the face of the "f" field to whatever text is formed by "do"ing the text (mathematical expression) contained in that field"):

REBOL [title: "CALCULATOR"]
do %r3-gui.r3
stylize [
    btn: button [
        facets: [init-size: 50x50]
        actors: [on-action: [set-face f join get-face f get-face face]]
    ]
]
view [
    hgroup [
        f: field return
        btn "1"  btn "2"  btn "3"  btn " + "  return
        btn "4"  btn "5"  btn "6"  btn " - "  return
        btn "7"  btn "8"  btn "9"  btn " * "  return
        btn "0"  btn "."  btn " / "   btn "=" on-action [
            attempt [set-face f form do get-face f]
        ]
    ]
]

3.13.2 Sliding Tile Puzzle

Here's another example that creates a custom widget style, using the "stylize" syntax. It's a small sliding tile game - click the tiles to arrange them visually into ascending numeric order from 1 to 8. Here's a brief explanation of the code:

  1. A new style called "p" is created, built on the existing button widget style.
  2. The default size of all "p" widgets is set to 60x60
  3. A default action for all "p" widgets is set, which executes any time a "p" widget is clicked. The 2 lines of code in that action simply swap the position of the currently clicked "p" button, with the button labeled "x" (the offset property of a widget's graphic "gob" refer to its coordinate position on screen).
  4. A GUI layout is created, with a white background specified as an option.
  5. 3 rows of "p" widgets are placed on the screen, displaying the numbers 8 to 1.
  6. The widget labeled "x" is a plain white box, the same size as the "p" buttons. Since it's white (the same color as the GUI background), it just appears as an empty space on the screen.

Any time one of the "p" buttons is clicked, it swaps places with the empty space. The user clicks the "p" buttons until they're arranged in proper numerical order. Pretty simple, right?

REBOL [title: "Sliding Tile Puzzle"]
do %r3-gui.r3
stylize [
    p: button [
        facets: [init-size: 60x60  max-size: 60x60]
        actors: [
            on-action: [
                t: reduce [face/gob/offset x/gob/offset]
                face/gob/offset: t/2  x/gob/offset: t/1
            ]
        ]
    ]
]
view/options [
    hgroup [ 
        p "8"   p "7"   p "6"   return
        p "5"   p "4"   p "3"   return
        p "2"   p "1"   x: box 60x60 white
    ]
] [bg-color: white]

Take a break from studying and play a few games :)

3.13.3 Drawing Entirely New Widget Styles

You can create your own entirely new widget styles using draw commands:

REBOL [title: "Blue Button"]
do %r3-gui.r3
stylize [
    circle: [
        draw: [
            fill-pen blue
            circle 50x50 30
        ]
    ]
]
view [circle circle circle]

All the existing widgets built into Rebol were created this way. You can see the draw commands used to build any widget using the help built into the R3 console:

? guie/styles/field/draw

Much more about draw commands will be covered in later sections of this text.

See http://development.saphirion.com/rebol/r3gui/styles/index.shtml for more information about creating styles.

3.14 Home-Made Requestors

For situations where you need to collect more information or provide specially selectable choices, custom text requestors and other basic input and output dialogues are simple to construct from existing widgets, as needed. For example, this script contains a simple dialogue that requests text from the user, when a button is clicked (in this case, a file name used to save the note text). Notice the use of the view/modal refinement. This keeps the dialogue on top of the main layout, until it is closed:

REBOL [title: "Tiny Text Editor"]
do %r3-gui.r3
view [
    text "Notes:"
    a1: area
    button "Save" on-action [
        view/modal [
            text "File Name:"
            f1: field "notes.txt"
            button "Submit" on-action [
                write (to-file get-face f1) (get-face a1)
                unview
            ]
        ]
    ]
    button "Load" on-action [set-face a1 to-string read %notes.txt]
]

We could take out the requestor code above and make a stand-alone function that gets text from a user:

REBOL [title: "Requestors"]
do %r3-gui.r3
request-text: func [ttl /local txt] [
    view/modal [
        title ttl
        f: field
        button "Submit" on-action [
            set 'txt get-face f
            unview
        ]
    ]
    txt
]

At this point, you don't really have to understand how all the code above works. Just save it in a file named "requestors.r3", in the same folder as r3-gui.r3 and your other scripts, and you can import the "request-text" function to be used in any other script, like this. Notice that it's not necessary to run the normal "do %r3-gui.r3" line, because that line is already executed in the requestor.r3 code above:

REBOL [title: "Requestors"]
do %requestors.r3
print request-text
halt

The value returned by the request-text function above (some text entered by the user), can be treated like any other static text value. For example, if the user types "John Smith" into the text requestor, the Rebol interpreter sees the last line of code above as: print "John Smith".

Here are a number of useful requestor functions that can be added to your requestors.r3 file:

REBOL [title: "Requestors"]
write %requestors.r3 {
    do %r3-gui.r3
    request-text: func [/title ttl /default dflt /local txt] [
        if not title [ttl: "Enter Text:"]
        if not default [dflt: ""]
        view/modal rqstr: [
            title (ttl)
            f: field (dflt)  on-action [
                set 'txt get-face f
                unview rqstr unview rqstr
            ]
        ]
        txt
    ]
    request-file: func [/local files file] [
        append files: copy ["../" "(New...)"] read %./
        view/modal rqstr: [
            text "File Name:"
            t2: text-list files on-action [
                set 'file to-file pick files get-face t2
                unview rqstr unview rqstr
            ]
        ]
        if not value? 'file [file: %none]
        if file = "(New...)" [
            view/modal rqstr2: [
                text "New File Name:"
                f: field on-action [
                    set 'file to-file get-face f
                    unview rqstr2 rqstr2
                ]
            ]
        ]
        if file = "../" [cd %../  request-file]
        attempt [if dir? file [cd :file request-file]]
        file
    ]
    request-date: func [/local current-date days] [
        set 'current-date none
        days: copy []  repeat i 31 [append days i]
        view/modal rqstr: [
            hgroup [
                m: text-list system/locale/months
                d: text-list days
                return
                text "Year:" y: field (form now/year) 
                button "Submit" on-action [
                    attempt [
                        set 'current-date to-date rejoin [
                            pick days get-face d "-"
                            pick system/locale/months get-face m "-"
                            get-face y
                        ]
                    ]
                    unview rqstr unview rqstr
                ]
            ]
        ]
        current-date
    ]
    request-color: func [/local colr] [
        view/modal rqstr: [
            c: color-picker
            button "Submit" on-action [
                set 'colr get-face c
                unview rqstr unview rqstr
            ]
        ]
        colr
    ]
    request-list: func [ttl lst /local rqstr lt] [attempt [
        view/modal rqstr: [
            title ttl
            text-list lst on-action [
                set 'lt pick lst arg
                unview rqstr unview rqstr
            ]
        ]
        lt
    ]]
}

And here are some examples that demonstrate how to use the requestor functions above:

do %requestors.r3
probe request-text
probe request-text/title "Enter your name:"
probe request-text/title/default "Enter your name:" "John"
probe request-file
probe request-date
probe request-color
probe request-list "Choose A Direction:" ["North" "South" "East" "West"]
view [
    b1: button 200 "Start" on-action [set-face face (request-date)]
    b2: button 200 "End" on-action [
        set-face face (form request-date)
        set-face f ((to-date get-face b2) - (to-date get-face b1))
    ]
    text "Days Between:"
    f: field
]

As you get more familiar with R3-GUI, rolling your own requestors will become a simple task, using R3's easy native GUI capabilities.

3.15 Additional Essential Resources

At this point, you should be able to create basic R3 GUI data entry forms that are useful in utility scripts and simple business apps. Be sure to check out the following examples to see more fundamentally useful code for R3/Droid and the Saphirion R3 builds. Pay particular attention to the tile game and draw test scripts by Cyphre to see how graphics, screen layout, event handling, and other useful facilities are implemented:

http://development.saphirion.com/experimental/demo.r

http://development.saphirion.com/experimental/tile-game.r

http://development.saphirion.com/experimental/draw-test.r

https://raw.github.com/angerangel/r3bazaar/master/builds/windows/editor.r

3.16 A Telling Comparison

To provide a quick idea of how much easier REBOL is than other languages, here's a short example. The following code to create a basic program window with REBOL was presented earlier (the first 2 lines are just stock header template code):

do %r3-gui.r3
view/options [] [init-hint: 400x300]

It works on every type of desktop computer and mobile device, in exactly the same way.

Code for the same simple example is presented below in the C++ language. It does the exact same thing as the REBOL one-liner above, except it only works on Microsoft Windows machines. If you want to do the same thing with a Macintosh computer, you need to memorize a completely different page of C++ code. The same is true for Android or any other operating system. You have to learn enormous chunks of code to do very simple things, and those chunks of code are different for every type of computer. Furthermore, you typically need to spend a semester's worth of time learning very basic things about code syntax and fundamentals about how a computer 'thinks' before you even begin to tackle useful basics like the code below:

#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "C_Example";

int WINAPI
WinMain (HINSTANCE hThisInstance,
         HINSTANCE hPrevInstance,
         LPSTR lpszArgument,
         int nFunsterStil)

{
    HWND hwnd;               
    /* This is the handle for our window */
    MSG messages;            
    /* Here messages to the application are saved */
    WNDCLASSEX wincl;        
    /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      
    /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 
    /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 
    /* No menu */
    wincl.cbClsExtra = 0;                      
    /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      
    /* structure or the window instance */
    /* Use Windows's default color as window background */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register window class. If it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   
            /* Extended possibilites for variation */
           szClassName,         
            /* Classname */
           "C_Example",       
            /* Title Text */
           WS_OVERLAPPEDWINDOW, 
            /* default window */
           CW_USEDEFAULT,       
            /* Windows decides the position */
           CW_USEDEFAULT,       
            /* where the window ends up on the screen */
           400,                 
            /* The programs width */
           300,                 
            /* and height in pixels */
           HWND_DESKTOP,        
            /* The window is a child-window to desktop */
           NULL,                
            /* No menu */
           hThisInstance,       
            /* Program Instance handler */
           NULL                
            /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);

    /* Run the message loop. 
        It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages 
            into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - 
        The value that PostQuitMessage() gave */
    return messages.wParam;
}

/*  This function is called by the Windows 
        function DispatchMessage()  */

LRESULT CALLBACK
WindowProcedure (HWND hwnd, UINT message, 
    WPARAM wParam, LPARAM lParam)
{
    switch (message)                  
    /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       
                /* send a WM_QUIT to the message queue */
            break;
        default:                      
            /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, 
                wParam, lParam);
    }

    return 0;
}

Yuck. Back to REBOL...

4. Getting Down to Business: Data Manipulation and Computation

The process of learning a programming language is similar to learning any spoken language (English, French, Spanish, etc.). If you move a person from the United States to Spain, you can be fairly certain that within a year, they'll be able to speak Spanish adequately, even if they aren't provided any appropriate structured Spanish training or guidance. Guidance certainly helps clarify the process, but a key essential component is immersion. Immersion in code works the same way. It may be painful and confusing at first to see or comprehend a totally foreign language and environment, but diving right into code is required if you want to become proficient at "speaking" REBOL. Run each example in this section, and along the way, try changing some text headers, button labels, text field sizes, and other obvious properties to see how the programs change. Getting used to using the REBOL interpreter, becoming aware that code examples in this text are malleable, and opening your mind to the prospect and activity of actually typing REBOL code, is an important first step.

4.1 Automatically Loading the GUI Library and other Scripts/Settings on Startup

IMPORTANT: Before going any farther, create a text file named "rebol.r", add the following code, and save it in the folder that contains your R3 interpreter (if you're not sure where Rebol is installed on your device, open the R3 console and type "pwd"):

REBOL []
; do %r3-gui.r3
do %requestors.r3

The rebol.r file is run every time the R3 interpreter starts. Since most of the examples in this text make use of GUI features, we'll use the above script to automatically load the GUI library every time the interpreter starts. You can edit your rebol.r file to automatically load other scripts, configure user settings, and otherwise personalize your installation of R3. The rebol.r file can be as complex as you need, and can contain program code of any sort. To move all your settings to another machine, just copy your rebol.r text file.

IMPORTANT: all the examples in the rest of this text will assume that you have the above startup script installed. If not, you will need to add the "do %r3-gui.r3" and/or "do %requestors.r3" lines to each individual script, as required.

4.2 Basics of Using REBOL Functions

Computer programming is about processing data - that's all computers do internally (although the results of that data processing can appear, and actually end up being, magically more human). Everything you learn about in this text, therefore, will necessarily have to do with inputting, manipulating, and outputting processed data.

Function words perform actions upon data values. The following function examples display some data values. Any text after a semicolon in these examples is a human readable "comment", and is ignored completely by the R3 interpreter. Try pasting these lines directly into the R3 interpreter console:

alert "Hello world"        ; ALERT and PRINT are the function word here.
print "Hello world"        ; "Hello world" is the text data parameter.
view [text "Hello world"]  ; "View" is the function here, layout is data.
wait 2                     ; "Wait" is the function here, "2" is the data.

Be sure to paste EVERY code example into the R3 console, and watch each line run.

4.2.1 Return Values

In REBOL, the output of one function (the "return value") can be used as the input ("argument" or "parameter") of another function:

; Here, the "print" function prints whatever date is input by the user:

print request-date

; Here, the "alert" function displays whatever text is input by the user:

alert request-text

4.2.2 Variable Labels, or "Set Words"

In REBOL you can assign data to a label word (also called a "variable"), using the colon symbol. Once data is assigned to a word label, you can use that word anywhere to refer to the assigned value:

balance: $53940.23 - $234
print balance
name: ask "Name:  "
print name
date: ask "Date:  "
print date

4.2.3 Concatenation

You can join together, or concatenate, text values using the "rejoin" function. Try adding this line to the end of the program above:

alert rejoin [name ", your balance on " date " is " balance]

4.2.4 Built In Values

There are a variety of useful values built into REBOL:

print rejoin ["Right now the date and time is: " now]
print rejoin ["The date is: " now/date]
print rejoin ["The time is: " now/time]
print rejoin ["The value of PI is " pi]
print rejoin ["The months are " system/locale/months]
print rejoin ["The days are " system/locale/days]

4.2.5 Calculations With Data Types

REBOL can perform useful calculations on many types of values:

print rejoin ["5 + 7 = " 5 + 7]
print rejoin ["Five days ago was " now/date - 5]
print rejoin ["Five minutes ago was " now/time - 00:05]
print rejoin ["Added coordinates: " 23x54 + 19x31]
print rejoin ["Multiplied coordinates: " 22x66 * 2]
print rejoin ["A multiplied coordinate matrix: " 22x66 * 2x3]
print rejoin ["Added tuple values: " 192.168.1.1 + 0.0.0.37]
print rejoin ["The RGB color value of purple - brown is: " purple - brown]

Remember, programming is fundamentally about managing data, so REBOL's ability to appropriately handle operations and computations with common data types leads to greater simplicity and productivity for programmers.

4.3 Conditional Evaluations

Conditional evaluations can be performed using the syntax: "if (this is true) [do this]":

if (now/time > 6:00am) [alert "It's time to get up!"]

Notice the natural handling of the time value in the example above. No special formatting or parsing is required to use that value. REBOL natively "understands" how to perform appropriate computations with time and other common data types.

Use the "either" evaluation to do one thing if a condition is true, and another if the condition is false:

do %requestors.r3
user: "sa7f"
pass: "8uqw"
username: request-text/title "Username:"
password: request-text/title "Password:"
either ((username = user) and (password = pass)) [
    alert rejoin ["Welcome back " user "!"]
][ 
    alert "Incorrect username/password combination"
]

In the code above:

  1. The word labels (variables) "user" and "pass" are assigned to text values.
  2. The variable "pass" is assigned to some text.
  3. A username/password combination is requested from the user, and the result is stored in the variables "username" and "password".
  4. An "either" conditional evaluation is performed on the returned username/password values. If the username/password values equal the set "user" and "pass" variables, the user is alerted with a welcome message. Otherwise, the user is alerted with an error message.

4.4 Some More Useful Functions

Try pasting every individual line below into the REBOL interpreter console to see how each function can be used to perform useful actions:

print read http://rebol.com  ; "read" retrieves the data from many sources
print read %./          ; the % symbol is used for local files and folders
view [area (to-string read http://rebol.com)]
view [area (to-string read %./)]
write %temp.txt "test"   ; write takes TWO parameters (file name and data)
view [area (to-string read %temp.txt)]
view [area (read/string request-file)]
write clipboard:// (to-string read http://rebol.com)
print read clipboard://
view [area (read clipboard://)]
print read dns://msn.com          ; REBOL can read many built in protocols
; print read nntp://news.grc.com     ; in R2, but not yet in R3 by default
write %/c/bay.jpg (read http://rebol.com/view/bay.jpg)
view [image %/c/bay.jpg]                                   ; view an image
view [image (load request-file)]              ; view a user selected image
write %tada.wav (read %/c/windows/media/tada.wav)
write %temp.dat (compress read http://rebol.com)           ; COMPRESS DATA
print to-string decompress read %temp.dat                ; DECOMPRESS DATA
name: ask "Enter your name"  print name  ; request a user value in console

call "notepad.exe c:\config.sys"            ; run an OS shell command
browse http://re-bol.com       ; open system default web browser to a page

rename %temp.txt %temp2.txt                             ; change file name
write %temp.txt read %temp2.txt                                ; copy file
write/append %temp2.txt ""     ; create file (or if it exists, do nothing)
delete %temp2.txt
change-dir %../
what-dir 
list-dir
make-dir %./temp
print read %./

attempt [print 0 / 0]                         ; test for and handle errors
if error? try [print 0 / 0] [alert "*** ERROR:  divide by zero"]

parse "asdf#qwer#zxcv" "#"                ; split strings at character set
trim "  asdf89w   we   "     ; remove white space at the beginning and end
replace/all "xaxbxcxd" "x" "q"   ; replace all occurrences of "x" with "q"
checksum read %file.txt            ; compute a checksum to ensure validity
print dehex "a%20space"                  ; convert from URL encoded string
probe to-url "a space"                     ; convert to URL encoded string
probe detab "tab    separated"                    ; convert tabs to spaces
probe enbase/base "a string" 64  ; convert string or bin to base 64, 16, 2
print encloak to-binary "my data" "my pass"                 ; encrypt data
print to-string decloak #{B5DC69FB8E7AAE} "my pass"         ; decrypt data
for i 1 99 3 [print i]                 ; count from 1 to 99, by steps of 3

; print read ftp://user:pass@website.com/name.txt    ; R2, an OPTION in R3
; write ftp://user:pass@website.com/name.txt "text"                   ; R2
; read pop://user:pass@website.com  ; R2 - read all emails in this account
; send user@website.com "Hello"  ; R2 send email, implemented OPTION in R3
; send user@website.com (read %file.txt)   ; email the text from this file
; send/attach user@website.com "My photos"  [%pic1.jpg %pic2.jpg pic3.jpg]

halt           ; "HALT" stops the REBOL console from closing,
               ; so you can see the printed results.

In order to see each of the lines above execute, paste them directly into the REBOL console, instead of into the editor. When running code directly in the console, it's not necessary to include the REBOL [ ] header, or the "halt" function:

Really take a look at how much computing ability is enabled by each of the functions above. That short collection of one line code snippets demonstrates how to do many of the most commonly useful tasks performed by a computer:

  1. Reading data from and writing data to files on a hard drive, thumb drive, etc.
  2. Reading/writing data from/to web servers and other network sources, the system clipboard, user input, etc. (in R2 this is all included by default. In R3 Saphir, you must simply include a single file for ftp support.)
  3. Reading emails, sending emails, sending attached files by email (in R2 this is all included by default. In R3, you must include a file for email support.)
  4. Displaying images.
  5. Playing sounds.
  6. Navigating and manipulating folders and files on the computer.
  7. Compressing, decompressing, encrypting, and decrypting data.
  8. Running third party programs on the computer.
  9. Reading, parsing, and converting back and forth between common data types and values.

And those lines are just a cursory introduction to a handful of built in REBOL functions. There are hundreds more. At this point in the tutorial, simply read the examples and paste them into the REBOL interpreter console to introduce yourself to the syntax, and to witness their resulting action. You will see these functions, and others, used repeatedly throughout this tutorial and in real working code, for as long as you study and use REBOL. Eventually, you will get to know their syntax and refinements by heart, but you can always refer to reference documentation while you're learning. If you're serious about learning to program, you should take some time now to try changing the parameters of each function to do some useful work (try reading the contents of different file names, send some emails to yourself, compress and decompress some data and view the results with the editor function, etc.)

You can get a list of all function words by typing the "what" function into the REBOL console:

what                             ; press the [ESC] key to stop the listing

You can see the syntax, parameters, and refinements of any function using the "help" function:

help print
help prin
help read
help write

The following script will save a help.txt file with help for more than 500 built in functions, and display it in a GUI area widget. Save it and read through it as a reference:

REBOL [title: "Help File"]
do %r3-gui.r3
write %words.txt "" write %help.txt ""
echo %words.txt what echo off   ; "echo" saves console activity to a file
echo %help.txt
foreach line read/lines %words.txt [
    word: first parse line none
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do rejoin ["help " word]
]
echo off
view [area (at read/string %help.txt 4)]

By learning to combine simple functions with a bit of conditional evaluation (if/then) thinking, along with some list processing techniques, you can accomplish truly useful programming goals that go far beyond the capabilities of even complex office suite programs (much more about 'list processing' will be covered shortly).

The important thing to understand at this point is that functions exist in REBOL, to perform actions on all sorts of useful data. Learning to recognize functions and the data parameters which follow them, when you see them in code, is an important first step in learning to read and write REBOL. Eventually memorizing the syntax and appropriate use patterns of all built in functions is a necessary goal if you want to write code fluently.

The benefit of pasting (or even better typing) every single example into the REBOL editor and/or console, cannot be overstated. Concepts will become more understandable, and important code details will be explicitly clarified as this text progresses. For now, working by rote is the best way to continue learning. Read and execute each example, and pay attention to which words are functions and which words are data arguments.

5. Lists, Tables, and the "Foreach" Function

5.1 Managing Spreadsheet-Like Data

5.1.1 Warning

NOTE TO BEGINNERS: This section of the book is the longest and most difficult to grasp at first read. Read through it once to introduce yourself to all the topics, and skim the code structures. Be prepared for it - the code is going to get a little hairy. Just press on, absorb what you can, and continue to read through the entire section. You'll refer back to it later in much greater detail, once you've seen how all the concepts, functions, and code patterns fit together to create useful programs.

5.1.2 Blocks

Most useful business programs process lists of data. Tables of data are actually dealt with programatically as consecutive lists of items. A list or "block" of data is created in REBOL by surrounding values with square brackets:

names: ["Bob" "Tom" "Bill"]

To perform an operation/computation with/to each data item in the block, use the foreach function. Foreach syntax can be read like this: "foreach (labeled item) in (this labeled block) [perform this operation with/to each labeled item:

names: ["Bob" "Tom" "Bill"] ; create a block of text items labeled "names"
foreach name names [print name]       ; print each name value in the block
halt

WHY "HALT"?: the "halt" function at the end of these examples is included so that you can paste the code directly into text script files, and run. If you don't include "halt", the interpreter will run the program, close instantly, and all you will see is a quick flash on the screen. This is true of any scripts that simply print output to the console without any other interaction. The halt function won't be included in scripts that display requestors, GUIs, or other interactions which require user input before ending the program.

This example prints each value stored in the built-in "system/locale/months" block:

months: system/locale/months     ; set the variable "months" to the values
foreach month months [print month]                ; print each month value
halt

Note that in the example above, the variable words "months" and "month" could be changed to any other desired, arbitrarily determined, label:

foo: system/locale/months
foreach bar foo [print bar]                ; variable labels are arbitrary
halt

Labeling the system/locale/months block is also not required. Without the label, the code is shorter, but perhaps just a bit harder to read:

foreach month system/locale/months [print month]
halt

Learning to read and think in terms of "foreach item in list [do this to each item]" is one of the most important fundamental concepts to grasp in programming. You'll see numerous repeated examples in this text. Be aware every time you see the word "foreach".

You can obtain lists of data from a variety of different sources. Notice that the "load" function is typically used to read lists of data. This example prints the files in the current folder on the hard drive:

folder: load %./
foreach file folder [print file]
halt

This example loads the list from a file stored on a web site:

names: load read http://re-bol.com/names.txt
foreach name names [print name]     
halt

5.2 Some Simple List Algorithms (Count, Sum, Average, Max/Min)

5.2.1 Counting Items

The "length?" function counts the number of items in a list:

do %r3-gui.r3
receipts: [$5.23 $95.98 $7.46 $34]        ; a list labeled "receipts"
alert rejoin ["There are " length? receipts " receipts in the list."]

You can assign counts to variable labels and use the values later:

do %r3-gui.r3
month-count: length? system/locale/months 
day-count: length? system/locale/days
alert rejoin ["There are " month-count " months and " day-count " days."]

Another way to count items in a list is to create a counter variable, initially set to 0. Use a foreach loop to go through each item in the list, and increment (add 1) to the count variable:

do %r3-gui.r3
count: 0
receipts: [$5.23 $95.98 $7.46 $34]
foreach receipt receipts [count: count + 1]   ; increment count by 1
alert rejoin ["There are " count " receipts in the list."]

Here's an alternate syntax for incrementing counter variables:

do %r3-gui.r3
count: 0
receipts: [$5.23 $95.98 $7.46 $34] 
foreach receipt receipts [++ count]           ; increment count by 1
alert rejoin ["There are " count " receipts in the list."]

This example counts the number of months in a year and the number of days in a week, using counter variables:

do %r3-gui.r3
month-count: 0
day-count: 0
foreach month system/locale/months [++ month-count] 
foreach day system/locale/days [++ day-count]
alert rejoin ["There are " month-count " months and " day-count " days."]

Counter variables are particularly useful when you only want to count certain items in a list. The following example counts only items that are number values:

do %r3-gui.r3
count: 0
list: ["screws" 14 "nuts" 38 "bolts" 23]
foreach item list [
    ; Increment only if item type is integer:
    if (type? item) = integer! [++ count]
]
alert rejoin ["The count of all number values in the list is: " count]

5.2.2 Sums

To calculate the sum of numbers in a list, start by assigning a sum variable to 0. Then use a foreach loop to increment the sum by each individual number value. This example starts by assigning the label "balance" to a value of 0. Then the label "receipts" is assigned to a list of money values. Then, each value in the receipts list is added to the balance, and that tallied balance is displayed:

do %r3-gui.r3
sum: 0                                ; a sum variable, initially set to 0
receipts: [$5.23 $95.98 $7.46 $34]            ; a list, labeled "receipts"
foreach item receipts [sum: sum + item]                      ; add them up
alert rejoin ["The sum of all receipts is: " sum]

You could total only the items in a list which contain number values, for example, like this:

do %r3-gui.r3
sum: 0
list: ["screws" 14 "nuts" 38 "bolts" 23]
foreach item list [
    if (type? item) = integer! [            ; only if item type is integer
        sum: sum + item                     ; add item to total
    ]
]
alert rejoin ["The total of all number values in the list is: " sum]

5.2.3 Averages

Computing the average value of items in a list is simply a matter of dividing the sum by the count:

do %r3-gui.r3
sum: 0
receipts: [$5.23 $95.98 $7.46 $34]
foreach item receipts [sum: sum + item]
average: sum / (length? receipts)
alert rejoin ["The average balance of all receipts is: " average]

You can round to a specified number of decimal places with the round/to function:

do %r3-gui.r3
sum: 0
receipts: [$5.23 $95.98 $7.46 $34]
foreach item receipts [sum: sum + item]
average: round/to (sum / (length? receipts)) .01
alert rejoin ["The average balance of all receipts is: " average]

5.2.4 Maximums and Minimums

REBOL has built in "maximum-of" and "minimum-of" functions:

receipts: [$5.23 $95.98 $7.46 $34]
print first maximum-of receipts
print first minimum-of receipts
halt

You can perform more complicated max/min comparisons by checking each value with a conditional evaluation. This example looks for the highest receipt value under $50:

do %r3-gui.r3
highest: $0
receipts: [$5.23 $95.98 $7.46 $34]
foreach receipt receipts [
    if (receipt > highest) and (receipt < $50) [highest: receipt]
]
alert rejoin ["Maximum receipt below fifty bucks: " highest]

5.3 Searching

The "find" function is used to perform simple searches:

do %r3-gui.r3
names: ["John" "Jane" "Bill" "Tom" "Jen" "Mike"]
if find names "Bill" [alert "Yes, Bill is in the list!"]
if not find names "Paul" [alert "No, Paul is not in the list."]

You can determine the index position of a found item in a list, using the "index?" function:

names: ["John" "Jane" "Bill" "Tom" "Jen" "Mike"]
indx: index? find names "Bill"
print rejoin ["Bill is at position " indx " in the list."]
halt

You can search for text within each item in a list using a foreach loop to search each individual value:

names: ["John" "Jane" "Bill" "Tom" "Jen" "Mike"]
foreach name names [
    if find name "j" [
        print rejoin ["'j' found in " name]
    ]
]
halt

5.4 Gathering Data, and the "Copy" Function

When collecting ("aggregating") values into a new block, always use the "copy" function to create the new block. You'll need to do this whenever a sub-list or super-list of values is created based upon conditional evaluations performed on data in a base list:

low-receipts: copy []             ; Create blank list with copy [], NOT []
receipts: [$5.23 $95.98 $7.46 $34]
foreach receipt receipts [
    if receipt < $10 [append low-receipts receipt]     ; add to blank list
]
print low-receipts
halt

For example, the following line should should NOT be used (it does not contain the word "copy" when creating a blank list):

low-receipts: []               ; WRONG - should be   low-receipts: COPY []

The same is true when creating blank string values. Use the "copy" function whenever you create an empty text value that you intend to adjust or add to:

names: copy {}                  ; Create blank string with copy {}, NOT {}
people: ["Joan" "George" "Phil" "Jane" "Peter" "Tom"]
foreach person people [
    if find person "e" [
        append names rejoin [person " "]    ; This appends to blank string
    ]
]
print names
halt

5.5 List Comparison Functions

REBOL has a variety of useful built in list comparison functions. You'll use these for determining differences, similarities, and combinations between sets of data:

group1: ["Joan" "George" "Phil" "Jane" "Peter" "Tom"]
group2: ["Paul" "George" "Andy" "Mary" "Tom" "Tom"]
print rejoin ["Group 1: " group1]  
print ""   
print rejoin ["Group 2: " group2]
print newline
print rejoin ["Intersection:           " intersect group1 group2]
print "^/(values shared by both groups)^/^/"  
print rejoin ["Difference:             " difference group1 group2]
print "^/(values not shared by both groups)^/^/"
print rejoin ["Union:                  " union group1 group2]
print "^/(all unique values contained in both groups)^/^/"
print rejoin ["Join:                   " join group1 group2]
print "^/(one group tacked to the end of the other group)^/^/"
print rejoin ["Excluded from Group 2:  " exclude group1 group2]
print "^/(values contained in group1, but not contained in group2)^/^/"
print rejoin ["Unique in Group 2:      " unique group2]
print "^/(unique values contained in group2)"
halt

5.6 Creating Lists From User Input - The Inventory Program

5.6.1 Creating New Blocks and Adding Values

You can create a new block using the code pattern below. Simply assign variable labels to "copy []":

items: copy []          ; new empty block named "items"
prices: copy []         ; new empty block named "prices"

Add values to new blocks using the "append" function:

items: copy []
prices: copy []
append items "Screwdriver"
append prices "1.99"
append items "Hammer"
append prices "4.99"
append items "Wrench"
append prices "5.99"

Use the "print", "probe", or "view [area ()]" functions to view the data in a block. The "print" function simply prints the values in the block. The "probe" function shows the block data structure (square brackets enclosing the values, quotes around text string values, etc.). The "view []" function opens a GUI window, with an area widget displayed the printed text - the "form" function is used to convert the block into a readable text string (GUI area widgets can only display text (string) values):

do %r3-gui.r3
items: copy []
prices: copy []
append items "Screwdriver"
append prices "1.99"
append items "Hammer"
append prices "4.99"
append items "Wrench"
append prices "5.99"

print rejoin ["ITEMS:   " items newline]
print rejoin ["PRICES:  " prices newline]

probe items
probe prices

view [area (form items)]
view [area (form prices)]

5.6.2 Accepting Data Input from a User

You've already been introduced to the "request-text" function. It accepts text input from a user (be sure to save the requestors.r3 file presented earlier in this text, and "do %requestors.r3" at the beginning of any script that uses that function):

request-text

You can assign a variable label to the data entered by the user, and then use that data later in your program:

price: request-text
alert price

You can add a text title to the request-text function, with the "/title" refinement:

price: request-text/title "Input a dollar value:"
alert price

You can add a default text response using the "/default" refinement:

price: request-text/default "14.99"
alert price

You can combine the "/title" and "/default" refinements:

price: request-text/title/default "Input a dollar value:" "14.99"
alert price

The "ask" function does the same thing, but within the text environment of the REBOL interpreter console (instead of using a popup windowed requestor):

price: ask "Input a dollar value:  $"
alert price

You've already seen how GUI field widgets can be used to enter data. Whereas text requestors provide a quick and simple way to get single strings of text from a user, most programs which require multiple fields of data entry, will typically use GUIs to simplify the process for the user. In a GUI fields can be entered in any order, edited as needed, and then submitted when complete. Use the "get-face" function to get data from labeled widgets:

REBOL [title: "GUI Data Entry"]
do %r3-gui.r3
view [
    i: field "Item"
    p: field "Price"
    button "Submit" on-action [
        alert get-face i
        alert get-face p
    ]        
]

5.6.3 Building Blocks from User-Entered Data

Add data to a block, which has been entered by the user, using the code pattern below. Append the variable label of the entered data to the block label:

do %requestors.r3
items: copy []
prices: copy []
item: request-text/title/default "Item:" "screwdriver"
price: request-text/title/default "Price:" "1.99"
append items item
append prices price
probe items
probe prices
halt

The example below allows users to enter new inventory items and prices into 2 GUI field widgets, labeled "i" and "p". A GUI button widget performs several operations to complete the submission process. Notice these important things about the button action block:

  1. The "append" function is used to add text typed by the user into the i and p field widgets, to the items and prices data blocks.
  2. The word "copy" is used to get data from the i and p fields. Do this any time you are building blocks of data from GUI widgets.
  3. An area widget labeled b is updated to display the data currently contained in each block. That text is all concatenated into a single long string of text, with the "rejoin" function.
  4. The ^/ character used in the rejoin function represents a new line (carriage return).
REBOL [title: "Inventory - Simple"]
do %r3-gui.r3
items: copy []
prices: copy []
view [
    i: field "Item"
    p: field "Price"
    button "Submit" on-action [
        append items copy get-face i
        append prices copy get-face p
        set-face b rejoin ["ITEMS:^/^/" items "^/^/PRICES:^/^/" prices]
    ]
    b: area
]

You could just as easily add the entered data to a single block. When examining the data block, you know that every two consecutive values is an item and a price. In practice, this pattern of writing consecutive data items to a single file is how most data is stored in typical REBOL programs:

REBOL [title: "Inventory - Single Data Block"]
do %r3-gui.r3
inventory: copy []
view [
    i: field "Item"
    p: field "Price"
    button "Submit" on-action [
        append inventory copy get-face i
        append inventory copy get-face p
        set-face b rejoin ["ITEMS AND PRICES:^/^/" inventory]
    ]
    b: area
]

5.6.4 Saving and Reading Block Data To/From Files

Save a block to a text file using the "save" function. Remember that in REBOL, file names always begin with the "%" character:

inventory: ["Screwdriver" "1.99" "Hammer" "4.99" "Wrench" "5.99"]
save %inv inventory
alert "Saved"

Load blocked data from a saved file using the "load" function. You can assign a variable label to the loaded data, to use it later in the program:

inventory: load %inv
print "Inventory:^/"
probe inventory
halt

If a data file doesn't exist, you can create it with the write/append function. Simply append "" (nothing) to it - that way if the file already exists, it won't be altered. If it doesn't exist, it will be created as an empty file:

write/append %inv ""

You can delete the data file at any point using the delete function. You can clear the file by writing "" to it (be careful!):

delete %inv
write %inv ""

Here's a GUI app that loads and saves inventory to a file, using all the techniques shown so far:

REBOL [title: "Inventory - Load and Save Data to File"]
do %r3-gui.r3
write/append %inv ""
inventory: load %inv
view [
    i: field "Item"
    p: field "Price"
    button "Submit" on-action [
        append inventory copy get-face i
        append inventory copy get-face p
        save %inv inventory
        set-face b rejoin ["ITEMS AND PRICES:^/^/" inventory]
    ]
    b: area
]

You can also append data directly to a file using the "write/append" function. This keeps you from having to load, append, and the re-save the data file. When using the "write/append" function, use the "mold" function to enclose each text value in quotes, and the "rejoin" function to separate each value with a space (enter blank item text to end input loop):

REBOL [title: "Inventory - Write/Append Data to File"]
do %r3-gui.r3
view [
    i: field "Item"
    p: field "Price"
    button "Submit" on-action [
        write/append %inv rejoin [mold get-face i " " mold get-face p " "]
        alert "Saved"
    ]
]

The following inventory program uses all the techniques described above

REBOL [title: "Inventory"]
do %r3-gui.r3
write/append %inv ""
view [
    i: field "Item"
    p: field "Price"
    button "Add New" on-action [
        write/append %inv rejoin [mold get-face i " " mold get-face p " "]
        set-face b form load %inv
    ]
    text "Items and Prices:"
    b: area (form load %inv)
    button "Delete All Inventory Items" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %inv ""
            set-face b form load %inv
        ]
    ]
]

5.7 Three Useful Data Storage Programs: Inventory, Contacts, Schedule

The final inventory program above provides a nice template for practical applications of all types:

REBOL [title: "Inventory with Data Printout"]
do %r3-gui.r3
write/append %inv ""
view [
    i: field "Item"
    p: field "Price"
    button "Add New" on-action [
        write/append %inv rejoin [mold get-face i " " mold get-face p " "]
        set-face b form load %inv
    ]
    text "Items and Prices:"
    b: area (form load %inv)
    button "Delete All Inventory Items" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %inv ""
            set-face b form load %inv
        ]
    ]
]

Here's the same program as above, changed slightly to store and display contact information:

REBOL [title: "Contacts with Data Printout"]
do %r3-gui.r3
write/append %contacts ""
view [
    n: field "Name"
    a: field "Address"
    p: field "Phone"
    button "Add New" on-action [
        write/append %contacts rejoin [
            mold get-face n " " mold get-face a " " mold get-face p " "
        ]
        set-face b form load %contacts
    ]
    text "Names, Addresses, and Phone Numbers:"
    b: area (form load %contacts)
    button "Delete All Contacts" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %contacts ""
            set-face b form load %contacts
        ]
    ]
]

Here it is again, repurposed to hold schedule information:

REBOL [title: "Schedule with Data Printout"]
do %r3-gui.r3
write/append %schedule ""
view [
    text "Event Title:"
    e: field "Meeting with "
    text "Date:"
    d: field "1-jan-2013"
    text "Time:"
    t: field "12:00pm"
    text "Notes:"
    n: field "Bring: "
    button "Add New" on-action [
        write/append %schedule rejoin [
            mold get-face e " " mold get-face d " " 
            mold get-face t " " mold get-face n " "
        ]
        set-face b form load %schedule
    ]
    text "Events:"
    b: area (form load %schedule)
    button "Delete All Events" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %schedule ""
            set-face b form load %schedule
        ]
    ]
]

The types of data you store using these sorts of operations can be adjusted specifically to your particular data management needs for any given task. Your ability to apply this code to practical situations is limited only by your own creativity. All you need to do is change the text titles and the variable labels to clarify the type of data being stored.

5.8 Working With Tables of Data: Columns and Rows

Columns within tables of data are arranged in sequential order in blocks. Indentation and white space helps to display columns neatly, within a visual "table" layout. The following table conceptually contains 3 rows of 3 columns of data, but the whole block is still just a sequential list of 9 items:

accounts:  [
    "Bob"   $529.23   21-jan-2013
    "Tom"   $691.37   13-jan-2013
    "Ann"   $928.85   19-jan-2013
]

The foreach function in the next example alerts the user with every three consecutive data values in the table (each row of 3 consecutive name, balance, and date column values):

do %r3-gui.r3
accounts:  [
    "Bob" $529.23 21-jan-2013
    "Tom" $691.37 13-jan-2013
    "Ann" $928.85 19-jan-2013
]
foreach [name balance date] accounts [
    alert rejoin [
        "Name: " name ", Date: " date ", Balance: " balance
    ]
]

This example displays the computed balance for each person on the given date. The amount displayed is the listed "balance" value for each account, minus a universal "fee" value):

do %r3-gui.r3
accounts:  [
    "Bob" $529.23 21-jan-2013
    "Tom" $691.37 13-jan-2013
    "Ann" $928.85 19-jan-2013
]
fee: $5
foreach [name balance date] accounts [
    alert rejoin [name "'s balance on " date " will be " balance - fee]
]

Here's a variation of the above example which displays the sum of values in all accounts:

do %r3-gui.r3
accounts:  [
    "Bob" $529.23 21-jan-2013
    "Tom" $691.37 13-jan-2013
    "Ann" $928.85 19-jan-2013
]
sum: $0
foreach [name balance date] accounts [sum: sum + balance]
alert rejoin ["The total of all balances is: " sum]

Here's a variation that computes the average balance:

do %r3-gui.r3
accounts:  [
    "Bob" $529.23 21-jan-2013
    "Tom" $691.37 13-jan-2013
    "Ann" $928.85 19-jan-2013
]
sum: $0
foreach [name balance date] accounts [sum: sum + balance]
alert rejoin [
    "The average of all balances is: " 
    sum / ((length? accounts) / 3)
]

Here are 3 variations of the Inventory, Schedule, and Contacts applications from the previous section, slightly adjusted using the "foreach" function to format a more cleanly printed data display:

REBOL [title: "Inventory with Data Printout"]
do %r3-gui.r3
write/append %inv ""
view [
    i: field "Item"
    p: field "Price"
    button "Add New" on-action [
        write/append %inv rejoin [mold get-face i " " mold get-face p " "]
        set-face b form load %inv
    ]
    text "Items and Prices:"
    b: area (form load %inv)
    button "Delete All Inventory Items" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %inv ""
            set-face b form load %inv
        ]
    ]
]
print "Inventory:^/"
foreach [item price] load %inv [
    print rejoin ["Item:   " item newline "Price:  " price]
]
print newline
halt

REBOL [title: "Contacts with Data Printout"]
do %r3-gui.r3
write/append %contacts ""
view [
    n: field "Name"
    a: field "Address"
    p: field "Phone"
    button "Add New" on-action [
        write/append %contacts rejoin [
            mold get-face n " " mold get-face a " " mold get-face p " "
        ]
        set-face b form load %contacts
    ]
    text "Names, Addresses, and Phone Numbers:"
    b: area (form load %contacts)
    button "Delete All Contacts" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %contacts ""
            set-face b form load %contacts
        ]
    ]
]
print "Contacts:^/"
foreach [name address phone] load %contacts [
    print rejoin [
        "Name:     " name newline
        "Address:  " address newline
        "Phone:    " phone newline
    ]
]
print newline
halt

REBOL [title: "Schedule with Data Printout"]
do %r3-gui.r3
write/append %schedule ""
view [
    text "Event Title:"
    e: field "Meeting with "
    text "Date:"
    d: field "1-jan-2013"
    text "Time:"
    t: field "12:00pm"
    text "Notes:"
    n: field "Bring: "
    button "Add New" on-action [
        write/append %schedule rejoin [
            mold get-face e " " mold get-face d " " 
            mold get-face t " " mold get-face n " "
        ]
        set-face b form load %schedule
    ]
    text "Events:"
    b: area (form load %schedule)
    button "Delete All Events" on-action [
        if not false = request/ask "Confirm" "Really Delete?" [
            write %schedule ""
            set-face b form load %schedule
        ]
    ]
]
print "Events:^/"
foreach [event date time notes] load %schedule [
    print rejoin [
        "Event:  " event newline
        "Date:   " date newline
        "Time:   " time newline
        "Notes:  " notes newline
    ]
]
print newline
halt

Here is a report for the Inventory program which counts the number of inventory items and calculates a sum of inventory prices:

REBOL [title: "Inventory"]
do %requestors.r3
inventory: load %inv
count: 0
sum: $0
foreach [item price] inventory [
    count: count + 1
    sum: sum + to-money price
]
print newpage
print rejoin ["Total # of Items:  " count]
print rejoin ["Sum of Prices:     " sum]
halt

Here's a variation of the Contacts application that searches for saved names, and prints out any matching contact information:

do %requestors.r3
search: request-text/title/default "Search text:" "John"
contacts: load %contacts
print newpage
print rejoin [search " found in:^/"]
foreach [name address phone] contacts [
    if find name search [
        print rejoin [
            "Name:      " name newline
            "Address:   " address newline
            "Phone:     " phone newline
        ]
    ]
]
halt

The ability to conceptually "flatten" tabular data into sequential streams of items, and vice-versa, to think of consecutive groups of items in a list as rows within mapped categorical columns, is fundamentally important to working with all sorts of business data sets. You'll see this concept applied regularly throughout examples in this tutorial and in real working code.

5.9 Additional List/Block/Series Functions and Techniques

REBOL has built-in functions for performing every imaginable manipulation to list content, order, and other block properties - adding, deleting, searching, sorting, comparing, counting, replacing, changing, moving, etc. Here's a quick demonstrative list of functions. Try pasting each line individually into the REBOL interpreter to see how each function works:

do %requestors.r3

names: ["John" "Jane" "Bill" "Tom" "Jen" "Mike"]  ; a list of text strings

print "Two ways of printing values, 'probe' and 'print':"
probe names   ; "Probe" is like "print", but it shows the actual data
print names   ;  structure. "Print" attempts to format the displayed data.

print "^/Sorting:"
sorted: sort copy names    ; "Sort" sorts values ascending or descending.
probe names                ; "Copy" keeps the names block from changing
print sorted
sort/reverse names         ; Here, the names block has been sorted without
probe names                ; copy, so it's permanently changed.

print "^/Picking items:"
probe first names                 ; 3 different ways to pick the 1st item:
probe names/1 
probe pick names 1 
probe second names                ; 3 different ways to pick the 2nd item:
probe names/2
probe pick names 2

print "^/Searching:"
probe find names "John"                            ; How to search a block
probe first find names "John"
probe find/last names "Jane"
probe select names "John"                    ; Find next item after "John"

print "^/Taking sections of a series:"
probe at names 2
probe skip names 2                                  ; Skip every two items
probe extract names 3                           ; Collect every third item

print "^/Making changes:"
append names "George"
probe names
insert (at names 3) "Lee" 
probe names
remove names
probe names 
remove find names "Mike" 
probe names
change names "Phil" 
probe names
change third names "Phil"
probe names
poke names 3 "Phil" 
probe names
probe copy/part names 2 
replace/all names "Phil" "Al"
probe names

print "^/Skipping around:"
probe head names 
probe next names 
probe back names 
probe last names 
probe tail names  
probe index? names 

print "^/Converting series blocks to strings of text:"
probe form names
probe mold names

print "^/Other Series functions:"
print length? names 
probe reverse names 
probe clear names
print empty? names
halt

To demonstrate just a few of the functions above, here are some practical examples of common list operations, performed on a block of user contact information. The demonstration block of data is organized as 5 rows of 3 columns of data (name, address, phone), or 15 consecutive items in a list labeled "users". Notice that to maintain the column and row structure, empty strings ("") are placed at positions in the list where there is no data:

users: [
    "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"
]

append users ["Joe Thomas" "" "555-321-7654"]  ; append to end of list
probe users

probe (at users 4)                      ; parentheses are not required

insert (at users 4) [
    "Tom Adams" "321 Way Lane Villageville, AZ" "555-987-6543"
]
probe users

remove (at users 4)                                   ; remove 1 item
probe users

; BE CAREFUL - the line above breaks the table structure by removing
; an item entirely, so all other data items are shifted into incorrect
; columns.  Instead, either replace the data with an empty place holder
; or remove the address and phone fields too:

remove/part (at users 4) 2                            ; remove 2 items
probe users

change (at users 1) "Jonathan Smith"
probe users

remove (at users 1) insert (at users 1) "Jonathan Smith"
probe users
halt

The "extract" function is useful for picking out columns of data from structured blocks:

users: [
    "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 users 3             ; names
probe extract (at users 2) 3      ; addresses 
probe extract (at users 3) 3      ; phone numbers 
halt

You can "pick" items at a particular index location in the list:

users: [
    "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"
]
print pick users 1                  ; FIRST name
print pick users 2                  ; FIRST address
print pick users 3                  ; FIRST phone
;
print pick users 4                  ; SECOND name
print pick users 5                  ; SECOND address
print pick users 6                  ; SECOND phone
;
indx: length? users                 ; index position of the LAST item
print pick users indx               ; last item
print pick users (indx - 1)         ; second to last item
print pick users (random length? users)  ; random item
halt

You can determine the index location at which an item is found, using the "find" function:

indx: index? find users "John Smith"

In REBOL there 4 ways to pick items at such a variable index. Each syntax below does the exact same thing. These are just variations of the "pick" syntax:

print pick users indx
print users/:indx
print compose [users/(indx)]       ; put composed values in parentheses
print reduce ['users/(indx)]       ; put a tick mark on non-reduced values

Pay particular attention to the "compose" and "reduce" functions. They allow you to convert static words in blocks to evaluated values:

; This example prints "[month]" 12 times:

foreach month system/locale/months [
    probe [month]
]

; These examples print all 12 month values:

foreach month system/locale/months [
    probe reduce [month]
]

foreach month system/locale/months [
    probe compose [(month)]
]

Here's a complete example that requests a name from the user, finds the index of that name in the list, and picks out the name, address, and phone data for that user (located at the found indx, indx + 1, and indx + 2 positions):

REBOL [title: "Search Users"]
do %requestors.r3
users: [
    "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"
]

name: request-text/title/default "Name:" "Jim Persee"
indx: index? find users name

print rejoin [
    (pick users indx) newline
    (pick users (indx + 1)) newline
    (pick users (indx + 2)) newline
]
halt

Here's a version that uses code from the "Contacts" program you saw earlier. It allows you to create your own user database, and then search and display entries with the code above:

REBOL [title: "Search My Stored Contacts"]
do %requestors.r3

; This data is borrowed from the "Contacts" program seen earlier:

users: load %contacts

; This is a variation of the code above which adds an error check, to
; provide a response if the search text is not found in the data block:

name: request-text/title/default "Search For:" "John Smith"
if error? try [indx: index? find users name] [
    alert "Name not found" quit
]
print rejoin [
    (pick users indx) newline
    (pick users (indx + 1)) newline
    (pick users (indx + 2)) newline
]
halt

5.10 Sorting Lists and Tables of Data

You can sort a list of data using the "sort" function:

print sort system/locale/months
halt

This example displays a list requestor with the months sorted alphabetically:

do %requestors.r3
request-list "Sorted months:" sort system/locale/months

If you sort a block of values consisting of data types that REBOL understands, the values will be sorted appropriately for their type (i.e., chronologically for dates and times, numerically for numbers, alphabetically for text strings):

probe sort [1 11 111 2 22 222 8 9 5]  ; sorted NUMERICALLY
probe sort ["1" "11" "111" "2" "22" "222" "8" "9" "5"]  ; ALPHABETICALLY
probe sort [1-jan-2012 1-feb-2012 1-feb-2011]  ; sorted CHRONOLOGICALLY
halt

To sort by the first column in a table, use the "sort/skip" refinement. The table below is made up of 5 rows of 3 conceptual columns, so the first item of each row is found by skipping every 3 values:

users: [
    "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 sort/skip users 3
halt

Sorting by any other selected column requires that data be restructured into blocks of blocks which clearly define the column structure. For example, this "flat" table, although visually clear, is really just a consecutive list of 15 data items:

users: [
    "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"
]

To sort it by colomn, the data must be represented as follows (notice that conceptual rows are now separated into discrete blocks of 3 columns of data):

blocked-users: [
    ["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"]
]

The following code demonstrates how to convert a flattened block into such a structure of nested row/column blocks:

users: [
    "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"
]
blocked-users: copy []
foreach [name address phone] users [
    ; APPEND/ONLY inserts blocks as blocks, instead of as individual items
    ; The REDUCE function convert the words "name", "address", and "phone"
    ; to text values:
    append/only blocked-users reduce [name address phone]
]
probe blocked-users
halt

Now you can use the "/compare" refinement of the sort function to sort by a chosen column (field):

blocked-users: [
    ["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"]
]
field: 2                          ; column to sort (address, in this case)
sort/compare blocked-users func [a b] [(at a field) < (at b field)]
probe blocked-users                ; sorted by the 2nd field (by address)
halt

To sort in the opposite direction (i.e., descending, as opposed to ascending), just change the "<" operater to ">":

blocked-users: [
    ["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"]
]
field: 2
sort/compare blocked-users func [a b] [(at a field) > (at b field)]
probe blocked-users
halt

Here's a complete example that converts a flat data block to a nested block of blocks, and then sorts by a user-selected field, in a chosen ascending/descending direction:

REBOL [title: "View Sorted Users"]
do %requestors.r3
users: [
    "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"
]
blocked-users: copy []
foreach [name address phone] users [
    append/only blocked-users reduce [name address phone]
]
field: to-integer request-list "Choose Field To Sort By:" ["1" "2" "3"]
order: request-list "Ascending or Descending:" ["ascending" "descending"]
either order = "ascending" [
    sort/compare blocked-users func [a b] [(at a field) < (at b field)]
][
    sort/compare blocked-users func [a b] [(at a field) > (at b field)]
]
probe blocked-users
halt

Here's a program that uses data created by the "Contacts" app presented earlier. It allows you to sort and display the fields, as in the example above:

REBOL [title: "Sort My Stored Contacts"]
do %requestors.r3

; This data is borrowed from the "Contacts" program seen earlier:

users: load %contacts

; This is a variation of the code from the example above:

blocked-users: copy []
foreach [name address phone] users [
    append/only blocked-users reduce [name address phone]
]
field-name: request-list "Choose Field To Sort By:" [
    "Name" "Address" "Phone"
]

; The "select" function chooses the next value in a list, selected by the
; user.  In this case if the field-name variable equals "name", the
; "field" variable is set to 1.  If the field-name variable equals
; "address", the "field" variable is set to 2.  If field-name="phone", the
; "field" variable is set to 3:

field: select ["name" 1 "address" 2 "phone" 3] field-name

order: request-list "Ascending or Descending:" ["ascending" "descending"]
either order = "ascending" [
    sort/compare blocked-users func [a b] [(at a field) < (at b field)]
][
    sort/compare blocked-users func [a b] [(at a field) > (at b field)]
]
probe blocked-users
halt

Note again that REBOL sorts data appropriately, according to type. If numbers, dates, times, and other recognized data types are stored as string values, the sort will be alphabetical for the chosen field (because that is the appropriate sort order for text):

do %requestors.r3
text-data: [
    "1"    "1-feb-2012"  "1:00am"   "abcd"
    "11"   "1-mar-2012"  "1:00pm"   "bcde"
    "111"  "1-feb-2013"  "11:00am"  "cdef"
    "2"    "1-mar-2013"  "13:00"    "defg"
    "22"   "2-feb-2012"  "9:00am"   "efgh"
    "222"  "2-feb-2009"  "11:00pm"  "fghi"
]
blocked: copy []
foreach [number date time string] text-data [
    append/only blocked reduce [number date time string]
]
field-name: request-list "Choose Field To Sort By:" [
    "number" "date" "time" "string"
]
field: select ["number" 1 "date" 2 "time" 3 "string" 4] field-name
order: request-list "Ascending or Descending:" ["ascending" "descending"]
either order = "ascending" [
    sort/compare blocked func [a b] [(at a field) < (at b field)]
][
    sort/compare blocked func [a b] [(at a field) > (at b field)]
]
probe blocked
halt

Convert values to appropriate data types during the process of blocking the "flattened" data, and fields will magically be sorted appropriately (in numerical, chronological, or other data-type-appropriate order):

do %requestors.r3
text-data: [
    "1"    "1-feb-2012"  "1:00am"   "abcd"
    "11"   "1-mar-2012"  "1:00pm"   "bcde"
    "111"  "1-feb-2013"  "11:00am"  "cdef"
    "2"    "1-mar-2013"  "13:00"    "defg"
    "22"   "2-feb-2012"  "9:00am"   "efgh"
    "222"  "2-feb-2009"  "11:00pm"  "fghi"
]
blocked: copy []
foreach [number date time string] text-data [
    append/only blocked reduce [
        to-integer number
        to-date date
        to-time time
        string
    ]
]
field-name: request-list "Choose Field To Sort By:" [
    "number" "date" "time" "string"
]
field: select ["number" 1 "date" 2 "time" 3 "string" 4] field-name
order: request-list "Ascending or Descending:" ["ascending" "descending"]
either order = "ascending" [
    sort/compare blocked func [a b] [(at a field) < (at b field)]
][
    sort/compare blocked func [a b] [(at a field) > (at b field)]
]
probe blocked
halt

5.11 CSV Files and the "Parse" Function

"Comma Separated Value" (CSV) files are a universal text format used to store and transfer tables of data. Spreadsheets, database systems, financial software, and other business applications typically can export and import tabular data to and from CSV format.

In CSV files, rows of data are separated by a line break. Column values are most often enclosed in quotes and separated by a comma or other "delimiter" character (sometimes a tab, pipe (|), or other symbol that visually separates the values).

5.11.1 Saving Tabular Data Blocks to CSV Files

You can create a CSV file from a block of REBOL table data, using the "foreach" function. Just rejoin each molded value (value enclosed in quotes), with commas separating each item, and a newline after each row, into a long text string. Then save the string to a file with the extension ".csv":

REBOL [title: "Save CSV"]
users: [
    "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] users [
    write/append %users.csv rejoin [
        mold name ", " mold address ", " mold phone newline
    ]
]

Try opening the file above with Excel or another spreadsheet application. Because the particular values in this data block contain commas within the address field, you may need to select "comma", "space", and "merge delimiters", or similar options, in programs such as OpenOffice Calc.

5.11.2 Loading Tabular Data Blocks From CSV Files

To import CSV files into REBOL, use the "read/lines" function to read the file contents, with one text line per item stored in the resulting block. Assign the results of the read/lines function to a variable label. Use "foreach" and REBOL's "parse" function to separate each item in the lines back to individual values. Collect all the resulting sequential values into an empty block, and you're ready to use the data in all the ways you've seen so far:

REBOL [title: "Load CSV - Flat"]
block: copy []
csv: read/lines %users.csv
foreach line csv [
    data: parse line ","
    append block data
]
probe block
foreach [name address phone] block [
    alert rejoin [name ": " address " " phone]
]
halt

The first parameter of the parse function is the data to be parsed (in the case above, each line of the CSV file). The second parameter is the delimiter character(s) used to separate each value. Assign a variable to the output of the parse function, and you can refer to each individual value as needed (using "pick" and other series functions). The code above creates a "flat" block. To create a block of blocks, in which each line of the CSV file is delineated into a separate interior (nested) block, just use the append/only function, as you've seen earlier:

REBOL [title: "Load CSV - Block of Blocks"]
block: copy []
csv: read/lines %users.csv
foreach line csv [
    data: parse line ","
    append/only block data
]
probe block
foreach line block [probe line]
halt

Parse's "/all" refinement can be used to control how spaces and other characters are treated during the text splitting process (for example, if you want to separate the data at commas contained within each quoted text string). You can use the "trim" function to eliminate extra spaces in values. Other functions such as "replace", "to-(value)", and conditional evaluations, for example, can be useful in converting, excluding, and otherwise processing imported CSV data.

Try downloading account data from an e-commerce site, or export report values from your financial software, and you'll likely see that the most prominent format is CSV. Accountants and others who use spreadsheets to crunch numbers will be able to instantly use CSV files in Excel, and/or export worksheet data to CSV format, for you to import and use in REBOL programs.

You'll learn much more about the extremely powerful "parse" function later. For now, it provides a simple way to import data stored in the common CSV format.

5.12 2 Paypal Report Programs, Analyzed

Take a look at the Paypal report example from the introduction of this text. You should now be able to follow how it parses and computes values from a downloaded CSV file:

REBOL [title: "Paypal Report"]
do %r3-gui.r3

; A variable used to calculate the sum is initially set to zero dollars:

sum: $0

; A foreach loop goes through every line in the downloaded CSV file,
; starting at the second line (the first line contains columns labels):

foreach line at parse read/lines http://re-bol.com/Download.csv  "^M" 2 [

    ; The sum is computed, using the money value in column 8:

    attempt [sum: sum + to-money pick row: parse/all line "," 8]

    ; If column 8 contains the name "Saoud", the amount (first column in
    ; the row), and the money value (eighth column) are both printed:

    if find row/4 "Saoud" [print rejoin [row/1 ", Saoud Gorn: " row/8]]

]

; The user is alerted with the total:

alert join "GROSS ACCOUNT TRANSACTIONS: " sum

Here's the whole program, without comments:

REBOL [title: "Paypal Report"]
do %r3-gui.r3
sum: $0
foreach line at parse read/lines http://re-bol.com/Download.csv  "^M" 2 [
    attempt [sum: sum + to-money pick row: parse/all line "," 8]
    if find row/4 "Saoud" [print rejoin [row/1 ", Saoud Gorn: " row/8]]
]
alert join "GROSS ACCOUNT TRANSACTIONS: " sum

This example deals with the time field in the same data file as above:

REBOL [title: "Paypal Morning Report"]
do %r3-gui.r3

; Variable used to calculate the sum is initially set to $0:

sum: $0

; A foreach loop goes through every line in the downloaded CSV file,
; starting at the second line (the first line contains columns labels):

foreach line at parse read/lines http://re-bol.com/Download.csv  "^M" 2 [

    ; If the name (column 4) contains "Ourliptef.com", then perform an
    ; additional conditional evaluation checking if the time field value
    ; (column 2) is between midnight and noon.  If so, add to the sum
    ; variable the money value in column 8:

    row: parse/all line ","
    if find row/4 "Ourliptef.com" [
        time: to-time row/2
        if (time >= 0:00am) and (time <= 12:00pm) [
            sum: sum + to-money row/8
        ]
    ]

]

; Alert the user with the result

alert join "2012 Ourliptef.com Morning Total: " sum

Here's the whole program, without comments:

REBOL [title: "Paypal Morning Reports]
do %r3-gui.r3
sum: $0
foreach line at parse read/lines http://re-bol.com/Download.csv  "^M" 2 [
    row: parse/all line ","
    if find row/4 "Ourliptef.com" [
        time: to-time row/2
        if (time >= 0:00am) and (time <= 12:00pm) [
            sum: sum + to-money row/8
        ]
    ]
]
alert join "2012 Ourliptef.com Morning Total: " sum

To see the data these scripts are sorting through, take a look at the raw data in the Download.csv file.

5.13 Some Perspective about Studying These Topics

The List and Table Data topics are the most difficult sections in the first half of the tutorial. They will likely require several readings to be fully understood. Start by skimming once, and become familiar with the basic language structures and general code patterns. During the first read, you should be aware that demonstrated functions and code snippets can simply be copied, altered, and pasted for use in other applications. You don't need to memorize or even thoroughly understand how each line of code works. Instead, it's more important to understand that the functions and block/table concepts contained here simply exist and produce the described results. You will learn and internalize the details only by rote, through an extended period of reading, studying, copying, altering, and eventually writing fluently. There is a lot of material here to consume. It will likely take numerous applied hours of coding to fully understand it all.

You may find small snippets of code which provide solutions for the initial problem(s) and curiosities that motivated you to "learn how to program". Spend extra time experimenting with those pieces of code that are most interesting and relevant to your immediate needs. Copy, paste, and run examples in the REBOL interpreter. Get used to editing and altering pieces of code to become more familiar with the syntax. Change variable labels, enter new data values, and try repurposing code examples to fit new data sets. Try to break working code and figure out how to fix it. Get used to USING the REBOL text editor and the interpreter console. Get your hands dirty and bury yourself in the mechanics of typing and running code. Becoming comfortable with the tool set and working environment is huge part of the battle.

You will learn REBOL and all other programming languages in the exact same way you would learn any spoken language: by "speaking" it. You must mimic at first (copy/paste code), and then learn to put together "phrases" that make sense (edit, experiment, and rearrange words), and eventually write larger compositions fluently. You will regularly make mistakes with function syntax as you learn to code, just as children make mistakes with grammar as they learn to speak. You'll only learn by experimenting creatively, and by experiencing errors.

It's important not to let initial confusion and error stop you from learning at this point. Use the materials in this section as a reference for looking up functions and syntax as you progress through the tutorial. Try to understand some key points about the structure of the language, especially the code patterns related to block and table operations, but realize that you'll only internalize so much detail during your first read. Acquire as much understanding and experiment with code as much as your curiosity and motivation allows, then move on and see how other important topics fit together to form useful coding skills.

Once you've made it past the next few sections, and in particular the complete programs section, you will have gotten a solid overview of how all the fundamental concepts are put to use. It's a good idea to review the entire first part of the tutorial at that point, paying closer attention to the fine details of each line of code, memorizing functions, immersing yourself in the logic of each word's operation, etc. For now, read and understand the general conceptual overview, and try not to get stuck on any single topic.

For more information about using lists and tables of data in REBOL, see http://www.rebol.com/docs/core23/rebolcore-6.html.

6. Quick Review and Clarification

The list below summarizes some key characteristics of the REBOL language. Knowing how to put these elements to use constitutes a fundamental understanding of how REBOL works:

  1. To start off, REBOL has hundreds of built-in function words that perform common tasks. As in other languages, function words are typically followed by passed data parameters. Unlike other languages, passed parameters are placed immediately after the function word and are not necessarily enclosed in parentheses. To accomplish a desired goal, functions are arranged in succession, one after another. The value(s) returned by one function are often used as the argument(s) input to another function. Line terminators are not required at any point, and all expressions are evaluated in left to right order, then vertically down through the code. Empty white space (spaces, tabs, newlines, etc.) can be inserted as desired to make code more readable. Text after a semicolon and before a new line is treated as a comment. You can complete significant work by simply knowing the predefined functions in the language, and organizing them into a useful order.
  2. REBOL contains a rich set of conditional structures, which can be used to manage program flow and data processing activities. If, either, and other typical structures are supported.
  3. Because many common types of data values are automatically recognized and handled natively by REBOL, calculating, looping, and making conditional decisions based upon data content is straightforward and natural to perform, without any external modules or toolkits. Numbers, text strings, money values, times, tuples, URLs, binary representations of images, sounds, etc. are all automatically handled. REBOL can increment, compare, and perform proper computations on most common types of data (i.e., the interpreter automatically knows that 5:32am + 00:35:15 = 6:07:15am, and it can automatically apply visual effects to raw binary image data, etc.). Network resources and Internet protocols (http documents, ftp directories, email accounts, dns services, etc.) can also be accessed natively, just as easily as local files. Data of any type can be written to and read from virtually any connected device or resource (i.e., "write %file.txt data" works just as easily as "write ftp://user:pass@website.com data", using the same common syntax). The percent symbol ("%") and the syntax "%(/drive)/path/path/.../file.ext" are used cross-platform to refer to local file values on any operating system.
  4. Any data or code can be assigned a word label. The colon character (":") is used to assign word labels to constants, variable values, evaluated expressions, and data/action blocks of any type. Once assigned, variable words can be used to represent all of the data contained in the given expression, block, etc. Just put a colon at the end of a word, and thereafter it represents all the following data.
  5. Multiple pieces of data are stored in "blocks", which are delineated by starting and ending brackets ("[]"). Blocks can contain data of any type: groups of text strings, arrays of binary data, other enclosed blocks, etc. Data items contained in blocks are separated by white space. Blocks can be automatically treated as lists of data, called "series", and manipulated using built-in functions that enable searching, sorting, ordering, and otherwise organizing the blocked data. Blocks are used to delineate most of the syntactic structures in REBOL (i.e., actions resulting from conditional evaluations, GUI widget layouts, etc.).
  6. "Foreach" function can be used to process lists and tables of data. Rows and columns if data in tables can be processed using the format: "foreach [col1 col2 col3...] table-block [do something to each row pof values]". Data can be saved to CSV files by rejoining molded (quoted) text and delimiters. CSV files can be read using the "read/lines" and "parse" functions. Sorting data by column with the sort/compare function requires that rows be saved in nested blocks, rather than as "flat" sequential lists of items. Data stored as (quoted) text string values is sorted alphabetically. Data stored or converted to specific data type values is sorted appropriately for the type.
  7. The syntax "view [block]" is used to create basic GUI layouts. You can add widgets to the layout simply by placing widget identifier words inside the block: "button", "field", "text-list", etc. Color, position, spacing, and other facet words can be added after each widget identifier. Action blocks added immediately after any widget and the "on-action" actor will perform the enclosed functions whenever the widget is activated (i.e., when the widget is clicked with a mouse, when the enter key pressed, etc.). Path refinements can be used to refer to items in the GUI layout (i.e., "face/gob/offset" refers to the position of the selected widget face. The "get-face" and "set-face" functions are are used to retrieve and set the main value stored in a widget, such as the text in a text field widget. Those simple guidelines can be used to create useful GUIs for data input and output, in a way that's native (doesn't require any external toolkits) and much easier than any other language.

7. COMPLETE APPLICATION EXAMPLES

Here are some simple but complete app examples.

7.1 Math Test

Here's a little app to help students practice math problems. You can set the number range and the operator in the line [random 10 " + " random 20]:

REBOL [title: "Math Test ("do"ing code in a field)"]
do %r3-gui.r3
random/seed now
x: does [rejoin [random 10 " + " random 20]]
view [
    f1: field (x)
    text "Answer:"
    f2: field on-action [
        either (get-face f2) = (form do get-face f1) [
            alert "Yes!"][alert "No!"
        ]
        set-face f1 x
        set-face f2 ""
        focus f2
    ]
]

7.2 Scheduler

Here's the GUI code for a scheduling app that allows users to create events on any day. Later, the user can click any day on the calendar to see the scheduled events:

REBOL [title: "Scheduler"]
do %requestors.r3
view [
    button "Date" 
    date: field
    text "Event Title:"
    event: field
    text "Time:"
    time: field
    text "Notes:"
    notes: field
    button "Add Appoinment"
    a: area
    button "View Schedule" 
]

Here's the final code that makes the whole scheduling app run. This entire program is really nothing more complex than the field saver, inventory, cash register, and other GUI examples you've seen so far. There are just a few more widgets, and of course a date requester to allow the user to select dates, but the same GUI, file reading/writing, and foreach block management are repurposed to enter, save, load, and display calendar data:

REBOL [title: "Scheduler"]
do %requestors.r3
view g: layout [
    button "Date" on-action [set-face date form request-date]
    date: field
    text "Event Title:"
    event: field
    text "Time:"
    time: field
    text "Notes:"
    notes: field
    button "Add Appoinment" on-action [
        write/append %appts rejoin [
            mold get-face date newline
            mold get-face event newline
            mold get-face time newline
            mold get-face notes newline 
        ]
        alert "Added"
        clear-layout g
    ]
    a: area
    button "View Schedule" on-action [
        today: request-date
        set-face a copy ""
        foreach [date event time notes] load %appts [
            if today = to-date date [
                set-face a rejoin [
                    get-face a
                    date newline
                    event newline
                    time newline
                    notes newline newline
                ]
            ]
        ]
    ]
]

7.3 Parts Database

Here's an application that allows workers to add, delete, edit, and view manufactured parts. This is another quintessential simple text field storage application. It can be used as shown here, to save parts information, but by adjusting just a few lines of code and text labels, it could be easily adapted to store contacts information, or any other type of related fields of blocked text data. Be sure to pay special attention to how the text-list widget is used, and be particularly aware of how the "pick" function is used to select consecutive values after a found index:

REBOL [title: "Parts"]
do %r3-gui.r3
write/append %data.txt ""
database: read/lines %data.txt
view [
    text "Parts in Stock:"
    name-list: text-list (extract database 4) on-action [
        if none = marker: arg [return none]
        marker: (marker * 4) - 3
        set-face n pick database marker
        set-face a pick database (marker + 1)
        set-face p pick database (marker + 2)
        set-face o pick database (marker + 3)
    ]
    text "Part Name:"       n: field
    text "Manufacturer:"    a: field
    text "SKU:"             p: field
    text "Notes:"           o: area
    hgroup [
        button "Save" on-action [
            if "" = get-face n [
                request "Error" "You must enter a Part name."
                return none
            ]
            if find (extract database 4) get-face n [
                either false = request/ask """Overwrite existing record?"[
                   return none
                ] [
                   remove/part (find database (get-face n)) 4
                ]
            ]
            append database reduce [
                get-face n  get-face a  get-face p  get-face o
            ]
            write/lines %data.txt database
            set-face/field name-list (extract copy database 4) 'data
            set-face name-list/names/scr 100%
        ]
        button "Delete" on-action [
            if not (false = request/ask "?" (rejoin [
                "Delete " get-face n "?"
            ])) [
                remove/part (find database (copy get-face n)) 4
                write/lines %data.txt database
                do-face b
                set-face/field name-list (extract copy database 4) 'data
                set-face name-list/names/scr 100%
            ]
        ]
        b: button "New" on-action [
            set-face n copy  ""
            set-face a copy  ""
            set-face p copy  ""
            set-face o copy  ""
        ]
    ]
]

Here's the same program as above, repurposed as a contacts database app (quite a bit more versatile than the earlier example). The field labels have simply been changed:

REBOL [title: "Contacts"]
do %r3-gui.r3
write/append %data.txt ""
database: read/lines %data.txt
clear-fields: does [
    set-face n copy  ""
    set-face a copy  ""
    set-face p copy  ""
    set-face o copy  ""
]
view [
    text "Existing Contacts:"
    name-list: text-list (extract database 4) on-action [
        if none = marker: get-face face [return none]
        marker: (marker * 4) - 3
        set-face n pick database marker
        set-face a pick database (marker + 1)
        set-face p pick database (marker + 2)
        set-face o pick database (marker + 3)
    ]
    text "Name:"       n: field
    text "Address:"    a: field
    text "Phone:"      p: field
    text "Notes:"      o: area
    hgroup [
        button "Save" on-action [
            if "" = get-face n [
                request "Error" "You must enter a name."
                return none
            ]
            if find (extract database 4) get-face n [
                either false = request/ask """Overwrite existing record?"[
                   return none
                ] [
                   remove/part (find database (get-face n)) 4
                ]
            ]
            append database reduce [
                get-face n  get-face a  get-face p  get-face o
            ]
            write/lines %data.txt database
            set-facet name-list 'list-data (extract copy database 4)
        ]
        button "Delete" on-action [
            if not (false = request/ask "?" (rejoin [
                "Delete " get-face n "?"
            ])) [
                remove/part (find database (copy get-face n)) 4
                write/lines %data.txt database
                clear-fields
                set-facet name-list 'list-data (extract copy database 4)
            ]
        ]
        button "New" on-action [clear-fields]
    ]
]

7.4 Time Clock and Payroll Report

Here's a time clock program that can be used to log employee punch-in and punch-out times for shift work. The GUI allows for easy addition and deletion of employee names, and a full audit history backup of every entry made to the sign ins, is saved to the hard drive each time any employee signs in or out.

REBOL [title: "Time Clock"]
do %requestors.r3
unless exists? %employees [write %employees {"John Smith" "(Add New...)"}]
write/append %time_sheet ""
cur-employee: copy ""
employees: copy sort load %employees
update-emp: does [
    write/append %employees (mold trim request-text/title "Name:")
    set 'employees (copy sort load %employees)
    set-face/field tl1 employees 'data
    set-face tl1/names/scr 100%
]
view/options [
    tl1: text-list (employees) on-action [          
        set 'cur-employee (pick employees arg)
        if cur-employee = "(Add New...)" [update-emp]
    ]
    button "Clock IN/OUT" on-action [
        if ((cur-employee = "") or (cur-employee = "(Add New...)")) [
            alert "You must select your name." return none
        ]
        record: rejoin [
            newline {[} mold cur-employee { "} mold now {"]}
        ]
        either false = request/ask "" rejoin [
            record " -- IS YOUR NAME AND THE TIME CORRECT?"
        ] [
            alert "CANCELED"
            return none
        ] [
            make-dir %./clock_history/
            write rejoin [
                %./clock_history/ 
                replace/all replace form now "/" "--" ":" "_"
            ] read %time_sheet
            write/append %time_sheet record
            alert rejoin [
                uppercase copy cur-employee ", YOUR TIME IS LOGGED."
            ]
        ]
    ]
] [
    shortcut-keys: [
        delete [
            del-emp: (copy to-string pick employees get-face tl1)
            temp-emp: (sort load %employees)
            if not false = request/ask "" rejoin ["REMOVE " del-emp "?"] [
                new-list: (head remove/part find temp-emp del-emp 1)
                save %employees new-list
                set 'employees (copy sort load %employees)
                set-face/field tl1 employees 'data
                set-face tl1/names/scr 100%
                alert rejoin [del-emp " removed."]
            ]
        ]
    ] 
]

Here's a program that creates payroll reports of hours worked by employees, logged by the program above, between any selected start and end dates:

REBOL [title: "Payroll Report"]
do %requestors.r3
timeclock-start-date: request-date
timeclock-end-date: request-date
totals: copy ""
names: load %employees
log: load %time_sheet
foreach name names [
    if name <> "(Add New...)" [
        times: copy reduce [name]
        foreach record log [
            if name = log-name: record/1 [
                date-time: parse record/2 "/"
                log-date: to-date date-time/1
                log-time: to-time first parse date-time/2 "-"
                if (
                    (log-date >= timeclock-start-date) and 
                    (log-date <= timeclock-end-date)
                ) [
                    append times log-date
                    append times log-time
                ] 
            ]
        ]
        append totals rejoin [name ":" newline]
        total-hours: 0
        foreach [in-date in-time out-date out-time] (at times 2) [
            append totals rejoin [
                newline 
                "    in: " in-date ", " in-time 
                "  out: " out-date ", " out-time "    "
            ]
            if error? try [
                total-hours: total-hours + (out-time - in-time)
            ] [
                alert rejoin [
                    "Missing login or Missing logout: " name
                ]
            ]
        ]
        append totals rejoin [
            newline newline
            "    TOTAL HOURS: " total-hours
            newline newline newline
        ]
    ]
]
write filename: copy rejoin [
    %timeclock_report-- timeclock-start-date 
    "_to_" timeclock-end-date ".txt"
] totals
view [area (read/string filename)]

7.5 A Universal Report Generator, For Paypal and any other CSV Table Data

This example creates reports on columns and rows of data in a .csv table. The script demonstrates typical CSV file operations using parse, foreach, and simple series functions. The code performs sums upon columns, and selective calculations upon specified fields, based upon conditional evaluations, searches, etc. Practical column and row based reporting capabilities such as this are a simple and useful skill in REBOL. Using basic permutations of techniques shown here, it's easy to surpass the capabilities of spreadsheets and other "office" reporting tools.

Actual data for use in testing this example can be downloaded as follows:

  1. Log into your Paypal account
  2. Click on My Account -> History -> Download History
  3. Pick a range of dates
  4. Select "Comma Delimited - All Activity" (where it's labeled "File Types to Download")
  5. Save the file to %Download.csv.

At the time this example code was created, the author's CSV file download contained the following fields ("A" through "AP" - 42 columns).

Date, Time, Time Zone, Name, Type, Status, Currency, Gross, Fee, Net, From Email Address, To Email Address, Transaction ID, Counterparty Status, Address Status, Item Title, Item ID, Shipping and Handling Amount, Insurance Amount, Sales Tax, Option 1 Name, Option 1 Value, Option 2 Name, Option 2 Value, Auction Site, Buyer ID, Item URL, Closing Date, Escrow Id, Invoice Id, Reference Txn ID, Invoice Number, Custom Number, Receipt ID, Balance, Address Line 1, Address Line 2/District/Neighborhood, Town/City, State/Province/Region/County/Territory/Prefecture/Republic, Zip/Postal Code, Country, Contact Phone Number

The code automatically handles tables with any arbitrary number of columns, allowing fields to be referred to by labels present in the first line of the .csv file.

The script is simple to understand:

REBOL [title: "Paypal Reports"]
do %requestors.r3
write %Download.csv read http://re-bol.com/Download.csv  ; sample data

; First, read in the lines of the CSV file
; (as described earlier in the CSV/parse section of this tutorial):

filename: request-file
lines: read/lines filename

; The first row contains column labels.  Let's convert that CSV line to a
; block, using the parse/all function:

labels: copy parse/all lines/1 ","

; Remove extra spaces from each of the labels in the "labels" block:

foreach label labels [trim label]

; Now we'll convert the CSV lines into a REBOL data block.  First,
; create an empty block to store the data:

database: copy []

; The data values start at line two of the CSV file.  Use foreach and
; parse/all to separate the individual values in each line, and collect
; them into a block of blocks (as described in the CSV/parse section):

foreach line (at lines 2) [
    parsed: parse/all line ","
    append/only database parsed
]

; For the first report, let's get a list of every person in the "Name"
; Column.  Find the index number of the "Name" column in the "labels"
; block.  We'll use that to pick out name values from each row:

name-index: index? find labels "Name"

; Create an empty text string to collect the names:

names: copy {}

; Loop through the database, picking the item at the name-index location
; from each row

foreach row database [
    append names rejoin ["Name:  " (pick row name-index) newline]
]

; Display the results:

view [area (names)]

; Now let's view every transaction in which the name field includes
; "Netflix".  It's basically the same routine as above, only now with an
; added conditional evaluation that uses the "find" function.  And for
; this report, I want to see the amount paid.  I'll set a variable to
; refer to the index position of the "Net" column:

net-index: index? find labels "Net"
amounts: copy {}
foreach row database [
    if find/only (pick row name-index) "Netflix" [
        append amounts rejoin ["Amount:  " (pick row net-index) newline]
    ]
]
view [area (amounts)]

; Now, let's get a sum of every Netflix transaction between January and
; December, 2012.  The dates are stored in a column labeled "Date", with
; a format like: "12/26/2012" (month/day/year).  We'll need to convert
; that to REBOL's internal format (day-month-year)

date-index: index? find labels "Date"

; You've seen earlier how to collect sums.  Start by setting a sum
; variable to 0.  We'll add values to the sum to get a total:

sum: $0

; Now loop through the database and perform some conditional evaluations
; on every row:

foreach row database [
    if find/only (pick row name-index) "Netflix" [

        ; We'll use "parse" to separate the date text at the
        ; "/" character:

        date: parse (pick row date-index) "/"

        ; Pick the month using the values in system/locale/months

        month:  pick system/locale/months to-integer date/1

        ; Then rearrange and put back together the date pieces using
        ; "rejoin":

        reb-date: to-date rejoin [date/2 "-" month "-" date/3]

        ; If the date is within the chosen range, add the value to
        ; the sum:

        if ((reb-date >= 1-jan-2012) and (reb-date <= 31-dec-2012)) [
            sum: sum + (to-money pick row net-index)
        ]
    ]
]

; Show the answer:

alert form sum

Here's the entire program without comments:

REBOL [title: "Paypal Reports"]
do %requestors.r3
write %Download.csv read http://re-bol.com/Download.csv  ; sample data
filename: request-file
lines: read/lines filename
labels: copy parse/all lines/1 ","
foreach label labels [trim label]
database: copy []
foreach line (at lines 2) [
    parsed: parse/all line ","
    append/only database parsed
]
name-index: index? find labels "Name"
names: copy {}
foreach row database [
    append names rejoin ["Name:  " (pick row name-index) newline]
]
view [area (names)]
net-index: index? find labels "Net"
amounts: copy {}
foreach row database [
    if find/only (pick row name-index) "Netflix" [
        append amounts rejoin ["Amount:  " (pick row net-index) newline]
    ]
]
view [area (amounts)]
date-index: index? find labels "Date"
sum: $0
foreach row database [
    if find/only (pick row name-index) "Netflix" [
        date: parse (pick row date-index) "/"
        month:  pick system/locale/months to-integer date/1
        reb-date: to-date rejoin [date/2 "-" month "-" date/3]
        if ((reb-date >= 1-jan-2012) and (reb-date <= 31-dec-2012)) [
            sum: sum + (to-money pick row net-index)
        ]
    ]
]
alert form sum

If you want to use this example to create reports for any other CSV file, just copy and paste the first part of the script. It loads and parses a user selected CSV file, and creates a block of labels from the first line:

REBOL [title: "Reports"]
filename: request-file
lines: read/lines filename
labels: copy parse/all lines/1 ","
foreach label labels [trim label]
database: copy []
foreach line (at lines 2) [
    parsed: parse/all line ","
    append/only database parsed
]

You can use this line of code to view the list of column labels in the CSV file:

view [area (labels)]

Next, assign variable(s) to the column label(s), on which you want to perform computations:

name-index: index? find labels "Name"
date-index: index? find labels "Date"
net-index: index? find labels "Net"

Set any initial variables needed to perform computations (sum, max/min, etc.), or to hold blocks of collected values:

sum: $0
names: copy {}
amounts: copy {}

Use foreach loop(s) to perform conditional evaluations and desired computations on every row of data:

foreach row database [
    append names rejoin ["Name:  " (pick row name-index) newline]
]
foreach row database [
    if find/only (pick row name-index) "Netflix" [
        append amounts rejoin ["Amount:  " (pick row net-index) newline]
    ]
]
foreach row database [
    if find/only (pick row name-index) "Netflix" [
        date: parse (pick row date-index) "/"
        month:  pick system/locale/months to-integer date/1
        reb-date: to-date rejoin [date/2 "-" month "-" date/3]
        if ((reb-date >= 1-jan-2012) and (reb-date <= 31-dec-2012)) [
            sum: sum + (to-money pick row net-index)
        ]
    ]
]

Display all the computed or collected results:

view [area (names)]
view [area (amounts)]
alert form sum

Of course, the computations above involve quite a bit of evaluation and data processing, in order to demonstrate useful reusable code snippets (such as converting dates to REBOL values that can be manipulated intelligently). In most cases, simple sums, averages, searches, and other common computations will require far less code. You can shorten code even further by using numbers to refer to columns. Simple reports rarely require much more code than the first example in this tutorial:

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

7.6 Catch Game

A simple arcade game that demonstrates how to handle keys, move GUI widgets, detect collisions, react to events, keep score. and satisfy other common game making requirements:

REBOL [title: "Catch Game (key control)"]
do %requestors.r3
s: 0  p: 3  random/seed now/time
stylize [box: box [facets: [max-size: 50x20]]]
view/no-wait/options [
    t: text"ARROW KEYS" y: box red pad pad pad pad pad pad pad z: box blue
] [
    shortcut-keys: [
        left  [z/gob/offset/1: z/gob/offset/1 - 50 draw-face z]
        right [z/gob/offset/1: z/gob/offset/1 + 50 draw-face z]
    ] 
    min-hint: 600x440
]
forever [
    wait .02
    y/gob/offset/2: y/gob/offset/2 + p draw-face y show-now y
    if inside? y/gob/offset (z/gob/offset - 49x0) (z/gob/offset + 49x20)[
        y/gob/offset: random 550x-20 s: s + 1 set-face t form s  p: p + .3
    ]
    if y/gob/offset/2 > 420 [alert join "Score: " s unview unview break]
]

This version, shown in the introduction of this text, includes a downloaded image. Try to catch the falling fish. Be careful, it gets faster as you go!

REBOL [title: "Catch Game"]
do %requestors.r3
fish: load http://learnrebol.com/r3book/fish2.png
s: 0  p: 3  random/seed now/time
stylize [
    box: box [facets: [max-size: 50x10]]
    img: image [facets: [max-size: 50x20 min-size: 50x20]]
]
view/no-wait/options [
    t: text"ARROW KEYS" y: img 50x20 (fish) pad z: box blue
] [
    shortcut-keys: [
        left  [z/gob/offset/1: z/gob/offset/1 - 50 draw-face z]
        right [z/gob/offset/1: z/gob/offset/1 + 50 draw-face z]
    ] 
    min-hint: 600x440  bg-color: white
]
forever [
    wait .02
    y/gob/offset/2: y/gob/offset/2 + p draw-face y show-now y
    if inside? y/gob/offset (z/gob/offset - 49x0) (z/gob/offset + 49x10)[
        y/gob/offset: random 550x-20 s: s + 1 set-face t form s  p: p + .3
    ]
    if y/gob/offset/2 > 425 [alert join "Score: " s unview unview break]
]

Here's a version in which the user must type the falling character before it hits the ground:

REBOL [title: "Typing Game"]
do %requestors.r3
s: 0   p: 3   random/seed now/time
stylize [box: box [facets: [max-size: 50x10]]]
view/no-wait/options [
    t: button "Quit" on-key [
        either (to-string arg/key) = get-face y [alert "Yes"] [alert "No"]
        y/gob/offset: random 550x-20
        set-face y first random "abcdefghijklmnopqrstuvwxyz"
    ] on-action [quit]
    y: title "Y"  pad
] [min-hint: 600x440  bg-color: white]
forever [
    wait .02  focus t
    y/gob/offset/2: y/gob/offset/2 + p draw-face y show-now y
    if y/gob/offset/2 > 425 [
        alert "Oops"
        y/gob/offset: random 550x-20
        set-face y first random "abcdefghijklmnopqrstuvwxyz"
    ]
]

7.7 Text Table Examples

This example downloads a displays a block of data from the Internet:

REBOL [title: "Text-Table With Values Loaded From HTTP Server"]
do %r3-gui.r3
view [
    text-table ["Text" 100 "Dates" 200 "Numbers"] 
    (load read/string http://re-bol.com/texttable.txt)
]

This example demonstrates a bit about how to create random data, and shows how to add data to the grid display:

REBOL [title: "Text-Table - Adding Random Data"]
do %r3-gui.r3
my-data: [
    ["abcd"  1-jan-2013   44]
    ["bdac"  27-may-2013  4 ]
    ["dcab"  13-aug-2014  5 ]
]
random/seed now/time
view [
    t: text-table ["Text" 100 "Dates" 200 "Numbers"] my-data
    button "Add to Grid" on-action [
        d: get-face/field t 'data
        append/only d copy/deep reduce [
            random "asdf" ((random 31-dec-0001) + 734868) random 9
        ]
        set-face/field/no-show t d 'data
        set-face t/names/scr 100%
    ]
    text "Click headers to sort by column, CTRL+H deletes selected row,"
    text "SHIFT+ARROW moves rows up/down, Right-Click cells to edit."
]

This example demonstrates how to add data to a grid display, which has been added by the user, via field widgets:

REBOL [title: "Updating and Saving Data in Text-Table Widgets"]
do %r3-gui.r3
view [
    t: text-table ["Name" 200 "Password" 300]
    text "Name:"
    n: field
    text "Password"
    p: field
    hgroup [
        button "Add to List" on-action [
            d: get-face/field t 'data
            append/only d copy/deep reduce [get-face n  get-face p]
            set-face/field t d 'data
            set-face t/names/scr 100%
        ]
        button "Save" on-action [
            save %passw get-face/field t 'data
        ]
        button "Load" on-action [
            attempt [ 
                attempt [set-face/field/no-show t load %passw 'data]
                set-face t/names/scr 100%
            ]
        ]
    ]
]

7.8 Contacts App With Text Table

Here is a contacts program demonstrating the text-table widget in practical use:

REBOL [title: "Contacts - Grid View"]
do %requestors.r3
add-contact: does [
    d: get-face/field t 'data
    append/only d copy/deep reduce [
        get-face f1  get-face f2  get-face f3
    ]
    set-face/field/no-show t d 'data
    set-face t/names/scr 100%
]
view [
    t: text-table ["Name" 200 "Phone" 100 "Address" 300] []
    hgroup [text 200 "Name:" text 100 "Phone:"  text 300 "Address:"]
    hgroup [
        f1: field 200 f2: field 100 f3: field 300 on-action [add-contact]
    ]
    hgroup [
        button "Add New Contact" on-action [add-contact]
        button "Save" on-action [
            save-file: request-file
            if save-file <> none [
                save save-file get-face/field t 'data
                alert "Saved"
            ]
        ]
        button "Load" on-action [
            attempt [set-face/field/no-show t load request-file 'data]
            set-face t/names/scr 100%
        ]
    ]
]

7.9 FTP Examples

This example demonstrates how to use FTP to upload/download files to/from a server/web site:

REBOL [title: "FTP Example"]
do %prot-ftp.r3   ; https://github.com/gchiu/Rebol3/tree/master/protocols
cmd: open decode-url "ftp://name%40site.com:pass@ftp.server.com" read cmd
; change folder and send text file:
write cmd [cwd "/public_html/folder/"]  read cmd
write cmd [pasv]  read cmd
write cmd [stor "temp.txt" %temp.txt]  read cmd
; send binary file:
write cmd [type "I"]
write cmd [pasv]  read cmd
write cmd [stor "r3-view.exe" %r3-view.exe]  read cmd
; get a text file (delete first, text will be appended to existing file):
delete %asdf.txt
write cmd [type "A"]
write cmd [pasv]  read cmd
write cmd [retr "temp.txt" %asdf.txt] read cmd
close cmd

7.10 Web Page Editor

An FTP example that implements an editor for web pages on a web site:

REBOL [title: "Web Page Editor"]
do %r3-gui.r3
do %prot-ftp.r3
view [
    vpanel 2 [
        text "Host:"
        h: field "site.com"
        text "Username"
        u: field "username%40site.com"
        text "Password:"
        p: field "password"
        text "Folder:"
        f: field "/public_html/folder/"
        text "File:"
        l: field "temp.txt"
        text "Web URL:"
        w: field "http://site.com/folder/temp.txt"
    ]
    a: area 600x350 
    button "Load" on-action [set-face a to-string read to-url get-face w]
    button "Save" on-action [
        url: rejoin ["ftp://" get-face u ":" get-face p "@" get-face h]
        cmd: open decode-url url read cmd
        write cmd compose [cwd (get-face f)]  read cmd
        write cmd [type "A"]
        write cmd [pasv]  read cmd
        write %ftemp.txt get-face a
        write cmd compose [stor (get-face l) %ftemp.txt] read cmd
        write cmd [quit] read cmd  write cmd [bye] read cmd
    ]
    button "Browse" on-action [browse to-url get-face w]
]

7.11 Currency Calculator

The calculator shown earlier is as basic as could be, but adding advanced math functions and other useful capabilities is easy. Imagine adding industry specific operations such as amortization calculations. The following example downloads and parses the current (live) US Dollar exchange rates from http://x-rates.com and allows the user to select from a list of currencies to convert calculated values to, then performs and displays the conversion from USD to the selected currency. This example will likely be a bit too advanced to understand completely at this point in the tutorial, but it's good to run it in the REBOL interpreter and browse through the code to recognize functions such as "read", "find", "parse", "attempt", etc., to which you've already been introduced. See if you can get a general concept of what the code is doing:

REBOL [title: "Currency Rate Conversion Calculator"]
do %requestors.r3
stylize [
    btn: button [
        facets: [init-size: 50x50]
        actors: [on-action:[set-face f join get-face f get-face face]]
    ]
]
view [
    hgroup [
        f: field return
        btn "1"  btn "2"  btn "3"  btn " + "  return
        btn "4"  btn "5"  btn "6"  btn " - "  return
        btn "7"  btn "8"  btn "9"  btn " * "  return
        btn "0"  btn "."  btn " / "   btn "=" on-action [
            attempt [set-face f form do get-face f]
        ]
        btn "Convert" on-action [
            x: copy []
            html: to-string read 
                http://www.x-rates.com/table/?from=USD&amount=1.00
            html: find html 
                "src='/themes/bootstrap/images/xrates_sm_tm.png'"
            parse html [
                any [
                    thru {from=USD} copy link to {</a>} (append x link)
                ] to end 
            ]
            rates: copy []
            foreach rate x [
                parse rate [thru {to=} copy c to {'>}]
                parse rate [thru {'>} copy v to end]
                if not error? try [
                    to-integer v
                ] [append rates reduce [c v]]
            ]  
            currency: request-list "Select Currency:" extract rates 2
            rate: to-decimal select rates currency
            attempt [
                alert rejoin [
                    currency ": " (rate * to-decimal get-face f)
                ]
            ]
        ]
    ]
]

7.12 Reviewing and Using the Code You've Seen To Model New Applications

You're already well on your way to understanding how to build useful business applications. At this point, it's wise to go back and review every section in the tutorial. The perspective you have now will help to clarify the value of concepts that were skimmed during a first read. Focus on experimenting with individual lines of code, memorizing functions, and understanding the details of syntax that may have been confusing the first time around. Try repurposing, altering, and editing the complete programs you've seen to far, to be useful in data management activities that apply to your own interests. Here are some ideas for experimentation:

  1. Add columns to data blocks in the existing apps (i.e, perhaps "email" and "mobile", "home", "work" fields to the contacts examples, or "name" a "title" fields to messages in the reminder app).
  2. Add new computations to the reporting examples (perhaps average, max, and min functions to columns in the Paypal reports).
  3. Apply the CSV, sorting, computing, and reporting code patterns you've seen to columns of data exported from other desktop applications and web sites.
  4. Apply new and more complex conditional operations to computations (perhaps apply tax only to items in a Paypal file that were shipped from a certain state, or apply a fee to prices of items that are made by a certain manufacturer in the "Parts" database app).
  5. Create a few small GUI apps that save CSV files which can be imported into a spreadsheet (perhaps add a CSV export feature to the "Contacts" or "Parts" examples, and especially try exporting computed reports as CSV files).
  6. Add list features to the email program (perhaps allow email addresses to be chosen from a contact list, or send emails to a group of users).
  7. Add useful features to the file editor and web page editor programs (maybe try storing a history of edited files, or FTP login credentials).
  8. Combine the Inventory and Cash Register programs, so that users can select inventory items from a drop down list in the cash register app.
  9. Add unique computation routines to the calculator app.
  10. Experiment with the "parse" and "find" functions (perhaps try adding a search feature to the schedule and email programs, or send pre-formed responses to email messages that contain a given string of text in the subject).
  11. Experiment with printing and displaying formatted data in the console and in GUI fields (perhaps build an email program that parses the header of each email message, and displays each part on separately printed lines in the console or in separate area widgets in a GUI).
  12. Add image and file sharing features to the Group Reminders app.
  13. Add images to stored records in the Parts database app.
  14. Build GUI versions of the FTP chat and Paypal Reports applications. Use text-list widgets to display messages in chat and to select column names and computation options in reports.
  15. Experiment with GUI layouts. Position text headers, fields, buttons, areas, text lists, etc. in ways that allow data to be arranged coherently and pleasantly on screen, and which invite intuitive user interactions and natural work flow patterns.
  16. Experiment with how colors, fonts, widget sizes, images, and other cosmetic features can be used to change the appearance of applications.

You'll see much more about how to approach tasks like this, as the tutorial progresses, but exploring creatively and learning to use the tools you've seen so far, will go a long way towards building fundamental coding ability. If you learn nothing more than the code presented up to this point, you will already be able to create a wide variety of practical business applications that would be impossible to implement quite so personally using generic off-the-shelf software products. Continue learning, and that potential will increase dramatically.

8. User Defined Functions and Imported Code Modules

8.1 "Do", "Does", and "Func"

REBOL's built-in functions satisfy many fundamental needs. To achieve more complex or specific computations, you can create your own function definitions.

Data and function words contained in blocks can be evaluated (their actions performed and their data values assigned) using the "do" word. Because of this, any block of code can essentially be treated as a function. That's a powerful key element of the REBOL language design:

do %requestors.r3
some-actions: [
    alert "Here is one action." 
    print "Here's a second action."
    write %/c/anotheraction.txt "Here's a third action."
    alert "Writing to the hard drive was the third action."
]

do some-actions

New function words can also be defined using the "does" and "func" words. "Does" is included directly after a word label definition, and forces a block to be evaluated every time the word is encountered:

do %requestors.r3
more-actions: does [
    alert "Counting some more actions:  4" 
    alert "And another:  5"
    alert "And finally:  6"
]

; Now, to use that function, just type the word label:

more-actions

8.1.1 "Func"

The "func" word creates an executable block in the same way as "does", but additionally allows you to pass your own specified parameters to the newly defined function word. The first block in a func definition contains the name(s) of the variable(s) to be passed. The second block contains the actions to be taken with those variables. Here's the "func" syntax:

func [names of variables to be passed] [
    actions to be taken with those variables
]

This function definition:

sqr-add-var: func [num1 num2] [print square-root (num1 + num2)]

Can be used as follows. Notice that no brackets, braces, or parentheses are required to contain the data arguments. Data parameters simply follow the function word, on the same line of code:

sqr-add-var 12 4      ; prints "4",  the square root of 12 + 4  (16)
sqr-add-var 96 48     ; prints "12", the square root of 96 + 48 (144)
halt

Here's a simple function to display images:

do %r3-gui.r3
display: func [filename] [view [image (load to-file filename)]]

display (request-file)

8.2 Return Values

By default, the last value evaluated by a function is returned when the function is complete:

concatenate: func [string1 string2] [join string1 string2]

string3: concatenate "Hello " "there."
print string3
halt

You can also use the word "return" to end the function with a return value. This can be helpful when breaking out of loops (the "for" function performs a count operation - it will be covered in depth in a later section of this tutorial):

stop-at: func [num] [
    for i 1 99 1 [
        if (i = num) [return i]
        print i
    ]
    return num
]
print stop-at 38
halt

8.3 Scope

By default, values used inside functions are treated as global, which means that if any variables are changed inside a function, they will be changed throughout the rest of your program:

x: 10

change-x-globally: func [y z] [x: y + z]

change-x-globally 10 20
print x
halt

You can change this default behavior, and specify that any value be treated as local to the function (not changed throughout the rest of your program), by using the "/local" refinement:

x: 10

change-x-locally: func [y z /local x] [x: y + z]

change-x-locally 10 20     ; inside the function, x is now 30
print x                    ; outside the function, x is still 10
halt

You can specify refinements to the way a function operates, simply by preceding optional operation arguments with a forward slash ("/"):

compute: func [x y /multiply /divide /subtract] [
    if multiply [return x * y]
    if divide   [return x / y]
    if subtract [return x - y]
    return x + y
]

probe compute/multiply 10 20
probe compute/divide 10 20
probe compute/subtract 10 20
probe compute 10 20
halt

8.4 Function Documentation

The "help" function provides usage information for any function, including user defined functions. You can type this directly into the R3 interpreter:

help for
help compute

You can include documentation for any user defined function by including a text string as the first item in it's argument list. This text is included in the description displayed by the help function:

doc-demo: func ["This function demonstrates doc strings"] [help doc-demo]
doc-demo

Acceptable data types for any parameter can be listed in a block, and doc strings can also be included immediately after any parameter:

concatenate-string-or-num: func [
    "This function will only concatenate strings or integers."
    val1 [string! integer!] "First string or integer"
    val2 [string! integer!] "Second string or integer"
] [
    join val1 val2
]

help concatenate-string-or-num
concatenate-string-or-num "Hello " "there."  ; this works correctly
concatenate-string-or-num 10 20              ; this works correctly
concatenate-string-or-num 10.1 20.3          ; this creates an error
halt

8.5 Doing Imported Code

You can "do" a module of code contained in any text file, as long as it contains the minimum header "REBOL [ ]" (this includes HTML files and any other files that can be read via REBOL's built-in protocols). For example, if you save the previous functions in a text file called "myfunctions.r":

REBOL []  ; THIS HEADER TEXT MUST BE INCLUDED AT THE TOP OF ANY REBOL FILE
do %requestors.r3
sqr-add-var: func [num1 num2] [print square-root (num1 + num2)]
display: func [filename] [view [image (load filename)]]

You can import and use them in your current code, as follows:

do %myfunctions.r

; now you can use those functions just as you would any other
; native function:

sqr-add-var 12 2
display %bay.jpg

Imported files can contain data definitions and any other executable code, including that which is contained in additional nested source files imported with the "do" function. Any code or data contained in a source file is evaluated when the file is "do"ne.

8.6 Separating Form and Function in GUIs - The Check Writer App

One common use for functions is to help keep GUI layout code clean and easy to read. Separating widget layout code from action block code helps to clarify what each widget does, and simplifies the readability of code which lays out screen design.

For example, the following buttons all perform several slightly different actions. The first button counts from 1 to 100 and then alerts the user with a "done" message, the second button counts from 101 to 200 and alerts the user when done, the third button counts from 201 to 300 and alerts the user when done (the "for" function performs a count operation):

do %requestors.r3
view [
    button "Count 1" on-action [
        for i 1 100 1 [print i]
        alert "Done"
    ]
    button "Count 2" on-action [
        for i 101 200 1 [print i]
        alert "Done"
    ]
    button "Count 3" on-action [
        for i 201 300 1 [print i]
        alert "Done"
    ]
]

The action blocks of each button above can all be reduced to a common function that counts from a start number to an end number, and then alerts the user. That function can be assigned a label, and then only that label needs to be used in the GUI widget action blocks:

do %requestors.r3
count: func [start end] [
    for i start end 1 [print i]
    alert "Done"
]
view [
    button "Count 1" on-action [count 1 100]
    button "Count 2" on-action [count 101 200]
    button "Count 3" on-action [count 201 300]
]

In large applications where the actions can become complex, separating function code from layout code improves clarity. You'll see this outline often:

action1: func [args] [
    actions
    actions
    actions
]
action2: func [args] [
    other actions
    other actions
    other actions
]
action3: func [args] [
    more actions
    more actions
    more actions
]
view [
    widget on-action [action1]
    widget on-action [action2]
    widget on-action [action3]
]

Here's a simple check writing example that takes amounts and names entered into an area widget and creates blocks of text to be written to a check. One function is created to "verbalize" the entered amounts for printing on the check (it converts number values to their spoken English equivalent, i.e., 23482194 = "Twenty Three million, Four Hundred Eighty Two thousand, One Hundred Ninety Four"). Another function is created to loop through each bit of raw check data and to create a block containing the name, the numerical amount, the verbalized amount, some memo text, and the date. The GUI code consists of only 4 lines:

REBOL [title: "Check Writer"] 
do %requestors.r3
verbalize: func [a-number] [
    if error? try [a-number: to-decimal a-number] [
        return "** Error **  Input must be a decimal value"
    ]
    if a-number = 0 [return "Zero"]
    the-original-number: round/down a-number
    pennies: a-number - the-original-number
    the-number: the-original-number
    if a-number < 1 [
        return join to-integer ((round/to pennies .01) * 100) "/100"
    ] 
    small-numbers: [
        "One" "Two" "Three" "Four" "Five" "Six" "Seven" "Eight"
        "Nine" "Ten" "Eleven" "Twelve" "Thirteen" "Fourteen" "Fifteen"
        "Sixteen" "Seventeen" "Eighteen" "Nineteen"
    ]
    tens-block: [
        { } "Twenty" "Thirty" "Forty" "Fifty" "Sixty" "Seventy" "Eighty"
        "Ninety"
    ]
    big-numbers-block: ["Thousand" "Million" "Billion"]    
    digit-groups: copy []
    for i 0 4 1 [
        append digit-groups (round/floor (mod the-number 1000))
        the-number: the-number / 1000
    ]    
    spoken: copy ""
    for i 5 1 -1 [
        flag: false
        hundreds: (pick digit-groups i) / 100
        tens-units: mod (pick digit-groups i) 100
        if hundreds <> 0 [
            if none <> hundreds-portion: (pick small-numbers hundreds) [
                append spoken join hundreds-portion " Hundred "
            ]
            flag: true
        ]
        tens: tens-units / 10
        units: mod tens-units 10
        if tens >= 2 [
            append spoken (pick tens-block tens)
            if units <> 0 [
                if none <> last-portion: (pick small-numbers units) [
                    append spoken rejoin [" " last-portion " "]
                ]
                flag: true
            ]
        ]
        if tens-units <> 0 [
            if none <> tens-portion: (pick small-numbers tens-units) [
                append spoken join tens-portion " "
            ]
            flag: true
        ]
        if flag = true [
            commas: copy {}    
            case [
                ((i = 4) and (the-original-number > 999999999)) [
                    commas: {billion, }
                ]
                ((i = 3) and (the-original-number > 999999)) [
                    commas: {million, }
                ]
                ((i = 2) and (the-original-number > 999)) [
                    commas: {thousand, }
                ]
            ]
            append spoken commas
        ]
    ]
    append spoken rejoin [
        "and " to-integer ((round/to pennies .01) * 100) "/100"
     ]
    return spoken
]
write-checks: does [
    checks: copy []
    data: to-block get-face a1
    foreach [name amount] data [
        check: copy []
        append/only checks reduce [
            rejoin ["Pay to the order of: "  name  "   "]
            rejoin ["$"  amount  newline]
            rejoin ["Amount: " verbalize amount newline]
            rejoin ["Sales for November    "  now/date  newline newline]
        ]
    ]
    set-face a2 form checks
]
view [
    text "Amounts and Names:"
    a1: area {"John Smith" 582 "Jen Huck" 95 "Sue Wells" 71 "Joe Lask" 38}
    button "Create Check Data" on-action [write-checks]
    a2: area
]

One of the clear benefits of using functions is that you don't have to remember, or even understand, how they work. Instead, you only really need to know how to use them. In the example above, you don't have to understand exactly how all computations in the "verbalize" function work. You just need to know that if you type "verbalize (number)", it spits out text representing the specified dollar amount. You can use that function in any other program where it's useful, blissfully unaware of how the verbalizing magic happens. Just paste the verbalize code above, and use it like any other function built into the language. This is true of any function you create, or any function that someone else creates. Working with functions in this way helps improve code re-usability, and the power of the language, dramatically. It also encourages clear code structures that others can read more easily.

Note: The verbalize algorithm was partially derived from the article at http://www.blackwasp.co.uk/NumberToWords.aspx (C# code):

9. 2D Drawing, Graphics, and Animation

9.1 Basic Shapes

With REBOL's GUI dialect you can easily build graphic user interfaces that include buttons, fields, text lists, images and other GUI widgets, but it's not meant to handle general purpose graphics or animation. For that purpose, REBOL includes a built-in "draw" dialect. Various drawing functions allow you to make lines, boxes, circles, arrows, and virtually any other shape. Fill patterns, color gradients, and effects of all sorts can be easily applied to drawings.

Implementing draw functions typically involves creating a 'view' GUI, with an image widget that's used as the viewing screen. Draw functions are then passed a block of instructions to perform the drawing of various shapes and other graphic elements in the box. Each draw function takes an appropriate set of arguments for the type of shape created (coordinate values, size value, etc.). Here's a basic example of the draw format:

REBOL [title: "Basic Draw Example"]
do %requestors.r3
bck: make image! 400x400
view/no-wait [image bck]
draw bck to-draw [circle 200x200 20] copy []
do-events

Any number of shape elements (functions) can be included in the draw block. Here is the exact same example as above with some more shapes:

REBOL [title: "Drawing Some More Shapes"]
do %requestors.r3
bck: make image! 400x400
view/no-wait [image bck] 
draw bck to-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
] copy []
do-events

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

REBOL [title: "Drawing Pen Colors, Widths, and Fill Colors"]
do %requestors.r3
bck: make image! 400x400
view/no-wait [image bck] 
draw bck to-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
] copy []
do-events

Here's a more practical example:

REBOL [title: "3D Box"]
do %requestors.r3
bck: make image! 400x220
view/no-wait [image bck] 
draw bck to-draw [
    fill-pen 200.100.90
    polygon 20x40 200x20 380x40 200x80
    fill-pen 200.130.110
    polygon 20x40 200x80 200x200 20x100
    fill-pen 100.80.50
    polygon 200x80 380x40 380x100 200x200
] copy []
do-events

A useful feature of draw is the ability to easily scale and distort images simply by indicating 4 coordinate points. The image will be altered to fit into the space marked by those four points: REBOL [title: "Image Distort"]

do %requestors.r3
bck: make image! 400x400
view/no-wait [image bck]
draw bck to-draw compose [
    image (load %bay.jpg) 10x10 350x200 250x300 50x300
] copy []
do-events

9.2 Animation

Animations can be created with draw by changing the coordinates of image elements. Here's a basic example that moves a circle to a new position when the button is pressed. Notice the shortcut-keys in the view/options block. Also note the /no-wait option, which allows the screen to be built, without responding to events. The update function is run to set the initial position of the circle, and then the activity of processing GUI interactions (the "event loop") is started with the do-events function:

REBOL [title: "Draw Example With Keyboard Controls"]
do %r3-gui.r3
bck: make image! 400x400
piece: [circle offset 20]
offset: 200x200
update: does [
    draw bck to-draw piece copy [] 
    draw-face scrn
]
move: func [distance] [
    offset: offset + distance
    bck/rgb: random white  ; clear scrn
    update
]
view/options/no-wait [scrn: image bck] [
    shortcut-keys: [
        up    [move 0x-10]
        down  [move 0x10]
        left  [move -10x0]
        right [move 10x0]
    ]
]
update
do-events

Variables can be assigned to positions, sizes, and/or other characteristics of draw elements, and loops can be used to create smooth animations by adjusting those elements incrementally. Animation coordinates (and other draw properties) could also be stored in blocks. Other data sources such as mouse/touch coordinates can also serve to control movement.

For more information about built-in shapes, functions, and capabilities of draw, see http://www.rebol.com/docs/draw-ref.html, http://www.rebol.com/docs/draw.html, http://translate.google.com/translate?hl=en&sl=fr&u=http://www.rebolfrance.info/org/articles/login11/login11.htm (translated by Google), http://www.rebolforces.com/zine/rzine-1-05.html, http://www.rebolforces.com/zine/rzine-1-06.html (updated code for these two tutorials is available at http://mail.rebol.net/maillist/msgs/39100.html). A nice, short tutorial demonstrating how to build multi-player, networked games with draw graphics is available at RebolFrance (translated by Google). Also be sure to see http://www.nwlink.com/~ecotope1/reb/easy-draw.r (a clickable rebsite version is available in the REBOL Desktop -> Docs -> Easy Draw).

10. Creating Web Applications using REBOL CGI

The Internet provides a fantastic scope of added potential for developers to offer online interfaces to data. Network connectivity allows multiple users to share data easily, anywhere in the world. The "web page" interface allows users to input data and view computed output using virtually any Internet connected device (computers, phones, tablets, etc.). The "web page" paradigm and generalized workflow is already familiar to an overwhelming majority of technically savvy users.

In CGI web applications, HTML forms on a web site act as the user interface (GUI) for scripts that run on a web server. Users typically type text into fields, select choices from drop down lists, click check boxes, and otherwise enter data into form "widgets" on a web page, and then click a Submit button when done. The submitted data is transferred to, and processed by, a script that you've stored at a specified URL (Internet address) on your web server. Data output from the script is then sent back to the user's browser and displayed on screen as a dynamically created web page. CGI programs of that sort, running on web sites, are among the most common types of computer application in contemporary use. PHP, Python, Java, PERL, and ASP are popular languages used to accomplish similar Internet programming tasks, but if you know REBOL, you don't need to learn them. REBOL's CGI interface makes Internet programming very easy.

In order to create REBOL CGI programs, you need an available web server. A web server is a computer attached to the Internet, which constantly runs a program that stores and sends out web page text and data, when requested from an Internet browser running on another computer. The most popular web serving application is Apache. Most small web sites are typically run on shared web server hosting accounts, rented from a data center for a few dollars per month (see http://www.lunarpages.com - they're REBOL friendly). While setting up a web server account, you can register an available domain name (i.e, www.yourwebsitename.com). When web site visitors type your ".com" domain address into their browser, they see files that you've created and saved into a publicly accessible file folder on your web server computer. Web hosting companies typically provide a collection of web based software tools that allow you to upload, view, edit, and manage files, folders, email, and other resources on your server. The most popular of these tools is cPanel. No matter what language you use to program web apps, registering a domain, purchasing hosted space, and learning to manage files and resources with tools such as cPanel, is a required starting point. Lunarpages and HostGator are two recommended hosts for REBOL CGI scripting. Full powered web site hosting, with a preconfigured cPanel interface, unlimited space and bandwidth, and all the tools needed to create professional web sites and apps, are available for less than $10 per month.

In order for REBOL CGI scripts to run, the REBOL interpreter must be installed on your web server. To do that, download from rebol.com the correct version of the REBOL interpreter for the operating system on which your web server runs (most often some type of Linux). Upload it to your user path on your web server, and set the permissions to allow it to be executed (typically "755"). Ask your web site host if you don't understand what that means. http://rebol.com/docs/cgi1.html#section-2.2 has some basic information about how to install REBOL on your server. If you don't have an online web server account, you can download a full featured free Apache web server package that will run on your local Windows PC, from http://www.uniformserver.com.

10.1 An HTML Crash Course

In order to create any sort of CGI application, 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 every possible way. 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 by the user 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 called "yourpage.html", upload it to your web server, and surf to http://yourwebserver.com/yourpage.html , you'll see in your browser a page entitled "Page title", with the text "A bunch of text and HTML formatting goes here...". All web pages work that way - this tutorial is in fact just an HTML document stored on the author's web server account. Click View -> Source in your browser, and you'll see the HTML tags that were used to format this document.

10.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 layout. Inside the form tags are a text input field tag, and a submit button tag:

<HTML>
    <HEAD><TITLE>Data Entry Form</TITLE></HEAD>
    <BODY>
        <FORM ACTION="http://yourwebserver.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://yourwebserver.com/your_rebol_script.cgi"' in the above form 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 data is sent to the following program, which in turn does some processing, and prints output directly 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/rebol -cs
REBOL []
print {content-type: text/html^/}  
; the line above is the same as:  print "content-type: text/html^/"
submitted: parse (to string! read system/ports/input) "&="

print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}
print rejoin [{Hello } second submitted {!}]
print {</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/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 "parse" 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 "print"ed 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.

10.2 A Standard CGI Template to Memorize

Most short 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/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: parse (to string! read system/ports/input) "&="

; 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 ACTION="http://yourwebserver.com/this_rebol_script.cgi">
        <INPUT TYPE="TEXT" NAME="username" SIZE="25">
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
        </BODY></HTML>
    }
] [ 
    print rejoin [{Hello } second submitted {!}]
    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. Memorize it.

11. Example CGI Applications

11.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:

#!./rebol3 -cs
REBOL [title: "Generic CGI Application, With HTML Form"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}
submitted: parse (to string! read system/ports/input) "&="
foreach item submitted [replace/all item "+" " "]
foreach item submitted [dehex item]
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 } dehex submitted/2 {!}]
    print {</BODY></HTML>}
]

11.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:

#!./rebol3 -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>}

11.3 CGI Text Chat

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

#!./rebol3 -cs
REBOL [title: "Group Chat"]
print {content-type: text/html^/}
url: %./chat.txt
write/append url ""
submitted: parse (to string! read system/ports/input) "&="
foreach item submitted [replace/all item "+" " "]
foreach item submitted [dehex item]
if submitted/2 = "erase" [write url ""]
if submitted/2 <> none [
    write/append url rejoin [
        now " (" submitted/2 "):  " submitted/4 "^/^/"
    ]
]
notes: dehex copy read/string 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>}
]

11.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: parse (to string! read system/ports/input) "&="
if not empty? submitted [
    print rejoin [{NAME SELECTED: } submitted/2 {<BR><BR>}]
    selected: rejoin [
        {TIME/DATE SELECTED: }
        submitted/4 { } submitted/6 {, } dehex 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

11.5 Bulletin Board

Here's a simple web site bulletin board program. A demo of this script is available at http://guitarz.org/rebol3/bb.cgi:

#!./rebol3 -cs
REBOL [title: "Bulletin Board"]
print {content-type: text/html^/}
print read/string %template_header.html
; print {<HTML><HEAD><TITLE>Bulletin Board</TITLE></HEAD><BODY>}
bbs: load %bb.db
print {
    <center><table border=1 cellpadding=10 width=600><tr><td>
    <center><strong><font size=4>
    Please REFRESH this page to see new messages.
    </font></strong></center>
}
print-all: does [
    print {<br><hr><font size=5> Posted Messages: </font> <br><hr>}
    foreach bb (reverse bbs) [
        print rejoin [
            {<BR>} bb/2 { } bb/1 {<BR><BR>} bb/3 {<BR><BR><HR>}
        ]
    ]
]
submitted: parse (to string! read system/ports/input) "&="
foreach item submitted [replace/all item "+" " "]
foreach item submitted [dehex item]
if submitted/2 <> none [
    entry: copy []
    append entry submitted/2
    append entry to-string (now + 3:00)
    append entry submitted/4
    append/only bbs entry
    save %bb.db bbs
    print {<BR><strong>Your message has been added: </strong><BR>}
]
print-all
print {
    <font size=5> Post A New Public Message:</font><hr>
    <FORM METHOD="POST">
    <br> Your Name:  <br>
    <input type=text size="50" name="student"><BR><BR>
    Your Message: <br>
    <textarea name=message rows=5 cols=50></textarea><BR><BR>
    <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Post Message">
    </FORM>
    </td></tr></table></center>
}
; print {</BODY></HTML>}
print read/string %template_footer.html

The template_header.html file used in the above example can include the standard required HTML outline, along with any formatting tags and static content that you'd like, in order to present a nicely designed page. A basic layout may include something similar to the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>Page Title</TITLE>    
<META http-equiv=Content-Type content="text/html; 
    charset=windows-1252">
</HEAD>    
<BODY bgColor=#000000>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 height="100%" width="85%">
<TR>
<TD background="" bgColor=white vAlign=top>

The footer closes any tables or tags opened in the header, and may include any static content that appears after the CGI script (copyright info, logos, etc.):

</TD>
</TR>
</TABLE>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 width="95%">
<TR>
<TD background="" cellPadding=2 bgColor=#000000 height=5>
<P align=center><FONT color=white size=1>Copyright  2009
    Yoursite.com.  All rights reserved.</FONT>
</P>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>

Here's an example data file for the program above:

[
    [
        "Nick Antonaccio"
        "8-Nov-2006/4:55:59-8:00"
        {
            WELCOME TO OUR PUBLIC BULLETIN BOARD.
             Please keep the posts clean cut and on topic.
            Thanks and have fun!
        }
    ]
]

11.6 Event Attendance

Here's an example that allows users to check attendance at various weekly events, and add/remove their names from each of the events. It stores all the user information in a flat file (simple text file) named "event.db". A demo of this script is available at http://guitarz.org/rebol3/event.cgi:

#!./rebol3 -cs
REBOL [title: "event.cgi"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Event Sign-Up</TITLE></HEAD><BODY>}
events: load %event.db
a-line: [] loop 65 [append a-line "-"]
a-line: trim to-string a-line
print {
    <hr> <font size=5>Sign up for an event:</font><hr><BR>
    <FORM METHOD="POST">
    Student Name:
    <input type=text size="50" name="student"><BR><BR>
    ADD yourself to this event:
    <select NAME="add"><option><option>all
}
foreach event events [prin rejoin [{<option>} event/1]]
print {
    </option> </select> <BR> <BR>
    REMOVE yourself from this event:
    <select NAME="remove"><option><option>all
}
foreach event events [prin rejoin [{<option>} event/1]]
print {
    </option> </select> <BR> <BR>
    <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
    </FORM>
}
print-all: does [
    print {
        <br><hr><font size=5>
        Currently scheduled events, and current attendance:
        </font><br><hr><br>
    }
    foreach event events [
        print rejoin [a-line {<BR>} event/1 {<BR>} a-line {<BR>}]
        for person 2 (length? event) 1 [
            print event/:person
            print {<BR>}
        ]
        print {<BR>}
    ]
    print {</BODY></HTML>}
]
submitted: parse (to string! read system/ports/input) "&="
foreach item submitted [replace/all item "+" " "]
foreach item submitted [dehex item]
if submitted/2 <> none [
    if ((submitted/4 = "") and (submitted/6 = "")) [
        print {
            <strong> Please try again. You must choose an event.</strong>
        }
        print-all
        quit
    ]
    if ((submitted/4 <> "") and (submitted/6 <> "")) [
        print {
            <strong> Please try again. Choose add OR remove.</strong>
        }
        print-all
        quit
    ]
    if submitted/4 = "all" [
        foreach event events [append event dehex submitted/2]
        save %event.db events
        print {
            <strong> Your name has been added to every event:</strong>
        }
        print-all
        quit
    ]
    if submitted/6 = "all" [
        foreach event events [
            if find event dehex submitted/2 [
                remove-each name event [(dehex submitted/2) = name]
                save %event.db events
            ]
        ]
        print {
            <strong> Your name has been removed from all events:</strong>
        }
        print-all
        quit
    ]
    foreach event events [
        if (find event dehex submitted/4) [
            append event dehex submitted/2
            save %event.db events
            print {
                <strong> Your name has been added to the selected event:
                </strong>
            }
            print-all
            quit    
        ]
    ]
    found: false
    foreach event events [
        if (find event dehex submitted/6) [
            if (find event dehex submitted/2) [
                remove-each name event [(dehex submitted/2) = name]
                save %event.db events
                print {
                    <strong>
                    Your name has been removed from the selected event:
                    </strong>
                }
                print-all
                quit
                found: true
            ]
        ]
    ]
    if found <> true [
        print {
            <strong> That name is not found in the specified event!
            </strong>
        }
        print-all
        quit
    ]
]
print-all

Here is a sample of the "event.db" data file used in the above example:

["Sunday September 16, 4:00pm"
    "Nick Antonaccio" "Steve Jones" "Mark Humphreys"]
["Friday September 21, 3:30pm"
    "Nick Antonaccio" "George Thompson" "Phil Belans"]
["Saturday September 29, 10:00am"
    "Nick Antonaccio" "Tom Rider" "Paul Grist"]

11.7 GET vs POST Example

The default format for REBOL CGI data is "GET". Data submitted by the GET method in an HTML form is displayed in the URL bar of the user's browser. If you don't want users to see that data displayed, or if the amount of submitted data is larger then can be contained in the URL bar of a browser, the "POST" method should be used. To work with the POST method, the action in your HTML form should be:

<FORM METHOD="post" ACTION="./your_script.cgi">

Here's an example script that demonstrates how do differentiate between GET and POST data. A demo of this script is available at http://guitarz.org/rebol3/getpost.cgi:

#!./rebol3 -cs
REBOL [title: "CGI GET vs POST"]
handle-get: funct [] [
    prin [
        "Content-type: text/html" crlf
        crlf
        <!doctype html>
        <title> "Rebol 3 CGI Sample: Form" </title>
        <form method="POST">
            "Your name:"
            <input type="text" name="field">
            <input type="submit">
        </form>
    ]   
]
handle-post: funct [] [
    data: to string! read system/ports/input
    fields: parse data "&="
    value: dehex select fields "field"
    prin [
        "Content-type: text/html" crlf
        crlf
        <!doctype html>
        <title> "Rebol 3 CGI Sample: Response" </title>
        "Hello," (join value "!")
    ]
]
switch get-env "REQUEST_METHOD" [
    "GET" [handle-get]
    "POST" [handle-post]
]

12. Network Ports

12.1 Transferring Data and Files over a TCP Connection

One important use of R3 ports is for transferring data via network connections (TCP and UDP "sockets"). When writing a network application, you must choose a specific port number through which data is to be transferred. Potential ports range from 0 to 65535, but many of those numbers are reserved for specific types of applications (email programs use port 110, web servers use port 80 by default, etc.). To avoid conflicting with other established network applications, it's best to choose a port number between 49152 and 65535 for small scripts. A list of reserved port numbers is available here.

Network applications are typically made up of two or more separate programs, each running on different computers. Any computer connected to a network or to the Internet is assigned a specific "IP address", notated in the format xxx.xxx.xxx.xxx. The numbers are different for every machine on a network, but most home and small business machines are normally in the IP range "192.168.xxx.xxx". You can obtain the IP address of your local computer with the following REBOL code:

? system/standard/net-info/local-ip

"Server" programs open a chosen network port and wait for one or more "client" programs to open the same port and then insert data into it. The port opened by the server program is referred to in a client program by combining the IP address of the computer on which the server runs, along with the chosen port number, each separated by a colon symbol (i.e., 192.168.1.2:55555).

The following simple set of scripts demonstrates how to use REBOL ports to transfer one line of text from a client to a server program. This example is intended to run on a single computer, for demonstration, so the word "localhost" is used to represent the IP address of the server (that's a standard convention used to refer to any computer's own local IP address). If you want to run this on two separate computers connected via a local area network, you'll need to obtain the IP address of the server machine (use the code above), and replace the word "localhost" with that number:

Here's the SERVER program. Be sure to run it before starting the client, or you will receive an error:

REBOL [title: "Server"]
print "Server waiting..."
server: open tcp://:8080
server/awake: func [event /local port] [
    if event/type = 'accept [
        port: first event/port
        port/awake: func [event] [
            switch event/type [
                read [
                    print join "Received: " mold to-string event/port/data
                    halt
                ]
            ]
        ]
        read port
    ]
]
wait [server 30]  ; timeout

Here's the CLIENT. Run it in a separate instance of the REBOL interpreter, after the above program has been started:

REBOL [title: "Client"]
client: open tcp://127.0.0.1:8080
client/awake: func [event] [
    switch event/type [
        lookup [open event/port]
        connect [write event/port to-binary ask "Your message:  "]
    ]
]
wait [client .1]  
print "Message sent" halt

Typically, servers will continuously wait for data to appear in a port, and repeatedly do something with that data. The script below loops to continuously send, receive, and display messages transferred from client(s) to the server, and back. This type of loop forms the basis for most peer-to-peer and client-server network applications. This example is reprinted exactly from http://www.rebol.net/wiki/Port_Examples. Here is the server - run it first:

print "Ping pong server"
server: open tcp://:8080
server/awake: func [event /local port] [
    if event/type = 'accept [
        port: first event/port
        port/awake: func [event] [
            ;probe event/type
            switch event/type [
                read [
                    print ["Client said:" to-string event/port/data]
                    clear event/port/data
                    write event/port to-binary "pong!"
                ]
                wrote [
                    print "Server sent pong to client"
                    read event/port
                ]
                close [
                    close event/port
                    return true
                ]
            ]
            false
        ]
        read port
    ]
    false
]
wait [server 30]
close server

Here is the client:

print "Ping pong client"
ping-count: 0
client: open tcp://127.0.0.1:8080
client/awake: func [event] [
    ;probe event/type
    switch event/type [
        lookup [open event/port]
        connect [write event/port to-binary "ping!"]
        wrote [
            print "Client sent ping to server"
            read event/port
        ]
        read [
            print ["Server said:" to-string event/port/data]
            if (++ ping-count) > 50 [return true]
            clear event/port/data
            write event/port to-binary "ping!"
        ]
    ]
    false
]
wait [client 10] ; timeout after 10 seconds
close client
wait 2

It's important to understand that REBOL servers like the one above can interact independently with more than one simultaneous client connection. The following script is a complete GUI network instant message application:

(coming...)

If you want to run scripts like these between computers connected to the Internet by broadband routers, you'll likely need to learn how to "forward" ports from your router to the IP address of the machine running your server program. In most situations where a router connects a local home/business network to the Internet, only the router device has an IP address which is visible on the Internet. The computers themselves are all assigned IP addresses that are only accessible within the local area network. Port forwarding allows you to send data coming to the IP address of the router (the IP which is visible on the Internet), on a unique port, to a specific computer inside the local area network. A full discussion of port forwarding is beyond the scope of this tutorial, but it's easy to learn how to do - just type "port forwarding" into Google. You'll need to learn how to forward ports on your particular brand and model of router.

With any client-server configuration, only the server machine needs to have an exposed IP address or an open router/firewall port. The client machine can be located behind a router or firewall, without any forwarded incoming ports.

Another option that enables network applications to work through routers is "VPN" software. Applications such as hamachi, comodo, and OpenVPN allow you to connect two separate LAN networks across the Internet, and treat all the machines as if they are connected locally (connect to any computer in the VPN using a local IP address, such as 192.168.1.xxx). VPN software also typically adds a layer of security to the data sent back and forth between the connected machines. The down side of VPN software is that data transmission can be slower than direct connection using port forwarding (the data travels through a third party server).

12.2 Transferring Binary Files Through TCP Network Sockets

This example is reprinted exactly from http://www.rebol.net/wiki/Port_Examples. Here is the server - run it first:

REBOL [title: "Binary File Transfer: Server"]
dir: %temp/
make-dir dir
do-command: func [port /local cmd] [
    cmd: attempt [load port/data]
    if all [cmd parse cmd [file! integer!]] [
        do in port/locals [
            name: first cmd
            size: second cmd
            file: open/new dir/:name
        ]
        return true
    ]
    false
]
transfer: func [port] [
    port/locals: context [name: size: file: none total: 0]
    port/awake: func [event /locals port locs len] [
        print ['subport event/type]
        port: event/port
        locs: port/locals
        switch event/type [
            read [
                ; If the file is open, transfer next chunk:
                either locs/file [
                    len: length? port/data
                    locs/total: locs/total + len
                    write locs/file port/data
                    clear port/data
                    print ["len:" len "total:" locs/total "of" locs/size]
                    read port
                ][
                    ; Otherwise, process the startup command:
                    either do-command port [
                        write port to-binary "go"
                    ][
                        read port ; get rest of start command
                    ]
                ]
            ]
            wrote [read port]
            close [
                if locs/file [close locs/file]
                close port
                return true
            ]
        ]
        false
    ]
    read port ; wait for client to speak
]
server: open tcp://:8080
server/awake: func [event /local port] [
    print ['server event/type]
    if event/type = 'accept [
        transfer first event/port
    ]
    false
]
wait [server 10] ; Note: increase the timeout for large files
close server

Here is the client:

REBOL [title: "Binary File Transfer: Client"]
file-name: %data.dat
info: query file-name
size: info/size
file: none
client: open tcp://127.0.0.1:8080
send-chunk: func [port file /local data] [
    data: read/part file 20000
    if empty? data [return false]
    print ["send:" length? data "bytes"]
    write port data
    true
]
client/awake: func [event /local port result] [
    probe event/type
    port: event/port
    switch event/type [
        read [
            ; What did the server tell us?
            result: load port/data
            clear port/data
            print ["Server says:" result]
            either result = 'go [
                file: open file-name
                send-chunk port file
            ][
                close port
            ]
        ]
        wrote [
            ; Ready for next chunk:
            either file [
                unless send-chunk port file [
                    close port ; finished!
                    return true
                ]
            ][
                ; Ask for server reply:
                read port
            ]
        ]
        close [
            if file [close file]
            close port
            return true
        ]
        lookup [open port]
        connect [
            write port to-binary remold [file-name size]
        ]
    ]
    false
]
wait [client 10] ; timeout after 10 seconds
if file [close file] ; to be sure
close client

For more information on R3 network ports, see:

http://rebol.informe.com/wiki/view/R3_network_scheme http://www.rebol.net/wiki/Ports:_Synchronous_and_Asynchronous_Operations http://www.rebol.net/wiki/TCP_Port_Examples http://www.rebol.net/wiki/TCP_Port_Details http://www.rebol.net/wiki/Ports:_Error_Handling http://www.rebol.com/docs/core23/rebolcore-14.html http://stackoverflow.com/questions/1291127/rebol-smallest-http-server-in-the-world-why-first-wait-listen-port http://www.rebol.net/docs/async-ports.html

13. Parse (REBOL's Answer to Regular Expressions)

The "parse" function is used to import and convert organized chunks of external data into the block format that REBOL recognizes natively. It also provides a means of dissecting, searching, comparing, extracting, and acting upon organized information within unformatted text data (similar to the pattern matching functionality implemented by regular expressions in other languages).

The basic format for parse is:

parse <data> <matching rules>

Parse has several modes of use. The simplest mode just splits up text at common delimiters and converts those pieces into a REBOL block. To do this, just specify "none" as the matching rule. Common delimiters are spaces, commas, tabs, semicolons, and newlines. Here are some examples:

text1: "apple orange pear"
parsed-block1: parse text1 none

text2: "apple,orange,pear"
parsed-block2: parse text2 none

text3: "apple        orange                    pear"
parsed-block3: parse text3 none

text4: "apple;orange;pear"
parsed-block4: parse text4 none

text5: "apple,orange pear"
parsed-block5: parse text5 none

text6: {"apple","orange","pear"}
parsed-block6: parse text6 none

text7: {
apple
orange  
pear
}
parsed-block7: parse text7 none

To split files based on some character other than the common delimiters, you can specify the delimiter as a rule. Just put the delimiter in quotes:

text: "apple*orange*pear"
parsed-block: parse text "*"

text: "apple&orange&pear"
parsed-block: parse text "&"

text: "apple    &    orange&pear"
parsed-block: parse text "&"

You can also include mixed multiple characters to be used as delimiters:

text: "apple&orange*pear"
parsed-block: parse text "&*"

text: "apple&orange*pear"
parsed-block: parse text "*&" ; the order doesn't matter

Using the "splitting" mode of parse is a great way to get formatted tables of data into your REBOL programs. Splitting the text below by carriage returns, you run into a little problem:

text: {    First Name
           Last Name
           Street Address
           City, State, Zip}

probe parsed-block: parse text "^/" 
halt

; ^/ is the REBOL symbol for a carriage return

Spaces are included in the parsing rule by default (parse automatically splits at all empty space), so you get a block of data that's more broken up than intended:

["First" "Name" "Last" "Name" "Street" "Address" "City,"
    "State," "Zip"]

You can use the "/all" refinement to eliminate spaces from the delimiter rule. The code below:

text: {    First Name
           Last Name
           Street Address
           City, State, Zip}

probe parsed-block: parse/all text "^/" 
halt

converts the given text to the following block:

[" First Name" "      Last Name" "      Street Address"
    "      City, State, Zip"]

Now you can trim the extra space from each of the strings:

foreach item parsed-block [trim item]
probe parsed-block

and you get the following parsed-block, as intended:

["First Name" "Last Name" "Street Address" "City, State, Zip"]

13.1 Using Parse to Load Speadsheet CSV Files and Other Structured Data

Parse is commonly used to convert spreadsheet data into REBOL blocks. In Excel, Open Office, and other spreadsheet programs, you can export all the columns of data in a worksheet by saving it as a CSV formatted ("comma separated value") .csv text file. People often put various bits of descriptive text, labels and column headers into spreadsheets to make them more readable:

;                         TITLE
                       DESCRIPTION

                         Header1 

Category1      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

                         Header2

Category2      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

                         Header3

Category3      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

The following code turns the exported CSV spreadsheet data into a nice useable REBOL block, with group heading data added to each line:

; Read and parse the CSV formatted file:

filename: %Download.csv ;%filename.csv
data: copy []
lines: read/lines filename
foreach line lines [
    append/only data parse/all line ","
]

; Add headers from sections of the spreadsheet to each line item:

info: copy ""
foreach line data [
    either find "Header" line/1 [
        info: line/1
    ][
        append line info
    ]
]

; Remove the unwanted descriptive header lines:

remove-each line data [find "Header" line/1/1]
remove-each line data [
    (line/3 = "TITLE") or (line/3 = "DESCRIPTION")
]
probe data halt

13.2 Using Parse's Pattern Matching Mode to Search Data

You can use parse to check whether any specific data exists within a given block. To do that, specify the rule (matching pattern) as the item you're searching for. Here's an example:

parse ["apple"] ["apple"]

parse ["apple" "orange"] ["apple" "orange"]

Both lines above evaluate to true because they match exactly. IMPORTANT: By default, as soon as parse comes across something that doesn't match, the entire expression evaluates to false, EVEN if the given rule IS found one or more times in the data. For example, the following is false:

parse ["apple" "orange"] ["apple"]

But that's just default behavior. You can control how parse responds to items that don't match. Adding the words below to a rule will return true if the given rule matches the data in the specified way:

  1. "any" - the rule matches the data zero or more times
  2. "some" - the rule matches the data one or more times
  3. "opt" - the rule matches the data zero or one time
  4. "one" - the rule matches the data exactly one time
  5. an integer - the rule matches the data the given number of times
  6. two integers - the rule matches the data a number of times included in the range between the two integers

The following examples are all true:

parse ["apple" "orange"] [any string!]
parse ["apple" "orange"] [some string!]
parse ["apple" "orange"] [1 2 string!]

IMPORTANT: By default, as soon as parse comes across something that doesn't match, the entire expression evaluates to false, EVEN if the given rule IS found one or more times in the data.

You can create rules that include multiple match options - just separate the choices by a "|" character and enclose them in brackets. The following is true:

parse ["apple" "orange"] [any [string! | url! | number!]]

You can trigger actions to occur whenever a rule is matched. Just enclose the action(s) in parentheses:

parse ["apple" "orange"] [any [string! 
    (alert "The block contains a string.") | url! | number!]]

You can skip through data, ignoring chunks until you get to, or past a given condition. The word "to" ignores data UNTIL the condition is found. The word "thru" ignores data until JUST PAST the condition is found. The following is true:

parse [234.1 $50 http://rebol.com "apple"] [thru string!]

The real value of pattern matching is that you can search for and extract data from unformatted text, in an organized way. The word "copy" is used to assign a variable to matched data. For example, the following code downloads the raw HTML from the REBOL homepage, ignores everything except what's between the HTML title tags, and displays that text:

parse to-string read http://rebol.com [
    thru <title> copy parsed-text to </title> (alert parsed-text)
]

The following code extends the example above to provide the useful feature of displaying the external ip address of the local computer. It reads http://guitarz.org/ip.cgi, parses out the title text, and then parses that text again to return only the IP number. The local network address is also displayed, using the built in dns protocol in REBOL:

REBOL [title: "Parse External IP Address"]
do %requestors.r3
parse to-string read http://guitarz.org/ip.cgi [
    thru <title> copy my-ip to </title>
]
parse my-ip [thru "Your IP Address is: " copy stripped-ip to end]
alert to-string rejoin ["External IP: " trim/all stripped-ip]

The following code was presented earlier in the "Currency Calculator" example. It downloads and parses the current (live) US Dollar exchange rates from http://x-rates.com. The user selects from a list of currencies to convert to, then performs and displays the conversion from USD to the selected currency:

REBOL [title: "Currency Rate Conversion Converter"]
do %requestors.r3
x: copy []
html: to-string read http://www.x-rates.com/table/?from=USD&amount=1.00
html: find html "src='/themes/bootstrap/images/xrates_sm_tm.png'" 
parse html [
    any [
        thru {from=USD} copy link to {</a>} (append x link)
    ] to end 
]
rates: copy []
foreach rate x [
    parse rate [thru {to=} copy c to {'>}]
    parse rate [thru {'>} copy v to end]
    if not error? try [to-integer v] [append rates reduce [c v]]
]  
currency: request-list "Select Currency:" extract rates 2
rate: to-decimal select rates currency
attempt [
    alert rejoin [
        currency ": " (rate * to-decimal request-text/title "US Dollars:")
    ]
]

Here's a useful example that removes all comments from a given REBOL script (any part of a line that begins with a semicolon ";"):

REBOL [title: "Remove Comments"]
do %requestors.r3
code: read/string request-file
parse/all code [any [
    to #";" begin: thru newline ending: (
        remove/part begin ((index? ending) - (index? begin))) :begin
    ]
]
view [area (code)]

For more about parse, see the following links:

http://www.rebol.com/r3/docs/functions/parse.html
http://www.rebol.com/r3/docs/concepts/parsing-summary.html
http://www.rebol.net/wiki/Parse_Project
http://www.codeconscious.com/rebol/r2-to-r3.html
http://www.codeconscious.com/rebol/parse-tutorial.html
http://www.rebol.com/docs/core23/rebolcore-15.html
http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Parse
http://www.rebolforces.com/zine/rzine-1-06.html#sect4.

14. Objects

Objects are code structures that allow you to encapsulate and replicate code. They can be thought of as code containers which are easily copied and modified to create multiple versions of similar code and/or duplicate data structures. They're also used to provide context and namespace management features (i.e., to avoid assigning the same variable words and/or function names to different pieces of code in large projects).

Object "prototypes" define a new object container. To create an original object prototype in REBOL, use the following syntax:

label: make object! [object definition]

The object definition can contain functions, values, and/or data of any type. Below is a blank user account object containing 6 variables which are all set to equal "none"):

account: make object! [
    first-name: last-name: address: phone: email-address: none
]

The account definition above simply wraps the 6 variables into a container, or context, called "account".

You can refer to data and functions within an object using refinement ("/path") notation:

object/word

In the account object, "account/phone" refers to the phone number data contained in the account. You can make changes to elements in an object as follows:

object/word: data

For example:

account/phone: "555-1234"
account/address: "4321 Street Place Cityville, USA 54321"

Once an object is created, you can view all its contents using the "help" function:

help object
? object  

; "?" is a synonym for "help"

If you've typed in all the account examples so far into the REBOL interpreter, then:

? account

displays the following info:

ACCOUNT is an object of value:
   first-name      none!     none
   last-name       none!     none
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   none!     none

You can obtain a list of all the items in an object using a foreach loop:

foreach item account [print item]

To get the values referred to by individual word labels in objects, use "get in":

get in account 'first-name
get in account 'address

; notice the tick mark

The following example demonstrates how to access and manipulate every value in an object:

count: 0
foreach item (next first account) [
    count: count + 1
    print rejoin ["Item " count ":   " item]
    print rejoin ["Value:    " (get in account item) newline]
]

Once you've created an object prototype, you can make a new object based on the original definition:

label: make existing-object [
    values to be changed from the original definition
]

This behaviour of copying values based on previous object definitions (called "inheritance") is one of the main reasons that objects are useful. The code below creates a new account object labeled "user1":

user1: make account [
    first-name: "John"
    last-name: "Smith"
    address: "1234 Street Place  Cityville, USA 12345"
    email-address: "john@hisdomain.com"
]

In this case, the phone number variable retains the default value of "none" established in the original account definition.

You can extend any existing object definition with new values:

label: make existing-object [new-values to be appended]

The definition below creates a new account object, redefines all the existing variables, and appends a new variable to hold the user's favorite color.

user2: make account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
    favorite-color: "blue"
]

"user2/favorite-color" now refers to "blue".

The code below creates a duplicate of the user2 account, with only the name and email changed:

user2a: make user2 [
    first-name: "Paul"
    email-address: "paul@mysite.net"
]

"? user2a" provides the following info:

USER2A is an object of value:
   first-name      string!   "Paul"
   last-name       string!   "Jones"
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   string!   "paul@mysite.net"
   favorite-color  string!   "blue"

You can include functions in your object definition:

complex-account: make object! [
    first-name: 
    last-name:
    address:
    phone:
    none
    email-address: does [
        return to-email rejoin [
            first-name "_" last-name "@website.com"
        ]
    ]
    display: does [
        print ""
        print rejoin ["Name:     " first-name " " last-name]
        print rejoin ["Address:  " address]
        print rejoin ["Phone:    " phone]
        print rejoin ["Email:    " email-address]
        print ""
    ]
]

Note that the variable "email-address" is initially assigned to the result of a function (which simply builds a default email address from the object's first and last name variables). You can override that definition by assigning a specified email address value. Once you've done that, the email-address function no longer exists in that particular object - it is overwritten by the specified email value.

Here are some implementations of the above object. Notice the email-address value in each object:

user1: make complex-account []

user2: make complex-account [
    first-name: "John"
    last-name: "Smith"
    phone:  "555-4321"
]

user3: make complex-account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
]

To print out all the data contained in each object:

user1/display user2/display user3/display

The display function prints out data contained in each object, and in each object the same variables refer to different values (the first two emails are created by the email-address function, and the third is assigned).

Here's a small game in which multiple character objects are created from a duplicated object template. Each character can store, alter, and print its own separately calculated position value based on one object prototype definition:

REBOL [title: "Object Game"]
hidden-prize: random 15x15
character: make object! [
    position: 0x0
    move: does [
        direction: ask "Move up, down, left, or right:  "
        switch/default direction [
            "up" [position: position + -1x0]
            "down" [position: position + 1x0]
            "left" [position: position + 0x-1]
            "right" [position: position + 0x1]
        ] [print newline print "THAT'S NOT A DIRECTION!"]
        if position = hidden-prize [
            print newline
            print "You found the hidden prize.  YOU WIN!"
            print newline
            halt
        ]
        print rejoin [
            newline
            "You moved character " movement " " direction
            ".  Character " movement " is now " 
            hidden-prize - position
            " spaces away from the hidden prize.  "
            newline
        ]
    ]
]
character1: make character[]
character2: make character[position: 3x3]
character3: make character[position: 6x6]
character4: make character[position: 9x9]
character5: make character[position: 12x12]
loop 20 [
    prin newpage
    movement: ask "Which character do you want to move (1-5)?  "
    if find ["1" "2" "3" "4" "5"] movement [
        do rejoin ["character" movement "/move"]
        print rejoin [
            newline
            "The position of each character is now:  "
            newline newline
            "CHARACTER ONE:   " character1/position newline
            "CHARACTER TWO:   " character2/position newline
            "CHARACTER THREE: " character3/position newline
            "CHARACTER FOUR:  " character4/position newline
            "CHARACTER FIVE:  " character5/position
        ]
        ask "^/Press the [Enter] key to continue."
    ]
]

You could, for example, extend this concept to create a vast world of complex characters in an online multi-player game. All such character definitions could be built from one base character definition containing default configuration values.

14 Namespace Management

In this example the same words are defined two times in the same program:

var: 1234.56
bank: does [
    print ""
    print rejoin ["Your bank account balance is:  $" var]
    print ""
]

var: "Wabash"
bank: does [
    print ""
    print rejoin [
        "Your favorite place is on the bank of the:  " var]
    print ""
]

bank

There's no way to access the bank account balance after the above code runs, because the "bank" and "var" words have been overwritten. In large coding projects, it's easy for multiple developers to unintentionally use the same variable names to refer to different pieces of code and/or data, which can lead to accidental deletion or alteration of values. That potential problem can be avoided by simply wrapping the above code into separate objects:

money: make object! [
    var: 1234.56
    bank: does [
        print ""
        print rejoin ["Your bank account balance is:  $" var]
        print ""
    ]
]

place: make object! [
    var: "Wabash"
    bank: does [
        print ""
        print rejoin [
            "Your favorite place is on the bank of the:  " var]
        print ""
    ]
]

Now you can access the "bank" and "var" words in their appropriate object contexts:

money/bank
place/bank

money/var
place/var

The objects below make further use of functions and variables contained in the above objects. Because the new objects "deposit" and "travel" are made from the "money" and "place" objects, they inherit all the existing code contained in the above objects:

deposit: make money [
    view layout [
        button "Deposit $10" [
            var: var + 10
            bank
        ]
    ]
]

travel: make place [
    view layout [
        new-favorite: field 300 trim {
            Type a new favorite river here, and press [Enter]} [
            var: value
            bank
        ]
    ]
]

Learning to use objects is important because much of REBOL is built using object structures. As you've seen earlier in the section about built-in help, the REBOL "system" object contains many important interpreter settings. In order to access all the values in the system object, it's essential to understand object notation:

get in system/components/graphics 'date

The same is true for GUI widget properties and many other features of REBOL.

For more information about objects, see:

http://rebol.com/docs/core23/rebolcore-10.html
http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Objects
http://en.wikipedia.org/wiki/Prototype-based_programming

15. Organizing Efficient Data Structures and Algorithms

The purpose of this tutorial is enable the use of a computing tool that improves productivity in business operations. Creating programs that execute quickly is an important goal toward that end, especially when dealing with large sets of data. In order to achieve speedy performance, it's critical to understand some fundamental concepts about efficient algorithmic design patterns and sensible techniques used to organize and store information effectively.

15.1 A Simple Loop Example

The example below provides a basic demonstration about how inefficient design can reduce performance. This code prints 30 lines of text to the REBOL interpreter console, each made up of 75 dash characters:

REBOL [title: "Badly Designed Line Printer"]
for i 1 30 1  [
    for i 1 75  1 [prin "-"]
    print ""
]
halt

Even on a very fast computer, you can watch the screen flicker, and see the characters appear as dashes are printed in a loop, 75 times per each line, across 30 lines. That inner character printing operation is repeated a total of 2250 times (30 lines * 75 characters). As it turns out, the REBOL "loop" function is slightly faster than the "for" function, when creating simple counted loop structures. So, you can see a slight performance improvement using the following code:

REBOL [title: "Slightly Improved Line Printer"]
loop 30 [
    loop 75 [prin "-"]
    print ""
]
halt

But the example above does not address the main bottle neck which slows down the display. The function that really takes time to execute is the "prin" function. The computer is MUCH slower at printing items to the screen, than it is at performing unseen computations in RAM memory. Watch how long it takes to simply perform the same set of loops and to increment a count (i.e., perform a sum computation), compared to printing during each loop. This example executes instantly, even on very slow computers:

REBOL [title: "Loops Without Printing"]
count: 0
loop 30 [
    loop 75 [count: count + 1]
]
print count
halt

A much more efficient design, therefore, would be to create a line of characters once, save it to a variable label, and then print that single saved line 30 times:

REBOL [title: "Well Designed Line Printer"]
line: copy {} 
loop 75 [append line "-"]
loop 30 [print line]
halt

The example above reduces the number of print functions from 2250 to 30, and the display is dramatically improved. The following code times the execution speed of each of the techniques above, so you can see just how much speed is saved by using memory and processing power more efficiently:

REBOL [title: "Printing Algorithm Timer"]

timer1: now/time/precise
for i 1 30 1  [
    for i 1 75  1 [prin "-"]
    print ""
]
elapsed1: now/time/precise - timer1
print newpage

timer2: now/time/precise
loop 30 [
    loop 75 [prin "-"]
    print ""
]
elapsed2: now/time/precise - timer2
print newpage

timer3: now/time/precise
count: 0
loop 30 [
    loop 75 [count: count + 1]
]
print count
elapsed3: now/time/precise - timer3
print newpage

timer4: now/time/precise
line: copy {} 
loop 75 [append line "-"]
loop 30 [print line]
elapsed4: now/time/precise - timer4
print newpage

print rejoin ["Printing 2250 characters, 'for':    " elapsed1]
print rejoin ["Printing 2250 characters, 'loop':   " elapsed2]
print rejoin ["Counting to 2250, printing result:  " elapsed3]
print rejoin ["Printing 30 preconstructed lines:   " elapsed4]

halt

This is, of course, a trivial demonstrative example, but identifying such "bottle necks", and designing code patterns which reduce loop counts and cut down on computational rigor, is a critical part of the thought process required to create fast and responsive applications. Knowing the benchmark speed of functions in a language, and understanding how to use effecient add-on tools such as database systems, can be helpful, but more often, smart architecture and sensible awareness of how data structures are organized, will have a much more dramatic effect on how well a program performs. When working with data sets that are expected to grow to a large scale, it's important to pay attention to how information will be stored and looped through, before any production code is written and implemented.

15.2 A Real Life Example: Checkout Register and Cashier Report System

The example below is the trivial POS (cash register) example demonstrated earlier in the tutorial. It saves the data for all receipts in a single text file:

REBOL [title: "Minimal Cash Register - Inefficient"]
do %r3-gui.r3
stylize [fld: field [init-size: 80]]   
view [
    hgroup [
        text "Cashier:"   cashier: fld 
        text "Item:"      item: fld 
        text "Price:"     price: fld on-action [
            if error? try [to-money get-face price] [
                request "Error" "Price error" 
                return none
            ]
            set-face a rejoin [
                get-face a mold get-face item tab get-face price newline
            ]
            set-face item copy "" set-face price copy ""
            sum: 0
            foreach [item price] load get-face a [
                sum: sum + to-money price
            ]
            set-face subtotal form sum
            set-face tax form sum * .06
            set-face total form sum * 1.06 
            focus item
        ]
        return
        a: area 600x300
        return
        text "Subtotal:"   subtotal: fld 
        text "Tax:"        tax: fld 
        text "Total:"      total: fld
        button "Save" on-action [
            items: replace/all (mold load get-face a) newline " "
            write/append %sales.txt rejoin [
                items newline get-face cashier newline now/date newline
            ]
            set-face item copy "" set-face price copy "" 
            set-face a copy ""    set-face subtotal copy ""
            set-face tax copy ""  set-face total copy ""
        ]
    ]
]

This code reports the total of all items sold on the current day, by any chosen cashier:

REBOL [title: "Daily Cashier Report" - Inefficient]
do %r3-gui.r3
name: ask "Cashier:  "
sales: read/lines %sales.txt
sum: $0
foreach [items cashier date] sales [
    if ((now/date = to-date date) and (cashier = name)) [
        foreach [item price] load items [
            sum: sum + to-money price
        ]
    ]
]
alert rejoin ["Total sales today for " name ": " sum]

This whole program appears to work just fine on first inspection, but what happens after a million sales transactions have been entered into the system? In that case, the report program must read in and loop through 3 million lines of data, perform the date and cashier comparison evaluations on every single sales entry, and then perform the summing loop on only the matching sales entries. That's a LOT of unnecessary processing, and reading data from the hard drive is particularly slow.

We could simply plan to occassionally copy, paste, and erase old transactions from the sales.txt file into a separate archive file. That would solve the problem (and may be a occasionally useful maintanence objective), but it doesn't really improve performance. By changing the method of storage just a bit, we can dramatically improve performance. The example below creates a new folder, and writes every sales transaction to a separate file. The FILE NAMES of saved sales contain the date, time, and cashier name of each transaction (only the code for the "Save" button has been changed slightly in this example):

REBOL [title: "Minimal Cash Register - Efficient"]
do %r3-gui.r3
stylize [fld: field [init-size: 80]]   
view [
    hgroup [
        text "Cashier:"   cashier: fld 
        text "Item:"      item: fld 
        text "Price:"     price: fld on-action [
            if error? try [to-money get-face price] [
                request "Error" "Price error" 
                return none
            ]
            set-face a rejoin [
                get-face a mold get-face item tab get-face price newline
            ]
            set-face item copy "" set-face price copy ""
            sum: 0
            foreach [item price] load get-face a [
                sum: sum + to-money price
            ]
            set-face subtotal form sum
            set-face tax form sum * .06
            set-face total form sum * 1.06 
            focus item
        ]
        return
        a: area 600x300
        return
        text "Subtotal:"   subtotal: fld 
        text "Tax:"        tax: fld 
        text "Total:"      total: fld
        button "Save" on-action [
            file: copy to-file rejoin [
                now/date
                "_" replace/all form now/time ":" "-"
                "_" get-face cashier
            ]
            make-dir %./sales/
            save rejoin [%./sales/ file] (load get-face a)
            set-face item copy "" set-face price copy "" 
            set-face a copy ""    set-face subtotal copy ""
            set-face tax copy ""  set-face total copy ""
        ]
    ]
]

This improved report reads the list of file names in the %./sales/ folder. Each file name is parsed into date, time, and name components, and then, only if the date and cashier items match the report requirements, the loop that computes the sum is run. This eliminates a dramatic volume of data reading (only the file list is read - not the entire contents of every file), and avoids a tremendous number of unnecessary looped computational steps:

REBOL [title: "Cashier Report - Efficient"]
do %requestors.r3
report-date: request-date
report-cashier: request-text/title/default "Cashier:" "Nick"
sum: $0
files: copy []
foreach file read %./sales/ [
    parsed: parse file "_"
    if ((report-date = to-date parsed/1) and (report-cashier = parsed/3))[
        append files file
    ]
]
cd %./sales/
foreach file files [ 
    foreach [item price] load file [
        sum: sum + to-money price
    ]
]
cd %../
alert rejoin ["Total for " report-cashier " on " report-date ": " sum]

When working with systems that are expected to handle large volumes of data, it's essential to run benchmark tests comparing potential design options. It's a simple task to write scripts which generate millions of entries of random data to test any system you create, at volume levels exceeding potential real life scenarios. Creating a script to generate random data to test the program above, for example, is as simple as looping the "Save" button code that creates the receipt files:

REBOL [title: "Automated Test Sales Data Generator"]
do %requestors.r3
random/seed now/time/precise
loop 10000 [
    file: to-file rejoin [
        ((random 31-dec-0001) + 734868)   ; 734868 days between 2013/0001
        "_" replace/all form random 23:59:59 ":" "-"
        "_" random {abcd}
    ]
    items: reduce [random "asd fgh jkl" to-money random 100]
    save rejoin [%./sales/ file] items
]
alert "done"

Try running the efficient report after creating the above test data, and you'll see that results are calculated instantly:

REBOL [title: "Cashier Report - Efficient"]
do %requestors.r3
report-date: request-date
report-cashier: request-text/title/default "Cashier:" "Nick"
sum: $0
files: copy []
foreach file read %./sales/ [
    parsed: parse file "_"
    if ((report-date = to-date parsed/1) and (report-cashier = parsed/3))[
        append files file
    ]
]
cd %./sales/
foreach file files [ 
    foreach [item price] load file [
        sum: sum + to-money price
    ]
]
cd %../
alert rejoin ["Total for " report-cashier " on " report-date ": " sum]

The method of saving structured data in separate text files, demonstrated by the simplified POS examples above, has been tested by the author in a variety of high volume commercial software implementations. The computational speed demonstrated by reports using this technique outperformed the capabilities of even powerful database systems. Other benefits, such as portability between systems (any operating system with a file structure can be used), no required DBMS installation, easy backup and transfer of data to other systems and mediums, etc., make this an extremely powerful way of approaching serious data management needs. Backing up all new data to a thumb drive is as simple as performing a folder sync. In one case, a web site reporting system was created to match a desktop POS reporting system that has tracked tens of millions of item sales. A simple REBOL script was used to upload new sales transaction files to the web server daily, and the exact same code was used to print the report as CGI script output. Users were then able to check sales online using browsers on any desktop PC, iPhone, Android, etc. Absolutely no database or machine dependent software architecture was required using this simple conceptual pattern of saving data to text files. The key to the success of this example of data organization, storage, and algorithmic report computation, is to simply engineer the system to store potentially searched data fields in the file names.

Learning to avoid wasted effort, and to pinpoint computational bottle necks, especially in loops that manage large data sets, is critical to designing fast applications. Even if you use powerful database systems to store data, it's still important to consider how you read, search, and otherwise manipulate the saved data. As a general rule, avoid reading, writing, transferring, or performing computations on anything other than the absolute smallest possible sets of values, and you'll find that performance can always be improved.

You'll see these sort of efficient design decisions applied to many applications in this tutorial. Pay particular attention to the way data is stored and reported in text files in the "RebGUI Point of Sale", "Simple POS", and "Simple POS Sales Report Printer" examples. More pointed techniques, such as the way in which messages were automatically moved to an "Archive" database in the "RebolForum" CGI application, help to dramatically improve the user experience by directly reducing the amount of data needed to present an initial display. That application has been tested in production situations, and responds instantly, even when the system contains many hundreds of thousands of messages.

It's important to consider the potential volume of data any system will be required to manage, before fully implementing the user interface and data structure in production environments. Consider and test all data fields that may be required as a system grows, and be sure your data structure and computational algorithms can handle more volume than will ever be needed. This will save you enormous headache down the road, when your software is firmly entrenched in business operations.

16. More REBOL Language Fundamentals

This section covers a variety of topics that form a more complete understanding of the basic REBOL language.

16.1 Comments

You've already seen that text after a semicolon and before a new line is treated as a comment (ignored entirely by the interpreter). Multi-line comments can be created by enclosing text in square or curly brackets and simply not assigning a label or function to process it. Since this entire program is just a header and comments, it does nothing:

; this is a comment
{
    This is a multi line comment.
    Comments don't do anything in a program.
    They just remind the programmer what's happening in the code.
}
[[
    This is also a multi line comment.
    alert "See...  Nothing."
]] 
comment [
    The "comment" function can be used to clarify that the following
    block does nothing, but it's not necessary.
]

16.2 Function Refinements

Many functions have "refinements" (options separated by "/"):

do %requestors.r3
request-text/default "Text"
request-text/title  "The /title refinement sets this header text"
request-text/title/default  "Name:"  "John Smith"     ; 2 options together

Typing "help (function)" in the REBOL interpreter console displays all available refinements for any function.

16.3 White Space and Indentation

Unlike other languages, REBOL does not require any line terminators between expressions (functions, parameters, etc.), and you can insert empty white space (tabs, spaces, newlines, etc.) as desired into code. Notice the use of indentation and comments in the code below. Notice also that the contents of the brackets are spread across multiple lines:

alert rejoin [
    "You chose: "               ; 1st piece of joined data
    (request "Choose one:")     ; 2nd piece of joined data
]

The code above works exactly the same as:

alert rejoin ["You chose: " request "Choose one:"]

ONE CAVEAT: parameters for most functions should begin on the same line as the function word. The following example will not work properly because the rejoin arguments' opening brackets need to be on the same line as the rejoin function:

alert rejoin                    ; This does NOT work. 
[                               ; Put this bracket on the line above.
    "You chose: "
    (request "Choose one:")
]

Blocks often contain other blocks. Such compound blocks are typically indented with consecutive tab stops. Starting and ending brackets are normally placed at the same indentation level. This is conventional in most programming languages, because it makes complex code easier to read, by grouping things visually. For example, the compound block below:

big-block: [[may june july] [[1 2 3] [[yes no] [monday tuesday friday]]]]

can be written as follows to show the beginnings and endings of blocks more clearly:

big-block: [
    [may june july] 
    [ 
        [1 2 3] 
        [
            [yes no]
            [monday tuesday friday]
        ]
    ]
]

probe first big-block
probe second big-block
probe first second big-block
probe second second big-block
probe first second second big-block
probe second second second big-block

Indentation is not required, but it's really helpful.

16.4 Multi Line Strings, Quotes, and Concatenation

Strings of text can be enclosed in quotes or curly brackets:

print "This is a string of text."
print {
    Curly braces are used for
    multi line text strings
    (instead of quotes).
}

alert {To use "quotes" in a text string, put them inside curly braces.}
alert "You can use {curly braces} inside quotes."
alert "'Single quotes' can go inside double quotes..."
alert {'...or inside curly braces'}
alert {"ANY quote symbol" {can actually be used within} 'curly braces'}
alert "In many cases"  alert {curly braces and quotes are interchangable.}

You can print a carriage return using the word "newline" or the characters ^/

print rejoin ["This text if followed by a carriage return." newline]
print "This text if followed by a carriage return.^/"

Clear the console screen using "newpage":

prin newpage

The "rejoin" function CONCATENATES (joins together) values:

rejoin ["Hello " "World"]
rejoin [{Concatenate } {as } {many items } {as } {you } {want.}]
rejoin [request-date {  } request-color {  } now/time {  } $29.99]
alert rejoin ["You chose: " request "Choose one:"] ; CASCADE return values
join {"Join" only concatenates TWO items } {("rejoin" is more powerful).}
print rejoin ["This text is followed by a carriage return." newline]
print "This text is also followed by a carriage return.^/"
prin {'Prin' } prin {doesn't } prin {print } print {a carriage return.}

16.5 More About Variables

The COLON symbol assigns a value to a word label (a "variable")

x: 10
print x
x: x + 1    ; increment variable by 1 (add 1 to the current value of x)
print x
y: "hello"  z: " world"

You can use the "PROBE" function to show RAW DATA assigned to a variable (PRINT formats nice output). Probe is useful for debugging problem code:

y: "hello"  z: " world"
print rejoin [y z]
probe rejoin [y z]   
print join y z

The "prin" function prints values next to each other (without a newline):

y: "hello"  z: " world"
prin y 
prin z

Variables (word labels) ARE ** NOT ** CASE SENSITIVE:

person: "john"
print person   print PERSON   print PeRsOn

You can cascade variable value assignments. Here, all 3 variables are set to "yes ":

value1: value2: value3: "yes "
print rejoin [value1 value2 value3]

The "ask" function gets text input from the user. You can assign that input (the return value of the ask function) directly to a variable label:

name: ask "Enter your name:  "  
print rejoin ["Hello " name]

You can do the same with values returned from requestor functions:

filename: request-file
alert rejoin ["You chose " filename]
osfile: to-local-file filename   ; REBOL uses its own multiplatform syntax
to-rebol-file osfile  ; Convert from native OS file notation back to REBOL
the-url: http://website.com/subfolder
split-path the-url   ; "split-path" breaks any file or URL into 2 parts

16.6 Data Types

REBOL automatically knows how to perform appropriate computations on times, dates, IP addresses, coordinate values, and other common types of data:

print 3:30am + 00:07:19                   ; increment time values properly
print now                                 ; print current date and time
print now + 0:0:30                        ; print 30 seconds from now
print now - 10                            ; print 10 days ago
$29.99 * 5                                ; perform math on money values
$29.99 / 7                                ; try this with a decimal value
29.99 / 7
print 23x54 + 19x31                       ; easily add coordinate pairs
22x66 * 2                                 ;  and perform other coordiante
22x66 * 2x3                               ;  math
print 192.168.1.1 + 000.000.000.37        ; easily increment ip addresses
11.22.33.44 * 9        ; note that each IP segment value is limited to 255
0.250.0 / 2       ; colors are represented as tuple values with 3 segments
red / 2                    ; so this is an easy way to adjust color values
x: 12  y: 33  q: 18  p: 7
(as-pair x y) + (as-pair q p)  ; very common in graphics apps using coords
remove form to-money 1 / 233
remove/part form to-time (1 / 233) 6

REBOL also natively understands how to use URLs, email addresses, files/directories, money values, tuples, hash tables, sounds, and other common values in expected ways, simply by the way the data is formatted. You don't need to declare, define, or otherwise prepare such types of data as in other languages - just use them.

To determine the type of any value, use the "type?" function:

some-text:  "This is a string of text"    ; strings of text go between
type? some-text                           ; "quotes" or {curly braces}

some-text:  {
    This is a multi line string of text.
    Strings are a native data type, delineated by
    quotes or curly braces, which enclose text.
    REBOL has MANY other built in data types, all
    delineated by various characters and text formats.
    The "type" function returns a value's data type.
    Below are just a few more native data types:
}                                          
type? some-text

an-integer: 3874904                       ; integer values are just pos-
type? an-integer                          ; itive/negative whole numbers

a-decimal: 7348.39                        ; decimal numbers are recognized
type? a-decimal                           ; by the decimal point

web-site: http://musiclessonz.com         ; URLs are recognized by the
type? web-site                            ; http://, ftp://, etc.

email-address: user@website.com           ; email values are in the
type? email-address                       ; format user@somewebsite.domain

the-file: %/c/myfile.txt                  ; files are preceded by the %
type? the-file                            ; character

bill-amount: $343.56                      ; money is preceded by the $
type? bill-amount                         ; symbol

html-tag: <br>                            ; tags are places between <>
type? html-tag                            ; characters

binary-info:  #{ddeedd}                   ; binary data is put between
type? binary-info                         ; curly braces and preceded by
                                          ; the pound symbol

image: load http://rebol.com/view/bay.jpg ; REBOL can even automatically
type? image                               ; recognize image data types

color: red
type? color

color-tuple: 123.54.212                    
type? color-tuple

a-character: #"z"                          
type? a-character

a-word: 'asdf                              
type? a-word

Data types can be specifically "cast" (created, or assigned to different types) using "to-(type)" functions:

numbr: 4729                ; The label 'numbr now represents the integer
                           ;   4729.
strng: to-string numbr     ; The label 'strng now represents a piece of
                           ;   quoted text made up of the characters
                           ;   "4729".  Try adding strng + numbr, and
                           ;   you'll get an error.

; This example creates and adds two coordinate pairs.  The pairs are
; created from individual integer values, using the "to-pair" function:

x: 12  y: 33  q: 18  p: 7
pair1: to-pair rejoin [x "x" y]        ; 12x33
pair2: to-pair rejoin [q "x" p]        ; 18x7
print pair1 + pair2                    ; 12x33 + 18x7 = 30x40

; This example builds and manipulates a time value using the "to-time"
; function:

hour: 3
minute: 45
second: 00
the-time: to-time rejoin [hour ":" minute ":" second]    ; 3:45am
later-time: the-time + 3:00:15
print rejoin ["3 hours and 15 seconds after 3:45 is " later-time]

; This converts REBOL color values (tuples) to HTML colors and visa versa:

to-binary request-color
to-tuple #{00CD00}

Try this list of data type conversion examples:

to-decimal 3874904        ; Now the number contains a decimal point
to-string http://go.com   ; now the web site URL is surrounded by quotes
form web-site             ; "form" also converts various values to string
form $29.99
alert form $29.99         ; the alert function REQUIRES a string parameter
alert $29.99              ; (this throws an error)
5 + 6                     ; you can perform math operations with integers
"5" + "6"                 ; (error) you can't perform math with strings
(to-integer "5") + (to-integer "6")    ; this eliminates the math problem
to-pair [12 43]           ; creates a coordinate pair
as-pair 12 43             ; a better way to create a coordinate pair
to-binary 123.54.212      ; convert a REBOL color value to hex color value
to-binary request-color   ; convert the color chosen by the user, to hex
to-tuple #{00CD00}        ; convert a hex color value to REBOL color value
form to-tuple #{00CD00}   ; covert the hex color value to a string
write %floorplan8.pdf debase read clipboard://          ; email attachment

REBOL has many built-in helper functions for dealing with common data types. Another way to create pair values is with the "as-pair" function. You'll see this sort of pair creation commonly to plot graphics at coordinate points on the screen:

x: 12  y: 33  q: 18  p: 7
print (as-pair x y) + (as-pair q p)    ; much simpler!

Built-in network protocols, native data types, and consistent language syntax for reading, writing, and manipulating data allow you to perform common coding chores easily and intuitively in REBOL. Remember to type or paste every example into the REBOL interpreter to see how each function and language construct operates.

16.7 Random Values

You can create random values of any type:

random/seed now/time   ; always use this line to get real random values
random 50              ; a random number between 0 and 50
random 50x100          ; left side is limited to 50, right limited to 100
random 222.222.222     ; each segment is limited to #s between 0 and 222
to-money random 500
random "asdfqwerty"    ; a random mix of the given characters

16.8 More About Reading, Writing, Loading, and Saving to and from Varied Sources

Thoughout this tutorial, you'll see examples of functions that read and write data to "local files", web sites, the system "clipboard", emails, and other data sources. If you're totally new to programming, a quick explanation of that topic and related terms is helpful here.

It may be familiar that in MS Windows, when you click the "My Computer" (or "Computer") icon on the desktop, you see a list of hard drives, USB flash drives, mapped network drives, and other storage devices attached to the computer:

Data on your computer's permanent hard drive is organized into a tree of folders (or "directories"), each containing individual files and additional branches of nested folders. In Windows, the root directory of your main hard drive is typically called "C:\". C is the letter name given to the disk, and "\" ("backslash") represents the root folder of the directory tree. The REBOL interpreter is installed, by default, in the folder C:\Program Files\rebol\view\

When you perform any sort of read, write, save, load, editor or other function that reads and writes data "on your computer", you are working with files on one of the "local" storage devices attached to the computer (as opposed to a folder on web server, email account, etc.). When using read and write functions, the default location for those files, in a default installation of REBOL, is "C:\Program Files\rebol\view\". The following image of a Windows file selector shows the list of files currently contained in that folder on the author's computer:

In REBOL, the percent character ("%") is used to represent local files. Because REBOL can be used on many operating systems, and because those operating systems all use different syntax to refer to drives, paths, etc., REBOL uses the universal format: %/drive/path/path/.../file.ext . For example, "C:\Program Files\rebol\view\" in Windows is referred to as "%/C/Program%20Files/rebol/view/" in REBOL code. Note that Windows uses backslashes (\), and REBOL uses forward slashes (/) to refer to folders. REBOL converts the "%" syntax to the appropriate operating system format, so that your code can be written once and used on every operating system, without alteration.

The list of files in the image above would be written in REBOL as below. Notice the forward slashes at the end of folder names:

%data/
%desktop/ 
%local/ 
%public/ 
%temp-examples/ 
%console_email.r
%e 
%edit-prefs.r 
%r 
%read-email-header.r 
%rebol.exe 
%temp.txt 
%user.r 
%word-cats.r 
%word-defs.r 
%wordbrowser.r

The following 2 functions convert REBOL file format to your operating system's format, and visa versa. This is particularly useful when dealing with files that contain spaces or other characters:

print to-local-file %/C/Program%20Files/rebol/view/rebol.exe 
print to-rebol-file {C:\Program Files\rebol\view\rebol.exe}

You can use the "change-dir" (or "cd") function to change folders:

change-dir %/c/                     ; changes to the C:\ folder in Windows
cd %/c/                             ; "cd" is a shortcut for "change-dir"

To move "up" a folder from the current directory, use "%../". For example, the following code moves from the default C:\Program Files\rebol\view\ folder, up to C:\Program Files\rebol\, and then to C:\Program Files\:

cd %../
cd %../

Refer to the current folder with "%./". You can read a listing of the files in the current folder, like this:

read %./
probe read %./
view [text-list (read %./)]

You can make a new folder within the current folder, using the "make-dir" function. Here are some additional folder functions:

make-dir %./newfolder/
what-dir 
list-dir

You can rename, copy, and delete files within a folder, using these functions:

rename %temp.txt %temp2.txt                             ; change file name
write %temp.txt read %temp2.txt                                ; copy file
delete %temp2.txt

The "write" function writes data to a file. It takes two parameters - a file name to write to, and some data to be written:

write %name.txt "John Smith"

The "read" function reads data from a file:

read %name.txt

You can assign variable labels to data that will be written to a file:

name: "John Smith"
write %name.txt name

Assign variable labels to data read from a file:

loaded-name: read %name.txt
print loaded-name

You can read data straight from a web server, an ftp account, an email account, etc. using the same format. Many Internet protocols are built right into the REBOL interpreter. They're understood natively, and REBOL knows exactly how to connect to them without any preparation by the programmer:

probe to-string read http://rebol.com        ; Reads the content of the
                                             ;   document at this URL.
view [area (to-string read clipboard://)]    ; Reads data that has
                                             ;   been copied/pasted to
                                             ;   the OS clipboard.
print read dns://msn.com                     ; Displays the DNS info
                                             ;   for this address.

Transferring data between devices connected by any supported protocol is easy - just read and write:

; read data from a web site, and paste it into the local clipboard:

write clipboard:// (read http://rebol.com)
; afterward, try pasting into your favorite text editor
; again, notice that the "write" function takes TWO parameters

Binary (non-text) data can be read and written the same way. You can read and write images, sounds, videos and other non-text files:

write %/c/bay.jpg read http://rebol.com/view/bay.jpg

For clarification, remember that the write function takes two parameters. The first parameter above is "%/c/bay.jpg". The second parameter is the binary data read from http://rebol.com/view/bay.jpg:

write (%/c/bay.jpg) (read http://rebol.com/view/bay.jpg)

The "load" and "save" functions also read and write data, but in the process, automatically format certain data types for use in REBOL. Try this:

; assign the word "picture" to the image "load"ed from a given URL:

picture: load http://rebol.com/view/bay.jpg

"Load" and "save" are used to conveniently manage certain types of data in formats directly usable by REBOL (blocks, images, sounds, DLLs, certain native data structures, etc. can be loaded and used immediately). You'll use "read" and "write" more commonly to store and retrieve typical types of data, exactly byte for byte, to/from a storage medium, when no conversion or formatting is necessary.

All these examples and options may currently come across as confusing. If the topic is feels daunting at the moment, simply accept this section as reference material and continue studying the next sections. You'll become much more familiar with reading and writing as you see the functions in use within real examples.

16.9 Understanding Return Values and the Order of Evaluation

In REBOL, you can put as many functions as you want on one line, and they are all evaluated strictly from left to right. Functions are grouped together automatically with their required data parameter(s). The following line contains two alert functions:

alert "First function" alert "Second function"

Rebol knows to look for one parameter after the first alert function, so it uses the next piece of data on that line as the argument for that function. Next on the line, the interpreter comes across another alert function, and uses the following text as it's data parameter.

Simple requester functions don't require any parameters, but like most functions, they RETURN a useful value. Try pasting these functions directly into the REBOL console to see their return values:

request-text
request-date 
request-color
request-file

In the following line, the first function, with its refinements "request-text/title/default" requires two parameters, so REBOL uses the next two items on the line ("My Title" and "Some Text") as its arguments. After that's complete, the interpreter comes across another "alert" function, and uses the following text, "Processing", as its argument:

request-text/title/default "My Title" "Some Text" alert "Processing"

IMPORTANT: In REBOL, the return values (output) from one function can be used directly as the arguments (input) for other functions. Everything is simply evaluated from left to right. In the line below, the "alert" function takes the next thing on the line as it's input parameter, which in this case is not a piece of data, but a function which returns some data (the concatenated text returned by the "rejoin" function):

alert rejoin ["Hello " "there" "!"]

To say it another way, the value returned above by the "rejoin" function is passed to (used as a parameter by) the "alert" function. Parentheses can be used to clarify which expressions are evaluated and passed as parameters to other functions. The parenthesized line below is treated by the REBOL interpreter exactly the same as the line above - it just lets you see more clearly what data the "alert" function puts on screen:

alert ( rejoin ["Hello " "there" "!"] )

Perhaps the hardest part of getting started with REBOL is understanding the order in which functions are evaluated. The process can appear to work backwords at times. In the example below, the "editor" function takes the next thing on the line as it's input parameter, and edits that text. In order for the editor function to begin its editing operation, however, it needs a text value to be returned from the "request-text" function. The first thing the user sees when this line runs, therefore, is the text requester. That appears backwards, compared to the way it's written:

editor (request-text)

Always remember that lines of REBOL code are evaluated from left to right. If you use the return value of one function as the argument for another function, the execution of the whole line will be held up until the necessary return value is processed.

Any number of functions can be written on a single line, with return values cascaded from one function to the next:

alert ( rejoin ( ["You chose: " ( request "Choose one:" ) ] ) )

The line above is typical of common REBOL language syntax. There are three functions: "alert", "rejoin", and "request". In order for the first alert function to complete, it needs a return value from "rejoin", which in turn needs a return value from the "request" function. The first thing the user sees, therefore, is the request function. After the user responds to the request, the selected response is rejoined with the text "You chose: ", and the joined text is displayed as an alert message. Think of it as reading "display (the following text joined together ("you chose" (an answer selected by the user))). To complete the line, the user must first answer the question.

To learn REBOL, it's essential to first memorize and recognize REBOL's many built-in function words, along with the parameters they accept as input, and the values which they return as output. When you get used to reading lines of code as functions, arguments, and return values, read from left to right, the language will quickly begin to make sense.

It should be noted that in REBOL, math expressions are evaluated from left to right like all other functions. There is no "order of precedence", as in other languages (i.e., multiplication doesn't automatically get computed before addition). To force a specific order of evaluation, enclose the functions in parentheses:

print   10  +  12  /  2      ;  22 / 2 = 11
print  (10  +  12) /  2      ;  22 / 2 = 11  (same as without parentheses)
print   10  + (12  /  2)     ;  10 + 6 = 16  (force multiplication first)

REBOL's left to right evaluation is simple and consistent. Parentheses can be used to clarify the flow of code, if ever there's confusion.

16.10 More About Conditional Evaluations

You've already seen the "if" and "either" conditional operations. Math operators are typically used to perform conditional evaluations: = < > <> (equal, less-than, greater-than, not-equal):

if now/time > 12:00 [alert "It's after noon."] 

either now/time > 8:00am [
    alert "It's time to get up!"
][
    alert "You can keep on sleeping."
]

16.10.1 Switch

The "switch" evaluation chooses between numerous functions to perform, based on multiple evaluations. Its syntax is:

switch/default (main value) [
    (value 1) [block to execute if value 1 = main value
    (value 2) [block to execute if value 2 = main value]
    (value 3) [block to execute if value 3 = main value]
    ; etc...
] [default block of code to execute if none of the values match]

You can compare as many values as you want against the main value, and run a block of code for each matching value:

favorite-day:  request-text/title "What's your favorite day of the week?"

switch/default favorite-day [
    "Monday"    [alert "Monday is the worst!  The work week begins..."]
    "Tuesday"   [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Wednesday" [alert "The hump day - the week is halfway over!"]
    "Thursday"  [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Friday"    [alert "Yay!  TGIF!"]
    "Saturday"  [alert "Of course, the weekend!"]
    "Sunday"    [alert "Of course, the weekend!"]
] [alert "You didn't type in the name of a day!"]

16.10.2 Case

You can choose between multiple evaluations of any complexity using the "case" structure. If none of the cases evaluate to true, you can use any true value to trigger a default evaluation:

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

for i 1 100 1 [
    case [
        (0 = modulo i 3) and (0 = modulo i 5) [print "fizzbuzz"]
        0 = modulo i 3 [print "fizz"]
        0 = modulo i 5 [print "buzz"]
        true [print i]
    ]
]

By default, the case evaluation automatically exits once a true evaluation is found (i.e., in the name example above, if the name contains more than one vowel, only the first vowel will be printed). To check all possible cases before ending the evaluation, use the /all refinement:

name: "brian" 
found: false
case/all [
    find name "a" [print {Your name contains the letter "a"} found: true]
    find name "e" [print {Your name contains the letter "e"} found: true]
    find name "i" [print {Your name contains the letter "i"} found: true]
    find name "o" [print {Your name contains the letter "o"} found: true]
    find name "u" [print {Your name contains the letter "u"} found: true]
    found = false [print {Your name doesn't contain any vowels!}]
]

16.10.3 Multiple Conditions: "and", "or", "all", "any"

You can check for more than one condition to be true, using the "and", "or", "all", and "any" words:

; first set some initial values all to be true:

value1: value2: value3: true

; then set some additional values all to be false:

value4: value5: value6: false

; The following prints "both true", because both the first
; condition AND the second condition are true:

either ( (value1 = true) and (value2 = true) ) [
    print "both true"
] [
    print "not both true"
]

; The following prints "both not true", because the second 
; condition is false:

either ( (value1 = true) and (value4 = true) ) [
    print "both true"
] [
    print "not both true"
]

; The following prints "either one OR the other is true"
; because the first condition is true:

either ( (value1 = true) or (value4 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "either one OR the other is true"
; because the second condition is true:

either ( (value4 = true) or (value1 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "either one OR the other is true"
; because both conditions are true:

either ( (value1 = true) or (value4 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "neither is true":

either ( (value4 = true) or (value5 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

For comparisons involving more items, you can use "any" and "all":

; The following lines both print "yes", because ALL comparisons are true.
; "All" is just shorthand for the multiple "and" evaluations:

if ((value1 = true) and (value2 = true) and (value3 = true)) [
    print "yes"
]

if all [value1 = true  value2 = true  value3 = true] [
     print "yes"
]

; The following lines both print "yes" because ANY ONE of the comparisons
; is true. "Any" is just shorthand for the multiple "or" evaluations:

if ((value1 = true) or (value4 = true) or (value5 = true)) [
    print "yes"
]

if any [value1 = true  value4 = true  value5 = true] [
     print "yes"
]

16.11 More About Loops

16.11.1 Forever

"Loop" structures provide programmatic ways to methodically repeat actions, manage program flow, and automate lengthy data processing activities. You've already seen the "foreach" loop structure. The "forever" function creates a simple repeating loop. Its syntax is:

forever [block of actions to repeat]

The following code uses a forever loop to continually check the time. It alerts the user when 60 seconds has passed. Notice the "break" function, used to stop the loop:

alarm-time: now/time + :00:60
forever [if now/time = alarm-time [alert "1 minute has passed" break]]

Here's a more interactive version using some info provided by the user. Notice how the forever loop, if evaluation, and alert arguments are indented to clarify the grouping of related parameters:

event-name: request-text/title "What do you want to be reminded of?"
seconds: to-integer request-text/title "Seconds to wait?"
alert rejoin [
    "It's now " now/time ", and you'll be alerted in " 
    seconds " seconds."
]
alarm-time: now/time + seconds
forever [
    if now/time = alarm-time [
        alert rejoin [
            "It's now "alarm-time ", and " seconds 
            " seconds have passed.  It's time for: " event-name
        ] 
        break
    ]
]

Here's a forever loop that displays/updates the current time in a GUI:

view layout [
    timer: field
    button "Start" [
        forever [
            set-face timer now/time 
            wait 1
        ]
    ]
]

16.11.2 Loop

The "loop" function allows you to repeatedly evaluate a block of code, a specified number of times:

loop 50 [print "REBOL is great!"]

16.11.3 Repeat

Like "loop", the "repeat" function allows you to repeatedly evaluate a block of code, a specified number of times. It additionally allows you to specify a counter variable which is automatically incremented each time through the loop:

repeat count 50 [print rejoin ["This is loop #: " count]]

The above code does the same thing as:

count: 0
loop 50 [
    count: count + 1
    print rejoin ["This is loop #: " count]
]

Another way to write it would be:

for i 1 50 1 [print rejoin ["This is loop #: " i]]

16.11.4 Forall and Forskip

"Forall" loops through a block, incrementing the marked index number of the series as it loops through:

some-names: ["John" "Bill" "Tom" "Mike"]

foreach name some-names [print index? some-names]  ; index doesn't change
forall some-names [print index? some-names]  ; index changes

foreach name some-names [print name]
forall some-names [print first some-names] ; same effect as line above

"Forskip" works like forall, but skips through the block, jumping a periodic number of elements on each loop:

some-names: ["John" "Bill" "Tom" "Mike"]
forskip some-names 2  [print first some-names]

16.11.5 While and Until

The "while" function repeatedly evaluates a block of code while the given condition is true. While loops are formatted as follows:

while [condition] [
    block of functions to be executed while the condition is true
]

This example counts to 5:

x: 1  ; create an initial counter value 
while [x <= 5] [
    alert to-string x 
    x: x + 1
]

In English, that code reads:

"x" initially equals 1. 
While x is less than or equal to 5, display the value of x,
then add 1 to the value of x and repeat.

Some additional "while" loop examples:

while [not request "End the program now?"] [
    alert "Select YES to end the program."
] 
; "not" reverses the value of data received from
; the user (i.e., yes becomes no and visa versa)

alert "Please select today's date" 
while [request-date <> now/date] [
    alert rejoin ["Please select TODAY's date.  It's " now/date]
]

while [request-pass <> ["username" "password"]] [
    alert "The username is 'username' and the password is 'password'"
]

"Until" loops are similar to "while" loops. They do everything in a given block, repeatedly, until the last expression in the block evaluates to true:

x: 10
until [
    print rejoin ["Counting down: " x]
    x: x - 1
    x = 0
]

16.11.6 For

The for function can loop through a series of items in a block, just like "foreach", but using a counted index. The for syntax reads like this: "Assign a (variable word) to refer to an ordinally counted sequence of numbers, begin counting on a (start number), count to an (end number), skipping by this (step number) [use the variable label to refer to each consecutive number in the count]:

for counter 1 10 1 [print counter] 
for counter 10 1 -1 [print counter] 
for counter 10 100 10 [print counter] 
for counter 1 5 .5 [print counter] 
halt

REBOL will properly increment any data type that it understands:

for timer 8:00 9:00 0:05 [print timer] 
for dimes $0.00 $1.00 $0.10 [print dimes] 
for date 1-dec-2005 25-jan-2006 8 [print date]
for alphabet #"a" #"z" 1 [prin alphabet]
halt

You can pick out indexed items from a list, using the incrementally counted numbers in a FOR loop. Just determine the length of the block using the "length?" function:

months: system/locale/months
len: length? months
for i 1 len 1 [
    print rejoin [(pick months i) " is month number " i]
]
halt

This code does the exact same thing as above, using one of the alternate syntaxes instead of "pick":

months: system/locale/months
len: length? months
for i 1 len 1 [
    print rejoin [months/:i " is month number " i]
]
halt

As you've seen, you can pick out consecutive items from a list using counter arithmetic (pick index, pick index + 1, pick index + 2). Within a for structure that uses a skip value, this concept allows you to pick out columns of data:

months: system/locale/months
len: length? months
for i 1 len 3 [
    print rejoin [
        "Months " i " to " (i + 2) " are:" newline newline
        (pick months i) newline
        (pick months (i + 1)) newline
        (pick months (i + 2)) newline 
    ]
]
halt

Here's an example that uses an "if" conditional evaluation to print only names that contain a requested string of text. This provides a functional search:

users: [
    "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"
]
search-text: request-text/title/default "Search for:" "t"
for i 1 length? users 3 [
    if find/only (pick users i) search-text [
        print rejoin [
            (pick users i) newline
            (pick users (i + 1)) newline
            (pick users (i + 2)) newline
        ]

    ]
]
halt

To clarify the syntactic difference between "for" and "foreach" loops, here is the same program as above, written using foreach:

users: [
    "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"
]
search-text: request-text/title/default "Search for:" "t"
foreach [name address phone] users [
    if find/only name search-text [
        print rejoin [
            name newline
            address newline
            phone newline
        ]

    ]
]
halt

As you can see, the for loop function is useful in almost the exact same way as foreach. It's a bit cryptic, but useful for certain common field selection algorithms which involve non-consecutive fields. You will find that both functions are used in applications of all types that deal with tabular data and lists. You'll see many use cases for each function, as this tutorial progresses - keep your eyes peeled for for and foreach.

The example below uses several loops to alert the user to feed the cat, every 6 hours between 8am and 8pm. It uses a for loop to increment the times to be alerted, a while loop to continually compare the incremented times with the current time, and a forever loop to do the same thing every day, continuously. Notice the indentation:

forever [
    for timer 8:00am 8:00pm 6:00 [
        while [now/time <= timer] [wait :00:01] 
        alert rejoin ["It's now " now/time ".  Time to feed the cat."]
    ]
]

16.12 More About Why/How Blocks are Useful

IMPORTANT: In REBOL, blocks can contain mixed data of ANY type (text and binary items, embedded lists of items (other blocks), variables, etc.):

some-items: ["item1" "item2" "item3" "item4"]
an-image: load http://rebol.com/view/bay.jpg
append some-items an-image

; "some-items" now contains 4 text strings, and an image!

; You can save that entire block of data, INCUDING THE BINARY
; IMAGE data, to your hard drive as a SIMPLE TEXT FILE: 

save/all %some-items some-items

; to load it back and use it later:

some-items: load %some-items
view [image (fifth some-items)]

Take a moment to examine the example above. REBOL's block structure works in a way that is dramatically easy to use compared to other languages and data management solutions (much more simply than most database systems). It's is a very flexible, simple, and powerful way to store data in code! The fact that blocks can hold all types of data using one simple syntactic structure is a fundamental reason it's easier to use than other programming languages and computing tools. You can save/load block code to the hard drive as a simple text file, send it in an email, display it in a GUI, compress it and transfer it to a web server to be downloaded by others, transfer it directly over a point-to-point network connection, or even convert it to XML, encrypt, and store parts of it in a secure multiuser database to be accessed by other programming languages, etc...

Remember, all programming, and computing in general, is essentially about storing, organizing, manipulating, and transferring data of some sort. REBOL makes working with all types of data very easy - just put any number of pieces of data, of any type, in between two brackets, and that data is automatically searchable, sortable, storable, transferable, and otherwise usable in your programs.

16.12.1 Evaluating Variables in Blocks: Compose, Reduce, Pick and More

You will often find that you want to refer to an item in a block by its index (position number), as in the earlier 'some-items' example:

view [image (some-items/5)]

You may not, however, always know the specific index number of the data item you want to access. For example, as you insert data items into a block, the index position of the last item changes (it increases). You can obtain the index number of the last item in a block simply by determining the number of items in the block (the position number of the last item in a block is always the same as the total number of items in the block). In the example below, that index number is assigned the variable word "last-item":

last-item: length? some-items

Now you can use that variable to pick out the last item:

view [image (pick some-items last-item)]

; In our earlier example, with 5 items in the block, the
; line above evaluates the same as:

view [image (pick some-items 5)]

You can refer to other items by adding and subtracting index numbers:

alert pick some-items (last-item - 4)

There are several other ways to do the exact same thing in REBOL. The "compose" function allows variables in parentheses to be evaluated and inserted as if they'd been typed explicitly into a code block:

view compose/deep [image (some-items/(last-item))]

; The line above appears to the interpreter as if the following
; had been typed:

view [image (some-items/5)]

The "compose" function is very useful whenever you want to refer to data at variable index positions within a block. The "reduce" function can also be used to produce the same type of evaluation. Function words in a reduced block should begin with the tick (') symbol:

view reduce ['image some-items/(last-item)]

Another way to use variable values explicitly is with the ":" format below. This code evaluates the same as the previous two examples:

view [image (some-items/:last-item)]

Think of the colon format above as the opposite of setting a variable. As you've seen, the colon symbol placed after a variable word sets the word to equal some value. A colon symbol placed before a variable word gets the value assigned to the variable, and inserts that value into the code as if it had been typed explicitly.

You can use the "index?" and "find" functions to determine the index position(s) of any data you're searching for in a block:

index-num: index? (find some-items "item4")

Any of the previous 4 formats can be used to select the data at the determined variable position:

print pick some-items index-num
print compose [some-items/(index-num)]
print reduce [some-items/(index-num)]  
; no function words are used in the block above, so no ticks are required
print some-items/:index-num

Here's an example that displays variable image data contained in a block, using a foreach loop. The "compose" function is used to include dynamically changeable data (image representations), as if that data had been typed directly into the code:

photo1: load http://rebol.com/view/bay.jpg
photo2: load http://rebol.com/view/demos/palms.jpg

; The REBOL interpreter sees the following line as if all the code
; representing the above images had been typed directly in the block:

photo-block: compose [(photo1) (photo2)]

foreach photo photo-block [view [image (photo)]]

For additional detailed information about using blocks and series functions see http://www.rebol.com/docs/core23/rebolcore-6.html.

16.13 REBOL Strings

In REBOL, a "string" is simply a series of characters. If you have experience with other programming languages, this can be one of the sticking points in learning REBOL. REBOL's solution is actually a very powerful, easy to learn and consistent with the way other operations work in the language. Proper string management simply requires a good understanding of list functions. Take a look at the following examples to see how to do a few common operations:

the-string: "abcdefghijklmnopqrstuvwxyz"

; Left String:  (get the left 7 characters of the string):

copy/part the-string 7

; Right String:  (Get the right 7 characters of the string):

copy at tail the-string -7

; Mid String 1:  (get 7 characters from the middle of the string,
; starting with the 12th character):

copy/part (at the-string 12) 7

; Mid String 2:  (get 7 characters from the middle of the string,
; starting 7 characters back from the letter "m"):

copy/part (find the-string "m") -7

; Mid String 3:  (get 7 characters from the middle of the string,
; starting 12 characters back from the letter "t"):

copy/part (skip (find the-string "t") -12) 7

; 3 different ways to get just the 7th character:

the-string/7 
pick the-string 7
seventh the-string

; Change "cde" to "123"

replace the-string "cde" "123"

; Several ways to change the 7th character to "7"

change (at the-string 7) "7"
poke the-string 7 #"7"  ; the pound symbol refers to a single character
poke the-string 7 (to-char "7")  ; another way to use single characters
print the-string

; Remove 15 characters, starting at the 3rd position:

remove/part (at the-string 3) 15
print the-string

; Insert 15 characters, starting at the 3rd position:

insert (at the-string 3) "cdefghijklmnopq"
print the-string

; Insert 3 instances of "-+" at the beginning of the string:

insert/dup head the-string "-+ " 3
print the-string

; Replace every instance of "-+ " with " ":

replace/all the-string "-+ "  " "
print the-string

; Remove spaces from a string (type "? trim" to see all its refinements!):

trim the-string
print the-string

; Get every third character from the string:

extract the-string 3

; Get the ASCII value for "c" (ASCII 99):

to-integer third the-string

; Get the character for ASCII 99 ("c"):

to-char 99

; Convert the above character value to a string value:

to-string to-char 99

; Convert any value to a string:

to-string now
to-string $2344.44
to-string to-char 99
to-string system/locale/months

; An even better way to convert values to strings:

form now
form $2344.44
form to-char 99
form system/locale/months  ; convert blocks to nicely formed strings

; Covert strings to a block of characters:

the-block: copy []
foreach item the-string [append the-block item]
probe the-block

REBOL's series functions are very versatile. Often, you can devise several ways to do the same thing:

; Remove the last part of a URL:

the-url: "http://website.com/path"
clear at the-url (index? find/last the-url "/")
print the-url

; Another way to do it:

the-url: "http://website.com/path"
print copy/part the-url (length? the-url)-(length? find/last the-url "/")

(Of course, REBOL has a built-in helper function to accomplish the above goal, directly with URLs):

the-url: http://website.com/path
print first split-path the-url

There are a number of additional functions that can be used to work specifically with string series. Run the following script for an introduction:

string-funcs: [
    build-tag checksum clean-path compress debase decode-cgi decompress
    dehex detab dirize enbase entab import-email lowercase mold parse-xml
    reform rejoin remold split-path suffix? uppercase
]    
echo %string-help.txt  ; "echo" saves console activity to a file
foreach word string-funcs [
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do compose [help (to-word word)]
]
echo off
editor at read %string-help.txt 4

See http://www.rebol.com/docs/dictionary.html and http://rebol.com/docs/core23/rebolcore-8.html for more information about the above functions.

17. More Essential Topics

17.1 Built-In Help and Online Resources

The "help" function displays required syntax for any REBOL function:

help print

"?" is a synonym for "help":

? print

The "what" function lists all built-in words:

what

Together, those two words provide a built-in reference guide for the entire core REBOL language. Here's a script that saves all the above documentation to a file, and then displays that file in a GUI text area. Give it a few seconds to run:

do %r3-gui.r3
write %words.txt "" write %help.txt ""
echo %words.txt what echo off   ; "echo" saves console activity to a file
echo %help.txt
foreach line read/lines %words.txt [
    word: first parse line none
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do rejoin ["help " word]
]
echo off
view [area (at read/string %help.txt 4)]

You can use help to search for defined words and values, when you can't remember the exact spelling of the word. Just type a portion of the word (hitting the tab key will also show a list of words for automatic word completion):

? to-         ; shows a list of all built-in type conversions
? reques      ; shows a list of built-in requester functions
? "load"      ; shows all words containing the characters "load"
? "?"         ; shows all words containing the character "?"

Here are some more examples of ways to search for useful info using help:

? datatype!   ; shows a list of built-in data types
? function!   ; shows a list of built-in functions
? native!     ; shows a list of native (compiled C code) functions
? char!       ; shows a list of built-in control characters
? tuple!      ; shows a list of built-in colors (RGB tuples)

You can view the source code for built-in "mezzanine" (non-native) functions with the "source" function. There is a huge volume of REBOL code accessible right in the interpreter, and all of the mezzanine functions were created by the language's designer, Carl Sassenrath. Studying mezzanine source is a great way to learn more about advanced REBOL code patterns:

source help
source request-text  ; only if you "do %requestors.r3"
source view
source layout

The "word browser" script is a useful tool for finding, cross referencing, and learning about all the critical functions in REBOL (made for R2, but still mostly relevant for R3, with lots of useful code examples):

write %wordbrowser.r read http://re-bol.com/wordbrowser.r
do %wordbrowser.r  ; open this with an *R2* interpreter from rebol.com

17.1.1 The REBOL System Object, and Help with GUI Widgets

"Help system" displays the contents of the REBOL system object, which contains many important settings and values. You can explore each level of the system object using path notation, like this:

? system/console/history        ; the current console session history
? system/options
? system/locale/months

You can see a list of all the built-in GUI widgets, with the script below:

REBOL [title: "View All Styles"]
do %r3-gui.r3
all-styles: find extract to-block guie/styles 2 'clicker
view [
    title "Pick a style:"
    text-list all-styles on-action [
        style-name: pick all-styles get-face face
        view/modal reduce [
            'title reform ["Example of a" style-name "style:"]
            style-name
        ]
    ]
]

You can see the facets (and all the other data/properties) of any widget using the "debug" keyword (use CTRL+C to stop the console scrolling output):

do %r3-gui.r3
view [text-list debug]

You can get further information about any facet using the "help" function ("?" is a synonym for "help"). Here's an example that displays the facet properties available in the text-list widget:

do %r3-gui.r3
help guie/styles/text-list/facets/init-size
? guie/styles/text-list/facets/init-size

It's important to note that you can SET any system value. Just use a colon, like when assigning variable values:

system/user/email: user@website.com

17.1.2 Online Documentation, The AltME Community, and Rebolforum.com

If you can't find answers to your REBOL programming questions using built-in help and resources, the first place to look is http://rebol.com/docs.html. Googling online documentation also tends to provide quick results, since the word "REBOL" is uncommon.

To ask a question directly of other REBOL developers, go to rebolforum.com. To get more involved in the community, download the program at AltME. AltME is a messaging program which makes up the most active forum of REBOL users around the world. Rebol.org maintains a searchable history of several hundred thousand posts from both the mailing list and AltME, along with a rich script archive. The REBOL user community is friendly, knowledgeable and helpful, and you will typically find answers to just about any question already in the archives. Unlike other programming communities, REBOL does not have a popular web based support forum. AltME is the primary way that REBOL developers interact. If you want to speak with others, you must download the AltME program and set up a user account (it's fast and easy to do). Just follow the instructions at http://www.rebol.org/aga-join.r.

17.2 "Compiling" REBOL Programs - Distributing Packaged Executable Files

The REBOL.exe interpreter is tiny and does not require any installation to operate properly. Most users run R3 scripts by simply downloading the interpreter and associating .r3 files to be opened automatically by the interpreter when clicked in a file explorer. When writing code, it's helpful to use an editor that can automatically run the script with the selected interpreter (you read about Jota for Android and Metapad for Windows earlier).

In some cases, you may want to distribute programs to end users, without requiring them to download the R3 interpreter separately. This saves them only one step, but for users who aren't familiar with R3, it can be a productive convenience. Saphirion has an encapper which creates APK files for Android. For other operating systems, you can follow the instructions below.

17.2.1 MS Windows Options

By packaging the interpreter, your REBOL script(s), and any supporting data file(s) into a single executable with an icon of your choice, XpackerX works like a REBOL 'compiler' that produces regular Windows programs that look and act just like those created by other compiled languages. To do that, you'll need to create a text file in the following format (save it as "template.xml"):

<?xml version="1.0"?>
<xpackerdefinition>
    <general>
        <!--shown in taskbar -->
        <appname>your_program_name</appname>
        <exepath>your_program_name.exe</exepath>
        <showextractioninfo>false</showextractioninfo>
        <!-- <iconpath>c:\icon.ico</iconpath> -->
    </general>
    <files>
        <file>
            <source>your_rebol_script.r</source>
            <destination>your_rebol_script.r</destination>
        </file>
        <file>
            <source>C:\Program Files\rebol\view\Rebol.exe</source>
            <destination>rebol.exe</destination>
        </file>
        <!--put any other data files here -->
    </files>
    <!-- $FINDEXE, $TMPRUN, $WINDIR, $PROGRAMDIR, $WINSYSDIR -->
    <onrun>$TMPRUN\rebol.exe -si $TMPRUN\your_rebol_script.r</onrun>
</xpackerdefinition>

Just download the free XpackerX program and alter the above template so that it contains the filenames you've given to your script(s) and file(s), and the correct path to your REBOL interpreter. Run XpackerX, and it'll spit out a beautifully packaged .exe file that requires no installation. Your users do not need to have REBOL installed to run this type of executable. To them it appears and runs just like any other native compiled Windows program. What actually happens is that every time your packaged .exe file runs, the REBOL interpreter and your script(s)/data file(s) are unzipped into a temporary folder on your computer. When your script is done running, the temporary folder is deleted.

Most modern compression (zip) applications have an "sfx" feature that allows you to create .exe packages from zip files. You can create a packaged REBOL .exe in the same way as XpackerX using just about any sfx packaging application (there are dozens of freeware zip/compression applications that can do this - use the one you're most familiar with).

The program "iexpress.exe" found on all versions of Windows (starting with Windows XP), is a popular choice for creating SFX files, because no additional software needs to be installed. Just click Start -> Run or Start -> Search Programs and Files, and type "iexpress". Follow the wizards to name the package, choose included files (the REBOL interpreter, scripts, images, etc.), and other options. Settings for any .exe you create with iexpress will be saved in a .sed file, which you can reload and make changes to later. This makes for quick re-"compilation" of updated scripts.

Iexpress can be particularly useful when packaging large programs made of many scripts stored in multiple folders. Use the rip.r script to package all the scripts and folders into a single file, then use a script such as the following to unpack the files and run the initial script in the program:

make-dir %/c/merchant/
write %/c/merchant/mv.rip read %mv.rip
write %/c/merchant/start.r read %start.r
write %/c/merchant/r3-view.exe read %r3-view.exe
cd %/c/merchant/
if not exists? %/C/merchant/checks/checks.txt [do %mv.rip]
launch %merchants_village.r

In your Iexpress .sed file, enter this line when prompted for the "Install Program to Launch":

r3-view.exe -si start.r

There is an explanation of how to use the NSIS install creator to make REBOL .exe's here. This is helpful if you want to adjust registry settings and make other changes to the computer system while installing your program.

17.2.2 Linux Based OSs

To create a self-extracting REBOL executable for Linux, first create a .tgz file containing all the files you want to distribute (the REBOL interpreter, your script(s), any external binary files, etc.). For the purposes of this example, name that bundle "rebol_files.tgz". Next, create a text file containing the following code, and save it as "sh_commands":

#!/bin/sh
SKIP=`awk '/^__REBOL_ARCHIVE__/ { print NR + 1; exit 0; }' $0`
tail +$SKIP $0 | tar xz    
exit 0
__REBOL_ARCHIVE__

Finally, use the following command to combine the above script file with the bundled .tgz file:

cat sh_commands rebol_files.tgz > rebol_program.sh

The above line will create a single executable file named "rebol_program.sh" that can be distributed and run by end users. The user will have to set the file permissions for rebol_program.sh to executable before running it ("chmod +x rebol_program.sh"), or execute it using the syntax "sh rebol_program.sh".

17.3 Common REBOL Errors, and How to Fix Them

Listed below are solutions to a variety of common errors you'll run into when first experimenting with REBOL:

1) "** Syntax Error: Script is missing a REBOL header" - Whenever you "do" a script that's saved as a file, it must contain at least a minimum required header at the top of the code. Just include the following text at the beginning of the script:

2) "** Syntax Error: Missing ] at end-of-script" - You'll get this error if you don't put a closing bracket at the end of a block. You'll see a similar error for unclosed parentheses and strings. The code below will give you an error, because it's missing a "]" at the end of the block:

fruits: ["apple" "orange" "pear" "grape"
print fruits

Instead it should be:

fruits: ["apple" "orange" "pear" "grape"]
print fruits

Indenting blocks helps to find and eliminate these kinds of errors.

3) "** Script Error: request expected str argument of type: string block object none" - This type of error occurs when you try to pass the wrong type of value to a function. The code below will give you an error, because REBOL automatically interprets the website variable as a URL, and the "alert" function requires a string value:

website: http://rebol.com
alert website

The code below solves the problem by converting the URL value to a string before passing it to the alert function:

website: to-string http://rebol.com
alert website

Whenever you see an error of the type "expected _____ argument of type: ___ ____ ___ ...", you need to convert your data to the appropriate type, using one of the "to-(type)" functions. Type "? to-" in the REBOL interpreter to get a list of all those functions.

4) "** Script Error: word has no value" - Miss-spellings will elicit this type of error. You'll run into it any time you try to use a word that isn't defined (either natively in the REBOL interpreter, or by you, in previous code):

wrod: "Hello world"
print word

5) If an error occurs in a "view" block, and the GUI becomes unresponsive, type "unview" at the interpreter command line and the broken GUI will be closed. To restart a stopped GUI, type "do-events". To break out of any endless loop, or to otherwise stop the execution of any errant code, just hit the [Esc] key on your keyboard.

6) "** User Error: Server error: tcp 550 Access denied - Invalid HELO name (See RFC2821 4.1.1.1)" and "** User Error: Server error: tcp -ERR Login failed.", among others, are errors that you'll see when trying to send and receive emails (to send emails, you must do the prot-smtp.r and prot-send.r scripts at https://github.com/gchiu/Rebol3/tree/master/protocols). To fix these errors, your mail server info needs to be set up in REBOL's user settings. The most common way to do that is to edit your mail account info in the graphic Viewtop or by using the "set-net" function (http://www.rebol.com/docs/words/wset-net.html). You can also set everything manually - this is how to adjust all the individual settings:

system/schemes/default/host: your.smtp.address
system/schemes/default/user: username
system/schemes/default/pass: password
system/schemes/pop/host: your.pop.address
system/user/email: your.email@site.com

7) Here's a quirk of REBOL that doesn't elicit an error, but which can cause confusing results, especially if you're familiar with other languages:

unexpected: [
    empty-variable: ""
    append empty-variable "*"
    print empty-variable
]

do unexpected
do unexpected
do unexpected

The line:

empty-variable: ""

doesn't re-initialize the variable to an empty state. Instead, every time the block is run, "empty-variable" contains the previous value. In order to set the variable back to empty, as intended, use the word "copy" as follows:

expected: [
    empty-variable: copy ""
    append empty-variable "*"
    print empty-variable
]

do expected
do expected
do expected

8) Load/Save, Read/Write, Mold, Reform, etc. - another point of confusion you may run into initially with REBOL has to do with various words that read, write, and format data. When saving data to a file on your hard drive, for example, you can use either of the words "save" or "write". "Save" is used to store data in a format more directly usable by REBOL. "Write" saves data in a raw, 'unREBOLized' form. "Load" and "read" share a comparable relationship. "Load" reads data in a way that is more automatically understood and put to use in REBOL code. "Read" opens data in exactly the format it's saved, byte for byte. Generally, data that is "save"d should also be "load"ed, and data that's "write"ed should be "read". For more information, see the following REBOL dictionary entries:

http://rebol.com/docs/words/wload.html

http://rebol.com/docs/words/wsave.html

http://rebol.com/docs/words/wread.html

http://rebol.com/docs/words/wwrite.html

Other built-in words such as "mold" and "reform" help you deal with text in ways that are either more human-readable or more natively readable by the REBOL interpreter. For a helpful explanation, see http://www.rebol.net/cookbook/recipes/0015.html.

9) Order of precedence - REBOL expressions are always evaluated from left to right, regardless of the operations involved. If you want specific mathematical operators to be evaluated first, they should either be enclosed in parenthesis or put first in the expression. For example, to the REBOL interpreter:

2 + 4 * 6

is the same as:

(2 + 4) * 6  ; the left side is evaluated first

== 6 * 6

== 36

This is contrary to other familiar evaluation rules. In many languages, for example, multiplication is typically handled before addition. So, the same expression:

2 + 4 * 6

is treated as:

2 + (4 * 6)  ; the multiplication operator is evaluated first

== 2 + 24

== 26

Just remember, evaluation is always left to right, without exception.

10) You may run into problems when copying/pasting interactive console scripts directly into the REBOL interpreter, especially when the code contains functions such as "ask", which require a response from the user before the remainder of the script is evaluated (each line of the script simply runs, as the pasting operation completes, without any response from the user, leaving necessary variables unassigned). To fix such interactivity problems when copying/pasting console code into the interpreter, simply wrap the entire script in square brackets and then "do" that block: do [...your full script code...]. This will force the entire script to be loaded before any of the code is evaluated. If you want to run the code several times, simply assign it a word label, and then run the word label as many times as needed: do x: [...your full script code...] do x do x do x .... This saves you from having to paste the code more than once. Another effective option, especially with large scripts, is to run the code from the clipboard using "do read clipboard://". This performs much faster than watching large amounts of text paste into the console.

17.3.1 Trapping Errors

There are several simple ways to keep your program from crashing when an error occurs. The words "error?" and "try" together provide a way to check for and handle expected error situations. For example, if no Internet connection is available, the code below will crash abruptly with an error:

html: read http://rebol.com

The adjusted code below will handle the error more gracefully:

if error? try [html: read http://rebol.com] [
    alert "Unavailable."
]

The word "attempt" is an alternative to the "error? try" routine. It returns the evaluated contents of a given block if it succeeds. Otherwise it returns "none":

if not attempt [html: read http://rebol.com] [
    alert "Unavailable."
]

To clarify, "error? try [block]" evaluates to true if the block produces an error, and "attempt [block]" evaluates to false if the block produces an error.

For a complete explanation of REBOL error codes, see: http://www.rebol.com/docs/core23/rebolcore-17.html.

17.4 Creating Apps on Platforms That Don't Support GUI Interfaces

Open source REBOL is currently being ported to many platforms that don't yet provide GUI support. Often, CGI applications can be run in the default Internet browsers on most new operating systems, without any changes. Many of the REBOL CGI programs in this tutorial have been running in commercial environments on iPhone and Android browsers since the earliest inceptions of those platforms.

On systems which don't currently have the REBOL GUI system ported, and for programs which aren't appropriately designed as CGI scripts, core console interactions can be used to provide a practical user interface. The script below is a simple replacement for GUIs that collect text input from fields and drop down lists. Just specify a block of labels, and a block of default values for each field ('answers). If the 'answers block contains a nested block, the values in that block will be used as items in a list selector (so that users don't need to type a long response and/or can select from a pre-defined list of options - similar to a GUI text-list or drop-down selector). This scripts works in all versions of REBOL, both R2 and R3, with only minimal console support:

REBOL [title: "Textual User Interface"]
; ------------------------------------------------------------------------
labels: ["First Name" "Last Name" "Favorite Color" "Address" "Phone"]
answers: copy ["" "" ["Red" "Green" "Blue" "Tan" "Black"] "" ""]
; ------------------------------------------------------------------------
; if system/build/date > 1-jan-2011 [
    newpage: copy {} loop 50 [append newpage "^/"]
; ]
if (length? labels) <> (length? answers) [
    print join newpage "'Labels and 'answers blocks must be equal length."
    halt
]
len: length? labels
lngth: 0
spaces: "    "
foreach label labels [
    if (l: length? label) > lngth [lngth: l]
]
pad: func [strng] [
    insert/dup tail str: join copy strng "" " " lngth
    join copy/part str (lngth) spaces
]
forever [
    prin newpage
    repeat i len [
        either ((answers/:i = "") or ((type? answers/:i) = block!)) [
            ans: ""
        ][
            ans: answers/:i
        ]
        prin rejoin [i ")  " pad labels/:i "|" spaces ans newline]
    ]
    prin rejoin [newline (len + 1) ")  SUBMIT"]
    choice: ask {^/^/}
    either error? try [num: to-integer choice] [] [
        either block? drop-down: answers/:num [
            print ""
            repeat i l: length? drop-down [
                prin rejoin [i ")  " pad form drop-down/:i newline]
            ]
            prin rejoin [(l + 1) ")  " pad "Other" newline]
            drop-choice: ask rejoin [{^/Select } labels/:num {:  }]
            either error? try [d-num: to-integer drop-choice] [] [
                either d-num = (l + 1) [
                    if "" <> resp: ask rejoin [
                        {^/Enter } labels/:num {:  }
                    ] [answers/:num: resp]
                ][
                    chosen: pick drop-down d-num
                    if ((chosen <> none) and (chosen <> (l + 1))) [
                        answers/:num: chosen
                    ]
                ]
            ]
        ][
            if ((num > 0) and (num <= (len + 1))) [
                either num = (len + 1) [
                    prin newpage probe answers halt  ; END ROUTINE
                ][
                    either answers/:num = "" [
                        ans: ""
                    ][
                        ans: answers/:num
                    ]
                    write clipboard:// ans
                    line: copy {}
                    loop ((length? labels/:num) + 1) [append line "-"]
                    answers/:num: ask rejoin [
                        newpage labels/:num ":  " ans "^/" line "^/^/"
                    ]
                ]
            ]
        ]
    ]
]

Console applications tend to be shorter to code than GUI apps which are functionally equivalent. Running simple console scripts on mobile devices and other machines with low powered processors can provide quicker performance than loading and updating GUI displays for simple text input and output. The beginnings of complex GUI desktop apps often start life as simple console scripts, and you'll find that when creating functional data management utilities which don't require pretty graphic displays, console scripts are quick to write, and provide all the interactivity required to perform most types of useful business input and output. And of course, even when a program doesn't sport a nice looking GUI interface, all the fundamentally useful data processing power enabled by the core programming language is still available. Try converting some of the other GUI apps in this tutorial, so that they run as console apps.

18. REAL WORLD CASE STUDIES - Learning To Think In Code

At this point, you've seen most essential bits of REBOL language syntax, but you're probably still saying to yourself "that's great ... but, how do I write a complete program that does ______". To materialize any working software from an imagined design, it's obviously essential to know which language constructs are available to build pieces of a program, but "thinking in code" is just as much about organizing those bits into larger structures, knowing where to begin, and being able to break down the process into a manageable, repeatable routine. This section is intended to provide some general understanding about how to convert human design concepts into REBOL code, and how to organize your work process to approach any unique situation. A number of case studies are presented to provide insight as to how specific real life situations were satisfied.

18 A Generalized Approach Using Outlines and Pseudo Code

Software virtually never springs to life in any sort of initially finalized form. It typically evolves through multiple revisions, and often develops in directions originally unanticipated. There's no perfect process to achieve final designs from scratch, but certain approaches typically do prove helpful. Having a plan of attack is what gets you started writing line 1 of your code, and it's what eventually delivers a working piece of software to your user's machines. Here's a generalized routine to consider:

  1. Start with a detailed definition of what the application should do, in human terms. You won't get anywhere in the design process until you can describe some form of imagined final program. Write down your explanation and flesh out the details of the imaginary program as much as possible. Include as much detail as possible: what should the program look like, how will the user interact with it, what sort of data will it take in, process, and return, etc.
  2. Determine a list of general code and data structures related to each of the 'human-described' program goals above. Take stock of any general code patterns which relate to the operation of each imagined program component. Think about how the user will get data into and out of the program. Will the user work with a desktop GUI window, web forms that connect to a CGI script, or directly via command line interactions in the interpreter console? Consider how the data used in the program can be represented in code, organized, and manipulated. What types of data will be involved (text types such as strings, time values, or URLs, binary types such as images and sounds, etc.). Could the program code potentially make use of variables, block/series structures and functions, or object structures? Will the data be stored in local files, in a remote database, or just in temporary memory? Think of how the program will flow from one operation to another. How will pieces of data need to be sorted, grouped and related to one another, what types of conditional and looping operations need to be performed, what types of repeated functions need to be isolated and codified? Consider everything which is intended to happen in the imagined piece of software, and start thinking, "_this_ is how I could potentially accomplish _that_, in code...".
  3. Begin writing a code outline. It's often easiest to do this by outlining a user interface, but a flow chart of operations can be helpful too. The idea here is to begin writing a generalized code container for your working program. At this point, the outline can be filled with simple natural language PSEUDO CODE that describes how actual code can be organized. Starting with a user interface outline is especially helpful because it provides a starting point to actually write large code structures, and it forces you to deal with how the program will handle the input, manipulation, and output of data. Simple structures such as "view layout [button [which does this when clicked...]]", "block: [with labels and sub-blocks organized like this...], "function: (which loops through this block and saves these elements to another variable...)" can be fleshed out later with complete code.
  4. Finally, move on to replacing pseudo code with actual working code. This isn't nearly as hard once you've completed the previous steps. A language dictionary/guide with cross referenced functions is very helpful at this stage. And once you're really familiar with all the available constructs in the language, all you'll likely need is an occasional syntax reminder from REBOL's built-in help. Eventually, you'll pass through the other design stages much more intuitively, and get to/through this stage very quickly.
  5. As a last step, debug your working code and add/change functionality as you test and use the program.

The basic plan of attack is to always explain to yourself what the intended program should do, in human terms, and then think through how all required code structures must be organized to accomplish that goal. As an imagined program takes shape, organize your work flow using a top down approach: imagined concept -> general outline -> pseudo code description / thought process -> working code -> finished code.

The majority of code you write will flow from one user input, data definition or internal function to the next. Begin mapping out all the things that need to "happen" in the program, and the info that needs to be manipulated along the way, in order for those things to happen, from beginning to end. The process of writing an outline can be helped by thinking of how the program must begin, and what must be done before the user starts to interact with the application. Think of any data or actions that need to be defined before the program starts. Then think of what must happen to accommodate each possible interaction the user might choose. In some cases, for example, all possible actions may occur as a result of the user clicking various GUI widgets. That should elicit the thought of certain bits of GUI code structure, and you can begin writing an outline to design a GUI interface. If you imagine an online CGI application, the user will likely work with forms on a web page. You can begin to design HTML forms, which will inevitably lead to specifying the variables that are passed to the CGI app. If your program will run as a simple command line app, the user may respond to text questions. Again, some code from the example applications in this tutorial should come to mind, and you can begin to form a coding structure that enables the general user interface and work flow.

Sometimes it's simpler to begin thinking through a development process using console interactions. It tends to be easier to develop a CGI application if you've got working console versions of various parts of the program. Whatever your conceived interface, think of all the choices the user can make at any given time, and provide a user interface component to allow for those choices. Then think of all the operations the computer must perform to react to each user choice, and describe what must happen in the code.

As you tackle each line of code, use natural language pseudo code to organize your thoughts. For example, if you imagine a button in a GUI interface doing something for your user, you don't need to immediately write the REBOL code that the button runs. Initially, just write a description of what you want the button to do. The same is true for functions and other chunks of code. As you flesh out your outline, describe the language elements and coding thought you conceive to perform various actions or to represent various data structures. The point of writing pseudo code is to keep clearly focused on the overall design of the program, at every stage of the development process. Doing that helps you to avoid getting lost in the nitty gritty syntax details of actual code. It's easy to lose sight of the big picture whenever you get involved in writing each line of code.

As you convert you pseudo code thoughts to language syntax, remember that most actions in a program occur as a result of conditional evaluations (if this happens, do this...), loops, or linear flow from one action to the next. If you're going to perform certain actions multiple times or cycle through lists of data, you'll likely need to run through some loops. If you need to work with changeable data, you'll need to define some variable words, and you'll probably need to pass them to functions to process the data. Think in those general terms first. Create a list of data and functions that are required, and put them into an order that makes the program structure build and flow from one definition, condition, loop, GUI element, action, etc., to the next.

What follows are a number of case studies that describe how I've approached various programming tasks in a productive way. Each example traces my train of thought from the organizational process through the completed code.

(to be continued...)

See http://business-programming.com for case studies and much more about programming with REBOL (an older, but far more mature version about R2).

Copyright Nick Antonaccio 2013, All Rights Reserved