跳转到内容

Rebol 编程/高级/解释器

来自维基教科书,开放世界中的开放书籍

作者:Ladislav Mecir

解释阶段

[编辑 | 编辑源代码]

Rebol 源代码的解释(文本文件中的文本、Rebol 字符串中的文本、从键盘输入的文本等)由三个基本步骤组成

  1. make 阶段
  2. load 阶段
  3. do 阶段

MAKE 阶段

[编辑 | 编辑源代码]

在这个阶段,make 函数创建一个 Rebol 块。

创建的块由 make 填充,以引用通过根据相应 Rebol 数据类型的规则解析提供的源文本而获得的 Rebol 值。

新块引用的所有单词(即所有 any-word! 数据类型的值)都是未绑定的,即它们没有上下文信息。有关上下文的更多详细信息,请参阅Bindology 论文。

LOAD 阶段

[编辑 | 编辑源代码]

在这个阶段,load 函数扩展全局上下文,以包含由上一阶段创建的块引用的所有单词(any-word! 数据类型的值),但细化除外。

创建的块中的所有单词(细化除外)都替换为其全局上下文对应项。

这就是为什么load 结果块中包含的所有单词都是全局单词(细化除外,如上所述)。

DO 阶段

[编辑 | 编辑源代码]

对于 do 函数来说,解释的块是前一阶段的结果还是任何其他操作的结果无关紧要。此模型描述了 do 函数处理所有情况下 Rebol 块和括号的行为。

do 函数以我们称为隐式求值的方式依次处理解释的块中包含的值。

简单隐式求值

[编辑 | 编辑源代码]

对于某些值,隐式求值的结果是遇到的值本身。例如,整数、小数、字符、字符串和块。

括号的隐式求值

[编辑 | 编辑源代码]

与隐式地求值为自身的块相反,在解释的块中遇到括号时,会像 do 函数解释块一样进一步解释。

函数的隐式求值

[编辑 | 编辑源代码]

当遇到 any-function! 数据类型的值时,需要收集它们的实参并将其提供给求值的函数。

具体来说,如果求值的是 do 函数,它会以我们称为显式求值的方式求值其实参。特别是,如果实参是一个块,do 函数会像“DO 阶段”部分中描述的那样递归地解释它。因此,对于块来说,显式求值与隐式求值不同。这就是使 Rebol 特别的原因。

单词的隐式求值

[编辑 | 编辑源代码]

当隐式求值的值是具有 word! 数据类型的单词时,它被视为一个变量,并且会查找该变量的值并根据其数据类型进行处理。我们称之为间接隐式求值

还存在间接显式求值,它发生在将单词或路径作为实参提供给 do 函数时。

解释的结果

[编辑 | 编辑源代码]

do 函数完成对块中包含的所有值的隐式求值时,最后获得的值将成为块解释的结果。

例外:如果 do 函数获得 error! 数据类型的值,并且该值没有用作接受错误值的函数的实参,则解释器将导致错误。

在我看来,这个例外是多余的,可以在将来版本的解释器中轻松避免。

模拟(用 Rebol 编写的简单 Rebol 解释器)

[编辑 | 编辑源代码]

下面的 simulation 函数是一个简单的解释器,能够解释您从键盘输入的一行文本。

  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
  ]

它说明了上面所写的描述。

Rebol 单词的求值

[编辑 | 编辑源代码]

Rebol 单词(word! 数据类型的值)表现出最复杂的行为。当 do 函数对一个单词进行求值时,do 函数首先会选择单词所引用的值。如果求值的单词没有上下文,则 do 函数将无法选择该值,并且它会改为引发错误。

>> b: make block! "a" ; == [a]
== [a]
>> do b
** Script Error: a word has no context
** Near: a

接下来的操作取决于所选值的类型。一个特殊情况是,当单词未设置时,即单词的值是 unset! 数据类型时。

>> do [a]
** Script Error: a has no value
** Near: a

对于某些值,不需要进一步的操作,并且所选的值将成为单词求值的结果;对于其他值,操作将根据数据类型执行。第二种类型的值可以称为单词活动值或单词活动数据类型。最近版本的解释器使用越来越少的单词活动数据类型。

单词活动值的典型代表是 any-function! 值。当在求值的单词的值中遇到此类值时,do 函数会收集该函数的所有实参并像上面一样“调用该函数”。

单词活动数据类型的不断缩减列表中仍然包含 lit-words 和 lit-paths(在我看来,这是多余的)。另一方面,unset! 值的行为也可能变得更加像函数。

   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 function
   factorial: func [n] [
       either n <= 1 [1] [n * factorial n - 1]
   ]
>> factorial 5
== 120

实际上,如果我们适当地使用活动值,我们甚至可以使非活动值表现出递归行为

   ; recursive block
   factorial-block: [
       either n <= 1 [1] [n * (n: n - 1 do factorial-block)]
   ]
>> n: 5 do factorial-block
== 120
华夏公益教科书