musa-dsl 0.30.2 → 0.41.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 +5 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/Gemfile +0 -1
- 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 +544 -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 +215 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
- data/lib/musa-dsl/generative/markov.rb +135 -3
- data/lib/musa-dsl/generative/rules.rb +312 -4
- data/lib/musa-dsl/generative/variatio.rb +286 -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 +113 -2
- data/lib/musa-dsl/midi/midi-voices.rb +275 -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 +353 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +1384 -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 +54 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -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 +57 -7
- data/lib/musa-dsl/series/queue-serie.rb +78 -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 +2 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +25 -18
- metadata +158 -16
|
@@ -1,9 +1,126 @@
|
|
|
1
1
|
module Musa
|
|
2
|
+
# Generative grammar system for creating formal grammars with combinatorial generation.
|
|
3
|
+
#
|
|
4
|
+
# GenerativeGrammar provides a DSL for defining formal grammars that can generate
|
|
5
|
+
# all possible combinations of terminal and non-terminal symbols according to
|
|
6
|
+
# production rules. Similar to context-free grammars in formal language theory.
|
|
7
|
+
#
|
|
8
|
+
# ## Core Concepts
|
|
9
|
+
#
|
|
10
|
+
# - **Nodes (N)**: Terminal or block nodes with content and attributes
|
|
11
|
+
# - **Proxy Nodes (PN)**: Placeholder nodes for recursive grammar definitions
|
|
12
|
+
# - **Operators**: Combine nodes to form production rules
|
|
13
|
+
# - `|` (or): Alternative/choice between nodes
|
|
14
|
+
# - `+` (next): Concatenation/sequence of nodes
|
|
15
|
+
# - **Modifiers**:
|
|
16
|
+
# - `repeat(min:, max:)`: Repeat node multiple times
|
|
17
|
+
# - `limit(&block)`: Filter options by condition
|
|
18
|
+
# - **Options**: Generate all valid combinations matching the grammar
|
|
19
|
+
#
|
|
20
|
+
# ## Grammar Definition Process
|
|
21
|
+
#
|
|
22
|
+
# 1. Define terminal nodes with N(content, **attributes)
|
|
23
|
+
# 2. Combine nodes using operators (|, +)
|
|
24
|
+
# 3. Add repetition and limits as needed
|
|
25
|
+
# 4. Use PN() for recursive grammars
|
|
26
|
+
# 5. Call .options to generate all valid combinations
|
|
27
|
+
#
|
|
28
|
+
# ## Musical Applications
|
|
29
|
+
#
|
|
30
|
+
# - Generate melodic patterns with rhythmic constraints
|
|
31
|
+
# - Create harmonic progressions with voice leading rules
|
|
32
|
+
# - Produce variations of musical motifs
|
|
33
|
+
# - Build algorithmic composition structures
|
|
34
|
+
#
|
|
35
|
+
# @example Simple sequence with alternatives
|
|
36
|
+
# include Musa::GenerativeGrammar
|
|
37
|
+
#
|
|
38
|
+
# a = N('a', size: 1)
|
|
39
|
+
# b = N('b', size: 1)
|
|
40
|
+
# c = N('c', size: 1)
|
|
41
|
+
#
|
|
42
|
+
# # Grammar: (a or b) repeated 3 times, then c
|
|
43
|
+
# grammar = (a | b).repeat(3) + c
|
|
44
|
+
#
|
|
45
|
+
# # Generate all possibilities
|
|
46
|
+
# grammar.options(content: :join)
|
|
47
|
+
# # => ["aaac", "aabc", "abac", "abbc", "baac", "babc", "bbac", "bbbc"]
|
|
48
|
+
#
|
|
49
|
+
# @example Grammar with constraints
|
|
50
|
+
# a = N('a', size: 1)
|
|
51
|
+
# b = N('b', size: 1)
|
|
52
|
+
#
|
|
53
|
+
# # Limit: total size must equal 3
|
|
54
|
+
# grammar = (a | b).repeat.limit { |o| o.collect { |_| _.attributes[:size] }.sum == 3 }
|
|
55
|
+
#
|
|
56
|
+
# # Filter options where size <= 4
|
|
57
|
+
# grammar.options(content: :join) { |o| o.collect { |e| e.attributes[:size] }.sum <= 4 }
|
|
58
|
+
# # => ["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"]
|
|
59
|
+
#
|
|
60
|
+
# @example Recursive grammar using proxy nodes
|
|
61
|
+
# a = N('a', size: 1)
|
|
62
|
+
# b = N('b', size: 1)
|
|
63
|
+
# c = N('c', size: 1)
|
|
64
|
+
#
|
|
65
|
+
# # Create proxy for recursion
|
|
66
|
+
# dp = PN()
|
|
67
|
+
#
|
|
68
|
+
# # Grammar: (c + dp) or (a or b), limit size to 3
|
|
69
|
+
# d = (c + dp | (a | b)).repeat.limit(:size, :sum, :==, 3)
|
|
70
|
+
#
|
|
71
|
+
# # Assign recursive reference
|
|
72
|
+
# dp.node = d
|
|
73
|
+
#
|
|
74
|
+
# d.options(:size, :sum, :<=, 4, content: :join)
|
|
75
|
+
# # => ["cca", "ccb", "caa", "cab", "cba", "cbb", "aca", "acb", ...]
|
|
76
|
+
#
|
|
77
|
+
# @example Block nodes for dynamic content
|
|
78
|
+
# a = N(color: :blue) { |parent| 'hola' }
|
|
79
|
+
# b = N(color: :red) { |parent, attributes|
|
|
80
|
+
# OptionElement.new('adios', final: true, **attributes)
|
|
81
|
+
# }
|
|
82
|
+
#
|
|
83
|
+
# grammar = (a | b).repeat(2)
|
|
84
|
+
# grammar.options
|
|
85
|
+
# # => [["hola", "hola"], ["hola", "adios"], ["adios", "hola"], ["adios", "adios"]]
|
|
86
|
+
#
|
|
87
|
+
# @see N Method to create terminal/block nodes
|
|
88
|
+
# @see PN Method to create proxy nodes for recursion
|
|
89
|
+
# @see Musa::Series::Serie Series conversion for grammar options
|
|
90
|
+
# @see https://en.wikipedia.org/wiki/Formal_grammar Formal grammar (Wikipedia)
|
|
91
|
+
# @see https://en.wikipedia.org/wiki/Context-free_grammar Context-free grammar (Wikipedia)
|
|
92
|
+
# @see https://en.wikipedia.org/wiki/Generative_grammar Generative grammar (Wikipedia)
|
|
2
93
|
module GenerativeGrammar
|
|
3
94
|
# TODO: refactor & reorganize regarding use of include Musa::GenerativeGrammar problems as default consumption mode (it forces the consumer to have new public methods -P, PN- and class names -OptionElement-)
|
|
4
95
|
|
|
5
96
|
extend self
|
|
6
97
|
|
|
98
|
+
# Creates a terminal or block node.
|
|
99
|
+
#
|
|
100
|
+
# Nodes are the basic building blocks of grammars. They can be:
|
|
101
|
+
#
|
|
102
|
+
# - **Terminal nodes**: Fixed content with attributes
|
|
103
|
+
# - **Block nodes**: Dynamic content generated by block
|
|
104
|
+
#
|
|
105
|
+
# @param content [Object, nil] terminal content (if no block given)
|
|
106
|
+
# @param attributes [Hash] attributes attached to node
|
|
107
|
+
#
|
|
108
|
+
# @yieldparam parent [Array<OptionElement>] parent elements in generation
|
|
109
|
+
# @yieldparam attributes [Hash] node attributes
|
|
110
|
+
# @yieldreturn [Object, OptionElement] generated content or element
|
|
111
|
+
#
|
|
112
|
+
# @return [Implementation::FinalNode, Implementation::BlockNode] created node
|
|
113
|
+
#
|
|
114
|
+
# @example Terminal node
|
|
115
|
+
# n = N('a', size: 1)
|
|
116
|
+
#
|
|
117
|
+
# @example Block node
|
|
118
|
+
# n = N(color: :blue) { |parent| "hello" }
|
|
119
|
+
#
|
|
120
|
+
# @example Block returning OptionElement
|
|
121
|
+
# n = N(type: :special) { |parent, attrs|
|
|
122
|
+
# OptionElement.new('value', **attrs)
|
|
123
|
+
# }
|
|
7
124
|
def N(content = nil, **attributes, &block)
|
|
8
125
|
if block_given? && content.nil?
|
|
9
126
|
Implementation::BlockNode.new(attributes, &block)
|
|
@@ -12,13 +129,45 @@ module Musa
|
|
|
12
129
|
end
|
|
13
130
|
end
|
|
14
131
|
|
|
132
|
+
# Creates a proxy node for recursive grammars.
|
|
133
|
+
#
|
|
134
|
+
# Proxy nodes act as placeholders that can be assigned later,
|
|
135
|
+
# enabling recursive and self-referential grammar definitions.
|
|
136
|
+
#
|
|
137
|
+
# @return [Implementation::ProxyNode] proxy node
|
|
138
|
+
#
|
|
139
|
+
# @example Recursive grammar
|
|
140
|
+
# a = N('a')
|
|
141
|
+
# b = N('b')
|
|
142
|
+
#
|
|
143
|
+
# # Create proxy
|
|
144
|
+
# proxy = PN()
|
|
145
|
+
#
|
|
146
|
+
# # Define grammar referencing itself through proxy
|
|
147
|
+
# grammar = a + proxy | b
|
|
148
|
+
#
|
|
149
|
+
# # Assign recursive reference
|
|
150
|
+
# proxy.node = grammar
|
|
15
151
|
def PN
|
|
16
152
|
Implementation::ProxyNode.new
|
|
17
153
|
end
|
|
18
154
|
|
|
155
|
+
# Container for generated option elements.
|
|
156
|
+
#
|
|
157
|
+
# Wraps content and attributes for each element in a generated option.
|
|
158
|
+
# Used internally when generating grammar options.
|
|
159
|
+
#
|
|
160
|
+
# @attr_reader content [Object] element content
|
|
161
|
+
# @attr_reader attributes [Hash] element attributes
|
|
19
162
|
class OptionElement
|
|
20
163
|
attr_reader :content, :attributes
|
|
21
164
|
|
|
165
|
+
# Creates option element.
|
|
166
|
+
#
|
|
167
|
+
# @param content [Object] element content
|
|
168
|
+
# @param attributes [Hash, nil] element attributes
|
|
169
|
+
#
|
|
170
|
+
# @return [void]
|
|
22
171
|
def initialize(content, attributes = nil)
|
|
23
172
|
@content = content
|
|
24
173
|
@attributes = attributes || {}
|
|
@@ -26,13 +175,54 @@ module Musa
|
|
|
26
175
|
end
|
|
27
176
|
|
|
28
177
|
module Implementation
|
|
178
|
+
# Base node class for grammar elements.
|
|
179
|
+
#
|
|
180
|
+
# Provides core operations for combining and transforming nodes.
|
|
181
|
+
# All node types (FinalNode, BlockNode, OrNode, etc.) inherit from this.
|
|
182
|
+
#
|
|
183
|
+
# @api private
|
|
29
184
|
class Node
|
|
185
|
+
# Creates alternation between this node and another.
|
|
186
|
+
#
|
|
187
|
+
# Generates options where either this node or the other is chosen.
|
|
188
|
+
#
|
|
189
|
+
# @param other [Node] alternative node
|
|
190
|
+
#
|
|
191
|
+
# @return [OrNode] alternation node
|
|
192
|
+
#
|
|
193
|
+
# @example
|
|
194
|
+
# a = N('a')
|
|
195
|
+
# b = N('b')
|
|
196
|
+
# ab = a.or(b) # or a | b
|
|
30
197
|
def or(other)
|
|
31
198
|
OrNode.new(self, other)
|
|
32
199
|
end
|
|
33
200
|
|
|
34
201
|
alias_method :|, :or
|
|
35
202
|
|
|
203
|
+
# Repeats this node with optional min/max bounds.
|
|
204
|
+
#
|
|
205
|
+
# Generates options with different repetition counts of this node.
|
|
206
|
+
# Without bounds, generates infinite options (use with limit).
|
|
207
|
+
#
|
|
208
|
+
# @param exactly [Integer, nil] exact repetition count
|
|
209
|
+
# @param min [Integer, nil] minimum repetitions
|
|
210
|
+
# @param max [Integer, nil] maximum repetitions
|
|
211
|
+
#
|
|
212
|
+
# @return [Node] node with repetition
|
|
213
|
+
#
|
|
214
|
+
# @raise [ArgumentError] if both exactly and min/max are specified
|
|
215
|
+
#
|
|
216
|
+
# @example Fixed repetition
|
|
217
|
+
# a = N('a')
|
|
218
|
+
# aaa = a.repeat(3) # exactly 3 times
|
|
219
|
+
# aaa = a.repeat(exactly: 3) # same
|
|
220
|
+
#
|
|
221
|
+
# @example Bounded repetition
|
|
222
|
+
# a_range = a.repeat(min: 2, max: 4) # 2, 3, or 4 times
|
|
223
|
+
#
|
|
224
|
+
# @example Unbounded (use with limit)
|
|
225
|
+
# a_any = a.repeat.limit { |o| o.size <= 5 }
|
|
36
226
|
def repeat(exactly = nil, min: nil, max: nil)
|
|
37
227
|
raise ArgumentError, 'Only exactly value or min/max values are allowed' if exactly && (min || max)
|
|
38
228
|
|
|
@@ -55,6 +245,30 @@ module Musa
|
|
|
55
245
|
end
|
|
56
246
|
end
|
|
57
247
|
|
|
248
|
+
# Limits generated options by condition.
|
|
249
|
+
#
|
|
250
|
+
# Filters options to only those satisfying the given condition.
|
|
251
|
+
# Can use simplified arguments or custom block.
|
|
252
|
+
#
|
|
253
|
+
# @param attribute [Symbol, nil] attribute to check (simplified form)
|
|
254
|
+
# @param after_collect_operation [Symbol, nil] operation on collected values
|
|
255
|
+
# @param comparison_method [Symbol, nil] comparison method to apply
|
|
256
|
+
# @param comparison_value [Object, nil] value to compare against
|
|
257
|
+
#
|
|
258
|
+
# @yieldparam option [Array<OptionElement>] option to evaluate
|
|
259
|
+
# @yieldreturn [Boolean] true if option should be included
|
|
260
|
+
#
|
|
261
|
+
# @return [ConditionNode] limited node
|
|
262
|
+
#
|
|
263
|
+
# @raise [ArgumentError] if both simplified arguments and block given
|
|
264
|
+
#
|
|
265
|
+
# @example Block form
|
|
266
|
+
# grammar = (a | b).repeat.limit { |o|
|
|
267
|
+
# o.collect { |e| e.attributes[:size] }.sum == 3
|
|
268
|
+
# }
|
|
269
|
+
#
|
|
270
|
+
# @example Simplified form
|
|
271
|
+
# grammar = (a | b).repeat.limit(:size, :sum, :==, 3)
|
|
58
272
|
def limit(attribute = nil, after_collect_operation = nil, comparison_method = nil, comparison_value = nil, &block)
|
|
59
273
|
raise ArgumentError, 'Cannot use simplified arguments and yield block at the same time' if (attribute || after_collect_operation || comparison_method || comparison_value) && @block
|
|
60
274
|
|
|
@@ -63,12 +277,61 @@ module Musa
|
|
|
63
277
|
ConditionNode.new(self, &block)
|
|
64
278
|
end
|
|
65
279
|
|
|
280
|
+
# Creates sequence of this node followed by another.
|
|
281
|
+
#
|
|
282
|
+
# Generates options with this node's content followed by the other's.
|
|
283
|
+
#
|
|
284
|
+
# @param other [Node] node to follow
|
|
285
|
+
#
|
|
286
|
+
# @return [NextNode] sequence node
|
|
287
|
+
#
|
|
288
|
+
# @example
|
|
289
|
+
# a = N('a')
|
|
290
|
+
# b = N('b')
|
|
291
|
+
# ab = a.next(b) # or a + b
|
|
66
292
|
def next(other)
|
|
67
293
|
NextNode.new(self, other)
|
|
68
294
|
end
|
|
69
295
|
|
|
70
296
|
alias_method :+, :next
|
|
71
297
|
|
|
298
|
+
# Generates all options from this grammar node.
|
|
299
|
+
#
|
|
300
|
+
# Produces all valid combinations matching the grammar definition.
|
|
301
|
+
# Can filter with condition and control output format.
|
|
302
|
+
#
|
|
303
|
+
# @param attribute [Symbol, nil] attribute for filtering (simplified)
|
|
304
|
+
# @param after_collect_operation [Symbol, nil] operation on collected values
|
|
305
|
+
# @param comparison_method [Symbol, nil] comparison method
|
|
306
|
+
# @param comparison_value [Object, nil] value to compare against
|
|
307
|
+
# @param raw [Boolean] if true, return raw OptionElement arrays
|
|
308
|
+
# @param content [Symbol] how to extract content (:itself, :join, etc.)
|
|
309
|
+
#
|
|
310
|
+
# @yieldparam option [Array<OptionElement>] option to evaluate
|
|
311
|
+
# @yieldreturn [Boolean] true if option should be included
|
|
312
|
+
#
|
|
313
|
+
# @return [Array] generated options
|
|
314
|
+
#
|
|
315
|
+
# @raise [ArgumentError] if simplified arguments and block both given
|
|
316
|
+
# @raise [ArgumentError] if raw and content both specified
|
|
317
|
+
#
|
|
318
|
+
# @example Basic usage
|
|
319
|
+
# grammar = (a | b).repeat(2)
|
|
320
|
+
# grammar.options
|
|
321
|
+
# # => [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]]
|
|
322
|
+
#
|
|
323
|
+
# @example With content formatting
|
|
324
|
+
# grammar.options(content: :join)
|
|
325
|
+
# # => ["aa", "ab", "ba", "bb"]
|
|
326
|
+
#
|
|
327
|
+
# @example With filtering
|
|
328
|
+
# grammar.options { |o| o.size == 2 }
|
|
329
|
+
#
|
|
330
|
+
# @example Simplified filtering
|
|
331
|
+
# grammar.options(:size, :sum, :<=, 4, content: :join)
|
|
332
|
+
#
|
|
333
|
+
# @example Raw OptionElements
|
|
334
|
+
# grammar.options(raw: true)
|
|
72
335
|
def options(attribute = nil,
|
|
73
336
|
after_collect_operation = nil,
|
|
74
337
|
comparison_method = nil,
|
|
@@ -92,16 +355,34 @@ module Musa
|
|
|
92
355
|
end
|
|
93
356
|
end
|
|
94
357
|
|
|
358
|
+
# Counts number of options generated by this node.
|
|
359
|
+
#
|
|
360
|
+
# @return [Integer] option count
|
|
95
361
|
def size
|
|
96
362
|
options.size
|
|
97
363
|
end
|
|
98
364
|
|
|
99
365
|
alias_method :length, :size
|
|
100
366
|
|
|
367
|
+
# Gets option at index as series node.
|
|
368
|
+
#
|
|
369
|
+
# @param index [Integer] option index
|
|
370
|
+
#
|
|
371
|
+
# @return [Node] option converted to node
|
|
101
372
|
def [](index)
|
|
102
373
|
options[index].to_serie.to_node
|
|
103
374
|
end
|
|
104
375
|
|
|
376
|
+
# Converts options to series.
|
|
377
|
+
#
|
|
378
|
+
# Generates options and converts them to a {Musa::Series::Serie}.
|
|
379
|
+
#
|
|
380
|
+
# @param flatten [Boolean] flatten nested series
|
|
381
|
+
#
|
|
382
|
+
# @yieldparam option [Array<OptionElement>] option to evaluate
|
|
383
|
+
# @yieldreturn [Boolean] true if option should be included
|
|
384
|
+
#
|
|
385
|
+
# @return [Musa::Series::Serie] series of options
|
|
105
386
|
def to_serie(flatten: true, &condition)
|
|
106
387
|
serie = _options(&condition).collect { |o| o.collect(&:content) }.to_serie(of_series: true).merge
|
|
107
388
|
serie = serie.flatten if flatten
|
|
@@ -111,12 +392,30 @@ module Musa
|
|
|
111
392
|
|
|
112
393
|
alias_method :s, :to_serie
|
|
113
394
|
|
|
395
|
+
# Internal method to generate option arrays.
|
|
396
|
+
#
|
|
397
|
+
# @param parent [Array<OptionElement>, nil] parent elements
|
|
398
|
+
#
|
|
399
|
+
# @yieldparam option [Array<OptionElement>] option to evaluate
|
|
400
|
+
# @yieldreturn [Boolean] true if option should be included
|
|
401
|
+
#
|
|
402
|
+
# @return [Array<Array<OptionElement>>] option arrays
|
|
403
|
+
#
|
|
404
|
+
# @api private
|
|
114
405
|
def _options(parent: nil, &condition)
|
|
115
406
|
raise NotImplementedError
|
|
116
407
|
end
|
|
117
408
|
|
|
118
409
|
protected
|
|
119
410
|
|
|
411
|
+
# Generates condition block from simplified arguments.
|
|
412
|
+
#
|
|
413
|
+
# @param attribute [Symbol, nil] attribute to check
|
|
414
|
+
# @param after_collect_operation [Symbol, nil] operation on collected values
|
|
415
|
+
# @param comparison_method [Symbol, nil] comparison method to apply
|
|
416
|
+
# @param comparison_value [Object, nil] value to compare against
|
|
417
|
+
#
|
|
418
|
+
# @return [Proc, nil] condition block or nil if arguments incomplete
|
|
120
419
|
def generate_simple_condition_block(attribute = nil,
|
|
121
420
|
after_collect_operation = nil,
|
|
122
421
|
comparison_method = nil,
|
|
@@ -132,23 +431,46 @@ module Musa
|
|
|
132
431
|
end
|
|
133
432
|
end
|
|
134
433
|
|
|
434
|
+
# Proxy node for recursive grammar references.
|
|
435
|
+
#
|
|
436
|
+
# Acts as placeholder that delegates to assigned node.
|
|
437
|
+
# Enables recursive and self-referential grammars.
|
|
438
|
+
#
|
|
439
|
+
# @api private
|
|
135
440
|
class ProxyNode < Node
|
|
441
|
+
# @return [Node, nil] assigned node
|
|
136
442
|
attr_accessor :node
|
|
137
443
|
|
|
444
|
+
# @api private
|
|
138
445
|
def _options(parent: nil, &condition)
|
|
139
446
|
@node._options parent: parent, &condition
|
|
140
447
|
end
|
|
141
448
|
end
|
|
142
449
|
|
|
450
|
+
# Terminal node with fixed content.
|
|
451
|
+
#
|
|
452
|
+
# Represents a leaf node in the grammar tree with constant content.
|
|
453
|
+
#
|
|
454
|
+
# @attr_reader content [Object] node content
|
|
455
|
+
# @attr_reader attributes [Hash] node attributes
|
|
456
|
+
#
|
|
457
|
+
# @api private
|
|
143
458
|
class FinalNode < Node
|
|
144
459
|
attr_reader :content
|
|
145
460
|
attr_reader :attributes
|
|
146
461
|
|
|
462
|
+
# @param content [Object] node content
|
|
463
|
+
# @param attributes [Hash] node attributes
|
|
464
|
+
#
|
|
465
|
+
# @return [void]
|
|
466
|
+
#
|
|
467
|
+
# @api private
|
|
147
468
|
def initialize(content, attributes)
|
|
148
469
|
super()
|
|
149
470
|
@element = OptionElement.new(content, attributes)
|
|
150
471
|
end
|
|
151
472
|
|
|
473
|
+
# @api private
|
|
152
474
|
def _options(parent: nil, &condition)
|
|
153
475
|
parent ||= []
|
|
154
476
|
|
|
@@ -164,12 +486,25 @@ module Musa
|
|
|
164
486
|
end
|
|
165
487
|
end
|
|
166
488
|
|
|
489
|
+
# Node with dynamic content generated by block.
|
|
490
|
+
#
|
|
491
|
+
# Executes block at generation time to produce content.
|
|
492
|
+
# Block receives parent elements and attributes.
|
|
493
|
+
#
|
|
494
|
+
# @api private
|
|
167
495
|
class BlockNode < Node
|
|
496
|
+
# @param attributes [Hash] node attributes
|
|
497
|
+
# @yield [parent, attributes] block to generate content
|
|
498
|
+
#
|
|
499
|
+
# @return [void]
|
|
500
|
+
#
|
|
501
|
+
# @api private
|
|
168
502
|
def initialize(attributes, &block)
|
|
169
503
|
@attributes = attributes
|
|
170
504
|
@block = block
|
|
171
505
|
end
|
|
172
506
|
|
|
507
|
+
# @api private
|
|
173
508
|
def _options(parent: nil, &condition)
|
|
174
509
|
parent ||= []
|
|
175
510
|
|
|
@@ -188,12 +523,25 @@ module Musa
|
|
|
188
523
|
end
|
|
189
524
|
end
|
|
190
525
|
|
|
526
|
+
# Node that filters child node options by condition.
|
|
527
|
+
#
|
|
528
|
+
# Applies condition block to filter generated options.
|
|
529
|
+
# Created by {Node#limit}.
|
|
530
|
+
#
|
|
531
|
+
# @api private
|
|
191
532
|
class ConditionNode < Node
|
|
533
|
+
# @param node [Node] node to filter
|
|
534
|
+
# @yield [option] condition block
|
|
535
|
+
#
|
|
536
|
+
# @return [void]
|
|
537
|
+
#
|
|
538
|
+
# @api private
|
|
192
539
|
def initialize(node, &block)
|
|
193
540
|
@node = node
|
|
194
541
|
@block = block
|
|
195
542
|
end
|
|
196
543
|
|
|
544
|
+
# @api private
|
|
197
545
|
def _options(parent: nil, &condition)
|
|
198
546
|
parent ||= []
|
|
199
547
|
|
|
@@ -207,13 +555,26 @@ module Musa
|
|
|
207
555
|
end
|
|
208
556
|
end
|
|
209
557
|
|
|
558
|
+
# Node representing alternation between two nodes.
|
|
559
|
+
#
|
|
560
|
+
# Generates options from either node1 or node2.
|
|
561
|
+
# Created by {Node#or} or `|` operator.
|
|
562
|
+
#
|
|
563
|
+
# @api private
|
|
210
564
|
class OrNode < Node
|
|
565
|
+
# @param node1 [Node] first alternative
|
|
566
|
+
# @param node2 [Node] second alternative
|
|
567
|
+
#
|
|
568
|
+
# @return [void]
|
|
569
|
+
#
|
|
570
|
+
# @api private
|
|
211
571
|
def initialize(node1, node2)
|
|
212
572
|
@node1 = node1
|
|
213
573
|
@node2 = node2
|
|
214
574
|
super()
|
|
215
575
|
end
|
|
216
576
|
|
|
577
|
+
# @api private
|
|
217
578
|
def _options(parent: nil, &condition)
|
|
218
579
|
parent ||= []
|
|
219
580
|
|
|
@@ -231,13 +592,26 @@ module Musa
|
|
|
231
592
|
end
|
|
232
593
|
end
|
|
233
594
|
|
|
595
|
+
# Node representing sequence of two nodes.
|
|
596
|
+
#
|
|
597
|
+
# Generates options with node followed by after.
|
|
598
|
+
# Created by {Node#next} or `+` operator.
|
|
599
|
+
#
|
|
600
|
+
# @api private
|
|
234
601
|
class NextNode < Node
|
|
602
|
+
# @param node [Node] first node in sequence
|
|
603
|
+
# @param after [Node] node to follow
|
|
604
|
+
#
|
|
605
|
+
# @return [void]
|
|
606
|
+
#
|
|
607
|
+
# @api private
|
|
235
608
|
def initialize(node, after)
|
|
236
609
|
@node = node
|
|
237
610
|
@after = after
|
|
238
611
|
super()
|
|
239
612
|
end
|
|
240
613
|
|
|
614
|
+
# @api private
|
|
241
615
|
def _options(parent: nil, &condition)
|
|
242
616
|
parent ||= []
|
|
243
617
|
|
|
@@ -251,7 +625,19 @@ module Musa
|
|
|
251
625
|
end
|
|
252
626
|
end
|
|
253
627
|
|
|
628
|
+
# Node representing repetition of a node.
|
|
629
|
+
#
|
|
630
|
+
# Generates options with different repetition counts.
|
|
631
|
+
# Created by {Node#repeat}.
|
|
632
|
+
#
|
|
633
|
+
# @api private
|
|
254
634
|
class RepeatNode < Node
|
|
635
|
+
# @param node [Node] node to repeat
|
|
636
|
+
# @param max [Integer, nil] maximum repetitions (nil = infinite)
|
|
637
|
+
#
|
|
638
|
+
# @return [void]
|
|
639
|
+
#
|
|
640
|
+
# @api private
|
|
255
641
|
def initialize(node, max = nil)
|
|
256
642
|
@node = node
|
|
257
643
|
@max = max
|
|
@@ -259,6 +645,7 @@ module Musa
|
|
|
259
645
|
super()
|
|
260
646
|
end
|
|
261
647
|
|
|
648
|
+
# @api private
|
|
262
649
|
def _options(parent: nil, depth: nil, &condition)
|
|
263
650
|
parent ||= []
|
|
264
651
|
depth ||= 0
|