diff --git a/changelog b/changelog index 91395670e2..4d2a0955af 100644 --- a/changelog +++ b/changelog @@ -138,6 +138,9 @@ 43) PR #383. Fixes to various pylint errors. + 44) PR #384 (working towards #354) Adds support for CASE constructs with + a DEFAULT clause when translating Fortran fparser2 to PSyIR. + release 1.7.0 20th December 2018 1) #172 and PR #173 Add support for logical declaration, the save diff --git a/doc/developer_guide/system_specific_setup.rst b/doc/developer_guide/system_specific_setup.rst index 35894746e9..8ffcbd9317 100644 --- a/doc/developer_guide/system_specific_setup.rst +++ b/doc/developer_guide/system_specific_setup.rst @@ -3,7 +3,7 @@ System-specific Developer Set-up ================================ -Section :ref:`setup_user` in the PSyclone User Guide +Section :ref:`user_guide:system_specific_setup` in the PSyclone User Guide describes the setup for a user of PSyclone. It includes all steps necessary to be able to use PSyclone. And while you could obviously do some development, none of the required tools for testing or @@ -12,7 +12,7 @@ documentation creation will be installed. This section adds software that is used to develop and test PSyclone. It includes all packages for testing and creation of documentation in html and pdf. We assume you have already installed -the software described in the :ref:`setup_user` section. +the software described in the :ref:`user_guide:system_specific_setup` section. It contains detailed instructions for Ubuntu 16.04.2 and OpenSUSE 42.2 - if you are working with a different Linux diff --git a/psyclone.pdf b/psyclone.pdf index 8e5853fb01..f87cf8c2e0 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 1b695b56b8..659fbc9745 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -5564,12 +5564,15 @@ def _case_construct_handler(self, node, parent): :type node: :py:class:`fparser.two.Fortran2003.Case_Construct` :param parent: Parent node of the PSyIR node we are constructing. :type parent: :py:class:`psyclone.psyGen.Node` + :returns: PSyIR representation of node :rtype: :py:class:`psyclone.psyGen.IfBlock` + :raises InternalError: If the fparser2 tree has an unexpected \ structure. :raises NotImplementedError: If the fparser2 tree contains an \ unsupported structure and should be placed in a CodeBlock. + ''' from fparser.two import Fortran2003 # Check that the fparser2 parsetree has the expected structure @@ -5582,24 +5585,33 @@ def _case_construct_handler(self, node, parent): "Failed to find closing case statement in: " "{0}".format(str(node))) - # Search for all the CASE clauses in the Case_Construct + # Search for all the CASE clauses in the Case_Construct. We do this + # because the fp2 parse tree has a flat structure at this point with + # the clauses being siblings of the contents of the clauses. The + # final index in this list will hold the position of the end-select + # statement. clause_indices = [] selector = None + # The position of the 'case default' clause, if any + default_clause_idx = None for idx, child in enumerate(node.content): child._parent = node # Retrofit parent info if isinstance(child, Fortran2003.Select_Case_Stmt): selector = child.items[0] if isinstance(child, Fortran2003.Case_Stmt): - # Case Default and value Ranges not supported yet, if found - # we raise a NotImplementedError that the process_node() will - # catch and generate a CodeBlock instead. + # Case value Ranges not supported yet, if found we + # raise a NotImplementedError that the process_node() + # will catch and generate a CodeBlock instead. case_expression = child.items[0].items[0] if isinstance(case_expression, (Fortran2003.Case_Value_Range, Fortran2003.Case_Value_Range_List)): raise NotImplementedError("Case Value Range Statement") - elif case_expression is None: - raise NotImplementedError("Case Default Statement") + if case_expression is None: + # This is a 'case default' clause - store its position. + # We do this separately as this clause is special and + # will be added as a final 'else'. + default_clause_idx = idx clause_indices.append(idx) if isinstance(child, Fortran2003.End_Select_Stmt): clause_indices.append(idx) @@ -5609,6 +5621,9 @@ def _case_construct_handler(self, node, parent): currentparent = parent num_clauses = len(clause_indices) - 1 for idx in range(num_clauses): + # Skip the 'default' clause for now because we handle it last + if clause_indices[idx] == default_clause_idx: + continue start_idx = clause_indices[idx] end_idx = clause_indices[idx+1] clause = node.content[start_idx] @@ -5647,13 +5662,32 @@ def _case_construct_handler(self, node, parent): elsebody = Schedule(parent=currentparent) currentparent.addchild(elsebody) elsebody.addchild(ifblock) - elsebody.ast = node.content[start_idx] - elsebody.ast_end = node.content[end_idx] + elsebody.ast = node.content[start_idx + 1] + elsebody.ast_end = node.content[end_idx - 1] else: rootif = ifblock currentparent = ifblock + if default_clause_idx: + # Finally, add the content of the 'default' clause as a last + # 'else' clause. + elsebody = Schedule(parent=currentparent) + start_idx = default_clause_idx + # Find the next 'case' clause that occurs after 'case default' + # (if any) + end_idx = -1 + for idx in clause_indices: + if idx > default_clause_idx: + end_idx = idx + break + self.process_nodes(parent=elsebody, + nodes=node.content[start_idx + 1: + end_idx], + nodes_parent=node) + currentparent.addchild(elsebody) + elsebody.ast = node.content[start_idx + 1] + elsebody.ast_end = node.content[end_idx - 1] return rootif def _return_handler(self, _, parent): @@ -5845,7 +5879,6 @@ class Symbol(object): :raises ValueError: Provided parameters contain invalid values. ''' - # Tuple with the valid values for the access attribute. valid_scope_types = ('local', 'global_argument') # Tuple with the valid datatypes. diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 719c76fb41..599c03d65d 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -4348,7 +4348,7 @@ def test_fparser2astprocessor_handling_complex_if_construct(f2008_parser): assert nested_if2.children[1].children[0].children[0].name == 'found' -def test_fparser2astprocessor_handling_Case_construct(f2008_parser): +def test_fparser2astprocessor_handling_case_construct(f2008_parser): ''' Test that fparser2 Case_Construct is converted to the expected PSyIR tree structure. ''' @@ -4389,7 +4389,44 @@ def test_fparser2astprocessor_handling_Case_construct(f2008_parser): assert len(ifnode.else_body[0].children) == 2 # SELECT CASE ends here -def test_fparser2astprocessor_handling_invalid_Case_construct(f2008_parser): +def test_fp2astproc_case_default(f2008_parser): + ''' Check that the fparser2ASTProcessor handles SELECT blocks with + a default clause. ''' + from fparser.common.readfortran import FortranStringReader + from fparser.two.Fortran2003 import Execution_Part, Assignment_Stmt + case_clauses = ["CASE default\nbranch3 = 1\nbranch3 = branch3 * 2\n", + "CASE (label1)\nbranch1 = 1\n", + "CASE (label2)\nbranch2 = 1\n"] + # Loop over the 3 possible locations for the 'default' clause + for idx1, idx2, idx3 in [(0, 1, 2), (1, 0, 2), (1, 2, 0)]: + fortran_text = ( + "SELECT CASE (selector)\n" + "{0}{1}{2}" + "END SELECT\n".format(case_clauses[idx1], case_clauses[idx2], + case_clauses[idx3])) + reader = FortranStringReader(fortran_text) + fparser2case_construct = Execution_Part.match(reader)[0][0] + + fake_parent = Node() + processor = Fparser2ASTProcessor() + processor.process_nodes(fake_parent, [fparser2case_construct], None) + assigns = fake_parent.walk(fake_parent.children, Assignment) + # Check that the assignment to 'branch 3' (in the default clause) is + # the deepest in the tree + assert "branch3" in str(assigns[2]) + assert isinstance(assigns[2].ast, Assignment_Stmt) + assert isinstance(assigns[2].parent, Schedule) + assert isinstance(assigns[2].parent.ast, Assignment_Stmt) + assert "branch3 * 2" in str(assigns[2].parent.ast_end) + assert isinstance(assigns[2].parent.parent, IfBlock) + # Check that the if-body of the parent IfBlock also contains + # an Assignment + assert isinstance(assigns[2].parent.parent.children[1], Schedule) + assert isinstance(assigns[2].parent.parent.children[1].children[0], + Assignment) + + +def test_fp2astproc_handling_invalid_case_construct(f2008_parser): ''' Test that the Case_Construct handler raises the proper errors when it parses invalid or unsupported fparser2 trees. ''' @@ -4409,20 +4446,7 @@ def test_fparser2astprocessor_handling_invalid_Case_construct(f2008_parser): processor.process_nodes(fake_parent, [fparser2case_construct], None) assert isinstance(fake_parent.children[0], CodeBlock) - # CASE DEFAULT Statment not supported - reader = FortranStringReader( - '''SELECT CASE (selector) - CASE DEFAULT - branch3 = 1 - END SELECT''') - fparser2case_construct = Execution_Part.match(reader)[0][0] - - fake_parent = Node() - processor = Fparser2ASTProcessor() - processor.process_nodes(fake_parent, [fparser2case_construct], None) - assert isinstance(fake_parent.children[0], CodeBlock) - - # but CASE (default) is just a regular symbol named default + # CASE (default) is just a regular symbol named default reader = FortranStringReader( '''SELECT CASE (selector) CASE (default)