musa-dsl 0.30.2 → 0.40.0
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/.gitignore +3 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- metadata +87 -8
|
@@ -2,13 +2,243 @@ require_relative '../core-ext/deep-copy'
|
|
|
2
2
|
require_relative '../generative/generative-grammar'
|
|
3
3
|
|
|
4
4
|
module Musa
|
|
5
|
+
# Core Serie infrastructure providing prototype/instance system and base implementation.
|
|
6
|
+
#
|
|
7
|
+
# This module defines the fundamental architecture for all Series in Musa DSL:
|
|
8
|
+
#
|
|
9
|
+
# ## Core Concepts
|
|
10
|
+
#
|
|
11
|
+
# ### Prototype/Instance Pattern
|
|
12
|
+
#
|
|
13
|
+
# Series use a **prototype/instance pattern** to enable reusable series definitions:
|
|
14
|
+
#
|
|
15
|
+
# - **Prototype**: Template/blueprint for series (cannot be consumed)
|
|
16
|
+
# - **Instance**: Cloned copy ready for consumption (can call next_value)
|
|
17
|
+
# - **Undefined**: Series with unresolved dependencies
|
|
18
|
+
#
|
|
19
|
+
# ### Serie States
|
|
20
|
+
#
|
|
21
|
+
# Every serie exists in one of three states:
|
|
22
|
+
#
|
|
23
|
+
# - **:prototype** - Template state, cannot consume, can create instances
|
|
24
|
+
# - **:instance** - Active state, can consume values, has independent state
|
|
25
|
+
# - **:undefined** - Unresolved state, cannot use until dependencies resolve
|
|
26
|
+
#
|
|
27
|
+
# ### Why Prototype/Instance?
|
|
28
|
+
#
|
|
29
|
+
# Enables **reusable series definitions** without re-evaluating constructors:
|
|
30
|
+
#
|
|
31
|
+
# ```ruby
|
|
32
|
+
# # Define once (prototype)
|
|
33
|
+
# melody = S(60, 64, 67, 72)
|
|
34
|
+
#
|
|
35
|
+
# # Use multiple times (instances)
|
|
36
|
+
# voice1 = melody.instance # Independent playback
|
|
37
|
+
# voice2 = melody.instance # Separate playback
|
|
38
|
+
# ```
|
|
39
|
+
#
|
|
40
|
+
# ## Integration
|
|
41
|
+
#
|
|
42
|
+
# Series integrate with:
|
|
43
|
+
#
|
|
44
|
+
# - **Sequencer**: Play series over time via play() method
|
|
45
|
+
# - **Generative**: Use with generative grammars
|
|
46
|
+
# - **MIDI**: Generate MIDI events from series
|
|
47
|
+
# - **Datasets**: Use AbsTimed, AbsD for timing
|
|
48
|
+
#
|
|
49
|
+
# ## Module Architecture
|
|
50
|
+
#
|
|
51
|
+
# ### Serie Module
|
|
52
|
+
#
|
|
53
|
+
# Factory module providing:
|
|
54
|
+
#
|
|
55
|
+
# - **Serie.base**: Base module without source/sources
|
|
56
|
+
# - **Serie.with**: Configurable module with source/sources/block
|
|
57
|
+
#
|
|
58
|
+
# ### Prototyping Module
|
|
59
|
+
#
|
|
60
|
+
# Implements prototype/instance lifecycle:
|
|
61
|
+
#
|
|
62
|
+
# - State management (:prototype, :instance, :undefined)
|
|
63
|
+
# - Cloning (prototype -> instance)
|
|
64
|
+
# - State resolution from sources
|
|
65
|
+
# - Validation and permissions
|
|
66
|
+
#
|
|
67
|
+
# ### SerieImplementation Module
|
|
68
|
+
#
|
|
69
|
+
# Core iteration protocol:
|
|
70
|
+
#
|
|
71
|
+
# - **init**: Initialize instance state
|
|
72
|
+
# - **restart**: Reset to beginning
|
|
73
|
+
# - **next_value**: Consume next element
|
|
74
|
+
# - **peek_next_value**: Look ahead without consuming
|
|
75
|
+
# - **current_value**: Last consumed value
|
|
76
|
+
#
|
|
77
|
+
# ## Serie Protocol
|
|
78
|
+
#
|
|
79
|
+
# All series must implement:
|
|
80
|
+
#
|
|
81
|
+
# ```ruby
|
|
82
|
+
# def _next_value
|
|
83
|
+
# # Return next value or nil when finished
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# def _init
|
|
87
|
+
# # Initialize instance state (optional)
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# def _restart
|
|
91
|
+
# # Reset to beginning (optional)
|
|
92
|
+
# end
|
|
93
|
+
# ```
|
|
94
|
+
#
|
|
95
|
+
# ## Usage Patterns
|
|
96
|
+
#
|
|
97
|
+
# ### Basic Prototype/Instance
|
|
98
|
+
#
|
|
99
|
+
# ```ruby
|
|
100
|
+
# # Create prototype
|
|
101
|
+
# proto = S(1, 2, 3)
|
|
102
|
+
# proto.prototype? # => true
|
|
103
|
+
#
|
|
104
|
+
# # Create instance
|
|
105
|
+
# inst = proto.instance # or proto.i
|
|
106
|
+
# inst.instance? # => true
|
|
107
|
+
#
|
|
108
|
+
# # Consume values
|
|
109
|
+
# inst.next_value # => 1
|
|
110
|
+
# inst.next_value # => 2
|
|
111
|
+
# ```
|
|
112
|
+
#
|
|
113
|
+
# ### Multiple Instances
|
|
114
|
+
#
|
|
115
|
+
# ```ruby
|
|
116
|
+
# proto = S(1, 2, 3)
|
|
117
|
+
#
|
|
118
|
+
# a = proto.i
|
|
119
|
+
# b = proto.i # Independent instance
|
|
120
|
+
#
|
|
121
|
+
# a.next_value # => 1
|
|
122
|
+
# b.next_value # => 1 (independent)
|
|
123
|
+
# ```
|
|
124
|
+
#
|
|
125
|
+
# ### State Resolution
|
|
126
|
+
#
|
|
127
|
+
# ```ruby
|
|
128
|
+
# proxy = PROXY() # Undefined - no source yet
|
|
129
|
+
# proxy.undefined? # => true
|
|
130
|
+
#
|
|
131
|
+
# proxy.source = S(1, 2, 3) # Becomes prototype
|
|
132
|
+
# proxy.prototype? # => true
|
|
133
|
+
# ```
|
|
134
|
+
#
|
|
135
|
+
# ## Technical Details
|
|
136
|
+
#
|
|
137
|
+
# ### Peek Mechanism
|
|
138
|
+
#
|
|
139
|
+
# `peek_next_value` uses internal buffering to look ahead without state change:
|
|
140
|
+
#
|
|
141
|
+
# ```ruby
|
|
142
|
+
# s = S(1, 2, 3).i
|
|
143
|
+
# s.peek_next_value # => 1 (buffered)
|
|
144
|
+
# s.peek_next_value # => 1 (same)
|
|
145
|
+
# s.next_value # => 1 (consumes buffered)
|
|
146
|
+
# ```
|
|
147
|
+
#
|
|
148
|
+
# ### Source/Sources Pattern
|
|
149
|
+
#
|
|
150
|
+
# Series can depend on:
|
|
151
|
+
#
|
|
152
|
+
# - **source**: Single upstream serie
|
|
153
|
+
# - **sources**: Multiple upstream series (Array or Hash)
|
|
154
|
+
#
|
|
155
|
+
# State automatically resolves based on dependencies:
|
|
156
|
+
#
|
|
157
|
+
# - All sources :prototype → :prototype
|
|
158
|
+
# - All sources :instance → :instance
|
|
159
|
+
# - Mixed or undefined → :undefined
|
|
160
|
+
#
|
|
161
|
+
# ### Deep Copy Support
|
|
162
|
+
#
|
|
163
|
+
# Uses Musa::Extension::DeepCopy for proper cloning including nested structures.
|
|
164
|
+
#
|
|
165
|
+
# ## Applications
|
|
166
|
+
#
|
|
167
|
+
# - Reusable melodic/harmonic patterns
|
|
168
|
+
# - Multiple voices from single definition
|
|
169
|
+
# - Lazy evaluation of algorithmic sequences
|
|
170
|
+
# - Composable transformations
|
|
171
|
+
# - Memory-efficient sequence playback
|
|
172
|
+
#
|
|
173
|
+
# @see Musa::Series::Constructors Serie constructor methods
|
|
174
|
+
# @see Musa::Series::Operations Serie transformation operations
|
|
175
|
+
#
|
|
176
|
+
# @api public
|
|
5
177
|
module Series
|
|
178
|
+
# Series constructor methods.
|
|
179
|
+
#
|
|
180
|
+
# @api public
|
|
6
181
|
module Constructors; extend self; end
|
|
182
|
+
|
|
183
|
+
# Serie transformation operations.
|
|
184
|
+
#
|
|
185
|
+
# @api public
|
|
7
186
|
module Operations; end
|
|
8
187
|
|
|
9
188
|
include Constructors
|
|
10
189
|
|
|
190
|
+
# Serie module factory providing configurable serie modules.
|
|
191
|
+
#
|
|
192
|
+
# Creates modules dynamically based on requirements:
|
|
193
|
+
#
|
|
194
|
+
# - **Serie.base**: Minimal module without dependencies
|
|
195
|
+
# - **Serie.with**: Configured module with source/sources/block
|
|
196
|
+
#
|
|
197
|
+
# ## Factory Pattern
|
|
198
|
+
#
|
|
199
|
+
# Serie.with generates modules at runtime with specific features:
|
|
200
|
+
#
|
|
201
|
+
# ```ruby
|
|
202
|
+
# # Generate module with source support
|
|
203
|
+
# include Serie.with(source: true, source_as: :upstream)
|
|
204
|
+
#
|
|
205
|
+
# # Now has @source, #upstream, #upstream= methods
|
|
206
|
+
# ```
|
|
207
|
+
#
|
|
208
|
+
# ## Configuration Options
|
|
209
|
+
#
|
|
210
|
+
# - **source**: Single upstream serie dependency
|
|
211
|
+
# - **sources**: Multiple upstream serie dependencies
|
|
212
|
+
# - **block**: Block/proc attribute
|
|
213
|
+
# - **smart_block**: SmartProcBinder-wrapped block
|
|
214
|
+
#
|
|
215
|
+
# ## Musical Applications
|
|
216
|
+
#
|
|
217
|
+
# Used internally by serie implementations to declare dependencies
|
|
218
|
+
# and automatically handle prototype/instance propagation.
|
|
219
|
+
#
|
|
220
|
+
# @see SerieImplementation Core serie protocol
|
|
221
|
+
# @see Prototyping Prototype/instance state management
|
|
222
|
+
#
|
|
223
|
+
# @api private
|
|
11
224
|
module Serie
|
|
225
|
+
# Creates base serie module without dependencies.
|
|
226
|
+
#
|
|
227
|
+
# Minimal module for series that generate values without upstream
|
|
228
|
+
# sources (e.g., array-backed series, value generators).
|
|
229
|
+
#
|
|
230
|
+
# @return [Module] base module with SerieImplementation
|
|
231
|
+
#
|
|
232
|
+
# @example Base serie
|
|
233
|
+
# class SimpleSerie
|
|
234
|
+
# include Serie.base
|
|
235
|
+
#
|
|
236
|
+
# def _next_value
|
|
237
|
+
# # Generate value
|
|
238
|
+
# end
|
|
239
|
+
# end
|
|
240
|
+
#
|
|
241
|
+
# @api private
|
|
12
242
|
def self.base
|
|
13
243
|
Module.new do
|
|
14
244
|
include SerieImplementation
|
|
@@ -21,6 +251,78 @@ module Musa
|
|
|
21
251
|
end
|
|
22
252
|
end
|
|
23
253
|
|
|
254
|
+
# Creates configurable serie module with specified features.
|
|
255
|
+
#
|
|
256
|
+
# Factory method generating modules with:
|
|
257
|
+
#
|
|
258
|
+
# - Single source dependency (source: true)
|
|
259
|
+
# - Multiple sources dependency (sources: true)
|
|
260
|
+
# - Block attribute (block: true, smart_block: true)
|
|
261
|
+
#
|
|
262
|
+
# ## Source Support
|
|
263
|
+
#
|
|
264
|
+
# **source: true** adds:
|
|
265
|
+
#
|
|
266
|
+
# - `@source` instance variable
|
|
267
|
+
# - `#source` getter (or custom name via source_as:)
|
|
268
|
+
# - `#source=` setter with state validation
|
|
269
|
+
# - Automatic prototype/instance propagation
|
|
270
|
+
#
|
|
271
|
+
# ## Sources Support
|
|
272
|
+
#
|
|
273
|
+
# **sources: true** adds:
|
|
274
|
+
#
|
|
275
|
+
# - `@sources` instance variable (Hash or Array)
|
|
276
|
+
# - `#sources` getter/setter
|
|
277
|
+
# - Automatic state resolution from all sources
|
|
278
|
+
#
|
|
279
|
+
# ## Block Support
|
|
280
|
+
#
|
|
281
|
+
# **block: true** - Simple proc attribute
|
|
282
|
+
# **smart_block: true** - SmartProcBinder-wrapped block
|
|
283
|
+
#
|
|
284
|
+
# ## State Propagation
|
|
285
|
+
#
|
|
286
|
+
# Sources automatically propagate state:
|
|
287
|
+
#
|
|
288
|
+
# - Setting source to :prototype → marks self as :prototype
|
|
289
|
+
# - Setting source to :instance → marks self as :instance
|
|
290
|
+
# - Cloning propagates through source/sources
|
|
291
|
+
#
|
|
292
|
+
# @param source [Boolean] add single source dependency
|
|
293
|
+
# @param source_as [Symbol, nil] custom name for source attribute
|
|
294
|
+
# @param private_source [Boolean, nil] make source methods private
|
|
295
|
+
# @param mandatory_source [Boolean, nil] require source to be set
|
|
296
|
+
# @param sources [Boolean] add multiple sources dependency
|
|
297
|
+
# @param sources_as [Symbol, nil] custom name for sources attribute
|
|
298
|
+
# @param private_sources [Boolean, nil] make sources methods private
|
|
299
|
+
# @param mandatory_sources [Boolean, nil] require sources to be set
|
|
300
|
+
# @param smart_block [Boolean] add SmartProcBinder block support
|
|
301
|
+
# @param block [Boolean] add simple block support
|
|
302
|
+
# @param block_as [Symbol, nil] custom name for block attribute
|
|
303
|
+
#
|
|
304
|
+
# @return [Module] configured module with SerieImplementation
|
|
305
|
+
#
|
|
306
|
+
# @example Serie with single source
|
|
307
|
+
# class ReverseSerie
|
|
308
|
+
# include Serie.with(source: true)
|
|
309
|
+
#
|
|
310
|
+
# def _next_value
|
|
311
|
+
# # Process source.next_value
|
|
312
|
+
# end
|
|
313
|
+
# end
|
|
314
|
+
#
|
|
315
|
+
# @example Serie with block
|
|
316
|
+
# class MapSerie
|
|
317
|
+
# include Serie.with(source: true, smart_block: true)
|
|
318
|
+
#
|
|
319
|
+
# def _next_value
|
|
320
|
+
# value = source.next_value
|
|
321
|
+
# value ? @block.call(value) : nil
|
|
322
|
+
# end
|
|
323
|
+
# end
|
|
324
|
+
#
|
|
325
|
+
# @api private
|
|
24
326
|
def self.with(source: false,
|
|
25
327
|
source_as: nil,
|
|
26
328
|
private_source: nil,
|
|
@@ -130,31 +432,148 @@ module Musa
|
|
|
130
432
|
end
|
|
131
433
|
end
|
|
132
434
|
|
|
435
|
+
# Prototype/instance state management for Series.
|
|
436
|
+
#
|
|
437
|
+
# Implements the prototype/instance pattern enabling reusable serie
|
|
438
|
+
# definitions. Every serie exists in one of three states:
|
|
439
|
+
#
|
|
440
|
+
# ## States
|
|
441
|
+
#
|
|
442
|
+
# - **:prototype** - Template state, cannot consume, can create instances
|
|
443
|
+
# - **:instance** - Active state, can consume values, has independent state
|
|
444
|
+
# - **:undefined** - Unresolved state, dependencies not yet resolved
|
|
445
|
+
#
|
|
446
|
+
# ## State Queries
|
|
447
|
+
#
|
|
448
|
+
# ```ruby
|
|
449
|
+
# serie.state # => :prototype | :instance | :undefined
|
|
450
|
+
# serie.prototype? # => true if prototype
|
|
451
|
+
# serie.instance? # => true if instance
|
|
452
|
+
# serie.undefined? # => true if undefined
|
|
453
|
+
# ```
|
|
454
|
+
#
|
|
455
|
+
# ## State Transitions
|
|
456
|
+
#
|
|
457
|
+
# ### Prototype Creation
|
|
458
|
+
#
|
|
459
|
+
# Created by constructors (S, E, RND, etc.):
|
|
460
|
+
#
|
|
461
|
+
# ```ruby
|
|
462
|
+
# proto = S(1, 2, 3)
|
|
463
|
+
# proto.prototype? # => true
|
|
464
|
+
# ```
|
|
465
|
+
#
|
|
466
|
+
# ### Instance Creation
|
|
467
|
+
#
|
|
468
|
+
# Via `.instance` (or `.i` alias):
|
|
469
|
+
#
|
|
470
|
+
# ```ruby
|
|
471
|
+
# inst = proto.instance
|
|
472
|
+
# inst.instance? # => true
|
|
473
|
+
# ```
|
|
474
|
+
#
|
|
475
|
+
# ### State Resolution
|
|
476
|
+
#
|
|
477
|
+
# Undefined series resolve state from sources:
|
|
478
|
+
#
|
|
479
|
+
# ```ruby
|
|
480
|
+
# proxy = PROXY() # Undefined
|
|
481
|
+
# proxy.source = S(1, 2, 3) # Becomes prototype
|
|
482
|
+
# ```
|
|
483
|
+
#
|
|
484
|
+
# ## Cloning Behavior
|
|
485
|
+
#
|
|
486
|
+
# Creating instance clones the serie and all dependencies:
|
|
487
|
+
#
|
|
488
|
+
# ```ruby
|
|
489
|
+
# proto = S(1, 2, 3).map { |x| x * 2 }
|
|
490
|
+
# inst = proto.instance # Clones both map and S
|
|
491
|
+
# ```
|
|
492
|
+
#
|
|
493
|
+
# ## Validation
|
|
494
|
+
#
|
|
495
|
+
# Operations check state permissions:
|
|
496
|
+
# - `next_value`, `restart` require :instance
|
|
497
|
+
# - `infinite?`, `to_a` allow :prototype
|
|
498
|
+
# - Undefined state raises PrototypingError
|
|
499
|
+
#
|
|
500
|
+
# ## Musical Applications
|
|
501
|
+
#
|
|
502
|
+
# - Reusable melodic patterns
|
|
503
|
+
# - Multiple independent playbacks
|
|
504
|
+
# - Lazy definition of transformations
|
|
505
|
+
# - Memory-efficient pattern libraries
|
|
506
|
+
#
|
|
507
|
+
# @api private
|
|
133
508
|
module Prototyping
|
|
509
|
+
# Returns current state of serie.
|
|
510
|
+
#
|
|
511
|
+
# Attempts to resolve undefined state from sources before returning.
|
|
512
|
+
# State is one of: :prototype, :instance, or :undefined.
|
|
513
|
+
#
|
|
514
|
+
# @return [Symbol] current state (:prototype, :instance, :undefined)
|
|
515
|
+
#
|
|
516
|
+
# @api public
|
|
134
517
|
def state
|
|
135
518
|
try_to_resolve_undefined_state_if_needed
|
|
136
519
|
@state || :undefined
|
|
137
520
|
end
|
|
138
521
|
|
|
522
|
+
# Checks if serie is in prototype state.
|
|
523
|
+
#
|
|
524
|
+
# @return [Boolean] true if prototype, false otherwise
|
|
525
|
+
#
|
|
526
|
+
# @api public
|
|
139
527
|
def prototype?
|
|
140
528
|
try_to_resolve_undefined_state_if_needed
|
|
141
529
|
@state&.==(:prototype)
|
|
142
530
|
end
|
|
143
531
|
|
|
532
|
+
# Checks if serie is in instance state.
|
|
533
|
+
#
|
|
534
|
+
# @return [Boolean] true if instance, false otherwise
|
|
535
|
+
#
|
|
536
|
+
# @api public
|
|
144
537
|
def instance?
|
|
145
538
|
try_to_resolve_undefined_state_if_needed
|
|
146
539
|
@state&.==(:instance)
|
|
147
540
|
end
|
|
148
541
|
|
|
542
|
+
# Checks if serie is in undefined state.
|
|
543
|
+
#
|
|
544
|
+
# @return [Boolean] true if undefined, false otherwise
|
|
545
|
+
#
|
|
546
|
+
# @api public
|
|
149
547
|
def undefined?
|
|
150
548
|
try_to_resolve_undefined_state_if_needed
|
|
151
549
|
@state.nil? || @state == :undefined
|
|
152
550
|
end
|
|
153
551
|
|
|
552
|
+
# Checks if serie state is defined (not undefined).
|
|
553
|
+
#
|
|
554
|
+
# @return [Boolean] true if prototype or instance, false if undefined
|
|
555
|
+
#
|
|
556
|
+
# @api public
|
|
154
557
|
def defined?
|
|
155
558
|
!undefined?
|
|
156
559
|
end
|
|
157
560
|
|
|
561
|
+
# Returns prototype of serie.
|
|
562
|
+
#
|
|
563
|
+
# - If already prototype, returns self
|
|
564
|
+
# - If instance, returns original prototype (if available)
|
|
565
|
+
# - If undefined, raises PrototypingError
|
|
566
|
+
#
|
|
567
|
+
# @return [Serie] prototype serie
|
|
568
|
+
#
|
|
569
|
+
# @raise [PrototypingError] if serie is undefined
|
|
570
|
+
#
|
|
571
|
+
# @example Get prototype
|
|
572
|
+
# proto = S(1, 2, 3)
|
|
573
|
+
# inst = proto.instance
|
|
574
|
+
# inst.prototype # => proto
|
|
575
|
+
#
|
|
576
|
+
# @api public
|
|
158
577
|
def prototype
|
|
159
578
|
try_to_resolve_undefined_state_if_needed
|
|
160
579
|
|
|
@@ -170,8 +589,41 @@ module Musa
|
|
|
170
589
|
end
|
|
171
590
|
end
|
|
172
591
|
|
|
592
|
+
# Short alias for {#prototype}.
|
|
593
|
+
#
|
|
594
|
+
# @return [Serie] prototype serie
|
|
595
|
+
#
|
|
596
|
+
# @api public
|
|
173
597
|
alias_method :p, :prototype
|
|
174
598
|
|
|
599
|
+
# Creates or returns instance of serie.
|
|
600
|
+
#
|
|
601
|
+
# - If already instance, returns self
|
|
602
|
+
# - If prototype, creates new instance by cloning
|
|
603
|
+
# - If undefined, raises PrototypingError
|
|
604
|
+
#
|
|
605
|
+
# ## Cloning Process
|
|
606
|
+
#
|
|
607
|
+
# 1. Clones serie structure
|
|
608
|
+
# 2. Marks clone as :instance
|
|
609
|
+
# 3. Propagates instance creation to sources
|
|
610
|
+
# 4. Calls init if defined
|
|
611
|
+
#
|
|
612
|
+
# Each call creates independent instance with separate state.
|
|
613
|
+
#
|
|
614
|
+
# @return [Serie] instance serie
|
|
615
|
+
#
|
|
616
|
+
# @raise [PrototypingError] if serie is undefined
|
|
617
|
+
#
|
|
618
|
+
# @example Create instances
|
|
619
|
+
# proto = S(1, 2, 3)
|
|
620
|
+
# a = proto.instance
|
|
621
|
+
# b = proto.instance # Different instance
|
|
622
|
+
#
|
|
623
|
+
# a.next_value # => 1
|
|
624
|
+
# b.next_value # => 1 (independent)
|
|
625
|
+
#
|
|
626
|
+
# @api public
|
|
175
627
|
def instance
|
|
176
628
|
try_to_resolve_undefined_state_if_needed
|
|
177
629
|
|
|
@@ -190,14 +642,27 @@ module Musa
|
|
|
190
642
|
end
|
|
191
643
|
end
|
|
192
644
|
|
|
645
|
+
# Short alias for {#instance}.
|
|
646
|
+
#
|
|
647
|
+
# @return [Serie] instance serie
|
|
648
|
+
#
|
|
649
|
+
# @api public
|
|
193
650
|
alias_method :i, :instance
|
|
194
651
|
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
652
|
+
# Converts serie and dependencies to prototype state.
|
|
653
|
+
#
|
|
654
|
+
# Called automatically during cloning. By default, handles @source and
|
|
655
|
+
# @sources attributes automatically. Subclasses can override to add
|
|
656
|
+
# custom prototyping logic.
|
|
657
|
+
#
|
|
658
|
+
# ## Default Behavior
|
|
659
|
+
#
|
|
660
|
+
# - Calls `.prototype` on @source if present
|
|
661
|
+
# - Calls `.prototype` on all @sources elements (Array or Hash)
|
|
200
662
|
#
|
|
663
|
+
# @return [void]
|
|
664
|
+
#
|
|
665
|
+
# @api private
|
|
201
666
|
protected def _prototype!
|
|
202
667
|
@source = @source.prototype if @source
|
|
203
668
|
|
|
@@ -209,6 +674,20 @@ module Musa
|
|
|
209
674
|
end
|
|
210
675
|
end
|
|
211
676
|
|
|
677
|
+
# Converts serie and dependencies to instance state.
|
|
678
|
+
#
|
|
679
|
+
# Called automatically during instance creation. By default, handles
|
|
680
|
+
# @source and @sources attributes automatically. Subclasses can
|
|
681
|
+
# override to add custom instancing logic.
|
|
682
|
+
#
|
|
683
|
+
# ## Default Behavior
|
|
684
|
+
#
|
|
685
|
+
# - Calls `.instance` on @source if present
|
|
686
|
+
# - Calls `.instance` on all @sources elements (Array or Hash)
|
|
687
|
+
#
|
|
688
|
+
# @return [void]
|
|
689
|
+
#
|
|
690
|
+
# @api private
|
|
212
691
|
protected def _instance!
|
|
213
692
|
@source = @source.instance if @source
|
|
214
693
|
|
|
@@ -220,6 +699,15 @@ module Musa
|
|
|
220
699
|
end
|
|
221
700
|
end
|
|
222
701
|
|
|
702
|
+
# Marks serie with specified state.
|
|
703
|
+
#
|
|
704
|
+
# @param state [Symbol, nil] desired state (:prototype, :instance, :undefined, nil)
|
|
705
|
+
#
|
|
706
|
+
# @return [void]
|
|
707
|
+
#
|
|
708
|
+
# @raise [ArgumentError] if state is not recognized
|
|
709
|
+
#
|
|
710
|
+
# @api private
|
|
223
711
|
protected def mark_as!(state)
|
|
224
712
|
case state
|
|
225
713
|
when nil, :undefined
|
|
@@ -233,6 +721,18 @@ module Musa
|
|
|
233
721
|
end
|
|
234
722
|
end
|
|
235
723
|
|
|
724
|
+
# Marks serie state based on source state.
|
|
725
|
+
#
|
|
726
|
+
# Propagates state from source:
|
|
727
|
+
# - Source nil/undefined → mark as undefined
|
|
728
|
+
# - Source prototype → mark as prototype
|
|
729
|
+
# - Source instance → mark as instance
|
|
730
|
+
#
|
|
731
|
+
# @param source [Serie, nil] source serie
|
|
732
|
+
#
|
|
733
|
+
# @return [void]
|
|
734
|
+
#
|
|
735
|
+
# @api private
|
|
236
736
|
protected def mark_regarding!(source)
|
|
237
737
|
if source.nil? || source.undefined?
|
|
238
738
|
mark_as_undefined!
|
|
@@ -243,11 +743,23 @@ module Musa
|
|
|
243
743
|
end
|
|
244
744
|
end
|
|
245
745
|
|
|
746
|
+
# Marks serie as undefined.
|
|
747
|
+
#
|
|
748
|
+
# @return [Serie] self
|
|
749
|
+
#
|
|
750
|
+
# @api private
|
|
246
751
|
protected def mark_as_undefined!
|
|
247
752
|
@state = :undefined
|
|
248
753
|
self
|
|
249
754
|
end
|
|
250
755
|
|
|
756
|
+
# Marks serie as prototype.
|
|
757
|
+
#
|
|
758
|
+
# Calls _sources_resolved if state changed.
|
|
759
|
+
#
|
|
760
|
+
# @return [Serie] self
|
|
761
|
+
#
|
|
762
|
+
# @api private
|
|
251
763
|
protected def mark_as_prototype!
|
|
252
764
|
notify = @state != :prototype
|
|
253
765
|
|
|
@@ -257,6 +769,15 @@ module Musa
|
|
|
257
769
|
self
|
|
258
770
|
end
|
|
259
771
|
|
|
772
|
+
# Marks serie as instance.
|
|
773
|
+
#
|
|
774
|
+
# Calls _sources_resolved if state changed.
|
|
775
|
+
#
|
|
776
|
+
# @param prototype [Serie, nil] original prototype
|
|
777
|
+
#
|
|
778
|
+
# @return [Serie] self
|
|
779
|
+
#
|
|
780
|
+
# @api private
|
|
260
781
|
protected def mark_as_instance!(prototype = nil)
|
|
261
782
|
notify = @state != :instance
|
|
262
783
|
|
|
@@ -267,8 +788,28 @@ module Musa
|
|
|
267
788
|
self
|
|
268
789
|
end
|
|
269
790
|
|
|
791
|
+
# Hook called when sources are resolved to defined state.
|
|
792
|
+
#
|
|
793
|
+
# Subclasses can override to perform actions when state becomes
|
|
794
|
+
# defined (e.g., validate configuration, initialize caches).
|
|
795
|
+
#
|
|
796
|
+
# @return [void]
|
|
797
|
+
#
|
|
798
|
+
# @api private
|
|
270
799
|
protected def _sources_resolved; end
|
|
271
800
|
|
|
801
|
+
# Attempts to resolve undefined state from sources.
|
|
802
|
+
#
|
|
803
|
+
# Called automatically before state queries. Resolves state based on
|
|
804
|
+
# @source and @sources dependencies:
|
|
805
|
+
#
|
|
806
|
+
# - All sources :prototype → :prototype
|
|
807
|
+
# - All sources :instance → :instance
|
|
808
|
+
# - Mixed or any undefined → :undefined
|
|
809
|
+
#
|
|
810
|
+
# @return [void]
|
|
811
|
+
#
|
|
812
|
+
# @api private
|
|
272
813
|
private def try_to_resolve_undefined_state_if_needed
|
|
273
814
|
|
|
274
815
|
return unless @state.nil? || @state == :undefined
|
|
@@ -323,7 +864,36 @@ module Musa
|
|
|
323
864
|
mark_as!(new_state)
|
|
324
865
|
end
|
|
325
866
|
|
|
867
|
+
# Error raised when serie is used in wrong state.
|
|
868
|
+
#
|
|
869
|
+
# Raised when attempting to consume a prototype serie or perform
|
|
870
|
+
# operations on undefined serie.
|
|
871
|
+
#
|
|
872
|
+
# ## Common Scenarios
|
|
873
|
+
#
|
|
874
|
+
# - Calling `next_value` on prototype
|
|
875
|
+
# - Calling `restart` on prototype
|
|
876
|
+
# - Using undefined serie
|
|
877
|
+
#
|
|
878
|
+
# ## Solution
|
|
879
|
+
#
|
|
880
|
+
# Call `.instance` (or `.i`) to create consumable instance:
|
|
881
|
+
#
|
|
882
|
+
# ```ruby
|
|
883
|
+
# proto = S(1, 2, 3)
|
|
884
|
+
# proto.next_value # => PrototypingError
|
|
885
|
+
#
|
|
886
|
+
# inst = proto.instance
|
|
887
|
+
# inst.next_value # => 1
|
|
888
|
+
# ```
|
|
889
|
+
#
|
|
890
|
+
# @api public
|
|
326
891
|
class PrototypingError < RuntimeError
|
|
892
|
+
# Creates prototyping error with message.
|
|
893
|
+
#
|
|
894
|
+
# @param message [String, nil] custom error message
|
|
895
|
+
#
|
|
896
|
+
# @api public
|
|
327
897
|
def initialize(message = nil)
|
|
328
898
|
message ||= 'This serie is a prototype serie: cannot be consumed. To consume the serie use an instance serie via .instance method'
|
|
329
899
|
super message
|
|
@@ -331,6 +901,56 @@ module Musa
|
|
|
331
901
|
end
|
|
332
902
|
end
|
|
333
903
|
|
|
904
|
+
# Core serie implementation providing iteration protocol.
|
|
905
|
+
#
|
|
906
|
+
# Includes all serie functionality:
|
|
907
|
+
# - Serie module (marker interface)
|
|
908
|
+
# - Prototyping (prototype/instance pattern)
|
|
909
|
+
# - Operations (transformations)
|
|
910
|
+
#
|
|
911
|
+
# ## Iteration Protocol
|
|
912
|
+
#
|
|
913
|
+
# Implements standard protocol for all series:
|
|
914
|
+
#
|
|
915
|
+
# ```ruby
|
|
916
|
+
# serie.init # Initialize/reset state
|
|
917
|
+
# serie.restart # Reset to beginning
|
|
918
|
+
# serie.next_value # Get next value
|
|
919
|
+
# serie.peek_next_value # Look ahead
|
|
920
|
+
# serie.current_value # Last value
|
|
921
|
+
# ```
|
|
922
|
+
#
|
|
923
|
+
# ## Subclass Requirements
|
|
924
|
+
#
|
|
925
|
+
# Subclasses must implement:
|
|
926
|
+
#
|
|
927
|
+
# ```ruby
|
|
928
|
+
# def _next_value
|
|
929
|
+
# # Return next value or nil when finished
|
|
930
|
+
# end
|
|
931
|
+
# ```
|
|
932
|
+
#
|
|
933
|
+
# Optional hooks:
|
|
934
|
+
#
|
|
935
|
+
# ```ruby
|
|
936
|
+
# def _init
|
|
937
|
+
# # Initialize instance state
|
|
938
|
+
# end
|
|
939
|
+
#
|
|
940
|
+
# def _restart
|
|
941
|
+
# # Reset to beginning
|
|
942
|
+
# end
|
|
943
|
+
# ```
|
|
944
|
+
#
|
|
945
|
+
# ## Peek Buffering
|
|
946
|
+
#
|
|
947
|
+
# `peek_next_value` buffers one value ahead without advancing state:
|
|
948
|
+
#
|
|
949
|
+
# - First peek: calls _next_value and buffers result
|
|
950
|
+
# - Subsequent peeks: returns buffered value
|
|
951
|
+
# - Next next_value: consumes buffered value
|
|
952
|
+
#
|
|
953
|
+
# @api private
|
|
334
954
|
module SerieImplementation
|
|
335
955
|
include Serie
|
|
336
956
|
include Prototyping
|
|
@@ -338,6 +958,19 @@ module Musa
|
|
|
338
958
|
|
|
339
959
|
using Musa::Extension::DeepCopy
|
|
340
960
|
|
|
961
|
+
# Initializes instance state.
|
|
962
|
+
#
|
|
963
|
+
# Called automatically when creating instance. Resets:
|
|
964
|
+
# - Peek buffer
|
|
965
|
+
# - Current value cache
|
|
966
|
+
# - Calls subclass _init hook
|
|
967
|
+
#
|
|
968
|
+
# @return [Serie] self
|
|
969
|
+
#
|
|
970
|
+
# @example Automatic initialization
|
|
971
|
+
# inst = S(1, 2, 3).instance # init called
|
|
972
|
+
#
|
|
973
|
+
# @api public
|
|
341
974
|
def init
|
|
342
975
|
@_have_peeked_next_value = false
|
|
343
976
|
@_peeked_next_value = nil
|
|
@@ -349,8 +982,33 @@ module Musa
|
|
|
349
982
|
self
|
|
350
983
|
end
|
|
351
984
|
|
|
985
|
+
# Subclass hook for custom initialization.
|
|
986
|
+
#
|
|
987
|
+
# Override to initialize instance-specific state (e.g., counters,
|
|
988
|
+
# buffers, internal series).
|
|
989
|
+
#
|
|
990
|
+
# @return [void]
|
|
991
|
+
#
|
|
992
|
+
# @api private
|
|
352
993
|
private def _init; end
|
|
353
994
|
|
|
995
|
+
# Restarts serie to beginning.
|
|
996
|
+
#
|
|
997
|
+
# Resets serie to initial state as if freshly created. Calls init
|
|
998
|
+
# and subclass _restart hook.
|
|
999
|
+
#
|
|
1000
|
+
# @return [Serie] self
|
|
1001
|
+
#
|
|
1002
|
+
# @raise [PrototypingError] if serie is not instance
|
|
1003
|
+
#
|
|
1004
|
+
# @example Restart series
|
|
1005
|
+
# s = S(1, 2, 3).i
|
|
1006
|
+
# s.next_value # => 1
|
|
1007
|
+
# s.next_value # => 2
|
|
1008
|
+
# s.restart
|
|
1009
|
+
# s.next_value # => 1
|
|
1010
|
+
#
|
|
1011
|
+
# @api public
|
|
354
1012
|
def restart(...)
|
|
355
1013
|
check_state_permissions
|
|
356
1014
|
init
|
|
@@ -359,8 +1017,38 @@ module Musa
|
|
|
359
1017
|
self
|
|
360
1018
|
end
|
|
361
1019
|
|
|
1020
|
+
# Subclass hook for custom restart logic.
|
|
1021
|
+
#
|
|
1022
|
+
# Override to reset subclass-specific state beyond what init handles.
|
|
1023
|
+
#
|
|
1024
|
+
# @return [void]
|
|
1025
|
+
#
|
|
1026
|
+
# @api private
|
|
362
1027
|
private def _restart; end
|
|
363
1028
|
|
|
1029
|
+
# Gets next value from serie.
|
|
1030
|
+
#
|
|
1031
|
+
# Advances serie to next element and returns it. Returns nil when
|
|
1032
|
+
# serie is exhausted. Once nil is returned, subsequent calls continue
|
|
1033
|
+
# returning nil.
|
|
1034
|
+
#
|
|
1035
|
+
# ## Peek Integration
|
|
1036
|
+
#
|
|
1037
|
+
# If peek_next_value was called, consumes peeked value instead of
|
|
1038
|
+
# calling _next_value again.
|
|
1039
|
+
#
|
|
1040
|
+
# @return [Object, nil] next value or nil if exhausted
|
|
1041
|
+
#
|
|
1042
|
+
# @raise [PrototypingError] if serie is not instance
|
|
1043
|
+
#
|
|
1044
|
+
# @example Basic iteration
|
|
1045
|
+
# s = S(1, 2, 3).i
|
|
1046
|
+
# s.next_value # => 1
|
|
1047
|
+
# s.next_value # => 2
|
|
1048
|
+
# s.next_value # => 3
|
|
1049
|
+
# s.next_value # => nil
|
|
1050
|
+
#
|
|
1051
|
+
# @api public
|
|
364
1052
|
def next_value
|
|
365
1053
|
check_state_permissions
|
|
366
1054
|
|
|
@@ -376,10 +1064,41 @@ module Musa
|
|
|
376
1064
|
@_current_value
|
|
377
1065
|
end
|
|
378
1066
|
|
|
1067
|
+
# Subclass implementation of value generation.
|
|
1068
|
+
#
|
|
1069
|
+
# Must be implemented by subclasses. Should return next value or nil
|
|
1070
|
+
# when serie is exhausted.
|
|
1071
|
+
#
|
|
1072
|
+
# @return [Object, nil] next value or nil if finished
|
|
1073
|
+
#
|
|
1074
|
+
# @api private
|
|
379
1075
|
private def _next_value; end
|
|
380
1076
|
|
|
1077
|
+
# Short alias for {#next_value}.
|
|
1078
|
+
#
|
|
1079
|
+
# @return [Object, nil] next value
|
|
1080
|
+
#
|
|
1081
|
+
# @api public
|
|
381
1082
|
alias_method :v, :next_value
|
|
382
1083
|
|
|
1084
|
+
# Peeks at next value without consuming it.
|
|
1085
|
+
#
|
|
1086
|
+
# Looks ahead to see what next_value will return, but doesn't advance
|
|
1087
|
+
# serie state. Multiple peeks return same value. Next next_value call
|
|
1088
|
+
# will consume peeked value.
|
|
1089
|
+
#
|
|
1090
|
+
# @return [Object, nil] next value that will be returned
|
|
1091
|
+
#
|
|
1092
|
+
# @raise [PrototypingError] if serie is not instance
|
|
1093
|
+
#
|
|
1094
|
+
# @example Peek ahead
|
|
1095
|
+
# s = S(1, 2, 3).i
|
|
1096
|
+
# s.peek_next_value # => 1
|
|
1097
|
+
# s.peek_next_value # => 1 (same)
|
|
1098
|
+
# s.next_value # => 1
|
|
1099
|
+
# s.peek_next_value # => 2
|
|
1100
|
+
#
|
|
1101
|
+
# @api public
|
|
383
1102
|
def peek_next_value
|
|
384
1103
|
check_state_permissions
|
|
385
1104
|
|
|
@@ -391,17 +1110,76 @@ module Musa
|
|
|
391
1110
|
@_peeked_next_value
|
|
392
1111
|
end
|
|
393
1112
|
|
|
1113
|
+
# Returns last consumed value.
|
|
1114
|
+
#
|
|
1115
|
+
# Returns value from most recent next_value call, or nil if
|
|
1116
|
+
# next_value hasn't been called yet.
|
|
1117
|
+
#
|
|
1118
|
+
# @return [Object, nil] last consumed value
|
|
1119
|
+
#
|
|
1120
|
+
# @raise [PrototypingError] if serie is not instance
|
|
1121
|
+
#
|
|
1122
|
+
# @example Track current
|
|
1123
|
+
# s = S(1, 2, 3).i
|
|
1124
|
+
# s.current_value # => nil
|
|
1125
|
+
# s.next_value # => 1
|
|
1126
|
+
# s.current_value # => 1
|
|
1127
|
+
#
|
|
1128
|
+
# @api public
|
|
394
1129
|
def current_value
|
|
395
1130
|
check_state_permissions
|
|
396
1131
|
|
|
397
1132
|
@_current_value
|
|
398
1133
|
end
|
|
399
1134
|
|
|
1135
|
+
# Checks if serie is infinite.
|
|
1136
|
+
#
|
|
1137
|
+
# Returns true if serie never exhausts (e.g., generators, cycles).
|
|
1138
|
+
# Prototypes allowed for this query.
|
|
1139
|
+
#
|
|
1140
|
+
# @return [Boolean] true if infinite, false if finite
|
|
1141
|
+
#
|
|
1142
|
+
# @api public
|
|
400
1143
|
def infinite?
|
|
401
1144
|
check_state_permissions(allows_prototype: true)
|
|
402
1145
|
@source&.infinite? || false
|
|
403
1146
|
end
|
|
404
1147
|
|
|
1148
|
+
# Converts serie to array by consuming all values.
|
|
1149
|
+
#
|
|
1150
|
+
# Creates instance, optionally duplicates/restarts it, consumes all
|
|
1151
|
+
# values, and returns them as array. Raises error if serie is infinite.
|
|
1152
|
+
#
|
|
1153
|
+
# ## Options
|
|
1154
|
+
#
|
|
1155
|
+
# - **duplicate**: Clone serie before consuming (default: based on dr)
|
|
1156
|
+
# - **recursive**: Convert nested Series to arrays (default: false)
|
|
1157
|
+
# - **restart**: Restart before consuming (default: based on dr)
|
|
1158
|
+
# - **dr**: Shorthand for duplicate+restart (default: true if instance)
|
|
1159
|
+
#
|
|
1160
|
+
# @param duplicate [Boolean, nil] clone before consuming
|
|
1161
|
+
# @param recursive [Boolean, nil] convert nested Series
|
|
1162
|
+
# @param restart [Boolean, nil] restart before consuming
|
|
1163
|
+
# @param dr [Boolean, nil] duplicate and restart shorthand
|
|
1164
|
+
#
|
|
1165
|
+
# @return [Array] array of all values
|
|
1166
|
+
#
|
|
1167
|
+
# @raise [RuntimeError] if serie is infinite
|
|
1168
|
+
# @raise [PrototypingError] if prototype and allows_prototype: false
|
|
1169
|
+
#
|
|
1170
|
+
# @example Basic conversion
|
|
1171
|
+
# proto = S(1, 2, 3)
|
|
1172
|
+
# proto.to_a # => [1, 2, 3]
|
|
1173
|
+
#
|
|
1174
|
+
# @example Preserve instance
|
|
1175
|
+
# inst = S(1, 2, 3).i
|
|
1176
|
+
# inst.to_a(duplicate: true) # Consumes copy, inst unchanged
|
|
1177
|
+
#
|
|
1178
|
+
# @example Recursive conversion
|
|
1179
|
+
# s = S(S(1, 2), S(3, 4))
|
|
1180
|
+
# s.to_a(recursive: true) # => [[1, 2], [3, 4]]
|
|
1181
|
+
#
|
|
1182
|
+
# @api public
|
|
405
1183
|
def to_a(duplicate: nil, recursive: nil, restart: nil, dr: nil)
|
|
406
1184
|
check_state_permissions(allows_prototype: true)
|
|
407
1185
|
raise 'Cannot convert to array an infinite serie' if infinite?
|
|
@@ -431,8 +1209,23 @@ module Musa
|
|
|
431
1209
|
array
|
|
432
1210
|
end
|
|
433
1211
|
|
|
1212
|
+
# Short alias for {#to_a}.
|
|
1213
|
+
#
|
|
1214
|
+
# @return [Array] array of all values
|
|
1215
|
+
#
|
|
1216
|
+
# @api public
|
|
434
1217
|
alias_method :a, :to_a
|
|
435
1218
|
|
|
1219
|
+
# Recursively converts series in value to arrays.
|
|
1220
|
+
#
|
|
1221
|
+
# Helper for to_a with recursive: true. Converts Serie values to
|
|
1222
|
+
# arrays and recursively processes Arrays and Hashes.
|
|
1223
|
+
#
|
|
1224
|
+
# @param value [Object] value to process
|
|
1225
|
+
#
|
|
1226
|
+
# @return [Object] processed value with Series converted to arrays
|
|
1227
|
+
#
|
|
1228
|
+
# @api private
|
|
436
1229
|
private def process_for_to_a(value)
|
|
437
1230
|
case value
|
|
438
1231
|
when Serie
|
|
@@ -448,12 +1241,43 @@ module Musa
|
|
|
448
1241
|
end
|
|
449
1242
|
end
|
|
450
1243
|
|
|
1244
|
+
# Converts serie to generative grammar node.
|
|
1245
|
+
#
|
|
1246
|
+
# Creates Node wrapper for use in generative grammar system. Nodes
|
|
1247
|
+
# can be used in generative rules and substitutions.
|
|
1248
|
+
#
|
|
1249
|
+
# @param attributes [Hash] additional node attributes
|
|
1250
|
+
#
|
|
1251
|
+
# @return [Node] generative grammar node wrapping serie
|
|
1252
|
+
#
|
|
1253
|
+
# @see Musa::GenerativeGrammar
|
|
1254
|
+
#
|
|
1255
|
+
# @api public
|
|
451
1256
|
def to_node(**attributes)
|
|
452
1257
|
Nodificator.to_node(self, **attributes)
|
|
453
1258
|
end
|
|
454
1259
|
|
|
1260
|
+
# Short alias for {#to_node}.
|
|
1261
|
+
#
|
|
1262
|
+
# @return [Node] generative grammar node
|
|
1263
|
+
#
|
|
1264
|
+
# @api public
|
|
455
1265
|
alias_method :node, :to_node
|
|
456
1266
|
|
|
1267
|
+
# Validates serie state before operation.
|
|
1268
|
+
#
|
|
1269
|
+
# Checks if serie is in valid state for requested operation. Raises
|
|
1270
|
+
# PrototypingError if:
|
|
1271
|
+
# - Serie is undefined
|
|
1272
|
+
# - Serie is prototype and allows_prototype is false
|
|
1273
|
+
#
|
|
1274
|
+
# @param allows_prototype [Boolean, nil] allow prototype state
|
|
1275
|
+
#
|
|
1276
|
+
# @return [void]
|
|
1277
|
+
#
|
|
1278
|
+
# @raise [PrototypingError] if state is invalid for operation
|
|
1279
|
+
#
|
|
1280
|
+
# @api private
|
|
457
1281
|
private def check_state_permissions(allows_prototype: nil)
|
|
458
1282
|
try_to_resolve_undefined_state_if_needed
|
|
459
1283
|
|
|
@@ -464,9 +1288,23 @@ module Musa
|
|
|
464
1288
|
end
|
|
465
1289
|
end
|
|
466
1290
|
|
|
1291
|
+
# Helper class for converting Series to generative grammar nodes.
|
|
1292
|
+
#
|
|
1293
|
+
# Extends GenerativeGrammar to provide N() constructor for creating
|
|
1294
|
+
# nodes from series.
|
|
1295
|
+
#
|
|
1296
|
+
# @api private
|
|
467
1297
|
class Nodificator
|
|
468
1298
|
extend Musa::GenerativeGrammar
|
|
469
1299
|
|
|
1300
|
+
# Converts serie to node using GenerativeGrammar#N.
|
|
1301
|
+
#
|
|
1302
|
+
# @param serie [Serie] serie to wrap
|
|
1303
|
+
# @param attributes [Hash] node attributes
|
|
1304
|
+
#
|
|
1305
|
+
# @return [Node] generative grammar node
|
|
1306
|
+
#
|
|
1307
|
+
# @api private
|
|
470
1308
|
def self.to_node(serie, **attributes)
|
|
471
1309
|
N(serie, **attributes)
|
|
472
1310
|
end
|