code-moniker-check 0.6.0

Rules engine for code-moniker: DSL, rule config/profiles, evaluation over the symbol graph, and suppression.
Documentation
use crate::check::expr::{Atom, Domain, LhsExpr, Node, PairProjection, PairSide, QuantKind, Rhs};

use super::collection::{
	PairCollectionScope, eval_pair_collection_size, eval_pair_collection_subset,
};
use super::local::{DomainItem, domain_items, eval_mode, project_lhs_value};
use super::value::{Value, apply_op, apply_op_values};
use super::{
	AtomOutcome, EvalCtx, Failure, NodeOutcome, eval_number_expr_def, resolve_def_lhs, walk_node,
};

pub(super) fn eval_pair_count(
	domain: &Domain,
	filter: Option<&Node>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> u32 {
	let items = domain_items(domain, def_idx, ctx);
	let mut count = 0;
	for (a, b) in pair_iter(&items) {
		let passes = filter.is_none_or(|node| {
			matches!(
				eval_pair_node(node, a, b, def_idx, self_idx, ctx),
				NodeOutcome::Pass
			)
		});
		if passes {
			count += 1;
		}
	}
	count
}

pub(super) fn eval_pair_quantifier(
	kind: QuantKind,
	domain: &Domain,
	filter: &Node,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> NodeOutcome {
	let items = domain_items(domain, def_idx, ctx);
	let mut total = 0u32;
	let mut passes = 0u32;
	for (a, b) in pair_iter(&items) {
		total += 1;
		if matches!(
			eval_pair_node(filter, a, b, def_idx, self_idx, ctx),
			NodeOutcome::Pass
		) {
			passes += 1;
		}
	}
	pair_quantifier_outcome(kind, total, passes)
}

fn pair_iter<'a>(
	items: &'a [DomainItem<'a>],
) -> impl Iterator<Item = (DomainItem<'a>, DomainItem<'a>)> + 'a {
	items
		.iter()
		.enumerate()
		.flat_map(|(idx, a)| items[idx + 1..].iter().map(move |b| (*a, *b)))
}

fn eval_pair_node(
	node: &Node,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> NodeOutcome {
	walk_node(
		node,
		&|atom| eval_pair_atom(atom, a, b, def_idx, self_idx, ctx),
		&|_, _, _| NodeOutcome::NotApplicable,
		&|_| NodeOutcome::NotApplicable,
		&|_| NodeOutcome::NotApplicable,
	)
}

fn eval_pair_atom(
	atom: &Atom,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> AtomOutcome {
	if let (LhsExpr::Collection(left), crate::check::expr::Op::Subset, Rhs::Collection(right)) =
		(&atom.lhs, atom.op, &atom.rhs)
	{
		let scope = PairCollectionScope { a, b, def_idx };
		return match eval_pair_collection_subset(left, right, scope, ctx) {
			Some(true) => AtomOutcome::Pass,
			Some(false) => AtomOutcome::Fail {
				actual: "not subset".to_string(),
				expected: "subset".to_string(),
			},
			None => AtomOutcome::NotApplicable,
		};
	}
	let Some(value) = pair_lhs_value(&atom.lhs, a, b, def_idx, self_idx, ctx) else {
		return AtomOutcome::NotApplicable;
	};
	if let Some(rhs_val) = pair_rhs_value(&atom.rhs, a, b, def_idx, self_idx, ctx) {
		return apply_op_values(&value, atom.op, &rhs_val);
	}
	apply_op(&value, atom)
}

fn pair_lhs_value(
	lhs: &LhsExpr,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> Option<Value> {
	match lhs {
		LhsExpr::PairProjection(projection) => pair_projection_value(*projection, a, b, ctx),
		_ => owner_lhs_value(lhs, a, b, def_idx, self_idx, ctx),
	}
}

fn pair_rhs_value(
	rhs: &Rhs,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> Option<Value> {
	match rhs {
		Rhs::PairProjection(projection) => pair_projection_value(*projection, a, b, ctx),
		Rhs::Projection(lhs) => resolve_def_lhs(*lhs, ctx.graph.def_at(def_idx), ctx),
		Rhs::Number(expr) => {
			eval_pair_number_expr(expr, a, b, def_idx, self_idx, ctx).map(Value::Number)
		}
		_ => None,
	}
}

fn pair_projection_value(
	projection: PairProjection,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	ctx: &EvalCtx<'_, '_>,
) -> Option<Value> {
	let item = match projection.side {
		PairSide::A => a,
		PairSide::B => b,
	};
	project_lhs_value(item, projection.lhs, ctx)
}

fn owner_lhs_value(
	lhs: &LhsExpr,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> Option<Value> {
	let owner = ctx.graph.def_at(def_idx);
	match lhs {
		LhsExpr::Attr(lhs) => resolve_def_lhs(*lhs, owner, ctx),
		LhsExpr::Number(expr) => {
			eval_pair_number_expr(expr, a, b, def_idx, self_idx, ctx).map(Value::Number)
		}
		LhsExpr::Mode(collection) => eval_mode(collection, def_idx, self_idx, ctx),
		_ => None,
	}
}

fn eval_pair_number_expr(
	expr: &crate::check::expr::NumberExpr,
	a: DomainItem<'_>,
	b: DomainItem<'_>,
	def_idx: usize,
	self_idx: usize,
	ctx: &EvalCtx<'_, '_>,
) -> Option<f64> {
	match expr {
		crate::check::expr::NumberExpr::Size(collection) => {
			let scope = PairCollectionScope { a, b, def_idx };
			eval_pair_collection_size(collection, scope, ctx).map(|size| size as f64)
		}
		_ => eval_number_expr_def(expr, ctx.graph.def_at(def_idx), def_idx, self_idx, ctx),
	}
}

fn pair_quantifier_outcome(kind: QuantKind, total: u32, passes: u32) -> NodeOutcome {
	let ok = match kind {
		QuantKind::Any => passes > 0,
		QuantKind::All => total == 0 || passes == total,
		QuantKind::None => passes == 0,
	};
	if ok {
		return NodeOutcome::Pass;
	}
	let label = match kind {
		QuantKind::Any => "any",
		QuantKind::All => "all",
		QuantKind::None => "none",
	};
	NodeOutcome::Fail(Failure {
		atom_raw: format!("{label}(pairs(...))"),
		lhs_label: label.to_string(),
		actual: format!("{passes}/{total}"),
		expected: match kind {
			QuantKind::Any => ">= 1 match".to_string(),
			QuantKind::All => "all match".to_string(),
			QuantKind::None => "zero matches".to_string(),
		},
		def_idx: None,
		details: None,
	})
}