##// END OF EJS Templates
parser: add helper function that constructs parsed tree from template...
Yuya Nishihara -
r34045:90896b61 default
parent child Browse files
Show More
@@ -1,604 +1,631 b''
1 1 # parser.py - simple top-down operator precedence parser for mercurial
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 # see http://effbot.org/zone/simple-top-down-parsing.htm and
9 9 # http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/
10 10 # for background
11 11
12 12 # takes a tokenizer and elements
13 13 # tokenizer is an iterator that returns (type, value, pos) tuples
14 14 # elements is a mapping of types to binding strength, primary, prefix, infix
15 15 # and suffix actions
16 16 # an action is a tree node name, a tree label, and an optional match
17 17 # __call__(program) parses program into a labeled tree
18 18
19 19 from __future__ import absolute_import
20 20
21 21 from .i18n import _
22 22 from . import (
23 23 error,
24 24 util,
25 25 )
26 26
27 27 class parser(object):
28 28 def __init__(self, elements, methods=None):
29 29 self._elements = elements
30 30 self._methods = methods
31 31 self.current = None
32 32 def _advance(self):
33 33 'advance the tokenizer'
34 34 t = self.current
35 35 self.current = next(self._iter, None)
36 36 return t
37 37 def _hasnewterm(self):
38 38 'True if next token may start new term'
39 39 return any(self._elements[self.current[0]][1:3])
40 40 def _match(self, m):
41 41 'make sure the tokenizer matches an end condition'
42 42 if self.current[0] != m:
43 43 raise error.ParseError(_("unexpected token: %s") % self.current[0],
44 44 self.current[2])
45 45 self._advance()
46 46 def _parseoperand(self, bind, m=None):
47 47 'gather right-hand-side operand until an end condition or binding met'
48 48 if m and self.current[0] == m:
49 49 expr = None
50 50 else:
51 51 expr = self._parse(bind)
52 52 if m:
53 53 self._match(m)
54 54 return expr
55 55 def _parse(self, bind=0):
56 56 token, value, pos = self._advance()
57 57 # handle prefix rules on current token, take as primary if unambiguous
58 58 primary, prefix = self._elements[token][1:3]
59 59 if primary and not (prefix and self._hasnewterm()):
60 60 expr = (primary, value)
61 61 elif prefix:
62 62 expr = (prefix[0], self._parseoperand(*prefix[1:]))
63 63 else:
64 64 raise error.ParseError(_("not a prefix: %s") % token, pos)
65 65 # gather tokens until we meet a lower binding strength
66 66 while bind < self._elements[self.current[0]][0]:
67 67 token, value, pos = self._advance()
68 68 # handle infix rules, take as suffix if unambiguous
69 69 infix, suffix = self._elements[token][3:]
70 70 if suffix and not (infix and self._hasnewterm()):
71 71 expr = (suffix, expr)
72 72 elif infix:
73 73 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
74 74 else:
75 75 raise error.ParseError(_("not an infix: %s") % token, pos)
76 76 return expr
77 77 def parse(self, tokeniter):
78 78 'generate a parse tree from tokens'
79 79 self._iter = tokeniter
80 80 self._advance()
81 81 res = self._parse()
82 82 token, value, pos = self.current
83 83 return res, pos
84 84 def eval(self, tree):
85 85 'recursively evaluate a parse tree using node methods'
86 86 if not isinstance(tree, tuple):
87 87 return tree
88 88 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
89 89 def __call__(self, tokeniter):
90 90 'parse tokens into a parse tree and evaluate if methods given'
91 91 t = self.parse(tokeniter)
92 92 if self._methods:
93 93 return self.eval(t)
94 94 return t
95 95
96 96 def splitargspec(spec):
97 97 """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
98 98
99 99 >>> splitargspec('')
100 100 ([], None, [], None)
101 101 >>> splitargspec('foo bar')
102 102 ([], None, ['foo', 'bar'], None)
103 103 >>> splitargspec('foo *bar baz **qux')
104 104 (['foo'], 'bar', ['baz'], 'qux')
105 105 >>> splitargspec('*foo')
106 106 ([], 'foo', [], None)
107 107 >>> splitargspec('**foo')
108 108 ([], None, [], 'foo')
109 109 """
110 110 optkey = None
111 111 pre, sep, post = spec.partition('**')
112 112 if sep:
113 113 posts = post.split()
114 114 if not posts:
115 115 raise error.ProgrammingError('no **optkey name provided')
116 116 if len(posts) > 1:
117 117 raise error.ProgrammingError('excessive **optkey names provided')
118 118 optkey = posts[0]
119 119
120 120 pre, sep, post = pre.partition('*')
121 121 pres = pre.split()
122 122 posts = post.split()
123 123 if sep:
124 124 if not posts:
125 125 raise error.ProgrammingError('no *varkey name provided')
126 126 return pres, posts[0], posts[1:], optkey
127 127 return [], None, pres, optkey
128 128
129 129 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
130 130 """Build dict from list containing positional and keyword arguments
131 131
132 132 Arguments are specified by a tuple of ``(poskeys, varkey, keys, optkey)``
133 133 where
134 134
135 135 - ``poskeys``: list of names of positional arguments
136 136 - ``varkey``: optional argument name that takes up remainder
137 137 - ``keys``: list of names that can be either positional or keyword arguments
138 138 - ``optkey``: optional argument name that takes up excess keyword arguments
139 139
140 140 If ``varkey`` specified, all ``keys`` must be given as keyword arguments.
141 141
142 142 Invalid keywords, too few positional arguments, or too many positional
143 143 arguments are rejected, but missing keyword arguments are just omitted.
144 144 """
145 145 poskeys, varkey, keys, optkey = argspec
146 146 kwstart = next((i for i, x in enumerate(trees) if x[0] == keyvaluenode),
147 147 len(trees))
148 148 if kwstart < len(poskeys):
149 149 raise error.ParseError(_("%(func)s takes at least %(nargs)d positional "
150 150 "arguments")
151 151 % {'func': funcname, 'nargs': len(poskeys)})
152 152 if not varkey and kwstart > len(poskeys) + len(keys):
153 153 raise error.ParseError(_("%(func)s takes at most %(nargs)d positional "
154 154 "arguments")
155 155 % {'func': funcname,
156 156 'nargs': len(poskeys) + len(keys)})
157 157 args = util.sortdict()
158 158 # consume positional arguments
159 159 for k, x in zip(poskeys, trees[:kwstart]):
160 160 args[k] = x
161 161 if varkey:
162 162 args[varkey] = trees[len(args):kwstart]
163 163 else:
164 164 for k, x in zip(keys, trees[len(args):kwstart]):
165 165 args[k] = x
166 166 # remainder should be keyword arguments
167 167 if optkey:
168 168 args[optkey] = util.sortdict()
169 169 for x in trees[kwstart:]:
170 170 if x[0] != keyvaluenode or x[1][0] != keynode:
171 171 raise error.ParseError(_("%(func)s got an invalid argument")
172 172 % {'func': funcname})
173 173 k = x[1][1]
174 174 if k in keys:
175 175 d = args
176 176 elif not optkey:
177 177 raise error.ParseError(_("%(func)s got an unexpected keyword "
178 178 "argument '%(key)s'")
179 179 % {'func': funcname, 'key': k})
180 180 else:
181 181 d = args[optkey]
182 182 if k in d:
183 183 raise error.ParseError(_("%(func)s got multiple values for keyword "
184 184 "argument '%(key)s'")
185 185 % {'func': funcname, 'key': k})
186 186 d[k] = x[2]
187 187 return args
188 188
189 189 def unescapestr(s):
190 190 try:
191 191 return util.unescapestr(s)
192 192 except ValueError as e:
193 193 # mangle Python's exception into our format
194 194 raise error.ParseError(str(e).lower())
195 195
196 196 def _prettyformat(tree, leafnodes, level, lines):
197 197 if not isinstance(tree, tuple) or tree[0] in leafnodes:
198 198 lines.append((level, str(tree)))
199 199 else:
200 200 lines.append((level, '(%s' % tree[0]))
201 201 for s in tree[1:]:
202 202 _prettyformat(s, leafnodes, level + 1, lines)
203 203 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
204 204
205 205 def prettyformat(tree, leafnodes):
206 206 lines = []
207 207 _prettyformat(tree, leafnodes, 0, lines)
208 208 output = '\n'.join((' ' * l + s) for l, s in lines)
209 209 return output
210 210
211 211 def simplifyinfixops(tree, targetnodes):
212 212 """Flatten chained infix operations to reduce usage of Python stack
213 213
214 214 >>> def f(tree):
215 215 ... print prettyformat(simplifyinfixops(tree, ('or',)), ('symbol',))
216 216 >>> f(('or',
217 217 ... ('or',
218 218 ... ('symbol', '1'),
219 219 ... ('symbol', '2')),
220 220 ... ('symbol', '3')))
221 221 (or
222 222 ('symbol', '1')
223 223 ('symbol', '2')
224 224 ('symbol', '3'))
225 225 >>> f(('func',
226 226 ... ('symbol', 'p1'),
227 227 ... ('or',
228 228 ... ('or',
229 229 ... ('func',
230 230 ... ('symbol', 'sort'),
231 231 ... ('list',
232 232 ... ('or',
233 233 ... ('or',
234 234 ... ('symbol', '1'),
235 235 ... ('symbol', '2')),
236 236 ... ('symbol', '3')),
237 237 ... ('negate',
238 238 ... ('symbol', 'rev')))),
239 239 ... ('and',
240 240 ... ('symbol', '4'),
241 241 ... ('group',
242 242 ... ('or',
243 243 ... ('or',
244 244 ... ('symbol', '5'),
245 245 ... ('symbol', '6')),
246 246 ... ('symbol', '7'))))),
247 247 ... ('symbol', '8'))))
248 248 (func
249 249 ('symbol', 'p1')
250 250 (or
251 251 (func
252 252 ('symbol', 'sort')
253 253 (list
254 254 (or
255 255 ('symbol', '1')
256 256 ('symbol', '2')
257 257 ('symbol', '3'))
258 258 (negate
259 259 ('symbol', 'rev'))))
260 260 (and
261 261 ('symbol', '4')
262 262 (group
263 263 (or
264 264 ('symbol', '5')
265 265 ('symbol', '6')
266 266 ('symbol', '7'))))
267 267 ('symbol', '8')))
268 268 """
269 269 if not isinstance(tree, tuple):
270 270 return tree
271 271 op = tree[0]
272 272 if op not in targetnodes:
273 273 return (op,) + tuple(simplifyinfixops(x, targetnodes) for x in tree[1:])
274 274
275 275 # walk down left nodes taking each right node. no recursion to left nodes
276 276 # because infix operators are left-associative, i.e. left tree is deep.
277 277 # e.g. '1 + 2 + 3' -> (+ (+ 1 2) 3) -> (+ 1 2 3)
278 278 simplified = []
279 279 x = tree
280 280 while x[0] == op:
281 281 l, r = x[1:]
282 282 simplified.append(simplifyinfixops(r, targetnodes))
283 283 x = l
284 284 simplified.append(simplifyinfixops(x, targetnodes))
285 285 simplified.append(op)
286 286 return tuple(reversed(simplified))
287 287
288 def _buildtree(template, placeholder, replstack):
289 if template == placeholder:
290 return replstack.pop()
291 if not isinstance(template, tuple):
292 return template
293 return tuple(_buildtree(x, placeholder, replstack) for x in template)
294
295 def buildtree(template, placeholder, *repls):
296 """Create new tree by substituting placeholders by replacements
297
298 >>> _ = ('symbol', '_')
299 >>> def f(template, *repls):
300 ... return buildtree(template, _, *repls)
301 >>> f(('func', ('symbol', 'only'), ('list', _, _)),
302 ... ('symbol', '1'), ('symbol', '2'))
303 ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
304 >>> f(('and', _, ('not', _)), ('symbol', '1'), ('symbol', '2'))
305 ('and', ('symbol', '1'), ('not', ('symbol', '2')))
306 """
307 if not isinstance(placeholder, tuple):
308 raise error.ProgrammingError('placeholder must be a node tuple')
309 replstack = list(reversed(repls))
310 r = _buildtree(template, placeholder, replstack)
311 if replstack:
312 raise error.ProgrammingError('too many replacements')
313 return r
314
288 315 def parseerrordetail(inst):
289 316 """Compose error message from specified ParseError object
290 317 """
291 318 if len(inst.args) > 1:
292 319 return _('at %d: %s') % (inst.args[1], inst.args[0])
293 320 else:
294 321 return inst.args[0]
295 322
296 323 class alias(object):
297 324 """Parsed result of alias"""
298 325
299 326 def __init__(self, name, args, err, replacement):
300 327 self.name = name
301 328 self.args = args
302 329 self.error = err
303 330 self.replacement = replacement
304 331 # whether own `error` information is already shown or not.
305 332 # this avoids showing same warning multiple times at each
306 333 # `expandaliases`.
307 334 self.warned = False
308 335
309 336 class basealiasrules(object):
310 337 """Parsing and expansion rule set of aliases
311 338
312 339 This is a helper for fileset/revset/template aliases. A concrete rule set
313 340 should be made by sub-classing this and implementing class/static methods.
314 341
315 342 It supports alias expansion of symbol and function-call styles::
316 343
317 344 # decl = defn
318 345 h = heads(default)
319 346 b($1) = ancestors($1) - ancestors(default)
320 347 """
321 348 # typically a config section, which will be included in error messages
322 349 _section = None
323 350 # tag of symbol node
324 351 _symbolnode = 'symbol'
325 352
326 353 def __new__(cls):
327 354 raise TypeError("'%s' is not instantiatable" % cls.__name__)
328 355
329 356 @staticmethod
330 357 def _parse(spec):
331 358 """Parse an alias name, arguments and definition"""
332 359 raise NotImplementedError
333 360
334 361 @staticmethod
335 362 def _trygetfunc(tree):
336 363 """Return (name, args) if tree is a function; otherwise None"""
337 364 raise NotImplementedError
338 365
339 366 @classmethod
340 367 def _builddecl(cls, decl):
341 368 """Parse an alias declaration into ``(name, args, errorstr)``
342 369
343 370 This function analyzes the parsed tree. The parsing rule is provided
344 371 by ``_parse()``.
345 372
346 373 - ``name``: of declared alias (may be ``decl`` itself at error)
347 374 - ``args``: list of argument names (or None for symbol declaration)
348 375 - ``errorstr``: detail about detected error (or None)
349 376
350 377 >>> sym = lambda x: ('symbol', x)
351 378 >>> symlist = lambda *xs: ('list',) + tuple(sym(x) for x in xs)
352 379 >>> func = lambda n, a: ('func', sym(n), a)
353 380 >>> parsemap = {
354 381 ... 'foo': sym('foo'),
355 382 ... '$foo': sym('$foo'),
356 383 ... 'foo::bar': ('dagrange', sym('foo'), sym('bar')),
357 384 ... 'foo()': func('foo', None),
358 385 ... '$foo()': func('$foo', None),
359 386 ... 'foo($1, $2)': func('foo', symlist('$1', '$2')),
360 387 ... 'foo(bar_bar, baz.baz)':
361 388 ... func('foo', symlist('bar_bar', 'baz.baz')),
362 389 ... 'foo(bar($1, $2))':
363 390 ... func('foo', func('bar', symlist('$1', '$2'))),
364 391 ... 'foo($1, $2, nested($1, $2))':
365 392 ... func('foo', (symlist('$1', '$2') +
366 393 ... (func('nested', symlist('$1', '$2')),))),
367 394 ... 'foo("bar")': func('foo', ('string', 'bar')),
368 395 ... 'foo($1, $2': error.ParseError('unexpected token: end', 10),
369 396 ... 'foo("bar': error.ParseError('unterminated string', 5),
370 397 ... 'foo($1, $2, $1)': func('foo', symlist('$1', '$2', '$1')),
371 398 ... }
372 399 >>> def parse(expr):
373 400 ... x = parsemap[expr]
374 401 ... if isinstance(x, Exception):
375 402 ... raise x
376 403 ... return x
377 404 >>> def trygetfunc(tree):
378 405 ... if not tree or tree[0] != 'func' or tree[1][0] != 'symbol':
379 406 ... return None
380 407 ... if not tree[2]:
381 408 ... return tree[1][1], []
382 409 ... if tree[2][0] == 'list':
383 410 ... return tree[1][1], list(tree[2][1:])
384 411 ... return tree[1][1], [tree[2]]
385 412 >>> class aliasrules(basealiasrules):
386 413 ... _parse = staticmethod(parse)
387 414 ... _trygetfunc = staticmethod(trygetfunc)
388 415 >>> builddecl = aliasrules._builddecl
389 416 >>> builddecl('foo')
390 417 ('foo', None, None)
391 418 >>> builddecl('$foo')
392 419 ('$foo', None, "invalid symbol '$foo'")
393 420 >>> builddecl('foo::bar')
394 421 ('foo::bar', None, 'invalid format')
395 422 >>> builddecl('foo()')
396 423 ('foo', [], None)
397 424 >>> builddecl('$foo()')
398 425 ('$foo()', None, "invalid function '$foo'")
399 426 >>> builddecl('foo($1, $2)')
400 427 ('foo', ['$1', '$2'], None)
401 428 >>> builddecl('foo(bar_bar, baz.baz)')
402 429 ('foo', ['bar_bar', 'baz.baz'], None)
403 430 >>> builddecl('foo($1, $2, nested($1, $2))')
404 431 ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
405 432 >>> builddecl('foo(bar($1, $2))')
406 433 ('foo(bar($1, $2))', None, 'invalid argument list')
407 434 >>> builddecl('foo("bar")')
408 435 ('foo("bar")', None, 'invalid argument list')
409 436 >>> builddecl('foo($1, $2')
410 437 ('foo($1, $2', None, 'at 10: unexpected token: end')
411 438 >>> builddecl('foo("bar')
412 439 ('foo("bar', None, 'at 5: unterminated string')
413 440 >>> builddecl('foo($1, $2, $1)')
414 441 ('foo', None, 'argument names collide with each other')
415 442 """
416 443 try:
417 444 tree = cls._parse(decl)
418 445 except error.ParseError as inst:
419 446 return (decl, None, parseerrordetail(inst))
420 447
421 448 if tree[0] == cls._symbolnode:
422 449 # "name = ...." style
423 450 name = tree[1]
424 451 if name.startswith('$'):
425 452 return (decl, None, _("invalid symbol '%s'") % name)
426 453 return (name, None, None)
427 454
428 455 func = cls._trygetfunc(tree)
429 456 if func:
430 457 # "name(arg, ....) = ...." style
431 458 name, args = func
432 459 if name.startswith('$'):
433 460 return (decl, None, _("invalid function '%s'") % name)
434 461 if any(t[0] != cls._symbolnode for t in args):
435 462 return (decl, None, _("invalid argument list"))
436 463 if len(args) != len(set(args)):
437 464 return (name, None, _("argument names collide with each other"))
438 465 return (name, [t[1] for t in args], None)
439 466
440 467 return (decl, None, _("invalid format"))
441 468
442 469 @classmethod
443 470 def _relabelargs(cls, tree, args):
444 471 """Mark alias arguments as ``_aliasarg``"""
445 472 if not isinstance(tree, tuple):
446 473 return tree
447 474 op = tree[0]
448 475 if op != cls._symbolnode:
449 476 return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:])
450 477
451 478 assert len(tree) == 2
452 479 sym = tree[1]
453 480 if sym in args:
454 481 op = '_aliasarg'
455 482 elif sym.startswith('$'):
456 483 raise error.ParseError(_("invalid symbol '%s'") % sym)
457 484 return (op, sym)
458 485
459 486 @classmethod
460 487 def _builddefn(cls, defn, args):
461 488 """Parse an alias definition into a tree and marks substitutions
462 489
463 490 This function marks alias argument references as ``_aliasarg``. The
464 491 parsing rule is provided by ``_parse()``.
465 492
466 493 ``args`` is a list of alias argument names, or None if the alias
467 494 is declared as a symbol.
468 495
469 496 >>> parsemap = {
470 497 ... '$1 or foo': ('or', ('symbol', '$1'), ('symbol', 'foo')),
471 498 ... '$1 or $bar': ('or', ('symbol', '$1'), ('symbol', '$bar')),
472 499 ... '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')),
473 500 ... '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')),
474 501 ... }
475 502 >>> class aliasrules(basealiasrules):
476 503 ... _parse = staticmethod(parsemap.__getitem__)
477 504 ... _trygetfunc = staticmethod(lambda x: None)
478 505 >>> builddefn = aliasrules._builddefn
479 506 >>> def pprint(tree):
480 507 ... print prettyformat(tree, ('_aliasarg', 'string', 'symbol'))
481 508 >>> args = ['$1', '$2', 'foo']
482 509 >>> pprint(builddefn('$1 or foo', args))
483 510 (or
484 511 ('_aliasarg', '$1')
485 512 ('_aliasarg', 'foo'))
486 513 >>> try:
487 514 ... builddefn('$1 or $bar', args)
488 515 ... except error.ParseError as inst:
489 516 ... print parseerrordetail(inst)
490 517 invalid symbol '$bar'
491 518 >>> args = ['$1', '$10', 'foo']
492 519 >>> pprint(builddefn('$10 or baz', args))
493 520 (or
494 521 ('_aliasarg', '$10')
495 522 ('symbol', 'baz'))
496 523 >>> pprint(builddefn('"$1" or "foo"', args))
497 524 (or
498 525 ('string', '$1')
499 526 ('string', 'foo'))
500 527 """
501 528 tree = cls._parse(defn)
502 529 if args:
503 530 args = set(args)
504 531 else:
505 532 args = set()
506 533 return cls._relabelargs(tree, args)
507 534
508 535 @classmethod
509 536 def build(cls, decl, defn):
510 537 """Parse an alias declaration and definition into an alias object"""
511 538 repl = efmt = None
512 539 name, args, err = cls._builddecl(decl)
513 540 if err:
514 541 efmt = _('bad declaration of %(section)s "%(name)s": %(error)s')
515 542 else:
516 543 try:
517 544 repl = cls._builddefn(defn, args)
518 545 except error.ParseError as inst:
519 546 err = parseerrordetail(inst)
520 547 efmt = _('bad definition of %(section)s "%(name)s": %(error)s')
521 548 if err:
522 549 err = efmt % {'section': cls._section, 'name': name, 'error': err}
523 550 return alias(name, args, err, repl)
524 551
525 552 @classmethod
526 553 def buildmap(cls, items):
527 554 """Parse a list of alias (name, replacement) pairs into a dict of
528 555 alias objects"""
529 556 aliases = {}
530 557 for decl, defn in items:
531 558 a = cls.build(decl, defn)
532 559 aliases[a.name] = a
533 560 return aliases
534 561
535 562 @classmethod
536 563 def _getalias(cls, aliases, tree):
537 564 """If tree looks like an unexpanded alias, return (alias, pattern-args)
538 565 pair. Return None otherwise.
539 566 """
540 567 if not isinstance(tree, tuple):
541 568 return None
542 569 if tree[0] == cls._symbolnode:
543 570 name = tree[1]
544 571 a = aliases.get(name)
545 572 if a and a.args is None:
546 573 return a, None
547 574 func = cls._trygetfunc(tree)
548 575 if func:
549 576 name, args = func
550 577 a = aliases.get(name)
551 578 if a and a.args is not None:
552 579 return a, args
553 580 return None
554 581
555 582 @classmethod
556 583 def _expandargs(cls, tree, args):
557 584 """Replace _aliasarg instances with the substitution value of the
558 585 same name in args, recursively.
559 586 """
560 587 if not isinstance(tree, tuple):
561 588 return tree
562 589 if tree[0] == '_aliasarg':
563 590 sym = tree[1]
564 591 return args[sym]
565 592 return tuple(cls._expandargs(t, args) for t in tree)
566 593
567 594 @classmethod
568 595 def _expand(cls, aliases, tree, expanding, cache):
569 596 if not isinstance(tree, tuple):
570 597 return tree
571 598 r = cls._getalias(aliases, tree)
572 599 if r is None:
573 600 return tuple(cls._expand(aliases, t, expanding, cache)
574 601 for t in tree)
575 602 a, l = r
576 603 if a.error:
577 604 raise error.Abort(a.error)
578 605 if a in expanding:
579 606 raise error.ParseError(_('infinite expansion of %(section)s '
580 607 '"%(name)s" detected')
581 608 % {'section': cls._section, 'name': a.name})
582 609 # get cacheable replacement tree by expanding aliases recursively
583 610 expanding.append(a)
584 611 if a.name not in cache:
585 612 cache[a.name] = cls._expand(aliases, a.replacement, expanding,
586 613 cache)
587 614 result = cache[a.name]
588 615 expanding.pop()
589 616 if a.args is None:
590 617 return result
591 618 # substitute function arguments in replacement tree
592 619 if len(l) != len(a.args):
593 620 raise error.ParseError(_('invalid number of arguments: %d')
594 621 % len(l))
595 622 l = [cls._expand(aliases, t, [], cache) for t in l]
596 623 return cls._expandargs(result, dict(zip(a.args, l)))
597 624
598 625 @classmethod
599 626 def expand(cls, aliases, tree):
600 627 """Expand aliases in tree, recursively.
601 628
602 629 'aliases' is a dictionary mapping user defined aliases to alias objects.
603 630 """
604 631 return cls._expand(aliases, tree, [], {})
General Comments 0
You need to be logged in to leave comments. Login now