musa-dsl 0.23.1 → 0.23.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38a9835b1d87d869a99fac037037eed22b8eaaf3ad133c70e732578a5631aa79
4
- data.tar.gz: 7e9057ff2498fc3039baa1d749cb1c23b5fcc42bdb56879aab38c4fafc3d9468
3
+ metadata.gz: e7ac7a74db8d0006786f871ba33f6d417704910b11eaab5a568fff617962ad52
4
+ data.tar.gz: 771ea64584e00fe078f4959537472032ae8438e4cb38b9a7cf1f6798a92a91de
5
5
  SHA512:
6
- metadata.gz: 191ea705bc3b3378d5660d1e75e63bb53ef0e53d1030fcae2642ff777b9753118b33af4ec7cf0edaec884c793fecded95d41bc6aef9ad41698feb2ca2c04e14e
7
- data.tar.gz: 0ee6ed871246af66ed7553a95ec2ca683d874034344b06e3c6128c54f70ec9592b33c7ac0df196dd11dc9a5290bd4c247df008fbf5b4f57af975fbd3a66bb468
6
+ metadata.gz: 9dd7592546589149d420e6681befca0acea4afde5d07676bb1bfa1f01ec9ff0ce541673618fbd5cd16e549d69034698c682e9a5a8eec61547cbc5be6f8e6c5bf
7
+ data.tar.gz: 5e4d56c555bc66cf5574a73d58c1c4f17027e4b6256d1b2f866337cefb8f776b8b6ce3987ad586c5171da4e69308a8d957e98b83d1f2387ccff3211e6eaf152a
@@ -38,12 +38,12 @@ module Musa
38
38
  include SerieImplementation
39
39
 
40
40
  if source
41
- define_method source_as do ||
41
+ define_method source_as do
42
42
  @source
43
43
  end
44
44
 
45
45
  define_method source_setter do |serie|
46
- raise ArgumentError, "New source should be a #{@get}" unless @source.nil? || @source.prototype? == serie&.prototype?
46
+ raise ArgumentError, "New #{source_as} should be a #{@get}" unless @source.nil? || @source.prototype? == serie&.prototype?
47
47
 
48
48
  serie ||= Musa::Series::Constructors.NIL
49
49
  @get = serie&.instance? ? :instance : :prototype
@@ -117,18 +117,18 @@ module Musa
117
117
  @buffer
118
118
  end
119
119
 
120
- private def _restart(main)
121
- raise ArgumentError, "Can't restart a BufferSerie directly. Should use a buffer instance instead." unless main
120
+ private def _restart(buffer)
121
+ raise ArgumentError, "Can't restart a BufferSerie directly. Should use a buffer instance instead." unless buffer
122
122
  return if @source_just_restarted
123
123
 
124
- next_nil = @nils.find { |_| _ > main.index }
124
+ next_nil = @nils.find { |_| _ > buffer.index }
125
125
 
126
- if next_nil && main.index < next_nil
127
- main.last_nil_index = main.index = next_nil
126
+ if next_nil && buffer.index < next_nil
127
+ buffer.last_nil_index = buffer.index = next_nil
128
128
 
129
129
  else
130
130
  until _next_value.nil?; end
131
- main.last_nil_index = main.index = @nils.last
131
+ buffer.last_nil_index = buffer.index = @nils.last
132
132
  end
133
133
 
134
134
  clear_old_history
@@ -216,8 +216,7 @@ module Musa
216
216
  @index += 1
217
217
  value = @history[@index]
218
218
  else
219
- @source.next_value
220
- value = _next_value
219
+ value = _next_value unless @source.next_value.nil?
221
220
  end
222
221
 
223
222
  if value.nil?
@@ -1,19 +1,32 @@
1
1
  module Musa
2
2
  module Series::Operations
3
3
  def split
4
- Splitter.new(Splitter::BufferedProxy.new(self))
4
+ Splitter.new(self)
5
5
  end
6
6
 
7
7
  class Splitter
8
8
  include Enumerable
9
+ include Series::Serie::Prototyping
9
10
 
10
- def initialize(proxy)
11
- @proxy = proxy
11
+ def initialize(source)
12
+ @source = source
12
13
  @series = {}
13
14
  end
14
15
 
16
+ def source=(serie)
17
+ @source = serie
18
+ @proxy.source = @source if @proxy
19
+ end
20
+
21
+ protected def _instance!
22
+ super
23
+ @proxy = SplitterProxy.new(@source)
24
+ end
25
+
15
26
  def [](key_or_index)
16
- if @series.has_key?(key_or_index)
27
+ raise "Can't get a component because Splitter is a prototype. To get a component you need a Splitter instance." unless @is_instance
28
+
29
+ if @series.key?(key_or_index)
17
30
  @series[key_or_index]
18
31
  else
19
32
  @series[key_or_index] = Split.new(@proxy, key_or_index)
@@ -21,6 +34,8 @@ module Musa
21
34
  end
22
35
 
23
36
  def each
37
+ raise "Can't iterate because Splitter is a prototype. To iterate you need a Splitter instance." unless @is_instance
38
+
24
39
  if block_given?
25
40
  if @proxy.hash_mode?
26
41
  @proxy.components.each do |key|
@@ -60,24 +75,24 @@ module Musa
60
75
  end
61
76
  end
62
77
 
63
- class BufferedProxy
64
- include Series::Serie::Prototyping
65
-
78
+ class SplitterProxy
66
79
  def initialize(hash_or_array_serie)
67
80
  @source = hash_or_array_serie
68
- mark_regarding! @source
69
-
70
- init
81
+ infer_components
71
82
  end
72
83
 
73
- attr_reader :components
84
+ attr_reader :source
85
+
86
+ def source=(hash_or_array_serie)
87
+ @source = hash_or_array_serie
88
+ infer_components
89
+ end
74
90
 
75
91
  def hash_mode?; @hash_mode; end
92
+
76
93
  def array_mode?; @array_mode; end
77
94
 
78
- def init
79
- infer_components
80
- end
95
+ attr_reader :components
81
96
 
82
97
  def restart(key_or_index = nil)
83
98
  if key_or_index
@@ -151,18 +166,18 @@ module Musa
151
166
  include Series::Serie.base
152
167
 
153
168
  def initialize(proxy, key_or_index)
154
- @source = proxy
169
+ @proxy = proxy
155
170
  @key_or_index = key_or_index
156
171
 
157
- mark_regarding! @source
172
+ mark_as_instance!
158
173
  end
159
174
 
160
175
  private def _restart
161
- @source.restart @key_or_index
176
+ @proxy.restart(@key_or_index)
162
177
  end
163
178
 
164
179
  private def _next_value
165
- @source.next_value(@key_or_index)
180
+ @proxy.next_value(@key_or_index)
166
181
  end
167
182
  end
168
183
 
@@ -566,7 +566,7 @@ module Musa
566
566
  end
567
567
 
568
568
  private def _next_value
569
- unless @have_current && @value.nil?
569
+ unless @sources.empty? || @have_current && @value.nil?
570
570
  pre_value = @sources.collect(&:peek_next_value)
571
571
 
572
572
  nils = 0
@@ -116,6 +116,10 @@ module Musa
116
116
  Anticipate.new self, &block
117
117
  end
118
118
 
119
+ def lazy(&block)
120
+ LazySerieEval.new self, &block
121
+ end
122
+
119
123
  ###
120
124
  ### Implementation
121
125
  ###
@@ -1006,6 +1010,45 @@ module Musa
1006
1010
  end
1007
1011
 
1008
1012
  private_constant :HashFromSeriesArray
1013
+
1014
+ class LazySerieEval
1015
+ include Serie.with(source: true, block: true)
1016
+
1017
+ def initialize(serie, &block)
1018
+ self.source = serie
1019
+ self.proc = block
1020
+
1021
+ init
1022
+ end
1023
+
1024
+ def source=(serie)
1025
+ super
1026
+ @processed = nil
1027
+ end
1028
+
1029
+ def proc(&block)
1030
+ super
1031
+ @processed = nil if block
1032
+ end
1033
+
1034
+ def proc=(block)
1035
+ super
1036
+ @processed = nil if block
1037
+ end
1038
+
1039
+ private def _restart
1040
+ @processed = nil
1041
+ @source.restart
1042
+ end
1043
+
1044
+ private def _next_value
1045
+ @processed ||= @block.call(@source)
1046
+ @processed.next_value
1047
+ end
1048
+ end
1049
+
1050
+ private_constant :LazySerieEval
1009
1051
  end
1052
+
1010
1053
  end
1011
1054
  end
@@ -7,10 +7,10 @@ module Musa
7
7
  end
8
8
 
9
9
  class ProxySerie
10
- include Series::Serie.with(source: true)
10
+ include Series::Serie.with(source: true, source_as: :proxy_source)
11
11
 
12
12
  def initialize(serie)
13
- self.source = serie
13
+ self.proxy_source = serie
14
14
  init
15
15
  end
16
16
 
@@ -27,15 +27,19 @@ module Musa
27
27
  end
28
28
 
29
29
  private def method_missing(method_name, *args, **key_args, &block)
30
- if @source && @source.respond_to?(method_name)
31
- @source.send method_name, *args, **key_args, &block
30
+ if @source
31
+ if @source.respond_to?(method_name)
32
+ @source.send method_name, *args, **key_args, &block
33
+ else
34
+ raise NoMethodError, "undefined method '#{method_name}' for proxied #{@source.to_s}"
35
+ end
32
36
  else
33
37
  super
34
38
  end
35
39
  end
36
40
 
37
41
  private def respond_to_missing?(method_name, include_private)
38
- @source && @source.respond_to?(method_name, include_private) # || super
42
+ @source && @source.respond_to?(method_name, include_private) # || super ??
39
43
  end
40
44
  end
41
45
  end
@@ -8,34 +8,48 @@ module Musa
8
8
  class Composer
9
9
  using Musa::Extension::Arrayfy
10
10
 
11
- attr_reader :inputs, :outputs
11
+ def initialize(inputs: [:input], outputs: [:output], auto_commit: nil, &block)
12
+ auto_commit = true if auto_commit.nil?
12
13
 
13
- def initialize(inputs: [:input], outputs: [:output], &block)
14
14
  @pipelines = {}
15
15
 
16
- @links = Set[]
17
- @links_from = {}
18
- @links_to = {}
16
+ def @pipelines.[]=(name, pipeline)
17
+ pipeline_to_add = @commited ? pipeline.commit! : pipeline
18
+ super(name, pipeline_to_add)
19
+ end
19
20
 
20
- @dsl = DSLContext.new(@pipelines, @links, @links_from, @links_to)
21
+ @dsl = DSLContext.new(@pipelines)
21
22
  @inputs = {}
22
23
  @outputs = {}
23
24
 
24
25
  inputs&.each do |input|
25
- @inputs[input] = Series::Constructors.PROXY
26
- @pipelines[input] = { input: nil, output: @inputs[input].buffered }
26
+ p = PROXY()
27
+ @inputs[input] = @pipelines[input] = Pipeline.new(input, input: p, output: p.buffered, pipelines: @pipelines)
27
28
 
28
29
  @dsl.define_singleton_method(input) { input }
29
30
  end
30
31
 
31
32
  outputs&.each do |output|
32
- @outputs[output] = Series::Constructors.PROXY
33
- @pipelines[output] = { input: @outputs[output], output: nil }
33
+ p = PROXY()
34
+ @outputs[output] = @pipelines[output] = Pipeline.new(output, is_output: true, input: p, output: p, pipelines: @pipelines)
34
35
 
35
36
  @dsl.define_singleton_method(output) { output }
36
37
  end
37
38
 
38
39
  @dsl.with &block if block
40
+ commit! if auto_commit
41
+ end
42
+
43
+ def input(name = nil)
44
+ name ||= :input
45
+ @inputs[name].input
46
+ end
47
+
48
+ def output(name = nil)
49
+ raise "Can't access output if the Composer is uncommited. Call '.commit' first." unless @commited
50
+
51
+ name ||= :output
52
+ @outputs[name].output
39
53
  end
40
54
 
41
55
  def route(from, to:, on: nil, as: nil)
@@ -50,15 +64,76 @@ module Musa
50
64
  @dsl.with &block
51
65
  end
52
66
 
67
+ def commit!
68
+ raise 'Already commited' if @commited
69
+
70
+ @outputs.each_value do |pipeline|
71
+ pipeline.commit!
72
+ end
73
+
74
+ @commited = true
75
+ end
76
+
77
+ class Pipeline
78
+ def initialize(name, is_output: false, input: nil, output: nil, first_proc: nil, chain_proc: nil, pipelines:)
79
+ @name = name
80
+ @is_output = is_output
81
+ @input = input
82
+ @output = output
83
+ @first_proc = first_proc
84
+ @chain_proc = chain_proc
85
+ @routes = {}
86
+ @pipelines = pipelines
87
+ end
88
+
89
+ attr_reader :name, :is_output
90
+ attr_accessor :input, :output, :proc
91
+
92
+ def [](on, as)
93
+ @routes[[on, as]]
94
+ end
95
+
96
+ def []=(on, as, source)
97
+ @routes[[on, as]] = Route.new(on, as, source)
98
+ end
99
+
100
+ def commit!
101
+ first_serie_operation = @first_proc&.call(NIL())
102
+ @input ||= first_serie_operation
103
+
104
+ @routes.each_value do |route|
105
+ route.source.commit!
106
+
107
+ if @is_output
108
+ @input.proxy_source = route.source.output.buffer
109
+ elsif route.as
110
+ @input.send(route.on)[route.as] = route.source.output.buffer
111
+ else
112
+ @input.send("#{route.on.to_s}=".to_sym, route.source.output.buffer)
113
+ end
114
+ end
115
+
116
+ chain_serie_operation = @chain_proc&.call(@input) || @input
117
+ @output ||= chain_serie_operation.buffered
118
+
119
+ self
120
+ end
121
+ end
122
+
123
+ class Route
124
+ def initialize(on, as, source)
125
+ @on = on
126
+ @as = as
127
+ @source = source
128
+ end
129
+ attr_accessor :on, :as, :source
130
+ end
131
+
53
132
  class DSLContext
54
133
  include Musa::Extension::With
55
134
 
56
- def initialize(pipelines, links, links_from, links_to)
135
+ def initialize(pipelines)
57
136
  @pipelines = pipelines
58
-
59
- @links = links
60
- @links_from = links_from
61
- @links_to = links_to
62
137
  end
63
138
 
64
139
  def route(from, to:, on: nil, as: nil)
@@ -68,69 +143,159 @@ module Musa
68
143
  raise ArgumentError, "Pipeline '#{from}' not found." unless from_pipeline
69
144
  raise ArgumentError, "Pipeline '#{to}' not found." unless to_pipeline
70
145
 
71
- @links_from[from] ||= Set[]
146
+ if to_pipeline.is_output && (on || as)
147
+ raise ArgumentError, "Output pipeline #{to_pipeline.name} only allows default routing"
148
+ end
72
149
 
73
- on ||= as ? :sources : :source
150
+ on ||= (as ? :sources : :source)
74
151
 
75
- raise ArgumentError, "Pipeline #{@links_to[[to, on, as]]} already connected to pipeline #{to} on #{on} as #{as}" if @links_to[[to, on, as]]
152
+ raise ArgumentError,
153
+ "Source of pipeline #{to} on #{on} as #{as} already connected to #{to_pipeline[on, as].source.name}" \
154
+ unless to_pipeline[on, as].nil?
76
155
 
77
- if as
78
- to_pipeline[:input].send(on)[as] = from_pipeline[:output].buffer
79
- else
80
- to_pipeline[:input].send("#{on.to_s}=".to_sym, from_pipeline[:output].buffer)
81
- end
82
156
 
83
- @links_from[from] << [to, on, as]
84
- @links_to[[to, on, as]] = from
85
- @links << [from, to, on, as]
157
+ to_pipeline[on, as] = from_pipeline
86
158
  end
87
159
 
88
160
  def pipeline(name, elements)
89
- first = last = nil
161
+ first, chain = parse(elements)
162
+ @pipelines[name] = Pipeline.new(name, first_proc: first, chain_proc: chain, pipelines: @pipelines)
90
163
 
91
- elements.each do |e|
92
- case e
93
- when Hash
94
- if e.size == 1
95
- operation = e.keys.first
96
- parameters = e.values.first
164
+ define_singleton_method(name) { name }
165
+ end
97
166
 
98
- if Musa::Series::Constructors.instance_methods.include?(operation)
99
- raise ArgumentError, "Called constructor '#{operation}' ignoring previous elements" unless last.nil?
167
+ private def parse(thing)
168
+ case thing
169
+ when Array
170
+ first = chain = nil
171
+
172
+ thing.each do |element|
173
+ case element
174
+ when Hash
175
+ new_chain = parse(element)
176
+ when Symbol
177
+ new_chain = operation_as_chained_proc(element, nil)
178
+ when Proc
179
+ new_chain = operation_as_chained_proc(:map, element)
180
+ else
181
+ raise ArgumentError, "Syntax error: don't know how to handle #{element}"
182
+ end
100
183
 
101
- last = Musa::Series::Constructors.method(operation).call(*parameters)
184
+ if first.nil?
185
+ first = new_chain unless first
186
+ else
187
+ chain = chain ? chain >> new_chain : new_chain
188
+ end
189
+ end
102
190
 
103
- elsif Musa::Series::Operations.instance_methods.include?(operation)
104
- first = last = Musa::Series::Constructors.PROXY if last.nil?
105
- last = last.send(operation, *parameters)
191
+ [first, chain]
106
192
 
107
- end
193
+ when Hash
194
+ if thing.size == 1
195
+ operation = thing.first[0] # key
196
+ parameter = thing.first[1] # value
197
+
198
+ if is_a_series_constructor?(operation)
199
+ operation_as_chained_proc(operation, parameter)
108
200
  else
109
- raise ArgumentError, "Don't know how to handle #{e}"
201
+ operation_as_chained_proc(operation, parse(parameter))
110
202
  end
111
- when Symbol
112
- first = last = Musa::Series::Constructors.PROXY if last.nil?
113
- # operation == e
114
- last = last.send(e) if Musa::Series::Operations.instance_methods.include?(e)
203
+ else
204
+ raise ArgumentError, "Syntax error: don't know how to handle #{element}"
115
205
  end
116
206
 
117
- first ||= last
207
+ when Symbol
208
+ operation_as_chained_proc(operation)
209
+
210
+ when Proc
211
+ thing
212
+
213
+ else
214
+ thing
118
215
  end
216
+ end
119
217
 
120
- @pipelines[name] = { input: first, output: last.buffered }
218
+ private def operation_as_chained_proc(operation, parameter = nil)
219
+ if is_a_series_constructor?(operation)
220
+ proc do |last|
221
+ call_constructor_according_to_last_and_parameter(last, operation, parameter)
222
+ end
121
223
 
122
- define_singleton_method(name) { name }
224
+ elsif is_a_series_operation?(operation)
225
+ proc { |last| call_operation_according_to_parameter(last, operation, parameter) }
226
+
227
+ else
228
+ # non-series operation
229
+ proc { |last| call_operation_according_to_parameter(last, operation, parameter) }
230
+ end
123
231
  end
124
232
 
125
- private def method_missing(symbol, *args, &block)
126
- if Musa::Series::Operations.instance_methods.include?(symbol)
127
- symbol
128
- elsif Musa::Series::Constructors.instance_methods.include?(symbol)
129
- symbol
233
+ private def call_constructor_according_to_last_and_parameter(last, constructor, parameter)
234
+ case last
235
+ when Proc
236
+ call_constructor_according_to_last_and_parameter(last.call, constructor, parameter)
237
+
238
+ when Serie
239
+ # TODO: ignoring last, should make an error?
240
+ Musa::Series::Constructors.method(constructor).call(*parameter)
241
+
242
+ when nil
243
+ Musa::Series::Constructors.method(constructor).call(*parameter)
244
+
245
+ when Array
246
+ raise "Unexpected parameter #{parameter} for constructor #{constructor} " \
247
+ "because the previous operation on the pipeline chain returned non-nil #{last}" \
248
+ unless parameter.nil?
249
+
250
+ Musa::Series::Constructors.method(constructor).call(*last)
251
+
252
+ when Hash
253
+ raise "Unexpected parameter #{parameter} for constructor #{constructor} " \
254
+ "because the previous operation on the pipeline chain returned non-nil #{last}" \
255
+ unless parameter.nil?
256
+
257
+ Musa::Series::Constructors.method(constructor).call(**last)
258
+
130
259
  else
131
- raise ArgumentError, "Pipeline '#{symbol}' is undefined" if args.empty?
260
+ raise ArgumentError, "Don't know how to handle last #{last}"
261
+ end
262
+ end
263
+
264
+ private def call_operation_according_to_parameter(target, operation, parameter)
265
+ case parameter
266
+ when nil
267
+ target.send(operation)
268
+ when Symbol
269
+ target.send(operation).send(parameter)
270
+ when Proc
271
+ target.send(operation, &parameter)
272
+ when Array
273
+ unless parameter.size == 2 && parameter.all? { |_| _.is_a?(Proc) }
274
+ raise ArgumentError, "Don't know how to handle parameter #{parameter}"
275
+ end
132
276
 
277
+ target.send(operation, &(parameter.first >> parameter.last))
278
+ else
279
+ target.send(operation, parameter)
280
+ end
281
+ end
282
+
283
+ private def is_a_series_constructor?(operation)
284
+ Musa::Series::Constructors.instance_methods.include?(operation)
285
+ end
286
+
287
+ private def is_a_series_operation?(operation)
288
+ Musa::Series::Operations.instance_methods.include?(operation)
289
+ end
290
+
291
+ private def method_missing(symbol, *args, &block)
292
+ if is_a_series_constructor?(symbol) || is_a_series_operation?(symbol)
293
+ symbol
294
+ elsif args.any? || block
295
+ args += [block] if block
133
296
  pipeline(symbol, args)
297
+ else # for non-series methods
298
+ symbol
134
299
  end
135
300
  end
136
301
 
@@ -142,6 +307,8 @@ module Musa
142
307
  end
143
308
  end
144
309
 
310
+ private_constant :Pipeline
311
+ private_constant :Route
145
312
  private_constant :DSLContext
146
313
  end
147
314
  end
data/musa-dsl.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'musa-dsl'
3
- s.version = '0.23.1'
4
- s.date = '2021-07-02'
3
+ s.version = '0.23.3'
4
+ s.date = '2021-07-28'
5
5
  s.summary = 'A simple Ruby DSL for making complex music'
6
6
  s.description = 'Musa-DSL: A Ruby framework and DSL for algorithmic sound and musical thinking and composition'
7
7
  s.authors = ['Javier Sánchez Yeste']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: musa-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.1
4
+ version: 0.23.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Sánchez Yeste
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-02 00:00:00.000000000 Z
11
+ date: 2021-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: citrus