implies/
symbol.rs

1//! This module contains the generic [`crate::symbol::Symbol`] type which wraps atomic,
2//! binary and unary operator types, and also other types that are likely to be generic over
3//! multiple logical languages, such as [`crate::symbol::Atom`] which can be used as an atomic
4//! type for many different logics.
5
6use super::formula::{Tree, Zipper};
7use crate::parser::Match;
8#[cfg(feature = "python")]
9use pyo3::prelude::*;
10use std::fmt::Display;
11use std::hash::Hash;
12use std::ops::{Deref, DerefMut};
13
14/// marker trait to show a type implements appropriate traits to be a symbol in a formula
15pub trait Symbolic:
16    Copy + PartialEq + Eq + PartialOrd + Ord + Clone + Display + Hash + Default
17{
18}
19
20#[derive(Copy, PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Debug)]
21pub enum Symbol<B, U, A>
22where
23    B: Symbolic,
24    U: Symbolic,
25    A: Symbolic,
26{
27    Binary(B),
28    Unary(U),
29    Atom(A),
30    Left,
31    Right, // Left and Right parentheses
32}
33
34impl<B, U, A> Display for Symbol<B, U, A>
35where
36    B: Symbolic,
37    U: Symbolic,
38    A: Symbolic,
39{
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Symbol::Binary(x) => {
43                write!(f, "{}", x.to_string())
44            }
45            Symbol::Unary(x) => {
46                write!(f, "{}", x.to_string())
47            }
48            Symbol::Atom(x) => {
49                write!(f, "{}", x.to_string())
50            }
51            Symbol::Left => {
52                write!(f, "(")
53            }
54            Symbol::Right => {
55                write!(f, ")")
56            }
57        }
58    }
59}
60
61/// A generic type for when we need to compare over B, U, and A, the types
62/// that go into our formulae. Since they implement Ord individually this wrapper
63/// type allows comparison between any of the three types assuming the convention
64/// that U(nary) operators always have higher precedence than B(inary) operators.
65impl<B, U, A> Symbol<B, U, A>
66where
67    B: Symbolic,
68    U: Symbolic,
69    A: Symbolic,
70{
71    pub fn from_tree(t: &Tree<B, U, A>) -> Self {
72        match t {
73            Tree::Binary {
74                conn,
75                left: _,
76                right: _,
77            } => Symbol::Binary(*conn),
78            Tree::Unary { conn, next: _ } => Symbol::Unary(*conn),
79            Tree::Atom(a) => Symbol::Atom(*a),
80        }
81    }
82
83    /// Turn the 'value' of a zipper into a symbol, or none for a top zipper.
84    pub fn from_zipper(z: &Zipper<B, U, A>) -> Option<Self> {
85        match z {
86            Zipper::Top => None,
87            Zipper::Right { bin, .. } => Some(Symbol::Binary(*bin)),
88            Zipper::Left { bin, .. } => Some(Symbol::Binary(*bin)),
89            Zipper::Up { un, .. } => Some(Symbol::Unary(*un)),
90        }
91    }
92}
93
94impl<B, U, A> Match for Symbol<B, U, A>
95where
96    B: Symbolic + Match,
97    U: Symbolic + Match,
98    A: Symbolic + Match,
99{
100    fn match_str(s: &str) -> Option<Self> {
101        if s == "(" {
102            Some(Symbol::Left)
103        } else if s == ")" {
104            Some(Symbol::Right)
105        } else if let Some(b) = B::match_str(s) {
106            Some(Symbol::Binary(b))
107        } else if let Some(u) = U::match_str(s) {
108            Some(Symbol::Unary(u))
109        } else if let Some(a) = A::match_str(s) {
110            Some(Symbol::Atom(a))
111        } else {
112            None
113        }
114    }
115
116    fn get_matches(&self) -> Vec<String> {
117        match self {
118            Symbol::Binary(s) => s.get_matches(),
119            Symbol::Unary(s) => s.get_matches(),
120            Symbol::Atom(s) => s.get_matches(),
121            Symbol::Left => vec!["(".to_string()],
122            Symbol::Right => vec![")".to_string()],
123        }
124    }
125}
126
127pub static ATOMS: [&'static str; 52] = [
128    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
129    "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
130    "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
131];
132
133/// A simple type to represent atoms: a wrapper around unsigned integers.
134/// Implements Deref to `usize` for ease of use. In terms of being parsed,
135/// any atom less than 26 maps to a corresponding lowercase letter and those
136/// from `26..52` map to the corresponding uppercase letter. If for whatever
137/// reason you need more than 52 atoms, then they can only be printed/parsed
138/// as the corresponding numbers.
139#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug, Default)]
140#[cfg_attr(feature = "python", pyclass)]
141pub struct Atom(pub usize);
142
143impl Deref for Atom {
144    type Target = usize;
145
146    fn deref(&self) -> &Self::Target {
147        &self.0
148    }
149}
150
151impl DerefMut for Atom {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        &mut self.0
154    }
155}
156
157impl Display for Atom {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        if **self < ATOMS.len() {
160            write!(f, "{}", ATOMS[**self])
161        } else {
162            write!(f, "{}", self.to_string())
163        }
164    }
165}
166
167impl Symbolic for Atom {}
168
169impl Match for Atom {
170    fn match_str(s: &str) -> Option<Self> {
171        if let Some(i) = ATOMS.iter().position(|val| &s == val) {
172            Some(Atom(i))
173        } else if let Ok(i) = s.parse::<usize>() {
174            Some(Atom(i))
175        } else {
176            None
177        }
178    }
179
180    fn get_matches(&self) -> Vec<String> {
181        match self.0 {
182            ..=51 => vec![self.0.to_string(), ATOMS[self.0].to_string()],
183            _ => vec![self.0.to_string()],
184        }
185    }
186}