Skip to content

Commit 27be8e5

Browse files
committed
Add support for coded source blocks
1 parent e9b5874 commit 27be8e5

File tree

3 files changed

+81
-42
lines changed

3 files changed

+81
-42
lines changed

src/foamlib/_files/_parsing/_parser.py

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@
3838
)
3939
_T = TypeVar("_T", FileDict, Data, StandaloneData, str)
4040

41+
_WHITESPACE = b" \n\t\r\f\v"
42+
_WHITESPACE_NO_NEWLINE = b" \t\r\f\v"
43+
44+
_IS_TOKEN_START = [False] * 256
45+
for c in b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_#$":
46+
_IS_TOKEN_START[c] = True
47+
48+
_IS_TOKEN_CONTINUATION = _IS_TOKEN_START.copy()
49+
for c in b"0123456789._<>#$:+-*/|^%&=!":
50+
_IS_TOKEN_CONTINUATION[c] = True
51+
4152

4253
class ParseError(Exception):
4354
def __init__(self, contents: bytes | bytearray, pos: int, *, expected: str) -> None:
@@ -50,18 +61,12 @@ def make_fatal(self) -> FoamFileDecodeError:
5061
return FoamFileDecodeError(self._contents, self.pos, expected=self._expected)
5162

5263

53-
_WHITESPACE = b" \n\t\r\f\v"
54-
_WHITESPACE_NO_NEWLINE = b" \t\r\f\v"
55-
# Characters that can continue a token (used for boundary checking)
56-
_TOKEN_CONTINUATION_CHARS = b"._<>#$:+-*/|^%&=!"
57-
58-
5964
def _is_token_boundary(contents: bytes | bytearray, pos: int) -> bool:
60-
"""Check if position is at a token boundary (not followed by a token continuation character)."""
61-
if pos >= len(contents):
65+
try:
66+
char = contents[pos]
67+
except IndexError:
6268
return True
63-
next_char = contents[pos : pos + 1]
64-
return not (next_char.isalnum() or next_char in _TOKEN_CONTINUATION_CHARS)
69+
return not _IS_TOKEN_CONTINUATION[char]
6570

6671

6772
def _skip(
@@ -526,48 +531,64 @@ def _parse_field(contents: bytes | bytearray, pos: int) -> tuple[Field, int]:
526531

527532

528533
def _parse_token(contents: bytes | bytearray, pos: int) -> tuple[str, int]:
529-
c = contents[pos : pos + 1]
530-
if c.isalpha() or (c and c in b"_#$"):
534+
try:
535+
first = contents[pos]
536+
except IndexError:
537+
raise ParseError(contents, pos, expected="token") from None
538+
try:
539+
second = contents[pos + 1]
540+
except IndexError:
541+
second = None
542+
543+
if ((first_ok := _IS_TOKEN_START[first]) and second is None) or (
544+
first != ord(b"#") and second == ord(b"{")
545+
):
546+
return contents[pos : pos + 1].decode("ascii"), pos + 1
547+
548+
if first_ok:
531549
end = pos + 1
532550
depth = 0
533-
while end < len(contents):
534-
c = contents[end : end + 1]
535-
assert c
536-
if (depth == 0 and (c.isalnum() or c in _TOKEN_CONTINUATION_CHARS)) or (
537-
depth > 0 and c not in b";(){}[]"
538-
):
539-
end += 1
540-
elif c == b"(":
541-
depth += 1
542-
end += 1
543-
elif c == b")" and depth > 0:
544-
depth -= 1
545-
end += 1
546-
else:
547-
break
551+
char: int = second # ty: ignore[invalid-assignment]
552+
with contextlib.suppress(IndexError):
553+
while True:
554+
if (depth == 0 and _IS_TOKEN_CONTINUATION[char]) or (
555+
depth > 0 and char not in b";(){}[]"
556+
):
557+
end += 1
558+
elif char == ord(b"("):
559+
depth += 1
560+
end += 1
561+
elif char == ord(b")") and depth > 0:
562+
depth -= 1
563+
end += 1
564+
else:
565+
break
566+
char = contents[end]
548567

549568
if depth != 0:
550569
raise FoamFileDecodeError(contents, pos, expected=")")
551570

552571
return contents[pos:end].decode("ascii"), end
553572

554-
if contents[pos : pos + 1] == b'"':
573+
if first == ord(b'"'):
555574
end = pos + 1
556-
while end < len(contents):
557-
c = contents[end : end + 1]
558-
if c == b"\\":
559-
if end + 1 >= len(contents):
560-
raise FoamFileDecodeError(
561-
contents, pos, expected="end of quoted string"
562-
)
563-
end += 2
564-
elif c == b'"':
565-
end += 1
566-
return contents[pos:end].decode(), end
567-
else:
568-
end += 1
575+
with contextlib.suppress(IndexError):
576+
while True:
577+
char = contents[end]
578+
if char == ord(b"\\"):
579+
end += 2
580+
elif char == ord(b'"'):
581+
return contents[pos : end + 1].decode(), end + 1
582+
else:
583+
end += 1
584+
569585
raise FoamFileDecodeError(contents, pos, expected="end of quoted string")
570586

587+
if first == ord(b"#") and second == ord(b"{"):
588+
if (end := contents.find(b"#}", pos + 2)) == -1:
589+
raise FoamFileDecodeError(contents, len(contents), expected="#}")
590+
return contents[pos : end + 2].decode(), end + 2
591+
571592
raise ParseError(contents, pos, expected="token")
572593

573594

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from foamlib import FoamFile
2+
3+
4+
def test_issue_730() -> None:
5+
file = {}
6+
file["functions"] = {}
7+
file["functions"]["difference"] = {
8+
"type": "coded",
9+
"libs": ["utilityFunctionObjects"],
10+
"name": "writeMagU",
11+
"codeWrite": '#{\
12+
const volVectorField& U = mesh().lookupObject<volVectorField>("U");\
13+
mag(U)().write();\
14+
#}',
15+
}
16+
17+
contents = FoamFile.dumps(file)
18+
read = FoamFile.loads(contents)
19+
assert read == file

tests/test_files/test_parsing/test_intermediate.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ def test_directives_in_dict() -> None:
310310
""")
311311

312312

313-
@pytest.mark.xfail(reason="Not currently supported")
314313
def test_code() -> None:
315314
ParsedFile(b"""
316315
code_name

0 commit comments

Comments
 (0)