Class: HeadMusic::Notation::LilyPond::DocumentReader

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/lily_pond/document_reader.rb

Overview

Reads a token list into a Document.

This reader is the envelope the Writer emits plus the common hand-written shapes around it: an optional \version, \header, and \score wrapper, and \layout and \midi blocks, which are skipped. The one music expression the document holds is read by the MusicReader.

Constant Summary collapse

HEADER_FIELDS =
%w[title composer].freeze
SCORE_SKIPPED_COMMANDS =
%w[layout midi].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tokens) ⇒ DocumentReader

Returns a new instance of DocumentReader.



13
14
15
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 13

def initialize(tokens)
  @cursor = TokenCursor.new(tokens)
end

Instance Attribute Details

#cursor ⇒ Object (readonly, private)

Returns the value of attribute cursor.



23
24
25
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 23

def cursor
  @cursor
end

Instance Method Details

#document ⇒ Object



17
18
19
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 17

def document
  @document ||= read
end

#read ⇒ Object (private)



25
26
27
28
29
30
31
32
33
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 25

def read
  @building = Document.new
  @music = MusicReader.new(cursor, @building)
  @music_read = false
  root = VoiceContext.new(@building, nil, explicit: false)
  read_document(root)
  root.close
  @building
end

#read_document(context) ⇒ Object (private)



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 35

def read_document(context)
  until cursor.eos?
    token = cursor.peek
    if token.type == :command && ENVELOPE_COMMANDS.include?(token.lexeme)
      read_envelope_command(context)
    elsif token.type == :word && cursor.peek(1)&.type == :equals
      raise cursor.unsupported(%(Variable assignments such as "#{token.lexeme} =" are not supported), token)
    else
      read_top_level_music(context)
    end
  end
end

#read_envelope_command(context) ⇒ Object (private)



48
49
50
51
52
53
54
55
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 48

def read_envelope_command(context)
  case cursor.peek.lexeme
  when "version" then read_version
  when "header" then read_header
  when "score" then read_score(context)
  else cursor.skip_block
  end
end

#read_header ⇒ Object (private)



72
73
74
75
76
77
78
79
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 72

def read_header
  cursor.advance
  cursor.expect(:open_brace, "\\header expects a block")
  AssignmentReader.new(cursor, "\\header", HEADER_FIELDS).read do |name, value|
    @building.title = value if name == "title"
    @building.composer = value if name == "composer"
  end
end

#read_score(context) ⇒ Object (private)



81
82
83
84
85
86
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 81

def read_score(context)
  cursor.advance
  cursor.expect(:open_brace, "\\score expects a block")
  read_score_item(context) until cursor.peek.type == :close_brace
  cursor.advance
end

#read_score_item(context) ⇒ Object (private)



88
89
90
91
92
93
94
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 88

def read_score_item(context)
  token = cursor.peek
  return cursor.skip_block if token.type == :command && SCORE_SKIPPED_COMMANDS.include?(token.lexeme)
  return read_header if token.type == :command && token.lexeme == "header"

  read_top_level_music(context)
end

#read_top_level_music(context) ⇒ Object (private)



57
58
59
60
61
62
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 57

def read_top_level_music(context)
  raise cursor.unsupported("Only one \\score or top-level music expression is supported", cursor.peek) if @music_read

  @music_read = true
  @music.read_expression(context)
end

#read_version ⇒ Object (private)



64
65
66
67
68
69
70
# File 'lib/head_music/notation/lily_pond/document_reader.rb', line 64

def read_version
  version = cursor.advance
  token = cursor.advance
  return if token&.type == :string

  raise cursor.error("\\version expects a quoted version string", token || version)
end