# 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
re_articulation = r"[-_^][_.>|!+^-]"
re_dynamic = (
r"\\[<!>]|"
r"\\(f{1,5}|p{1,5}"
r"|mf|mp|fp|spp?|sff?|sfz|rfz"
r"|cresc|decresc|dim|cr|decr"
r")(?![A-Za-z])")
re_duration = r"(\\(maxima|longa|breve)\b|(1|2|4|8|16|32|64|128|256|512|1024|2048)(?!\d))"
re_dot = r"\."
re_scaling = r"\*[\t ]*\d+(/\d+)?"
# 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])"
[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\b"
[docs] def update_state(self, state):
state.enter(ExpectScore())
[docs]class Book(Keyword):
rx = r"\\book\b"
[docs] def update_state(self, state):
state.enter(ExpectBook())
[docs]class BookPart(Keyword):
rx = r"\\bookpart\b"
[docs] def update_state(self, state):
state.enter(ExpectBookPart())
[docs]class Paper(Keyword):
rx = r"\\paper\b"
[docs] def update_state(self, state):
state.enter(ExpectPaper())
[docs]class Layout(Keyword):
rx = r"\\layout\b"
[docs] def update_state(self, state):
state.enter(ExpectLayout())
[docs]class Midi(Keyword):
rx = r"\\midi\b"
[docs] def update_state(self, state):
state.enter(ExpectMidi())
[docs]class With(Keyword):
rx = r"\\with\b"
[docs] def update_state(self, state):
state.enter(ExpectWith())
[docs]class LayoutContext(Keyword):
rx = r"\\context\b"
[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\b"
[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(?![A-Za-z])"
[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\b"
[docs] def update_state(self, state):
state.enter(ParseTempo())
[docs]class TempoSeparator(Delimiter):
rx = r"[-~](?=\s*\d)"
[docs]class Partial(Command):
rx = r"\\partial\b"
[docs]class Override(Keyword):
rx = r"\\override\b"
[docs] def update_state(self, state):
state.enter(ParseOverride())
[docs]class Set(Override):
rx = r"\\set\b"
[docs] def update_state(self, state):
state.enter(ParseSet())
[docs]class Revert(Override):
rx = r"\\revert\b"
[docs] def update_state(self, state):
state.enter(ParseRevert())
[docs]class Unset(Keyword):
rx = r"\\unset\b"
[docs] def update_state(self, state):
state.enter(ParseUnset())
[docs]class Tweak(Keyword):
rx = r"\\tweak\b"
[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\b"
[docs]class Context(Translator):
rx = r"\\context\b"
[docs]class Change(Translator):
rx = r"\\change\b"
[docs]class AccidentalStyle(Command):
rx = r"\\accidentalStyle\b"
[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\b"
[docs] def update_state(self, state):
state.enter(ParseAlterBroken())
[docs]class Clef(Command):
rx = r"\\clef\b"
[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)\b"
[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\b"
[docs] def update_state(self, state):
state.enter(ParseHideOmit())
[docs]class Omit(Keyword):
rx = r"\\omit\b"
[docs] def update_state(self, state):
state.enter(ParseHideOmit())
[docs]class Unit(Command):
rx = r"\\(mm|cm|in|pt)\b"
[docs]class LyricMode(InputMode):
rx = r"\\(lyricmode|((old)?add)?lyrics|lyricsto)\b"
[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)\b"
[docs] def update_state(self, state):
state.enter(ExpectNoteMode())
[docs]class ChordMode(InputMode):
rx = r"\\(chords|chordmode)\b"
[docs] def update_state(self, state):
state.enter(ExpectChordMode())
[docs]class DrumMode(InputMode):
rx = r"\\(drums|drummode)\b"
[docs] def update_state(self, state):
state.enter(ExpectDrumMode())
[docs]class UserCommand(IdentifierRef):
pass
[docs]class SimultaneousOrSequentialCommand(Keyword):
rx = r"\\(simultaneous|sequential)\b"
[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,
)