rapydscript-ns 0.9.0 → 0.9.2

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 (100) 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 +10 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +7 -6
  8. package/TODO.md +116 -1
  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 +9 -9
  15. package/language-service/language-service.d.ts +1 -1
  16. package/package.json +6 -2
  17. package/publish.py +37 -37
  18. package/release/compiler.js +246 -231
  19. package/release/signatures.json +23 -23
  20. package/session.vim +4 -4
  21. package/setup.cfg +2 -2
  22. package/src/compiler.pyj +36 -36
  23. package/src/errors.pyj +30 -30
  24. package/src/lib/aes.pyj +646 -646
  25. package/src/lib/contextlib.pyj +379 -0
  26. package/src/lib/copy.pyj +120 -120
  27. package/src/lib/datetime.pyj +712 -0
  28. package/src/lib/elementmaker.pyj +83 -83
  29. package/src/lib/encodings.pyj +126 -126
  30. package/src/lib/gettext.pyj +569 -569
  31. package/src/lib/io.pyj +500 -0
  32. package/src/lib/itertools.pyj +580 -580
  33. package/src/lib/json.pyj +227 -0
  34. package/src/lib/math.pyj +193 -193
  35. package/src/lib/operator.pyj +11 -11
  36. package/src/lib/pythonize.pyj +20 -20
  37. package/src/lib/random.pyj +118 -118
  38. package/src/lib/react.pyj +74 -74
  39. package/src/lib/traceback.pyj +63 -63
  40. package/src/lib/uuid.pyj +77 -77
  41. package/src/monaco-language-service/diagnostics.js +4 -4
  42. package/src/monaco-language-service/dts.js +550 -550
  43. package/src/monaco-language-service/index.js +2 -2
  44. package/src/output/comments.pyj +45 -45
  45. package/src/output/exceptions.pyj +201 -201
  46. package/src/output/jsx.pyj +164 -164
  47. package/src/output/loops.pyj +9 -0
  48. package/src/output/treeshake.pyj +182 -182
  49. package/src/output/utils.pyj +72 -72
  50. package/src/string_interpolation.pyj +72 -72
  51. package/src/tokenizer.pyj +1 -1
  52. package/src/unicode_aliases.pyj +576 -576
  53. package/src/utils.pyj +192 -192
  54. package/test/_import_one.pyj +37 -37
  55. package/test/_import_two/__init__.pyj +11 -11
  56. package/test/_import_two/level2/deep.pyj +4 -4
  57. package/test/_import_two/other.pyj +6 -6
  58. package/test/_import_two/sub.pyj +13 -13
  59. package/test/aes_vectors.pyj +421 -421
  60. package/test/annotations.pyj +80 -80
  61. package/test/contextlib.pyj +362 -0
  62. package/test/datetime.pyj +500 -0
  63. package/test/debugger_stmt.pyj +41 -0
  64. package/test/decorators.pyj +77 -77
  65. package/test/docstrings.pyj +39 -39
  66. package/test/elementmaker_test.pyj +45 -45
  67. package/test/functions.pyj +151 -151
  68. package/test/generators.pyj +41 -41
  69. package/test/generic.pyj +370 -370
  70. package/test/imports.pyj +72 -72
  71. package/test/internationalization.pyj +73 -73
  72. package/test/io.pyj +316 -0
  73. package/test/json.pyj +196 -0
  74. package/test/lint.pyj +164 -164
  75. package/test/loops.pyj +85 -85
  76. package/test/numpy.pyj +734 -734
  77. package/test/omit_function_metadata.pyj +20 -20
  78. package/test/repl.pyj +121 -121
  79. package/test/scoped_flags.pyj +76 -76
  80. package/test/unit/index.js +66 -0
  81. package/test/unit/language-service-dts.js +543 -543
  82. package/test/unit/language-service-hover.js +455 -455
  83. package/test/unit/language-service.js +1 -1
  84. package/test/unit/web-repl.js +533 -0
  85. package/tools/compiler.d.ts +367 -0
  86. package/tools/completer.js +131 -131
  87. package/tools/gettext.js +185 -185
  88. package/tools/ini.js +65 -65
  89. package/tools/msgfmt.js +187 -187
  90. package/tools/repl.js +223 -223
  91. package/tools/test.js +118 -118
  92. package/tools/utils.js +128 -128
  93. package/tools/web_repl.js +95 -95
  94. package/try +41 -41
  95. package/web-repl/env.js +196 -196
  96. package/web-repl/index.html +163 -163
  97. package/web-repl/prism.css +139 -139
  98. package/web-repl/prism.js +113 -113
  99. package/web-repl/rapydscript.js +224 -224
  100. package/web-repl/sha1.js +25 -25
@@ -0,0 +1,379 @@
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD
3
+ # RapydScript implementation of Python's contextlib standard library.
4
+ #
5
+ # Supported:
6
+ # AbstractContextManager — base class with default __enter__ (returns self)
7
+ # @contextmanager — turn a generator function into a context manager
8
+ # closing(thing) — calls thing.close() on exit
9
+ # nullcontext(val) — no-op context manager; __enter__ returns val
10
+ # suppress(*excs) — silently swallow specified exception types
11
+ # ExitStack — dynamic LIFO stack of context managers / callbacks
12
+ #
13
+ # Usage:
14
+ # from contextlib import contextmanager, suppress, closing, nullcontext, ExitStack
15
+ #
16
+ # @contextmanager
17
+ # def managed(name):
18
+ # print('enter', name)
19
+ # try:
20
+ # yield name.upper()
21
+ # finally:
22
+ # print('exit', name)
23
+ #
24
+ # with managed('hello') as val:
25
+ # print(val) # HELLO
26
+ # # prints: enter hello / HELLO / exit hello
27
+ #
28
+ # with suppress(ValueError):
29
+ # int('not-a-number')
30
+ # # no exception raised
31
+ #
32
+ # stack = ExitStack()
33
+ # with stack:
34
+ # f = stack.enter_context(some_cm())
35
+ #
36
+ # Implementation notes:
37
+ # - contextmanager uses the JS generator protocol (.next() / .throw()).
38
+ # Since 'throw' is a reserved JS keyword it cannot appear as a bare
39
+ # identifier in RapydScript method-call syntax; the call is made via a
40
+ # verbatim v"""...""" block.
41
+ # - The with-statement machinery in RapydScript calls __exit__() (0 args)
42
+ # when no exception occurred and __exit__(exc_type, exc_val, exc_tb)
43
+ # (3 args) when one did. All __exit__ implementations here use default
44
+ # parameters (=None) so both signatures work transparently.
45
+ # - ExitStack.__exit__ may raise a new exception that replaces the original;
46
+ # the with-statement machinery propagates whatever __exit__ throws.
47
+
48
+
49
+ # RuntimeError is not defined in the RapydScript baselib; define it here so
50
+ # that the contextmanager helpers can raise it for protocol violations.
51
+ class RuntimeError(Exception):
52
+ pass
53
+
54
+
55
+ class AbstractContextManager:
56
+ """
57
+ ABC for context managers that provides a default __enter__ returning self.
58
+
59
+ Subclasses must override __exit__. A default no-op __exit__ is also
60
+ provided so that plain subclasses work without override if no cleanup is
61
+ needed.
62
+
63
+ Example::
64
+
65
+ class Timed(AbstractContextManager):
66
+ def __enter__(self):
67
+ self._start = Date.now()
68
+ return self
69
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
70
+ print('elapsed:', Date.now() - self._start, 'ms')
71
+ return False
72
+ """
73
+
74
+ def __enter__(self):
75
+ return self
76
+
77
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
78
+ return None
79
+
80
+
81
+ class closing:
82
+ """
83
+ Context manager that calls thing.close() when the block exits.
84
+
85
+ Equivalent to Python's contextlib.closing.
86
+
87
+ Example::
88
+
89
+ from contextlib import closing
90
+
91
+ with closing(open_connection()) as conn:
92
+ conn.send(data)
93
+ # conn.close() is guaranteed to be called
94
+ """
95
+
96
+ def __init__(self, thing):
97
+ self.thing = thing
98
+
99
+ def __enter__(self):
100
+ return self.thing
101
+
102
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
103
+ self.thing.close()
104
+ return False
105
+
106
+
107
+ class nullcontext:
108
+ """
109
+ No-op context manager. __enter__ returns enter_result (default None).
110
+
111
+ Useful as a conditional stand-in when you optionally want a real context
112
+ manager::
113
+
114
+ cm = open_lock() if need_lock else nullcontext()
115
+ with cm:
116
+ do_work()
117
+
118
+ Also handy for parameterised tests::
119
+
120
+ with nullcontext(42) as val:
121
+ assert val == 42
122
+ """
123
+
124
+ def __init__(self, enter_result=None):
125
+ self.enter_result = enter_result
126
+
127
+ def __enter__(self):
128
+ return self.enter_result
129
+
130
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
131
+ return None
132
+
133
+
134
+ class suppress:
135
+ """
136
+ Context manager that silently suppresses the listed exception types.
137
+
138
+ If a listed exception is raised inside the block the block is exited
139
+ and execution continues after the with statement. Any other exception
140
+ type propagates normally.
141
+
142
+ Example::
143
+
144
+ from contextlib import suppress
145
+
146
+ with suppress(KeyError, ValueError):
147
+ d = {}
148
+ _ = d['missing'] # KeyError suppressed
149
+ # execution continues here
150
+ """
151
+
152
+ def __init__(self, *exceptions):
153
+ self._exceptions = exceptions
154
+
155
+ def __enter__(self):
156
+ return self
157
+
158
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
159
+ if exc_type is None:
160
+ return False
161
+ for exc_class in self._exceptions:
162
+ if isinstance(exc_val, exc_class):
163
+ return True
164
+ return False
165
+
166
+
167
+ def contextmanager(func):
168
+ """
169
+ Decorator that turns a generator function into a context manager.
170
+
171
+ The decorated function must contain exactly one ``yield`` expression.
172
+ Everything before the yield is the __enter__ body; everything after is
173
+ the __exit__ body. The yielded value is bound to the ``as`` target.
174
+
175
+ Exception handling works as in Python: if an exception is raised inside
176
+ the with block it is thrown into the generator at the yield point. The
177
+ generator may catch and suppress it (by not re-raising) or let it
178
+ propagate.
179
+
180
+ Example::
181
+
182
+ from contextlib import contextmanager
183
+
184
+ @contextmanager
185
+ def temp_value(d, key, val):
186
+ old = d.get(key)
187
+ d[key] = val
188
+ try:
189
+ yield val
190
+ finally:
191
+ if old is None:
192
+ del d[key]
193
+ else:
194
+ d[key] = old
195
+
196
+ d = {}
197
+ with temp_value(d, 'x', 99) as v:
198
+ assert v == 99
199
+ assert d['x'] == 99
200
+ assert 'x' not in d
201
+ """
202
+
203
+ def _make_cm(*args, **kwargs):
204
+ gen = func(*args, **kwargs)
205
+
206
+ class _GeneratorContextManager:
207
+
208
+ def __enter__(self):
209
+ r = gen.next()
210
+ if r.done:
211
+ raise RuntimeError('generator did not yield')
212
+ return r.value
213
+
214
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
215
+ if exc_type is None:
216
+ # No-exception path: advance the generator to completion.
217
+ r = gen.next()
218
+ if not r.done:
219
+ raise RuntimeError('generator did not stop after yield')
220
+ return False
221
+ # Exception path: throw exc_val into the generator.
222
+ # 'throw' is a reserved JS keyword so we use verbatim JS.
223
+ v"""
224
+ var ρσ_cm_result;
225
+ try {
226
+ ρσ_cm_result = gen.throw(exc_val);
227
+ } catch (ρσ_cm_err) {
228
+ // Generator did not catch the exception (re-raised or threw a
229
+ // different one). If it's the same object, return false so
230
+ // the with-machinery re-raises it. Otherwise let the new
231
+ // exception propagate from __exit__.
232
+ if (ρσ_cm_err === exc_val) {
233
+ return false;
234
+ }
235
+ throw ρσ_cm_err;
236
+ }
237
+ // Generator caught the exception and returned normally.
238
+ if (!ρσ_cm_result.done) {
239
+ throw new RuntimeError('generator did not stop after throw()');
240
+ }
241
+ return true;
242
+ """
243
+
244
+ return _GeneratorContextManager()
245
+
246
+ return _make_cm
247
+
248
+
249
+ class ExitStack:
250
+ """
251
+ Context manager for dynamic stacks of context managers and callbacks.
252
+
253
+ Maintains a LIFO stack of registered cleanup actions. All registered
254
+ actions are guaranteed to run on exit, even if earlier ones raise.
255
+
256
+ Key methods:
257
+ enter_context(cm) — enter *cm* and push its __exit__ for cleanup
258
+ push(exit_cb) — push a raw (exc_type, exc_val, exc_tb) callback
259
+ or a context manager (its __exit__ is pushed)
260
+ callback(func, *a, **kw) — push func(*a, **kw) as a no-exc callback
261
+ close() — trigger exit immediately (convenience)
262
+
263
+ Example::
264
+
265
+ from contextlib import ExitStack
266
+
267
+ with ExitStack() as stack:
268
+ files = [stack.enter_context(open_file(name)) for name in names]
269
+ # all files closed on exit, even if some raises
270
+
271
+ # Manually-built stack:
272
+ stack = ExitStack()
273
+ conn = stack.enter_context(get_connection())
274
+ stack.callback(log, 'connection cleaned up')
275
+ try:
276
+ use(conn)
277
+ finally:
278
+ stack.close()
279
+ """
280
+
281
+ def __init__(self):
282
+ self._exit_callbacks = []
283
+
284
+ def __enter__(self):
285
+ return self
286
+
287
+ def push(self, exit_or_cm):
288
+ """
289
+ Register an exit callback or a context manager.
290
+
291
+ If *exit_or_cm* has an ``__exit__`` attribute it is treated as a
292
+ context manager (its __exit__ is registered but __enter__ is NOT
293
+ called — use enter_context() if you need that). Otherwise it must be
294
+ a callable with the ``(exc_type, exc_val, exc_tb)`` signature.
295
+
296
+ Returns *exit_or_cm* unchanged, so it can be used as a decorator.
297
+ """
298
+ if hasattr(exit_or_cm, '__exit__'):
299
+ _cm = exit_or_cm
300
+ def _cm_exit(exc_type=None, exc_val=None, exc_tb=None):
301
+ return _cm.__exit__(exc_type, exc_val, exc_tb)
302
+ self._exit_callbacks.append(_cm_exit)
303
+ else:
304
+ self._exit_callbacks.append(exit_or_cm)
305
+ return exit_or_cm
306
+
307
+ def enter_context(self, cm):
308
+ """
309
+ Enter a context manager and push its __exit__ for cleanup.
310
+
311
+ Returns the value returned by cm.__enter__().
312
+ """
313
+ result = cm.__enter__()
314
+ _cm2 = cm
315
+ def _cm2_exit(exc_type=None, exc_val=None, exc_tb=None):
316
+ return _cm2.__exit__(exc_type, exc_val, exc_tb)
317
+ self._exit_callbacks.append(_cm2_exit)
318
+ return result
319
+
320
+ def callback(self, func, *args, **kwargs):
321
+ """
322
+ Register a plain callback to run on exit, ignoring exception info.
323
+
324
+ The callback is called as func(*args, **kwargs) and its return value
325
+ is ignored. Returns *func* unchanged.
326
+ """
327
+ stored_args = args
328
+ stored_kwargs = kwargs
329
+ def _plain_cb(exc_type=None, exc_val=None, exc_tb=None):
330
+ func(*stored_args, **stored_kwargs)
331
+ return False
332
+ self._exit_callbacks.append(_plain_cb)
333
+ return func
334
+
335
+ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
336
+ """
337
+ Run all registered cleanups in LIFO order.
338
+
339
+ Returns True if an active exception was suppressed by any cleanup.
340
+ If a cleanup raises, that exception becomes the active exception for
341
+ subsequent cleanups; if it is still active at the end it propagates
342
+ from __exit__ (replacing the original).
343
+ """
344
+ received_exc = exc_type is not None
345
+ suppressed = False
346
+
347
+ current_type = exc_type
348
+ current_val = exc_val
349
+ current_tb = exc_tb
350
+
351
+ new_exc = None
352
+ raised_new = False
353
+
354
+ while len(self._exit_callbacks) > 0:
355
+ cb = self._exit_callbacks.pop()
356
+ try:
357
+ result = cb(current_type, current_val, current_tb)
358
+ if result:
359
+ suppressed = True
360
+ current_type = None
361
+ current_val = None
362
+ current_tb = None
363
+ except Exception as e:
364
+ # Cleanup raised — this becomes the active exception.
365
+ new_exc = e
366
+ raised_new = True
367
+ current_type = e.__class__
368
+ current_val = e
369
+ current_tb = None
370
+ suppressed = False
371
+
372
+ if raised_new:
373
+ raise new_exc
374
+
375
+ return received_exc and suppressed
376
+
377
+ def close(self):
378
+ """Exit the stack without an active exception."""
379
+ self.__exit__(None, None, None)
package/src/lib/copy.pyj CHANGED
@@ -1,120 +1,120 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD
3
- # RapydScript implementation of Python's copy standard library.
4
- #
5
- # Supported: copy, deepcopy
6
- # Classes may define __copy__() and __deepcopy__(memo) for custom behaviour.
7
-
8
-
9
- def _is_primitive(x):
10
- t = jstype(x)
11
- return x is None or t is 'number' or t is 'boolean' or t is 'string' or t is 'undefined'
12
-
13
-
14
- def copy(x):
15
- """Return a shallow copy of x.
16
-
17
- For immutable primitives (numbers, strings, booleans, None) the object
18
- itself is returned unchanged. For containers a new container of the same
19
- type is created whose top-level items are the same objects as in the
20
- original.
21
-
22
- Dispatch order:
23
- 1. Primitive → return as-is.
24
- 2. ``__copy__`` method → call and return.
25
- 3. list → ``list(x)`` (slice).
26
- 4. set → ``set(x)``.
27
- 5. frozenset → ``frozenset(x)``.
28
- 6. dict (ρσ_dict) → ``x.copy()``.
29
- 7. Plain JS object (constructor is Object or null-proto) → Object.assign.
30
- 8. Other object (class instance) → Object.create + Object.assign.
31
- """
32
- if _is_primitive(x):
33
- return x
34
- if jstype(x.__copy__) is 'function':
35
- return x.__copy__()
36
- if Array.isArray(x):
37
- return list(x)
38
- if isinstance(x, set):
39
- return set(x)
40
- if isinstance(x, frozenset):
41
- return frozenset(x)
42
- if isinstance(x, dict):
43
- return x.copy()
44
- proto = Object.getPrototypeOf(x)
45
- if x.constructor is Object or proto is None:
46
- return Object.assign({}, x)
47
- # Class instance: create a same-prototype object and copy own properties.
48
- result = Object.create(proto)
49
- Object.assign(result, x)
50
- return result
51
-
52
-
53
- def deepcopy(x, memo=None):
54
- """Return a deep (recursive) copy of x.
55
-
56
- Circular references are handled via the *memo* mapping (a JS Map), which
57
- stores already-copied objects so that they are only copied once.
58
-
59
- Dispatch order (same structure as ``copy`` but recursive):
60
- 1. Primitive → return as-is.
61
- 2. Memo hit → return the previously copied object.
62
- 3. ``__deepcopy__(memo)`` method → call and return.
63
- 4. list → recurse into elements.
64
- 5. set → recurse into elements, build new set.
65
- 6. frozenset → recurse into elements, build new frozenset.
66
- 7. dict → recurse into keys and values.
67
- 8. Plain JS object → recurse into own-enumerable properties.
68
- 9. Class instance → recurse into own-enumerable properties.
69
- """
70
- if memo is None:
71
- memo = v'new Map()'
72
- if _is_primitive(x):
73
- return x
74
- if memo.has(x):
75
- return memo.get(x)
76
- if jstype(x.__deepcopy__) is 'function':
77
- result = x.__deepcopy__(memo)
78
- memo.set(x, result)
79
- return result
80
- if Array.isArray(x):
81
- result = []
82
- memo.set(x, result)
83
- for i in range(x.length):
84
- result.push(deepcopy(x[i], memo))
85
- return result
86
- if isinstance(x, set):
87
- result = set()
88
- memo.set(x, result)
89
- iterator = x[ρσ_iterator_symbol]()
90
- r = iterator.next()
91
- while not r.done:
92
- result.add(deepcopy(r.value, memo))
93
- r = iterator.next()
94
- return result
95
- if isinstance(x, frozenset):
96
- items = []
97
- iterator = x[ρσ_iterator_symbol]()
98
- r = iterator.next()
99
- while not r.done:
100
- items.push(deepcopy(r.value, memo))
101
- r = iterator.next()
102
- result = frozenset(items)
103
- memo.set(x, result)
104
- return result
105
- if isinstance(x, dict):
106
- result = dict()
107
- memo.set(x, result)
108
- iterator = x.items()
109
- r = iterator.next()
110
- while not r.done:
111
- result.set(deepcopy(r.value[0], memo), deepcopy(r.value[1], memo))
112
- r = iterator.next()
113
- return result
114
- proto = Object.getPrototypeOf(x)
115
- result = Object.create(proto)
116
- memo.set(x, result)
117
- keys = Object.keys(x)
118
- for i in range(keys.length):
119
- result[keys[i]] = deepcopy(x[keys[i]], memo)
120
- return result
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD
3
+ # RapydScript implementation of Python's copy standard library.
4
+ #
5
+ # Supported: copy, deepcopy
6
+ # Classes may define __copy__() and __deepcopy__(memo) for custom behaviour.
7
+
8
+
9
+ def _is_primitive(x):
10
+ t = jstype(x)
11
+ return x is None or t is 'number' or t is 'boolean' or t is 'string' or t is 'undefined'
12
+
13
+
14
+ def copy(x):
15
+ """Return a shallow copy of x.
16
+
17
+ For immutable primitives (numbers, strings, booleans, None) the object
18
+ itself is returned unchanged. For containers a new container of the same
19
+ type is created whose top-level items are the same objects as in the
20
+ original.
21
+
22
+ Dispatch order:
23
+ 1. Primitive → return as-is.
24
+ 2. ``__copy__`` method → call and return.
25
+ 3. list → ``list(x)`` (slice).
26
+ 4. set → ``set(x)``.
27
+ 5. frozenset → ``frozenset(x)``.
28
+ 6. dict (ρσ_dict) → ``x.copy()``.
29
+ 7. Plain JS object (constructor is Object or null-proto) → Object.assign.
30
+ 8. Other object (class instance) → Object.create + Object.assign.
31
+ """
32
+ if _is_primitive(x):
33
+ return x
34
+ if jstype(x.__copy__) is 'function':
35
+ return x.__copy__()
36
+ if Array.isArray(x):
37
+ return list(x)
38
+ if isinstance(x, set):
39
+ return set(x)
40
+ if isinstance(x, frozenset):
41
+ return frozenset(x)
42
+ if isinstance(x, dict):
43
+ return x.copy()
44
+ proto = Object.getPrototypeOf(x)
45
+ if x.constructor is Object or proto is None:
46
+ return Object.assign({}, x)
47
+ # Class instance: create a same-prototype object and copy own properties.
48
+ result = Object.create(proto)
49
+ Object.assign(result, x)
50
+ return result
51
+
52
+
53
+ def deepcopy(x, memo=None):
54
+ """Return a deep (recursive) copy of x.
55
+
56
+ Circular references are handled via the *memo* mapping (a JS Map), which
57
+ stores already-copied objects so that they are only copied once.
58
+
59
+ Dispatch order (same structure as ``copy`` but recursive):
60
+ 1. Primitive → return as-is.
61
+ 2. Memo hit → return the previously copied object.
62
+ 3. ``__deepcopy__(memo)`` method → call and return.
63
+ 4. list → recurse into elements.
64
+ 5. set → recurse into elements, build new set.
65
+ 6. frozenset → recurse into elements, build new frozenset.
66
+ 7. dict → recurse into keys and values.
67
+ 8. Plain JS object → recurse into own-enumerable properties.
68
+ 9. Class instance → recurse into own-enumerable properties.
69
+ """
70
+ if memo is None:
71
+ memo = v'new Map()'
72
+ if _is_primitive(x):
73
+ return x
74
+ if memo.has(x):
75
+ return memo.get(x)
76
+ if jstype(x.__deepcopy__) is 'function':
77
+ result = x.__deepcopy__(memo)
78
+ memo.set(x, result)
79
+ return result
80
+ if Array.isArray(x):
81
+ result = []
82
+ memo.set(x, result)
83
+ for i in range(x.length):
84
+ result.push(deepcopy(x[i], memo))
85
+ return result
86
+ if isinstance(x, set):
87
+ result = set()
88
+ memo.set(x, result)
89
+ iterator = x[ρσ_iterator_symbol]()
90
+ r = iterator.next()
91
+ while not r.done:
92
+ result.add(deepcopy(r.value, memo))
93
+ r = iterator.next()
94
+ return result
95
+ if isinstance(x, frozenset):
96
+ items = []
97
+ iterator = x[ρσ_iterator_symbol]()
98
+ r = iterator.next()
99
+ while not r.done:
100
+ items.push(deepcopy(r.value, memo))
101
+ r = iterator.next()
102
+ result = frozenset(items)
103
+ memo.set(x, result)
104
+ return result
105
+ if isinstance(x, dict):
106
+ result = dict()
107
+ memo.set(x, result)
108
+ iterator = x.items()
109
+ r = iterator.next()
110
+ while not r.done:
111
+ result.set(deepcopy(r.value[0], memo), deepcopy(r.value[1], memo))
112
+ r = iterator.next()
113
+ return result
114
+ proto = Object.getPrototypeOf(x)
115
+ result = Object.create(proto)
116
+ memo.set(x, result)
117
+ keys = Object.keys(x)
118
+ for i in range(keys.length):
119
+ result[keys[i]] = deepcopy(x[keys[i]], memo)
120
+ return result