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