Skip to content
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
13 changes: 8 additions & 5 deletions flopy4/mf6/codec/reader/grammar/basic.lark
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
start: [_NL*] (block [_NL*])*
block: "begin"i block_name _NL _list "end"i block_name [_NL+]
block_name: CNAME [NUMBER]
block_name: CNAME [TOKEN]
_list: line*
line: item* _NL+
item: word | NUMBER
word: /[a-zA-Z0-9'().\/:;<=>?@_~+\[\\-]+/
line: TOKEN* _NL+
// Every whitespace-delimited token lexes as one terminal, and the
// transformer decides whether it's a number. Separate NUMBER and word
// terminals overlap, and the lexer takes the first that matches rather
// than the longest, so e.g. an ISO datetime `1997-07-16T19:20:30` lexed as
// `1997` and `-07-16T19:20:30`.
TOKEN: /[a-zA-Z0-9'().\/:;<=>?@_~+\[\\-]+/
COMMA: ","

%import common.NEWLINE -> _NL
%import common.WS_INLINE
%import common.CNAME
%import common.WORD
%import common.NUMBER
%import common.INT
%import common.SH_COMMENT
%import common._STRING_INNER
Expand Down
13 changes: 9 additions & 4 deletions flopy4/mf6/codec/reader/grammar/typed.lark
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,19 @@ open_close_redirect: "open/close"i filename [_remark] _NL
%import common.WS_INLINE
%import common.CNAME
%import common.WORD
%import common.NUMBER
%import common.INT
%import common.SH_COMMENT
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.SIGNED_INT

COMMA: ","
%ignore WS_INLINE
%ignore SH_COMMENT
%ignore COMMA

// common's number terminals, but only as a whole token. Otherwise a token
// that starts like a number lexes as a number followed by a word, e.g. an
// ISO datetime `1997-07-16T19:20:30` as `1997`, `-07`, `-16` and
// `T19:20:30`.
INT: /\d+(?![^\s,#])/
SIGNED_INT: /[+-]\d+(?![^\s,#])/
NUMBER: /(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?(?![^\s,#])/
SIGNED_NUMBER: /[+-](\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?(?![^\s,#])/
17 changes: 9 additions & 8 deletions flopy4/mf6/codec/reader/transformer/basic.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import re
from typing import Any

from lark import Token, Transformer

from flopy4.utils import parse_number

# A whole token that's a number, allowing Fortran `D` exponents (`1D-5`).
_NUMBER = re.compile(r"[+-]?(\d+(\.\d*)?|\.\d+)([eEdD][+-]?\d+)?")


class BasicTransformer(Transformer):
"""
Expand Down Expand Up @@ -41,14 +45,11 @@ def _list(self, items: list[Any]) -> list[Any]:
def line(self, items: list[Any]) -> list[Any]:
return items

def item(self, items: list[Any]) -> str | float | int:
return items[0]

def word(self, items: list[Token]) -> str:
return str(items[0])

def NUMBER(self, token: Token) -> int | float:
return parse_number(str(token))
def TOKEN(self, token: Token) -> str | int | float:
value = str(token)
if _NUMBER.fullmatch(value):
return parse_number(value.replace("d", "e").replace("D", "E"))
return value

def CNAME(self, token: Token) -> str:
return str(token)
Expand Down
19 changes: 19 additions & 0 deletions test/mf6/test_mf6_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ def test_loads_dis_generic_simple():
assert result["griddata"] == [["delr"], ["constant", 100.0], ["delc"], ["constant", 100.0]]


def test_loads_number_only_as_whole_token():
mf6_input = """
BEGIN options
start_date_time 1997-07-16T19:20:30.45+01:00
ts6 filein 1model.ts
x 1,2.5 .5 7. 1e5 3 # comment
y -5 +2 -1.5e3 1D-5 8.2d-4
END options
"""

result = loads(mf6_input)
assert result["options"] == [
["start_date_time", "1997-07-16T19:20:30.45+01:00"],
["ts6", "filein", "1model.ts"],
["x", 1, 2.5, 0.5, 7.0, 1e5, 3],
["y", -5, 2, -1500.0, 1e-5, 8.2e-4],
]


def test_dumps_ic():
from flopy4.mf6.gwf import Dis, Gwf, Ic

Expand Down
42 changes: 17 additions & 25 deletions test/mf6/test_mf6_io_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,6 @@
from .test_mf6_load_all_models import KNOWN_PASSING

XFAIL = {
# the basic grammar splits an ISO datetime (TDIS `START_DATE_TIME`) into
# a number and a word, so TDIS fails to write
"tokenizer splits ISO datetimes": {
"mf6/test/test001a_Tharmonic",
"mf6/test/test001a_Tharmonic_tabs",
"mf6/test/test001h_drn_list4",
"mf6/test/test001h_evt_array1",
"mf6/test/test001h_evt_array2",
"mf6/test/test001h_evt_array3",
"mf6/test/test001h_evt_array4",
"mf6/test/test001h_evt_list1",
"mf6/test/test001h_evt_list2",
"mf6/test/test001h_evt_list3",
"mf6/test/test001h_evt_list4",
"mf6/test/test001h_rch_array1",
"mf6/test/test001h_rch_array2",
"mf6/test/test001h_rch_array3",
"mf6/test/test001h_rch_array4",
"mf6/test/test001h_rch_list1",
"mf6/test/test001h_rch_list2",
"mf6/test/test001h_rch_list3",
"mf6/test/test001h_rch_list4",
"mf6/test/test001i_gwf-gwf",
"mf6/test/test001i_multilayer",
},
# array values are written with 9 significant digits
"writer rounds floats": {
"mf6/test/test033_wtdecay",
Expand Down Expand Up @@ -86,6 +61,22 @@
# `AUXILIARY <one name>` loads as a str, not a list, and egress drops it
"ingress loads a single AUXILIARY name as a str": {
"mf6/test/test001a_Tharmonic_extlist",
"mf6/test/test001h_evt_array1",
"mf6/test/test001h_evt_array2",
"mf6/test/test001h_evt_array3",
"mf6/test/test001h_evt_array4",
"mf6/test/test001h_evt_list1",
"mf6/test/test001h_evt_list2",
"mf6/test/test001h_evt_list3",
"mf6/test/test001h_evt_list4",
"mf6/test/test001h_rch_array1",
"mf6/test/test001h_rch_array2",
"mf6/test/test001h_rch_array3",
"mf6/test/test001h_rch_array4",
"mf6/test/test001h_rch_list1",
"mf6/test/test001h_rch_list2",
"mf6/test/test001h_rch_list3",
"mf6/test/test001h_rch_list4",
"mf6/test/test005_advgw_tidal",
"mf6/test/test201_gwtbuy-henryCHD",
"mf6/test/test202_gwtbuy-henryCHDm",
Expand All @@ -96,6 +87,7 @@
# a TIMEARRAYSERIES-sourced period array is dropped on load (see
# test_tas_period_array_kept)
"ingress drops TAS period arrays": {
"mf6/test/test001h_rch_array3",
"mf6/test/test027_TimeseriesTest",
"mf6/test/test027_TimeseriesTest_idomain",
},
Expand Down
20 changes: 20 additions & 0 deletions test/mf6/test_mf6_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,3 +782,23 @@ def test_typed_grammar_repeating_block_double_header(tmp_path):
result = transformer.transform(parser.parse("BEGIN TIME 1.5\n X CONSTANT 1.0\nEND TIME\n"))

assert 1.5 in result["time"]


def test_typed_loads_filename_with_leading_digit(dfn_path):
from flopy4.mf6.codec.reader import loads_typed

result = loads_typed(
"BEGIN options\n ts6 filein 1model.ts\nEND options\n", "gwf-chd", dfn_path=dfn_path
)
assert result["options"]["ts_filerecord"]["ts6_filename"] == "1model.ts"


def test_typed_loads_iso_datetime(dfn_path):
from flopy4.mf6.codec.reader import loads_typed

result = loads_typed(
"BEGIN options\n start_date_time 1997-07-16T19:20:30.45+01:00\nEND options\n",
"sim-tdis",
dfn_path=dfn_path,
)
assert result["options"]["start_date_time"] == "1997-07-16T19:20:30.45+01:00"
Loading