use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SymbolKind {
Quantifier,
Connective,
Variable,
Predicate,
Constant,
Modal,
Identity,
Punctuation,
Temporal,
}
impl SymbolKind {
pub fn label(&self) -> &'static str {
match self {
SymbolKind::Quantifier => "Quantifier",
SymbolKind::Connective => "Connective",
SymbolKind::Variable => "Variable",
SymbolKind::Predicate => "Predicate",
SymbolKind::Constant => "Constant",
SymbolKind::Modal => "Modal",
SymbolKind::Identity => "Identity",
SymbolKind::Punctuation => "Punctuation",
SymbolKind::Temporal => "Temporal",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SymbolEntry {
pub symbol: String,
pub kind: SymbolKind,
pub description: String,
}
pub fn extract_symbols(logic: &str) -> Vec<SymbolEntry> {
let mut entries = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
if logic.contains("∀") && seen.insert("∀".to_string()) {
entries.push(SymbolEntry {
symbol: "∀".to_string(),
kind: SymbolKind::Quantifier,
description: "Universal quantifier: \"for all\"".to_string(),
});
}
if logic.contains("∃") && seen.insert("∃".to_string()) {
entries.push(SymbolEntry {
symbol: "∃".to_string(),
kind: SymbolKind::Quantifier,
description: "Existential quantifier: \"there exists\"".to_string(),
});
}
if logic.contains("∃!") && seen.insert("∃!".to_string()) {
entries.push(SymbolEntry {
symbol: "∃!".to_string(),
kind: SymbolKind::Quantifier,
description: "Unique existence: \"there exists exactly one\"".to_string(),
});
}
if logic.contains("MOST") && seen.insert("MOST".to_string()) {
entries.push(SymbolEntry {
symbol: "MOST".to_string(),
kind: SymbolKind::Quantifier,
description: "Generalized quantifier: \"most\"".to_string(),
});
}
if logic.contains("FEW") && seen.insert("FEW".to_string()) {
entries.push(SymbolEntry {
symbol: "FEW".to_string(),
kind: SymbolKind::Quantifier,
description: "Generalized quantifier: \"few\"".to_string(),
});
}
if logic.contains("∧") && seen.insert("∧".to_string()) {
entries.push(SymbolEntry {
symbol: "∧".to_string(),
kind: SymbolKind::Connective,
description: "Conjunction: \"and\"".to_string(),
});
}
if logic.contains("∨") && seen.insert("∨".to_string()) {
entries.push(SymbolEntry {
symbol: "∨".to_string(),
kind: SymbolKind::Connective,
description: "Disjunction: \"or\"".to_string(),
});
}
if logic.contains("→") && seen.insert("→".to_string()) {
entries.push(SymbolEntry {
symbol: "→".to_string(),
kind: SymbolKind::Connective,
description: "Implication: \"if...then\"".to_string(),
});
}
if logic.contains("↔") && seen.insert("↔".to_string()) {
entries.push(SymbolEntry {
symbol: "↔".to_string(),
kind: SymbolKind::Connective,
description: "Biconditional: \"if and only if\"".to_string(),
});
}
if logic.contains("¬") && seen.insert("¬".to_string()) {
entries.push(SymbolEntry {
symbol: "¬".to_string(),
kind: SymbolKind::Connective,
description: "Negation: \"not\"".to_string(),
});
}
if logic.contains("□") && seen.insert("□".to_string()) {
entries.push(SymbolEntry {
symbol: "□".to_string(),
kind: SymbolKind::Modal,
description: "Necessity: \"it is necessary that\"".to_string(),
});
}
if logic.contains("◇") && seen.insert("◇".to_string()) {
entries.push(SymbolEntry {
symbol: "◇".to_string(),
kind: SymbolKind::Modal,
description: "Possibility: \"it is possible that\"".to_string(),
});
}
if logic.contains("O_") && seen.insert("O".to_string()) {
entries.push(SymbolEntry {
symbol: "O".to_string(),
kind: SymbolKind::Modal,
description: "Deontic obligation: \"it ought to be that\"".to_string(),
});
}
if logic.contains(" = ") && seen.insert("=".to_string()) {
entries.push(SymbolEntry {
symbol: "=".to_string(),
kind: SymbolKind::Identity,
description: "Identity: \"is identical to\"".to_string(),
});
}
extract_predicates(logic, &mut entries, &mut seen);
extract_variables(logic, &mut entries, &mut seen);
extract_constants(logic, &mut entries, &mut seen);
entries
}
fn extract_predicates(logic: &str, entries: &mut Vec<SymbolEntry>, seen: &mut HashSet<String>) {
let chars: Vec<char> = logic.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i].is_ascii_uppercase() {
let start = i;
while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
i += 1;
}
if i < chars.len() && chars[i] == '(' {
let predicate: String = chars[start..i].iter().collect();
if seen.insert(format!("pred_{}", predicate)) {
entries.push(SymbolEntry {
symbol: predicate.clone(),
kind: SymbolKind::Predicate,
description: format!("Predicate: {}", predicate),
});
}
}
}
i += 1;
}
}
fn extract_variables(logic: &str, entries: &mut Vec<SymbolEntry>, seen: &mut HashSet<String>) {
for var in ['x', 'y', 'z', 'w', 'e'] {
let var_str = var.to_string();
if logic.contains(&format!("({})", var))
|| logic.contains(&format!("({},", var))
|| logic.contains(&format!(", {})", var))
|| logic.contains(&format!("{}.", var))
|| logic.contains(&format!(" {}", var))
{
if seen.insert(format!("var_{}", var)) {
entries.push(SymbolEntry {
symbol: var_str,
kind: SymbolKind::Variable,
description: "Bound variable".to_string(),
});
}
}
}
}
fn extract_constants(logic: &str, entries: &mut Vec<SymbolEntry>, seen: &mut HashSet<String>) {
let chars: Vec<char> = logic.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i].is_ascii_uppercase() {
let start = i;
while i < chars.len() && (chars[i].is_ascii_alphanumeric()) {
i += 1;
}
if i >= chars.len() || chars[i] != '(' {
let constant: String = chars[start..i].iter().collect();
if constant.len() <= 3
&& !["MOST", "FEW", "ALL", "THE"].contains(&constant.as_str())
&& seen.insert(format!("const_{}", constant))
{
entries.push(SymbolEntry {
symbol: constant.clone(),
kind: SymbolKind::Constant,
description: format!("Constant: {}", constant),
});
}
}
}
i += 1;
}
}
pub fn group_symbols_by_kind(entries: &[SymbolEntry]) -> Vec<(SymbolKind, Vec<&SymbolEntry>)> {
let kinds = [
SymbolKind::Quantifier,
SymbolKind::Connective,
SymbolKind::Modal,
SymbolKind::Identity,
SymbolKind::Predicate,
SymbolKind::Variable,
SymbolKind::Constant,
];
kinds
.iter()
.filter_map(|&kind| {
let matching: Vec<_> = entries.iter().filter(|e| e.kind == kind).collect();
if matching.is_empty() {
None
} else {
Some((kind, matching))
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_quantifier_symbols() {
let logic = "∀x(Dog(x) → Mortal(x))";
let symbols = extract_symbols(logic);
let quantifiers: Vec<_> = symbols.iter().filter(|s| s.kind == SymbolKind::Quantifier).collect();
assert!(quantifiers.iter().any(|s| s.symbol == "∀"), "Should find universal quantifier");
}
#[test]
fn test_extract_existential() {
let logic = "∃x(Cat(x) ∧ Black(x))";
let symbols = extract_symbols(logic);
assert!(symbols.iter().any(|s| s.symbol == "∃"), "Should find existential quantifier");
}
#[test]
fn test_extract_connective_symbols() {
let logic = "∀x(Dog(x) → (Loyal(x) ∧ Friendly(x)))";
let symbols = extract_symbols(logic);
let connectives: Vec<_> = symbols.iter().filter(|s| s.kind == SymbolKind::Connective).collect();
assert!(connectives.iter().any(|s| s.symbol == "∧"), "Should find conjunction");
assert!(connectives.iter().any(|s| s.symbol == "→"), "Should find implication");
}
#[test]
fn test_extract_predicate_names() {
let logic = "∀x(Dog(x) → Mammal(x))";
let symbols = extract_symbols(logic);
let predicates: Vec<_> = symbols.iter().filter(|s| s.kind == SymbolKind::Predicate).collect();
assert!(predicates.iter().any(|s| s.symbol == "Dog"), "Should find Dog predicate");
assert!(predicates.iter().any(|s| s.symbol == "Mammal"), "Should find Mammal predicate");
}
#[test]
fn test_extract_variable_names() {
let logic = "∀x∃y(Loves(x, y))";
let symbols = extract_symbols(logic);
let variables: Vec<_> = symbols.iter().filter(|s| s.kind == SymbolKind::Variable).collect();
assert!(variables.iter().any(|s| s.symbol == "x"), "Should find variable x");
assert!(variables.iter().any(|s| s.symbol == "y"), "Should find variable y");
}
#[test]
fn test_no_duplicate_symbols() {
let logic = "∀x(Dog(x) → Dog(x))";
let symbols = extract_symbols(logic);
let dog_count = symbols.iter().filter(|s| s.symbol == "Dog").count();
assert_eq!(dog_count, 1, "Should not have duplicate predicates");
}
#[test]
fn test_symbol_has_description() {
let logic = "∀x(P(x))";
let symbols = extract_symbols(logic);
for symbol in &symbols {
assert!(!symbol.description.is_empty(), "Every symbol should have a description");
}
}
#[test]
fn test_modal_symbols() {
let logic = "□(P(x)) ∧ ◇(Q(y))";
let symbols = extract_symbols(logic);
assert!(symbols.iter().any(|s| s.symbol == "□"), "Should find necessity operator");
assert!(symbols.iter().any(|s| s.symbol == "◇"), "Should find possibility operator");
}
#[test]
fn test_group_symbols_by_kind() {
let logic = "∀x(Dog(x) → ∃y(Loves(x, y)))";
let symbols = extract_symbols(logic);
let grouped = group_symbols_by_kind(&symbols);
assert!(!grouped.is_empty(), "Should have grouped symbols");
assert!(grouped.iter().any(|(k, _)| *k == SymbolKind::Quantifier), "Should have quantifier group");
}
}