diff --git a/dace/frontend/fortran/ast_components.py b/dace/frontend/fortran/ast_components.py index 332c3a563f..ab0aa9c777 100644 --- a/dace/frontend/fortran/ast_components.py +++ b/dace/frontend/fortran/ast_components.py @@ -987,9 +987,6 @@ def block_nonlabel_do_construct(self, node: FASTNode): body=ast_internal_classes.Execution_Part_Node(execution=body), line_number=do.line_number) - def real_literal_constant(self, node: FASTNode): - return node - def subscript_triplet(self, node: FASTNode): if node.string == ":": return ast_internal_classes.ParDecl_Node(type="ALL") diff --git a/dace/frontend/fortran/ast_utils.py b/dace/frontend/fortran/ast_utils.py index 41cbeff1f9..b52bd31df7 100644 --- a/dace/frontend/fortran/ast_utils.py +++ b/dace/frontend/fortran/ast_utils.py @@ -188,8 +188,31 @@ def intlit2string(self, node: ast_internal_classes.Int_Literal_Node): return "".join(map(str, node.value)) def floatlit2string(self, node: ast_internal_classes.Real_Literal_Node): - - return "".join(map(str, node.value)) + # Typecheck and crash early if unexpected. + assert hasattr(node, 'value') + lit = node.value + assert isinstance(lit, str) + + # Fortran "real literals" may have an additional suffix at the end. + # Examples: + # valid: 1.0 => 1 + # valid: 1. => 1 + # valid: 1.e5 => 1e5 + # valid: 1.d5 => 1e5 + # valid: 1._kinder => 1 (precondition: somewhere earlier, `integer, parameter :: kinder=8`) + # valid: 1.e5_kinder => 1e5 + # not valid: 1.d5_kinder => 1e5 + # TODO: Is there a complete spec of the structure of real literals? + if '_' in lit: + # First, deal with kind specification and remove it altogether, since we know the type anyway. + parts = lit.split('_') + assert 1 <= len(parts) <= 2, f"{lit} is not a valid fortran literal." + lit = parts[0] + assert 'd' not in lit, f"{lit} is not a valid fortran literal." + if 'd' in lit: + # Again, since we know the type anyway, here we just make the s/d/e/ replacement. + lit = lit.replace('d', 'e') + return f"{float(lit)}" def boollit2string(self, node: ast_internal_classes.Bool_Literal_Node): diff --git a/tests/fortran/ast_utils_test.py b/tests/fortran/ast_utils_test.py new file mode 100644 index 0000000000..4ab7b87f35 --- /dev/null +++ b/tests/fortran/ast_utils_test.py @@ -0,0 +1,28 @@ +import pytest + +from dace.frontend.fortran.ast_internal_classes import Real_Literal_Node + +from dace.frontend.fortran.ast_utils import TaskletWriter + + +def test_floatlit2string(): + def parse(fl: str) -> float: + t = TaskletWriter([], []) # The parameters won't matter. + return t.floatlit2string(Real_Literal_Node(value=fl)) + + assert parse('1.0') == '1.0' + assert parse('1.') == '1.0' + assert parse('1.e5') == '100000.0' + assert parse('1.d5') == '100000.0' + assert parse('1._kinder') == '1.0' + assert parse('1.e5_kinder') == '100000.0' + with pytest.raises(AssertionError): + parse('1.d5_kinder') + with pytest.raises(AssertionError): + parse('1._kinder_kinder') + with pytest.raises(ValueError, match="could not convert string to float"): + parse('1.2.0') + with pytest.raises(ValueError, match="could not convert string to float"): + parse('1.d0d0') + with pytest.raises(ValueError, match="could not convert string to float"): + parse('foo') diff --git a/tests/fortran/fortran_language_test.py b/tests/fortran/fortran_language_test.py index 32ab23714b..9034ba20f3 100644 --- a/tests/fortran/fortran_language_test.py +++ b/tests/fortran/fortran_language_test.py @@ -1,22 +1,8 @@ # Copyright 2023 ETH Zurich and the DaCe authors. All rights reserved. -from fparser.common.readfortran import FortranStringReader -from fparser.common.readfortran import FortranFileReader -from fparser.two.parser import ParserFactory -import sys, os import numpy as np -import pytest - -from dace import SDFG, SDFGState, nodes, dtypes, data, subsets, symbolic from dace.frontend.fortran import fortran_parser -from fparser.two.symbol_table import SymbolTable -from dace.sdfg import utils as sdutil - -import dace.frontend.fortran.ast_components as ast_components -import dace.frontend.fortran.ast_transforms as ast_transforms -import dace.frontend.fortran.ast_utils as ast_utils -import dace.frontend.fortran.ast_internal_classes as ast_internal_classes def test_fortran_frontend_real_kind_selector(): @@ -24,23 +10,25 @@ def test_fortran_frontend_real_kind_selector(): Tests that the size intrinsics are correctly parsed and translated to DaCe. """ test_string = """ - PROGRAM real_kind_selector_test - implicit none - INTEGER, PARAMETER :: JPRB = SELECTED_REAL_KIND(13,300) - INTEGER, PARAMETER :: JPIM = SELECTED_INT_KIND(9) - REAL(KIND=JPRB) d(4) - CALL real_kind_selector_test_function(d) - end - - SUBROUTINE real_kind_selector_test_function(d) - REAL(KIND=JPRB) d(4) - INTEGER(KIND=JPIM) i - - i=7 - d(2)=5.5+i - - END SUBROUTINE real_kind_selector_test_function - """ +program real_kind_selector_test + implicit none + integer, parameter :: JPRB = selected_real_kind(13, 300) + real(KIND=JPRB) d(4) + call real_kind_selector_test_function(d) +end + +subroutine real_kind_selector_test_function(d) + implicit none + integer, parameter :: JPRB = selected_real_kind(13, 300) + integer, parameter :: JPIM = selected_int_kind(9) + real(KIND=JPRB) d(4) + integer(KIND=JPIM) i + + i = 7 + d(2) = 5.5 + i + +end subroutine real_kind_selector_test_function +""" sdfg = fortran_parser.create_sdfg_from_string(test_string, "real_kind_selector_test") sdfg.simplify(verbose=True) a = np.full([4], 42, order="F", dtype=np.float64) @@ -129,30 +117,30 @@ def test_fortran_frontend_function_statement1(): """ Tests that the function statement are correctly removed recursively. """ - - test_string = """ - PROGRAM function_statement1_test - implicit none - double precision d(3,4,5) - CALL function_statement1_test_function(d) - end - SUBROUTINE function_statement1_test_function(d) - double precision d(3,4,5) - double precision :: PTARE,RTT(2),FOEDELTA,FOELDCP - double precision :: RALVDCP(2),RALSDCP(2),RES - - FOEDELTA (PTARE) = MAX (0.0,SIGN(1.0,PTARE-RTT(1))) - FOELDCP ( PTARE ) = FOEDELTA(PTARE)*RALVDCP(1) + (1.0-FOEDELTA(PTARE))*RALSDCP(1) - - RTT(1)=4.5 - RALVDCP(1)=4.9 - RALSDCP(1)=5.1 - d(1,1,1)=FOELDCP(3.0) - RES=FOELDCP(3.0) - d(1,1,2)=RES - END SUBROUTINE function_statement1_test_function - """ + test_string = """ +program function_statement1_test + implicit none + double precision d(3, 4, 5) + call function_statement1_test_function(d) +end + +subroutine function_statement1_test_function(d) + double precision d(3, 4, 5) + double precision :: PTARE, RTT(2), FOEDELTA, FOELDCP + double precision :: RALVDCP(2), RALSDCP(2), RES + + FOEDELTA(PTARE) = max(0.0, sign(1.d0, PTARE - RTT(1))) + FOELDCP(PTARE) = FOEDELTA(PTARE)*RALVDCP(1) + (1.0 - FOEDELTA(PTARE))*RALSDCP(1) + + RTT(1) = 4.5 + RALVDCP(1) = 4.9 + RALSDCP(1) = 5.1 + d(1, 1, 1) = FOELDCP(3.d0) + RES = FOELDCP(3.d0) + d(1, 1, 2) = RES +end subroutine function_statement1_test_function +""" sdfg = fortran_parser.create_sdfg_from_string(test_string, "function_statement1_test") sdfg.simplify(verbose=True) d = np.full([3, 4, 5], 42, order="F", dtype=np.float64)