Source code for abjad.parsers.reduced

from .. import _iterlib
from .. import bind as _bind
from .. import duration as _duration
from .. import enums as _enums
from .. import exceptions as _exceptions
from .. import indicators as _indicators
from .. import pitch as _pitch
from .. import score as _score
from .. import select as _select
from .base import Parser


[docs]class ReducedLyParser(Parser): r""" Parses the "reduced-ly" syntax, a modified subset of LilyPond syntax. .. container:: example >>> from abjad.parsers.reduced import ReducedLyParser >>> parser = ReducedLyParser() Understands LilyPond-like representation of notes, chords and rests: >>> string = "c'4 r8. <b d' fs'>16" >>> container = parser(string) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'4 r8. <b d' fs'>16 } Also parses bare duration as notes on middle-C, and negative bare durations as rests: >>> string = '4 -8 16. -32' >>> container = parser(string) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'4 r8 c'16. r32 } .. container:: example Note that the leaf syntax is greedy, and therefore duration specifiers following pitch specifiers will be treated as part of the same expression. The following produces 2 leaves, rather than 3: >>> string = "4 d' 4" >>> container = parser(string) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'4 d'4 } Understands LilyPond-like default durations: >>> string = "c'4 d' e' f'" >>> container = parser(string) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c'4 d'4 e'4 f'4 } Also understands various types of container specifications. .. container:: example Can create arbitrarily nested tuplets: >>> string = "2/3 { 4 4 3/5 { 8 8 8 } }" >>> tuplet = parser(string) >>> abjad.show(tuplet) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(tuplet) >>> print(string) \tweak edge-height #'(0.7 . 0) \times 2/3 { c'4 c'4 \tweak text #tuplet-number::calc-fraction-text \tweak edge-height #'(0.7 . 0) \times 3/5 { c'8 c'8 c'8 } } .. container:: example Can create measures too: >>> string = '| 4/4 4 4 4 4 || 3/8 8 8 8 |' >>> container = parser(string) >>> staff = abjad.Staff([container]) >>> score = abjad.Score([staff], name="Score") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { { { \time 4/4 c'4 c'4 c'4 c'4 } { \time 3/8 c'8 c'8 c'8 } } } .. container:: example Finally, understands ties, slurs and beams: >>> string = 'c16 [ ( d ~ d ) f ]' >>> container = parser(string) >>> abjad.show(container) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(container) >>> print(string) { c16 [ ( d16 ~ d16 ) f16 ] } """ ### CLASS VARIABLES ### __slots__ = ("_default_duration", "_toplevel_component_count") ### INITIALIZER ### def __init__(self, debug=False): self._default_duration = _duration.Duration((1, 4)) self._toplevel_component_count = None Parser.__init__(self, debug=debug) ### LEX SETUP ### tokens = ( "APOSTROPHE", "BRACE_L", "BRACE_R", "BRACKET_L", "BRACKET_R", "CARAT_L", "CARAT_R", "COMMA", "DOT", "FRACTION", "INTEGER_N", "INTEGER_P", "PAREN_L", "PAREN_R", "PIPE", "PITCHNAME", "RESTNAME", "TILDE", ) t_APOSTROPHE = "'" t_BRACE_L = "{" t_BRACE_R = "}" t_BRACKET_L = r"\[" t_BRACKET_R = r"\]" t_CARAT_L = r"\<" t_CARAT_R = r"\>" t_COMMA = "," t_DOT = r"\." t_PAREN_L = r"\(" t_PAREN_R = r"\)" t_PIPE = r"\|" t_RESTNAME = "r" t_TILDE = "~" t_ignore = " \t\r" ### YACC SETUP ### start = "start" ### LEX METHODS ###
[docs] def t_FRACTION(self, t): r"([1-9]\d*/[1-9]\d*)" parts = t.value.split("/") t.value = int(parts[0]), int(parts[1]) return t
[docs] def t_INTEGER_N(self, t): r"(-[1-9]\d*)" t.value = int(t.value) return t
[docs] def t_INTEGER_P(self, t): r"([1-9]\d*)" t.value = int(t.value) return t
[docs] def t_PITCHNAME(self, t): r"[a-g](ff|ss|f|s|tqf|tqs|qs|qf)?" t.value = _pitch.NamedPitchClass(t.value) return t
[docs] def t_error(self, t): print(("Illegal character '%s'" % t.value[0])) t.lexer.skip(1)
[docs] def t_newline(self, t): r"\n+" t.lexer.lineno += t.value.count("\n")
### YACC METHODS ###
[docs] def p_apostrophes__APOSTROPHE(self, p): """ apostrophes : APOSTROPHE """ p[0] = 1
[docs] def p_apostrophes__apostrophes__APOSTROPHE(self, p): """ apostrophes : apostrophes APOSTROPHE """ p[0] = p[1] + 1
[docs] def p_beam__BRACKET_L(self, p): """ beam : BRACKET_L """ p[0] = (_indicators.StartBeam, _enums.LEFT)
[docs] def p_beam__BRACKET_R(self, p): """ beam : BRACKET_R """ p[0] = (_indicators.StopBeam, _enums.RIGHT)
[docs] def p_chord_body__chord_pitches(self, p): """ chord_body : chord_pitches """ p[0] = _score.Chord(p[1], self._default_duration)
[docs] def p_chord_body__chord_pitches__positive_leaf_duration(self, p): """ chord_body : chord_pitches positive_leaf_duration """ p[0] = _score.Chord(p[1], p[2])
[docs] def p_chord_pitches__CARAT_L__pitches__CARAT_R(self, p): """ chord_pitches : CARAT_L pitches CARAT_R """ p[0] = p[2]
[docs] def p_commas__COMMA(self, p): """ commas : COMMA """ p[0] = 1
[docs] def p_commas__commas__commas(self, p): """ commas : commas COMMA """ p[0] = p[1] + 1
[docs] def p_component__container(self, p): """ component : container """ p[0] = p[1]
[docs] def p_component__fixed_duration_container(self, p): """ component : fixed_duration_container """ p[0] = p[1]
[docs] def p_component__leaf(self, p): """ component : leaf """ p[0] = p[1]
[docs] def p_component__tuplet(self, p): """ component : tuplet """ p[0] = p[1]
[docs] def p_component_list__EMPTY(self, p): """ component_list : """ p[0] = []
[docs] def p_component_list__component_list__component(self, p): """ component_list : component_list component """ p[0] = p[1] + [p[2]]
[docs] def p_container__BRACE_L__component_list__BRACE_R(self, p): r"""container : BRACE_L component_list BRACE_R""" p[0] = _score.Container() for component in p[2]: p[0].append(component)
[docs] def p_dots__EMPTY(self, p): """ dots : """ p[0] = 0
[docs] def p_dots__dots__DOT(self, p): """ dots : dots DOT """ p[0] = p[1] + 1
[docs] def p_error(self, p): if p: print(("Syntax error at '%s'" % p.value)) else: print("Syntax error at EOF")
[docs] def p_fixed_duration_container__BRACE_L__FRACTION__BRACE_R(self, p): """ fixed_duration_container : BRACE_L FRACTION BRACE_R """ raise Exception("fixed-duration containers no longer supported.")
[docs] def p_leaf__leaf_body__post_events(self, p): """ leaf : leaf_body post_events """ p[0] = p[1] if p[2]: annotation = {"post events": p[2]} _bind._unsafe_attach(annotation, p[0])
[docs] def p_leaf_body__chord_body(self, p): """ leaf_body : chord_body """ p[0] = p[1]
[docs] def p_leaf_body__note_body(self, p): """ leaf_body : note_body """ p[0] = p[1]
[docs] def p_leaf_body__rest_body(self, p): """ leaf_body : rest_body """ p[0] = p[1]
[docs] def p_measure__PIPE__FRACTION__component_list__PIPE(self, p): """ measure : PIPE FRACTION component_list PIPE """ measure = _score.Container() for x in p[3]: measure.append(x) leaf = _iterlib._get_leaf(measure, 0) time_signature = _indicators.TimeSignature(p[2]) try: _bind._unsafe_attach(time_signature, leaf) except _exceptions.MissingContextError: score = _score.Score([measure]) _bind._unsafe_attach(time_signature, leaf) score[:] = [] p[0] = measure
[docs] def p_negative_leaf_duration__INTEGER_N__dots(self, p): """ negative_leaf_duration : INTEGER_N dots """ duration_log = p[1] dots = "." * p[2] string = f"{abs(duration_log)}{dots}" duration = _duration.Duration.from_lilypond_duration_string(string) self._default_duration = duration p[0] = duration
[docs] def p_note_body__pitch(self, p): """ note_body : pitch """ p[0] = _score.Note(p[1], self._default_duration)
[docs] def p_note_body__pitch__positive_leaf_duration(self, p): """ note_body : pitch positive_leaf_duration """ p[0] = _score.Note(p[1], p[2])
[docs] def p_note_body__positive_leaf_duration(self, p): """ note_body : positive_leaf_duration """ p[0] = _score.Note(0, p[1])
[docs] def p_pitch__PITCHNAME(self, p): """ pitch : PITCHNAME """ p[0] = _pitch.NamedPitch(p[1].name)
[docs] def p_pitch__PITCHNAME__apostrophes(self, p): """ pitch : PITCHNAME apostrophes """ p[0] = _pitch.NamedPitch(p[1].name + "'" * p[2])
[docs] def p_pitch__PITCHNAME__commas(self, p): """ pitch : PITCHNAME commas """ p[0] = _pitch.NamedPitch(p[1].name + "," * p[2])
[docs] def p_pitches__pitch(self, p): """ pitches : pitch """ p[0] = [p[1]]
[docs] def p_pitches__pitches__pitch(self, p): """ pitches : pitches pitch """ p[0] = p[1] + [p[2]]
[docs] def p_positive_leaf_duration__INTEGER_P__dots(self, p): """ positive_leaf_duration : INTEGER_P dots """ duration_log = p[1] dots = "." * p[2] duration = _duration.Duration.from_lilypond_duration_string( f"{abs(duration_log)}{dots}" ) self._default_duration = duration p[0] = duration
[docs] def p_post_event__beam(self, p): """ post_event : beam """ p[0] = p[1]
[docs] def p_post_event__slur(self, p): """ post_event : slur """ p[0] = p[1]
[docs] def p_post_event__tie(self, p): """ post_event : tie """ p[0] = p[1]
[docs] def p_post_events__EMPTY(self, p): """ post_events : """ p[0] = {}
[docs] def p_post_events__post_events__post_event(self, p): """ post_events : post_events post_event """ kind, direction = p[2] if kind in p[1]: p[1][kind].append(direction) else: p[1][kind] = [direction] p[0] = p[1]
[docs] def p_rest_body__RESTNAME(self, p): """ rest_body : RESTNAME """ p[0] = _score.Rest(self._default_duration)
[docs] def p_rest_body__RESTNAME__positive_leaf_duration(self, p): """ rest_body : RESTNAME positive_leaf_duration """ p[0] = _score.Rest(p[2])
[docs] def p_rest_body__negative_leaf_duration(self, p): """ rest_body : negative_leaf_duration """ p[0] = _score.Rest(p[1])
[docs] def p_slur__PAREN_L(self, p): """ slur : PAREN_L """ p[0] = (_indicators.StartSlur, _enums.LEFT)
[docs] def p_slur__PAREN_R(self, p): """ slur : PAREN_R """ p[0] = (_indicators.StopSlur, _enums.RIGHT)
[docs] def p_start__EMPTY(self, p): """ start : """ self._toplevel_component_count = 0 p[0] = []
[docs] def p_start__start__component(self, p): """ start : start component """ self._toplevel_component_count += 1 p[0] = p[1] + [p[2]]
[docs] def p_start__start__measure(self, p): """ start : start measure """ self._toplevel_component_count += 1 p[0] = p[1] + [p[2]]
[docs] def p_tie__TILDE(self, p): """ tie : TILDE """ p[0] = (_indicators.Tie, _enums.LEFT)
[docs] def p_tuplet__FRACTION__container(self, p): """ tuplet : FRACTION container """ assert isinstance(p[2], _score.Container) leaves = p[2][:] p[2][:] = [] p[0] = _score.Tuplet(p[1], leaves)
### PRIVATE METHODS ### def _attach_indicators(self, leaves): for leaf in leaves: span_events = self._get_span_events(leaf) for current_class, directions in span_events.items(): if current_class in ( _indicators.StartSlur, _indicators.StopSlur, ): indicator = current_class() _bind._unsafe_attach(indicator, leaf) continue if current_class in ( _indicators.StartBeam, _indicators.StopBeam, ): indicator = current_class() _bind._unsafe_attach(indicator, leaf) continue if current_class is _indicators.Tie: indicator = current_class() _bind._unsafe_attach(indicator, leaf) continue def _cleanup(self, parsed): container = _score.Container() for x in parsed: container.append(x) parsed = container leaves = _select.leaves(parsed) if leaves: self._attach_indicators(leaves) for leaf in leaves: _bind.detach(dict, leaf) if 1 < self._toplevel_component_count: return parsed return parsed[0] def _get_span_events(self, leaf): annotations = leaf._get_indicators(dict) _bind.detach(dict, leaf) annotations = [x for x in annotations if "post events" in x] if annotations: return annotations[0]["post events"] return {} def _setup(self): self._toplevel_component_count = 0 self._default_duration = _duration.Duration((1, 4)) ### PUBLIC PROPERTIES ### @property def debug(self): """ Gets debug boolean of reduced ly parser. Returns true or false. """ return self._debug @property def lexer_rules_object(self): """ Lexer rules object of reduced ly parser. """ return self @property def parser_rules_object(self): """ Parser rules object of reduced ly parser. """ return self
### FUNCTIONS ###
[docs]def parse_reduced_ly_syntax(string) -> _score.Container: """ Parse the reduced LilyPond rhythmic syntax: .. container:: example >>> string = '4 -4. 8.. 5/3 { } 4' >>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(string) >>> container Container("c'4 r4. c'8.. { 5/3 } c'4") >>> for component in container: ... component ... Note("c'4") Rest('r4.') Note("c'8..") Tuplet('3:5', '') Note("c'4") """ return ReducedLyParser()(string)