jia_parse/ast.rs
1//! Abstract Syntax Tree (AST) for PDDL domain and problem files.
2//!
3//! Every type here is produced by the parser (see [`super::parser`]). The AST is a direct
4//! structural representation of the PDDL syntax: no semantic analysis, type checking, or
5//! planning is performed at this stage.
6
7use serde::Serialize;
8
9// ---------------------------------------------------------------------------
10// Names & identifiers
11// ---------------------------------------------------------------------------
12
13/// A symbol name: predicate/action/object names, type names, etc.
14pub type Name = String;
15
16/// A variable reference including the leading `?` (e.g. `?x`, `?from`).
17pub type Variable = String; // includes the leading '?'
18
19// ---------------------------------------------------------------------------
20// Requirements
21// ---------------------------------------------------------------------------
22
23/// PDDL requirement flags declared in the `:requirements` section of a domain.
24#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
25pub enum Requirement {
26 Strips,
27 Typing,
28 NegativePreconditions,
29 DisjunctivePreconditions,
30 Equality,
31 ExistentialPreconditions,
32 UniversalPreconditions,
33 QuantifiedPreconditions,
34 ConditionalEffects,
35 Fluents,
36 NumericFluents,
37 Adl,
38 DurativeActions,
39 DurationInequalities,
40 TimedInitialLiterals,
41 Preferences,
42 Constraints,
43 ActionCosts,
44 GoalUtilities,
45 DerivedPredicates,
46 DomainAxioms,
47}
48
49// ---------------------------------------------------------------------------
50// Typed lists: "?x ?y - type1 ?z - type2"
51// ---------------------------------------------------------------------------
52
53/// A group of items sharing a common PDDL type.
54///
55/// For example, `?x ?y - location` is represented as:
56///
57/// ```text
58/// TypedGroup { items: ["?x", "?y"], type_name: Some("location") }
59/// ```
60///
61/// When no type is specified (untyped PDDL), `type_name` is `None`.
62#[derive(Debug, Clone, PartialEq, Serialize)]
63pub struct TypedGroup<T: Serialize> {
64 /// The items sharing this type.
65 pub items: Vec<T>,
66 /// The PDDL type name, or `None` for untyped items (implicitly type `object`).
67 pub type_name: Option<Name>,
68}
69
70/// A sequence of typed groups; the standard PDDL typed-list construct (e.g. `?x ?y - type1 ?z - type2`).
71pub type TypedList<T> = Vec<TypedGroup<T>>;
72
73// ---------------------------------------------------------------------------
74// Type declarations (with hierarchy)
75// ---------------------------------------------------------------------------
76
77/// Represents type declarations like `driver truck obj - locatable`.
78pub type TypeDeclarations = TypedList<Name>;
79
80// ---------------------------------------------------------------------------
81// Predicate / Function declarations
82// ---------------------------------------------------------------------------
83
84/// A predicate declaration from the `:predicates` section, with name and typed parameters.
85#[derive(Debug, Clone, PartialEq, Serialize)]
86pub struct PredicateDecl {
87 pub name: Name,
88 pub parameters: TypedList<Variable>,
89}
90
91/// A numeric function declaration from `:functions`, with optional return type.
92#[derive(Debug, Clone, PartialEq, Serialize)]
93pub struct FunctionDecl {
94 pub name: Name,
95 pub parameters: TypedList<Variable>,
96 pub return_type: Option<Name>,
97}
98
99// ---------------------------------------------------------------------------
100// Numeric expressions
101// ---------------------------------------------------------------------------
102
103/// A numeric expression tree used in preconditions, effects, durations, and metrics.
104///
105/// N-ary PDDL operators like `(+ a b c)` are desugared into nested binary operations
106/// during parsing, e.g. `(+ (+ a b) c)`.
107#[derive(Debug, Clone, PartialEq, Serialize)]
108pub enum NumericExpr {
109 /// A numeric literal (e.g. `3.14`, `0`, `100`).
110 Number(f64),
111 /// A reference to a numeric fluent (e.g. `(distance ?x ?y)`).
112 FunctionCall(FunctionTerm),
113 /// A binary arithmetic operation.
114 BinaryOp {
115 op: BinaryOp,
116 left: Box<NumericExpr>,
117 right: Box<NumericExpr>,
118 },
119 /// Unary negation (e.g. `(- expr)`).
120 Negate(Box<NumericExpr>),
121 /// The built-in `total-time` expression (plan makespan).
122 TotalTime,
123 /// The `?duration` variable inside durative action constraints.
124 Duration,
125}
126
127/// Arithmetic binary operators for [`NumericExpr::BinaryOp`].
128#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
129pub enum BinaryOp {
130 /// Addition (`+`).
131 Add,
132 /// Subtraction (`-`).
133 Sub,
134 /// Multiplication (`*`).
135 Mul,
136 /// Division (`/`).
137 Div,
138}
139
140/// A reference to a numeric function (fluent) with its arguments.
141///
142/// For example, `(distance ?from ?to)` becomes:
143///
144/// ```text
145/// FunctionTerm { name: "distance", args: [Variable("?from"), Variable("?to")] }
146/// ```
147#[derive(Debug, Clone, PartialEq, Serialize)]
148pub struct FunctionTerm {
149 /// The function (fluent) name.
150 pub name: Name,
151 /// The arguments to the function, each a constant name or variable reference.
152 pub args: Vec<Term>,
153}
154
155/// A term appearing as an argument to a predicate or function.
156#[derive(Debug, Clone, PartialEq, Serialize)]
157pub enum Term {
158 /// A constant (object) name, e.g. `city1`, `truck-a`.
159 Name(Name),
160 /// A variable reference, e.g. `?x`. Includes the leading `?`.
161 Variable(Variable),
162}
163
164// ---------------------------------------------------------------------------
165// Comparisons
166// ---------------------------------------------------------------------------
167
168/// Numeric comparison operators used in [`Condition::NumericComparison`] and
169/// [`DurationConstraint::Cmp`].
170#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
171pub enum CompareOp {
172 /// `<` -- strictly less than.
173 Lt,
174 /// `<=` -- less than or equal.
175 Lte,
176 /// `>` -- strictly greater than.
177 Gt,
178 /// `>=` -- greater than or equal.
179 Gte,
180 /// `=` -- equal.
181 Eq,
182}
183
184// ---------------------------------------------------------------------------
185// Conditions / Goal descriptions
186// ---------------------------------------------------------------------------
187
188/// A condition (goal description) in PDDL.
189///
190/// This is the central recursive type for preconditions, goals, and constraint bodies.
191/// It covers logical connectives, first-order quantifiers, predicate tests, numeric
192/// comparisons, temporal wrappers (durative actions), PDDL3 trajectory constraints,
193/// and preferences.
194#[derive(Debug, Clone, PartialEq, Serialize)]
195pub enum Condition {
196 /// Conjunction: `(and c1 c2 ...)`.
197 And(Vec<Condition>),
198 /// Disjunction: `(or c1 c2 ...)`.
199 Or(Vec<Condition>),
200 /// Negation: `(not c)`.
201 Not(Box<Condition>),
202 /// Implication: `(imply c1 c2)`.
203 Imply(Box<Condition>, Box<Condition>),
204 /// Universal quantification: `(forall (?x - type) c)`.
205 Forall {
206 variables: TypedList<Variable>,
207 condition: Box<Condition>,
208 },
209 /// Existential quantification: `(exists (?x - type) c)`.
210 Exists {
211 variables: TypedList<Variable>,
212 condition: Box<Condition>,
213 },
214 /// A positive predicate test: `(at ?x ?y)`.
215 Predicate(AtomicFormula),
216 /// Object equality: `(= ?x ?y)`.
217 Equals(Term, Term),
218 /// Numeric comparison: `(<op> <expr> <expr>)`.
219 NumericComparison {
220 op: CompareOp,
221 left: NumericExpr,
222 right: NumericExpr,
223 },
224 /// PDDL3 named preference: `(preference <name> <cond>)`.
225 Preference {
226 name: Option<Name>,
227 condition: Box<Condition>,
228 },
229
230 // -- Temporal conditions (only inside durative actions) --
231 /// `(at start <cond>)` -- holds at the action's start.
232 AtStart(Box<Condition>),
233 /// `(at end <cond>)` -- holds at the action's end.
234 AtEnd(Box<Condition>),
235 /// `(over all <cond>)` -- holds throughout the action.
236 OverAll(Box<Condition>),
237
238 // -- PDDL3 trajectory constraints --
239 /// `(always <cond>)`.
240 Always(Box<Condition>),
241 /// `(sometime <cond>)`.
242 Sometime(Box<Condition>),
243 /// `(at-most-once <cond>)`.
244 AtMostOnce(Box<Condition>),
245 /// `(within <deadline> <cond>)`.
246 Within(f64, Box<Condition>),
247 /// `(sometime-before <cond1> <cond2>)`.
248 SometimeBefore(Box<Condition>, Box<Condition>),
249 /// `(sometime-after <cond1> <cond2>)`.
250 SometimeAfter(Box<Condition>, Box<Condition>),
251 /// `(always-within <window> <cond1> <cond2>)`.
252 AlwaysWithin(f64, Box<Condition>, Box<Condition>),
253 /// `(hold-during <t1> <t2> <cond>)`.
254 HoldDuring(f64, f64, Box<Condition>),
255 /// `(hold-after <t> <cond>)`.
256 HoldAfter(f64, Box<Condition>),
257}
258
259/// A predicate applied to arguments, e.g. `(at truck1 city-a)`.
260#[derive(Debug, Clone, PartialEq, Serialize)]
261pub struct AtomicFormula {
262 /// The predicate name.
263 pub name: Name,
264 /// Arguments (constants or variables).
265 pub args: Vec<Term>,
266}
267
268// ---------------------------------------------------------------------------
269// Effects
270// ---------------------------------------------------------------------------
271
272/// An action effect.
273///
274/// Effects can add/delete predicate instances, perform numeric assignments,
275/// and include conditional (`when`) and universal (`forall`) sub-effects.
276/// Inside durative actions, effects are wrapped in temporal markers (`AtStart`, `AtEnd`).
277#[derive(Debug, Clone, PartialEq, Serialize)]
278pub enum Effect {
279 /// Conjunction: `(and e1 e2 ...)`.
280 And(Vec<Effect>),
281 /// Add (assert) a predicate instance.
282 Predicate(AtomicFormula),
283 /// Delete (negate) a predicate instance: `(not (pred ...))`.
284 NotPredicate(AtomicFormula),
285 /// Universal effect: `(forall (?x - type) <effect>)`.
286 Forall {
287 variables: TypedList<Variable>,
288 effect: Box<Effect>,
289 },
290 /// Conditional effect: `(when <condition> <effect>)`.
291 When {
292 condition: Condition,
293 effect: Box<Effect>,
294 },
295 /// Numeric assignment: `(assign/increase/decrease/scale-up/scale-down <fn> <expr>)`.
296 NumericAssign {
297 op: AssignOp,
298 function: FunctionTerm,
299 expr: NumericExpr,
300 },
301
302 // -- Temporal effects (only inside durative actions) --
303 /// `(at start <effect>)`.
304 AtStart(Box<Effect>),
305 /// `(at end <effect>)`.
306 AtEnd(Box<Effect>),
307}
308
309/// Numeric assignment operators for [`Effect::NumericAssign`].
310#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
311pub enum AssignOp {
312 /// `(assign <fn> <expr>)` -- set to value.
313 Assign,
314 /// `(scale-up <fn> <expr>)` -- multiply.
315 ScaleUp,
316 /// `(scale-down <fn> <expr>)` -- divide.
317 ScaleDown,
318 /// `(increase <fn> <expr>)` -- add.
319 Increase,
320 /// `(decrease <fn> <expr>)` -- subtract.
321 Decrease,
322}
323
324// ---------------------------------------------------------------------------
325// Duration constraints
326// ---------------------------------------------------------------------------
327
328/// Duration constraint on a durative action (e.g. `(= ?duration 5)`, `(>= ?duration (distance ?x ?y))`).
329#[derive(Debug, Clone, PartialEq, Serialize)]
330pub enum DurationConstraint {
331 /// Conjunction of multiple duration constraints.
332 And(Vec<DurationConstraint>),
333 /// A single comparison against `?duration`.
334 Cmp {
335 /// The comparison operator (`=`, `>=`, `<=`).
336 op: CompareOp,
337 /// The right-hand-side expression (left-hand side is always `?duration`).
338 expr: NumericExpr,
339 },
340}
341
342// ---------------------------------------------------------------------------
343// Actions
344// ---------------------------------------------------------------------------
345
346/// A non-temporal (instantaneous) PDDL action defined with `:action`.
347#[derive(Debug, Clone, PartialEq, Serialize)]
348pub struct BasicAction {
349 /// The action name (e.g. `"drive"`).
350 pub name: Name,
351 /// Typed parameter list. Includes any PDDL 1.2 `:vars` parameters.
352 pub parameters: TypedList<Variable>,
353 /// Optional precondition; `None` means unconditionally applicable.
354 pub precondition: Option<Condition>,
355 /// Optional effect; `None` means no state change.
356 pub effect: Option<Effect>,
357}
358
359/// A temporal PDDL action defined with `:durative-action`.
360///
361/// Conditions and effects may contain temporal wrappers (`at start`, `at end`, `over all`).
362#[derive(Debug, Clone, PartialEq, Serialize)]
363pub struct DurativeAction {
364 /// The action name (e.g. `"drive"`).
365 pub name: Name,
366 /// Typed parameter list.
367 pub parameters: TypedList<Variable>,
368 /// Duration constraint (e.g. `(= ?duration 10)`).
369 pub duration: DurationConstraint,
370 /// Optional condition with temporal annotations.
371 pub condition: Option<Condition>,
372 /// Optional effect with temporal annotations.
373 pub effect: Option<Effect>,
374}
375
376// ---------------------------------------------------------------------------
377// Derived predicates
378// ---------------------------------------------------------------------------
379
380/// A derived predicate (`:derived`) with axiom body.
381#[derive(Debug, Clone, PartialEq, Serialize)]
382pub struct DerivedPredicate {
383 pub predicate: AtomicFormula,
384 pub condition: Condition,
385}
386
387// ---------------------------------------------------------------------------
388// Init elements (problem file)
389// ---------------------------------------------------------------------------
390
391/// An element of the problem's `:init` section.
392#[derive(Debug, Clone, PartialEq, Serialize)]
393pub enum InitElement {
394 /// A predicate that holds in the initial state (e.g. `(at truck1 s0)`).
395 Predicate(AtomicFormula),
396 /// A negated predicate in the initial state (e.g. `(not (visited c1))`).
397 NotPredicate(AtomicFormula),
398 /// A numeric fluent initialization (e.g. `(= (distance a b) 10)`).
399 NumericAssignment(FunctionTerm, f64),
400 /// A timed initial literal: `(at <time> <literal>)`. The inner element is
401 /// typically a `Predicate` or `NotPredicate`.
402 At(f64, Box<InitElement>),
403}
404
405// ---------------------------------------------------------------------------
406// Metric specification
407// ---------------------------------------------------------------------------
408
409/// Optimization direction for the `:metric` specification.
410#[derive(Debug, Clone, PartialEq, Serialize)]
411pub enum Optimization {
412 /// `minimize` the metric expression.
413 Minimize,
414 /// `maximize` the metric expression.
415 Maximize,
416}
417
418/// The `:metric` specification combining optimization direction and expression.
419#[derive(Debug, Clone, PartialEq, Serialize)]
420pub struct MetricSpec {
421 pub optimization: Optimization,
422 pub expr: NumericExpr,
423}
424
425// ---------------------------------------------------------------------------
426// Top-level structures
427// ---------------------------------------------------------------------------
428
429/// A parsed PDDL domain file.
430///
431/// Produced by [`super::parser::parse_domain`] / [`super::parser::parse_domain_str`].
432#[derive(Debug, Clone, PartialEq, Serialize)]
433pub struct Domain {
434 /// The domain name from `(domain <name>)`.
435 pub name: Name,
436 /// Declared requirements (`:requirements`).
437 pub requirements: Vec<Requirement>,
438 /// Type hierarchy declarations (`:types`).
439 pub types: TypeDeclarations,
440 /// Domain-level constants (`:constants`).
441 pub constants: TypedList<Name>,
442 /// Predicate declarations (`:predicates`).
443 pub predicates: Vec<PredicateDecl>,
444 /// Numeric function declarations (`:functions`).
445 pub functions: Vec<FunctionDecl>,
446 /// Instantaneous actions (`:action`).
447 pub actions: Vec<BasicAction>,
448 /// Temporal actions (`:durative-action`).
449 pub durative_actions: Vec<DurativeAction>,
450 /// Derived predicates / axioms (`:derived`).
451 pub derived_predicates: Vec<DerivedPredicate>,
452}
453
454impl Domain {
455 /// Sort all declaration lists alphabetically by name.
456 ///
457 /// Sorts predicates, functions, actions, durative actions, derived predicates,
458 /// and items within each typed group (constants, types) for deterministic ordering.
459 pub fn sort_alphabetically(&mut self) {
460 for group in &mut self.types {
461 group.items.sort();
462 }
463 for group in &mut self.constants {
464 group.items.sort();
465 }
466 self.predicates.sort_by(|a, b| a.name.cmp(&b.name));
467 self.functions.sort_by(|a, b| a.name.cmp(&b.name));
468 self.actions.sort_by(|a, b| a.name.cmp(&b.name));
469 self.durative_actions.sort_by(|a, b| a.name.cmp(&b.name));
470 self.derived_predicates
471 .sort_by(|a, b| a.predicate.name.cmp(&b.predicate.name));
472 }
473}
474
475/// A parsed PDDL problem file.
476///
477/// Produced by [`super::parser::parse_problem`] / [`super::parser::parse_problem_str`].
478#[derive(Debug, Clone, PartialEq, Serialize)]
479pub struct Problem {
480 /// The problem name from `(problem <name>)`.
481 pub name: Name,
482 /// The domain this problem refers to, from `(:domain <name>)`.
483 pub domain_name: Name,
484 /// Declared requirements (`:requirements`), if any.
485 pub requirements: Vec<Requirement>,
486 /// Typed object list (`:objects`).
487 pub objects: TypedList<Name>,
488 /// Initial state elements (`:init`).
489 pub init: Vec<InitElement>,
490 /// Goal condition (`:goal`).
491 pub goal: Condition,
492 /// Optional optimization metric (`:metric`).
493 pub metric: Option<MetricSpec>,
494 /// Optional PDDL3 trajectory constraints (`:constraints`).
495 pub constraints: Option<Condition>,
496}
497
498impl Problem {
499 /// Sort object lists and init elements alphabetically for deterministic ordering.
500 pub fn sort_alphabetically(&mut self) {
501 for group in &mut self.objects {
502 group.items.sort();
503 }
504 self.init.sort_by_key(init_sort_key);
505 }
506}
507
508/// Format an atomic formula for sorting purposes.
509fn format_atomic_formula(af: &AtomicFormula) -> String {
510 if af.args.is_empty() {
511 af.name.clone()
512 } else {
513 let args: Vec<&str> = af.args.iter().map(term_name).collect();
514 format!("{}({})", af.name, args.join(","))
515 }
516}
517
518/// Format a function term for sorting purposes.
519fn format_function_term(ft: &FunctionTerm) -> String {
520 if ft.args.is_empty() {
521 ft.name.clone()
522 } else {
523 let args: Vec<&str> = ft.args.iter().map(term_name).collect();
524 format!("{}({})", ft.name, args.join(","))
525 }
526}
527
528/// Compute a sort key for an init element.
529fn init_sort_key(e: &InitElement) -> (u8, String) {
530 match e {
531 InitElement::Predicate(af) => (0, format_atomic_formula(af)),
532 InitElement::NotPredicate(af) => (1, format_atomic_formula(af)),
533 InitElement::NumericAssignment(ft, _) => (2, format_function_term(ft)),
534 InitElement::At(t, inner) => {
535 let (_, s) = init_sort_key(inner);
536 (3, format!("{t:.6}{s}"))
537 }
538 }
539}
540
541/// Extract the name from a Term for sorting.
542fn term_name(t: &Term) -> &str {
543 match t {
544 Term::Name(n) => n.as_str(),
545 Term::Variable(v) => v.as_str(),
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn domain_sort_orders_all_named_declarations() {
555 let mut domain = Domain {
556 name: "d".to_string(),
557 requirements: Vec::new(),
558 types: vec![TypedGroup {
559 items: vec!["z".to_string(), "a".to_string()],
560 type_name: None,
561 }],
562 constants: vec![TypedGroup {
563 items: vec!["c2".to_string(), "c1".to_string()],
564 type_name: None,
565 }],
566 predicates: vec![
567 PredicateDecl {
568 name: "zpred".to_string(),
569 parameters: Vec::new(),
570 },
571 PredicateDecl {
572 name: "apred".to_string(),
573 parameters: Vec::new(),
574 },
575 ],
576 functions: vec![
577 FunctionDecl {
578 name: "zfunc".to_string(),
579 parameters: Vec::new(),
580 return_type: None,
581 },
582 FunctionDecl {
583 name: "afunc".to_string(),
584 parameters: Vec::new(),
585 return_type: None,
586 },
587 ],
588 actions: vec![
589 BasicAction {
590 name: "zact".to_string(),
591 parameters: Vec::new(),
592 precondition: None,
593 effect: None,
594 },
595 BasicAction {
596 name: "aact".to_string(),
597 parameters: Vec::new(),
598 precondition: None,
599 effect: None,
600 },
601 ],
602 durative_actions: vec![
603 DurativeAction {
604 name: "zdur".to_string(),
605 parameters: Vec::new(),
606 duration: DurationConstraint::Cmp {
607 op: CompareOp::Eq,
608 expr: NumericExpr::Number(1.0),
609 },
610 condition: None,
611 effect: None,
612 },
613 DurativeAction {
614 name: "adur".to_string(),
615 parameters: Vec::new(),
616 duration: DurationConstraint::Cmp {
617 op: CompareOp::Eq,
618 expr: NumericExpr::Number(1.0),
619 },
620 condition: None,
621 effect: None,
622 },
623 ],
624 derived_predicates: vec![
625 DerivedPredicate {
626 predicate: AtomicFormula {
627 name: "zderived".to_string(),
628 args: Vec::new(),
629 },
630 condition: Condition::And(Vec::new()),
631 },
632 DerivedPredicate {
633 predicate: AtomicFormula {
634 name: "aderived".to_string(),
635 args: Vec::new(),
636 },
637 condition: Condition::And(Vec::new()),
638 },
639 ],
640 };
641
642 domain.sort_alphabetically();
643
644 assert_eq!(domain.types[0].items, ["a", "z"]);
645 assert_eq!(domain.constants[0].items, ["c1", "c2"]);
646 assert_eq!(domain.predicates[0].name, "apred");
647 assert_eq!(domain.functions[0].name, "afunc");
648 assert_eq!(domain.actions[0].name, "aact");
649 assert_eq!(domain.durative_actions[0].name, "adur");
650 assert_eq!(domain.derived_predicates[0].predicate.name, "aderived");
651 }
652
653 #[test]
654 fn problem_sort_handles_zero_arity_predicates_and_variable_terms() {
655 let mut problem = Problem {
656 name: "p".to_string(),
657 domain_name: "d".to_string(),
658 requirements: Vec::new(),
659 objects: Vec::new(),
660 init: vec![
661 InitElement::NumericAssignment(
662 FunctionTerm {
663 name: "cost".to_string(),
664 args: vec![Term::Variable("?x".to_string())],
665 },
666 1.0,
667 ),
668 InitElement::Predicate(AtomicFormula {
669 name: "ready".to_string(),
670 args: Vec::new(),
671 }),
672 ],
673 goal: Condition::And(Vec::new()),
674 metric: None,
675 constraints: None,
676 };
677
678 problem.sort_alphabetically();
679 assert!(matches!(problem.init[0], InitElement::Predicate(_)));
680 assert!(matches!(
681 problem.init[1],
682 InitElement::NumericAssignment(_, _)
683 ));
684 }
685}