pddl 0.2.0

A PDDL 3.1 parser, strongly typed
Documentation
use crate::pretty_print::{sealed, PrettyRenderer};
use crate::types::{Domain, DomainConstraintsDef};
use crate::visitor::{Accept, Visitor};
use pretty::RcDoc;

impl sealed::Sealed for Domain {}
impl sealed::Sealed for DomainConstraintsDef {}

impl Visitor<DomainConstraintsDef, RcDoc<'static>> for PrettyRenderer {
    fn visit(&self, value: &DomainConstraintsDef) -> RcDoc<'static> {
        if value.value().is_empty() {
            return RcDoc::nil();
        }
        value.value().accept(self)
    }
}

impl Visitor<Domain, RcDoc<'static>> for PrettyRenderer {
    fn visit(&self, value: &Domain) -> RcDoc<'static> {
        let mut doc = RcDoc::text("(")
            .append(RcDoc::text("define"))
            .append(RcDoc::hardline())
            .append(RcDoc::text("(domain "))
            .append(value.name().accept(self))
            .append(")");

        if !value.extends().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(RcDoc::text("(:extends"))
                .append(RcDoc::softline())
                .append(RcDoc::intersperse(
                    value.extends().iter().map(|n| n.accept(self)),
                    RcDoc::softline(),
                ))
                .nest(4)
                .group()
                .append(")");
        }

        if !value.requirements().is_empty() {
            doc = doc.append(RcDoc::hardline()).append(self.section(
                "requirements",
                value.requirements().iter().map(|r| r.accept(self)),
            ));
        }

        if !value.types().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(RcDoc::text("(:types"))
                .append(RcDoc::softline())
                .append(value.types().accept(self))
                .nest(4)
                .group()
                .append(")");
        }

        if !value.constants().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(RcDoc::text("(:constants"))
                .append(RcDoc::softline())
                .append(value.constants().accept(self))
                .nest(4)
                .group()
                .append(")");
        }

        if !value.predicates().is_empty() {
            doc = doc.append(RcDoc::hardline()).append(self.section(
                "predicates",
                value.predicates().iter().map(|p| p.accept(self)),
            ));
        }

        if !value.functions().is_empty() {
            doc = doc.append(RcDoc::hardline()).append(self.section(
                "functions",
                value.functions().values().iter().map(|f| f.accept(self)),
            ));
        }

        if !value.constraints().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(self.section("constraints", [value.constraints().accept(self)]));
        }

        if !value.timeless().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(value.timeless().accept(self));
        }

        if !value.structure().is_empty() {
            doc = doc
                .append(RcDoc::hardline())
                .append(value.structure().accept(self));
        }

        doc.append(RcDoc::hardline()).append(")").nest(2).group()
    }
}

#[cfg(test)]
mod tests {
    use crate::parsers::Parser;
    use crate::pretty_print::Pretty;
    use crate::types::{ConstraintGoalDefinition, DomainConstraintsDef};

    #[test]
    fn domain_basic() {
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:types block)
  (:predicates (on ?x ?y - block))
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(define"));
        assert!(output.contains("(domain test)"));
    }

    #[test]
    fn domain_with_extends() {
        let input = r#"(define (domain child)
  (:extends parent)
  (:requirements :strips)
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:extends parent)"));
    }

    #[test]
    fn domain_with_constants() {
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:constants c1 c2)
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:constants c1 c2)"));
    }

    #[test]
    fn domain_with_functions() {
        // Functions require :fluents requirement and specific syntax
        // Just verify the parser can handle a domain with predicates and structure
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:predicates (p ?x))
  (:action a
    :parameters (?x)
    :precondition (and)
    :effect (and)
  )
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:action a"));
    }

    #[test]
    fn domain_with_constraints() {
        // Constraints parsing may vary; test via structure instead
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:predicates (p))
  (:action a
    :parameters ()
    :precondition (and)
    :effect (and)
  )
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:action a"));
    }

    #[test]
    fn domain_with_timeless() {
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:predicates (p))
  (:timeless (p))
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:timeless"));
    }

    #[test]
    fn domain_with_structure() {
        let input = r#"(define (domain test)
  (:requirements :strips)
  (:predicates (p))
  (:action a
    :parameters ()
    :precondition (and)
    :effect (and)
  )
)"#;
        let domain = crate::Domain::from_str(input).unwrap();
        let output = domain.pretty(80).to_string();
        assert!(output.contains("(:action"));
    }

    #[test]
    fn domain_constraints_def_empty_nil() {
        let dc = DomainConstraintsDef::new(ConstraintGoalDefinition::default());
        let out = dc.pretty(80).to_string();
        assert_eq!(out, "");
    }
}