syncraft 0.1.28__tar.gz → 0.1.30__tar.gz
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.
- {syncraft-0.1.28 → syncraft-0.1.30}/PKG-INFO +1 -3
- {syncraft-0.1.28 → syncraft-0.1.30}/README.md +0 -2
- {syncraft-0.1.28 → syncraft-0.1.30}/pyproject.toml +1 -1
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/algebra.py +4 -1
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/ast.py +135 -144
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/finder.py +13 -13
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/generator.py +29 -18
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/parser.py +4 -2
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/syntax.py +2 -2
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/PKG-INFO +1 -3
- {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_bimap.py +34 -48
- {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_parse.py +6 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/LICENSE +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/setup.cfg +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/__init__.py +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/constraint.py +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/py.typed +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/sqlite3.py +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_until.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.30
|
|
4
4
|
Summary: Parser combinator library
|
|
5
5
|
Author-email: Michael Afmokt <michael@esacca.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -29,8 +29,6 @@ pip install syncraft
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
## TODO
|
|
32
|
-
- [ ] simplify the result of then_left and then_right by bimap the result in syntax.
|
|
33
|
-
- [ ] simplify the result of sep_by and between by bimap the result in syntax
|
|
34
32
|
- [ ] convert to dict/dataclass via bimap in syntax
|
|
35
33
|
- [ ] define DSL over Variable to construct predicates
|
|
36
34
|
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
@@ -14,8 +14,6 @@ pip install syncraft
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
## TODO
|
|
17
|
-
- [ ] simplify the result of then_left and then_right by bimap the result in syntax.
|
|
18
|
-
- [ ] simplify the result of sep_by and between by bimap the result in syntax
|
|
19
17
|
- [ ] convert to dict/dataclass via bimap in syntax
|
|
20
18
|
- [ ] define DSL over Variable to construct predicates
|
|
21
19
|
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
@@ -8,7 +8,10 @@ import traceback
|
|
|
8
8
|
from dataclasses import dataclass, replace, asdict
|
|
9
9
|
from weakref import WeakKeyDictionary
|
|
10
10
|
from abc import ABC
|
|
11
|
-
from syncraft.ast import ThenKind, Then,
|
|
11
|
+
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind
|
|
12
|
+
from syncraft.constraint import Bindable
|
|
13
|
+
|
|
14
|
+
S = TypeVar('S', bound=Bindable)
|
|
12
15
|
|
|
13
16
|
A = TypeVar('A') # Result type
|
|
14
17
|
B = TypeVar('B') # Mapped result type
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import re
|
|
5
5
|
from typing import (
|
|
6
|
-
Optional, Any, TypeVar, Tuple, runtime_checkable,
|
|
7
|
-
|
|
6
|
+
Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
|
|
7
|
+
Generic, Callable, Union, Protocol, Type, List, ClassVar
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, replace, is_dataclass, asdict, fields
|
|
12
12
|
from enum import Enum
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
@@ -18,7 +18,9 @@ A = TypeVar('A')
|
|
|
18
18
|
B = TypeVar('B')
|
|
19
19
|
C = TypeVar('C')
|
|
20
20
|
D = TypeVar('D')
|
|
21
|
-
S = TypeVar('S'
|
|
21
|
+
S = TypeVar('S')
|
|
22
|
+
S1 = TypeVar('S1')
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
@dataclass(frozen=True)
|
|
24
26
|
class Biarrow(Generic[A, B]):
|
|
@@ -79,20 +81,6 @@ class Lens(Generic[C, A]):
|
|
|
79
81
|
return other.__truediv__(self)
|
|
80
82
|
|
|
81
83
|
|
|
82
|
-
@dataclass(frozen=True)
|
|
83
|
-
class Reducer(Generic[A, S]):
|
|
84
|
-
run_f: Callable[[A, S], S]
|
|
85
|
-
def __call__(self, a: A, s: S) -> S:
|
|
86
|
-
return self.run_f(a, s)
|
|
87
|
-
|
|
88
|
-
def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
|
|
89
|
-
def map_run(b: B, s: S) -> S:
|
|
90
|
-
return self(f(b), s)
|
|
91
|
-
return Reducer(map_run)
|
|
92
|
-
|
|
93
|
-
def __rshift__(self, other: Reducer[A, S]) -> Reducer[A, S]:
|
|
94
|
-
return Reducer(lambda a, s: other(a, self(a, s)))
|
|
95
|
-
|
|
96
84
|
@dataclass(frozen=True)
|
|
97
85
|
class Bimap(Generic[A, B]):
|
|
98
86
|
run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
|
|
@@ -161,14 +149,36 @@ class Bimap(Generic[A, B]):
|
|
|
161
149
|
return abc, inv_f
|
|
162
150
|
return Bimap(when_run)
|
|
163
151
|
|
|
164
|
-
|
|
165
152
|
|
|
153
|
+
@dataclass(frozen=True)
|
|
154
|
+
class Reducer(Generic[A, S]):
|
|
155
|
+
run_f: Callable[[A, S], S]
|
|
156
|
+
def __call__(self, a: A, s: S) -> S:
|
|
157
|
+
return self.run_f(a, s)
|
|
166
158
|
|
|
159
|
+
def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
|
|
160
|
+
def map_run(b: B, s: S) -> S:
|
|
161
|
+
return self(f(b), s)
|
|
162
|
+
return Reducer(map_run)
|
|
163
|
+
|
|
164
|
+
def __rshift__(self, other: Reducer[A, S]) -> Reducer[A, S]:
|
|
165
|
+
return Reducer(lambda a, s: other(a, self(a, s)))
|
|
166
|
+
|
|
167
|
+
def zip(self, other: Reducer[A, S1])-> Reducer[A, Tuple[S, S1]]:
|
|
168
|
+
return Reducer(lambda a, s: (self(a, s[0]), other(a, s[1])))
|
|
169
|
+
|
|
170
|
+
def diff(self, other: Reducer[B, S]) -> Reducer[Tuple[A, B], S]:
|
|
171
|
+
return Reducer(lambda ab, s: other(ab[1], self(ab[0], s)))
|
|
172
|
+
|
|
173
|
+
def filter(self, f: Callable[[A, S], bool]) -> Reducer[A, S]:
|
|
174
|
+
return Reducer(lambda a, s: self(a, s) if f(a, s) else s)
|
|
175
|
+
|
|
176
|
+
|
|
167
177
|
|
|
168
178
|
@dataclass(frozen=True)
|
|
169
179
|
class AST:
|
|
170
|
-
def
|
|
171
|
-
return
|
|
180
|
+
def bimap(self, r: Bimap[Any, Any]=Bimap.identity()) -> Tuple[Any, Callable[[Any], Any]]:
|
|
181
|
+
return r(self)
|
|
172
182
|
|
|
173
183
|
@dataclass(frozen=True)
|
|
174
184
|
class Nothing(AST):
|
|
@@ -182,19 +192,10 @@ class Nothing(AST):
|
|
|
182
192
|
class Marked(Generic[A], AST):
|
|
183
193
|
name: str
|
|
184
194
|
value: A
|
|
185
|
-
def
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class DataclassProtocol(Protocol):
|
|
190
|
-
__dataclass_fields__: dict
|
|
191
|
-
|
|
192
|
-
E = TypeVar("E")
|
|
193
|
-
@dataclass(frozen=True)
|
|
194
|
-
class Collect(Generic[A, E], AST):
|
|
195
|
-
collector: Type[E]
|
|
196
|
-
value: A
|
|
197
|
-
|
|
195
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[Marked[B], Callable[[Marked[B]], Marked[A]]]:
|
|
196
|
+
v, inner_f = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
|
|
197
|
+
return Marked(name=self.name, value=v), lambda b: Marked(name = b.name, value=inner_f(b.value))
|
|
198
|
+
|
|
198
199
|
class ChoiceKind(Enum):
|
|
199
200
|
LEFT = 'left'
|
|
200
201
|
RIGHT = 'right'
|
|
@@ -203,49 +204,122 @@ class ChoiceKind(Enum):
|
|
|
203
204
|
class Choice(Generic[A, B], AST):
|
|
204
205
|
kind: Optional[ChoiceKind]
|
|
205
206
|
value: Optional[A | B] = None
|
|
206
|
-
def
|
|
207
|
-
if self.value is
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else
|
|
211
|
-
|
|
212
|
-
return s
|
|
207
|
+
def bimap(self, r: Bimap[A | B, C]=Bimap.identity()) -> Tuple[Optional[C], Callable[[Optional[C]], Choice[A, B]]]:
|
|
208
|
+
if self.value is None:
|
|
209
|
+
return None, lambda c: replace(self, value=None, kind=None)
|
|
210
|
+
else:
|
|
211
|
+
v, inv = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
|
|
212
|
+
return v, lambda c: replace(self, value=inv(c) if c is not None else None, kind=None)
|
|
213
213
|
|
|
214
214
|
@dataclass(frozen=True)
|
|
215
215
|
class Many(Generic[A], AST):
|
|
216
216
|
value: Tuple[A, ...]
|
|
217
|
-
def
|
|
218
|
-
for
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[List[B], Callable[[List[B]], Many[A]]]:
|
|
218
|
+
ret = [v.bimap(r) if isinstance(v, AST) else r(v) for v in self.value]
|
|
219
|
+
def inv(bs: List[B]) -> Many[A]:
|
|
220
|
+
if len(bs) <= len(ret):
|
|
221
|
+
return Many(value = tuple(ret[i][1](bs[i]) for i in range(len(bs))))
|
|
221
222
|
else:
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
half = [ret[i][1](bs[i]) for i in range(len(bs))]
|
|
224
|
+
tmp = [ret[-1][1](bs[i]) for i in range(len(ret)-1, len(bs))]
|
|
225
|
+
return Many(value = tuple(half + tmp))
|
|
226
|
+
return [v[0] for v in ret], inv
|
|
224
227
|
|
|
225
228
|
class ThenKind(Enum):
|
|
226
229
|
BOTH = '+'
|
|
227
230
|
LEFT = '//'
|
|
228
231
|
RIGHT = '>>'
|
|
229
232
|
|
|
230
|
-
FlatThen = Tuple[Any, ...]
|
|
231
|
-
MarkedThen = Tuple[Dict[str, Any] | Any, FlatThen]
|
|
232
|
-
|
|
233
233
|
@dataclass(eq=True, frozen=True)
|
|
234
234
|
class Then(Generic[A, B], AST):
|
|
235
235
|
kind: ThenKind
|
|
236
236
|
left: A
|
|
237
237
|
right: B
|
|
238
|
-
def
|
|
239
|
-
if
|
|
240
|
-
|
|
238
|
+
def arity(self)->int:
|
|
239
|
+
if self.kind == ThenKind.LEFT:
|
|
240
|
+
return self.left.arity() if isinstance(self.left, Then) else 1
|
|
241
|
+
elif self.kind == ThenKind.RIGHT:
|
|
242
|
+
return self.right.arity() if isinstance(self.right, Then) else 1
|
|
243
|
+
elif self.kind == ThenKind.BOTH:
|
|
244
|
+
left_arity = self.left.arity() if isinstance(self.left, Then) else 1
|
|
245
|
+
right_arity = self.right.arity() if isinstance(self.right, Then) else 1
|
|
246
|
+
return left_arity + right_arity
|
|
241
247
|
else:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
def bimap(self, r: Bimap[A|B, Any]=Bimap.identity()) -> Tuple[Any | Tuple[Any, ...], Callable[[Any | Tuple[Any, ...]], Then[A, B]]]:
|
|
251
|
+
def need_wrap(x: Any) -> bool:
|
|
252
|
+
return not (isinstance(x, Then) and x.kind == ThenKind.BOTH)
|
|
253
|
+
match self.kind:
|
|
254
|
+
case ThenKind.LEFT:
|
|
255
|
+
lb, linv = self.left.bimap(r) if isinstance(self.left, AST) else r(self.left)
|
|
256
|
+
return lb, lambda c: replace(self, left=cast(A, linv(c)))
|
|
257
|
+
case ThenKind.RIGHT:
|
|
258
|
+
rb, rinv = self.right.bimap(r) if isinstance(self.right, AST) else r(self.right)
|
|
259
|
+
return rb, lambda c: replace(self, right=cast(B, rinv(c)))
|
|
260
|
+
case ThenKind.BOTH:
|
|
261
|
+
lb, linv = self.left.bimap(r) if isinstance(self.left, AST) else r(self.left)
|
|
262
|
+
rb, rinv = self.right.bimap(r) if isinstance(self.right, AST) else r(self.right)
|
|
263
|
+
left_v = (lb,) if need_wrap(self.left) else lb
|
|
264
|
+
right_v = (rb,) if need_wrap(self.right) else rb
|
|
265
|
+
def invf(b: Tuple[C, ...]) -> Then[A, B]:
|
|
266
|
+
left_size = self.left.arity() if isinstance(self.left, Then) else 1
|
|
267
|
+
right_size = self.right.arity() if isinstance(self.right, Then) else 1
|
|
268
|
+
lraw: Tuple[Any, ...] = b[:left_size]
|
|
269
|
+
rraw: Tuple[Any, ...] = b[left_size:left_size + right_size]
|
|
270
|
+
lraw = lraw[0] if left_size == 1 else lraw
|
|
271
|
+
rraw = rraw[0] if right_size == 1 else rraw
|
|
272
|
+
la = linv(lraw)
|
|
273
|
+
ra = rinv(rraw)
|
|
274
|
+
return replace(self, left=cast(A, la), right=cast(B, ra))
|
|
275
|
+
return left_v + right_v, invf
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DataclassInstance(Protocol):
|
|
279
|
+
__dataclass_fields__: ClassVar[dict[str, Any]]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
E = TypeVar("E", bound=DataclassInstance)
|
|
283
|
+
|
|
284
|
+
Collector = Type[E] | Callable[..., E]
|
|
285
|
+
@dataclass(frozen=True)
|
|
286
|
+
class Collect(Generic[A, E], AST):
|
|
287
|
+
collector: Collector
|
|
288
|
+
value: A
|
|
289
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[B | E, Callable[[B | E], Collect[A, E]]]:
|
|
290
|
+
b, inner_f = r(self.value)
|
|
291
|
+
if isinstance(self.value, Then) and self.value.kind == ThenKind.BOTH:
|
|
292
|
+
assert isinstance(b, tuple), f"Expected tuple from Then.BOTH combinator, got {type(b)}"
|
|
293
|
+
index: List[str | int] = []
|
|
294
|
+
named_count = 0
|
|
295
|
+
for i, v in enumerate(b):
|
|
296
|
+
if isinstance(v, Marked):
|
|
297
|
+
index.append(v.name)
|
|
298
|
+
named_count += 1
|
|
299
|
+
else:
|
|
300
|
+
index.append(i - named_count)
|
|
301
|
+
named = {v.name: v.value for v in b if isinstance(v, Marked)}
|
|
302
|
+
unnamed = [v for v in b if not isinstance(v, Marked)]
|
|
303
|
+
ret: E = self.collector(*unnamed, **named)
|
|
304
|
+
def invf(e: E) -> Tuple[Any, ...]:
|
|
305
|
+
assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
|
|
306
|
+
named_dict = asdict(e)
|
|
307
|
+
unnamed = []
|
|
308
|
+
for f in fields(e):
|
|
309
|
+
if f.name not in named:
|
|
310
|
+
unnamed.append(named_dict[f.name])
|
|
311
|
+
tmp = []
|
|
312
|
+
for x in index:
|
|
313
|
+
if isinstance(x, str):
|
|
314
|
+
tmp.append(Marked(name=x, value=named_dict[x]))
|
|
315
|
+
else:
|
|
316
|
+
tmp.append(unnamed[x])
|
|
317
|
+
return tuple(tmp)
|
|
318
|
+
return ret, lambda e: replace(self, value=inner_f(invf(e))) # type: ignore
|
|
245
319
|
else:
|
|
246
|
-
|
|
247
|
-
return s
|
|
320
|
+
return b, lambda e: replace(self, value=inner_f(e)) # type: ignore
|
|
248
321
|
|
|
322
|
+
#########################################################################################################################
|
|
249
323
|
@dataclass(frozen=True)
|
|
250
324
|
class Token(AST):
|
|
251
325
|
token_type: Enum
|
|
@@ -255,11 +329,7 @@ class Token(AST):
|
|
|
255
329
|
|
|
256
330
|
def __repr__(self) -> str:
|
|
257
331
|
return self.__str__()
|
|
258
|
-
|
|
259
|
-
def walk(self, r: Reducer['Token', S], s: S) -> S:
|
|
260
|
-
return r(self, s)
|
|
261
|
-
|
|
262
|
-
|
|
332
|
+
|
|
263
333
|
@runtime_checkable
|
|
264
334
|
class TokenProtocol(Protocol):
|
|
265
335
|
@property
|
|
@@ -290,89 +360,10 @@ ParseResult = Union[
|
|
|
290
360
|
Marked['ParseResult[T]'],
|
|
291
361
|
Choice['ParseResult[T]', 'ParseResult[T]'],
|
|
292
362
|
Many['ParseResult[T]'],
|
|
363
|
+
Collect['ParseResult[T]', Any],
|
|
293
364
|
Nothing,
|
|
294
365
|
T,
|
|
295
366
|
]
|
|
296
367
|
|
|
297
368
|
|
|
298
369
|
|
|
299
|
-
"""
|
|
300
|
-
@staticmethod
|
|
301
|
-
def collect_marked(a: FlatThen, f: Optional[Callable[..., Any]] = None)->Tuple[MarkedThen, Callable[[MarkedThen], FlatThen]]:
|
|
302
|
-
index: List[str | int] = []
|
|
303
|
-
named_count = 0
|
|
304
|
-
for i, v in enumerate(a):
|
|
305
|
-
if isinstance(v, Marked):
|
|
306
|
-
index.append(v.name)
|
|
307
|
-
named_count += 1
|
|
308
|
-
else:
|
|
309
|
-
index.append(i - named_count)
|
|
310
|
-
named = {v.name: v.value for v in a if isinstance(v, Marked)}
|
|
311
|
-
unnamed = [v for v in a if not isinstance(v, Marked)]
|
|
312
|
-
if f is None:
|
|
313
|
-
ret = (named, tuple(unnamed))
|
|
314
|
-
else:
|
|
315
|
-
ret = (f(**named), tuple(unnamed))
|
|
316
|
-
def invf(b: MarkedThen) -> Tuple[Any, ...]:
|
|
317
|
-
named_value, unnamed_value = b
|
|
318
|
-
assert isinstance(named_value, dict) or is_dataclass(named_value), f"Expected dict or dataclass for named values, got {type(named_value)}"
|
|
319
|
-
if is_dataclass(named_value):
|
|
320
|
-
named_dict = named | asdict(cast(Any, named_value))
|
|
321
|
-
else:
|
|
322
|
-
named_dict = named | named_value
|
|
323
|
-
ret = []
|
|
324
|
-
for x in index:
|
|
325
|
-
if isinstance(x, str):
|
|
326
|
-
assert x in named_dict, f"Missing named value: {x}"
|
|
327
|
-
ret.append(named_dict[x])
|
|
328
|
-
else:
|
|
329
|
-
assert 0 <= x < len(unnamed_value), f"Missing unnamed value at index: {x}"
|
|
330
|
-
ret.append(unnamed_value[x])
|
|
331
|
-
return tuple(ret)
|
|
332
|
-
return ret, invf
|
|
333
|
-
|
|
334
|
-
def bimap(self, f: Bimap[Any, Any]=Bimap.identity()) -> Tuple[FlatThen, Callable[[FlatThen], Then[A, B]]]:
|
|
335
|
-
match self.kind:
|
|
336
|
-
case ThenKind.LEFT:
|
|
337
|
-
lb, linv = self.left.bimap(f) if isinstance(self.left, AST) else f(self.left)
|
|
338
|
-
return lb, lambda b: replace(self, left=linv(b))
|
|
339
|
-
case ThenKind.RIGHT:
|
|
340
|
-
rb, rinv = self.right.bimap(f) if isinstance(self.right, AST) else f(self.right)
|
|
341
|
-
return rb, lambda b: replace(self, right=rinv(b))
|
|
342
|
-
case ThenKind.BOTH:
|
|
343
|
-
lb, linv = self.left.bimap(f) if isinstance(self.left, AST) else f(self.left)
|
|
344
|
-
rb, rinv = self.right.bimap(f) if isinstance(self.right, AST) else f(self.right)
|
|
345
|
-
left_v = (lb,) if not isinstance(self.left, Then) else lb
|
|
346
|
-
right_v = (rb,) if not isinstance(self.right, Then) else rb
|
|
347
|
-
def invf(b: Tuple[Any, ...]) -> Then[A, B]:
|
|
348
|
-
left_size = self.left.arity() if isinstance(self.left, Then) else 1
|
|
349
|
-
right_size = self.right.arity() if isinstance(self.right, Then) else 1
|
|
350
|
-
lraw = b[:left_size]
|
|
351
|
-
rraw = b[left_size:left_size + right_size]
|
|
352
|
-
lraw = lraw[0] if left_size == 1 else lraw
|
|
353
|
-
rraw = rraw[0] if right_size == 1 else rraw
|
|
354
|
-
la = linv(lraw)
|
|
355
|
-
ra = rinv(rraw)
|
|
356
|
-
return replace(self, left=la, right=ra)
|
|
357
|
-
return left_v + right_v, invf
|
|
358
|
-
|
|
359
|
-
def bimap_collected(self, f: Bimap[Any, Any]=Bimap.identity()) -> Tuple[MarkedThen, Callable[[MarkedThen], Then[A, B]]]:
|
|
360
|
-
data, invf = self.bimap(f)
|
|
361
|
-
data, func = Then.collect_marked(data)
|
|
362
|
-
return data, lambda d: invf(func(d))
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
def arity(self)->int:
|
|
366
|
-
if self.kind == ThenKind.LEFT:
|
|
367
|
-
return self.left.arity() if isinstance(self.left, Then) else 1
|
|
368
|
-
elif self.kind == ThenKind.RIGHT:
|
|
369
|
-
return self.right.arity() if isinstance(self.right, Then) else 1
|
|
370
|
-
elif self.kind == ThenKind.BOTH:
|
|
371
|
-
left_arity = self.left.arity() if isinstance(self.left, Then) else 1
|
|
372
|
-
right_arity = self.right.arity() if isinstance(self.right, Then) else 1
|
|
373
|
-
return left_arity + right_arity
|
|
374
|
-
else:
|
|
375
|
-
return 1
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"""
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, Tuple, Generator as YieldGen
|
|
4
|
+
Any, Tuple, Generator as YieldGen, TypeVar
|
|
5
5
|
)
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from syncraft.algebra import (
|
|
8
8
|
Algebra, Either, Right,
|
|
9
9
|
)
|
|
10
|
-
from syncraft.ast import
|
|
10
|
+
from syncraft.ast import TokenProtocol, ParseResult, Choice, Many, Then, Marked, Collect
|
|
11
11
|
|
|
12
12
|
from syncraft.generator import GenState, Generator
|
|
13
13
|
|
|
14
14
|
from syncraft.syntax import Syntax
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
T=TypeVar('T', bound=TokenProtocol)
|
|
18
18
|
@dataclass(frozen=True)
|
|
19
19
|
class Finder(Generator[T]):
|
|
20
20
|
@classmethod
|
|
21
21
|
def anything(cls)->Algebra[Any, GenState[T]]:
|
|
22
22
|
def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[Any, GenState[T]]]:
|
|
23
23
|
return Right((input.ast, input))
|
|
24
|
-
return cls(anything_run, name=cls.__name__ + '.anything
|
|
24
|
+
return cls(anything_run, name=cls.__name__ + '.anything')
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
anything = Syntax(lambda cls: cls.factory('anything')).describe(name="
|
|
28
|
+
anything = Syntax(lambda cls: cls.factory('anything')).describe(name="Anything", fixity='infix')
|
|
29
29
|
|
|
30
|
-
def matches(syntax: Syntax[Any, Any], data: ParseResult[
|
|
30
|
+
def matches(syntax: Syntax[Any, Any], data: ParseResult[Any])-> bool:
|
|
31
31
|
gen = syntax(Finder)
|
|
32
|
-
state = GenState.from_ast(ast = data, restore_pruned=True)
|
|
32
|
+
state = GenState[Any].from_ast(ast = data, restore_pruned=True)
|
|
33
33
|
result = gen.run(state, use_cache=True)
|
|
34
34
|
return isinstance(result, Right)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def find(syntax: Syntax[Any, Any], data: ParseResult[
|
|
37
|
+
def find(syntax: Syntax[Any, Any], data: ParseResult[Any]) -> YieldGen[ParseResult[Any], None, None]:
|
|
38
38
|
if matches(syntax, data):
|
|
39
39
|
yield data
|
|
40
40
|
match data:
|
|
@@ -48,10 +48,10 @@ def find(syntax: Syntax[Any, Any], data: ParseResult[T]) -> YieldGen[ParseResult
|
|
|
48
48
|
yield from find(syntax, e)
|
|
49
49
|
case Marked(value=value):
|
|
50
50
|
yield from find(syntax, value)
|
|
51
|
-
case Choice(
|
|
52
|
-
if
|
|
53
|
-
yield from find(syntax,
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
case Choice(value=value):
|
|
52
|
+
if value is not None:
|
|
53
|
+
yield from find(syntax, value)
|
|
54
|
+
case Collect(value=value):
|
|
55
|
+
yield from find(syntax, value)
|
|
56
56
|
case _:
|
|
57
57
|
pass
|
|
@@ -11,8 +11,8 @@ from syncraft.algebra import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
from syncraft.ast import (
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
ParseResult, AST, Token, TokenSpec,
|
|
15
|
+
Nothing, TokenProtocol,
|
|
16
16
|
Choice, Many, ChoiceKind,
|
|
17
17
|
Then, ThenKind, Marked
|
|
18
18
|
)
|
|
@@ -24,6 +24,14 @@ import rstr
|
|
|
24
24
|
from functools import lru_cache
|
|
25
25
|
import random
|
|
26
26
|
from rich import print
|
|
27
|
+
from syncraft.constraint import Bindable
|
|
28
|
+
|
|
29
|
+
T = TypeVar('T', bound=TokenProtocol)
|
|
30
|
+
|
|
31
|
+
S = TypeVar('S', bound=Bindable)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
B = TypeVar('B')
|
|
28
36
|
|
|
29
37
|
|
|
@@ -63,9 +71,7 @@ class GenState(Bindable, Generic[T]):
|
|
|
63
71
|
if isinstance(self.ast, Then) and (self.ast.kind != ThenKind.LEFT or self.restore_pruned):
|
|
64
72
|
return replace(self, ast=self.ast.right)
|
|
65
73
|
return replace(self, ast=None)
|
|
66
|
-
|
|
67
74
|
|
|
68
|
-
|
|
69
75
|
def down(self, index: int) -> GenState[T]:
|
|
70
76
|
if self.ast is None:
|
|
71
77
|
return self
|
|
@@ -76,19 +82,27 @@ class GenState(Bindable, Generic[T]):
|
|
|
76
82
|
raise TypeError(f"Invalid AST type({self.ast}) for down traversal")
|
|
77
83
|
|
|
78
84
|
@classmethod
|
|
79
|
-
def from_ast(cls,
|
|
85
|
+
def from_ast(cls,
|
|
86
|
+
*,
|
|
87
|
+
ast: Optional[ParseResult[T]],
|
|
88
|
+
seed: int = 0,
|
|
89
|
+
restore_pruned:bool=False) -> GenState[T]:
|
|
80
90
|
return cls(ast=ast, seed=seed, restore_pruned=restore_pruned)
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
92
|
@lru_cache(maxsize=None)
|
|
87
|
-
def token_type_from_string(token_type: Optional[TokenType],
|
|
93
|
+
def token_type_from_string(token_type: Optional[TokenType],
|
|
94
|
+
text: str,
|
|
95
|
+
case_sensitive:bool = False)-> TokenType:
|
|
88
96
|
if not isinstance(token_type, TokenType) or token_type == TokenType.VAR:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
if case_sensitive:
|
|
98
|
+
for t in TokenType:
|
|
99
|
+
if t.value == text:
|
|
100
|
+
return t
|
|
101
|
+
else:
|
|
102
|
+
text = text.lower()
|
|
103
|
+
for t in TokenType:
|
|
104
|
+
if t.value == text or str(t.value).lower() == text:
|
|
105
|
+
return t
|
|
92
106
|
return TokenType.VAR
|
|
93
107
|
return token_type
|
|
94
108
|
|
|
@@ -121,13 +135,10 @@ class TokenGen(TokenSpec):
|
|
|
121
135
|
else:
|
|
122
136
|
text = "VALUE"
|
|
123
137
|
|
|
124
|
-
return Token(token_type=
|
|
125
|
-
text,
|
|
126
|
-
self.case_sensitive),
|
|
127
|
-
text=text)
|
|
138
|
+
return Token(token_type=token_type_from_string(self.token_type, text, case_sensitive=False), text=text)
|
|
128
139
|
|
|
129
140
|
@staticmethod
|
|
130
|
-
def from_string(string: str)->Token:
|
|
141
|
+
def from_string(string: str) -> Token:
|
|
131
142
|
return Token(token_type=token_type_from_string(None, string, case_sensitive=False), text=string)
|
|
132
143
|
|
|
133
144
|
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
import re
|
|
3
3
|
from sqlglot import tokenize, TokenType, Parser as GlotParser, exp
|
|
4
4
|
from typing import (
|
|
5
|
-
Optional, List, Any, Tuple,
|
|
5
|
+
Optional, List, Any, Tuple, TypeVar,
|
|
6
6
|
Generic
|
|
7
7
|
)
|
|
8
8
|
from syncraft.algebra import (
|
|
@@ -13,9 +13,11 @@ from enum import Enum
|
|
|
13
13
|
from functools import reduce
|
|
14
14
|
from syncraft.syntax import Syntax
|
|
15
15
|
|
|
16
|
-
from syncraft.ast import Token, TokenSpec, AST,
|
|
16
|
+
from syncraft.ast import Token, TokenSpec, AST, TokenProtocol
|
|
17
|
+
from syncraft.constraint import Bindable
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
T = TypeVar('T', bound=TokenProtocol)
|
|
19
21
|
@dataclass(frozen=True)
|
|
20
22
|
class ParserState(Bindable, Generic[T]):
|
|
21
23
|
input: Tuple[T, ...] = field(default_factory=tuple)
|
|
@@ -8,7 +8,7 @@ from dataclasses import dataclass, field, replace
|
|
|
8
8
|
from functools import reduce
|
|
9
9
|
from syncraft.algebra import Algebra, Error, Either, Right
|
|
10
10
|
from syncraft.constraint import Variable, Bindable
|
|
11
|
-
from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E
|
|
11
|
+
from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector
|
|
12
12
|
from types import MethodType, FunctionType
|
|
13
13
|
|
|
14
14
|
from rich import print
|
|
@@ -249,7 +249,7 @@ class Syntax(Generic[A, S]):
|
|
|
249
249
|
ret = self.mark(var.name).map_all(bind_v) if var.name else self.map_all(bind_v)
|
|
250
250
|
return ret.describe(name=f'bind({var.name})', fixity='postfix', parameter=(self,))
|
|
251
251
|
|
|
252
|
-
def to(self, f:
|
|
252
|
+
def to(self, f: Collector[E])-> Syntax[Collect[A, E], S]:
|
|
253
253
|
def to_f(v: A) -> Collect[A, E]:
|
|
254
254
|
if isinstance(v, Collect):
|
|
255
255
|
return replace(v, collector=f)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.30
|
|
4
4
|
Summary: Parser combinator library
|
|
5
5
|
Author-email: Michael Afmokt <michael@esacca.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -29,8 +29,6 @@ pip install syncraft
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
## TODO
|
|
32
|
-
- [ ] simplify the result of then_left and then_right by bimap the result in syntax.
|
|
33
|
-
- [ ] simplify the result of sep_by and between by bimap the result in syntax
|
|
34
32
|
- [ ] convert to dict/dataclass via bimap in syntax
|
|
35
33
|
- [ ] define DSL over Variable to construct predicates
|
|
36
34
|
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token
|
|
3
|
+
from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token, Marked, Nothing
|
|
4
4
|
from syncraft.algebra import Error
|
|
5
5
|
from syncraft.parser import literal, parse
|
|
6
6
|
import syncraft.generator as gen
|
|
@@ -19,9 +19,9 @@ def test1_simple_then() -> None:
|
|
|
19
19
|
print("---" * 40)
|
|
20
20
|
print(generated)
|
|
21
21
|
assert ast == generated
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
value, bmap = generated.bimap()
|
|
23
|
+
print(value)
|
|
24
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def test2_named_results() -> None:
|
|
@@ -35,10 +35,9 @@ def test2_named_results() -> None:
|
|
|
35
35
|
print("---" * 40)
|
|
36
36
|
print(generated)
|
|
37
37
|
assert ast == generated
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# assert bmap(value) == generated
|
|
38
|
+
value, bmap = generated.bimap()
|
|
39
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
40
|
+
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
def test3_many_literals() -> None:
|
|
@@ -52,9 +51,8 @@ def test3_many_literals() -> None:
|
|
|
52
51
|
print("---" * 40)
|
|
53
52
|
print(generated)
|
|
54
53
|
assert ast == generated
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# assert bmap(value) == generated
|
|
54
|
+
value, bmap = generated.bimap()
|
|
55
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
def test4_mixed_many_named() -> None:
|
|
@@ -69,9 +67,8 @@ def test4_mixed_many_named() -> None:
|
|
|
69
67
|
print("---" * 40)
|
|
70
68
|
print(generated)
|
|
71
69
|
assert ast == generated
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# assert bmap(value) == generated
|
|
70
|
+
value, bmap = generated.bimap()
|
|
71
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
75
72
|
|
|
76
73
|
|
|
77
74
|
def test5_nested_then_many() -> None:
|
|
@@ -85,9 +82,8 @@ def test5_nested_then_many() -> None:
|
|
|
85
82
|
print("---" * 40)
|
|
86
83
|
print(generated)
|
|
87
84
|
assert ast == generated
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# assert bmap(value) == generated
|
|
85
|
+
value, bmap = generated.bimap()
|
|
86
|
+
assert gen.generate(syntax, bmap(value), restore_pruned=True) == generated
|
|
91
87
|
|
|
92
88
|
|
|
93
89
|
|
|
@@ -99,8 +95,8 @@ def test_then_flatten():
|
|
|
99
95
|
print(ast)
|
|
100
96
|
generated = gen.generate(syntax, ast)
|
|
101
97
|
assert ast == generated
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
value, bmap = generated.bimap()
|
|
99
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
104
100
|
|
|
105
101
|
|
|
106
102
|
|
|
@@ -114,11 +110,8 @@ def test_named_in_then():
|
|
|
114
110
|
print(ast)
|
|
115
111
|
generated = gen.generate(syntax, ast)
|
|
116
112
|
assert ast == generated
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# print(value)
|
|
120
|
-
# assert set(x.name for x in value if isinstance(x, Marked)) == {"first", "second", "third"}
|
|
121
|
-
# assert bmap(value) == ast
|
|
113
|
+
value, bmap = generated.bimap()
|
|
114
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
122
115
|
|
|
123
116
|
|
|
124
117
|
def test_named_in_many():
|
|
@@ -129,10 +122,8 @@ def test_named_in_many():
|
|
|
129
122
|
print(ast)
|
|
130
123
|
generated = gen.generate(syntax, ast)
|
|
131
124
|
assert ast == generated
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# assert all(isinstance(v, Marked) for v in value if isinstance(v, Marked))
|
|
135
|
-
# assert bmap(value) == ast
|
|
125
|
+
value, bmap = generated.bimap()
|
|
126
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
136
127
|
|
|
137
128
|
|
|
138
129
|
def test_named_in_or():
|
|
@@ -144,10 +135,8 @@ def test_named_in_or():
|
|
|
144
135
|
print(ast)
|
|
145
136
|
generated = gen.generate(syntax, ast)
|
|
146
137
|
assert ast == generated
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# assert value.name == "b"
|
|
150
|
-
# assert bmap(value) == ast
|
|
138
|
+
value, bmap = generated.bimap()
|
|
139
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
151
140
|
|
|
152
141
|
|
|
153
142
|
|
|
@@ -165,8 +154,8 @@ def test_deep_mix():
|
|
|
165
154
|
print('---' * 40)
|
|
166
155
|
print(generated)
|
|
167
156
|
assert ast == generated
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
value, bmap = generated.bimap()
|
|
158
|
+
assert gen.generate(syntax, bmap(value)) == generated
|
|
170
159
|
|
|
171
160
|
|
|
172
161
|
def test_empty_many() -> None:
|
|
@@ -183,8 +172,8 @@ def test_backtracking_many() -> None:
|
|
|
183
172
|
syntax = (A.many() + B) # must not eat the final "a" needed for B
|
|
184
173
|
sql = "a a a a b"
|
|
185
174
|
ast = parse(syntax, sql, dialect="sqlite")
|
|
186
|
-
|
|
187
|
-
|
|
175
|
+
value, bmap = ast.bimap()
|
|
176
|
+
assert gen.generate(syntax, bmap(value)) == ast
|
|
188
177
|
|
|
189
178
|
def test_deep_nesting() -> None:
|
|
190
179
|
A = literal("a")
|
|
@@ -209,9 +198,8 @@ def test_named_many() -> None:
|
|
|
209
198
|
syntax = A.many()
|
|
210
199
|
sql = "a a"
|
|
211
200
|
ast = parse(syntax, sql, dialect="sqlite")
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
# assert all(isinstance(x, Marked) for x in flattened)
|
|
201
|
+
value, bmap = ast.bimap()
|
|
202
|
+
assert gen.generate(syntax, bmap(value)) == ast
|
|
215
203
|
|
|
216
204
|
|
|
217
205
|
def test_or_named() -> None:
|
|
@@ -220,10 +208,8 @@ def test_or_named() -> None:
|
|
|
220
208
|
syntax = A | B
|
|
221
209
|
sql = "b"
|
|
222
210
|
ast = parse(syntax, sql, dialect="sqlite")
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# value, _ = ast.bimap()
|
|
226
|
-
# assert value == Marked(name="y", value=TokenGen.from_string("b"))
|
|
211
|
+
value, bmap = ast.bimap()
|
|
212
|
+
assert gen.generate(syntax, bmap(value)) == ast
|
|
227
213
|
|
|
228
214
|
|
|
229
215
|
def test_then_associativity() -> None:
|
|
@@ -269,11 +255,11 @@ def test_optional():
|
|
|
269
255
|
A = literal("a").mark("a")
|
|
270
256
|
syntax = A.optional()
|
|
271
257
|
ast1 = parse(syntax, "", dialect="sqlite")
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
258
|
+
v1, _ = ast1.bimap()
|
|
259
|
+
assert isinstance(v1, Nothing)
|
|
260
|
+
ast2 = parse(syntax, "a", dialect="sqlite")
|
|
261
|
+
v2, _ = ast2.bimap()
|
|
262
|
+
assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
|
|
277
263
|
|
|
278
264
|
|
|
279
265
|
|
|
@@ -16,6 +16,8 @@ def test_between()->None:
|
|
|
16
16
|
ast:AST = parse(syntax, sql, dialect='sqlite')
|
|
17
17
|
generated = gen.generate(syntax, ast)
|
|
18
18
|
assert ast == generated, "Parsed and generated results do not match."
|
|
19
|
+
x, f = generated.bimap()
|
|
20
|
+
assert gen.generate(syntax, f(x)) == ast
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def test_sep_by()->None:
|
|
@@ -24,6 +26,8 @@ def test_sep_by()->None:
|
|
|
24
26
|
ast:AST = parse(syntax, sql, dialect='sqlite')
|
|
25
27
|
generated = gen.generate(syntax, ast)
|
|
26
28
|
assert ast == generated, "Parsed and generated results do not match."
|
|
29
|
+
x, f = generated.bimap()
|
|
30
|
+
assert gen.generate(syntax, f(x)) == ast
|
|
27
31
|
|
|
28
32
|
def test_many_or()->None:
|
|
29
33
|
IF = literal("if")
|
|
@@ -34,3 +38,5 @@ def test_many_or()->None:
|
|
|
34
38
|
ast:AST = parse(syntax, sql, dialect='sqlite')
|
|
35
39
|
generated = gen.generate(syntax, ast)
|
|
36
40
|
assert ast == generated, "Parsed and generated results do not match."
|
|
41
|
+
x, f = generated.bimap()
|
|
42
|
+
assert gen.generate(syntax, f(x)) == ast
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|