Browse Source

[chordpro][WIP] Les lignes sont correctement analysées

pull/70/head
Louis 10 years ago
committed by Luthaf
parent
commit
5d0d0d7000
  1. 17
      patacrep/songs/chordpro/__init__.py
  2. 46
      patacrep/songs/chordpro/lexer.py
  3. 88
      patacrep/songs/chordpro/parser.py
  4. 136
      patacrep/songs/chordpro/syntax.py

17
patacrep/songs/chordpro/__init__.py

@ -1,8 +1,23 @@
from patacrep import encoding
from patacrep.songs import Song from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song
class ChordproSong(Song): class ChordproSong(Song):
pass """Chordpros song parser."""
def parse(self):
"""Parse content, and return the dictinory of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath)
print(self.data)
import sys; sys.exit(1)
self.titles = self.data['@titles']
del self.data['@titles']
self.languages = self.data['@languages']
del self.data['@languages']
self.authors = self.data['by']
del self.data['by']
SONG_PARSERS = { SONG_PARSERS = {
'sgc': ChordproSong, 'sgc': ChordproSong,

46
patacrep/songs/chordpro/lexer.py

@ -7,15 +7,16 @@ LOGGER = logging.getLogger()
#pylint: disable=invalid-name #pylint: disable=invalid-name
tokens = ( tokens = (
'LBRACKET', #'LBRACKET',
'RBRACKET', #'RBRACKET',
'CHORD',
'LBRACE', 'LBRACE',
'RBRACE', 'RBRACE',
'NEWLINE', 'NEWLINE',
'COLON', #'COLON',
'WORD', 'WORD',
'SPACE', 'SPACE',
'NUMBER' #'NUMBER',
) )
class ChordProLexer: class ChordProLexer:
@ -23,12 +24,15 @@ class ChordProLexer:
tokens = tokens tokens = tokens
t_LBRACKET = r'\[' states = (
t_RBRACKET = r'\]' ('chord', 'exclusive'),
)
t_LBRACE = r'{' t_LBRACE = r'{'
t_RBRACE = r'}' t_RBRACE = r'}'
t_SPACE = r'[ \t]+' t_SPACE = r'[ \t]+'
t_COLON = r':' #t_COLON = r':'
t_chord_CHORD = r'[A-G7#m]+' # TODO This can be refined
def __init__(self): def __init__(self):
self.__class__.lexer = lex.lex(module=self) self.__class__.lexer = lex.lex(module=self)
@ -41,23 +45,37 @@ class ChordProLexer:
return token return token
@staticmethod @staticmethod
def t_comment(token): def t_COMMENT(token):
r'\#.*' r'\#.*'
pass pass
@staticmethod @staticmethod
def t_WORD(token): def t_WORD(token):
r'[a-zA-Z_]+[.,;:!?]?' r'[^\n\][\t ]+'
return token return token
@staticmethod def t_LBRACKET(self, token):
def t_NUMBER(token): r'\['
r'[0-9]+' self.lexer.push_state('chord')
token.value = int(token.value)
return token def t_chord_RBRACKET(self, token):
r'\]'
self.lexer.pop_state()
#@staticmethod
#def t_NUMBER(token):
# r'[0-9]+'
# token.value = int(token.value)
# return token
@staticmethod @staticmethod
def t_error(token): def t_error(token):
"""Manage errors""" """Manage errors"""
LOGGER.error("Illegal character '{}'".format(token.value[0])) LOGGER.error("Illegal character '{}'".format(token.value[0]))
token.lexer.skip(1) token.lexer.skip(1)
@staticmethod
def t_chord_error(token):
"""Manage errors"""
LOGGER.error("Illegal character '{}' in chord..".format(token.value[0]))
token.lexer.skip(1)

88
patacrep/songs/chordpro/parser.py

@ -1,88 +0,0 @@
# -*- coding: utf-8 -*-
"""ChordPro parser"""
import logging
import ply.yacc as yacc
from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
from patacrep.songs.chordpro import ast
from patacrep.errors import SongbookError
LOGGER = logging.getLogger()
class ParsingError(SongbookError):
"""Parsing error."""
def __init__(self, message):
super().__init__(self)
self.message = message
def __str__(self):
return self.message
class Parser:
"""ChordPro parser class"""
def __init__(self, filename=None):
self.tokens = tokens
self.filename = filename
@staticmethod
def __find_column(token):
"""Return the column of ``token``."""
last_cr = token.lexer.lexdata.rfind('\n', 0, token.lexpos)
if last_cr < 0:
last_cr = 0
column = (token.lexpos - last_cr) + 1
return column
def p_error(self, token):
"""Manage parsing errors."""
LOGGER.error("Error in file {}, line {}:{}.".format(
str(self.filename),
token.lineno,
self.__find_column(token),
)
)
@staticmethod
def p_expression(symbols):
"""expression : brackets expression
| braces expression
| command expression
| NEWLINE expression
| word expression
| SPACE expression
| empty
"""
if len(symbols) == 3:
if symbols[2] is None:
symbols[0] = ast.Expression(symbols[1])
else:
symbols[0] = symbols[2].prepend(symbols[1])
else:
symbols[0] = None
@staticmethod
def p_empty(__symbols):
"""empty :"""
return None
@staticmethod
def p_brackets(symbols):
"""brackets : LBRACKET expression RBRACKET"""
symbols[0] = symbols[2]
@staticmethod
def p_braces(symbols):
"""braces : LBRACE expression COLON expression RBRACE"""
symbols[0] = symbols[2]
def parsesong(string, filename=None):
"""Parse song and return its metadata."""
return yacc.yacc(module=Parser(filename)).parse(
string,
lexer=ChordProLexer().lexer,
)

136
patacrep/songs/chordpro/syntax.py

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
"""ChordPro parser"""
import logging
import ply.yacc as yacc
from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
from patacrep.songs.chordpro import ast
from patacrep.errors import SongbookError
LOGGER = logging.getLogger()
class ParsingError(SongbookError):
"""Parsing error."""
def __init__(self, message):
super().__init__(self)
self.message = message
def __str__(self):
return self.message
class Parser:
"""ChordPro parser class"""
def __init__(self, filename=None):
self.tokens = tokens
self.filename = filename
@staticmethod
def __find_column(token):
"""Return the column of ``token``."""
last_cr = token.lexer.lexdata.rfind('\n', 0, token.lexpos)
if last_cr < 0:
last_cr = 0
column = (token.lexpos - last_cr) + 1
return column
def p_error(self, token):
"""Manage parsing errors."""
if token: # TODO remove this test
LOGGER.error("Error in file {}, line {}:{}.".format(
str(self.filename),
token.lineno,
self.__find_column(token),
)
)
@staticmethod
def p_song(symbols):
"""song : block song
| empty
"""
if len(symbols) == 2:
symbols[0] = ('song')
else:
symbols[0] = ('song', symbols[1], symbols[2])
@staticmethod
def p_block(symbols):
"""block : directive NEWLINE newlines
| stanza NEWLINE newlines
"""
symbols[0] = ('block', symbols[1])
@staticmethod
def p_newlines(symbols):
"""newlines : NEWLINE newlines
| empty"""
symbols[0] = ('newlines')
@staticmethod
def p_directive(symbols):
"""directive : LBRACE WORD RBRACE"""
symbols[0] = ('directive', symbols[1])
@staticmethod
def p_line(symbols):
"""line : WORD line_next
| CHORD line_next
| SPACE line_next
"""
symbols[0] = ('line', symbols[1], symbols[2])
@staticmethod
def p_line_next(symbols):
"""line_next : WORD line_next
| SPACE line_next
| CHORD line_next
| empty
"""
if len(symbols) == 2:
symbols[0] = ('line-next')
else:
symbols[0] = ('line-next', symbols[1], symbols[2])
@staticmethod
def p_stanza(symbols):
"""stanza : line NEWLINE stanza_next
"""
symbols[0] = ('stanza', symbols[1], symbols[3])
@staticmethod
def p_stanza_next(symbols):
"""stanza_next : line NEWLINE stanza_next
| empty
"""
if len(symbols) == 2:
symbols[0] = ('stanza-next')
else:
symbols[0] = ('stanza-next', symbols[1], symbols[3])
#@staticmethod
#def p_braces(symbols):
# """braces : LBRACE expression COLON expression RBRACE"""
# symbols[0] = symbols[2]
@staticmethod
def p_empty(symbols):
"""empty :"""
symbols[0] = None
#@staticmethod
#def p_comment(symbols):
# """comment : COMMENT"""
# symbols[0] = ('comment', symbols[1])
def parse_song(content, filename=None):
"""Parse song and return its metadata."""
return yacc.yacc(module=Parser(filename)).parse(
content,
lexer=ChordProLexer().lexer,
)
Loading…
Cancel
Save