fast-tcpn 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +674 -0
- data/README.md +453 -0
- data/fast-tcpn.gemspec +40 -0
- data/fast-tcpn.rb +192 -0
- data/lib/fast-tcpn.rb +25 -0
- data/lib/fast-tcpn/clone.rb +7 -0
- data/lib/fast-tcpn/clone/using_code_from_stack.rb +50 -0
- data/lib/fast-tcpn/clone/using_deep_clone.rb +10 -0
- data/lib/fast-tcpn/clone/using_deep_dive.rb +25 -0
- data/lib/fast-tcpn/clone/using_marshal.rb +8 -0
- data/lib/fast-tcpn/dsl.rb +177 -0
- data/lib/fast-tcpn/hash_marking.rb +220 -0
- data/lib/fast-tcpn/place.rb +70 -0
- data/lib/fast-tcpn/tcpn.rb +288 -0
- data/lib/fast-tcpn/timed_hash_marking.rb +87 -0
- data/lib/fast-tcpn/timed_place.rb +44 -0
- data/lib/fast-tcpn/timed_token.rb +27 -0
- data/lib/fast-tcpn/token.rb +30 -0
- data/lib/fast-tcpn/transition.rb +224 -0
- data/lib/fast-tcpn/version.rb +3 -0
- data/spec/callbacks_spec.rb +164 -0
- data/spec/dsl/page_spec.rb +195 -0
- data/spec/dsl/transition_spec.rb +41 -0
- data/spec/hash_marking_spec.rb +9 -0
- data/spec/place_spec.rb +10 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/support/hash_marking_shared.rb +274 -0
- data/spec/support/place_shared.rb +66 -0
- data/spec/support/token_shared.rb +27 -0
- data/spec/support/uses_temp_files.rb +31 -0
- data/spec/tcpn_binding_spec.rb +54 -0
- data/spec/tcpn_sim_spec.rb +323 -0
- data/spec/tcpn_spec.rb +150 -0
- data/spec/timed_hash_marking_spec.rb +132 -0
- data/spec/timed_place_spec.rb +38 -0
- data/spec/timed_token_spec.rb +50 -0
- data/spec/token_spec.rb +13 -0
- data/spec/transition_spec.rb +236 -0
- metadata +156 -0
data/fast-tcpn.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Used this istruction to create the gem:
|
2
|
+
# http://guides.rubygems.org/make-your-own-gem/
|
3
|
+
# some things below were based on docile gem.
|
4
|
+
|
5
|
+
$:.push File.expand_path('../lib', __FILE__)
|
6
|
+
require 'fast-tcpn/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'fast-tcpn'
|
10
|
+
s.version = FastTCPN::VERSION
|
11
|
+
s.authors = ['Wojciech Rząsa']
|
12
|
+
s.email = %w(me@wojciechrzasa.pl)
|
13
|
+
s.homepage = 'https://github.com/wrzasa/fast-tcpn'
|
14
|
+
s.summary = 'FastTCPN is Ruby based modeling and simulation tool for simulation of TCPN with convenient DSL.'
|
15
|
+
s.description = 'You can model your Timed Colored Petri Net in Ruby using convenient DSL and simulate it quite efficiently.'
|
16
|
+
s.license = 'GPL-3.0'
|
17
|
+
|
18
|
+
s.platform = 'ruby'
|
19
|
+
s.required_ruby_version = '~> 2.0'
|
20
|
+
|
21
|
+
# s.rubyforge_project = ''
|
22
|
+
|
23
|
+
s.files = `git ls-files -z`.split("\x0")
|
24
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
25
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
26
|
+
s.require_paths = %w(lib)
|
27
|
+
|
28
|
+
s.add_runtime_dependency 'docile', '~> 1.1'
|
29
|
+
|
30
|
+
# Running rspec tests from rake
|
31
|
+
s.add_development_dependency 'rspec', '~> 3.1'
|
32
|
+
s.add_development_dependency 'simplecov', '~> 0'
|
33
|
+
|
34
|
+
s.extra_rdoc_files << 'README.md'
|
35
|
+
s.rdoc_options << '--main' << 'README.md'
|
36
|
+
s.rdoc_options << '--title' << 'fast-tcpn -- Fast TCPN modeling and simulation tool'
|
37
|
+
s.rdoc_options << '--line-numbers'
|
38
|
+
s.rdoc_options << '-A'
|
39
|
+
s.rdoc_options << '-x coverage'
|
40
|
+
end
|
data/fast-tcpn.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
# WRz 2014-11-14
|
2
|
+
#
|
3
|
+
# This is a proof of concept implementation of
|
4
|
+
# fast TCPN simulator.
|
5
|
+
# Main concept if that guard is not a function
|
6
|
+
# receiving binding and answering if it is valid or not.
|
7
|
+
# Instead guard gets list of all input tokens from input
|
8
|
+
# places and returns valid bindngs. If programmer is a fool
|
9
|
+
# and guard is naive, it will be slow as it was so far. But
|
10
|
+
# generally it is possible to generate possible bindings
|
11
|
+
# using Hashes in linear time (about n*k where k is number of
|
12
|
+
# input places and n is number of tokens in each place) instead
|
13
|
+
# of n**k as in case of analysing cartesian product for traditional
|
14
|
+
# boolean guard.
|
15
|
+
#
|
16
|
+
# Since what we use instead of guard now is not precisely
|
17
|
+
# what Jensen had in mind, we call it sentry, not to confuse
|
18
|
+
# anyone.
|
19
|
+
|
20
|
+
#
|
21
|
+
# Note on cloning.
|
22
|
+
#
|
23
|
+
# Branch clone_selected_tokens assumes that only tokens
|
24
|
+
# used by the user are cloned. Here we clone whole marking
|
25
|
+
# before it can be used in guard or inscription. For well
|
26
|
+
# prepared guard, when possible small number of tokens is
|
27
|
+
# taken from marking it is faster to clone individual tokens
|
28
|
+
# (4 sec instead of 6.9 sec). But for worse guard it is faster
|
29
|
+
# to clone whole marking at the begining, then many individual
|
30
|
+
# tokens (14 sec. instead of 6.9 sec). The better guard in this
|
31
|
+
# example is when we put all processes in a Hash and try to match
|
32
|
+
# a CPU, worse guard is when we put all CPUs in a Hash and try to
|
33
|
+
# match a process. We have 10 times more CPUs the processes in this
|
34
|
+
# example, and putting a token into a Hash requires cloning it.
|
35
|
+
#
|
36
|
+
# The above times were obtained for 1000 procs, 10 cpus for each and
|
37
|
+
# ruby_deep_clone library.
|
38
|
+
#
|
39
|
+
# This library might also be worth checking:
|
40
|
+
# https://github.com/flajann2/deep_dive/blob/master/lib/deep_dive/deep_dive.rb
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# THIS IS IMPLEMENTED with selective token cloning
|
44
|
+
# Possible optimization.
|
45
|
+
# If we could define how a place stores its marking, it would make
|
46
|
+
# firing transition much faster. Ther would be no need to rewrite
|
47
|
+
# tokens from array to other structure, e.g Hash. We could e.g. define
|
48
|
+
# that processes should be stored in a Hash keyed by process name and
|
49
|
+
# thus make all guards matching by process name much faster! How to
|
50
|
+
# do this to make it sufficiently general? Different (custom)
|
51
|
+
# implementations of Marking class?
|
52
|
+
#
|
53
|
+
# HashMarking -- when creating Place define token keys that will be used
|
54
|
+
# to search tokens in different transitions, these keys will be passed to
|
55
|
+
# HashMarking object created in the Place. The HashMarking object maintains
|
56
|
+
# a Hash of tokens for each of the keys defined when creating the Place.
|
57
|
+
# Every token in the Place is put in each Hash udenr different keys.
|
58
|
+
# HashMarking exposes method named after the keys, that find token in
|
59
|
+
# appropriate Hash using given key. This way we will have possibility
|
60
|
+
# to quickly find tokens using require criterions. Adding a token to marking
|
61
|
+
# requires adding to each Hash (an probably an Array that probably should
|
62
|
+
# also be maintaned). Deleting requires removal from every Hash (but using
|
63
|
+
# the Hash'es key, thus quickly). It will be a little slower, but adding
|
64
|
+
# and deleting is much less often then finding tokens while trying to fire
|
65
|
+
# a transition. Memory oveshead will not be significant, since the Hashes
|
66
|
+
# will store only reference to a single copy of the object. The keys may
|
67
|
+
# not uniquely identify objects, so the Hashes should store arrays of
|
68
|
+
# objects matching given key. Iterators should be exposed for these arrays
|
69
|
+
# and for the whole marking. The iterators should shuffle the arrays before
|
70
|
+
# starting iteration and clone yielded objects -- anyway we assume that
|
71
|
+
# shuffling will let one take first object returned by iterator, so cloning
|
72
|
+
# will be rare.
|
73
|
+
#
|
74
|
+
# Thus we will be able to fire transition not in linear time, but in O(1)
|
75
|
+
# time at the expense of not significant memory overhead and insignificant
|
76
|
+
# complication of add/delete token operations for marking!
|
77
|
+
#
|
78
|
+
#
|
79
|
+
#
|
80
|
+
# (***) Note on efficiency (find this mark in code).
|
81
|
+
#
|
82
|
+
# calling
|
83
|
+
# marking_hash[:process].each
|
84
|
+
# without additional parameters results in need to do
|
85
|
+
# @global_list.keys in HashMarking and to generate an
|
86
|
+
# awfully large array. This is major cause that is blocking
|
87
|
+
# subsequent speedup of simulation. Unfortunately this
|
88
|
+
# array is required to enable shuffling of elements to
|
89
|
+
# ensure fair treatment of TCPN conflicts.
|
90
|
+
#
|
91
|
+
# THE BELOW IS NOT TRUE -- tested it!
|
92
|
+
# Similar situation will occcur in case of places that
|
93
|
+
# store a lot of tokens under one key -- there we also use
|
94
|
+
# Hash with tokens as keys and generate token list using
|
95
|
+
# Hash#keys method. Adding and removing tokens is faster
|
96
|
+
# in this case, especially for the large token lists.
|
97
|
+
#
|
98
|
+
#
|
99
|
+
# Not timed places causes simulator to be faster! Reason is
|
100
|
+
# that their tokens need not be considered when looking for
|
101
|
+
# next time to which simulator clock should be advanced to
|
102
|
+
# enable a transition. Thus if you have a place with a lot
|
103
|
+
# of tokens, you can gain significant speed up by making
|
104
|
+
# this place not timed (if you can).
|
105
|
+
|
106
|
+
require 'benchmark'
|
107
|
+
require 'deep_clone'
|
108
|
+
require 'fast-tcpn'
|
109
|
+
|
110
|
+
require 'ruby-prof'
|
111
|
+
|
112
|
+
|
113
|
+
AppProcess = Struct.new(:name)
|
114
|
+
CPU = Struct.new(:name, :process)
|
115
|
+
|
116
|
+
|
117
|
+
profile = false
|
118
|
+
timed = true
|
119
|
+
|
120
|
+
tcpn = FastTCPN.model do
|
121
|
+
|
122
|
+
page "Benchmark model" do
|
123
|
+
if timed
|
124
|
+
puts "Timed places"
|
125
|
+
p1 = timed_place :process, { name: :name }
|
126
|
+
cpu = timed_place :cpu, { process: :process }
|
127
|
+
p2 = timed_place :done
|
128
|
+
else
|
129
|
+
puts "Not timed places"
|
130
|
+
p1 = place :process, { name: :name }
|
131
|
+
cpu = place :cpu, { process: :process }
|
132
|
+
p2 = place :done
|
133
|
+
end
|
134
|
+
|
135
|
+
transition 'run' do
|
136
|
+
input p1
|
137
|
+
input cpu
|
138
|
+
output p2 do |binding|
|
139
|
+
binding[:process].value.name.to_s + "_done"
|
140
|
+
end
|
141
|
+
output cpu do |binding|
|
142
|
+
binding[:cpu]
|
143
|
+
end
|
144
|
+
|
145
|
+
sentry do |marking_for, clock, result|
|
146
|
+
# (***) see note on efficiency above
|
147
|
+
marking_for[:process].each do |p|
|
148
|
+
marking_for[:cpu].each(:process, p.value.name) do |c|
|
149
|
+
result << { process: p, cpu: c }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
p1 = tcpn.find_place(:process)
|
159
|
+
p2 = tcpn.find_place(:done)
|
160
|
+
cpu = tcpn.find_place(:cpu)
|
161
|
+
|
162
|
+
10_000.times do |p|
|
163
|
+
p1.add AppProcess.new(p)
|
164
|
+
10.times.map { |c| cpu.add CPU.new("CPU#{c}_#{p}", p) }
|
165
|
+
end
|
166
|
+
|
167
|
+
puts p1.marking.size
|
168
|
+
puts p2.marking.size
|
169
|
+
puts cpu.marking.size
|
170
|
+
|
171
|
+
RubyProf.start if profile
|
172
|
+
|
173
|
+
Benchmark.bm do |x|
|
174
|
+
x.report do
|
175
|
+
tcpn.sim
|
176
|
+
# {} while t.fire
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
if profile
|
182
|
+
result = RubyProf.stop
|
183
|
+
# Print a flat profile to text
|
184
|
+
printer = RubyProf::FlatPrinter.new(result)
|
185
|
+
#printer = RubyProf::FlatPrinterWithLineNumbers.new(result)
|
186
|
+
#printer = RubyProf::GraphHtmlPrinter.new(result)
|
187
|
+
printer.print(STDOUT)
|
188
|
+
end
|
189
|
+
|
190
|
+
puts p1.marking.size
|
191
|
+
puts p2.marking.size
|
192
|
+
puts cpu.marking.size
|
data/lib/fast-tcpn.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'fast-tcpn/version'
|
2
|
+
require 'fast-tcpn/place'
|
3
|
+
require 'fast-tcpn/timed_place'
|
4
|
+
require 'fast-tcpn/transition'
|
5
|
+
require 'fast-tcpn/clone'
|
6
|
+
require 'fast-tcpn/hash_marking'
|
7
|
+
require 'fast-tcpn/timed_hash_marking'
|
8
|
+
require 'fast-tcpn/token'
|
9
|
+
require 'fast-tcpn/timed_token'
|
10
|
+
require 'fast-tcpn/tcpn'
|
11
|
+
require 'fast-tcpn/dsl'
|
12
|
+
|
13
|
+
module FastTCPN
|
14
|
+
@@debug = false
|
15
|
+
|
16
|
+
# Check debugging of FastTCPN -- full backtraces from simulator
|
17
|
+
def self.debug
|
18
|
+
@@debug
|
19
|
+
end
|
20
|
+
|
21
|
+
# Turn on/off debugging of FastTCPN -- full backtraces from simulator
|
22
|
+
def self.debug=(d)
|
23
|
+
@@debug = d
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# This file should require one selected
|
2
|
+
# implementation of deep-cloning from clone/ dir.
|
3
|
+
|
4
|
+
#require 'fast-tcpn/clone/using_deep_clone'
|
5
|
+
#require 'fast-tcpn/clone/using_deep_dive'
|
6
|
+
#require 'fast-tcpn/clone/using_marshal'
|
7
|
+
require 'fast-tcpn/clone/using_code_from_stack'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
DontCloneClasses = [ Numeric, Symbol, TrueClass, FalseClass, NilClass ]
|
3
|
+
module Clone
|
4
|
+
# :nodoc:
|
5
|
+
def clone(token)
|
6
|
+
token.deep_clone
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# http://stackoverflow.com/questions/8206523/how-to-create-a-deep-copy-of-an-object-in-ruby
|
12
|
+
class Object
|
13
|
+
def deep_clone
|
14
|
+
return @deep_cloning_obj if @deep_cloning
|
15
|
+
return @deep_cloning_obj if frozen?
|
16
|
+
@deep_cloning_obj = clone
|
17
|
+
@deep_cloning_obj.instance_variables.each do |var|
|
18
|
+
val = @deep_cloning_obj.instance_variable_get(var)
|
19
|
+
begin
|
20
|
+
@deep_cloning = true
|
21
|
+
val = val.deep_clone
|
22
|
+
# silent rescue is never a good idea...
|
23
|
+
#rescue TypeError
|
24
|
+
# next
|
25
|
+
ensure
|
26
|
+
# better not to litter original object with
|
27
|
+
# temporary instance variables
|
28
|
+
#@deep_cloning = false
|
29
|
+
remove_instance_variable :@deep_cloning
|
30
|
+
end
|
31
|
+
@deep_cloning_obj.instance_variable_set(var, val)
|
32
|
+
end
|
33
|
+
deep_cloning_obj = @deep_cloning_obj
|
34
|
+
@deep_cloning_obj = nil
|
35
|
+
# better not to litter original object with
|
36
|
+
# temporary instance variables
|
37
|
+
remove_instance_variable :@deep_cloning_obj
|
38
|
+
deep_cloning_obj
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
FastTCPN::DontCloneClasses.each do |klazz|
|
43
|
+
klazz.class_eval do
|
44
|
+
def deep_clone
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'deep_dive'
|
2
|
+
|
3
|
+
[ Fixnum, Symbol, TrueClass, FalseClass ].each do |klazz|
|
4
|
+
klazz.class_eval do
|
5
|
+
def clone
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Object
|
12
|
+
include DeepDive
|
13
|
+
exclude do |sym, obj|
|
14
|
+
obj.kind_of? Symbol
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module FastTCPN
|
19
|
+
module Clone
|
20
|
+
# :nodoc:
|
21
|
+
def clone(token)
|
22
|
+
token.dclone
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'docile'
|
2
|
+
|
3
|
+
module FastTCPN
|
4
|
+
# Allows to create new TCPN model by interpreting passed block of code.
|
5
|
+
# If you read this code from a file, pass it's name as a parameter, it
|
6
|
+
# will be used in exceptions.
|
7
|
+
def self.model(file = nil, &block)
|
8
|
+
tcpn = TCPN.new
|
9
|
+
load_model tcpn, file, &block
|
10
|
+
tcpn
|
11
|
+
end
|
12
|
+
|
13
|
+
# Read TCPN model from a +file+.
|
14
|
+
def self.read(file)
|
15
|
+
block = instance_eval "proc { #{File.read file} }", file
|
16
|
+
model file, &block
|
17
|
+
end
|
18
|
+
|
19
|
+
# For interal use. Use #read od #model.
|
20
|
+
def self.load_model(tcpn, file, &block)
|
21
|
+
Docile.dsl_eval(DSL::TCPNDSL.new(tcpn, file), &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# This module implements DSL to easily create TCPN models.
|
25
|
+
# The model consists of pages that have places and transitions.
|
26
|
+
# Places are repsesented by their names and the same place can
|
27
|
+
# appear on numerous pages. Every page can consist of subsequent
|
28
|
+
# pages. Pages can be loaded to the model from other files.
|
29
|
+
#
|
30
|
+
# DSL models can be loaded from external files using FastTCPN.read method
|
31
|
+
# or interpreted directly from core using FastTCPN.model method. Both methods
|
32
|
+
# return created model as TCPN object. You can then use TCPN class API to set
|
33
|
+
# markings, test markings, define callbacks and run simulation.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
#
|
37
|
+
# File: model/top.rb
|
38
|
+
# page "top" do
|
39
|
+
# sub_page "process.rb"
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# File: model/process.rb
|
43
|
+
# page "process" do
|
44
|
+
# process = timed_place :process, { name: :name }
|
45
|
+
# done = timed_place :done { name: :name }
|
46
|
+
#
|
47
|
+
# transition "work" do
|
48
|
+
# input process
|
49
|
+
# output done do |binding, clock|
|
50
|
+
# binding[:process].with_time clock + 100
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# Calling TCPN.read 'model/top.rb' will load whole model and return as TCPN object.
|
56
|
+
module DSL
|
57
|
+
|
58
|
+
# Represents and encapsulates all errors that will occur while
|
59
|
+
# running DSL.
|
60
|
+
class DSLError < RuntimeError
|
61
|
+
attr_reader :cause, :page, :files
|
62
|
+
|
63
|
+
def initialize(cause, page, file = nil)
|
64
|
+
super cause
|
65
|
+
@cause, @page = cause, page
|
66
|
+
if @cause.respond_to? :full_backtrace
|
67
|
+
set_backtrace @cause.full_backtrace
|
68
|
+
else
|
69
|
+
set_backtrace @cause.backtrace
|
70
|
+
end
|
71
|
+
@files = []
|
72
|
+
@files += @cause.files if @cause.respond_to? :files
|
73
|
+
@files << file
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
"<#{self.class} #{@cause.inspect} on TCPN page: `#{@page}`>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def message
|
81
|
+
"#{@cause.message} on TCPN page: `#{@page}`"
|
82
|
+
end
|
83
|
+
|
84
|
+
alias full_backtrace backtrace
|
85
|
+
|
86
|
+
def backtrace
|
87
|
+
return full_backtrace if @files.empty?
|
88
|
+
full_backtrace.select do |b|
|
89
|
+
not @files.select { |f| b =~ /#{f}/ }.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class TCPNDSL
|
95
|
+
def initialize(tcpn, file)
|
96
|
+
@tcpn = tcpn
|
97
|
+
@file = file
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define a page of TCPN model
|
101
|
+
def page(name, &block)
|
102
|
+
Docile.dsl_eval(PageDSL.new(@tcpn, self, @file), &block)
|
103
|
+
rescue StandardError => e
|
104
|
+
raise DSLError.new e, name, @file
|
105
|
+
rescue SyntaxError => e
|
106
|
+
raise DSLError.new e, name, @file
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class PageDSL
|
111
|
+
def initialize(tcpn, dsl, file)
|
112
|
+
@tcpn = tcpn
|
113
|
+
@dsl = dsl
|
114
|
+
@file = file
|
115
|
+
end
|
116
|
+
|
117
|
+
# Create and return a new place (not timed). If a place with this
|
118
|
+
# name exists somewhere in the model (e.g. on other pages), and object
|
119
|
+
# representing exisiting place will be returned. Keys of both keys will
|
120
|
+
# be merged.
|
121
|
+
def place(name, keys = {})
|
122
|
+
@tcpn.place name, keys
|
123
|
+
end
|
124
|
+
|
125
|
+
# Create and return a new timed place.
|
126
|
+
def timed_place(name, keys = {})
|
127
|
+
@tcpn.timed_place name, keys
|
128
|
+
end
|
129
|
+
|
130
|
+
# Create and return new transition for the TCPN. Block
|
131
|
+
# defines transitions inputs, outputs and sentry.
|
132
|
+
def transition(name, &block)
|
133
|
+
transition = @tcpn.transition name
|
134
|
+
Docile.dsl_eval(TransitionDSL.new(transition), &block)
|
135
|
+
transition
|
136
|
+
end
|
137
|
+
|
138
|
+
# define new TCPN (sub) page on this page.
|
139
|
+
def page(name, &block)
|
140
|
+
@dsl.page name, &block
|
141
|
+
end
|
142
|
+
|
143
|
+
# load TCPN sub page from a file. File location is relative to
|
144
|
+
# the location of currently interpreted file (if known)
|
145
|
+
def sub_page(file)
|
146
|
+
file = File.expand_path(file, File.dirname(@file)) unless @file.nil?
|
147
|
+
block = instance_eval "proc { #{File.read file} }", file
|
148
|
+
FastTCPN.load_model @tcpn, file, &block
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class TransitionDSL
|
153
|
+
def initialize(transition)
|
154
|
+
@transition = transition
|
155
|
+
end
|
156
|
+
|
157
|
+
# Defines input place for this transition. +place+ must an
|
158
|
+
# object returned by +place+ ot +timed_place+ statement.
|
159
|
+
def input(place)
|
160
|
+
@transition.input place
|
161
|
+
end
|
162
|
+
|
163
|
+
# Defines output place for this transition. +place+ must an
|
164
|
+
# object returned by +place+ ot +timed_place+ statement.
|
165
|
+
#
|
166
|
+
def output(place, &block)
|
167
|
+
@transition.output place, &block
|
168
|
+
end
|
169
|
+
|
170
|
+
# Defines sentry for this transition.
|
171
|
+
def sentry(&block)
|
172
|
+
@transition.sentry &block
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|