]> Kevux Git Server - fll/commitdiff
Progress: Begin fixing combining logic. 0.8-combining
authorKevin Day <Kevin@kevux.org>
Mon, 1 Jun 2026 01:36:43 +0000 (20:36 -0500)
committerKevin Day <Kevin@kevux.org>
Mon, 1 Jun 2026 01:36:43 +0000 (20:36 -0500)
Have to add is_combining functions to detect combining for the purpose of FSS processing.

Skip past initial place holders in the skip functions.

The initial combining returns `F_complete_not_utf_start` only for combining at the start.
This should be then handled as a combining.

Begin adding the unit tests.
These should fail because the functionality that they are testing is not implemented.

What needs to be done next is to have all FSS processing code look past the special characters (except new lines).
Check for combining characters.

31 files changed:
build/disable/level_0/f_fss.h
build/stand_alone/fake.config.h
build/stand_alone/fss_identify.config.h
build/stand_alone/fss_read.config.fss_basic.h
build/stand_alone/fss_read.config.fss_basic_list.h
build/stand_alone/fss_read.config.fss_embedded_list.h
build/stand_alone/fss_read.config.fss_extended.h
build/stand_alone/fss_read.config.fss_extended_list.h
build/stand_alone/fss_read.config.fss_payload.h
build/stand_alone/fss_read.config.h
build/stand_alone/fss_write.config.fss_basic.h
build/stand_alone/fss_write.config.fss_basic_list.h
build/stand_alone/fss_write.config.fss_embedded_list.h
build/stand_alone/fss_write.config.fss_extended.h
build/stand_alone/fss_write.config.fss_extended_list.h
build/stand_alone/fss_write.config.fss_payload.h
build/stand_alone/fss_write.config.h
build/stand_alone/status_code.config.fss.h
level_0/f_fss/c/fss.c
level_0/f_fss/c/fss.h
level_1/fl_fss/c/private-fss.c
level_1/fl_fss/c/private-fss.h
level_1/fl_fss/data/tests/contents/basic-combining_read.txt [new file with mode: 0644]
level_1/fl_fss/data/tests/matches/basic-combining_read.txt [new file with mode: 0644]
level_1/fl_fss/data/tests/objects/basic-combining_read.txt [new file with mode: 0644]
level_1/fl_fss/data/tests/strings/basic-combining_read.txt [new file with mode: 0644]
level_1/fl_fss/tests/unit/c/test-fss-basic_content_read.c
level_1/fl_fss/tests/unit/c/test-fss-basic_content_read.h
level_1/fl_fss/tests/unit/c/test-fss-basic_object_read.c
level_1/fl_fss/tests/unit/c/test-fss-basic_object_read.h
level_1/fl_fss/tests/unit/c/test-fss.c

index 14f5006c963b025c84e0e41dd2046c5af60e0dc4..85066c34a5ebf87308d218cbae339ab5eafc0822 100644 (file)
@@ -99,6 +99,7 @@
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+#define _di_f_fss_skip_past_combining_
 #define _di_f_fss_skip_past_delimit_
 #define _di_f_fss_skip_past_space_
 #define _di_f_fss_state_flag_e_
index 5ce2c9a31d3eec70388537417b5efeb678369bfa..b46bdac3b660e723979d1c63e12b853a3c392d02 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 88702454b445746524de30fd31b0c0d8e82972b2..3f51736981554ac1eaa2eff5165050931416fb17 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+#define _di_f_fss_skip_past_combining_
 #define _di_f_fss_skip_past_delimit_
 #define _di_f_fss_skip_past_space_
 #define _di_f_fss_state_flag_e_
index 7b1627bab618921378c961f687c6b828679882d1..9e124ed1a14610f8373465826e929b51647444ea 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 6a2ee483aebde28fe70594d44db5a3a42ac29b25..bd7f0fc7e698eda5801a6450acde62c282c9e590 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index d4c26f7a94ad6ddab0e2ba31d2730430e84e733d..3d38aca46018d517d70adf285f9a431e7720a347 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 86f6837d7fb97608d76baa749a882cb73df4a99a..b239ec1f7dd96665af2b93947b748ff5815b62fd 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index d0039047ef03dac4b50571831812f1e8420c112d..a8335c2ed7205537d10b1915fcf16066d6170127 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 11f17554860987bc24820b43649a15a9f3161370..42a5cc82d17ec6c1e0523b7de0a0ff8a6a20c87c 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index f50d6f85389c46d50b6897e75dc3967a620cd644..56bdc1fb603cb72f132e667eab61979d34c5d109 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 26846825aabd21856c3324c8e38d885f771b72a9..db7d584e0e59431b1cbffc43049620d29a0e3871 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 40fb24fa97fdf2b47bb39051a78c47f8e3c019e6..f598c2f943f5af10f1a44b43f7d167f08675b825 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 86419a0ddf2ab9657bc69559fb78bcde1f4cde72..3dfc7f0637402de3e492fbfbc95866aa4ba36382 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 5a56a2293658c77a33e508f796e342245bcfb309..53d812ab2f6d85f9069e727a44efdd7d5ff5b8f6 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 41088a8767cf4b3218e37aa7f676d8cc17176740..62643ec1fa89e417eaa6093cc1784d80955bc8ce 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 6bdfa83bda17d7494b00c09f2db15c762e0dff5d..f00bd1808d23d9e9f5bd52f2cb58f5e1567353f0 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index ed889622e235b6dea20a857816540268f62b4779..b0bcd7b487499e7671a248643ee6f66c690e6ccd 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+//#define _di_f_fss_skip_past_combining_
 //#define _di_f_fss_skip_past_delimit_
 //#define _di_f_fss_skip_past_space_
 //#define _di_f_fss_state_flag_e_
index 7094cfeab609f378c1ad641fcc29a70a49a1a929..570caf4fa1af8d001835fe9f0c6d69275ebb8dc2 100644 (file)
 #define _di_f_fss_simple_packetss_delete_callback_
 #define _di_f_fss_simple_packetss_destroy_callback_
 #define _di_f_fss_simple_packetss_t_
+#define _di_f_fss_skip_past_combining_
 #define _di_f_fss_skip_past_delimit_
 #define _di_f_fss_skip_past_space_
 #define _di_f_fss_state_flag_e_
index 3bf7cda935abfa568d4a80dcf6c5b68e5e097cf0..df7bff748a6e2f690dcbb839753827b42863ce40 100644 (file)
@@ -311,6 +311,127 @@ extern "C" {
   }
 #endif // _di_f_fss_seek_to_eol_
 
+#ifndef _di_f_fss_skip_past_combining_
+  void f_fss_skip_past_combining(const f_string_static_t buffer, f_range_t * const range, f_state_t * const state) {
+    #ifndef _di_level_0_parameter_checking_
+      if (!state) return;
+
+      if (!range) {
+        state->status = F_status_set_error(F_parameter);
+
+        return;
+      }
+    #endif // _di_level_0_parameter_checking_
+
+    if (!buffer.used || range->start > range->stop || range->start >= buffer.used) {
+      state->status = F_data_not;
+
+      return;
+    }
+
+    // Skip past initial place holders.
+    for (;; ++range->start) {
+
+      if (range->start >= buffer.used) {
+        state->status = F_okay_eos;
+
+        return;
+      }
+
+      if (range->start > range->stop) {
+        state->status = F_okay_stop;
+
+        return;
+      }
+
+      if (buffer.string[range->start] != f_fss_placeholder_s.string[0]) break;
+    } // for
+
+    uint8_t width = 0;
+    f_number_unsigned_t width_max = (range->stop - range->start) + 1;
+
+    if (width_max > buffer.used - range->start) {
+      width_max = buffer.used - range->start;
+    }
+
+    // Check that the first character is not a combining character.
+    state->status = f_utf_is_combining(buffer.string + range->start, width_max);
+
+    f_fss_fail_utf_to_false(state);
+    if (F_status_is_error(state->status)) return;
+
+    if (state->status == F_true) {
+      state->status = F_is_not;
+
+      return;
+    }
+
+    for (;;) {
+
+      width = macro_f_utf_byte_width(buffer.string[range->start]);
+
+      if (width > 1) {
+        if (range->start + width >= buffer.used) {
+          state->status = F_status_set_error(F_complete_not_utf_eos);
+
+          f_fss_fail_utf(state);
+
+          return;
+        }
+
+        if (range->start + width > range->stop) {
+          state->status = F_status_set_error(F_complete_not_utf_stop);
+
+          f_fss_fail_utf(state);
+
+          return;
+        }
+      }
+
+      range->start += width;
+
+      if (range->start >= buffer.used) {
+        state->status = F_okay_eos;
+
+        return;
+      }
+
+      if (range->start > range->stop) {
+        state->status = F_okay_stop;
+
+        return;
+      }
+
+      width_max = (range->stop - range->start) + 1;
+
+      if (width_max > buffer.used - range->start) {
+        width_max = buffer.used - range->start;
+      }
+
+      if (buffer.string[range->start] == f_fss_eol_s.string[0]) {
+        state->status = F_okay_eol;
+
+        return;
+      }
+
+      if (buffer.string[range->start] == f_fss_placeholder_s.string[0]) {
+        ++range->start;
+
+        continue;
+      }
+
+      state->status = f_utf_is_combining(buffer.string + range->start, width_max);
+
+      f_fss_fail_utf_to_false(state);
+
+      if (state->status == F_false) break;
+      if (F_status_is_error(state->status)) return;
+    } // for
+
+    state->status = F_okay;
+  }
+#endif // _di_f_fss_skip_past_combining_
+
 #ifndef _di_f_fss_skip_past_delimit_
   void f_fss_skip_past_delimit(const f_string_static_t buffer, f_range_t * const range, f_state_t * const state) {
     #ifndef _di_level_0_parameter_checking_
@@ -368,7 +489,26 @@ extern "C" {
       return;
     }
 
+    // Skip past initial place holders.
+    for (;; ++range->start) {
+
+      if (range->start >= buffer.used) {
+        state->status = F_okay_eos;
+
+        return;
+      }
+
+      if (range->start > range->stop) {
+        state->status = F_okay_stop;
+
+        return;
+      }
+
+      if (buffer.string[range->start] != f_fss_placeholder_s.string[0]) break;
+    } // for
+
     uint8_t width = 0;
+    f_number_unsigned_t previous = range->start;
     f_number_unsigned_t width_max = (range->stop - range->start) + 1;
 
     if (width_max > buffer.used - range->start) {
@@ -427,11 +567,22 @@ extern "C" {
             f_fss_fail_utf_to_false(state);
             if (state->status == F_false) break;
           }
+          else {
+            // This is combining, so any previous white space is no longer considered white space as such.
+            range->start = previous;
+
+            // Ensure previous is not a place holder.
+            while (range->start && buffer.string[range->start] == f_fss_placeholder_s.string[0]) --range->start;
+
+            break;
+          }
         }
       }
 
       if (F_status_is_error(state->status)) return;
 
+      previous = range->start;
+
       width = macro_f_utf_byte_width(buffer.string[range->start]);
 
       if (width > 1) {
index a4f233d288c656309f7383e9c677f848f9e38f0e..b668ad01b67f8897b62c6cd96406c0eb7f6e23e9 100644 (file)
@@ -340,6 +340,44 @@ extern "C" {
   extern void f_fss_seek_to_eol(const f_string_dynamic_t buffer, f_range_t * const range, f_state_t * const state);
 #endif // _di_f_fss_seek_to_eol_
 
+/**
+ * Skip past all combining characters.
+ *
+ * This will do nothing if the first character is not a combining character.
+ *
+ * This skips past place holders.
+ *
+ * This stops on end of line.
+ *
+ * @param buffer
+ *   The string to process.
+ * @param range
+ *   The start and stop positions in the buffer being processed.
+ *   This increments range->start.
+ * @param state
+ *   A state for providing flags and handling interrupts during long running operations.
+ *
+ *   This alters state.status:
+ *     F_okay on success.
+ *     F_data_not on success but buffer.used is 0, initial range.start is greater than range.stop, or initial range.start is greater than or equal to buffer.used.
+ *     F_is_not on success, but the first character is not a combining character.
+ *     F_okay_eol on success and EOL was reached.
+ *     F_okay_eos on success and EOS was reached.
+ *     F_okay_stop on success and stop point was reached.
+ *
+ *     F_complete_not_utf_eos (with error bit) if unable to get entire UTF-8 sequence due to EOS.
+ *     F_complete_not_utf_start (with error bit) if the first character is a combining character.
+ *     F_complete_not_utf_stop (with error bit) if unable to get entire UTF-8 sequence due to stop point reached.
+ *     F_parameter (with error bit) if a parameter is invalid.
+ *
+ *     Errors (with error bit) from: f_utf_is_combining().
+ *
+ * @see f_utf_is_combining()
+ */
+#ifndef _di_f_fss_skip_past_combining_
+  extern void f_fss_skip_past_combining(const f_string_static_t buffer, f_range_t * const range, f_state_t * const state);
+#endif // _di_f_fss_skip_past_combining_
+
 /**
  * Skip past all delimit placeholders until a non-delimit placeholder is reached.
  *
@@ -369,6 +407,13 @@ extern "C" {
  * If the first character in the given range is a combining character, then this will not skip past anything.
  * This is because combining characters apply from right to left.
  *
+ * White space is checked for combining characters after the white space.
+ * If there is a combining character after, then the white space is not consider as such.
+ *
+ * This skips past place holders.
+ *
+ * This stops on end of line.
+ *
  * @param buffer
  *   The string to process.
  * @param range
index 3333da780077b4559a9de0b70dbd43e541656679..f3c72e997b902860893eac4736901d3208680f28 100644 (file)
@@ -9,7 +9,14 @@ extern "C" {
   void private_fl_fss_basic_or_extended_read(const f_string_static_t buffer, const uint8_t flag, f_range_t * const range, f_range_t * const found, uint8_t * const quote, f_number_unsigneds_t * const delimits, f_state_t * const state) {
 
     f_fss_skip_past_space(buffer, range, state);
-    if (F_status_is_error(state->status) || state->status == F_data_not) return;
+
+    if (F_status_is_error(state->status) || state->status == F_data_not) {
+      if (F_status_set_fine(state->status) == F_complete_not_utf_start) {
+        f_fss_skip_past_combining(buffer, range, state);
+        if (F_status_is_error(state->status) || state->status == F_data_not) return;
+      }
+      else return;
+    }
 
     if (state->status == F_okay_eol) {
       // Move the start position to after the EOL.
@@ -630,7 +637,14 @@ extern "C" {
   void private_fl_fss_basic_write(const uint8_t flag, const f_string_static_t object, const uint8_t quote, f_range_t * const range, f_string_dynamic_t * const destination, f_state_t * const state, void * const internal) {
 
     f_fss_skip_past_space(object, range, state);
-    if (F_status_is_error(state->status) || state->status == F_data_not) return;
+
+    if (F_status_is_error(state->status) || state->status == F_data_not) {
+      if (F_status_set_fine(state->status) == F_complete_not_utf_start) {
+        f_fss_skip_past_combining(object, range, state);
+        if (F_status_is_error(state->status) || state->status == F_data_not) return;
+      }
+      else return;
+    }
 
     if (state->status == F_okay_eos) {
       state->status = F_data_not_eos;
index 639070dbcaa146af62478db8cc10d792bf817fc9..3efe1c0e96ba784f6f14dc76126b820a1e9c3bca 100644 (file)
@@ -73,6 +73,7 @@ extern "C" {
  *     Errors (with error bit) from: f_fss_is_graph().
  *     Errors (with error bit) from: f_fss_is_space().
  *     Errors (with error bit) from: f_fss_is_zero_width().
+ *     Errors (with error bit) from: f_fss_skip_past_combining().
  *     Errors (with error bit) from: f_fss_skip_past_delimit().
  *     Errors (with error bit) from: f_fss_skip_past_space().
  *     Errors (with error bit) from: f_utf_buffer_increment().
@@ -82,6 +83,7 @@ extern "C" {
  * @see f_fss_is_graph()
  * @see f_fss_is_space()
  * @see f_fss_is_zero_width()
+ * @see f_fss_skip_past_combining()
  * @see f_fss_skip_past_delimit()
  * @see f_fss_skip_past_space()
  * @see f_utf_buffer_increment()
@@ -139,6 +141,7 @@ extern "C" {
  *     F_parameter (with error bit) if a parameter is invalid.
  *
  *     Errors (with error bit) from: f_fss_is_space().
+ *     Errors (with error bit) from: f_fss_skip_past_combining().
  *     Errors (with error bit) from: f_fss_skip_past_delimit().
  *     Errors (with error bit) from: f_fss_skip_past_space().
  *     Errors (with error bit) from: f_memory_array_increase().
@@ -150,6 +153,7 @@ extern "C" {
  *   Set to NULL to not use.
  *
  * @see f_fss_is_space()
+ * @see f_fss_skip_past_combining()
  * @see f_fss_skip_past_delimit()
  * @see f_fss_skip_past_space()
  * @see f_memory_array_increase()
diff --git a/level_1/fl_fss/data/tests/contents/basic-combining_read.txt b/level_1/fl_fss/data/tests/contents/basic-combining_read.txt
new file mode 100644 (file)
index 0000000..bbec7e7
--- /dev/null
@@ -0,0 +1,6 @@
+valid.
+and does not have embedded new line.
+
+
+is valid.
+is valid with two combiners.
diff --git a/level_1/fl_fss/data/tests/matches/basic-combining_read.txt b/level_1/fl_fss/data/tests/matches/basic-combining_read.txt
new file mode 100644 (file)
index 0000000..8a23f1a
--- /dev/null
@@ -0,0 +1,6 @@
+2
+2
+1
+2
+2
+2
diff --git a/level_1/fl_fss/data/tests/objects/basic-combining_read.txt b/level_1/fl_fss/data/tests/objects/basic-combining_read.txt
new file mode 100644 (file)
index 0000000..839e3bf
--- /dev/null
@@ -0,0 +1,6 @@
+first
+is_second
+fourth ͜no_space
+fifth
+sixtẖ̚
diff --git a/level_1/fl_fss/data/tests/strings/basic-combining_read.txt b/level_1/fl_fss/data/tests/strings/basic-combining_read.txt
new file mode 100644 (file)
index 0000000..fe24da3
--- /dev/null
@@ -0,0 +1,6 @@
+first valid.
+is_second and does not have embedded new line.
+fourth ͜no_space
+fifth is valid.
+sixtẖ̚ is valid with two combiners.
index 20bba296d3127acc497dcdd05dafed4340ee81d8..c71db0cd7860a9aacf774a308148210fd381c521 100644 (file)
@@ -216,6 +216,213 @@ void test__fl_fss_basic_content_read__works(void **void_state) {
   }
 }
 
+void test__fl_fss_basic_content_read__works_using_combining(void **void_state) {
+
+  {
+    // Note: These files are required to have the same number of lines and each line should probably be at max 255 characters.
+    FILE *file_contents = data__file_open__named("contents", "basic", "combining_read");
+    FILE *file_matches = data__file_open__named("matches", "basic", "combining_read");
+    FILE *file_strings = data__file_open__named("strings", "basic", "combining_read");
+
+    assert_non_null(file_contents);
+    assert_non_null(file_matches);
+    assert_non_null(file_strings);
+
+    int matches = 0;
+    size_t max = 0;
+    char *line_string = 0;
+    char *line_content = 0;
+    char *line_matches = 0;
+    ssize_t result = 0;
+
+    f_string_static_t buffer_string = f_string_static_t_initialize;
+
+    f_state_t state = f_state_t_initialize;
+    f_range_t range = f_range_t_initialize;
+    f_range_t found_object = f_range_t_initialize;
+    f_ranges_t found = f_ranges_t_initialize;
+    uint8_t quote = 0;
+    f_number_unsigneds_t delimits = f_number_unsigneds_t_initialize;
+    f_string_dynamic_t result_string = f_string_dynamic_t_initialize;
+    f_string_dynamic_t delimit_string = f_string_dynamic_t_initialize;
+    f_status_t status_object = F_okay;
+
+    for (;;) {
+
+      max = 255;
+
+      result = getline(&line_matches, &max, file_matches);
+      assert_return_code(result, 0);
+
+      matches = atoi(line_matches);
+
+      max = 255;
+
+      result = getline(&line_string, &max, file_strings);
+      if (result == -1) break;
+
+      buffer_string.string = line_string;
+      buffer_string.used = (f_number_unsigned_t) result;
+      buffer_string.size = buffer_string.used;
+
+      max = 255;
+
+      result = getline(&line_content, &max, file_contents);
+      assert_return_code(result, 0);
+
+      // The newline is copied by getline(), and so remove that newline before comparing.
+      line_content[result - 1] = 0;
+
+      state.status = F_none;
+      range.start = 0;
+      range.stop = buffer_string.used - 1;
+      found_object.start = 1;
+      found_object.stop = 0;
+
+      fl_fss_basic_object_read(buffer_string, &range, &found_object, &quote, &delimits, &state);
+
+      if (matches) {
+        // When matches is 2, then this matches both object and content.
+        if (matches == 2) {
+          if (!(state.status == F_fss_found_object)) {
+            printf("[ -------> ] --- [00] Failure with line '%s', matches=%d, status=%u.\n", line_content, matches, state.status);
+          }
+
+          assert_true(state.status == F_fss_found_object);
+        }
+
+        // When matches is 1, then this matches only object.
+        else {
+          if (!(state.status == F_fss_found_object_content_not)) {
+            printf("[ -------> ] --- [02] Failure with line '%s', matches=%d, status=%u.\n", line_content, matches, state.status);
+          }
+
+          assert_true(state.status == F_fss_found_object_content_not);
+        }
+      }
+      else {
+
+        // This is currently not supported.
+        assert_true(F_false);
+      }
+
+      status_object = state.status;
+      state.status = F_none;
+
+      fl_fss_basic_content_read(buffer_string, &range, &found, &delimits, &state);
+
+      if (status_object == F_fss_found_object) {
+        if (!(status_object == F_fss_found_object)) {
+          printf("[ -------> ] --- [02] Failure with line '%s'.\n", line_content);
+        }
+
+        assert_int_equal(state.status, F_fss_found_content);
+      }
+      else {
+        if (state.status != F_data_not) {
+          printf("[ -------> ] --- [03] Failure with line '%s'.\n", line_content);
+        }
+
+        assert_int_equal(state.status, F_data_not);
+      }
+
+      if (matches == 2) {
+        if (state.status == F_fss_found_content) {
+          if (!found.used) {
+            printf("[ -------> ] --- [04] Failure with line '%s'.\n", line_content);
+          }
+
+          assert_true(found.used);
+
+          {
+            const f_status_t status = f_string_dynamic_append(buffer_string, &delimit_string);
+
+            if (status != F_okay) {
+              printf("[ -------> ] --- [05] Failure with line '%s'.\n", line_content);
+            }
+
+            assert_int_equal(status, F_okay);
+          }
+
+          state.status = F_none;
+
+          f_fss_apply_delimit(delimits, &delimit_string, &state);
+          assert_int_equal(state.status, F_okay);
+
+          {
+            const f_status_t status = f_string_dynamic_partial_append_nulless(delimit_string, found.array[0], &result_string);
+
+            if (!(status == F_okay || status == F_data_not_eos)) {
+              printf("[ -------> ] --- [06] Failure with line '%s', status=%u.\n", line_content, status);
+            }
+
+            assert_true(status == F_okay || status == F_data_not_eos);
+          }
+
+          {
+            const f_status_t status = f_string_dynamic_terminate_after(&result_string);
+
+            if (status != F_okay) {
+              printf("[ -------> ] --- [07] Failure with line '%s'.\n", line_content);
+            }
+
+            assert_int_equal(status, F_okay);
+          }
+
+          if (strcmp((const char *) result_string.string, (const char *) line_content)) {
+            printf("[ -------> ] --- [08] Failure with line '%s'; got '%s', expected '%s'.\n", line_content, result_string.string, line_content);
+          }
+
+          assert_string_equal(result_string.string, line_content);
+        }
+        else {
+          if (found.used) {
+            printf("[ -------> ] --- [09] Failure with line '%s'.\n", line_content);
+          }
+
+          assert_true(!found.used);
+        }
+      }
+
+      if (line_string) free(line_string);
+      if (line_content) free(line_content);
+      if (line_matches) free(line_matches);
+      if (result_string.string) free(result_string.string);
+      if (delimit_string.string) free(delimit_string.string);
+      if (delimits.array) free(delimits.array);
+      if (found.array) free(found.array);
+
+      line_string = 0;
+      line_content = 0;
+      line_matches = 0;
+      result_string.string = 0;
+      result_string.used = 0;
+      result_string.size = 0;
+      delimit_string.string = 0;
+      delimit_string.used = 0;
+      delimit_string.size = 0;
+      delimits.array = 0;
+      delimits.used = 0;
+      delimits.size = 0;
+      found.array = 0;
+      found.used = 0;
+      found.size = 0;
+    } // for
+
+    if (file_strings) fclose(file_strings);
+    if (file_contents) fclose(file_contents);
+    if (file_matches) fclose(file_matches);
+
+    if (delimits.array) free(delimits.array);
+    if (found.array) free(found.array);
+    if (line_string) free(line_string);
+    if (line_content) free(line_content);
+    if (line_matches) free(line_matches);
+    if (result_string.string) free(result_string.string);
+    if (delimit_string.string) free(delimit_string.string);
+  }
+}
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
index 6eb08e0c593fa19364e71590f56058ee849a6f66..f648b64a705c98788430eb0b77d05d5ce32a8c46 100644 (file)
@@ -31,4 +31,11 @@ extern void test__fl_fss_basic_content_read__returns_data_not(void **state);
  */
 extern void test__fl_fss_basic_content_read__works(void **state);
 
+/**
+ * Test that the function works when using combining characters.
+ *
+ * @see fl_fss_basic_content_read()
+ */
+extern void test__fl_fss_basic_content_read__works_using_combining(void **state);
+
 #endif // _TEST__FL_fss_basic_content_read_h
index 2fd4e35e450e87a5576148dae93654ae9b4f62d2..633ac7af46aab6c1750abd9c294659f25e897a71 100644 (file)
@@ -241,6 +241,181 @@ void test__fl_fss_basic_object_read__works(void **void_state) {
   }
 }
 
+void test__fl_fss_basic_object_read__works_using_combining(void **void_state) {
+
+  {
+    // Note: These files are required to have the same number of lines and each line should probably be at max 255 characters.
+    FILE *file_matches = data__file_open__named("matches", "basic", "combining_read");
+    FILE *file_objects = data__file_open__named("objects", "basic", "combining_read");
+    FILE *file_strings = data__file_open__named("strings", "basic", "combining_read");
+
+    assert_non_null(file_matches);
+    assert_non_null(file_objects);
+    assert_non_null(file_strings);
+
+    int matches = 0;
+    size_t max = 0;
+    char *line_matches = 0;
+    char *line_object = 0;
+    char *line_string = 0;
+    ssize_t result = 0;
+
+    f_string_static_t buffer_string = f_string_static_t_initialize;
+
+    f_state_t state = f_state_t_initialize;
+    f_range_t range = f_range_t_initialize;
+    f_range_t found = f_range_t_initialize;
+    uint8_t quote = 0;
+    f_number_unsigneds_t delimits = f_number_unsigneds_t_initialize;
+    f_string_dynamic_t result_string = f_string_dynamic_t_initialize;
+    f_string_dynamic_t delimit_string = f_string_dynamic_t_initialize;
+
+    for (;;) {
+
+      max = 255;
+
+      result = getline(&line_matches, &max, file_matches);
+      assert_return_code(result, 0);
+
+      matches = atoi(line_matches);
+
+      max = 255;
+
+      result = getline(&line_string, &max, file_strings);
+      if (result == -1) break;
+
+      buffer_string.string = line_string;
+      buffer_string.used = (f_number_unsigned_t) result;
+      buffer_string.size = buffer_string.used;
+
+      max = 255;
+
+      result = getline(&line_object, &max, file_objects);
+      assert_return_code(result, 0);
+
+      // The newline is copied by getline(), and so remove that newline before comparing.
+      line_object[result - 1] = 0;
+
+      state.status = F_none;
+      range.start = 0;
+      range.stop = buffer_string.used - 1;
+      found.start = 1;
+      found.stop = 0;
+
+      fl_fss_basic_object_read(buffer_string, &range, &found, &quote, &delimits, &state);
+
+      if (matches) {
+        // When matches is 2, then this matches both object and content.
+        if (matches == 2) {
+          if (!(state.status == F_fss_found_object)) {
+            printf("[ -------> ] --- [00] Failure with line '%s', matches=%d, status=%u.\n", line_object, matches, state.status);
+          }
+
+          assert_true(state.status == F_fss_found_object);
+
+          if (!(found.start <= found.stop)) {
+            printf("[ -------> ] --- [01] Failure with line '%s', matches=%d, found=(%lu, %lu).\n", line_object, matches, found.start, found.stop);
+          }
+
+          assert_true(found.start <= found.stop);
+        }
+
+        // When matches is 1, then this matches only object.
+        else {
+          if (!(state.status == F_fss_found_object_content_not)) {
+            printf("[ -------> ] --- [02] Failure with line '%s', matches=%d, status=%u.\n", line_object, matches, state.status);
+          }
+
+          assert_true(state.status == F_fss_found_object_content_not);
+        }
+      }
+      else {
+
+        // This is currently not supported.
+        assert_true(F_false);
+      }
+
+
+      {
+        const f_status_t status = f_string_dynamic_append(buffer_string, &delimit_string);
+
+        if (status != F_okay) {
+          printf("[ -------> ] --- [03] Failure with line '%s'.\n", line_object);
+        }
+
+        assert_int_equal(status, F_okay);
+      }
+
+      state.status = F_none;
+
+      f_fss_apply_delimit(delimits, &delimit_string, &state);
+
+      if (state.status != F_okay) {
+        printf("[ -------> ] --- [04] Failure with line '%s'.\n", line_object);
+      }
+
+      assert_int_equal(state.status, F_okay);
+
+      {
+        const f_status_t status = f_string_dynamic_partial_append_nulless(delimit_string, found, &result_string);
+
+        if (!(status == F_okay || status == F_data_not_eos)) {
+          printf("[ -------> ] --- [05] Failure with line '%s', status=%u.\n", line_object, status);
+        }
+
+        assert_true(status == F_okay || status == F_data_not_eos);
+      }
+
+      {
+        const f_status_t status = f_string_dynamic_terminate_after(&result_string);
+
+        if (status != F_okay) {
+          printf("[ -------> ] --- [06] Failure with line '%s'.\n", line_object);
+        }
+
+        assert_int_equal(status, F_okay);
+      }
+
+      if (strcmp((const char *) result_string.string, (const char *) line_object)) {
+        printf("[ -------> ] --- [07] Failure with line '%s'.\n", line_object);
+      }
+
+      assert_string_equal(result_string.string, line_object);
+
+      if (line_matches) free(line_matches);
+      if (line_object) free(line_object);
+      if (line_string) free(line_string);
+      if (result_string.string) free(result_string.string);
+      if (delimit_string.string) free(delimit_string.string);
+      if (delimits.array) free(delimits.array);
+
+      line_matches = 0;
+      line_object = 0;
+      line_string = 0;
+      result_string.string = 0;
+      result_string.used = 0;
+      result_string.size = 0;
+      delimit_string.string = 0;
+      delimit_string.used = 0;
+      delimit_string.size = 0;
+      delimits.array = 0;
+      delimits.used = 0;
+      delimits.size = 0;
+    } // for
+
+    if (file_matches) fclose(file_matches);
+    if (file_objects) fclose(file_objects);
+    if (file_strings) fclose(file_strings);
+
+    if (delimits.array) free(delimits.array);
+    if (line_string) free(line_string);
+    if (line_object) free(line_object);
+    if (result_string.string) free(result_string.string);
+    if (delimit_string.string) free(delimit_string.string);
+  }
+}
+
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
index 33e6111611796a25a478ce43148d6c5aff4b850d..f82681597b29c91c877689255c6f997096f84252 100644 (file)
@@ -31,4 +31,11 @@ extern void test__fl_fss_basic_object_read__returns_data_not(void **state);
  */
 extern void test__fl_fss_basic_object_read__works(void **state);
 
+/**
+ * Test that the function works when using combining characters.
+ *
+ * @see fl_fss_basic_object_read()
+ */
+extern void test__fl_fss_basic_object_read__works_using_combining(void **state);
+
 #endif // _TEST__FL_fss_basic_object_read_h
index 4302d408d4ca487d5f91de968a24b91a75a3771e..c48cf3059cd26f85d651204f826c7b22823b851c 100644 (file)
@@ -25,7 +25,9 @@ int main(void) {
     cmocka_unit_test(test__fl_fss_basic_object_write__returns_data_not),
 
     cmocka_unit_test(test__fl_fss_basic_content_read__works),
+    cmocka_unit_test(test__fl_fss_basic_content_read__works_using_combining),
     cmocka_unit_test(test__fl_fss_basic_object_read__works),
+    cmocka_unit_test(test__fl_fss_basic_object_read__works_using_combining),
 
     cmocka_unit_test(test__fl_fss_basic_list_content_read__returns_data_not),
     cmocka_unit_test(test__fl_fss_basic_list_content_write__returns_data_not),