comparison mercurial/parser.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 29798c9ba5c9
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
22 from . import ( 22 from . import (
23 error, 23 error,
24 pycompat, 24 pycompat,
25 util, 25 util,
26 ) 26 )
27 from .utils import ( 27 from .utils import stringutil
28 stringutil, 28
29 )
30 29
31 class parser(object): 30 class parser(object):
32 def __init__(self, elements, methods=None): 31 def __init__(self, elements, methods=None):
33 self._elements = elements 32 self._elements = elements
34 self._methods = methods 33 self._methods = methods
35 self.current = None 34 self.current = None
35
36 def _advance(self): 36 def _advance(self):
37 'advance the tokenizer' 37 'advance the tokenizer'
38 t = self.current 38 t = self.current
39 self.current = next(self._iter, None) 39 self.current = next(self._iter, None)
40 return t 40 return t
41
41 def _hasnewterm(self): 42 def _hasnewterm(self):
42 'True if next token may start new term' 43 'True if next token may start new term'
43 return any(self._elements[self.current[0]][1:3]) 44 return any(self._elements[self.current[0]][1:3])
45
44 def _match(self, m): 46 def _match(self, m):
45 'make sure the tokenizer matches an end condition' 47 'make sure the tokenizer matches an end condition'
46 if self.current[0] != m: 48 if self.current[0] != m:
47 raise error.ParseError(_("unexpected token: %s") % self.current[0], 49 raise error.ParseError(
48 self.current[2]) 50 _("unexpected token: %s") % self.current[0], self.current[2]
51 )
49 self._advance() 52 self._advance()
53
50 def _parseoperand(self, bind, m=None): 54 def _parseoperand(self, bind, m=None):
51 'gather right-hand-side operand until an end condition or binding met' 55 'gather right-hand-side operand until an end condition or binding met'
52 if m and self.current[0] == m: 56 if m and self.current[0] == m:
53 expr = None 57 expr = None
54 else: 58 else:
55 expr = self._parse(bind) 59 expr = self._parse(bind)
56 if m: 60 if m:
57 self._match(m) 61 self._match(m)
58 return expr 62 return expr
63
59 def _parse(self, bind=0): 64 def _parse(self, bind=0):
60 token, value, pos = self._advance() 65 token, value, pos = self._advance()
61 # handle prefix rules on current token, take as primary if unambiguous 66 # handle prefix rules on current token, take as primary if unambiguous
62 primary, prefix = self._elements[token][1:3] 67 primary, prefix = self._elements[token][1:3]
63 if primary and not (prefix and self._hasnewterm()): 68 if primary and not (prefix and self._hasnewterm()):
76 elif infix: 81 elif infix:
77 expr = (infix[0], expr, self._parseoperand(*infix[1:])) 82 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
78 else: 83 else:
79 raise error.ParseError(_("not an infix: %s") % token, pos) 84 raise error.ParseError(_("not an infix: %s") % token, pos)
80 return expr 85 return expr
86
81 def parse(self, tokeniter): 87 def parse(self, tokeniter):
82 'generate a parse tree from tokens' 88 'generate a parse tree from tokens'
83 self._iter = tokeniter 89 self._iter = tokeniter
84 self._advance() 90 self._advance()
85 res = self._parse() 91 res = self._parse()
86 token, value, pos = self.current 92 token, value, pos = self.current
87 return res, pos 93 return res, pos
94
88 def eval(self, tree): 95 def eval(self, tree):
89 'recursively evaluate a parse tree using node methods' 96 'recursively evaluate a parse tree using node methods'
90 if not isinstance(tree, tuple): 97 if not isinstance(tree, tuple):
91 return tree 98 return tree
92 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]]) 99 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
100
93 def __call__(self, tokeniter): 101 def __call__(self, tokeniter):
94 'parse tokens into a parse tree and evaluate if methods given' 102 'parse tokens into a parse tree and evaluate if methods given'
95 t = self.parse(tokeniter) 103 t = self.parse(tokeniter)
96 if self._methods: 104 if self._methods:
97 return self.eval(t) 105 return self.eval(t)
98 return t 106 return t
107
99 108
100 def splitargspec(spec): 109 def splitargspec(spec):
101 """Parse spec of function arguments into (poskeys, varkey, keys, optkey) 110 """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
102 111
103 >>> splitargspec(b'') 112 >>> splitargspec(b'')
128 if not posts: 137 if not posts:
129 raise error.ProgrammingError('no *varkey name provided') 138 raise error.ProgrammingError('no *varkey name provided')
130 return pres, posts[0], posts[1:], optkey 139 return pres, posts[0], posts[1:], optkey
131 return [], None, pres, optkey 140 return [], None, pres, optkey
132 141
142
133 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode): 143 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
134 """Build dict from list containing positional and keyword arguments 144 """Build dict from list containing positional and keyword arguments
135 145
136 Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)`` 146 Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)``
137 where 147 where
145 155
146 Invalid keywords, too few positional arguments, or too many positional 156 Invalid keywords, too few positional arguments, or too many positional
147 arguments are rejected, but missing keyword arguments are just omitted. 157 arguments are rejected, but missing keyword arguments are just omitted.
148 """ 158 """
149 poskeys, varkey, keys, optkey = argspec 159 poskeys, varkey, keys, optkey = argspec
150 kwstart = next((i for i, x in enumerate(trees) 160 kwstart = next(
151 if x and x[0] == keyvaluenode), 161 (i for i, x in enumerate(trees) if x and x[0] == keyvaluenode),
152 len(trees)) 162 len(trees),
163 )
153 if kwstart < len(poskeys): 164 if kwstart < len(poskeys):
154 raise error.ParseError(_("%(func)s takes at least %(nargs)d positional " 165 raise error.ParseError(
155 "arguments") 166 _("%(func)s takes at least %(nargs)d positional " "arguments")
156 % {'func': funcname, 'nargs': len(poskeys)}) 167 % {'func': funcname, 'nargs': len(poskeys)}
168 )
157 if not varkey and kwstart > len(poskeys) + len(keys): 169 if not varkey and kwstart > len(poskeys) + len(keys):
158 raise error.ParseError(_("%(func)s takes at most %(nargs)d positional " 170 raise error.ParseError(
159 "arguments") 171 _("%(func)s takes at most %(nargs)d positional " "arguments")
160 % {'func': funcname, 172 % {'func': funcname, 'nargs': len(poskeys) + len(keys)}
161 'nargs': len(poskeys) + len(keys)}) 173 )
162 args = util.sortdict() 174 args = util.sortdict()
163 # consume positional arguments 175 # consume positional arguments
164 for k, x in zip(poskeys, trees[:kwstart]): 176 for k, x in zip(poskeys, trees[:kwstart]):
165 args[k] = x 177 args[k] = x
166 if varkey: 178 if varkey:
167 args[varkey] = trees[len(args):kwstart] 179 args[varkey] = trees[len(args) : kwstart]
168 else: 180 else:
169 for k, x in zip(keys, trees[len(args):kwstart]): 181 for k, x in zip(keys, trees[len(args) : kwstart]):
170 args[k] = x 182 args[k] = x
171 # remainder should be keyword arguments 183 # remainder should be keyword arguments
172 if optkey: 184 if optkey:
173 args[optkey] = util.sortdict() 185 args[optkey] = util.sortdict()
174 for x in trees[kwstart:]: 186 for x in trees[kwstart:]:
175 if not x or x[0] != keyvaluenode or x[1][0] != keynode: 187 if not x or x[0] != keyvaluenode or x[1][0] != keynode:
176 raise error.ParseError(_("%(func)s got an invalid argument") 188 raise error.ParseError(
177 % {'func': funcname}) 189 _("%(func)s got an invalid argument") % {'func': funcname}
190 )
178 k = x[1][1] 191 k = x[1][1]
179 if k in keys: 192 if k in keys:
180 d = args 193 d = args
181 elif not optkey: 194 elif not optkey:
182 raise error.ParseError(_("%(func)s got an unexpected keyword " 195 raise error.ParseError(
183 "argument '%(key)s'") 196 _("%(func)s got an unexpected keyword " "argument '%(key)s'")
184 % {'func': funcname, 'key': k}) 197 % {'func': funcname, 'key': k}
198 )
185 else: 199 else:
186 d = args[optkey] 200 d = args[optkey]
187 if k in d: 201 if k in d:
188 raise error.ParseError(_("%(func)s got multiple values for keyword " 202 raise error.ParseError(
189 "argument '%(key)s'") 203 _(
190 % {'func': funcname, 'key': k}) 204 "%(func)s got multiple values for keyword "
205 "argument '%(key)s'"
206 )
207 % {'func': funcname, 'key': k}
208 )
191 d[k] = x[2] 209 d[k] = x[2]
192 return args 210 return args
211
193 212
194 def unescapestr(s): 213 def unescapestr(s):
195 try: 214 try:
196 return stringutil.unescapestr(s) 215 return stringutil.unescapestr(s)
197 except ValueError as e: 216 except ValueError as e:
198 # mangle Python's exception into our format 217 # mangle Python's exception into our format
199 raise error.ParseError(pycompat.bytestr(e).lower()) 218 raise error.ParseError(pycompat.bytestr(e).lower())
219
200 220
201 def _prettyformat(tree, leafnodes, level, lines): 221 def _prettyformat(tree, leafnodes, level, lines):
202 if not isinstance(tree, tuple): 222 if not isinstance(tree, tuple):
203 lines.append((level, stringutil.pprint(tree))) 223 lines.append((level, stringutil.pprint(tree)))
204 elif tree[0] in leafnodes: 224 elif tree[0] in leafnodes:
208 lines.append((level, '(%s' % tree[0])) 228 lines.append((level, '(%s' % tree[0]))
209 for s in tree[1:]: 229 for s in tree[1:]:
210 _prettyformat(s, leafnodes, level + 1, lines) 230 _prettyformat(s, leafnodes, level + 1, lines)
211 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')] 231 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
212 232
233
213 def prettyformat(tree, leafnodes): 234 def prettyformat(tree, leafnodes):
214 lines = [] 235 lines = []
215 _prettyformat(tree, leafnodes, 0, lines) 236 _prettyformat(tree, leafnodes, 0, lines)
216 output = '\n'.join((' ' * l + s) for l, s in lines) 237 output = '\n'.join((' ' * l + s) for l, s in lines)
217 return output 238 return output
239
218 240
219 def simplifyinfixops(tree, targetnodes): 241 def simplifyinfixops(tree, targetnodes):
220 """Flatten chained infix operations to reduce usage of Python stack 242 """Flatten chained infix operations to reduce usage of Python stack
221 243
222 >>> from . import pycompat 244 >>> from . import pycompat
293 x = l 315 x = l
294 simplified.append(simplifyinfixops(x, targetnodes)) 316 simplified.append(simplifyinfixops(x, targetnodes))
295 simplified.append(op) 317 simplified.append(op)
296 return tuple(reversed(simplified)) 318 return tuple(reversed(simplified))
297 319
320
298 def _buildtree(template, placeholder, replstack): 321 def _buildtree(template, placeholder, replstack):
299 if template == placeholder: 322 if template == placeholder:
300 return replstack.pop() 323 return replstack.pop()
301 if not isinstance(template, tuple): 324 if not isinstance(template, tuple):
302 return template 325 return template
303 return tuple(_buildtree(x, placeholder, replstack) for x in template) 326 return tuple(_buildtree(x, placeholder, replstack) for x in template)
327
304 328
305 def buildtree(template, placeholder, *repls): 329 def buildtree(template, placeholder, *repls):
306 """Create new tree by substituting placeholders by replacements 330 """Create new tree by substituting placeholders by replacements
307 331
308 >>> _ = (b'symbol', b'_') 332 >>> _ = (b'symbol', b'_')
320 r = _buildtree(template, placeholder, replstack) 344 r = _buildtree(template, placeholder, replstack)
321 if replstack: 345 if replstack:
322 raise error.ProgrammingError('too many replacements') 346 raise error.ProgrammingError('too many replacements')
323 return r 347 return r
324 348
349
325 def _matchtree(pattern, tree, placeholder, incompletenodes, matches): 350 def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
326 if pattern == tree: 351 if pattern == tree:
327 return True 352 return True
328 if not isinstance(pattern, tuple) or not isinstance(tree, tuple): 353 if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
329 return False 354 return False
330 if pattern == placeholder and tree[0] not in incompletenodes: 355 if pattern == placeholder and tree[0] not in incompletenodes:
331 matches.append(tree) 356 matches.append(tree)
332 return True 357 return True
333 if len(pattern) != len(tree): 358 if len(pattern) != len(tree):
334 return False 359 return False
335 return all(_matchtree(p, x, placeholder, incompletenodes, matches) 360 return all(
336 for p, x in zip(pattern, tree)) 361 _matchtree(p, x, placeholder, incompletenodes, matches)
362 for p, x in zip(pattern, tree)
363 )
364
337 365
338 def matchtree(pattern, tree, placeholder=None, incompletenodes=()): 366 def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
339 """If a tree matches the pattern, return a list of the tree and nodes 367 """If a tree matches the pattern, return a list of the tree and nodes
340 matched with the placeholder; Otherwise None 368 matched with the placeholder; Otherwise None
341 369
373 raise error.ProgrammingError('placeholder must be a node tuple') 401 raise error.ProgrammingError('placeholder must be a node tuple')
374 matches = [tree] 402 matches = [tree]
375 if _matchtree(pattern, tree, placeholder, incompletenodes, matches): 403 if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
376 return matches 404 return matches
377 405
406
378 def parseerrordetail(inst): 407 def parseerrordetail(inst):
379 """Compose error message from specified ParseError object 408 """Compose error message from specified ParseError object
380 """ 409 """
381 if len(inst.args) > 1: 410 if len(inst.args) > 1:
382 return _('at %d: %s') % (inst.args[1], inst.args[0]) 411 return _('at %d: %s') % (inst.args[1], inst.args[0])
383 else: 412 else:
384 return inst.args[0] 413 return inst.args[0]
414
385 415
386 class alias(object): 416 class alias(object):
387 """Parsed result of alias""" 417 """Parsed result of alias"""
388 418
389 def __init__(self, name, args, err, replacement): 419 def __init__(self, name, args, err, replacement):
394 # whether own `error` information is already shown or not. 424 # whether own `error` information is already shown or not.
395 # this avoids showing same warning multiple times at each 425 # this avoids showing same warning multiple times at each
396 # `expandaliases`. 426 # `expandaliases`.
397 self.warned = False 427 self.warned = False
398 428
429
399 class basealiasrules(object): 430 class basealiasrules(object):
400 """Parsing and expansion rule set of aliases 431 """Parsing and expansion rule set of aliases
401 432
402 This is a helper for fileset/revset/template aliases. A concrete rule set 433 This is a helper for fileset/revset/template aliases. A concrete rule set
403 should be made by sub-classing this and implementing class/static methods. 434 should be made by sub-classing this and implementing class/static methods.
406 437
407 # decl = defn 438 # decl = defn
408 h = heads(default) 439 h = heads(default)
409 b($1) = ancestors($1) - ancestors(default) 440 b($1) = ancestors($1) - ancestors(default)
410 """ 441 """
442
411 # typically a config section, which will be included in error messages 443 # typically a config section, which will be included in error messages
412 _section = None 444 _section = None
413 # tag of symbol node 445 # tag of symbol node
414 _symbolnode = 'symbol' 446 _symbolnode = 'symbol'
415 447
663 def _expand(cls, aliases, tree, expanding, cache): 695 def _expand(cls, aliases, tree, expanding, cache):
664 if not isinstance(tree, tuple): 696 if not isinstance(tree, tuple):
665 return tree 697 return tree
666 r = cls._getalias(aliases, tree) 698 r = cls._getalias(aliases, tree)
667 if r is None: 699 if r is None:
668 return tuple(cls._expand(aliases, t, expanding, cache) 700 return tuple(
669 for t in tree) 701 cls._expand(aliases, t, expanding, cache) for t in tree
702 )
670 a, l = r 703 a, l = r
671 if a.error: 704 if a.error:
672 raise error.Abort(a.error) 705 raise error.Abort(a.error)
673 if a in expanding: 706 if a in expanding:
674 raise error.ParseError(_('infinite expansion of %(section)s ' 707 raise error.ParseError(
675 '"%(name)s" detected') 708 _('infinite expansion of %(section)s ' '"%(name)s" detected')
676 % {'section': cls._section, 'name': a.name}) 709 % {'section': cls._section, 'name': a.name}
710 )
677 # get cacheable replacement tree by expanding aliases recursively 711 # get cacheable replacement tree by expanding aliases recursively
678 expanding.append(a) 712 expanding.append(a)
679 if a.name not in cache: 713 if a.name not in cache:
680 cache[a.name] = cls._expand(aliases, a.replacement, expanding, 714 cache[a.name] = cls._expand(
681 cache) 715 aliases, a.replacement, expanding, cache
716 )
682 result = cache[a.name] 717 result = cache[a.name]
683 expanding.pop() 718 expanding.pop()
684 if a.args is None: 719 if a.args is None:
685 return result 720 return result
686 # substitute function arguments in replacement tree 721 # substitute function arguments in replacement tree
687 if len(l) != len(a.args): 722 if len(l) != len(a.args):
688 raise error.ParseError(_('invalid number of arguments: %d') 723 raise error.ParseError(
689 % len(l)) 724 _('invalid number of arguments: %d') % len(l)
725 )
690 l = [cls._expand(aliases, t, [], cache) for t in l] 726 l = [cls._expand(aliases, t, [], cache) for t in l]
691 return cls._expandargs(result, dict(zip(a.args, l))) 727 return cls._expandargs(result, dict(zip(a.args, l)))
692 728
693 @classmethod 729 @classmethod
694 def expand(cls, aliases, tree): 730 def expand(cls, aliases, tree):