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