Skip to content

Commit

Permalink
Deprecate 'load_grammar' method and add test of new method to load ad…
Browse files Browse the repository at this point in the history
…ditional model(s)
  • Loading branch information
sognetic committed Dec 12, 2023
1 parent 69233a8 commit 793879e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 28 deletions.
105 changes: 81 additions & 24 deletions src/decaylanguage/dec/dec.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
import re
import warnings
from io import StringIO
from itertools import zip_longest
from itertools import chain, zip_longest
from pathlib import Path
from typing import Any
from typing import Any, Callable, Iterable

from lark import Lark, Token, Transformer, Tree, Visitor
from lark.lexer import TerminalDef
Expand Down Expand Up @@ -87,6 +87,7 @@ class DecFileParser:
"_parsed_dec_file",
"_parsed_decays",
"_include_ccdecays",
"_additional_decay_models",
)

def __init__(self, *filenames: str | os.PathLike[str]) -> None:
Expand Down Expand Up @@ -136,6 +137,9 @@ def __init__(self, *filenames: str | os.PathLike[str]) -> None:
self._parsed_decays: None | (
Any
) = None # Particle decays found in the decay file
self._additional_decay_models: None | Iterable[
str
] = None # Additional decay models not (yet) known to decaylanguage

# By default, consider charge-conjugate decays when parsing
self._include_ccdecays = True
Expand Down Expand Up @@ -164,9 +168,8 @@ def parse(self, include_ccdecays: bool = True) -> None:
Parse the given .dec decay file(s) according to the default Lark parser
and specified options.
See method 'load_grammar' for how to explicitly define the grammar
and set the Lark parsing options. This method needs to be called
before 'parse' to override the parser and its options.
Use the method `load_additional_decay_models` before `parse` to load decay models
that might not yet be available in 'decaylanguage'.
Parameters
----------
Expand All @@ -187,15 +190,17 @@ def parse(self, include_ccdecays: bool = True) -> None:
# effectively loading it
opts = self.grammar_info()
extraopts = {
k: v for k, v in opts.items() if k not in ("lark_file", "parser", "lexer")
k: v
for k, v in opts.items()
if k not in ("lark_file", "parser", "lexer", "edit_terminals")
}

# Instantiate the Lark parser according to chosen settings
parser = Lark(
self.grammar(),
parser=opts["parser"],
lexer=opts["lexer"],
edit_terminals=_edit_model_name_terminals,
edit_terminals=opts["edit_terminals"],
**extraopts,
)
self._parsed_dec_file = parser.parse(self._dec_file)
Expand Down Expand Up @@ -239,7 +244,7 @@ def grammar(self) -> str:
The Lark grammar definition file.
"""
if not self.grammar_loaded:
self.load_grammar()
self._load_grammar()

return self._grammar # type: ignore[return-value]

Expand All @@ -255,7 +260,7 @@ def grammar_info(self) -> dict[str, Any]:
The Lark grammar definition file name and parser options.
"""
if not self.grammar_loaded:
self.load_grammar()
self._load_grammar()
assert self._grammar_info is not None

return self._grammar_info
Expand All @@ -268,6 +273,8 @@ def load_grammar(
**options: Any,
) -> None:
"""
DEPRECATED, please use "`load_additional_decay_models`" instead.
Load a Lark grammar definition file, either the default one,
or a user-specified one, optionally setting Lark parsing options.
Expand All @@ -286,18 +293,78 @@ def load_grammar(
for parser, lexer and options.
"""

warnings.warn(
"This method is deprecated, please add unknown decay models by passing them to "
"`load_additional_decay_models` instead before parsing the decayfile.",
DeprecationWarning,
stacklevel=2,
)

if filename is None:
filename = "decfile.lark"
with data.basepath.joinpath(filename).open() as f1:
self._grammar = f1.read()

self._load_grammar(filename, parser, lexer, **options)

def load_additional_decay_models(self, *models: str) -> None:
if self._additional_decay_models is None:
self._additional_decay_models = known_decay_models
else:
with Path(filename).open(encoding="utf_8") as f2:
self._grammar = f2.read()
self._additional_decay_models = chain.from_iterable(
(self._additional_decay_models, models)
)

def _load_grammar(
self,
filename: str = "decfile.lark",
parser: str = "lalr",
lexer: str = "auto",
**options: Any,
) -> None:
"""
Load the default Lark grammar definition file with default parser and lexer.
Parameters
----------
parser: str, optional, default='lalr'
The Lark parser engine name.
lexer: str, optional, default='auto'
The Lark parser lexer mode to use.
options: keyword arguments, optional
Extra options to pass on to the parsing algorithm.
"""

with data.basepath.joinpath(filename).open() as f1:
self._grammar = f1.read()

self._grammar_info = dict(
lark_file=filename, parser=parser, lexer=lexer, **options
lark_file=filename,
parser=parser,
lexer=lexer,
edit_terminals=self._generate_edit_terminals_callback(),
**options,
)

def _generate_edit_terminals_callback(self) -> Callable[[TerminalDef], None]:
if self._additional_decay_models is None:
decay_models = known_decay_models # type: tuple[str, ...]
else:
decay_models = tuple(
chain.from_iterable([known_decay_models, self._additional_decay_models])
)

def edit_model_name_terminals(t: TerminalDef) -> None:
"""
Edits the terminals of the grammar to replace the model name placeholder with the actual names of the models.
"""

modelstr = rf"(?:{'|'.join(re.escape(dm) for dm in sorted(decay_models, key=len, reverse=True))})"
if t.name == "MODEL_NAME":
t.pattern.value = t.pattern.value.replace(
"MODEL_NAME_PLACEHOLDER", modelstr
)

return edit_model_name_terminals

@property
def grammar_loaded(self) -> bool:
"""
Expand Down Expand Up @@ -1825,13 +1892,3 @@ def _str_to_bool(arg: str) -> bool:
raise ValueError(
f"String {arg!r} cannot be converted to boolean! Only 'yes/no' accepted."
)


def _edit_model_name_terminals(t: TerminalDef) -> None:
"""
Edits the terminals of the grammar to replace the model name placeholder with the actual names of the models.
"""

modelstr = rf"(?:{'|'.join(re.escape(kdm) for kdm in sorted(known_decay_models, key=len, reverse=True))})"
if t.name == "MODEL_NAME":
t.pattern.value = t.pattern.value.replace("MODEL_NAME_PLACEHOLDER", modelstr)
6 changes: 6 additions & 0 deletions tests/data/test_custom_decay_model.dec
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Simple decay with a custom model name

Decay D*+
0.5 D+ pi0 CUSTOM_MODEL1;
0.5 D+ gamma CUSTOM_MODEL2 1 2 3 4;
Enddecay
9 changes: 5 additions & 4 deletions tests/dec/test_dec.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ def test_default_grammar_loading():
assert p.grammar_loaded


def test_explicit_grammar_loading():
p = DecFileParser(DIR / "../data/test_example_Dst.dec")
p.load_grammar(DIR / "../../src/decaylanguage/data/decfile.lark")
def test_custom_model_name():
p = DecFileParser(DIR / "../data/test_custom_decay_model.dec")
p.load_additional_decay_models("CUSTOM_MODEL1", "CUSTOM_MODEL2")

assert p.grammar_loaded is True
assert p.grammar() is not None
assert p.grammar_loaded


def test_string_representation():
Expand Down

0 comments on commit 793879e

Please sign in to comment.