musa-dsl 0.23.1 → 0.23.3

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 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