我们在给uppaal进行书写时,声明的定义肯定是需要的,不管是全局声明还是局部声明,接下来我将介绍一下官方给的声明给出的定义格式。
1.类型
有 4 种预定义类型:int、bool、clock 和 chan。数组和记录类型可以基于这些类型和其他类型进行定义。
Type ::= Prefix TypeId
Prefix ::= 'urgent' | 'broadcast' | 'meta' | 'const'
TypeId ::= ID | 'int' | 'clock' | 'chan' | 'bool'
| 'int' '[' Expression ',' Expression ']'
| 'scalar' '[' Expression ']'
| 'struct' '{' FieldDecl (FieldDecl)* '}'
FieldDecl ::= Type ID ArrayDecl* (',' ID ArrayDecl*)* ';'
ArrayDecl ::= '[' Expression ']'
| '[' Type ']'
整数的默认范围是 [-32768, 32767]。任何超出范围的赋值都将导致验证中止。
bool 类型的变量可以具有值 false 和 true,它们相当于整数值 0 和 1。与 C 语言一样,任何非零整数值计算结果为 true,而 0 计算结果为 false。
可以将通道声明为紧急通道和/或广播通道。有关紧急通道和广播通道的信息,请参阅4.同步部分。
常量
整数、布尔值、整数和布尔值上的数组以及记录可以通过在类型前加上关键字 const 来标记为常量。
元变量
整数、布尔值、整数和布尔值上的数组以及记录可以通过在类型前加上关键字 meta 来标记为元变量。
元变量存储在状态向量中,但从语义上讲不被视为状态的一部分。也就是说,只有元变量不同的两个状态被视为相等。
数组
数组的大小可以指定为整数、有界整数类型或标量集类型。在第一种情况下,数组将以 0 为索引。在后一种情况下,索引将为给定类型。以下声明了一个大小为 3 的标量集 s_t 和一个由标量索引的大小为 3 的整数数组 a:
typedef scalar[3] s_t;
int a[s_t];
记录变量
记录类型使用 struct 关键字指定,后跟 C 符号。例如,下面的记录由两个字段 a 和 b 组成:
struct
{
int a;
int b;
} s;
标量
UPPAAL 中的标量是类似整数的元素,但操作数量有限:赋值和身份测试。只能比较来自同一标量集的标量。操作数量有限意味着标量是无序的(或者所有顺序都是等效的,因为模型无法区分它们中的任何一个)。 UPPAAL 将对称性减少应用于任何使用标量的模型。对称性减少可以显著减少模型的状态空间。从而加快验证速度并减少内存使用。请注意,如果启用了诊断跟踪生成或验证了 A<>、E[] 或 --> 属性,则不会应用对称性减少。标量集被视为类型。使用 scalar[n] 类型构造函数构造新的标量集,其中 n 是一个整数,表示标量集的大小。不同标量集的标量是无法比较的。使用 typedef 命名标量集,以便可以多次使用,例如:
typedef scalar[3] mySet;
mySet s;
int a[mySet];
这里,mySet 是一个大小为 3 的标量集,s 是一个变量,其值属于标量集 mySet,a 是一个由标量集 mySet 索引的整数数组。因此,a[s] = 2 是一个有效的表达式。
2.函数
函数可以与其他声明一起声明。函数的语法由函数的语法定义:
Function ::= Type ID '(' Parameters ')' Block
Block ::= '{' Declarations Statement* '}'
Statement ::= Block
| ';'
| Expression ';'
| ForLoop
| Iteration
| WhileLoop
| DoWhileLoop
| IfStatement
| ReturnStatement
ForLoop ::= 'for' '(' Expression ';' Expression ';' Expression ')' Statement
Iteration ::= 'for' '(' ID ':' Type ')' Statement
WhileLoop ::= 'while' '(' Expression ')' Statement
DoWhile ::= 'do' Statement 'while' '(' Expression ')' ';'
IfStatment ::= 'if' '(' Expression ')' Statement [ 'else' Statement ]
ReturnStatement ::= 'return' [ Expression ] ';'
迭代器
关键字 for 有两种用途:一种是类似 C/C++/Java 的 for 循环,另一种是类似 Java 的迭代器。后者主要用于迭代标量索引的数组。语句 for (ID : Type) Statement 将对 Type 类型的每个值 ID 执行一次 Statement。 ID 的范围是内部表达式 Expr,Type 必须是有界整数或标量集。示例
add
以下函数返回两个整数之和。参数按值调用。
int add(int a, int b)
{
return a + b;
}
交换
以下过程交换两个引用调用整数参数的值。
void swap(int &a, int &b)
{
int c = a;
a = b;
b = c;
}
初始化
以下过程初始化一个数组,使得每个元素都包含其在数组中的索引。请注意,除非在声明中使用了 & 符号,否则数组参数是按值调用参数。这与 C++ 语法不同,在 C++ 语法中,该参数可以被视为对整数的引用数组。
void initialize(int& a[10])
{
for (i : int[0,9])
{
a[i] = i;
}
}
3.参数
模板和函数都是参数化的。参数的语法由参数的语法定义:
Parameters ::= [ Parameter (',' Parameter)* ]
Parameter ::= Type [ '&' ] ID ArrayDecl*
与全局和局部声明不同,参数列表不应以分号结尾。
按引用调用和按值调用
参数可以声明为具有按值调用或按引用调用语义。语法取自 C++,其中按引用调用参数的标识符在参数声明中以 & 符号为前缀。按值调用参数不以 & 符号为前缀。
时钟和通道必须始终是引用参数。
注意:数组参数必须以 & 符号为前缀才能通过引用传递,这不符合 C 语义。
例子
- P(clock &x, bool bit)
过程模板P有两个参数:时钟x和布尔变量bit。 - Q(clock &x, clock &y, int i1, int &i2, chan &a, chan &b)
过程模板Q有6个参数:两个时钟,两个整型变量(有默认范围),两个通道。除i1外,其他参数均为引用参数。
4.同步
通道用于同步进程。这是通过使用同步标签注释模型中的边来完成的。同步标签在语法上非常简单。它们的形式为 e? 或 e!,其中 e 是无副作用的表达式,可计算为通道。
直觉是,两个进程可以在使用互补同步标签注释的启用边上同步,即如果满足两个边的保护条件,则不同进程中的两个边可以同步,并且它们分别具有同步标签 e1? 和 e2!,其中 e1 和 e2 计算为同一通道。
当两个进程同步时,两个边会同时触发,即两个进程的当前位置都会发生变化。在 e1! 上同步的边上的更新表达式在在 e2? 上同步的边上的更新表达式之前执行。这类似于 CCS 中使用的同步类型或 SPIN 中的会合同步。
紧急通道与常规通道类似,不同之处在于,如果可以通过紧急通道触发同步,则无法延迟源状态。请注意,在紧急通道上同步的边缘不允许使用时钟保护。
广播通道允许一对多同步。直觉是,具有同步标签 e!的边缘在通道 e 上发出广播,并且任何启用了同步标签 e?的边缘都将与发射过程同步。即,广播通道上具有发射同步的边缘始终可以触发(前提是满足保护),无论是否启用任何接收边缘。但那些启用的接收边缘将同步。请注意,在广播通道上接收的边缘不允许使用时钟保护。首先执行发射边缘的更新。接收边缘的更新按照系统定义中给出的进程的顺序从左到右执行。
请注意,对于紧急通道和广播通道,了解边缘何时启用非常重要。如果满足保护,则启用边缘。根据不变量,目标状态可能未定义。但这不会改变边缘已启用的事实!例如,当两个不同进程中的两个边缘通过广播通道同步,并且接收边缘的目标位置的不变量被违反时,则此状态未定义。发射边缘不能自行触发,因为接收边缘已启用,因此必须同步。有关更多详细信息,请参阅有关语义的部分。
还支持没有输入/输出说明符(“?”和“!”)的 CSP 类(通信顺序进程)通道同步,但同步标签需要在整个模型中保持一致,并且不能与 I/O 同步混合。