parser: move alias declaration parser to common rule-set class
The original _parsealiasdecl() function is split into common _builddecl()
and revset-specific _parsealiasdecl(). And the original _parsealiasdecl()
call is temporarily replaced by rules._builddecl(), which should be eliminated
later.
The doctests are mostly ported by using the dummy parse(), but the test for
'foo bar' is kept in _parsealiasdecl() as it checks if "pos != len(decl)" is
working. Also, 'foo($1)' test is added to make sure the alias tokenizer can
handle '$1' symbol, which is the only reason why we need _parsealiasdecl().
--- a/mercurial/parser.py Sun Apr 03 16:55:23 2016 +0900
+++ b/mercurial/parser.py Mon Feb 29 17:54:03 2016 +0900
@@ -264,3 +264,108 @@
def _getlist(tree):
"""Extract a list of arguments from parsed tree"""
raise NotImplementedError
+
+ @classmethod
+ def _builddecl(cls, decl):
+ """Parse an alias declaration into ``(name, tree, args, errorstr)``
+
+ This function analyzes the parsed tree. The parsing rule is provided
+ by ``_parsedecl()``.
+
+ - ``name``: of declared alias (may be ``decl`` itself at error)
+ - ``tree``: parse result (or ``None`` at error)
+ - ``args``: list of argument names (or None for symbol declaration)
+ - ``errorstr``: detail about detected error (or None)
+
+ >>> sym = lambda x: ('symbol', x)
+ >>> symlist = lambda *xs: ('list',) + tuple(sym(x) for x in xs)
+ >>> func = lambda n, a: ('func', sym(n), a)
+ >>> parsemap = {
+ ... 'foo': sym('foo'),
+ ... '$foo': sym('$foo'),
+ ... 'foo::bar': ('dagrange', sym('foo'), sym('bar')),
+ ... 'foo()': func('foo', None),
+ ... '$foo()': func('$foo', None),
+ ... 'foo($1, $2)': func('foo', symlist('$1', '$2')),
+ ... 'foo(bar_bar, baz.baz)':
+ ... func('foo', symlist('bar_bar', 'baz.baz')),
+ ... 'foo(bar($1, $2))':
+ ... func('foo', func('bar', symlist('$1', '$2'))),
+ ... 'foo($1, $2, nested($1, $2))':
+ ... func('foo', (symlist('$1', '$2') +
+ ... (func('nested', symlist('$1', '$2')),))),
+ ... 'foo("bar")': func('foo', ('string', 'bar')),
+ ... 'foo($1, $2': error.ParseError('unexpected token: end', 10),
+ ... 'foo("bar': error.ParseError('unterminated string', 5),
+ ... 'foo($1, $2, $1)': func('foo', symlist('$1', '$2', '$1')),
+ ... }
+ >>> def parse(expr):
+ ... x = parsemap[expr]
+ ... if isinstance(x, Exception):
+ ... raise x
+ ... return x
+ >>> def getlist(tree):
+ ... if not tree:
+ ... return []
+ ... if tree[0] == 'list':
+ ... return list(tree[1:])
+ ... return [tree]
+ >>> class aliasrules(basealiasrules):
+ ... _parsedecl = staticmethod(parse)
+ ... _getlist = staticmethod(getlist)
+ >>> builddecl = aliasrules._builddecl
+ >>> builddecl('foo')
+ ('foo', ('symbol', 'foo'), None, None)
+ >>> builddecl('$foo')
+ ('$foo', None, None, "'$' not for alias arguments")
+ >>> builddecl('foo::bar')
+ ('foo::bar', None, None, 'invalid format')
+ >>> builddecl('foo()')
+ ('foo', ('func', ('symbol', 'foo')), [], None)
+ >>> builddecl('$foo()')
+ ('$foo()', None, None, "'$' not for alias arguments")
+ >>> builddecl('foo($1, $2)')
+ ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
+ >>> builddecl('foo(bar_bar, baz.baz)')
+ ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
+ >>> builddecl('foo($1, $2, nested($1, $2))')
+ ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
+ >>> builddecl('foo(bar($1, $2))')
+ ('foo(bar($1, $2))', None, None, 'invalid argument list')
+ >>> builddecl('foo("bar")')
+ ('foo("bar")', None, None, 'invalid argument list')
+ >>> builddecl('foo($1, $2')
+ ('foo($1, $2', None, None, 'at 10: unexpected token: end')
+ >>> builddecl('foo("bar')
+ ('foo("bar', None, None, 'at 5: unterminated string')
+ >>> builddecl('foo($1, $2, $1)')
+ ('foo', None, None, 'argument names collide with each other')
+ """
+ try:
+ tree = cls._parsedecl(decl)
+ except error.ParseError as inst:
+ return (decl, None, None, parseerrordetail(inst))
+
+ if tree[0] == cls._symbolnode:
+ # "name = ...." style
+ name = tree[1]
+ if name.startswith('$'):
+ return (decl, None, None, _("'$' not for alias arguments"))
+ return (name, tree, None, None)
+
+ if tree[0] == cls._funcnode and tree[1][0] == cls._symbolnode:
+ # "name(arg, ....) = ...." style
+ name = tree[1][1]
+ if name.startswith('$'):
+ return (decl, None, None, _("'$' not for alias arguments"))
+ args = []
+ for arg in cls._getlist(tree[2]):
+ if arg[0] != cls._symbolnode:
+ return (decl, None, None, _("invalid argument list"))
+ args.append(arg[1])
+ if len(args) != len(set(args)):
+ return (name, None, None,
+ _("argument names collide with each other"))
+ return (name, tree[:2], args, None)
+
+ return (decl, None, None, _("invalid format"))
--- a/mercurial/revset.py Sun Apr 03 16:55:23 2016 +0900
+++ b/mercurial/revset.py Mon Feb 29 17:54:03 2016 +0900
@@ -2237,75 +2237,18 @@
def _parsealiasdecl(decl):
"""Parse alias declaration ``decl``
- This returns ``(name, tree, args, errorstr)`` tuple:
-
- - ``name``: of declared alias (may be ``decl`` itself at error)
- - ``tree``: parse result (or ``None`` at error)
- - ``args``: list of alias argument names (or None for symbol declaration)
- - ``errorstr``: detail about detected error (or None)
-
- >>> _parsealiasdecl('foo')
- ('foo', ('symbol', 'foo'), None, None)
- >>> _parsealiasdecl('$foo')
- ('$foo', None, None, "'$' not for alias arguments")
- >>> _parsealiasdecl('foo::bar')
- ('foo::bar', None, None, 'invalid format')
+ >>> _parsealiasdecl('foo($1)')
+ ('func', ('symbol', 'foo'), ('symbol', '$1'))
>>> _parsealiasdecl('foo bar')
- ('foo bar', None, None, 'at 4: invalid token')
- >>> _parsealiasdecl('foo()')
- ('foo', ('func', ('symbol', 'foo')), [], None)
- >>> _parsealiasdecl('$foo()')
- ('$foo()', None, None, "'$' not for alias arguments")
- >>> _parsealiasdecl('foo($1, $2)')
- ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
- >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
- ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
- >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
- ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
- >>> _parsealiasdecl('foo(bar($1, $2))')
- ('foo(bar($1, $2))', None, None, 'invalid argument list')
- >>> _parsealiasdecl('foo("string")')
- ('foo("string")', None, None, 'invalid argument list')
- >>> _parsealiasdecl('foo($1, $2')
- ('foo($1, $2', None, None, 'at 10: unexpected token: end')
- >>> _parsealiasdecl('foo("string')
- ('foo("string', None, None, 'at 5: unterminated string')
- >>> _parsealiasdecl('foo($1, $2, $1)')
- ('foo', None, None, 'argument names collide with each other')
+ Traceback (most recent call last):
+ ...
+ ParseError: ('invalid token', 4)
"""
p = parser.parser(elements)
- try:
- tree, pos = p.parse(_tokenizealias(decl))
- if (pos != len(decl)):
- raise error.ParseError(_('invalid token'), pos)
- tree = parser.simplifyinfixops(tree, ('list',))
- except error.ParseError as inst:
- return (decl, None, None, parser.parseerrordetail(inst))
-
- if True: # XXX to be removed
- if tree[0] == 'symbol':
- # "name = ...." style
- name = tree[1]
- if name.startswith('$'):
- return (decl, None, None, _("'$' not for alias arguments"))
- return (name, tree, None, None)
-
- if tree[0] == 'func' and tree[1][0] == 'symbol':
- # "name(arg, ....) = ...." style
- name = tree[1][1]
- if name.startswith('$'):
- return (decl, None, None, _("'$' not for alias arguments"))
- args = []
- for arg in getlist(tree[2]):
- if arg[0] != 'symbol':
- return (decl, None, None, _("invalid argument list"))
- args.append(arg[1])
- if len(args) != len(set(args)):
- return (name, None, None,
- _("argument names collide with each other"))
- return (name, tree[:2], args, None)
-
- return (decl, None, None, _("invalid format"))
+ tree, pos = p.parse(_tokenizealias(decl))
+ if pos != len(decl):
+ raise error.ParseError(_('invalid token'), pos)
+ return parser.simplifyinfixops(tree, ('list',))
def _relabelaliasargs(tree, args):
if not isinstance(tree, tuple):
@@ -2369,6 +2312,7 @@
class _aliasrules(parser.basealiasrules):
"""Parsing and expansion rule set of revset aliases"""
_section = _('revset alias')
+ _parsedecl = staticmethod(_parsealiasdecl)
_getlist = staticmethod(getlist)
class revsetalias(object):
@@ -2382,7 +2326,8 @@
h = heads(default)
b($1) = ancestors($1) - ancestors(default)
'''
- self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
+ r = _aliasrules._builddecl(name)
+ self.name, self.tree, self.args, self.error = r
if self.error:
self.error = _('failed to parse the declaration of revset alias'
' "%s": %s') % (self.name, self.error)