mercurial/parser.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43117 8ff1ecfadcd1
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    32         self._elements = elements
    32         self._elements = elements
    33         self._methods = methods
    33         self._methods = methods
    34         self.current = None
    34         self.current = None
    35 
    35 
    36     def _advance(self):
    36     def _advance(self):
    37         'advance the tokenizer'
    37         b'advance the tokenizer'
    38         t = self.current
    38         t = self.current
    39         self.current = next(self._iter, None)
    39         self.current = next(self._iter, None)
    40         return t
    40         return t
    41 
    41 
    42     def _hasnewterm(self):
    42     def _hasnewterm(self):
    43         'True if next token may start new term'
    43         b'True if next token may start new term'
    44         return any(self._elements[self.current[0]][1:3])
    44         return any(self._elements[self.current[0]][1:3])
    45 
    45 
    46     def _match(self, m):
    46     def _match(self, m):
    47         'make sure the tokenizer matches an end condition'
    47         b'make sure the tokenizer matches an end condition'
    48         if self.current[0] != m:
    48         if self.current[0] != m:
    49             raise error.ParseError(
    49             raise error.ParseError(
    50                 _("unexpected token: %s") % self.current[0], self.current[2]
    50                 _(b"unexpected token: %s") % self.current[0], self.current[2]
    51             )
    51             )
    52         self._advance()
    52         self._advance()
    53 
    53 
    54     def _parseoperand(self, bind, m=None):
    54     def _parseoperand(self, bind, m=None):
    55         'gather right-hand-side operand until an end condition or binding met'
    55         b'gather right-hand-side operand until an end condition or binding met'
    56         if m and self.current[0] == m:
    56         if m and self.current[0] == m:
    57             expr = None
    57             expr = None
    58         else:
    58         else:
    59             expr = self._parse(bind)
    59             expr = self._parse(bind)
    60         if m:
    60         if m:
    68         if primary and not (prefix and self._hasnewterm()):
    68         if primary and not (prefix and self._hasnewterm()):
    69             expr = (primary, value)
    69             expr = (primary, value)
    70         elif prefix:
    70         elif prefix:
    71             expr = (prefix[0], self._parseoperand(*prefix[1:]))
    71             expr = (prefix[0], self._parseoperand(*prefix[1:]))
    72         else:
    72         else:
    73             raise error.ParseError(_("not a prefix: %s") % token, pos)
    73             raise error.ParseError(_(b"not a prefix: %s") % token, pos)
    74         # gather tokens until we meet a lower binding strength
    74         # gather tokens until we meet a lower binding strength
    75         while bind < self._elements[self.current[0]][0]:
    75         while bind < self._elements[self.current[0]][0]:
    76             token, value, pos = self._advance()
    76             token, value, pos = self._advance()
    77             # handle infix rules, take as suffix if unambiguous
    77             # handle infix rules, take as suffix if unambiguous
    78             infix, suffix = self._elements[token][3:]
    78             infix, suffix = self._elements[token][3:]
    79             if suffix and not (infix and self._hasnewterm()):
    79             if suffix and not (infix and self._hasnewterm()):
    80                 expr = (suffix, expr)
    80                 expr = (suffix, expr)
    81             elif infix:
    81             elif infix:
    82                 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
    82                 expr = (infix[0], expr, self._parseoperand(*infix[1:]))
    83             else:
    83             else:
    84                 raise error.ParseError(_("not an infix: %s") % token, pos)
    84                 raise error.ParseError(_(b"not an infix: %s") % token, pos)
    85         return expr
    85         return expr
    86 
    86 
    87     def parse(self, tokeniter):
    87     def parse(self, tokeniter):
    88         'generate a parse tree from tokens'
    88         b'generate a parse tree from tokens'
    89         self._iter = tokeniter
    89         self._iter = tokeniter
    90         self._advance()
    90         self._advance()
    91         res = self._parse()
    91         res = self._parse()
    92         token, value, pos = self.current
    92         token, value, pos = self.current
    93         return res, pos
    93         return res, pos
    94 
    94 
    95     def eval(self, tree):
    95     def eval(self, tree):
    96         'recursively evaluate a parse tree using node methods'
    96         b'recursively evaluate a parse tree using node methods'
    97         if not isinstance(tree, tuple):
    97         if not isinstance(tree, tuple):
    98             return tree
    98             return tree
    99         return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
    99         return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
   100 
   100 
   101     def __call__(self, tokeniter):
   101     def __call__(self, tokeniter):
   102         'parse tokens into a parse tree and evaluate if methods given'
   102         b'parse tokens into a parse tree and evaluate if methods given'
   103         t = self.parse(tokeniter)
   103         t = self.parse(tokeniter)
   104         if self._methods:
   104         if self._methods:
   105             return self.eval(t)
   105             return self.eval(t)
   106         return t
   106         return t
   107 
   107 
   119     ([], 'foo', [], None)
   119     ([], 'foo', [], None)
   120     >>> splitargspec(b'**foo')
   120     >>> splitargspec(b'**foo')
   121     ([], None, [], 'foo')
   121     ([], None, [], 'foo')
   122     """
   122     """
   123     optkey = None
   123     optkey = None
   124     pre, sep, post = spec.partition('**')
   124     pre, sep, post = spec.partition(b'**')
   125     if sep:
   125     if sep:
   126         posts = post.split()
   126         posts = post.split()
   127         if not posts:
   127         if not posts:
   128             raise error.ProgrammingError('no **optkey name provided')
   128             raise error.ProgrammingError(b'no **optkey name provided')
   129         if len(posts) > 1:
   129         if len(posts) > 1:
   130             raise error.ProgrammingError('excessive **optkey names provided')
   130             raise error.ProgrammingError(b'excessive **optkey names provided')
   131         optkey = posts[0]
   131         optkey = posts[0]
   132 
   132 
   133     pre, sep, post = pre.partition('*')
   133     pre, sep, post = pre.partition(b'*')
   134     pres = pre.split()
   134     pres = pre.split()
   135     posts = post.split()
   135     posts = post.split()
   136     if sep:
   136     if sep:
   137         if not posts:
   137         if not posts:
   138             raise error.ProgrammingError('no *varkey name provided')
   138             raise error.ProgrammingError(b'no *varkey name provided')
   139         return pres, posts[0], posts[1:], optkey
   139         return pres, posts[0], posts[1:], optkey
   140     return [], None, pres, optkey
   140     return [], None, pres, optkey
   141 
   141 
   142 
   142 
   143 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
   143 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode):
   161         (i for i, x in enumerate(trees) if x and x[0] == keyvaluenode),
   161         (i for i, x in enumerate(trees) if x and x[0] == keyvaluenode),
   162         len(trees),
   162         len(trees),
   163     )
   163     )
   164     if kwstart < len(poskeys):
   164     if kwstart < len(poskeys):
   165         raise error.ParseError(
   165         raise error.ParseError(
   166             _("%(func)s takes at least %(nargs)d positional " "arguments")
   166             _(b"%(func)s takes at least %(nargs)d positional " b"arguments")
   167             % {'func': funcname, 'nargs': len(poskeys)}
   167             % {b'func': funcname, b'nargs': len(poskeys)}
   168         )
   168         )
   169     if not varkey and kwstart > len(poskeys) + len(keys):
   169     if not varkey and kwstart > len(poskeys) + len(keys):
   170         raise error.ParseError(
   170         raise error.ParseError(
   171             _("%(func)s takes at most %(nargs)d positional " "arguments")
   171             _(b"%(func)s takes at most %(nargs)d positional " b"arguments")
   172             % {'func': funcname, 'nargs': len(poskeys) + len(keys)}
   172             % {b'func': funcname, b'nargs': len(poskeys) + len(keys)}
   173         )
   173         )
   174     args = util.sortdict()
   174     args = util.sortdict()
   175     # consume positional arguments
   175     # consume positional arguments
   176     for k, x in zip(poskeys, trees[:kwstart]):
   176     for k, x in zip(poskeys, trees[:kwstart]):
   177         args[k] = x
   177         args[k] = x
   184     if optkey:
   184     if optkey:
   185         args[optkey] = util.sortdict()
   185         args[optkey] = util.sortdict()
   186     for x in trees[kwstart:]:
   186     for x in trees[kwstart:]:
   187         if not x or x[0] != keyvaluenode or x[1][0] != keynode:
   187         if not x or x[0] != keyvaluenode or x[1][0] != keynode:
   188             raise error.ParseError(
   188             raise error.ParseError(
   189                 _("%(func)s got an invalid argument") % {'func': funcname}
   189                 _(b"%(func)s got an invalid argument") % {b'func': funcname}
   190             )
   190             )
   191         k = x[1][1]
   191         k = x[1][1]
   192         if k in keys:
   192         if k in keys:
   193             d = args
   193             d = args
   194         elif not optkey:
   194         elif not optkey:
   195             raise error.ParseError(
   195             raise error.ParseError(
   196                 _("%(func)s got an unexpected keyword " "argument '%(key)s'")
   196                 _(b"%(func)s got an unexpected keyword " b"argument '%(key)s'")
   197                 % {'func': funcname, 'key': k}
   197                 % {b'func': funcname, b'key': k}
   198             )
   198             )
   199         else:
   199         else:
   200             d = args[optkey]
   200             d = args[optkey]
   201         if k in d:
   201         if k in d:
   202             raise error.ParseError(
   202             raise error.ParseError(
   203                 _(
   203                 _(
   204                     "%(func)s got multiple values for keyword "
   204                     b"%(func)s got multiple values for keyword "
   205                     "argument '%(key)s'"
   205                     b"argument '%(key)s'"
   206                 )
   206                 )
   207                 % {'func': funcname, 'key': k}
   207                 % {b'func': funcname, b'key': k}
   208             )
   208             )
   209         d[k] = x[2]
   209         d[k] = x[2]
   210     return args
   210     return args
   211 
   211 
   212 
   212 
   221 def _prettyformat(tree, leafnodes, level, lines):
   221 def _prettyformat(tree, leafnodes, level, lines):
   222     if not isinstance(tree, tuple):
   222     if not isinstance(tree, tuple):
   223         lines.append((level, stringutil.pprint(tree)))
   223         lines.append((level, stringutil.pprint(tree)))
   224     elif tree[0] in leafnodes:
   224     elif tree[0] in leafnodes:
   225         rs = map(stringutil.pprint, tree[1:])
   225         rs = map(stringutil.pprint, tree[1:])
   226         lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
   226         lines.append((level, b'(%s %s)' % (tree[0], b' '.join(rs))))
   227     else:
   227     else:
   228         lines.append((level, '(%s' % tree[0]))
   228         lines.append((level, b'(%s' % tree[0]))
   229         for s in tree[1:]:
   229         for s in tree[1:]:
   230             _prettyformat(s, leafnodes, level + 1, lines)
   230             _prettyformat(s, leafnodes, level + 1, lines)
   231         lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
   231         lines[-1:] = [(lines[-1][0], lines[-1][1] + b')')]
   232 
   232 
   233 
   233 
   234 def prettyformat(tree, leafnodes):
   234 def prettyformat(tree, leafnodes):
   235     lines = []
   235     lines = []
   236     _prettyformat(tree, leafnodes, 0, lines)
   236     _prettyformat(tree, leafnodes, 0, lines)
   237     output = '\n'.join(('  ' * l + s) for l, s in lines)
   237     output = b'\n'.join((b'  ' * l + s) for l, s in lines)
   238     return output
   238     return output
   239 
   239 
   240 
   240 
   241 def simplifyinfixops(tree, targetnodes):
   241 def simplifyinfixops(tree, targetnodes):
   242     """Flatten chained infix operations to reduce usage of Python stack
   242     """Flatten chained infix operations to reduce usage of Python stack
   337     ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
   337     ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
   338     >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
   338     >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
   339     ('and', ('symbol', '1'), ('not', ('symbol', '2')))
   339     ('and', ('symbol', '1'), ('not', ('symbol', '2')))
   340     """
   340     """
   341     if not isinstance(placeholder, tuple):
   341     if not isinstance(placeholder, tuple):
   342         raise error.ProgrammingError('placeholder must be a node tuple')
   342         raise error.ProgrammingError(b'placeholder must be a node tuple')
   343     replstack = list(reversed(repls))
   343     replstack = list(reversed(repls))
   344     r = _buildtree(template, placeholder, replstack)
   344     r = _buildtree(template, placeholder, replstack)
   345     if replstack:
   345     if replstack:
   346         raise error.ProgrammingError('too many replacements')
   346         raise error.ProgrammingError(b'too many replacements')
   347     return r
   347     return r
   348 
   348 
   349 
   349 
   350 def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
   350 def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
   351     if pattern == tree:
   351     if pattern == tree:
   396     >>> _ = None
   396     >>> _ = None
   397     >>> f((b'func', (b'symbol', b'ancestors'), None),
   397     >>> f((b'func', (b'symbol', b'ancestors'), None),
   398     ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
   398     ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
   399     """
   399     """
   400     if placeholder is not None and not isinstance(placeholder, tuple):
   400     if placeholder is not None and not isinstance(placeholder, tuple):
   401         raise error.ProgrammingError('placeholder must be a node tuple')
   401         raise error.ProgrammingError(b'placeholder must be a node tuple')
   402     matches = [tree]
   402     matches = [tree]
   403     if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
   403     if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
   404         return matches
   404         return matches
   405 
   405 
   406 
   406 
   407 def parseerrordetail(inst):
   407 def parseerrordetail(inst):
   408     """Compose error message from specified ParseError object
   408     """Compose error message from specified ParseError object
   409     """
   409     """
   410     if len(inst.args) > 1:
   410     if len(inst.args) > 1:
   411         return _('at %d: %s') % (inst.args[1], inst.args[0])
   411         return _(b'at %d: %s') % (inst.args[1], inst.args[0])
   412     else:
   412     else:
   413         return inst.args[0]
   413         return inst.args[0]
   414 
   414 
   415 
   415 
   416 class alias(object):
   416 class alias(object):
   441     """
   441     """
   442 
   442 
   443     # typically a config section, which will be included in error messages
   443     # typically a config section, which will be included in error messages
   444     _section = None
   444     _section = None
   445     # tag of symbol node
   445     # tag of symbol node
   446     _symbolnode = 'symbol'
   446     _symbolnode = b'symbol'
   447 
   447 
   448     def __new__(cls):
   448     def __new__(cls):
   449         raise TypeError("'%s' is not instantiatable" % cls.__name__)
   449         raise TypeError(b"'%s' is not instantiatable" % cls.__name__)
   450 
   450 
   451     @staticmethod
   451     @staticmethod
   452     def _parse(spec):
   452     def _parse(spec):
   453         """Parse an alias name, arguments and definition"""
   453         """Parse an alias name, arguments and definition"""
   454         raise NotImplementedError
   454         raise NotImplementedError
   541             return (decl, None, parseerrordetail(inst))
   541             return (decl, None, parseerrordetail(inst))
   542 
   542 
   543         if tree[0] == cls._symbolnode:
   543         if tree[0] == cls._symbolnode:
   544             # "name = ...." style
   544             # "name = ...." style
   545             name = tree[1]
   545             name = tree[1]
   546             if name.startswith('$'):
   546             if name.startswith(b'$'):
   547                 return (decl, None, _("invalid symbol '%s'") % name)
   547                 return (decl, None, _(b"invalid symbol '%s'") % name)
   548             return (name, None, None)
   548             return (name, None, None)
   549 
   549 
   550         func = cls._trygetfunc(tree)
   550         func = cls._trygetfunc(tree)
   551         if func:
   551         if func:
   552             # "name(arg, ....) = ...." style
   552             # "name(arg, ....) = ...." style
   553             name, args = func
   553             name, args = func
   554             if name.startswith('$'):
   554             if name.startswith(b'$'):
   555                 return (decl, None, _("invalid function '%s'") % name)
   555                 return (decl, None, _(b"invalid function '%s'") % name)
   556             if any(t[0] != cls._symbolnode for t in args):
   556             if any(t[0] != cls._symbolnode for t in args):
   557                 return (decl, None, _("invalid argument list"))
   557                 return (decl, None, _(b"invalid argument list"))
   558             if len(args) != len(set(args)):
   558             if len(args) != len(set(args)):
   559                 return (name, None, _("argument names collide with each other"))
   559                 return (
       
   560                     name,
       
   561                     None,
       
   562                     _(b"argument names collide with each other"),
       
   563                 )
   560             return (name, [t[1] for t in args], None)
   564             return (name, [t[1] for t in args], None)
   561 
   565 
   562         return (decl, None, _("invalid format"))
   566         return (decl, None, _(b"invalid format"))
   563 
   567 
   564     @classmethod
   568     @classmethod
   565     def _relabelargs(cls, tree, args):
   569     def _relabelargs(cls, tree, args):
   566         """Mark alias arguments as ``_aliasarg``"""
   570         """Mark alias arguments as ``_aliasarg``"""
   567         if not isinstance(tree, tuple):
   571         if not isinstance(tree, tuple):
   571             return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:])
   575             return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:])
   572 
   576 
   573         assert len(tree) == 2
   577         assert len(tree) == 2
   574         sym = tree[1]
   578         sym = tree[1]
   575         if sym in args:
   579         if sym in args:
   576             op = '_aliasarg'
   580             op = b'_aliasarg'
   577         elif sym.startswith('$'):
   581         elif sym.startswith(b'$'):
   578             raise error.ParseError(_("invalid symbol '%s'") % sym)
   582             raise error.ParseError(_(b"invalid symbol '%s'") % sym)
   579         return (op, sym)
   583         return (op, sym)
   580 
   584 
   581     @classmethod
   585     @classmethod
   582     def _builddefn(cls, defn, args):
   586     def _builddefn(cls, defn, args):
   583         """Parse an alias definition into a tree and marks substitutions
   587         """Parse an alias definition into a tree and marks substitutions
   636     def build(cls, decl, defn):
   640     def build(cls, decl, defn):
   637         """Parse an alias declaration and definition into an alias object"""
   641         """Parse an alias declaration and definition into an alias object"""
   638         repl = efmt = None
   642         repl = efmt = None
   639         name, args, err = cls._builddecl(decl)
   643         name, args, err = cls._builddecl(decl)
   640         if err:
   644         if err:
   641             efmt = _('bad declaration of %(section)s "%(name)s": %(error)s')
   645             efmt = _(b'bad declaration of %(section)s "%(name)s": %(error)s')
   642         else:
   646         else:
   643             try:
   647             try:
   644                 repl = cls._builddefn(defn, args)
   648                 repl = cls._builddefn(defn, args)
   645             except error.ParseError as inst:
   649             except error.ParseError as inst:
   646                 err = parseerrordetail(inst)
   650                 err = parseerrordetail(inst)
   647                 efmt = _('bad definition of %(section)s "%(name)s": %(error)s')
   651                 efmt = _(b'bad definition of %(section)s "%(name)s": %(error)s')
   648         if err:
   652         if err:
   649             err = efmt % {'section': cls._section, 'name': name, 'error': err}
   653             err = efmt % {
       
   654                 b'section': cls._section,
       
   655                 b'name': name,
       
   656                 b'error': err,
       
   657             }
   650         return alias(name, args, err, repl)
   658         return alias(name, args, err, repl)
   651 
   659 
   652     @classmethod
   660     @classmethod
   653     def buildmap(cls, items):
   661     def buildmap(cls, items):
   654         """Parse a list of alias (name, replacement) pairs into a dict of
   662         """Parse a list of alias (name, replacement) pairs into a dict of
   684         """Replace _aliasarg instances with the substitution value of the
   692         """Replace _aliasarg instances with the substitution value of the
   685         same name in args, recursively.
   693         same name in args, recursively.
   686         """
   694         """
   687         if not isinstance(tree, tuple):
   695         if not isinstance(tree, tuple):
   688             return tree
   696             return tree
   689         if tree[0] == '_aliasarg':
   697         if tree[0] == b'_aliasarg':
   690             sym = tree[1]
   698             sym = tree[1]
   691             return args[sym]
   699             return args[sym]
   692         return tuple(cls._expandargs(t, args) for t in tree)
   700         return tuple(cls._expandargs(t, args) for t in tree)
   693 
   701 
   694     @classmethod
   702     @classmethod
   703         a, l = r
   711         a, l = r
   704         if a.error:
   712         if a.error:
   705             raise error.Abort(a.error)
   713             raise error.Abort(a.error)
   706         if a in expanding:
   714         if a in expanding:
   707             raise error.ParseError(
   715             raise error.ParseError(
   708                 _('infinite expansion of %(section)s ' '"%(name)s" detected')
   716                 _(b'infinite expansion of %(section)s ' b'"%(name)s" detected')
   709                 % {'section': cls._section, 'name': a.name}
   717                 % {b'section': cls._section, b'name': a.name}
   710             )
   718             )
   711         # get cacheable replacement tree by expanding aliases recursively
   719         # get cacheable replacement tree by expanding aliases recursively
   712         expanding.append(a)
   720         expanding.append(a)
   713         if a.name not in cache:
   721         if a.name not in cache:
   714             cache[a.name] = cls._expand(
   722             cache[a.name] = cls._expand(
   719         if a.args is None:
   727         if a.args is None:
   720             return result
   728             return result
   721         # substitute function arguments in replacement tree
   729         # substitute function arguments in replacement tree
   722         if len(l) != len(a.args):
   730         if len(l) != len(a.args):
   723             raise error.ParseError(
   731             raise error.ParseError(
   724                 _('invalid number of arguments: %d') % len(l)
   732                 _(b'invalid number of arguments: %d') % len(l)
   725             )
   733             )
   726         l = [cls._expand(aliases, t, [], cache) for t in l]
   734         l = [cls._expand(aliases, t, [], cache) for t in l]
   727         return cls._expandargs(result, dict(zip(a.args, l)))
   735         return cls._expandargs(result, dict(zip(a.args, l)))
   728 
   736 
   729     @classmethod
   737     @classmethod