Patchwork [2,of,3] revset: add experimental relation and subscript operators

login
register
mail settings
Submitter Yuya Nishihara
Date July 13, 2017, 4:16 p.m.
Message ID <eed6cd6aa792698d0535.1499962578@mimosa>
Download mbox | patch
Permalink /patch/22295/
State Accepted
Headers show

Comments

Yuya Nishihara - July 13, 2017, 4:16 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1499486879 -32400
#      Sat Jul 08 13:07:59 2017 +0900
# Node ID eed6cd6aa792698d0535f276e150f88500943866
# Parent  f98c5d417af06e54df22a58fa2e6305ff7b5a7ba
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

Patch

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 '()'