use nibli_types::logic::{LogicBuffer, LogicNode, LogicalTerm};
pub(super) fn lb(text: &str) -> LogicBuffer {
let ast = crate::parse_checked(text).unwrap_or_else(|e| panic!("parse {text:?}: {e}"));
nibli_semantics::compile_from_ast(ast)
.unwrap_or_else(|e| panic!("nibli-semantics {text:?}: {e}"))
}
pub(super) fn root(b: &LogicBuffer) -> &LogicNode {
&b.nodes[b.roots[0] as usize]
}
pub(super) fn node(b: &LogicBuffer, id: u32) -> &LogicNode {
&b.nodes[id as usize]
}
pub(super) fn pred_args(b: &LogicBuffer, rel: &str) -> Option<Vec<LogicalTerm>> {
b.nodes.iter().find_map(|n| match n {
LogicNode::Predicate((r, args)) if r == rel => Some(args.clone()),
_ => None,
})
}
pub(super) fn has_pred(b: &LogicBuffer, rel: &str) -> bool {
pred_args(b, rel).is_some()
}
pub(super) fn role_is_const(b: &LogicBuffer, rel: &str, c: &str) -> bool {
matches!(
pred_args(b, rel).as_deref(),
Some([_, LogicalTerm::Constant(k)]) if k == c
)
}
pub(super) fn role_filler(b: &LogicBuffer, rel: &str) -> Option<LogicalTerm> {
match pred_args(b, rel).as_deref() {
Some([_, filler]) => Some(filler.clone()),
_ => None,
}
}
fn children(n: &LogicNode) -> Vec<u32> {
match n {
LogicNode::Predicate(_) | LogicNode::ComputeNode(_) => vec![],
LogicNode::AndNode((l, r)) | LogicNode::OrNode((l, r)) => vec![*l, *r],
LogicNode::NotNode(i)
| LogicNode::PastNode(i)
| LogicNode::PresentNode(i)
| LogicNode::FutureNode(i)
| LogicNode::ObligatoryNode(i)
| LogicNode::PermittedNode(i) => vec![*i],
LogicNode::ExistsNode((_, b)) | LogicNode::ForAllNode((_, b)) => vec![*b],
LogicNode::CountNode((_, _, b)) => vec![*b],
}
}
pub(super) fn has_pred_from(b: &LogicBuffer, id: u32, rel: &str) -> bool {
let n = node(b, id);
if let LogicNode::Predicate((r, _)) = n
&& r == rel
{
return true;
}
children(n).into_iter().any(|c| has_pred_from(b, c, rel))
}
fn subtree_has_forall(b: &LogicBuffer, id: u32) -> bool {
let n = node(b, id);
if matches!(n, LogicNode::ForAllNode(_)) {
return true;
}
children(n).into_iter().any(|c| subtree_has_forall(b, c))
}
pub(super) fn free_vars(b: &LogicBuffer) -> Vec<String> {
fn walk(b: &LogicBuffer, id: u32, bound: &mut Vec<String>, out: &mut Vec<String>) {
match node(b, id) {
LogicNode::Predicate((_, args)) | LogicNode::ComputeNode((_, args)) => {
for a in args {
if let LogicalTerm::Variable(v) = a
&& !bound.contains(v)
{
out.push(v.clone());
}
}
}
LogicNode::AndNode((l, r)) | LogicNode::OrNode((l, r)) => {
walk(b, *l, bound, out);
walk(b, *r, bound, out);
}
LogicNode::NotNode(i)
| LogicNode::PastNode(i)
| LogicNode::PresentNode(i)
| LogicNode::FutureNode(i)
| LogicNode::ObligatoryNode(i)
| LogicNode::PermittedNode(i) => walk(b, *i, bound, out),
LogicNode::ExistsNode((v, body)) | LogicNode::ForAllNode((v, body)) => {
bound.push(v.clone());
walk(b, *body, bound, out);
bound.pop();
}
LogicNode::CountNode((v, _, body)) => {
bound.push(v.clone());
walk(b, *body, bound, out);
bound.pop();
}
}
}
let mut out = Vec::new();
walk(b, b.roots[0], &mut Vec::new(), &mut out);
out.sort();
out.dedup();
out
}
pub(super) fn count_exists_binding(b: &LogicBuffer, name: &str) -> usize {
fn walk(b: &LogicBuffer, id: u32, name: &str) -> usize {
let n = node(b, id);
let here = matches!(n, LogicNode::ExistsNode((v, _)) if v == name) as usize;
here + children(n)
.into_iter()
.map(|c| walk(b, c, name))
.sum::<usize>()
}
walk(b, b.roots[0], name)
}
pub(super) fn exists_outscopes_forall(b: &LogicBuffer, name: &str) -> bool {
fn walk(b: &LogicBuffer, id: u32, name: &str) -> bool {
let n = node(b, id);
if let LogicNode::ExistsNode((v, body)) = n
&& v == name
&& subtree_has_forall(b, *body)
{
return true;
}
children(n).into_iter().any(|c| walk(b, c, name))
}
walk(b, b.roots[0], name)
}
#[derive(Debug, PartialEq)]
pub(super) enum Binder {
Exists(String),
ForAll,
Count(u32),
}
pub(super) fn binder_spine(b: &LogicBuffer) -> Vec<Binder> {
let mut out = Vec::new();
let mut cur = b.roots[0];
loop {
match node(b, cur) {
LogicNode::ExistsNode((v, body)) => {
out.push(Binder::Exists(v.clone()));
cur = *body;
}
LogicNode::ForAllNode((_, body)) => {
out.push(Binder::ForAll);
cur = *body;
}
LogicNode::CountNode((_, n, body)) => {
out.push(Binder::Count(*n));
cur = *body;
}
LogicNode::NotNode(i)
| LogicNode::PastNode(i)
| LogicNode::PresentNode(i)
| LogicNode::FutureNode(i)
| LogicNode::ObligatoryNode(i)
| LogicNode::PermittedNode(i) => cur = *i,
_ => break,
}
}
out
}
pub(super) fn forall_or_split(b: &LogicBuffer) -> (u32, u32) {
let body = match root(b) {
LogicNode::ForAllNode((_, body)) => *body,
other => panic!("root is not ForAll: {other:?}"),
};
let (l, r) = match node(b, body) {
LogicNode::OrNode((l, r)) => (*l, *r),
other => panic!("ForAll body is not Or: {other:?}"),
};
let antecedent = match node(b, l) {
LogicNode::NotNode(p) => *p,
_ => l,
};
(antecedent, r)
}
mod abstractions;
mod events;
mod injection;
mod lowering;
mod mutation_audit;
mod quantifiers;
mod regressions;
mod rel_clauses;
mod scoping;
mod terms;
mod wrappers;