# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2008 - 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.
"""
Parses and tokenizes LilyPond input.
"""
from __future__ import unicode_literals
import itertools
from . import _token
from . import Parser, FallthroughParser
# an identifier allowing letters and single hyphens in between
re_identifier = r"[^\W\d_]+([_-][^\W\d_]+)*"
# the lookahead pattern for the end of an identifier (ref)
re_identifier_end = r"(?![_-]?[^\W\d])"
re_articulation = r"[-_^][_.>|!+^-]"
re_dynamic = (
    r"\\[<!>]|"
    r"\\(f{1,5}|p{1,5}"
    r"|mf|mp|fp|spp?|sff?|sfz|rfz|n"
    r"|cresc|decresc|dim|cr|decr"
    r")(?![A-Za-z])")
re_duration = rf"(\\(maxima|longa|breve){re_identifier_end}|(1|2|4|8|16|32|64|128|256|512|1024|2048)(?!\d))"
re_dot = r"\."
re_scaling = r"\*[\t ]*\d+(/\d+)?"
[docs]
class Identifier(_token.Token):
    """A variable name, like ``some-variable``."""
    rx = r"(?<![^\W\d])" + re_identifier + re_identifier_end 
[docs]
class IdentifierRef(_token.Token):
    r"""A reference to an identifier, e.g. ``\some-variable``."""
    rx = r"\\" + re_identifier + re_identifier_end 
[docs]
class Variable(Identifier):
    pass 
[docs]
class UserVariable(Identifier):
    pass 
[docs]
class Value(_token.Item, _token.Numeric):
    pass 
[docs]
class DecimalValue(Value):
    rx = r"-?\d+(\.\d+)?" 
[docs]
class IntegerValue(DecimalValue):
    rx = r"\d+" 
[docs]
class Fraction(Value):
    rx = r"\d+/\d+" 
[docs]
class Delimiter(_token.Token):
    pass 
[docs]
class DotPath(Delimiter):
    """A dot in dotted path notation."""
    rx = r"\." 
[docs]
class Error(_token.Error):
    pass 
[docs]
class String(_token.String):
    pass 
[docs]
class StringQuotedStart(String, _token.StringStart):
    rx = r'"'
[docs]
    def update_state(self, state):
        state.enter(ParseString()) 
 
[docs]
class StringQuotedEnd(String, _token.StringEnd):
    rx = r'"'
[docs]
    def update_state(self, state):
        state.leave()
        state.endArgument() 
 
[docs]
class StringQuoteEscape(_token.Character):
    rx = r'\\[\\"]' 
[docs]
class MusicItem(_token.Token):
    r"""A note, rest, spacer, ``\skip`` or ``q``.""" 
[docs]
class Skip(MusicItem):
    rx = r"\\skip" + re_identifier_end 
[docs]
class Spacer(MusicItem):
    rx = r"s(?![A-Za-z])" 
[docs]
class Rest(MusicItem):
    rx = r"[Rr](?![A-Za-z])" 
[docs]
class Note(MusicItem):
    rx = r"[a-x]+(?![A-Za-z])" 
[docs]
class Q(MusicItem):
    rx = r"q(?![A-Za-z])" 
[docs]
class DrumNote(MusicItem):
    rx = r"[a-z]+(?![A-Za-z])" 
[docs]
class Octave(_token.Token):
    rx = r",+|'+" 
[docs]
class OctaveCheck(_token.Token):
    rx = r"=(,+|'+)?" 
[docs]
class Accidental(_token.Token):
    pass 
[docs]
class AccidentalReminder(Accidental):
    rx = r"!" 
[docs]
class AccidentalCautionary(Accidental):
    rx = r"\?" 
[docs]
class Duration(_token.Token):
    pass 
[docs]
class Length(Duration):
    rx = re_duration
[docs]
    def update_state(self, state):
        state.enter(ParseDuration()) 
 
[docs]
class Dot(Duration):
    rx = re_dot 
[docs]
class Scaling(Duration):
    rx = re_scaling 
[docs]
class OpenBracket(Delimiter, _token.MatchStart, _token.Indent):
    """An open bracket, does not enter different parser, subclass or reimplement Parser.update_state()."""
    rx = r"\{"
    matchname = "bracket" 
[docs]
class CloseBracket(Delimiter, _token.MatchEnd, _token.Dedent):
    rx = r"\}"
    matchname = "bracket"
[docs]
    def update_state(self, state):
        state.leave()
        state.endArgument() 
 
[docs]
class OpenSimultaneous(Delimiter, _token.MatchStart, _token.Indent):
    """An open double French quote, does not enter different parser, subclass or reimplement Parser.update_state()."""
    rx = r"<<"
    matchname = "simultaneous" 
[docs]
class CloseSimultaneous(Delimiter, _token.MatchEnd, _token.Dedent):
    rx = r">>"
    matchname = "simultaneous"
[docs]
    def update_state(self, state):
        state.leave()
        state.endArgument() 
 
[docs]
class SequentialStart(OpenBracket):
[docs]
    def update_state(self, state):
        state.enter(ParseMusic()) 
 
[docs]
class SequentialEnd(CloseBracket):
    pass 
[docs]
class SimultaneousStart(OpenSimultaneous):
[docs]
    def update_state(self, state):
        state.enter(ParseMusic()) 
 
[docs]
class SimultaneousEnd(CloseSimultaneous):
    pass 
[docs]
class PipeSymbol(Delimiter):
    rx = r"\|" 
[docs]
class Articulation(_token.Token):
    """Base class for articulation things.""" 
[docs]
class ArticulationCommand(Articulation, IdentifierRef):
[docs]
    @classmethod
    def test_match(cls, match):
        s = match.group()[1:]
        if '-' not in s:
            from .. import words
            for l in (
                words.articulations,
                words.ornaments,
                words.fermatas,
                words.instrument_scripts,
                words.repeat_scripts,
                words.ancient_scripts,
            ):
                if s in l:
                    return True
        return False 
 
[docs]
class Direction(_token.Token):
    rx = r"[-_^]"
[docs]
    def update_state(self, state):
        state.enter(ParseScriptAbbreviationOrFingering()) 
 
[docs]
class ScriptAbbreviation(Articulation, _token.Leaver):
    rx = r"[+|!>._^-]" 
[docs]
class Fingering(Articulation, _token.Leaver):
    rx = r"\d+" 
[docs]
class StringNumber(Articulation):
    rx = r"\\\d+" 
[docs]
class Slur(_token.Token):
    pass 
[docs]
class SlurStart(Slur, _token.MatchStart):
    rx = r"\("
    matchname = "slur" 
[docs]
class SlurEnd(Slur, _token.MatchEnd):
    rx = r"\)"
    matchname = "slur" 
[docs]
class PhrasingSlurStart(SlurStart):
    rx = r"\\\("
    matchname = "phrasingslur" 
[docs]
class PhrasingSlurEnd(SlurEnd):
    rx = r"\\\)"
    matchname = "phrasingslur" 
[docs]
class Tie(Slur):
    rx = r"~" 
[docs]
class Beam(_token.Token):
    pass 
[docs]
class BeamStart(Beam, _token.MatchStart):
    rx = r"\["
    matchname = "beam" 
[docs]
class BeamEnd(Beam, _token.MatchEnd):
    rx = r"\]"
    matchname = "beam" 
[docs]
class Ligature(_token.Token):
    pass 
[docs]
class LigatureStart(Ligature, _token.MatchStart):
    rx = r"\\\["
    matchname = "ligature" 
[docs]
class LigatureEnd(Ligature, _token.MatchEnd):
    rx = r"\\\]"
    matchname = "ligature" 
[docs]
class Tremolo(_token.Token):
    pass 
[docs]
class TremoloColon(Tremolo):
    rx = r":"
[docs]
    def update_state(self, state):
        state.enter(ParseTremolo()) 
 
[docs]
class TremoloDuration(Tremolo, _token.Leaver):
    rx = r"\b(8|16|32|64|128|256|512|1024|2048)(?!\d)" 
[docs]
class ChordItem(_token.Token):
    """Base class for chordmode items.""" 
[docs]
class ChordModifier(ChordItem):
    rx = r"((?<![a-z])|^)(aug|dim|sus|min|maj|m)(?![a-z])" 
[docs]
class ChordSeparator(ChordItem):
    rx = r":|\^|/\+?" 
[docs]
class ChordStepNumber(ChordItem):
    rx = r"\d+[-+]?" 
[docs]
class DotChord(ChordItem):
    rx = r"\." 
[docs]
class VoiceSeparator(Delimiter):
    rx = r"\\\\" 
[docs]
class Dynamic(_token.Token):
    rx = re_dynamic 
[docs]
class Command(_token.Item, IdentifierRef):
[docs]
    @classmethod
    def test_match(cls, match):
        s = match.group()[1:]
        if '-' not in s:
            from .. import words
            return s in words.lilypond_music_commands
        return False 
 
[docs]
class Keyword(_token.Item, IdentifierRef):
[docs]
    @classmethod
    def test_match(cls, match):
        s = match.group()[1:]
        if '-' not in s:
            from .. import words
            return s in words.lilypond_keywords
        return False 
 
[docs]
class Specifier(_token.Token):
    # a specifier of a command e.g. the name of clef or repeat style.
    pass 
[docs]
class Score(Keyword):
    rx = r"\\score" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectScore()) 
 
[docs]
class Book(Keyword):
    rx = r"\\book" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectBook()) 
 
[docs]
class BookPart(Keyword):
    rx = r"\\bookpart" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectBookPart()) 
 
[docs]
class Paper(Keyword):
    rx = r"\\paper" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectPaper()) 
 
[docs]
class Layout(Keyword):
    rx = r"\\layout" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectLayout()) 
 
[docs]
class Midi(Keyword):
    rx = r"\\midi" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectMidi()) 
 
[docs]
class With(Keyword):
    rx = r"\\with" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectWith()) 
 
[docs]
class LayoutContext(Keyword):
    rx = r"\\context" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectContext()) 
 
[docs]
class Markup(_token.Item):
    """Base class for all markup commands.""" 
[docs]
class MarkupStart(Markup, Command):
    rx = r"\\markup" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseMarkup(1)) 
 
[docs]
class MarkupLines(Markup):
    rx = r"\\markuplines" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseMarkup(1)) 
 
[docs]
class MarkupList(Markup):
    rx = r"\\markuplist" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseMarkup(1)) 
 
[docs]
class MarkupCommand(Markup, IdentifierRef):
    """A markup command."""
[docs]
    @classmethod
    def test_match(cls, match):
        from .. import words
        return match.group()[1:] in words.markupcommands 
[docs]
    def update_state(self, state):
        from .. import words
        command = self[1:]
        if command in words.markupcommands_nargs[0]:
            state.endArgument()
        else:
            for argcount in 2, 3, 4, 5:
                if command in words.markupcommands_nargs[argcount]:
                    break
            else:
                argcount = 1
            state.enter(ParseMarkup(argcount)) 
 
[docs]
class MarkupScore(Markup):
    rx = r"\\score" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectScore()) 
 
[docs]
class MarkupUserCommand(Markup, IdentifierRef):
    """A user-defined markup (i.e. not in the words markupcommands list)."""
[docs]
    def update_state(self, state):
        state.endArgument() 
 
[docs]
class MarkupWord(_token.Item):
    rx = r'[^{}"\\\s#%]+' 
[docs]
class OpenBracketMarkup(OpenBracket):
[docs]
    def update_state(self, state):
        state.enter(ParseMarkup()) 
 
[docs]
class CloseBracketMarkup(CloseBracket):
[docs]
    def update_state(self, state):
        # go back to the opening bracket, this is the ParseMarkup
        # parser with the 0 argcount
        while state.parser().argcount > 0:
            state.leave()
        state.leave()
        state.endArgument() 
 
[docs]
class Repeat(Command):
    rx = r"\\repeat" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseRepeat()) 
 
[docs]
class RepeatSpecifier(Specifier):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\b({0})(?![A-Za-z])".format("|".join(words.repeat_types)) 
[docs]
class RepeatCount(IntegerValue, _token.Leaver):
    pass 
[docs]
class Tempo(Command):
    rx = r"\\tempo" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseTempo()) 
 
[docs]
class TempoSeparator(Delimiter):
    rx = r"[-~](?=\s*\d)" 
[docs]
class Partial(Command):
    rx = r"\\partial" + re_identifier_end 
[docs]
class Override(Keyword):
    rx = r"\\override" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseOverride()) 
 
[docs]
class Set(Override):
    rx = r"\\set" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseSet()) 
 
[docs]
class Revert(Override):
    rx = r"\\revert" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseRevert()) 
 
[docs]
class Unset(Keyword):
    rx = r"\\unset" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseUnset()) 
 
[docs]
class Tweak(Keyword):
    rx = r"\\tweak" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseTweak()) 
 
[docs]
class Translator(Command):
[docs]
    def update_state(self, state):
        state.enter(ParseTranslator()) 
 
[docs]
class New(Translator):
    rx = r"\\new" + re_identifier_end 
[docs]
class Context(Translator):
    rx = r"\\context" + re_identifier_end 
[docs]
class Change(Translator):
    rx = r"\\change" + re_identifier_end 
[docs]
class AccidentalStyle(Command):
    rx = r"\\accidentalStyle" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseAccidentalStyle()) 
 
[docs]
class AccidentalStyleSpecifier(Specifier):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\b({0})(?!-?\w)".format("|".join(words.accidentalstyles)) 
[docs]
class AlterBroken(Command):
    rx = r"\\alterBroken" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseAlterBroken()) 
 
[docs]
class Clef(Command):
    rx = r"\\clef" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseClef()) 
 
[docs]
class ClefSpecifier(Specifier):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\b({0})\b".format("|".join(words.clefs_plain))
[docs]
    def update_state(self, state):
        state.leave() 
 
[docs]
class PitchCommand(Command):
    rx = r"\\(relative|transpose|transposition|key|octaveCheck)" + re_identifier_end
[docs]
    def update_state(self, state):
        argcount = 2 if self == '\\transpose' else 1
        state.enter(ParsePitchCommand(argcount)) 
 
[docs]
class KeySignatureMode(Command):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\\({0})(?![A-Za-z])".format("|".join(words.modes)) 
[docs]
class Hide(Keyword):
    rx = r"\\hide" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseHideOmit()) 
 
[docs]
class Omit(Keyword):
    rx = r"\\omit" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ParseHideOmit()) 
 
[docs]
class Unit(Command):
    rx = r"\\(mm|cm|in|pt|bp)" + re_identifier_end 
[docs]
class LyricMode(InputMode):
    rx = r"\\(lyricmode|((old)?add)?lyrics|lyricsto)" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectLyricMode()) 
 
[docs]
class Lyric(_token.Item):
    """Base class for Lyric items.""" 
[docs]
class LyricText(Lyric):
    rx = r"[^\\\s\d\"]+" 
[docs]
class LyricHyphen(Lyric):
    rx = r"--(?=($|[\s\\]))" 
[docs]
class LyricExtender(Lyric):
    rx = r"__(?=($|[\s\\]))" 
[docs]
class LyricSkip(Lyric):
    rx = r"_(?=($|[\s\\]))" 
[docs]
class NoteMode(InputMode):
    rx = r"\\(notes|notemode)" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectNoteMode()) 
 
[docs]
class ChordMode(InputMode):
    rx = r"\\(chords|chordmode)" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectChordMode()) 
 
[docs]
class DrumMode(InputMode):
    rx = r"\\(drums|drummode)" + re_identifier_end
[docs]
    def update_state(self, state):
        state.enter(ExpectDrumMode()) 
 
[docs]
class UserCommand(IdentifierRef):
    pass 
[docs]
class SimultaneousOrSequentialCommand(Keyword):
    rx = r"\\(simultaneous|sequential)" + re_identifier_end 
[docs]
class SchemeStart(_token.Item):
    rx = "[#$](?![{}])"
[docs]
    def update_state(self, state):
        from . import scheme
        state.enter(scheme.ParseScheme(1)) 
 
[docs]
class ContextName(_token.Token):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\b({0})\b".format("|".join(words.contexts)) 
[docs]
class BackSlashedContextName(ContextName):
    @_token.patternproperty
    def rx():
        from .. import words
        return r"\\({0})\b".format("|".join(words.contexts)) 
[docs]
class GrobName(_token.Token):
    @_token.patternproperty
    def rx():
        from .. import data
        return r"\b({0})\b".format("|".join(data.grobs())) 
[docs]
class GrobProperty(Variable):
    rx = r"\b([a-z]+|[XY])(-([a-z]+|[XY]))*(?![\w])" 
[docs]
class ContextProperty(Variable):
    @_token.patternproperty
    def rx():
        from .. import data
        return r"\b({0})\b".format("|".join(data.context_properties())) 
[docs]
class PaperVariable(Variable):
    """A variable inside Paper. Always follow this one by UserVariable."""
[docs]
    @classmethod
    def test_match(cls, match):
        from .. import words
        return match.group() in words.papervariables 
 
[docs]
class LayoutVariable(Variable):
    """A variable inside Header. Always follow this one by UserVariable."""
[docs]
    @classmethod
    def test_match(cls, match):
        from .. import words
        return match.group() in words.layoutvariables 
 
[docs]
class Chord(_token.Token):
    """Base class for Chord delimiters."""
    pass 
[docs]
class ChordStart(Chord):
    rx = r"<"
[docs]
    def update_state(self, state):
        state.enter(ParseChord()) 
 
[docs]
class ChordEnd(Chord, _token.Leaver):
    rx = r">" 
[docs]
class DrumChordStart(ChordStart):
[docs]
    def update_state(self, state):
        state.enter(ParseDrumChord()) 
 
[docs]
class DrumChordEnd(ChordEnd):
    pass 
[docs]
class ErrorInChord(Error):
    rx = "|".join((
        re_articulation, # articulation
        r"<<|>>", # double french quotes
        r"\\[\\\]\[\(\)()]", # slurs beams
        re_duration, # duration
        re_scaling, # scaling
    )) 
[docs]
class Name(UserVariable):
    r"""A variable name without \ prefix.""" 
[docs]
class EqualSign(_token.Token):
    rx = r"=" 
# Parsers
[docs]
class ParseLilyPond(Parser):
    mode = 'lilypond' 
# basic stuff that can appear everywhere
space_items = (
    _token.Space,
    BlockCommentStart,
    LineComment,
)
base_items = space_items + (
    SchemeStart,
    StringQuotedStart,
)
# items that represent commands in both toplevel and music mode
command_items = (
    Repeat,
    PitchCommand,
    Override, Revert,
    Set, Unset,
    Hide, Omit,
    Tweak,
    New, Context, Change,
    With,
    Clef,
    Tempo,
    Partial,
    KeySignatureMode,
    AccidentalStyle,
    AlterBroken,
    SimultaneousOrSequentialCommand,
    ChordMode, DrumMode, FigureMode, LyricMode, NoteMode,
    MarkupStart, MarkupLines, MarkupList,
    ArticulationCommand,
    Keyword,
    Command,
    SimultaneousOrSequentialCommand,
    UserCommand,
)
# items that occur in toplevel, book, bookpart or score
# no Leave-tokens!
toplevel_base_items = base_items + (
    SequentialStart,
    SimultaneousStart,
) + command_items
# items that occur in music expressions
music_items = base_items + (
    Dynamic,
    Skip,
    Spacer,
    Q,
    Rest,
    Note,
    Fraction,
    Length,
    Octave,
    OctaveCheck,
    AccidentalCautionary,
    AccidentalReminder,
    PipeSymbol,
    VoiceSeparator,
    SequentialStart, SequentialEnd,
    SimultaneousStart, SimultaneousEnd,
    ChordStart,
    ContextName,
    GrobName,
    SlurStart, SlurEnd,
    PhrasingSlurStart, PhrasingSlurEnd,
    Tie,
    BeamStart, BeamEnd,
    LigatureStart, LigatureEnd,
    Direction,
    StringNumber,
    IntegerValue,
) + command_items
# items that occur inside chords
music_chord_items = (
    ErrorInChord,
    ChordEnd,
) + music_items
[docs]
class ParseGlobal(ParseLilyPond):
    """Parses LilyPond from the toplevel of a file."""
    items = (
        Book,
        BookPart,
        Score,
        MarkupStart, MarkupLines, MarkupList,
        Paper, Header, Layout,
    ) + toplevel_base_items + (
        Name,
        DotPath,
        EqualSign,
        Fraction,
        DecimalValue,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, EqualSign):
            state.enter(ParseGlobalAssignment()) 
 
[docs]
class ParseGlobalAssignment(FallthroughParser, ParseLilyPond):
    items = space_items + (
        Skip,
        Spacer,
        Q,
        Rest,
        Note,
        Length,
        Fraction,
        DecimalValue,
        Direction,
        StringNumber,
        Dynamic,
    ) 
[docs]
class ExpectOpenBracket(FallthroughParser, ParseLilyPond):
    """Waits for an OpenBracket and then replaces the parser with the class set in the replace attribute.
    Subclass this to set the destination for the OpenBracket.
    """
    default = Error
    items = space_items + (
        OpenBracket,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, OpenBracket):
            state.replace(self.replace()) 
 
[docs]
class ExpectMusicList(FallthroughParser, ParseLilyPond):
    """Waits for an OpenBracket or << and then replaces the parser with the class set in the replace attribute.
    Subclass this to set the destination for the OpenBracket.
    """
    items = space_items + (
        OpenBracket,
        OpenSimultaneous,
        SimultaneousOrSequentialCommand,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, (OpenBracket, OpenSimultaneous)):
            state.replace(self.replace()) 
 
[docs]
class ParseScore(ParseLilyPond):
    r"""Parses the expression after ``\score {``, leaving at ``}`` """
    items = (
        CloseBracket,
        Header, Layout, Midi, With,
    ) + toplevel_base_items 
[docs]
class ExpectScore(ExpectOpenBracket):
    replace = ParseScore 
[docs]
class ParseBook(ParseLilyPond):
    r"""Parses the expression after ``\book {``, leaving at ``}`` """
    items = (
        CloseBracket,
        MarkupStart, MarkupLines, MarkupList,
        BookPart,
        Score,
        Paper, Header, Layout,
    ) + toplevel_base_items 
[docs]
class ExpectBook(ExpectOpenBracket):
    replace = ParseBook 
[docs]
class ParseBookPart(ParseLilyPond):
    r"""Parses the expression after ``\bookpart {``, leaving at ``}`` """
    items = (
        CloseBracket,
        MarkupStart, MarkupLines, MarkupList,
        Score,
        Paper, Header, Layout,
    ) + toplevel_base_items 
[docs]
class ExpectBookPart(ExpectOpenBracket):
    replace = ParseBookPart 
[docs]
class ParsePaper(ParseLilyPond):
    r"""Parses the expression after ``\paper {``, leaving at ``}`` """
    items = base_items + (
        CloseBracket,
        MarkupStart, MarkupLines, MarkupList,
        PaperVariable,
        UserVariable,
        EqualSign,
        DotPath,
        DecimalValue,
        Unit,
    ) 
[docs]
class ExpectPaper(ExpectOpenBracket):
    replace = ParsePaper 
[docs]
class ParseLayout(ParseLilyPond):
    r"""Parses the expression after ``\layout {``, leaving at ``}`` """
    items = base_items + (
        CloseBracket,
        LayoutContext,
        LayoutVariable,
        UserVariable,
        EqualSign,
        DotPath,
        DecimalValue,
        Unit,
        ContextName,
        GrobName,
    ) + command_items 
[docs]
class ExpectLayout(ExpectOpenBracket):
    replace = ParseLayout 
[docs]
class ParseMidi(ParseLilyPond):
    r"""Parses the expression after ``\midi {``, leaving at ``}`` """
    items = base_items + (
        CloseBracket,
        LayoutContext,
        LayoutVariable,
        UserVariable,
        EqualSign,
        DotPath,
        DecimalValue,
        Unit,
        ContextName,
        GrobName,
    ) + command_items 
[docs]
class ExpectMidi(ExpectOpenBracket):
    replace = ParseMidi 
[docs]
class ParseWith(ParseLilyPond):
    r"""Parses the expression after ``\with {``, leaving at ``}`` """
    items = (
        CloseBracket,
        ContextName,
        GrobName,
        ContextProperty,
        EqualSign,
        DotPath,
    ) + toplevel_base_items 
[docs]
class ExpectWith(ExpectOpenBracket):
    replace = ParseWith 
[docs]
class ParseContext(ParseLilyPond):
    r"""Parses the expression after (``\layout {``) ``\context {``, leaving at ``}`` """
    items = (
        CloseBracket,
        BackSlashedContextName,
        ContextProperty,
        EqualSign,
        DotPath,
    ) + toplevel_base_items 
[docs]
class ExpectContext(ExpectOpenBracket):
    replace = ParseContext 
[docs]
class ParseMusic(ParseLilyPond):
    """Parses LilyPond music expressions."""
    items = music_items + (
        TremoloColon,
    ) 
[docs]
class ParseChord(ParseMusic):
    """LilyPond inside chords ``< >``"""
    items = music_chord_items 
[docs]
class ParseString(Parser):
    default = String
    items = (
        StringQuotedEnd,
        StringQuoteEscape,
    ) 
[docs]
class ParseMarkup(Parser):
    items =  (
        MarkupScore,
        MarkupCommand,
        MarkupUserCommand,
        OpenBracketMarkup,
        CloseBracketMarkup,
        MarkupWord,
    ) + base_items 
[docs]
class ParseRepeat(FallthroughParser):
    items = space_items + (
        RepeatSpecifier,
        StringQuotedStart,
        RepeatCount,
    ) 
[docs]
class ParseTempo(FallthroughParser):
    items = space_items + (
        MarkupStart,
        StringQuotedStart,
        SchemeStart,
        Length,
        EqualSign,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, EqualSign):
            state.replace(ParseTempoAfterEqualSign()) 
 
[docs]
class ParseTempoAfterEqualSign(FallthroughParser):
    items = space_items + (
        IntegerValue,
        TempoSeparator,
    ) 
[docs]
class ParseDuration(FallthroughParser):
    items = space_items + (
        Dot,
    )
[docs]
    def fallthrough(self, state):
        state.replace(ParseDurationScaling()) 
 
[docs]
class ParseDurationScaling(ParseDuration):
    items = space_items + (
        Scaling,
    )
[docs]
    def fallthrough(self, state):
        state.leave() 
 
[docs]
class ParseOverride(ParseLilyPond):
    argcount = 0
    items = (
        ContextName,
        DotPath,
        GrobName,
        GrobProperty,
        EqualSign,
    ) + base_items
[docs]
    def update_state(self, state, token):
        if isinstance(token, EqualSign):
            state.replace(ParseDecimalValue()) 
 
[docs]
class ParseRevert(FallthroughParser):
    r"""parse the arguments of ``\revert``"""
    # allow both the old scheme syntax but also the dotted 2.18+ syntax
    # allow either a dot between the GrobName and the property path or not
    # correctly fall through when one property path has been parsed
    # (uses ParseGrobPropertyPath and ExpectGrobProperty)
    # (When the old scheme syntax is used this parser also falls through,
    # assuming that the previous parser will handle it)
    items = space_items + (
        ContextName,
        DotPath,
        GrobName,
        GrobProperty,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, GrobProperty):
            state.replace(ParseGrobPropertyPath()) 
 
[docs]
class ParseGrobPropertyPath(FallthroughParser):
    items = space_items + (
        DotPath,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, DotPath):
            state.enter(ExpectGrobProperty()) 
 
[docs]
class ExpectGrobProperty(FallthroughParser):
    items = space_items + (
        GrobProperty,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, GrobProperty):
            state.leave() 
 
[docs]
class ParseSet(ParseLilyPond):
    argcount = 0
    items = (
        ContextName,
        DotPath,
        ContextProperty,
        EqualSign,
        Name,
    ) + base_items
[docs]
    def update_state(self, state, token):
        if isinstance(token, EqualSign):
            state.replace(ParseDecimalValue()) 
 
[docs]
class ParseUnset(FallthroughParser):
    items = space_items + (
        ContextName,
        DotPath,
        ContextProperty,
        Name,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, ContextProperty) or token[:1].islower():
            state.leave() 
 
[docs]
class ParseTweak(FallthroughParser):
    items = space_items + (
        GrobName,
        DotPath,
        GrobProperty,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, GrobProperty):
            state.replace(ParseTweakGrobProperty()) 
 
[docs]
class ParseTweakGrobProperty(FallthroughParser):
    items = space_items + (
        DotPath,
        DecimalValue,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, DotPath):
            state.enter(ExpectGrobProperty())
        elif isinstance(token, DecimalValue):
            state.leave() 
 
[docs]
class ParseTranslator(FallthroughParser):
    items = space_items + (
        ContextName,
        Name,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, (Name, ContextName)):
            state.replace(ExpectTranslatorId()) 
 
[docs]
class ExpectTranslatorId(FallthroughParser):
    items = space_items + (
        EqualSign,
    )
[docs]
    def update_state(self, state, token):
        if token == '=':
            state.replace(ParseTranslatorId()) 
 
[docs]
class ParseTranslatorId(FallthroughParser):
    argcount = 1
    items = space_items + (
        Name,
        StringQuotedStart,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, Name):
            state.leave() 
 
[docs]
class ParseClef(FallthroughParser):
    argcount = 1
    items = space_items + (
        ClefSpecifier,
        StringQuotedStart,
    ) 
[docs]
class ParseHideOmit(FallthroughParser):
    items = space_items + (
        ContextName,
        DotPath,
        GrobName,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, GrobName):
            state.leave() 
 
[docs]
class ParseAccidentalStyle(FallthroughParser):
    items = space_items + (
        ContextName,
        DotPath,
        AccidentalStyleSpecifier,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, AccidentalStyleSpecifier):
            state.leave() 
 
[docs]
class ParseAlterBroken(FallthroughParser):
    items = space_items + (
        GrobProperty,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, GrobProperty):
            state.replace(ParseGrobPropertyPath()) 
 
[docs]
class ParseScriptAbbreviationOrFingering(FallthroughParser):
    argcount = 1
    items = space_items + (
        ScriptAbbreviation,
        Fingering,
    ) 
[docs]
class ParseLyricMode(ParseInputMode):
    r"""Parser for ``\lyrics``, ``\lyricmode``, ``\addlyrics``, etc."""
    items = base_items + (
        CloseBracket,
        CloseSimultaneous,
        OpenBracket,
        OpenSimultaneous,
        PipeSymbol,
        LyricHyphen,
        LyricExtender,
        LyricSkip,
        LyricText,
        Dynamic,
        Skip,
        Length,
        MarkupStart, MarkupLines, MarkupList,
    ) + command_items 
[docs]
class ExpectLyricMode(ExpectMusicList):
    replace = ParseLyricMode
    items = space_items + (
        OpenBracket,
        OpenSimultaneous,
        SchemeStart,
        StringQuotedStart,
        Name,
        SimultaneousOrSequentialCommand,
    ) 
[docs]
class ParseChordMode(ParseInputMode, ParseMusic):
    r"""Parser for ``\chords`` and ``\chordmode``."""
    items = (
        OpenBracket,
        OpenSimultaneous,
    ) + music_items + ( # TODO: specify items exactly, e.g. < > is not allowed
        ChordSeparator,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, ChordSeparator):
            state.enter(ParseChordItems())
        else:
            super(ParseChordMode, self).update_state(state, token) 
 
[docs]
class ExpectChordMode(ExpectMusicList):
    replace = ParseChordMode 
[docs]
class ParseNoteMode(ParseMusic):
    r"""Parser for ``\notes`` and ``\notemode``. Same as Music itself.""" 
[docs]
class ExpectNoteMode(ExpectMusicList):
    replace = ParseNoteMode 
[docs]
class ParseDrumChord(ParseMusic):
    """LilyPond inside chords in drummode ``< >``"""
    items = base_items + (
        ErrorInChord,
        DrumChordEnd,
        Dynamic,
        Skip,
        Spacer,
        Q,
        Rest,
        DrumNote,
        Fraction,
        Length,
        PipeSymbol,
        VoiceSeparator,
        SequentialStart, SequentialEnd,
        SimultaneousStart, SimultaneousEnd,
        ChordStart,
        ContextName,
        GrobName,
        SlurStart, SlurEnd,
        PhrasingSlurStart, PhrasingSlurEnd,
        Tie,
        BeamStart, BeamEnd,
        LigatureStart, LigatureEnd,
        Direction,
        StringNumber,
        IntegerValue,
    ) + command_items 
[docs]
class ParseDrumMode(ParseInputMode, ParseMusic):
    r"""Parser for ``\drums`` and ``\drummode``."""
    items = (
        OpenBracket,
        OpenSimultaneous,
    ) + base_items + (
        Dynamic,
        Skip,
        Spacer,
        Q,
        Rest,
        DrumNote,
        Fraction,
        Length,
        PipeSymbol,
        VoiceSeparator,
        SequentialStart, SequentialEnd,
        SimultaneousStart, SimultaneousEnd,
        DrumChordStart,
        ContextName,
        GrobName,
        SlurStart, SlurEnd,
        PhrasingSlurStart, PhrasingSlurEnd,
        Tie,
        BeamStart, BeamEnd,
        LigatureStart, LigatureEnd,
        Direction,
        StringNumber,
        IntegerValue,
    ) + command_items 
[docs]
class ExpectDrumMode(ExpectMusicList):
    replace = ParseDrumMode 
[docs]
class ParsePitchCommand(FallthroughParser):
    argcount = 1
    items = space_items + (
        Note,
        Octave,
    )
[docs]
    def update_state(self, state, token):
        if isinstance(token, Note):
            self.argcount -= 1
        elif isinstance(token, _token.Space) and self.argcount <= 0:
            state.leave() 
 
[docs]
class ParseTremolo(FallthroughParser):
    items = (TremoloDuration,) 
[docs]
class ParseChordItems(FallthroughParser):
    items = (
        ChordSeparator,
        ChordModifier,
        ChordStepNumber,
        DotChord,
        Note,
    ) 
[docs]
class ParseDecimalValue(FallthroughParser):
    """Parses a decimal value without a # before it (if present)."""
    items = space_items + (
        Fraction,
        DecimalValue,
    )