Rebol Tips and Techniques

This page is a collection of bits of information that people may find useful.

It reflects information that should be suitable for programmers learning about the Rebol language itself and what it can do.

Language elements

What is any-function?

>> type? :break
== native!
>> type? :func
== function!
>> type? :add
== action!
>> type? :+
== op!

Any-function is a pseudotype which includes function!, action!, op!, and native! (i.e. the action any-function? returns true for any of these). Try

help native!
help function!

etc

to get a list of the currently defined values of that type.

Other pseudotypes are series, any-block, and any-string. This topic is discussed (to some extent) in the docs. Elan's book provides more info.

Larry Palmiter

Using reverse

>> index? reverse "abcd"
== 5

>> head reverse "abcd"
== "dcba"

Elan

The Does function

Use does when your function does not require arguments or locals.

e.g instead of using func with a an empty arg block print-hello: func [][print "Hello"]

use print-hello: does [print "Hello"]

Allen Kamp

Testing if a word exists

> Two questions: > 1) How may I query rebol to verify whether a word exists? > 2) How may I query rebol to verify whether a member of an object exists?

>> value? 'print == true >> value? 'antidisestablishmentarianism == false >> o: make object! [m: "mmmmmmmm"] >> in o 'antidisestablishmentarianism == none

Andrew Martin

Words with values

> I've tried to use a part of rebdoc.r to generate a list of all words > before and after a function call.. The problem is that I also get a > million of words that has no values in my lists, and this makes the > function unusable for the purpose I was thinking of. > > The purpose was to generate a list before a function call, and compare > it to a list generated afterwards, to see if a function by mistake has > created new words.

words-with-vals: has [word-list] [
    word-list: copy []
    foreach word first rebol/words [
        if not error? try [get in rebol/words :word] [append word-list word]
    ]
    word-list
]

Larry Palmiter

binary!

Think of binary as a string represented in hexadecimal.

>> to-char #{14}
** Script Error: Invalid argument: #{14}
** Where: to-char
** Near: to char! :value
>> series? #{14}
== true
>> first #{14}
== 20

So #{14} is a series of bytes (of length 1). If we want to convert to characters, we have to get one character-sized part.

>> to-char first #{14}
== #"^T"

This means that there are a bunch of expressions that numerically evaluate to 20...

>> to-integer #{14}
== 20
>> to-string #{14}
== "^T"
>> to-integer #"^T"
== 20
>> to-integer first "^T yadda, yadda, yadda"
== 20

And back again...

>> to-binary "^T"
== #{14}

Now, remember your ASCII codes:

>> to-integer #"2"
== 50
>> to-integer #"0"
== 48
>> to-hex 50
== #00000032
>> to-hex 48
== #00000030

So, when you convert directly from integer to binary, you're getting an implicit string conversion in the middle:

>> to-binary 20
== #{3230}
>> to-char first to-binary 20
== #"2"
>> to-char second to-binary 20
== #"0"
>> to-string to-binary 20
== "20"

pick and poke binary

"Can someone help me with the following error? Why can I swap elements of a string! with pick and poke, but not the elements of a binary! ?"

>> list: to-binary "1234567890"
== #{31323334353637383930}
>> poke list 1 pick list 2
** Script Error: Invalid argument: 50
** Where: halt-view
** Near: poke list 1 pick list

Let's look at your data another way

>> foo: to-binary "1234567890"
== #{31323334353637383930}
>> pick foo 2
== 50
>> second foo
== 50

So what we're getting is a byte promoted to a full-sized integer. To poke it back, let's force it back down to byte width.

>> poke foo 1 to-char pick foo 2
== #{32323334353637383930}
>> to-string foo
== "2234567890"

Joel Neely

There is one more feature which can be handy. To directly convert an integer in the range 0 to 255 to a 1-byte binary, just put it in a block:

>> to-binary [255]
== #{FF}
>> to-integer to-binary [255]
== 255

As you noted the last line will return a full 4-byte REBOL integer (same as long in C). REBOL does not natively support 2-byte integers, 1-byte integers can be handled as char.

Larry Palmiter

Bitwise operations

Just FYI: Recent REBOL's include the ability to perform bitwise operations on binary types using AND, OR, and XOR.

nibble: func [
    a [binary!]  "Binary Byte"
    b [integer!] "0-15"  /high
][a or to-binary to-char either high [b * 16][b]]


comment [ 
    nibble/high #{01} 15 == #{F1}  
    nibble      #{10} 15 == #{1F} 
]

Jeff

Enbase and Debase

enbase takes your string and returns another string. The returned string is an encoding of your value in your choosen representation. Here you elected to use a base 2 encoding. Therefore enbase returned you a string of "1"s and "0"s. What was the value you encoded? Ascii.

>> enbase/base "2" 2 ; Encode string into a binary encoding using base 2. == "00110010"

A related function is debase. Debase will convert a string into binary. It does this by decoding the representation stored in the string.

debase/base "00110010" 2 ; Interpret string as base 2 encoded binary. == #{32}

>> to-integer #{32} ; Interpret hex as integer. == 50

>> to-char 50 ; Interpret integer as character (or you can think of it as a byte of that value) == #"2"

You could also do

>> to-string #{32} ; Interpret binary as string. == "2"

So here's how to get the result you first expected

>> enbase/base to-string to-char 2 2 == "00000010"

And to do a complete the loop.

>> to-string debase/base enbase/base "The quick brown fox." 2 2 == "The quick brown fox."

Brett Handley

Dword values

This is a confusing and poorly documented part of REBOL. There are two problems. First to-hex returns an issue! NOT a binary!, so a conversion is required. Second REBOL only supports signed 32-bit integers, so the upper half of the unsigned binary range is represented as negative integers. So we have to use the decimal! type to represent unsigned 32-bit greater than 2 ** 31 - 1 or 2147483647 which is the largest signed 32-bit, and then convert to the correct negative integer by subtracting 2 ** 32. It would be nice if RT added direct binary conversion for integers to the language.

Here is a function which will work. As you can see, the required steps are not so obvious. If you write these to a file or port, be sure to use binary mode.

; Convert unsigned 32-bit integer represented
; as REBOL decimal! or integer! to binary form.
; On Intel platform the bytes need to be
; reversed to be read by other programs.
; The maximum value is 2 ** 32 - 1 which is
; 4294967295 or #{FFFFFFFF} in binary.

unsigned-to-binary: func [n [number!] /rev][
 if n > (2 ** 31 - 1) [n: n - (2 ** 32)]
 n: load join "#{" [form to-hex to-integer n "}"]
 either rev [head reverse n][n]
]

>> unsigned-to-binary 5555555555
== #{4B230CE3}
>> unsigned-to-binary (2 ** 32 - 1)
== #{FFFFFFFF}

Larry Palmiter

Getting into paths

> Paths are a little weird, and so on... > You can't get to the parts of a path > as if it were a block.

Actually you can:

>> third 'a/b/c
== c

or if the path is referenced by a word

>> p: 'a/b/c
== a/b/c
>> type? :p
== path!
>> third :p
== c

or if the path is in a block

>> blk: ["string" a/b/c]
== ["string" a/b/c]
>> type? second blk
== path!
>> third second blk
== c

The trick is to prevent evaluation of the path in the same fashion as with functions. Also you can convert between paths and blocks.

>> to-block :p
== [a b c]
>> to-path to-block :p
== a/b/c

Larry Palmiter

Sorting a series of objects

"Sorting a series of objects" {

SORT has a compare refinement. So use sort on the series but supply a function that will compare your objects.

See "Comparison Functions" in "Sorting Series" of the Rebol/Core documentation.

So using your example:

dir: []
foreach file read %. [
     info: info? file
     append dir make object! [date: info/date file_name: file]
]
sort/compare dir func[a b][lesser? a/date b/date]

We can make a function that sorts series given a field name:

sort-object-series: func [
    "Sorts a series of objects"
    series [series!]
    field [word!]
][
    sort/compare series func[a b][lesser? get in a field get in b field]
]

Now we can do this:

sort-object-series dir 'date
sort-object-series dir 'file_name

Brett Handley

Return from the middle of a script

you can try:

result: catch [
    do script
]

and in script:

if some-condition [
    throw string1
]

What are objects good for

"Objects provide a way to group a set of values into a context that can be
passed around as a whole and treated as a single value. This is useful for
dealing with structures that are more complex in nature, as they allow the
data and code to be kept together (encapsulated).

Once created, an object can be treated as a single value. It can be passed
to a function as an argument, returned from a function as a result, or set
to a word. The values within the object are accessed by name. "

That was from the user guide.

Objects help to structure scripts.

Think of how useful local variables are in functions. You create a local variable, use it and know that it will not clash with words outside of the function*. Objects give you local variables and local functions packaged together into one blob. Now that is useful. The local functions can see the local variables and the other local functions and be like a mini-program embedded inside a bigger one.

Before I get clouted for using confusing terminology - the guide refers to these as "fields" or "instance variables" and "object functions".

Here's an example, when I've been programming using the parse function, I can end up with quite a few parse rules and other variables related to parsing. It might be that I want my parsing functionality to be part of a bigger script - so it would be good to put everything related to parsing (rules, flags, functions) in one spot - an object. Doing this makes my overall script more readable since I know that everything in that particular object relates to the parsing functionality. Also, it simplifies the my naming of words and prevents naming clashes with global words. It sort of like having work areas of a factory floor painted with coloured safety lines in order to separate hazardous machines or processes.

As the guide mentions another use for objects is for structuring your data. You can create a special data structure with functions to manipulate it and put these in an object. So you can have object functions that add and remove values from your data structure and be comforted to know that the object is making sure the structure is always mainted properly.

Brett Handley

One function instance for multiple objects

To create only one instance of the function, define the function first and assign it to a word. Then, assign the object element to a reference of this word. Example:

my-func: func [][print "hello!"]

obj1: make object! [f: :my-func]
obj2: make object! [f: :my-func]

same? (get in obj1 'f) (get in obj2 'f)
== true

Michael Jelinek

Copying objects with subobjects

Well, it's just that subobjects are not cloned. I think this saves a lot of memory in /View, for example.

The workaround is to clone the subobject manually:

c: make main [ sub: make sub [ name: "joe" ] ]
probe c

Gabriele Santilli

Rebol Environment

Display words by type

The improved help in view can also display by type

Try these

? native!
? function!
? char!

Allen Kamp

System status words

The globally known words are returned in a block by

words: first system/words

You can print them one to a line with

foreach word first system/words [print word]

You can use WHAT to list all functions.

Larry Palmiter

Techniques

Intercepting the escape key

> Can the escape key sequence be intercepted so that > rebol can then provide a prompt for additional input, rather > than immediately terminating?

system/console/break: false

Julian Kinraid

Polling for keystrokes

> From rebol/core, I would like to run a program in > a forever loop that will process (something) until > any of a set of keystrokes are intercepted.

>> con: open/binary console:/ 
>> forever [                 
[    ; do-my-stuff            
[    if ch: pick con 1 [      
[        print ["broken with:" to-char ch "(" ch ")"]
[        break                                       
[        ]
[    ]
broken with: b ( 98 )

Gabriele Santilli

Polling for keystrokes 2

some lines i use:

if not value? 'cons [cons: open/binary [scheme: 'console]]

if you want to wait for a key:

system/console/break: false
wait cons
system/console/break: true

do-key is my handler function, rest is "gimme a key". i think without a key 'none ? ever 'wait 'ed :)

do-key to char! pick cons 1 ; get and handle the key

Volker

Cautions on using Load for manipulations

Yes, there are at least 2 caveats to the approach of converting the string to a block of REBOL words with

>> string: "This is a string with some words"
== "This is a string with some words"
>> b: to-block "This is a string with some words"
== [This is a string with some words]
>> type? first b
== word!

The first caveat is that not everything we may want as "words" is a valid REBOL word.

>> to-block "This is a string |\funny"
** Syntax Error: Invalid word -- |\funny.
** Where: (line 1) This is a string |\funny

So the conversion fails.

The second caveat is that the REBOL dictionary only holds 2558 words.

>> repeat j 3000 [append b to-word join 'word j]
** Internal Error: No more global variable space.
** Where: to word! :value
>> length? first rebol/words
== 2558

CAUTION: There is no way to remove words from the dictionary, the GC does not touch them. In order to create a new word after this experiment, you will have to start a new REBOL session.

So if there are many unique "words" in the string, you will permanently tie up space in the REBOL dictionary.

Galt's solution:

string: "This is a string with some words"
blk: parse/all string " "
print first back find blk "with"

is much better, because it converts the string to a block of string! values rather than a block of REBOL words.

If you want to just parse on any whitespace (including linefeed, etc), you can use

>> parse string none
== ["This" "is" "a" "string" "with" "some" "words"]

which in this case gives the same result as

parse/all string " "

Larry Palmiter

Can Rebol print?

Tiana asked: > Does anyone know how to let Rebol print stuff?

If you're using Windows, then this function works for me:

Printer: func [Text [string!] /Page] [
    secure [
        %//prn [allow write]
    ]
    write %//prn Text
    ; Append Carriage Return (CR) and Page Feed.
    if Page [write/binary %//prn "^(0D)^(page)"]
    Text
]

For unix, it's easier, so I believe.

Andrew Martin

Printing under Windows

Printing to a network printer under Windows can be done with Core and View as well as Command.

If the network printer is in Network Neighborhood as

\\server\printer

and you have a prn file myfile.prn (which may be binary!) in the current dir you can just use

write/binary %/server/printer read/binary %myfile.prn

Larry Palmiter

Testing for integers

To test if a string is a valid REBOL integer, you can just do:

integer? try [load string]

Gabriele Santilli

E-mail

E-mail is always BCC

An important note is that when you use SEND, the [block of addresses] is, by default, entirely Bcc. If you want it to show up in the destination email with the list of people you sent it to, you'll want to use send/header and make a header object like below. Then you'll want to set the to field to the comma separated list of addresses:

send-to: [person1@someplace.com person2@someplace2.com]
cc-to: [cc1@nowhere.com cc2@whatever.com]
bcc-to: [bcc1@blind1.com bcc2@blind2.com]

header: make system/standard/email [
    from: you@wherever.com
    to: replace/all form send-to " " ", "
    cc: replace/all form cc-to " " ", "
    subject: "Message subject"
]

with any other headers you like.

Then:

send/header join join send-to cc-to bcc-to "message text" header

and it should be off. And the other end sees that you sent the email to two people, and cc'd two others while you actually sent it to 6. If you want to add the Bcc header to the ones you Bcc'd, then just mail those after and add the Bcc header field for them.

Bottom line is that REBOL is not trying to pretend to be an emailer. You get to decide how you want your mail to look. If you want an extra reference, check out the %attach.r script on REBOL.org under the 'email area of the script library. I wrote that to send mail with MIME attachements (looks like you're doing the same given that content type header) and it does what I've explained above.

Sterling

Custom E-mail headers

You can add your own header to an email using the header object.

For example,

your-address: someone@somewhere.com
header: make system/standard/email compose [
    from: to-string your-address
    to: to-string your-address
    subject: "Test message"
    x-approval-by-bruno: 'Yup
]
send/header your-address "Test message contents" header

Brett Handley

Printing

Under windows

Printing to a network printer under Windows can be done with Core and View as well as Command.

If the network printer is in Network Neighborhood as

\\server\printer

and you have a prn file myfile.prn (which may be binary!) in the current dir

you can just use

write/binary %/server/printer read/binary %myfile.prn

Larry Palmiter

Some scripts

Traversing the tree generated by parse-xml

A recursive function for traversing the tree could look something like this:

traverse-tree: func [element] [
  either not none? element/3 [
    prin rejoin ["<" element/1 ">"]
    foreach subelement element/3 [
      either block? subelement [
        traverse-tree subelement
      ][
        prin subelement
      ]
    ]
    prin rejoin ["</" element/1 ">"]
  ][
    prin rejoin ["<" element/1 "/>"]
  ]
]

You can also parse the whole parse-xml structure with the new block parser in /View and /Core 2.3. It only takes about 6 lines of code. :-)

Try this:

doc-rule: ['document none! subtags-rule]
subtags-rule: [none! | into [some [tag-rule | substring-rule]]]
tag-rule: [into [string! parameters-rule subtags-rule]]
substring-rule: [string!]
parameters-rule: [none! | block!]
parse (parse-xml {<a>teststring<b/><c/></a>}) doc-rule

Martin Johannesson

Ip address to integer

tup-to-num2: func [x /local z out][
  out: 0.0
  repeat j length? x [
    out: out * 256 + pick x j
  ]
]

Eric

Decimal to Binary

dec2bin: func [
    "Converts Based 10 Integers to Binary"
    dn [integer!] "Base 10 Integer"
    /local holder "accumulate non-zero results"
] [
    either dn = 0 [
        "0"
    ] [
        either dn < 0 [
            holder: next copy "-"
            dn: - dn
        ] [
            holder: copy ""
        ]
        while [dn > 0] [
            insert holder dn // 2
            dn: to-integer dn / 2
        ]
        head holder
    ]
]

Joel Neely

Parse

Explaining parse

Parse Tutorial

Brett Handley

Miscellaneous

Redirecting output

> When I REBOL start from a command shell (DOS-Box) with > REBOL script.r > how can I redirect all REBOL-Output to the DOS-Console?

c:\rebol.exe -w script > out.txt

Jeff

also try

rebol -w your-script.r | more

At least it works in Windows NT, don't know about Win95.

Michal Kracik

Environment variables

>I've checked all the documentation I can think of, but still can't figure >out how to access the value of a Windows `Environmental Variable'. Can >someone point me in the right direction?

With /Command or /Pro you can access the C getenv function from the C library, or you can use the call function like

a: "" call/output "echo %PATH%" a

or for all variables

a: "" call/output "set" a

With all REBOLs you can pass them on the command line like

rebol.exe --do "app-paths: {%PATH%}" blah.r

and even preprocess them like this

rebol.exe --do "app-paths: parse {%PATH%} {;}" blah.r

which returns the PATH variable broken into its components.

I used a trick like the latter one to implement REBOL batch files that could be anywhere in the PATH directories.

Brian Hawley

Reinstalling View

There is a reinstall command line option. Type >>usage in \view console to get the details.

Allen Kamp

On math

REBOL (unfortunately, in my opinion) interprets "to-integer" as

"the integer part as written out in text",

rather than the more mathematically correct

"largest integer not exceeding"

(and REBOL is not alone in this). What this means is that we get

>> -5 // 2
== -2.5
>> to-integer -5 / 2
== -2

Now, based on the definition of remainder, / and // MUST satisfy (for integral a and b)

a = b * (to-integer a / b) + (a // b)

which backs us into the weird case that

>> -5 // 2
== -1

Why is this weird? There's lots of useful mathematics (and several useful programming techniques, as well) based on the idea that the modulus (remainder after division) is ALWAYS bounded between zero and (divisor - 1). For example, think about "clock arithmetic" using a 24-hour clock (in which the hours run from 0 to 23). To get the hour that is thirteen hours after four in the afternoon, just evaluate

>> (4 + 12 + 13) // 24
== 5

that is, 4 (o'clock) + 12 (i.e. PM) + 13 (elapsed time).

BUT... If I want to find out what hour is seventeen hours before three in the morning, I get a problem...

>> (3 - 17) // 24
== -14

which ISN'T on the clock. We can sidestep this ugliness by either

1) making sure that we never try to take a remainder with negative

arguments (which is the approach taken in dec2bin above), or

2) write a helper function that cleans up the positive/negative

mess, as follows

mod: func [
    "computes true (natural) modulus"
    a [integer!] "dividend"
    b [integer!] "divisor"
] [
    a // b + (b: abs b) // b
]

which allows us to see that

>> mod -5 2
== 1
>> mod -5 3
== 1

and solve the time problems above

>> mod (4 + 12 + 13) 24
== 5
>> mod (3 - 17) 24
== 10

Joel Neely

Internet Time

As you probably know, Swatch came up with "Internet time" based on 1000 beats in a day and with Biel, Switzerland as the mean time.

Here is a function which will give you the time in beats.

>> internet-time
== 982.1875

 internet-time: func [][
     local-mean-time: now/time
     zone-designator: now/zone * -1
     greenwich-mean-time: local-mean-time + zone-designator
     biel-mean-time: greenwich-mean-time - 1:00:00
     biel-mean-time-total-seconds: (biel-mean-time/hour * 3600) + (biel-mean-time/minute * 60) + biel-mean-time/second
     beats: biel-mean-time-total-seconds * .01157407407407407407407407407
 ]

Ryan C. Christiansen

Databases

An object database

What!? You just want us to just whip out an object database, eh? ;)

Ok, why not.... Here is a small "contacts" database keyed by email. You can expand or reduce the record definition without corrupting or affecting the database.

REBOL [Title: "Email Contact Database"]

db-file: %data.r
record: context [name: email: phone: web: none]
database: []

load-data: has [data] [
    data: load/all db-file
    clear database
    foreach item blk [
        item: make record item
        repend database [item/email item]
    ]
]

save-data: has [data] [
    data: copy []
    foreach [key obj] database [
        append/only data third obj
    ]
    save db-file data
]

find-data: func [email] [select database email]

remove-data: func [email] [remove/part find database email 2]

insert-data: func [email' name' phone' web'] [
    repend database [
        email'
        make record [
            email: email'
            name: name'
            phone: phone'
            web: web'
        ]
    ]
]

You can expand/contract the record definition at any time.

This is untested... but should be close to working, less a few minor typos. If you expect to grow this database to a large size, you will want to MAKE HASH! the database when you load it.

The remove/part on find really does work. Remove none is allowed in Core 2.5

Carl Sassenrath