rapydscript-ns 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +39 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  8. package/PYTHON_FEATURE_COVERAGE.md +106 -15
  9. package/README.md +831 -52
  10. package/TODO.md +4 -286
  11. package/add-toc-to-readme +2 -2
  12. package/bin/export +75 -75
  13. package/bin/rapydscript +70 -70
  14. package/bin/web-repl-export +102 -102
  15. package/build +2 -2
  16. package/language-service/index.js +4623 -0
  17. package/language-service/language-service.d.ts +40 -0
  18. package/package.json +9 -7
  19. package/publish.py +37 -37
  20. package/release/baselib-plain-pretty.js +2006 -229
  21. package/release/baselib-plain-ugly.js +70 -3
  22. package/release/compiler.js +11554 -3870
  23. package/release/signatures.json +31 -29
  24. package/session.vim +4 -4
  25. package/setup.cfg +2 -2
  26. package/src/ast.pyj +93 -1
  27. package/src/baselib-builtins.pyj +99 -2
  28. package/src/baselib-containers.pyj +107 -4
  29. package/src/baselib-errors.pyj +44 -0
  30. package/src/baselib-internal.pyj +124 -5
  31. package/src/baselib-itertools.pyj +97 -97
  32. package/src/baselib-str.pyj +32 -1
  33. package/src/compiler.pyj +36 -36
  34. package/src/errors.pyj +30 -30
  35. package/src/lib/aes.pyj +646 -646
  36. package/src/lib/collections.pyj +1 -1
  37. package/src/lib/copy.pyj +120 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/gettext.pyj +569 -569
  41. package/src/lib/itertools.pyj +580 -580
  42. package/src/lib/math.pyj +193 -193
  43. package/src/lib/numpy.pyj +10 -10
  44. package/src/lib/operator.pyj +11 -11
  45. package/src/lib/pythonize.pyj +20 -20
  46. package/src/lib/random.pyj +118 -118
  47. package/src/lib/re.pyj +470 -470
  48. package/src/lib/react.pyj +74 -0
  49. package/src/lib/traceback.pyj +63 -63
  50. package/src/lib/uuid.pyj +77 -77
  51. package/src/monaco-language-service/analyzer.js +131 -9
  52. package/src/monaco-language-service/builtins.js +17 -2
  53. package/src/monaco-language-service/completions.js +170 -1
  54. package/src/monaco-language-service/diagnostics.js +25 -3
  55. package/src/monaco-language-service/dts.js +550 -550
  56. package/src/monaco-language-service/index.js +17 -0
  57. package/src/monaco-language-service/scope.js +3 -0
  58. package/src/output/classes.pyj +128 -11
  59. package/src/output/codegen.pyj +17 -3
  60. package/src/output/comments.pyj +45 -45
  61. package/src/output/exceptions.pyj +201 -105
  62. package/src/output/functions.pyj +13 -16
  63. package/src/output/jsx.pyj +164 -0
  64. package/src/output/literals.pyj +28 -2
  65. package/src/output/loops.pyj +0 -9
  66. package/src/output/modules.pyj +2 -5
  67. package/src/output/operators.pyj +22 -2
  68. package/src/output/statements.pyj +2 -2
  69. package/src/output/stream.pyj +1 -13
  70. package/src/output/treeshake.pyj +182 -182
  71. package/src/output/utils.pyj +72 -72
  72. package/src/parse.pyj +434 -114
  73. package/src/string_interpolation.pyj +72 -72
  74. package/src/tokenizer.pyj +29 -0
  75. package/src/unicode_aliases.pyj +576 -576
  76. package/src/utils.pyj +192 -192
  77. package/test/_import_one.pyj +37 -37
  78. package/test/_import_two/__init__.pyj +11 -11
  79. package/test/_import_two/level2/deep.pyj +4 -4
  80. package/test/_import_two/other.pyj +6 -6
  81. package/test/_import_two/sub.pyj +13 -13
  82. package/test/aes_vectors.pyj +421 -421
  83. package/test/annotations.pyj +80 -80
  84. package/test/baselib.pyj +4 -4
  85. package/test/classes.pyj +56 -17
  86. package/test/collections.pyj +5 -5
  87. package/test/decorators.pyj +77 -77
  88. package/test/docstrings.pyj +39 -39
  89. package/test/elementmaker_test.pyj +45 -45
  90. package/test/functions.pyj +151 -151
  91. package/test/generators.pyj +41 -41
  92. package/test/generic.pyj +370 -370
  93. package/test/imports.pyj +72 -72
  94. package/test/internationalization.pyj +73 -73
  95. package/test/lint.pyj +164 -164
  96. package/test/loops.pyj +85 -85
  97. package/test/numpy.pyj +734 -734
  98. package/test/omit_function_metadata.pyj +20 -20
  99. package/test/python_compat.pyj +326 -0
  100. package/test/python_features.pyj +129 -29
  101. package/test/regexp.pyj +55 -55
  102. package/test/repl.pyj +121 -121
  103. package/test/scoped_flags.pyj +76 -76
  104. package/test/slice.pyj +105 -0
  105. package/test/str.pyj +25 -0
  106. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  107. package/test/unit/index.js +2296 -71
  108. package/test/unit/language-service-builtins.js +70 -0
  109. package/test/unit/language-service-bundle.js +5 -5
  110. package/test/unit/language-service-completions.js +180 -0
  111. package/test/unit/language-service-dts.js +543 -543
  112. package/test/unit/language-service-hover.js +455 -455
  113. package/test/unit/language-service-index.js +350 -0
  114. package/test/unit/language-service-scope.js +255 -0
  115. package/test/unit/language-service.js +625 -4
  116. package/test/unit/run-language-service.js +1 -0
  117. package/test/unit/web-repl.js +437 -0
  118. package/tools/build-language-service.js +2 -2
  119. package/tools/cli.js +547 -547
  120. package/tools/compile.js +219 -219
  121. package/tools/compiler.js +0 -24
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +251 -251
  124. package/tools/export.js +3 -37
  125. package/tools/gettext.js +185 -185
  126. package/tools/ini.js +65 -65
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -74
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -254
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +227 -139
  139. package/web-repl/sha1.js +25 -25
  140. package/hack_demo.pyj +0 -112
  141. package/web-repl/language-service.js +0 -4187
@@ -1,20 +1,20 @@
1
- # globals: RapydScript
2
-
3
- # Test that omit_function_metadata flag suppresses __module__ and __argnames__
4
- code = 'def foo(a, b):\n return a + b'
5
- ast = RapydScript.parse(code, {'filename': '<test>'})
6
-
7
- # Without flag: output should contain __module__ and __argnames__
8
- output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
9
- ast.print(output_with)
10
- result_with = output_with.get()
11
- assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
12
- assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
13
-
14
- # With flag: output should not contain __module__, __argnames__, or Object.defineProperties
15
- output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
16
- ast.print(output_without)
17
- result_without = output_without.get()
18
- assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
19
- assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
20
- assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
1
+ # globals: RapydScript
2
+
3
+ # Test that omit_function_metadata flag suppresses __module__ and __argnames__
4
+ code = 'def foo(a, b):\n return a + b'
5
+ ast = RapydScript.parse(code, {'filename': '<test>'})
6
+
7
+ # Without flag: output should contain __module__ and __argnames__
8
+ output_with = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true})'
9
+ ast.print(output_with)
10
+ result_with = output_with.get()
11
+ assrt.ok(result_with.indexOf('__module__') >= 0, '__module__ should appear in default output')
12
+ assrt.ok(result_with.indexOf('__argnames__') >= 0, '__argnames__ should appear in default output')
13
+
14
+ # With flag: output should not contain __module__, __argnames__, or Object.defineProperties
15
+ output_without = v'new RapydScript.OutputStream({"beautify": true, "omit_baselib": true, "omit_function_metadata": true})'
16
+ ast.print(output_without)
17
+ result_without = output_without.get()
18
+ assrt.ok(result_without.indexOf('__module__') is -1, '__module__ should not appear when omit_function_metadata is set')
19
+ assrt.ok(result_without.indexOf('__argnames__') is -1, '__argnames__ should not appear when omit_function_metadata is set')
20
+ assrt.ok(result_without.indexOf('Object.defineProperties') is -1, 'Object.defineProperties should not appear when omit_function_metadata is set')
@@ -0,0 +1,326 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # python_compat.pyj
5
+ # Tests for places where RapydScript intentionally differs from Python.
6
+ # Each section documents the Python behavior, the RapydScript behavior, and
7
+ # asserts that RapydScript does what the README says it does.
8
+
9
+ ae = assrt.equal
10
+ ade = assrt.deepEqual
11
+ ok = assrt.ok
12
+
13
+ # ── 1. Function argument leniency — too few arguments ────────────────────────
14
+ # Python: raises TypeError("f() missing 1 required positional argument: 'b'")
15
+ # RapydScript: extra parameters are undefined instead of raising TypeError.
16
+
17
+ def _f_two_args(a, b):
18
+ return b
19
+
20
+ ae(_f_two_args(1), undefined) # b is undefined
21
+ ae(_f_two_args(1, 2), 2) # normal call still works
22
+
23
+ def _f_default(a, b=99):
24
+ return b
25
+
26
+ ae(_f_default(1), 99) # default value used when b not passed
27
+ ae(_f_default(1, 7), 7)
28
+
29
+ # ── 2. Function argument leniency — too many arguments ───────────────────────
30
+ # Python: raises TypeError("f() takes 1 positional argument but 3 were given")
31
+ # RapydScript: extra positional arguments are silently discarded.
32
+
33
+ def _f_one_arg(a):
34
+ return a
35
+
36
+ ae(_f_one_arg(42, 99, 'extra'), 42) # extras silently ignored
37
+
38
+ # ── 3. Positional-only parameter enforcement — lenient ───────────────────────
39
+ # Python: greet(name="Alice") raises TypeError when name is positional-only.
40
+ # RapydScript: the named keyword arg is silently ignored; the parameter gets
41
+ # its positional value or undefined if not provided positionally.
42
+
43
+ def _po_only(a, b, /):
44
+ return [a, b]
45
+
46
+ ade(_po_only(1, 2), [1, 2]) # normal positional call works
47
+
48
+ # Passing 'b' by keyword: keyword is silently ignored, b gets undefined.
49
+ result3 = _po_only(1, b=2)
50
+ ae(result3[0], 1)
51
+ ae(result3[1], undefined) # named value was silently ignored
52
+
53
+ # ── 4. Keyword-only parameter enforcement — lenient ──────────────────────────
54
+ # Python: f(1, 2) raises TypeError when b is keyword-only (after bare *).
55
+ # RapydScript: positional value is accepted without error.
56
+
57
+ def _kw_only(a, *, b=10):
58
+ return [a, b]
59
+
60
+ ade(_kw_only(1, b=99), [1, 99]) # normal keyword call works
61
+ ade(_kw_only(1), [1, 10]) # default value used
62
+
63
+ # Passing keyword-only param positionally: no TypeError in RapydScript.
64
+ # The extra positional arg is silently discarded; b gets its default value.
65
+ # (Python would raise TypeError here.)
66
+ _kw_result = None
67
+ _kw_error = None
68
+ try:
69
+ _kw_result = _kw_only(1, 99)
70
+ except TypeError as e:
71
+ _kw_error = e
72
+ ok(_kw_error is None) # no TypeError raised
73
+ ae(_kw_result[0], 1)
74
+ ae(_kw_result[1], 10) # default used; positional 99 was discarded
75
+
76
+ # ── 5. `is` operator — maps to JavaScript === ────────────────────────────────
77
+ # Python: `is` checks object identity (id(a) == id(b)).
78
+ # RapydScript: `is` compiles to ===, which is strict value equality for primitives.
79
+ # Most cases behave identically, but NaN is a notable divergence.
80
+
81
+ ae(1 is 1, True)
82
+ ae('hello' is 'hello', True)
83
+ ae(None is None, True)
84
+
85
+ # NaN: RapydScript special-cases `x is NaN` → compiles to isNaN(x).
86
+ # This is a deliberate deviation from `===` to make NaN checks useful.
87
+ # (Plain `NaN === NaN` is always false in JS, so `is NaN` would be useless.)
88
+ ok(42 / 'x' is NaN) # compiles to: isNaN(42 / 'x') → true
89
+ ok(42 is not NaN) # compiles to: !isNaN(42) → true
90
+
91
+ # ── 6. Arithmetic coercion: number + string ───────────────────────────────────
92
+ # Python: 1 + '1' raises TypeError.
93
+ # RapydScript: delegates to JavaScript which coerces 1 to '1', giving '11'.
94
+
95
+ ae(1 + '1', '11')
96
+ ae('prefix_' + 42, 'prefix_42')
97
+
98
+ # ── 7. List + list concatenation ─────────────────────────────────────────────
99
+ # Python: [1] + [2] returns [1, 2].
100
+ # RapydScript: list + list returns a new concatenated list (Python semantics).
101
+ # The + operator compiles to ρσ_list_add which uses Array.concat when both sides are arrays.
102
+
103
+ ade([1] + [2], [1, 2])
104
+ ade([1, 2] + [3, 4], [1, 2, 3, 4])
105
+
106
+ # Variables holding lists also work:
107
+ _a7 = [1, 2]
108
+ _b7 = [3, 4]
109
+ ade(_a7 + _b7, [1, 2, 3, 4])
110
+
111
+ # += extends the list in-place (Python semantics — same object is mutated):
112
+ _a8 = [1, 2]
113
+ _b8 = _a8
114
+ _a8 += [3, 4]
115
+ ade(_a8, [1, 2, 3, 4])
116
+ ade(_b8, [1, 2, 3, 4]) # _b8 still points to same object, now extended
117
+
118
+ # ── 8. Ordering operators on lists — JS string coercion ──────────────────────
119
+ # Python: [10] < [9] compares element-wise → False (10 > 9).
120
+ # RapydScript: < coerces to string → '10' < '9' is True (char comparison).
121
+ # This is a common gotcha: single-element lists with multi-digit numbers.
122
+
123
+ # RapydScript lists have a Python-style __str__, so [10].toString() = '[10]'.
124
+ # String compare of '[10]' vs '[9]': '[' = '[', then '1' < '9' → [10] < [9] is True.
125
+ ok([10] < [9]) # True in RapydScript (string: '[10]' < '[9]')
126
+ ae([9] < [10], False) # False (string: '[9]' > '[10]')
127
+
128
+ # Compare: Python uses numeric element-wise ordering, so [10] > [9] (10 > 9).
129
+ # RapydScript gives the opposite result for these specific values.
130
+
131
+ # For correct numeric comparisons, compare elements explicitly:
132
+ ok(10 > 9) # direct numeric comparison always works
133
+
134
+ # ── 9. List sort() — Python numeric sort (jssort() for JS lexicographic) ──────
135
+ # list.sort() now does Python-style numeric ordering by default (in-place).
136
+ # list.jssort() gives the native JS sort (lexicographic, for JS interop).
137
+
138
+ lst_py = [10, 9, 2]
139
+ lst_py.sort() # Python-style numeric sort
140
+ ade(lst_py, [2, 9, 10])
141
+
142
+ # sort with key and reverse also work:
143
+ lst_rev = [3, 1, 2]
144
+ lst_rev.sort(reverse=True)
145
+ ade(lst_rev, [3, 2, 1])
146
+
147
+ # jssort() is the native JS lexicographic sort (for JS interop):
148
+ lst_js = [10, 9, 2]
149
+ lst_js.jssort() # JS sort: compares as strings '10', '9', '2'
150
+ ade(lst_js, [10, 2, 9]) # lexicographic: '1' < '2' < '9'
151
+
152
+ # ── 10. List pop() — Python bounds-checked pop (jspop() for JS native) ────────
153
+ # list.pop(i) now behaves like Python: raises IndexError for out-of-bounds index.
154
+ # list.jspop() is the native JS pop — no bounds check, always pops the last element.
155
+
156
+ lst10 = [1, 2, 3]
157
+ ae(lst10.pop(), 3) # Python pop: removes and returns last element
158
+ ade(lst10, [1, 2])
159
+
160
+ lst10b = [1, 2, 3]
161
+ ae(lst10b.pop(0), 1) # Python pop with index: removes and returns element at 0
162
+ ade(lst10b, [2, 3])
163
+
164
+ # pop raises IndexError for out-of-bounds:
165
+ _caught10 = False
166
+ try:
167
+ [1].pop(99)
168
+ except IndexError:
169
+ _caught10 = True
170
+ ok(_caught10)
171
+
172
+ # jspop() is the native JS pop — ignores arguments, always pops the last element:
173
+ lst10c = [1, 2, 3]
174
+ ae(lst10c.jspop(), 3) # JS pop: always removes and returns the last element
175
+ ade(lst10c, [1, 2])
176
+
177
+ # ── 11. Default dict (JS object) — missing key returns undefined ──────────────
178
+ # Python: d['missing'] raises KeyError.
179
+ # RapydScript (no dict_literals): missing key returns undefined.
180
+
181
+ d11 = {'a': 1, 'b': 2}
182
+ ae(d11['a'], 1)
183
+ ae(d11['missing'], undefined) # no KeyError
184
+
185
+ # With dict_literals, Python behavior is enabled:
186
+ def _test_keyerror():
187
+ from __python__ import dict_literals, overload_getitem
188
+ d = {'a': 1}
189
+ caught = False
190
+ try:
191
+ x = d['missing']
192
+ except KeyError:
193
+ caught = True
194
+ ok(caught)
195
+ _test_keyerror()
196
+
197
+ # ── 12. Default dict — numeric keys auto-converted to strings ─────────────────
198
+ # Python: {1: 'one'} keeps integer key 1.
199
+ # RapydScript JS object: numeric keys become strings. d[1] and d['1'] are the same.
200
+
201
+ d12 = {}
202
+ d12[1] = 'one'
203
+ ae(d12[1], 'one')
204
+ ae(d12['1'], 'one') # same slot — number keys coerced to string
205
+
206
+ # ── 13. Default dict — keys accessible as attributes ─────────────────────────
207
+ # Python dicts: d.foo is always an AttributeError (use d['foo'] or d.get('foo')).
208
+ # RapydScript JS object: d.foo and d['foo'] access the same slot.
209
+
210
+ d13 = {'hello': 42, 'world': 99}
211
+ ae(d13.hello, 42)
212
+ ae(d13['hello'], 42)
213
+ d13.newkey = 'set via attr'
214
+ ae(d13['newkey'], 'set via attr')
215
+
216
+ # ── 14. String methods not on string instances by default ─────────────────────
217
+ # Python: 'hello'.strip() works directly on the string.
218
+ # RapydScript (default): Python string methods live on the `str` object,
219
+ # not on string instances. Native JS methods (upper, toLowerCase, etc.) do work.
220
+
221
+ s14 = ' hello '
222
+ ae(str.strip(s14), 'hello') # Python method via str object
223
+ ae(s14.trim(), 'hello') # JS native trim() works on instances
224
+
225
+ s14b = 'hello world'
226
+ ae(str.upper(s14b), 'HELLO WORLD') # Python via str
227
+ ae(s14b.toUpperCase(), 'HELLO WORLD') # JS native
228
+
229
+ # After strings(), Python methods work on instances:
230
+ from pythonize import strings
231
+ strings()
232
+
233
+ ae(' hello '.strip(), 'hello')
234
+ ae('hello world'.upper(), 'HELLO WORLD')
235
+
236
+ # ── 15. strings() leaves split() and replace() as JS versions ─────────────────
237
+ # (strings() is active from section 14 above)
238
+ # Python str.split() with no args: splits on any whitespace, strips leading/trailing.
239
+ # JS String.prototype.split() with no args: returns the whole string in a 1-element array.
240
+
241
+ # Python semantics via str module:
242
+ ade(str.split('a b'), ['a', 'b']) # Python split on any whitespace
243
+ ade(str.split('a b c'), ['a', 'b', 'c'])
244
+
245
+ # After strings(), .split() is STILL the JS version (by design):
246
+ ade('a b'.split(), ['a b']) # JS: no-arg split = one-element array
247
+ ade('a b'.split(' '), ['a', 'b']) # JS split with separator still works
248
+
249
+ # Python str.replace() vs JS String.prototype.replace():
250
+ # Python replaces ALL occurrences by default.
251
+ # JS .replace() with a string pattern only replaces the FIRST occurrence.
252
+ ae(str.replace('aaa', 'a', 'b'), 'bbb') # Python: replaces all
253
+ ae('aaa'.replace('a', 'b'), 'baa') # JS: replaces only first
254
+
255
+ # ── 16. Method binding — not automatic ──────────────────────────────────────
256
+ # Python: bound methods carry a reference to self.
257
+ # RapydScript (default): methods are unbound; extracting a method loses self.
258
+
259
+ class _Bindable:
260
+ def __init__(self, value):
261
+ self.value = value
262
+ def get_value(self):
263
+ return self.value if self else None # guard: self may be falsy if unbound
264
+
265
+ obj16 = _Bindable(55)
266
+ ae(obj16.get_value(), 55) # direct call works
267
+
268
+ unbound = obj16.get_value
269
+ ae(unbound(), None) # self is not obj16; guard returns None
270
+
271
+ # With bound_methods flag, extraction works correctly:
272
+ class _AutoBound:
273
+ from __python__ import bound_methods
274
+ def __init__(self, value):
275
+ self.value = value
276
+ def get_value(self):
277
+ return self.value
278
+
279
+ obj16b = _AutoBound(77)
280
+ bound_fn = obj16b.get_value
281
+ ae(bound_fn(), 77) # self is correctly bound to obj16b
282
+
283
+ # ── 17. `global` keyword — prefers outer scope over module global ─────────────
284
+ # Python: `global x` inside a nested function always refers to the module-level x.
285
+ # RapydScript: if x also exists in an intermediate outer scope, that outer scope
286
+ # variable is used instead of the module-level one.
287
+ #
288
+ # NOTE: This is a documented incompatibility. The README says:
289
+ # "if a variable is present both in the outer scope and the global scope,
290
+ # the variable from the outer scope will be used"
291
+ #
292
+ # SKIP: This behavior is inherently tied to how JavaScript closures work and is
293
+ # difficult to exercise meaningfully in a compiled-to-JS test environment,
294
+ # because the "global" and "outer" scopes collapse to different things
295
+ # depending on context. The behavior is documented but not testable with a
296
+ # simple assertion here without knowing the exact generated JS scope.
297
+ #
298
+ # Example (would be run in a context where x is BOTH a global and a local):
299
+ # _x17 = 'global_value'
300
+ # def _outer17():
301
+ # _x17 = 'outer_value'
302
+ # def _inner17():
303
+ # global _x17
304
+ # return _x17
305
+ # return _inner17()
306
+ # # Python: _outer17() == 'global_value' (global keyword forces module level)
307
+ # # RapydScript: _outer17() == 'outer_value' (outer scope wins)
308
+
309
+ # ── 18. Truthiness — JS defaults ─────────────────────────────────────────────
310
+ # Tested more fully in python_features.pyj #40, but demonstrating JS defaults here.
311
+ # Without `from __python__ import truthiness`:
312
+
313
+ _empty_list = []
314
+ _empty_dict = {}
315
+
316
+ # In JS, empty arrays and objects are truthy:
317
+ ok(_empty_list) # True (JS truthiness: any object is truthy)
318
+ ok(_empty_dict) # True (JS truthiness: any object is truthy)
319
+
320
+ # Enable Python truthiness:
321
+ from __python__ import truthiness
322
+
323
+ ok(not []) # False (Python semantics)
324
+ ok(not {}) # False (Python semantics)
325
+ ok([1]) # True
326
+ ok({'a': 1}) # True
@@ -228,6 +228,9 @@ ae(str.isspace('a '), False)
228
228
  ae(str.casefold('Hello World'), 'hello world')
229
229
  ae(str.casefold('ABC'), 'abc')
230
230
  ae(str.casefold('already'), 'already')
231
+ # Unicode: Latin capital letters with diacritics fold to lowercase equivalents
232
+ ae(str.casefold('ÄÖÜ'), 'äöü')
233
+ ae(str.casefold('ÄÖÜ'), str.casefold('äöü'))
231
234
 
232
235
  # ── 12. str.removeprefix() / str.removesuffix() ──────────────────────────────
233
236
  # STATUS: ✓ WORKS (added in this release)
@@ -496,7 +499,7 @@ _test_raise_from_none()
496
499
  # STATUS: ✗ NOT ENFORCED — __slots__ is accepted but does not restrict attrs.
497
500
 
498
501
  # ── 24. Nested classes ───────────────────────────────────────────────────────
499
- # STATUS: NOT SUPPORTED see comment in test/classes.pyj
502
+ # STATUS: WORKSadded in commit 44c9802; tested fully in test/classes.pyj
500
503
 
501
504
  # ── 25. Generator .throw() ───────────────────────────────────────────────────
502
505
  # STATUS: ✓ WORKS
@@ -937,21 +940,29 @@ ae(_c(), 3)
937
940
  # STATUS: ✗ NOT SUPPORTED — no attribute-lookup override.
938
941
 
939
942
  # ── 50. __format__ dunder ────────────────────────────────────────────────────
940
- # STATUS: NOT SUPPORTED — format(obj, spec) is not a defined builtin;
941
- # __format__ is not dispatched.
942
- # SKIP:
943
- # class _Fmt:
944
- # def __format__(self, spec):
945
- # return '<' + spec + '>'
946
- # ae(format(_Fmt(), 'test'), '<test>')
943
+ # STATUS: WORKS — format(obj, spec) dispatches to obj.__format__(spec).
944
+ # Tested fully in test/str.pyj.
947
945
 
948
946
  # ── 51. __class_getitem__ ────────────────────────────────────────────────────
949
- # STATUS: NOT SUPPORTED no generic subscript MyClass[T] syntax.
950
- # SKIP:
951
- # class _Box:
952
- # def __class_getitem__(cls, item):
953
- # return cls
954
- # ok(_Box[int] is _Box)
947
+ # STATUS: WORKSClass[item] dispatches to __class_getitem__(cls, item).
948
+ # Subclasses inherit __class_getitem__; cls is set to the calling class.
949
+
950
+ class _Box51:
951
+ def __class_getitem__(cls, item):
952
+ return cls
953
+
954
+ ok(_Box51[int] is _Box51)
955
+
956
+ class _BoxNamed51:
957
+ def __class_getitem__(cls, item):
958
+ return cls.__name__ + '[' + str(item) + ']'
959
+
960
+ ok(_BoxNamed51[42] == '_BoxNamed51[42]')
961
+
962
+ class _BoxChild51(_BoxNamed51):
963
+ pass
964
+
965
+ ok(_BoxChild51[99] == '_BoxChild51[99]')
955
966
 
956
967
  # ── 52. __init_subclass__ ────────────────────────────────────────────────────
957
968
  # STATUS: ✗ NOT SUPPORTED — no subclass hook.
@@ -1087,9 +1098,9 @@ _check_unhashable({1, 2})
1087
1098
  _check_unhashable({'a': 1})
1088
1099
 
1089
1100
  # ── 55. format(value, spec) builtin ──────────────────────────────────────────
1090
- # STATUS: NOT IMPLEMENTED — format(value, spec) builtin is not defined.
1091
- # str.format() and f-strings work fine.
1092
- # SKIP:
1101
+ # STATUS: WORKS — format(value, spec) is defined and dispatches to __format__.
1102
+ # Tested fully in test/str.pyj.
1103
+ #
1093
1104
  # ae(format(3.14159, '.2f'), '3.14')
1094
1105
 
1095
1106
  # ── 56. next(iterator[, default]) builtin ────────────────────────────────────
@@ -1132,20 +1143,60 @@ ae(next(g), 200)
1132
1143
  ae(next(g, 'stop'), 'stop')
1133
1144
 
1134
1145
  # ── 57. iter(callable, sentinel) two-arg form ────────────────────────────────
1135
- # STATUS: NOT IMPLEMENTED — two-argument iter(callable, sentinel) not supported.
1136
- # SKIP:
1137
- # count = [0]
1138
- # def counter():
1139
- # count[0] += 1; return count[0]
1140
- # ae(list(iter(counter, 3)), [1, 2])
1146
+ # Basic usage: collect values until sentinel
1147
+ _count57 = [0]
1148
+ def _counter57():
1149
+ _count57[0] += 1
1150
+ return _count57[0]
1151
+ ade(list(iter(_counter57, 3)), [1, 2])
1152
+ # Sentinel never reached: stops at exact match
1153
+ _vals57 = ['a', 'b', 'c', None]
1154
+ _idx57 = [0]
1155
+ def _src57():
1156
+ v = _vals57[_idx57[0]]
1157
+ _idx57[0] += 1
1158
+ return v
1159
+ ade(list(iter(_src57, None)), ['a', 'b', 'c'])
1160
+ # Sentinel on first call returns empty iterator
1161
+ _first57 = [0]
1162
+ def _fn57():
1163
+ _first57[0] += 1
1164
+ return 'stop'
1165
+ ade(list(iter(_fn57, 'stop')), [])
1166
+ # Works with for-loop
1167
+ _out57 = []
1168
+ _i57 = [0]
1169
+ def _seq57():
1170
+ _i57[0] += 1
1171
+ return _i57[0]
1172
+ for _v57 in iter(_seq57, 4):
1173
+ _out57.append(_v57)
1174
+ ade(_out57, [1, 2, 3])
1175
+ # Works with next()
1176
+ _j57 = [0]
1177
+ def _f57():
1178
+ _j57[0] += 1
1179
+ return _j57[0]
1180
+ _it57 = iter(_f57, 5)
1181
+ ae(next(_it57), 1)
1182
+ ae(next(_it57), 2)
1183
+ ae(next(_it57), 3)
1184
+ ae(next(_it57), 4)
1185
+ ae(next(_it57, 'done'), 'done')
1186
+ # Callable object with __call__
1187
+ class _Caller57:
1188
+ def __init__(self):
1189
+ self.n = 0
1190
+ def __call__(self):
1191
+ self.n += 1
1192
+ return self.n
1193
+ _caller57 = _Caller57()
1194
+ ade(list(iter(_caller57, 3)), [1, 2])
1141
1195
 
1142
1196
  # ── 58. slice() as builtin object ────────────────────────────────────────────
1143
- # STATUS: NOT IMPLEMENTED slice() constructor is not a builtin.
1144
- # SKIP:
1145
- # s = slice(1, 5, 2)
1146
- # ae(s.start, 1)
1147
- # ae(s.stop, 5)
1148
- # ae(s.step, 2)
1197
+ # STATUS: WORKSimplemented (see item #63 below for full tests).
1198
+ # NOTE: This item was previously marked NOT IMPLEMENTED but was added in a
1199
+ # recent release. Full tests live in item #63 and test/slice.pyj.
1149
1200
 
1150
1201
  # ── 59. complex() builtin ────────────────────────────────────────────────────
1151
1202
  # STATUS: ✗ NOT SUPPORTED — no complex number type.
@@ -1182,3 +1233,52 @@ def _ko(a, *, b):
1182
1233
 
1183
1234
  ade(_ko(1, b=2), [1, 2])
1184
1235
  ade(_ko(b=2, a=1), [1, 2])
1236
+
1237
+ # ── 63. slice() builtin ───────────────────────────────────────────────────────
1238
+ # STATUS: ✓ WORKS
1239
+ # Subscript read/write tests (lst[s], lst[s]=val) are in test/slice.pyj
1240
+ # because they require `from __python__ import overload_getitem`.
1241
+
1242
+ # Construction forms
1243
+ _s1 = slice(5)
1244
+ ae(_s1.start, None)
1245
+ ae(_s1.stop, 5)
1246
+ ae(_s1.step, None)
1247
+
1248
+ _s2 = slice(2, 8)
1249
+ ae(_s2.start, 2)
1250
+ ae(_s2.stop, 8)
1251
+ ae(_s2.step, None)
1252
+
1253
+ _s3 = slice(1, 9, 2)
1254
+ ae(_s3.start, 1)
1255
+ ae(_s3.stop, 9)
1256
+ ae(_s3.step, 2)
1257
+
1258
+ # None values preserved
1259
+ _sn = slice(None, None, -1)
1260
+ ae(_sn.start, None)
1261
+ ae(_sn.stop, None)
1262
+ ae(_sn.step, -1)
1263
+
1264
+ # indices() method
1265
+ ade(_s2.indices(10), [2, 8, 1])
1266
+ ade(slice(None, None, 1).indices(5), [0, 5, 1])
1267
+ ade(slice(-3, -1).indices(10), [7, 9, 1])
1268
+ ade(slice(None, None, -1).indices(5), [4, -1, -1])
1269
+
1270
+ # repr / str
1271
+ ae(str(slice(1, 5, None)), 'slice(1, 5, None)')
1272
+ ae(str(slice(None, 5, 2)), 'slice(None, 5, 2)')
1273
+
1274
+ # Equality
1275
+ ok(slice(1, 5) == slice(1, 5))
1276
+ ok(not (slice(1, 5) == slice(1, 6)))
1277
+
1278
+ # isinstance
1279
+ ok(isinstance(slice(1, 5), slice))
1280
+
1281
+ # Delete with slice object uses ρσ_delitem (no overload_getitem needed)
1282
+ _b = [0, 1, 2, 3, 4]
1283
+ del _b[slice(1, 3)]
1284
+ ade(_b, [0, 3, 4])