diff mercurial/parser.py @ 28895:4bf9ed7a260e

parser: move functions that process alias expansion to rule-set class They will be commonly used by revset and templater. It isn't easy to understand how _expand() works, so I'll add comments by a follow-up patch. The local variable 'alias' is renamed to 'a' to avoid shadowing the global 'alias' class.
author Yuya Nishihara <yuya@tcha.org>
date Mon, 29 Feb 2016 22:15:44 +0900
parents ee11167fe1da
children 4c76a032ec7e
line wrap: on
line diff
--- a/mercurial/parser.py	Mon Feb 29 22:10:48 2016 +0900
+++ b/mercurial/parser.py	Mon Feb 29 22:15:44 2016 +0900
@@ -472,3 +472,73 @@
             a = cls.build(decl, defn)
             aliases[a.name] = a
         return aliases
+
+    @classmethod
+    def _getalias(cls, aliases, tree):
+        """If tree looks like an unexpanded alias, return it. Return None
+        otherwise.
+        """
+        if not isinstance(tree, tuple):
+            return None
+        if tree[0] == cls._symbolnode:
+            name = tree[1]
+            a = aliases.get(name)
+            if a and a.args is None and a.tree == tree:
+                return a
+        if tree[0] == cls._funcnode and tree[1][0] == cls._symbolnode:
+            name = tree[1][1]
+            a = aliases.get(name)
+            if a and a.args is not None and a.tree == tree[:2]:
+                return a
+        return None
+
+    @classmethod
+    def _expandargs(cls, tree, args):
+        """Replace _aliasarg instances with the substitution value of the
+        same name in args, recursively.
+        """
+        if not isinstance(tree, tuple):
+            return tree
+        if tree[0] == '_aliasarg':
+            sym = tree[1]
+            return args[sym]
+        return tuple(cls._expandargs(t, args) for t in tree)
+
+    @classmethod
+    def _expand(cls, aliases, tree, expanding, cache):
+        if not isinstance(tree, tuple):
+            return tree
+        a = cls._getalias(aliases, tree)
+        if a is not None:
+            if a.error:
+                raise error.Abort(a.error)
+            if a in expanding:
+                raise error.ParseError(_('infinite expansion of %(section)s '
+                                         '"%(name)s" detected')
+                                       % {'section': cls._section,
+                                          'name': a.name})
+            expanding.append(a)
+            if a.name not in cache:
+                cache[a.name] = cls._expand(aliases, a.replacement, expanding,
+                                            cache)
+            result = cache[a.name]
+            expanding.pop()
+            if a.args is not None:
+                l = cls._getlist(tree[2])
+                if len(l) != len(a.args):
+                    raise error.ParseError(_('invalid number of arguments: %d')
+                                           % len(l))
+                l = [cls._expand(aliases, t, [], cache) for t in l]
+                result = cls._expandargs(result, dict(zip(a.args, l)))
+        else:
+            result = tuple(cls._expand(aliases, t, expanding, cache)
+                           for t in tree)
+        return result
+
+    @classmethod
+    def expand(cls, aliases, tree):
+        """Expand aliases in tree, recursively.
+
+        'aliases' is a dictionary mapping user defined aliases to alias objects.
+        """
+        return cls._expand(aliases, tree, [], {})