The POSIX function fmemopen [1] is the way to create an input stream that
reads from a string in memory. Unfortunately, it is not portable [2],
and there is no way to make it portable (especially to native Windows) [3][4].

But some applications (e.g. GNU gettext) can make good use of such a
construct. So, here is a module 'sf-istream' that implements an input stream
abstraction that is either based on a 'FILE *' or on a string in memory.

Another module 'sfl-istream' is essentially a sample: It adds line number
tracking to 'sf-istream'.

One can think of many "input stream" implementations, each with a different
focus. Unfortunately, formulating such an abstraction requires an object-
oriented programming language; it's hard to do in C. This is why here I
concentrate on the practically most useful case.

[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/fmemopen.html
[2] https://www.gnu.org/software/gnulib/manual/html_node/fmemopen.html
[3] https://lists.gnu.org/archive/html/bug-gnulib/2021-02/msg00077.html
[4] https://lists.gnu.org/archive/html/bug-gnulib/2021-02/msg00079.html


2024-09-24  Bruno Haible  <br...@clisp.org>

        sfl-istream: Add tests.
        * tests/test-sfl-istream.c: New file, based on tests/test-sf-istream.c.
        * modules/sfl-istream-tests: New file.

        sfl-istream: New module.
        * lib/sfl-istream.h: New file.
        * lib/sfl-istream.c: New file.
        * modules/sfl-istream: New file.

2024-09-24  Bruno Haible  <br...@clisp.org>

        sf-istream: Add tests.
        * tests/test-sf-istream.c: New file.
        * modules/sf-istream-tests: New file.

        sf-istream: New module.
        * lib/sf-istream.h: New file.
        * lib/sf-istream.c: New file.
        * modules/sf-istream: New file.
        * doc/posix-functions/fmemopen.texi: Mention the new module.

>From 336a4d57d75c6349e9040383eb098d1112d31edd Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 24 Sep 2024 12:30:54 +0200
Subject: [PATCH 1/4] sf-istream: New module.

* lib/sf-istream.h: New file.
* lib/sf-istream.c: New file.
* modules/sf-istream: New file.
* doc/posix-functions/fmemopen.texi: Mention the new module.
---
 ChangeLog                         |  8 +++
 doc/posix-functions/fmemopen.texi |  3 +
 lib/sf-istream.c                  | 96 +++++++++++++++++++++++++++++++
 lib/sf-istream.h                  | 77 +++++++++++++++++++++++++
 modules/sf-istream                | 23 ++++++++
 5 files changed, 207 insertions(+)
 create mode 100644 lib/sf-istream.c
 create mode 100644 lib/sf-istream.h
 create mode 100644 modules/sf-istream

diff --git a/ChangeLog b/ChangeLog
index c870d9dd1c..2b0ffa4da0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2024-09-24  Bruno Haible  <br...@clisp.org>
+
+	sf-istream: New module.
+	* lib/sf-istream.h: New file.
+	* lib/sf-istream.c: New file.
+	* modules/sf-istream: New file.
+	* doc/posix-functions/fmemopen.texi: Mention the new module.
+
 2024-09-23  Bruno Haible  <br...@clisp.org>
 
 	getopt-posix: Fix compilation error in C++ mode (regression 2024-09-21).
diff --git a/doc/posix-functions/fmemopen.texi b/doc/posix-functions/fmemopen.texi
index da92ba2d72..7f66b100ba 100644
--- a/doc/posix-functions/fmemopen.texi
+++ b/doc/posix-functions/fmemopen.texi
@@ -16,3 +16,6 @@
 This function is missing on many non-glibc platforms:
 Mac OS X 10.5, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, Solaris 11.3, Cygwin 1.5.x, mingw, MSVC 14, Android 5.1.
 @end itemize
+
+An alternative to the @code{fmemopen} function is the Gnulib module
+@code{sf-istream}.
diff --git a/lib/sf-istream.c b/lib/sf-istream.c
new file mode 100644
index 0000000000..1bc4fa15e5
--- /dev/null
+++ b/lib/sf-istream.c
@@ -0,0 +1,96 @@
+/* A string or file based input stream.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sf-istream.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+void
+sf_istream_init_from_file (sf_istream_t *stream, FILE *fp)
+{
+  stream->fp = fp;
+  stream->input = NULL;
+  stream->input_end = NULL;
+}
+
+void
+sf_istream_init_from_string (sf_istream_t *stream,
+                             const char *input)
+{
+  stream->fp = NULL;
+  stream->input = input;
+  stream->input_end = input + strlen (input);
+}
+
+void
+sf_istream_init_from_string_desc (sf_istream_t *stream,
+                                  string_desc_t input)
+{
+  stream->fp = NULL;
+  stream->input = string_desc_data (input);
+  stream->input_end = stream->input + string_desc_length (input);
+}
+
+int
+sf_getc (sf_istream_t *stream)
+{
+  int c;
+
+  if (stream->fp != NULL)
+    c = getc (stream->fp);
+  else
+    {
+      if (stream->input == stream->input_end)
+        return EOF;
+      c = (unsigned char) *(stream->input++);
+    }
+
+  return c;
+}
+
+int
+sf_ferror (sf_istream_t *stream)
+{
+  return (stream->fp != NULL && ferror (stream->fp));
+}
+
+void
+sf_ungetc (sf_istream_t *stream, int c)
+{
+  if (c != EOF)
+    {
+      if (stream->fp != NULL)
+        ungetc (c, stream->fp);
+      else
+        {
+          stream->input--;
+          if (!(c == (unsigned char) *stream->input))
+            /* C was incorrect.  */
+            abort ();
+        }
+    }
+}
+
+void
+sf_free (sf_istream_t *stream)
+{
+}
diff --git a/lib/sf-istream.h b/lib/sf-istream.h
new file mode 100644
index 0000000000..e45c561708
--- /dev/null
+++ b/lib/sf-istream.h
@@ -0,0 +1,77 @@
+/* A string or file based input stream.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#ifndef _SF_ISTREAM_H
+#define _SF_ISTREAM_H
+
+#include <stdio.h>
+
+#include "string-desc.h"
+
+/* An input stream type that can read from a file or from a string.  */
+typedef struct sf_istream sf_istream_t;
+struct sf_istream
+{
+  /* The input file stream, when reading from a file.  */
+  FILE *fp;
+  /* The input area, when reading from a string.  */
+  const char *input;
+  const char *input_end;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Initializes STREAM to read from FP.
+   FP must be a FILE stream open for reading.  */
+extern void sf_istream_init_from_file (sf_istream_t *stream, FILE *fp);
+/* Initializes STREAM to read from a NUL-terminated string INPUT.
+   The contents of INPUT must stay available and unchanged as long as STREAM
+   is in use.  */
+extern void sf_istream_init_from_string (sf_istream_t *stream,
+                                         const char *input);
+/* Initializes STREAM to read from a string INPUT.
+   The contents of INPUT must stay available and unchanged as long as STREAM
+   is in use.  Operations on STREAM will not modify the contents of INPUT.  */
+extern void sf_istream_init_from_string_desc (sf_istream_t *stream,
+                                              string_desc_t input);
+
+/* Reads a single 'char' from STREAM, and returns it as an 'unsigned char'.
+   Returns EOF when the end of stream was already reached.  */
+extern int sf_getc (sf_istream_t *stream);
+
+/* Tests whether STREAM has encountered an error.
+   You may want to call this function after sf_getc (stream) has
+   returned EOF; in other situations it is guaranteed to return 0.  */
+extern int sf_ferror (sf_istream_t *stream);
+
+/* Assuming that C was the last value returned by sf_getc (stream),
+   this call pushes back C onto the stream.
+   Only 1 character of pushback is guaranteed.  */
+extern void sf_ungetc (sf_istream_t *stream, int c);
+
+/* Frees all memory held by STREAM.
+   This call has no effect on the arguments provided to sf_istream_init_*.  */
+extern void sf_free (sf_istream_t *stream);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SF_ISTREAM_H */
diff --git a/modules/sf-istream b/modules/sf-istream
new file mode 100644
index 0000000000..ab1289d7c4
--- /dev/null
+++ b/modules/sf-istream
@@ -0,0 +1,23 @@
+Description:
+A string or file based input stream.
+
+Files:
+lib/sf-istream.h
+lib/sf-istream.c
+
+Depends-on:
+string-desc
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += sf-istream.c
+
+Include:
+"sf-istream.h"
+
+License:
+LGPL
+
+Maintainer:
+all
-- 
2.34.1

>From 357c5d521d7af3c56fb23cb2a98db562017468ed Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 24 Sep 2024 12:30:58 +0200
Subject: [PATCH 2/4] sf-istream: Add tests.

* tests/test-sf-istream.c: New file.
* modules/sf-istream-tests: New file.
---
 ChangeLog                |   4 ++
 modules/sf-istream-tests |  12 +++++
 tests/test-sf-istream.c  | 109 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+)
 create mode 100644 modules/sf-istream-tests
 create mode 100644 tests/test-sf-istream.c

diff --git a/ChangeLog b/ChangeLog
index 2b0ffa4da0..6012c6e7e0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2024-09-24  Bruno Haible  <br...@clisp.org>
 
+	sf-istream: Add tests.
+	* tests/test-sf-istream.c: New file.
+	* modules/sf-istream-tests: New file.
+
 	sf-istream: New module.
 	* lib/sf-istream.h: New file.
 	* lib/sf-istream.c: New file.
diff --git a/modules/sf-istream-tests b/modules/sf-istream-tests
new file mode 100644
index 0000000000..dc0f80d541
--- /dev/null
+++ b/modules/sf-istream-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-sf-istream.c
+tests/macros.h
+
+Depends-on:
+unlink
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-sf-istream
+check_PROGRAMS += test-sf-istream
diff --git a/tests/test-sf-istream.c b/tests/test-sf-istream.c
new file mode 100644
index 0000000000..61d096f55f
--- /dev/null
+++ b/tests/test-sf-istream.c
@@ -0,0 +1,109 @@
+/* Test of string or file based input stream.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sf-istream.h"
+
+#include <unistd.h>
+
+#include "macros.h"
+
+#define CONTENTS_LEN 7
+#define CONTENTS "Hello\377\n"
+
+static void
+test_open_stream (sf_istream_t *stream)
+{
+  int c;
+
+  c = sf_getc (stream);
+  ASSERT (c == 'H');
+  c = sf_getc (stream);
+  ASSERT (c == 'e');
+  c = sf_getc (stream);
+  ASSERT (c == 'l');
+  c = sf_getc (stream);
+  ASSERT (c == 'l');
+  c = sf_getc (stream);
+  ASSERT (c == 'o');
+  sf_ungetc (stream, c);
+  c = sf_getc (stream);
+  ASSERT (c == 'o');
+  c = sf_getc (stream);
+  ASSERT (c == 0xff);
+  sf_ungetc (stream, c);
+  c = sf_getc (stream);
+  ASSERT (c == 0xff);
+  c = sf_getc (stream);
+  ASSERT (c == '\n');
+  c = sf_getc (stream);
+  ASSERT (c == EOF);
+  c = sf_getc (stream);
+  ASSERT (c == EOF);
+  sf_ungetc (stream, c);
+  c = sf_getc (stream);
+  ASSERT (c == EOF);
+  ASSERT (!sf_ferror (stream));
+}
+
+int
+main ()
+{
+  char const contents[CONTENTS_LEN] = CONTENTS;
+
+  /* Test reading from a file.  */
+  {
+    const char *filename = "test-sf-istream.tmp";
+    unlink (filename);
+    {
+      FILE *fp = fopen (filename, "wb");
+      ASSERT (fwrite (contents, 1, CONTENTS_LEN, fp) == CONTENTS_LEN);
+      ASSERT (fclose (fp) == 0);
+    }
+    {
+      FILE *fp = fopen (filename, "rb");
+      sf_istream_t stream;
+      sf_istream_init_from_file (&stream, fp);
+      test_open_stream (&stream);
+      sf_free (&stream);
+    }
+    unlink (filename);
+  }
+
+  /* Test reading from a string in memory.  */
+  {
+    sf_istream_t stream;
+    sf_istream_init_from_string_desc (&stream,
+                                      string_desc_new_addr (CONTENTS_LEN,
+                                                            (char *) contents));
+    test_open_stream (&stream);
+    sf_free (&stream);
+  }
+
+  /* Test reading from a NUL-terminated string in memory.  */
+  {
+    sf_istream_t stream;
+    sf_istream_init_from_string (&stream, CONTENTS);
+    test_open_stream (&stream);
+    sf_free (&stream);
+  }
+
+  return 0;
+}
-- 
2.34.1

>From a61bea3aa85dbfd44dd6d5ef1d006f9ccc431af6 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 24 Sep 2024 12:31:03 +0200
Subject: [PATCH 3/4] sfl-istream: New module.

* lib/sfl-istream.h: New file.
* lib/sfl-istream.c: New file.
* modules/sfl-istream: New file.
---
 ChangeLog           |  7 ++++
 lib/sfl-istream.c   | 84 +++++++++++++++++++++++++++++++++++++++++++++
 lib/sfl-istream.h   | 79 ++++++++++++++++++++++++++++++++++++++++++
 modules/sfl-istream | 23 +++++++++++++
 4 files changed, 193 insertions(+)
 create mode 100644 lib/sfl-istream.c
 create mode 100644 lib/sfl-istream.h
 create mode 100644 modules/sfl-istream

diff --git a/ChangeLog b/ChangeLog
index 6012c6e7e0..52a498383f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2024-09-24  Bruno Haible  <br...@clisp.org>
+
+	sfl-istream: New module.
+	* lib/sfl-istream.h: New file.
+	* lib/sfl-istream.c: New file.
+	* modules/sfl-istream: New file.
+
 2024-09-24  Bruno Haible  <br...@clisp.org>
 
 	sf-istream: Add tests.
diff --git a/lib/sfl-istream.c b/lib/sfl-istream.c
new file mode 100644
index 0000000000..ed6dd16646
--- /dev/null
+++ b/lib/sfl-istream.c
@@ -0,0 +1,84 @@
+/* A string or file based input stream, that keeps track of a line number.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sfl-istream.h"
+
+void
+sfl_istream_init_from_file (sfl_istream_t *stream, FILE *fp)
+{
+  sf_istream_init_from_file (&stream->istream, fp);
+  stream->line_number = 1;
+}
+
+void
+sfl_istream_init_from_string (sfl_istream_t *stream, const char *input)
+{
+  sf_istream_init_from_string (&stream->istream, input);
+  stream->line_number = 1;
+}
+
+void
+sfl_istream_init_from_string_desc (sfl_istream_t *stream, string_desc_t input)
+{
+  sf_istream_init_from_string_desc (&stream->istream, input);
+  stream->line_number = 1;
+}
+
+void
+sfl_set_line_number (sfl_istream_t *stream, size_t line_number)
+{
+  stream->line_number = line_number;
+}
+
+size_t
+sfl_get_line_number (sfl_istream_t *stream)
+{
+  return stream->line_number;
+}
+
+int
+sfl_getc (sfl_istream_t *stream)
+{
+  int c = sf_getc (&stream->istream);
+  if (c == '\n')
+    stream->line_number++;
+  return c;
+}
+
+int
+sfl_ferror (sfl_istream_t *stream)
+{
+  return sf_ferror (&stream->istream);
+}
+
+void
+sfl_ungetc (sfl_istream_t *stream, int c)
+{
+  if (c == '\n')
+    stream->line_number--;
+  sf_ungetc (&stream->istream, c);
+}
+
+void
+sfl_free (sfl_istream_t *stream)
+{
+  sf_free (&stream->istream);
+}
diff --git a/lib/sfl-istream.h b/lib/sfl-istream.h
new file mode 100644
index 0000000000..7b487abbba
--- /dev/null
+++ b/lib/sfl-istream.h
@@ -0,0 +1,79 @@
+/* A string or file based input stream, that keeps track of a line number.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#ifndef _SFL_ISTREAM_H
+#define _SFL_ISTREAM_H
+
+#include "sf-istream.h"
+
+/* An input stream type that can read from a file or from a string
+   and that keeps track of a line number.  */
+typedef struct sfl_istream sfl_istream_t;
+struct sfl_istream
+{
+  sf_istream_t istream;
+  size_t line_number;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Initializes STREAM to read from FP.
+   FP must be a FILE stream open for reading.  */
+extern void sfl_istream_init_from_file (sfl_istream_t *stream, FILE *fp);
+/* Initializes STREAM to read from a NUL-terminated string INPUT.
+   The contents of INPUT must stay available and unchanged as long as STREAM
+   is in use.  */
+extern void sfl_istream_init_from_string (sfl_istream_t *stream,
+                                          const char *input);
+/* Initializes STREAM to read from a string INPUT.
+   The contents of INPUT must stay available and unchanged as long as STREAM
+   is in use.  Operations on STREAM will not modify the contents of INPUT.  */
+extern void sfl_istream_init_from_string_desc (sfl_istream_t *stream,
+                                               string_desc_t input);
+
+/* Sets the current line number of STREAM.  */
+extern void sfl_set_line_number (sfl_istream_t *stream, size_t line_number);
+
+/* Returns the current line number of STREAM.  */
+extern size_t sfl_get_line_number (sfl_istream_t *stream);
+
+/* Reads a single 'char' from STREAM, and returns it as an 'unsigned char'.
+   Returns EOF when the end of stream was already reached.  */
+extern int sfl_getc (sfl_istream_t *stream);
+
+/* Tests whether STREAM has encountered an error.
+   You may want to call this function after sf_getc (stream) has
+   returned EOF; in other situations it is guaranteed to return 0.  */
+extern int sfl_ferror (sfl_istream_t *stream);
+
+/* Assuming that C was the last value returned by sf_getc (stream),
+   this call pushes back C onto the stream.
+   Only 1 character of pushback is guaranteed.  */
+extern void sfl_ungetc (sfl_istream_t *stream, int c);
+
+/* Frees all memory held by STREAM.
+   This call has no effect on the arguments provided to sfl_istream_init_*.  */
+extern void sfl_free (sfl_istream_t *stream);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SFL_ISTREAM_H */
diff --git a/modules/sfl-istream b/modules/sfl-istream
new file mode 100644
index 0000000000..800f81e5ed
--- /dev/null
+++ b/modules/sfl-istream
@@ -0,0 +1,23 @@
+Description:
+A string or file based input stream, that keeps track of a line number.
+
+Files:
+lib/sfl-istream.h
+lib/sfl-istream.c
+
+Depends-on:
+sf-istream
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += sfl-istream.c
+
+Include:
+"sfl-istream.h"
+
+License:
+LGPL
+
+Maintainer:
+all
-- 
2.34.1

>From 5a7754d11ecaf369987b7074333f2b392238d04a Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 24 Sep 2024 12:31:07 +0200
Subject: [PATCH 4/4] sfl-istream: Add tests.

* tests/test-sfl-istream.c: New file, based on tests/test-sf-istream.c.
* modules/sfl-istream-tests: New file.
---
 ChangeLog                 |   4 ++
 modules/sfl-istream-tests |  12 ++++
 tests/test-sfl-istream.c  | 133 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 149 insertions(+)
 create mode 100644 modules/sfl-istream-tests
 create mode 100644 tests/test-sfl-istream.c

diff --git a/ChangeLog b/ChangeLog
index 52a498383f..934ed7233d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2024-09-24  Bruno Haible  <br...@clisp.org>
 
+	sfl-istream: Add tests.
+	* tests/test-sfl-istream.c: New file, based on tests/test-sf-istream.c.
+	* modules/sfl-istream-tests: New file.
+
 	sfl-istream: New module.
 	* lib/sfl-istream.h: New file.
 	* lib/sfl-istream.c: New file.
diff --git a/modules/sfl-istream-tests b/modules/sfl-istream-tests
new file mode 100644
index 0000000000..eba2aa0abb
--- /dev/null
+++ b/modules/sfl-istream-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-sfl-istream.c
+tests/macros.h
+
+Depends-on:
+unlink
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-sfl-istream
+check_PROGRAMS += test-sfl-istream
diff --git a/tests/test-sfl-istream.c b/tests/test-sfl-istream.c
new file mode 100644
index 0000000000..bf8eeeccb6
--- /dev/null
+++ b/tests/test-sfl-istream.c
@@ -0,0 +1,133 @@
+/* Test of string or file based input stream with line number.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2024.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "sfl-istream.h"
+
+#include <unistd.h>
+
+#include "macros.h"
+
+#define CONTENTS_LEN 9
+#define CONTENTS "Hello\377\n\nW"
+
+static void
+test_open_stream (sfl_istream_t *stream)
+{
+  int c;
+
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 'H');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 'e');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 'l');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 'l');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 'o');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  sfl_ungetc (stream, c);
+  c = sfl_getc (stream);
+  ASSERT (c == 'o');
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == 0xff);
+  ASSERT (sfl_get_line_number (stream) == 1);
+  sfl_ungetc (stream, c);
+  c = sfl_getc (stream);
+  ASSERT (c == 0xff);
+  ASSERT (sfl_get_line_number (stream) == 1);
+  c = sfl_getc (stream);
+  ASSERT (c == '\n');
+  ASSERT (sfl_get_line_number (stream) == 2);
+  c = sfl_getc (stream);
+  ASSERT (c == '\n');
+  ASSERT (sfl_get_line_number (stream) == 3);
+  sfl_ungetc (stream, c);
+  ASSERT (sfl_get_line_number (stream) == 2);
+  c = sfl_getc (stream);
+  ASSERT (c == '\n');
+  ASSERT (sfl_get_line_number (stream) == 3);
+  c = sfl_getc (stream);
+  ASSERT (c == 'W');
+  ASSERT (sfl_get_line_number (stream) == 3);
+  c = sfl_getc (stream);
+  ASSERT (c == EOF);
+  ASSERT (sfl_get_line_number (stream) == 3);
+  c = sfl_getc (stream);
+  ASSERT (c == EOF);
+  ASSERT (sfl_get_line_number (stream) == 3);
+  sfl_ungetc (stream, c);
+  c = sfl_getc (stream);
+  ASSERT (c == EOF);
+  ASSERT (sfl_get_line_number (stream) == 3);
+  ASSERT (!sfl_ferror (stream));
+}
+
+int
+main ()
+{
+  char const contents[CONTENTS_LEN] = CONTENTS;
+
+  /* Test reading from a file.  */
+  {
+    const char *filename = "test-sfl-istream.tmp";
+    unlink (filename);
+    {
+      FILE *fp = fopen (filename, "wb");
+      ASSERT (fwrite (contents, 1, CONTENTS_LEN, fp) == CONTENTS_LEN);
+      ASSERT (fclose (fp) == 0);
+    }
+    {
+      FILE *fp = fopen (filename, "rb");
+      sfl_istream_t stream;
+      sfl_istream_init_from_file (&stream, fp);
+      test_open_stream (&stream);
+      sfl_free (&stream);
+    }
+    unlink (filename);
+  }
+
+  /* Test reading from a string in memory.  */
+  {
+    sfl_istream_t stream;
+    sfl_istream_init_from_string_desc (&stream,
+                                       string_desc_new_addr (CONTENTS_LEN,
+                                                             (char *) contents));
+    test_open_stream (&stream);
+    sfl_free (&stream);
+  }
+
+  /* Test reading from a NUL-terminated string in memory.  */
+  {
+    sfl_istream_t stream;
+    sfl_istream_init_from_string (&stream, CONTENTS);
+    test_open_stream (&stream);
+    sfl_free (&stream);
+  }
+
+  return 0;
+}
-- 
2.34.1

Reply via email to