Skip to content

Commit

Permalink
Merge pull request #435 from stfc/426_convert_arg_to_open
Browse files Browse the repository at this point in the history
(Closes #426) Add convert argument to open().
  • Loading branch information
sergisiso authored Jan 29, 2024
2 parents 0410062 + 53f32fa commit 6d8481c
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 163 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Modifications by (in alphabetical order):
* P. Vitt, University of Siegen, Germany
* A. Voysey, UK Met Office

29/01/2024 PR #435 for #426. Add support for the CONVERT extension of the open()
intrinsic.

25/01/2024 PR #418 for #313. Allow intrinsic shadowing and improve fparser symbol table.

11/01/2024 PR #439 for #432. Fix RTD build and clean up setuptools config.
Expand Down
15 changes: 14 additions & 1 deletion doc/source/fparser2.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. Copyright (c) 2017-2022 Science and Technology Facilities Council.
.. Copyright (c) 2017-2024 Science and Technology Facilities Council.
All rights reserved.
Expand Down Expand Up @@ -353,6 +353,19 @@ extension adds support for the dollar descriptor in fparser.
For more information see
https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-dollar-sign-and-backslash-editing

CONVERT argument to OPEN
++++++++++++++++++++++++

The CONVERT argument may be used to specify how unformatted data being read
from file is to be converted before being stored. For example::

OPEN(unit=23, file="some_data", form='unformatted',access='sequential', &
convert="LITTLE_ENDIAN")

This extension is supported by (at least) the Gnu, Intel and Cray compilers
but is not a part of any Fortran standard. More details can be found at
https://gcc.gnu.org/onlinedocs/gfortran/CONVERT-specifier.html

Classes
-------

Expand Down
148 changes: 82 additions & 66 deletions src/fparser/two/Fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
DynamicImport,
)
from fparser.two.utils import (
EXTENSIONS,
NoMatchError,
FortranSyntaxError,
InternalSyntaxError,
Expand Down Expand Up @@ -323,7 +324,6 @@ def match(reader):


class Include_Filename(StringBase): # pylint: disable=invalid-name

"""Implements the matching of a filename from an include statement."""

# There are no other classes. This is a simple string match.
Expand All @@ -346,7 +346,6 @@ def match(string):


class Include_Stmt(Base): # pylint: disable=invalid-name

"""Implements the matching of a Fortran include statement. There is no
rule for this as the compiler is expected to inline any content
from an include statement when one is found. However, for a parser
Expand Down Expand Up @@ -2602,7 +2601,6 @@ def tostr(self):


class Binding_Attr(STRINGBase): # pylint: disable=invalid-name

"""
Fortran2003 Rule R453::
Expand Down Expand Up @@ -2632,7 +2630,6 @@ def match(string):


class Final_Binding(StmtBase, WORDClsBase): # pylint: disable=invalid-name

"""
Fortran2003 Rule R454::
Expand Down Expand Up @@ -4232,9 +4229,7 @@ def match(string):
:rtype: (str, Cray_Pointer_Decl_List) or None
"""
from fparser.two.utils import EXTENSIONS

if "cray-pointer" not in EXTENSIONS:
if "cray-pointer" not in EXTENSIONS():
return None
return WORDClsBase.match(
"POINTER", Cray_Pointer_Decl_List, string, require_cls=True
Expand Down Expand Up @@ -8546,7 +8541,9 @@ class Internal_File_Variable(Base): # R903

class Open_Stmt(StmtBase, CALLBase): # R904
"""
<open-stmt> = OPEN ( <connect-spec-list> )
R904 is:
open-stmt is OPEN ( connect-spec-list )
"""

subclass_names = []
Expand All @@ -8561,27 +8558,32 @@ def match(string):

class Connect_Spec(KeywordValueBase):
"""
R905::
R905 is:
<connect-spec> = [ UNIT = ] <file-unit-number>
| ACCESS = <scalar-default-char-expr>
| ACTION = <scalar-default-char-expr>
| ASYNCHRONOUS = <scalar-default-char-expr>
| BLANK = <scalar-default-char-expr>
| DECIMAL = <scalar-default-char-expr>
| DELIM = <scalar-default-char-expr>
| ENCODING = <scalar-default-char-expr>
| ERR = <label>
| FILE = <file-name-expr>
| FORM = <scalar-default-char-expr>
| IOMSG = <iomsg-variable>
| IOSTAT = <scalar-int-variable>
| PAD = <scalar-default-char-expr>
| POSITION = <scalar-default-char-expr>
| RECL = <scalar-int-expr>
| ROUND = <scalar-default-char-expr>
| SIGN = <scalar-default-char-expr>
| STATUS = <scalar-default-char-expr>
connect-spec is [ UNIT = ] file-unit-number
or ACCESS = scalar-default-char-expr
or ACTION = scalar-default-char-expr
or ASYNCHRONOUS = scalar-default-char-expr
or BLANK = scalar-default-char-expr
[ or CONVERT = scalar-default-char-expr ]
or DECIMAL = scalar-default-char-expr
or DELIM = scalar-default-char-expr
or ENCODING = scalar-default-char-expr
or ERR = label
or FILE = file-name-expr
or FORM = scalar-default-char-expr
or IOMSG = iomsg-variable
or IOSTAT = scalar-int-variable
or PAD = scalar-default-char-expr
or POSITION = scalar-default-char-expr
or RECL = scalar-int-expr
or ROUND = scalar-default-char-expr
or SIGN = scalar-default-char-expr
or STATUS = scalar-default-char-expr
Note that CONVERT is not a part of the Fortran standard but is supported
by several major compilers (Gnu, Intel, Cray etc.) and thus is matched
by fparser if the utils.EXTENSIONS() list includes the string 'open-convert'.
"""

Expand All @@ -8596,44 +8598,64 @@ class Connect_Spec(KeywordValueBase):
"Scalar_Int_Variable",
]

@staticmethod
def match(string):
"""
@classmethod
def _keyword_value_list(cls):
"""
Defines the valid keywords and corresponding classes to match against.
This has to be a method rather than a class property as those classes
are generated after this class has been created.
:returns: list of keyword, class pairs to match against.
:rtype: list[tuple[str, type]]
"""
result = [
("ACCESS", Scalar_Default_Char_Expr),
("ACTION", Scalar_Default_Char_Expr),
("ASYNCHRONOUS", Scalar_Default_Char_Expr),
("BLANK", Scalar_Default_Char_Expr),
("DECIMAL", Scalar_Default_Char_Expr),
("DELIM", Scalar_Default_Char_Expr),
("ENCODING", Scalar_Default_Char_Expr),
("FORM", Scalar_Default_Char_Expr),
("PAD", Scalar_Default_Char_Expr),
("POSITION", Scalar_Default_Char_Expr),
("ROUND", Scalar_Default_Char_Expr),
("SIGN", Scalar_Default_Char_Expr),
("STATUS", Scalar_Default_Char_Expr),
("ERR", Label),
("FILE", File_Name_Expr),
("IOSTAT", Scalar_Int_Variable),
("IOMSG", Iomsg_Variable),
("RECL", Scalar_Int_Expr),
("UNIT", File_Unit_Number),
]
if "open-convert" in EXTENSIONS():
# The CONVERT keyword is a non-standard extension supported by
# many compilers.
result.append(("CONVERT", Scalar_Default_Char_Expr))
return result

@classmethod
def match(cls, string):
"""Implements the matching for connect-spec.
Note that this is implemented as a `classmethod` (not a
`staticmethod`), using attribute keywords from the list provided
as a class method. This allows expanding this list for
Fortran 2008 without having to reimplement the matching.
:param str string: Fortran code to check for a match
:return: 2-tuple containing the keyword and value or None if the
supplied string is not a match
:rtype: 2-tuple containing keyword (e.g. "UNIT") and associated value
"""
if "=" not in string:
# The only argument which need not be named is the unit number
return "UNIT", File_Unit_Number(string)
# We have a keyword-value pair. Check whether it is valid...
for keyword, value in [
(
[
"ACCESS",
"ACTION",
"ASYNCHRONOUS",
"BLANK",
"DECIMAL",
"DELIM",
"ENCODING",
"FORM",
"PAD",
"POSITION",
"ROUND",
"SIGN",
"STATUS",
],
Scalar_Default_Char_Expr,
),
("ERR", Label),
("FILE", File_Name_Expr),
("IOSTAT", Scalar_Int_Variable),
("IOMSG", Iomsg_Variable),
("RECL", Scalar_Int_Expr),
("UNIT", File_Unit_Number),
]:
for keyword, value in cls._keyword_value_list():
try:
obj = KeywordValueBase.match(keyword, value, string, upper_lhs=True)
except NoMatchError:
Expand Down Expand Up @@ -10056,9 +10078,7 @@ def match(string):
:rtype: str
"""
from fparser.two.utils import EXTENSIONS

if "hollerith" not in EXTENSIONS:
if "hollerith" not in EXTENSIONS():
return None
if not string:
return None
Expand Down Expand Up @@ -10573,9 +10593,7 @@ def match(string):
if not strip_string:
return None
if len(strip_string) == 1 and strip_string in "/:$":
from fparser.two.utils import EXTENSIONS

if strip_string == "$" and "dollar-descriptor" not in EXTENSIONS:
if strip_string == "$" and "dollar-descriptor" not in EXTENSIONS():
return None
return None, strip_string
if strip_string[-1] == "/":
Expand Down Expand Up @@ -10684,9 +10702,7 @@ def match(string):
return start, number_obj
if strip_string_upper[-1] == "X":
# We match *X
from fparser.two.utils import EXTENSIONS

if "x-format" in EXTENSIONS and len(strip_string_upper) == 1:
if "x-format" in EXTENSIONS() and len(strip_string_upper) == 1:
# The match just contains 'X' which is not valid
# fortran 2003 but is an accepted extension
return None, "X"
Expand Down
66 changes: 12 additions & 54 deletions src/fparser/two/Fortran2008/connect_spec_r905.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2023, Science and Technology Facilities Council.
# Copyright (c) 2023-2024, Science and Technology Facilities Council.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -48,7 +48,8 @@

class Connect_Spec(Connect_Spec_2003):
"""
Fortran2008 rule R905.
Fortran2008 rule R905. Extends the Fortran2003 definition with support for
the NEWUNIT specifier.
connect-spec is [ UNIT = ] file-unit-number
or ACCESS = scalar-default-char-expr
Expand Down Expand Up @@ -104,58 +105,15 @@ class Connect_Spec(Connect_Spec_2003):
"Scalar_Int_Variable",
]

@staticmethod
def match(string):
@classmethod
def _keyword_value_list(cls):
"""
:param str string: Fortran code to check for a match
Extends the list of keywords supported in Fortran2003 with NEWUNIT.
:returns: 2-tuple containing the keyword and value or None if the
supplied string is not a match
:rtype: Optional[Tuple[str, Any]]
"""
# Avoid circular dependencies by importing here.
# pylint: disable=import-outside-toplevel
from fparser.two.Fortran2008 import (
Scalar_Default_Char_Expr,
Scalar_Int_Variable,
Scalar_Int_Expr,
)
:returns: list of keyword, class pairs to match against.
:rtype: list[tuple[str, type]]
if "=" not in string:
# The only argument which need not be named is the unit number
return "UNIT", File_Unit_Number(string)
# We have a keyword-value pair. Check whether it is valid...
for keyword, value in [
(
[
"ACCESS",
"ACTION",
"ASYNCHRONOUS",
"BLANK",
"DECIMAL",
"DELIM",
"ENCODING",
"FORM",
"PAD",
"POSITION",
"ROUND",
"SIGN",
"STATUS",
],
Scalar_Default_Char_Expr,
),
("ERR", Label),
("FILE", File_Name_Expr),
("IOSTAT", Scalar_Int_Variable),
("IOMSG", Iomsg_Variable),
("RECL", Scalar_Int_Expr),
("UNIT", File_Unit_Number),
("NEWUNIT", File_Unit_Number),
]:
try:
obj = KeywordValueBase.match(keyword, value, string, upper_lhs=True)
except NoMatchError:
obj = None
if obj is not None:
return obj
return None
"""
result = Connect_Spec_2003._keyword_value_list()
result.append(("NEWUNIT", File_Unit_Number))
return result
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019 Science and Technology Facilities Council
# Copyright (c) 2019-2024 Science and Technology Facilities Council.

# All rights reserved.

Expand Down Expand Up @@ -35,6 +35,7 @@
"""Tests for a Fortran 2003 R1011 control edit descriptor."""

import pytest
from fparser.two import utils
from fparser.two.Fortran2003 import Control_Edit_Desc
from fparser.two.utils import NoMatchError, InternalError

Expand Down Expand Up @@ -80,8 +81,6 @@ def test_dollar_valid(f2003_create, monkeypatch):
example with spaces.
"""
from fparser.two import utils

monkeypatch.setattr(utils, "EXTENSIONS", ["dollar-descriptor"])
for my_input in ["$", " $ "]:
ast = Control_Edit_Desc(my_input)
Expand All @@ -93,9 +92,7 @@ def test_dollar_invalid(f2003_create, monkeypatch):
the 'dollar-format' extension is not in the EXTENSIONS list.
"""
from fparser.two import utils

monkeypatch.setattr(utils, "EXTENSIONS", [])
monkeypatch.setattr(utils, "_EXTENSIONS", [])
for my_input in ["$", " $ "]:
with pytest.raises(NoMatchError):
_ = Control_Edit_Desc(my_input)
Expand Down
Loading

0 comments on commit 6d8481c

Please sign in to comment.