pulse-framework 0.1.73__py3-none-any.whl → 0.1.75__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/js/__init__.py +36 -0
- pulse/js/__init__.pyi +48 -0
- pulse/js/abort_controller.py +61 -0
- pulse/js/array_buffer.py +227 -0
- pulse/js/blob.py +60 -0
- pulse/js/crypto.py +69 -0
- pulse/js/custom_event.py +49 -0
- pulse/js/dom_parser.py +40 -0
- pulse/js/fetch_api.py +126 -0
- pulse/js/file.py +72 -0
- pulse/js/file_reader.py +37 -0
- pulse/js/form_data.py +58 -0
- pulse/js/intersection_observer.py +79 -0
- pulse/js/intl.py +101 -0
- pulse/js/mutation_observer.py +87 -0
- pulse/js/performance_observer.py +54 -0
- pulse/js/resize_observer.py +55 -0
- pulse/js/text_encoding.py +66 -0
- pulse/js/url.py +114 -0
- pulse/transpiler/__init__.py +5 -0
- pulse/transpiler/function.py +56 -32
- pulse/transpiler/nodes.py +17 -2
- pulse/transpiler/parse.py +70 -0
- pulse/transpiler/transpiler.py +327 -76
- {pulse_framework-0.1.73.dist-info → pulse_framework-0.1.75.dist-info}/METADATA +1 -1
- {pulse_framework-0.1.73.dist-info → pulse_framework-0.1.75.dist-info}/RECORD +28 -10
- {pulse_framework-0.1.73.dist-info → pulse_framework-0.1.75.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.73.dist-info → pulse_framework-0.1.75.dist-info}/entry_points.txt +0 -0
pulse/transpiler/transpiler.py
CHANGED
|
@@ -10,8 +10,9 @@ from __future__ import annotations
|
|
|
10
10
|
import ast
|
|
11
11
|
import re
|
|
12
12
|
from collections.abc import Callable, Mapping
|
|
13
|
+
from dataclasses import dataclass
|
|
13
14
|
from pathlib import Path
|
|
14
|
-
from typing import Any, cast
|
|
15
|
+
from typing import Any, cast, override
|
|
15
16
|
|
|
16
17
|
from pulse.transpiler.builtins import BUILTINS, emit_method
|
|
17
18
|
from pulse.transpiler.errors import TranspileError
|
|
@@ -30,6 +31,7 @@ from pulse.transpiler.nodes import (
|
|
|
30
31
|
Function,
|
|
31
32
|
Identifier,
|
|
32
33
|
If,
|
|
34
|
+
LetDecl,
|
|
33
35
|
Literal,
|
|
34
36
|
Member,
|
|
35
37
|
New,
|
|
@@ -78,6 +80,228 @@ ALLOWED_CMPOPS: dict[type[ast.cmpop], str] = {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
|
|
83
|
+
def _collect_param_names(args: ast.arguments) -> list[str]:
|
|
84
|
+
"""Collect argument names (regular, vararg, kwonly, kwarg)."""
|
|
85
|
+
names: list[str] = [arg.arg for arg in args.args]
|
|
86
|
+
if args.vararg:
|
|
87
|
+
names.append(args.vararg.arg)
|
|
88
|
+
names.extend(arg.arg for arg in args.kwonlyargs)
|
|
89
|
+
if args.kwarg:
|
|
90
|
+
names.append(args.kwarg.arg)
|
|
91
|
+
return names
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(slots=True)
|
|
95
|
+
class Scope:
|
|
96
|
+
locals: set[str]
|
|
97
|
+
params: set[str]
|
|
98
|
+
parent: "Scope | None" = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ScopeAnalyzer(ast.NodeVisitor):
|
|
102
|
+
"""Collect locals per scope (function/lambda/comprehension)."""
|
|
103
|
+
|
|
104
|
+
_scopes: dict[ast.AST, Scope]
|
|
105
|
+
_stack: list[Scope]
|
|
106
|
+
_global_scope: Scope
|
|
107
|
+
|
|
108
|
+
def __init__(self) -> None:
|
|
109
|
+
self._scopes = {}
|
|
110
|
+
self._stack = []
|
|
111
|
+
self._global_scope = Scope(locals=set(), params=set(), parent=None)
|
|
112
|
+
|
|
113
|
+
def analyze(
|
|
114
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef
|
|
115
|
+
) -> dict[ast.AST, Scope]:
|
|
116
|
+
self._stack.append(self._global_scope)
|
|
117
|
+
self._visit_defaults(node.args)
|
|
118
|
+
scope = self._new_scope(
|
|
119
|
+
node, _collect_param_names(node.args), parent=self._global_scope
|
|
120
|
+
)
|
|
121
|
+
self._stack.append(scope)
|
|
122
|
+
for stmt in node.body:
|
|
123
|
+
self.visit(stmt)
|
|
124
|
+
self._stack.pop()
|
|
125
|
+
self._stack.pop()
|
|
126
|
+
return self._scopes
|
|
127
|
+
|
|
128
|
+
def _new_scope(
|
|
129
|
+
self,
|
|
130
|
+
node: ast.AST,
|
|
131
|
+
params: list[str],
|
|
132
|
+
*,
|
|
133
|
+
parent: Scope | None,
|
|
134
|
+
) -> Scope:
|
|
135
|
+
param_set = set(params)
|
|
136
|
+
scope = Scope(locals=set(param_set), params=param_set, parent=parent)
|
|
137
|
+
self._scopes[node] = scope
|
|
138
|
+
return scope
|
|
139
|
+
|
|
140
|
+
def _current_scope(self) -> Scope:
|
|
141
|
+
return self._stack[-1]
|
|
142
|
+
|
|
143
|
+
def _add_local(self, name: str) -> None:
|
|
144
|
+
self._current_scope().locals.add(name)
|
|
145
|
+
|
|
146
|
+
def _add_targets(self, target: ast.expr) -> None:
|
|
147
|
+
if isinstance(target, ast.Name):
|
|
148
|
+
self._add_local(target.id)
|
|
149
|
+
return
|
|
150
|
+
if isinstance(target, (ast.Tuple, ast.List)):
|
|
151
|
+
for elt in target.elts:
|
|
152
|
+
if isinstance(elt, ast.Name):
|
|
153
|
+
self._add_local(elt.id)
|
|
154
|
+
|
|
155
|
+
def _visit_target_expr(self, target: ast.expr) -> None:
|
|
156
|
+
if isinstance(target, (ast.Tuple, ast.List)):
|
|
157
|
+
for elt in target.elts:
|
|
158
|
+
self._visit_target_expr(elt)
|
|
159
|
+
return
|
|
160
|
+
self.visit(target)
|
|
161
|
+
|
|
162
|
+
def _analyze_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
163
|
+
self._visit_defaults(node.args)
|
|
164
|
+
scope = self._new_scope(
|
|
165
|
+
node,
|
|
166
|
+
_collect_param_names(node.args),
|
|
167
|
+
parent=self._current_scope(),
|
|
168
|
+
)
|
|
169
|
+
self._stack.append(scope)
|
|
170
|
+
for stmt in node.body:
|
|
171
|
+
self.visit(stmt)
|
|
172
|
+
self._stack.pop()
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def global_scope(self) -> Scope:
|
|
176
|
+
return self._global_scope
|
|
177
|
+
|
|
178
|
+
def _visit_defaults(self, args: ast.arguments) -> None:
|
|
179
|
+
for default in args.defaults:
|
|
180
|
+
self.visit(default)
|
|
181
|
+
for default in args.kw_defaults:
|
|
182
|
+
if default is not None:
|
|
183
|
+
self.visit(default)
|
|
184
|
+
|
|
185
|
+
@override
|
|
186
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
187
|
+
self._add_local(node.name)
|
|
188
|
+
self._analyze_function(node)
|
|
189
|
+
|
|
190
|
+
@override
|
|
191
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
192
|
+
self._add_local(node.name)
|
|
193
|
+
self._analyze_function(node)
|
|
194
|
+
|
|
195
|
+
@override
|
|
196
|
+
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
197
|
+
scope = self._new_scope(
|
|
198
|
+
node,
|
|
199
|
+
_collect_param_names(node.args),
|
|
200
|
+
parent=self._current_scope(),
|
|
201
|
+
)
|
|
202
|
+
self._stack.append(scope)
|
|
203
|
+
self.visit(node.body)
|
|
204
|
+
self._stack.pop()
|
|
205
|
+
|
|
206
|
+
def _visit_comprehension(
|
|
207
|
+
self,
|
|
208
|
+
node: ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp,
|
|
209
|
+
*,
|
|
210
|
+
elt: ast.expr,
|
|
211
|
+
key: ast.expr | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
scope = self._new_scope(node, [], parent=self._current_scope())
|
|
214
|
+
self._stack.append(scope)
|
|
215
|
+
if key is not None:
|
|
216
|
+
self.visit(key)
|
|
217
|
+
self.visit(elt)
|
|
218
|
+
for gen in node.generators:
|
|
219
|
+
self.visit(gen.iter)
|
|
220
|
+
for if_node in gen.ifs:
|
|
221
|
+
self.visit(if_node)
|
|
222
|
+
self._stack.pop()
|
|
223
|
+
|
|
224
|
+
@override
|
|
225
|
+
def visit_ListComp(self, node: ast.ListComp) -> None:
|
|
226
|
+
self._visit_comprehension(node, elt=node.elt)
|
|
227
|
+
|
|
228
|
+
@override
|
|
229
|
+
def visit_SetComp(self, node: ast.SetComp) -> None:
|
|
230
|
+
self._visit_comprehension(node, elt=node.elt)
|
|
231
|
+
|
|
232
|
+
@override
|
|
233
|
+
def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
|
|
234
|
+
self._visit_comprehension(node, elt=node.elt)
|
|
235
|
+
|
|
236
|
+
@override
|
|
237
|
+
def visit_DictComp(self, node: ast.DictComp) -> None:
|
|
238
|
+
self._visit_comprehension(node, elt=node.value, key=node.key)
|
|
239
|
+
|
|
240
|
+
@override
|
|
241
|
+
def visit_Assign(self, node: ast.Assign) -> None:
|
|
242
|
+
for target in node.targets:
|
|
243
|
+
if isinstance(target, (ast.Name, ast.Tuple, ast.List)):
|
|
244
|
+
self._add_targets(target)
|
|
245
|
+
self._visit_target_expr(target)
|
|
246
|
+
self.visit(node.value)
|
|
247
|
+
|
|
248
|
+
@override
|
|
249
|
+
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
|
|
250
|
+
if isinstance(node.target, (ast.Name, ast.Tuple, ast.List)):
|
|
251
|
+
self._add_targets(node.target)
|
|
252
|
+
self._visit_target_expr(node.target)
|
|
253
|
+
if node.value is not None:
|
|
254
|
+
self.visit(node.value)
|
|
255
|
+
|
|
256
|
+
@override
|
|
257
|
+
def visit_AugAssign(self, node: ast.AugAssign) -> None:
|
|
258
|
+
if isinstance(node.target, (ast.Name, ast.Tuple, ast.List)):
|
|
259
|
+
self._add_targets(node.target)
|
|
260
|
+
self._visit_target_expr(node.target)
|
|
261
|
+
self.visit(node.value)
|
|
262
|
+
|
|
263
|
+
@override
|
|
264
|
+
def visit_For(self, node: ast.For) -> None:
|
|
265
|
+
if isinstance(node.target, (ast.Name, ast.Tuple, ast.List)):
|
|
266
|
+
self._add_targets(node.target)
|
|
267
|
+
self._visit_target_expr(node.target)
|
|
268
|
+
self.visit(node.iter)
|
|
269
|
+
for stmt in node.body:
|
|
270
|
+
self.visit(stmt)
|
|
271
|
+
for stmt in node.orelse:
|
|
272
|
+
self.visit(stmt)
|
|
273
|
+
|
|
274
|
+
@override
|
|
275
|
+
def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None:
|
|
276
|
+
if node.name:
|
|
277
|
+
self._add_local(node.name)
|
|
278
|
+
if node.type is not None:
|
|
279
|
+
self.visit(node.type)
|
|
280
|
+
for stmt in node.body:
|
|
281
|
+
self.visit(stmt)
|
|
282
|
+
|
|
283
|
+
@override
|
|
284
|
+
def visit_With(self, node: ast.With) -> None:
|
|
285
|
+
for item in node.items:
|
|
286
|
+
if item.optional_vars and isinstance(
|
|
287
|
+
item.optional_vars, (ast.Name, ast.Tuple, ast.List)
|
|
288
|
+
):
|
|
289
|
+
self._add_targets(item.optional_vars)
|
|
290
|
+
if item.optional_vars is not None:
|
|
291
|
+
self._visit_target_expr(item.optional_vars)
|
|
292
|
+
self.visit(item.context_expr)
|
|
293
|
+
for stmt in node.body:
|
|
294
|
+
self.visit(stmt)
|
|
295
|
+
|
|
296
|
+
@override
|
|
297
|
+
def visit_Global(self, node: ast.Global) -> None:
|
|
298
|
+
raise TranspileError("global is not supported", node=node)
|
|
299
|
+
|
|
300
|
+
@override
|
|
301
|
+
def visit_Nonlocal(self, node: ast.Nonlocal) -> None:
|
|
302
|
+
raise TranspileError("nonlocal is not supported", node=node)
|
|
303
|
+
|
|
304
|
+
|
|
81
305
|
class Transpiler:
|
|
82
306
|
"""Transpile Python AST to v2 Expr/Stmt AST nodes.
|
|
83
307
|
|
|
@@ -93,10 +317,12 @@ class Transpiler:
|
|
|
93
317
|
fndef: ast.FunctionDef | ast.AsyncFunctionDef
|
|
94
318
|
args: list[str]
|
|
95
319
|
deps: Mapping[str, Expr]
|
|
96
|
-
locals: set[str]
|
|
97
320
|
jsx: bool
|
|
98
321
|
source_file: Path | None
|
|
99
322
|
_temp_counter: int
|
|
323
|
+
_scope_map: dict[ast.AST, Scope]
|
|
324
|
+
_scope_stack: list[Scope]
|
|
325
|
+
_global_scope: Scope
|
|
100
326
|
|
|
101
327
|
def __init__(
|
|
102
328
|
self,
|
|
@@ -109,22 +335,21 @@ class Transpiler:
|
|
|
109
335
|
self.fndef = fndef
|
|
110
336
|
self.source_file = source_file
|
|
111
337
|
# Collect all argument names (regular, vararg, kwonly, kwarg)
|
|
112
|
-
args
|
|
113
|
-
if fndef.args.vararg:
|
|
114
|
-
args.append(fndef.args.vararg.arg)
|
|
115
|
-
args.extend(arg.arg for arg in fndef.args.kwonlyargs)
|
|
116
|
-
if fndef.args.kwarg:
|
|
117
|
-
args.append(fndef.args.kwarg.arg)
|
|
118
|
-
self.args = args
|
|
338
|
+
self.args = _collect_param_names(fndef.args)
|
|
119
339
|
self.deps = deps
|
|
120
340
|
self.jsx = jsx
|
|
121
|
-
self.locals = set(self.args)
|
|
122
341
|
self._temp_counter = 0
|
|
342
|
+
analyzer = ScopeAnalyzer()
|
|
343
|
+
self._scope_map = analyzer.analyze(fndef)
|
|
344
|
+
self._global_scope = analyzer.global_scope
|
|
345
|
+
self._scope_stack = [self._scope_map[fndef]]
|
|
123
346
|
self.init_temp_counter()
|
|
124
347
|
|
|
125
348
|
def init_temp_counter(self) -> None:
|
|
126
349
|
"""Initialize temp counter to avoid collisions with args or globals."""
|
|
127
|
-
all_names = set(self.
|
|
350
|
+
all_names = set(self.deps.keys())
|
|
351
|
+
for scope in self._scope_map.values():
|
|
352
|
+
all_names.update(scope.locals)
|
|
128
353
|
counter = 0
|
|
129
354
|
while f"$tmp{counter}" in all_names:
|
|
130
355
|
counter += 1
|
|
@@ -136,6 +361,38 @@ class Transpiler:
|
|
|
136
361
|
self._temp_counter += 1
|
|
137
362
|
return name
|
|
138
363
|
|
|
364
|
+
def _current_scope(self) -> Scope:
|
|
365
|
+
return self._scope_stack[-1]
|
|
366
|
+
|
|
367
|
+
def _push_scope(self, node: ast.AST) -> None:
|
|
368
|
+
self._scope_stack.append(self._scope_map[node])
|
|
369
|
+
|
|
370
|
+
def _pop_scope(self) -> None:
|
|
371
|
+
self._scope_stack.pop()
|
|
372
|
+
|
|
373
|
+
def _is_local_here(self, name: str) -> bool:
|
|
374
|
+
return name in self._current_scope().locals
|
|
375
|
+
|
|
376
|
+
def _is_local(self, name: str) -> bool:
|
|
377
|
+
scope: Scope | None = self._current_scope()
|
|
378
|
+
while scope is not None:
|
|
379
|
+
if name in scope.locals:
|
|
380
|
+
return True
|
|
381
|
+
scope = scope.parent
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def _require_local(
|
|
385
|
+
self, name: str, *, node: ast.expr | ast.stmt | ast.excepthandler
|
|
386
|
+
) -> None:
|
|
387
|
+
if not self._is_local_here(name):
|
|
388
|
+
raise TranspileError(f"Assignment to unknown local: {name}", node=node)
|
|
389
|
+
|
|
390
|
+
def _function_prelude(self, scope: Scope) -> list[Stmt]:
|
|
391
|
+
names = sorted(scope.locals - scope.params)
|
|
392
|
+
if not names:
|
|
393
|
+
return []
|
|
394
|
+
return [LetDecl(names)]
|
|
395
|
+
|
|
139
396
|
# --- Entrypoint ---------------------------------------------------------
|
|
140
397
|
|
|
141
398
|
def transpile(self) -> Function | Arrow:
|
|
@@ -176,7 +433,8 @@ class Transpiler:
|
|
|
176
433
|
return Arrow(self.args, expr)
|
|
177
434
|
|
|
178
435
|
# General case: Function (for JSX or multi-statement)
|
|
179
|
-
|
|
436
|
+
prelude = self._function_prelude(self._current_scope())
|
|
437
|
+
stmts = prelude + [self.emit_stmt(s) for s in body]
|
|
180
438
|
is_async = isinstance(self.fndef, ast.AsyncFunctionDef)
|
|
181
439
|
args = [self._jsx_args()] if self.jsx else self.args
|
|
182
440
|
return Function(args, stmts, is_async=is_async)
|
|
@@ -202,7 +460,7 @@ class Transpiler:
|
|
|
202
460
|
if default_idx >= 0:
|
|
203
461
|
# Has a default value
|
|
204
462
|
default_node = args.defaults[default_idx]
|
|
205
|
-
default_expr = self.
|
|
463
|
+
default_expr = self._emit_default_expr(default_node)
|
|
206
464
|
default_out.clear()
|
|
207
465
|
default_expr.emit(default_out)
|
|
208
466
|
destructure_parts.append(f"{param_name} = {''.join(default_out)}")
|
|
@@ -220,7 +478,7 @@ class Transpiler:
|
|
|
220
478
|
default_node = args.kw_defaults[i]
|
|
221
479
|
if default_node is not None:
|
|
222
480
|
# Has a default value
|
|
223
|
-
default_expr = self.
|
|
481
|
+
default_expr = self._emit_default_expr(default_node)
|
|
224
482
|
default_out.clear()
|
|
225
483
|
default_expr.emit(default_out)
|
|
226
484
|
destructure_parts.append(f"{param_name} = {''.join(default_out)}")
|
|
@@ -234,6 +492,14 @@ class Transpiler:
|
|
|
234
492
|
|
|
235
493
|
return "{" + ", ".join(destructure_parts) + "}"
|
|
236
494
|
|
|
495
|
+
def _emit_default_expr(self, node: ast.expr) -> Expr:
|
|
496
|
+
"""Emit defaults in defining-scope context."""
|
|
497
|
+
self._scope_stack.append(self._global_scope)
|
|
498
|
+
try:
|
|
499
|
+
return self.emit_expr(node)
|
|
500
|
+
finally:
|
|
501
|
+
self._scope_stack.pop()
|
|
502
|
+
|
|
237
503
|
# --- Statements ----------------------------------------------------------
|
|
238
504
|
|
|
239
505
|
def emit_stmt(self, node: ast.stmt) -> Stmt:
|
|
@@ -262,6 +528,7 @@ class Transpiler:
|
|
|
262
528
|
"Only simple augmented assignments supported", node=node
|
|
263
529
|
)
|
|
264
530
|
target = node.target.id
|
|
531
|
+
self._require_local(target, node=node)
|
|
265
532
|
op_type = type(node.op)
|
|
266
533
|
if op_type not in ALLOWED_BINOPS:
|
|
267
534
|
raise TranspileError(
|
|
@@ -296,22 +563,16 @@ class Transpiler:
|
|
|
296
563
|
target = target_node.id
|
|
297
564
|
value_expr = self.emit_expr(node.value)
|
|
298
565
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
else:
|
|
302
|
-
self.locals.add(target)
|
|
303
|
-
return Assign(target, value_expr, declare="let")
|
|
566
|
+
self._require_local(target, node=node)
|
|
567
|
+
return Assign(target, value_expr)
|
|
304
568
|
|
|
305
569
|
if isinstance(node, ast.AnnAssign):
|
|
306
570
|
if not isinstance(node.target, ast.Name):
|
|
307
571
|
raise TranspileError("Only simple annotated assignments supported")
|
|
308
572
|
target = node.target.id
|
|
309
573
|
value = Literal(None) if node.value is None else self.emit_expr(node.value)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
else:
|
|
313
|
-
self.locals.add(target)
|
|
314
|
-
return Assign(target, value, declare="let")
|
|
574
|
+
self._require_local(target, node=node)
|
|
575
|
+
return Assign(target, value)
|
|
315
576
|
|
|
316
577
|
if isinstance(node, ast.If):
|
|
317
578
|
cond = self.emit_expr(node.test)
|
|
@@ -358,11 +619,8 @@ class Transpiler:
|
|
|
358
619
|
assert isinstance(e, ast.Name)
|
|
359
620
|
name = e.id
|
|
360
621
|
sub = Subscript(Identifier(tmp_name), Literal(idx))
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
else:
|
|
364
|
-
self.locals.add(name)
|
|
365
|
-
stmts.append(Assign(name, sub, declare="let"))
|
|
622
|
+
self._require_local(name, node=target)
|
|
623
|
+
stmts.append(Assign(name, sub))
|
|
366
624
|
|
|
367
625
|
return StmtSequence(stmts)
|
|
368
626
|
|
|
@@ -453,7 +711,7 @@ class Transpiler:
|
|
|
453
711
|
"Only simple name targets supported in for-loop unpacking"
|
|
454
712
|
)
|
|
455
713
|
names.append(e.id)
|
|
456
|
-
self.
|
|
714
|
+
self._require_local(e.id, node=node)
|
|
457
715
|
iter_expr = self.emit_expr(node.iter)
|
|
458
716
|
body = [self.emit_stmt(s) for s in node.body]
|
|
459
717
|
# Use array pattern for destructuring
|
|
@@ -464,7 +722,7 @@ class Transpiler:
|
|
|
464
722
|
raise TranspileError("Only simple name targets supported in for-loops")
|
|
465
723
|
|
|
466
724
|
target = node.target.id
|
|
467
|
-
self.
|
|
725
|
+
self._require_local(target, node=node)
|
|
468
726
|
iter_expr = self.emit_expr(node.iter)
|
|
469
727
|
body = [self.emit_stmt(s) for s in node.body]
|
|
470
728
|
return ForOf(target, iter_expr, body)
|
|
@@ -474,31 +732,29 @@ class Transpiler:
|
|
|
474
732
|
) -> Stmt:
|
|
475
733
|
"""Emit a nested function definition."""
|
|
476
734
|
name = node.name
|
|
477
|
-
params =
|
|
478
|
-
|
|
479
|
-
# Save current locals and extend with params
|
|
480
|
-
saved_locals = set(self.locals)
|
|
481
|
-
self.locals.update(params)
|
|
482
|
-
|
|
483
|
-
# Skip docstrings and emit body
|
|
484
|
-
body_stmts = node.body
|
|
485
|
-
if (
|
|
486
|
-
body_stmts
|
|
487
|
-
and isinstance(body_stmts[0], ast.Expr)
|
|
488
|
-
and isinstance(body_stmts[0].value, ast.Constant)
|
|
489
|
-
and isinstance(body_stmts[0].value.value, str)
|
|
490
|
-
):
|
|
491
|
-
body_stmts = body_stmts[1:]
|
|
735
|
+
params = _collect_param_names(node.args)
|
|
736
|
+
self._require_local(name, node=node)
|
|
492
737
|
|
|
493
|
-
|
|
738
|
+
self._push_scope(node)
|
|
739
|
+
try:
|
|
740
|
+
# Skip docstrings and emit body
|
|
741
|
+
body_stmts = node.body
|
|
742
|
+
if (
|
|
743
|
+
body_stmts
|
|
744
|
+
and isinstance(body_stmts[0], ast.Expr)
|
|
745
|
+
and isinstance(body_stmts[0].value, ast.Constant)
|
|
746
|
+
and isinstance(body_stmts[0].value.value, str)
|
|
747
|
+
):
|
|
748
|
+
body_stmts = body_stmts[1:]
|
|
494
749
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
750
|
+
prelude = self._function_prelude(self._current_scope())
|
|
751
|
+
stmts: list[Stmt] = prelude + [self.emit_stmt(s) for s in body_stmts]
|
|
752
|
+
finally:
|
|
753
|
+
self._pop_scope()
|
|
498
754
|
|
|
499
755
|
is_async = isinstance(node, ast.AsyncFunctionDef)
|
|
500
756
|
fn = Function(params, stmts, is_async=is_async)
|
|
501
|
-
return Assign(name, fn
|
|
757
|
+
return Assign(name, fn)
|
|
502
758
|
|
|
503
759
|
def _emit_try(self, node: ast.Try) -> Stmt:
|
|
504
760
|
"""Emit a try/except/finally statement."""
|
|
@@ -517,7 +773,6 @@ class Transpiler:
|
|
|
517
773
|
handler = node.handlers[0]
|
|
518
774
|
if handler.name:
|
|
519
775
|
catch_param = handler.name
|
|
520
|
-
self.locals.add(catch_param)
|
|
521
776
|
catch_body = [self.emit_stmt(s) for s in handler.body]
|
|
522
777
|
|
|
523
778
|
# Handle finally
|
|
@@ -594,23 +849,21 @@ class Transpiler:
|
|
|
594
849
|
|
|
595
850
|
if isinstance(node, ast.ListComp):
|
|
596
851
|
return self._emit_comprehension_chain(
|
|
597
|
-
node
|
|
852
|
+
node, lambda: self.emit_expr(node.elt)
|
|
598
853
|
)
|
|
599
854
|
|
|
600
855
|
if isinstance(node, ast.GeneratorExp):
|
|
601
856
|
return self._emit_comprehension_chain(
|
|
602
|
-
node
|
|
857
|
+
node, lambda: self.emit_expr(node.elt)
|
|
603
858
|
)
|
|
604
859
|
|
|
605
860
|
if isinstance(node, ast.SetComp):
|
|
606
|
-
arr = self._emit_comprehension_chain(
|
|
607
|
-
node.generators, lambda: self.emit_expr(node.elt)
|
|
608
|
-
)
|
|
861
|
+
arr = self._emit_comprehension_chain(node, lambda: self.emit_expr(node.elt))
|
|
609
862
|
return New(Identifier("Set"), [arr])
|
|
610
863
|
|
|
611
864
|
if isinstance(node, ast.DictComp):
|
|
612
865
|
pairs = self._emit_comprehension_chain(
|
|
613
|
-
node
|
|
866
|
+
node,
|
|
614
867
|
lambda: Array([self.emit_expr(node.key), self.emit_expr(node.value)]),
|
|
615
868
|
)
|
|
616
869
|
return New(Identifier("Map"), [pairs])
|
|
@@ -648,14 +901,14 @@ class Transpiler:
|
|
|
648
901
|
"""Emit a name reference."""
|
|
649
902
|
name = node.id
|
|
650
903
|
|
|
651
|
-
#
|
|
904
|
+
# Local variable (current or enclosing scope)
|
|
905
|
+
if self._is_local(name):
|
|
906
|
+
return Identifier(name)
|
|
907
|
+
|
|
908
|
+
# Check deps
|
|
652
909
|
if name in self.deps:
|
|
653
910
|
return self.deps[name]
|
|
654
911
|
|
|
655
|
-
# Local variable
|
|
656
|
-
if name in self.locals:
|
|
657
|
-
return Identifier(name)
|
|
658
|
-
|
|
659
912
|
# Check builtins
|
|
660
913
|
if name in BUILTINS:
|
|
661
914
|
return BUILTINS[name]
|
|
@@ -1093,29 +1346,26 @@ class Transpiler:
|
|
|
1093
1346
|
|
|
1094
1347
|
def _emit_lambda(self, node: ast.Lambda) -> Expr:
|
|
1095
1348
|
"""Emit a lambda expression as an arrow function."""
|
|
1096
|
-
params =
|
|
1097
|
-
|
|
1098
|
-
# Add params to locals temporarily
|
|
1099
|
-
saved_locals = set(self.locals)
|
|
1100
|
-
self.locals.update(params)
|
|
1101
|
-
|
|
1102
|
-
body = self.emit_expr(node.body)
|
|
1349
|
+
params = _collect_param_names(node.args)
|
|
1103
1350
|
|
|
1104
|
-
self.
|
|
1351
|
+
self._push_scope(node)
|
|
1352
|
+
try:
|
|
1353
|
+
body = self.emit_expr(node.body)
|
|
1354
|
+
finally:
|
|
1355
|
+
self._pop_scope()
|
|
1105
1356
|
|
|
1106
1357
|
return Arrow(params, body)
|
|
1107
1358
|
|
|
1108
1359
|
def _emit_comprehension_chain(
|
|
1109
1360
|
self,
|
|
1110
|
-
|
|
1361
|
+
node: ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp,
|
|
1111
1362
|
build_last: Callable[[], Expr],
|
|
1112
1363
|
) -> Expr:
|
|
1113
1364
|
"""Build a flatMap/map chain for comprehensions."""
|
|
1365
|
+
generators = node.generators
|
|
1114
1366
|
if len(generators) == 0:
|
|
1115
1367
|
raise TranspileError("Empty comprehension")
|
|
1116
1368
|
|
|
1117
|
-
saved_locals = set(self.locals)
|
|
1118
|
-
|
|
1119
1369
|
def build_chain(gen_index: int) -> Expr:
|
|
1120
1370
|
gen = generators[gen_index]
|
|
1121
1371
|
if gen.is_async:
|
|
@@ -1139,7 +1389,7 @@ class Transpiler:
|
|
|
1139
1389
|
)
|
|
1140
1390
|
|
|
1141
1391
|
for nm in names:
|
|
1142
|
-
self.locals.add(nm)
|
|
1392
|
+
self._current_scope().locals.add(nm)
|
|
1143
1393
|
|
|
1144
1394
|
base = iter_expr
|
|
1145
1395
|
|
|
@@ -1159,10 +1409,11 @@ class Transpiler:
|
|
|
1159
1409
|
inner = build_chain(gen_index + 1)
|
|
1160
1410
|
return Call(Member(base, "flatMap"), [Arrow(params, inner)])
|
|
1161
1411
|
|
|
1412
|
+
self._push_scope(node)
|
|
1162
1413
|
try:
|
|
1163
1414
|
return build_chain(0)
|
|
1164
1415
|
finally:
|
|
1165
|
-
self.
|
|
1416
|
+
self._pop_scope()
|
|
1166
1417
|
|
|
1167
1418
|
|
|
1168
1419
|
def transpile(
|
|
@@ -48,27 +48,44 @@ pulse/hooks/runtime.py,sha256=ogrm4Prvr9ZNaBb5bLfZBHrzbJuYe1zKpIPkbdnIzsw,12286
|
|
|
48
48
|
pulse/hooks/setup.py,sha256=NcQPKnMV5dO0vUsWi4u9c9LB0wqFstrtiPGdvihtGiQ,6872
|
|
49
49
|
pulse/hooks/stable.py,sha256=hLCOl_oAbgdNwiaWwwUTk7ZsHvMqXFFozFztJZMyGbQ,3627
|
|
50
50
|
pulse/hooks/state.py,sha256=zRFlcOUdl-SBkJ-EzVXRLrLXzAc4-uRzgH4hD9rM1oU,5251
|
|
51
|
-
pulse/js/__init__.py,sha256=
|
|
52
|
-
pulse/js/__init__.pyi,sha256=
|
|
51
|
+
pulse/js/__init__.py,sha256=csEWvFon_ayAiR6OVQl2wnukaLWLK1_PPodVlcY_EvY,5062
|
|
52
|
+
pulse/js/__init__.pyi,sha256=DCgO3uNh6dQLVd9QD3I-LyiPP9VrtE7MZYYHr-8EvVg,5479
|
|
53
53
|
pulse/js/_types.py,sha256=F4Go2JtJ2dbxq1fXpc2ablG_nyvhvHzOlZLlEv0VmyU,7421
|
|
54
|
+
pulse/js/abort_controller.py,sha256=nKiUgS_uy2Rgh-MNG7fcGIm8Qd6g7hNV_UVk7OgK3qg,1161
|
|
54
55
|
pulse/js/array.py,sha256=_tC6QZlflWCXOXXUMMtowM3UK7iDWAtFM8BKqR5rjKk,8883
|
|
56
|
+
pulse/js/array_buffer.py,sha256=-iuiSZuv-rx3d0jpQtQYZLXZ2nLuMF93QrQBO-HXv5o,4100
|
|
57
|
+
pulse/js/blob.py,sha256=XiI4g_fKaKtB8OPzS6D4rFG0fRBZlCl9kbO7a5IFPPk,981
|
|
55
58
|
pulse/js/console.py,sha256=A-GNKEnPby10gdcTdYsBPVfz4m94PYzTXRwGhfaPRpc,1775
|
|
59
|
+
pulse/js/crypto.py,sha256=Atxtc2oqb4xnW9nQV3ruamQhCAl2O_H2XiECspN1JvI,1245
|
|
60
|
+
pulse/js/custom_event.py,sha256=SxtzPQJwgSXRmesd3SiaVdCGYHsFcrnAiPZA63YuunA,901
|
|
56
61
|
pulse/js/date.py,sha256=qJjdwupuUtKS95u8N8C8FKMKOIB8qjVMsYA3VYfe-tA,3363
|
|
57
62
|
pulse/js/document.py,sha256=SBinVGfb05jFpeyxAE0yk5Z__dkdW_mFsTI-rvgc-S8,3004
|
|
63
|
+
pulse/js/dom_parser.py,sha256=0qRlAHVr1gDphCPUOSJRcZR94giJF6JmsWOJv4I6p80,818
|
|
58
64
|
pulse/js/error.py,sha256=v0_DmpN5ESt_CJTrIYfy8980eerjK8mHhQatNV_1M_8,2611
|
|
65
|
+
pulse/js/fetch_api.py,sha256=-4qy5DkQcCeE9CvFMtq-by-WgA7PirgOrmgMflMsluQ,2342
|
|
66
|
+
pulse/js/file.py,sha256=LDoAUXyq82VUULVkIeSjcIj1Vvy2hCi_LRUWh8THyP8,1177
|
|
67
|
+
pulse/js/file_reader.py,sha256=-Y5rfjb2j9tQ-NDX-mWgERC6Alfllh2VvRr6rzXoII0,664
|
|
68
|
+
pulse/js/form_data.py,sha256=_NKZZmD4G4gHsq-QKFMAFwU73ZatGK06p6_oUdQnh_U,1258
|
|
69
|
+
pulse/js/intersection_observer.py,sha256=ZcNV1qyiXjKPOKz5zUQTMZ3oCpfxxSRxLCEDHS_5aKY,1672
|
|
70
|
+
pulse/js/intl.py,sha256=Y_a1YvdQPzOpJAnpPiyM7Ip03L5mh-noChRDeDneVjU,2034
|
|
59
71
|
pulse/js/json.py,sha256=P8nxOANjIxrzUA1XkBrd0SmNyAGyB3pVXZDPnA1OKks,1908
|
|
60
72
|
pulse/js/map.py,sha256=bhw75CUMIearH4JACCs9zAffdzfla3Rae7SKGcCLGoc,2243
|
|
61
73
|
pulse/js/math.py,sha256=OBbMlgoa6ZHLDgmXGNKMj5wYrvroV5ICIx-VsSE5_II,1757
|
|
74
|
+
pulse/js/mutation_observer.py,sha256=7Ai74vHZFkAnVS-OrGYRanY9TpJZmHJ4PrKpz8jmTTU,1831
|
|
62
75
|
pulse/js/navigator.py,sha256=2QWSr9xyyBfgd76P472qmayXQRYXIEo-b8zLzvfhHfg,1517
|
|
63
76
|
pulse/js/number.py,sha256=fX2M6hZ5ry2FPsYaHhGlqgO6reBEXw7C-gtu0-8_Zyw,1262
|
|
64
77
|
pulse/js/obj.py,sha256=8JG9OZZ1CNqAFoMTdYtxWhTmb6zs1BqxC-nLT7KYMF8,2030
|
|
65
78
|
pulse/js/object.py,sha256=95WvnGWgB-PL-D7l12UgdxNy_fxO5sJXool3Rx5ahUQ,4433
|
|
79
|
+
pulse/js/performance_observer.py,sha256=uletaqnJ6kvJqFSSFtmNo0j--FduK4uZHd8cfRqeN3A,1156
|
|
66
80
|
pulse/js/promise.py,sha256=vBXcL-U9BuZN-q1jbYhyzQaOL2niDPw4LsD7q7Y_yco,4670
|
|
67
81
|
pulse/js/pulse.py,sha256=m-LgqwhYygVBj7GzjeO-uo8fK5ThyVe7c3QvOJt_vc0,2962
|
|
68
82
|
pulse/js/react.py,sha256=eRMrgM8RsoAIn2lcHDoUYas3l4tImLOW51dwmw9AxQU,12057
|
|
69
83
|
pulse/js/regexp.py,sha256=qO-3nmt7uGN7V_bwimPCN-2RSsPfE6YiY7G1MjoP3YY,1055
|
|
84
|
+
pulse/js/resize_observer.py,sha256=MN0pqnBETmI7bSrWT1bus4_vTZKmJ0jwBoW-cYQWoT4,1162
|
|
70
85
|
pulse/js/set.py,sha256=omG3g-25GRHxgoKISSB4x-M8UDFlaXtFV9cSIpd5uB0,3017
|
|
71
86
|
pulse/js/string.py,sha256=VsvDF_ve8R9QIiBdDotLP2KpCKwmpEfGgRQWckOCmHk,813
|
|
87
|
+
pulse/js/text_encoding.py,sha256=5Qvl4XRva9m6tz9NKlv9Nej-O92epMG-y_bu6vCNHwI,1291
|
|
88
|
+
pulse/js/url.py,sha256=-sOthAlPOhJvkXh4hhNLOuWbPKd_ICnG7u--yHDPzY4,2200
|
|
72
89
|
pulse/js/weakmap.py,sha256=HACZEZ8EZk1xoaCmTXk__opvJLxlJ9_0U3y-01KQkHU,1412
|
|
73
90
|
pulse/js/weakset.py,sha256=jerMG9ubroR29HvOLIm6lkLxMj-GGWbiE57U9F6zRuQ,1256
|
|
74
91
|
pulse/js/window.py,sha256=yC1BjyH2jqp1x-CXJCUFta-ASyZ5668ozQ0AmAjZcxA,4097
|
|
@@ -101,13 +118,13 @@ pulse/state/property.py,sha256=pitudvCGBNqK6lePdPrs4Inyy7szo8qcR_0BI30veek,6274
|
|
|
101
118
|
pulse/state/query_param.py,sha256=7faf244_KJ3yrsBimKTBlNLg6wP96FUuWoZl40oxpyY,14478
|
|
102
119
|
pulse/state/state.py,sha256=_4DcB-og2s5Ui01kDx9HzpNwmAVD_pdCLB0TfYbcnzM,11603
|
|
103
120
|
pulse/test_helpers.py,sha256=4iO5Ymy3SMvSjh-UaAaSdqm1I_SAJMNjdY2iYVro5f8,436
|
|
104
|
-
pulse/transpiler/__init__.py,sha256=
|
|
121
|
+
pulse/transpiler/__init__.py,sha256=0UqeEQ2dMD2tDDkzTASv-StQfKZkPuSvbYTqjkNk-3A,4895
|
|
105
122
|
pulse/transpiler/assets.py,sha256=digd5hKYPEgLOzMtDBHULX3Adj1sfngdvnx3quQmgPY,2299
|
|
106
123
|
pulse/transpiler/builtins.py,sha256=QZrow7XJ2wxGMAE-mgZmaUD03egOnXCbikOg8yMx9vQ,30807
|
|
107
124
|
pulse/transpiler/dynamic_import.py,sha256=1AmBl6agGSoTZBp_94seXH733fewLOULUix9BOBPtKI,3372
|
|
108
125
|
pulse/transpiler/emit_context.py,sha256=GyK6VdsBSTVIewQRhBagaV0hlqLTlPZ1i8EAZGi8SaY,1321
|
|
109
126
|
pulse/transpiler/errors.py,sha256=LSBjLBnMglbl2D94p9JR4y-3jDefk6iHSlUVBaBOTu4,2823
|
|
110
|
-
pulse/transpiler/function.py,sha256=
|
|
127
|
+
pulse/transpiler/function.py,sha256=8-WZC2VcPTGfFeWWQpCltOVPLyr4f1H8pWoCvSnI654,18105
|
|
111
128
|
pulse/transpiler/id.py,sha256=CdgA1NndBpZjv0Hp4XiYbKn7wi-x4zWsFSjEiViKxVk,434
|
|
112
129
|
pulse/transpiler/imports.py,sha256=gWLjRr9jakbUzBGDEepE2RI5Xn_UZwOD4TmlqjNIapM,10302
|
|
113
130
|
pulse/transpiler/js_module.py,sha256=OcIgmrfiA6Hh6aukzgkyX63KsVSHdLzx5ezdKiJFUaQ,11093
|
|
@@ -118,15 +135,16 @@ pulse/transpiler/modules/math.py,sha256=8gjvdYTMqtuOnXrvX_Lwuo0ywAdSl7cpss4TMk6m
|
|
|
118
135
|
pulse/transpiler/modules/pulse/__init__.py,sha256=TfMsiiB53ZFlxdNl7jfCAiMZs-vSRUTxUmqzkLTj-po,91
|
|
119
136
|
pulse/transpiler/modules/pulse/tags.py,sha256=FMN1mWMlnsXa2qO6VmXxUAhFn1uOfGoKPQOjH4ZPlRE,6218
|
|
120
137
|
pulse/transpiler/modules/typing.py,sha256=J9QCkXE6zzwMjiprX2q1BtK-iKLIiS21sQ78JH4RSMc,1716
|
|
121
|
-
pulse/transpiler/nodes.py,sha256=
|
|
138
|
+
pulse/transpiler/nodes.py,sha256=ObdCFIEvtKMVRO8iy1hIN4L-vC4yPqRvhPS6E344-bE,52673
|
|
139
|
+
pulse/transpiler/parse.py,sha256=uz_KDnjmjzFSjGtVKRznWg95P0NHM8CafWgvqrqJcOs,1622
|
|
122
140
|
pulse/transpiler/py_module.py,sha256=um4BYLrbs01bpgv2LEBHTbhXXh8Bs174c3ygv5tHHOg,4410
|
|
123
|
-
pulse/transpiler/transpiler.py,sha256=
|
|
141
|
+
pulse/transpiler/transpiler.py,sha256=bu33-wGNqHGheT_ZqMnQgEARyPG6xyOvuLuixjxIZnI,42761
|
|
124
142
|
pulse/transpiler/vdom.py,sha256=Bf1yw10hQl8BXa6rhr5byRa5ua3qgRsVGNgEtQneA2A,6460
|
|
125
143
|
pulse/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
144
|
pulse/types/event_handler.py,sha256=psQCydj-WEtBcFU5JU4mDwvyzkW8V2O0g_VFRU2EOHI,1618
|
|
127
145
|
pulse/user_session.py,sha256=nsnsMgqq2xGJZLpbHRMHUHcLrElMP8WcA4gjGMrcoBk,10208
|
|
128
146
|
pulse/version.py,sha256=711vaM1jVIQPgkisGgKZqwmw019qZIsc_QTae75K2pg,1895
|
|
129
|
-
pulse_framework-0.1.
|
|
130
|
-
pulse_framework-0.1.
|
|
131
|
-
pulse_framework-0.1.
|
|
132
|
-
pulse_framework-0.1.
|
|
147
|
+
pulse_framework-0.1.75.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
148
|
+
pulse_framework-0.1.75.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
|
|
149
|
+
pulse_framework-0.1.75.dist-info/METADATA,sha256=Y2M3HpMari75ZADpqGSxEfyP5YGCnRS2AkZ3tE3lmpA,8299
|
|
150
|
+
pulse_framework-0.1.75.dist-info/RECORD,,
|
|
File without changes
|