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