Interpretation

    Author: Ladislav Mecir
    Date: 26/February/2004

Contents:

1. References
2. Motivation
3. Interpretation phases
4. MAKE Phase
5. LOAD Phase
6. DO Phase
7. Evaluation Of REBOL Words
8. Recursive Behaviour

1. References

Ladislav's articles page is at Rebol Articles, where you can find other useful references too.


2. Motivation

The goal of this article is to contain a description of how the REBOL source code is interpreted.

The present version of the article has been tested with REBOL/Core 2.5.5.


3. Interpretation phases

The interpretation of REBOL source code (a text contained in a text file, a text contained in a REBOL string, a text typed in from the keyboard, etc.), consists from two basic steps:

  1. MAKE phase

  2. LOAD phase

  3. DO phase


4. MAKE Phase

In this phase the REBOL interpreter creates a REBOL block.

The created block is filled by the interpreter to refer to the REBOL values received by parsing the supplied source text according to the rules for the corresponding REBOL datatypes.

All words (i.e. all values of ANY-WORD! datatype) referenced by the new block are unbound, i.e. they have no context information (for more details on contexts see Bindology).


5. LOAD Phase

In this phase the REBOL interpreter extends the global context to contain all words (values of the ANY-WORD! datatype) referenced by the block made in the previous phase.

The only exception make the refinements.

All words in the created blocks are then replaced by their global context counterparts.

That is why all words contained in a LOAD phase result block are global words (except for the refinements, as was stated above).


6. DO Phase

For the interpreter is immaterial, whether the interpreted block is a result of the previous phase or not. That is why we can have only one description that can be used to describe the interpretation of REBOL blocks (and parens) in general.

During the DO phase the REBOL interpreter processes the values contained in the interpreted block one by one.

When the interpreter encounters a REBOL value in the interpreted block, it checks its datatype first. For some values it exhibits a complicated behaviour - the value is processed further to get a result - for other values no further processing is needed and the result of evaluation is the encountered value itself.

The values of the first kind I call the active values:

    active?: func [
        {finds out, if a Rebol value is active}
        value [any-type!]
    ] [
        parse head insert/only copy [] get/any 'value [
            any-function! | get-word! | lit-word! |
            set-word! | word! | lit-path! | paren! |
            path! | set-path!
        ]
    ]
The behaviour of the values that aren't active is simple. Notice an important thing that distinguishes Rebol from other languages! Rebol BLOCK! datatype values aren't active! Rebol has got an active counterpart of the BLOCK! datatype though. Try to find out which one it is.

Let's describe the behaviour of the ANY-FUNCTION! datatype values. To evaluate them, Rebol interpreter has to collect their arguments and supply them to the evaluated functions.

As you might have found out, the Rebol PAREN! datatype values are the active counterparts of blocks, which means, that this whole section describes their evaluation too.

When the interpreter finishes the evaluation of all values contained in the block, the last obtained value becomes the result of the evaluation.

Exception: if the interpreter evaluates an ERROR! datatype value and the value isn't used as an argument to a function accepting error values, the interpreter fires the error.

In my opinion, this exception is unnecessary and can be painlessly avoided in the future versions of the interpreter.


7. Evaluation Of REBOL Words

Rebol words (the values of the WORD! datatype) exhibit the most complicated behaviour. When a word is evaluated by the interpreter, the interpreter tries to pick the value the word refers to. If the evaluated word has no context, the interpreter is unable to pick the value and it fires an error instead:

    b: make block! "a" ; == [a]
    do b

    ; ** Script Error: a word has no context
    ; ** Near: a
The next action depends on the datatype of the picked value. A special case is, when the word is unset, i.e. when the value of the word is of the UNSET! datatype:

    do [a]

    ; ** Script Error: a has no value
    ; ** Near: a
For some values no further action is needed and the picked value becomes the result of the word evaluation, for other values the action is taken in accordance with the datatype. The values of the second kind we could call word-active values, or word-active datatypes. The recent versions of the interpreter use decreasing number of word-active datatypes.

Typical representants of the word-active values are ANY-FUNCTION! values. When such a value is encountered as a value of an evaluated word, the interpreter collects all the arguments for the function and "calls the function" like above.

My point of view is, that the ANY-FUNCTION! datatype values should be the only word-active values. Nevertheless, the list of the word-active datatypes still contains lit-words and lit-paths:

    word-active?: func [
        {finds out, if a Rebol value is word-active}
        value [any-type!]
    ] [
        parse head insert/only copy [] get/any 'value [
            unset! | any-function! | lit-word! | lit-path! 
        ]
    ]

8. Recursive Behaviour

The word-active values can exhibit recursive behaviour like:

    ; recursive function
    fact: func [n] [
        either n <= 1 [1] [n * fact n - 1]
    ]
    fact 5
In fact, we can make even not-active values to exhibit recursive behaviour, if we use active values appropriately:

    ; recursive block
    factb: [
            either n <= 1 [1] [n: n - 1 (n + 1) * do factb]
    ]
    n: 5
    do factb