From 33328e789fcf5a5097db8f39bd64bf05b83bffbf Mon Sep 17 00:00:00 2001
From: Louis
Date: Mon, 12 Oct 2015 23:45:01 +0200
Subject: [PATCH] Directives are allowed only on their own line (with nothing
else on this line)
Better error handling of the parser.
---
.../data/examples/songs/tests/newline.sgc | 6 +-
patacrep/songs/chordpro/ast.py | 6 +-
patacrep/songs/chordpro/syntax.py | 61 +++++++++++++++----
test/test_chordpro/newline.html | 2 +-
test/test_chordpro/newline.sgc | 2 +-
test/test_chordpro/newline.source | 2 +-
test/test_chordpro/newline.tex | 2 +-
7 files changed, 62 insertions(+), 19 deletions(-)
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