Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/fparser/two/Fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ def match(reader):
[Use_Stmt, Import_Stmt, Implicit_Part, Declaration_Construct],
None,
reader,
strict_order=True,
)


Expand Down Expand Up @@ -1748,6 +1749,7 @@ def match(reader):
End_Type_Stmt,
reader,
match_names=True, # C431
strict_order=True,
)


Expand Down Expand Up @@ -2453,7 +2455,11 @@ def match(reader):
`Name`]) or `None`
"""
return BlockBase.match(
Contains_Stmt, [Binding_Private_Stmt, Proc_Binding_Stmt], None, reader
Contains_Stmt,
[Binding_Private_Stmt, Proc_Binding_Stmt],
None,
reader,
strict_order=True,
)


Expand Down Expand Up @@ -2865,7 +2871,10 @@ class Enum_Def(BlockBase): # R460
@staticmethod
def match(reader):
return BlockBase.match(
Enum_Def_Stmt, [Enumerator_Def_Stmt], End_Enum_Stmt, reader
Enum_Def_Stmt,
[Enumerator_Def_Stmt],
End_Enum_Stmt,
reader,
)


Expand Down Expand Up @@ -11058,6 +11067,7 @@ def match(reader):
reader,
match_names=True,
strict_order=True,
once_only=True,
)


Expand Down Expand Up @@ -11118,6 +11128,8 @@ def match(reader):
[Specification_Part, Execution_Part, Internal_Subprogram_Part],
End_Program_Stmt,
reader,
strict_order=True,
once_only=True,
)

SYMBOL_TABLES.exit_scope()
Expand Down Expand Up @@ -11216,6 +11228,8 @@ def match(reader):
[Specification_Part, Module_Subprogram_Part],
End_Module_Stmt,
reader,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each sub-class can only match once for a valid module? (I initially got worried that this wouldn't work for multiple modules in a file but it's fine.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a Module can only contain one Module_Stmt, one Specification_Part, one Module_Subprogram_Part and one End_Module_Stmt. Multiple modules are supported in 'higher level' rules, such as Program_Unit.

strict_order=True,
once_only=True,
)


Expand Down Expand Up @@ -12723,6 +12737,8 @@ def match(reader):
[Specification_Part, Execution_Part, Internal_Subprogram_Part],
End_Function_Stmt,
reader,
strict_order=True,
once_only=True,
)


Expand Down Expand Up @@ -13010,6 +13026,8 @@ def match(reader):
[Specification_Part, Execution_Part, Internal_Subprogram_Part],
End_Subroutine_Stmt,
reader,
strict_order=True,
once_only=True,
)


Expand Down
82 changes: 56 additions & 26 deletions src/fparser/two/tests/test_comments_and_directives.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2017-2020 Science and Technology Facilities Council
# Copyright (c) 2017-2022 Science and Technology Facilities Council
# All rights reserved.
#
# Modifications made as part of the fparser project are distributed
Expand Down Expand Up @@ -35,10 +35,25 @@

import pytest
from fparser.two.Fortran2003 import Program, Comment, Directive, Subroutine_Subprogram
from fparser.two.utils import walk, FortranSyntaxError
from fparser.api import get_reader

from fparser.api import get_reader
from fparser.two.Fortran2003 import (
Program,
Comment,
Subroutine_Subprogram,
Execution_Part,
Specification_Part,
Block_Nonlabel_Do_Construct,
Main_Program,
Write_Stmt,
End_Program_Stmt,
If_Construct,
Allocate_Stmt,
Derived_Type_Def,
Function_Subprogram,
)
from fparser.two.parser import ParserFactory
from fparser.two.utils import walk, get_child, FortranSyntaxError

# this is required to setup the fortran2003 classes
_ = ParserFactory().create(std="f2003")
Expand Down Expand Up @@ -238,24 +253,21 @@ def test_prog_comments():
# |--> Comment
# |--> Main_Program
# . |--> Program_Stmt
# . |--> Specification_Part
# . . \--> Implicit_Part
# . . \--> Comment
# . |--> Comment
# |--> Execution_Part
# | |--> Write_Stmt
# | \--> Comment
# | |--> Comment
# . .
# .
# |--> Comment
from fparser.two.Fortran2003 import Main_Program, Write_Stmt, End_Program_Stmt

walk(obj.children, Comment, debug=True)
assert type(obj.content[0]) is Comment
assert str(obj.content[0]) == "! A troublesome comment"
assert type(obj.content[1]) is Main_Program
main_prog = obj.content[1]
assert type(main_prog.content[1].content[0].content[0]) is Comment
assert str(main_prog.content[1].content[0].content[0]) == "! A full comment line"
assert type(main_prog.content[1]) is Comment
assert str(main_prog.content[1]) == "! A full comment line"
exec_part = main_prog.content[2]
assert type(exec_part.content[0]) is Write_Stmt
# Check that we have the in-line comment as a second statement
Expand Down Expand Up @@ -295,7 +307,6 @@ def test_function_comments():
! That was a function
end function my_mod
"""
from fparser.two.Fortran2003 import Function_Subprogram

reader = get_reader(source, isfree=True, ignore_comments=False)
fn_unit = Function_Subprogram(reader)
Expand All @@ -304,17 +315,15 @@ def test_function_comments():
# <class 'fparser.two.Fortran2003.Name'>
# <type 'NoneType'>
# <type 'NoneType'>
# <class 'fparser.two.Fortran2003.Specification_Part'>
# <class 'fparser.two.Fortran2003.Implicit_Part'>
# <class 'fparser.two.Fortran2003.Comment'>
# <class 'fparser.two.Fortran2003.Comment'>
# <type 'str'>, "'! This is a function'"
comment = fn_unit.content[1].content[0].content[0]
comment = fn_unit.content[1]
assert isinstance(comment, Comment)
assert "! This is a function" in str(comment)
comment = fn_unit.content[1].content[2].content[0]
comment = fn_unit.content[2].content[2]
assert isinstance(comment, Comment)
assert "! Comment1" in str(comment)
exec_part = fn_unit.content[2]
exec_part = fn_unit.content[3]
comment = exec_part.content[1]
assert isinstance(comment, Comment)
assert "! Comment2" in str(comment)
Expand All @@ -338,16 +347,15 @@ def test_subroutine_comments():
reader = get_reader(source, isfree=True, ignore_comments=False)
fn_unit = Subroutine_Subprogram(reader)
assert isinstance(fn_unit, Subroutine_Subprogram)
walk(fn_unit.children, Comment, debug=True)
spec_part = fn_unit.content[1]
comment = spec_part.content[0].content[0]
comment = fn_unit.content[1]
assert isinstance(comment, Comment)
assert "! First comment" in str(comment)
comment = spec_part.content[2].content[0]
spec_part = fn_unit.children[2]
comment = spec_part.children[2]
assert isinstance(comment, Comment)
assert comment.parent is spec_part.content[2]
assert comment.parent is spec_part
assert "! Body comment" in str(comment)
exec_part = fn_unit.content[2]
exec_part = fn_unit.content[3]
comment = exec_part.content[1]
assert isinstance(comment, Comment)
assert comment.parent is exec_part
Expand All @@ -364,7 +372,6 @@ def test_derived_type():
! Ending comment
end type my_type
"""
from fparser.two.Fortran2003 import Derived_Type_Def

reader = get_reader(source, isfree=True, ignore_comments=False)
dtype = Derived_Type_Def(reader)
Expand Down Expand Up @@ -395,8 +402,6 @@ def test_action_stmts():
my_array2(size))
end if
"""
from fparser.two.Fortran2003 import If_Construct, Allocate_Stmt
from fparser.two.utils import get_child

reader = get_reader(source, isfree=True, ignore_comments=False)
ifstmt = If_Construct(reader)
Expand All @@ -407,6 +412,31 @@ def test_action_stmts():
assert cmt.parent is ifstmt


def test_do_loop_coments():
"""Check that comments around and within do loops appear in the expected
places in the tree."""
source = """\
program test
integer :: arg1
integer :: iterator
!comment_out 1
arg1 = 10
!comment_out 2
do iterator = 0,arg1
!comment_in
print *, iterator
end do
!comment_out 3
end program test"""
reader = get_reader(source, isfree=True, ignore_comments=False)
obj = Program(reader)
comments = walk(obj, Comment)
assert isinstance(comments[0].parent, Specification_Part)
assert isinstance(comments[1].parent, Execution_Part)
assert isinstance(comments[2].parent, Block_Nonlabel_Do_Construct)
assert isinstance(comments[3].parent, Execution_Part)


def test_directive_stmts():
"""Test that directives are created instead of comments when
appropriate."""
Expand Down
47 changes: 47 additions & 0 deletions src/fparser/two/tests/test_fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,53 @@ def test_specification_part():
assert isinstance(obj, tcls), repr(obj)
assert "TYPE :: a\nEND TYPE a\nTYPE :: b\nEND TYPE b" in str(obj)

obj = tcls(
get_reader(
"""\
! comment1
use a
! comment2
use b
! comment3
#define x
use c
include 'fred'
#ifdef x
use d
#endif
""",
ignore_comments=False,
)
)
assert isinstance(obj, tcls), repr(obj)
expected = (
"! comment1\nUSE a\n! comment2\nUSE b\n! comment3\n#define x\n"
"USE c\nINCLUDE 'fred'\n#ifdef x\nUSE d\n#endif"
)
assert expected in str(obj)

# Test a correct order of subclasses matches and then an incorrect
# order of subclasses fails to match (due to the strict_order flag
# being set). In this case Use_Stmt should precede
# Declaration_Construct
code = "USE my_mod\n" "INTEGER :: a"
reader = get_reader(code)
tcls = Specification_Part
obj = tcls(reader)
assert str(obj) == code

reader = get_reader("""
module mymod
integer :: a
use my_mod
end module
""")

with pytest.raises(NoMatchError) as excinfo:
obj = Module(reader)
error = str(excinfo.value)
assert "at line 4\n>>> use my_mod" in error


#
# SECTION 3
Expand Down
24 changes: 13 additions & 11 deletions src/fparser/two/tests/utils/test_blockbase.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019-2021, Science and Technology Facilities Council
# Copyright (c) 2019-2022, Science and Technology Facilities Council

# All rights reserved.

Expand Down Expand Up @@ -81,20 +81,22 @@ def test_include(f2003_create):
),
ignore_comments=False,
)
result = BlockBase.match(startcls, subclasses, endcls, reader)
result = BlockBase.match(
startcls, subclasses, endcls, reader, strict_order=True, once_only=True
)
assert (
"([Include_Stmt(Include_Filename('1')), Comment('! comment1'), "
"Program_Stmt('PROGRAM', Name('test')), Specification_Part("
"Implicit_Part(Include_Stmt(Include_Filename('2')), "
"Comment('! comment2')), Type_Declaration_Stmt(Intrinsic_Type_Spec("
"'INTEGER', None), None, Entity_Decl_List(',', (Entity_Decl(Name("
"'i'), None, None, None),))), Implicit_Part(Include_Stmt("
"Include_Filename('3')), Comment('! comment3'))), Execution_Part("
"Assignment_Stmt(Name('i'), '=', Int_Literal_Constant('1', None)), "
"Include_Stmt(Include_Filename('4')), Comment('! comment4')), "
"Program_Stmt('PROGRAM', Name('test')), Include_Stmt("
"Include_Filename('2')), Comment('! comment2'), Specification_Part"
"(Type_Declaration_Stmt(Intrinsic_Type_Spec('INTEGER', None), None, "
"Entity_Decl_List(',', (Entity_Decl(Name('i'), None, None, None),))), "
"Include_Stmt(Include_Filename('3')), Comment('! comment3')), "
"Execution_Part(Assignment_Stmt(Name('i'), '=', "
"Int_Literal_Constant('1', None)), Include_Stmt("
"Include_Filename('4')), Comment('! comment4')), "
"Internal_Subprogram_Part(Contains_Stmt('CONTAINS'), Include_Stmt("
"Include_Filename('5')), Comment('! comment5')), End_Program_Stmt("
"'PROGRAM', Name('test'))],)" in str(result).replace("u'", "'")
"'PROGRAM', Name('test'))],)" in str(result)
)
assert "should" not in str(result)

Expand Down
Loading
Loading