| title | Fortran Parser Reference |
|---|---|
| audience | developers |
| prerequisites | repository structure, parser architecture |
| related | adding-a-fortran-construct.md, repository-structure.md |
| status | maintained |
| publication | draft |
This document defines the currently supported parser subset, expected behavior, and practical usage from terminal and Python.
- Free-form Fortran:
.f90,.f95,.f03,.f08 - Fixed-form Fortran:
.f,.for,.ftn - Free/fixed comment stripping
- Continuation handling for both forms
subroutineheadersfunctionheaders- Header modifiers:
pure,elemental,recursive - Function
result(...)parsing (tolerant support forresults(...))
- Intrinsic types:
integer,real,complex,logical,character - Kind extraction from declaration specs (
kind=...) - Attribute extraction:
intent(in|out|inout)optionalvalueallocatablepointertarget
- Array extraction:
dimension(...)- variable-level shape syntax (
x(:),x(n))
- Module discovery
- Module variable extraction
- Shared specification-part parsing for module-like scopes (modules, submodules, programs, and block-data units), preserving original line numbers while skipping contained procedure bodies where they are not wrap-relevant
useextraction at module and procedure scope- Explicit
usesymbol mappings preserve importedsourcenames and localtargetnames for renamed imports - Propagation of module-level
useimports into contained procedures - Folder/project parsing with dependency-aware ordering
- Cross-file kind constant resolution (e.g., kinds modules)
- Cached compile-time expression resolution for local/module parameters, module/program variable shapes, and character lengths
type :: ... end typeand legacytype name ... end typediscovery- Parameterized derived-type headers such as
type :: buffer_type(k, n)and declarations such astype(buffer_type(real64, 4)) - Type attributes (e.g.,
abstract) - Inheritance (
extends(parent)) - Field extraction including shape/pointer/allocatable
- Type-bound procedures:
procedure ... :: ...bindings with attributes (e.g.pass(self),nopass)generic ... :: name => target1, target2
- Parser diagnostics report source-level parse errors and unsupported parser constructs.
- Parser JSON remains parse-only and does not contain wrapper-plan decisions or support diagnostics.
- Wrapper builds complete policy from semantic IR and validate the resulting wrapper plan. Unsupported contracts report the owning plan path and completed-policy diagnostic.
Supported public API:
parse_fortran_file(source_or_path, filename=None, encoding="utf-8") -> FortranFileparse_fortran_project(files, encoding="utf-8") -> FortranProject
prik/parsers/fortran/parser.py is now intentionally organized into clearly labeled
sections and carries embedded implementation guidance. Start with the thin public
wrappers at the bottom, then read the class from top to bottom:
- Regex/constants, parser-wide type aliases, private unit dataclasses, and the compile-time resolver
FortranParserinternals grouped by domain:- public parse entrypoints (
parse_file,parse_project). The supported module-level API remains the wrappers listed above. - source-unit visitors for files, modules, submodules, programs, procedures, interfaces, derived types, and block data
- recursive source-unit slicing (
header, specification part, execution part,contains) with original line numbers preserved on each slice - shared declaration parsing for module variables, program/block-data variables, procedure arguments/results, and derived-type fields
_helper_*methods for scoped parsing, expression resolution, same-level duplicate checks, and shared specification-part collection
- public parse entrypoints (
- Thin module-level convenience wrappers that delegate to a shared parser instance
Parser methods carry focused docstrings, with examples where a grammar visitor or lexical helper is easier to understand from a concrete call.
The Fortran parser is now packaged under prik.parsers.fortran rather than a
top-level parser package. The package includes its CLI module, lexer,
JSON-compatible parse models, project parser, type resolver, and utility
helpers. Public callers should use the stable top-level prik parser exports
or prik.parsers.fortran package imports.
This file is the single maintained Fortran parser reference. It replaces the older standalone implementation-reference document; parser feature inventory, testing workflow, and maintenance guard policy live here.
The implementation inventory is maintained across these surfaces:
prik/parsers/fortran/parser.pyowns source slicing, declaration extraction, diagnostics, project ordering, dependency resolution, and compile-time expression resolution.prik/parsers/fortran/models.pyowns parse-only dataclasses and JSON-compatible parser facts.prik/semantics/fortran2ir.pyowns conversion from parser facts to semantic IR, including kind mapping, compile-time specialization, storage contracts, projection metadata, and wrapper-planning inputs.tests/fortran/source_parsing/parsing/covers parser contracts, source-unit slicing, diagnostics, project behavior, and fixture regressions.tests/fortran/semantic_ir/semantics/covers semantic conversion, datatype precision mapping, wrapper planning,.pyiemission, and compile-time specialization.
parse_file is the central orchestration path. It first slices the source into
direct file-level units, then each class visitor parses only its own substring
and recursively slices direct children. This is the key parser design: each
Fortran grammar unit has a header, a specification region, optional execution
region, and optional contains region. The differences between modules,
programs, procedures, derived types, interfaces, and block data are expressed
by small visitor decisions and grammar flags rather than separate whole-file
parsing loops.
Nested unit boundaries and placement outside execution regions are checked even
when they are not exported as wrapper metadata. Internal procedures inside a
host procedure's contains block are structurally sliced, then their
declarations and bodies are skipped. Once an execution boundary is detected,
procedure bodies and standalone included execution fragments are intentionally
skipped. Procedure-local interface blocks are still visited enough to type
callback dummy arguments and to preserve interface metadata.
Small input:
module m
integer, parameter :: n = 4
contains
subroutine scale(x)
real, intent(inout) :: x(n)
end subroutine scale
end module mThe parser handles it in this order:
parse_filepreprocesses the source and calls_helper_slice_child_unitsat file scope. The result is oneModuleUnitcarrying the module name, lines, and source locations.- the shared
ClassVisitor._visitdispatcher selects_visit_ModuleUnit. _visit_ModuleUnitcreates a module_ParserScope, calls_helper_split_unit_parts, and sends only the module specification lines to_parse_specification_part._parse_specification_partuses the shared declaration backend:_helper_parse_declaration_lineparsesinteger, parameter :: n = 4, then_helper_push_declaration_to_scopeappends the resulting parameter variable toFortranModule.variables.- The module visitor recursively slices direct children from its substring.
It finds one procedure unit,
scale, and dispatches it to_visit_ProcedureUnit. _visit_ProcedureUnitcreates a procedure_ParserScope, splits the procedure into header/specification/execution/contains, and visits only the specification part. The same declaration backend parsesreal, intent(inout) :: x(n)and pushes the metadata into the procedure argument symbol table.
Scope is always an explicit argument to the shared helpers. That is the reason
two modules can each define type :: state without conflict, while two
same-level module m declarations or two same-level contained procedures with
the same name are rejected by _helper_validate_sibling_units.
End-name validation is strict for structural units whose names define exported scope boundaries, such as modules, submodules, programs, interfaces, and derived types. Procedure end-name mismatches are still tolerated while slicing third-party sources because some accepted fixture code contains copy/paste procedure end labels; the procedure is closed by unit kind so parsing can continue, and duplicate procedure names are validated at the sibling scope.
The only separate specification-line visitors are grammar-specific:
module-like units share _parse_module_like_spec_line, procedures use
_parse_procedure_spec_line for implicit, external, import, and
local parameter handling, and derived types use
_parse_type_spec_line for sequence, private, and type-bound
declaration rules. All three still call the same declaration parser/pusher for
actual declarations.
Most parser organization changes are structural, but behavior, model-schema, coverage, or fixture changes should be reflected in this reference.
Parameter constants expose both value and serialized symbolic_value when
available. value is reserved for a literal/evaluated result after
compile-time folding. If an initializer cannot be evaluated safely, such as
selected_real_kind(...), value is None and symbolic_value preserves the
original initializer for validation, debugging, downstream diagnostics, and
JSON consumers.
Procedure-local parameters may be folded into argument shapes during procedure
finalization. Module-level and use-associated parameters used in procedure
argument shapes are kept symbolic in the signature (x(n) remains ["n"])
and are treated as valid scope references for policy completion. Module/program
variable shapes and parameter values can be resolved through the compile-time
resolver when enough information is available.
Use the Fortran parser as the reference for any source language with nested program units, scoped declarations, and a later semantic handoff. The details are Fortran-specific, but the parser architecture is reusable.
Recommended frontend responsibilities:
- Keep one typed model layer for parse-only facts.
- Keep one parser orchestration class with thin public wrappers.
- Slice source into grammar units before parsing declarations.
- Pass scope explicitly into shared helpers rather than using global mutable parser state for symbol resolution.
- Parse only wrapper-relevant specification facts; skip executable bodies once they are outside the parser contract.
- Preserve source locations and original line numbers through preprocessing and recursive slicing.
- Emit parser diagnostics for malformed source, but leave wrappability policy to semantic policy completion.
The Fortran data flow is:
source path or source text
-> compiler/native include preprocessing
-> FortranParser.parse_file(...)
-> source-unit slices with original line numbers
-> scoped specification parsing
-> FortranFile parser facts
-> parse_fortran_project(...) dependency ordering and namespace resolution
-> semantics.fortran2ir conversion
-> policy completion, `.pyi`, and the implemented Fortran wrapper stages
The recursive parsing pattern is:
- Identify direct child units at the current grammar level.
- Split each child into header, specification part, execution part, and
containspart where that language construct allows them. - Parse declarations only from the specification part.
- Recurse only into direct children that are legal for the current unit kind.
- Validate sibling names and scope-local duplicate declarations.
- Finalize procedure arguments/results after local declarations and parameters are known.
- Resolve cross-file or imported compile-time facts only at project or semantic-conversion boundaries.
When adding another parser, keep these test layers separate:
- parser unit tests for grammar slicing and declarations;
- parser fixture tests for stable JSON/model output;
- parser error fixture tests for fatal diagnostic contracts;
- project tests for dependency ordering and cross-file resolution;
- CLI tests for frontend selection, stage dispatch, output files, and debug behavior;
- semantic conversion tests for parser-to-IR mapping;
.pyitests for generated and edited interface round trips.
Executable references:
- Fortran parser walkthrough:
tests/fortran/source_parsing/parsing/test_developer_tutorial.py - Procedure/type parsing:
tests/fortran/source_parsing/parsing/ - Scope and project behavior:
tests/fortran/modules/parsing/test_scope_handling.pyandtests/fortran/modules/parsing/test_project_scope_models.py - Fortran fixture workflow:
tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py - Shared CLI behavior:
tests/fortran/command_line_interface/pipeline/ - Fortran semantic handoff:
tests/fortran/semantic_ir/semantics/
python -m prik parse path/to/file.f90Recognizable Fortran files can omit --language. Directories require explicit
frontend selection:
python -m prik parse path/to/fortran_src --language fortranFortran directories are recursively scanned for .f, .for, .ftn, .f90,
.f95, .f03, .f08.
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.
The human-readable parse tree keeps scope variables compact by default as
vars=N. Add --show-vars to print the variables, or --print-limit N to
print only the first N items in each repeated section.
Input Fortran (tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90):
module m1
contains
subroutine add1(n, x)
integer, intent(in) :: n
real(kind=8), intent(inout), dimension(n) :: x
end subroutine add1
end module m1Command:
python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90Expected output:
File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90
Modules: 1
- module m1 (vars=0, uses=0)
Procedures: 1
- subroutine add1(n:integer[0], x:real(8)[1])
The same command with --show-vars uses the variable-expanded report path.
This fixture currently has no module variables to print, so the output remains
compact:
python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --show-varsFile: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90
Modules: 1
- module m1 (vars=0, uses=0)
Procedures: 1
- subroutine add1(n:integer[0], x:real(8)[1])
For large files:
python -m prik parse path/to/file.f90 --show-vars --print-limit 50--print-limit applies independently to modules, submodules, programs, block
data units, derived types, fields, procedures, and variables when variables are
shown. Counts such as Procedures: 80 and Variables: 657 still show the full
totals even when only the first N entries are printed.
Interpretation:
- Parsed entities are counted per file.
- Free procedures (outside modules) are shown in top-level
Procedures. - Module-contained procedures are nested under each module.
- Empty sections are omitted from the human-readable report.
More complex example:
Input Fortran (mixed_example.f90):
Command:
python -m prik mixed_example.f90File: mixed_example.f90
Procedures: 1
- subroutine driver(n:integer[0])
Modules: 2
- module math_ops (vars=1, uses=1)
Procedures: 2
- subroutine saxpy(n:integer[0], a:real[0], x:real[1], y:real[1])
- function dot(x:real[1], y:real[1])
- module io_ops (vars=0, uses=0)
Procedures: 1
- subroutine dump(v:real[1])
Print parser JSON:
python -m prik tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --jsonWrite parser JSON:
python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --json --out report.jsonExpected JSON layout:
- Top-level object keyed by input path
- Per-file payload with keys:
signaturestypesmodulessubmodulesprogramsblock_data
When prik parse --json applies compiler preprocessing, the per-file payload
also contains preprocessing_recipe. The CLI applies compiler preprocessing
for file-based parsing; compiler linemarkers remain accepted for provenance.
The recipe records the exact compiler executable or adapter, argv, include
paths, macro flags, standard, extra compiler arguments, working directory,
include graph, source mappings, diagnostics, and optional macro metadata used
to produce the parsed stdout stream.
Fortran CPP directives are handled by the configured compiler. Native Fortran
include "file.inc" statements are then expanded recursively by the
preprocessing layer before the single parser pass. Native INCLUDE is textual
insertion into the current scope; it is not a use import from a separately
compiled module. Include lookup is relative to the including file first, then
the configured include directories, duplicate textual inclusion is preserved,
and missing files or cycles produce INCLUDE_NOT_FOUND or INCLUDE_CYCLE
diagnostics.
use import shape:
- A renamed import such as
use list_input, delete_input => delete_input_listrecords both sides:
"uses": {
"list_input": [
{
"source": "delete_input_list",
"target": "delete_input"
}
]
}Parser output and semantic IR are separate stages. Run parser inspection with:
python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90Build a wrapper with the default wrapper stage. If the completed plan cannot lower a contract, the build reports the precise plan owner and blocker:
python -m prik tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90Parser JSON stays parse-only.
Semantic IR JSON uses the same output channels, but the per-file payload is the semantic model projection instead of raw parser output:
python -m prik semantics tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90Generated .pyi text is printed with:
python -m prik generate --pyi tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90When parsing fails, the CLI prints a compiler-style diagnostic to stderr and
exits with status code 1. By default this output is intended for end users: it
includes the source location, diagnostic code, message, source line, and caret
context, but it does not include a Python traceback.
Example command:
python -m prik tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90Example diagnostic shape:
tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90:1:1: error[PARSE_DUPLICATE_ARGUMENT]: Duplicate argument name 'x' in procedure 'dup'.
|
1 | subroutine dup(x, y, x)
| ^
ANSI color is enabled by default when available; no color flag is needed for
normal use. To disable color explicitly, pass --no-color or set the standard
NO_COLOR environment variable:
python -m prik bad.f90 --no-color
NO_COLOR=1 python -m prik bad.f90For parser development, use --debug to re-raise
FortranParseError and let Python print the full traceback showing where the
error was raised internally:
python -m prik bad.f90 --debugThe same developer mode can be enabled with the environment variable
FORTRAN_PARSER_DEBUG=1:
FORTRAN_PARSER_DEBUG=1 python -m prik bad.f90In debug mode, the traceback's final exception message also includes a
note: parser raised at ... line with the internal parser file, line, and
function that created the diagnostic.
from prik import parse_fortran_project
from pathlib import Path
files = list(Path("tests/fortran/source_parsing/parsing/fixtures/general").glob("*.f90"))[:5]
project = parse_fortran_project(files)
print(len(project.files))
print(len(project.modules))Expected behavior:
- Recursively scans Fortran files.
- Resolves dependencies and module imports across files.
- Returns aggregate namespace parse output.
from pathlib import Path
from prik import parse_fortran_file
from semantics.fortran2ir import fortran_file_to_semantic_modules
p = Path("tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90")
code = p.read_text()
parsed = parse_fortran_file(code, filename=str(p))
modules = fortran_file_to_semantic_modules(parsed, standalone_module_name=p.stem)
print("procedures", len(parsed.procedures))
print("semantic modules", len(modules))Expected behavior:
parsedis aFortranFileaggregate model with parsed units and symbols.modulesis the semantic IR projection used by.pyiprinting and wrapper planning.
Compatibility fields such as FortranArgument.shape, lbound, ubound, and
kind remain serialized as strings/lists. For callers that need typed access,
argument and variable models also expose structured helpers:
structured_shapereturns aFortranShapecontaining parsed dimensions.- Slice-like dimensions such as
1:n:2are represented asFortranSlice. - Whole-expression function calls such as
lbound(x, 1)are represented asFortranFunctionCall. kind_expressionandvalue_expressionparsekindandvaluestrings using the same lightweight expression model.
Example:
arg.shape
# ["lbound(src, 2):ubound(src, 2)"]
dim = arg.structured_shape.dimensions[0]
dim.lower.name
# "lbound"
dim.upper.name
# "ubound"Run all tests:
PYTHONPATH=. pytest -qRun parser-focused tests:
python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --language fortran --json
PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/
PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py
PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/Focused test files by implementation area:
- Parser walkthrough and expected developer flow:
tests/fortran/source_parsing/parsing/test_developer_tutorial.py - Procedure headers, declarations, derived types, interfaces, and type-bound
procedures:
tests/fortran/source_parsing/parsing/ - Function header edge cases:
tests/fortran/functions/parsing/test_function_headers.py - Scope handling and project namespace behavior:
tests/fortran/modules/parsing/test_scope_handling.pyandtests/fortran/modules/parsing/test_project_scope_models.py - Preprocessing, native includes, and execution-boundary skipping:
tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py - Parser diagnostics and fatal error contracts:
tests/fortran/source_parsing/parsing/test_error_handling.py - Regression contracts:
tests/fortran/source_parsing/parsing/ - Public entrypoints:
tests/fortran/source_parsing/parsing/test_public_entrypoints.py - Parser fixture goldens:
tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py - Parser error fixture goldens:
tests/fortran/source_parsing/parsing/test_error_fixture_suite.py - Parser JSON shape:
tests/fortran/source_parsing/parsing/test_json_sanity.py - Cached Fortran compiler/type and intrinsic-storage probing:
tests/fortran/data_types/probes/test_fortran_type_probes.py - Shared CLI behavior:
tests/fortran/command_line_interface/pipeline/
When adding or changing a Fortran parser feature, add a focused parser test near the implementation concern first, then update fixture goldens only when the serialized parser contract intentionally changes.
Update golden JSON fixtures:
python tests/fortran/source_parsing/parsing/generate_parser_goldens.pyUpdate selected fixture(s):
python tests/fortran/source_parsing/parsing/generate_parser_goldens.py tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90In-test auto-update mode:
FORTRAN_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py --confcutdir=tests/Semantic and .pyi fixtures have separate generators:
python tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py
WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.pyAll parse failures raise FortranParseError, a subclass of ValueError. The
exception keeps structured metadata for consumers:
filename— source path supplied to the parser, if anyline_number— 1-based source line where the error was detected, if knownsource_line— original source text for context, if knownbase_message— stable error text without location/source contextcode— stable, explicit diagnostic category identifier; manually constructed fallback errors usePARSE_ERROR, while grammar rejection usesPARSE_INVALID_SYNTAX
Diagnostic codes are for programmatic matching in tests, tools, and
documentation. The category name states the failure class directly. The shared
registry is diagnostic-codes.md.
str(error) and error.format_diagnostic(color=False) render a
compiler-style diagnostic:
<filename>:<line>:1: error[<CATEGORY>]: <message>
|
<N> | <source line>
| ^
If no filename is available, the location is rendered as <unknown>. If a line
number or source line is unavailable, that part of the diagnostic is omitted or
shown with ? as appropriate. Use error.base_message when tests or API
consumers need only the message text.
format_diagnostic(color=True) adds ANSI styling. The CLI requests colored
diagnostics by default when available; pass --no-color or set NO_COLOR=1 to
disable ANSI output. On Windows, ANSI console compatibility is enabled through
colorama when it is installed.
For parser development, format_diagnostic(debug=True) appends a note with the
internal parser file, line, and function that raised the error. The CLI exposes
this through --debug or FORTRAN_PARSER_DEBUG=1; normal CLI parse errors intentionally hide Python
tracebacks.
The sections below list each error category, the triggering condition, and the
exact base_message format (with <...> placeholders for runtime values).
Triggered when a declaration line cannot be matched to any known intrinsic type,
type(...), or character variant.
In a procedure:
Unknown or unsupported datatype declaration for procedure '<name>': <line>
Example Fortran that triggers this:
subroutine bad(x)
weirdtype :: x
end subroutine badExample error:
bad.f90:2:1: error[PARSE_UNSUPPORTED_DECLARATION]: Unknown or unsupported datatype declaration for procedure 'bad': weirdtype :: x
|
2 | weirdtype :: x
| ^
In a derived type:
Unknown or unsupported datatype declaration in type '<name>': <line>
In a module:
Unknown or unsupported datatype declaration in module '<name>': <line>
Triggered when the same symbol is declared more than once in the same scope.
In a procedure (arguments and local declarations):
Duplicate declaration of symbol '<name>' in procedure '<proc>'.
Example:
subroutine dup(x)
real :: x
integer :: x
end subroutine dupExample error:
dup.f90:3:1: error[PARSE_DUPLICATE_DECLARATION]: Duplicate declaration of symbol 'x' in procedure 'dup'.
|
3 | integer :: x
| ^
PARAMETER constants:
Duplicate PARAMETER declaration of symbol '<name>' in procedure '<proc>'.
In a derived type:
Duplicate field '<name>' in derived type '<type>'.
In a module:
Duplicate variable '<name>' in module '<module>'.
Triggered when the same procedure name appears more than once within the same
module or global scope.
Internal procedures inside separate host contains blocks are scoped to their
host and do not conflict with each other.
Global scope:
Duplicate procedure name '<name>' in global scope.
Module scope:
Duplicate procedure name '<name>' in module '<module>'.
Example:
subroutine work(n)
integer, intent(in) :: n
end subroutine work
subroutine work(n)
integer, intent(in) :: n
end subroutine workExample error:
dup.f90:5:1: error[PARSE_DUPLICATE_PROCEDURE]: Duplicate procedure name 'work' in global scope.
|
5 | subroutine work(n)
| ^
Triggered when a procedure's argument list contains the same name more than once.
Duplicate argument name '<name>' in procedure '<proc>'.
Example:
subroutine dup(x, y, x)
integer, intent(in) :: x
real, intent(in) :: y
end subroutine dupExample error:
dup_arg.f90:1:1: error[PARSE_DUPLICATE_ARGUMENT]: Duplicate argument name 'x' in procedure 'dup'.
|
1 | subroutine dup(x, y, x)
| ^
Legacy type*N declarations, such as real*8, are accepted in both fixed-form
and modern-extension files. Numeric star declarations preserve their fixed
total storage width for semantic conversion. This matters most for complex
types: complex*8 is an 8-byte Complex64, while modern complex(kind=8) is
a compiler kind and is 16 bytes on the documented gfortran target.
DOUBLE PRECISION and DOUBLE COMPLEX retain a compiler-dependent double-kind
expression and use the cached Fortran type probe. For CHARACTER*N and
CHARACTER*(*), the star value is a length, not a kind or element storage
width.
subroutine accepted(x)
real*8 :: x
end subroutine acceptedSee the generated modern and legacy datatype mapping for the exact GitHub Actions target results.
The parser records source-form metadata from the filename and lexer, but does
not reject a construct solely because a .f77 suffix was used. Grammar-region
validation still applies after preprocessing.
Triggered when implicit none is active and an argument (or function result)
has no matching type declaration.
Argument:
Argument '<name>' in procedure '<proc>' has no type declaration (implicit none is active).
Function result:
Function result '<name>' in procedure '<proc>' has no type declaration (implicit none is active).
Example:
subroutine foo(x, y)
implicit none
integer, intent(in) :: x
end subroutine fooExample error:
implicit_none.f90:1:1: error[PARSE_IMPLICIT_NONE_UNDECLARED_SYMBOL]: Argument 'y' in procedure 'foo' has no type declaration (implicit none is active).
|
1 | subroutine foo(x, y)
| ^
Triggered when a function result has no resolvable type after parsing (and
implicit none prevents implicit typing).
Unknown datatype for function result '<name>' in procedure '<proc>'.
Example:
function f(x) result(res)
implicit none
real :: x
end function fExample error:
bad.f90:1:1: error[PARSE_UNKNOWN_FUNCTION_RESULT_TYPE]: Unknown datatype for function result 'res' in procedure 'f'.
|
1 | function f(x) result(res)
| ^
Triggered by _validate_module_variables when a parsed module variable still
has base_type == "unknown" after declaration parsing.
Unknown type for variable '<name>' in module '<module>'.
Triggered by _validate_derived_type_fields when a field still has
base_type == "unknown".
Unknown type for field '<name>' in derived type '<type>'.
Triggered when a legacy PARAMETER (...) statement names a symbol that has not
been typed and implicit none is in effect.
Unknown datatype for PARAMETER symbol '<name>' in procedure '<proc>'.
Example:
subroutine cst(a)
implicit none
real a
parameter ( zero = 0.0e+0 )
endExample error:
legacy.f:4:1: error[PARSE_UNKNOWN_PARAMETER_TYPE]: Unknown datatype for PARAMETER symbol 'zero' in procedure 'cst'.
|
4 | parameter ( zero = 0.0e+0 )
| ^
Triggered when a result(name) clause reuses an argument name (and the two
names are different from each other — the special case result(f) on a
function named f is allowed).
Function result variable '<result>' in function '<func>' shadows an argument name.
Example:
function f(res) result(res)
integer, intent(in) :: res
end function fExample error:
shadow.f90:1:1: error[PARSE_RESULT_SHADOWS_ARGUMENT]: Function result variable 'res' in function 'f' shadows an argument name.
|
1 | function f(res) result(res)
| ^
An internal safety check: if a symbol was explicitly declared but its type could not be applied (a parser regression guard), the following error is raised.
Failed to resolve declared argument '<name>' in procedure '<proc>'.
This parser is intentionally wrapper-focused and not a complete Fortran front end. Unsupported syntax should be surfaced through parser diagnostics or later semantic policy inputs for incremental parser extension.
The parser accepts legacy callback-style declarations inside procedure scopes, including:
external :: cb(treated as a procedure-typed dummy)real, external :: f/integer, external :: g(typed external function dummies)
Under implicit none, these declarations count as valid argument declarations, so callback arguments are not reported as missing datatype declarations.
Use the stable top-level API:
parse_fortran_file(source_or_path, filename=None, encoding="utf-8") -> FortranFileparse_fortran_project(files, encoding="utf-8") -> FortranProject
Lower-level unit parsers are internal FortranParser methods.
Semantic conversion lives in prik/semantics/fortran2ir.py. It accepts parsed FortranFile
(or selected FortranModule) structures and converts metadata into semantic IR
consumed by the .pyi printer and current Fortran wrapper/runtime stages.
Compiler-backed shared-CLI semantic stages resolve compiler-dependent kind
expressions, measure numeric and logical intrinsic storage with storage_size,
attach those facts to semantic types, and reuse memory and persistent caches.
Character declarations are excluded from storage probing: their semantic type
is String, while fixed or deferred element length is carried separately from
the declaration or runtime descriptor. The generated mapping report describes
the modeled eight-bit character code unit directly and does not manufacture a
compiler probe fact for character rows. For the maintained GitHub Actions
gfortran profile, unqualified integer, real, and complex map to Int32,
Float32, and Complex64; target-changing flags can change those mappings.
Source-driven wrapper builds add the normalized native Fortran compiler flags
to the internal probe configuration, so semantic type facts and native
implementation compilation use the same default-kind profile. The
generated target datatype mapping
measures and verifies those storage facts.
The Fortran probe cache key includes the generated expression source, resolved
compiler binary identity, target flags, includes, macros, requested standard,
working directory, target-related environment, and runner. The persistent
location is $XDG_CACHE_HOME/prik/fortran_type_probe or
~/.cache/prik/fortran_type_probe; PRIK_CACHE_DIR changes the internal cache
root. The standalone prik probe command additionally exposes --cache-dir
and --refresh for explicit inspection runs.
The standalone probe can create a reusable report containing the exact compile-time and storage expressions needed by a source:
python3 -m prik probe --language fortran --compiler gfortran \
--expr='selected_real_kind(12)' \
--expr='storage_size(real(0.0,kind=8))' \
--out build/fortran-types.jsonThe report is an inspection and verification output. Semantic conversion and wrapper builds measure the facts they need internally from their selected compiler; the report is not a second semantic-stage input path. A missing required expression is reported explicitly instead of falling back to an unrelated target mapping.
The semantic converter also supports compile-time specialization for values the
parser intentionally leaves symbolic. Use
collect_semantic_compile_time_requirements(parsed) to list missing parameter
or kind values, then pass a dictionary such as
{"selected_real_kind(12)": 8} to
fortran_module_to_semantic_module(..., compile_time_values=...) or
fortran_file_to_semantic_modules(..., compile_time_values=...). Existing
semantic IR can be copied and specialized with
resolve_semantic_compile_time_values(module, {"n": 64}).