rachinations 0.0.7 → 0.0.8
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.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
|