omlish 0.0.0.dev304__py3-none-any.whl → 0.0.0.dev306__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.
- omlish/.manifests.json +14 -0
- omlish/__about__.py +3 -3
- omlish/dataclasses/__init__.py +5 -0
- omlish/dataclasses/api/__init__.py +1 -0
- omlish/dataclasses/api/fields/metadata.py +8 -0
- omlish/dataclasses/tools/only_.py +45 -0
- omlish/diag/_pycharm/runhack.py +4 -1
- omlish/formats/edn/LICENSE +16 -0
- omlish/formats/edn/__init__.py +15 -0
- omlish/formats/edn/codec.py +26 -0
- omlish/formats/edn/parsing.py +359 -0
- omlish/formats/edn/values.py +162 -0
- omlish/graphs/trees.py +6 -0
- omlish/lang/__init__.py +7 -0
- omlish/lang/cached/function.py +7 -2
- omlish/lang/objects.py +28 -0
- omlish/lang/recursion.py +109 -0
- omlish/marshal/__init__.py +1 -1
- omlish/marshal/objects/helpers.py +1 -1
- omlish/sql/api/__init__.py +8 -1
- omlish/sql/api/base.py +1 -1
- omlish/sql/api/funcs.py +70 -11
- omlish/sql/qualifiedname.py +20 -12
- omlish/sql/queries/__init__.py +3 -0
- omlish/sql/queries/base.py +16 -0
- omlish/sql/queries/idents.py +12 -1
- omlish/sql/queries/names.py +8 -1
- omlish/sql/queries/relations.py +0 -12
- omlish/sql/queries/rendering.py +0 -3
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/RECORD +35 -28
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev304.dist-info → omlish-0.0.0.dev306.dist-info}/top_level.txt +0 -0
omlish/.manifests.json
CHANGED
@@ -51,6 +51,20 @@
|
|
51
51
|
}
|
52
52
|
}
|
53
53
|
},
|
54
|
+
{
|
55
|
+
"module": ".formats.edn.codec",
|
56
|
+
"attr": "_EDN_LAZY_CODEC",
|
57
|
+
"file": "omlish/formats/edn/codec.py",
|
58
|
+
"line": 25,
|
59
|
+
"value": {
|
60
|
+
"$.codecs.base.LazyLoadedCodec": {
|
61
|
+
"mod_name": "omlish.formats.edn.codec",
|
62
|
+
"attr_name": "EDN_CODEC",
|
63
|
+
"name": "edn",
|
64
|
+
"aliases": null
|
65
|
+
}
|
66
|
+
}
|
67
|
+
},
|
54
68
|
{
|
55
69
|
"module": ".formats.ini.codec",
|
56
70
|
"attr": "_INI_LAZY_CODEC",
|
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev306'
|
2
|
+
__revision__ = 'dda70244f84192bfce2428b682f9a5e2349fb05a'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -101,7 +101,7 @@ class Project(ProjectBase):
|
|
101
101
|
|
102
102
|
'apsw ~= 3.49',
|
103
103
|
|
104
|
-
'sqlean.py ~= 3.
|
104
|
+
'sqlean.py ~= 3.49',
|
105
105
|
|
106
106
|
'duckdb ~= 1.2',
|
107
107
|
],
|
omlish/dataclasses/__init__.py
CHANGED
@@ -60,6 +60,7 @@ from .api import ( # noqa
|
|
60
60
|
extra_field_params,
|
61
61
|
set_field_metadata,
|
62
62
|
update_extra_field_params,
|
63
|
+
with_extra_field_params,
|
63
64
|
)
|
64
65
|
|
65
66
|
from .errors import ( # noqa
|
@@ -108,6 +109,10 @@ from .tools.modifiers import ( # noqa
|
|
108
109
|
update_fields,
|
109
110
|
)
|
110
111
|
|
112
|
+
from .tools.only_ import ( # noqa
|
113
|
+
only,
|
114
|
+
)
|
115
|
+
|
111
116
|
from .tools.replace import ( # noqa
|
112
117
|
deep_replace,
|
113
118
|
)
|
@@ -5,6 +5,7 @@ from .... import check
|
|
5
5
|
from .... import lang
|
6
6
|
from ...debug import DEBUG
|
7
7
|
from ...specs import FieldSpec
|
8
|
+
from ...tools.modifiers import field_modifier
|
8
9
|
from ...utils import chain_mapping_proxy
|
9
10
|
|
10
11
|
|
@@ -92,3 +93,10 @@ def update_extra_field_params(
|
|
92
93
|
**(fe if unless_non_default else {}),
|
93
94
|
},
|
94
95
|
})
|
96
|
+
|
97
|
+
|
98
|
+
def with_extra_field_params(**kwargs: ta.Any) -> field_modifier:
|
99
|
+
@field_modifier
|
100
|
+
def inner(f: dc.Field) -> dc.Field:
|
101
|
+
return update_extra_field_params(f, **kwargs)
|
102
|
+
return inner
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import collections.abc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .iter import fields_dict
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
def _only_test(v: ta.Any) -> bool:
|
11
|
+
if v is None:
|
12
|
+
return False
|
13
|
+
elif isinstance(v, collections.abc.Iterable):
|
14
|
+
return bool(v)
|
15
|
+
else:
|
16
|
+
return True
|
17
|
+
|
18
|
+
|
19
|
+
def only(
|
20
|
+
obj: ta.Any,
|
21
|
+
*flds: str,
|
22
|
+
all: bool = False, # noqa
|
23
|
+
test: ta.Callable[[ta.Any], bool] = _only_test,
|
24
|
+
) -> bool:
|
25
|
+
fdct = fields_dict(obj)
|
26
|
+
for fn in flds:
|
27
|
+
if fn not in fdct:
|
28
|
+
raise KeyError(fn)
|
29
|
+
|
30
|
+
rem = set(flds)
|
31
|
+
for fn, f in fdct.items():
|
32
|
+
if not f.compare and fn not in rem:
|
33
|
+
continue
|
34
|
+
|
35
|
+
v = getattr(obj, fn)
|
36
|
+
if test(v):
|
37
|
+
if fn in rem:
|
38
|
+
rem.remove(fn)
|
39
|
+
else:
|
40
|
+
return False
|
41
|
+
|
42
|
+
if rem and all:
|
43
|
+
return False
|
44
|
+
|
45
|
+
return True
|
omlish/diag/_pycharm/runhack.py
CHANGED
@@ -24,7 +24,10 @@ DEBUG_ENV_VAR = 'OMLISH_PYCHARM_RUNHACK_DEBUG'
|
|
24
24
|
|
25
25
|
#
|
26
26
|
|
27
|
-
|
27
|
+
# This hack must run first, before things like '__editable__.foo.pth' files installed by setuptools. pth file processing
|
28
|
+
# is sorted by name, so prepend with underscores:
|
29
|
+
# https://github.com/python/cpython/blob/aeb3a6f61af53ed3fbf31f0b3704f49b71ac553c/Lib/site.py#L246
|
30
|
+
_DEFAULT_PTH_FILE_NAME = '___omlish-pycharm-runhack.pth'
|
28
31
|
|
29
32
|
_DEFAULT_DEBUG = False
|
30
33
|
_DEFAULT_ENABLED = True
|
@@ -0,0 +1,16 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Jorin Vogel
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
11
|
+
Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ..codecs import make_object_lazy_loaded_codec
|
4
|
+
from ..codecs import make_str_object_codec
|
5
|
+
from .parsing import parse
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
def dumps(obj: ta.Any) -> str:
|
12
|
+
# return json.dumps(obj)
|
13
|
+
raise NotImplementedError
|
14
|
+
|
15
|
+
|
16
|
+
def loads(s: str) -> ta.Any:
|
17
|
+
return parse(s)
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
EDN_CODEC = make_str_object_codec('edn', dumps, loads)
|
24
|
+
|
25
|
+
# @omlish-manifest
|
26
|
+
_EDN_LAZY_CODEC = make_object_lazy_loaded_codec(__name__, 'EDN_CODEC', EDN_CODEC)
|
@@ -0,0 +1,359 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- reader meta - ^:foo
|
4
|
+
"""
|
5
|
+
# https://github.com/jorinvo/edn-data/blob/1e5824f63803eb58f35e98839352000053d47115/test/parse.test.ts
|
6
|
+
import datetime
|
7
|
+
import enum
|
8
|
+
import re
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from ... import check
|
12
|
+
from .values import Char
|
13
|
+
from .values import Keyword
|
14
|
+
from .values import List
|
15
|
+
from .values import Map
|
16
|
+
from .values import Set
|
17
|
+
from .values import Symbol
|
18
|
+
from .values import TaggedVal
|
19
|
+
from .values import Vector
|
20
|
+
|
21
|
+
|
22
|
+
##
|
23
|
+
|
24
|
+
|
25
|
+
class ListParser:
|
26
|
+
DEFAULT_TAG_HANDLERS: ta.ClassVar[ta.Mapping[str, ta.Callable[..., ta.Any]]] = {
|
27
|
+
'inst': lambda val: datetime.datetime.fromisoformat(val) if isinstance(val, str) else None,
|
28
|
+
}
|
29
|
+
|
30
|
+
def __init__(
|
31
|
+
self,
|
32
|
+
*,
|
33
|
+
keyword_maker: ta.Callable[..., ta.Any] = Keyword,
|
34
|
+
char_maker: ta.Callable[..., ta.Any] = Char,
|
35
|
+
symbol_maker: ta.Callable[..., ta.Any] = Symbol,
|
36
|
+
|
37
|
+
list_maker: ta.Callable[..., ta.Any] = List.new,
|
38
|
+
vector_maker: ta.Callable[..., ta.Any] = Vector.new,
|
39
|
+
set_maker: ta.Callable[..., ta.Any] = Set.new,
|
40
|
+
map_maker: ta.Callable[..., ta.Any] = Map.new,
|
41
|
+
|
42
|
+
tag_handlers: ta.Mapping[str, ta.Callable[..., ta.Any]] | None = None,
|
43
|
+
) -> None:
|
44
|
+
super().__init__()
|
45
|
+
|
46
|
+
self._keyword_maker = keyword_maker
|
47
|
+
self._char_maker = char_maker
|
48
|
+
self._symbol_maker = symbol_maker
|
49
|
+
|
50
|
+
self._list_maker = list_maker
|
51
|
+
self._vector_maker = vector_maker
|
52
|
+
self._set_maker = set_maker
|
53
|
+
self._map_maker = map_maker
|
54
|
+
|
55
|
+
self._tag_handlers = {
|
56
|
+
**self.DEFAULT_TAG_HANDLERS,
|
57
|
+
**(tag_handlers or {}),
|
58
|
+
}
|
59
|
+
|
60
|
+
self._stack: list[tuple[ListParser._ParseMode | ListParser._StackItem, ta.Any]] = []
|
61
|
+
self._mode: ListParser._ParseMode = ListParser._ParseMode.IDLE
|
62
|
+
self._state = ''
|
63
|
+
self._result: ta.Any = self._UNDEFINED
|
64
|
+
|
65
|
+
#
|
66
|
+
|
67
|
+
class _UNDEFINED: # noqa
|
68
|
+
def __new__(cls, *args, **kwargs): # noqa
|
69
|
+
raise TypeError
|
70
|
+
|
71
|
+
class _ParseMode(enum.Enum):
|
72
|
+
IDLE = 0
|
73
|
+
STRING = 1
|
74
|
+
ESCAPE = 2
|
75
|
+
COMMENT = 3
|
76
|
+
|
77
|
+
class _StackItem(enum.Enum):
|
78
|
+
VECTOR = 0
|
79
|
+
LIST = 1
|
80
|
+
MAP = 2
|
81
|
+
SET = 3
|
82
|
+
TAG = 4
|
83
|
+
|
84
|
+
#
|
85
|
+
|
86
|
+
def _update_stack(self) -> None:
|
87
|
+
if not self._stack or self._result is self._UNDEFINED:
|
88
|
+
return
|
89
|
+
|
90
|
+
stack_item, prev_state = self._stack[-1]
|
91
|
+
|
92
|
+
if stack_item == ListParser._StackItem.VECTOR:
|
93
|
+
prev_state.append(self._result)
|
94
|
+
|
95
|
+
elif stack_item == ListParser._StackItem.LIST:
|
96
|
+
prev_state.append(self._result)
|
97
|
+
|
98
|
+
elif stack_item == ListParser._StackItem.SET:
|
99
|
+
prev_state.append(self._result)
|
100
|
+
|
101
|
+
elif stack_item == ListParser._StackItem.MAP:
|
102
|
+
if len(prev_state[1]) > 0:
|
103
|
+
prev_state[0].append([prev_state[1].pop(), self._result])
|
104
|
+
else:
|
105
|
+
prev_state[1].append(self._result)
|
106
|
+
|
107
|
+
elif stack_item == ListParser._StackItem.TAG:
|
108
|
+
self._stack.pop()
|
109
|
+
|
110
|
+
if prev_state == '_':
|
111
|
+
self._result = self._UNDEFINED
|
112
|
+
|
113
|
+
else:
|
114
|
+
tag_handler = self._tag_handlers.get(prev_state)
|
115
|
+
if tag_handler:
|
116
|
+
self._result = tag_handler(self._result)
|
117
|
+
else:
|
118
|
+
self._result = TaggedVal(prev_state, self._result)
|
119
|
+
|
120
|
+
self._update_stack()
|
121
|
+
return
|
122
|
+
|
123
|
+
# TODO: else error
|
124
|
+
# Reset result after updating stack
|
125
|
+
self._result = self._UNDEFINED
|
126
|
+
|
127
|
+
#
|
128
|
+
|
129
|
+
_INT_PAT = re.compile(r'^[-+]?(0|[1-9][0-9]*)$')
|
130
|
+
_BIGINT_PAT = re.compile(r'^[-+]?(0|[1-9][0-9]*)N$')
|
131
|
+
_FLOAT_PAT = re.compile(r'^[-+]?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?(0|[1-9][0-9]*))?M?$')
|
132
|
+
|
133
|
+
def _match(self) -> None:
|
134
|
+
if self._state == 'nil':
|
135
|
+
self._result = None
|
136
|
+
|
137
|
+
elif self._state == 'true':
|
138
|
+
self._result = True
|
139
|
+
|
140
|
+
elif self._state == 'false':
|
141
|
+
self._result = False
|
142
|
+
|
143
|
+
elif self._state.startswith(':'):
|
144
|
+
# Keyword
|
145
|
+
self._result = self._keyword_maker(self._state[1:])
|
146
|
+
|
147
|
+
elif self._state.startswith('#'):
|
148
|
+
# Tag
|
149
|
+
self._stack.append((ListParser._StackItem.TAG, self._state[1:]))
|
150
|
+
self._result = self._UNDEFINED
|
151
|
+
|
152
|
+
elif self._INT_PAT.match(self._state):
|
153
|
+
# Int
|
154
|
+
self._result = int(self._state)
|
155
|
+
|
156
|
+
elif self._FLOAT_PAT.match(self._state):
|
157
|
+
# Float
|
158
|
+
self._result = float(self._state)
|
159
|
+
|
160
|
+
elif self._BIGINT_PAT.match(self._state):
|
161
|
+
# BigInt
|
162
|
+
self._result = int(self._state[:-1]) # In Python we don't need special handling for bigint
|
163
|
+
|
164
|
+
elif self._state.startswith('\\'):
|
165
|
+
# Char
|
166
|
+
check.state(len(self._state) > 1)
|
167
|
+
if self._state == '\\space':
|
168
|
+
c = ' '
|
169
|
+
elif self._state == '\\newline':
|
170
|
+
c = '\n'
|
171
|
+
elif self._state == '\\return':
|
172
|
+
c = '\r'
|
173
|
+
elif self._state == '\\tab':
|
174
|
+
c = '\t'
|
175
|
+
elif self._state == '\\\\':
|
176
|
+
c = '\\'
|
177
|
+
elif self._state.startswith('\\u'):
|
178
|
+
check.state(len(self._state) == 6)
|
179
|
+
c = chr(int(self._state[2:], 16))
|
180
|
+
else:
|
181
|
+
check.state(len(self._state) == 2)
|
182
|
+
c = self._state[1:]
|
183
|
+
|
184
|
+
self._result = self._char_maker(c)
|
185
|
+
|
186
|
+
elif self._state:
|
187
|
+
# Symbol
|
188
|
+
self._result = self._symbol_maker(self._state)
|
189
|
+
|
190
|
+
self._state = ''
|
191
|
+
|
192
|
+
#
|
193
|
+
|
194
|
+
_SPACE_CHARS: ta.ClassVar[ta.AbstractSet[str]] = frozenset([',', ' ', '\t', '\n', '\r'])
|
195
|
+
|
196
|
+
_STRING_ESCAPE_MAP: ta.ClassVar[ta.Mapping[str, str]] = {
|
197
|
+
't': '\t',
|
198
|
+
'r': '\r',
|
199
|
+
'n': '\n',
|
200
|
+
'\\': '\\',
|
201
|
+
'"': '"',
|
202
|
+
}
|
203
|
+
|
204
|
+
def parse(self, src: str) -> list[ta.Any]:
|
205
|
+
values = []
|
206
|
+
|
207
|
+
i = -1
|
208
|
+
for i in range(len(src)):
|
209
|
+
if not self._stack and self._result is not self._UNDEFINED:
|
210
|
+
values.append(self._result)
|
211
|
+
self._result = self._UNDEFINED
|
212
|
+
|
213
|
+
char = src[i]
|
214
|
+
|
215
|
+
if self._mode == ListParser._ParseMode.IDLE:
|
216
|
+
if char == '"':
|
217
|
+
self._match()
|
218
|
+
self._update_stack()
|
219
|
+
self._mode = ListParser._ParseMode.STRING
|
220
|
+
self._state = ''
|
221
|
+
continue
|
222
|
+
|
223
|
+
if char == ';':
|
224
|
+
self._mode = ListParser._ParseMode.COMMENT
|
225
|
+
continue
|
226
|
+
|
227
|
+
if char in self._SPACE_CHARS:
|
228
|
+
self._match()
|
229
|
+
self._update_stack()
|
230
|
+
continue
|
231
|
+
|
232
|
+
if char == '}':
|
233
|
+
self._match()
|
234
|
+
self._update_stack()
|
235
|
+
|
236
|
+
if self._stack:
|
237
|
+
stack_item, prev_state = self._stack.pop()
|
238
|
+
|
239
|
+
if stack_item == ListParser._StackItem.MAP:
|
240
|
+
check.empty(prev_state[1])
|
241
|
+
self._result = self._map_maker(prev_state[0])
|
242
|
+
|
243
|
+
else: # Set
|
244
|
+
# FIXME:
|
245
|
+
# check.state(stack_item == ListParser._StackItem.SET)
|
246
|
+
self._result = self._set_maker(prev_state)
|
247
|
+
|
248
|
+
self._update_stack()
|
249
|
+
continue
|
250
|
+
|
251
|
+
if char == ']':
|
252
|
+
self._match()
|
253
|
+
self._update_stack()
|
254
|
+
stack_item, prev_state = self._stack.pop()
|
255
|
+
self._result = self._vector_maker(tuple(prev_state))
|
256
|
+
self._update_stack()
|
257
|
+
continue
|
258
|
+
|
259
|
+
if char == ')':
|
260
|
+
self._match()
|
261
|
+
self._update_stack()
|
262
|
+
stack_item, prev_state = self._stack.pop()
|
263
|
+
self._result = self._list_maker(prev_state)
|
264
|
+
self._update_stack()
|
265
|
+
continue
|
266
|
+
|
267
|
+
if char == '[':
|
268
|
+
self._match()
|
269
|
+
self._update_stack()
|
270
|
+
self._stack.append((ListParser._StackItem.VECTOR, []))
|
271
|
+
continue
|
272
|
+
|
273
|
+
if char == '(':
|
274
|
+
self._match()
|
275
|
+
self._update_stack()
|
276
|
+
self._stack.append((ListParser._StackItem.LIST, []))
|
277
|
+
continue
|
278
|
+
|
279
|
+
state_plus_char = self._state + char
|
280
|
+
if state_plus_char == '#_':
|
281
|
+
self._stack.append((ListParser._StackItem.TAG, char))
|
282
|
+
self._result = self._UNDEFINED
|
283
|
+
self._state = ''
|
284
|
+
continue
|
285
|
+
|
286
|
+
if state_plus_char.endswith('#{'):
|
287
|
+
self._state = self._state[:-1] # Remove the '#'
|
288
|
+
self._match()
|
289
|
+
self._update_stack()
|
290
|
+
self._stack.append((ListParser._StackItem.SET, []))
|
291
|
+
self._state = ''
|
292
|
+
continue
|
293
|
+
|
294
|
+
if char == '{':
|
295
|
+
self._match()
|
296
|
+
self._update_stack()
|
297
|
+
self._stack.append((ListParser._StackItem.MAP, [[], []]))
|
298
|
+
self._state = ''
|
299
|
+
continue
|
300
|
+
|
301
|
+
self._state += char
|
302
|
+
continue
|
303
|
+
|
304
|
+
elif self._mode == ListParser._ParseMode.STRING: # noqa
|
305
|
+
if char == '\\':
|
306
|
+
self._stack.append((self._mode, self._state))
|
307
|
+
self._mode = ListParser._ParseMode.ESCAPE
|
308
|
+
self._state = ''
|
309
|
+
continue
|
310
|
+
|
311
|
+
if char == '"':
|
312
|
+
self._mode = ListParser._ParseMode.IDLE
|
313
|
+
self._result = self._state
|
314
|
+
self._update_stack()
|
315
|
+
self._state = ''
|
316
|
+
continue
|
317
|
+
|
318
|
+
self._state += char
|
319
|
+
|
320
|
+
elif self._mode == ListParser._ParseMode.ESCAPE:
|
321
|
+
# TODO what should happen when escaping other char
|
322
|
+
escaped_char = self._STRING_ESCAPE_MAP.get(char, char)
|
323
|
+
stack_item, prev_state = self._stack.pop()
|
324
|
+
self._mode = check.isinstance(stack_item, ListParser._ParseMode)
|
325
|
+
self._state = prev_state + escaped_char
|
326
|
+
|
327
|
+
elif self._mode == ListParser._ParseMode.COMMENT:
|
328
|
+
if char == '\n':
|
329
|
+
self._mode = ListParser._ParseMode.IDLE
|
330
|
+
|
331
|
+
else:
|
332
|
+
raise RuntimeError(self._mode)
|
333
|
+
|
334
|
+
if i >= 0:
|
335
|
+
self._match()
|
336
|
+
self._update_stack()
|
337
|
+
|
338
|
+
check.state(not self._stack)
|
339
|
+
|
340
|
+
if self._result is not self._UNDEFINED:
|
341
|
+
values.append(self._result)
|
342
|
+
return values
|
343
|
+
|
344
|
+
|
345
|
+
#
|
346
|
+
|
347
|
+
|
348
|
+
def parse_list(src: str, **kwargs: ta.Any) -> list[ta.Any]:
|
349
|
+
"""Parse an edn string and return the corresponding Python object."""
|
350
|
+
|
351
|
+
parser = ListParser(**kwargs)
|
352
|
+
return parser.parse(src)
|
353
|
+
|
354
|
+
|
355
|
+
def parse(src: str, **kwargs: ta.Any) -> ta.Any | None:
|
356
|
+
values = parse_list(src, **kwargs)
|
357
|
+
if not values:
|
358
|
+
return None
|
359
|
+
return check.single(values)
|