diff --git a/mercurial/revset.py b/mercurial/revset.py --- a/mercurial/revset.py +++ b/mercurial/revset.py @@ -151,6 +151,15 @@ def orset(repo, subset, x, order): def notset(repo, subset, x, order): return subset - getset(repo, subset, x) +def relationset(repo, subset, x, y, order): + raise error.ParseError(_("can't use a relation in this context")) + +def relsubscriptset(repo, subset, x, y, z, order): + raise error.ParseError(_("can't use a relation in this context")) + +def subscriptset(repo, subset, x, y, order): + raise error.ParseError(_("can't use a subscript in this context")) + def listset(repo, subset, *xs): raise error.ParseError(_("can't use a list in this context"), hint=_('see hg help "revsets.x or y"')) @@ -2004,6 +2013,9 @@ methods = { "or": orset, "not": notset, "difference": differenceset, + "relation": relationset, + "relsubscript": relsubscriptset, + "subscript": subscriptset, "list": listset, "keyvalue": keyvaluepair, "func": func, diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py --- a/mercurial/revsetlang.py +++ b/mercurial/revsetlang.py @@ -21,6 +21,8 @@ from . import ( elements = { # token-type: binding-strength, primary, prefix, infix, suffix "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None), + "[": (21, None, None, ("subscript", 1, "]"), None), + "#": (21, None, None, ("relation", 21), None), "##": (20, None, None, ("_concat", 20), None), "~": (18, None, None, ("ancestor", 18), None), "^": (18, None, None, ("parent", 18), "parentpost"), @@ -39,6 +41,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 +50,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( @@ -331,6 +334,9 @@ def _fixops(x): # make number of arguments deterministic: # x + y + z -> (or x y z) -> (or (list x y z)) return (op, _fixops(('list',) + x[1:])) + elif op == 'subscript' and x[1][0] == 'relation': + # x#y[z] ternary + return _fixops(('relsubscript', x[1][1], x[1][2], x[2])) return (op,) + tuple(_fixops(y) for y in x[1:]) @@ -369,10 +375,16 @@ def _analyze(x, order): return (op, _analyze(x[1], defineorder), order) elif op == 'group': return _analyze(x[1], order) - elif op in ('dagrange', 'range', 'parent', 'ancestor'): + elif op in ('dagrange', 'range', 'parent', 'ancestor', 'relation', + 'subscript'): ta = _analyze(x[1], defineorder) tb = _analyze(x[2], defineorder) return (op, ta, tb, order) + elif op == 'relsubscript': + ta = _analyze(x[1], defineorder) + tb = _analyze(x[2], defineorder) + tc = _analyze(x[3], defineorder) + return (op, ta, tb, tc, order) elif op == 'list': return (op,) + tuple(_analyze(y, order) for y in x[1:]) elif op == 'keyvalue': @@ -481,10 +493,14 @@ def _optimize(x, small): wb, tb = _optimize(x[2], small) order = x[3] return wa + wb, (op, ta, tb, order) - elif op in ('parent', 'ancestor'): + elif op in ('parent', 'ancestor', 'relation', 'subscript'): w, t = _optimize(x[1], small) order = x[3] return w, (op, t, x[2], order) + elif op == 'relsubscript': + w, t = _optimize(x[1], small) + order = x[4] + return w, (op, t, x[2], x[3], order) elif op == 'list': ws, ts = zip(*(_optimize(y, small) for y in x[1:])) return sum(ws), (op,) + ts diff --git a/tests/test-annotate.t b/tests/test-annotate.t --- a/tests/test-annotate.t +++ b/tests/test-annotate.t @@ -812,7 +812,7 @@ check error cases abort: line range exceeds file size [255] $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=[1])' - hg: parse error at 43: syntax error in revset 'followlines(baz, 2:4, startrev=20, descend=[1])' + hg: parse error at 43: not a prefix: [ [255] $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=a)' hg: parse error: descend argument must be a boolean diff --git a/tests/test-revset.t b/tests/test-revset.t --- a/tests/test-revset.t +++ b/tests/test-revset.t @@ -500,6 +500,151 @@ keyword arguments hg: parse error: can't use a key-value pair in this context [255] +relation-subscript operator has the highest binding strength (as function call): + + $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]' + * parsed: + (range + ('symbol', 'tip') + (relsubscript + (parentpost + ('symbol', 'tip')) + ('symbol', 'generations') + (negate + ('symbol', '1')))) + hg: parse error: can't use a relation in this context + [255] + + $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]' + * parsed: + (not + (relsubscript + (func + ('symbol', 'public') + None) + ('symbol', 'generations') + ('symbol', '0'))) + hg: parse error: can't use a relation in this context + [255] + +left-hand side of relation-subscript operator should be optimized recursively: + + $ hg debugrevspec -p analyzed -p optimized --no-show-revs \ + > '(not public())#generations[0]' + * analyzed: + (relsubscript + (not + (func + ('symbol', 'public') + None + any) + define) + ('symbol', 'generations') + ('symbol', '0') + define) + * optimized: + (relsubscript + (func + ('symbol', '_notpublic') + None + any) + ('symbol', 'generations') + ('symbol', '0') + define) + hg: parse error: can't use a relation in this context + [255] + +resolution of subscript and relation-subscript ternary operators: + + $ hg debugrevspec -p analyzed 'tip[0]' + * analyzed: + (subscript + ('symbol', 'tip') + ('symbol', '0') + define) + hg: parse error: can't use a subscript in this context + [255] + + $ hg debugrevspec -p analyzed 'tip#rel[0]' + * analyzed: + (relsubscript + ('symbol', 'tip') + ('symbol', 'rel') + ('symbol', '0') + define) + hg: parse error: can't use a relation in this context + [255] + + $ hg debugrevspec -p analyzed '(tip#rel)[0]' + * analyzed: + (subscript + (relation + ('symbol', 'tip') + ('symbol', 'rel') + define) + ('symbol', '0') + define) + hg: parse error: can't use a subscript in this context + [255] + + $ hg debugrevspec -p analyzed 'tip#rel[0][1]' + * analyzed: + (subscript + (relsubscript + ('symbol', 'tip') + ('symbol', 'rel') + ('symbol', '0') + define) + ('symbol', '1') + define) + hg: parse error: can't use a subscript in this context + [255] + + $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]' + * analyzed: + (relsubscript + (relation + ('symbol', 'tip') + ('symbol', 'rel0') + define) + ('symbol', 'rel1') + ('symbol', '1') + define) + hg: parse error: can't use a relation in this context + [255] + + $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]' + * analyzed: + (relsubscript + (relsubscript + ('symbol', 'tip') + ('symbol', 'rel0') + ('symbol', '0') + define) + ('symbol', 'rel1') + ('symbol', '1') + define) + hg: parse error: can't use a relation in this context + [255] + +parse errors of relation, subscript and relation-subscript operators: + + $ hg debugrevspec '[0]' + hg: parse error at 0: not a prefix: [ + [255] + $ hg debugrevspec '.#' + hg: parse error at 2: not a prefix: end + [255] + $ hg debugrevspec '#rel' + hg: parse error at 0: not a prefix: # + [255] + $ hg debugrevspec '.#rel[0' + hg: parse error at 7: unexpected token: end + [255] + $ hg debugrevspec '.]' + hg: parse error at 1: invalid token + [255] + parsed tree at stages: $ hg debugrevspec -p all '()'