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