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.
- metta/__init__.py +1261 -0
- metta/__main__.py +255 -0
- metta/_api_types.py +18 -0
- metta/_async_ops.py +464 -0
- metta/_atom_namespace.py +256 -0
- metta/_atom_wire.py +334 -0
- metta/_atoms_core.py +1886 -0
- metta/_call_binding.py +65 -0
- metta/_callable_mentions.py +110 -0
- metta/_callbacks.py +173 -0
- metta/_codec_kit.py +416 -0
- metta/_compliance.py +627 -0
- metta/_config.py +176 -0
- metta/_contract.py +252 -0
- metta/_convert_build.py +265 -0
- metta/_convert_project.py +392 -0
- metta/_convert_registry.py +452 -0
- metta/_define_context.py +221 -0
- metta/_define_expression.py +1549 -0
- metta/_define_facts.py +281 -0
- metta/_define_loops.py +173 -0
- metta/_define_statements.py +1969 -0
- metta/_define_twins.py +252 -0
- metta/_documentation.py +189 -0
- metta/_engine.py +1004 -0
- metta/_fn.py +708 -0
- metta/_fn.pyi +359 -0
- metta/_gateway_compliance.py +349 -0
- metta/_host_island.py +104 -0
- metta/_json.py +117 -0
- metta/_library.py +200 -0
- metta/_lint_analysis.py +950 -0
- metta/_lint_events.py +728 -0
- metta/_lint_model.py +211 -0
- metta/_name_mapping.py +171 -0
- metta/_network.py +195 -0
- metta/_object_fields.py +28 -0
- metta/_operator_lowerings.py +119 -0
- metta/_ops.py +838 -0
- metta/_optional.py +31 -0
- metta/_parameterized.py +280 -0
- metta/_persistent.py +1542 -0
- metta/_prelude.py +445 -0
- metta/_rules.py +228 -0
- metta/_runtime/engine/.gitignore +13 -0
- metta/_runtime/engine/bench-baseline.json +88 -0
- metta/_runtime/engine/bench.pl +454 -0
- metta/_runtime/engine/bench.py +343 -0
- metta/_runtime/engine/bench.sh +81 -0
- metta/_runtime/engine/build.sh +46 -0
- metta/_runtime/engine/check.sh +504 -0
- metta/_runtime/engine/duals.pl +1438 -0
- metta/_runtime/engine/ext_points.pl +1754 -0
- metta/_runtime/engine/filereader/source_lifecycle.pl +766 -0
- metta/_runtime/engine/filereader.pl +1634 -0
- metta/_runtime/engine/json_codec.c +1219 -0
- metta/_runtime/engine/json_codec.pl +315 -0
- metta/_runtime/engine/kernel.pl +131 -0
- metta/_runtime/engine/main.pl +104 -0
- metta/_runtime/engine/mbr.c +355 -0
- metta/_runtime/engine/metta/control.pl +1365 -0
- metta/_runtime/engine/metta/effects.pl +2437 -0
- metta/_runtime/engine/metta/input_guards.pl +329 -0
- metta/_runtime/engine/metta/interop.pl +1603 -0
- metta/_runtime/engine/metta/operators.pl +960 -0
- metta/_runtime/engine/metta/registration.pl +675 -0
- metta/_runtime/engine/metta/runtime.pl +664 -0
- metta/_runtime/engine/metta/space_hooks.pl +1185 -0
- metta/_runtime/engine/metta/terms.pl +833 -0
- metta/_runtime/engine/metta/types.pl +1608 -0
- metta/_runtime/engine/metta.pl +1593 -0
- metta/_runtime/engine/metta_token.h +158 -0
- metta/_runtime/engine/narrowing.pl +482 -0
- metta/_runtime/engine/parser.pl +1480 -0
- metta/_runtime/engine/prelude.metta +696 -0
- metta/_runtime/engine/qlf_boot.pl +180 -0
- metta/_runtime/engine/reader.c +923 -0
- metta/_runtime/engine/scc.pl +155 -0
- metta/_runtime/engine/spaces/bounded_matching.pl +989 -0
- metta/_runtime/engine/spaces/catalog.pl +1548 -0
- metta/_runtime/engine/spaces/foreign.pl +2080 -0
- metta/_runtime/engine/spaces/lifecycle.pl +1836 -0
- metta/_runtime/engine/spaces/native_matching.pl +676 -0
- metta/_runtime/engine/spaces/segment_matching.pl +895 -0
- metta/_runtime/engine/spaces.pl +373 -0
- metta/_runtime/engine/specializer.pl +831 -0
- metta/_runtime/engine/support_graph.pl +787 -0
- metta/_runtime/engine/test.sh +87 -0
- metta/_runtime/engine/tracer.pl +221 -0
- metta/_runtime/engine/translator/analysis.pl +1479 -0
- metta/_runtime/engine/translator/lowering.pl +1755 -0
- metta/_runtime/engine/translator/runtime.pl +1425 -0
- metta/_runtime/engine/translator/special_forms.pl +1925 -0
- metta/_runtime/engine/translator/typing.pl +702 -0
- metta/_runtime/engine/translator.pl +377 -0
- metta/_runtime/engine/translator_rules.pl +651 -0
- metta/_runtime/engine/trs.pl +506 -0
- metta/_runtime/engine/type_rules.pl +246 -0
- metta/_runtime/engine/writer.c +915 -0
- metta/_runtime/extensions/mork/extension.pl +34 -0
- metta/_runtime/extensions/python/bridge.pl +844 -0
- metta/_runtime/extensions/python/extension.pl +23 -0
- metta/_runtime/extensions/python/helper.pl +34 -0
- metta/_runtime/extensions/python/metta/shim.pl +4900 -0
- metta/_runtime/extensions/python/metta_py.py +377 -0
- metta/_runtime/lib/.gitignore +4 -0
- metta/_runtime/lib/builtin_mods/skel.metta +23 -0
- metta/_runtime/lib/builtin_mods/skel.pl +17 -0
- metta/_runtime/lib/lib_builtin_types/lib_builtin_types.metta +508 -0
- metta/_runtime/lib/lib_combinatorics/lib_combinatorics.metta +33 -0
- metta/_runtime/lib/lib_conformance/lib_conformance.metta +26 -0
- metta/_runtime/lib/lib_conformance/lib_conformance.pl +411 -0
- metta/_runtime/lib/lib_constraints/lib_constraints.metta +30 -0
- metta/_runtime/lib/lib_constraints/lib_constraints.pl +166 -0
- metta/_runtime/lib/lib_crypto/lib_crypto.metta +17 -0
- metta/_runtime/lib/lib_crypto/lib_crypto.pl +23 -0
- metta/_runtime/lib/lib_datastructures/lib_datastructures.metta +201 -0
- metta/_runtime/lib/lib_datetime/lib_datetime.metta +8 -0
- metta/_runtime/lib/lib_datetime/lib_datetime.pl +10 -0
- metta/_runtime/lib/lib_derived/lib_derived.metta +44 -0
- metta/_runtime/lib/lib_dict/lib_dict.metta +78 -0
- metta/_runtime/lib/lib_doc/lib_doc.metta +44 -0
- metta/_runtime/lib/lib_file/lib_file.metta +51 -0
- metta/_runtime/lib/lib_file/lib_file.pl +287 -0
- metta/_runtime/lib/lib_gitimport/lib_gitimport.pl +385 -0
- metta/_runtime/lib/lib_he/lib_he.metta +50 -0
- metta/_runtime/lib/lib_import/lib_import.metta +41 -0
- metta/_runtime/lib/lib_import/lib_import.pl +143 -0
- metta/_runtime/lib/lib_json/lib_json.metta +20 -0
- metta/_runtime/lib/lib_json/lib_json.pl +211 -0
- metta/_runtime/lib/lib_measure/lib_measure.metta +102 -0
- metta/_runtime/lib/lib_memo/lib_memo.metta +23 -0
- metta/_runtime/lib/lib_memo/lib_memo.pl +1750 -0
- metta/_runtime/lib/lib_memo/lib_memo_doc.md +226 -0
- metta/_runtime/lib/lib_mm2/lib_mm2.metta +35 -0
- metta/_runtime/lib/lib_nars/lib_nars.metta +275 -0
- metta/_runtime/lib/lib_patrick/lib_patrick.metta +27 -0
- metta/_runtime/lib/lib_pln/lib_pln.metta +474 -0
- metta/_runtime/lib/lib_redis/lib_redis.metta +15 -0
- metta/_runtime/lib/lib_redis/lib_redis.pl +290 -0
- metta/_runtime/lib/lib_reflect/lib_reflect.metta +93 -0
- metta/_runtime/lib/lib_reflect/lib_reflect.pl +140 -0
- metta/_runtime/lib/lib_regex/lib_regex.metta +24 -0
- metta/_runtime/lib/lib_regex/lib_regex.pl +61 -0
- metta/_runtime/lib/lib_roman/lib_roman.metta +110 -0
- metta/_runtime/lib/lib_soft/lib_soft.metta +70 -0
- metta/_runtime/lib/lib_spaces/lib_spaces.metta +35 -0
- metta/_runtime/lib/lib_strategy/lib_strategy.metta +272 -0
- metta/_runtime/lib/lib_string/lib_string.metta +37 -0
- metta/_runtime/lib/lib_string/lib_string.pl +258 -0
- metta/_runtime/lib/lib_tabling/lib_tabling.metta +68 -0
- metta/_runtime/lib/lib_tabling/lib_tabling.pl +540 -0
- metta/_runtime/lib/lib_thread/lib_thread.metta +179 -0
- metta/_runtime/lib/lib_thread/lib_thread.pl +1809 -0
- metta/_runtime/lib/lib_thread/lib_thread_doc.md +283 -0
- metta/_runtime/lib/lib_torch/lib_torch.metta +44 -0
- metta/_runtime/lib/lib_vector/lib_vector.metta +24 -0
- metta/_runtime/lib/lib_zar/lib_zar.metta +49 -0
- metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.metta +181 -0
- metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.pl +284 -0
- metta/_runtime/lib/minimal_metta_lib/minimal_metta_lib.py +82 -0
- metta/_runtime/tests/codec/corpus.json +620 -0
- metta/_saga.py +935 -0
- metta/_source_forms.py +92 -0
- metta/_space.py +6237 -0
- metta/_space_definitions.py +1057 -0
- metta/_space_diagnostics.py +146 -0
- metta/_space_execution.py +891 -0
- metta/_space_objects.py +1375 -0
- metta/_space_persistence.py +364 -0
- metta/_space_query.py +124 -0
- metta/_state.py +79 -0
- metta/_task_context.py +150 -0
- metta/_tokens.py +24 -0
- metta/_trace.py +96 -0
- metta/_type_annotations.py +417 -0
- metta/_under.py +51 -0
- metta/_version.py +8 -0
- metta/_world.py +337 -0
- metta/aio.py +3160 -0
- metta/algebra.py +1408 -0
- metta/answer.py +138 -0
- metta/arrays.py +817 -0
- metta/atoms.py +619 -0
- metta/benchmarking.py +1075 -0
- metta/casting.py +117 -0
- metta/cli.py +98 -0
- metta/convert.py +41 -0
- metta/define.py +1108 -0
- metta/derivation.py +247 -0
- metta/errors.py +538 -0
- metta/events.py +762 -0
- metta/foreign.py +923 -0
- metta/integrate.py +660 -0
- metta/ipython.py +73 -0
- metta/lint.py +93 -0
- metta/manifest.py +501 -0
- metta/ops.py +1049 -0
- metta/parallel.py +542 -0
- metta/paths.py +155 -0
- metta/py.typed +0 -0
- metta/pytest_plugin.py +39 -0
- metta/remote.py +1909 -0
- metta/results.py +1216 -0
- metta/shim.pl +4900 -0
- metta/spaces.py +668 -0
- metta/strategies.py +58 -0
- metta/structures.py +734 -0
- metta/subscribe.py +347 -0
- metta/tables.py +597 -0
- metta/testing.py +989 -0
- metta/vocabularies.py +372 -0
- metta/wire.py +17 -0
- pymetta-0.2.0.dist-info/METADATA +713 -0
- pymetta-0.2.0.dist-info/RECORD +219 -0
- pymetta-0.2.0.dist-info/WHEEL +5 -0
- pymetta-0.2.0.dist-info/entry_points.txt +5 -0
- pymetta-0.2.0.dist-info/licenses/LICENSE +21 -0
- 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()
|