AFSim仿真系统---WSF 语法指南

       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.newtypestruct Platform
platformType.newtype.processors.ystruct WSF_SCRIPT_PROCESSOR
platformType.newtype.weapons.zstruct WSF_EXPLICIT_WEAPON
platform.xstruct Platform
platform.x.processors.ystruct WSF_SCRIPT_PROCESSOR

如您所见,符号表包含解析文件所需的确切内容。如果接下来发生对 platform x 块的编辑,我们可以确定 platform.x 存在,并且它具有名为 y 的类型为 WSF_SCRIPT_PROCESSOR 的处理器。

符号表的维护通过 newnew_replaceload 和 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 相同,但将参数附加到当前类型。当前类型最初是一个空元组,但通过使用 newnew_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 和 ObjectMapList 是有序对象列表,用于路线的航路点等。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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小道士写程序

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值