quantum-framework 0.9.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/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -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 +527 -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 +547 -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 +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -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_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -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 +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -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/database_service.py +322 -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 +228 -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 +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -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 +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -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 +54 -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 +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -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/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -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 +82 -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 +28 -0
- quantum/runtime/action_handler.py +443 -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/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -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 +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -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 +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -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 +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -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/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -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/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python Code Generator
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
Generates Python code from Quantum AST nodes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, List, Set, Any
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
# Add paths
|
|
14
|
+
|
|
15
|
+
from quantum.compiler.base_generator import CodeGenerator, GeneratorContext
|
|
16
|
+
from quantum.compiler.expression_transformer import ExpressionTransformer
|
|
17
|
+
from quantum.core.ast_nodes import (
|
|
18
|
+
QuantumNode, ComponentNode, ApplicationNode, SetNode, LoopNode,
|
|
19
|
+
IfNode, FunctionNode, HTMLNode, TextNode, QueryNode, ActionNode,
|
|
20
|
+
ImportNode, SlotNode, ComponentCallNode, PythonNode, PyImportNode,
|
|
21
|
+
RedirectNode, FlashNode, DocTypeNode, CommentNode, QueryParamNode
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PythonGenerator(CodeGenerator):
|
|
26
|
+
"""
|
|
27
|
+
Generates Python code from Quantum AST.
|
|
28
|
+
|
|
29
|
+
Produces clean, readable Python that mirrors the Quantum structure
|
|
30
|
+
while being fully native Python.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.expr_transformer = ExpressionTransformer(target='python')
|
|
36
|
+
self.imports: Set[str] = set()
|
|
37
|
+
self.component_name: str = ''
|
|
38
|
+
self.in_render: bool = False
|
|
39
|
+
self.functions: List[str] = []
|
|
40
|
+
|
|
41
|
+
def file_extension(self) -> str:
|
|
42
|
+
return '.py'
|
|
43
|
+
|
|
44
|
+
def generate_header(self) -> str:
|
|
45
|
+
"""Generate Python file header."""
|
|
46
|
+
lines = [
|
|
47
|
+
'"""',
|
|
48
|
+
'Generated by Quantum Transpiler',
|
|
49
|
+
'Do not edit directly - changes will be overwritten.',
|
|
50
|
+
'"""',
|
|
51
|
+
'',
|
|
52
|
+
]
|
|
53
|
+
return '\n'.join(lines)
|
|
54
|
+
|
|
55
|
+
def generate_footer(self) -> str:
|
|
56
|
+
"""Generate file footer."""
|
|
57
|
+
return ''
|
|
58
|
+
|
|
59
|
+
def transpile_expression(self, expr: str) -> str:
|
|
60
|
+
"""Transpile Quantum expression to Python."""
|
|
61
|
+
return self.expr_transformer.transform(expr)
|
|
62
|
+
|
|
63
|
+
# =========================================================================
|
|
64
|
+
# Component & Application
|
|
65
|
+
# =========================================================================
|
|
66
|
+
|
|
67
|
+
def visit_ComponentNode(self, node: ComponentNode) -> str:
|
|
68
|
+
"""Generate Python class for component."""
|
|
69
|
+
self.component_name = node.name or 'Component'
|
|
70
|
+
self.imports.add('from quantum.compiler.python.runtime import Component, escape, bind')
|
|
71
|
+
|
|
72
|
+
# Get statements/children - ComponentNode uses 'statements'
|
|
73
|
+
statements = getattr(node, 'statements', None) or getattr(node, 'children', None) or []
|
|
74
|
+
|
|
75
|
+
# Emit imports first
|
|
76
|
+
for imp in sorted(self.imports):
|
|
77
|
+
self.emit(imp)
|
|
78
|
+
self.emit_blank()
|
|
79
|
+
|
|
80
|
+
# Class definition
|
|
81
|
+
self.emit(f'class {self.component_name}(Component):')
|
|
82
|
+
self.indent()
|
|
83
|
+
self.emit(f'"""Quantum component: {self.component_name}"""')
|
|
84
|
+
self.emit_blank()
|
|
85
|
+
|
|
86
|
+
# Render method
|
|
87
|
+
self.emit('def render(self) -> str:')
|
|
88
|
+
self.indent()
|
|
89
|
+
self.in_render = True
|
|
90
|
+
|
|
91
|
+
# Initialize HTML builder
|
|
92
|
+
self.emit('_html = []')
|
|
93
|
+
self.emit_blank()
|
|
94
|
+
|
|
95
|
+
# Initialize variables from props
|
|
96
|
+
self.emit('# Props')
|
|
97
|
+
self.emit('for _k, _v in self.props.items():')
|
|
98
|
+
self.indent()
|
|
99
|
+
self.emit('locals()[_k] = _v')
|
|
100
|
+
self.dedent()
|
|
101
|
+
self.emit_blank()
|
|
102
|
+
|
|
103
|
+
# Process statements
|
|
104
|
+
self.push_scope()
|
|
105
|
+
self.emit('# Component body')
|
|
106
|
+
for child in statements:
|
|
107
|
+
self.visit(child)
|
|
108
|
+
|
|
109
|
+
self.pop_scope()
|
|
110
|
+
self.emit_blank()
|
|
111
|
+
|
|
112
|
+
# Return joined HTML
|
|
113
|
+
self.emit("return '\\n'.join(_html)")
|
|
114
|
+
|
|
115
|
+
self.in_render = False
|
|
116
|
+
self.dedent()
|
|
117
|
+
self.dedent()
|
|
118
|
+
|
|
119
|
+
return self.component_name
|
|
120
|
+
|
|
121
|
+
def visit_ApplicationNode(self, node: ApplicationNode) -> str:
|
|
122
|
+
"""Generate Python code for application."""
|
|
123
|
+
app_name = node.name or 'Application'
|
|
124
|
+
app_type = getattr(node, 'type', 'web')
|
|
125
|
+
|
|
126
|
+
self.imports.add('from quantum.compiler.python.runtime import Component, escape')
|
|
127
|
+
|
|
128
|
+
# Emit imports
|
|
129
|
+
for imp in sorted(self.imports):
|
|
130
|
+
self.emit(imp)
|
|
131
|
+
self.emit_blank()
|
|
132
|
+
|
|
133
|
+
self.emit(f'# Application: {app_name} ({app_type})')
|
|
134
|
+
self.emit_blank()
|
|
135
|
+
|
|
136
|
+
# Process children (components, routes, etc.)
|
|
137
|
+
for child in node.children or []:
|
|
138
|
+
self.visit(child)
|
|
139
|
+
|
|
140
|
+
return app_name
|
|
141
|
+
|
|
142
|
+
# =========================================================================
|
|
143
|
+
# State Management
|
|
144
|
+
# =========================================================================
|
|
145
|
+
|
|
146
|
+
def visit_SetNode(self, node: SetNode) -> str:
|
|
147
|
+
"""Generate variable assignment."""
|
|
148
|
+
name = node.name
|
|
149
|
+
value = getattr(node, 'value', None)
|
|
150
|
+
scope = getattr(node, 'scope', 'local')
|
|
151
|
+
|
|
152
|
+
if value is None:
|
|
153
|
+
self.emit(f'{name} = None')
|
|
154
|
+
elif self.is_expression(str(value)):
|
|
155
|
+
expr = self.transpile_expression(str(value))
|
|
156
|
+
self.emit(f'{name} = {expr}')
|
|
157
|
+
elif self.is_literal(str(value)):
|
|
158
|
+
self.emit(f'{name} = {value}')
|
|
159
|
+
else:
|
|
160
|
+
# String value
|
|
161
|
+
self.emit(f'{name} = {repr(value)}')
|
|
162
|
+
|
|
163
|
+
self.declare_var(name)
|
|
164
|
+
return name
|
|
165
|
+
|
|
166
|
+
# =========================================================================
|
|
167
|
+
# Control Flow
|
|
168
|
+
# =========================================================================
|
|
169
|
+
|
|
170
|
+
def visit_LoopNode(self, node: LoopNode) -> str:
|
|
171
|
+
"""Generate for loop."""
|
|
172
|
+
# Get loop variable - LoopNode uses var_name
|
|
173
|
+
var = getattr(node, 'var_name', None) or getattr(node, 'var', None) or 'item'
|
|
174
|
+
index_var = getattr(node, 'index_name', None) or getattr(node, 'index', None)
|
|
175
|
+
|
|
176
|
+
# Get collection or range - LoopNode uses 'items' for collection
|
|
177
|
+
collection = getattr(node, 'items', None) or getattr(node, 'collection', None)
|
|
178
|
+
from_val = getattr(node, 'from_value', None) or getattr(node, 'from_val', None)
|
|
179
|
+
to_val = getattr(node, 'to_value', None) or getattr(node, 'to_val', None)
|
|
180
|
+
step = getattr(node, 'step_value', None) or getattr(node, 'step', None)
|
|
181
|
+
|
|
182
|
+
# Get body - LoopNode uses 'body' not 'children'
|
|
183
|
+
body = getattr(node, 'body', None) or getattr(node, 'children', None) or []
|
|
184
|
+
|
|
185
|
+
if collection:
|
|
186
|
+
# Collection-based loop
|
|
187
|
+
coll_expr = self.transpile_expression(str(collection))
|
|
188
|
+
|
|
189
|
+
if index_var:
|
|
190
|
+
self.emit(f'for {index_var}, {var} in enumerate({coll_expr}):')
|
|
191
|
+
else:
|
|
192
|
+
self.emit(f'for {var} in {coll_expr}:')
|
|
193
|
+
else:
|
|
194
|
+
# Range-based loop
|
|
195
|
+
start = from_val if from_val is not None else 0
|
|
196
|
+
# Quantum loops are inclusive, Python range is exclusive
|
|
197
|
+
end_expr = f'{to_val} + 1' if to_val is not None else '0'
|
|
198
|
+
|
|
199
|
+
if self.is_expression(str(start)):
|
|
200
|
+
start = self.transpile_expression(str(start))
|
|
201
|
+
if to_val and self.is_expression(str(to_val)):
|
|
202
|
+
end_expr = f'({self.transpile_expression(str(to_val))}) + 1'
|
|
203
|
+
|
|
204
|
+
if step:
|
|
205
|
+
self.emit(f'for {var} in range({start}, {end_expr}, {step}):')
|
|
206
|
+
else:
|
|
207
|
+
self.emit(f'for {var} in range({start}, {end_expr}):')
|
|
208
|
+
|
|
209
|
+
self.indent()
|
|
210
|
+
self.push_scope()
|
|
211
|
+
self.declare_var(var)
|
|
212
|
+
if index_var:
|
|
213
|
+
self.declare_var(index_var)
|
|
214
|
+
|
|
215
|
+
# Process body
|
|
216
|
+
for child in body:
|
|
217
|
+
self.visit(child)
|
|
218
|
+
|
|
219
|
+
self.pop_scope()
|
|
220
|
+
self.dedent()
|
|
221
|
+
|
|
222
|
+
return ''
|
|
223
|
+
|
|
224
|
+
def visit_IfNode(self, node: IfNode) -> str:
|
|
225
|
+
"""Generate if/elif/else."""
|
|
226
|
+
condition = getattr(node, 'condition', None) or getattr(node, 'test', None)
|
|
227
|
+
|
|
228
|
+
if condition:
|
|
229
|
+
cond_expr = self.transpile_expression(str(condition))
|
|
230
|
+
self.emit(f'if {cond_expr}:')
|
|
231
|
+
else:
|
|
232
|
+
self.emit('if True:')
|
|
233
|
+
|
|
234
|
+
self.indent()
|
|
235
|
+
self.push_scope()
|
|
236
|
+
|
|
237
|
+
# Process if-body (IfNode uses if_body, not children)
|
|
238
|
+
if_body = getattr(node, 'if_body', None) or getattr(node, 'children', None) or []
|
|
239
|
+
has_content = False
|
|
240
|
+
|
|
241
|
+
for child in if_body:
|
|
242
|
+
self.visit(child)
|
|
243
|
+
has_content = True
|
|
244
|
+
|
|
245
|
+
if not has_content:
|
|
246
|
+
self.emit('pass')
|
|
247
|
+
|
|
248
|
+
self.pop_scope()
|
|
249
|
+
self.dedent()
|
|
250
|
+
|
|
251
|
+
# Handle elseif blocks
|
|
252
|
+
elseif_blocks = getattr(node, 'elseif_blocks', []) or []
|
|
253
|
+
for elseif in elseif_blocks:
|
|
254
|
+
elseif_cond = elseif.get('condition', '')
|
|
255
|
+
if elseif_cond:
|
|
256
|
+
cond_expr = self.transpile_expression(str(elseif_cond))
|
|
257
|
+
self.emit(f'elif {cond_expr}:')
|
|
258
|
+
else:
|
|
259
|
+
self.emit('elif True:')
|
|
260
|
+
|
|
261
|
+
self.indent()
|
|
262
|
+
elseif_body = elseif.get('body', []) or []
|
|
263
|
+
if elseif_body:
|
|
264
|
+
for child in elseif_body:
|
|
265
|
+
self.visit(child)
|
|
266
|
+
else:
|
|
267
|
+
self.emit('pass')
|
|
268
|
+
self.dedent()
|
|
269
|
+
|
|
270
|
+
# Handle else body
|
|
271
|
+
else_body = getattr(node, 'else_body', []) or []
|
|
272
|
+
if else_body:
|
|
273
|
+
self.emit('else:')
|
|
274
|
+
self.indent()
|
|
275
|
+
for child in else_body:
|
|
276
|
+
self.visit(child)
|
|
277
|
+
self.dedent()
|
|
278
|
+
|
|
279
|
+
return ''
|
|
280
|
+
|
|
281
|
+
# =========================================================================
|
|
282
|
+
# Functions
|
|
283
|
+
# =========================================================================
|
|
284
|
+
|
|
285
|
+
def visit_FunctionNode(self, node: FunctionNode) -> str:
|
|
286
|
+
"""Generate function definition."""
|
|
287
|
+
name = node.name or 'unnamed_function'
|
|
288
|
+
params = getattr(node, 'params', []) or []
|
|
289
|
+
|
|
290
|
+
# Build parameter list
|
|
291
|
+
param_list = []
|
|
292
|
+
for param in params:
|
|
293
|
+
if hasattr(param, 'name'):
|
|
294
|
+
# QuantumParam object
|
|
295
|
+
param_name = param.name
|
|
296
|
+
default = getattr(param, 'default', None)
|
|
297
|
+
if default is not None:
|
|
298
|
+
param_list.append(f'{param_name}={repr(default)}')
|
|
299
|
+
else:
|
|
300
|
+
param_list.append(param_name)
|
|
301
|
+
elif isinstance(param, str):
|
|
302
|
+
# String parameter
|
|
303
|
+
param_list.append(param.strip())
|
|
304
|
+
elif isinstance(param, dict):
|
|
305
|
+
# Dict parameter
|
|
306
|
+
param_name = param.get('name', '')
|
|
307
|
+
default = param.get('default')
|
|
308
|
+
if default is not None:
|
|
309
|
+
param_list.append(f'{param_name}={repr(default)}')
|
|
310
|
+
else:
|
|
311
|
+
param_list.append(param_name)
|
|
312
|
+
|
|
313
|
+
param_str = ', '.join(param_list)
|
|
314
|
+
self.emit(f'def {name}({param_str}):')
|
|
315
|
+
|
|
316
|
+
self.indent()
|
|
317
|
+
self.push_scope()
|
|
318
|
+
|
|
319
|
+
# Declare parameters
|
|
320
|
+
for param in params:
|
|
321
|
+
if hasattr(param, 'name'):
|
|
322
|
+
self.declare_var(param.name)
|
|
323
|
+
elif isinstance(param, str):
|
|
324
|
+
param_name = param.split('=')[0].strip()
|
|
325
|
+
self.declare_var(param_name)
|
|
326
|
+
|
|
327
|
+
# Process body (FunctionNode uses 'body' not 'children')
|
|
328
|
+
body = getattr(node, 'body', None) or getattr(node, 'children', None) or []
|
|
329
|
+
has_return = False
|
|
330
|
+
for child in body:
|
|
331
|
+
child_type = type(child).__name__
|
|
332
|
+
if child_type == 'ReturnNode':
|
|
333
|
+
has_return = True
|
|
334
|
+
self.visit(child)
|
|
335
|
+
|
|
336
|
+
# Add implicit return if needed
|
|
337
|
+
if not has_return:
|
|
338
|
+
self.emit('return None')
|
|
339
|
+
|
|
340
|
+
self.pop_scope()
|
|
341
|
+
self.dedent()
|
|
342
|
+
self.emit_blank()
|
|
343
|
+
|
|
344
|
+
self.functions.append(name)
|
|
345
|
+
return name
|
|
346
|
+
|
|
347
|
+
def visit_ReturnNode(self, node) -> str:
|
|
348
|
+
"""Generate return statement."""
|
|
349
|
+
value = getattr(node, 'value', None)
|
|
350
|
+
|
|
351
|
+
if value:
|
|
352
|
+
expr = self.transpile_expression(str(value))
|
|
353
|
+
self.emit(f'return {expr}')
|
|
354
|
+
else:
|
|
355
|
+
self.emit('return None')
|
|
356
|
+
|
|
357
|
+
return ''
|
|
358
|
+
|
|
359
|
+
# =========================================================================
|
|
360
|
+
# HTML Rendering
|
|
361
|
+
# =========================================================================
|
|
362
|
+
|
|
363
|
+
def visit_HTMLNode(self, node: HTMLNode) -> str:
|
|
364
|
+
"""Generate HTML element."""
|
|
365
|
+
tag = node.tag or 'div'
|
|
366
|
+
attributes = getattr(node, 'attributes', {}) or {}
|
|
367
|
+
children = node.children or []
|
|
368
|
+
|
|
369
|
+
# Build attributes string
|
|
370
|
+
attr_parts = []
|
|
371
|
+
for key, value in attributes.items():
|
|
372
|
+
if value is None:
|
|
373
|
+
attr_parts.append(key)
|
|
374
|
+
elif self.is_expression(str(value)):
|
|
375
|
+
expr = self.transpile_expression(str(value))
|
|
376
|
+
attr_parts.append(f'{key}="{{escape({expr})}}"')
|
|
377
|
+
else:
|
|
378
|
+
attr_parts.append(f'{key}="{value}"')
|
|
379
|
+
|
|
380
|
+
attr_str = ' ' + ' '.join(attr_parts) if attr_parts else ''
|
|
381
|
+
|
|
382
|
+
# Self-closing tags
|
|
383
|
+
self_closing = tag.lower() in ('br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr')
|
|
384
|
+
|
|
385
|
+
if self_closing:
|
|
386
|
+
self.emit(f'_html.append(f\'<{tag}{attr_str} />\')')
|
|
387
|
+
elif not children:
|
|
388
|
+
self.emit(f'_html.append(f\'<{tag}{attr_str}></{tag}>\')')
|
|
389
|
+
else:
|
|
390
|
+
self.emit(f'_html.append(f\'<{tag}{attr_str}>\')')
|
|
391
|
+
|
|
392
|
+
# Process children
|
|
393
|
+
for child in children:
|
|
394
|
+
self.visit(child)
|
|
395
|
+
|
|
396
|
+
self.emit(f"_html.append('</{tag}>')")
|
|
397
|
+
|
|
398
|
+
return ''
|
|
399
|
+
|
|
400
|
+
def visit_TextNode(self, node: TextNode) -> str:
|
|
401
|
+
"""Generate text content."""
|
|
402
|
+
content = getattr(node, 'content', '') or getattr(node, 'text', '') or ''
|
|
403
|
+
|
|
404
|
+
if not content.strip():
|
|
405
|
+
return ''
|
|
406
|
+
|
|
407
|
+
# Check for databinding
|
|
408
|
+
if '{' in content and '}' in content:
|
|
409
|
+
# Complex expression - use bind()
|
|
410
|
+
self.emit(f"_html.append(bind({repr(content)}, locals()))")
|
|
411
|
+
else:
|
|
412
|
+
# Plain text
|
|
413
|
+
self.emit(f"_html.append({repr(content)})")
|
|
414
|
+
|
|
415
|
+
return ''
|
|
416
|
+
|
|
417
|
+
def visit_DocTypeNode(self, node: DocTypeNode) -> str:
|
|
418
|
+
"""Generate DOCTYPE."""
|
|
419
|
+
self.emit("_html.append('<!DOCTYPE html>')")
|
|
420
|
+
return ''
|
|
421
|
+
|
|
422
|
+
def visit_CommentNode(self, node: CommentNode) -> str:
|
|
423
|
+
"""Generate HTML comment."""
|
|
424
|
+
content = getattr(node, 'content', '') or getattr(node, 'text', '') or ''
|
|
425
|
+
self.emit(f"_html.append('<!-- {content} -->')")
|
|
426
|
+
return ''
|
|
427
|
+
|
|
428
|
+
# =========================================================================
|
|
429
|
+
# Database
|
|
430
|
+
# =========================================================================
|
|
431
|
+
|
|
432
|
+
def visit_QueryNode(self, node: QueryNode) -> str:
|
|
433
|
+
"""Generate database query."""
|
|
434
|
+
name = node.name or 'result'
|
|
435
|
+
datasource = getattr(node, 'datasource', 'default')
|
|
436
|
+
|
|
437
|
+
# Get SQL from content or children
|
|
438
|
+
sql_parts = []
|
|
439
|
+
params = []
|
|
440
|
+
|
|
441
|
+
# Check for raw SQL content
|
|
442
|
+
sql_content = getattr(node, 'sql', None) or getattr(node, 'content', None)
|
|
443
|
+
if sql_content:
|
|
444
|
+
sql_parts.append(sql_content.strip())
|
|
445
|
+
|
|
446
|
+
# Check children for params and additional SQL
|
|
447
|
+
for child in node.children or []:
|
|
448
|
+
child_type = type(child).__name__
|
|
449
|
+
if child_type == 'QueryParamNode':
|
|
450
|
+
value = getattr(child, 'value', None)
|
|
451
|
+
if value:
|
|
452
|
+
params.append(self.transpile_expression(str(value)))
|
|
453
|
+
sql_parts.append('?')
|
|
454
|
+
elif child_type == 'TextNode':
|
|
455
|
+
text = getattr(child, 'content', '') or getattr(child, 'text', '')
|
|
456
|
+
if text.strip():
|
|
457
|
+
sql_parts.append(text.strip())
|
|
458
|
+
|
|
459
|
+
sql = ' '.join(sql_parts).strip()
|
|
460
|
+
|
|
461
|
+
# Clean up SQL
|
|
462
|
+
sql = ' '.join(sql.split()) # Normalize whitespace
|
|
463
|
+
|
|
464
|
+
self.imports.add('from quantum.compiler.python.runtime import Database')
|
|
465
|
+
|
|
466
|
+
self.emit(f'# Query: {name}')
|
|
467
|
+
self.emit(f'_db = Database.get_connection({repr(datasource)})')
|
|
468
|
+
|
|
469
|
+
if params:
|
|
470
|
+
self.emit(f'{name} = _db.execute({repr(sql)}, [{", ".join(params)}]).fetchall()')
|
|
471
|
+
else:
|
|
472
|
+
self.emit(f'{name} = _db.execute({repr(sql)}).fetchall()')
|
|
473
|
+
|
|
474
|
+
self.declare_var(name)
|
|
475
|
+
return name
|
|
476
|
+
|
|
477
|
+
def visit_QueryParamNode(self, node: QueryParamNode) -> str:
|
|
478
|
+
"""Query params are handled in visit_QueryNode."""
|
|
479
|
+
return ''
|
|
480
|
+
|
|
481
|
+
# =========================================================================
|
|
482
|
+
# Actions & Redirects
|
|
483
|
+
# =========================================================================
|
|
484
|
+
|
|
485
|
+
def visit_ActionNode(self, node: ActionNode) -> str:
|
|
486
|
+
"""Generate action handler."""
|
|
487
|
+
name = getattr(node, 'name', 'action')
|
|
488
|
+
method = getattr(node, 'method', 'POST')
|
|
489
|
+
|
|
490
|
+
self.imports.add('from quantum.compiler.python.runtime import request, redirect, flash')
|
|
491
|
+
|
|
492
|
+
self.emit(f'# Action: {name}')
|
|
493
|
+
self.emit(f'def action_{name}():')
|
|
494
|
+
self.indent()
|
|
495
|
+
self.emit(f'"""Handle {method} action"""')
|
|
496
|
+
|
|
497
|
+
self.push_scope()
|
|
498
|
+
|
|
499
|
+
for child in node.children or []:
|
|
500
|
+
self.visit(child)
|
|
501
|
+
|
|
502
|
+
self.pop_scope()
|
|
503
|
+
self.dedent()
|
|
504
|
+
self.emit_blank()
|
|
505
|
+
|
|
506
|
+
return f'action_{name}'
|
|
507
|
+
|
|
508
|
+
def visit_RedirectNode(self, node: RedirectNode) -> str:
|
|
509
|
+
"""Generate redirect."""
|
|
510
|
+
url = getattr(node, 'url', '/') or getattr(node, 'to', '/')
|
|
511
|
+
status = getattr(node, 'status', 302) or getattr(node, 'code', 302)
|
|
512
|
+
|
|
513
|
+
self.imports.add('from quantum.compiler.python.runtime import redirect')
|
|
514
|
+
|
|
515
|
+
if self.is_expression(str(url)):
|
|
516
|
+
url_expr = self.transpile_expression(str(url))
|
|
517
|
+
self.emit(f'return redirect({url_expr}, code={status})')
|
|
518
|
+
else:
|
|
519
|
+
self.emit(f'return redirect({repr(url)}, code={status})')
|
|
520
|
+
|
|
521
|
+
return ''
|
|
522
|
+
|
|
523
|
+
def visit_FlashNode(self, node: FlashNode) -> str:
|
|
524
|
+
"""Generate flash message."""
|
|
525
|
+
message = getattr(node, 'message', '')
|
|
526
|
+
category = getattr(node, 'category', 'info') or getattr(node, 'type', 'info')
|
|
527
|
+
|
|
528
|
+
self.imports.add('from quantum.compiler.python.runtime import flash')
|
|
529
|
+
|
|
530
|
+
if self.is_expression(str(message)):
|
|
531
|
+
msg_expr = self.transpile_expression(str(message))
|
|
532
|
+
self.emit(f'flash({msg_expr}, {repr(category)})')
|
|
533
|
+
else:
|
|
534
|
+
self.emit(f'flash({repr(message)}, {repr(category)})')
|
|
535
|
+
|
|
536
|
+
return ''
|
|
537
|
+
|
|
538
|
+
# =========================================================================
|
|
539
|
+
# Imports & Python Integration
|
|
540
|
+
# =========================================================================
|
|
541
|
+
|
|
542
|
+
def visit_ImportNode(self, node: ImportNode) -> str:
|
|
543
|
+
"""Generate component import."""
|
|
544
|
+
name = getattr(node, 'name', None)
|
|
545
|
+
path = getattr(node, 'path', None) or getattr(node, 'from', None)
|
|
546
|
+
|
|
547
|
+
if path and name:
|
|
548
|
+
# Convert path to Python import
|
|
549
|
+
module_path = path.replace('/', '.').replace('.q', '')
|
|
550
|
+
self.imports.add(f'from {module_path} import {name}')
|
|
551
|
+
|
|
552
|
+
return ''
|
|
553
|
+
|
|
554
|
+
def visit_PyImportNode(self, node: PyImportNode) -> str:
|
|
555
|
+
"""Generate Python import."""
|
|
556
|
+
module = getattr(node, 'module', '')
|
|
557
|
+
names = getattr(node, 'names', '') or getattr(node, 'import', '')
|
|
558
|
+
alias = getattr(node, 'as', None) or getattr(node, 'alias', None)
|
|
559
|
+
|
|
560
|
+
if names:
|
|
561
|
+
if isinstance(names, str):
|
|
562
|
+
names = names.replace(' ', '')
|
|
563
|
+
self.imports.add(f'from {module} import {names}')
|
|
564
|
+
elif alias:
|
|
565
|
+
self.imports.add(f'import {module} as {alias}')
|
|
566
|
+
else:
|
|
567
|
+
self.imports.add(f'import {module}')
|
|
568
|
+
|
|
569
|
+
return ''
|
|
570
|
+
|
|
571
|
+
def visit_PythonNode(self, node: PythonNode) -> str:
|
|
572
|
+
"""Generate inline Python code."""
|
|
573
|
+
code = getattr(node, 'code', '') or getattr(node, 'content', '')
|
|
574
|
+
|
|
575
|
+
if code:
|
|
576
|
+
# Emit raw Python code
|
|
577
|
+
for line in code.strip().split('\n'):
|
|
578
|
+
self.emit(line)
|
|
579
|
+
|
|
580
|
+
return ''
|
|
581
|
+
|
|
582
|
+
# =========================================================================
|
|
583
|
+
# Component Calls
|
|
584
|
+
# =========================================================================
|
|
585
|
+
|
|
586
|
+
def visit_ComponentCallNode(self, node: ComponentCallNode) -> str:
|
|
587
|
+
"""Generate component instantiation."""
|
|
588
|
+
name = getattr(node, 'name', 'Component')
|
|
589
|
+
props = getattr(node, 'props', {}) or getattr(node, 'attributes', {}) or {}
|
|
590
|
+
|
|
591
|
+
# Build props
|
|
592
|
+
prop_parts = []
|
|
593
|
+
for key, value in props.items():
|
|
594
|
+
if self.is_expression(str(value)):
|
|
595
|
+
expr = self.transpile_expression(str(value))
|
|
596
|
+
prop_parts.append(f'{key}={expr}')
|
|
597
|
+
else:
|
|
598
|
+
prop_parts.append(f'{key}={repr(value)}')
|
|
599
|
+
|
|
600
|
+
props_str = ', '.join(prop_parts)
|
|
601
|
+
|
|
602
|
+
if self.in_render:
|
|
603
|
+
self.emit(f'_html.append({name}({props_str}).render())')
|
|
604
|
+
else:
|
|
605
|
+
self.emit(f'{name}({props_str})')
|
|
606
|
+
|
|
607
|
+
return ''
|
|
608
|
+
|
|
609
|
+
def visit_SlotNode(self, node: SlotNode) -> str:
|
|
610
|
+
"""Generate slot placeholder."""
|
|
611
|
+
name = getattr(node, 'name', 'default')
|
|
612
|
+
|
|
613
|
+
self.emit(f"# Slot: {name}")
|
|
614
|
+
self.emit(f"if '{name}' in self.props.get('_slots', {{}}):")
|
|
615
|
+
self.indent()
|
|
616
|
+
self.emit(f"_html.append(self.props['_slots']['{name}'])")
|
|
617
|
+
self.dedent()
|
|
618
|
+
|
|
619
|
+
# Default content
|
|
620
|
+
if node.children:
|
|
621
|
+
self.emit('else:')
|
|
622
|
+
self.indent()
|
|
623
|
+
for child in node.children:
|
|
624
|
+
self.visit(child)
|
|
625
|
+
self.dedent()
|
|
626
|
+
|
|
627
|
+
return ''
|
|
628
|
+
|
|
629
|
+
# =========================================================================
|
|
630
|
+
# Data Nodes (Phase 2)
|
|
631
|
+
# =========================================================================
|
|
632
|
+
|
|
633
|
+
def visit_DataNode(self, node) -> str:
|
|
634
|
+
"""Generate data import code."""
|
|
635
|
+
name = getattr(node, 'name', 'data')
|
|
636
|
+
source = getattr(node, 'source', '')
|
|
637
|
+
data_type = getattr(node, 'data_type', None) or getattr(node, 'type', 'json')
|
|
638
|
+
|
|
639
|
+
self.imports.add('from quantum.compiler.python.runtime import load_data')
|
|
640
|
+
|
|
641
|
+
source_expr = self.transpile_expression(str(source)) if self.is_expression(str(source)) else repr(source)
|
|
642
|
+
|
|
643
|
+
self.emit(f'# Data import: {name}')
|
|
644
|
+
self.emit(f'{name} = load_data({source_expr}, type={repr(data_type)})')
|
|
645
|
+
self.declare_var(name)
|
|
646
|
+
|
|
647
|
+
return name
|
|
648
|
+
|
|
649
|
+
def visit_InvokeNode(self, node) -> str:
|
|
650
|
+
"""Generate HTTP/service invocation."""
|
|
651
|
+
name = getattr(node, 'name', 'result')
|
|
652
|
+
url = getattr(node, 'url', None)
|
|
653
|
+
method = getattr(node, 'method', 'GET')
|
|
654
|
+
function = getattr(node, 'function', None)
|
|
655
|
+
component = getattr(node, 'component', None)
|
|
656
|
+
|
|
657
|
+
self.imports.add('from quantum.compiler.python.runtime import invoke')
|
|
658
|
+
|
|
659
|
+
self.emit(f'# Invoke: {name}')
|
|
660
|
+
|
|
661
|
+
if function:
|
|
662
|
+
# Local function call
|
|
663
|
+
func_expr = self.transpile_expression(str(function)) if self.is_expression(str(function)) else function
|
|
664
|
+
self.emit(f'{name} = {func_expr}()')
|
|
665
|
+
elif component:
|
|
666
|
+
# Component instantiation
|
|
667
|
+
comp_expr = self.transpile_expression(str(component)) if self.is_expression(str(component)) else component
|
|
668
|
+
self.emit(f'{name} = {comp_expr}().render()')
|
|
669
|
+
elif url:
|
|
670
|
+
# HTTP request
|
|
671
|
+
url_expr = self.transpile_expression(str(url)) if self.is_expression(str(url)) else repr(url)
|
|
672
|
+
|
|
673
|
+
# Build headers and body from children
|
|
674
|
+
headers = {}
|
|
675
|
+
body = None
|
|
676
|
+
for child in getattr(node, 'children', []) or []:
|
|
677
|
+
child_type = type(child).__name__
|
|
678
|
+
if child_type == 'InvokeHeaderNode':
|
|
679
|
+
headers[getattr(child, 'name', '')] = getattr(child, 'value', '')
|
|
680
|
+
elif child_type == 'InvokeBodyNode':
|
|
681
|
+
body = getattr(child, 'content', None)
|
|
682
|
+
|
|
683
|
+
headers_str = repr(headers) if headers else '{}'
|
|
684
|
+
body_str = self.transpile_expression(str(body)) if body and self.is_expression(str(body)) else repr(body) if body else 'None'
|
|
685
|
+
|
|
686
|
+
self.emit(f'{name} = invoke({url_expr}, method={repr(method)}, headers={headers_str}, body={body_str})')
|
|
687
|
+
else:
|
|
688
|
+
self.emit(f'{name} = None # No target specified')
|
|
689
|
+
|
|
690
|
+
self.declare_var(name)
|
|
691
|
+
return name
|
|
692
|
+
|
|
693
|
+
def visit_FileNode(self, node) -> str:
|
|
694
|
+
"""Generate file operation code."""
|
|
695
|
+
action = getattr(node, 'action', 'upload')
|
|
696
|
+
file_var = getattr(node, 'file', '')
|
|
697
|
+
destination = getattr(node, 'destination', './uploads/')
|
|
698
|
+
result_var = getattr(node, 'result', None) or getattr(node, 'name', 'file_result')
|
|
699
|
+
|
|
700
|
+
self.imports.add('from quantum.compiler.python.runtime import handle_file')
|
|
701
|
+
|
|
702
|
+
file_expr = self.transpile_expression(str(file_var)) if self.is_expression(str(file_var)) else file_var
|
|
703
|
+
dest_expr = self.transpile_expression(str(destination)) if self.is_expression(str(destination)) else repr(destination)
|
|
704
|
+
|
|
705
|
+
self.emit(f'# File operation: {action}')
|
|
706
|
+
self.emit(f'{result_var} = handle_file({file_expr}, action={repr(action)}, destination={dest_expr})')
|
|
707
|
+
self.declare_var(result_var)
|
|
708
|
+
|
|
709
|
+
return result_var
|
|
710
|
+
|
|
711
|
+
def visit_MailNode(self, node) -> str:
|
|
712
|
+
"""Generate email sending code."""
|
|
713
|
+
to = getattr(node, 'to', '')
|
|
714
|
+
from_addr = getattr(node, 'from_', '') or getattr(node, 'sender', '')
|
|
715
|
+
subject = getattr(node, 'subject', '')
|
|
716
|
+
body_content = None
|
|
717
|
+
|
|
718
|
+
# Get body from children
|
|
719
|
+
for child in getattr(node, 'children', []) or []:
|
|
720
|
+
if hasattr(child, 'content'):
|
|
721
|
+
body_content = child.content
|
|
722
|
+
break
|
|
723
|
+
elif hasattr(child, 'text'):
|
|
724
|
+
body_content = child.text
|
|
725
|
+
break
|
|
726
|
+
|
|
727
|
+
self.imports.add('from quantum.compiler.python.runtime import send_mail')
|
|
728
|
+
|
|
729
|
+
to_expr = self.transpile_expression(str(to)) if self.is_expression(str(to)) else repr(to)
|
|
730
|
+
from_expr = self.transpile_expression(str(from_addr)) if self.is_expression(str(from_addr)) else repr(from_addr)
|
|
731
|
+
subject_expr = self.transpile_expression(str(subject)) if self.is_expression(str(subject)) else repr(subject)
|
|
732
|
+
body_expr = self.transpile_expression(str(body_content)) if body_content and self.is_expression(str(body_content)) else repr(body_content or '')
|
|
733
|
+
|
|
734
|
+
self.emit(f'# Send email')
|
|
735
|
+
self.emit(f'send_mail(to={to_expr}, from_addr={from_expr}, subject={subject_expr}, body={body_expr})')
|
|
736
|
+
|
|
737
|
+
return ''
|
|
738
|
+
|
|
739
|
+
# =========================================================================
|
|
740
|
+
# Async Nodes (Phase 3)
|
|
741
|
+
# =========================================================================
|
|
742
|
+
|
|
743
|
+
def visit_JobNode(self, node) -> str:
|
|
744
|
+
"""Generate job queue code."""
|
|
745
|
+
name = getattr(node, 'name', 'job')
|
|
746
|
+
queue = getattr(node, 'queue', 'default')
|
|
747
|
+
action = getattr(node, 'action', 'define')
|
|
748
|
+
priority = getattr(node, 'priority', 0)
|
|
749
|
+
delay = getattr(node, 'delay', None)
|
|
750
|
+
|
|
751
|
+
self.imports.add('from quantum.compiler.python.runtime import JobQueue')
|
|
752
|
+
|
|
753
|
+
self.emit(f'# Job: {name}')
|
|
754
|
+
|
|
755
|
+
if action == 'define':
|
|
756
|
+
self.emit(f'@JobQueue.task(queue={repr(queue)}, priority={priority})')
|
|
757
|
+
self.emit(f'def job_{name}(**params):')
|
|
758
|
+
self.indent()
|
|
759
|
+
self.emit('"""Job task"""')
|
|
760
|
+
self.push_scope()
|
|
761
|
+
for child in getattr(node, 'children', []) or []:
|
|
762
|
+
self.visit(child)
|
|
763
|
+
self.pop_scope()
|
|
764
|
+
self.dedent()
|
|
765
|
+
self.emit_blank()
|
|
766
|
+
elif action == 'dispatch':
|
|
767
|
+
delay_str = f', delay={repr(delay)}' if delay else ''
|
|
768
|
+
self.emit(f'JobQueue.dispatch({repr(name)}{delay_str})')
|
|
769
|
+
elif action == 'cancel':
|
|
770
|
+
self.emit(f'JobQueue.cancel({repr(name)})')
|
|
771
|
+
|
|
772
|
+
return f'job_{name}'
|
|
773
|
+
|
|
774
|
+
def visit_ScheduleNode(self, node) -> str:
|
|
775
|
+
"""Generate scheduled task code."""
|
|
776
|
+
name = getattr(node, 'name', 'task')
|
|
777
|
+
interval = getattr(node, 'interval', None)
|
|
778
|
+
cron = getattr(node, 'cron', None)
|
|
779
|
+
at = getattr(node, 'at', None)
|
|
780
|
+
enabled = getattr(node, 'enabled', True)
|
|
781
|
+
|
|
782
|
+
self.imports.add('from quantum.compiler.python.runtime import Scheduler')
|
|
783
|
+
|
|
784
|
+
self.emit(f'# Scheduled task: {name}')
|
|
785
|
+
|
|
786
|
+
schedule_args = []
|
|
787
|
+
if interval:
|
|
788
|
+
schedule_args.append(f'interval={repr(interval)}')
|
|
789
|
+
elif cron:
|
|
790
|
+
schedule_args.append(f'cron={repr(cron)}')
|
|
791
|
+
elif at:
|
|
792
|
+
schedule_args.append(f'at={repr(at)}')
|
|
793
|
+
|
|
794
|
+
schedule_args.append(f'enabled={enabled}')
|
|
795
|
+
args_str = ', '.join(schedule_args)
|
|
796
|
+
|
|
797
|
+
self.emit(f'@Scheduler.schedule({args_str})')
|
|
798
|
+
self.emit(f'def scheduled_{name}():')
|
|
799
|
+
self.indent()
|
|
800
|
+
self.emit(f'"""Scheduled task: {name}"""')
|
|
801
|
+
self.push_scope()
|
|
802
|
+
for child in getattr(node, 'children', []) or []:
|
|
803
|
+
self.visit(child)
|
|
804
|
+
self.pop_scope()
|
|
805
|
+
self.dedent()
|
|
806
|
+
self.emit_blank()
|
|
807
|
+
|
|
808
|
+
return f'scheduled_{name}'
|
|
809
|
+
|
|
810
|
+
def visit_ThreadNode(self, node) -> str:
|
|
811
|
+
"""Generate async thread code."""
|
|
812
|
+
name = getattr(node, 'name', 'thread')
|
|
813
|
+
action = getattr(node, 'action', 'run')
|
|
814
|
+
priority = getattr(node, 'priority', 'normal')
|
|
815
|
+
timeout = getattr(node, 'timeout', None)
|
|
816
|
+
on_complete = getattr(node, 'on_complete', None) or getattr(node, 'onComplete', None)
|
|
817
|
+
|
|
818
|
+
self.imports.add('from quantum.compiler.python.runtime import AsyncThread')
|
|
819
|
+
|
|
820
|
+
self.emit(f'# Thread: {name}')
|
|
821
|
+
|
|
822
|
+
if action == 'run':
|
|
823
|
+
timeout_str = f', timeout={repr(timeout)}' if timeout else ''
|
|
824
|
+
callback_str = f', on_complete={on_complete}' if on_complete else ''
|
|
825
|
+
|
|
826
|
+
self.emit(f'def thread_{name}_work():')
|
|
827
|
+
self.indent()
|
|
828
|
+
self.emit('"""Thread work function"""')
|
|
829
|
+
self.push_scope()
|
|
830
|
+
for child in getattr(node, 'children', []) or []:
|
|
831
|
+
self.visit(child)
|
|
832
|
+
self.pop_scope()
|
|
833
|
+
self.dedent()
|
|
834
|
+
self.emit_blank()
|
|
835
|
+
|
|
836
|
+
self.emit(f'thread_{name} = AsyncThread({repr(name)}, thread_{name}_work{timeout_str}{callback_str})')
|
|
837
|
+
self.emit(f'thread_{name}.start()')
|
|
838
|
+
elif action == 'join':
|
|
839
|
+
self.emit(f'thread_{name}.join()')
|
|
840
|
+
elif action == 'terminate':
|
|
841
|
+
self.emit(f'thread_{name}.terminate()')
|
|
842
|
+
|
|
843
|
+
return f'thread_{name}'
|
|
844
|
+
|
|
845
|
+
def visit_ParallelNode(self, node) -> str:
|
|
846
|
+
"""Generate parallel execution code."""
|
|
847
|
+
name = getattr(node, 'name', 'parallel')
|
|
848
|
+
max_workers = getattr(node, 'max_workers', None) or getattr(node, 'maxWorkers', 4)
|
|
849
|
+
|
|
850
|
+
self.imports.add('from quantum.compiler.python.runtime import parallel_execute')
|
|
851
|
+
|
|
852
|
+
self.emit(f'# Parallel execution: {name}')
|
|
853
|
+
self.emit(f'def parallel_{name}_tasks():')
|
|
854
|
+
self.indent()
|
|
855
|
+
self.emit('tasks = []')
|
|
856
|
+
|
|
857
|
+
# Collect child tasks
|
|
858
|
+
for idx, child in enumerate(getattr(node, 'children', []) or []):
|
|
859
|
+
child_type = type(child).__name__
|
|
860
|
+
if child_type == 'ThreadNode' or child_type == 'TaskNode':
|
|
861
|
+
self.emit(f'def _task_{idx}():')
|
|
862
|
+
self.indent()
|
|
863
|
+
self.visit(child)
|
|
864
|
+
self.dedent()
|
|
865
|
+
self.emit(f'tasks.append(_task_{idx})')
|
|
866
|
+
else:
|
|
867
|
+
self.visit(child)
|
|
868
|
+
|
|
869
|
+
self.emit(f'return parallel_execute(tasks, max_workers={max_workers})')
|
|
870
|
+
self.dedent()
|
|
871
|
+
self.emit_blank()
|
|
872
|
+
self.emit(f'{name}_results = parallel_{name}_tasks()')
|
|
873
|
+
self.declare_var(f'{name}_results')
|
|
874
|
+
|
|
875
|
+
return name
|
|
876
|
+
|
|
877
|
+
# =========================================================================
|
|
878
|
+
# Comments
|
|
879
|
+
# =========================================================================
|
|
880
|
+
|
|
881
|
+
def emit_comment(self, comment: str):
|
|
882
|
+
"""Emit Python comment."""
|
|
883
|
+
self.emit(f'# {comment}')
|