clausal 0.3.0__tar.gz

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 (206) hide show
  1. clausal-0.3.0/LICENSE +21 -0
  2. clausal-0.3.0/MANIFEST.in +4 -0
  3. clausal-0.3.0/PKG-INFO +176 -0
  4. clausal-0.3.0/README.md +160 -0
  5. clausal-0.3.0/clausal/__init__.py +63 -0
  6. clausal-0.3.0/clausal/codegen.py +166 -0
  7. clausal-0.3.0/clausal/import_hook.py +645 -0
  8. clausal-0.3.0/clausal/logic/__init__.py +0 -0
  9. clausal-0.3.0/clausal/logic/_trampoline.c +406 -0
  10. clausal-0.3.0/clausal/logic/builtins/__init__.py +86 -0
  11. clausal-0.3.0/clausal/logic/builtins/_helpers.py +96 -0
  12. clausal-0.3.0/clausal/logic/builtins/_registry.py +407 -0
  13. clausal-0.3.0/clausal/logic/builtins/arithmetic.py +171 -0
  14. clausal-0.3.0/clausal/logic/builtins/constraints.py +135 -0
  15. clausal-0.3.0/clausal/logic/builtins/control.py +74 -0
  16. clausal-0.3.0/clausal/logic/builtins/database_ops.py +255 -0
  17. clausal-0.3.0/clausal/logic/builtins/dcg.py +64 -0
  18. clausal-0.3.0/clausal/logic/builtins/dict_set.py +444 -0
  19. clausal-0.3.0/clausal/logic/builtins/higher_order.py +434 -0
  20. clausal-0.3.0/clausal/logic/builtins/inspection.py +224 -0
  21. clausal-0.3.0/clausal/logic/builtins/io.py +104 -0
  22. clausal-0.3.0/clausal/logic/builtins/keyword_ops.py +116 -0
  23. clausal-0.3.0/clausal/logic/builtins/lists.py +450 -0
  24. clausal-0.3.0/clausal/logic/builtins/pairs.py +57 -0
  25. clausal-0.3.0/clausal/logic/builtins/type_checks.py +101 -0
  26. clausal-0.3.0/clausal/logic/clpb.py +771 -0
  27. clausal-0.3.0/clausal/logic/clpfd.py +1010 -0
  28. clausal-0.3.0/clausal/logic/clpr.py +923 -0
  29. clausal-0.3.0/clausal/logic/compiler.py +6734 -0
  30. clausal-0.3.0/clausal/logic/compiler_v2.py +219 -0
  31. clausal-0.3.0/clausal/logic/constraints.py +244 -0
  32. clausal-0.3.0/clausal/logic/continuation_search.py +30 -0
  33. clausal-0.3.0/clausal/logic/database.py +431 -0
  34. clausal-0.3.0/clausal/logic/exceptions.py +71 -0
  35. clausal-0.3.0/clausal/logic/goal_expansion.py +289 -0
  36. clausal-0.3.0/clausal/logic/predicate.py +259 -0
  37. clausal-0.3.0/clausal/logic/reif.py +91 -0
  38. clausal-0.3.0/clausal/logic/solve.py +344 -0
  39. clausal-0.3.0/clausal/logic/tabling.py +562 -0
  40. clausal-0.3.0/clausal/logic/term_expansion.py +291 -0
  41. clausal-0.3.0/clausal/logic/trampoline.py +238 -0
  42. clausal-0.3.0/clausal/logic/units_constraint.py +120 -0
  43. clausal-0.3.0/clausal/logic/variables/__init__.py +37 -0
  44. clausal-0.3.0/clausal/logic/variables/_variables.c +1600 -0
  45. clausal-0.3.0/clausal/modules/__init__.py +31 -0
  46. clausal-0.3.0/clausal/modules/date_time.py +8 -0
  47. clausal-0.3.0/clausal/modules/graphs.py +669 -0
  48. clausal-0.3.0/clausal/modules/log.py +13 -0
  49. clausal-0.3.0/clausal/modules/py/__init__.py +35 -0
  50. clausal-0.3.0/clausal/modules/py/_scipy_relations.py +82 -0
  51. clausal-0.3.0/clausal/modules/py/_scipy_units.py +174 -0
  52. clausal-0.3.0/clausal/modules/py/datetime.py +417 -0
  53. clausal-0.3.0/clausal/modules/py/imperial.py +119 -0
  54. clausal-0.3.0/clausal/modules/py/logging.py +374 -0
  55. clausal-0.3.0/clausal/modules/py/re.py +229 -0
  56. clausal-0.3.0/clausal/modules/py/scipy_cluster.py +304 -0
  57. clausal-0.3.0/clausal/modules/py/scipy_constants.py +223 -0
  58. clausal-0.3.0/clausal/modules/py/scipy_differentiate.py +349 -0
  59. clausal-0.3.0/clausal/modules/py/scipy_fft.py +250 -0
  60. clausal-0.3.0/clausal/modules/py/scipy_integrate.py +444 -0
  61. clausal-0.3.0/clausal/modules/py/scipy_interpolate.py +659 -0
  62. clausal-0.3.0/clausal/modules/py/scipy_linalg.py +503 -0
  63. clausal-0.3.0/clausal/modules/py/scipy_ndimage.py +250 -0
  64. clausal-0.3.0/clausal/modules/py/scipy_optimize.py +328 -0
  65. clausal-0.3.0/clausal/modules/py/scipy_signal.py +440 -0
  66. clausal-0.3.0/clausal/modules/py/scipy_sparse.py +521 -0
  67. clausal-0.3.0/clausal/modules/py/scipy_spatial.py +629 -0
  68. clausal-0.3.0/clausal/modules/py/scipy_special.py +447 -0
  69. clausal-0.3.0/clausal/modules/py/scipy_stats.py +717 -0
  70. clausal-0.3.0/clausal/modules/py/sklearn.py +1326 -0
  71. clausal-0.3.0/clausal/modules/py/spacy.py +31 -0
  72. clausal-0.3.0/clausal/modules/py/sqlite.py +296 -0
  73. clausal-0.3.0/clausal/modules/py/sympy.py +1333 -0
  74. clausal-0.3.0/clausal/modules/py/units.py +513 -0
  75. clausal-0.3.0/clausal/modules/py/uuid.py +320 -0
  76. clausal-0.3.0/clausal/modules/py/yaml.py +225 -0
  77. clausal-0.3.0/clausal/modules/regex.py +7 -0
  78. clausal-0.3.0/clausal/modules/scipy_cluster.py +7 -0
  79. clausal-0.3.0/clausal/modules/scipy_constants.py +10 -0
  80. clausal-0.3.0/clausal/modules/scipy_differentiate.py +5 -0
  81. clausal-0.3.0/clausal/modules/scipy_fft.py +12 -0
  82. clausal-0.3.0/clausal/modules/scipy_integrate.py +8 -0
  83. clausal-0.3.0/clausal/modules/scipy_interpolate.py +18 -0
  84. clausal-0.3.0/clausal/modules/scipy_linalg.py +12 -0
  85. clausal-0.3.0/clausal/modules/scipy_ndimage.py +18 -0
  86. clausal-0.3.0/clausal/modules/scipy_optimize.py +12 -0
  87. clausal-0.3.0/clausal/modules/scipy_signal.py +23 -0
  88. clausal-0.3.0/clausal/modules/scipy_sparse.py +17 -0
  89. clausal-0.3.0/clausal/modules/scipy_spatial.py +22 -0
  90. clausal-0.3.0/clausal/modules/scipy_special.py +17 -0
  91. clausal-0.3.0/clausal/modules/scipy_stats.py +19 -0
  92. clausal-0.3.0/clausal/modules/spacy_module.py +483 -0
  93. clausal-0.3.0/clausal/modules/sqlite.py +9 -0
  94. clausal-0.3.0/clausal/modules/sympy_module.py +15 -0
  95. clausal-0.3.0/clausal/modules/units.py +2 -0
  96. clausal-0.3.0/clausal/modules/uuid_mod.py +9 -0
  97. clausal-0.3.0/clausal/modules/yaml_module.py +7 -0
  98. clausal-0.3.0/clausal/pythonic_ast/__init__.py +0 -0
  99. clausal-0.3.0/clausal/pythonic_ast/conversion_from_python_ast.py +670 -0
  100. clausal-0.3.0/clausal/pythonic_ast/node_class.py +263 -0
  101. clausal-0.3.0/clausal/pythonic_ast/nodes.py +1292 -0
  102. clausal-0.3.0/clausal/pythonic_ast/transform.py +29 -0
  103. clausal-0.3.0/clausal/pythonic_terms.py +113 -0
  104. clausal-0.3.0/clausal/regex.py +2 -0
  105. clausal-0.3.0/clausal/repl.py +288 -0
  106. clausal-0.3.0/clausal/stdlib/__init__.py +0 -0
  107. clausal-0.3.0/clausal/templating/__init__.py +0 -0
  108. clausal-0.3.0/clausal/templating/compiler.py +478 -0
  109. clausal-0.3.0/clausal/templating/parser.py +104 -0
  110. clausal-0.3.0/clausal/templating/term_rewriting.py +2993 -0
  111. clausal-0.3.0/clausal/terms.py +872 -0
  112. clausal-0.3.0/clausal/testing.py +182 -0
  113. clausal-0.3.0/clausal/tools/__init__.py +1 -0
  114. clausal-0.3.0/clausal/tools/clear_pycache.py +68 -0
  115. clausal-0.3.0/clausal/tools/dump_transformed.py +125 -0
  116. clausal-0.3.0/clausal/tools/visualize.py +201 -0
  117. clausal-0.3.0/clausal.egg-info/PKG-INFO +176 -0
  118. clausal-0.3.0/clausal.egg-info/SOURCES.txt +204 -0
  119. clausal-0.3.0/clausal.egg-info/dependency_links.txt +1 -0
  120. clausal-0.3.0/clausal.egg-info/requires.txt +5 -0
  121. clausal-0.3.0/clausal.egg-info/top_level.txt +1 -0
  122. clausal-0.3.0/pyproject.toml +31 -0
  123. clausal-0.3.0/setup.cfg +4 -0
  124. clausal-0.3.0/setup.py +15 -0
  125. clausal-0.3.0/tests/test_body_star_decon.py +551 -0
  126. clausal-0.3.0/tests/test_builtin_classes.py +430 -0
  127. clausal-0.3.0/tests/test_builtins.py +916 -0
  128. clausal-0.3.0/tests/test_callsite_specialization.py +661 -0
  129. clausal-0.3.0/tests/test_clausal_modules.py +750 -0
  130. clausal-0.3.0/tests/test_clpb.py +883 -0
  131. clausal-0.3.0/tests/test_clpfd.py +746 -0
  132. clausal-0.3.0/tests/test_clpr.py +651 -0
  133. clausal-0.3.0/tests/test_codegen.py +190 -0
  134. clausal-0.3.0/tests/test_compiled_programs.py +774 -0
  135. clausal-0.3.0/tests/test_compiler.py +1002 -0
  136. clausal-0.3.0/tests/test_compiler_goals.py +722 -0
  137. clausal-0.3.0/tests/test_compiler_optimizations.py +1091 -0
  138. clausal-0.3.0/tests/test_compiler_trampoline.py +634 -0
  139. clausal-0.3.0/tests/test_compiler_v2.py +245 -0
  140. clausal-0.3.0/tests/test_continuation_search.py +152 -0
  141. clausal-0.3.0/tests/test_control.py +103 -0
  142. clausal-0.3.0/tests/test_database.py +371 -0
  143. clausal-0.3.0/tests/test_date_time.py +562 -0
  144. clausal-0.3.0/tests/test_dcg.py +636 -0
  145. clausal-0.3.0/tests/test_deep_indexing.py +642 -0
  146. clausal-0.3.0/tests/test_dict_set_builtins.py +607 -0
  147. clausal-0.3.0/tests/test_dict_set_compiler.py +193 -0
  148. clausal-0.3.0/tests/test_dict_set_terms.py +325 -0
  149. clausal-0.3.0/tests/test_dif.py +459 -0
  150. clausal-0.3.0/tests/test_directives.py +225 -0
  151. clausal-0.3.0/tests/test_edcg.py +518 -0
  152. clausal-0.3.0/tests/test_exceptions.py +423 -0
  153. clausal-0.3.0/tests/test_first_arg_index.py +508 -0
  154. clausal-0.3.0/tests/test_groundness_dispatch.py +481 -0
  155. clausal-0.3.0/tests/test_higher_order.py +321 -0
  156. clausal-0.3.0/tests/test_import.py +380 -0
  157. clausal-0.3.0/tests/test_io.py +582 -0
  158. clausal-0.3.0/tests/test_ipython_integration.py +128 -0
  159. clausal-0.3.0/tests/test_lambdas.py +778 -0
  160. clausal-0.3.0/tests/test_list_edge_cases.py +548 -0
  161. clausal-0.3.0/tests/test_list_util.py +399 -0
  162. clausal-0.3.0/tests/test_logging_module.py +422 -0
  163. clausal-0.3.0/tests/test_meta.py +315 -0
  164. clausal-0.3.0/tests/test_module_imports.py +446 -0
  165. clausal-0.3.0/tests/test_mutable_dict_set.py +130 -0
  166. clausal-0.3.0/tests/test_predicate_meta.py +381 -0
  167. clausal-0.3.0/tests/test_pycache.py +291 -0
  168. clausal-0.3.0/tests/test_python_interop.py +187 -0
  169. clausal-0.3.0/tests/test_regex.py +733 -0
  170. clausal-0.3.0/tests/test_reif_builtins.py +408 -0
  171. clausal-0.3.0/tests/test_reified_ite.py +1541 -0
  172. clausal-0.3.0/tests/test_repl.py +147 -0
  173. clausal-0.3.0/tests/test_scipy_cluster.py +449 -0
  174. clausal-0.3.0/tests/test_scipy_constants.py +264 -0
  175. clausal-0.3.0/tests/test_scipy_differentiate.py +419 -0
  176. clausal-0.3.0/tests/test_scipy_fft.py +529 -0
  177. clausal-0.3.0/tests/test_scipy_integrate.py +592 -0
  178. clausal-0.3.0/tests/test_scipy_interpolate.py +581 -0
  179. clausal-0.3.0/tests/test_scipy_linalg.py +543 -0
  180. clausal-0.3.0/tests/test_scipy_ndimage.py +562 -0
  181. clausal-0.3.0/tests/test_scipy_optimize.py +460 -0
  182. clausal-0.3.0/tests/test_scipy_signal.py +681 -0
  183. clausal-0.3.0/tests/test_scipy_sparse.py +636 -0
  184. clausal-0.3.0/tests/test_scipy_spatial.py +664 -0
  185. clausal-0.3.0/tests/test_scipy_special.py +668 -0
  186. clausal-0.3.0/tests/test_scipy_stats.py +905 -0
  187. clausal-0.3.0/tests/test_search.py +891 -0
  188. clausal-0.3.0/tests/test_simple_ast.py +997 -0
  189. clausal-0.3.0/tests/test_slg_termination.py +225 -0
  190. clausal-0.3.0/tests/test_solve.py +396 -0
  191. clausal-0.3.0/tests/test_spacy_module.py +701 -0
  192. clausal-0.3.0/tests/test_sqlite.py +594 -0
  193. clausal-0.3.0/tests/test_sympy_module.py +506 -0
  194. clausal-0.3.0/tests/test_tabling.py +546 -0
  195. clausal-0.3.0/tests/test_template_compiler.py +779 -0
  196. clausal-0.3.0/tests/test_term_expansion.py +449 -0
  197. clausal-0.3.0/tests/test_term_inspection.py +364 -0
  198. clausal-0.3.0/tests/test_term_rewriting.py +663 -0
  199. clausal-0.3.0/tests/test_terms.py +326 -0
  200. clausal-0.3.0/tests/test_transform_nodes.py +340 -0
  201. clausal-0.3.0/tests/test_unify.py +331 -0
  202. clausal-0.3.0/tests/test_units.py +1647 -0
  203. clausal-0.3.0/tests/test_uuid_module.py +511 -0
  204. clausal-0.3.0/tests/test_variables.py +612 -0
  205. clausal-0.3.0/tests/test_wfs.py +471 -0
  206. clausal-0.3.0/tests/test_yaml_module.py +417 -0
clausal-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Amy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include clausal/logic/variables/_variables.c
2
+ include clausal/logic/_trampoline.c
3
+ include LICENSE
4
+ include README.md
clausal-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: clausal
3
+ Version: 0.3.0
4
+ Summary: A Prolog-style logic programming DSL embedded in Python
5
+ Author-email: Mike Amy <mikeamycoder@gmail.com>
6
+ License: MIT
7
+ Project-URL: Repository, https://gitlab.com/MikeAmy/clausal
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: greenlet
12
+ Requires-Dist: pyyaml>=6.0
13
+ Provides-Extra: spacy
14
+ Requires-Dist: spacy>=3.0; extra == "spacy"
15
+ Dynamic: license-file
16
+
17
+ # clausal
18
+
19
+ A Prolog-style logic programming DSL embedded in Python. Write relational logic programs using `.clausal` source files or directly in Python, with full constraint solving, tabling, DCGs, and a rich standard library.
20
+
21
+ ## Features
22
+
23
+ - **`.clausal` source files** — import logic modules with Python's standard import system
24
+ - **Prolog-style predicates** — Horn clauses, unification, backtracking search
25
+ - **Constraint solving** — CLP(FD) for integers, CLP(B) for booleans, `dif/2` disequality
26
+ - **SLG tabling** — memoised subgoal calls for termination on cyclic structures
27
+ - **DCGs** — Definite Clause Grammars with `>>` syntax and `phrase/2,3`
28
+ - **Well-Founded Semantics** — negation-as-failure with `\+` / `tabled` predicates
29
+ - **Module system** — `-import_from/2`, `-import_module/1`, qualified calls
30
+ - **Meta-predicates** — `findall/3`, `bagof/3`, `setof/3`, `forall/2`, `call/N`
31
+ - **Higher-order** — `maplist/2,3`, `foldl/4`, `include/3`, `exclude/3`
32
+ - **Python interop** — `++expr` escape, lambda goal closures, f-string support
33
+ - **SciPy integration** — wrappers for `scipy.special`, `scipy.linalg`, `scipy.optimize`, `scipy.interpolate`, `scipy.signal`
34
+ - **Regex** — `match/2,3`, `search/2,3`, `replace/4`, `split/3` with auto-binding goal expansion
35
+ - **Term expansion** — macro system for source-level term rewriting
36
+ - **C extensions** — fast logic variables, trail-based backtracking, trampoline (no WAM)
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install clausal
42
+ ```
43
+
44
+ Requires Python ≥ 3.13 and a C compiler for the extension modules.
45
+
46
+ ## Quick start
47
+
48
+ ### Python API
49
+
50
+ ```python
51
+ from clausal import query, solve, once, Var
52
+ from clausal.import_hook import LogicModule
53
+
54
+ # Load a .clausal module
55
+ import clausal.examples.family # if you have one
56
+ ```
57
+
58
+ ### Writing `.clausal` files
59
+
60
+ ```python
61
+ # family.clausal
62
+ parent(tom, bob) <-
63
+ parent(tom, liz) <-
64
+ parent(bob, ann) <-
65
+ parent(bob, pat) <-
66
+
67
+ grandparent(X_, Z_) <- (parent(X_, Y_), parent(Y_, Z_))
68
+ ```
69
+
70
+ ```python
71
+ # Python
72
+ import family # .clausal files load via the import hook
73
+
74
+ from clausal import query
75
+ results = list(query(family.grandparent(Var(), Var())))
76
+ # → [(tom, ann), (tom, pat)]
77
+ ```
78
+
79
+ ### Constraints (CLP(FD))
80
+
81
+ ```python
82
+ # queens.clausal
83
+ -import_from(clpfd, [in_, all_different, label])
84
+
85
+ queens(N_, QS_) <- (
86
+ length(QS_, N_),
87
+ QS_ in_ 1..N_,
88
+ safe(QS_),
89
+ label(QS_)
90
+ )
91
+ ```
92
+
93
+ ### Arithmetic & builtins
94
+
95
+ ```clausal
96
+ fib(0, 0) <-
97
+ fib(1, 1) <-
98
+ fib(N_, F_) <- (
99
+ N_ > 1,
100
+ N1_ is N_ - 1,
101
+ N2_ is N_ - 2,
102
+ fib(N1_, F1_),
103
+ fib(N2_, F2_),
104
+ F_ is F1_ + F2_
105
+ )
106
+ ```
107
+
108
+ ### DCGs
109
+
110
+ ```clausal
111
+ sentence >> (noun_phrase, verb_phrase)
112
+ noun_phrase >> ([the], noun)
113
+ verb_phrase >> ([runs])
114
+ noun >> ([cat])
115
+ noun >> ([dog])
116
+ ```
117
+
118
+ ```python
119
+ from clausal import once
120
+ result = once(phrase(sentence, [the, cat, runs]))
121
+ ```
122
+
123
+ ## Testing
124
+
125
+ `.clausal` files can include inline tests as `test/1` clauses:
126
+
127
+ ```clausal
128
+ test("fib(5) = 5") <- fib(5, 5)
129
+ ```
130
+
131
+ **Standalone runner:**
132
+
133
+ ```bash
134
+ python -m clausal.testing clausal/examples/ # all .clausal files
135
+ python -m clausal.testing clausal/examples/hanoi.clausal # single file
136
+ python -m clausal.testing -v clausal/examples/ # verbose
137
+ ```
138
+
139
+ **Via pytest** (`.clausal` tests collected automatically):
140
+
141
+ ```bash
142
+ python -m pytest tests/ clausal/examples/ -q
143
+ ```
144
+
145
+ ## Logic variables and backtracking
146
+
147
+ The C extension `clausal.logic.variables` provides Prolog-style logic variables and trail-based backtracking without a Warren Abstract Machine.
148
+
149
+ ```python
150
+ from clausal.logic.variables import Var, Trail, unify, is_var
151
+
152
+ trail = Trail()
153
+ X = Var()
154
+ Y = Var()
155
+
156
+ unify(X, 42, trail)
157
+ assert X.value == 42
158
+
159
+ mark = trail.mark()
160
+ unify(Y, "temporary", trail)
161
+ trail.undo(mark)
162
+ assert is_var(Y) # Y is unbound again
163
+ ```
164
+
165
+ See [docs/](docs/) for full documentation.
166
+
167
+ ## Requirements
168
+
169
+ - Python ≥ 3.13
170
+ - [greenlet](https://pypi.org/project/greenlet/)
171
+ - [pyyaml](https://pypi.org/project/PyYAML/) ≥ 6.0
172
+ - C compiler (for building from source)
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,160 @@
1
+ # clausal
2
+
3
+ A Prolog-style logic programming DSL embedded in Python. Write relational logic programs using `.clausal` source files or directly in Python, with full constraint solving, tabling, DCGs, and a rich standard library.
4
+
5
+ ## Features
6
+
7
+ - **`.clausal` source files** — import logic modules with Python's standard import system
8
+ - **Prolog-style predicates** — Horn clauses, unification, backtracking search
9
+ - **Constraint solving** — CLP(FD) for integers, CLP(B) for booleans, `dif/2` disequality
10
+ - **SLG tabling** — memoised subgoal calls for termination on cyclic structures
11
+ - **DCGs** — Definite Clause Grammars with `>>` syntax and `phrase/2,3`
12
+ - **Well-Founded Semantics** — negation-as-failure with `\+` / `tabled` predicates
13
+ - **Module system** — `-import_from/2`, `-import_module/1`, qualified calls
14
+ - **Meta-predicates** — `findall/3`, `bagof/3`, `setof/3`, `forall/2`, `call/N`
15
+ - **Higher-order** — `maplist/2,3`, `foldl/4`, `include/3`, `exclude/3`
16
+ - **Python interop** — `++expr` escape, lambda goal closures, f-string support
17
+ - **SciPy integration** — wrappers for `scipy.special`, `scipy.linalg`, `scipy.optimize`, `scipy.interpolate`, `scipy.signal`
18
+ - **Regex** — `match/2,3`, `search/2,3`, `replace/4`, `split/3` with auto-binding goal expansion
19
+ - **Term expansion** — macro system for source-level term rewriting
20
+ - **C extensions** — fast logic variables, trail-based backtracking, trampoline (no WAM)
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install clausal
26
+ ```
27
+
28
+ Requires Python ≥ 3.13 and a C compiler for the extension modules.
29
+
30
+ ## Quick start
31
+
32
+ ### Python API
33
+
34
+ ```python
35
+ from clausal import query, solve, once, Var
36
+ from clausal.import_hook import LogicModule
37
+
38
+ # Load a .clausal module
39
+ import clausal.examples.family # if you have one
40
+ ```
41
+
42
+ ### Writing `.clausal` files
43
+
44
+ ```python
45
+ # family.clausal
46
+ parent(tom, bob) <-
47
+ parent(tom, liz) <-
48
+ parent(bob, ann) <-
49
+ parent(bob, pat) <-
50
+
51
+ grandparent(X_, Z_) <- (parent(X_, Y_), parent(Y_, Z_))
52
+ ```
53
+
54
+ ```python
55
+ # Python
56
+ import family # .clausal files load via the import hook
57
+
58
+ from clausal import query
59
+ results = list(query(family.grandparent(Var(), Var())))
60
+ # → [(tom, ann), (tom, pat)]
61
+ ```
62
+
63
+ ### Constraints (CLP(FD))
64
+
65
+ ```python
66
+ # queens.clausal
67
+ -import_from(clpfd, [in_, all_different, label])
68
+
69
+ queens(N_, QS_) <- (
70
+ length(QS_, N_),
71
+ QS_ in_ 1..N_,
72
+ safe(QS_),
73
+ label(QS_)
74
+ )
75
+ ```
76
+
77
+ ### Arithmetic & builtins
78
+
79
+ ```clausal
80
+ fib(0, 0) <-
81
+ fib(1, 1) <-
82
+ fib(N_, F_) <- (
83
+ N_ > 1,
84
+ N1_ is N_ - 1,
85
+ N2_ is N_ - 2,
86
+ fib(N1_, F1_),
87
+ fib(N2_, F2_),
88
+ F_ is F1_ + F2_
89
+ )
90
+ ```
91
+
92
+ ### DCGs
93
+
94
+ ```clausal
95
+ sentence >> (noun_phrase, verb_phrase)
96
+ noun_phrase >> ([the], noun)
97
+ verb_phrase >> ([runs])
98
+ noun >> ([cat])
99
+ noun >> ([dog])
100
+ ```
101
+
102
+ ```python
103
+ from clausal import once
104
+ result = once(phrase(sentence, [the, cat, runs]))
105
+ ```
106
+
107
+ ## Testing
108
+
109
+ `.clausal` files can include inline tests as `test/1` clauses:
110
+
111
+ ```clausal
112
+ test("fib(5) = 5") <- fib(5, 5)
113
+ ```
114
+
115
+ **Standalone runner:**
116
+
117
+ ```bash
118
+ python -m clausal.testing clausal/examples/ # all .clausal files
119
+ python -m clausal.testing clausal/examples/hanoi.clausal # single file
120
+ python -m clausal.testing -v clausal/examples/ # verbose
121
+ ```
122
+
123
+ **Via pytest** (`.clausal` tests collected automatically):
124
+
125
+ ```bash
126
+ python -m pytest tests/ clausal/examples/ -q
127
+ ```
128
+
129
+ ## Logic variables and backtracking
130
+
131
+ The C extension `clausal.logic.variables` provides Prolog-style logic variables and trail-based backtracking without a Warren Abstract Machine.
132
+
133
+ ```python
134
+ from clausal.logic.variables import Var, Trail, unify, is_var
135
+
136
+ trail = Trail()
137
+ X = Var()
138
+ Y = Var()
139
+
140
+ unify(X, 42, trail)
141
+ assert X.value == 42
142
+
143
+ mark = trail.mark()
144
+ unify(Y, "temporary", trail)
145
+ trail.undo(mark)
146
+ assert is_var(Y) # Y is unbound again
147
+ ```
148
+
149
+ See [docs/](docs/) for full documentation.
150
+
151
+ ## Requirements
152
+
153
+ - Python ≥ 3.13
154
+ - [greenlet](https://pypi.org/project/greenlet/)
155
+ - [pyyaml](https://pypi.org/project/PyYAML/) ≥ 6.0
156
+ - C compiler (for building from source)
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,63 @@
1
+ """clausal — logic programming for Python.
2
+
3
+ Top-level public API (Step 7 and later).
4
+ """
5
+
6
+ from clausal.logic.solve import call, solve, query, once, _deref_walk
7
+ from clausal.logic.database import Module, Database, Clause
8
+ from clausal.logic.variables import Var, Trail, deref, unify
9
+ from clausal.terms import Compound, KWTerm, Quantity, UnitsMismatch
10
+ from clausal.logic.builtins import (
11
+ structural_unify,
12
+ get_builtin_class,
13
+ _BUILTIN_CLASSES,
14
+ )
15
+ from clausal.logic.predicate import PredicateMeta, make_predicate
16
+ from clausal.logic.exceptions import LogicException
17
+ from clausal.repl import Solutions
18
+ import clausal.import_hook as _import_hook # registers .clausal finder on sys.meta_path
19
+
20
+
21
+ # ── Export all builtin predicate classes as top-level names ────────────────────
22
+ # This lets users write: from clausal import Append, Between, In, Length, ...
23
+
24
+ def _export_builtin_classes():
25
+ """Inject all builtin PredicateMeta classes into this module's namespace."""
26
+ import sys
27
+ mod = sys.modules[__name__]
28
+ names = []
29
+ for name, cls in _BUILTIN_CLASSES.items():
30
+ setattr(mod, name, cls)
31
+ names.append(name)
32
+ return names
33
+
34
+ _builtin_names = _export_builtin_classes()
35
+
36
+
37
+ __all__ = [
38
+ # Query API
39
+ "call",
40
+ "solve",
41
+ "query",
42
+ "once",
43
+ # Runtime objects
44
+ "Module",
45
+ "Database",
46
+ "Clause",
47
+ "Var",
48
+ "Trail",
49
+ "Compound",
50
+ "KWTerm",
51
+ "Quantity",
52
+ "UnitsMismatch",
53
+ "deref",
54
+ "unify",
55
+ "structural_unify",
56
+ "PredicateMeta",
57
+ "make_predicate",
58
+ "LogicException",
59
+ "get_builtin_class",
60
+ "Solutions",
61
+ # All builtin predicate classes
62
+ *_builtin_names,
63
+ ]
@@ -0,0 +1,166 @@
1
+ """
2
+ codegen.py — Compile Python AST nodes into callable functions.
3
+ """
4
+
5
+ import ast
6
+ import builtins as _builtins
7
+
8
+ __all__ = [
9
+ "functiondef_to_function",
10
+ "stmts_to_function",
11
+ ]
12
+
13
+ _BUILTIN_NAMES = frozenset(vars(_builtins))
14
+
15
+
16
+ def _infer_args(stmts: list[ast.stmt], globals_: dict | None = None) -> ast.arguments:
17
+ """Infer positional parameters by scanning name loads before assignments.
18
+
19
+ A name becomes a parameter if it is loaded before it is assigned anywhere
20
+ in ``stmts`` (at the top-level scope only — nested functions, classes,
21
+ lambdas, and comprehensions introduce their own scopes and are not
22
+ descended into, except that the outermost iterable of a comprehension IS
23
+ evaluated in the enclosing scope). Built-in names and names present in
24
+ ``globals_`` are excluded (they are already available, not arguments).
25
+
26
+ Order is the order of first qualifying load.
27
+ """
28
+ assigned: set[str] = set()
29
+ params: list[str] = []
30
+ seen: set[str] = set()
31
+ excluded = _BUILTIN_NAMES if not globals_ else _BUILTIN_NAMES | globals_.keys()
32
+
33
+ class _Scanner(ast.NodeVisitor):
34
+ # ── nested scopes: record the name being bound, but do not descend ─────
35
+ def visit_FunctionDef(self, node):
36
+ assigned.add(node.name)
37
+ visit_AsyncFunctionDef = visit_FunctionDef
38
+ def visit_ClassDef(self, node):
39
+ assigned.add(node.name)
40
+ def visit_Lambda(self, node): pass
41
+
42
+ # Only the outermost iterable of a comprehension is in the enclosing scope.
43
+ def _comp(self, node):
44
+ self.visit(node.generators[0].iter)
45
+ visit_ListComp = _comp
46
+ visit_SetComp = _comp
47
+ visit_DictComp = _comp
48
+ visit_GeneratorExp = _comp
49
+
50
+ # ── name handling ─────────────────────────────────────────────────────
51
+ def _record_load(self, name: str):
52
+ if name not in assigned and name not in seen and name not in excluded:
53
+ params.append(name)
54
+ seen.add(name)
55
+
56
+ def visit_Name(self, node):
57
+ if isinstance(node.ctx, ast.Load):
58
+ self._record_load(node.id)
59
+ elif isinstance(node.ctx, (ast.Store, ast.Del)):
60
+ assigned.add(node.id)
61
+
62
+ # ── assignment ordering: value (RHS) must be visited before targets ──
63
+ def visit_Assign(self, node):
64
+ self.visit(node.value)
65
+ for target in node.targets:
66
+ self.visit(target)
67
+
68
+ def visit_AnnAssign(self, node):
69
+ if node.value:
70
+ self.visit(node.value)
71
+ self.visit(node.target)
72
+
73
+ def visit_AugAssign(self, node):
74
+ # Read-modify-write: target is implicitly loaded before being stored.
75
+ if isinstance(node.target, ast.Name):
76
+ self._record_load(node.target.id)
77
+ assigned.add(node.target.id)
78
+ else:
79
+ self.visit(node.target) # Attribute/Subscript: object is Load
80
+ self.visit(node.value)
81
+
82
+ # ── for loop: evaluate iterable before assigning loop target ─────────
83
+ def visit_For(self, node):
84
+ self.visit(node.iter)
85
+ self.visit(node.target)
86
+ for s in node.body:
87
+ self.visit(s)
88
+ for s in node.orelse:
89
+ self.visit(s)
90
+
91
+ # ── global/nonlocal: treat as already available, not parameters ──────
92
+ def visit_Global(self, node):
93
+ for name in node.names:
94
+ assigned.add(name)
95
+
96
+ def visit_Nonlocal(self, node):
97
+ for name in node.names:
98
+ assigned.add(name)
99
+
100
+ scanner = _Scanner()
101
+ for stmt in stmts:
102
+ scanner.visit(stmt)
103
+
104
+ return ast.arguments(
105
+ posonlyargs=[],
106
+ args=[ast.arg(arg=name) for name in params],
107
+ vararg=None,
108
+ kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[],
109
+ )
110
+
111
+
112
+ def functiondef_to_function(
113
+ node: ast.FunctionDef | ast.AsyncFunctionDef,
114
+ globals_: dict | None = None,
115
+ filename: str = "<template>",
116
+ ):
117
+ """Compile an ast.FunctionDef (or AsyncFunctionDef) into a Python callable.
118
+
119
+ The function name is read from node.name. globals_ provides names
120
+ visible at definition time (closures, helper functions, etc.).
121
+ """
122
+ module = ast.Module(body=[node], type_ignores=[])
123
+ ast.fix_missing_locations(module)
124
+ code = compile(module, filename, "exec")
125
+ ns = dict(globals_ or {})
126
+ exec(code, ns)
127
+ return ns[node.name]
128
+
129
+
130
+ def stmts_to_function(
131
+ body: list[ast.stmt],
132
+ name: str,
133
+ args: ast.arguments | None = None,
134
+ *,
135
+ decorators: list[ast.expr] | None = None,
136
+ returns: ast.expr | None = None,
137
+ globals_: dict | None = None,
138
+ filename: str = "<template>",
139
+ ):
140
+ """Wrap a statement list in a function definition and return a callable.
141
+
142
+ Parameters
143
+ ----------
144
+ body: statements forming the function body
145
+ name: name to give the function
146
+ args: parameter signature; if omitted, inferred from the body via
147
+ ``_infer_args`` (names that are loaded before being assigned
148
+ become positional parameters, in order of first load)
149
+ decorators: decorator list
150
+ returns: return-annotation expression
151
+ globals_: names made available in the function's defining scope
152
+ filename: filename used in tracebacks
153
+ """
154
+ if args is None:
155
+ args = _infer_args(body, globals_)
156
+ extra = {'type_params': []} if 'type_params' in ast.FunctionDef._fields else {}
157
+ func_node = ast.FunctionDef(
158
+ name=name,
159
+ args=args,
160
+ body=body,
161
+ decorator_list=decorators or [],
162
+ returns=returns,
163
+ type_comment=None,
164
+ **extra,
165
+ )
166
+ return functiondef_to_function(func_node, globals_=globals_, filename=filename)