QL 语言规范¶
这是 QL 语言的正式规范。它为 QL 的术语、语法和其他技术细节提供了全面的参考。
简介¶
QL 是一种用于 CodeQL 数据库的查询语言。数据是关系型数据:命名关系保存元组集。查询语言是 Datalog 的一种方言,使用分层语义,并且它包含面向对象的类。
符号¶
本节介绍规范中使用的符号。
Unicode 字符¶
本文档中的 Unicode 字符以两种方式描述。一种是在文本中内嵌字符,并将其置于双引号之间。另一种是写一个大写字母 U,后面跟着一个加号,然后是一个四位十六进制数,代表字符的代码点。例如,QL 名称中的第一个字符是“Q”(U+0051)。
语法¶
QL 结构的语法形式使用修改后的巴克斯-诺尔范式(BNF)指定。语法形式(包括标记类)使用裸标识符命名。引用的文本通过其在源代码中的确切字符序列来表示标记。
BNF 推导规则写为一个标识符,它命名语法元素,后面跟着 ::=
,后面跟着语法本身。
在语法本身中,并置表示排序。竖线 (|
, U+007C) 表示备选语法。圆括号表示分组。星号 (*
, U+002A) 表示零次或多次重复,加号 (+
, U+002B) 表示一次或多次重复。语法后面跟着问号 (?
, U+003F) 表示该语法出现零次或一次。
体系结构¶
一个QL 程序由一个在 QL 文件中定义的查询模块和多个在 QLL 文件中定义的库模块组成,这些库模块被它导入(参见“导入指令”)。QL 文件中的模块包含一个或多个查询(参见“查询”)。一个模块还可以包含导入指令(参见“导入指令”)、非成员谓词(参见“非成员谓词”)、类定义(参见“类”)和模块定义(参见“模块”)。
QL 程序在数据库和库路径的上下文中进行解释。数据库提供了一些定义:数据库类型(参见“类型”)、实体(参见“值”)、内置谓词(参见“内置函数”)以及内置谓词和外部谓词的数据库内容(参见“评估”)。库路径是包含 QLL 文件的文件系统目录序列。
一个 QL 程序可以评估(参见“评估”)以产生一组值元组(参见“值”)。
为了使 QL 程序有效,它必须符合本规范中描述的各种条件;否则,该程序被认为是无效的。QL 的实现必须检测所有无效程序,并拒绝评估它们。
库路径¶
库路径是一个有序的目录位置列表。它用于解析模块导入(参见“模块解析”)。严格来说,库路径不是 QL 语言的核心部分,因为 QL 的不同实现以略微不同的方式构建它。大多数 QL 工具还允许您在命令行上为特定调用显式指定库路径,尽管这种情况很少见,而且只有在非常特殊的情况下才有用。本节介绍库路径的默认构造。
首先,确定要编译的 .ql
文件的查询目录。从包含 .ql
文件的目录开始,向上遍历目录结构,检查每个目录中是否有名为 qlpack.yml
或 codeql-pack.yml
的文件。找到此类文件的第一个目录就是查询目录。如果没有这样的目录,则 .ql
文件本身的目录就是查询目录。
一个 qlpack.yml
文件定义了一个 CodeQL 包。qlpack.yml
文件的内容在 CodeQL CLI 文档中描述。codeql-pack.yml
是 qlpack.yml
的别名。
CodeQL CLI 和基于它的工具(例如,GitHub 代码扫描和用于 Visual Studio Code 的 CodeQL 扩展)使用 CodeQL 包构建库路径。对于添加到库路径的每个 CodeQL 包,将在其 dependencies
中命名的 CodeQL 包将随后添加到库路径,并且该过程将持续进行,直到所有包都已解析。实际的库路径由所选 CodeQL 包的根目录组成。此过程依赖于一种通过包名称和版本查找 CodeQL 包的机制,如 CodeQL CLI 文档中所述。
当查询目录中既没有 qlpack.yml
文件,也没有 codeql-pack.yml
文件时,它被认为是一个没有名称并且没有库依赖项的 CodeQL 包。这会导致库路径仅包含查询目录本身。这通常没有用,但足以运行不使用数据库信息的 QL 代码的玩具示例。
名称解析¶
所有模块都有六个环境来决定名称解析。这些都是键到声明的多映射。
这些环境是
- 模块环境,其键是模块名称,其值是模块。
- 类型环境,其键是类型名称,其值是类型。
- 谓词环境,其键是谓词名称和元数对,其值是谓词。
- 模块签名环境,其键是模块签名名称,其值是模块签名。
- 类型签名环境,其键是类型签名名称,其值是类型签名。
- 谓词签名环境,其键是谓词签名名称和元数对,其值是谓词签名。
对于每个模块,一些命名空间被强制为不相交
- 模块命名空间和模块签名命名空间之间不能共享任何键。
- 类型命名空间和类型签名命名空间之间不能共享任何键。
- 模块命名空间和类型签名命名空间之间不能共享任何键。
- 类型命名空间和模块签名命名空间之间不能共享任何键。
- 谓词命名空间和谓词签名命名空间之间不能共享任何键。
- 模块签名命名空间和类型签名命名空间之间不能共享任何键。
如果没有另行说明,则语法段的环境与其包含语法的环境相同。
当在环境中解析键时,如果该键没有值,则程序无效。
环境可以按以下方式组合
- 并集。这将两个环境的条目集取并集。
- 覆盖并集。这将两个环境取并集,但如果第一个映射中存在键的条目,则不会从第二个映射中包含该键的任何其他条目。
确定性环境仅包含对每个键在弱别名意义上相等的值。
全局环境¶
全局模块环境有一个条目 QlBuiltins
。
全局类型环境包含对基本类型 int
、float
、string
、boolean
和 date
的条目。
全局谓词环境包含所有内置的无类谓词。
三个全局签名环境为空。
模块环境¶
对于模块、类型、谓词、模块签名、类型签名和谓词签名中的每一个,我们区分四个环境:公共声明、私有声明、导出和可见。它们定义如下(其中 X 表示我们当前正在考虑的实体类型)
模块的私有声明 X 环境是模块本身中声明的 X 声明和别名的多映射,这些声明和别名被注释为
private
。模块的公共声明 X 环境是模块本身中声明的 X 声明和别名的多映射,这些声明和别名没有被注释为
private
。模块的导出 X 环境是以下内容的并集
- 其公共声明 X 环境,以及
- 对于当前模块直接导入的每个模块(不包括
private
导入 - 参见“导入指令”):来自导出 X 环境的所有条目,这些条目具有不在当前模块的公共声明 X 环境中的键,并且 - 如果 X 是
predicate
,则对于当前模块实现的每个模块签名S
:在S
中,对于每个模块签名默认谓词的条目,该条目没有与当前模块的**公开声明的谓词环境**中的任何条目具有相同的名称和元数。
模块的可见 X 环境是以下内容的并集
- 它的导出 X 环境,以及
- 它的私有声明的 X 环境,以及
- 全局 X 环境,以及
- 对于当前模块私有导入的每个模块:来自导出 X 环境的所有条目,这些条目的键不在当前模块的公开声明的 X 环境中,以及
- 如果存在一个封闭模块:来自封闭模块的可见 X 环境的所有条目,这些条目的键不在当前模块的公开声明的 X 环境中,以及
- 如果不存在封闭模块,并且 X 是
type
或predicate
:来自数据库模式 X 环境的所有条目,这些条目的键不在当前模块的公开声明的 X 环境中,以及 - 当前模块的所有类型为 X 的参数。
如果这些环境中的任何一个不是确定性的,则程序无效。
模块定义可以是递归的,因此模块环境被定义为上述定义给出的运算符的最小不动点。由于所有涉及的操作都是单调的,因此该不动点存在且是唯一的。
模块¶
模块定义¶
QL 模块定义具有以下语法
module ::= annotation* "module" modulename parameters? implements? "{" moduleBody "}"
parameters ::= "<" signatureExpr parameterName ("," signatureExpr parameterName)* ">"
implements ::= "implements" moduleSignatureExpr ("," moduleSignatureExpr)*
moduleBody ::= (import | predicate | class | module | signature | alias | select)*
模块定义使用从模块名称到模块定义的映射扩展了当前模块的声明的模块环境。
QL 文件和 QLL 文件仅包含一个模块体,没有名称和周围的大括号
ql ::= moduleBody
QL 文件和 QLL 文件定义一个与该文件对应的模块,其名称与文件名相同。
模块类型¶
一个模块可以是
- 声明的模块,如果它是通过
module
或ql
语法规则定义的。 - 未声明的模块,如果它不是声明的模块。
声明的模块可以是
- 文件模块,如果它是由 QL 文件或 QLL 文件隐式定义的。
- 查询模块,如果它是由 QL 文件隐式定义的。
- 库模块,如果它不是查询模块。
未声明的模块可以是
查询模块必须包含一个或多个查询。
导入指令¶
导入指令引用模块表达式
import ::= annotations "import" importModuleExpr ("as" modulename)?
importModuleExpr ::= importModuleId arguments?
importModuleId ::= qualId | importModuleExpr "::" modulename
qualId ::= simpleId | qualId "." simpleId
arguments ::= "<" argument ("," argument)* ">"
argument ::= moduleExpr | type | predicateRef "/" int
导入指令可以使用 as
声明来可选地命名导入的模块。如果定义了名称,则导入指令会将从名称到导入模块的声明的映射添加到当前模块的声明的模块环境中。否则,当前模块会直接导入导入的模块。
模块解析¶
模块标识符按以下方式解析为模块。
对于简单标识符
- 首先,标识符被解析为一个段的限定标识符(参见下面)。
- 如果失败,则在当前模块的可见模块环境中解析标识符。
对于选择标识符(a::b
)
- 选择(
a
)的限定符被解析为一个模块,然后名称(b
)在限定符模块的导出模块环境中解析。
对于限定标识符(a.b
)
- 构建一个候选搜索路径列表,该列表包含当前文件的目录,然后是当前文件的查询目录,最后是 库路径 上的每个目录(按顺序)。
- 确定第一个候选搜索路径,该路径具有与导入指令的限定名称匹配的 QLL 文件。候选搜索路径中的 QLL 文件被称为匹配限定名称,如果从候选搜索路径开始,对于限定名称中的每个后续限定符,存在一个子目录,并且由最后一个限定符命名的目录包含一个文件名与限定名称的基本名称匹配的文件,并添加文件扩展名
.qll
。文件和目录名称区分大小写匹配,无论文件系统是否区分大小写。 - 解析的模块是由选定的候选搜索路径定义的模块。
限定模块标识符仅在导入中有效。
模块表达式包含模块标识符和可选参数。如果存在参数,则模块表达式会实例化标识符解析到的模块(参见参数化模块)。
模块表达式不能引用参数化模块。相反,参数化模块在引用时必须始终完全实例化。
参数化模块¶
具有参数的模块称为参数化模块。声明的模块只有在它是库模块并且其声明使用 parameters
语法时才具有参数。
参数化模块可以使用与参数签名匹配的参数来实例化
- 给定类型签名,相应的参数必须是一个类型,该类型传递地扩展指定的
extends
类型,并且是指定的instanceof
类型的传递子类型。 - 给定谓词签名,相应的参数必须是一个具有完全匹配关系类型的谓词。
- 给定模块签名,相应的参数必须是一个导出所有指定的类型和谓词成员的模块。此外,该模块必须声明为通过
implements
语法匹配模块签名。
实例化参数化模块的结果是实例化的模块。参数化模块被称为实例化的模块的基础模块。
实例化相关的和实例化嵌套的实体¶
给定实例化的模块,每个实体都有一个称为实例化相关的实体的相应实体,该实体按如下方式确定
- 如果实体是基础模块,则其实例化相关的实体是实例化的模块。
- 如果实体是基础模块的参数,则其实例化相关的实体是相应参数。
- 如果实体在基础模块或其嵌套模块中声明,则其实例化相关的实体是一个实例化嵌套的实体,该实体是由模块实例化生成的。出于此目的,嵌套在基础模块中的任何模块的参数都被认为是在模块中声明的。
- 否则,实体的实例化相关的实体是初始实体本身。
当实体的实例化相关的实体是实例化嵌套的实体时,则初始实体被称为实例化嵌套的实体的基础嵌套的实体*,实例化的模块被称为实例化嵌套的实体的实例化根,基础模块被称为实例化嵌套的实体的基础根。
实例化嵌套的实体的组件是其基础嵌套的实体的组件的实例化相关的实体。除其他事项外,这适用于
- 实例化嵌套的模块的导出环境中的值,
- 实例化嵌套的谓词和谓词签名的关系类型,
- 实例化嵌套的参数的必需签名,
- 实例化嵌套的参数化模块的参数,
- 实例化嵌套的数据类的字段和成员谓词。
给定实例化的模块,程序中的任何别名都有一个称为实例化相关的别名的相应别名,该别名指向实例化相关的实体。
应用实例化¶
每个实体都有一个基础完全未实例化的实体,该实体按如下方式确定
- 如果实体是实例化的模块,则其基础完全未实例化的实体是基础模块的基础完全未实例化的实体。
- 如果实体是实例化嵌套的实体,则其基础完全未实例化的实体是基础嵌套的实体的基础完全未实例化的实体。
- 否则,其基础完全未实例化的实体是实体本身。
如果实体是其自己的基础完全未实例化的实体,则该实体被称为完全未实例化的实体。
每个完全未实例化的实体都有一个相关参数集,该参数集是该实体传递地嵌套在其中的所有模块的所有参数的集合。对于未嵌套在任何模块中的实体,相关参数集为空。
请注意,相关参数集根据定义仅包含完全未实例化的参数。
对于完全未实例化的参数,相对于实体的自下而上的实例化解析被定义为
- 如果实体是实例化的模块或实例化嵌套的实体,则自下而上的实例化解析是相对于基础模块的自下而上的实例化解析的实例化相关的实体。
- 否则,自下而上的实例化解析是参数本身。
如果实体的基础完全未实例化的实体的相关参数集中的参数的自下而上的实例化解析都不是参数,则该实体被称为完全实例化的。
如果两个实例化的模块或两个实例化嵌套的实体具有相同的基础完全未实例化的实体,并且其相关参数集中的每个参数相对于这两个实例化的模块都具有弱别名等价的自下而上的实例化解析*,则这两个实例化的模块或两个实例化嵌套的实体被认为是等价的。
模块实例化是应用性的,这意味着等价的实例化的模块和等价的实例化嵌套的实体是无法区分的。
类型¶
QL 是一种类型化语言。本节将指定可用的类型、它们的属性以及引用它们的语法。
类型种类¶
QL 中的类型可以是 *原始* 类型、*数据库* 类型、*类* 类型、*字符* 类型、*类域* 类型、类型 *参数* 或 *实例化嵌套* 类型。
原始类型是 boolean
、date
、float
、int
和 string
。
数据库类型作为数据库的一部分提供。每个数据库类型都有一个 *名称*,它是一个以 at 符号 (@
, U+0040) 开头,后跟小写字母的标识符。数据库类型具有一定数量的 *基本类型*,它们是其他数据库类型。在有效的数据库中,基本类型关系是非循环的。
类类型在 QL 中定义,本文档后面将对此进行说明(参见“类”)。每个类类型都有一个名称,它是一个以大写字母开头的标识符。每个类类型都具有一个或多个基本类型,这些基本类型可以是除类域类型之外的任何类型。类类型可以声明为 *抽象* 的。
QL 中的任何类都具有一个关联的类域类型和一个关联的字符类型。
在规范中,C
的类类型写为 C.class
,字符类型写为 C.C
,域类型写为 C.extends
。但是类类型仍然命名为 C
。
类型引用¶
除了类域类型和字符类型(它们不能在 QL 源代码中显式引用)之外,对类型的引用被写为类型的名称。在数据库类型的情况下,名称包括 at 符号 (@
, U+0040)。
type ::= (moduleExpr "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
moduleExpr ::= moduleId arguments?
moduleId ::= modulename | moduleExpr "::" modulename
类型引用解析为类型的方式如下
- 如果是选择标识符(例如,
a::B
),则限定符 (a
) 被解析为模块(参见“模块解析”)。然后,标识符 (B
) 在限定符模块的导出类型环境中解析。 - 否则,标识符在当前模块的可见类型环境中解析。
类型之间的关系¶
类型彼此之间存在子类型关系。如果以下情况之一为真,则类型 A 是类型 B 的 *子类型*
- A 和 B 是相同的类型。
- 存在某个类型 C,其中 A 是 C 的子类型,而 C 是 B 的子类型。
- A 和 B 是数据库类型,并且 B 是 A 的基本类型。
- A 是 C 的字符类型,而 B 是 C 的类域类型。
- A 是一个类类型,而 B 是 A 的字符类型。
- A 是一个类域类型,而 B 是关联类类型的基本类型。
- A 是
int
,而 B 是float
。
超类型是子类型的反面:如果 B 是 A 的子类型,则 A 是 B 的 *超类型*。
如果类型 A 和 B 具有共同的超类型,或者它们各自具有某个超类型是数据库类型,则类型 A 和 B *兼容* 彼此。
类型环境¶
*类型环境* 是一个从变量到类型的有限映射。映射中的每个变量都是一个标识符,或者以下两个特殊符号之一:this
和 result
。
大多数形式的 QL 语法都有一个适用于它们的类型环境。该类型环境由语法出现的上下文决定。
请注意,这与类型环境不同,类型环境是从类型名称到类型的映射。
活动类型¶
在 QL 程序中,*活动* 类型是在活动模块中定义的那些类型。在本规范的其余部分,对程序中类型的任何引用仅指活动类型。
值¶
值是 QL 程序计算的基础数据。本节将指定 QL 中可用的值种类,指定它们的排序顺序,并描述如何将值组合成元组。
值种类¶
QL 中有六种值:五种原始类型的每一种值,以及 *实体*。每个值都有一个类型。
布尔值类型为 boolean
,可以具有两种不同的值之一:true
或 false
。
日期值类型为 date
。它用格里高利历编码时间和日期。具体来说,它包括年份、月份、日期、小时、分钟、秒和毫秒,它们都是整数。年份范围为 -16777216 到 16777215,月份范围为 0 到 11,日期范围为 1 到 31,小时范围为 0 到 23,分钟范围为 0 到 59,秒范围为 0 到 59,毫秒范围为 0 到 999。
浮点值类型为 float
。每个浮点值都是 IEEE 754 中指定的二进制 64 位浮点值。
整数值类型为 int
。每个值都是一个 32 位二进制补码整数。
字符串是 16 位字符的有限序列。字符被解释为 Unicode 代码点。
数据库包括一些不透明的实体值。每个这样的值都具有一个类型,该类型是数据库类型之一,以及一个标识整数。实体值被写为其数据库类型名称,后跟括号中的标识整数。例如,@tree(12)
、@person(16)
和 @location(38132)
是实体值。在本规范中,标识整数对程序员来说是不透明的,因此 QL 的实现可以自由地使用其他可数标签集来标识其实体。
排序¶
值通常没有指定的排序。特别是,实体值与实体或任何其他值没有指定的排序。但是,原始值与相同类型中的其他原始值具有总排序。原始类型及其子类型被称为 *可排序* 的。
对于布尔值,false
在 true
之前排序。
对于日期,排序是按时间顺序的。
对于浮点数,排序是按照 IEEE 754 中规定的排序(如果存在),只是 NaN 被认为等于自身,并且在所有其他浮点数之后排序,而负零被认为严格小于正零。
对于整数,排序是按照二进制补码整数的排序。
对于字符串,排序是按字典顺序的。
元组¶
值可以通过两种不同的方式组合成元组。
*有序元组* 是值的有限有序序列。例如,(1
, 2
, "three"
) 是两个整数和一个字符串的有序序列。
*命名元组* 是从变量到值的有限映射。命名元组中的每个变量都是一个标识符,this
或者 result
。
*变量声明列表* 提供一系列变量,并为每个变量提供一个类型
var_decls ::= (var_decl ("," var_decl)*)?
var_decl ::= type lowerId
有效的变量声明列表不能包含两个具有相同变量名称的声明。此外,如果声明具有适用的类型环境,则它不能使用类型环境中已经存在的变量名称。
对于给定变量声明列表,命名元组的 *扩展* 是一个命名元组,它另外将列表中的每个变量映射到一个值。每个新变量映射的值必须在给定列表中与该变量关联的类型中;有关值属于类型的定义,请参见“存储器”。
存储器¶
QL 程序在 *存储器* 的上下文中执行。本节将指定与存储器相关的几个定义。
*事实* 是一个谓词或类型以及一个命名元组。事实被写为谓词名称或类型名称,后面紧跟着元组。以下是事实的一些示例
successor(fst: 0, snd:1)
Tree.toString(this:@method_tree(12), result:"def println")
Location.class(this:@location(43))
Location.getURL(this: @location(43), result:"file:///etc/hosts:2:0:2:12")
*存储器* 是事实的可变集合。可以通过向其中添加更多事实来修改存储器。
如果存储器中存在一个具有给定元组和谓词或类型的 事实,则命名元组 *直接满足* 具有给定元组的谓词或类型。
在以下任何情况下,值 v
都属于类型 t
v
的类型是t
,并且t
是一个原始类型。- 存在一个元组,其中
this
组件是v
,它直接满足t
。
如果存储器中存在一个具有给定谓词和命名元组 v'
的事实,并且通过取由 v'
的 this
组件形成的有序元组,后面跟着每个参数的组件,得到的有序元组等于给定的有序元组,则有序元组 v
*直接满足* 具有给定元组的谓词。
有序元组在以下情况下 *满足谓词* p
。如果 p
不是成员谓词,则只要命名元组满足元组,元组就满足谓词。
否则,元组必须是存储器中具有谓词 q
的事实的元组,其中 q
与 p
共享一个根定义。元组的 *第一个* 元素必须在 q
中点之前的类型中,并且不能有其他谓词覆盖 q
,使这种情况成立(有关覆盖和根定义的详细信息,请参见“类”)。
一个有序元组 (a0, an)
满足谓词的 +
闭包,如果存在一系列二元元组 (a0, a1)
,(a1, a2)
,…,(an-1, an)
都满足该谓词。一个有序元组 (a, b)
满足谓词的 *
闭包,如果它要么满足 +
闭包,要么 a
和 b
相同,并且它们在谓词的每个参数类型中。
词法语法¶
QL 和 QLL 文件包含一系列以 Unicode 文本编码的令牌。本节描述令牌化算法、可用令牌的类型以及它们在 Unicode 中的表示形式。
某些类型的令牌在其节标题中以括号括起来的标识符表示。如果存在该标识符,它将用作规范中后面语法生成中的一个终结符。此外,“标识符”部分给出了几种类型的标识符,每种标识符都有自己的语法终结符。
令牌化¶
源文件根据以下算法解释为一系列令牌。首先,从文件开头开始应用下面描述的最长匹配规则。其次,从序列中丢弃所有空格令牌和注释。
最长匹配规则按以下方式应用。文件中的第一个令牌是在文件开头以连续字符组成的最长令牌。任何其他令牌之后的下一个令牌是在紧接任何前一个令牌之后以连续字符组成的最长令牌。
如果文件不能完整地令牌化,则该文件无效。
空格¶
空格令牌是一系列空格 (U+0020)、制表符 (U+0009)、回车符 (U+000D) 和换行符 (U+000A)。
注释¶
QL 中有两种类型的注释:单行注释和多行注释。
单行注释是两个斜杠字符 (/
, U+002F) 后面跟着任何字符序列,但不包括换行符 (U+000A) 和回车符 (U+000D)。以下是一个单行注释的示例
// This is a comment
多行注释是一个注释开始,后面跟着一个注释体,再跟着一个注释结束。注释开始是一个斜杠 (/
, U+002F) 后面跟着一个星号 (*
, U+002A),注释结束是一个星号后面跟着一个斜杠。注释体是任何不包含注释结束且不以星号开头的字符序列。以下是一个多行注释的示例
/*
It was the best of code.
It was the worst of code.
It had a multiline comment.
*/
QLDoc (qldoc)¶
QLDoc 注释是一个qldoc 注释开始,后面跟着一个qldoc 注释体,再跟着一个qldoc 注释结束。注释开始是一个斜杠 (/
, U+002F) 后面跟着两个星号 (*
, U+002A),qldoc 注释结束是一个星号后面跟着一个斜杠。qldoc 注释体是任何不包含注释结束的字符序列。以下是一个 QLDoc 注释的示例
/**
It was the best of code.
It was the worst of code.
It had a qldoc comment.
*/
QLDoc 注释的“内容”是该注释的注释体,省略了最初的 /**
、尾部的 */
以及每行内部的空格后面跟着的 *
。
有关如何解释内容的更多信息,请参见下面的“QLDoc”。
关键字¶
以下字符序列是关键字令牌
and
any
as
asc
avg
boolean
by
class
concat
count
date
desc
else
exists
extends
false
float
forall
forex
from
if
implies
import
in
instanceof
int
max
min
module
newtype
none
not
or
order
predicate
rank
result
select
strictconcat
strictcount
strictsum
string
sum
super
then
this
true
unique
where
标识符¶
标识符是一个可选的“@”符号 (U+0040) 后面跟着一个标识符字符序列。标识符字符是小写 ASCII 字母 (a
到 z
, U+0061 到 U+007A),大写 ASCII 字母 (A
到 Z
, U+0041 到 U+005A),十进制数字 (0
到 9
, U+0030 到 U+0039) 和下划线 (_
, U+005F)。除任何“@”符号以外,标识符的第一个字符必须是字母。
标识符不能与关键字具有相同的字符序列,也不能是“@”符号后面跟着一个关键字。
以下是一些标识符示例
width
Window_width
window5000_mark_II
@expr
有几种类型的标识符
lowerId
:以小写字母开头的标识符。upperId
:以大写字母开头的标识符。atLowerId
:以“@”符号开头,然后是小写字母的标识符。
标识符用于以下语法结构
simpleId ::= lowerId | upperId
modulename ::= simpleId
moduleSignatureName ::= upperId
classname ::= upperId
dbasetype ::= atLowerId
predicateRef ::= (moduleExpr "::")? literalId
signatureExpr ::= (moduleExpr "::")? simpleId ("/" Integer | arguments)?;
predicateName ::= lowerId
parameterName ::= simpleId
varname ::= lowerId
literalId ::= lowerId | atLowerId
字符串字面量 (string)¶
字符串字面量表示一个字符序列。它以双引号字符 (U+0022) 开始和结束。双引号之间是一系列字符串字符指示符,每个指示符表示应包含在字符串中的一个字符。字符串字符指示符如下所示。
- 任何除双引号 (U+0022)、反斜杠 (U+005C)、换行符 (U+000A)、回车符 (U+000D) 或制表符 (U+0009) 之外的字符。这样的字符表示它本身。
- 一个反斜杠 (U+005C) 后面跟着以下字符之一
- 另一个反斜杠 (U+005C),在这种情况下表示一个反斜杠字符。
- 一个双引号 (U+0022),在这种情况下表示一个双引号。
- 字母 “n” (U+006E),在这种情况下表示一个换行符 (U+000A)。
- 字母 “r” (U+0072),在这种情况下表示一个回车符 (U+000D)。
- 字母 “t” (U+0074),在这种情况下表示一个制表符 (U+0009)。
以下是一些字符串字面量的示例
"hello"
"He said, \"Logic clearly dictates that the needs of the many...\""
注释¶
各种语法可以应用注释。注释如下
annotations ::= annotation*
annotation ::= simpleAnnotation | argsAnnotation
simpleAnnotation ::= "abstract"
| "cached"
| "external"
| "extensible"
| "final"
| "transient"
| "library"
| "private"
| "deprecated"
| "override"
| "additional"
| "query"
argsAnnotation ::= "pragma" "[" ("inline" | "inline_late" | "noinline" | "nomagic" | "noopt" | "assume_small_delta") "]"
| "language" "[" "monotonicAggregates" "]"
| "bindingset" "[" (variable ( "," variable)*)? "]"
每个简单注释都会在其前面的语法实体中添加一个同名属性。例如,如果一个类前面是 abstract
注释,则称该类是抽象类。
有效的注释列表中可能不包含同一个简单注释多次,也不可能包含同一个参数化注释多次,且具有相同的参数。但是,它可能包含同一个参数化注释多次,但具有不同的参数。
简单注释¶
下表汇总了可以在有效程序中使用每个注释标记的语法结构;例如,在字符前面使用 abstract
注释是无效的。
注释 | 类 | 字符 | 成员谓词 | 非成员谓词 | 导入 | 字段 | 模块 | 别名 | 签名 |
---|---|---|---|---|---|---|---|---|---|
abstract |
是 | 是 | |||||||
cached |
是 | 是 | 是 | 是 | 是 | ||||
external |
是 | ||||||||
extensible |
是 | ||||||||
final |
是 | 是 | 是 | (是) | |||||
transient |
是 | ||||||||
library |
(是) | ||||||||
private |
是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | |
deprecated |
是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | |
override |
是 | 是 | |||||||
additional |
是 | 是 | 是 | 是 | 是 | ||||
query |
是 | 是 |
library
注释只能在 QLL 文件中使用,不能在 QL 文件中使用。 final
注释可用于类型别名,但不能用于模块别名和谓词别名。
别名上的注释适用于别名引入的名称。例如,别名可能与它所引用的名称具有不同的私密性。
参数化注释¶
参数化注释接受一些额外的参数。
参数化注释 pragma
提供编译器编译指示,并且可以根据具体编译指示应用于各种环境。
编译指示 | 类 | 字符 | 成员谓词 | 非成员谓词 | 导入 | 字段 | 模块 | 别名 |
---|---|---|---|---|---|---|---|---|
inline |
是 | 是 | 是 | |||||
inline_late |
是 | 是 | 是 | |||||
noinline |
是 | 是 | 是 | |||||
nomagic |
是 | 是 | 是 | |||||
noopt |
是 | 是 | 是 | |||||
assume_small_delta |
是 | 是 | 是 |
参数化注释 language
提供更改语言行为的语言编译指示。语言编译指示在作用域级别应用,并由嵌套作用域继承。
编译指示 | 类 | 字符 | 成员谓词 | 非成员谓词 | 导入 | 字段 | 模块 | 别名 |
---|---|---|---|---|---|---|---|---|
monotonicAggregates |
是 | 是 | 是 | 是 | 是 |
谓词的绑定集是谓词参数的子集,使得如果这些参数被绑定(限制为有限的值范围),则所有谓词参数都将被绑定。
参数化注释 bindingset
可以应用于谓词(参见“非成员谓词”和“成员”),以指定绑定集。
此注释接受(可能为空的)变量名称列表作为参数。命名变量必须都是谓词的参数,可能包括 this
用于特征谓词和成员谓词,以及 result
用于生成结果的谓词。
在用户没有指定绑定集的默认情况下,假设只有一个空绑定集,即谓词的正文必须绑定所有参数。
QL 编译器通过以下方式检查绑定集
- 它假定绑定集中提到的所有变量都已绑定。
- 它检查在该假设下,所有剩余的参数变量是否都由谓词正文绑定。
谓词可能具有多个不同的绑定集,可以通过在同一个谓词上使用多个 bindingset
注释来声明。
注释 | 类 | 字符 | 成员谓词 | 非成员谓词 | 导入 | 字段 | 模块 | 别名 | 签名 |
---|---|---|---|---|---|---|---|---|---|
bindingset |
是 | 是 | 是 | (是) |
bindingset
编译指示可用于类型签名和谓词签名,但不能用于模块签名。
QLDoc¶
QLDoc 用于记录 QL 实体和绑定。用作声明一部分的 QLDoc 被称为已声明。
模棱两可的 QLDoc¶
如果 QLDoc 可以解析为文件模块的一部分或文件中的第一个声明的一部分,那么它将解析为第一个声明的一部分。
继承 QLDoc¶
如果未提供 QLDoc,则可能继承。
在别名的情况下,它可能从别名的右侧继承。
在成员谓词的情况下,我们收集所有用声明的 QLDoc 覆盖它的成员谓词。如果该集合中有一个成员谓词覆盖该集合中的所有其他成员谓词,则该成员谓词的 QLDoc 被用作 QLDoc。
在字段的情况下,我们收集所有用声明的 QLDoc 覆盖它的字段。如果该集合中有一个字段覆盖该集合中的所有其他字段,则该字段的 QLDoc 被用作 QLDoc。
内容¶
QLDoc 注释的内容被解释为CommonMark,具有以下扩展
- 自动解释链接和电子邮件地址。
- 使用适当的字符来表示省略号、破折号、撇号和引号。
QLDoc 注释的内容可以包含元数据标签,如下所示
标签以任意数量的空格字符开头,后跟一个@
符号。在此处,可能存在任意数量的非空格字符,这些字符构成标签的键。然后是一个空格字符,它将键与值隔开。标签的值由该行剩下的部分以及任何后续行形成,直到出现另一个@
标签或内容结束为止。值中的任何连续空格字符序列都被替换为一个空格。
元数据¶
如果查询文件以空格开头,后跟 QLDoc 注释,则来自该 QLDoc 注释的标签构成查询元数据。
顶层实体¶
模块包含五种顶层实体:谓词、类、模块、别名、签名和 select 子句。
非成员谓词¶
一个谓词被声明为一系列注释、一个头部和一个可选的正文
predicate ::= qldoc? annotations head optbody
谓词定义将从谓词名称和元数到谓词声明的映射添加到当前模块的已声明谓词环境中。
当谓词是模块中的顶层子句时,它被称为非成员谓词。有关“成员谓词”的详细信息,请参见下文。
有效的非成员谓词可以使用cached
、deprecated
、external
、transient
、private
和query
进行注释。请注意,transient
注释只能在非成员谓词也用external
注释的情况下应用。
谓词的头部给出名称、可选的结果类型和一系列参数的变量声明
head ::= ("predicate" | type) predicateName "(" var_decls ")"
谓词的正文有以下三种形式之一
optbody ::= ";"
| "{" formula "}"
| "=" literalId "(" (predicateRef "/" int ("," predicateRef "/" int)*)? ")" "(" (exprs)? ")"
在第一种形式中,只有分号,谓词被称为没有正文。在第二种形式中,谓词的正文是给定的公式(参见“公式”)。在第三种形式中,正文是高阶关系。
有效的非成员谓词必须有一个正文,要么是公式,要么是高阶关系,除非它是外部的,在这种情况下它必须没有正文。
如果存在,公式正文的类型环境将谓词头部中的变量映射到它们关联的类型。如果谓词具有结果类型,则类型环境还将result
映射到结果类型。
类¶
类定义具有以下语法
class ::= qldoc? annotations "class" classname ("extends" type ("," type)*)? ("instanceof" type ("," type)*)? "{" member* "}"
在class
关键字后面的标识符是类的名称。
在extends
关键字后面的类型是类的基类型。
在instanceof
关键字后面的类型是类的instanceof 类型。
类类型被称为最终继承自是最终类型或通过最终别名引用的基类型,并且类类型被称为继承自它的其他基类型。此外,继承是可传递的
- 如果类型
A
继承自类型B
,并且B
继承自类型C
,则A
继承自C
。 - 如果类型
A
最终继承自类型B
,并且B
继承自类型C
,则A
最终继承自C
。 - 如果类型
A
继承自类型B
,并且B
最终继承自类型C
,则A
最终继承自C
。 - 如果类型
A
最终继承自类型B
,并且B
最终继承自类型C
,则A
最终继承自C
。
类将从类名称到类声明的映射添加到当前模块的已声明类型环境中。
有效的类可以使用abstract
、final
、library
和private
进行注释。任何其他注释都会使类无效。
有效的类不能继承自身,也不能继承多个基本类型。有效的类继承的类型集必须与它最终继承的类型集不相交。
有效的类必须至少具有一个基类型或 instanceof 类型。
类依赖项¶
如果存在类依赖项循环,则程序无效。
以下是类依赖项
C
依赖于C.C
C.C
依赖于C.extends
- 如果
C
是抽象的,那么它依赖于所有类D
,其中C
是D
的基类型,并且D
继承自C
。 C.extends
依赖于D.D
,对于C
的每个基类型D
。C.extends
依赖于D
,对于C
的每个 instanceof 类型D
。
类环境¶
对于成员谓词和字段,每个类继承和声明以及导出一个环境。这些定义如下(其中 X 表示我们当前正在考虑的实体类型)
- 类的继承的 X 环境是它继承的类型的导出 X 环境的并集,不包括被另一个元素覆盖的任何元素。
- 类的声明的 X 环境是类本身中 X 声明的多重映射。
- 类的导出 X 环境是它声明的 X 环境(不包括
private
声明条目)与它继承的 X 环境的覆盖并集。 - 可见 X 环境是声明的 X 环境和继承的 X 环境的覆盖并集。
如果这些环境中的任何一个不是确定性的,则程序无效。
对于成员谓词和字段,每个域类型导出一个环境。我们说导出 X 扩展环境是类继承的类型的导出X
环境的并集,不包括被另一个元素覆盖的任何元素。我们说导出 X instanceof 环境是类 instanceof 类型继承的类型的导出X
环境的并集,不包括被另一个元素覆盖的任何元素。域类型的导出 X 环境是导出X
扩展环境和导出X
instanceof 环境的并集。
成员¶
类的每个成员都是一个字符、一个谓词或一个字段
member ::= character | predicate | field
character ::= qldoc? annotations classname "(" ")" "{" formula "}"
field ::= qldoc? annotations var_decl ";"
成员谓词¶
作为类成员的谓词称为成员谓词。谓词的名称是紧接在左括号之前的标识符。
成员谓词将从谓词名称和元数到谓词声明的映射添加到类的已声明成员谓词环境中。
有效的成员谓词可以使用abstract
、cached
、final
、private
、deprecated
和override
进行注释。
如果在成员谓词的名称之前提供类型,则该类型是谓词的结果类型。否则,谓词没有结果类型。var_decls
中变量的类型称为谓词的参数类型。
当成员谓词 p
具有封闭类 C
覆盖 具有封闭类 D
的成员谓词 p'
时,p
被注释为 overrride
,C
继承自 D
,p'
在 C
中可见,p'
不是最终的,并且 p
和 p'
具有相同的名称和相同的元数。覆盖谓词必须具有与任何被覆盖的谓词相同的参数类型序列,否则程序无效。
当成员谓词 p
具有封闭类 C
遮蔽 具有封闭类 D
的成员谓词 p'
时,C
从 D
最终继承,p'
在 C
中可见,并且 p
和 p'
具有相同的名称和相同的元数。此外,当成员谓词 p
具有封闭类 C
遮蔽 具有封闭类 D
的成员谓词 p'
时,C
继承自 D
,p'
在 C
中可见,p'
是最终的,并且 p
和 p'
具有相同的名称和相同的元数。
成员谓词具有一个或多个根定义。如果成员谓词不覆盖任何其他成员谓词,那么它是它自己的根定义。否则,它的根定义是任何被覆盖的成员谓词的根定义。
有效的成员谓词必须具有主体,除非它是抽象的或外部的,在这种情况下它不能具有主体。
有效的成员谓词必须覆盖另一个成员谓词,如果它被注释为覆盖。
当成员谓词 p
覆盖成员谓词 q
时,p
和 q
必须都具有结果类型,或者它们都不能具有结果类型。如果它们确实具有结果类型,则 p
的结果类型必须是 q
的结果类型的子类型。 q
不能是最终谓词。如果 p
是抽象的,那么 q
也必须是抽象的。
类不能继承自具有抽象成员谓词的类,除非它要么包含覆盖该抽象谓词的成员谓词,要么它继承自另一个这样做的类。
有效的类必须包含一个名为 toString
的非私有谓词,它没有参数,结果类型为 string
,或者它必须继承自包含此谓词的类。
有效的类不能继承自两个不同的类,这些类包含具有相同名称和参数数量的谓词,除非其中一个谓词覆盖或遮蔽另一个,或者类定义了一个覆盖或遮蔽这两个谓词的谓词。
有效的类不能最终继承自两个不同的类,这些类包含具有相同名称和参数数量的谓词,除非其中一个谓词覆盖或遮蔽另一个,或者类定义了一个遮蔽这两个谓词的谓词。
成员谓词或字符的类型环境与非成员谓词相同,只是它还将 this
映射到一个类型,并将类上的任何字段映射到一个类型。如果成员是字符,则类型环境将 this
映射到类的类域类型。否则,它将 this
映射到类本身的类类型。类型环境还将任何字段映射到字段的类型。
字段¶
字段声明在类的已声明字段环境中引入从字段名称到字段声明的映射。
当具有封闭类 C
的字段 f
覆盖 具有封闭类 D
的字段 f'
时,f
被注释为 override
,C
继承自 D
,p'
在 C
中可见,p'
不是最终的,并且 p
和 p'
具有相同的名称。
当具有封闭类 C
的字段 f
遮蔽 具有封闭类 D
的字段 f'
时,C
从 D
最终继承,p'
在 C
中可见,并且 p
和 p'
具有相同的名称。此外,当具有封闭类 C
的字段 f
遮蔽 具有封闭类 D
的字段 f'
时,C
继承自 D
,p'
在 C
中可见,p'
是最终的,并且 p
和 p'
具有相同的名称。
有效的类不能继承自两个不同的类,这些类包含具有相同名称的字段,除非其中一个字段覆盖或遮蔽另一个,或者类定义了一个覆盖或遮蔽这两个字段的字段。
有效的类不能最终继承自两个不同的类,这些类包含具有相同名称的字段,除非其中一个字段覆盖或遮蔽另一个,或者类定义了一个遮蔽这两个字段的字段。
有效的字段必须覆盖另一个字段,如果它被注释为 override
。
当字段 f
覆盖字段 g
时,f
的类型必须是 g
的类型的子类型。 f
不能是最终字段。
签名¶
签名定义具有以下语法
signature ::= predicateSignature | typeSignature | moduleSignature
predicateSignature ::= qldoc? annotations "signature" head ";"
typeSignature ::= qldoc? annotations "signature" "class" classname ("extends" type ("," type)*)? (";" | "{" signaturePredicate* "}")
moduleSignature ::= qldoc? annotation* "signature" "module" moduleSignatureName parameters? "{" moduleSignatureBody "}"
moduleSignatureBody ::= (signaturePredicate | defaultPredicate | signatureType)*
signaturePredicate ::= qldoc? annotations head ";"
defaultPredicate ::= qldoc? annotations "default" head "{" formula "}"
signatureType ::= qldoc? annotations "class" classname ("extends" type ("," type)*)? "{" signaturePredicate* "}"
谓词签名定义通过从谓词签名名称和元数到谓词签名定义的映射来扩展当前模块的已声明谓词签名环境。
类型签名定义通过从类型签名名称到类型签名定义的映射来扩展当前模块的已声明类型签名环境。
模块签名定义通过从模块签名名称到模块签名定义的映射来扩展当前模块的已声明模块签名环境。
选择子句¶
QL 文件最多可以包含一个选择子句。该选择子句具有以下语法
select ::= ("from" var_decls)? ("where" formula)? "select" select_exprs ("order" "by" orderbys)?
有效的 QLL 文件不能包含任何选择子句。
选择子句被认为是匿名谓词的声明,其参数对应于选择子句的选择表达式。
如果存在,from
关键字后面跟着公式的变量。否则,选择子句没有变量。
如果存在,where
关键字后面跟着选择子句的公式。否则,选择子句没有公式。
select
关键字后面跟着多个选择表达式。选择表达式具有以下语法
as_exprs ::= as_expr ("," as_expr)*
as_expr ::= expr ("as" lowerId)?
as
关键字为它所属的选择表达式提供一个标签。没有两个选择表达式可以具有相同的标签。没有表达式的标签可以与选择子句的变量之一相同。
如果存在,order
关键字后面跟着多个排序指令。排序指令具有以下语法
orderbys ::= orderby ("," orderby)*
orderby ::= lowerId ("asc" | "desc")?
排序指令中的每个标识符必须标识选择表达式中的一个。它必须是表达式的标签,或者它必须是等效于选择表达式中的一个的变量表达式。指定的选择表达式的类型必须是原始类型的子类型。
不能通过多个排序指令指定选择表达式。有关更多信息,请参阅“排序”。
查询¶
QL 模块中的查询是
- 该模块中定义的选择子句(如果有)。
- 该模块中范围内被注释为
query
的任何谓词。
查询的目标谓词是选择子句或注释的谓词。
查询的目标谓词的每个参数都必须是具有 toString()
成员谓词的类型。
表达式¶
表达式是用于表示值的语法形式。每个表达式都具有一个由表达式出现的上下文决定的类型环境。每个有效的表达式都具有一个类型,如本节所述,除非它是无关表达式。
给定一个命名元组和一个存储,每个表达式都有一个或多个值。本节指定了每种表达式的值。
有几种类型的表达式
exprs ::= expr ("," expr)*
expr ::= dontcare
| unop
| binop
| cast
| primary
primary ::= eparen
| literal
| variable
| super_expr
| postfix_cast
| callwithresults
| aggregation
| expression_pragma
| any
| range
| setliteral
带括号的表达式¶
带括号的表达式是指用圆括号括起来的表达式。
eparen ::= "(" expr ")"
嵌套表达式的类型环境与外部表达式的类型环境相同。外部表达式的类型和值与嵌套表达式的类型和值相同。
字面量¶
字面量表达式如下所示。
literal ::= "false" | "true" | int | float | string
字面量表达式的类型是字面量表示的值的类型:boolean
代表 false
或 true
,int
代表整数字面量,float
代表浮点数字面量,或 string
代表字符串字面量。字面量表达式的值与字面量表示的值相同。
一元运算¶
一元运算是指将 +
或 -
应用于另一个表达式。
unop ::= "+" expr
| "-" expr
运算中的 +
或 -
称为运算符,表达式称为操作数。操作数的类型环境与一元运算的类型环境相同。
对于有效的一元运算,操作数必须是 int
或 float
类型。运算的类型与其操作数的类型相同。
如果运算符是 +
,则表达式的值与操作数的值相同。如果运算符是 -
,则表达式的值是操作数值的算术否定。
二元运算¶
二元运算写成左操作数后跟二元运算符,再后跟右操作数。
binop ::= expr "+" expr
| expr "-" expr
| expr "*" expr
| expr "/" expr
| expr "%" expr
两个操作数的类型环境与运算的类型环境相同。如果运算符是 +
,则两个操作数必须都是 int
或 float
的子类型,或者至少有一个操作数必须是 string
的子类型。如果运算符是其他任何运算符,则每个操作数必须是 int
或 float
的子类型。
如果运算的类型是 string
,则任一操作数都是 string
的子类型。否则,如果两个操作数都是 int
的子类型,则运算的类型是 int
。否则,运算的类型是 float
。
如果结果的类型是 string
,则运算的左值是具有左操作数作为接收者、toString
作为谓词名称且没有参数的“带结果的调用”表达式的值(参见“带结果的调用”)。否则,左值是左操作数的值。同样,右值要么是调用 toString
对右操作数返回的值,要么是右操作数本身的值。
对于左值和右值的每种组合,二元运算都有一个值。该值由以下方式确定。
- 如果左操作数和右操作数的类型是字符串的子类型,则运算的值是左值和右值的串联。
- 否则,如果两个操作数的类型都是
int
的子类型,则运算的值是应用对应于 QL 二元运算符的补码 32 位整数运算的结果。 - 否则,两个操作数的类型必须是
float
的子类型。如果任一操作数的类型是int
,则将其转换为浮点数。然后,运算的值是应用对应于 QL 二元运算符的 IEEE 754 浮点运算符的结果:+
代表加法,-
代表减法,*
代表乘法,/
代表除法,%
代表取余。
变量¶
变量的语法如下所示。
variable ::= varname | "this" | "result"
有效的变量表达式必须出现在类型环境中。变量表达式的类型与类型环境中变量的类型相同。
变量的值是命名字典中变量的值。
超类¶
超类表达式的语法如下所示。
super_expr ::= "super" | type "." "super"
要使超类表达式有效,this
关键字必须在类型环境中具有类型和值。表达式的类型与类型环境中 this
类型域类型相同。
超类表达式的值与命名字典中 this
的值相同。
强制类型转换¶
强制类型转换表达式是圆括号中的类型后跟另一个表达式。
cast ::= "(" type ")" expr
嵌套表达式的类型环境与强制类型转换表达式的类型环境相同。强制类型转换表达式的类型是圆括号中的类型。
强制类型转换表达式的值是嵌套表达式中属于圆括号中给定类型的那些值。
对于基本 float
和 int
类型之间的强制类型转换,上述规则意味着,为了使强制类型转换表达式具有值,它必须既可以表示为 32 位补码整数,也可以表示为 64 位 IEEE 754 浮点数。其他值将不会包含在强制类型转换表达式的值中。
后缀强制类型转换¶
后缀强制类型转换是主表达式后跟一个点,然后是圆括号中的类或基本类型。
postfix_cast ::= primary "." "(" type ")"
所有普通强制类型转换规则都适用于后缀强制类型转换:后缀强制类型转换与带圆括号的普通强制类型转换完全等效。
带结果的调用¶
带结果的调用表达式有两种形式。
callwithresult ::= predicateRef (closure)? "(" (exprs)? ")"
| primary "." predicateName (closure)? "(" (exprs)? ")"
closure ::= "*" | "+"
圆括号中的表达式是调用的参数。点之前的表达式(如果有)是调用的接收者。
参数的类型环境与调用的类型环境相同。
有效的带结果的调用解析为一组谓词。调用解析的方式如下所示。
- 如果调用没有接收者,并且谓词引用是简单标识符,则通过在封闭类的可见谓词环境中查找谓词引用和元数来解析调用。
- 如果调用没有接收者,并且谓词引用是简单标识符,则通过在封闭模块的可见谓词环境中查找谓词引用和元数来解析调用。
- 如果调用没有接收者,并且谓词引用是选择标识符,则限定符被解析为模块(参见“模块解析”),并且通过在限定符模块的导出谓词环境中查找标识符来解析调用。
- 如果调用具有接收者,并且接收者的类型与封闭类相同,则通过在封闭类的可见谓词环境中查找谓词名称和元数来解析调用。
- 如果调用具有接收者,并且接收者的类型与封闭类不同,则通过在接收者类型的导出谓词环境中查找谓词名称和元数来解析调用。
如果调用解析的所有谓词都在基本类型上声明,那么我们限制为谓词集合,其中调用的每个参数都是对应谓词参数类型的子类型。然后,我们找到该新集合中的所有谓词 p
,使得不存在另一个谓词 p'
,其中 p'
的每个参数都是 p
中对应参数的子类型。然后,我们说调用解析为该集合。
有效调用只能解析为单个谓词。
对于除无关表达式以外的每个参数,参数的类型必须与谓词对应参数类型的类型兼容,否则调用无效。
有效的带结果的调用必须解析为具有结果类型的谓词。该结果类型也是调用的类型。
如果解析的谓词是内置的,则调用不能包含闭包。如果调用确实具有闭包,则它必须解析为谓词,其中谓词的关系元数为 2。谓词的关系元数是以下数字的总和。
- 谓词的参数数量。
- 如果谓词是成员谓词,则为 1,否则为 0。
- 如果谓词具有结果,则为 1,否则为 0。
如果调用包含闭包,则所有声明的谓词参数、声明的封闭类型(如果存在)以及声明的结果类型(如果存在)必须兼容。如果其中一个类型是 int
的子类型,则所有其他参数必须是 int
的子类型。
- 对成员谓词的调用可能是直接调用。
- 如果接收者不是超类表达式,则它不是直接调用。
- 如果接收者是
A.super
,并且A
是实例类型,而不是从其继承的基本类型,则它不是直接调用。 - 如果接收者是
A.super
,并且A
是从其最终继承的基本类型,则它不是直接调用。 - 如果接收者是
A.super
,并且A
是从其继承的基本类型,而不是实例类型,则它是直接调用。 - 如果接收者是
A.super
,并且A
是从其继承的基本类型,并且是实例类型,则调用无效。 - 如果接收方是
super
,并且成员谓词在 instanceof 类型的导出成员谓词环境中,而不是在基类型的导出成员谓词环境中,则它不是直接的。 - 如果接收方是
super
,并且成员谓词在基类型的导出成员谓词环境中,而不是在 instanceof 类型的导出成员谓词环境中,则它是直接的。 - 如果接收方是
super
,并且成员谓词在基类型的导出成员谓词环境中以及在 instanceof 类型的导出成员谓词环境中,则调用无效。
如果调用解析为成员谓词,则接收方值如下所示。如果调用具有接收方,则接收方值是该接收方的值。如果调用没有接收方,则单个接收方值是上下文命名元组中 this
的值。
具有结果的调用的元组前缀包括来自每个参数表达式的值的单个值,顺序与参数的顺序相同。如果调用解析为非成员谓词,则这些值正好是调用的元组前缀。如果调用解析为成员谓词,则元组前缀还包括接收方值,按顺序排在参数值之前。
具有结果的调用的匹配元组是所有有序元组,它们是元组前缀之一,后跟与调用类型相同的任何值。如果调用没有闭包,则所有匹配元组还必须满足调用的已解析谓词,除非调用是直接的,在这种情况下,它们必须直接满足调用的已解析谓词。如果调用具有 *
或 +
闭包,则匹配元组必须满足或直接满足已解析谓词的关联闭包。
具有结果的调用的值是每个调用的匹配元组的最后一个元素。
聚合¶
聚合可以用以下两种形式之一编写
aggregation ::= aggid ("[" expr "]")? "(" var_decls ("|" (formula)? ("|" as_exprs ("order" "by" aggorderbys)?)?)? ")"
| aggid ("[" expr "]")? "(" as_exprs ("order" "by" aggorderbys)? ")"
| "unique" "(" var_decls "|" (formula)? ("|" as_exprs)? ")"
aggid ::= "avg" | "concat" | "count" | "max" | "min" | "rank" | "strictconcat" | "strictcount" | "strictsum" | "sum"
aggorderbys ::= aggorderby ("," aggorderby)*
aggorderby ::= expr ("asc" | "desc")?
如果存在,方括号 ([
和 ]
,U+005B 和 U+005D) 中包含的表达式称为排名表达式。它必须具有类型 int
。
如果存在,as_exprs
称为聚合表达式。如果聚合表达式是 expr as v
形式,则表达式被称为命名 v。
如果聚合 ID 是 rank
,则排名表达式必须存在;否则,它不能存在。
除了排名变量的存在与否之外,聚合的所有其他简化形式都等同于使用以下步骤的完整形式
- 如果省略公式,则它被认为是
any()
。 - 如果没有聚合表达式,则以下两种情况之一成立
- 聚合 ID 是
count
或strictcount
,表达式被认为是1
。 - 必须有且只有一个变量声明,并且聚合表达式被认为是对该变量的引用。
- 聚合 ID 是
- 如果聚合 ID 是
concat
或strictconcat
并且它只有一个表达式,则第二个表达式被认为是""
。 - 如果未启用
monotonicAggregates
语言pragma,或者原始公式和变量声明都被省略,则聚合将按如下方式转换- 对于每个聚合表达式
expr_i
,声明一个新的变量v_i
,其类型与表达式相同,此外还有原始变量声明。 - 新范围是原始范围与每个聚合表达式
expr_i
的项v_i = expr_i
的连接。 - 每个原始聚合表达式
expr_i
被替换为新的聚合表达式v_i
。
- 对于每个聚合表达式
变量声明列表中的变量不能出现在类型环境中。
排名表达式的类型环境与聚合的类型环境相同。
公式的类型环境是通过获取聚合的类型环境并添加给定 var_decls
列表中的所有变量类型来获得的。
聚合表达式的类型环境是通过获取公式的类型环境,然后对于每个在当前表达式之前出现的命名聚合表达式,添加一个从较早表达式的名称到较早表达式的类型的映射来获得的。
排序指令的类型环境是通过获取公式的类型环境,然后对于聚合中的每个命名聚合表达式,添加一个从表达式的名称到表达式的类型的映射来获得的。
聚合表达式的数量和类型受到以下限制
max
、min
、rank
或unique
聚合必须只有一个表达式。- 在没有排序指令表达式的情况下,
max
、min
或rank
聚合中表达式的类型必须是可排序类型。 count
或strictcount
聚合不能有表达式。sum
、strictsum
或avg
聚合必须有一个聚合表达式,该表达式必须具有类型,该类型是float
的子类型。concat
或strictconcat
聚合必须有两个表达式。两个表达式都必须具有类型,该类型是string
的子类型。
count
、strictcount
聚合的类型是 int
。 avg
聚合的类型是 float
。 concat
或 strictconcat
聚合的类型是 string
。 sum
或 strictsum
聚合的类型是 int
,如果聚合表达式是 int
的子类型,否则是 float
。 rank
、min
或 max
聚合的类型是单个表达式的类型。
排序指令只能为 max
、min
、rank
、concat
或 strictconcat
聚合指定。排序指令中表达式的类型必须是可排序类型。
聚合表达式的值按如下方式确定。首先,范围元组是聚合正在评估的命名元组的扩展,包含聚合的变量声明,并且匹配公式(参见“公式”)。
对于每个范围元组,聚合元组是对范围元组到聚合变量和排序变量的扩展。
聚合变量由聚合表达式给出。如果聚合表达式被命名,则其聚合变量由其名称给出,否则将创建一个新的合成变量。该值是通过使用前一个表达式的结果作为命名元组,或者如果这是第一个聚合表达式,则使用范围元组来评估表达式而得到的。
排序变量是为排序指令中的每个表达式创建的合成变量,其值由排序指令中表达式的值给出。
如果聚合 ID 是 max
、min
或 rank
并且没有排序指令,则对于每个聚合元组,将添加一个合成排序变量,其值由聚合变量给出。
聚合表达式的值是通过将聚合函数应用于通过为每个范围元组选择一个聚合元组而获得的每组元组来得到的。
- 如果聚合 ID 是
avg
,并且集合非空,则结果值是集合中每个元组中聚合变量的值的平均值,按集合中元组的数量加权,在将值转换为浮点数后。 - 如果聚合 ID 是
count
,则结果值是集合中元组的数量。如果集合中没有元组,则该值为整数0
。 - 如果聚合 ID 是
max
,则值是与排序值的最大元组相关联的聚合变量的那些值。如果集合为空,则聚合没有值。 - 如果聚合 ID 是
min
,则值是与排序值的最小元组相关联的聚合变量的那些值。如果集合为空,则聚合没有值。 - 如果聚合 ID 是
rank
,则结果值是聚合变量的值,使得具有严格更小的排序变量元组的聚合元组的数量恰好比由聚合的排名表达式绑定的整数少 1。如果不存在这样的值,则聚合没有值。 - 如果聚合 ID 是
strictcount
,则结果值与聚合 ID 为count
相同,除非元组集合为空。如果元组集合为空,则聚合没有值。 - 如果聚合 ID 为
strictsum
,则结果值与聚合 ID 为sum
时相同,除非元组集为空。如果元组集为空,则聚合没有值。 - 如果聚合 ID 为
sum
,则结果值与聚合变量在元组集中的值的总和相同,并根据每个值在元组集中的出现次数加权。如果元组集中没有元组,则聚合的结果值为整数0
。 - 如果聚合 ID 为
concat
,则对于第二个聚合变量的每个值都存在一个值,该值由每个元组的第一个聚合变量的值与用作分隔符的第二个聚合变量的值连接而成,并按排序变量排序。如果存在多个具有相同排序变量的聚合元组,则使用第一个区分值来打破平局。如果元组集中没有元组,则聚合的单个值为一个空字符串。 - 如果聚合 ID 为
strictconcat
,则结果与concat
相同,除了在没有聚合元组的情况下,聚合没有值。
- 如果聚合 ID 为
unique
,则结果是聚合变量的值,如果只有一个这样的值。否则,聚合没有值。
任何¶
any
表达式是一种特殊的量化表达式。
any ::= "any" "(" var_decls ("|" (formula)? ("|" expr)?)? ")"
any
表达式的值为表达式匹配的公式的值。
any
表达式的缩写情况与聚合的解释方式相同。
表达式元数据¶
表达式元数据可以用于指导优化。
- ::
expression_pragma ::= “pragma” “[” expression_pragma_type “]” “(” expr “)”
expression_pragma_type ::= “only_bind_out” | “only_bind_into”
表达式元数据的取值是其包含表达式的取值。
类型 only_bind_out 提示表达式元数据的计算结果的用途不应用于指导包含表达式的结果的计算。在检查所有值是否都被绑定时,编译器不假设如果表达式元数据的计算结果被绑定,那么包含表达式的计算结果也被绑定。
类型 only_bind_into 提示包含表达式的用途不应用于指导表达式元数据的计算结果的计算。在检查所有值是否都被绑定时,编译器不假设如果包含表达式的计算结果被绑定,那么表达式元数据的计算结果也被绑定。
范围¶
范围表达式表示一组值。
range ::= "[" expr ".." expr "]"
两个表达式都必须是 int
、float
或 date
的子类型。如果它们中的任何一个是 date
类型,则它们都必须是 date
类型。
如果两个表达式都是 int
的子类型,则范围的类型是 int
。如果两个表达式都是 date
的子类型,则范围的类型是 date
。否则,范围的类型是 float
。
范围表达式的取值是那些按顺序包含在第一个表达式取值和第二个表达式取值之间的取值。
集合字面量¶
集合字面量表示从一组值中进行选择。
setliteral ::= "[" expr ("," expr)* ","? "]"
集合字面量可以是任何类型,但在集合字面量中的类型必须根据以下标准一致:至少一个集合元素必须是所有集合元素类型的超类型。此超类型是集合字面量的类型。例如,float
是 float
和 int
的超类型,因此 x = [4, 5.6]
是有效的。另一方面,y = [5, "test"]
不符合该标准。
集合字面量表达式的取值是所有包含的元素表达式的所有取值。
自 CodeQL CLI 的 2.7.1 版本发布以来,在集合字面量中允许使用尾随逗号。
表达式的消歧¶
本节中给出的语法首先通过优先级进行消歧,其次通过从左到右关联进行消歧。优先级顺序从最高到最低是
- 强制转换
- 一元
+
和-
- 二元
*
、/
和%
- 二元
+
和-
每当可以将一系列标记解释为对具有结果(具有指定闭包)的谓词的调用,或者解释为具有运算符 +
或 *
的二元运算,该语法都会被解释为对具有结果的谓词的调用。
每当可以将一系列标记解释为带括号的变量的算术运算或解释为一元运算的强制转换,该语法都会被解释为强制转换。
公式¶
公式是用于在给定存储的情况下匹配命名元组的语法形式。
公式有多种类型
formula ::= fparen
| disjunction
| conjunction
| implies
| ifthen
| negated
| quantified
| comparison
| instanceof
| inrange
| call
本节指定每种公式的语法以及它们匹配的元组。
蕴涵¶
蕴涵公式是两个公式,由 implies
关键字分隔
implies ::= formula "implies" formula
这两个公式都不能是另一个蕴涵。
蕴涵公式在第二个公式匹配或第一个公式不匹配时匹配。
条件公式¶
条件公式具有以下语法
ifthen ::= "if" formula "then" formula "else" formula
第一个公式称为条件公式的条件。第二个公式称为真分支,第三个公式称为假分支。
条件公式在条件和真分支都匹配时匹配。它还在假分支匹配且条件不匹配时匹配。
量化公式¶
量化公式有几种语法
quantified ::= "exists" "(" expr ")"
| "exists" "(" var_decls ("|" formula)? ("|" formula)? ")"
| "forall" "(" var_decls ("|" formula)? "|" formula ")"
| "forex" "(" var_decls ("|" formula)? "|" formula ")"
在所有情况下,嵌套表达式或公式的类型环境与量化公式的类型环境相同,只是它还将变量声明中的变量映射到其关联类型。
第一种形式在给定表达式至少有一个值时匹配。
对于其他形式,给定变量声明的当前命名元组的扩展称为量词扩展。嵌套公式称为第一个量化公式,如果存在,则称为第二个量化公式。
第二个 exists
公式在量词扩展之一是量化公式或公式都匹配的情况下匹配。
带有单个量化公式的 forall
公式在该量化公式匹配所有量词扩展的情况下匹配。带有两个量化公式的 forall
在第二个公式匹配第一个公式匹配的所有扩展的情况下匹配。
带有单个量化公式的 forex
公式在与匹配 forall
公式相同的条件下匹配,只是必须至少存在一个量词扩展,其中该第一个量化公式匹配。
比较¶
比较公式是两个表达式,由比较运算符分隔
comparison ::= expr compop expr
compop ::= "=" | "!=" | "<" | ">" | "<=" | ">="
比较公式在左表达式的一个值与右表达式的一个值处于给定顺序时匹配。使用的顺序在“顺序”中指定。如果一个值是整数,另一个值是浮点数,则在比较之前将整数转换为浮点数。
如果运算符是 =
,则左表达式和右表达式中至少有一个必须具有类型;如果它们都具有类型,则这些类型必须兼容。
如果运算符是 !=
,则两个表达式都必须具有类型,并且这些类型必须兼容。
如果运算符是任何其他运算符,则两个表达式都必须具有类型。这些类型必须彼此兼容。这些类型中的每一个都必须是可排序的。
类型检查¶
类型检查公式具有以下语法
instanceof ::= expr "instanceof" type
位于 instanceof
右侧的类型称为类型检查类型。
表达式的类型必须与类型检查类型兼容。
如果表达式的其中一个值位于类型检查类型中,则公式匹配。
范围检查¶
范围检查具有以下语法
inrange ::= expr "in" (range | setliteral)
公式等效于 expr "=" range
或 expr "=" setliteral
。
调用¶
调用具有以下语法
call ::= predicateRef (closure)? "(" (exprs)? ")"
| primary "." predicateName (closure)? "(" (exprs)? ")"
标识符称为调用的谓词名。
调用必须解析为一个谓词,使用与带结果的调用相同的解析定义(参见“带结果的调用”)。
调用可以是直接的,使用与带结果的调用相同的直接定义(参见“带结果的调用”)。
解析后的谓词不能具有结果类型。
如果解析后的谓词是基本类型的内置成员谓词,则调用可能不包含闭包。如果调用确实有闭包,则调用必须解析为关系元数为 2 的谓词。
调用的候选元组是通过从调用的每个参数中选择一个值形成的有序元组。
如果调用没有闭包,则只要候选元组之一满足调用的解析谓词,它就匹配,除非调用是直接的,在这种情况下,候选元组必须直接满足解析谓词。如果调用具有*
或+
闭包,则只要候选元组之一满足或直接满足解析谓词的关联闭包,调用就匹配。
别名¶
别名定义了现有 QL 绑定 的新名称。
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
| qldoc? annotations "class" classname "=" type ";"
| qldoc? annotations "module" modulename "=" moduleExpr ";"
别名在当前模块的可见谓词、类型或模块环境中分别引入了从新名称到右边的绑定所引用的绑定的绑定。
当且仅当别名具有final
注释时,它被称为强别名。否则,它被称为弱别名。
当且仅当满足以下条件之一时,两个绑定A,B被称为弱别名相等
- A和B是相同的绑定,或者
- A`由C的弱别名引入,其中B和C是弱别名相等的(反之亦然),或者
- A和B由相同的强别名引入,并且它们是弱别名相等的绑定的别名。
请注意,第三个条件只与参数化模块相关,其中别名引入的绑定可能取决于实例化参数。
内置函数¶
QL 数据库包含许多内置谓词。本节定义所有数据库都包含的许多内置谓词。每个数据库还包含许多未在此文档中指定的其他非成员谓词。
本节给出了几个内置谓词表。对于每个谓词,该表给出每个具有结果的谓词的结果类型,以及参数类型的序列。
每个表还指定了每个谓词的数据库内容中包含哪些有序元组。它使用一个描述来指定这一点,该描述对于包含的元组来说是完全正确的。在每个描述中,“结果”是每个元组的最后一个元素,如果谓词具有结果类型。 “接收器”是每个元组的第一个元素。“参数”是每个元组的所有元素,除了结果和接收器。
非成员内置函数¶
以下内置谓词是非成员谓词
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
any |
空元组。 | ||
none |
无元组。 | ||
toUrl |
string, int, int, int, int, string | 令参数为file 、startLine 、startCol 、endLine 、endCol 和url 。如果url 等于字符串file://file:startLine:startCol:endLine:endCol ,则谓词成立。 |
布尔类型的内置函数¶
以下内置谓词是类型boolean
的成员
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
booleanAnd |
boolean | boolean | 结果是接收器和参数的布尔与。 |
booleanNot |
boolean | 结果是接收器的布尔非。 | |
booleanOr |
boolean | boolean | 结果是接收器和参数的布尔或。 |
booleanXor |
boolean | boolean | 结果是接收器和参数的布尔异或。 |
toString |
string | 如果接收器为true ,则结果为“true”,否则为“false”。 |
日期类型的内置函数¶
以下内置谓词是类型date
的成员
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
daysTo |
int | date | 结果是在接收器和参数之间但不包括接收器和参数的日期数。 |
getDay |
int | 结果是接收器的日期部分。 | |
getHours |
int | 结果是接收器的小时部分。 | |
getMinutes |
int | 结果是接收器的分钟部分。 | |
getMonth |
string | 结果是根据接收器的月份部分确定的字符串。该字符串是以下字符串之一:January 、February 、March 、April 、May 、June 、July 、August 、September 、October 、November 或December 。 |
|
getSeconds |
int | 结果是接收器的秒部分。 | |
getYear |
int | 结果是接收器的年份部分。 | |
toISO |
string | 结果是日期的字符串表示。表示方式未指定。 | |
toString |
string | 结果是日期的字符串表示。表示方式未指定。 |
浮点数类型的内置函数¶
以下内置谓词是类型float
的成员
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
abs |
float | 结果是接收器的绝对值。 | |
acos |
float | 结果是接收器的反余弦。 | |
asin |
float | 结果是接收器的反正弦。 | |
atan |
float | 结果是接收器的反正切。 | |
ceil |
int | 结果是最小的大于或等于接收器的整数。 | |
copySign |
float | float | 结果是具有接收器大小和参数符号的浮点数。 |
cos |
float | 结果是接收器的余弦。 | |
cosh |
float | 结果是接收器的双曲余弦。 | |
exp |
float | 结果是 e 的值,即自然对数的底,以接收器为幂。 | |
floor |
int | 结果是不大于接收器的最大整数。 | |
log |
float | 结果是接收器的自然对数。 | |
log |
float | float | 结果是接收器以参数为底的对数。 |
log |
float | int | 结果是接收器以参数为底的对数。 |
log10 |
float | 结果是接收器以 10 为底的对数。 | |
log2 |
float | 结果是接收器以 2 为底的对数。 | |
maximum |
float | float | 结果是接收器和参数中较大的值。 |
maximum |
float | int | 结果是接收器和参数中较大的值。 |
minimum |
float | float | 结果是接收器和参数中较小的值。 |
minimum |
float | int | 结果是接收器和参数中较小的值。 |
nextAfter |
float | float | 结果是沿着参数方向相邻于接收器的数字。 |
nextDown |
float | 结果是沿着负无穷方向相邻于接收器的数字。 | |
nextUp |
float | 结果是沿着正无穷方向相邻于接收器的数字。 | |
pow |
float | float | 结果是接收器以参数为幂的值。 |
pow |
float | int | 结果是接收器以参数为幂的值。 |
signum |
float | 结果是接收器的符号:如果为零,则为零;如果大于零,则为 1.0;如果小于零,则为 -1.0。 | |
sin |
float | 结果是接收器的正弦。 | |
sinh |
float | 结果是接收器的双曲正弦。 | |
sqrt |
float | 结果是接收器的平方根。 | |
tan |
float | 结果是接收器的正切。 | |
tanh |
float | 结果是接收器的双曲正切。 | |
toString |
string | 数字的小数形式的字符串表示。 | |
ulp |
float | 结果是接收器的 ULP(最后一位单位)。 |
整数类型的内置函数¶
以下内置谓词是类型int
的成员
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
abs |
int | 结果是接收器的绝对值。 | |
acos |
float | 结果是接收器的反余弦。 | |
asin |
float | 结果是接收器的反正弦。 | |
atan |
float | 结果是接收器的反正切。 | |
cos |
float | 结果是接收器的余弦。 | |
cosh |
float | 结果是接收器的双曲余弦。 | |
exp |
float | 结果是 e 的值,即自然对数的底,以接收器为幂的值。 | |
gcd |
int | int | 结果是接收器和参数的最大公约数。 |
log |
float | 结果是接收器的自然对数。 | |
log |
float | float | 结果是接收器以参数为底的对数。 |
log |
float | int | 结果是接收器以参数为底的对数。 |
log10 |
float | 结果是接收器以 10 为底的对数。 | |
log2 |
float | 结果是接收器以 2 为底的对数。 | |
maximum |
float | float | 结果是接收器和参数中较大的值。 |
maximum |
int | int | 结果是接收器和参数中较大的值。 |
minimum |
float | float | 结果是接收器和参数中较小的值。 |
minimum |
int | int | 结果是接收器和参数中较小的值。 |
pow |
float | float | 结果是接收器以参数为幂的值。 |
pow |
float | int | 结果是接收器以参数为幂的值。 |
sin |
float | 结果是接收器的正弦。 | |
sinh |
float | 结果是接收器的双曲正弦。 | |
sqrt |
float | 结果是接收器的平方根。 | |
tan |
float | 结果是接收器的正切。 | |
tanh |
float | 结果是接收器的双曲正切。 | |
bitAnd |
int | int | 结果是接收器和参数的按位与。 |
bitOr |
int | int | 结果是接收器和参数的按位或。 |
bitXor |
int | int | 结果是接收器和参数的按位异或。 |
bitNot |
int | 结果是接收器的按位取反。 | |
bitShiftLeft |
int | int | 结果是接收器以参数为模 32 的按位左移。 |
bitShiftRight |
int | int | 结果是接收器以参数为模 32 的按位右移。 |
bitShiftRightSigned |
int | int | 结果是接收器以参数为模 32 的带符号的按位右移。 |
toString |
string | 结果是数字的小数形式的字符串表示。 | |
toUnicode |
string | 结果是接收器作为 Unicode 码点的 Unicode 字符。 |
bitShiftRightSigned
后的最左侧位取决于符号扩展,而bitShiftRight
后的最左侧位为零。
字符串类型的内置函数¶
以下内置谓词是类型string
的成员
名称 | 结果类型 | 参数类型 | 内容 |
---|---|---|---|
charAt |
string | int | 结果是一个包含接收器中由参数给出的索引处的字符的 1 个字符的字符串。字符串的第一个元素位于索引 0 处。 |
indexOf |
int | string | 结果是参数在接收器中出现的索引。 |
indexOf |
int | string, int, int | 假设参数为 s 、n 和 start 。结果是在接收器中从 start 开始的,substring s 的第 n 次出现的索引。 |
isLowercase |
接收器不包含大写字母。 | ||
isUppercase |
接收器不包含小写字母。 | ||
length |
int | 结果是接收器中字符的数量。 | |
matches |
string | 参数是一个与接收器匹配的模式,与 SQL 中的 LIKE 运算符相同。模式可能包含 _ 来匹配单个字符,以及 % 来匹配任何字符序列。反斜杠可用于转义下划线、百分号或反斜杠。否则,模式中的所有字符(除 _ 和 % 以及 \\ 之外)都必须完全匹配。 |
|
prefix |
string | int | 结果是接收器的前缀,其长度正好等于参数。如果参数为负数或大于接收器的长度,则没有结果。 |
regexpCapture |
string | string, int | 接收器完全匹配第一个参数中的正则表达式,结果是第二个参数编号的匹配组。 |
regexpFind |
string | string, int, int | 接收器包含一个或多个第一个参数中正则表达式的出现。结果是与正则表达式匹配的 substring ,第二个参数是出现次数,第三个参数是接收器中出现开始的索引。 |
regexpMatch |
string | 接收器与参数作为正则表达式匹配。 | |
regexpReplaceAll |
string | string, string | 结果是通过将第一个参数作为正则表达式在接收器中所有出现替换为第二个参数而获得的。 |
replaceAll |
string | string, string | 结果是通过将第一个参数在接收器中所有出现替换为第二个参数而获得的。 |
splitAt |
string | string | 结果是通过在参数的每次出现处拆分接收器而获得的字符串之一。 |
splitAt |
string | string, int | 假设参数为 delim 和 i 。结果是在通过在参数的每次出现处拆分接收器而获得的字段中,第 i 个字段。 |
substring |
string | int, int | 结果是接收器的 substring ,从第一个参数的索引开始,到第二个参数的索引之前结束。 |
suffix |
string | int | 结果是接收器的后缀,其长度正好等于接收器的长度减去参数。如果参数为负数或大于接收器的长度,则没有结果。因此,对于 i 在 [0, s.length()] 中,恒等式 s.prefix(i)+s.suffix(i)=s 成立。 |
toDate |
date | 结果是由接收器确定的日期值。接收器的格式未指定,但如果 (d, s) 在 date.toString 中,则 (s, d) 在 string.toDate 中。 |
|
toFloat |
float | 结果是接收器表示的浮点数。如果接收器不能被解析为浮点数,则没有结果。 | |
toInt |
int | 结果是接收器表示的整数。如果接收器不能被解析为整数,或者不能被表示为 QL int ,则没有结果。解析器接受一个可选的前导 - 或 + 字符,后跟一个或多个十进制数字。 |
|
toLowerCase |
string | 结果是接收器,其中所有字母都转换为小写。 | |
toString |
string | 结果是接收器。 | |
toUpperCase |
string | 结果是接收器,其中所有字母都转换为大写。 | |
trim |
string | 结果是接收器,其中从字符串的开头和结尾删除了所有空格。 | |
codePointAt |
int | int | 结果是参数给定的索引处的 Unicode 代码点。 |
codePointCount |
int | int, int | 结果是在给定索引之间,接收器中 Unicode 代码点的数量。 |
正则表达式与 Java 中的 java.util.regex.Pattern
定义相同。有关更多信息,请参阅 Java API 文档。
评估¶
本节规定 QL 程序的评估。评估分为三个阶段。首先,程序被分层为多个层。其次,这些层被逐个评估。最后,评估 QL 文件中的查询以生成有序元组集。
分层¶
QL 程序可以被分层为一系列层。层是谓词和类型的集合。
有效的分层必须包含 QL 程序中完全实例化的每个谓词和类型。它不能包含任何其他谓词或类型。
有效的分层不能在多个层中包含相同的谓词。
每个非抽象谓词都有一个关联的体。对于声明模块中的谓词,这是谓词声明。实例化嵌套谓词的体是底层嵌套谓词的体,其中所有引用和调用都已替换为实例化相对实体或别名。
谓词体内的公式、变量声明和表达式具有否定极性,可以是正、负或零。正和负是彼此相反的,而零则是其自身的相反。然后,根据以下规定确定公式或表达式的否定极性
- 谓词的体是正的。
- 否定公式内的公式与否定公式的极性相反。
- 条件公式的条件为零极性。
- 蕴含公式左侧的公式与蕴含的极性相反。
- 聚合的公式和变量声明为零极性。
- 如果
monotonicAggregates
语言编译指令未启用,或者原始公式和变量声明都被省略,则聚合的表达式和排序表达式为零极性。 - 如果
monotonicAggregates
语言编译指令已启用,并且原始公式和变量声明并未都被省略,则聚合的表达式和排序表达式具有聚合的极性。 - 如果一个
forall
具有两个量化公式,则第一个量化公式与forall
的极性相反。 - 一个
forall
的变量声明与forall
的极性相反。 - 如果一个
forex
具有两个量化公式,则第一个量化公式为零极性。 - 一个
forex
的变量声明为零极性。 - 在所有其他情况下,公式或表达式与其紧邻的封闭公式或表达式具有相同的极性。
对于成员谓词 p
,我们定义严格分派依赖项。严格分派依赖项定义为
- 覆盖
p
的任何谓词的严格分派依赖项。 - 如果
p
不是抽象的,则对于任何具有覆盖p
的谓词的类C
,则为C.class
。
对于成员谓词 p
,我们定义分派依赖项。分派依赖项定义为
- 覆盖
p
的谓词的分派依赖项。 - 谓词
p
本身。 C.class
,其中C
是定义p
的类。
谓词和类型可以相互依赖和严格依赖。此类依赖项存在于以下情况下
- 如果
A
严格依赖于B
,则A
依赖于B
。 - 如果
A
依赖于B
,则A
也依赖于B
所依赖的任何内容。 - 如果
A
严格依赖于B
,则A
和依赖于A
的任何内容都严格依赖于B
所依赖的任何内容(包括B
本身)。 - 如果一个谓词的参数的声明类型是类类型
C
,则它依赖于C.class
。 - 如果一个谓词声明的结果类型是类类型
C
,则它依赖于C.class
。 - 类
C
的成员谓词依赖于C.class
。 - 如果一个谓词包含一个变量声明,该变量的声明类型是类类型
C
,则该谓词依赖于C.class
。如果声明具有负或零极性,则该依赖项是严格的。 - 如果一个谓词包含一个变量声明,该变量的声明类型是类类型
C
,并且具有负或零极性,则该谓词严格依赖于C.class
。 - 如果一个谓词包含一个表达式,其类型是类类型
C
,而不是变量引用,则该谓词依赖于C.class
。如果表达式具有负或零极性,则该依赖项是严格的。 - 包含谓词调用的谓词取决于调用解析到的谓词。如果调用具有负极性或零极性,则依赖关系是严格的。
- 包含谓词调用的谓词,该调用解析为成员谓词,其中调用不是直接的,取决于调用目标的根定义的调度依赖关系。如果调用具有负极性或零极性,则依赖关系是严格的。谓词严格依赖于根定义的严格调度依赖关系。
- 对于程序中的每个类
C
,对于C
的每个基类B
,C.extends
依赖于B.B
。 - 对于程序中的每个类
C
,对于C
的每个 instanceof 类型B
,C.extends
依赖于B
。 - 对于程序中的每个类
C
,对于C
的每个基类型B
(不是类类型),C.extends
依赖于B
。 - 对于程序中的每个类
C
,C.class
依赖于C.C
。 - 对于程序中的每个类
C
,C.C
依赖于C.extends
。 - 对于程序中声明了类类型
B
字段的每个类C
,C.C
依赖于B.class
。 - 对于每个具有特征谓词的类
C
,C.C
依赖于特征谓词。 - 对于程序中的每个抽象类
A
,对于从A
继承且具有A
作为基类型的每个类型C
,A.class
依赖于C.class
。 - 具有高阶主体的谓词可能严格依赖或依赖于主体内的每个谓词引用。确切的依赖关系未指定。
有效的分层必须没有依赖于更高层谓词的谓词。此外,它必须没有严格依赖于同一层谓词的谓词。
如果 QL 程序没有有效的分层,则该程序本身无效。如果它有分层,则 QL 实现必须选择一个分层。选择的具体分层未指定。
层评估¶
存储首先使用所有内置谓词和外部谓词的数据库内容进行初始化。谓词的数据库内容是一组包含在数据库中的有序元组。
分层中的每一层都按顺序填充。要填充一层,重复填充层中的每个谓词,直到存储停止更改。谓词的填充方式如下
要填充以公式作为主体的谓词,请找到每个具有以下属性的命名元组
t
- 元组与主体公式匹配。
- 变量应该是谓词的参数。
- 如果谓词有结果,则元组还应该具有
result
的值。 - 如果谓词是类的成员谓词或特征谓词
C
,则元组还应具有this
和类上每个可见字段的值。 - 与参数相对应的值都应是声明参数类型的成员。
- 与
result
相对应的值都应是结果类型的成员。 - 与字段相对应的值都应是声明字段类型的成员。
- 如果谓词是类
C
的成员谓词且不是特征谓词,则元组还应扩展C.class
中的某个元组。 - 如果谓词是类
C
的特征谓词,则在C.extends
中应该存在元组t'
,使得对于C
中的每个可见字段,任何等于或覆盖t'
中字段的字段都应在t
中具有相同的值。this
也应该映射到t
和t'
中的相同值。
对于每个这样的元组,删除与字段相对应的任何组件,并将其添加到存储中的谓词中。
要填充抽象谓词,请什么也不做。
具有高阶主体的谓词的填充仅部分指定。一些元组被添加到存储中的给定谓词中。添加的元组必须由 QL 程序和存储的状态完全确定。
要填充类
C
的类型C.extends
,请识别每个具有以下属性的命名元组this
的值在C
的所有非类基类型中。this
的值在C
的所有 instanceof 类型中。- 元组的键是
this
以及每个基类型的公共字段的并集。 - 对于
C
的每个类基类型B
,存在一个命名元组,该元组具有来自B
的公共字段和this
的变量,给定元组和B.B
中的某个元组都扩展了该变量。
对于每个这样的元组,将其添加到
C.extends
中。要填充类
C
的类型C.C
,如果C
有特征谓词,则将该谓词中的所有元组添加到存储中。否则,添加所有元组t
,使得t
的变量应该是this
以及C
的可见字段。- 与字段相对应的值都应是声明字段类型的成员。
- 如果谓词是类
C
的特征谓词,则在C.extends
中应该存在元组t'
,使得对于C
中的每个可见字段,任何等于或覆盖t'
中字段的字段都应在t
中具有相同的值。this
也应该映射到t
和t'
中的相同值。
要填充非抽象类类型
C
的类型C.class
,将C.C
中的每个元组添加到C.class
中。- 要填充抽象类类型
C
的类型C.class
,请识别每个具有以下属性的命名元组 - 它是
C.C
的成员。 - 对于每个从
C
继承且具有C
作为基类型的类D
,则存在一个命名元组,该元组具有来自C
的公共字段和this
的变量,给定元组和D.class
中的某个元组都扩展了该变量。
- 它是
- 要填充抽象类类型
查询评估¶
查询的评估如下
- 识别有关查询谓词的所有事实。
- 如果存在 select 子句,则找到所有具有在
from
子句中声明的变量的命名元组,这些变量与where
子句中给定的公式匹配(如果有)。对于每个命名元组,将其转换为一组有序元组,其中每个有序元组的元素是在命名元组的上下文中,对应 select 表达式之一的值。然后按字典顺序对有序元组进行排序。字典顺序的第一个元素是查询目标谓词(如果有)的排序指令指定的元组元素。每个这样的元素按排序指令指定的升序 (asc
) 或降序 (desc
) 排序,或者如果排序指令未指定,则按升序排序。此字典顺序仅是部分排序,如果排序指令少于元组的元素。实现可以生成满足此部分排序的任何有序元组序列。 - 结果是来自查询谓词的事实,加上来自 select 子句的有序元组列表(如果存在)。
语法摘要¶
QL 的完整语法如下
ql ::= qldoc? moduleBody
module ::= annotation* "module" modulename parameters? implements? "{" moduleBody "}"
parameters ::= "<" signatureExpr parameterName ("," signatureExpr parameterName)* ">"
implements ::= "implements" moduleSignatureExpr ("," moduleSignatureExpr)*
moduleBody ::= (import | predicate | class | module | alias | select)*
import ::= annotations "import" importModuleExpr ("as" modulename)?
qualId ::= simpleId | qualId "." simpleId
importModuleExpr ::= qualId | importModuleExpr "::" modulename arguments?
arguments ::= "<" argument ("," argument)* ">"
argument ::= moduleExpr | type | predicateRef "/" int
signature ::= predicateSignature | typeSignature | moduleSignature
predicateSignature ::= qldoc? annotations "signature" head ";"
typeSignature ::= qldoc? annotations "signature" "class" classname ("extends" type ("," type)*)? (";" | "{" signaturePredicate* "}")
moduleSignature ::= qldoc? annotation* "signature" "module" moduleSignatureName parameters? "{" moduleSignatureBody "}"
moduleSignatureBody ::= (signaturePredicate | defaultPredicate | signatureType)*
signaturePredicate ::= qldoc? annotations head ";"
defaultPredicate ::= qldoc? annotations "default" head "{" formula "}"
signatureType ::= qldoc? annotations "class" classname ("extends" type ("," type)*)? "{" signaturePredicate* "}"
select ::= ("from" var_decls)? ("where" formula)? "select" as_exprs ("order" "by" orderbys)?
as_exprs ::= as_expr ("," as_expr)*
as_expr ::= expr ("as" lowerId)?
orderbys ::= orderby ("," orderby)*
orderby ::= lowerId ("asc" | "desc")?
predicate ::= qldoc? annotations head optbody
annotations ::= annotation*
annotation ::= simpleAnnotation | argsAnnotation
simpleAnnotation ::= "abstract"
| "cached"
| "external"
| "extensible"
| "final"
| "transient"
| "library"
| "private"
| "deprecated"
| "override"
| "additional"
| "query"
argsAnnotation ::= "pragma" "[" ("inline" | "inline_late" | "noinline" | "nomagic" | "noopt" | "assume_small_delta") "]"
| "language" "[" "monotonicAggregates" "]"
| "bindingset" "[" (variable ( "," variable)*)? "]"
head ::= ("predicate" | type) predicateName "(" var_decls ")"
optbody ::= ";"
| "{" formula "}"
| "=" literalId "(" (predicateRef "/" int ("," predicateRef "/" int)*)? ")" "(" (exprs)? ")"
class ::= qldoc? annotations "class" classname ("extends" type ("," type)*)? ("instanceof" type ("," type)*)? "{" member* "}"
member ::= character | predicate | field
character ::= qldoc? annotations classname "(" ")" "{" formula "}"
field ::= qldoc? annotations var_decl ";"
moduleExpr ::= modulename arguments? | moduleExpr "::" modulename arguments?
moduleSignatureExpr ::= (moduleExpr "::")? moduleSignatureName arguments?
signatureExpr : (moduleExpr "::")? simpleId ("/" Integer | arguments)?;
type ::= (moduleExpr "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
exprs ::= expr ("," expr)*
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
| qldoc? annotations "class" classname "=" type ";"
| qldoc? annotations "module" modulename "=" moduleExpr ";"
var_decls ::= (var_decl ("," var_decl)*)?
var_decl ::= type lowerId
formula ::= fparen
| disjunction
| conjunction
| implies
| ifthen
| negated
| quantified
| comparison
| instanceof
| inrange
| call
fparen ::= "(" formula ")"
disjunction ::= formula "or" formula
conjunction ::= formula "and" formula
implies ::= formula "implies" formula
ifthen ::= "if" formula "then" formula "else" formula
negated ::= "not" formula
quantified ::= "exists" "(" expr ")"
| "exists" "(" var_decls ("|" formula)? ("|" formula)? ")"
| "forall" "(" var_decls ("|" formula)? "|" formula ")"
| "forex" "(" var_decls ("|" formula)? "|" formula ")"
comparison ::= expr compop expr
compop ::= "=" | "!=" | "<" | ">" | "<=" | ">="
instanceof ::= expr "instanceof" type
inrange ::= expr "in" (range | setliteral)
call ::= predicateRef (closure)? "(" (exprs)? ")"
| primary "." predicateName (closure)? "(" (exprs)? ")"
closure ::= "*" | "+"
expr ::= dontcare
| unop
| binop
| cast
| primary
primary ::= eparen
| literal
| variable
| super_expr
| postfix_cast
| callwithresults
| aggregation
| expression_pragma
| any
| range
| setliteral
eparen ::= "(" expr ")"
dontcare ::= "_"
literal ::= "false" | "true" | int | float | string
unop ::= "+" expr
| "-" expr
binop ::= expr "+" expr
| expr "-" expr
| expr "*" expr
| expr "/" expr
| expr "%" expr
variable ::= varname | "this" | "result"
super_expr ::= "super" | type "." "super"
cast ::= "(" type ")" expr
postfix_cast ::= primary "." "(" type ")"
aggregation ::= aggid ("[" expr "]")? "(" var_decls ("|" (formula)? ("|" as_exprs ("order" "by" aggorderbys)?)?)? ")"
| aggid ("[" expr "]")? "(" as_exprs ("order" "by" aggorderbys)? ")"
| "unique" "(" var_decls "|" (formula)? ("|" as_exprs)? ")"
expression_pragma ::= "pragma" "[" expression_pragma_type "]" "(" expr ")"
expression_pragma_type ::= "only_bind_out" | "only_bind_into"
aggid ::= "avg" | "concat" | "count" | "max" | "min" | "rank" | "strictconcat" | "strictcount" | "strictsum" | "sum"
aggorderbys ::= aggorderby ("," aggorderby)*
aggorderby ::= expr ("asc" | "desc")?
any ::= "any" "(" var_decls ("|" (formula)? ("|" expr)?)? ")"
callwithresults ::= predicateRef (closure)? "(" (exprs)? ")"
| primary "." predicateName (closure)? "(" (exprs)? ")"
range ::= "[" expr ".." expr "]"
setliteral ::= "[" expr ("," expr)* ","? "]"
simpleId ::= lowerId | upperId
modulename ::= simpleId
moduleSignatureName ::= upperId
classname ::= upperId
dbasetype ::= atLowerId
predicateRef ::= (moduleExpr "::")? literalId
predicateName ::= lowerId
parameterName ::= simpleId
varname ::= lowerId
literalId ::= lowerId | atLowerId | "any" | "none"