rachinations 0.0.3 → 0.0.4
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 +4 -4
- data/Gemfile +1 -16
- data/Gemfile.lock +2 -0
- data/lib/rachinations/domain/diagrams/diagram.rb +46 -14
- data/lib/rachinations/domain/edges/edge.rb +34 -20
- data/lib/rachinations/domain/modules/common/hash_init.rb +2 -1
- data/lib/rachinations/domain/modules/common/invariant.rb +2 -2
- data/lib/rachinations/domain/modules/common/refiners/number_modifiers.rb +22 -0
- data/lib/rachinations/domain/nodes/converter.rb +5 -5
- data/lib/rachinations/domain/nodes/gate.rb +77 -0
- data/lib/rachinations/domain/nodes/node.rb +69 -36
- data/lib/rachinations/domain/nodes/pool.rb +121 -74
- data/lib/rachinations/domain/nodes/resourceful_node.rb +0 -1
- data/lib/rachinations/domain/nodes/sink.rb +3 -0
- data/lib/rachinations/domain/nodes/source.rb +3 -2
- data/lib/rachinations/domain/resource_bag.rb +3 -4
- data/lib/rachinations/dsl/bad_dsl.rb +2 -0
- data/lib/rachinations/dsl/bootstrap.rb +59 -0
- data/lib/rachinations/dsl/diagram_shorthand_methods.rb +107 -0
- data/lib/rachinations/dsl/helpers/parser.rb +170 -0
- data/lib/rachinations/extras/constant_hash.rb +25 -0
- data/lib/rachinations/extras/fifo.rb +25 -19
- data/lib/rachinations/helpers/edge_helper.rb +40 -0
- data/lib/rachinations/utils/string_helper.rb +7 -0
- data/lib/rachinations/version.rb +1 -1
- data/lib/rachinations.rb +13 -5
- data/rachinations.gemspec +3 -2
- data/testing/simulations/modelo1.rb +1 -1
- data/testing/simulations/sequencial.rb +1 -1
- data/testing/simulations/sobonito.rb +1 -1
- data/testing/simulations/sobonitowhile.rb +1 -1
- data/testing/simulations/whatIwish1.rb +2 -2
- data/testing/spec/canon/conditions_spec.rb +3 -4
- data/testing/spec/converter_spec.rb +3 -4
- data/testing/spec/diagram_spec.rb +293 -238
- data/testing/spec/edge_spec.rb +28 -14
- data/testing/spec/gate_spec.rb +34 -0
- data/testing/spec/pool_spec.rb +8 -10
- data/testing/spec/release1/dsl_spec.rb +204 -0
- data/testing/spec/spec_helper.rb +1 -0
- data/testing/spec/xexeo_spec.rb +39 -40
- metadata +30 -8
- data/lib/rachinations/domain/edges/random_edge.rb +0 -4
- data/lib/rachinations/dsl/dsl.rb +0 -63
- data/testing/spec/canon/trigger_spec.rb +0 -128
@@ -4,12 +4,14 @@ require_relative '../../domain/resources/token'
|
|
4
4
|
require_relative '../resource_bag'
|
5
5
|
require_relative '../../domain/exceptions/no_elements_matching_condition_error'
|
6
6
|
require_relative '../../domain/modules/common/refiners/proc_convenience_methods'
|
7
|
-
|
7
|
+
require_relative '../../../../lib/rachinations/helpers/edge_helper'
|
8
8
|
|
9
9
|
using ProcConvenienceMethods
|
10
10
|
|
11
11
|
class Pool < ResourcefulNode
|
12
12
|
|
13
|
+
Helper = Helpers::EdgeHelper
|
14
|
+
|
13
15
|
def initialize(hsh={})
|
14
16
|
|
15
17
|
check_options!(hsh)
|
@@ -17,7 +19,8 @@ class Pool < ResourcefulNode
|
|
17
19
|
|
18
20
|
@resources = get_initial_resources(params[:initial_value])
|
19
21
|
|
20
|
-
@types = get_types(params[:initial_value],
|
22
|
+
@types = get_types(initial_value: params[:initial_value],
|
23
|
+
given_types: params[:types])
|
21
24
|
|
22
25
|
#reference to the underlying diagram
|
23
26
|
@diagram = params[:diagram]
|
@@ -47,16 +50,25 @@ class Pool < ResourcefulNode
|
|
47
50
|
|
48
51
|
pull_any!
|
49
52
|
|
50
|
-
|
53
|
+
elsif push? && all?
|
54
|
+
|
55
|
+
push_all!
|
56
|
+
|
57
|
+
elsif pull? && all?
|
58
|
+
|
59
|
+
pull_all!
|
51
60
|
|
61
|
+
else
|
62
|
+
|
63
|
+
end
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
|
-
def resource_count(type
|
67
|
+
def resource_count(type: nil, expr: nil)
|
56
68
|
|
57
|
-
raise ArgumentError.new('Please provide either a type or a block, but not both.') if
|
69
|
+
raise ArgumentError.new('Please provide either a type or a block, but not both.') if !expr.nil? && !type.nil?
|
58
70
|
|
59
|
-
if type.nil? &&
|
71
|
+
if type.nil? && expr.nil?
|
60
72
|
resources.count_where { |r| r.unlocked? }
|
61
73
|
elsif type.is_a?(Class) && type <= Token
|
62
74
|
|
@@ -67,12 +79,12 @@ class Pool < ResourcefulNode
|
|
67
79
|
else
|
68
80
|
raise UnsupportedTypeError.new "Unsupported type: #{type.name}"
|
69
81
|
end
|
70
|
-
elsif
|
82
|
+
elsif !expr.nil?
|
71
83
|
|
72
84
|
# client doesn't need to know about locked vs unlocked resources
|
73
|
-
unlock_condition =
|
85
|
+
unlock_condition = proc { |r| r.unlocked? }
|
74
86
|
|
75
|
-
resources.count_where { |r| unlock_condition.match?(r) &&
|
87
|
+
resources.count_where { |r| unlock_condition.match?(r) && expr.match?(r) }
|
76
88
|
|
77
89
|
else
|
78
90
|
raise ArgumentError.new("Wrong parameter types passed to #{__callee__}")
|
@@ -117,16 +129,16 @@ class Pool < ResourcefulNode
|
|
117
129
|
end
|
118
130
|
end
|
119
131
|
|
120
|
-
def take_resource!(
|
132
|
+
def take_resource!(expression=nil)
|
121
133
|
|
122
|
-
|
134
|
+
if expression.nil?
|
123
135
|
# if no conditions given, then anything goes.
|
124
|
-
expression = Proc.new{ |res| true }
|
136
|
+
expression = Proc.new { |res| true }
|
125
137
|
end
|
126
138
|
|
127
139
|
raise RuntimeError.new unless resources.count_where(&expression) > 0
|
128
140
|
|
129
|
-
res=remove_resource!
|
141
|
+
res=remove_resource! expression
|
130
142
|
|
131
143
|
fire_triggers!
|
132
144
|
|
@@ -135,7 +147,7 @@ class Pool < ResourcefulNode
|
|
135
147
|
end
|
136
148
|
|
137
149
|
def to_s
|
138
|
-
"Pool '#{@name}':
|
150
|
+
"Pool '#{@name}': #{resources} "
|
139
151
|
end
|
140
152
|
|
141
153
|
# TODO this smells. where is this used? can i do without it?
|
@@ -156,25 +168,6 @@ class Pool < ResourcefulNode
|
|
156
168
|
|
157
169
|
end
|
158
170
|
|
159
|
-
# TODO document this or else refactor it out
|
160
|
-
def get_types(initial_value, given_types)
|
161
|
-
inv { !self.instance_variable_defined?(:@types) }
|
162
|
-
|
163
|
-
if initial_value.is_a?(Fixnum) && given_types.empty?
|
164
|
-
# nothing to do
|
165
|
-
elsif initial_value == 0 && !given_types.empty?
|
166
|
-
# nothing to do
|
167
|
-
elsif initial_value.is_a?(Hash)
|
168
|
-
initial_value.each_key { |type| given_types.push(type) }
|
169
|
-
else
|
170
|
-
raise ArgumentError.new
|
171
|
-
end
|
172
|
-
|
173
|
-
given_types.uniq
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
|
178
171
|
def types
|
179
172
|
@types
|
180
173
|
end
|
@@ -183,16 +176,28 @@ class Pool < ResourcefulNode
|
|
183
176
|
|
184
177
|
attr_reader :resources
|
185
178
|
|
186
|
-
|
179
|
+
# Tries to remove a Resource that matches given expression from this node.
|
180
|
+
# @param [Proc] expression the expression to match against Resources
|
181
|
+
# @raise [RuntimeError] In case it was not possible to remove a Resource
|
182
|
+
def remove_resource!(expression)
|
187
183
|
|
188
184
|
# client doesn't need to know about locked vs unlocked resources
|
189
185
|
unlocked_expression = Proc.new { |r| r.unlocked? }
|
190
186
|
|
191
|
-
|
187
|
+
begin
|
188
|
+
# the original expression plus the expression that specifies that only unlocked
|
189
|
+
# resources may be retrieved
|
190
|
+
res=resources.get_where { |r| unlocked_expression.call(r) && expression.call(r) }
|
191
|
+
rescue ArgumentError
|
192
|
+
raise RuntimeError, 'wrong arguments'
|
193
|
+
rescue NoElementsMatchingConditionError
|
194
|
+
raise RuntimeError, 'no elements matching'
|
195
|
+
else
|
196
|
+
@resources_removed[res.class] += 1
|
197
|
+
res
|
198
|
+
end
|
192
199
|
|
193
|
-
@resources_removed[res.class] += 1
|
194
200
|
|
195
|
-
res
|
196
201
|
end
|
197
202
|
|
198
203
|
def add_resource!(res)
|
@@ -201,43 +206,22 @@ class Pool < ResourcefulNode
|
|
201
206
|
|
202
207
|
end
|
203
208
|
|
204
|
-
def options
|
205
|
-
[:conditions, :name, :activation, :mode, :types, :initial_value, :diagram]
|
206
|
-
end
|
207
|
-
|
208
|
-
def defaults
|
209
|
-
{
|
210
|
-
activation: :passive,
|
211
|
-
mode: :pull_any,
|
212
|
-
types: [],
|
213
|
-
initial_value: 0
|
214
|
-
}
|
215
|
-
end
|
216
|
-
|
217
|
-
def aliases
|
218
|
-
{:initial_values => :initial_value}
|
219
|
-
end
|
220
|
-
|
221
209
|
def push_any!
|
222
210
|
|
223
211
|
outgoing_edges.shuffle.each do |edge|
|
224
|
-
|
225
|
-
|
226
|
-
rescue => ex
|
227
|
-
# Could not get a block for one Edge, but this is push_any so I'll go ahead.
|
228
|
-
next #other edges might still be able to serve me.
|
229
|
-
end
|
212
|
+
|
213
|
+
blk = edge.push_expression
|
230
214
|
|
231
215
|
edge.label.times do
|
232
216
|
begin
|
233
|
-
res = remove_resource!(
|
234
|
-
rescue
|
235
|
-
|
217
|
+
res = remove_resource!(blk)
|
218
|
+
rescue RuntimeError
|
219
|
+
# Failed to remove this resource. Let's try another Edge, perhaps?
|
236
220
|
break
|
221
|
+
else
|
222
|
+
edge.push!(res)
|
237
223
|
end
|
238
224
|
|
239
|
-
edge.push!(res)
|
240
|
-
|
241
225
|
end
|
242
226
|
|
243
227
|
end
|
@@ -245,26 +229,89 @@ class Pool < ResourcefulNode
|
|
245
229
|
|
246
230
|
def pull_any!
|
247
231
|
incoming_edges.shuffle.each do |edge|
|
248
|
-
|
249
|
-
|
250
|
-
rescue RuntimeError => ex
|
251
|
-
# Could not get a block for one Edge, but this is pull_any so I'll go ahead.
|
252
|
-
next #other edges might still be able to serve me.
|
253
|
-
end
|
232
|
+
|
233
|
+
blk = edge.pull_expression
|
254
234
|
|
255
235
|
edge.label.times do
|
256
236
|
begin
|
257
|
-
res = edge.pull!(
|
258
|
-
rescue RuntimeError
|
237
|
+
res = edge.pull!(blk)
|
238
|
+
rescue RuntimeError
|
259
239
|
# Let's try another Edge, perhaps?
|
260
240
|
break
|
241
|
+
else
|
242
|
+
add_resource!(res.lock!)
|
261
243
|
end
|
262
244
|
|
263
|
-
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def pull_all!
|
251
|
+
|
252
|
+
enabled_incoming_edges = incoming_edges.select { |edge| edge.enabled? }
|
253
|
+
|
254
|
+
if enabled_incoming_edges.all? { |edge| edge.test_ping? }
|
255
|
+
|
256
|
+
enabled_incoming_edges.each do |edge|
|
257
|
+
blk = edge.pull_expression
|
258
|
+
|
259
|
+
edge.label.times do
|
260
|
+
res = edge.pull!(blk)
|
261
|
+
|
262
|
+
add_resource!(res.lock!)
|
263
|
+
|
264
|
+
end
|
264
265
|
|
265
266
|
end
|
267
|
+
fire_triggers!
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
def push_all!
|
273
|
+
|
274
|
+
enabled_outgoing_edges = outgoing_edges.select { |edge| edge.enabled? }
|
275
|
+
|
276
|
+
|
277
|
+
if Helper.all_can_push?(edges, require_all: true)
|
278
|
+
|
279
|
+
enabled_outgoing_edges.each do |edge|
|
280
|
+
|
281
|
+
expression = edge.push_expression
|
282
|
+
|
283
|
+
edge.label.times do
|
284
|
+
|
285
|
+
res = remove_resource!(expression)
|
286
|
+
|
287
|
+
edge.push!(res)
|
288
|
+
|
289
|
+
end
|
266
290
|
|
291
|
+
end
|
292
|
+
fire_triggers!
|
267
293
|
end
|
294
|
+
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
def options
|
299
|
+
[:conditions, :name, :activation, :mode, :types, :initial_value, :diagram]
|
300
|
+
end
|
301
|
+
|
302
|
+
def defaults
|
303
|
+
{
|
304
|
+
activation: :passive,
|
305
|
+
mode: :pull_any,
|
306
|
+
types: [],
|
307
|
+
initial_value: 0
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
def aliases
|
312
|
+
{
|
313
|
+
:initial_values => :initial_value
|
314
|
+
}
|
268
315
|
end
|
269
316
|
|
270
317
|
end
|
@@ -28,7 +28,7 @@ class Source < ResourcefulNode
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def to_s
|
31
|
-
"Source '#{@name}'
|
31
|
+
"Source '#{@name}'\n\n"
|
32
32
|
end
|
33
33
|
|
34
34
|
def put_resource!;
|
@@ -98,7 +98,7 @@ class Source < ResourcefulNode
|
|
98
98
|
|
99
99
|
#we'll need to change this if source starts accepting
|
100
100
|
# more than a single type
|
101
|
-
inv { types.size === 1 }
|
101
|
+
inv("Sources can only have a single type") { types.size === 1 }
|
102
102
|
|
103
103
|
if type.nil?
|
104
104
|
res = Token.new
|
@@ -144,6 +144,7 @@ class Source < ResourcefulNode
|
|
144
144
|
end
|
145
145
|
|
146
146
|
end
|
147
|
+
fire_triggers!
|
147
148
|
end
|
148
149
|
|
149
150
|
def options
|
@@ -82,16 +82,15 @@ class ResourceBag
|
|
82
82
|
unlocked = count_where{|r| (r.is_type? klass) && (r.unlocked?) }
|
83
83
|
locked = count_where{|r| (r.is_type? klass) && (r.locked?) }
|
84
84
|
|
85
|
-
out += "
|
85
|
+
out += "#{name} -> #{unlocked} (#{locked}) \n"
|
86
86
|
end
|
87
87
|
|
88
88
|
if classes.empty?
|
89
|
-
"
|
89
|
+
"Empty\n\n"
|
90
90
|
else
|
91
|
-
out
|
91
|
+
out+"\n"
|
92
92
|
end
|
93
93
|
|
94
|
-
|
95
94
|
end
|
96
95
|
|
97
96
|
# created so that I don't have to call count everytime just to see whether there's at least one element matching said condition
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../../../lib/rachinations/domain/diagrams/diagram'
|
2
|
+
require_relative 'diagram_shorthand_methods'
|
3
|
+
require_relative 'bad_dsl'
|
4
|
+
require_relative 'helpers/parser'
|
5
|
+
|
6
|
+
# Bootstraping a diagram using the DiagramShorthandMethods
|
7
|
+
module DSL
|
8
|
+
module Bootstrap
|
9
|
+
|
10
|
+
def diagram(name='new_diagram', mode: :silent, &blk)
|
11
|
+
|
12
|
+
# cant verbose be a simple boolean instead?
|
13
|
+
if mode == :silent || mode == 'silent'
|
14
|
+
dia= Diagram.new(Parser.validate_name!(name))
|
15
|
+
elsif mode == :verbose || mode == 'verbose'
|
16
|
+
dia = VerboseDiagram.new(Parser.validate_name!(name))
|
17
|
+
else
|
18
|
+
raise BadDSL, "Unknown diagram mode: #{mode.to_s}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# methods in the block get run as if they were called on
|
22
|
+
# the diagram (augmented by the DiagramShorthandMethods module) itself
|
23
|
+
dia.instance_eval(&blk)
|
24
|
+
|
25
|
+
dia
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def non_deterministic_diagram(name, verbose=:silent, &blk)
|
31
|
+
|
32
|
+
dia=NonDeterministicDiagram.new(Parser.validate_name!(name))
|
33
|
+
|
34
|
+
# cant verbose be a simple boolean instead?
|
35
|
+
if verbose === :verbose
|
36
|
+
dia.extend(Verbose)
|
37
|
+
end
|
38
|
+
|
39
|
+
dia.instance_eval &blk
|
40
|
+
|
41
|
+
dia
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# This is just a convenience method to create a proc in a way that's more
|
46
|
+
# intuitive for ends users.
|
47
|
+
# @example Create a proc, equivalent to proc{|x| x+1 }
|
48
|
+
# expr{|x| x+1 }
|
49
|
+
def expr(&blk)
|
50
|
+
if !block_given?
|
51
|
+
raise ArgumentError, "expected a block, but none was given"
|
52
|
+
else
|
53
|
+
Proc.new(&blk)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require_relative '../domain/diagrams/diagram'
|
2
|
+
require_relative '../domain/diagrams/verbose_diagram'
|
3
|
+
require_relative '../domain/diagrams/non_deterministic_diagram'
|
4
|
+
require_relative '../domain/modules/diagrams/verbose'
|
5
|
+
require_relative '../domain/modules/common/refiners/proc_convenience_methods'
|
6
|
+
require_relative './bad_dsl'
|
7
|
+
require_relative '../utils/string_helper'
|
8
|
+
require_relative 'helpers/parser'
|
9
|
+
|
10
|
+
module DSL
|
11
|
+
|
12
|
+
module DiagramShorthandMethods
|
13
|
+
|
14
|
+
using ProcConvenienceMethods
|
15
|
+
|
16
|
+
# if you can turn this into a refiner and still keep all tests passing,
|
17
|
+
# send a PR for me :smile:
|
18
|
+
class ::Diagram
|
19
|
+
|
20
|
+
alias_method :run, :run!
|
21
|
+
|
22
|
+
def pool(*args)
|
23
|
+
|
24
|
+
hash = Parser.parse_arguments(args)
|
25
|
+
|
26
|
+
add_node! Pool, hash
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def source(*args)
|
31
|
+
|
32
|
+
hash = Parser.parse_arguments(args)
|
33
|
+
|
34
|
+
add_node! Source, hash
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def sink(*args)
|
39
|
+
|
40
|
+
hash = Parser.parse_arguments(args)
|
41
|
+
|
42
|
+
add_node! Sink, hash
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def converter(*args)
|
47
|
+
|
48
|
+
hash = Parser.parse_arguments(args)
|
49
|
+
|
50
|
+
add_node! Converter, hash
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def trader(*args)
|
55
|
+
|
56
|
+
hash = Parser.parse_arguments(args)
|
57
|
+
|
58
|
+
add_node! Trader, hash
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def gate(*args)
|
63
|
+
# gate is different because it doesn't take some arguments
|
64
|
+
hash = Parser.parse_gate_arguments(args)
|
65
|
+
|
66
|
+
add_node! Gate, hash
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# methods to create edges
|
71
|
+
def edge(*args)
|
72
|
+
|
73
|
+
hash = Parser.parse_edge_arguments(args)
|
74
|
+
|
75
|
+
add_edge! Edge, hash
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# so that I can easily access elements which have been given a name
|
81
|
+
# (mostly nodes and maybe edges too)
|
82
|
+
def method_missing(method_sym, *args, &block)
|
83
|
+
super if method_sym.to_s == 'anonymous' #these aren't actual node names
|
84
|
+
|
85
|
+
begin
|
86
|
+
#does a node with that name exist?
|
87
|
+
node=get_node(method_sym.to_s)
|
88
|
+
rescue RuntimeError => err_node
|
89
|
+
#what about an edge?
|
90
|
+
begin
|
91
|
+
edge = get_edge(method_sym.to_s)
|
92
|
+
rescue RuntimeError => err_edge
|
93
|
+
#no luck, sorry
|
94
|
+
super
|
95
|
+
else
|
96
|
+
edge
|
97
|
+
end
|
98
|
+
else
|
99
|
+
node
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|