omlish 0.0.0.dev27__py3-none-any.whl → 0.0.0.dev28__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.dev27'
2
- __revision__ = 'c62e03dc2f032aa532d2c2b9f0d71ff96158cdec'
1
+ __version__ = '0.0.0.dev28'
2
+ __revision__ = '8adf3c1230237633abb8801258638f0ae2fa4099'
3
3
 
4
4
 
5
5
  #
@@ -36,6 +36,7 @@ def field( # noqa
36
36
  check_type: bool | None = None,
37
37
  override: bool = False,
38
38
  repr_fn: ta.Callable[[ta.Any], str | None] | None = None,
39
+ frozen: bool | None = None,
39
40
  ): # -> dc.Field
40
41
  if default is not MISSING and default_factory is not MISSING:
41
42
  raise ValueError('cannot specify both default and default_factory')
@@ -47,6 +48,7 @@ def field( # noqa
47
48
  check_type=check_type,
48
49
  override=override,
49
50
  repr_fn=repr_fn,
51
+ frozen=frozen,
50
52
  )
51
53
 
52
54
  md: ta.Mapping = {FieldExtras: fx}
@@ -42,12 +42,13 @@ from .metadata import METADATA_ATTR
42
42
 
43
43
  @dc.dataclass(frozen=True, kw_only=True)
44
44
  class FieldExtras(lang.Final):
45
- derive: ta.Callable[..., ta.Any] | None = None
45
+ derive: ta.Callable[..., ta.Any] | None = None # TODO
46
46
  coerce: bool | ta.Callable[[ta.Any], ta.Any] | None = None
47
47
  validate: ta.Callable[[ta.Any], bool] | None = None
48
48
  check_type: bool | None = None
49
49
  override: bool = False
50
50
  repr_fn: ta.Callable[[ta.Any], str | None] | None = None
51
+ frozen: bool | None = None # TODO
51
52
 
52
53
 
53
54
  DEFAULT_FIELD_EXTRAS = FieldExtras()
omlish/docker.py CHANGED
@@ -170,9 +170,9 @@ _LIKELY_IN_DOCKER_PATTERN = re.compile(r'^overlay / .*/docker/')
170
170
 
171
171
 
172
172
  def is_likely_in_docker() -> bool:
173
- if sys.platform != 'linux':
173
+ if getattr(sys, 'platform') != 'linux':
174
174
  return False
175
- with open('/proc/mounts') as f: # type: ignore
175
+ with open('/proc/mounts') as f:
176
176
  ls = f.readlines()
177
177
  return any(_LIKELY_IN_DOCKER_PATTERN.match(l) for l in ls)
178
178
 
@@ -57,11 +57,6 @@ def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
57
57
  ##
58
58
 
59
59
 
60
- def unwrap_func(fn: ta.Callable) -> ta.Callable:
61
- fn, _ = unwrap_func_with_partials(fn)
62
- return fn
63
-
64
-
65
60
  def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
66
61
  ps = []
67
62
  while True:
@@ -88,6 +83,11 @@ def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functo
88
83
  return fn, ps
89
84
 
90
85
 
86
+ def unwrap_func(fn: ta.Callable) -> ta.Callable:
87
+ uw, _ = unwrap_func_with_partials(fn)
88
+ return uw
89
+
90
+
91
91
  ##
92
92
 
93
93
 
@@ -55,8 +55,13 @@ from .helpers import ( # noqa
55
55
  )
56
56
 
57
57
  from .objects import ( # noqa
58
+ FieldInfo,
58
59
  FieldMetadata,
60
+ ObjectMarshaler,
59
61
  ObjectMetadata,
62
+ ObjectUnmarshaler,
63
+ SimpleObjectMarshalerFactory,
64
+ SimpleObjectUnmarshalerFactory,
60
65
  )
61
66
 
62
67
  from .polymorphism import ( # noqa
omlish/marshal/objects.py CHANGED
@@ -9,6 +9,7 @@ import typing as ta
9
9
 
10
10
  from .. import check
11
11
  from .. import dataclasses as dc
12
+ from .. import reflect as rfl
12
13
  from .base import MarshalContext
13
14
  from .base import Marshaler
14
15
  from .base import MarshalerFactory
@@ -87,6 +88,27 @@ class ObjectMarshaler(Marshaler):
87
88
  return ret
88
89
 
89
90
 
91
+ @dc.dataclass(frozen=True)
92
+ class SimpleObjectMarshalerFactory(MarshalerFactory):
93
+ dct: ta.Mapping[type, ta.Sequence[FieldInfo]]
94
+ unknown_field: str | None = None
95
+
96
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
97
+ return isinstance(rty, type) and rty in self.dct
98
+
99
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
100
+ ty = check.isinstance(rty, type)
101
+ flds = self.dct[ty]
102
+ fields = [
103
+ (fi, ctx.make(fi.type))
104
+ for fi in flds
105
+ ]
106
+ return ObjectMarshaler(
107
+ fields,
108
+ unknown_field=self.unknown_field,
109
+ )
110
+
111
+
90
112
  ##
91
113
 
92
114
 
@@ -123,3 +145,26 @@ class ObjectUnmarshaler(Unmarshaler):
123
145
  kw[fi.name] = u.unmarshal(ctx, mv)
124
146
 
125
147
  return self.cls(**kw)
148
+
149
+
150
+ @dc.dataclass(frozen=True)
151
+ class SimpleObjectUnmarshalerFactory(UnmarshalerFactory):
152
+ dct: ta.Mapping[type, ta.Sequence[FieldInfo]]
153
+ unknown_field: str | None = None
154
+
155
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
156
+ return isinstance(rty, type) and rty in self.dct
157
+
158
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
159
+ ty = check.isinstance(rty, type)
160
+ flds = self.dct[ty]
161
+ fields_by_unmarshal_name = {
162
+ n: (fi, ctx.make(fi.type))
163
+ for fi in flds
164
+ for n in fi.unmarshal_names
165
+ }
166
+ return ObjectUnmarshaler(
167
+ ty,
168
+ fields_by_unmarshal_name,
169
+ unknown_field=self.unknown_field,
170
+ )
omlish/text/asdl.py ADDED
@@ -0,0 +1,528 @@
1
+ """
2
+ https://github.com/python/cpython/blob/21d2a9ab2f4dcbf1be462d3b7f7a231a46bc1cb7/Parser/asdl.py
3
+
4
+ -------------------------------------------------------------------------------
5
+
6
+ Parser for ASDL [1] definition files. Reads in an ASDL description and parses it into an AST that describes it.
7
+
8
+ The EBNF we're parsing here: Figure 1 of the paper [1]. Extended to support modules and attributes after a product.
9
+ Words starting with Capital letters are terminals. Literal tokens are in "double quotes". Others are non-terminals. Id
10
+ is either TokenId or ConstructorId.
11
+
12
+ module ::= "module" Id "{" [definitions] "}"
13
+ definitions ::= { TypeId "=" type }
14
+ type ::= product | sum
15
+ product ::= fields ["attributes" fields]
16
+ fields ::= "(" { field, "," } field ")"
17
+ field ::= TypeId ["?" | "*"] [Id]
18
+ sum ::= constructor { "|" constructor } ["attributes" fields]
19
+ constructor ::= ConstructorId [fields]
20
+
21
+ [1] "The Zephyr Abstract Syntax Description Language" by Wang, et. al. See http://asdl.sourceforge.net/
22
+ """
23
+ # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
24
+ # --------------------------------------------
25
+ #
26
+ # 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
27
+ # ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
28
+ # documentation.
29
+ #
30
+ # 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
31
+ # royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
32
+ # works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
33
+ # Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001-2024 Python Software Foundation; All Rights
34
+ # Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
35
+ #
36
+ # 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
37
+ # wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
38
+ # any such work a brief summary of the changes made to Python.
39
+ #
40
+ # 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
41
+ # EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
42
+ # OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
43
+ # RIGHTS.
44
+ #
45
+ # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
46
+ # DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
47
+ # ADVISED OF THE POSSIBILITY THEREOF.
48
+ #
49
+ # 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
50
+ #
51
+ # 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
52
+ # venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
53
+ # name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
54
+ #
55
+ # 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
56
+ # License Agreement.
57
+ # ruff: noqa: N802
58
+ import abc
59
+ import dataclasses as dc
60
+ import enum
61
+ import re
62
+ import typing as ta
63
+
64
+ from .. import cached
65
+ from .. import check
66
+
67
+
68
+ ##
69
+
70
+
71
+ # The following classes define nodes into which the Asdl description is parsed. Note: this is a "meta-AST". Asdl files
72
+ # (such as Python.asdl) describe the AST structure used by a programming language. But Asdl files themselves need to be
73
+ # parsed. This module parses Asdl files and uses a simple AST to represent them. See the EBNF at the top of the file to
74
+ # understand the logical connection between the various node types.
75
+
76
+ class Ast(abc.ABC):
77
+ @abc.abstractmethod
78
+ def __repr__(self) -> str:
79
+ raise NotImplementedError
80
+
81
+
82
+ @dc.dataclass(frozen=True)
83
+ class Field(Ast):
84
+ type: str
85
+ name: str | None = None
86
+ seq: bool = False
87
+ opt: bool = False
88
+
89
+ def __str__(self) -> str:
90
+ if self.seq:
91
+ extra = '*'
92
+ elif self.opt:
93
+ extra = '?'
94
+ else:
95
+ extra = ''
96
+
97
+ return f'{self.type}{extra} {self.name}'
98
+
99
+ def __repr__(self) -> str:
100
+ if self.seq:
101
+ extra = ', seq=True'
102
+ elif self.opt:
103
+ extra = ', opt=True'
104
+ else:
105
+ extra = ''
106
+ if self.name is None:
107
+ return f'Field({self.type}{extra})'
108
+ else:
109
+ return f'Field({self.type}, {self.name}{extra})'
110
+
111
+
112
+ @dc.dataclass(frozen=True)
113
+ class Constructor(Ast):
114
+ name: str
115
+ fields: ta.Sequence[Field] | None = None
116
+
117
+ def __repr__(self) -> str:
118
+ return f'Constructor({self.name}, {self.fields})'
119
+
120
+
121
+ @dc.dataclass(frozen=True)
122
+ class Sum(Ast):
123
+ types: ta.Sequence[Constructor]
124
+ attributes: ta.Sequence[Field] | None = None
125
+
126
+ def __repr__(self) -> str:
127
+ if self.attributes:
128
+ return f'Sum({self.types}, {self.attributes})'
129
+ else:
130
+ return f'Sum({self.types})'
131
+
132
+
133
+ @dc.dataclass(frozen=True)
134
+ class Product(Ast):
135
+ fields: ta.Sequence[Field]
136
+ attributes: ta.Sequence[Field] | None = None
137
+
138
+ def __repr__(self) -> str:
139
+ if self.attributes:
140
+ return f'Product({self.fields}, {self.attributes})'
141
+ else:
142
+ return f'Product({self.fields})'
143
+
144
+
145
+ @dc.dataclass(frozen=True)
146
+ class Type(Ast):
147
+ name: str
148
+ value: Sum | Product
149
+
150
+ def __repr__(self) -> str:
151
+ return f'Type({self.name}, {self.value})'
152
+
153
+
154
+ @dc.dataclass(frozen=True)
155
+ class Module(Ast):
156
+ name: str
157
+ dfns: ta.Sequence[Type]
158
+
159
+ @cached.property
160
+ def types(self) -> ta.Mapping[str, Sum | Product]:
161
+ return {ty.name: ty.value for ty in self.dfns}
162
+
163
+ def __repr__(self) -> str:
164
+ return f'Module({self.name}, {self.dfns})'
165
+
166
+
167
+ ##
168
+
169
+
170
+ # A generic visitor for the meta-Ast that describes Asdl. This can be used by emitters. Note that this visitor does not
171
+ # provide a generic visit method, so a subclass needs to define visit methods from visitModule to as deep as the
172
+ # interesting node.
173
+ # We also define a Check visitor that makes sure the parsed Asdl is well-formed.
174
+
175
+ class VisitorBase:
176
+ """Generic tree visitor for Asts."""
177
+
178
+ def __init__(self) -> None:
179
+ super().__init__()
180
+ self.cache: dict[type, ta.Any] = {}
181
+
182
+ def visit(self, obj, *args):
183
+ klass = obj.__class__
184
+ meth = self.cache.get(klass)
185
+ if meth is None:
186
+ methname = 'visit' + klass.__name__
187
+ meth = getattr(self, methname, None)
188
+ self.cache[klass] = meth
189
+ if meth:
190
+ try:
191
+ meth(obj, *args)
192
+ except Exception as e:
193
+ raise Exception(f'Error visiting {obj!r}') from e
194
+
195
+
196
+ ##
197
+
198
+
199
+ BUILTIN_TYPES: ta.AbstractSet[str] = {
200
+ 'identifier',
201
+ 'string',
202
+ 'int',
203
+ 'constant',
204
+ }
205
+
206
+
207
+ ##
208
+
209
+
210
+ # Types for describing tokens in an Asdl specification.
211
+ class TokenKind(enum.IntEnum):
212
+ """TokenKind is provides a scope for enumerated token kinds."""
213
+
214
+ CONSTRUCTOR_ID = enum.auto()
215
+ TYPE_ID = enum.auto()
216
+ EQUALS = enum.auto()
217
+ COMMA = enum.auto()
218
+ QUESTION = enum.auto()
219
+ PIPE = enum.auto()
220
+ AstERISK = enum.auto()
221
+ L_PAREN = enum.auto()
222
+ R_PAREN = enum.auto()
223
+ L_BRACE = enum.auto()
224
+ R_BRACE = enum.auto()
225
+
226
+
227
+ OPERATOR_TABLE: ta.Mapping[str, TokenKind] = {
228
+ '=': TokenKind.EQUALS,
229
+ ',': TokenKind.COMMA,
230
+ '?': TokenKind.QUESTION,
231
+ '|': TokenKind.PIPE,
232
+ '(': TokenKind.L_PAREN,
233
+ ')': TokenKind.R_PAREN,
234
+ '*': TokenKind.AstERISK,
235
+ '{': TokenKind.L_BRACE,
236
+ '}': TokenKind.R_BRACE,
237
+ }
238
+
239
+
240
+ @dc.dataclass(frozen=True)
241
+ class Token:
242
+ kind: TokenKind
243
+ value: str
244
+ lineno: int
245
+
246
+
247
+ class AsdlSyntaxError(Exception):
248
+ def __init__(self, msg: str, lineno: int | None = None) -> None:
249
+ super().__init__()
250
+ self.msg = msg
251
+ self.lineno = lineno or '<unknown>'
252
+
253
+ def __str__(self) -> str:
254
+ return f'Syntax error on line {self.lineno}: {self.msg}'
255
+
256
+
257
+ def tokenize_asdl(buf: str) -> ta.Iterator[Token]:
258
+ """Tokenize the given buffer. Yield Token objects."""
259
+
260
+ for lineno, line in enumerate(buf.splitlines(), 1):
261
+ for m in re.finditer(r'\s*(\w+|--.*|.)', line.strip()):
262
+ c = m.group(1)
263
+ if c[0].isalpha():
264
+ # Some kind of identifier
265
+ if c[0].isupper():
266
+ yield Token(TokenKind.CONSTRUCTOR_ID, c, lineno)
267
+ else:
268
+ yield Token(TokenKind.TYPE_ID, c, lineno)
269
+ elif c[:2] == '--':
270
+ # Comment
271
+ break
272
+ else:
273
+ # Operators
274
+ try:
275
+ op_kind = OPERATOR_TABLE[c]
276
+ except KeyError:
277
+ raise AsdlSyntaxError(f'Invalid operator {c}', lineno) # noqa
278
+ yield Token(op_kind, c, lineno)
279
+
280
+
281
+ ##
282
+
283
+
284
+ class AsdlParser:
285
+ """
286
+ Parser for Asdl files.
287
+
288
+ Create, then call the parse method on a buffer containing Asdl. This is a simple recursive descent parser that uses
289
+ tokenize_asdl for the lexing.
290
+ """
291
+
292
+ def __init__(self) -> None:
293
+ super().__init__()
294
+ self._tokenizer: ta.Iterator[Token] | None = None
295
+ self.cur_token: Token | None = None
296
+
297
+ def cur(self) -> Token:
298
+ return check.not_none(self.cur_token)
299
+
300
+ def parse(self, buf: str) -> Module:
301
+ """Parse the Asdl in the buffer and return an Ast with a Module root."""
302
+
303
+ self._tokenizer = tokenize_asdl(buf)
304
+ self._advance()
305
+ return self._parse_module()
306
+
307
+ def _parse_module(self) -> Module:
308
+ if self._at_keyword('module'):
309
+ self._advance()
310
+ else:
311
+ raise AsdlSyntaxError(f'Expected "module" (found {self.cur().value})', self.cur().lineno) # noqa
312
+ name = self._match(self._id_kinds)
313
+ self._match(TokenKind.L_BRACE)
314
+ defs = self._parse_definitions()
315
+ self._match(TokenKind.R_BRACE)
316
+ return Module(name, defs)
317
+
318
+ def _parse_definitions(self) -> ta.Sequence[Type]:
319
+ defs = []
320
+ while self.cur().kind == TokenKind.TYPE_ID:
321
+ typename = check.non_empty_str(self._advance())
322
+ self._match(TokenKind.EQUALS)
323
+ ty = self._parse_type()
324
+ defs.append(Type(typename, ty))
325
+ return defs
326
+
327
+ def _parse_type(self) -> Sum | Product:
328
+ if self.cur().kind == TokenKind.L_PAREN:
329
+ # If we see a (, it's a product
330
+ return self._parse_product()
331
+ else:
332
+ # Otherwise it's a sum. Look for ConstructorId
333
+ sumlist = [Constructor(self._match(TokenKind.CONSTRUCTOR_ID), self._parse_optional_fields())]
334
+ while self.cur().kind == TokenKind.PIPE:
335
+ # More constructors
336
+ self._advance()
337
+ sumlist.append(Constructor(
338
+ self._match(TokenKind.CONSTRUCTOR_ID),
339
+ self._parse_optional_fields()),
340
+ )
341
+ return Sum(sumlist, self._parse_optional_attributes())
342
+
343
+ def _parse_product(self) -> Product:
344
+ return Product(self._parse_fields(), self._parse_optional_attributes())
345
+
346
+ def _parse_fields(self) -> ta.Sequence[Field]:
347
+ fields = []
348
+ self._match(TokenKind.L_PAREN)
349
+ while self.cur().kind == TokenKind.TYPE_ID:
350
+ typename = check.non_empty_str(self._advance())
351
+ is_seq, is_opt = self._parse_optional_field_quantifier()
352
+ id = self._advance() if self.cur().kind in self._id_kinds else None # noqa
353
+ fields.append(Field(typename, id, seq=is_seq, opt=is_opt))
354
+ if self.cur().kind == TokenKind.R_PAREN:
355
+ break
356
+ elif self.cur().kind == TokenKind.COMMA:
357
+ self._advance()
358
+ self._match(TokenKind.R_PAREN)
359
+ return fields
360
+
361
+ def _parse_optional_fields(self) -> ta.Sequence[Field] | None:
362
+ if self.cur().kind == TokenKind.L_PAREN:
363
+ return self._parse_fields()
364
+ else:
365
+ return None
366
+
367
+ def _parse_optional_attributes(self) -> ta.Sequence[Field] | None:
368
+ if self._at_keyword('attributes'):
369
+ self._advance()
370
+ return self._parse_fields()
371
+ else:
372
+ return None
373
+
374
+ def _parse_optional_field_quantifier(self) -> tuple[bool, bool]: # (seq, opt)
375
+ is_seq, is_opt = False, False
376
+ if self.cur().kind == TokenKind.AstERISK:
377
+ is_seq = True
378
+ self._advance()
379
+ elif self.cur().kind == TokenKind.QUESTION:
380
+ is_opt = True
381
+ self._advance()
382
+ return is_seq, is_opt
383
+
384
+ def _advance(self) -> str | None:
385
+ """Return the value of the current token and read the next one into self.cur_token."""
386
+
387
+ cur_val = None if self.cur_token is None else self.cur_token.value
388
+ try:
389
+ self.cur_token = next(check.not_none(self._tokenizer))
390
+ except StopIteration:
391
+ self.cur_token = None
392
+ return cur_val
393
+
394
+ _id_kinds = (TokenKind.CONSTRUCTOR_ID, TokenKind.TYPE_ID)
395
+
396
+ def _match(self, kind: TokenKind | tuple[TokenKind, ...]) -> str:
397
+ """The 'match' primitive of RD parsers.
398
+
399
+ * Verifies that the current token is of the given kind (kind can be a tuple, in which the kind must match one of
400
+ its members).
401
+ * Returns the value of the current token
402
+ * Reads in the next token
403
+ """
404
+
405
+ if isinstance(kind, tuple) and self.cur().kind in kind or self.cur().kind == kind:
406
+ value = self.cur().value
407
+ self._advance()
408
+ return value
409
+ else:
410
+ raise AsdlSyntaxError(
411
+ f'Unmatched {kind} (found {self.cur().kind})',
412
+ self.cur().lineno,
413
+ )
414
+
415
+ def _at_keyword(self, keyword: str) -> bool:
416
+ return self.cur().kind == TokenKind.TYPE_ID and self.cur().value == keyword
417
+
418
+
419
+ ##
420
+
421
+
422
+ FlatFieldArity: ta.TypeAlias = ta.Literal[1, '?', '*']
423
+
424
+
425
+ @dc.dataclass(frozen=True)
426
+ class FlatField:
427
+ name: str
428
+ type: str
429
+ n: FlatFieldArity = 1
430
+
431
+
432
+ @dc.dataclass(frozen=True)
433
+ class FlatNode(abc.ABC):
434
+ name: str
435
+ fields: ta.Sequence[FlatField] = dc.field(default=(), kw_only=True)
436
+ attributes: ta.Sequence[FlatField] = dc.field(default=(), kw_only=True)
437
+
438
+
439
+ @dc.dataclass(frozen=True)
440
+ class FlatSum(FlatNode):
441
+ constructors: ta.Sequence[str] = dc.field(default=(), kw_only=True)
442
+
443
+
444
+ @dc.dataclass(frozen=True)
445
+ class FlatProduct(FlatNode):
446
+ pass
447
+
448
+
449
+ @dc.dataclass(frozen=True)
450
+ class FlatConstructor(FlatNode):
451
+ sum: str = dc.field(kw_only=True)
452
+
453
+
454
+ def flatten(mod: Module) -> ta.Mapping[str, FlatNode]:
455
+ lst: list[FlatNode] = []
456
+
457
+ def mk_field(af: Field) -> FlatField:
458
+ return FlatField(
459
+ check.non_empty_str(af.name),
460
+ af.type,
461
+ n='*' if af.seq else '?' if af.opt else 1,
462
+ )
463
+
464
+ def mk_fields(afs: ta.Iterable[Field] | None) -> ta.Sequence[FlatField]:
465
+ return list(map(mk_field, afs or []))
466
+
467
+ for ty in mod.dfns:
468
+ v = ty.value
469
+
470
+ if isinstance(v, Sum):
471
+ lst.append(FlatSum(
472
+ ty.name,
473
+ attributes=mk_fields(v.attributes),
474
+ constructors=[c.name for c in v.types],
475
+ ))
476
+
477
+ for c in v.types:
478
+ lst.append(FlatConstructor(
479
+ c.name,
480
+ fields=mk_fields(c.fields),
481
+ attributes=mk_fields(v.attributes),
482
+ sum=ty.name,
483
+ ))
484
+
485
+ elif isinstance(v, Product):
486
+ lst.append(FlatProduct(
487
+ ty.name,
488
+ fields=mk_fields(v.fields),
489
+ attributes=mk_fields(v.attributes),
490
+ ))
491
+
492
+ else:
493
+ raise TypeError(v)
494
+
495
+ #
496
+
497
+ dct: dict[str, FlatNode] = {}
498
+
499
+ for n in lst:
500
+ if n.name in dct:
501
+ raise KeyError(n.name)
502
+ dct[n.name] = n
503
+
504
+ return dct
505
+
506
+
507
+ #
508
+
509
+
510
+ def build_fields_info(
511
+ nodes: ta.Mapping[str, FlatNode],
512
+ *,
513
+ exclude_builtins: bool = False,
514
+ ) -> ta.Mapping[str, ta.Mapping[str, tuple[str, FlatFieldArity]]]:
515
+ dct: dict[str, dict[str, tuple[str, FlatFieldArity]]] = {}
516
+ for n in nodes.values():
517
+ cur = {}
518
+ f: FlatField
519
+ for f in [*n.fields, *n.attributes]:
520
+ if f.type in BUILTIN_TYPES:
521
+ if exclude_builtins:
522
+ continue
523
+ elif f.type not in nodes:
524
+ raise KeyError(f.type)
525
+ cur[f.name] = (f.type, f.n)
526
+ if cur:
527
+ dct[n.name] = cur
528
+ return dct
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev27
3
+ Version: 0.0.0.dev28
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=N1F-Xz3GaBn2H1p7uKzhkhKCQV8QVR0t76XD6wmFtXA,3
2
- omlish/__about__.py,sha256=zjwXesy8pai60AJpe7FV4t72dwRlRVraIytt0d1XmnE,2721
2
+ omlish/__about__.py,sha256=SF90VIrjkctgajp8SFdNIxh69XQtRt20Y0ZhGvmT-3Q,2721
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=QRQmX9G0-L_nATkFtGHvpd4qrpYzKATdjuFLbBqzJPM,6224
5
5
  omlish/c3.py,sha256=W5EwYx9Por3rWYLkKUitJ6OoRMLLgVTfLTyroOz41Y0,8047
@@ -7,7 +7,7 @@ omlish/cached.py,sha256=UAizxlH4eMWHPzQtmItmyE6FEpFEUFzIkxaO2BHWZ5s,196
7
7
  omlish/check.py,sha256=3qp1_W8uRp23I26nWvG_c7YFxdTwJAZEFxmY8Bfw50Y,10078
8
8
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
9
9
  omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
10
- omlish/docker.py,sha256=GyzrHUTdD3rHeDBPGOI_mBzysVDtv5YAo1XN-HnMeo8,6231
10
+ omlish/docker.py,sha256=wipM7Xsx7rUv7-PoBTTT2v0uk3JVan0USljgm2QesXg,6227
11
11
  omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
12
12
  omlish/fnpairs.py,sha256=hVuLqQFdRNNze3FYH2cAQO3GC7nK5yQP_GWPUSbL7nE,10601
13
13
  omlish/genmachine.py,sha256=LCMiqvK32dAWtrlB6lKw9tXdQFiXC8rRdk4TMQYIroU,1603
@@ -70,7 +70,7 @@ omlish/dataclasses/__init__.py,sha256=L2kRMvsWgsennXVw7VgZdczCtdLsQzyPcMFit2rBpb
70
70
  omlish/dataclasses/utils.py,sha256=qBKHyW2LSjnJYfPSAExEsVZ5EdmDBfp4napAhfWkZFs,3221
71
71
  omlish/dataclasses/impl/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
72
72
  omlish/dataclasses/impl/__init__.py,sha256=rQJRcE9fVsGEvRUOZ18r4DE0xiLbSRjspyJRXgbLS3I,348
73
- omlish/dataclasses/impl/api.py,sha256=PyBMuoamkp77r3OS7TIWKv27cy7laqN2iviq5JoihuE,6339
73
+ omlish/dataclasses/impl/api.py,sha256=UsCUtpHbElFEht0h-LiLKKOwlsrngmryz8jy9HDbNLw,6398
74
74
  omlish/dataclasses/impl/as_.py,sha256=CD-t7hkC1EP2F_jvZKIA_cVoDuwZ-Ln_xC4fJumPYX0,2598
75
75
  omlish/dataclasses/impl/copy.py,sha256=Tn8_n6Vohs-w4otbGdubBEvhd3TsSTaM3EfNGdS2LYo,591
76
76
  omlish/dataclasses/impl/descriptors.py,sha256=rEYE1Len99agTQCC25hSPMnM19BgPr0ZChABGi58Fdk,2476
@@ -84,7 +84,7 @@ omlish/dataclasses/impl/main.py,sha256=Ti0PKbFKraKvfmoPuR-G7nLVNzRC8mvEuXhCuC-M2
84
84
  omlish/dataclasses/impl/metaclass.py,sha256=dlQEIN9MHBirll7Nx3StpzxYxXjrqxJ-QsorMcCNt7w,2828
85
85
  omlish/dataclasses/impl/metadata.py,sha256=4veWwTr-aA0KP-Y1cPEeOcXHup9EKJTYNJ0ozIxtzD4,1401
86
86
  omlish/dataclasses/impl/order.py,sha256=zWvWDkSTym8cc7vO1cLHqcBhhjOlucHOCUVJcdh4jt0,1369
87
- omlish/dataclasses/impl/params.py,sha256=1kvL75eptGll9NXqBv66HN94ACwv_tucBRb1cDAvglM,2620
87
+ omlish/dataclasses/impl/params.py,sha256=KOeRuqVgYRa229tstv0whaYvT1SlXWEDLksiBCxxcQ4,2667
88
88
  omlish/dataclasses/impl/processing.py,sha256=DFxyFjL_h3awRyF_5eyTnB8QkuApx7Zc4QFnVoltlao,459
89
89
  omlish/dataclasses/impl/reflect.py,sha256=a19BbNxrmjNTbXzWuAl_794RCIQSMYyVqQ2Bf-DnNnM,5305
90
90
  omlish/dataclasses/impl/replace.py,sha256=wS9GHX4fIwaPv1JBJzIewdBfXyK3X3V7_t55Da87dYo,1217
@@ -170,7 +170,7 @@ omlish/lang/clsdct.py,sha256=AjtIWLlx2E6D5rC97zQ3Lwq2SOMkbg08pdO_AxpzEHI,1744
170
170
  omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
171
171
  omlish/lang/contextmanagers.py,sha256=rzMSwJU7ObFXl46r6pGDbD45Zi_qZ9NHxDPnLNuux9o,9732
172
172
  omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
173
- omlish/lang/descriptors.py,sha256=tZzDyXSp3wMNzTytt9BScFFriBwMRpt0EeYP3CRKPX8,6602
173
+ omlish/lang/descriptors.py,sha256=OLM1qi14kY7PLGIJnvkd6CBEOzHgD9q8Cs2cB6Kzflk,6602
174
174
  omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
175
175
  omlish/lang/functions.py,sha256=yJxWwqlXEAT2gied4uTwiz5x1qXeuVubOSXyn9zy5aI,3624
176
176
  omlish/lang/imports.py,sha256=04ugFC8NI5sbL7NH4V0r0q_nFsP_AMkHLz697CVkMtQ,6274
@@ -215,7 +215,7 @@ omlish/logs/formatters.py,sha256=q79nMnR2mRIStPyGrydQHpYTXgC5HHptt8lH3W2Wwbs,671
215
215
  omlish/logs/handlers.py,sha256=nyuFgmO05By_Xwq7es58ClzS51-F53lJL7gD0x5IqAg,228
216
216
  omlish/logs/noisy.py,sha256=8JORjI1dH38yU2MddM54OB6qt32Xozfocdb88vY4wro,335
217
217
  omlish/logs/utils.py,sha256=MgGovbP0zUrZ3FGD3qYNQWn-l0jy0Y0bStcQvv5BOmQ,391
218
- omlish/marshal/__init__.py,sha256=8r5nY0DMVT_kT4hARuTDsa8GOdshko40VEzOq0qNdS8,1628
218
+ omlish/marshal/__init__.py,sha256=RkZGfdp7x9KU8nEpb8hYBGANK-P_9AXMyjBXCrdYMmc,1757
219
219
  omlish/marshal/any.py,sha256=e82OyYK3Emm1P1ClnsnxP7fIWC2iNVyW0H5nK4mLmWM,779
220
220
  omlish/marshal/base.py,sha256=EIgrqsQ1OQ4mVUMuDH5zRBCwJpn8ijVS98Nmoka_Mrs,6025
221
221
  omlish/marshal/base64.py,sha256=F-3ogJdcFCtWINRgJgWT0rErqgx6f4qahhcg8OrkqhE,1089
@@ -232,7 +232,7 @@ omlish/marshal/mappings.py,sha256=zhLtyot7tzQtBNj7C4RBxjMELxA5r2q2Mth8Br7xkFs,28
232
232
  omlish/marshal/maybes.py,sha256=tKkVsJATERgbVcEfBnsHBK_2_LCQIVyBzca-cA-9KH0,2112
233
233
  omlish/marshal/naming.py,sha256=UCviMAXTTUpW1lyAGymybGP2rFUAW44P1X0zrIVbvi4,464
234
234
  omlish/marshal/numbers.py,sha256=oY_yMNJEnJhjfLh89gpPXvKqeUyhQcaTcQB6ecyHiG8,1704
235
- omlish/marshal/objects.py,sha256=T3prlm0HESpOLXHWcdsYekfmythJRZQOvaoL0is8J_o,3097
235
+ omlish/marshal/objects.py,sha256=R-NPCT1-UZhONTnrsrAvZvAtM2qyQsKZ8CPLfqkSg5g,4494
236
236
  omlish/marshal/optionals.py,sha256=r0XB5rqfasvgZJNrKYd6Unq2U4nHt3JURi26j0dYHlw,1499
237
237
  omlish/marshal/polymorphism.py,sha256=gyvNYUAkmQVhWrcXBLzXINxqx6RHyulf9n16Iv38PFI,5597
238
238
  omlish/marshal/primitives.py,sha256=wcvcs5GH_TWVmzAszh3dvyKibJgBxnXke-AlAXiwrrI,1107
@@ -306,12 +306,13 @@ omlish/testing/pytest/plugins/spacing.py,sha256=JQQhi9q3c523Ro1a_K_9RGAb7HotiO74
306
306
  omlish/testing/pytest/plugins/switches.py,sha256=9FtN5qtPBoS-teEp54OHPF6jlZJakRJdq4pnLJpPj_A,3001
307
307
  omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0McdhwZWvfMlU,261
308
308
  omlish/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
309
+ omlish/text/asdl.py,sha256=9Zbd8az7jum1EqRYuNaKyRFG58WA56G4nLHWc_NBN8g,16856
309
310
  omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
310
311
  omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
311
312
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
312
313
  omlish/text/parts.py,sha256=KGgo0wHOIMVMZtDso-rhSWKAcAkYAH2IGpg9tULabu8,6505
313
- omlish-0.0.0.dev27.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
314
- omlish-0.0.0.dev27.dist-info/METADATA,sha256=y3f_PoZpAVj2Mpn51ruURaZtqJ0Tm8YWmtFacYsa0ck,3666
315
- omlish-0.0.0.dev27.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
316
- omlish-0.0.0.dev27.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
317
- omlish-0.0.0.dev27.dist-info/RECORD,,
314
+ omlish-0.0.0.dev28.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
315
+ omlish-0.0.0.dev28.dist-info/METADATA,sha256=kz-tHFHaXXkqK4NutvYQ6Uvxag9ogDnJFyUX0DgBrFo,3666
316
+ omlish-0.0.0.dev28.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
317
+ omlish-0.0.0.dev28.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
318
+ omlish-0.0.0.dev28.dist-info/RECORD,,