轻量级建模语言形式语义定义方法解析
1. UML 动作语义研究
在 UML 相关研究中,M.L. Crane 和 J. Dingel 支持可变动作以及带保护条件的决策控制节点,而 fUML 则支持循环和条件节点,但二者都不支持链接或关联。研究对 UML 动作子集的执行语义进行了形式化定义,该定义通过表示执行 UML 模型的全局状态机的状态变化来表达。同时,还探讨了最复杂动作(即调用动作)的常见使用场景。
研究发现了 UML 动作存在的一些模糊性和隐含假设。例如,UML 规范未明确当 DestroyObjectAction 销毁其所属对象时会发生什么;同步 CallOperationAction 与 AcceptCallAction 配对的要求在前者的描述中不清晰;异步 CallOperationAction 应与 AcceptEventAction 匹配,但也可能与 AcceptCallAction 匹配,这可能会让建模者在创建接收行为前需要先确定调用是同步还是异步。
此外,研究还验证了三层语义层次结构,通过将活动作为动作语言,成功地将高级行为形式主义映射到动作,再将这些动作映射到基于系统模型的状态转换器。该系统模型旨在从最通用的层面捕捉 UML 模型的语义,并轻松处理不同类型 UML 图的集合。这项研究也是创建 UML 虚拟机的持续努力的一部分。
2. 建模语言形式语义定义概述
要全面定义一种建模语言,需要明确抽象语法(模型的结构)、具体语法(呈现给用户的实际符号)、静态语义(模型的格式规则)和动态语义(模型的含义)。理想情况下,这些元素都应进行形式化定义,这有助于对语言进行精确推理,并为开发语言工具提供良好基础。然而,许多建模语言在引入时并未进行完整的形式化定义,如统一建模语言(UML),其语义未完全形式化,这给 UML 工具的开发和互操作性带来了困难。
3. 示例语言:EP 子集
选择的 EP 子集由元模型指定。一个 EP 系统定义了一组模型,每个模型由一组属性和一组事件组成,其作用类似于面向对象系统中的类。模型的实例在运行时存在,属性的赋值构成系统的状态,事件代表对这些实例的可用操作。为简化起见,省略了基本类型,即类型为模型,值为实例。每个有效系统应恰好有一个主模型,作为系统的入口点,EP 系统的初始状态通过创建其唯一实例获得。
EP 语言的新颖之处在于通过事件边以声明方式对事件行为进行建模。执行实例上的事件可通过两种方式影响系统状态:一是使用影响边更改实例属性的值,新值从与该边关联的表达式计算得出;二是通过推送边触发某些实例上的其他事件。推送边是从源事件到目标事件的有向连接,它有一个链接属性,指示目标事件将在哪个实例上触发,并且为目标事件的每个参数指定一个计算该参数值的表达式。
以下是一个简单示例:存在两个实例 “a” 和 “b”,实例 “a” 有一个无参数的事件 “e”,实例 “b” 有一个接受两个整数参数的事件 “e′”。三角形头的实心箭头表示事件之间的推送边,标有 “Link” 的虚线箭头指定边的链接,即 “a” 的一个指向 “b” 的局部属性。方头箭头表示事件 “e′” 与局部属性 “p” 之间的影响边,标签为表达式 x + y。执行 “a” 上的 “e” 会触发 “b” 上的 “e′”,参数为 1 和 1,进而将 “b” 的 “p” 值更改为 2。
4. 传统定义方法
4.1 抽象语法和静态语义
- EP 元模型和 OCL 约束 :通过元模型定义抽象语法,使用对象约束语言(OCL)约束定义静态语义。除类图指定的固有语法限制(如组合和多重性)外,还指定了四组格式规则。对于有效系统,指定的主模型应在系统中定义;对于有效事件,提供属性的集合应在拥有该事件的模型中定义。影响边和推送边的规则更为复杂,影响边有效的条件包括:指定受影响属性新值的表达式只能引用提供事件的属性和事件参数;表达式和受影响属性应具有相同类型;受影响属性和拥有该边的事件应属于同一模型。推送边有效的条件包括:用于计算目标事件参数的表达式只能引用源事件的参数或提供源事件的属性;边的链接应指向一个属性,该属性的类型(即模型)拥有目标事件;目标事件的参数序列和参数序列应分别具有相同的大小和类型。
- EBNF 语法和类型检查规则 :另一种形式化抽象语法和静态语义的方法是采用基于扩展巴科斯 - 诺尔范式(EBNF)的语法和类型检查规则。该方法是文本式的,通过名称进行引用。假设存在不相交的名称集合,包括变量名、属性名、事件名和模型名。
EBNF 抽象语法如下:
Sys ::= Mdf
Mdf ::= model m {m p; Edf}
Edf ::= p feed e(m x) {Edg}
Edg ::= push Exp into e via p
| impact p with Exp
类型检查规则中的特殊类型 “ok” 表示类型良好。类型环境 Γ 是变量到类型的有限绑定,模型上的掩码操作 “m↾p” 用于控制模型 m 属性在不同情况下的可见性。类型检查规则还使用了两个辅助函数:etype(m, e) 返回模型 m 中定义的事件 e 的参数类型序列,否则返回 ⊥;ptype(m↾p, p) 返回模型 m 中定义的属性 p 的类型,若被掩码则返回 ⊥。类型检查规则原则上与 OCL 约束相对应,但由于该方法的文本性质,需要额外的记录和名称区分前提条件。
4.2 操作语义
系统配置 (Λ, s) 是评估环境和状态的一对。状态记录当前的实例集合,环境将变量绑定到值,指示如何通过变量访问这些实例。值 v 可以是 null 或表示某个模型实例的标识 id。实例标识是唯一的,在状态中,实例标识与一对 (m, ϕm) 绑定,其中 m 表示实例的模型,ϕm 表示其评估。评估是从属性到值的部分函数,对于每个模型 m,假设其实例有一个默认评估 φm,将所有属性初始化为 null。
系统状态替换 σ 是从实例标识和属性对到值的部分函数,应用替换 σ 到状态 s 会返回一个新状态。初始化时,系统配置 (Λ0, s0) 有一个唯一实例,其标识为 id,模型为 Main,评估为 φMain,并且在 Λ0 中有一个唯一绑定 (main : id)。与 EP 系统交互相当于调用从变量 main 可访问的某个实例上的事件,并将系统转换为新的配置。
事件调用和边的评估规则如下:
Values, states, and substitutions:
States:
s ::= ∅ | (id : (m, ϕm)), s
Values:
v ::= null | id
Evaluations:
ϕm : P → V
State substitutions:
σ : ID × P → V
Substitution application:
sσ(id).m = s(id).m
sσ(id).ϕm(p) =
{ s(id).ϕm(p) if σ(id, p) = ⊥
{ σ(id, p) otherwise
Evaluation environments:
Λ ::= ϵ | (x : v), Λ
Expression evaluation (partial):
Variable:
Λ(x) = v
Λ ⊨(x, s) ⇓(v, ∅)
InstanceCreation:
id fresh
Λ ⊨(m :: create(), s) ⇓(id, (id : (m, φm)))
PropertyCall:
Λ ⊨(Exp, s) ⇓(id, s∆)
(s ∪ s∆)(id).ϕm(p) = v
Λ ⊨(Exp.p, s) ⇓(v, s∆)
Edge evaluation:
EImpactEdge:
Λ ⊨(Exp, s) ⇓(v1, s1∆)
Λ ⊨(self, s) ⇓(v2, s2∆)
Λ ⊨(impact p with Exp, s) ⇓(((v2, p) : v1), s1∆ ∪ s2∆)
EPushEdge:
Λ ⊨((self.p).e(Exp), s) ⇓(σ, s∆)
Λ ⊨(push Exp into e via p, s) ⇓(σ, s∆)
Event call evaluation:
EventCall:
Λ ⊨(Exp, s) ⇓(v, s∆)
Λ ⊨(Exp, s) ⇓(v, s∆)
v ≠ null
p feed e(m x) {Edgi i∈I} ∈ (s ∪ s∆)(v).m
(Λ ∪ (x : v) ∪ (self : v) ⊨(Edgi, s ∪ s∆ ∪ s∆) ⇓(σi, si∆)) i∈I
Λ ⊨(Exp.e(Exp), s) ⇓(∪i∈I σi, s∆ ∪ s∆ ∪ (∪i∈I si∆))
Event invocation reduction:
EventInvocationReduction:
Λ ⊨(Exp.e(Exp), s) ⇓(σ, s∆)
(Λ, s) Exp.e(Exp) → (Λ, (s ∪ s∆)σ)
调用事件 e 在环境 Λ 和状态 s 下的评估结果为替换 σ 和在评估期间创建的新实例集合 s∆。评估影响边会得到一个替换,要求将受影响属性的值更改为从表达式计算出的新值;评估推送边的结果是通过在相同环境和状态下递归调用目标事件得到的。评估结果的第二部分 s∆ 跟踪在评估期间创建的新实例,由于新实例只能在 create 表达式中创建,且要求标识的新鲜性,因此累积的 s∆ 不会在标识上发生冲突。
以下是一个简单的流程图,展示了事件调用和状态转换的过程:
graph LR
A[开始] --> B[初始化系统配置]
B --> C[调用事件]
C --> D{事件评估}
D -->|影响边| E[更改属性值]
D -->|推送边| F[触发其他事件]
E --> G[更新状态]
F --> G
G --> H[结束]
综上所述,传统方法在定义建模语言的抽象语法、静态语义和动态语义方面有各自的特点和应用场景,但也存在一些局限性,如 UML 动作的模糊性和传统形式语义方法的可读性和工具支持不足等问题。接下来将介绍基于 Alloy 语言的统一方法,并与传统方法进行对比。
5. 基于 Alloy 语言的统一方法
5.1 Alloy 方法概述
为了定义建模语言的形式语义,提出了一种基于 Alloy 语言的新颖方法。Alloy 语言为定义建模语言的抽象语法、静态语义和动态语义提供了统一的解决方案。与传统方法相比,Alloy 具有统一的符号表示,并且可以使用 Alloy 分析器进行自动分析,这有助于在开发复杂语义描述时尽早发现错误,实现增量式开发。
5.2 用 Alloy 定义示例语言
以之前介绍的 EP 子集为例,使用 Alloy 语言来定义其各个方面的语义。
-
抽象语法和静态语义
:在 Alloy 中,可以使用关系和约束来定义模型的结构和格式规则。例如,定义模型、属性、事件和边之间的关系,以及确保系统中只有一个主模型等规则。以下是一个简单的 Alloy 代码示例,用于定义 EP 系统的部分抽象语法和静态语义:
// 定义模型集合
sig Model {
properties: set Property,
events: set Event
}
// 定义属性集合
sig Property {}
// 定义事件集合
sig Event {
feedingProperties: set Property,
edges: set Edge
}
// 定义边的抽象类型
abstract sig Edge {}
// 定义影响边
sig ImpactEdge extends Edge {
impactedProperty: Property,
expression: Expression
}
// 定义推送边
sig PushEdge extends Edge {
targetEvent: Event,
linkProperty: Property,
argumentExpressions: set Expression
}
// 定义表达式集合
sig Expression {}
// 约束:每个系统只有一个主模型
one sig MainModel in Model {}
- 动态语义 :Alloy 可以通过定义状态转换关系来描述系统的动态行为。例如,定义事件的执行如何导致系统状态的变化,包括属性值的更新和其他事件的触发。以下是一个简单的 Alloy 代码示例,用于描述事件执行的动态语义:
// 定义系统状态
sig State {
instances: set Instance,
propertyValues: Instance -> Property -> Value
}
// 定义实例集合
sig Instance {
model: Model
}
// 定义值集合
sig Value {}
// 定义事件执行的转换关系
pred executeEvent [s, s': State, e: Event, i: Instance] {
// 处理事件的影响边
all ie: e.edges & ImpactEdge | {
let p = ie.impactedProperty, exp = ie.expression | {
// 计算表达式的值
let newValue = evaluateExpression[exp, s, i] | {
// 更新属性值
s'.propertyValues[i][p] = newValue
}
}
}
// 处理事件的推送边
all pe: e.edges & PushEdge | {
let target = pe.targetEvent, link = pe.linkProperty, args = pe.argumentExpressions | {
// 找到目标实例
let targetInstance = findInstance[s, i, link] | {
// 计算参数值
let argValues = evaluateExpressions[args, s, i] | {
// 递归执行目标事件
executeEvent[s', s'', target, targetInstance, argValues]
}
}
}
}
}
// 辅助函数:计算表达式的值
fun evaluateExpression [exp: Expression, s: State, i: Instance]: Value {
// 具体实现省略
Value
}
// 辅助函数:计算多个表达式的值
fun evaluateExpressions [exps: set Expression, s: State, i: Instance]: set Value {
// 具体实现省略
set Value
}
// 辅助函数:找到目标实例
fun findInstance [s: State, i: Instance, p: Property]: Instance {
// 具体实现省略
Instance
}
6. Alloy 方法与传统方法对比
| 对比项 | 传统方法 | Alloy 方法 |
|---|---|---|
| 符号表示 | 多种不同的符号和形式,如 EBNF、OCL、操作语义规则等,缺乏统一的表示方式 | 统一的符号表示,易于理解和维护 |
| 自动分析 | 部分方法缺乏有效的自动分析工具支持,错误发现和调试较为困难 | 可以使用 Alloy 分析器进行自动分析,有助于尽早发现错误 |
| 开发难度 | 对于复杂语义的开发,需要处理多种不同的规则和表示,开发过程较为繁琐 | 提供了统一的框架,便于进行增量式开发 |
| 可读性 | 一些传统形式语义方法的规则和表示可能较为复杂,可读性较差 | Alloy 的关系和约束表示相对直观,提高了可读性 |
以下是一个 mermaid 格式的流程图,展示了 Alloy 方法和传统方法在开发过程中的对比:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B{选择方法}:::decision
B -->|传统方法| C(定义抽象语法<br>使用 EBNF 或元模型):::process
B -->|Alloy 方法| D(使用 Alloy 定义结构和约束):::process
C --> E(定义静态语义<br>使用 OCL 或类型检查规则):::process
D --> E
C --> F(定义动态语义<br>使用操作语义):::process
D --> F
F --> G(调试和验证<br>可能缺乏自动工具支持):::process
E --> G
D --> H(使用 Alloy 分析器自动分析):::process
H --> I(调试和验证<br>尽早发现错误):::process
G --> J([结束]):::startend
I --> J
7. 总结
通过对传统方法和基于 Alloy 语言的方法进行研究和对比,可以得出以下结论:
- 传统方法在定义建模语言语义方面有着深厚的理论基础和广泛的应用,但存在符号不统一、自动分析能力不足、开发难度较大和可读性较差等问题。
- 基于 Alloy 语言的方法提供了一种轻量级、统一的解决方案,具有统一的符号表示和自动分析能力,有望使形式化定义更加容易,推动形式化技术在建模语言定义中的更广泛应用。
在实际应用中,可以根据具体的需求和场景选择合适的方法,或者将不同的方法结合使用,以充分发挥各自的优势,实现对建模语言语义的准确、高效定义。
超级会员免费看
1837

被折叠的 条评论
为什么被折叠?



