pulse-framework 0.1.73__tar.gz → 0.1.74__tar.gz
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_framework-0.1.73 → pulse_framework-0.1.74}/PKG-INFO +1 -1
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/pyproject.toml +1 -1
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/__init__.py +5 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/function.py +56 -32
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/nodes.py +17 -2
- pulse_framework-0.1.74/src/pulse/transpiler/parse.py +70 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/transpiler.py +327 -76
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/README.md +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/_examples.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/app.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/channel.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/cmd.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/dependencies.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/folder_lock.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/helpers.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/logging.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/models.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/packages.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/processes.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/secrets.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cli/uvicorn_log_config.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/code_analysis.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/codegen.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/templates/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/templates/layout.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/templates/route.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/templates/routes_ts.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/codegen/utils.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/component.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/components/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/components/for_.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/components/if_.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/components/react_router.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/context.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/cookies.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/debounce.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/decorators.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/elements.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/events.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/props.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/svg.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/tags.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/dom/tags.pyi +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/env.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/forms.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/helpers.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/core.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/effects.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/init.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/runtime.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/setup.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/stable.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/hooks/state.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/__init__.pyi +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/_types.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/array.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/console.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/date.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/document.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/error.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/json.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/map.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/math.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/navigator.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/number.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/obj.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/object.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/promise.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/pulse.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/react.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/regexp.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/set.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/string.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/weakmap.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/weakset.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/js/window.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/messages.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/middleware.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/plugin.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/proxy.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/py.typed +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/client.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/common.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/effect.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/infinite_query.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/mutation.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/protocol.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/query.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/queries/store.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/react_component.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/reactive.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/reactive_extensions.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/render_session.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/renderer.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/request.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/requirements.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/routing.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/scheduling.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/serializer.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/state/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/state/property.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/state/query_param.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/state/state.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/test_helpers.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/assets.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/builtins.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/dynamic_import.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/emit_context.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/errors.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/id.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/imports.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/js_module.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/asyncio.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/json.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/math.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/pulse/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/pulse/tags.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/modules/typing.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/py_module.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/transpiler/vdom.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/types/__init__.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/types/event_handler.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/user_session.py +0 -0
- {pulse_framework-0.1.73 → pulse_framework-0.1.74}/src/pulse/version.py +0 -0
|
@@ -106,6 +106,11 @@ from pulse.transpiler.nodes import While as While
|
|
|
106
106
|
# Emit
|
|
107
107
|
from pulse.transpiler.nodes import emit as emit
|
|
108
108
|
|
|
109
|
+
# Parse helpers
|
|
110
|
+
from pulse.transpiler.parse import ParsedSource as ParsedSource
|
|
111
|
+
from pulse.transpiler.parse import get_ast as get_ast
|
|
112
|
+
from pulse.transpiler.parse import get_source as get_source
|
|
113
|
+
|
|
109
114
|
# Transpiler
|
|
110
115
|
from pulse.transpiler.transpiler import Transpiler as Transpiler
|
|
111
116
|
from pulse.transpiler.transpiler import transpile as transpile
|
|
@@ -7,8 +7,8 @@ and JsFunction which wraps transpiled functions with their dependencies.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import ast
|
|
10
|
+
import dis
|
|
10
11
|
import inspect
|
|
11
|
-
import textwrap
|
|
12
12
|
import types as pytypes
|
|
13
13
|
from collections.abc import Callable
|
|
14
14
|
from dataclasses import dataclass, field
|
|
@@ -25,7 +25,6 @@ from typing import (
|
|
|
25
25
|
override,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
from pulse.helpers import getsourcecode
|
|
29
28
|
from pulse.transpiler.errors import TranspileError
|
|
30
29
|
from pulse.transpiler.id import next_id, reset_id_counter
|
|
31
30
|
from pulse.transpiler.imports import Import
|
|
@@ -38,6 +37,7 @@ from pulse.transpiler.nodes import (
|
|
|
38
37
|
Return,
|
|
39
38
|
to_js_identifier,
|
|
40
39
|
)
|
|
40
|
+
from pulse.transpiler.parse import clear_parse_cache, get_ast, get_source
|
|
41
41
|
from pulse.transpiler.transpiler import Transpiler
|
|
42
42
|
from pulse.transpiler.vdom import VDOMExpr
|
|
43
43
|
|
|
@@ -63,6 +63,7 @@ def clear_function_cache() -> None:
|
|
|
63
63
|
|
|
64
64
|
FUNCTION_CACHE.clear()
|
|
65
65
|
CONSTANT_REGISTRY.clear()
|
|
66
|
+
clear_parse_cache()
|
|
66
67
|
clear_import_registry()
|
|
67
68
|
clear_asset_registry()
|
|
68
69
|
reset_id_counter()
|
|
@@ -137,33 +138,17 @@ def _transpile_function_body(
|
|
|
137
138
|
deps: dict[str, Expr],
|
|
138
139
|
*,
|
|
139
140
|
jsx: bool = False,
|
|
140
|
-
) ->
|
|
141
|
+
) -> Function | Arrow:
|
|
141
142
|
"""Shared transpilation logic for JsFunction and JsxFunction.
|
|
142
143
|
|
|
143
|
-
Returns the transpiled Function/Arrow node
|
|
144
|
+
Returns the transpiled Function/Arrow node.
|
|
144
145
|
"""
|
|
145
146
|
# Get and parse source
|
|
146
|
-
|
|
147
|
-
src =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
source_start_line = None
|
|
152
|
-
module = ast.parse(src)
|
|
153
|
-
|
|
154
|
-
# Find the function definition
|
|
155
|
-
fndefs = [
|
|
156
|
-
n for n in module.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
157
|
-
]
|
|
158
|
-
if not fndefs:
|
|
159
|
-
raise TranspileError("No function definition found in source")
|
|
160
|
-
fndef = fndefs[-1]
|
|
161
|
-
|
|
162
|
-
# Get filename for error messages and source file resolution
|
|
163
|
-
try:
|
|
164
|
-
filename = inspect.getfile(fn)
|
|
165
|
-
except (TypeError, OSError):
|
|
166
|
-
filename = None
|
|
147
|
+
parsed = get_source(fn)
|
|
148
|
+
src = parsed.source
|
|
149
|
+
fndef = get_ast(fn)
|
|
150
|
+
filename = parsed.filename
|
|
151
|
+
source_start_line = parsed.source_start_line
|
|
167
152
|
|
|
168
153
|
# Transpile with source context for errors
|
|
169
154
|
try:
|
|
@@ -181,7 +166,7 @@ def _transpile_function_body(
|
|
|
181
166
|
) from None
|
|
182
167
|
raise
|
|
183
168
|
|
|
184
|
-
return result
|
|
169
|
+
return result
|
|
185
170
|
|
|
186
171
|
|
|
187
172
|
@dataclass(slots=True, init=False)
|
|
@@ -238,7 +223,7 @@ class JsFunction(Expr, Generic[*Args, R]):
|
|
|
238
223
|
if self._transpiled is not None:
|
|
239
224
|
return self._transpiled
|
|
240
225
|
|
|
241
|
-
result
|
|
226
|
+
result = _transpile_function_body(self.fn, self.deps)
|
|
242
227
|
|
|
243
228
|
# Convert Arrow to Function if needed, and set the name
|
|
244
229
|
if isinstance(result, Function):
|
|
@@ -326,7 +311,7 @@ class JsxFunction(Expr, Generic[P, R]):
|
|
|
326
311
|
if self._transpiled is not None:
|
|
327
312
|
return self._transpiled
|
|
328
313
|
|
|
329
|
-
result
|
|
314
|
+
result = _transpile_function_body(self.fn, self.deps, jsx=True)
|
|
330
315
|
|
|
331
316
|
# JSX transpilation always returns Function (never Arrow)
|
|
332
317
|
assert isinstance(result, Function), (
|
|
@@ -376,7 +361,6 @@ def analyze_code_object(
|
|
|
376
361
|
- effective_globals: dict mapping names to their values (includes closure vars)
|
|
377
362
|
- all_names: set of all names referenced in the code (including nested functions)
|
|
378
363
|
"""
|
|
379
|
-
import dis
|
|
380
364
|
|
|
381
365
|
code = fn.__code__
|
|
382
366
|
|
|
@@ -443,14 +427,54 @@ def analyze_deps(fn: Callable[..., Any]) -> dict[str, Expr]:
|
|
|
443
427
|
"""
|
|
444
428
|
# Analyze code object and resolve globals + closure vars
|
|
445
429
|
effective_globals, all_names = analyze_code_object(fn)
|
|
430
|
+
code_names = set(all_names)
|
|
431
|
+
default_names: set[str] = set()
|
|
432
|
+
default_name_values: dict[str, Any] = {}
|
|
433
|
+
|
|
434
|
+
# Include names referenced only in default expressions (not in bytecode)
|
|
435
|
+
try:
|
|
436
|
+
args = get_ast(fn).args
|
|
437
|
+
pos_defaults = list(args.defaults)
|
|
438
|
+
py_defaults = fn.__defaults__ or ()
|
|
439
|
+
num_args = len(args.args)
|
|
440
|
+
num_defaults = len(pos_defaults)
|
|
441
|
+
for i, _arg in enumerate(args.args):
|
|
442
|
+
default_idx = i - (num_args - num_defaults)
|
|
443
|
+
if default_idx < 0 or default_idx >= len(pos_defaults):
|
|
444
|
+
continue
|
|
445
|
+
default_node = pos_defaults[default_idx]
|
|
446
|
+
if isinstance(default_node, ast.Name) and default_idx < len(py_defaults):
|
|
447
|
+
default_name_values[default_node.id] = py_defaults[default_idx]
|
|
448
|
+
for node in ast.walk(default_node):
|
|
449
|
+
if isinstance(node, ast.Name):
|
|
450
|
+
default_names.add(node.id)
|
|
451
|
+
|
|
452
|
+
py_kwdefaults = fn.__kwdefaults__ or {}
|
|
453
|
+
for i, kwarg in enumerate(args.kwonlyargs):
|
|
454
|
+
default_node = args.kw_defaults[i]
|
|
455
|
+
if default_node is None:
|
|
456
|
+
continue
|
|
457
|
+
if isinstance(default_node, ast.Name) and kwarg.arg in py_kwdefaults:
|
|
458
|
+
default_name_values[default_node.id] = py_kwdefaults[kwarg.arg]
|
|
459
|
+
for node in ast.walk(default_node):
|
|
460
|
+
if isinstance(node, ast.Name):
|
|
461
|
+
default_names.add(node.id)
|
|
462
|
+
except (OSError, TypeError, SyntaxError, TranspileError):
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
all_names.update(default_names)
|
|
466
|
+
default_only_names = default_names - code_names
|
|
446
467
|
|
|
447
468
|
# Build dependencies dictionary - all values are Expr
|
|
448
469
|
deps: dict[str, Expr] = {}
|
|
449
470
|
|
|
471
|
+
missing = object()
|
|
450
472
|
for name in all_names:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
473
|
+
if name in default_only_names and name in default_name_values:
|
|
474
|
+
value = default_name_values[name]
|
|
475
|
+
else:
|
|
476
|
+
value = effective_globals.get(name, missing)
|
|
477
|
+
if value is missing:
|
|
454
478
|
# Not in globals - could be a builtin or unresolved
|
|
455
479
|
# For now, skip - builtins will be handled by the transpiler
|
|
456
480
|
# TODO: Add builtin support
|
|
@@ -1517,7 +1517,7 @@ class If(Stmt):
|
|
|
1517
1517
|
|
|
1518
1518
|
@dataclass(slots=True)
|
|
1519
1519
|
class ForOf(Stmt):
|
|
1520
|
-
"""JS for-of loop: for (
|
|
1520
|
+
"""JS for-of loop: for (x of iter) { ... }
|
|
1521
1521
|
|
|
1522
1522
|
target can be a single name or array pattern for destructuring: [a, b]
|
|
1523
1523
|
"""
|
|
@@ -1528,7 +1528,7 @@ class ForOf(Stmt):
|
|
|
1528
1528
|
|
|
1529
1529
|
@override
|
|
1530
1530
|
def emit(self, out: list[str]) -> None:
|
|
1531
|
-
out.append("for (
|
|
1531
|
+
out.append("for (")
|
|
1532
1532
|
out.append(self.target)
|
|
1533
1533
|
out.append(" of ")
|
|
1534
1534
|
self.iter.emit(out)
|
|
@@ -1619,6 +1619,21 @@ class Assign(Stmt):
|
|
|
1619
1619
|
out.append(";")
|
|
1620
1620
|
|
|
1621
1621
|
|
|
1622
|
+
@dataclass(slots=True)
|
|
1623
|
+
class LetDecl(Stmt):
|
|
1624
|
+
"""JS let declaration: let a, b;"""
|
|
1625
|
+
|
|
1626
|
+
names: Sequence[str]
|
|
1627
|
+
|
|
1628
|
+
@override
|
|
1629
|
+
def emit(self, out: list[str]) -> None:
|
|
1630
|
+
if not self.names:
|
|
1631
|
+
return
|
|
1632
|
+
out.append("let ")
|
|
1633
|
+
out.append(", ".join(self.names))
|
|
1634
|
+
out.append(";")
|
|
1635
|
+
|
|
1636
|
+
|
|
1622
1637
|
@dataclass(slots=True)
|
|
1623
1638
|
class ExprStmt(Stmt):
|
|
1624
1639
|
"""JS expression statement: expr;"""
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Cached parsing helpers for transpiler source inspection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import inspect
|
|
7
|
+
import textwrap
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pulse.helpers import getsourcecode
|
|
13
|
+
from pulse.transpiler.errors import TranspileError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class ParsedSource:
|
|
18
|
+
source: str
|
|
19
|
+
filename: str | None
|
|
20
|
+
source_start_line: int | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_SOURCE_CACHE: dict[Callable[..., Any], ParsedSource] = {}
|
|
24
|
+
_AST_CACHE: dict[Callable[..., Any], ast.FunctionDef | ast.AsyncFunctionDef] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clear_parse_cache() -> None:
|
|
28
|
+
_SOURCE_CACHE.clear()
|
|
29
|
+
_AST_CACHE.clear()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_source(fn: Callable[..., Any]) -> ParsedSource:
|
|
33
|
+
cached = _SOURCE_CACHE.get(fn)
|
|
34
|
+
if cached is not None:
|
|
35
|
+
return cached
|
|
36
|
+
|
|
37
|
+
src = getsourcecode(fn)
|
|
38
|
+
src = textwrap.dedent(src)
|
|
39
|
+
try:
|
|
40
|
+
source_start_line = inspect.getsourcelines(fn)[1]
|
|
41
|
+
except (OSError, TypeError):
|
|
42
|
+
source_start_line = None
|
|
43
|
+
try:
|
|
44
|
+
filename = inspect.getfile(fn)
|
|
45
|
+
except (TypeError, OSError):
|
|
46
|
+
filename = None
|
|
47
|
+
|
|
48
|
+
parsed = ParsedSource(
|
|
49
|
+
source=src,
|
|
50
|
+
filename=filename,
|
|
51
|
+
source_start_line=source_start_line,
|
|
52
|
+
)
|
|
53
|
+
_SOURCE_CACHE[fn] = parsed
|
|
54
|
+
return parsed
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_ast(fn: Callable[..., Any]) -> ast.FunctionDef | ast.AsyncFunctionDef:
|
|
58
|
+
cached = _AST_CACHE.get(fn)
|
|
59
|
+
if cached is not None:
|
|
60
|
+
return cached
|
|
61
|
+
|
|
62
|
+
module = ast.parse(get_source(fn).source)
|
|
63
|
+
fndefs = [
|
|
64
|
+
n for n in module.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
65
|
+
]
|
|
66
|
+
if not fndefs:
|
|
67
|
+
raise TranspileError("No function definition found in source")
|
|
68
|
+
fndef = fndefs[-1]
|
|
69
|
+
_AST_CACHE[fn] = fndef
|
|
70
|
+
return fndef
|