quantum-framework 0.18.0__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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +548 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +565 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +79 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +164 -0
- quantum/core/xml_lines.py +81 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +25 -0
- quantum/runtime/action_handler.py +532 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Expression Transformer
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
Transforms Quantum expressions to target language syntax.
|
|
6
|
+
Handles differences between Python and JavaScript expression syntax.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import re
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExpressionTransformer:
|
|
15
|
+
"""
|
|
16
|
+
Transforms Quantum expressions to target language.
|
|
17
|
+
|
|
18
|
+
Quantum expressions are Python-like but need transformation for:
|
|
19
|
+
- JavaScript: == becomes ===, and/or become &&/||
|
|
20
|
+
- Both: property access, method calls, list comprehensions
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Built-in function mappings
|
|
24
|
+
PYTHON_BUILTINS = {
|
|
25
|
+
'len', 'str', 'int', 'float', 'bool', 'list', 'dict', 'set',
|
|
26
|
+
'sum', 'min', 'max', 'abs', 'round', 'range', 'enumerate',
|
|
27
|
+
'sorted', 'reversed', 'zip', 'map', 'filter', 'any', 'all',
|
|
28
|
+
'upper', 'lower', 'strip', 'split', 'join', 'replace',
|
|
29
|
+
'startswith', 'endswith', 'format', 'isinstance', 'type',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Python to JavaScript function mappings
|
|
33
|
+
PY_TO_JS_FUNCTIONS = {
|
|
34
|
+
'len': lambda arg: f'{arg}.length',
|
|
35
|
+
'str': lambda arg: f'String({arg})',
|
|
36
|
+
'int': lambda arg: f'parseInt({arg})',
|
|
37
|
+
'float': lambda arg: f'parseFloat({arg})',
|
|
38
|
+
'bool': lambda arg: f'Boolean({arg})',
|
|
39
|
+
'abs': lambda arg: f'Math.abs({arg})',
|
|
40
|
+
'round': lambda arg: f'Math.round({arg})',
|
|
41
|
+
'min': lambda *args: f'Math.min({", ".join(args)})',
|
|
42
|
+
'max': lambda *args: f'Math.max({", ".join(args)})',
|
|
43
|
+
'sum': lambda arg: f'{arg}.reduce((a, b) => a + b, 0)',
|
|
44
|
+
'sorted': lambda arg: f'[...{arg}].sort()',
|
|
45
|
+
'reversed': lambda arg: f'[...{arg}].reverse()',
|
|
46
|
+
'range': '_range', # Special handling needed
|
|
47
|
+
'enumerate': '_enumerate', # Special handling needed
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Python string methods to JavaScript
|
|
51
|
+
PY_TO_JS_METHODS = {
|
|
52
|
+
'upper': 'toUpperCase',
|
|
53
|
+
'lower': 'toLowerCase',
|
|
54
|
+
'strip': 'trim',
|
|
55
|
+
'lstrip': 'trimStart',
|
|
56
|
+
'rstrip': 'trimEnd',
|
|
57
|
+
'split': 'split',
|
|
58
|
+
'join': '_join', # Special: "sep".join(list) -> list.join("sep")
|
|
59
|
+
'replace': 'replace',
|
|
60
|
+
'startswith': 'startsWith',
|
|
61
|
+
'endswith': 'endsWith',
|
|
62
|
+
'find': 'indexOf',
|
|
63
|
+
'count': '_count', # Special handling
|
|
64
|
+
'isdigit': '_isDigit', # Special handling
|
|
65
|
+
'isalpha': '_isAlpha', # Special handling
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def __init__(self, target: str = 'python'):
|
|
69
|
+
"""
|
|
70
|
+
Initialize the transformer.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
target: Target language ('python' or 'javascript')
|
|
74
|
+
"""
|
|
75
|
+
self.target = target.lower()
|
|
76
|
+
|
|
77
|
+
def transform(self, expr: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Transform a Quantum expression to target language.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
expr: The expression to transform (may have {})
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Transformed expression string
|
|
86
|
+
"""
|
|
87
|
+
if not expr:
|
|
88
|
+
return ''
|
|
89
|
+
|
|
90
|
+
expr = str(expr).strip()
|
|
91
|
+
|
|
92
|
+
# Remove { } wrapper if present
|
|
93
|
+
if expr.startswith('{') and expr.endswith('}'):
|
|
94
|
+
expr = expr[1:-1].strip()
|
|
95
|
+
|
|
96
|
+
if not expr:
|
|
97
|
+
return ''
|
|
98
|
+
|
|
99
|
+
# Quick path: if no transformation needed
|
|
100
|
+
if self.target == 'python' and not self._needs_transformation(expr):
|
|
101
|
+
return expr
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# Parse as Python AST
|
|
105
|
+
tree = ast.parse(expr, mode='eval')
|
|
106
|
+
return self._transform_node(tree.body)
|
|
107
|
+
except SyntaxError:
|
|
108
|
+
# Fallback: return as-is with basic transformations
|
|
109
|
+
return self._basic_transform(expr)
|
|
110
|
+
|
|
111
|
+
def _needs_transformation(self, expr: str) -> bool:
|
|
112
|
+
"""Check if expression needs any transformation."""
|
|
113
|
+
# For Python target, most expressions work as-is
|
|
114
|
+
if self.target == 'python':
|
|
115
|
+
return False
|
|
116
|
+
# For JavaScript, check for Python-specific syntax
|
|
117
|
+
return any(x in expr for x in ['and ', 'or ', 'not ', ' in ', 'True', 'False', 'None'])
|
|
118
|
+
|
|
119
|
+
def _basic_transform(self, expr: str) -> str:
|
|
120
|
+
"""Apply basic transformations without full AST parsing."""
|
|
121
|
+
if self.target == 'javascript':
|
|
122
|
+
# Replace Python operators with JavaScript equivalents
|
|
123
|
+
expr = re.sub(r'\band\b', '&&', expr)
|
|
124
|
+
expr = re.sub(r'\bor\b', '||', expr)
|
|
125
|
+
expr = re.sub(r'\bnot\b', '!', expr)
|
|
126
|
+
expr = re.sub(r'\bTrue\b', 'true', expr)
|
|
127
|
+
expr = re.sub(r'\bFalse\b', 'false', expr)
|
|
128
|
+
expr = re.sub(r'\bNone\b', 'null', expr)
|
|
129
|
+
# == to === (careful with !=)
|
|
130
|
+
expr = re.sub(r'([^!<>=])={2}([^=])', r'\1===\2', expr)
|
|
131
|
+
expr = re.sub(r'!=', '!==', expr)
|
|
132
|
+
return expr
|
|
133
|
+
|
|
134
|
+
def _transform_node(self, node: ast.expr) -> str:
|
|
135
|
+
"""Transform an AST node to target language."""
|
|
136
|
+
method_name = f'_transform_{type(node).__name__}'
|
|
137
|
+
transformer = getattr(self, method_name, self._transform_default)
|
|
138
|
+
return transformer(node)
|
|
139
|
+
|
|
140
|
+
def _transform_default(self, node: ast.expr) -> str:
|
|
141
|
+
"""Default transformation: use Python's unparse."""
|
|
142
|
+
result = ast.unparse(node)
|
|
143
|
+
if self.target == 'javascript':
|
|
144
|
+
return self._basic_transform(result)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
# =========================================================================
|
|
148
|
+
# AST Node Transformers
|
|
149
|
+
# =========================================================================
|
|
150
|
+
|
|
151
|
+
def _transform_Constant(self, node: ast.Constant) -> str:
|
|
152
|
+
"""Transform constants."""
|
|
153
|
+
value = node.value
|
|
154
|
+
if value is True:
|
|
155
|
+
return 'true' if self.target == 'javascript' else 'True'
|
|
156
|
+
elif value is False:
|
|
157
|
+
return 'false' if self.target == 'javascript' else 'False'
|
|
158
|
+
elif value is None:
|
|
159
|
+
return 'null' if self.target == 'javascript' else 'None'
|
|
160
|
+
elif isinstance(value, str):
|
|
161
|
+
# Use repr for proper escaping
|
|
162
|
+
return repr(value)
|
|
163
|
+
else:
|
|
164
|
+
return str(value)
|
|
165
|
+
|
|
166
|
+
def _transform_Name(self, node: ast.Name) -> str:
|
|
167
|
+
"""Transform variable names."""
|
|
168
|
+
name = node.id
|
|
169
|
+
if self.target == 'javascript':
|
|
170
|
+
# Map Python built-in names to JavaScript
|
|
171
|
+
mappings = {'True': 'true', 'False': 'false', 'None': 'null'}
|
|
172
|
+
return mappings.get(name, name)
|
|
173
|
+
return name
|
|
174
|
+
|
|
175
|
+
def _transform_BinOp(self, node: ast.BinOp) -> str:
|
|
176
|
+
"""Transform binary operations."""
|
|
177
|
+
left = self._transform_node(node.left)
|
|
178
|
+
right = self._transform_node(node.right)
|
|
179
|
+
op = self._get_binop(node.op)
|
|
180
|
+
return f'({left} {op} {right})'
|
|
181
|
+
|
|
182
|
+
def _get_binop(self, op: ast.operator) -> str:
|
|
183
|
+
"""Get binary operator string."""
|
|
184
|
+
ops = {
|
|
185
|
+
ast.Add: '+',
|
|
186
|
+
ast.Sub: '-',
|
|
187
|
+
ast.Mult: '*',
|
|
188
|
+
ast.Div: '/',
|
|
189
|
+
ast.FloorDiv: '//' if self.target == 'python' else 'Math.floor(/',
|
|
190
|
+
ast.Mod: '%',
|
|
191
|
+
ast.Pow: '**',
|
|
192
|
+
ast.BitOr: '|',
|
|
193
|
+
ast.BitXor: '^',
|
|
194
|
+
ast.BitAnd: '&',
|
|
195
|
+
ast.LShift: '<<',
|
|
196
|
+
ast.RShift: '>>',
|
|
197
|
+
}
|
|
198
|
+
return ops.get(type(op), '+')
|
|
199
|
+
|
|
200
|
+
def _transform_Compare(self, node: ast.Compare) -> str:
|
|
201
|
+
"""Transform comparison operations."""
|
|
202
|
+
left = self._transform_node(node.left)
|
|
203
|
+
parts = [left]
|
|
204
|
+
|
|
205
|
+
for op, comparator in zip(node.ops, node.comparators):
|
|
206
|
+
comp_str = self._transform_node(comparator)
|
|
207
|
+
|
|
208
|
+
if isinstance(op, ast.Eq):
|
|
209
|
+
parts.append('===' if self.target == 'javascript' else '==')
|
|
210
|
+
elif isinstance(op, ast.NotEq):
|
|
211
|
+
parts.append('!==' if self.target == 'javascript' else '!=')
|
|
212
|
+
elif isinstance(op, ast.Lt):
|
|
213
|
+
parts.append('<')
|
|
214
|
+
elif isinstance(op, ast.LtE):
|
|
215
|
+
parts.append('<=')
|
|
216
|
+
elif isinstance(op, ast.Gt):
|
|
217
|
+
parts.append('>')
|
|
218
|
+
elif isinstance(op, ast.GtE):
|
|
219
|
+
parts.append('>=')
|
|
220
|
+
elif isinstance(op, ast.In):
|
|
221
|
+
if self.target == 'javascript':
|
|
222
|
+
# x in y -> y.includes(x)
|
|
223
|
+
return f'{comp_str}.includes({left})'
|
|
224
|
+
parts.append('in')
|
|
225
|
+
elif isinstance(op, ast.NotIn):
|
|
226
|
+
if self.target == 'javascript':
|
|
227
|
+
return f'!{comp_str}.includes({left})'
|
|
228
|
+
parts.append('not in')
|
|
229
|
+
elif isinstance(op, ast.Is):
|
|
230
|
+
parts.append('===' if self.target == 'javascript' else 'is')
|
|
231
|
+
elif isinstance(op, ast.IsNot):
|
|
232
|
+
parts.append('!==' if self.target == 'javascript' else 'is not')
|
|
233
|
+
else:
|
|
234
|
+
parts.append('==')
|
|
235
|
+
|
|
236
|
+
parts.append(comp_str)
|
|
237
|
+
|
|
238
|
+
return ' '.join(parts)
|
|
239
|
+
|
|
240
|
+
def _transform_BoolOp(self, node: ast.BoolOp) -> str:
|
|
241
|
+
"""Transform boolean operations (and/or)."""
|
|
242
|
+
if isinstance(node.op, ast.And):
|
|
243
|
+
op = '&&' if self.target == 'javascript' else 'and'
|
|
244
|
+
else:
|
|
245
|
+
op = '||' if self.target == 'javascript' else 'or'
|
|
246
|
+
|
|
247
|
+
values = [self._transform_node(v) for v in node.values]
|
|
248
|
+
return f' {op} '.join(f'({v})' for v in values)
|
|
249
|
+
|
|
250
|
+
def _transform_UnaryOp(self, node: ast.UnaryOp) -> str:
|
|
251
|
+
"""Transform unary operations."""
|
|
252
|
+
operand = self._transform_node(node.operand)
|
|
253
|
+
|
|
254
|
+
if isinstance(node.op, ast.Not):
|
|
255
|
+
op = '!' if self.target == 'javascript' else 'not '
|
|
256
|
+
elif isinstance(node.op, ast.USub):
|
|
257
|
+
op = '-'
|
|
258
|
+
elif isinstance(node.op, ast.UAdd):
|
|
259
|
+
op = '+'
|
|
260
|
+
elif isinstance(node.op, ast.Invert):
|
|
261
|
+
op = '~'
|
|
262
|
+
else:
|
|
263
|
+
op = ''
|
|
264
|
+
|
|
265
|
+
return f'{op}{operand}'
|
|
266
|
+
|
|
267
|
+
def _transform_IfExp(self, node: ast.IfExp) -> str:
|
|
268
|
+
"""Transform ternary/conditional expressions."""
|
|
269
|
+
test = self._transform_node(node.test)
|
|
270
|
+
body = self._transform_node(node.body)
|
|
271
|
+
orelse = self._transform_node(node.orelse)
|
|
272
|
+
|
|
273
|
+
if self.target == 'javascript':
|
|
274
|
+
return f'({test} ? {body} : {orelse})'
|
|
275
|
+
else:
|
|
276
|
+
return f'({body} if {test} else {orelse})'
|
|
277
|
+
|
|
278
|
+
def _transform_Attribute(self, node: ast.Attribute) -> str:
|
|
279
|
+
"""Transform attribute access."""
|
|
280
|
+
value = self._transform_node(node.value)
|
|
281
|
+
attr = node.attr
|
|
282
|
+
|
|
283
|
+
if self.target == 'javascript':
|
|
284
|
+
# Map Python methods to JavaScript
|
|
285
|
+
if attr in self.PY_TO_JS_METHODS:
|
|
286
|
+
js_method = self.PY_TO_JS_METHODS[attr]
|
|
287
|
+
if not js_method.startswith('_'):
|
|
288
|
+
return f'{value}.{js_method}'
|
|
289
|
+
# Special handling needed - return as-is for now
|
|
290
|
+
return f'{value}.{attr}'
|
|
291
|
+
|
|
292
|
+
return f'{value}.{attr}'
|
|
293
|
+
|
|
294
|
+
def _transform_Subscript(self, node: ast.Subscript) -> str:
|
|
295
|
+
"""Transform subscript access (indexing)."""
|
|
296
|
+
value = self._transform_node(node.value)
|
|
297
|
+
slice_node = node.slice
|
|
298
|
+
|
|
299
|
+
if isinstance(slice_node, ast.Constant):
|
|
300
|
+
index = self._transform_node(slice_node)
|
|
301
|
+
return f'{value}[{index}]'
|
|
302
|
+
elif isinstance(slice_node, ast.Slice):
|
|
303
|
+
return self._transform_slice(value, slice_node)
|
|
304
|
+
else:
|
|
305
|
+
index = self._transform_node(slice_node)
|
|
306
|
+
return f'{value}[{index}]'
|
|
307
|
+
|
|
308
|
+
def _transform_slice(self, value: str, node: ast.Slice) -> str:
|
|
309
|
+
"""Transform slice operations."""
|
|
310
|
+
lower = self._transform_node(node.lower) if node.lower else ''
|
|
311
|
+
upper = self._transform_node(node.upper) if node.upper else ''
|
|
312
|
+
step = self._transform_node(node.step) if node.step else ''
|
|
313
|
+
|
|
314
|
+
if self.target == 'javascript':
|
|
315
|
+
if step:
|
|
316
|
+
# JavaScript doesn't have native slice with step
|
|
317
|
+
return f'{value}.slice({lower or 0}, {upper or "undefined"})'
|
|
318
|
+
return f'{value}.slice({lower or 0}, {upper or "undefined"})'
|
|
319
|
+
else:
|
|
320
|
+
return f'{value}[{lower}:{upper}{":" + step if step else ""}]'
|
|
321
|
+
|
|
322
|
+
def _transform_Call(self, node: ast.Call) -> str:
|
|
323
|
+
"""Transform function calls."""
|
|
324
|
+
args = [self._transform_node(arg) for arg in node.args]
|
|
325
|
+
kwargs = [f'{kw.arg}={self._transform_node(kw.value)}' for kw in node.keywords]
|
|
326
|
+
all_args = ', '.join(args + kwargs)
|
|
327
|
+
|
|
328
|
+
# Handle method calls
|
|
329
|
+
if isinstance(node.func, ast.Attribute):
|
|
330
|
+
obj = self._transform_node(node.func.value)
|
|
331
|
+
method = node.func.attr
|
|
332
|
+
|
|
333
|
+
if self.target == 'javascript':
|
|
334
|
+
# Special method mappings
|
|
335
|
+
if method == 'join':
|
|
336
|
+
# Python: "sep".join(list) -> JS: list.join("sep")
|
|
337
|
+
return f'{args[0]}.join({obj})'
|
|
338
|
+
elif method in self.PY_TO_JS_METHODS:
|
|
339
|
+
js_method = self.PY_TO_JS_METHODS[method]
|
|
340
|
+
if not js_method.startswith('_'):
|
|
341
|
+
return f'{obj}.{js_method}({all_args})'
|
|
342
|
+
|
|
343
|
+
return f'{obj}.{method}({all_args})'
|
|
344
|
+
|
|
345
|
+
# Handle regular function calls
|
|
346
|
+
if isinstance(node.func, ast.Name):
|
|
347
|
+
func_name = node.func.id
|
|
348
|
+
|
|
349
|
+
if self.target == 'javascript' and func_name in self.PY_TO_JS_FUNCTIONS:
|
|
350
|
+
mapping = self.PY_TO_JS_FUNCTIONS[func_name]
|
|
351
|
+
if callable(mapping):
|
|
352
|
+
return mapping(*args)
|
|
353
|
+
# Special handling marker
|
|
354
|
+
if mapping == '_range':
|
|
355
|
+
return self._js_range(args)
|
|
356
|
+
elif mapping == '_enumerate':
|
|
357
|
+
return f'{args[0]}.entries()'
|
|
358
|
+
|
|
359
|
+
return f'{func_name}({all_args})'
|
|
360
|
+
|
|
361
|
+
# Fallback
|
|
362
|
+
func = self._transform_node(node.func)
|
|
363
|
+
return f'{func}({all_args})'
|
|
364
|
+
|
|
365
|
+
def _js_range(self, args: list) -> str:
|
|
366
|
+
"""Generate JavaScript range equivalent."""
|
|
367
|
+
if len(args) == 1:
|
|
368
|
+
return f'Array.from({{length: {args[0]}}}, (_, i) => i)'
|
|
369
|
+
elif len(args) == 2:
|
|
370
|
+
return f'Array.from({{length: {args[1]} - {args[0]}}}, (_, i) => i + {args[0]})'
|
|
371
|
+
else:
|
|
372
|
+
return f'Array.from({{length: Math.ceil(({args[1]} - {args[0]}) / {args[2]})}}, (_, i) => {args[0]} + i * {args[2]})'
|
|
373
|
+
|
|
374
|
+
def _transform_List(self, node: ast.List) -> str:
|
|
375
|
+
"""Transform list literals."""
|
|
376
|
+
elements = [self._transform_node(el) for el in node.elts]
|
|
377
|
+
return f'[{", ".join(elements)}]'
|
|
378
|
+
|
|
379
|
+
def _transform_Dict(self, node: ast.Dict) -> str:
|
|
380
|
+
"""Transform dictionary literals."""
|
|
381
|
+
pairs = []
|
|
382
|
+
for key, value in zip(node.keys, node.values):
|
|
383
|
+
k = self._transform_node(key) if key else '...'
|
|
384
|
+
v = self._transform_node(value)
|
|
385
|
+
if self.target == 'javascript' and isinstance(key, ast.Constant) and isinstance(key.value, str):
|
|
386
|
+
# JavaScript object literal with string keys
|
|
387
|
+
pairs.append(f'{key.value}: {v}')
|
|
388
|
+
else:
|
|
389
|
+
pairs.append(f'{k}: {v}')
|
|
390
|
+
|
|
391
|
+
if self.target == 'javascript':
|
|
392
|
+
return '{' + ', '.join(pairs) + '}'
|
|
393
|
+
else:
|
|
394
|
+
return '{' + ', '.join(pairs) + '}'
|
|
395
|
+
|
|
396
|
+
def _transform_ListComp(self, node: ast.ListComp) -> str:
|
|
397
|
+
"""Transform list comprehensions."""
|
|
398
|
+
if self.target == 'javascript':
|
|
399
|
+
return self._listcomp_to_js(node)
|
|
400
|
+
else:
|
|
401
|
+
return ast.unparse(node)
|
|
402
|
+
|
|
403
|
+
def _listcomp_to_js(self, node: ast.ListComp) -> str:
|
|
404
|
+
"""Convert list comprehension to JavaScript map/filter."""
|
|
405
|
+
gen = node.generators[0]
|
|
406
|
+
var = self._transform_node(gen.target)
|
|
407
|
+
iterable = self._transform_node(gen.iter)
|
|
408
|
+
expr = self._transform_node(node.elt)
|
|
409
|
+
|
|
410
|
+
if gen.ifs:
|
|
411
|
+
# Has filter condition
|
|
412
|
+
conditions = [self._transform_node(if_) for if_ in gen.ifs]
|
|
413
|
+
filter_expr = ' && '.join(f'({c})' for c in conditions)
|
|
414
|
+
return f'{iterable}.filter({var} => {filter_expr}).map({var} => {expr})'
|
|
415
|
+
else:
|
|
416
|
+
return f'{iterable}.map({var} => {expr})'
|
|
417
|
+
|
|
418
|
+
def _transform_Lambda(self, node: ast.Lambda) -> str:
|
|
419
|
+
"""Transform lambda expressions."""
|
|
420
|
+
args = ', '.join(arg.arg for arg in node.args.args)
|
|
421
|
+
body = self._transform_node(node.body)
|
|
422
|
+
|
|
423
|
+
if self.target == 'javascript':
|
|
424
|
+
return f'({args}) => {body}'
|
|
425
|
+
else:
|
|
426
|
+
return f'lambda {args}: {body}'
|
|
427
|
+
|
|
428
|
+
def _transform_JoinedStr(self, node: ast.JoinedStr) -> str:
|
|
429
|
+
"""Transform f-strings."""
|
|
430
|
+
parts = []
|
|
431
|
+
for value in node.values:
|
|
432
|
+
if isinstance(value, ast.Constant):
|
|
433
|
+
parts.append(str(value.value))
|
|
434
|
+
elif isinstance(value, ast.FormattedValue):
|
|
435
|
+
expr = self._transform_node(value.value)
|
|
436
|
+
if self.target == 'javascript':
|
|
437
|
+
parts.append('${' + expr + '}')
|
|
438
|
+
else:
|
|
439
|
+
parts.append('{' + expr + '}')
|
|
440
|
+
|
|
441
|
+
if self.target == 'javascript':
|
|
442
|
+
return '`' + ''.join(parts) + '`'
|
|
443
|
+
else:
|
|
444
|
+
return "f'" + ''.join(parts) + "'"
|