musa-dsl 0.14.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +20 -0
- data/LICENSE.md +157 -0
- data/README.md +8 -0
- data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
- data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
- data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
- data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
- data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
- data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
- data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
- data/lib/musa-dsl/core-ext.rb +13 -0
- data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
- data/lib/musa-dsl/datasets/gdv.rb +499 -0
- data/lib/musa-dsl/datasets/pdv.rb +44 -0
- data/lib/musa-dsl/datasets.rb +5 -0
- data/lib/musa-dsl/generative/darwin.rb +145 -0
- data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
- data/lib/musa-dsl/generative/markov.rb +78 -0
- data/lib/musa-dsl/generative/rules.rb +282 -0
- data/lib/musa-dsl/generative/variatio.rb +331 -0
- data/lib/musa-dsl/generative.rb +5 -0
- data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
- data/lib/musa-dsl/midi/midi-voices.rb +274 -0
- data/lib/musa-dsl/midi.rb +2 -0
- data/lib/musa-dsl/music/chord-definition.rb +99 -0
- data/lib/musa-dsl/music/chord-definitions.rb +13 -0
- data/lib/musa-dsl/music/chords.rb +326 -0
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
- data/lib/musa-dsl/music/scales.rb +584 -0
- data/lib/musa-dsl/music.rb +6 -0
- data/lib/musa-dsl/neuma/neuma.rb +181 -0
- data/lib/musa-dsl/neuma.rb +1 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
- data/lib/musa-dsl/neumalang.rb +3 -0
- data/lib/musa-dsl/repl/repl.rb +143 -0
- data/lib/musa-dsl/repl.rb +1 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
- data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
- data/lib/musa-dsl/sequencer.rb +1 -0
- data/lib/musa-dsl/series/base-series.rb +245 -0
- data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
- data/lib/musa-dsl/series/holder-serie.rb +87 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
- data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
- data/lib/musa-dsl/series/proxy-serie.rb +69 -0
- data/lib/musa-dsl/series/queue-serie.rb +94 -0
- data/lib/musa-dsl/series/series.rb +8 -0
- data/lib/musa-dsl/series.rb +1 -0
- data/lib/musa-dsl/transport/clock.rb +36 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
- data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
- data/lib/musa-dsl/transport/timer-clock.rb +102 -0
- data/lib/musa-dsl/transport/timer.rb +40 -0
- data/lib/musa-dsl/transport/transport.rb +137 -0
- data/lib/musa-dsl/transport.rb +9 -0
- data/lib/musa-dsl.rb +17 -0
- data/musa-dsl.gemspec +17 -0
- metadata +174 -0
@@ -0,0 +1,294 @@
|
|
1
|
+
module Musa
|
2
|
+
module GenerativeGrammar
|
3
|
+
|
4
|
+
def N(content = nil, **attributes, &block)
|
5
|
+
if block_given? && content.nil?
|
6
|
+
BlockNode.new(attributes, &block)
|
7
|
+
else
|
8
|
+
FinalNode.new(content, attributes)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class OptionElement
|
13
|
+
attr_reader :content, :attributes
|
14
|
+
|
15
|
+
def initialize(content, attributes = nil)
|
16
|
+
@content = content
|
17
|
+
@attributes = attributes || {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Node
|
22
|
+
def or(other)
|
23
|
+
OrNode.new(self, other)
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :|, :or
|
27
|
+
|
28
|
+
def repeat(exactly = nil, min: nil, max: nil)
|
29
|
+
raise ArgumentError, 'Only exactly value or min/max values are allowed' if exactly && (min || max)
|
30
|
+
|
31
|
+
min = max = exactly if exactly
|
32
|
+
|
33
|
+
if min && min > 0
|
34
|
+
pre = self
|
35
|
+
|
36
|
+
(min - 1).times do
|
37
|
+
pre += self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if pre && max == min
|
42
|
+
pre
|
43
|
+
elsif pre && max > min
|
44
|
+
pre + RepeatNode.new(self, max - min)
|
45
|
+
else
|
46
|
+
RepeatNode.new(self, max)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def limit(attribute = nil, after_collect_operation = nil, comparison_method = nil, comparison_value = nil, &block)
|
51
|
+
raise ArgumentError, 'Cannot use simplified arguments and yield block at the same time' if (attribute || after_collect_operation || comparison_method || comparison_value) && @block
|
52
|
+
|
53
|
+
block ||= generate_simple_condition_block(attribute, after_collect_operation, comparison_method, comparison_value)
|
54
|
+
|
55
|
+
ConditionNode.new(self, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def next(other)
|
59
|
+
NextNode.new(self, other)
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :+, :next
|
63
|
+
|
64
|
+
def options(attribute = nil,
|
65
|
+
after_collect_operation = nil,
|
66
|
+
comparison_method = nil,
|
67
|
+
comparison_value = nil,
|
68
|
+
raw: nil,
|
69
|
+
content: nil,
|
70
|
+
&condition)
|
71
|
+
|
72
|
+
raise ArgumentError, 'Cannot use simplified arguments and yield block at the same time' if (attribute || after_collect_operation || comparison_method || comparison_value) && @condition
|
73
|
+
raise ArgumentError, 'Cannot use raw: true and content: option at the same time' if raw && content
|
74
|
+
|
75
|
+
raw ||= false
|
76
|
+
content ||= :itself
|
77
|
+
|
78
|
+
condition ||= generate_simple_condition_block(attribute, after_collect_operation, comparison_method, comparison_value)
|
79
|
+
|
80
|
+
if raw
|
81
|
+
_options(&condition)
|
82
|
+
else
|
83
|
+
_options(&condition).collect { |o| o.collect(&:content).send(content) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def size
|
88
|
+
options.size
|
89
|
+
end
|
90
|
+
|
91
|
+
alias_method :length, :size
|
92
|
+
|
93
|
+
def [](index)
|
94
|
+
options[index].to_serie.to_node
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_serie(flatten: nil, &condition)
|
98
|
+
flatten ||= true
|
99
|
+
|
100
|
+
serie = _options(&condition).collect { |o| o.collect(&:content) }.to_serie(of_series: true).merge
|
101
|
+
serie = serie.flatten if flatten
|
102
|
+
|
103
|
+
serie.prototype
|
104
|
+
end
|
105
|
+
|
106
|
+
alias_method :s, :to_serie
|
107
|
+
|
108
|
+
def _options(parent: nil, &condition)
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
def generate_simple_condition_block(attribute = nil,
|
115
|
+
after_collect_operation = nil,
|
116
|
+
comparison_method = nil,
|
117
|
+
comparison_value = nil)
|
118
|
+
|
119
|
+
if attribute && after_collect_operation && comparison_method && comparison_value
|
120
|
+
proc do |o|
|
121
|
+
o.collect { |_| _.attributes[attribute] }
|
122
|
+
.send(after_collect_operation)
|
123
|
+
.send(comparison_method, comparison_value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private_constant :Node
|
130
|
+
|
131
|
+
class ProxyNode < Node
|
132
|
+
attr_accessor :node
|
133
|
+
|
134
|
+
def _options(parent: nil, &condition)
|
135
|
+
@node._options parent: parent, &condition
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class FinalNode < Node
|
140
|
+
attr_reader :content
|
141
|
+
attr_reader :attributes
|
142
|
+
|
143
|
+
def initialize(content, attributes)
|
144
|
+
super()
|
145
|
+
@element = OptionElement.new(content, attributes)
|
146
|
+
end
|
147
|
+
|
148
|
+
def _options(parent: nil, &condition)
|
149
|
+
parent ||= []
|
150
|
+
|
151
|
+
if block_given?
|
152
|
+
if yield(parent + [@element])
|
153
|
+
[[@element]]
|
154
|
+
else
|
155
|
+
[]
|
156
|
+
end
|
157
|
+
else
|
158
|
+
[[@element]]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
private_constant :FinalNode
|
164
|
+
|
165
|
+
class BlockNode < Node
|
166
|
+
def initialize(attributes, &block)
|
167
|
+
@attributes = attributes
|
168
|
+
@block = block
|
169
|
+
end
|
170
|
+
|
171
|
+
def _options(parent: nil, &condition)
|
172
|
+
parent ||= []
|
173
|
+
|
174
|
+
element = @block.call(parent, @attributes)
|
175
|
+
element = OptionElement.new(element, @attributes) unless element.is_a?(OptionElement)
|
176
|
+
|
177
|
+
if block_given?
|
178
|
+
if yield(parent + [element], @attributes)
|
179
|
+
[[element]]
|
180
|
+
else
|
181
|
+
[]
|
182
|
+
end
|
183
|
+
else
|
184
|
+
[[element]]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private_constant :BlockNode
|
190
|
+
|
191
|
+
class ConditionNode < Node
|
192
|
+
def initialize(node, &block)
|
193
|
+
@node = node
|
194
|
+
@block = block
|
195
|
+
end
|
196
|
+
|
197
|
+
def _options(parent: nil, &condition)
|
198
|
+
parent ||= []
|
199
|
+
|
200
|
+
r = []
|
201
|
+
|
202
|
+
@node._options(parent: parent, &condition).each do |node_option|
|
203
|
+
r << node_option if (!block_given? || yield(parent + node_option)) && @block.call(parent + node_option)
|
204
|
+
end
|
205
|
+
|
206
|
+
r
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
private_constant :ConditionNode
|
211
|
+
|
212
|
+
class OrNode < Node
|
213
|
+
def initialize(node1, node2)
|
214
|
+
@node1 = node1
|
215
|
+
@node2 = node2
|
216
|
+
super()
|
217
|
+
end
|
218
|
+
|
219
|
+
def _options(parent: nil, &condition)
|
220
|
+
parent ||= []
|
221
|
+
|
222
|
+
r = []
|
223
|
+
|
224
|
+
@node1._options(parent: parent, &condition).each do |node_option|
|
225
|
+
r << node_option if !block_given? || yield(parent + node_option)
|
226
|
+
end
|
227
|
+
|
228
|
+
@node2._options(parent: parent, &condition).each do |node_option|
|
229
|
+
r << node_option if !block_given? || yield(parent + node_option)
|
230
|
+
end
|
231
|
+
|
232
|
+
r
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private_constant :OrNode
|
237
|
+
|
238
|
+
class NextNode < Node
|
239
|
+
def initialize(node, after)
|
240
|
+
@node = node
|
241
|
+
@after = after
|
242
|
+
super()
|
243
|
+
end
|
244
|
+
|
245
|
+
def _options(parent: nil, &condition)
|
246
|
+
parent ||= []
|
247
|
+
|
248
|
+
r = []
|
249
|
+
@node._options(parent: parent, &condition).each do |node_option|
|
250
|
+
@after._options(parent: parent + node_option, &condition).each do |after_option|
|
251
|
+
r << node_option + after_option unless after_option.empty?
|
252
|
+
end
|
253
|
+
end
|
254
|
+
r
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
private_constant :NextNode
|
259
|
+
|
260
|
+
class RepeatNode < Node
|
261
|
+
def initialize(node, max = nil)
|
262
|
+
@node = node
|
263
|
+
@max = max
|
264
|
+
|
265
|
+
super()
|
266
|
+
end
|
267
|
+
|
268
|
+
def _options(parent: nil, depth: nil, &condition)
|
269
|
+
parent ||= []
|
270
|
+
depth ||= 0
|
271
|
+
|
272
|
+
r = []
|
273
|
+
|
274
|
+
if @max.nil? || depth < @max
|
275
|
+
node_options = @node._options(parent: parent, &condition)
|
276
|
+
|
277
|
+
node_options.each do |node_option|
|
278
|
+
r << node_option
|
279
|
+
|
280
|
+
node_suboptions = _options(parent: parent + node_option, depth: depth + 1, &condition)
|
281
|
+
|
282
|
+
node_suboptions.each do |node_suboption|
|
283
|
+
r << node_option + node_suboption
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
r
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
private_constant :RepeatNode
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'musa-dsl/series'
|
2
|
+
|
3
|
+
module Musa
|
4
|
+
|
5
|
+
# TODO: adapt to series prototyping
|
6
|
+
|
7
|
+
class Markov
|
8
|
+
include Serie
|
9
|
+
|
10
|
+
attr_accessor :start, :finish, :random, :transitions
|
11
|
+
|
12
|
+
def initialize(transitions:, start:, finish: nil, random: nil)
|
13
|
+
@transitions = transitions.clone.freeze
|
14
|
+
|
15
|
+
@start = start
|
16
|
+
@finish = finish
|
17
|
+
|
18
|
+
@random = Random.new random if random.is_a?(Integer)
|
19
|
+
@random ||= Random.new
|
20
|
+
|
21
|
+
@procedure_binders = {}
|
22
|
+
|
23
|
+
_restart
|
24
|
+
end
|
25
|
+
|
26
|
+
def _restart
|
27
|
+
@current = nil
|
28
|
+
@finished = false
|
29
|
+
@history = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def _next_value
|
33
|
+
if @finished
|
34
|
+
@current = nil
|
35
|
+
else
|
36
|
+
if @current.nil?
|
37
|
+
@current = @start
|
38
|
+
else
|
39
|
+
if @transitions.has_key? @current
|
40
|
+
options = @transitions[@current]
|
41
|
+
|
42
|
+
case options
|
43
|
+
when Array
|
44
|
+
@current = options[@random.rand(0...options.size)]
|
45
|
+
|
46
|
+
when Hash
|
47
|
+
total = accumulated = 0.0
|
48
|
+
options.each_value { |probability| total += probability.abs }
|
49
|
+
r = @random.rand total
|
50
|
+
|
51
|
+
@current = options.find { |key, probability|
|
52
|
+
accumulated += probability;
|
53
|
+
r >= accumulated - probability && r < accumulated }[0]
|
54
|
+
|
55
|
+
when Proc
|
56
|
+
procedure_binder = @procedure_binders[options] ||= KeyParametersProcedureBinder.new options
|
57
|
+
@current = procedure_binder.call @history
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Option #{option} is not allowed. Only Array, Hash or Proc are allowed."
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise RuntimeError, "No transition defined for #{@current}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
@history << @current
|
67
|
+
@finished = true if !@finish.nil? && (@current == @finish)
|
68
|
+
end
|
69
|
+
|
70
|
+
@current
|
71
|
+
end
|
72
|
+
|
73
|
+
def infinite?
|
74
|
+
@finish.nil?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'musa-dsl/core-ext/as-context-run'
|
2
|
+
require 'musa-dsl/core-ext/key-parameters-procedure-binder'
|
3
|
+
|
4
|
+
module Musa
|
5
|
+
class Rules
|
6
|
+
def initialize(&block)
|
7
|
+
@context = RulesEvalContext.new.tap { |_| _._as_context_run block }
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_possibilities(object, confirmed_node = nil, node = nil, rules = nil)
|
11
|
+
node ||= Node.new
|
12
|
+
rules ||= @context._rules
|
13
|
+
|
14
|
+
history = confirmed_node.history if confirmed_node
|
15
|
+
history ||= []
|
16
|
+
|
17
|
+
rules = rules.clone
|
18
|
+
rule = rules.shift
|
19
|
+
|
20
|
+
if rule
|
21
|
+
rule.generate_possibilities(object, history).each do |new_object|
|
22
|
+
new_node = Node.new new_object, node
|
23
|
+
new_node.mark_as_ended! if @context._ended? new_object
|
24
|
+
|
25
|
+
rejection = @context._rejections.find { |rejection| rejection.rejects?(new_object, history) }
|
26
|
+
# TODO: include rejection secondary reasons in rejection message
|
27
|
+
|
28
|
+
new_node.reject! rejection if rejection
|
29
|
+
|
30
|
+
node.children << new_node
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
unless rules.empty?
|
35
|
+
node.children.each do |node|
|
36
|
+
generate_possibilities node.object, confirmed_node, node, rules unless node.rejected || node.ended?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
node
|
41
|
+
end
|
42
|
+
|
43
|
+
def apply(object_or_list, node = nil)
|
44
|
+
list = object_or_list.arrayfy.clone
|
45
|
+
|
46
|
+
node ||= Node.new
|
47
|
+
|
48
|
+
seed = list.shift
|
49
|
+
|
50
|
+
if seed
|
51
|
+
result = generate_possibilities seed, node
|
52
|
+
|
53
|
+
fished = result.fish
|
54
|
+
|
55
|
+
node.reject! 'All children are rejected' if fished.empty?
|
56
|
+
|
57
|
+
fished.each do |object|
|
58
|
+
subnode = node.add(object).mark_as_ended!
|
59
|
+
apply list, subnode
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
node
|
64
|
+
end
|
65
|
+
|
66
|
+
class RulesEvalContext
|
67
|
+
attr_reader :_rules, :_ended_when, :_rejections
|
68
|
+
|
69
|
+
def rule(name, &block)
|
70
|
+
@_rules ||= []
|
71
|
+
@_rules << Rule.new(name, self, block)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def ended_when(&block)
|
76
|
+
@_ended_when = block
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def rejection(reason, &block)
|
81
|
+
@_rejections ||= []
|
82
|
+
@_rejections << Rejection.new(reason, self, block)
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def _ended?(object)
|
87
|
+
as_context_run @_ended_when, object
|
88
|
+
end
|
89
|
+
|
90
|
+
class Rule
|
91
|
+
attr_reader :name
|
92
|
+
|
93
|
+
def initialize(name, context, block)
|
94
|
+
@name = name
|
95
|
+
@context = context
|
96
|
+
@block = block
|
97
|
+
end
|
98
|
+
|
99
|
+
def generate_possibilities(object, history)
|
100
|
+
# TODO: optimize context using only one instance for all genereate_possibilities calls
|
101
|
+
context = RuleEvalContext.new @context
|
102
|
+
context.as_context_run @block, object, history
|
103
|
+
|
104
|
+
context._possibilities
|
105
|
+
end
|
106
|
+
|
107
|
+
class RuleEvalContext
|
108
|
+
attr_reader :_possibilities
|
109
|
+
|
110
|
+
def initialize(parent_context)
|
111
|
+
@_parent_context = parent_context
|
112
|
+
@_possibilities = []
|
113
|
+
end
|
114
|
+
|
115
|
+
def possibility(object)
|
116
|
+
@_possibilities << object
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def method_missing(method_name, *args, **key_args, &block)
|
123
|
+
if @_parent_context.respond_to? method_name
|
124
|
+
@_parent_context.send_nice method_name, *args, **key_args, &block
|
125
|
+
else
|
126
|
+
super
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def respond_to_missing?(method_name, include_private)
|
131
|
+
@_parent_context.respond_to?(method_name, include_private) || super
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private_constant :RuleEvalContext
|
136
|
+
end
|
137
|
+
|
138
|
+
private_constant :Rule
|
139
|
+
|
140
|
+
class Rejection
|
141
|
+
attr_reader :reason
|
142
|
+
|
143
|
+
def initialize(reason, context = nil, block = nil)
|
144
|
+
@reason = reason
|
145
|
+
@context = context
|
146
|
+
@block = block
|
147
|
+
end
|
148
|
+
|
149
|
+
def rejects?(object, history)
|
150
|
+
# TODO: optimize context using only one instance for all rejects? checks
|
151
|
+
context = RejectionEvalContext.new @context
|
152
|
+
context.as_context_run @block, object, history
|
153
|
+
|
154
|
+
reasons = context._secondary_reasons.collect { |_| ("#{@reason} (#{_})" if _) || @reason }
|
155
|
+
|
156
|
+
reasons.empty? ? nil : reasons
|
157
|
+
end
|
158
|
+
|
159
|
+
class RejectionEvalContext
|
160
|
+
attr_reader :_secondary_reasons
|
161
|
+
|
162
|
+
def initialize(parent_context)
|
163
|
+
@_parent_context = parent_context
|
164
|
+
@_secondary_reasons = []
|
165
|
+
end
|
166
|
+
|
167
|
+
def reject(secondary_reason = nil)
|
168
|
+
@_secondary_reasons << secondary_reason
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def method_missing(method_name, *args, **key_args, &block)
|
175
|
+
if @_parent_context.respond_to? method_name
|
176
|
+
@_parent_context.send_nice method_name, *args, **key_args, &block
|
177
|
+
else
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def respond_to_missing?(method_name, include_private)
|
183
|
+
@_parent_context.respond_to?(method_name, include_private) || super
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
private_constant :RejectionEvalContext
|
188
|
+
end
|
189
|
+
|
190
|
+
private_constant :Rejection
|
191
|
+
end
|
192
|
+
|
193
|
+
private_constant :RulesEvalContext
|
194
|
+
|
195
|
+
class Node
|
196
|
+
attr_reader :parent, :children, :object, :rejected
|
197
|
+
|
198
|
+
def initialize(object = nil, parent = nil)
|
199
|
+
@parent = parent
|
200
|
+
@children = []
|
201
|
+
@object = object
|
202
|
+
|
203
|
+
@ended = false
|
204
|
+
@rejected = nil
|
205
|
+
end
|
206
|
+
|
207
|
+
def add(object)
|
208
|
+
Node.new(object, self).tap { |n| @children << n }
|
209
|
+
end
|
210
|
+
|
211
|
+
def reject!(rejection)
|
212
|
+
@rejected = rejection
|
213
|
+
self
|
214
|
+
end
|
215
|
+
|
216
|
+
def mark_as_ended!
|
217
|
+
@children.each(&:update_rejection_by_children!)
|
218
|
+
|
219
|
+
if !@children.empty? && !@children.find { |n| !n.rejected }
|
220
|
+
reject! Rejection::AllChildrenRejected
|
221
|
+
end
|
222
|
+
|
223
|
+
@ended = true
|
224
|
+
|
225
|
+
self
|
226
|
+
end
|
227
|
+
|
228
|
+
def ended?
|
229
|
+
@ended
|
230
|
+
end
|
231
|
+
|
232
|
+
def history
|
233
|
+
objects = []
|
234
|
+
n = self
|
235
|
+
while n && n.object
|
236
|
+
objects << n.object
|
237
|
+
n = n.parent
|
238
|
+
end
|
239
|
+
|
240
|
+
objects.reverse
|
241
|
+
end
|
242
|
+
|
243
|
+
def fish
|
244
|
+
fished = []
|
245
|
+
|
246
|
+
@children.each do |node|
|
247
|
+
unless node.rejected
|
248
|
+
if node.ended?
|
249
|
+
fished << node.object
|
250
|
+
else
|
251
|
+
fished += node.fish
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
fished
|
257
|
+
end
|
258
|
+
|
259
|
+
def combinations(parent_combination = nil)
|
260
|
+
parent_combination ||= []
|
261
|
+
|
262
|
+
combinations = []
|
263
|
+
|
264
|
+
unless rejected
|
265
|
+
if @children.empty?
|
266
|
+
combinations << parent_combination
|
267
|
+
else
|
268
|
+
@children.each do |node|
|
269
|
+
node.combinations(parent_combination + [node.object]).each do |object|
|
270
|
+
combinations << object
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
combinations
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
private_constant :Node
|
281
|
+
end
|
282
|
+
end
|