revset: add experimental relation and subscript operators
authorYuya Nishihara <yuya@tcha.org>
Sat, 08 Jul 2017 13:07:59 +0900
changeset 33416 9467d5337292
parent 33415 371f59c6a89e
child 33417 d1b13d4995ed
revset: add experimental relation and subscript operators The proposed syntax [1] was originally 'set{n rel}', but it seemed slightly confusing if template is involved. On the other hand, we want to keep 'set[n]' for future extension. So this patch introduces 'set#rel[n]' ternary operator. I chose '#' just because it looks like applying an attribute. This also adds stubs for 'set[n]' and 'set#rel' operators since these syntax elements are fundamental for constructing 'set#rel[n]'. [1]: https://www.mercurial-scm.org/wiki/RevsetOperatorPlan#ideas_from_mpm
mercurial/revset.py
mercurial/revsetlang.py
tests/test-annotate.t
tests/test-revset.t
--- a/mercurial/revset.py	Sat Jul 08 12:49:46 2017 +0900
+++ b/mercurial/revset.py	Sat Jul 08 13:07:59 2017 +0900
@@ -151,6 +151,15 @@
 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 @@
     "or": orset,
     "not": notset,
     "difference": differenceset,
+    "relation": relationset,
+    "relsubscript": relsubscriptset,
+    "subscript": subscriptset,
     "list": listset,
     "keyvalue": keyvaluepair,
     "func": func,
--- a/mercurial/revsetlang.py	Sat Jul 08 12:49:46 2017 +0900
+++ b/mercurial/revsetlang.py	Sat Jul 08 13:07:59 2017 +0900
@@ -21,6 +21,8 @@
 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 @@
     "=": (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 @@
 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 @@
         # 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 @@
         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 @@
         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
--- a/tests/test-annotate.t	Sat Jul 08 12:49:46 2017 +0900
+++ b/tests/test-annotate.t	Sat Jul 08 13:07:59 2017 +0900
@@ -812,7 +812,7 @@
   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
--- a/tests/test-revset.t	Sat Jul 08 12:49:46 2017 +0900
+++ b/tests/test-revset.t	Sat Jul 08 13:07:59 2017 +0900
@@ -500,6 +500,151 @@
   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 '()'