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