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
@@ -119,6 +119,70 @@ function run_js(js) {
119
119
  });
120
120
  }
121
121
 
122
+ // Minimal React stub — mirrors the one in web-repl/env.js so that compiled
123
+ // code that imports from the react library can run in a plain Node vm context.
124
+ var REACT_STUB = (function () {
125
+ var Fragment = Symbol("React.Fragment");
126
+ function createElement(type, props) {
127
+ var children = Array.prototype.slice.call(arguments, 2);
128
+ return { type: type, props: Object.assign({ children: children.length === 1 ? children[0] : children }, props) };
129
+ }
130
+ function useState(initial) {
131
+ var s = typeof initial === "function" ? initial() : initial;
132
+ return [s, function (v) { s = typeof v === "function" ? v(s) : v; }];
133
+ }
134
+ function useEffect(fn) { fn(); }
135
+ function useLayoutEffect(fn) { fn(); }
136
+ function useMemo(fn) { return fn(); }
137
+ function useCallback(fn) { return fn; }
138
+ function useRef(initial) { return { current: initial }; }
139
+ function useContext(ctx) { return ctx && ctx._currentValue !== undefined ? ctx._currentValue : undefined; }
140
+ function useReducer(reducer, initial) { return [initial, function (a) {}]; }
141
+ function useImperativeHandle(ref, create) { if (ref) ref.current = create(); }
142
+ function useDebugValue() {}
143
+ function useId() { return ":r" + (Math.random() * 1e9 | 0) + ":"; }
144
+ function useTransition() { return [false, function (fn) { fn(); }]; }
145
+ function useDeferredValue(v) { return v; }
146
+ function useSyncExternalStore(sub, get) { return get(); }
147
+ function useInsertionEffect(fn) { fn(); }
148
+ function createContext(def) { return { _currentValue: def }; }
149
+ function createRef() { return { current: null }; }
150
+ function forwardRef(render) { return function (props) { return render(props, null); }; }
151
+ function memo(C) { return C; }
152
+ function lazy(f) { return f; }
153
+ function cloneElement(el, props) { return Object.assign({}, el, { props: Object.assign({}, el.props, props) }); }
154
+ function isValidElement(o) { return o !== null && typeof o === "object" && o.type !== undefined; }
155
+ function Component() {}
156
+ Component.prototype.setState = function (u) { this.state = Object.assign({}, this.state, typeof u === "function" ? u(this.state) : u); };
157
+ function PureComponent() {}
158
+ PureComponent.prototype = Object.create(Component.prototype);
159
+ PureComponent.prototype.constructor = PureComponent;
160
+ return {
161
+ Fragment: Fragment, createElement: createElement,
162
+ useState: useState, useEffect: useEffect, useLayoutEffect: useLayoutEffect,
163
+ useMemo: useMemo, useCallback: useCallback, useRef: useRef,
164
+ useContext: useContext, useReducer: useReducer,
165
+ useImperativeHandle: useImperativeHandle, useDebugValue: useDebugValue,
166
+ useId: useId, useTransition: useTransition, useDeferredValue: useDeferredValue,
167
+ useSyncExternalStore: useSyncExternalStore, useInsertionEffect: useInsertionEffect,
168
+ createContext: createContext, createRef: createRef, forwardRef: forwardRef,
169
+ memo: memo, lazy: lazy, cloneElement: cloneElement, isValidElement: isValidElement,
170
+ Component: Component, PureComponent: PureComponent,
171
+ StrictMode: {}, Suspense: {}, Profiler: {},
172
+ };
173
+ })();
174
+
175
+ // Run compiled JS in a vm context that also has a React stub available.
176
+ function run_js_with_react(js) {
177
+ return vm.runInNewContext(js, {
178
+ __name__ : "<test>",
179
+ console : console,
180
+ assrt : assert,
181
+ ρσ_last_exception : undefined,
182
+ React : REACT_STUB,
183
+ });
184
+ }
185
+
122
186
  // ---------------------------------------------------------------------------
123
187
  // Tests
124
188
  // ---------------------------------------------------------------------------
@@ -555,6 +619,41 @@ var TESTS = [
555
619
  },
556
620
  },
557
621
 
622
+ {
623
+ name: "bundle_tuple_annotation_variable",
624
+ description: "variable type annotation with tuple does not become a function call",
625
+ run: function () {
626
+ var repl = RS.web_repl();
627
+ var js = bundle_compile(repl, [
628
+ "# globals: assrt",
629
+ "range_limits: tuple = (0, 100)",
630
+ "assrt.equal(range_limits[0], 0)",
631
+ "assrt.equal(range_limits[1], 100)",
632
+ ].join("\n"));
633
+ // Ensure it compiled to an assignment, not a call
634
+ if (/range_limits\s*\(/.test(js)) throw new Error("range_limits was compiled as a function call:\n" + js);
635
+ run_js(js);
636
+ },
637
+ },
638
+
639
+ {
640
+ name: "bundle_tuple_annotation_function_arg",
641
+ description: "function argument type annotation with tuple and int works at runtime",
642
+ run: function () {
643
+ var repl = RS.web_repl();
644
+ var js = bundle_compile(repl, [
645
+ "# globals: assrt",
646
+ "def clamp(val: int, bounds: tuple) -> int:",
647
+ " lo, hi = bounds",
648
+ " return max(lo, min(hi, val))",
649
+ "range_limits: tuple = (0, 100)",
650
+ "assrt.equal(clamp(150, range_limits), 100)",
651
+ "assrt.equal(clamp(-5, range_limits), 0)",
652
+ ].join("\n"));
653
+ run_js(js);
654
+ },
655
+ },
656
+
558
657
  {
559
658
  name: "bundle_list_concatenation",
560
659
  description: "list + list returns a concatenated list in the bundled baselib",
@@ -579,6 +678,1013 @@ var TESTS = [
579
678
  },
580
679
  },
581
680
 
681
+ // ── React stub tests ─────────────────────────────────────────────────────
682
+ // These tests require a React mock in the vm context because the compiled
683
+ // react.pyj module initialises with `React.*` references at load time.
684
+
685
+ {
686
+ name: "bundle_react_stub_memo",
687
+ description: "web-repl React stub: memo() returns its argument (identity wrapper)",
688
+ run: function () {
689
+ var repl = RS.web_repl();
690
+ var js = bundle_compile(repl, [
691
+ "from react import memo",
692
+ "def MyComp(props): return props.x",
693
+ "Memoised = memo(MyComp)",
694
+ "assrt.equal(Memoised, MyComp)",
695
+ ].join("\n"));
696
+ run_js_with_react(js);
697
+ },
698
+ },
699
+
700
+ {
701
+ name: "bundle_react_stub_usestate",
702
+ description: "web-repl React stub: useState returns [initialValue, setter]",
703
+ run: function () {
704
+ var repl = RS.web_repl();
705
+ var js = bundle_compile(repl, [
706
+ "from react import useState",
707
+ "state, setState = useState(42)",
708
+ "assrt.equal(state, 42)",
709
+ "assrt.equal(jstype(setState), 'function')",
710
+ ].join("\n"));
711
+ run_js_with_react(js);
712
+ },
713
+ },
714
+
715
+ {
716
+ name: "bundle_react_stub_useeffect",
717
+ description: "web-repl React stub: useEffect calls its callback immediately",
718
+ run: function () {
719
+ var repl = RS.web_repl();
720
+ var js = bundle_compile(repl, [
721
+ "from react import useEffect",
722
+ "ran = [False]",
723
+ "def effect(): ran[0] = True",
724
+ "useEffect(effect, [])",
725
+ "assrt.ok(ran[0])",
726
+ ].join("\n"));
727
+ run_js_with_react(js);
728
+ },
729
+ },
730
+
731
+ {
732
+ name: "bundle_react_stub_usememo",
733
+ description: "web-repl React stub: useMemo returns the computed value",
734
+ run: function () {
735
+ var repl = RS.web_repl();
736
+ var js = bundle_compile(repl, [
737
+ "from react import useMemo",
738
+ "result = useMemo(def(): return 6 * 7;, [])",
739
+ "assrt.equal(result, 42)",
740
+ ].join("\n"));
741
+ run_js_with_react(js);
742
+ },
743
+ },
744
+
745
+ {
746
+ name: "bundle_react_stub_useref",
747
+ description: "web-repl React stub: useRef returns {current: initialValue}",
748
+ run: function () {
749
+ var repl = RS.web_repl();
750
+ var js = bundle_compile(repl, [
751
+ "from react import useRef",
752
+ "ref = useRef(None)",
753
+ "assrt.equal(ref.current, None)",
754
+ "ref.current = 'hello'",
755
+ "assrt.equal(ref.current, 'hello')",
756
+ ].join("\n"));
757
+ run_js_with_react(js);
758
+ },
759
+ },
760
+
761
+ {
762
+ name: "bundle_react_stub_usereducer",
763
+ description: "web-repl React stub: useReducer returns [initialState, dispatch]",
764
+ run: function () {
765
+ var repl = RS.web_repl();
766
+ var js = bundle_compile(repl, [
767
+ "from react import useReducer",
768
+ "def reducer(state, action):",
769
+ " if action == 'inc': return state + 1",
770
+ " return state",
771
+ "state, dispatch = useReducer(reducer, 0)",
772
+ "assrt.equal(state, 0)",
773
+ "assrt.equal(jstype(dispatch), 'function')",
774
+ ].join("\n"));
775
+ run_js_with_react(js);
776
+ },
777
+ },
778
+
779
+ {
780
+ name: "bundle_react_stub_createcontext",
781
+ description: "web-repl React stub: createContext returns a context with default value",
782
+ run: function () {
783
+ var repl = RS.web_repl();
784
+ var js = bundle_compile(repl, [
785
+ "from react import createContext, useContext",
786
+ "ThemeCtx = createContext('dark')",
787
+ "theme = useContext(ThemeCtx)",
788
+ "assrt.equal(theme, 'dark')",
789
+ ].join("\n"));
790
+ run_js_with_react(js);
791
+ },
792
+ },
793
+
794
+ {
795
+ name: "bundle_react_stub_forwardref",
796
+ description: "web-repl React stub: forwardRef wraps the render function",
797
+ run: function () {
798
+ var repl = RS.web_repl();
799
+ var js = bundle_compile(repl, [
800
+ "from react import forwardRef",
801
+ "def render(props, ref): return props['x']",
802
+ "FancyInput = forwardRef(render)",
803
+ "assrt.equal(jstype(FancyInput), 'function')",
804
+ "assrt.equal(FancyInput({'x': 99}), 99)",
805
+ ].join("\n"));
806
+ run_js_with_react(js);
807
+ },
808
+ },
809
+
810
+ {
811
+ name: "bundle_react_stub_jsx_and_memo",
812
+ description: "web-repl React stub: JSX component wrapped with memo compiles and runs",
813
+ run: function () {
814
+ var repl = RS.web_repl();
815
+ var js = bundle_compile(repl, [
816
+ "from __python__ import jsx",
817
+ "from react import useState, memo",
818
+ "def Counter(props):",
819
+ " count, setCount = useState(props.initial or 0)",
820
+ " return <span>{count}</span>",
821
+ "MemoCounter = memo(Counter)",
822
+ "assrt.equal(jstype(MemoCounter), 'function')",
823
+ "el = MemoCounter({'initial': 7})",
824
+ "assrt.equal(el.type, 'span')",
825
+ ].join("\n"));
826
+ run_js_with_react(js);
827
+ },
828
+ },
829
+
830
+ // ── __import__() ─────────────────────────────────────────────────────
831
+
832
+ {
833
+ name: "bundle___import__-basic",
834
+ description: "__import__(name) returns a stdlib module reference in the web-repl bundle",
835
+ run: function () {
836
+ var repl = RS.web_repl();
837
+ var js = bundle_compile(repl, [
838
+ "from collections import Counter",
839
+ "m = __import__('collections')",
840
+ // m.Counter should be the same object as the statically-imported Counter
841
+ "assrt.ok(m.Counter is Counter)",
842
+ "c = m.Counter('aabb')",
843
+ "top = c.most_common(1)",
844
+ "assrt.equal(top[0][0], 'a')",
845
+ "assrt.equal(top[0][1], 2)",
846
+ ].join("\n"));
847
+ run_js(js);
848
+ },
849
+ },
850
+
851
+ {
852
+ name: "bundle___import__-fromlist",
853
+ description: "__import__ with fromlist returns the exact named module",
854
+ run: function () {
855
+ var repl = RS.web_repl();
856
+ var js = bundle_compile(repl, [
857
+ "from collections import deque",
858
+ "m = __import__('collections', None, None, ['deque'])",
859
+ "d = m.deque([1, 2, 3])",
860
+ "assrt.equal(len(d), 3)",
861
+ "assrt.equal(d.popleft(), 1)",
862
+ ].join("\n"));
863
+ run_js(js);
864
+ },
865
+ },
866
+
867
+ // ── bytes / bytearray ────────────────────────────────────────────────────
868
+
869
+ {
870
+ name: "bundle_bytes_basic",
871
+ description: "bytes() construction and basic operations work in bundled baselib",
872
+ run: function () {
873
+ var repl = RS.web_repl();
874
+ var js = bundle_compile(repl, [
875
+ "b = bytes([72, 101, 108, 108, 111])",
876
+ "assrt.equal(len(b), 5)",
877
+ "assrt.equal(b[0], 72)",
878
+ "assrt.equal(b[-1], 111)",
879
+ "assrt.ok(isinstance(b, bytes))",
880
+ ].join("\n"));
881
+ run_js(js);
882
+ },
883
+ },
884
+
885
+ {
886
+ name: "bundle_bytes_from_string",
887
+ description: "bytes(str, encoding) encodes a string to UTF-8 bytes in bundled baselib",
888
+ run: function () {
889
+ var repl = RS.web_repl();
890
+ var js = bundle_compile(repl, [
891
+ "b = bytes('Hello', 'utf-8')",
892
+ "assrt.equal(len(b), 5)",
893
+ "assrt.equal(b[0], 72)",
894
+ "assrt.equal(b.decode('utf-8'), 'Hello')",
895
+ ].join("\n"));
896
+ run_js(js);
897
+ },
898
+ },
899
+
900
+ {
901
+ name: "bundle_bytes_hex",
902
+ description: "bytes.hex() and bytes.fromhex() round-trip in bundled baselib",
903
+ run: function () {
904
+ var repl = RS.web_repl();
905
+ var js = bundle_compile(repl, [
906
+ "b = bytes([0, 15, 255])",
907
+ "assrt.equal(b.hex(), '000fff')",
908
+ "b2 = bytes.fromhex('000fff')",
909
+ "assrt.ok(b == b2)",
910
+ ].join("\n"));
911
+ run_js(js);
912
+ },
913
+ },
914
+
915
+ {
916
+ name: "bundle_bytes_slice",
917
+ description: "bytes slice returns bytes in bundled baselib",
918
+ run: function () {
919
+ var repl = RS.web_repl();
920
+ var js = bundle_compile(repl, [
921
+ "b = bytes([10, 20, 30, 40, 50])",
922
+ "s = b[1:4]",
923
+ "assrt.ok(isinstance(s, bytes))",
924
+ "assrt.equal(len(s), 3)",
925
+ "assrt.equal(s[0], 20)",
926
+ "assrt.equal(s[2], 40)",
927
+ ].join("\n"));
928
+ run_js(js);
929
+ },
930
+ },
931
+
932
+ {
933
+ name: "bundle_bytearray_mutation",
934
+ description: "bytearray append/extend/pop work in bundled baselib",
935
+ run: function () {
936
+ var repl = RS.web_repl();
937
+ var js = bundle_compile(repl, [
938
+ "ba = bytearray([1, 2, 3])",
939
+ "assrt.ok(isinstance(ba, bytearray))",
940
+ "ba.append(4)",
941
+ "assrt.equal(len(ba), 4)",
942
+ "assrt.equal(ba[3], 4)",
943
+ "p = ba.pop()",
944
+ "assrt.equal(p, 4)",
945
+ "assrt.equal(len(ba), 3)",
946
+ ].join("\n"));
947
+ run_js(js);
948
+ },
949
+ },
950
+
951
+ {
952
+ name: "bundle_bytes_repr",
953
+ description: "repr(bytes(...)) returns b'...' notation in bundled baselib",
954
+ run: function () {
955
+ var repl = RS.web_repl();
956
+ var js = bundle_compile(repl, [
957
+ "b = bytes([72, 101, 108, 108, 111])",
958
+ "assrt.equal(repr(b), \"b'Hello'\")",
959
+ ].join("\n"));
960
+ run_js(js);
961
+ },
962
+ },
963
+
964
+ {
965
+ name: "bundle_bytes_literal_syntax",
966
+ description: "b'...' bytes literal compiles and runs correctly in bundled compiler",
967
+ run: function () {
968
+ var repl = RS.web_repl();
969
+ var js = bundle_compile(repl, [
970
+ "b = b'Hello'",
971
+ "assrt.ok(isinstance(b, bytes))",
972
+ "assrt.equal(len(b), 5)",
973
+ "assrt.equal(b[0], 72)",
974
+ "assrt.equal(b[4], 111)",
975
+ "assrt.ok(b == bytes([72, 101, 108, 108, 111]))",
976
+ "# hex escapes",
977
+ "b2 = b'\\x48\\x65\\x6c\\x6c\\x6f'",
978
+ "assrt.ok(b2 == b)",
979
+ "# empty literal",
980
+ "e = b''",
981
+ "assrt.equal(len(e), 0)",
982
+ "# adjacent literal concatenation",
983
+ "c = b'foo' b'bar'",
984
+ "assrt.equal(len(c), 6)",
985
+ "assrt.ok(c == bytes([102, 111, 111, 98, 97, 114]))",
986
+ "# uppercase B prefix",
987
+ "u = B'ABC'",
988
+ "assrt.ok(isinstance(u, bytes))",
989
+ "assrt.equal(u[0], 65)",
990
+ "# repr shows b'...' notation",
991
+ "assrt.equal(repr(b'Hi'), \"b'Hi'\")",
992
+ ].join("\n"));
993
+ run_js(js);
994
+ },
995
+ },
996
+
997
+ {
998
+ name: "bundle___import__-error",
999
+ description: "__import__ raises ModuleNotFoundError for unknown module in web-repl",
1000
+ run: function () {
1001
+ var repl = RS.web_repl();
1002
+ var js = bundle_compile(repl, [
1003
+ "from collections import Counter",
1004
+ "caught = False",
1005
+ "try:",
1006
+ " __import__('no_such_module')",
1007
+ "except ModuleNotFoundError as e:",
1008
+ " caught = True",
1009
+ "assrt.ok(caught)",
1010
+ ].join("\n"));
1011
+ run_js(js);
1012
+ },
1013
+ },
1014
+
1015
+ // ── Enum ─────────────────────────────────────────────────────────────────
1016
+
1017
+ {
1018
+ name: "bundle_enum_basic",
1019
+ description: "Enum subclass .name and .value attributes work in bundled baselib",
1020
+ run: function () {
1021
+ var repl = RS.web_repl();
1022
+ var js = bundle_compile(repl, [
1023
+ "from enum import Enum",
1024
+ "class Color(Enum):",
1025
+ " RED = 1",
1026
+ " GREEN = 2",
1027
+ " BLUE = 3",
1028
+ "assrt.equal(Color.RED.name, 'RED')",
1029
+ "assrt.equal(Color.RED.value, 1)",
1030
+ "assrt.equal(Color.GREEN.name, 'GREEN')",
1031
+ "assrt.equal(Color.GREEN.value, 2)",
1032
+ "assrt.equal(Color.BLUE.name, 'BLUE')",
1033
+ "assrt.equal(Color.BLUE.value, 3)",
1034
+ ].join("\n"));
1035
+ run_js(js);
1036
+ },
1037
+ },
1038
+
1039
+ {
1040
+ name: "bundle_enum_identity",
1041
+ description: "Enum members are singletons — Color.RED is Color.RED",
1042
+ run: function () {
1043
+ var repl = RS.web_repl();
1044
+ var js = bundle_compile(repl, [
1045
+ "from enum import Enum",
1046
+ "class Color(Enum):",
1047
+ " RED = 1",
1048
+ " GREEN = 2",
1049
+ "assrt.ok(Color.RED is Color.RED)",
1050
+ "assrt.ok(Color.RED is not Color.GREEN)",
1051
+ ].join("\n"));
1052
+ run_js(js);
1053
+ },
1054
+ },
1055
+
1056
+ {
1057
+ name: "bundle_enum_iteration",
1058
+ description: "list(Color) and for-loop over an Enum class yield all members in order",
1059
+ run: function () {
1060
+ var repl = RS.web_repl();
1061
+ var js = bundle_compile(repl, [
1062
+ "from enum import Enum",
1063
+ "class Color(Enum):",
1064
+ " RED = 1",
1065
+ " GREEN = 2",
1066
+ " BLUE = 3",
1067
+ "members = list(Color)",
1068
+ "assrt.equal(len(members), 3)",
1069
+ "assrt.equal(members[0].name, 'RED')",
1070
+ "assrt.equal(members[1].name, 'GREEN')",
1071
+ "assrt.equal(members[2].name, 'BLUE')",
1072
+ "names = []",
1073
+ "for m in Color:",
1074
+ " names.push(m.name)",
1075
+ "assrt.deepEqual(names, ['RED', 'GREEN', 'BLUE'])",
1076
+ ].join("\n"));
1077
+ run_js(js);
1078
+ },
1079
+ },
1080
+
1081
+ {
1082
+ name: "bundle_enum_isinstance",
1083
+ description: "isinstance(Color.RED, Color) and isinstance(Color.RED, Enum) both return True",
1084
+ run: function () {
1085
+ var repl = RS.web_repl();
1086
+ var js = bundle_compile(repl, [
1087
+ "from enum import Enum",
1088
+ "class Color(Enum):",
1089
+ " RED = 1",
1090
+ "assrt.ok(isinstance(Color.RED, Color))",
1091
+ "assrt.ok(isinstance(Color.RED, Enum))",
1092
+ "assrt.ok(not isinstance(1, Color))",
1093
+ ].join("\n"));
1094
+ run_js(js);
1095
+ },
1096
+ },
1097
+
1098
+ {
1099
+ name: "bundle_enum_repr_str",
1100
+ description: "repr() and str() of an enum member produce the expected strings",
1101
+ run: function () {
1102
+ var repl = RS.web_repl();
1103
+ var js = bundle_compile(repl, [
1104
+ "from enum import Enum",
1105
+ "class Color(Enum):",
1106
+ " RED = 1",
1107
+ "assrt.equal(repr(Color.RED), '<Color.RED: 1>')",
1108
+ "assrt.equal(str(Color.RED), 'Color.RED')",
1109
+ ].join("\n"));
1110
+ run_js(js);
1111
+ },
1112
+ },
1113
+
1114
+ {
1115
+ name: "bundle_dataclasses_basic",
1116
+ description: "@dataclass generates __init__, __repr__, __eq__ in bundled baselib",
1117
+ run: function () {
1118
+ var repl = RS.web_repl();
1119
+ var js = bundle_compile(repl, [
1120
+ "from dataclasses import dataclass, field, is_dataclass",
1121
+ "@dataclass",
1122
+ "class Point:",
1123
+ " x: int",
1124
+ " y: int = 0",
1125
+ "p = Point(3, 4)",
1126
+ "assrt.equal(p.x, 3)",
1127
+ "assrt.equal(p.y, 4)",
1128
+ "assrt.ok(p == Point(3, 4))",
1129
+ "assrt.ok(p is not Point(3, 4))",
1130
+ "assrt.equal(repr(p), 'Point(x=3, y=4)')",
1131
+ "assrt.ok(is_dataclass(p))",
1132
+ "assrt.ok(is_dataclass(Point))",
1133
+ ].join("\n"));
1134
+ run_js(js);
1135
+ },
1136
+ },
1137
+
1138
+ {
1139
+ name: "bundle_dataclasses_field_factory",
1140
+ description: "field(default_factory=...) gives each instance its own mutable default",
1141
+ run: function () {
1142
+ var repl = RS.web_repl();
1143
+ var js = bundle_compile(repl, [
1144
+ "from dataclasses import dataclass, field",
1145
+ "@dataclass",
1146
+ "class Container:",
1147
+ " items: list = field(default_factory=list)",
1148
+ "a = Container()",
1149
+ "b = Container()",
1150
+ "a.items.push(1)",
1151
+ "assrt.equal(a.items.length, 1)",
1152
+ "assrt.equal(b.items.length, 0)",
1153
+ ].join("\n"));
1154
+ run_js(js);
1155
+ },
1156
+ },
1157
+
1158
+ {
1159
+ name: "bundle_dataclasses_asdict",
1160
+ description: "asdict() recursively converts a dataclass instance to a plain dict",
1161
+ run: function () {
1162
+ var repl = RS.web_repl();
1163
+ var js = bundle_compile(repl, [
1164
+ "from dataclasses import dataclass, asdict",
1165
+ "@dataclass",
1166
+ "class Inner:",
1167
+ " value: int",
1168
+ "@dataclass",
1169
+ "class Outer:",
1170
+ " inner: object",
1171
+ " tag: str",
1172
+ "d = asdict(Outer(Inner(42), 'hello'))",
1173
+ "assrt.equal(d['tag'], 'hello')",
1174
+ "assrt.equal(d['inner']['value'], 42)",
1175
+ ].join("\n"));
1176
+ run_js(js);
1177
+ },
1178
+ },
1179
+
1180
+ {
1181
+ name: "bundle_dataclasses_frozen",
1182
+ description: "frozen=True makes fields read-only (writable:false, assignment silently ignored outside strict mode)",
1183
+ run: function () {
1184
+ var repl = RS.web_repl();
1185
+ var js = bundle_compile(repl, [
1186
+ "from dataclasses import dataclass",
1187
+ "@dataclass(frozen=True)",
1188
+ "class FP:",
1189
+ " x: int",
1190
+ " y: int",
1191
+ "fp = FP(1, 2)",
1192
+ "assrt.equal(fp.x, 1)",
1193
+ "assrt.equal(fp.y, 2)",
1194
+ "fp.x = 99",
1195
+ // In non-strict mode, writable:false assignment is silently ignored
1196
+ "assrt.equal(fp.x, 1)",
1197
+ ].join("\n"));
1198
+ run_js(js);
1199
+ },
1200
+ },
1201
+
1202
+ {
1203
+ name: "bundle_format_dunder",
1204
+ description: "__format__ dunder dispatches from format(), str.format(), and f-strings in web-repl bundle",
1205
+ run: function () {
1206
+ var repl = RS.web_repl();
1207
+ var js = bundle_compile(repl, [
1208
+ "class Money:",
1209
+ " def __init__(self, amount):",
1210
+ " self.amount = amount",
1211
+ " def __str__(self):",
1212
+ " return str(self.amount)",
1213
+ " def __format__(self, spec):",
1214
+ " if spec == 'usd':",
1215
+ " return '$' + str(self.amount)",
1216
+ " return format(self.amount, spec)",
1217
+ "m = Money(42)",
1218
+ "assrt.equal(format(m, 'usd'), '$42')",
1219
+ "assrt.equal(str.format('{:usd}', m), '$42')",
1220
+ "assrt.equal(f'{m:usd}', '$42')",
1221
+ "assrt.equal(format(m, '.2f'), '42.00')",
1222
+ "assrt.equal(f'{m:.2f}', '42.00')",
1223
+ // Default __format__ (no custom __format__ defined)
1224
+ "class Plain:",
1225
+ " def __str__(self):",
1226
+ " return 'plain'",
1227
+ "p = Plain()",
1228
+ "assrt.equal(format(p), 'plain')",
1229
+ "assrt.equal(str.format('{}', p), 'plain')",
1230
+ "assrt.equal(f'{p}', 'plain')",
1231
+ ].join("\n"));
1232
+ run_js(js);
1233
+ },
1234
+ },
1235
+
1236
+ // ── object() builtin ──────────────────────────────────────────────────────
1237
+
1238
+ {
1239
+ name: "bundle_object_sentinel",
1240
+ description: "object() returns distinct instances usable as sentinels",
1241
+ run: function () {
1242
+ var repl = RS.web_repl();
1243
+ var js = bundle_compile(repl, [
1244
+ "s1 = object()",
1245
+ "s2 = object()",
1246
+ "assrt.ok(s1 is not s2)",
1247
+ "assrt.ok(isinstance(s1, object))",
1248
+ "assrt.ok(isinstance(s2, object))",
1249
+ "h1 = hash(s1)",
1250
+ "h2 = hash(s2)",
1251
+ "assrt.ok(h1 is not h2)",
1252
+ "assrt.equal(hash(s1), h1)",
1253
+ ].join("\n"));
1254
+ run_js(js);
1255
+ },
1256
+ },
1257
+
1258
+ {
1259
+ name: "bundle_object_subclass",
1260
+ description: "class Foo(object) works and isinstance checks succeed",
1261
+ run: function () {
1262
+ var repl = RS.web_repl();
1263
+ var js = bundle_compile(repl, [
1264
+ "class Node(object):",
1265
+ " def __init__(self, val):",
1266
+ " self.val = val",
1267
+ "n = Node(42)",
1268
+ "assrt.ok(isinstance(n, Node))",
1269
+ "assrt.ok(isinstance(n, object))",
1270
+ "assrt.equal(n.val, 42)",
1271
+ ].join("\n"));
1272
+ run_js(js);
1273
+ },
1274
+ },
1275
+
1276
+ // ── float.is_integer() ───────────────────────────────────────────────────
1277
+
1278
+ {
1279
+ name: "bundle_float_is_integer",
1280
+ description: "float.is_integer() returns True for whole numbers and False for fractions/inf/nan",
1281
+ run: function () {
1282
+ var repl = RS.web_repl();
1283
+ var js = bundle_compile(repl, [
1284
+ "assrt.equal((1.0).is_integer(), True)",
1285
+ "assrt.equal((1.5).is_integer(), False)",
1286
+ "assrt.equal((0.0).is_integer(), True)",
1287
+ "assrt.equal((-2.0).is_integer(), True)",
1288
+ "assrt.equal((-2.5).is_integer(), False)",
1289
+ "assrt.equal((1e10).is_integer(), True)",
1290
+ "assrt.equal(v'Infinity'.is_integer(), False)",
1291
+ "assrt.equal(v'NaN'.is_integer(), False)",
1292
+ ].join("\n"));
1293
+ run_js(js);
1294
+ },
1295
+ },
1296
+
1297
+ // ── int.bit_length() ─────────────────────────────────────────────────────
1298
+
1299
+ {
1300
+ name: "bundle_int_bit_length",
1301
+ description: "int.bit_length() returns the number of bits needed to represent the integer",
1302
+ run: function () {
1303
+ var repl = RS.web_repl();
1304
+ var js = bundle_compile(repl, [
1305
+ "assrt.equal((0).bit_length(), 0)",
1306
+ "assrt.equal((1).bit_length(), 1)",
1307
+ "assrt.equal((255).bit_length(), 8)",
1308
+ "assrt.equal((256).bit_length(), 9)",
1309
+ "assrt.equal((1023).bit_length(), 10)",
1310
+ "assrt.equal((1024).bit_length(), 11)",
1311
+ "assrt.equal((-1).bit_length(), 1)",
1312
+ "assrt.equal((-5).bit_length(), 3)",
1313
+ "assrt.equal((-255).bit_length(), 8)",
1314
+ ].join("\n"));
1315
+ run_js(js);
1316
+ },
1317
+ },
1318
+
1319
+ // ── abc module ───────────────────────────────────────────────────────────
1320
+
1321
+ {
1322
+ name: "bundle_abc_basic",
1323
+ description: "ABC + @abstractmethod raises TypeError when abstract class is instantiated",
1324
+ run: function () {
1325
+ var repl = RS.web_repl();
1326
+ var js = bundle_compile(repl, [
1327
+ "from abc import ABC, abstractmethod",
1328
+ "class Shape(ABC):",
1329
+ " @abstractmethod",
1330
+ " def area(self): pass",
1331
+ "raised = False",
1332
+ "try:",
1333
+ " Shape()",
1334
+ "except TypeError:",
1335
+ " raised = True",
1336
+ "assrt.ok(raised)",
1337
+ "assrt.ok('area' in Shape.__abstractmethods__)",
1338
+ "assrt.equal(len(Shape.__abstractmethods__), 1)",
1339
+ ].join("\n"));
1340
+ run_js(js);
1341
+ },
1342
+ },
1343
+
1344
+ {
1345
+ name: "bundle_abc_concrete",
1346
+ description: "Concrete ABC subclass can be instantiated; isinstance sees the full chain",
1347
+ run: function () {
1348
+ var repl = RS.web_repl();
1349
+ var js = bundle_compile(repl, [
1350
+ "from abc import ABC, abstractmethod",
1351
+ "class Shape(ABC):",
1352
+ " @abstractmethod",
1353
+ " def area(self): pass",
1354
+ "class Circle(Shape):",
1355
+ " def __init__(self, r):",
1356
+ " self.r = r",
1357
+ " def area(self):",
1358
+ " return 3.14159 * self.r * self.r",
1359
+ "c = Circle(5)",
1360
+ "assrt.ok(isinstance(c, Circle))",
1361
+ "assrt.ok(isinstance(c, Shape))",
1362
+ "assrt.ok(isinstance(c, ABC))",
1363
+ "assrt.equal(c.r, 5)",
1364
+ "assrt.ok(abs(c.area() - 78.53975) < 0.001)",
1365
+ "assrt.equal(len(Circle.__abstractmethods__), 0)",
1366
+ ].join("\n"));
1367
+ run_js(js);
1368
+ },
1369
+ },
1370
+
1371
+ {
1372
+ name: "bundle_abc_register",
1373
+ description: "ABC.register() makes isinstance return True for virtual subclasses",
1374
+ run: function () {
1375
+ var repl = RS.web_repl();
1376
+ var js = bundle_compile(repl, [
1377
+ "from abc import ABC, abstractmethod",
1378
+ "class MyABC(ABC):",
1379
+ " @abstractmethod",
1380
+ " def do_it(self): pass",
1381
+ "class External:",
1382
+ " def do_it(self): return 42",
1383
+ "MyABC.register(External)",
1384
+ "assrt.ok(isinstance(External(), MyABC))",
1385
+ "class Unrelated:",
1386
+ " def do_it(self): return 99",
1387
+ "assrt.ok(not isinstance(Unrelated(), MyABC))",
1388
+ ].join("\n"));
1389
+ run_js(js);
1390
+ },
1391
+ },
1392
+
1393
+ {
1394
+ name: "bundle_abc_protocol",
1395
+ description: "@runtime_checkable Protocol enables structural isinstance() checks",
1396
+ run: function () {
1397
+ var repl = RS.web_repl();
1398
+ var js = bundle_compile(repl, [
1399
+ "from abc import Protocol, runtime_checkable",
1400
+ "@runtime_checkable",
1401
+ "class Drawable(Protocol):",
1402
+ " def draw(self): pass",
1403
+ "class Canvas:",
1404
+ " def draw(self):",
1405
+ " return 'painting'",
1406
+ "class NotDrawable:",
1407
+ " def paint(self): pass",
1408
+ "assrt.ok(isinstance(Canvas(), Drawable))",
1409
+ "assrt.ok(not isinstance(NotDrawable(), Drawable))",
1410
+ "assrt.ok(not isinstance(42, Drawable))",
1411
+ "assrt.ok('draw' in Drawable.__protocol_attrs__)",
1412
+ ].join("\n"));
1413
+ run_js(js);
1414
+ },
1415
+ },
1416
+
1417
+ // ── eval / exec ───────────────────────────────────────────────────────
1418
+
1419
+ {
1420
+ name: "bundle_zip_strict",
1421
+ description: "zip(strict=True) raises ValueError on length mismatch in the web-repl bundle",
1422
+ run: function () {
1423
+ var repl = RS.web_repl();
1424
+ var js = bundle_compile(repl, [
1425
+ // equal-length: no error
1426
+ "assrt.deepEqual(list(zip([1,2], [3,4], strict=True)), [[1,3],[2,4]])",
1427
+ // zero iterables with strict: empty result, no error
1428
+ "assrt.deepEqual(list(zip(strict=True)), [])",
1429
+ // first longer than second
1430
+ "err1 = False",
1431
+ "try:",
1432
+ " list(zip([1,2], [3], strict=True))",
1433
+ "except ValueError:",
1434
+ " err1 = True",
1435
+ "assrt.ok(err1)",
1436
+ // second longer than first
1437
+ "err2 = False",
1438
+ "try:",
1439
+ " list(zip([1], [3,4], strict=True))",
1440
+ "except ValueError:",
1441
+ " err2 = True",
1442
+ "assrt.ok(err2)",
1443
+ ].join("\n"));
1444
+ run_js(js);
1445
+ },
1446
+ },
1447
+
1448
+ {
1449
+ name: "bundle_eval_exec",
1450
+ description: "eval and exec builtins work in the web-repl bundle",
1451
+ run: function () {
1452
+ var repl = RS.web_repl();
1453
+ var js = bundle_compile(repl, [
1454
+ // eval — basic expression
1455
+ "assrt.equal(eval('1 + 2'), 3)",
1456
+ "assrt.equal(eval('10 * 5'), 50)",
1457
+ "assrt.equal(eval('True'), True)",
1458
+ // eval with globals dict
1459
+ "assrt.equal(eval('x + y', {'x': 10, 'y': 5}), 15)",
1460
+ "assrt.equal(eval('a * b', {'a': 3, 'b': 4}), 12)",
1461
+ // eval with locals overriding globals
1462
+ "assrt.equal(eval('x', {'x': 1}, {'x': 99}), 99)",
1463
+ // exec returns None (null in JS)
1464
+ "assrt.equal(exec('1 + 2'), None)",
1465
+ // exec side-effects via mutable object in globals
1466
+ "log = []",
1467
+ "exec(\"log.push('hello')\", {'log': log})",
1468
+ "assrt.equal(log[0], 'hello')",
1469
+ "exec('log.push(1 + 2)', {'log': log})",
1470
+ "assrt.equal(log[1], 3)",
1471
+ // exec with function in globals
1472
+ "out = []",
1473
+ "def _add(a, b): out.push(a + b);",
1474
+ "exec('fn(10, 7)', {'fn': _add, 'out': out})",
1475
+ "assrt.equal(out[0], 17)",
1476
+ ].join("\n"));
1477
+ run_js(js);
1478
+ },
1479
+ },
1480
+
1481
+ // ── complex numbers ───────────────────────────────────────────────────────
1482
+
1483
+ {
1484
+ name: "bundle_complex_constructor",
1485
+ description: "complex() constructor and .real/.imag attributes work in bundled baselib",
1486
+ run: function () {
1487
+ var repl = RS.web_repl();
1488
+ var js = bundle_compile(repl, [
1489
+ "c = complex(3, 4)",
1490
+ "assrt.equal(c.real, 3)",
1491
+ "assrt.equal(c.imag, 4)",
1492
+ "c0 = complex()",
1493
+ "assrt.equal(c0.real, 0)",
1494
+ "assrt.equal(c0.imag, 0)",
1495
+ "c1 = complex(5)",
1496
+ "assrt.equal(c1.real, 5)",
1497
+ "assrt.equal(c1.imag, 0)",
1498
+ ].join("\n"));
1499
+ run_js(js);
1500
+ },
1501
+ },
1502
+
1503
+ {
1504
+ name: "bundle_complex_j_literal",
1505
+ description: "j-suffix imaginary literal compiles and runs correctly in bundled compiler",
1506
+ run: function () {
1507
+ var repl = RS.web_repl();
1508
+ var js = bundle_compile(repl, [
1509
+ "c = 4j",
1510
+ "assrt.equal(c.real, 0)",
1511
+ "assrt.equal(c.imag, 4)",
1512
+ "c2 = 3+4j",
1513
+ "assrt.equal(c2.real, 3)",
1514
+ "assrt.equal(c2.imag, 4)",
1515
+ "assrt.ok(isinstance(c, complex))",
1516
+ ].join("\n"));
1517
+ run_js(js);
1518
+ },
1519
+ },
1520
+
1521
+ {
1522
+ name: "bundle_complex_arithmetic",
1523
+ description: "complex arithmetic (+, -, *, /) and abs() work in bundled baselib",
1524
+ run: function () {
1525
+ var repl = RS.web_repl();
1526
+ var js = bundle_compile(repl, [
1527
+ "a = complex(1, 2)",
1528
+ "b = complex(3, 4)",
1529
+ "assrt.ok(a + b == complex(4, 6))",
1530
+ "assrt.ok(b - a == complex(2, 2))",
1531
+ "assrt.ok(a * b == complex(-5, 10))",
1532
+ "assrt.ok(b / complex(1, 0) == b)",
1533
+ "assrt.equal(abs(complex(3, 4)), 5)",
1534
+ "assrt.ok(-complex(3, 4) == complex(-3, -4))",
1535
+ "assrt.ok(complex(3, 4).conjugate() == complex(3, -4))",
1536
+ ].join("\n"));
1537
+ run_js(js);
1538
+ },
1539
+ },
1540
+
1541
+ {
1542
+ name: "bundle_complex_repr",
1543
+ description: "repr() and str() of complex produce Python-style notation in bundled baselib",
1544
+ run: function () {
1545
+ var repl = RS.web_repl();
1546
+ var js = bundle_compile(repl, [
1547
+ "assrt.equal(repr(complex(0, 0)), '0j')",
1548
+ "assrt.equal(repr(complex(1, 0)), '(1+0j)')",
1549
+ "assrt.equal(repr(complex(0, 1)), '1j')",
1550
+ "assrt.equal(repr(complex(3, 4)), '(3+4j)')",
1551
+ "assrt.equal(repr(complex(3, -4)), '(3-4j)')",
1552
+ "assrt.equal(str(complex(3, 4)), '(3+4j)')",
1553
+ ].join("\n"));
1554
+ run_js(js);
1555
+ },
1556
+ },
1557
+
1558
+ {
1559
+ name: "bundle_vars_locals_globals",
1560
+ description: "vars(), locals(), globals() work in bundled baselib",
1561
+ run: function () {
1562
+ var repl = RS.web_repl();
1563
+ var js = bundle_compile(repl, [
1564
+ "from __python__ import overload_getitem",
1565
+ "class Point:",
1566
+ " def __init__(self, x, y):",
1567
+ " self.x = x",
1568
+ " self.y = y",
1569
+ "p = Point(7, 9)",
1570
+ "d = vars(p)",
1571
+ "assrt.equal(d['x'], 7)",
1572
+ "assrt.equal(d['y'], 9)",
1573
+ // snapshot — mutation does not affect original
1574
+ "d['x'] = 0",
1575
+ "assrt.equal(p.x, 7)",
1576
+ // vars() with no arg returns empty dict
1577
+ "v = vars()",
1578
+ "assrt.ok(v is not None)",
1579
+ "assrt.ok(isinstance(v, dict))",
1580
+ // locals() returns empty dict
1581
+ "loc = locals()",
1582
+ "assrt.ok(isinstance(loc, dict))",
1583
+ // globals() returns dict
1584
+ "g = globals()",
1585
+ "assrt.ok(isinstance(g, dict))",
1586
+ // vars(obj) returns dict instance
1587
+ "assrt.ok(isinstance(vars(Point(1, 2)), dict))",
1588
+ ].join("\n"));
1589
+ run_js(js);
1590
+ },
1591
+ },
1592
+
1593
+ // -------------------------------------------------------------------------
1594
+ // Context-persistence tests: compile WITHOUT keep_baselib, run in the repl
1595
+ // vm context. These catch "ReferenceError: ρσ_X is not defined" bugs that
1596
+ // only surface when baselib symbols are `let`-declared (not `var` / function
1597
+ // declarations) and therefore don't persist between vm.runInContext calls.
1598
+ // -------------------------------------------------------------------------
1599
+
1600
+ {
1601
+ name: "repl_in_operator_persistence",
1602
+ description: "ρσ_in accessible after baselib init — 'in' operator regression in web-repl context",
1603
+ run: function () {
1604
+ var repl = RS.web_repl();
1605
+ // Compile WITHOUT keep_baselib so the output does not include the
1606
+ // baselib — the compiled code references ρσ_in from the persistent ctx.
1607
+ var js = repl.compile([
1608
+ "assert 'x' in ['x', 'y', 'z'], \"'x' in list should be True\"",
1609
+ "assert 'a' not in ['x', 'y', 'z'], \"'a' not in list should be True\"",
1610
+ "assert 2 in {1: 'one', 2: 'two'}, '2 in dict should be True'",
1611
+ // Original failing case: 'Math' in globals() must not throw
1612
+ "g = globals()",
1613
+ "result = 'Math' in g", // just must not throw ReferenceError
1614
+ ].join("\n"), {export_main: true, tree_shake: false});
1615
+ repl.runjs(js);
1616
+ },
1617
+ },
1618
+
1619
+ {
1620
+ name: "repl_kwargs_persistence",
1621
+ description: "ρσ_desugar_kwargs accessible after baselib init — **kwargs in web-repl context",
1622
+ run: function () {
1623
+ var repl = RS.web_repl();
1624
+ var js = repl.compile([
1625
+ "def greet(name, greeting='Hello'):",
1626
+ " return greeting + ', ' + name + '!'",
1627
+ "result = greet('World', greeting='Hi')",
1628
+ "assert result == 'Hi, World!', 'kwargs call should produce Hi, World!'",
1629
+ ].join("\n"), {export_main: true, tree_shake: false});
1630
+ repl.runjs(js);
1631
+ },
1632
+ },
1633
+
1634
+ {
1635
+ name: "repl_nameerror_persistence",
1636
+ description: "NameError accessible after baselib init — except NameError in web-repl context",
1637
+ run: function () {
1638
+ var repl = RS.web_repl();
1639
+ var js = repl.compile([
1640
+ "caught = False",
1641
+ "try:",
1642
+ " raise NameError('test error')",
1643
+ "except NameError:",
1644
+ " caught = True",
1645
+ "assert caught, 'NameError should be catchable in except clause'",
1646
+ ].join("\n"), {export_main: true, tree_shake: false});
1647
+ repl.runjs(js);
1648
+ },
1649
+ },
1650
+
1651
+ {
1652
+ name: "repl_getattr_persistence",
1653
+ description: "ρσ_JS_Proxy/ρσ_attr_proxy_handler accessible after baselib init — __getattr__ in web-repl context",
1654
+ run: function () {
1655
+ var repl = RS.web_repl();
1656
+ var js = repl.compile([
1657
+ "class Magic:",
1658
+ " def __getattr__(self, name):",
1659
+ " return name + '_value'",
1660
+ "m = Magic()",
1661
+ "assert m.foo == 'foo_value', '__getattr__ should return foo_value'",
1662
+ "assert m.bar == 'bar_value', '__getattr__ should return bar_value'",
1663
+ ].join("\n"), {export_main: true, tree_shake: false});
1664
+ repl.runjs(js);
1665
+ },
1666
+ },
1667
+
1668
+ {
1669
+ name: "repl_exists_persistence",
1670
+ description: "ρσ_exists accessible after baselib init — existential operator on non-SymbolRef in web-repl context",
1671
+ run: function () {
1672
+ var repl = RS.web_repl();
1673
+ var js = repl.compile([
1674
+ // fns['key'] is a subscript (not SymbolRef) — emits ρσ_exists.c(fns['key'])()
1675
+ "def get_val(): return 42",
1676
+ "fns = {'key': get_val}",
1677
+ "result = fns['key']?()",
1678
+ "assert result == 42, 'existential call via subscript should return 42'",
1679
+ // None value: dict lookup miss returns None, existential function call returns undefined
1680
+ "missing = fns.get('nokey')",
1681
+ "result2 = missing?()",
1682
+ "assert result2 is undefined, 'existential call on None should return undefined'",
1683
+ ].join("\n"), {export_main: true, tree_shake: false});
1684
+ repl.runjs(js);
1685
+ },
1686
+ },
1687
+
582
1688
  ];
583
1689
 
584
1690
  // ---------------------------------------------------------------------------