# 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]def skip(source, what=(lex.Space, lex.Comment)):
"""Yield tokens from source, skipping items of classes specified in what.
By default, comments and whitespace are skipped.
"""
for t in source:
if not isinstance(t, what):
yield t
[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,
}
_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