pulse-framework 0.1.51__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.
Files changed (84) hide show
  1. pulse/__init__.py +542 -562
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +0 -14
  4. pulse/cli/cmd.py +96 -80
  5. pulse/cli/dependencies.py +10 -41
  6. pulse/cli/folder_lock.py +3 -3
  7. pulse/cli/helpers.py +40 -67
  8. pulse/cli/logging.py +102 -0
  9. pulse/cli/packages.py +16 -0
  10. pulse/cli/processes.py +40 -23
  11. pulse/codegen/codegen.py +70 -35
  12. pulse/codegen/js.py +2 -4
  13. pulse/codegen/templates/route.py +94 -146
  14. pulse/component.py +115 -0
  15. pulse/components/for_.py +1 -1
  16. pulse/components/if_.py +1 -1
  17. pulse/components/react_router.py +16 -22
  18. pulse/{html → dom}/events.py +1 -1
  19. pulse/{html → dom}/props.py +6 -6
  20. pulse/{html → dom}/tags.py +11 -11
  21. pulse/dom/tags.pyi +480 -0
  22. pulse/form.py +7 -6
  23. pulse/hooks/init.py +1 -13
  24. pulse/js/__init__.py +37 -41
  25. pulse/js/__init__.pyi +22 -2
  26. pulse/js/_types.py +5 -3
  27. pulse/js/array.py +121 -38
  28. pulse/js/console.py +9 -9
  29. pulse/js/date.py +22 -19
  30. pulse/js/document.py +8 -4
  31. pulse/js/error.py +12 -14
  32. pulse/js/json.py +4 -3
  33. pulse/js/map.py +17 -7
  34. pulse/js/math.py +2 -2
  35. pulse/js/navigator.py +4 -4
  36. pulse/js/number.py +8 -8
  37. pulse/js/object.py +9 -13
  38. pulse/js/promise.py +25 -9
  39. pulse/js/regexp.py +6 -6
  40. pulse/js/set.py +20 -8
  41. pulse/js/string.py +7 -7
  42. pulse/js/weakmap.py +6 -6
  43. pulse/js/weakset.py +6 -6
  44. pulse/js/window.py +17 -14
  45. pulse/messages.py +1 -4
  46. pulse/react_component.py +3 -1001
  47. pulse/render_session.py +74 -66
  48. pulse/renderer.py +311 -238
  49. pulse/routing.py +1 -10
  50. pulse/transpiler/__init__.py +84 -114
  51. pulse/transpiler/builtins.py +661 -343
  52. pulse/transpiler/errors.py +78 -2
  53. pulse/transpiler/function.py +463 -133
  54. pulse/transpiler/id.py +18 -0
  55. pulse/transpiler/imports.py +230 -325
  56. pulse/transpiler/js_module.py +218 -209
  57. pulse/transpiler/modules/__init__.py +16 -13
  58. pulse/transpiler/modules/asyncio.py +45 -26
  59. pulse/transpiler/modules/json.py +12 -8
  60. pulse/transpiler/modules/math.py +161 -216
  61. pulse/transpiler/modules/pulse/__init__.py +5 -0
  62. pulse/transpiler/modules/pulse/tags.py +231 -0
  63. pulse/transpiler/modules/typing.py +33 -28
  64. pulse/transpiler/nodes.py +1607 -923
  65. pulse/transpiler/py_module.py +118 -95
  66. pulse/transpiler/react_component.py +51 -0
  67. pulse/transpiler/transpiler.py +593 -437
  68. pulse/transpiler/vdom.py +255 -0
  69. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.52.dist-info}/METADATA +1 -1
  70. pulse_framework-0.1.52.dist-info/RECORD +120 -0
  71. pulse/html/tags.pyi +0 -470
  72. pulse/transpiler/constants.py +0 -110
  73. pulse/transpiler/context.py +0 -26
  74. pulse/transpiler/ids.py +0 -16
  75. pulse/transpiler/modules/re.py +0 -466
  76. pulse/transpiler/modules/tags.py +0 -268
  77. pulse/transpiler/utils.py +0 -4
  78. pulse/vdom.py +0 -599
  79. pulse_framework-0.1.51.dist-info/RECORD +0 -119
  80. /pulse/{html → dom}/__init__.py +0 -0
  81. /pulse/{html → dom}/elements.py +0 -0
  82. /pulse/{html → dom}/svg.py +0 -0
  83. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.52.dist-info}/WHEEL +0 -0
  84. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.52.dist-info}/entry_points.txt +0 -0
@@ -1,320 +1,265 @@
1
1
  """Python math module transpilation to JavaScript Math.
2
2
 
3
- This module provides transpilation from Python's `math` module to JavaScript's `Math` object.
4
- For direct JavaScript Math bindings, use `pulse.js.math` instead.
3
+ Provides transpilation from Python's `math` module to JavaScript's `Math` object.
5
4
  """
6
5
 
7
- # pyright: reportUnannotatedClassAttribute=false
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, final
8
9
 
9
- from pulse.transpiler.constants import jsify
10
10
  from pulse.transpiler.nodes import (
11
- JSBinary,
12
- JSExpr,
13
- JSIdentifier,
14
- JSMember,
15
- JSMemberCall,
16
- JSNumber,
17
- JSUnary,
11
+ Binary,
12
+ Call,
13
+ Expr,
14
+ Identifier,
15
+ Literal,
16
+ Member,
17
+ Unary,
18
18
  )
19
19
  from pulse.transpiler.py_module import PyModule
20
+ from pulse.transpiler.transpiler import Transpiler
20
21
 
22
+ # Helpers for building Math.* calls
23
+ _Math = Identifier("Math")
24
+ _Number = Identifier("Number")
21
25
 
22
- # Helper for generating Math method calls during transpilation
23
- def MathCall(name: str, *args: int | float | JSExpr) -> JSExpr:
24
- return JSMemberCall(JSIdentifier("Math"), name, [jsify(a) for a in args])
25
26
 
27
+ def _math_prop(name: str) -> Expr:
28
+ return Member(_Math, name)
26
29
 
27
- def MathProp(name: str) -> JSExpr:
28
- return JSMember(JSIdentifier("Math"), name)
29
30
 
31
+ def _math_call(name: str, args: list[Expr]) -> Expr:
32
+ return Call(Member(_Math, name), args)
30
33
 
31
- class PyMath(PyModule):
32
- """Provides transpilation for Python math functions to JavaScript."""
33
34
 
34
- # Constants (as class attributes returning JSExpr)
35
- pi = MathProp("PI")
36
- e = MathProp("E")
37
- tau = JSBinary(JSNumber(2), "*", MathProp("PI")) # 2 * PI
38
- inf = JSIdentifier("Infinity")
39
- nan = JSIdentifier("NaN")
35
+ def _number_call(name: str, args: list[Expr]) -> Expr:
36
+ return Call(Member(_Number, name), args)
40
37
 
41
- @staticmethod
42
- def acos(x: int | float | JSExpr) -> JSExpr:
43
- return MathCall("acos", x)
44
38
 
45
- @staticmethod
46
- def acosh(x: int | float | JSExpr) -> JSExpr:
47
- return MathCall("acosh", x)
39
+ @final
40
+ class PyMath(PyModule):
41
+ """Provides transpilation for Python math functions to JavaScript."""
48
42
 
49
- @staticmethod
50
- def asin(x: int | float | JSExpr) -> JSExpr:
51
- return MathCall("asin", x)
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")
52
49
 
50
+ # Basic functions
53
51
  @staticmethod
54
- def asinh(x: int | float | JSExpr) -> JSExpr:
55
- return MathCall("asinh", x)
52
+ def acos(x: Any, *, ctx: Transpiler) -> Expr:
53
+ return _math_call("acos", [ctx.emit_expr(x)])
56
54
 
57
55
  @staticmethod
58
- def atan(x: int | float | JSExpr) -> JSExpr:
59
- return MathCall("atan", x)
56
+ def acosh(x: Any, *, ctx: Transpiler) -> Expr:
57
+ return _math_call("acosh", [ctx.emit_expr(x)])
60
58
 
61
59
  @staticmethod
62
- def atan2(y: int | float | JSExpr, x: int | float | JSExpr) -> JSExpr:
63
- return MathCall("atan2", y, x)
60
+ def asin(x: Any, *, ctx: Transpiler) -> Expr:
61
+ return _math_call("asin", [ctx.emit_expr(x)])
64
62
 
65
63
  @staticmethod
66
- def atanh(x: int | float | JSExpr) -> JSExpr:
67
- return MathCall("atanh", x)
64
+ def asinh(x: Any, *, ctx: Transpiler) -> Expr:
65
+ return _math_call("asinh", [ctx.emit_expr(x)])
68
66
 
69
67
  @staticmethod
70
- def cbrt(x: int | float | JSExpr) -> JSExpr:
71
- return MathCall("cbrt", x)
68
+ def atan(x: Any, *, ctx: Transpiler) -> Expr:
69
+ return _math_call("atan", [ctx.emit_expr(x)])
72
70
 
73
71
  @staticmethod
74
- def ceil(x: int | float | JSExpr) -> JSExpr:
75
- return MathCall("ceil", x)
72
+ def atan2(y: Any, x: Any, *, ctx: Transpiler) -> Expr:
73
+ return _math_call("atan2", [ctx.emit_expr(y), ctx.emit_expr(x)])
76
74
 
77
75
  @staticmethod
78
- def copysign(x: int | float | JSExpr, y: int | float | JSExpr) -> JSExpr:
79
- # Math.sign(y) * Math.abs(x)
80
- return JSBinary(MathCall("sign", y), "*", MathCall("abs", x))
76
+ def atanh(x: Any, *, ctx: Transpiler) -> Expr:
77
+ return _math_call("atanh", [ctx.emit_expr(x)])
81
78
 
82
79
  @staticmethod
83
- def cos(x: int | float | JSExpr) -> JSExpr:
84
- return MathCall("cos", x)
80
+ def cbrt(x: Any, *, ctx: Transpiler) -> Expr:
81
+ return _math_call("cbrt", [ctx.emit_expr(x)])
85
82
 
86
83
  @staticmethod
87
- def cosh(x: int | float | JSExpr) -> JSExpr:
88
- return MathCall("cosh", x)
84
+ def ceil(x: Any, *, ctx: Transpiler) -> Expr:
85
+ return _math_call("ceil", [ctx.emit_expr(x)])
89
86
 
90
87
  @staticmethod
91
- def degrees(x: int | float | JSExpr) -> JSExpr:
92
- # Convert radians to degrees: x * (180 / π)
93
- return JSBinary(jsify(x), "*", JSBinary(JSNumber(180), "/", MathProp("PI")))
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
+ )
94
95
 
95
96
  @staticmethod
96
- def dist(
97
- p: int | float | JSExpr | list[int | float | JSExpr],
98
- q: int | float | JSExpr | list[int | float | JSExpr],
99
- ) -> JSExpr:
100
- raise NotImplementedError("dist requires array/iterable handling")
97
+ def cos(x: Any, *, ctx: Transpiler) -> Expr:
98
+ return _math_call("cos", [ctx.emit_expr(x)])
101
99
 
102
100
  @staticmethod
103
- def erf(x: int | float | JSExpr) -> JSExpr:
104
- raise NotImplementedError("erf requires special function implementation")
101
+ def cosh(x: Any, *, ctx: Transpiler) -> Expr:
102
+ return _math_call("cosh", [ctx.emit_expr(x)])
105
103
 
106
104
  @staticmethod
107
- def erfc(x: int | float | JSExpr) -> JSExpr:
108
- raise NotImplementedError("erfc requires special function implementation")
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
+ )
109
112
 
110
113
  @staticmethod
111
- def exp(x: int | float | JSExpr) -> JSExpr:
112
- return MathCall("exp", x)
114
+ def exp(x: Any, *, ctx: Transpiler) -> Expr:
115
+ return _math_call("exp", [ctx.emit_expr(x)])
113
116
 
114
117
  @staticmethod
115
- def exp2(x: int | float | JSExpr) -> JSExpr:
118
+ def exp2(x: Any, *, ctx: Transpiler) -> Expr:
116
119
  # 2 ** x
117
- return JSBinary(JSNumber(2), "**", jsify(x))
120
+ return Binary(Literal(2), "**", ctx.emit_expr(x))
118
121
 
119
122
  @staticmethod
120
- def expm1(x: int | float | JSExpr) -> JSExpr:
121
- return MathCall("expm1", x)
123
+ def expm1(x: Any, *, ctx: Transpiler) -> Expr:
124
+ return _math_call("expm1", [ctx.emit_expr(x)])
122
125
 
123
126
  @staticmethod
124
- def fabs(x: int | float | JSExpr) -> JSExpr:
125
- return MathCall("abs", x)
127
+ def fabs(x: Any, *, ctx: Transpiler) -> Expr:
128
+ return _math_call("abs", [ctx.emit_expr(x)])
126
129
 
127
130
  @staticmethod
128
- def factorial(x: int | float | JSExpr) -> JSExpr:
129
- raise NotImplementedError("factorial requires iterative implementation")
131
+ def floor(x: Any, *, ctx: Transpiler) -> Expr:
132
+ return _math_call("floor", [ctx.emit_expr(x)])
130
133
 
131
134
  @staticmethod
132
- def floor(x: int | float | JSExpr) -> JSExpr:
133
- return MathCall("floor", x)
135
+ def fmod(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
136
+ return Binary(ctx.emit_expr(x), "%", ctx.emit_expr(y))
134
137
 
135
138
  @staticmethod
136
- def fmod(x: int | float | JSExpr, y: int | float | JSExpr) -> JSExpr:
137
- # JavaScript % operator matches Python fmod for most cases
138
- return JSBinary(jsify(x), "%", jsify(y))
139
-
140
- @staticmethod
141
- def frexp(x: int | float | JSExpr) -> JSExpr:
142
- raise NotImplementedError("frexp returns tuple, requires special handling")
143
-
144
- @staticmethod
145
- def fsum(seq: int | float | JSExpr | list[int | float | JSExpr]) -> JSExpr:
146
- raise NotImplementedError("fsum requires iterable handling")
147
-
148
- @staticmethod
149
- def gamma(x: int | float | JSExpr) -> JSExpr:
150
- raise NotImplementedError("gamma requires special function implementation")
151
-
152
- @staticmethod
153
- def gcd(*integers: int | float | JSExpr) -> JSExpr:
154
- raise NotImplementedError("gcd requires iterative implementation")
155
-
156
- @staticmethod
157
- def hypot(*coordinates: int | float | JSExpr) -> JSExpr:
158
- return MathCall("hypot", *coordinates)
139
+ def hypot(*coords: Any, ctx: Transpiler) -> Expr:
140
+ return _math_call("hypot", [ctx.emit_expr(c) for c in coords])
159
141
 
160
142
  @staticmethod
161
143
  def isclose(
162
- a: int | float | JSExpr,
163
- b: int | float | JSExpr,
164
- *,
165
- rel_tol: int | float | JSExpr = 1e-09,
166
- abs_tol: int | float | JSExpr = 0.0,
167
- ) -> JSExpr:
144
+ a: Any, b: Any, *, rel_tol: Any = 1e-9, abs_tol: Any = 0.0, ctx: Transpiler
145
+ ) -> Expr:
168
146
  # abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
169
- abs_diff = MathCall("abs", JSBinary(jsify(a), "-", jsify(b)))
170
- max_abs = JSMemberCall(
171
- JSIdentifier("Math"),
172
- "max",
173
- [MathCall("abs", a), MathCall("abs", b)],
174
- )
175
- rel_bound = JSBinary(jsify(rel_tol), "*", max_abs)
176
- max_bound = JSMemberCall(
177
- JSIdentifier("Math"), "max", [rel_bound, jsify(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])]
178
155
  )
179
- return JSBinary(abs_diff, "<=", max_bound)
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)
180
159
 
181
160
  @staticmethod
182
- def isfinite(x: int | float | JSExpr) -> JSExpr:
183
- return JSMemberCall(JSIdentifier("Number"), "isFinite", [jsify(x)])
161
+ def isfinite(x: Any, *, ctx: Transpiler) -> Expr:
162
+ return _number_call("isFinite", [ctx.emit_expr(x)])
184
163
 
185
164
  @staticmethod
186
- def isinf(x: int | float | JSExpr) -> JSExpr:
187
- is_finite = JSMemberCall(JSIdentifier("Number"), "isFinite", [jsify(x)])
188
- is_nan = JSMemberCall(JSIdentifier("Number"), "isNaN", [jsify(x)])
189
- return JSBinary(JSUnary("!", is_finite), "&&", JSUnary("!", is_nan))
190
-
191
- @staticmethod
192
- def isnan(x: int | float | JSExpr) -> JSExpr:
193
- return JSMemberCall(JSIdentifier("Number"), "isNaN", [jsify(x)])
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
+ )
194
173
 
195
174
  @staticmethod
196
- def isqrt(n: int | float | JSExpr) -> JSExpr:
197
- return MathCall("floor", MathCall("sqrt", n))
175
+ def isnan(x: Any, *, ctx: Transpiler) -> Expr:
176
+ return _number_call("isNaN", [ctx.emit_expr(x)])
198
177
 
199
178
  @staticmethod
200
- def lcm(*integers: int | float | JSExpr) -> JSExpr:
201
- raise NotImplementedError("lcm requires iterative implementation")
179
+ def isqrt(n: Any, *, ctx: Transpiler) -> Expr:
180
+ return _math_call("floor", [_math_call("sqrt", [ctx.emit_expr(n)])])
202
181
 
203
182
  @staticmethod
204
- def ldexp(x: int | float | JSExpr, i: int | float | JSExpr) -> JSExpr:
183
+ def ldexp(x: Any, i: Any, *, ctx: Transpiler) -> Expr:
205
184
  # x * (2 ** i)
206
- return JSBinary(jsify(x), "*", JSBinary(JSNumber(2), "**", jsify(i)))
207
-
208
- @staticmethod
209
- def lgamma(x: int | float | JSExpr) -> JSExpr:
210
- raise NotImplementedError("lgamma requires special function implementation")
185
+ return Binary(
186
+ ctx.emit_expr(x),
187
+ "*",
188
+ Binary(Literal(2), "**", ctx.emit_expr(i)),
189
+ )
211
190
 
212
191
  @staticmethod
213
- def log(
214
- value: int | float | JSExpr,
215
- base: int | float | JSExpr | None = None,
216
- ) -> JSExpr:
192
+ def log(value: Any, base: Any = None, *, ctx: Transpiler) -> Expr:
217
193
  if base is None:
218
- return MathCall("log", value)
219
- return JSBinary(MathCall("log", value), "/", MathCall("log", base))
220
-
221
- @staticmethod
222
- def log10(x: int | float | JSExpr) -> JSExpr:
223
- return MathCall("log10", x)
224
-
225
- @staticmethod
226
- def log1p(x: int | float | JSExpr) -> JSExpr:
227
- return MathCall("log1p", x)
228
-
229
- @staticmethod
230
- def log2(x: int | float | JSExpr) -> JSExpr:
231
- return MathCall("log2", x)
232
-
233
- @staticmethod
234
- def modf(x: int | float | JSExpr) -> JSExpr:
235
- raise NotImplementedError("modf returns tuple, requires special handling")
236
-
237
- @staticmethod
238
- def nextafter(
239
- x: int | float | JSExpr,
240
- y: int | float | JSExpr,
241
- *,
242
- steps: int | float | JSExpr | None = None,
243
- ) -> JSExpr:
244
- raise NotImplementedError("nextafter requires special implementation")
245
-
246
- @staticmethod
247
- def perm(n: int | float | JSExpr, k: int | float | JSExpr | None = None) -> JSExpr:
248
- raise NotImplementedError("perm requires factorial implementation")
249
-
250
- @staticmethod
251
- def pow(x: int | float | JSExpr, y: int | float | JSExpr) -> JSExpr:
252
- return MathCall("pow", x, y)
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
+ )
253
200
 
254
201
  @staticmethod
255
- def prod(
256
- iterable: int | float | JSExpr | list[int | float | JSExpr],
257
- *,
258
- start: int | float | JSExpr = 1,
259
- ) -> JSExpr:
260
- raise NotImplementedError("prod requires iterable handling")
202
+ def log10(x: Any, *, ctx: Transpiler) -> Expr:
203
+ return _math_call("log10", [ctx.emit_expr(x)])
261
204
 
262
205
  @staticmethod
263
- def radians(x: int | float | JSExpr) -> JSExpr:
264
- # Convert degrees to radians: x * (π / 180)
265
- return JSBinary(jsify(x), "*", JSBinary(MathProp("PI"), "/", JSNumber(180)))
206
+ def log1p(x: Any, *, ctx: Transpiler) -> Expr:
207
+ return _math_call("log1p", [ctx.emit_expr(x)])
266
208
 
267
209
  @staticmethod
268
- def remainder(x: int | float | JSExpr, y: int | float | JSExpr) -> JSExpr:
269
- # x - n * y where n is the nearest integer to x/y
270
- n = MathCall("round", JSBinary(jsify(x), "/", jsify(y)))
271
- return JSBinary(jsify(x), "-", JSBinary(n, "*", jsify(y)))
210
+ def log2(x: Any, *, ctx: Transpiler) -> Expr:
211
+ return _math_call("log2", [ctx.emit_expr(x)])
272
212
 
273
213
  @staticmethod
274
- def sin(x: int | float | JSExpr) -> JSExpr:
275
- return MathCall("sin", x)
214
+ def pow(x: Any, y: Any, *, ctx: Transpiler) -> Expr:
215
+ return _math_call("pow", [ctx.emit_expr(x), ctx.emit_expr(y)])
276
216
 
277
217
  @staticmethod
278
- def sinh(x: int | float | JSExpr) -> JSExpr:
279
- return MathCall("sinh", x)
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
+ )
280
225
 
281
226
  @staticmethod
282
- def sqrt(x: int | float | JSExpr) -> JSExpr:
283
- return MathCall("sqrt", x)
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))
284
233
 
285
234
  @staticmethod
286
- def sumprod(
287
- p: int | float | JSExpr | list[int | float | JSExpr],
288
- q: int | float | JSExpr | list[int | float | JSExpr],
289
- ) -> JSExpr:
290
- raise NotImplementedError("sumprod requires iterable handling")
235
+ def sin(x: Any, *, ctx: Transpiler) -> Expr:
236
+ return _math_call("sin", [ctx.emit_expr(x)])
291
237
 
292
238
  @staticmethod
293
- def tan(x: int | float | JSExpr) -> JSExpr:
294
- return MathCall("tan", x)
239
+ def sinh(x: Any, *, ctx: Transpiler) -> Expr:
240
+ return _math_call("sinh", [ctx.emit_expr(x)])
295
241
 
296
242
  @staticmethod
297
- def tanh(x: int | float | JSExpr) -> JSExpr:
298
- return MathCall("tanh", x)
243
+ def sqrt(x: Any, *, ctx: Transpiler) -> Expr:
244
+ return _math_call("sqrt", [ctx.emit_expr(x)])
299
245
 
300
246
  @staticmethod
301
- def trunc(x: int | float | JSExpr) -> JSExpr:
302
- return MathCall("trunc", x)
247
+ def tan(x: Any, *, ctx: Transpiler) -> Expr:
248
+ return _math_call("tan", [ctx.emit_expr(x)])
303
249
 
304
250
  @staticmethod
305
- def ulp(x: int | float | JSExpr) -> JSExpr:
306
- raise NotImplementedError("ulp requires special implementation")
251
+ def tanh(x: Any, *, ctx: Transpiler) -> Expr:
252
+ return _math_call("tanh", [ctx.emit_expr(x)])
307
253
 
308
254
  @staticmethod
309
- def fma(
310
- x: int | float | JSExpr,
311
- y: int | float | JSExpr,
312
- z: int | float | JSExpr,
313
- ) -> JSExpr:
314
- # Fused multiply-add: (x * y) + z (with single rounding)
315
- # JavaScript doesn't have native fma, so we just do the operation
316
- return JSBinary(JSBinary(jsify(x), "*", jsify(y)), "+", jsify(z))
255
+ def trunc(x: Any, *, ctx: Transpiler) -> Expr:
256
+ return _math_call("trunc", [ctx.emit_expr(x)])
317
257
 
318
258
  @staticmethod
319
- def comb(n: int | float | JSExpr, k: int | float | JSExpr) -> JSExpr:
320
- raise NotImplementedError("comb requires factorial implementation")
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,5 @@
1
+ """Pulse module transpilation.
2
+
3
+ Submodules:
4
+ - tags: HTML tag functions -> JSX elements
5
+ """