Class: HeadMusic::Notation::LilyPond::SourceScanner
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::SourceScanner
- Defined in:
- lib/head_music/notation/lily_pond/source_scanner.rb
Overview
A StringScanner over a LilyPond source that knows where it is.
The line and column of the scan position stay true across every consumed match -- block comments, strings, and whitespace runs all span newlines -- so a token can be stamped with where it began and an error can say where it happened. Comments are skipped here rather than pre-stripped, so a % inside a string survives and the line numbers stay true.
Constant Summary collapse
- BLOCK_COMMENT_PATTERN =
/%\{.*?%\}/m
- LINE_COMMENT_PATTERN =
/%[^\n]*/- SNIPPET_LENGTH =
20
Instance Attribute Summary collapse
-
#line ⇒ Object
readonly
Returns the value of attribute line.
-
#scanner ⇒ Object
readonly
private
Returns the value of attribute scanner.
Instance Method Summary collapse
- #[](group) ⇒ Object
-
#captures ⇒ Object
StringScanner#captures answers "" for a group that took no part in the match, where #[] answers nil, and nil is what a caller can test.
- #column ⇒ Object
- #consume(pattern) ⇒ Object
- #error(message) ⇒ Object
-
#initialize(source) ⇒ SourceScanner
constructor
A new instance of SourceScanner.
- #normalize(source) ⇒ Object private
- #skip_block_comment ⇒ Object
-
#skip_insignificant ⇒ Object
Whitespace and comments: what the language ignores between tokens.
- #unexpected_character_error ⇒ Object
Constructor Details
#initialize(source) ⇒ SourceScanner
Returns a new instance of SourceScanner.
21 22 23 24 25 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 21 def initialize(source) @scanner = StringScanner.new(normalize(source)) @line = 1 @line_start = 0 end |
Instance Attribute Details
#line ⇒ Object (readonly)
Returns the value of attribute line.
17 18 19 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 17 def line @line end |
#scanner ⇒ Object (readonly, private)
Returns the value of attribute scanner.
80 81 82 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 80 def scanner @scanner end |
Instance Method Details
#[](group) ⇒ Object
31 32 33 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 31 def [](group) scanner[group] end |
#captures ⇒ Object
StringScanner#captures answers "" for a group that took no part in the match, where #[] answers nil, and nil is what a caller can test.
37 38 39 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 37 def captures (1...scanner.size).map { |group| scanner[group] } end |
#column ⇒ Object
27 28 29 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 27 def column scanner.charpos - @line_start + 1 end |
#consume(pattern) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 41 def consume(pattern) start = scanner.charpos text = scanner.scan(pattern) return unless text newlines = text.count("\n") if newlines.positive? @line += newlines @line_start = start + text.rindex("\n") + 1 end text end |
#error(message) ⇒ Object
66 67 68 69 70 71 72 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 66 def error() ParseError.new( , line_number: line, column: column, snippet: scanner.peek(SNIPPET_LENGTH).lines.first&.chomp ) end |
#normalize(source) ⇒ Object (private)
82 83 84 85 86 87 88 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 82 def normalize(source) text = source.to_s text = text.dup.force_encoding(Encoding::UTF_8) unless text.encoding == Encoding::UTF_8 raise ParseError, "LilyPond input is not valid UTF-8" unless text.valid_encoding? text.delete_prefix("\uFEFF") end |
#skip_block_comment ⇒ Object
59 60 61 62 63 64 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 59 def skip_block_comment return consume(BLOCK_COMMENT_PATTERN) if match?(BLOCK_COMMENT_PATTERN) return false unless match?(/%\{/) raise error("Unterminated block comment") end |
#skip_insignificant ⇒ Object
Whitespace and comments: what the language ignores between tokens.
55 56 57 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 55 def skip_insignificant consume(/\s+/) || skip_block_comment || consume(LINE_COMMENT_PATTERN) end |
#unexpected_character_error ⇒ Object
74 75 76 |
# File 'lib/head_music/notation/lily_pond/source_scanner.rb', line 74 def unexpected_character_error error("Unexpected character #{peek(1).inspect} at column #{column}") end |