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,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser Registry - Central dispatch for tag parsing
|
|
3
|
+
|
|
4
|
+
Replaces the large if-elif chain in parser.py with a tag-based lookup.
|
|
5
|
+
Parsers register themselves for specific tag names, and the registry
|
|
6
|
+
dispatches parsing to the correct parser.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Optional, List, TYPE_CHECKING
|
|
10
|
+
from xml.etree import ElementTree as ET
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from quantum.core.parsers.base import BaseTagParser
|
|
15
|
+
from quantum.core.ast_nodes import QuantumNode
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ParserNotFoundError(Exception):
|
|
21
|
+
"""Raised when no parser is registered for a tag"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ParserRegistry:
|
|
26
|
+
"""
|
|
27
|
+
Registry for tag parsers.
|
|
28
|
+
|
|
29
|
+
Maps XML tag names to their parsers and dispatches parsing.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
registry = ParserRegistry()
|
|
33
|
+
registry.register(LoopParser(main_parser))
|
|
34
|
+
registry.register(IfParser(main_parser))
|
|
35
|
+
|
|
36
|
+
# Later, parse any element
|
|
37
|
+
node = registry.parse(element, 'loop')
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
"""Initialize empty registry"""
|
|
42
|
+
self._parsers: Dict[str, 'BaseTagParser'] = {}
|
|
43
|
+
self._html_parser: Optional['BaseTagParser'] = None
|
|
44
|
+
self._component_call_parser: Optional['BaseTagParser'] = None
|
|
45
|
+
|
|
46
|
+
def register(self, parser: 'BaseTagParser') -> 'ParserRegistry':
|
|
47
|
+
"""
|
|
48
|
+
Register a parser for its handled tags.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
parser: Parser instance to register
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Self for chaining
|
|
55
|
+
"""
|
|
56
|
+
for tag_name in parser.tag_names:
|
|
57
|
+
if tag_name in self._parsers:
|
|
58
|
+
logger.warning(
|
|
59
|
+
f"Overwriting parser for '{tag_name}': "
|
|
60
|
+
f"{self._parsers[tag_name].__class__.__name__} -> "
|
|
61
|
+
f"{parser.__class__.__name__}"
|
|
62
|
+
)
|
|
63
|
+
self._parsers[tag_name] = parser
|
|
64
|
+
logger.debug(f"Registered {parser.__class__.__name__} for '{tag_name}'")
|
|
65
|
+
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def register_all(self, parsers: List['BaseTagParser']) -> 'ParserRegistry':
|
|
69
|
+
"""
|
|
70
|
+
Register multiple parsers at once.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
parsers: List of parser instances
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Self for chaining
|
|
77
|
+
"""
|
|
78
|
+
for parser in parsers:
|
|
79
|
+
self.register(parser)
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def register_html_parser(self, parser: 'BaseTagParser') -> 'ParserRegistry':
|
|
83
|
+
"""
|
|
84
|
+
Register the HTML element parser.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
parser: HTML parser instance
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Self for chaining
|
|
91
|
+
"""
|
|
92
|
+
self._html_parser = parser
|
|
93
|
+
logger.debug(f"Registered HTML parser: {parser.__class__.__name__}")
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def register_component_call_parser(self, parser: 'BaseTagParser') -> 'ParserRegistry':
|
|
97
|
+
"""
|
|
98
|
+
Register the component call parser (for uppercase tags).
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
parser: Component call parser instance
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Self for chaining
|
|
105
|
+
"""
|
|
106
|
+
self._component_call_parser = parser
|
|
107
|
+
logger.debug(f"Registered component call parser: {parser.__class__.__name__}")
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def get_parser(self, tag_name: str) -> Optional['BaseTagParser']:
|
|
111
|
+
"""
|
|
112
|
+
Get the parser for a tag name.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
tag_name: Tag name (without q: prefix)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Parser instance or None if not found
|
|
119
|
+
"""
|
|
120
|
+
return self._parsers.get(tag_name)
|
|
121
|
+
|
|
122
|
+
def has_parser(self, tag_name: str) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Check if a parser is registered for a tag.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
tag_name: Tag name
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if parser exists
|
|
131
|
+
"""
|
|
132
|
+
return tag_name in self._parsers
|
|
133
|
+
|
|
134
|
+
def parse(self, element: ET.Element, tag_name: str) -> Optional['QuantumNode']:
|
|
135
|
+
"""
|
|
136
|
+
Parse an element using its registered parser.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
element: XML element to parse
|
|
140
|
+
tag_name: Tag name (without q: prefix)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
AST node or None
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ParserNotFoundError: If no parser is registered for the tag
|
|
147
|
+
"""
|
|
148
|
+
parser = self._parsers.get(tag_name)
|
|
149
|
+
|
|
150
|
+
if parser is not None:
|
|
151
|
+
# One-time notice if this tag is outside the supported surface
|
|
152
|
+
# (PRODUCTION_READINESS.md Fase 0). Experimental tags still parse
|
|
153
|
+
# and run; the operator just gets told, once, that they are
|
|
154
|
+
# leaning on something unsupported.
|
|
155
|
+
from quantum.core.tiers import warn_if_unsupported
|
|
156
|
+
warn_if_unsupported(tag_name)
|
|
157
|
+
return parser.parse(element)
|
|
158
|
+
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def parse_html(self, element: ET.Element) -> Optional['QuantumNode']:
|
|
162
|
+
"""
|
|
163
|
+
Parse an HTML element.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
element: HTML element to parse
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
AST node or None
|
|
170
|
+
"""
|
|
171
|
+
if self._html_parser is not None:
|
|
172
|
+
return self._html_parser.parse(element)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def parse_component_call(self, element: ET.Element) -> Optional['QuantumNode']:
|
|
176
|
+
"""
|
|
177
|
+
Parse a component call (uppercase tag).
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
element: Component call element
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
AST node or None
|
|
184
|
+
"""
|
|
185
|
+
if self._component_call_parser is not None:
|
|
186
|
+
return self._component_call_parser.parse(element)
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def is_uppercase(self, tag_name: str) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if tag is a component call (uppercase first letter).
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
tag_name: Tag name
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
True if uppercase
|
|
198
|
+
"""
|
|
199
|
+
return bool(tag_name and tag_name[0].isupper())
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def registered_tags(self) -> List[str]:
|
|
203
|
+
"""Get list of all registered tag names"""
|
|
204
|
+
return list(self._parsers.keys())
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def parser_count(self) -> int:
|
|
208
|
+
"""Get number of registered parsers (unique)"""
|
|
209
|
+
return len(set(self._parsers.values()))
|
|
210
|
+
|
|
211
|
+
def __repr__(self) -> str:
|
|
212
|
+
tags = list(self._parsers.keys())
|
|
213
|
+
return f"ParserRegistry({len(tags)} tags: {tags[:5]}{'...' if len(tags) > 5 else ''})"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Parsers Package
|
|
3
|
+
|
|
4
|
+
Modular parsers for XML tag parsing. Each parser handles specific tag types.
|
|
5
|
+
|
|
6
|
+
Categories:
|
|
7
|
+
- control_flow: if, loop, set
|
|
8
|
+
- data: query, invoke, data, transaction
|
|
9
|
+
- services: log, dump, file, mail
|
|
10
|
+
- ai: llm, agent, team, knowledge
|
|
11
|
+
- messaging: websocket, message, subscribe, queue
|
|
12
|
+
- jobs: schedule, thread, job
|
|
13
|
+
- scripting: python, pyimport, pyclass
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .base import BaseTagParser, ParserError
|
|
17
|
+
|
|
18
|
+
# Control flow parsers
|
|
19
|
+
from .control_flow import IfParser, LoopParser, SetParser
|
|
20
|
+
|
|
21
|
+
# Data parsers
|
|
22
|
+
from .data import QueryParser, InvokeParser, DataParser, TransactionParser
|
|
23
|
+
|
|
24
|
+
# Service parsers
|
|
25
|
+
from .services import LogParser, DumpParser, FileParser, MailParser
|
|
26
|
+
|
|
27
|
+
# AI parsers
|
|
28
|
+
from .ai import LLMParser, AgentParser, TeamParser, KnowledgeParser
|
|
29
|
+
|
|
30
|
+
# Messaging parsers
|
|
31
|
+
from .messaging import (
|
|
32
|
+
WebSocketParser, WebSocketSendParser, WebSocketCloseParser,
|
|
33
|
+
MessageParser, SubscribeParser, QueueParser
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Jobs parsers
|
|
37
|
+
from .jobs import ScheduleParser, ThreadParser, JobParser
|
|
38
|
+
|
|
39
|
+
# Scripting parsers
|
|
40
|
+
from .scripting import PythonParser, PyImportParser, PyClassParser
|
|
41
|
+
|
|
42
|
+
# HTML parsers
|
|
43
|
+
from .html import HTMLParser, ComponentCallParser
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# Base
|
|
48
|
+
'BaseTagParser', 'ParserError',
|
|
49
|
+
|
|
50
|
+
# Control flow
|
|
51
|
+
'IfParser', 'LoopParser', 'SetParser',
|
|
52
|
+
|
|
53
|
+
# Data
|
|
54
|
+
'QueryParser', 'InvokeParser', 'DataParser', 'TransactionParser',
|
|
55
|
+
|
|
56
|
+
# Services
|
|
57
|
+
'LogParser', 'DumpParser', 'FileParser', 'MailParser',
|
|
58
|
+
|
|
59
|
+
# AI
|
|
60
|
+
'LLMParser', 'AgentParser', 'TeamParser', 'KnowledgeParser',
|
|
61
|
+
|
|
62
|
+
# Messaging
|
|
63
|
+
'WebSocketParser', 'WebSocketSendParser', 'WebSocketCloseParser',
|
|
64
|
+
'MessageParser', 'SubscribeParser', 'QueueParser',
|
|
65
|
+
|
|
66
|
+
# Jobs
|
|
67
|
+
'ScheduleParser', 'ThreadParser', 'JobParser',
|
|
68
|
+
|
|
69
|
+
# Scripting
|
|
70
|
+
'PythonParser', 'PyImportParser', 'PyClassParser',
|
|
71
|
+
|
|
72
|
+
# HTML
|
|
73
|
+
'HTMLParser', 'ComponentCallParser',
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
__version__ = '1.0.0'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI Parsers
|
|
3
|
+
|
|
4
|
+
Parsers for AI operations: llm, agent, team, knowledge.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .llm_parser import LLMParser
|
|
8
|
+
from .agent_parser import AgentParser
|
|
9
|
+
from .team_parser import TeamParser
|
|
10
|
+
from .knowledge_parser import KnowledgeParser
|
|
11
|
+
|
|
12
|
+
__all__ = ['LLMParser', 'AgentParser', 'TeamParser', 'KnowledgeParser']
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Parser - Parse q:agent statements
|
|
3
|
+
|
|
4
|
+
Handles AI agent configuration with tools.
|
|
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.agents.src.ast_node import (
|
|
11
|
+
AgentNode, AgentToolNode, AgentToolParamNode,
|
|
12
|
+
AgentInstructionNode, AgentExecuteNode
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentParser(BaseTagParser):
|
|
17
|
+
"""
|
|
18
|
+
Parser for q:agent statements.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- Tool definitions
|
|
22
|
+
- System instructions
|
|
23
|
+
- Execute configuration
|
|
24
|
+
- Multiple providers
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def tag_names(self) -> List[str]:
|
|
29
|
+
return ['agent']
|
|
30
|
+
|
|
31
|
+
def parse(self, element: ET.Element) -> AgentNode:
|
|
32
|
+
"""
|
|
33
|
+
Parse q:agent statement.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
element: XML element for q:agent
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
AgentNode AST node
|
|
40
|
+
"""
|
|
41
|
+
name = self.get_attr(element, 'name')
|
|
42
|
+
|
|
43
|
+
if not name:
|
|
44
|
+
raise ParserError("Agent requires 'name' attribute")
|
|
45
|
+
|
|
46
|
+
agent_node = AgentNode(
|
|
47
|
+
name=name,
|
|
48
|
+
model=self.get_attr(element, 'model', 'phi3'),
|
|
49
|
+
endpoint=self.get_attr(element, 'endpoint', ''),
|
|
50
|
+
provider=self.get_attr(element, 'provider', 'auto'),
|
|
51
|
+
api_key=self.get_attr(element, 'apiKey', ''),
|
|
52
|
+
max_iterations=self.get_int_attr(element, 'maxIterations', 10),
|
|
53
|
+
timeout=self.get_int_attr(element, 'timeout', 60000)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Parse children
|
|
57
|
+
for child in element:
|
|
58
|
+
child_type = self.get_element_name(child)
|
|
59
|
+
|
|
60
|
+
if child_type == 'instruction':
|
|
61
|
+
agent_node.instruction = AgentInstructionNode(
|
|
62
|
+
content=self.get_text(child)
|
|
63
|
+
)
|
|
64
|
+
elif child_type == 'tool':
|
|
65
|
+
tool = self._parse_tool(child)
|
|
66
|
+
agent_node.tools.append(tool)
|
|
67
|
+
elif child_type == 'execute':
|
|
68
|
+
agent_node.execute = AgentExecuteNode(
|
|
69
|
+
task=self.get_attr(child, 'task', self.get_text(child)),
|
|
70
|
+
context=self.get_attr(child, 'context', ''),
|
|
71
|
+
entry=self.get_attr(child, 'entry', '')
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return agent_node
|
|
75
|
+
|
|
76
|
+
def _parse_tool(self, element: ET.Element) -> AgentToolNode:
|
|
77
|
+
"""Parse q:tool within q:agent."""
|
|
78
|
+
name = self.get_attr(element, 'name')
|
|
79
|
+
description = self.get_attr(element, 'description', '')
|
|
80
|
+
builtin = self.get_bool_attr(element, 'builtin', False)
|
|
81
|
+
|
|
82
|
+
tool = AgentToolNode(
|
|
83
|
+
name=name,
|
|
84
|
+
description=description,
|
|
85
|
+
builtin=builtin
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Parse params and body
|
|
89
|
+
for child in element:
|
|
90
|
+
child_type = self.get_element_name(child)
|
|
91
|
+
|
|
92
|
+
if child_type == 'param':
|
|
93
|
+
param = AgentToolParamNode(
|
|
94
|
+
name=self.get_attr(child, 'name', ''),
|
|
95
|
+
type=self.get_attr(child, 'type', 'string'),
|
|
96
|
+
required=self.get_bool_attr(child, 'required', False),
|
|
97
|
+
description=self.get_attr(child, 'description', '')
|
|
98
|
+
)
|
|
99
|
+
tool.params.append(param)
|
|
100
|
+
else:
|
|
101
|
+
# Tool body statement
|
|
102
|
+
statement = self.parse_statement(child)
|
|
103
|
+
if statement:
|
|
104
|
+
tool.body.append(statement)
|
|
105
|
+
|
|
106
|
+
return tool
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Knowledge Parser - Parse q:knowledge statements
|
|
3
|
+
|
|
4
|
+
Handles knowledge base configuration for RAG.
|
|
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.knowledge_base.src.ast_node import KnowledgeNode, KnowledgeSourceNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KnowledgeParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:knowledge statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Multiple source types
|
|
19
|
+
- Embedding configuration
|
|
20
|
+
- Persistence options
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['knowledge']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> KnowledgeNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:knowledge statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:knowledge
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
KnowledgeNode AST node
|
|
36
|
+
"""
|
|
37
|
+
name = self.get_attr(element, 'name')
|
|
38
|
+
|
|
39
|
+
if not name:
|
|
40
|
+
raise ParserError("Knowledge requires 'name' attribute")
|
|
41
|
+
|
|
42
|
+
knowledge_node = KnowledgeNode(
|
|
43
|
+
name=name,
|
|
44
|
+
model=self.get_attr(element, 'model'),
|
|
45
|
+
embed_model=self.get_attr(element, 'embedModel', 'nomic-embed-text'),
|
|
46
|
+
chunk_size=self.get_int_attr(element, 'chunkSize', 500),
|
|
47
|
+
chunk_overlap=self.get_int_attr(element, 'chunkOverlap', 50),
|
|
48
|
+
# Persist by default: re-embedding an unchanged corpus on every
|
|
49
|
+
# boot costs minutes of API calls and buys nothing. Opt out with
|
|
50
|
+
# persist="false" for throwaway/in-memory bases.
|
|
51
|
+
persist=self.get_bool_attr(element, 'persist', True),
|
|
52
|
+
persist_path=self.get_attr(element, 'persistPath', './.quantum/knowledge'),
|
|
53
|
+
rebuild=self.get_bool_attr(element, 'rebuild', False)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Parse sources
|
|
57
|
+
for child in element:
|
|
58
|
+
child_type = self.get_element_name(child)
|
|
59
|
+
|
|
60
|
+
if child_type == 'source':
|
|
61
|
+
source = self._parse_source(child)
|
|
62
|
+
knowledge_node.add_source(source)
|
|
63
|
+
|
|
64
|
+
return knowledge_node
|
|
65
|
+
|
|
66
|
+
def _parse_source(self, element: ET.Element) -> KnowledgeSourceNode:
|
|
67
|
+
"""Parse q:source within q:knowledge."""
|
|
68
|
+
source_type = self.get_attr(element, 'type', 'text')
|
|
69
|
+
|
|
70
|
+
source = KnowledgeSourceNode(
|
|
71
|
+
source_type=source_type,
|
|
72
|
+
path=self.get_attr(element, 'path'),
|
|
73
|
+
pattern=self.get_attr(element, 'pattern'),
|
|
74
|
+
url=self.get_attr(element, 'url'),
|
|
75
|
+
datasource=self.get_attr(element, 'datasource'),
|
|
76
|
+
chunk_size=self.get_int_attr(element, 'chunkSize', 0) or None,
|
|
77
|
+
chunk_overlap=self.get_int_attr(element, 'chunkOverlap', 0) or None
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Get content for text source
|
|
81
|
+
if source_type == 'text':
|
|
82
|
+
source.content = self.get_text(element)
|
|
83
|
+
elif source_type == 'query':
|
|
84
|
+
source.sql = self.get_text(element)
|
|
85
|
+
|
|
86
|
+
return source
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Parser - Parse q:llm statements
|
|
3
|
+
|
|
4
|
+
Handles LLM invocation configuration.
|
|
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.ast_nodes import LLMNode, LLMMessageNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LLMParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:llm statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Completion mode (prompt)
|
|
19
|
+
- Chat mode (messages)
|
|
20
|
+
- Temperature/token configuration
|
|
21
|
+
- Response formatting
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tag_names(self) -> List[str]:
|
|
26
|
+
return ['llm']
|
|
27
|
+
|
|
28
|
+
def parse(self, element: ET.Element) -> LLMNode:
|
|
29
|
+
"""
|
|
30
|
+
Parse q:llm statement.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
element: XML element for q:llm
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
LLMNode AST node
|
|
37
|
+
"""
|
|
38
|
+
name = self.get_attr(element, 'name')
|
|
39
|
+
|
|
40
|
+
if not name:
|
|
41
|
+
raise ParserError("LLM requires 'name' attribute")
|
|
42
|
+
|
|
43
|
+
llm_node = LLMNode(name)
|
|
44
|
+
llm_node.model = self.get_attr(element, 'model')
|
|
45
|
+
llm_node.endpoint = self.get_attr(element, 'endpoint')
|
|
46
|
+
|
|
47
|
+
# Temperature and tokens
|
|
48
|
+
temp = self.get_attr(element, 'temperature')
|
|
49
|
+
if temp:
|
|
50
|
+
try:
|
|
51
|
+
llm_node.temperature = float(temp)
|
|
52
|
+
except ValueError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
llm_node.max_tokens = self.get_int_attr(element, 'maxTokens', 0) or None
|
|
56
|
+
llm_node.response_format = self.get_attr(element, 'responseFormat')
|
|
57
|
+
llm_node.cache = self.get_bool_attr(element, 'cache', False)
|
|
58
|
+
llm_node.ttl = self.get_int_attr(element, 'ttl', 0) or None
|
|
59
|
+
# q:llm spoke only Ollama while q:agent was already multi-provider;
|
|
60
|
+
# same attributes now work on both tags.
|
|
61
|
+
llm_node.provider = self.get_attr(element, 'provider')
|
|
62
|
+
llm_node.api_key = self.get_attr(element, 'apiKey')
|
|
63
|
+
llm_node.timeout = self.get_int_attr(element, 'timeout', 30)
|
|
64
|
+
|
|
65
|
+
# Parse children
|
|
66
|
+
for child in element:
|
|
67
|
+
child_type = self.get_element_name(child)
|
|
68
|
+
|
|
69
|
+
if child_type == 'prompt':
|
|
70
|
+
llm_node.prompt = self.get_text(child)
|
|
71
|
+
elif child_type == 'system':
|
|
72
|
+
llm_node.system = self.get_text(child)
|
|
73
|
+
elif child_type == 'message':
|
|
74
|
+
role = self.get_attr(child, 'role', 'user')
|
|
75
|
+
content = self.get_text(child)
|
|
76
|
+
llm_node.messages.append(LLMMessageNode(role, content))
|
|
77
|
+
|
|
78
|
+
return llm_node
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Team Parser - Parse q:team statements
|
|
3
|
+
|
|
4
|
+
Handles multi-agent team configuration.
|
|
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.agents.src.ast_node import AgentTeamNode, AgentExecuteNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TeamParser(BaseTagParser):
|
|
14
|
+
"""
|
|
15
|
+
Parser for q:team statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Multiple agent definitions
|
|
19
|
+
- Shared context
|
|
20
|
+
- Supervisor/entry configuration
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tag_names(self) -> List[str]:
|
|
25
|
+
return ['team']
|
|
26
|
+
|
|
27
|
+
def parse(self, element: ET.Element) -> AgentTeamNode:
|
|
28
|
+
"""
|
|
29
|
+
Parse q:team statement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
element: XML element for q:team
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
AgentTeamNode AST node
|
|
36
|
+
"""
|
|
37
|
+
name = self.get_attr(element, 'name')
|
|
38
|
+
|
|
39
|
+
if not name:
|
|
40
|
+
raise ParserError("Team requires 'name' attribute")
|
|
41
|
+
|
|
42
|
+
# Create team node with defaults (matching legacy behavior)
|
|
43
|
+
team_node = AgentTeamNode(name=name)
|
|
44
|
+
|
|
45
|
+
# Parse attributes (support both snake_case and camelCase)
|
|
46
|
+
team_node.supervisor = self.get_attr(element, 'supervisor', '')
|
|
47
|
+
|
|
48
|
+
max_handoffs = self.get_attr(element, 'max_handoffs') or self.get_attr(element, 'maxHandoffs')
|
|
49
|
+
if max_handoffs:
|
|
50
|
+
try:
|
|
51
|
+
team_node.max_handoffs = int(max_handoffs)
|
|
52
|
+
except ValueError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
max_iterations = self.get_attr(element, 'max_total_iterations') or self.get_attr(element, 'maxTotalIterations')
|
|
56
|
+
if max_iterations:
|
|
57
|
+
try:
|
|
58
|
+
team_node.max_total_iterations = int(max_iterations)
|
|
59
|
+
except ValueError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# Parse children
|
|
63
|
+
for child in element:
|
|
64
|
+
child_type = self.get_element_name(child)
|
|
65
|
+
|
|
66
|
+
if child_type == 'shared':
|
|
67
|
+
# Parse shared context (q:set nodes)
|
|
68
|
+
for shared_child in child:
|
|
69
|
+
statement = self.parse_statement(shared_child)
|
|
70
|
+
if statement:
|
|
71
|
+
team_node.shared.append(statement)
|
|
72
|
+
elif child_type == 'agent':
|
|
73
|
+
# Parse agent using main parser
|
|
74
|
+
agent = self.parse_statement(child)
|
|
75
|
+
if agent:
|
|
76
|
+
# Team members are driven by the team's <q:execute>, not
|
|
77
|
+
# one of their own — mark them so validate() doesn't
|
|
78
|
+
# reject the whole component.
|
|
79
|
+
agent.in_team = True
|
|
80
|
+
team_node.agents.append(agent)
|
|
81
|
+
elif child_type == 'execute':
|
|
82
|
+
team_node.execute = AgentExecuteNode(
|
|
83
|
+
task=self.get_attr(child, 'task', self.get_text(child)),
|
|
84
|
+
context=self.get_attr(child, 'context', ''),
|
|
85
|
+
entry=self.get_attr(child, 'entry', '')
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return team_node
|