Skip to content

lark_parser

This submodule implements the LarkParser class, which uses the lark library to parse filter strings with a defined OPTIMADE filter grammar into Lark.Tree objects for use by the filter transformers.

LarkParser

This class wraps a versioned OPTIMADE grammar and allows it to be parsed into Lark tree objects.

Source code in optimade/filterparser/lark_parser.py
 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
class LarkParser:
    """This class wraps a versioned OPTIMADE grammar and allows
    it to be parsed into Lark tree objects.

    """

    def __init__(
        self, version: tuple[int, int, int] | None = None, variant: str = "default"
    ):
        """For a given version and variant, try to load the corresponding grammar.

        Parameters:
            version: The grammar version number to use (e.g., `(1, 0, 1)` for v1.0.1).
            variant: The grammar variant to employ.

        Raises:
            ParserError: If the requested version/variant of the
                grammar does not exist.

        """

        if not version:
            version = max(
                _ for _ in AVAILABLE_PARSERS if AVAILABLE_PARSERS[_].get("default")
            )

        if version not in AVAILABLE_PARSERS:
            raise ParserError(f"Unknown parser grammar version: {version}")

        if variant not in AVAILABLE_PARSERS[version]:
            raise ParserError(f"Unknown variant of the parser: {variant}")

        self.version = version
        self.variant = variant

        with open(AVAILABLE_PARSERS[version][variant]) as f:
            self.lark = Lark(f, maybe_placeholders=False)

        self.tree: Tree | None = None
        self.filter: str | None = None

    def parse(self, filter_: str) -> Tree:
        """Parse a filter string into a `lark.Tree`.

        Parameters:
            filter_: The filter string to parse.

        Raises:
            BadRequest: If the filter cannot be parsed.

        Returns:
            The parsed filter.

        """
        try:
            self.tree = self.lark.parse(filter_)
            self.filter = filter_
            return self.tree
        except Exception as exc:
            raise BadRequest(
                detail=f"Unable to parse filter {filter_}. Lark traceback: \n{exc}"
            ) from exc

    def __repr__(self):
        if isinstance(self.tree, Tree):
            return self.tree.pretty()
        return repr(self.lark)

__init__(version=None, variant='default')

For a given version and variant, try to load the corresponding grammar.

Parameters:

Name Type Description Default
version tuple[int, int, int] | None

The grammar version number to use (e.g., (1, 0, 1) for v1.0.1).

None
variant str

The grammar variant to employ.

'default'

Raises:

Type Description
ParserError

If the requested version/variant of the grammar does not exist.

Source code in optimade/filterparser/lark_parser.py
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
def __init__(
    self, version: tuple[int, int, int] | None = None, variant: str = "default"
):
    """For a given version and variant, try to load the corresponding grammar.

    Parameters:
        version: The grammar version number to use (e.g., `(1, 0, 1)` for v1.0.1).
        variant: The grammar variant to employ.

    Raises:
        ParserError: If the requested version/variant of the
            grammar does not exist.

    """

    if not version:
        version = max(
            _ for _ in AVAILABLE_PARSERS if AVAILABLE_PARSERS[_].get("default")
        )

    if version not in AVAILABLE_PARSERS:
        raise ParserError(f"Unknown parser grammar version: {version}")

    if variant not in AVAILABLE_PARSERS[version]:
        raise ParserError(f"Unknown variant of the parser: {variant}")

    self.version = version
    self.variant = variant

    with open(AVAILABLE_PARSERS[version][variant]) as f:
        self.lark = Lark(f, maybe_placeholders=False)

    self.tree: Tree | None = None
    self.filter: str | None = None

parse(filter_)

Parse a filter string into a lark.Tree.

Parameters:

Name Type Description Default
filter_ str

The filter string to parse.

required

Raises:

Type Description
BadRequest

If the filter cannot be parsed.

Returns:

Type Description
Tree

The parsed filter.

Source code in optimade/filterparser/lark_parser.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def parse(self, filter_: str) -> Tree:
    """Parse a filter string into a `lark.Tree`.

    Parameters:
        filter_: The filter string to parse.

    Raises:
        BadRequest: If the filter cannot be parsed.

    Returns:
        The parsed filter.

    """
    try:
        self.tree = self.lark.parse(filter_)
        self.filter = filter_
        return self.tree
    except Exception as exc:
        raise BadRequest(
            detail=f"Unable to parse filter {filter_}. Lark traceback: \n{exc}"
        ) from exc

ParserError

Bases: Exception

Triggered by critical parsing errors that should lead to 500 Server Error HTTP statuses.

Source code in optimade/filterparser/lark_parser.py
16
17
18
19
class ParserError(Exception):
    """Triggered by critical parsing errors that should lead
    to 500 Server Error HTTP statuses.
    """

get_versions()

Find grammar files within this package's grammar directory, returning a dictionary broken down by scraped grammar version (major, minor, patch) and variant (a string tag).

Returns:

Type Description
dict[tuple[int, int, int], dict[str, Path]]

A mapping from version, variant to grammar file name.

Source code in optimade/filterparser/lark_parser.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def get_versions() -> dict[tuple[int, int, int], dict[str, Path]]:
    """Find grammar files within this package's grammar directory,
    returning a dictionary broken down by scraped grammar version
    (major, minor, patch) and variant (a string tag).

    Returns:
        A mapping from version, variant to grammar file name.

    """
    dct: dict[tuple[int, int, int], dict[str, Path]] = {}
    for filename in Path(__file__).parent.joinpath("../grammar").glob("*.lark"):
        tags = filename.stem.lstrip("v").split(".")
        version: tuple[int, int, int] = (int(tags[0]), int(tags[1]), int(tags[2]))
        variant: str = "default" if len(tags) == 3 else str(tags[-1])
        if version not in dct:
            dct[version] = {}
        dct[version][variant] = filename
    return dct