1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Provides parsers for p-effects.

use crate::parsers::{
    atomic_formula, parse_assign_op, parse_f_exp, parse_f_head, parse_function_term, parse_term,
    ParseResult, Span,
};
use crate::parsers::{parens, prefix_expr};
use crate::types::PEffect;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::multispace1;
use nom::combinator::map;
use nom::sequence::{preceded, terminated, tuple};

/// Parses p-effects.
///
/// ## Example
/// ```
/// # use pddl::parsers::{parse_p_effect, preamble::*};
/// # use pddl::{AssignOp, AtomicFormula, EqualityAtomicFormula, FExp, FHead, FunctionSymbol, FunctionTerm, PEffect, Term};
/// assert!(parse_p_effect("(= x y)").is_value(
///     PEffect::AtomicFormula(AtomicFormula::Equality(
///         EqualityAtomicFormula::new(
///             Term::Name("x".into()),
///             Term::Name("y".into()))
///         )
///     )
/// ));
///
/// assert!(parse_p_effect("(not (= ?a B))").is_value(
///     PEffect::NotAtomicFormula(AtomicFormula::Equality(
///         EqualityAtomicFormula::new(
///             Term::Variable("a".into()),
///             Term::Name("B".into()))
///         )
///     )
/// ));
///
/// assert!(parse_p_effect("(assign fun-sym 1.23)").is_value(
///     PEffect::new_numeric_fluent(
///         AssignOp::Assign,
///         FHead::new(FunctionSymbol::from_str("fun-sym")),
///         FExp::new_number(1.23)
///     )
/// ));
///
/// assert!(parse_p_effect("(assign fun-sym 1.23)").is_value(
///     PEffect::new_numeric_fluent(
///         AssignOp::Assign,
///         FHead::new(FunctionSymbol::from_str("fun-sym")),
///         FExp::new_number(1.23)
///     )
/// ));
///
/// assert!(parse_p_effect("(assign (fun-sym) undefined)").is_value(
///     PEffect::new_object_fluent(
///         FunctionTerm::new(FunctionSymbol::from_str("fun-sym"), []),
///         None
///     )
/// ));
///
/// assert!(parse_p_effect("(assign (fun-sym) something)").is_value(
///     PEffect::new_object_fluent(
///         FunctionTerm::new(FunctionSymbol::from_str("fun-sym"), []),
///         Some(Term::Name("something".into()))
///     )
/// ));
/// ```
pub fn parse_p_effect<'a, T: Into<Span<'a>>>(input: T) -> ParseResult<'a, PEffect> {
    let is = map(atomic_formula(parse_term), |af| PEffect::new(af));
    let is_not = map(prefix_expr("not", atomic_formula(parse_term)), |af| {
        PEffect::new_not(af)
    });

    // :numeric-fluents
    let numeric = map(
        parens(tuple((
            parse_assign_op,
            preceded(multispace1, parse_f_head),
            preceded(multispace1, parse_f_exp),
        ))),
        |(op, head, exp)| PEffect::new_numeric_fluent(op, head, exp),
    );

    // :object-fluents
    let object_undefined = map(
        prefix_expr(
            "assign",
            terminated(parse_function_term, tuple((multispace1, tag("undefined")))),
        ),
        |f_term| PEffect::new_object_fluent(f_term, None),
    );
    let object = map(
        prefix_expr(
            "assign",
            tuple((parse_function_term, preceded(multispace1, parse_term))),
        ),
        |(f_term, term)| PEffect::new_object_fluent(f_term, Some(term)),
    );

    alt((is_not, object_undefined, object, numeric, is))(input.into())
}

impl crate::parsers::Parser for PEffect {
    type Item = PEffect;

    /// See [`parse_p_effect`].
    fn parse<'a, S: Into<Span<'a>>>(input: S) -> ParseResult<'a, Self::Item> {
        parse_p_effect(input)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let input = "(can-move ?from-waypoint ?to-waypoint)";
        let (_, _effect) = parse_p_effect(Span::new(input)).unwrap();
    }

    #[test]
    fn not_works() {
        let input = "(not (at B ?m))";
        let mut is_not = map(prefix_expr("not", atomic_formula(parse_term)), |af| {
            PEffect::new_not(af)
        });

        let result = is_not(input.into());
        assert!(result.is_ok());
    }
}