cpython/Tools/c-analyzer/c_parser/parser/_info.py

#import re

from ..info import KIND, ParsedItem, FileInfo


class TextInfo:

    def __init__(self, text, start=None, end=None):
        # immutable:
        if not start:
            start = 1
        self.start = start

        # mutable:
        lines = text.splitlines() or ['']
        self.text = text.strip()
        if not end:
            end = start + len(lines) - 1
        self.end = end
        self.line = lines[-1]

    def __repr__(self):
        args = (f'{a}={getattr(self, a)!r}'
                for a in ['text', 'start', 'end'])
        return f'{type(self).__name__}({", ".join(args)})'

    def add_line(self, line, lno=None):
        if lno is None:
            lno = self.end + 1
        else:
            if isinstance(lno, FileInfo):
                fileinfo = lno
                if fileinfo.filename != self.filename:
                    raise NotImplementedError((fileinfo, self.filename))
                lno = fileinfo.lno
            # XXX
            #if lno < self.end:
            #    raise NotImplementedError((lno, self.end))
        line = line.lstrip()
        self.text += ' ' + line
        self.line = line
        self.end = lno


class SourceInfo:

    _ready = False

    def __init__(self, filename, _current=None):
        # immutable:
        self.filename = filename
        # mutable:
        if isinstance(_current, str):
            _current = TextInfo(_current)
        self._current = _current
        start = -1
        self._start = _current.start if _current else -1
        self._nested = []
        self._set_ready()

    def __repr__(self):
        args = (f'{a}={getattr(self, a)!r}'
                for a in ['filename', '_current'])
        return f'{type(self).__name__}({", ".join(args)})'

    @property
    def start(self):
        if self._current is None:
            return self._start
        return self._current.start

    @property
    def end(self):
        if self._current is None:
            return self._start
        return self._current.end

    @property
    def text(self):
        if self._current is None:
            return ''
        return self._current.text

    def nest(self, text, before, start=None):
        if self._current is None:
            raise Exception('nesting requires active source text')
        current = self._current
        current.text = before
        self._nested.append(current)
        self._replace(text, start)

    def resume(self, remainder=None):
        if not self._nested:
            raise Exception('no nested text to resume')
        if self._current is None:
            raise Exception('un-nesting requires active source text')
        if remainder is None:
            remainder = self._current.text
        self._clear()
        self._current = self._nested.pop()
        self._current.text += ' ' + remainder
        self._set_ready()

    def advance(self, remainder, start=None):
        if self._current is None:
            raise Exception('advancing requires active source text')
        if remainder.strip():
            self._replace(remainder, start, fixnested=True)
        else:
            if self._nested:
                self._replace('', start, fixnested=True)
                #raise Exception('cannot advance while nesting')
            else:
                self._clear(start)

    def resolve(self, kind, data, name, parent=None):
        # "field" isn't a top-level kind, so we leave it as-is.
        if kind and kind != 'field':
            kind = KIND._from_raw(kind)
        fileinfo = FileInfo(self.filename, self._start)
        return ParsedItem(fileinfo, kind, parent, name, data)

    def done(self):
        self._set_ready()

    def too_much(self, maxtext, maxlines):
        if maxtext and len(self.text) > maxtext:
            pass
        elif maxlines and self.end - self.start > maxlines:
            pass
        else:
            return False

        #if re.fullmatch(r'[^;]+\[\][ ]*=[ ]*[{]([ ]*\d+,)*([ ]*\d+,?)\s*',
        #                self._current.text):
        #    return False
        return True

    def _set_ready(self):
        if self._current is None:
            self._ready = False
        else:
            self._ready = self._current.text.strip() != ''

    def _used(self):
        ready = self._ready
        self._ready = False
        return ready

    def _clear(self, start=None):
        old = self._current
        if self._current is not None:
            # XXX Fail if self._current wasn't used up?
            if start is None:
                start = self._current.end
            self._current = None
        if start is not None:
            self._start = start
        self._set_ready()
        return old

    def _replace(self, text, start=None, *, fixnested=False):
        end = self._current.end
        old = self._clear(start)
        self._current = TextInfo(text, self._start, end)
        if fixnested and self._nested and self._nested[-1] is old:
            self._nested[-1] = self._current
        self._set_ready()

    def _add_line(self, line, lno=None):
        if not line.strip():
            # We don't worry about multi-line string literals.
            return
        if self._current is None:
            self._start = lno
            self._current = TextInfo(line, lno)
        else:
            # XXX
            #if lno < self._current.end:
            #    # A circular include?
            #    raise NotImplementedError((lno, self))
            self._current.add_line(line, lno)
        self._ready = True