##// END OF EJS Templates
revset: do not compute weight for integer literal argument...
Yuya Nishihara -
r33415:371f59c6 default
parent child Browse files
Show More
@@ -1,725 +1,729 b''
1 # revsetlang.py - parser, tokenizer and utility for revision set language
1 # revsetlang.py - parser, tokenizer and utility for revision set language
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import string
10 import string
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 node,
15 node,
16 parser,
16 parser,
17 pycompat,
17 pycompat,
18 util,
18 util,
19 )
19 )
20
20
21 elements = {
21 elements = {
22 # token-type: binding-strength, primary, prefix, infix, suffix
22 # token-type: binding-strength, primary, prefix, infix, suffix
23 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
24 "##": (20, None, None, ("_concat", 20), None),
24 "##": (20, None, None, ("_concat", 20), None),
25 "~": (18, None, None, ("ancestor", 18), None),
25 "~": (18, None, None, ("ancestor", 18), None),
26 "^": (18, None, None, ("parent", 18), "parentpost"),
26 "^": (18, None, None, ("parent", 18), "parentpost"),
27 "-": (5, None, ("negate", 19), ("minus", 5), None),
27 "-": (5, None, ("negate", 19), ("minus", 5), None),
28 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
28 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
29 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
29 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
30 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
30 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
31 "not": (10, None, ("not", 10), None, None),
31 "not": (10, None, ("not", 10), None, None),
32 "!": (10, None, ("not", 10), None, None),
32 "!": (10, None, ("not", 10), None, None),
33 "and": (5, None, None, ("and", 5), None),
33 "and": (5, None, None, ("and", 5), None),
34 "&": (5, None, None, ("and", 5), None),
34 "&": (5, None, None, ("and", 5), None),
35 "%": (5, None, None, ("only", 5), "onlypost"),
35 "%": (5, None, None, ("only", 5), "onlypost"),
36 "or": (4, None, None, ("or", 4), None),
36 "or": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
39 "=": (3, None, None, ("keyvalue", 3), None),
39 "=": (3, None, None, ("keyvalue", 3), None),
40 ",": (2, None, None, ("list", 2), None),
40 ",": (2, None, None, ("list", 2), None),
41 ")": (0, None, None, None, None),
41 ")": (0, None, None, None, None),
42 "symbol": (0, "symbol", None, None, None),
42 "symbol": (0, "symbol", None, None, None),
43 "string": (0, "string", None, None, None),
43 "string": (0, "string", None, None, None),
44 "end": (0, None, None, None, None),
44 "end": (0, None, None, None, None),
45 }
45 }
46
46
47 keywords = {'and', 'or', 'not'}
47 keywords = {'and', 'or', 'not'}
48
48
49 _quoteletters = {'"', "'"}
49 _quoteletters = {'"', "'"}
50 _simpleopletters = set(pycompat.iterbytestr("():=,-|&+!~^%"))
50 _simpleopletters = set(pycompat.iterbytestr("():=,-|&+!~^%"))
51
51
52 # default set of valid characters for the initial letter of symbols
52 # default set of valid characters for the initial letter of symbols
53 _syminitletters = set(pycompat.iterbytestr(
53 _syminitletters = set(pycompat.iterbytestr(
54 string.ascii_letters.encode('ascii') +
54 string.ascii_letters.encode('ascii') +
55 string.digits.encode('ascii') +
55 string.digits.encode('ascii') +
56 '._@')) | set(map(pycompat.bytechr, xrange(128, 256)))
56 '._@')) | set(map(pycompat.bytechr, xrange(128, 256)))
57
57
58 # default set of valid characters for non-initial letters of symbols
58 # default set of valid characters for non-initial letters of symbols
59 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
59 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
60
60
61 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
61 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
62 '''
62 '''
63 Parse a revset statement into a stream of tokens
63 Parse a revset statement into a stream of tokens
64
64
65 ``syminitletters`` is the set of valid characters for the initial
65 ``syminitletters`` is the set of valid characters for the initial
66 letter of symbols.
66 letter of symbols.
67
67
68 By default, character ``c`` is recognized as valid for initial
68 By default, character ``c`` is recognized as valid for initial
69 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
69 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
70
70
71 ``symletters`` is the set of valid characters for non-initial
71 ``symletters`` is the set of valid characters for non-initial
72 letters of symbols.
72 letters of symbols.
73
73
74 By default, character ``c`` is recognized as valid for non-initial
74 By default, character ``c`` is recognized as valid for non-initial
75 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
75 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
76
76
77 Check that @ is a valid unquoted token character (issue3686):
77 Check that @ is a valid unquoted token character (issue3686):
78 >>> list(tokenize("@::"))
78 >>> list(tokenize("@::"))
79 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
79 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
80
80
81 '''
81 '''
82 program = pycompat.bytestr(program)
82 program = pycompat.bytestr(program)
83 if syminitletters is None:
83 if syminitletters is None:
84 syminitletters = _syminitletters
84 syminitletters = _syminitletters
85 if symletters is None:
85 if symletters is None:
86 symletters = _symletters
86 symletters = _symletters
87
87
88 if program and lookup:
88 if program and lookup:
89 # attempt to parse old-style ranges first to deal with
89 # attempt to parse old-style ranges first to deal with
90 # things like old-tag which contain query metacharacters
90 # things like old-tag which contain query metacharacters
91 parts = program.split(':', 1)
91 parts = program.split(':', 1)
92 if all(lookup(sym) for sym in parts if sym):
92 if all(lookup(sym) for sym in parts if sym):
93 if parts[0]:
93 if parts[0]:
94 yield ('symbol', parts[0], 0)
94 yield ('symbol', parts[0], 0)
95 if len(parts) > 1:
95 if len(parts) > 1:
96 s = len(parts[0])
96 s = len(parts[0])
97 yield (':', None, s)
97 yield (':', None, s)
98 if parts[1]:
98 if parts[1]:
99 yield ('symbol', parts[1], s + 1)
99 yield ('symbol', parts[1], s + 1)
100 yield ('end', None, len(program))
100 yield ('end', None, len(program))
101 return
101 return
102
102
103 pos, l = 0, len(program)
103 pos, l = 0, len(program)
104 while pos < l:
104 while pos < l:
105 c = program[pos]
105 c = program[pos]
106 if c.isspace(): # skip inter-token whitespace
106 if c.isspace(): # skip inter-token whitespace
107 pass
107 pass
108 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
108 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
109 yield ('::', None, pos)
109 yield ('::', None, pos)
110 pos += 1 # skip ahead
110 pos += 1 # skip ahead
111 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
111 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
112 yield ('..', None, pos)
112 yield ('..', None, pos)
113 pos += 1 # skip ahead
113 pos += 1 # skip ahead
114 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
114 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
115 yield ('##', None, pos)
115 yield ('##', None, pos)
116 pos += 1 # skip ahead
116 pos += 1 # skip ahead
117 elif c in _simpleopletters: # handle simple operators
117 elif c in _simpleopletters: # handle simple operators
118 yield (c, None, pos)
118 yield (c, None, pos)
119 elif (c in _quoteletters or c == 'r' and
119 elif (c in _quoteletters or c == 'r' and
120 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
120 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
121 if c == 'r':
121 if c == 'r':
122 pos += 1
122 pos += 1
123 c = program[pos]
123 c = program[pos]
124 decode = lambda x: x
124 decode = lambda x: x
125 else:
125 else:
126 decode = parser.unescapestr
126 decode = parser.unescapestr
127 pos += 1
127 pos += 1
128 s = pos
128 s = pos
129 while pos < l: # find closing quote
129 while pos < l: # find closing quote
130 d = program[pos]
130 d = program[pos]
131 if d == '\\': # skip over escaped characters
131 if d == '\\': # skip over escaped characters
132 pos += 2
132 pos += 2
133 continue
133 continue
134 if d == c:
134 if d == c:
135 yield ('string', decode(program[s:pos]), s)
135 yield ('string', decode(program[s:pos]), s)
136 break
136 break
137 pos += 1
137 pos += 1
138 else:
138 else:
139 raise error.ParseError(_("unterminated string"), s)
139 raise error.ParseError(_("unterminated string"), s)
140 # gather up a symbol/keyword
140 # gather up a symbol/keyword
141 elif c in syminitletters:
141 elif c in syminitletters:
142 s = pos
142 s = pos
143 pos += 1
143 pos += 1
144 while pos < l: # find end of symbol
144 while pos < l: # find end of symbol
145 d = program[pos]
145 d = program[pos]
146 if d not in symletters:
146 if d not in symletters:
147 break
147 break
148 if d == '.' and program[pos - 1] == '.': # special case for ..
148 if d == '.' and program[pos - 1] == '.': # special case for ..
149 pos -= 1
149 pos -= 1
150 break
150 break
151 pos += 1
151 pos += 1
152 sym = program[s:pos]
152 sym = program[s:pos]
153 if sym in keywords: # operator keywords
153 if sym in keywords: # operator keywords
154 yield (sym, None, s)
154 yield (sym, None, s)
155 elif '-' in sym:
155 elif '-' in sym:
156 # some jerk gave us foo-bar-baz, try to check if it's a symbol
156 # some jerk gave us foo-bar-baz, try to check if it's a symbol
157 if lookup and lookup(sym):
157 if lookup and lookup(sym):
158 # looks like a real symbol
158 # looks like a real symbol
159 yield ('symbol', sym, s)
159 yield ('symbol', sym, s)
160 else:
160 else:
161 # looks like an expression
161 # looks like an expression
162 parts = sym.split('-')
162 parts = sym.split('-')
163 for p in parts[:-1]:
163 for p in parts[:-1]:
164 if p: # possible consecutive -
164 if p: # possible consecutive -
165 yield ('symbol', p, s)
165 yield ('symbol', p, s)
166 s += len(p)
166 s += len(p)
167 yield ('-', None, pos)
167 yield ('-', None, pos)
168 s += 1
168 s += 1
169 if parts[-1]: # possible trailing -
169 if parts[-1]: # possible trailing -
170 yield ('symbol', parts[-1], s)
170 yield ('symbol', parts[-1], s)
171 else:
171 else:
172 yield ('symbol', sym, s)
172 yield ('symbol', sym, s)
173 pos -= 1
173 pos -= 1
174 else:
174 else:
175 raise error.ParseError(_("syntax error in revset '%s'") %
175 raise error.ParseError(_("syntax error in revset '%s'") %
176 program, pos)
176 program, pos)
177 pos += 1
177 pos += 1
178 yield ('end', None, pos)
178 yield ('end', None, pos)
179
179
180 # helpers
180 # helpers
181
181
182 _notset = object()
182 _notset = object()
183
183
184 def getsymbol(x):
184 def getsymbol(x):
185 if x and x[0] == 'symbol':
185 if x and x[0] == 'symbol':
186 return x[1]
186 return x[1]
187 raise error.ParseError(_('not a symbol'))
187 raise error.ParseError(_('not a symbol'))
188
188
189 def getstring(x, err):
189 def getstring(x, err):
190 if x and (x[0] == 'string' or x[0] == 'symbol'):
190 if x and (x[0] == 'string' or x[0] == 'symbol'):
191 return x[1]
191 return x[1]
192 raise error.ParseError(err)
192 raise error.ParseError(err)
193
193
194 def getinteger(x, err, default=_notset):
194 def getinteger(x, err, default=_notset):
195 if not x and default is not _notset:
195 if not x and default is not _notset:
196 return default
196 return default
197 try:
197 try:
198 return int(getstring(x, err))
198 return int(getstring(x, err))
199 except ValueError:
199 except ValueError:
200 raise error.ParseError(err)
200 raise error.ParseError(err)
201
201
202 def getboolean(x, err):
202 def getboolean(x, err):
203 value = util.parsebool(getsymbol(x))
203 value = util.parsebool(getsymbol(x))
204 if value is not None:
204 if value is not None:
205 return value
205 return value
206 raise error.ParseError(err)
206 raise error.ParseError(err)
207
207
208 def getlist(x):
208 def getlist(x):
209 if not x:
209 if not x:
210 return []
210 return []
211 if x[0] == 'list':
211 if x[0] == 'list':
212 return list(x[1:])
212 return list(x[1:])
213 return [x]
213 return [x]
214
214
215 def getrange(x, err):
215 def getrange(x, err):
216 if not x:
216 if not x:
217 raise error.ParseError(err)
217 raise error.ParseError(err)
218 op = x[0]
218 op = x[0]
219 if op == 'range':
219 if op == 'range':
220 return x[1], x[2]
220 return x[1], x[2]
221 elif op == 'rangepre':
221 elif op == 'rangepre':
222 return None, x[1]
222 return None, x[1]
223 elif op == 'rangepost':
223 elif op == 'rangepost':
224 return x[1], None
224 return x[1], None
225 elif op == 'rangeall':
225 elif op == 'rangeall':
226 return None, None
226 return None, None
227 raise error.ParseError(err)
227 raise error.ParseError(err)
228
228
229 def getargs(x, min, max, err):
229 def getargs(x, min, max, err):
230 l = getlist(x)
230 l = getlist(x)
231 if len(l) < min or (max >= 0 and len(l) > max):
231 if len(l) < min or (max >= 0 and len(l) > max):
232 raise error.ParseError(err)
232 raise error.ParseError(err)
233 return l
233 return l
234
234
235 def getargsdict(x, funcname, keys):
235 def getargsdict(x, funcname, keys):
236 return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
236 return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
237 keyvaluenode='keyvalue', keynode='symbol')
237 keyvaluenode='keyvalue', keynode='symbol')
238
238
239 def _isnamedfunc(x, funcname):
239 def _isnamedfunc(x, funcname):
240 """Check if given tree matches named function"""
240 """Check if given tree matches named function"""
241 return x and x[0] == 'func' and getsymbol(x[1]) == funcname
241 return x and x[0] == 'func' and getsymbol(x[1]) == funcname
242
242
243 def _isposargs(x, n):
243 def _isposargs(x, n):
244 """Check if given tree is n-length list of positional arguments"""
244 """Check if given tree is n-length list of positional arguments"""
245 l = getlist(x)
245 l = getlist(x)
246 return len(l) == n and all(y and y[0] != 'keyvalue' for y in l)
246 return len(l) == n and all(y and y[0] != 'keyvalue' for y in l)
247
247
248 def _matchnamedfunc(x, funcname):
248 def _matchnamedfunc(x, funcname):
249 """Return args tree if given tree matches named function; otherwise None
249 """Return args tree if given tree matches named function; otherwise None
250
250
251 This can't be used for testing a nullary function since its args tree
251 This can't be used for testing a nullary function since its args tree
252 is also None. Use _isnamedfunc() instead.
252 is also None. Use _isnamedfunc() instead.
253 """
253 """
254 if not _isnamedfunc(x, funcname):
254 if not _isnamedfunc(x, funcname):
255 return
255 return
256 return x[2]
256 return x[2]
257
257
258 # Constants for ordering requirement, used in _analyze():
258 # Constants for ordering requirement, used in _analyze():
259 #
259 #
260 # If 'define', any nested functions and operations can change the ordering of
260 # If 'define', any nested functions and operations can change the ordering of
261 # the entries in the set. If 'follow', any nested functions and operations
261 # the entries in the set. If 'follow', any nested functions and operations
262 # should take the ordering specified by the first operand to the '&' operator.
262 # should take the ordering specified by the first operand to the '&' operator.
263 #
263 #
264 # For instance,
264 # For instance,
265 #
265 #
266 # X & (Y | Z)
266 # X & (Y | Z)
267 # ^ ^^^^^^^
267 # ^ ^^^^^^^
268 # | follow
268 # | follow
269 # define
269 # define
270 #
270 #
271 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
271 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
272 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
272 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
273 #
273 #
274 # 'any' means the order doesn't matter. For instance,
274 # 'any' means the order doesn't matter. For instance,
275 #
275 #
276 # X & !Y
276 # X & !Y
277 # ^
277 # ^
278 # any
278 # any
279 #
279 #
280 # 'y()' can either enforce its ordering requirement or take the ordering
280 # 'y()' can either enforce its ordering requirement or take the ordering
281 # specified by 'x()' because 'not()' doesn't care the order.
281 # specified by 'x()' because 'not()' doesn't care the order.
282 #
282 #
283 # Transition of ordering requirement:
283 # Transition of ordering requirement:
284 #
284 #
285 # 1. starts with 'define'
285 # 1. starts with 'define'
286 # 2. shifts to 'follow' by 'x & y'
286 # 2. shifts to 'follow' by 'x & y'
287 # 3. changes back to 'define' on function call 'f(x)' or function-like
287 # 3. changes back to 'define' on function call 'f(x)' or function-like
288 # operation 'x (f) y' because 'f' may have its own ordering requirement
288 # operation 'x (f) y' because 'f' may have its own ordering requirement
289 # for 'x' and 'y' (e.g. 'first(x)')
289 # for 'x' and 'y' (e.g. 'first(x)')
290 #
290 #
291 anyorder = 'any' # don't care the order
291 anyorder = 'any' # don't care the order
292 defineorder = 'define' # should define the order
292 defineorder = 'define' # should define the order
293 followorder = 'follow' # must follow the current order
293 followorder = 'follow' # must follow the current order
294
294
295 # transition table for 'x & y', from the current expression 'x' to 'y'
295 # transition table for 'x & y', from the current expression 'x' to 'y'
296 _tofolloworder = {
296 _tofolloworder = {
297 anyorder: anyorder,
297 anyorder: anyorder,
298 defineorder: followorder,
298 defineorder: followorder,
299 followorder: followorder,
299 followorder: followorder,
300 }
300 }
301
301
302 def _matchonly(revs, bases):
302 def _matchonly(revs, bases):
303 """
303 """
304 >>> f = lambda *args: _matchonly(*map(parse, args))
304 >>> f = lambda *args: _matchonly(*map(parse, args))
305 >>> f('ancestors(A)', 'not ancestors(B)')
305 >>> f('ancestors(A)', 'not ancestors(B)')
306 ('list', ('symbol', 'A'), ('symbol', 'B'))
306 ('list', ('symbol', 'A'), ('symbol', 'B'))
307 """
307 """
308 ta = _matchnamedfunc(revs, 'ancestors')
308 ta = _matchnamedfunc(revs, 'ancestors')
309 tb = bases and bases[0] == 'not' and _matchnamedfunc(bases[1], 'ancestors')
309 tb = bases and bases[0] == 'not' and _matchnamedfunc(bases[1], 'ancestors')
310 if _isposargs(ta, 1) and _isposargs(tb, 1):
310 if _isposargs(ta, 1) and _isposargs(tb, 1):
311 return ('list', ta, tb)
311 return ('list', ta, tb)
312
312
313 def _fixops(x):
313 def _fixops(x):
314 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
314 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
315 handled well by our simple top-down parser"""
315 handled well by our simple top-down parser"""
316 if not isinstance(x, tuple):
316 if not isinstance(x, tuple):
317 return x
317 return x
318
318
319 op = x[0]
319 op = x[0]
320 if op == 'parent':
320 if op == 'parent':
321 # x^:y means (x^) : y, not x ^ (:y)
321 # x^:y means (x^) : y, not x ^ (:y)
322 # x^: means (x^) :, not x ^ (:)
322 # x^: means (x^) :, not x ^ (:)
323 post = ('parentpost', x[1])
323 post = ('parentpost', x[1])
324 if x[2][0] == 'dagrangepre':
324 if x[2][0] == 'dagrangepre':
325 return _fixops(('dagrange', post, x[2][1]))
325 return _fixops(('dagrange', post, x[2][1]))
326 elif x[2][0] == 'rangepre':
326 elif x[2][0] == 'rangepre':
327 return _fixops(('range', post, x[2][1]))
327 return _fixops(('range', post, x[2][1]))
328 elif x[2][0] == 'rangeall':
328 elif x[2][0] == 'rangeall':
329 return _fixops(('rangepost', post))
329 return _fixops(('rangepost', post))
330 elif op == 'or':
330 elif op == 'or':
331 # make number of arguments deterministic:
331 # make number of arguments deterministic:
332 # x + y + z -> (or x y z) -> (or (list x y z))
332 # x + y + z -> (or x y z) -> (or (list x y z))
333 return (op, _fixops(('list',) + x[1:]))
333 return (op, _fixops(('list',) + x[1:]))
334
334
335 return (op,) + tuple(_fixops(y) for y in x[1:])
335 return (op,) + tuple(_fixops(y) for y in x[1:])
336
336
337 def _analyze(x, order):
337 def _analyze(x, order):
338 if x is None:
338 if x is None:
339 return x
339 return x
340
340
341 op = x[0]
341 op = x[0]
342 if op == 'minus':
342 if op == 'minus':
343 return _analyze(('and', x[1], ('not', x[2])), order)
343 return _analyze(('and', x[1], ('not', x[2])), order)
344 elif op == 'only':
344 elif op == 'only':
345 t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
345 t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
346 return _analyze(t, order)
346 return _analyze(t, order)
347 elif op == 'onlypost':
347 elif op == 'onlypost':
348 return _analyze(('func', ('symbol', 'only'), x[1]), order)
348 return _analyze(('func', ('symbol', 'only'), x[1]), order)
349 elif op == 'dagrangepre':
349 elif op == 'dagrangepre':
350 return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
350 return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
351 elif op == 'dagrangepost':
351 elif op == 'dagrangepost':
352 return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
352 return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
353 elif op == 'negate':
353 elif op == 'negate':
354 s = getstring(x[1], _("can't negate that"))
354 s = getstring(x[1], _("can't negate that"))
355 return _analyze(('string', '-' + s), order)
355 return _analyze(('string', '-' + s), order)
356 elif op in ('string', 'symbol'):
356 elif op in ('string', 'symbol'):
357 return x
357 return x
358 elif op == 'and':
358 elif op == 'and':
359 ta = _analyze(x[1], order)
359 ta = _analyze(x[1], order)
360 tb = _analyze(x[2], _tofolloworder[order])
360 tb = _analyze(x[2], _tofolloworder[order])
361 return (op, ta, tb, order)
361 return (op, ta, tb, order)
362 elif op == 'or':
362 elif op == 'or':
363 return (op, _analyze(x[1], order), order)
363 return (op, _analyze(x[1], order), order)
364 elif op == 'not':
364 elif op == 'not':
365 return (op, _analyze(x[1], anyorder), order)
365 return (op, _analyze(x[1], anyorder), order)
366 elif op == 'rangeall':
366 elif op == 'rangeall':
367 return (op, None, order)
367 return (op, None, order)
368 elif op in ('rangepre', 'rangepost', 'parentpost'):
368 elif op in ('rangepre', 'rangepost', 'parentpost'):
369 return (op, _analyze(x[1], defineorder), order)
369 return (op, _analyze(x[1], defineorder), order)
370 elif op == 'group':
370 elif op == 'group':
371 return _analyze(x[1], order)
371 return _analyze(x[1], order)
372 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
372 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
373 ta = _analyze(x[1], defineorder)
373 ta = _analyze(x[1], defineorder)
374 tb = _analyze(x[2], defineorder)
374 tb = _analyze(x[2], defineorder)
375 return (op, ta, tb, order)
375 return (op, ta, tb, order)
376 elif op == 'list':
376 elif op == 'list':
377 return (op,) + tuple(_analyze(y, order) for y in x[1:])
377 return (op,) + tuple(_analyze(y, order) for y in x[1:])
378 elif op == 'keyvalue':
378 elif op == 'keyvalue':
379 return (op, x[1], _analyze(x[2], order))
379 return (op, x[1], _analyze(x[2], order))
380 elif op == 'func':
380 elif op == 'func':
381 f = getsymbol(x[1])
381 f = getsymbol(x[1])
382 d = defineorder
382 d = defineorder
383 if f == 'present':
383 if f == 'present':
384 # 'present(set)' is known to return the argument set with no
384 # 'present(set)' is known to return the argument set with no
385 # modification, so forward the current order to its argument
385 # modification, so forward the current order to its argument
386 d = order
386 d = order
387 return (op, x[1], _analyze(x[2], d), order)
387 return (op, x[1], _analyze(x[2], d), order)
388 raise ValueError('invalid operator %r' % op)
388 raise ValueError('invalid operator %r' % op)
389
389
390 def analyze(x, order=defineorder):
390 def analyze(x, order=defineorder):
391 """Transform raw parsed tree to evaluatable tree which can be fed to
391 """Transform raw parsed tree to evaluatable tree which can be fed to
392 optimize() or getset()
392 optimize() or getset()
393
393
394 All pseudo operations should be mapped to real operations or functions
394 All pseudo operations should be mapped to real operations or functions
395 defined in methods or symbols table respectively.
395 defined in methods or symbols table respectively.
396
396
397 'order' specifies how the current expression 'x' is ordered (see the
397 'order' specifies how the current expression 'x' is ordered (see the
398 constants defined above.)
398 constants defined above.)
399 """
399 """
400 return _analyze(x, order)
400 return _analyze(x, order)
401
401
402 def _optimize(x, small):
402 def _optimize(x, small):
403 if x is None:
403 if x is None:
404 return 0, x
404 return 0, x
405
405
406 smallbonus = 1
406 smallbonus = 1
407 if small:
407 if small:
408 smallbonus = .5
408 smallbonus = .5
409
409
410 op = x[0]
410 op = x[0]
411 if op in ('string', 'symbol'):
411 if op in ('string', 'symbol'):
412 return smallbonus, x # single revisions are small
412 return smallbonus, x # single revisions are small
413 elif op == 'and':
413 elif op == 'and':
414 wa, ta = _optimize(x[1], True)
414 wa, ta = _optimize(x[1], True)
415 wb, tb = _optimize(x[2], True)
415 wb, tb = _optimize(x[2], True)
416 order = x[3]
416 order = x[3]
417 w = min(wa, wb)
417 w = min(wa, wb)
418
418
419 # (::x and not ::y)/(not ::y and ::x) have a fast path
419 # (::x and not ::y)/(not ::y and ::x) have a fast path
420 tm = _matchonly(ta, tb) or _matchonly(tb, ta)
420 tm = _matchonly(ta, tb) or _matchonly(tb, ta)
421 if tm:
421 if tm:
422 return w, ('func', ('symbol', 'only'), tm, order)
422 return w, ('func', ('symbol', 'only'), tm, order)
423
423
424 if tb is not None and tb[0] == 'not':
424 if tb is not None and tb[0] == 'not':
425 return wa, ('difference', ta, tb[1], order)
425 return wa, ('difference', ta, tb[1], order)
426
426
427 if wa > wb:
427 if wa > wb:
428 return w, (op, tb, ta, order)
428 return w, (op, tb, ta, order)
429 return w, (op, ta, tb, order)
429 return w, (op, ta, tb, order)
430 elif op == 'or':
430 elif op == 'or':
431 # fast path for machine-generated expression, that is likely to have
431 # fast path for machine-generated expression, that is likely to have
432 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
432 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
433 order = x[2]
433 order = x[2]
434 ws, ts, ss = [], [], []
434 ws, ts, ss = [], [], []
435 def flushss():
435 def flushss():
436 if not ss:
436 if not ss:
437 return
437 return
438 if len(ss) == 1:
438 if len(ss) == 1:
439 w, t = ss[0]
439 w, t = ss[0]
440 else:
440 else:
441 s = '\0'.join(t[1] for w, t in ss)
441 s = '\0'.join(t[1] for w, t in ss)
442 y = ('func', ('symbol', '_list'), ('string', s), order)
442 y = ('func', ('symbol', '_list'), ('string', s), order)
443 w, t = _optimize(y, False)
443 w, t = _optimize(y, False)
444 ws.append(w)
444 ws.append(w)
445 ts.append(t)
445 ts.append(t)
446 del ss[:]
446 del ss[:]
447 for y in getlist(x[1]):
447 for y in getlist(x[1]):
448 w, t = _optimize(y, False)
448 w, t = _optimize(y, False)
449 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
449 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
450 ss.append((w, t))
450 ss.append((w, t))
451 continue
451 continue
452 flushss()
452 flushss()
453 ws.append(w)
453 ws.append(w)
454 ts.append(t)
454 ts.append(t)
455 flushss()
455 flushss()
456 if len(ts) == 1:
456 if len(ts) == 1:
457 return ws[0], ts[0] # 'or' operation is fully optimized out
457 return ws[0], ts[0] # 'or' operation is fully optimized out
458 if order != defineorder:
458 if order != defineorder:
459 # reorder by weight only when f(a + b) == f(b + a)
459 # reorder by weight only when f(a + b) == f(b + a)
460 ts = [wt[1] for wt in sorted(zip(ws, ts), key=lambda wt: wt[0])]
460 ts = [wt[1] for wt in sorted(zip(ws, ts), key=lambda wt: wt[0])]
461 return max(ws), (op, ('list',) + tuple(ts), order)
461 return max(ws), (op, ('list',) + tuple(ts), order)
462 elif op == 'not':
462 elif op == 'not':
463 # Optimize not public() to _notpublic() because we have a fast version
463 # Optimize not public() to _notpublic() because we have a fast version
464 if x[1][:3] == ('func', ('symbol', 'public'), None):
464 if x[1][:3] == ('func', ('symbol', 'public'), None):
465 order = x[1][3]
465 order = x[1][3]
466 newsym = ('func', ('symbol', '_notpublic'), None, order)
466 newsym = ('func', ('symbol', '_notpublic'), None, order)
467 o = _optimize(newsym, not small)
467 o = _optimize(newsym, not small)
468 return o[0], o[1]
468 return o[0], o[1]
469 else:
469 else:
470 o = _optimize(x[1], not small)
470 o = _optimize(x[1], not small)
471 order = x[2]
471 order = x[2]
472 return o[0], (op, o[1], order)
472 return o[0], (op, o[1], order)
473 elif op == 'rangeall':
473 elif op == 'rangeall':
474 return smallbonus, x
474 return smallbonus, x
475 elif op in ('rangepre', 'rangepost', 'parentpost'):
475 elif op in ('rangepre', 'rangepost', 'parentpost'):
476 o = _optimize(x[1], small)
476 o = _optimize(x[1], small)
477 order = x[2]
477 order = x[2]
478 return o[0], (op, o[1], order)
478 return o[0], (op, o[1], order)
479 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
479 elif op in ('dagrange', 'range'):
480 wa, ta = _optimize(x[1], small)
480 wa, ta = _optimize(x[1], small)
481 wb, tb = _optimize(x[2], small)
481 wb, tb = _optimize(x[2], small)
482 order = x[3]
482 order = x[3]
483 return wa + wb, (op, ta, tb, order)
483 return wa + wb, (op, ta, tb, order)
484 elif op in ('parent', 'ancestor'):
485 w, t = _optimize(x[1], small)
486 order = x[3]
487 return w, (op, t, x[2], order)
484 elif op == 'list':
488 elif op == 'list':
485 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
489 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
486 return sum(ws), (op,) + ts
490 return sum(ws), (op,) + ts
487 elif op == 'keyvalue':
491 elif op == 'keyvalue':
488 w, t = _optimize(x[2], small)
492 w, t = _optimize(x[2], small)
489 return w, (op, x[1], t)
493 return w, (op, x[1], t)
490 elif op == 'func':
494 elif op == 'func':
491 f = getsymbol(x[1])
495 f = getsymbol(x[1])
492 wa, ta = _optimize(x[2], small)
496 wa, ta = _optimize(x[2], small)
493 if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
497 if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
494 'keyword', 'outgoing', 'user', 'destination'):
498 'keyword', 'outgoing', 'user', 'destination'):
495 w = 10 # slow
499 w = 10 # slow
496 elif f in ('modifies', 'adds', 'removes'):
500 elif f in ('modifies', 'adds', 'removes'):
497 w = 30 # slower
501 w = 30 # slower
498 elif f == "contains":
502 elif f == "contains":
499 w = 100 # very slow
503 w = 100 # very slow
500 elif f == "ancestor":
504 elif f == "ancestor":
501 w = 1 * smallbonus
505 w = 1 * smallbonus
502 elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
506 elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
503 w = 0
507 w = 0
504 elif f == "sort":
508 elif f == "sort":
505 w = 10 # assume most sorts look at changelog
509 w = 10 # assume most sorts look at changelog
506 else:
510 else:
507 w = 1
511 w = 1
508 order = x[3]
512 order = x[3]
509 return w + wa, (op, x[1], ta, order)
513 return w + wa, (op, x[1], ta, order)
510 raise ValueError('invalid operator %r' % op)
514 raise ValueError('invalid operator %r' % op)
511
515
512 def optimize(tree):
516 def optimize(tree):
513 """Optimize evaluatable tree
517 """Optimize evaluatable tree
514
518
515 All pseudo operations should be transformed beforehand.
519 All pseudo operations should be transformed beforehand.
516 """
520 """
517 _weight, newtree = _optimize(tree, small=True)
521 _weight, newtree = _optimize(tree, small=True)
518 return newtree
522 return newtree
519
523
520 # the set of valid characters for the initial letter of symbols in
524 # the set of valid characters for the initial letter of symbols in
521 # alias declarations and definitions
525 # alias declarations and definitions
522 _aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
526 _aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
523
527
524 def _parsewith(spec, lookup=None, syminitletters=None):
528 def _parsewith(spec, lookup=None, syminitletters=None):
525 """Generate a parse tree of given spec with given tokenizing options
529 """Generate a parse tree of given spec with given tokenizing options
526
530
527 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
531 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
528 ('func', ('symbol', 'foo'), ('symbol', '$1'))
532 ('func', ('symbol', 'foo'), ('symbol', '$1'))
529 >>> _parsewith('$1')
533 >>> _parsewith('$1')
530 Traceback (most recent call last):
534 Traceback (most recent call last):
531 ...
535 ...
532 ParseError: ("syntax error in revset '$1'", 0)
536 ParseError: ("syntax error in revset '$1'", 0)
533 >>> _parsewith('foo bar')
537 >>> _parsewith('foo bar')
534 Traceback (most recent call last):
538 Traceback (most recent call last):
535 ...
539 ...
536 ParseError: ('invalid token', 4)
540 ParseError: ('invalid token', 4)
537 """
541 """
538 p = parser.parser(elements)
542 p = parser.parser(elements)
539 tree, pos = p.parse(tokenize(spec, lookup=lookup,
543 tree, pos = p.parse(tokenize(spec, lookup=lookup,
540 syminitletters=syminitletters))
544 syminitletters=syminitletters))
541 if pos != len(spec):
545 if pos != len(spec):
542 raise error.ParseError(_('invalid token'), pos)
546 raise error.ParseError(_('invalid token'), pos)
543 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
547 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
544
548
545 class _aliasrules(parser.basealiasrules):
549 class _aliasrules(parser.basealiasrules):
546 """Parsing and expansion rule set of revset aliases"""
550 """Parsing and expansion rule set of revset aliases"""
547 _section = _('revset alias')
551 _section = _('revset alias')
548
552
549 @staticmethod
553 @staticmethod
550 def _parse(spec):
554 def _parse(spec):
551 """Parse alias declaration/definition ``spec``
555 """Parse alias declaration/definition ``spec``
552
556
553 This allows symbol names to use also ``$`` as an initial letter
557 This allows symbol names to use also ``$`` as an initial letter
554 (for backward compatibility), and callers of this function should
558 (for backward compatibility), and callers of this function should
555 examine whether ``$`` is used also for unexpected symbols or not.
559 examine whether ``$`` is used also for unexpected symbols or not.
556 """
560 """
557 return _parsewith(spec, syminitletters=_aliassyminitletters)
561 return _parsewith(spec, syminitletters=_aliassyminitletters)
558
562
559 @staticmethod
563 @staticmethod
560 def _trygetfunc(tree):
564 def _trygetfunc(tree):
561 if tree[0] == 'func' and tree[1][0] == 'symbol':
565 if tree[0] == 'func' and tree[1][0] == 'symbol':
562 return tree[1][1], getlist(tree[2])
566 return tree[1][1], getlist(tree[2])
563
567
564 def expandaliases(tree, aliases, warn=None):
568 def expandaliases(tree, aliases, warn=None):
565 """Expand aliases in a tree, aliases is a list of (name, value) tuples"""
569 """Expand aliases in a tree, aliases is a list of (name, value) tuples"""
566 aliases = _aliasrules.buildmap(aliases)
570 aliases = _aliasrules.buildmap(aliases)
567 tree = _aliasrules.expand(aliases, tree)
571 tree = _aliasrules.expand(aliases, tree)
568 # warn about problematic (but not referred) aliases
572 # warn about problematic (but not referred) aliases
569 if warn is not None:
573 if warn is not None:
570 for name, alias in sorted(aliases.iteritems()):
574 for name, alias in sorted(aliases.iteritems()):
571 if alias.error and not alias.warned:
575 if alias.error and not alias.warned:
572 warn(_('warning: %s\n') % (alias.error))
576 warn(_('warning: %s\n') % (alias.error))
573 alias.warned = True
577 alias.warned = True
574 return tree
578 return tree
575
579
576 def foldconcat(tree):
580 def foldconcat(tree):
577 """Fold elements to be concatenated by `##`
581 """Fold elements to be concatenated by `##`
578 """
582 """
579 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
583 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
580 return tree
584 return tree
581 if tree[0] == '_concat':
585 if tree[0] == '_concat':
582 pending = [tree]
586 pending = [tree]
583 l = []
587 l = []
584 while pending:
588 while pending:
585 e = pending.pop()
589 e = pending.pop()
586 if e[0] == '_concat':
590 if e[0] == '_concat':
587 pending.extend(reversed(e[1:]))
591 pending.extend(reversed(e[1:]))
588 elif e[0] in ('string', 'symbol'):
592 elif e[0] in ('string', 'symbol'):
589 l.append(e[1])
593 l.append(e[1])
590 else:
594 else:
591 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
595 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
592 raise error.ParseError(msg)
596 raise error.ParseError(msg)
593 return ('string', ''.join(l))
597 return ('string', ''.join(l))
594 else:
598 else:
595 return tuple(foldconcat(t) for t in tree)
599 return tuple(foldconcat(t) for t in tree)
596
600
597 def parse(spec, lookup=None):
601 def parse(spec, lookup=None):
598 return _parsewith(spec, lookup=lookup)
602 return _parsewith(spec, lookup=lookup)
599
603
600 def _quote(s):
604 def _quote(s):
601 r"""Quote a value in order to make it safe for the revset engine.
605 r"""Quote a value in order to make it safe for the revset engine.
602
606
603 >>> _quote('asdf')
607 >>> _quote('asdf')
604 "'asdf'"
608 "'asdf'"
605 >>> _quote("asdf'\"")
609 >>> _quote("asdf'\"")
606 '\'asdf\\\'"\''
610 '\'asdf\\\'"\''
607 >>> _quote('asdf\'')
611 >>> _quote('asdf\'')
608 "'asdf\\''"
612 "'asdf\\''"
609 >>> _quote(1)
613 >>> _quote(1)
610 "'1'"
614 "'1'"
611 """
615 """
612 return "'%s'" % util.escapestr(pycompat.bytestr(s))
616 return "'%s'" % util.escapestr(pycompat.bytestr(s))
613
617
614 def formatspec(expr, *args):
618 def formatspec(expr, *args):
615 '''
619 '''
616 This is a convenience function for using revsets internally, and
620 This is a convenience function for using revsets internally, and
617 escapes arguments appropriately. Aliases are intentionally ignored
621 escapes arguments appropriately. Aliases are intentionally ignored
618 so that intended expression behavior isn't accidentally subverted.
622 so that intended expression behavior isn't accidentally subverted.
619
623
620 Supported arguments:
624 Supported arguments:
621
625
622 %r = revset expression, parenthesized
626 %r = revset expression, parenthesized
623 %d = int(arg), no quoting
627 %d = int(arg), no quoting
624 %s = string(arg), escaped and single-quoted
628 %s = string(arg), escaped and single-quoted
625 %b = arg.branch(), escaped and single-quoted
629 %b = arg.branch(), escaped and single-quoted
626 %n = hex(arg), single-quoted
630 %n = hex(arg), single-quoted
627 %% = a literal '%'
631 %% = a literal '%'
628
632
629 Prefixing the type with 'l' specifies a parenthesized list of that type.
633 Prefixing the type with 'l' specifies a parenthesized list of that type.
630
634
631 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
635 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
632 '(10 or 11):: and ((this()) or (that()))'
636 '(10 or 11):: and ((this()) or (that()))'
633 >>> formatspec('%d:: and not %d::', 10, 20)
637 >>> formatspec('%d:: and not %d::', 10, 20)
634 '10:: and not 20::'
638 '10:: and not 20::'
635 >>> formatspec('%ld or %ld', [], [1])
639 >>> formatspec('%ld or %ld', [], [1])
636 "_list('') or 1"
640 "_list('') or 1"
637 >>> formatspec('keyword(%s)', 'foo\\xe9')
641 >>> formatspec('keyword(%s)', 'foo\\xe9')
638 "keyword('foo\\\\xe9')"
642 "keyword('foo\\\\xe9')"
639 >>> b = lambda: 'default'
643 >>> b = lambda: 'default'
640 >>> b.branch = b
644 >>> b.branch = b
641 >>> formatspec('branch(%b)', b)
645 >>> formatspec('branch(%b)', b)
642 "branch('default')"
646 "branch('default')"
643 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
647 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
644 "root(_list('a\\x00b\\x00c\\x00d'))"
648 "root(_list('a\\x00b\\x00c\\x00d'))"
645 '''
649 '''
646
650
647 def argtype(c, arg):
651 def argtype(c, arg):
648 if c == 'd':
652 if c == 'd':
649 return '%d' % int(arg)
653 return '%d' % int(arg)
650 elif c == 's':
654 elif c == 's':
651 return _quote(arg)
655 return _quote(arg)
652 elif c == 'r':
656 elif c == 'r':
653 parse(arg) # make sure syntax errors are confined
657 parse(arg) # make sure syntax errors are confined
654 return '(%s)' % arg
658 return '(%s)' % arg
655 elif c == 'n':
659 elif c == 'n':
656 return _quote(node.hex(arg))
660 return _quote(node.hex(arg))
657 elif c == 'b':
661 elif c == 'b':
658 return _quote(arg.branch())
662 return _quote(arg.branch())
659
663
660 def listexp(s, t):
664 def listexp(s, t):
661 l = len(s)
665 l = len(s)
662 if l == 0:
666 if l == 0:
663 return "_list('')"
667 return "_list('')"
664 elif l == 1:
668 elif l == 1:
665 return argtype(t, s[0])
669 return argtype(t, s[0])
666 elif t == 'd':
670 elif t == 'd':
667 return "_intlist('%s')" % "\0".join('%d' % int(a) for a in s)
671 return "_intlist('%s')" % "\0".join('%d' % int(a) for a in s)
668 elif t == 's':
672 elif t == 's':
669 return "_list('%s')" % "\0".join(s)
673 return "_list('%s')" % "\0".join(s)
670 elif t == 'n':
674 elif t == 'n':
671 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
675 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
672 elif t == 'b':
676 elif t == 'b':
673 return "_list('%s')" % "\0".join(a.branch() for a in s)
677 return "_list('%s')" % "\0".join(a.branch() for a in s)
674
678
675 m = l // 2
679 m = l // 2
676 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
680 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
677
681
678 expr = pycompat.bytestr(expr)
682 expr = pycompat.bytestr(expr)
679 ret = ''
683 ret = ''
680 pos = 0
684 pos = 0
681 arg = 0
685 arg = 0
682 while pos < len(expr):
686 while pos < len(expr):
683 c = expr[pos]
687 c = expr[pos]
684 if c == '%':
688 if c == '%':
685 pos += 1
689 pos += 1
686 d = expr[pos]
690 d = expr[pos]
687 if d == '%':
691 if d == '%':
688 ret += d
692 ret += d
689 elif d in 'dsnbr':
693 elif d in 'dsnbr':
690 ret += argtype(d, args[arg])
694 ret += argtype(d, args[arg])
691 arg += 1
695 arg += 1
692 elif d == 'l':
696 elif d == 'l':
693 # a list of some type
697 # a list of some type
694 pos += 1
698 pos += 1
695 d = expr[pos]
699 d = expr[pos]
696 ret += listexp(list(args[arg]), d)
700 ret += listexp(list(args[arg]), d)
697 arg += 1
701 arg += 1
698 else:
702 else:
699 raise error.Abort(_('unexpected revspec format character %s')
703 raise error.Abort(_('unexpected revspec format character %s')
700 % d)
704 % d)
701 else:
705 else:
702 ret += c
706 ret += c
703 pos += 1
707 pos += 1
704
708
705 return ret
709 return ret
706
710
707 def prettyformat(tree):
711 def prettyformat(tree):
708 return parser.prettyformat(tree, ('string', 'symbol'))
712 return parser.prettyformat(tree, ('string', 'symbol'))
709
713
710 def depth(tree):
714 def depth(tree):
711 if isinstance(tree, tuple):
715 if isinstance(tree, tuple):
712 return max(map(depth, tree)) + 1
716 return max(map(depth, tree)) + 1
713 else:
717 else:
714 return 0
718 return 0
715
719
716 def funcsused(tree):
720 def funcsused(tree):
717 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
721 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
718 return set()
722 return set()
719 else:
723 else:
720 funcs = set()
724 funcs = set()
721 for s in tree[1:]:
725 for s in tree[1:]:
722 funcs |= funcsused(s)
726 funcs |= funcsused(s)
723 if tree[0] == 'func':
727 if tree[0] == 'func':
724 funcs.add(tree[1][1])
728 funcs.add(tree[1][1])
725 return funcs
729 return funcs
General Comments 0
You need to be logged in to leave comments. Login now