Skip to content

Commit

Permalink
Merge pull request #187 from ecmwf-ifs/186-empty-file-regex-frontend
Browse files Browse the repository at this point in the history
Fix handling of empty files in frontends (fix #186)
  • Loading branch information
reuterbal authored Nov 13, 2023
2 parents a2f2428 + 443da5b commit 9d27cab
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 14 deletions.
2 changes: 1 addition & 1 deletion loki/backend/fgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ def fgen(ir, depth=0, conservative=False, linewidth=132):
"""
Generate standardized Fortran code from one or many IR objects/trees.
"""
return FortranCodegen(depth=depth, linewidth=linewidth, conservative=conservative).visit(ir)
return FortranCodegen(depth=depth, linewidth=linewidth, conservative=conservative).visit(ir) or ''


"""
Expand Down
4 changes: 2 additions & 2 deletions loki/frontend/fparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def get_source(self, o, source):
"""
Helper method that builds the source object for the node.
"""
if not isinstance(o, str) and o.item is not None:
if o is not None and not isinstance(o, str) and o.item is not None:
lines = (o.item.span[0], o.item.span[1])
string = ''.join(self.raw_source[lines[0] - 1:lines[1]]).strip('\n')
source = Source(lines=lines, string=string)
Expand All @@ -288,7 +288,7 @@ def get_label(self, o):
"""
Helper method that returns the label of the node.
"""
if not isinstance(o, str) and o.item is not None:
if o is not None and not isinstance(o, str) and o.item is not None:
return getattr(o.item, 'label', None)
return None

Expand Down
5 changes: 4 additions & 1 deletion loki/frontend/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ def to_source(self, include_padding=False):
"""
Create a :any:`Source` object with the content of the reader
"""
if include_padding:
if not self.source_lines:
string = ''
lines = (self.line_offset + 1, self.line_offset + 1)
elif include_padding:
string = '\n'.join(self.source_lines)
lines = (self.line_offset + 1, self.line_offset + len(self.source_lines))
else:
Expand Down
26 changes: 16 additions & 10 deletions tests/test_frontends.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Deallocation, Associate, BasicType, OMNI, OFP, FP, Enumeration,
config, REGEX, Sourcefile, Import, RawSource, CallStatement,
RegexParserClass, ProcedureType, DerivedType, Comment, Pragma,
PreprocessorDirective, config_override
PreprocessorDirective, config_override, Section
)
from loki.expression import symbols as sym

Expand Down Expand Up @@ -134,9 +134,7 @@ def test_check_alloc_opts(here, frontend):

@pytest.mark.parametrize('frontend', available_frontends())
def test_associates(here, frontend):
"""
Test the use of associate to access and modify other items
"""
"""Test the use of associate to access and modify other items"""

fcode = """
module derived_types_mod
Expand Down Expand Up @@ -255,9 +253,7 @@ def test_associates_deferred(frontend):

@pytest.mark.parametrize('frontend', available_frontends())
def test_associates_expr(here, frontend):
"""
Verify that associates with expressions are supported
"""
"""Verify that associates with expressions are supported"""
fcode = """
subroutine associates_expr(in, out)
implicit none
Expand Down Expand Up @@ -296,9 +292,7 @@ def test_associates_expr(here, frontend):

@pytest.mark.parametrize('frontend', available_frontends())
def test_enum(here, frontend):
"""
Verify that enums are represented correctly
"""
"""Verify that enums are represented correctly"""
# F2008, Note 4.67
fcode = """
subroutine test_enum (out)
Expand Down Expand Up @@ -1490,3 +1484,15 @@ def test_regex_fypp():
assert len(module.routines) == 2
assert module.routines[0].name == 'first_routine'
assert module.routines[1].name == 'last_routine'


@pytest.mark.parametrize(
'frontend',
available_frontends(include_regex=True, xfail=[(OMNI, 'OMNI may segfault on empty files')])
)
@pytest.mark.parametrize('fcode', ['', '\n', '\n\n\n\n'])
def test_frontend_empty_file(frontend, fcode):
"""Ensure that all frontends can handle empty source files correctly (#186)"""
source = Sourcefile.from_source(fcode, frontend=frontend)
assert isinstance(source.ir, Section)
assert not source.to_fortran().strip()
13 changes: 13 additions & 0 deletions tests/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,16 @@ def sanitize_empty_lines_and_comments(string):
for _ in reader:
iterated_code += reader.source_from_current_line().string + '\n'
assert sanitize_empty_lines_and_comments(fcode) == iterated_code


@pytest.mark.parametrize('fcode', ['', '\n'])
def test_fortran_reader_empty(fcode):
"""Test :any:`FortranReader` for empty strings"""
reader = FortranReader(fcode)
assert isinstance(reader, FortranReader)
assert not reader.source_lines
assert not reader.sanitized_lines
source = reader.to_source()
assert isinstance(source, Source)
assert source.lines == (1, 1)
assert source.string == ''

0 comments on commit 9d27cab

Please sign in to comment.