rachinations 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +184 -5
- data/lib/rachinations.rb +5 -4
- data/lib/rachinations/domain/diagrams/diagram.rb +35 -24
- data/lib/rachinations/domain/exceptions/bad_config.rb +1 -1
- data/lib/rachinations/domain/{exceptions → modules/common}/bad_options.rb +0 -0
- data/lib/rachinations/domain/modules/common/hash_init.rb +1 -0
- data/lib/rachinations/domain/modules/common/refiners/number_modifiers.rb +2 -1
- data/lib/rachinations/domain/modules/diagrams/verbose.rb +1 -1
- data/lib/rachinations/domain/nodes/gate.rb +23 -27
- data/lib/rachinations/domain/nodes/node.rb +4 -20
- data/lib/rachinations/domain/nodes/pool.rb +2 -3
- data/lib/rachinations/domain/nodes/resourceful_node.rb +4 -3
- data/lib/rachinations/domain/nodes/sink.rb +1 -1
- data/lib/rachinations/dsl/bad_dsl.rb +3 -0
- data/lib/rachinations/dsl/bootstrap.rb +10 -8
- data/lib/rachinations/dsl/diagram_shorthand_methods.rb +10 -5
- data/lib/rachinations/dsl/helpers/parser.rb +83 -49
- data/lib/rachinations/helpers/edge_helper.rb +105 -8
- data/lib/rachinations/utils/math_utils.rb +83 -0
- data/lib/rachinations/utils/string_utils.rb +8 -0
- data/lib/rachinations/version.rb +1 -1
- data/rachinations.gemspec +2 -1
- data/testing/spec/diagram_spec.rb +78 -17
- data/testing/spec/gate_spec.rb +223 -2
- data/testing/spec/non_deterministic_diagram_spec.rb +1 -1
- data/testing/spec/release1/dsl_spec.rb +100 -3
- data/testing/spec/release1/individual_cases_spec.rb +39 -0
- data/testing/spec/release1/monografia_spec.rb +69 -0
- metadata +24 -5
- data/lib/rachinations/utils/string_helper.rb +0 -7
@@ -1,6 +1,8 @@
|
|
1
1
|
require_relative '../../domain/modules/common/invariant'
|
2
2
|
require_relative '../../domain/modules/common/hash_init'
|
3
3
|
|
4
|
+
# @abstract Subclass and override {#take_resource!} and
|
5
|
+
# {#put_resource!} to implement nodes
|
4
6
|
class Node
|
5
7
|
|
6
8
|
include Invariant
|
@@ -35,7 +37,6 @@ class Node
|
|
35
37
|
|
36
38
|
end
|
37
39
|
|
38
|
-
|
39
40
|
def edges
|
40
41
|
if @edges.is_a? Array
|
41
42
|
@edges
|
@@ -102,34 +103,17 @@ class Node
|
|
102
103
|
@triggers
|
103
104
|
end
|
104
105
|
|
105
|
-
# def clear_triggers
|
106
|
-
# triggers.each do |t|
|
107
|
-
# t[2]=true
|
108
|
-
# end
|
109
|
-
# end
|
110
|
-
|
111
106
|
# Call trigger! on each node stored in self.triggers
|
112
107
|
#
|
113
108
|
def fire_triggers!
|
114
|
-
triggers.each
|
115
|
-
node.trigger!
|
116
|
-
# if (n[0].is_a? Proc) && n[2]
|
117
|
-
# if n[0].call
|
118
|
-
# n[2]=false
|
119
|
-
# n[1].trigger!
|
120
|
-
# end
|
121
|
-
# elsif n[0] && n[2]
|
122
|
-
# n[2]=false
|
123
|
-
# n[1].trigger!
|
124
|
-
# end
|
125
|
-
end
|
109
|
+
triggers.each { |node| node.trigger! }
|
126
110
|
end
|
127
111
|
|
128
112
|
def enabled?
|
129
113
|
status=true
|
130
114
|
conditions.each do |condition|
|
131
115
|
if condition.is_a? Proc
|
132
|
-
status = (status && condition.call)
|
116
|
+
status = (status && condition.call())
|
133
117
|
elsif condition === false
|
134
118
|
return false
|
135
119
|
elsif condition === true
|
@@ -3,7 +3,6 @@ require_relative '../../domain/nodes/node'
|
|
3
3
|
require_relative '../../domain/resources/token'
|
4
4
|
require_relative '../resource_bag'
|
5
5
|
require_relative '../../domain/exceptions/no_elements_matching_condition_error'
|
6
|
-
require_relative '../../domain/exceptions/bad_config'
|
7
6
|
require_relative '../../domain/modules/common/refiners/proc_convenience_methods'
|
8
7
|
require_relative '../../../../lib/rachinations/helpers/edge_helper'
|
9
8
|
|
@@ -11,7 +10,7 @@ using ProcConvenienceMethods
|
|
11
10
|
|
12
11
|
class Pool < ResourcefulNode
|
13
12
|
|
14
|
-
|
13
|
+
EdgeHelper = Helpers::EdgeHelper
|
15
14
|
|
16
15
|
def initialize(hsh={})
|
17
16
|
|
@@ -276,7 +275,7 @@ class Pool < ResourcefulNode
|
|
276
275
|
enabled_outgoing_edges = outgoing_edges.select { |edge| edge.enabled? }
|
277
276
|
|
278
277
|
|
279
|
-
if
|
278
|
+
if EdgeHelper.all_can_push?(edges, require_all: true)
|
280
279
|
|
281
280
|
enabled_outgoing_edges.each do |edge|
|
282
281
|
|
@@ -5,6 +5,9 @@ require_relative '../../domain/modules/common/refiners/proc_convenience_methods'
|
|
5
5
|
|
6
6
|
using ProcConvenienceMethods
|
7
7
|
|
8
|
+
# @abstract Subclass and override {#trigger!}, {#resource_count},
|
9
|
+
# {#pull_any!}, {#pull_all!}, {#push_any!}, {#push_all!}, {#take_resource!}
|
10
|
+
# and {#put_resource!} to implement nodes that can store resources
|
8
11
|
class ResourcefulNode < Node
|
9
12
|
|
10
13
|
include Invariant
|
@@ -25,13 +28,11 @@ class ResourcefulNode < Node
|
|
25
28
|
|
26
29
|
end
|
27
30
|
|
28
|
-
# pools are about resources
|
29
|
-
|
30
31
|
def supports?(klass)
|
31
32
|
if klass.eql?(Token)
|
32
33
|
untyped?
|
33
34
|
else
|
34
|
-
#untyped nodes support everything.
|
35
|
+
# untyped nodes support everything.
|
35
36
|
if untyped?
|
36
37
|
true
|
37
38
|
else
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative '../../../lib/rachinations/domain/diagrams/diagram'
|
2
|
-
require_relative 'diagram_shorthand_methods'
|
3
2
|
require_relative 'bad_dsl'
|
4
3
|
require_relative 'helpers/parser'
|
5
4
|
|
@@ -7,15 +6,17 @@ require_relative 'helpers/parser'
|
|
7
6
|
module DSL
|
8
7
|
module Bootstrap
|
9
8
|
|
10
|
-
def diagram(name='new_diagram', mode: :
|
9
|
+
def diagram(name='new_diagram', mode: :default, &blk)
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
supported_modes = [:default, :silent, :verbose]
|
12
|
+
|
13
|
+
raise BadDSL, "Unknown diagram mode: #{mode.to_s}" unless supported_modes.include? mode
|
14
|
+
|
15
|
+
# right now silent and default are the same thing
|
16
|
+
if mode == :default || mode == :silent
|
14
17
|
dia= Diagram.new(Parser.validate_name!(name))
|
15
18
|
elsif mode == :verbose || mode == 'verbose'
|
16
19
|
dia = VerboseDiagram.new(Parser.validate_name!(name))
|
17
|
-
else
|
18
|
-
raise BadDSL, "Unknown diagram mode: #{mode.to_s}"
|
19
20
|
end
|
20
21
|
|
21
22
|
# This is a modified version of Diagram#add_edge!. It defers some method
|
@@ -77,7 +78,7 @@ module DSL
|
|
77
78
|
|
78
79
|
node.attach_condition &condition
|
79
80
|
|
80
|
-
|
81
|
+
unless triggered_by.nil?
|
81
82
|
|
82
83
|
attach_trigger_task = lambda do |triggeree, triggerer_name, diagram|
|
83
84
|
# ask the diagram to evaluate what node it is
|
@@ -88,7 +89,7 @@ module DSL
|
|
88
89
|
|
89
90
|
end
|
90
91
|
|
91
|
-
|
92
|
+
unless triggers.nil?
|
92
93
|
|
93
94
|
attach_trigger_task = lambda do |triggerer, triggeree_name, diagram|
|
94
95
|
# ask the diagram to evaluate what node it is
|
@@ -105,6 +106,7 @@ module DSL
|
|
105
106
|
|
106
107
|
end
|
107
108
|
|
109
|
+
# after defining all those methods we run
|
108
110
|
dia.instance_eval(&blk)
|
109
111
|
dia.run_scheduled_tasks
|
110
112
|
|
@@ -1,10 +1,6 @@
|
|
1
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
2
|
require_relative '../domain/modules/common/refiners/proc_convenience_methods'
|
6
3
|
require_relative './bad_dsl'
|
7
|
-
require_relative '../utils/string_helper'
|
8
4
|
require_relative 'helpers/parser'
|
9
5
|
|
10
6
|
module DSL
|
@@ -15,6 +11,9 @@ module DSL
|
|
15
11
|
|
16
12
|
# if you can turn this into a refiner and still keep all tests passing,
|
17
13
|
# send a PR for me :smile:
|
14
|
+
|
15
|
+
# Reopen class Diagram and add methods that will only be visible
|
16
|
+
# inside a diagram do .. end block
|
18
17
|
class ::Diagram
|
19
18
|
|
20
19
|
alias_method :run, :run!
|
@@ -67,7 +66,6 @@ module DSL
|
|
67
66
|
|
68
67
|
end
|
69
68
|
|
70
|
-
# methods to create edges
|
71
69
|
def edge(*args)
|
72
70
|
|
73
71
|
hash = Parser.parse_edge_arguments(args)
|
@@ -76,6 +74,13 @@ module DSL
|
|
76
74
|
|
77
75
|
end
|
78
76
|
|
77
|
+
def stop(*args)
|
78
|
+
hash = Parser.parse_stop_condition_arguments(args)
|
79
|
+
|
80
|
+
add_stop_condition!(hash)
|
81
|
+
|
82
|
+
end
|
83
|
+
|
79
84
|
# so that I can easily access elements which have been given a name
|
80
85
|
# (mostly nodes and maybe edges too)
|
81
86
|
def method_missing(method_sym, *args, &block)
|
@@ -8,6 +8,7 @@ module DSL
|
|
8
8
|
using ProcConvenienceMethods
|
9
9
|
|
10
10
|
ConstantHash = ::Extras::ConstantHash
|
11
|
+
StringUtils = ::Utils::StringUtils
|
11
12
|
|
12
13
|
# these patterns define what each argument should look like
|
13
14
|
|
@@ -17,86 +18,93 @@ module DSL
|
|
17
18
|
|
18
19
|
MODE = proc { |arg| [:pull_any, :pull_all, :push_any, :push_all].include? arg }
|
19
20
|
|
21
|
+
GATE_MODE = proc { |arg| [:probabilistic, :deterministic].include? arg }
|
22
|
+
|
20
23
|
ACTIVATION= proc { |arg| [:automatic, :passive, :start].include? arg }
|
21
24
|
|
22
25
|
PROC = proc { |arg| arg.is_a? Proc }
|
23
26
|
|
24
27
|
LABEL = proc { |arg| arg.is_a?(Numeric) || arg.is_a?(Proc) }
|
25
28
|
|
29
|
+
SHORT_STRING = proc { |arg| arg.is_a?(String) && arg.length.between?(1, 25) }
|
30
|
+
|
26
31
|
# Parse an arbitrary list of arguments and returns a well-formed
|
27
32
|
# Hash which can then be used as argument to method add_node!
|
28
33
|
# @param [Array] arguments an array of arguments.
|
29
34
|
# @return [Hash] a well-formed hash
|
30
35
|
def self.parse_arguments(arguments)
|
31
|
-
arguments.inject(ConstantHash.new) do |
|
36
|
+
arguments.inject(ConstantHash.new) do |acc, elem|
|
32
37
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
38
|
+
# elem is here either a hash or a single argument that's
|
39
|
+
# been passed to this method
|
40
|
+
|
41
|
+
if elem.is_a? Hash
|
36
42
|
|
37
|
-
if
|
38
|
-
if ACTIVATION.match?
|
39
|
-
|
43
|
+
if elem.has_key? :activation
|
44
|
+
if ACTIVATION.match? elem[:activation]
|
45
|
+
acc[:activation] = elem[:activation]
|
40
46
|
else
|
41
47
|
raise BadDSL.new
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
|
-
if
|
46
|
-
if PROC.match?
|
47
|
-
|
51
|
+
if elem.has_key? :condition
|
52
|
+
if PROC.match? elem[:condition]
|
53
|
+
acc[:condition] = elem[:condition]
|
48
54
|
else
|
49
55
|
raise BadDSL.new
|
50
56
|
end
|
51
57
|
end
|
52
58
|
|
53
|
-
if
|
54
|
-
if INITIAL_VALUE.match?
|
55
|
-
|
59
|
+
if elem.has_key? :initial_value
|
60
|
+
if INITIAL_VALUE.match? elem[:initial_value]
|
61
|
+
acc[:initial_value] = elem[:initial_value]
|
56
62
|
else
|
57
63
|
raise BadDSL.new
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
61
|
-
if
|
62
|
-
if MODE.match?
|
63
|
-
|
67
|
+
if elem.has_key? :mode
|
68
|
+
if MODE.match? elem[:mode]
|
69
|
+
acc[:mode] = elem[:mode]
|
64
70
|
else
|
65
71
|
raise BadDSL.new
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
69
|
-
if
|
70
|
-
if IDENTIFIER.match?
|
71
|
-
|
75
|
+
if elem.has_key? :triggered_by
|
76
|
+
if IDENTIFIER.match? elem[:triggered_by]
|
77
|
+
acc[:triggered_by] = elem[:triggered_by]
|
72
78
|
else
|
73
79
|
raise BadDSL.new
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
77
|
-
if
|
78
|
-
if IDENTIFIER.match?
|
79
|
-
|
83
|
+
if elem.has_key? :triggers
|
84
|
+
if IDENTIFIER.match? elem[:triggers]
|
85
|
+
acc[:triggers] = elem[:triggers]
|
80
86
|
else
|
81
87
|
raise BadDSL.new
|
82
88
|
end
|
83
89
|
end
|
84
90
|
|
85
91
|
else
|
86
|
-
if IDENTIFIER.match?(
|
87
|
-
|
88
|
-
elsif INITIAL_VALUE.match?(
|
89
|
-
|
90
|
-
elsif MODE.match?(
|
91
|
-
|
92
|
-
elsif ACTIVATION.match?(
|
93
|
-
|
92
|
+
if IDENTIFIER.match?(elem) # a node's name, if present, is always the first elemument
|
93
|
+
acc[:name] = elem
|
94
|
+
elsif INITIAL_VALUE.match?(elem)
|
95
|
+
acc[:initial_value] = elem
|
96
|
+
elsif MODE.match?(elem)
|
97
|
+
acc[:mode] = elem
|
98
|
+
elsif ACTIVATION.match?(elem)
|
99
|
+
acc[:activation] = elem
|
100
|
+
elsif PROC.match?(elem)
|
101
|
+
acc[:condition] = elem
|
94
102
|
else
|
95
|
-
raise BadDSL, "
|
103
|
+
raise BadDSL, "elemument #{elem} doesn't fit any known signature"
|
96
104
|
end
|
97
105
|
end
|
98
106
|
# passing the accumulator onto the next iteration
|
99
|
-
|
107
|
+
acc
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
@@ -110,6 +118,8 @@ module DSL
|
|
110
118
|
accumulator[:condition] = arg[:condition] if PROC.match?(arg[:condition])
|
111
119
|
elsif arg.has_key? :triggered_by
|
112
120
|
accumulator[:triggered_by] = arg[:triggered_by] if IDENTIFIER.match?(arg[:triggered_by])
|
121
|
+
elsif arg.has_key?[:mode]
|
122
|
+
accumulator[:mode] = arg[:mode] if GATE_MODE.match?(arg[:mode])
|
113
123
|
else
|
114
124
|
raise BadDSL, "Named argument doesn't fit any known signature"
|
115
125
|
end
|
@@ -118,8 +128,10 @@ module DSL
|
|
118
128
|
accumulator[:name] = arg
|
119
129
|
elsif ACTIVATION.match?(arg)
|
120
130
|
accumulator[:activation] = arg
|
131
|
+
elsif GATE_MODE.match?(arg)
|
132
|
+
accumulator[:mode] = arg
|
121
133
|
else
|
122
|
-
raise BadDSL, "Argument #{arg} doesn't fit any known signature"
|
134
|
+
raise BadDSL, "Argument '#{arg}' doesn't fit any known signature"
|
123
135
|
end
|
124
136
|
end
|
125
137
|
accumulator
|
@@ -129,31 +141,53 @@ module DSL
|
|
129
141
|
|
130
142
|
def self.parse_edge_arguments(arguments)
|
131
143
|
|
132
|
-
arguments.inject(ConstantHash.new) do |
|
133
|
-
if
|
134
|
-
if
|
135
|
-
|
144
|
+
arguments.inject(ConstantHash.new) do |acc, elem|
|
145
|
+
if elem.is_a? Hash
|
146
|
+
if elem.has_key? :from
|
147
|
+
acc[:from] = elem[:from] if IDENTIFIER.match? elem[:from]
|
136
148
|
end
|
137
|
-
if
|
138
|
-
|
149
|
+
if elem.has_key? :to
|
150
|
+
acc[:to] = elem[:to] if IDENTIFIER.match? elem[:to]
|
139
151
|
end
|
140
|
-
if
|
141
|
-
|
152
|
+
if elem.has_key? :label
|
153
|
+
acc[:label] = elem[:label] if LABEL.match? elem[:label]
|
142
154
|
end
|
143
155
|
else
|
144
|
-
if IDENTIFIER.match?(
|
145
|
-
|
146
|
-
elsif LABEL.match?(
|
147
|
-
|
156
|
+
if IDENTIFIER.match?(elem)
|
157
|
+
acc[:name] = elem
|
158
|
+
elsif LABEL.match?(elem)
|
159
|
+
acc[:label]=elem
|
148
160
|
else
|
149
|
-
raise BadDSL, "
|
161
|
+
raise BadDSL, "argument #{elem} doesn't fit any known signature."
|
150
162
|
end
|
151
163
|
end
|
152
|
-
|
164
|
+
acc
|
153
165
|
end
|
154
166
|
|
155
167
|
end
|
156
168
|
|
169
|
+
def self.parse_stop_condition_arguments(arguments)
|
170
|
+
arguments.inject(ConstantHash.new) do |acc, elem|
|
171
|
+
|
172
|
+
if elem.is_a? Hash
|
173
|
+
|
174
|
+
if elem.has_key? :message
|
175
|
+
acc[:message] = elem[:message] if IDENTIFIER.match? elem[:message]
|
176
|
+
end
|
177
|
+
|
178
|
+
if elem.has_key? :condition
|
179
|
+
acc[:condition] = elem[:condition] if PROC.match? elem[:condition]
|
180
|
+
end
|
181
|
+
|
182
|
+
else
|
183
|
+
acc[:condition] = elem if PROC.match? elem
|
184
|
+
acc[:message] = elem if SHORT_STRING.match? elem
|
185
|
+
end
|
186
|
+
|
187
|
+
acc
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
157
191
|
# Used to validate that a string is a valid name for diagram components
|
158
192
|
#
|
159
193
|
# @param [String] text the name we want to validate
|
@@ -161,7 +195,7 @@ module DSL
|
|
161
195
|
# @return [String] the text itself, but only if it's valid. Otherwise
|
162
196
|
# an exception will be raised.
|
163
197
|
def self.validate_name!(text)
|
164
|
-
if
|
198
|
+
if StringUtils.valid_ruby_variable_name?(text)
|
165
199
|
text
|
166
200
|
else
|
167
201
|
raise BadDSL, "Invalid name: '#{text}'"
|
@@ -171,7 +205,7 @@ module DSL
|
|
171
205
|
# @param [String] text the name we want to validate
|
172
206
|
# @return [Boolean] whether or not given text is a valid name for diagram elements
|
173
207
|
def self.valid_name?(text)
|
174
|
-
|
208
|
+
StringUtils.valid_ruby_variable_name?(text)
|
175
209
|
end
|
176
210
|
|
177
211
|
end
|