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.
Files changed (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,262 @@
1
+ """
2
+ Base Executor - Abstract base class for all node executors
3
+
4
+ All executors inherit from BaseExecutor and implement the execute() method.
5
+ The ExecutorRegistry uses the 'handles' property to dispatch nodes to the correct executor.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, List, Type, Dict, TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from quantum.runtime.execution_context import ExecutionContext
13
+
14
+
15
+ class ExecutorError(Exception):
16
+ """Base exception for executor errors"""
17
+ pass
18
+
19
+
20
+ class BaseExecutor(ABC):
21
+ """
22
+ Abstract base class for all node executors.
23
+
24
+ Each executor handles one or more AST node types and implements
25
+ the execute() method to process them.
26
+
27
+ Example:
28
+ class LoopExecutor(BaseExecutor):
29
+ @property
30
+ def handles(self) -> List[Type]:
31
+ return [LoopNode]
32
+
33
+ def execute(self, node: LoopNode, context: ExecutionContext) -> Any:
34
+ # Execute loop logic
35
+ pass
36
+ """
37
+
38
+ def __init__(self, runtime: 'ComponentRuntime'):
39
+ """
40
+ Initialize executor with reference to runtime.
41
+
42
+ Args:
43
+ runtime: The ComponentRuntime instance (provides services and context)
44
+ """
45
+ self._runtime = runtime
46
+
47
+ @property
48
+ def runtime(self) -> 'ComponentRuntime':
49
+ """Access to the component runtime"""
50
+ return self._runtime
51
+
52
+ @property
53
+ def context(self) -> 'ExecutionContext':
54
+ """Shortcut to execution context"""
55
+ return self._runtime.execution_context
56
+
57
+ @property
58
+ def services(self) -> 'ServiceContainer':
59
+ """Shortcut to service container"""
60
+ return self._runtime.services
61
+
62
+ @property
63
+ @abstractmethod
64
+ def handles(self) -> List[Type]:
65
+ """
66
+ List of AST node types this executor handles.
67
+
68
+ Returns:
69
+ List of node classes (e.g., [LoopNode, ForNode])
70
+ """
71
+ pass
72
+
73
+ @abstractmethod
74
+ def execute(self, node: Any, exec_context: 'ExecutionContext') -> Any:
75
+ """
76
+ Execute the node and return result.
77
+
78
+ Args:
79
+ node: The AST node to execute
80
+ exec_context: The execution context with variables and scopes
81
+
82
+ Returns:
83
+ The result of execution (type depends on node type)
84
+ """
85
+ pass
86
+
87
+ # ==========================================================================
88
+ # Helper methods - Common utilities available to all executors
89
+ # ==========================================================================
90
+
91
+ def apply_databinding(self, value: str, context: Dict[str, Any] = None) -> Any:
92
+ """
93
+ Apply databinding to resolve {variable} expressions.
94
+
95
+ Args:
96
+ value: String potentially containing {expressions}
97
+ context: Variable context (uses exec_context if not provided)
98
+
99
+ Returns:
100
+ Resolved value
101
+ """
102
+ if context is None:
103
+ context = self.get_all_variables()
104
+ return self._runtime._apply_databinding(value, context)
105
+
106
+ def evaluate_condition(self, condition: str, context: Dict[str, Any] = None) -> bool:
107
+ """
108
+ Evaluate a condition string.
109
+
110
+ Args:
111
+ condition: Condition expression (e.g., "{x} > 5")
112
+ context: Variable context
113
+
114
+ Returns:
115
+ Boolean result
116
+ """
117
+ if context is None:
118
+ context = self.get_all_variables()
119
+ return self._runtime._evaluate_condition(condition, context)
120
+
121
+ def get_all_variables(self) -> Dict[str, Any]:
122
+ """
123
+ Get all variables from execution context.
124
+
125
+ Returns:
126
+ Dict of all variables across all scopes
127
+ """
128
+ return self.context.get_all_variables()
129
+
130
+ def set_variable(self, name: str, value: Any, scope: str = "local"):
131
+ """
132
+ Set a variable in the execution context.
133
+
134
+ Args:
135
+ name: Variable name
136
+ value: Variable value
137
+ scope: Scope (local, component, session, application)
138
+ """
139
+ self.context.set_variable(name, value, scope=scope)
140
+
141
+ def get_variable(self, name: str, default: Any = None) -> Any:
142
+ """
143
+ Get a variable from the execution context.
144
+
145
+ Args:
146
+ name: Variable name
147
+ default: Default value if not found
148
+
149
+ Returns:
150
+ Variable value or default
151
+ """
152
+ try:
153
+ return self.context.get_variable(name)
154
+ except Exception:
155
+ return default
156
+
157
+ def execute_child(self, node: Any, exec_context: 'ExecutionContext' = None) -> Any:
158
+ """
159
+ Execute a child node using the registry.
160
+
161
+ Args:
162
+ node: Child AST node
163
+ exec_context: Execution context (uses self.context if not provided)
164
+
165
+ Returns:
166
+ Result of child execution
167
+ """
168
+ if exec_context is None:
169
+ exec_context = self.context
170
+ return self._runtime.executor_registry.execute(node, exec_context)
171
+
172
+ def execute_children(self, nodes: List[Any], exec_context: 'ExecutionContext' = None) -> List[Any]:
173
+ """
174
+ Execute multiple child nodes.
175
+
176
+ Args:
177
+ nodes: List of AST nodes
178
+ exec_context: Execution context
179
+
180
+ Returns:
181
+ List of results
182
+ """
183
+ results = []
184
+ for node in nodes:
185
+ result = self.execute_child(node, exec_context)
186
+ if result is not None:
187
+ results.append(result)
188
+ return results
189
+
190
+ def require_python_scripting(self, tag: str):
191
+ """Refuse to run in-process Python when policy disables it.
192
+
193
+ q:python/q:pyclass/q:pyimport execute arbitrary Python in the server
194
+ process — no sandbox, full access to the filesystem, network and os.
195
+ That is a deliberate escape hatch for author-written code, but it
196
+ must be switchable off before anything the author did not write
197
+ (LLM-generated templates, user-submitted components) can reach it.
198
+ """
199
+ if not self.services.python_scripting_enabled:
200
+ # A mensagem tem de dizer COMO ligar. O default virou desligado,
201
+ # entao esta e a primeira coisa que quem usa q:python encontra —
202
+ # e um erro que so diz "desabilitado" manda a pessoa procurar no
203
+ # codigo qual e a chave.
204
+ raise ExecutorError(
205
+ f"{tag} is off: security.python_scripting is not enabled. It "
206
+ f"executes arbitrary Python in the server process with no "
207
+ f"sandbox, so it is off by default — turn it on only for "
208
+ f"code you wrote yourself.\n"
209
+ f" In quantum.config.yaml:\n"
210
+ f" security:\n"
211
+ f" python_scripting: true\n"
212
+ f" Or in the environment: QUANTUM_PYTHON_SCRIPTING=1"
213
+ )
214
+
215
+ def run_body_in_child_context(self, body: List[Any], variables: Dict[str, Any] = None) -> Any:
216
+ """
217
+ Execute a list of AST statements in a fresh child context.
218
+
219
+ Used by nodes whose body runs detached from the surrounding flow
220
+ (q:schedule callbacks, q:thread callbacks, q:agent tool bodies).
221
+ Render-only nodes are skipped — they belong to HTMLRenderer, not the
222
+ executor registry. Returns the first q:return value found, if any.
223
+ """
224
+ from quantum.core.ast_nodes import (
225
+ QuantumReturn, HTMLNode, TextNode, DocTypeNode, CommentNode,
226
+ )
227
+ render_only = (HTMLNode, TextNode, DocTypeNode, CommentNode)
228
+
229
+ child_context = self.context.create_child_context()
230
+ for key, value in (variables or {}).items():
231
+ child_context.set_variable(key, value, scope="local")
232
+
233
+ for statement in body or []:
234
+ if isinstance(statement, QuantumReturn):
235
+ return self.resolve_value(
236
+ statement.value, child_context.get_all_variables()
237
+ )
238
+ if isinstance(statement, render_only):
239
+ continue
240
+ self._runtime.executor_registry.execute(statement, child_context)
241
+
242
+ return None
243
+
244
+ def resolve_value(self, value: str, context: Dict[str, Any] = None) -> Any:
245
+ """
246
+ Resolve a value string to its actual value.
247
+
248
+ Handles:
249
+ - Databinding: {variable}
250
+ - Literals: "string", 123, true/false
251
+ - JSON: {"key": "value"}
252
+
253
+ Args:
254
+ value: Value string to resolve
255
+ context: Variable context
256
+
257
+ Returns:
258
+ Resolved value
259
+ """
260
+ if context is None:
261
+ context = self.get_all_variables()
262
+ return self._runtime._process_return_value(value, context)
@@ -0,0 +1,11 @@
1
+ """
2
+ Control Flow Executors
3
+
4
+ Executors for control flow statements: if, loop, set.
5
+ """
6
+
7
+ from .if_executor import IfExecutor
8
+ from .loop_executor import LoopExecutor
9
+ from .set_executor import SetExecutor
10
+
11
+ __all__ = ['IfExecutor', 'LoopExecutor', 'SetExecutor']
@@ -0,0 +1,93 @@
1
+ """
2
+ If Executor - Execute q:if statements
3
+
4
+ Handles conditional execution with elseif and else blocks.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
9
+ from quantum.runtime.executors.control_flow.loop_executor import produced_return
10
+ from quantum.core.features.conditionals.src.ast_node import IfNode
11
+ from quantum.core.ast_nodes import (
12
+ QuantumReturn,
13
+ HTMLNode, TextNode, DocTypeNode, CommentNode,
14
+ ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
15
+ )
16
+
17
+ # Same render-only skip list as component.py's top-level _execute_statement
18
+ # and loop_executor.py — text/HTML inside a q:if body is handled by
19
+ # HTMLRenderer, not by the executor registry.
20
+ _RENDER_ONLY_NODE_TYPES = (
21
+ HTMLNode, TextNode, DocTypeNode, CommentNode,
22
+ ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
23
+ )
24
+
25
+
26
+ class IfExecutor(BaseExecutor):
27
+ """
28
+ Executor for q:if statements.
29
+
30
+ Supports:
31
+ - Simple if conditions
32
+ - Multiple elseif blocks
33
+ - Optional else block
34
+ - Nested conditionals
35
+ """
36
+
37
+ @property
38
+ def handles(self) -> List[Type]:
39
+ return [IfNode]
40
+
41
+ def execute(self, node: IfNode, exec_context) -> Any:
42
+ """
43
+ Execute q:if statement with elseif and else.
44
+
45
+ Args:
46
+ node: IfNode to execute
47
+ exec_context: Execution context
48
+
49
+ Returns:
50
+ Result of executed branch, or None
51
+ """
52
+ context = self.get_all_variables()
53
+
54
+ # Evaluate main if condition
55
+ if self.evaluate_condition(node.condition, context):
56
+ return self._execute_body(node.if_body, context)
57
+
58
+ # Check elseif conditions
59
+ for elseif_block in node.elseif_blocks:
60
+ if self.evaluate_condition(elseif_block["condition"], context):
61
+ return self._execute_body(elseif_block["body"], context)
62
+
63
+ # Execute else block if present
64
+ if node.else_body:
65
+ return self._execute_body(node.else_body, context)
66
+
67
+ return None
68
+
69
+ def _execute_body(self, statements: List, context: Dict[str, Any]) -> Any:
70
+ """
71
+ Execute a list of statements in a branch body.
72
+
73
+ Args:
74
+ statements: List of AST nodes to execute
75
+ context: Variable context
76
+
77
+ Returns:
78
+ Return value if any statement returns, None otherwise
79
+ """
80
+ for statement in statements:
81
+ if isinstance(statement, QuantumReturn):
82
+ return self.resolve_value(statement.value, context)
83
+ elif isinstance(statement, _RENDER_ONLY_NODE_TYPES):
84
+ continue
85
+ else:
86
+ # Use registry to execute child statements
87
+ result = self.execute_child(statement)
88
+ # A nested q:if that returned, or a q:loop that collected
89
+ # returns, ends this branch with that value.
90
+ if produced_return(statement, result):
91
+ return result
92
+
93
+ return None
@@ -0,0 +1,307 @@
1
+ """
2
+ Loop Executor - Execute q:loop statements
3
+
4
+ Handles all loop types: range, array, list, query.
5
+ """
6
+
7
+ from typing import Any, List, Dict, Type
8
+ import json
9
+ from quantum.runtime.executors.base import BaseExecutor, ExecutorError
10
+ from quantum.core.features.loops.src.ast_node import LoopNode
11
+ from quantum.core.features.conditionals.src.ast_node import IfNode
12
+ from quantum.core.features.state_management.src.ast_node import SetNode
13
+ from quantum.core.features.logging.src import LogNode
14
+ from quantum.core.features.dump.src import DumpNode
15
+ from quantum.core.ast_nodes import (
16
+ QuantumReturn,
17
+ HTMLNode, TextNode, DocTypeNode, CommentNode,
18
+ ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
19
+ )
20
+
21
+ # Render-only / special-purpose nodes that never go through the executor
22
+ # registry (see component.py's top-level _execute_statement, which skips the
23
+ # same types). A loop body with plain text content used to raise
24
+ # "No executor registered for TextNode" — reachable only once the loop-type
25
+ # inference bug in loop_parser.py was fixed, since before that such bodies
26
+ # never parsed as array/list/query loops in the first place.
27
+ _RENDER_ONLY_NODE_TYPES = (
28
+ HTMLNode, TextNode, DocTypeNode, CommentNode,
29
+ ActionNode, RedirectNode, FlashNode, ImportNode, SlotNode, FunctionNode,
30
+ )
31
+
32
+
33
+ class LoopReturns(list):
34
+ """Os valores dos q:return executados dentro de um q:loop, em ordem.
35
+
36
+ Um tipo proprio, e nao `list`, porque o resultado de um loop antes
37
+ misturava qualquer coisa nao-None que o corpo produzisse — linhas de um
38
+ q:query, por exemplo — e por isso quem chamava nao tinha como tratar o
39
+ loop como return e simplesmente jogava o resultado fora. Era assim que
40
+ `<q:loop ...><q:return value="{x}"/></q:loop>` num componente devolvia
41
+ None, contra o que getting-started.md documenta.
42
+ """
43
+
44
+
45
+ def produced_return(statement, result) -> bool:
46
+ """O statement executado deve encerrar o corpo que o contem?
47
+
48
+ Vale para um q:if cujo ramo executou um q:return, e para um q:loop que
49
+ coletou pelo menos um. Um loop que nao coletou nada — so q:set, ou um
50
+ filtro que nao casou — deixa a execucao seguir, como um q:if falso.
51
+ """
52
+ if isinstance(result, LoopReturns):
53
+ return len(result) > 0
54
+ return result is not None and isinstance(statement, IfNode)
55
+
56
+
57
+ def as_return_value(result):
58
+ """Entrega um LoopReturns como lista comum para quem esta fora do runtime."""
59
+ return list(result) if isinstance(result, LoopReturns) else result
60
+
61
+
62
+ class LoopExecutor(BaseExecutor):
63
+ """
64
+ Executor for q:loop statements.
65
+
66
+ Supports:
67
+ - range: numeric iteration (from/to/step)
68
+ - array: iterate over array items
69
+ - list: iterate over delimited string
70
+ - query: iterate over query result rows
71
+ """
72
+
73
+ @property
74
+ def handles(self) -> List[Type]:
75
+ return [LoopNode]
76
+
77
+ def execute(self, node: LoopNode, exec_context) -> Any:
78
+ """
79
+ Execute q:loop statement based on type.
80
+
81
+ Args:
82
+ node: LoopNode to execute
83
+ exec_context: Execution context
84
+
85
+ Returns:
86
+ List of results from loop iterations
87
+ """
88
+ if node.loop_type == 'range':
89
+ return self._execute_range(node, exec_context)
90
+ elif node.loop_type == 'array':
91
+ return self._execute_array(node, exec_context)
92
+ elif node.loop_type == 'list':
93
+ return self._execute_list(node, exec_context)
94
+ elif node.loop_type == 'query':
95
+ return self._execute_query(node, exec_context)
96
+ else:
97
+ raise ExecutorError(f"Unsupported loop type: {node.loop_type}")
98
+
99
+ def _execute_range(self, node: LoopNode, exec_context) -> List:
100
+ """Execute range loop (from/to/step)"""
101
+ results = LoopReturns()
102
+ context = self.get_all_variables()
103
+
104
+ try:
105
+ start = int(self._evaluate_simple_expr(node.from_value, context))
106
+ end = int(self._evaluate_simple_expr(node.to_value, context))
107
+ step = node.step_value
108
+
109
+ for i in range(start, end + 1, step):
110
+ # Update loop variable
111
+ loop_context = context.copy()
112
+ loop_context[node.var_name] = i
113
+ exec_context.set_variable(node.var_name, i, scope="local")
114
+
115
+ # Execute body
116
+ for statement in node.body:
117
+ result = self._execute_body_statement(statement, loop_context, exec_context)
118
+ self._collect(results, statement, result)
119
+
120
+ return results
121
+
122
+ except (ValueError, TypeError) as e:
123
+ raise ExecutorError(f"Range loop error: {e}")
124
+
125
+ def _execute_array(self, node: LoopNode, exec_context) -> List:
126
+ """Execute array loop"""
127
+ results = LoopReturns()
128
+ context = self.get_all_variables()
129
+
130
+ try:
131
+ array_data = self._parse_array_items(node.items, context)
132
+
133
+ for index, item in enumerate(array_data):
134
+ exec_context.set_variable(node.var_name, item, scope="local")
135
+
136
+ if node.index_name:
137
+ exec_context.set_variable(node.index_name, index, scope="local")
138
+
139
+ loop_context = exec_context.get_all_variables()
140
+
141
+ for statement in node.body:
142
+ result = self._execute_body_statement(statement, loop_context, exec_context)
143
+ self._collect(results, statement, result)
144
+ loop_context = exec_context.get_all_variables()
145
+
146
+ return results
147
+
148
+ except Exception as e:
149
+ raise ExecutorError(f"Array loop error: {e}")
150
+
151
+ def _execute_list(self, node: LoopNode, exec_context) -> List:
152
+ """Execute list loop (delimited string)"""
153
+ results = LoopReturns()
154
+ context = self.get_all_variables()
155
+
156
+ try:
157
+ list_data = self._parse_list_items(node.items, node.delimiter, context)
158
+
159
+ for index, item in enumerate(list_data):
160
+ loop_context = context.copy()
161
+ loop_context[node.var_name] = item.strip()
162
+ exec_context.set_variable(node.var_name, item.strip(), scope="local")
163
+
164
+ if node.index_name:
165
+ loop_context[node.index_name] = index
166
+ exec_context.set_variable(node.index_name, index, scope="local")
167
+
168
+ for statement in node.body:
169
+ result = self._execute_body_statement(statement, loop_context, exec_context)
170
+ self._collect(results, statement, result)
171
+
172
+ return results
173
+
174
+ except Exception as e:
175
+ raise ExecutorError(f"List loop error: {e}")
176
+
177
+ def _execute_query(self, node: LoopNode, exec_context) -> List:
178
+ """Execute query loop - iterate over query result rows"""
179
+ results = LoopReturns()
180
+ context = self.get_all_variables()
181
+
182
+ try:
183
+ query_name = node.query_name if hasattr(node, 'query_name') else node.var_name
184
+ query_data = context.get(query_name)
185
+
186
+ if query_data is None:
187
+ query_data = exec_context.get_variable(query_name)
188
+
189
+ if not query_data:
190
+ raise ExecutorError(f"Query '{query_name}' not found in context")
191
+
192
+ if not isinstance(query_data, list):
193
+ raise ExecutorError(f"Query '{query_name}' is not iterable")
194
+
195
+ for index, row in enumerate(query_data):
196
+ loop_context = context.copy()
197
+
198
+ # Make row fields accessible
199
+ if isinstance(row, dict):
200
+ for field_name, field_value in row.items():
201
+ dotted_key = f"{query_name}.{field_name}"
202
+ loop_context[dotted_key] = field_value
203
+ exec_context.set_variable(dotted_key, field_value, scope="local")
204
+ loop_context[field_name] = field_value
205
+ exec_context.set_variable(field_name, field_value, scope="local")
206
+
207
+ loop_context['currentRow'] = row
208
+ exec_context.set_variable('currentRow', row, scope="local")
209
+
210
+ if node.index_name:
211
+ loop_context[node.index_name] = index
212
+ exec_context.set_variable(node.index_name, index, scope="local")
213
+
214
+ for statement in node.body:
215
+ result = self._execute_body_statement(statement, loop_context, exec_context)
216
+ self._collect(results, statement, result)
217
+
218
+ return results
219
+
220
+ except Exception as e:
221
+ raise ExecutorError(f"Query loop error: {e}")
222
+
223
+ @staticmethod
224
+ def _collect(results: LoopReturns, statement, result) -> None:
225
+ """Guarda em `results` o que o statement retornou, e so isso.
226
+
227
+ Cada q:return executado vira um item — inclusive os de um loop
228
+ aninhado, que entram soltos, na ordem (docs/guide/loops.md, "Nested
229
+ Loops"). Um q:if cujo ramo retornou vira um item. O resto (linhas de
230
+ q:query, valores de q:set etc.) nao e return e nao entra.
231
+
232
+ So LoopReturns e achatado: `<q:return value="{lista}"/>` continua
233
+ sendo UM item, mesmo sendo uma lista.
234
+ """
235
+ if isinstance(statement, QuantumReturn):
236
+ if result is not None:
237
+ results.append(result)
238
+ elif isinstance(result, LoopReturns):
239
+ results.extend(result)
240
+ elif produced_return(statement, result):
241
+ results.append(result)
242
+
243
+ def _execute_body_statement(self, statement, context: Dict[str, Any], exec_context) -> Any:
244
+ """Execute a statement inside a loop body"""
245
+ if isinstance(statement, QuantumReturn):
246
+ return self.resolve_value(statement.value, context)
247
+ elif isinstance(statement, _RENDER_ONLY_NODE_TYPES):
248
+ # Text/HTML content is handled by HTMLRenderer._render_loop(),
249
+ # which independently re-iterates and renders the body — it is
250
+ # not meant to be executed here.
251
+ return None
252
+ else:
253
+ # Use registry for all other statements
254
+ return self.execute_child(statement, exec_context)
255
+
256
+ def _evaluate_simple_expr(self, expr: str, context: Dict[str, Any]) -> Any:
257
+ """Evaluate simple numeric expression"""
258
+ if not expr:
259
+ return 0
260
+
261
+ try:
262
+ return int(expr)
263
+ except ValueError:
264
+ try:
265
+ return float(expr)
266
+ except ValueError:
267
+ return context.get(expr, expr)
268
+
269
+ def _parse_array_items(self, items_expr: str, context: Dict[str, Any]) -> list:
270
+ """Parse array items from expression"""
271
+ if not items_expr:
272
+ return []
273
+
274
+ # Apply databinding
275
+ if '{' in items_expr and '}' in items_expr:
276
+ resolved = self.apply_databinding(items_expr, context)
277
+ if isinstance(resolved, list):
278
+ return resolved
279
+ items_expr = str(resolved) if not isinstance(resolved, str) else resolved
280
+
281
+ # Handle JSON array notation
282
+ if items_expr.startswith('[') and items_expr.endswith(']'):
283
+ try:
284
+ return json.loads(items_expr)
285
+ except json.JSONDecodeError:
286
+ items_str = items_expr[1:-1]
287
+ return [item.strip().strip('"\'') for item in items_str.split(',') if item.strip()]
288
+
289
+ # Variable reference
290
+ if items_expr in context:
291
+ return context[items_expr]
292
+
293
+ return [items_expr]
294
+
295
+ def _parse_list_items(self, items_expr: str, delimiter: str, context: Dict[str, Any]) -> list:
296
+ """Parse list items from delimited string"""
297
+ if not items_expr:
298
+ return []
299
+
300
+ if items_expr in context:
301
+ items_value = context[items_expr]
302
+ if isinstance(items_value, list):
303
+ return items_value
304
+ elif isinstance(items_value, str):
305
+ return items_value.split(delimiter)
306
+
307
+ return items_expr.split(delimiter)