[PATCH] revset: add experimental set subscript operator

Jun Wu quark at fb.com
Fri Jun 30 20:08:31 EDT 2017


Excerpts from Yuya Nishihara's message of 2017-06-30 23:58:43 +0900:
> # HG changeset patch
> # User Yuya Nishihara <yuya at tcha.org>
> # Date 1498302078 -32400
> #      Sat Jun 24 20:01:18 2017 +0900
> # Node ID b7f6740b0e58c468ecc82296fe1ee0800a7e0582
> # Parent  fe3419b41a456661a3d35ae963d06a203d4446a2
> revset: add experimental set subscript operator
> 
> It only supports integer indices as a beginning. See the following post
> for further ideas.
> 
> https://www.mercurial-scm.org/wiki/RevsetOperatorPlan#ideas_from_mpm 

Glad to see progress on new operators! The implementation looks good to me.

Maybe this is unnecessary bike-shedding, but I feel "[]" is more natural for
"indexing" purpose.  "{}" is used in templates which might be confusing.

Templates may seem unrelated, but I think it could be embedded in revsets in
the future.  That enables more flexible queries, like "commits whose author
is the same as (antoher revset)'s author".  Template strings need some
function to "bind" to a ctx, like: author(template(REV, "{author}")).

> I'm not sure if the current semantics are the most useful and extendable.
> We might want the way to select the nth ancestors/descendants in sub graph,
> by e.g. 'follow(filename){-2}', which is completely different so the '{n}'
> operator wouldn't be usable.
> 
> diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
> --- a/mercurial/revsetlang.py
> +++ b/mercurial/revsetlang.py
> @@ -21,6 +21,7 @@ from . import (
>  elements = {
>      # token-type: binding-strength, primary, prefix, infix, suffix
>      "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
> +    "{": (21, None, None, ("subscript", 1, "}"), None),
>      "##": (20, None, None, ("_concat", 20), None),
>      "~": (18, None, None, ("ancestor", 18), None),
>      "^": (18, None, None, ("parent", 18), "parentpost"),
> @@ -39,6 +40,7 @@ elements = {
>      "=": (3, None, None, ("keyvalue", 3), None),
>      ",": (2, None, None, ("list", 2), None),
>      ")": (0, None, None, None, None),
> +    "}": (0, None, None, None, None),
>      "symbol": (0, "symbol", None, None, None),
>      "string": (0, "string", None, None, None),
>      "end": (0, None, None, None, None),
> @@ -47,7 +49,7 @@ elements = {
>  keywords = {'and', 'or', 'not'}
>  
>  _quoteletters = {'"', "'"}
> -_simpleopletters = set(pycompat.iterbytestr("():=,-|&+!~^%"))
> +_simpleopletters = set(pycompat.iterbytestr("(){}:=,-|&+!~^%"))
>  
>  # default set of valid characters for the initial letter of symbols
>  _syminitletters = set(pycompat.iterbytestr(
> @@ -310,6 +312,18 @@ def _matchonly(revs, bases):
>      if _isposargs(ta, 1) and _isposargs(tb, 1):
>          return ('list', ta, tb)
>  
> +def _transformsubscript(x, y):
> +    """Rewrite 'x{y}' operator to evaluatable tree"""
> +    # this is pretty basic implementation of 'x{y}' operator, still
> +    # experimental so undocumented. see the wiki for further ideas.
> +    # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan#ideas_from_mpm 
> +    n = getinteger(y, _("set subscript must be an integer"))
> +    if n <= 0:
> +        y = ('symbol', '%s' % -n)
> +        return ('func', ('symbol', 'ancestors'), ('list', x, y, y))
> +    else:
> +        return ('func', ('symbol', 'descendants'), ('list', x, y, y))
> +
>  def _fixops(x):
>      """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
>      handled well by our simple top-down parser"""
> @@ -377,6 +391,9 @@ def _analyze(x, order):
>          return (op,) + tuple(_analyze(y, order) for y in x[1:])
>      elif op == 'keyvalue':
>          return (op, x[1], _analyze(x[2], order))
> +    elif op == 'subscript':
> +        t = _transformsubscript(x[1], _analyze(x[2], order))
> +        return _analyze(t, order)
>      elif op == 'func':
>          f = getsymbol(x[1])
>          d = defineorder
> diff --git a/tests/test-revset.t b/tests/test-revset.t
> --- a/tests/test-revset.t
> +++ b/tests/test-revset.t
> @@ -499,6 +499,145 @@ keyword arguments
>    hg: parse error: can't use a key-value pair in this context
>    [255]
>  
> +subscript operator:
> +
> +  $ hg debugrevspec -p parsed -p analyzed 'tip{0}'
> +  * parsed:
> +  (subscript
> +    ('symbol', 'tip')
> +    ('symbol', '0'))
> +  * analyzed:
> +  (func
> +    ('symbol', 'ancestors')
> +    (list
> +      ('symbol', 'tip')
> +      ('symbol', '0')
> +      ('symbol', '0'))
> +    define)
> +  9
> +
> +  $ hg debugrevspec -p parsed -p analyzed '.{-1}'
> +  * parsed:
> +  (subscript
> +    ('symbol', '.')
> +    (negate
> +      ('symbol', '1')))
> +  * analyzed:
> +  (func
> +    ('symbol', 'ancestors')
> +    (list
> +      ('symbol', '.')
> +      ('symbol', '1')
> +      ('symbol', '1'))
> +    define)
> +  8
> +
> +  $ hg debugrevspec -p parsed -p analyzed 'roots(:){2}'
> +  * parsed:
> +  (subscript
> +    (func
> +      ('symbol', 'roots')
> +      (rangeall
> +        None))
> +    ('symbol', '2'))
> +  * analyzed:
> +  (func
> +    ('symbol', 'descendants')
> +    (list
> +      (func
> +        ('symbol', 'roots')
> +        (rangeall
> +          None
> +          define)
> +        define)
> +      ('symbol', '2')
> +      ('symbol', '2'))
> +    define)
> +  2
> +  3
> +
> + has the highest binding strength (as function call)
> +
> +  $ hg debugrevspec -p parsed 'tip:tip^{-1}'
> +  * parsed:
> +  (range
> +    ('symbol', 'tip')
> +    (subscript
> +      (parentpost
> +        ('symbol', 'tip'))
> +      (negate
> +        ('symbol', '1'))))
> +  9
> +  8
> +  7
> +  6
> +  5
> +  4
> +
> +  $ hg debugrevspec -p parsed --no-show-revs 'not public(){0}'
> +  * parsed:
> +  (not
> +    (subscript
> +      (func
> +        ('symbol', 'public')
> +        None)
> +      ('symbol', '0')))
> +
> + left-hand side should be optimized recursively
> +
> +  $ hg debugrevspec -p parsed -p analyzed -p optimized --no-show-revs \
> +  > '(not public()){0}'
> +  * parsed:
> +  (subscript
> +    (group
> +      (not
> +        (func
> +          ('symbol', 'public')
> +          None)))
> +    ('symbol', '0'))
> +  * analyzed:
> +  (func
> +    ('symbol', 'ancestors')
> +    (list
> +      (not
> +        (func
> +          ('symbol', 'public')
> +          None
> +          any)
> +        define)
> +      ('symbol', '0')
> +      ('symbol', '0'))
> +    define)
> +  * optimized:
> +  (func
> +    ('symbol', 'ancestors')
> +    (list
> +      (func
> +        ('symbol', '_notpublic')
> +        None
> +        any)
> +      ('symbol', '0')
> +      ('symbol', '0'))
> +    define)
> +
> + parse errors
> +
> +  $ hg debugrevspec '{0}'
> +  hg: parse error at 0: not a prefix: {
> +  [255]
> +  $ hg debugrevspec '.{0'
> +  hg: parse error at 3: unexpected token: end
> +  [255]
> +  $ hg debugrevspec '.}'
> +  hg: parse error at 1: invalid token
> +  [255]
> +  $ hg debugrevspec '.{a}'
> +  hg: parse error: set subscript must be an integer
> +  [255]
> +  $ hg debugrevspec '.{1-2}'
> +  hg: parse error: set subscript must be an integer
> +  [255]
> +
>  parsed tree at stages:
>  
>    $ hg debugrevspec -p all '()'


More information about the Mercurial-devel mailing list