Patchwork [2,of,4] templater: tokenize decimal integer literal (issue4638) (BC)

login
register
mail settings
Submitter Yuya Nishihara
Date May 10, 2015, 11:03 p.m.
Message ID <663a1ce35fd69b0dc946.1431298996@mimosa>
Download mbox | patch
Permalink /patch/9012/
State Accepted
Headers show

Comments

Yuya Nishihara - May 10, 2015, 11:03 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1430480635 -32400
#      Fri May 01 20:43:55 2015 +0900
# Node ID 663a1ce35fd69b0dc946b467aca652a992a7ffc0
# Parent  0d41b8bb164d4942c978b92d9f66208aca56ab72
templater: tokenize decimal integer literal (issue4638) (BC)

Before this patch, we had to quote integer literals to pass to template
functions. It was error-prone, so we should allow "word(0, x)" syntax.
Currently only decimal integers are allowed. It's easy to support 0x, 0b and 0
prefixes, but I don't think they are useful.

This patch assumes that template keywords and names defined in map files do
not start with digits, except for positional variables seen in the schemes
extension.

Patch

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -20,6 +20,7 @@  elements = {
     "|": (5, None, ("|", 5)),
     "%": (6, None, ("%", 6)),
     ")": (0, None, None),
+    "integer": (0, ("integer",), None),
     "symbol": (0, ("symbol",), None),
     "string": (0, ("string",), None),
     "rawstring": (0, ("rawstring",), None),
@@ -59,6 +60,20 @@  def tokenizer(data):
                 pos += 1
             else:
                 raise error.ParseError(_("unterminated string"), s)
+        elif c.isdigit() or c == '-':
+            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():
+                    break
+                pos += 1
+            yield ('integer', program[s:pos], s)
+            pos -= 1
         elif c.isalnum() or c in '_':
             s = pos
             pos += 1
@@ -135,6 +150,9 @@  def gettemplate(exp, context):
         return context._load(exp[1])
     raise error.ParseError(_("expected template specifier"))
 
+def runinteger(context, mapping, data):
+    return int(data)
+
 def runstring(context, mapping, data):
     return data.decode("string-escape")
 
@@ -567,6 +585,7 @@  def word(context, mapping, args):
 
 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
 exprmethods = {
+    "integer": lambda e, c: (runinteger, e[1]),
     "string": lambda e, c: (runstring, e[1]),
     "rawstring": lambda e, c: (runrawstring, e[1]),
     "symbol": lambda e, c: (runsymbol, e[1]),
@@ -579,6 +598,7 @@  exprmethods = {
 
 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
 methods = exprmethods.copy()
+methods["integer"] = exprmethods["symbol"]  # '{1}' as variable
 
 funcs = {
     "date": date,
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
@@ -2291,6 +2291,39 @@  Test invalid date:
   hg: parse error: date expects a date information
   [255]
 
+Test integer literal:
+
+  $ hg log -Ra -r0 -T '{(0)}\n'
+  0
+  $ hg log -Ra -r0 -T '{(123)}\n'
+  123
+  $ hg log -Ra -r0 -T '{(-4)}\n'
+  -4
+  $ hg log -Ra -r0 -T '{(-)}\n'
+  hg: parse error at 2: integer literal without digits
+  [255]
+  $ hg log -Ra -r0 -T '{(-a)}\n'
+  hg: parse error at 2: integer literal without digits
+  [255]
+
+top-level integer literal is interpreted as symbol (i.e. variable name):
+
+  $ hg log -Ra -r0 -T '{1}\n'
+  
+  $ hg log -Ra -r0 -T '{if("t", "{1}")}\n'
+  
+  $ hg log -Ra -r0 -T '{1|stringify}\n'
+  
+
+unless explicit symbol is expected:
+
+  $ hg log -Ra -r0 -T '{desc|1}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+  $ hg log -Ra -r0 -T '{1()}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+
 Test string escaping:
 
   $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
@@ -2737,8 +2770,13 @@  Test word error messages for not enough 
   hg: parse error: word expects two or three arguments, got 7
   [255]
 
+Test word for integer literal
+
+  $ hg log -R a --template "{word(2, desc)}\n" -r0
+  line
+
 Test word for invalid numbers
 
-  $ hg log -Gv -R a --template "{word(2, desc)}"
+  $ hg log -Gv -R a --template "{word('a', desc)}"
   hg: parse error: Use strings like '3' for numbers passed to word function
   [255]