use std::fmt;
use crate::number::Number;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Clause {
Fact(Atom),
Rule {
head: Atom,
body: Vec<Goal>,
},
ConstraintFact(ConstraintExpr),
ConstraintRule {
head: Atom,
body: Vec<Goal>,
},
Implication {
constraint: ConstraintExpr,
head: Atom,
},
Query(Vec<Goal>),
Directive {
name: String,
indicators: Vec<Indicator>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Indicator {
pub name: String,
pub arity: Number,
}
impl fmt::Display for Indicator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", display_name(&self.name), self.arity)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Goal {
Atom(Atom),
Constraint(ConstraintExpr),
Or(Vec<Goal>, Vec<Goal>),
IfThenElse {
cond: Vec<Goal>,
then: Vec<Goal>,
els: Option<Vec<Goal>>,
},
Not(Vec<Goal>),
}
pub fn body_has_constraint(body: &[Goal]) -> bool {
body.iter().any(|g| match g {
Goal::Atom(_) => false,
Goal::Constraint(_) => true,
Goal::Or(a, b) => body_has_constraint(a) || body_has_constraint(b),
Goal::IfThenElse { cond, then, els } => {
body_has_constraint(cond)
|| body_has_constraint(then)
|| els.as_deref().is_some_and(body_has_constraint)
}
Goal::Not(g) => body_has_constraint(g),
})
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Atom {
pub name: String,
pub args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Expr {
Atom(Box<Atom>),
Var(String),
Number(Number),
Ident(String),
Neg(Box<Expr>),
Binary {
op: ArithOp,
left: Box<Expr>,
right: Box<Expr>,
},
List {
items: Vec<Expr>,
tail: Option<Box<Expr>>,
},
Len(Box<Expr>),
Concat(Box<Expr>, Box<Expr>),
Comprehension {
template: Box<Expr>,
body: Vec<Goal>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArithOp {
Add,
Sub,
Mul,
Div,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Constraint {
pub left: Expr,
pub op: RelOp,
pub right: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConstraintExpr {
pub terms: Vec<Constraint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RelOp {
Eq,
Neq,
Lt,
Gt,
Le,
Ge,
}
impl fmt::Display for RelOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
RelOp::Eq => "=",
RelOp::Neq => "!=",
RelOp::Lt => "<",
RelOp::Gt => ">",
RelOp::Le => "<=",
RelOp::Ge => ">=",
})
}
}
impl fmt::Display for ArithOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
ArithOp::Add => "+",
ArithOp::Sub => "-",
ArithOp::Mul => "*",
ArithOp::Div => "/",
})
}
}
impl ArithOp {
fn precedence(self) -> u8 {
match self {
ArithOp::Add | ArithOp::Sub => 1,
ArithOp::Mul | ArithOp::Div => 2,
}
}
}
impl Expr {
fn precedence(&self) -> u8 {
match self {
Expr::List { tail: Some(_), .. } | Expr::Concat(..) => 0,
Expr::Binary { op, .. } => op.precedence(),
Expr::Comprehension { .. } => 4,
Expr::Neg(_) => 3,
_ => 4,
}
}
fn take_children(&mut self, out: &mut Vec<Expr>) {
match self {
Expr::Var(_) | Expr::Number(_) | Expr::Ident(_) => {}
Expr::Atom(a) => out.append(&mut a.args),
Expr::Neg(x) | Expr::Len(x) => {
out.push(std::mem::replace(x, Expr::Ident(String::new())))
}
Expr::Binary { left, right, .. } | Expr::Concat(left, right) => {
out.push(std::mem::replace(left, Expr::Ident(String::new())));
out.push(std::mem::replace(right, Expr::Ident(String::new())));
}
Expr::List { items, tail } => {
out.append(items);
if let Some(t) = tail.take() {
out.push(*t);
}
}
Expr::Comprehension { template, .. } => {
out.push(std::mem::replace(template, Expr::Ident(String::new())))
}
}
}
}
impl Drop for Expr {
fn drop(&mut self) {
let mut work: Vec<Expr> = Vec::new();
self.take_children(&mut work);
while let Some(mut e) = work.pop() {
e.take_children(&mut work);
}
}
}
enum Piece<'a> {
Expr(&'a Expr, u8),
Atom(&'a Atom),
Text(&'static str),
Owned(String),
}
fn render(f: &mut fmt::Formatter<'_>, root: Piece<'_>) -> fmt::Result {
let mut work = vec![root];
while let Some(piece) = work.pop() {
match piece {
Piece::Text(t) => f.write_str(t)?,
Piece::Owned(t) => f.write_str(&t)?,
Piece::Atom(a) => {
write!(f, "{}(", display_name(&a.name))?;
work.push(Piece::Text(")"));
for (i, arg) in a.args.iter().enumerate().rev() {
work.push(Piece::Expr(arg, 0));
if i > 0 {
work.push(Piece::Text(", "));
}
}
}
Piece::Expr(e, min) => {
let prec = e.precedence();
if prec < min {
f.write_str("(")?;
work.push(Piece::Text(")"));
}
match e {
Expr::Atom(a) => work.push(Piece::Atom(a)),
Expr::Var(v) => f.write_str(v)?,
Expr::Number(n) => write!(f, "{n}")?,
Expr::Ident(i) => f.write_str(&display_name(i))?,
Expr::Neg(x) => {
f.write_str("-")?;
work.push(Piece::Expr(x, 3));
}
Expr::Binary { op, left, right } => {
work.push(Piece::Expr(right, prec + 1));
work.push(Piece::Text(match op {
ArithOp::Add => " + ",
ArithOp::Sub => " - ",
ArithOp::Mul => " * ",
ArithOp::Div => " / ",
}));
work.push(Piece::Expr(left, prec));
}
Expr::Len(x) => {
f.write_str("|")?;
work.push(Piece::Text("|"));
work.push(Piece::Expr(x, 0));
}
Expr::Concat(l, r) => {
work.push(Piece::Expr(r, 0));
work.push(Piece::Text(" . "));
work.push(Piece::Expr(l, 1));
}
Expr::Comprehension { template, body } => {
f.write_str("<")?;
work.push(Piece::Text(">"));
work.push(Piece::Owned(BodyDisplay(body).to_string()));
work.push(Piece::Text(" | "));
work.push(Piece::Expr(template, 0));
}
Expr::List { items, tail } => {
f.write_str("<")?;
if let Some(t) = tail {
work.push(Piece::Expr(t, 0));
work.push(Piece::Text(" . "));
}
work.push(Piece::Text(">"));
for (i, item) in items.iter().enumerate().rev() {
work.push(Piece::Expr(item, 0));
if i > 0 {
work.push(Piece::Text(", "));
}
}
}
}
}
}
}
Ok(())
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
render(f, Piece::Expr(self, 0))
}
}
pub fn name_is_plain(name: &str) -> bool {
let mut chars = name.chars();
chars.next().is_some_and(|c| c.is_ascii_lowercase())
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn display_name(name: &str) -> std::borrow::Cow<'_, str> {
if name_is_plain(name) {
return std::borrow::Cow::Borrowed(name);
}
let mut out = String::with_capacity(name.len() + 2);
out.push('\'');
for c in name.chars() {
if c == '\'' || c == '\\' {
out.push('\\');
}
out.push(c);
}
out.push('\'');
std::borrow::Cow::Owned(out)
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
render(f, Piece::Atom(self))
}
}
impl fmt::Display for Constraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.left, self.op, self.right)
}
}
impl fmt::Display for ConstraintExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("{ ")?;
for (i, c) in self.terms.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{c}")?;
}
f.write_str(" }")
}
}
impl fmt::Display for Goal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Goal::Atom(a) => write!(f, "{a}"),
Goal::Constraint(c) => write!(f, "{c}"),
Goal::Or(..) | Goal::IfThenElse { .. } => fmt_body(f, std::slice::from_ref(self)),
Goal::Not(body) => {
f.write_str("\\+ ")?;
match body.as_slice() {
[g @ (Goal::Atom(_) | Goal::Constraint(_) | Goal::Not(_))] => write!(f, "{g}"),
_ => {
f.write_str("(")?;
fmt_body(f, body)?;
f.write_str(")")
}
}
}
}
}
}
struct BodyDisplay<'a>(&'a [Goal]);
impl fmt::Display for BodyDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_body(f, self.0)
}
}
fn fmt_body(f: &mut fmt::Formatter<'_>, body: &[Goal]) -> fmt::Result {
match body {
[Goal::Or(a, b)] => {
fmt_conj(f, a)?;
f.write_str(" ; ")?;
fmt_body(f, b)
}
[Goal::IfThenElse { cond, then, els }] => {
fmt_conj(f, cond)?;
f.write_str(" -> ")?;
fmt_conj(f, then)?;
if let Some(e) = els {
f.write_str(" ; ")?;
fmt_body(f, e)?;
}
Ok(())
}
_ => fmt_conj(f, body),
}
}
fn fmt_conj(f: &mut fmt::Formatter<'_>, goals: &[Goal]) -> fmt::Result {
for (i, g) in goals.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
match g {
Goal::Or(..) | Goal::IfThenElse { .. } => {
f.write_str("(")?;
write!(f, "{g}")?;
f.write_str(")")?;
}
g => write!(f, "{g}")?,
}
}
Ok(())
}
impl fmt::Display for Clause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Clause::Fact(a) => write!(f, "{a}."),
Clause::Rule { head, body } | Clause::ConstraintRule { head, body } => {
write!(f, "{head} :- ")?;
fmt_body(f, body)?;
f.write_str(".")
}
Clause::ConstraintFact(c) => write!(f, "{c}."),
Clause::Implication { constraint, head } => write!(f, "{constraint} => {head}."),
Clause::Query(body) => {
f.write_str("?- ")?;
fmt_body(f, body)?;
f.write_str(".")
}
Clause::Directive { name, indicators } => {
write!(f, ":- {name} ")?;
for (i, ind) in indicators.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{ind}")?;
}
f.write_str(".")
}
}
}
}