Hi all,
The attached implements the last piece of the puzzle for EX formatting. In this
case reading list directed input where hex format is found.
Regression tested on x86_64.
OK for mainline? Maybe backport?
Regards,
Jerry
---
fortran: Implement list directed read of hex float formats.
PR libfortran/93727
libgfortran/ChangeLog:
* io/list_read.c (parse_real): Parse the possible hexadecimal
float formatted and then allow the convert_real function to
validate the result.
gcc/testsuite/ChangeLog:
* gfortran.dg/EXformat_5.F90: New test.
---commit 850dd8c506e4847fbb510543fbcef03904f3a976
Author: Jerry DeLisle <[email protected]>
Date: Wed Jul 15 20:29:38 2026 -0700
fortran: Implement list directed read of hex float formats.
PR libfortran/93727
libgfortran/ChangeLog:
* io/list_read.c (parse_real): Parse the possible hexadecimal
float formatted and then allow the convert_real function to
validate the result.
gcc/testsuite/ChangeLog:
* gfortran.dg/EXformat_5.F90: New test.
diff --git a/gcc/testsuite/gfortran.dg/EXformat_5.F90 b/gcc/testsuite/gfortran.dg/EXformat_5.F90
new file mode 100644
index 00000000000..cfd699dd641
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/EXformat_5.F90
@@ -0,0 +1,131 @@
+! { dg-do run }
+! PR93727 Test hex float for list-directed READ.
+
+program main
+ implicit none
+ call test04
+ call test08
+ call test10
+ call test16
+
+contains
+
+subroutine test04
+ real(4) :: r4, a, b
+ real(4) :: arr(3)
+ complex(4) :: z
+ character(len=64) :: s
+
+ ! Round-trip
+ s = ' '
+ write(s,'(EX0.0)') -huge(1.0_4)
+ read(s,*) r4
+ if (r4 /= -huge(1.0_4)) stop 1
+
+ s = '0X1.8P+0' ! = 1.5
+ read(s,*) r4
+ if (r4 /= 1.5_4) stop 2
+
+ s = '-0X1.P+0' ! = -1.0
+ read(s,*) r4
+ if (r4 /= -1.0_4) stop 3
+
+ ! Lowercase
+ s = '0x1.8p-1' ! = 0.75
+ read(s,*) r4
+ if (r4 /= 0.75_4) stop 4
+
+ ! Hex digit 'E' is not an exponent.
+ s = '0X1.EP+3' ! (1 + 14/16) * 8 = 15.0
+ read(s,*) r4
+ if (r4 /= 15.0_4) stop 5
+
+ ! Comma-separated list of hex values.
+ s = '0X1P0, 0X1.8P+1'
+ read(s,*) a, b
+ if (a /= 1.0_4 .or. b /= 3.0_4) stop 6
+
+ ! Using a repeat count
+ s = '3*0X1P+1'
+ read(s,*) arr
+ if (any(arr /= 2.0_4)) stop 7
+
+ ! Complex constant with hex real and imaginary parts.
+ s = '(0X1P0, 0X1.8P+1)'
+ read(s,*) z
+ if (z /= (1.0_4, 3.0_4)) stop 8
+end subroutine test04
+
+
+subroutine test08
+ real(8) :: r8, nx
+ character(len=64) :: s
+ namelist /nl8/ nx
+
+ ! Round-trip.
+ s = ' '
+ write(s,'(EX0.0)') -huge(1.0_8)
+ read(s,*) r8
+ if (r8 /= -huge(1.0_8)) stop 10
+
+ ! Explicit hex: IEEE 754 representation of 1/3.
+ s = '0X1.5555555555555P-2'
+ read(s,*) r8
+ if (r8 /= 1.0_8 / 3.0_8) stop 11
+
+ ! pi, double precision.
+ s = '0X1.921FB54442D18P+1'
+ read(s,*) r8
+ if (abs(r8 - acos(-1.0_8)) > 2.0_8 * epsilon(r8)) stop 12
+
+ ! Hex value inside a NAMELIST.
+ nx = -999.0_8
+ s = '&nl8 nx=0X1.8P+3 /'
+ read(s,nml=nl8)
+ if (nx /= 12.0_8) stop 13
+
+end subroutine test08
+
+
+#ifdef __GFC_REAL_10__
+subroutine test10
+ real(10) :: r10
+ character(len=64) :: s
+
+ s = ' '
+ write(s,'(EX0.0)') -huge(1.0_10)
+ read(s,*) r10
+ if (r10 /= -huge(1.0_10)) stop 14
+
+ s = '0X1.8P+0'
+ read(s,*) r10
+ if (r10 /= 1.5_10) stop 15
+
+end subroutine test10
+#else
+subroutine test10
+end subroutine test10
+#endif
+
+
+#ifdef __GFC_REAL_16__
+subroutine test16
+ real(16) :: r16
+ character(len=64) :: s
+
+ s = ' '
+ write(s,'(EX0.0)') -huge(1.0_16)
+ read(s,*) r16
+ if (r16 /= -huge(1.0_16)) stop 16
+
+ s = '0X1.8P+0'
+ read(s,*) r16
+ if (r16 /= 1.5_16) stop 17
+
+end subroutine test16
+#else
+subroutine test16
+end subroutine test16
+#endif
+
+end program main
diff --git a/libgfortran/io/list_read.c b/libgfortran/io/list_read.c
index 2b577baa7d3..f2179e2a1d8 100644
--- a/libgfortran/io/list_read.c
+++ b/libgfortran/io/list_read.c
@@ -1539,6 +1539,17 @@ parse_real (st_parameter_dt *dtp, void *buffer, int length)
seen_dp = (c == '.') ? 1 : 0;
+ if (c == '0')
+ {
+ int c2 = next_char (dtp);
+ if (c2 == 'x' || c2 == 'X')
+ {
+ push_char (dtp, c2);
+ goto hex_real;
+ }
+ unget_char (dtp, c2);
+ }
+
for (;;)
{
if ((c = next_char (dtp)) == EOF)
@@ -1644,6 +1655,30 @@ parse_real (st_parameter_dt *dtp, void *buffer, int length)
}
}
+ /* Hexadecimal format so collect the remaining characters.
+ The convert_real will validate it. */
+ hex_real:
+ for (;;)
+ {
+ if ((c = next_char (dtp)) == EOF)
+ goto bad;
+ switch (c)
+ {
+ case '!':
+ if (!dtp->u.p.namelist_mode)
+ goto bad;
+ /* Fall through. */
+
+ CASE_SEPARATORS:
+ case ')':
+ goto done;
+
+ default:
+ push_char (dtp, c);
+ break;
+ }
+ }
+
done:
unget_char (dtp, c);
push_char (dtp, '\0');
@@ -1878,6 +1913,17 @@ next:
switch (c)
{
CASE_DIGITS:
+ if (c == '0')
+ {
+ int c2 = next_char (dtp);
+ if (c2 == 'x' || c2 == 'X')
+ {
+ push_char (dtp, c);
+ push_char (dtp, c2);
+ goto hex_real;
+ }
+ unget_char (dtp, c2);
+ }
push_char (dtp, c);
break;
@@ -2018,6 +2064,17 @@ next:
push_char (dtp, c);
+ if (c == '0')
+ {
+ int c2 = next_char (dtp);
+ if (c2 == 'x' || c2 == 'X')
+ {
+ push_char (dtp, c2);
+ goto hex_real;
+ }
+ unget_char (dtp, c2);
+ }
+
real_loop:
for (;;)
{
@@ -2117,6 +2174,34 @@ next:
}
}
+ /* Hexadecimal-significand form: [sign] 0X hex-significand P
+ decimal-exp (F2023 13.7.2.3.2 para 7), also accepted on list-directed
+ input per the same rules as Fw.d editing. The '0X'/'0x' prefix has
+ already been pushed; collect the remaining characters (hex digits,
+ radix point, P/p exponent letter, exponent sign) verbatim up to the
+ next separator and let convert_real/strtod validate and parse the
+ whole thing, just as read_ex does for formatted EX input. */
+ hex_real:
+ for (;;)
+ {
+ c = next_char (dtp);
+ switch (c)
+ {
+ case '!':
+ if (!dtp->u.p.namelist_mode)
+ goto bad_real;
+ /* Fall through. */
+
+ CASE_SEPARATORS:
+ case EOF:
+ goto done;
+
+ default:
+ push_char (dtp, c);
+ break;
+ }
+ }
+
done:
unget_char (dtp, c);
eat_separator (dtp);