implies/lib.rs
1//! *implies* is a Rust crate for easily creating and manipulating
2//! logical formulas represented as parse trees.
3//!
4//! A few things make using *implies* more ergonomic than, say, a generic
5//! binary tree data structure, for a logic use-case.
6//!
7//! 1. The [`Formula`] struct, which is the heart of the crate, is an `enum`
8//! which makes it impossible to construct ill-formed formulas by forcing each
9//! node to specify whether it's an atomic formula, a unary formula, or a binary formula.
10//! 2. [`Formula`] is generic over any language which has binary and unary operators,
11//! and atomic formulas, i.e. the overwhelming majority of logics.
12//! Simply providing enums for your operators and atomic types
13//! grants you access to the struct's full power.
14//! 3. Transformations are safe and fast. Internally the formulas are implemented as
15//! a kind of [`Zipper`] which allows easy in-place mutation. All transforming methods
16//! are designed for safety, only being executed when the state of the formula is valid
17//! for that specific transformation. This also allows code that can call large chains
18//! of transformations on formulae without branching, often necessary when executing common
19//! transformation algorithms, e.g. natural deduction rules or conversion to normal forms.
20//! 4. The [`Formula`] struct comes with several transformations that are ubiquitous for logical
21//! formulas like rotating precedence and operator distribution.
22//! 5. If you implement the [`Match`] trait for your types, you have full access to a lexer and parser
23//! which converts strings into formulas for you, making it easy to convert back and forth between
24//! strings and trees for your own formulas.
25//!
26//! There is a built-in implementation of propositional logic as [`PropFormula`], but again,
27//! you can immediately start using this crate by defining your own atomic, binary operator, and
28//! unary operator types for whatever logic you want to use. They do have to implement the [`Symbolic`]
29//! trait which is just a wrapper of pretty basic requirements for the most part. The surprising ones
30//! may be [`Ord`] + [`PartialOrd`], but this is of course for determining operator precedence; and
31//! [`Copy`] which is because it's assumed the types of the operators/atoms are cheap, i.e. fieldless enums
32//! or integers. This may be relaxed later, however.
33//!
34//! Fair warning, though: if you've never used a [`Zipper`] data structure before e.g. in a functional
35//! language, this crate may seem really counterintuitive. It's recommended to understand/play around
36//! with zipper data structures a little bit before intensely using this crate.
37//!
38//! [`PropFormula`]: self::prop::PropFormula
39//! [`Symbolic`]: self::symbol::Symbolic
40//! [`Ord`]: std::cmp::Ord
41//! [`PartialOrd`]: std::cmp::PartialOrd
42//! [`Copy`]: std::marker::Copy
43//! [`Formula`]: formula::Formula
44//! [`Zipper`]: formula::Zipper
45//! [`Match`]: parser::Match
46
47pub mod formula;
48pub mod modal;
49pub mod parser;
50pub mod prop;
51pub mod symbol;
52
53mod tests;
54
55#[cfg(feature = "python")]
56pub mod python;
57
58#[cfg(feature = "python")]
59use {pyo3::prelude::*, python::proposition::Proposition};
60
61#[cfg(feature = "python")]
62#[pymodule]
63fn implies(py: Python<'_>, m: &PyModule) -> PyResult<()> {
64 m.add_class::<symbol::Atom>()?;
65 m.add_class::<prop::PropBinary>()?;
66 m.add_class::<prop::PropUnary>()?;
67 m.add_class::<Proposition>()?;
68 Ok(())
69}