Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dfn parser working #33

Merged
merged 1 commit into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed flopy4/converter.py
Empty file.
25 changes: 25 additions & 0 deletions flopy4/io/lark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import numpy as np


def parse_word(self, w):
(w,) = w
return str(w)


def parse_string(self, s):
return " ".join(s)


def parse_int(self, i):
(i,) = i
return int(i)


def parse_float(self, f):
(f,) = f
return float(f)


def parse_array(self, a):
(a,) = a
return np.array(a)
File renamed without changes.
3 changes: 3 additions & 0 deletions flopy4/mf6/io/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def make_converter():
TODO
pass
34 changes: 18 additions & 16 deletions flopy4/mf6/io/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@

MF6_GRAMMAR = r"""
// component
component: _NL* (block _NL+)* _NL*
component: _NL* (block _NL+)+ _NL*

// block
block: _paramblock | _listblock
_paramblock: _BEGIN paramblock _NL params _END paramblock
block: _dictblock | _listblock
_dictblock: _BEGIN dictblock _NL dict _END dictblock
_listblock: _BEGIN listblock _NL list _END listblock
paramblock: PARAMBLOCK
dictblock: DICTBLOCK
listblock: LISTBLOCK [_blockindex]
_blockindex: INT
_BEGIN: "begin"i
_END: "end"i

// dict
dict.1: (param _NL)*

// list adapted from https://github.com/lark-parser/lark/blob/master/examples/composition/csv.lark
// negative priority for records because the pattern is so indiscriminate
list.-1: record*
record.-1: _record+ _NL
_record: scalar

// parameter
params.1: (param _NL)*
param: key | _pair
_pair: key value
key: PARAM
Expand Down Expand Up @@ -54,12 +62,6 @@
factor: "FACTOR" NUMBER
iprn: "IPRN" INT

// list adapted from https://github.com/lark-parser/lark/blob/master/examples/composition/csv.lark
// negative priority for records because the pattern is so indiscriminate
list.-1: record*
record.-1: _record+ _NL
_record: scalar

// newline
_NL: /(\r?\n[\t ]*)+/

Expand All @@ -80,7 +82,7 @@

def make_parser(
params: Iterable[str],
param_blocks: Iterable[str],
dict_blocks: Iterable[str],
list_blocks: Iterable[str],
):
"""
Expand All @@ -92,18 +94,18 @@ def make_parser(
We specify blocks containing parameters separately from blocks
that contain a list. These must be handled separately because
the pattern for list elements (records) casts a wider net than
the pattern for parameters, causing parameter blocks to parse
as lists otherwise.
the pattern for parameters, which can cause a dictionary block
of named parameters to parse as a block with a list of records.

"""
params = "|".join(['"' + n + '"i' for n in params])
param_blocks = "|".join(['"' + n + '"i' for n in param_blocks])
dict_blocks = "|".join(['"' + n + '"i' for n in dict_blocks])
list_blocks = "|".join(['"' + n + '"i' for n in list_blocks])
grammar = linesep.join(
[
MF6_GRAMMAR,
f"PARAM: ({params})",
f"PARAMBLOCK: ({param_blocks})",
f"DICTBLOCK: ({dict_blocks})",
f"LISTBLOCK: ({list_blocks})",
]
)
Expand Down
4 changes: 4 additions & 0 deletions flopy4/mf6/io/spec/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__all__ = ["make_parser", "DFNTransformer"]

from flopy4.mf6.io.spec.parser import make_parser
from flopy4.mf6.io.spec.transformer import DFNTransformer
78 changes: 78 additions & 0 deletions flopy4/mf6/io/spec/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from os import linesep

from lark import Lark

ATTRIBUTES = [
"block",
"name",
"type",
"reader",
"optional",
"true",
"mf6internal",
"longname",
"description",
"layered",
"shape",
"valid",
"tagged",
"in_record",
"preserve_case",
"default_value",
"numeric_index",
"deprecated",
]

DFN_GRAMMAR = r"""
// dfn
dfn: _NL* (block _NL*)+ _NL*

// block
block: _header parameter*
_header: _hash _dashes _headtext _dashes _NL+
_headtext: component subcompnt blockname
component: _word
subcompnt: _word
blockname: _word

// parameter
parameter.+1: _paramhead _NL (attribute _NL)*
_paramhead: paramblock _NL paramname
paramblock: "block" _word
paramname: "name" _word

// attribute
attribute.-1: key value
key: ATTRIBUTE
value: string

// string
_word: /[a-zA-z0-9.;\(\)\-\,\\\/]+/
string: _word+

// newline
_NL: /(\r?\n[\t ]*)+/

// comment format
_hash: /\#/
_dashes: /[\-]+/

%import common.SH_COMMENT -> COMMENT
%import common.WORD
%import common.WS_INLINE

%ignore WS_INLINE
"""
"""
EBNF description for the MODFLOW 6 definition language.
"""


def make_parser():
"""
Create a parser for the MODFLOW 6 definition language.
"""

attributes = "|".join(['"' + n + '"i' for n in ATTRIBUTES])
grammar = linesep.join([DFN_GRAMMAR, f"ATTRIBUTE: ({attributes})"])
return Lark(grammar, start="dfn")
63 changes: 63 additions & 0 deletions flopy4/mf6/io/spec/transformer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from lark import Transformer

from flopy4.io.lark import parse_string


class DFNTransformer(Transformer):
"""
Transforms a parse tree for the MODFLOW 6
specification language into a nested AST
suitable for generating an object model.

Notes
-----
Rather than a flat list of parameters for each component,
which a subsequent step is responsible for turning into a
an object hierarchy, we derive the hierarchical parameter
structure from the DFN file and return a dict of blocks,
each of which is a dict of parameters.

This can be fed to a Jinja template to generate component
modules.
"""

def key(self, k):
(k,) = k
return str(k).lower()

def value(self, v):
(v,) = v
return str(v)

def attribute(self, p):
return str(p[0]), str(p[1])

def parameter(self, p):
return dict(p[1:])

def paramname(self, n):
(n,) = n
return "name", str(n)

def paramblock(self, b):
(b,) = b
return "block", str(b)

def component(self, c):
(c,) = c
return "component", str(c)

def subcompnt(self, s):
(s,) = s
return "subcomponent", str(s)

def blockname(self, b):
(b,) = b
return "block", str(b)

def block(self, b):
params = {p["name"]: p for p in b[6:]}
return b[4][1], params

string = parse_string
dfn = dict
59 changes: 26 additions & 33 deletions flopy4/mf6/io/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
import numpy as np
from lark import Transformer

from flopy4.io.lark import (
parse_array,
parse_float,
parse_int,
parse_string,
parse_word,
)


class MF6Transformer(Transformer):
"""
Expand All @@ -25,29 +33,6 @@ def key(self, k):
(k,) = k
return str(k).lower()

def word(self, w):
(w,) = w
return str(w)

def path(self, p):
_, p = p
return Path(p)

def string(self, s):
return " ".join(s)

def int(self, i):
(i,) = i
return int(i)

def float(self, f):
(f,) = f
return float(f)

def array(self, a):
(a,) = a
return a

def constantarray(self, a):
# TODO factor out `ConstantArray`
# array-like class from `MFArray`
Expand All @@ -65,27 +50,35 @@ def externalarray(self, a):
# TODO
pass

record = tuple
list = list
def path(self, p):
_, p = p
return Path(p)

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

params = dict

def block(self, b):
return tuple(b[:2])

def paramblock(self, bn):
return str(bn[0]).lower()
def dictblock(self, b):
return str(b[0]).lower()

def listblock(self, bn):
name = str(bn[0])
if len(bn) == 2:
index = int(bn[1])
def listblock(self, b):
name = str(b[0])
if len(b) == 2:
index = int(b[1])
name = f"{name} {index}"
return name.lower()

word = parse_word
string = parse_string
int = parse_int
float = parse_float
array = parse_array
record = tuple
list = list
dict = dict
params = dict
component = dict
Loading
Loading