rework parser to use parse actions instead of having to reparse xml
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index a39324e..01c29bb 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -6,6 +6,7 @@
 # Copyright © 2007–14 martin f. krafft <madduck@madduck.net>
 # Released under the terms of the Artistic Licence 2.0
 #
+import sys
 import types
 from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER,\
                              PARAMETER_DICT_KEY_OVERRIDE_PREFIX
@@ -45,6 +46,7 @@
         self._delimiter = delimiter
         self._base = {}
         self._occurrences = {}
+        self._escapes_handled = {}
         if mapping is not None:
             # we initialise by merging, otherwise the list of references might
             # not be updated
@@ -92,14 +94,9 @@
             ret = new
 
         else:
-            # the new value is a string, let's see if it contains references,
-            # by way of wrapping it in a RefValue and querying the result
+            # the new value is a string, but still wrap it in a ref value to
+            # allow character escaping to handled
             ret = RefValue(new, self.delimiter)
-            if not ret.has_references():
-                # do not replace with RefValue instance if there are no
-                # references, i.e. discard the RefValue in ret, just return
-                # the new value
-                return new
 
         # So we now have a RefValue. Let's, keep a reference to the instance
         # we just created, in a dict indexed by the dictionary path, instead
@@ -265,12 +262,19 @@
                 # dependencies of the current ref, so move on
                 continue
 
-        try:
-            new = refvalue.render(self._base)
-            path.set_value(self._base, new)
+        if refvalue.assembledAllRefs():
+            try:
+                new = refvalue.render(self._base)
+                path.set_value(self._base, new)
 
-            # finally, remove the reference from the occurrences cache
-            del self._occurrences[path]
-        except UndefinedVariableError as e:
-            raise UndefinedVariableError(e.var, path)
-
+                # finally, remove the reference from the occurrences cache
+                del self._occurrences[path]
+            except UndefinedVariableError as e:
+                raise UndefinedVariableError(e.var, path)
+        else:
+            old_ref_count = len(refvalue.get_references())
+            refvalue.assembleRefs(self._base)
+            if old_ref_count != len(refvalue.get_references()):
+                self._interpolate_inner(path, refvalue)
+            else:
+                raise InterpolationError('Bad reference count, path:' + path)
diff --git a/reclass/utils/refvalue.py b/reclass/utils/refvalue.py
index e02f195..1582d96 100644
--- a/reclass/utils/refvalue.py
+++ b/reclass/utils/refvalue.py
@@ -64,53 +64,79 @@
         self._delim = delim
         self._tokens = []
         self._refs = []
+        self._allRefs = False
         self._parse(string)
 
-    def _parse(self, string):
-        # order of checking is important here, the nested ${} check then the regex
-        scanner = pp.ZeroOrMore(pp.MatchFirst([
-                                pp.nestedExpr(opener=PARAMETER_INTERPOLATION_SENTINELS[0], closer=PARAMETER_INTERPOLATION_SENTINELS[1]).setResultsName(_REF),
-                                pp.Regex(_RE).setResultsName(_STR, listAllMatches=True)
-                  ]))
-        result = scanner.leaveWhitespace().parseString(string)
-        xml = etree.fromstring(result.asXML())
-        self._parseXML(xml)
-        self._assembleRefs(self._tokens)
+    def _getParser():
 
-    def _parseXML(self, elements):
-        self._tokens = []
-        for el in elements:
-            if (el.tag == _STR):
-                self._tokens.append((_STR, el.text))
-            elif (el.tag == _REF):
-                self._tokens.append((_REF, self._parseRefXML(el)))
-            else:
-                self._tokens.append(('???', '???'))
+        def _push(description, string, location, tokens):
+            RefValue._stack.append((description, tokens))
 
-    def _parseRefXML(self, elements):
+        def _string(string, location, tokens):
+            _push(_STR, string, location, tokens)
+
+        def _reference(string, location, tokens):
+            _push(_REF, string, location, tokens)
+
+        string = (pp.Literal('\\\\').setParseAction(pp.replaceWith('\\')) |
+                  pp.Literal('\\$').setParseAction(pp.replaceWith('$')) |
+                  pp.White() |
+                  pp.Word(pp.printables, excludeChars='\\$')).setParseAction(_string)
+
+        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)
+
+        refItem = pp.Forward()
+        refItems = pp.OneOrMore(refItem)
+        reference = (pp.Literal('${').suppress() + pp.Group(refItems) + pp.Literal('}').suppress()).setParseAction(_reference)
+        refItem << (reference | refString)
+
+        item = reference | string
+        line = pp.OneOrMore(item) + pp.StringEnd()
+        return line
+
+    _stack = []
+    _parser = _getParser()
+
+    def _tokenise(self, items, stack):
         result = []
-        for el in elements:
-            if (len(el) == 0):
-                result.append((_STR, el.text))
+        for n, i in reversed(list(enumerate(items))):
+            t = stack.pop()[0]
+            if (t == _REF):
+               result.insert(0, (_REF, self._tokenise(i, stack) ))
             else:
-                result.append((_REF, self._parseRefXML(el)))
+               result.insert(0, (_STR, i))
         return result
 
-    def _assembleRefs(self, tokens, first=True):
-        string = ''
-        retNone = False
+    def _parse(self, string):
+        del RefValue._stack[:]
+        result = RefValue._parser.leaveWhitespace().parseString(string)
+        self._tokens = self._tokenise(result, RefValue._stack)
+        self.assembleRefs()
+
+    def _assembleRefs(self, tokens, resolver, first=True):
         for token in tokens:
-            if token[0] == _STR:
-                string += token[1]
-            elif token[0] == _REF:
-                s = self._assembleRefs(token[1], first=False)
-                if s != None:
+            if token[0] == _REF:
+                self._assembleRefs(token[1], resolver, False)
+                try:
+                    s = self._assemble(token[1], resolver)
                     self._refs.append(s)
-                if not first:
-                   retNone = True
-        if retNone:
-            string = None
-        return string
+                except UndefinedVariableError as e:
+                    self._allRefs = False
+                    pass
+
+    def assembleRefs(self, context={}):
+        resolver = lambda s: self._resolve(s, context)
+        self._refs = []
+        self._allRefs = True
+        self._assembleRefs(self._tokens, resolver, True)
+
+    def assembledAllRefs(self):
+        return self._allRefs
 
     def _resolve(self, ref, context):
         path = DictPath(self._delim, ref)