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