mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-21 22:45:33 -04:00
230 lines
8.5 KiB
Python
Executable file
230 lines
8.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import glob
|
|
from string import whitespace
|
|
from sys import exit
|
|
from textwrap import TextWrapper
|
|
|
|
|
|
################################################################################
|
|
# Fortran reading/parsing backend.
|
|
################################################################################
|
|
|
|
|
|
class LineOfCode(object):
|
|
"""Contains and provides basic info about a line of Fortran 90 code."""
|
|
def __init__(self, number, content, is_continuation=False,
|
|
string_terminator=None):
|
|
# Initialize member variables.
|
|
self.number = number
|
|
self.content = content
|
|
self.is_continuation = is_continuation
|
|
self.is_continued = False
|
|
assert (string_terminator is None or
|
|
string_terminator == "'" or
|
|
string_terminator == '"')
|
|
self.initial_string_terminator = string_terminator
|
|
self.final_string_terminator = None
|
|
self.is_comment_only = False
|
|
|
|
# Parse the string.
|
|
self.parse()
|
|
|
|
def __repr__(self):
|
|
rep = 'LineOfCode: line number = {0:d}\n'.format(self.number)
|
|
rep += '\tis_continuation = ' + str(self.is_continuation) + '\n'
|
|
rep += '\tis_continued = ' + str(self.is_continued) + '\n'
|
|
rep += ('\tinitial_string_terminator = '
|
|
+ str(self.initial_string_terminator) + '\n')
|
|
rep += ('\tfinal_string_terminator = '
|
|
+ str(self.final_string_terminator) + '\n')
|
|
rep += '\tis_comment_only = ' + str(self.is_comment_only) + '\n'
|
|
rep += '\tContent:\n' + self.content
|
|
return rep
|
|
|
|
def __str__(self):
|
|
return repr(self)
|
|
|
|
def parse(self):
|
|
# Initialize the string variables.
|
|
if self.initial_string_terminator == "'":
|
|
in_a_single_quote = True
|
|
in_a_double_quote = False
|
|
elif self.initial_string_terminator == '"':
|
|
in_a_single_quote = False
|
|
in_a_double_quote = True
|
|
else:
|
|
in_a_single_quote = False
|
|
in_a_double_quote = False
|
|
|
|
# Initialize other variables.
|
|
in_a_comment = False
|
|
ends_with_ampersand = False
|
|
has_real_content = False
|
|
|
|
# Parse through the line.
|
|
for char in self.content:
|
|
# Check for the start or end of strings.
|
|
if char == "'" and in_a_single_quote:
|
|
in_a_single_quote = False
|
|
elif char == "'" and not in_a_double_quote:
|
|
in_a_single_quote = True
|
|
elif char == '"' and in_a_double_quote:
|
|
in_a_double_quote = False
|
|
elif char == '"' and not in_a_single_quote:
|
|
in_a_double_quote = True
|
|
|
|
# Check for the start of a comment.
|
|
if (char == "!" and not in_a_single_quote
|
|
and not in_a_double_quote):
|
|
in_a_comment = True
|
|
break # Don't care about anything in a comment.
|
|
|
|
# Check for a continuation ampersand.
|
|
if char == "&":
|
|
ends_with_ampersand = True
|
|
elif all([char != w for w in whitespace]):
|
|
ends_with_ampersand = False
|
|
|
|
# Check to see if there is any real content (non-whitespace, not in
|
|
# a comment)
|
|
if (not in_a_comment) and all([char != w for w in whitespace]):
|
|
has_real_content = True
|
|
|
|
# Make sure nothing went terribly wrong.
|
|
assert not (in_a_single_quote and in_a_double_quote)
|
|
assert not (in_a_single_quote and in_a_comment)
|
|
assert not (in_a_double_quote and in_a_comment)
|
|
|
|
# Is this line continued onto the next?
|
|
if ends_with_ampersand:
|
|
self.is_continued = True
|
|
|
|
# Is a multiline string continued on the next line?
|
|
if in_a_single_quote:
|
|
assert self.is_continued
|
|
self.final_string_terminator = "'"
|
|
if in_a_double_quote:
|
|
assert self.is_continued
|
|
self.final_string_terminator = '"'
|
|
|
|
# Is this line a comment-only line?
|
|
if (not has_real_content) and in_a_comment:
|
|
self.is_comment_only = True
|
|
|
|
def get_indent(self):
|
|
"""Return the number of indentation spaces prefixing this line."""
|
|
return len(self.content) - len(self.content.lstrip(' '))
|
|
|
|
def contains_whitespace_only(self):
|
|
"""Return True/False if all characters in the line are whitespace."""
|
|
if len(self.content.strip('\n')) == 0: return False
|
|
is_ws = [ any([char == ws for ws in whitespace])
|
|
for char in self.content.strip('\n') ]
|
|
return all(is_ws)
|
|
|
|
def has_trailing_whitespace(self):
|
|
"""Return True/False if this line ends with a whitespace character."""
|
|
stripped = self.content.strip('\n')
|
|
if len(stripped) == 0: return False
|
|
return any([stripped[-1] == ws for ws in whitespace])
|
|
|
|
def contains_tab(self):
|
|
"""Return True/False if a tab character appears in the line."""
|
|
return '\t' in self.content
|
|
|
|
|
|
def read_lines_of_code(fname):
|
|
line_num = 0
|
|
cont = False
|
|
str_term = None
|
|
with open(fname) as fin:
|
|
for line in fin:
|
|
loc = LineOfCode(line_num, line, is_continuation=cont,
|
|
string_terminator=str_term)
|
|
cont = loc.is_continued
|
|
str_term = loc.final_string_terminator
|
|
line_num += 1
|
|
yield loc
|
|
|
|
|
|
################################################################################
|
|
# Error checking.
|
|
################################################################################
|
|
|
|
|
|
def print_error(fname, line_number, err_msg):
|
|
header = "Error in file {0}, line {1}:".format(fname, line_number + 1)
|
|
|
|
tw = TextWrapper(width=80, initial_indent=' '*4, subsequent_indent=' '*4)
|
|
body = '\n'.join(tw.wrap(err_msg))
|
|
|
|
print(header + '\n' + body + '\n')
|
|
|
|
|
|
def check_source(fname):
|
|
"""Make sure the given Fortran source file meets OpenMC standards.
|
|
|
|
If errors are found, messages will be printed to the screen describing the
|
|
error. The function will return True if no errors were found or False
|
|
otherwise.
|
|
"""
|
|
good_code = True
|
|
base_indent = 0
|
|
|
|
for loc in read_lines_of_code(fname):
|
|
# Check for extra whitespace errors.
|
|
if loc.contains_whitespace_only():
|
|
good_code = False
|
|
print_error(fname, loc.number, "Line contains whitespace "
|
|
"characters but no content. Please remove whitespace.")
|
|
elif loc.has_trailing_whitespace():
|
|
good_code = False
|
|
print_error(fname, loc.number, "Line has trailing whitespace after"
|
|
" the content. Please remove trailing whitespace.")
|
|
if loc.contains_tab():
|
|
good_code = False
|
|
print_error(fname, loc.number, "Line contains a tab character. "
|
|
"Please replace with single whitespace characters.")
|
|
|
|
# Check indentation.
|
|
current_indent = loc.get_indent()
|
|
if ((not loc.is_continuation) and (not loc.is_comment_only) and
|
|
(not loc.contains_whitespace_only()) and current_indent % 2 != 0):
|
|
good_code = False
|
|
print_error(fname, loc.number, "Line is indented an odd number of "
|
|
"spaces. All non-continuation lines should be indented an "
|
|
"even number of spaces.")
|
|
if loc.is_continuation and current_indent < base_indent + 5:
|
|
good_code = False
|
|
print_error(fname, loc.number, "Continuation lines must be "
|
|
"indented by at least 5 spaces, but this line is indented {0}"
|
|
" spaces.".format(current_indent - base_indent))
|
|
|
|
# Set base indentation for next lines.
|
|
if not loc.is_continuation:
|
|
base_indent = current_indent
|
|
|
|
return good_code
|
|
|
|
|
|
################################################################################
|
|
# Main loop.
|
|
################################################################################
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Get a list of the F90 source files.
|
|
f90_files = glob.glob('../src/*.F90')
|
|
|
|
# Make sure we found the source files.
|
|
assert len(f90_files) != 0, 'No .F90 source files found.'
|
|
|
|
# Make sure all the F90 source files meet our standards.
|
|
good_code = [check_source(fname) for fname in f90_files]
|
|
if not all(good_code):
|
|
print("ERROR: The Fortran source code does not meet OpenMC's standards")
|
|
exit(-1)
|
|
else:
|
|
print("SUCCESS! Looks like the Fortran source meets our standards")
|
|
exit(0)
|