pulse-framework 0.1.50__py3-none-any.whl → 0.1.52__py3-none-any.whl
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.
- pulse/__init__.py +542 -562
- pulse/_examples.py +29 -0
- pulse/app.py +0 -14
- pulse/cli/cmd.py +96 -80
- pulse/cli/dependencies.py +10 -41
- pulse/cli/folder_lock.py +3 -3
- pulse/cli/helpers.py +40 -67
- pulse/cli/logging.py +102 -0
- pulse/cli/packages.py +16 -0
- pulse/cli/processes.py +40 -23
- pulse/codegen/codegen.py +70 -35
- pulse/codegen/js.py +2 -4
- pulse/codegen/templates/route.py +94 -146
- pulse/component.py +115 -0
- pulse/components/for_.py +1 -1
- pulse/components/if_.py +1 -1
- pulse/components/react_router.py +16 -22
- pulse/{html → dom}/events.py +1 -1
- pulse/{html → dom}/props.py +6 -6
- pulse/{html → dom}/tags.py +11 -11
- pulse/dom/tags.pyi +480 -0
- pulse/form.py +7 -6
- pulse/hooks/init.py +1 -13
- pulse/js/__init__.py +37 -41
- pulse/js/__init__.pyi +22 -2
- pulse/js/_types.py +5 -3
- pulse/js/array.py +121 -38
- pulse/js/console.py +9 -9
- pulse/js/date.py +22 -19
- pulse/js/document.py +8 -4
- pulse/js/error.py +12 -14
- pulse/js/json.py +4 -3
- pulse/js/map.py +17 -7
- pulse/js/math.py +2 -2
- pulse/js/navigator.py +4 -4
- pulse/js/number.py +8 -8
- pulse/js/object.py +9 -13
- pulse/js/promise.py +25 -9
- pulse/js/regexp.py +6 -6
- pulse/js/set.py +20 -8
- pulse/js/string.py +7 -7
- pulse/js/weakmap.py +6 -6
- pulse/js/weakset.py +6 -6
- pulse/js/window.py +17 -14
- pulse/messages.py +1 -4
- pulse/react_component.py +3 -999
- pulse/render_session.py +74 -66
- pulse/renderer.py +311 -238
- pulse/routing.py +1 -10
- pulse/serializer.py +11 -1
- pulse/transpiler/__init__.py +84 -114
- pulse/transpiler/builtins.py +661 -343
- pulse/transpiler/errors.py +78 -2
- pulse/transpiler/function.py +463 -133
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +230 -325
- pulse/transpiler/js_module.py +218 -209
- pulse/transpiler/modules/__init__.py +16 -13
- pulse/transpiler/modules/asyncio.py +45 -26
- pulse/transpiler/modules/json.py +12 -8
- pulse/transpiler/modules/math.py +161 -216
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +231 -0
- pulse/transpiler/modules/typing.py +33 -28
- pulse/transpiler/nodes.py +1607 -923
- pulse/transpiler/py_module.py +118 -95
- pulse/transpiler/react_component.py +51 -0
- pulse/transpiler/transpiler.py +593 -437
- pulse/transpiler/vdom.py +255 -0
- {pulse_framework-0.1.50.dist-info → pulse_framework-0.1.52.dist-info}/METADATA +1 -1
- pulse_framework-0.1.52.dist-info/RECORD +120 -0
- pulse/html/tags.pyi +0 -470
- pulse/transpiler/constants.py +0 -110
- pulse/transpiler/context.py +0 -26
- pulse/transpiler/ids.py +0 -16
- pulse/transpiler/modules/re.py +0 -466
- pulse/transpiler/modules/tags.py +0 -268
- pulse/transpiler/utils.py +0 -4
- pulse/vdom.py +0 -667
- pulse_framework-0.1.50.dist-info/RECORD +0 -119
- /pulse/{html → dom}/__init__.py +0 -0
- /pulse/{html → dom}/elements.py +0 -0
- /pulse/{html → dom}/svg.py +0 -0
- {pulse_framework-0.1.50.dist-info → pulse_framework-0.1.52.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.50.dist-info → pulse_framework-0.1.52.dist-info}/entry_points.txt +0 -0
pulse/transpiler/builtins.py
CHANGED
|
@@ -1,395 +1,477 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Python builtin functions -> JavaScript equivalents for v2 transpiler.
|
|
1
|
+
"""Python builtin functions and methods -> JavaScript equivalents.
|
|
3
2
|
|
|
4
3
|
This module provides transpilation for Python builtins to JavaScript.
|
|
4
|
+
Builtin methods use runtime type checks when the type is not statically known.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import builtins
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING, Any, override
|
|
12
12
|
|
|
13
|
-
from pulse.transpiler.errors import
|
|
13
|
+
from pulse.transpiler.errors import TranspileError
|
|
14
14
|
from pulse.transpiler.nodes import (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
js_transformer,
|
|
15
|
+
Array,
|
|
16
|
+
Arrow,
|
|
17
|
+
Binary,
|
|
18
|
+
Call,
|
|
19
|
+
Expr,
|
|
20
|
+
Identifier,
|
|
21
|
+
Literal,
|
|
22
|
+
Member,
|
|
23
|
+
New,
|
|
24
|
+
Object,
|
|
25
|
+
Spread,
|
|
26
|
+
Subscript,
|
|
27
|
+
Template,
|
|
28
|
+
Ternary,
|
|
29
|
+
Throw,
|
|
30
|
+
Transformer,
|
|
31
|
+
Unary,
|
|
32
|
+
Undefined,
|
|
33
|
+
transformer,
|
|
35
34
|
)
|
|
36
35
|
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# Builtin Function Transpilers
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
37
44
|
|
|
38
|
-
@
|
|
39
|
-
def
|
|
45
|
+
@transformer("print")
|
|
46
|
+
def emit_print(*args: Any, ctx: Transpiler) -> Expr:
|
|
40
47
|
"""print(*args) -> console.log(...)"""
|
|
41
|
-
return
|
|
48
|
+
return Call(Member(Identifier("console"), "log"), [ctx.emit_expr(a) for a in args])
|
|
42
49
|
|
|
43
50
|
|
|
44
|
-
@
|
|
45
|
-
def
|
|
51
|
+
@transformer("len")
|
|
52
|
+
def emit_len(x: Any, *, ctx: Transpiler) -> Expr:
|
|
46
53
|
"""len(x) -> x.length ?? x.size"""
|
|
47
|
-
|
|
48
|
-
x
|
|
49
|
-
return JSBinary(JSMember(x, "length"), "??", JSMember(x, "size"))
|
|
54
|
+
x = ctx.emit_expr(x)
|
|
55
|
+
return Binary(Member(x, "length"), "??", Member(x, "size"))
|
|
50
56
|
|
|
51
57
|
|
|
52
|
-
@
|
|
53
|
-
def
|
|
58
|
+
@transformer("min")
|
|
59
|
+
def emit_min(*args: Any, ctx: Transpiler) -> Expr:
|
|
54
60
|
"""min(*args) -> Math.min(...)"""
|
|
55
|
-
|
|
61
|
+
if builtins.len(args) == 0:
|
|
62
|
+
raise TranspileError("min() expects at least one argument")
|
|
63
|
+
if builtins.len(args) == 1:
|
|
64
|
+
iterable = ctx.emit_expr(args[0])
|
|
65
|
+
return Call(
|
|
66
|
+
Member(Identifier("Math"), "min"),
|
|
67
|
+
[Spread(Call(Member(Identifier("Array"), "from"), [iterable]))],
|
|
68
|
+
)
|
|
69
|
+
return Call(Member(Identifier("Math"), "min"), [ctx.emit_expr(a) for a in args])
|
|
56
70
|
|
|
57
71
|
|
|
58
|
-
@
|
|
59
|
-
def
|
|
72
|
+
@transformer("max")
|
|
73
|
+
def emit_max(*args: Any, ctx: Transpiler) -> Expr:
|
|
60
74
|
"""max(*args) -> Math.max(...)"""
|
|
61
|
-
|
|
75
|
+
if builtins.len(args) == 0:
|
|
76
|
+
raise TranspileError("max() expects at least one argument")
|
|
77
|
+
if builtins.len(args) == 1:
|
|
78
|
+
iterable = ctx.emit_expr(args[0])
|
|
79
|
+
return Call(
|
|
80
|
+
Member(Identifier("Math"), "max"),
|
|
81
|
+
[Spread(Call(Member(Identifier("Array"), "from"), [iterable]))],
|
|
82
|
+
)
|
|
83
|
+
return Call(Member(Identifier("Math"), "max"), [ctx.emit_expr(a) for a in args])
|
|
62
84
|
|
|
63
85
|
|
|
64
|
-
@
|
|
65
|
-
def
|
|
86
|
+
@transformer("abs")
|
|
87
|
+
def emit_abs(x: Any, *, ctx: Transpiler) -> Expr:
|
|
66
88
|
"""abs(x) -> Math.abs(x)"""
|
|
67
|
-
return
|
|
89
|
+
return Call(Member(Identifier("Math"), "abs"), [ctx.emit_expr(x)])
|
|
68
90
|
|
|
69
91
|
|
|
70
|
-
@
|
|
71
|
-
def
|
|
92
|
+
@transformer("round")
|
|
93
|
+
def emit_round(number: Any, ndigits: Any = None, *, ctx: Transpiler) -> Expr:
|
|
72
94
|
"""round(number, ndigits=None) -> Math.round(...) or toFixed(...)"""
|
|
73
|
-
number =
|
|
95
|
+
number = ctx.emit_expr(number)
|
|
74
96
|
if ndigits is None:
|
|
75
|
-
return
|
|
76
|
-
# With ndigits: Number(x).toFixed(ndigits)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
return Call(Member(Identifier("Math"), "round"), [number])
|
|
98
|
+
# With ndigits: Number(Number(x).toFixed(ndigits)) to keep numeric semantics
|
|
99
|
+
return Call(
|
|
100
|
+
Identifier("Number"),
|
|
101
|
+
[
|
|
102
|
+
Call(
|
|
103
|
+
Member(Call(Identifier("Number"), [number]), "toFixed"),
|
|
104
|
+
[ctx.emit_expr(ndigits)],
|
|
105
|
+
)
|
|
106
|
+
],
|
|
80
107
|
)
|
|
81
108
|
|
|
82
109
|
|
|
83
|
-
@
|
|
84
|
-
def
|
|
110
|
+
@transformer("str")
|
|
111
|
+
def emit_str(x: Any, *, ctx: Transpiler) -> Expr:
|
|
85
112
|
"""str(x) -> String(x)"""
|
|
86
|
-
return
|
|
113
|
+
return Call(Identifier("String"), [ctx.emit_expr(x)])
|
|
87
114
|
|
|
88
115
|
|
|
89
|
-
@
|
|
90
|
-
def
|
|
116
|
+
@transformer("int")
|
|
117
|
+
def emit_int(*args: Any, ctx: Transpiler) -> Expr:
|
|
91
118
|
"""int(x) or int(x, base) -> parseInt(...)"""
|
|
92
119
|
if builtins.len(args) == 1:
|
|
93
|
-
return
|
|
120
|
+
return Call(Identifier("parseInt"), [ctx.emit_expr(args[0])])
|
|
94
121
|
if builtins.len(args) == 2:
|
|
95
|
-
return
|
|
96
|
-
|
|
122
|
+
return Call(
|
|
123
|
+
Identifier("parseInt"), [ctx.emit_expr(args[0]), ctx.emit_expr(args[1])]
|
|
97
124
|
)
|
|
98
|
-
raise
|
|
125
|
+
raise TranspileError("int() expects one or two arguments")
|
|
99
126
|
|
|
100
127
|
|
|
101
|
-
@
|
|
102
|
-
def
|
|
128
|
+
@transformer("float")
|
|
129
|
+
def emit_float(x: Any, *, ctx: Transpiler) -> Expr:
|
|
103
130
|
"""float(x) -> parseFloat(x)"""
|
|
104
|
-
return
|
|
131
|
+
return Call(Identifier("parseFloat"), [ctx.emit_expr(x)])
|
|
105
132
|
|
|
106
133
|
|
|
107
|
-
@
|
|
108
|
-
def
|
|
134
|
+
@transformer("list")
|
|
135
|
+
def emit_list(x: Any, *, ctx: Transpiler) -> Expr:
|
|
109
136
|
"""list(x) -> Array.from(x)"""
|
|
110
|
-
return
|
|
137
|
+
return Call(Member(Identifier("Array"), "from"), [ctx.emit_expr(x)])
|
|
111
138
|
|
|
112
139
|
|
|
113
|
-
@
|
|
114
|
-
def
|
|
140
|
+
@transformer("bool")
|
|
141
|
+
def emit_bool(x: Any, *, ctx: Transpiler) -> Expr:
|
|
115
142
|
"""bool(x) -> Boolean(x)"""
|
|
116
|
-
return
|
|
143
|
+
return Call(Identifier("Boolean"), [ctx.emit_expr(x)])
|
|
117
144
|
|
|
118
145
|
|
|
119
|
-
@
|
|
120
|
-
def
|
|
146
|
+
@transformer("set")
|
|
147
|
+
def emit_set(*args: Any, ctx: Transpiler) -> Expr:
|
|
121
148
|
"""set() or set(iterable) -> new Set([iterable])"""
|
|
122
149
|
if builtins.len(args) == 0:
|
|
123
|
-
return
|
|
150
|
+
return New(Identifier("Set"), [])
|
|
124
151
|
if builtins.len(args) == 1:
|
|
125
|
-
return
|
|
126
|
-
raise
|
|
152
|
+
return New(Identifier("Set"), [ctx.emit_expr(args[0])])
|
|
153
|
+
raise TranspileError("set() expects at most one argument")
|
|
127
154
|
|
|
128
155
|
|
|
129
|
-
@
|
|
130
|
-
def
|
|
156
|
+
@transformer("tuple")
|
|
157
|
+
def emit_tuple(*args: Any, ctx: Transpiler) -> Expr:
|
|
131
158
|
"""tuple() or tuple(iterable) -> Array.from(iterable)"""
|
|
132
159
|
if builtins.len(args) == 0:
|
|
133
|
-
return
|
|
160
|
+
return Array([])
|
|
134
161
|
if builtins.len(args) == 1:
|
|
135
|
-
return
|
|
136
|
-
raise
|
|
162
|
+
return Call(Member(Identifier("Array"), "from"), [ctx.emit_expr(args[0])])
|
|
163
|
+
raise TranspileError("tuple() expects at most one argument")
|
|
137
164
|
|
|
138
165
|
|
|
139
|
-
@
|
|
140
|
-
def
|
|
166
|
+
@transformer("dict")
|
|
167
|
+
def emit_dict(*args: Any, ctx: Transpiler) -> Expr:
|
|
141
168
|
"""dict() or dict(iterable) -> new Map([iterable])"""
|
|
142
169
|
if builtins.len(args) == 0:
|
|
143
|
-
return
|
|
170
|
+
return New(Identifier("Map"), [])
|
|
144
171
|
if builtins.len(args) == 1:
|
|
145
|
-
return
|
|
146
|
-
raise
|
|
172
|
+
return New(Identifier("Map"), [ctx.emit_expr(args[0])])
|
|
173
|
+
raise TranspileError("dict() expects at most one argument")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@transformer("obj")
|
|
177
|
+
def obj(*args: Any, ctx: Transpiler, **kwargs: Any) -> Expr:
|
|
178
|
+
"""obj(key=value, ...) -> { key: value, ... }
|
|
179
|
+
|
|
180
|
+
Creates a plain JavaScript object literal.
|
|
181
|
+
Use this instead of dict() when you need a plain object (e.g., for React props).
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
style=obj(display="block", color="red")
|
|
185
|
+
-> style={{ display: "block", color: "red" }}
|
|
186
|
+
"""
|
|
187
|
+
if args:
|
|
188
|
+
raise TranspileError("obj() only accepts keyword arguments")
|
|
189
|
+
props: list[tuple[str, Expr]] = []
|
|
190
|
+
for key, value in kwargs.items():
|
|
191
|
+
props.append((key, ctx.emit_expr(value)))
|
|
192
|
+
return Object(props)
|
|
147
193
|
|
|
148
194
|
|
|
149
|
-
@
|
|
150
|
-
def
|
|
195
|
+
@transformer("filter")
|
|
196
|
+
def emit_filter(*args: Any, ctx: Transpiler) -> Expr:
|
|
151
197
|
"""filter(func, iterable) -> iterable.filter(func)"""
|
|
152
198
|
if not (1 <= builtins.len(args) <= 2):
|
|
153
|
-
raise
|
|
199
|
+
raise TranspileError("filter() expects one or two arguments")
|
|
154
200
|
if builtins.len(args) == 1:
|
|
155
201
|
# filter(iterable) - filter truthy values
|
|
156
|
-
iterable =
|
|
157
|
-
predicate =
|
|
158
|
-
return
|
|
159
|
-
func, iterable =
|
|
202
|
+
iterable = ctx.emit_expr(args[0])
|
|
203
|
+
predicate = Arrow(["v"], Identifier("v"))
|
|
204
|
+
return Call(Member(iterable, "filter"), [predicate])
|
|
205
|
+
func, iterable = ctx.emit_expr(args[0]), ctx.emit_expr(args[1])
|
|
160
206
|
# filter(None, iterable) means filter truthy
|
|
161
|
-
if builtins.isinstance(func,
|
|
162
|
-
func
|
|
163
|
-
|
|
207
|
+
if builtins.isinstance(func, (Literal, Undefined)) and (
|
|
208
|
+
builtins.isinstance(func, Undefined)
|
|
209
|
+
or (builtins.isinstance(func, Literal) and func.value is None)
|
|
210
|
+
):
|
|
211
|
+
func = Arrow(["v"], Identifier("v"))
|
|
212
|
+
return Call(Member(iterable, "filter"), [func])
|
|
164
213
|
|
|
165
214
|
|
|
166
|
-
@
|
|
167
|
-
def
|
|
215
|
+
@transformer("map")
|
|
216
|
+
def emit_map(func: Any, iterable: Any, *, ctx: Transpiler) -> Expr:
|
|
168
217
|
"""map(func, iterable) -> iterable.map(func)"""
|
|
169
|
-
return
|
|
218
|
+
return Call(Member(ctx.emit_expr(iterable), "map"), [ctx.emit_expr(func)])
|
|
170
219
|
|
|
171
220
|
|
|
172
|
-
@
|
|
173
|
-
def
|
|
221
|
+
@transformer("reversed")
|
|
222
|
+
def emit_reversed(iterable: Any, *, ctx: Transpiler) -> Expr:
|
|
174
223
|
"""reversed(iterable) -> iterable.slice().reverse()"""
|
|
175
|
-
return
|
|
224
|
+
return Call(
|
|
225
|
+
Member(Call(Member(ctx.emit_expr(iterable), "slice"), []), "reverse"), []
|
|
226
|
+
)
|
|
176
227
|
|
|
177
228
|
|
|
178
|
-
@
|
|
179
|
-
def
|
|
229
|
+
@transformer("enumerate")
|
|
230
|
+
def emit_enumerate(iterable: Any, start: Any = None, *, ctx: Transpiler) -> Expr:
|
|
180
231
|
"""enumerate(iterable, start=0) -> iterable.map((v, i) => [i + start, v])"""
|
|
181
|
-
base =
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
"map",
|
|
232
|
+
base = Literal(0) if start is None else ctx.emit_expr(start)
|
|
233
|
+
return Call(
|
|
234
|
+
Member(ctx.emit_expr(iterable), "map"),
|
|
185
235
|
[
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
236
|
+
Arrow(
|
|
237
|
+
["v", "i"],
|
|
238
|
+
Array([Binary(Identifier("i"), "+", base), Identifier("v")]),
|
|
189
239
|
)
|
|
190
240
|
],
|
|
191
241
|
)
|
|
192
242
|
|
|
193
243
|
|
|
194
|
-
@
|
|
195
|
-
def
|
|
244
|
+
@transformer("range")
|
|
245
|
+
def emit_range(*args: Any, ctx: Transpiler) -> Expr:
|
|
196
246
|
"""range(stop) or range(start, stop[, step]) -> Array.from(...)"""
|
|
197
247
|
if not (1 <= builtins.len(args) <= 3):
|
|
198
|
-
raise
|
|
248
|
+
raise TranspileError("range() expects 1 to 3 arguments")
|
|
199
249
|
if builtins.len(args) == 1:
|
|
200
|
-
stop =
|
|
201
|
-
length =
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
[
|
|
205
|
-
)
|
|
206
|
-
start =
|
|
207
|
-
stop =
|
|
208
|
-
step =
|
|
250
|
+
stop = ctx.emit_expr(args[0])
|
|
251
|
+
length = Call(Member(Identifier("Math"), "max"), [Literal(0), stop])
|
|
252
|
+
return Call(
|
|
253
|
+
Member(Identifier("Array"), "from"),
|
|
254
|
+
[Call(Member(New(Identifier("Array"), [length]), "keys"), [])],
|
|
255
|
+
)
|
|
256
|
+
start = ctx.emit_expr(args[0])
|
|
257
|
+
stop = ctx.emit_expr(args[1])
|
|
258
|
+
step = ctx.emit_expr(args[2]) if builtins.len(args) == 3 else Literal(1)
|
|
209
259
|
# count = max(0, ceil((stop - start) / step))
|
|
210
|
-
diff =
|
|
211
|
-
div =
|
|
212
|
-
ceil =
|
|
213
|
-
count =
|
|
260
|
+
diff = Binary(stop, "-", start)
|
|
261
|
+
div = Binary(diff, "/", step)
|
|
262
|
+
ceil = Call(Member(Identifier("Math"), "ceil"), [div])
|
|
263
|
+
count = Call(Member(Identifier("Math"), "max"), [Literal(0), ceil])
|
|
214
264
|
# Array.from(new Array(count).keys(), i => start + i * step)
|
|
215
|
-
return
|
|
216
|
-
|
|
265
|
+
return Call(
|
|
266
|
+
Member(Identifier("Array"), "from"),
|
|
217
267
|
[
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"i", JSBinary(start, "+", JSBinary(JSIdentifier("i"), "*", step))
|
|
221
|
-
),
|
|
268
|
+
Call(Member(New(Identifier("Array"), [count]), "keys"), []),
|
|
269
|
+
Arrow(["i"], Binary(start, "+", Binary(Identifier("i"), "*", step))),
|
|
222
270
|
],
|
|
223
271
|
)
|
|
224
272
|
|
|
225
273
|
|
|
226
|
-
@
|
|
227
|
-
def
|
|
274
|
+
@transformer("sorted")
|
|
275
|
+
def emit_sorted(
|
|
276
|
+
*args: Any, key: Any = None, reverse: Any = None, ctx: Transpiler
|
|
277
|
+
) -> Expr:
|
|
228
278
|
"""sorted(iterable, key=None, reverse=False) -> iterable.slice().sort(...)"""
|
|
229
279
|
if builtins.len(args) != 1:
|
|
230
|
-
raise
|
|
231
|
-
iterable =
|
|
232
|
-
clone =
|
|
280
|
+
raise TranspileError("sorted() expects exactly one positional argument")
|
|
281
|
+
iterable = ctx.emit_expr(args[0])
|
|
282
|
+
clone = Call(Member(iterable, "slice"), [])
|
|
233
283
|
# comparator: (a, b) => (a > b) - (a < b) or with key
|
|
234
284
|
if key is None:
|
|
235
|
-
cmp_expr =
|
|
236
|
-
|
|
285
|
+
cmp_expr = Binary(
|
|
286
|
+
Binary(Identifier("a"), ">", Identifier("b")),
|
|
237
287
|
"-",
|
|
238
|
-
|
|
288
|
+
Binary(Identifier("a"), "<", Identifier("b")),
|
|
239
289
|
)
|
|
240
290
|
else:
|
|
241
|
-
key_js =
|
|
242
|
-
cmp_expr =
|
|
243
|
-
|
|
244
|
-
|
|
291
|
+
key_js = ctx.emit_expr(key)
|
|
292
|
+
cmp_expr = Binary(
|
|
293
|
+
Binary(
|
|
294
|
+
Call(key_js, [Identifier("a")]),
|
|
245
295
|
">",
|
|
246
|
-
|
|
296
|
+
Call(key_js, [Identifier("b")]),
|
|
247
297
|
),
|
|
248
298
|
"-",
|
|
249
|
-
|
|
250
|
-
|
|
299
|
+
Binary(
|
|
300
|
+
Call(key_js, [Identifier("a")]),
|
|
251
301
|
"<",
|
|
252
|
-
|
|
302
|
+
Call(key_js, [Identifier("b")]),
|
|
253
303
|
),
|
|
254
304
|
)
|
|
255
|
-
sort_call =
|
|
305
|
+
sort_call = Call(Member(clone, "sort"), [Arrow(["a", "b"], cmp_expr)])
|
|
256
306
|
if reverse is None:
|
|
257
307
|
return sort_call
|
|
258
|
-
return
|
|
259
|
-
|
|
308
|
+
return Ternary(
|
|
309
|
+
ctx.emit_expr(reverse), Call(Member(sort_call, "reverse"), []), sort_call
|
|
260
310
|
)
|
|
261
311
|
|
|
262
312
|
|
|
263
|
-
@
|
|
264
|
-
def
|
|
313
|
+
@transformer("zip")
|
|
314
|
+
def emit_zip(*args: Any, ctx: Transpiler) -> Expr:
|
|
265
315
|
"""zip(*iterables) -> Array.from(...) with paired elements"""
|
|
266
316
|
if builtins.len(args) == 0:
|
|
267
|
-
return
|
|
317
|
+
return Array([])
|
|
268
318
|
|
|
269
|
-
js_args = [
|
|
319
|
+
js_args = [ctx.emit_expr(a) for a in args]
|
|
270
320
|
|
|
271
|
-
def length_of(x:
|
|
272
|
-
return
|
|
321
|
+
def length_of(x: Expr) -> Expr:
|
|
322
|
+
return Member(x, "length")
|
|
273
323
|
|
|
274
324
|
min_len = length_of(js_args[0])
|
|
275
325
|
for it in js_args[1:]:
|
|
276
|
-
min_len =
|
|
326
|
+
min_len = Call(Member(Identifier("Math"), "min"), [min_len, length_of(it)])
|
|
277
327
|
|
|
278
|
-
elems = [
|
|
279
|
-
make_pair =
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
[
|
|
328
|
+
elems = [Subscript(arg, Identifier("i")) for arg in js_args]
|
|
329
|
+
make_pair = Arrow(["i"], Array(elems))
|
|
330
|
+
return Call(
|
|
331
|
+
Member(Identifier("Array"), "from"),
|
|
332
|
+
[Call(Member(New(Identifier("Array"), [min_len]), "keys"), []), make_pair],
|
|
283
333
|
)
|
|
284
334
|
|
|
285
335
|
|
|
286
|
-
@
|
|
287
|
-
def
|
|
336
|
+
@transformer("pow")
|
|
337
|
+
def emit_pow(base: Any, exp: Any, *, ctx: Transpiler) -> Expr:
|
|
288
338
|
"""pow(base, exp) -> Math.pow(base, exp)"""
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return JSMemberCall(
|
|
292
|
-
JSIdentifier("Math"), "pow", [JSExpr.of(args[0]), JSExpr.of(args[1])]
|
|
339
|
+
return Call(
|
|
340
|
+
Member(Identifier("Math"), "pow"), [ctx.emit_expr(base), ctx.emit_expr(exp)]
|
|
293
341
|
)
|
|
294
342
|
|
|
295
343
|
|
|
296
|
-
@
|
|
297
|
-
def
|
|
344
|
+
@transformer("chr")
|
|
345
|
+
def emit_chr(x: Any, *, ctx: Transpiler) -> Expr:
|
|
298
346
|
"""chr(x) -> String.fromCharCode(x)"""
|
|
299
|
-
return
|
|
347
|
+
return Call(Member(Identifier("String"), "fromCharCode"), [ctx.emit_expr(x)])
|
|
300
348
|
|
|
301
349
|
|
|
302
|
-
@
|
|
303
|
-
def
|
|
350
|
+
@transformer("ord")
|
|
351
|
+
def emit_ord(x: Any, *, ctx: Transpiler) -> Expr:
|
|
304
352
|
"""ord(x) -> x.charCodeAt(0)"""
|
|
305
|
-
return
|
|
353
|
+
return Call(Member(ctx.emit_expr(x), "charCodeAt"), [Literal(0)])
|
|
306
354
|
|
|
307
355
|
|
|
308
|
-
@
|
|
309
|
-
def
|
|
356
|
+
@transformer("any")
|
|
357
|
+
def emit_any(x: Any, *, ctx: Transpiler) -> Expr:
|
|
310
358
|
"""any(iterable) -> iterable.some(v => v)"""
|
|
311
|
-
x =
|
|
359
|
+
x = ctx.emit_expr(x)
|
|
312
360
|
# Optimization: if x is a map call, use .some directly
|
|
313
|
-
if
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
361
|
+
if (
|
|
362
|
+
builtins.isinstance(x, Call)
|
|
363
|
+
and builtins.isinstance(x.callee, Member)
|
|
364
|
+
and x.callee.prop == "map"
|
|
365
|
+
and x.args
|
|
366
|
+
):
|
|
367
|
+
return Call(Member(x.callee.obj, "some"), [x.args[0]])
|
|
368
|
+
return Call(Member(x, "some"), [Arrow(["v"], Identifier("v"))])
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@transformer("all")
|
|
372
|
+
def emit_all(x: Any, *, ctx: Transpiler) -> Expr:
|
|
320
373
|
"""all(iterable) -> iterable.every(v => v)"""
|
|
321
|
-
x =
|
|
374
|
+
x = ctx.emit_expr(x)
|
|
322
375
|
# Optimization: if x is a map call, use .every directly
|
|
323
|
-
if
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
376
|
+
if (
|
|
377
|
+
builtins.isinstance(x, Call)
|
|
378
|
+
and builtins.isinstance(x.callee, Member)
|
|
379
|
+
and x.callee.prop == "map"
|
|
380
|
+
and x.args
|
|
381
|
+
):
|
|
382
|
+
return Call(Member(x.callee.obj, "every"), [x.args[0]])
|
|
383
|
+
return Call(Member(x, "every"), [Arrow(["v"], Identifier("v"))])
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@transformer("sum")
|
|
387
|
+
def emit_sum(*args: Any, ctx: Transpiler) -> Expr:
|
|
330
388
|
"""sum(iterable, start=0) -> iterable.reduce((a, b) => a + b, start)"""
|
|
331
389
|
if not (1 <= builtins.len(args) <= 2):
|
|
332
|
-
raise
|
|
333
|
-
start =
|
|
334
|
-
base =
|
|
335
|
-
reducer =
|
|
336
|
-
|
|
337
|
-
)
|
|
338
|
-
return JSMemberCall(base, "reduce", [reducer, start])
|
|
390
|
+
raise TranspileError("sum() expects one or two arguments")
|
|
391
|
+
start = ctx.emit_expr(args[1]) if builtins.len(args) == 2 else Literal(0)
|
|
392
|
+
base = ctx.emit_expr(args[0])
|
|
393
|
+
reducer = Arrow(["a", "b"], Binary(Identifier("a"), "+", Identifier("b")))
|
|
394
|
+
return Call(Member(base, "reduce"), [reducer, start])
|
|
339
395
|
|
|
340
396
|
|
|
341
|
-
@
|
|
342
|
-
def
|
|
397
|
+
@transformer("divmod")
|
|
398
|
+
def emit_divmod(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
|
|
343
399
|
"""divmod(x, y) -> [Math.floor(x / y), x - Math.floor(x / y) * y]"""
|
|
344
|
-
x, y =
|
|
345
|
-
q =
|
|
346
|
-
r =
|
|
347
|
-
return
|
|
400
|
+
x, y = ctx.emit_expr(x), ctx.emit_expr(y)
|
|
401
|
+
q = Call(Member(Identifier("Math"), "floor"), [Binary(x, "/", y)])
|
|
402
|
+
r = Binary(x, "-", Binary(q, "*", y))
|
|
403
|
+
return Array([q, r])
|
|
348
404
|
|
|
349
405
|
|
|
350
|
-
@
|
|
351
|
-
def
|
|
406
|
+
@transformer("isinstance")
|
|
407
|
+
def emit_isinstance(*args: Any, ctx: Transpiler) -> Expr:
|
|
352
408
|
"""isinstance is not directly supported in v2; raise error."""
|
|
353
|
-
raise
|
|
354
|
-
|
|
355
|
-
|
|
409
|
+
raise TranspileError("isinstance() is not supported in JavaScript transpilation")
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@transformer("Exception")
|
|
413
|
+
def emit_exception(*args: Any, ctx: Transpiler) -> Expr:
|
|
414
|
+
"""Exception(msg) -> new Error(msg)"""
|
|
415
|
+
return New(Identifier("Error"), [ctx.emit_expr(a) for a in args])
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@transformer("ValueError")
|
|
419
|
+
def emit_value_error(*args: Any, ctx: Transpiler) -> Expr:
|
|
420
|
+
"""ValueError(msg) -> new Error(msg)"""
|
|
421
|
+
return New(Identifier("Error"), [ctx.emit_expr(a) for a in args])
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@transformer("TypeError")
|
|
425
|
+
def emit_type_error(*args: Any, ctx: Transpiler) -> Expr:
|
|
426
|
+
"""TypeError(msg) -> new TypeError(msg)"""
|
|
427
|
+
return New(Identifier("TypeError"), [ctx.emit_expr(a) for a in args])
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@transformer("RuntimeError")
|
|
431
|
+
def emit_runtime_error(*args: Any, ctx: Transpiler) -> Expr:
|
|
432
|
+
"""RuntimeError(msg) -> new Error(msg)"""
|
|
433
|
+
return New(Identifier("Error"), [ctx.emit_expr(a) for a in args])
|
|
356
434
|
|
|
357
435
|
|
|
358
436
|
# Registry of builtin transformers
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
437
|
+
# Note: @transformer decorator returns Transformer but lies about the type
|
|
438
|
+
# for ergonomic reasons. These are all Transformer instances at runtime.
|
|
439
|
+
BUILTINS: dict[str, Transformer[Any]] = dict(
|
|
440
|
+
print=emit_print,
|
|
441
|
+
len=emit_len,
|
|
442
|
+
min=emit_min,
|
|
443
|
+
max=emit_max,
|
|
444
|
+
abs=emit_abs,
|
|
445
|
+
round=emit_round,
|
|
446
|
+
str=emit_str,
|
|
447
|
+
int=emit_int,
|
|
448
|
+
float=emit_float,
|
|
449
|
+
list=emit_list,
|
|
450
|
+
bool=emit_bool,
|
|
451
|
+
set=emit_set,
|
|
452
|
+
tuple=emit_tuple,
|
|
453
|
+
dict=emit_dict,
|
|
454
|
+
filter=emit_filter,
|
|
455
|
+
map=emit_map,
|
|
456
|
+
reversed=emit_reversed,
|
|
457
|
+
enumerate=emit_enumerate,
|
|
458
|
+
range=emit_range,
|
|
459
|
+
sorted=emit_sorted,
|
|
460
|
+
zip=emit_zip,
|
|
461
|
+
pow=emit_pow,
|
|
462
|
+
chr=emit_chr,
|
|
463
|
+
ord=emit_ord,
|
|
464
|
+
any=emit_any,
|
|
465
|
+
all=emit_all,
|
|
466
|
+
sum=emit_sum,
|
|
467
|
+
divmod=emit_divmod,
|
|
468
|
+
isinstance=emit_isinstance,
|
|
469
|
+
# Exception types
|
|
470
|
+
Exception=emit_exception,
|
|
471
|
+
ValueError=emit_value_error,
|
|
472
|
+
TypeError=emit_type_error,
|
|
473
|
+
RuntimeError=emit_runtime_error,
|
|
474
|
+
) # pyright: ignore[reportAssignmentType]
|
|
393
475
|
|
|
394
476
|
|
|
395
477
|
# =============================================================================
|
|
@@ -406,12 +488,12 @@ BUILTINS = cast(
|
|
|
406
488
|
class BuiltinMethods(ABC):
|
|
407
489
|
"""Abstract base class for type-specific method transpilation."""
|
|
408
490
|
|
|
409
|
-
def __init__(self, obj:
|
|
410
|
-
self.this:
|
|
491
|
+
def __init__(self, obj: Expr) -> None:
|
|
492
|
+
self.this: Expr = obj
|
|
411
493
|
|
|
412
494
|
@classmethod
|
|
413
495
|
@abstractmethod
|
|
414
|
-
def __runtime_check__(cls, expr:
|
|
496
|
+
def __runtime_check__(cls, expr: Expr) -> Expr:
|
|
415
497
|
"""Return a JS expression that checks if expr is this type at runtime."""
|
|
416
498
|
...
|
|
417
499
|
|
|
@@ -427,67 +509,117 @@ class StringMethods(BuiltinMethods):
|
|
|
427
509
|
|
|
428
510
|
@classmethod
|
|
429
511
|
@override
|
|
430
|
-
def __runtime_check__(cls, expr:
|
|
431
|
-
return
|
|
512
|
+
def __runtime_check__(cls, expr: Expr) -> Expr:
|
|
513
|
+
return Binary(Unary("typeof", expr), "===", Literal("string"))
|
|
432
514
|
|
|
433
515
|
@classmethod
|
|
434
516
|
@override
|
|
435
517
|
def __methods__(cls) -> set[str]:
|
|
436
518
|
return STR_METHODS
|
|
437
519
|
|
|
438
|
-
def lower(self) ->
|
|
520
|
+
def lower(self) -> Expr:
|
|
439
521
|
"""str.lower() -> str.toLowerCase()"""
|
|
440
|
-
return
|
|
522
|
+
return Call(Member(self.this, "toLowerCase"), [])
|
|
441
523
|
|
|
442
|
-
def upper(self) ->
|
|
524
|
+
def upper(self) -> Expr:
|
|
443
525
|
"""str.upper() -> str.toUpperCase()"""
|
|
444
|
-
return
|
|
526
|
+
return Call(Member(self.this, "toUpperCase"), [])
|
|
445
527
|
|
|
446
|
-
def strip(self) ->
|
|
528
|
+
def strip(self) -> Expr:
|
|
447
529
|
"""str.strip() -> str.trim()"""
|
|
448
|
-
return
|
|
530
|
+
return Call(Member(self.this, "trim"), [])
|
|
449
531
|
|
|
450
|
-
def lstrip(self) ->
|
|
532
|
+
def lstrip(self) -> Expr:
|
|
451
533
|
"""str.lstrip() -> str.trimStart()"""
|
|
452
|
-
return
|
|
534
|
+
return Call(Member(self.this, "trimStart"), [])
|
|
453
535
|
|
|
454
|
-
def rstrip(self) ->
|
|
536
|
+
def rstrip(self) -> Expr:
|
|
455
537
|
"""str.rstrip() -> str.trimEnd()"""
|
|
456
|
-
return
|
|
538
|
+
return Call(Member(self.this, "trimEnd"), [])
|
|
457
539
|
|
|
458
|
-
def zfill(self, width:
|
|
540
|
+
def zfill(self, width: Expr) -> Expr:
|
|
459
541
|
"""str.zfill(width) -> str.padStart(width, '0')"""
|
|
460
|
-
return
|
|
542
|
+
return Call(Member(self.this, "padStart"), [width, Literal("0")])
|
|
461
543
|
|
|
462
|
-
def startswith(self, prefix:
|
|
544
|
+
def startswith(self, prefix: Expr) -> Expr:
|
|
463
545
|
"""str.startswith(prefix) -> str.startsWith(prefix)"""
|
|
464
|
-
return
|
|
546
|
+
return Call(Member(self.this, "startsWith"), [prefix])
|
|
465
547
|
|
|
466
|
-
def endswith(self, suffix:
|
|
548
|
+
def endswith(self, suffix: Expr) -> Expr:
|
|
467
549
|
"""str.endswith(suffix) -> str.endsWith(suffix)"""
|
|
468
|
-
return
|
|
550
|
+
return Call(Member(self.this, "endsWith"), [suffix])
|
|
469
551
|
|
|
470
|
-
def replace(self, old:
|
|
552
|
+
def replace(self, old: Expr, new: Expr) -> Expr:
|
|
471
553
|
"""str.replace(old, new) -> str.replaceAll(old, new)"""
|
|
472
|
-
return
|
|
554
|
+
return Call(Member(self.this, "replaceAll"), [old, new])
|
|
473
555
|
|
|
474
|
-
def capitalize(self) ->
|
|
556
|
+
def capitalize(self) -> Expr:
|
|
475
557
|
"""str.capitalize() -> str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()"""
|
|
476
|
-
left =
|
|
477
|
-
|
|
558
|
+
left = Call(
|
|
559
|
+
Member(Call(Member(self.this, "charAt"), [Literal(0)]), "toUpperCase"), []
|
|
478
560
|
)
|
|
479
|
-
right =
|
|
480
|
-
|
|
561
|
+
right = Call(
|
|
562
|
+
Member(Call(Member(self.this, "slice"), [Literal(1)]), "toLowerCase"), []
|
|
481
563
|
)
|
|
482
|
-
return
|
|
564
|
+
return Binary(left, "+", right)
|
|
483
565
|
|
|
484
|
-
def split(self, sep:
|
|
485
|
-
"""str.split()
|
|
486
|
-
|
|
566
|
+
def split(self, sep: Expr | None = None) -> Expr | None:
|
|
567
|
+
"""str.split(sep) -> str.split(sep) or special whitespace handling.
|
|
568
|
+
|
|
569
|
+
Python's split() without args splits on whitespace and removes empties:
|
|
570
|
+
"a b".split() -> ["a", "b"]
|
|
571
|
+
|
|
572
|
+
JavaScript's split() without args returns the whole string:
|
|
573
|
+
"a b".split() -> ["a b"]
|
|
574
|
+
|
|
575
|
+
Fix: str.trim().split(/\\s+/)
|
|
576
|
+
"""
|
|
577
|
+
if sep is None:
|
|
578
|
+
# Python's default: split on whitespace and filter empties
|
|
579
|
+
trimmed = Call(Member(self.this, "trim"), [])
|
|
580
|
+
return Call(Member(trimmed, "split"), [Identifier(r"/\s+/")])
|
|
581
|
+
return None # Fall through for explicit separator
|
|
487
582
|
|
|
488
|
-
def join(self, iterable:
|
|
583
|
+
def join(self, iterable: Expr) -> Expr:
|
|
489
584
|
"""str.join(iterable) -> iterable.join(str)"""
|
|
490
|
-
return
|
|
585
|
+
return Call(Member(iterable, "join"), [self.this])
|
|
586
|
+
|
|
587
|
+
def find(self, sub: Expr) -> Expr:
|
|
588
|
+
"""str.find(sub) -> str.indexOf(sub)"""
|
|
589
|
+
return Call(Member(self.this, "indexOf"), [sub])
|
|
590
|
+
|
|
591
|
+
def rfind(self, sub: Expr) -> Expr:
|
|
592
|
+
"""str.rfind(sub) -> str.lastIndexOf(sub)"""
|
|
593
|
+
return Call(Member(self.this, "lastIndexOf"), [sub])
|
|
594
|
+
|
|
595
|
+
def count(self, sub: Expr) -> Expr:
|
|
596
|
+
"""str.count(sub) -> (str.split(sub).length - 1)"""
|
|
597
|
+
return Binary(
|
|
598
|
+
Member(Call(Member(self.this, "split"), [sub]), "length"),
|
|
599
|
+
"-",
|
|
600
|
+
Literal(1),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
def isdigit(self) -> Expr:
|
|
604
|
+
r"""str.isdigit() -> /^\d+$/.test(str)"""
|
|
605
|
+
return Call(
|
|
606
|
+
Member(Identifier("/^\\d+$/"), "test"),
|
|
607
|
+
[self.this],
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
def isalpha(self) -> Expr:
|
|
611
|
+
r"""str.isalpha() -> /^[a-zA-Z]+$/.test(str)"""
|
|
612
|
+
return Call(
|
|
613
|
+
Member(Identifier("/^[a-zA-Z]+$/"), "test"),
|
|
614
|
+
[self.this],
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
def isalnum(self) -> Expr:
|
|
618
|
+
r"""str.isalnum() -> /^[a-zA-Z0-9]+$/.test(str)"""
|
|
619
|
+
return Call(
|
|
620
|
+
Member(Identifier("/^[a-zA-Z0-9]+$/"), "test"),
|
|
621
|
+
[self.this],
|
|
622
|
+
)
|
|
491
623
|
|
|
492
624
|
|
|
493
625
|
STR_METHODS = {k for k in StringMethods.__dict__ if not k.startswith("_")}
|
|
@@ -498,58 +630,118 @@ class ListMethods(BuiltinMethods):
|
|
|
498
630
|
|
|
499
631
|
@classmethod
|
|
500
632
|
@override
|
|
501
|
-
def __runtime_check__(cls, expr:
|
|
502
|
-
return
|
|
633
|
+
def __runtime_check__(cls, expr: Expr) -> Expr:
|
|
634
|
+
return Call(Member(Identifier("Array"), "isArray"), [expr])
|
|
503
635
|
|
|
504
636
|
@classmethod
|
|
505
637
|
@override
|
|
506
638
|
def __methods__(cls) -> set[str]:
|
|
507
639
|
return LIST_METHODS
|
|
508
640
|
|
|
509
|
-
def append(self, value:
|
|
510
|
-
"""list.append(value) -> (list.push(value), undefined)"""
|
|
511
|
-
|
|
641
|
+
def append(self, value: Expr) -> Expr:
|
|
642
|
+
"""list.append(value) -> (list.push(value), undefined)[1]"""
|
|
643
|
+
# Returns undefined to match Python's None return
|
|
644
|
+
return Subscript(
|
|
645
|
+
Array([Call(Member(self.this, "push"), [value]), Undefined()]),
|
|
646
|
+
Literal(1),
|
|
647
|
+
)
|
|
512
648
|
|
|
513
|
-
def extend(self, iterable:
|
|
514
|
-
"""list.extend(iterable) -> (list.push(...iterable), undefined)"""
|
|
515
|
-
return
|
|
516
|
-
[
|
|
649
|
+
def extend(self, iterable: Expr) -> Expr:
|
|
650
|
+
"""list.extend(iterable) -> (list.push(...iterable), undefined)[1]"""
|
|
651
|
+
return Subscript(
|
|
652
|
+
Array([Call(Member(self.this, "push"), [Spread(iterable)]), Undefined()]),
|
|
653
|
+
Literal(1),
|
|
517
654
|
)
|
|
518
655
|
|
|
519
|
-
def pop(self, index:
|
|
656
|
+
def pop(self, index: Expr | None = None) -> Expr | None:
|
|
520
657
|
"""list.pop() or list.pop(index)"""
|
|
521
658
|
if index is None:
|
|
522
659
|
return None # Fall through to default .pop()
|
|
523
|
-
return
|
|
524
|
-
|
|
660
|
+
return Subscript(
|
|
661
|
+
Call(Member(self.this, "splice"), [index, Literal(1)]), Literal(0)
|
|
525
662
|
)
|
|
526
663
|
|
|
527
|
-
def copy(self) ->
|
|
664
|
+
def copy(self) -> Expr:
|
|
528
665
|
"""list.copy() -> list.slice()"""
|
|
529
|
-
return
|
|
666
|
+
return Call(Member(self.this, "slice"), [])
|
|
530
667
|
|
|
531
|
-
def count(self, value:
|
|
668
|
+
def count(self, value: Expr) -> Expr:
|
|
532
669
|
"""list.count(value) -> list.filter(v => v === value).length"""
|
|
533
|
-
return
|
|
534
|
-
|
|
535
|
-
self.this,
|
|
536
|
-
"
|
|
537
|
-
[JSArrowFunction("v", JSBinary(JSIdentifier("v"), "===", value))],
|
|
670
|
+
return Member(
|
|
671
|
+
Call(
|
|
672
|
+
Member(self.this, "filter"),
|
|
673
|
+
[Arrow(["v"], Binary(Identifier("v"), "===", value))],
|
|
538
674
|
),
|
|
539
675
|
"length",
|
|
540
676
|
)
|
|
541
677
|
|
|
542
|
-
def index(self, value:
|
|
678
|
+
def index(self, value: Expr) -> Expr:
|
|
543
679
|
"""list.index(value) -> list.indexOf(value)"""
|
|
544
|
-
return
|
|
680
|
+
return Call(Member(self.this, "indexOf"), [value])
|
|
681
|
+
|
|
682
|
+
def reverse(self) -> Expr:
|
|
683
|
+
"""list.reverse() -> (list.reverse(), undefined)[1]"""
|
|
684
|
+
return Subscript(
|
|
685
|
+
Array([Call(Member(self.this, "reverse"), []), Undefined()]),
|
|
686
|
+
Literal(1),
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
def sort(self) -> Expr:
|
|
690
|
+
"""list.sort() -> (list.sort(), undefined)[1]"""
|
|
691
|
+
return Subscript(
|
|
692
|
+
Array([Call(Member(self.this, "sort"), []), Undefined()]),
|
|
693
|
+
Literal(1),
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
def clear(self) -> Expr:
|
|
697
|
+
"""list.clear() -> (list.length = 0, undefined)[1]"""
|
|
698
|
+
# Setting length to 0 clears the array
|
|
699
|
+
return Subscript(
|
|
700
|
+
Array([Binary(Member(self.this, "length"), "=", Literal(0)), Undefined()]),
|
|
701
|
+
Literal(1),
|
|
702
|
+
)
|
|
545
703
|
|
|
546
|
-
def
|
|
547
|
-
"""list.
|
|
548
|
-
return
|
|
704
|
+
def insert(self, index: Expr, value: Expr) -> Expr:
|
|
705
|
+
"""list.insert(index, value) -> (list.splice(index, 0, value), undefined)[1]"""
|
|
706
|
+
return Subscript(
|
|
707
|
+
Array(
|
|
708
|
+
[
|
|
709
|
+
Call(Member(self.this, "splice"), [index, Literal(0), value]),
|
|
710
|
+
Undefined(),
|
|
711
|
+
]
|
|
712
|
+
),
|
|
713
|
+
Literal(1),
|
|
714
|
+
)
|
|
549
715
|
|
|
550
|
-
def
|
|
551
|
-
"""list.
|
|
552
|
-
|
|
716
|
+
def remove(self, value: Expr) -> Expr:
|
|
717
|
+
"""list.remove(value) -> safe removal with error on not found.
|
|
718
|
+
|
|
719
|
+
Python raises ValueError if value not in list. We generate:
|
|
720
|
+
(($i) => $i < 0 ? (() => { throw new Error(...) })() : list.splice($i, 1))(list.indexOf(value))
|
|
721
|
+
"""
|
|
722
|
+
idx = Identifier("$i")
|
|
723
|
+
index_call = Call(Member(self.this, "indexOf"), [value])
|
|
724
|
+
# IIFE that throws using Arrow with statement body
|
|
725
|
+
throw_iife = Call(
|
|
726
|
+
Arrow(
|
|
727
|
+
[],
|
|
728
|
+
[
|
|
729
|
+
Throw(
|
|
730
|
+
New(
|
|
731
|
+
Identifier("Error"),
|
|
732
|
+
[Literal("list.remove(x): x not in list")],
|
|
733
|
+
)
|
|
734
|
+
)
|
|
735
|
+
],
|
|
736
|
+
),
|
|
737
|
+
[],
|
|
738
|
+
)
|
|
739
|
+
safe_splice = Ternary(
|
|
740
|
+
Binary(idx, "<", Literal(0)),
|
|
741
|
+
throw_iife,
|
|
742
|
+
Call(Member(self.this, "splice"), [idx, Literal(1)]),
|
|
743
|
+
)
|
|
744
|
+
return Call(Arrow(["$i"], safe_splice), [index_call])
|
|
553
745
|
|
|
554
746
|
|
|
555
747
|
LIST_METHODS = {k for k in ListMethods.__dict__ if not k.startswith("_")}
|
|
@@ -560,40 +752,82 @@ class DictMethods(BuiltinMethods):
|
|
|
560
752
|
|
|
561
753
|
@classmethod
|
|
562
754
|
@override
|
|
563
|
-
def __runtime_check__(cls, expr:
|
|
564
|
-
return
|
|
755
|
+
def __runtime_check__(cls, expr: Expr) -> Expr:
|
|
756
|
+
return Binary(expr, "instanceof", Identifier("Map"))
|
|
565
757
|
|
|
566
758
|
@classmethod
|
|
567
759
|
@override
|
|
568
760
|
def __methods__(cls) -> set[str]:
|
|
569
761
|
return DICT_METHODS
|
|
570
762
|
|
|
571
|
-
def get(self, key:
|
|
763
|
+
def get(self, key: Expr, default: Expr | None = None) -> Expr | None:
|
|
572
764
|
"""dict.get(key, default) -> dict.get(key) ?? default"""
|
|
573
765
|
if default is None:
|
|
574
766
|
return None # Fall through to default .get()
|
|
575
|
-
return
|
|
767
|
+
return Binary(Call(Member(self.this, "get"), [key]), "??", default)
|
|
576
768
|
|
|
577
|
-
def keys(self) ->
|
|
769
|
+
def keys(self) -> Expr:
|
|
578
770
|
"""dict.keys() -> [...dict.keys()]"""
|
|
579
|
-
return
|
|
771
|
+
return Array([Spread(Call(Member(self.this, "keys"), []))])
|
|
580
772
|
|
|
581
|
-
def values(self) ->
|
|
773
|
+
def values(self) -> Expr:
|
|
582
774
|
"""dict.values() -> [...dict.values()]"""
|
|
583
|
-
return
|
|
775
|
+
return Array([Spread(Call(Member(self.this, "values"), []))])
|
|
584
776
|
|
|
585
|
-
def items(self) ->
|
|
777
|
+
def items(self) -> Expr:
|
|
586
778
|
"""dict.items() -> [...dict.entries()]"""
|
|
587
|
-
return
|
|
779
|
+
return Array([Spread(Call(Member(self.this, "entries"), []))])
|
|
588
780
|
|
|
589
|
-
def copy(self) ->
|
|
781
|
+
def copy(self) -> Expr:
|
|
590
782
|
"""dict.copy() -> new Map(dict.entries())"""
|
|
591
|
-
return
|
|
783
|
+
return New(Identifier("Map"), [Call(Member(self.this, "entries"), [])])
|
|
592
784
|
|
|
593
|
-
def clear(self) ->
|
|
785
|
+
def clear(self) -> Expr | None:
|
|
594
786
|
"""dict.clear() doesn't need transformation."""
|
|
595
787
|
return None
|
|
596
788
|
|
|
789
|
+
def pop(self, key: Expr, default: Expr | None = None) -> Expr:
|
|
790
|
+
"""dict.pop(key, default) -> complex expression to get and delete"""
|
|
791
|
+
# (v => (dict.delete(key), v))(dict.get(key) ?? default)
|
|
792
|
+
get_val = Call(Member(self.this, "get"), [key])
|
|
793
|
+
if default is not None:
|
|
794
|
+
get_val = Binary(get_val, "??", default)
|
|
795
|
+
delete_call = Call(Member(self.this, "delete"), [key])
|
|
796
|
+
return Call(
|
|
797
|
+
Arrow(
|
|
798
|
+
["$v"], Subscript(Array([delete_call, Identifier("$v")]), Literal(1))
|
|
799
|
+
),
|
|
800
|
+
[get_val],
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
def update(self, other: Expr) -> Expr:
|
|
804
|
+
"""dict.update(other) -> other.forEach((v, k) => dict.set(k, v))"""
|
|
805
|
+
return Call(
|
|
806
|
+
Member(other, "forEach"),
|
|
807
|
+
[
|
|
808
|
+
Arrow(
|
|
809
|
+
["$v", "$k"],
|
|
810
|
+
Call(
|
|
811
|
+
Member(self.this, "set"), [Identifier("$k"), Identifier("$v")]
|
|
812
|
+
),
|
|
813
|
+
)
|
|
814
|
+
],
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
def setdefault(self, key: Expr, default: Expr | None = None) -> Expr:
|
|
818
|
+
"""dict.setdefault(key, default) -> dict.has(key) ? dict.get(key) : (dict.set(key, default), default)[1]"""
|
|
819
|
+
default_val = default if default is not None else Literal(None)
|
|
820
|
+
return Ternary(
|
|
821
|
+
Call(Member(self.this, "has"), [key]),
|
|
822
|
+
Call(Member(self.this, "get"), [key]),
|
|
823
|
+
Subscript(
|
|
824
|
+
Array(
|
|
825
|
+
[Call(Member(self.this, "set"), [key, default_val]), default_val]
|
|
826
|
+
),
|
|
827
|
+
Literal(1),
|
|
828
|
+
),
|
|
829
|
+
)
|
|
830
|
+
|
|
597
831
|
|
|
598
832
|
DICT_METHODS = {k for k in DictMethods.__dict__ if not k.startswith("_")}
|
|
599
833
|
|
|
@@ -603,30 +837,96 @@ class SetMethods(BuiltinMethods):
|
|
|
603
837
|
|
|
604
838
|
@classmethod
|
|
605
839
|
@override
|
|
606
|
-
def __runtime_check__(cls, expr:
|
|
607
|
-
return
|
|
840
|
+
def __runtime_check__(cls, expr: Expr) -> Expr:
|
|
841
|
+
return Binary(expr, "instanceof", Identifier("Set"))
|
|
608
842
|
|
|
609
843
|
@classmethod
|
|
610
844
|
@override
|
|
611
845
|
def __methods__(cls) -> set[str]:
|
|
612
846
|
return SET_METHODS
|
|
613
847
|
|
|
614
|
-
def add(self, value:
|
|
848
|
+
def add(self, value: Expr) -> Expr | None:
|
|
615
849
|
"""set.add() doesn't need transformation."""
|
|
616
850
|
return None
|
|
617
851
|
|
|
618
|
-
def remove(self, value:
|
|
852
|
+
def remove(self, value: Expr) -> Expr:
|
|
619
853
|
"""set.remove(value) -> set.delete(value)"""
|
|
620
|
-
return
|
|
854
|
+
return Call(Member(self.this, "delete"), [value])
|
|
621
855
|
|
|
622
|
-
def discard(self, value:
|
|
856
|
+
def discard(self, value: Expr) -> Expr:
|
|
623
857
|
"""set.discard(value) -> set.delete(value)"""
|
|
624
|
-
return
|
|
858
|
+
return Call(Member(self.this, "delete"), [value])
|
|
625
859
|
|
|
626
|
-
def clear(self) ->
|
|
860
|
+
def clear(self) -> Expr | None:
|
|
627
861
|
"""set.clear() doesn't need transformation."""
|
|
628
862
|
return None
|
|
629
863
|
|
|
864
|
+
def copy(self) -> Expr:
|
|
865
|
+
"""set.copy() -> new Set(set)"""
|
|
866
|
+
return New(Identifier("Set"), [self.this])
|
|
867
|
+
|
|
868
|
+
def pop(self) -> Expr:
|
|
869
|
+
"""set.pop() -> (v => (set.delete(v), v))(set.values().next().value)"""
|
|
870
|
+
get_first = Member(
|
|
871
|
+
Call(Member(Call(Member(self.this, "values"), []), "next"), []), "value"
|
|
872
|
+
)
|
|
873
|
+
delete_call = Call(Member(self.this, "delete"), [Identifier("$v")])
|
|
874
|
+
return Call(
|
|
875
|
+
Arrow(
|
|
876
|
+
["$v"], Subscript(Array([delete_call, Identifier("$v")]), Literal(1))
|
|
877
|
+
),
|
|
878
|
+
[get_first],
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
def update(self, other: Expr) -> Expr:
|
|
882
|
+
"""set.update(other) -> other.forEach(v => set.add(v))"""
|
|
883
|
+
return Call(
|
|
884
|
+
Member(other, "forEach"),
|
|
885
|
+
[Arrow(["$v"], Call(Member(self.this, "add"), [Identifier("$v")]))],
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
def intersection(self, other: Expr) -> Expr:
|
|
889
|
+
"""set.intersection(other) -> new Set([...set].filter(x => other.has(x)))"""
|
|
890
|
+
filtered = Call(
|
|
891
|
+
Member(Array([Spread(self.this)]), "filter"),
|
|
892
|
+
[Arrow(["$x"], Call(Member(other, "has"), [Identifier("$x")]))],
|
|
893
|
+
)
|
|
894
|
+
return New(Identifier("Set"), [filtered])
|
|
895
|
+
|
|
896
|
+
def union(self, other: Expr) -> Expr:
|
|
897
|
+
"""set.union(other) -> new Set([...set, ...other])"""
|
|
898
|
+
return New(
|
|
899
|
+
Identifier("Set"),
|
|
900
|
+
[Array([Spread(self.this), Spread(other)])],
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
def difference(self, other: Expr) -> Expr:
|
|
904
|
+
"""set.difference(other) -> new Set([...set].filter(x => !other.has(x)))"""
|
|
905
|
+
filtered = Call(
|
|
906
|
+
Member(Array([Spread(self.this)]), "filter"),
|
|
907
|
+
[
|
|
908
|
+
Arrow(
|
|
909
|
+
["$x"],
|
|
910
|
+
Unary("!", Call(Member(other, "has"), [Identifier("$x")])),
|
|
911
|
+
)
|
|
912
|
+
],
|
|
913
|
+
)
|
|
914
|
+
return New(Identifier("Set"), [filtered])
|
|
915
|
+
|
|
916
|
+
def issubset(self, other: Expr) -> Expr:
|
|
917
|
+
"""set.issubset(other) -> [...set].every(x => other.has(x))"""
|
|
918
|
+
return Call(
|
|
919
|
+
Member(Array([Spread(self.this)]), "every"),
|
|
920
|
+
[Arrow(["$x"], Call(Member(other, "has"), [Identifier("$x")]))],
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
def issuperset(self, other: Expr) -> Expr:
|
|
924
|
+
"""set.issuperset(other) -> [...other].every(x => set.has(x))"""
|
|
925
|
+
return Call(
|
|
926
|
+
Member(Array([Spread(other)]), "every"),
|
|
927
|
+
[Arrow(["$x"], Call(Member(self.this, "has"), [Identifier("$x")]))],
|
|
928
|
+
)
|
|
929
|
+
|
|
630
930
|
|
|
631
931
|
SET_METHODS = {k for k in SetMethods.__dict__ if not k.startswith("_")}
|
|
632
932
|
|
|
@@ -636,7 +936,7 @@ ALL_METHODS = STR_METHODS | LIST_METHODS | DICT_METHODS | SET_METHODS
|
|
|
636
936
|
|
|
637
937
|
# Method classes in priority order (higher priority = later in list = outermost ternary)
|
|
638
938
|
# We prefer string/list semantics first, then set, then dict.
|
|
639
|
-
METHOD_CLASSES: list[type[BuiltinMethods]] = [
|
|
939
|
+
METHOD_CLASSES: builtins.list[builtins.type[BuiltinMethods]] = [
|
|
640
940
|
DictMethods,
|
|
641
941
|
SetMethods,
|
|
642
942
|
ListMethods,
|
|
@@ -645,8 +945,12 @@ METHOD_CLASSES: list[type[BuiltinMethods]] = [
|
|
|
645
945
|
|
|
646
946
|
|
|
647
947
|
def _try_dispatch_method(
|
|
648
|
-
cls: type[BuiltinMethods],
|
|
649
|
-
|
|
948
|
+
cls: builtins.type[BuiltinMethods],
|
|
949
|
+
obj: Expr,
|
|
950
|
+
method: str,
|
|
951
|
+
args: list[Expr],
|
|
952
|
+
kwargs: builtins.dict[builtins.str, Expr] | None = None,
|
|
953
|
+
) -> Expr | None:
|
|
650
954
|
"""Try to dispatch a method call to a specific builtin class.
|
|
651
955
|
|
|
652
956
|
Returns the transformed expression, or None if the method returns None
|
|
@@ -657,62 +961,76 @@ def _try_dispatch_method(
|
|
|
657
961
|
|
|
658
962
|
try:
|
|
659
963
|
handler = cls(obj)
|
|
660
|
-
method_fn = getattr(handler, method, None)
|
|
964
|
+
method_fn = builtins.getattr(handler, method, None)
|
|
661
965
|
if method_fn is None:
|
|
662
966
|
return None
|
|
967
|
+
if kwargs:
|
|
968
|
+
return method_fn(*args, **kwargs)
|
|
663
969
|
return method_fn(*args)
|
|
664
970
|
except TypeError:
|
|
665
971
|
return None
|
|
666
972
|
|
|
667
973
|
|
|
668
|
-
def emit_method(
|
|
974
|
+
def emit_method(
|
|
975
|
+
obj: Expr,
|
|
976
|
+
method: str,
|
|
977
|
+
args: list[Expr],
|
|
978
|
+
kwargs: builtins.dict[builtins.str, Expr] | None = None,
|
|
979
|
+
) -> Expr | None:
|
|
669
980
|
"""Emit a method call, handling Python builtin methods.
|
|
670
981
|
|
|
671
|
-
For known literal types (
|
|
982
|
+
For known literal types (Literal str, Template, Array, New Set/Map),
|
|
672
983
|
dispatches directly without runtime checks.
|
|
673
984
|
|
|
674
985
|
For unknown types, builds a ternary chain that checks types at runtime
|
|
675
986
|
and dispatches to the appropriate method implementation.
|
|
676
987
|
|
|
677
988
|
Returns:
|
|
678
|
-
|
|
989
|
+
Expr if the method should be transpiled specially
|
|
679
990
|
None if the method should be emitted as a regular method call
|
|
680
991
|
"""
|
|
681
992
|
if method not in ALL_METHODS:
|
|
682
993
|
return None
|
|
683
994
|
|
|
684
995
|
# Fast path: known literal types - dispatch directly without runtime checks
|
|
685
|
-
if builtins.isinstance(obj, (
|
|
996
|
+
if builtins.isinstance(obj, Literal) and builtins.isinstance(obj.value, str):
|
|
997
|
+
if method in StringMethods.__methods__():
|
|
998
|
+
result = _try_dispatch_method(StringMethods, obj, method, args, kwargs)
|
|
999
|
+
if result is not None:
|
|
1000
|
+
return result
|
|
1001
|
+
return None
|
|
1002
|
+
|
|
1003
|
+
if builtins.isinstance(obj, Template):
|
|
686
1004
|
if method in StringMethods.__methods__():
|
|
687
|
-
result = _try_dispatch_method(StringMethods, obj, method, args)
|
|
1005
|
+
result = _try_dispatch_method(StringMethods, obj, method, args, kwargs)
|
|
688
1006
|
if result is not None:
|
|
689
1007
|
return result
|
|
690
1008
|
return None
|
|
691
1009
|
|
|
692
|
-
if builtins.isinstance(obj,
|
|
1010
|
+
if builtins.isinstance(obj, Array):
|
|
693
1011
|
if method in ListMethods.__methods__():
|
|
694
|
-
result = _try_dispatch_method(ListMethods, obj, method, args)
|
|
1012
|
+
result = _try_dispatch_method(ListMethods, obj, method, args, kwargs)
|
|
695
1013
|
if result is not None:
|
|
696
1014
|
return result
|
|
697
1015
|
return None
|
|
698
1016
|
|
|
699
1017
|
# Fast path: new Set(...) and new Map(...) are known types
|
|
700
|
-
if builtins.isinstance(obj,
|
|
1018
|
+
if builtins.isinstance(obj, New) and builtins.isinstance(obj.ctor, Identifier):
|
|
701
1019
|
if obj.ctor.name == "Set" and method in SetMethods.__methods__():
|
|
702
|
-
result = _try_dispatch_method(SetMethods, obj, method, args)
|
|
1020
|
+
result = _try_dispatch_method(SetMethods, obj, method, args, kwargs)
|
|
703
1021
|
if result is not None:
|
|
704
1022
|
return result
|
|
705
1023
|
return None
|
|
706
1024
|
if obj.ctor.name == "Map" and method in DictMethods.__methods__():
|
|
707
|
-
result = _try_dispatch_method(DictMethods, obj, method, args)
|
|
1025
|
+
result = _try_dispatch_method(DictMethods, obj, method, args, kwargs)
|
|
708
1026
|
if result is not None:
|
|
709
1027
|
return result
|
|
710
1028
|
return None
|
|
711
1029
|
|
|
712
1030
|
# Slow path: unknown type - build ternary chain with runtime type checks
|
|
713
1031
|
# Start with the default fallback (regular method call)
|
|
714
|
-
default_expr =
|
|
715
|
-
expr:
|
|
1032
|
+
default_expr = Call(Member(obj, method), args)
|
|
1033
|
+
expr: Expr = default_expr
|
|
716
1034
|
|
|
717
1035
|
# Apply in increasing priority so that later (higher priority) wrappers
|
|
718
1036
|
# end up outermost in the final expression.
|
|
@@ -720,9 +1038,9 @@ def emit_method(obj: JSExpr, method: str, args: list[JSExpr]) -> JSExpr | None:
|
|
|
720
1038
|
if method not in cls.__methods__():
|
|
721
1039
|
continue
|
|
722
1040
|
|
|
723
|
-
dispatch_expr = _try_dispatch_method(cls, obj, method, args)
|
|
1041
|
+
dispatch_expr = _try_dispatch_method(cls, obj, method, args, kwargs)
|
|
724
1042
|
if dispatch_expr is not None:
|
|
725
|
-
expr =
|
|
1043
|
+
expr = Ternary(cls.__runtime_check__(obj), dispatch_expr, expr)
|
|
726
1044
|
|
|
727
1045
|
# If we built ternaries, return them; otherwise return None to fall through
|
|
728
1046
|
if expr is not default_expr:
|