BIP语言教程(一)

目前国内尚没有与BIP语言相关的中文资料,因此我决定将BIP官网的教程翻译成中文,供大家学习参考。我接触BIP语言的时间也不长,可能还存在一些理解偏差,如有错误请指出,谢谢!

原文链接:http://www-verimag.imag.fr/TOOLS/DCS/bip/doc/latest/html/tutorial.html

以下章节使用了一些简单的例子来指导大家如何使用BIP语言。第一部分给出了BIP语言基础用法的一些示例。第二部分用一些可以运行的示例演示了如何通过BIP引擎让BIP语言与C++进行交互。

重要:所有本章节中的例子都可以在网上下载:http://www-verimag.imag.fr/TOOLS/DCS/bip/doc/examples/。每一个例子都有一个build.sh脚本,可以用来编译对应的例子。还有个build_all.sh脚本可以用来一次性编译所有例子。

Hello world

下面我们先从一个简单的例子开始,在一个HelloPackage.bip文件中,输入以下代码:
package HelloPackage
  port type HelloPort_t()

  atom type HelloAtom()
    port HelloPort_t p()
    place START,END
    initial to START
    on p from START to END
  end

  compound type HelloCompound()
    component HelloAtom c1()
  end
end

这个包包含了三种类型:
  • 一个没有参数的端口类型HelloPort_t
  • 一个原子类型HelloAtom,包括:
一个HelloPort_t类型的内部端口声明p
两个状态:START,初始状态,和END
一个p端口上的迁移从START到END
  • 一个复合类型HelloCompound,包括:
一个HelloAtom类型的构件声明c1
如果我们把HelloCompound类型的构件作为系统的根(译者注:类似main函数),那么p端口上的迁移被执行之后,原子构件c1从START切换到END,系统将会进入死锁。

下面我们通过这个简单的例子来演示如何通过BIP引擎来运行BIP代码。在这个例子中,我们将BIP语言编译并转换成C++(还可以转换成别的语言,但是目前只支持C++)。
用以下命令来将通过BIP引擎将BIP语言编译链接,并转换成C++:
$ bipc.sh -I . -p HelloPackage -d "HelloCompound()"\
  --gencpp-output output
$ mkdir output/build
$ cd output/build
$ cmake ..
[...]
$ make
[...]

最后,我们来运行生成的可执行文件system:
$ ./system
...
[BIP ENGINE]: initialize components...
[BIP ENGINE]: state #0: 1 internal port:
[BIP ENGINE]:   [0] ROOT.c1._iport_decl__p
[BIP ENGINE]:  -> choose [0] ROOT.c1._iport_decl__p
[BIP ENGINE]: state #1: deadlock!

在唯一的迁移触发之后,和我们预计的一样,系统进入了死锁。

使用BIP2中的交互来让各个构件进行同步

各个构件之间的强同步

下面我们来稍微修改一下之前的Hello World例子。我们再增加两个HelloAtom原子类型的实例,并强制他们进行迁移同步:
@cpp(include="stdio.h")
package HelloPackage
  port type HelloPort_t()

  atom type HelloAtom(int id)
    export port HelloPort_t p()
    place START,END
    initial to START
    on p from START to END do {printf("Hello World from %d\n", id);}
  end

  connector type ThreeRendezVous(HelloPort_t p1, HelloPort_t p2, HelloPort_t p3)
    define p1 p2 p3
  end

  compound type HelloCompound()
    component HelloAtom c1(1), c2(2), c3(3)
    connector ThreeRendezVous connect(c1.p, c2.p, c3.p)
  end
end

@cpp()符号我们之后会详细说明,它的作用是让我们可以使用标准C库中的printf()函数。在这个例子中,我们增加了一个连接子类型ThreeRendezVous,其中包括三个HelloPort_t类型的参数,以及一个同步三个端口的交互。

使用下面的命令来通过BIP引擎编译链接生成C++代码:
$ bipc.sh -I . -p HelloPackage -d "HelloCompound()"\
  --gencpp-output output
$ mkdir output/build
$ cd output/build
$ cmake ..
[...]
$ make
[...]

运行可执行文件system之后,我们会发现三个原子构件的迁移被同时触发了。三个原子构件以一个随机的顺序相继被执行:
...
[BIP ENGINE]: initialize components...
[BIP ENGINE]: state #0: 1 interaction:
[BIP ENGINE]:   [0] ROOT.connect: ROOT.c1.p() ROOT.c2.p() ROOT.c3.p()
[BIP ENGINE]: -> choose [0] ROOT.connect: ROOT.c1.p() ROOT.c2.p() ROOT.c3.p()
Hello World from 1
Hello World from 2
Hello World from 3
[BIP ENGINE]: state #1: deadlock!

向几个构件广播数据

现在我们继续来修改上面这个例子,让它同时拥有一个sender构件和三个receiver构件。sender构件用于广播一个代表它id的整型变量到其他三个receiver构件。相应的BIP2代码如下:
@cpp(include="stdio.h")
package HelloPackage
  port type HelloPort_t(int d)

  atom type HelloSender(int id)
    data int myd
    export port HelloPort_t p(myd)

    place START, END

    initial to START do { myd = id; }

    on p from START to END
      do { printf("I'm %d, sending Hello World....\n", myd); }
  end

  atom type HelloReceiver(int id)
    data int myd
    export port HelloPort_t sync(myd)

    place START,END

    initial to START

    on sync from START to END
      provided (id == 1 || id == 3)
      do { printf("I'm %d, Hello World received from %d\n", id, myd); }
  end

  connector type OneToThree(HelloPort_t s, HelloPort_t r1, HelloPort_t r2, HelloPort_t r3)
    define s' r1 r2 r3

    on s r1 r2 r3 down { r1.d = s.d; r2.d = s.d; r3.d = s.d; }
    on s r1 r2    down { r1.d = s.d; r2.d = s.d;             }
    on s r1    r3 down { r1.d = s.d;             r3.d = s.d; }
    on s    r2 r3 down {             r2.d = s.d; r3.d = s.d; }
    on s r1       down { r1.d = s.d;                         }
    on s    r2    down {             r2.d = s.d;             }
    on s       r3 down {                         r3.d = s.d; }
  end

  compound type HelloCompound()
    component HelloSender s(0)
    component HelloReceiver r1(1), r2(2), r3(3)
    connector OneToThree brd(s.p, r1.sync, r2.sync, r3.sync)
  end
end

在连接子类型OneToThree中,对应于sender的端口s是触发器。它可以被单独执行,而不需要与其他构件保持同步。由于其他构件都是同步的,OneToThree连接子定义了以下交互:"s", "s,r1", "s,r2", "s,r3", "s,r1,r2", "s,r1,r3", "s,r2,r3"和"s,r1,r2,r3"。

为了实现从端口s广播数据,我们使用了一系列on语句,其中包括down代码块,它们定义了所有receiver中涉及到的所有交互。需要注意的是虽然交互s并没有包含在列表中,但是它仍被认为是一个可能发生的交互,只是在s单独执行时没有发生数据迁移。

由于我们在sync迁移中加上了约束条件,只有以下交互在初始迁移被执行之后才会被触发:"s", "s,r1", "s,r3"和"s,r1,r3"。然而正如我们在Priorities中解释的那样,BIP2语言的默认优先级是执行端口数量最多的交互,所以"s,r1,r3"交互会被执行:
...
[BIP ENGINE]: initialize components...
[BIP ENGINE]: state #0: 1 interaction:
[BIP ENGINE]:   [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.r1.p({d}=0;) ROOT.r3.p({d}=0;)
[BIP ENGINE]: -> choose [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.r1.p({d}=0;) ROOT.r3.p({d}=0;)
I'm 0, sending Hello World....
I'm 1, Hello World received from 0
I'm 3, Hello World received from 0
[BIP ENGINE]: state #1: deadlock!


广播可以用一个连接子(左图),也可以用两个层次的连接子(右图)。

使用层次连接子可以实现同样的效果。在这个例子中,三个receiver被一个SyncReceivers类型的sync连接子同步。sync连接子允许三个receiver中的任意子集参与到广播中。层次连接子是在sync的基础上建立的。所以我们需要在sender和sync的外部端口之间增加一个广播。在以下代码中我们省略了HelloPort_t,HelloSender和HelloReceiver的定义,由于它们和上一个例子中的定义相同。
@cpp(include="stdio.h")
package HelloPackage
  // [...] definitions of HelloPort_t, HelloSender and HelloReceiver

  connector type SyncRecvs(HelloPort_t r1, HelloPort_t r2, HelloPort_t r3)
    data int d
    export port HelloPort_t ep(d)
    define r1' r2' r3'

    on r1 r2 r3 down { r1.d = d; r2.d = d; r3.d = d; }
    on r1 r2    down { r1.d = d; r2.d = d;           }
    on r1    r3 down { r1.d = d;           r3.d = d; }
    on    r2 r3 down {           r2.d = d; r3.d = d; }
    on r1       down { r1.d = d;                     }
    on    r2    down {           r2.d = d;           }
    on       r3 down {                     r3.d = d; }
  end

  connector type OneToOne(HelloPort_t s, HelloPort_t c)
    define s' c
    on s c down { c.d = s.d; }
  end

  compound type HelloCompound()
    component HelloSender s(0)
    component HelloReceiver r1(1), r2(2), r3(3)
    connector SyncRecvs sync(r1.p, r2.p, r3.p)
    connector OneToOne brd(s.p, sync.ep)
  end
end

这个层次连接子由brd和sync连接子组成。下面我们来说明一下它们之间的交互。首先,会计算出所有sync中可以进行的交互,它们是"r1", "r3"和"r1, r3"。然后,根据这三个交互我们可以计算出brd中可以进行的交互:"s", "s,r1", "s,r3"和"s, r1, r3"。应用最大端口数的默认优先级,可以得到以下执行结果:
...
[BIP ENGINE]: initialize components...
[BIP ENGINE]: state #0: 1 interaction:
[BIP ENGINE]:   [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.sync.ep({d}=135026452;)
[BIP ENGINE]: -> choose [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.sync.ep({d}=135026452;)
I'm 0, sending Hello World....
I'm 1, Hello World received from 0
I'm 3, Hello World received from 0
[BIP ENGINE]: state #1: deadlock!

将构件包含在一个复合构件中

假如我们想将上一个例子中提到的三个receiver包含到一个复合构件中,同时还要保证执行结果相同,那应该怎么做呢?事实上我们仅需要创建一个包含三个receiver的复合构件,一个负责同步它们的连接子,以及一个与外部交互的端口即可:
@cpp(include="stdio.h")
package HelloPackage
  // [...] definitions of HelloPort_t, HelloSender, HelloReceiver,
  // SyncReceivers and OneToOne

  compound type RecvsCompound()
    component HelloReceiver c1(1), c2(2), c3(3)
    connector SyncRecvs sync(c1.p, c2.p, c3.p)

    export port sync.ep as p
  end

  compound type HelloCompound()
    component HelloSender s(0)
    component RecvsCompound rcvrs()

    connector OneToOne brd(s.p, rcvrs.p)
  end
end

在这个例子中,我们可以得到一个与上一个例子相同的结果:
...
[BIP ENGINE]: initialize components...
[BIP ENGINE]: state #0: 1 interaction:
[BIP ENGINE]:   [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.rcvrs.p({d}=135034644;)
[BIP ENGINE]: -> choose [0] ROOT.brd: ROOT.s.p({d}=0;) ROOT.rcvrs.p({d}=135034644;)
I'm 0, sending Hello World....
I'm 1, Hello World received from 0
I'm 3, Hello World received from 0
[BIP ENGINE]: state #1: deadlock!


HelloCompund实例中的结构

需要注意的是,在上面这个例子中,只有端口数最多的交互才能与brd通信,这是由于复合构件的外部端口也会使用默认优先级。最终的结果会与使用层次连接子但没有把三个receiver包含到一个复合构件中的结果相同。但一般来说并不是这样,原因如下:

重要:当在连接子中定义了约束条件后,将若干构件和连接子包含到一个复合构件中得到的结果,有可能会和不包含的结果不同。这是由于当连接子的端口被导出成为复合构件的端口时,连接子中的交互会应用默认优先级,只有端口数量最多的交互才可以在对应的复合构件的端口中触发。

这个执行过程在数据操作的部分还有一个有趣的地方,在输出结果的第四行我们可以看到:
ROOT.rcvrs.p({d}=135038644;)

这个数字135038644说明了相应的变量从未被初始化。同时,编译器也给出了我们一些警告,例如:
[WARNING] In path/to/HelloPackage.bip:
'up' maybe missing: data associated with exported port won't be "fresh" :
    70:
    71:     on r1 r2
------------^
    72:       down {
    73:         r1.d = cd;

需要注意的是,这仅仅是一个警告,而不是一个必须修复的错误。在这个例子中,up{}语句被忽略是没有影响的,即时有一个包含变量的导出端口。只要与该导出端口绑定的实例在up{}语句块中不访问这个变量就不会有问题。在引擎中仍然会显示这个变量的值,在这里并没有参考价值。

提示:正如在其他编程语言中一样,你应该避免使用未初始化的变量,否则会导致潜在的错误,并且很难被发现。

转载请注明出处,谢谢!
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值