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.
- clausal-0.3.0/LICENSE +21 -0
- clausal-0.3.0/MANIFEST.in +4 -0
- clausal-0.3.0/PKG-INFO +176 -0
- clausal-0.3.0/README.md +160 -0
- clausal-0.3.0/clausal/__init__.py +63 -0
- clausal-0.3.0/clausal/codegen.py +166 -0
- clausal-0.3.0/clausal/import_hook.py +645 -0
- clausal-0.3.0/clausal/logic/__init__.py +0 -0
- clausal-0.3.0/clausal/logic/_trampoline.c +406 -0
- clausal-0.3.0/clausal/logic/builtins/__init__.py +86 -0
- clausal-0.3.0/clausal/logic/builtins/_helpers.py +96 -0
- clausal-0.3.0/clausal/logic/builtins/_registry.py +407 -0
- clausal-0.3.0/clausal/logic/builtins/arithmetic.py +171 -0
- clausal-0.3.0/clausal/logic/builtins/constraints.py +135 -0
- clausal-0.3.0/clausal/logic/builtins/control.py +74 -0
- clausal-0.3.0/clausal/logic/builtins/database_ops.py +255 -0
- clausal-0.3.0/clausal/logic/builtins/dcg.py +64 -0
- clausal-0.3.0/clausal/logic/builtins/dict_set.py +444 -0
- clausal-0.3.0/clausal/logic/builtins/higher_order.py +434 -0
- clausal-0.3.0/clausal/logic/builtins/inspection.py +224 -0
- clausal-0.3.0/clausal/logic/builtins/io.py +104 -0
- clausal-0.3.0/clausal/logic/builtins/keyword_ops.py +116 -0
- clausal-0.3.0/clausal/logic/builtins/lists.py +450 -0
- clausal-0.3.0/clausal/logic/builtins/pairs.py +57 -0
- clausal-0.3.0/clausal/logic/builtins/type_checks.py +101 -0
- clausal-0.3.0/clausal/logic/clpb.py +771 -0
- clausal-0.3.0/clausal/logic/clpfd.py +1010 -0
- clausal-0.3.0/clausal/logic/clpr.py +923 -0
- clausal-0.3.0/clausal/logic/compiler.py +6734 -0
- clausal-0.3.0/clausal/logic/compiler_v2.py +219 -0
- clausal-0.3.0/clausal/logic/constraints.py +244 -0
- clausal-0.3.0/clausal/logic/continuation_search.py +30 -0
- clausal-0.3.0/clausal/logic/database.py +431 -0
- clausal-0.3.0/clausal/logic/exceptions.py +71 -0
- clausal-0.3.0/clausal/logic/goal_expansion.py +289 -0
- clausal-0.3.0/clausal/logic/predicate.py +259 -0
- clausal-0.3.0/clausal/logic/reif.py +91 -0
- clausal-0.3.0/clausal/logic/solve.py +344 -0
- clausal-0.3.0/clausal/logic/tabling.py +562 -0
- clausal-0.3.0/clausal/logic/term_expansion.py +291 -0
- clausal-0.3.0/clausal/logic/trampoline.py +238 -0
- clausal-0.3.0/clausal/logic/units_constraint.py +120 -0
- clausal-0.3.0/clausal/logic/variables/__init__.py +37 -0
- clausal-0.3.0/clausal/logic/variables/_variables.c +1600 -0
- clausal-0.3.0/clausal/modules/__init__.py +31 -0
- clausal-0.3.0/clausal/modules/date_time.py +8 -0
- clausal-0.3.0/clausal/modules/graphs.py +669 -0
- clausal-0.3.0/clausal/modules/log.py +13 -0
- clausal-0.3.0/clausal/modules/py/__init__.py +35 -0
- clausal-0.3.0/clausal/modules/py/_scipy_relations.py +82 -0
- clausal-0.3.0/clausal/modules/py/_scipy_units.py +174 -0
- clausal-0.3.0/clausal/modules/py/datetime.py +417 -0
- clausal-0.3.0/clausal/modules/py/imperial.py +119 -0
- clausal-0.3.0/clausal/modules/py/logging.py +374 -0
- clausal-0.3.0/clausal/modules/py/re.py +229 -0
- clausal-0.3.0/clausal/modules/py/scipy_cluster.py +304 -0
- clausal-0.3.0/clausal/modules/py/scipy_constants.py +223 -0
- clausal-0.3.0/clausal/modules/py/scipy_differentiate.py +349 -0
- clausal-0.3.0/clausal/modules/py/scipy_fft.py +250 -0
- clausal-0.3.0/clausal/modules/py/scipy_integrate.py +444 -0
- clausal-0.3.0/clausal/modules/py/scipy_interpolate.py +659 -0
- clausal-0.3.0/clausal/modules/py/scipy_linalg.py +503 -0
- clausal-0.3.0/clausal/modules/py/scipy_ndimage.py +250 -0
- clausal-0.3.0/clausal/modules/py/scipy_optimize.py +328 -0
- clausal-0.3.0/clausal/modules/py/scipy_signal.py +440 -0
- clausal-0.3.0/clausal/modules/py/scipy_sparse.py +521 -0
- clausal-0.3.0/clausal/modules/py/scipy_spatial.py +629 -0
- clausal-0.3.0/clausal/modules/py/scipy_special.py +447 -0
- clausal-0.3.0/clausal/modules/py/scipy_stats.py +717 -0
- clausal-0.3.0/clausal/modules/py/sklearn.py +1326 -0
- clausal-0.3.0/clausal/modules/py/spacy.py +31 -0
- clausal-0.3.0/clausal/modules/py/sqlite.py +296 -0
- clausal-0.3.0/clausal/modules/py/sympy.py +1333 -0
- clausal-0.3.0/clausal/modules/py/units.py +513 -0
- clausal-0.3.0/clausal/modules/py/uuid.py +320 -0
- clausal-0.3.0/clausal/modules/py/yaml.py +225 -0
- clausal-0.3.0/clausal/modules/regex.py +7 -0
- clausal-0.3.0/clausal/modules/scipy_cluster.py +7 -0
- clausal-0.3.0/clausal/modules/scipy_constants.py +10 -0
- clausal-0.3.0/clausal/modules/scipy_differentiate.py +5 -0
- clausal-0.3.0/clausal/modules/scipy_fft.py +12 -0
- clausal-0.3.0/clausal/modules/scipy_integrate.py +8 -0
- clausal-0.3.0/clausal/modules/scipy_interpolate.py +18 -0
- clausal-0.3.0/clausal/modules/scipy_linalg.py +12 -0
- clausal-0.3.0/clausal/modules/scipy_ndimage.py +18 -0
- clausal-0.3.0/clausal/modules/scipy_optimize.py +12 -0
- clausal-0.3.0/clausal/modules/scipy_signal.py +23 -0
- clausal-0.3.0/clausal/modules/scipy_sparse.py +17 -0
- clausal-0.3.0/clausal/modules/scipy_spatial.py +22 -0
- clausal-0.3.0/clausal/modules/scipy_special.py +17 -0
- clausal-0.3.0/clausal/modules/scipy_stats.py +19 -0
- clausal-0.3.0/clausal/modules/spacy_module.py +483 -0
- clausal-0.3.0/clausal/modules/sqlite.py +9 -0
- clausal-0.3.0/clausal/modules/sympy_module.py +15 -0
- clausal-0.3.0/clausal/modules/units.py +2 -0
- clausal-0.3.0/clausal/modules/uuid_mod.py +9 -0
- clausal-0.3.0/clausal/modules/yaml_module.py +7 -0
- clausal-0.3.0/clausal/pythonic_ast/__init__.py +0 -0
- clausal-0.3.0/clausal/pythonic_ast/conversion_from_python_ast.py +670 -0
- clausal-0.3.0/clausal/pythonic_ast/node_class.py +263 -0
- clausal-0.3.0/clausal/pythonic_ast/nodes.py +1292 -0
- clausal-0.3.0/clausal/pythonic_ast/transform.py +29 -0
- clausal-0.3.0/clausal/pythonic_terms.py +113 -0
- clausal-0.3.0/clausal/regex.py +2 -0
- clausal-0.3.0/clausal/repl.py +288 -0
- clausal-0.3.0/clausal/stdlib/__init__.py +0 -0
- clausal-0.3.0/clausal/templating/__init__.py +0 -0
- clausal-0.3.0/clausal/templating/compiler.py +478 -0
- clausal-0.3.0/clausal/templating/parser.py +104 -0
- clausal-0.3.0/clausal/templating/term_rewriting.py +2993 -0
- clausal-0.3.0/clausal/terms.py +872 -0
- clausal-0.3.0/clausal/testing.py +182 -0
- clausal-0.3.0/clausal/tools/__init__.py +1 -0
- clausal-0.3.0/clausal/tools/clear_pycache.py +68 -0
- clausal-0.3.0/clausal/tools/dump_transformed.py +125 -0
- clausal-0.3.0/clausal/tools/visualize.py +201 -0
- clausal-0.3.0/clausal.egg-info/PKG-INFO +176 -0
- clausal-0.3.0/clausal.egg-info/SOURCES.txt +204 -0
- clausal-0.3.0/clausal.egg-info/dependency_links.txt +1 -0
- clausal-0.3.0/clausal.egg-info/requires.txt +5 -0
- clausal-0.3.0/clausal.egg-info/top_level.txt +1 -0
- clausal-0.3.0/pyproject.toml +31 -0
- clausal-0.3.0/setup.cfg +4 -0
- clausal-0.3.0/setup.py +15 -0
- clausal-0.3.0/tests/test_body_star_decon.py +551 -0
- clausal-0.3.0/tests/test_builtin_classes.py +430 -0
- clausal-0.3.0/tests/test_builtins.py +916 -0
- clausal-0.3.0/tests/test_callsite_specialization.py +661 -0
- clausal-0.3.0/tests/test_clausal_modules.py +750 -0
- clausal-0.3.0/tests/test_clpb.py +883 -0
- clausal-0.3.0/tests/test_clpfd.py +746 -0
- clausal-0.3.0/tests/test_clpr.py +651 -0
- clausal-0.3.0/tests/test_codegen.py +190 -0
- clausal-0.3.0/tests/test_compiled_programs.py +774 -0
- clausal-0.3.0/tests/test_compiler.py +1002 -0
- clausal-0.3.0/tests/test_compiler_goals.py +722 -0
- clausal-0.3.0/tests/test_compiler_optimizations.py +1091 -0
- clausal-0.3.0/tests/test_compiler_trampoline.py +634 -0
- clausal-0.3.0/tests/test_compiler_v2.py +245 -0
- clausal-0.3.0/tests/test_continuation_search.py +152 -0
- clausal-0.3.0/tests/test_control.py +103 -0
- clausal-0.3.0/tests/test_database.py +371 -0
- clausal-0.3.0/tests/test_date_time.py +562 -0
- clausal-0.3.0/tests/test_dcg.py +636 -0
- clausal-0.3.0/tests/test_deep_indexing.py +642 -0
- clausal-0.3.0/tests/test_dict_set_builtins.py +607 -0
- clausal-0.3.0/tests/test_dict_set_compiler.py +193 -0
- clausal-0.3.0/tests/test_dict_set_terms.py +325 -0
- clausal-0.3.0/tests/test_dif.py +459 -0
- clausal-0.3.0/tests/test_directives.py +225 -0
- clausal-0.3.0/tests/test_edcg.py +518 -0
- clausal-0.3.0/tests/test_exceptions.py +423 -0
- clausal-0.3.0/tests/test_first_arg_index.py +508 -0
- clausal-0.3.0/tests/test_groundness_dispatch.py +481 -0
- clausal-0.3.0/tests/test_higher_order.py +321 -0
- clausal-0.3.0/tests/test_import.py +380 -0
- clausal-0.3.0/tests/test_io.py +582 -0
- clausal-0.3.0/tests/test_ipython_integration.py +128 -0
- clausal-0.3.0/tests/test_lambdas.py +778 -0
- clausal-0.3.0/tests/test_list_edge_cases.py +548 -0
- clausal-0.3.0/tests/test_list_util.py +399 -0
- clausal-0.3.0/tests/test_logging_module.py +422 -0
- clausal-0.3.0/tests/test_meta.py +315 -0
- clausal-0.3.0/tests/test_module_imports.py +446 -0
- clausal-0.3.0/tests/test_mutable_dict_set.py +130 -0
- clausal-0.3.0/tests/test_predicate_meta.py +381 -0
- clausal-0.3.0/tests/test_pycache.py +291 -0
- clausal-0.3.0/tests/test_python_interop.py +187 -0
- clausal-0.3.0/tests/test_regex.py +733 -0
- clausal-0.3.0/tests/test_reif_builtins.py +408 -0
- clausal-0.3.0/tests/test_reified_ite.py +1541 -0
- clausal-0.3.0/tests/test_repl.py +147 -0
- clausal-0.3.0/tests/test_scipy_cluster.py +449 -0
- clausal-0.3.0/tests/test_scipy_constants.py +264 -0
- clausal-0.3.0/tests/test_scipy_differentiate.py +419 -0
- clausal-0.3.0/tests/test_scipy_fft.py +529 -0
- clausal-0.3.0/tests/test_scipy_integrate.py +592 -0
- clausal-0.3.0/tests/test_scipy_interpolate.py +581 -0
- clausal-0.3.0/tests/test_scipy_linalg.py +543 -0
- clausal-0.3.0/tests/test_scipy_ndimage.py +562 -0
- clausal-0.3.0/tests/test_scipy_optimize.py +460 -0
- clausal-0.3.0/tests/test_scipy_signal.py +681 -0
- clausal-0.3.0/tests/test_scipy_sparse.py +636 -0
- clausal-0.3.0/tests/test_scipy_spatial.py +664 -0
- clausal-0.3.0/tests/test_scipy_special.py +668 -0
- clausal-0.3.0/tests/test_scipy_stats.py +905 -0
- clausal-0.3.0/tests/test_search.py +891 -0
- clausal-0.3.0/tests/test_simple_ast.py +997 -0
- clausal-0.3.0/tests/test_slg_termination.py +225 -0
- clausal-0.3.0/tests/test_solve.py +396 -0
- clausal-0.3.0/tests/test_spacy_module.py +701 -0
- clausal-0.3.0/tests/test_sqlite.py +594 -0
- clausal-0.3.0/tests/test_sympy_module.py +506 -0
- clausal-0.3.0/tests/test_tabling.py +546 -0
- clausal-0.3.0/tests/test_template_compiler.py +779 -0
- clausal-0.3.0/tests/test_term_expansion.py +449 -0
- clausal-0.3.0/tests/test_term_inspection.py +364 -0
- clausal-0.3.0/tests/test_term_rewriting.py +663 -0
- clausal-0.3.0/tests/test_terms.py +326 -0
- clausal-0.3.0/tests/test_transform_nodes.py +340 -0
- clausal-0.3.0/tests/test_unify.py +331 -0
- clausal-0.3.0/tests/test_units.py +1647 -0
- clausal-0.3.0/tests/test_uuid_module.py +511 -0
- clausal-0.3.0/tests/test_variables.py +612 -0
- clausal-0.3.0/tests/test_wfs.py +471 -0
- 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.
|
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
|
clausal-0.3.0/README.md
ADDED
|
@@ -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)
|