• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import re
2
3from ..info import KIND, ParsedItem, FileInfo
4
5
6class TextInfo:
7
8    def __init__(self, text, start=None, end=None):
9        # immutable:
10        if not start:
11            start = 1
12        self.start = start
13
14        # mutable:
15        lines = text.splitlines() or ['']
16        self.text = text.strip()
17        if not end:
18            end = start + len(lines) - 1
19        self.end = end
20        self.line = lines[-1]
21
22    def __repr__(self):
23        args = (f'{a}={getattr(self, a)!r}'
24                for a in ['text', 'start', 'end'])
25        return f'{type(self).__name__}({", ".join(args)})'
26
27    def add_line(self, line, lno=None):
28        if lno is None:
29            lno = self.end + 1
30        else:
31            if isinstance(lno, FileInfo):
32                fileinfo = lno
33                if fileinfo.filename != self.filename:
34                    raise NotImplementedError((fileinfo, self.filename))
35                lno = fileinfo.lno
36            # XXX
37            #if lno < self.end:
38            #    raise NotImplementedError((lno, self.end))
39        line = line.lstrip()
40        self.text += ' ' + line
41        self.line = line
42        self.end = lno
43
44
45class SourceInfo:
46
47    _ready = False
48
49    def __init__(self, filename, _current=None):
50        # immutable:
51        self.filename = filename
52        # mutable:
53        if isinstance(_current, str):
54            _current = TextInfo(_current)
55        self._current = _current
56        start = -1
57        self._start = _current.start if _current else -1
58        self._nested = []
59        self._set_ready()
60
61    def __repr__(self):
62        args = (f'{a}={getattr(self, a)!r}'
63                for a in ['filename', '_current'])
64        return f'{type(self).__name__}({", ".join(args)})'
65
66    @property
67    def start(self):
68        if self._current is None:
69            return self._start
70        return self._current.start
71
72    @property
73    def end(self):
74        if self._current is None:
75            return self._start
76        return self._current.end
77
78    @property
79    def text(self):
80        if self._current is None:
81            return ''
82        return self._current.text
83
84    def nest(self, text, before, start=None):
85        if self._current is None:
86            raise Exception('nesting requires active source text')
87        current = self._current
88        current.text = before
89        self._nested.append(current)
90        self._replace(text, start)
91
92    def resume(self, remainder=None):
93        if not self._nested:
94            raise Exception('no nested text to resume')
95        if self._current is None:
96            raise Exception('un-nesting requires active source text')
97        if remainder is None:
98            remainder = self._current.text
99        self._clear()
100        self._current = self._nested.pop()
101        self._current.text += ' ' + remainder
102        self._set_ready()
103
104    def advance(self, remainder, start=None):
105        if self._current is None:
106            raise Exception('advancing requires active source text')
107        if remainder.strip():
108            self._replace(remainder, start, fixnested=True)
109        else:
110            if self._nested:
111                self._replace('', start, fixnested=True)
112                #raise Exception('cannot advance while nesting')
113            else:
114                self._clear(start)
115
116    def resolve(self, kind, data, name, parent=None):
117        # "field" isn't a top-level kind, so we leave it as-is.
118        if kind and kind != 'field':
119            kind = KIND._from_raw(kind)
120        fileinfo = FileInfo(self.filename, self._start)
121        return ParsedItem(fileinfo, kind, parent, name, data)
122
123    def done(self):
124        self._set_ready()
125
126    def too_much(self, maxtext, maxlines):
127        if maxtext and len(self.text) > maxtext:
128            pass
129        elif maxlines and self.end - self.start > maxlines:
130            pass
131        else:
132            return False
133
134        #if re.fullmatch(r'[^;]+\[\][ ]*=[ ]*[{]([ ]*\d+,)*([ ]*\d+,?)\s*',
135        #                self._current.text):
136        #    return False
137        return True
138
139    def _set_ready(self):
140        if self._current is None:
141            self._ready = False
142        else:
143            self._ready = self._current.text.strip() != ''
144
145    def _used(self):
146        ready = self._ready
147        self._ready = False
148        return ready
149
150    def _clear(self, start=None):
151        old = self._current
152        if self._current is not None:
153            # XXX Fail if self._current wasn't used up?
154            if start is None:
155                start = self._current.end
156            self._current = None
157        if start is not None:
158            self._start = start
159        self._set_ready()
160        return old
161
162    def _replace(self, text, start=None, *, fixnested=False):
163        end = self._current.end
164        old = self._clear(start)
165        self._current = TextInfo(text, self._start, end)
166        if fixnested and self._nested and self._nested[-1] is old:
167            self._nested[-1] = self._current
168        self._set_ready()
169
170    def _add_line(self, line, lno=None):
171        if not line.strip():
172            # We don't worry about multi-line string literals.
173            return
174        if self._current is None:
175            self._start = lno
176            self._current = TextInfo(line, lno)
177        else:
178            # XXX
179            #if lno < self._current.end:
180            #    # A circular include?
181            #    raise NotImplementedError((lno, self))
182            self._current.add_line(line, lno)
183        self._ready = True
184