Skip to content

Commit

Permalink
feat: use TypesIndex to parse and validate typed_list_variables
Browse files Browse the repository at this point in the history
  • Loading branch information
marcofavorito committed Jun 7, 2023
1 parent 889b3fc commit 73e4fac
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 69 deletions.
2 changes: 1 addition & 1 deletion pddl/parser/domain.lark
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ atomic_formula_term: LPAR predicate term* RPAR
constant: NAME

typed_list_variable: variable*
| variable+ TYPE_SEP type_def (typed_list_variable)
| (variable+ TYPE_SEP type_def)+ variable*
?variable: "?" NAME

typed_list_name: NAME*
Expand Down
90 changes: 22 additions & 68 deletions pddl/parser/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@

"""Implementation of the PDDL domain parser."""
import sys
from typing import AbstractSet, Dict, List, Mapping, Optional, Sequence, Set, Tuple
from typing import Dict, Optional
from typing import OrderedDict as OrderedDictType
from typing import Set

from lark import Lark, ParseError, Transformer

from pddl.core import Action, Domain, Requirements
from pddl.custom_types import name
from pddl.exceptions import PDDLMissingRequirementError, PDDLParsingError
from pddl.helpers.base import assert_, safe_index
from pddl.helpers.base import assert_
from pddl.logic.base import (
And,
ExistsCondition,
Expand Down Expand Up @@ -307,7 +309,7 @@ def typed_list_name(self, args) -> Dict[name, Optional[name]]:
f"error while parsing tokens {list(map(str, args))}: {str(e)}"
) from None

def typed_list_variable(self, args) -> Dict[str, Set[str]]:
def typed_list_variable(self, args) -> OrderedDictType[name, Set[name]]:
"""
Process the 'typed_list_variable' rule.
Expand All @@ -316,79 +318,31 @@ def typed_list_variable(self, args) -> Dict[str, Set[str]]:
:param args: the argument of this grammar rule
:return: a typed list (variable), i.e. a mapping from variables to the supported types
"""
type_sep_index = safe_index(args, Symbols.TYPE_SEP.value)
if type_sep_index is None:
result = self._parse_simple_typed_list(args, check_for_duplicates=False)
return {var: set() for var in result}

# if we are here, the matched pattern is: [name_1 ... name_n], "-", type_def, other_typed_list_dict # noqa
# make sure there are only two tokens after "-"
assert_(len(args[type_sep_index:]) == 3, "unexpected parser state")

variables: Tuple[str, ...] = tuple(args[:type_sep_index])
type_def: Set[str] = self._process_type_def(args[type_sep_index + 1])
other_typed_list_dict: Mapping[str, Set[str]] = args[type_sep_index + 2]
new_typed_list_dict: Mapping[str, Set[str]] = {v: type_def for v in variables}

# check type conflicts
self._check_duplicates(other_typed_list_dict.keys(), new_typed_list_dict.keys())

return {**new_typed_list_dict, **other_typed_list_dict}
try:
types_index = TypesIndex.parse_typed_list(args)
return types_index.get_typed_list_of_variables()
except ValueError as e:
raise PDDLParsingError(
f"error while parsing tokens {list(map(str, args))}: {str(e)}"
) from None

def type_def(self, args):
"""Parse the 'type_def' rule."""
return args if len(args) == 1 else args[1:-1]
assert_(len(args) != 0, "unexpected parser state: empty type_def")

def _has_requirement(self, requirement: Requirements) -> bool:
"""Check whether a requirement is satisfied by the current state of the domain parsing."""
return requirement in self._extended_requirements

def _check_duplicates(
self,
other_names: AbstractSet[str],
new_names: AbstractSet[str],
) -> None:
names_intersection = new_names & other_names
if len(names_intersection) != 0:
names_list_as_strings = map(repr, map(str, names_intersection))
names_list_str = ", ".join(sorted(names_list_as_strings))
raise PDDLParsingError(
f"detected conflicting items in a typed list: items occurred twice: [{names_list_str}]"
)

def _parse_simple_typed_list(
self, args: Sequence[str], check_for_duplicates: bool = True
) -> Dict[str, Optional[str]]:
"""
Parse a 'simple' typed list.
In this simple case, there are no type specifications, i.e. just a list of items.
If check_for_duplicates is True, a check for duplicates is performed.
"""
# check for duplicates
if check_for_duplicates and len(set(args)) != len(args):
# find duplicates
seen = set()
dupes = [str(x) for x in args if x in seen or seen.add(x)] # type: ignore
raise PDDLParsingError(
f"duplicate items {dupes} found in the typed list: {list(map(str, args))}'"
)

return {arg: None for arg in args}

def _process_type_def(self, type_def: List[str]) -> Set[str]:
"""Process a raw type_def and return a set of types."""
assert_(len(type_def) != 0, "unexpected parser state: empty type_def")

if len(type_def) == 1:
if len(args) == 1:
# single-typed type-def, return
return set(type_def)
return args

# if we are here, type_def is of the form (either t1 ... tn)
either_keyword, types = type_def[0], type_def[1:]
# ignore first and last tokens since they are brackets.
either_keyword, types = args[1], args[2:-1]
assert_(str(either_keyword) == Symbols.EITHER.value)
return set(types)
return types

def _has_requirement(self, requirement: Requirements) -> bool:
"""Check whether a requirement is satisfied by the current state of the domain parsing."""
return requirement in self._extended_requirements


_domain_parser_lark = DOMAIN_GRAMMAR_FILE.read_text()
Expand Down
4 changes: 4 additions & 0 deletions pddl/parser/types_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def get_typed_list_of_names(self) -> Dict[name, Optional[name]]:
result[item] = type_tag
return result

def get_typed_list_of_variables(self) -> OrderedDictType[name, Set[name]]:
"""Get the typed list of variables in form of dictionary."""
return self._item_to_types

@classmethod
def parse_typed_list(cls, tokens: List[Union[str, List[str]]]) -> "TypesIndex":
"""
Expand Down

0 comments on commit 73e4fac

Please sign in to comment.