Class: HeadMusic::Notation::LilyPond::MusicReader

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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.read_whole_bar_rest(context)
  when :open_chord then items.read_chord(context)
  when :tie then items.read_tie(context)
  when :bar_check then items.read_bar_check(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