https://gcc.gnu.org/g:11d9f143494723200268654a252a2da108820bfe

commit r17-3957-g11d9f143494723200268654a252a2da108820bfe
Author: Robert Dubner <[email protected]>
Date:   Sun Sep 6 13:55:40 2026 -0400

    cobol: Move STRING and UNSTRING routines to unstring.cc file.
    
    This cuts down the unwieldy size of the libgcobol.cc file.
    
    libgcobol/ChangeLog:
    
            * Makefile.am: Incorporate new unstring.cc file.
            * Makefile.in: Regenerate.
            * libgcobol.cc (string_operand): Moved.
            (__gg__string_1): Likewise.
            (find_string_24): Likewise.
            (__gg__string_24): Likewise.
            (__gg__string): Likewise.
            (struct unstring_finder_1): Likewise.
            (struct unstring_finder_24): Likewise.
            (unstring_fixed): Likewise.
            (__gg__unstring_1): Likewise.
            (__gg__unstring_24): Likewise.
            (__gg__unstring): Likewise.
            (__gg__move_stash_release): Formatting.
            * libgcobol.h (__gg__move_literala): New declaration.
            * unstring.cc: New file.
    
    gcc/testsuite/ChangeLog:
    
            * cobol.dg/group2/EXIT_PERFORM_CYCLE.cob: Removed.
            * cobol.dg/group2/EXIT_PERFORM_CYCLE.out: Removed.

Diff:
---
 .../cobol.dg/group2/EXIT_PERFORM_CYCLE.cob         |   16 -
 .../cobol.dg/group2/EXIT_PERFORM_CYCLE.out         |    1 -
 libgcobol/Makefile.am                              |   37 +-
 libgcobol/Makefile.in                              |   38 +-
 libgcobol/libgcobol.cc                             |  928 +-----------------
 libgcobol/libgcobol.h                              |    7 +
 libgcobol/unstring.cc                              | 1022 ++++++++++++++++++++
 7 files changed, 1069 insertions(+), 980 deletions(-)

diff --git a/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.cob 
b/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.cob
deleted file mode 100644
index 5e6a2152dd15..000000000000
--- a/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.cob
+++ /dev/null
@@ -1,16 +0,0 @@
-      *> Do not edit this generated file.  See README.txt
-      *> { dg-do run }
-       *> { dg-output-file "group2/EXIT_PERFORM_CYCLE.out" }
-
-       IDENTIFICATION   DIVISION.
-       PROGRAM-ID.      prog.
-       PROCEDURE        DIVISION.
-           PERFORM 2 TIMES
-             DISPLAY "OK" NO ADVANCING
-             END-DISPLAY
-             EXIT PERFORM CYCLE
-             DISPLAY "NOT OK"
-             END-DISPLAY
-           END-PERFORM
-           STOP RUN.
-
diff --git a/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.out 
b/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.out
deleted file mode 100644
index d65874ef6651..000000000000
--- a/gcc/testsuite/cobol.dg/group2/EXIT_PERFORM_CYCLE.out
+++ /dev/null
@@ -1 +0,0 @@
-OKOK
diff --git a/libgcobol/Makefile.am b/libgcobol/Makefile.am
index 4efbcee119f5..18f04bad0d38 100644
--- a/libgcobol/Makefile.am
+++ b/libgcobol/Makefile.am
@@ -51,24 +51,25 @@ libsubincludedir = $(libsubdir)
 ## 2.2.12 Automatic Dependency Tracking
 ## Automake generates code for automatic dependency tracking by default
 
-libgcobol_la_SOURCES =                         \
-       charmaps.cc                             \
-       constants.cc                            \
-       gfileio.cc                              \
-       gmath.cc                                \
-       inspect.cc                              \
-       intrinsic.cc                            \
-       io.cc                                   \
-       libgcobol.cc                            \
-       posix/shim/errno.cc                     \
-       posix/shim/fstat.cc                     \
-       posix/shim/localtime.cc                 \
-       posix/shim/lseek.cc                     \
-       posix/shim/open.cc                      \
-       posix/shim/stat.cc                      \
-       stringbin.cc                            \
-       valconv.cc                              \
-       xmlparse.cc
+libgcobol_la_SOURCES =      \
+    charmaps.cc             \
+    constants.cc            \
+    gfileio.cc              \
+    gmath.cc                \
+    inspect.cc              \
+    intrinsic.cc            \
+    io.cc                   \
+    libgcobol.cc            \
+    posix/shim/errno.cc     \
+    posix/shim/fstat.cc     \
+    posix/shim/localtime.cc \
+    posix/shim/lseek.cc     \
+    posix/shim/open.cc      \
+    posix/shim/stat.cc      \
+    stringbin.cc            \
+    unstring.cc             \
+    valconv.cc              \
+    xmlparse.cc
 
 libgcobol_compat_gnu_la_SOURCES =              \
        compat/gnu/udf/cobrt-file-status.cbl    \
diff --git a/libgcobol/Makefile.in b/libgcobol/Makefile.in
index e1286e3ded25..67b0282834b2 100644
--- a/libgcobol/Makefile.in
+++ b/libgcobol/Makefile.in
@@ -188,7 +188,7 @@ am__dirstamp = $(am__leading_dot)dirstamp
 @BUILD_LIBGCOBOL_TRUE@ posix/shim/localtime.lo \
 @BUILD_LIBGCOBOL_TRUE@ posix/shim/lseek.lo posix/shim/open.lo \
 @BUILD_LIBGCOBOL_TRUE@ posix/shim/stat.lo stringbin.lo \
-@BUILD_LIBGCOBOL_TRUE@ valconv.lo xmlparse.lo
+@BUILD_LIBGCOBOL_TRUE@ unstring.lo valconv.lo xmlparse.lo
 libgcobol_la_OBJECTS = $(am_libgcobol_la_OBJECTS)
 @BUILD_LIBGCOBOL_TRUE@am_libgcobol_la_rpath = -rpath $(toolexeclibdir)
 libgcobol_compat_gnu_la_LIBADD =
@@ -476,23 +476,24 @@ gcc_version := $(shell @get_gcc_base_ver@ 
$(top_srcdir)/../gcc/BASE-VER)
 @BUILD_LIBGCOBOL_TRUE@libsubdir := 
$(libdir)/gcc/$(target_noncanonical)/$(version)$(MULTISUBDIR)/cobol
 @BUILD_LIBGCOBOL_TRUE@libsubincludedir = $(libsubdir)
 @BUILD_LIBGCOBOL_TRUE@libgcobol_la_SOURCES = \
-@BUILD_LIBGCOBOL_TRUE@ charmaps.cc                             \
-@BUILD_LIBGCOBOL_TRUE@ constants.cc                            \
-@BUILD_LIBGCOBOL_TRUE@ gfileio.cc                              \
-@BUILD_LIBGCOBOL_TRUE@ gmath.cc                                \
-@BUILD_LIBGCOBOL_TRUE@ inspect.cc                              \
-@BUILD_LIBGCOBOL_TRUE@ intrinsic.cc                            \
-@BUILD_LIBGCOBOL_TRUE@ io.cc                                   \
-@BUILD_LIBGCOBOL_TRUE@ libgcobol.cc                            \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/errno.cc                     \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/fstat.cc                     \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/localtime.cc                 \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/lseek.cc                     \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/open.cc                      \
-@BUILD_LIBGCOBOL_TRUE@ posix/shim/stat.cc                      \
-@BUILD_LIBGCOBOL_TRUE@ stringbin.cc                            \
-@BUILD_LIBGCOBOL_TRUE@ valconv.cc                              \
-@BUILD_LIBGCOBOL_TRUE@ xmlparse.cc
+@BUILD_LIBGCOBOL_TRUE@    charmaps.cc             \
+@BUILD_LIBGCOBOL_TRUE@    constants.cc            \
+@BUILD_LIBGCOBOL_TRUE@    gfileio.cc              \
+@BUILD_LIBGCOBOL_TRUE@    gmath.cc                \
+@BUILD_LIBGCOBOL_TRUE@    inspect.cc              \
+@BUILD_LIBGCOBOL_TRUE@    intrinsic.cc            \
+@BUILD_LIBGCOBOL_TRUE@    io.cc                   \
+@BUILD_LIBGCOBOL_TRUE@    libgcobol.cc            \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/errno.cc     \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/fstat.cc     \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/localtime.cc \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/lseek.cc     \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/open.cc      \
+@BUILD_LIBGCOBOL_TRUE@    posix/shim/stat.cc      \
+@BUILD_LIBGCOBOL_TRUE@    stringbin.cc            \
+@BUILD_LIBGCOBOL_TRUE@    unstring.cc             \
+@BUILD_LIBGCOBOL_TRUE@    valconv.cc              \
+@BUILD_LIBGCOBOL_TRUE@    xmlparse.cc
 
 @BUILD_LIBGCOBOL_TRUE@libgcobol_compat_gnu_la_SOURCES = \
 @BUILD_LIBGCOBOL_TRUE@ compat/gnu/udf/cobrt-file-status.cbl    \
@@ -830,6 +831,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/io.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgcobol.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stringbin.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unstring.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/valconv.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xmlparse.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@posix/shim/$(DEPDIR)/errno.Plo@am__quote@
diff --git a/libgcobol/libgcobol.cc b/libgcobol/libgcobol.cc
index ffe951ed81f3..445ddb5ffffc 100644
--- a/libgcobol/libgcobol.cc
+++ b/libgcobol/libgcobol.cc
@@ -7794,450 +7794,6 @@ normalize_for_inspect_format_4(const cblc_field_t  *var,
   return retval;
   }
 
-// The compiler ensures that every operand uses the target encoding, so an
-// ordinary operand can be used directly.
-static inline const char *
-string_operand(const cblc_referlet_t &refer,
-               charmap_t             *charmap,
-               size_t                 stride,
-               char                   figurative[4],
-               size_t                *size)
-  {
-  const cblc_field_t *field = refer.field;
-  if( !field )
-    {
-    *size = 0;
-    return figurative;
-    }
-
-  cbl_figconst_t figconst =
-                  static_cast<cbl_figconst_t>(field->attr & FIGCONST_MASK);
-  // We have a corner case to deal with:
-  if(    !figconst
-      && field->name[0] == 'N'
-      && strcmp(field->name, "NULLS") == 0 )
-    {
-    figconst = null_value_e;
-    }
-
-  if( figconst )
-    {
-    cbl_char_t figchar = '\0';
-    switch( figconst )
-      {
-      case low_value_e   :
-        figchar = charmap->low_value_character();
-        break;
-      case zero_value_e  :
-        figchar = charmap->mapped_character(ascii_0);
-        break;
-      case space_value_e :
-        figchar = charmap->mapped_character(ascii_space);
-        break;
-      case quote_value_e :
-        figchar = charmap->quote_character();
-        break;
-      case high_value_e  :
-        figchar = charmap->high_value_character();
-        break;
-      case null_value_e:
-        break;
-      default:
-        abort();
-        break;
-      }
-    charmap->putch(figchar, figurative, size_t(0));
-    *size = stride;
-    return figurative;
-    }
-
-  *size = refer.size;
-  return as_chars(field->data + refer.offset);
-  }
-
-extern "C"
-int
-__gg__string_1(const size_t integers[], const cblc_referlet_t *ref)
-  {
-  // The first integer is the count of identifier-2 values.  Call it N
-  // The following N integers are the counts of each of the identifier-1
-  // values,
-  // one for each identifier-1.  Call them M.
-
-  // The first refer is the target
-  // The second refer is the pointer
-  // The third refer is identifier-2 for N1
-  // That's followed by M1 identifier-1 values
-  // That's followed by identifier2 for N2
-  // And so on
-
-  static const int INDEX_OF_POINTER = 1;
-
-  size_t index_cblc = 0 ;
-
-  // Pick up the target
-  const cblc_field_t *tgt = ref[index_cblc].field;
-
-  // Pick up the target encoding, which according to the ISO specification
-  // controls all the parameters.
-  cbl_encoding_t tgt_encoding = tgt->encoding;
-  charmap_t *charmap = __gg__get_charmap(tgt_encoding);
-
-  // Pick up the rest of the parameters
-  size_t tgt_o = ref[index_cblc].offset;
-  size_t tgt_s = ref[index_cblc].size;
-  index_cblc += 1;
-
-  char  *dest         = as_chars(tgt->data + tgt_o);
-  size_t dest_length = tgt_s;
-
-  // Skip over the index of POINTER:
-  index_cblc += 1;
-
-  // Pick up the pointer, if any
-  size_t pointer = 0;
-  int overflow = 0;
-  if( ref[INDEX_OF_POINTER].field )
-    {
-    __int128 p = __gg__int128_from_qualified_field(
-                                              ref[INDEX_OF_POINTER].field,
-                                              ref[INDEX_OF_POINTER].offset,
-                                              ref[INDEX_OF_POINTER].size);
-    if( p < 1 || static_cast<unsigned __int128>(p) > dest_length )
-      {
-      overflow = 1;
-      }
-    else
-      {
-      pointer = static_cast<size_t>(p - 1);
-      }
-    }
-
-  // Make sure that the destination pointer is within the destination
-  if( !overflow && pointer < dest_length )
-    {
-    // We are go for looping through identifier-2 values:
-
-    size_t index_int  = 0;
-
-    // Pick up the number of identifier-2 values
-    size_t N = integers[index_int++];
-
-    std::string delimiter;
-    for( size_t i=0; i<N; i++ )
-      {
-      // Pick up the number of M identifier-1 values for this list of
-      // identifier-2 values:
-      size_t M = integers[index_int++];
-
-      // Pick up the identifier_2 DELIMITED BY value
-      char fig_id2[4];
-      size_t str_id2_size;
-      const char *str_id2 = string_operand(ref[index_cblc],
-                                            charmap,
-                                            1,
-                                            fig_id2,
-                                            &str_id2_size);
-      index_cblc += 1;
-
-      // Preserve the delimiter in case it overlaps the target.
-      delimiter.assign(str_id2, str_id2_size);
-
-      for(size_t j=0; j<M; j++)
-        {
-        // Pick up the next id-1 source string for the current id-2 delimiter
-        char fig_id1[4];
-        size_t str_id1_size;
-        const char *str_id1 = string_operand(ref[index_cblc],
-                                              charmap,
-                                              1,
-                                              fig_id1,
-                                              &str_id1_size);
-        index_cblc += 1;
-
-        size_t nfound;
-        if( delimiter.empty() )
-          {
-          // No given delimiter means DELIMITED BY SIZE
-          nfound = str_id1_size;
-          }
-        else if( delimiter.size() == 1 )
-          {
-          // A one-character delimiter is overwhelmingly the common case.
-          const void *found = memchr(str_id1,
-                                     static_cast<unsigned char>(delimiter[0]),
-                                     str_id1_size);
-          nfound = found
-                 ? static_cast<size_t>(
-                              static_cast<const char *>(found) - str_id1)
-                 : str_id1_size;
-          }
-        else
-          {
-          // We have an id2, so we look for it inside id1
-          const char *found = std::search(str_id1,
-                                          str_id1 + str_id1_size,
-                                          delimiter.begin(),
-                                          delimiter.end());
-          nfound = static_cast<size_t>(found - str_id1);
-          }
-
-        // We have found id2 inside id1 at location nfound.
-        size_t available = dest_length - pointer;
-        size_t count = std::min(nfound, available);
-        memmove(dest + pointer, str_id1, count);
-        pointer += count;
-        if( count != nfound )
-          {
-          overflow = 1;
-          }
-        if( overflow )
-          {
-          break;
-          }
-        }
-      if( overflow )
-        {
-        break;
-        }
-      }
-
-    // Update the pointer, if there is one
-    if( ref[INDEX_OF_POINTER].field )
-      {
-      __gg__int128_to_qualified_field(ref[INDEX_OF_POINTER].field,
-                                      ref[INDEX_OF_POINTER].offset,
-                                      ref[INDEX_OF_POINTER].size,
-                                      (__int128)(pointer+1),
-                                      0,
-                                      truncation_e);
-      }
-    }
-  else
-    {
-    // The initial pointer is not inside the destination
-    overflow = 1;
-    }
-
-  return overflow;
-  }
-
-static size_t
-find_string_24(const char *str_id1,
-               size_t      str_id1_size,
-               const char *str_id2,
-               size_t      str_id2_size,
-               size_t      stride)
-  {
-  size_t id1_characters = str_id1_size / stride;
-  size_t id2_characters = str_id2_size / stride;
-
-  if( id2_characters == 0 || id2_characters > id1_characters )
-    {
-    return id1_characters;
-    }
-
-  size_t last = id1_characters - id2_characters;
-  if( stride == 2 )
-    {
-    uint16_t first = load_unaligned<uint16_t>(str_id2);
-    for(size_t i=0; i<=last; i++)
-      {
-      const char *candidate = str_id1 + i * stride;
-      if(    load_unaligned<uint16_t>(candidate) == first
-          && (   id2_characters == 1
-              || memcmp(candidate, str_id2, str_id2_size) == 0) )
-        {
-        return i;
-        }
-      }
-    }
-  else
-    {
-    uint32_t first = load_unaligned<uint32_t>(str_id2);
-    for(size_t i=0; i<=last; i++)
-      {
-      const char *candidate = str_id1 + i * stride;
-      if(    load_unaligned<uint32_t>(candidate) == first
-          && (   id2_characters == 1
-              || memcmp(candidate, str_id2, str_id2_size) == 0) )
-        {
-        return i;
-        }
-      }
-    }
-  return id1_characters;
-  }
-
-extern "C"
-int
-__gg__string_24(const size_t integers[], const cblc_referlet_t *ref)
-  {
-  // The first integer is the count of identifier-2 values.  Call it N
-  // The following N integers are the counts of each of the identifier-1
-  // values,
-  // one for each identifier-1.  Call them M.
-
-  // The first refer is the target
-  // The second refer is the pointer
-  // The third refer is identifier-2 for N1
-  // That's followed by M1 identifier-1 values
-  // That's followed by identifier2 for N2
-  // And so on
-
-  static const int INDEX_OF_POINTER = 1;
-
-  size_t index_cblc = 0 ;
-
-  // Pick up the target
-  const cblc_field_t *tgt = ref[index_cblc].field;
-
-  // Pick up the target encoding, which according to the ISO specification
-  // controls all the parameters.
-  cbl_encoding_t tgt_encoding = tgt->encoding;
-  charmap_t *charmap = __gg__get_charmap(tgt_encoding);
-  size_t stride = static_cast<size_t>(charmap->stride());
-
-  // Pick up the rest of the parameters
-  size_t tgt_o = ref[index_cblc].offset;
-  size_t tgt_s = ref[index_cblc].size;
-  index_cblc += 1;
-
-  char  *dest        = as_chars(tgt->data + tgt_o);
-  size_t dest_length = tgt_s / stride;
-
-  // Skip over the index of POINTER:
-  index_cblc += 1;
-
-  // Pick up the pointer, if any
-  size_t pointer = 0;
-  int overflow = 0;
-  if( ref[INDEX_OF_POINTER].field )
-    {
-    __int128 p = __gg__int128_from_qualified_field(
-                                              ref[INDEX_OF_POINTER].field,
-                                              ref[INDEX_OF_POINTER].offset,
-                                              ref[INDEX_OF_POINTER].size);
-    if( p < 1 || static_cast<unsigned __int128>(p) > dest_length )
-      {
-      overflow = 1;
-      }
-    else
-      {
-      pointer = static_cast<size_t>(p - 1);
-      }
-    }
-
-  // Make sure that the destination pointer is within the destination
-  if( !overflow && pointer < dest_length )
-    {
-    // We are go for looping through identifier-2 values:
-
-    size_t index_int = 0;
-
-    // Pick up the number of identifier-2 values
-    size_t N = integers[index_int++];
-
-    std::string delimiter;
-    for(size_t i=0; i<N; i++)
-      {
-      // Pick up the number of M identifier-1 values for this list of
-      // identifier-2 values:
-      size_t M = integers[index_int++];
-
-      // Pick up the identifier_2 DELIMITED BY value
-      char fig_id2[4];
-      size_t str_id2_size;
-      const char *str_id2 = string_operand(ref[index_cblc],
-                                            charmap,
-                                            stride,
-                                            fig_id2,
-                                            &str_id2_size);
-      index_cblc += 1;
-
-      // Preserve the delimiter in case it overlaps the target.
-      delimiter.assign(str_id2, str_id2_size);
-
-      for(size_t j=0; j<M; j++)
-        {
-        // Pick up the next id-1 source string for the current id-2 delimiter
-        char fig_id1[4];
-        size_t str_id1_size;
-        const char *str_id1 = string_operand(ref[index_cblc],
-                                              charmap,
-                                              stride,
-                                              fig_id1,
-                                              &str_id1_size);
-        index_cblc += 1;
-
-        size_t nfound;
-        if( delimiter.empty() )
-          {
-          // No given delimiter means DELIMITED BY SIZE
-          nfound = str_id1_size / stride;
-          }
-        else
-          {
-          // We have an id2, so we look for it inside id1
-          nfound = find_string_24(str_id1,
-                                  str_id1_size,
-                                  delimiter.data(),
-                                  delimiter.size(),
-                                  stride);
-          }
-
-        // We have found id2 inside id1 at location nfound.
-        size_t available = dest_length - pointer;
-        size_t count = std::min(nfound, available);
-        memmove(dest + pointer * stride,
-                str_id1,
-                count * stride);
-        pointer += count;
-        if( count != nfound )
-          {
-          overflow = 1;
-          }
-        if( overflow )
-          {
-          break;
-          }
-        }
-      if( overflow )
-        {
-        break;
-        }
-      }
-
-    // Update the pointer, if there is one
-    if( ref[INDEX_OF_POINTER].field )
-      {
-      __gg__int128_to_qualified_field(ref[INDEX_OF_POINTER].field,
-                                      ref[INDEX_OF_POINTER].offset,
-                                      ref[INDEX_OF_POINTER].size,
-                                      (__int128)(pointer+1),
-                                      0,
-                                      truncation_e);
-      }
-    }
-  else
-    {
-    // The initial pointer is not inside the destination
-    overflow = 1;
-    }
-
-  return overflow;
-  }
-
-extern "C"
-int
-__gg__string(const size_t integers[], const cblc_referlet_t *ref)
-  {
-  const charmap_t *charmap = __gg__get_charmap(ref[0].field->encoding);
-  return charmap->stride() == 1
-       ? __gg__string_1(integers, ref)
-       : __gg__string_24(integers, ref);
-  }
-
 static
 void
 display_both(cblc_field_t  *field,
@@ -10121,489 +9677,6 @@ __gg__assign_value_from_stack(cblc_field_t *dest, 
__int128 parameter)
     }
   }
 
-struct unstring_finder_1
-  {
-  static size_t
-  find(const char *str_id1,
-       size_t      str_id1_size,
-       const char *str_id2,
-       size_t      str_id2_size,
-       size_t)
-    {
-    if( str_id2_size == 1 )
-      {
-      const void *found = memchr(str_id1,
-                                 static_cast<unsigned char>(str_id2[0]),
-                                 str_id1_size);
-      return found
-           ? static_cast<size_t>(static_cast<const char *>(found) - str_id1)
-           : str_id1_size;
-      }
-
-    const char *found = std::search(str_id1,
-                                    str_id1 + str_id1_size,
-                                    str_id2,
-                                    str_id2 + str_id2_size);
-    return static_cast<size_t>(found - str_id1);
-    }
-  };
-
-struct unstring_finder_24
-  {
-  static size_t
-  find(const char *str_id1,
-       size_t      str_id1_size,
-       const char *str_id2,
-       size_t      str_id2_size,
-       size_t      stride)
-    {
-    return find_string_24(str_id1,
-                          str_id1_size,
-                          str_id2,
-                          str_id2_size,
-                          stride);
-    }
-  };
-
-template <typename finder_t>
-static int
-unstring_fixed( const cblc_referlet_t *id2,
-                const cblc_referlet_t *id4,
-                const cblc_referlet_t *id5,
-                const cblc_referlet_t *id6,
-                const cblc_field_t *id1,        // The string being unstring
-                size_t              id1_o,
-                size_t              id1_s,
-                size_t ndelimiteds,       // The number of DELIMITED entries
-                const char *all_flags,    // ALL flags, one per ndelimiteds
-                size_t nreceivers,        // The number of DELIMITER receivers
-                cblc_field_t *id7,        // Character index, start and end
-                size_t        id7_o,
-                size_t        id7_s,
-                cblc_field_t *id8,        // Count of identifier-4 updates
-                size_t        id8_o,
-                size_t        id8_s,
-                size_t        stride_id1)
-  {
-  // The names of the parameters are based on the ISO 1989:2014 specification.
-
-  // There are complexities because of figurative constants, including the
-  // LOW-VALUE figurative constant, which precludes the use of string
-  // operations that would be confused by embedded NUL characters.  Dammit.
-
-  // For each delimiter, there is an identifier-4 receiver that must be
-  // resolved.  Each might have an identifier-5 delimiter, and each might have
-  // an identifier-6 count.
-
-  // Initialize the state variables
-  int overflow = 0;
-  int tally = 0;
-  size_t pointer = 1;
-  size_t nreceiver;
-  size_t left=0;
-  size_t right=0;
-
-  std::string str_id1;
-  std::vector<std::string> delimiters;
-
-  charmap_t *charmap_id1 = __gg__get_charmap(id1->encoding);
-
-  if( id8  )
-    {
-    tally = (int)__gg__int128_from_qualified_field(id8,
-                                                   id8_o,
-                                                   id8_s);
-    }
-
-  if( id7 )
-    {
-    __int128 p = __gg__int128_from_qualified_field(id7,
-                                                    id7_o,
-                                                    id7_s);
-    if(    p < 1
-        || static_cast<unsigned __int128>(p)
-                                      > std::numeric_limits<size_t>::max() )
-      {
-      overflow = 1;
-      goto done;
-      }
-    pointer = static_cast<size_t>(p);
-    }
-
-  // As per the spec, if the string is zero-length; we are done.
-  if( id1_s == 0 )
-    {
-    goto done;
-    }
-
-  // As per the spec, we have an overflow condition if pointer is out of
-  // range:
-  if( pointer > id1_s/stride_id1 )
-    {
-    overflow = 1;
-    goto done;
-    }
-  // pointer is one-based throughout; don't forget that
-
-  /* The earlier implementation converted everything to UTF32.  The
-     fixed-width entry points handle their encodings directly. */
-
-  // Preserve identifier-1 because a receiving item can overlap it.
-  str_id1.assign(as_chars(id1->data + id1_o), id1_s);
-  left = pointer-1;
-  right = str_id1.size() / stride_id1;
-  if( ndelimiteds == 0 )
-    {
-    // There are no DELIMITED BY identifier-2 values, so we just peel off
-    // characters from identifier-1 and put them into each identifier-4:
-    for( size_t receiver=0; receiver<nreceivers; receiver++ )
-      {
-      if( left >= right )
-        {
-        // We have run out of input characters.
-        break;
-        }
-      // We will peel off enough characters to fit the receiving id4:
-      size_t id_4_size = id4[receiver].size/stride_id1;
-      if( id4[receiver].field->attr & separate_e )
-        {
-        // The receiver is NumericDisplay with a separate sign, so, as per
-        // the spec, we reduce the size by one character.
-        if( id_4_size )
-          {
-          id_4_size -= 1;
-          }
-        }
-
-      // Make sure id_4_size doesn't take us past the end of the universe
-      if( left + id_4_size > right )
-        {
-        id_4_size = right - left;
-        }
-
-      // Put the specified string into place:
-      __gg__move_literala(id4[receiver].field,
-                          id4[receiver].offset,
-                          id4[receiver].size,
-                          truncation_e,
-                          str_id1.data() + left * stride_id1,
-                          id_4_size * stride_id1);
-      // Update the state variables:
-      left += id_4_size;
-      pointer += id_4_size;
-      tally += 1;
-      }
-    goto done;
-    }
-
-  // Arriving here means there is some number of ndelimiteds
-
-  // Preserve the delimiters because receiving items can overlap them.
-  delimiters.reserve(ndelimiteds);
-  for( size_t i=0; i<ndelimiteds; i++ )
-    {
-    char figurative[4];
-    size_t delimiter_size;
-    const char *delimiter = string_operand(id2[i],
-                                            charmap_id1,
-                                            stride_id1,
-                                            figurative,
-                                            &delimiter_size);
-    delimiters.emplace_back(delimiter, delimiter_size);
-    }
-
-  nreceiver = 0;
-  while( left < right )
-    {
-    // If we've used up all receivers, we bail at this point
-    if( nreceiver >= nreceivers )
-      {
-      break;
-      }
-
-    // Starting at 'left', see if we can find any of the delimiters.  For each
-    // 'left' position, we look through all of the delimiters,
-
-    size_t best_delimiter = ndelimiteds;
-    size_t best_leftmost = right; // This is the location of the start of ALL
-    size_t best_location = right; // This is the location of the last of ALL
-    for( size_t i=0; i<ndelimiteds; i++ )
-      {
-      const std::string &str_id2 = delimiters[i];
-      if( str_id2.empty() )
-        {
-        continue;
-        }
-      size_t nfound = left + finder_t::find(
-                                     str_id1.data() + left * stride_id1,
-                                     (right - left) * stride_id1,
-                                     str_id2.data(),
-                                     str_id2.size(),
-                                     stride_id1);
-      if( nfound != right )
-        {
-        // We found a delimiter
-        if( nfound > best_leftmost )
-          {
-          // This delimiter lives to the right of the best one we found so far.
-          // Ignore it, and proceed to the next delimiter.
-          continue;
-          }
-        // This delimiter is the leftmost we've seen so far:
-        best_delimiter = i;
-        best_leftmost  = nfound;
-        best_location  = nfound;
-
-        if( all_flags[i] == ascii_1 )
-          {
-          // This delimiter is flagged as ALL, so we need to see if we have
-          // a flock of them:
-          size_t delimiter_characters = str_id2.size() / stride_id1;
-          size_t next = nfound + delimiter_characters;
-          while(    delimiter_characters <= right - next
-                 && memcmp(str_id1.data() + next * stride_id1,
-                           str_id2.data(),
-                           str_id2.size()) == 0 )
-            {
-            // We found another consecutive one at next:
-            best_location = next;
-            next += delimiter_characters;
-            }
-          }
-        }
-      }
-
-    if( best_delimiter == ndelimiteds )
-      {
-      // We were unable to find a delimiter, so we eat up the remainder
-      // of the sender:
-      best_leftmost = right;
-      best_location = right;
-      }
-
-    // Apply what we have learned to the next receiver:
-
-    size_t examined = best_leftmost - left;
-
-    // Put the specified string into place:
-    __gg__move_literala(id4[nreceiver].field,
-                        id4[nreceiver].offset,
-                        id4[nreceiver].size,
-                        truncation_e,
-                        str_id1.data() + left * stride_id1,
-                        examined * stride_id1);
-    // Update the left edge
-    left = best_location + (best_delimiter != ndelimiteds
-                            ? delimiters[best_delimiter].size() / stride_id1
-                            : 0) ;
-    if( id5[nreceiver].field )
-      {
-      // The caller wants to know what the delimiter was:
-      if( best_delimiter != ndelimiteds )
-        {
-        __gg__move_literala(id5[nreceiver].field,
-                            id5[nreceiver].offset,
-                            id5[nreceiver].size,
-                            truncation_e,
-                            delimiters[best_delimiter].data(),
-                            delimiters[best_delimiter].size());
-        }
-      else
-        {
-        // We didn't find a delimiter
-        __gg__move_literala(id5[nreceiver].field,
-                            id5[nreceiver].offset,
-                            id5[nreceiver].size,
-                            truncation_e,
-                            "",
-                            0);
-        }
-      }
-
-    if( id6[nreceiver].field )
-      {
-      __gg__int128_to_qualified_field(id6[nreceiver].field,
-                                      id6[nreceiver].offset,
-                                      id6[nreceiver].size,
-                                      (__int128)examined,
-                                      0,
-                                      truncation_e);
-      }
-
-    // Update the state variables:
-    tally += 1;
-    nreceiver += 1;
-    if( best_delimiter != ndelimiteds )
-      {
-      pointer = left+1 ;
-      }
-    }
-
-done:
-
-  if( id8 )
-    {
-    __gg__int128_to_qualified_field(id8,
-                                    id8_o,
-                                    id8_s,
-                                    (__int128)tally,
-                                    0,
-                                    truncation_e);
-    }
-
-  if( id7 )
-    {
-    __gg__int128_to_qualified_field(id7,
-                                    id7_o,
-                                    id7_s,
-                                    (__int128)pointer,
-                                    0,
-                                    truncation_e);
-    }
-
-  if( left < right )
-    {
-    overflow = 1;
-    }
-
-  return overflow;
-  }
-
-extern "C"
-int
-__gg__unstring_1( const cblc_referlet_t *id2,
-                  const cblc_referlet_t *id4,
-                  const cblc_referlet_t *id5,
-                  const cblc_referlet_t *id6,
-                  const cblc_field_t *id1,
-                  size_t              id1_o,
-                  size_t              id1_s,
-                  size_t              ndelimiteds,
-                  const char         *all_flags,
-                  size_t              nreceivers,
-                  cblc_field_t       *id7,
-                  size_t              id7_o,
-                  size_t              id7_s,
-                  cblc_field_t       *id8,
-                  size_t              id8_o,
-                  size_t              id8_s)
-  {
-  return unstring_fixed<unstring_finder_1>(id2,
-                                           id4,
-                                           id5,
-                                           id6,
-                                           id1,
-                                           id1_o,
-                                           id1_s,
-                                           ndelimiteds,
-                                           all_flags,
-                                           nreceivers,
-                                           id7,
-                                           id7_o,
-                                           id7_s,
-                                           id8,
-                                           id8_o,
-                                           id8_s,
-                                           1);
-  }
-
-extern "C"
-int
-__gg__unstring_24( const cblc_referlet_t *id2,
-                   const cblc_referlet_t *id4,
-                   const cblc_referlet_t *id5,
-                   const cblc_referlet_t *id6,
-                   const cblc_field_t *id1,
-                   size_t              id1_o,
-                   size_t              id1_s,
-                   size_t              ndelimiteds,
-                   const char         *all_flags,
-                   size_t              nreceivers,
-                   cblc_field_t       *id7,
-                   size_t              id7_o,
-                   size_t              id7_s,
-                   cblc_field_t       *id8,
-                   size_t              id8_o,
-                   size_t              id8_s)
-  {
-  size_t stride = static_cast<size_t>(
-                         __gg__get_charmap(id1->encoding)->stride());
-  return unstring_fixed<unstring_finder_24>(id2,
-                                            id4,
-                                            id5,
-                                            id6,
-                                            id1,
-                                            id1_o,
-                                            id1_s,
-                                            ndelimiteds,
-                                            all_flags,
-                                            nreceivers,
-                                            id7,
-                                            id7_o,
-                                            id7_s,
-                                            id8,
-                                            id8_o,
-                                            id8_s,
-                                            stride);
-  }
-
-extern "C"
-int
-__gg__unstring( const cblc_referlet_t *id2,
-                const cblc_referlet_t *id4,
-                const cblc_referlet_t *id5,
-                const cblc_referlet_t *id6,
-                const cblc_field_t *id1,
-                size_t              id1_o,
-                size_t              id1_s,
-                size_t              ndelimiteds,
-                const char         *all_flags,
-                size_t              nreceivers,
-                cblc_field_t       *id7,
-                size_t              id7_o,
-                size_t              id7_s,
-                cblc_field_t       *id8,
-                size_t              id8_o,
-                size_t              id8_s)
-  {
-  const charmap_t *charmap = __gg__get_charmap(id1->encoding);
-  if( charmap->stride() == 1 )
-    {
-    return __gg__unstring_1(id2,
-                            id4,
-                            id5,
-                            id6,
-                            id1,
-                            id1_o,
-                            id1_s,
-                            ndelimiteds,
-                            all_flags,
-                            nreceivers,
-                            id7,
-                            id7_o,
-                            id7_s,
-                            id8,
-                            id8_o,
-                            id8_s);
-    }
-  return __gg__unstring_24(id2,
-                           id4,
-                           id5,
-                           id6,
-                           id1,
-                           id1_o,
-                           id1_s,
-                           ndelimiteds,
-                           all_flags,
-                           nreceivers,
-                           id7,
-                           id7_o,
-                           id7_s,
-                           id8,
-                           id8_o,
-                           id8_s);
-  }
-
 static std::set<void *> to_be_canceled;
 
 extern "C"
@@ -14530,3 +13603,4 @@ __gg__move_stash_release(const unsigned char *stash)
 
   move_stashes.depth -= 1;
   }
+
diff --git a/libgcobol/libgcobol.h b/libgcobol/libgcobol.h
index 49f1b1f47682..35919e4ab818 100644
--- a/libgcobol/libgcobol.h
+++ b/libgcobol/libgcobol.h
@@ -158,4 +158,11 @@ extern "C" __int128 __gg__int128_to_int128_rounded( 
cbl_round_t rounded,
                                                     __int128    factor,
                                                     int        *compute_error);
 
+extern "C" int __gg__move_literala( cblc_field_t *field,
+                                    size_t        field_offset,
+                                    size_t        field_size,
+                                    cbl_round_t   rounded_,
+                                    const char   *str,
+                                    size_t        strlen );
+
 #endif
diff --git a/libgcobol/unstring.cc b/libgcobol/unstring.cc
new file mode 100644
index 000000000000..10701e225de0
--- /dev/null
+++ b/libgcobol/unstring.cc
@@ -0,0 +1,1022 @@
+/*
+ * Copyright (c) 2021-2026 Symas Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * * Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following disclaimer
+ *   in the documentation and/or other materials provided with the
+ *   distribution.
+ * * Neither the name of the Symas Corporation nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <algorithm>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <set>
+#include <stack>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <cwctype>
+#include <limits>
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fenv.h>
+#include <math.h> // required for fpclassify(3), not in cmath
+#include <setjmp.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdarg.h>
+#if __has_include(<errno.h>)
+# include <errno.h> // for program_invocation_short_name
+#endif
+#include <langinfo.h>
+
+#include "config.h"
+#include "libgcobol-fp.h"
+
+#include "ec.h"
+#include "common-defs.h"
+#include "io.h"
+#include "gcobolio.h"
+#include "cobol-endian.h"
+#include "libgcobol.h"
+#include "gfileio.h"
+#include "charmaps.h"
+#include "valconv.h"
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <execinfo.h>
+#include "exceptl.h"
+#include "stringbin.h"
+
+static inline char *
+as_chars(unsigned char *p)
+  {
+  return reinterpret_cast<char *>(p);
+  }
+
+template <typename T>
+static T
+load_unaligned(const void *p)
+  {
+  T value;
+  memcpy(&value, p, sizeof(value));
+  return value;
+  }
+
+// The compiler ensures that every operand uses the target encoding, so an
+// ordinary operand can be used directly.
+static inline const char *
+string_operand(const cblc_referlet_t &refer,
+               charmap_t             *charmap,
+               size_t                 stride,
+               char                   figurative[4],
+               size_t                *size)
+  {
+  const cblc_field_t *field = refer.field;
+  if( !field )
+    {
+    *size = 0;
+    return figurative;
+    }
+
+  cbl_figconst_t figconst =
+                  static_cast<cbl_figconst_t>(field->attr & FIGCONST_MASK);
+  // We have a corner case to deal with:
+  if(    !figconst
+      && field->name[0] == 'N'
+      && strcmp(field->name, "NULLS") == 0 )
+    {
+    figconst = null_value_e;
+    }
+
+  if( figconst )
+    {
+    cbl_char_t figchar = '\0';
+    switch( figconst )
+      {
+      case low_value_e   :
+        figchar = charmap->low_value_character();
+        break;
+      case zero_value_e  :
+        figchar = charmap->mapped_character(ascii_0);
+        break;
+      case space_value_e :
+        figchar = charmap->mapped_character(ascii_space);
+        break;
+      case quote_value_e :
+        figchar = charmap->quote_character();
+        break;
+      case high_value_e  :
+        figchar = charmap->high_value_character();
+        break;
+      case null_value_e:
+        break;
+      default:
+        abort();
+        break;
+      }
+    charmap->putch(figchar, figurative, size_t(0));
+    *size = stride;
+    return figurative;
+    }
+
+  *size = refer.size;
+  return as_chars(field->data + refer.offset);
+  }
+
+extern "C"
+int
+__gg__string_1(const size_t integers[], const cblc_referlet_t *ref)
+  {
+  // The first integer is the count of identifier-2 values.  Call it N
+  // The following N integers are the counts of each of the identifier-1
+  // values,
+  // one for each identifier-1.  Call them M.
+
+  // The first refer is the target
+  // The second refer is the pointer
+  // The third refer is identifier-2 for N1
+  // That's followed by M1 identifier-1 values
+  // That's followed by identifier2 for N2
+  // And so on
+
+  static const int INDEX_OF_POINTER = 1;
+
+  size_t index_cblc = 0 ;
+
+  // Pick up the target
+  const cblc_field_t *tgt = ref[index_cblc].field;
+
+  // Pick up the target encoding, which according to the ISO specification
+  // controls all the parameters.
+  cbl_encoding_t tgt_encoding = tgt->encoding;
+  charmap_t *charmap = __gg__get_charmap(tgt_encoding);
+
+  // Pick up the rest of the parameters
+  size_t tgt_o = ref[index_cblc].offset;
+  size_t tgt_s = ref[index_cblc].size;
+  index_cblc += 1;
+
+  char  *dest         = as_chars(tgt->data + tgt_o);
+  size_t dest_length = tgt_s;
+
+  // Skip over the index of POINTER:
+  index_cblc += 1;
+
+  // Pick up the pointer, if any
+  size_t pointer = 0;
+  int overflow = 0;
+  if( ref[INDEX_OF_POINTER].field )
+    {
+    __int128 p = __gg__int128_from_qualified_field(
+                                              ref[INDEX_OF_POINTER].field,
+                                              ref[INDEX_OF_POINTER].offset,
+                                              ref[INDEX_OF_POINTER].size);
+    if( p < 1 || static_cast<unsigned __int128>(p) > dest_length )
+      {
+      overflow = 1;
+      }
+    else
+      {
+      pointer = static_cast<size_t>(p - 1);
+      }
+    }
+
+  // Make sure that the destination pointer is within the destination
+  if( !overflow && pointer < dest_length )
+    {
+    // We are go for looping through identifier-2 values:
+
+    size_t index_int  = 0;
+
+    // Pick up the number of identifier-2 values
+    size_t N = integers[index_int++];
+
+    std::string delimiter;
+    for( size_t i=0; i<N; i++ )
+      {
+      // Pick up the number of M identifier-1 values for this list of
+      // identifier-2 values:
+      size_t M = integers[index_int++];
+
+      // Pick up the identifier_2 DELIMITED BY value
+      char fig_id2[4];
+      size_t str_id2_size;
+      const char *str_id2 = string_operand(ref[index_cblc],
+                                            charmap,
+                                            1,
+                                            fig_id2,
+                                            &str_id2_size);
+      index_cblc += 1;
+
+      // Preserve the delimiter in case it overlaps the target.
+      delimiter.assign(str_id2, str_id2_size);
+
+      for(size_t j=0; j<M; j++)
+        {
+        // Pick up the next id-1 source string for the current id-2 delimiter
+        char fig_id1[4];
+        size_t str_id1_size;
+        const char *str_id1 = string_operand(ref[index_cblc],
+                                              charmap,
+                                              1,
+                                              fig_id1,
+                                              &str_id1_size);
+        index_cblc += 1;
+
+        size_t nfound;
+        if( delimiter.empty() )
+          {
+          // No given delimiter means DELIMITED BY SIZE
+          nfound = str_id1_size;
+          }
+        else if( delimiter.size() == 1 )
+          {
+          // A one-character delimiter is overwhelmingly the common case.
+          const void *found = memchr(str_id1,
+                                     static_cast<unsigned char>(delimiter[0]),
+                                     str_id1_size);
+          nfound = found
+                 ? static_cast<size_t>(
+                              static_cast<const char *>(found) - str_id1)
+                 : str_id1_size;
+          }
+        else
+          {
+          // We have an id2, so we look for it inside id1
+          const char *found = std::search(str_id1,
+                                          str_id1 + str_id1_size,
+                                          delimiter.begin(),
+                                          delimiter.end());
+          nfound = static_cast<size_t>(found - str_id1);
+          }
+
+        // We have found id2 inside id1 at location nfound.
+        size_t available = dest_length - pointer;
+        size_t count = std::min(nfound, available);
+        memmove(dest + pointer, str_id1, count);
+        pointer += count;
+        if( count != nfound )
+          {
+          overflow = 1;
+          }
+        if( overflow )
+          {
+          break;
+          }
+        }
+      if( overflow )
+        {
+        break;
+        }
+      }
+
+    // Update the pointer, if there is one
+    if( ref[INDEX_OF_POINTER].field )
+      {
+      __gg__int128_to_qualified_field(ref[INDEX_OF_POINTER].field,
+                                      ref[INDEX_OF_POINTER].offset,
+                                      ref[INDEX_OF_POINTER].size,
+                                      (__int128)(pointer+1),
+                                      0,
+                                      truncation_e);
+      }
+    }
+  else
+    {
+    // The initial pointer is not inside the destination
+    overflow = 1;
+    }
+
+  return overflow;
+  }
+
+static size_t
+find_string_24(const char *str_id1,
+               size_t      str_id1_size,
+               const char *str_id2,
+               size_t      str_id2_size,
+               size_t      stride)
+  {
+  size_t id1_characters = str_id1_size / stride;
+  size_t id2_characters = str_id2_size / stride;
+
+  if( id2_characters == 0 || id2_characters > id1_characters )
+    {
+    return id1_characters;
+    }
+
+  size_t last = id1_characters - id2_characters;
+  if( stride == 2 )
+    {
+    uint16_t first = load_unaligned<uint16_t>(str_id2);
+    for(size_t i=0; i<=last; i++)
+      {
+      const char *candidate = str_id1 + i * stride;
+      if(    load_unaligned<uint16_t>(candidate) == first
+          && (   id2_characters == 1
+              || memcmp(candidate, str_id2, str_id2_size) == 0) )
+        {
+        return i;
+        }
+      }
+    }
+  else
+    {
+    uint32_t first = load_unaligned<uint32_t>(str_id2);
+    for(size_t i=0; i<=last; i++)
+      {
+      const char *candidate = str_id1 + i * stride;
+      if(    load_unaligned<uint32_t>(candidate) == first
+          && (   id2_characters == 1
+              || memcmp(candidate, str_id2, str_id2_size) == 0) )
+        {
+        return i;
+        }
+      }
+    }
+  return id1_characters;
+  }
+
+extern "C"
+int
+__gg__string_24(const size_t integers[], const cblc_referlet_t *ref)
+  {
+  // The first integer is the count of identifier-2 values.  Call it N
+  // The following N integers are the counts of each of the identifier-1
+  // values,
+  // one for each identifier-1.  Call them M.
+
+  // The first refer is the target
+  // The second refer is the pointer
+  // The third refer is identifier-2 for N1
+  // That's followed by M1 identifier-1 values
+  // That's followed by identifier2 for N2
+  // And so on
+
+  static const int INDEX_OF_POINTER = 1;
+
+  size_t index_cblc = 0 ;
+
+  // Pick up the target
+  const cblc_field_t *tgt = ref[index_cblc].field;
+
+  // Pick up the target encoding, which according to the ISO specification
+  // controls all the parameters.
+  cbl_encoding_t tgt_encoding = tgt->encoding;
+  charmap_t *charmap = __gg__get_charmap(tgt_encoding);
+  size_t stride = static_cast<size_t>(charmap->stride());
+
+  // Pick up the rest of the parameters
+  size_t tgt_o = ref[index_cblc].offset;
+  size_t tgt_s = ref[index_cblc].size;
+  index_cblc += 1;
+
+  char  *dest        = as_chars(tgt->data + tgt_o);
+  size_t dest_length = tgt_s / stride;
+
+  // Skip over the index of POINTER:
+  index_cblc += 1;
+
+  // Pick up the pointer, if any
+  size_t pointer = 0;
+  int overflow = 0;
+  if( ref[INDEX_OF_POINTER].field )
+    {
+    __int128 p = __gg__int128_from_qualified_field(
+                                              ref[INDEX_OF_POINTER].field,
+                                              ref[INDEX_OF_POINTER].offset,
+                                              ref[INDEX_OF_POINTER].size);
+    if( p < 1 || static_cast<unsigned __int128>(p) > dest_length )
+      {
+      overflow = 1;
+      }
+    else
+      {
+      pointer = static_cast<size_t>(p - 1);
+      }
+    }
+
+  // Make sure that the destination pointer is within the destination
+  if( !overflow && pointer < dest_length )
+    {
+    // We are go for looping through identifier-2 values:
+
+    size_t index_int = 0;
+
+    // Pick up the number of identifier-2 values
+    size_t N = integers[index_int++];
+
+    std::string delimiter;
+    for(size_t i=0; i<N; i++)
+      {
+      // Pick up the number of M identifier-1 values for this list of
+      // identifier-2 values:
+      size_t M = integers[index_int++];
+
+      // Pick up the identifier_2 DELIMITED BY value
+      char fig_id2[4];
+      size_t str_id2_size;
+      const char *str_id2 = string_operand(ref[index_cblc],
+                                            charmap,
+                                            stride,
+                                            fig_id2,
+                                            &str_id2_size);
+      index_cblc += 1;
+
+      // Preserve the delimiter in case it overlaps the target.
+      delimiter.assign(str_id2, str_id2_size);
+
+      for(size_t j=0; j<M; j++)
+        {
+        // Pick up the next id-1 source string for the current id-2 delimiter
+        char fig_id1[4];
+        size_t str_id1_size;
+        const char *str_id1 = string_operand(ref[index_cblc],
+                                              charmap,
+                                              stride,
+                                              fig_id1,
+                                              &str_id1_size);
+        index_cblc += 1;
+
+        size_t nfound;
+        if( delimiter.empty() )
+          {
+          // No given delimiter means DELIMITED BY SIZE
+          nfound = str_id1_size / stride;
+          }
+        else
+          {
+          // We have an id2, so we look for it inside id1
+          nfound = find_string_24(str_id1,
+                                  str_id1_size,
+                                  delimiter.data(),
+                                  delimiter.size(),
+                                  stride);
+          }
+
+        // We have found id2 inside id1 at location nfound.
+        size_t available = dest_length - pointer;
+        size_t count = std::min(nfound, available);
+        memmove(dest + pointer * stride,
+                str_id1,
+                count * stride);
+        pointer += count;
+        if( count != nfound )
+          {
+          overflow = 1;
+          }
+        if( overflow )
+          {
+          break;
+          }
+        }
+      if( overflow )
+        {
+        break;
+        }
+      }
+
+    // Update the pointer, if there is one
+    if( ref[INDEX_OF_POINTER].field )
+      {
+      __gg__int128_to_qualified_field(ref[INDEX_OF_POINTER].field,
+                                      ref[INDEX_OF_POINTER].offset,
+                                      ref[INDEX_OF_POINTER].size,
+                                      (__int128)(pointer+1),
+                                      0,
+                                      truncation_e);
+      }
+    }
+  else
+    {
+    // The initial pointer is not inside the destination
+    overflow = 1;
+    }
+
+  return overflow;
+  }
+
+extern "C"
+int
+__gg__string(const size_t integers[], const cblc_referlet_t *ref)
+  {
+  const charmap_t *charmap = __gg__get_charmap(ref[0].field->encoding);
+  return charmap->stride() == 1
+       ? __gg__string_1(integers, ref)
+       : __gg__string_24(integers, ref);
+  }
+
+struct unstring_finder_1
+  {
+  static size_t
+  find(const char *str_id1,
+       size_t      str_id1_size,
+       const char *str_id2,
+       size_t      str_id2_size,
+       size_t)
+    {
+    if( str_id2_size == 1 )
+      {
+      const void *found = memchr(str_id1,
+                                 static_cast<unsigned char>(str_id2[0]),
+                                 str_id1_size);
+      return found
+           ? static_cast<size_t>(static_cast<const char *>(found) - str_id1)
+           : str_id1_size;
+      }
+
+    const char *found = std::search(str_id1,
+                                    str_id1 + str_id1_size,
+                                    str_id2,
+                                    str_id2 + str_id2_size);
+    return static_cast<size_t>(found - str_id1);
+    }
+  };
+
+struct unstring_finder_24
+  {
+  static size_t
+  find(const char *str_id1,
+       size_t      str_id1_size,
+       const char *str_id2,
+       size_t      str_id2_size,
+       size_t      stride)
+    {
+    return find_string_24(str_id1,
+                          str_id1_size,
+                          str_id2,
+                          str_id2_size,
+                          stride);
+    }
+  };
+
+template <typename finder_t>
+static int
+unstring_fixed( const cblc_referlet_t *id2,
+                const cblc_referlet_t *id4,
+                const cblc_referlet_t *id5,
+                const cblc_referlet_t *id6,
+                const cblc_field_t *id1,        // The string being unstring
+                size_t              id1_o,
+                size_t              id1_s,
+                size_t ndelimiteds,       // The number of DELIMITED entries
+                const char *all_flags,    // ALL flags, one per ndelimiteds
+                size_t nreceivers,        // The number of DELIMITER receivers
+                cblc_field_t *id7,        // Character index, start and end
+                size_t        id7_o,
+                size_t        id7_s,
+                cblc_field_t *id8,        // Count of identifier-4 updates
+                size_t        id8_o,
+                size_t        id8_s,
+                size_t        stride_id1)
+  {
+  // The names of the parameters are based on the ISO 1989:2014 specification.
+
+  // There are complexities because of figurative constants, including the
+  // LOW-VALUE figurative constant, which precludes the use of string
+  // operations that would be confused by embedded NUL characters.  Dammit.
+
+  // For each delimiter, there is an identifier-4 receiver that must be
+  // resolved.  Each might have an identifier-5 delimiter, and each might have
+  // an identifier-6 count.
+
+  // Initialize the state variables
+  int overflow = 0;
+  int tally = 0;
+  size_t pointer = 1;
+  size_t nreceiver;
+  size_t left=0;
+  size_t right=0;
+
+  std::string str_id1;
+  std::vector<std::string> delimiters;
+
+  charmap_t *charmap_id1 = __gg__get_charmap(id1->encoding);
+
+  if( id8  )
+    {
+    tally = (int)__gg__int128_from_qualified_field(id8,
+                                                   id8_o,
+                                                   id8_s);
+    }
+
+  if( id7 )
+    {
+    __int128 p = __gg__int128_from_qualified_field(id7,
+                                                    id7_o,
+                                                    id7_s);
+    if(    p < 1
+        || static_cast<unsigned __int128>(p)
+                                      > std::numeric_limits<size_t>::max() )
+      {
+      overflow = 1;
+      goto done;
+      }
+    pointer = static_cast<size_t>(p);
+    }
+
+  // As per the spec, if the string is zero-length; we are done.
+  if( id1_s == 0 )
+    {
+    goto done;
+    }
+
+  // As per the spec, we have an overflow condition if pointer is out of
+  // range:
+  if( pointer > id1_s/stride_id1 )
+    {
+    overflow = 1;
+    goto done;
+    }
+  // pointer is one-based throughout; don't forget that
+
+  /* The earlier implementation converted everything to UTF32.  The
+     fixed-width entry points handle their encodings directly. */
+
+  // Preserve identifier-1 because a receiving item can overlap it.
+  str_id1.assign(as_chars(id1->data + id1_o), id1_s);
+  left = pointer-1;
+  right = str_id1.size() / stride_id1;
+  if( ndelimiteds == 0 )
+    {
+    // There are no DELIMITED BY identifier-2 values, so we just peel off
+    // characters from identifier-1 and put them into each identifier-4:
+    for( size_t receiver=0; receiver<nreceivers; receiver++ )
+      {
+      if( left >= right )
+        {
+        // We have run out of input characters.
+        break;
+        }
+      // We will peel off enough characters to fit the receiving id4:
+      size_t id_4_size = id4[receiver].size/stride_id1;
+      if( id4[receiver].field->attr & separate_e )
+        {
+        // The receiver is NumericDisplay with a separate sign, so, as per
+        // the spec, we reduce the size by one character.
+        if( id_4_size )
+          {
+          id_4_size -= 1;
+          }
+        }
+
+      // Make sure id_4_size doesn't take us past the end of the universe
+      if( left + id_4_size > right )
+        {
+        id_4_size = right - left;
+        }
+
+      // Put the specified string into place:
+      __gg__move_literala(id4[receiver].field,
+                          id4[receiver].offset,
+                          id4[receiver].size,
+                          truncation_e,
+                          str_id1.data() + left * stride_id1,
+                          id_4_size * stride_id1);
+      // Update the state variables:
+      left += id_4_size;
+      pointer += id_4_size;
+      tally += 1;
+      }
+    goto done;
+    }
+
+  // Arriving here means there is some number of ndelimiteds
+
+  // Preserve the delimiters because receiving items can overlap them.
+  delimiters.reserve(ndelimiteds);
+  for( size_t i=0; i<ndelimiteds; i++ )
+    {
+    char figurative[4];
+    size_t delimiter_size;
+    const char *delimiter = string_operand(id2[i],
+                                            charmap_id1,
+                                            stride_id1,
+                                            figurative,
+                                            &delimiter_size);
+    delimiters.emplace_back(delimiter, delimiter_size);
+    }
+
+  nreceiver = 0;
+  while( left < right )
+    {
+    // If we've used up all receivers, we bail at this point
+    if( nreceiver >= nreceivers )
+      {
+      break;
+      }
+
+    // Starting at 'left', see if we can find any of the delimiters.  For each
+    // 'left' position, we look through all of the delimiters,
+
+    size_t best_delimiter = ndelimiteds;
+    size_t best_leftmost = right; // This is the location of the start of ALL
+    size_t best_location = right; // This is the location of the last of ALL
+    for( size_t i=0; i<ndelimiteds; i++ )
+      {
+      const std::string &str_id2 = delimiters[i];
+      if( str_id2.empty() )
+        {
+        continue;
+        }
+      size_t nfound = left + finder_t::find(
+                                     str_id1.data() + left * stride_id1,
+                                     (right - left) * stride_id1,
+                                     str_id2.data(),
+                                     str_id2.size(),
+                                     stride_id1);
+      if( nfound != right )
+        {
+        // We found a delimiter
+        if( nfound > best_leftmost )
+          {
+          // This delimiter lives to the right of the best one we found so far.
+          // Ignore it, and proceed to the next delimiter.
+          continue;
+          }
+        // This delimiter is the leftmost we've seen so far:
+        best_delimiter = i;
+        best_leftmost  = nfound;
+        best_location  = nfound;
+
+        if( all_flags[i] == ascii_1 )
+          {
+          // This delimiter is flagged as ALL, so we need to see if we have
+          // a flock of them:
+          size_t delimiter_characters = str_id2.size() / stride_id1;
+          size_t next = nfound + delimiter_characters;
+          while(    delimiter_characters <= right - next
+                 && memcmp(str_id1.data() + next * stride_id1,
+                           str_id2.data(),
+                           str_id2.size()) == 0 )
+            {
+            // We found another consecutive one at next:
+            best_location = next;
+            next += delimiter_characters;
+            }
+          }
+        }
+      }
+
+    if( best_delimiter == ndelimiteds )
+      {
+      // We were unable to find a delimiter, so we eat up the remainder
+      // of the sender:
+      best_leftmost = right;
+      best_location = right;
+      }
+
+    // Apply what we have learned to the next receiver:
+
+    size_t examined = best_leftmost - left;
+
+    // Put the specified string into place:
+    __gg__move_literala(id4[nreceiver].field,
+                        id4[nreceiver].offset,
+                        id4[nreceiver].size,
+                        truncation_e,
+                        str_id1.data() + left * stride_id1,
+                        examined * stride_id1);
+    // Update the left edge
+    left = best_location + (best_delimiter != ndelimiteds
+                            ? delimiters[best_delimiter].size() / stride_id1
+                            : 0) ;
+    if( id5[nreceiver].field )
+      {
+      // The caller wants to know what the delimiter was:
+      if( best_delimiter != ndelimiteds )
+        {
+        __gg__move_literala(id5[nreceiver].field,
+                            id5[nreceiver].offset,
+                            id5[nreceiver].size,
+                            truncation_e,
+                            delimiters[best_delimiter].data(),
+                            delimiters[best_delimiter].size());
+        }
+      else
+        {
+        // We didn't find a delimiter
+        __gg__move_literala(id5[nreceiver].field,
+                            id5[nreceiver].offset,
+                            id5[nreceiver].size,
+                            truncation_e,
+                            "",
+                            0);
+        }
+      }
+
+    if( id6[nreceiver].field )
+      {
+      __gg__int128_to_qualified_field(id6[nreceiver].field,
+                                      id6[nreceiver].offset,
+                                      id6[nreceiver].size,
+                                      (__int128)examined,
+                                      0,
+                                      truncation_e);
+      }
+
+    // Update the state variables:
+    tally += 1;
+    nreceiver += 1;
+    if( best_delimiter != ndelimiteds )
+      {
+      pointer = left+1 ;
+      }
+    }
+
+done:
+
+  if( id8 )
+    {
+    __gg__int128_to_qualified_field(id8,
+                                    id8_o,
+                                    id8_s,
+                                    (__int128)tally,
+                                    0,
+                                    truncation_e);
+    }
+
+  if( id7 )
+    {
+    __gg__int128_to_qualified_field(id7,
+                                    id7_o,
+                                    id7_s,
+                                    (__int128)pointer,
+                                    0,
+                                    truncation_e);
+    }
+
+  if( left < right )
+    {
+    overflow = 1;
+    }
+
+  return overflow;
+  }
+
+extern "C"
+int
+__gg__unstring_1( const cblc_referlet_t *id2,
+                  const cblc_referlet_t *id4,
+                  const cblc_referlet_t *id5,
+                  const cblc_referlet_t *id6,
+                  const cblc_field_t *id1,
+                  size_t              id1_o,
+                  size_t              id1_s,
+                  size_t              ndelimiteds,
+                  const char         *all_flags,
+                  size_t              nreceivers,
+                  cblc_field_t       *id7,
+                  size_t              id7_o,
+                  size_t              id7_s,
+                  cblc_field_t       *id8,
+                  size_t              id8_o,
+                  size_t              id8_s)
+  {
+  return unstring_fixed<unstring_finder_1>(id2,
+                                           id4,
+                                           id5,
+                                           id6,
+                                           id1,
+                                           id1_o,
+                                           id1_s,
+                                           ndelimiteds,
+                                           all_flags,
+                                           nreceivers,
+                                           id7,
+                                           id7_o,
+                                           id7_s,
+                                           id8,
+                                           id8_o,
+                                           id8_s,
+                                           1);
+  }
+
+extern "C"
+int
+__gg__unstring_24( const cblc_referlet_t *id2,
+                   const cblc_referlet_t *id4,
+                   const cblc_referlet_t *id5,
+                   const cblc_referlet_t *id6,
+                   const cblc_field_t *id1,
+                   size_t              id1_o,
+                   size_t              id1_s,
+                   size_t              ndelimiteds,
+                   const char         *all_flags,
+                   size_t              nreceivers,
+                   cblc_field_t       *id7,
+                   size_t              id7_o,
+                   size_t              id7_s,
+                   cblc_field_t       *id8,
+                   size_t              id8_o,
+                   size_t              id8_s)
+  {
+  size_t stride = static_cast<size_t>(
+                         __gg__get_charmap(id1->encoding)->stride());
+  return unstring_fixed<unstring_finder_24>(id2,
+                                            id4,
+                                            id5,
+                                            id6,
+                                            id1,
+                                            id1_o,
+                                            id1_s,
+                                            ndelimiteds,
+                                            all_flags,
+                                            nreceivers,
+                                            id7,
+                                            id7_o,
+                                            id7_s,
+                                            id8,
+                                            id8_o,
+                                            id8_s,
+                                            stride);
+  }
+
+extern "C"
+int
+__gg__unstring( const cblc_referlet_t *id2,
+                const cblc_referlet_t *id4,
+                const cblc_referlet_t *id5,
+                const cblc_referlet_t *id6,
+                const cblc_field_t *id1,
+                size_t              id1_o,
+                size_t              id1_s,
+                size_t              ndelimiteds,
+                const char         *all_flags,
+                size_t              nreceivers,
+                cblc_field_t       *id7,
+                size_t              id7_o,
+                size_t              id7_s,
+                cblc_field_t       *id8,
+                size_t              id8_o,
+                size_t              id8_s)
+  {
+  const charmap_t *charmap = __gg__get_charmap(id1->encoding);
+  if( charmap->stride() == 1 )
+    {
+    return __gg__unstring_1(id2,
+                            id4,
+                            id5,
+                            id6,
+                            id1,
+                            id1_o,
+                            id1_s,
+                            ndelimiteds,
+                            all_flags,
+                            nreceivers,
+                            id7,
+                            id7_o,
+                            id7_s,
+                            id8,
+                            id8_o,
+                            id8_s);
+    }
+  return __gg__unstring_24(id2,
+                           id4,
+                           id5,
+                           id6,
+                           id1,
+                           id1_o,
+                           id1_s,
+                           ndelimiteds,
+                           all_flags,
+                           nreceivers,
+                           id7,
+                           id7_o,
+                           id7_s,
+                           id8,
+                           id8_o,
+                           id8_s);
+  }

Reply via email to