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