Originally published in Rebol Forces.

Volume 1 Issue 6
Date: 1-May-2002
Issues: prev | next



Another month, another collection of short articles and tips. This month includes a new Parse article from Robert M, Cyphre continues his Draw series, Gregg speeds up his image manipulation from last month and Chris contributes a google search and shows how to add the Logo-bar to IOS reblets. Lets dig in..

Add Google to your scripts

by Chris RG

Google, many would argue, has been the most successful at indexing the heaving mass that is the World Wide Web.  With this in mind, I wrote this script to update a database of companies to include the most likely web site match.

It uses only HTTP and works by emulating the "I'm Feeling Lucky" button on Google's home page which sends the browser to the top matching location.

As it uses HTTP, we must URL encode the search string before we can send it.

encode-cgi: func [
{Converts string to a CGI argument string.}
args [any-string!] "Starts at first argument word"
replace/all args "%" "%25"
replace/all args "+" "%2B"
replace/all args " " "+"
mold to-url args

Now for the search.  A quick overview - the function takes one argument, the search string. It is set up to loop three times should the query return an error.  It sends an HTTP request to Google, and parses the reponse looking for the 'Location' value.  If successful, it will return the top matching URL, otherwise it will return 'none.

google-search: func [
{Returns top-ranking site from a Google Search}
search-string [any-string!] {Google search string}
/local trys google-port google-wire google-response google-result
trys: 0
while [
and trys < 3 error: error? try [
google-response: copy {}

Since we are using a custom HTTP request, we can do it through a raw TCP port.

google-port: open tcp://www.google.com:80

Then we create our HTTP request.

insert google-port rejoin [
{GET /search?q=} encode-cgi lowercase copy search-string
{&btnI=I'm+Feeling+Lucky HTTP/1.1} newline
{host: www.google.com} newline
{connection: close} newline newline

Now we need to collate the response.

while [
google-wire: copy google-port
append google-response google-wire
close google-port

And parse the response for the top matching URL.

parse/all google-response [
thru {Location: }
copy google-result to newline
to end
google-result: to-url google-result
trys: trys + 1
wait 1

If after three attempts, we have been unable to get a result from Google, we return 'none.

if error [google-result: none]
return google-result

With these two functions at the top of your script, you can apply a Google search like so:

>> google-search "Rebol"
== http://www.rebol.com/
>> google-search "Scotland"
== http://www.visitscotland.com/

Discovering The forgotten DRAW dialect - Part II

Part 2 - Drawing 2D vector objects using DRAW

Author: Cyphre<cyphre@seznam.cz>

Lines, circles, polygons and the others...

As I promised in the previous issue of Zine I would like to show you how to use DRAW dialect for drawing simple objects and shapes. You may know from "The official DRAW tutorial document" DRAW dialect disposes of several commands for drawing but anyway here is a little summary:

Commands for choosing various drawing pens

[foreground-color [tuple! none!]] [background-color [tuple! none!]]
[foreground-color [tuple! none!]] [background-color [tuple! none!]]
[len1 [integer!]] [len2 [integer!]] ... [lenn [integer!]]

Commands for drawing various shapes

[point1 [pair!]] [point2 [pair!]] ... [pointn [pair!]]
[point1 [pair!]] [point2 [pair!]] ... [pointn [pair!]]
[center [pair!]] [radius [integer!]]
[point1 [pair!]] [point2 [pair!]]

Something for warm-up...

You can use combinations of these commands in the DRAW block in a common "static" way like:

view layout [
origin 0x0
box black 100x100 effect [
draw [
fill-pen blue
polygon 50x5 60x40 95x50 60x60 50x95 40x60 5x50 40x40
pen yellow
fill-pen red
circle 50x5 5
circle 50x95 5
circle 5x50 5
circle 95x50 5
pen white
line 45x45 55x45 55x55 45x55 45x45

Following code will create simple 4-arm-star like this...

[ Image ]

Not bad but it lacks action, doesn't it? In the next section I will show you one possible method how to get this DRAW stuff moving.

DRAW in the action!


The objective of this tutorial is make fast and simple routine for manipulating with DRAW graphics objects. I choose method using Rebol's built-in parser. This method is fast and you don't have to put your input data in any custom format because the routine takes as an input common DRAW block.

How to do it?

We define the block of DRAW dialect. You can easily replace this example dialect by your own vector shapes. Just keep in mind the resolution. In this example the virtual resolution of initial object size is 100x100 pixels so you can draw any shape in that area.

blk: [
fill-pen blue
polygon 50x5 60x40 95x50 60x60 50x95 40x60 5x50 40x40
pen yellow
fill-pen red
circle 50x5 5
circle 50x95 5
circle 5x50 5
circle 95x50 5
pen white
line 45x45 55x45 55x55 45x55 45x45

Another ingredience in our engine is this useful function used during the calculation...

angle: func [
"Handy function which returns angle of given point"
x y /local result
result: either x = 0 [
either y < 0 [-90][90]
arctangent y / x
if x < 0 [result: result + 180]
return result

And here is the magic - simple parser function which returns the re-calculated DRAW block.

make-draw: func [
{Calculate new values from given block of DRAW dialect.}
block [block!]
{initial DRAW block input}
size [pair!]
{zooing aspect for x and y axes}
rotation [integer!]
{rotation in degrees}
center [pair!]
{centering point for rotation}
/local result x y p r ang resolution
result: copy []
parse blk [
some [
set p pair! (
p: p - center
r: square-root ((power p/x 2) + (power p/y 2))
ang: rotation + angle p/x p/y
x: to-integer (r * (cosine ang) + center/x)
y: to-integer (r * (sine ang) + center/y)
insert tail result (
to-pair reduce [
to-integer x / (100 / size/x)
to-integer y / (100 / size/y)
) + (center - (size / 2))
| set p integer! (insert tail result to-integer p / (100 / size/x))
| set p skip (insert tail result p)
return result

Now we have simple 2D vector engine. The last thing we need is to visualize the result:

view layout [
origin 0x0 box black 100x100 with [
rate: 20
rot: 0
zoom: 50
dz: 1
feel: make feel [
engage: func [f a e][
rot: rot + 5
zoom: zoom + dz
if zoom < 2 [dz: 1]
if zoom > 99 [dz: -1]
effect: compose/deep [
draw [
(make-draw blk to-pair zoom f/rot 50x50)
show f

Now you can enjoy realtime calculated rotating and zooming 2D objects!

notes and enhancement tips...
  • If you want to move on the screen this object just change 'offset variable of the face (in this example 'box style)
  • you can play with multiple effects, just put them into the 'effect block together with generated DRAW dialect
  • don't use the DRAW command 'box in this example because it cannot be rotated due its kind of syntax

Advanced game-like example

If you are interested in the method mentioned above you can have a look at the more advanced script(~5Kb)

You can find it here:


The advanced script contains:

  • DRAW parser with dynamic buffer for better performance
  • keyboard handler
  • simple AI driven computer vehicles ;-)

If you like it feel free to enhance it. If you have any questions comments you can contact me at: cyphre@seznam.cz

Nothing is perfect...

At the end of this article I'd like write little summary of problems I encountered while experimenting with DRAW and Rebol/View and some enhancements which would have pushed this nice tool forward:

known bugs:

  • "squared-circle" bug - if you choose same pen and fill-pen for drawing circle the result will be square (fortunately this is fixed for the upcoming release)
  • pen doesn't make contour around filled polygons
  • Rebol/View crashes when drawing filled poligons partially out of visible face area = you cannot zoom/move any object out of face borders - very annoying problem (this problem doesn't crash latest Rebol/Link...instead of it interpret shows "not enough memory" error)
  • DRAW's 'text command doesn't recognize underlined font style (manipulating text will be explained in the next issue of ZINE...Watch out!)

my enhancement dreams:

  • missing 'ellipse command - why there is no posible to make elipse when 'circle is available?!?
  • missing 'point or 'dot command - currently you have to write [line 10x10 10x10] to make single dot at 10x10 coords on the screen
  • missing 'round command - DRAW cannot make arcs in a simple way
  • missing 'fill-pattern command - for simple texturing of filled polygons
  • better performance - I thought that Rebol has slow math but while doing some speed tests I noticed that the most CPU power takes visualization for example:

Parsing and recalculating rotation (using dynamic buffer) of DRAW block containing 10 filled polygons around 360 degrees takes less than half second on Celeron 533Mhz... that means Rebol can calculate such DRAW block more than 600fps but when you want to visualize it the performance goes down to about 20fps...

Anyway, I'm not sure if it is possible to do something with the performance or not...I cannot see the guts of View engine, I just hope that guys from Rebol Technologies will make the best to "multimedialize" such great tool like Rebol/View is.

Note to all developers

Please, experiment with Rebol/View and try to get full potential from it and always send all your problems/enhancements to RT's feedback, don't believe people who says "View = VID, forget Rebol. Flash is the right thing".

View engine(incl.DRAW) is based on realy great technology. If developers will use it not only for making simple VID layouts maybe one day we will be able to visualize advanced 3D graphics using true Rebol 3D dialect...

Parsing Tutorial

Autor: Robert M. Münch
EMail: robert.muench@robertmuench.de
Web : http://www.robertmuench.de

Make your own little dialect

With parse you can easly create dialects for your application that users can use. Including a dialect to control your application can be of great value as other Rebol scripts can create such a dialect file and send it to your application. Further parse can be used to read in a stream of characters and find out what it's all about.

When creating dialects you have to think about a smart syntax you want to use and than you just need to use parse to read in such structured files.

When reading in an existing stream, you have to look at the source data and try to find an underlying pattern you can use.

And if you look at the examples in this tutorial you see that parse itself uses a dialect: The parse dialect

With this parse dialect you write down your grammar and just use the grammar as rule part for the parse command.

The concept of parse

If you have parser tools like YACC, ANTLR or something like this you might find how parse works a bit strange in the beginning. Nevertheless it's a very powerful command.

Here are some differences to all the other parsing tools:

  1. Parse has no implicit backtracking.
  2. Parse evaluate rules from top to bottom and within one rule from left to right. This sounds logical and simple but it's not in the first place.
  3. Parse uses only one namespace for the variables you assign values to. These variables are not handled by stack approach, you have to keep track of them yourself if you use recursive rules.
  4. Parse will return true in one and only one case: If parse could reach the END of the input stream. This helps you to see if you rules are complete. If this is not the case, than parse exited the parsing process somewhere on its way through the input stream.

For all examples I use the following generic rules:

space: charset " ^-"
spaces: [any space]
chars: complement nochar: charset " ^-^/"

Maximum matching

When starting to parse with a rule, parse tries to parse a maximum match. This means that the current part of a rule will be used as long as possible. At the first point of violation of the current parse rule the next part of the rule will be used. Let's make an example:

rule1: [#"a" any chars #"z"]

This rule says: Parse the single character "a" then zero or more chars and finally the character "z". Ok let's see what result we get for different input strings:

>> parse "abcdz" rule1

Huh, why this? Here you will be caught be the maximum match behavior. Let's parse the example with the given rule by hand:

  1. The character "a" is parsed and the next parse rule part to use is "any chars".
  2. Now "bcdz" are parsed by the "any chars" part. Even the "z" is parsed because it's an "any char".
  3. The last part of the rule is never reached because in step 2 we already consumed all input characters. That's why we get a false as return value.

What can we do to get a "true" as return value? Well, we have to remove the character "z" from the chars charset.

>> chars: complement " ^-^/z"
>> parse "abcdz" rule1

Now the parse rule part "any chars" will fail when reaching the character "z" and parse uses the next rule part. This matches the input character and parse reached the end of the input-string and the end of the rule: returning true.

The rule of thumb for this is if you need to parse for single characters remove this special character from the normal charset. If you look at my make-doc-pro rules you can see that I have used this pattern all over to parse for the single markup characters I use.

The tricky part is to find out when such a single character isn't the trigger for the next rule part instead should be parsed as normal character. This depends a lot of how you construct your dialect.

Optional parsing

With parse's optional feature you can make your life easier. There are two optional semantics you can use:

  1. By using the parse dialect word 'opt and a rule. If you use this, the rule part will only be used if the input stream would match. Otherwise the rule is taken as if the 'opt part wouldn't be there.
  2. By using the | char in a rule block. This means an alternate. As you can see it's named different so there must be a different meaning.

If you start with parse this might confuse you when doing the first steps. When to use which option.

The first one can be read as: Use the rule following 'opt zero or one time. That's it.

The second option is a bit more tricky:

>> rule2: [#"a" [#"b" | #"c" | #"d"] #"z" ]
>> test2_1: "abz"
>> parse test2_1 rule2

Ok, I think that's obvious. And this one?

>> test2_1: "abcz"
>> parse test2_1 rule2

Didn't we just discuss a maximum matching rule? Didn't we say a parse rule part will be used as long as possible? That's right but the rule part doesn't state that it should be used more than once even though you might think it looks like it should because of the alternatives.

To get a true for the test2_1 string we only have to make one little change to our rule:

>> rule2: [#"a" any [#"b" | #"c" | #"d"] #"z" ]

Now it says: Use the alternate block zero or more times.

The rule to remember with alternates is that it's a one-shot block. The first part that matches will end the alternate block evaluation and continue with the next rule part. The nice thing with the changed rule2 is that it will parse any combination of the characters "a, b, c".

Keeping track of states

While parsing is a lot of fun you most likely need to store some information about the progress of your parse run. This can be done quite easy because you can execute any Rebol code you want at specific points in a rule:

b: 0
c: 0
d: 0
rule3: [#"a" any [#"b" (b: b + 1)| #"c" (c: c + 1)| #"d" (d: d + 1)] #"z"]
test3_1: "acbz"
print parse test3_1 rule3
print ["b:" b "c:" c "d:" d]

This will store the number of parsed 'b 'c and 'd characters. So you can use any Rebol variable with in the action part of rules to store information or you can call Rebol functions if you need to execute more complex things.

There is one thing to keep in mind: parse uses the variables from your Rebol context and doesn't have an own context. To see this effect let's use the rule from above, but now we want to count the letters 'a 'b and 'c per input line. Ok, here is a first try:

a: b: c: 0
rule4_1: [some [any rule4_2 newline]]
rule4_2: [[#"a" (a: a + 1)| #"b" (b: b + 1)| #"c" (c: c + 1)]]
test4_1: rejoin ["abcaabcc" newline "abbccab"]
print parse test4_1 rule4_1
print ["a:" a "b:" b "c:" c]

I used two rules to show you how sub-rule calling can be used. We have to move the any word up to rule4_1 because otherwise we would end up with an endless loop.

However, the output is this:

a: 5 b: 5 c: 5

Why do we get a false here? This indicates that the input string and the rules we have written don't match exactly. Do you see the problem? It's in rule4_1. The rule says: Parse one or more times rule4_2 followed by newline character. But we don't have a newline character as last character in our input string. We have end. Parsing (or better expecting) the end of an input string at some point needs to be mentioned explicitly:

rule4_1: [any [some rule4_2 [newline | end]]]

Please note that we swapped the 'some and 'any keywords because otherwise you will end with an endless loop. It's always a good rule to keep in mind that you can use the combination of 'any and 'some but never 'some and 'any. Ok, now we get true as return value from parse. But yet we don't count per line, we count the number of chars for the complete input string. Now we have several possibilities.

  1. The obvious one: Reset at newline and print immediately.
  2. The generic one: Store the state in a stack and print after parsing everything.

You always have a choice between these two ways. The first one is OK for simple things but as soon as you need to access the parsing results from your Rebol script several times you better go with option two.

Let's see how option one looks like:

>> rule4_1: [
(a: b: c: 0)
any [
some rule4_2 [newline | end]
(print ["a:" a "b:" b "c:" c] a: b: c: 0)
>> print parse test4_1 rule4_1

First we reset the counters than we have the parsing rule and the values will be printed as soon as 'newline or 'end was reached. Further we have to reset the values for the next parsing iteration. Than 'any will iterate as long as 'end wasn't reached.

As you can see the results from the first line are overwritten. You can't access them anymore from other Rebol functions. To avoid this I usally use stacks to store such state information. For each line a new entry is pushed on the stack.

stack_a: []
stack_b: []
stack_c: []
rule4_1: [
(a: b: c: 0)
any [
some rule4_2 [newline | end]
(insert stack_a a insert stack_b b insert stack_c c
a: b: c:0)
print parse test4_1 rule4_1
print stack_a
print stack_b
print stack_c

Now you have access to the counters for each line. You have saved the state of the results from parsing for later access. Please remember, that on a stack your last parsed line results are at the top of the stack.


There is a lot more to say about parse and what can be done with it. It's a very powerful dialect that can help making your programming live a lot easier. But it's tricky to use and you have to develop a feeling for it. Most of the time it helped me to just think the opposite way when my first ideas didn't worked...

Bigger than a pixel - speeding things up!

By Gregg Irwin

Picking up where we left off

In the first part of this article, we picked and poked our way around an image one pixel at a time. That being a very slow process, and me being an impatient developer, let's see if we can speed things up a bit.

Row, row, row your boat

The obvious way to speed things up is to work on more than one pixel at a time, and it makes sense to step up to the biggest grouping possible for maximum performance. In our case, the biggest group of pixels we can easily deal with at once is a single row of pixels in the image.

TO-INDEX hasn't change from part I.

to-index: func [pos [pair!] cols [integer!]] [
pos/y - 1 * cols + pos/x

PICK-XY and POKE-XY now have a /part refinement so you can specify how many items you want to operate on.

pick-xy: func [
data [series!] pos [pair!] cols [integer!]
/part wd [integer!]
either part [
copy/part at data to-index pos cols wd
pick data to-index pos cols
poke-xy: func [
data [series!] pos [pair!] cols [integer!] value
/part wd [integer!]
either part [
change/part at data to-index pos cols value wd
poke data to-index pos cols value

Now we'll wrap PICK-XY and POKE-XY with more meaningful names, and let the wrapper functions deal with a detail specific to our image processing purposes. That detail is the fact that each "pixel" is actually 4 values (red, green, blue, and alpha channel information for the pixel).

pick-row: func [img [image!] pos [pair!] wd [integer!]] [
; We use wd*4 because there are 4 bytes per pixel.
pick-xy/part img pos img/size/x wd * 4
poke-row: func [img [image!] pos [pair!] wd [integer!] value] [
; We use wd*4 because there are 4 bytes per pixel.
poke-xy/part img pos img/size/x value wd * 4

Now the CLIP and PAINT-CLIP functions from part I look like this:

clip: func [img [image!] offset [pair!] size [pair!] /local result] [
result: make image! size
repeat row size/y [
to-pair reduce [1 row]
pick-row img add offset to-pair reduce [1 row] size/x
paint-clip: func [
{The mirror of clip to poke a clip into an image at a given offset.}
img [image!]
dest [image!]
/at pos [pair!]
/part sz [pair!]
/local offset size
offset: either at [pos][0x0]
size: either part [min sz img/size][img/size]
repeat row size/y [
add offset to-pair reduce [1 row]
pick-row img to-pair reduce [1 row] size/x

Can it go even faster?

I haven't figured out how to make PAINT-CLIP faster, but Ryan Cole gave me a push in the right direction by mentioning the CROP effect as being a possibility to speed up the CLIPing side of things. The idea being that you create a face, using the image you're working with, and apply the CROP effect to it. Here's how it works.

clip: func [img [image!] offset [pair!] size [pair!]] [
to-image make face compose/deep [
image: img
edge: none
effect: [crop (offset) (size)]

Not only is it less code, but it's a lot faster to boot. Thanks Ryan!

How much speed have we gained from our original version of CLIP to this final version using CROP? On my system, clipping a 100x100 section out of an image, the version that paints by rows instead of pixels is about 50 times faster than the original, and the final version is about 6 times faster than that.

The moral of the story seems to be: Whenever you can, let Rebol do the work.

What does the future hold?

Romano Paulo Tenca recently pointed out to me, on the Rebol mailing list, that there is a pretty severe flaw in my basic TO-INDEX function. It is based on the concept of an 1x1 origin, whereas images have an origin of 0x0.

The next step for this experimental code would be to solve that issue, add some boundary checking and error handling, and document everything. Have fun!

Adding the Rebol logo to IOS Reblets

by Chris RG

There are a couple of good reasons for doing this:

  • The logo bar is a non-intrusive style (in the visual sense) imposed on all the 'official' Reblets and lends a uniformity to the diverse applications.
  • Including the logo bar gives free promotion to Rebol Tech. (again non-intrusive) who gave us this expressive language in the first place. (ed. It is also easy to re-brand/re-skin with you own logo-bar image)

And I'm sure there are more.

To add the logo bar to a VID layout:

my-form: layout [
origin 10x10 pad 24x0 guide
... your VID code here ...
at 0x0 lb: logo-bar 24x20 ; see note below
lb/size/y: my-form/size/y

If you have used any specific positioning keywords, e.g. [at 10x10], just add 24 to the 'x value, [at 34x10] in this example.  'origin is optional and subject to your own preference.

The 'logo-bar style has its own default height that may be bigger than your VID layout, so I give it the initial height of 24x20 (the width of the logo bar is always 24).  The function 'update will readjust the logo bar to a new given height.

Reblets with Logo-Bar (down left side)

Bits & Pieces

by Allen Kamp

Did you know?

Extract Styles

Ever forget the name of a VID style? This one liner gives you a quick reminder.

probe extract system/view/vid/vid-styles 2


net-utils/export is used internally by send to produce a string representation of the header object when sending an email. It produces a string output with each name/value pair on a separate line. If a value is none then that name/value pair will not be shown. You can make use of it for your own shallow objects when you need a simple string output, such as in an alert msg or for writing to a file.

msg: context [
name: "zine" color: 255.0.0
email: zine@rebolforces.com content: "This is a demo" date: none
net-utils/export msg
== {name: zine
color: 255.0.0
email: zine@rebolforces.com
content: This is a demo}

This could be used to format an alert msg

show-info: func [
str [object! string!]
if object? str [str: net-utils/export str]
inform layout [
text bold str 320 as-is
button "OK" [hide-popup]
show-info net-utils/export msg

Pick of the List

Pick of the list this month was a popular reply post from Gregg Irwin.

<< I've already signed up and got my evaluation User ID. I'll probably get everything set up this Saturday. >>

Should take you about 5 minutes.

<< The problem for me is that I'm still not sure as to exactly what Rebol IOS is. Is it an OS? Is it a suite of applications? Or is it both? I assume the answer is both. >>

It is both; it is neither; and it is much much more. Cryptic enough for you? ;)

Simply put, it is a client side piece, called Link, that connects to the server piece (called Serve, just as you would expect). Link appears as a window, called the Desktop, that is your main point of navigation. It's very much like the View desktop if you've played with that. On the desktop are a number of little applications (reblets) along with folders that contain files, other reblets, etc. None of this is terribly exciting from its description, I know.

What's exciting is the first time you fire up Link and it synchronizes you with everything that's published on the server quickly and reliably. Now you can publicly Conference, privately Message, submit feedback, browse the Expert FAQ list, see Who is on line, see what News has been posted, see if anything interesting is happening on the shared Calendar, check out what others have published, etc. Hmmm, still not very exciting.

So, not being excited by this so far, you decide to get a second opinion. You fly to Paris and unpack your Link client from the single floppy you brought with you. You connect and, BOOM, everything is there, just as if you were at home.

I wrote a little utility and published it on IOS. One day, Messenger popped up and someone said "Can I add a couple features to it?" I said that was no problem, made sure I had published the latest version I had, and a short time later, a new version appeared with their changes. Messenger came up again and they said "Hmmm, I'm seeing a bug that causes..." I opened the code they had published, fixed the bug, and republished it (after testing it of course :). If I didn't like the end result, I'd just pull up History for the script and republish an eariler version.

<< Also, since I'm responsible for the movement of data to and from our ODS (Operation Data Store), I'm curious about REBEL IOS's strengths in this department. Which is why I asked the question that I did. >>

IOS is built on Rebol, the language, and Rebol is a "messaging language". Eventually, as the clouds are lifted from our eyes, we'll begin to understand what that really means to us as developers. In the meantime, it's a very net-aware language with ties to a few interop mechanisms and the ability to create new ones without much difficulty.

<< I've been asked by our "Technology Architect" to investigate Rebol's CGI capabilities, as well. So I'm hoping a few members on this list can clue me in on them. >>

Others will have to speak to that, as it's not my area, but I've see lots of great things done with it. Sometimes there's an initial setup hurdle or two it seems, just because it's not mainstream, but I haven't seen any horror stories posted.

<< 1) How well does Rebol manage libraries across disparate systems, keeping them synchronous. I assume "very well". :) >>

If you mean managing Rebol scripts, general purpose or otherwise, that's very general and could be done a number of ways. If you're using IOS, it's seamless. You publish. It syncs.

<< 2) How does it manage script complexity, where info/data is dependent on code (I.e. Perl, C, etc...)? >>

I'm not sure exactly what you mean, but Rebol is very different in how the code/data line is not just blurred, but can disappear entirely. You could write a lot of solutions in Rebol very much like you would in other languages and, while a little different syntactically, there wouldn't be much overall difference. When you stop thinking in terms of how you would do it in other languages, and just think of how you *want* to do it, volumes of code can disappear and the remaining pieces communicate more clearly. The Rebol code I write is totally different from what I would write in other languages, and I still haven't let go of "the old ways" as much as I want to. :)

<< 3) Can it move data without XML, or is it dependent on it? >>

There are some XML libs, and some built in support for XML, but Rebol's native format is all it needs.

I hope I don't sound too evangelistic. I just love working with this language.




Look forward to another Zine net month, when we will cover changes in Core 2.6, look at writing a simple IOS Reblet, and using Rebol from CGI. If you would like to contribute to the Zine, send an email to zine@rebolforces.com.

Until then have fun Reboling,

Allen K