clm-kernel 0.0.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 (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. clm_kernel-0.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,726 @@
1
+ """
2
+ Lambda Runtime - PTR interface for Lambda Calculus
3
+
4
+ Provides a unified runtime interface for Lambda Calculus operations,
5
+ integrating the parser and all conversion modules.
6
+
7
+ This module provides Python parity with mcard-js/src/ptr/lambda/LambdaRuntime.ts
8
+ """
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Any, Literal
12
+
13
+ from clm.layer2.card_collection import CardCollection
14
+ from clm.layer3.satori.alpha_conversion import alpha_equivalent, alpha_normalize
15
+ from clm.layer3.satori.beta_reduction import (
16
+ ReductionStrategy,
17
+ beta_reduce,
18
+ normalize,
19
+ )
20
+ from clm.layer3.satori.eta_conversion import eta_expand, eta_reduce
21
+ from clm.layer3.satori.free_variables import free_variables, is_closed
22
+ from clm.layer3.satori.io_effects import create_io_handler
23
+ from clm.layer3.satori.lambda_term import (
24
+ AbsTerm,
25
+ AppTerm,
26
+ VarTerm,
27
+ load_term,
28
+ mk_abs,
29
+ mk_app,
30
+ mk_var,
31
+ pretty_print_deep,
32
+ store_term,
33
+ )
34
+
35
+ # ─────────────────────────────────────────────────────────────────────────────
36
+ # Parser
37
+ # ─────────────────────────────────────────────────────────────────────────────
38
+
39
+
40
+ class ParseError(Exception):
41
+ """Error during Lambda expression parsing"""
42
+
43
+ pass
44
+
45
+
46
+ def parse_lambda_expression(collection: CardCollection, expr: str) -> str:
47
+ """
48
+ Parse a Lambda expression string and store terms in collection.
49
+
50
+ Syntax:
51
+ <term> ::= <var> | <abs> | <app> | "(" <term> ")"
52
+ <var> ::= [a-zA-Z][a-zA-Z0-9']*
53
+ <abs> ::= ("λ" | "\\") <var> "." <term>
54
+ <app> ::= <term> <term>
55
+
56
+ Returns the hash of the parsed term.
57
+ """
58
+ parser = LambdaParser(collection, expr)
59
+ return parser.parse()
60
+
61
+
62
+ class LambdaParser:
63
+ """Recursive descent parser for Lambda expressions"""
64
+
65
+ def __init__(self, collection: CardCollection, input_str: str):
66
+ self.collection = collection
67
+ self.input = input_str.strip()
68
+ self.pos = 0
69
+
70
+ def parse(self) -> str:
71
+ """Parse the entire input and return term hash"""
72
+ result = self._parse_term()
73
+ self._skip_whitespace()
74
+ if self.pos < len(self.input):
75
+ raise ParseError(
76
+ f"Unexpected character at position {self.pos}: '{self.input[self.pos]}'"
77
+ )
78
+ return result
79
+
80
+ def _skip_whitespace(self):
81
+ """Skip whitespace characters"""
82
+ while self.pos < len(self.input) and self.input[self.pos] in " \t\n\r":
83
+ self.pos += 1
84
+
85
+ def _peek(self) -> str | None:
86
+ """Look at current character without consuming"""
87
+ self._skip_whitespace()
88
+ if self.pos >= len(self.input):
89
+ return None
90
+ return self.input[self.pos]
91
+
92
+ def _consume(self, char: str):
93
+ """Consume expected character"""
94
+ self._skip_whitespace()
95
+ if self.pos >= len(self.input):
96
+ raise ParseError(f"Expected '{char}' but reached end of input")
97
+ if self.input[self.pos] != char:
98
+ raise ParseError(
99
+ f"Expected '{char}' but found '{self.input[self.pos]}' at position {self.pos}"
100
+ )
101
+ self.pos += 1
102
+
103
+ def _parse_term(self) -> str:
104
+ """Parse a Lambda term (handles application as left-associative)"""
105
+ atoms = []
106
+
107
+ while True:
108
+ atom = self._parse_atom()
109
+ if atom is None:
110
+ break
111
+ atoms.append(atom)
112
+
113
+ if not atoms:
114
+ raise ParseError(f"Expected term at position {self.pos}")
115
+
116
+ # Build left-associative application
117
+ result = atoms[0]
118
+ for i in range(1, len(atoms)):
119
+ result = store_term(self.collection, mk_app(result, atoms[i]))
120
+
121
+ return result
122
+
123
+ def _parse_atom(self) -> str | None:
124
+ """Parse an atomic term (variable, abstraction, or parenthesized)"""
125
+ self._skip_whitespace()
126
+
127
+ if self.pos >= len(self.input):
128
+ return None
129
+
130
+ c = self.input[self.pos]
131
+
132
+ # Lambda/backslash - abstraction
133
+ if c == "λ" or c == "\\":
134
+ return self._parse_abstraction()
135
+
136
+ # Open paren - grouped term
137
+ if c == "(":
138
+ return self._parse_grouped()
139
+
140
+ # String literal
141
+ if c == '"':
142
+ return self._parse_string()
143
+
144
+ # Variable
145
+ if c.isalpha() or c == "_":
146
+ return self._parse_variable()
147
+
148
+ return None
149
+
150
+ def _parse_string(self) -> str:
151
+ """Parse a double-quoted string literal as a variable"""
152
+ # Consume opening quote
153
+ self._consume('"')
154
+
155
+ start = self.pos
156
+ while self.pos < len(self.input) and self.input[self.pos] != '"':
157
+ self.pos += 1
158
+
159
+ if self.pos >= len(self.input):
160
+ raise ParseError(f"Unterminated string literal starting at {start - 1}")
161
+
162
+ content = self.input[start : self.pos]
163
+
164
+ # Consume closing quote
165
+ self._consume('"')
166
+
167
+ # Store as variable with quotes preserved to indicate string-ness
168
+ return store_term(self.collection, mk_var(f'"{content}"'))
169
+
170
+ def _parse_variable(self) -> str:
171
+ """Parse a variable name"""
172
+ self._skip_whitespace()
173
+ start = self.pos
174
+
175
+ # First character must be letter
176
+ if self.pos >= len(self.input) or not self.input[self.pos].isalpha():
177
+ raise ParseError(f"Expected variable at position {self.pos}")
178
+
179
+ # Read variable name
180
+ while self.pos < len(self.input) and (
181
+ self.input[self.pos].isalnum() or self.input[self.pos] in ("'", "_")
182
+ ):
183
+ self.pos += 1
184
+
185
+ name = self.input[start : self.pos]
186
+ return store_term(self.collection, mk_var(name))
187
+
188
+ def _parse_abstraction(self) -> str:
189
+ """Parse an abstraction: λx.M or \\x.M"""
190
+ self._skip_whitespace()
191
+
192
+ # Consume lambda symbol
193
+ if self.input[self.pos] == "λ" or self.input[self.pos] == "\\":
194
+ self.pos += 1
195
+ else:
196
+ raise ParseError(f"Expected λ or \\ at position {self.pos}")
197
+
198
+ self._skip_whitespace()
199
+
200
+ # Parse parameter
201
+ start = self.pos
202
+ while self.pos < len(self.input) and (
203
+ self.input[self.pos].isalnum() or self.input[self.pos] in ("'", "_")
204
+ ):
205
+ self.pos += 1
206
+
207
+ if self.pos == start:
208
+ raise ParseError(f"Expected parameter name at position {self.pos}")
209
+
210
+ param = self.input[start : self.pos]
211
+
212
+ # Consume dot
213
+ self._skip_whitespace()
214
+ self._consume(".")
215
+
216
+ # Parse body
217
+ body = self._parse_term()
218
+
219
+ return store_term(self.collection, mk_abs(param, body))
220
+
221
+ def _parse_grouped(self) -> str:
222
+ """Parse a parenthesized term: (M)"""
223
+ self._consume("(")
224
+ term = self._parse_term()
225
+ self._consume(")")
226
+ return term
227
+
228
+
229
+ # ─────────────────────────────────────────────────────────────────────────────
230
+ # Lambda Runtime
231
+ # ─────────────────────────────────────────────────────────────────────────────
232
+
233
+ LambdaOperation = Literal[
234
+ "parse",
235
+ "normalize",
236
+ "normalize-applicative",
237
+ "normalize-lazy",
238
+ "beta-reduce",
239
+ "alpha-equiv",
240
+ "alpha-normalize",
241
+ "eta-reduce",
242
+ "eta-expand",
243
+ "free-vars",
244
+ "is-closed",
245
+ "pretty-print",
246
+ "check-readiness",
247
+ "num-add",
248
+ "num-sub",
249
+ "num-mul",
250
+ "num-div",
251
+ "http-request",
252
+ "church-to-int",
253
+ ]
254
+
255
+
256
+ @dataclass
257
+ class LambdaConfig:
258
+ """Configuration for Lambda runtime operations"""
259
+
260
+ operation: LambdaOperation = "normalize"
261
+ strategy: ReductionStrategy = "normal"
262
+ max_steps: int = 100
263
+ io_effects: dict[str, Any] | None = None
264
+
265
+
266
+ @dataclass
267
+ class LambdaRuntimeResult:
268
+ """Result from Lambda runtime execution"""
269
+
270
+ success: bool
271
+ term_hash: str | None = None
272
+ pretty_print: str | None = None
273
+ free_variables: list[str] | None = None
274
+ is_closed: bool | None = None
275
+ steps: int | None = None
276
+ alpha_equivalent: bool | None = None
277
+ eta_equivalent: bool | None = None
278
+ error: str | None = None
279
+
280
+
281
+ class LambdaRuntime:
282
+ """
283
+ Lambda Calculus runtime for PTR.
284
+
285
+ Provides α-β-η conversions on MCard-stored Lambda terms.
286
+ """
287
+
288
+ def __init__(self, collection: CardCollection):
289
+ self.collection = collection
290
+
291
+ def execute(
292
+ self,
293
+ code_or_hash: str,
294
+ context: dict[str, Any],
295
+ config: dict[str, Any],
296
+ _chapter_dir: str = "",
297
+ ) -> LambdaRuntimeResult:
298
+ """
299
+ Execute a Lambda operation.
300
+
301
+ Args:
302
+ code_or_hash: Either a Lambda expression string or a term hash
303
+ context: Additional context (may contain 'expression', 'other_hash')
304
+ config: Operation configuration
305
+ chapter_dir: Working directory (unused)
306
+
307
+ Returns:
308
+ LambdaRuntimeResult with operation output
309
+ """
310
+ try:
311
+ operation = config.get("operation", "normalize")
312
+ strategy = config.get("strategy", "normal")
313
+ max_steps = config.get("max_steps", config.get("maxSteps", 100))
314
+
315
+ # Parse expression if provided in context
316
+ if context.get("expression"):
317
+ term_hash = parse_lambda_expression(
318
+ self.collection, context["expression"]
319
+ )
320
+ elif code_or_hash and len(code_or_hash) == 64:
321
+ # Looks like a hash
322
+ term_hash = code_or_hash
323
+ elif code_or_hash and code_or_hash.strip():
324
+ # Parse as expression
325
+ term_hash = parse_lambda_expression(self.collection, code_or_hash)
326
+ else:
327
+ return LambdaRuntimeResult(
328
+ success=False, error="No expression or hash provided"
329
+ )
330
+
331
+ # Execute operation
332
+ if operation == "parse":
333
+ return self._parse(context)
334
+
335
+ elif operation == "normalize":
336
+ return self._normalize(term_hash, strategy, max_steps, config)
337
+
338
+ elif operation == "normalize-applicative":
339
+ return self._normalize(term_hash, "applicative", max_steps, config)
340
+
341
+ elif operation == "normalize-lazy":
342
+ return self._normalize(term_hash, "lazy", max_steps, config)
343
+
344
+ elif operation == "beta-reduce":
345
+ return self._beta_reduce(term_hash)
346
+
347
+ elif operation == "alpha-equiv":
348
+ other_hash = context.get("other_hash")
349
+ if not other_hash:
350
+ return LambdaRuntimeResult(
351
+ success=False,
352
+ error="alpha-equiv requires 'other_hash' in context",
353
+ )
354
+ return self._alpha_equiv(term_hash, other_hash)
355
+
356
+ elif operation == "alpha-normalize":
357
+ return self._alpha_normalize(term_hash)
358
+
359
+ elif operation == "eta-reduce":
360
+ return self._eta_reduce(term_hash)
361
+
362
+ elif operation == "eta-expand":
363
+ return self._eta_expand(term_hash)
364
+
365
+ elif operation == "free-vars":
366
+ return self._free_vars(term_hash)
367
+
368
+ elif operation == "is-closed":
369
+ return self._is_closed(term_hash)
370
+
371
+ elif operation == "pretty-print":
372
+ return self._pretty_print(term_hash)
373
+
374
+ elif operation == "check-readiness":
375
+ return self._check_readiness(context)
376
+
377
+ elif operation.startswith("num-"):
378
+ return self._numeric_op(operation, context)
379
+
380
+ elif operation == "http-request":
381
+ return self._http_request(context)
382
+
383
+ elif operation == "church-to-int":
384
+ return self._church_to_int(term_hash)
385
+
386
+ else:
387
+ return LambdaRuntimeResult(
388
+ success=False, error=f"Unknown operation: {operation}"
389
+ )
390
+
391
+ except ParseError as e:
392
+ return LambdaRuntimeResult(success=False, error=f"Parse error: {str(e)}")
393
+ except (ValueError, TypeError, RuntimeError, RecursionError) as e:
394
+ return LambdaRuntimeResult(success=False, error=str(e))
395
+
396
+ def _parse(self, context: dict[str, Any]) -> LambdaRuntimeResult:
397
+ """Parse a Lambda expression"""
398
+ expr = context.get("expression", "")
399
+ if not expr:
400
+ return LambdaRuntimeResult(success=False, error="No expression to parse")
401
+
402
+ term_hash = parse_lambda_expression(self.collection, expr)
403
+ pretty = pretty_print_deep(self.collection, term_hash)
404
+
405
+ return LambdaRuntimeResult(
406
+ success=True, term_hash=term_hash, pretty_print=pretty
407
+ )
408
+
409
+ def _normalize(
410
+ self, term_hash: str, strategy: str, max_steps: int, config: dict[str, Any]
411
+ ) -> LambdaRuntimeResult:
412
+ """Normalize a term with optional IO effects"""
413
+ # Create IO effects handler from config
414
+ io_handler = create_io_handler(config)
415
+
416
+ # IO callback for step events
417
+ def on_step(step: int, hash: str) -> None:
418
+ if io_handler.is_enabled():
419
+ pretty = pretty_print_deep(self.collection, hash)
420
+ io_handler.emit_step(step, hash, pretty)
421
+
422
+ result = normalize(
423
+ self.collection,
424
+ term_hash,
425
+ strategy,
426
+ max_steps,
427
+ on_step=on_step if io_handler.is_enabled() else None,
428
+ )
429
+
430
+ if result is None:
431
+ if io_handler.is_enabled():
432
+ io_handler.emit_error("Normalization failed or diverged", 0)
433
+ return LambdaRuntimeResult(
434
+ success=False, error="Normalization failed or diverged"
435
+ )
436
+
437
+ pretty = pretty_print_deep(self.collection, result.normal_form)
438
+
439
+ # IO Effect: emit completion
440
+ if io_handler.is_enabled():
441
+ io_handler.emit_complete(
442
+ result.normal_form, pretty, result.steps, result.reduction_path
443
+ )
444
+
445
+ return LambdaRuntimeResult(
446
+ success=True,
447
+ term_hash=result.normal_form,
448
+ pretty_print=pretty,
449
+ steps=result.steps,
450
+ )
451
+
452
+ def _beta_reduce(self, term_hash: str) -> LambdaRuntimeResult:
453
+ """Single beta reduction step"""
454
+ result = beta_reduce(self.collection, term_hash)
455
+
456
+ if result is None:
457
+ return LambdaRuntimeResult(success=False, error="Not a redex")
458
+
459
+ pretty = pretty_print_deep(self.collection, result)
460
+
461
+ return LambdaRuntimeResult(success=True, term_hash=result, pretty_print=pretty)
462
+
463
+ def _alpha_equiv(self, hash1: str, hash2: str) -> LambdaRuntimeResult:
464
+ """Check alpha equivalence"""
465
+ equiv = alpha_equivalent(self.collection, hash1, hash2)
466
+
467
+ if equiv is None:
468
+ return LambdaRuntimeResult(success=False, error="Terms not found")
469
+
470
+ return LambdaRuntimeResult(success=True, alpha_equivalent=equiv)
471
+
472
+ def _alpha_normalize(self, term_hash: str) -> LambdaRuntimeResult:
473
+ """Normalize bound variable names"""
474
+ result = alpha_normalize(self.collection, term_hash)
475
+
476
+ if result is None:
477
+ return LambdaRuntimeResult(
478
+ success=False, error="Alpha normalization failed"
479
+ )
480
+
481
+ pretty = pretty_print_deep(self.collection, result)
482
+
483
+ return LambdaRuntimeResult(success=True, term_hash=result, pretty_print=pretty)
484
+
485
+ def _eta_reduce(self, term_hash: str) -> LambdaRuntimeResult:
486
+ """Eta reduce a term"""
487
+ result = eta_reduce(self.collection, term_hash)
488
+
489
+ if result is None:
490
+ return LambdaRuntimeResult(success=False, error="Eta reduction failed")
491
+
492
+ pretty = pretty_print_deep(self.collection, result)
493
+
494
+ return LambdaRuntimeResult(success=True, term_hash=result, pretty_print=pretty)
495
+
496
+ def _eta_expand(self, term_hash: str) -> LambdaRuntimeResult:
497
+ """Eta expand a term"""
498
+ result = eta_expand(self.collection, term_hash)
499
+
500
+ if result is None:
501
+ return LambdaRuntimeResult(success=False, error="Eta expansion failed")
502
+
503
+ pretty = pretty_print_deep(self.collection, result)
504
+
505
+ return LambdaRuntimeResult(success=True, term_hash=result, pretty_print=pretty)
506
+
507
+ def _free_vars(self, term_hash: str) -> LambdaRuntimeResult:
508
+ """Get free variables"""
509
+ fv = free_variables(self.collection, term_hash)
510
+
511
+ if fv is None:
512
+ return LambdaRuntimeResult(success=False, error="Term not found")
513
+
514
+ return LambdaRuntimeResult(
515
+ success=True, term_hash=term_hash, free_variables=sorted(fv)
516
+ )
517
+
518
+ def _is_closed(self, term_hash: str) -> LambdaRuntimeResult:
519
+ """Check if term is closed"""
520
+ closed = is_closed(self.collection, term_hash)
521
+
522
+ if closed is None:
523
+ return LambdaRuntimeResult(success=False, error="Term not found")
524
+
525
+ pretty = pretty_print_deep(self.collection, term_hash)
526
+
527
+ return LambdaRuntimeResult(
528
+ success=True, term_hash=term_hash, pretty_print=pretty, is_closed=closed
529
+ )
530
+
531
+ def _pretty_print(self, term_hash: str) -> LambdaRuntimeResult:
532
+ """Pretty print a term"""
533
+ pretty = pretty_print_deep(self.collection, term_hash)
534
+
535
+ return LambdaRuntimeResult(
536
+ success=True, term_hash=term_hash, pretty_print=pretty
537
+ )
538
+
539
+ def _check_readiness(self, context: dict[str, Any]) -> LambdaRuntimeResult:
540
+ """Check if a runtime or network is ready."""
541
+ from clm.layer1.runtimes import RuntimeFactory
542
+
543
+ runtime_name = context.get("runtime_name", "lambda")
544
+
545
+ # Get status for all runtimes
546
+ status_map = RuntimeFactory.get_detailed_status()
547
+
548
+ if runtime_name == "all":
549
+ available = all(s["available"] for s in status_map.values())
550
+ return LambdaRuntimeResult(
551
+ success=True,
552
+ pretty_print=f"All Runtimes Ready: {available}",
553
+ is_closed=available,
554
+ )
555
+
556
+ status = status_map.get(runtime_name.lower())
557
+ if not status:
558
+ return LambdaRuntimeResult(
559
+ success=False, error=f"Unknown runtime: {runtime_name}"
560
+ )
561
+
562
+ return LambdaRuntimeResult(
563
+ success=True,
564
+ pretty_print=f"Runtime {runtime_name} status: {status['available']}",
565
+ is_closed=status["available"],
566
+ )
567
+
568
+ def _numeric_op(
569
+ self, operation: str, context: dict[str, Any]
570
+ ) -> LambdaRuntimeResult:
571
+ """Perform a built-in numeric operation."""
572
+ a = context.get("a", 0)
573
+ b = context.get("b", 0)
574
+
575
+ try:
576
+ val_a = float(a)
577
+ val_b = float(b)
578
+
579
+ if operation == "num-add":
580
+ result = val_a + val_b
581
+ elif operation == "num-sub":
582
+ result = val_a - val_b
583
+ elif operation == "num-mul":
584
+ result = val_a * val_b
585
+ elif operation == "num-div":
586
+ if val_b == 0:
587
+ return LambdaRuntimeResult(success=False, error="Division by zero")
588
+ result = val_a / val_b
589
+ else:
590
+ return LambdaRuntimeResult(
591
+ success=False, error=f"Unknown numeric op: {operation}"
592
+ )
593
+
594
+ # Convert result back to string for pretty print
595
+ res_str = str(int(result) if result.is_integer() else result)
596
+ return LambdaRuntimeResult(success=True, pretty_print=res_str)
597
+ except (ValueError, TypeError):
598
+ return LambdaRuntimeResult(success=False, error="Invalid numeric inputs")
599
+
600
+ def _http_request(self, context: dict[str, Any]) -> LambdaRuntimeResult:
601
+ """Perform a built-in network request."""
602
+ from clm.layer1.runtimes import RuntimeFactory
603
+
604
+ net_executor = RuntimeFactory.get_executor("network")
605
+ if not net_executor:
606
+ return LambdaRuntimeResult(
607
+ success=False, error="Network runtime not available"
608
+ )
609
+
610
+ # Forward request to NetworkRuntime
611
+ # We need a dummy target as execute expects it
612
+ from clm.layer0.model.card import MCard
613
+
614
+ dummy_target = MCard("dummy")
615
+
616
+ # Build config from context
617
+ config = {
618
+ "method": context.get("method", "GET"),
619
+ "url": context.get("url"),
620
+ "headers": context.get("headers", {}),
621
+ "body": context.get("body"),
622
+ }
623
+
624
+ if not config["url"]:
625
+ return LambdaRuntimeResult(
626
+ success=False, error="No URL provided for http-request"
627
+ )
628
+
629
+ try:
630
+ res = net_executor.execute({}, dummy_target, config)
631
+ return LambdaRuntimeResult(success=True, pretty_print=str(res))
632
+ except (OSError, ConnectionError, RuntimeError, ValueError) as e:
633
+ return LambdaRuntimeResult(success=False, error=f"Network error: {str(e)}")
634
+
635
+ def _church_to_int(self, term_hash: str) -> LambdaRuntimeResult:
636
+ """
637
+ Decode a Church numeral to a regular integer.
638
+
639
+ Church numeral n = λf.λx.f^n(x) where f is applied n times.
640
+
641
+ Algorithm:
642
+ 1. Normalize the term first
643
+ 2. Expect form: Abs(f, Abs(x, body))
644
+ 3. Count how many times 'f' appears in application position in body
645
+ """
646
+ try:
647
+ # First normalize the term
648
+ result = normalize(self.collection, term_hash, "normal", 1000)
649
+
650
+ if result is None:
651
+ return LambdaRuntimeResult(
652
+ success=False, error="Failed to normalize term"
653
+ )
654
+
655
+ pretty = pretty_print_deep(self.collection, result.normal_form)
656
+
657
+ # Decode Church numeral structure: λf.λx.body
658
+ count = self._count_church_applications(result.normal_form)
659
+
660
+ return LambdaRuntimeResult(
661
+ success=True,
662
+ term_hash=result.normal_form,
663
+ pretty_print=f"{count} (Church: {pretty})",
664
+ )
665
+ except (ValueError, TypeError, ArithmeticError, RecursionError) as e:
666
+ return LambdaRuntimeResult(
667
+ success=False, error=f"Church decode error: {str(e)}"
668
+ )
669
+
670
+ def _count_church_applications(self, term_hash: str) -> int:
671
+ """
672
+ Count applications in a Church numeral body.
673
+ Church numeral n has the form: λf.λx.f(f(f(...f(x)...)))
674
+ where f appears n times.
675
+ """
676
+ term = load_term(self.collection, term_hash)
677
+ if term is None:
678
+ return -1
679
+
680
+ # Expect: Abs(f, Abs(x, body))
681
+ if not isinstance(term, AbsTerm):
682
+ return -1
683
+
684
+ f_var = term.param
685
+ inner_term = load_term(self.collection, term.body)
686
+
687
+ if inner_term is None or not isinstance(inner_term, AbsTerm):
688
+ return -1
689
+
690
+ x_var = inner_term.param
691
+
692
+ # Count how many times we see App(f, ...) wrapping
693
+ count = 0
694
+ current_hash = inner_term.body
695
+
696
+ while True:
697
+ body = load_term(self.collection, current_hash)
698
+ if body is None:
699
+ break
700
+
701
+ if isinstance(body, AppTerm):
702
+ func = load_term(self.collection, body.func)
703
+
704
+ # Check if function is the 'f' variable
705
+ if func and isinstance(func, VarTerm) and func.name == f_var:
706
+ count += 1
707
+ current_hash = body.arg
708
+ else:
709
+ # Not a simple Church numeral structure
710
+ break
711
+ elif isinstance(body, VarTerm) and body.name == x_var:
712
+ # Reached the base case 'x'
713
+ return count
714
+ else:
715
+ break
716
+
717
+ return count
718
+
719
+
720
+ # Register with Layer 1 runtime factory (INV-383-01 / DEL-383-04)
721
+ try:
722
+ from clm.layer1.runtimes.factory import register_external_executor
723
+
724
+ register_external_executor("lambda", LambdaRuntime)
725
+ except ImportError:
726
+ pass