# HG changeset patch # User Yuya Nishihara # Date 2016-02-29 08:54:03 # Node ID 6d6201fc5aaeff8fb25463551c2511f5b30f06eb # Parent 475dad3432fd5826bfa674192ea62ea75744bd34 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(). diff --git a/mercurial/parser.py b/mercurial/parser.py --- a/mercurial/parser.py +++ b/mercurial/parser.py @@ -264,3 +264,108 @@ class basealiasrules(object): 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")) diff --git a/mercurial/revset.py b/mercurial/revset.py --- a/mercurial/revset.py +++ b/mercurial/revset.py @@ -2237,75 +2237,18 @@ def _tokenizealias(program, lookup=None) 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 @@ def _parsealiasdefn(defn, args): 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 @@ class revsetalias(object): 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)