# clausal

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.

## Features

- **`.clausal` source files** — import logic modules with Python's standard import system
- **Prolog-style predicates** — Horn clauses, unification, backtracking search
- **Constraint solving** — CLP(FD) for integers, CLP(B) for booleans, `dif/2` disequality
- **SLG tabling** — memoised subgoal calls for termination on cyclic structures
- **DCGs** — Definite Clause Grammars with `>>` syntax and `phrase/2,3`
- **Well-Founded Semantics** — negation-as-failure with `\+` / `tabled` predicates
- **Module system** — `-import_from/2`, `-import_module/1`, qualified calls
- **Meta-predicates** — `findall/3`, `bagof/3`, `setof/3`, `forall/2`, `call/N`
- **Higher-order** — `maplist/2,3`, `foldl/4`, `include/3`, `exclude/3`
- **Python interop** — `++expr` escape, lambda goal closures, f-string support
- **SciPy integration** — wrappers for `scipy.special`, `scipy.linalg`, `scipy.optimize`, `scipy.interpolate`, `scipy.signal`
- **Regex** — `match/2,3`, `search/2,3`, `replace/4`, `split/3` with auto-binding goal expansion
- **Term expansion** — macro system for source-level term rewriting
- **C extensions** — fast logic variables, trail-based backtracking, trampoline (no WAM)

## Installation

```bash
pip install clausal
```

Requires Python ≥ 3.13 and a C compiler for the extension modules.

## Quick start

### Python API

```python
from clausal import query, solve, once, Var
from clausal.import_hook import LogicModule

# Load a .clausal module
import clausal.examples.family  # if you have one
```

### Writing `.clausal` files

```python
# family.clausal
parent(tom, bob) <-
parent(tom, liz) <-
parent(bob, ann) <-
parent(bob, pat) <-

grandparent(X_, Z_) <- (parent(X_, Y_), parent(Y_, Z_))
```

```python
# Python
import family   # .clausal files load via the import hook

from clausal import query
results = list(query(family.grandparent(Var(), Var())))
# → [(tom, ann), (tom, pat)]
```

### Constraints (CLP(FD))

```python
# queens.clausal
-import_from(clpfd, [in_, all_different, label])

queens(N_, QS_) <- (
    length(QS_, N_),
    QS_ in_ 1..N_,
    safe(QS_),
    label(QS_)
)
```

### Arithmetic & builtins

```clausal
fib(0, 0) <-
fib(1, 1) <-
fib(N_, F_) <- (
    N_ > 1,
    N1_ is N_ - 1,
    N2_ is N_ - 2,
    fib(N1_, F1_),
    fib(N2_, F2_),
    F_ is F1_ + F2_
)
```

### DCGs

```clausal
sentence >> (noun_phrase, verb_phrase)
noun_phrase >> ([the], noun)
verb_phrase >> ([runs])
noun >> ([cat])
noun >> ([dog])
```

```python
from clausal import once
result = once(phrase(sentence, [the, cat, runs]))
```

## Testing

`.clausal` files can include inline tests as `test/1` clauses:

```clausal
test("fib(5) = 5") <- fib(5, 5)
```

**Standalone runner:**

```bash
python -m clausal.testing clausal/examples/           # all .clausal files
python -m clausal.testing clausal/examples/hanoi.clausal  # single file
python -m clausal.testing -v clausal/examples/        # verbose
```

**Via pytest** (`.clausal` tests collected automatically):

```bash
python -m pytest tests/ clausal/examples/ -q
```

## Logic variables and backtracking

The C extension `clausal.logic.variables` provides Prolog-style logic variables and trail-based backtracking without a Warren Abstract Machine.

```python
from clausal.logic.variables import Var, Trail, unify, is_var

trail = Trail()
X = Var()
Y = Var()

unify(X, 42, trail)
assert X.value == 42

mark = trail.mark()
unify(Y, "temporary", trail)
trail.undo(mark)
assert is_var(Y)   # Y is unbound again
```

See [docs/](docs/) for full documentation.

## Requirements

- Python ≥ 3.13
- [greenlet](https://pypi.org/project/greenlet/)
- [pyyaml](https://pypi.org/project/PyYAML/) ≥ 6.0
- C compiler (for building from source)

## License

MIT
