1mod condition;
24mod cursor;
25mod domain;
26mod effect;
27mod expr;
28mod problem;
29mod terms;
30mod utils;
31
32use crate::ast::{Domain, Problem};
33use crate::error::ParseError;
34use crate::lexer::Token;
35
36pub fn parse_domain(tokens: &[Token]) -> Result<Domain, ParseError> {
56 let mut p = cursor::Parser::new(tokens);
57 let mut domain = domain::parse_domain_def(&mut p)?;
58 domain.sort_alphabetically();
59 Ok(domain)
60}
61
62pub fn parse_problem(tokens: &[Token]) -> Result<Problem, ParseError> {
78 let mut p = cursor::Parser::new(tokens);
79 let mut problem = problem::parse_problem_def(&mut p)?;
80 problem.sort_alphabetically();
81 Ok(problem)
82}
83
84pub fn parse_domain_str(input: &str) -> Result<Domain, ParseError> {
102 let tokens = crate::lexer::tokenize(input)?;
103 parse_domain(&tokens)
104}
105
106pub fn parse_problem_str(input: &str) -> Result<Problem, ParseError> {
123 let tokens = crate::lexer::tokenize(input)?;
124 parse_problem(&tokens)
125}
126
127#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::ast::{AssignOp, Condition, DurationConstraint, Effect, Optimization};
135
136 #[test]
137 fn test_parse_minimal_domain() {
138 let input = r#"
139(define (domain test)
140 (:requirements :strips :typing)
141 (:types block - object)
142 (:predicates (on ?x - block ?y - block) (clear ?x - block))
143)
144"#;
145 let domain = parse_domain_str(input).unwrap();
146 assert_eq!(domain.name, "test");
147 assert_eq!(domain.requirements.len(), 2);
148 assert_eq!(domain.predicates.len(), 2);
149 assert_eq!(domain.predicates[0].name, "clear");
150 assert_eq!(domain.predicates[1].name, "on");
151 }
152
153 #[test]
154 fn test_parse_durative_action() {
155 let input = r#"
156(define (domain test)
157 (:durative-action move
158 :parameters (?x - obj)
159 :duration (= ?duration 5)
160 :condition (and
161 (at start (clear ?x))
162 (over all (safe ?x))
163 )
164 :effect (and
165 (at start (not (clear ?x)))
166 (at end (moved ?x))
167 )
168 )
169)
170"#;
171 let domain = parse_domain_str(input).unwrap();
172 assert_eq!(domain.durative_actions.len(), 1);
173 let da = &domain.durative_actions[0];
174 assert_eq!(da.name, "move");
175 }
176
177 #[test]
178 fn test_parse_minimal_problem() {
179 let input = r#"
180(define (problem test-prob)
181 (:domain test)
182 (:objects a b - block)
183 (:init (on a b) (clear a) (= (cost) 0))
184 (:goal (and (on b a)))
185)
186"#;
187 let problem = parse_problem_str(input).unwrap();
188 assert_eq!(problem.name, "test-prob");
189 assert_eq!(problem.domain_name, "test");
190 assert_eq!(problem.init.len(), 3);
191 }
192
193 #[test]
194 fn test_parse_metric() {
195 let input = r#"
196(define (problem test)
197 (:domain d)
198 (:init)
199 (:goal (and))
200 (:metric minimize (total-time))
201)
202"#;
203 let problem = parse_problem_str(input).unwrap();
204 assert!(problem.metric.is_some());
205 let m = problem.metric.unwrap();
206 assert_eq!(m.optimization, Optimization::Minimize);
207 }
208
209 #[test]
210 fn test_parse_functions_derived_and_duration_inequalities() {
211 let input = r#"
212(define (domain test)
213 (:requirements :typing :numeric-fluents :durative-actions :derived-predicates)
214 (:predicates (ready ?x - obj))
215 (:functions (fuel ?x - obj) (total-cost) - number)
216 (:derived (available ?x - obj) (ready ?x))
217 (:durative-action wait
218 :parameters (?x - obj)
219 :duration (and (>= ?duration 1) (<= ?duration 5))
220 :condition (and (at start (ready ?x)))
221 :effect (and))
222)
223"#;
224
225 let domain = parse_domain_str(input).unwrap();
226
227 assert_eq!(domain.functions.len(), 2);
228 assert_eq!(domain.derived_predicates.len(), 1);
229 assert!(matches!(
230 domain.durative_actions[0].duration,
231 DurationConstraint::And(_)
232 ));
233 }
234
235 #[test]
236 fn test_parse_numeric_precondition() {
237 let input = r#"
238(define (domain test)
239 (:durative-action act
240 :parameters (?d - driver)
241 :duration (= ?duration 10)
242 :condition (and
243 (at start (>= (time_available ?d) 10))
244 )
245 :effect (and
246 (at start (decrease (time_available ?d) 10))
247 )
248 )
249)
250"#;
251 let domain = parse_domain_str(input).unwrap();
252 let da = &domain.durative_actions[0];
253 assert!(da.condition.is_some());
254 assert!(da.effect.is_some());
255 }
256
257 #[test]
258 fn test_parse_broad_condition_variants() {
259 let input = r#"
260(define (problem test)
261 (:domain d)
262 (:init)
263 (:goal
264 (and
265 (or (ready a) (ready b))
266 (not (blocked a))
267 (imply (ready a) (ready b))
268 (forall (?x - obj) (ready ?x))
269 (exists (?x - obj) (ready ?x))
270 (preference (ready a))
271 (at a loc)
272 (over a b)
273 (= a b)
274 (= (cost) 0)
275 (< (cost) 10)
276 (> (cost) 0)
277 (always (ready a))
278 (sometime (ready b))
279 (at-most-once (ready a))
280 (within 5 (ready a))
281 (sometime-before (ready a) (ready b))
282 (sometime-after (ready a) (ready b))
283 (always-within 3 (ready a) (ready b))
284 (hold-during 1 2 (ready a))
285 (hold-after 4 (ready a))
286 ))
287)
288"#;
289
290 let problem = parse_problem_str(input).unwrap();
291 let conditions = match &problem.goal {
292 Condition::And(conditions) => Some(conditions),
293 _ => None,
294 }
295 .unwrap();
296
297 assert!(conditions.iter().any(|c| matches!(c, Condition::Or(_))));
298 assert!(conditions.iter().any(|c| matches!(c, Condition::Not(_))));
299 assert!(conditions
300 .iter()
301 .any(|c| matches!(c, Condition::Imply(_, _))));
302 assert!(conditions
303 .iter()
304 .any(|c| matches!(c, Condition::Forall { .. })));
305 assert!(conditions
306 .iter()
307 .any(|c| matches!(c, Condition::Exists { .. })));
308 assert!(conditions
309 .iter()
310 .any(|c| matches!(c, Condition::Preference { name: None, .. })));
311 assert!(conditions.iter().any(|c| matches!(
312 c,
313 Condition::Predicate(predicate) if predicate.name == "at"
314 )));
315 assert!(conditions.iter().any(|c| matches!(
316 c,
317 Condition::Predicate(predicate) if predicate.name == "over"
318 )));
319 assert!(conditions
320 .iter()
321 .any(|c| matches!(c, Condition::Equals(_, _))));
322 assert!(conditions
323 .iter()
324 .any(|c| matches!(c, Condition::NumericComparison { .. })));
325 assert!(conditions.iter().any(|c| matches!(c, Condition::Always(_))));
326 assert!(conditions
327 .iter()
328 .any(|c| matches!(c, Condition::Sometime(_))));
329 assert!(conditions
330 .iter()
331 .any(|c| matches!(c, Condition::AtMostOnce(_))));
332 assert!(conditions
333 .iter()
334 .any(|c| matches!(c, Condition::Within(_, _))));
335 assert!(conditions
336 .iter()
337 .any(|c| matches!(c, Condition::SometimeBefore(_, _))));
338 assert!(conditions
339 .iter()
340 .any(|c| matches!(c, Condition::SometimeAfter(_, _))));
341 assert!(conditions
342 .iter()
343 .any(|c| matches!(c, Condition::AlwaysWithin(_, _, _))));
344 assert!(conditions
345 .iter()
346 .any(|c| matches!(c, Condition::HoldDuring(_, _, _))));
347 assert!(conditions
348 .iter()
349 .any(|c| matches!(c, Condition::HoldAfter(_, _))));
350 }
351
352 #[test]
353 fn test_parse_broad_effect_variants() {
354 let input = r#"
355(define (domain test)
356 (:action a
357 :parameters (?x - obj)
358 :precondition (and)
359 :effect
360 (and
361 (at ?x loc)
362 (assign (cost) 1)
363 (increase (cost) 2)
364 (scale-up (cost) 3)
365 (when (ready ?x) (decrease (cost) 1))))
366 (:durative-action d
367 :parameters (?x - obj)
368 :duration (= ?duration 1)
369 :condition (and)
370 :effect
371 (and
372 (at start (at ?x loc))
373 (at end (scale-down (cost) 2))))
374)
375"#;
376
377 let domain = parse_domain_str(input).unwrap();
378 let effects = domain.actions[0]
379 .effect
380 .as_ref()
381 .and_then(|effect| match effect {
382 Effect::And(effects) => Some(effects),
383 _ => None,
384 })
385 .unwrap();
386
387 assert!(effects.iter().any(|e| matches!(
388 e,
389 Effect::Predicate(predicate) if predicate.name == "at"
390 )));
391 assert!(effects.iter().any(|e| matches!(
392 e,
393 Effect::NumericAssign {
394 op: AssignOp::Assign,
395 ..
396 }
397 )));
398 assert!(effects.iter().any(|e| matches!(
399 e,
400 Effect::NumericAssign {
401 op: AssignOp::Increase,
402 ..
403 }
404 )));
405 assert!(effects.iter().any(|e| matches!(
406 e,
407 Effect::NumericAssign {
408 op: AssignOp::ScaleUp,
409 ..
410 }
411 )));
412 assert!(effects.iter().any(|e| matches!(e, Effect::When { .. })));
413
414 let durative_effects = domain.durative_actions[0]
415 .effect
416 .as_ref()
417 .and_then(|effect| match effect {
418 Effect::And(effects) => Some(effects),
419 _ => None,
420 })
421 .unwrap();
422 assert!(durative_effects
423 .iter()
424 .any(|e| matches!(e, Effect::AtStart(_))));
425 assert!(durative_effects
426 .iter()
427 .any(|e| matches!(e, Effect::AtEnd(_))));
428 }
429
430 #[test]
431 fn parser_skips_unknown_domain_and_problem_sections() {
432 let domain = parse_domain_str(
433 r#"
434(define (domain test)
435 (:requirements :strips)
436 (:unknown-section (nested value))
437 (legacy-section (ignored value))
438 (:action a
439 :parameters ()
440 :precondition (and)
441 :unknown-action-field (ignored value)
442 :effect (and))
443 (:durative-action d
444 :parameters ()
445 :duration (= ?duration 1)
446 :unknown-durative-field (ignored value)
447 :condition (and)
448 :effect (and))
449)
450"#,
451 )
452 .unwrap();
453 assert_eq!(domain.actions.len(), 1);
454 assert_eq!(domain.durative_actions.len(), 1);
455
456 let problem = parse_problem_str(
457 r#"
458(define (problem p)
459 (:domain test)
460 (:unknown-section (nested value))
461 (legacy-section (ignored value))
462 (:init)
463 (:goal (and))
464)
465"#,
466 )
467 .unwrap();
468 assert_eq!(problem.domain_name, "test");
469 }
470
471 #[test]
472 fn parser_skips_unknown_action_fields_with_sexp_values() {
473 let domain = parse_domain_str(
474 r#"
475(define (domain test)
476 (:action a
477 :parameters ()
478 :precondition (and)
479 :unknown-action-field (ignored value)
480 :effect (and))
481 (:durative-action d
482 :parameters ()
483 :duration (= ?duration 1)
484 :condition (and)
485 :unknown-durative-field (ignored value)
486 :effect (and))
487)
488"#,
489 )
490 .unwrap();
491
492 assert_eq!(domain.actions.len(), 1);
493 assert_eq!(domain.durative_actions.len(), 1);
494 }
495
496 #[test]
497 fn parser_reports_malformed_conditions_effects_and_metrics() {
498 for input in [
499 "(define (problem p) (:domain d) (:goal 42))",
500 "(define (problem p) (:domain d) (:goal (42)))",
501 "(define (problem p) (:domain d) (:goal (> ?x 1)))",
502 "(define (problem p) (:domain d) (:goal (at",
503 "(define (problem p) (:domain d) (:goal (over",
504 "(define (problem p) (:domain d) (:goal (and)) (:metric fastest 1))",
505 "(define (problem p) (:domain d) (:goal (and)) (:metric minimize ?x))",
506 "(define (problem p) (:domain d) (:goal (and)) (:metric minimize (42)))",
507 ] {
508 assert!(parse_problem_str(input).is_err(), "{input}");
509 }
510
511 for input in [
512 "(define (domain d) (:action a :parameters () :precondition (and) :effect (42)))",
513 "(define (domain d) (:action a :parameters () :precondition (and) :effect (at",
514 "(define (domain d) (:action a :parameters () :precondition (and) :effect (increase (cost) ?x)))",
515 ] {
516 assert!(parse_domain_str(input).is_err(), "{input}");
517 }
518 }
519}