17db96d56Sopenharmony_ciimport re
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_cifrom ..info import KIND, ParsedItem, FileInfo
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciclass TextInfo:
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ci    def __init__(self, text, start=None, end=None):
97db96d56Sopenharmony_ci        # immutable:
107db96d56Sopenharmony_ci        if not start:
117db96d56Sopenharmony_ci            start = 1
127db96d56Sopenharmony_ci        self.start = start
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci        # mutable:
157db96d56Sopenharmony_ci        lines = text.splitlines() or ['']
167db96d56Sopenharmony_ci        self.text = text.strip()
177db96d56Sopenharmony_ci        if not end:
187db96d56Sopenharmony_ci            end = start + len(lines) - 1
197db96d56Sopenharmony_ci        self.end = end
207db96d56Sopenharmony_ci        self.line = lines[-1]
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ci    def __repr__(self):
237db96d56Sopenharmony_ci        args = (f'{a}={getattr(self, a)!r}'
247db96d56Sopenharmony_ci                for a in ['text', 'start', 'end'])
257db96d56Sopenharmony_ci        return f'{type(self).__name__}({", ".join(args)})'
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ci    def add_line(self, line, lno=None):
287db96d56Sopenharmony_ci        if lno is None:
297db96d56Sopenharmony_ci            lno = self.end + 1
307db96d56Sopenharmony_ci        else:
317db96d56Sopenharmony_ci            if isinstance(lno, FileInfo):
327db96d56Sopenharmony_ci                fileinfo = lno
337db96d56Sopenharmony_ci                if fileinfo.filename != self.filename:
347db96d56Sopenharmony_ci                    raise NotImplementedError((fileinfo, self.filename))
357db96d56Sopenharmony_ci                lno = fileinfo.lno
367db96d56Sopenharmony_ci            # XXX
377db96d56Sopenharmony_ci            #if lno < self.end:
387db96d56Sopenharmony_ci            #    raise NotImplementedError((lno, self.end))
397db96d56Sopenharmony_ci        line = line.lstrip()
407db96d56Sopenharmony_ci        self.text += ' ' + line
417db96d56Sopenharmony_ci        self.line = line
427db96d56Sopenharmony_ci        self.end = lno
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_ci
457db96d56Sopenharmony_ciclass SourceInfo:
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci    _ready = False
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_ci    def __init__(self, filename, _current=None):
507db96d56Sopenharmony_ci        # immutable:
517db96d56Sopenharmony_ci        self.filename = filename
527db96d56Sopenharmony_ci        # mutable:
537db96d56Sopenharmony_ci        if isinstance(_current, str):
547db96d56Sopenharmony_ci            _current = TextInfo(_current)
557db96d56Sopenharmony_ci        self._current = _current
567db96d56Sopenharmony_ci        start = -1
577db96d56Sopenharmony_ci        self._start = _current.start if _current else -1
587db96d56Sopenharmony_ci        self._nested = []
597db96d56Sopenharmony_ci        self._set_ready()
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci    def __repr__(self):
627db96d56Sopenharmony_ci        args = (f'{a}={getattr(self, a)!r}'
637db96d56Sopenharmony_ci                for a in ['filename', '_current'])
647db96d56Sopenharmony_ci        return f'{type(self).__name__}({", ".join(args)})'
657db96d56Sopenharmony_ci
667db96d56Sopenharmony_ci    @property
677db96d56Sopenharmony_ci    def start(self):
687db96d56Sopenharmony_ci        if self._current is None:
697db96d56Sopenharmony_ci            return self._start
707db96d56Sopenharmony_ci        return self._current.start
717db96d56Sopenharmony_ci
727db96d56Sopenharmony_ci    @property
737db96d56Sopenharmony_ci    def end(self):
747db96d56Sopenharmony_ci        if self._current is None:
757db96d56Sopenharmony_ci            return self._start
767db96d56Sopenharmony_ci        return self._current.end
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci    @property
797db96d56Sopenharmony_ci    def text(self):
807db96d56Sopenharmony_ci        if self._current is None:
817db96d56Sopenharmony_ci            return ''
827db96d56Sopenharmony_ci        return self._current.text
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ci    def nest(self, text, before, start=None):
857db96d56Sopenharmony_ci        if self._current is None:
867db96d56Sopenharmony_ci            raise Exception('nesting requires active source text')
877db96d56Sopenharmony_ci        current = self._current
887db96d56Sopenharmony_ci        current.text = before
897db96d56Sopenharmony_ci        self._nested.append(current)
907db96d56Sopenharmony_ci        self._replace(text, start)
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci    def resume(self, remainder=None):
937db96d56Sopenharmony_ci        if not self._nested:
947db96d56Sopenharmony_ci            raise Exception('no nested text to resume')
957db96d56Sopenharmony_ci        if self._current is None:
967db96d56Sopenharmony_ci            raise Exception('un-nesting requires active source text')
977db96d56Sopenharmony_ci        if remainder is None:
987db96d56Sopenharmony_ci            remainder = self._current.text
997db96d56Sopenharmony_ci        self._clear()
1007db96d56Sopenharmony_ci        self._current = self._nested.pop()
1017db96d56Sopenharmony_ci        self._current.text += ' ' + remainder
1027db96d56Sopenharmony_ci        self._set_ready()
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci    def advance(self, remainder, start=None):
1057db96d56Sopenharmony_ci        if self._current is None:
1067db96d56Sopenharmony_ci            raise Exception('advancing requires active source text')
1077db96d56Sopenharmony_ci        if remainder.strip():
1087db96d56Sopenharmony_ci            self._replace(remainder, start, fixnested=True)
1097db96d56Sopenharmony_ci        else:
1107db96d56Sopenharmony_ci            if self._nested:
1117db96d56Sopenharmony_ci                self._replace('', start, fixnested=True)
1127db96d56Sopenharmony_ci                #raise Exception('cannot advance while nesting')
1137db96d56Sopenharmony_ci            else:
1147db96d56Sopenharmony_ci                self._clear(start)
1157db96d56Sopenharmony_ci
1167db96d56Sopenharmony_ci    def resolve(self, kind, data, name, parent=None):
1177db96d56Sopenharmony_ci        # "field" isn't a top-level kind, so we leave it as-is.
1187db96d56Sopenharmony_ci        if kind and kind != 'field':
1197db96d56Sopenharmony_ci            kind = KIND._from_raw(kind)
1207db96d56Sopenharmony_ci        fileinfo = FileInfo(self.filename, self._start)
1217db96d56Sopenharmony_ci        return ParsedItem(fileinfo, kind, parent, name, data)
1227db96d56Sopenharmony_ci
1237db96d56Sopenharmony_ci    def done(self):
1247db96d56Sopenharmony_ci        self._set_ready()
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci    def too_much(self, maxtext, maxlines):
1277db96d56Sopenharmony_ci        if maxtext and len(self.text) > maxtext:
1287db96d56Sopenharmony_ci            pass
1297db96d56Sopenharmony_ci        elif maxlines and self.end - self.start > maxlines:
1307db96d56Sopenharmony_ci            pass
1317db96d56Sopenharmony_ci        else:
1327db96d56Sopenharmony_ci            return False
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci        #if re.fullmatch(r'[^;]+\[\][ ]*=[ ]*[{]([ ]*\d+,)*([ ]*\d+,?)\s*',
1357db96d56Sopenharmony_ci        #                self._current.text):
1367db96d56Sopenharmony_ci        #    return False
1377db96d56Sopenharmony_ci        return True
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_ci    def _set_ready(self):
1407db96d56Sopenharmony_ci        if self._current is None:
1417db96d56Sopenharmony_ci            self._ready = False
1427db96d56Sopenharmony_ci        else:
1437db96d56Sopenharmony_ci            self._ready = self._current.text.strip() != ''
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ci    def _used(self):
1467db96d56Sopenharmony_ci        ready = self._ready
1477db96d56Sopenharmony_ci        self._ready = False
1487db96d56Sopenharmony_ci        return ready
1497db96d56Sopenharmony_ci
1507db96d56Sopenharmony_ci    def _clear(self, start=None):
1517db96d56Sopenharmony_ci        old = self._current
1527db96d56Sopenharmony_ci        if self._current is not None:
1537db96d56Sopenharmony_ci            # XXX Fail if self._current wasn't used up?
1547db96d56Sopenharmony_ci            if start is None:
1557db96d56Sopenharmony_ci                start = self._current.end
1567db96d56Sopenharmony_ci            self._current = None
1577db96d56Sopenharmony_ci        if start is not None:
1587db96d56Sopenharmony_ci            self._start = start
1597db96d56Sopenharmony_ci        self._set_ready()
1607db96d56Sopenharmony_ci        return old
1617db96d56Sopenharmony_ci
1627db96d56Sopenharmony_ci    def _replace(self, text, start=None, *, fixnested=False):
1637db96d56Sopenharmony_ci        end = self._current.end
1647db96d56Sopenharmony_ci        old = self._clear(start)
1657db96d56Sopenharmony_ci        self._current = TextInfo(text, self._start, end)
1667db96d56Sopenharmony_ci        if fixnested and self._nested and self._nested[-1] is old:
1677db96d56Sopenharmony_ci            self._nested[-1] = self._current
1687db96d56Sopenharmony_ci        self._set_ready()
1697db96d56Sopenharmony_ci
1707db96d56Sopenharmony_ci    def _add_line(self, line, lno=None):
1717db96d56Sopenharmony_ci        if not line.strip():
1727db96d56Sopenharmony_ci            # We don't worry about multi-line string literals.
1737db96d56Sopenharmony_ci            return
1747db96d56Sopenharmony_ci        if self._current is None:
1757db96d56Sopenharmony_ci            self._start = lno
1767db96d56Sopenharmony_ci            self._current = TextInfo(line, lno)
1777db96d56Sopenharmony_ci        else:
1787db96d56Sopenharmony_ci            # XXX
1797db96d56Sopenharmony_ci            #if lno < self._current.end:
1807db96d56Sopenharmony_ci            #    # A circular include?
1817db96d56Sopenharmony_ci            #    raise NotImplementedError((lno, self))
1827db96d56Sopenharmony_ci            self._current.add_line(line, lno)
1837db96d56Sopenharmony_ci        self._ready = True
184