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