[PATCH v2] templater: provide arithmetic operations on integers

2016-10-09 Thread Simon Farnsworth
# HG changeset patch
# User Simon Farnsworth 
# Date 1476017464 25200
#  Sun Oct 09 05:51:04 2016 -0700
# Node ID 2e2c959de0fe2c17bf6c5f47c01035a36f13c593
# Parent  dbcef8918bbdd8a64d9f79a37bcfa284a26f3a39
templater: provide arithmetic operations on integers

The termwidth template keyword is of limited use without some way to ensure
that margins are respected.

Provide a full set of arithmetic operators (four basic operations plus the
mod function, defined to match Python's // for division), so that you can
create termwidth based layouts that match the user's terminal size

diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -43,6 +43,12 @@
 
 .. functionsmarker
 
+We provide a limited set of infix arithmetic operations on integers:
+ + for addition
+ - for subtraction
+ * for multiplication
+ / for floor division (division rounded to integer nearest -infinity)
+Division fulfils the law x = x / y + mod(x, y).
 Also, for any expression that returns a list, there is a list operator::
 
 expr % "{template}"
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -33,6 +33,10 @@
 "|": (5, None, None, ("|", 5), None),
 "%": (6, None, None, ("%", 6), None),
 ")": (0, None, None, None, None),
+"+": (3, None, None, ("+", 3), None),
+"-": (3, None, ("negate", 10), ("-", 3), None),
+"*": (4, None, None, ("*", 4), None),
+"/": (4, None, None, ("/", 4), None),
 "integer": (0, "integer", None, None, None),
 "symbol": (0, "symbol", None, None, None),
 "string": (0, "string", None, None, None),
@@ -48,7 +52,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,13 +74,8 @@
 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
-if pos >= end or not program[pos].isdigit():
-raise error.ParseError(_("integer literal without digits"), s)
-pos += 1
 while pos < end:
 d = program[pos]
 if not d.isdigit():
@@ -420,6 +419,28 @@
 # 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])]
@@ -713,6 +734,20 @@
 tzoffset = util.makedate()[1]
 return (date[0], tzoffset)
 
+@templatefunc('mod(a, b)')
+def mod(context, mapping, args):
+"""Calculate a mod b such that a / b + a mod b == a"""
+if not len(args) == 2:
+# i18n: "mod" is a keyword
+raise error.ParseError(_("mod expects two arguments"))
+
+left = evalinteger(context, mapping, args[0],
+_('arithmetic only defined on integers'))
+right = evalinteger(context, mapping, args[1],
+_('arithmetic only defined on integers'))
+
+return left % right
+
 @templatefunc('revset(query[, formatargs...])')
 def revset(context, mapping, args):
 """Execute a revision set query. See
@@ -906,6 +941,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 +950,11 @@
 "|": 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"

Re: [PATCH v2] templater: provide arithmetic operations on integers

2016-10-09 Thread Yuya Nishihara
On Sun, 9 Oct 2016 05:53:25 -0700, Simon Farnsworth wrote:
> # HG changeset patch
> # User Simon Farnsworth 
> # Date 1476017464 25200
> #  Sun Oct 09 05:51:04 2016 -0700
> # Node ID 2e2c959de0fe2c17bf6c5f47c01035a36f13c593
> # Parent  dbcef8918bbdd8a64d9f79a37bcfa284a26f3a39
> templater: provide arithmetic operations on integers

Nice. Queued slight modified version, thanks.

We'll need to catch ZeroDivisionError as a follow-up.

> --- a/mercurial/help/templates.txt
> +++ b/mercurial/help/templates.txt
> @@ -43,6 +43,12 @@
>  
>  .. functionsmarker
>  
> +We provide a limited set of infix arithmetic operations on integers:
> + + for addition
> + - for subtraction
> + * for multiplication
> + / for floor division (division rounded to integer nearest -infinity)
> +Division fulfils the law x = x / y + mod(x, y).

Reformatted as reST.

>  # 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]),

Looks like a copy-paste error. Removed this.

>  "string": lambda e, c: (runstring, e[1]),
>  "symbol": lambda e, c: (runsymbol, e[1]),
>  "template": buildtemplate,
> @@ -914,6 +950,11 @@
>  "|": 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),
> +"/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
>  }

> +But negate binds closer still:
> +
> +  $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
> +  (template
> +(-
> +  ('integer', '1')
> +  (|
> +('integer', '3')
> +('symbol', 'stringify')))
> +('string', '\n'))
> +  hg: parse error: arithmetic only defined on integers
> +  [255]

For the record, this fails because '3' is taken as a keyword (for backward
compatibility), and evaluated to ''.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH v2] templater: provide arithmetic operations on integers

2016-10-09 Thread Simon Farnsworth

On 09/10/2016 16:51, Yuya Nishihara wrote:

On Sun, 9 Oct 2016 05:53:25 -0700, Simon Farnsworth wrote:

# HG changeset patch
# User Simon Farnsworth 
# Date 1476017464 25200
#  Sun Oct 09 05:51:04 2016 -0700
# Node ID 2e2c959de0fe2c17bf6c5f47c01035a36f13c593
# Parent  dbcef8918bbdd8a64d9f79a37bcfa284a26f3a39
templater: provide arithmetic operations on integers


Nice. Queued slight modified version, thanks.

We'll need to catch ZeroDivisionError as a follow-up.


Follow-up incoming to turn ZeroDivisionError into an abort (and to reuse 
runarithmetic() in mod()).





+But negate binds closer still:
+
+  $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
+  (template
+(-
+  ('integer', '1')
+  (|
+('integer', '3')
+('symbol', 'stringify')))
+('string', '\n'))
+  hg: parse error: arithmetic only defined on integers
+  [255]


For the record, this fails because '3' is taken as a keyword (for backward
compatibility), and evaluated to ''.

Indeed - the parse tree is the important bit here, not the failure, as 
it shows that we've parsed '1-3' as a subtraction with the right 
precedence, not as '1' then '-3'.

--
Simon Farnsworth
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel