Skip to content

Commit ee01a00

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

File tree

3 files changed

+79
-42
lines changed

3 files changed

+79
-42
lines changed

src/foamlib/_files/_parsing/_parser.py

Lines changed: 60 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,62 @@ 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:
544+
return contents[pos : pos + 1].decode("ascii"), pos + 1
545+
546+
if first_ok and (first != ord(b"#") or second != ord(b"{")):
531547
end = pos + 1
532548
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
549+
char: int = second # ty: ignore[invalid-assignment]
550+
with contextlib.suppress(IndexError):
551+
while True:
552+
if (depth == 0 and _IS_TOKEN_CONTINUATION[char]) or (
553+
depth > 0 and char not in b";(){}[]"
554+
):
555+
end += 1
556+
elif char == ord(b"("):
557+
depth += 1
558+
end += 1
559+
elif char == ord(b")") and depth > 0:
560+
depth -= 1
561+
end += 1
562+
else:
563+
break
564+
char = contents[end]
548565

549566
if depth != 0:
550567
raise FoamFileDecodeError(contents, pos, expected=")")
551568

552569
return contents[pos:end].decode("ascii"), end
553570

554-
if contents[pos : pos + 1] == b'"':
571+
if first == ord(b'"'):
555572
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
573+
with contextlib.suppress(IndexError):
574+
while True:
575+
char = contents[end]
576+
if char == ord(b"\\"):
577+
end += 2
578+
elif char == ord(b'"'):
579+
return contents[pos : end + 1].decode(), end + 1
580+
else:
581+
end += 1
582+
569583
raise FoamFileDecodeError(contents, pos, expected="end of quoted string")
570584

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

573592

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)