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