Skip to content

Commit 1038142

Browse files
authored
Merge pull request #514 from stfc/428_f08_intrinsic
(Towards #428) Implementation of the Fortran2008 intrinsics
2 parents ea5c601 + 5a27f24 commit 1038142

6 files changed

Lines changed: 160 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Modifications by (in alphabetical order):
2222
* P. Vitt, University of Siegen, Germany
2323
* A. Voysey, UK Met Office
2424

25+
25/06/2026 PR #514 towards #428. Add some Fortran2008-only intrinsics.
26+
2527
19/06/2026 PR #513 for #512. Fix circular import in Fortran2008.
2628

2729
## Release 0.2.3 (11/06/2026) ##

doc/source/developers_guide.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ returned. An example of a simple choice rule is `R202`. See the
170170
:ref:`program-unit-class` section for a description of its
171171
implementation.
172172

173+
Another example is the support for Fortran 2008 intrinsics.
174+
These are defined in the `Fortran2008_Intrinsic_Names` class, which is then
175+
defined in the `subclass_names` list of the base `Intrinsic_Names` class in
176+
the Fortran2003 spec. When fparser runs with the 2008 standard, it will attempt
177+
to match both the Fortran2003 `Intrinsic_Names` and the
178+
`Fortran2008_Intrinsic_Names` classes, but with only the 2003 standard it will
179+
only use the base Fortran2003 intrinsic lists.
180+
173181
The `use_names` list should contain any classes that are referenced by the
174182
implementation of the current class. These lists of names are aggregated
175183
(along with `subclass_names`) and used to ensure that all necessary `Scalar_`,

src/fparser/two/Fortran2003.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12459,8 +12459,8 @@ class Intrinsic_Name(STRINGBase): # No explicit rule
1245912459

1246012460
subclass_names = []
1246112461

12462-
@staticmethod
12463-
def match(string):
12462+
@classmethod
12463+
def match(cls, string):
1246412464
"""Attempt to match the input `string` with the intrinsic function
1246512465
names defined in `generic_function_names` or
1246612466
`specific_function_names`. If there is a match the resultant
@@ -12473,7 +12473,7 @@ def match(string):
1247312473
:rtype: (str,) or NoneType
1247412474
1247512475
"""
12476-
return STRINGBase.match(Intrinsic_Name.function_names, string)
12476+
return STRINGBase.match(cls.function_names, string)
1247712477

1247812478

1247912479
class Intrinsic_Function_Reference(CallBase): # No explicit rule
@@ -12486,9 +12486,12 @@ class Intrinsic_Function_Reference(CallBase): # No explicit rule
1248612486

1248712487
subclass_names = []
1248812488
use_names = ["Intrinsic_Name", "Actual_Arg_Spec_List"]
12489+
# Set the type of Intrinsic_Name to be used (so it can be overridden
12490+
# in subclasses).
12491+
_intrinsic_type = Intrinsic_Name
1248912492

12490-
@staticmethod
12491-
def match(string):
12493+
@classmethod
12494+
def match(cls, string):
1249212495
"""Match the string as an intrinsic function. Also check that the
1249312496
number of arguments provided matches the number expected by
1249412497
the intrinsic.
@@ -12506,12 +12509,12 @@ def match(string):
1250612509
(that overrides it) into scope.
1250712510
1250812511
"""
12509-
result = CallBase.match(Intrinsic_Name, Actual_Arg_Spec_List, string)
12512+
result = CallBase.match(cls._intrinsic_type, Actual_Arg_Spec_List, string)
1251012513
if not result:
1251112514
return None
12512-
1251312515
# There is a match so check the number of args provided
1251412516
# matches the number of args expected by the intrinsic.
12517+
intrinsic_type = type(result[0])
1251512518
function_name = str(result[0])
1251612519
function_args = result[1]
1251712520

@@ -12528,16 +12531,15 @@ def match(string):
1252812531
pass
1252912532

1253012533
nargs = 0 if function_args is None else len(function_args.items)
12531-
12532-
if function_name in Intrinsic_Name.specific_function_names.keys():
12534+
if function_name in intrinsic_type.specific_function_names.keys():
1253312535
# If this is a specific function then use its generic
1253412536
# name to test min and max number of arguments.
12535-
test_name = Intrinsic_Name.specific_function_names[function_name]
12537+
test_name = intrinsic_type.specific_function_names[function_name]
1253612538
else:
1253712539
test_name = function_name
1253812540

12539-
min_nargs = Intrinsic_Name.generic_function_names[test_name]["min"]
12540-
max_nargs = Intrinsic_Name.generic_function_names[test_name]["max"]
12541+
min_nargs = intrinsic_type.generic_function_names[test_name]["min"]
12542+
max_nargs = intrinsic_type.generic_function_names[test_name]["max"]
1254112543

1254212544
# None indicates an unlimited number of arguments
1254312545
if max_nargs is None:

src/fparser/two/Fortran2008/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@
101101
)
102102
from fparser.two.Fortran2008.label_do_stmt_r816 import Label_Do_Stmt
103103
from fparser.two.Fortran2008.nonlabel_do_stmt_r817 import Nonlabel_Do_Stmt
104+
from fparser.two.Fortran2008.intrinsics_f08 import (
105+
Intrinsic_Name,
106+
Intrinsic_Function_Reference,
107+
)
104108

105109
# pylint: disable=eval-used
106110
# pylint: disable=exec-used
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# -----------------------------------------------------------------------------
2+
# BSD 3-Clause License
3+
#
4+
# Copyright (c) 2026, Science and Technology Facilities Council.
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without
8+
# modification, are permitted provided that the following conditions are met:
9+
#
10+
# * Redistributions of source code must retain the above copyright notice, this
11+
# list of conditions and the following disclaimer.
12+
#
13+
# * Redistributions in binary form must reproduce the above copyright notice,
14+
# this list of conditions and the following disclaimer in the documentation
15+
# and/or other materials provided with the distribution.
16+
#
17+
# * Neither the name of the copyright holder nor the names of its
18+
# contributors may be used to endorse or promote products derived from
19+
# this software without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31+
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
# POSSIBILITY OF SUCH DAMAGE.
33+
# -----------------------------------------------------------------------------
34+
35+
"""
36+
Module containing Fortran2008 Intrinsics.
37+
"""
38+
39+
from fparser.two.Fortran2003 import Intrinsic_Name as F2003_Intrinsic_Name
40+
from fparser.two.Fortran2003 import (
41+
Intrinsic_Function_Reference as F2003_Intrinsic_Function_Reference,
42+
)
43+
44+
45+
class Intrinsic_Name(F2003_Intrinsic_Name):
46+
"""
47+
Represents the name of a Fortran 2008 intrinsic function.
48+
49+
All generic intrinsic names are specified as keys in the
50+
`generic_function_names` dictionary, with their values indicating
51+
the minimum and maximum number of arguments allowed for this
52+
intrinsic function. A `-1` indicates an unlimited number of
53+
arguments. The names are split into the categories specified in
54+
the Fortran2003 specification document.
55+
56+
All specific intrinsic names (which have a different name to their
57+
generic counterpart) are specified as keys in the
58+
`specific_function_names` dictionary, with their values indicating
59+
which generic function they are associated with
60+
"""
61+
62+
f08_math_intrinsics = {
63+
"ERF": {"min": 1, "max": 1},
64+
"GAMMA": {"min": 1, "max": 1},
65+
}
66+
67+
f08_bitshift_intrinsics = {
68+
"SHIFTL": {"min": 2, "max": 2},
69+
"SHIFTR": {"min": 2, "max": 2},
70+
"SHIFTA": {"min": 2, "max": 2},
71+
}
72+
73+
# Create the dicts (not inherited from F2003_Intrinsic_Name)
74+
generic_function_names = {}
75+
generic_function_names.update(F2003_Intrinsic_Name.generic_function_names)
76+
generic_function_names.update(f08_math_intrinsics)
77+
generic_function_names.update(f08_bitshift_intrinsics)
78+
79+
specific_function_names = F2003_Intrinsic_Name.specific_function_names
80+
81+
# A list of all function names
82+
function_names = list(generic_function_names.keys()) + list(
83+
specific_function_names.keys()
84+
)
85+
86+
87+
class Intrinsic_Function_Reference(F2003_Intrinsic_Function_Reference):
88+
"""
89+
Represents Fortran intrinsics::
90+
91+
function-reference is intrinsic-name ( [ actual-arg-spec-list ] )
92+
93+
"""
94+
95+
# Set the type of Intrinsic_Name to be used
96+
_intrinsic_type = Intrinsic_Name

src/fparser/two/tests/fortran2008/test_intrinsics_2008.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@
3232
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3333
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3434

35-
"""Test intrinsic handling within Fortran2008. At the moment, the only
36-
special consideration (beyond 2003) is that we can't disambiguate a
37-
shadowed intrinsic within a submodule.
35+
"""Test intrinsic handling within Fortran2008. In particular: the special
36+
consideration (beyond 2003) is that we can't disambiguate a shadowed
37+
intrinsic within a submodule. There are also a few new intrinsic
38+
functions.
3839
3940
"""
4041

4142
import pytest
4243
from fparser.api import get_reader
44+
from fparser.common.readfortran import FortranStringReader
4345
from fparser.two import Fortran2003, Fortran2008
4446
from fparser.two.utils import walk
4547

@@ -63,3 +65,34 @@ def test_intrinsic_in_submodule():
6365
""")
6466
ast = Fortran2008.Submodule(reader)
6567
assert not walk(ast, Fortran2003.Intrinsic_Function_Reference)
68+
69+
70+
def test_f2008_intrinsic(f2008_parser):
71+
"""Test Fortran2008 intrinsic is created with the f2008 parser."""
72+
73+
reader = FortranStringReader("""subroutine test
74+
integer :: i
75+
76+
i = erf(i)
77+
end subroutine test
78+
""")
79+
tree = f2008_parser(reader)
80+
intrinsic = walk(tree, Fortran2008.Intrinsic_Name)
81+
assert len(intrinsic) == 1
82+
assert str(intrinsic[0]) == "ERF"
83+
84+
85+
def test_f2008_intrinsic_f2003_parse(f2003_parser):
86+
"""Test Fortran2008 intrinsic is not created with the f2003 parser."""
87+
reader = FortranStringReader("""subroutine test
88+
integer :: i
89+
90+
i = erf(i)
91+
end subroutine test
92+
""")
93+
tree = f2003_parser(reader)
94+
intrinsic = walk(tree, Fortran2008.Intrinsic_Name)
95+
assert len(intrinsic) == 0
96+
partref = walk(tree, Fortran2003.Part_Ref)
97+
assert len(partref) == 1
98+
assert str(partref[0]) == "erf(i)"

0 commit comments

Comments
 (0)