omlish 0.0.0.dev306__py3-none-any.whl → 0.0.0.dev308__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/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev306'
2
- __revision__ = 'dda70244f84192bfce2428b682f9a5e2349fb05a'
1
+ __version__ = '0.0.0.dev308'
2
+ __revision__ = '78cf17d1a6de1ee32440b0b62de28fdd17f9bedc'
3
3
 
4
4
 
5
5
  #
@@ -50,11 +50,11 @@ class AnyioSubprocesses(AbstractAsyncSubprocesses):
50
50
  stderr = io.BytesIO()
51
51
  tg.start_soon(read_output, proc.stderr, stderr)
52
52
 
53
- if proc.stdin and run.input is not None:
54
- await proc.stdin.send(run.input)
55
- await proc.stdin.aclose()
53
+ if proc.stdin and run.input is not None:
54
+ await proc.stdin.send(run.input)
55
+ await proc.stdin.aclose()
56
56
 
57
- await proc.wait()
57
+ await proc.wait()
58
58
 
59
59
  if run.check and proc.returncode != 0:
60
60
  raise subprocess.CalledProcessError(
@@ -1,4 +1,5 @@
1
1
  from .parsing import ( # Noqa
2
+ MetaMaker,
2
3
  parse,
3
4
  parse_list,
4
5
  )
@@ -10,6 +11,6 @@ from .values import ( # noqa
10
11
  Map,
11
12
  Set,
12
13
  Symbol,
13
- TaggedVal,
14
+ Tagged,
14
15
  Vector,
15
16
  )
@@ -0,0 +1,315 @@
1
+ r"""
2
+ https://github.com/edn-format/edn
3
+ https://github.com/antlr/grammars-v4/blob/master/edn/edn.g4
4
+ https://github.com/jorinvo/edn-data/blob/1e5824f63803eb58f35e98839352000053d47115/src/parse.ts
5
+ https://clojure.org/reference/reader#_extensible_data_notation_edn
6
+ """
7
+ import dataclasses as dc
8
+ import io
9
+ import typing as ta
10
+
11
+ from ... import check
12
+ from ...funcs.genmachine import GenMachine
13
+
14
+
15
+ ##
16
+
17
+
18
+ TokenKind: ta.TypeAlias = ta.Literal[
19
+ 'STRING',
20
+ 'CHAR',
21
+ 'WORD',
22
+ 'COMMENT',
23
+
24
+ 'LPAREN',
25
+ 'RPAREN',
26
+ 'LBRACKET',
27
+ 'RBRACKET',
28
+ 'HASH_LBRACE',
29
+ 'LBRACE',
30
+ 'RBRACE',
31
+
32
+ 'HASH_UNDERSCORE',
33
+ 'META',
34
+ 'QUOTE',
35
+
36
+ 'SPACE',
37
+ ]
38
+
39
+
40
+ class Position(ta.NamedTuple):
41
+ ofs: int
42
+ line: int
43
+ col: int
44
+
45
+
46
+ class Token(ta.NamedTuple):
47
+ kind: TokenKind
48
+ src: str
49
+
50
+ pos: Position
51
+
52
+ def __iter__(self):
53
+ raise TypeError
54
+
55
+
56
+ ##
57
+
58
+
59
+ SINGLE_TOKENS: ta.Mapping[str, TokenKind] = {
60
+ '(': 'LPAREN',
61
+ ')': 'RPAREN',
62
+ '[': 'LBRACKET',
63
+ ']': 'RBRACKET',
64
+ '{': 'LBRACE',
65
+ '}': 'RBRACE',
66
+
67
+ '^': 'META',
68
+ "'": 'QUOTE',
69
+ }
70
+
71
+
72
+ HASH_TOKENS: ta.Mapping[str, TokenKind] = {
73
+ '{': 'HASH_LBRACE',
74
+ '_': 'HASH_UNDERSCORE',
75
+ }
76
+
77
+
78
+ WORD_FIRST_SPECIAL_CHARS = ':.*+!-_?$%&=<>.'
79
+ WORD_BODY_SPECIAL_CHARS = '/'
80
+
81
+
82
+ ##
83
+
84
+
85
+ @dc.dataclass()
86
+ class StreamLexError(Exception):
87
+ message: str
88
+
89
+ pos: Position
90
+
91
+
92
+ class StreamLexer(GenMachine[str, Token]):
93
+ def __init__(
94
+ self,
95
+ *,
96
+ include_space: bool = False,
97
+ ) -> None:
98
+ self._include_space = include_space
99
+
100
+ self._ofs = 0
101
+ self._line = 1
102
+ self._col = 0
103
+
104
+ self._buf = io.StringIO()
105
+
106
+ super().__init__(self._do_main())
107
+
108
+ @property
109
+ def pos(self) -> Position:
110
+ return Position(
111
+ self._ofs,
112
+ self._line,
113
+ self._col,
114
+ )
115
+
116
+ def _char_in(self, c: str) -> str:
117
+ if not isinstance(c, str):
118
+ raise TypeError(c)
119
+ if c and len(c) != 1:
120
+ raise ValueError(c)
121
+
122
+ self._ofs += 1
123
+
124
+ if c == '\n':
125
+ self._line += 1
126
+ self._col = 0
127
+ else:
128
+ self._col += 1
129
+
130
+ return c
131
+
132
+ def _make_tok(
133
+ self,
134
+ kind: TokenKind,
135
+ src: str,
136
+ pos: Position,
137
+ ) -> ta.Sequence[Token]:
138
+ tok = Token(
139
+ kind,
140
+ src,
141
+ pos,
142
+ )
143
+ return (tok,)
144
+
145
+ def _flip_buf(self) -> str:
146
+ src = self._buf.getvalue()
147
+ self._buf.seek(0)
148
+ self._buf.truncate()
149
+ return src
150
+
151
+ def _raise(self, msg: str, src: Exception | None = None) -> ta.NoReturn:
152
+ raise StreamLexError(msg, self.pos) from src
153
+
154
+ def _do_main(self, p: str | None = None):
155
+ while True:
156
+ if p is not None:
157
+ c = p
158
+ p = None
159
+ else:
160
+ c = self._char_in((yield None)) # noqa
161
+
162
+ if not c:
163
+ return None
164
+
165
+ if c.isspace() or c == ',':
166
+ if self._include_space:
167
+ yield self._make_tok('SPACE', c, self.pos)
168
+ continue
169
+
170
+ if c in SINGLE_TOKENS:
171
+ yield self._make_tok(SINGLE_TOKENS[c], c, self.pos)
172
+ continue
173
+
174
+ if c == ';':
175
+ return self._do_comment()
176
+
177
+ if c == '"':
178
+ return self._do_string()
179
+
180
+ if c == '\\':
181
+ return self._do_char()
182
+
183
+ if c == '#':
184
+ return self._do_hash()
185
+
186
+ if (
187
+ c.isalnum() or
188
+ c in WORD_FIRST_SPECIAL_CHARS
189
+ ):
190
+ return self._do_word(c)
191
+
192
+ self._raise(f'Unexpected input: {c}')
193
+
194
+ def _do_comment(self):
195
+ check.state(self._buf.tell() == 0)
196
+ self._buf.write(';')
197
+
198
+ pos = self.pos
199
+
200
+ while True:
201
+ try:
202
+ c = self._char_in((yield None)) # noqa
203
+ except GeneratorExit:
204
+ self._raise('Unexpected end of input')
205
+
206
+ if not c or c == '\n':
207
+ break
208
+
209
+ self._buf.write(c)
210
+
211
+ src = self._flip_buf()
212
+ yield self._make_tok('COMMENT', src, pos)
213
+ return self._do_main()
214
+
215
+ def _do_string(self):
216
+ check.state(self._buf.tell() == 0)
217
+ self._buf.write('"')
218
+
219
+ pos = self.pos
220
+
221
+ esc = False
222
+ while True:
223
+ try:
224
+ c = self._char_in((yield None)) # noqa
225
+ except GeneratorExit:
226
+ self._raise('Unexpected end of input')
227
+
228
+ if not c:
229
+ self._raise(f'Unterminated string literal: {self._buf.getvalue()}')
230
+
231
+ self._buf.write(c)
232
+ if esc:
233
+ esc = False
234
+ elif c == '\\':
235
+ esc = True
236
+ elif c == '"':
237
+ break
238
+
239
+ src = self._flip_buf()
240
+ yield self._make_tok('STRING', src, pos)
241
+ return self._do_main()
242
+
243
+ def _do_char(self):
244
+ check.state(self._buf.tell() == 0)
245
+ self._buf.write('\\')
246
+
247
+ pos = self.pos
248
+
249
+ while True:
250
+ try:
251
+ c = self._char_in((yield None)) # noqa
252
+ except GeneratorExit:
253
+ self._raise('Unexpected end of input')
254
+
255
+ if not c or not (
256
+ c.isalnum() or
257
+ c == '\\'
258
+ ):
259
+ break
260
+
261
+ self._buf.write(c)
262
+
263
+ src = self._flip_buf()
264
+ yield self._make_tok('CHAR', src, pos)
265
+ return self._do_main(c)
266
+
267
+ def _do_hash(self):
268
+ check.state(self._buf.tell() == 0)
269
+
270
+ pos = self.pos
271
+
272
+ try:
273
+ c = self._char_in((yield None)) # noqa
274
+ except GeneratorExit:
275
+ self._raise('Unexpected end of input')
276
+
277
+ if (ht := HASH_TOKENS.get(c)) is not None:
278
+ yield self._make_tok(ht, '#' + c, pos)
279
+ return self._do_main()
280
+
281
+ elif (
282
+ c.isalnum() or
283
+ c == '#' or
284
+ c in WORD_FIRST_SPECIAL_CHARS
285
+ ):
286
+ return self._do_word('#' + c, pos=pos)
287
+
288
+ else:
289
+ self._raise(f'Unexpected input: {c}')
290
+
291
+ def _do_word(self, pfx: str, *, pos: Position | None = None):
292
+ check.state(self._buf.tell() == 0)
293
+ self._buf.write(pfx)
294
+
295
+ if pos is None:
296
+ pos = self.pos
297
+
298
+ while True:
299
+ try:
300
+ c = self._char_in((yield None)) # noqa
301
+ except GeneratorExit:
302
+ self._raise('Unexpected end of input')
303
+
304
+ if not c or not (
305
+ c.isalnum() or
306
+ c in WORD_FIRST_SPECIAL_CHARS or
307
+ c in WORD_BODY_SPECIAL_CHARS
308
+ ):
309
+ break
310
+
311
+ self._buf.write(c)
312
+
313
+ src = self._flip_buf()
314
+ yield self._make_tok('WORD', src, pos)
315
+ return self._do_main(c)