Class: HeadMusic::Notation::ABC::ChordScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/abc/chord_scanner.rb

Overview

Scans one bracket chord — “[CEG]2” — from the position of its opening bracket.

Non-note content inside the brackets (ties, rests, spaces, decorations) makes the whole group one unsupported token, matching how those constructs surface outside a chord.

Defined Under Namespace

Classes: ChordNote

Constant Summary collapse

START_PATTERN =

Matches a bracket whose first content is a note, distinguishing a chord from the other things a “[” can open.

/\[(\^\^|\^|__|_|=)?[A-Ga-g]/
NOTE_PATTERN =
%r{(\^\^|\^|__|_|=)?([A-Ga-g])([',]*)([\d/]*)}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scanner, line_number, column) ⇒ ChordScanner

Returns a new instance of ChordScanner.



21
22
23
24
25
26
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 21

def initialize(scanner, line_number, column)
  @scanner = scanner
  @line_number = line_number
  @column = column
  @start_pos = scanner.pos
end

Instance Attribute Details

#columnObject (readonly, private)

Returns the value of attribute column.



40
41
42
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 40

def column
  @column
end

#line_numberObject (readonly, private)

Returns the value of attribute line_number.



40
41
42
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 40

def line_number
  @line_number
end

#scannerObject (readonly, private)

Returns the value of attribute scanner.



40
41
42
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 40

def scanner
  @scanner
end

#start_posObject (readonly, private)

Returns the value of attribute start_pos.



40
41
42
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 40

def start_pos
  @start_pos
end

Instance Method Details

#collect_notesObject (private)

Collects the notes between the brackets, or nil when a non-note is hit (leaving the scanner where it stopped so the fallback can react).



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 44

def collect_notes
  notes = []
  until scanner.skip(/\]/)
    return nil unless scanner.scan(NOTE_PATTERN)

    notes << ChordNote.new(
      accidental: scanner[1], letter: scanner[2],
      octave_marks: scanner[3], length: scanner[4]
    )
  end
  notes
end

#ensure_terminatedObject (private)

Raises:



64
65
66
67
68
69
70
71
72
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 64

def ensure_terminated
  return unless scanner.eos?

  raise ParseError.new(
    'Unterminated chord; expected "]"',
    line_number: line_number,
    snippet: scanner.string[start_pos, BodyLexer::SNIPPET_LENGTH]
  )
end

#fallback_tokenObject (private)



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

def fallback_token
  ensure_terminated
  scanner.pos = start_pos
  lexeme = scanner.scan(/\[[^\]]*\]/) || scanner.scan(/\[[^\]]*/)
  new_token(type: :unsupported, lexeme: lexeme)
end

#new_token(**attributes) ⇒ Object (private)



74
75
76
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 74

def new_token(**attributes)
  Token.new(line: line_number, column: column, **attributes)
end

#tokenObject

The :chord token, or the :unsupported token the whole group falls back to. Raises when the brackets never close.



30
31
32
33
34
35
36
# File 'lib/head_music/notation/abc/chord_scanner.rb', line 30

def token
  scanner.skip(/\[/)
  notes = collect_notes
  return fallback_token unless notes

  new_token(type: :chord, notes: notes, length: scanner.scan(%r{[\d/]*}))
end