rapydscript-ns 0.8.4 → 0.9.1

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.
Files changed (141) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +26 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +716 -169
  8. package/TODO.md +7 -2
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +36 -27
  15. package/package.json +1 -1
  16. package/publish.py +37 -37
  17. package/release/baselib-plain-pretty.js +2358 -168
  18. package/release/baselib-plain-ugly.js +73 -3
  19. package/release/compiler.js +6283 -3093
  20. package/release/signatures.json +31 -30
  21. package/session.vim +4 -4
  22. package/setup.cfg +2 -2
  23. package/src/ast.pyj +1 -0
  24. package/src/baselib-builtins.pyj +340 -2
  25. package/src/baselib-bytes.pyj +664 -0
  26. package/src/baselib-errors.pyj +1 -1
  27. package/src/baselib-internal.pyj +267 -60
  28. package/src/baselib-itertools.pyj +110 -97
  29. package/src/baselib-str.pyj +22 -4
  30. package/src/compiler.pyj +36 -36
  31. package/src/errors.pyj +30 -30
  32. package/src/lib/abc.pyj +317 -0
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/contextlib.pyj +379 -0
  35. package/src/lib/copy.pyj +120 -120
  36. package/src/lib/dataclasses.pyj +532 -0
  37. package/src/lib/datetime.pyj +712 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/enum.pyj +125 -0
  41. package/src/lib/gettext.pyj +569 -569
  42. package/src/lib/io.pyj +500 -0
  43. package/src/lib/itertools.pyj +580 -580
  44. package/src/lib/json.pyj +227 -0
  45. package/src/lib/math.pyj +193 -193
  46. package/src/lib/operator.pyj +11 -11
  47. package/src/lib/pythonize.pyj +20 -20
  48. package/src/lib/random.pyj +118 -118
  49. package/src/lib/re.pyj +504 -470
  50. package/src/lib/react.pyj +74 -74
  51. package/src/lib/traceback.pyj +63 -63
  52. package/src/lib/typing.pyj +577 -0
  53. package/src/lib/uuid.pyj +77 -77
  54. package/src/monaco-language-service/builtins.js +14 -4
  55. package/src/monaco-language-service/diagnostics.js +19 -20
  56. package/src/monaco-language-service/dts.js +550 -550
  57. package/src/output/classes.pyj +62 -26
  58. package/src/output/comments.pyj +45 -45
  59. package/src/output/exceptions.pyj +201 -201
  60. package/src/output/functions.pyj +78 -5
  61. package/src/output/jsx.pyj +164 -164
  62. package/src/output/loops.pyj +5 -2
  63. package/src/output/operators.pyj +100 -34
  64. package/src/output/treeshake.pyj +182 -182
  65. package/src/output/utils.pyj +72 -72
  66. package/src/parse.pyj +80 -16
  67. package/src/string_interpolation.pyj +72 -72
  68. package/src/tokenizer.pyj +10 -5
  69. package/src/unicode_aliases.pyj +576 -576
  70. package/src/utils.pyj +192 -192
  71. package/test/_import_one.pyj +37 -37
  72. package/test/_import_two/__init__.pyj +11 -11
  73. package/test/_import_two/level2/deep.pyj +4 -4
  74. package/test/_import_two/other.pyj +6 -6
  75. package/test/_import_two/sub.pyj +13 -13
  76. package/test/abc.pyj +291 -0
  77. package/test/aes_vectors.pyj +421 -421
  78. package/test/annotations.pyj +80 -80
  79. package/test/arithmetic_nostrict.pyj +88 -0
  80. package/test/arithmetic_types.pyj +169 -0
  81. package/test/baselib.pyj +91 -0
  82. package/test/bytes.pyj +467 -0
  83. package/test/classes.pyj +1 -0
  84. package/test/comparison_ops.pyj +173 -0
  85. package/test/contextlib.pyj +362 -0
  86. package/test/dataclasses.pyj +253 -0
  87. package/test/datetime.pyj +500 -0
  88. package/test/debugger_stmt.pyj +41 -0
  89. package/test/decorators.pyj +77 -77
  90. package/test/docstrings.pyj +39 -39
  91. package/test/elementmaker_test.pyj +45 -45
  92. package/test/enum.pyj +134 -0
  93. package/test/eval_exec.pyj +56 -0
  94. package/test/format.pyj +148 -0
  95. package/test/functions.pyj +151 -151
  96. package/test/generators.pyj +41 -41
  97. package/test/generic.pyj +370 -370
  98. package/test/imports.pyj +72 -72
  99. package/test/internationalization.pyj +73 -73
  100. package/test/io.pyj +316 -0
  101. package/test/json.pyj +196 -0
  102. package/test/lint.pyj +164 -164
  103. package/test/loops.pyj +85 -85
  104. package/test/numpy.pyj +734 -734
  105. package/test/object.pyj +64 -0
  106. package/test/omit_function_metadata.pyj +20 -20
  107. package/test/python_compat.pyj +17 -15
  108. package/test/python_features.pyj +70 -15
  109. package/test/regexp.pyj +83 -55
  110. package/test/repl.pyj +121 -121
  111. package/test/scoped_flags.pyj +76 -76
  112. package/test/tuples.pyj +96 -0
  113. package/test/typing.pyj +469 -0
  114. package/test/unit/index.js +116 -7
  115. package/test/unit/language-service-dts.js +543 -543
  116. package/test/unit/language-service-hover.js +455 -455
  117. package/test/unit/language-service.js +84 -0
  118. package/test/unit/web-repl.js +1337 -1
  119. package/test/vars_locals_globals.pyj +94 -0
  120. package/tools/cli.js +558 -547
  121. package/tools/compile.js +224 -219
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +262 -251
  124. package/tools/gettext.js +185 -185
  125. package/tools/ini.js +65 -65
  126. package/tools/lint.js +16 -19
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -196
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -252
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +224 -224
  139. package/web-repl/sha1.js +25 -25
  140. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  141. package/PYTHON_FEATURE_COVERAGE.md +0 -200
@@ -0,0 +1,532 @@
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD
3
+ # RapydScript implementation of Python's dataclasses standard library.
4
+ #
5
+ # Supported:
6
+ # @dataclass — auto-generates __init__, __repr__, __eq__
7
+ # @dataclass(init, repr, eq, order, unsafe_hash, frozen)
8
+ # field(...) — field descriptor; see note on 'default' below
9
+ # fields(instance_or_class) — tuple of Field objects
10
+ # asdict(instance) — deep-converts to plain dict
11
+ # astuple(instance) — deep-converts to tuple
12
+ # replace(instance, **changes) — copy with field changes
13
+ # is_dataclass(obj) — True if class or instance is a dataclass
14
+ # MISSING — sentinel for "no value given"
15
+ # KW_ONLY — sentinel for keyword-only field separator
16
+ #
17
+ # Note on field() and the 'default' reserved word:
18
+ # In JavaScript (and therefore RapydScript), 'default' is a reserved word
19
+ # and cannot be used as a parameter name. Use the FIRST POSITIONAL ARGUMENT
20
+ # to pass a plain default value instead of the keyword form:
21
+ #
22
+ # name: str = field("unnamed") # default="unnamed"
23
+ # items: list = field(default_factory=list) # default_factory=list
24
+ #
25
+ # Usage:
26
+ # from dataclasses import dataclass, field
27
+ #
28
+ # @dataclass
29
+ # class Point:
30
+ # x: float
31
+ # y: float = 0.0
32
+ #
33
+ # p = Point(1.0)
34
+ # repr(p) # 'Point(x=1.0, y=0.0)'
35
+ # p == Point(1.0) # True
36
+ #
37
+ # Implementation notes:
38
+ # - The RapydScript compiler now emits ClassName.__annotations__ = {...}
39
+ # for any class that contains annotated variable declarations (x: T or
40
+ # x: T = v). @dataclass reads this to discover field names in order.
41
+ # - Fields that have no default value have no corresponding prototype key.
42
+ # - Field sentinels (Field objects) on the prototype are cleaned up by the
43
+ # decorator so instances don't inherit them.
44
+ # - Frozen dataclasses use Object.defineProperty with writable: false.
45
+
46
+
47
+ # Internal sentinel — distinct from None and undefined.
48
+ class _MISSING_TYPE:
49
+ def __repr__(self):
50
+ return 'MISSING'
51
+
52
+ MISSING = _MISSING_TYPE()
53
+
54
+ # KW_ONLY sentinel
55
+ class _KW_ONLY_TYPE:
56
+ def __repr__(self):
57
+ return 'KW_ONLY'
58
+
59
+ KW_ONLY = _KW_ONLY_TYPE()
60
+
61
+
62
+ class Field:
63
+ """Represents the metadata for a single dataclass field."""
64
+
65
+ # Parameters use _default / _default_factory because 'default' is a JS
66
+ # reserved word and cannot be used as an identifier in RapydScript.
67
+ def __init__(self, _default, _default_factory, _init, _repr, _hash,
68
+ _compare, _metadata, _kw_only):
69
+ self.name = None # set by @dataclass
70
+ self.type = None # always None (type info erased in JS)
71
+ self.default = _default
72
+ self.default_factory = _default_factory
73
+ self.init = _init
74
+ self.repr = _repr
75
+ self.hash = _hash
76
+ self.compare = _compare
77
+ self.metadata = _metadata if _metadata is not None else {}
78
+ self.kw_only = _kw_only
79
+ self._field_type = 'FIELD'
80
+
81
+ def __repr__(self):
82
+ return ('Field(name=' + repr(self.name) +
83
+ ',default=' + repr(self.default) +
84
+ ',default_factory=' + repr(self.default_factory) + ')')
85
+
86
+
87
+ def field(*args, **kwargs):
88
+ """
89
+ Return a Field descriptor for use as a class-level default in a dataclass.
90
+
91
+ Because 'default' is a JS reserved word, pass a plain default value as
92
+ the FIRST POSITIONAL ARGUMENT instead of default=...:
93
+
94
+ name: str = field("unnamed") # plain default
95
+ items: list = field(default_factory=list) # factory default
96
+ secret: str = field(repr=False) # repr suppressed, no default
97
+ dist: float = field(init=False) # computed in __post_init__
98
+ """
99
+ # First positional arg (if any) is the plain default value.
100
+ _default = args[0] if args.length > 0 else MISSING
101
+ _default_factory = kwargs['default_factory'] if 'default_factory' in kwargs else MISSING
102
+
103
+ if _default is not MISSING and _default_factory is not MISSING:
104
+ raise ValueError('cannot specify both default and default_factory')
105
+
106
+ _init = kwargs['init'] if 'init' in kwargs else True
107
+ _repr = kwargs['repr'] if 'repr' in kwargs else True
108
+ _hash = kwargs['hash'] if 'hash' in kwargs else None
109
+ _compare = kwargs['compare'] if 'compare' in kwargs else True
110
+ _meta = kwargs['metadata'] if 'metadata' in kwargs else None
111
+ _kw_only = kwargs['kw_only'] if 'kw_only' in kwargs else False
112
+
113
+ return Field(_default, _default_factory, _init, _repr, _hash, _compare, _meta, _kw_only)
114
+
115
+
116
+ def _is_field(obj):
117
+ return isinstance(obj, Field)
118
+
119
+
120
+ def _get_own_annotations(cls):
121
+ """Return cls.__annotations__ (own only, not inherited), or {}."""
122
+ v"""if (!Object.prototype.hasOwnProperty.call(cls, '__annotations__')) return {};"""
123
+ anns = cls.__annotations__
124
+ return anns if anns else {}
125
+
126
+
127
+ def _collect_fields(cls):
128
+ """
129
+ Return a list of Field objects for *cls* in declaration order.
130
+
131
+ Base-class fields (from parent dataclasses) come first, then the
132
+ subclass's own fields — matching Python's dataclasses behaviour.
133
+ """
134
+ # Collect inherited fields from the direct parent dataclass (if any).
135
+ # __bases__ is defined on cls.prototype (via Object.defineProperty).
136
+ inherited = []
137
+ bases = v"(cls.prototype && cls.prototype.__bases__) || []"
138
+ for base in bases:
139
+ if base and base.__dataclass_fields__:
140
+ base_dfc = base.__dataclass_fields__
141
+ for nm in v"Object.keys(base_dfc)":
142
+ inherited.push(base_dfc[nm])
143
+
144
+ own_anns = _get_own_annotations(cls)
145
+ own_names = v"Object.keys(own_anns)"
146
+ own_fields = []
147
+ for name in own_names:
148
+ if name.startsWith('_'):
149
+ continue
150
+ proto_val = cls.prototype[name]
151
+ if _is_field(proto_val):
152
+ f = proto_val
153
+ elif jstype(proto_val) is not 'undefined':
154
+ f = Field(proto_val, MISSING, True, True, None, True, None, False)
155
+ else:
156
+ f = Field(MISSING, MISSING, True, True, None, True, None, False)
157
+ f.name = name
158
+ f.type = None
159
+ f._field_type = 'FIELD'
160
+ own_fields.push(f)
161
+
162
+ # Build result: inherited first, then own fields, own overrides inherited.
163
+ seen = {}
164
+ result = []
165
+ for f in inherited:
166
+ seen[f.name] = result.length
167
+ result.push(f)
168
+ for f in own_fields:
169
+ if f.name in seen:
170
+ result[seen[f.name]] = f
171
+ else:
172
+ result.push(f)
173
+ return result
174
+
175
+
176
+ def _process_class(cls, do_init, do_repr, do_eq, do_order, unsafe_hash, frozen):
177
+ """Introspect cls and install the generated dunder methods."""
178
+ dc_fields = _collect_fields(cls)
179
+
180
+ # Store the field registry on the class constructor.
181
+ cls.__dataclass_fields__ = {}
182
+ for f in dc_fields:
183
+ cls.__dataclass_fields__[f.name] = f
184
+ # Clean up Field sentinels from the prototype.
185
+ if _is_field(cls.prototype[f.name]):
186
+ v"delete cls.prototype[f.name]"
187
+
188
+ # Validate ordering: non-default fields must precede default fields
189
+ # (among init=True, non-kw_only fields).
190
+ seen_default = False
191
+ for f in dc_fields:
192
+ if not f.init or f.kw_only:
193
+ continue
194
+ has_def = f.default is not MISSING or f.default_factory is not MISSING
195
+ if has_def:
196
+ seen_default = True
197
+ elif seen_default:
198
+ raise TypeError(
199
+ 'non-default argument ' + repr(f.name) +
200
+ ' follows default argument in dataclass ' + cls.__name__)
201
+
202
+ if do_init:
203
+ _make_init(cls, dc_fields, frozen)
204
+ if do_repr:
205
+ _make_repr(cls, dc_fields)
206
+ if do_eq:
207
+ _make_eq(cls, dc_fields)
208
+ if do_order:
209
+ _make_order(cls, dc_fields)
210
+ if frozen:
211
+ _make_frozen(cls, dc_fields)
212
+ if unsafe_hash or (do_eq and frozen):
213
+ _make_hash(cls, dc_fields)
214
+
215
+ cls.__dataclass_params__ = {
216
+ 'init': do_init, 'repr': do_repr, 'eq': do_eq,
217
+ 'order': do_order, 'unsafe_hash': unsafe_hash, 'frozen': frozen,
218
+ }
219
+
220
+
221
+ def _make_init(cls, dc_fields, frozen):
222
+ """Install a generated __init__ on cls.prototype."""
223
+ init_fields = [f for f in dc_fields if f.init]
224
+
225
+ # The generated function handles both positional and RapydScript-style
226
+ # keyword arguments (kwargs sentinel is the last argument object when
227
+ # keywords are used in a call).
228
+ v"""
229
+ cls.prototype.__init__ = function() {
230
+ var self = this;
231
+ var args = Array.prototype.slice.call(arguments);
232
+ var kwargs = null;
233
+ if (args.length > 0 &&
234
+ args[args.length - 1] !== null &&
235
+ typeof args[args.length - 1] === 'object' &&
236
+ args[args.length - 1][ρσ_kwargs_symbol] === true) {
237
+ kwargs = args.pop();
238
+ }
239
+ for (var ρσ_i = 0; ρσ_i < init_fields.length; ρσ_i++) {
240
+ var f = init_fields[ρσ_i];
241
+ var val;
242
+ if (ρσ_i < args.length) {
243
+ val = args[ρσ_i];
244
+ } else if (kwargs !== null &&
245
+ Object.prototype.hasOwnProperty.call(kwargs, f.name)) {
246
+ val = kwargs[f.name];
247
+ } else if (f.default_factory !== MISSING) {
248
+ val = f.default_factory();
249
+ } else if (f.default !== MISSING) {
250
+ val = f.default;
251
+ } else {
252
+ throw new TypeError(
253
+ cls.__name__ + '() missing required argument: "' + f.name + '"');
254
+ }
255
+ if (frozen) {
256
+ Object.defineProperty(self, f.name, {
257
+ value: val, writable: false, enumerable: true, configurable: true
258
+ });
259
+ } else {
260
+ self[f.name] = val;
261
+ }
262
+ }
263
+ if (typeof self.__post_init__ === 'function') {
264
+ self.__post_init__();
265
+ }
266
+ };
267
+ """
268
+
269
+
270
+ def _make_repr(cls, dc_fields):
271
+ """Install a __repr__ that renders ClassName(field=value, ...)."""
272
+ repr_fields = [f for f in dc_fields if f.repr]
273
+
274
+ def _repr_fn():
275
+ _self = this
276
+ parts = []
277
+ for f in repr_fields:
278
+ parts.push(f.name + '=' + repr(_self[f.name]))
279
+ return _self.__class__.__name__ + '(' + parts.join(', ') + ')'
280
+
281
+ cls.prototype.__repr__ = _repr_fn
282
+
283
+
284
+ def _make_eq(cls, dc_fields):
285
+ """Install __eq__ comparing all compare-flagged fields."""
286
+ cmp_fields = [f for f in dc_fields if f.compare]
287
+
288
+ v"""
289
+ cls.prototype.__eq__ = function(other) {
290
+ if (!(other instanceof cls)) return false;
291
+ for (var ρσ_i = 0; ρσ_i < cmp_fields.length; ρσ_i++) {
292
+ var nm = cmp_fields[ρσ_i].name;
293
+ if (this[nm] !== other[nm]) return false;
294
+ }
295
+ return true;
296
+ };
297
+ """
298
+
299
+
300
+ def _make_order(cls, dc_fields):
301
+ """Install __lt__, __le__, __gt__, __ge__ based on compare fields."""
302
+ cmp_fields = [f for f in dc_fields if f.compare]
303
+
304
+ def _key(obj):
305
+ return [obj[f.name] for f in cmp_fields]
306
+
307
+ def _lt(other):
308
+ a = _key(this)
309
+ b = _key(other)
310
+ for i in range(a.length):
311
+ if a[i] < b[i]:
312
+ return True
313
+ if a[i] > b[i]:
314
+ return False
315
+ return False
316
+
317
+ def _le(other):
318
+ return this.__lt__(other) or this.__eq__(other)
319
+
320
+ def _gt(other):
321
+ return not this.__le__(other)
322
+
323
+ def _ge(other):
324
+ return not this.__lt__(other)
325
+
326
+ cls.prototype.__lt__ = _lt
327
+ cls.prototype.__le__ = _le
328
+ cls.prototype.__gt__ = _gt
329
+ cls.prototype.__ge__ = _ge
330
+
331
+
332
+ def _make_frozen(cls, dc_fields):
333
+ """Install __setattr__ and __delattr__ that raise AttributeError."""
334
+ def _setattr(name, value):
335
+ raise AttributeError(
336
+ 'cannot assign to field ' + repr(name) +
337
+ ' of frozen dataclass ' + repr(this.__class__.__name__))
338
+
339
+ def _delattr(name):
340
+ raise AttributeError(
341
+ 'cannot delete field ' + repr(name) +
342
+ ' of frozen dataclass ' + repr(this.__class__.__name__))
343
+
344
+ cls.prototype.__setattr__ = _setattr
345
+ cls.prototype.__delattr__ = _delattr
346
+
347
+
348
+ def _make_hash(cls, dc_fields):
349
+ """Install __hash__ based on compare-flagged (or explicitly hashed) fields."""
350
+ hash_fields = [f for f in dc_fields
351
+ if (f.hash is None and f.compare) or f.hash is True]
352
+
353
+ def _hash_fn():
354
+ _self = this
355
+ h = 0
356
+ for f in hash_fields:
357
+ v_val = _self[f.name]
358
+ hv = 0
359
+ if jstype(v_val) is 'number':
360
+ hv = v_val
361
+ elif jstype(v_val) is 'string':
362
+ for i in range(v_val.length):
363
+ hv = v'(hv * 31 + v_val.charCodeAt(i)) | 0'
364
+ else:
365
+ hv = v_val if v_val is not None else 0
366
+ h = v'(h * 1000003 ^ hv) | 0'
367
+ return h
368
+
369
+ cls.prototype.__hash__ = _hash_fn
370
+
371
+
372
+ # ─── Public API ──────────────────────────────────────────────────────────────
373
+
374
+ def dataclass(cls=None, *, init=True, repr=True, eq=True, order=False,
375
+ unsafe_hash=False, frozen=False):
376
+ """
377
+ Class decorator that generates __init__, __repr__, __eq__ (and optionally
378
+ ordering and hashing methods) from annotated class variables.
379
+
380
+ Can be used as @dataclass or @dataclass(frozen=True, order=True).
381
+ """
382
+ def wrap(c):
383
+ _process_class(c, init, repr, eq, order, unsafe_hash, frozen)
384
+ return c
385
+
386
+ if cls is None:
387
+ return wrap # @dataclass(...)
388
+ return wrap(cls) # @dataclass
389
+
390
+
391
+ def fields(class_or_instance):
392
+ """Return a tuple of Field objects for the dataclass or its instance."""
393
+ cls = None
394
+ if jstype(class_or_instance) is 'function':
395
+ cls = class_or_instance
396
+ elif class_or_instance is not None:
397
+ cls = class_or_instance.__class__
398
+ if cls and cls.__dataclass_fields__:
399
+ return tuple(v"Object.values(cls.__dataclass_fields__)")
400
+ raise TypeError(repr(class_or_instance) + ' is not a dataclass or dataclass instance')
401
+
402
+
403
+ def is_dataclass(obj):
404
+ """Return True if obj is a dataclass class or instance."""
405
+ if obj is None:
406
+ return False
407
+ if jstype(obj) is 'function':
408
+ return bool(obj.__dataclass_fields__)
409
+ cls = obj.__class__
410
+ return bool(cls and cls.__dataclass_fields__)
411
+
412
+
413
+ def asdict(obj, *, dict_factory=None):
414
+ """Recursively convert a dataclass instance to a dict."""
415
+ if not is_dataclass(obj) or jstype(obj) is 'function':
416
+ raise TypeError('asdict() should be called on dataclass instances')
417
+ return _asdict_inner(obj, dict_factory)
418
+
419
+
420
+ def _asdict_inner(obj, dict_factory):
421
+ if is_dataclass(obj) and jstype(obj) is not 'function':
422
+ result = {}
423
+ dfc = obj.__class__.__dataclass_fields__
424
+ for name in v"Object.keys(dfc)":
425
+ result[name] = _asdict_inner(obj[name], dict_factory)
426
+ if dict_factory is not None:
427
+ pairs = [[k, v] for k, v in result.items()]
428
+ return dict_factory(pairs)
429
+ return result
430
+ elif isinstance(obj, list):
431
+ return [_asdict_inner(v, dict_factory) for v in obj]
432
+ elif isinstance(obj, tuple):
433
+ return tuple([_asdict_inner(v, dict_factory) for v in list(obj)])
434
+ elif isinstance(obj, dict):
435
+ result = {}
436
+ for k, v in obj.items():
437
+ result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
438
+ if dict_factory is not None:
439
+ pairs = [[ki, vi] for ki, vi in result.items()]
440
+ return dict_factory(pairs)
441
+ return result
442
+ else:
443
+ return obj
444
+
445
+
446
+ def astuple(obj, *, tuple_factory=tuple):
447
+ """Recursively convert a dataclass instance to a tuple of its field values."""
448
+ if not is_dataclass(obj) or jstype(obj) is 'function':
449
+ raise TypeError('astuple() should be called on dataclass instances')
450
+ return _astuple_inner(obj, tuple_factory)
451
+
452
+
453
+ def _astuple_inner(obj, tuple_factory):
454
+ if is_dataclass(obj) and jstype(obj) is not 'function':
455
+ result = []
456
+ dfc = obj.__class__.__dataclass_fields__
457
+ for name in v"Object.keys(dfc)":
458
+ result.push(_astuple_inner(obj[name], tuple_factory))
459
+ return tuple_factory(result)
460
+ elif isinstance(obj, list):
461
+ return [_astuple_inner(v, tuple_factory) for v in obj]
462
+ elif isinstance(obj, tuple):
463
+ return tuple_factory([_astuple_inner(v, tuple_factory) for v in list(obj)])
464
+ elif isinstance(obj, dict):
465
+ return {k: _astuple_inner(v, tuple_factory) for k, v in obj.items()}
466
+ else:
467
+ return obj
468
+
469
+
470
+
471
+
472
+
473
+ def replace(obj, **changes):
474
+ """Return a new dataclass instance with the specified fields replaced."""
475
+ if not is_dataclass(obj) or jstype(obj) is 'function':
476
+ raise TypeError('replace() should be called on dataclass instances')
477
+ cls = obj.__class__
478
+ dfc = cls.__dataclass_fields__
479
+ init_kwargs = {}
480
+ for name in v"Object.keys(dfc)":
481
+ f = dfc[name]
482
+ if not f.init:
483
+ continue
484
+ init_kwargs[name] = changes[name] if name in changes else obj[name]
485
+ v"""
486
+ var ρσ_kw = init_kwargs;
487
+ ρσ_kw[ρσ_kwargs_symbol] = true;
488
+ return new cls(ρσ_kw);
489
+ """
490
+
491
+
492
+ def make_dataclass(cls_name, fields_spec, *, bases=None,
493
+ init=True, repr=True, eq=True, order=False,
494
+ unsafe_hash=False, frozen=False):
495
+ """
496
+ Dynamically create a new dataclass from a list of field specifications.
497
+
498
+ Each item in fields_spec can be:
499
+ 'name' → required field (no default)
500
+ ('name', type) → required field with type hint (type ignored at runtime)
501
+ ('name', type, field()) → field with a field() descriptor
502
+ """
503
+ anns = {}
504
+ proto_vals = {}
505
+ for item in fields_spec:
506
+ if jstype(item) is 'string':
507
+ anns[item] = None
508
+ elif item.length >= 3:
509
+ anns[item[0]] = item[1]
510
+ proto_vals[item[0]] = item[2]
511
+ else:
512
+ anns[item[0]] = item[1] if item.length > 1 else None
513
+
514
+ v"""
515
+ var new_cls;
516
+ var base_list = bases || [];
517
+ new_cls = function() {
518
+ if (!(this instanceof new_cls)) return new new_cls(...arguments);
519
+ Object.defineProperty(this, 'ρσ_object_id', {value: ++ρσ_object_counter});
520
+ if (new_cls.prototype.__init__) new_cls.prototype.__init__.apply(this, arguments);
521
+ };
522
+ new_cls.__name__ = cls_name;
523
+ new_cls.__qualname__ = cls_name;
524
+ new_cls.__annotations__ = anns;
525
+ if (base_list.length > 0) ρσ_extends(new_cls, base_list[0]);
526
+ """
527
+
528
+ for k in v"Object.keys(proto_vals)":
529
+ new_cls.prototype[k] = proto_vals[k] # noqa: F821
530
+
531
+ _process_class(new_cls, init, repr, eq, order, unsafe_hash, frozen) # noqa: F821
532
+ return new_cls # noqa: F821