pulse-framework 0.1.62__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 +1493 -0
- pulse/_examples.py +29 -0
- pulse/app.py +1086 -0
- pulse/channel.py +607 -0
- pulse/cli/__init__.py +0 -0
- pulse/cli/cmd.py +575 -0
- pulse/cli/dependencies.py +181 -0
- pulse/cli/folder_lock.py +134 -0
- pulse/cli/helpers.py +271 -0
- pulse/cli/logging.py +102 -0
- pulse/cli/models.py +35 -0
- pulse/cli/packages.py +262 -0
- pulse/cli/processes.py +292 -0
- pulse/cli/secrets.py +39 -0
- pulse/cli/uvicorn_log_config.py +87 -0
- pulse/code_analysis.py +38 -0
- pulse/codegen/__init__.py +0 -0
- pulse/codegen/codegen.py +359 -0
- pulse/codegen/templates/__init__.py +0 -0
- pulse/codegen/templates/layout.py +106 -0
- pulse/codegen/templates/route.py +345 -0
- pulse/codegen/templates/routes_ts.py +42 -0
- pulse/codegen/utils.py +20 -0
- pulse/component.py +237 -0
- pulse/components/__init__.py +0 -0
- pulse/components/for_.py +83 -0
- pulse/components/if_.py +86 -0
- pulse/components/react_router.py +94 -0
- pulse/context.py +108 -0
- pulse/cookies.py +322 -0
- pulse/decorators.py +344 -0
- pulse/dom/__init__.py +0 -0
- pulse/dom/elements.py +1024 -0
- pulse/dom/events.py +445 -0
- pulse/dom/props.py +1250 -0
- pulse/dom/svg.py +0 -0
- pulse/dom/tags.py +328 -0
- pulse/dom/tags.pyi +480 -0
- pulse/env.py +178 -0
- pulse/form.py +538 -0
- pulse/helpers.py +541 -0
- pulse/hooks/__init__.py +0 -0
- pulse/hooks/core.py +452 -0
- pulse/hooks/effects.py +88 -0
- pulse/hooks/init.py +668 -0
- pulse/hooks/runtime.py +464 -0
- pulse/hooks/setup.py +254 -0
- pulse/hooks/stable.py +138 -0
- pulse/hooks/state.py +192 -0
- pulse/js/__init__.py +125 -0
- pulse/js/__init__.pyi +115 -0
- pulse/js/_types.py +299 -0
- pulse/js/array.py +339 -0
- pulse/js/console.py +50 -0
- pulse/js/date.py +119 -0
- pulse/js/document.py +145 -0
- pulse/js/error.py +140 -0
- pulse/js/json.py +66 -0
- pulse/js/map.py +97 -0
- pulse/js/math.py +69 -0
- pulse/js/navigator.py +79 -0
- pulse/js/number.py +57 -0
- pulse/js/obj.py +81 -0
- pulse/js/object.py +172 -0
- pulse/js/promise.py +172 -0
- pulse/js/pulse.py +115 -0
- pulse/js/react.py +495 -0
- pulse/js/regexp.py +57 -0
- pulse/js/set.py +124 -0
- pulse/js/string.py +38 -0
- pulse/js/weakmap.py +53 -0
- pulse/js/weakset.py +48 -0
- pulse/js/window.py +205 -0
- pulse/messages.py +202 -0
- pulse/middleware.py +471 -0
- pulse/plugin.py +96 -0
- pulse/proxy.py +242 -0
- pulse/py.typed +0 -0
- pulse/queries/__init__.py +0 -0
- pulse/queries/client.py +609 -0
- pulse/queries/common.py +101 -0
- pulse/queries/effect.py +55 -0
- pulse/queries/infinite_query.py +1418 -0
- pulse/queries/mutation.py +295 -0
- pulse/queries/protocol.py +136 -0
- pulse/queries/query.py +1314 -0
- pulse/queries/store.py +120 -0
- pulse/react_component.py +88 -0
- pulse/reactive.py +1208 -0
- pulse/reactive_extensions.py +1172 -0
- pulse/render_session.py +768 -0
- pulse/renderer.py +584 -0
- pulse/request.py +205 -0
- pulse/routing.py +598 -0
- pulse/serializer.py +279 -0
- pulse/state.py +556 -0
- pulse/test_helpers.py +15 -0
- pulse/transpiler/__init__.py +111 -0
- pulse/transpiler/assets.py +81 -0
- pulse/transpiler/builtins.py +1029 -0
- pulse/transpiler/dynamic_import.py +130 -0
- pulse/transpiler/emit_context.py +49 -0
- pulse/transpiler/errors.py +96 -0
- pulse/transpiler/function.py +611 -0
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +341 -0
- pulse/transpiler/js_module.py +336 -0
- pulse/transpiler/modules/__init__.py +33 -0
- pulse/transpiler/modules/asyncio.py +57 -0
- pulse/transpiler/modules/json.py +24 -0
- pulse/transpiler/modules/math.py +265 -0
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +250 -0
- pulse/transpiler/modules/typing.py +63 -0
- pulse/transpiler/nodes.py +1987 -0
- pulse/transpiler/py_module.py +135 -0
- pulse/transpiler/transpiler.py +1100 -0
- pulse/transpiler/vdom.py +256 -0
- pulse/types/__init__.py +0 -0
- pulse/types/event_handler.py +50 -0
- pulse/user_session.py +386 -0
- pulse/version.py +69 -0
- pulse_framework-0.1.62.dist-info/METADATA +198 -0
- pulse_framework-0.1.62.dist-info/RECORD +126 -0
- pulse_framework-0.1.62.dist-info/WHEEL +4 -0
- pulse_framework-0.1.62.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Python asyncio module transpilation to JavaScript Promise operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, cast, final
|
|
6
|
+
|
|
7
|
+
from pulse.transpiler.nodes import (
|
|
8
|
+
Array,
|
|
9
|
+
Binary,
|
|
10
|
+
Call,
|
|
11
|
+
Expr,
|
|
12
|
+
Identifier,
|
|
13
|
+
Literal,
|
|
14
|
+
Member,
|
|
15
|
+
Ternary,
|
|
16
|
+
)
|
|
17
|
+
from pulse.transpiler.py_module import PyModule
|
|
18
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
19
|
+
|
|
20
|
+
_Promise = Identifier("Promise")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
class PyAsyncio(PyModule):
|
|
25
|
+
"""Provides transpilation for Python asyncio functions to JavaScript Promise methods."""
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def gather(*coros: Any, return_exceptions: Any = False, ctx: Transpiler) -> Expr:
|
|
29
|
+
"""Transpile asyncio.gather to Promise.all or Promise.allSettled.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
*coros: Variable number of coroutine/promise expressions
|
|
33
|
+
return_exceptions: If True, use Promise.allSettled
|
|
34
|
+
ctx: Transpiler context
|
|
35
|
+
"""
|
|
36
|
+
promises = Array([ctx.emit_expr(c) for c in coros])
|
|
37
|
+
all_call = Call(Member(_Promise, "all"), [promises])
|
|
38
|
+
all_settled_call = Call(Member(_Promise, "allSettled"), [promises])
|
|
39
|
+
|
|
40
|
+
# Optimized: literal True -> allSettled
|
|
41
|
+
if return_exceptions is True or (
|
|
42
|
+
isinstance(return_exceptions, Literal) and return_exceptions.value is True
|
|
43
|
+
):
|
|
44
|
+
return all_settled_call
|
|
45
|
+
|
|
46
|
+
# Optimized: literal False or default -> all
|
|
47
|
+
if return_exceptions is False or (
|
|
48
|
+
isinstance(return_exceptions, Literal) and return_exceptions.value is False
|
|
49
|
+
):
|
|
50
|
+
return all_call
|
|
51
|
+
|
|
52
|
+
# General case: emit ternary on the expression
|
|
53
|
+
return Ternary(
|
|
54
|
+
Binary(ctx.emit_expr(cast(Any, return_exceptions)), "===", Literal(True)),
|
|
55
|
+
all_settled_call,
|
|
56
|
+
all_call,
|
|
57
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Python json module transpilation to JavaScript JSON."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, final
|
|
6
|
+
|
|
7
|
+
from pulse.transpiler.nodes import Call, Expr, Identifier, Member
|
|
8
|
+
from pulse.transpiler.py_module import PyModule
|
|
9
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
10
|
+
|
|
11
|
+
_JSON = Identifier("JSON")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@final
|
|
15
|
+
class PyJson(PyModule):
|
|
16
|
+
"""Provides transpilation for Python json functions to JavaScript."""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def dumps(obj: Any, *, ctx: Transpiler) -> Expr:
|
|
20
|
+
return Call(Member(_JSON, "stringify"), [ctx.emit_expr(obj)])
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def loads(s: Any, *, ctx: Transpiler) -> Expr:
|
|
24
|
+
return Call(Member(_JSON, "parse"), [ctx.emit_expr(s)])
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Python math module transpilation to JavaScript Math.
|
|
2
|
+
|
|
3
|
+
Provides transpilation from Python's `math` module to JavaScript's `Math` object.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, final
|
|
9
|
+
|
|
10
|
+
from pulse.transpiler.nodes import (
|
|
11
|
+
Binary,
|
|
12
|
+
Call,
|
|
13
|
+
Expr,
|
|
14
|
+
Identifier,
|
|
15
|
+
Literal,
|
|
16
|
+
Member,
|
|
17
|
+
Unary,
|
|
18
|
+
)
|
|
19
|
+
from pulse.transpiler.py_module import PyModule
|
|
20
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
21
|
+
|
|
22
|
+
# Helpers for building Math.* calls
|
|
23
|
+
_Math = Identifier("Math")
|
|
24
|
+
_Number = Identifier("Number")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _math_prop(name: str) -> Expr:
|
|
28
|
+
return Member(_Math, name)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _math_call(name: str, args: list[Expr]) -> Expr:
|
|
32
|
+
return Call(Member(_Math, name), args)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _number_call(name: str, args: list[Expr]) -> Expr:
|
|
36
|
+
return Call(Member(_Number, name), args)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@final
|
|
40
|
+
class PyMath(PyModule):
|
|
41
|
+
"""Provides transpilation for Python math functions to JavaScript."""
|
|
42
|
+
|
|
43
|
+
# Constants
|
|
44
|
+
pi = _math_prop("PI")
|
|
45
|
+
e = _math_prop("E")
|
|
46
|
+
tau = Binary(Literal(2), "*", _math_prop("PI"))
|
|
47
|
+
inf = Identifier("Infinity")
|
|
48
|
+
nan = Identifier("NaN")
|
|
49
|
+
|
|
50
|
+
# Basic functions
|
|
51
|
+
@staticmethod
|
|
52
|
+
def acos(x: Any, *, ctx: Transpiler) -> Expr:
|
|
53
|
+
return _math_call("acos", [ctx.emit_expr(x)])
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def acosh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
57
|
+
return _math_call("acosh", [ctx.emit_expr(x)])
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def asin(x: Any, *, ctx: Transpiler) -> Expr:
|
|
61
|
+
return _math_call("asin", [ctx.emit_expr(x)])
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def asinh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
65
|
+
return _math_call("asinh", [ctx.emit_expr(x)])
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def atan(x: Any, *, ctx: Transpiler) -> Expr:
|
|
69
|
+
return _math_call("atan", [ctx.emit_expr(x)])
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def atan2(y: Any, x: Any, *, ctx: Transpiler) -> Expr:
|
|
73
|
+
return _math_call("atan2", [ctx.emit_expr(y), ctx.emit_expr(x)])
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def atanh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
77
|
+
return _math_call("atanh", [ctx.emit_expr(x)])
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def cbrt(x: Any, *, ctx: Transpiler) -> Expr:
|
|
81
|
+
return _math_call("cbrt", [ctx.emit_expr(x)])
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def ceil(x: Any, *, ctx: Transpiler) -> Expr:
|
|
85
|
+
return _math_call("ceil", [ctx.emit_expr(x)])
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def copysign(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
|
|
89
|
+
# Math.sign(y) * Math.abs(x)
|
|
90
|
+
return Binary(
|
|
91
|
+
_math_call("sign", [ctx.emit_expr(y)]),
|
|
92
|
+
"*",
|
|
93
|
+
_math_call("abs", [ctx.emit_expr(x)]),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def cos(x: Any, *, ctx: Transpiler) -> Expr:
|
|
98
|
+
return _math_call("cos", [ctx.emit_expr(x)])
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def cosh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
102
|
+
return _math_call("cosh", [ctx.emit_expr(x)])
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def degrees(x: Any, *, ctx: Transpiler) -> Expr:
|
|
106
|
+
# x * (180 / PI)
|
|
107
|
+
return Binary(
|
|
108
|
+
ctx.emit_expr(x),
|
|
109
|
+
"*",
|
|
110
|
+
Binary(Literal(180), "/", _math_prop("PI")),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def exp(x: Any, *, ctx: Transpiler) -> Expr:
|
|
115
|
+
return _math_call("exp", [ctx.emit_expr(x)])
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def exp2(x: Any, *, ctx: Transpiler) -> Expr:
|
|
119
|
+
# 2 ** x
|
|
120
|
+
return Binary(Literal(2), "**", ctx.emit_expr(x))
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def expm1(x: Any, *, ctx: Transpiler) -> Expr:
|
|
124
|
+
return _math_call("expm1", [ctx.emit_expr(x)])
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def fabs(x: Any, *, ctx: Transpiler) -> Expr:
|
|
128
|
+
return _math_call("abs", [ctx.emit_expr(x)])
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def floor(x: Any, *, ctx: Transpiler) -> Expr:
|
|
132
|
+
return _math_call("floor", [ctx.emit_expr(x)])
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def fmod(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
|
|
136
|
+
return Binary(ctx.emit_expr(x), "%", ctx.emit_expr(y))
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def hypot(*coords: Any, ctx: Transpiler) -> Expr:
|
|
140
|
+
return _math_call("hypot", [ctx.emit_expr(c) for c in coords])
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def isclose(
|
|
144
|
+
a: Any, b: Any, *, rel_tol: Any = 1e-9, abs_tol: Any = 0.0, ctx: Transpiler
|
|
145
|
+
) -> Expr:
|
|
146
|
+
# abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
|
|
147
|
+
a_expr = ctx.emit_expr(a)
|
|
148
|
+
b_expr = ctx.emit_expr(b)
|
|
149
|
+
rel_tol_expr = ctx.emit_expr(rel_tol)
|
|
150
|
+
abs_tol_expr = ctx.emit_expr(abs_tol)
|
|
151
|
+
|
|
152
|
+
abs_diff = _math_call("abs", [Binary(a_expr, "-", b_expr)])
|
|
153
|
+
max_abs = _math_call(
|
|
154
|
+
"max", [_math_call("abs", [a_expr]), _math_call("abs", [b_expr])]
|
|
155
|
+
)
|
|
156
|
+
rel_bound = Binary(rel_tol_expr, "*", max_abs)
|
|
157
|
+
max_bound = _math_call("max", [rel_bound, abs_tol_expr])
|
|
158
|
+
return Binary(abs_diff, "<=", max_bound)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def isfinite(x: Any, *, ctx: Transpiler) -> Expr:
|
|
162
|
+
return _number_call("isFinite", [ctx.emit_expr(x)])
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def isinf(x: Any, *, ctx: Transpiler) -> Expr:
|
|
166
|
+
# !Number.isFinite(x) && !Number.isNaN(x)
|
|
167
|
+
x_expr = ctx.emit_expr(x)
|
|
168
|
+
return Binary(
|
|
169
|
+
Unary("!", _number_call("isFinite", [x_expr])),
|
|
170
|
+
"&&",
|
|
171
|
+
Unary("!", _number_call("isNaN", [x_expr])),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def isnan(x: Any, *, ctx: Transpiler) -> Expr:
|
|
176
|
+
return _number_call("isNaN", [ctx.emit_expr(x)])
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def isqrt(n: Any, *, ctx: Transpiler) -> Expr:
|
|
180
|
+
return _math_call("floor", [_math_call("sqrt", [ctx.emit_expr(n)])])
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def ldexp(x: Any, i: Any, *, ctx: Transpiler) -> Expr:
|
|
184
|
+
# x * (2 ** i)
|
|
185
|
+
return Binary(
|
|
186
|
+
ctx.emit_expr(x),
|
|
187
|
+
"*",
|
|
188
|
+
Binary(Literal(2), "**", ctx.emit_expr(i)),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def log(value: Any, base: Any = None, *, ctx: Transpiler) -> Expr:
|
|
193
|
+
if base is None:
|
|
194
|
+
return _math_call("log", [ctx.emit_expr(value)])
|
|
195
|
+
return Binary(
|
|
196
|
+
_math_call("log", [ctx.emit_expr(value)]),
|
|
197
|
+
"/",
|
|
198
|
+
_math_call("log", [ctx.emit_expr(base)]),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def log10(x: Any, *, ctx: Transpiler) -> Expr:
|
|
203
|
+
return _math_call("log10", [ctx.emit_expr(x)])
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def log1p(x: Any, *, ctx: Transpiler) -> Expr:
|
|
207
|
+
return _math_call("log1p", [ctx.emit_expr(x)])
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def log2(x: Any, *, ctx: Transpiler) -> Expr:
|
|
211
|
+
return _math_call("log2", [ctx.emit_expr(x)])
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def pow(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
|
|
215
|
+
return _math_call("pow", [ctx.emit_expr(x), ctx.emit_expr(y)])
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def radians(x: Any, *, ctx: Transpiler) -> Expr:
|
|
219
|
+
# x * (PI / 180)
|
|
220
|
+
return Binary(
|
|
221
|
+
ctx.emit_expr(x),
|
|
222
|
+
"*",
|
|
223
|
+
Binary(_math_prop("PI"), "/", Literal(180)),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def remainder(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
|
|
228
|
+
# x - round(x/y) * y
|
|
229
|
+
x_expr = ctx.emit_expr(x)
|
|
230
|
+
y_expr = ctx.emit_expr(y)
|
|
231
|
+
n = _math_call("round", [Binary(x_expr, "/", y_expr)])
|
|
232
|
+
return Binary(x_expr, "-", Binary(n, "*", y_expr))
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def sin(x: Any, *, ctx: Transpiler) -> Expr:
|
|
236
|
+
return _math_call("sin", [ctx.emit_expr(x)])
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def sinh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
240
|
+
return _math_call("sinh", [ctx.emit_expr(x)])
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def sqrt(x: Any, *, ctx: Transpiler) -> Expr:
|
|
244
|
+
return _math_call("sqrt", [ctx.emit_expr(x)])
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def tan(x: Any, *, ctx: Transpiler) -> Expr:
|
|
248
|
+
return _math_call("tan", [ctx.emit_expr(x)])
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def tanh(x: Any, *, ctx: Transpiler) -> Expr:
|
|
252
|
+
return _math_call("tanh", [ctx.emit_expr(x)])
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def trunc(x: Any, *, ctx: Transpiler) -> Expr:
|
|
256
|
+
return _math_call("trunc", [ctx.emit_expr(x)])
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def fma(x: Any, y: Any, z: Any, *, ctx: Transpiler) -> Expr:
|
|
260
|
+
# (x * y) + z
|
|
261
|
+
return Binary(
|
|
262
|
+
Binary(ctx.emit_expr(x), "*", ctx.emit_expr(y)),
|
|
263
|
+
"+",
|
|
264
|
+
ctx.emit_expr(z),
|
|
265
|
+
)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""HTML tag function transpilation to JSX elements.
|
|
2
|
+
|
|
3
|
+
This module provides transpilation from pulse.dom.tags (like div, span, etc.)
|
|
4
|
+
to JSX elements. Tag functions can be called with props and children:
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
div("Hello", className="container")
|
|
8
|
+
|
|
9
|
+
# JavaScript
|
|
10
|
+
<div className="container">Hello</div>
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any, final, override
|
|
18
|
+
|
|
19
|
+
from pulse.components.for_ import emit_for
|
|
20
|
+
from pulse.transpiler.nodes import (
|
|
21
|
+
Element,
|
|
22
|
+
Expr,
|
|
23
|
+
Literal,
|
|
24
|
+
Node,
|
|
25
|
+
Prop,
|
|
26
|
+
Spread,
|
|
27
|
+
spread_dict,
|
|
28
|
+
)
|
|
29
|
+
from pulse.transpiler.py_module import PyModule
|
|
30
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True, frozen=True)
|
|
34
|
+
class TagExpr(Expr):
|
|
35
|
+
"""Expr that creates JSX elements when called.
|
|
36
|
+
|
|
37
|
+
Represents a tag function like `div`, `span`, etc.
|
|
38
|
+
When called, produces an Element with props from kwargs and children from args.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
tag: str
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
def emit(self, out: list[str]) -> None:
|
|
45
|
+
out.append(f'"{self.tag}"')
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def render(self):
|
|
49
|
+
return self.tag
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
def transpile_call(
|
|
53
|
+
self,
|
|
54
|
+
args: list[ast.expr],
|
|
55
|
+
keywords: list[ast.keyword],
|
|
56
|
+
ctx: Transpiler,
|
|
57
|
+
) -> Expr:
|
|
58
|
+
"""Handle tag calls: positional args are children, kwargs are props.
|
|
59
|
+
|
|
60
|
+
Spread (**expr) is supported for prop spreading.
|
|
61
|
+
"""
|
|
62
|
+
# Build children from positional args
|
|
63
|
+
children: list[Node] = []
|
|
64
|
+
for a in args:
|
|
65
|
+
children.append(ctx.emit_expr(a))
|
|
66
|
+
|
|
67
|
+
# Build props from kwargs
|
|
68
|
+
props: list[tuple[str, Prop] | Spread] = []
|
|
69
|
+
key: str | Expr | None = None
|
|
70
|
+
for kw in keywords:
|
|
71
|
+
if kw.arg is None:
|
|
72
|
+
# **spread syntax
|
|
73
|
+
props.append(spread_dict(ctx.emit_expr(kw.value)))
|
|
74
|
+
else:
|
|
75
|
+
k = kw.arg
|
|
76
|
+
prop_value = ctx.emit_expr(kw.value)
|
|
77
|
+
if k == "key":
|
|
78
|
+
# Accept any expression as key for transpilation
|
|
79
|
+
if isinstance(prop_value, Literal) and isinstance(
|
|
80
|
+
prop_value.value, str
|
|
81
|
+
):
|
|
82
|
+
key = prop_value.value # Optimize string literals
|
|
83
|
+
else:
|
|
84
|
+
key = prop_value # Keep as expression
|
|
85
|
+
else:
|
|
86
|
+
props.append((k, prop_value))
|
|
87
|
+
|
|
88
|
+
return Element(
|
|
89
|
+
tag=self.tag,
|
|
90
|
+
props=props if props else None,
|
|
91
|
+
children=children if children else None,
|
|
92
|
+
key=key,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# -------------------------------------------------------------------------
|
|
96
|
+
# Python dunder methods: allow natural syntax in @javascript functions
|
|
97
|
+
# -------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
def __call__(self, *args: Any, **kwargs: Any): # pyright: ignore[reportIncompatibleMethodOverride]
|
|
101
|
+
"""Allow calling TagExpr objects in Python code.
|
|
102
|
+
|
|
103
|
+
Returns a placeholder Element for type checking. The actual transpilation
|
|
104
|
+
happens via transpile_call when the transpiler processes the AST.
|
|
105
|
+
"""
|
|
106
|
+
return Element(tag=self.tag, props=None, children=None, key=None)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@final
|
|
110
|
+
class PulseTags(PyModule):
|
|
111
|
+
"""Provides transpilation for pulse.dom.tags to JSX elements."""
|
|
112
|
+
|
|
113
|
+
# Regular tags
|
|
114
|
+
a = TagExpr("a")
|
|
115
|
+
abbr = TagExpr("abbr")
|
|
116
|
+
address = TagExpr("address")
|
|
117
|
+
article = TagExpr("article")
|
|
118
|
+
aside = TagExpr("aside")
|
|
119
|
+
audio = TagExpr("audio")
|
|
120
|
+
b = TagExpr("b")
|
|
121
|
+
bdi = TagExpr("bdi")
|
|
122
|
+
bdo = TagExpr("bdo")
|
|
123
|
+
blockquote = TagExpr("blockquote")
|
|
124
|
+
body = TagExpr("body")
|
|
125
|
+
button = TagExpr("button")
|
|
126
|
+
canvas = TagExpr("canvas")
|
|
127
|
+
caption = TagExpr("caption")
|
|
128
|
+
cite = TagExpr("cite")
|
|
129
|
+
code = TagExpr("code")
|
|
130
|
+
colgroup = TagExpr("colgroup")
|
|
131
|
+
data = TagExpr("data")
|
|
132
|
+
datalist = TagExpr("datalist")
|
|
133
|
+
dd = TagExpr("dd")
|
|
134
|
+
del_ = TagExpr("del")
|
|
135
|
+
details = TagExpr("details")
|
|
136
|
+
dfn = TagExpr("dfn")
|
|
137
|
+
dialog = TagExpr("dialog")
|
|
138
|
+
div = TagExpr("div")
|
|
139
|
+
dl = TagExpr("dl")
|
|
140
|
+
dt = TagExpr("dt")
|
|
141
|
+
em = TagExpr("em")
|
|
142
|
+
fieldset = TagExpr("fieldset")
|
|
143
|
+
figcaption = TagExpr("figcaption")
|
|
144
|
+
figure = TagExpr("figure")
|
|
145
|
+
footer = TagExpr("footer")
|
|
146
|
+
form = TagExpr("form")
|
|
147
|
+
h1 = TagExpr("h1")
|
|
148
|
+
h2 = TagExpr("h2")
|
|
149
|
+
h3 = TagExpr("h3")
|
|
150
|
+
h4 = TagExpr("h4")
|
|
151
|
+
h5 = TagExpr("h5")
|
|
152
|
+
h6 = TagExpr("h6")
|
|
153
|
+
head = TagExpr("head")
|
|
154
|
+
header = TagExpr("header")
|
|
155
|
+
hgroup = TagExpr("hgroup")
|
|
156
|
+
html = TagExpr("html")
|
|
157
|
+
i = TagExpr("i")
|
|
158
|
+
iframe = TagExpr("iframe")
|
|
159
|
+
ins = TagExpr("ins")
|
|
160
|
+
kbd = TagExpr("kbd")
|
|
161
|
+
label = TagExpr("label")
|
|
162
|
+
legend = TagExpr("legend")
|
|
163
|
+
li = TagExpr("li")
|
|
164
|
+
main = TagExpr("main")
|
|
165
|
+
map_ = TagExpr("map")
|
|
166
|
+
mark = TagExpr("mark")
|
|
167
|
+
menu = TagExpr("menu")
|
|
168
|
+
meter = TagExpr("meter")
|
|
169
|
+
nav = TagExpr("nav")
|
|
170
|
+
noscript = TagExpr("noscript")
|
|
171
|
+
object_ = TagExpr("object")
|
|
172
|
+
ol = TagExpr("ol")
|
|
173
|
+
optgroup = TagExpr("optgroup")
|
|
174
|
+
option = TagExpr("option")
|
|
175
|
+
output = TagExpr("output")
|
|
176
|
+
p = TagExpr("p")
|
|
177
|
+
picture = TagExpr("picture")
|
|
178
|
+
pre = TagExpr("pre")
|
|
179
|
+
progress = TagExpr("progress")
|
|
180
|
+
q = TagExpr("q")
|
|
181
|
+
rp = TagExpr("rp")
|
|
182
|
+
rt = TagExpr("rt")
|
|
183
|
+
ruby = TagExpr("ruby")
|
|
184
|
+
s = TagExpr("s")
|
|
185
|
+
samp = TagExpr("samp")
|
|
186
|
+
script = TagExpr("script")
|
|
187
|
+
section = TagExpr("section")
|
|
188
|
+
select = TagExpr("select")
|
|
189
|
+
small = TagExpr("small")
|
|
190
|
+
span = TagExpr("span")
|
|
191
|
+
strong = TagExpr("strong")
|
|
192
|
+
style = TagExpr("style")
|
|
193
|
+
sub = TagExpr("sub")
|
|
194
|
+
summary = TagExpr("summary")
|
|
195
|
+
sup = TagExpr("sup")
|
|
196
|
+
table = TagExpr("table")
|
|
197
|
+
tbody = TagExpr("tbody")
|
|
198
|
+
td = TagExpr("td")
|
|
199
|
+
template = TagExpr("template")
|
|
200
|
+
textarea = TagExpr("textarea")
|
|
201
|
+
tfoot = TagExpr("tfoot")
|
|
202
|
+
th = TagExpr("th")
|
|
203
|
+
thead = TagExpr("thead")
|
|
204
|
+
time = TagExpr("time")
|
|
205
|
+
title = TagExpr("title")
|
|
206
|
+
tr = TagExpr("tr")
|
|
207
|
+
u = TagExpr("u")
|
|
208
|
+
ul = TagExpr("ul")
|
|
209
|
+
var = TagExpr("var")
|
|
210
|
+
video = TagExpr("video")
|
|
211
|
+
|
|
212
|
+
# Self-closing tags
|
|
213
|
+
area = TagExpr("area")
|
|
214
|
+
base = TagExpr("base")
|
|
215
|
+
br = TagExpr("br")
|
|
216
|
+
col = TagExpr("col")
|
|
217
|
+
embed = TagExpr("embed")
|
|
218
|
+
hr = TagExpr("hr")
|
|
219
|
+
img = TagExpr("img")
|
|
220
|
+
input = TagExpr("input")
|
|
221
|
+
link = TagExpr("link")
|
|
222
|
+
meta = TagExpr("meta")
|
|
223
|
+
param = TagExpr("param")
|
|
224
|
+
source = TagExpr("source")
|
|
225
|
+
track = TagExpr("track")
|
|
226
|
+
wbr = TagExpr("wbr")
|
|
227
|
+
|
|
228
|
+
# SVG tags
|
|
229
|
+
svg = TagExpr("svg")
|
|
230
|
+
circle = TagExpr("circle")
|
|
231
|
+
ellipse = TagExpr("ellipse")
|
|
232
|
+
g = TagExpr("g")
|
|
233
|
+
line = TagExpr("line")
|
|
234
|
+
path = TagExpr("path")
|
|
235
|
+
polygon = TagExpr("polygon")
|
|
236
|
+
polyline = TagExpr("polyline")
|
|
237
|
+
rect = TagExpr("rect")
|
|
238
|
+
text = TagExpr("text")
|
|
239
|
+
tspan = TagExpr("tspan")
|
|
240
|
+
defs = TagExpr("defs")
|
|
241
|
+
clipPath = TagExpr("clipPath")
|
|
242
|
+
mask = TagExpr("mask")
|
|
243
|
+
pattern = TagExpr("pattern")
|
|
244
|
+
use = TagExpr("use")
|
|
245
|
+
|
|
246
|
+
# React fragment
|
|
247
|
+
fragment = TagExpr("")
|
|
248
|
+
|
|
249
|
+
# For component - maps to array.map()
|
|
250
|
+
For = emit_for
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Python typing module transpilation - mostly no-ops for type hints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import final, override
|
|
8
|
+
|
|
9
|
+
from pulse.transpiler.nodes import Expr
|
|
10
|
+
from pulse.transpiler.py_module import PyModule
|
|
11
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class TypeHint(Expr):
|
|
16
|
+
"""A type hint that should never be emitted directly.
|
|
17
|
+
|
|
18
|
+
Used for typing constructs like Any that can be passed to cast() but
|
|
19
|
+
shouldn't appear in generated code.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
name: str
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
def emit(self, out: list[str]) -> None:
|
|
26
|
+
raise TypeError(
|
|
27
|
+
f"Type hint '{self.name}' cannot be emitted as JavaScript. "
|
|
28
|
+
+ "It should only be used with typing.cast() or similar."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
def render(self):
|
|
33
|
+
raise TypeError(
|
|
34
|
+
f"Type hint '{self.name}' cannot be rendered. "
|
|
35
|
+
+ "It should only be used with typing.cast() or similar."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@override
|
|
39
|
+
def transpile_subscript(self, key: ast.expr, ctx: Transpiler) -> Expr:
|
|
40
|
+
# List[int], Optional[str], etc. -> still a type hint
|
|
41
|
+
return TypeHint(f"{self.name}[...]")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@final
|
|
45
|
+
class PyTyping(PyModule):
|
|
46
|
+
"""Provides transpilation for Python typing functions."""
|
|
47
|
+
|
|
48
|
+
# Type constructs used with cast() - error if emitted directly
|
|
49
|
+
Any = TypeHint("Any")
|
|
50
|
+
Optional = TypeHint("Optional")
|
|
51
|
+
Union = TypeHint("Union")
|
|
52
|
+
List = TypeHint("List")
|
|
53
|
+
Dict = TypeHint("Dict")
|
|
54
|
+
Set = TypeHint("Set")
|
|
55
|
+
Tuple = TypeHint("Tuple")
|
|
56
|
+
FrozenSet = TypeHint("FrozenSet")
|
|
57
|
+
Type = TypeHint("Type")
|
|
58
|
+
Callable = TypeHint("Callable")
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def cast(_type: ast.expr, val: ast.expr, *, ctx: Transpiler) -> Expr:
|
|
62
|
+
"""cast(T, val) -> val (type cast is a no-op at runtime)."""
|
|
63
|
+
return ctx.emit_expr(val)
|