From d3da0dec85a93dd8a85964aa371643254ad109e8 Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Tue, 26 Mar 2024 13:31:48 -0300 Subject: [PATCH] Switch to custom dictionary parser --- foamlib/_dictionaries/__init__.py | 22 ++++++++---- foamlib/_dictionaries/_parsing.py | 48 +++++++++++++++++++------ tests/test_dictionaries.py | 60 ++++++++++++++++++------------- 3 files changed, 90 insertions(+), 40 deletions(-) diff --git a/foamlib/_dictionaries/__init__.py b/foamlib/_dictionaries/__init__.py index 78d9df5..6b53794 100644 --- a/foamlib/_dictionaries/__init__.py +++ b/foamlib/_dictionaries/__init__.py @@ -10,7 +10,7 @@ cast, ) from ._values import FoamDimensionSet, FoamDimensioned, FoamValue -from ._parsing import parse +from ._parsing import DICTIONARY, VALUE from ._serialization import serialize from .._subprocesses import run_process, CalledProcessError @@ -67,13 +67,17 @@ def _cmd(self, args: Sequence[str], *, key: Optional[str] = None) -> str: ) from None def __getitem__(self, key: str) -> Union[FoamValue, "FoamDictionary"]: - value = self._cmd(["-value"], key=key) + contents = self._file.path.read_text() + value = DICTIONARY.parse_string(contents, parse_all=True).as_dict() - if value.startswith("{"): - assert value.endswith("}") + for key in [*self._keywords, key]: + value = value[key] + + if isinstance(value, dict): return FoamDictionary(self._file, [*self._keywords, key]) else: - return parse(value) + start, end = value + return VALUE.parse_string(contents[start:end], parse_all=True).as_list()[0] def _setitem( self, @@ -88,6 +92,7 @@ def _setitem( elif isinstance(value, Mapping): self._cmd(["-set", "{}"], key=key) subdict = self[key] + print(subdict) assert isinstance(subdict, FoamDictionary) for k, v in value.items(): subdict[k] = v @@ -114,7 +119,12 @@ def __delitem__(self, key: str) -> None: self._cmd(["-remove"], key=key) def __iter__(self) -> Iterator[str]: - yield from self._cmd(["-keywords"]).splitlines() + value = DICTIONARY.parse_file(self._file.path, parse_all=True).as_dict() + + for key in self._keywords: + value = value[key] + + yield from value def __len__(self) -> int: return len(list(iter(self))) diff --git a/foamlib/_dictionaries/_parsing.py b/foamlib/_dictionaries/_parsing.py index a91b5dc..7155237 100644 --- a/foamlib/_dictionaries/_parsing.py +++ b/foamlib/_dictionaries/_parsing.py @@ -1,18 +1,23 @@ -from typing import cast - from pyparsing import ( + Dict, Forward, Group, Keyword, + LineEnd, Literal, + Located, Opt, QuotedString, Word, + c_style_comment, common, + cpp_style_comment, printables, + identchars, + identbodychars, ) -from ._values import FoamValue, FoamDimensionSet, FoamDimensioned +from ._values import FoamDimensionSet, FoamDimensioned _YES = Keyword("yes").set_parse_action(lambda: True) @@ -20,7 +25,9 @@ _DIMENSIONS = ( Literal("[").suppress() + common.number * 7 + Literal("]").suppress() ).set_parse_action(lambda tks: FoamDimensionSet(*tks)) -_TOKEN = common.identifier | QuotedString('"', unquote_results=False) +_TOKEN = QuotedString('"', unquote_results=False) | Word( + identchars + "$", identbodychars +) _ITEM = Forward() _LIST = Opt( Literal("List") + Literal("<") + common.identifier + Literal(">") @@ -45,12 +52,33 @@ _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN ) -_TOKENS = (QuotedString('"', unquote_results=False) | Word(printables))[ - 1, ... -].set_parse_action(lambda tks: " ".join(tks)) +_TOKENS = ( + QuotedString('"', unquote_results=False) + | Word(printables.replace(";", "").replace("{", "").replace("}", "")) +)[2, ...].set_parse_action(lambda tks: " ".join(tks)) -_VALUE = _ITEM ^ _TOKENS +VALUE = (_ITEM ^ _TOKENS).ignore(c_style_comment).ignore(cpp_style_comment) -def parse(value: str) -> FoamValue: - return cast(FoamValue, _VALUE.parse_string(value, parse_all=True).as_list()[0]) +_UNPARSED_VALUE = ( + QuotedString('"', unquote_results=False) + | Word(printables.replace(";", "").replace("{", "").replace("}", "")) +)[...] +_KEYWORD = QuotedString('"', unquote_results=False) | Word( + identchars + "$(,.)", identbodychars + "$(,.)" +) +DICTIONARY = Forward() +_ENTRY = _KEYWORD + ( + ( + Located(_UNPARSED_VALUE).set_parse_action(lambda tks: (tks[0], tks[2])) + + Literal(";").suppress() + ) + | (Literal("{").suppress() + DICTIONARY + Literal("}").suppress()) +) +DICTIONARY <<= ( + Dict(Group(_ENTRY)[...]) + .set_parse_action(lambda tks: {} if not tks else tks) + .ignore(c_style_comment) + .ignore(cpp_style_comment) + .ignore(Literal("#include") + ... + LineEnd()) # type: ignore +) diff --git a/tests/test_dictionaries.py b/tests/test_dictionaries.py index 2a3eeac..c7cd754 100644 --- a/tests/test_dictionaries.py +++ b/tests/test_dictionaries.py @@ -7,40 +7,52 @@ import numpy as np from foamlib import * -from foamlib._dictionaries._parsing import parse - - -def test_parse() -> None: - assert parse("1") == 1 - assert parse("1.0") == 1.0 - assert parse("1.0e-3") == 1.0e-3 - assert parse("yes") is True - assert parse("no") is False - assert parse("word") == "word" - assert parse("word word") == "word word" - assert parse('"a string"') == '"a string"' - assert parse("uniform 1") == 1 - assert parse("uniform 1.0") == 1.0 - assert parse("uniform 1.0e-3") == 1.0e-3 - assert parse("(1.0 2.0 3.0)") == [1.0, 2.0, 3.0] - assert parse("nonuniform List 2(1 2)") == [1, 2] - assert parse("3(1 2 3)") == [1, 2, 3] - assert parse("2((1 2 3) (4 5 6))") == [[1, 2, 3], [4, 5, 6]] - assert parse("nonuniform List 2((1 2 3) (4 5 6))") == [ +from foamlib._dictionaries._parsing import VALUE + + +def test_parse_value() -> None: + assert VALUE.parse_string("1").as_list()[0] == 1 + assert VALUE.parse_string("1.0").as_list()[0] == 1.0 + assert VALUE.parse_string("1.0e-3").as_list()[0] == 1.0e-3 + assert VALUE.parse_string("yes").as_list()[0] is True + assert VALUE.parse_string("no").as_list()[0] is False + assert VALUE.parse_string("word").as_list()[0] == "word" + assert VALUE.parse_string("word word").as_list()[0] == "word word" + assert VALUE.parse_string('"a string"').as_list()[0] == '"a string"' + assert VALUE.parse_string("uniform 1").as_list()[0] == 1 + assert VALUE.parse_string("uniform 1.0").as_list()[0] == 1.0 + assert VALUE.parse_string("uniform 1.0e-3").as_list()[0] == 1.0e-3 + assert VALUE.parse_string("(1.0 2.0 3.0)").as_list()[0] == [1.0, 2.0, 3.0] + assert VALUE.parse_string("uniform (1 2 3)").as_list()[0] == [1, 2, 3] + assert VALUE.parse_string("nonuniform List 2(1 2)").as_list()[0] == [1, 2] + assert VALUE.parse_string("3(1 2 3)").as_list()[0] == [1, 2, 3] + assert VALUE.parse_string("2((1 2 3) (4 5 6))").as_list()[0] == [ [1, 2, 3], [4, 5, 6], ] - assert parse("[1 1 -2 0 0 0 0]") == FoamDimensionSet(mass=1, length=1, time=-2) - assert parse("g [1 1 -2 0 0 0 0] (0 0 -9.81)") == FoamDimensioned( + assert VALUE.parse_string("nonuniform List 2((1 2 3) (4 5 6))").as_list()[ + 0 + ] == [ + [1, 2, 3], + [4, 5, 6], + ] + assert VALUE.parse_string("[1 1 -2 0 0 0 0]").as_list()[0] == FoamDimensionSet( + mass=1, length=1, time=-2 + ) + assert VALUE.parse_string("g [1 1 -2 0 0 0 0] (0 0 -9.81)").as_list()[ + 0 + ] == FoamDimensioned( name="g", dimensions=FoamDimensionSet(mass=1, length=1, time=-2), value=[0, 0, -9.81], ) - assert parse("[1 1 -2 0 0 0 0] 9.81") == FoamDimensioned( + assert VALUE.parse_string("[1 1 -2 0 0 0 0] 9.81").as_list()[0] == FoamDimensioned( dimensions=FoamDimensionSet(mass=1, length=1, time=-2), value=9.81 ) assert ( - parse("hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)") + VALUE.parse_string( + "hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)" + ).as_list()[0] == "hex (0 1 2 3 4 5 6 7) (1 1 1) simpleGrading (1 1 1)" )