#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum LogicalTerm {
Variable(String),
Constant(String),
Description(String),
Unspecified,
Number(f64),
}
impl LogicalTerm {
pub fn display(&self) -> String {
match self {
LogicalTerm::Constant(s) => s.clone(),
LogicalTerm::Number(n) => format!("{n}"),
LogicalTerm::Variable(s) => s.clone(),
LogicalTerm::Description(s) => format!("the_{s}"),
LogicalTerm::Unspecified => "(unspecified)".to_string(),
}
}
pub fn trace_display(&self) -> String {
match self {
LogicalTerm::Constant(s) => s.clone(),
LogicalTerm::Number(n) => {
if *n == (*n as i64) as f64 {
format!("{}", *n as i64)
} else {
format!("{n}")
}
}
LogicalTerm::Variable(s) => format!("?{s}"),
LogicalTerm::Description(s) => format!("the {s}"),
LogicalTerm::Unspecified => "something".to_string(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LogicNode {
Predicate((String, Vec<LogicalTerm>)),
ComputeNode((String, Vec<LogicalTerm>)),
AndNode((u32, u32)),
OrNode((u32, u32)),
NotNode(u32),
ExistsNode((String, u32)),
ForAllNode((String, u32)),
PastNode(u32),
PresentNode(u32),
FutureNode(u32),
ObligatoryNode(u32),
PermittedNode(u32),
CountNode((String, u32, u32)),
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LogicBuffer {
pub nodes: Vec<LogicNode>,
pub roots: Vec<u32>,
}
impl LogicBuffer {
pub fn split_roots(&self) -> Vec<LogicBuffer> {
if self.roots.len() <= 1 {
return vec![self.clone()];
}
self.roots
.iter()
.map(|&r| LogicBuffer {
nodes: self.nodes.clone(),
roots: vec![r],
})
.collect()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct WitnessBinding {
pub variable: String,
pub term: LogicalTerm,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UnknownReason {
CycleCut,
IncompleteKnowledge,
NafDependent,
BackendUnavailable,
NonFinite,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResourceKind {
Depth,
Fuel,
Memory,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum QueryResult {
True,
False,
Unknown(UnknownReason),
ResourceExceeded(ResourceKind),
}
impl QueryResult {
pub fn is_true(&self) -> bool {
matches!(self, Self::True)
}
pub fn is_false(&self) -> bool {
matches!(self, Self::False)
}
pub fn is_definitive(&self) -> bool {
matches!(self, Self::True | Self::False)
}
pub fn status_label(&self) -> &'static str {
match self {
Self::True => "TRUE",
Self::False => "FALSE",
Self::Unknown(_) => "UNKNOWN",
Self::ResourceExceeded(_) => "RESOURCE_EXCEEDED",
}
}
pub fn detail_label(&self) -> Option<&'static str> {
match self {
Self::Unknown(UnknownReason::CycleCut) => Some("cycle-cut"),
Self::Unknown(UnknownReason::IncompleteKnowledge) => Some("incomplete-knowledge"),
Self::Unknown(UnknownReason::NafDependent) => Some("naf-dependent"),
Self::Unknown(UnknownReason::BackendUnavailable) => Some("backend-unavailable"),
Self::Unknown(UnknownReason::NonFinite) => Some("non-finite"),
Self::ResourceExceeded(ResourceKind::Depth) => Some("depth"),
Self::ResourceExceeded(ResourceKind::Fuel) => Some("fuel"),
Self::ResourceExceeded(ResourceKind::Memory) => Some("memory"),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum ProofRule {
#[cfg_attr(feature = "serde", serde(rename = "conjunction"))]
Conjunction,
#[cfg_attr(feature = "serde", serde(rename = "disjunction_check"))]
DisjunctionCheck { detail: String },
#[cfg_attr(feature = "serde", serde(rename = "disjunction_intro"))]
DisjunctionIntro { side: String },
#[cfg_attr(feature = "serde", serde(rename = "negation"))]
Negation,
#[cfg_attr(feature = "serde", serde(rename = "modal_passthrough"))]
ModalPassthrough { kind: String },
#[cfg_attr(feature = "serde", serde(rename = "exists_witness"))]
ExistsWitness { var: String, term: LogicalTerm },
#[cfg_attr(feature = "serde", serde(rename = "exists_failed"))]
ExistsFailed,
#[cfg_attr(feature = "serde", serde(rename = "forall_vacuous"))]
ForallVacuous,
#[cfg_attr(feature = "serde", serde(rename = "forall_verified"))]
ForallVerified { entities: Vec<LogicalTerm> },
#[cfg_attr(feature = "serde", serde(rename = "forall_counterexample"))]
ForallCounterexample { entity: LogicalTerm },
#[cfg_attr(feature = "serde", serde(rename = "count_result"))]
CountResult { expected: u32, actual: u32 },
#[cfg_attr(feature = "serde", serde(rename = "predicate_check"))]
PredicateCheck { method: String, detail: String },
#[cfg_attr(feature = "serde", serde(rename = "compute_check"))]
ComputeCheck { method: String, detail: String },
#[cfg_attr(feature = "serde", serde(rename = "asserted"))]
Asserted { fact: String },
#[cfg_attr(feature = "serde", serde(rename = "derived"))]
Derived { label: String, fact: String },
#[cfg_attr(feature = "serde", serde(rename = "proof_ref"))]
ProofRef { fact: String },
#[cfg_attr(feature = "serde", serde(rename = "equality_substitution"))]
EqualitySubstitution {
original: String,
equality_facts: String,
substituted: String,
},
#[cfg_attr(feature = "serde", serde(rename = "rule_attempt_failed"))]
RuleAttemptFailed {
rule_label: String,
failed_condition: String,
},
#[cfg_attr(feature = "serde", serde(rename = "predicate_not_found"))]
PredicateNotFound { predicate: String },
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProofStep {
pub rule: ProofRule,
pub holds: bool,
pub children: Vec<u32>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProofTrace {
pub steps: Vec<ProofStep>,
pub root: u32,
#[cfg_attr(feature = "serde", serde(default))]
pub naf_dependent: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub cwa_false: bool,
}
impl ProofTrace {
pub fn has_naf_dependency(&self) -> bool {
self.steps
.iter()
.any(|s| matches!(s.rule, ProofRule::Negation) && s.holds)
}
}
#[derive(Clone, Debug)]
pub enum AggregateOp {
Sum,
Min,
Max,
Avg,
}
pub type FactId = u64;
#[derive(Clone, Debug)]
pub struct FactSummary {
pub id: FactId,
pub label: String,
pub root_count: u32,
}
#[doc(hidden)]
pub fn __exhaustiveness_guard(node: &LogicNode, term: &LogicalTerm, rule: &ProofRule) {
match node {
LogicNode::Predicate(_) => {}
LogicNode::ComputeNode(_) => {}
LogicNode::AndNode(_) => {}
LogicNode::OrNode(_) => {}
LogicNode::NotNode(_) => {}
LogicNode::ExistsNode(_) => {}
LogicNode::ForAllNode(_) => {}
LogicNode::PastNode(_) => {}
LogicNode::PresentNode(_) => {}
LogicNode::FutureNode(_) => {}
LogicNode::ObligatoryNode(_) => {}
LogicNode::PermittedNode(_) => {}
LogicNode::CountNode(_) => {}
}
match term {
LogicalTerm::Variable(_) => {}
LogicalTerm::Constant(_) => {}
LogicalTerm::Description(_) => {}
LogicalTerm::Unspecified => {}
LogicalTerm::Number(_) => {}
}
match rule {
ProofRule::Conjunction => {}
ProofRule::DisjunctionCheck { .. } => {}
ProofRule::DisjunctionIntro { .. } => {}
ProofRule::Negation => {}
ProofRule::ModalPassthrough { .. } => {}
ProofRule::ExistsWitness { .. } => {}
ProofRule::ExistsFailed => {}
ProofRule::ForallVacuous => {}
ProofRule::ForallVerified { .. } => {}
ProofRule::ForallCounterexample { .. } => {}
ProofRule::CountResult { .. } => {}
ProofRule::PredicateCheck { .. } => {}
ProofRule::ComputeCheck { .. } => {}
ProofRule::Asserted { .. } => {}
ProofRule::Derived { .. } => {}
ProofRule::ProofRef { .. } => {}
ProofRule::EqualitySubstitution { .. } => {}
ProofRule::RuleAttemptFailed { .. } => {}
ProofRule::PredicateNotFound { .. } => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exhaustiveness_guard_is_callable() {
__exhaustiveness_guard(
&LogicNode::NotNode(0),
&LogicalTerm::Unspecified,
&ProofRule::Conjunction,
);
}
fn pred(name: &str) -> LogicNode {
LogicNode::Predicate((name.to_string(), vec![]))
}
#[test]
fn split_roots_multi_returns_one_buffer_per_root() {
let buf = LogicBuffer {
nodes: vec![pred("gerku"), pred("mlatu")],
roots: vec![0, 1],
};
let parts = buf.split_roots();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].roots, vec![0]);
assert_eq!(parts[1].roots, vec![1]);
assert_eq!(parts[0].nodes, buf.nodes);
assert_eq!(parts[1].nodes, buf.nodes);
}
#[test]
fn split_roots_single_is_identity() {
let buf = LogicBuffer {
nodes: vec![pred("gerku")],
roots: vec![0],
};
let parts = buf.split_roots();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], buf);
}
#[test]
fn split_roots_empty_returns_self() {
let buf = LogicBuffer {
nodes: vec![],
roots: vec![],
};
let parts = buf.split_roots();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], buf);
}
#[test]
fn split_roots_connective_root_is_not_split() {
let buf = LogicBuffer {
nodes: vec![pred("gerku"), pred("mlatu"), LogicNode::AndNode((0, 1))],
roots: vec![2],
};
let parts = buf.split_roots();
assert_eq!(
parts.len(),
1,
"a connective's single And-root must stay one fact"
);
assert_eq!(parts[0], buf);
}
}