##// END OF EJS Templates
templater: provide arithmetic operations on integers...
Simon Farnsworth -
r30115:8e42dfde default
parent child Browse files
Show More
@@ -43,6 +43,15 b' In addition to filters, there are some b'
43
43
44 .. functionsmarker
44 .. functionsmarker
45
45
46 We provide a limited set of infix arithmetic operations on integers::
47
48 + for addition
49 - for subtraction
50 * for multiplication
51 / for floor division (division rounded to integer nearest -infinity)
52
53 Division fulfils the law x = x / y + mod(x, y).
54
46 Also, for any expression that returns a list, there is a list operator::
55 Also, for any expression that returns a list, there is a list operator::
47
56
48 expr % "{template}"
57 expr % "{template}"
@@ -33,6 +33,10 b' elements = {'
33 "|": (5, None, None, ("|", 5), None),
33 "|": (5, None, None, ("|", 5), None),
34 "%": (6, None, None, ("%", 6), None),
34 "%": (6, None, None, ("%", 6), None),
35 ")": (0, None, None, None, None),
35 ")": (0, None, None, None, None),
36 "+": (3, None, None, ("+", 3), None),
37 "-": (3, None, ("negate", 10), ("-", 3), None),
38 "*": (4, None, None, ("*", 4), None),
39 "/": (4, None, None, ("/", 4), None),
36 "integer": (0, "integer", None, None, None),
40 "integer": (0, "integer", None, None, None),
37 "symbol": (0, "symbol", None, None, None),
41 "symbol": (0, "symbol", None, None, None),
38 "string": (0, "string", None, None, None),
42 "string": (0, "string", None, None, None),
@@ -48,7 +52,7 b' def tokenize(program, start, end, term=N'
48 c = program[pos]
52 c = program[pos]
49 if c.isspace(): # skip inter-token whitespace
53 if c.isspace(): # skip inter-token whitespace
50 pass
54 pass
51 elif c in "(,)%|": # handle simple operators
55 elif c in "(,)%|+-*/": # handle simple operators
52 yield (c, None, pos)
56 yield (c, None, pos)
53 elif c in '"\'': # handle quoted templates
57 elif c in '"\'': # handle quoted templates
54 s = pos + 1
58 s = pos + 1
@@ -70,13 +74,8 b' def tokenize(program, start, end, term=N'
70 pos += 1
74 pos += 1
71 else:
75 else:
72 raise error.ParseError(_("unterminated string"), s)
76 raise error.ParseError(_("unterminated string"), s)
73 elif c.isdigit() or c == '-':
77 elif c.isdigit():
74 s = pos
78 s = pos
75 if c == '-': # simply take negate operator as part of integer
76 pos += 1
77 if pos >= end or not program[pos].isdigit():
78 raise error.ParseError(_("integer literal without digits"), s)
79 pos += 1
80 while pos < end:
79 while pos < end:
81 d = program[pos]
80 d = program[pos]
82 if not d.isdigit():
81 if not d.isdigit():
@@ -420,6 +419,28 b' def runmap(context, mapping, data):'
420 # If so, return the expanded value.
419 # If so, return the expanded value.
421 yield i
420 yield i
422
421
422 def buildnegate(exp, context):
423 arg = compileexp(exp[1], context, exprmethods)
424 return (runnegate, arg)
425
426 def runnegate(context, mapping, data):
427 data = evalinteger(context, mapping, data,
428 _('negation needs an integer argument'))
429 return -data
430
431 def buildarithmetic(exp, context, func):
432 left = compileexp(exp[1], context, exprmethods)
433 right = compileexp(exp[2], context, exprmethods)
434 return (runarithmetic, (func, left, right))
435
436 def runarithmetic(context, mapping, data):
437 func, left, right = data
438 left = evalinteger(context, mapping, left,
439 _('arithmetic only defined on integers'))
440 right = evalinteger(context, mapping, right,
441 _('arithmetic only defined on integers'))
442 return func(left, right)
443
423 def buildfunc(exp, context):
444 def buildfunc(exp, context):
424 n = getsymbol(exp[1])
445 n = getsymbol(exp[1])
425 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
446 args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
@@ -713,6 +734,20 b' def localdate(context, mapping, args):'
713 tzoffset = util.makedate()[1]
734 tzoffset = util.makedate()[1]
714 return (date[0], tzoffset)
735 return (date[0], tzoffset)
715
736
737 @templatefunc('mod(a, b)')
738 def mod(context, mapping, args):
739 """Calculate a mod b such that a / b + a mod b == a"""
740 if not len(args) == 2:
741 # i18n: "mod" is a keyword
742 raise error.ParseError(_("mod expects two arguments"))
743
744 left = evalinteger(context, mapping, args[0],
745 _('arithmetic only defined on integers'))
746 right = evalinteger(context, mapping, args[1],
747 _('arithmetic only defined on integers'))
748
749 return left % right
750
716 @templatefunc('relpath(path)')
751 @templatefunc('relpath(path)')
717 def relpath(context, mapping, args):
752 def relpath(context, mapping, args):
718 """Convert a repository-absolute path into a filesystem path relative to
753 """Convert a repository-absolute path into a filesystem path relative to
@@ -926,6 +961,11 b' exprmethods = {'
926 "|": buildfilter,
961 "|": buildfilter,
927 "%": buildmap,
962 "%": buildmap,
928 "func": buildfunc,
963 "func": buildfunc,
964 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
965 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
966 "negate": buildnegate,
967 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
968 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
929 }
969 }
930
970
931 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
971 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
@@ -29,6 +29,111 b''
29 $ hg merge -q foo
29 $ hg merge -q foo
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31
31
32 Test arithmetic operators have the right precedence:
33
34 $ hg log -l 1 -T '{date(date, "%s") + 5 * 10} {date(date, "%s") - 2 * 3}\n'
35 1500051 1499995
36 $ hg log -l 1 -T '{date(date, "%s") * 5 + 10} {date(date, "%s") * 3 - 2}\n'
37 7500015 4500001
38
39 Test division:
40
41 $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
42 (template
43 (/
44 ('integer', '5')
45 ('integer', '2'))
46 ('string', ' ')
47 (func
48 ('symbol', 'mod')
49 (list
50 ('integer', '5')
51 ('integer', '2')))
52 ('string', '\n'))
53 2 1
54 $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
55 (template
56 (/
57 ('integer', '5')
58 (negate
59 ('integer', '2')))
60 ('string', ' ')
61 (func
62 ('symbol', 'mod')
63 (list
64 ('integer', '5')
65 (negate
66 ('integer', '2'))))
67 ('string', '\n'))
68 -3 -1
69 $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
70 (template
71 (/
72 (negate
73 ('integer', '5'))
74 ('integer', '2'))
75 ('string', ' ')
76 (func
77 ('symbol', 'mod')
78 (list
79 (negate
80 ('integer', '5'))
81 ('integer', '2')))
82 ('string', '\n'))
83 -3 1
84 $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
85 (template
86 (/
87 (negate
88 ('integer', '5'))
89 (negate
90 ('integer', '2')))
91 ('string', ' ')
92 (func
93 ('symbol', 'mod')
94 (list
95 (negate
96 ('integer', '5'))
97 (negate
98 ('integer', '2'))))
99 ('string', '\n'))
100 2 -1
101
102 Filters bind closer than arithmetic:
103
104 $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
105 (template
106 (-
107 (|
108 (func
109 ('symbol', 'revset')
110 ('string', '.'))
111 ('symbol', 'count'))
112 ('integer', '1'))
113 ('string', '\n'))
114 0
115
116 But negate binds closer still:
117
118 $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
119 (template
120 (-
121 ('integer', '1')
122 (|
123 ('integer', '3')
124 ('symbol', 'stringify')))
125 ('string', '\n'))
126 hg: parse error: arithmetic only defined on integers
127 [255]
128 $ hg debugtemplate -r0 -v '{-3|stringify}\n'
129 (template
130 (|
131 (negate
132 ('integer', '3'))
133 ('symbol', 'stringify'))
134 ('string', '\n'))
135 -3
136
32 Second branch starting at nullrev:
137 Second branch starting at nullrev:
33
138
34 $ hg update null
139 $ hg update null
@@ -2890,14 +2995,15 b' Test integer literal:'
2890 $ hg debugtemplate -v '{(-4)}\n'
2995 $ hg debugtemplate -v '{(-4)}\n'
2891 (template
2996 (template
2892 (group
2997 (group
2893 ('integer', '-4'))
2998 (negate
2999 ('integer', '4')))
2894 ('string', '\n'))
3000 ('string', '\n'))
2895 -4
3001 -4
2896 $ hg debugtemplate '{(-)}\n'
3002 $ hg debugtemplate '{(-)}\n'
2897 hg: parse error at 2: integer literal without digits
3003 hg: parse error at 3: not a prefix: )
2898 [255]
3004 [255]
2899 $ hg debugtemplate '{(-a)}\n'
3005 $ hg debugtemplate '{(-a)}\n'
2900 hg: parse error at 2: integer literal without digits
3006 hg: parse error: negation needs an integer argument
2901 [255]
3007 [255]
2902
3008
2903 top-level integer literal is interpreted as symbol (i.e. variable name):
3009 top-level integer literal is interpreted as symbol (i.e. variable name):
General Comments 0
You need to be logged in to leave comments. Login now