A Python module and command line tool for parsing, writing, and modifying Fortran 90 namelist files
—
Configurable parsing engine for handling Fortran syntax variations, custom comment tokens, and complex array indexing. The Parser class provides fine-grained control over how namelist files are interpreted and processed.
Advanced parser with configurable options for handling diverse Fortran namelist dialects and edge cases.
class Parser:
"""
Fortran namelist parser with extensive configuration options.
Provides control over parsing behavior, indexing conventions,
comment handling, and data structure organization.
"""
def __init__(self):
"""Create parser object with default configuration."""Usage Examples:
# Create parser with custom configuration
parser = f90nml.Parser()
# Configure parser options
parser.comment_tokens = '!#' # Support both ! and # comments
parser.default_start_index = 0 # Use 0-based indexing
parser.strict_logical = False # Relaxed logical parsing
# Use parser
nml = parser.read('config.nml')Core methods for processing namelist data from files and strings.
def read(self, nml_fname, nml_patch_in=None, patch_fname=None):
"""
Parse a Fortran namelist file and store the contents.
Args:
nml_fname: str or file-like object - Input file path or file object
nml_patch_in: dict, optional - Patch data to apply during parsing
patch_fname: str, optional - Output file for patch results
Returns:
Namelist: Parsed namelist data
Raises:
ValueError: If namelist syntax is invalid
IOError: If file cannot be read
"""
def reads(self, nml_string):
"""
Parse a namelist string and return equivalent Namelist object.
Args:
nml_string: str - String containing Fortran namelist data
Returns:
Namelist: Parsed namelist data
Raises:
ValueError: If namelist syntax is invalid
"""Usage Examples:
parser = f90nml.Parser()
# Basic parsing
nml = parser.read('input.nml')
# Parse with simultaneous patching
patch_data = {'config': {'modified_param': 100}}
nml = parser.read('input.nml', patch_data, 'patched_output.nml')
# Parse string
nml_str = '&data x=1, y=2 /'
nml = parser.reads(nml_str)Customize which characters are treated as comment delimiters.
comment_tokens: str
"""
String of single-character comment tokens (default: '!').
Fortran standard uses '!' but some programs support additional
tokens like '#' for preprocessing compatibility.
"""Usage Examples:
parser = f90nml.Parser()
# Support multiple comment types
parser.comment_tokens = '!#'
# Parse file with mixed comments
nml_content = '''
&config
! Standard Fortran comment
param1 = 10
# Preprocessor-style comment
param2 = 20
/
'''
nml = parser.reads(nml_content)
# Add custom comment token
parser.comment_tokens += '%' # Now supports !, #, and %Control how array indices are interpreted and handled.
default_start_index: int
"""
Assumed starting index for vectors without explicit indexing (default: 1).
Fortran allows arbitrary start indices. When not specified,
this value determines the assumed starting position.
"""
global_start_index: int
"""
Explicit start index for all vectors (default: None).
When set, forces all arrays to use this starting index,
overriding any explicit indices in the namelist.
"""Usage Examples:
parser = f90nml.Parser()
# Use 0-based indexing (Python-style)
parser.default_start_index = 0
# Parse namelist with ambiguous indexing
nml_content = '''
&data
array1(3:5) = 10, 20, 30 ! Explicit indices
array2 = 1, 2, 3 ! Uses default_start_index
/
'''
nml = parser.reads(nml_content)
print(nml['data']['array2']) # [1, 2, 3] starting at index 0
# Force global indexing
parser.global_start_index = 1
nml = parser.reads(nml_content)
# Now array2 will be treated as starting at index 1Control how multidimensional arrays and sparse data are organized.
row_major: bool
"""
Read multidimensional arrays in row-major format (default: False).
By default, preserves Fortran column-major ordering.
When True, converts to row-major (C-style) ordering.
"""
sparse_arrays: bool
"""
Store unset rows of multidimensional arrays as empty lists (default: False).
Provides more compact representation for sparse data structures.
"""Usage Examples:
parser = f90nml.Parser()
# Configure for row-major arrays
parser.row_major = True
# Parse multidimensional data
nml_content = '''
&matrix
data(1:2, 1:3) = 1, 2, 3, 4, 5, 6
/
'''
nml = parser.reads(nml_content)
# Array will be organized in row-major order
# Enable sparse array handling
parser.sparse_arrays = True
sparse_content = '''
&sparse
matrix(1, 1) = 10
matrix(5, 5) = 20
/
'''
nml = parser.reads(sparse_content)
# Unset rows will be empty lists instead of None-filledControl how logical (boolean) values are interpreted.
strict_logical: bool
"""
Use strict rules for logical value parsing (default: True).
When True: Only standard forms (.true., .t., true, t, .false., .f., false, f)
When False: Any string starting with 't' or 'f' is interpreted as boolean
"""Usage Examples:
parser = f90nml.Parser()
# Strict logical parsing (default)
parser.strict_logical = True
try:
nml = parser.reads('&data flag = totally_true /')
except ValueError:
print("Invalid logical value in strict mode")
# Relaxed logical parsing
parser.strict_logical = False
nml = parser.reads('&data flag = totally_true /') # Interprets as True
print(nml['data']['flag']) # True
# Standard logical values work in both modes
standard_content = '''
&flags
flag1 = .true.
flag2 = .false.
flag3 = T
flag4 = F
/
'''
nml = parser.reads(standard_content)Handle complex Fortran namelist features and edge cases.
Derived Types:
# Parse Fortran derived types
derived_content = '''
&particles
particle(1)%x = 1.0
particle(1)%y = 2.0
particle(2)%x = 3.0
particle(2)%y = 4.0
/
'''
parser = f90nml.Parser()
nml = parser.reads(derived_content)
print(nml['particles']['particle'][0]['x']) # 1.0Complex Numbers:
# Parse complex number literals
complex_content = '''
&physics
impedance = (1.0, 2.0)
frequency_response = (3.14, -1.57), (2.71, 0.0)
/
'''
nml = parser.reads(complex_content)
print(nml['physics']['impedance']) # (1.0+2.0j)Vector Indexing:
# Handle explicit vector indices
indexed_content = '''
&arrays
pressure(0:2) = 1013.25, 850.0, 700.0
temperature(5:7) = 273.15, 283.15, 293.15
/
'''
parser = f90nml.Parser()
parser.default_start_index = 0
nml = parser.reads(indexed_content)
# Access start index information
print(nml['arrays'].start_index) # {'pressure': [0], 'temperature': [5]}parser = f90nml.Parser()
# Handle invalid syntax
try:
nml = parser.reads('&invalid syntax without end')
except ValueError as e:
print(f"Parse error: {e}")
# Handle file access errors
try:
nml = parser.read('/nonexistent/file.nml')
except IOError as e:
print(f"File error: {e}")
# Validate configuration
try:
parser.default_start_index = "invalid"
except TypeError as e:
print(f"Configuration error: {e}")Parser objects can be used instead of the module-level functions for full control:
# Module-level functions (use default parser)
nml = f90nml.read('input.nml')
# Equivalent using custom parser
parser = f90nml.Parser()
parser.comment_tokens = '!#'
parser.default_start_index = 0
nml = parser.read('input.nml')
# Parser configuration persists across multiple files
nml1 = parser.read('file1.nml')
nml2 = parser.read('file2.nml') # Uses same configurationInstall with Tessl CLI
npx tessl i tessl/pypi-f90nml