comparison mercurial/parser.py @ 28910:1203159c8928

parser: factor out _trygetfunc() that extracts function name and arguments This provides a customization point for templater. In templater, there are two ways to call a unary function: func(x) and x|func. They are processed differently in templater due to historical reasons, but they should be handled in the same way while expanding aliases. In short, x|func should be processed as syntactic sugar for func(x). _funcnode and _getlist() are replaced by _trygetfunc().
author Yuya Nishihara <yuya@tcha.org>
date Tue, 29 Mar 2016 17:27:34 +0900
parents edbffdc7f6a0
children dbed4c4f48ae
comparison
equal deleted inserted replaced
28909:edbffdc7f6a0 28910:1203159c8928
254 h = heads(default) 254 h = heads(default)
255 b($1) = ancestors($1) - ancestors(default) 255 b($1) = ancestors($1) - ancestors(default)
256 """ 256 """
257 # typically a config section, which will be included in error messages 257 # typically a config section, which will be included in error messages
258 _section = None 258 _section = None
259 # tags of symbol and function nodes 259 # tag of symbol node
260 _symbolnode = 'symbol' 260 _symbolnode = 'symbol'
261 _funcnode = 'func'
262 261
263 def __new__(cls): 262 def __new__(cls):
264 raise TypeError("'%s' is not instantiatable" % cls.__name__) 263 raise TypeError("'%s' is not instantiatable" % cls.__name__)
265 264
266 @staticmethod 265 @staticmethod
267 def _parse(spec): 266 def _parse(spec):
268 """Parse an alias name, arguments and definition""" 267 """Parse an alias name, arguments and definition"""
269 raise NotImplementedError 268 raise NotImplementedError
270 269
271 @staticmethod 270 @staticmethod
272 def _getlist(tree): 271 def _trygetfunc(tree):
273 """Extract a list of arguments from parsed tree""" 272 """Return (name, args) if tree is a function; otherwise None"""
274 raise NotImplementedError 273 raise NotImplementedError
275 274
276 @classmethod 275 @classmethod
277 def _builddecl(cls, decl): 276 def _builddecl(cls, decl):
278 """Parse an alias declaration into ``(name, args, errorstr)`` 277 """Parse an alias declaration into ``(name, args, errorstr)``
309 >>> def parse(expr): 308 >>> def parse(expr):
310 ... x = parsemap[expr] 309 ... x = parsemap[expr]
311 ... if isinstance(x, Exception): 310 ... if isinstance(x, Exception):
312 ... raise x 311 ... raise x
313 ... return x 312 ... return x
314 >>> def getlist(tree): 313 >>> def trygetfunc(tree):
315 ... if not tree: 314 ... if not tree or tree[0] != 'func' or tree[1][0] != 'symbol':
316 ... return [] 315 ... return None
317 ... if tree[0] == 'list': 316 ... if not tree[2]:
318 ... return list(tree[1:]) 317 ... return tree[1][1], []
319 ... return [tree] 318 ... if tree[2][0] == 'list':
319 ... return tree[1][1], list(tree[2][1:])
320 ... return tree[1][1], [tree[2]]
320 >>> class aliasrules(basealiasrules): 321 >>> class aliasrules(basealiasrules):
321 ... _parse = staticmethod(parse) 322 ... _parse = staticmethod(parse)
322 ... _getlist = staticmethod(getlist) 323 ... _trygetfunc = staticmethod(trygetfunc)
323 >>> builddecl = aliasrules._builddecl 324 >>> builddecl = aliasrules._builddecl
324 >>> builddecl('foo') 325 >>> builddecl('foo')
325 ('foo', None, None) 326 ('foo', None, None)
326 >>> builddecl('$foo') 327 >>> builddecl('$foo')
327 ('$foo', None, "'$' not for alias arguments") 328 ('$foo', None, "'$' not for alias arguments")
358 name = tree[1] 359 name = tree[1]
359 if name.startswith('$'): 360 if name.startswith('$'):
360 return (decl, None, _("'$' not for alias arguments")) 361 return (decl, None, _("'$' not for alias arguments"))
361 return (name, None, None) 362 return (name, None, None)
362 363
363 if tree[0] == cls._funcnode and tree[1][0] == cls._symbolnode: 364 func = cls._trygetfunc(tree)
365 if func:
364 # "name(arg, ....) = ...." style 366 # "name(arg, ....) = ...." style
365 name = tree[1][1] 367 name, args = func
366 if name.startswith('$'): 368 if name.startswith('$'):
367 return (decl, None, _("'$' not for alias arguments")) 369 return (decl, None, _("'$' not for alias arguments"))
368 args = [] 370 if any(t[0] != cls._symbolnode for t in args):
369 for arg in cls._getlist(tree[2]): 371 return (decl, None, _("invalid argument list"))
370 if arg[0] != cls._symbolnode:
371 return (decl, None, _("invalid argument list"))
372 args.append(arg[1])
373 if len(args) != len(set(args)): 372 if len(args) != len(set(args)):
374 return (name, None, _("argument names collide with each other")) 373 return (name, None, _("argument names collide with each other"))
375 return (name, args, None) 374 return (name, [t[1] for t in args], None)
376 375
377 return (decl, None, _("invalid format")) 376 return (decl, None, _("invalid format"))
378 377
379 @classmethod 378 @classmethod
380 def _relabelargs(cls, tree, args): 379 def _relabelargs(cls, tree, args):
409 ... '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')), 408 ... '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')),
410 ... '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')), 409 ... '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')),
411 ... } 410 ... }
412 >>> class aliasrules(basealiasrules): 411 >>> class aliasrules(basealiasrules):
413 ... _parse = staticmethod(parsemap.__getitem__) 412 ... _parse = staticmethod(parsemap.__getitem__)
414 ... _getlist = staticmethod(lambda x: []) 413 ... _trygetfunc = staticmethod(lambda x: None)
415 >>> builddefn = aliasrules._builddefn 414 >>> builddefn = aliasrules._builddefn
416 >>> def pprint(tree): 415 >>> def pprint(tree):
417 ... print prettyformat(tree, ('_aliasarg', 'string', 'symbol')) 416 ... print prettyformat(tree, ('_aliasarg', 'string', 'symbol'))
418 >>> args = ['$1', '$2', 'foo'] 417 >>> args = ['$1', '$2', 'foo']
419 >>> pprint(builddefn('$1 or foo', args)) 418 >>> pprint(builddefn('$1 or foo', args))
481 if tree[0] == cls._symbolnode: 480 if tree[0] == cls._symbolnode:
482 name = tree[1] 481 name = tree[1]
483 a = aliases.get(name) 482 a = aliases.get(name)
484 if a and a.args is None: 483 if a and a.args is None:
485 return a, None 484 return a, None
486 if tree[0] == cls._funcnode and tree[1][0] == cls._symbolnode: 485 func = cls._trygetfunc(tree)
487 name = tree[1][1] 486 if func:
487 name, args = func
488 a = aliases.get(name) 488 a = aliases.get(name)
489 if a and a.args is not None: 489 if a and a.args is not None:
490 return a, cls._getlist(tree[2]) 490 return a, args
491 return None 491 return None
492 492
493 @classmethod 493 @classmethod
494 def _expandargs(cls, tree, args): 494 def _expandargs(cls, tree, args):
495 """Replace _aliasarg instances with the substitution value of the 495 """Replace _aliasarg instances with the substitution value of the