| #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_text(self, maxtext): | |
| return maxtext and len(self.text) > maxtext | |
| def too_many_lines(self, maxlines): | |
| return maxlines and self.end - self.start > maxlines | |
| def too_much(self, maxtext, maxlines): | |
| if self.too_much_text(maxtext): | |
| pass | |
| elif self.too_many_lines(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 |