Skip to content

Commit eea01c8

Browse files
authored
Merge pull request #33 from wpbonelli/lark
dfn parser working, still need to handle records and lists as nested contexts
2 parents e66b09d + 2e0b811 commit eea01c8

File tree

10 files changed

+302
-58
lines changed

10 files changed

+302
-58
lines changed

flopy4/converter.py

Whitespace-only changes.

flopy4/io/lark.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import numpy as np
2+
3+
4+
def parse_word(self, w):
5+
(w,) = w
6+
return str(w)
7+
8+
9+
def parse_string(self, s):
10+
return " ".join(s)
11+
12+
13+
def parse_int(self, i):
14+
(i,) = i
15+
return int(i)
16+
17+
18+
def parse_float(self, f):
19+
(f,) = f
20+
return float(f)
21+
22+
23+
def parse_array(self, a):
24+
(a,) = a
25+
return np.array(a)
File renamed without changes.

flopy4/mf6/io/converter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def make_converter():
2+
TODO
3+
pass

flopy4/mf6/io/parser.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55

66
MF6_GRAMMAR = r"""
77
// component
8-
component: _NL* (block _NL+)* _NL*
8+
component: _NL* (block _NL+)+ _NL*
99
1010
// block
11-
block: _paramblock | _listblock
12-
_paramblock: _BEGIN paramblock _NL params _END paramblock
11+
block: _dictblock | _listblock
12+
_dictblock: _BEGIN dictblock _NL dict _END dictblock
1313
_listblock: _BEGIN listblock _NL list _END listblock
14-
paramblock: PARAMBLOCK
14+
dictblock: DICTBLOCK
1515
listblock: LISTBLOCK [_blockindex]
1616
_blockindex: INT
1717
_BEGIN: "begin"i
1818
_END: "end"i
1919
20+
// dict
21+
dict.1: (param _NL)*
22+
23+
// list adapted from https://github.com/lark-parser/lark/blob/master/examples/composition/csv.lark
24+
// negative priority for records because the pattern is so indiscriminate
25+
list.-1: record*
26+
record.-1: _record+ _NL
27+
_record: scalar
28+
2029
// parameter
21-
params.1: (param _NL)*
2230
param: key | _pair
2331
_pair: key value
2432
key: PARAM
@@ -54,12 +62,6 @@
5462
factor: "FACTOR" NUMBER
5563
iprn: "IPRN" INT
5664
57-
// list adapted from https://github.com/lark-parser/lark/blob/master/examples/composition/csv.lark
58-
// negative priority for records because the pattern is so indiscriminate
59-
list.-1: record*
60-
record.-1: _record+ _NL
61-
_record: scalar
62-
6365
// newline
6466
_NL: /(\r?\n[\t ]*)+/
6567
@@ -80,7 +82,7 @@
8082

8183
def make_parser(
8284
params: Iterable[str],
83-
param_blocks: Iterable[str],
85+
dict_blocks: Iterable[str],
8486
list_blocks: Iterable[str],
8587
):
8688
"""
@@ -92,18 +94,18 @@ def make_parser(
9294
We specify blocks containing parameters separately from blocks
9395
that contain a list. These must be handled separately because
9496
the pattern for list elements (records) casts a wider net than
95-
the pattern for parameters, causing parameter blocks to parse
96-
as lists otherwise.
97+
the pattern for parameters, which can cause a dictionary block
98+
of named parameters to parse as a block with a list of records.
9799
98100
"""
99101
params = "|".join(['"' + n + '"i' for n in params])
100-
param_blocks = "|".join(['"' + n + '"i' for n in param_blocks])
102+
dict_blocks = "|".join(['"' + n + '"i' for n in dict_blocks])
101103
list_blocks = "|".join(['"' + n + '"i' for n in list_blocks])
102104
grammar = linesep.join(
103105
[
104106
MF6_GRAMMAR,
105107
f"PARAM: ({params})",
106-
f"PARAMBLOCK: ({param_blocks})",
108+
f"DICTBLOCK: ({dict_blocks})",
107109
f"LISTBLOCK: ({list_blocks})",
108110
]
109111
)

flopy4/mf6/io/spec/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__all__ = ["make_parser", "DFNTransformer"]
2+
3+
from flopy4.mf6.io.spec.parser import make_parser
4+
from flopy4.mf6.io.spec.transformer import DFNTransformer

flopy4/mf6/io/spec/parser.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from os import linesep
2+
3+
from lark import Lark
4+
5+
ATTRIBUTES = [
6+
"block",
7+
"name",
8+
"type",
9+
"reader",
10+
"optional",
11+
"true",
12+
"mf6internal",
13+
"longname",
14+
"description",
15+
"layered",
16+
"shape",
17+
"valid",
18+
"tagged",
19+
"in_record",
20+
"preserve_case",
21+
"default_value",
22+
"numeric_index",
23+
"deprecated",
24+
]
25+
26+
DFN_GRAMMAR = r"""
27+
// dfn
28+
dfn: _NL* (block _NL*)+ _NL*
29+
30+
// block
31+
block: _header parameter*
32+
_header: _hash _dashes _headtext _dashes _NL+
33+
_headtext: component subcompnt blockname
34+
component: _word
35+
subcompnt: _word
36+
blockname: _word
37+
38+
// parameter
39+
parameter.+1: _paramhead _NL (attribute _NL)*
40+
_paramhead: paramblock _NL paramname
41+
paramblock: "block" _word
42+
paramname: "name" _word
43+
44+
// attribute
45+
attribute.-1: key value
46+
key: ATTRIBUTE
47+
value: string
48+
49+
// string
50+
_word: /[a-zA-z0-9.;\(\)\-\,\\\/]+/
51+
string: _word+
52+
53+
// newline
54+
_NL: /(\r?\n[\t ]*)+/
55+
56+
// comment format
57+
_hash: /\#/
58+
_dashes: /[\-]+/
59+
60+
%import common.SH_COMMENT -> COMMENT
61+
%import common.WORD
62+
%import common.WS_INLINE
63+
64+
%ignore WS_INLINE
65+
"""
66+
"""
67+
EBNF description for the MODFLOW 6 definition language.
68+
"""
69+
70+
71+
def make_parser():
72+
"""
73+
Create a parser for the MODFLOW 6 definition language.
74+
"""
75+
76+
attributes = "|".join(['"' + n + '"i' for n in ATTRIBUTES])
77+
grammar = linesep.join([DFN_GRAMMAR, f"ATTRIBUTE: ({attributes})"])
78+
return Lark(grammar, start="dfn")

flopy4/mf6/io/spec/transformer.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from lark import Transformer
2+
3+
from flopy4.io.lark import parse_string
4+
5+
6+
class DFNTransformer(Transformer):
7+
"""
8+
Transforms a parse tree for the MODFLOW 6
9+
specification language into a nested AST
10+
suitable for generating an object model.
11+
12+
Notes
13+
-----
14+
Rather than a flat list of parameters for each component,
15+
which a subsequent step is responsible for turning into a
16+
an object hierarchy, we derive the hierarchical parameter
17+
structure from the DFN file and return a dict of blocks,
18+
each of which is a dict of parameters.
19+
20+
This can be fed to a Jinja template to generate component
21+
modules.
22+
"""
23+
24+
def key(self, k):
25+
(k,) = k
26+
return str(k).lower()
27+
28+
def value(self, v):
29+
(v,) = v
30+
return str(v)
31+
32+
def attribute(self, p):
33+
return str(p[0]), str(p[1])
34+
35+
def parameter(self, p):
36+
return dict(p[1:])
37+
38+
def paramname(self, n):
39+
(n,) = n
40+
return "name", str(n)
41+
42+
def paramblock(self, b):
43+
(b,) = b
44+
return "block", str(b)
45+
46+
def component(self, c):
47+
(c,) = c
48+
return "component", str(c)
49+
50+
def subcompnt(self, s):
51+
(s,) = s
52+
return "subcomponent", str(s)
53+
54+
def blockname(self, b):
55+
(b,) = b
56+
return "block", str(b)
57+
58+
def block(self, b):
59+
params = {p["name"]: p for p in b[6:]}
60+
return b[4][1], params
61+
62+
string = parse_string
63+
dfn = dict

flopy4/mf6/io/transformer.py

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
import numpy as np
44
from lark import Transformer
55

6+
from flopy4.io.lark import (
7+
parse_array,
8+
parse_float,
9+
parse_int,
10+
parse_string,
11+
parse_word,
12+
)
13+
614

715
class MF6Transformer(Transformer):
816
"""
@@ -25,29 +33,6 @@ def key(self, k):
2533
(k,) = k
2634
return str(k).lower()
2735

28-
def word(self, w):
29-
(w,) = w
30-
return str(w)
31-
32-
def path(self, p):
33-
_, p = p
34-
return Path(p)
35-
36-
def string(self, s):
37-
return " ".join(s)
38-
39-
def int(self, i):
40-
(i,) = i
41-
return int(i)
42-
43-
def float(self, f):
44-
(f,) = f
45-
return float(f)
46-
47-
def array(self, a):
48-
(a,) = a
49-
return a
50-
5136
def constantarray(self, a):
5237
# TODO factor out `ConstantArray`
5338
# array-like class from `MFArray`
@@ -65,27 +50,35 @@ def externalarray(self, a):
6550
# TODO
6651
pass
6752

68-
record = tuple
69-
list = list
53+
def path(self, p):
54+
_, p = p
55+
return Path(p)
7056

7157
def param(self, p):
7258
k = p[0]
7359
v = True if len(p) == 1 else p[1]
7460
return k, v
7561

76-
params = dict
77-
7862
def block(self, b):
7963
return tuple(b[:2])
8064

81-
def paramblock(self, bn):
82-
return str(bn[0]).lower()
65+
def dictblock(self, b):
66+
return str(b[0]).lower()
8367

84-
def listblock(self, bn):
85-
name = str(bn[0])
86-
if len(bn) == 2:
87-
index = int(bn[1])
68+
def listblock(self, b):
69+
name = str(b[0])
70+
if len(b) == 2:
71+
index = int(b[1])
8872
name = f"{name} {index}"
8973
return name.lower()
9074

75+
word = parse_word
76+
string = parse_string
77+
int = parse_int
78+
float = parse_float
79+
array = parse_array
80+
record = tuple
81+
list = list
82+
dict = dict
83+
params = dict
9184
component = dict

0 commit comments

Comments
 (0)