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