Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
lexer.py964 linesDownload Raw Back to pygments
1"""2    pygments.lexer3    ~~~~~~~~~~~~~~4 5    Base lexer classes.6 7    :copyright: Copyright 2006-present by the Pygments team, see AUTHORS.8    :license: BSD, see LICENSE for details.9"""10 11import re12import sys13import time14 15from pygments.filter import apply_filters, Filter16from pygments.filters import get_filter_by_name17from pygments.token import Error, Text, Other, Whitespace, _TokenType18from pygments.util import get_bool_opt, get_int_opt, get_list_opt, \19    make_analysator, Future, guess_decode20from pygments.regexopt import regex_opt21 22__all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',23           'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',24           'default', 'words', 'line_re']25 26line_re = re.compile('.*?\n')27 28_encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),29                 (b'\xff\xfe\0\0', 'utf-32'),30                 (b'\0\0\xfe\xff', 'utf-32be'),31                 (b'\xff\xfe', 'utf-16'),32                 (b'\xfe\xff', 'utf-16be')]33 34_default_analyse = staticmethod(lambda x: 0.0)35 36 37class LexerMeta(type):38    """39    This metaclass automagically converts ``analyse_text`` methods into40    static methods which always return float values.41    """42 43    def __new__(mcs, name, bases, d):44        if 'analyse_text' in d:45            d['analyse_text'] = make_analysator(d['analyse_text'])46        return type.__new__(mcs, name, bases, d)47 48 49class Lexer(metaclass=LexerMeta):50    """51    Lexer for a specific language.52 53    See also :doc:`lexerdevelopment`, a high-level guide to writing54    lexers.55 56    Lexer classes have attributes used for choosing the most appropriate57    lexer based on various criteria.58 59    .. autoattribute:: name60       :no-value:61    .. autoattribute:: aliases62       :no-value:63    .. autoattribute:: filenames64       :no-value:65    .. autoattribute:: alias_filenames66    .. autoattribute:: mimetypes67       :no-value:68    .. autoattribute:: priority69 70    Lexers included in Pygments should have two additional attributes:71 72    .. autoattribute:: url73       :no-value:74    .. autoattribute:: version_added75       :no-value:76 77    Lexers included in Pygments may have additional attributes:78 79    .. autoattribute:: _example80       :no-value:81 82    You can pass options to the constructor. The basic options recognized83    by all lexers and processed by the base `Lexer` class are:84 85    ``stripnl``86        Strip leading and trailing newlines from the input (default: True).87    ``stripall``88        Strip all leading and trailing whitespace from the input89        (default: False).90    ``ensurenl``91        Make sure that the input ends with a newline (default: True).  This92        is required for some lexers that consume input linewise.93 94        .. versionadded:: 1.395 96    ``tabsize``97        If given and greater than 0, expand tabs in the input (default: 0).98    ``encoding``99        If given, must be an encoding name. This encoding will be used to100        convert the input string to Unicode, if it is not already a Unicode101        string (default: ``'guess'``, which uses a simple UTF-8 / Locale /102        Latin1 detection.  Can also be ``'chardet'`` to use the chardet103        library, if it is installed.104    ``inencoding``105        Overrides the ``encoding`` if given.106    """107 108    #: Full name of the lexer, in human-readable form109    name = None110 111    #: A list of short, unique identifiers that can be used to look112    #: up the lexer from a list, e.g., using `get_lexer_by_name()`.113    aliases = []114 115    #: A list of `fnmatch` patterns that match filenames which contain116    #: content for this lexer. The patterns in this list should be unique among117    #: all lexers.118    filenames = []119 120    #: A list of `fnmatch` patterns that match filenames which may or may not121    #: contain content for this lexer. This list is used by the122    #: :func:`.guess_lexer_for_filename()` function, to determine which lexers123    #: are then included in guessing the correct one. That means that124    #: e.g. every lexer for HTML and a template language should include125    #: ``\*.html`` in this list.126    alias_filenames = []127 128    #: A list of MIME types for content that can be lexed with this lexer.129    mimetypes = []130 131    #: Priority, should multiple lexers match and no content is provided132    priority = 0133 134    #: URL of the language specification/definition. Used in the Pygments135    #: documentation. Set to an empty string to disable.136    url = None137 138    #: Version of Pygments in which the lexer was added.139    version_added = None140 141    #: Example file name. Relative to the ``tests/examplefiles`` directory.142    #: This is used by the documentation generator to show an example.143    _example = None144 145    def __init__(self, **options):146        """147        This constructor takes arbitrary options as keyword arguments.148        Every subclass must first process its own options and then call149        the `Lexer` constructor, since it processes the basic150        options like `stripnl`.151 152        An example looks like this:153 154        .. sourcecode:: python155 156           def __init__(self, **options):157               self.compress = options.get('compress', '')158               Lexer.__init__(self, **options)159 160        As these options must all be specifiable as strings (due to the161        command line usage), there are various utility functions162        available to help with that, see `Utilities`_.163        """164        self.options = options165        self.stripnl = get_bool_opt(options, 'stripnl', True)166        self.stripall = get_bool_opt(options, 'stripall', False)167        self.ensurenl = get_bool_opt(options, 'ensurenl', True)168        self.tabsize = get_int_opt(options, 'tabsize', 0)169        self.encoding = options.get('encoding', 'guess')170        self.encoding = options.get('inencoding') or self.encoding171        self.filters = []172        for filter_ in get_list_opt(options, 'filters', ()):173            self.add_filter(filter_)174 175    def __repr__(self):176        if self.options:177            return f'<pygments.lexers.{self.__class__.__name__} with {self.options!r}>'178        else:179            return f'<pygments.lexers.{self.__class__.__name__}>'180 181    def add_filter(self, filter_, **options):182        """183        Add a new stream filter to this lexer.184        """185        if not isinstance(filter_, Filter):186            filter_ = get_filter_by_name(filter_, **options)187        self.filters.append(filter_)188 189    def analyse_text(text):190        """191        A static method which is called for lexer guessing.192 193        It should analyse the text and return a float in the range194        from ``0.0`` to ``1.0``.  If it returns ``0.0``, the lexer195        will not be selected as the most probable one, if it returns196        ``1.0``, it will be selected immediately.  This is used by197        `guess_lexer`.198 199        The `LexerMeta` metaclass automatically wraps this function so200        that it works like a static method (no ``self`` or ``cls``201        parameter) and the return value is automatically converted to202        `float`. If the return value is an object that is boolean `False`203        it's the same as if the return values was ``0.0``.204        """205 206    def _preprocess_lexer_input(self, text):207        """Apply preprocessing such as decoding the input, removing BOM and normalizing newlines."""208 209        if not isinstance(text, str):210            if self.encoding == 'guess':211                text, _ = guess_decode(text)212            elif self.encoding == 'chardet':213                try:214                    import chardet215                except ImportError as e:216                    raise ImportError('To enable chardet encoding guessing, '217                                      'please install the chardet library '218                                      'from http://chardet.feedparser.org/') from e219                # check for BOM first220                decoded = None221                for bom, encoding in _encoding_map:222                    if text.startswith(bom):223                        decoded = text[len(bom):].decode(encoding, 'replace')224                        break225                # no BOM found, so use chardet226                if decoded is None:227                    enc = chardet.detect(text[:1024])  # Guess using first 1KB228                    decoded = text.decode(enc.get('encoding') or 'utf-8',229                                          'replace')230                text = decoded231            else:232                text = text.decode(self.encoding)233                if text.startswith('\ufeff'):234                    text = text[len('\ufeff'):]235        else:236            if text.startswith('\ufeff'):237                text = text[len('\ufeff'):]238 239        # text now *is* a unicode string240        text = text.replace('\r\n', '\n')241        text = text.replace('\r', '\n')242        if self.stripall:243            text = text.strip()244        elif self.stripnl:245            text = text.strip('\n')246        if self.tabsize > 0:247            text = text.expandtabs(self.tabsize)248        if self.ensurenl and not text.endswith('\n'):249            text += '\n'250 251        return text252 253    def get_tokens(self, text, unfiltered=False):254        """255        This method is the basic interface of a lexer. It is called by256        the `highlight()` function. It must process the text and return an257        iterable of ``(tokentype, value)`` pairs from `text`.258 259        Normally, you don't need to override this method. The default260        implementation processes the options recognized by all lexers261        (`stripnl`, `stripall` and so on), and then yields all tokens262        from `get_tokens_unprocessed()`, with the ``index`` dropped.263 264        If `unfiltered` is set to `True`, the filtering mechanism is265        bypassed even if filters are defined.266        """267        text = self._preprocess_lexer_input(text)268 269        def streamer():270            for _, t, v in self.get_tokens_unprocessed(text):271                yield t, v272        stream = streamer()273        if not unfiltered:274            stream = apply_filters(stream, self.filters, self)275        return stream276 277    def get_tokens_unprocessed(self, text):278        """279        This method should process the text and return an iterable of280        ``(index, tokentype, value)`` tuples where ``index`` is the starting281        position of the token within the input text.282 283        It must be overridden by subclasses. It is recommended to284        implement it as a generator to maximize effectiveness.285        """286        raise NotImplementedError287 288 289class DelegatingLexer(Lexer):290    """291    This lexer takes two lexer as arguments. A root lexer and292    a language lexer. First everything is scanned using the language293    lexer, afterwards all ``Other`` tokens are lexed using the root294    lexer.295 296    The lexers from the ``template`` lexer package use this base lexer.297    """298 299    def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):300        self.root_lexer = _root_lexer(**options)301        self.language_lexer = _language_lexer(**options)302        self.needle = _needle303        Lexer.__init__(self, **options)304 305    def get_tokens_unprocessed(self, text):306        buffered = ''307        insertions = []308        lng_buffer = []309        for i, t, v in self.language_lexer.get_tokens_unprocessed(text):310            if t is self.needle:311                if lng_buffer:312                    insertions.append((len(buffered), lng_buffer))313                    lng_buffer = []314                buffered += v315            else:316                lng_buffer.append((i, t, v))317        if lng_buffer:318            insertions.append((len(buffered), lng_buffer))319        return do_insertions(insertions,320                             self.root_lexer.get_tokens_unprocessed(buffered))321 322 323# ------------------------------------------------------------------------------324# RegexLexer and ExtendedRegexLexer325#326 327 328class include(str):  # pylint: disable=invalid-name329    """330    Indicates that a state should include rules from another state.331    """332    pass333 334 335class _inherit:336    """337    Indicates the a state should inherit from its superclass.338    """339    def __repr__(self):340        return 'inherit'341 342inherit = _inherit()  # pylint: disable=invalid-name343 344 345class combined(tuple):  # pylint: disable=invalid-name346    """347    Indicates a state combined from multiple states.348    """349 350    def __new__(cls, *args):351        return tuple.__new__(cls, args)352 353    def __init__(self, *args):354        # tuple.__init__ doesn't do anything355        pass356 357 358class _PseudoMatch:359    """360    A pseudo match object constructed from a string.361    """362 363    def __init__(self, start, text):364        self._text = text365        self._start = start366 367    def start(self, arg=None):368        return self._start369 370    def end(self, arg=None):371        return self._start + len(self._text)372 373    def group(self, arg=None):374        if arg:375            raise IndexError('No such group')376        return self._text377 378    def groups(self):379        return (self._text,)380 381    def groupdict(self):382        return {}383 384 385def bygroups(*args):386    """387    Callback that yields multiple actions for each group in the match.388    """389    def callback(lexer, match, ctx=None):390        for i, action in enumerate(args):391            if action is None:392                continue393            elif type(action) is _TokenType:394                data = match.group(i + 1)395                if data:396                    yield match.start(i + 1), action, data397            else:398                data = match.group(i + 1)399                if data is not None:400                    if ctx:401                        ctx.pos = match.start(i + 1)402                    for item in action(lexer,403                                       _PseudoMatch(match.start(i + 1), data), ctx):404                        if item:405                            yield item406        if ctx:407            ctx.pos = match.end()408    return callback409 410 411class _This:412    """413    Special singleton used for indicating the caller class.414    Used by ``using``.415    """416 417this = _This()418 419 420def using(_other, **kwargs):421    """422    Callback that processes the match with a different lexer.423 424    The keyword arguments are forwarded to the lexer, except `state` which425    is handled separately.426 427    `state` specifies the state that the new lexer will start in, and can428    be an enumerable such as ('root', 'inline', 'string') or a simple429    string which is assumed to be on top of the root state.430 431    Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.432    """433    gt_kwargs = {}434    if 'state' in kwargs:435        s = kwargs.pop('state')436        if isinstance(s, (list, tuple)):437            gt_kwargs['stack'] = s438        else:439            gt_kwargs['stack'] = ('root', s)440 441    if _other is this:442        def callback(lexer, match, ctx=None):443            # if keyword arguments are given the callback444            # function has to create a new lexer instance445            if kwargs:446                # XXX: cache that somehow447                d = dict(lexer.options)448                d.update(kwargs)449                lx = lexer.__class__(**d)450            else:451                lx = lexer452            s = match.start()453            for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):454                yield i + s, t, v455            if ctx:456                ctx.pos = match.end()457    else:458        def callback(lexer, match, ctx=None):459            # XXX: cache that somehow460            d = dict(lexer.options)461            d.update(kwargs)462            lx = _other(**d)463 464            s = match.start()465            for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):466                yield i + s, t, v467            if ctx:468                ctx.pos = match.end()469    return callback470 471 472class default:473    """474    Indicates a state or state action (e.g. #pop) to apply.475    For example default('#pop') is equivalent to ('', Token, '#pop')476    Note that state tuples may be used as well.477 478    .. versionadded:: 2.0479    """480    def __init__(self, state):481        self.state = state482 483 484class words(Future):485    """486    Indicates a list of literal words that is transformed into an optimized487    regex that matches any of the words.488 489    .. versionadded:: 2.0490    """491    def __init__(self, words, prefix='', suffix=''):492        self.words = words493        self.prefix = prefix494        self.suffix = suffix495 496    def get(self):497        return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)498 499 500class RegexLexerMeta(LexerMeta):501    """502    Metaclass for RegexLexer, creates the self._tokens attribute from503    self.tokens on the first instantiation.504    """505 506    def _process_regex(cls, regex, rflags, state):507        """Preprocess the regular expression component of a token definition."""508        if isinstance(regex, Future):509            regex = regex.get()510        return re.compile(regex, rflags).match511 512    def _process_token(cls, token):513        """Preprocess the token component of a token definition."""514        assert type(token) is _TokenType or callable(token), \515            f'token type must be simple type or callable, not {token!r}'516        return token517 518    def _process_new_state(cls, new_state, unprocessed, processed):519        """Preprocess the state transition action of a token definition."""520        if isinstance(new_state, str):521            # an existing state522            if new_state == '#pop':523                return -1524            elif new_state in unprocessed:525                return (new_state,)526            elif new_state == '#push':527                return new_state528            elif new_state[:5] == '#pop:':529                return -int(new_state[5:])530            else:531                assert False, f'unknown new state {new_state!r}'532        elif isinstance(new_state, combined):533            # combine a new state from existing ones534            tmp_state = '_tmp_%d' % cls._tmpname535            cls._tmpname += 1536            itokens = []537            for istate in new_state:538                assert istate != new_state, f'circular state ref {istate!r}'539                itokens.extend(cls._process_state(unprocessed,540                                                  processed, istate))541            processed[tmp_state] = itokens542            return (tmp_state,)543        elif isinstance(new_state, tuple):544            # push more than one state545            for istate in new_state:546                assert (istate in unprocessed or547                        istate in ('#pop', '#push')), \548                    'unknown new state ' + istate549            return new_state550        else:551            assert False, f'unknown new state def {new_state!r}'552 553    def _process_state(cls, unprocessed, processed, state):554        """Preprocess a single state definition."""555        assert isinstance(state, str), f"wrong state name {state!r}"556        assert state[0] != '#', f"invalid state name {state!r}"557        if state in processed:558            return processed[state]559        tokens = processed[state] = []560        rflags = cls.flags561        for tdef in unprocessed[state]:562            if isinstance(tdef, include):563                # it's a state reference564                assert tdef != state, f"circular state reference {state!r}"565                tokens.extend(cls._process_state(unprocessed, processed,566                                                 str(tdef)))567                continue568            if isinstance(tdef, _inherit):569                # should be processed already, but may not in the case of:570                # 1. the state has no counterpart in any parent571                # 2. the state includes more than one 'inherit'572                continue573            if isinstance(tdef, default):574                new_state = cls._process_new_state(tdef.state, unprocessed, processed)575                tokens.append((re.compile('').match, None, new_state))576                continue577 578            assert type(tdef) is tuple, f"wrong rule def {tdef!r}"579 580            try:581                rex = cls._process_regex(tdef[0], rflags, state)582            except Exception as err:583                raise ValueError(f"uncompilable regex {tdef[0]!r} in state {state!r} of {cls!r}: {err}") from err584 585            token = cls._process_token(tdef[1])586 587            if len(tdef) == 2:588                new_state = None589            else:590                new_state = cls._process_new_state(tdef[2],591                                                   unprocessed, processed)592 593            tokens.append((rex, token, new_state))594        return tokens595 596    def process_tokendef(cls, name, tokendefs=None):597        """Preprocess a dictionary of token definitions."""598        processed = cls._all_tokens[name] = {}599        tokendefs = tokendefs or cls.tokens[name]600        for state in list(tokendefs):601            cls._process_state(tokendefs, processed, state)602        return processed603 604    def get_tokendefs(cls):605        """606        Merge tokens from superclasses in MRO order, returning a single tokendef607        dictionary.608 609        Any state that is not defined by a subclass will be inherited610        automatically.  States that *are* defined by subclasses will, by611        default, override that state in the superclass.  If a subclass wishes to612        inherit definitions from a superclass, it can use the special value613        "inherit", which will cause the superclass' state definition to be614        included at that point in the state.615        """616        tokens = {}617        inheritable = {}618        for c in cls.__mro__:619            toks = c.__dict__.get('tokens', {})620 621            for state, items in toks.items():622                curitems = tokens.get(state)623                if curitems is None:624                    # N.b. because this is assigned by reference, sufficiently625                    # deep hierarchies are processed incrementally (e.g. for626                    # A(B), B(C), C(RegexLexer), B will be premodified so X(B)627                    # will not see any inherits in B).628                    tokens[state] = items629                    try:630                        inherit_ndx = items.index(inherit)631                    except ValueError:632                        continue633                    inheritable[state] = inherit_ndx634                    continue635 636                inherit_ndx = inheritable.pop(state, None)637                if inherit_ndx is None:638                    continue639 640                # Replace the "inherit" value with the items641                curitems[inherit_ndx:inherit_ndx+1] = items642                try:643                    # N.b. this is the index in items (that is, the superclass644                    # copy), so offset required when storing below.645                    new_inh_ndx = items.index(inherit)646                except ValueError:647                    pass648                else:649                    inheritable[state] = inherit_ndx + new_inh_ndx650 651        return tokens652 653    def __call__(cls, *args, **kwds):654        """Instantiate cls after preprocessing its token definitions."""655        if '_tokens' not in cls.__dict__:656            cls._all_tokens = {}657            cls._tmpname = 0658            if hasattr(cls, 'token_variants') and cls.token_variants:659                # don't process yet660                pass661            else:662                cls._tokens = cls.process_tokendef('', cls.get_tokendefs())663 664        return type.__call__(cls, *args, **kwds)665 666 667class RegexLexer(Lexer, metaclass=RegexLexerMeta):668    """669    Base for simple stateful regular expression-based lexers.670    Simplifies the lexing process so that you need only671    provide a list of states and regular expressions.672    """673 674    #: Flags for compiling the regular expressions.675    #: Defaults to MULTILINE.676    flags = re.MULTILINE677 678    #: At all time there is a stack of states. Initially, the stack contains679    #: a single state 'root'. The top of the stack is called "the current state".680    #:681    #: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``682    #:683    #: ``new_state`` can be omitted to signify no state transition.684    #: If ``new_state`` is a string, it is pushed on the stack. This ensure685    #: the new current state is ``new_state``.686    #: If ``new_state`` is a tuple of strings, all of those strings are pushed687    #: on the stack and the current state will be the last element of the list.688    #: ``new_state`` can also be ``combined('state1', 'state2', ...)``689    #: to signify a new, anonymous state combined from the rules of two690    #: or more existing ones.691    #: Furthermore, it can be '#pop' to signify going back one step in692    #: the state stack, or '#push' to push the current state on the stack693    #: again. Note that if you push while in a combined state, the combined694    #: state itself is pushed, and not only the state in which the rule is695    #: defined.696    #:697    #: The tuple can also be replaced with ``include('state')``, in which698    #: case the rules from the state named by the string are included in the699    #: current one.700    tokens = {}701 702    def get_tokens_unprocessed(self, text, stack=('root',)):703        """704        Split ``text`` into (tokentype, text) pairs.705 706        ``stack`` is the initial stack (default: ``['root']``)707        """708        pos = 0709        tokendefs = self._tokens710        statestack = list(stack)711        statetokens = tokendefs[statestack[-1]]712        while 1:713            for rexmatch, action, new_state in statetokens:714                m = rexmatch(text, pos)715                if m:716                    if action is not None:717                        if type(action) is _TokenType:718                            yield pos, action, m.group()719                        else:720                            yield from action(self, m)721                    pos = m.end()722                    if new_state is not None:723                        # state transition724                        if isinstance(new_state, tuple):725                            for state in new_state:726                                if state == '#pop':727                                    if len(statestack) > 1:728                                        statestack.pop()729                                elif state == '#push':730                                    statestack.append(statestack[-1])731                                else:732                                    statestack.append(state)733                        elif isinstance(new_state, int):734                            # pop, but keep at least one state on the stack735                            # (random code leading to unexpected pops should736                            # not allow exceptions)737                            if abs(new_state) >= len(statestack):738                                del statestack[1:]739                            else:740                                del statestack[new_state:]741                        elif new_state == '#push':742                            statestack.append(statestack[-1])743                        else:744                            assert False, f"wrong state def: {new_state!r}"745                        statetokens = tokendefs[statestack[-1]]746                    break747            else:748                # We are here only if all state tokens have been considered749                # and there was not a match on any of them.750                try:751                    if text[pos] == '\n':752                        # at EOL, reset state to "root"753                        statestack = ['root']754                        statetokens = tokendefs['root']755                        yield pos, Whitespace, '\n'756                        pos += 1757                        continue758                    yield pos, Error, text[pos]759                    pos += 1760                except IndexError:761                    break762 763 764class LexerContext:765    """766    A helper object that holds lexer position data.767    """768 769    def __init__(self, text, pos, stack=None, end=None):770        self.text = text771        self.pos = pos772        self.end = end or len(text)  # end=0 not supported ;-)773        self.stack = stack or ['root']774 775    def __repr__(self):776        return f'LexerContext({self.text!r}, {self.pos!r}, {self.stack!r})'777 778 779class ExtendedRegexLexer(RegexLexer):780    """781    A RegexLexer that uses a context object to store its state.782    """783 784    def get_tokens_unprocessed(self, text=None, context=None):785        """786        Split ``text`` into (tokentype, text) pairs.787        If ``context`` is given, use this lexer context instead.788        """789        tokendefs = self._tokens790        if not context:791            ctx = LexerContext(text, 0)792            statetokens = tokendefs['root']793        else:794            ctx = context795            statetokens = tokendefs[ctx.stack[-1]]796            text = ctx.text797        while 1:798            for rexmatch, action, new_state in statetokens:799                m = rexmatch(text, ctx.pos, ctx.end)800                if m:801                    if action is not None:802                        if type(action) is _TokenType:803                            yield ctx.pos, action, m.group()804                            ctx.pos = m.end()805                        else:806                            yield from action(self, m, ctx)807                            if not new_state:808                                # altered the state stack?809                                statetokens = tokendefs[ctx.stack[-1]]810                    # CAUTION: callback must set ctx.pos!811                    if new_state is not None:812                        # state transition813                        if isinstance(new_state, tuple):814                            for state in new_state:815                                if state == '#pop':816                                    if len(ctx.stack) > 1:817                                        ctx.stack.pop()818                                elif state == '#push':819                                    ctx.stack.append(ctx.stack[-1])820                                else:821                                    ctx.stack.append(state)822                        elif isinstance(new_state, int):823                            # see RegexLexer for why this check is made824                            if abs(new_state) >= len(ctx.stack):825                                del ctx.stack[1:]826                            else:827                                del ctx.stack[new_state:]828                        elif new_state == '#push':829                            ctx.stack.append(ctx.stack[-1])830                        else:831                            assert False, f"wrong state def: {new_state!r}"832                        statetokens = tokendefs[ctx.stack[-1]]833                    break834            else:835                try:836                    if ctx.pos >= ctx.end:837                        break838                    if text[ctx.pos] == '\n':839                        # at EOL, reset state to "root"840                        ctx.stack = ['root']841                        statetokens = tokendefs['root']842                        yield ctx.pos, Text, '\n'843                        ctx.pos += 1844                        continue845                    yield ctx.pos, Error, text[ctx.pos]846                    ctx.pos += 1847                except IndexError:848                    break849 850 851def do_insertions(insertions, tokens):852    """853    Helper for lexers which must combine the results of several854    sublexers.855 856    ``insertions`` is a list of ``(index, itokens)`` pairs.857    Each ``itokens`` iterable should be inserted at position858    ``index`` into the token stream given by the ``tokens``859    argument.860 861    The result is a combined token stream.862 863    TODO: clean up the code here.864    """865    insertions = iter(insertions)866    try:867        index, itokens = next(insertions)868    except StopIteration:869        # no insertions870        yield from tokens871        return872 873    realpos = None874    insleft = True875 876    # iterate over the token stream where we want to insert877    # the tokens from the insertion list.878    for i, t, v in tokens:879        # first iteration. store the position of first item880        if realpos is None:881            realpos = i882        oldi = 0883        while insleft and i + len(v) >= index:884            tmpval = v[oldi:index - i]885            if tmpval:886                yield realpos, t, tmpval887                realpos += len(tmpval)888            for it_index, it_token, it_value in itokens:889                yield realpos, it_token, it_value890                realpos += len(it_value)891            oldi = index - i892            try:893                index, itokens = next(insertions)894            except StopIteration:895                insleft = False896                break  # not strictly necessary897        if oldi < len(v):898            yield realpos, t, v[oldi:]899            realpos += len(v) - oldi900 901    # leftover tokens902    while insleft:903        # no normal tokens, set realpos to zero904        realpos = realpos or 0905        for p, t, v in itokens:906            yield realpos, t, v907            realpos += len(v)908        try:909            index, itokens = next(insertions)910        except StopIteration:911            insleft = False912            break  # not strictly necessary913 914 915class ProfilingRegexLexerMeta(RegexLexerMeta):916    """Metaclass for ProfilingRegexLexer, collects regex timing info."""917 918    def _process_regex(cls, regex, rflags, state):919        if isinstance(regex, words):920            rex = regex_opt(regex.words, prefix=regex.prefix,921                            suffix=regex.suffix)922        else:923            rex = regex924        compiled = re.compile(rex, rflags)925 926        def match_func(text, pos, endpos=sys.maxsize):927            info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])928            t0 = time.time()929            res = compiled.match(text, pos, endpos)930            t1 = time.time()931            info[0] += 1932            info[1] += t1 - t0933            return res934        return match_func935 936 937class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):938    """Drop-in replacement for RegexLexer that does profiling of its regexes."""939 940    _prof_data = []941    _prof_sort_index = 4  # defaults to time per call942 943    def get_tokens_unprocessed(self, text, stack=('root',)):944        # this needs to be a stack, since using(this) will produce nested calls945        self.__class__._prof_data.append({})946        yield from RegexLexer.get_tokens_unprocessed(self, text, stack)947        rawdata = self.__class__._prof_data.pop()948        data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],949                        n, 1000 * t, 1000 * t / n)950                       for ((s, r), (n, t)) in rawdata.items()),951                      key=lambda x: x[self._prof_sort_index],952                      reverse=True)953        sum_total = sum(x[3] for x in data)954 955        print()956        print('Profiling result for %s lexing %d chars in %.3f ms' %957              (self.__class__.__name__, len(text), sum_total))958        print('=' * 110)959        print('%-20s %-64s ncalls  tottime  percall' % ('state', 'regex'))960        print('-' * 110)961        for d in data:962            print('%-20s %-65s %5d %8.4f %8.4f' % d)963        print('=' * 110)964 
codekingpro/portable-devtools · Team Ai