Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty only #118 #122

Merged
merged 9 commits into from
Nov 23, 2018
64 changes: 42 additions & 22 deletions src/fparser/two/Fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -7235,14 +7235,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 @@ -7256,10 +7256,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 @@ -7296,49 +7297,68 @@ 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()
if not line:
# Expected ':' but there is nothing after the 'ONLY'
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment describes nicely what the if test does. It is quite similar to the comment in line 7318 which is not the best description of what the if test in 7319 does (tests that character after ONLY is not the permitted :).
Would you kindly update the comment in 7318? I am aware the original developer made it unclear :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've hopefully made it more clear :-)

# specification
return
# Missing ':' after ', ONLY' specification
if line[0] != ':':
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 "
Copy link
Collaborator

Choose a reason for hiding this comment

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

pylint says W:7343, 0: Bad indentation. Found 16 spaces, expected 12 (bad-indentation)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops, I missed that. Thanks.

"be a module name but it is empty")
if self.items[3] is None:
raise InternalError("Use_Stmt.tostr(). 'Items' entry 3 should "
Copy link
Collaborator

Choose a reason for hiding this comment

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

pylint says W:7346, 0: Bad indentation. Found 16 spaces, expected 12 (bad-indentation)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And again.

"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)