From b643a0acbed7518c6fa0ddb76f60db8fafb86e27 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 22 Jun 2026 08:55:53 -0400 Subject: [PATCH 01/18] 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 | 20 ++++++++++++ src/julienne_m.F90 | 1 + test/test_stop_and_print.F90 | 38 ++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 src/julienne/julienne_stop_and_print_m.F90 create mode 100644 test/test_stop_and_print.F90 diff --git a/src/julienne/julienne_stop_and_print_m.F90 b/src/julienne/julienne_stop_and_print_m.F90 new file mode 100644 index 000000000..19d358b9a --- /dev/null +++ b/src/julienne/julienne_stop_and_print_m.F90 @@ -0,0 +1,20 @@ +! Copyright (c), The Regents of the University of California and Sourcery Institute +! 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 + implicit none + + private + public :: stop_and_print + +contains + + pure subroutine stop_and_print(message) + implicit none + type(string_t), intent(in) :: message + error stop message%string() + end subroutine + +end module diff --git a/src/julienne_m.F90 b/src/julienne_m.F90 index abff7c3d2..1825a9343 100644 --- a/src/julienne_m.F90 +++ b/src/julienne_m.F90 @@ -9,6 +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_string_m, only : string_t, array_of_strings & ,operator(.cat.) & ,operator(.csv.) & diff --git a/test/test_stop_and_print.F90 b/test/test_stop_and_print.F90 new file mode 100644 index 000000000..194eab78c --- /dev/null +++ b/test/test_stop_and_print.F90 @@ -0,0 +1,38 @@ +! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +#include "julienne-assert-macros.h" +#include "language-support.F90" + +program stop_and_print_in_pure_procedure + !! 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 + +#if HAVE_MULTI_IMAGE_SUPPORT + associate(command_line => command_line_t(), me => this_image()) +#else + associate(command_line => command_line_t(), me => 1) +#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('') + call pure_subroutine +#else + if (me==1) print '(a)', & + new_line('') // & + 'Skipping the test in ' // __FILE__ // '.' // new_line('') // & + 'Add the following to your fpm command to test the stop_and_print: --flag "-DASSERTIONS -DTEST_INTENTIONAL_FAILURE"' // & + new_line('') +#endif + end if + end associate + +contains + + pure subroutine pure_subroutine + integer, parameter :: array(*) = [1,2,3,4] + call stop_and_print("array = " // .csv. string_t(array)) + end subroutine + +end program From 274bfd64477d525d2fb29a42604d4a842f62074e Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Wed, 24 Jun 2026 21:39:28 -0600 Subject: [PATCH 02/18] 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 7d77843996ffda006f5a118407c7f05e819ba041 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 12:58:56 -0600 Subject: [PATCH 03/18] 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 57806be06c2da0c2e4210107f1064f7caf6dd053 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 18:41:36 -0600 Subject: [PATCH 04/18] 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 cc072461931a40ec8e99fdf721517601c18ec88c Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Fri, 26 Jun 2026 19:24:10 -0600 Subject: [PATCH 05/18] 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 480acf7773651da5344421d6fd6c1ccde9b7eaff Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 06:10:12 -0600 Subject: [PATCH 06/18] 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 9b32f8c8a45dc024517cbf02d77374ce7b1a03a5 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 17:30:51 -0600 Subject: [PATCH 07/18] 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 ff9102dd36854cbc1b7d7979990f98f3b4b7f948 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 18:51:56 -0600 Subject: [PATCH 08/18] 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 06834911ff9b96833325b9de39e93680316ab765 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sat, 27 Jun 2026 19:10:44 -0600 Subject: [PATCH 09/18] 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 aed7e2bbf23d242940a027f61fa8459d0ad80697 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 19:38:58 -0700 Subject: [PATCH 10/18] 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 4f1a41f157d576fb57fd3e5ac6cfa6a53590440c Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 20:36:35 -0700 Subject: [PATCH 11/18] 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 dd3c8151b1d5ec2a89b7cf7a7aabed539fbf9922 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:12:39 -0700 Subject: [PATCH 12/18] 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 404da0307cf2335e80b20a07236a1ceb7644cca6 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:14:21 -0700 Subject: [PATCH 13/18] 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 79afcc22e07bbe65b943c798a96bf51e49cf283d Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 21:14:48 -0700 Subject: [PATCH 14/18] 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 a08eec35ec2d748ca20f6f1ee83500a2be80a6e8 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 22:40:48 -0700 Subject: [PATCH 15/18] 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 b053b887f0cdf33645de51be20b79a44738cff49 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 23:01:26 -0700 Subject: [PATCH 16/18] 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 b6a7f80c68f2ec8dbc7c8da9e98f881ca16097e0 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Sun, 28 Jun 2026 23:15:57 -0700 Subject: [PATCH 17/18] 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 ba844c9d66cb1c298df20c210e14c3077e847bed Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Mon, 29 Jun 2026 00:22:13 -0700 Subject: [PATCH 18/18] 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 644f21e4d..3fa13275c 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: