From b6c0d1073e3b792c456abd19095ab1f1c36e93f1 Mon Sep 17 00:00:00 2001 From: Brijesh-Thakkar Date: Sun, 14 Dec 2025 06:12:58 +0530 Subject: [PATCH 1/5] stdlib_io: add Python-like input() function --- doc/specs/stdlib_io.md | 46 ++++++++++++++++++++++++++++++++++++ example/io/example_input.f90 | 2 ++ src/stdlib_io.fypp | 43 ++++++++++++++++++++++++++++++++- test/io/test_input.f90 | 3 +++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 example/io/example_input.f90 create mode 100644 test/io/test_input.f90 diff --git a/doc/specs/stdlib_io.md b/doc/specs/stdlib_io.md index 46befe2ea..ecdbdcd6f 100644 --- a/doc/specs/stdlib_io.md +++ b/doc/specs/stdlib_io.md @@ -246,6 +246,52 @@ Read a whole line from a formatted unit into a string variable ```fortran {!example/io/example_get_line.f90!} ``` +## `input` — read a line from standard input + +### Status + +Experimental + +### Description + +Reads a line from standard input, optionally displaying a prompt. +This is similar to Python’s `input()` function. + +The function returns the input as an allocatable character string. +Trailing spaces and tabs are preserved. +No numeric conversion is performed. + +### Syntax + +`line = ` [[stdlib_io(module):input(function)]] `([prompt][, iostat][, iomsg])` + +### Arguments + +`prompt` (optional): +A character expression containing a prompt to be displayed before reading input. + +`iostat` (optional): +Default integer, contains status of reading from standard input. +Zero indicates success. + +`iomsg` (optional): +Deferred-length character variable containing an error message if `iostat` is non-zero. + +### Return value + +Returns a deferred-length allocatable `character` variable containing the input line. + +### Notes + +- Trailing spaces and tabs are preserved +- No type conversion is performed +- To convert to numbers, use `read(line, *)` + +### Example + +```fortran +{!example/io/example_input.f90!} +``` ## Formatting constants diff --git a/example/io/example_input.f90 b/example/io/example_input.f90 new file mode 100644 index 000000000..4dfbaf411 --- /dev/null +++ b/example/io/example_input.f90 @@ -0,0 +1,2 @@ +name = input("Enter your name: ") +print *, "Hello:", name diff --git a/src/stdlib_io.fypp b/src/stdlib_io.fypp index 6ba82ad12..f812ecd17 100644 --- a/src/stdlib_io.fypp +++ b/src/stdlib_io.fypp @@ -16,7 +16,7 @@ module stdlib_io implicit none private ! Public API - public :: loadtxt, savetxt, open, get_line, get_file + public :: loadtxt, savetxt, open, get_line, get_file , input !! version: experimental !! @@ -82,6 +82,13 @@ module stdlib_io module procedure :: get_line_input_string end interface get_line + !> Version: experimental + !> + !> Read a line from standard input with an optional prompt + interface input + module procedure :: input_char + end interface input + interface loadtxt !! version: experimental !! @@ -597,6 +604,40 @@ contains call get_line(input_unit, line, iostat, iomsg) end subroutine get_line_input_char + !> Version: experimental + !> + !> Read a line from standard input with an optional prompt. + !! Similar to Python's input(). + !! + !! - Preserves trailing whitespace + !! - Returns allocatable character string + !! - Does not perform type conversion + !! - Does not stop on error unless caller chooses to + function input_char(prompt, iostat, iomsg) result(line) + use, intrinsic :: iso_fortran_env, only : output_unit + character(len=*), intent(in), optional :: prompt + integer, intent(out), optional :: iostat + character(len=:), allocatable, optional :: iomsg + character(len=:), allocatable :: line + + integer :: stat + + ! Print prompt without newline + if (present(prompt)) then + write(output_unit, '(a)', advance='no') prompt + end if + + ! Read line from stdin + call get_line_input_char(line, stat, iomsg) + + if (present(iostat)) then + iostat = stat + else if (stat /= 0) then + call error_stop("input: error reading from standard input") + end if + end function input_char + + !> Version: experimental !> !> Read a whole line from the standard input into a string variable diff --git a/test/io/test_input.f90 b/test/io/test_input.f90 new file mode 100644 index 000000000..a1c81d6c2 --- /dev/null +++ b/test/io/test_input.f90 @@ -0,0 +1,3 @@ +call write_test_input(" abc ") +s = input() +call assert_equal(s, " abc ") From f2284c83aba677d476d8054e6caa69b2f7c2fff2 Mon Sep 17 00:00:00 2001 From: Brijesh-Thakkar Date: Sun, 14 Dec 2025 06:42:31 +0530 Subject: [PATCH 2/5] stdlib_io: add Python-like input() function Fixes #259 --- doc/specs/stdlib_io.md | 5 +- example/io/example_input.f90 | 12 ++++- src/stdlib_io.fypp | 3 +- test/io/test_input.f90 | 88 ++++++++++++++++++++++++++++++++++-- 4 files changed, 100 insertions(+), 8 deletions(-) diff --git a/doc/specs/stdlib_io.md b/doc/specs/stdlib_io.md index ecdbdcd6f..37fda59c9 100644 --- a/doc/specs/stdlib_io.md +++ b/doc/specs/stdlib_io.md @@ -268,14 +268,17 @@ No numeric conversion is performed. ### Arguments `prompt` (optional): -A character expression containing a prompt to be displayed before reading input. +A character expression containing a prompt to be displayed before reading input. +This argument is `intent(in)`. `iostat` (optional): Default integer, contains status of reading from standard input. Zero indicates success. +This argument is `intent(out)`. `iomsg` (optional): Deferred-length character variable containing an error message if `iostat` is non-zero. +This argument is `intent(out)`. ### Return value diff --git a/example/io/example_input.f90 b/example/io/example_input.f90 index 4dfbaf411..080e05dfe 100644 --- a/example/io/example_input.f90 +++ b/example/io/example_input.f90 @@ -1,2 +1,10 @@ -name = input("Enter your name: ") -print *, "Hello:", name +program example_input + use stdlib_io, only : input + implicit none + + character(len=:), allocatable :: name + + name = input("Enter your name: ") + print *, "Hello:", name + +end program example_input diff --git a/src/stdlib_io.fypp b/src/stdlib_io.fypp index f812ecd17..6df05c117 100644 --- a/src/stdlib_io.fypp +++ b/src/stdlib_io.fypp @@ -16,7 +16,7 @@ module stdlib_io implicit none private ! Public API - public :: loadtxt, savetxt, open, get_line, get_file , input + public :: loadtxt, savetxt, open, get_line, get_file, input !! version: experimental !! @@ -637,7 +637,6 @@ contains end if end function input_char - !> Version: experimental !> !> Read a whole line from the standard input into a string variable diff --git a/test/io/test_input.f90 b/test/io/test_input.f90 index a1c81d6c2..6b59a265e 100644 --- a/test/io/test_input.f90 +++ b/test/io/test_input.f90 @@ -1,3 +1,85 @@ -call write_test_input(" abc ") -s = input() -call assert_equal(s, " abc ") +module test_input + use testdrive, only : new_unittest, unittest_type, error_type, assert_equal + use stdlib_io, only : input + use stdlib_test_utils, only : write_test_input + implicit none + private + public :: collect + +contains + + subroutine collect(tests) + type(unittest_type), allocatable, intent(out) :: tests(:) + + tests = [ & + new_unittest("input preserves whitespace", test_input_whitespace), & + new_unittest("input with prompt", test_input_prompt), & + new_unittest("input with iostat", test_input_iostat), & + new_unittest("input with iomsg", test_input_iomsg), & + new_unittest("input without optional args", test_input_no_args) & + ] + end subroutine collect + + + subroutine test_input_whitespace(error) + type(error_type), allocatable, intent(out) :: error + character(len=:), allocatable :: s + + call write_test_input(" abc ") + s = input() + call assert_equal(error, s, " abc ") + end subroutine test_input_whitespace + + + subroutine test_input_prompt(error) + type(error_type), allocatable, intent(out) :: error + character(len=:), allocatable :: s + + call write_test_input("abc") + s = input("Enter value: ") + call assert_equal(error, s, "abc") + end subroutine test_input_prompt + + + subroutine test_input_iostat(error) + type(error_type), allocatable, intent(out) :: error + character(len=:), allocatable :: s + integer :: ios + + call write_test_input("abc") + s = input(iostat=ios) + call assert_equal(error, ios, 0) + call assert_equal(error, s, "abc") + end subroutine test_input_iostat + + + subroutine test_input_iomsg(error) + type(error_type), allocatable, intent(out) :: error + character(len=:), allocatable :: s + character(len=:), allocatable :: msg + + call write_test_input("abc") + s = input(iomsg=msg) + call assert_equal(error, s, "abc") + end subroutine test_input_iomsg + + + subroutine test_input_no_args(error) + type(error_type), allocatable, intent(out) :: error + character(len=:), allocatable :: s + + call write_test_input("abc") + s = input() + call assert_equal(error, s, "abc") + end subroutine test_input_no_args + +end module test_input + + +program run_test_input + use testdrive, only : run_testsuite + use test_input, only : collect + implicit none + + call run_testsuite(collect) +end program run_test_input From 92516a47f03d7a9da7de9e94c6d82adbc97009cb Mon Sep 17 00:00:00 2001 From: Brijesh-Thakkar Date: Sun, 14 Dec 2025 23:14:08 +0530 Subject: [PATCH 3/5] test(io): move input test to top-level for fpm compatibility (fixes #259) --- test/{io => }/test_input.f90 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{io => }/test_input.f90 (100%) diff --git a/test/io/test_input.f90 b/test/test_input.f90 similarity index 100% rename from test/io/test_input.f90 rename to test/test_input.f90 From fb7572b35a9e661b62db29e13ae779fe49031450 Mon Sep 17 00:00:00 2001 From: Brijesh-Thakkar Date: Mon, 15 Dec 2025 02:39:53 +0530 Subject: [PATCH 4/5] io: implement input() with tests and documentation (fixes #259) --- doc/specs/stdlib_io.md | 12 ++++++------ example/io/example_input.f90 | 2 +- src/stdlib_io.fypp | 10 ++++------ test/test_input.f90 | 5 ++--- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/doc/specs/stdlib_io.md b/doc/specs/stdlib_io.md index 37fda59c9..751d23964 100644 --- a/doc/specs/stdlib_io.md +++ b/doc/specs/stdlib_io.md @@ -255,7 +255,6 @@ Experimental ### Description Reads a line from standard input, optionally displaying a prompt. -This is similar to Python’s `input()` function. The function returns the input as an allocatable character string. Trailing spaces and tabs are preserved. @@ -268,16 +267,17 @@ No numeric conversion is performed. ### Arguments `prompt` (optional): -A character expression containing a prompt to be displayed before reading input. +A `character` scalar containing a prompt to be displayed before reading input. This argument is `intent(in)`. `iostat` (optional): -Default integer, contains status of reading from standard input. -Zero indicates success. +Default `integer` scalar that contains the status of reading from standard input. +The value is zero if the operation succeeds; otherwise the value is non-zero. +If this argument is not provided and an error occurs, an `error stop` is triggered. This argument is `intent(out)`. `iomsg` (optional): -Deferred-length character variable containing an error message if `iostat` is non-zero. +Deferred-length `character` variable containing an error message if `iostat` is non-zero. This argument is `intent(out)`. ### Return value @@ -288,7 +288,7 @@ Returns a deferred-length allocatable `character` variable containing the input - Trailing spaces and tabs are preserved - No type conversion is performed -- To convert to numbers, use `read(line, *)` +- To convert to numbers, use `to_num` from `stdlib_string_to_num` ### Example diff --git a/example/io/example_input.f90 b/example/io/example_input.f90 index 080e05dfe..cdb01e447 100644 --- a/example/io/example_input.f90 +++ b/example/io/example_input.f90 @@ -1,6 +1,6 @@ program example_input use stdlib_io, only : input - implicit none + implicit none(type, external) character(len=:), allocatable :: name diff --git a/src/stdlib_io.fypp b/src/stdlib_io.fypp index 6df05c117..913249717 100644 --- a/src/stdlib_io.fypp +++ b/src/stdlib_io.fypp @@ -6,7 +6,7 @@ module stdlib_io !! Provides a support for file handling !! ([Specification](../page/specs/stdlib_io.html)) - use, intrinsic :: iso_fortran_env, only : input_unit + use, intrinsic :: iso_fortran_env, only : input_unit, output_unit use stdlib_kinds, only: sp, dp, xdp, qp, & int8, int16, int32, int64 use stdlib_error, only: error_stop, state_type, STDLIB_IO_ERROR @@ -607,14 +607,12 @@ contains !> Version: experimental !> !> Read a line from standard input with an optional prompt. - !! Similar to Python's input(). !! !! - Preserves trailing whitespace !! - Returns allocatable character string - !! - Does not perform type conversion - !! - Does not stop on error unless caller chooses to + !! - Does not perform any type conversion; the input is returned as character data + !! - If `iostat` is present, errors are reported via `iostat`/`iomsg` instead of triggering `error_stop` function input_char(prompt, iostat, iomsg) result(line) - use, intrinsic :: iso_fortran_env, only : output_unit character(len=*), intent(in), optional :: prompt integer, intent(out), optional :: iostat character(len=:), allocatable, optional :: iomsg @@ -627,7 +625,7 @@ contains write(output_unit, '(a)', advance='no') prompt end if - ! Read line from stdin + ! Read line from standard input call get_line_input_char(line, stat, iomsg) if (present(iostat)) then diff --git a/test/test_input.f90 b/test/test_input.f90 index 6b59a265e..2b8cda9e3 100644 --- a/test/test_input.f90 +++ b/test/test_input.f90 @@ -1,7 +1,6 @@ module test_input - use testdrive, only : new_unittest, unittest_type, error_type, assert_equal + use testdrive, only : new_unittest, unittest_type, error_type, check use stdlib_io, only : input - use stdlib_test_utils, only : write_test_input implicit none private public :: collect @@ -25,7 +24,7 @@ subroutine test_input_whitespace(error) type(error_type), allocatable, intent(out) :: error character(len=:), allocatable :: s - call write_test_input(" abc ") + call feed_stdin(" abc ") s = input() call assert_equal(error, s, " abc ") end subroutine test_input_whitespace From 53a495dad74a8256f65120c7ae1785ab7499b1fa Mon Sep 17 00:00:00 2001 From: Brijesh-Thakkar Date: Mon, 15 Dec 2025 13:30:54 +0530 Subject: [PATCH 5/5] test(io): add input() tests --- test/io/CMakeLists.txt | 1 + test/{ => io}/test_input.f90 | 0 2 files changed, 1 insertion(+) rename test/{ => io}/test_input.f90 (100%) diff --git a/test/io/CMakeLists.txt b/test/io/CMakeLists.txt index 4e19b5fbe..d6d43d6fb 100644 --- a/test/io/CMakeLists.txt +++ b/test/io/CMakeLists.txt @@ -17,3 +17,4 @@ ADDTEST(get_line) ADDTEST(npy) ADDTEST(open) ADDTEST(parse_mode) +ADDTEST(input) \ No newline at end of file diff --git a/test/test_input.f90 b/test/io/test_input.f90 similarity index 100% rename from test/test_input.f90 rename to test/io/test_input.f90