diff --git a/docs/c_parser/c_parser_cli_workflow.md b/docs/c_parser/c_parser_cli_workflow.md index d0086a9ca..8a9811532 100644 --- a/docs/c_parser/c_parser_cli_workflow.md +++ b/docs/c_parser/c_parser_cli_workflow.md @@ -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 diff --git a/docs/fortran/fortran_parser.md b/docs/fortran/fortran_parser.md index a02b29ad9..7a03b2046 100644 --- a/docs/fortran/fortran_parser.md +++ b/docs/fortran/fortran_parser.md @@ -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. diff --git a/fortran_parser/parser.py b/fortran_parser/parser.py index 08e4f11b6..e2500b581 100644 --- a/fortran_parser/parser.py +++ b/fortran_parser/parser.py @@ -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]] @@ -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] = [] @@ -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", @@ -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, @@ -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, @@ -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, @@ -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")) diff --git a/tests/parser/c/test_c_functions.py b/tests/parser/c/test_c_functions.py index 4fcb619f2..98f5d8932 100644 --- a/tests/parser/c/test_c_functions.py +++ b/tests/parser/c/test_c_functions.py @@ -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", ], diff --git a/tests/parser/test_cli.py b/tests/parser/test_cli.py index d1bb95995..1a3193098 100644 --- a/tests/parser/test_cli.py +++ b/tests/parser/test_cli.py @@ -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(): diff --git a/tests/parser/test_error_handling.py b/tests/parser/test_error_handling.py index 1689149f5..68f621586 100644 --- a/tests/parser/test_error_handling.py +++ b/tests/parser/test_error_handling.py @@ -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() @@ -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)