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
2 changes: 1 addition & 1 deletion docs/c_parser/c_parser_cli_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ unknown-suffix source inputs require an explicit frontend selection. A known C
path explicitly passed with `--language fortran` is rejected before parsing,
so it cannot silently produce an empty Fortran interface. The parser also
rejects unmistakable Fortran unit or declaration syntax found in C input, and
the Fortran parser rejects unmistakable C declarations outside execution
the Fortran parser rejects unsupported non-Fortran syntax outside execution
regions that are intentionally not modeled.

The C parser output differs from Fortran parser output by using C-specific
Expand Down
2 changes: 1 addition & 1 deletion docs/fortran/fortran_parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ python -m x2py path/to/fortran_src --language fortran --parse
Fortran directories are recursively scanned for `.f`, `.for`, `.ftn`, `.f90`,
`.f95`, `.f03`, `.f08`.

The Fortran frontend rejects unmistakable C declaration syntax before
The Fortran frontend rejects unsupported non-Fortran syntax before
wrapper-focused parsing when it appears outside executable procedure/program
bodies, which are intentionally not represented in the extracted interface.

Expand Down
83 changes: 6 additions & 77 deletions fortran_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,37 +118,6 @@
"unsupported_procedure_pointer",
"unsupported_c_ptr",
)
_FOREIGN_C_DECLARATION = re.compile(
r"""
^\s*
(?:(?:typedef|extern|static|register|inline|const|volatile|restrict|_Atomic)\s+)*
(?:
(?:signed|unsigned)(?:\s+(?:char|short|int|long)(?:\s+long)?)?
|(?:void|char|short|int|long|float|double)(?:\s+(?:int|long|_Complex))?
|(?:struct|union|enum)\s+[A-Za-z_]\w*
)
\s+(?:\*+\s*)?[A-Za-z_]\w*\s*(?:\(|\[|=|;|\{)
""",
re.IGNORECASE | re.VERBOSE,
)
_FOREIGN_C_ALIAS_DECLARATION = re.compile(
r"""
^\s*
(?!
(?:allocate|associate|backspace|block|call|case|close|continue|cycle|
data|deallocate|do|else|elseif|end|error|exit|external|format|
goto|go|if|implicit|include|inquire|intrinsic|nullify|open|
parameter|pause|print|read|return|rewind|save|select|stop|use|
wait|where|write|integer|real|complex|logical|character|double|
type|class|procedure)\b
)
(?:(?:typedef|extern|static|register|inline|const|volatile|restrict|_Atomic)\s+)*
[A-Za-z_]\w*\s+(?:\*+\s*)?[A-Za-z_]\w*
\s*(?:\([^;{}]*\)|\[[^\]]*\])?
\s*(?:=[^;{}]*)?[;{]\s*$
""",
re.IGNORECASE | re.VERBOSE,
)


_PreprocessedLines = list[tuple[str, int | None, str | None]]
Expand Down Expand Up @@ -1169,7 +1138,12 @@ def _helper_validate_unit_headers(self, lines: _PreprocessedLines, filename: str
)

def _helper_validate_file_scope_unparsed_lines(self, lines: _PreprocessedLines, filename: str | None) -> None:
"""Reject foreign C declarations not owned by a Fortran source unit."""
"""Reject any non-Fortran syntax outside recognized unit bodies.

This guard is intentionally language-agnostic: lines that are neither
valid file-scope Fortran constructs nor part of a recognized unit are
rejected via the generic invalid-syntax diagnostic path.
"""
index = 0
pp_condition_stack: list[tuple[int, int]] = []
pp_active_stack: list[bool] = []
Expand Down Expand Up @@ -1203,12 +1177,6 @@ def _helper_validate_file_scope_unparsed_lines(self, lines: _PreprocessedLines,
if self._is_executable_statement_start(stripped):
index += 1
continue
self._raise_if_foreign_c_syntax_line(
stripped,
filename=filename,
lineno=lineno,
source_line=source_line,
)
self._raise_invalid_fortran_syntax_line(
stripped,
context="file scope",
Expand All @@ -1232,27 +1200,6 @@ def _is_allowed_unparsed_file_scope_line(line: str) -> bool:
or FortranParser._is_openmp_directive(stripped)
)

@staticmethod
def _raise_if_foreign_c_syntax_line(
line: str,
*,
filename: str | None,
lineno: int | None,
source_line: str | None,
) -> None:
if not (
_FOREIGN_C_DECLARATION.match(line)
or _FOREIGN_C_ALIAS_DECLARATION.match(line)
):
return
raise FortranParseError(
"C declaration syntax is not valid Fortran input; parse C source with --language c.",
filename=filename,
line_number=lineno,
source_line=source_line,
code="PARSE_FOREIGN_C_SYNTAX",
)

@staticmethod
def _raise_invalid_fortran_syntax_line(
line: str,
Expand All @@ -1262,12 +1209,6 @@ def _raise_invalid_fortran_syntax_line(
lineno: int | None,
source_line: str | None,
) -> None:
FortranParser._raise_if_foreign_c_syntax_line(
line,
filename=filename,
lineno=lineno,
source_line=source_line,
)
raise FortranParseError(
f"Invalid Fortran syntax in {context}: {line.strip()}",
filename=filename,
Expand Down Expand Up @@ -2183,12 +2124,6 @@ def _helper_visit_spec_part(
continue
if stripped.startswith("#"):
continue
self._raise_if_foreign_c_syntax_line(
stripped,
filename=filename,
lineno=lineno,
source_line=source_line,
)
if scope.kind == "procedure":
self._helper_visit_procedure_spec_line(
stripped,
Expand Down Expand Up @@ -2464,12 +2399,6 @@ def _parse_derived_type_contains_line(
lineno: int | None = None,
source_line: str | None = None,
) -> None:
self._raise_if_foreign_c_syntax_line(
line,
filename=filename,
lineno=lineno,
source_line=source_line,
)
proc_binding = _REGEX["procedure_binding"].match(line)
if proc_binding:
binding_names = split_csv(proc_binding.group("names"))
Expand Down
4 changes: 2 additions & 2 deletions tests/parser/c/test_c_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ def test_old_style_knr_function_definition_raises_unsupported_diagnostic():
@pytest.mark.parametrize(
"source",
[
"subroutine solve()\nend subroutine solve\n",
"integer function answer()\nend function answer\n",
"def solve():\n return 0\n",
"lambda x: x\n",
"int add(int a, int b);\ninteger :: state;\n",
"int add(int a, int b);\ntype(c_ptr) :: handle;\n",
],
Expand Down
4 changes: 2 additions & 2 deletions tests/parser/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,8 @@ def test_cli_fortran_rejects_embedded_c_declaration_outside_execution_body(tmp_p
)

assert result.returncode == 1
assert "PARSE_FOREIGN_C_SYNTAX" in result.stderr
assert "C declaration syntax is not valid Fortran input" in result.stderr
assert "PARSE001" in result.stderr
assert "Unknown or unsupported datatype declaration" in result.stderr


def test_cli_parse_shows_module_derived_types_and_derived_arg_kinds():
Expand Down
13 changes: 8 additions & 5 deletions tests/parser/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,14 +760,17 @@ def test_slicer_reports_missing_end_unit():
""",
],
)
def test_fortran_parser_rejects_foreign_c_declarations_outside_execution_bodies(code):
with pytest.raises(FortranParseError, match="C declaration syntax is not valid Fortran input") as exc_info:
def test_fortran_parser_rejects_invalid_non_fortran_syntax_outside_execution_bodies(code):
with pytest.raises(
FortranParseError,
match=r"Invalid Fortran syntax|Unknown or unsupported datatype declaration",
) as exc_info:
parse_fortran_file(code, filename="mixed.f90")

assert exc_info.value.code == "PARSE_FOREIGN_C_SYNTAX"
assert exc_info.value.code in {"PARSE_INVALID_SYNTAX", "PARSE001"}


def test_fortran_parser_ignores_foreign_c_declarations_after_execution_boundary():
def test_fortran_parser_ignores_non_fortran_syntax_after_execution_boundary():
parsed = parse_fortran_file(
"""
subroutine mixed_body()
Expand Down Expand Up @@ -818,7 +821,7 @@ def test_fortran_parser_ignores_invalid_syntax_after_execution_boundary():
assert parsed.procedures[0].name == "ignored_body"


def test_foreign_c_check_preserves_valid_semicolon_separated_fortran_statements():
def test_invalid_syntax_guard_preserves_valid_semicolon_separated_fortran_statements():
parsed = parse_fortran_file(
"""
subroutine valid_body(x)
Expand Down
Loading