Skip to content

Commit 6fe7495

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

File tree

3 files changed

+83
-42
lines changed

3 files changed

+83
-42
lines changed

src/foamlib/_files/_parsing/_parser.py

Lines changed: 64 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,66 @@ 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 (
544+
(first_ok := _IS_TOKEN_START[first])
545+
and (second is None or second == ord(b"{"))
546+
and not (first == ord(b"#") and second == ord(b"{"))
547+
):
548+
return contents[pos : pos + 1].decode("ascii"), pos + 1
549+
550+
if first_ok:
531551
end = pos + 1
532552
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
553+
char: int = second # ty: ignore[invalid-assignment]
554+
with contextlib.suppress(IndexError):
555+
while True:
556+
if (depth == 0 and _IS_TOKEN_CONTINUATION[char]) or (
557+
depth > 0 and char not in b";(){}[]"
558+
):
559+
end += 1
560+
elif char == ord(b"("):
561+
depth += 1
562+
end += 1
563+
elif char == ord(b")") and depth > 0:
564+
depth -= 1
565+
end += 1
566+
else:
567+
break
568+
char = contents[end]
548569

549570
if depth != 0:
550571
raise FoamFileDecodeError(contents, pos, expected=")")
551572

552573
return contents[pos:end].decode("ascii"), end
553574

554-
if contents[pos : pos + 1] == b'"':
575+
if first == ord(b'"'):
555576
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
577+
with contextlib.suppress(IndexError):
578+
while True:
579+
char = contents[end]
580+
if char == ord(b"\\"):
581+
end += 2
582+
elif char == ord(b'"'):
583+
return contents[pos : end + 1].decode(), end + 1
584+
else:
585+
end += 1
586+
569587
raise FoamFileDecodeError(contents, pos, expected="end of quoted string")
570588

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

573596

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)