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/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Comber
3
+
4
+ """
5
+ from math import inf
6
+ from .parser import ParseError, EndOfInputError, Emitter
7
+ from .combinator import Combinator, C, Id, Lit, Seq, Choice, Repeat
8
+ from .extras import cs, rs, defer
comber/combinator.py ADDED
@@ -0,0 +1,352 @@
1
+ """
2
+ Combinator definitions.
3
+ """
4
+ from typing import cast, Optional, Tuple, List, Union, Any
5
+ import weakref
6
+ from math import inf
7
+ from abc import ABC
8
+ from .parser import Parser, State, Expect, Emitter, ParseError
9
+
10
+ Parseable = Union['Combinator', str]
11
+
12
+ class Combinator(Parser, ABC):
13
+ """
14
+ Combinator definitions.
15
+ """
16
+ def __matmul__(self, arg:Union[str, Emitter, Tuple[str, Emitter]]) -> 'Combinator':
17
+ if isinstance(arg, str):
18
+ self.name = arg
19
+ elif callable(arg):
20
+ self.emit = arg
21
+ elif isinstance(arg, tuple):
22
+ self.name = arg[0]
23
+ self.emit = arg[1]
24
+ else:
25
+ raise TypeError(
26
+ 'Expected name or name-emitter tuple, e.g. '\
27
+ 'combinator@"name", combinator@lambda x: int(x), or combinator@("name, lambda x: int(x)"')
28
+
29
+ return self
30
+
31
+ def __add__(self, right:Parseable) -> Parseable:
32
+ return Seq(self, right)
33
+
34
+ def __or__(self, right:Parseable) -> Parseable:
35
+ return Choice(self, right)
36
+
37
+ def __getitem__(self, args:Union[int,Tuple[int,int|float],Tuple[int,int|float,Parseable]]) -> 'Combinator':
38
+ minimum = args if isinstance(args, int) else args[0]
39
+ maximum = None if isinstance(args, int) else args[1]
40
+ separator = None if isinstance(args, int) or len(args) < 3 else cast(Tuple[int,int,Parseable], args)[2]
41
+ return Repeat(self, minimum, maximum, separator)
42
+
43
+ def __invert__(self) -> 'Combinator':
44
+ return Repeat(self, 0, 1, None)
45
+
46
+ def __pos__(self) -> 'Combinator':
47
+ return Repeat(self, 0, inf, None)
48
+
49
+ def __mul__(self, separator:Parseable) -> Parseable:
50
+ return Repeat(self, 0, inf, separator)
51
+
52
+ def simplify(self) -> 'Combinator':
53
+ """ Return a simplified version of the combinator """
54
+ return self
55
+
56
+ def analyze(self) -> None:
57
+ analyzed:set[Combinator] = set()
58
+ parsers:list[Combinator] = [self]
59
+
60
+ while parsers:
61
+ parser = parsers.pop()
62
+
63
+ if hasattr(parser, 'subparsers'):
64
+ subparsers = tuple(sub.simplify() for sub in getattr(parser, 'subparsers'))
65
+ setattr(parser, 'subparsers', subparsers)
66
+ parsers.extend(list(sub for sub in subparsers if sub not in analyzed))
67
+ analyzed.update(subparsers)
68
+
69
+ elif hasattr(parser, 'subparser'):
70
+ subparser = getattr(parser, 'subparser').simplify()
71
+ setattr(parser, 'subparser', subparser)
72
+ if parser not in analyzed:
73
+ parsers.append(subparser)
74
+ analyzed.add(subparser)
75
+
76
+
77
+ class Lit(Combinator):
78
+ """
79
+ A parser of an exact string
80
+ """
81
+ instances:weakref.WeakValueDictionary[str,'Lit'] = weakref.WeakValueDictionary()
82
+ recurse = True # As an optimization - there's no way Lit can recurse, so don't check
83
+
84
+ def __new__(cls, string:str) -> 'Lit':
85
+ if string not in cls.instances:
86
+ instance = super().__new__(cls)
87
+ cls.__init__(instance, string)
88
+ cls.instances[string] = instance
89
+ else:
90
+ instance = cls.instances[string]
91
+
92
+ return instance
93
+
94
+ def __init__(self, string:str) -> None:
95
+ super().__init__()
96
+ self.string = string
97
+ self._hash = hash(string)
98
+
99
+ def expect(self, state:Expect) -> List[str]:
100
+ return [self.string]
101
+
102
+ def recognize(self, state:State) -> Optional[State]:
103
+ if not state.text.startswith(self.string):
104
+ return None
105
+
106
+ state.consume(len(self.string))
107
+ return state
108
+
109
+ def __hash__(self) -> int:
110
+ return self._hash
111
+
112
+ def repr(self) -> str:
113
+ return f'Lit({self.string})'
114
+
115
+
116
+ def asCombinator(arg:Parseable) -> Combinator:
117
+ """
118
+ Ensure a value is a Combinator
119
+ """
120
+ if isinstance(arg, Combinator):
121
+ return arg
122
+ else:
123
+ return Lit(arg)
124
+
125
+ # TODO: Make a multi-parser parent of Seq and Choice
126
+
127
+ class Seq(Combinator):
128
+ """
129
+ A sequence of parsers.
130
+ """
131
+ compound = True
132
+
133
+ def __init__(self, left:Parseable, right:Parseable) -> None:
134
+ super().__init__()
135
+ self.subparsers:tuple[Combinator, ...]
136
+
137
+ # TODO: this doesn't flatten the rhs
138
+ if isinstance(left, Seq) and not left.emit:
139
+ subparsers = list(left.subparsers)
140
+ subparsers.append(asCombinator(right))
141
+ self.subparsers = tuple(subparsers)
142
+ else:
143
+ if left is not C:
144
+ self.subparsers = (asCombinator(left), asCombinator(right))
145
+ else:
146
+ self.subparsers = (asCombinator(right), )
147
+
148
+ self._hash:int = hash(self.subparsers)
149
+
150
+ def expect(self, state:Expect) -> List[str]:
151
+ return self.subparsers[0].expectCore(state)
152
+
153
+ def recognize(self, state:State) -> Optional[State]:
154
+ first = True
155
+ for parser in self.subparsers:
156
+ try:
157
+ if not first:
158
+ state.shiftParser()
159
+ state = parser.parseCore(state)
160
+ finally:
161
+ if not first:
162
+ state.unshiftParser()
163
+ else:
164
+ first = False
165
+
166
+ return state
167
+
168
+ def __add__(self, right:Parseable) -> Parseable:
169
+ subparsers = list(self.subparsers)
170
+ subparsers.append(asCombinator(right))
171
+ self.subparsers = tuple(subparsers)
172
+ self._hash = hash(self.subparsers)
173
+ return self
174
+
175
+ def __hash__(self) -> int:
176
+ return self._hash
177
+
178
+ def repr(self) -> str:
179
+ return f'Seq{self.subparsers}'
180
+
181
+
182
+ class Choice(Combinator):
183
+ """
184
+ Parse as the first successful parse.
185
+ """
186
+ recurse = True
187
+ compound = True
188
+
189
+ def __init__(self, left:Parseable, right:Parseable) -> None:
190
+ super().__init__()
191
+ self.subparsers:tuple[Combinator, ...]
192
+
193
+ if isinstance(left, Choice) and not left.emit:
194
+ subparsers = list(left.subparsers)
195
+ subparsers.append(asCombinator(right))
196
+ self.subparsers = tuple(subparsers)
197
+ else:
198
+ if left is not C:
199
+ self.subparsers = (asCombinator(left), asCombinator(right))
200
+ else:
201
+ self.subparsers = (asCombinator(right), )
202
+
203
+ self._hash:int = hash(self.subparsers)
204
+
205
+ def expect(self, state:Expect) -> List[str]:
206
+ return \
207
+ [ string
208
+ for subparser in self.subparsers
209
+ for string in subparser.expectCore(state)
210
+ ]
211
+
212
+ def recognize(self, state:State) -> Optional[State]:
213
+ bestState:State|None = None
214
+
215
+ for parser in self.subparsers:
216
+ if not state.inRecursion(parser):
217
+ try:
218
+ if parser.compound:
219
+ trialState = state.pushState()
220
+ else:
221
+ trialState = state
222
+
223
+ state = parser.parseCore(trialState)
224
+
225
+ if parser.compound:
226
+ state = state.popState()
227
+ bestState = state
228
+ break
229
+ except ParseError:
230
+ continue
231
+ return bestState
232
+
233
+ def __or__(self, right:Parseable) -> Parseable:
234
+ subparsers = list(self.subparsers)
235
+ subparsers.append(asCombinator(right))
236
+ self.subparsers = tuple(subparsers)
237
+ self._hash = hash(self.subparsers)
238
+ return self
239
+
240
+ def __hash__(self) -> int:
241
+ return self._hash
242
+
243
+ def repr(self) -> str:
244
+ return f'Choice{self.subparsers}'
245
+
246
+
247
+ class Repeat(Combinator):
248
+ """
249
+ Repeat a combinator.
250
+ """
251
+ compound = True
252
+
253
+ def __init__(self,
254
+ subparser:Combinator,
255
+ minimum:int,
256
+ maximum:Optional[int|float],
257
+ separator:Optional[Parseable]
258
+ ) -> None:
259
+ super().__init__()
260
+ self.subparser = subparser
261
+ self.minimum = minimum
262
+ self.maximum = maximum
263
+ self.separator = None if separator is None else asCombinator(separator)
264
+ self._hash = hash(hash(self.subparser)+hash(self.minimum)+hash(self.maximum))
265
+
266
+ def expect(self, state:Expect) -> List[str]:
267
+ return self.subparser.expectCore(state)
268
+
269
+ def recognize(self, state:State) -> Optional[State]:
270
+ parsed = 0
271
+
272
+ while parsed < self.minimum:
273
+ if parsed > 0 and self.separator:
274
+ state = self.separator.parseCore(state)
275
+ state = self.subparser.parseCore(state)
276
+ parsed += 1
277
+
278
+ if self.maximum is not None:
279
+ while parsed < self.maximum:
280
+ try:
281
+ mustPop = self.subparser.compound or self.separator and self.separator.compound
282
+ if mustPop:
283
+ trialState = state.pushState()
284
+ else:
285
+ trialState = state
286
+
287
+ if parsed > 0 and self.separator:
288
+ trialState = self.separator.parseCore(trialState)
289
+
290
+ state = self.subparser.parseCore(trialState)
291
+
292
+ if mustPop:
293
+ state = state.popState()
294
+
295
+ parsed += 1
296
+ except ParseError:
297
+ break
298
+
299
+ return state
300
+
301
+ def __hash__(self) -> int:
302
+ return self._hash
303
+
304
+ def repr(self) -> str:
305
+ return f'Repeat({self.subparser}, {self.minimum}, {self.maximum}, {self.separator})'
306
+
307
+
308
+ class Id(Combinator):
309
+ """
310
+ Parse exactly the subparser.
311
+ """
312
+ compound = True
313
+
314
+ def __init__(self, subparser:Parseable) -> None:
315
+ super().__init__()
316
+ self.subparser = asCombinator(subparser)
317
+
318
+ def expect(self, state:Expect) -> List[str]:
319
+ return self.subparser.expectCore(state)
320
+
321
+ def recognize(self, state:State) -> Optional[State]:
322
+ return self.subparser.parseCore(state)
323
+
324
+ def __eq__(self, right:Any) -> bool:
325
+ return isinstance(right, Id) and right.subparser == self.subparser
326
+
327
+ def __hash__(self) -> int:
328
+ return hash(self.subparser)
329
+
330
+ def repr(self) -> str:
331
+ return f'Id({self.subparser})'
332
+
333
+
334
+ class CClass(Combinator):
335
+ """
336
+ The combinator start's class. Don't instantiate.
337
+ """
338
+ def expect(self, state:Expect) -> List[str]:
339
+ return []
340
+
341
+ def recognize(self, state:State) -> Optional[State]:
342
+ return state
343
+
344
+ #pylint: disable=signature-differs
345
+ def __call__(self, arg:Parseable) -> Id:#type:ignore
346
+ return Id(arg)
347
+
348
+ def repr(self) -> str:
349
+ return 'C'
350
+
351
+
352
+ C = CClass()
comber/extras.py ADDED
@@ -0,0 +1,121 @@
1
+ """
2
+ Additional non-core parsers.
3
+ """
4
+ from typing import Iterable, List, Optional
5
+ import re
6
+ from .parser import State, Expect
7
+ from .combinator import Combinator, asCombinator
8
+
9
+ #pylint: disable=invalid-name
10
+ class cs(Combinator):
11
+ """
12
+ Parse one of a list of strings, or one of a character in a string.
13
+ """
14
+ recurse = True # As an optimization - there's no way cs can recurse, so don't check
15
+
16
+ def __init__(self, string:Iterable) -> None:
17
+ super().__init__()
18
+ self.string = tuple(set(string))
19
+
20
+ def expect(self, state:Expect) -> List[str]:
21
+ return list(self.string)
22
+
23
+ def recognize(self, state:State) -> Optional[State]:
24
+ for string in self.string:
25
+ if state.text.startswith(string):
26
+ state.consume(len(string))
27
+ return state
28
+
29
+ return None
30
+
31
+ def __hash__(self) -> int:
32
+ return hash(self.string)
33
+
34
+ def repr(self) -> str:
35
+ return f'cs({self.string})'
36
+
37
+
38
+ #pylint: disable=invalid-name
39
+ class rs(Combinator):
40
+ """
41
+ Parse using a regular expression
42
+ """
43
+ recurse = True # As an optimization - there's no way rs can recurse, so don't check
44
+
45
+ def __init__(self, regex:str, caseInsensitive=False) -> None:
46
+ super().__init__()
47
+ self.raw = regex
48
+ self.regex = re.compile(
49
+ self.raw,
50
+ re.IGNORECASE if caseInsensitive else 0)
51
+
52
+ def expect(self, state:Expect) -> List[str]:
53
+ return [f'/{self.raw}/']
54
+
55
+ def recognize(self, state:State) -> Optional[State]:
56
+ matched = self.regex.match(state.text)
57
+ if matched:
58
+ state.consume(len(matched[0]))
59
+ return state
60
+
61
+ return None
62
+
63
+ def __hash__(self) -> int:
64
+ return hash(self.raw)
65
+
66
+ def repr(self) -> str:
67
+ return f'rs({self.raw})'
68
+
69
+
70
+ #pylint: disable=invalid-name
71
+ class defer(Combinator):
72
+ """
73
+ A placeholder parser that can be filled in later with another parser.
74
+ Useful for recusive definitions.
75
+ """
76
+ recurse = False
77
+ compound = True
78
+
79
+ def __init__(self) -> None:
80
+ super().__init__()
81
+ self._coreparser:Optional[Combinator] = None
82
+
83
+ @property
84
+ def coreparser(self) -> Combinator:
85
+ """
86
+ The parser this defer parser is the stand-in for.
87
+ """
88
+ if self._coreparser is None:
89
+ message = 'Unfulfilled defer parser'
90
+ if self.name:
91
+ message += f' ({self.name})'
92
+ #pylint: disable=broad-exception-raised
93
+ raise Exception(message)
94
+
95
+ return self._coreparser
96
+
97
+ def fill(self, coreparser:Combinator) -> None:
98
+ """
99
+ Fill in the parser for this deferred parser.
100
+ """
101
+ self._coreparser = asCombinator(coreparser)
102
+
103
+ def expect(self, state:Expect) -> List[str]:
104
+ return self.coreparser.expect(state)
105
+
106
+ def recognize(self, state:State) -> Optional[State]:
107
+ raise NotImplementedError('Deferred parsers have no recogizer')
108
+
109
+ def parseCore(self, state:State) -> State:
110
+ return self.coreparser.parseCore(state)
111
+
112
+ def simplify(self) -> Combinator:
113
+ return self.coreparser
114
+
115
+ def __hash__(self) -> int:
116
+ # Where we care about this, we care about literal identity
117
+ return hash(id(self))
118
+
119
+ def repr(self) -> str:
120
+ return f'defer({self._coreparser})'
121
+