Class: HeadMusic::Notation::LilyPond::MusicReader
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::MusicReader
- Defined in:
- lib/head_music/notation/lily_pond/music_reader.rb
Overview
Reads one music expression by recursive descent: sequential braces, << >> parallels, and \relative and \absolute wrappers. A ContextReader reads the \new Staff and \new Voice declarations inside, and a MusicItemReader the notes, rests, and commands. Every other command raises as unsupported rather than being skipped, because skipping a \transpose or a \tuplet would yield a plausible but wrong flow.
Constant Summary collapse
- MAX_NESTING_DEPTH =
The reader recurses per brace level and per \relative, \absolute, or \new wrapper; the lexer and balance check do not, so nesting must be bounded here to stay inside ParseError.
1000- DEFAULT_RELATIVE_REFERENCE =
"F3"
Instance Attribute Summary collapse
-
#contexts ⇒ Object
readonly
private
Returns the value of attribute contexts.
-
#cursor ⇒ Object
readonly
private
Returns the value of attribute cursor.
-
#items ⇒ Object
readonly
private
Returns the value of attribute items.
-
#readers ⇒ Object
readonly
private
Returns the value of attribute readers.
Instance Method Summary collapse
-
#initialize(cursor, document) ⇒ MusicReader
constructor
A new instance of MusicReader.
- #nested(opener) ⇒ Object
- #read_absolute(&block) ⇒ Object
- #read_expression(context) ⇒ Object
- #read_expression_command(context) ⇒ Object private
- #read_item(context) ⇒ Object private
- #read_item_command(context) ⇒ Object private
- #read_parallel(context) ⇒ Object private
-
#read_relative(&block) ⇒ Object
\relative and \absolute wrap a music expression, so the readers that descend into one — this one and the ContextReader — enter the wrapper through here.
- #read_sequential(context) ⇒ Object private
- #relative_reference ⇒ Object private
Constructor Details
#initialize(cursor, document) ⇒ MusicReader
Returns a new instance of MusicReader.
16 17 18 19 20 21 22 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 16 def initialize(cursor, document) @cursor = cursor @readers = PitchReaderStack.new @items = MusicItemReader.new(cursor, @readers) @contexts = ContextReader.new(cursor, document, self) @depth = 0 end |
Instance Attribute Details
#contexts ⇒ Object (readonly, private)
Returns the value of attribute contexts.
58 59 60 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 58 def contexts @contexts end |
#cursor ⇒ Object (readonly, private)
Returns the value of attribute cursor.
58 59 60 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 58 def cursor @cursor end |
#items ⇒ Object (readonly, private)
Returns the value of attribute items.
58 59 60 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 58 def items @items end |
#readers ⇒ Object (readonly, private)
Returns the value of attribute readers.
58 59 60 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 58 def readers @readers end |
Instance Method Details
#nested(opener) ⇒ Object
48 49 50 51 52 53 54 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 48 def nested(opener) @depth += 1 raise cursor.error("Music expressions are nested too deeply", opener) if @depth > MAX_NESTING_DEPTH yield @depth -= 1 end |
#read_absolute(&block) ⇒ Object
43 44 45 46 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 43 def read_absolute(&block) token = cursor.advance readers.absolute { nested(token, &block) } end |
#read_expression(context) ⇒ Object
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 24 def read_expression(context) token = cursor.peek case token&.type when :open_brace then read_sequential(context) when :open_parallel then read_parallel(context) when :command then read_expression_command(context) when :unsupported then raise cursor.unsupported_token(token) else raise cursor.error("Expected a music expression", token) end end |
#read_expression_command(context) ⇒ Object (private)
60 61 62 63 64 65 66 67 68 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 60 def read_expression_command(context) token = cursor.peek case token.lexeme when "relative" then read_relative { read_expression(context) } when "absolute" then read_absolute { read_expression(context) } when "new" then contexts.read_new(context) else raise cursor.unsupported_command(token) end end |
#read_item(context) ⇒ Object (private)
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 96 def read_item(context) token = cursor.peek case token.type when :note then items.read_note(context) when :rest then items.read_rest(context) when :whole_bar_rest then items.(context) when :open_chord then items.read_chord(context) when :tie then items.read_tie(context) when :bar_check then items.(context) when :open_brace then read_sequential(context) when :open_parallel then read_parallel(context) when :command then read_item_command(context) when :unsupported then raise cursor.unsupported_token(token) else raise cursor.error(%(Unexpected token "#{token.lexeme}"), token) end end |
#read_item_command(context) ⇒ Object (private)
113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 113 def read_item_command(context) token = cursor.peek case token.lexeme when "key" then items.read_key(context) when "time" then items.read_time(context) when "clef" then items.read_clef when "relative" then read_relative { read_expression(context) } when "absolute" then read_absolute { read_expression(context) } when "new" then contexts.read_sequential_new(context) when *ENVELOPE_COMMANDS then raise cursor.error(%(Unexpected \\#{token.lexeme} inside music), token) else raise cursor.unsupported_command(token) end end |
#read_parallel(context) ⇒ Object (private)
77 78 79 80 81 82 83 84 85 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 77 def read_parallel(context) token = cursor.advance raise cursor.unsupported("Simultaneous music inside \\relative is not supported", token) if readers.current.relative? nested(token) do contexts.read_parallel_item(context) until cursor.peek.type == :close_parallel end cursor.advance end |
#read_relative(&block) ⇒ Object
\relative and \absolute wrap a music expression, so the readers that descend into one — this one and the ContextReader — enter the wrapper through here.
38 39 40 41 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 38 def read_relative(&block) token = cursor.advance readers.relative(relative_reference) { nested(token, &block) } end |
#read_sequential(context) ⇒ Object (private)
70 71 72 73 74 75 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 70 def read_sequential(context) nested(cursor.advance) do read_item(context) until cursor.peek.type == :close_brace end cursor.advance end |
#relative_reference ⇒ Object (private)
87 88 89 90 91 92 93 94 |
# File 'lib/head_music/notation/lily_pond/music_reader.rb', line 87 def relative_reference return HeadMusic::Rudiment::Pitch.get(DEFAULT_RELATIVE_REFERENCE) unless cursor.peek&.type == :note token = cursor.advance raise cursor.error("\\relative expects a pitch", token) if token.duration || token.multiplier PitchReader.absolute.pitch(token) end |