use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum VarNameError {
#[error("变量名不能为空")]
Empty,
#[error("变量名 {0:?} 不是合法标识符")]
Invalid(String),
#[error("变量名 {0:?} 是保留常量 token")]
Reserved(String),
}
pub fn is_identifier(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
(first.is_alphabetic() || first == '_') && chars.all(|c| c.is_alphanumeric() || c == '_')
}
pub fn is_reserved_token(name: &str) -> bool {
matches!(
name,
"0" | "False"
| "false"
| "FALSE"
| "1"
| "True"
| "true"
| "TRUE"
| "X"
| "x"
| "?"
| "Unknown"
| "unknown"
)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Expr {
Const(crate::value::Ternary),
Var(String),
Not(Box<Expr>),
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
Xor(Box<Expr>, Box<Expr>),
Implies(Box<Expr>, Box<Expr>),
Equiv(Box<Expr>, Box<Expr>),
}
impl Expr {
pub fn and(a: Expr, b: Expr) -> Self {
Self::And(Box::new(a), Box::new(b))
}
pub fn or(a: Expr, b: Expr) -> Self {
Self::Or(Box::new(a), Box::new(b))
}
pub fn logical_not(a: Expr) -> Self {
Self::Not(Box::new(a))
}
pub fn xor(a: Expr, b: Expr) -> Self {
Self::Xor(Box::new(a), Box::new(b))
}
pub fn implies(a: Expr, b: Expr) -> Self {
Self::Implies(Box::new(a), Box::new(b))
}
pub fn equiv(a: Expr, b: Expr) -> Self {
Self::Equiv(Box::new(a), Box::new(b))
}
pub fn try_var(name: impl Into<String>) -> Result<Self, VarNameError> {
let name = name.into();
if name.is_empty() {
return Err(VarNameError::Empty);
}
if is_reserved_token(&name) {
return Err(VarNameError::Reserved(name));
}
if !is_identifier(&name) {
return Err(VarNameError::Invalid(name));
}
Ok(Self::Var(name))
}
pub const fn is_unknown_const(&self) -> bool {
matches!(self, Self::Const(crate::value::Ternary::Unknown))
}
pub fn variables(&self) -> Vec<String> {
let mut out = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut stack = vec![self];
while let Some(node) = stack.pop() {
match node {
Self::Const(_) => {}
Self::Var(name) => {
if seen.insert(name.clone()) {
out.push(name.clone());
}
}
Self::Not(a) => stack.push(a),
Self::And(a, b)
| Self::Or(a, b)
| Self::Xor(a, b)
| Self::Implies(a, b)
| Self::Equiv(a, b) => {
stack.push(b);
stack.push(a);
}
}
}
out
}
pub fn node_count(&self) -> usize {
let mut count = 0usize;
let mut stack = vec![self];
while let Some(node) = stack.pop() {
count += 1;
match node {
Self::Const(_) | Self::Var(_) => {}
Self::Not(a) => stack.push(a),
Self::And(a, b)
| Self::Or(a, b)
| Self::Xor(a, b)
| Self::Implies(a, b)
| Self::Equiv(a, b) => {
stack.push(a);
stack.push(b);
}
}
}
count
}
pub fn depth(&self) -> usize {
let mut max = 0usize;
let mut stack = vec![(self, 1usize)];
while let Some((node, depth)) = stack.pop() {
max = max.max(depth);
match node {
Self::Const(_) | Self::Var(_) => {}
Self::Not(a) => stack.push((a, depth + 1)),
Self::And(a, b)
| Self::Or(a, b)
| Self::Xor(a, b)
| Self::Implies(a, b)
| Self::Equiv(a, b) => {
stack.push((a, depth + 1));
stack.push((b, depth + 1));
}
}
}
max
}
pub fn to_infix(&self) -> String {
match self {
Self::Const(v) => v.to_string(),
Self::Var(name) => name.clone(),
Self::Not(a) => format!("¬({})", a.to_infix()),
Self::And(a, b) => format!("({} ∧ {})", a.to_infix(), b.to_infix()),
Self::Or(a, b) => format!("({} ∨ {})", a.to_infix(), b.to_infix()),
Self::Xor(a, b) => format!("({} ⊕ {})", a.to_infix(), b.to_infix()),
Self::Implies(a, b) => format!("({} ⇒ {})", a.to_infix(), b.to_infix()),
Self::Equiv(a, b) => format!("({} ≡ {})", a.to_infix(), b.to_infix()),
}
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_infix())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Ternary;
#[test]
fn collects_variables_in_order() {
let e = Expr::and(
Expr::Var("a".into()),
Expr::or(Expr::Var("b".into()), Expr::Var("a".into())),
);
assert_eq!(e.variables(), vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn node_count_and_depth() {
let e = Expr::and(Expr::Var("a".into()), Expr::Var("b".into()));
assert_eq!(e.node_count(), 3);
assert_eq!(e.depth(), 2);
}
#[test]
fn infix_rendering() {
let e = Expr::logical_not(Expr::Var("a".into()));
assert_eq!(e.to_infix(), "¬(a)");
let e2 = Expr::equiv(Expr::Const(Ternary::True), Expr::Var("x".into()));
assert_eq!(e2.to_infix(), "(1 ≡ x)");
}
#[test]
fn try_var_rejects_reserved_and_malformed_names() {
assert_eq!(Expr::try_var("a_1").unwrap(), Expr::Var("a_1".into()));
assert!(matches!(
Expr::try_var("X").unwrap_err(),
VarNameError::Reserved(_)
));
assert!(matches!(
Expr::try_var("a b").unwrap_err(),
VarNameError::Invalid(_)
));
assert!(matches!(
Expr::try_var("").unwrap_err(),
VarNameError::Empty
));
}
#[test]
fn display_does_not_round_trip_arbitrary_vars() {
let e = Expr::Var("X".into());
assert_eq!(e.to_string(), "X");
assert!(matches!(
crate::parser::Parser::parse(&e.to_string()).unwrap(),
Expr::Const(Ternary::Unknown)
));
}
#[test]
fn iterative_metrics_match_hand_computed_values() {
let e = Expr::and(
Expr::Var("a".into()),
Expr::implies(Expr::Var("b".into()), Expr::Var("c".into())),
);
assert_eq!(e.node_count(), 5);
assert_eq!(e.depth(), 3);
assert_eq!(e.variables(), vec!["a", "b", "c"]);
}
}