1# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
2#
3# Copyright (c) 2008 - 2015 by Wilbert Berendsen
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18# See http://www.gnu.org/licenses/ for more information.
19
20"""
21Parses and tokenizes LilyPond input.
22"""
23
24from __future__ import unicode_literals
25
26import itertools
27
28from . import _token
29from . import Parser, FallthroughParser
30
31
32re_articulation = r"[-_^][_.>|!+^-]"
33re_dynamic = (
34    r"\\[<!>]|"
35    r"\\(f{1,5}|p{1,5}"
36    r"|mf|mp|fp|spp?|sff?|sfz|rfz"
37    r"|cresc|decresc|dim|cr|decr"
38    r")(?![A-Za-z])")
39
40re_duration = r"(\\(maxima|longa|breve)\b|(1|2|4|8|16|32|64|128|256|512|1024|2048)(?!\d))"
41re_dot = r"\."
42re_scaling = r"\*[\t ]*\d+(/\d+)?"
43
44# an identifier allowing letters and single hyphens in between
45re_identifier = r"[^\W\d_]+([_-][^\W\d_]+)*"
46
47# the lookahead pattern for the end of an identifier (ref)
48re_identifier_end = r"(?![_-]?[^\W\d])"
49
50
51class Identifier(_token.Token):
52    """A variable name, like ``some-variable``."""
53    rx = r"(?<![^\W\d])" + re_identifier + re_identifier_end
54
55
56class IdentifierRef(_token.Token):
57    r"""A reference to an identifier, e.g. ``\some-variable``."""
58    rx = r"\\" + re_identifier + re_identifier_end
59
60
61class Variable(Identifier):
62    pass
63
64
65class UserVariable(Identifier):
66    pass
67
68
69class Value(_token.Item, _token.Numeric):
70    pass
71
72
73class DecimalValue(Value):
74    rx = r"-?\d+(\.\d+)?"
75
76
77class IntegerValue(DecimalValue):
78    rx = r"\d+"
79
80
81class Fraction(Value):
82    rx = r"\d+/\d+"
83
84
85class Delimiter(_token.Token):
86    pass
87
88
89class DotPath(Delimiter):
90    """A dot in dotted path notation."""
91    rx = r"\."
92
93
94class Error(_token.Error):
95    pass
96
97
98class Comment(_token.Comment):
99    pass
100
101
102class BlockCommentStart(Comment, _token.BlockCommentStart):
103    rx = r"%{"
104    def update_state(self, state):
105        state.enter(ParseBlockComment())
106
107
108class BlockCommentEnd(Comment, _token.BlockCommentEnd, _token.Leaver):
109    rx = r"%}"
110
111
112class BlockComment(Comment, _token.BlockComment):
113    pass
114
115
116class LineComment(Comment, _token.LineComment):
117    rx = r"%.*$"
118
119
120class String(_token.String):
121    pass
122
123
124class StringQuotedStart(String, _token.StringStart):
125    rx = r'"'
126    def update_state(self, state):
127        state.enter(ParseString())
128
129
130class StringQuotedEnd(String, _token.StringEnd):
131    rx = r'"'
132    def update_state(self, state):
133        state.leave()
134        state.endArgument()
135
136
137class StringQuoteEscape(_token.Character):
138    rx = r'\\[\\"]'
139
140
141class MusicItem(_token.Token):
142    r"""A note, rest, spacer, ``\skip`` or ``q``."""
143
144
145class Skip(MusicItem):
146    rx = r"\\skip" + re_identifier_end
147
148
149class Spacer(MusicItem):
150    rx = r"s(?![A-Za-z])"
151
152
153class Rest(MusicItem):
154    rx = r"[Rr](?![A-Za-z])"
155
156
157class Note(MusicItem):
158    rx = r"[a-x]+(?![A-Za-z])"
159
160
161class Q(MusicItem):
162    rx = r"q(?![A-Za-z])"
163
164
165class DrumNote(MusicItem):
166    rx = r"[a-z]+(?![A-Za-z])"
167
168
169class Octave(_token.Token):
170    rx = r",+|'+"
171
172
173class OctaveCheck(_token.Token):
174    rx = r"=(,+|'+)?"
175
176
177class Accidental(_token.Token):
178    pass
179
180
181class AccidentalReminder(Accidental):
182    rx = r"!"
183
184
185class AccidentalCautionary(Accidental):
186    rx = r"\?"
187
188
189class Duration(_token.Token):
190    pass
191
192
193class Length(Duration):
194    rx = re_duration
195    def update_state(self, state):
196        state.enter(ParseDuration())
197
198
199class Dot(Duration):
200    rx = re_dot
201
202
203class Scaling(Duration):
204    rx = re_scaling
205
206
207class OpenBracket(Delimiter, _token.MatchStart, _token.Indent):
208    """An open bracket, does not enter different parser, subclass or reimplement Parser.update_state()."""
209    rx = r"\{"
210    matchname = "bracket"
211
212
213class CloseBracket(Delimiter, _token.MatchEnd, _token.Dedent):
214    rx = r"\}"
215    matchname = "bracket"
216    def update_state(self, state):
217        state.leave()
218        state.endArgument()
219
220
221class OpenSimultaneous(Delimiter, _token.MatchStart, _token.Indent):
222    """An open double French quote, does not enter different parser, subclass or reimplement Parser.update_state()."""
223    rx = r"<<"
224    matchname = "simultaneous"
225
226
227class CloseSimultaneous(Delimiter, _token.MatchEnd, _token.Dedent):
228    rx = r">>"
229    matchname = "simultaneous"
230    def update_state(self, state):
231        state.leave()
232        state.endArgument()
233
234
235class SequentialStart(OpenBracket):
236    def update_state(self, state):
237        state.enter(ParseMusic())
238
239
240class SequentialEnd(CloseBracket):
241    pass
242
243
244class SimultaneousStart(OpenSimultaneous):
245    def update_state(self, state):
246        state.enter(ParseMusic())
247
248
249class SimultaneousEnd(CloseSimultaneous):
250    pass
251
252
253class PipeSymbol(Delimiter):
254    rx = r"\|"
255
256
257class Articulation(_token.Token):
258    """Base class for articulation things."""
259
260
261class ArticulationCommand(Articulation, IdentifierRef):
262    @classmethod
263    def test_match(cls, match):
264        s = match.group()[1:]
265        if '-' not in s:
266            from .. import words
267            for l in (
268                words.articulations,
269                words.ornaments,
270                words.fermatas,
271                words.instrument_scripts,
272                words.repeat_scripts,
273                words.ancient_scripts,
274            ):
275                if s in l:
276                    return True
277        return False
278
279
280class Direction(_token.Token):
281    rx = r"[-_^]"
282    def update_state(self, state):
283        state.enter(ParseScriptAbbreviationOrFingering())
284
285
286class ScriptAbbreviation(Articulation, _token.Leaver):
287    rx = r"[+|!>._^-]"
288
289
290class Fingering(Articulation, _token.Leaver):
291    rx = r"\d+"
292
293
294class StringNumber(Articulation):
295    rx = r"\\\d+"
296
297
298class Slur(_token.Token):
299    pass
300
301
302class SlurStart(Slur, _token.MatchStart):
303    rx = r"\("
304    matchname = "slur"
305
306
307class SlurEnd(Slur, _token.MatchEnd):
308    rx = r"\)"
309    matchname = "slur"
310
311
312class PhrasingSlurStart(SlurStart):
313    rx = r"\\\("
314    matchname = "phrasingslur"
315
316
317class PhrasingSlurEnd(SlurEnd):
318    rx = r"\\\)"
319    matchname = "phrasingslur"
320
321
322class Tie(Slur):
323    rx = r"~"
324
325
326class Beam(_token.Token):
327    pass
328
329
330class BeamStart(Beam, _token.MatchStart):
331    rx = r"\["
332    matchname = "beam"
333
334
335class BeamEnd(Beam, _token.MatchEnd):
336    rx = r"\]"
337    matchname = "beam"
338
339
340class Ligature(_token.Token):
341    pass
342
343
344class LigatureStart(Ligature, _token.MatchStart):
345    rx = r"\\\["
346    matchname = "ligature"
347
348
349class LigatureEnd(Ligature, _token.MatchEnd):
350    rx = r"\\\]"
351    matchname = "ligature"
352
353
354class Tremolo(_token.Token):
355    pass
356
357
358class TremoloColon(Tremolo):
359    rx = r":"
360    def update_state(self, state):
361        state.enter(ParseTremolo())
362
363
364class TremoloDuration(Tremolo, _token.Leaver):
365    rx = r"\b(8|16|32|64|128|256|512|1024|2048)(?!\d)"
366
367
368class ChordItem(_token.Token):
369    """Base class for chordmode items."""
370
371
372class ChordModifier(ChordItem):
373    rx = r"((?<![a-z])|^)(aug|dim|sus|min|maj|m)(?![a-z])"
374
375
376class ChordSeparator(ChordItem):
377    rx = r":|\^|/\+?"
378
379
380class ChordStepNumber(ChordItem):
381    rx = r"\d+[-+]?"
382
383
384class DotChord(ChordItem):
385    rx = r"\."
386
387
388class VoiceSeparator(Delimiter):
389    rx = r"\\\\"
390
391
392class Dynamic(_token.Token):
393    rx = re_dynamic
394
395
396class Command(_token.Item, IdentifierRef):
397    @classmethod
398    def test_match(cls, match):
399        s = match.group()[1:]
400        if '-' not in s:
401            from .. import words
402            return s in words.lilypond_music_commands
403        return False
404
405
406class Keyword(_token.Item, IdentifierRef):
407    @classmethod
408    def test_match(cls, match):
409        s = match.group()[1:]
410        if '-' not in s:
411            from .. import words
412            return s in words.lilypond_keywords
413        return False
414
415
416class Specifier(_token.Token):
417    # a specifier of a command e.g. the name of clef or repeat style.
418    pass
419
420
421class Score(Keyword):
422    rx = r"\\score\b"
423    def update_state(self, state):
424        state.enter(ExpectScore())
425
426
427class Book(Keyword):
428    rx = r"\\book\b"
429    def update_state(self, state):
430        state.enter(ExpectBook())
431
432
433class BookPart(Keyword):
434    rx = r"\\bookpart\b"
435    def update_state(self, state):
436        state.enter(ExpectBookPart())
437
438
439class Paper(Keyword):
440    rx = r"\\paper\b"
441    def update_state(self, state):
442        state.enter(ExpectPaper())
443
444
445class Header(Keyword):
446    rx = r"\\header\b"
447    def update_state(self, state):
448        state.enter(ExpectHeader())
449
450
451class Layout(Keyword):
452    rx = r"\\layout\b"
453    def update_state(self, state):
454        state.enter(ExpectLayout())
455
456
457class Midi(Keyword):
458    rx = r"\\midi\b"
459    def update_state(self, state):
460        state.enter(ExpectMidi())
461
462
463class With(Keyword):
464    rx = r"\\with\b"
465    def update_state(self, state):
466        state.enter(ExpectWith())
467
468
469class LayoutContext(Keyword):
470    rx = r"\\context\b"
471    def update_state(self, state):
472        state.enter(ExpectContext())
473
474
475class Markup(_token.Item):
476    """Base class for all markup commands."""
477
478
479class MarkupStart(Markup, Command):
480    rx = r"\\markup" + re_identifier_end
481    def update_state(self, state):
482        state.enter(ParseMarkup(1))
483
484
485class MarkupLines(Markup):
486    rx = r"\\markuplines" + re_identifier_end
487    def update_state(self, state):
488        state.enter(ParseMarkup(1))
489
490
491class MarkupList(Markup):
492    rx = r"\\markuplist" + re_identifier_end
493    def update_state(self, state):
494        state.enter(ParseMarkup(1))
495
496
497class MarkupCommand(Markup, IdentifierRef):
498    """A markup command."""
499    @classmethod
500    def test_match(cls, match):
501        from .. import words
502        return match.group()[1:] in words.markupcommands
503
504    def update_state(self, state):
505        from .. import words
506        command = self[1:]
507        if command in words.markupcommands_nargs[0]:
508            state.endArgument()
509        else:
510            for argcount in 2, 3, 4, 5:
511                if command in words.markupcommands_nargs[argcount]:
512                    break
513            else:
514                argcount = 1
515            state.enter(ParseMarkup(argcount))
516
517
518class MarkupScore(Markup):
519    rx = r"\\score\b"
520    def update_state(self, state):
521        state.enter(ExpectScore())
522
523
524class MarkupUserCommand(Markup, IdentifierRef):
525    """A user-defined markup (i.e. not in the words markupcommands list)."""
526    def update_state(self, state):
527        state.endArgument()
528
529
530class MarkupWord(_token.Item):
531    rx = r'[^{}"\\\s#%]+'
532
533
534class OpenBracketMarkup(OpenBracket):
535    def update_state(self, state):
536        state.enter(ParseMarkup())
537
538
539class CloseBracketMarkup(CloseBracket):
540    def update_state(self, state):
541        # go back to the opening bracket, this is the ParseMarkup
542        # parser with the 0 argcount
543        while state.parser().argcount > 0:
544            state.leave()
545        state.leave()
546        state.endArgument()
547
548
549class Repeat(Command):
550    rx = r"\\repeat(?![A-Za-z])"
551    def update_state(self, state):
552        state.enter(ParseRepeat())
553
554
555class RepeatSpecifier(Specifier):
556    @_token.patternproperty
557    def rx():
558        from .. import words
559        return r"\b({0})(?![A-Za-z])".format("|".join(words.repeat_types))
560
561
562class RepeatCount(IntegerValue, _token.Leaver):
563    pass
564
565
566class Tempo(Command):
567    rx = r"\\tempo\b"
568    def update_state(self, state):
569        state.enter(ParseTempo())
570
571
572class TempoSeparator(Delimiter):
573    rx = r"[-~](?=\s*\d)"
574
575
576class Partial(Command):
577    rx = r"\\partial\b"
578
579
580class Override(Keyword):
581    rx = r"\\override\b"
582    def update_state(self, state):
583        state.enter(ParseOverride())
584
585
586class Set(Override):
587    rx = r"\\set\b"
588    def update_state(self, state):
589        state.enter(ParseSet())
590
591
592class Revert(Override):
593    rx = r"\\revert\b"
594    def update_state(self, state):
595        state.enter(ParseRevert())
596
597
598class Unset(Keyword):
599    rx = r"\\unset\b"
600    def update_state(self, state):
601        state.enter(ParseUnset())
602
603
604class Tweak(Keyword):
605    rx = r"\\tweak\b"
606    def update_state(self, state):
607        state.enter(ParseTweak())
608
609
610class Translator(Command):
611    def update_state(self, state):
612        state.enter(ParseTranslator())
613
614
615class New(Translator):
616    rx = r"\\new\b"
617
618
619class Context(Translator):
620    rx = r"\\context\b"
621
622
623class Change(Translator):
624    rx = r"\\change\b"
625
626
627class AccidentalStyle(Command):
628    rx = r"\\accidentalStyle\b"
629    def update_state(self, state):
630        state.enter(ParseAccidentalStyle())
631
632
633class AccidentalStyleSpecifier(Specifier):
634    @_token.patternproperty
635    def rx():
636        from .. import words
637        return r"\b({0})(?!-?\w)".format("|".join(words.accidentalstyles))
638
639
640class AlterBroken(Command):
641    rx = r"\\alterBroken\b"
642    def update_state(self, state):
643        state.enter(ParseAlterBroken())
644
645
646class Clef(Command):
647    rx = r"\\clef\b"
648    def update_state(self, state):
649        state.enter(ParseClef())
650
651
652class ClefSpecifier(Specifier):
653    @_token.patternproperty
654    def rx():
655        from .. import words
656        return r"\b({0})\b".format("|".join(words.clefs_plain))
657
658    def update_state(self, state):
659        state.leave()
660
661
662class PitchCommand(Command):
663    rx = r"\\(relative|transpose|transposition|key|octaveCheck)\b"
664    def update_state(self, state):
665        argcount = 2 if self == '\\transpose' else 1
666        state.enter(ParsePitchCommand(argcount))
667
668
669class KeySignatureMode(Command):
670    @_token.patternproperty
671    def rx():
672        from .. import words
673        return r"\\({0})(?![A-Za-z])".format("|".join(words.modes))
674
675
676class Hide(Keyword):
677    rx = r"\\hide\b"
678    def update_state(self, state):
679        state.enter(ParseHideOmit())
680
681
682class Omit(Keyword):
683    rx = r"\\omit\b"
684    def update_state(self, state):
685        state.enter(ParseHideOmit())
686
687
688class Unit(Command):
689    rx = r"\\(mm|cm|in|pt)\b"
690
691
692class InputMode(Command):
693    pass
694
695
696class LyricMode(InputMode):
697    rx = r"\\(lyricmode|((old)?add)?lyrics|lyricsto)\b"
698    def update_state(self, state):
699        state.enter(ExpectLyricMode())
700
701
702class Lyric(_token.Item):
703    """Base class for Lyric items."""
704
705
706class LyricText(Lyric):
707    rx = r"[^\\\s\d\"]+"
708
709
710class LyricHyphen(Lyric):
711    rx = r"--(?=($|[\s\\]))"
712
713
714class LyricExtender(Lyric):
715    rx = r"__(?=($|[\s\\]))"
716
717
718class LyricSkip(Lyric):
719    rx = r"_(?=($|[\s\\]))"
720
721
722class Figure(_token.Token):
723    """Base class for Figure items."""
724
725
726class FigureStart(Figure):
727    rx = r"<"
728    def update_state(self, state):
729        state.enter(ParseFigure())
730
731
732class FigureEnd(Figure, _token.Leaver):
733    rx = r">"
734
735
736class FigureBracket(Figure):
737    rx = r"[][]"
738
739
740class FigureStep(Figure):
741    """A step figure number or the underscore."""
742    rx = r"_|\d+"
743
744
745class FigureAccidental(Figure):
746    """A figure accidental."""
747    rx = r"[-+!]+"
748
749
750class FigureModifier(Figure):
751    """A figure modifier."""
752    rx = r"\\[\\!+]|/"
753
754
755class NoteMode(InputMode):
756    rx = r"\\(notes|notemode)\b"
757    def update_state(self, state):
758        state.enter(ExpectNoteMode())
759
760
761class ChordMode(InputMode):
762    rx = r"\\(chords|chordmode)\b"
763    def update_state(self, state):
764        state.enter(ExpectChordMode())
765
766
767class DrumMode(InputMode):
768    rx = r"\\(drums|drummode)\b"
769    def update_state(self, state):
770        state.enter(ExpectDrumMode())
771
772
773class FigureMode(InputMode):
774    rx = r"\\(figures|figuremode)\b"
775    def update_state(self, state):
776        state.enter(ExpectFigureMode())
777
778
779class UserCommand(IdentifierRef):
780    pass
781
782
783class SimultaneousOrSequentialCommand(Keyword):
784    rx = r"\\(simultaneous|sequential)\b"
785
786
787class SchemeStart(_token.Item):
788    rx = "[#$](?![{}])"
789    def update_state(self, state):
790        from . import scheme
791        state.enter(scheme.ParseScheme(1))
792
793
794class ContextName(_token.Token):
795    @_token.patternproperty
796    def rx():
797        from .. import words
798        return r"\b({0})\b".format("|".join(words.contexts))
799
800
801class BackSlashedContextName(ContextName):
802    @_token.patternproperty
803    def rx():
804        from .. import words
805        return r"\\({0})\b".format("|".join(words.contexts))
806
807
808class GrobName(_token.Token):
809    @_token.patternproperty
810    def rx():
811        from .. import data
812        return r"\b({0})\b".format("|".join(data.grobs()))
813
814
815class GrobProperty(Variable):
816    rx = r"\b([a-z]+|[XY])(-([a-z]+|[XY]))*(?![\w])"
817
818
819class ContextProperty(Variable):
820    @_token.patternproperty
821    def rx():
822        from .. import data
823        return r"\b({0})\b".format("|".join(data.context_properties()))
824
825
826class PaperVariable(Variable):
827    """A variable inside Paper. Always follow this one by UserVariable."""
828    @classmethod
829    def test_match(cls, match):
830        from .. import words
831        return match.group() in words.papervariables
832
833
834class HeaderVariable(Variable):
835    """A variable inside Header. Always follow this one by UserVariable."""
836    @classmethod
837    def test_match(cls, match):
838        from .. import words
839        return match.group() in words.headervariables
840
841
842class LayoutVariable(Variable):
843    """A variable inside Header. Always follow this one by UserVariable."""
844    @classmethod
845    def test_match(cls, match):
846        from .. import words
847        return match.group() in words.layoutvariables
848
849
850class Chord(_token.Token):
851    """Base class for Chord delimiters."""
852    pass
853
854
855class ChordStart(Chord):
856    rx = r"<"
857    def update_state(self, state):
858        state.enter(ParseChord())
859
860
861class ChordEnd(Chord, _token.Leaver):
862    rx = r">"
863
864
865class DrumChordStart(ChordStart):
866    def update_state(self, state):
867        state.enter(ParseDrumChord())
868
869
870class DrumChordEnd(ChordEnd):
871    pass
872
873
874class ErrorInChord(Error):
875    rx = "|".join((
876        re_articulation, # articulation
877        r"<<|>>", # double french quotes
878        r"\\[\\\]\[\(\)()]", # slurs beams
879        re_duration, # duration
880        re_scaling, # scaling
881    ))
882
883
884class Name(UserVariable):
885    r"""A variable name without \ prefix."""
886
887
888class EqualSign(_token.Token):
889    rx = r"="
890
891
892# Parsers
893class ParseLilyPond(Parser):
894    mode = 'lilypond'
895
896# basic stuff that can appear everywhere
897space_items = (
898    _token.Space,
899    BlockCommentStart,
900    LineComment,
901)
902
903
904base_items = space_items + (
905    SchemeStart,
906    StringQuotedStart,
907)
908
909
910# items that represent commands in both toplevel and music mode
911command_items = (
912    Repeat,
913    PitchCommand,
914    Override, Revert,
915    Set, Unset,
916    Hide, Omit,
917    Tweak,
918    New, Context, Change,
919    With,
920    Clef,
921    Tempo,
922    Partial,
923    KeySignatureMode,
924    AccidentalStyle,
925    AlterBroken,
926    SimultaneousOrSequentialCommand,
927    ChordMode, DrumMode, FigureMode, LyricMode, NoteMode,
928    MarkupStart, MarkupLines, MarkupList,
929    ArticulationCommand,
930    Keyword,
931    Command,
932    SimultaneousOrSequentialCommand,
933    UserCommand,
934)
935
936
937# items that occur in toplevel, book, bookpart or score
938# no Leave-tokens!
939toplevel_base_items = base_items + (
940    SequentialStart,
941    SimultaneousStart,
942) + command_items
943
944
945# items that occur in music expressions
946music_items = base_items + (
947    Dynamic,
948    Skip,
949    Spacer,
950    Q,
951    Rest,
952    Note,
953    Fraction,
954    Length,
955    Octave,
956    OctaveCheck,
957    AccidentalCautionary,
958    AccidentalReminder,
959    PipeSymbol,
960    VoiceSeparator,
961    SequentialStart, SequentialEnd,
962    SimultaneousStart, SimultaneousEnd,
963    ChordStart,
964    ContextName,
965    GrobName,
966    SlurStart, SlurEnd,
967    PhrasingSlurStart, PhrasingSlurEnd,
968    Tie,
969    BeamStart, BeamEnd,
970    LigatureStart, LigatureEnd,
971    Direction,
972    StringNumber,
973    IntegerValue,
974) + command_items
975
976
977# items that occur inside chords
978music_chord_items = (
979    ErrorInChord,
980    ChordEnd,
981) + music_items
982
983
984
985class ParseGlobal(ParseLilyPond):
986    """Parses LilyPond from the toplevel of a file."""
987    items = (
988        Book,
989        BookPart,
990        Score,
991        MarkupStart, MarkupLines, MarkupList,
992        Paper, Header, Layout,
993    ) + toplevel_base_items + (
994        Name,
995        DotPath,
996        EqualSign,
997        Fraction,
998        DecimalValue,
999    )
1000    def update_state(self, state, token):
1001        if isinstance(token, EqualSign):
1002            state.enter(ParseGlobalAssignment())
1003
1004
1005class ParseGlobalAssignment(FallthroughParser, ParseLilyPond):
1006    items = space_items + (
1007        Skip,
1008        Spacer,
1009        Q,
1010        Rest,
1011        Note,
1012        Length,
1013        Fraction,
1014        DecimalValue,
1015        Direction,
1016        StringNumber,
1017        Dynamic,
1018    )
1019
1020
1021class ExpectOpenBracket(FallthroughParser, ParseLilyPond):
1022    """Waits for an OpenBracket and then replaces the parser with the class set in the replace attribute.
1023
1024    Subclass this to set the destination for the OpenBracket.
1025
1026    """
1027    default = Error
1028    items = space_items + (
1029        OpenBracket,
1030    )
1031    def update_state(self, state, token):
1032        if isinstance(token, OpenBracket):
1033            state.replace(self.replace())
1034
1035
1036class ExpectMusicList(FallthroughParser, ParseLilyPond):
1037    """Waits for an OpenBracket or << and then replaces the parser with the class set in the replace attribute.
1038
1039    Subclass this to set the destination for the OpenBracket.
1040
1041    """
1042    items = space_items + (
1043        OpenBracket,
1044        OpenSimultaneous,
1045        SimultaneousOrSequentialCommand,
1046    )
1047    def update_state(self, state, token):
1048        if isinstance(token, (OpenBracket, OpenSimultaneous)):
1049            state.replace(self.replace())
1050
1051
1052class ParseScore(ParseLilyPond):
1053    r"""Parses the expression after ``\score {``, leaving at ``}`` """
1054    items = (
1055        CloseBracket,
1056        Header, Layout, Midi, With,
1057    ) + toplevel_base_items
1058
1059
1060class ExpectScore(ExpectOpenBracket):
1061    replace = ParseScore
1062
1063
1064class ParseBook(ParseLilyPond):
1065    r"""Parses the expression after ``\book {``, leaving at ``}`` """
1066    items = (
1067        CloseBracket,
1068        MarkupStart, MarkupLines, MarkupList,
1069        BookPart,
1070        Score,
1071        Paper, Header, Layout,
1072    ) + toplevel_base_items
1073
1074
1075
1076class ExpectBook(ExpectOpenBracket):
1077    replace = ParseBook
1078
1079
1080class ParseBookPart(ParseLilyPond):
1081    r"""Parses the expression after ``\bookpart {``, leaving at ``}`` """
1082    items = (
1083        CloseBracket,
1084        MarkupStart, MarkupLines, MarkupList,
1085        Score,
1086        Paper, Header, Layout,
1087    ) + toplevel_base_items
1088
1089
1090class ExpectBookPart(ExpectOpenBracket):
1091    replace = ParseBookPart
1092
1093
1094class ParsePaper(ParseLilyPond):
1095    r"""Parses the expression after ``\paper {``, leaving at ``}`` """
1096    items = base_items + (
1097        CloseBracket,
1098        MarkupStart, MarkupLines, MarkupList,
1099        PaperVariable,
1100        UserVariable,
1101        EqualSign,
1102        DotPath,
1103        DecimalValue,
1104        Unit,
1105    )
1106
1107
1108class ExpectPaper(ExpectOpenBracket):
1109    replace = ParsePaper
1110
1111
1112class ParseHeader(ParseLilyPond):
1113    r"""Parses the expression after ``\header {``, leaving at ``}`` """
1114    items = (
1115        CloseBracket,
1116        MarkupStart, MarkupLines, MarkupList,
1117        HeaderVariable,
1118        UserVariable,
1119        EqualSign,
1120        DotPath,
1121    ) + toplevel_base_items
1122
1123
1124class ExpectHeader(ExpectOpenBracket):
1125    replace = ParseHeader
1126
1127
1128class ParseLayout(ParseLilyPond):
1129    r"""Parses the expression after ``\layout {``, leaving at ``}`` """
1130    items = base_items + (
1131        CloseBracket,
1132        LayoutContext,
1133        LayoutVariable,
1134        UserVariable,
1135        EqualSign,
1136        DotPath,
1137        DecimalValue,
1138        Unit,
1139        ContextName,
1140        GrobName,
1141    ) + command_items
1142
1143
1144class ExpectLayout(ExpectOpenBracket):
1145    replace = ParseLayout
1146
1147
1148class ParseMidi(ParseLilyPond):
1149    r"""Parses the expression after ``\midi {``, leaving at ``}`` """
1150    items = base_items + (
1151        CloseBracket,
1152        LayoutContext,
1153        LayoutVariable,
1154        UserVariable,
1155        EqualSign,
1156        DotPath,
1157        DecimalValue,
1158        Unit,
1159        ContextName,
1160        GrobName,
1161    ) + command_items
1162
1163
1164class ExpectMidi(ExpectOpenBracket):
1165    replace = ParseMidi
1166
1167
1168class ParseWith(ParseLilyPond):
1169    r"""Parses the expression after ``\with {``, leaving at ``}`` """
1170    items = (
1171        CloseBracket,
1172        ContextName,
1173        GrobName,
1174        ContextProperty,
1175        EqualSign,
1176        DotPath,
1177    ) + toplevel_base_items
1178
1179
1180class ExpectWith(ExpectOpenBracket):
1181    replace = ParseWith
1182
1183
1184class ParseContext(ParseLilyPond):
1185    r"""Parses the expression after (``\layout {``) ``\context {``, leaving at ``}`` """
1186    items = (
1187        CloseBracket,
1188        BackSlashedContextName,
1189        ContextProperty,
1190        EqualSign,
1191        DotPath,
1192    ) + toplevel_base_items
1193
1194
1195class ExpectContext(ExpectOpenBracket):
1196    replace = ParseContext
1197
1198
1199class ParseMusic(ParseLilyPond):
1200    """Parses LilyPond music expressions."""
1201    items = music_items + (
1202        TremoloColon,
1203    )
1204
1205
1206class ParseChord(ParseMusic):
1207    """LilyPond inside chords ``< >``"""
1208    items = music_chord_items
1209
1210
1211class ParseString(Parser):
1212    default = String
1213    items = (
1214        StringQuotedEnd,
1215        StringQuoteEscape,
1216    )
1217
1218
1219class ParseBlockComment(Parser):
1220    default = BlockComment
1221    items = (
1222        BlockCommentEnd,
1223    )
1224
1225
1226class ParseMarkup(Parser):
1227    items =  (
1228        MarkupScore,
1229        MarkupCommand,
1230        MarkupUserCommand,
1231        OpenBracketMarkup,
1232        CloseBracketMarkup,
1233        MarkupWord,
1234    ) + base_items
1235
1236
1237class ParseRepeat(FallthroughParser):
1238    items = space_items + (
1239        RepeatSpecifier,
1240        StringQuotedStart,
1241        RepeatCount,
1242    )
1243
1244
1245class ParseTempo(FallthroughParser):
1246    items = space_items + (
1247        MarkupStart,
1248        StringQuotedStart,
1249        SchemeStart,
1250        Length,
1251        EqualSign,
1252    )
1253    def update_state(self, state, token):
1254        if isinstance(token, EqualSign):
1255            state.replace(ParseTempoAfterEqualSign())
1256
1257
1258class ParseTempoAfterEqualSign(FallthroughParser):
1259    items = space_items + (
1260        IntegerValue,
1261        TempoSeparator,
1262    )
1263
1264
1265class ParseDuration(FallthroughParser):
1266    items = space_items + (
1267        Dot,
1268    )
1269    def fallthrough(self, state):
1270        state.replace(ParseDurationScaling())
1271
1272
1273class ParseDurationScaling(ParseDuration):
1274    items = space_items + (
1275        Scaling,
1276    )
1277    def fallthrough(self, state):
1278        state.leave()
1279
1280
1281class ParseOverride(ParseLilyPond):
1282    argcount = 0
1283    items = (
1284        ContextName,
1285        DotPath,
1286        GrobName,
1287        GrobProperty,
1288        EqualSign,
1289    ) + base_items
1290    def update_state(self, state, token):
1291        if isinstance(token, EqualSign):
1292            state.replace(ParseDecimalValue())
1293
1294
1295class ParseRevert(FallthroughParser):
1296    r"""parse the arguments of ``\revert``"""
1297    # allow both the old scheme syntax but also the dotted 2.18+ syntax
1298    # allow either a dot between the GrobName and the property path or not
1299    # correctly fall through when one property path has been parsed
1300    # (uses ParseGrobPropertyPath and ExpectGrobProperty)
1301    # (When the old scheme syntax is used this parser also falls through,
1302    # assuming that the previous parser will handle it)
1303    items = space_items + (
1304        ContextName,
1305        DotPath,
1306        GrobName,
1307        GrobProperty,
1308    )
1309    def update_state(self, state, token):
1310        if isinstance(token, GrobProperty):
1311            state.replace(ParseGrobPropertyPath())
1312
1313
1314class ParseGrobPropertyPath(FallthroughParser):
1315    items = space_items + (
1316        DotPath,
1317    )
1318    def update_state(self, state, token):
1319        if isinstance(token, DotPath):
1320            state.enter(ExpectGrobProperty())
1321
1322
1323class ExpectGrobProperty(FallthroughParser):
1324    items = space_items + (
1325        GrobProperty,
1326    )
1327    def update_state(self, state, token):
1328        if isinstance(token, GrobProperty):
1329            state.leave()
1330
1331
1332class ParseSet(ParseLilyPond):
1333    argcount = 0
1334    items = (
1335        ContextName,
1336        DotPath,
1337        ContextProperty,
1338        EqualSign,
1339        Name,
1340    ) + base_items
1341    def update_state(self, state, token):
1342        if isinstance(token, EqualSign):
1343            state.replace(ParseDecimalValue())
1344
1345
1346class ParseUnset(FallthroughParser):
1347    items = space_items + (
1348        ContextName,
1349        DotPath,
1350        ContextProperty,
1351        Name,
1352    )
1353    def update_state(self, state, token):
1354        if isinstance(token, ContextProperty) or token[:1].islower():
1355            state.leave()
1356
1357
1358class ParseTweak(FallthroughParser):
1359    items = space_items + (
1360        GrobName,
1361        DotPath,
1362        GrobProperty,
1363    )
1364    def update_state(self, state, token):
1365        if isinstance(token, GrobProperty):
1366            state.replace(ParseTweakGrobProperty())
1367
1368
1369class ParseTweakGrobProperty(FallthroughParser):
1370    items = space_items + (
1371        DotPath,
1372        DecimalValue,
1373    )
1374    def update_state(self, state, token):
1375        if isinstance(token, DotPath):
1376            state.enter(ExpectGrobProperty())
1377        elif isinstance(token, DecimalValue):
1378            state.leave()
1379
1380
1381class ParseTranslator(FallthroughParser):
1382    items = space_items + (
1383        ContextName,
1384        Name,
1385    )
1386
1387    def update_state(self, state, token):
1388        if isinstance(token, (Name, ContextName)):
1389            state.replace(ExpectTranslatorId())
1390
1391
1392class ExpectTranslatorId(FallthroughParser):
1393    items = space_items + (
1394        EqualSign,
1395    )
1396
1397    def update_state(self, state, token):
1398        if token == '=':
1399            state.replace(ParseTranslatorId())
1400
1401
1402class ParseTranslatorId(FallthroughParser):
1403    argcount = 1
1404    items = space_items + (
1405        Name,
1406        StringQuotedStart,
1407    )
1408
1409    def update_state(self, state, token):
1410        if isinstance(token, Name):
1411            state.leave()
1412
1413
1414class ParseClef(FallthroughParser):
1415    argcount = 1
1416    items = space_items + (
1417        ClefSpecifier,
1418        StringQuotedStart,
1419    )
1420
1421
1422class ParseHideOmit(FallthroughParser):
1423    items = space_items + (
1424        ContextName,
1425        DotPath,
1426        GrobName,
1427    )
1428    def update_state(self, state, token):
1429        if isinstance(token, GrobName):
1430            state.leave()
1431
1432
1433class ParseAccidentalStyle(FallthroughParser):
1434    items = space_items + (
1435        ContextName,
1436        DotPath,
1437        AccidentalStyleSpecifier,
1438    )
1439    def update_state(self, state, token):
1440        if isinstance(token, AccidentalStyleSpecifier):
1441            state.leave()
1442
1443
1444class ParseAlterBroken(FallthroughParser):
1445    items = space_items + (
1446        GrobProperty,
1447    )
1448    def update_state(self, state, token):
1449        if isinstance(token, GrobProperty):
1450            state.replace(ParseGrobPropertyPath())
1451
1452
1453class ParseScriptAbbreviationOrFingering(FallthroughParser):
1454    argcount = 1
1455    items = space_items + (
1456        ScriptAbbreviation,
1457        Fingering,
1458    )
1459
1460
1461class ParseInputMode(ParseLilyPond):
1462    """Base class for parser for mode-changing music commands."""
1463    @classmethod
1464    def update_state(cls, state, token):
1465        if isinstance(token, (OpenSimultaneous, OpenBracket)):
1466            state.enter(cls())
1467
1468
1469class ParseLyricMode(ParseInputMode):
1470    r"""Parser for ``\lyrics``, ``\lyricmode``, ``\addlyrics``, etc."""
1471    items = base_items + (
1472        CloseBracket,
1473        CloseSimultaneous,
1474        OpenBracket,
1475        OpenSimultaneous,
1476        PipeSymbol,
1477        LyricHyphen,
1478        LyricExtender,
1479        LyricSkip,
1480        LyricText,
1481        Dynamic,
1482        Skip,
1483        Length,
1484        MarkupStart, MarkupLines, MarkupList,
1485    ) + command_items
1486
1487
1488class ExpectLyricMode(ExpectMusicList):
1489    replace = ParseLyricMode
1490    items = space_items + (
1491        OpenBracket,
1492        OpenSimultaneous,
1493        SchemeStart,
1494        StringQuotedStart,
1495        Name,
1496        SimultaneousOrSequentialCommand,
1497    )
1498
1499
1500class ParseChordMode(ParseInputMode, ParseMusic):
1501    r"""Parser for ``\chords`` and ``\chordmode``."""
1502    items = (
1503        OpenBracket,
1504        OpenSimultaneous,
1505    ) + music_items + ( # TODO: specify items exactly, e.g. < > is not allowed
1506        ChordSeparator,
1507    )
1508    def update_state(self, state, token):
1509        if isinstance(token, ChordSeparator):
1510            state.enter(ParseChordItems())
1511        else:
1512            super(ParseChordMode, self).update_state(state, token)
1513
1514
1515class ExpectChordMode(ExpectMusicList):
1516    replace = ParseChordMode
1517
1518
1519class ParseNoteMode(ParseMusic):
1520    r"""Parser for ``\notes`` and ``\notemode``. Same as Music itself."""
1521
1522
1523class ExpectNoteMode(ExpectMusicList):
1524    replace = ParseNoteMode
1525
1526
1527class ParseDrumChord(ParseMusic):
1528    """LilyPond inside chords in drummode ``< >``"""
1529    items = base_items + (
1530        ErrorInChord,
1531        DrumChordEnd,
1532        Dynamic,
1533        Skip,
1534        Spacer,
1535        Q,
1536        Rest,
1537        DrumNote,
1538        Fraction,
1539        Length,
1540        PipeSymbol,
1541        VoiceSeparator,
1542        SequentialStart, SequentialEnd,
1543        SimultaneousStart, SimultaneousEnd,
1544        ChordStart,
1545        ContextName,
1546        GrobName,
1547        SlurStart, SlurEnd,
1548        PhrasingSlurStart, PhrasingSlurEnd,
1549        Tie,
1550        BeamStart, BeamEnd,
1551        LigatureStart, LigatureEnd,
1552        Direction,
1553        StringNumber,
1554        IntegerValue,
1555    ) + command_items
1556
1557
1558class ParseDrumMode(ParseInputMode, ParseMusic):
1559    r"""Parser for ``\drums`` and ``\drummode``."""
1560    items = (
1561        OpenBracket,
1562        OpenSimultaneous,
1563    ) + base_items + (
1564        Dynamic,
1565        Skip,
1566        Spacer,
1567        Q,
1568        Rest,
1569        DrumNote,
1570        Fraction,
1571        Length,
1572        PipeSymbol,
1573        VoiceSeparator,
1574        SequentialStart, SequentialEnd,
1575        SimultaneousStart, SimultaneousEnd,
1576        DrumChordStart,
1577        ContextName,
1578        GrobName,
1579        SlurStart, SlurEnd,
1580        PhrasingSlurStart, PhrasingSlurEnd,
1581        Tie,
1582        BeamStart, BeamEnd,
1583        LigatureStart, LigatureEnd,
1584        Direction,
1585        StringNumber,
1586        IntegerValue,
1587    ) + command_items
1588
1589
1590class ExpectDrumMode(ExpectMusicList):
1591    replace = ParseDrumMode
1592
1593
1594class ParseFigureMode(ParseInputMode, ParseMusic):
1595    r"""Parser for ``\figures`` and ``\figuremode``."""
1596    items = base_items + (
1597        CloseBracket,
1598        CloseSimultaneous,
1599        OpenBracket,
1600        OpenSimultaneous,
1601        PipeSymbol,
1602        FigureStart,
1603        Skip, Spacer, Rest,
1604        Length,
1605    ) + command_items
1606
1607
1608class ParseFigure(Parser):
1609    """Parse inside ``< >`` in figure mode."""
1610    items = base_items + (
1611        FigureEnd,
1612        FigureBracket,
1613        FigureStep,
1614        FigureAccidental,
1615        FigureModifier,
1616        MarkupStart, MarkupLines, MarkupList,
1617    )
1618
1619
1620class ExpectFigureMode(ExpectMusicList):
1621    replace = ParseFigureMode
1622
1623
1624class ParsePitchCommand(FallthroughParser):
1625    argcount = 1
1626    items = space_items + (
1627        Note,
1628        Octave,
1629    )
1630    def update_state(self, state, token):
1631        if isinstance(token, Note):
1632            self.argcount -= 1
1633        elif isinstance(token, _token.Space) and self.argcount <= 0:
1634            state.leave()
1635
1636
1637class ParseTremolo(FallthroughParser):
1638    items = (TremoloDuration,)
1639
1640
1641class ParseChordItems(FallthroughParser):
1642    items = (
1643        ChordSeparator,
1644        ChordModifier,
1645        ChordStepNumber,
1646        DotChord,
1647        Note,
1648    )
1649
1650
1651class ParseDecimalValue(FallthroughParser):
1652    """Parses a decimal value without a # before it (if present)."""
1653    items = space_items + (
1654        Fraction,
1655        DecimalValue,
1656    )
1657
1658
1659