comparison mercurial/revsetlang.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents ddb174511f1b
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
16 parser, 16 parser,
17 pycompat, 17 pycompat,
18 smartset, 18 smartset,
19 util, 19 util,
20 ) 20 )
21 from .utils import ( 21 from .utils import stringutil
22 stringutil,
23 )
24 22
25 elements = { 23 elements = {
26 # token-type: binding-strength, primary, prefix, infix, suffix 24 # token-type: binding-strength, primary, prefix, infix, suffix
27 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None), 25 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
28 "[": (21, None, None, ("subscript", 1, "]"), None), 26 "[": (21, None, None, ("subscript", 1, "]"), None),
29 "#": (21, None, None, ("relation", 21), None), 27 "#": (21, None, None, ("relation", 21), None),
30 "##": (20, None, None, ("_concat", 20), None), 28 "##": (20, None, None, ("_concat", 20), None),
31 "~": (18, None, None, ("ancestor", 18), None), 29 "~": (18, None, None, ("ancestor", 18), None),
32 "^": (18, None, None, ("parent", 18), "parentpost"), 30 "^": (18, None, None, ("parent", 18), "parentpost"),
33 "-": (5, None, ("negate", 19), ("minus", 5), None), 31 "-": (5, None, ("negate", 19), ("minus", 5), None),
34 "::": (17, "dagrangeall", ("dagrangepre", 17), ("dagrange", 17), 32 "::": (
35 "dagrangepost"), 33 17,
36 "..": (17, "dagrangeall", ("dagrangepre", 17), ("dagrange", 17), 34 "dagrangeall",
37 "dagrangepost"), 35 ("dagrangepre", 17),
36 ("dagrange", 17),
37 "dagrangepost",
38 ),
39 "..": (
40 17,
41 "dagrangeall",
42 ("dagrangepre", 17),
43 ("dagrange", 17),
44 "dagrangepost",
45 ),
38 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"), 46 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
39 "not": (10, None, ("not", 10), None, None), 47 "not": (10, None, ("not", 10), None, None),
40 "!": (10, None, ("not", 10), None, None), 48 "!": (10, None, ("not", 10), None, None),
41 "and": (5, None, None, ("and", 5), None), 49 "and": (5, None, None, ("and", 5), None),
42 "&": (5, None, None, ("and", 5), None), 50 "&": (5, None, None, ("and", 5), None),
59 67
60 _quoteletters = {'"', "'"} 68 _quoteletters = {'"', "'"}
61 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%")) 69 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%"))
62 70
63 # default set of valid characters for the initial letter of symbols 71 # default set of valid characters for the initial letter of symbols
64 _syminitletters = set(pycompat.iterbytestr( 72 _syminitletters = set(
65 pycompat.sysbytes(string.ascii_letters) + 73 pycompat.iterbytestr(
66 pycompat.sysbytes(string.digits) + 74 pycompat.sysbytes(string.ascii_letters)
67 '._@')) | set(map(pycompat.bytechr, pycompat.xrange(128, 256))) 75 + pycompat.sysbytes(string.digits)
76 + '._@'
77 )
78 ) | set(map(pycompat.bytechr, pycompat.xrange(128, 256)))
68 79
69 # default set of valid characters for non-initial letters of symbols 80 # default set of valid characters for non-initial letters of symbols
70 _symletters = _syminitletters | set(pycompat.iterbytestr('-/')) 81 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
82
71 83
72 def tokenize(program, lookup=None, syminitletters=None, symletters=None): 84 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
73 ''' 85 '''
74 Parse a revset statement into a stream of tokens 86 Parse a revset statement into a stream of tokens
75 87
89 >>> list(tokenize(b"@::")) 101 >>> list(tokenize(b"@::"))
90 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)] 102 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
91 103
92 ''' 104 '''
93 if not isinstance(program, bytes): 105 if not isinstance(program, bytes):
94 raise error.ProgrammingError('revset statement must be bytes, got %r' 106 raise error.ProgrammingError(
95 % program) 107 'revset statement must be bytes, got %r' % program
108 )
96 program = pycompat.bytestr(program) 109 program = pycompat.bytestr(program)
97 if syminitletters is None: 110 if syminitletters is None:
98 syminitletters = _syminitletters 111 syminitletters = _syminitletters
99 if symletters is None: 112 if symletters is None:
100 symletters = _symletters 113 symletters = _symletters
115 return 128 return
116 129
117 pos, l = 0, len(program) 130 pos, l = 0, len(program)
118 while pos < l: 131 while pos < l:
119 c = program[pos] 132 c = program[pos]
120 if c.isspace(): # skip inter-token whitespace 133 if c.isspace(): # skip inter-token whitespace
121 pass 134 pass
122 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully 135 elif (
136 c == ':' and program[pos : pos + 2] == '::'
137 ): # look ahead carefully
123 yield ('::', None, pos) 138 yield ('::', None, pos)
124 pos += 1 # skip ahead 139 pos += 1 # skip ahead
125 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully 140 elif (
141 c == '.' and program[pos : pos + 2] == '..'
142 ): # look ahead carefully
126 yield ('..', None, pos) 143 yield ('..', None, pos)
127 pos += 1 # skip ahead 144 pos += 1 # skip ahead
128 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully 145 elif (
146 c == '#' and program[pos : pos + 2] == '##'
147 ): # look ahead carefully
129 yield ('##', None, pos) 148 yield ('##', None, pos)
130 pos += 1 # skip ahead 149 pos += 1 # skip ahead
131 elif c in _simpleopletters: # handle simple operators 150 elif c in _simpleopletters: # handle simple operators
132 yield (c, None, pos) 151 yield (c, None, pos)
133 elif (c in _quoteletters or c == 'r' and 152 elif (
134 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings 153 c in _quoteletters
154 or c == 'r'
155 and program[pos : pos + 2] in ("r'", 'r"')
156 ): # handle quoted strings
135 if c == 'r': 157 if c == 'r':
136 pos += 1 158 pos += 1
137 c = program[pos] 159 c = program[pos]
138 decode = lambda x: x 160 decode = lambda x: x
139 else: 161 else:
140 decode = parser.unescapestr 162 decode = parser.unescapestr
141 pos += 1 163 pos += 1
142 s = pos 164 s = pos
143 while pos < l: # find closing quote 165 while pos < l: # find closing quote
144 d = program[pos] 166 d = program[pos]
145 if d == '\\': # skip over escaped characters 167 if d == '\\': # skip over escaped characters
146 pos += 2 168 pos += 2
147 continue 169 continue
148 if d == c: 170 if d == c:
149 yield ('string', decode(program[s:pos]), s) 171 yield ('string', decode(program[s:pos]), s)
150 break 172 break
153 raise error.ParseError(_("unterminated string"), s) 175 raise error.ParseError(_("unterminated string"), s)
154 # gather up a symbol/keyword 176 # gather up a symbol/keyword
155 elif c in syminitletters: 177 elif c in syminitletters:
156 s = pos 178 s = pos
157 pos += 1 179 pos += 1
158 while pos < l: # find end of symbol 180 while pos < l: # find end of symbol
159 d = program[pos] 181 d = program[pos]
160 if d not in symletters: 182 if d not in symletters:
161 break 183 break
162 if d == '.' and program[pos - 1] == '.': # special case for .. 184 if d == '.' and program[pos - 1] == '.': # special case for ..
163 pos -= 1 185 pos -= 1
164 break 186 break
165 pos += 1 187 pos += 1
166 sym = program[s:pos] 188 sym = program[s:pos]
167 if sym in keywords: # operator keywords 189 if sym in keywords: # operator keywords
168 yield (sym, None, s) 190 yield (sym, None, s)
169 elif '-' in sym: 191 elif '-' in sym:
170 # some jerk gave us foo-bar-baz, try to check if it's a symbol 192 # some jerk gave us foo-bar-baz, try to check if it's a symbol
171 if lookup and lookup(sym): 193 if lookup and lookup(sym):
172 # looks like a real symbol 194 # looks like a real symbol
173 yield ('symbol', sym, s) 195 yield ('symbol', sym, s)
174 else: 196 else:
175 # looks like an expression 197 # looks like an expression
176 parts = sym.split('-') 198 parts = sym.split('-')
177 for p in parts[:-1]: 199 for p in parts[:-1]:
178 if p: # possible consecutive - 200 if p: # possible consecutive -
179 yield ('symbol', p, s) 201 yield ('symbol', p, s)
180 s += len(p) 202 s += len(p)
181 yield ('-', None, s) 203 yield ('-', None, s)
182 s += 1 204 s += 1
183 if parts[-1]: # possible trailing - 205 if parts[-1]: # possible trailing -
184 yield ('symbol', parts[-1], s) 206 yield ('symbol', parts[-1], s)
185 else: 207 else:
186 yield ('symbol', sym, s) 208 yield ('symbol', sym, s)
187 pos -= 1 209 pos -= 1
188 else: 210 else:
189 raise error.ParseError(_("syntax error in revset '%s'") % 211 raise error.ParseError(
190 program, pos) 212 _("syntax error in revset '%s'") % program, pos
213 )
191 pos += 1 214 pos += 1
192 yield ('end', None, pos) 215 yield ('end', None, pos)
193 216
217
194 # helpers 218 # helpers
195 219
196 _notset = object() 220 _notset = object()
221
197 222
198 def getsymbol(x): 223 def getsymbol(x):
199 if x and x[0] == 'symbol': 224 if x and x[0] == 'symbol':
200 return x[1] 225 return x[1]
201 raise error.ParseError(_('not a symbol')) 226 raise error.ParseError(_('not a symbol'))
202 227
228
203 def getstring(x, err): 229 def getstring(x, err):
204 if x and (x[0] == 'string' or x[0] == 'symbol'): 230 if x and (x[0] == 'string' or x[0] == 'symbol'):
205 return x[1] 231 return x[1]
206 raise error.ParseError(err) 232 raise error.ParseError(err)
233
207 234
208 def getinteger(x, err, default=_notset): 235 def getinteger(x, err, default=_notset):
209 if not x and default is not _notset: 236 if not x and default is not _notset:
210 return default 237 return default
211 try: 238 try:
212 return int(getstring(x, err)) 239 return int(getstring(x, err))
213 except ValueError: 240 except ValueError:
214 raise error.ParseError(err) 241 raise error.ParseError(err)
215 242
243
216 def getboolean(x, err): 244 def getboolean(x, err):
217 value = stringutil.parsebool(getsymbol(x)) 245 value = stringutil.parsebool(getsymbol(x))
218 if value is not None: 246 if value is not None:
219 return value 247 return value
220 raise error.ParseError(err) 248 raise error.ParseError(err)
249
221 250
222 def getlist(x): 251 def getlist(x):
223 if not x: 252 if not x:
224 return [] 253 return []
225 if x[0] == 'list': 254 if x[0] == 'list':
226 return list(x[1:]) 255 return list(x[1:])
227 return [x] 256 return [x]
257
228 258
229 def getrange(x, err): 259 def getrange(x, err):
230 if not x: 260 if not x:
231 raise error.ParseError(err) 261 raise error.ParseError(err)
232 op = x[0] 262 op = x[0]
238 return x[1], None 268 return x[1], None
239 elif op == 'rangeall': 269 elif op == 'rangeall':
240 return None, None 270 return None, None
241 raise error.ParseError(err) 271 raise error.ParseError(err)
242 272
273
243 def getintrange(x, err1, err2, deffirst=_notset, deflast=_notset): 274 def getintrange(x, err1, err2, deffirst=_notset, deflast=_notset):
244 """Get [first, last] integer range (both inclusive) from a parsed tree 275 """Get [first, last] integer range (both inclusive) from a parsed tree
245 276
246 If any of the sides omitted, and if no default provided, ParseError will 277 If any of the sides omitted, and if no default provided, ParseError will
247 be raised. 278 be raised.
250 n = getinteger(x, err1) 281 n = getinteger(x, err1)
251 return n, n 282 return n, n
252 a, b = getrange(x, err1) 283 a, b = getrange(x, err1)
253 return getinteger(a, err2, deffirst), getinteger(b, err2, deflast) 284 return getinteger(a, err2, deffirst), getinteger(b, err2, deflast)
254 285
286
255 def getargs(x, min, max, err): 287 def getargs(x, min, max, err):
256 l = getlist(x) 288 l = getlist(x)
257 if len(l) < min or (max >= 0 and len(l) > max): 289 if len(l) < min or (max >= 0 and len(l) > max):
258 raise error.ParseError(err) 290 raise error.ParseError(err)
259 return l 291 return l
260 292
293
261 def getargsdict(x, funcname, keys): 294 def getargsdict(x, funcname, keys):
262 return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys), 295 return parser.buildargsdict(
263 keyvaluenode='keyvalue', keynode='symbol') 296 getlist(x),
297 funcname,
298 parser.splitargspec(keys),
299 keyvaluenode='keyvalue',
300 keynode='symbol',
301 )
302
264 303
265 # cache of {spec: raw parsed tree} built internally 304 # cache of {spec: raw parsed tree} built internally
266 _treecache = {} 305 _treecache = {}
306
267 307
268 def _cachedtree(spec): 308 def _cachedtree(spec):
269 # thread safe because parse() is reentrant and dict.__setitem__() is atomic 309 # thread safe because parse() is reentrant and dict.__setitem__() is atomic
270 tree = _treecache.get(spec) 310 tree = _treecache.get(spec)
271 if tree is None: 311 if tree is None:
272 _treecache[spec] = tree = parse(spec) 312 _treecache[spec] = tree = parse(spec)
273 return tree 313 return tree
274 314
315
275 def _build(tmplspec, *repls): 316 def _build(tmplspec, *repls):
276 """Create raw parsed tree from a template revset statement 317 """Create raw parsed tree from a template revset statement
277 318
278 >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2')) 319 >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2'))
279 ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2')) 320 ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
280 """ 321 """
281 template = _cachedtree(tmplspec) 322 template = _cachedtree(tmplspec)
282 return parser.buildtree(template, ('symbol', '_'), *repls) 323 return parser.buildtree(template, ('symbol', '_'), *repls)
324
283 325
284 def _match(patspec, tree): 326 def _match(patspec, tree):
285 """Test if a tree matches the given pattern statement; return the matches 327 """Test if a tree matches the given pattern statement; return the matches
286 328
287 >>> _match(b'f(_)', parse(b'f()')) 329 >>> _match(b'f(_)', parse(b'f()'))
288 >>> _match(b'f(_)', parse(b'f(1)')) 330 >>> _match(b'f(_)', parse(b'f(1)'))
289 [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')] 331 [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
290 >>> _match(b'f(_)', parse(b'f(1, 2)')) 332 >>> _match(b'f(_)', parse(b'f(1, 2)'))
291 """ 333 """
292 pattern = _cachedtree(patspec) 334 pattern = _cachedtree(patspec)
293 return parser.matchtree(pattern, tree, ('symbol', '_'), 335 return parser.matchtree(
294 {'keyvalue', 'list'}) 336 pattern, tree, ('symbol', '_'), {'keyvalue', 'list'}
337 )
338
295 339
296 def _matchonly(revs, bases): 340 def _matchonly(revs, bases):
297 return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases)) 341 return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
342
298 343
299 def _fixops(x): 344 def _fixops(x):
300 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be 345 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
301 handled well by our simple top-down parser""" 346 handled well by our simple top-down parser"""
302 if not isinstance(x, tuple): 347 if not isinstance(x, tuple):
322 elif op == 'subscript' and x[1][0] == 'relation': 367 elif op == 'subscript' and x[1][0] == 'relation':
323 # x#y[z] ternary 368 # x#y[z] ternary
324 return _fixops(('relsubscript', x[1][1], x[1][2], x[2])) 369 return _fixops(('relsubscript', x[1][1], x[1][2], x[2]))
325 370
326 return (op,) + tuple(_fixops(y) for y in x[1:]) 371 return (op,) + tuple(_fixops(y) for y in x[1:])
372
327 373
328 def _analyze(x): 374 def _analyze(x):
329 if x is None: 375 if x is None:
330 return x 376 return x
331 377
351 return (op, None) 397 return (op, None)
352 elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}: 398 elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}:
353 return (op, _analyze(x[1])) 399 return (op, _analyze(x[1]))
354 elif op == 'group': 400 elif op == 'group':
355 return _analyze(x[1]) 401 return _analyze(x[1])
356 elif op in {'and', 'dagrange', 'range', 'parent', 'ancestor', 'relation', 402 elif op in {
357 'subscript'}: 403 'and',
404 'dagrange',
405 'range',
406 'parent',
407 'ancestor',
408 'relation',
409 'subscript',
410 }:
358 ta = _analyze(x[1]) 411 ta = _analyze(x[1])
359 tb = _analyze(x[2]) 412 tb = _analyze(x[2])
360 return (op, ta, tb) 413 return (op, ta, tb)
361 elif op == 'relsubscript': 414 elif op == 'relsubscript':
362 ta = _analyze(x[1]) 415 ta = _analyze(x[1])
369 return (op, x[1], _analyze(x[2])) 422 return (op, x[1], _analyze(x[2]))
370 elif op == 'func': 423 elif op == 'func':
371 return (op, x[1], _analyze(x[2])) 424 return (op, x[1], _analyze(x[2]))
372 raise ValueError('invalid operator %r' % op) 425 raise ValueError('invalid operator %r' % op)
373 426
427
374 def analyze(x): 428 def analyze(x):
375 """Transform raw parsed tree to evaluatable tree which can be fed to 429 """Transform raw parsed tree to evaluatable tree which can be fed to
376 optimize() or getset() 430 optimize() or getset()
377 431
378 All pseudo operations should be mapped to real operations or functions 432 All pseudo operations should be mapped to real operations or functions
379 defined in methods or symbols table respectively. 433 defined in methods or symbols table respectively.
380 """ 434 """
381 return _analyze(x) 435 return _analyze(x)
382 436
437
383 def _optimize(x): 438 def _optimize(x):
384 if x is None: 439 if x is None:
385 return 0, x 440 return 0, x
386 441
387 op = x[0] 442 op = x[0]
388 if op in ('string', 'symbol', 'smartset'): 443 if op in ('string', 'symbol', 'smartset'):
389 return 0.5, x # single revisions are small 444 return 0.5, x # single revisions are small
390 elif op == 'and': 445 elif op == 'and':
391 wa, ta = _optimize(x[1]) 446 wa, ta = _optimize(x[1])
392 wb, tb = _optimize(x[2]) 447 wb, tb = _optimize(x[2])
393 w = min(wa, wb) 448 w = min(wa, wb)
394 449
410 return w, (op, ta, tb) 465 return w, (op, ta, tb)
411 elif op == 'or': 466 elif op == 'or':
412 # fast path for machine-generated expression, that is likely to have 467 # fast path for machine-generated expression, that is likely to have
413 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()' 468 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
414 ws, ts, ss = [], [], [] 469 ws, ts, ss = [], [], []
470
415 def flushss(): 471 def flushss():
416 if not ss: 472 if not ss:
417 return 473 return
418 if len(ss) == 1: 474 if len(ss) == 1:
419 w, t = ss[0] 475 w, t = ss[0]
422 y = _build('_list(_)', ('string', s)) 478 y = _build('_list(_)', ('string', s))
423 w, t = _optimize(y) 479 w, t = _optimize(y)
424 ws.append(w) 480 ws.append(w)
425 ts.append(t) 481 ts.append(t)
426 del ss[:] 482 del ss[:]
483
427 for y in getlist(x[1]): 484 for y in getlist(x[1]):
428 w, t = _optimize(y) 485 w, t = _optimize(y)
429 if t is not None and (t[0] == 'string' or t[0] == 'symbol'): 486 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
430 ss.append((w, t)) 487 ss.append((w, t))
431 continue 488 continue
432 flushss() 489 flushss()
433 ws.append(w) 490 ws.append(w)
434 ts.append(t) 491 ts.append(t)
435 flushss() 492 flushss()
436 if len(ts) == 1: 493 if len(ts) == 1:
437 return ws[0], ts[0] # 'or' operation is fully optimized out 494 return ws[0], ts[0] # 'or' operation is fully optimized out
438 return max(ws), (op, ('list',) + tuple(ts)) 495 return max(ws), (op, ('list',) + tuple(ts))
439 elif op == 'not': 496 elif op == 'not':
440 # Optimize not public() to _notpublic() because we have a fast version 497 # Optimize not public() to _notpublic() because we have a fast version
441 if _match('public()', x[1]): 498 if _match('public()', x[1]):
442 o = _optimize(_build('_notpublic()')) 499 o = _optimize(_build('_notpublic()'))
476 return w + wa, _build('_commonancestorheads(_)', m[1]) 533 return w + wa, _build('_commonancestorheads(_)', m[1])
477 534
478 return w + wa, (op, x[1], ta) 535 return w + wa, (op, x[1], ta)
479 raise ValueError('invalid operator %r' % op) 536 raise ValueError('invalid operator %r' % op)
480 537
538
481 def optimize(tree): 539 def optimize(tree):
482 """Optimize evaluatable tree 540 """Optimize evaluatable tree
483 541
484 All pseudo operations should be transformed beforehand. 542 All pseudo operations should be transformed beforehand.
485 """ 543 """
486 _weight, newtree = _optimize(tree) 544 _weight, newtree = _optimize(tree)
487 return newtree 545 return newtree
488 546
547
489 # the set of valid characters for the initial letter of symbols in 548 # the set of valid characters for the initial letter of symbols in
490 # alias declarations and definitions 549 # alias declarations and definitions
491 _aliassyminitletters = _syminitletters | {'$'} 550 _aliassyminitletters = _syminitletters | {'$'}
551
492 552
493 def _parsewith(spec, lookup=None, syminitletters=None): 553 def _parsewith(spec, lookup=None, syminitletters=None):
494 """Generate a parse tree of given spec with given tokenizing options 554 """Generate a parse tree of given spec with given tokenizing options
495 555
496 >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters) 556 >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters)
505 ParseError: ('invalid token', 4) 565 ParseError: ('invalid token', 4)
506 """ 566 """
507 if lookup and spec.startswith('revset(') and spec.endswith(')'): 567 if lookup and spec.startswith('revset(') and spec.endswith(')'):
508 lookup = None 568 lookup = None
509 p = parser.parser(elements) 569 p = parser.parser(elements)
510 tree, pos = p.parse(tokenize(spec, lookup=lookup, 570 tree, pos = p.parse(
511 syminitletters=syminitletters)) 571 tokenize(spec, lookup=lookup, syminitletters=syminitletters)
572 )
512 if pos != len(spec): 573 if pos != len(spec):
513 raise error.ParseError(_('invalid token'), pos) 574 raise error.ParseError(_('invalid token'), pos)
514 return _fixops(parser.simplifyinfixops(tree, ('list', 'or'))) 575 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
515 576
577
516 class _aliasrules(parser.basealiasrules): 578 class _aliasrules(parser.basealiasrules):
517 """Parsing and expansion rule set of revset aliases""" 579 """Parsing and expansion rule set of revset aliases"""
580
518 _section = _('revset alias') 581 _section = _('revset alias')
519 582
520 @staticmethod 583 @staticmethod
521 def _parse(spec): 584 def _parse(spec):
522 """Parse alias declaration/definition ``spec`` 585 """Parse alias declaration/definition ``spec``
529 592
530 @staticmethod 593 @staticmethod
531 def _trygetfunc(tree): 594 def _trygetfunc(tree):
532 if tree[0] == 'func' and tree[1][0] == 'symbol': 595 if tree[0] == 'func' and tree[1][0] == 'symbol':
533 return tree[1][1], getlist(tree[2]) 596 return tree[1][1], getlist(tree[2])
597
534 598
535 def expandaliases(tree, aliases, warn=None): 599 def expandaliases(tree, aliases, warn=None):
536 """Expand aliases in a tree, aliases is a list of (name, value) tuples""" 600 """Expand aliases in a tree, aliases is a list of (name, value) tuples"""
537 aliases = _aliasrules.buildmap(aliases) 601 aliases = _aliasrules.buildmap(aliases)
538 tree = _aliasrules.expand(aliases, tree) 602 tree = _aliasrules.expand(aliases, tree)
542 if alias.error and not alias.warned: 606 if alias.error and not alias.warned:
543 warn(_('warning: %s\n') % (alias.error)) 607 warn(_('warning: %s\n') % (alias.error))
544 alias.warned = True 608 alias.warned = True
545 return tree 609 return tree
546 610
611
547 def foldconcat(tree): 612 def foldconcat(tree):
548 """Fold elements to be concatenated by `##` 613 """Fold elements to be concatenated by `##`
549 """ 614 """
550 if (not isinstance(tree, tuple) 615 if not isinstance(tree, tuple) or tree[0] in (
551 or tree[0] in ('string', 'symbol', 'smartset')): 616 'string',
617 'symbol',
618 'smartset',
619 ):
552 return tree 620 return tree
553 if tree[0] == '_concat': 621 if tree[0] == '_concat':
554 pending = [tree] 622 pending = [tree]
555 l = [] 623 l = []
556 while pending: 624 while pending:
564 raise error.ParseError(msg) 632 raise error.ParseError(msg)
565 return ('string', ''.join(l)) 633 return ('string', ''.join(l))
566 else: 634 else:
567 return tuple(foldconcat(t) for t in tree) 635 return tuple(foldconcat(t) for t in tree)
568 636
637
569 def parse(spec, lookup=None): 638 def parse(spec, lookup=None):
570 try: 639 try:
571 return _parsewith(spec, lookup=lookup) 640 return _parsewith(spec, lookup=lookup)
572 except error.ParseError as inst: 641 except error.ParseError as inst:
573 if len(inst.args) > 1: # has location 642 if len(inst.args) > 1: # has location
579 # start. Therefore, we print "loc + 1" spaces (instead of "loc") 648 # start. Therefore, we print "loc + 1" spaces (instead of "loc")
580 # to line up the caret with the location of the error. 649 # to line up the caret with the location of the error.
581 inst.hint = spec + '\n' + ' ' * (loc + 1) + '^ ' + _('here') 650 inst.hint = spec + '\n' + ' ' * (loc + 1) + '^ ' + _('here')
582 raise 651 raise
583 652
653
584 def _quote(s): 654 def _quote(s):
585 r"""Quote a value in order to make it safe for the revset engine. 655 r"""Quote a value in order to make it safe for the revset engine.
586 656
587 >>> _quote(b'asdf') 657 >>> _quote(b'asdf')
588 "'asdf'" 658 "'asdf'"
593 >>> _quote(1) 663 >>> _quote(1)
594 "'1'" 664 "'1'"
595 """ 665 """
596 return "'%s'" % stringutil.escapestr(pycompat.bytestr(s)) 666 return "'%s'" % stringutil.escapestr(pycompat.bytestr(s))
597 667
668
598 def _formatargtype(c, arg): 669 def _formatargtype(c, arg):
599 if c == 'd': 670 if c == 'd':
600 return '_rev(%d)' % int(arg) 671 return '_rev(%d)' % int(arg)
601 elif c == 's': 672 elif c == 's':
602 return _quote(arg) 673 return _quote(arg)
603 elif c == 'r': 674 elif c == 'r':
604 if not isinstance(arg, bytes): 675 if not isinstance(arg, bytes):
605 raise TypeError 676 raise TypeError
606 parse(arg) # make sure syntax errors are confined 677 parse(arg) # make sure syntax errors are confined
607 return '(%s)' % arg 678 return '(%s)' % arg
608 elif c == 'n': 679 elif c == 'n':
609 return _quote(node.hex(arg)) 680 return _quote(node.hex(arg))
610 elif c == 'b': 681 elif c == 'b':
611 try: 682 try:
612 return _quote(arg.branch()) 683 return _quote(arg.branch())
613 except AttributeError: 684 except AttributeError:
614 raise TypeError 685 raise TypeError
615 raise error.ParseError(_('unexpected revspec format character %s') % c) 686 raise error.ParseError(_('unexpected revspec format character %s') % c)
687
616 688
617 def _formatlistexp(s, t): 689 def _formatlistexp(s, t):
618 l = len(s) 690 l = len(s)
619 if l == 0: 691 if l == 0:
620 return "_list('')" 692 return "_list('')"
633 raise TypeError 705 raise TypeError
634 706
635 m = l // 2 707 m = l // 2
636 return '(%s or %s)' % (_formatlistexp(s[:m], t), _formatlistexp(s[m:], t)) 708 return '(%s or %s)' % (_formatlistexp(s[:m], t), _formatlistexp(s[m:], t))
637 709
710
638 def _formatintlist(data): 711 def _formatintlist(data):
639 try: 712 try:
640 l = len(data) 713 l = len(data)
641 if l == 0: 714 if l == 0:
642 return "_list('')" 715 return "_list('')"
644 return _formatargtype('d', data[0]) 717 return _formatargtype('d', data[0])
645 return "_intlist('%s')" % "\0".join('%d' % int(a) for a in data) 718 return "_intlist('%s')" % "\0".join('%d' % int(a) for a in data)
646 except (TypeError, ValueError): 719 except (TypeError, ValueError):
647 raise error.ParseError(_('invalid argument for revspec')) 720 raise error.ParseError(_('invalid argument for revspec'))
648 721
722
649 def _formatparamexp(args, t): 723 def _formatparamexp(args, t):
650 return ', '.join(_formatargtype(t, a) for a in args) 724 return ', '.join(_formatargtype(t, a) for a in args)
725
651 726
652 _formatlistfuncs = { 727 _formatlistfuncs = {
653 'l': _formatlistexp, 728 'l': _formatlistexp,
654 'p': _formatparamexp, 729 'p': _formatparamexp,
655 } 730 }
731
656 732
657 def formatspec(expr, *args): 733 def formatspec(expr, *args):
658 ''' 734 '''
659 This is a convenience function for using revsets internally, and 735 This is a convenience function for using revsets internally, and
660 escapes arguments appropriately. Aliases are intentionally ignored 736 escapes arguments appropriately. Aliases are intentionally ignored
702 ret.append(_formatintlist(list(arg))) 778 ret.append(_formatintlist(list(arg)))
703 else: 779 else:
704 raise error.ProgrammingError("unknown revspec item type: %r" % t) 780 raise error.ProgrammingError("unknown revspec item type: %r" % t)
705 return b''.join(ret) 781 return b''.join(ret)
706 782
783
707 def spectree(expr, *args): 784 def spectree(expr, *args):
708 """similar to formatspec but return a parsed and optimized tree""" 785 """similar to formatspec but return a parsed and optimized tree"""
709 parsed = _parseargs(expr, args) 786 parsed = _parseargs(expr, args)
710 ret = [] 787 ret = []
711 inputs = [] 788 inputs = []
723 tree = parser.buildtree(tree, ('symbol', '$'), *inputs) 800 tree = parser.buildtree(tree, ('symbol', '$'), *inputs)
724 tree = foldconcat(tree) 801 tree = foldconcat(tree)
725 tree = analyze(tree) 802 tree = analyze(tree)
726 tree = optimize(tree) 803 tree = optimize(tree)
727 return tree 804 return tree
805
728 806
729 def _parseargs(expr, args): 807 def _parseargs(expr, args):
730 """parse the expression and replace all inexpensive args 808 """parse the expression and replace all inexpensive args
731 809
732 return a list of tuple [(arg-type, arg-value)] 810 return a list of tuple [(arg-type, arg-value)]
761 raise error.ParseError(_('missing argument for revspec')) 839 raise error.ParseError(_('missing argument for revspec'))
762 f = _formatlistfuncs.get(d) 840 f = _formatlistfuncs.get(d)
763 if f: 841 if f:
764 # a list of some type, might be expensive, do not replace 842 # a list of some type, might be expensive, do not replace
765 pos += 1 843 pos += 1
766 islist = (d == 'l') 844 islist = d == 'l'
767 try: 845 try:
768 d = expr[pos] 846 d = expr[pos]
769 except IndexError: 847 except IndexError:
770 raise error.ParseError(_('incomplete revspec format character')) 848 raise error.ParseError(_('incomplete revspec format character'))
771 if islist and d == 'd' and arg: 849 if islist and d == 'd' and arg:
792 raise error.ParseError(_('too many revspec arguments specified')) 870 raise error.ParseError(_('too many revspec arguments specified'))
793 except StopIteration: 871 except StopIteration:
794 pass 872 pass
795 return ret 873 return ret
796 874
875
797 def prettyformat(tree): 876 def prettyformat(tree):
798 return parser.prettyformat(tree, ('string', 'symbol')) 877 return parser.prettyformat(tree, ('string', 'symbol'))
878
799 879
800 def depth(tree): 880 def depth(tree):
801 if isinstance(tree, tuple): 881 if isinstance(tree, tuple):
802 return max(map(depth, tree)) + 1 882 return max(map(depth, tree)) + 1
803 else: 883 else:
804 return 0 884 return 0
885
805 886
806 def funcsused(tree): 887 def funcsused(tree):
807 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'): 888 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
808 return set() 889 return set()
809 else: 890 else:
812 funcs |= funcsused(s) 893 funcs |= funcsused(s)
813 if tree[0] == 'func': 894 if tree[0] == 'func':
814 funcs.add(tree[1][1]) 895 funcs.add(tree[1][1])
815 return funcs 896 return funcs
816 897
898
817 _hashre = util.re.compile('[0-9a-fA-F]{1,40}$') 899 _hashre = util.re.compile('[0-9a-fA-F]{1,40}$')
900
818 901
819 def _ishashlikesymbol(symbol): 902 def _ishashlikesymbol(symbol):
820 """returns true if the symbol looks like a hash""" 903 """returns true if the symbol looks like a hash"""
821 return _hashre.match(symbol) 904 return _hashre.match(symbol)
905
822 906
823 def gethashlikesymbols(tree): 907 def gethashlikesymbols(tree):
824 """returns the list of symbols of the tree that look like hashes 908 """returns the list of symbols of the tree that look like hashes
825 909
826 >>> gethashlikesymbols(parse(b'3::abe3ff')) 910 >>> gethashlikesymbols(parse(b'3::abe3ff'))