Articles Library: 18 Articles
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

REBOL programming

tags: tutorial

REBOL

Rebol Programming Tutorial

By: Nick Antonaccio
Updated: 1-5-2007
Learn to program quickly and easily in the Rebol development environment.

Contents:

1. Introducing Rebol
1.1 Quick Overview
1.2 An Amazing Tour of Rebol Features and Benefits
2. Getting Started: Installing Rebol, Hello World, and 5 Short Examples
3. A Quick Summary of the Rebol Language
3.1 Built-In Functions and Basic Syntax
3.2 More Basics: Word Assignment, I/O, Files, Built-In Data Types and Native Protocols
3.3 Built-in Help and Resources
3.4 GUIs
3.5 Blocks and Series
3.6 Conditions
3.7 Loops
3.8 User Defined Functions
4. More Useful Topics
4.1 Binary Embedding and Compression
4.2 Running Command Line Applications
4.3 Saving and Running Rebol Scripts
4.4 "Compiling" Rebol Programs - Distributing Packaged .EXE Files
4.5 Responding to Special Events in a GUI
4.6 Common Errors
5. Examples
5.1 Little Email Client
5.2 Simple Web Page Editor
5.3 Simple Menu Example
5.4 FTP Chat Room
5.5 Image Effector
5.6 Guitar Chord Diagram Maker
5.7 Listview Database Front End
5.8 Peer-to-Peer Instant Messenger
5.9 Textris
6. Additional Topics
6.1 2D Drawing, Graphics, and Animation
6.2 3D Graphics with r3D
6.3 Multitasking
6.4 Using DLLs and Shared Code Files in Rebol
6.5 Web Programming and the CGI Interface
6.6 Rebol as a Browser Plugin
6.7 MySQL and databases
6.8 Parsing
6.9 Objects
6.10 Rebcode
6.11 Useful Rebol Tools
6.12 6 Rebol Flavors
6.13 Learning More About Rebol

1. Introducing Rebol

1.1 Quick Overview

What is Rebol? What does it do? Who might benefit from learning it? What's so great about it? Here's a quick overview:

  • Rebol can be thought of as: (1) a rich development environment that looks and operates the same way on over 40 operating systems (2) a light scripting language for average computer users, and (3) a simple multi-platform utility application.
  • Rebol can be used immediately as a lightweight file manager, text editor, calculator, email client, ftp client, news reader, image viewer/editor, and more.
  • Rebol can be used as a framework for desktop software development and for distributed network application development, for web site development using the CGI interface, and to create applications that run in a browser plugin. To accomplish tasks that typically require varied tools such as Visual Basic, PHP, Perl, Javascript, Flash, C, C++, Python, Java, Ruby, wxWidgets and related frameworks, as well as a variety of utility applications, Rebol covers the same ground with one simple and consistent paradigm.
  • Rebol includes GUI, network, graphics, sound, image editing, data management, math, parsing, compression, CGI decoding, secure network services, and other functions built-in. No external modules, toolkits, or IDEs are required for any essential functionality. Other features such as a built-in text/code editor and a graphical browser for Rebol's online repository of open source applications are also built-in.
  • Rebol is ultra compact. Its uncompressed file size is about 1/2 Meg on most platforms. It can be downloaded, installed, and put to use on all supported operating systems in less than a minute. No other comparable tool is as small or as easy to get working.
  • Rebol is easy enough for absolute beginners and average computer users to operate immediately, but powerful enough for a wide variety of complex professional work.
  • Rebol has useful built-in help for all available functions and language constructs.
  • Rebol is available in both free and supported commercial versions. The free version can be used to create commercial applications, with very few license restrictions.
  • Rebol has a facility for ultra fast performance using "Rebcode", which can be optimized like assembly language, but works the same way across all supported hardware and operating systems (using the exact same code).
  • Rebol is a modern tool for application development, supporting object oriented design and other organizational approaches. Do to its code dialecting approach, Rebol is often dramatically more productive than other development tools. Rebol code is typically much shorter and more readable than language implementations in other development environments. 1 line of natural language code can create a complete, functional GUI application, and that sort of short, simple, powerful syntax can be implemented to easily enable complex tasks in virtually any area of computing work. Because the syntax required to use potential Rebol dialects is so simple, the learning curve required to get real work done in Rebol is much easier than other development environments.
  • Rebol is small, practical, portable, extremely productive, and different than the typical mess of modern computing tools.

1.2 An Amazing Tour of Rebol Features and Benefits

(Skip this section if you want to jump right into coding with Rebol. The following paragraphs simply explain in detail what Rebol is, and what the main reasons are to learn and use it).

Rebol is an extraordinary tool for software development and for general computing productivity, created by Carl Sassenrath. It provides a single, simple, multi-platform solution for a wide variety of common computing tasks. Absolute beginners can use the Rebol interpreter immediately as a lightweight file manager, text editor, calculator, email client, ftp client, news reader, image viewer/editor, and more. Average computer users can learn Rebol's super-simple scripting language to quickly create small customized programs that accomplish personally useful and interesting computing tasks. Professionals can use Rebol as a robust and uncompromisingly productive development environment for demanding commercial work. Rebol can be used to build real-world desktop and web site applications of all types, with modern graphics, network functionality, database connectivity, and much more. With unique versatility, Rebol performs equally well as a desktop development environment for PC, Mac, Linux, BSD, Windows CE, QNX, Solaris, and other operating systems (use it to create typical windowed user programs), as an architecture for distributed LAN software (use it to create software that communicates across a network, allowing multiple users to work with shared data, to create networked games, etc.), as a tool for rich in-browser application development (the Rebol browser plugin allows complete graphical desktop applications to run immediately in a web browser (i.e., as part of a web page)), and as a CGI solution for web site development on just about any platform (use it to make your web site dynamically interactive, to process and display data as needed by each unique visitor, to manage email, to keep pages updated, etc.). Learning to write simple Rebol scripts requires about as much effort as learning to use a typical piece of modern user software. But with greater potential functionality than any single user application, Rebol can easily replace a hard drive full of bloated software with a single lightweight program that allows you to do things with your computer, your network, and your web site, when you can't find the required functionality in existing pre-made software packages. Using Rebol to achieve specific personal computing goals leverages your future effort and time by enabling a powerful development tool that is more exactly configurable to your unique productivity requirements and creative interests. It's a great way to get started with software development, and it's a uniquely productive and versatile tool for experienced coders.

Rebol's blend of capability, compact size, ease of use, and cross-platform functionality make it a remarkably versatile solution for a broad scope of computing problems. Even more significant is the efficient and effective way it embodies and implements those solutions. The free Rebol interpreter is trivially small to download (<600k on most operating systems) and can be used immediately, with or without installation (it's also available as a plugin for popular browsers). Rebol code works identically on over 40 operating systems, and with fewer gotchas than other cross-platform computing solutions. Rebol's multimedia functions are easy to use, and it's built-in graphic tools are powerful enough to handle 3D animation without any external requirements (no OpenGL, DirectX, etc. needed). Common network protocols and data types are natively usable in Rebol without any preparation by the programmer, and the storage/manipulation/transfer of all data is managed by a single, simple, ubiquitous code structure (arrays, lists, tables, and other structures of text and binary data are all simply stored as "blocks"). Data processing of all types is easy to perform with Rebol's native list management functions. Built-in help is available for any language construct, and the interpreter itself functions as a graphic client to Rebol's online repository of shared code (hundreds of useful applications and their source codes are immediately available directly within the interpreter).

Rebol handles familiar, traditional programming techniques, and adds some concise coding facilities that dramatically increase user productivity. Rebol is designed to allow for a new paradigm in natural language dialect ("DSL") creation, which is a powerful approach to simplifying common tasks with short syntax patterns. The built-in parsing, drawing, image editing, and GUI dialects are uniquely elegant and simple-to-use realizations of that ability. In fact, there's absolutely no simpler solution for cross-platform GUI creation, anywhere (you can create complete, functional graphic applications with 1 line of code). But that just scratches the surface. Rebol's ability to simplify other difficult computing tasks with dramatically small and usable code dialects is applicable to virtually every area of computing. Only the most basic understanding of Rebol language syntax, and very short code snippets, are required to accomplish difficult objectives using high level code dialects. Detailed control of underlying code is always accessible to advanced users. As a result of the ability to build upon layers of complexity using unusually simple syntax structures, Rebol code for just about any given task tends to be much shorter and more readable than that of other development tools.

Rebol is among the easiest scripting languages to get started with, but despite the quick learning curve and compact file size, it is not a limited learning environment or a simple software toy. It is a deep, rich, and powerful tool, suitable for a broad scope of industrial work. At the far end of the usability spectrum, Rebol's low level "rebcode" VM acts as a sort of cross-platform assembly language that can dramatically improve the processing speed of computational tasks which benefit from tight optimization. Rebol allows for elements of object oriented (prototype based) design and other modern organizational approaches, but it's really the practical, useful value of Rebol's whole package of features that is it's greatest strength. In many cases, Rebol is versatile and powerful enough to gracefully replace such favorite tools as Python, Perl, Visual Basic, PHP, Java, Ruby, C, C++, Javascript, Flash, wxWidgets and related frameworks, as well as a broad variety of utility applications for users. Because Rebol can work on just about any machine as a client or server framework, as a user interface framework, as an in-browser application framework, as a web site development solution, etc., it covers much of the same ground as those other tools. Yet, all its features exist inside one tiny downloadable executable that anyone can get running, on just about any computer, in less than a minute. Because so many essential computing elements all interact natively and naturally without requiring any third party toolkits, even average computer users with absolutely no coding experience can learn to create real, useful and powerful Rebol applications in a day.

To be productive with just one simple paradigm that efficiently handles virtually all modern computing problem domains, Rebol is a unique and refreshingly straightforward solution. Whether you're new to programming, or if you've struggled to be productive with other development tools, or if you've ever spent too much time integrating numerous pieces of software just to complete a simple computing task, or if you've ever found the language and/or data syntax of other development environments to be unwieldy, or if you've ever been frustrated by the slow development cycle, lack of portability, large download size, or diverse bloated IDEs of other modern tools, then you'll love Rebol. If the functional features don't pique your curiosity, Rebol is also interesting to study simply because it's ingenious, thought provoking, and different than other modern development technology. Rebol is easy to learn and use, small, practical, portable, and productive. Especially for simple projects that should be easy to complete, to get real computing jobs done, Rebol just works.

This tutorial demonstrates essential elements of Rebol programming in a concise format. For those who are interested, this document provides a working knowledge of Rebol's main features in less than 100 pages. If you're an absolute beginner who can't imagine "programming" your own applications, see Rebol Programming for the Absolute Beginner. It covers many of the same examples as this text, but with more explanation of each topic and a less technical learning curve. That tutorial also includes an in-depth section of case studies intended to teach beginning coders how to think more fundamentally about approaching any general programming task. If you're not sure where to start, try this text first, then pick up the absolute beginners tutorial above if you need additional guidance and perspective.

2. Getting Started: Installing Rebol, Hello World, and 5 Short Examples

The Rebol interpreter is a program that runs on your computer. It translates written text in the Rebol language syntax ("source code") to instructions the computer understands. To get the free Rebol interpreter for Microsoft Windows, go to http://rebol.com/view-platforms.htmland download the view.exe file for Windows - just click it with your mouse and save it to your hard drive. If you want to run Rebol on any other operating system (Macintosh, Linux, etc.), just select, download and run the correct file for your computer. It works the same way on every operating system.

Once you've got the Rebol interpreter downloaded and running on your computer, click the "Console" icon, and you're ready to start typing in Rebol programs. To create your first program, type the following line into the Rebol interpreter, and then press the [Enter] (return) key on your keyboard:

alert "Hello world!"

Before going any further, give it a try. Download Rebol, and type in the code above to see how it works. It's simple and it only takes a moment. To benefit from this tutorial, type or paste each code example into the Rebol interpreter to see what happens.

To whet your appetite, here are 5 tiny GUI programs that demonstrate just how potent Rebol code is. The first example is a fully functional web page editor. You can use it to edit html pages and other text files on any web site ftp server:

view layout [
    h1 "Enter your ftp info, then click 'load' to download and edit a file:"
    p: field 600 "ftp://user:pass@website.com/public_html/filename.html"
    h: area 600x440 across 
    btn "Load" [h/text: read (to-url p/text) show h]
    btn "Save" [write (to-url p/text) h/text]
]

Here's a classic graphic sliding tile game:

view center-face layout [
    origin 0x0 space 0x0 across 
    style p button 60x60 [
        if not find [0x60 60x0 0x-60 -60x0] 
            face/offset - empty/offset [exit]
        temp: face/offset face/offset: empty/offset 
            empty/offset: temp
    ]
    p "A" p "B" p "C" p "D" return p "E" p "F" p "G" p "H" return
    p "I" p "J" p "K" p "L" return p "M" p "N" p "O"  
    empty: p 200.200.200 edge [size: 0]
]

Here's little painting program:

view layout [
    h1 "Paint with the mouse:"
    scrn: box black 400x400 feel [
        engage: func [face action event] [
            if find [down over] action [
                append scrn/effect/draw event/offset show scrn
            ]
            if action = 'up [append scrn/effect/draw 'line]
        ]
    ] effect [draw [line]]
    btn "Save" [
        save/png %/c/painting.png to-image layout [
            origin 0x0 box black 400x400 effect pick get scrn 9
        ] alert "Saved to C:\painting.png"
    ]
    btn "Clear" [scrn/effect/draw: copy [line] show scrn]
]

Here's a short program that uses Rebol's parsing and networking abilities to display the current WAN and LAN IP addresses of your computer:

parse read http://whatismyip.com [thru <title> copy my-ip to </title>]
parse my-ip [thru "-" copy stripped-ip to end]
alert to-string rejoin ["WAN: " stripped-ip " ---- LAN: "
    read join dns:// read dns://]

Here's a little email client you can use to read and send emails to/from any pop/smtp server (edit the first line so it contains your personal email account info):

set-net [user:pass@website.com smtp.website.com pop.website.com]
view layout[
    h1 "Send:" a: field "user@website.com" s: field "Subject" b: area
    btn "Send"[
        send/subject to-email a/text b/text s/text alert "Sent"
    ]
    h1 "Read:" m: field "pop://user:pass@website.com"
    btn "Read"[editor read to-url m/text]
]

Try pasting those examples into the Rebol interpreter to see what just a little Rebol code can do (those programs take up a total of less than 1 printed page of code). Before the end of this tutorial you'll know exactly how they all work, and much more...

3. A Quick Summary of the Rebol Language

3.1 Built-In Functions and Basic Syntax

To use Rebol, you need to learn how to use "functions". Functions are words that perform actions. Function words are followed by data parameters ("arguments"). Paste these functions into the Rebol interpreter to see how they work:

alert "Alert is a function. This text is its parameter."
request "Are you having fun yet?"
editor "Edit this text."
browse http://rebol.com

Some functions don't require any data parameters, but do produce "return" values. Try these functions in the interpreter. They each return a value entered by the user:

request-pass
request-date
request-color
request-file

Many functions have optional or limited parameters/return values. These options ("refinements") are specified by the "/" symbol:

request-pass/only
request-pass/user "username"
request-pass/title "The 'title' refinement sets this header text."
request-pass/offset/title 10x100 "'offset' repositions the requester."

Some functions take multiple arguments. The "rejoin" function returns the joined ("concatenated") text arguments inside brackets:

rejoin ["Hello " "there" "!"]

IMPORTANT: Return values from one function can be used as arguments for other functions. Here, the value returned by the "rejoin" function is passed as a parameter to the "alert" function:

alert rejoin ["Hello " "there" "!"]

Here, the value returned by the "request-text" function is passed as an argument to the "editor" function:

editor request-text

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

Parentheses can be used to clarify which expressions are evaluated and passed as parameters to other functions. The parenthesized line below is exactly the same as the line above:

alert (rejoin ["You chose: " (request "Choose one:")])

Understanding the above line is important - it's typical of Rebol language syntax. There are three functions in that line: "alert", "rejoin", and "request". The "alert" function is the first to be executed ("evaluated"). It can't complete its evaluation, though, until a value is returned by the rejoin function, which in turn needs a value returned by the request function. So, when you paste the above code into the interpreter, the first thing you see is a request box (the last function in that line of code). The return value from that function is required before any other evaluations can be completed. After you respond to the request, the selected response is rejoined with the text "You chose: ", and the joined text is then displayed as an alert message. The parentheses in the above example are not required - they are only for human readability. Rebol knows how many parameters to expect for any function, and it will simply move along a line of code, grouping together each function word with its appropriate parameters, evaluating functions from left to right on the line.

If that seems confusing, just remember to keep an eye out for function words and their associated parameters. Learning to recognise and use Rebol's built-in function words is an important part of the initial learning curve. Getting used to using several function words in a row is also an important part of the process. That's all.

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. Text after a semicolon and before a new line is treated as a comment (ignored entirely by the interpreter). The code below works exactly the same as the previous example:

alert rejoin [
    "You chose: "               ; 1st piece of joined data
    (request "Choose one:")     ; 2nd piece of joined data
]

ONE CAVEAT: parameters for any given function need to be included on the same line, so the following will not work (the rejoin arguments' opening brackets must be on the same line as the rejoin function):

alert rejoin 
[
    "You chose: "               ; 1st piece of joined data
    (request "Choose one:")     ; 2nd piece of joined data
]

Math expressions are evaluated from left to right like all other functions. To force a specific order of evaluation, enclose the functions in parentheses:

print 10 + 12 / 2       ; 11 
print 10 + (12 / 2)     ; 16

If you're completely new to programming, this may all seem a bit technical. Just follow along and paste every example into the Rebol interpreter. Seeing the code work is essential. Rebol is simpler to learn than any other programming language, and using functions is an important part of its language style. Whether you're new to programming, or just new to Rebol, you'll get used to the syntax very quickly. If you get done with this tutorial and still feel lost, take a look at Rebol Programming for the Absolute Beginner. It's much more descriptive and explanatory for average computer users and beginners.

3.2 More Basics: Word Assignment, I/O, Files, Built-In Data Types and Native Protocols

In Rebol, the colon (":") symbol is used to assign word labels ("variables") to values. In the example below, the word "filename" is assigned to the value returned by the request-file function:

filename: request-file

Now, the word label "filename" can be used anywhere, to represent the file selected above:

alert rejoin ["You chose " filename]

The "ask" function is a simple way to get some text data from a user:

name: (ask "What is your name? ")

The "print" function is a simple way to display text data at the interpreter's command line:

print rejoin ["Good to meet you " name]

The "prin" function prints consecutive text elements right next to each other (not on consecutive lines):

prin "All " prin "on " prin "one " print "line." print "On another."

Multi-line text is enclosed in curly braces ("{}"):

print {
    Line 1
    Line 2
    Line 3
}

The "write" function saves data to a file. It takes two parameters: a file name to write to, and some data to write to that file.

write %/C/YOURNAME.txt name

NOTE: 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/windows/notepad.exe" refers to "C:\Windows\Notepad.exe" in Windows. Rebol converts that syntax to the appropriate operating system format, so that your code can be written once and used on every operating system, without alteration.

You can write data to a web site using the exact same write syntax (be sure to use an appropriate username and password for your web site ftp account):

write ftp://user:pass@website.com/name.txt name

The "read" function reads data from a file:

print (read %/C/YOURNAME.txt)

Rebol has a built-in text editor that can also read, write, and manipulate text data:

editor %/c/YOURNAME.txt

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:

editor read http://rebol.com
editor read ftp://user:pass@website.com/public_html/index.html
print read dns://msn.com
print read whois://yahoo.com@rs.internic.net
editor read pop://user:pass@website.com      ; reads all emails in
                                             ; the inbox
editor read clipboard://                     ; reads data that has
                                             ; been copied/pasted to
                                             ; the OS clipboard

Transferring data between devices connected by any supported protocol is easy:

; 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

; read a page from one web site, and write it to another:
write ftp://user:pass@website2.com (read http://website1.com)

Sending email is just as easy, using a similar syntax:

send user@website.com "Hello"
send user@website.com (read file.txt)        ; sends an email, with
                                             ; file.txt as the body

The "/binary" modifier is used to read or write binary (non-text) data:

write/binary %/c/bay.jpg read/binary http://rebol.com/view/bay.jpg

For clarification, the first parameter of the function above is "%/c/bay.jpg". The second parameter is the binary data read from http://rebol.com/view/bay.jpg:

write/binary (%/c/bay.jpg) (read/binary http://rebol.com/view/bay.jpg)

The "load" and "save" functions read, write, and in the process, automatically format certain data types for use in Rebol:

picture: load http://rebol.com/view/bay.jpg
save/png %/c/picture.png picture
view layout [image load %/c/picture.png]

Rebol also 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 + :30                     ; print 30 minutes from now
print now - 10                      ; print 10 days ago
print 23x54 + 19x31                 ; easily add coordinate pairs
print 192.168.1.1 + 000.000.000.37  ; easily increment ip addresses

Data types can be specifically assigned ("cast") using "to-(type)" functions:

; this example creates and adds two coordinate pairs 
; 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]

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.

3.3 Built-in Help and Resources

The "help" function displays required syntax for any Rebol function:

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.

Additionally, the Rebol desktop that appears by default when you run the view.exe interpreter can be used as a gateway into a world of "Rebsites" that developers use to share useful code. Surfing the public rebsites is a great way to explore the language more deeply. All of the code in the rebol.org archive, and much more, is available on the rebsites. When typing at the interpreter console, the "desktop" function brings up the Rebol desktop:

desktop

Click the "REBOL" or "Public" folders to see hundreds of interesting demos and useful examples. Source code for every example is available by right-clicking individual program icons and selecting "edit". You can learn volumes about the Rebol language using only the resources built directly into the 600k interpreter!

For detailed, categorized, and cross-referenced information about built-in functions, see the Rebol Dictionary rebsite, found in the Rebol desktop folder REBOL->Tools (an html version is also available at http://www.rebol.com/docs/dictionary.html).

3.4 GUIs

Graphic user interfaces ("GUI"s) are easier to create in Rebol than in any other language. The functions "view" and "layout" are used together to display GUIs. The parameters passed to the layout function are enclosed in brackets. Those brackets can include identifiers for all types of GUI elements ("widgets"):

view layout [btn]  ; creates a GUI with a button

view layout [field]  ; a text input field

view layout [text "Rebol is really pretty easy to program"]

view layout [text-list]  ; a selection list

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

You can adjust the characteristics of a widget by following it with appropriate modifiers:

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

A variety of functions are available to control the alignment, spacing, and size of elements in a GUI layout:

view layout/size [
    across
    btn "side" btn "by" btn "side"
    return
    btn "on the next line"
    tab 
    btn "over a bit"
    tab
    btn "over more"
    below
    btn 160 "underneath" btn 160 "one" btn 160 "another"
    at 359x256 
    btn "at 359x256"
] 500x350

IMPORTANT: You can have widgets perform functions when clicked, or when otherwise activated. Just put the functions inside another set of brackets after the widget. This is how you get your GUIs to 'do something' (using the fundamentals introduced in the previous section):

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

view layout [btn "Display Rebol.com Html" [editor read http://rebol.com]]

view layout [btn "Write current time to HD" [write %time.txt now/time]]

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

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

To react to right-button mouse clicks on a widget, put the functions to be performed inside a second set of brackets after the widget:

view layout [
    btn "Right Click Me" [alert "left click"][alert "right click"]
]

You can assign keyboard shortcuts (keystrokes) to any widget, so that pressing the key reacts the same way as activating the GUI widget:

view layout [
    btn "Click me or press the 'A' key on your keyboard" #"a" [
        alert "You just clicked the button OR pressed the 'A' key"
    ]
]

You can assign a word label to any widget, and refer to data and properties of that widget by its label. The "text" property is especially useful:

view layout [
    page-to-read: field "http://rebol.com"
    ; page-to-read/text now refers to the text contained in that field
    btn "Display Html" [editor read (to-url page-to-read/text)]
]

You can also set various properties of a widget using its label. When the "Edit HTML Page" button is clicked below, the text of the multi-line area widget is set to contain the text read from the given url. The "show" function in the example below is very important. It must be used to update the GUI display any time a widget property is changed (if you ever create a GUI that doesn't seem to respond properly, the first thing to check is that you've used a "show" function to properly update any changes on screen):

view layout [
    page-to-read: field "http://rebol.com"
    the-html: area 600x440
    btn "Download Html Page" [
        the-html/text: read (to-url page-to-read/text)
        ; try commenting out the following line to see what happens:
        show the-html
    ]
]

The "offset" of a widget holds its coordinate position. It's another useful property, especially for GUIs which involve movement:

view layout/size [
    jumper: button "click me" [
        jumper/offset: random 580x420
        ; The "random" function creates a random value within
        ; a specified range.  In this example, it creates a
        ; random coordinate pair within the range 580x420,
        ; every time the button is clicked.  That random value
        ; is assigned to the position of the button.
    ]
] 600x440

The "style" function is very powerful. It allows you to assign a specific widget definition, including all its properties and actions, to any word label you choose. Any instance of that word label is thereafter treated as a replication of the entire widget definition:

view layout/size [
    style my-btn btn green "click me" [
        face/offset: random 580x420
    ]
    ; "my-btn" now refers to all the above code
    at 254x84 my-btn
    at 19x273 my-btn
    at 85x348 my-btn
    at 498x12 my-btn
    at 341x385 my-btn
] 600x440

Rebol is great at dealing with all types of common data - not just text. You can easily display photos and other graphics in your GUIs, play sounds, display web pages, etc. Here's some code that downloads an image from a web server and displays it in a GUI - notice the "view layout" functions again:

view layout [image (load http://rebol.com/view/bay.jpg)]

The "image" widget inside the brackets displays a picture (.bmp, .jpg, .gif., .png) in the GUI. The "load" function downloads the image to be displayed.

Rebol can apply many built-in effects to images:

view layout [image load http://rebol.com/view/bay.jpg effect [Grayscale]]
view layout [image load http://rebol.com/view/bay.jpg effect [Emboss]]
view layout [image load http://rebol.com/view/bay.jpg effect [Flip 1x1]]
; there are many more built-in effects

Here are some other GUI elements used in Rebol's "VID" layout language:

view layout [
    backcolor white
    h1 "More GUI Examples:"
    box red 500x2
    bar: progress
    slider 200x16 [bar/data: value show bar]
    area "Type here"
    drop-down
    across 
    toggle "Click" "Here" [print value]
    rotary "Click" "Again" "And Again" [print value]
    choice "Choose" "Item 1" "Item 2" "Item 3" [print value]
    radio radio radio
    led
    arrow
    return
    text "Normal"
    text "Bold" bold
    text "Italic" italic
    text "Underline" underline
    text "Bold italic underline" bold italic underline
    text "Serif style text" font-name font-serif
    text "Spaced text" font [space: 5x0]
    return
    h1 "Heading 1"
    h2 "Heading 2"
    h3 "Heading 3"
    h4 "Heading 4"
    tt "Typewriter text"
    code "Code text"
    below
    text "Big" font-size 32
    title "Centered title" 200
    across
    vtext "Normal"
    vtext "Bold" bold
    vtext "Italic" italic
    vtext "Underline" underline
    vtext "Bold italic underline" bold italic underline
    vtext "Serif style text" font-name font-serif
    vtext "Spaced text" font [space: 5x0]
    return
    vh1 "Video Heading 1"
    vh2 "Video Heading 2"
    vh3 "Video Heading 3"
    vh4 "Video Heading 3"
    label "Label"
    below
    vtext "Big" font-size 32
    banner "Banner" 200
]

That's just the tip of the iceberg. With Rebol, even absolute beginners can create nice looking, powerful graphic interfaces in minutes. See http://rebol.com/docs/easy-vid.htmland http://rebol.com/docs/view-guide.htmlfor more information. Here's a little game that demonstrates common GUI techniques (this whole program was presented earlier, in a more compact format without any comments. It's tiny.):

; Create a GUI that's centered on the user's screen:

view center-face layout [

    ; Define some basic layout parameters.  "origin 0x0" 
    ; starts the layout in the upper left corner of the
    ; GUI window.  "space 0x0" dictates that there's no
    ; space between adjacent widgets, and "across" lays
    ; out consecutive widgets next to each other:

    origin 0x0 space 0x0 across 

    ; The section below creates a newly defined button
    ; style called "piece", with an action block that 
    ; swaps the current button's position with that of 
    ; the adjacent empty space.  That action is run
    ; whenever one of the buttons is clicked:

    style piece button 60x60 [

        ; The line below checks to see if the clicked button
        ; is adjacent to the empty space.  The "offset" 
        ; refinement contains the position of the given 
        ; widget.  The word "face" is used to refer to the
        ; currently clicked widget.  The "empty" button is
        ; defined later (at the end of the GUI layout).
        ; It's ok that the empty button is not yet defined,
        ; because this code is not evaluated until the
        ; the entire layout is built and "view"ed:

        if not find [0x60 60x0 0x-60 -60x0
            ] (face/offset - empty/offset) [exit]

        ; In English, that reads 'subtract the position of
        ; the empty space from the position of the clicked
        ; button (the positions are in the form of 
        ; Horizontal x Vertical coordinate pairs).  If that 
        ; difference isn't 60 pixels on one of the 4 sides,
        ; then don't do anything.' (60 pixels is the size of
        ; the "piece" button defined above.)

        ; The next three lines swap the positions of the 
        ; clicked button with the empty button.

        ; First, create a variable to hold the current
        ; position of the clicked button:

        temp: face/offset  

        ; Next, move the button's position to that of the 
        ; current empty space:

        face/offset: empty/offset 

        ; Last, move the empty space (button), to the old
        ; position occupied by the clicked button:

        empty/offset: temp
    ]

    ; The lines below draw the "piece" style buttons onto 
    ; the GUI display.  Each of these buttons contains all
    ; of the action code defined for the piece style above:

    piece "1"   piece "2"   piece "3"   piece "4" return
    piece "5"   piece "6"   piece "7"   piece "8" return
    piece "9"   piece "10"  piece "11"  piece "12" return
    piece "13"  piece "14"  piece "15"

    ; Here's the empty space.  Its beveled edge is removed
    ; to make it look less like a movable piece, and more
    ; like an empty space:

    empty: piece 200.200.200 edge [size: 0]
]

3.5 Blocks and Series

In Rebol, all data and code is stored in "blocks". Blocks are delineated by starting and ending brackets ("[]"). Data items in blocks are separated by white space. Like any other variable data, blocks can be assigned word labels:

some-names: ["John" "Bill" "Tom" "Mike"]
print some-names

Blocks were snuck in earlier as arguments passed to the "rejoin" function, and as brackets used to delineate GUI code passed to the 'view layout' functions. Like any other blocks, GUI code can be labeled and referred to by the assigned word:

gui-layout1: [button field text-list]
view layout gui-layout1

Blocks of user data can also be easily displayed in GUI widgets:

view layout [text-list data some-names]
view layout [area rejoin some-names]

The "append" function is used to add items to a block:

append some-names "Lee"
print some-names

append gui-layout1 [text "This text was appended to the GUI block."]
view layout gui-layout1

The "foreach" function is used to do something to/with each item in a block:

foreach item some-names [alert item]

Here's a slightly more complex foreach example:

some-names: ["John" "Bill" "Tom" "Mike"]
count: 0
foreach name some-names [
    count: count + 1
    print rejoin ["Item " count ": " name]
]

The "remove-each" function can be used to remove items from a block that match a certain criteria:

remove-each name some-names [find name "i"]

Empty data blocks are created with the "copy" function. "Copy" assures that blocks are erased and defined without any previous content:

empty-block: copy []

Here's an example in which an empty block is created and data is appended using a foreach function. The data is then converted to a text string and displayed in a GUI:

some-names: ["John" "Bill" "Tom" "Mike"]
data-block: copy []
count: 0
foreach name some-names [
    count: count + 1
    append data-block rejoin ["Item " count ": " name newline]
]
view layout [area to-string data-block]

In Rebol, blocks can be automatically treated as lists of data, called "series", and manipulated using built-in functions that enable searching, sorting, and otherwise organizing the blocked data:

some-names: ["John" "Bill" "Tom" "Mike"]


sortednames: sort some-names    ; sort alphabetically/ordinally

print first sortednames         ; display the first item ("Bill")

print sortednames/1             ; display the first item ("Bill")

print pick sortednames 1        ; display the first item ("Bill")

find some-names "John"          ; search for "John" in the block, 
                                ;  set a position marker after that
                                ;  item

remove sortednames              ; remove the first item in the block

remove find sortednames "Mike"  ; find the "Mike" item in the block
                                ;  and remove it

length? sortednames             ; count items in the block

head sortednames                ; set a position marker at the 
                                ;  beginning of the block

next sortednames                ; set a position marker at the next
                                ;  item in the block

last sortednames                ; set a position marker at the last
                                ;  item in the block

back sortednames                ; set a position marker at the 
                                ;  previous item in the block

tail sortednames                ; set a position marker after the
                                ;  last item in the block

insert sortednames "Lee"        ; add the name "Lee" at the current
                                ;  position in the block


write %/c/names.txt some-names  ; write the block to the hard drive
                                ;  as raw text data

save %/c/namess.txt some-names  ; write the block to the hard drive
                                ;  as native Rebol formatted code

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!
view layout [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. 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. All programming, and computing in general, after all, at its core, is essentially about storing, organizing, manipulating, and transferring data of some sort. Rebol makes working with all types of data very easy.

Blocks are also used as the fundamental structure for organizing Rebol code. Brackets are used throughout the Rebol language syntax to separate and delineate groups of functions, as well as collections of data parameters. The "compose" function allows variables in parentheses to be evaluated and inserted as if they'd been typed explicitly into a code block:

length-of-block: length? some-items
view layout compose [image some-items/(length-of-block)]

The last line in the example above appears to the interpreter as if the following had been typed:

view layout [image some-items/5]

The "reduce" function can be used to produce the same type of evaluation. Function words in a reduced block should begin with the tick (') symbol:

view layout reduce ['image some-items/(length-of-block)]

Another way to use variable values explicitly is with the ":" format below. This code evaluates the same as the previous two examples:

view layout [image some-items/:length-of-block]

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

photo-block: compose [(photo1) (photo2)]

foreach photo photo-block [view layout [image photo]]

The practical application of block structures will be presented by example throughout this tutorial. For more detailed information about using blocks and series functions see http://www.rebol.com/docs/core23/rebolcore-6.html.

3.6 Conditions

3.6.1 If

Conditions are used to manage program flow. The most basic conditional evaluation is "if":

if (this expression is true) [do this block of code] 
; parentheses are not required

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."] 

; get a username and password:
userpass: request-pass/title "Type 'username' and 'password'"
; test it and provide a response if correct:
if (userpass = ["username" "password"]) [alert "Welcome back!"]

3.6.2 Either

"Either" is an if/then/else evaluation that chooses between two blocks to evaluate, based on whether the given condition is true or false. Its syntax is:

either (condition) [
    block to perform if the condition is true
][
    block to perform if the condition is false
]

For example:

either now/time > 8:00am [
    alert "It's time to get up!"
][
    alert "You can keep on sleeping."
] 

userpass: request-pass
either userpass = ["username" "password"] [
    alert "Welcome back!"
][
    alert "Incorrect user/password combination!"
]

3.6.3 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!  Just the start of the week..."]
    "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!"]

3.7 Loops

3.7.1 Forever

"Loop" structures provide programmatic ways to methodically repeat actions, manage program flow, and automate lengthy data processing activities. 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
        ]
    ]
]

3.7.2 For

"For" loops allow you to control repetition patterns that involve consecutively changing values. You specify a start value, end value, incremental value, and a variable name to hold the current value during the loop. Here's the "for" loop syntax:

for {variable word to hold current value} {starting value} {ending value} {incremental value} [block of code to perform, which can use the current variable value]

For example:

for counter 1 10 1 [print counter] 
; starts on 1 and counts to 10 by increments of 1   
for counter 10 1 -1 [print counter] 
; starts on 10 and counts backwards to 1 by increments of -1    
for counter 10 100 10 [print counter] 
; starts on 10 and counts to 100 by increments of 10    
for counter 1 5 .5 [print counter] 
; starts on 1 and counts to 5 by increments of .5   
for timer 8:00 9:00 0:05 [print timer] 
; starts at 8:00am and counts to 9:00am by increments of 5 seconds  
for dimes $0.00 $1.00 $0.10 [print dimes] 
; starts at 0 cents and counts to 1 dollar by increments of a dime  
for date 1-dec-2005 25-jan-2006 8 [print date] 
; starts at December 12, 2005 and counts to January 25, 2006 
; and by increments of 8 days   
for alphabet #"a" #"z" 1 [prin alphabet] 
; starts at the character a and counts to z by increments of 1 letter

Notice that Rebol properly increments dates, money, time, etc.

This "for" loop displays the first 5 file names in the current folder on your hard drive:

files: read %.  
for count 1 5 1 compose [print files/(count)]

Notice the "compose" word used in the for loop. "files/1" represents the first item in the file list, "files/2" represents the second, and so on. The first time though the loop, the code reads as if [print files/1] had been typed in manually, etc.

3.7.3 Foreach

The "foreach" function lets you easily loop through a block of data. Its syntax is:

foreach {variable name referring to each consecutive item in the given block} [given block] [block of functions to be executed upon each item in the given block, using the variable name to refer to each successive item]

This example prints the name of every file in the current directory on your hard drive:

folder: read %. 
foreach file folder [print file]

This line reads and prints each successive message in a user's email box:

foreach mail (read pop://user:pass@website.com) [print mail]

3.7.4 While

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 join "Please select TODAY's date.  It's " [now/date]
]

while [request-pass <> ["username" "password"]] [
    alert "The username is 'username' and the password is 'password'"
]

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."]
    ]
]

3.7.5 A Real World Example Using Loops, Conditions, and Blocks:

This longer example uses a variety of loops and conditions to process data in typical ways. It also demonstrates how a simple block can be used to store a simple database of information:

Rebol [Title: "User Database Looping Example"]

users: [
    ["John" "Smith" "123 Tomline Lane"
        "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Place"
        "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"]
]
a-line: copy [] 
loop 65 [append a-line "-"]
a-line: trim to-string a-line
print-all: does [
    foreach user users [
        print a-line
        print rejoin ["User:     " user/1 " " user/2]
        print a-line
        print rejoin ["Address:  " user/3 "  " user/4]
        print rejoin ["Phone:    " user/5]
        print newline
    ]
]   
forever [
    prin "^(1B)[J"  ; clear the screen
    print "Here are the current users in the database:^/"
    print a-line
    foreach user users [prin rejoin [user/1 " " user/2 "  "]]
    print "" print a-line
    print "Type the name of a user below (part of a name will perform search):^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What person would you like info about?  }
    print newline
    switch/default answer [
        "all"   [print-all]
        ""      [ask "Goodbye!  Press any key to end." quit]
        ][
        found: false
        foreach user users [
            if find rejoin [user/1 " " user/2] answer [
                print a-line
                print rejoin ["User:     " user/1 " " user/2]
                print a-line
                print rejoin ["Address:  " user/3 " " user/4]
                print rejoin ["Phone:    " user/5]
                print newline
                found: true
            ]
        ]
        if found <> true [
            print "That user is not in the database!^/"
        ]
    ]
    ask "Press [ENTER] to continue"
]

For some perspective, here's a GUI version of the same program that demonstrates how GUI and command line programming styles differ:

Rebol [title: "User Database GUI Example"]

users: [
    ["John" "Smith" "123 Tomline Lane"
        "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Place"
        "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"]
]
user-list: copy []
foreach user users [append user-list user/1]
user-list: sort user-list

view display-gui: layout [
    h2 "Click a user name to display their information:"
    across
    list-users: text-list 200x400 data user-list [
        current-info: []
        foreach user users [
            if find user/1 value [
                current-info: rejoin [
                    "FIRST NAME:  " user/1 newline newline
                    "LAST NAME:   " user/2 newline newline
                    "ADDRESS:     " user/3 newline newline
                    "CITY/STATE:  " user/4 newline newline
                    "PHONE:       " user/5
                ]
            ]
        ]
        display/text: current-info
        show display show list-users
    ]
    display: area "" 300x400 wrap
]

More powerful and specific looping structures and conditions such as "forskip", "any", and "all" add to Rebol's flexibility. For more information, see the Rebol Dictionary in the Rebol desktop folder REBOL->Tools.

3.8 User Defined Functions

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:

some-actions: [
    alert "Here is one action." 
    print "Here's a second action."
    write %/c/anotheraction.txt "Here's a 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:

more-actions: does [
    alert "4" 
    alert "5"
    alert "6"
]

more-actions

Here's a useful function to clear the command line screen in the Rebol interpreter.

cls: does [prin "^(1B)[J"]

cls

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. Here's the "func" syntax:

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

This function definition:

sqr-var: func [anumber] [print square-root (4 + anumber)]

Can be used as follows:

sqr-var 12  ; prints "4", the square root of 12+4 (16)  
sqr-var 96  ; prints "10", the square root of 96+4 (100)

Here's a simple function to display images:

display: func [filename] [view layout [image load filename]]

display (to-file request-file)

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 []

sqr-var: func [anumber] [print square-root (4 + anumber)]
display: func [filename] [view layout [image load filename]]
cls: does [prin "^(1B)[J"]

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

do %myfunctions.r
sqr-var
display
cls

Here's an example function that plays a .wave sound file. Save this code as C:\play_sound.r:

Rebol [title: "play-sound"]  ; you can add a title to the header

play-sound: func [sound-file] [
    wait 0
    ring: load sound-file
    sound-port: open sound://
    insert sound-port ring
    wait sound-port
    close sound-port
]

Then run the code below to import the function and play selected .wav files:

do %/c/play_sound.r

play-sound %/C/WINDOWS/Media/chimes.wav
play-sound to-file request-file/file %/C/WINDOWS/Media/tada.wav

4. More Useful Topics

Before typing in or pasting any more code, adjust the following option in the Rebol interpreter: click the "User" menu in the graphic "Viewtop" that opens by default with Rebol, and uncheck "Open Desktop On Startup". That'll save you the trouble of clicking the "Console" button every time you start Rebol.

4.1 Binary Embedding and Compression

The following program can be used to encode external files (images, sounds, .exe files, etc.) so that they can be included within the text of your program code. Use "load (data)" to make use of any text data created by this program:

Rebol [Title: "Simple Binary Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
data: read/binary file
editor data

The example below uses a text representation of the image at http://musiclessonz.com/test.png, encoded with the program above:

picture: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAAE3RFWHRTb2Z0d2Fy
ZQBSRUJPTC9WaWV3j9kWeAAAAU1JREFUeJztlzEOgzAQBHkaT7s2ryZUUZoYRz4t
e9xsSzTjEXIktqP3trsPcPPo7z36e4/+3qO/9y76t/qjn3766V/oj4jBb86nUyZP
lM7kidKZPFE6kydq/Pjxq/nSElGv3qv50vj/o59++hNQM6Z93+P3zqefAw12Fyqh
v/ToX+4Pt0ubiNKZPFE6Ux5q/O/436lkh6affvrpp38ZRT/99Ov6+f4tPPqX+8Ps
/meidCZPlM7kidKZPFE6kydKZ/JE6UyeKJ3JE6UzeaJ0Jk+UzuSJ0pk8UTMmvn8L
j/7l/nC7tIkonekLdXm9dafSmeinn376D/rpp5/+vv1GqBkT37+FR/9yf7hd2kSU
zuSJ0pk8UTqTJ0pn8kTpTJ4onckTpTN5onQmT5TO5InSmTxROpMnasbE92/h0b/Q
//jR33v09x79vUd/73XvfwNmVzlr+eOLmgAAAABJRU5ErkJggg==
}
view layout [image picture]

The program below allows you to compress and embed binary files in your code:

REBOL [Title: "Rebol Binary Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
if not file [quit]
uncompressed: read/binary file
compressed: compress to-string uncompressed
editor compressed
alert rejoin ["Uncompressed size:  " length? uncompressed
    " bytes.  Compressed size: " length? compressed " bytes."]

To use the compressed version of data created by the program above, use the following code:

to-binary decompress {compressed data}

For example:

image-compressed: load to-binary decompress 64#{
eJzrDPBz5+WS4mJgYOD19HAJAtL/GRgYdTiYgKzm7Z9WACnhEteIkuD8tJLyxKJU
hiBXJ38f/bDM1PL+m2IVDAzsFz1dHEMq5ry9u3GijKcAy0Fh3kVzn/0XmRW5WXGV
sUF25EOmKwrSjrrF9v89o//u+cs/IS75763Tv7ZO/5qt//p63LX1e9fEV0fu/7ap
7m0qZRIJf+2DmGZoVER5MQiz+ntzJix6kKnJ6CNio6va0Nm0fCmLQeCHLVMY1Ljm
TRM64HLwMpGK/334Hf4n+vkn+1pr9md7jAVsYv+X8Z3Z+M/yscIX/j32H7sl/0j3
KK+of/CX8/X63sV1w51WqNj1763MjOS/xcccX8hzzFtXDwyXL9f/P19/f0vxz4f2
OucaHfmZDwID+P7Hso/5snw8m+qevH1030pG4kr8fhNC4f/34Z89ov+vHe4vAeut
SsdqX8T/OYUCv9iblr++f67R8pp9ukzLv8YHL39tL07o+3pekn1h/dDVBgzLU/d3
9te/Lki4cNgBmA6/lO+J/RPdzty8Rr5y94/tfOxsX6/r8xJK0/UW9vlH93/9oAzR
e09yKIUBVbT9/br/U/m7x6CU98VAAJS2ZPPF/197eEDhtfs9vX9rDzc6/v3qzUyo
nJA/dz76Y77tHw+w3gXlbEMpDKihza/+7/o/c3+DU54tDwsobR2/fXR/qYXBiV8T
t3eDEmpA/d9LDASK0y/tnz+H/Ynmt78E1vti7lAKA6pouxz/X7v+uR045ZFdRE6x
1q21pG7NiSzx1f5R40pvvdNn+oB1P4Onq5/LOqeEJgCemFy1KQgAAA==
}
view layout [image image-compressed]

4.2 Running Command Line Applications

The "call" function executes commands in your computer's operating system (i.e., DOS and Unix commands). The example below opens Windows' Notepad to edit the "C:\YOURNAME.txt" text file created earlier:

call "notepad.exe c:\YOURNAME.txt"

This next example opens Windows' Paint program to edit an image we downloaded earlier in the tutorial:

call "mspaint.exe c:\bay.jpg"

Here's an example that embeds an executable program into the code, decompresses, and writes the program to the hard drive, and then runs it with the call function:

program: load to-binary decompress 64#{
eJztF11sU2X03K4VqJsrkZJp6OzchhFJsx8qDB9od1fHdIO6ds7AgJX2jttyey/p
vWUjJuNnmNhMibzwaCSLi+EBE1ziGIkBGh0BSYTwwAMme9Dk4kgkgSiKcj3nu7es
QrKFhMUQOcn5+c7fd875+vXe27FJAg4AbIiGAQwWIwZMEbqTcmODN5xRdmRi6aoy
Z83YogngLlaNtV+s6kV7q9KelHeu9LYqQTXt7e/v97UqLcLuqKJIvriShnAIoJ0r
gXvPn+StlDAF5dyzHLwAdlw4TZ1Mm7oQvWDu7jKLslsxBc4KQ30bb9bMHF3F/D5j
MFAHEIbHD+cwb88s9riSEIjvK7EKogZs//bxAvQmYlqM5JsOUwHPWFgEAYDTvqTp
eYdy1Fn5Sh/O96h9nLrrDcD4IpQm7UOkWL/nt6MlqMvxrkl+GVWS7xqWalzDzqGz
9rbyD5ehpmnl+ezt3M/RSPe7Q9/ajeh5+9Ztm3vKh9xoM7SaimLUR18C2JKf+Kg2
APoJwzDOuiAF+hHU/pHXryObdLyP+y2kEhx7UaLfo0gq/RJa60/n88Ndrpz7FmqG
u5bk3L8zwdWXc0+jdOYXkn4lnYfW++/qOPLyDz7BfH3jTXVnplx949inhPvnSgw/
8RSIHM7P8PdSUYtxlxSkONE+o/u7EkNElMbpcuRKUhTjmLH/iHbDQQ7DHqL77zbh
oQxeRa9duBQHkRj+HnIdr7y/e178AvmmnHt5VQAmaNo59/EZ8QSJAY7EURJvMu2x
KipYj2CaEToYve2eYYiwl4rWY6jN8RWF5XtsuWSyhO7aJG8XXQFkNdWYIqIHK8nH
8FOSFJMoteEfZfQEo1SNCPCW2/BTjWK1uXkp9dDDegjrDqpkAUtiJhNp4ma3qUrx
MG6dqkyFMQ2ExQmaxgU2c/07D2ZJsCz3Q68Xh76Cvac2pZwi8jCO8rIZd4jielmc
uHxmsEMe1vMBZJf0YY8Pda95yH5p+tWrI86XMZbTE5a1gVlXFKyryeowp0Cy4Wf+
hdSrWGp26N008hW4XnS6/OBS7MnUVHoK0osoTV+22qF56c95qKdtZBzB66J/imSc
/Rmsg/KDdHFbA9O3RrZWByD/qPf1KTCwze3y2KCbn9vnP4ExoItiwr11zvncqq6+
oXGV//XVa5qCzXxL6M3ZfBfMZyFPBvywgD3FGDjLnGVl83o4T+HJAZ/PFxWTqrcj
GxerHljRqyL9sWXxqU2/nkHki1H4HDkvJeM7vZooeLdnNU2R10K34G1XdgveTmE7
vmv7fNDcFY1u3ABpNa5J6rZd9MouqGpjw6z1GLXn6vDxV/s9o1cYvcroNUanGP2J
UZ3RG4zeZPQ2o3cY/YtRqCdqZ3Qho6WMuhitYHQZ0pr6mRr21Zvv03VFuuMoX0Gd
VqT7BlupKFoXw8eo/8yynUR+HvEa4g3EPxEXYuwSxOWIaxADiGHEBKKGeADxCOIx
a1wXkE81zH/ut0OdG0LtjQ2+hCSBzLUKWoeSyErC+pickIQgfAmhgaSG319xPEvo
ioQ6Ld9D0CL04ddZQuknaxA4W1hRtXeySa0DXWM7BHjDFhHkhLUKYs2cJTcrA0H4
mmtXYgk+m1GVTBBOsVVbXJGDsNTWKexIqpqQ4aWYqgbps4LPCDFNMPcLYXQpldrC
g0bcVHcKcQ220DqyB4PTHYKWScZVgCGsw/LBEgHWsjYLZR2zRTMxWZUwfaFwOAot
SXVXTIuLM9V/ZeuSMw/UxW/s4KOF6W2GNjmp8Uo6rci8ImsZRVLxG+1hZWhgrlv6
/4F/ABcSIgQAEAAA
}
write/binary %program.exe program
call %program.exe

4.3 Saving and Running Rebol Scripts

Remember, whenever you save a Rebol program to a text file, the code must begin with the following bit of text:

REBOL []

That text tells the Rebol interpreter that the file contains a valid Rebol program. The code below is a web cam viewer program. Type in or copy/paste the complete code source below into a text editor such as Windows Notepad or the Rebol built-in text editor ({editor ""} at the Rebol console prompt). Save the text as a file called "webcam.r" on your C:\ drive.

Rebol [Title: "Webcam Viewer"]

; try http://www.webcam-index.com/USA/ for more webcam links.

temp-url: "http://209.165.153.2/axis-cgi/jpg/image.cgi"
while [true]  [
    webcam-url: to-url request-text/title/default trim {
        Enter the web cam URL:} temp-url
    either attempt [webcam: load webcam-url] 
        [break]
        [either request [trim {
            That webcam is not currently available.} trim {
            Try Again} "Quit"]
            [temp-url: to-string webcam-url]
            [quit]
    ] 
] 
resize-screen: func [size] [
    webcam/size: to-pair size
    window/size: (to-pair size) + 40x72
    show window
]
window: layout [
    across 
    btn "Stop" [webcam/rate: none show webcam]
    btn "Start" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    rotary "320x240" "640x480" "160x120" [
        resize-screen to-pair value 
    ]
    btn "Exit" [quit] return
    webcam: image load webcam-url  320x240 
    with [
        rate: 0
        feel/engage: func [face action event][
            switch action [
                time [face/image: load webcam-url show face]
            ] 
        ] 
    ] 
]
view center-face window

Once you've saved the webcam.r program to C:\, you can run it in any one of a variety of ways:

  1. If you've already installed the Rebol interpreter in Windows, just find the C:\webcam.r file icon in your file explorer and double click it (i.e., click My Computer -> C: -> webcam.r). The Rebol interpreter automatically executes the script. By default, during Rebol's initial installation, all files with an ".r" extension are associated with the interpreter. They can be clicked and run as if they're executable programs, just like ".exe" files. This is the most common way to run Rebol scripts, and it works the same way on all major graphic operating systems.
  2. Type "do %/c/webcam.r" into the Rebol interpreter.
  3. Use the built-in editor in Rebol by typing "editor %/c/webcam.r" at the prompt. Pressing F5 in the editor will automatically run the script.
  4. Scripts can be run at the command line. In Windows, copy rebol.exe and webcam.r to the same folder (C:\), then click Start -> Run, and type "C:\rebol.exe C:\webcam.r" Those commands will start the Rebol interpreter and do the webcam.r code.
  5. At the Windows command prompt (in a Windows DOS box), type "C:\rebol.exe C:\webcam.r"
  6. Create a text file called webcam.bat, containing the text "C:\rebol.exe C:\webcam.r" . Click on the webcam.bat file in Windows, and it'll run those commands.
  7. Use a program such as XpackerXto package and distribute the program. XpackerX allows you to wrap the Rebol interpreter and webcam.r program into a single executable file that has a clickable icon, and automatically runs both files. That allows you to create a single file executable Windows program that can be distributed and run like any other application. Just click it and run...
  8. Buy the commercial "SDK" version of Rebol, which provides the best method for packaging Rebol applications.

IMPORTANT: To turn off the default security requester that continually asks permission to read/write the hard drive, type "secure none" in the Rebol interpreter, and then run the program with "do {filename}". Running "C:\rebol.exe -s {filename}" does the same thing . The "-s" launches the Rebol interpreter without any security features turned on, making it behave like a typical Windows program.

4.4 "Compiling" Rebol Programs - Distributing Packaged .EXE Files

By packaging the Rebol.exe interpreter, your Rebol script(s), and any supporting data file(s) into a single executable with an icon of your choice, XpackerXworks 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>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\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.

4.5 Responding to Special Events in a GUI

Rebol's simple GUI syntax makes it easy for widgets to respond to mouse clicks. As you've seen, you can simply put the block of code you want evaluated immediately after the widget that activates it:

view layout [btn "Click me" [alert "Thank you for the click :)"]]

But what if you want your GUI to respond to events other than a mouse click directly on a widget? What if, for example, you want the program to react whenever a user clicks anywhere on the GUI screen (in a paint program, for example), or if you want a widget to do something after a certain amount of time has passed, or if you want to capture clicks on the GUI close button so that the user can't accidentally shut down an important data screen. That's what the "feel" object and "insert-event-func" function are used for.

Here's an example of the basic feel syntax:

view layout [
    text "Click, right-click, and drag the mouse over this text." feel [
        engage: func [face action event] [
            print action
            print event/offset
        ]
    ]
]

The above code is often shortened using "f a e" to represent "face action event":

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            print a
            print e/offset
        ]
    ]
]

You can respond to specific events as follows:

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            if a = 'up [print "You just released the mouse."]
        ]
    ]
]

You can also assign timer events to any widget, as follows:

view layout [
    text "This text has a timer event attached." rate 00:00:00.5 feel [
        engage: func [f a e] [
            if a = 'time [print "1/2 second has passed."]
        ]
    ]
]

Here's a button with a time event attached (a rate of "0" means don't wait at all). Every 0 seconds, when the timer event is detected, the offset (position) of the button is updated. This creates animation:

view layout/size [
    mover: btn rate 0 feel [
        engage: func [f a e] [
            if a = 'time [
                mover/offset: mover/offset + 5x5
                show mover
            ]
        ]
    ]
] 400x400

By updating the offset of a widget every time it's clicked, you can enable drag-and-drop operations:

view layout/size [
    text "Click and drag this text" feel [
        ; remember f="face", a="action", e="event":
        engage: func [f a e] [
            ; first, record the coordinate at which the mouse is
            ; initially clicked:
            if a = 'down [initial-position: e/offset]
            ; if the mouse is moved while holding down the button, 
            ; move the position of the clicked widget the same amount
            ; (the difference between the intial clicked coordinate
            ; recorded above, and the new current coordinate determined
            ; whenever a mouse move event occurs):
            if find [over away] a [
                f/offset: f/offset + (e/offset - initial-position)
            ]
            show f
        ]
    ]
] 600X440

Feel objects and event functions can be included right inside a style definition. The definition below allows you to easily create multiple GUI widgets that can be dragged around the screen. "movestyle" is defined as a block of code that's later passed to a widget's "feel" object, and is therefore included in the overall style definition (the remove and append functions have been added here to place the moved widget on top of other widgets in the GUI (i.e., to bring the dragged widget to the visual foreground)). You can add this "feel movestyle" code to any GUI widget to make it drag-able:

movestyle: [
    engage: func [f a e] [
        if a = 'down [
            initial-position: e/offset
            remove find f/parent-face/pane f
            append f/parent-face/pane f
        ]
        if find [over away] a [
            f/offset: f/offset + (e/offset - initial-position)
        ]
        show f
    ]
]

view layout/size [
    style moveable-object box 20x20 feel movestyle
    ; "random 255.255.255" represents a different random
    ;  color for each piece:
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    text "This text and all the boxes are movable" feel movestyle
] 600x440

To handle global events in a GUI such as resizing and closing, "insert-event-func" is useful. The following example checks for resize events:

insert-event-func [
    either event/type = 'resize [
        alert "I've been resized"
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [text "Resize this window."] [resize]

You can use that technique to adjust the window layout, and specifically reposition widgets when a screen is resized:

insert-event-func [
    either event/type = 'resize [
        stay-here/offset:
            stay-here/parent-face/size - stay-here/size - 20x20
        show stay-here
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [
    stay-here: text "Resize this window."
] [resize]

To remove an installed event handler, use "remove-event-func". The following example captures three consecutive close events, and then removes the event handler, allowing you to close the GUI on the 4th try:

count: 1
evtfunc: insert-event-func [
    either event/type = 'close [
        if count = 3 [remove-event-func :evtfunc]
        count: count + 1
        none
    ][
        event
    ]
]

view layout [text "Try to close this window 4 times."]

For more information about handling events see http://www.rebol.com/how-to/feel.html, http://www.codeconscious.com/rebol/view-notes.html, and http://www.rebol.com/docs/view-system.html.

4.6 Common Errors

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:

Rebol []

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

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 layout" block, and the GUI becomes unresponsive, type "unview" at the interpreter command line and the broken GUI will be closed. To break out of an endless loop, or to otherwise stop the execution of errant code, just hit the [Esc] key on your keyboard.

6) 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

7) 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.

8) 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.

4.6.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.

5. Examples

The examples in this section demonstrate how Rebol code is put together to create complete programs. The code is heavily commented to provide line-by-line explanations of how each element works. The recommended way to run the examples is to install Rebol on your computer, paste the code for each program into a text editor, save the code file as "(program).r" and then double click the icon for the text file you've created. With Rebol installed, any file with a ".r" extension will automatically run as if it's an .exe program. You can also use XpackerX to package and distribute the code file as a real .exe, or use any of the other methods described earlier. A downloadable zip file containing screen shots, XpackerX instructions, and .exe files of these examples and others from this tutorial is available at:

http://musiclessonz.com/rebol_tutorial_examples.zip

Be sure to check out the hundreds of additional code examples available directly from rebsites on the desktop of the Rebol interpreter!

5.1 Little Email Client

The first example is a complete graphical email client that can be used to read and send messages:

Rebol [Title: "Little Email Client"] 
; (every program requires a minimum header)

view layout [
        ; The line above creates the GUI layout.
    h1 "Send Email:"
        ; The second line adds a text label to the GUI.
    address: field "recipient@website.com"
        ; This line creates a text entry field, containing
        ; the default text "recipient@website.com".  It assigns
        ; the variable word "address" to the text entered here.
    subject: field "Subject" 
        ; another text entry field for the email subject line
    body: area "Body"
        ; This creates a larger, multi-line text entry area for
        ; the body text of the email.
    btn "Send" [
            ; A button with the word "send".  The functions
            ; inside this action block are executed whenever
            ; the button is clicked.
        send/subject to-email address/text body/text subject/text
            ; This line does most of the work.  It uses the 
            ; built-in Rebol word "send" to send the email.  The
            ; send function, with its "/subject" refinement
            ; accepts three parameters.  It's passed the current
            ; text contained in each field labeled above
            ; (referred to as "address/text" "body/text" and
            ; "subject/text").  The built-in "to-email" function
            ; ensures that the address text is treated as an
            ; email data value.
        alert "Message Sent."
            ; alerts the user when the previous line is complete.
    ]
    h1 "Read Email:"
        ; Another text label
    mailbox: field "pop://user:pass@website.com"
        ; Another text entry field.  The user's email account
        ; info is entered here.
    btn "Read" [
            ; An additional button, this time with an action
            ; block that reads messages from a specified mailbox.
            ; It only takes one line:
        editor read to-url mailbox/text
            ; The built-in "to-url" function ensures that the
            ; text in the mailbox field is treated as a url.  
            ; The contents of the mailbox are read and displayed
            ; in the built-in Rebol editor.
    ]
]

Here's the same code, without comments - it's very simple. Try pasting it directly into the Rebol interpreter:

Rebol [Title: "Little Email Client"]
view layout [
    h1 "Send Email:"
    address: field "recipient@website.com"
    subject: field "Subject"
    body: area "Body"
    btn "Send" [
        send/subject to-email address/text body/text subject/text
        alert "Message Sent."
    ]
    h1 "Read Email:"
    mailbox: field "pop://user:pass@website.com"
    btn "Read" [
        editor read to-url mailbox/text
    ]
]

5.2 Simple Web Page Editor

The following program can be used to load, edit, and save html files (or any other text file) directly to/from a live web server or to/from a drive on your local computer. It requires 14 lines of code:

Rebol [Title: "Web Page Editor"]  ; required header

view layout [
    ; Create a text entry field containing a generic url address for
    ; the page to be edited.  Assign the label "page-to-read" to the
    ; text entered here:
    page-to-read: field 600 "ftp://user:pass@website.com/path/page.html"
    ; Create a multi-line text field to hold and edit the html
    ; downloaded from the above url.  Assign the label "the-html" to it:
    the-html: area 600x440
    ; Layout the next three buttons on the same line:
    across
    ; Create a button to download and display the html at the url given
    ; above.
    btn "Download Html Page" [
        ; Whenever the button is clicked, download the html at the url
        ; above, insert it into the multi-line text area (by setting the
        ; text property of that field to the downloaded text), and update
        ; the display:
        the-html/text: read (to-url page-to-read/text)
        show the-html
    ]
    ; Create another button to read and display html from a local file:
    btn "Load Local Html File" [
        ; Whenever the button is clicked, read the html from a file
        ; selected by the user, insert it into the multi-line text area,
        ; and update the display:
        the-html/text: read (to-file request-file)
        show the-html
    ]
    ; Create another button to write the edited contents of the multi-
    ; line text area back to the url:
    btn "Save Changes to Web Site" [
        write (to-url page-to-read/text) the-html/text
    ]
    ; Create another button to write the edited contents of the multi-
    ; line text area to a local file selected by the user:
    btn "Save Changes to Local File" [
        write (to-file request-file/save) the-html/text
    ]
]

5.3 Simple Menu Example

A module that produces full blown menus with all the bells and whistles, animated icons, appropriate look-and-feel for various operating systems, and every possible display option is available at http://www.rebol.org/library/scripts/menu-system.r. Here's a simpler homemade example that can be included in your programs to provide basic menu functionality. It's constructed using only raw, native Rebol GUI components:

Rebol [Title: "Simple Menu Example"]

view center-face gui: layout/size [

    at 100x100 H3 "You selected:"
    display: field

    ; Here's the menu.  Make sure it goes AFTER other GUI code.
    ; If you put it before other code, the menu will appear be-
    ; hind other widgets in the GUI.  The menu is basically just
    ; a text-list widget, which is initially hidden off-screen
    ; at position -200x-200.  When an item in the list is 
    ; clicked upon, the action block for the text-list runs
    ; through a conditional switch structure, to decide what to
    ; do for the chosen item.  The code for each option first 
    ; re-hides the menu by repositioning it off screen (at 
    ; -200x-200 again).  For use in your own programs, you can 
    ; put as many items as you want in the list, and the action 
    ; block for each item can perform any actions you want.
    ; Here, each option just updates the text in the "display"
    ; text entry field, created above.  Change, add to, or 
    ; delete the "item1" "item2" and "quit" elements to suit 
    ; your needs:

    origin 2x2 space 5x5 across
    at -200x-200 file-menu: text-list "item1" "item2" "quit" [
        switch value [
            "item1" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item1"
            ]
            "item2" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item2"
            ]
            "quit" [quit]
        ]
    ]

    ; The menu initially just appears as some text choices at 
    ; the top of the GUI.  When the "File" menu is clicked,
    ; the action block of that text widget repositions the 
    ; text-list above, so that it appears directly underneath
    ; the File menu ("face/offset" is the location of the 
    ; currently selected text widget).  It disappears when
    ; clicked again - the code checks to see if the text-list
    ; is positioned beneath the menu.  If so, it repositions
    ; it out of sight.

    at 2x2
    text bold "File" [
        either (face/offset + 0x22) = file-menu/offset [
            file-menu/offset: -200x-200
            show file-menu
        ][
            file-menu/offset: (face/offset + 0x22)
            show file-menu
        ]
    ]

    ; Here's an additional top level menu option.  It provides
    ; just a single choice.  Instead of opening a text-list 
    ; widget with multiple options, it simply ensures that the
    ; other menu is closed (re-hidden), and then runs some code.

    text bold "Help" [
        file-menu/offset: -200x-200
        show file-menu
        ; PUT YOUR CODE HERE:
        set-face display "Help"
    ]
] 400x300

5.4 FTP Chat Room

This example is a simple chat application that lets users send instant text messages back and forth across the Internet. It includes password protected access for administrators to erase chat contents. It also allows users to pause activity momentarily, and requires a username/password to continue ["secret" "password"]. The chat "rooms" are created by dynamically creating, reading, appending, and saving text files via ftp (to use the program, you'll need access to an available ftp server: ftp address, username, and password. Nothing else needs to be configured on the server).

Rebol [title: "FTP Chat Room"]  ; required header

webserver: to-url request-text/title/default trim {
    Web Server Address:} {ftp://user:pass@website.com/chat.txt}
; get the url of a webserver text file to use for the chat.
; The ftp username, password, domain, and filename must be 
; entered in the format shown.

name: request-text/title "Enter your name:"
; get the user's name

cls: does [prin "^(1B)[J"]
; "cls" is assigned a function definition that clears the screen.

write/append webserver join now [
    ": " name " has entered the room." newline
]
; The line above writes some text to the webserver.
; The "/append" refinement adds it to the existing 
; text in the webserver file (as opposed to erasing
; what's already there).  Using "join", the text 
; written to the webserver is the combined value of 
; {the user's name}, some static text, the current 
; date and time, and a carriage return.

forever [
    current-chat: read webserver 
    ; read the messages that are currently on the webserver,
    ; and assign the variable word "current-chat"

    cls ; clear the screen using the word defined above
    print rejoin [ 
        "--------------------------------------------------"
        newline {You are logged in as: } name newline 
        {Type "room" to switch chat rooms.} newline
        {Type "lock" to pause/lock your chat.} newline
        {Type "quit" to end your chat.} newline 
        {Type "clear" to erase the current chat.} newline 
        {Press [ENTER] to periodically update the display.} newline 
        "--------------------------------------------------" newline]
    ; displays a greeting and some instructions

    print join "Here's the current chat text at: " [webserver newline]
    print current-chat 

    sent-message: copy join name [
        " says: " entered-text: ask "You say:  "
    ] 
    ; get the text to send, then check for commands below 
    ; ("quit", "clear", "room", "lock", and [ENTER])
    ; The built-in word "ask" requests some info within
    ; the interpreter.

    switch/default entered-text [
        "quit"  [break] 
            ; if the user typed in "quit", 
            ; stop the forever loop (exit the program)
        "clear" [
            if/else request-pass = ["secret" "password"] [
                write webserver ""
                ; "if/else" is the same as "either"
            ][
                alert trim {
                You must know the administrator 
                password to clear the room!}
                ; if the user typed in "clear", erase the 
                ; current text chat.  But first, ask user 
                ; for the administrator username/password 
            ]
        ]
        "room"  [
            write/append webserver join now [
                ": " name " has left the room." newline]
            webserver: to-url request-text/title/default {New Web 
            Server Address:} to-string webserver
            write/append webserver join now [
                ": " name " has entered the room." newline
            ; if the user typed in "room", request a new 
            ; webserver address, and run some code that was 
            ; presented earlier in the program,
            ; using the newly entered "webserver" variable, 
            ; to effectively change chat "rooms". 
            ]
        ]
        "lock" [
            alert trim {The program will now pause for 5 seconds. 
                You'll need the correct username and password 
                to continue.
            }
            pause-time: now/time + 5  
            ; assign a variable to the time 5 seconds from now
            forever [if now/time = pause-time [
                ; wait 5 seconds
                while [request-pass <> ["secret" "password"]] [
                    alert "Incorrect password - look in the source!"
                    ]
                ; don't go on until the user gets the password right.
                break
                ]
            ]
            ; exit the forever loop after 5 seconds have passed
        ]
    ][
        if entered-text <> "" [
            write/append webserver join sent-message [newline]
        ]
        ; default case: as long as the entered message is not 
        ; blank ([Enter]), write the message to the web server 
        ; (append it to the current text)
    ]
]
; when the "forever" loop is exited, do the following:
cls print "Goodbye!" 
write/append webserver join now [
    ": " name " has closed chat." newline]
wait 1

The bulk of this program runs within a "forever" loop, and uses a conditional "switch" statement to decide how to respond to user input. This is a classic structure that can be adjusted to match a variety of generalized situations in which the computer repeatedly waits for and responds to user interaction at the command prompt.

5.5 Image Effector

The next application creates a GUI interface, downloads and displays an image from the Internet, allows you to apply effects to it, and lets you save the effected image to the hard drive. In the mix, there are several routines which get data, and alert the user with text information.

REBOL [Title: ""] 
; header is still required, even if a title isn't included  

effect-types: ["Invert" "Grayscale" "Emboss" "Blur" "Sharpen"
                "Flip 1x1" "Rotate 90" "Tint 83" "Contrast 66"
                "Luma 150" "None"] 
; this creates a short list of image effects that are built 
; into Rebol, and assigns the variable word "effect-types" 
; to the block

do %/c/play_sound.r
; The line above imports the simple "play-sound" function 
; created earlier in the tutorial.  For this program to work 
; correctly as it is, the play_sound.r file should be saved 
; to C:\

image-url: to-url request-text/title/default {
    Enter the url of an image to use:} trim {
    http://rebol.com/view/demos/palms.jpg}
; ask user for the location of a new image (with a default
; location), and assign it to the word "new-image"

gui: [

; The following code displays the program menu, using a 
; "choice" button widget (a menu-select type of button 
; built in to Rebol).  The button is 160 pixels 
; across, and is placed at the uppermost, leftmost 
; pixel in the GUI (0x0) using the built-in word "at".  
; The action block for the button contains various 
; functions to be performed, based on the selected choice
; (using conditional "if" evaluations.  This could have
; been done with less code, using a "switch" syntax.  
; "If" was used, however, to demonstrate that there are
; always alternate ways to express yourself in code - 
; just like in spoken language.).

    across 
    ; horizontally aligns all the following GUI widgets, 
    ; so they appear next to each other in the layout 
    ; (the default behavior in Rebol is to align elements 
    ; vertically).
    space -1 
    ; changes the spacing of consecutive widgets so they're
    ; on top of each other
    at 20x2 choice 160 tan trim {
        Save Image} "View Saved Image" "Download New Image" trim {
            -------------} "Exit" [
        if value = "Save Image" [ 
            filename: to-file request-file/title/file/save trim {
                Save file as:} "Save" %/c/effectedimage.png
            ; request a filename to save the image as, 
            ; defaults to "c:\effectedimage.png"
            save/png filename to-image picture 
            ; save the image to hard drive
        ]
        if value = "View Saved Image" [
            view-filename: to-file request-file/title/file trim {
                View file:} "Save" filename
            view/new center-face layout [image load view-filename]
            ; read the selected image from the hard drive 
            ; and display it in a new GUI window
        ] 
        if value = "Download New Image" [
            new-image: load to-url request-text/title/default trim {
                Enter a new image url} trim {
                http://www.rebol.com/view/bay.jpg}
            ; ask for the location of a new image, 
            ; and assign it to the word "new-image"
            picture/image: new-image
            ; replace the old image with the new one
            show picture ; update the GUI display
        ]
        if value = "-------------" [] ; don't do anything
        if value = "Exit" [
            play-sound %/c/windows/media/tada.wav
            quit ; exit the program 
        ]       
    ]

    choice tan "Info" "About" 
        [alert "Image Effector - Copyright 2005, Nick Antonaccio"] 
    ; a simple "about" box  

    below 
    ; vertically aligns successive GUI widgets - 
    ; the opposite of "across"
    space 5 
    ; spread out the widgets some more
    pad 2 
    ; put 2 pixels of blank space before the next widget
    box 550x1 white 
    ; draws a line 550 pixels wide, 1 pixel tall 
    ; (just a cosmetic separator)
    pad 10  
    ; put some more space between widgets
    vh1 "Double click each effect in the list on the right:" 
    ; a big text header for the GUI     
    return 
    ; advances to the next row in the GUI
    across

    picture: image load image-url
    ; get the image entered at the beginning of the program, 
    ; and give it a label

    text-list data effect-types [
        current-effect: to-string value 
        picture/effect: to-block form current-effect 
        show picture
    ]

    ; The code above creates a text-list gui widget 
    ; and assigns a block of actions to it, to be run whenever the 
    ; user clicks on the list.  The block of actions is indented 
    ; and each action is placed on separate line for readability.  
    ; The first line assigns the word "current-effect" to the value 
    ; which the user has selected from the list.  The second line 
    ; applies that effect to the image (the words "to-block" and "form"
    ; are required for the way effects are applied syntactically.  
    ; The third line displays the newly effected image.  The "show" 
    ; word is _very_ important.  It needs to be used whenever a GUI 
    ; element is updated.
]

view/options center-face layout gui [no-title] 
; display the gui block above
; "/options [no title]" displays the window without a title bar 
; (so it can't be moved around), 
; and "center-face" centers the window on the screen

5.6 Guitar Chord Diagram Maker

This program creates, saves, and prints collections of guitar chord fretboard diagrams. It demonstrates some common and useful file, data, and GUI manipulation techniques, including the drag-and-drop "feel" technique, used here to slide the pieces around the screen. It also demonstrates the very important technique of printing output to html, and then previewing in a browser (to be printed on paper, uploaded to a web site, etc.). This is a useful cross-platform technique that can be used to view and print formatted hard copies of Rebol data:

Rebol [Title: "Guitar Chord Diagram Maker"]

; load embedded images:

fretboard: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAA2UlEQVR4nO3YQQqDQBAF0XTIwXtuNjfrLITs0rowGqbqbRWxEEL+
RFU9wJ53v8DN7Gezn81+NvvZXv3liLjmPX6n/4NL//72s9l/QGbWd5m53dbc8/kR
uv5RJ/QvzH42+9nsZ7OfzX62nfOPzZzzyNUxxh8+qhfVHo94/rM49y+b/Wz2s9nP
Zj+b/WzuX/cvmfuXzX42+9nsZ7OfzX4296/7l8z9y2Y/m/1s9rPZz2Y/m/vX/Uvm
/mWzn81+NvvZ7Gezn8396/4l2/n+y6N/f/vZ7Gezn81+tjenRWXD3TC8nAAAAABJ
RU5ErkJggg==
}

barimage: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAEoAAAAFCAIAAABtvO2fAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAHElEQVR4nGNsaGhgGL6AaaAdQFsw6r2hDIa59wCf/AGKgzU3RwAA
AABJRU5ErkJggg==
}

dot: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAFElEQVR4nGNsaGhgwA2Y8MiNYGkA22EBlPG3fjQAAAAASUVORK5C
YII=
}

; Gui Design:

; The routine below was defined in the section about "feel":

movestyle: [
    engage: func [f a e] [
        if a = 'down [
            initial-position: e/offset
            remove find f/parent-face/pane f
            append f/parent-face/pane f
        ]
        if find [over away] a [
            f/offset: f/offset + (e/offset - initial-position)
        ]
        show f
    ]
]

; With that defined, adding "feel movestyle" to any widget
; makes it movable within the GUI.  It's very useful for all 
; sorts of graphic applications...  If you want to pursue 
; building graphic layouts that respond to user events, learning
; all about how "feel" works in Rebol is very important.  See
; the URL above for more info.

gui: [
    backdrop white
        ; makes the GUI background white
    currentfretboard: image fretboard 255x300
        ; show the fretboard image, and resize it 
        ; (the saved image is actually 85x100 pixels)
    currentbar: image barimage 240x15 feel movestyle
        ; Show the bar image, resize it, and make it movable.
        ; Notice the "feel movestyle".  Thats' what enables 
        ; the dragging.
    text "INSTRUCTIONS:" underline
    text "Drag dots and other widgets onto the fretboard."
    across  
    text "Resize the fretboard:"
    tab 
    ; "tab" aligns the next GUI element with a predefined 
    ; column spacer
    rotary "255x300" "170x200" "85x100" [
        currentfretboard/size: to-pair value show currentfretboard
        switch value [
            "255x300" [currentbar/size: 240x15 show currentbar]
            "170x200" [currentbar/size: 160x10 show currentbar]
            "85x100" [currentbar/size: 80x5 show currentbar]
        ]
    ]   

    ; The rotary button above lets you select a size for the 
    ; fretboard.  In the action block, the fretboard image is 
    ; resized, and then the bar image is also resized,
    ; according to the value chosen.  This keeps the bar size 
    ; proportioned correctly to the fretboard image.  
    ; After each resize, the GUI is updated to actually display 
    ; the changed image.  The built-in word "show" updates the 
    ; display.  This needs to be done whenever a widget is 
    ; changed within a GUI.  Be aware of this - not "show"ing 
    ; a changed GUI element is an easily overlooked source of 
    ; errors.

    return
    button "Save Diagram" [
        filename: to-file request-file/save/file "1.png"
        save/png filename to-image currentfretboard
    ]

    ; The action block of the above button requests a filename 
    ; from the user, and then saves the current fretboard image 
    ; to that filename.

    tab

    ; The action block of the button below prints out a user-
    ; selected set of images to an html page, where they can be
    ; viewed together, uploaded the Internet, sent to a printer,
    ; etc.  

    button "Print" [
        filelist: 
            sort request-file/title "Select image(s) to print:"
        ; Get a list of files to print.
        html: copy "<html><body>"
        ; start creating a block that holds the html layout, 
        ; and give it the label "html".
        foreach file filelist [
            append html rejoin [
                {<img src="file:///} to-local-file file {">}
            ]
        ]
        ; The foreach loop builds an html layout that displays
        ; each of the selected images.  
        append html [</body></html>]
        ; finish up the html layout.  Now the variable "html" 
        ; contains a complete html document that will be 
        ; written to the hard drive and opened in the default
        ; browser.  The code below accomplishes that.
        write %chords.html trim/auto html
        browse %chords.html 
    ]
]

; Each of the following loops puts 50 movable dots onto the GUI,
; all at the same locations.  This creates three stacks of dots
; that the user can move around the screen and put onto the 
; fretboard.  There are three sizes to accommodate the resizing
; feature of the fretboard image.  Notice the "feel movestyle" 
; code at the end of each line.  Again, that's what makes the 
; dots draggable.  

loop 50 [append gui [at 275x50 image dot 30x30 feel movestyle]]
loop 50 [append gui [at 275x100 image dot 20x20 feel movestyle]]
loop 50 [append gui [at 275x140 image dot 10x10 feel movestyle]]

; The following loops add some additional dragable widgets to 
; the GUI.

loop 6 [append gui [at 273x165 text "X" bold feel movestyle]]
loop 6 [append gui [at 273x185 text "O" bold feel movestyle]]

view layout gui

5.7 Listview Database Front End

This example uses the listview module found at http://www.hmkdesign.dk/rebol/list-view/list-view.r. The listview module handles all the main work of displaying, sorting, filtering, altering, and manipulating data, with a familiar user interface that's easy to program. Documentation is available at http://www.hmkdesign.dk/rebol/list-view/list-view.html. This example downloads the list-view module from the Internet, and then imports it from the hard drive. To avoid that step, the module could simply be embedded in the code. Clicking on a column header in the example below sorts the data by the selected column, ascending or descending. Clicking the diamond in the upper right hand corner returns the data to its unsorted order. Selecting a row of data with the mouse allows each cell to be edited directly. Because inline editing is possible, no additional GUI widgets are required for data input/output. That's a powerful tool which is useful in a wide variety of situations.

Rebol [title: "Database"]

; The function below watches for the GUI close button, to keep
; the program from being shut down accidentally.  The code was
; adjusted from an example at: 
; http://www.rebolforces.com/view-faq.html

evt-close: func [face event] [
    either event/type = 'close [
        inform layout [
            across
            Button "Save Changes" [
                ; when the save button is clicked, a backup data
                ; file is automatically created:
                backup-file: to-file rejoin ["backup_" now/date]
                write backup-file read %database.db
                save %database.db theview/data quit
            ]
            Button "Lose Changes" [quit]
            Button "CANCEL" [hide-popup]
        ] none ] [ 
        event
    ]
]
insert-event-func :evt-close

; The code below is the list-view.r module in compressed format.
; It's decompressed, and then imported with the "do" command.
; Like any other module, you don't have to understand how it was
; programmed (the uncompressed code is all just native Rebol).
; You just have to include the compressed blob, and learn how
; to use it...

if not exists? %list-view.r [write %list-view.r read
    http://www.hmkdesign.dk/rebol/list-view/list-view.r
]
do %list-view.r

; The following conditional evaluation checks to see if a
; database file exists.  If not, it creates a file with 
; some empty blocks:

if not exists? %database.db [write %database.db {[][]}]

; Now the stored data is read into a variable word:

database: load %database.db

; Here's the guts of the program.  Be sure to read the 
; list-view documentation to see how the widget works.

view center-face gui: layout [
    h3 {To enter data, double-click any row, and type directly 
        into the listview.  Click column headers to sort:}
    theview: list-view 775x200 with [
        data-columns: [Student Teacher Day Time Phone 
            Parent Age Payments Reschedule Notes]
        data: copy database
        tri-state-sort: false
        editable?: true
    ]
    across
    button "add row" [theview/insert-row]
    button "remove row" [
        if (to-string request-list "Are you sure?" 
                [yes no]) = "yes" [
            theview/remove-row
        ]
    ]
    button "filter data" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        if filter-text <> none [
            theview/filter-string: filter-text
            theview/update
        ]
    ]
    button "save db" [
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
    ]
]

5.8 Peer-to-Peer Instant Messenger

This example allows two users to connect directly via a TCP/IP network port (a user selected IP address and port setting) to exchange messages. The techniques it demonstrates can be used to enable all sorts of networked application activity. Unlike the FTP Chat Room above, the text is sent directly between two computers, across a network socket connection (on either the Internet or a local network). The application can act as either client or server, depending on the user's selection. As with any client-server configuration, 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. Program operation can be demonstrated on a single computer. For instructions, see the help documentation included in the code.

REBOL [Title: "Peer-to-Peer Instant Messenger"]

connected: false
; This is a flag variable, used to mark whether or not the
; two machines have already connected.  It helps to more 
; gracefully handle connection and shutdown actions throughout
; the script.

; The code below traps the close button (just a variation of 
; the routine used in the previous database example).  It 
; assures that all open ports are closed, and sends a message
; to the remote machine that the connection has been terminated.
; Notice that the lines in the disconnect message are sent
; in reverse order.  When they're received by the other machine,
; they're printed out one at a time, each line on top of the
; previous - so it appears correctly when viewed on the other
; side.

insert-event-func closedown: func [face event] [
    either event/type = 'close [
        if connected [
            insert port trim {
                *************************************************
                AND RECONNECT.
                YOU MUST RESTART THE APPLICATION
                TO CONTINUE WITH ANOTHER CHAT,
                THE REMOTE PARTY HAS DISCONNECTED.
                *************************************************
            }
            close port
            if mode/text = "Server Mode" [close listen]
        ]
        quit
    ] [event]
]

view/new center-face gui: layout [
    across
    at 5x2  ; this code positions the following items in the GUI

    ; The text below appears as a menu option in the upper
    ; left hand corner of the GUI.  When it's clicked, the
    ; text contained in the "display" area is saved to a 
    ; user selected file.

    text bold "Save Chat" [
        filename: to-file request-file/title/file/save trim {
            Save file as:} "Save" %/c/chat.txt
        write filename display/text 
    ]

    ; The text below is another menu option.  It displays
    ; the user's IP address when clicked.  It relies on a
    ; public web server to find the external address
    ; (whatismyip.com).  The "parse" command is used to 
    ; extract the IP address from the page.  Parsing is 
    ; covered in a separate dedicated section later in 
    ; the tutorial.

    text bold "Lookup IP" [
        parse read http://whatismyip.com [
            thru <title> copy my-ip to </title>]
        parse my-ip [
            thru "-" copy stripped-ip to end]
        alert to-string rejoin [
            "External: " trim/all stripped-ip "  "
            "Internal: " read join dns:// read dns://
        ]
    ]

    ; The text below is a third menu option.  It displays 
    ; the help text when clicked.

    text bold "Help" [
        alert {
        Enter the IP address and port number in the fields
        provided.  If you will listen for others to call you, 
        use the rotary button to select "Server Mode" (you
        must have an exposed IP address and/or an open port
        to accept an incoming chat).  Select "Client Mode" if
        you will connect to another's chat server (you can do
        that even if you're behind an unconfigured firewall, 
        router, etc.).  Click "Connect" to begin the chat. 
        To test the application on one machine, open two
        instances of the chat application, leave the IP set
        to "localhost" on both.  Set one instance to run as 
        server, and the other as client, then click connect.
        You can edit the chat text directly in the display
        area, and you can save the text to a local file.
        }
    ]
    return

    ; Below are the widgets used to enter connection info.
    ; Notice the labels assigned to each item.  Later, the
    ; text contained in these widgets is referred to as
    ; <label>/text.  Take a good look at the action block 
    ; for the rotary button too.  Whenever it's clicked, 
    ; it either hides or shows the other widgets.  When in
    ; server mode, no connection IP address is needed - the
    ; application just waits for a connection on the given
    ; port.  Hiding the IP address field spares the user some
    ; confusion.

    lab1: h3 "IP Address:"  IP: field "localhost" 102
    lab2: h3 "Port:" portspec: field "9083" 50
    mode: rotary 120 "Client Mode" "Server Mode" [
        either value = "Client Mode" [
            show lab1 show IP
        ][
            hide lab1 hide IP
        ]
    ]

    ; Below is the connect button, and the large action block
    ; that does most of the work.  When the button is clicked,
    ; it's first hidden, so that the user isn't tempted to
    ; open the port again (that would cause an error).  Then,
    ; a TCP/IP port is opened - the type (server/client) is
    ; determined using an "either" construct.  If an error
    ; occurs in either of the port opening operations, the
    ; error is trapped and the user is alerted with a message -
    ; that's more graceful and informative than letting the 
    ; program crash with an error.  Notice that the IP
    ; address and port info are gathered from the fields above.
    ; If the server mode is selected (i.e., if the "mode" button
    ; above isn't displaying the text "Client Mode"), then the
    ; the TCP ports are opened in listening mode - waiting
    ; for a client to connect.  If the client mode is selected,
    ; an attempt is made to open a direct connection to the IP
    ; address and port selected. 

    cnnct: button red "Connect" [
        hide cnnct
        either mode/text = "Client Mode" [
            if error? try [
                port: open/direct/lines/no-wait to-url rejoin [
                    "tcp://" IP/text ":" portspec/text]
            ][alert "Server is not responding." return]
        ][
            if error? try [
                listen: open/direct/lines/no-wait to-url rejoin [
                    "tcp://:" portspec/text]
                wait listen
                port: first listen
            ][alert "Server is already running." return]
        ]

        ; After the ports have been opened, the text entry field
        ; is highlighted, and the connection flag is set to true.
        ; Focusing on the text entry field provides a nice visual
        ; cue to the user that the connection has been made, but
        ; it's not required.

        focus entry
        connected: true

        ; The forever loop below continuously waits for data to
        ; appear in the open network connection.  Whenever data
        ; is inserted on the other side, it's copied and
        ; appended to the current text in the display area, and
        ; then the display area is updated to show the new text.

        forever [
            wait port
            foreach msg any [copy port []] [
                display/text: rejoin [
                    ">>>  "msg newline display/text]
            ]
            show display
        ]
    ]

    ; Below are the display area and text entry fields.  Notice
    ; the labels assigned to each.  The "return"s just put each
    ; widget on a new line in the GUI (because the layout mode
    ; is set to "across" above).

    return  display: area "" 537x500
    return  entry: field 428  ; the numbers are pixel sizes

    ; The send button below does some more important work.
    ; First, it checks to see if the connection has been made
    ; (using the flag set above).  If so, it inserts the text
    ; contained in the "entry" field above into the open TCP/IP
    ; port, to be picked up by the remote machine - if the 
    ; connection has been made, the program on the other end
    ; is waiting to read any data inserted into that port.
    ; After sending the data across the network connection,
    ; the text is appended to the local current text display
    ; area, and the display is updated:

    button "Send Text" [
        if connected [
            insert port entry/text focus entry
            display/text: rejoin [
                "<<<  " entry/text newline display/text]
            show display
        ]
    ]
]

show gui do-events  ; these are required because the "/new"
                    ; refinement is used above.

5.9 Textris

This is a console mode version of Tetris (using only text - no GUI or graphics functions). It makes use of a shortened version of the "TUI" dialectto position characters on screen. A detailed case study of this example is available at http://musiclessonz.com/rebol_tutorial.html. Here's a quick synopsis of the program:

  • The TUI dialect is defined.
  • The "shape" block, containing the TUI instructions for drawing each shape is defined.
  • The "floor", "oc", and "width" coordinate blocks are defined. The "score" variable is also defined.
  • The backdrop characters (left, right, and bottom barriers), instructions, headers, and score are printed.
  • A forever loop runs the main actions of the program. The subsections of that loop are:
    • A shape is printed on the screen.
    • User keystrokes are watched for.
    • A switch structure decides what to do with entered keystrokes (l = move right, k = move left, o = rotate shape, p = pause).
    • Another switch structure determines which shape to print when the current shape is rotated.
    • The currently printed shape is erased.
    • Two foreach loops check whether the current shape has reached a position at which it should stop falling.
    • If the piece has reached a stopping point, the coordinates occupied by the piece are added to the "floor" block.
    • The shape is printed at its final resting place.
    • If the current shape touches the ceiling, the game ends.
    • The score is updated.
    • If any rows have been completely filled in, their coordinates are removed from the floor block, the coordinates of all other characters are moved down a row, and the screen is reprinted with the new floor coordinates and the new score.
    • The forever loop continues.

Take a break from coding, and play a few games!

Rebol [Title: "Textris"]

tui: func [commands [block!]] [
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    arg: parse commands [
        any [
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set arg string! (append string arg)
        ]
        end
    ]
    string
]

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]
    ;
    ["    "]
    [" " down 1 left 1 " " down 1 left 1 " " down 1 left 1 " "]
    ["   " down 1 left 2 " "]
    [right 1 " " down 1 left 2 "  " down 1 left 1 " "]
    [right 1 " " down 1 left 2 "   "]
    [" " down 1 left 1 "  " down 1 left 2 " "]
    ["   " down 1 left 3 " "]
    ["  " down 1 left 1 " " down 1 left 1 " "]
    [right 2 " " down 1 left 3 "   "]
    [" " down 1 left 1 " " down 1 left 1 "  "]
    ["   " down 1 left 1 " "]
    [right 1 " " down 1 left 1 " " down 1 left 2 "  "]
    [" " down 1 left 1 "   "]
    ["  " down 1 left 2 " " down 1 left 1 " "]
    ["  " down 1 left 1 "  "]
    [right 1 " " down 1 left 2 "  " down 1 left 2 " "]
    [right 1 "  " down 1 left 3 "  "]
    [" " down 1 left 1 "  " down 1 left 1 " "]
    ["  " down 1 left 2 "  "]
]
floor:  [
    21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13 21x14 21x15
]
oc:  [ 
    [0x0 0x1 0x2 0x3] [0x0 1x0 2x0 3x0] [0x0 0x1 0x2 1x1]
    [0x1 1x0 1x1 2x1] [0x1 1x0 1x1 1x2] [0x0 1x0 1x1 2x0]
    [0x0 0x1 0x2 1x0] [0x0 0x1 1x1 2x1] [0x2 1x0 1x1 1x2]
    [0x0 1x0 2x0 2x1] [0x0 0x1 0x2 1x2] [0x1 1x1 2x0 2x1]
    [0x0 1x0 1x1 1x2] [0x0 0x1 1x0 2x0] [0x0 0x1 1x1 1x2]
    [0x1 1x0 1x1 2x0] [0x1 0x2 1x0 1x1] [0x0 1x0 1x1 2x1]
    [0x0 0x1 1x0 1x1] 
]
width: [4 1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2]
score: 0

prin tui [clear]
a-line: copy [] loop 11 [append a-line " "] 
a-line: rejoin ["   |"  to-string a-line "|"]
loop 20 [print a-line] prin "   " loop 13 [prin "+"] print ""
print tui compose [
    at 4x21 "TEXTRIS" at 5x21 "-------" 
    at 7x20 "Use arrow keys" at 8x20 "to move/spin."
    at 10x20 "'P' = pause"
    at 13x20 "SCORE:  " (to-string score)
]

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 9
    for i 1 20 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            switch/default to-string copy keys [
                "p" [
                    print tui [
                        at 23x0 "Press [Enter] to continue"
                    ]
                    ask ""
                    print tui [
                        at 24x0 "                              "
                        at 23x0 "                              "
                    ]
                ]
                "^[[D" [if (xpos > 5) [
                        xpos: xpos - 1
                ]]
                "^[[C" [if (xpos < (16 - compose width/(r))) [
                        xpos: xpos + 1
                ]]
                "^[[A" [if (xpos < (16 - compose width/(r)))  [
                        switch to-string r [
                            "1" [r: 2]
                            "2" [r: 1]
                            "3" [r: 6]
                            "4" [r: 3]
                            "5" [r: 4]
                            "6" [r: 5]
                            "7" [r: 10]
                            "8" [r: 7]
                            "9" [r: 8]
                            "10" [r: 9]
                            "11" [r: 14]
                            "12" [r: 11]
                            "13" [r: 12]
                            "14" [r: 13]
                            "15" [r: 16]
                            "16" [r: 15]
                            "17" [r: 18]
                            "18" [r: 17]
                            "19" [r: 19]
                        ]
                    ]
                ]                   
            ] []
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
        stop: false
        foreach po compose oc/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: r
                    stop: true
                    break
                ]
            ]
        ]
        foreach po compose oc/(old-r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  old-xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: old-r
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [
            left-col: second pos 
            width-of-shape: length? compose oc/(stop-shape-num)
            right-col: left-col + width-of-shape - 1
            counter: 1
            for current-column left-col right-col 1 [
                add-coord: compose oc/(stop-shape-num)/(counter)
                new-floor-coord: (pos + add-coord)
                append floor new-floor-coord
                counter: counter + 1
            ]
            break
        ]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(old-r)]
    if (first pos) < 2 [
        prin tui [at 23x0]
        print "   GAME OVER!!!^/^/"
        halt
    ]
    score: score + 10
    print tui compose [at 13x28 (to-string score)]
    for row 1 20 1 [
        line-is-full: true
        for colmn 5 15 1 [
            each-coord: to-pair rejoin [row "x" colmn]
            if not find floor each-coord [
                line-is-full: false
                break
            ]
        ]
        if line-is-full = true [
            remove-each cor floor [(first cor) = row]
            new-floor: copy [
                21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13
                21x14 21x15
            ]
            foreach cords floor [
                either ((first cords) < row) [
                    append new-floor (cords + 1x0)
                ][
                    append new-floor cords
                ]
            ]
            floor: copy unique new-floor
            score: score + 1000
            prin tui [clear]
            loop 20 [print a-line] 
            prin "   " loop 13 [prin "+"] print ""
            print tui compose [
                at 4x21 "TEXTRIS" at 5x21 "-------" 
                at 7x20 "Use arrow keys" at 8x20 "to move/spin."
                at 10x20 "'P' = pause"
                at 13x20 "SCORE:  " (to-string score)
            ]
            foreach was-here floor [
                if not ((first was-here) = 21) [
                    prin tui compose [at (was-here)]
                    prin "#"
                ]
            ]
        ]
    ]
]

6. Additional Topics

6.1 2D Drawing, Graphics, and Animation

With Rebol's "view layout" ("VID") 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 layout' GUI, with a box widget that's used as the viewing screen. "Effect" and "draw" functions are then added to the box definition, and a block is passed to the draw function which contains more functions that actually 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:

view layout [box 400x400 effect [draw [line 10x39 322x211]]]
;  "line" is a draw function

Here's the exact same example indented and broken apart onto several lines:

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

Any number of shape elements (functions) can be included in the draw block:

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

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:

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

Gradients and other effects can be easily applied to the elements:

view layout [
    box 400x220 effect [
        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
        ]
        gradmul 180.180.210 60.60.90
    ]
]

Drawn shapes are automatically anti-aliased (lines are smoothed), but that default feature can be disabled:

view layout [
    box 400x400 black effect [
        draw [
            ;  with default smoothing:
            circle 150x150 100
            ;  without smoothing:
            anti-alias off
            circle 250x250 100
        ]
    ]
]

6.1.1 Animation

Animations can be created with draw by changing the coordinates of image elements. The fundamental process is as follows:

  1. Assign a word label to the box in which the drawing takes place (the word "scrn" is used in the following examples).
  2. Create a new draw block in which the characteristics of the graphic elements (position, size, etc.) are changed.
  3. Assign the new block to "{yourlabel}/effect/draw" (i.e., "scrn/label/draw: [changed draw block]" in this case).
  4. Display the changes with a "show {yourlabel}" function (i.e., "show scrn" in this case).

Here's a basic example that moves a circle to a new position when the button is pressed:

view layout [
    scrn: box 400x400 black effect [draw [circle 200x200 20]]
    btn "Move" [
        scrn/effect/draw: [circle 200x300 20]  ; replace the block above
        show scrn
    ]
]

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:

pos: 200x50
view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Move Smoothly" [
        loop 50 [
            ; increment the "y" value of the coordinate:
            pos/y: pos/y + 1
            scrn/effect/draw: copy [circle pos 20]
            show scrn
        ]
    ]
]

Animation coordinates (and other draw properties) can also be stored in blocks:

pos: 200x200
coords: [70x346 368x99 143x45 80x125 237x298 200x200]

view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Jump Around" [
        foreach coord coords [
            scrn/effect/draw: copy [circle coord 20]
            show scrn
            wait 1
        ]
    ]
]

Other data sources can also serve to control movement. In the next example, user data input moves the circle around the screen. Notice the use of the "feel" function to update the screen every 10th of a second ("rate 0:0:0.1"). Since feel is used to watch, wait for, and respond to window events, you'll likely need it in many situations where animation is used, such as in games:

pos: 200x200
view layout [
    scrn: box 400x400 black rate 0:0:0.1 feel [
        engage: func [face action event] [
            if action = 'time [
                scrn/effect/draw: copy []
                append scrn/effect/draw [circle pos 20]
                show scrn
            ]   
        ] 
    ] effect [ draw [] ]
    across
    btn "Up" [pos/y: pos/y - 10]
    btn "Down" [pos/y: pos/y + 10]
    btn "Right" [pos/x: pos/x + 10]
    btn "Left" [pos/x: pos/x - 10]
]

Here's a very simple paint program that also uses the feel function. Whenever a mouse-down action is detected, the coordinate of the mouse event ("event/offset") is added to the draw block (i.e., a new dot is added to the screen wherever the mouse is clicked), and then the block is shown:

view layout [
    scrn: box black 400x400 feel [
        engage: func [face action event] [
            if find [down over] action [
                append scrn/effect/draw event/offset
                show scrn
            ]
            if action = 'up [append scrn/effect/draw 'line]
        ]
    ] effect [draw [line]]
]

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:

view layout [
    box 400x400 black effect [
        draw [
            image logo.gif 10x10 350x200 250x300 50x300
            ; "logo.gif" is built into the Rebol interpreter
        ]
    ]
]

Here's an example that incoporates the image scaling technique above with some animation. IMPORTANT: In the following example, the coordinate position calculations occur inside the draw block. Whenever such evaluations occur inside a draw block (i.e., when values are added or subtracted to a variable coordinate position, size, etc.), a "reduce" or "compose" function must be used to evaluate those values. Notice the tick mark (') next to the "image" function. Function words inside a reduced block need to be marked with that symbol to evaluate correctly:

pos: 300x300
view layout [
    scrn: box pos black effect [
        draw [image logo.gif 0x0 300x0 300x300 0x300
    ]]
    btn "Animate" [
        for point 1 140 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (pos - 300x300)
                (1x1 + (to-pair rejoin ["300x" point]))
                (pos - (to-pair rejoin ["1x" point]))
                (pos - 300x0)
            ]
            show scrn
        ]
        for point 1 300 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (1x1 + (to-pair rejoin ["1x" point]))
                (pos - 0x300)
                (pos - 0x0)
                (pos - (to-pair rejoin [point "x1"]))
            ]
            show scrn
        ]
        ; no "reduce" is required below, because no calculations
        ; occur in the draw block - they're just static coords: 
        scrn/effect/draw: copy [
            image logo.gif 0x0 300x0 300x300 0x300
        ]
        show scrn
    ]
]

Here's another example of a draw block which contains evaluated calculations, and therefore requires "reduce"d evaluation:

view layout [
    scrn: box 400x400 black effect [draw [line 0x0 400x400]]
    btn "Spin" [
        startpoint: 0x0
        endpoint: 400x400
        loop 400 [
            scrn/effect/draw: copy reduce [
                'line 
                startpoint: startpoint + 0x1
                endpoint: endpoint - 0x1
            ]
            show scrn
        ]
    ]
]

The useful little paint program at http://rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=paintplus.r consists of only 238 lines of code. Take a look at it to see how efficient Rebol's draw code is:

url: http://rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?
script: "script-name=paintplus.r"
do rejoin [url script]
paint none []

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).

6.2 3D Graphics with r3D

The "r3D" modeling engine by Andrew Hoadley is built entirely from native Rebol 2D draw functions. It demonstrates the significantly powerful potential of draw. The examples below show some of what you can accomplish with r3D:

do http://www.rebol.net/demos/BF02D682713522AA/i-rebot.r
do http://www.rebol.net/demos/BF02D682713522AA/histogram.r
do http://www.rebol.net/demos/BF02D682713522AA/objective.r

The r3D engine is small. Here's the entire module in compressed, embeddable format (this is all just standard Rebol code compressed into a more compact format). To enable 3D graphics in your Rebol programs, just include this text in your code (paste it, or "do" it from a file). If you'd like to read and learn from the pure Rebol code that makes up this module, see the examples above (the r3D module is included in those examples as regular text code):

do to-string decompress 64#{
eJzdPGtT28iWn+Nf0cOXsTMYkGXznL1bBMzEtQSnjPMAipqSpTboRpa8kmwwv37P
Od0tdethOzNTu1VLJUTqPu9XP5VR/8Pwmj002NhPA37KdmL7cqfBzhfpcxTD63no
xfyFfYwcL+Ar6PnK48SPwlNm7R3sHTQeG43GGbuI5qvYf3pOWdNtsc7BweEuMzER
6jOPZ36C2MxP2DOP+WTFnmInTLm3y6Yx5yyaMvfZiZ/4Lksj5oQrNgd+gBBNUscP
/fCJOcwFbgiZPgOZJJqmL07MGTBwQo85SRK5vgMkmRe5ixkPUydFllM/4Alrps+c
7dxKpJ0W8fG4EzA/ZNinutiLDyZYpCzmSRr7LtLYBR5+6AYLDwVRAIE/8yUPJEB2
SJDsIgE1UNhdNos8f4r/ctJtvpgEfvK8yzwfiU8WKTQm2OjyELCELvtRzBIeBEjD
B9lJ5VzCXdIX+MzRrqm0FHF+eY5mpjZgqekiDoEpJxwvAsshH+D6b+6m2IYI0ygI
ohdUz41Cz0etklN03hg6nUm05KSRcHYYpSCwEAN9Mc8dLLuSZwfkn3BpNuANZnY0
lWIUIEkhBnzwAfCZRzExLWq7R0J87LPb4dX42/mozwa37PNo+HVw2b+EOL2F951d
9m0w/jj8MmYAMTq/Gd+x4RU7v7lj/zW4udxl/e+fR/3bWzYcscGnz9eDPrQNbi6u
v1wObv5gHwAPmNwMx+x68GkwBrrjIfGU1Ab9W6T3qT+6+Aiv5x8G14Px3S67Goxv
kOwV0D1nn89H48HFl+vzEfv8ZfR5eNsHCS4F5ZvBzdUIePU/9W/Ge8Ab2lj/K7yw
24/n19fE7fwL6DAiKS+Gn+9Ggz8+jtnH4fVlHxo/9EG48w/XfcENVLu4Ph982mWX
55/O/wABgQ90DIHQiCCljN8+9qkJWJ7Dn4vxYHiDylwMb8YjeN0FXUfjDPXb4LYP
OTwa3KJlrkZD4IB2BQw03g0m/fDmpi/ooNVN5wAQvn+57efSXPbPr4HaLcqgA+9R
DWn/1R+qP1BDwpSCJra99syBrHrdi1mbiUeK0SXEOQRc4E9iJ141zpCrG3OoFZCH
0ynUoxDyYDWXqQaFKZlG8YxSO2k0kLDvAYyfrk5ZA2qm9gPVkB3kf40+bNP7S506
QGWnAsByi2Jgqs9BG38JJXu6CF0mpNm5EOo4TINQJsB6BT1LB6sAZvg88kFhjy1C
H0w3jWXNcB2oUs4OEfTYg8ddf+YEvzyyHQ/LCIJg2XJCyG9ZNYo021Bfwl9TqF4p
86m2vPE4Yr/8stN4NO1GLzH3FkDsQTeSVL/WkOq91l5rAZDKPmiH/z7SIIZmJZcH
YMAao6p+LHDCqMJK3w0rZVSgXZjWCSIwDRoKAvHVTwTWXQ3WXQlrpWHd12Ddl7De
JJa0eJWRv5eto7rv6g13vz5IH1WUJq4T1JmS+tYYUfRHYmyCaYLDpg5lL2Tm960M
uo7C3VbG3ZZCjaG/F2xTMtqdBlBp0ftNMW6YO45gFsK/GwY3jKoEhKrnBO6CwiYK
5QCegGKc1ElwiIYWN0ogsYMFTwgLmpG4eGDfqQ1ARKt4gNZSqCmRdfG155JqGll6
brYF51aNKTLRZJuOW4tQJ1CFPe8Me94V7fk3DXqXGfROM+hdZtA706Cyu6R81lKp
rdLMbCtCS0PftUqOuFtjyWqrmmVVGPLeMKSeaf+AHe8zO95rdrzP7HhfZcd7Xe/7
lqmJoW0OnZEuab7RPEU3bIWgno3IhDLE254fc1qVGHa9hAka/jxMgsj9gVXsK816
bIbTAViD4CCdoWIlmwY4lxf1bzFnBewvczVtmjkrMZGnZVlOgr+6fA6rDyeGeT0P
cLAHIQS9fSAD0/pXrI9sRb/f8LeqRPRySlO2EGdasCjiiE2dr1Wd+ObGUZLM4wh8
maLInkRYbYWAFhICNczZh2jdtzJS4lEIKZ+LgSFxOjlOR8PprMOxcxxbw7GrcbSY
2Fi85BwR4hQd1BYDmBEloulPBWL6/IwWaDZ5dMZTmEgyXEHa4HNYTePqDlwqgkIk
oCLzZxr9GUTRjz+dNKdmxJX+oyD0QEHXP2aBLFx5u5jALAccV8WmqMg/FVUQUiD0
/1ZUFbTYt346xooUOj8dcUUK9t+Nv1n3ddY1om5m5WEx61QGQMyTRZDmMyl8OxUb
Pg8CEFZk3HGf2YPDJsxl3iORzYR15nMOQ4TAzJ1gjnQOew8C7Fst9htrTsRLj15c
8XJCL54Es1s5biWhjk7oUCdkHRiUuq1qArZO4MggYBkEejUEujqBY4NAxyBwqBF4
NJR61EyuBhrw4dI2fZi7cLnGg9lcM+WzOTSZPiz6tc6xZb9Kcuscu9T8upSucemZ
rOzV66/mIGKNYu2/sCRiLzgVCXDAW8LiP5+WpPwZFoGghj+HXrE6wDKFVag5dRKo
m9LUfrhsv5wqkZti6SkVAcc1asNWwVggPRHZDNrZHtReAwoyqglTuojDqqhoO3Hs
rIzYWPIYN/2SQvGfddcFCiLx120yXkBqXDYlfSYriiCxH3Vfl7Qj9dxoBlWQt4vV
i9aqUP1TY6hk1ALzKBiNAg5+ZxbrvnYleHVtE1PUWbcdcJi9wa+n9Pk/NQYqEn3a
AoWxF8ffMGI8BAiKfpIb58RqQ0rnB2iSOPsPdqAniVJY28kyY98PfdyIxSGO9rKF
RWnniHaH/RgURDFWar+c5C25LtcFBrOiNjBIRjAnF2QyXRxJuKyCVaWCLDOG9BAp
jEIlFrTn6I5MbhVMRNlnnYyDPn6c5TmNiJApSz9aJIrfRDSH/BWkDTieLmiSCQNk
gx+b++4PPW4EY0lKyo7lpz7VaH+nMhq3GD7PWIYOcVoIykxalS0zqDOz/R78PcFn
SBqoJrP9Q3w5wF+Avo+tR/iCsBYCY+sxviCwdZitUaC0wMzxp+QGf4GfIFmxLGGV
97imS/SDjoywID9Fe3t75SWjQEcPizg9Vi6S01TsNhXGyopS56NA8YcUey+UXQd0
ICltINLdgN+r67fW45+w7fjbG/A38d+Ev0k/+7HstkpnZUGQO6zRbJIL94UsYp4l
WrrUcqS19KjluNVi7QyvU8KzS3iHCq9RQOtpQEclNKmfyc8qIR6XEE/K/OwaNF1w
6Q+TX7dGUAOxkyGWXJUROiwROqkjdGRI0CshSsNUyH5UJYIhhuDQKfnLLvmrW2H3
Tik+7FJ8IF6jgGeVDG+XDNGtiCurZHe7bAeTYbcGsVO2s8nRrpG0UzazybFXQpSC
VWDaBsvDEuZJnaw2sazI9LxAJxyPl7WkZzUlWmh6XFNWZN6rwKkG6mxBoq5uyiCv
6bXX4lpb8K2tt1vwXY+7XqNehXfKPmm6kTjxSFrFoiyn3PlYXVOlrW4pDa1eKQ9h
WVqRUN1S5huohxlqsVZbdimlrIqiVOBqlXGPy7gnFVztWsxuOa0KXLu1EnfLmUW4
m+q2Va5XlbR6FaXbqqhZlUr0NlZvaRSr7DG9ZEhZDqpkMUqLgitFk2UUuJM61JOy
cyyD6VFZXmWAssAnBtOjsnDKyOVR6MQMOqsMZ9cJfGAOV1ZZ4l4JVelgWvikXuKy
w0Diqmru4d7wzA/hmfrg/ZQ1Zf1RdUGMGHmkNGUFyvo7pX7b6LdL/V2jv9vK1664
xqd7Y8vIx+tfeNYhl3OcNnASusmRCStvAfBUrcDqNx9etVL3UL3h0ASg90iNtYob
DIWVXL4EVHsOXpTKnWRzR0XbMV3+zI6pEmlpkTuWarMT3jvivaPebfFut9TKTd/X
/uvSNKrkedAlAM+2dQk6rcqCooNYCsXKhK5FsTI92zpXq5XtdOobCXoFk3YQ+zKm
BX7eBcl/L5yYt+MoSiFCmkshl3KHkEo5Q6iJhsmcce555g0KaMCbKBGbgF4ObUVO
IClDPJvjPm6wiFW+K27XyXMaNlmoq02zBay2AMR9Fmd0jqnTpFJFP+Wz7TbnBaiV
7Wo5bOP2fFNslDgClUwhWiayxdifrtglUUdEpqWyg6OJuNvk/L81WPtnDfZJ7nGZ
BlOtQHiyYmGtuRx1oywz1y6jK73yRL/STKF+4P9/Zaj3LNxkmkva5zcNI9r+slnC
KGzT/be/aR8cwtbZyJ8y2vHdwz3fzzFexFOig+AkgZPi9n/KvR2lvSONAbTVgEjn
wf+o1VHwNXYXls9OV3+65OJG5wprYn5A6wh95d0FdNcqWrAXPwjYE4zQZvfEcX8Q
IdqPz2s/E1JL/zaZ2lFvaRZQjngwLmbJY4YKuGwkxJFgn0hWj2GkPIwPBLMOxJZk
6o82WD6ozTEs1mzKSotmh3FMnsYx8ziOyOi9mntl36PJsnCOuKxliatP7C7womZW
w+bsL1waxsu/hWvD4HQ/5HsxgxyHdzxG8MDxohnvCzdG1GJo8hLFgWcGqbhBa7bB
hOrf8sZMATZcOsmt/wYxgZPuyo1xmjyK2S0+dTz8oIAH4rcQACqPEz4FPFlXITIg
s1nIOxA79iIB8u172Wse0gKIvBcp1W9k7iKZiMiQILTbDLm4pzkF7a4CNLhRgN+d
5Nh2TvkTol+QNPopiym+YPKNpMpRz/Lr3OLCsjpEhBiKGLg6mTsunnfh7d5ldq9Z
qq6oEI2vgHrKCueggiueoOC/BTS8rgUWFyAdXSjs8cNpRJHo+GHCOoyCg0IyVAd+
u8xpEawscIkYf1r4SQeICdOTe4CfJjw1eA6AspD0Qi3arqD5RtLItBECbmEs5wlk
ZIvElxdftZjWTpYyS3W8sp3ySM6Ri6NJFqWELVKu441VazvxZ7igU6lA0mcK6wml
4kkvjGcwCs+htCdRLKxVTAzs2I+5CKY8q4zwV82aqHm9msIgg2wXEMeqHxbmmnln
zg9xvhot0gCvJyZ0Bw/vEdCHNVBz0mcnlEeX9LUMkmNFQxUPvH+FVhimcgFg6LX2
cJX/qHHH8U+nOnES7jHwIQlBn/5AuCQbmSGNNnLU+D02atB+VZao6c8M1dkEYG8C
6NaOg9lyuyoftrnKIEJtzQkmxdYSswsWy7BGXtpsmc7mFv2GV3fOAvy8LclvyFTV
6YxMzR0ZqgVSmIJnjYSlrzHEB18wAyRUH7LpVR3eI4kMf2mdiolbho7dUNNyiE4l
RO4yHOQrAOxGMfzyWpbjop1EwUjUyg0taBkQnSIEGDjnn4O689PyJT3NFWXIfO4I
TcUw3qc7QGo2ZQBIjVQlfgOPtaPYy/qlv09Z2zqAH+1iJ8zXaWvjX1lMPOTQ1PNo
wHZqYTslWLsW1jZgZQ7lcatQdItmuaQu1hSDPZvrlYv1LRVrI7my0YV7X7fOs6y+
bzN7gsJ/gYWfPaQL4F49p8KFkKrjSxGSIjkiKMKQIG60CEWovq3LVRgtIy/CVaHr
LmLcGFV1NB9wxEaprPlEXYhJj6esqanxHtYPPbmnOTWqkzSAnE9pJULNItTcggSH
xVzmvw1lw33mkLK+/KBW5CbnHn1JCstbXB1RI5TMAIqJnmfYLhJHJr4mspAjA1UC
S8A8eEwwKbww/m/MMljdn2octZEAZKdu9jved8pzU+iXDbeKZ91dJoosvPRHt5Hy
Flo2Trg2cwAz0FUgX36JYHDUI8uMlULWGSGohDOlV64TkUluMD+gEyrqlR/gzLpP
YhKBEuZSeqMqJQXLEkqV7NnCX6bOb8xJ2piPtMrFVay5UWtao+AuSqcshwxjYC5C
IRUPwOVgz9po12ZeDd5LxFYFklHhdQIZbGF9rW8aXUeO1x5eXZmX7KdTz0mdTZcf
E5mR4WL2lV7h4Uq19D0oxgQm83IxIxxwFXqV2lTFnr2yGSxE3nAm1MCth8Y7yAsp
Bbjh93+xX0FG6Hj3Tiyedz5QeNMWVQprCobd+J38jrZtQfOnh0z5d0rO04y0GEyh
XS50VLut2jlqkbd3649bloKysfMniGbzoHfZUKbG0mSGX56oFmqi2D1lPclozh1h
7NzKxT0s4X/hkPz+AdBIo7bcmGNNyhXlV+JRcwpa+JmtNtHBUrclrbctaHX0Kb80
huqz9TiGCIG4qZwpzNRtWYRZ1cCsNJi3Gpg3k9nvmcMAKvddgV0dlMmwDopY6s5X
2aNGv8w4KqOuongMGYWZJ2uiYVN9vieLLLVTndRTh+7q0uYirat25R1yHFDpaNLz
6PIrPGJdh1SUAkLSsqaKTk0YqHYdteH4riJihUL5+ZoFHv474QU/9TRQyH+SkhGm
+XV8sEohZpFOhXHKFVnfYNWKa1PFZTuLEjqk/Zm9Q9oyxEovZgaO2mti1IH/P8aI
T6CO+jilCZM0hiUHOJ3OnuhTYg9mUBPczXmOXnB/AUJl4sPIG/vgR7x1HkCcnQmi
iTpcWEAQ7bFbDhx5EL3AwIrRxl+dEPdAoikSw2iSXy1IZNy4RylHtpdvX56xmwi/
bcfGZDHH/2kD5vugkNrP2cuCWPy/B9AYR4sncfebtsVxnANX0jBHWzdnjQYq1Za7
fvKi9lm2AKTXvNY+0BGDZeyS5+3tqo72+p46WupvJY9iR3t9T94hA07oKEILJv4h
qIoj6QuuHl8gZtXExVCc9dihKQ68d4yWDrQcFVqOmG202NByXGg5Zl2jpQstvUJL
D/jpLcdA57DQcljAsoC/XWixM1606vsfGt6vyUFIAAA=
}

Here's a simple example that demonstrates the basic syntax and use of r3D. Be sure to do the code above before running this example:

Transx:  Transy:  Transz: 300.0         ; Set some camera
Lookatx:  Lookaty:  Lookatz: 100.0      ; positions to
                                        ; start with.
do update: does [           ; This "update" function is where
    world: copy []          ; everything is defined.
    append world reduce [   ; Add your 3D objects inside this "append".
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]                       ; A red 'cube' 100x150x125 pixels is added.
    camera: r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0] 
    RenderTriangles: render world camera r3d-perspective 250.0 400x360
    probe RenderTriangles   ; This line demonstrates what's going on
]                           ; under the hood.  You can eliminate it.

view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]  ; basic draw
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
]

R3D works by rendering 3D images to native Rebol 2D draw functions, which are contained in the "RenderTriangles" block above. R3D provides basic shape structures and a simple language interface to create and view those images in a Rebol application. It automatically adjusts lighting and other characteristics of images as they're viewed from different perspectives. To see how the rendering of images is converted into simple Rebol draw functions, watch the output of the "probe RenderTriangles" line in the Rebol interpreter as you adjust the sliders above. It displays the list of draw commands used to create each image in the moving 3D world.

In the example above, slider widgets are used to adjust values in the animation. Those values could just as easily be controlled by loops or other forms of data input. In the example below, the values are adjusted by keystrokes assigned to empty text widgets (use the "asdfghqwerty" keys to move the cube):

Transx:  Transy:  Transz: 2.0
Lookatx:  Lookaty:  Lookatz: 1.0 
do update: does [
    world: copy []
    append world reduce [ 
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]
    Rendered: render world 
        r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0]
        r3d-perspective 360.0 400x360
]
view layout [
    across
    text "" #"a" [Transx: (Transx + 10) update show scrn]
    text "" #"s" [Transx: (Transx - 10) update show scrn]
    text "" #"d" [Transy: (Transy + 10) update show scrn]
    text "" #"f" [Transy: (Transy - 10) update show scrn]
    text "" #"g" [Transz: (Transz + 10) update show scrn]
    text "" #"h" [Transz: (Transz - 10) update show scrn]
    text "" #"q" [Lookatx: (Lookatx + 10) update show scrn]
    text "" #"w" [Lookatx: (Lookatx - 10) update show scrn]
    text "" #"e" [Lookaty: (Lookaty + 10) update show scrn]
    text "" #"r" [Lookaty: (Lookaty - 10) update show scrn]
    text "" #"t" [Lookatz: (Lookatz + 10) update show scrn]
    text "" #"y" [Lookatz: (Lookatz - 10) update show scrn]
    at 20x20
    scrn: box 400x360 black effect [draw Rendered] 
]

The r3D module can work with models saved in native .R3d format, and the "OFF" format (established by the GeomView program at http://www.geom.uiuc.edu/projects/visualization/. See http://local.wasp.uwa.edu.au/~pbourke/dataformats/oogl/#OFFfor a description of the OFF file format). A number of OFF example objects are available at http://www.mpi-sb.mpg.de/~kettner/proj/obj3d/.

To understand how to create/import and manipulate more complex 3D shapes, examine the way objects are designed inside the "update" function in each of Andrew's three examples. Here's a simplified variation of Andrew's objective.r example that loads .off models from the hard drive. Be sure to do the r3D module code above before running this example, and then try downloading and loading some of the example .off files at the web site above:

RenderTriangles: []
view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
    return btn "Load Model" [
        model: r3d-load-OFF load to-file request-file
        modelsize: 1.0
        if model/3 [modelsize: model/3]
        if modelsize < 1.0 [ modelsize: 1.0 ]
        defaultScale: 200.0 / modelsize
        objectScaleX: objectScaleY: objectScaleZ: defaultscale
        objectRotateX: objectRotateY: objectRotateZ: 0.0
        objectTranslateX: objectTranslateY: objectTranslateZ: 0.0   
        Transx:  Transy:  Transz: 300.0
        Lookatx:  Lookaty:  Lookatz: 200.0
        modelWorld: r3d-compose-m4 reduce [
            r3d-scale objectScaleX objectScaleY objectScaleZ
            r3d-translate 
                objectTranslateX objectTranslateY objectTranslateZ
            r3d-rotatex objectRotateX
            r3d-rotatey objectRotateY 
            r3d-rotatez objectRotateZ
        ]
        r3d-object: reduce [model modelWorld red]
        do update: does [
            world: copy []  
            append world reduce [r3d-object]
            camera: r3d-position-object   
                reduce [Transx Transy Transz]
                reduce [Lookatx Lookaty Lookatz]
                [0.0 0.0 1.0] 
            RenderTriangles: 
                render world camera r3d-perspective 250.0 400x360
        ]
        update show scrn
    ]
]

Like most Rebol solutions, r3D is a brilliantly simple, compact, and powerful design that doesn't require any external toolkits. It's pure Rebol, and it's really amazing!

6.3 Multitasking

"Threads" are a feature of modern operating systems that allow multiple pieces of code to run concurrently, without waiting for the others to complete. Without threads, individual portions of code must be evaluated in consecutive order. Unfortunately, Rebol does not implement a formal mechanism for threading at the OS level, but does contain built-in support for asynchronous network port and services activity. See http://www.rebol.net/docs/async-ports.html, http://www.rebol.net/docs/async-examples.html, http://www.rebol.net/rebservices/services-start.html, and http://www.rebol.net/rebservices/quick-start.htmlfor more information.

The following technique provides an alternate way to evaluate other types of code in a multitasking manner:

  1. Assign a rate of 0 to a GUI item in a 'view layout' block.
  2. Assign a "feel" detection to that item, and put the actions you want performed simultaneously inside the block that gets evaluated every time a 'time event occurs.
  3. Stop and start the evaluation of concurrently active portions of code by assigning a rate of "none" or 0, respectively, to the associated GUI item.

The following is an example of a webcam viewer which creates a video stream by repeatedly downloading and displaying images from a given webcam url. To create a moving video effect, the process of downloading each image must run without stopping (i.e., in some sort of unending "forever" loop). But for a user to control the stop/start of the video flow (by clicking a button, for example), the interpreter must be able to check for user events that occur outside the forever loop. By running the repeated download using the technique outlined above, the program can continue to respond to other events while continuously looping the download code:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
]

Here's an example in which two webcam video updates are treated as separate processes. Both can be stopped and started as needed:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    across 
    btn "Start Camera 1" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Camera 1" [webcam/rate: none show webcam]
    btn "Start Camera 2" [
        webcam2/rate: 0 
        webcam2/image: load webcam-url 
        show webcam2
    ]
    btn "Stop Camera 2" [webcam2/rate: none show webcam2]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    webcam2: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ] 
]

Unfortunately, this technique is not asynchronous. Each piece of event code is actually executed consecutively, in an alternating pattern, instead of simultaneously. Although the effect is similar (even indistinguishable) in many cases, the evaluation of code is not concurrent. For example, the following example adds a time display to the webcam viewer. You'll see that the clock is not updated every second. That's because the image download code and the clock code run alternately. The image download must be completed before the clock's 'time action can be evaluated. Try stopping the video to see the difference:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    clock: field to-string now/time/precise rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/text: to-string now/time/precise show face
            ] 
        ] 
    ]
]

One solution to achieving truly asynchronous activity is to simply write the code for one process into a separate file and run it in a separate Rebol interpreter process using the "launch" function:

write %async.r {
    Rebol []
    view layout [
        clock: field to-string now/time/precise rate 0 feel [
            engage: func [face action event][
                if action = 'time [
                    face/text: to-string now/time/precise show face
                ] 
            ] 
        ]
    ]
}

launch %async.r
; Rebol will NOT wait for the evaluation of code in async.r
; to complete before going on:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ]
        ]
    ]
]

The technique above simply creates two totally separate Rebol programs from within a single code file. If such programs need to interact, share data, or respond to interactive activity states, they can communicate via http protocol, or by reading/writing data via a shared storage device.

6.4 Using DLLs and Shared Code Files in Rebol

"Dll"s in Windows, "So" files in Linux, and "Dylib" on Macs are libraries of functions that can be shared among different programming languages. Most of the executable code in each operating system is contained in such files. Third party code libraries are also available to make easy work of multimedia programming, 3d game programming, complex data processing, etc. The commercial versions of Rebol support using DLLs and shared code files in a similar manner on all platforms. You can try using Dlls in Rebol by downloading a free patch for the Rebol interpreter by Pascal Hurni, called "Rebcall":

http://mortimer.devcave.net/projects/rebcall/

Patched versions of the Rebol interpreter are not supported by the makers of Rebol. Commercial versions of the interpreter are recommended for professional work.

Using the format below, you can access and use the functions contained in most DLLs, as if they're native Rebol functions:

do/args %rebcall.r 'connect
lib: load-library "TheNameOfYour.DLL"

; "TheFunctionNameInsideTheDll" is loaded from the Dll and converted
; into a new Rebol function called "your-rebol-function-name":
your-rebol-function-name: make-routine [
    return-value: [data-type!]
    first-parameter [data-type!] 
    another-parameter [data-type!] 
    more-parameters [and-their-data-types!]
    ...
] lib "TheFunctionNameInsideTheDll"

; When the new Rebol function is used, it actually runs the function
; inside the Dll:
your-rebol-function-name parameter1 parameter2 ...

free-library lib

The first line initiates the rebcall engine. The second line opens access to the functions contained in the specified Dll. The following lines convert the function contained in the Dll to a format that can be used in Rebol. To make the conversion, a Rebol function is labeled and defined (i.e, "your-rebol-function-name" above), and a block containing the labels and types of parameters used and values returned from the function must be provided ("[return: [integer!]]" and "first-parameter [data-type!] another-parameter [data-type!] more-parameters [and-their-data-types!]" above). The name of the function, as labeled in the Dll, must also be provided immediately after the parameter block ("TheFunctionNameInsideTheDll" above). The second to last line above actually executes the new Rebol function, using any appropriate parameters you choose. When you're done using functions from the Dll, the last line is used to free up the Dll so that it's closed by the operating system. It should be noted that in the commercial versions of Rebol, the "load-library" function is typed as "load/library", and "free-library" is simply "free". Those are the only syntactic differences with Rebcall.

Here are some examples:

Rebol []

do/args %rebcall.r 'connect
; the kernel32.dll is a standard dll in all Windows installations:
lib: load-library "kernel32.dll"

play-sound: make-routine [
    return: [integer!] pitch [integer!] duration [integer!]
] lib "Beep"

for hertz 0 5000 10 [
    print rejoin ["The pitch is now " hertz " hertz."]
    play-sound hertz 50
]

free-library lib
halt

The next example uses the "dictionary.dll" from http://www.reelmedia.org/pureproject/archive411/dll/Dictionary.zipto perform a spell check on text entered at the Rebol command line. There are two functions in the dll that are required to perform a spell check - "Dictionary_Load" and "Dictionary_Check":

Rebol []

check-me: ask "Enter a word to be spell-checked:  "

do/args %rebcall.r 'connect
lib: load-library "Dictionary.dll"

; Two new Rebol functions are created:

load-dic: make-routine [
    a [string!] 
    return: [none]
] lib "Dictionary_Load"

check-word: make-routine [
    a [string!]
    b [integer!]
    return: [integer!]
] lib "Dictionary_Check"

; This line runs the Dictionary_Load function from the DLL:

load-dic ""

; This line runs the Dictionary_Check function in the DLL:

response: check-word check-me 0

; The Dictionary_Check function returns 0 if there are no errors:

either response = 0 [
    print "No spelling errors found." 
] [
    print "That word is not in the dictionary."
]

free-library lib
halt

The following example plays an mp3 sound file using the Dll at http://musiclessonz.com/mp3.dll. Of course, that Dll could be compressed and embedded in the code to eliminate the necessity of downloading the file:

Rebol []

write/binary %mp3.dll read/binary http://musiclessonz.com/mp3.dll
do/args %rebcall.r 'connect
lib: load-library "mp3.dll"

; the "playfile" function is loaded from the Dll, and converted
; to a new Rebol "play-mp3" function:

play-mp3: make-routine [a [string!] return: [none]] lib "playfile"

; Then an mp3 file name is requested from the user, which is played
; by the "playfile" function in the Dll:

file: to-local-file to-string request-file
play-mp3 file

print "Done playing, Press [Esc] to quit this program: "
free-library lib

6.5 Web Programming and the CGI Interface

The CGI interface allows Rebol programs to make use of data submitted via forms on web pages. If you have a web page form such as the following:

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

You can use that data in a Rebol CGI program as follows. Notice the "decode-cgi" line - it's the key to getting data from submitted html forms:

#!/home/youruserpath/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]

submitted: decode-cgi system/options/cgi/query-string

print ["Hello " second submitted "!"]
print [</BODY></HTML>]

To make this program work on your web server, go to rebol.com and obtain the correct version of the Rebol interpreter for the operating system on which your web server runs (often Linux). Upload it to (youruserpath) on the web server, and set the permissions to allow it to be executed (typically 755).

Here's a Rebol CGI form-mail program that prints a form, and then sends an email containing user-submitted data:

#!/home/youruserpath/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Email Us"</TITLE></HEAD><BODY>]

print read %template_header.html
set-net [from_address@website.com  smtp.website.com]
submitted: decode-cgi system/options/cgi/query-string

if not empty? submitted [
    sent-message: rejoin [
        newline "INFO SUBMITTED BY WEB FORM" newline newline
        "Time Stamp: " (now + 3:00) newline
        "Name: " submitted/2 newline 
        "Email: " submitted/4 newline
        "Message: " submitted/6 newline 
    ]

    send/subject to_address@website.com sent-message "FORM SUBMISSION"

    html: make string! 2000
    emit: func [data] [repend html data]
    foreach [var value] submitted [
        emit [<TR><TD> mold var </TD><TD> mold value </TD></TR>]
    ]
    print [<font size=5>"Thank You!"</font> <br><br>]
    print ["The following information has been sent:" <BR><BR>]
    print rejoin ["Time Stamp: " now + 3:00]
    print [<BR><BR>]
    print [<table>]
    print html
    print [</table>]
    print read %template_footer.html
    quit
]

print [<CENTER><TABLE><TR><TD>]
print [<BR><strong>"Please enter your info below:"</strong><BR><BR>]
print [<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">]
print ["Name:" <BR> <INPUT TYPE="TEXT" NAME="name"><BR><BR>]
print ["Email:" <BR> <INPUT TYPE="TEXT" NAME="email"><BR><BR>]
print ["Message:" <BR>]
print [<TEXTAREA cols=75 name=message rows=5></textarea> <BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</TD></TR></TABLE></CENTER>]

print read %template_footer.html

The template_header.html file used in the above example can include any page formatting 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="95%">
<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:

</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  2006
    Yoursite.com.  All rights reserved.</FONT>
</P>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>

The following example demonstrates how to automatically build selectable lists of days, months, times, and data read from a file, using dynamic loops:

#!/home/youruserpath/rebol -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Dropdown Lists"</TITLE></HEAD><BODY>]

submitted: decode-cgi system/options/cgi/query-string

if not empty? submitted [
    print rejoin ["NAME SELECTED: " submitted/2 <BR><BR>]
    selected: rejoin [
        "TIME/DATE SELECTED: "
        submitted/4 " " submitted/6 ", " submitted/8
    ]
    print selected
    quit
]

; Print the form:
print [<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">]
print [" 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">]
for time 10:00am 12:30pm :30 [prin rejoin ["<option>" time]]
for time 1:00 10:00 :30 [prin rejoin ["<option>" time]]
print [</option> </select> <br> <br>]

print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]

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

nick
john
jim
bob

Here's a simple CGI that displays all photos in the current folder on a web site:

#! /home/path/public_html/rebol/rebol -cs
Rebol [title: "Photo Viewer"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Photos"</TITLE></HEAD><BODY>]
print read %template_header.html

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 [<BR>]
print rejoin ["Total Images: " count]
print read %template_footer.html

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 "jams.db":

#! /home/path/public_html/rebol/rebol -cs
Rebol [title: "event.cgi"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Event Sign-Up"</TITLE></HEAD><BODY>]

jams: load %jam.db

a-line: [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print [[

] " Sign up for an event:"



]

print [<FORM ACTION="http://yourwebsite.com/cgi-bin/event.cgi">]
print [" Student Name: "]
print [<input type=text size="50" name="student"><BR><BR>]
print [" ADD yourself to this event:       "]
print [<select NAME="add"><option>""<option>"all"]
foreach jam jams [prin rejoin ["<option>" jam/1]]
print [</option> </select> <BR> <BR>]
print [" REMOVE yourself from this event: "]
print [<select NAME="remove"><option>""<option>"all"]
foreach jam jams [prin rejoin ["<option>" jam/1]]
print [</option> </select> <BR> <BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]

print-all: does [
    print [<br>[

]]

print " Currently scheduled events, and current attendance:"
print [</font><br>]
foreach jam jams [
    print a-line
    print [<BR>]
    print rejoin ["" jam/1]
    print [<BR>]
    print a-line
    print [<BR>]
    for person 2 (length? jam) 1 [
        print jam/:person
        print [<BR>]
    ]
    print [<BR>]
]
print [</BODY></HTML>]
]

selection: decode-cgi system/options/cgi/query-string

if selection/2 <> none [
if ((selection/4 = "") and (selection/6 = "")) [
    print [<strong>]
    print "Please try again. You must choose an event."
    print [</strong>]
    print-all
    quit
]
if ((selection/4 <> "") and (selection/6 <> "")) [
    print [<strong>]
    print "Please try again. Choose add OR remove."
    print [</strong>]
    print-all
    quit
]
if selection/4 = "all" [
    foreach jam jams [append jam selection/2]
    save %jam.db jams
    print [<strong>]
    print "Your name has been added to every event: "
    print [</strong>]
    print-all
    quit
]
if selection/6 = "all" [
    foreach jam jams [
        if find jam selection/2 [
            remove-each name jam [name = selection/2]
            save %jam.db jams
        ]
    ]
    print [<strong>]
    print "Your name has been removed from all events: "
    print [</strong>]
    print-all
    quit
]
foreach jam jams [
    if (find jam selection/4) [
        append jam selection/2
        save %jam.db jams
        print [<strong>]
        print "Your name has been added to the selected event: "
        print [</strong>]
        print-all
        quit    
    ]
]
found: false
foreach jam jams [
    if (find jam selection/6) [
        if (find jam selection/2) [
            remove-each name jam [name = selection/2]
            save %jam.db jams
            print [<strong>]
            print "Your name has been removed "
            print "from the selected event: "
            print [</strong>]
            print-all
            quit
            found: true
        ]
    ]
]
if found <> true [
    print [<strong>]
    print "That name is not found in the specified event!"
    print [</strong>]
    print-all
    quit
]
]

print-all

Here is a sample of the "jam.db" datafile used in the above example:

["Sunday September 16, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 23, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 30, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]

Here's a simple web site bulletin board program:

#! /home/path/public_html/rebol/rebol -cs
Rebol [title: "Jam"]
print "content-type: text/html^/"
print read %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>]
print [<center><strong><font size=4>]
print "Please REFRESH this page to see new messages."
print [</font></strong></center>]

print-all: does [
    print [<br>[

]" Posted Messages:"
]

print [[

]]

foreach bb (reverse bbs) [
    print [<BR>]
    print rejoin ["Date/Time: " bb/2]
    print "    "
    print rejoin ["Name: " bb/1]
    print [<BR><BR>]
    print bb/3
    print [<BR><BR>[

]]

]
]

selection: decode-cgi system/options/cgi/query-string

if selection/2 <> none [
entry: copy []
append entry selection/2
append entry to-string (now + 3:00)
append entry selection/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>[

]]

print [<FORM ACTION="http://website.com/bb/bb.cgi">]
print [<br>" Your Name: " <br>]
print [<input type=text size="50" name="student"><BR><BR>]
print [" Your Message: " <br>]
print [<textarea name=message rows=5 cols=50></textarea><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Post Message">]
print [</FORM>]

print [</td></tr></table></center>]
print read %template_footer.html

Here's an example datafile 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!
        }
    ]
]

A more detailed explanation of CGI programming, HTML, and relevant topics are covered in Rebol Programming for the Absolute Beginner

6.6 Rebol as a Browser Plugin

Rebol interpreters exist not only for an enormous variety of operating systems, but also as plugins for several popular browsers (Internet Explorer and many Mozilla variations, including Opera). That means that you can embed the Rebol interpreter directly into a web page, and have complete, complex Rebol programs run right inside pages of your web site (in a way similar to Flash and Java, and useful like Javascript code). This provides a nice alternative to CGI programming for any type of application that runs appropriately in the stand-alone view.exe interpreter (i.e., for games, multimedia applications, and rich graphic/GUI applications of all types). Since the browser plugin runs typical Rebol code in the same way as the downloadable view.exe interpreter, you can run code directly on your web pages, without making any changes. For more information about the Rebol plugins, see http://www.rebol.com/plugin/install.html.

6.7 MySQL and databases

MySQL is a free, open source database system used in many web sites and software projects. It manages all the difficult details of searching, sorting, and otherwise manipulating large amounts of data, quickly and safely in a multiuser environment. The commercial versions of Rebol have built-in native access to MySQL. You can also download a free MySQL protocol module that runs in every free version of Rebol, from http://softinnov.org/rebol/mysql.shtml(a free module for the postgre database system is also available at the same site). Most web hosting accounts come with MySQL already installed. See your web host account instructions to learn how to access it (you need a web address, database name, user name, and password). To get a free, simple-to-install web server package for Windows that includes MySQL, go to http://www.uniformserver.com. With that program, you can use MySQL on your local computer.

To use the Rebol MySQL module, unpack the compressed "rip" file available at the link above. This step only needs to be done the first time you use the package on a given computer:

do mysql-107.rip

Every time you access a MySQL database, you need to "do" the module that was unpacked in the step above:

do %mysql-r107/mysql-protocol.r

; At the time of this writing, 1.07 was the most current version of
; the mysql module.  Update the numbers in the previous two lines
; of code to reflect the current version number you've downloaded.

Next, enter the database info (location, username, and password), as in the instructions at http://softinnov.org/rebol/mysql-usage.html:

db: open mysql://username:password@yourwebsite.com/yourdatabasename

In databases, data is stored in "tables", which are made up of columns of related information. A "Contacts" table, for example, may contain name, address, phone, and birthday columns. "SQL" statements let you work with data stored in the table. Some SQL statements are used to create, destroy, and fill columns with data:

CREATE TABLE table_name     ; create a new table of information
DROP TABLE table_name       ; delete a table
INSERT INTO table_name VALUES (value1, value2,....)
INSERT INTO Contacts 
    VALUES ('Billy Powell', '5 Binlow Dr.', '555-6789', '1968-04-19')
INSERT INTO Contacts (name, phone) 
    VALUES ('Robert Ingram', '555-7890')

The SELECT statement is used to retrieve information from columns in a given table:

SELECT column_name(s) FROM table_name
SELECT * FROM Contacts
SELECT name,address FROM Contacts
SELECT DISTINCT birthday FROM Contacts ; returns no duplicate entries
; To perform searches, use WHERE.  Enclose search text in single
; quotes and use the following operators:
;  =, <>, >, <, >=, <=, BETWEEN, LIKE (use "%" for wildcards)
SELECT * FROM Contacts WHERE name='John Smith'
SELECT * FROM Contacts WHERE name LIKE 'J%'
SELECT * FROM Contacts WHERE birthday LIKE '%72%' OR phone LIKE '%34'
SELECT * FROM Contacts
    WHERE birthday NOT BETWEEN '1900-01-01' AND '2010-01-01'
; IN lets you specify a list of data to match within a column:
SELECT * FROM Contacts WHERE phone IN ('555-1234','555-2345')
SELECT * FROM Contacts ORDER BY name  ; sort results alphabetically
SELECT name, birthday FROM Contacts ORDER BY birthday, name DESC

Other SQL statements:

UPDATE Contacts SET address = '643 Pine Valley Rd.' 
    WHERE name = 'Robert Ingram'     ; alter or add to existing data
DELETE FROM Contacts WHERE name = 'John Smith'
DELETE * FROM Contacts
ALTER TABLE  - change the column structure of a table
CREATE INDEX - create a search key
DROP INDEX   - delete a search key

To integrate SQL statements in your Rebol code, enclose them as follows:

insert db {SQL command}

To retrieve the result set created by any inserted command, use:

copy db

You can use the data results of any query just as you would any other data contained in Rebol blocks. To retrieve only the first result of any command, for example, use:

first db

When you're done using the database, close the connection:

close db

Here's a complete example that opens a database connection, creates a new "Contacts" table, inserts data into the table, makes some changes to the table, and then retrieves and prints all the contents of the table, and closes the connection:

Rebol []

do %mysql-protocol.r 
db: open mysql://root:root@localhost/Contacts
; insert db {drop table Contacts} ; erase the old table if it exists
insert db {create table Contacts (
    name            varchar(100),
    address         text,
    phone           varchar(12),
    birthday        date 
)} 
insert db {INSERT into Contacts VALUES 
    ('John Doe', '1 Street Lane', '555-9876', '1967-10-10'),
    ('John Smith', '123 Toleen Lane', '555-1234', '1972-02-01'),
    ('Paul Thompson', '234 Georgetown Pl.', '555-2345', '1972-02-01'),
    ('Jim Persee', '345 Portman Pike', '555-3456', '1929-07-02'),
    ('George Jones', '456 Topforge Court', '', '1989-12-23'),
    ('Tim Paulson', '', '555-5678', '2001-05-16')
}
insert db "DELETE from Contacts WHERE birthday = '1967-10-10'"
insert db "SELECT * from Contacts"
results: copy db
probe results
close db
halt

Here's a shorter coding format that can be used to work with database tables quickly and easily:

read join mysql://user:pass@host/DB? "SELECT * from DB"

For example:

foreach row read rejoin [mysql://root:root@localhost/Contacts? 
    "SELECT * from Contacts"] [print row]

Here's a GUI example:

results: read rejoin [
    mysql://root:root@localhost/Contacts? "SELECT * from Contacts"]
view layout [
    text-list 100x400 data results [
        string: rejoin [
            "NAME:      " value/1 newline
            "ADDRESS:   " value/2 newline
            "PHONE:     " value/3 newline
            "BIRTHDAY:  " value/4
        ]
        view/new layout [
            area string
        ] 
    ] 
]

For a more detailed explanation about how to set up MYSQL, how to us the SQL language syntax, and other related topics, see Rebol Programming for the Absolute Beginner.

6.8 Parsing

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. 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}

parsed-block: parse text "^/" 

; ^/ 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}

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

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]

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

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

Pattern Matching Mode:

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!]

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 unformated 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 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:/whatismyip.com, 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:

parse read http://whatismyip.com [
    thru <title> copy my-ip to </title>
]
parse my-ip [
    thru "-" copy stripped-ip to end
]
alert to-string rejoin [
    "External: " trim/all stripped-ip "  "
    "Internal: " read join dns:// read dns://
]

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

code: read to-file request-file
parse/all code [any [
    to #";" begin: thru newline ending: (
        remove/part begin ((index? ending) - (index? begin))) :begin
    ]
]
editor code

6.9 Objects

Objects are code structures that allow you to encapsulate and replicate code. They're primarily useful when creating duplicatable data structures and when implementing multiple copies of similar code. They are also useful in providing context and namespace management features.

To make an original object blueprint 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 cascaded 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

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
]

The code below creates a new account block 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]

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 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 ""
    ]
]

Here are some implementations of the above 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. Note that the variable "email-address" is initially assigned to a function. You can override that definition, as in the user3 definition, to simply refer to an email address value. Once you've done that, the email-address function no longer exists in that particular object.

Here's a small game in which multiple character objects are created from a duplicated object template:

Rebol []

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 "^(1B)[J"
    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 characters in an online multi-player game.

6.9.1 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 code at this point, because the "bank" and "var" words have been overwritten. You can avoid that problem by simply wrapping the above code into separate objects:

show-money: make object! [
    var: 1234.56
    bank: does [
        print ""
        print rejoin ["Your bank account balance is:  $" var]
        print ""
    ]
]

show-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 contexts:

show-money/bank
show-place/bank

show-money/var
show-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 "show-money" and "show-place" objects, they inherit all the existing code contained in the above objects:

deposit: make show-money [
    view layout [
        button "Deposit $10" [
            var: var + 10
            bank
        ]
    ]
]

travel: make show-place [
    view layout [
        new-favorite: field 300 trim {
            Type a new favorite river here, and press [Enter]} [
            var: value
            bank
        ]
    ]
]

6.10 Rebcode

Rebol provides speedy performance for most common scripting tasks. For situations where higher performance computations are required (for image processing, large looping mathematical evaluations, etc.), Rebol's "rebcode" VM acts as a sort of native cross-platform assembly language which can dramatically improve the processing speed of CPU intensive tasks that benefit from low level optimization. Rebcode uses a syntax similar to typical Rebol block/function code, and allows you to access variables used outside the Rebcode context, but it is not intended for beginner programmers. Rebcode is structured similarly to assembly language, with some additional benefits such as the ability to use built-in math functions, loops and conditional evaluations, embedded documentation, and the ability to run identically on all processors. Low level Rebcode typically improves performance speed by 10x-30x. Using Rebcode is beyond the scope of this tutorial. For more information, see http://www.rebol.com/docs/rebcode.htmland http://www.rebol.net/rebcode/, and search the mailing list at rebol.org. Be sure to see the the examples at http://www.rebol.net/rebcode/docs/rebcode-demos.html:

To use rebcode, you must use a version of Rebol downloaded from http://www.rebol.net/builds/, in the section marked "Download Directories" (others don't contain the rebcode VM). Get the most recently dated version available (for Windows, at least rebview1361031.exe). Once you've downloaded a rebcode enabled interpreter, try this example:

do http://www.rebol.net/rebcode/demos/dot-flowers.r

6.11 Useful Rebol Tools

Here are some web links containing free code modules and various programs that can help you accomplish useful programmatic tasks in Rebol:

http://www.hmkdesign.dk/rebol/list-view/list-view.r- a powerful listview widget to display and manipulate formatted data in GUI applications. Perhaps the single most useful additional to the Rebol GUI language.

http://www.dobeash.com/it/rebdb/- a database module written entirely in native Rebol code that lets you easily store and organize data. There's also a rich GUI library and a spell checker module that can be included in your programs.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=rebzip.r- a module to compress/decompress zip formatted files.

http://www.colellachiara.com/soft/Misc/pdf-maker.r- a dialect to create pdf files directly in Rebol.

http://softinnov.org/rebol/mysql.shtml- a module to directly manipulate mysql databases within Rebol (covered earlier). A module for postgre databases is also freely available at the same site.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=menu-system.r- a dialect to create all types of useful GUI menus in Rebol.

http://softinnov.org/rebol/uniserve.shtml- a framework to help build client-server network applications.

http://softinnov.org/cheyenne.shtml- a full featured web server written entirely in native Rebol.

http://www.rebol.net/demos/BF02D682713522AA/i-rebot.rhttp://www.rebol.net/demos/BF02D682713522AA/objective.rand http://www.rebol.net/demos/BF02D682713522AA/histogram.r- these examples contain a 3D engine module written entirely in native Rebol draw dialect. The module lets you easily add and manipulate 3D graphics objects in your Rebol apps.

http://web.archive.org/web/20030411094732/www3.sympatico.ca/gavin.mckenzie/- a Rebol XML parser library.

http://earl.strain.at/space/rebXR- a full client/server XML-RPC implementation for Rebol (contains the parser library above). Tutorials (translated from French by Google) are available hereand here.

http://box.lebeda.ws/~hmm/rswf/- a dialect to create flash (SWF) files directly from Rebol scripts.

http://www.rebolforces.com/articles/tui-dialect/- a dialect to position characters on the screen in command line versions of Rebol.

http://www.rebol.net/docs/makedoc.html- converts text files into nicely formatted HTML files. This tutorial page is written and maintained entirely with makedoc.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=layout-1.8.r- a simple visual layout designer for Rebol GUI code.

http://www.crimsoneditor.com/- a source code editor for Windows, with color highlighting especially for Rebol syntax. Quick start instructions are available at http://www.rebol.net/article/0187.html.

http://www.rebol.org- the official Rebol library - full of many additional modules and useful code fragments. The first place to look when searching for Rebol source code.

6.12 6 Rebol Flavors

This tutorial covers a version of the Rebol language interpreter called Rebol/View. Rebol/View is actually only one of several available Rebol releases. Here's a quick description of the different versions:

  1. View - free to download and use, it includes language constructs used to create and manipulate graphic elements. View comes with the built-in dialect called "VID", which is a shorthand mini-language used to display common GUI widgets. View and VID dialect concepts have been integrated throughout this document. The "layout" word in a typical "view layout" GUI design actually signifies the use of VID dialect code in the enclosed block. The VID dialect is used internally by the Rebol interpreter to parse and convert simple VID code to lower level View commands, which are composed from scratch by the rudimentary display engine in Rebol. VID makes GUI creation simple, without the need to deal with graphics at a rudimentary level. But for fine control of all graphic operations, the full View language is exposed in Rebol/View, and can be mixed with VID code. View also has a built-in "draw" dialect that's used to compose and alter images on screen. Aside from graphic effects, View has built in sound, and access to the "call" function for executing command line applications. The newest official releases of View can be download from http://rebol.com/view-platforms.html. The newest test versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-view.html.
  2. Core - a text-only version of the language that provides basic functionality. It's smaller than View (about 1/3 to 1/2 the file size), without the GUI extensions, but still fully network enabled and able to run all non-graphic Rebol code constructs. It's intended for console and server applications, such as CGI scripting, in which the GUI facilities are not needed. Core is also free and can be downloaded from http://rebol.com/platforms.html. Newest versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-core.html.
  3. View/Pro - created for professional developers, it adds encryption features, Dll access and more. Pro licenses are not free. See http://www.rebol.com/purchase.html.
  4. SDK - also intended for professionals, it adds the ability create stand-alone executables from Rebol scripts, as well as Windows registry access and more to View/Pro. SDK licenses are not free.
  5. Command - another commercial solution, it adds native access to common database systems, SSL, FastCGI and other features to View/Pro.
  6. Command/SDK - combines features of SDK and Command.

Some of the functionalities provides by SDK and Command versions of Rebol have been enabled by modules, patches, and applications created by the Rebol user community. For example, mysql and postgre database access, dll access, and stand-alone executable packaging can be managed by free third party creations (search rebol.org for options). Because those solutions don't conform to official Rebol standards, and because no support for them is offered by Rebol Technologies, commercial solutions by RT are recommended for critical work.

6.13 Learning More About Rebol

This tutorial is a shortened version of Rebol Programming for the Absolute Beginner. That text covers every aspect of Rebol programming in greater detail, with more explanation and more examples. There is also a large section of in-depth case studies (including Tetris, Freecell, and a number of real-world data processing examples) which demonstrate important concepts about how to conceptualize all sorts of programming tasks, to move from reading and understanding the language, to writing and thinking in it more fluently. It's especially useful for anyone who's new to programming, but also for anyone who'd like to think more naturally about the process of developing algorithmic thought in Rebol. Additionally, many smaller topics such as Rebol version differences and common Rebol techniques are covered throughout the text.

The tutorial at http://www.rebol.com/docs/rebol-tutorial-3109.pdfprovides a nice summary of fundamental concepts. It's a great document to read next. To learn Rebol in earnest, read the Rebol core users manual: http://rebol.com/docs/core23/rebolcore.html. It covers all of the data types, built-in word functions and ways of dealing with data that make up the Rebol/Core language (but not the graphic extensions in View). It also includes many basic examples of code that you can use in your programs to complete common programmatic tasks. Also, be sure to keep the Rebol function dictionary handy whenever you write any Rebol code: http://rebol.com/docs/dictionary.html. It defines all the words in the Rebol language and their specific syntax use. It's also helpful in cross-referencing function words that do related actions in the language. Along the way, read the Rebol View and VID documents at: http://rebol.com/docs/easy-vid.html, http://rebol.com/docs/view-guide.html, and http://rebol.com/docs/view-system.html. Those documents explain how to write Graphical User Interfaces in Rebol. Once you've got an understanding of the grammar and vocabulary of the language, dive into the Rebol cookbook: http://www.rebol.net/cookbook/. It contains many simple and useful examples of code needed to create real-world applications. When you've read all that, finish the rest of the documents at http://rebol.com/docs.html.

Beyond the basic documentation, there is a library of hundreds of commented and approved source codes for Rebol programs available at http://rebol.org. There's also a searchable mailing list archive with nearly 45,000 emails at the same rebol.org web site. It contains answers to numerous questions encountered by Rebol programmers. There are many other web sites such as http://www.codeconscious.com/rebol/, http://www.rebolforces.com/, http://www.fm.vslib.cz/~ladislav/rebol/, http://www.reboltalk.com/forum/, and RebolFrance (translated by Google)that provide more help in understanding and using the language. For a complete list of all web pages and articles related to Rebol, see http://dmoz.org/Computers/Programming/Languages/REBOL/.

Don't forget to click the rebsite icons in the "REBOL" and "Public" folders, right in the desktop of the Rebol interpreter. Right-click any of the hundreds of individual program icons and select "edit" to see the code for any example. That's a great way to see how to do things in Rebol. Good luck and have fun!

MakeDoc2 by REBOL- 20-Jan-2007