WSF 语法格式,如在 WSF 语法格式中概述的,定义了 WSF 命令的语法。该语法在许多工具和应用程序中用于解析 WSF 文件。许多 WSF 模块的语法文件已经存在,并可以在 WSF 安装的“bin”目录的 “grammar” 子目录中的 .ag 文件中找到。
当添加新类型和命令时,开发者有责任维护语法文件,以便工具和应用程序能够正确处理 WSF 文件。
语法文件定义
语法文件具有两个主要特性:解析 WSF 输入的规则和创建表示输入文件的数据的动作。
规则
规则构成语法的基础。规则共同定义了如何解析输入文件。单个规则可以匹配或不匹配给定的输入。找到的第一个匹配规则将被使用并消耗文本。
序列
最基本的规则是序列。序列由 {
和 }
符号表示。序列定义了一组必须依次匹配的子规则。
{ rule0 rule1 ... }
序列中的每个规则都必须匹配才能使序列匹配。在序列中,规则从 0 开始计数。之后,可以使用 $0
符号引用规则。
文本常量
文本常量匹配单个特定的字符字符串。文本常量可以是带引号的字符串或普通文本。解析器假设用空格分隔的标记。
end_time
"the end time"
规则引用
规则在定义后可以使用 <rule-name>
格式被引用:
<my-rule>
递归
递归是指在 0 到多个或 0 到 1 次间重用的规则引用。*
字符用于表示零到多个的递归,+
表示 1 到多个,?
表示 0 到 1。
{
string_list <string>* end_string_list
| A B C D { E F G }?
}
在使用 *
或 +
时,终止文本常量的存在是关键。在 stuff <string>* end_stuff
中,将读取任意数量的字符串,直到找到 end_stuff
为止。但在 stuff <int>*
的情况下,将消耗每个与整数规则匹配的标记,并仅在遇到非整数标记后停止。尽可能提供终止文本常量。
内置规则
<string>
匹配以空格分隔的字符字符串:
MY_PLATFORM
banana's-apple
<quotable-string>
匹配空格分隔的字符串或带引号的字符串:
MY_PLATFORM
"C:\Program Files\MyPath"
<string-except>
匹配除一组例外外的任何字符串:
# 匹配任何以空格分隔的字符串,除了 invalid_word1 和 invalid_word2
(string-except invalid_word1 invalid_word2)
<integer>
匹配整数:
0
-123
<real>
匹配实数:
-2
-2.0
-2.0e-7
(error {})
定义一个序列,当匹配触发错误时:
# 这会匹配任何字符串。如果是整数,将记录一个错误。
{ (error { <integer> })
| <string>
}
(delimited …)
定义一个非空格分隔的单词。所有规则的匹配假设输入是以空格分隔的。这提供了一种绕过此限制的方法。每个参数在没有空格的情况下进行匹配。使用此规则时存在一些限制,尤其是,每个其他参数必须是文本常量:
# 这将匹配类似 10.5n 和 60s 的纬度值
{
(delimited <real> n)
| (delimited <real> s)
}
(name name-kind)
匹配与 <string>
相同,但将词标识为某物的名称。name-kind
指定名称的类型。此规则的目的是使用户界面能够自动填充建议来填充此字段。示例用法:
icon (name icon)
| category (name category)
| ignore (name category)
(typeref type-prefix)
匹配与 <string>
相同,但指示匹配的词应该是一个现有类型。type-prefix
指示符号表中该类型的键前缀。例如:
exclusion_zone (typeref zone)
| inclusion_zone (typeref zone)
(nocase { … })
使任何序列不区分大小写:
(nocase { true | false })
(file-reference file-type)
与 <quotable-string>
相同,但还将文本标记为指定类型输入文件的位置。
(output-file-reference file-type)
与 <quotable-string>
相同,但还将文本标记为指定类型输出文件的位置。
<TypeCommand>
使用与当前符号关联的规则。请参见符号表。
<ScriptBlock>
、<ScriptVariables>
、<ScriptFunctionBlock>
与递归 (*) 一起使用。匹配任何字符串,但将文本块标记为属于脚本。脚本内容在另一个项目中解析,此语法只是注释了文本块。
# 读取脚本及其返回类型、名称、参数
script <ScriptFunctionBlock>* end_script
# 读取没有返回类型、名称和参数的脚本
on_exit <ScriptBlock>* end_on_exit
# 读取脚本变量块
script_variables <ScriptVariables>* end_script_variables
命名规则
使用以下语法创建新的命名规则:
(rule *rule-name* {
*rules*
})
示例:
# 定义一个新规则
(rule my-rule {
end_time <Time>
})
# 使用新规则
(rule my-rule-2 {
<my-rule>
| not <my-rule>
})
规则名称是用户定义的,根命令规则除外。根命令规则必须被定义,并作为语法的入口点。任何正在处理的顶级输入都应以某种方式匹配根命令规则。
为了支持可扩展性,命名规则可以被重新打开。例如,这两个代码块可以存在于任何语法文件的任何位置,并且都向根命令添加了新命令:
(rule root-command {
apple { core | peel }
})
(rule root-command {
banana { seed | peel }
})
这等同于:
(rule root-command {
apple { core | peel }
| banana { seed | peel }
})
结构体
结构体是一种特殊类型的命名规则,表示 WSF 理解的对象。Platform、Sensor 和 Processor 是结构体的示例。结构体可以包含规则定义,就像 (rule ..)
命令一样,但也可以包含变量。示例:
(struct MY_SENSOR :base_type Sensor
:symbol (type sensorType MY_SENSOR)
(var String my_setting)
{
my_command <String> [my_setting=$1]
| <Sensor>
})
结构体可以稍后使用 <struct-name>
语法引用,就像 (rule ....
一样。
:base_type BaseTypeName
指示该结构体从另一个结构体继承所有变量。
:symbol …
指定该结构体应插入到解析时使用的符号表中。符号被新建和加载规则使用。请参见符号表。
变量在后面描述的代理表示中使用。它们对文件的解析没有影响。
符号表
要解析使用类型的文件,我们需要一个符号表。符号允许文件引用之前定义的类型。符号表是从键到结构体类型的映射。键是字符串的元组。
给定以下示例输入文件:
platform_type newtype WSF_PLATFORM
processor y WSF_SCRIPT_PROCESSOR end_processor
weapon z WSF_EXPLICIT_WEAPON end_weapon
end_platform_type
platform x newtype
delete weapon z
end_platform
生成的符号表如下所示:
符号 | 类型 |
---|---|
platformType.newtype | struct Platform |
platformType.newtype.processors.y | struct WSF_SCRIPT_PROCESSOR |
platformType.newtype.weapons.z | struct WSF_EXPLICIT_WEAPON |
platform.x | struct Platform |
platform.x.processors.y | struct WSF_SCRIPT_PROCESSOR |
如您所见,符号表包含解析文件所需的确切内容。如果接下来发生对 platform x
块的编辑,我们可以确定 platform.x
存在,并且它具有名为 y
的类型为 WSF_SCRIPT_PROCESSOR
的处理器。
符号表的维护通过 new
、new_replace
、load
和 delete
规则完成。引用符号表中的位置(键)是通过类型和子类型命令完成的。
(type …)
指定符号表中的一个位置。允许任意数量的参数。每个参数可以是字符串或序列规则引用。序列规则引用将从给定规则匹配的文本插入作为类型键的一部分。示例:
(type platform x) # -> platform.x
(type platformType newtype processors y) # -> platformType.newtype.processors.y
# 使用序列规则引用作为序列的一部分(未显示) (type platform $1) # -> platform.<text-from-parsed-file>
(subtype …)
与 type
相同,但将参数附加到当前类型。当前类型最初是一个空元组,但通过使用 new
、new_replace
或 load
规则进行更改。
(new storage-address load-address [:backup load-address])
定义一个尝试创建新符号的规则。load-address
处的符号被复制到 storage-address
。地址必须是类型或子类型命令。如果 storage-address
尚未使用,并且 load-address
指向有效符号,则规则成功。storage-address
成为后续命令的新当前符号,直到当前序列结束:
{
sensor <string> <string> (new (subtype sensors $1) (type sensorType $2)
<TypeCommand>*
end_sensor
| other_command <string>
}
在上述示例中,如果 new
规则成功,则当前符号被设置为新的符号表条目。TypeCommand
将调用与新符号关联的结构体,并在匹配到 end_sensor
令牌后,当前符号将恢复到其先前状态。
如果 new
命令失败——要么在 storage-address
处已存在符号,或者在 load-address
处没有符号,则规则不匹配。这会导致整个序列匹配失败。
:backup alternate-load-address
此选项添加了一个备用位置,以便在第一个加载位置无效时加载类型。此外,会记录一个错误。主要用于当用户输入一个未知的类型名称时,备用类型至少提供部分理解用户意图的能力。
(new_replace storage-address load-address [:option…])
与 (new …)
相同,但会替换任何现有符号。
(load load-address)
从符号表加载现有符号并将其设置为当前符号。如果符号不存在,则此规则匹配失败。
(delete address)
删除现有符号。如果在该地址没有符号,则规则匹配失败。
# 在当前符号上创建一个名为 'mover' 的新子符号,从 'moverType.WSF_AIR_MOVER' 加载
(new (subtype mover) (type moverType WSF_AIR_MOVER))
# 尝试加载用户定义的 mover。如果未找到用户定义的类型,则创建一个 WSF_AIR_MOVER。(记录错误)
(new (subtype mover) (type moverType $1) :backup (type moverType WSF_AIR_MOVER))
# 将 'mover' 加载为新的当前符号
(load (subtype mover)
代理
上述所有构造都是正确解析输入文件所必需的。本节中的构造为语法提供了语义,构建了输入文件的含义表示,这就是我们所称之为的代理(Proxy)。
代理的结构与符号表相似。与平台对应的符号表键也将对应于代理中的平台。代理存储关于该对象的信息,而不是存储解析对象的关联规则。对于一个平台,我们可能存储其侧面、图标、位置、部件列表等……其意图并不是构建输入文件中对象的全面描述,而是保存构建特定用户界面所需的关键信息。因此,一些结构体定义几乎没有变量,而其他结构体则有许多变量。
变量
变量是结构体的成员,用于存储有关对象的数据片段。变量由类型和名称组成。变量类型定义了变量包含的数据类型。可用变量类型的完整列表可以在核心语法文件中找到——这些类型是通过 (value …)
命令定义的。此外,还有两种容器类型:List
和 ObjectMap
。List
是有序对象列表,用于路线的航路点等。ObjectMap
是将字符串映射到值类型的关联数组。对于 List
和 ObjectMap
,必须使用以下语法指定包含的类型:
List/Platform
List/Waypoint
ObjectMap/Platform
ObjectMap/Sensor
最后,语法中定义的任何结构体都可以作为变量类型使用。
在结构体内,变量使用以下语法定义:
(var *type* *name* [:default <value>])
其中可以提供一个可选的默认值。
(var Real earthRadiusMultiplier :default 1.0)
(var String myName :default "a name")
动作
动作可以放置在序列规则的条目之前或之后。动作提供了一种在代理中存储数据的机制。动作位于 [
和 ]
字符之间。可以使用 ;
字符指定多个动作:
[my_setting1="ok";my_setting2=$1]
有多种类型的动作:
- 赋值:将新值分配给属性。
attribute=value
side=blue
icon="F-18"
width="24 inches"
height=$1
值可以是字面值,也可以是用户输入值的引用。输入 $0
取自该序列中的第一个规则的用户输入结果。$$
表示前一个规则的结果:
{ side <string> [side=$1]
| set width <Length> [width=$2]
| make height equal to <Length> [height=$$]
}
作为快捷方式,可以在规则引用中使用属性名称以表示自动赋值。这是等价的:
{ side <$side>
| set width <$width>
| make height equal to <$height>
}
- push(attribute-name)
更新当前值到指定属性。代理中的动作作用于当前对象,因此这将影响后续动作或子规则的处理,直到下一个子规则完成:
这将 auxData
属性设置为当前对象,以便稍后通过 AuxData.block
规则中的命令进行修改:
{
[push(auxData)] <AuxData.block>
}
- new(attribute-name, key-name)
向ObjectMap
属性添加新条目,并将当前对象设置为新值。
这将向类别添加新条目:
# 将命令链名称映射到指挥官名称
(var ObjectMap/String commandChains)
{
# 当匹配此规则时,添加一个新命令链条目,使用第一个用户输入
# 然后将值分配给第二个用户输入。
command_chain <string> <string> [new(commandChains,$1);this=$2]
}
- apply($$)
将先前的(new …)
或(load …)
规则应用于代理数据结构。
通常,(new …)
和 (load …)
仅在符号表上操作,对结构体属性没有影响。此命令有效地对代理结构执行与符号表相同的操作。
- skip()
进入没有当前代理对象的模式。这允许执行规则而不应用任何代理更改。例如,在使用<Platform>
规则时,当前代理对象必须是Platform
类型,否则将报告错误。使用:
[skip()] <Platform>
或者使用省略形式(规则名称前缀为冒号):
<:Platform>