escape sequences now use $ instead of doubling

This was causing a problem with situation where }} would have semantic meaning
other than escaping a }. Specifically, %func{%func{arg}} contains a }} but
should not escape the }. $} seems to cover this situation. However, ${ is not
permitted as an escape sequence because it looks like the beginning of a symbol
(variable reference) like ${foo}. This is OK because { can be used anywhere as a
literal.
This commit is contained in:
Adrian Sampson 2011-12-15 00:11:57 -08:00
parent 829bd14993
commit ae2f0db540
2 changed files with 16 additions and 13 deletions

View file

@ -32,6 +32,7 @@ FUNC_DELIM = u'%'
GROUP_OPEN = u'{'
GROUP_CLOSE = u'}'
ARG_SEP = u','
ESCAPE_CHAR = u'$'
class Environment(object):
"""Contains the values and functions to be substituted into a
@ -139,7 +140,7 @@ class Parser(object):
char = self.string[self.pos]
if char not in (SYMBOL_DELIM, FUNC_DELIM, GROUP_OPEN,
GROUP_CLOSE, ARG_SEP):
GROUP_CLOSE, ARG_SEP, ESCAPE_CHAR):
# A non-special character.
# TODO: This can be made more efficient by repeatedly asking
# for the next special character rather than walking through
@ -158,9 +159,13 @@ class Parser(object):
break
next_char = self.string[self.pos + 1]
if char == next_char:
# An escaped special character ($$, etc.).
text_parts.append(char)
if char == ESCAPE_CHAR and next_char in \
(SYMBOL_DELIM, FUNC_DELIM, GROUP_CLOSE, ARG_SEP):
# An escaped special character ($$, $}, etc.). Note that
# ${ is not an escape sequence: this is ambiguous with
# the start of a symbol and it's not necessary (just
# using { suffices in all cases).
text_parts.append(next_char)
self.pos += 2 # Skip the next character.
continue

View file

@ -86,16 +86,13 @@ class ParseTest(unittest.TestCase):
self.assertEqual(list(_normparse(u'hello $$')), [u'hello $'])
def test_escaped_function_delim(self):
self.assertEqual(list(_normparse(u'a %% b')), [u'a % b'])
self.assertEqual(list(_normparse(u'a $% b')), [u'a % b'])
def test_escaped_sep(self):
self.assertEqual(list(_normparse(u'a ,, b')), [u'a , b'])
def test_escaped_open_brace(self):
self.assertEqual(list(_normparse(u'a {{ b')), [u'a { b'])
self.assertEqual(list(_normparse(u'a $, b')), [u'a , b'])
def test_escaped_close_brace(self):
self.assertEqual(list(_normparse(u'a }} b')), [u'a } b'])
self.assertEqual(list(_normparse(u'a $} b')), [u'a } b'])
def test_bare_value_delim_kept_intact(self):
self.assertEqual(list(_normparse(u'a $ b')), [u'a $ b'])
@ -169,13 +166,13 @@ class ParseTest(unittest.TestCase):
self.assertEqual(list(_normexpr(parts[0].args[1])), [u'baz'])
def test_call_with_escaped_sep(self):
parts = list(_normparse(u'%foo{bar,,baz}'))
parts = list(_normparse(u'%foo{bar$,baz}'))
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 1)
self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar,baz'])
def test_call_with_escaped_close(self):
parts = list(_normparse(u'%foo{bar}}baz}'))
parts = list(_normparse(u'%foo{bar$}baz}'))
self.assertEqual(len(parts), 1)
self._assert_call(parts[0], u"foo", 1)
self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar}baz'])
@ -204,7 +201,8 @@ class ParseTest(unittest.TestCase):
self._assert_call(parts[0], u"foo", 1)
arg_parts = list(_normexpr(parts[0].args[0]))
self.assertEqual(len(arg_parts), 1)
self._assert_call(arg_parts[0], u"bar", 0)
self._assert_call(arg_parts[0], u"bar", 1)
self.assertEqual(list(_normexpr(arg_parts[0].args[0])), [u'baz'])
class EvalTest(unittest.TestCase):
def _eval(self, template):