REBOL Programming/Advanced/Interpreter

< REBOL Programming‎ | Advanced

Author: Ladislav Mecir

Interpretation phasesEdit

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 three basic steps:

  1. make phase
  2. load phase
  3. do phase

MAKE phaseEdit

In this phase the make function creates a REBOL block.

The created block is filled by make 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 essay.

LOAD phaseEdit

In this phase the load function extends the global context to contain all words (values of the any-word! datatype) referenced by the block made in the previous phase, with the exception of refinements.

All words (except for refinements) in the created blocks are replaced by their global context counterparts.

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

DO phaseEdit

For the do function is immaterial, whether the interpreted block is a result of the previous phase or a result of any other operation. This model describes the behaviour of the do function processing REBOL blocks and parens in all situations.

The do function processes the values contained in the interpreted block one by one. When it 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, the do function 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 do function finishes the evaluation of all values contained in the block, the last obtained value becomes the result of the evaluation.

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

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

Simulation (simple REBOL interpreter written in REBOL)Edit

The simulation function below is a simple interpreter able to interpret one line of text you input from the keyboard.

  simulation: func [/local input-line created-block loaded-block] [
      ; step #0, INPUT
      input-line: ask ">> "
      ; step #1, MAKE
      created-block: make block! input-line
      ; step #2, LOAD
      loaded-block: load created-block
      ; step #3, DO
      do loaded-block
      ; done

It illustrates the description written above.

Evaluation of REBOL wordsEdit

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

>> b: make block! "a" ; == [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 do function collects all the arguments for the function and "calls the function" like above.

The shrinking list of the word-active datatypes still contains lit-words and lit-paths (unnecessarily, in my opinion). On the other hand, the behaviour of unset! values could be made more function-like too.

   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! 

Recursive behaviourEdit

The word-active values can exhibit recursive behaviour like:

   ; recursive function
   factorial: func [n] [
       either n <= 1 [1] [n * factorial n - 1]
>> factorial 5
== 120

In fact, we can make even not-active values to exhibit recursive behaviour, if we use active values appropriately:

   ; recursive block
   factorial-block: [
       either n <= 1 [1] [n * (n: n - 1 do factorial-block)]
>> n: 5 do factorial-block
== 120