pymetta 0.2.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 (219) hide show
  1. metta/__init__.py +1261 -0
  2. metta/__main__.py +255 -0
  3. metta/_api_types.py +18 -0
  4. metta/_async_ops.py +464 -0
  5. metta/_atom_namespace.py +256 -0
  6. metta/_atom_wire.py +334 -0
  7. metta/_atoms_core.py +1886 -0
  8. metta/_call_binding.py +65 -0
  9. metta/_callable_mentions.py +110 -0
  10. metta/_callbacks.py +173 -0
  11. metta/_codec_kit.py +416 -0
  12. metta/_compliance.py +627 -0
  13. metta/_config.py +176 -0
  14. metta/_contract.py +252 -0
  15. metta/_convert_build.py +265 -0
  16. metta/_convert_project.py +392 -0
  17. metta/_convert_registry.py +452 -0
  18. metta/_define_context.py +221 -0
  19. metta/_define_expression.py +1549 -0
  20. metta/_define_facts.py +281 -0
  21. metta/_define_loops.py +173 -0
  22. metta/_define_statements.py +1969 -0
  23. metta/_define_twins.py +252 -0
  24. metta/_documentation.py +189 -0
  25. metta/_engine.py +1004 -0
  26. metta/_fn.py +708 -0
  27. metta/_fn.pyi +359 -0
  28. metta/_gateway_compliance.py +349 -0
  29. metta/_host_island.py +104 -0
  30. metta/_json.py +117 -0
  31. metta/_library.py +200 -0
  32. metta/_lint_analysis.py +950 -0
  33. metta/_lint_events.py +728 -0
  34. metta/_lint_model.py +211 -0
  35. metta/_name_mapping.py +171 -0
  36. metta/_network.py +195 -0
  37. metta/_object_fields.py +28 -0
  38. metta/_operator_lowerings.py +119 -0
  39. metta/_ops.py +838 -0
  40. metta/_optional.py +31 -0
  41. metta/_parameterized.py +280 -0
  42. metta/_persistent.py +1542 -0
  43. metta/_prelude.py +445 -0
  44. metta/_rules.py +228 -0
  45. metta/_runtime/engine/.gitignore +13 -0
  46. metta/_runtime/engine/bench-baseline.json +88 -0
  47. metta/_runtime/engine/bench.pl +454 -0
  48. metta/_runtime/engine/bench.py +343 -0
  49. metta/_runtime/engine/bench.sh +81 -0
  50. metta/_runtime/engine/build.sh +46 -0
  51. metta/_runtime/engine/check.sh +504 -0
  52. metta/_runtime/engine/duals.pl +1438 -0
  53. metta/_runtime/engine/ext_points.pl +1754 -0
  54. metta/_runtime/engine/filereader/source_lifecycle.pl +766 -0
  55. metta/_runtime/engine/filereader.pl +1634 -0
  56. metta/_runtime/engine/json_codec.c +1219 -0
  57. metta/_runtime/engine/json_codec.pl +315 -0
  58. metta/_runtime/engine/kernel.pl +131 -0
  59. metta/_runtime/engine/main.pl +104 -0
  60. metta/_runtime/engine/mbr.c +355 -0
  61. metta/_runtime/engine/metta/control.pl +1365 -0
  62. metta/_runtime/engine/metta/effects.pl +2437 -0
  63. metta/_runtime/engine/metta/input_guards.pl +329 -0
  64. metta/_runtime/engine/metta/interop.pl +1603 -0
  65. metta/_runtime/engine/metta/operators.pl +960 -0
  66. metta/_runtime/engine/metta/registration.pl +675 -0
  67. metta/_runtime/engine/metta/runtime.pl +664 -0
  68. metta/_runtime/engine/metta/space_hooks.pl +1185 -0
  69. metta/_runtime/engine/metta/terms.pl +833 -0
  70. metta/_runtime/engine/metta/types.pl +1608 -0
  71. metta/_runtime/engine/metta.pl +1593 -0
  72. metta/_runtime/engine/metta_token.h +158 -0
  73. metta/_runtime/engine/narrowing.pl +482 -0
  74. metta/_runtime/engine/parser.pl +1480 -0
  75. metta/_runtime/engine/prelude.metta +696 -0
  76. metta/_runtime/engine/qlf_boot.pl +180 -0
  77. metta/_runtime/engine/reader.c +923 -0
  78. metta/_runtime/engine/scc.pl +155 -0
  79. metta/_runtime/engine/spaces/bounded_matching.pl +989 -0
  80. metta/_runtime/engine/spaces/catalog.pl +1548 -0
  81. metta/_runtime/engine/spaces/foreign.pl +2080 -0
  82. metta/_runtime/engine/spaces/lifecycle.pl +1836 -0
  83. metta/_runtime/engine/spaces/native_matching.pl +676 -0
  84. metta/_runtime/engine/spaces/segment_matching.pl +895 -0
  85. metta/_runtime/engine/spaces.pl +373 -0
  86. metta/_runtime/engine/specializer.pl +831 -0
  87. metta/_runtime/engine/support_graph.pl +787 -0
  88. metta/_runtime/engine/test.sh +87 -0
  89. metta/_runtime/engine/tracer.pl +221 -0
  90. metta/_runtime/engine/translator/analysis.pl +1479 -0
  91. metta/_runtime/engine/translator/lowering.pl +1755 -0
  92. metta/_runtime/engine/translator/runtime.pl +1425 -0
  93. metta/_runtime/engine/translator/special_forms.pl +1925 -0
  94. metta/_runtime/engine/translator/typing.pl +702 -0
  95. metta/_runtime/engine/translator.pl +377 -0
  96. metta/_runtime/engine/translator_rules.pl +651 -0
  97. metta/_runtime/engine/trs.pl +506 -0
  98. metta/_runtime/engine/type_rules.pl +246 -0
  99. metta/_runtime/engine/writer.c +915 -0
  100. metta/_runtime/extensions/mork/extension.pl +34 -0
  101. metta/_runtime/extensions/python/bridge.pl +844 -0
  102. metta/_runtime/extensions/python/extension.pl +23 -0
  103. metta/_runtime/extensions/python/helper.pl +34 -0
  104. metta/_runtime/extensions/python/metta/shim.pl +4900 -0
  105. metta/_runtime/extensions/python/metta_py.py +377 -0
  106. metta/_runtime/lib/.gitignore +4 -0
  107. metta/_runtime/lib/builtin_mods/skel.metta +23 -0
  108. metta/_runtime/lib/builtin_mods/skel.pl +17 -0
  109. metta/_runtime/lib/lib_builtin_types/lib_builtin_types.metta +508 -0
  110. metta/_runtime/lib/lib_combinatorics/lib_combinatorics.metta +33 -0
  111. metta/_runtime/lib/lib_conformance/lib_conformance.metta +26 -0
  112. metta/_runtime/lib/lib_conformance/lib_conformance.pl +411 -0
  113. metta/_runtime/lib/lib_constraints/lib_constraints.metta +30 -0
  114. metta/_runtime/lib/lib_constraints/lib_constraints.pl +166 -0
  115. metta/_runtime/lib/lib_crypto/lib_crypto.metta +17 -0
  116. metta/_runtime/lib/lib_crypto/lib_crypto.pl +23 -0
  117. metta/_runtime/lib/lib_datastructures/lib_datastructures.metta +201 -0
  118. metta/_runtime/lib/lib_datetime/lib_datetime.metta +8 -0
  119. metta/_runtime/lib/lib_datetime/lib_datetime.pl +10 -0
  120. metta/_runtime/lib/lib_derived/lib_derived.metta +44 -0
  121. metta/_runtime/lib/lib_dict/lib_dict.metta +78 -0
  122. metta/_runtime/lib/lib_doc/lib_doc.metta +44 -0
  123. metta/_runtime/lib/lib_file/lib_file.metta +51 -0
  124. metta/_runtime/lib/lib_file/lib_file.pl +287 -0
  125. metta/_runtime/lib/lib_gitimport/lib_gitimport.pl +385 -0
  126. metta/_runtime/lib/lib_he/lib_he.metta +50 -0
  127. metta/_runtime/lib/lib_import/lib_import.metta +41 -0
  128. metta/_runtime/lib/lib_import/lib_import.pl +143 -0
  129. metta/_runtime/lib/lib_json/lib_json.metta +20 -0
  130. metta/_runtime/lib/lib_json/lib_json.pl +211 -0
  131. metta/_runtime/lib/lib_measure/lib_measure.metta +102 -0
  132. metta/_runtime/lib/lib_memo/lib_memo.metta +23 -0
  133. metta/_runtime/lib/lib_memo/lib_memo.pl +1750 -0
  134. metta/_runtime/lib/lib_memo/lib_memo_doc.md +226 -0
  135. metta/_runtime/lib/lib_mm2/lib_mm2.metta +35 -0
  136. metta/_runtime/lib/lib_nars/lib_nars.metta +275 -0
  137. metta/_runtime/lib/lib_patrick/lib_patrick.metta +27 -0
  138. metta/_runtime/lib/lib_pln/lib_pln.metta +474 -0
  139. metta/_runtime/lib/lib_redis/lib_redis.metta +15 -0
  140. metta/_runtime/lib/lib_redis/lib_redis.pl +290 -0
  141. metta/_runtime/lib/lib_reflect/lib_reflect.metta +93 -0
  142. metta/_runtime/lib/lib_reflect/lib_reflect.pl +140 -0
  143. metta/_runtime/lib/lib_regex/lib_regex.metta +24 -0
  144. metta/_runtime/lib/lib_regex/lib_regex.pl +61 -0
  145. metta/_runtime/lib/lib_roman/lib_roman.metta +110 -0
  146. metta/_runtime/lib/lib_soft/lib_soft.metta +70 -0
  147. metta/_runtime/lib/lib_spaces/lib_spaces.metta +35 -0
  148. metta/_runtime/lib/lib_strategy/lib_strategy.metta +272 -0
  149. metta/_runtime/lib/lib_string/lib_string.metta +37 -0
  150. metta/_runtime/lib/lib_string/lib_string.pl +258 -0
  151. metta/_runtime/lib/lib_tabling/lib_tabling.metta +68 -0
  152. metta/_runtime/lib/lib_tabling/lib_tabling.pl +540 -0
  153. metta/_runtime/lib/lib_thread/lib_thread.metta +179 -0
  154. metta/_runtime/lib/lib_thread/lib_thread.pl +1809 -0
  155. metta/_runtime/lib/lib_thread/lib_thread_doc.md +283 -0
  156. metta/_runtime/lib/lib_torch/lib_torch.metta +44 -0
  157. metta/_runtime/lib/lib_vector/lib_vector.metta +24 -0
  158. metta/_runtime/lib/lib_zar/lib_zar.metta +49 -0
  159. metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.metta +181 -0
  160. metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.pl +284 -0
  161. metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.py +82 -0
  162. metta/_runtime/tests/codec/corpus.json +620 -0
  163. metta/_saga.py +935 -0
  164. metta/_source_forms.py +92 -0
  165. metta/_space.py +6237 -0
  166. metta/_space_definitions.py +1057 -0
  167. metta/_space_diagnostics.py +146 -0
  168. metta/_space_execution.py +891 -0
  169. metta/_space_objects.py +1375 -0
  170. metta/_space_persistence.py +364 -0
  171. metta/_space_query.py +124 -0
  172. metta/_state.py +79 -0
  173. metta/_task_context.py +150 -0
  174. metta/_tokens.py +24 -0
  175. metta/_trace.py +96 -0
  176. metta/_type_annotations.py +417 -0
  177. metta/_under.py +51 -0
  178. metta/_version.py +8 -0
  179. metta/_world.py +337 -0
  180. metta/aio.py +3160 -0
  181. metta/algebra.py +1408 -0
  182. metta/answer.py +138 -0
  183. metta/arrays.py +817 -0
  184. metta/atoms.py +619 -0
  185. metta/benchmarking.py +1075 -0
  186. metta/casting.py +117 -0
  187. metta/cli.py +98 -0
  188. metta/convert.py +41 -0
  189. metta/define.py +1108 -0
  190. metta/derivation.py +247 -0
  191. metta/errors.py +538 -0
  192. metta/events.py +762 -0
  193. metta/foreign.py +923 -0
  194. metta/integrate.py +660 -0
  195. metta/ipython.py +73 -0
  196. metta/lint.py +93 -0
  197. metta/manifest.py +501 -0
  198. metta/ops.py +1049 -0
  199. metta/parallel.py +542 -0
  200. metta/paths.py +155 -0
  201. metta/py.typed +0 -0
  202. metta/pytest_plugin.py +39 -0
  203. metta/remote.py +1909 -0
  204. metta/results.py +1216 -0
  205. metta/shim.pl +4900 -0
  206. metta/spaces.py +668 -0
  207. metta/strategies.py +58 -0
  208. metta/structures.py +734 -0
  209. metta/subscribe.py +347 -0
  210. metta/tables.py +597 -0
  211. metta/testing.py +989 -0
  212. metta/vocabularies.py +372 -0
  213. metta/wire.py +17 -0
  214. pymetta-0.2.0.dist-info/METADATA +713 -0
  215. pymetta-0.2.0.dist-info/RECORD +219 -0
  216. pymetta-0.2.0.dist-info/WHEEL +5 -0
  217. pymetta-0.2.0.dist-info/entry_points.txt +5 -0
  218. pymetta-0.2.0.dist-info/licenses/LICENSE +21 -0
  219. pymetta-0.2.0.dist-info/top_level.txt +1 -0
metta/__init__.py ADDED
@@ -0,0 +1,1261 @@
1
+ """Purpose: expose MeTTa's narrow Python core and lazily load satellites.
2
+
3
+ Assumes:
4
+ - ``metta._space.MeTTa`` owns runtime context and ``metta._space.Space``
5
+ owns storage and query verbs [source:
6
+ extensions/python/metta/_space.py:306 and :3090; commit=f88aa8be03cb64cb59d3307515ded8701f418321]
7
+ Guarantees:
8
+ - the R5 root exports the term builders, relational solve, and lazy State
9
+ handle while ``record`` and atom-specialist ``order_key`` stay absent
10
+ [tested: test_m7_narrow_core_surface,
11
+ test_solve_retires_the_five_relational_let_workarounds,
12
+ test_keyword_builders_retire_53_raw_if_mentions, and
13
+ test_state_retires_three_state_function_strings; commit=cff2e7f319bd2212f0c2d74f8d5fe5be3ac693b5]
14
+ - ``dir(metta)`` is exactly the curated public surface and loads no
15
+ satellites [tested: test_m7_narrow_core_surface; commit=f88aa8be03cb64cb59d3307515ded8701f418321]
16
+ - satellite modules are imported only by attribute access, following PEP
17
+ 562 with their real module identity intact [tested:
18
+ test_m7_satellites_are_lazy_and_identity_stable; commit=f88aa8be03cb64cb59d3307515ded8701f418321]
19
+ - ``space()`` is the only space-creation door and cannot be overwritten by
20
+ an implementation submodule [tested: test_m7_space_factory_keeps_identity;
21
+ commit=f88aa8be03cb64cb59d3307515ded8701f418321]
22
+ - ``space()`` accepts both text and a space-name Symbol returned by the
23
+ engine [tested: test_space_factory_accepts_a_name_symbol; commit=18b1135167d60396c41e63e42ded2f66d0eb1900]
24
+ - ``fn`` is an inert, generated, statically typed mention namespace and
25
+ importing it never starts the engine [tested:
26
+ test_the_fn_namespace_is_generated; commit=6b77b811c44e1819ed9cd99f3809c0667f289e2e]
27
+ - package ``match`` reads the default space while ``superpose`` evaluates
28
+ its expression form; compiled definitions lower their syntactic match
29
+ calls before either Python function executes [tested:
30
+ test_module_tier_exposes_the_mode_and_definition_family; commit=b2527d32dc851615e6cf1e11c94ac017d4e78c86]
31
+ - ``unify`` keeps the symmetric two-atom matcher at arity two and evaluates
32
+ the engine's conditional form at arity four [tested:
33
+ test_expression_position_unify_uses_the_engine_conditional_in_both_contexts;
34
+ commit=6917bef7ca902671999eafcae3a7a86db8f69723]
35
+ - ``view`` lazily opens a live provider space over Python mappings, sets,
36
+ and sequences [tested: test_view_is_a_live_queryable_space;
37
+ commit=b1de70215dd3f0c9d5437558c57c5911c13948b5]
38
+ - the root exports ``seg``, the named segment builder, beside the ``...``
39
+ spelling Python already has [tested: test_seg_builds_a_named_segment;
40
+ commit=a3dff3abc83b9d82f3652093246e1d693d526cdb]
41
+ - coordination functions are lazy satellite exports and Timeout remains
42
+ catchable as builtin TimeoutError [tested:
43
+ test_the_coordination_family_is_python_shaped; commit=b1de70215dd3f0c9d5437558c57c5911c13948b5]
44
+ - module define/stats/limits/trace verbs defer engine creation
45
+ until called and target the default self space [tested:
46
+ test_module_tier_exposes_the_mode_and_definition_family; commit=b1de70215dd3f0c9d5437558c57c5911c13948b5]
47
+ - ``op`` forwards unchanged to the lazy default receiver and therefore keeps
48
+ its required five-rank ``effect=`` contract [tested:
49
+ test_module_tier_op_forwards_identity_to_the_default_receiver,
50
+ test_module_tier_op_registration_precedes_definition_compilation;
51
+ commit=fc7ec0b08cd8b5876a3f4105211c487185f6a9bf]
52
+ - ``py(expr)`` is an identity in ordinary Python and the exact visible marker
53
+ the definition compiler recognizes for an inline host island [tested:
54
+ test_py_is_identity_outside_a_compiled_body,
55
+ test_py_host_island_executes_per_engine_application; commit=3f0a1d237a3c969b2d4ad0d48b2195ce196b631a]
56
+ - under scopes an algebra through ContextVar state and the exact counting,
57
+ tropical, probability, provenance, and ranking carriers stay lazy root
58
+ exports [tested:
59
+ test_scoped_under_is_task_local_and_explicit_under_wins,
60
+ test_requested_carrier_spellings_are_declared; commit=c7468b2789746bcf95c4bacc0e2d517ec4d972fa]
61
+ - ``speculate()`` is the exact module-tier spelling for the default
62
+ receiver's discarded execution scope [tested:
63
+ test_speculative_execution_discards_its_event_segment; commit=3ded7552797b66d78e666141eb51f3bc14686bd2]
64
+ - ``strategies`` is a lazy satellite whose exports are reified Symbols rather
65
+ than promoted root callbacks [tested:
66
+ test_m7_satellites_are_lazy_and_identity_stable and
67
+ test_strategy_exports_are_reified_atoms; commit=0d37dd6b24fe916e44cdbfb4efc6a1d5ffaf74aa]
68
+ - ``catalog`` names the queryable ``&metta`` space and ``fresh()`` supplies
69
+ hygienic variables for helper-authored patterns [tested:
70
+ test_catalog_is_the_root_queryable_reflection_space and
71
+ test_fresh_variables_keep_library_patterns_hygienic; commit=46ae646e5efe14320c01e1e110d9cfd6cd0fc7e1]
72
+ Open Obligations:
73
+ To Do: None
74
+ Hacks: None
75
+ Future Enhancements: None
76
+ """
77
+ # The generated module doors carry Space's own parameter names, and two of
78
+ # them (fn, under) are also module objects; inside a door the parameter is
79
+ # the meaning, which is the point.
80
+ # pylint: disable=redefined-outer-name
81
+
82
+
83
+ from __future__ import annotations
84
+
85
+ import builtins as _builtins
86
+ import functools as _functools
87
+ import importlib as _importlib
88
+ import os as _os
89
+ from collections.abc import Mapping as _Mapping
90
+ from typing import TYPE_CHECKING
91
+ from typing import Any as _Any
92
+ from typing import overload as _overload
93
+
94
+ if TYPE_CHECKING:
95
+ from collections.abc import Callable as _Callable
96
+ from collections.abc import Iterable as _Iterable
97
+ from typing import Literal as _Literal
98
+
99
+ # The static faces of _LAZY_ATTRIBUTES below, name for name: the lazy
100
+ # __getattr__ keeps `import metta` narrow at runtime, and without these
101
+ # a checker types every root export Any, py.typed notwithstanding.
102
+ from ._rules import equation, rules
103
+ from ._space import _P, _R, MeTTa, Space
104
+ from ._space_execution import ScopedExecution as _ScopedExecution
105
+ from ._space_objects import ScopedLimits as _ScopedLimits
106
+ from ._space_objects import _StatsBlock
107
+ from ._state import State
108
+ from .algebra import counting, prob, prov, ranked, tropical
109
+ from .answer import Answer, Bindings
110
+ from .define import Defined
111
+ from .define import Defined as _Defined
112
+ from .define import PrologBacked as _PrologBacked
113
+ from .foreign import SpaceProvider
114
+ from .manifest import boot
115
+ from .parallel import channel, every, par_map, race, spawn
116
+ from .results import Answers as _Answers
117
+ from .spaces import view
118
+ from .vocabularies import EffectClass as _EffectClass
119
+
120
+ from ._config import Config, config
121
+ from ._fn import fn
122
+ from ._host_island import py
123
+ from ._library import Library, lib
124
+ from ._under import _UNSET
125
+ from ._version import __version__
126
+ from .atoms import (
127
+ FALSE,
128
+ TRUE,
129
+ UNIT,
130
+ Atom,
131
+ Expression,
132
+ G,
133
+ Grounded,
134
+ Handle,
135
+ S,
136
+ Symbol,
137
+ Undefined,
138
+ V,
139
+ Variable,
140
+ and_,
141
+ arrow,
142
+ fresh,
143
+ ground,
144
+ if_,
145
+ in_,
146
+ not_,
147
+ or_,
148
+ parse,
149
+ seg,
150
+ typed,
151
+ )
152
+ from .atoms import unify as _unify_atoms
153
+ from .errors import MettaError, NotReducible, Timeout
154
+
155
+ _SATELLITES = frozenset(
156
+ {
157
+ "aio",
158
+ "algebra",
159
+ "arrays",
160
+ "casting",
161
+ "convert",
162
+ "derivation",
163
+ "events",
164
+ "foreign",
165
+ "integrate",
166
+ "lint",
167
+ "manifest",
168
+ "parallel",
169
+ "paths",
170
+ "remote",
171
+ "spaces",
172
+ "strategies",
173
+ "structures",
174
+ "subscribe",
175
+ "tables",
176
+ "testing",
177
+ "vocabularies",
178
+ "wire",
179
+ }
180
+ )
181
+
182
+ _LAZY_ATTRIBUTES = {
183
+ "Answer": ("answer", "Answer"),
184
+ "Bindings": ("answer", "Bindings"),
185
+ "Defined": ("define", "Defined"),
186
+ "MeTTa": ("_space", "MeTTa"),
187
+ "Space": ("_space", "Space"),
188
+ "SpaceProvider": ("foreign", "SpaceProvider"),
189
+ "State": ("_state", "State"),
190
+ "counting": ("algebra", "counting"),
191
+ "prob": ("algebra", "prob"),
192
+ "prov": ("algebra", "prov"),
193
+ "ranked": ("algebra", "ranked"),
194
+ "tropical": ("algebra", "tropical"),
195
+ "boot": ("manifest", "boot"),
196
+ "equation": ("_rules", "equation"),
197
+ "rules": ("_rules", "rules"),
198
+ "channel": ("parallel", "channel"),
199
+ "every": ("parallel", "every"),
200
+ "par_map": ("parallel", "par_map"),
201
+ "race": ("parallel", "race"),
202
+ "spawn": ("parallel", "spawn"),
203
+ "view": ("spaces", "view"),
204
+ }
205
+
206
+ _HIDDEN_IMPLEMENTATION_MODULES = {
207
+ "answer",
208
+ "atoms",
209
+ "define",
210
+ "errors",
211
+ "ops",
212
+ "results",
213
+ }
214
+
215
+ _OMITTED = object()
216
+
217
+
218
+ def _path_exists(path: str) -> bool:
219
+ """Check a runtime path without importing pathlib into the narrow root."""
220
+ return _os.path.exists(path) # noqa: FURB141 -- pathlib adds eager imports to plain ``import metta``
221
+
222
+
223
+ def _resolve_metta_path() -> str:
224
+ """Locate either the upstream or current bundled/source runtime tree."""
225
+ env_path = _os.environ.get("METTA_PATH")
226
+ if env_path:
227
+ return _os.path.abspath(env_path)
228
+
229
+ here = _os.path.dirname(_os.path.abspath(__file__))
230
+ bundled = _os.path.join(here, "_runtime")
231
+ if _path_exists(_os.path.join(bundled, "src", "main.pl")) or _path_exists(
232
+ _os.path.join(bundled, "engine", "main.pl")
233
+ ):
234
+ return bundled
235
+
236
+ return _os.path.abspath(_os.path.join(here, _os.pardir, _os.pardir, _os.pardir))
237
+
238
+
239
+ def __getattr__(name: str) -> _Any:
240
+ """Load one advertised satellite or lazy core object on first access."""
241
+ if name in _SATELLITES:
242
+ value = _importlib.import_module(f".{name}", __name__)
243
+ elif name in _LAZY_ATTRIBUTES:
244
+ module_name, attribute = _LAZY_ATTRIBUTES[name]
245
+ module = _importlib.import_module(f".{module_name}", __name__)
246
+ value = getattr(module, attribute)
247
+ # policy-inventory-exempt: mechanism-internal; reason=one handle's two documented module-attribute names for the &metta space, not a vocabulary a program selects from; evidence=extensions/python/metta/__init__.py:__getattr__
248
+ elif name in {"catalog", "reflection"}:
249
+ value = engine().space("&metta")
250
+ else:
251
+ msg = f"module {__name__!r} has no attribute {name!r}"
252
+ raise AttributeError(msg)
253
+ _rehide_implementation_modules()
254
+ globals()[name] = value
255
+ return value
256
+
257
+
258
+ def _rehide_implementation_modules() -> None:
259
+ """Restore each root verb an implementation-module import shadowed.
260
+
261
+ Importing a submodule writes it onto its parent package, so any import
262
+ that pulls in ``metta.define`` and its siblings replaces the root VERB
263
+ with the module object. This puts the verb back. A name with no verb is
264
+ removed. During partial package initialization the verbs table is not
265
+ bound yet; popping then would delete the verb with nothing to restore
266
+ it, which is how ``metta.define`` once vanished for the life of the
267
+ process, so the pass defers to the end-of-init sweep instead.
268
+ """
269
+ verbs = globals().get("_ROOT_IMPLEMENTATION_VERBS")
270
+ if verbs is None:
271
+ return
272
+ for implementation_name in _HIDDEN_IMPLEMENTATION_MODULES:
273
+ replacement = verbs.get(implementation_name)
274
+ if replacement is None:
275
+ globals().pop(implementation_name, None)
276
+ else:
277
+ globals()[implementation_name] = replacement
278
+
279
+
280
+ def __dir__() -> list[str]:
281
+ """Return only the designed public surface without resolving it."""
282
+ return sorted(__all__)
283
+
284
+
285
+ @_functools.cache
286
+ def engine():
287
+ """Return the process-default runtime context, creating it on first use.
288
+
289
+ This is the one context whose home is the engine's own ``&self``; a bare
290
+ ``MeTTa()`` is a fresh isolated context instead.
291
+ """
292
+ return __getattr__("MeTTa")(__getattr__("Space")())
293
+
294
+
295
+ def space(
296
+ name: str | Atom | None = None,
297
+ backing: _Any = None,
298
+ *,
299
+ inherits: _Any = None,
300
+ restricted: bool = False,
301
+ grants: _Any = (),
302
+ journal: str | None = None,
303
+ schema: _Any = None,
304
+ sync: str = "none",
305
+ ):
306
+ """Create or open a space; the backing value derives its implementation."""
307
+ return engine().space(
308
+ name,
309
+ backing,
310
+ inherits=inherits,
311
+ restricted=restricted,
312
+ grants=grants,
313
+ journal=journal,
314
+ schema=schema,
315
+ sync=sync,
316
+ )
317
+
318
+
319
+ def attach(name: str | Symbol, backing: _Any):
320
+ """Attach a provider or remote URL through the unified creation door."""
321
+ return space(name, backing=backing)
322
+
323
+
324
+ def current_space():
325
+ """Return the ambient space selected by an enclosing space context."""
326
+ space_api = _importlib.import_module(f"{__name__}._space")
327
+ value = space_api.current_space()
328
+ _rehide_implementation_modules()
329
+ return value
330
+
331
+
332
+ def forms(source: str) -> list[Atom]:
333
+ """Parse every top-level form without evaluating any of them."""
334
+ source_forms = _importlib.import_module(f"{__name__}._source_forms")
335
+ return [parse(form.text) for form in source_forms.positioned_forms(source)]
336
+
337
+
338
+ # ------------------------------------------------- generated module tier
339
+ # Every door below is GENERATED by tools/aiogen.py from the synchronous Space
340
+ # method it delegates to, whose signature, return annotation and docstring it
341
+ # carries, each with the tier note appended. Do not edit them here: change
342
+ # Space, or remove the door's row from MODULE_DOORS in tools/aio_divergences.py.
343
+
344
+ def run(
345
+ source: str,
346
+ *,
347
+ timeout: float | None = None,
348
+ inferences: int | None = None,
349
+ ) -> list[list[Atom]]:
350
+ """Run MeTTa source: one list of answers per ! directive.
351
+
352
+ The pipeline is the engine's own reader, compiler and evaluator, so
353
+ the answers are exactly what the CLI would print, kept grouped per
354
+ directive instead of flattened. Equations and facts in the source
355
+ land in this space.
356
+
357
+ `bind()` names Python values the source refers to by bare symbol,
358
+ the way DuckDB reads a local dataframe by its variable name:
359
+
360
+ with m.bind({"graph": my_graph}):
361
+ m.run("!(py-len graph)")
362
+
363
+ Each named symbol substitutes to its value (objects by identity),
364
+ after reading, before anything runs. It is a BLOCK rather than a
365
+ keyword because a binding mapping is the kind of value that grows,
366
+ and a block grows down the page where a keyword has to fit beside
367
+ everything else on the call. Every target door reads the same scope,
368
+ so one block covers a run(), an eval() and an answers() together.
369
+
370
+ `timeout` (seconds) and `inferences` (engine steps) bound the call
371
+ with the engine's own guards; passing either raises TimeLimitError
372
+ or InferenceLimitError when the bound is hit, and whatever the
373
+ source completed before the stop, writes included, stands.
374
+
375
+ `with m.capture() as output` collects printed text in `output.text`
376
+ without changing this method's return shape. `with m.atomic()`
377
+ and `with m.speculative()` scope execution policy without boolean
378
+ combinations on each call. Atomic commits or rolls
379
+ back each complete source; speculative answers and discards its
380
+ writes. Both cover engine state; Python side effects and subscription
381
+ callbacks already fired stay where they happened.
382
+
383
+ A term the engine hands back unevaluated is an ordinary MeTTa value,
384
+ not a failure: `!(hello world)` answers `(hello world)` and that is
385
+ the whole of hello world in this language. eval_status() reports
386
+ which answers reduced and which did not, as data, for a caller who
387
+ wants to decide about it.
388
+ Runs against the default context's self space.
389
+ """
390
+ return engine().self.run(source, timeout=timeout, inferences=inferences)
391
+
392
+
393
+ def load(
394
+ path: str | _os.PathLike[str],
395
+ *,
396
+ timeout: float | None = None,
397
+ inferences: int | None = None,
398
+ ) -> list[list[Atom]]:
399
+ """Add a text program or trusted fast cache to this space.
400
+
401
+ This is a consult, so it always loads and what it loads REPLACES
402
+ what the same file put in this space before. Edit the file, load it
403
+ again, and the space holds the new definitions and not both; the
404
+ engine says on stderr which file it replaced and how many atoms
405
+ went. Atoms from other sources, and ones you added yourself, stay.
406
+ A load that raises leaves the previous definitions standing, so a
407
+ broken edit costs nothing but the error.
408
+
409
+ `!(import! &self path)` is the other door and loads a file that is
410
+ new or edited, skipping one that is neither. The two agree on what
411
+ a reload means and differ only in whether an unchanged file runs
412
+ again, which is SWI's consult/1 against its if(changed).
413
+
414
+ A .gz path is detected and read through the decompressed bytes.
415
+
416
+ `timeout` (seconds) and `inferences` (engine steps) bound the load
417
+ with the engine's own guards, raising TimeLimitError or
418
+ InferenceLimitError. A load is all or nothing: a stop takes back
419
+ everything the file had put in a space, the same way a load that
420
+ fails on a bad form does, because a file the space holds half of is
421
+ not a file it can replace later. run() is the entry point that
422
+ keeps finished work when a bound stops it. This is the one most
423
+ likely to be handed code the caller did not write, since a file can
424
+ carry `!` directives and an import graph, so it takes the same pair
425
+ its siblings take.
426
+ Runs against the default context's self space.
427
+ """
428
+ return engine().self.load(path, timeout=timeout, inferences=inferences)
429
+
430
+
431
+ def match(
432
+ *patterns: _Any,
433
+ where: _Any | None = None,
434
+ limit: int | None = None,
435
+ timeout: float | None = None,
436
+ inferences: int | None = None,
437
+ under: _Any = _UNSET,
438
+ into: _builtins.type | None = None,
439
+ ) -> _Any:
440
+ """Lazily match patterns against this space as one conjunction.
441
+
442
+ Variables shared between patterns join, the engine's own match/4
443
+ doing the joining. Columns are the variable names in first
444
+ appearance order. `where` is a guard term over the same variables,
445
+ evaluated per join and required true, so restrictions a pattern
446
+ cannot spell (an inequality) compose onto the match:
447
+
448
+ m.match(S.person(V.name, V.age), where=V.age.ge(18))
449
+
450
+ `limit` bounds the answers, the engine stopping at the count
451
+ rather than trimming afterwards. `timeout` (seconds) and
452
+ `inferences` (engine steps) bound the whole call, raising
453
+ TimeLimitError or InferenceLimitError when hit, for joins whose
454
+ size is not known in advance.
455
+
456
+ The returned Answers view pulls only what Python observes. ``bool``
457
+ pulls one row, exact-one operations pull at most two, and slicing
458
+ retains an Answers view. ``len`` uses an engine-side aggregate when
459
+ no row has yet been pulled.
460
+
461
+ ``under=`` interprets the same ask through an annotation algebra.
462
+ ``under=counting`` answers one integer computed by an engine
463
+ aggregate, including duplicate derivations without crossing their
464
+ rows into Python. Ordered carriers sort in their declared direction
465
+ before slicing, so ``m.match(q, under=ranked)[:3]`` is top-k and
466
+ ``under=tropical`` puts the cheapest annotation first. Other carriers
467
+ answer ``TaggedAnswer`` values with ``annotation``, ``why()`` and
468
+ ``under(other)``; the latter two reuse the retained derivation rather
469
+ than querying the space again. ``with metta.under(carrier)`` supplies
470
+ the carrier when this call has no explicit ``under=``.
471
+
472
+ `into=Rows` explicitly chooses the eager Rows face. Other `into=`
473
+ values shape each row into a dataclass, NamedTuple, or
474
+ TypedDict matched by field name, sqlite3's row_factory reading:
475
+ `m.match(S.edge(V.a, V.b), into=Edge)` answers `list[Edge]`,
476
+ and Rows stays the default so nothing is lost. A one-variable query
477
+ whose column holds complete constructor expressions rebuilds those
478
+ expressions instead: `m.match(V.edge, into=Edge)`.
479
+
480
+ m.match(S.Edge(V.x, V.y), S.Edge(V.y, V.z))
481
+ Runs against the default context's self space.
482
+ """
483
+ return engine().self.match(
484
+ *patterns, where=where, limit=limit, timeout=timeout, inferences=inferences, under=under, into=into
485
+ )
486
+
487
+
488
+ def add(*atoms: _Any) -> None:
489
+ """Add atoms to this space, one engine round-trip for the lot.
490
+ An (= ...) atom compiles as an equation. Every Atom shape the engine's
491
+ add-atom accepts crosses unchanged, including a bare Symbol, Grounded
492
+ value, and empty Expression; a free Variable receives the engine's own
493
+ insufficient-instantiation refusal.
494
+
495
+ A variable's NAME is not stored. `(rule $x $y)` reads back as
496
+ `(rule $_17902 $_17904)`, because a variable is an identity and not a
497
+ spelling. That is the right property for a logic engine and it is the
498
+ one thing about storage that surprises everybody once.
499
+
500
+ A library IS knowledge, so the same door imports it: ``m += lib.he``
501
+ performs ``!(import! <m> (library lib_he))`` with this space as the
502
+ target. An import is an effect, so it refuses to hide inside an atom
503
+ batch or share a call with stored atoms.
504
+ Runs against the default context's self space.
505
+ """ # noqa: D205 -- the API contract is one continuous invariant, not summary-and-body prose
506
+ return engine().self.add(*atoms)
507
+
508
+
509
+ def remove(atom: _Any, *more: _Any) -> bool | int:
510
+ """Remove ONE unifying occurrence and say whether one was there,
511
+ which is Python's own `list.remove` grain.
512
+
513
+ Variadic like `add` and `transfer`: several atoms ride one engine
514
+ crossing inside one transaction, and the answer counts the found,
515
+ so the one-atom call still reads as the truth value it always
516
+ was.
517
+
518
+ `space -= atom` is this same grain without the report, the way
519
+ `+=` is `add` without one: Python's in-place difference over a
520
+ MULTISET, whose own Python spelling is `collections.Counter`,
521
+ subtracts the multiplicity given rather than clearing the key.
522
+ That is the only reading under which the operators are inverses,
523
+ so `s += a; s -= a` leaves the space it found. `-=` classifies its
524
+ operand exactly as `+=` does, so the fact stream one door stores
525
+ the other subtracts, one occurrence per element, in one
526
+ transactional crossing.
527
+
528
+ The DRAIN is the pattern-shaped door: `del m[pattern]` takes every
529
+ unifying occurrence in one crossing and raises when nothing
530
+ matched, as Python's `del` does, and MeTTa spells it `remove-atom`
531
+ [source: engine/spaces/foreign.pl, remove_matching_atoms/2].
532
+ MeTTa spells this method's grain `subtract-atom`. This is the one
533
+ door that reports absence.
534
+
535
+ A bare variable is the remove-everything reading a multiset space
536
+ gives it, each atom leaving through its own proper path, equations
537
+ and their compiled clauses included.
538
+ Runs against the default context's self space.
539
+ """ # noqa: D205 -- the API contract is one continuous invariant, not summary-and-body prose
540
+ return engine().self.remove(atom, *more)
541
+
542
+
543
+ @_overload
544
+ def eval( # noqa: A001 -- eval is the ruled public verb
545
+ target: _Any,
546
+ *,
547
+ timeout: float | None = ...,
548
+ inferences: int | None = ...,
549
+ under: _Any = ...,
550
+ theory: _Any | None = ...,
551
+ interpreter: _Any | None = ...,
552
+ ) -> list[Atom | Undefined]: ...
553
+ @_overload
554
+ def eval( # noqa: A001 -- eval is the ruled public verb
555
+ target: _Any,
556
+ second: _Any,
557
+ /,
558
+ *more: _Any,
559
+ timeout: float | None = ...,
560
+ inferences: int | None = ...,
561
+ under: _Any = ...,
562
+ theory: _Any | None = ...,
563
+ interpreter: _Any | None = ...,
564
+ ) -> list[list[Atom | Undefined]]: ...
565
+ def eval( # noqa: A001 -- eval is the ruled public verb
566
+ target: _Any,
567
+ *more: _Any,
568
+ timeout: float | None = None,
569
+ inferences: int | None = None,
570
+ under: _Any = _UNSET,
571
+ theory: _Any | None = None,
572
+ interpreter: _Any | None = None,
573
+ ) -> list[Atom | Undefined] | list[list[Atom | Undefined]]:
574
+ """Evaluate a term, returning every answer.
575
+
576
+ This is what !(...) runs, minus the printing: the engine's
577
+ translate_expr over the term, then its goals. Nondeterminism means
578
+ the list can hold any number of answers, including none.
579
+
580
+ Variadic, and that is how evaluation BATCHES: several terms ride
581
+ one engine crossing and the answer is one group per term in call
582
+ order, run()'s own grouping carried to the term door. One term
583
+ keeps its flat list, so the scalar reading never changes shape.
584
+
585
+ Every answer carries its truth: an answer that is undefined under
586
+ Well Founded Semantics (a tabled loop through tnot, reachable via
587
+ translatePredicate or injected Prolog) arrives as an Undefined
588
+ holding the answer and the delay condition that makes it
589
+ undefined, never as an ordinary-looking value. A term to which no
590
+ rule applies is the ordinary answer itself; `eval_status()` names
591
+ that path `not-reducible`. run() does not carry the third truth
592
+ value; evaluate through eval() when it matters.
593
+
594
+ `bind()` binds named host values into the term before it evaluates,
595
+ exactly as it does for run(): inside `with m.bind({"x": tensor})`,
596
+ `m.eval("(decide x)")` hands the tensor itself to the rule, by
597
+ identity, rather than a printed form of it. The name is the SYMBOL x
598
+ and not the variable $x, on this door and the source door alike. The evaluation doors take the same
599
+ vocabulary the source door takes, so reaching for a term instead
600
+ of source text costs no change of spelling.
601
+
602
+ A key may be a NAME or an ATOM. A name means the symbol of that name,
603
+ which is what the engine's own substitution matches and what run()
604
+ takes. An atom means exactly that atom, so `bind({V.x: 5})` fills a
605
+ VARIABLE hole -- the one substitution `unify` reports and the one no
606
+ door could apply, because a variable crosses the wire as ['v', 'x']
607
+ where a symbol crosses as ['s', 'x'] and the engine matches names.
608
+
609
+ `timeout` (seconds) and `inferences` (engine steps) bound the call,
610
+ raising TimeLimitError or InferenceLimitError when hit. A surrounding
611
+ `capture()` scope collects printed text without changing the list.
612
+
613
+ `under`, `theory` and `interpreter` are answers()' three, and mean
614
+ exactly what they mean there; this door is that one materialised. A
615
+ surrounding `with metta.under(carrier)` reaches here too, which it did
616
+ not before: match() and answers() both honoured such a scope while
617
+ eval() ignored it in silence.
618
+ Runs against the default context's self space.
619
+ """
620
+ return engine().self.eval(
621
+ target, *more, timeout=timeout, inferences=inferences, under=under, theory=theory, interpreter=interpreter
622
+ )
623
+
624
+
625
+ def solve(pattern: _Any, subject: _Any) -> _Any:
626
+ """Run relational ``let`` and return bindings keyed by its variables.
627
+
628
+ ``solve(4, V.x - 1).x`` places the known value on let's pattern side,
629
+ lets the arithmetic relation solve backwards, and projects ``x``.
630
+ The answer template is derived from the pattern's variables followed
631
+ by any new subject variables, so either relational direction can
632
+ introduce the bindings and the third hand-written ``let`` argument
633
+ disappears.
634
+ Runs against the default context's self space.
635
+ """
636
+ return engine().self.solve(pattern, subject)
637
+
638
+
639
+ def doc(atom: _Any) -> Atom:
640
+ """Return this space's structured ``get-doc`` answer for one subject.
641
+
642
+ The answer is the ``(@doc ...)`` atom the engine holds for the
643
+ subject, whether it was documented in MeTTa source or built from a
644
+ Python docstring:
645
+
646
+ m.doc(S.area)
647
+ # (@doc-formal (@item area) (@kind function) (@desc "Circle area.") ...)
648
+
649
+ A subject with no documentation raises, exactly as ``type`` raises
650
+ for a subject ``get-type`` cannot answer.
651
+ Runs against the default context's self space.
652
+ """
653
+ return engine().self.doc(atom)
654
+
655
+
656
+ @_overload
657
+ def define(fn: _builtins.type, /, *, accessors: bool = ..., methods: bool = ...) -> _builtins.type: ... # type: ignore[overload-overlap]
658
+ @_overload
659
+ def define(
660
+ fn: _Callable[_P, _R],
661
+ /,
662
+ *,
663
+ name: str | None = ...,
664
+ accessors: bool = ...,
665
+ methods: bool = ...,
666
+ ) -> _Defined[_P, _R]: ...
667
+ @_overload
668
+ def define(*, name: str) -> _Callable[[_Callable[_P, _R]], _Defined[_P, _R]]: ...
669
+ @_overload
670
+ def define(
671
+ *,
672
+ prolog: str | _os.PathLike[str],
673
+ name: str | None = None,
674
+ ) -> _Callable[[_Callable[_P, _R]], _PrologBacked[_P, _R]]: ...
675
+ def define(
676
+ fn: _Callable[..., _Any] | None = None,
677
+ *,
678
+ prolog: str | _os.PathLike[str] | None = None,
679
+ name: str | None = None,
680
+ accessors: bool = True,
681
+ methods: bool = True,
682
+ ) -> _Any:
683
+ """Compile a Python function into MeTTa equations, decorator-style.
684
+
685
+ With `prolog=`, the Prolog file is registered and becomes the
686
+ function, and the Python stays as the reference twin rather than
687
+ being compiled:
688
+
689
+ @m.define(prolog=Path(__file__).parent / "fast.pl")
690
+ def vec_dot(a, b):
691
+ return sum(x * y for x, y in zip(a, b))
692
+
693
+ m.eval("(vec-dot (1 2) (3 4))")[0] # the Prolog answer
694
+ vec_dot.py((1, 2), (3, 4)) # the reference answers
695
+
696
+ Rewriting a defined function in Prolog for speed used to mean
697
+ deleting the Python and the differential oracle with it. Here both
698
+ are declared together and `metta.testing.check_twin` proves they
699
+ agree on ground inputs. The file must register the function's own
700
+ MeTTa name and at the twin's arity, inputs then one output, and
701
+ says so if it does not; its `metta_export` declaration owns the
702
+ types, so annotations on the Python are documentation only.
703
+
704
+ Written for whoever is fluent in Python rather than s-expressions:
705
+ the body is read as syntax and lowered deterministically, refusals
706
+ name the construct, the line and what to write instead, and the
707
+ original stays reachable as .py, a twin the equations can be checked
708
+ against on any ground input.
709
+
710
+ @m.define
711
+ def add_one(n):
712
+ return n + 1
713
+
714
+ add_one(5) # [6], evaluated by the engine
715
+ S.add_one(5) # (add_one 5), staged as data
716
+ add_one.py(5) # 6, ordinary Python
717
+
718
+ The equation's implicit name applies the factories' total mechanical
719
+ map, replacing each underscore with a hyphen. ``name=`` is the exact
720
+ quoted-name escape for punctuation that map cannot preserve:
721
+
722
+ @m.define(name="add-one")
723
+ def add_one(n):
724
+ return n + 1
725
+
726
+ This is rung 4 of the naming ladder applied to the definition door
727
+ itself: ``def not_provable`` lands as ``not-provable``. An authored
728
+ MeTTa underscore therefore uses explicit ``name="not_provable"``.
729
+
730
+ A generator compiles to nondeterminism (each yield one answer), a
731
+ lambda to the engine's own |->, a comprehension to map-atom and
732
+ filter-atom, and match(Pattern(x, y), template) to a match against
733
+ the running space, lowercase free names in the pattern binding as
734
+ variables.
735
+ Runs against the default context's self space.
736
+ """
737
+ return engine().self.define(fn, prolog=prolog, name=name, accessors=accessors, methods=methods)
738
+
739
+
740
+ @_overload
741
+ def op(
742
+ fn: _Callable[_P, _R],
743
+ /,
744
+ *,
745
+ name: str | None = ...,
746
+ # policy-inventory-exempt: mechanism-internal; reason=mirrored from the Space door of the same name, whose adjacent exemption carries the reason; evidence=extensions/python/metta/ops.py:_operation_kind
747
+ transport: _Literal['encoded', 'raw'] = ...,
748
+ effect: _EffectClass | str,
749
+ declarations: _Iterable[Atom] = ...,
750
+ arities: list[int] | None = ...,
751
+ inverse: _Callable | None = ...,
752
+ ) -> _Callable[_P, _R]: ...
753
+ @_overload
754
+ def op(
755
+ *,
756
+ name: str | None = ...,
757
+ # policy-inventory-exempt: mechanism-internal; reason=mirrored from the Space door of the same name, whose adjacent exemption carries the reason; evidence=extensions/python/metta/ops.py:_operation_kind
758
+ transport: _Literal['encoded', 'raw'] = ...,
759
+ effect: _EffectClass | str,
760
+ declarations: _Iterable[Atom] = ...,
761
+ arities: list[int] | None = ...,
762
+ inverse: _Callable | None = ...,
763
+ ) -> _Callable[[_Callable[_P, _R]], _Callable[_P, _R]]: ...
764
+ def op(
765
+ fn: _Callable | None = None,
766
+ *,
767
+ name: str | None = None,
768
+ # policy-inventory-exempt: mechanism-internal; reason=mirrored from the Space door of the same name, whose adjacent exemption carries the reason; evidence=extensions/python/metta/ops.py:_operation_kind
769
+ transport: _Literal['encoded', 'raw'] = 'encoded',
770
+ effect: _EffectClass | str | None = None,
771
+ declarations: _Iterable[Atom] = (),
772
+ arities: list[int] | None = None,
773
+ inverse: _Callable | None = None,
774
+ ) -> _Any:
775
+ """Register a Python callable as a MeTTa function, decorator-style.
776
+
777
+ @m.op(effect=EffectClass.pureStructural)
778
+ def double(x: int) -> int:
779
+ return 2 * x # !(double 21) -> 42
780
+
781
+ @m.op(effect=EffectClass.nondeterministicReadOnly)
782
+ def neighbours(n: int):
783
+ yield n - 1 # a generator is nondeterministic
784
+ yield n + 1
785
+
786
+ An implicit Python name maps underscores to MeTTa hyphens. ``name=``
787
+ is exact, for source vocabularies that deliberately use underscores.
788
+
789
+ A name must read back as one MeTTa symbol. A space, parenthesis,
790
+ quote, comment opener, variable spelling, number, boolean, or another
791
+ registered reader token is refused before any registry changes, with
792
+ the name and the conflicting character in the error.
793
+
794
+ Annotations become ordinary `(: ...)` declarations. An unannotated
795
+ callable makes no type claim. `transport="raw"` skips wire encoding
796
+ both ways and is reflected as raw_det or raw_many in `(op ...)`;
797
+ symbols then reach Python as strings, so encoded transport is the
798
+ fidelity-preserving default. unregister_op(name) removes every
799
+ registered arity and every declaration the registration owns.
800
+
801
+ An `Atom` parameter changes evaluation order. The declaration tells
802
+ the compiler to pass the argument as written, before it reduces:
803
+
804
+ @m.op(effect=EffectClass.pureStructural)
805
+ def anyatom(term: Atom) -> Atom:
806
+ return term
807
+
808
+ # with (= (side) 42), !(anyatom (side)) answers (side)
809
+
810
+ An unconstrained parameter receives the evaluated value instead, so
811
+ the otherwise identical `def anyval(term): return term` answers 42.
812
+ Use `Atom` only when the operation deliberately implements syntax or
813
+ a control form; it is not just a static hint.
814
+
815
+ An encoded generator may instead yield exact tuples as positional
816
+ relation rows, or exact dicts keyed by parameter name as sparse rows.
817
+ The engine unifies each candidate against the written call, so one
818
+ implementation serves free, partially bound, and ground arguments:
819
+
820
+ @m.op
821
+ def route(origin, destination):
822
+ yield (S.paris, S.lyon)
823
+ yield {"destination": S.nice} # origin is unconstrained
824
+
825
+ # route(V.origin, S.lyon).rows[0].origin == S.paris
826
+
827
+ Each matching occurrence answers unit and duplicate yields remain
828
+ duplicate answers. Use `Answer(value=...)` when an exact tuple or dict
829
+ is the result value rather than a parameter row. Relational rows
830
+ require encoded transport; raw calls cannot carry unbound argument
831
+ positions.
832
+
833
+ When evaluation order stays ordinary but the callable needs the
834
+ resulting Atom wrappers, declare that policy as data:
835
+
836
+ m.op(
837
+ inspect_atom,
838
+ name="inspect-atom",
839
+ effect=EffectClass.pureStructural,
840
+ declarations=[parse("(arguments inspect-atom atoms)")],
841
+ )
842
+
843
+ The declaration is matchable in &metta and is retired with the
844
+ operation. Raw transport refuses this declaration because it bypasses
845
+ the atom codec entirely.
846
+
847
+ The cost ladder, measured on the maintained box in inferences per
848
+ call, explains the transport choice:
849
+
850
+ native MeTTa function 9.11 the floor
851
+ transport="raw" 10.11 opaque handles, near-native
852
+ encoded 17.11 encoded values
853
+ encoded, typed literal 17.11 the check hoists to compile
854
+ py-call, dotted 22.11 the ad-hoc escape hatch
855
+
856
+ The ergonomic default (encoded, typed) costs about 1.7x raw on the
857
+ counter and more on wall clock, since encoding walks the value both
858
+ ways; a registered raw operation measured 0.85us against 2.26us
859
+ encoded. Bulk data should stay opaque: one transparent 64-float
860
+ crossing costs 330 inferences where the handle costs 10.
861
+
862
+ `inverse=` remains the distinct-output form. Use it when the forward
863
+ operation returns a result and a separate callable must recover the
864
+ arguments from that result:
865
+
866
+ m.op(
867
+ cons,
868
+ name="cons",
869
+ inverse=uncons,
870
+ effect=EffectClass.pureStructural,
871
+ )
872
+ # !(let (cons $h $t) (1 2 3) ($h $t)) -> (1 (2 3))
873
+
874
+ It takes the result and returns the arguments, as a tuple, or the
875
+ bare value at arity one; a generator enumerates every preimage, and
876
+ None or NotReducible means there is none. It runs only when the arguments
877
+ are not ground and the result is, so a forward call never reaches it,
878
+ and an operation without one compiles exactly what it did before.
879
+
880
+ A parameter annotated `metta.MeTTa` is the framework's to fill,
881
+ FastAPI's Depends read with the house convention that the
882
+ annotation is the request. The engine injects itself bound to the
883
+ CALLING context's space, so an operation invoked from a program
884
+ running in &kb queries &kb; the slot never counts toward MeTTa
885
+ arities or the declared arrow, and only operations that ask pay
886
+ the weaving:
887
+
888
+ @m.op(effect=EffectClass.nondeterministicReadOnly)
889
+ def related(term, engine: metta.MeTTa):
890
+ for row in engine.match(Expression(S.link, term, V.x)):
891
+ yield row[0]
892
+
893
+ Every operation declares its strongest observable effect. The five
894
+ ordered choices are ``pureStructural``, ``readOnlyLookup``,
895
+ ``nondeterministicReadOnly``, ``writesState``, and ``oracleIO``:
896
+
897
+ m.op(
898
+ len,
899
+ name="size",
900
+ effect=EffectClass.pureStructural,
901
+ )
902
+ # (= (count-of $x) (size $x)) is cacheable
903
+
904
+ It is an allow-list on purpose. An operation that does not say so is
905
+ refused by name in a cached body, loudly, rather than cached and
906
+ quietly wrong.
907
+ Runs against the default context's self space.
908
+ """
909
+ return engine().self.op(
910
+ fn, name=name, transport=transport, effect=effect, declarations=declarations, arities=arities, inverse=inverse
911
+ )
912
+
913
+
914
+ def pure(fn: _Callable | None = None, /, **options: _Any) -> _Any:
915
+ """An operation whose answer depends only on its arguments.
916
+
917
+ @m.pure
918
+ def double(x: int) -> int:
919
+ return 2 * x
920
+
921
+ The cache-safe class, and the only one memoization and tabling admit
922
+ without an explicit policy.
923
+
924
+ A GENERATOR written this way is lifted to `nondeterministicReadOnly`,
925
+ because a generator is nondeterministic whatever it declares, and the
926
+ registration reads that off the function rather than asking. The lift
927
+ only ever raises the rank, so it widens the answer-count claim and
928
+ never weakens the effect claim -- but it does mean a generator is not
929
+ cache-safe, which is the whole reason it is lifted out of this class
930
+ [tested: test_a_generator_is_lifted_to_the_nondeterministic_rank;
931
+ commit=7e5091540a8dc0903bcee24f3e5b8b85a19f805f].
932
+
933
+ Every ``op`` keyword applies: ``name``, ``arities``,
934
+ ``declarations``, ``inverse`` and ``transport``. They arrive as
935
+ ``**options`` and forward unchanged, so the signature above shows
936
+ the mechanism and this line shows the surface.
937
+ Runs against the default context's self space.
938
+ """
939
+ return engine().self.pure(fn, **options)
940
+
941
+
942
+ def reads(fn: _Callable | None = None, /, **options: _Any) -> _Any:
943
+ """An operation that reads stable state without changing it.
944
+
945
+ Every ``op`` keyword applies: ``name``, ``arities``,
946
+ ``declarations``, ``inverse`` and ``transport``. They arrive as
947
+ ``**options`` and forward unchanged, so the signature above shows
948
+ the mechanism and this line shows the surface.
949
+ Runs against the default context's self space.
950
+ """
951
+ return engine().self.reads(fn, **options)
952
+
953
+
954
+ def writes(fn: _Callable | None = None, /, **options: _Any) -> _Any:
955
+ """An operation that changes engine or host state.
956
+
957
+ Every ``op`` keyword applies: ``name``, ``arities``,
958
+ ``declarations``, ``inverse`` and ``transport``. They arrive as
959
+ ``**options`` and forward unchanged, so the signature above shows
960
+ the mechanism and this line shows the surface.
961
+ Runs against the default context's self space.
962
+ """
963
+ return engine().self.writes(fn, **options)
964
+
965
+
966
+ def io(fn: _Callable | None = None, /, **options: _Any) -> _Any:
967
+ """An operation that observes an external oracle.
968
+
969
+ A clock, randomness, a network, a file, another runtime.
970
+
971
+ @m.io
972
+ def now() -> float:
973
+ return time.time()
974
+
975
+ The fail-closed top of the lattice. Declare it when what the operation
976
+ reaches is decided at run time or by a library the engine cannot bound.
977
+
978
+ Every ``op`` keyword applies: ``name``, ``arities``,
979
+ ``declarations``, ``inverse`` and ``transport``. They arrive as
980
+ ``**options`` and forward unchanged, so the signature above shows
981
+ the mechanism and this line shows the surface.
982
+ Runs against the default context's self space.
983
+ """
984
+ return engine().self.io(fn, **options)
985
+
986
+
987
+ def stats() -> _StatsBlock:
988
+ """The engine's own counters over a with-block, as deltas.
989
+
990
+ with m.stats() as s:
991
+ m.match(S.edge(V.x, V.y), S.edge(V.y, V.z))
992
+ s.inferences # engine steps the block spent
993
+ s.cputime # engine CPU seconds
994
+ s.walltime # wall seconds, Python's clock
995
+ s.gc_count, s.gc_freed, s.gc_time
996
+ s.table_bytes # answer-table bytes grown, tabling's memory
997
+
998
+ The counters are SWI's statistics/2 read on the CALLING thread, so
999
+ a block that runs other threads' engine work counts that work too;
1000
+ the honest reading is "what this thread saw the engine do while the
1001
+ block ran". A lazy cursor is the exception, and a large one: its
1002
+ goal runs in an SWI engine, an engine counts its own inferences,
1003
+ and this thread cannot see them. Draining 20,000 rows through the
1004
+ match cursor reports 40,049 inferences against about 381,000 the
1005
+ cursor's engine really spent, 10.5% of the work; the real cost is
1006
+ readable off the `inferences` budget, which does count the engine
1007
+ [measured 2026-08-27]. The evaluation cursor behind `answers()`
1008
+ does report its engine's spend, so that one is whole. The z3py
1009
+ Solver.statistics() reading, on the engine this library actually
1010
+ has.
1011
+ Runs against the default context's self space.
1012
+ """
1013
+ return engine().self.stats()
1014
+
1015
+
1016
+ def limits(
1017
+ *,
1018
+ timeout: float | None = None,
1019
+ inferences: int | None = None,
1020
+ stack: int | None = None,
1021
+ ) -> _ScopedLimits:
1022
+ """Scoped default bounds for every call in the with-block:
1023
+
1024
+ with m.limits(inferences=1_000_000, timeout=2.0):
1025
+ m.match(...) # bounded without saying so again
1026
+
1027
+ decimal.localcontext's shape, contextvars underneath, so the
1028
+ scope is async-correct and per-task. A per-call timeout= or
1029
+ inferences= still overrides, which is the whole ladder: one
1030
+ block replaces the parameter forest, and the forest remains
1031
+ for whoever wants per-call control.
1032
+
1033
+ stack= is SWI's combined stack ceiling in BYTES, the bound a
1034
+ runaway recursion hits as a StackOverflow error atom. It is NOT
1035
+ MeTTa's reduction depth: that is the max-stack-depth pragma,
1036
+ `(with-pragma! ((max-stack-depth N)) expr)`, which counts
1037
+ reduction steps and is scoped in the program text.
1038
+ Runs against the default context's self space.
1039
+ """ # noqa: D415 -- the first line deliberately introduces the indented example that follows
1040
+ return engine().self.limits(timeout=timeout, inferences=inferences, stack=stack)
1041
+
1042
+
1043
+ def speculate() -> _ScopedExecution:
1044
+ """Run each source against a snapshot and discard its writes.
1045
+
1046
+ Runs against the default context's self space.
1047
+ """
1048
+ return engine().self.speculative()
1049
+
1050
+
1051
+ def trace(source: Atom | str, max_events: int = 1000000):
1052
+ """Run a TERM, or source, under the engine's reduction trace and
1053
+ answer TraceEvent records: what entered reduction at which depth,
1054
+ what it answered, and which reductions failed (a call with no
1055
+ exit). `m.trace(S.fib(10))` is the ordinary spelling, the same
1056
+ argument `answers` and `eval` take; a string is still a string.
1057
+ What is traced executes for real, writes included, like run();
1058
+ the wrap exists only while tracing, so untraced calls pay
1059
+ nothing. max_events bounds the recording, raising past it rather
1060
+ than accumulating a long run's trace without limit.
1061
+ Runs against the default context's self space.
1062
+ """ # noqa: D205 -- the API contract is one continuous invariant, not summary-and-body prose
1063
+ return engine().self.trace(source, max_events)
1064
+
1065
+
1066
+ # ------------------------------------------ end of generated module tier
1067
+
1068
+
1069
+ def _ambient_space():
1070
+ """Open the space selected by the active Python or engine context."""
1071
+ return engine().space(current_space())
1072
+
1073
+
1074
+ @_overload
1075
+ def unify(left: _Any, right: _Any) -> _Mapping[Atom, Atom] | None: ...
1076
+
1077
+
1078
+ @_overload
1079
+ def unify(left: _Any, right: _Any, then: _Any, els: _Any) -> _Answers[Atom]: ...
1080
+
1081
+
1082
+ def unify(
1083
+ left: _Any,
1084
+ right: _Any,
1085
+ then: _Any = _OMITTED,
1086
+ els: _Any = _OMITTED,
1087
+ ) -> _Any:
1088
+ """Unify two atoms, or evaluate the four-argument engine conditional.
1089
+
1090
+ ``unify(a, b)`` returns a symmetric bindings mapping or ``None`` without
1091
+ starting the engine. ``unify(a, b, then, els)`` evaluates
1092
+ ``(unify a b then els)`` in the ambient space, once per binding set on
1093
+ success and through ``els`` only when no binding exists. A compiled body
1094
+ lowers the same four-argument spelling directly to that engine form.
1095
+ """
1096
+ if then is els is _OMITTED:
1097
+ return _unify_atoms(left, right)
1098
+ if then is not _OMITTED and els is not _OMITTED:
1099
+ return _ambient_space().answers(S.unify(left, right, then, els))
1100
+ given = 3
1101
+ msg = f"unify() takes exactly 2 or 4 arguments ({given} given)"
1102
+ raise TypeError(msg)
1103
+
1104
+
1105
+ def superpose(*alternatives: _Any):
1106
+ """Evaluate expression-position alternatives in the ambient space.
1107
+
1108
+ With no alternatives this evaluates ``(empty)``. Inside a compiled
1109
+ definition the compiler lowers this same function spelling directly to
1110
+ ``(superpose (...))``.
1111
+ """
1112
+ target = S.empty() if not alternatives else S.superpose(Expression(alternatives))
1113
+ return _ambient_space().answers(target)
1114
+
1115
+
1116
+ def accept(atom: _Any = _OMITTED) -> Expression:
1117
+ """Build a pre-add verdict that keeps or replaces the offered atom."""
1118
+ return S.accept() if atom is _OMITTED else S.accept(atom)
1119
+
1120
+
1121
+ def refuse(words: _Any) -> Expression:
1122
+ """Build a pre-add verdict that rejects a write with the judge's words."""
1123
+ return S.refuse(words)
1124
+
1125
+
1126
+ def drop() -> Expression:
1127
+ """Build a pre-add verdict that silently skips the offered atom."""
1128
+ return S.drop()
1129
+
1130
+
1131
+ def under(algebra: _Any):
1132
+ """Scope the default algebra for match, call-answer, and fold carriers.
1133
+
1134
+ The scope is task-local, nests with token restoration, and never mutates
1135
+ the catalog. An explicit ``under=`` on a carrier outranks this default.
1136
+ """
1137
+ scoped = _importlib.import_module(f"{__name__}._under")
1138
+ return scoped.ScopedUnder(algebra)
1139
+
1140
+
1141
+ _ROOT_IMPLEMENTATION_VERBS = {
1142
+ "define": define,
1143
+ "trace": trace,
1144
+ }
1145
+
1146
+
1147
+ __all__ = [
1148
+ "FALSE",
1149
+ "TRUE",
1150
+ "UNIT",
1151
+ "Answer",
1152
+ "Atom",
1153
+ "Bindings",
1154
+ "Config",
1155
+ "Defined",
1156
+ "Expression",
1157
+ "G",
1158
+ "Grounded",
1159
+ "Handle",
1160
+ "Library",
1161
+ "MeTTa",
1162
+ "MettaError",
1163
+ "NotReducible",
1164
+ "S",
1165
+ "Space",
1166
+ "SpaceProvider",
1167
+ "State",
1168
+ "Symbol",
1169
+ "Timeout",
1170
+ "Undefined",
1171
+ "V",
1172
+ "Variable",
1173
+ "__version__",
1174
+ "accept",
1175
+ "add",
1176
+ "aio",
1177
+ "algebra",
1178
+ "and_",
1179
+ "arrays",
1180
+ "arrow",
1181
+ "attach",
1182
+ "boot",
1183
+ "casting",
1184
+ "catalog",
1185
+ "channel",
1186
+ "config",
1187
+ "convert",
1188
+ "counting",
1189
+ "current_space",
1190
+ "define",
1191
+ "derivation",
1192
+ "doc",
1193
+ "drop",
1194
+ "engine",
1195
+ "equation",
1196
+ "eval",
1197
+ "events",
1198
+ "every",
1199
+ "fn",
1200
+ "foreign",
1201
+ "forms",
1202
+ "fresh",
1203
+ "ground",
1204
+ "if_",
1205
+ "in_",
1206
+ "integrate",
1207
+ "io",
1208
+ "lib",
1209
+ "limits",
1210
+ "lint",
1211
+ "manifest",
1212
+ "match",
1213
+ "not_",
1214
+ "op",
1215
+ "or_",
1216
+ "par_map",
1217
+ "parallel",
1218
+ "parse",
1219
+ "paths",
1220
+ "prob",
1221
+ "prov",
1222
+ "pure",
1223
+ "py",
1224
+ "race",
1225
+ "ranked",
1226
+ "reads",
1227
+ "reflection",
1228
+ "refuse",
1229
+ "remote",
1230
+ "remove",
1231
+ "rules",
1232
+ "run",
1233
+ "seg",
1234
+ "solve",
1235
+ "space",
1236
+ "spaces",
1237
+ "spawn",
1238
+ "speculate",
1239
+ "stats",
1240
+ "strategies",
1241
+ "structures",
1242
+ "subscribe",
1243
+ "superpose",
1244
+ "tables",
1245
+ "testing",
1246
+ "trace",
1247
+ "tropical",
1248
+ "typed",
1249
+ "under",
1250
+ "unify",
1251
+ "view",
1252
+ "vocabularies",
1253
+ "wire",
1254
+ "writes",
1255
+ ]
1256
+
1257
+ # Importing a submodule writes it onto its parent package. These concrete
1258
+ # modules remain explicitly importable, but they are implementation modules,
1259
+ # not root attributes. The verbs table is bound by here, so this is the
1260
+ # end-of-init sweep the partial-init guard in the helper defers to.
1261
+ _rehide_implementation_modules()