Skip to content

Commit

Permalink
Merge pull request #122 from stfc/empty_only
Browse files Browse the repository at this point in the history
Empty only #118
  • Loading branch information
TeranIvy committed Nov 23, 2018
2 parents c856517 + 65e5280 commit d8066f6
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 151 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Modifications by (in alphabetical order):
* A. R. Porter, Science & Technology Facilities Council, UK
* P. Vitt, University of Siegen, Germany

23/11/2018 PR #122 for issue #118. Bug fix for reporting invalid
Fortran when parsing `use module_name, only:` in fparser2.

21/11/2018 PR #127 for #126. Adds get_child function to help AST traversal.

19/11/2018 PR #124 for #112. Bug fix - spaces within names are now
Expand Down
67 changes: 44 additions & 23 deletions src/fparser/two/Fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -7247,14 +7247,14 @@ class Module_Subprogram(Base): # R1108


class Use_Stmt(StmtBase): # pylint: disable=invalid-name
"""
R1109
'''
Fortran 2003 rule R1109
<use-stmt> = USE [ [ , <module-nature> ] :: ] <module-name>
[ , <rename-list> ]
| USE [ [ , <module-nature> ] :: ] <module-name> ,
ONLY: [ <only-list> ]
"""
use-stmt is USE [ [ , module-nature ] :: ] module-name [ , rename-list ]
or USE [ [ , module-nature ] :: ] module-name ,
ONLY : [ only-list ]
'''
subclass_names = []
use_names = ['Module_Nature', 'Module_Name', 'Rename_List', 'Only_List']

Expand All @@ -7268,10 +7268,11 @@ def match(string):
"ONLY" specification and optional "Rename" or "Only" list)
:rtype: 5-tuple of objects (module name and 4 optional)
'''
# Incorrect 'USE' statement
if string[:3].upper() != 'USE':
line = string.strip()
# Incorrect 'USE' statement or line too short
if line[:3].upper() != 'USE':
return
line = string[3:]
line = line[3:]
# Empty string after 'USE'
if not line:
return
Expand Down Expand Up @@ -7308,49 +7309,69 @@ def match(string):
if nature is not None:
return

i = line.find(',')
if i == -1:
position = line.find(',')
if position == -1:
return nature, dcolon, Module_Name(line), '', None
name = line[:i].rstrip()
name = line[:position].rstrip()
# Missing Module_Name before Only_List
if not name:
return
name = Module_Name(name)
line = line[i+1:].lstrip()
line = line[position+1:].lstrip()
# Missing 'ONLY' specification after 'USE Module_Name,'
if not line:
return
if line[:4].upper() == 'ONLY':
line = line[4:].lstrip()
# Missing ':' after ', ONLY' specification
if not line:
# Expected ':' but there is nothing after the 'ONLY'
# specification
return
if line[0] != ':':
# Expected ':' but there is a different character
# after the 'ONLY' specification
return
line = line[1:].lstrip()
# Missing Only_List/Rename_List after 'USE Module_Name, ONLY:'
if not line:
return
# Missing Only_List after 'USE Module_Name, ONLY:'
return nature, dcolon, name, ', ONLY:', None
return nature, dcolon, name, ', ONLY:', Only_List(line)
return nature, dcolon, name, ',', Rename_List(line)

def tostr(self):
'''
:return: parsed representation of "USE" statement
:rtype: string
:raises InternalError: if items array is not the expected size
:raises InternalError: if items array[2] is not a string or is an \
empty string
:raises InternalError: if items array[3] is 'None' as it should be \
a string
'''
if len(self.items) != 5:
raise InternalError(
"Use_Stmt.tostr(). 'Items' should be of size 5 but found "
"'{0}'.".format(len(self.items)))
if not self.items[2]:
raise InternalError("Use_Stmt.tostr(). 'Items' entry 2 should "
"be a module name but it is empty")
if self.items[3] is None:
raise InternalError("Use_Stmt.tostr(). 'Items' entry 3 should "
"be a string but found 'None'")
usestmt = 'USE'
# Add optional Module_Nature ("INTRINSIC" or "NON_INTRINSIC")
# followed by a double colon to "USE" statement
if self.items[0] is not None and self.items[1] is not None:
usestmt += ', %s %s' % (self.items[0], self.items[1])
if self.items[0] and self.items[1]:
usestmt += ", {0} {1}".format(self.items[0], self.items[1])
# Add optional double colon after "USE" statement without
# Module_Nature (valid Fortran)
elif self.items[0] is None and self.items[1] is not None:
usestmt += ' %s' % (self.items[1])
elif not self.items[0] and self.items[1]:
usestmt += " {0}".format(self.items[1])
# Add Module_Name and optional "ONLY" specifier if present
usestmt += ' %s%s' % (self.items[2], self.items[3])
usestmt += " {0}{1}".format(self.items[2], self.items[3])
# Add optional Only_List or Rename_List if present
if self.items[4] is not None:
usestmt += ' %s' % (self.items[4])
usestmt += " {0}".format(self.items[4])
return usestmt


Expand Down
221 changes: 221 additions & 0 deletions src/fparser/two/tests/fortran2003/test_usestmt_r1109.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Copyright (c) 2018 Science and Technology Facilities Council

# All rights reserved.

# Modifications made as part of the fparser project are distributed
# under the following license:

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:

# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.

# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.

# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''Test Fortran 2003 rule R1109 : This file tests the support for the
Use statement.
'''

import pytest
from fparser.api import get_reader
from fparser.two.Fortran2003 import Use_Stmt
from fparser.two.utils import NoMatchError, InternalError

# match() use ...


# match() 'use x'. Use both string and reader input here, but from
# here on we will just use string input as that is what is passed to
# the match() method
def test_use(f2003_create):
'''Check that a basic use is parsed correctly. Input separately as a
string and as a reader object
'''
def check_use(reader):
'''Internal helper function to avoid code replication.'''
ast = Use_Stmt(reader)
assert "USE my_model" in str(ast)
assert repr(ast) == "Use_Stmt(None, None, Name('my_model'), '', None)"

line = "use my_model"
check_use(line)
reader = get_reader(line)
check_use(reader)


# match() 'use :: x'
def test_use_colons(f2003_create):
'''Check that a basic use with '::' is parsed correctly.'''
line = "use :: my_model"
ast = Use_Stmt(line)
assert "USE :: my_model" in str(ast)
assert repr(ast) == "Use_Stmt(None, '::', Name('my_model'), '', None)"


# match() 'use, nature :: x'
def test_use_nature(f2003_create):
'''Check that a use with a 'nature' specification is parsed correctly.'''
line = "use, intrinsic :: my_model"
ast = Use_Stmt(line)
assert "USE, INTRINSIC :: my_model" in str(ast)
assert repr(ast) == (
"Use_Stmt(Module_Nature('INTRINSIC'), '::', Name('my_model'), "
"'', None)")


# match() 'use x, rename'
def test_use_rename(f2003_create):
'''Check that a use with a nename clause is parsed correctly.'''
line = "use my_module, name=>new_name"
ast = Use_Stmt(line)
assert "USE my_module, name => new_name" in str(ast)
assert repr(ast) == (
"Use_Stmt(None, None, Name('my_module'), ',', "
"Rename(None, Name('name'), Name('new_name')))")


# match() 'use x, only: y'
def test_use_only(f2003_create):
'''Check that a use statement is parsed correctly when there is an
only clause.
'''
line = "use my_model, only: name"
ast = Use_Stmt(line)
assert "USE my_model, ONLY: name" in str(ast)
assert repr(ast) == (
"Use_Stmt(None, None, Name('my_model'), ', ONLY:', Name('name'))")


# match() 'use x, only:'
def test_use_only_empty(f2003_create):
'''Check that a use statement is parsed correctly when there is an
only clause without any content.
'''
line = "use my_model, only:"
ast = Use_Stmt(line)
assert "USE my_model, ONLY:" in str(ast)
assert repr(ast) == (
"Use_Stmt(None, None, Name('my_model'), ', ONLY:', None)")


# match() ' use , nature :: x , name=>new_name'
def test_use_spaces_1(f2003_create):
'''Check that a use statement with spaces works correctly with
renaming.
'''
line = " Use , intrinsic :: my_model , name=>new_name "
ast = Use_Stmt(line)
assert "USE, INTRINSIC :: my_model, name => new_name" in str(ast)
assert repr(ast) == (
"Use_Stmt(Module_Nature('INTRINSIC'), '::', Name('my_model'), ',', "
"Rename(None, Name('name'), Name('new_name')))")


# match() ' use , nature :: x , only : name'
def test_use_spaces_2(f2003_create):
'''Check that a use statement with spaces works correctly with an only
clause.
'''
line = " use , intrinsic :: my_model , only : name "
ast = Use_Stmt(line)
assert "USE, INTRINSIC :: my_model, ONLY: name" in str(ast)
assert repr(ast) == ("Use_Stmt(Module_Nature('INTRINSIC'), '::', "
"Name('my_model'), ', ONLY:', Name('name'))")


# match() mixed case
def test_use_mixed_case(f2003_create):
'''Check that a use statement with mixed case keywords ('use' and
'only') works as expected.
'''
line = "UsE my_model, OnLy: name"
ast = Use_Stmt(line)
assert "USE my_model, ONLY: name" in str(ast)
assert repr(ast) == ("Use_Stmt(None, None, Name('my_model'), ', ONLY:', "
"Name('name'))")

# match() Syntax errors


def test_syntaxerror(f2003_create):
'''Test that NoMatchError is raised for various syntax errors.'''
for line in ["us", "ust", "use", "usemy_model", "use, ", "use, ::",
"use, intrinsic", "use, intrinsic::",
"use, intrinsic my_module", "use,", "use my_model,",
"use my_model, only", "use my_model, only ;",
"use my_model, only name"]:
with pytest.raises(NoMatchError) as excinfo:
_ = Use_Stmt(line)
assert "Use_Stmt: '{0}'".format(line) in str(excinfo)

# match() Internal errors


def test_use_internal_error1(f2003_create):
'''Check that an internal error is raised if the length of the Items
list is not 5 as the str() method assumes that it is.
'''
line = "use my_model"
ast = Use_Stmt(line)
ast.items = (None, None, None, None)
with pytest.raises(InternalError) as excinfo:
str(ast)
assert "should be of size 5 but found '4'" in str(excinfo)


def test_use_internal_error2(f2003_create):
'''Check that an internal error is raised if the module name (entry 2
of Items) is empty or None as the str() method assumes that it is
a string with content.
'''
line = "use my_model"
ast = Use_Stmt(line)
for content in [None, ""]:
ast.items = (None, None, content, None, None)
with pytest.raises(InternalError) as excinfo:
str(ast)
assert ("entry 2 should be a module name but it is "
"empty") in str(excinfo)


def test_use_internal_error3(f2003_create):
'''Check that an internal error is raised if entry 3 of Items is
'None' as the str() method assumes it is a (potentially empty)
string.
'''
line = "use my_model"
ast = Use_Stmt(line)
ast.items = (None, None, "my_module", None, None)
with pytest.raises(InternalError) as excinfo:
str(ast)
assert "entry 3 should be a string but found 'None'" in str(excinfo)
Loading

0 comments on commit d8066f6

Please sign in to comment.