diff --git a/patacrep/data/examples/songs/tests/newline.sgc b/patacrep/data/examples/songs/tests/newline.sgc index 4510d5ff..c14acb20 100644 --- a/patacrep/data/examples/songs/tests/newline.sgc +++ b/patacrep/data/examples/songs/tests/newline.sgc @@ -32,4 +32,8 @@ New lines can also {newline} Be surrounded by spaces -New lines can {newline} appear in the middle of a line +New lines cannot have text before them {newline} + +{newline} New lines cannot have text after them + +New lines cannot be {newline} surrounded by text. diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 80949e10..18805e9b 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -60,9 +60,9 @@ class Line(AST): _template = "line" - def __init__(self): + def __init__(self, *items): super().__init__() - self.line = [] + self.line = list(items) def __iter__(self): yield from self.line @@ -310,7 +310,7 @@ class Directive(AST): return self.keyword def __str__(self): - return self.argument + return str(self.argument) @property def inline(self): diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index c86e7ef4..e88d7360 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -1,5 +1,6 @@ """ChordPro parser""" +import logging import ply.yacc as yacc import re @@ -7,6 +8,8 @@ from patacrep.songs.syntax import Parser from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer +LOGGER = logging.getLogger() + class ChordproParser(Parser): """ChordPro parser class""" # pylint: disable=too-many-public-methods @@ -18,6 +21,19 @@ class ChordproParser(Parser): self.tokens = tokens self.song = ast.Song(filename) self.filename = filename + self.parser = yacc.yacc( + module=self, + debug=0, + write_tables=0, + ) + + def parse(self, *args, **kwargs): + """Parse file + + This is a shortcut to `yacc.yacc(...).parse()`. The arguments are + transmitted to this method. + """ + return self.parser.parse(*args, **kwargs) def p_song(self, symbols): """song : block song @@ -32,6 +48,7 @@ class ChordproParser(Parser): def p_block(symbols): """block : SPACE block | line ENDOFLINE + | line_error ENDOFLINE | chorus ENDOFLINE | tab ENDOFLINE | bridge ENDOFLINE @@ -167,20 +184,35 @@ class ChordproParser(Parser): else: symbols[0] = None + @staticmethod + def p_line_error(symbols): + """line_error : error directive""" + LOGGER.error("Directive can only be preceded or followed by spaces") + symbols[0] = ast.Line() + @staticmethod def p_line(symbols): """line : word line_next | chord line_next - | directive line_next + | directive maybespace """ - symbols[0] = symbols[2].prepend(symbols[1]) + if isinstance(symbols[2], ast.Line): + # Line with words, etc. + symbols[0] = symbols[2].prepend(symbols[1]) + else: + # Directive + if symbols[1] is None: + # Meta directive. Nothing to do + symbols[0] = ast.Line() + else: + # Inline directive + symbols[0] = ast.Line(symbols[1]) @staticmethod def p_line_next(symbols): """line_next : word line_next | space line_next | chord line_next - | directive line_next | empty """ if len(symbols) == 2: @@ -212,6 +244,7 @@ class ChordproParser(Parser): @staticmethod def p_chorus_content(symbols): """chorus_content : line ENDOFLINE chorus_content + | line_error ENDOFLINE chorus_content | SPACE chorus_content | empty """ @@ -231,6 +264,7 @@ class ChordproParser(Parser): @staticmethod def p_bridge_content(symbols): """bridge_content : line ENDOFLINE bridge_content + | line_error ENDOFLINE bridge_content | SPACE bridge_content | empty """ @@ -267,13 +301,18 @@ class ChordproParser(Parser): """empty :""" symbols[0] = None + def p_error(self, token): + super().p_error(token) + while True: + token = self.parser.token() + if not token or token.type == "ENDOFLINE": + break + self.parser.errok() + return token + def parse_song(content, filename=None): """Parse song and return its metadata.""" - return yacc.yacc( - module=ChordproParser(filename), - debug=0, - write_tables=0, - ).parse( - content, - lexer=ChordProLexer(filename=filename).lexer, - ) + return ChordproParser(filename).parse( + content, + lexer=ChordProLexer(filename=filename).lexer, + ) diff --git a/test/test_chordpro/newline.html b/test/test_chordpro/newline.html index 16cba958..1fbb02a3 100644 --- a/test/test_chordpro/newline.html +++ b/test/test_chordpro/newline.html @@ -45,6 +45,6 @@
- New lines can't appear in the middle of a line + New lines cannot
diff --git a/test/test_chordpro/newline.sgc b/test/test_chordpro/newline.sgc index 426c45f5..72bdc1a2 100644 --- a/test/test_chordpro/newline.sgc +++ b/test/test_chordpro/newline.sgc @@ -38,4 +38,4 @@ New lines can also Be surrounded by spaces -New lines can {newline} appear in the middle of a line +New lines cannot diff --git a/test/test_chordpro/newline.source b/test/test_chordpro/newline.source index 717639ee..03095b88 100644 --- a/test/test_chordpro/newline.source +++ b/test/test_chordpro/newline.source @@ -29,4 +29,4 @@ New lines can also {newline} Be surrounded by spaces -New lines can {newline} appear in the middle of a line +New lines cannot {newline} appear in the middle of a line diff --git a/test/test_chordpro/newline.tex b/test/test_chordpro/newline.tex index f8ab7d02..d557de5f 100644 --- a/test/test_chordpro/newline.tex +++ b/test/test_chordpro/newline.tex @@ -55,7 +55,7 @@ \begin{verse} - New lines can ~\\ appear in the middle of a line + New lines cannot \end{verse} \endsong