[PATCH RFC] templater: provide ring operations on integers

Simon Farnsworth simonfar at fb.com
Sat Oct 8 16:24:46 UTC 2016


# HG changeset patch
# User Simon Farnsworth <simonfar at fb.com>
# Date 1475943860 25200
#      Sat Oct 08 09:24:20 2016 -0700
# Node ID e89699ba5c9f0bf883bfae7c485e50219b90b2f9
# Parent  91a3c58ecf938ed675f5364b88f0d663f12b0047
templater: provide ring operations on integers

The termwidth template keyword is of limited use without some way to ensure
that margins are respected. Provide the three core ring operations -
addition, subtraction and multiplication. We can extend to division and
modulus later if necessary.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -33,6 +33,9 @@
     "|": (5, None, None, ("|", 5), None),
     "%": (6, None, None, ("%", 6), None),
     ")": (0, None, None, None, None),
+    "+": (10, None, None, ("+", 10), None),
+    "-": (10, None, ("negate", 10), ("-", 10), None),
+    "*": (12, None, None, ("*", 12), None),
     "integer": (0, "integer", None, None, None),
     "symbol": (0, "symbol", None, None, None),
     "string": (0, "string", None, None, None),
@@ -48,7 +51,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(,)%|": # handle simple operators
+        elif c in "(,)%|+-*": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -70,7 +73,7 @@
                 pos += 1
             else:
                 raise error.ParseError(_("unterminated string"), s)
-        elif c.isdigit() or c == '-':
+        elif c.isdigit():
             s = pos
             if c == '-': # simply take negate operator as part of integer
                 pos += 1
@@ -420,6 +423,29 @@
             # If so, return the expanded value.
             yield i
 
+def buildnegate(exp, context):
+    arg = compileexp(exp[1], context, exprmethods)
+    return (runnegate, arg)
+
+def runnegate(context, mapping, data):
+    data = evalinteger(context, mapping, data,
+                        _('negation needs an integer argument'))
+    return -data
+
+
+def buildarithmetic(exp, context, func):
+    left = compileexp(exp[1], context, exprmethods)
+    right = compileexp(exp[2], context, exprmethods)
+    return (runarithmetic, (func, left, right))
+
+def runarithmetic(context, mapping, data):
+    func, left, right = data
+    left = evalinteger(context, mapping, left,
+                        _('arithmetic only defined on integers'))
+    right = evalinteger(context, mapping, right,
+                        _('arithmetic only defined on integers'))
+    return func(left, right)
+
 def buildfunc(exp, context):
     n = getsymbol(exp[1])
     args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
@@ -906,6 +932,7 @@
 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
 exprmethods = {
     "integer": lambda e, c: (runinteger, e[1]),
+    "negate": lambda e, c: (runinteger, e[1]),
     "string": lambda e, c: (runstring, e[1]),
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
@@ -914,6 +941,10 @@
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
+    "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
+    "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
+    "negate": buildnegate,
+    "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
     }
 
 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -5,6 +5,8 @@
   $ echo line 1 > b
   $ echo line 2 >> b
   $ hg commit -l b -d '1000000 0' -u 'User Name <user at hostname>'
+  $ hg log -T '{date(date, "%s") + 1} {date(date, "%s") - 2 * 1}\n'
+  1000001 999998
 
   $ hg add b
   $ echo other 1 > c
@@ -2890,14 +2892,15 @@
   $ hg debugtemplate -v '{(-4)}\n'
   (template
     (group
-      ('integer', '-4'))
+      (negate
+        ('integer', '4')))
     ('string', '\n'))
   -4
   $ hg debugtemplate '{(-)}\n'
-  hg: parse error at 2: integer literal without digits
+  hg: parse error at 3: not a prefix: )
   [255]
   $ hg debugtemplate '{(-a)}\n'
-  hg: parse error at 2: integer literal without digits
+  hg: parse error: negation needs an integer argument
   [255]
 
 top-level integer literal is interpreted as symbol (i.e. variable name):


More information about the Mercurial-devel mailing list