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.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +26 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/README.md +716 -169
- package/TODO.md +7 -2
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +36 -27
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +2358 -168
- package/release/baselib-plain-ugly.js +73 -3
- package/release/compiler.js +6283 -3093
- package/release/signatures.json +31 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +1 -0
- package/src/baselib-builtins.pyj +340 -2
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-errors.pyj +1 -1
- package/src/baselib-internal.pyj +267 -60
- package/src/baselib-itertools.pyj +110 -97
- package/src/baselib-str.pyj +22 -4
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/abc.pyj +317 -0
- package/src/lib/aes.pyj +646 -646
- package/src/lib/contextlib.pyj +379 -0
- package/src/lib/copy.pyj +120 -120
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/datetime.pyj +712 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/enum.pyj +125 -0
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/io.pyj +500 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/json.pyj +227 -0
- package/src/lib/math.pyj +193 -193
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/re.pyj +504 -470
- package/src/lib/react.pyj +74 -74
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/typing.pyj +577 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/builtins.js +14 -4
- package/src/monaco-language-service/diagnostics.js +19 -20
- package/src/monaco-language-service/dts.js +550 -550
- package/src/output/classes.pyj +62 -26
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +78 -5
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +5 -2
- package/src/output/operators.pyj +100 -34
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -16
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +10 -5
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/abc.pyj +291 -0
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/arithmetic_nostrict.pyj +88 -0
- package/test/arithmetic_types.pyj +169 -0
- package/test/baselib.pyj +91 -0
- package/test/bytes.pyj +467 -0
- package/test/classes.pyj +1 -0
- package/test/comparison_ops.pyj +173 -0
- package/test/contextlib.pyj +362 -0
- package/test/dataclasses.pyj +253 -0
- package/test/datetime.pyj +500 -0
- package/test/debugger_stmt.pyj +41 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/imports.pyj +72 -72
- package/test/internationalization.pyj +73 -73
- package/test/io.pyj +316 -0
- package/test/json.pyj +196 -0
- package/test/lint.pyj +164 -164
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/object.pyj +64 -0
- package/test/omit_function_metadata.pyj +20 -20
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +70 -15
- package/test/regexp.pyj +83 -55
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +116 -7
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service.js +84 -0
- package/test/unit/web-repl.js +1337 -1
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +558 -547
- package/tools/compile.js +224 -219
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +262 -251
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/lint.js +16 -19
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -196
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +252 -252
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +224 -224
- package/web-repl/sha1.js +25 -25
- package/PYTHON_DIFFERENCES_REPORT.md +0 -291
- 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
|