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.
- package/CHANGELOG.md +26 -0
- package/README.md +1351 -141
- package/TODO.md +12 -6
- package/language-service/index.js +184 -26
- package/package.json +1 -1
- package/release/baselib-plain-pretty.js +5895 -1928
- package/release/baselib-plain-ugly.js +140 -3
- package/release/compiler.js +16282 -5408
- package/release/signatures.json +25 -22
- package/src/ast.pyj +94 -1
- package/src/baselib-builtins.pyj +362 -3
- package/src/baselib-bytes.pyj +664 -0
- package/src/baselib-containers.pyj +99 -0
- package/src/baselib-errors.pyj +45 -1
- package/src/baselib-internal.pyj +346 -49
- package/src/baselib-itertools.pyj +17 -4
- package/src/baselib-str.pyj +46 -4
- package/src/lib/abc.pyj +317 -0
- package/src/lib/copy.pyj +120 -0
- package/src/lib/dataclasses.pyj +532 -0
- package/src/lib/enum.pyj +125 -0
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +35 -1
- package/src/lib/react.pyj +74 -0
- package/src/lib/typing.pyj +577 -0
- package/src/monaco-language-service/builtins.js +19 -4
- package/src/monaco-language-service/diagnostics.js +40 -19
- package/src/output/classes.pyj +161 -25
- package/src/output/codegen.pyj +16 -2
- package/src/output/exceptions.pyj +97 -1
- package/src/output/functions.pyj +87 -5
- package/src/output/jsx.pyj +164 -0
- package/src/output/literals.pyj +28 -2
- package/src/output/loops.pyj +5 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +108 -36
- package/src/output/statements.pyj +2 -2
- package/src/output/stream.pyj +1 -0
- package/src/parse.pyj +496 -128
- package/src/tokenizer.pyj +38 -4
- package/test/abc.pyj +291 -0
- package/test/arithmetic_nostrict.pyj +88 -0
- package/test/arithmetic_types.pyj +169 -0
- package/test/baselib.pyj +91 -0
- package/test/bytes.pyj +467 -0
- package/test/classes.pyj +1 -0
- package/test/comparison_ops.pyj +173 -0
- package/test/dataclasses.pyj +253 -0
- package/test/enum.pyj +134 -0
- package/test/eval_exec.pyj +56 -0
- package/test/format.pyj +148 -0
- package/test/object.pyj +64 -0
- package/test/python_compat.pyj +17 -15
- package/test/python_features.pyj +89 -21
- package/test/regexp.pyj +29 -1
- package/test/tuples.pyj +96 -0
- package/test/typing.pyj +469 -0
- package/test/unit/index.js +2292 -70
- package/test/unit/language-service.js +674 -4
- package/test/unit/web-repl.js +1106 -0
- package/test/vars_locals_globals.pyj +94 -0
- package/tools/cli.js +11 -0
- package/tools/compile.js +5 -0
- package/tools/embedded_compiler.js +15 -4
- package/tools/lint.js +16 -19
- package/tools/repl.js +1 -1
- package/web-repl/env.js +122 -0
- package/web-repl/main.js +1 -3
- package/web-repl/rapydscript.js +125 -3
- package/PYTHON_DIFFERENCES_REPORT.md +0 -291
- package/PYTHON_FEATURE_COVERAGE.md +0 -200
- package/hack_demo.pyj +0 -112
package/src/tokenizer.pyj
CHANGED
|
@@ -90,7 +90,7 @@ KEYWORDS_ATOM = "False None True"
|
|
|
90
90
|
# see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
|
|
91
91
|
RESERVED_WORDS = ("break case class catch const continue debugger default delete do else export extends"
|
|
92
92
|
" finally for function if import in instanceof new return switch this throw try typeof var void"
|
|
93
|
-
" while with yield
|
|
93
|
+
" while with yield implements static private package let public protected interface await null true false" )
|
|
94
94
|
|
|
95
95
|
KEYWORDS_BEFORE_EXPRESSION = "return yield new del raise elif else if"
|
|
96
96
|
|
|
@@ -104,7 +104,7 @@ IDENTIFIER_PAT = /^[a-z_$][_a-z0-9$]*$/i
|
|
|
104
104
|
|
|
105
105
|
def is_string_modifier(val):
|
|
106
106
|
for ch in val:
|
|
107
|
-
if '
|
|
107
|
+
if 'vrufbVRUFB'.indexOf(ch) is -1:
|
|
108
108
|
return False
|
|
109
109
|
return True
|
|
110
110
|
|
|
@@ -177,6 +177,7 @@ def tokenizer(raw_text, filename):
|
|
|
177
177
|
'index_or_slice': v'[ false ]',
|
|
178
178
|
'expecting_object_literal_key': False, # This is set by the parser when it is expecting an object literal key
|
|
179
179
|
'prev_was_comma': False, # True when the previous token was a ',' — used to detect positional-only '/' separator
|
|
180
|
+
'jsx_depth': 0, # >0 when inside JSX children content
|
|
180
181
|
}
|
|
181
182
|
def peek():
|
|
182
183
|
return S.text.charAt(S.pos)
|
|
@@ -362,6 +363,8 @@ def tokenizer(raw_text, filename):
|
|
|
362
363
|
return False
|
|
363
364
|
elif ch is '.':
|
|
364
365
|
return (has_dot = True) if not has_dot and not has_x and not has_e else False
|
|
366
|
+
elif ch is 'j' or ch is 'J':
|
|
367
|
+
return False # imaginary suffix — stop here; handled after loop
|
|
365
368
|
return is_alphanumeric_char(ch.charCodeAt(0))
|
|
366
369
|
)
|
|
367
370
|
if prefix:
|
|
@@ -369,6 +372,9 @@ def tokenizer(raw_text, filename):
|
|
|
369
372
|
|
|
370
373
|
valid = parse_js_number(num)
|
|
371
374
|
if not isNaN(valid):
|
|
375
|
+
if peek() is 'j' or peek() is 'J':
|
|
376
|
+
next()
|
|
377
|
+
return token("imaginary", valid)
|
|
372
378
|
return token("num", valid)
|
|
373
379
|
else:
|
|
374
380
|
parse_error("Invalid syntax: " + num)
|
|
@@ -639,6 +645,23 @@ def tokenizer(raw_text, filename):
|
|
|
639
645
|
if not ch:
|
|
640
646
|
return token("eof")
|
|
641
647
|
|
|
648
|
+
# JSX children mode: when inside JSX content, read raw text until { or <
|
|
649
|
+
if S.jsx_depth > 0 and ch is not '{' and ch is not '<':
|
|
650
|
+
result = ""
|
|
651
|
+
while S.pos < S.text.length:
|
|
652
|
+
c = S.text.charAt(S.pos)
|
|
653
|
+
if c is '{' or c is '<':
|
|
654
|
+
break
|
|
655
|
+
if c is '\n':
|
|
656
|
+
S.line += 1
|
|
657
|
+
S.col = 0
|
|
658
|
+
S.newline_before = True
|
|
659
|
+
else:
|
|
660
|
+
S.col += 1
|
|
661
|
+
result += c
|
|
662
|
+
S.pos += 1
|
|
663
|
+
return token("jsx_text", result)
|
|
664
|
+
|
|
642
665
|
code = ch.charCodeAt(0)
|
|
643
666
|
tmp_ = code
|
|
644
667
|
if tmp_ is 34 or tmp_ is 39: # double-quote (") or single quote (')
|
|
@@ -680,7 +703,7 @@ def tokenizer(raw_text, filename):
|
|
|
680
703
|
|
|
681
704
|
if is_identifier_start(code):
|
|
682
705
|
tok = read_word()
|
|
683
|
-
if '
|
|
706
|
+
if (peek() is "'" or peek() is '"') and is_string_modifier(tok.value):
|
|
684
707
|
mods = tok.value.toLowerCase()
|
|
685
708
|
start_pos_for_string = S.tokpos
|
|
686
709
|
stok = read_string(mods.indexOf('r') is not -1, mods.indexOf('v') is not -1)
|
|
@@ -689,7 +712,7 @@ def tokenizer(raw_text, filename):
|
|
|
689
712
|
tok.col += start_pos_for_string - tok.pos
|
|
690
713
|
return handle_interpolated_string(stok.value, tok)
|
|
691
714
|
tok.value = stok.value
|
|
692
|
-
tok.type = stok.type
|
|
715
|
+
tok.type = 'bytes_literal' if mods.indexOf('b') is not -1 else stok.type
|
|
693
716
|
return tok
|
|
694
717
|
|
|
695
718
|
parse_error("Unexpected character «" + ch + "»")
|
|
@@ -700,4 +723,15 @@ def tokenizer(raw_text, filename):
|
|
|
700
723
|
S = nc
|
|
701
724
|
return S
|
|
702
725
|
|
|
726
|
+
next_token.jsx_enter = def():
|
|
727
|
+
S.jsx_depth += 1
|
|
728
|
+
S.indentation_matters.push(False)
|
|
729
|
+
|
|
730
|
+
next_token.jsx_exit = def():
|
|
731
|
+
if S.jsx_depth > 0:
|
|
732
|
+
S.jsx_depth -= 1
|
|
733
|
+
S.indentation_matters.pop()
|
|
734
|
+
# After < in JSX context, / is always an operator (e.g. </tag>), never a regex
|
|
735
|
+
S.regex_allowed = False
|
|
736
|
+
|
|
703
737
|
return next_token
|
package/test/abc.pyj
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# abc.pyj — tests for the abc standard library module.
|
|
5
|
+
|
|
6
|
+
ae = assrt.equal
|
|
7
|
+
ade = assrt.deepEqual
|
|
8
|
+
ok = assrt.ok
|
|
9
|
+
|
|
10
|
+
from abc import ABC, ABCMeta, abstractmethod, Protocol, runtime_checkable, get_cache_token
|
|
11
|
+
|
|
12
|
+
# ── 1. @abstractmethod marks the function ─────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
def _tagged(): pass
|
|
15
|
+
abstractmethod(_tagged)
|
|
16
|
+
ok(_tagged.__isabstractmethod__)
|
|
17
|
+
|
|
18
|
+
# ── 2. Abstract class cannot be instantiated ──────────────────────────────────
|
|
19
|
+
|
|
20
|
+
class _Shape(ABC):
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def area(self): pass
|
|
23
|
+
|
|
24
|
+
_raised = False
|
|
25
|
+
try:
|
|
26
|
+
_Shape()
|
|
27
|
+
except TypeError:
|
|
28
|
+
_raised = True
|
|
29
|
+
ok(_raised)
|
|
30
|
+
|
|
31
|
+
# The error message contains the abstract method name
|
|
32
|
+
_msg = ''
|
|
33
|
+
try:
|
|
34
|
+
_Shape()
|
|
35
|
+
except TypeError as e:
|
|
36
|
+
_msg = str(e)
|
|
37
|
+
ok('_Shape' in _msg)
|
|
38
|
+
ok('area' in _msg)
|
|
39
|
+
|
|
40
|
+
# ── 3. Concrete subclass CAN be instantiated ──────────────────────────────────
|
|
41
|
+
|
|
42
|
+
class _Circle(_Shape):
|
|
43
|
+
def __init__(self, r):
|
|
44
|
+
self.r = r
|
|
45
|
+
def area(self):
|
|
46
|
+
return 3.14159 * self.r * self.r
|
|
47
|
+
|
|
48
|
+
_c = _Circle(5)
|
|
49
|
+
ok(_c is not None)
|
|
50
|
+
ae(_c.r, 5)
|
|
51
|
+
ok(abs(_c.area() - 78.53975) < 0.001)
|
|
52
|
+
|
|
53
|
+
# ── 4. isinstance checks work for concrete subclasses ─────────────────────────
|
|
54
|
+
|
|
55
|
+
ok(isinstance(_c, _Circle))
|
|
56
|
+
ok(isinstance(_c, _Shape))
|
|
57
|
+
ok(isinstance(_c, ABC))
|
|
58
|
+
|
|
59
|
+
# ── 5. Multiple abstract methods listed in TypeError (alphabetical order) ──────
|
|
60
|
+
|
|
61
|
+
class _Multi(ABC):
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def bar(self): pass
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def foo(self): pass
|
|
66
|
+
|
|
67
|
+
_m_raised = False
|
|
68
|
+
_m_msg = ''
|
|
69
|
+
try:
|
|
70
|
+
_Multi()
|
|
71
|
+
except TypeError as e:
|
|
72
|
+
_m_raised = True
|
|
73
|
+
_m_msg = str(e)
|
|
74
|
+
ok(_m_raised)
|
|
75
|
+
ok('bar' in _m_msg)
|
|
76
|
+
ok('foo' in _m_msg)
|
|
77
|
+
ok('methods' in _m_msg) # plural
|
|
78
|
+
|
|
79
|
+
# ── 6. Partial implementation still raises TypeError ──────────────────────────
|
|
80
|
+
|
|
81
|
+
class _PartialMulti(_Multi):
|
|
82
|
+
def foo(self):
|
|
83
|
+
return 1
|
|
84
|
+
|
|
85
|
+
_partial_raised = False
|
|
86
|
+
try:
|
|
87
|
+
_PartialMulti()
|
|
88
|
+
except TypeError:
|
|
89
|
+
_partial_raised = True
|
|
90
|
+
ok(_partial_raised)
|
|
91
|
+
|
|
92
|
+
# Full implementation works
|
|
93
|
+
class _FullMulti(_Multi):
|
|
94
|
+
def foo(self): return 1
|
|
95
|
+
def bar(self): return 2
|
|
96
|
+
|
|
97
|
+
_fm = _FullMulti()
|
|
98
|
+
ae(_fm.foo(), 1)
|
|
99
|
+
ae(_fm.bar(), 2)
|
|
100
|
+
|
|
101
|
+
# ── 7. __abstractmethods__ set on the class ───────────────────────────────────
|
|
102
|
+
|
|
103
|
+
ok('area' in _Shape.__abstractmethods__)
|
|
104
|
+
ae(len(_Shape.__abstractmethods__), 1)
|
|
105
|
+
ae(len(_Circle.__abstractmethods__), 0)
|
|
106
|
+
|
|
107
|
+
# ── 8. Abstract class with own __init__ ───────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
class _Vehicle(ABC):
|
|
110
|
+
def __init__(self, name):
|
|
111
|
+
self.name = name
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def drive(self): pass
|
|
115
|
+
|
|
116
|
+
class _Car(_Vehicle):
|
|
117
|
+
def drive(self):
|
|
118
|
+
return 'driving ' + self.name
|
|
119
|
+
|
|
120
|
+
_car = _Car('Tesla')
|
|
121
|
+
ae(_car.name, 'Tesla')
|
|
122
|
+
ae(_car.drive(), 'driving Tesla')
|
|
123
|
+
|
|
124
|
+
_veh_raised = False
|
|
125
|
+
try:
|
|
126
|
+
_Vehicle('Bus')
|
|
127
|
+
except TypeError:
|
|
128
|
+
_veh_raised = True
|
|
129
|
+
ok(_veh_raised)
|
|
130
|
+
|
|
131
|
+
# ── 9. Multi-level abstract inheritance ───────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
class _A(ABC):
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def m1(self): pass
|
|
136
|
+
|
|
137
|
+
class _B(_A):
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def m2(self): pass
|
|
140
|
+
|
|
141
|
+
_b_raised = False
|
|
142
|
+
try:
|
|
143
|
+
_B()
|
|
144
|
+
except TypeError:
|
|
145
|
+
_b_raised = True
|
|
146
|
+
ok(_b_raised)
|
|
147
|
+
|
|
148
|
+
class _C(_B):
|
|
149
|
+
def m1(self): return 'one'
|
|
150
|
+
def m2(self): return 'two'
|
|
151
|
+
|
|
152
|
+
_cobj = _C()
|
|
153
|
+
ae(_cobj.m1(), 'one')
|
|
154
|
+
ae(_cobj.m2(), 'two')
|
|
155
|
+
|
|
156
|
+
# ── 10. ABC.register() — virtual subclass isinstance ─────────────────────────
|
|
157
|
+
|
|
158
|
+
class _MyABC(ABC):
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def do_it(self): pass
|
|
161
|
+
|
|
162
|
+
class _External:
|
|
163
|
+
def do_it(self):
|
|
164
|
+
return 42
|
|
165
|
+
|
|
166
|
+
_MyABC.register(_External)
|
|
167
|
+
ok(isinstance(_External(), _MyABC))
|
|
168
|
+
|
|
169
|
+
# Non-registered class without inheritance is NOT an instance
|
|
170
|
+
class _Unrelated:
|
|
171
|
+
def do_it(self): return 99
|
|
172
|
+
|
|
173
|
+
ok(not isinstance(_Unrelated(), _MyABC))
|
|
174
|
+
|
|
175
|
+
# ── 11. ABC.register() used as a class decorator ──────────────────────────────
|
|
176
|
+
|
|
177
|
+
class _Base(ABC):
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def compute(self): pass
|
|
180
|
+
|
|
181
|
+
@_Base.register
|
|
182
|
+
class _Virtual:
|
|
183
|
+
def compute(self): return 7
|
|
184
|
+
|
|
185
|
+
ok(isinstance(_Virtual(), _Base))
|
|
186
|
+
|
|
187
|
+
# ── 12. get_cache_token() returns 0 ──────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
ae(get_cache_token(), 0)
|
|
190
|
+
|
|
191
|
+
# ── 13. Protocol + @runtime_checkable — structural isinstance ──────────────────
|
|
192
|
+
|
|
193
|
+
@runtime_checkable
|
|
194
|
+
class _Drawable(Protocol):
|
|
195
|
+
def draw(self): pass
|
|
196
|
+
|
|
197
|
+
class _Canvas:
|
|
198
|
+
def draw(self):
|
|
199
|
+
return 'painting'
|
|
200
|
+
|
|
201
|
+
class _NotDrawable:
|
|
202
|
+
def paint(self): pass
|
|
203
|
+
|
|
204
|
+
ok(isinstance(_Canvas(), _Drawable))
|
|
205
|
+
ok(not isinstance(_NotDrawable(), _Drawable))
|
|
206
|
+
ok(not isinstance(42, _Drawable))
|
|
207
|
+
ok(not isinstance(None, _Drawable))
|
|
208
|
+
|
|
209
|
+
# ── 14. Protocol without @runtime_checkable — normal inheritance check ─────────
|
|
210
|
+
|
|
211
|
+
class _Closeable(Protocol):
|
|
212
|
+
def close(self): pass
|
|
213
|
+
|
|
214
|
+
class _ManagedFile(_Closeable):
|
|
215
|
+
def close(self):
|
|
216
|
+
return 'closed'
|
|
217
|
+
|
|
218
|
+
ok(isinstance(_ManagedFile(), _ManagedFile))
|
|
219
|
+
ok(isinstance(_ManagedFile(), _Closeable)) # direct subclass => True
|
|
220
|
+
|
|
221
|
+
class _OtherFile:
|
|
222
|
+
def close(self): return 'also closed'
|
|
223
|
+
|
|
224
|
+
# Without runtime_checkable, structural check does NOT happen
|
|
225
|
+
ok(not isinstance(_OtherFile(), _Closeable))
|
|
226
|
+
|
|
227
|
+
# ── 15. Protocol with @abstractmethod enforces instantiation guard ─────────────
|
|
228
|
+
|
|
229
|
+
class _AbstractProto(Protocol):
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def required(self): pass
|
|
232
|
+
|
|
233
|
+
_ap_raised = False
|
|
234
|
+
try:
|
|
235
|
+
_AbstractProto()
|
|
236
|
+
except TypeError:
|
|
237
|
+
_ap_raised = True
|
|
238
|
+
ok(_ap_raised)
|
|
239
|
+
|
|
240
|
+
# Concrete implementation is instantiable
|
|
241
|
+
class _ConcreteProto(_AbstractProto):
|
|
242
|
+
def required(self): return 'done'
|
|
243
|
+
|
|
244
|
+
ok(_ConcreteProto() is not None)
|
|
245
|
+
|
|
246
|
+
# ── 16. Protocol __protocol_attrs__ contains non-dunder method names ───────────
|
|
247
|
+
|
|
248
|
+
@runtime_checkable
|
|
249
|
+
class _Sizeable(Protocol):
|
|
250
|
+
def size(self): pass
|
|
251
|
+
def count(self): pass
|
|
252
|
+
|
|
253
|
+
ok('size' in _Sizeable.__protocol_attrs__)
|
|
254
|
+
ok('count' in _Sizeable.__protocol_attrs__)
|
|
255
|
+
|
|
256
|
+
class _Box:
|
|
257
|
+
def size(self): return 10
|
|
258
|
+
def count(self): return 5
|
|
259
|
+
|
|
260
|
+
class _Half:
|
|
261
|
+
def size(self): return 3
|
|
262
|
+
|
|
263
|
+
ok(isinstance(_Box(), _Sizeable))
|
|
264
|
+
ok(not isinstance(_Half(), _Sizeable))
|
|
265
|
+
|
|
266
|
+
# ── 17. Protocol protocol_attrs inheritance (sub-protocols) ───────────────────
|
|
267
|
+
|
|
268
|
+
@runtime_checkable
|
|
269
|
+
class _Reader(Protocol):
|
|
270
|
+
def read(self): pass
|
|
271
|
+
|
|
272
|
+
@runtime_checkable
|
|
273
|
+
class _Writer(Protocol):
|
|
274
|
+
def write(self, data): pass
|
|
275
|
+
|
|
276
|
+
@runtime_checkable
|
|
277
|
+
class _ReadWriter(_Reader, _Writer, Protocol):
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
ok('read' in _ReadWriter.__protocol_attrs__)
|
|
281
|
+
ok('write' in _ReadWriter.__protocol_attrs__)
|
|
282
|
+
|
|
283
|
+
class _FullRW:
|
|
284
|
+
def read(self): return 'data'
|
|
285
|
+
def write(self, data): pass
|
|
286
|
+
|
|
287
|
+
class _ReadOnly:
|
|
288
|
+
def read(self): return 'data'
|
|
289
|
+
|
|
290
|
+
ok(isinstance(_FullRW(), _ReadWriter))
|
|
291
|
+
ok(not isinstance(_ReadOnly(), _ReadWriter))
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# arithmetic_nostrict.pyj
|
|
5
|
+
# Tests for no_strict_arithmetic: when strict_arithmetic is disabled, incompatible
|
|
6
|
+
# operand types fall back to JavaScript's native coercion instead of raising TypeError.
|
|
7
|
+
# overload_operators must be active for the ρσ_op_*_ns helpers to be dispatched.
|
|
8
|
+
|
|
9
|
+
from __python__ import overload_operators, no_strict_arithmetic
|
|
10
|
+
|
|
11
|
+
ae = assrt.equal
|
|
12
|
+
ade = assrt.deepEqual
|
|
13
|
+
ok = assrt.ok
|
|
14
|
+
|
|
15
|
+
def _no_error(fn):
|
|
16
|
+
try:
|
|
17
|
+
fn()
|
|
18
|
+
return True
|
|
19
|
+
except:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
# ── 1. Valid numeric arithmetic still works ───────────────────────────────────
|
|
23
|
+
|
|
24
|
+
ae(1 + 2, 3)
|
|
25
|
+
ae(10 - 3, 7)
|
|
26
|
+
ae(3 * 4, 12)
|
|
27
|
+
ae(7 / 2, 3.5)
|
|
28
|
+
ae(7 // 2, 3)
|
|
29
|
+
ae(7 % 3, 1)
|
|
30
|
+
ae(2 ** 8, 256)
|
|
31
|
+
|
|
32
|
+
# ── 2. Valid string concatenation still works ─────────────────────────────────
|
|
33
|
+
|
|
34
|
+
ae('hello' + ' world', 'hello world')
|
|
35
|
+
|
|
36
|
+
# ── 3. Valid list concatenation still works ───────────────────────────────────
|
|
37
|
+
|
|
38
|
+
ade([1, 2] + [3, 4], [1, 2, 3, 4])
|
|
39
|
+
|
|
40
|
+
# ── 4. String / list repetition still works ──────────────────────────────────
|
|
41
|
+
|
|
42
|
+
ae('ha' * 3, 'hahaha')
|
|
43
|
+
ae(3 * 'ha', 'hahaha')
|
|
44
|
+
ade([0] * 3, [0, 0, 0])
|
|
45
|
+
|
|
46
|
+
# ── 5. Mixed types do NOT raise TypeError ─────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
ok(_no_error(def(): return 1 + 'x';))
|
|
49
|
+
ok(_no_error(def(): return 'x' + 1;))
|
|
50
|
+
ok(_no_error(def(): return 1 - 'x';))
|
|
51
|
+
ok(_no_error(def(): return None + 1;))
|
|
52
|
+
|
|
53
|
+
# ── 6. JS coercion semantics for mixed types ──────────────────────────────────
|
|
54
|
+
|
|
55
|
+
# int + str coerces via JS (number becomes string)
|
|
56
|
+
ae(1 + 'x', '1x')
|
|
57
|
+
ae('x' + 2, 'x2')
|
|
58
|
+
|
|
59
|
+
# number - string coerces string to NaN
|
|
60
|
+
ok(isNaN(1 - 'x'))
|
|
61
|
+
|
|
62
|
+
# ── 7. Dunder methods still dispatch ──────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
class _Meter:
|
|
65
|
+
def __init__(self, n):
|
|
66
|
+
self.n = n
|
|
67
|
+
def __add__(self, other):
|
|
68
|
+
return _Meter(self.n + other.n)
|
|
69
|
+
def __mul__(self, scalar):
|
|
70
|
+
return _Meter(self.n * scalar)
|
|
71
|
+
|
|
72
|
+
m = _Meter(3) + _Meter(4)
|
|
73
|
+
ae(m.n, 7)
|
|
74
|
+
m2 = _Meter(5) * 2
|
|
75
|
+
ae(m2.n, 10)
|
|
76
|
+
|
|
77
|
+
# ── 8. Augmented assignment does not raise TypeError ──────────────────────────
|
|
78
|
+
|
|
79
|
+
ok(_no_error(def():
|
|
80
|
+
x = 1
|
|
81
|
+
x += 'oops'
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# ── 9. Comparison operators fall through to JS ────────────────────────────────
|
|
85
|
+
|
|
86
|
+
# JS comparisons with mixed types don't throw; they may produce surprising results
|
|
87
|
+
ok(_no_error(def(): return 1 < 'x';))
|
|
88
|
+
ok(_no_error(def(): return None >= 0;))
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# globals: assrt
|
|
2
|
+
# vim:fileencoding=utf-8
|
|
3
|
+
#
|
|
4
|
+
# arithmetic_types.pyj
|
|
5
|
+
# Tests for Python-style arithmetic type coercion: incompatible operand types
|
|
6
|
+
# raise TypeError instead of silently coercing (as JavaScript would).
|
|
7
|
+
# overload_operators must be active for the ρσ_op_* helpers to be dispatched.
|
|
8
|
+
|
|
9
|
+
from __python__ import overload_operators, strict_arithmetic
|
|
10
|
+
|
|
11
|
+
ae = assrt.equal
|
|
12
|
+
ade = assrt.deepEqual
|
|
13
|
+
ok = assrt.ok
|
|
14
|
+
|
|
15
|
+
def _raises_type_error(fn):
|
|
16
|
+
try:
|
|
17
|
+
fn()
|
|
18
|
+
return False
|
|
19
|
+
except TypeError:
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
# ── 1. Valid numeric arithmetic ──────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
ae(1 + 2, 3)
|
|
25
|
+
ae(1.5 + 2.5, 4.0)
|
|
26
|
+
ae(10 - 3, 7)
|
|
27
|
+
ae(3 * 4, 12)
|
|
28
|
+
ae(7 / 2, 3.5)
|
|
29
|
+
ae(7 // 2, 3)
|
|
30
|
+
ae(7 % 3, 1)
|
|
31
|
+
ae(2 ** 8, 256)
|
|
32
|
+
|
|
33
|
+
# ── 2. Valid string concatenation ────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
ae('hello' + ' world', 'hello world')
|
|
36
|
+
ae('a' + 'b' + 'c', 'abc')
|
|
37
|
+
|
|
38
|
+
# ── 3. Valid list concatenation ──────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
ade([1, 2] + [3, 4], [1, 2, 3, 4])
|
|
41
|
+
ade([] + [1], [1])
|
|
42
|
+
|
|
43
|
+
# ── 4. Valid string/list repetition ──────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
ae('ha' * 3, 'hahaha')
|
|
46
|
+
ae(3 * 'ha', 'hahaha')
|
|
47
|
+
ade([0] * 3, [0, 0, 0])
|
|
48
|
+
ade(3 * [1, 2], [1, 2, 1, 2, 1, 2])
|
|
49
|
+
|
|
50
|
+
# ── 5. bool treated as int for arithmetic (Python semantics) ─────────────────
|
|
51
|
+
|
|
52
|
+
ae(True + 1, 2)
|
|
53
|
+
ae(False + 0, 0)
|
|
54
|
+
ae(True + True, 2)
|
|
55
|
+
ae(True * 5, 5)
|
|
56
|
+
ae(False * 100, 0)
|
|
57
|
+
ae(True + 1.5, 2.5)
|
|
58
|
+
|
|
59
|
+
# bool * str and bool * list repetition
|
|
60
|
+
ae(True * 'x', 'x')
|
|
61
|
+
ae(False * 'x', '')
|
|
62
|
+
ade(True * [1, 2], [1, 2])
|
|
63
|
+
ade(False * [1, 2], [])
|
|
64
|
+
|
|
65
|
+
# ── 6. int + str raises TypeError ────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
ok(_raises_type_error(def(): return 1 + 'x';))
|
|
68
|
+
ok(_raises_type_error(def(): return 'x' + 1;))
|
|
69
|
+
ok(_raises_type_error(def(): return 1 + '1';))
|
|
70
|
+
|
|
71
|
+
# ── 7. numeric - non-numeric raises TypeError ────────────────────────────────
|
|
72
|
+
|
|
73
|
+
ok(_raises_type_error(def(): return 1 - 'x';))
|
|
74
|
+
ok(_raises_type_error(def(): return 'a' - 'b';))
|
|
75
|
+
ok(_raises_type_error(def(): return [1] - [2];))
|
|
76
|
+
|
|
77
|
+
# ── 8. numeric / non-numeric raises TypeError ────────────────────────────────
|
|
78
|
+
|
|
79
|
+
ok(_raises_type_error(def(): return 1 / 'x';))
|
|
80
|
+
ok(_raises_type_error(def(): return 'a' / 2;))
|
|
81
|
+
|
|
82
|
+
# ── 9. numeric // non-numeric raises TypeError ───────────────────────────────
|
|
83
|
+
|
|
84
|
+
ok(_raises_type_error(def(): return 5 // 'x';))
|
|
85
|
+
ok(_raises_type_error(def(): return 'a' // 2;))
|
|
86
|
+
|
|
87
|
+
# ── 10. numeric % non-numeric raises TypeError ───────────────────────────────
|
|
88
|
+
|
|
89
|
+
ok(_raises_type_error(def(): return 5 % 'x';))
|
|
90
|
+
ok(_raises_type_error(def(): return 'a' % 2;))
|
|
91
|
+
|
|
92
|
+
# ── 11. numeric ** non-numeric raises TypeError ──────────────────────────────
|
|
93
|
+
|
|
94
|
+
ok(_raises_type_error(def(): return 2 ** 'x';))
|
|
95
|
+
ok(_raises_type_error(def(): return 'a' ** 2;))
|
|
96
|
+
|
|
97
|
+
# ── 12. mixed str * non-numeric raises TypeError ─────────────────────────────
|
|
98
|
+
|
|
99
|
+
ok(_raises_type_error(def(): return 'ha' * 'ha';))
|
|
100
|
+
ok(_raises_type_error(def(): return 'ha' * [1];))
|
|
101
|
+
|
|
102
|
+
# ── 13. list + non-list raises TypeError ─────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
ok(_raises_type_error(def(): return [1, 2] + 1;))
|
|
105
|
+
ok(_raises_type_error(def(): return [1, 2] + 'x';))
|
|
106
|
+
ok(_raises_type_error(def(): return 1 + [1, 2];))
|
|
107
|
+
|
|
108
|
+
# ── 14. None as operand raises TypeError ─────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
ok(_raises_type_error(def(): return None + 1;))
|
|
111
|
+
ok(_raises_type_error(def(): return 1 + None;))
|
|
112
|
+
ok(_raises_type_error(def(): return None + 'x';))
|
|
113
|
+
|
|
114
|
+
# ── 15. TypeError message contains operand type names ────────────────────────
|
|
115
|
+
|
|
116
|
+
msg = ''
|
|
117
|
+
try:
|
|
118
|
+
v = 1 + 'x'
|
|
119
|
+
except TypeError as e:
|
|
120
|
+
msg = str(e)
|
|
121
|
+
ok(msg.indexOf('int') >= 0)
|
|
122
|
+
ok(msg.indexOf('str') >= 0)
|
|
123
|
+
ok(msg.indexOf('+') >= 0)
|
|
124
|
+
|
|
125
|
+
msg2 = ''
|
|
126
|
+
try:
|
|
127
|
+
v = 2.5 - 'y'
|
|
128
|
+
except TypeError as e:
|
|
129
|
+
msg2 = str(e)
|
|
130
|
+
ok(msg2.indexOf('float') >= 0)
|
|
131
|
+
ok(msg2.indexOf('str') >= 0)
|
|
132
|
+
ok(msg2.indexOf('-') >= 0)
|
|
133
|
+
|
|
134
|
+
# ── 16. augmented assignment also raises TypeError ───────────────────────────
|
|
135
|
+
|
|
136
|
+
ok(_raises_type_error(def():
|
|
137
|
+
x = 1
|
|
138
|
+
x += 'oops'
|
|
139
|
+
))
|
|
140
|
+
ok(_raises_type_error(def():
|
|
141
|
+
x = 'hello'
|
|
142
|
+
x -= 1
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
# ── 17. dunder methods still take priority over type checks ──────────────────
|
|
146
|
+
|
|
147
|
+
class _Meter:
|
|
148
|
+
def __init__(self, n):
|
|
149
|
+
self.n = n
|
|
150
|
+
def __add__(self, other):
|
|
151
|
+
return _Meter(self.n + other.n)
|
|
152
|
+
def __mul__(self, scalar):
|
|
153
|
+
return _Meter(self.n * scalar)
|
|
154
|
+
|
|
155
|
+
m = _Meter(3) + _Meter(4)
|
|
156
|
+
ae(m.n, 7)
|
|
157
|
+
m2 = _Meter(5) * 2
|
|
158
|
+
ae(m2.n, 10)
|
|
159
|
+
|
|
160
|
+
# ── 18. radd/rsub reflected methods still work ───────────────────────────────
|
|
161
|
+
|
|
162
|
+
class _Vec:
|
|
163
|
+
def __init__(self, x):
|
|
164
|
+
self.x = x
|
|
165
|
+
def __radd__(self, other):
|
|
166
|
+
return _Vec(other + self.x)
|
|
167
|
+
|
|
168
|
+
v = 10 + _Vec(5)
|
|
169
|
+
ae(v.x, 15)
|