预处理器

1. 概念

Red 预处理器是 Red 的一种方言,它允许使用一个在常规 Red 语言之上的特定的层来转换 Red 源码。转换是通过在 Red 源码中内联预处理器关键字(称为指令)达到的。这些指令被处理的时机为:

  • 当 Red 源码被编译的时候

  • 当 Red 源码通过传有一个文件参数的 do 函数被运行的时候

  • 当以一个区块值调用 expand-directives 的时候

预处理器在加载阶段之后被调用,所以它处理的是 Red 值而不是文本形式的源码。

指令类别:

  • 条件指令:根据一个表达式的结果引入代码。

  • 控制指令:控制预处理器的行为。

  • 宏:使用函数转换代码,它允许更复杂的转换。

指令以特定的 issue! 值来表示(以一个 # 符号开头)。

当一个指令被处理的时候,它会被替换为它返回的结果值(有些指令没有返回值,所以它们仅会被移除),这就是达成源码的转换的方法。

注意

Red/System 有它自己的预处理器,是类似的,不过它是在底层,并且是应用在文本形式的源码上。

1.1. 配置对象

它在条件表达式与宏中提供了一个隐含的 config 对象,以给与用于编译源码的设定的访问,这些设定经常用于有条件地引入代码。完整的设定清单可以从这里找到。

例:

    #if config/OS = 'Windows [#include %windows.red]
注意

当在运行时(从 Red 的解释器)使用预处理器时,config 对象也有效,而且它会反映用来编译目前用来运行代码的该 Red 可执行文件的选项。

1.2. 执行语境

为了避免泄露单词到全局语境,造成多余的副作用,所有在指令中使用的表达式都绑定到一个专用的语境中。语境会随着用于条件表达式、宏和 #do 指令的每个设词扩展。

贴士
  • 可以使用以下表达式打印出隐藏的语境的内容:

        #do [probe self]
  • 当在运行时被使用的时候,隐藏的语境也可以使用以下形式直接访问:

        probe preprocessor/exec

1.3. 实现上的注意

目前,用于编译时的指令里的表达式都是用 Rebol2 解释器求值的,因为它运行着工具链的代码。这是临时的,应会尽快地把它切换成一个 Red 执行器。在此期间要确保你的表达式和宏代码也可以用 Red 运行,以在运行时(未来还包含编译时)和 Red 解释器兼容。

2. 宏

Red 预处理器支持定义宏函数(简称)来实现更复杂的转换。宏允许以高效的形式进行元编程,它执行的代价花费在编译时而不是运行时。在运行时 Red 已经有很强的元编程能力,不过为了使 Red 源码在编译器和解释器运行地一样好,宏也可以在运行时被解析。

注意

没有读取时(读者)宏。考虑到用 Parse 方言对文本形式的源进行预处理已经是如此简单,这种支持现在是多余的。

预处理器支持两种类型的宏:

2.1. 命名宏

这种类型的宏是高级的,预处理器负责为用户获取参数和替换返回值。典型的形式是:

    #macro name: func [arg1 arg2... /local word1 word2...][...code...]

这样定义一个宏之后,源码中每个遇到 name 单词的地方,都会触发以下步骤:

  1. 获取参数值。

  2. 用参数调用宏函数。

  3. 用函数的最后一个值替换宏调用及其参数。

  4. 从替换了值的地方恢复预处理器(为了允许宏的递归执行)。

注意

目前不支持指定参数类型。

例:

Red []
#macro make-KB: func [n][n * 1024]
print make-KB 64

结果将为:

Red []
print 65536

从一个宏中调用其他宏:

Red []
#macro make-KB: func [n][n * 1024]
#macro make-MB: func [n][make-KB make-KB n]

print make-MB 1

结果将为:

Red []
print 1048576

2.2. 模式匹配宏

这种类型的宏以 Parse 方言的规则或关键字来匹配一个模式,而不是匹配一个单词并获取参数。像命名宏一样,返回值被当作匹配到的模式的替换品。

不过,也有这种类型的宏的一个低级版本,它以 [manual](手动)特性触发。在这种情况下没有隐含的行为,而是给与用户完全的控制。自动替换不会发生,期望的转换的应用和处理的恢复点的设置都取决于宏函数。

模式匹配宏的典型形式为:

 #macro <rule> func [<attribute> start end /local word1 word2...][...code...]

<rule> 部分可以为:

  • 一个 lit-word! 值:用来匹配一个特定的单词。

  • 一个 word! 值:一个 Parse 关键字,比如一个数据类型的名称,或者写 skip 以匹配所有的值。

  • 一个 block! 值:一组 Parse 方言规则。

startend 参数是在源码中界定匹配到的模式的引用。返回值需为一个对恢复位置的引用。

<attribute> 可以为 [manual],它会触发宏的低级手动模式。

例:

Red []

#macro integer! func [s e][s/1 + 1]
print 1 + 2

结果将为:

Red []
print 2 + 3

使用 manual 模式,同样的宏可以被写作:

Red []

#macro integer! func [[manual] s e][s/1: s/1 + 1 next s]
print 1 + 2

使用一个规则区块来创建一个可变参数函数:

Red []
#macro ['max some [integer!]] func [s e][
    first maximum-of copy/part next s e
]
print max 4 2 3 8 1

结果将为:

Red []
print 8

3. 指令

3.1. #if

语法

#if <expr> [<body>]

<expr> : 表达式,它的最后一个值会被用作一个条件。
<body> : 若 <expr> 为 true,将会被引入的代码。

描述

如果条件表达式为真,则引入一整个区块的代码。如果引入了 <body> 区块,它也将被传递给预处理器。

Red []

#if config/OS = 'Windows [print "OS is Windows"]

如果在 Windows 下运行,结果将为以下代码:

Red []

print "OS is Windows"

否则结果将仅为:

Red []

也可以使用 #do 指令定义你自己的单词,它能用在之后的条件表达式当中:

Red []

#do [debug?: yes]

#if debug? [print "running in debug mode"]

结果将为:

Red []

print "running in debug mode"

3.2. #either

语法

#either <expr> [<true>][<false>]

<expr>  : 表达式,它的最后一个值会被用作一个条件。
<true>  : 若 <expr> 为 true,将会被引入的代码。
<false> : 若 <expr> 为 false,将会被引入的代码。

描述

根据一个条件表达式选择要引入的一个代码区块,引入了的区块也会被传递给预处理器。

Red []

print #either config/OS = 'Windows ["Windows"]["Unix"]

如果在 Windows 下运行,结果将为以下代码:

Red []

print "Windows"

否则结果将为:

Red []

print "Unix"

3.3. #switch

语法

#switch <expr> [<value1> [<case1>] <value2> [<case2>] ...]
#switch <expr> [<value1> [<case1>] <value2> [<case2>] ... #default [<default>]]

<valueN>  : 要匹配的值。
<caseN>   : 若最后一个测试过的值匹配,将会被引入的代码。
<default> : 若没有其他值是匹配的,将会被引入的代码。

描述

根据一个值在多个选择中选取要引入的一个代码区块,引入了的区块也会被传递给预处理器。

Red []

print #switch config/OS [
    Windows ["Windows"]
    Linux   ["Unix"]
    MacOSX  ["macOS"]
]

如果在 Windows 下运行,结果将为:

Red []

print "Windows"

3.4. #case

语法

#case [<expr1> [<case1>] <expr2> [<case2>] ...]

<exprN> : 条件表达式。
<caseN> : 若最后一个条件表达式为 true,将会被引入的代码。

描述

根据一个值在多个选择中选取要引入的一个代码区块,引入了的区块也会被传递给预处理器。

Red []

#do [level: 2]

print #case [
    level = 1  ["Easy"]
    level >= 2 ["Medium"]
    level >= 4 ["Hard"]
]

结果将为:

Red []

print "Medium"

3.5. #include

语法

#include <file>

<file> : 要引入的 Red 文件 (file!)。

描述

在编译时被求值时,读取并将参数文件内容引入到当前位置。该文件可以引入对于当前脚本绝对或相对的路径。当被 Red 解释器运行时,该指令只是被替换为一个 do,不会包含进文件。

3.6. #do

语法

#do [<body>]
#do keep [<body>]

<body> : 任意 Red 代码。

描述

在隐藏的语境中对 body 区块进行求值。如果使用了 keep,则将指令和参数替换为 body 执行的结果。

Red []

#do [a: 1]

print ["2 + 3 =" #do keep [2 + 3]]

#if a < 0 [print "negative"]

结果将为:

Red []

print ["2 + 3 =" 5]

3.7. #macro

语法

#macro <name> func <spec> <body>
#macro <pattern> func <spec> <body>

<name>    : 宏函数的名称 (set-word!)。
<pattern> : 用于触发宏的匹配规则 (block! word! lit-word!)。
<spec>    : 宏函数的规格区块。
<body>    : 宏函数的主体区块。

描述

创建一个宏函数。

对于命名宏,规格区块可以声明任意所需数量的参数。主体需要返回一个用于替换宏调用及其参数的值,返回一个空区块将仅删除宏调用及其参数。

对于模式匹配宏,规格区块必须只声明两个参数:匹配到的模式的起始引用和结束引用。按照惯例,参数名称为:func [start end],或较短的格式 func [s e]。默认情况下,主体需要返回一个用于替换匹配到的模式的值,返回一个空区块将仅删除匹配到的模式。

还有一个手动模式可用于模式匹配宏,可以通过在函数 spec 区块中放一个 [manual] 特性来设置:func [[manual] start end]。这种手动模式需要宏返回恢复位置(而不是一个替换值)。如果它需要重新处理一个被替换过的模式,那么要返回的值为 start。如果它需要跳过匹配到的该模式,那么要返回的值为 end。它也能返回其他位置,这取决于该宏要达成的转换,以及你想部分地或全部地重新处理替换值的需求。

一个模式匹配宏会接收:

  • 一个区块:使用 Parse 方言指定要匹配的一个模式。

  • 一个单词:指定一个有效的 Parse 方言单词(比如一个数据类型名称,或写 skip 以匹配所有的值)。

  • 一个原词:指定要匹配的一个特定的字面单词。

Red []
#macro pow2: func [n][to integer! n ** 2]
print pow2 10
print pow2 3 + pow2 4 = pow2 5

结果将为:

Red []
print 100
print 9 + 16 = 25

模式匹配宏例:

Red []
#macro [number! '+ number! '= number!] func [s e][
    do copy/part s e
]

print 9 + 16 = 25

结果将为:

Red []
print true

一个手动模式的模式匹配宏:

Red []
#macro ['sqrt number!] func [[manual] s e][
    if negative? s/2 [
        print [
            "*** SQRT Error: no negative number allowed" lf
            "*** At:" copy/part s e
        ]
        halt
    ]
    e             ;-- 返回匹配到的模式之后的位置
]

print sqrt 9
print sqrt -4

将会输出:

*** SQRT Error: no negative number allowed
*** At: sqrt -4
(halted)

3.8. #local

语法

#local [<body>]

<body> : 包含局部宏定义的任意 Red 代码。

描述

为宏创建局部语境,在该语境中定义的所有宏将在退出时被丢弃。因此,局部宏也需要在局部使用。这个指令可以递归使用(#local<body> 中的一个有效的指令)。

Red []
print 1.0
#local [
    #macro float! func [s e][to integer! s/1]
    print [1.23 2.54 123.789]
]
print 2.0

结果将为:

Red []
print 1.0
print [1 3 124]
print 2.0

3.9. #reset

语法

#reset

描述

重置隐藏的语境,将其从所有之前定义的单词中清空,并删除所有之前定义的宏。

3.10. #process

语法

#process [on | off]

描述

启用或禁用预处理器(默认情况下启用)。这是一个避免处理 Red 文件中的一部分的转义机制,在这种部分里的指令是按照原义使用而不是用于预处理器的(例如,像是用在具有不同含义的方言中)。

实现上的约束:先前禁用它后再次启用预处理器时,#process off 指令需要在代码中有相同(或更高)的嵌套级别。

Red []

print "Conditional directives:"
#process off
foreach d [#if #either #switch #case][probe d]
#process on

结果将为:

Red []

print "Conditional directives:"
foreach d [#if #either #switch #case][probe d]

3.11. #trace

语法

#trace [on | off]

描述

启用或禁用已求值的表达式与宏的在屏幕上的调试输出。该指令在 Red 源中的使用位置没有特别的限制。

4. 运行时 API

Red 预处理器也可以在运行时工作,以便也能够从解释器中使用预处理器指令来对源代码进行求值,以一个 file! 值调用 do 时会自动调用它。请注意,以下形式可用于在不调用预处理程序的情况下 do 一个文件:do load %file

4.1. expand-directives

语法

expand-directives [<body>]
expand-directives/clean [<body>]

<body> : 含有预处理指令的任意 Red 代码。

描述

在一个区块值上调用预处理器,参数区块将被修改并用作返回值。如果使用了 / clean 修饰词,则预处理器状态被重置,之前定义的所有宏都被擦除。

expand-directives [print #either config/OS = 'Windows ["Windows"]["Unix"]]

在 Windows 平台上会返回:

[print "Windows"]

results matching ""

    No results matching ""