rapydscript-ns 0.8.1 → 0.8.3

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 (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTORS +3 -2
  3. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  4. package/PYTHON_FEATURE_COVERAGE.md +200 -0
  5. package/README.md +480 -79
  6. package/TODO.md +6 -318
  7. package/hack_demo.pyj +112 -0
  8. package/language-service/index.js +4474 -0
  9. package/language-service/language-service.d.ts +40 -0
  10. package/package.json +9 -10
  11. package/src/ast.pyj +30 -6
  12. package/src/baselib-builtins.pyj +181 -11
  13. package/src/baselib-containers.pyj +154 -5
  14. package/src/baselib-errors.pyj +3 -0
  15. package/src/baselib-internal.pyj +40 -1
  16. package/src/baselib-str.pyj +42 -1
  17. package/src/lib/collections.pyj +1 -1
  18. package/src/lib/numpy.pyj +10 -10
  19. package/src/monaco-language-service/analyzer.js +132 -22
  20. package/src/monaco-language-service/builtins.js +22 -2
  21. package/src/monaco-language-service/completions.js +224 -3
  22. package/src/monaco-language-service/diagnostics.js +55 -5
  23. package/src/monaco-language-service/index.js +26 -5
  24. package/src/monaco-language-service/scope.js +3 -0
  25. package/src/output/classes.pyj +20 -3
  26. package/src/output/codegen.pyj +38 -3
  27. package/src/output/functions.pyj +35 -25
  28. package/src/output/loops.pyj +64 -11
  29. package/src/output/modules.pyj +1 -4
  30. package/src/output/operators.pyj +67 -1
  31. package/src/output/statements.pyj +7 -3
  32. package/src/output/stream.pyj +6 -13
  33. package/src/parse.pyj +94 -14
  34. package/src/tokenizer.pyj +1 -0
  35. package/test/baselib.pyj +4 -4
  36. package/test/classes.pyj +56 -17
  37. package/test/collections.pyj +5 -5
  38. package/test/python_compat.pyj +326 -0
  39. package/test/python_features.pyj +1271 -0
  40. package/test/slice.pyj +105 -0
  41. package/test/str.pyj +25 -0
  42. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  43. package/test/unit/index.js +119 -7
  44. package/test/unit/language-service-builtins.js +70 -0
  45. package/test/unit/language-service-bundle.js +83 -0
  46. package/test/unit/language-service-completions.js +289 -0
  47. package/test/unit/language-service-index.js +350 -0
  48. package/test/unit/language-service-scope.js +255 -0
  49. package/test/unit/language-service.js +158 -1
  50. package/test/unit/run-language-service.js +2 -0
  51. package/test/unit/web-repl.js +134 -0
  52. package/tools/build-language-service.js +2 -2
  53. package/tools/compiler.js +0 -24
  54. package/tools/export.js +3 -37
  55. package/tools/lint.js +1 -1
  56. package/tools/self.js +1 -9
  57. package/web-repl/rapydscript.js +6 -40
  58. package/web-repl/language-service.js +0 -4084
@@ -0,0 +1,40 @@
1
+ // language-service.d.ts — Ambient type declarations for language-service.js
2
+ // The actual implementation lives in language-service.js (bundled, do not edit directly).
3
+
4
+ export interface WebReplInstance {
5
+ in_block_mode: boolean;
6
+ compile(code: string, opts?: Record<string, unknown>): string;
7
+ compile_mapped(code: string, opts?: Record<string, unknown>): { code: string; sourceMap: string };
8
+ runjs(code: string): unknown;
9
+ replace_print(writeLine: (...args: unknown[]) => void): void;
10
+ is_input_complete(source: string): boolean;
11
+ init_completions(completelib: unknown): void;
12
+ find_completions(line: string): unknown;
13
+ }
14
+
15
+ export interface RapydScriptOptions {
16
+ compiler?: unknown;
17
+ virtualFiles?: Record<string, string>;
18
+ stdlibFiles?: Record<string, string>;
19
+ dtsFiles?: Array<{ name: string; content: string }>;
20
+ parseDelay?: number;
21
+ extraBuiltins?: Record<string, true>;
22
+ pythonFlags?: string;
23
+ pythonize_strings?: boolean;
24
+ loadDts?: (name: string) => string | Promise<string>;
25
+ }
26
+
27
+ export declare class RapydScriptLanguageService {
28
+ constructor(monaco: unknown, options: RapydScriptOptions);
29
+
30
+ setVirtualFiles(files: Record<string, string>): void;
31
+ removeVirtualFile(name: string): void;
32
+ addGlobals(names: string[]): void;
33
+ getScopeMap(model: unknown): unknown | null;
34
+ addDts(name: string, dtsText: string): void;
35
+ loadDts(name: string): Promise<void>;
36
+ dispose(): void;
37
+ }
38
+
39
+ export declare function registerRapydScript(monaco: unknown, options?: RapydScriptOptions): RapydScriptLanguageService;
40
+ export declare function web_repl(): WebReplInstance;
package/package.json CHANGED
@@ -2,6 +2,11 @@
2
2
  "name": "rapydscript-ns",
3
3
  "description": "Pythonic JavaScript that doesn't suck",
4
4
  "homepage": "https://github.com/ficocelliguy/rapydscript-ns",
5
+ "version": "0.8.3",
6
+ "license": "BSD-2-Clause",
7
+ "engines": {
8
+ "node": ">=0.12.0"
9
+ },
5
10
  "keywords": [
6
11
  "javascript",
7
12
  "rapydscript",
@@ -11,7 +16,10 @@
11
16
  "main": "tools/compiler.js",
12
17
  "exports": {
13
18
  ".": "./tools/compiler.js",
14
- "./language-service": "./web-repl/language-service.js"
19
+ "./language-service": {
20
+ "types": "./language-service/language-service.d.ts",
21
+ "default": "./language-service/index.js"
22
+ }
15
23
  },
16
24
  "scripts": {
17
25
  "test": "node bin/rapydscript self --complete --test && npm run test:unit && npm run test:ls",
@@ -23,11 +31,6 @@
23
31
  "build": "rm -rf **/*pyj-cached dev && node bin/rapydscript self --complete && node bin/web-repl-export web-repl && node tools/build-language-service.js",
24
32
  "prepublishOnly": "node tools/build-language-service.js"
25
33
  },
26
- "version": "0.8.1",
27
- "license": "BSD-2-Clause",
28
- "engines": {
29
- "node": ">=0.12.0"
30
- },
31
34
  "maintainers": [
32
35
  {
33
36
  "name": "Michael Ficocelli",
@@ -39,12 +42,8 @@
39
42
  "url": "https://github.com/ficocelliguy/rapydscript-ns.git"
40
43
  },
41
44
  "dependencies": {
42
- "regenerator": ">= 0.12.1",
43
45
  "uglify-js": ">= 3.0.15"
44
46
  },
45
- "optionalDependencies": {
46
- "v8-profiler": ">= 5.2.9"
47
- },
48
47
  "bin": {
49
48
  "rapydscript": "bin/rapydscript"
50
49
  }
package/src/ast.pyj CHANGED
@@ -159,6 +159,7 @@ class AST_Assert(AST_Statement):
159
159
  properties = {
160
160
  'condition': "[AST_Node] the expression that should be tested",
161
161
  'message': "[AST_Node*] the expression that is the error message or None",
162
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
162
163
  }
163
164
 
164
165
  def _walk(self, visitor):
@@ -213,7 +214,8 @@ class AST_StatementWithBody(AST_Statement):
213
214
  class AST_DWLoop(AST_StatementWithBody):
214
215
  "Base class for do/while statements"
215
216
  properties = {
216
- 'condition': "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
217
+ 'condition': "[AST_Node] the loop condition. Should not be instanceof AST_Statement",
218
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
217
219
  }
218
220
 
219
221
  def _walk(self, visitor):
@@ -227,13 +229,25 @@ class AST_Do(AST_DWLoop):
227
229
 
228
230
  class AST_While(AST_DWLoop):
229
231
  "A `while` statement"
232
+ properties = {
233
+ 'belse': "[AST_Else?] the `else` clause, run when loop exits without `break`"
234
+ }
235
+
236
+ def _walk(self, visitor):
237
+ return visitor._visit(self, def():
238
+ self.condition._walk(visitor)
239
+ self.body._walk(visitor)
240
+ if self.belse:
241
+ self.belse._walk(visitor)
242
+ )
230
243
 
231
244
  class AST_ForIn(AST_StatementWithBody):
232
245
  "A `for ... in` statement"
233
246
  properties = {
234
247
  'init': "[AST_Node] the `for/in` initialization code",
235
248
  'name': "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
236
- 'object': "[AST_Node] the object that we're looping through"
249
+ 'object': "[AST_Node] the object that we're looping through",
250
+ 'belse': "[AST_Else?] the `else` clause, run when no break occurred"
237
251
  }
238
252
 
239
253
  def _walk(self, visitor):
@@ -243,6 +257,8 @@ class AST_ForIn(AST_StatementWithBody):
243
257
  self.object._walk(visitor)
244
258
  if self.body:
245
259
  self.body._walk(visitor)
260
+ if self.belse:
261
+ self.belse._walk(visitor)
246
262
  )
247
263
 
248
264
  class AST_ForJS(AST_StatementWithBody):
@@ -647,6 +663,9 @@ class AST_Await(AST_Node):
647
663
 
648
664
  class AST_Throw(AST_Exit):
649
665
  "A `throw` statement"
666
+ properties = {
667
+ 'cause': "[AST_Node?] the __cause__ of the exception (from `raise X from Y`); may be None"
668
+ }
650
669
 
651
670
  class AST_LoopControl(AST_Jump):
652
671
  "Base class for loop control statements (`break` and `continue`)"
@@ -663,7 +682,8 @@ class AST_If(AST_StatementWithBody):
663
682
  "A `if` statement"
664
683
  properties = {
665
684
  'condition': "[AST_Node] the `if` condition",
666
- 'alternative': "[AST_Statement?] the `else` part, or null if not present"
685
+ 'alternative': "[AST_Statement?] the `else` part, or null if not present",
686
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
667
687
  }
668
688
 
669
689
  def _walk(self, visitor):
@@ -767,7 +787,8 @@ class AST_BaseCall(AST_Node):
767
787
  class AST_Call(AST_BaseCall):
768
788
  "A function call expression"
769
789
  properties = {
770
- 'expression': "[AST_Node] expression to invoke as function"
790
+ 'expression': "[AST_Node] expression to invoke as function",
791
+ 'python_truthiness': "[bool] Whether to use Python truthiness for __call__ dispatch"
771
792
  }
772
793
 
773
794
  def _walk(self, visitor):
@@ -941,7 +962,8 @@ class AST_Unary(AST_Node):
941
962
  'operator': "[string] the operator",
942
963
  'expression': "[AST_Node] expression that this unary operator applies to",
943
964
  'parenthesized': "[bool] Whether this unary expression was parenthesized",
944
- 'overloaded': "[bool] Whether to use Python-style operator overloading dispatch"
965
+ 'overloaded': "[bool] Whether to use Python-style operator overloading dispatch",
966
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
945
967
  }
946
968
 
947
969
  def _walk(self, visitor):
@@ -958,7 +980,8 @@ class AST_Binary(AST_Node):
958
980
  'left': "[AST_Node] left-hand side expression",
959
981
  'operator': "[string] the operator",
960
982
  'right': "[AST_Node] right-hand side expression",
961
- 'overloaded': "[bool] Whether to use Python-style operator overloading dispatch"
983
+ 'overloaded': "[bool] Whether to use Python-style operator overloading dispatch",
984
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
962
985
  }
963
986
 
964
987
  def _walk(self, visitor):
@@ -987,6 +1010,7 @@ class AST_Conditional(AST_Node):
987
1010
  'condition': "[AST_Node]",
988
1011
  'consequent': "[AST_Node]",
989
1012
  'alternative': "[AST_Node]",
1013
+ 'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
990
1014
  }
991
1015
 
992
1016
  def _walk(self, visitor):
@@ -5,7 +5,21 @@
5
5
  # globals: exports, console, ρσ_iterator_symbol, ρσ_kwargs_symbol, ρσ_arraylike, ρσ_list_contains
6
6
 
7
7
  def ρσ_bool(val):
8
- return v'!!val'
8
+ # Python truthiness — written entirely with verbatim JS so that the
9
+ # compiled ρσ_bool function never calls itself (no bootstrap recursion).
10
+ v'if (val === null || val === undefined) return false'
11
+ v'var ρσ_bool_t = typeof val'
12
+ v'if (ρσ_bool_t === "boolean") return val'
13
+ v'if (ρσ_bool_t === "number") return val !== 0'
14
+ v'if (ρσ_bool_t === "string") return val.length > 0'
15
+ v'if (ρσ_bool_t === "function") return true'
16
+ v'if (val.constructor && val.constructor.prototype === val) return true'
17
+ v'if (typeof val.__bool__ === "function") return !!val.__bool__()'
18
+ v'if (Array.isArray(val)) return val.length > 0'
19
+ v'if (typeof val.__len__ === "function") return val.__len__() > 0'
20
+ v'if ((typeof Set === "function" && val instanceof Set) || (typeof Map === "function" && val instanceof Map)) return val.size > 0'
21
+ v'if (!val.constructor || val.constructor === Object) return Object.keys(val).length > 0'
22
+ return True
9
23
 
10
24
  def ρσ_print(*args, **kwargs):
11
25
  if v'typeof console' is 'object':
@@ -73,7 +87,24 @@ def ρσ_chr(code):
73
87
  return String.fromCharCode(0xD800+(code>>10), 0xDC00+(code&0x3FF))
74
88
 
75
89
  def ρσ_callable(x):
76
- return v'typeof x === "function"'
90
+ return v'typeof x === "function" || (x !== null && x !== undefined && typeof x.__call__ === "function")'
91
+
92
+ def ρσ_callable_call(fn):
93
+ # Dispatch fn(args): check __call__ first (callable objects), then direct call.
94
+ # __call__ is checked first so callable objects (not functions) are dispatched correctly.
95
+ args = v'Array.prototype.slice.call(arguments, 1)'
96
+ if v'fn !== null && fn !== undefined && typeof fn.__call__ === "function"':
97
+ return v'fn.__call__.apply(fn, args)'
98
+ if v'typeof fn === "function"':
99
+ return v'fn.apply(undefined, args)'
100
+ raise TypeError('object is not callable')
101
+
102
+
103
+ def ρσ_round(x, ndigits):
104
+ if ndigits is undefined or ndigits is 0:
105
+ return Math.round(x)
106
+ factor = Math.pow(10, ndigits)
107
+ return Math.round(x * factor) / factor
77
108
 
78
109
  def ρσ_bin(x):
79
110
  if jstype(x) is not 'number' or x % 1 is not 0:
@@ -95,15 +126,17 @@ def ρσ_hex(x):
95
126
  ans = '0x' + ans
96
127
  return ans
97
128
 
98
- def ρσ_enumerate(iterable):
99
- ans = v'{"_i":-1}'
129
+ def ρσ_enumerate(iterable, start):
130
+ offset = 0 if start is undefined else start
131
+ ans = {'_i': offset - 1}
100
132
  ans[ρσ_iterator_symbol] = def():
101
133
  return this
102
134
  if ρσ_arraylike(iterable):
103
135
  ans['next'] = def():
104
136
  this._i += 1
105
- if this._i < iterable.length:
106
- return v"{'done':false, 'value':[this._i, iterable[this._i]]}"
137
+ idx = this._i - offset
138
+ if idx < iterable.length:
139
+ return {'done': False, 'value': [this._i, iterable[idx]]}
107
140
  return v"{'done':true}"
108
141
  return ans
109
142
  if jstype(iterable[ρσ_iterator_symbol]) is 'function':
@@ -114,9 +147,9 @@ def ρσ_enumerate(iterable):
114
147
  if r.done:
115
148
  return v"{'done':true}"
116
149
  this._i += 1
117
- return v"{'done':false, 'value':[this._i, r.value]}"
150
+ return {'done': False, 'value': [this._i, r.value]}
118
151
  return ans
119
- return ρσ_enumerate(Object.keys(iterable))
152
+ return ρσ_enumerate(Object.keys(iterable), start)
120
153
 
121
154
  def ρσ_reversed(iterable):
122
155
  if ρσ_arraylike(iterable):
@@ -131,7 +164,22 @@ def ρσ_reversed(iterable):
131
164
  return ans
132
165
  raise TypeError('reversed() can only be called on arrays or strings')
133
166
 
134
- def ρσ_iter(iterable):
167
+ def ρσ_iter(iterable, sentinel):
168
+ # Two-argument form: iter(callable, sentinel) — call repeatedly until sentinel is returned
169
+ if arguments.length >= 2:
170
+ callable_ = iterable
171
+ ans = v'{"_callable":callable_,"_sentinel":sentinel,"_done":false}'
172
+ ans[ρσ_iterator_symbol] = def():
173
+ return this
174
+ ans['next'] = def():
175
+ if this._done:
176
+ return v"{'done':true}"
177
+ val = ρσ_callable_call(this._callable)
178
+ if v'val === this._sentinel':
179
+ this._done = True
180
+ return v"{'done':true}"
181
+ return v"{'done':false,'value':val}"
182
+ return ans
135
183
  # Generate a JavaScript iterator object from iterable
136
184
  if jstype(iterable[ρσ_iterator_symbol]) is 'function':
137
185
  return iterable.keys() if jstype(Map) is 'function' and v'iterable instanceof Map' else iterable[ρσ_iterator_symbol]()
@@ -244,6 +292,67 @@ def ρσ_type(x):
244
292
  return x.constructor
245
293
 
246
294
 
295
+ def ρσ_issubclass(cls, base):
296
+ if Array.isArray(base):
297
+ for b in base:
298
+ if ρσ_issubclass(cls, b):
299
+ return True
300
+ return False
301
+ if jstype(cls) is not 'function':
302
+ raise TypeError('issubclass() arg 1 must be a class')
303
+ if jstype(base) is not 'function':
304
+ raise TypeError('issubclass() arg 2 must be a class')
305
+ if cls is base:
306
+ return True
307
+ v'var proto = cls.prototype; while (proto !== null && proto !== undefined) { if (proto === base.prototype) return true; proto = Object.getPrototypeOf(proto); }'
308
+ return False
309
+
310
+
311
+ v'var ρσ_hash_id_counter = 0'
312
+
313
+ def ρσ_hash(obj):
314
+ v'var ρσ_t = typeof obj'
315
+ v'if (obj === null || obj === undefined) return 0'
316
+ v'if (ρσ_t === "boolean") return obj ? 1 : 0'
317
+ v'if (ρσ_t === "number") { return (obj === Math.floor(obj)) ? (obj | 0) : ((obj * 2654435761) | 0); }'
318
+ v'''if (ρσ_t === "string") {
319
+ var ρσ_h = 5381;
320
+ for (var ρσ_i = 0; ρσ_i < obj.length; ρσ_i++) {
321
+ ρσ_h = (((ρσ_h << 5) + ρσ_h) ^ obj.charCodeAt(ρσ_i)) | 0;
322
+ }
323
+ return ρσ_h;
324
+ }'''
325
+ v'if (typeof obj.__hash__ === "function") return obj.__hash__()'
326
+ v'if (Array.isArray(obj)) throw new TypeError("unhashable type: \'list\'")'
327
+ v'if (typeof ρσ_set === "function" && obj instanceof ρσ_set) throw new TypeError("unhashable type: \'set\'")'
328
+ v'if (typeof Set === "function" && obj instanceof Set) throw new TypeError("unhashable type: \'set\'")'
329
+ v'if (typeof ρσ_dict === "function" && obj instanceof ρσ_dict) throw new TypeError("unhashable type: \'dict\'")'
330
+ v'if (typeof Map === "function" && obj instanceof Map) throw new TypeError("unhashable type: \'dict\'")'
331
+ v'if (!obj.constructor || obj.constructor === Object) throw new TypeError("unhashable type: \'dict\'")'
332
+ v'if (obj.ρσ_object_id === undefined) obj.ρσ_object_id = ++ρσ_hash_id_counter'
333
+ v'return obj.ρσ_object_id'
334
+
335
+
336
+ def ρσ_next(iterator, defval):
337
+ if iterator is None or iterator is undefined:
338
+ raise TypeError('object is not an iterator')
339
+ if jstype(iterator.next) is 'function':
340
+ r = iterator.next()
341
+ if r.done:
342
+ if arguments.length > 1:
343
+ return defval
344
+ raise StopIteration()
345
+ return r.value
346
+ if jstype(iterator.__next__) is 'function':
347
+ try:
348
+ return iterator.__next__()
349
+ except StopIteration:
350
+ if arguments.length > 1:
351
+ return defval
352
+ raise
353
+ raise TypeError("object is not an iterator")
354
+
355
+
247
356
  def ρσ_divmod(x, y):
248
357
  if y is 0:
249
358
  raise ZeroDivisionError('integer division or modulo by zero')
@@ -269,11 +378,72 @@ def ρσ_max(*args, **kwargs):
269
378
  raise TypeError('expected at least one argument')
270
379
 
271
380
 
381
+ class ρσ_slice:
382
+ def __init__(self, start_or_stop, stop, step):
383
+ if arguments.length is 1:
384
+ self.start = None
385
+ self.stop = start_or_stop
386
+ self.step = None
387
+ elif arguments.length is 2:
388
+ self.start = start_or_stop
389
+ self.stop = stop
390
+ self.step = None
391
+ else:
392
+ self.start = start_or_stop
393
+ self.stop = stop
394
+ self.step = step
395
+
396
+ def indices(self, length):
397
+ step = 1 if self.step is None else self.step
398
+ if step is 0:
399
+ raise ValueError('slice step cannot be zero')
400
+ if step > 0:
401
+ lower = 0
402
+ upper = length
403
+ start = lower if self.start is None else self.start
404
+ stop = upper if self.stop is None else self.stop
405
+ else:
406
+ lower = -1
407
+ upper = length - 1
408
+ start = upper if self.start is None else self.start
409
+ stop = lower if self.stop is None else self.stop
410
+ # Only clamp values that were explicitly provided (None defaults are already correct).
411
+ if self.start is not None:
412
+ if start < 0:
413
+ start = max(start + length, lower)
414
+ if start > upper:
415
+ start = upper
416
+ if self.stop is not None:
417
+ if stop < 0:
418
+ stop = max(stop + length, lower)
419
+ if stop > upper:
420
+ stop = upper
421
+ return start, stop, step
422
+
423
+ def __repr__(self):
424
+ s = 'None' if self.start is None else String(self.start)
425
+ stop = 'None' if self.stop is None else String(self.stop)
426
+ step = 'None' if self.step is None else String(self.step)
427
+ return 'slice(' + s + ', ' + stop + ', ' + step + ')'
428
+
429
+ def __str__(self):
430
+ return self.__repr__()
431
+
432
+ def __eq__(self, other):
433
+ if not v'other instanceof ρσ_slice':
434
+ return False
435
+ return self.start is other.start and self.stop is other.stop and self.step is other.step
436
+
437
+ def __hash__(self):
438
+ raise TypeError("unhashable type: 'slice'")
439
+
440
+
272
441
  v'var abs = Math.abs, max = ρσ_max.bind(Math.max), min = ρσ_max.bind(Math.min), bool = ρσ_bool, type = ρσ_type'
273
442
  v'var float = ρσ_float, int = ρσ_int, arraylike = ρσ_arraylike_creator(), ρσ_arraylike = arraylike'
274
443
  v'var id = ρσ_id, get_module = ρσ_get_module, pow = ρσ_pow, divmod = ρσ_divmod'
275
- v'var dir = ρσ_dir, ord = ρσ_ord, chr = ρσ_chr, bin = ρσ_bin, hex = ρσ_hex, callable = ρσ_callable'
444
+ v'var dir = ρσ_dir, ord = ρσ_ord, chr = ρσ_chr, bin = ρσ_bin, hex = ρσ_hex, callable = ρσ_callable, round = ρσ_round'
276
445
  v'var enumerate = ρσ_enumerate, iter = ρσ_iter, reversed = ρσ_reversed, len = ρσ_len'
277
- v'var range = ρσ_range, getattr = ρσ_getattr, setattr = ρσ_setattr, hasattr = ρσ_hasattr'
446
+ v'var range = ρσ_range, getattr = ρσ_getattr, setattr = ρσ_setattr, hasattr = ρσ_hasattr, issubclass = ρσ_issubclass, hash = ρσ_hash, next = ρσ_next'
278
447
  v'var ρσ_Ellipsis = Object.freeze({toString: function(){return "Ellipsis";}, __repr__: function(){return "Ellipsis";}})'
279
448
  v'var Ellipsis = ρσ_Ellipsis'
449
+ v'var slice = ρσ_slice'
@@ -140,7 +140,7 @@ def ρσ_list_sort(key=None, reverse=False):
140
140
  k = this[i] # noqa:undef
141
141
  keymap.set(k, key(k))
142
142
  posmap.set(k, i)
143
- this.sort(def (a, b): return mult * ρσ_list_sort_cmp(keymap.get(a), keymap.get(b), posmap.get(a), posmap.get(b));)
143
+ Array.prototype.sort.call(this, def (a, b): return mult * ρσ_list_sort_cmp(keymap.get(a), keymap.get(b), posmap.get(a), posmap.get(b));)
144
144
 
145
145
  def ρσ_list_concat(): # ensure concat() returns an object of type list
146
146
  ans = Array.prototype.concat.apply(this, arguments)
@@ -190,14 +190,18 @@ def ρσ_list_decorate(ans):
190
190
  ans.inspect = ρσ_list_to_string
191
191
  ans.extend = ρσ_list_extend
192
192
  ans.index = ρσ_list_index
193
- ans.pypop = ρσ_list_pop
193
+ ans.jspop = Array.prototype.pop # native JS pop (no bounds check, ignores args)
194
+ ans.pop = ρσ_list_pop # Python pop (bounds-checked, supports negative index)
195
+ ans.pypop = ρσ_list_pop # backward-compat alias for pop
194
196
  ans.remove = ρσ_list_remove
195
197
  ans.insert = ρσ_list_insert
196
198
  ans.copy = ρσ_list_copy
197
199
  ans.clear = ρσ_list_clear
198
200
  ans.count = ρσ_list_count
199
201
  ans.concat = ρσ_list_concat
200
- ans.pysort = ρσ_list_sort
202
+ ans.jssort = Array.prototype.sort # native JS sort (lexicographic by default)
203
+ ans.sort = ρσ_list_sort # Python sort (numeric by default)
204
+ ans.pysort = ρσ_list_sort # backward-compat alias for sort
201
205
  ans.slice = ρσ_list_slice
202
206
  ans.as_array = ρσ_list_as_array
203
207
  ans.__len__ = ρσ_list_len
@@ -235,7 +239,7 @@ v'var list = ρσ_list_constructor, list_wrap = ρσ_list_decorate'
235
239
 
236
240
  def sorted(iterable, key=None, reverse=False):
237
241
  ans = ρσ_list_constructor(iterable)
238
- ans.pysort(key, reverse)
242
+ ans.sort(key, reverse)
239
243
  return ans
240
244
  # }}}
241
245
 
@@ -468,7 +472,7 @@ Object.defineProperties(ρσ_set.prototype, {
468
472
  return '{' + list(this).join(', ') + '}'
469
473
 
470
474
  ρσ_set.prototype.__eq__ = def(other):
471
- if not v'other instanceof this.constructor':
475
+ if v'!other || !other.jsset':
472
476
  return False
473
477
  if other.size is not this.size:
474
478
  return False
@@ -490,6 +494,142 @@ def ρσ_set_wrap(x):
490
494
  v'var set = ρσ_set, set_wrap = ρσ_set_wrap'
491
495
  # }}}
492
496
 
497
+ # frozenset {{{
498
+ def ρσ_frozenset(iterable):
499
+ if v'this instanceof ρσ_frozenset':
500
+ this.jsset = new ρσ_set_implementation() # noqa:undef
501
+ ans = this
502
+ if iterable is undefined:
503
+ return ans
504
+ s = ans.jsset
505
+ if ρσ_arraylike(iterable):
506
+ for v'var i = 0; i < iterable.length; i++':
507
+ s.add(iterable[i])
508
+ elif jstype(iterable[ρσ_iterator_symbol]) is 'function':
509
+ iterator = iterable.keys() if jstype(Map) is 'function' and v'iterable instanceof Map' else iterable[ρσ_iterator_symbol]()
510
+ result = iterator.next()
511
+ while not result.done:
512
+ s.add(result.value)
513
+ result = iterator.next()
514
+ else:
515
+ keys = Object.keys(iterable)
516
+ for v'var j=0; j < keys.length; j++':
517
+ s.add(keys[j])
518
+ return ans
519
+ else:
520
+ return new ρσ_frozenset(iterable)
521
+ ρσ_frozenset.prototype.__name__ = 'frozenset'
522
+
523
+ Object.defineProperties(ρσ_frozenset.prototype, {
524
+ 'length': { 'get': def(): return this.jsset.size; },
525
+ 'size': { 'get': def(): return this.jsset.size; },
526
+ })
527
+
528
+ ρσ_frozenset.prototype.__len__ = def(): return this.jsset.size
529
+ ρσ_frozenset.prototype.has = ρσ_frozenset.prototype.__contains__ = def(x): return this.jsset.has(x)
530
+ ρσ_frozenset.prototype.copy = def(): return ρσ_frozenset(this)
531
+ ρσ_frozenset.prototype[ρσ_iterator_symbol] = def(): return this.jsset.values()
532
+
533
+ ρσ_frozenset.prototype.difference = def():
534
+ ans = new ρσ_frozenset()
535
+ s = ans.jsset
536
+ iterator = this.jsset.values()
537
+ r = iterator.next()
538
+ while not r.done:
539
+ x = r.value
540
+ has = False
541
+ for v'var i = 0; i < arguments.length; i++':
542
+ if arguments[i].has(x): # noqa:undef
543
+ has = True
544
+ break
545
+ if not has:
546
+ s.add(x)
547
+ r = iterator.next()
548
+ return ans
549
+
550
+ ρσ_frozenset.prototype.intersection = def():
551
+ ans = new ρσ_frozenset()
552
+ s = ans.jsset
553
+ iterator = this.jsset.values()
554
+ r = iterator.next()
555
+ while not r.done:
556
+ x = r.value
557
+ has = True
558
+ for v'var i = 0; i < arguments.length; i++':
559
+ if not arguments[i].has(x): # noqa:undef
560
+ has = False
561
+ break
562
+ if has:
563
+ s.add(x)
564
+ r = iterator.next()
565
+ return ans
566
+
567
+ ρσ_frozenset.prototype.isdisjoint = def(other):
568
+ iterator = this.jsset.values()
569
+ r = iterator.next()
570
+ while not r.done:
571
+ x = r.value
572
+ if other.has(x):
573
+ return False
574
+ r = iterator.next()
575
+ return True
576
+
577
+ ρσ_frozenset.prototype.issubset = def(other):
578
+ iterator = this.jsset.values()
579
+ r = iterator.next()
580
+ while not r.done:
581
+ x = r.value
582
+ if not other.has(x):
583
+ return False
584
+ r = iterator.next()
585
+ return True
586
+
587
+ ρσ_frozenset.prototype.issuperset = def(other):
588
+ s = this.jsset
589
+ iterator = other[ρσ_iterator_symbol]()
590
+ r = iterator.next()
591
+ while not r.done:
592
+ x = r.value
593
+ if not s.has(x):
594
+ return False
595
+ r = iterator.next()
596
+ return True
597
+
598
+ ρσ_frozenset.prototype.symmetric_difference = def(other):
599
+ return this.union(other).difference(this.intersection(other))
600
+
601
+ ρσ_frozenset.prototype.union = def():
602
+ ans = ρσ_frozenset(this)
603
+ s = ans.jsset
604
+ for v'var i=0; i < arguments.length; i++':
605
+ iterator = arguments[i][ρσ_iterator_symbol]() # noqa:undef
606
+ r = iterator.next()
607
+ while not r.done:
608
+ s.add(r.value)
609
+ r = iterator.next()
610
+ return ans
611
+
612
+ ρσ_frozenset.prototype.toString = ρσ_frozenset.prototype.__repr__ = ρσ_frozenset.prototype.__str__ = ρσ_frozenset.prototype.inspect = def():
613
+ return 'frozenset({' + list(this).join(', ') + '})'
614
+
615
+ ρσ_frozenset.prototype.__eq__ = def(other):
616
+ if v'!other || !other.jsset':
617
+ return False
618
+ if other.size is not this.size:
619
+ return False
620
+ if other.size is 0:
621
+ return True
622
+ iterator = other[ρσ_iterator_symbol]()
623
+ r = iterator.next()
624
+ while not r.done:
625
+ if not this.has(r.value):
626
+ return False
627
+ r = iterator.next()
628
+ return True
629
+
630
+ v'var frozenset = ρσ_frozenset'
631
+ # }}}
632
+
493
633
  # dict {{{
494
634
  v'var ρσ_dict_implementation'
495
635
 
@@ -704,6 +844,15 @@ Object.defineProperties(ρσ_dict.prototype, {
704
844
  r = iterator.next()
705
845
  return True
706
846
 
847
+ ρσ_dict.prototype.__or__ = def(other):
848
+ result = ρσ_dict(this)
849
+ result.update(other)
850
+ return result
851
+
852
+ ρσ_dict.prototype.__ior__ = def(other):
853
+ this.update(other)
854
+ return this
855
+
707
856
  ρσ_dict.prototype.as_object = def(other):
708
857
  ans = {}
709
858
  iterator = this.jsmap.entries()
@@ -35,3 +35,6 @@ class AssertionError(Exception):
35
35
 
36
36
  class ZeroDivisionError(Exception):
37
37
  pass
38
+
39
+ class StopIteration(Exception):
40
+ pass