Browse Source

Chords are now parsed with a regular expression: ply is overkill here

* Preparing for parsing of `{define: FOO}` statements
* Brackets can now contain several space-separated chords (as in `[A/A B#]`).
pull/79/head
Louis 9 years ago
parent
commit
43448ab788
  1. 1
      patacrep/data/examples/songs/chords.sgc
  2. 46
      patacrep/songs/chordpro/ast.py
  3. 25
      patacrep/songs/chordpro/data/latex/content_chord.tex
  4. 12
      patacrep/songs/chordpro/lexer.py
  5. 71
      patacrep/songs/chordpro/syntax.py
  6. 1
      patacrep/songs/chordpro/test/chords.sgc
  7. 1
      patacrep/songs/chordpro/test/chords.txt

1
patacrep/data/examples/songs/chords.sgc

@ -15,3 +15,4 @@
[A/A]Deux notes [A/A]Deux notes
[F/Fb]Deux notes, bémol [F/Fb]Deux notes, bémol
[B/C#]Deux notes, dièse [B/C#]Deux notes, dièse
[A/C# Dmaj]Plusieurs accords

46
patacrep/songs/chordpro/ast.py

@ -117,19 +117,37 @@ class Space(LineElement):
def __str__(self): def __str__(self):
return " " return " "
class Chord(LineElement): class ChordList(LineElement):
"""A chord.""" """A list of chords."""
_template = "chord" _template = "chord"
def __init__(self, key, alteration, modifier, add_note, bass): def __init__(self, *chords):
self.chords = chords
def __str__(self):
return "[{}]".format(" ".join(
[str(chord) for chord in self.chords]
))
class Chord:
"""A chord."""
def __init__(
self,
key,
alteration=None,
modifier=None,
addnote=None,
basskey=None,
bassalteration=None,
):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
super().__init__()
self.key = key self.key = key
self.alteration = alteration self.alteration = alteration
self.modifier = modifier self.modifier = modifier
self.add_note = add_note self.addnote = addnote
self.bass = bass self.basskey = basskey
self.bassalteration = bassalteration
def __str__(self): def __str__(self):
text = "" text = ""
@ -138,13 +156,13 @@ class Chord(LineElement):
text += self.alteration text += self.alteration
if self.modifier is not None: if self.modifier is not None:
text += self.modifier text += self.modifier
if self.add_note is not None: if self.addnote is not None:
text += str(self.add_note) text += str(self.addnote)
if self.bass is not None: if self.basskey is not None:
text += "/" + self.bass[0] text += "/" + self.basskey
if self.bass[1] is not None: if self.bassalteration is not None:
text += self.bass[1] text += self.bassalteration
return "[{}]".format(text) return text
class Verse(AST): class Verse(AST):
"""A verse (or bridge, or chorus)""" """A verse (or bridge, or chorus)"""

25
patacrep/songs/chordpro/data/latex/content_chord.tex

@ -1,13 +1,16 @@
\[ \[
((- content.key -)) (*- for chord in content.chords -*)
(* if content.alteration == '#' *)#(* endif -*) (* if not loop.first *) (* endif *)
(* if content.alteration == 'b' *)&(* endif -*) ((- chord.key -))
(* if content.modifier *)((content.modifier))(* endif -*) (* if chord.alteration == '#' *)#(* endif -*)
(* if content.add_note *)((content.add_note))(* endif -*) (* if chord.alteration == 'b' *)&(* endif -*)
(* if content.bass -*) (* if chord.modifier *)((chord.modifier))(* endif -*)
/ (* if chord.add_note *)((chord.add_note))(* endif -*)
((- content.bass[0] -)) (* if chord.basskey -*)
(* if content.bass[1] == '#' *)#(* endif -*) /
(* if content.bass[1] == 'b' *)&(* endif -*) ((- chord.basskey -))
(* endif -*) (* if chord.bassalteration == '#' *)#(* endif -*)
(* if chord.bassalteration == 'b' *)&(* endif -*)
(* endif -*)
(* endfor -*)
] ]

12
patacrep/songs/chordpro/lexer.py

@ -9,15 +9,11 @@ LOGGER = logging.getLogger()
tokens = ( tokens = (
'LBRACE', 'LBRACE',
'RBRACE', 'RBRACE',
'KEY',
'ALTERATION',
'MODIFIER',
'ADDNOTE',
'SLASH',
'NEWLINE', 'NEWLINE',
'COLON', 'COLON',
'WORD', 'WORD',
'SPACE', 'SPACE',
'CHORD',
'TEXT', 'TEXT',
'KEYWORD', 'KEYWORD',
'SOC', 'SOC',
@ -43,11 +39,7 @@ class ChordProLexer:
t_SPACE = r'[ \t]+' t_SPACE = r'[ \t]+'
t_chord_KEY = r'[A-G]' t_chord_CHORD = r'[A-G#bmajdisus2-9/ ]+'
t_chord_ALTERATION = r'[#b]'
t_chord_MODIFIER = r'(maj|dim|m|sus)'
t_chord_ADDNOTE = r'[2-9]'
t_chord_SLASH = r'/'
t_directive_SPACE = r'[ \t]+' t_directive_SPACE = r'[ \t]+'
t_directive_KEYWORD = r'[a-zA-Z_]+' t_directive_KEYWORD = r'[a-zA-Z_]+'

71
patacrep/songs/chordpro/syntax.py

@ -2,6 +2,7 @@
import logging import logging
import ply.yacc as yacc import ply.yacc as yacc
import re
from patacrep.songs.syntax import Parser from patacrep.songs.syntax import Parser
from patacrep.songs.chordpro import ast from patacrep.songs.chordpro import ast
@ -9,6 +10,32 @@ from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
LOGGER = logging.getLogger() LOGGER = logging.getLogger()
CHORD_RE = re.compile(
r"""
(?P<key>[A-G])
(?P<alteration>[b#])?
(?P<modifier>(maj|sus|dim|m))?
(?P<addnote>[2-9])?
(
/
(?P<basskey>[A-G])
(?P<bassalteration>[b#])?
)?
""",
re.VERBOSE
)
def _parse_chords(string):
"""Parse a list of chords.
Iterate over :class:`ast.Chord` objects.
"""
for chord in string.split():
match = CHORD_RE.match(chord)
if match is None:
TODO
yield ast.Chord(**match.groupdict())
class ChordproParser(Parser): class ChordproParser(Parser):
"""ChordPro parser class""" """ChordPro parser class"""
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
@ -108,48 +135,8 @@ class ChordproParser(Parser):
@staticmethod @staticmethod
def p_chord(symbols): def p_chord(symbols):
"""chord : KEY chordalteration chordmodifier chordaddnote chordbass""" """chord : CHORD"""
symbols[0] = ast.Chord( symbols[0] = ast.ChordList(*_parse_chords(symbols[1]))
symbols[1],
symbols[2],
symbols[3],
symbols[4],
symbols[5],
)
@staticmethod
def p_chordalteration(symbols):
"""chordalteration : ALTERATION
| empty
"""
symbols[0] = symbols[1]
@staticmethod
def p_chordmodifier(symbols):
"""chordmodifier : MODIFIER
| empty
"""
symbols[0] = symbols[1]
@staticmethod
def p_chordaddnote(symbols):
"""chordaddnote : ADDNOTE
| empty
"""
if symbols[1] is None:
symbols[0] = symbols[1]
else:
symbols[0] = int(symbols[1])
@staticmethod
def p_chordbass(symbols):
"""chordbass : SLASH KEY chordalteration
| empty
"""
if len(symbols) == 2:
symbols[0] = None
else:
symbols[0] = (symbols[2], symbols[3])
@staticmethod @staticmethod
def p_chorus(symbols): def p_chorus(symbols):

1
patacrep/songs/chordpro/test/chords.sgc

@ -10,3 +10,4 @@
[A/A]Deux notes [A/A]Deux notes
[F/Fb]Deux notes, bémol [F/Fb]Deux notes, bémol
[B/C#]Deux notes, dièse [B/C#]Deux notes, dièse
[Ab B#/A]Plusieurs notes à la suite

1
patacrep/songs/chordpro/test/chords.txt

@ -12,4 +12,5 @@
[A/A]Deux notes [A/A]Deux notes
[F/Fb]Deux notes, bémol [F/Fb]Deux notes, bémol
[B/C#]Deux notes, dièse [B/C#]Deux notes, dièse
[Ab B#/A]Plusieurs notes à la suite
{end_of_verse} {end_of_verse}

Loading…
Cancel
Save