diff --git a/flopy4/lark/__init__.py b/flopy4/lark/__init__.py new file mode 100644 index 0000000..8f81a2c --- /dev/null +++ b/flopy4/lark/__init__.py @@ -0,0 +1,68 @@ +from lark import Lark + +MF6_GRAMMAR = r""" +?start: _NL* _item* +_item: (block | COMMENT) _NL+ + +// block +block: _begin _NL params _end +_begin: _BEGIN name [index] +_end: _END name +name: WORD +index: INT +_BEGIN: "begin"i +_END: "end"i + +// parameter +params: (param _NL)* +param: _key [_value] +_key: KEYS +_value: NUMBER | path | string | array | list + +// string +string: WORD+ + +// file path +path: INOUT PATH +PATH: [_PATHSEP] (NON_SEPARATOR_STRING [_PATHSEP]) [NON_SEPARATOR_STRING] +_PATHSEP: "/" +INOUT: "filein"i|"fileout"i + +// array +array: constantarray | internalarray | externalarray +constantarray: "CONSTANT" NUMBER +internalarray: "INTERNAL" [factor] [iprn] (NUMBER* [_NL])* +externalarray: "OPEN/CLOSE" WORD [factor] ["binary"] [iprn] +factor: "FACTOR" NUMBER +iprn: "IPRN" INT + +// list (adapted from https://github.com/lark-parser/lark/blob/master/examples/composition/csv.lark) +list: header _NL row* +header: "#" " "? (WORD _SEPARATOR?)+ +row: (_anything _SEPARATOR?)+ _NL +_anything: INT | WORD | NON_SEPARATOR_STRING | FLOAT | SIGNED_FLOAT +NON_SEPARATOR_STRING: /[a-zA-z.;\\\/]+/ +_SEPARATOR: /[ ]+/ + | "\t" + | "," + +// newline +_NL: /(\r?\n[\t ]*)+/ + +// parameter keys file can be generated +// with the rest of the plugin interface +// and maybe placed in a separate file +KEYS: "K"|"I"|"D"|"S"|"F"|"A" + +%import common.SH_COMMENT -> COMMENT +%import common.SIGNED_NUMBER -> NUMBER +%import common.SIGNED_FLOAT +%import common.INT +%import common.FLOAT +%import common.WORD +%import common.WS_INLINE + +%ignore WS_INLINE +""" + +MF6_PARSER = Lark(MF6_GRAMMAR, start="start") diff --git a/flopy4/param.py b/flopy4/param.py index 64a1dcb..23bfba7 100644 --- a/flopy4/param.py +++ b/flopy4/param.py @@ -29,8 +29,6 @@ class MFParamSpec: repeating: bool = False tagged: bool = True reader: MFReader = MFReader.urword - # todo change to variadic tuple of str and resolve - # actual shape at load time from simulation context shape: Optional[Tuple[int]] = None default_value: Optional[Any] = None diff --git a/pyproject.toml b/pyproject.toml index bfda02d..16c1602 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,10 +36,11 @@ classifiers = [ ] requires-python = ">=3.9" dependencies = [ - "attrs", # todo: lower bound? - "cattrs", # todo: lower bound? - "Jinja2>=3.0", + "attrs", # todo: bounds? + "cattrs", # todo: bounds? "flopy>=3.7.0", + "Jinja2>=3.0", + "lark", # todo: bounds? "numpy>=1.20.3", "pandas>=2.0.0", "toml>=0.10", @@ -54,6 +55,7 @@ test = [ "flopy4[lint]", "coverage", "GitPython", + "interegular", "jupyter", "jupytext", "modflow-devtools", diff --git a/test/test_lark.py b/test/test_lark.py new file mode 100644 index 0000000..2ac5b52 --- /dev/null +++ b/test/test_lark.py @@ -0,0 +1,42 @@ +from pprint import pprint + +import pytest +from lark import Transformer + +from flopy4.lark import MF6_PARSER + +TEST_PKG = """ +BEGIN OPTIONS + K + I 1 + D 1.0 + S hello world + F FILEIN some/path +END OPTIONS + +BEGIN PACKAGEDATA 1 + A INTERNAL 1.0 2.0 3.0 +END PACKAGEDATA +""" + + +def test_parse_mf6(): + tree = MF6_PARSER.parse(TEST_PKG) + # this is working, check it with: + # pytest test/test_lark.py::test_parse_mf6 -s + print(tree.pretty()) + + +class MF6Transformer(Transformer): + # TODO + pass + + +MF6_TRANSFORMER = MF6Transformer() + + +@pytest.mark.xfail +def test_transform_mf6(): + tree = MF6_PARSER.parse(TEST_PKG) + data = MF6_TRANSFORMER.transform(tree) + pprint(data)