Source code for ly.music.read

# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2014 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
The Reader is used to construct a tree structure of (musical and other)
items in a LilyPond document. The item types that are used are in the items
module.

You instantiate a Reader with a ly.document.Source that reads tokens from
any (part of a) ly.document.Document.

Whitespace and comments are left out.

All nodes (instances of Item) have a 'position' attribute that indicates 
where the item starts in the source text. Almost all items have the token 
that starts the expression in the 'token' attribute and possibly other 
tokens in the 'tokens' attribute, as a tuple. 

The 'end_position()' method returns the position where the node (including 
its child nodes) ends.


"""

from __future__ import unicode_literals
from __future__ import division

import itertools
from fractions import Fraction

import ly.duration
import ly.pitch

from ly import lex
from ly.lex import lilypond
from ly.lex import scheme

from .items import *





[docs]class dispatcher(object): """Decorator creator to dispatch commands, keywords, etc. to a method.""" def __init__(self): self.d = {}
[docs] def read_arg(self, a): return a
def __call__(self, *args): d = self.d def wrapper(func): for a in args: d[a] = func doc = "handle " + ", ".join(map(self.read_arg, args)) func.__doc__ = doc if not func.__doc__ else func.__doc__ + '\n\n' + doc return func return wrapper
[docs] def method(self, token): """The registered method to call for the token. May return None.""" return self.d.get(token)
[docs]class dispatcher_class(dispatcher): """Decorator creator to dispatch the class of a token to a method."""
[docs] def read_arg(self, a): return a.__name__
[docs] def method(self, token): """The registered method to call for the token's class. May return None.""" cls = token.__class__ d = self.d try: return d[cls] except KeyError: for c in cls.__mro__[1:]: try: meth = d[cls] = d[c] except KeyError: if c is not lex.Token: continue meth = d[cls] = None return meth
[docs]class Reader(object): """Reads tokens from a Source and builds a meaningful tree structure.""" _commands = dispatcher() _keywords = dispatcher() _tokencls = dispatcher_class() _markup = dispatcher_class() _scheme = dispatcher_class() def __init__(self, source): """Initialize with a ly.document.Source. The language is set to "nederlands". """ self.source = source self.language = "nederlands" self.in_chord = False self.prev_duration = Fraction(1, 4), 1
[docs] def set_language(self, lang): r"""Changes the pitch name language to use. Called internally when \language or \include tokens are encountered with a valid language name/file. Sets the language attribute to the language name. """ if lang in ly.pitch.pitchInfo: self.language = lang return True
[docs] def add_duration(self, item, token=None, source=None): """Add a duration attribute to the item. When there are tokens, a Duration item is also appended to the item. """ source = source or self.source tokens = [] if not token or isinstance(token, lilypond.Duration): if token: tokens.append(token) for token in source: if isinstance(token, lilypond.Duration): if tokens and isinstance(token, lilypond.Length): self.source.pushback() break tokens.append(token) elif not isinstance(token, lex.Space): self.source.pushback() break if tokens: d = self.factory(Duration, tokens[0]) d.tokens = tuple(tokens[1:]) item.append(d) item.duration = self.prev_duration = ly.duration.base_scaling(tokens) else: item.duration = self.prev_duration
[docs] def consume(self, last_token=None): """Yield the tokens from our source until a parser is exit. If last_token is given, it is called with the last token that is read. """ t = None for t in self.source.until_parser_end(): yield t if last_token and t is not None: last_token(t)
[docs] def factory(self, cls, token=None, consume=False, position=None): """Create Item instance for token. If consume is True, consume()s the source into item.tokens. If you don't specify a token, you must specify the position (>= 0). """ item = cls() item.document = self.source.document if token: item.token = token item.position = token.pos elif position is None: raise ValueError("position must be specified if no token") else: item.position = position if consume: item.tokens = tuple(self.consume()) if not token and item.tokens: item.position = item.tokens[0].pos return item
[docs] def add_bracketed(self, item, source): """Append the arguments between brackets to the item. Returns True if that succeeded, else False. """ for t in source: if isinstance(t, lilypond.OpenBracket): tokens = [t] item.extend(self.read(self.consume(tokens.append))) item.tokens = tuple(tokens) return True elif not isinstance(t, lex.Space): self.source.pushback() break return False
[docs] def read(self, source=None): """Yield Item instances reading from source.""" source = source or self.source for t in skip(source): item = self.read_item(t, source) if item: yield item
[docs] def read_item(self, t, source=None): """Return one Item that starts with token t. May return None.""" meth = self._tokencls.method(t) if meth: return meth(self, t, source or self.source)
[docs] @_tokencls(lilypond.SchemeStart) @_markup(lilypond.SchemeStart) def handle_scheme_start(self, t, source=None): return self.read_scheme_item(t)
[docs] @_tokencls(lex.StringStart) @_markup(lex.StringStart) @_scheme(lex.StringStart) def handle_string_start(self, t, source=None): return self.factory(String, t, True)
[docs] @_tokencls( lilypond.DecimalValue, lilypond.IntegerValue, lilypond.Fraction, ) def handle_number_class(self, t, source=None): return self.factory(Number, t)
[docs] @_tokencls(lilypond.MusicItem) def handle_music_item(self, t, source): return self.read_music_item(t, source)
[docs] @_tokencls(lilypond.Length) def handle_length(self, t, source): item = self.factory(Unpitched, position=t.pos) self.add_duration(item, t, source) return item
[docs] @_tokencls(lilypond.ChordStart) def handle_chord_start(self, t, source): if not self.in_chord: self.in_chord = True chord = self.factory(Chord, t) def last(t): chord.tokens += (t,) chord.extend(self.read(self.consume(last))) self.in_chord = False self.add_duration(chord, None, source) return chord
[docs] @_tokencls( lilypond.OpenBracket, lilypond.OpenSimultaneous, lilypond.SimultaneousOrSequentialCommand, ) def handle_music_list(self, t, source): item, it = self.test_music_list(t) if item: if it: item.extend(self.read(it)) return item
[docs] @_tokencls(lilypond.Command) def read_command(self, t, source): """Read the rest of a command given in t from the source.""" meth = self._commands.method(t) if meth: return meth(self, t, source) return self.factory(Command, t)
[docs] @_tokencls(lilypond.Keyword) def read_keyword(self, t, source): """Read the rest of a keyword given in t from the source.""" meth = self._keywords.method(t) if meth: return meth(self, t, source) return self.factory(Keyword, t)
[docs] @_tokencls(lilypond.UserCommand) def read_user_command(self, t, source): """Read a user command, this can be a variable reference.""" return self.factory(UserCommand, t)
[docs] @_tokencls(lilypond.ChordSeparator) def read_chord_specifier(self, t, source=None): """Read stuff behind notes in chordmode.""" item = self.factory(ChordSpecifier, position=t.pos) item.append(self.factory(ChordItem, t)) for t in self.consume(): if isinstance(t, lilypond.ChordItem): item.append(self.factory(ChordItem, t)) elif isinstance(t, lilypond.Note): r = ly.pitch.pitchReader(self.language)(t) if r: note = self.factory(Note, t) note.pitch = ly.pitch.Pitch(*r) item.append(note) return item
[docs] @_tokencls(lilypond.TremoloColon) def read_tremolo(self, t, source=None): """Read a tremolo.""" item = self.factory(Tremolo, t) for t in self.source: if isinstance(t, lilypond.TremoloDuration): item.append(self.factory(Duration, t)) item.duration = ly.duration.base_scaling_string(t) else: self.source.pushback() break return item
[docs] @_tokencls(lilypond.Name, lilypond.ContextProperty) def handle_name(self, t, source): return self.read_assignment(t)
[docs] @_tokencls( lilypond.PaperVariable, lilypond.LayoutVariable, lilypond.HeaderVariable, lilypond.UserVariable, ) def handle_variable_assignment(self, t, source): item = self.read_assignment(t) if item: # handle \pt, \in etc. for t in skip(self.source): if isinstance(t, lilypond.Unit): item.append(self.factory(Command, t)) else: self.source.pushback() break return item
_direct_items = { lilypond.VoiceSeparator: VoiceSeparator, lilypond.PipeSymbol: PipeSymbol, lilypond.Dynamic: Dynamic, lilypond.Tie: Tie, }
[docs] @_tokencls(*_direct_items) def handle_direct_items(self, t, source): """Tokens that directly translate to an Item.""" return self.factory(self._direct_items[t.__class__], t)
[docs] @_tokencls(lilypond.Direction) def handle_direction(self, t, source): item = self.factory(Postfix, t) item.direction = '_-^'.index(t) - 1 for t in skip(source): if isinstance(t, ( lex.StringStart, lilypond.MarkupStart, lilypond.Articulation, lilypond.Slur, lilypond.Beam, lilypond.Dynamic, )): item.append(self.read_item(t)) elif isinstance(t, lilypond.Command) and t in ('\\tag'): item.append(self.read_item(t)) elif isinstance(t, lilypond.Keyword) and t in ('\\tweak'): item.append(self.read_item(t)) else: self.source.pushback() break return item
[docs] @_tokencls(lilypond.Slur) def handle_slurs(self, t, source=None): cls = PhrasingSlur if t.startswith('\\') else Slur item = self.factory(cls, t) item.event = 'start' if t.endswith('(') else 'stop' return item
[docs] @_tokencls(lilypond.Beam) def handle_beam(self, t, source=None): item = self.factory(Beam, t) item.event = 'start' if t == '[' else 'stop' return item
[docs] @_tokencls(lilypond.Articulation) def handle_articulation(self, t, source=None): return self.factory(Articulation, t)
[docs] def read_assignment(self, t): """Read an assignment from the variable name. May return None.""" item = self.factory(Assignment, t) for t in skip(self.source): if isinstance(t, (lilypond.Variable, lilypond.UserVariable, lilypond.DotPath)): item.append(self.factory(PathItem, t)) elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) for i in self.read(): item.append(i) break return item elif isinstance(t, lilypond.SchemeStart): # accept only one scheme item, if another one is found, # return the first, and discard the Assignment item # (should not normally happen) for s in item.find(Scheme): self.source.pushback() return s item.append(self.read_scheme_item(t)) else: self.source.pushback() return
[docs] def test_music_list(self, t): r"""Test whether a music list ({ ... }, << ... >>, starts here. Also handles \simultaneous { ... } and \sequential { ... } correctly. These obscure commands are not even highlighted by lex, but they exist in LilyPond... \simultaneous { ... } is like << ... >> but \sequential << ... >> just behaves like << ... >> Returns a two-tuple(item; iterable), both may be None. If item is not None, it can be either a UserCommand or a MusicList. If iterable is None, the item is a UserCommand (namely \simultaneous or \sequential, but not followed by a { or <<); else the item is a MusicList, and the iterable should be read fully to get all the tokens inside the MusicList. If item is None, there is no MusicList and no token is read. This way you can handle the { ... } and << ... >> transparently in every input mode. """ def make_music_list(t, simultaneous, tokens=()): """Make the MusicList item.""" item = self.factory(MusicList, t) item.simultaneous = simultaneous item.tokens = tokens def last(t): item.tokens += (t,) return item, self.consume(last) if isinstance(t, (lilypond.OpenBracket, lilypond.OpenSimultaneous)): return make_music_list(t, t == '<<') elif isinstance(t, lilypond.SimultaneousOrSequentialCommand): for t1 in skip(self.source): if isinstance(t1, (lilypond.OpenBracket, lilypond.OpenSimultaneous)): return make_music_list(t, t == '\\simultaneous' or t1 == '<<', (t1,)) else: self.source.pushback() return self.factory(Keyword, t), None return None, None
[docs] def read_music_item(self, t, source): r"""Read one music item (note, rest, s, \skip, or q) from t and source.""" item = None in_pitch_command = isinstance(self.source.state.parser(), lilypond.ParsePitchCommand) if t.__class__ == lilypond.Note: r = ly.pitch.pitchReader(self.language)(t) if r: item = self.factory(Note, t) p = item.pitch = ly.pitch.Pitch(*r) for t in source: if isinstance(t, lilypond.Octave): p.octave = ly.pitch.octaveToNum(t) item.octave_token = t elif isinstance(t, lilypond.Accidental): item.accidental_token = p.accidental = t elif isinstance(t, lilypond.OctaveCheck): p.octavecheck = ly.pitch.octaveToNum(t) item.octavecheck_token = t break elif not isinstance(t, lex.Space): self.source.pushback() break else: cls = { lilypond.Rest: Rest, lilypond.Skip: Skip, lilypond.Spacer: Skip, lilypond.Q: Q, lilypond.DrumNote: DrumNote, }[t.__class__] item = self.factory(cls, t) if item: if not self.in_chord and not in_pitch_command: self.add_duration(item, None, source) return item
[docs] @_commands('\\relative') def handle_relative(self, t, source): item = self.factory(Relative, t) # get one pitch and exit on a non-comment pitch_found = False for i in self.read(source): item.append(i) if not pitch_found and isinstance(i, Note): pitch_found = True continue break return item
[docs] @_commands('\\absolute') def handle_absolute(self, t, source): item = self.factory(Absolute, t) for i in self.read(source): item.append(i) break return item
[docs] @_commands('\\transpose') def handle_transpose(self, t, source): item = self.factory(Transpose, t) # get two pitches pitches_found = 0 for i in self.read(source): item.append(i) if pitches_found < 2 and isinstance(i, Note): pitches_found += 1 continue break return item
[docs] @_commands('\\clef') def handle_clef(self, t, source): item = self.factory(Clef, t) for t in skip(source): if isinstance(t, lilypond.ClefSpecifier): item._specifier = t elif isinstance(t, lex.StringStart): item._specifier = self.factory(String, t, True) break return item
[docs] @_commands('\\key') def handle_key(self, t, source): item = self.factory(KeySignature, t) item.extend(itertools.islice(self.read(source), 2)) return item
[docs] @_commands('\\times', '\\tuplet', '\\scaleDurations') def handle_scaler(self, t, source): item = self.factory(Scaler, t) item.scaling = 1 if t == '\\scaleDurations': for i in self.read(source): item.append(i) if isinstance(i, Number): item.scaling = i.value() elif isinstance(i, Scheme): try: pair = i.get_pair_ints() if pair: item.scaling = Fraction(*pair) else: val = i.get_fraction() if val is not None: item.scaling = val except ZeroDivisionError: pass break elif t == '\\tuplet': for t in source: if isinstance(t, lilypond.Fraction): item.append(self.factory(Number, t)) item.numerator, item.denominator = map(int, t.split('/')) item.scaling = 1 / Fraction(t) elif isinstance(t, lilypond.Duration): self.add_duration(item, t, source) break elif not isinstance(t, lex.Space): self.source.pushback() break else: # t == '\\times' for t in source: if isinstance(t, lilypond.Fraction): item.append(self.factory(Number, t)) item.numerator, item.denominator = map(int, t.split('/')) item.scaling = Fraction(t) break elif not isinstance(t, lex.Space): self.source.pushback() break for i in self.read(source): item.append(i) break return item
[docs] @_commands('\\tag', '\\keepWithTag', '\\removeWithTag', '\\appendToTag', '\\pushToTag') def handle_tag(self, t, source): item = self.factory(Tag, t) argcount = 3 if t in ('\\appendToTag', '\\pushToTag') else 2 item.extend(itertools.islice(self.read(), argcount)) return item
[docs] @_commands('\\grace', '\\acciaccatura', '\\appoggiatura', '\\slashedGrace') def handle_grace(self, t, source): item = self.factory(Grace, t) for i in self.read(source): item.append(i) break return item
[docs] @_commands('\\afterGrace') def handle_after_grace(self, t, source): item = self.factory(AfterGrace, t) for i in itertools.islice(self.read(source), 2): item.append(i) # put the grace music in a Grace item if len(item) > 1: i = self.factory(Grace, position=item[-1].position) i.append(item[-1]) item.append(i) return item
[docs] @_commands('\\repeat') def handle_repeat(self, t, source): item = self.factory(Repeat, t) item._specifier = None item._repeat_count = None for t in skip(source): if isinstance(t, lilypond.RepeatSpecifier): item._specifier = t elif not item.specifier and isinstance(t, lex.StringStart): item._specifier = self.factory(String, t, True) elif isinstance(t, lilypond.RepeatCount): item._repeat_count = t elif isinstance(t, lilypond.SchemeStart): # the specifier or count may be specified using scheme s = self.read_scheme_item(t) if item._specifier: if item._repeat_count: item.append(s) break item._repeat_count = s else: item._specifier = s else: self.source.pushback() for i in self.read(source): item.append(i) break for t in skip(source): if t == '\\alternative' and isinstance(t, lilypond.Command): item.append(self.handle_alternative(t, source)) else: self.source.pushback() break break return item
[docs] @_commands('\\alternative') def handle_alternative(self, t, source): item = self.factory(Alternative, t) for i in self.read(source): item.append(i) break return item
[docs] @_commands('\\tempo') def handle_tempo(self, t, source): item = self.factory(Tempo, t) source = self.consume() equal_sign_seen = False text_seen = False t = None for t in source: if not equal_sign_seen: if (not text_seen and isinstance(t, (lilypond.SchemeStart, lex.StringStart, lilypond.Markup))): item.append(self.read_item(t)) t = None text_seen = True elif isinstance(t, lilypond.Length): self.add_duration(item, t, source) t = None elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) equal_sign_seen = True t = None elif isinstance(t, (lilypond.IntegerValue, lilypond.SchemeStart)): item.append(self.read_item(t)) elif t == "-": item.tokens += (t,) ## if the last token does not belong to the \\tempo expression anymore, ## push it back if t and not isinstance(t, (lex.Space, lex.Comment)): self.source.pushback() return item
[docs] @_commands('\\time') def handle_time(self, t, source): item = self.factory(TimeSignature, t) for t in skip(source): if isinstance(t, lilypond.SchemeStart): item._beatstructure = self.read_scheme_item(t) continue elif isinstance(t, lilypond.Fraction): item._num, den = map(int, t.split('/')) item._fraction = Fraction(1, den) else: self.source.pushback() break return item
[docs] @_commands('\\partial') def handle_partial(self, t, source): item = self.factory(Partial, t) self.add_duration(item, None, source) return item
[docs] @_commands('\\new', '\\context', '\\change') def handle_translator(self, t, source): cls = Change if t == '\\change' else Context item = self.factory(cls, t) isource = self.consume() for t in skip(isource): if isinstance(t, (lilypond.ContextName, lilypond.Name)): item._context = t for t in isource: if isinstance(t, lilypond.EqualSign): for t in isource: if isinstance(t, lex.StringStart): item._context_id = self.factory(String, t, True) break elif isinstance(t, lilypond.Name): item._context_id = t break elif not isinstance(t, lex.Space): self.source.pushback() break elif not isinstance(t, lex.Space): self.source.pushback() break else: self.source.pushback() break if cls is not Change: for i in self.read(source): item.append(i) if not isinstance(i, With): break return item
_inputmode_commands = { '\\notemode': NoteMode, '\\notes': NoteMode, '\\chordmode': ChordMode, '\\chords': ChordMode, '\\figuremode': FigureMode, '\\figures': FigureMode, '\\drummode': DrumMode, '\\drums': DrumMode, }
[docs] @_commands(*_inputmode_commands) def handle_inputmode(self, t, source): cls = self._inputmode_commands[t] item = self.factory(cls, t) for i in self.read(): item.append(i) break return item
_lyricmode_commands = { '\\lyricmode': LyricMode, '\\lyrics': LyricMode, '\\oldaddlyrics': LyricMode, '\\addlyrics': LyricMode, '\\lyricsto': LyricsTo, }
[docs] @_commands(*_lyricmode_commands) def handle_lyricmode(self, t, source): cls = self._lyricmode_commands[t] item = self.factory(cls, t) if cls is LyricsTo: for t in skip(source): if isinstance(t, lilypond.Name): item._context_id = t elif isinstance(t, (lex.String, lilypond.SchemeStart)): item._context_id = self.read_item(t) else: self.source.pushback() break for t in skip(self.consume()): i = self.read_lyric_item(t) or self.read_item(t) if i: item.append(i) break return item
[docs] def read_lyric_item(self, t): """Read one lyric item. Returns None for tokens it does not handle.""" if isinstance(t, (lex.StringStart, lilypond.MarkupStart)): item = self.factory(LyricText, position=t.pos) item.append(self.read_item(t)) self.add_duration(item) return item elif isinstance(t, lilypond.LyricText): item = self.factory(LyricText, t) self.add_duration(item) return item elif isinstance(t, lilypond.Lyric): return self.factory(LyricItem, t) else: item, source = self.test_music_list(t) if item: if source: for t in skip(source): i = self.read_lyric_item(t) or self.read_item(t) if i: item.append(i) return item
[docs] @_commands('\\stringTuning') def handle_string_tuning(self, t, source): item = self.factory(StringTuning, t) for arg in self.read(source): item.append(arg) break return item
[docs] @_commands('\\partcombine') def handle_partcombine(self, t, source=None): item = self.factory(PartCombine, t) item.extend(itertools.islice(self.read(), 2)) return item
[docs] @_keywords('\\language') def handle_language(self, t, source): item = self.factory(Language, t) for name in self.read(source): item.append(name) if isinstance(name, String): value = item.language = name.value() if value in ly.pitch.pitchInfo: self.language = value break return item
[docs] @_keywords('\\include') def handle_include(self, t, source): item = None name = None for name in self.read(source): if isinstance(name, String): value = name.value() if value.endswith('.ly') and value[:-3] in ly.pitch.pitchInfo: item = self.factory(Language, t) item.language = self.language = value[:-3] item.append(name) break if not item: item = self.factory(Include, t) if name: item.append(name) return item
[docs] @_keywords('\\version') def handle_version(self, t, source): item = self.factory(Version, t) for arg in self.read(source): item.append(arg) break return item
_bracketed_keywords = { '\\header': Header, '\\score': Score, '\\bookpart': BookPart, '\\book': Book, '\\paper': Paper, '\\layout': Layout, '\\midi': Midi, '\\with': With, '\\context': LayoutContext, }
[docs] @_keywords(*_bracketed_keywords) def handle_bracketed(self, t, source): cls = self._bracketed_keywords[t] item = self.factory(cls, t) if not self.add_bracketed(item, source) and t == '\\with': # \with also supports one other argument instead of { ... } for i in self.read(source): item.append(i) break return item
[docs] @_keywords('\\set') def handle_set(self, t, source): item = self.factory(Set, t) tokens = [] for t in skip(source): tokens.append(t) if isinstance(t, lilypond.EqualSign): item.tokens = tuple(tokens) for i in self.read(source): item.append(i) break break return item
[docs] @_keywords('\\unset') def handle_unset(self, t, source): item = self.factory(Unset, t) tokens = [] for t in skip(self.consume()): if type(t) not in lilypond.ParseUnset.items: self.source.pushback() break tokens.append(t) item.tokens = tuple(tokens) return item
[docs] @_keywords('\\override') def handle_override(self, t, source): item = self.factory(Override, t) for t in skip(self.consume()): if isinstance(t, (lex.StringStart, lilypond.SchemeStart)): item.append(self.read_item(t)) elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) for i in self.read(): item.append(i) break break else: item.append(self.factory(PathItem, t)) return item
[docs] @_keywords('\\revert') def handle_revert(self, t, source): item = self.factory(Revert, t) t = None for t in skip(self.consume()): if type(t) in lilypond.ParseRevert.items: item.append(self.factory(PathItem, t)) else: break if isinstance(t, lilypond.SchemeStart) and not any( isinstance(i.token, lilypond.GrobProperty) for i in item): item.append(self.read_scheme_item(t)) else: self.source.pushback() return item
[docs] @_keywords('\\tweak') def handle_tweak(self, t, source): item = self.factory(Tweak, t) t = None for t in skip(self.consume()): if type(t) in lilypond.ParseTweak.items: item.append(self.factory(PathItem, t)) else: self.source.pushback() break if len(item) == 0 and isinstance(t, lilypond.SchemeStart): item.append(self.read_scheme_item(t)) for i in self.read(): item.append(i) break return item
[docs] @_commands('\\markup', '\\markuplist', '\\markuplines') def handle_markup(self, t, source=None): item = self.factory(Markup, t) self.add_markup_arguments(item) return item
[docs] def read_markup(self, t): """Read LilyPond markup (recursively).""" meth = self._markup.method(t) if meth: return meth(self, t)
[docs] @_markup(lilypond.MarkupScore) def handle_markup_score(self, t): item = self.factory(MarkupScore, t) for t in self.consume(): if isinstance(t, lilypond.OpenBracket): item.tokens = (t,) def last(t): item.tokens += (t,) item.extend(self.read(self.consume(last))) return item elif not isinstance(t, lex.Space): self.source.pushback() break return item
[docs] @_markup(lilypond.MarkupCommand) def handle_markup_command(self, t): item = self.factory(MarkupCommand, t) self.add_markup_arguments(item) return item
[docs] @_markup(lilypond.MarkupUserCommand) def handle_markup_user_command(self, t): item = self.factory(MarkupUserCommand, t) return item
[docs] @_markup(lilypond.OpenBracketMarkup) def handle_markup_open_bracket(self, t): item = self.factory(MarkupList, t) self.add_markup_arguments(item) return item
[docs] @_markup(lilypond.MarkupWord) def handle_markup_word(self, t): return self.factory(MarkupWord, t)
[docs] def add_markup_arguments(self, item): """Add markup arguments to the item.""" for t in self.consume(): i = self.read_markup(t) if i: item.append(i) elif isinstance(item, MarkupList) and isinstance(t, lilypond.CloseBracketMarkup): item.tokens = (t,) return item
[docs] def read_scheme_item(self, t): """Reads a Scheme expression (just after the # in LilyPond mode).""" item = self.factory(Scheme, t) for t in self.consume(): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) break return item
[docs] def read_scheme(self, t): """Return a Scheme item from the token t.""" meth = self._scheme.method(t) if meth: return meth(self, t)
[docs] @_scheme(scheme.Quote) def handle_scheme_quote(self, t): item = self.factory(SchemeQuote, t) for t in self.consume(): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) break return item
[docs] @_scheme(scheme.OpenParen) def handle_scheme_open_parenthesis(self, t): item = self.factory(SchemeList, t) def last(t): item.tokens = (t,) for t in self.consume(last): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) return item
[docs] @_scheme( scheme.Dot, scheme.Bool, scheme.Char, scheme.Word, scheme.Number, scheme.Fraction, scheme.Float, ) def handle_scheme_token(self, t): return self.factory(SchemeItem, t)
[docs] @_scheme(scheme.LilyPondStart) def handle_scheme_lilypond_start(self, t): item = self.factory(SchemeLily, t) def last(t): item.tokens = (t,) item.extend(self.read(self.consume(last))) return item