2020-10-30 18:46:52 -03:00
|
|
|
import re
|
|
|
|
|
2020-10-22 21:42:51 -03:00
|
|
|
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()
|
|
|
|
|
2020-10-30 18:46:52 -03:00
|
|
|
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
|
|
|
|
|
2020-10-22 21:42:51 -03:00
|
|
|
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
|