语言简介
包 Package
package 'Package Example' {
import ISO::TorqueValue;
import ScalarValues::*;
part def Automobile;
alias Car for Automobile;
alias Torque for ISO::TorqueValue;
}
包的作用是作为一个其成员的命名空间,以及其所拥有成员的一个容器。
成员和拥有成员的区别:拥有成员表示此成员直接在此包内定义,例如上面所定义的Automobile
,而成员可能包括从其他命名空间下引入的成员。
可以使用import来将其他包内的某个成员或者所有成员引入到当前包内。
要引入全部成员,可以使用 *,例如import ScalarValues::*
。
在包内,可以对成员创建别名。别名既可以对包的拥有成员创建,也可以对引入的其他包内的成员创建。
如果包的名称中包含空格,那么需要将其用单引号包裹起来。
成员可见性
package 'Package Example' {
public import ISO::TorqueValue;
private import ScalarValues::**;
private part def Automobile;
public alias Car for Automobile;
alias Torque for ISO::TorqueValue;
}
在包内,可以使用关键字private、public 来控制成员的可见性(Visibility)。
可见性关键字可以在import前使用。如果使用public import
,那么相当于将引入的成员再次导出,其相对于引用此包的命名空间,也是可见的。
私有的(private)成员对包的外部是不可见的,但是对于包内部的子包而言,则是可见的。
默认情况下,所有成员都是公共的(public)。使用 public 关键字只是将此语义显示的标明出来。
注释 Comment
package 'Comment Example' {
/* 这是文档注释,是模型的一部分,
* 默认作为包的说明文档。 */
comment Comment11 /* 这是一个具名注释 */
comment about Automobile
/* 这是一个不具名的文档注释,
* 使用about来指定说明的元素。
*/
part def Automobile;
/**
* 这是一个文档注释,用来说明紧邻其下的元素。
*
*/
alias Car for Automobile;
// 这是一个批注。
// 批注以文本形式展示,但是并不是模型的一部分。
alias Torque for ISO::TorqueValue;
}
注释用来对模型做出说明,以 /* 开始,以 */ 结束。
注释可以作为单独的元素存在,此时其可以具有一个名字。
当注释直接作为其所注释的元素的一部分时,分为几种情况:
元素文档
以 /** 开始,作为紧邻其下元素的一部分。
不相邻元素文档
可以使用
comment about 指定元素
的方式,将注释和元素放在不同的地方。此时,由于此注释不是单独的元素,所以不能具有名字。包文档
只以 /* 开始,作为其所在包的一部分。
最后,对于模型的单纯批注,可以使用 // 开头。多行批注则可以 //* 开头。批注不作为模型的一部分。
文档 Documentation
package 'Documentation Example' {
doc /* 这是所属包的文档
*/
part def Automoble {
doc /* 这是Automobile的文档 */
}
alias Car for Automobile;
alias Torque for ISO::TorqueValue;
}
文档是一种特殊类型的注释。
部件和属性定义 Part/Attribute Definition
part def Vehicle {
attribute mass : ScalarValues::Real;
part eng : Engine;
ref part driver : Person;
}
attribute def VehicleStatus {
import ScalarValues::*;
attribute gearSetting : Integer;
attribute acceleratorPosition : Real;
}
part def Engine;
part def Person;
在 SysMLv2 里,广泛使用定义(definition )和使用(usage)的概念,以支持重用。 定义表示某种建模概念的声明,而使用则表示定义的概念在某个上下文中的实例。
定义和使用都属于命名空间的一种,因此可以在其中使用import来引入其他成员。
部件定义是系统或者系统组成的类型定义,表示在物理时空上存在并且可变的某种实体。 部件定义可以被作为部件或者引用部件来使用。其中,部件使用表示一个聚合特征,而引用部件使用表示一个引用性的特征。
属性定义表示对修饰性数据的定义,可以用来描述系统或部件。 属性定义中只能包括其他属性的使用,而不能包括部件使用。
泛化和特化 Generalization/Specialization
abstract part def Vehicle;
part def HumanDrivenVehicle specializes Vehicle {
ref part driver : Person;
}
part def PoweredVehicle :> Vehicle {
part eng : Engine;
}
part def HumanDrivenPoweredVehicle :>
HumanDrivenVehicle, PoweredVehicle;
part def Engine;
part def Person;
不同的定义之间可以使用关键字specializes来表示继承关系。 一个特化的定义,即表示它所泛化的定义的一个子集。在特化的定义中,还可以定义属于它本身的额外特征。
定义可以有多个泛化,表示此定义继承它所有泛化定义的特征。
specializes 关键字可以用符号:>来代替。
子集 subsetting
part def Vehicle {
part parts : VehiclePart[*];
part eng : Engine subsets parts;
part trans : Transmission subsets parts;
part wheels : Wheel[4] :> parts;
}
abstract part def VehiclePart;
part def Engine :> VehiclePart;
part def Transmission :> VehiclePart;
part def Wheel :> VehiclePart;
子集是两个特征(feature)之间的一种泛化(generalization)关系。 它表示在公共的上下文里,一个特征的值是另一个特征的值的子集。
和特化关系一样,子集也可以用符号:>来表示。
以Vehicle
为例,eng 和 trans 这两个特征,在同一个 Vehicle 里面,它们的值属于 parts 这个特征的值的子集。
重定义 Redefinition
part def Vehicle {
part eng : Engine;
}
part def SmallVehicle :> Vehicle {
part smallEng : SmallEngine redefine eng;
}
part def BigVehicle :> Vehicle {
part bigEng : BigEngine :>> eng;
}
part Engine {
part cyl : Cylinder[4..6];
}
part def SmallEngine :> Engine {
part redefines cyl[4];
}
part def BigEngine :> Engine {
part redefines cyl[6];
}
part def Cylinder;
在一个特化的定义里,可以使用重定义,来改变继承的特征的名称、类型、多重性。
重定义使用关键字redefines来实现;同时,也可以使用符号:>>来表示。
如果重定义的特征的名字不发生变化,那么可以使用简写形式:
// 在泛化定义中存在名为 someFeature 的特征
part redefines someFeature;
如果重定义的是特征的类型,那么新的类型必须是原有类型的特化。
// SmallEngine 是特征eng的类型 Engine 的一个特化
part smallEng : SmallEngine redefines eng;
枚举定义 Enumeration Definition
enum def TrafficLightColor {
enum green;
enum yellow;
enum red;
}
part def TrafficLight {
attribute currentColor : TrafficLightColor;
}
part def TrafficLightGo specializes TrafficLight {
attribute refefines currentColor = TrafficLightColor::green;
}
枚举定义(Enumeration Definition)是属性定义的一种,其可以定义一组枚举值。
如果某个属性使用的类型是枚举,那么它的值只可能是该枚举所定义的值。
枚举定义之间不能进行特化,但是枚举定义可以特化一个属性定义。 这种情况下,该枚举定义的枚举值可以继承属性定义的特征, 这些特征可以进一步被重定义。
attribute def ClassificationLevel {
attribute code : String;
attribute color : TrafficLightColor;
}
enum def ClassificationKind specializes ClassificationLevel {
unclassified {
:>> code = "uncl";
:>> color = TrafficLightColor::green;
}
confidential {
:>> code = "conf";
:>> color = TrafficLightColor::yellow;
}
secret {
:>> code = "secr";
:>> color = TrafficLightColor::red;
}
}
在枚举定义特化的情况下,其枚举值也可以直接进行赋值。
enum def GradePoints :> Real {
A = 4.0;
B = 3.0;
C = 2.0;
}
部件 Part
部件通过part def
来进行定义,通过part
来进行使用。
// 定义
part def Vehicle {
part eng : Engine;
}
part def Engine {
part cyl : Cylinder[4..6];
}
part def Cylinder;
// 使用
part smallVehicle : Vehicle {
part redefines eng {
part redefines cyl[4];
}
}
part bigVehicle : Vehicle {
part redefines eng {
part redefines cyl[6];
}
}
通过这种定义和使用,在 smallVehicle 和 Vehicle 之间建立的是一种 defined by 关系。 这种关系是泛化的一种。
部件继承了其定义的特征,这些特征可以进行重定义。重定义可以发生在任意层级。例如, 上面的 smallVehicle 内部的 eng 特征
part definition
SysMLv2 强调的是复用性,对于部件定义和部件使用,同样如此。 如果某个部件需要被重复使用,那么就使用part def。
例如,对于 smallVehicle 的 eng 特征,如果这种引擎也可能使用在其他地方,那么可以将其表示为部件定义。
part def SmallEngine :> Engine {
part redefines cyl[4];
}
part smallVehicle : Vehicle {
part redefines eng : SmallEngine;
}
所以,部件定义提供了一种复用和抽象的方式,只使用部件也可以完全的描述复合结构。 部件之间也可以进行特化。
// 定义
part def Vehicle;
part def Engine;
part def Cylinder;
// 使用
part vehicle : Vehicle {
part eng : Engine {
part cyl : Cylinder[4..6];
}
}
part smallVehicle :> vehicle {
part redefines eng {
part redefines cyl[4];
}
}
part bigVehicle :> vehicle {
part redefines eng {
part redefines cyl[6];
}
}
项目 Item
项目定义用来表示这样一类对象,它们在物理时空中存在,但是不一定是所建模的系统的一部分。 所有的部件都可以认为是项目,但是不是所有的项目都是部件。 将一类对象建模为项目还是部件,由所关心得问题和具体的系统设计来确定。
项目可以对和系统发生交互或者在系统内传递的离散对象建模。 项目也可以对存储在系统内或者在系统部件之间流动的连续对象进行建模。
item def Fuel;
item def Person;
part def Vehicle {
attribute mass : Real;
// 司机是和车辆系统进行交互的一个外部离散对象
ref item driver : Person;
part fuelTank {
// 燃料是存储在油箱中的连续对象
item fuel : Fuel;
}
}
如果一个项目在空间上的任一部分,仍然是同一种东西,那么就认为该项目是连续的。 例如,燃料的一部分仍然是燃料;但是人的一部分, 就不在是一个完整的人了。
连接 Connection
连接定义是部件定义的一种,用来表示其两个端之间的连接。
connection def PressureSeat {
end : TireBead[1];
end : TireMountingRim[1];
}
而连接就是对连接定义的一个使用,用来连接部件中的两个特征。
part wheelHubAssembly : WheelHubAssembly {
part wheel : WheelAssembly[1] {
part t : Tire[1] {
part bead : TireBead[2];
}
part w : Wheel[1] {
part rim : TireMountingRim[2];
part mountingHoles : LugBoltMountingHole[5];
}
connection : PressureSeat connect t.bead to w.rim;
}
part lugBoltJoints : LugBoltJoint[0..5];
part hub : Hub[1] {
part h : LugBoltThreadableHole[5];
}
connect lugBoltJoints[0..1]
to mountingHole :> wheel.w.mountingHoles[1];
connect lugBoltJoints[0..1]
to threadedHole :> hub.h[1];
}
使用点式记号可以将深层次的特征相连,例如wheel.w.mountingHoles
。
在 SysMLv1 中,需要使用构造性 NestedConnectorEnd 中的 propertyPath,来达到深层次连接的效果。
端口 Port
端口定义用来定义部件可以通过端口向外开放的特征。
有向特征
具有方向的特征,称为有向特征。
有向特征的方向可以是in
、out
、inout
。
端口可以拥有属性和引用特征。而有向特征都是引用特征,所以关键字ref
可以省略。
port def FuelOutPort {
attribute temperature: Temp;
out item fuelSupply : Fuel;
in item fuelReturn: Fuel;
}
port def FuelInPort {
attribute temperature : Temp;
in item fuelSupply : Fuel;
out item fuelReturn : Fuel;
}
part def FuelTankAssembly {
port fuelTankPort : FuelOutPort;
}
part def Engine {
port engineFuelPort : FuelInPort;
}
如果两个端口具有方向相反的有向特征,那么就说这两个端口是兼容的。例如FuelInPort
和FuelOutPort
。
端口定义的作用相当于 SysMLv1 中的接口模块(InterfaceBlock), 而端口的作用相当于 v1 中的代理端口。
端口共轭 Port Conjugation
端口定义的同时,会隐式的自动定义一个共轭端口,这个端口的所有有向特征具有相反的方向。
可以通过在原有端口的名字前加上符号~
来引用它。
part def Engine {
port engineFuelPort : ~FuelPort;
}
接口 Interface
接口定义是连接定义的一种,其两端连接的都是端口。 接口的两端的类型必须是兼容的(即有向特征的方向是相反的)。
interface def FuelInterface {
end supplierPort : FuelOutPort;
end consumerPort : FuelInPort;
}
part vehicle : Vehicle {
part tankAssy : FuelTankAssembly;
part eng : Engine;
interface : FuelInterface connect
supplierPort :> tankAssy.fuelTankPort to
consumerPort :> eng.engineFuelPort;
}
连接用来将部件连起来,而接口用来将端口连起来。 正如端口是部件的一个特化,接口也是连接的一个特化。
绑定连接 Binding Connection
绑定连接是连接的一种,表示连接的特征具有相等性, 即在相同的上下文里,它们的值是相等的,一般表示这两者指向同一个对象。
part tank : FuelTankAssembly {
port redefines fuelTankPort {
out item redefines fuelSupply;
in item redefines fuelReturn;
}
bind fuelTankPort.fuelSupply = pump.pumpOut;
bind fuelTankPort.fuelReturn = tank.fuelIn;
part pump : FuelPump {
out item pumpOut : Fuel;
in item pumpIn : Fuel;
}
part tank : FuelTank {
out item fuelOut : Fuel;
in item fuelIn : Fuel;
}
}
上面示例中,将端口fuelTankPort
的流出的对象fuelSupply
和部件pump
的流出对象pumpOut
连接起来,
表示由部件 pump 流出的 fuel,经端口 fuelTankPort 代理后,再向外部流出。
绑定连接的语法为 bind oneFeature = anotherFeature
,前面的bind
关键字可以省略。
注意这和赋值语句的含义是不同的。
流连接 Flow Connection
流连接既表示源端口和目标端口之间的一个连接,也表示从源的输出到目标的输入之间存在项目的传输。
part vehicle : Vehicle {
part tankAssy : FuelTankAssembly;
part eng : Engine;
flow of Fuel
from tankAssy.fuelTankPort.fuelSupply
to eng.engineFuelPort.fuelSupply;
flow of Fuel
from eng.engineFuelPort.fuelReturn
to tankAssy.fuelTankPort.fuelReturn;
}
在第一条流连接中,连接的是fuelTankPort
和engineFuelPort
这两个端口,
以及将流动的内容指定为各自的 fuelSupply 项目。而其中具体流动的项目,通过of
来指定。
指定的项目的类型,必须和流连接所连接的两端类型相兼容(和两端类型相同或者是其特化类型)。
Streaming Flow Connection
TODO:
连续接口 Streaming Interface
TODO:
动作 Action
动作定义是对可以执行的某些动作的定义。
动作定义可以拥有参数,参数的方向可以是in
、out
或者inout
。
如果未指定方向,则默认为in
。
动作的参数定义在动作名称之后的小括号中;也可以直接定义在动作体中,类似于有向特征的定义。
action def Focus(in scene: Scene, out image : Image);
action def Shoot(in image: Image, out picture : Picture);
action def TakePicture
(in scene : Scene,
out picture : Picture) {
bind focus.scene = scene;
action focus : Focus (in scene, out image);
flow focus.image to shoot.image;
action shoot : Shoot (in image ,out picture);
bind shoot.picture = picture;
}
动作在使用时,其具有和定义时相同类型和方向的参数,但是名字可以不一样。
动作的参数和动作定义的参数,可以使用绑定连接;
绑定连接的形式除了使用bind
之外,还可以直接在动作体内部进行绑定:
action focus : Focus {
in item scene = TakePicture::scene;
out item image;
}
动作之间,可以使用流连接来关联参数,表示动作之间存在项目的传递。
流连接除了使用flow to
之外,还可以直接在动作体内进行关联:
action shoot : Shoot {
in item image flow from focus.image;
out item picture = TakePicture::picture;
}
动作顺序 Action Succession
动作顺序指定在第二个动作开始之前,第一个动作必须完成。
action def TakePicture {
in item scene : Scene;
out item picture : Picture;
bind focus.scene = scene;
action focus : Focus (in scene, out image);
flow focus.image to shoot.iamge;
first focus then shoot; // 指定执行顺序
action shoot : Shoot(in image, out picture);
bind shoot.picture = picture;
}
除了使用first ... then ...
的形式之外,还可以直接在动作前面使用关键字then
来表示当前动作和其之前紧邻的上一个动作之间存在先后顺序。
action focus : Focus (in scene, out image);
then action shoot : Shoot(in image, out picture);
不同于 SysMLv1 中使用栓(Pin)来对动作的输入和输出进行封装,这里直接使用输入和输出参数进行连接。
可以使用简写形式succession flow
表示
动作分解 Action Decomposition
TODO:
条件顺序 Condional Succession
动作顺序可以添加守卫条件,当前一个动作完成,并且满足守卫条件时,才可以执行下一个动作。
action takePicture : TakePicture {
in item scene;
out item picture;
action focus : Focus {
in item scene = takePicture;
out item image;
}
// 当图像聚焦时,才进行拍摄动作
first focus
if focus.image.isFocused then shoot;
action shoot : Shoot {
in item image flow from focus.image;
out item picture = takePicture::picture;
}
}
同样,守卫条件也可以使用简写形式,将first ...
省略,此种情况表示在词法上相邻的两个动作之间的条件顺序。
合并节点 Merge Node
合并节点可以连接多条输入顺序,当且仅当其中的一个发生的时候,才执行之后的动作。
action takePicture : TakePicture {
first start;
then merge continue;
then action trigger {out item scene;}
then action focus : Focus {
in item scene flow from trigger.scene;
out item image;
}
then action shoot : Shoot {
in image flow from focus.image;
out picture;
}
then action display {
in picture flow from shoot.picture;
}
then continue;
}
判断节点 Decision Node
判断节点选择其之后的一个动作进行执行。
action def ChargeBattery {
first start;
then merge continueCharging;
then action monitor : MonitorBattery {
out batteryCharge : Real;
}
then decide; // 使用decide关键字定义判断节点
if monitor.batteryCharge <= 100 then addCharge;
if monitor.batteryCharge >= 100 then endCharging;
action addCharge : AddCharge {
in charge = monitor.batteryCharge;
}
then continueCharging;
action endCharging : EndCharging;
then done;
}
在最后一个判断条件中,可以使用else
来对其余情况进行处理。
控制结构 Control Structure
控制结构用来对动作的条件执行和循环,提供了一种结构化的实现方式。
action def ChargeBattery {
loop action charging {
action monitor : MonitorBattery {
out charge;
}
then if monitor.charge < 100 {
action addCharge : AddCharge {
in charge = monitor.Charge
}
}
} until charging.monitor.charge >= 100;
}
控制结构的表达式也可以使用while
来实现。
while charging.monitor.charge >=100 {
action charging {}
}
控制结构实现的功能,可以通过 merge,decision 来实现,不过控制结构的方式更为简洁。
分支/集合 Fork/Join
分支节点将其之后的所有紧邻后续动作一起执行。
action def Brake {
action TurnOn;
then fork;
then monitorBrakePedal; // 此三个动作的源动作都是分支节点
then monitorTraction;
then braking;
action monitorBrakePedal : MonitorBrakePedal {
out brakePressure;
} then joinNode;
action monitorTraction : MonitorTraction {
out modulationFrequency;
} then joinNode;
action braking : Braking {
in brakePressure flow from
monitorBrakePedal.brakePressure;
in modulationFrequency flow from
monitorTraction.modulationFrequency;
} then joinNode;
join joinNode;
then done;
}
履行动作 Performed Action
使用关键字perform
可以指定动作的执行者。
part camera : Camera {
perform action takePhoto [*] ordered
:> takePicture; // 对takePicture动作进行继承
part f : AutoFocus {
perform takePhoto.focus;
}
part i : Imager {
perform takePhoto.shoot;
}
}
类似于 SysML1 中的动作结构分配。
赋值动作 Assignment Action
通过赋值动作,可以将一个表达式的值赋给某个特征。
action def ComputeMotion {
in attribute powerProfiles :> ISQ::power[*];
in attribute vehicleMass :> ISQ::mass;
in attribute initialPosition :> ISQ::length;
in attribute initialSpeed :> ISQ::speed;
in attribute deltaT :> ISQ::time;
out attribute positions :> IsQ::length[*] := ();
private attribute position := initialPosition;
private attribute speed := initialSpeed;
for i in 1..powerProfiles->size()-1 {
perform action dynamics : StraightLightDynamics {
in power = powerProfiles[i];
in mass = vehicleMass;
in delta_t = deltaT;
in x_in = position; in v_in = speed;
out x_out; out v_out;
}
then assign position := dynamics.x_out;
then assign speed := dynamics.v_out;
then assign positions := positions->including(position);
}
}
使用:=
操作符,可以进行初始值的赋值操作。注意和绑定操作符=
的区别。
使用assign
关键字,可以创建一个赋值动作。
其中用到的size()
和including()
函数来自于SequenceFunctions
基础库,
可以通过import SequenceFunctions::*
来使用它们。