Skip to content

Commit

Permalink
parser cleanup/organization, tuple structuring working (but still clu…
Browse files Browse the repository at this point in the history
…nky)
  • Loading branch information
wpbonelli committed Sep 6, 2024
1 parent 97349dc commit 95fccd4
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 200 deletions.
83 changes: 0 additions & 83 deletions flopy4/lark/__init__.py

This file was deleted.

4 changes: 4 additions & 0 deletions flopy4/mf6/io/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__all__ = ["make_parser", "MF6Transformer"]

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

from lark import Lark

MF6_GRAMMAR = r"""
// component
component: _NL* (block _NL+)* _NL*
// block
block: _paramblock | _listblock
_paramblock: _BEGIN paramblock _NL params _END paramblock
_listblock: _BEGIN listblock _NL list _END listblock
paramblock: PARAMBLOCK
listblock: LISTBLOCK [_blockindex]
_blockindex: INT
_BEGIN: "begin"i
_END: "end"i
// parameter
params.1: (param _NL)*
param: key | _pair
_pair: key value
key: PARAM
?value: array
| list
| path
| string
| scalar
?scalar: int
| float
| word
// string
word: WORD
?string: word+
NON_SEPARATOR_STRING: /[a-zA-z.;\\\/]+/
// number
int: INT
float: FLOAT
// 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" float
internalarray: "INTERNAL" [factor] [iprn] (float* [_NL])*
externalarray: "OPEN/CLOSE" PATH [factor] ["binary"] [iprn]
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 ]*)+/
%import common.SH_COMMENT -> COMMENT
%import common.SIGNED_NUMBER -> NUMBER
%import common.SIGNED_INT -> INT
%import common.SIGNED_FLOAT -> FLOAT
%import common.WORD
%import common.WS_INLINE
%ignore COMMENT
%ignore WS_INLINE
"""
"""
EBNF description for the MODFLOW 6 input language.
"""


def make_parser(
params: Iterable[str],
param_blocks: Iterable[str],
list_blocks: Iterable[str],
):
"""
Create a parser for the MODFLOW 6 input language with the given
parameter and block specification.
Notes
-----
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.
"""
params = "|".join(['"' + n + '"i' for n in params])
param_blocks = "|".join(['"' + n + '"i' for n in param_blocks])
list_blocks = "|".join(['"' + n + '"i' for n in list_blocks])
grammar = linesep.join(
[
MF6_GRAMMAR,
f"PARAM: ({params})",
f"PARAMBLOCK: ({param_blocks})",
f"LISTBLOCK: ({list_blocks})",
]
)
return Lark(grammar, start="component")
91 changes: 91 additions & 0 deletions flopy4/mf6/io/transformer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from pathlib import Path

import numpy as np
from lark import Transformer


class MF6Transformer(Transformer):
"""
Transforms a parse tree for the MODFLOW 6 input language
into a nested dictionary AST suitable for structuring to
a strongly-typed input data model.
Notes
-----
Each function represents a node in the tree. Its argument
is a list of its children. Nodes are processed bottom-up,
so non-leaf functions can assume they will get a list of
primitives which are already in the right representation.
See https://lark-parser.readthedocs.io/en/stable/visitors.html#transformer
for more info.
"""

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`
# with deferred shape and use it
pass

def internalarray(self, a):
factor = a[0]
array = np.array(a[2:])
if factor is not None:
array *= factor
return array

def externalarray(self, a):
# TODO
pass

record = tuple
list = list

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 listblock(self, bn):
name = str(bn[0])
if len(bn) == 2:
index = int(bn[1])
name = f"{name} {index}"
return name.lower()

component = dict
Loading

0 comments on commit 95fccd4

Please sign in to comment.