comber 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
comber/parser.py ADDED
@@ -0,0 +1,335 @@
1
+ """
2
+ Base parser definitions.
3
+ """
4
+ from typing import Optional, Callable, Any
5
+ from abc import abstractmethod
6
+
7
+
8
+ class Expect:
9
+ """ Internal state of expect calculation """
10
+ def __init__(self) -> None:
11
+ self._recurseStack:list[list] = [[]]
12
+
13
+ def pushParser(self, parser:Any) -> None:
14
+ """
15
+ Push the current parser.
16
+ """
17
+ self._recurseStack[-1].append(parser)
18
+
19
+ def popParser(self) -> None:
20
+ """
21
+ Pop the last parser.
22
+ """
23
+ self._recurseStack[-1].pop()
24
+
25
+ def inRecursion(self, parser:Any) -> bool:
26
+ """
27
+ See if we're already trying to parse a given parser.
28
+ """
29
+ return not parser.recurse and parser in self._recurseStack[-1]
30
+
31
+
32
+ class State:
33
+ """
34
+ Internal parse state.
35
+ """
36
+ def __init__(self,
37
+ text:str,
38
+ whitespace:str|None,
39
+
40
+ line:int = 1,
41
+ char:int = 1,
42
+ tree:list[list]|None = None,
43
+ recurseStack:list[set[int]]|None = None,
44
+ ) -> None:
45
+ self.text = text
46
+ """ Unparsed input """
47
+ self.line = line
48
+ """ Current line offset into the input text (starts at 1) """
49
+ self.char = char
50
+ """ Current character offset into the current line (starts at 1) """
51
+ self.eof = False
52
+ """ True if we have it the end of the text """
53
+ self._tree:list[list] = tree if tree is not None else [[]]
54
+ self._recurseStack:list[set[int]] = recurseStack if recurseStack is not None else [set()]
55
+ self._whitespace = whitespace
56
+
57
+ @property
58
+ def tree(self) -> list:
59
+ """
60
+ The current parse tree
61
+ """
62
+ return self._tree[0]
63
+
64
+ def eatWhite(self) -> None:
65
+ """
66
+ Consume the leading whitespace, if whitespace was defined.
67
+ """
68
+ if self._whitespace:
69
+ text = self.text.lstrip(self._whitespace)
70
+ eaten = self.text[0:len(self.text) - len(text)]
71
+ lines = eaten.count('\n')
72
+ self.text = text
73
+ self.line += lines
74
+ self.char = \
75
+ len(eaten) - (eaten.rfind('\n') + 1) \
76
+ if lines \
77
+ else self.char + len(eaten)
78
+ self.eof = not self.text
79
+
80
+ def consume(self, length:int) -> None:
81
+ """
82
+ Consume a number of characters in the stream.
83
+ """
84
+ eaten = text = self.text[0:length]
85
+ self.text = self.text[length:]
86
+
87
+ if self._whitespace:
88
+ stripped = self.text.lstrip(self._whitespace)
89
+ eaten = text + self.text[0:len(self.text) - len(stripped)]
90
+ self.text = stripped
91
+
92
+ lines = eaten.count('\n')
93
+
94
+ self.line += lines
95
+ self.char = \
96
+ length - (eaten.rfind('\n') + 1) \
97
+ if lines \
98
+ else self.char + length
99
+
100
+ self._tree[-1].append(text)
101
+ self.eof = not self.text
102
+
103
+ def pushLeaf(self, value:Any) -> None:
104
+ """
105
+ Push a value onto the current stack branch.
106
+ """
107
+ self._tree[-1].append(value)
108
+
109
+ def pushBranch(self) -> None:
110
+ """
111
+ Push a new stack branch.
112
+ """
113
+ self._tree.append([])
114
+
115
+ def popBranch(self) -> list:
116
+ """
117
+ Pop a stack branch off the tree stack
118
+ """
119
+ return self._tree.pop()
120
+
121
+ def pushState(self) -> 'State':
122
+ """
123
+ Extend this state.
124
+ """
125
+ stack = list(self._recurseStack)
126
+ stack[-1] = set(stack[-1])
127
+ tree = list(self._tree)
128
+ tree.append([])
129
+ state = State(
130
+ self.text,
131
+ self._whitespace,
132
+ self.line,
133
+ self.char,
134
+ tree,
135
+ stack
136
+ )
137
+
138
+ return state
139
+
140
+ def popState(self) -> 'State':
141
+ """
142
+ Collapse an extended state.
143
+ """
144
+ popped = self.popBranch()
145
+ self._tree[-1] += popped
146
+ return self
147
+
148
+ def pushParser(self, parser:'Parser') -> None:
149
+ """
150
+ Push the current parser.
151
+ """
152
+ self._recurseStack[-1].add(id(parser))
153
+
154
+ def popParser(self, parser:'Parser') -> None:
155
+ """
156
+ Pop the last parser.
157
+ """
158
+ self._recurseStack[-1].remove(id(parser))
159
+
160
+ def shiftParser(self) -> None:
161
+ """
162
+ Create a new parser stack because we're looking for the element in a sequence
163
+ """
164
+ self._recurseStack.append(set())
165
+
166
+ def unshiftParser(self) -> None:
167
+ """
168
+ Toss out the current parser stack.
169
+ """
170
+ self._recurseStack.pop()
171
+
172
+ def inRecursion(self, parser:Any) -> bool:
173
+ """
174
+ See if we're already trying to parse a given parser.
175
+ """
176
+ return not parser.recurse and id(parser) in self._recurseStack[-1]
177
+
178
+
179
+ class ParseError(Exception):
180
+ """
181
+ When a string cannot be parsed, this exception is thrown.
182
+ """
183
+ def __init__(self, state:State, parser:'Parser') -> None:
184
+ super().__init__('Unexpected text')
185
+ self.line = state.line
186
+ """ The input line the error occurred at. """
187
+ self.char = state.char
188
+ """ The character offset into the line the error occurred at. """
189
+ self.text = state.text
190
+ """ The unparsed input text. """
191
+ self.parser = parser
192
+ """ The parser that failed. """
193
+
194
+ @property
195
+ def expected(self) -> list[str]:
196
+ """ The possible next tokens """
197
+ return list(set(self.parser.expectCore()))
198
+
199
+ @property
200
+ def message(self) -> str:
201
+ """ A lazy version of the exception message. """
202
+ return str(self.line)+":"+str(self.char)+": " \
203
+ +'Unexpected text: ' \
204
+ +self.text[0:10] \
205
+ +'. Expected one of: ' \
206
+ +', '.join(self.expected)
207
+
208
+ def __str__(self) -> str:
209
+ return self.message
210
+
211
+
212
+ class EndOfInputError(ParseError):
213
+ """
214
+ When we reach the end of input before completing a full parse.
215
+ """
216
+
217
+ @property
218
+ def message(self) -> str:
219
+ return 'Unexpected end of input. Expected one of: ' \
220
+ +', '.join(self.expected)
221
+
222
+
223
+ Emitter = Callable[[list[Any]], Any]
224
+ """
225
+ Type of emitter functions.
226
+ """
227
+
228
+
229
+ class Parser:
230
+ """
231
+ Base parser.
232
+ """
233
+
234
+ recurse = False
235
+ """ If True, the parser class is allowed to recurse without any checks. """
236
+ compound = False
237
+ """ If True, the parser class is a compound class, and so may fail partway through. """
238
+
239
+ def __init__(self) -> None:
240
+ self.name:Optional[str] = None
241
+ """ Friendly name of this sub-parser """
242
+ self.emit:Optional[Emitter] = None
243
+ """ Internalizer function; if not provided, the result will be the parsed string """
244
+ self.whitespace:str|None = ' \t\n'
245
+ """ Default whitespace """
246
+
247
+ def __call__(self, text:str, whitespace:str|None=None) -> State:
248
+ """
249
+ Parse a string.
250
+ """
251
+ state = State(text, whitespace if whitespace is not None else self.whitespace)
252
+ state.eatWhite()
253
+ return self.parseCore(state)
254
+
255
+
256
+ def parseCore(self, state:State) -> State:
257
+ """
258
+ Internal parse function, for calling by subparsers.
259
+ """
260
+ if self.emit:
261
+ state.pushBranch()
262
+
263
+ if not self.recurse:
264
+ state.pushParser(self)
265
+
266
+ try:
267
+ newState = self.recognize(state)
268
+ except ParseError:
269
+ # Nothing that Can recurse will actually throw
270
+ #if not self.recurse:
271
+ state.popParser(self)
272
+ raise
273
+
274
+ if newState is None:
275
+ if state.eof:
276
+ raise EndOfInputError(state, self)
277
+ else:
278
+ raise ParseError(state, self)
279
+
280
+ if not self.recurse:
281
+ newState.popParser(self)
282
+
283
+ if self.emit is not None:
284
+ value = self.emit(newState.popBranch())
285
+ newState.pushLeaf(value)
286
+
287
+ return newState
288
+
289
+
290
+ def expectCore(self, state:Expect|None = None) -> list[str]:
291
+ """
292
+ If this parser has a name, then a list containing only its name, otherwise the value returned by expect
293
+ """
294
+ state = state or Expect()
295
+ if state.inRecursion(self):
296
+ expecting = []
297
+ else:
298
+ state.pushParser(self)
299
+ expecting = [self.name] if self.name else self.expect(state)
300
+ state.popParser()
301
+ return expecting
302
+
303
+ def __repr__(self) -> str:
304
+ """
305
+ A string representation of the combinator
306
+ """
307
+ if self.name:
308
+ return f"@{self.name}"
309
+ return self.repr()
310
+
311
+
312
+ def analyze(self) -> None:
313
+ """
314
+ Analyze the grammar to improve performance.
315
+ """
316
+
317
+
318
+ @abstractmethod
319
+ def expect(self, state:Expect) -> list[str]:
320
+ """
321
+ Strings representing what's expected by this parser.
322
+ """
323
+
324
+
325
+ @abstractmethod
326
+ def recognize(self, state:State) -> Optional[State]:
327
+ """
328
+ Core parse function of a parser.
329
+ """
330
+
331
+ @abstractmethod
332
+ def repr(self) -> str:
333
+ """
334
+ The specific combinator string represenation.
335
+ """
comber/py.typed ADDED
File without changes