Browse Source

Directive {newline} is correctly parsed and rendered

* Moreover, error messages also mention file name
* Directives can now appear inside text (and not only on their own line)
pull/112/head
Louis 9 years ago
parent
commit
39b61959f8
  1. 2
      patacrep/data/examples/songs/errors.sgc
  2. 35
      patacrep/data/examples/songs/tests/newline.sgc
  3. 43
      patacrep/songs/chordpro/ast.py
  4. 6
      patacrep/songs/chordpro/data/latex/content_verse
  5. 19
      patacrep/songs/chordpro/lexer.py
  6. 10
      patacrep/songs/chordpro/syntax.py
  7. 8
      patacrep/songs/syntax.py
  8. 1
      test/test_chordpro/greensleeves.sgc
  9. 1
      test/test_chordpro/greensleeves.tex
  10. 1
      test/test_chordpro/metadata.sgc
  11. 3
      test/test_chordpro/metadata.tex

2
patacrep/data/examples/songs/errors.sgc

@ -10,4 +10,6 @@
Bla []bla
Bla [H]bla
{fo#: bar}

35
patacrep/data/examples/songs/tests/newline.sgc

@ -0,0 +1,35 @@
{title: Newline}
{subtitle: Test of "newline" directive}
This is a verse
With a new line
{newline}
The second part of the verse
Is this line
Here is a new line at the end
{newline}
Foo bar
{newline}
And a new line
At the beginning
{soc}
New lines can also
{newline}
Be in chorus
{eoc}
{sob}
New lines can also
{newline}
Be in bridges
{eob}
New lines can also
{newline}
Be surrounded by spaces
New lines can {newline} appear in the middle of a line

43
patacrep/songs/chordpro/ast.py

@ -13,11 +13,12 @@ def _indent(string):
#: List of properties that are to be displayed in the flow of the song (not as
#: metadata at the beginning or end of song.
INLINE_PROPERTIES = {
INLINE_DIRECTIVES = {
"partition",
"comment",
"guitar_comment",
"image",
"newline",
}
#: Some directive have alternative names. For instance `{title: Foo}` and `{t:
@ -63,6 +64,9 @@ class Line(AST):
super().__init__()
self.line = []
def __iter__(self):
yield from self.line
def prepend(self, data):
"""Add an object at the beginning of line.
@ -86,6 +90,7 @@ class Line(AST):
return self
def is_empty(self):
"""Return `True` iff line is empty."""
return len(self.strip().line) == 0
class LineElement(AST):
@ -144,6 +149,14 @@ class Verse(AST):
self.lines.insert(0, data)
return self
def directive(self):
"""Return `True` iff the verse is composed only of directives."""
for line in self.lines:
for element in line:
if not isinstance(element, Directive):
return False
return True
class Chorus(Verse):
"""Chorus"""
type = 'chorus'
@ -191,7 +204,7 @@ class Song(AST):
# New line
if not (self.content and isinstance(self.content[0], EndOfLine)):
self.content.insert(0, EndOfLine())
elif isinstance(data, Line) or isinstance(data, NewLine):
elif isinstance(data, Line):
# Add a new line, maybe in the current verse.
if not data.is_empty():
if not (self.content and isinstance(self.content[0], Verse)):
@ -273,7 +286,7 @@ class EndOfLine(AST):
class Directive(AST):
"""A directive"""
meta = True
inline = False
def __init__(self, keyword, argument=None):
super().__init__()
@ -288,29 +301,23 @@ class Directive(AST):
"""
return self.keyword
@property
def inline(self):
"""True iff this directive is to be rendered in the flow on the song.
"""
return self.keyword in INLINE_PROPERTIES
def __str__(self):
return self.argument
@classmethod
def create(cls, keyword, argument=None):
if keyword == "newline":
return NewLine(keyword, argument)
"""Create a :class:`Directive` or :class:`InlineDirective`.
Depending on the keyword.
"""
if keyword in INLINE_DIRECTIVES:
return InlineDirective(keyword, argument)
else:
return cls(keyword, argument)
class NewLine(Directive):
keyword = "newline"
_template = "newline"
meta = False
def strip(self):
return self
class InlineDirective(Directive):
"""Directive displayed in the flow of the song"""
inline = True
class Define(Directive):
"""A chord definition.

6
patacrep/songs/chordpro/data/latex/content_verse

@ -1,5 +1,11 @@
(* if content.directive() *)
(* for line in content.lines -*)
((- render(line) ))
(* endfor -*)
(*- else -*)
\begin{(( content.type ))}
(* for line in content.lines *)
(( render(line) ))
(* endfor *)
\end{(( content.type ))}
(*- endif *)

19
patacrep/songs/chordpro/lexer.py

@ -83,8 +83,9 @@ class ChordProLexer:
t_tablature_TEXT = r'[^\n]+'
t_tablature_ENDOFLINE = r'\n'
def __init__(self):
def __init__(self, *, filename=None):
self.__class__.lexer = lex.lex(module=self)
self.filename = filename
# Define a rule so we can track line numbers
@staticmethod
@ -132,16 +133,16 @@ class ChordProLexer:
self.lexer.push_state('directiveargument')
return token
@staticmethod
def error(token, more=""):
def error(self, token, more=""):
"""Display error message, and skip illegal token."""
LOGGER.error(
"Line {line}: Illegal character '{char}'{more}.".format(
line=token.lexer.lineno,
char=token.value[0],
more=more,
)
message = "Line {line}: Illegal character '{char}'{more}.".format(
line=token.lexer.lineno,
char=token.value[0],
more=more,
)
if self.filename is not None:
message = "File {}: {}".format(self.filename, message)
LOGGER.error(message)
token.lexer.skip(1)
def t_error(self, token):

10
patacrep/songs/chordpro/syntax.py

@ -17,6 +17,7 @@ class ChordproParser(Parser):
super().__init__()
self.tokens = tokens
self.song = ast.Song(filename)
self.filename = filename
def p_song(self, symbols):
"""song : block song
@ -144,10 +145,11 @@ class ChordproParser(Parser):
else:
directive = ast.Directive.create(keyword, argument)
if directive.meta:
self.song.add(directive)
else:
if directive.inline:
symbols[0] = directive
else:
self.song.add(directive)
@staticmethod
def p_directive_next(symbols):
@ -273,5 +275,5 @@ def parse_song(content, filename=None):
write_tables=0,
).parse(
content,
lexer=ChordProLexer().lexer,
lexer=ChordProLexer(filename=filename).lexer,
)

8
patacrep/songs/syntax.py

@ -20,8 +20,7 @@ class Parser:
column = (token.lexpos - last_cr) + 1
return column
@staticmethod
def error(*, line=None, column=None, message=""):
def error(self, *, line=None, column=None, message=""):
"""Display an error message"""
coordinates = []
if line is not None:
@ -35,7 +34,10 @@ class Parser:
text += message
else:
text += "."
LOGGER.error(text)
if self.filename is None:
LOGGER.error(text)
else:
LOGGER.error("File {}: {}".format(self.filename, text))
def p_error(self, token):
"""Manage parsing errors."""

1
test/test_chordpro/greensleeves.sgc

@ -10,6 +10,7 @@
{partition: greensleeves.ly}
A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously
And [Am]I have loved [G]you so long

1
test/test_chordpro/greensleeves.tex

@ -17,6 +17,7 @@ Un sous titre}[
\lilypond{greensleeves.ly}
\begin{verse}
A\[Am]las, my love, ye \[G]do me wrong
To \[Am]cast me oft dis\[E]curteously

1
test/test_chordpro/metadata.sgc

@ -18,4 +18,5 @@
{partition: Lilypond}
{image: Image}
Foo

3
test/test_chordpro/metadata.tex

@ -22,7 +22,10 @@ Subtitle5}[
\lilypond{Lilypond}
\image{Image}
\begin{verse}
Foo
\end{verse}
\endsong

Loading…
Cancel
Save