Skip to content

Commit a10b399

Browse files
committed
codex: update parser grammar documentation
1 parent 186f0af commit a10b399

4 files changed

Lines changed: 98 additions & 33 deletions

File tree

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -453,15 +453,20 @@ The parser exposes stable file/project entrypoints:
453453
- `parse_fortran_project(...)` for many sources returning `FortranProject`.
454454
- `assess_wrap_readiness(...)` for wrappability diagnostics.
455455

456-
Internally, `FortranParser.visit_file` uses a recursive source-unit parser:
457-
the file is sliced into direct modules/submodules/programs/procedures/block
458-
data/interfaces/types, then each unit visitor parses only its own substring and
459-
recurses into direct children. Shared declaration helpers parse variables,
460-
procedure arguments/results, and type fields, then push them into the active
461-
scope. Procedure execution bodies and internal subprograms are ignored for
462-
wrapper metadata; procedure-local interfaces are retained for callback typing.
456+
Internally, `FortranParser.visit_file` uses a recursive grammar-style
457+
source-unit parser. The file is first sliced into direct
458+
modules/submodules/programs/procedures/block-data/interfaces/types. Each unit
459+
visitor then parses only its own substring, splits it into header,
460+
specification, optional execution, and optional `contains` regions, and recurses
461+
into direct child units where that grammar allows children. Shared declaration
462+
helpers parse variables, procedure arguments/results, and type fields, then
463+
push them into the active scope. Procedure execution bodies and internal
464+
subprograms are ignored for wrapper metadata; procedure-local interfaces are
465+
retained for callback typing.
463466
Parameter variables keep both `value` (best resolved value) and runtime
464467
`symbolic_value` (the original expression) when the parser has that information.
468+
Module-level parameters used in procedure argument shapes remain symbolic in
469+
the signature while still being valid scoped references for readiness checks.
465470

466471
The semantics layer consumes `FortranFile`/`FortranModule` objects and projects them into language-independent semantic IR (`SemanticModule`, `SemanticFunction`, `SemanticClass`, `SemanticType`). This keeps the semantic API model independent from parser internals, matching the project goal that parser output is a helper and the semantic interface/IR is the source of truth.
467472

fortran_parser.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ sections so maintainers can navigate the file by concern instead of by history:
8585
- Module-level helper blocks (source-form rules, preprocessor logic,
8686
diagnostics, compile-time expression resolution, dependency ordering)
8787
- `FortranParser` internals grouped by domain:
88-
- visitor-style API entrypoints (`visit_file`, `visit_project`,
89-
`visit_wrap_readiness`)
88+
- internal visitor entrypoints (`visit_file`, `visit_project`,
89+
`visit_wrap_readiness`). The public API remains the module-level wrappers
90+
listed above.
9091
- source-unit visitors for files, modules, submodules, programs,
9192
procedures, interfaces, derived types, and block data
9293
- recursive source-unit slicing (`header`, specification part, execution
@@ -101,10 +102,17 @@ sections so maintainers can navigate the file by concern instead of by history:
101102

102103
`visit_file` is the central orchestration path. It first slices the source into
103104
direct file-level units, then each unit visitor parses only its own substring
104-
and recursively slices direct children. Procedure execution parts are ignored
105-
for wrapper metadata, and procedure-internal subprograms are not exported as
106-
file/module procedures. Procedure-local interface blocks are still visited
107-
enough to type callback dummy arguments and to preserve interface metadata.
105+
and recursively slices direct children. This is the key parser design: each
106+
Fortran grammar unit has a header, a specification region, optional execution
107+
region, and optional `contains` region. The differences between modules,
108+
programs, procedures, derived types, interfaces, and block data are expressed
109+
by small visitor decisions and grammar flags rather than separate whole-file
110+
parsing loops.
111+
112+
Procedure execution parts are ignored for wrapper metadata, and
113+
procedure-internal subprograms are not exported as file/module procedures.
114+
Procedure-local interface blocks are still visited enough to type callback
115+
dummy arguments and to preserve interface metadata.
108116

109117
### 2.1 Recursive parser sketch
110118

@@ -171,6 +179,13 @@ folding; `symbolic_value` preserves the original parameter initializer for
171179
validation, debugging, and downstream diagnostics without changing the legacy
172180
JSON fixture shape.
173181

182+
Procedure-local parameters may be folded into argument shapes during procedure
183+
finalization. Module-level and `use`-associated parameters used in procedure
184+
argument shapes are kept symbolic in the signature (`x(n)` remains `["n"]`)
185+
and are treated as valid scope references for readiness checks. Module/program
186+
variable shapes and parameter values can be resolved through the compile-time
187+
resolver when enough information is available.
188+
174189
## 3) Terminal usage and expected outputs
175190

176191
### 3.1 Basic CLI invocation

fortran_parser/parser.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@
2626
file/project models.
2727
2828
Recommended reading order for maintainers:
29-
- Start from `FortranParser.visit_file` / `visit_project`
29+
- Start from the module-level public wrappers (`parse_fortran_file`,
30+
`parse_fortran_project`, `assess_wrap_readiness`)
31+
- Then read `FortranParser.visit_file` / `visit_project`
3032
- Then read the high-level unit visitor methods at the top of the class
3133
- Then drill into `_helper_*` implementations and low-level helpers
3234
3335
`FortranParser` class layout (top -> bottom):
34-
- Visitor API: `visit_file`, `visit_project`, `visit_wrap_readiness`, plus
35-
small `visit_*_unit` methods for each already-sliced source unit.
36+
- Internal visitor entrypoints: `visit_file`, `visit_project`,
37+
`visit_wrap_readiness`, plus small `visit_*_unit` methods for each
38+
already-sliced source unit. The stable public API is the module-level wrapper
39+
layer at the bottom of the file.
3640
- Core `_helper_*` methods for source slicing, grammar-region splitting,
3741
scoped specification-part visits, declaration parsing, symbol pushing,
3842
preprocessor branch selection, and same-level duplicate checks.
@@ -1629,33 +1633,48 @@ class FortranParser:
16291633
16301634
State carried on the instance:
16311635
- `macro_defines`: optional macro-selection configuration used while
1632-
collecting procedures when conditional branches are present.
1636+
selecting preprocessor branches before source-unit slicing.
16331637
16341638
Parsing pipeline used by `visit_file`:
16351639
1. Preprocess source into normalized lines (`_preprocessed_lines`).
1636-
2. Parse signatures/types/interfaces/program units.
1637-
3. Attach parsed members to owning module/submodule scopes.
1638-
4. Build `FortranFile` symbol table and standalone entity lists.
1640+
2. Slice direct file-level source units (`module`, `submodule`,
1641+
`program`, standalone `procedure`, `block data`, file-level
1642+
`interface`, and file-level derived type).
1643+
3. Dispatch each `_SourceUnit` to a small `visit_*_unit` method.
1644+
4. Each unit visitor parses only that unit's own substring, builds its own
1645+
`_ParserScope`, splits the unit into grammar regions, visits the
1646+
specification part, and recursively slices direct children where the
1647+
grammar allows them.
1648+
5. Shared declaration helpers push variables, procedure symbols, and type
1649+
fields into the active scope model.
1650+
6. Build `FortranFile` symbol table and standalone entity lists.
16391651
16401652
Class section map:
1641-
- Public API methods first (developer discovery).
1642-
- High-level unit parsing methods next (top-down by Fortran block size).
1643-
- Internal `_helper_*` methods after that (full scoped parsing logic).
1653+
- Internal visitor entrypoints first (developer discovery).
1654+
- Unit visitors next (one visitor per grammar-level source unit).
1655+
- Internal `_helper_*` methods after that (reusable scoped parsing logic).
16441656
- Lower-level declaration/header helpers and assembly utilities last.
16451657
16461658
Scope behavior summary:
1647-
- `current_module` tracks ownership for procedures/types/interfaces.
1648-
- `interface_depth`/stacks track when declarations belong to interface blocks.
1649-
- Per-procedure state tracks declaration-part vs executable-part boundaries.
1650-
- Type parsing tracks `contains` sub-region for bindings/generics vs fields.
1651-
- Program/module/submodule parsers collect specification-part declarations
1652-
and stop collecting variable declarations after `contains`.
1659+
- `_ParserScope` is passed explicitly into shared helpers; there is no
1660+
ambient `current_module` or interface stack.
1661+
- Module/submodule scopes own contained procedures, interfaces, and derived
1662+
types; program and block-data scopes collect their specification
1663+
variables only.
1664+
- Procedure scopes parse only wrapper-relevant specification declarations;
1665+
execution statements and internal procedures after `contains` are
1666+
ignored, except procedure-local interfaces are revisited to type callback
1667+
dummy arguments.
1668+
- Derived-type scopes parse fields in the specification region and
1669+
type-bound procedure/generic bindings in the `contains` region.
1670+
- Same-level unit names are validated by the slicer with preprocessor
1671+
branch-awareness, while identical names in different scopes remain valid.
16531672
16541673
`visit_project` composes multiple `FortranFile` objects into one
16551674
`FortranProject` registry and validates duplicate symbols by scope.
16561675
"""
16571676
# ------------------------------------------------------------------
1658-
# Public API (kept first for discoverability)
1677+
# Internal visitor entrypoints (kept first for developer discovery)
16591678
# ------------------------------------------------------------------
16601679

16611680
def __init__(self, macro_defines: set[str] | dict[str, int | bool | str] | None = None):

parser_implementation_reference.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ another source language.
105105
including renamed imports from parsed modules. Safely evaluable arithmetic
106106
parameter chains are folded to their final integer kind; compiler-dependent
107107
intrinsics such as `selected_real_kind(...)` remain as resolved expressions.
108-
- Shape expressions resolved using available symbol dictionary.
108+
- Procedure-local parameter expressions can be folded into argument shapes
109+
during procedure finalization. Module-level and `use`-associated parameters
110+
used in procedure argument shapes remain symbolic in the signature while
111+
still being considered valid scope references for readiness diagnostics.
109112
- Module/program variable parameter values, character lengths, and shapes are
110113
resolved through the same cached compile-time resolver where safe.
111114
- The resolver caches symbol and expression results per scope and evaluates a
@@ -230,6 +233,8 @@ Covers, among others:
230233
- storage directives (`save`, `common`) in procedures are recognized and skipped as non-declaration statements
231234
- module variable and `use` parsing
232235
- module children attachment (procedures/types/interfaces)
236+
- executable parser-internals tutorial in
237+
`tests/parser/test_parser_developer_tutorial.py`
233238
- ignoring local vars in external signatures
234239
- external callback declarations (including typed `real, external :: f`) under `implicit none`
235240
- ignoring internal procedures in `contains`
@@ -345,13 +350,15 @@ ask it to implement each of these layers explicitly:
345350
11. Project namespace parser with dependency ordering.
346351
12. Readiness validator with unsupported-pattern rules + unknown type checks.
347352
13. CLI with tree output + JSON output + file emission.
348-
11. Unit tests per feature + fixture/golden regression suite + golden
353+
14. Unit tests per feature + fixture/golden regression suite + golden
349354
regeneration script.
350355

351356

352357
### 6.1 Parser control-flow example
353358

354-
Use this as the mental model when changing `fortran_parser/parser.py`:
359+
Use this as the mental model when changing `fortran_parser/parser.py`: parsing
360+
is recursive over source units, and each unit is handled by the same grammar
361+
shape before grammar-specific exceptions are applied.
355362

356363
```fortran
357364
module m
@@ -384,6 +391,20 @@ The control flow is:
384391
build a scope, split the unit, visit the relevant specification part, and
385392
push declarations into that scope.
386393

394+
The grammar shape is the design rule:
395+
396+
- every sliced source unit has a header and a specification part
397+
- procedures and programs can also have an execution part
398+
- modules, submodules, programs, procedures, and derived types can have a
399+
`contains` part, but visitors decide whether children in that region matter
400+
for wrapping
401+
- block data is specification-only
402+
- interfaces use the same child-slicing mechanism for procedure declarations
403+
404+
That means new parser behavior should usually be implemented by extending the
405+
grammar profile, unit splitter, or shared specification/declaration helpers,
406+
not by adding a new whole-file scan.
407+
387408
Declaration parsing is deliberately shared. Module variables, program/block
388409
data variables, procedure arguments/results, and derived-type fields all call
389410
`_helper_parse_declaration_line`, then `_helper_push_declaration_to_scope`.
@@ -443,6 +464,11 @@ implemented today:
443464
- **Module specification scope tracking**: module `parameter` collection is
444465
restricted to the module specification part and stops at `contains`, avoiding
445466
leakage from executable regions.
467+
- **Module parameter references in contained procedure shapes**: a contained
468+
procedure argument such as `real :: x(n)` may refer to a module-level
469+
parameter `n`. The signature keeps the shape token symbolic (`"n"`) while
470+
readiness validation treats it as a valid scoped reference. This protects
471+
module-level parameters from being mistaken for undeclared procedure locals.
446472
- **Interface scope tracking**: procedures parsed inside `interface ... end
447473
interface` are represented separately and flagged as interface procedures.
448474
Interface-local argument declarations do not conflict with host declarations;

0 commit comments

Comments
 (0)