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,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Tag Parser - Abstract base class for all tag parsers
|
|
3
|
+
|
|
4
|
+
All parsers inherit from BaseTagParser and implement the parse() method.
|
|
5
|
+
The ParserRegistry uses the 'tag_names' property to dispatch parsing to the correct parser.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
10
|
+
from xml.etree import ElementTree as ET
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from quantum.core.ast_nodes import QuantumNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ParserError(Exception):
|
|
17
|
+
"""Base exception for parser errors"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseTagParser(ABC):
|
|
22
|
+
"""
|
|
23
|
+
Abstract base class for tag-specific parsers.
|
|
24
|
+
|
|
25
|
+
Each parser handles one or more XML tags and implements
|
|
26
|
+
the parse() method to convert them to AST nodes.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
class LoopParser(BaseTagParser):
|
|
30
|
+
@property
|
|
31
|
+
def tag_names(self) -> List[str]:
|
|
32
|
+
return ['loop']
|
|
33
|
+
|
|
34
|
+
def parse(self, element: ET.Element) -> LoopNode:
|
|
35
|
+
# Parse loop element
|
|
36
|
+
pass
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, main_parser: 'QuantumParser'):
|
|
40
|
+
"""
|
|
41
|
+
Initialize parser with reference to main parser.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
main_parser: The QuantumParser instance (provides helpers and registry)
|
|
45
|
+
"""
|
|
46
|
+
self._parser = main_parser
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def parser(self) -> 'QuantumParser':
|
|
50
|
+
"""Access to the main parser"""
|
|
51
|
+
return self._parser
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def tag_names(self) -> List[str]:
|
|
56
|
+
"""
|
|
57
|
+
List of tag names this parser handles (without q: prefix).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of tag names (e.g., ['loop', 'for', 'while'])
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def parse(self, element: ET.Element) -> 'QuantumNode':
|
|
66
|
+
"""
|
|
67
|
+
Parse the element and return AST node.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
element: XML element to parse
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
AST node representing the element
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
# ==========================================================================
|
|
78
|
+
# Helper methods - Common utilities available to all parsers
|
|
79
|
+
# ==========================================================================
|
|
80
|
+
|
|
81
|
+
def get_element_name(self, element: ET.Element) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Get element name without namespace prefix.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
element: XML element
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tag name without namespace
|
|
90
|
+
"""
|
|
91
|
+
return self._parser._get_element_name(element)
|
|
92
|
+
|
|
93
|
+
def find_element(self, parent: ET.Element, tag: str) -> Optional[ET.Element]:
|
|
94
|
+
"""
|
|
95
|
+
Find a child element by tag name.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
parent: Parent element
|
|
99
|
+
tag: Tag name to find
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Element or None
|
|
103
|
+
"""
|
|
104
|
+
return self._parser._find_element(parent, tag)
|
|
105
|
+
|
|
106
|
+
def find_all_elements(self, parent: ET.Element, tag: str) -> List[ET.Element]:
|
|
107
|
+
"""
|
|
108
|
+
Find all child elements by tag name.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
parent: Parent element
|
|
112
|
+
tag: Tag name to find
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of elements
|
|
116
|
+
"""
|
|
117
|
+
return self._parser._find_all_elements(parent, tag)
|
|
118
|
+
|
|
119
|
+
def parse_statement(self, element: ET.Element) -> Optional['QuantumNode']:
|
|
120
|
+
"""
|
|
121
|
+
Parse a child statement using the registry.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
element: Child element to parse
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
AST node or None
|
|
128
|
+
"""
|
|
129
|
+
return self._parser._parse_statement(element)
|
|
130
|
+
|
|
131
|
+
def parse_statements(self, parent: ET.Element) -> List['QuantumNode']:
|
|
132
|
+
"""
|
|
133
|
+
Parse all direct children as statements.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
parent: Parent element
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of AST nodes
|
|
140
|
+
"""
|
|
141
|
+
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
142
|
+
|
|
143
|
+
statements = []
|
|
144
|
+
for child in parent:
|
|
145
|
+
nome = self.get_element_name(child)
|
|
146
|
+
|
|
147
|
+
# `<q:else>` / `<q:elseif>` como IRMAO de `<q:if>`.
|
|
148
|
+
#
|
|
149
|
+
# A forma que funcionava era so a ANINHADA (o else dentro do if);
|
|
150
|
+
# a irma — `</q:if>` e depois `<q:else>` — nao tinha parser
|
|
151
|
+
# registrado (IfParser.tag_names = ['if']), caia no fallback e
|
|
152
|
+
# era DESCARTADA sem uma palavra. A documentacao ensinava a forma
|
|
153
|
+
# irma em dezenas de exemplos (inclusive o passo 3 do quick-start)
|
|
154
|
+
# e 7 arquivos .q entregues a usavam, entao o else simplesmente
|
|
155
|
+
# nao acontecia e ninguem era avisado.
|
|
156
|
+
#
|
|
157
|
+
# Agora um else/elseif que segue um q:if e anexado a ele — as duas
|
|
158
|
+
# formas passam a significar a mesma coisa. Um else/elseif SEM um
|
|
159
|
+
# if antes e um erro de verdade, e agora e dito em voz alta.
|
|
160
|
+
if nome in ('else', 'elseif'):
|
|
161
|
+
anterior = statements[-1] if statements else None
|
|
162
|
+
if isinstance(anterior, IfNode):
|
|
163
|
+
self._attach_else_branch(anterior, nome, child)
|
|
164
|
+
continue
|
|
165
|
+
from quantum.core.parser import QuantumParseError
|
|
166
|
+
raise QuantumParseError(
|
|
167
|
+
f"<q:{nome}> has no matching <q:if> before it. "
|
|
168
|
+
f"Put it inside the if, or right after it: "
|
|
169
|
+
f"<q:if ...>...</q:if> <q:{nome}>...</q:{nome}>"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
node = self.parse_statement(child)
|
|
173
|
+
if node is not None:
|
|
174
|
+
statements.append(node)
|
|
175
|
+
return statements
|
|
176
|
+
|
|
177
|
+
def _attach_else_branch(self, if_node, kind: str, element: 'ET.Element'):
|
|
178
|
+
"""Anexa um else/elseif IRMAO ao IfNode anterior — mesmo resultado do
|
|
179
|
+
aninhado, que o IfParser ja monta com estes mesmos metodos."""
|
|
180
|
+
if kind == 'elseif':
|
|
181
|
+
condicao = self.get_attr(element, 'condition', '')
|
|
182
|
+
corpo = []
|
|
183
|
+
for filho in element:
|
|
184
|
+
stmt = self.parse_statement(filho)
|
|
185
|
+
if stmt:
|
|
186
|
+
corpo.append(stmt)
|
|
187
|
+
if_node.add_elseif_block(condicao, corpo)
|
|
188
|
+
else:
|
|
189
|
+
for filho in element:
|
|
190
|
+
stmt = self.parse_statement(filho)
|
|
191
|
+
if stmt:
|
|
192
|
+
if_node.add_else_statement(stmt)
|
|
193
|
+
|
|
194
|
+
def get_attr(self, element: ET.Element, name: str, default: str = None) -> Optional[str]:
|
|
195
|
+
"""
|
|
196
|
+
Get element attribute with optional default.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
element: XML element
|
|
200
|
+
name: Attribute name
|
|
201
|
+
default: Default value
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Attribute value or default
|
|
205
|
+
"""
|
|
206
|
+
return element.get(name, default)
|
|
207
|
+
|
|
208
|
+
def get_bool_attr(self, element: ET.Element, name: str, default: bool = False) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Get boolean attribute.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
element: XML element
|
|
214
|
+
name: Attribute name
|
|
215
|
+
default: Default value
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Boolean value
|
|
219
|
+
"""
|
|
220
|
+
value = element.get(name, '').lower()
|
|
221
|
+
if value in ('true', '1', 'yes'):
|
|
222
|
+
return True
|
|
223
|
+
if value in ('false', '0', 'no'):
|
|
224
|
+
return False
|
|
225
|
+
return default
|
|
226
|
+
|
|
227
|
+
def get_int_attr(self, element: ET.Element, name: str, default: int = 0) -> int:
|
|
228
|
+
"""
|
|
229
|
+
Get integer attribute.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
element: XML element
|
|
233
|
+
name: Attribute name
|
|
234
|
+
default: Default value
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Integer value
|
|
238
|
+
"""
|
|
239
|
+
try:
|
|
240
|
+
return int(element.get(name, default))
|
|
241
|
+
except (ValueError, TypeError):
|
|
242
|
+
return default
|
|
243
|
+
|
|
244
|
+
def get_text(self, element: ET.Element) -> str:
|
|
245
|
+
"""
|
|
246
|
+
Get element text content.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
element: XML element
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Text content (empty string if None)
|
|
253
|
+
"""
|
|
254
|
+
return element.text or ''
|
|
255
|
+
|
|
256
|
+
def require_attr(self, element: ET.Element, name: str) -> str:
|
|
257
|
+
"""
|
|
258
|
+
Get required attribute, raise error if missing.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
element: XML element
|
|
262
|
+
name: Attribute name
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Attribute value
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ParserError: If attribute is missing
|
|
269
|
+
"""
|
|
270
|
+
value = element.get(name)
|
|
271
|
+
if value is None:
|
|
272
|
+
tag = self.get_element_name(element)
|
|
273
|
+
raise ParserError(f"<q:{tag}> requires '{name}' attribute")
|
|
274
|
+
return value
|
|
275
|
+
|
|
276
|
+
def is_html_element(self, element: ET.Element) -> bool:
|
|
277
|
+
"""
|
|
278
|
+
Check if element is an HTML element.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
element: XML element
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if HTML element
|
|
285
|
+
"""
|
|
286
|
+
return self._parser._is_html_element(element)
|
|
287
|
+
|
|
288
|
+
def parse_param(self, element: ET.Element) -> 'QuantumParam':
|
|
289
|
+
"""
|
|
290
|
+
Parse a q:param element.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
element: Param element
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
QuantumParam node
|
|
297
|
+
"""
|
|
298
|
+
return self._parser._parse_param(element)
|
|
299
|
+
|
|
300
|
+
def parse_child(self, element: ET.Element) -> Optional['QuantumNode']:
|
|
301
|
+
"""
|
|
302
|
+
Alias for parse_statement. Parse a child element using the registry.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
element: Child element to parse
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
AST node or None
|
|
309
|
+
"""
|
|
310
|
+
return self.parse_statement(element)
|
|
311
|
+
|
|
312
|
+
def _get_element_name(self, element: ET.Element) -> str:
|
|
313
|
+
"""
|
|
314
|
+
Alias for get_element_name. Get element name without namespace prefix.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
element: XML element
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Tag name without namespace
|
|
321
|
+
"""
|
|
322
|
+
return self.get_element_name(element)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Import Parser - Parse q:import statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import ImportNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImportParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:import statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:import component="Header" />
|
|
17
|
+
<q:import component="Button" from="./ui" />
|
|
18
|
+
<q:import component="AdminLayout" as="Layout" />
|
|
19
|
+
|
|
20
|
+
Game Engine Imports:
|
|
21
|
+
<q:import behavior="PlayerBehavior" from="./behaviors" />
|
|
22
|
+
<q:import prefab="KoopaGreen" from="./prefabs" />
|
|
23
|
+
<q:import tilemap="yi1" from="./levels" />
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def tag_names(self) -> List[str]:
|
|
28
|
+
return ['import']
|
|
29
|
+
|
|
30
|
+
def parse(self, element: ET.Element) -> ImportNode:
|
|
31
|
+
"""Parse q:import element"""
|
|
32
|
+
component = element.get('component')
|
|
33
|
+
behavior = element.get('behavior')
|
|
34
|
+
prefab = element.get('prefab')
|
|
35
|
+
tilemap = element.get('tilemap')
|
|
36
|
+
from_path = element.get('from')
|
|
37
|
+
alias = element.get('as')
|
|
38
|
+
|
|
39
|
+
if not component and not behavior and not prefab and not tilemap:
|
|
40
|
+
from quantum.core.parser import QuantumParseError
|
|
41
|
+
raise QuantumParseError("q:import requires one of 'component', 'behavior', 'prefab', or 'tilemap' attribute")
|
|
42
|
+
|
|
43
|
+
return ImportNode(
|
|
44
|
+
component=component,
|
|
45
|
+
from_path=from_path,
|
|
46
|
+
alias=alias,
|
|
47
|
+
behavior=behavior,
|
|
48
|
+
prefab=prefab,
|
|
49
|
+
tilemap=tilemap
|
|
50
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Slot Parser - Parse q:slot statements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
8
|
+
from quantum.core.ast_nodes import SlotNode, TextNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SlotParser(BaseTagParser):
|
|
12
|
+
"""
|
|
13
|
+
Parser for q:slot statements.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
<q:slot /> <!-- Default slot -->
|
|
17
|
+
<q:slot name="header" />
|
|
18
|
+
<q:slot name="footer">
|
|
19
|
+
<p>Default footer content</p>
|
|
20
|
+
</q:slot>
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['slot']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> SlotNode:
|
|
28
|
+
"""Parse q:slot element"""
|
|
29
|
+
name = element.get('name', 'default')
|
|
30
|
+
|
|
31
|
+
# Parse default content (children of slot)
|
|
32
|
+
default_content = []
|
|
33
|
+
|
|
34
|
+
if element.text and element.text.strip():
|
|
35
|
+
default_content.append(TextNode(element.text))
|
|
36
|
+
|
|
37
|
+
for child in element:
|
|
38
|
+
child_node = self.parse_child(child)
|
|
39
|
+
if child_node:
|
|
40
|
+
default_content.append(child_node)
|
|
41
|
+
|
|
42
|
+
if child.tail and child.tail.strip():
|
|
43
|
+
default_content.append(TextNode(child.tail))
|
|
44
|
+
|
|
45
|
+
return SlotNode(
|
|
46
|
+
name=name,
|
|
47
|
+
default_content=default_content
|
|
48
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
If Parser - Parse q:if statements
|
|
3
|
+
|
|
4
|
+
Handles conditional statements with elseif and else blocks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
10
|
+
from quantum.core.features.conditionals.src.ast_node import IfNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IfParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:if statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Simple if conditions
|
|
19
|
+
- Multiple elseif blocks
|
|
20
|
+
- Optional else block
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['if']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> IfNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:if statement with elseif and else blocks.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:if
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
IfNode AST node
|
|
36
|
+
"""
|
|
37
|
+
condition = self.get_attr(element, 'condition', '')
|
|
38
|
+
if_node = IfNode(condition)
|
|
39
|
+
|
|
40
|
+
for child in element:
|
|
41
|
+
child_type = self.get_element_name(child)
|
|
42
|
+
|
|
43
|
+
if child_type == 'elseif':
|
|
44
|
+
# Parse elseif block
|
|
45
|
+
elseif_condition = self.get_attr(child, 'condition', '')
|
|
46
|
+
elseif_body = []
|
|
47
|
+
|
|
48
|
+
for elseif_child in child:
|
|
49
|
+
statement = self.parse_statement(elseif_child)
|
|
50
|
+
if statement:
|
|
51
|
+
elseif_body.append(statement)
|
|
52
|
+
|
|
53
|
+
if_node.add_elseif_block(elseif_condition, elseif_body)
|
|
54
|
+
|
|
55
|
+
elif child_type == 'else':
|
|
56
|
+
# Parse else block
|
|
57
|
+
for else_child in child:
|
|
58
|
+
statement = self.parse_statement(else_child)
|
|
59
|
+
if statement:
|
|
60
|
+
if_node.add_else_statement(statement)
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
# Main if block statement
|
|
64
|
+
statement = self.parse_statement(child)
|
|
65
|
+
if statement:
|
|
66
|
+
if_node.add_if_statement(statement)
|
|
67
|
+
|
|
68
|
+
return if_node
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loop Parser - Parse q:loop statements
|
|
3
|
+
|
|
4
|
+
Handles various loop types: range, array, list, query.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from quantum.core.parsers.base import BaseTagParser, ParserError
|
|
10
|
+
from quantum.core.features.loops.src.ast_node import LoopNode
|
|
11
|
+
from quantum.core.ast_nodes import TextNode
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoopParser(BaseTagParser):
|
|
15
|
+
"""
|
|
16
|
+
Parser for q:loop statements.
|
|
17
|
+
|
|
18
|
+
Supports:
|
|
19
|
+
- Range loops (from/to)
|
|
20
|
+
- Array iteration
|
|
21
|
+
- List iteration (with delimiter)
|
|
22
|
+
- Query result iteration
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def tag_names(self) -> List[str]:
|
|
27
|
+
return ['loop']
|
|
28
|
+
|
|
29
|
+
def parse(self, element: ET.Element) -> LoopNode:
|
|
30
|
+
"""
|
|
31
|
+
Parse q:loop statement with various types.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
element: XML element for q:loop
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
LoopNode AST node
|
|
38
|
+
"""
|
|
39
|
+
# Check for query attribute (shorthand syntax)
|
|
40
|
+
query_attr = self.get_attr(element, 'query')
|
|
41
|
+
|
|
42
|
+
if query_attr:
|
|
43
|
+
# <q:loop query="users"> shorthand: the query name doubles as the
|
|
44
|
+
# row variable, so the body reads {users.name}.
|
|
45
|
+
#
|
|
46
|
+
# `var=` is honoured here when given. It used to be read only in
|
|
47
|
+
# the branch below, so <q:loop query="projects" var="p"> silently
|
|
48
|
+
# discarded the name and {p.name} rendered as literal text with no
|
|
49
|
+
# error anywhere — found by writing a real screen against it
|
|
50
|
+
# (FRAMEWORK_PLAN.md Fase 4). Omitting var= keeps the old
|
|
51
|
+
# behaviour exactly.
|
|
52
|
+
loop_type = 'query'
|
|
53
|
+
var_name = self.get_attr(element, 'var') or query_attr
|
|
54
|
+
loop_node = LoopNode(loop_type, var_name)
|
|
55
|
+
loop_node.query_name = query_attr
|
|
56
|
+
else:
|
|
57
|
+
# Traditional syntax
|
|
58
|
+
loop_type = self.get_attr(element, 'type')
|
|
59
|
+
if not loop_type:
|
|
60
|
+
# No explicit type: infer from attributes present instead of
|
|
61
|
+
# always defaulting to 'range'. A bare <q:loop items="..."
|
|
62
|
+
# var="..."> with no type= used to be misclassified as a range
|
|
63
|
+
# loop and fail validation ("Range loop requires 'from'
|
|
64
|
+
# attribute") — confirmed breaking examples in multiple
|
|
65
|
+
# unrelated features (agents, data_import, scripting).
|
|
66
|
+
loop_type = 'array' if self.get_attr(element, 'items') else 'range'
|
|
67
|
+
var_name = self.get_attr(element, 'var')
|
|
68
|
+
|
|
69
|
+
if not var_name:
|
|
70
|
+
raise ParserError("Loop requires 'var' attribute")
|
|
71
|
+
|
|
72
|
+
loop_node = LoopNode(loop_type, var_name)
|
|
73
|
+
|
|
74
|
+
# Configure based on loop type
|
|
75
|
+
if loop_type == 'range':
|
|
76
|
+
loop_node.from_value = self.get_attr(element, 'from')
|
|
77
|
+
loop_node.to_value = self.get_attr(element, 'to')
|
|
78
|
+
loop_node.step_value = self.get_int_attr(element, 'step', 1)
|
|
79
|
+
|
|
80
|
+
elif loop_type == 'array':
|
|
81
|
+
loop_node.items = self.get_attr(element, 'items')
|
|
82
|
+
loop_node.index_name = self.get_attr(element, 'index')
|
|
83
|
+
|
|
84
|
+
elif loop_type == 'list':
|
|
85
|
+
loop_node.items = self.get_attr(element, 'items')
|
|
86
|
+
loop_node.delimiter = self.get_attr(element, 'delimiter', ',')
|
|
87
|
+
loop_node.index_name = self.get_attr(element, 'index')
|
|
88
|
+
|
|
89
|
+
elif loop_type == 'query':
|
|
90
|
+
loop_node.index_name = self.get_attr(element, 'index')
|
|
91
|
+
|
|
92
|
+
# Parse loop body - add text content before first child
|
|
93
|
+
if element.text and element.text.strip():
|
|
94
|
+
loop_node.add_statement(TextNode(element.text))
|
|
95
|
+
|
|
96
|
+
# Parse loop body statements
|
|
97
|
+
for child in element:
|
|
98
|
+
statement = self.parse_statement(child)
|
|
99
|
+
if statement:
|
|
100
|
+
loop_node.add_statement(statement)
|
|
101
|
+
# Add tail text after child element
|
|
102
|
+
if child.tail and child.tail.strip():
|
|
103
|
+
loop_node.add_statement(TextNode(child.tail))
|
|
104
|
+
|
|
105
|
+
return loop_node
|