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,319 @@
1
+ """
2
+ Quantum Component Composer
3
+
4
+ Phase 2: Component Composition
5
+ Orchestrates component rendering with props and slots.
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Dict, List, Any, Optional
11
+
12
+ # Fix imports
13
+
14
+ from quantum.core.ast_nodes import (
15
+ ComponentNode, ComponentCallNode, SlotNode, QuantumNode,
16
+ HTMLNode, TextNode, ImportNode
17
+ )
18
+ from quantum.runtime.component_resolver import ComponentResolver, ComponentNotFoundError
19
+ from quantum.runtime.component import ComponentRuntime
20
+ from quantum.runtime.execution_context import ExecutionContext
21
+ from quantum.runtime.renderer import HTMLRenderer
22
+
23
+
24
+ class ComponentComposer:
25
+ """
26
+ Composes components with props and slots.
27
+
28
+ Phase 2: Component Composition
29
+
30
+ Responsibilities:
31
+ - Load child components
32
+ - Pass props from parent to child
33
+ - Inject slot content
34
+ - Render composed output
35
+ """
36
+
37
+ def __init__(self, resolver: ComponentResolver):
38
+ self.resolver = resolver
39
+
40
+ def compose(
41
+ self,
42
+ component_call: ComponentCallNode,
43
+ parent_context: ExecutionContext
44
+ ) -> str:
45
+ """
46
+ Compose and render a component call.
47
+
48
+ Args:
49
+ component_call: The <ComponentName /> call
50
+ parent_context: Parent component's execution context
51
+
52
+ Returns:
53
+ Rendered HTML string
54
+ """
55
+
56
+ # 1. Resolve component
57
+ try:
58
+ metadata = self.resolver.resolve(component_call.component_name)
59
+ except ComponentNotFoundError as e:
60
+ # Return error placeholder
61
+ return f'<!-- Component Error: {e} -->'
62
+
63
+ child_component = metadata.ast
64
+
65
+ # 2. Prepare props (merge call props + defaults)
66
+ props = self._prepare_props(child_component, component_call.props, parent_context)
67
+
68
+ # 3. Execute child component with props
69
+ runtime = ComponentRuntime()
70
+ runtime.execute_component(child_component, props)
71
+
72
+ # 4. Replace slots with content from parent
73
+ processed_ast = self._process_slots(
74
+ child_component,
75
+ component_call.children,
76
+ parent_context
77
+ )
78
+
79
+ # 5. Render child component
80
+ renderer = HTMLRenderer(runtime.execution_context)
81
+ html = renderer.render(processed_ast)
82
+
83
+ return html
84
+
85
+ def _prepare_props(
86
+ self,
87
+ child_component: ComponentNode,
88
+ call_props: Dict[str, str],
89
+ parent_context: ExecutionContext
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Prepare props for child component.
93
+
94
+ 1. Get component parameter definitions
95
+ 2. Merge with call props
96
+ 3. Apply databinding from parent context
97
+ 4. Validate required props
98
+ """
99
+
100
+ props = {}
101
+
102
+ # Process each parameter definition
103
+ for param_def in child_component.params:
104
+ param_name = param_def.name
105
+
106
+ # Get value from call props or use default
107
+ if param_name in call_props:
108
+ value = call_props[param_name]
109
+
110
+ # Apply databinding from parent context
111
+ value = self._apply_databinding_from_context(value, parent_context)
112
+
113
+ props[param_name] = value
114
+
115
+ elif param_def.default is not None:
116
+ props[param_name] = param_def.default
117
+
118
+ elif param_def.required:
119
+ # Required prop missing!
120
+ raise ComponentCompositionError(
121
+ f"Required prop '{param_name}' missing for component '{child_component.name}'"
122
+ )
123
+
124
+ return props
125
+
126
+ def _apply_databinding_from_context(self, value: str, context: ExecutionContext) -> Any:
127
+ """
128
+ Apply databinding from parent context.
129
+
130
+ Examples:
131
+ "{user.name}" → "John Doe"
132
+ "{product.price}" → 29.99
133
+ "Hello {name}!" → "Hello World!"
134
+ """
135
+
136
+ if not isinstance(value, str):
137
+ return value
138
+
139
+ # Check if entire value is a binding expression
140
+ if value.startswith('{') and value.endswith('}'):
141
+ expression = value[1:-1].strip()
142
+ try:
143
+ return context.get_variable(expression)
144
+ except:
145
+ return value
146
+
147
+ # Mixed content - apply string interpolation
148
+ import re
149
+ pattern = r'\{([^}]+)\}'
150
+
151
+ def replace_binding(match):
152
+ expr = match.group(1).strip()
153
+ try:
154
+ result = context.get_variable(expr)
155
+ return str(result) if result is not None else ''
156
+ except:
157
+ return match.group(0)
158
+
159
+ return re.sub(pattern, replace_binding, value)
160
+
161
+ def _process_slots(
162
+ self,
163
+ child_component: ComponentNode,
164
+ slot_content: List[QuantumNode],
165
+ parent_context: ExecutionContext
166
+ ) -> ComponentNode:
167
+ """
168
+ Replace <q:slot> nodes with content from parent.
169
+
170
+ Args:
171
+ child_component: Child component AST
172
+ slot_content: Children from parent's component call
173
+ parent_context: Parent's execution context
174
+
175
+ Returns:
176
+ Modified component AST with slots filled
177
+ """
178
+
179
+ # Pre-process slot content to apply databinding from parent context
180
+ processed_slot_content = self._apply_parent_databinding_to_nodes(
181
+ slot_content,
182
+ parent_context
183
+ )
184
+
185
+ # Build slot content map: {slot_name: [nodes]}
186
+ slot_content_map = {
187
+ 'default': processed_slot_content
188
+ }
189
+
190
+ # TODO: Support named slots
191
+ # For now, all content goes to default slot
192
+
193
+ # Traverse child component and replace SlotNodes
194
+ processed_statements = self._replace_slots_in_nodes(
195
+ child_component.statements,
196
+ slot_content_map,
197
+ parent_context
198
+ )
199
+
200
+ # Create modified component
201
+ modified_component = ComponentNode(
202
+ name=child_component.name,
203
+ component_type=child_component.component_type
204
+ )
205
+ modified_component.statements = processed_statements
206
+ modified_component.params = child_component.params
207
+ modified_component.returns = child_component.returns
208
+ modified_component.functions = child_component.functions
209
+
210
+ return modified_component
211
+
212
+ def _replace_slots_in_nodes(
213
+ self,
214
+ nodes: List[QuantumNode],
215
+ slot_content_map: Dict[str, List[QuantumNode]],
216
+ parent_context: ExecutionContext
217
+ ) -> List[QuantumNode]:
218
+ """
219
+ Recursively replace SlotNodes in node tree.
220
+ """
221
+
222
+ result = []
223
+
224
+ for node in nodes:
225
+ if isinstance(node, SlotNode):
226
+ # Replace with slot content
227
+ slot_name = node.name
228
+ content = slot_content_map.get(slot_name, node.default_content)
229
+ result.extend(content)
230
+
231
+ elif isinstance(node, HTMLNode):
232
+ # Process children recursively
233
+ processed_children = self._replace_slots_in_nodes(
234
+ node.children,
235
+ slot_content_map,
236
+ parent_context
237
+ )
238
+ node.children = processed_children
239
+ result.append(node)
240
+
241
+ else:
242
+ # Keep node as-is
243
+ result.append(node)
244
+
245
+ return result
246
+
247
+ def _apply_parent_databinding_to_nodes(
248
+ self,
249
+ nodes: List[QuantumNode],
250
+ parent_context: ExecutionContext
251
+ ) -> List[QuantumNode]:
252
+ """
253
+ Apply databinding from parent context to nodes recursively.
254
+
255
+ This ensures that slot content uses parent's variables, not child's.
256
+
257
+ Args:
258
+ nodes: List of nodes (TextNode, HTMLNode, etc)
259
+ parent_context: Parent component's execution context
260
+
261
+ Returns:
262
+ Nodes with databinding applied
263
+ """
264
+ from quantum.core.ast_nodes import TextNode, HTMLNode
265
+
266
+ result = []
267
+
268
+ for node in nodes:
269
+ if isinstance(node, TextNode):
270
+ # Apply databinding to text content
271
+ if node.has_databinding:
272
+ processed_content = self._apply_databinding_from_context(
273
+ node.content,
274
+ parent_context
275
+ )
276
+ # Create new TextNode with resolved content
277
+ new_node = TextNode(processed_content)
278
+ new_node.has_databinding = False # Already resolved
279
+ result.append(new_node)
280
+ else:
281
+ result.append(node)
282
+
283
+ elif isinstance(node, HTMLNode):
284
+ # Process children recursively
285
+ processed_children = self._apply_parent_databinding_to_nodes(
286
+ node.children,
287
+ parent_context
288
+ )
289
+
290
+ # Process attributes (for databinding in attributes)
291
+ processed_attrs = {}
292
+ for key, value in node.attributes.items():
293
+ if isinstance(value, str) and '{' in value:
294
+ processed_attrs[key] = self._apply_databinding_from_context(
295
+ value,
296
+ parent_context
297
+ )
298
+ else:
299
+ processed_attrs[key] = value
300
+
301
+ # Create new HTMLNode with processed content
302
+ new_node = HTMLNode(
303
+ tag=node.tag,
304
+ attributes=processed_attrs,
305
+ children=processed_children,
306
+ self_closing=node.self_closing
307
+ )
308
+ result.append(new_node)
309
+
310
+ else:
311
+ # Other node types - pass through
312
+ result.append(node)
313
+
314
+ return result
315
+
316
+
317
+ class ComponentCompositionError(Exception):
318
+ """Error during component composition"""
319
+ pass
@@ -0,0 +1,174 @@
1
+ """
2
+ Quantum Component Resolver
3
+
4
+ Phase 2: Component Composition
5
+ Responsible for finding, loading, and caching imported components.
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Dict, Optional, List
11
+ from dataclasses import dataclass
12
+
13
+ # Fix imports
14
+
15
+ from quantum.core.parser import QuantumParser, QuantumParseError
16
+ from quantum.core.ast_nodes import ComponentNode, ImportNode
17
+
18
+
19
+ @dataclass
20
+ class ComponentMetadata:
21
+ """Metadata about a resolved component"""
22
+ name: str
23
+ path: Path
24
+ ast: ComponentNode
25
+ imports: List[ImportNode]
26
+
27
+
28
+ class ComponentResolver:
29
+ """
30
+ Resolves and loads Quantum components.
31
+
32
+ Phase 2: Component Composition
33
+
34
+ Responsibilities:
35
+ - Find component .q files by name
36
+ - Load and parse components
37
+ - Cache parsed components
38
+ - Resolve component dependencies
39
+ """
40
+
41
+ def __init__(self, components_dir: str = "./components"):
42
+ self.components_dir = Path(components_dir)
43
+ self.parser = QuantumParser()
44
+
45
+ # Component cache: {name: ComponentMetadata}
46
+ self.cache: Dict[str, ComponentMetadata] = {}
47
+
48
+ def resolve(self, component_name: str, from_path: Optional[str] = None) -> ComponentMetadata:
49
+ """
50
+ Resolve component by name.
51
+
52
+ Args:
53
+ component_name: Name of component (e.g., "Header", "Button")
54
+ from_path: Optional relative path (e.g., "./ui", "../shared")
55
+
56
+ Returns:
57
+ ComponentMetadata with AST and metadata
58
+
59
+ Raises:
60
+ ComponentNotFoundError: If component file doesn't exist
61
+ """
62
+
63
+ # Check cache first
64
+ cache_key = f"{from_path or 'default'}:{component_name}"
65
+ if cache_key in self.cache:
66
+ return self.cache[cache_key]
67
+
68
+ # Find component file
69
+ component_path = self._find_component_file(component_name, from_path)
70
+
71
+ if not component_path.exists():
72
+ raise ComponentNotFoundError(
73
+ f"Component '{component_name}' not found. "
74
+ f"Searched: {component_path}"
75
+ )
76
+
77
+ # Parse component
78
+ try:
79
+ ast = self.parser.parse_file(str(component_path))
80
+
81
+ if not isinstance(ast, ComponentNode):
82
+ raise ComponentNotFoundError(
83
+ f"File '{component_path}' is not a component "
84
+ f"(got {type(ast).__name__})"
85
+ )
86
+
87
+ # Extract imports from component
88
+ imports = [stmt for stmt in ast.statements if isinstance(stmt, ImportNode)]
89
+
90
+ # Create metadata
91
+ metadata = ComponentMetadata(
92
+ name=component_name,
93
+ path=component_path,
94
+ ast=ast,
95
+ imports=imports
96
+ )
97
+
98
+ # Cache it
99
+ self.cache[cache_key] = metadata
100
+
101
+ return metadata
102
+
103
+ except QuantumParseError as e:
104
+ raise ComponentNotFoundError(
105
+ f"Failed to parse component '{component_name}': {e}"
106
+ )
107
+
108
+ def _find_component_file(self, component_name: str, from_path: Optional[str] = None) -> Path:
109
+ """
110
+ Find component .q file.
111
+
112
+ Resolution order:
113
+ 1. If from_path provided: {from_path}/{ComponentName}.q
114
+ 2. Default: {components_dir}/{ComponentName}.q
115
+ 3. Try lowercase: {components_dir}/{componentname}.q
116
+ 4. Try snake_case: {components_dir}/{component_name}.q
117
+ """
118
+
119
+ # Try from_path first if provided
120
+ if from_path:
121
+ from_dir = self.components_dir / from_path.lstrip('./')
122
+ path = from_dir / f"{component_name}.q"
123
+ if path.exists():
124
+ return path
125
+
126
+ # Try exact name
127
+ path = self.components_dir / f"{component_name}.q"
128
+ if path.exists():
129
+ return path
130
+
131
+ # Try lowercase
132
+ path = self.components_dir / f"{component_name.lower()}.q"
133
+ if path.exists():
134
+ return path
135
+
136
+ # Try snake_case conversion
137
+ snake_case_name = self._to_snake_case(component_name)
138
+ path = self.components_dir / f"{snake_case_name}.q"
139
+ if path.exists():
140
+ return path
141
+
142
+ # Return original path (will trigger not found error)
143
+ return self.components_dir / f"{component_name}.q"
144
+
145
+ def _to_snake_case(self, name: str) -> str:
146
+ """
147
+ Convert PascalCase to snake_case.
148
+
149
+ Examples:
150
+ ProductCard → product_card
151
+ AdminLayout → admin_layout
152
+ UserProfileHeader → user_profile_header
153
+ """
154
+ import re
155
+ # Insert underscore before uppercase letters
156
+ result = re.sub(r'(?<!^)(?=[A-Z])', '_', name)
157
+ return result.lower()
158
+
159
+ def clear_cache(self):
160
+ """Clear component cache (useful for development hot-reload)"""
161
+ self.cache.clear()
162
+
163
+ def preload(self, component_names: List[str]):
164
+ """Preload components into cache"""
165
+ for name in component_names:
166
+ try:
167
+ self.resolve(name)
168
+ except ComponentNotFoundError:
169
+ pass # Skip missing components
170
+
171
+
172
+ class ComponentNotFoundError(Exception):
173
+ """Component file not found or invalid"""
174
+ pass