rapydscript-ns 0.8.3 → 0.9.0

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 (72) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1351 -141
  3. package/TODO.md +12 -6
  4. package/language-service/index.js +184 -26
  5. package/package.json +1 -1
  6. package/release/baselib-plain-pretty.js +5895 -1928
  7. package/release/baselib-plain-ugly.js +140 -3
  8. package/release/compiler.js +16282 -5408
  9. package/release/signatures.json +25 -22
  10. package/src/ast.pyj +94 -1
  11. package/src/baselib-builtins.pyj +362 -3
  12. package/src/baselib-bytes.pyj +664 -0
  13. package/src/baselib-containers.pyj +99 -0
  14. package/src/baselib-errors.pyj +45 -1
  15. package/src/baselib-internal.pyj +346 -49
  16. package/src/baselib-itertools.pyj +17 -4
  17. package/src/baselib-str.pyj +46 -4
  18. package/src/lib/abc.pyj +317 -0
  19. package/src/lib/copy.pyj +120 -0
  20. package/src/lib/dataclasses.pyj +532 -0
  21. package/src/lib/enum.pyj +125 -0
  22. package/src/lib/pythonize.pyj +1 -1
  23. package/src/lib/re.pyj +35 -1
  24. package/src/lib/react.pyj +74 -0
  25. package/src/lib/typing.pyj +577 -0
  26. package/src/monaco-language-service/builtins.js +19 -4
  27. package/src/monaco-language-service/diagnostics.js +40 -19
  28. package/src/output/classes.pyj +161 -25
  29. package/src/output/codegen.pyj +16 -2
  30. package/src/output/exceptions.pyj +97 -1
  31. package/src/output/functions.pyj +87 -5
  32. package/src/output/jsx.pyj +164 -0
  33. package/src/output/literals.pyj +28 -2
  34. package/src/output/loops.pyj +5 -2
  35. package/src/output/modules.pyj +1 -1
  36. package/src/output/operators.pyj +108 -36
  37. package/src/output/statements.pyj +2 -2
  38. package/src/output/stream.pyj +1 -0
  39. package/src/parse.pyj +496 -128
  40. package/src/tokenizer.pyj +38 -4
  41. package/test/abc.pyj +291 -0
  42. package/test/arithmetic_nostrict.pyj +88 -0
  43. package/test/arithmetic_types.pyj +169 -0
  44. package/test/baselib.pyj +91 -0
  45. package/test/bytes.pyj +467 -0
  46. package/test/classes.pyj +1 -0
  47. package/test/comparison_ops.pyj +173 -0
  48. package/test/dataclasses.pyj +253 -0
  49. package/test/enum.pyj +134 -0
  50. package/test/eval_exec.pyj +56 -0
  51. package/test/format.pyj +148 -0
  52. package/test/object.pyj +64 -0
  53. package/test/python_compat.pyj +17 -15
  54. package/test/python_features.pyj +89 -21
  55. package/test/regexp.pyj +29 -1
  56. package/test/tuples.pyj +96 -0
  57. package/test/typing.pyj +469 -0
  58. package/test/unit/index.js +2292 -70
  59. package/test/unit/language-service.js +674 -4
  60. package/test/unit/web-repl.js +1106 -0
  61. package/test/vars_locals_globals.pyj +94 -0
  62. package/tools/cli.js +11 -0
  63. package/tools/compile.js +5 -0
  64. package/tools/embedded_compiler.js +15 -4
  65. package/tools/lint.js +16 -19
  66. package/tools/repl.js +1 -1
  67. package/web-repl/env.js +122 -0
  68. package/web-repl/main.js +1 -3
  69. package/web-repl/rapydscript.js +125 -3
  70. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  71. package/PYTHON_FEATURE_COVERAGE.md +0 -200
  72. package/hack_demo.pyj +0 -112
@@ -87,27 +87,24 @@ def ρσ_extends(child, parent):
87
87
  child.prototype.constructor = child
88
88
  Object.setPrototypeOf(child, parent)
89
89
 
90
- ρσ_in = (def ():
91
- if jstype(Map) is 'function' and jstype(Set) is 'function':
92
- return def(val, arr):
93
- if jstype(arr) is 'string':
94
- return arr.indexOf(val) is not -1
95
- if jstype(arr.__contains__) is 'function':
96
- return arr.__contains__(val)
97
- if v'arr instanceof Map || arr instanceof Set':
98
- return arr.has(val)
99
- if ρσ_arraylike(arr):
100
- return ρσ_list_contains.call(arr, val)
101
- return Object.prototype.hasOwnProperty.call(arr, val)
102
- return def(val, arr):
103
- if jstype(arr) is 'string':
104
- return arr.indexOf(val) is not -1
105
- if jstype(arr.__contains__) is 'function':
106
- return arr.__contains__(val)
107
- if ρσ_arraylike(arr):
108
- return ρσ_list_contains.call(arr, val)
109
- return Object.prototype.hasOwnProperty.call(arr, val)
110
- )()
90
+ def ρσ_object_new(cls):
91
+ return v'Object.create(cls.prototype)'
92
+
93
+ def ρσ_new(parent, cls):
94
+ if parent and jstype(parent.__new__) is 'function':
95
+ return parent.__new__.apply(parent, v'Array.prototype.slice.call(arguments, 1)')
96
+ return v'Object.create(cls.prototype)'
97
+
98
+ def ρσ_in(val, arr):
99
+ if jstype(arr) is 'string':
100
+ return arr.indexOf(val) is not -1
101
+ if jstype(arr.__contains__) is 'function':
102
+ return arr.__contains__(val)
103
+ if jstype(Map) is 'function' and (v'arr instanceof Map' or v'arr instanceof Set'):
104
+ return arr.has(val)
105
+ if ρσ_arraylike(arr):
106
+ return ρσ_list_contains.call(arr, val)
107
+ return Object.prototype.hasOwnProperty.call(arr, val)
111
108
 
112
109
  def ρσ_Iterable(iterable):
113
110
  # Once ES6 is mature, change AST_ForIn to use the iterator protocol and get
@@ -125,23 +122,20 @@ def ρσ_Iterable(iterable):
125
122
  # so we can use 'for ... in' syntax with objects, as we would with dicts in python
126
123
  return Object.keys(iterable)
127
124
 
128
- ρσ_desugar_kwargs = (def ():
129
- if jstype(Object.assign) is 'function':
130
- return def():
131
- ans = Object.create(None)
132
- ans[ρσ_kwargs_symbol] = True
133
- for v'var i = 0; i < arguments.length; i++':
134
- Object.assign(ans, arguments[i])
135
- return ans
136
- return def():
137
- ans = Object.create(None)
138
- ans[ρσ_kwargs_symbol] = True
139
- for v'var i = 0; i < arguments.length; i++':
140
- keys = Object.keys(arguments[i])
125
+ def ρσ_desugar_kwargs():
126
+ ans = Object.create(None)
127
+ ans[ρσ_kwargs_symbol] = True
128
+ for v'var i = 0; i < arguments.length; i++':
129
+ arg = arguments[i]
130
+ if v'arg && arg.jsmap && typeof arg.jsmap.forEach === "function"':
131
+ v'arg.jsmap.forEach(function(v, k) { ans[k] = v; })'
132
+ elif jstype(Object.assign) is 'function':
133
+ Object.assign(ans, arg)
134
+ else:
135
+ keys = Object.keys(arg)
141
136
  for v'var j = 0; j < keys.length; j++':
142
- ans[keys[j]] = arguments[i][keys[j]]
143
- return ans
144
- )()
137
+ ans[keys[j]] = arg[keys[j]]
138
+ return ans
145
139
 
146
140
  def ρσ_interpolate_kwargs(f, supplied_args):
147
141
  if not f.__argnames__:
@@ -177,6 +171,8 @@ def ρσ_interpolate_kwargs_constructor(apply, f, supplied_args):
177
171
  return this
178
172
 
179
173
  def ρσ_getitem(obj, key):
174
+ if v'typeof obj === "function"' and obj.__class_getitem__:
175
+ return obj.__class_getitem__(key)
180
176
  if obj.__getitem__:
181
177
  return obj.__getitem__(key)
182
178
  if v'typeof ρσ_slice !== "undefined" && key instanceof ρσ_slice':
@@ -249,6 +245,7 @@ def ρσ_splice(arr, val, start, end):
249
245
  ,'e': def(expr, alt):
250
246
  return alt if expr is undefined or expr is None else expr
251
247
  }
248
+ v'(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : global)["ρσ_exists"] = ρσ_exists'
252
249
 
253
250
  def ρσ_mixin():
254
251
  # Implement a depth-first left-to-right method resolution order This is not
@@ -278,55 +275,96 @@ def ρσ_mixin():
278
275
  p = Object.getPrototypeOf(p)
279
276
  Object.defineProperties(target, resolved_props)
280
277
 
278
+ # Returns a Python-style type name used in arithmetic TypeError messages.
279
+ def ρσ_arith_type_name(v):
280
+ if v is None or v is undefined:
281
+ return 'NoneType'
282
+ t = jstype(v)
283
+ if t is 'boolean':
284
+ return 'bool'
285
+ if t is 'number':
286
+ return 'int' if Number.isInteger(v) else 'float'
287
+ if t is 'string' or v'v instanceof String':
288
+ return 'str'
289
+ if Array.isArray(v):
290
+ return 'list'
291
+ if v.constructor and v.constructor.__name__:
292
+ return v.constructor.__name__
293
+ return t
294
+
281
295
  # Arithmetic/bitwise operator overloading helpers.
282
296
  # Each checks for the forward dunder method, then the reflected one, then
283
- # falls back to the native JS operation (with special handling for // and **).
297
+ # enforces Python-style type compatibility before the native JS operation.
298
+ # Incompatible operand types raise TypeError, mirroring Python semantics.
284
299
  def ρσ_op_add(a, b):
285
300
  if a is not None and jstype(a.__add__) is 'function': return a.__add__(b)
286
301
  if b is not None and jstype(b.__radd__) is 'function': return b.__radd__(a)
287
- return a + b
302
+ if Array.isArray(a) and Array.isArray(b): return ρσ_list_constructor(a.concat(b))
303
+ ta = jstype(a)
304
+ tb = jstype(b)
305
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return v'a + b'
306
+ if (ta is 'string' or v'a instanceof String') and (tb is 'string' or v'b instanceof String'): return v'a + b'
307
+ raise TypeError("unsupported operand type(s) for +: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
288
308
 
289
309
  def ρσ_op_sub(a, b):
290
310
  if a is not None and jstype(a.__sub__) is 'function': return a.__sub__(b)
291
311
  if b is not None and jstype(b.__rsub__) is 'function': return b.__rsub__(a)
292
- return a - b
312
+ ta = jstype(a)
313
+ tb = jstype(b)
314
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return a - b
315
+ raise TypeError("unsupported operand type(s) for -: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
293
316
 
294
317
  def ρσ_op_mul(a, b):
295
318
  if a is not None and jstype(a.__mul__) is 'function': return a.__mul__(b)
296
319
  if b is not None and jstype(b.__rmul__) is 'function': return b.__rmul__(a)
297
- if (jstype(a) is 'string' or v'a instanceof String') and jstype(b) is 'number': return a.repeat(b)
298
- if (jstype(b) is 'string' or v'b instanceof String') and jstype(a) is 'number': return b.repeat(a)
299
- if Array.isArray(a) and jstype(b) is 'number':
320
+ ta = jstype(a)
321
+ tb = jstype(b)
322
+ if (ta is 'string' or v'a instanceof String') and (tb is 'number' or tb is 'boolean'): return a.repeat(b)
323
+ if (tb is 'string' or v'b instanceof String') and (ta is 'number' or ta is 'boolean'): return b.repeat(a)
324
+ if Array.isArray(a) and (tb is 'number' or tb is 'boolean'):
300
325
  result = v'[]'
301
326
  for v'var ρσ_mi = 0; ρσ_mi < b; ρσ_mi++':
302
327
  result = result.concat(a) # noqa:undef
303
328
  return ρσ_list_constructor(result)
304
- if Array.isArray(b) and jstype(a) is 'number':
329
+ if Array.isArray(b) and (ta is 'number' or ta is 'boolean'):
305
330
  result = v'[]'
306
331
  for v'var ρσ_mi = 0; ρσ_mi < a; ρσ_mi++':
307
332
  result = result.concat(b) # noqa:undef
308
333
  return ρσ_list_constructor(result)
309
- return a * b
334
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return a * b
335
+ raise TypeError("unsupported operand type(s) for *: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
310
336
 
311
337
  def ρσ_op_truediv(a, b):
312
338
  if a is not None and jstype(a.__truediv__) is 'function': return a.__truediv__(b)
313
339
  if b is not None and jstype(b.__rtruediv__) is 'function': return b.__rtruediv__(a)
314
- return a / b
340
+ ta = jstype(a)
341
+ tb = jstype(b)
342
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return a / b
343
+ raise TypeError("unsupported operand type(s) for /: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
315
344
 
316
345
  def ρσ_op_floordiv(a, b):
317
346
  if a is not None and jstype(a.__floordiv__) is 'function': return a.__floordiv__(b)
318
347
  if b is not None and jstype(b.__rfloordiv__) is 'function': return b.__rfloordiv__(a)
319
- return Math.floor(a / b)
348
+ ta = jstype(a)
349
+ tb = jstype(b)
350
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return Math.floor(a / b)
351
+ raise TypeError("unsupported operand type(s) for //: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
320
352
 
321
353
  def ρσ_op_mod(a, b):
322
354
  if a is not None and jstype(a.__mod__) is 'function': return a.__mod__(b)
323
355
  if b is not None and jstype(b.__rmod__) is 'function': return b.__rmod__(a)
324
- return a % b
356
+ ta = jstype(a)
357
+ tb = jstype(b)
358
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return a % b
359
+ raise TypeError("unsupported operand type(s) for %: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
325
360
 
326
361
  def ρσ_op_pow(a, b):
327
362
  if a is not None and jstype(a.__pow__) is 'function': return a.__pow__(b)
328
363
  if b is not None and jstype(b.__rpow__) is 'function': return b.__rpow__(a)
329
- return Math.pow(a, b)
364
+ ta = jstype(a)
365
+ tb = jstype(b)
366
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return Math.pow(a, b)
367
+ raise TypeError("unsupported operand type(s) for **: '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
330
368
 
331
369
  def ρσ_op_and(a, b):
332
370
  if a is not None and jstype(a.__and__) is 'function': return a.__and__(b)
@@ -353,12 +391,74 @@ def ρσ_op_rshift(a, b):
353
391
  if b is not None and jstype(b.__rrshift__) is 'function': return b.__rrshift__(a)
354
392
  return a >> b
355
393
 
394
+ # Ordered-comparison operator overloading helpers.
395
+ # ρσ_op_lt dispatches __lt__ (forward) then __gt__ (reflected), handles lists
396
+ # lexicographically, enforces type compatibility, and raises TypeError otherwise.
397
+ # ρσ_op_gt / ρσ_op_ge delegate to ρσ_op_lt / ρσ_op_le after dunder checks.
398
+ def ρσ_op_lt(a, b):
399
+ if a is not None and jstype(a.__lt__) is 'function': return a.__lt__(b)
400
+ if b is not None and jstype(b.__gt__) is 'function': return b.__gt__(a)
401
+ if Array.isArray(a) and Array.isArray(b):
402
+ n = Math.min(a.length, b.length)
403
+ for v'var ρσ_ci = 0; ρσ_ci < n; ρσ_ci++':
404
+ ea = v'a[ρσ_ci]'
405
+ eb = v'b[ρσ_ci]'
406
+ if ρσ_op_lt(ea, eb): return True
407
+ if ρσ_op_lt(eb, ea): return False
408
+ return v'a.length < b.length'
409
+ ta = jstype(a)
410
+ tb = jstype(b)
411
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return v'a < b'
412
+ if (ta is 'string' or v'a instanceof String') and (tb is 'string' or v'b instanceof String'): return v'a < b'
413
+ raise TypeError("'<' not supported between instances of '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
414
+
415
+ def ρσ_op_le(a, b):
416
+ if a is not None and jstype(a.__le__) is 'function': return a.__le__(b)
417
+ if b is not None and jstype(b.__ge__) is 'function': return b.__ge__(a)
418
+ if Array.isArray(a) and Array.isArray(b):
419
+ n = Math.min(a.length, b.length)
420
+ for v'var ρσ_ci = 0; ρσ_ci < n; ρσ_ci++':
421
+ ea = v'a[ρσ_ci]'
422
+ eb = v'b[ρσ_ci]'
423
+ if ρσ_op_lt(ea, eb): return True
424
+ if ρσ_op_lt(eb, ea): return False
425
+ return v'a.length <= b.length'
426
+ ta = jstype(a)
427
+ tb = jstype(b)
428
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return v'a <= b'
429
+ if (ta is 'string' or v'a instanceof String') and (tb is 'string' or v'b instanceof String'): return v'a <= b'
430
+ raise TypeError("'<=' not supported between instances of '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
431
+
432
+ def ρσ_op_gt(a, b):
433
+ if a is not None and jstype(a.__gt__) is 'function': return a.__gt__(b)
434
+ if b is not None and jstype(b.__lt__) is 'function': return b.__lt__(a)
435
+ if Array.isArray(a) and Array.isArray(b): return ρσ_op_lt(b, a)
436
+ ta = jstype(a)
437
+ tb = jstype(b)
438
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return v'a > b'
439
+ if (ta is 'string' or v'a instanceof String') and (tb is 'string' or v'b instanceof String'): return v'a > b'
440
+ raise TypeError("'>' not supported between instances of '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
441
+
442
+ def ρσ_op_ge(a, b):
443
+ if a is not None and jstype(a.__ge__) is 'function': return a.__ge__(b)
444
+ if b is not None and jstype(b.__le__) is 'function': return b.__le__(a)
445
+ if Array.isArray(a) and Array.isArray(b): return ρσ_op_le(b, a)
446
+ ta = jstype(a)
447
+ tb = jstype(b)
448
+ if (ta is 'number' or ta is 'boolean') and (tb is 'number' or tb is 'boolean'): return v'a >= b'
449
+ if (ta is 'string' or v'a instanceof String') and (tb is 'string' or v'b instanceof String'): return v'a >= b'
450
+ raise TypeError("'>=' not supported between instances of '" + ρσ_arith_type_name(a) + "' and '" + ρσ_arith_type_name(b) + "'")
451
+
356
452
  # List-concatenation helpers (always available, no overload_operators required).
357
453
  # ρσ_list_add: used for the + operator; creates a new list when both sides are arrays.
358
454
  # NOTE: the fallback uses v'a + b' (verbatim JS) to avoid recursive self-compilation.
359
455
  def ρσ_list_add(a, b):
360
456
  if Array.isArray(a) and Array.isArray(b):
361
457
  return ρσ_list_constructor(a.concat(b))
458
+ if a is not None and a is not undefined and jstype(a.__add__) is 'function':
459
+ return a.__add__(b)
460
+ if b is not None and b is not undefined and jstype(b.__radd__) is 'function':
461
+ return b.__radd__(a)
362
462
  return v'a + b'
363
463
 
364
464
  # ρσ_list_iadd: used for +=; extends a in-place when both sides are arrays (Python semantics).
@@ -366,6 +466,10 @@ def ρσ_list_iadd(a, b):
366
466
  if Array.isArray(a) and Array.isArray(b):
367
467
  v'Array.prototype.push.apply(a, b)'
368
468
  return a
469
+ if a is not None and a is not undefined and jstype(a.__iadd__) is 'function':
470
+ return a.__iadd__(b)
471
+ if a is not None and a is not undefined and jstype(a.__add__) is 'function':
472
+ return a.__add__(b)
369
473
  return v'a + b'
370
474
 
371
475
  # Unary operator overloading helpers
@@ -430,6 +534,126 @@ def ρσ_op_irshift(a, b):
430
534
  if a is not None and jstype(a.__irshift__) is 'function': return a.__irshift__(b)
431
535
  return ρσ_op_rshift(a, b)
432
536
 
537
+ # No-strict (ns) variants: same dunder dispatch but JS native fallback instead of TypeError.
538
+ # Used when overload_operators is active but strict_arithmetic is disabled
539
+ # (from __python__ import no_strict_arithmetic).
540
+ def ρσ_op_add_ns(a, b):
541
+ if a is not None and jstype(a.__add__) is 'function': return a.__add__(b)
542
+ if b is not None and jstype(b.__radd__) is 'function': return b.__radd__(a)
543
+ if Array.isArray(a) and Array.isArray(b): return ρσ_list_constructor(a.concat(b))
544
+ return v'a + b'
545
+
546
+ def ρσ_op_sub_ns(a, b):
547
+ if a is not None and jstype(a.__sub__) is 'function': return a.__sub__(b)
548
+ if b is not None and jstype(b.__rsub__) is 'function': return b.__rsub__(a)
549
+ return v'a - b'
550
+
551
+ def ρσ_op_mul_ns(a, b):
552
+ if a is not None and jstype(a.__mul__) is 'function': return a.__mul__(b)
553
+ if b is not None and jstype(b.__rmul__) is 'function': return b.__rmul__(a)
554
+ ta = jstype(a)
555
+ tb = jstype(b)
556
+ if (ta is 'string' or v'a instanceof String') and (tb is 'number' or tb is 'boolean'): return a.repeat(b)
557
+ if (tb is 'string' or v'b instanceof String') and (ta is 'number' or ta is 'boolean'): return b.repeat(a)
558
+ if Array.isArray(a) and (tb is 'number' or tb is 'boolean'):
559
+ result = v'[]'
560
+ for v'var ρσ_mi = 0; ρσ_mi < b; ρσ_mi++':
561
+ result = result.concat(a) # noqa:undef
562
+ return ρσ_list_constructor(result)
563
+ if Array.isArray(b) and (ta is 'number' or ta is 'boolean'):
564
+ result = v'[]'
565
+ for v'var ρσ_mi = 0; ρσ_mi < a; ρσ_mi++':
566
+ result = result.concat(b) # noqa:undef
567
+ return ρσ_list_constructor(result)
568
+ return v'a * b'
569
+
570
+ def ρσ_op_truediv_ns(a, b):
571
+ if a is not None and jstype(a.__truediv__) is 'function': return a.__truediv__(b)
572
+ if b is not None and jstype(b.__rtruediv__) is 'function': return b.__rtruediv__(a)
573
+ return v'a / b'
574
+
575
+ def ρσ_op_floordiv_ns(a, b):
576
+ if a is not None and jstype(a.__floordiv__) is 'function': return a.__floordiv__(b)
577
+ if b is not None and jstype(b.__rfloordiv__) is 'function': return b.__rfloordiv__(a)
578
+ return Math.floor(v'a / b')
579
+
580
+ def ρσ_op_mod_ns(a, b):
581
+ if a is not None and jstype(a.__mod__) is 'function': return a.__mod__(b)
582
+ if b is not None and jstype(b.__rmod__) is 'function': return b.__rmod__(a)
583
+ return v'a % b'
584
+
585
+ def ρσ_op_pow_ns(a, b):
586
+ if a is not None and jstype(a.__pow__) is 'function': return a.__pow__(b)
587
+ if b is not None and jstype(b.__rpow__) is 'function': return b.__rpow__(a)
588
+ return Math.pow(a, b)
589
+
590
+ def ρσ_op_lt_ns(a, b):
591
+ if a is not None and jstype(a.__lt__) is 'function': return a.__lt__(b)
592
+ if b is not None and jstype(b.__gt__) is 'function': return b.__gt__(a)
593
+ if Array.isArray(a) and Array.isArray(b):
594
+ n = Math.min(a.length, b.length)
595
+ for v'var ρσ_ci = 0; ρσ_ci < n; ρσ_ci++':
596
+ ea = v'a[ρσ_ci]'
597
+ eb = v'b[ρσ_ci]'
598
+ if ρσ_op_lt_ns(ea, eb): return True
599
+ if ρσ_op_lt_ns(eb, ea): return False
600
+ return v'a.length < b.length'
601
+ return v'a < b'
602
+
603
+ def ρσ_op_le_ns(a, b):
604
+ if a is not None and jstype(a.__le__) is 'function': return a.__le__(b)
605
+ if b is not None and jstype(b.__ge__) is 'function': return b.__ge__(a)
606
+ if Array.isArray(a) and Array.isArray(b):
607
+ n = Math.min(a.length, b.length)
608
+ for v'var ρσ_ci = 0; ρσ_ci < n; ρσ_ci++':
609
+ ea = v'a[ρσ_ci]'
610
+ eb = v'b[ρσ_ci]'
611
+ if ρσ_op_lt_ns(ea, eb): return True
612
+ if ρσ_op_lt_ns(eb, ea): return False
613
+ return v'a.length <= b.length'
614
+ return v'a <= b'
615
+
616
+ def ρσ_op_gt_ns(a, b):
617
+ if a is not None and jstype(a.__gt__) is 'function': return a.__gt__(b)
618
+ if b is not None and jstype(b.__lt__) is 'function': return b.__lt__(a)
619
+ if Array.isArray(a) and Array.isArray(b): return ρσ_op_lt_ns(b, a)
620
+ return v'a > b'
621
+
622
+ def ρσ_op_ge_ns(a, b):
623
+ if a is not None and jstype(a.__ge__) is 'function': return a.__ge__(b)
624
+ if b is not None and jstype(b.__le__) is 'function': return b.__le__(a)
625
+ if Array.isArray(a) and Array.isArray(b): return ρσ_op_le_ns(b, a)
626
+ return v'a >= b'
627
+
628
+ # No-strict augmented-assignment helpers
629
+ def ρσ_op_iadd_ns(a, b):
630
+ if a is not None and jstype(a.__iadd__) is 'function': return a.__iadd__(b)
631
+ return ρσ_op_add_ns(a, b)
632
+
633
+ def ρσ_op_isub_ns(a, b):
634
+ if a is not None and jstype(a.__isub__) is 'function': return a.__isub__(b)
635
+ return ρσ_op_sub_ns(a, b)
636
+
637
+ def ρσ_op_imul_ns(a, b):
638
+ if a is not None and jstype(a.__imul__) is 'function': return a.__imul__(b)
639
+ return ρσ_op_mul_ns(a, b)
640
+
641
+ def ρσ_op_itruediv_ns(a, b):
642
+ if a is not None and jstype(a.__itruediv__) is 'function': return a.__itruediv__(b)
643
+ return ρσ_op_truediv_ns(a, b)
644
+
645
+ def ρσ_op_ifloordiv_ns(a, b):
646
+ if a is not None and jstype(a.__ifloordiv__) is 'function': return a.__ifloordiv__(b)
647
+ return ρσ_op_floordiv_ns(a, b)
648
+
649
+ def ρσ_op_imod_ns(a, b):
650
+ if a is not None and jstype(a.__imod__) is 'function': return a.__imod__(b)
651
+ return ρσ_op_mod_ns(a, b)
652
+
653
+ def ρσ_op_ipow_ns(a, b):
654
+ if a is not None and jstype(a.__ipow__) is 'function': return a.__ipow__(b)
655
+ return ρσ_op_pow_ns(a, b)
656
+
433
657
  def ρσ_instanceof():
434
658
  obj = arguments[0]
435
659
  bases = ''
@@ -458,3 +682,76 @@ def ρσ_instanceof():
458
682
  break
459
683
  cls = p.constructor
460
684
  return False
685
+
686
+ # ── Attribute-dunder support (__getattr__ / __setattr__ / __delattr__ / __getattribute__) ──
687
+ # We use a JavaScript Proxy to intercept attribute access/assignment/deletion on class instances
688
+ # that define any of the four attribute dunders.
689
+
690
+ # Cache the JS Proxy constructor with a ρσ_ name to avoid collisions with user-defined 'Proxy' classes.
691
+ v'var ρσ_JS_Proxy = typeof Proxy === "function" ? Proxy : null'
692
+
693
+ # Symbol (or fallback string key) used to retrieve the raw underlying target from a wrapped proxy.
694
+ v'var ρσ_proxy_target_symbol = typeof Symbol === "function" ? Symbol("ρσ_proxy_target") : "__ρσ_proxy_target__"'
695
+
696
+ ρσ_attr_proxy_handler = {
697
+ 'get': def(target, prop, receiver):
698
+ # Always expose the raw target via the sentinel key (used by ρσ_object_* helpers).
699
+ if prop is ρσ_proxy_target_symbol:
700
+ return target
701
+ # Pass through JS symbols (Symbol.iterator, Symbol.toPrimitive, etc.) unchanged.
702
+ if jstype(prop) is 'symbol':
703
+ return v'Reflect.get(target, prop, receiver)'
704
+ # __getattribute__ overrides every attribute lookup (except itself, to avoid infinite recursion).
705
+ if jstype(target.__getattribute__) is 'function' and prop is not '__getattribute__':
706
+ try:
707
+ return target.__getattribute__.call(receiver, prop)
708
+ except AttributeError:
709
+ # AttributeError from __getattribute__ → try __getattr__ fallback.
710
+ if jstype(target.__getattr__) is 'function':
711
+ return target.__getattr__.call(receiver, prop)
712
+ raise
713
+ # Normal property lookup via prototype chain.
714
+ val = v'Reflect.get(target, prop, receiver)'
715
+ # __getattr__ is only called when the attribute is genuinely missing.
716
+ if val is undefined and jstype(target.__getattr__) is 'function' and not v'(prop in target)':
717
+ return target.__getattr__.call(receiver, prop)
718
+ return val
719
+ ,
720
+ 'set': def(target, prop, value, receiver):
721
+ if jstype(target.__setattr__) is 'function':
722
+ target.__setattr__.call(receiver, prop, value)
723
+ return True
724
+ # Pass 'target' as receiver (not proxy) to avoid infinite recursion through the Proxy set trap.
725
+ return v'Reflect.set(target, prop, value, target)'
726
+ ,
727
+ 'deleteProperty': def(target, prop):
728
+ if jstype(target.__delattr__) is 'function':
729
+ # deleteProperty has no receiver; pass target as 'this'.
730
+ # Inside __delattr__, use ρσ_object_delattr(self, name) for a direct delete.
731
+ target.__delattr__.call(target, prop)
732
+ return True
733
+ return v'Reflect.deleteProperty(target, prop)'
734
+ }
735
+ v'(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : global)["ρσ_attr_proxy_handler"] = ρσ_attr_proxy_handler'
736
+
737
+ def ρσ_object_setattr(obj, name, value):
738
+ # Bypass __setattr__ and set directly on the underlying object.
739
+ # Use this inside __setattr__ to avoid infinite recursion (like object.__setattr__ in Python).
740
+ target = obj[ρσ_proxy_target_symbol]
741
+ if target is undefined:
742
+ target = obj
743
+ v'target[name] = value'
744
+
745
+ def ρσ_object_getattr(obj, name):
746
+ # Bypass __getattribute__ and read directly from the underlying object.
747
+ target = obj[ρσ_proxy_target_symbol]
748
+ if target is undefined:
749
+ target = obj
750
+ return target[name]
751
+
752
+ def ρσ_object_delattr(obj, name):
753
+ # Bypass __delattr__ and delete directly from the underlying object.
754
+ target = obj[ρσ_proxy_target_symbol]
755
+ if target is undefined:
756
+ target = obj
757
+ v'delete target[name]'
@@ -2,7 +2,7 @@
2
2
  # License: BSD
3
3
  # Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
4
4
 
5
- # globals: ρσ_iterator_symbol, ρσ_bool
5
+ # globals: ρσ_iterator_symbol, ρσ_bool, ρσ_kwargs_symbol
6
6
 
7
7
  def sum(iterable, start):
8
8
  if Array.isArray(iterable):
@@ -52,17 +52,30 @@ def filter(func_or_none, iterable):
52
52
  return ans
53
53
 
54
54
  def zip():
55
- iterators = new Array(arguments.length)
56
- for v'var i = 0; i < arguments.length; i++':
55
+ n = arguments.length
56
+ strict = False
57
+ if n > 0 and v'typeof arguments[n - 1] === "object" && arguments[n - 1] !== null' and arguments[n-1][ρσ_kwargs_symbol] is True:
58
+ strict = arguments[n-1]['strict'] or False
59
+ n -= 1
60
+ iterators = new Array(n)
61
+ for v'var i = 0; i < n; i++':
57
62
  iterators[i] = iter(arguments[i]) # noqa:undef
58
- ans = v"{'_iterators':iterators}"
63
+ ans = v"{'_iterators':iterators, '_strict':strict}"
59
64
  ans[ρσ_iterator_symbol] = def():
60
65
  return this
61
66
  ans['next'] = def():
67
+ if not this._iterators.length:
68
+ return v"{'done':true}"
62
69
  args = new Array(this._iterators.length)
63
70
  for v'var i = 0; i < this._iterators.length; i++':
64
71
  r = this._iterators[i].next()
65
72
  if r.done:
73
+ if this._strict:
74
+ for v'var j = i + 1; j < this._iterators.length; j++':
75
+ if not this._iterators[j].next().done:
76
+ raise ValueError('zip() has arguments with different lengths')
77
+ if v'i > 0':
78
+ raise ValueError('zip() has arguments with different lengths')
66
79
  return v"{'done':true}"
67
80
  args[i] = r.value # noqa:undef
68
81
  return v"{'done':false, 'value':args}"
@@ -109,7 +109,8 @@ define_str_func = def(name, func):
109
109
  if func.__argnames__:
110
110
  Object.defineProperty(f, '__argnames__', {'value':v"['string']".concat(func.__argnames__)})
111
111
 
112
- ρσ_orig_split, ρσ_orig_replace = String.prototype.split.call.bind(String.prototype.split), String.prototype.replace.call.bind(String.prototype.replace)
112
+ ρσ_orig_split = String.prototype.split.call.bind(String.prototype.split)
113
+ ρσ_orig_replace = String.prototype.replace.call.bind(String.prototype.replace)
113
114
 
114
115
  # format() {{{
115
116
  define_str_func('format', def ():
@@ -367,9 +368,20 @@ define_str_func('format', def ():
367
368
  idx += 1
368
369
  if jstype(object) is 'function':
369
370
  object = object()
370
- ans = '' + object
371
- if format_spec:
372
- ans = apply_formatting(ans, format_spec)
371
+ # Apply !-conversion (transformer) before format spec, per Python semantics
372
+ if transformer is 'r':
373
+ object = ρσ_repr(object)
374
+ elif transformer is 's':
375
+ object = ρσ_str(object)
376
+ elif transformer is 'a':
377
+ object = ρσ_repr(object)
378
+ # Dispatch to __format__ when defined and no transformer was applied
379
+ if not transformer and object is not None and object is not undefined and v'typeof object.__format__ === "function"':
380
+ ans = object.__format__(format_spec or '')
381
+ else:
382
+ ans = '' + object
383
+ if format_spec:
384
+ ans = apply_formatting(ans, format_spec)
373
385
  if ends_with_equal:
374
386
  ans = f'{key}={ans}'
375
387
  return ans
@@ -693,6 +705,12 @@ define_str_func('split', def(sep, maxsplit):
693
705
  ans.push(extra)
694
706
  return ρσ_list_decorate(ans)
695
707
  )
708
+ # Patch String.prototype.split so that no-argument calls (text.split()) use Python
709
+ # semantics (split on whitespace) rather than JS semantics (return [text]).
710
+ String.prototype.split = def(sep, limit):
711
+ if sep is undefined:
712
+ return ρσ_str.prototype.split.call(this)
713
+ return ρσ_orig_split(this, sep, limit)
696
714
 
697
715
  define_str_func('rsplit', def(sep, maxsplit):
698
716
  if not maxsplit:
@@ -774,6 +792,29 @@ define_str_func('zfill', def(width):
774
792
  return string
775
793
  )
776
794
 
795
+ define_str_func('expandtabs', def(tabsize):
796
+ if tabsize is undefined:
797
+ tabsize = 8
798
+ string = this
799
+ ans = ''
800
+ col = 0
801
+ for v'var i = 0; i < string.length; i++':
802
+ ch = string[i] # noqa:undef
803
+ if ch is '\t':
804
+ if tabsize > 0:
805
+ spaces = tabsize - (col % tabsize)
806
+ ans += v'new Array(spaces + 1).join(" ")'
807
+ col += spaces
808
+ # tabsize <= 0: tab adds no spaces, col stays
809
+ elif ch is '\n' or ch is '\r':
810
+ ans += ch
811
+ col = 0
812
+ else:
813
+ ans += ch
814
+ col += 1
815
+ return ans
816
+ )
817
+
777
818
  ρσ_str.uchrs = def(string, with_positions):
778
819
  # Return iterator over unicode chars in string. Will yield the unicode
779
820
  # replacement char U+FFFD for broken surrogate pairs
@@ -836,4 +877,5 @@ def ρσ_format(value, spec):
836
877
  return ρσ_str(value)
837
878
  return str.format('{:' + spec + '}', value)
838
879
 
880
+ ρσ_str.__name__ = 'str'
839
881
  v'var str = ρσ_str, repr = ρσ_repr, format = ρσ_format'