treat the escape char normally when not next to reference open
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 77c45e3..c76b721 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -7,7 +7,7 @@
 # Released under the terms of the Artistic Licence 2.0
 #
 from reclass.datatypes import Parameters
-from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS
+from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS, ESCAPE_CHARACTER
 from reclass.errors import InfiniteRecursionError
 from reclass.utils.mergeoptions import MergeOptions
 import unittest
@@ -340,5 +340,52 @@
         p1.interpolate()
         self.assertEqual(p1.as_dict(), r)
 
+    def test_interpolate_escaping(self):
+        v = 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS)
+        d = {'foo': ESCAPE_CHARACTER + 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+             'bar': 'unused'}
+        p = Parameters(d)
+        p.render_simple()
+        self.assertEqual(p.as_dict()['foo'], v)
+
+    def test_interpolate_double_escaping(self):
+        v = ESCAPE_CHARACTER + 'meep'
+        d = {'foo': ESCAPE_CHARACTER + ESCAPE_CHARACTER + 'bar'.join(PARAMETER_INTERPOLATION_SENTINELS),
+             'bar': 'meep'}
+        p = Parameters(d)
+        p.interpolate()
+        self.assertEqual(p.as_dict()['foo'], v)
+
+    def test_interpolate_escaping_backwards_compatibility(self):
+        """In all following cases, escaping should not happen and the escape character
+        needs to be printed as-is, to ensure backwards compatibility to older versions."""
+        v = ' '.join([
+            # Escape character followed by unescapable character
+            '1', ESCAPE_CHARACTER,
+            # Escape character followed by escape character
+            '2', ESCAPE_CHARACTER + ESCAPE_CHARACTER,
+            # Escape character followed by interpolation end sentinel
+            '3', ESCAPE_CHARACTER + PARAMETER_INTERPOLATION_SENTINELS[1],
+            # Escape character at the end of the string
+            '4', ESCAPE_CHARACTER
+            ])
+        d = {'foo': v}
+        p = Parameters(d)
+        p.render_simple()
+        self.assertEqual(p.as_dict()['foo'], v)
+
+    def test_escape_close_in_ref(self):
+        p1 = Parameters({'one}': 1, 'two': '${one\\}}'})
+        r = {'one}': 1, 'two': 1}
+        p1.interpolate()
+        self.assertEqual(p1.as_dict(), r)
+
+    def test_double_escape_in_ref(self):
+        d = {'one\\': 1, 'two': '${one\\\\}'}
+        p1 = Parameters(d)
+        r = {'one\\': 1, 'two': 1}
+        p1.interpolate()
+        self.assertEqual(p1.as_dict(), r)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/reclass/defaults.py b/reclass/defaults.py
index 13b8496..641aace 100644
--- a/reclass/defaults.py
+++ b/reclass/defaults.py
@@ -28,6 +28,7 @@
 PARAMETER_INTERPOLATION_SENTINELS = ('${', '}')
 PARAMETER_INTERPOLATION_DELIMITER = ':'
 PARAMETER_DICT_KEY_OVERRIDE_PREFIX = '~'
+ESCAPE_CHARACTER = '\\'
 
 MERGE_ALLOW_SCALAR_OVER_DICT = False
 MERGE_ALLOW_SCALAR_OVER_LIST = False
diff --git a/reclass/errors.py b/reclass/errors.py
index 5ce4d73..d6936d3 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -211,3 +211,17 @@
               "definition in '{3}'. Nodes can only be defined once " \
               "per inventory."
         return msg.format(self._storage, self._name, self._uris[1], self._uris[0])
+
+
+class ParseError(ReclassException):
+
+    def __init__(self, msg, line, col, lineno, rc=posix.EX_DATAERR):
+        super(ParseError, self).__init__(rc=rc, msg=None)
+        self._err = msg
+        self._line = line
+        self._col = col
+        self._lineno = lineno
+
+    def _get_message(self):
+        msg = "Parse error: {0} : {1} at char {2}"
+        return msg.format(self._line, self._err, self._col - 1)
diff --git a/reclass/utils/tests/test_value.py b/reclass/utils/tests/test_value.py
index 39cf062..5e11ef4 100644
--- a/reclass/utils/tests/test_value.py
+++ b/reclass/utils/tests/test_value.py
@@ -13,7 +13,7 @@
 from reclass.defaults import PARAMETER_INTERPOLATION_SENTINELS, \
         PARAMETER_INTERPOLATION_DELIMITER
 from reclass.errors import UndefinedVariableError, \
-        IncompleteInterpolationError
+        IncompleteInterpolationError, ParseError
 import unittest
 
 def _var(s):
@@ -122,7 +122,7 @@
 
     def test_incomplete_variable(self):
         s = PARAMETER_INTERPOLATION_SENTINELS[0] + 'incomplete'
-        with self.assertRaises(pp.ParseException):
+        with self.assertRaises(ParseError):
             tv = Value(s)
 
 if __name__ == '__main__':
diff --git a/reclass/utils/value.py b/reclass/utils/value.py
index b934b1e..7af16c1 100644
--- a/reclass/utils/value.py
+++ b/reclass/utils/value.py
@@ -12,10 +12,23 @@
 from reclass.utils.listitem import ListItem
 from reclass.utils.refitem import RefItem
 from reclass.utils.scaitem import ScaItem
-from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER, PARAMETER_INTERPOLATION_SENTINELS
+from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER, PARAMETER_INTERPOLATION_SENTINELS, ESCAPE_CHARACTER
+from reclass.errors import *
+
 
 _STR = 'STR'
 _REF = 'REF'
+_OPEN = PARAMETER_INTERPOLATION_SENTINELS[0]
+_CLOSE = PARAMETER_INTERPOLATION_SENTINELS[1]
+_CLOSE_FIRST = _CLOSE[0]
+_ESCAPE = ESCAPE_CHARACTER
+_DOUBLE_ESCAPE = _ESCAPE + _ESCAPE
+_ESCAPE_OPEN = _ESCAPE + _OPEN
+_ESCAPE_CLOSE = _ESCAPE + _CLOSE
+_DOUBLE_ESCAPE_OPEN = _DOUBLE_ESCAPE + _OPEN
+_DOUBLE_ESCAPE_CLOSE = _DOUBLE_ESCAPE + _CLOSE
+_EXCLUDES = _ESCAPE + _OPEN + _CLOSE
+
 
 class Value(object):
 
@@ -29,23 +42,25 @@
             token = list(tokens[0])
             tokens[0] = (_REF, token)
 
-        string = (pp.Literal('\\\\').setParseAction(pp.replaceWith('\\')) |
-                  pp.Literal('\\$').setParseAction(pp.replaceWith('$')) |
-                  pp.White() |
-                  pp.Word(pp.printables, excludeChars='\\$')).setParseAction(_string)
+        ref_open = pp.Literal(_OPEN).suppress()
+        ref_close = pp.Literal(_CLOSE).suppress()
+        not_open = ~pp.Literal(_OPEN) + ~pp.Literal(_ESCAPE_OPEN) + ~pp.Literal(_DOUBLE_ESCAPE_OPEN)
+        not_close = ~pp.Literal(_CLOSE) + ~pp.Literal(_ESCAPE_CLOSE) + ~pp.Literal(_DOUBLE_ESCAPE_CLOSE)
+        escape_open = pp.Literal(_ESCAPE_OPEN).setParseAction(pp.replaceWith(_OPEN))
+        escape_close = pp.Literal(_ESCAPE_CLOSE).setParseAction(pp.replaceWith(_CLOSE))
+        double_escape = pp.Combine(pp.Literal(_DOUBLE_ESCAPE) + pp.MatchFirst([pp.FollowedBy(_OPEN), pp.FollowedBy(_CLOSE)])).setParseAction(pp.replaceWith(_ESCAPE))
+        text = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXCLUDES), pp.CharsNotIn('', exact=1)])
+        text_ref = pp.MatchFirst([pp.Word(pp.printables, excludeChars=_EXCLUDES), pp.CharsNotIn(_CLOSE_FIRST, exact=1)])
+        white_space = pp.White()
 
-        refString = (pp.Literal('\\\\').setParseAction(pp.replaceWith('\\')) |
-                     pp.Literal('\\$').setParseAction(pp.replaceWith('$')) |
-                     pp.Literal('\\{').setParseAction(pp.replaceWith('{')) |
-                     pp.Literal('\\}').setParseAction(pp.replaceWith('}')) |
-                     pp.White() |
-                     pp.Word(pp.printables, excludeChars='\\${}')).setParseAction(_string)
+        content = pp.Combine(pp.OneOrMore(not_open + text))
+        ref_content = pp.Combine(pp.OneOrMore(not_open + not_close + text_ref))
+        string = pp.MatchFirst([double_escape, escape_open, content, white_space]).setParseAction(_string)
+        refString = pp.MatchFirst([double_escape, escape_open, escape_close, ref_content, white_space]).setParseAction(_string)
 
         refItem = pp.Forward()
         refItems = pp.OneOrMore(refItem)
-        reference = (pp.Literal(PARAMETER_INTERPOLATION_SENTINELS[0]).suppress() +
-                     pp.Group(refItems) +
-                     pp.Literal(PARAMETER_INTERPOLATION_SENTINELS[1]).suppress()).setParseAction(_reference)
+        reference = (ref_open + pp.Group(refItems) + ref_close).setParseAction(_reference)
         refItem << (reference | refString)
 
         item = reference | string
@@ -60,7 +75,10 @@
         self._allRefs = False
         self._container = False
         if isinstance(val, str):
-            tokens = Value._parser.leaveWhitespace().parseString(val).asList()
+            try:
+                tokens = Value._parser.leaveWhitespace().parseString(val).asList()
+            except pp.ParseException as e:
+                raise ParseError(e.msg, e.line, e.col, e.lineno)
             items = self._createItems(tokens)
             if len(items) is 1:
                 self._item = items[0]