# 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,
)