From patchwork Fri Jun 30 14:58:43 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: revset: add experimental set subscript operator From: Yuya Nishihara X-Patchwork-Id: 21849 Message-Id: To: mercurial-devel@mercurial-scm.org Date: Fri, 30 Jun 2017 23:58:43 +0900 # HG changeset patch # User Yuya Nishihara # 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 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 '()'