From 78bfc1892bf084eb564b7b1346ddc7acef87cdc1 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 22 Jun 2026 08:55:53 -0400 Subject: [PATCH 01/48] feat(stop_and_print): print string_t in pure procs This commit adds a pure stop_and_print subroutine and a corresponding unit test. The new subroutine facilitates printing string_t objects, including the results of string_t expressions, during error termination. Example Usage: call stop_and_print( "array = " // string_t( [1,2,3,4] ) ) --- src/julienne/julienne_stop_and_print_m.F90 | 5 ++--- test/test_stop_and_print.F90 | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index bf2142870..19d358b9a 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -2,9 +2,8 @@ ! Terms of use are as specified in LICENSE.txt module julienne_stop_and_print_m - !! Define a pure subroutine that terminates with an ERROR STOP message from a string_t + !! Define a pure subroutine subroutine that prints string_t objects/arrays use julienne_string_m, only : string_t - use julienne_multi_image_m, only : internal_error_stop implicit none private @@ -15,7 +14,7 @@ module julienne_stop_and_print_m pure subroutine stop_and_print(message) implicit none type(string_t), intent(in) :: message - call internal_error_stop(message%string()) + error stop message%string() end subroutine end module diff --git a/test/test_stop_and_print.F90 b/test/test_stop_and_print.F90 index 81a196643..194eab78c 100644 --- a/test/test_stop_and_print.F90 +++ b/test/test_stop_and_print.F90 @@ -16,7 +16,7 @@ program stop_and_print_in_pure_procedure #endif if (.not. command_line%argument_present([character(len=len("--help"))::"--help","-h"])) then #if TEST_INTENTIONAL_FAILURE && ASSERTIONS - if (me==1) print '(a)', new_line('') // 'Test the intentional failure of stop_and_print in a pure procedure: ' // new_line('') + if (me==1) print '(a)', new_line('') // 'Test the intentional failure of an idiomatic assertion: ' // new_line('') call pure_subroutine #else if (me==1) print '(a)', & From c5c1cb6d646477ee8084877be006adec5e875af9 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Wed, 24 Jun 2026 21:39:28 -0600 Subject: [PATCH 02/48] feat: mk stop_and_print generic --- src/julienne/julienne_stop_and_print_m.F90 | 89 +++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index 19d358b9a..ca4333f5f 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -8,13 +8,98 @@ module julienne_stop_and_print_m private public :: stop_and_print + + interface stop_and_print + module procedure print_string + module procedure print_header_and_data + end interface + +contains -contains - pure subroutine stop_and_print(message) + pure subroutine print_string(message) implicit none type(string_t), intent(in) :: message error stop message%string() end subroutine + + pure subroutine print_header_and_data(header, data) + implicit none + character(len=*), intent(in) :: header + class(*), intent(in) :: data + error stop new_line('') // header // new_line('') // stringify(data) + end subroutine + + pure function stringify(stuff) result(characters) + class(*), intent(in) :: stuff(..) + character(len=:), allocatable :: characters + + type(string_t) stringy_stuff + integer row + + select rank(stuff) + rank(0) + select type(stuff) + type is(character(len=*)) + stringy_stuff = stuff + type is(complex) + stringy_stuff = string_t(stuff) + characters = stringy_stuff%string() + type is(double precision) + stringy_stuff = string_t(stuff) + characters = stringy_stuff%string() + type is(integer) + stringy_stuff = string_t(stuff) + characters = stringy_stuff%string() + type is(real) + stringy_stuff = string_t(stuff) + characters = stringy_stuff%string() + class default + error stop "stringify: unsupported type" + end select + rank(1) + select type(stuff) + type is(character(len=*)) + stringy_stuff = .csv. string_t(stuff) + characters = stringy_stuff%string() + type is(complex) + stringy_stuff = .csv. string_t(stuff) + characters = stringy_stuff%string() + type is(double precision) + stringy_stuff = .csv. string_t(stuff) + characters = stringy_stuff%string() + type is(integer) + stringy_stuff = .csv. string_t(stuff) + characters = stringy_stuff%string() + type is(real) + stringy_stuff = .csv. string_t(stuff) + characters = stringy_stuff%string() + class default + error stop "stringify: unsupported type" + end select + rank(2) + select type(stuff) + type is(character(len=*)) + stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + characters = stringy_stuff%string() + type is(complex) + stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + characters = stringy_stuff%string() + type is(double precision) + stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + characters = stringy_stuff%string() + type is(integer) + stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + characters = stringy_stuff%string() + type is(real) + stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + characters = stringy_stuff%string() + class default + error stop "stringify: unsupported type" + end select + rank default + error stop "stringify: unsupported rank" + end select + end function end module From 8aebfba73eadb6078abe3b2eb4027906b039f2d2 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 12:58:56 -0600 Subject: [PATCH 03/48] test(character_stop_code): 1D integer array passes --- src/julienne/julienne_stop_and_print_m.F90 | 47 ++++++++-------- src/julienne_m.F90 | 2 +- test/driver.F90 | 3 +- test/modules/character_stop_code_test_m.F90 | 62 +++++++++++++++++++++ 4 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 test/modules/character_stop_code_test_m.F90 diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index ca4333f5f..bea27f169 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -3,11 +3,12 @@ module julienne_stop_and_print_m !! Define a pure subroutine subroutine that prints string_t objects/arrays - use julienne_string_m, only : string_t + use julienne_string_m, only : string_t, operator(.csv.) implicit none private public :: stop_and_print + public :: character_stop_code interface stop_and_print module procedure print_string @@ -27,12 +28,12 @@ pure subroutine print_header_and_data(header, data) implicit none character(len=*), intent(in) :: header class(*), intent(in) :: data - error stop new_line('') // header // new_line('') // stringify(data) + error stop new_line('') // header // new_line('') // character_stop_code(data) end subroutine - pure function stringify(stuff) result(characters) + pure function character_stop_code(stuff) result(stop_code) class(*), intent(in) :: stuff(..) - character(len=:), allocatable :: characters + character(len=:), allocatable :: stop_code type(string_t) stringy_stuff integer row @@ -44,61 +45,63 @@ pure function stringify(stuff) result(characters) stringy_stuff = stuff type is(complex) stringy_stuff = string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(double precision) stringy_stuff = string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(integer) stringy_stuff = string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(real) stringy_stuff = string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() class default - error stop "stringify: unsupported type" + error stop "character_stop_code (in print_and_stop_s): unsupported type" end select rank(1) select type(stuff) type is(character(len=*)) stringy_stuff = .csv. string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(complex) stringy_stuff = .csv. string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(double precision) stringy_stuff = .csv. string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(integer) stringy_stuff = .csv. string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(real) stringy_stuff = .csv. string_t(stuff) - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() class default - error stop "stringify: unsupported type" + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type" end select rank(2) select type(stuff) type is(character(len=*)) stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(complex) stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(double precision) stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(integer) stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() type is(real) stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] - characters = stringy_stuff%string() + stop_code = stringy_stuff%string() class default - error stop "stringify: unsupported type" + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type" end select rank default - error stop "stringify: unsupported rank" + associate(stop_code_rank => string_t(stop_code)) + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code rank: " // stop_code_rank%string() + end associate end select end function diff --git a/src/julienne_m.F90 b/src/julienne_m.F90 index 1825a9343..5b85b6325 100644 --- a/src/julienne_m.F90 +++ b/src/julienne_m.F90 @@ -9,7 +9,7 @@ module julienne_m use julienne_file_m, only : file_t use julienne_formats_m, only : separated_values, csv use julienne_github_ci_m, only : github_ci - use julienne_stop_and_print_m, only : stop_and_print + use julienne_stop_and_print_m, only : stop_and_print, character_stop_code use julienne_string_m, only : string_t, array_of_strings & ,operator(.cat.) & ,operator(.csv.) & diff --git a/test/driver.F90 b/test/driver.F90 index 4aeb4c591..3112f44df 100644 --- a/test/driver.F90 +++ b/test/driver.F90 @@ -10,6 +10,7 @@ program test_suite_driver ! Modules containing test_t child types: use assert_test_m ,only : assert_test_t use bin_test_m ,only : bin_test_t + use character_stop_code_test_m ,only : character_stop_code_test_t use command_line_test_m ,only : command_line_test_t use formats_test_m ,only : formats_test_t use multi_image_test_m ,only : multi_image_test_t, multi_image_setup @@ -17,7 +18,6 @@ program test_suite_driver use test_description_test_m ,only : test_description_test_t use test_diagnosis_test_m ,only : test_diagnosis_test_t use test_result_test_m ,only : test_result_test_t - implicit none call multi_image_setup() @@ -27,6 +27,7 @@ program test_suite_driver associate(test_harness => test_harness_t([ & test_fixture_t( assert_test_t()) & ,test_fixture_t( bin_test_t()) & + ,test_fixture_t( character_stop_code_test_t()) & ,test_fixture_t( formats_test_t()) & ,test_fixture_t( multi_image_test_t()) & ,test_fixture_t( string_test_t()) & diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 new file mode 100644 index 000000000..8efa0776e --- /dev/null +++ b/test/modules/character_stop_code_test_m.F90 @@ -0,0 +1,62 @@ +! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +#include "language-support.F90" + +module character_stop_code_test_m + !! Check data partitioning across bins + use julienne_m, only : & + character_stop_code & + ,operator(.all.) & + ,operator(.equalsExpected.) & + ,passing_test & + ,string_t & + ,test_description_t & + ,test_diagnosis_t & + ,test_result_t & + ,test_t & + ,usher + implicit none + + private + public :: character_stop_code_test_t + + type, extends(test_t) :: character_stop_code_test_t + contains + procedure, nopass :: subject + procedure, nopass :: results + end type + +contains + + pure function subject() result(specimen) + character(len=:), allocatable :: specimen + specimen = "The character_stop_code function" + end function + + function results() result(test_results) + type(test_result_t), allocatable :: test_results(:) + type(test_description_t), allocatable :: test_descriptions(:) + type(character_stop_code_test_t) character_stop_code_test + + test_descriptions = [ & + test_description_t(string_t("converting a 1D array to a comma-separated-value string"), usher(check_1D_array)) & + ] + test_results = character_stop_code_test%run(test_descriptions) + end function + + function check_1D_array() result(test_diagnosis) + !! Check conversion of a 1D array to a character string containing comma-separated values + type(test_diagnosis_t) test_diagnosis + + integer, parameter :: expected_array(*) = [1,2,3,4] + integer actual_array(size(expected_array)) + + + test_diagnosis = passing_test() + + read(character_stop_code(expected_array),*) actual_array + test_diagnosis = .all. (actual_array .equalsExpected. expected_array) + end function + +end module character_stop_code_test_m From 1cadca41e6fc0801735c11284a23b8fd2b302d59 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 18:41:36 -0600 Subject: [PATCH 04/48] test(character_stop_code): check comma count --- test/modules/character_stop_code_test_m.F90 | 27 +++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 8efa0776e..2479518ed 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -7,7 +7,9 @@ module character_stop_code_test_m !! Check data partitioning across bins use julienne_m, only : & character_stop_code & + ,operator(//) & ,operator(.all.) & + ,operator(.also.) & ,operator(.equalsExpected.) & ,passing_test & ,string_t & @@ -27,6 +29,10 @@ module character_stop_code_test_m procedure, nopass :: results end type + interface operator(.occurrencesIn.) + module procedure occurrences_in + end interface + contains pure function subject() result(specimen) @@ -40,23 +46,30 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting a 1D array to a comma-separated-value string"), usher(check_1D_array)) & + test_description_t(string_t("converting a 1D array to a comma-separated-value (CSV) string"), usher(check_1D_array)) & ] test_results = character_stop_code_test%run(test_descriptions) end function + pure function occurrences_in(lhs, rhs) result(occurrences) + character(len=1), intent(in) :: lhs + character(len=*), intent(in) :: rhs + integer occurrences, c + occurrences = count([(rhs(c:c)==lhs, c=1,len(rhs))]) + end function + function check_1D_array() result(test_diagnosis) - !! Check conversion of a 1D array to a character string containing comma-separated values type(test_diagnosis_t) test_diagnosis - integer, parameter :: expected_array(*) = [1,2,3,4] - integer actual_array(size(expected_array)) - + integer c, actual_array(size(expected_array)) test_diagnosis = passing_test() - read(character_stop_code(expected_array),*) actual_array - test_diagnosis = .all. (actual_array .equalsExpected. expected_array) + associate(stop_code => character_stop_code(expected_array)) + read(stop_code,*) actual_array + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_array)-1) // " commas in " // stop_code + end associate end function end module character_stop_code_test_m From 9de5f3b456e8167de4bc1c4a16dbf6c1444c884c Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 19:24:10 -0600 Subject: [PATCH 05/48] fix(character_stop_code): rm extraneous commas --- src/julienne/julienne_stop_and_print_m.F90 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index bea27f169..c8afa48ea 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -3,7 +3,7 @@ module julienne_stop_and_print_m !! Define a pure subroutine subroutine that prints string_t objects/arrays - use julienne_string_m, only : string_t, operator(.csv.) + use julienne_string_m, only : string_t, operator(.csv.), operator(.separatedBy.) implicit none private @@ -81,19 +81,19 @@ pure function character_stop_code(stuff) result(stop_code) rank(2) select type(stuff) type is(character(len=*)) - stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() type is(complex) - stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() type is(double precision) - stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() type is(integer) - stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() type is(real) - stringy_stuff = .csv. [(.csv. string_t(stuff(row,:)) // new_line(''), row=1,size(stuff,2))] + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() class default error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type" From b2250ca4181e69c609ef1ed7df2fa910f337de05 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 06:10:12 -0600 Subject: [PATCH 06/48] test(character_stop_code): 2D integer array passes --- test/modules/character_stop_code_test_m.F90 | 33 ++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 2479518ed..eec63c581 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -46,7 +46,8 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting a 1D array to a comma-separated-value (CSV) string"), usher(check_1D_array)) & + test_description_t(string_t("converting a 1D array to a comma-separated-value (CSV) string"), usher(check_1D_array)) & + ,test_description_t(string_t("converting a 2D array to new-line-separated CSV strings"), usher(check_2D_array)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -58,6 +59,16 @@ pure function occurrences_in(lhs, rhs) result(occurrences) occurrences = count([(rhs(c:c)==lhs, c=1,len(rhs))]) end function + function search_and_replace(string, search_for, replace_with) result(replacement_string) + character(len=*), intent(in) :: string + character(len=1), intent(in) :: search_for, replace_with + character(len=len(string)) :: replacement_string + + do concurrent(integer :: c = 1:len(string)) + replacement_string(c:c) = merge(string(c:c), replace_with, string(c:c)/=search_for) + end do + end function + function check_1D_array() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis integer, parameter :: expected_array(*) = [1,2,3,4] @@ -72,4 +83,24 @@ function check_1D_array() result(test_diagnosis) end associate end function + function check_2D_array() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + integer, parameter :: expected_array(*,*) = reshape([1,2,3,4,5,6], [2,3]) + integer actual_array(size(expected_array,1),size(expected_array,2)) + + test_diagnosis = passing_test() + + associate( & + stop_code => character_stop_code(expected_array) & + ,rows => size(expected_array,1) & + ,cols => size(expected_array,2) & + ) + read(stop_code,*) actual_array(1,:), actual_array(2,:) + + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end function + end module character_stop_code_test_m From 059a4f4c4cfcb5b6a9dfe2962cf111a10b8bdd0d Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 17:30:51 -0600 Subject: [PATCH 07/48] test(character_stop_code): 2D/3D real/integer arrays --- src/julienne/julienne_stop_and_print_m.F90 | 26 ++++- test/modules/character_stop_code_test_m.F90 | 117 ++++++++++++++++++-- 2 files changed, 127 insertions(+), 16 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index c8afa48ea..dc18b6675 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -17,7 +17,6 @@ module julienne_stop_and_print_m contains - pure subroutine print_string(message) implicit none type(string_t), intent(in) :: message @@ -36,7 +35,7 @@ pure function character_stop_code(stuff) result(stop_code) character(len=:), allocatable :: stop_code type(string_t) stringy_stuff - integer row + integer row, page select rank(stuff) rank(0) @@ -56,7 +55,7 @@ pure function character_stop_code(stuff) result(stop_code) stringy_stuff = string_t(stuff) stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported type" + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" end select rank(1) select type(stuff) @@ -76,7 +75,7 @@ pure function character_stop_code(stuff) result(stop_code) stringy_stuff = .csv. string_t(stuff) stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type" + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array" end select rank(2) select type(stuff) @@ -96,7 +95,24 @@ pure function character_stop_code(stuff) result(stop_code) stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type" + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-2 array" + end select + rank(3) + select type(stuff) + type is(complex) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(double precision) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(integer) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(real) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + class default + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array" end select rank default associate(stop_code_rank => string_t(stop_code)) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index eec63c581..544f34a2f 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -10,7 +10,9 @@ module character_stop_code_test_m ,operator(//) & ,operator(.all.) & ,operator(.also.) & + ,operator(.approximates.) & ,operator(.equalsExpected.) & + ,operator(.within.) & ,passing_test & ,string_t & ,test_description_t & @@ -46,8 +48,9 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting a 1D array to a comma-separated-value (CSV) string"), usher(check_1D_array)) & - ,test_description_t(string_t("converting a 2D array to new-line-separated CSV strings"), usher(check_2D_array)) & + test_description_t(string_t("converting a 1D arrays to a comma-separated-value (CSV) strings"), usher(check_1D_array)) & + ,test_description_t(string_t("converting a 2D arrays to new-line-separated CSV strings"), usher(check_2D_array)) & + ,test_description_t(string_t("converting a 3D arrays to new-line-separated CSV strings"), usher(check_3D_array)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -59,35 +62,74 @@ pure function occurrences_in(lhs, rhs) result(occurrences) occurrences = count([(rhs(c:c)==lhs, c=1,len(rhs))]) end function - function search_and_replace(string, search_for, replace_with) result(replacement_string) + function search_and_replace(string, search_for, replace_with, except_final) result(replacement_string) character(len=*), intent(in) :: string character(len=1), intent(in) :: search_for, replace_with - character(len=len(string)) :: replacement_string - - do concurrent(integer :: c = 1:len(string)) - replacement_string(c:c) = merge(string(c:c), replace_with, string(c:c)/=search_for) + character(len=:), allocatable :: replacement_string + logical, intent(in), optional :: except_final + integer c, c_final, c_ + + allocate(character(len=len(string)) :: replacement_string) + + c_final = 0 + c_ = 0 + do c = 1, len(string) + c_ = c_ + 1 + if (string(c:c)==search_for) then + c_final = c + if (c>1) then + if (string(c-1:c-1) /= search_for) then + replacement_string(c_:c_) = replace_with + else + c_ = c_ - 1 + end if + end if + else + replacement_string(c_:c_) = string(c:c) + end if end do + if (present(except_final)) then + if (except_final .and. c_final>0) replacement_string(c_final:c_final) = search_for + end if + + replacement_string = trim(replacement_string) end function function check_1D_array() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis + integer, parameter :: expected_array(*) = [1,2,3,4] - integer c, actual_array(size(expected_array)) + integer actual_array(size(expected_array)) + + real, parameter :: expected_real_array(*) = real(expected_array) + real actual_real_array(size(expected_real_array,1)) test_diagnosis = passing_test() associate(stop_code => character_stop_code(expected_array)) read(stop_code,*) actual_array test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) - test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_array)-1) // " commas in " // stop_code + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_array)-1) & + // " commas in " // stop_code + end associate + + associate(stop_code => character_stop_code(expected_real_array)) + read(stop_code,*) actual_real_array + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_real_array)-1) & + // " commas in " // stop_code end associate end function function check_2D_array() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis - integer, parameter :: expected_array(*,*) = reshape([1,2,3,4,5,6], [2,3]) + + integer, parameter :: expected_array(*,*) = reshape([11,21,12,22,13,23], [2,3]) integer actual_array(size(expected_array,1),size(expected_array,2)) + real, parameter :: expected_real_array(*,*) = real(expected_array) + real actual_real_array(size(expected_array,1),size(expected_array,2)) + test_diagnosis = passing_test() associate( & @@ -96,11 +138,64 @@ function check_2D_array() result(test_diagnosis) ,cols => size(expected_array,2) & ) read(stop_code,*) actual_array(1,:), actual_array(2,:) - test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) end associate + + associate( & + stop_code => character_stop_code(expected_real_array) & + ,rows => size(expected_real_array,1) & + ,cols => size(expected_real_array,2) & + ) + associate(one_line => search_and_replace(stop_code, search_for=new_line(''), replace_with=",")) + read(one_line,*) actual_real_array(1,:), actual_real_array(2,:) + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end associate + end function + + function check_3D_array() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + integer, parameter :: expected_array(*,*,*) = reshape([111,211,121,221, 112,212,122,222, 113,213,123,223], [2,2,3]) + integer actual_array(size(expected_array,1),size(expected_array,2),size(expected_array,3)) + + real, parameter :: expected_real_array(*,*,*) = real(expected_array) + real actual_real_array(size(expected_real_array,1),size(expected_real_array,2),size(expected_real_array,3)) + + test_diagnosis = passing_test() + + associate( & + stop_code => character_stop_code(expected_array) & + ,rows => size(expected_array,1) & + ,cols => size(expected_array,2) & + ,pages => size(expected_array,3) & + ) + associate(one_line => search_and_replace(stop_code, search_for=new_line(''), replace_with=",")) + read(one_line,'(*(i3,1x))') actual_array(1,:,1), actual_array(2,:,1), actual_array(1,:,2), actual_array(2,:,2), actual_array(1,:,3), actual_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end associate + + associate( & + stop_code => character_stop_code(expected_real_array) & + ,rows => size(expected_real_array,1) & + ,cols => size(expected_real_array,2) & + ,pages => size(expected_real_array,3) & + ) + associate(one_line => trim(search_and_replace(stop_code, search_for=new_line(''), replace_with=","))) + read(one_line(1:179),*) actual_real_array(1,:,1), actual_real_array(2,:,1), actual_real_array(1,:,2), actual_real_array(2,:,2), actual_real_array(1,:,3), actual_real_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. expected_real_array .within. 0.) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end associate end function end module character_stop_code_test_m From 6f67167e28e96c1814da85ac74bc7338b21f204e Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 18:51:56 -0600 Subject: [PATCH 08/48] test(character_stop_code): 1D complex array --- test/modules/character_stop_code_test_m.F90 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 544f34a2f..fa17d0802 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -104,6 +104,10 @@ function check_1D_array() result(test_diagnosis) real, parameter :: expected_real_array(*) = real(expected_array) real actual_real_array(size(expected_real_array,1)) + complex, parameter :: i = (0.,1.) + complex, parameter :: expected_complex_array(*) = cmplx(expected_array) - expected_array*i + complex actual_complex_array(size(expected_complex_array,1)) + test_diagnosis = passing_test() associate(stop_code => character_stop_code(expected_array)) @@ -119,6 +123,12 @@ function check_1D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_real_array)-1) & // " commas in " // stop_code end associate + + associate(stop_code => character_stop_code(expected_complex_array), expected_imaginary_part => -expected_array*i) + read(stop_code,*) actual_complex_array + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%re .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%im .approximates. expected_imaginary_part %im .within. 0.) + end associate end function function check_2D_array() result(test_diagnosis) From f9496df257030b7cbb59f92023ee88a559e75001 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 19:10:44 -0600 Subject: [PATCH 09/48] test(character_stop_code): {1,2,3}D dble prec arrays --- test/modules/character_stop_code_test_m.F90 | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index fa17d0802..f2ff12f57 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -108,6 +108,9 @@ function check_1D_array() result(test_diagnosis) complex, parameter :: expected_complex_array(*) = cmplx(expected_array) - expected_array*i complex actual_complex_array(size(expected_complex_array,1)) + double precision, parameter :: expected_dble_array(*) = dble(expected_array) + double precision actual_dble_array(size(expected_dble_array,1)) + test_diagnosis = passing_test() associate(stop_code => character_stop_code(expected_array)) @@ -129,6 +132,13 @@ function check_1D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%re .approximates. real(expected_array) .within. 0.) test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%im .approximates. expected_imaginary_part %im .within. 0.) end associate + + associate(stop_code => character_stop_code(expected_dble_array)) + read(stop_code,*) actual_dble_array + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. dble(expected_array) .within. 0D0) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_dble_array)-1) & + // " commas in " // stop_code + end associate end function function check_2D_array() result(test_diagnosis) @@ -140,6 +150,9 @@ function check_2D_array() result(test_diagnosis) real, parameter :: expected_real_array(*,*) = real(expected_array) real actual_real_array(size(expected_array,1),size(expected_array,2)) + double precision, parameter :: expected_dble_array(*,*) = dble(expected_array) + double precision actual_dble_array(size(expected_dble_array,1), size(expected_dble_array,2)) + test_diagnosis = passing_test() associate( & @@ -165,6 +178,19 @@ function check_2D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) end associate end associate + + associate( & + stop_code => character_stop_code(expected_dble_array) & + ,rows => size(expected_dble_array,1) & + ,cols => size(expected_dble_array,2) & + ) + associate(one_line => search_and_replace(stop_code, search_for=new_line(''), replace_with=",")) + read(one_line,*) actual_dble_array(1,:), actual_dble_array(2,:) + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. dble(expected_array) .within. 0D0) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end associate end function function check_3D_array() result(test_diagnosis) @@ -175,6 +201,9 @@ function check_3D_array() result(test_diagnosis) real, parameter :: expected_real_array(*,*,*) = real(expected_array) real actual_real_array(size(expected_real_array,1),size(expected_real_array,2),size(expected_real_array,3)) + double precision, parameter :: expected_dble_array(*,*,*) = dble(expected_array) + double precision actual_dble_array(size(expected_dble_array,1), size(expected_dble_array,2), size(expected_dble_array,3)) + test_diagnosis = passing_test() associate( & @@ -206,6 +235,21 @@ function check_3D_array() result(test_diagnosis) // " new-line characters" end associate end associate + + associate( & + stop_code => character_stop_code(expected_dble_array) & + ,rows => size(expected_dble_array,1) & + ,cols => size(expected_dble_array,2) & + ,pages => size(expected_dble_array,3) & + ) + associate(one_line => trim(search_and_replace(stop_code, search_for=new_line(''), replace_with=","))) + read(one_line(1:179),*) actual_dble_array(1,:,1), actual_dble_array(2,:,1), actual_dble_array(1,:,2), actual_dble_array(2,:,2), actual_dble_array(1,:,3), actual_dble_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. expected_dble_array .within. 0D0) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end associate end function end module character_stop_code_test_m From 985890bb98634963e5015a54ad7158e6799585ce Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 19:38:58 -0700 Subject: [PATCH 10/48] build/test(print_and_stop): gfortran workarounds This commit works around several build-time and runtime gfortran conpiler bugs. --- src/julienne/julienne_stop_and_print_m.F90 | 8 + test/modules/character_stop_code_test_m.F90 | 228 +++++++++++++++++++- 2 files changed, 230 insertions(+), 6 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index dc18b6675..7105ce8f5 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -27,7 +27,15 @@ pure subroutine print_header_and_data(header, data) implicit none character(len=*), intent(in) :: header class(*), intent(in) :: data +#ifndef __GFORTRAN__ error stop new_line('') // header // new_line('') // character_stop_code(data) +#else + block + character(len=:), allocatable :: code + code = new_line('') // header // new_line('') // character_stop_code(data) + error stop code + end block +#endif end subroutine pure function character_stop_code(stuff) result(stop_code) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index f2ff12f57..763db1df4 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -48,9 +48,10 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting a 1D arrays to a comma-separated-value (CSV) strings"), usher(check_1D_array)) & - ,test_description_t(string_t("converting a 2D arrays to new-line-separated CSV strings"), usher(check_2D_array)) & - ,test_description_t(string_t("converting a 3D arrays to new-line-separated CSV strings"), usher(check_3D_array)) & + test_description_t(string_t("converting scalars to character stop codes"), usher(check_scalars)) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_1D_array)) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes"), usher(check_2D_array)) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes"), usher(check_3D_array)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -95,6 +96,69 @@ function search_and_replace(string, search_for, replace_with, except_final) resu replacement_string = trim(replacement_string) end function + function check_scalars() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + + integer, parameter :: expected_integer_value = 42 + integer actual_value + + real, parameter :: expected_real_value = real(expected_integer_value) + real actual_real_value + + complex, parameter :: i = (0.,1.), expected_complex_value = cmplx(expected_integer_value) - expected_integer_value*i + complex actual_complex_value + + double precision, parameter :: expected_dble_value = dble(expected_integer_value) + double precision actual_dble_value + + test_diagnosis = passing_test() + +#ifndef __GFORTRAN__ + associate(stop_code => character_stop_code(expected_integer_value)) + read(stop_code,*) actual_value + end associate + test_diagnosis = test_diagnosis .also. (actual_value .equalsExpected. expected_integer_value) // " for an integer value" + + associate(stop_code => character_stop_code(expected_real_value)) + read(stop_code,*) actual_real_value + end associate + test_diagnosis = test_diagnosis .also. (actual_real_value .approximates. expected_real_value .within. 0.) // " for a real value" + + associate(stop_code => character_stop_code(expected_complex_value)) + read(stop_code,*) actual_complex_value + end associate + test_diagnosis = test_diagnosis .also. (actual_complex_value%Re .approximates. expected_complex_value%Re .within. 0.) // " for the real part of a complex value" + test_diagnosis = test_diagnosis .also. (actual_complex_value%Im .approximates. expected_complex_value%Im .within. 0.) // " for the imaginary part of a complex value" + + associate(stop_code => character_stop_code(expected_dble_value)) + read(stop_code,*) actual_dble_value + end associate + test_diagnosis = test_diagnosis .also. (actual_dble_value .approximates. expected_dble_value .within. 0D0) // " for a double-precision value" +#else + block + character(len=:), allocatable :: stop_code + + stop_code = character_stop_code(expected_integer_value) + read(stop_code,*) actual_value + test_diagnosis = test_diagnosis .also. (actual_value .equalsExpected. expected_integer_value) + + stop_code = character_stop_code(expected_real_value) + read(stop_code,*) actual_real_value + test_diagnosis = test_diagnosis .also. (actual_real_value .approximates. expected_real_value .within. 0.) + + stop_code = character_stop_code(expected_complex_value) + read(stop_code,*) actual_complex_value + test_diagnosis = test_diagnosis .also. (actual_complex_value%Re .approximates. expected_complex_value%Re .within. 0.) + test_diagnosis = test_diagnosis .also. (actual_complex_value%Im .approximates. expected_complex_value%Im .within. 0.) + + stop_code = character_stop_code(expected_dble_value) + read(stop_code,*) actual_dble_value + test_diagnosis = test_diagnosis .also. (actual_dble_value .approximates. expected_dble_value .within. 0D0) + end block +#endif + + end function + function check_1D_array() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis @@ -111,34 +175,79 @@ function check_1D_array() result(test_diagnosis) double precision, parameter :: expected_dble_array(*) = dble(expected_array) double precision actual_dble_array(size(expected_dble_array,1)) + integer c + test_diagnosis = passing_test() +#ifndef __GFORTRAN__ associate(stop_code => character_stop_code(expected_array)) read(stop_code,*) actual_array test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_array)-1) & // " commas in " // stop_code end associate +#else + block + character(len=:), allocatable :: stop_code + stop_code = character_stop_code(expected_array) + read(stop_code,*) actual_array + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (count([(stop_code(c:c)==",", c=1,len(stop_code))]) .equalsExpected. size(expected_array)-1) & + // " commas in " // stop_code + end block +#endif +#ifndef __GFORTRAN__ associate(stop_code => character_stop_code(expected_real_array)) read(stop_code,*) actual_real_array test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. real(expected_array) .within. 0.) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_real_array)-1) & // " commas in " // stop_code end associate +#else + block + character(len=:), allocatable :: stop_code + stop_code = character_stop_code(expected_real_array) + read(stop_code,*) actual_real_array + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. (count([(stop_code(c:c)==",", c=1,len(stop_code))]) .equalsExpected. size(expected_real_array)-1) & + // " commas in " // stop_code + end block +#endif +#ifndef __GFORTRAN__ associate(stop_code => character_stop_code(expected_complex_array), expected_imaginary_part => -expected_array*i) read(stop_code,*) actual_complex_array - test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%re .approximates. real(expected_array) .within. 0.) - test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%im .approximates. expected_imaginary_part %im .within. 0.) + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%Re .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%Im .approximates. expected_imaginary_part%Im .within. 0.) end associate +#else + block + character(len=:), allocatable :: stop_code + stop_code = character_stop_code(expected_complex_array) + read(stop_code,*) actual_complex_array + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%Re .approximates. expected_complex_array%Re .within. 0.) + test_diagnosis = test_diagnosis .also. .all. (actual_complex_array%Im .approximates. expected_complex_array%Im .within. 0.) + end block +#endif +#ifndef __GFORTRAN__ associate(stop_code => character_stop_code(expected_dble_array)) read(stop_code,*) actual_dble_array test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. dble(expected_array) .within. 0D0) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. size(expected_dble_array)-1) & // " commas in " // stop_code end associate +#else + block + character(len=:), allocatable :: stop_code + stop_code = character_stop_code(expected_dble_array) + read(stop_code,*) actual_dble_array + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. expected_dble_array .within. 0D0) + test_diagnosis = test_diagnosis .also. (count([(stop_code(c:c)==",", c=1,len(stop_code))]) .equalsExpected. size(expected_dble_array)-1) & + // " commas in " // stop_code + end block +#endif end function function check_2D_array() result(test_diagnosis) @@ -155,6 +264,7 @@ function check_2D_array() result(test_diagnosis) test_diagnosis = passing_test() +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_array) & ,rows => size(expected_array,1) & @@ -165,7 +275,23 @@ function check_2D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) end associate +#else + block + character(len=:), allocatable :: stop_code + stop_code = character_stop_code(expected_array) + associate( & + rows => size(expected_array,1) & + ,cols => size(expected_array,2) & + ) + read(stop_code,*) actual_array(1,:), actual_array(2,:) + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end block +#endif +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_real_array) & ,rows => size(expected_real_array,1) & @@ -178,7 +304,24 @@ function check_2D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) end associate end associate +#else + block + character(len=:), allocatable :: stop_code, one_line + stop_code = character_stop_code(expected_real_array) + associate( & + rows => size(expected_real_array,1) & + ,cols => size(expected_real_array,2) & + ) + one_line = search_and_replace(stop_code, search_for=new_line(''), replace_with=",") + read(one_line,*) actual_real_array(1,:), actual_real_array(2,:) + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. real(expected_array) .within. 0.) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end block +#endif +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_dble_array) & ,rows => size(expected_dble_array,1) & @@ -191,6 +334,22 @@ function check_2D_array() result(test_diagnosis) test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) end associate end associate +#else + block + character(len=:), allocatable :: stop_code, one_line + stop_code = character_stop_code(expected_dble_array) + associate( & + rows => size(expected_dble_array,1) & + ,cols => size(expected_dble_array,2) & + ) + one_line = search_and_replace(stop_code, search_for=new_line(''), replace_with=",") + read(one_line,*) actual_dble_array(1,:), actual_dble_array(2,:) + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. dble(expected_array) .within. 0D0) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows) + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. rows-1) + end associate + end block +#endif end function function check_3D_array() result(test_diagnosis) @@ -206,6 +365,7 @@ function check_3D_array() result(test_diagnosis) test_diagnosis = passing_test() +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_array) & ,rows => size(expected_array,1) & @@ -220,7 +380,26 @@ function check_3D_array() result(test_diagnosis) // " new-line characters" end associate end associate +#else + block + character(len=:), allocatable :: stop_code, one_line + stop_code = character_stop_code(expected_array) + associate( & + rows => size(expected_array,1) & + ,cols => size(expected_array,2) & + ,pages => size(expected_array,3) & + ) + one_line = search_and_replace(stop_code, search_for=new_line(''), replace_with=",") + read(one_line,'(*(i3,1x))') actual_array(1,:,1), actual_array(2,:,1), actual_array(1,:,2), actual_array(2,:,2), actual_array(1,:,3), actual_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_array .equalsExpected. expected_array) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end block +#endif +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_real_array) & ,rows => size(expected_real_array,1) & @@ -235,7 +414,26 @@ function check_3D_array() result(test_diagnosis) // " new-line characters" end associate end associate - +#else + block + character(len=:), allocatable :: stop_code, one_line + stop_code = character_stop_code(expected_real_array) + associate( & + rows => size(expected_real_array,1) & + ,cols => size(expected_real_array,2) & + ,pages => size(expected_real_array,3) & + ) + one_line = trim(search_and_replace(stop_code, search_for=new_line(''), replace_with=",")) + read(one_line(1:179),*) actual_real_array(1,:,1), actual_real_array(2,:,1), actual_real_array(1,:,2), actual_real_array(2,:,2), actual_real_array(1,:,3), actual_real_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_real_array .approximates. expected_real_array .within. 0.) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end block +#endif + +#ifndef __GFORTRAN__ associate( & stop_code => character_stop_code(expected_dble_array) & ,rows => size(expected_dble_array,1) & @@ -250,6 +448,24 @@ function check_3D_array() result(test_diagnosis) // " new-line characters" end associate end associate +#else + block + character(len=:), allocatable :: stop_code, one_line + stop_code = character_stop_code(expected_dble_array) + associate( & + rows => size(expected_dble_array,1) & + ,cols => size(expected_dble_array,2) & + ,pages => size(expected_dble_array,3) & + ) + one_line = trim(search_and_replace(stop_code, search_for=new_line(''), replace_with=",")) + read(one_line(1:179),*) actual_dble_array(1,:,1), actual_dble_array(2,:,1), actual_dble_array(1,:,2), actual_dble_array(2,:,2), actual_dble_array(1,:,3), actual_dble_array(2,:,3) + test_diagnosis = test_diagnosis .also. .all. (actual_dble_array .approximates. expected_dble_array .within. 0D0) + test_diagnosis = test_diagnosis .also. (("," .occurrencesIn. stop_code) .equalsExpected. (cols-1)*rows*pages) // " commas" + test_diagnosis = test_diagnosis .also. ((new_line('') .occurrencesIn. stop_code) .equalsExpected. (rows*pages-1) + (pages-1)) & + // " new-line characters" + end associate + end block +#endif end function end module character_stop_code_test_m From f6754252e5f0920a30d4d59680e9573853a71952 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 20:36:35 -0700 Subject: [PATCH 11/48] feat(stop_and_print): support 1D string_t arrays --- src/julienne/julienne_stop_and_print_m.F90 | 5 ++++ test/modules/character_stop_code_test_m.F90 | 33 ++++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index 7105ce8f5..ff7a5e6b0 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -62,6 +62,8 @@ pure function character_stop_code(stuff) result(stop_code) type is(real) stringy_stuff = string_t(stuff) stop_code = stringy_stuff%string() + class is(string_t) + stop_code = stuff%string() class default error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" end select @@ -82,6 +84,9 @@ pure function character_stop_code(stuff) result(stop_code) type is(real) stringy_stuff = .csv. string_t(stuff) stop_code = stringy_stuff%string() + class is(string_t) + stringy_stuff = .csv. stuff + stop_code = stringy_stuff%string() class default error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array" end select diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 763db1df4..3475f243d 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -11,6 +11,7 @@ module character_stop_code_test_m ,operator(.all.) & ,operator(.also.) & ,operator(.approximates.) & + ,operator(.csv.) & ,operator(.equalsExpected.) & ,operator(.within.) & ,passing_test & @@ -48,10 +49,11 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting scalars to character stop codes"), usher(check_scalars)) & - ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_1D_array)) & - ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes"), usher(check_2D_array)) & - ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes"), usher(check_3D_array)) & + test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes") , usher(check_intrinsic_1D_arrays)) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & + ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -96,7 +98,7 @@ function search_and_replace(string, search_for, replace_with, except_final) resu replacement_string = trim(replacement_string) end function - function check_scalars() result(test_diagnosis) + function check_intrinsic_scalars() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis integer, parameter :: expected_integer_value = 42 @@ -159,7 +161,7 @@ function check_scalars() result(test_diagnosis) end function - function check_1D_array() result(test_diagnosis) + function check_intrinsic_1D_arrays() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis integer, parameter :: expected_array(*) = [1,2,3,4] @@ -250,7 +252,7 @@ function check_1D_array() result(test_diagnosis) #endif end function - function check_2D_array() result(test_diagnosis) + function check_intrinsic_2D_arrays() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis integer, parameter :: expected_array(*,*) = reshape([11,21,12,22,13,23], [2,3]) @@ -352,7 +354,7 @@ function check_2D_array() result(test_diagnosis) #endif end function - function check_3D_array() result(test_diagnosis) + function check_intrinsic_3D_arrays() result(test_diagnosis) type(test_diagnosis_t) test_diagnosis integer, parameter :: expected_array(*,*,*) = reshape([111,211,121,221, 112,212,122,222, 113,213,123,223], [2,2,3]) integer actual_array(size(expected_array,1),size(expected_array,2),size(expected_array,3)) @@ -468,4 +470,19 @@ function check_3D_array() result(test_diagnosis) #endif end function + function check_string_t_1D_array() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + + test_diagnosis = passing_test() + + associate(messages => string_t(["foo","bar"])) + associate( & + expected_stop_code => .csv. messages & + ,stop_code => character_stop_code(messages) & + ) + test_diagnosis = stop_code .equalsExpected. expected_stop_code + end associate + end associate + end function + end module character_stop_code_test_m From 258ee18033efb42fea1aef756e894b2744ff6f3f Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:12:39 -0700 Subject: [PATCH 12/48] feat(file_t): add from_character_lines constructor --- src/julienne/julienne_file_m.f90 | 8 +++++++- src/julienne/julienne_file_s.F90 | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/julienne/julienne_file_m.f90 b/src/julienne/julienne_file_m.f90 index 7a113638b..3d4f2fb4b 100644 --- a/src/julienne/julienne_file_m.f90 +++ b/src/julienne/julienne_file_m.f90 @@ -31,12 +31,18 @@ module function from_file_with_character_name(file_name) result(file_object) type(file_t) file_object end function - pure module function from_lines(lines) result(file_object) + pure module function from_string_t_lines(lines) result(file_object) implicit none type(string_t), intent(in) :: lines(:) type(file_t) file_object end function + pure module function from_character_lines(lines) result(file_object) + implicit none + character(len=*), intent(in) :: lines(:) + type(file_t) file_object + end function + end interface interface diff --git a/src/julienne/julienne_file_s.F90 b/src/julienne/julienne_file_s.F90 index bfa6dd19a..e243640eb 100644 --- a/src/julienne/julienne_file_s.F90 +++ b/src/julienne/julienne_file_s.F90 @@ -42,10 +42,14 @@ call self%write_to_character_file_name(file_name%string()) end procedure - module procedure from_lines + module procedure from_string_t_lines allocate(file_object%lines_, source=lines) end procedure + module procedure from_character_lines + allocate(file_object%lines_, source=string_t(lines)) + end procedure + module procedure from_file_with_character_name file_object = from_file_with_string_name(string_t(file_name)) end procedure From 700a690276194e8457a2cabd7a3827524b483e6a Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:14:21 -0700 Subject: [PATCH 13/48] feat(character_stop_code): support file_t --- src/julienne/julienne_stop_and_print_m.F90 | 4 ++++ test/modules/character_stop_code_test_m.F90 | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index ff7a5e6b0..9f354a8c4 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -4,6 +4,7 @@ module julienne_stop_and_print_m !! Define a pure subroutine subroutine that prints string_t objects/arrays use julienne_string_m, only : string_t, operator(.csv.), operator(.separatedBy.) + use julienne_file_m, only : file_t implicit none private @@ -56,6 +57,9 @@ pure function character_stop_code(stuff) result(stop_code) type is(double precision) stringy_stuff = string_t(stuff) stop_code = stringy_stuff%string() + type is(file_t) + stringy_stuff = stuff%lines() .separatedBy. new_line('') + stop_code = stringy_stuff%string() type is(integer) stringy_stuff = string_t(stuff) stop_code = stringy_stuff%string() diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 3475f243d..c1fcef974 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -6,13 +6,14 @@ module character_stop_code_test_m !! Check data partitioning across bins use julienne_m, only : & - character_stop_code & + file_t & ,operator(//) & ,operator(.all.) & ,operator(.also.) & ,operator(.approximates.) & ,operator(.csv.) & ,operator(.equalsExpected.) & + ,operator(.separatedBy.) & ,operator(.within.) & ,passing_test & ,string_t & @@ -21,6 +22,8 @@ module character_stop_code_test_m ,test_result_t & ,test_t & ,usher + use julienne_stop_and_print_m, only : character_stop_code + implicit none private @@ -54,6 +57,7 @@ function results() result(test_results) ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & + ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code") , usher(check_file_t)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -485,4 +489,19 @@ function check_string_t_1D_array() result(test_diagnosis) end associate end function + function check_file_t() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + + test_diagnosis = passing_test() + + associate(file_ => file_t(["yada","yada"])) + associate( & + expected_stop_code => file_%lines() .separatedBy. new_line('') & + ,stop_code => character_stop_code(file_) & + ) + test_diagnosis = stop_code .equalsExpected. expected_stop_code + end associate + end associate + end function + end module character_stop_code_test_m From e34d6a5607887c1cde39d8f80ac8f4f31463959f Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:14:48 -0700 Subject: [PATCH 14/48] refac(julienne_m): rm character_stop_code The character_stop_code subroutine is public only for purposes of calling it from thetest suite so there's no need to have it in the public interface (julienne_m). --- src/julienne_m.F90 | 2 +- test/modules/character_stop_code_test_m.F90 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/julienne_m.F90 b/src/julienne_m.F90 index 5b85b6325..1825a9343 100644 --- a/src/julienne_m.F90 +++ b/src/julienne_m.F90 @@ -9,7 +9,7 @@ module julienne_m use julienne_file_m, only : file_t use julienne_formats_m, only : separated_values, csv use julienne_github_ci_m, only : github_ci - use julienne_stop_and_print_m, only : stop_and_print, character_stop_code + use julienne_stop_and_print_m, only : stop_and_print use julienne_string_m, only : string_t, array_of_strings & ,operator(.cat.) & ,operator(.csv.) & diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index c1fcef974..62d5aaedd 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -484,7 +484,7 @@ function check_string_t_1D_array() result(test_diagnosis) expected_stop_code => .csv. messages & ,stop_code => character_stop_code(messages) & ) - test_diagnosis = stop_code .equalsExpected. expected_stop_code + test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) end associate end associate end function @@ -499,7 +499,7 @@ function check_file_t() result(test_diagnosis) expected_stop_code => file_%lines() .separatedBy. new_line('') & ,stop_code => character_stop_code(file_) & ) - test_diagnosis = stop_code .equalsExpected. expected_stop_code + test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) end associate end associate end function From 65e61d99ab3d21da944b5ee24385dbd85812d904 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 22:40:48 -0700 Subject: [PATCH 15/48] feat(character_stop_code): support derived types This commit supports using user-defined derived-type output (UDDTO) to create stop codes for derived types that extend the new writable_t abstract type, which has - A write_formatted deferred binding that obligates child types to support UDDTO, - A private maxlen_ component that sets the maximum stop-code length, - A maxlen type-bound function that returns maxlen_, and - A set_maxlen type-bound subroutine that sets maxlen_. This commit also includes a corresponding unit test. --- src/julienne/julienne_stop_and_print_m.F90 | 49 ++++++++++++++++++++- src/julienne_m.F90 | 2 +- test/modules/character_stop_code_test_m.F90 | 42 +++++++++++++++--- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index 9f354a8c4..6d99ca719 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -10,22 +10,57 @@ module julienne_stop_and_print_m private public :: stop_and_print public :: character_stop_code + public :: writable_t interface stop_and_print module procedure print_string module procedure print_header_and_data end interface + type, abstract :: writable_t + private + integer :: maxlen_ = 16384 + contains + generic :: write(formatted) => write_formatted + procedure(write_formatted_i), deferred :: write_formatted + procedure :: set_maxlen + procedure :: maxlen + end type + + abstract interface + + subroutine write_formatted_i(self, unit, edit_descriptor, v_list, iostat, iomsg) + import writable_t + class(writable_t), intent(in) :: self + integer, intent(in) :: unit + character(len=*), intent(in) :: edit_descriptor + integer, intent(in) :: v_list(:) + integer, intent(out) :: iostat + character(len=*), intent(inout) :: iomsg + end subroutine + + end interface + contains pure subroutine print_string(message) - implicit none type(string_t), intent(in) :: message error stop message%string() end subroutine + pure subroutine set_maxlen(self, length) + class(writable_t), intent(inout) :: self + integer, intent(in) :: length + self%maxlen_ = length + end subroutine + + pure function maxlen(self) result(length) + class(writable_t), intent(in) :: self + integer length + length = self%maxlen_ + end function + pure subroutine print_header_and_data(header, data) - implicit none character(len=*), intent(in) :: header class(*), intent(in) :: data #ifndef __GFORTRAN__ @@ -68,6 +103,16 @@ pure function character_stop_code(stuff) result(stop_code) stop_code = stringy_stuff%string() class is(string_t) stop_code = stuff%string() + class is(writable_t) + allocate(character(len=stuff%maxlen()) :: stop_code) + block + integer io_status + write(stop_code,*,iostat=io_status) stuff + associate(code_maxlen => string_t(stuff%maxlen())) + if (io_status /= 0) error stop "Call writable_t's set_maxlen procedure to increase stop_code maximum size above " // code_maxlen%string() + end associate + end block + stop_code = trim(stop_code) class default error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" end select diff --git a/src/julienne_m.F90 b/src/julienne_m.F90 index 1825a9343..3cca68e04 100644 --- a/src/julienne_m.F90 +++ b/src/julienne_m.F90 @@ -9,7 +9,7 @@ module julienne_m use julienne_file_m, only : file_t use julienne_formats_m, only : separated_values, csv use julienne_github_ci_m, only : github_ci - use julienne_stop_and_print_m, only : stop_and_print + use julienne_stop_and_print_m, only : stop_and_print, writable_t use julienne_string_m, only : string_t, array_of_strings & ,operator(.cat.) & ,operator(.csv.) & diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 62d5aaedd..c79e88f47 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -21,7 +21,8 @@ module character_stop_code_test_m ,test_diagnosis_t & ,test_result_t & ,test_t & - ,usher + ,usher & + ,writable_t use julienne_stop_and_print_m, only : character_stop_code implicit none @@ -39,6 +40,11 @@ module character_stop_code_test_m module procedure occurrences_in end interface + type, extends(writable_t) :: write_stuff_t + contains + procedure :: write_formatted + end type + contains pure function subject() result(specimen) @@ -52,12 +58,13 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & - test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & - ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes") , usher(check_intrinsic_1D_arrays)) & - ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & - ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & - ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & - ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code") , usher(check_file_t)) & + test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & + ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & + ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code"), usher(check_file_t)) & + ,test_description_t(string_t("converting a writable_t child object into character stop code") , usher(check_writable_t)) & ] test_results = character_stop_code_test%run(test_descriptions) end function @@ -504,4 +511,25 @@ function check_file_t() result(test_diagnosis) end associate end function + function check_writable_t() result(test_diagnosis) + type(test_diagnosis_t) test_diagnosis + type(write_stuff_t) write_stuff + + test_diagnosis = passing_test() + + associate(stop_code => character_stop_code(write_stuff)) + test_diagnosis = stop_code .equalsExpected. "written stuff" + end associate + end function + + subroutine write_formatted(self, unit, edit_descriptor, v_list, iostat, iomsg) + class(write_stuff_t), intent(in) :: self + integer, intent(in) :: unit + character(len=*), intent(in) :: edit_descriptor + integer, intent(in) :: v_list(:) + integer, intent(out) :: iostat + character(len=*), intent(inout) :: iomsg + write(unit,'(a)') "written stuff" + end subroutine + end module character_stop_code_test_m From c158baf0594cb6d5d730ed1cf0343159686a921f Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 23:01:26 -0700 Subject: [PATCH 16/48] fix: skip 3 tests that crash with gfortran --- test/modules/character_stop_code_test_m.F90 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index c79e88f47..5ad5a6f7d 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -62,9 +62,15 @@ function results() result(test_results) ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & +#ifndef __GFORTRAN__ ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code"), usher(check_file_t)) & ,test_description_t(string_t("converting a writable_t child object into character stop code") , usher(check_writable_t)) & +#else + ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & + ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & + ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & +#endif ] test_results = character_stop_code_test%run(test_descriptions) end function From a91a5e2346047fb9a72705eac1468e96298e4a36 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 23:15:57 -0700 Subject: [PATCH 17/48] refac(stop_and_print): split {sub,}module --- src/julienne/julienne_stop_and_print_m.F90 | 179 ++++----------------- src/julienne/julienne_stop_and_print_s.F90 | 142 ++++++++++++++++ 2 files changed, 176 insertions(+), 145 deletions(-) create mode 100644 src/julienne/julienne_stop_and_print_s.F90 diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index 6d99ca719..c16466782 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -2,21 +2,15 @@ ! Terms of use are as specified in LICENSE.txt module julienne_stop_and_print_m - !! Define a pure subroutine subroutine that prints string_t objects/arrays - use julienne_string_m, only : string_t, operator(.csv.), operator(.separatedBy.) - use julienne_file_m, only : file_t + !! Define a pure subroutine that formats and prints various data types during error termination + use julienne_string_m, only : string_t implicit none private public :: stop_and_print - public :: character_stop_code public :: writable_t + public :: character_stop_code - interface stop_and_print - module procedure print_string - module procedure print_header_and_data - end interface - type, abstract :: writable_t private integer :: maxlen_ = 16384 @@ -41,146 +35,41 @@ subroutine write_formatted_i(self, unit, edit_descriptor, v_list, iostat, iomsg) end interface -contains + interface stop_and_print - pure subroutine print_string(message) - type(string_t), intent(in) :: message - error stop message%string() - end subroutine + pure module subroutine stop_and_print_string(message) + implicit none + type(string_t), intent(in) :: message + end subroutine - pure subroutine set_maxlen(self, length) - class(writable_t), intent(inout) :: self - integer, intent(in) :: length - self%maxlen_ = length - end subroutine + pure module subroutine stop_and_print_header_and_data(header, data) + implicit none + character(len=*), intent(in) :: header + class(*), intent(in) :: data + end subroutine - pure function maxlen(self) result(length) - class(writable_t), intent(in) :: self - integer length - length = self%maxlen_ - end function + end interface - pure subroutine print_header_and_data(header, data) - character(len=*), intent(in) :: header - class(*), intent(in) :: data -#ifndef __GFORTRAN__ - error stop new_line('') // header // new_line('') // character_stop_code(data) -#else - block - character(len=:), allocatable :: code - code = new_line('') // header // new_line('') // character_stop_code(data) - error stop code - end block -#endif - end subroutine + interface - pure function character_stop_code(stuff) result(stop_code) - class(*), intent(in) :: stuff(..) - character(len=:), allocatable :: stop_code + pure module subroutine set_maxlen(self, length) + implicit none + class(writable_t), intent(inout) :: self + integer, intent(in) :: length + end subroutine - type(string_t) stringy_stuff - integer row, page + pure module function maxlen(self) result(length) + implicit none + class(writable_t), intent(in) :: self + integer length + end function - select rank(stuff) - rank(0) - select type(stuff) - type is(character(len=*)) - stringy_stuff = stuff - type is(complex) - stringy_stuff = string_t(stuff) - stop_code = stringy_stuff%string() - type is(double precision) - stringy_stuff = string_t(stuff) - stop_code = stringy_stuff%string() - type is(file_t) - stringy_stuff = stuff%lines() .separatedBy. new_line('') - stop_code = stringy_stuff%string() - type is(integer) - stringy_stuff = string_t(stuff) - stop_code = stringy_stuff%string() - type is(real) - stringy_stuff = string_t(stuff) - stop_code = stringy_stuff%string() - class is(string_t) - stop_code = stuff%string() - class is(writable_t) - allocate(character(len=stuff%maxlen()) :: stop_code) - block - integer io_status - write(stop_code,*,iostat=io_status) stuff - associate(code_maxlen => string_t(stuff%maxlen())) - if (io_status /= 0) error stop "Call writable_t's set_maxlen procedure to increase stop_code maximum size above " // code_maxlen%string() - end associate - end block - stop_code = trim(stop_code) - class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" - end select - rank(1) - select type(stuff) - type is(character(len=*)) - stringy_stuff = .csv. string_t(stuff) - stop_code = stringy_stuff%string() - type is(complex) - stringy_stuff = .csv. string_t(stuff) - stop_code = stringy_stuff%string() - type is(double precision) - stringy_stuff = .csv. string_t(stuff) - stop_code = stringy_stuff%string() - type is(integer) - stringy_stuff = .csv. string_t(stuff) - stop_code = stringy_stuff%string() - type is(real) - stringy_stuff = .csv. string_t(stuff) - stop_code = stringy_stuff%string() - class is(string_t) - stringy_stuff = .csv. stuff - stop_code = stringy_stuff%string() - class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array" - end select - rank(2) - select type(stuff) - type is(character(len=*)) - stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') - stop_code = stringy_stuff%string() - type is(complex) - stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') - stop_code = stringy_stuff%string() - type is(double precision) - stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') - stop_code = stringy_stuff%string() - type is(integer) - stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') - stop_code = stringy_stuff%string() - type is(real) - stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') - stop_code = stringy_stuff%string() - class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-2 array" - end select - rank(3) - select type(stuff) - type is(complex) - stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) - stop_code = stringy_stuff%string() - type is(double precision) - stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) - stop_code = stringy_stuff%string() - type is(integer) - stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) - stop_code = stringy_stuff%string() - type is(real) - stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) - stop_code = stringy_stuff%string() - class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array" - end select - rank default - associate(stop_code_rank => string_t(stop_code)) - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code rank: " // stop_code_rank%string() - end associate - end select - end function - -end module + pure module function character_stop_code(stuff) result(stop_code) + implicit none + class(*), intent(in) :: stuff(..) + character(len=:), allocatable :: stop_code + end function + + end interface + +end module julienne_stop_and_print_m diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 new file mode 100644 index 000000000..ad6abde9a --- /dev/null +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -0,0 +1,142 @@ +! Copyright (c), The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +submodule(julienne_stop_and_print_m) julienne_stop_and_print_s + use julienne_string_m, only : operator(.csv.), operator(.separatedBy.) + use julienne_file_m, only : file_t + implicit none + +contains + + module procedure stop_and_print_string + error stop message%string() + end procedure + + module procedure set_maxlen + self%maxlen_ = length + end procedure + + module procedure maxlen + length = self%maxlen_ + end procedure + + module procedure stop_and_print_header_and_data +#ifndef __GFORTRAN__ + error stop new_line('') // header // new_line('') // character_stop_code(data) +#else + block + character(len=:), allocatable :: code + code = new_line('') // header // new_line('') // character_stop_code(data) + error stop code + end block +#endif + end procedure + + module procedure character_stop_code + + type(string_t) stringy_stuff + integer row, page + + select rank(stuff) + rank(0) + select type(stuff) + type is(character(len=*)) + stringy_stuff = stuff + type is(complex) + stringy_stuff = string_t(stuff) + stop_code = stringy_stuff%string() + type is(double precision) + stringy_stuff = string_t(stuff) + stop_code = stringy_stuff%string() + type is(file_t) + stringy_stuff = stuff%lines() .separatedBy. new_line('') + stop_code = stringy_stuff%string() + type is(integer) + stringy_stuff = string_t(stuff) + stop_code = stringy_stuff%string() + type is(real) + stringy_stuff = string_t(stuff) + stop_code = stringy_stuff%string() + class is(string_t) + stop_code = stuff%string() + class is(writable_t) + allocate(character(len=stuff%maxlen()) :: stop_code) + block + integer io_status + write(stop_code,*,iostat=io_status) stuff + associate(code_maxlen => string_t(stuff%maxlen())) + if (io_status /= 0) error stop "Call writable_t's set_maxlen procedure to increase stop_code maximum size above " // code_maxlen%string() + end associate + end block + stop_code = trim(stop_code) + class default + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" + end select + rank(1) + select type(stuff) + type is(character(len=*)) + stringy_stuff = .csv. string_t(stuff) + stop_code = stringy_stuff%string() + type is(complex) + stringy_stuff = .csv. string_t(stuff) + stop_code = stringy_stuff%string() + type is(double precision) + stringy_stuff = .csv. string_t(stuff) + stop_code = stringy_stuff%string() + type is(integer) + stringy_stuff = .csv. string_t(stuff) + stop_code = stringy_stuff%string() + type is(real) + stringy_stuff = .csv. string_t(stuff) + stop_code = stringy_stuff%string() + class is(string_t) + stringy_stuff = .csv. stuff + stop_code = stringy_stuff%string() + class default + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array" + end select + rank(2) + select type(stuff) + type is(character(len=*)) + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') + stop_code = stringy_stuff%string() + type is(complex) + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') + stop_code = stringy_stuff%string() + type is(double precision) + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') + stop_code = stringy_stuff%string() + type is(integer) + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') + stop_code = stringy_stuff%string() + type is(real) + stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') + stop_code = stringy_stuff%string() + class default + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-2 array" + end select + rank(3) + select type(stuff) + type is(complex) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(double precision) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(integer) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + type is(real) + stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) + stop_code = stringy_stuff%string() + class default + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array" + end select + rank default + associate(stop_code_rank => string_t(stop_code)) + error stop "character_stop_code (in print_and_stop_s): unsupported stop-code rank: " // stop_code_rank%string() + end associate + end select + end procedure + +end submodule julienne_stop_and_print_s From 44ed48b69ea124831aafe8da2054b930cbb90828 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 29 Jun 2026 00:22:13 -0700 Subject: [PATCH 18/48] doc(README): describe output in pure procedures --- README.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e10f5e916..bf0e5f32f 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,34 @@ Julienne: Idiomatic Correctness Checking for Fortran 2023 ========================================================= -The Julienne framework offers a unified approach to writing unit tests and -assertions. Julienne defines idioms for specifying correctness conditions in a -common in tests that wrap the tested procedures or assertions that conditionally -execute inside procedures. Julienne idioms center around expressions built from +The Julienne framework offers unified approaches to unit testing, assertion +enforcement, and formatted error-output inside `pure` procedures. Julienne +defines idioms for specifying correctness conditions in a common way in tests +that wrap the tested procedures or assertions that conditionally execute inside +procedures. Julienne idioms center around expressions built from defined operations: a uniquely flexible Fortran capability allowing developers to define _new_ operators or to overloading Fortran's intrinsic operators. +Output in pure procedures +------------------------- +Julienne's `stop_and_print` generic interface facilitates automatic or user-defined +formatting of various data types and ranks inside `pure` procedures via either of two +specific subroutines: + +1. One with a Julienne `string_t` dummy argument and +2. Another with `character` and unlimited-polymorphic/assumed-rank dummy arugments. + +The first subroutine accepts Julienne `string_t` expressions that, for example, convert +numeric arrays to comma-separated text with `.csv. string_t([1,2,3])`. The second +subroutine prints its `character` argument as a header followed by user-formatted or +automatically-formatted representrations of its polymorphic argument. Julienne +automatically formats and prints numeric scalars or arrays up to rank 3. Users can +format information for printing by encapsulating the text in a Julienne `file_t` object +or passing an object, or object wrapper, that extends Julienne's `writable_t` abstract +type and defines the so-inherited `write(formatted)` generic binding. + +Expressive idioms +----------------- Example expressions | Supported operand types -----------------------------------------------------|-------------------------------------- `x .approximates. y .within. tolerance` | `real`, `double precision` for `x`, `y`, `tolerance` @@ -38,8 +59,6 @@ where * `.equalsExpected.` generates asymmetric diagnostic output for failures, denoting the left- and right-hand sides as the actual value and expected values, respectively; and * `//` appends the subsequent string to diagnostics strings, if any. -Expressive idioms ------------------ ### Assertions Any of the above expressions can be the actual argument in an invocation of Julienne's `call_julienne_assert` function-line preprocessor macro: From a3e5768d100b1c216ce64c917a1564c2d09aa106 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 29 Jun 2026 16:51:51 -0700 Subject: [PATCH 19/48] feat(command_line): mk argument_present generic This commit makes the command_line_t argument_present type-bound function's name a generic interface with two corresponding specific procedures: 1. character_argument_present (renamed from argument_present) 2. string_argument_present (new) The first function still has a character-array dummy argument with fixed-length elements as required by the standard. The second function allows for ragged-edged arrays of character variables by accepting a string_t array dummy argument. --- src/julienne/julienne_command_line_m.f90 | 13 +++++++++++-- src/julienne/julienne_command_line_s.f90 | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/julienne/julienne_command_line_m.f90 b/src/julienne/julienne_command_line_m.f90 index c555d7e0d..4332d5aa5 100644 --- a/src/julienne/julienne_command_line_m.f90 +++ b/src/julienne/julienne_command_line_m.f90 @@ -3,6 +3,7 @@ module julienne_command_line_m !! return command line argument information + use julienne_string_m, only : string_t implicit none private @@ -10,13 +11,14 @@ module julienne_command_line_m type command_line_t contains - procedure, nopass :: argument_present + generic :: argument_present => character_argument_present, string_argument_present + procedure, nopass :: character_argument_present, string_argument_present procedure, nopass :: flag_value end type interface - module function argument_present(acceptable_argument) result(found) + module function character_argument_present(acceptable_argument) result(found) implicit none !! result is .true. only if a command-line argument matches an element of this function's argument character(len=*), intent(in) :: acceptable_argument(:) @@ -26,6 +28,13 @@ module function argument_present(acceptable_argument) result(found) logical found end function + module function string_argument_present(acceptable_argument) result(found) + implicit none + !! same as `character_argument_present` but allowing ragged-edged array of character values + type(string_t), intent(in) :: acceptable_argument(:) + logical found + end function + module function flag_value(flag) !! result = { the value passed immediately after a command-line flag if the flag is present or !! { an empty string otherwise. diff --git a/src/julienne/julienne_command_line_s.f90 b/src/julienne/julienne_command_line_s.f90 index 5413b87dd..dfd486e0b 100644 --- a/src/julienne/julienne_command_line_s.f90 +++ b/src/julienne/julienne_command_line_s.f90 @@ -1,4 +1,4 @@ -! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute +! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt submodule(julienne_command_line_m) julienne_command_line_s @@ -6,7 +6,16 @@ contains - module procedure argument_present + module procedure string_argument_present + integer a + associate(maxlen => maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))])) + found = character_argument_present( & + [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & + ) + end associate + end procedure + + module procedure character_argument_present !! list of acceptable arguments !! sample list: [character(len=len(longest_argument)):: "--benchmark", "-b", "/benchmark", "/b"] !! where dashes support Linux/macOS and slashes support Windows From ad1376b103f987876ebe7ba3e786a427b09b6227 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 29 Jun 2026 17:05:19 -0700 Subject: [PATCH 20/48] feat(command_line): mk flag_value generic This commit makes the command_line_t flag_value type-bound function's name a generic binding with two corresponding specific procedures: 1. character_flag_value (renamed from flag_value) 2. string_flag_value (new) The first function still has a character-array dummy argument. The second function accepts a string_t dummy argument. --- src/julienne/julienne_command_line_m.f90 | 12 ++++++++++-- src/julienne/julienne_command_line_s.f90 | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/julienne/julienne_command_line_m.f90 b/src/julienne/julienne_command_line_m.f90 index 4332d5aa5..9eb207267 100644 --- a/src/julienne/julienne_command_line_m.f90 +++ b/src/julienne/julienne_command_line_m.f90 @@ -13,7 +13,8 @@ module julienne_command_line_m contains generic :: argument_present => character_argument_present, string_argument_present procedure, nopass :: character_argument_present, string_argument_present - procedure, nopass :: flag_value + generic :: flag_value => character_flag_value, string_flag_value + procedure, nopass :: character_flag_value, string_flag_value end type interface @@ -35,7 +36,7 @@ module function string_argument_present(acceptable_argument) result(found) logical found end function - module function flag_value(flag) + module function character_flag_value(flag) result(flag_value) !! result = { the value passed immediately after a command-line flag if the flag is present or !! { an empty string otherwise. implicit none @@ -43,6 +44,13 @@ module function flag_value(flag) character(len=:), allocatable :: flag_value end function + module function string_flag_value(flag) result(flag_value) + !! same as `character_flag_value` but accepting a string_t dummy argument + implicit none + type(string_t), intent(in) :: flag + character(len=:), allocatable :: flag_value + end function + end interface end module diff --git a/src/julienne/julienne_command_line_s.f90 b/src/julienne/julienne_command_line_s.f90 index dfd486e0b..0ffa4dedd 100644 --- a/src/julienne/julienne_command_line_s.f90 +++ b/src/julienne/julienne_command_line_s.f90 @@ -47,7 +47,11 @@ end procedure - module procedure flag_value + module procedure string_flag_value + flag_value = character_flag_value(flag%string()) + end procedure + + module procedure character_flag_value integer argnum, arglen, flag_value_length character(len=:), allocatable :: arg From 0a70b1b1403e39b3c02511676b64d2c1e36b2477 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 29 Jun 2026 23:41:17 -0700 Subject: [PATCH 21/48] doc(example): add pure-stop-and-print.F90 This commit introduces the pure-stop-and-print.F90 example program, which deonstrates the printing of a 2D integeray array, a derived type, or a text file. Usage: fpm run \ --example pure-stop-and-print \ --compiler flang --profile release \ -- [-h|--help] | [--file ] | [--array] | [--derived-type] where pipes (|) separate alternatives, square brackets ([]) delimit optional arguments, and angular brackets (<>) delimit user input. --- example/pure-printing/pure-stop-and-print.F90 | 43 +++++++++++++++++ example/pure-printing/write_stuff_m.F90 | 28 +++++++++++ example/pure-printing/write_stuff_s.F90 | 16 +++++++ src/julienne/julienne_stop_and_print_m.F90 | 17 ++----- src/julienne/julienne_stop_and_print_s.F90 | 48 ++++++++++++++----- 5 files changed, 127 insertions(+), 25 deletions(-) create mode 100644 example/pure-printing/pure-stop-and-print.F90 create mode 100644 example/pure-printing/write_stuff_m.F90 create mode 100644 example/pure-printing/write_stuff_s.F90 diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 new file mode 100644 index 000000000..d21135872 --- /dev/null +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -0,0 +1,43 @@ +! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +program pure_stop_and_print + !! Demonstrate Julienne's support for printing during error termination inside pure procedures + use julienne_m, only : & + command_line_t & + ,file_t & + ,stop_and_print & + ,string_t + use write_stuff_m, only : write_stuff_t + implicit none + + type(command_line_t) command_line + + if ( command_line%argument_present( [string_t("--help"), string_t("-h") ] )) stop usage_info() + if (.not. command_line%argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop usage_info() + + associate(file_name => command_line%flag_value("--file")) + if (len(file_name) > 0) call stop_and_print(header = "___" // file_name // "___", data = file_t(file_name), footer = "________") + end associate + + associate(array => reshape([111,211,121,221, 112,212,122,222], [2,2,2])) + if (command_line%argument_present(["--array"])) call stop_and_print(array) + end associate + + if (command_line%argument_present(["--derived-type"])) call stop_and_print(write_stuff_t()) + +contains + + pure function usage_info() result(message) + character(len=:), allocatable :: message + message = new_line('') // new_line('') & + // 'Usage:' // new_line('') // new_line('') & + // ' fpm run \' // new_line('') & + // ' --example pure-stop-and-print \' // new_line('') & + // ' --compiler flang --profile release \' // new_line('') & + // ' -- [-h|--help] | [--file ] | [--array] | [--derived-type]' // new_line('') // new_line('') & + // 'where pipes (|) separate alternatives, square brackets ([]) delimit' // new_line('') & + // 'optional arguments, and angular brackets (<>) delimit user input.' // new_line('') + end function + +end program pure_stop_and_print diff --git a/example/pure-printing/write_stuff_m.F90 b/example/pure-printing/write_stuff_m.F90 new file mode 100644 index 000000000..9f027c9af --- /dev/null +++ b/example/pure-printing/write_stuff_m.F90 @@ -0,0 +1,28 @@ +! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +module write_stuff_m + !! Demonstrate a derived type that is writable to a stop via Julienne's stop_and_print utility + use julienne_m, only : writable_t + implicit none + + type, extends(writable_t) :: write_stuff_t + integer :: answer_ = 42 + contains + procedure :: write_formatted + end type + + interface + + module subroutine write_formatted(self, unit, edit_descriptor, v_list, iostat, iomsg) + class(write_stuff_t), intent(in) :: self + integer, intent(in) :: unit + character(len=*), intent(in) :: edit_descriptor + integer, intent(in) :: v_list(:) + integer, intent(out) :: iostat + character(len=*), intent(inout) :: iomsg + end subroutine + + end interface + +end module diff --git a/example/pure-printing/write_stuff_s.F90 b/example/pure-printing/write_stuff_s.F90 new file mode 100644 index 000000000..a73010a26 --- /dev/null +++ b/example/pure-printing/write_stuff_s.F90 @@ -0,0 +1,16 @@ +! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +submodule(write_stuff_m) write_stuff_s + implicit none + +contains + + module procedure write_formatted + write(unit,'(a)' ) new_line('') + write(unit,'(a)' ) "write_stuff_t {" // new_line('') + write(unit,'(a,i2,a)') " answer = ", self%answer_, new_line('') + write(unit,'(a)' ) "}" // new_line('') + end procedure + +end submodule diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index c16466782..e9366f3a3 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -35,23 +35,14 @@ subroutine write_formatted_i(self, unit, edit_descriptor, v_list, iostat, iomsg) end interface - interface stop_and_print - - pure module subroutine stop_and_print_string(message) - implicit none - type(string_t), intent(in) :: message - end subroutine + interface - pure module subroutine stop_and_print_header_and_data(header, data) + pure module subroutine stop_and_print(data, header, footer) implicit none - character(len=*), intent(in) :: header - class(*), intent(in) :: data + class(*), intent(in) :: data(..) + character(len=*), intent(in), optional :: header, footer end subroutine - end interface - - interface - pure module subroutine set_maxlen(self, length) implicit none class(writable_t), intent(inout) :: self diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index ad6abde9a..85ece0388 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -8,10 +8,6 @@ contains - module procedure stop_and_print_string - error stop message%string() - end procedure - module procedure set_maxlen self%maxlen_ = length end procedure @@ -20,16 +16,44 @@ length = self%maxlen_ end procedure - module procedure stop_and_print_header_and_data -#ifndef __GFORTRAN__ - error stop new_line('') // header // new_line('') // character_stop_code(data) -#else - block + module procedure stop_and_print + + select rank(data) + rank(0) + select type(data) + type is(character(len=*)) + error stop data + class is(string_t) + error stop data%string() + class default + call stop_and_print_header_data_footer(data, header, footer) + end select + rank default + call stop_and_print_header_data_footer(data, header, footer) + end select + + contains + + pure subroutine stop_and_print_header_data_footer(data, header, footer) + character(len=*), intent(in), optional :: header, footer + class(*), intent(in) :: data(..) character(len=:), allocatable :: code - code = new_line('') // header // new_line('') // character_stop_code(data) + + if (present(header)) then + if (present(footer)) then + code = new_line('') // header // new_line('') // character_stop_code(data) // new_line('') // footer // new_line('') + else + code = new_line('') // header // new_line('') // character_stop_code(data) + end if + else if (present(footer)) then + code = character_stop_code(data) // new_line('') // footer // new_line('') + else + code = new_line('') // character_stop_code(data) // new_line('') + end if + error stop code - end block -#endif + end subroutine + end procedure module procedure character_stop_code From 59648a5ee98f11354bb361137cd3962c1b8115e2 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 00:07:47 -0700 Subject: [PATCH 22/48] doc(example): describe the use of stop_and_print This commit updates 1. `example/README.md` 2. `pure-printing/README.md` with descriptions of the use of Julienne's `stop_and_print` utility. --- example/README.md | 1 + example/pure-printing/README.md | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 example/pure-printing/README.md diff --git a/example/README.md b/example/README.md index 4bcbee270..276ec83ee 100644 --- a/example/README.md +++ b/example/README.md @@ -4,5 +4,6 @@ Please see the following directories for examples of the listed Julienne uses: * [Assertions](./assertions): runtime assertion checking using idioms that evaluate to `test_diagnosis_t` result objects, * [Command-line parsing](./command-line-parsing): checking a command-line flag's presence and getting an associated value using the `command_line_t` type, +* [Printing in procedures](./pure-printing): producing automatically-formatted or user-formatted text in `pure` procedures via the `stop_and_print` subroutine, * [String operations](./strings): operating on strings using the `string_t` type and defined operations, and * [Testing](../demo): a demonstration unit test suite using the `test_t` type. diff --git a/example/pure-printing/README.md b/example/pure-printing/README.md new file mode 100644 index 000000000..76b344b50 --- /dev/null +++ b/example/pure-printing/README.md @@ -0,0 +1,58 @@ +Pure Printing +============= + +This directory contains a program and a supporting module/submodule +pair that collectively demonstrate the use of Julienne's +`stop_and_print` subroutine designed for use inside `pure` procedures. +Specifically, the program shows how to print the following entities: + +- a text file, +- a two-dimensional (2D) integer array, or +- an object of derived type. + +``` +Usage: + fpm run \ + --example pure-stop-and-print \ + --compiler flang --profile release \ + -- [-h|--help] | [--file ] | [--array] | [--derived-type] +``` + +where pipes (|) separate alternatives, square brackets ([]) delimit +optional arguments, and angular brackets (<>) delimit user input. + +Getting help +------------ +The following command prints the above usage text: + +``` + fpm run --example pure-stop-and-print --compiler flang -- --help +``` + +Examples +-------- +### Printing a 2D integer array +The following command prints a 2D integer array defined in the example program: + +``` + fpm run --example pure-stop-and-print --compiler flang -- --array +``` + +### Printing a derived type +The following command prints the derived type defined in this directory's +[write_stuff_m](./write_stuff_m.F90)) module: + +``` + fpm run --example pure-stop-and-print --compiler flang -- --derived-type +``` + +using the derived-type output procedure in the [write_stuff_s](./write_stuff_s.F90)) +submodule. + +### Printing a text file + +The following command prints this repository's `fpm` mannifest: `fpm.toml`. + +``` + fpm run --example pure-stop-and-print --compiler flang -- --file fpm.toml +``` From a04f05f01d1c5af26b3f01c805b885f458c928c1 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 00:29:31 -0700 Subject: [PATCH 23/48] build(gfortran): work around compiler issues Gfortran rejects valid code containing a function invocation as a stop code. This has been reported as GCC issue 126018 at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126018. --- example/pure-printing/pure-stop-and-print.F90 | 9 +++++++++ src/julienne/julienne_stop_and_print_s.F90 | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 index d21135872..0a741cc3b 100644 --- a/example/pure-printing/pure-stop-and-print.F90 +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -13,8 +13,17 @@ program pure_stop_and_print type(command_line_t) command_line +#ifndef __GFORTRAN__ if ( command_line%argument_present( [string_t("--help"), string_t("-h") ] )) stop usage_info() if (.not. command_line%argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop usage_info() +#else + block + character(len=:), allocatable :: stop_code + stop_code = usage_info() + if ( command_line%argument_present( [string_t("--help"), string_t("-h") ] )) stop stop_code + if (.not. command_line%argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop stop_code + end block +#endif associate(file_name => command_line%flag_value("--file")) if (len(file_name) > 0) call stop_and_print(header = "___" // file_name // "___", data = file_t(file_name), footer = "________") diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index 85ece0388..09d838775 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -24,7 +24,15 @@ type is(character(len=*)) error stop data class is(string_t) +#ifndef __GFORTRAN__ error stop data%string() +#else + block + character(len=:), allocatable :: stop_code + stop_code = data%string() + error stop stop_code + end block +#endif class default call stop_and_print_header_data_footer(data, header, footer) end select From 99979914ea4fea55dc8456159c31ad14098bb474 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 00:39:12 -0700 Subject: [PATCH 24/48] fix(stop_and_print): call internal_error_stop Replace `error stop` with `internal_error_stop` --- src/julienne/julienne_stop_and_print_s.F90 | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index 09d838775..2a4a6e6a6 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -4,6 +4,7 @@ submodule(julienne_stop_and_print_m) julienne_stop_and_print_s use julienne_string_m, only : operator(.csv.), operator(.separatedBy.) use julienne_file_m, only : file_t + use julienne_multi_image_m, only : internal_error_stop implicit none contains @@ -22,15 +23,15 @@ rank(0) select type(data) type is(character(len=*)) - error stop data + call internal_error_stop(data) class is(string_t) #ifndef __GFORTRAN__ - error stop data%string() + call internal_error_stop(data%string()) #else block character(len=:), allocatable :: stop_code stop_code = data%string() - error stop stop_code + call internal_error_stop(stop_code) end block #endif class default @@ -59,7 +60,7 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) code = new_line('') // character_stop_code(data) // new_line('') end if - error stop code + call internal_error_stop(code) end subroutine end procedure @@ -97,12 +98,12 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) integer io_status write(stop_code,*,iostat=io_status) stuff associate(code_maxlen => string_t(stuff%maxlen())) - if (io_status /= 0) error stop "Call writable_t's set_maxlen procedure to increase stop_code maximum size above " // code_maxlen%string() + if (io_status /= 0) call internal_error_stop("Call writable_t's set_maxlen procedure to increase stop_code maximum size above " // code_maxlen%string()) end associate end block stop_code = trim(stop_code) class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar" + call internal_error_stop("character_stop_code (in print_and_stop_s): unsupported stop-code type for scalar") end select rank(1) select type(stuff) @@ -125,7 +126,7 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) stringy_stuff = .csv. stuff stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array" + call internal_error_stop("character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-1 array") end select rank(2) select type(stuff) @@ -145,7 +146,7 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) stringy_stuff = [(.csv. string_t(stuff(row,:)) , row=1,size(stuff,1))] .separatedBy. new_line('') stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-2 array" + call internal_error_stop("character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-2 array") end select rank(3) select type(stuff) @@ -162,11 +163,11 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) stop_code = stringy_stuff%string() class default - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array" + call internal_errorestop("character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array") end select rank default associate(stop_code_rank => string_t(stop_code)) - error stop "character_stop_code (in print_and_stop_s): unsupported stop-code rank: " // stop_code_rank%string() + call internal_error_stop("character_stop_code (in print_and_stop_s): unsupported stop-code rank: " // stop_code_rank%string()) end associate end select end procedure From fcf9de6c9a26c1d6d8699d79c980a77db40f4939 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 00:47:37 -0700 Subject: [PATCH 25/48] fix(error stop): replace with internal_error_stop --- src/julienne/julienne_formats_s.F90 | 6 ++++-- src/julienne/julienne_stop_and_print_s.F90 | 2 +- src/julienne/julienne_test_diagnosis_s.F90 | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/julienne/julienne_formats_s.F90 b/src/julienne/julienne_formats_s.F90 index fca17e91a..8d6b3e24e 100644 --- a/src/julienne/julienne_formats_s.F90 +++ b/src/julienne/julienne_formats_s.F90 @@ -3,6 +3,8 @@ submodule(julienne_formats_m) julienne_formats_s !! Construct separated-value formats + use julienne_multi_image_m, only : internal_error_stop + implicit none contains @@ -27,10 +29,10 @@ type is(character(len=*)) format_string = prefix // separator // suffix class default - error stop "format_s separated_values: unsupported type" + call internal_error_stop("format_s separated_values: unsupported type") end select rank default - error stop "formats_s separated_values: unsupported rank" + call internal_error_stop("formats_s separated_values: unsupported rank") end select end procedure diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index 2a4a6e6a6..b64be8e55 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -163,7 +163,7 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) stringy_stuff = [( [(.csv. string_t(stuff(row,:,page)) , row=1,size(stuff,1))] .separatedBy. new_line(''), page = 1,size(stuff,3) )] .separatedBy. (new_line('') // new_line('')) stop_code = stringy_stuff%string() class default - call internal_errorestop("character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array") + call internal_error_stop("character_stop_code (in print_and_stop_s): unsupported stop-code type for rank-3 array") end select rank default associate(stop_code_rank => string_t(stop_code)) diff --git a/src/julienne/julienne_test_diagnosis_s.F90 b/src/julienne/julienne_test_diagnosis_s.F90 index 0ca9bb5d7..514c2338e 100644 --- a/src/julienne/julienne_test_diagnosis_s.F90 +++ b/src/julienne/julienne_test_diagnosis_s.F90 @@ -7,6 +7,7 @@ submodule(julienne_test_diagnosis_m) julienne_test_diagnosis_s use assert_m use julienne_string_m, only : operator(.cat.) + use julienne_multi_image_m, only : internal_error_stop use iso_c_binding, only : c_associated, c_intptr_t implicit none contains @@ -89,7 +90,7 @@ diagnosis = aggregate_vector_diagnosis(reshape(diagnoses, shape=[size(diagnoses)])) rank default associate(diagnoses_rank => string_t(rank(diagnoses))) - error stop "aggregate_diagnosis (julienne_test_diagnosis_s): rank " // diagnoses_rank%string() // " unspported" + call internal_error_stop("aggregate_diagnosis (julienne_test_diagnosis_s): rank " // diagnoses_rank%string() // " unspported") end associate end select From 482bb716452433abdcfa6afa2925cc566083d983 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 00:55:47 -0700 Subject: [PATCH 26/48] build(ifx): work around compiler issue --- src/julienne/julienne_command_line_s.f90 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/julienne/julienne_command_line_s.f90 b/src/julienne/julienne_command_line_s.f90 index 0ffa4dedd..938d100d0 100644 --- a/src/julienne/julienne_command_line_s.f90 +++ b/src/julienne/julienne_command_line_s.f90 @@ -8,11 +8,19 @@ module procedure string_argument_present integer a +#ifndef __INTEL_COMPILER associate(maxlen => maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))])) found = character_argument_present( & [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & ) end associate +#else + associate(maxlen => 128) + found = character_argument_present( & + [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & + ) + end associate +#endif end procedure module procedure character_argument_present From 78ce9bf28d98a1c9416c22b250a3cbd6a039e97f Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 01:45:05 -0700 Subject: [PATCH 27/48] feat(example): mv stop_and_print calls to pure sub --- example/pure-printing/pure-stop-and-print.F90 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 index 0a741cc3b..559f5a898 100644 --- a/example/pure-printing/pure-stop-and-print.F90 +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -26,17 +26,20 @@ program pure_stop_and_print #endif associate(file_name => command_line%flag_value("--file")) - if (len(file_name) > 0) call stop_and_print(header = "___" // file_name // "___", data = file_t(file_name), footer = "________") + if (len(file_name) > 0) call pure_subroutine(.false., .false., file_t(file_name)) end associate - - associate(array => reshape([111,211,121,221, 112,212,122,222], [2,2,2])) - if (command_line%argument_present(["--array"])) call stop_and_print(array) - end associate - - if (command_line%argument_present(["--derived-type"])) call stop_and_print(write_stuff_t()) + call pure_subroutine(command_line%argument_present(["--array"]), command_line%argument_present(["--derived-type"])) contains + pure subroutine pure_subroutine(print_array, print_derived_type, file) + logical, intent(in) :: print_array, print_derived_type + type(file_t), intent(in), optional :: file + if (present(file)) call stop_and_print(header = "______________", data = file, footer = "______________") + if (print_array) call stop_and_print(reshape([111,211,121,221, 112,212,122,222], [2,2,2])) + if (print_derived_type) call stop_and_print(write_stuff_t()) + end subroutine + pure function usage_info() result(message) character(len=:), allocatable :: message message = new_line('') // new_line('') & From 492a874cec41004d3bfeae9a6828202af960c2ec Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 11:39:17 -0700 Subject: [PATCH 28/48] doc(README): fix typo Co-authored-by: Dan Bonachea --- example/pure-printing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pure-printing/README.md b/example/pure-printing/README.md index 76b344b50..d876dcd3e 100644 --- a/example/pure-printing/README.md +++ b/example/pure-printing/README.md @@ -51,7 +51,7 @@ submodule. ### Printing a text file -The following command prints this repository's `fpm` mannifest: `fpm.toml`. +The following command prints this repository's `fpm` manifest: `fpm.toml`. ``` fpm run --example pure-stop-and-print --compiler flang -- --file fpm.toml From b6100f13791650110472a9805ac29bcd416ced34 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 11:39:51 -0700 Subject: [PATCH 29/48] chore(stop_and_print): rm redundant use statement Co-authored-by: Dan Bonachea --- src/julienne/julienne_stop_and_print_s.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index b64be8e55..3f9612e53 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -4,7 +4,6 @@ submodule(julienne_stop_and_print_m) julienne_stop_and_print_s use julienne_string_m, only : operator(.csv.), operator(.separatedBy.) use julienne_file_m, only : file_t - use julienne_multi_image_m, only : internal_error_stop implicit none contains From ffd6586f0be463429355808dbf74ca7dd82ddd22 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 11:44:36 -0700 Subject: [PATCH 30/48] fix(write_formatted): set iostat to 0 --- example/pure-printing/write_stuff_s.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/example/pure-printing/write_stuff_s.F90 b/example/pure-printing/write_stuff_s.F90 index a73010a26..6038a36c8 100644 --- a/example/pure-printing/write_stuff_s.F90 +++ b/example/pure-printing/write_stuff_s.F90 @@ -11,6 +11,7 @@ write(unit,'(a)' ) "write_stuff_t {" // new_line('') write(unit,'(a,i2,a)') " answer = ", self%answer_, new_line('') write(unit,'(a)' ) "}" // new_line('') + iostat = 0 end procedure end submodule From 3b2e3585ad05d51835f05f10404b6b05a94b9c21 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 11:58:18 -0700 Subject: [PATCH 31/48] test(stop_and_print): work around gfortran bug --- test/modules/character_stop_code_test_m.F90 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 5ad5a6f7d..b78b7a61b 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -493,12 +493,21 @@ function check_string_t_1D_array() result(test_diagnosis) test_diagnosis = passing_test() associate(messages => string_t(["foo","bar"])) +#ifdef __GFORTRAN__ associate( & expected_stop_code => .csv. messages & ,stop_code => character_stop_code(messages) & ) test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) end associate +#else + block + type(string_t) expected_stop_code , stop_code + expected_stop_code = .csv. messages + stop_code = character_stop_code(messages) + test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) + end block +#endif end associate end function From 9a407819fa8f4d9201791489a817d667b59e1b10 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:02:27 -0700 Subject: [PATCH 32/48] chore(command_line_s): .f90 -> .F90 to preprocess (cherry picked from commit e35d3cec0633d0283b2ae143eb467570a700a231) --- .../{julienne_command_line_s.f90 => julienne_command_line_s.F90} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/julienne/{julienne_command_line_s.f90 => julienne_command_line_s.F90} (100%) diff --git a/src/julienne/julienne_command_line_s.f90 b/src/julienne/julienne_command_line_s.F90 similarity index 100% rename from src/julienne/julienne_command_line_s.f90 rename to src/julienne/julienne_command_line_s.F90 From 5b08072ed37ab7b8f70bac9d4fb4bb865c104ecf Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:05:44 -0700 Subject: [PATCH 33/48] doc(README): fix typo Co-authored-by: Dan Bonachea (cherry picked from commit 07b890f65aa03b139d35c31a8885491353b8cf91) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf0e5f32f..e67a51e7d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ formatting of various data types and ranks inside `pure` procedures via either o specific subroutines: 1. One with a Julienne `string_t` dummy argument and -2. Another with `character` and unlimited-polymorphic/assumed-rank dummy arugments. +2. Another with `character` and unlimited-polymorphic/assumed-rank dummy arguments. The first subroutine accepts Julienne `string_t` expressions that, for example, convert numeric arrays to comma-separated text with `.csv. string_t([1,2,3])`. The second From 7f4baf2f544911c11ab81b028a7d88c46f670832 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:10:47 -0700 Subject: [PATCH 34/48] chore: fix/redo 5358e --- test/modules/character_stop_code_test_m.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index b78b7a61b..55050d56b 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -493,7 +493,7 @@ function check_string_t_1D_array() result(test_diagnosis) test_diagnosis = passing_test() associate(messages => string_t(["foo","bar"])) -#ifdef __GFORTRAN__ +#ifndef __GFORTRAN__ associate( & expected_stop_code => .csv. messages & ,stop_code => character_stop_code(messages) & From c1117f3317b852dbc212612818a562018d22fe16 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:13:29 -0700 Subject: [PATCH 35/48] fix(stop_and_print): import internal_error_stop --- src/julienne/julienne_stop_and_print_s.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index 3f9612e53..b64be8e55 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -4,6 +4,7 @@ submodule(julienne_stop_and_print_m) julienne_stop_and_print_s use julienne_string_m, only : operator(.csv.), operator(.separatedBy.) use julienne_file_m, only : file_t + use julienne_multi_image_m, only : internal_error_stop implicit none contains From 817c3a374cd7a6bb853642628b481c6675cebe2a Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:20:29 -0700 Subject: [PATCH 36/48] test(stop_and_print): work around gfortran associate issue --- test/modules/character_stop_code_test_m.F90 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 55050d56b..93977b6bc 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -492,23 +492,25 @@ function check_string_t_1D_array() result(test_diagnosis) test_diagnosis = passing_test() - associate(messages => string_t(["foo","bar"])) #ifndef __GFORTRAN__ + associate(messages => string_t(["foo","bar"])) associate( & expected_stop_code => .csv. messages & ,stop_code => character_stop_code(messages) & ) test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) end associate + end associate #else block type(string_t) expected_stop_code , stop_code + type(string_t), allocatable :: messages(:) + messages = string_t(["foo","bar"]) expected_stop_code = .csv. messages stop_code = character_stop_code(messages) test_diagnosis = test_diagnosis .also. (stop_code .equalsExpected. expected_stop_code) end block #endif - end associate end function function check_file_t() result(test_diagnosis) From 17977b58694901ec0794b74bd29e4f54bdfdf2bd Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 12:31:35 -0700 Subject: [PATCH 37/48] fix(string_argument_present): work around gfortran 13 --- src/julienne/julienne_command_line_s.F90 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/julienne/julienne_command_line_s.F90 b/src/julienne/julienne_command_line_s.F90 index 938d100d0..525f8443b 100644 --- a/src/julienne/julienne_command_line_s.F90 +++ b/src/julienne/julienne_command_line_s.F90 @@ -1,6 +1,8 @@ ! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + submodule(julienne_command_line_m) julienne_command_line_s implicit none @@ -8,14 +10,14 @@ module procedure string_argument_present integer a -#ifndef __INTEL_COMPILER - associate(maxlen => maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))])) +#if defined(__INTEL_COMPILER) || (GCC_VERSION > 0 && GCC_VERSION < 140000) + associate(maxlen => 128) found = character_argument_present( & [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & ) end associate #else - associate(maxlen => 128) + associate(maxlen => maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))])) found = character_argument_present( & [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & ) From dc77c1e656d97a11c25088f2581b89f54801c140 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 13:26:47 -0700 Subject: [PATCH 38/48] test(stop_and_print): skip tests with flang 19 --- test/modules/character_stop_code_test_m.F90 | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 93977b6bc..4156b89bb 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -58,15 +58,27 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & +#ifndef __GFORTRAN__ && __FLANG_MAJOR > 19 test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & -#ifndef __GFORTRAN__ ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code"), usher(check_file_t)) & ,test_description_t(string_t("converting a writable_t child object into character stop code") , usher(check_writable_t)) & -#else +#elif __GFORTRAN__ + test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & + ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & + ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & + ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & +#elif __flang__ + test_description_t(string_t("converting scalars to character stop codes") ) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes")) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") ) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") ) & ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & From d56344f0b45d72aa86c27bd7e63262a46ce96494 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 13:34:50 -0700 Subject: [PATCH 39/48] chore: refactor compiler macro version logic --- test/modules/character_stop_code_test_m.F90 | 23 ++++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 4156b89bb..8645b7442 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -58,15 +58,8 @@ function results() result(test_results) type(character_stop_code_test_t) character_stop_code_test test_descriptions = [ & -#ifndef __GFORTRAN__ && __FLANG_MAJOR > 19 - test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & - ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & - ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & - ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & - ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & - ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code"), usher(check_file_t)) & - ,test_description_t(string_t("converting a writable_t child object into character stop code") , usher(check_writable_t)) & -#elif __GFORTRAN__ +#if __GFORTRAN__ + ! Skip some tests test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & @@ -74,7 +67,8 @@ function results() result(test_results) ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & -#elif __flang__ +#elif __FLANG_MAJOR__ > 19 + ! Skip all tests test_description_t(string_t("converting scalars to character stop codes") ) & ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes")) & ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") ) & @@ -82,6 +76,15 @@ function results() result(test_results) ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & +#else + ! Run all tests + test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & + ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes"), usher(check_intrinsic_1D_arrays)) & + ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_2D_arrays)) & + ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") , usher(check_intrinsic_3D_arrays)) & + ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") , usher(check_string_t_1D_array)) & + ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code"), usher(check_file_t)) & + ,test_description_t(string_t("converting a writable_t child object into character stop code") , usher(check_writable_t)) & #endif ] test_results = character_stop_code_test%run(test_descriptions) From b034c936377bb5e62320ab941417aead75d87715 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 13:42:44 -0700 Subject: [PATCH 40/48] test(stop_and_print): skip with flang 19 --- test/driver.F90 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/driver.F90 b/test/driver.F90 index 3112f44df..124af541f 100644 --- a/test/driver.F90 +++ b/test/driver.F90 @@ -27,7 +27,10 @@ program test_suite_driver associate(test_harness => test_harness_t([ & test_fixture_t( assert_test_t()) & ,test_fixture_t( bin_test_t()) & +#if (__FLANG_MAJOR__ > 0) && (__FLANG_MAJOR__ < 20) +#else ,test_fixture_t( character_stop_code_test_t()) & +#endif ,test_fixture_t( formats_test_t()) & ,test_fixture_t( multi_image_test_t()) & ,test_fixture_t( string_test_t()) & From ae5f00ddfb95273f8a5c761e192a7b28876c3dbe Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 30 Jun 2026 17:02:41 -0700 Subject: [PATCH 41/48] build(stop_and_print): HAVE_STOP_AND_PRINT_SUPPORT This commit defined and uses a new macro to remove the new stop_and_print feature and corresponding tests with compilers or compiler versions that do do not compile the code correctly. --- include/language-support.F90 | 6 ++++++ src/julienne/julienne_stop_and_print_m.F90 | 5 +++++ src/julienne/julienne_stop_and_print_s.F90 | 6 ++++++ test/driver.F90 | 3 +-- test/modules/character_stop_code_test_m.F90 | 13 ++++--------- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/language-support.F90 b/include/language-support.F90 index ff0678283..c85af7148 100644 --- a/include/language-support.F90 +++ b/include/language-support.F90 @@ -14,6 +14,12 @@ #define GCC_GE_MINIMUM #endif +#if defined(__GFORTRAN__) || __flang_major__ >= 20 || __INTEL_COMPILER >= 20260000 +# define HAVE_STOP_AND_PRINT_SUPPORT 1 +#else +# define HAVE_STOP_AND_PRINT_SUPPORT 0 +#endif + ! If not already determined, make a compiler-dependent determination of whether Julienne may use ! multi-image features such as `this_image()` and `sync all`. #ifndef HAVE_MULTI_IMAGE_SUPPORT diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 index e9366f3a3..539fec4dc 100644 --- a/src/julienne/julienne_stop_and_print_m.F90 +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -1,6 +1,10 @@ ! Copyright (c), The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + +#if HAVE_STOP_AND_PRINT_SUPPORT + module julienne_stop_and_print_m !! Define a pure subroutine that formats and prints various data types during error termination use julienne_string_m, only : string_t @@ -64,3 +68,4 @@ pure module function character_stop_code(stuff) result(stop_code) end interface end module julienne_stop_and_print_m +#endif diff --git a/src/julienne/julienne_stop_and_print_s.F90 b/src/julienne/julienne_stop_and_print_s.F90 index b64be8e55..ad46d45a7 100644 --- a/src/julienne/julienne_stop_and_print_s.F90 +++ b/src/julienne/julienne_stop_and_print_s.F90 @@ -1,6 +1,10 @@ ! Copyright (c), The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + +#if HAVE_STOP_AND_PRINT_SUPPORT + submodule(julienne_stop_and_print_m) julienne_stop_and_print_s use julienne_string_m, only : operator(.csv.), operator(.separatedBy.) use julienne_file_m, only : file_t @@ -173,3 +177,5 @@ pure subroutine stop_and_print_header_data_footer(data, header, footer) end procedure end submodule julienne_stop_and_print_s + +#endif diff --git a/test/driver.F90 b/test/driver.F90 index 124af541f..3301f0a73 100644 --- a/test/driver.F90 +++ b/test/driver.F90 @@ -27,8 +27,7 @@ program test_suite_driver associate(test_harness => test_harness_t([ & test_fixture_t( assert_test_t()) & ,test_fixture_t( bin_test_t()) & -#if (__FLANG_MAJOR__ > 0) && (__FLANG_MAJOR__ < 20) -#else +#if HAVE_STOP_AND_PRINT_SUPPORT ,test_fixture_t( character_stop_code_test_t()) & #endif ,test_fixture_t( formats_test_t()) & diff --git a/test/modules/character_stop_code_test_m.F90 b/test/modules/character_stop_code_test_m.F90 index 8645b7442..c05c92194 100644 --- a/test/modules/character_stop_code_test_m.F90 +++ b/test/modules/character_stop_code_test_m.F90 @@ -3,6 +3,8 @@ #include "language-support.F90" +#if HAVE_STOP_AND_PRINT_SUPPORT + module character_stop_code_test_m !! Check data partitioning across bins use julienne_m, only : & @@ -67,15 +69,6 @@ function results() result(test_results) ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & -#elif __FLANG_MAJOR__ > 19 - ! Skip all tests - test_description_t(string_t("converting scalars to character stop codes") ) & - ,test_description_t(string_t("converting 1D arrays to comma-separated-value (CSV) character stop codes")) & - ,test_description_t(string_t("converting 2D arrays to new-line-separated CSV character stop codes") ) & - ,test_description_t(string_t("converting 3D arrays to new-line-separated CSV character stop codes") ) & - ,test_description_t(string_t("converting a 1D string_t array into a CSV character stop code") ) & - ,test_description_t(string_t("converting a file_t object into a new-line-separated character stop code")) & - ,test_description_t(string_t("converting a writable_t child object into character stop code") ) & #else ! Run all tests test_description_t(string_t("converting scalars to character stop codes") , usher(check_intrinsic_scalars)) & @@ -565,3 +558,5 @@ subroutine write_formatted(self, unit, edit_descriptor, v_list, iostat, iomsg) end subroutine end module character_stop_code_test_m + +#endif From aed94def60557bb66da969e7b24486eda3b00387 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Tue, 30 Jun 2026 22:02:53 -0400 Subject: [PATCH 42/48] Add missing preprocessor directives for HAVE_STOP_AND_PRINT_SUPPORT Co-authored-by: Dan Bonachea --- src/julienne_m.F90 | 4 ++++ test/driver.F90 | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/julienne_m.F90 b/src/julienne_m.F90 index 3cca68e04..0bb77a5ab 100644 --- a/src/julienne_m.F90 +++ b/src/julienne_m.F90 @@ -1,6 +1,8 @@ ! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + module julienne_m !! Global aggregation of all public entities use julienne_assert_m, only : call_julienne_assert_, julienne_assert @@ -9,7 +11,9 @@ module julienne_m use julienne_file_m, only : file_t use julienne_formats_m, only : separated_values, csv use julienne_github_ci_m, only : github_ci +#if HAVE_STOP_AND_PRINT_SUPPORT use julienne_stop_and_print_m, only : stop_and_print, writable_t +#endif use julienne_string_m, only : string_t, array_of_strings & ,operator(.cat.) & ,operator(.csv.) & diff --git a/test/driver.F90 b/test/driver.F90 index 3301f0a73..c1800afec 100644 --- a/test/driver.F90 +++ b/test/driver.F90 @@ -1,6 +1,8 @@ ! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + program test_suite_driver !! Julienne test-suite driver @@ -10,7 +12,9 @@ program test_suite_driver ! Modules containing test_t child types: use assert_test_m ,only : assert_test_t use bin_test_m ,only : bin_test_t +#if HAVE_STOP_AND_PRINT_SUPPORT use character_stop_code_test_m ,only : character_stop_code_test_t +#endif use command_line_test_m ,only : command_line_test_t use formats_test_m ,only : formats_test_t use multi_image_test_m ,only : multi_image_test_t, multi_image_setup From 7fda68320d4ee25e1ac583e8f9bf60e73b49fc67 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Tue, 30 Jun 2026 19:16:50 -0700 Subject: [PATCH 43/48] Add more missing preprocessor conditionals for HAVE_STOP_AND_PRINT_SUPPORT --- example/pure-printing/pure-stop-and-print.F90 | 6 ++++++ example/pure-printing/write_stuff_m.F90 | 4 ++++ example/pure-printing/write_stuff_s.F90 | 4 ++++ test/test_stop_and_print.F90 | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 index 559f5a898..47cd51ca4 100644 --- a/example/pure-printing/pure-stop-and-print.F90 +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -1,7 +1,10 @@ ! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + program pure_stop_and_print +#if HAVE_STOP_AND_PRINT_SUPPORT !! Demonstrate Julienne's support for printing during error termination inside pure procedures use julienne_m, only : & command_line_t & @@ -52,4 +55,7 @@ pure function usage_info() result(message) // 'optional arguments, and angular brackets (<>) delimit user input.' // new_line('') end function +#else + error stop "Julienne's stop_and_print feature is not supported on this compiler" +#endif end program pure_stop_and_print diff --git a/example/pure-printing/write_stuff_m.F90 b/example/pure-printing/write_stuff_m.F90 index 9f027c9af..116cb976d 100644 --- a/example/pure-printing/write_stuff_m.F90 +++ b/example/pure-printing/write_stuff_m.F90 @@ -1,6 +1,9 @@ ! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + +#if HAVE_STOP_AND_PRINT_SUPPORT module write_stuff_m !! Demonstrate a derived type that is writable to a stop via Julienne's stop_and_print utility use julienne_m, only : writable_t @@ -26,3 +29,4 @@ module subroutine write_formatted(self, unit, edit_descriptor, v_list, iostat, i end interface end module +#endif diff --git a/example/pure-printing/write_stuff_s.F90 b/example/pure-printing/write_stuff_s.F90 index 6038a36c8..06b265711 100644 --- a/example/pure-printing/write_stuff_s.F90 +++ b/example/pure-printing/write_stuff_s.F90 @@ -1,6 +1,9 @@ ! Copyright (c) 2024-2026, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt +#include "language-support.F90" + +#if HAVE_STOP_AND_PRINT_SUPPORT submodule(write_stuff_m) write_stuff_s implicit none @@ -15,3 +18,4 @@ end procedure end submodule +#endif diff --git a/test/test_stop_and_print.F90 b/test/test_stop_and_print.F90 index 194eab78c..e952c320d 100644 --- a/test/test_stop_and_print.F90 +++ b/test/test_stop_and_print.F90 @@ -5,6 +5,7 @@ #include "language-support.F90" program stop_and_print_in_pure_procedure +#if HAVE_STOP_AND_PRINT_SUPPORT !! Conditionally test printing via error termination inside a pure procedure use julienne_m, only : command_line_t, string_t, operator(.csv.), stop_and_print implicit none @@ -35,4 +36,7 @@ pure subroutine pure_subroutine call stop_and_print("array = " // .csv. string_t(array)) end subroutine +#else + stop "SKIPPED: Julienne's stop_and_print feature is not supported on this compiler" +#endif end program From 5c83a22f8fa8df8972390104a75e272e114ffa4a Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 1 Jul 2026 11:58:20 -0700 Subject: [PATCH 44/48] Disable HAVE_STOP_AND_PRINT_SUPPORT for Intel 2026.0 This was always crashing at runtime, we just missed the failure in CI --- include/language-support.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/language-support.F90 b/include/language-support.F90 index c85af7148..7b3e204a1 100644 --- a/include/language-support.F90 +++ b/include/language-support.F90 @@ -14,7 +14,7 @@ #define GCC_GE_MINIMUM #endif -#if defined(__GFORTRAN__) || __flang_major__ >= 20 || __INTEL_COMPILER >= 20260000 +#if defined(__GFORTRAN__) || __flang_major__ >= 20 # define HAVE_STOP_AND_PRINT_SUPPORT 1 #else # define HAVE_STOP_AND_PRINT_SUPPORT 0 From 7a3d443c30f92c9c9455b2c49ae43f7da665db98 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 1 Jul 2026 11:39:25 -0700 Subject: [PATCH 45/48] test_stop_and_print: Fix copy pasta that made a confusing message --- test/test_stop_and_print.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_stop_and_print.F90 b/test/test_stop_and_print.F90 index e952c320d..718dcf9ad 100644 --- a/test/test_stop_and_print.F90 +++ b/test/test_stop_and_print.F90 @@ -17,7 +17,7 @@ program stop_and_print_in_pure_procedure #endif if (.not. command_line%argument_present([character(len=len("--help"))::"--help","-h"])) then #if TEST_INTENTIONAL_FAILURE && ASSERTIONS - if (me==1) print '(a)', new_line('') // 'Test the intentional failure of an idiomatic assertion: ' // new_line('') + if (me==1) print '(a)', new_line('') // 'Test the intentional failure of an idiomatic stop_and_print: ' // new_line('') call pure_subroutine #else if (me==1) print '(a)', & From fe9abbb72f503c8429ed84dcfb7e46453b1a7112 Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 1 Jul 2026 11:24:39 -0700 Subject: [PATCH 46/48] command_line_t: Remove uses of generic interface for now These break all current releases of LFortran, so are being deferred to a separate PR. Minor related cleanups --- example/pure-printing/pure-stop-and-print.F90 | 15 ++---- src/julienne/julienne_command_line_m.f90 | 18 +++---- src/julienne/julienne_command_line_s.F90 | 47 ++++++++++--------- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 index 47cd51ca4..ebcf49087 100644 --- a/example/pure-printing/pure-stop-and-print.F90 +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -15,18 +15,11 @@ program pure_stop_and_print implicit none type(command_line_t) command_line + character(len=:), allocatable :: stop_code -#ifndef __GFORTRAN__ - if ( command_line%argument_present( [string_t("--help"), string_t("-h") ] )) stop usage_info() - if (.not. command_line%argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop usage_info() -#else - block - character(len=:), allocatable :: stop_code - stop_code = usage_info() - if ( command_line%argument_present( [string_t("--help"), string_t("-h") ] )) stop stop_code - if (.not. command_line%argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop stop_code - end block -#endif + stop_code = usage_info() + if ( command_line%string_argument_present( [string_t("--help"), string_t("-h") ] )) stop stop_code + if (.not. command_line%string_argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop stop_code associate(file_name => command_line%flag_value("--file")) if (len(file_name) > 0) call pure_subroutine(.false., .false., file_t(file_name)) diff --git a/src/julienne/julienne_command_line_m.f90 b/src/julienne/julienne_command_line_m.f90 index 9eb207267..7253d533b 100644 --- a/src/julienne/julienne_command_line_m.f90 +++ b/src/julienne/julienne_command_line_m.f90 @@ -11,15 +11,15 @@ module julienne_command_line_m type command_line_t contains - generic :: argument_present => character_argument_present, string_argument_present - procedure, nopass :: character_argument_present, string_argument_present - generic :: flag_value => character_flag_value, string_flag_value - procedure, nopass :: character_flag_value, string_flag_value + generic :: generic_argument_present => argument_present, string_argument_present + procedure, nopass :: argument_present, string_argument_present + generic :: generic_flag_value => flag_value, string_flag_value + procedure, nopass :: flag_value, string_flag_value end type interface - module function character_argument_present(acceptable_argument) result(found) + module function argument_present(acceptable_argument) result(found) implicit none !! result is .true. only if a command-line argument matches an element of this function's argument character(len=*), intent(in) :: acceptable_argument(:) @@ -36,19 +36,19 @@ module function string_argument_present(acceptable_argument) result(found) logical found end function - module function character_flag_value(flag) result(flag_value) + module function flag_value(flag) result(value) !! result = { the value passed immediately after a command-line flag if the flag is present or !! { an empty string otherwise. implicit none character(len=*), intent(in) :: flag - character(len=:), allocatable :: flag_value + character(len=:), allocatable :: value end function - module function string_flag_value(flag) result(flag_value) + module function string_flag_value(flag) result(value) !! same as `character_flag_value` but accepting a string_t dummy argument implicit none type(string_t), intent(in) :: flag - character(len=:), allocatable :: flag_value + character(len=:), allocatable :: value end function end interface diff --git a/src/julienne/julienne_command_line_s.F90 b/src/julienne/julienne_command_line_s.F90 index 525f8443b..f50535f00 100644 --- a/src/julienne/julienne_command_line_s.F90 +++ b/src/julienne/julienne_command_line_s.F90 @@ -10,22 +10,27 @@ module procedure string_argument_present integer a -#if defined(__INTEL_COMPILER) || (GCC_VERSION > 0 && GCC_VERSION < 140000) - associate(maxlen => 128) - found = character_argument_present( & - [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & - ) - end associate -#else - associate(maxlen => maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))])) - found = character_argument_present( & - [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & - ) - end associate -#endif + integer maxlen + + maxlen = maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))]) + found = argument_present( & + [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & + ) +# ifdef __INTEL_COMPILER + ! workaround ifx bug where it thinks argument to len must be a constant expression + contains + pure function len(char) result(l) + character(len=*), intent(in) :: char + integer :: l + block + intrinsic :: len + l = len(char) + end block + end function +# endif end procedure - module procedure character_argument_present + module procedure argument_present ! specific procedure for character argument !! list of acceptable arguments !! sample list: [character(len=len(longest_argument)):: "--benchmark", "-b", "/benchmark", "/b"] !! where dashes support Linux/macOS and slashes support Windows @@ -58,11 +63,11 @@ end procedure module procedure string_flag_value - flag_value = character_flag_value(flag%string()) + value = flag_value(flag%string()) end procedure - module procedure character_flag_value - integer argnum, arglen, flag_value_length + module procedure flag_value ! specific procedure for character argument + integer argnum, arglen, value_length character(len=:), allocatable :: arg do argnum = 1,command_argument_count()-1 @@ -70,14 +75,14 @@ allocate(character(len=arglen) :: arg) call get_command_argument(argnum, arg) if (arg==flag) then - call get_command_argument(argnum+1, length=flag_value_length) - allocate(character(len=flag_value_length) :: flag_value) - call get_command_argument(argnum+1, flag_value) + call get_command_argument(argnum+1, length=value_length) + allocate(character(len=value_length) :: value) + call get_command_argument(argnum+1, value) return end if deallocate(arg) end do - flag_value="" + value="" end procedure end submodule From cd52fbe02ec69a30c2bdfefb4c91190df70c535c Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 1 Jul 2026 14:19:59 -0700 Subject: [PATCH 47/48] workaround some gfortran bugs --- example/pure-printing/pure-stop-and-print.F90 | 9 +++++---- src/julienne/julienne_command_line_s.F90 | 18 +++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/example/pure-printing/pure-stop-and-print.F90 b/example/pure-printing/pure-stop-and-print.F90 index ebcf49087..b7ed1902a 100644 --- a/example/pure-printing/pure-stop-and-print.F90 +++ b/example/pure-printing/pure-stop-and-print.F90 @@ -15,15 +15,16 @@ program pure_stop_and_print implicit none type(command_line_t) command_line - character(len=:), allocatable :: stop_code + character(len=:), allocatable :: stop_code, file_name stop_code = usage_info() if ( command_line%string_argument_present( [string_t("--help"), string_t("-h") ] )) stop stop_code if (.not. command_line%string_argument_present( [string_t("--file"), string_t("--array"), string_t("--derived-type")] )) error stop stop_code - associate(file_name => command_line%flag_value("--file")) - if (len(file_name) > 0) call pure_subroutine(.false., .false., file_t(file_name)) - end associate + file_name = command_line%flag_value("--file") + if (len(file_name) > 0) then + call pure_subroutine(.false., .false., file_t(file_name)) + end if call pure_subroutine(command_line%argument_present(["--array"]), command_line%argument_present(["--derived-type"])) contains diff --git a/src/julienne/julienne_command_line_s.F90 b/src/julienne/julienne_command_line_s.F90 index f50535f00..675c4471d 100644 --- a/src/julienne/julienne_command_line_s.F90 +++ b/src/julienne/julienne_command_line_s.F90 @@ -9,13 +9,17 @@ contains module procedure string_argument_present - integer a - integer maxlen - - maxlen = maxval([(len(acceptable_argument(a)%string()), a = 1,size(acceptable_argument))]) - found = argument_present( & - [( [character(len=maxlen) :: acceptable_argument(a)%string()], a = 1, size(acceptable_argument))] & - ) + integer a, sz, maxlen + + sz = size(acceptable_argument) + maxlen = maxval([(len(acceptable_argument(a)%string()), a = 1,sz)]) + block + character(maxlen) :: strings(size(acceptable_argument)) + do a=1,sz + strings(a) = acceptable_argument(a)%string() + end do + found = argument_present(strings) + end block # ifdef __INTEL_COMPILER ! workaround ifx bug where it thinks argument to len must be a constant expression contains From e71c86e3b76507c3133d1fb753597934d468922a Mon Sep 17 00:00:00 2001 From: Dan Bonachea Date: Wed, 1 Jul 2026 13:07:43 -0700 Subject: [PATCH 48/48] CI: Add coverage for stop-and-print examples --- .github/workflows/build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bec60033a..9c185687c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -293,6 +293,25 @@ jobs: test ${PIPESTATUS[0]} > 0 && grep -q "expected 2; actual value is 1" output ) + - name: Run Stop-and-Print Example (Assertions ON) + if: ${{ matrix.compiler == 'gfortran' || + (matrix.compiler == 'flang' && (matrix.version >= 20 || matrix.version == 'latest') ) }} + env: + FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -DASSERTIONS + run: | + ( set +e + fpm run --example pure-stop-and-print ${FPM_FLAGS} --flag "$FFLAGS" -- --array 2>&1 | tee output + test ${PIPESTATUS[0]} > 0 && grep -q "212,222" output + ) + ( set +e + fpm run --example pure-stop-and-print ${FPM_FLAGS} --flag "$FFLAGS" -- --derived-type 2>&1 | tee output + test ${PIPESTATUS[0]} > 0 && grep -q "answer = 42" output + ) + ( set +e + fpm run --example pure-stop-and-print ${FPM_FLAGS} --flag "$FFLAGS" -- --file fpm.toml 2>&1 | tee output + test ${PIPESTATUS[0]} > 0 && grep -q "[install]" output + ) + - name: Test w/ Parallel Callbacks env: FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -DJULIENNE_PARALLEL_CALLBACKS --flag -DTEST_PARALLEL_CALLBACKS