Skip to content

Latest commit

 

History

History
520 lines (395 loc) · 13.2 KB

preprocessor.adoc

File metadata and controls

520 lines (395 loc) · 13.2 KB

预处理器

1. 概念

Red预处理器是Red的一种方言,可以转换Red源码在常规的Red语言特定层之上。 转换是通过Red源码中的内联预处理器关键字(称为`directives`)实现的。这些指令将会处理:

  • 当Red源码被编译的时候

  • 当Red源码通过`do`函数调用文件参数执行的时候

  • 当`expand-directives`函数在block值上被调用的时候

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

指令分类:

  • 条件指令:根据表达式执行的结果包含代码。

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

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

指令被表示为一个特定的`issue!值(以#`开头)。

当指令被处理的时候,它会被替换为它返回的结果值(有些指令没有返回值,所以它们仅仅只是被移除)。这就是源码转换是如何实现的。

注意:Red/System有它自己的 预处理器,它和Red的预处理相似,但是更底层并且应用到文本形式的源码。

1.1. 配置对象

在条件表达式或宏中,为了访问编译源码时提供的设置,有一个隐式的`config`对象被提供。这些设置经常用于在条件表达式中包含代码。详细的设置列表可以在 这里 查看。

例子:

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

注意: 当在运行时(从Red的解释器中)使用预处理器时,`config`对象也有效,并且可以反射在编译Red可执行文件到可运行代码时的选项。

1.2. 执行上下文

为了避免暴露words到全局上下文,以造成不必要的副作用,在指令中的所有表达式都限定在特定的上下文中。上下文延伸到在条件表达式、宏和`#do`指令的每个set-word。

提示:

  • 可以用以下表达式打印输出隐藏上下文:

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

    probe preprocessor/exec

1.3. 实现笔记

当前,那些用在编译时的指令中的表达式都是用Rebol2解释器执行的,由它来运行工具链代码。这是临时的,以后会尽可能地切换为Red执行器。与此同时,为了在运行时(将来还包含编译时)和Red解释器兼容,确保你的表达式和宏代码也可以用Red运行。

2. 宏

Red预处理器支持定义宏函数(简称为*macros*)来实现更复杂的转换。宏允许高效的元编程,即执行花费在编译时而不是运行时。Red在运行时已经有很强的元编程能力,但是,为了使得Red源码在编译器和解释器运行等效,宏也可以在运行时被解析。

注意:没有读取时间宏。考虑到用解析方言对文本形式的源进行预处理是多么简单,这种支持现在是多余的。

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

2.1. 命名宏

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

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

这样定义一个宏之后,源码中每个出现`name`的地方,都会触发以下步骤:

  1. 获取参数值。

  2. 用参数调用宏函数。

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

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

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

3. 例子:

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

=== 模式匹配宏

与匹配单词和获取参数相反,这种类型的宏按照Parse方言的规则或关键字来匹配宏。和命名宏相同,返回值被用作匹配模式的替换值。

虽然,也有这种类型的宏的低级版本,采用`[manual]属性触发`。在这种情况下,没有隐式的行为,而是由用户完全控制。它不会自动替换匹配的值,而是取决于宏函数应用所需的转换并设置处理的恢复点。

模式匹配宏的典型形式为:
#macro <rule> func [<attribute> start end /local word1 word2...][...code...]
`<rule>`部分可以是:

* lit-word!值:用来匹配指定的word。
* word!值:Parse关键字,和数据类型名字相同或者用`skip`匹配*所有*的值。
* block!值:Parse方言规则。

`start`和`end`参数是引用划定匹配模式的源代码。返回值需要是对恢复位置的引用。

`<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

使用block规则创建一个变元函数:

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

== 指令

=== #if

*语法*

#if <expr> [<body>]

<expr> : expression whose last value will be used as a condition. <body> : code to be included if <expr> is true.

*描述*

如果条件表达式为真,则包含一段代码。如果包含`<body>`块,它也将被传递给预处理器。

*例子*

Red []

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

如果在Windows运行的话将会输出以下结果:

Red []

print "OS is Windows"

否则的话,仅仅输出:

Red []

也可以利用`#do`指令定义你自己的word,使得可以用在条件表达式后:

Red []

#do [debug?: yes]

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

将会输出:

Red []

print "running in debug mode"

=== #either

*语法*

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

<expr> : expression whose last value will be used as a condition. <true> : code to be included if <expr> is true. <false> : code to be included if <expr> is false.

*描述*

根据条件表达式选择要包含的代码块。包括块也将传递给预处理器。

*例子*

Red []

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

如果在Windows运行的话将会输出以下结果:

Red []

print "Windows"

否则的话,将会输出:

Red []

print "Unix"

=== #switch

*语法*

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

<valueN> : value to match. <caseN> : code to be included if last tested value matched. <default> : code to be included if no other value matched.

*描述*

选择一个代码块,根据它的值选择是否包含多个代码块中的一个。所包含的块也将传递给预处理器。

*例子*

Red []

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

如果在Windows运行的话将会输出以下结果:

Red []

print "Windows"

=== #case

*语法*

#case [<expr1> [<case1>] <expr2> [<case2>] …​]

<exprN> : conditional expression. <caseN> : code to be included if last conditional expression was true.

*描述*

选择一个代码块,根据它的值选择是否包含多个代码块中的一个。所包含的块也将传递给预处理器。

*例子*

Red []

#do [level: 2]

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

将会输出:

Red []

print "Medium"

=== #include

*语法*

#include <file>

<file> : Red file to be included (file!).

*描述*

在编译时进行评估时,读取并将参数文件内容包含在当前位置。 该文件可以包含与当前脚本绝对或相对的路径。 当红色解释器运行时,该指令只是被替换为`do`,并且不会包含文件。

=== #do

*语法*

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

<body> : any Red code.

*描述*

执行隐藏上下文中的body块。 如果使用`keep`,则将指令和参数替换为“body”的结果。

*例子*

Red []

#do [a: 1]

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

#if a < 0 [print "negative"]

将会输出:

Red []

print ["2 + 3 =" 5]

=== #macro

*语法*

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

<name> : name of the macro function (set-word!). <pattern> : matching rule for triggering the macro (block!, word!, lit-word!). <spec> : specification block for the macro function. <body> : body block of the macro function.

*描述*

创建一个宏函数。

对于命名宏,指定的block可以根据需要声明任意数量的参数。body需要返回一个用于替换宏调用及其参数的值。 返回空块将仅删除宏调用及其参数。

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

*手动*模式也可用于模式匹配宏。 可以通过在函数* spec *块中放置一个`[manual]`属性来设置:`func [[manual] start end]`。这种手动模式需要宏返回恢复位置(而不是替换值)。如果需要*重新处理*一个替换的模式,那么`start`是要返回的值。 如果需要*跳过匹配的模式,那么`end`是要返回的值。还可以返回其他位置,这取决于宏实现的转换,以及部分或全部重新处理替换值的愿望。

模式匹配宏接受:

* block: 使用Parse方言指定要匹配的模式。
* word: 指定一个有效的Parse方言word(如数据类型名称,或“skip”匹配所有值)。
* lit-word: 指定一个特定的文字来匹配。

*例子*

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 ;-- returns position passed the matched pattern ]

print sqrt 9 print sqrt -4

将会输出:
  • SQRT Error: no negative number allowed

  • At: sqrt -4 (halted)

=== #local

*语法*

#local [<body>]

<body> : arbitrary Red code containing local macros definitions.

*描述*

为宏创建本地上下文。 在该上下文中定义的所有宏将在退出时被丢弃。 因此,本地宏也需要在本地应用。 这个指令可以递归使用(`#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

=== #reset

*语法*

#reset

*描述*

重置隐藏的上下文,将其从所有以前定义的单词中清空,并删除所有以前定义的宏。

=== #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]

=== #trace

*语法*

#trace [on | off]

*描述*

启用或禁用屏幕上评估的表达式和宏的调试输出。 在Red源中可以使用该指令的方式没有特别的限制。


== 运行时API anchor:runtime-api[]

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

=== expand-directives

*语法*

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

<body> : arbitrary Red code containing preprocessor directives.

*描述*

对块值调用预处理器。 参数块将被修改并用作返回值。 如果使用`/ clean`细化,则预处理器状态被重置,所以先前定义的所有宏被擦除。

*例子*

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

Windows平台上会输出: