+
+/* System file writer casewriter class. */
+static const struct casewriter_class sys_file_casewriter_class =
+ {
+ sys_file_casewriter_write,
+ sys_file_casewriter_destroy,
+ NULL,
+ };
+\f
+/* Writes case C to system file W, without compressing it. */
+static void
+write_case_uncompressed (struct sfm_writer *w, const struct ccase *c)
+{
+ size_t i;
+
+ for (i = 0; i < w->sfm_var_cnt; i++)
+ {
+ struct sfm_var *v = &w->sfm_vars[i];
+
+ if (v->var_width == 0)
+ write_float (w, case_num_idx (c, v->case_index));
+ else
+ {
+ write_bytes (w, case_str_idx (c, v->case_index) + v->offset,
+ v->segment_width);
+ write_spaces (w, v->padding);
+ }
+ }
+}
+
+/* Writes case C to system file W, with compression. */
+static void
+write_case_compressed (struct sfm_writer *w, const struct ccase *c)
+{
+ size_t i;
+
+ for (i = 0; i < w->sfm_var_cnt; i++)
+ {
+ struct sfm_var *v = &w->sfm_vars[i];
+
+ if (v->var_width == 0)
+ {
+ double d = case_num_idx (c, v->case_index);
+ if (d == SYSMIS)
+ put_cmp_opcode (w, 255);
+ else if (d >= 1 - COMPRESSION_BIAS
+ && d <= 251 - COMPRESSION_BIAS
+ && d == (int) d)
+ put_cmp_opcode (w, (int) d + COMPRESSION_BIAS);
+ else
+ {
+ put_cmp_opcode (w, 253);
+ put_cmp_number (w, d);
+ }
+ }
+ else
+ {
+ int offset = v->offset;
+ int width, padding;
+
+ /* This code properly deals with a width that is not a
+ multiple of 8, by ensuring that the final partial
+ oct (8 byte unit) is treated as padded with spaces
+ on the right. */
+ for (width = v->segment_width; width > 0; width -= 8, offset += 8)
+ {
+ const void *data = case_str_idx (c, v->case_index) + offset;
+ int chunk_size = MIN (width, 8);
+ if (!memcmp (data, " ", chunk_size))
+ put_cmp_opcode (w, 254);
+ else
+ {
+ put_cmp_opcode (w, 253);
+ put_cmp_string (w, data, chunk_size);
+ }
+ }
+
+ /* This code deals properly with padding that is not a
+ multiple of 8 bytes, by discarding the remainder,
+ which was already effectively padded with spaces in
+ the previous loop. (Note that v->width + v->padding
+ is always a multiple of 8.) */
+ for (padding = v->padding / 8; padding > 0; padding--)
+ put_cmp_opcode (w, 254);
+ }
+ }
+}
+
+/* Flushes buffered compressed opcodes and data to W.
+ The compression buffer must not be empty. */
+static void
+flush_compressed (struct sfm_writer *w)
+{
+ assert (w->opcode_cnt > 0 && w->opcode_cnt <= 8);
+
+ write_bytes (w, w->opcodes, w->opcode_cnt);
+ write_zeros (w, 8 - w->opcode_cnt);
+
+ write_bytes (w, w->data, w->data_cnt * sizeof *w->data);
+
+ w->opcode_cnt = w->data_cnt = 0;
+}
+
+/* Appends OPCODE to the buffered set of compression opcodes in
+ W. Flushes the compression buffer beforehand if necessary. */
+static void
+put_cmp_opcode (struct sfm_writer *w, uint8_t opcode)
+{
+ if (w->opcode_cnt >= 8)
+ flush_compressed (w);
+
+ w->opcodes[w->opcode_cnt++] = opcode;
+}
+
+/* Appends NUMBER to the buffered compression data in W. The
+ buffer must not be full; the way to assure that is to call
+ this function only just after a call to put_cmp_opcode, which
+ will flush the buffer as necessary. */
+static void
+put_cmp_number (struct sfm_writer *w, double number)
+{
+ assert (w->opcode_cnt > 0);
+ assert (w->data_cnt < 8);
+
+ convert_double_to_output_format (number, w->data[w->data_cnt++]);
+}
+
+/* Appends SIZE bytes of DATA to the buffered compression data in
+ W, followed by enough spaces to pad the output data to exactly
+ 8 bytes (thus, SIZE must be no greater than 8). The buffer
+ must not be full; the way to assure that is to call this
+ function only just after a call to put_cmp_opcode, which will
+ flush the buffer as necessary. */
+static void
+put_cmp_string (struct sfm_writer *w, const void *data, size_t size)
+{
+ assert (w->opcode_cnt > 0);
+ assert (w->data_cnt < 8);
+ assert (size <= 8);
+
+ memset (w->data[w->data_cnt], ' ', 8);
+ memcpy (w->data[w->data_cnt], data, size);
+ w->data_cnt++;
+}
+\f
+/* Writes 32-bit integer X to the output file for writer W. */
+static void
+write_int (struct sfm_writer *w, int32_t x)
+{
+ write_bytes (w, &x, sizeof x);
+}
+
+/* Converts NATIVE to the 64-bit format used in output files in
+ OUTPUT. */
+static inline void
+convert_double_to_output_format (double native, uint8_t output[8])
+{
+ /* If "double" is not a 64-bit type, then convert it to a
+ 64-bit type. Otherwise just copy it. */
+ if (FLOAT_NATIVE_DOUBLE != FLOAT_NATIVE_64_BIT)
+ float_convert (FLOAT_NATIVE_DOUBLE, &native, FLOAT_NATIVE_64_BIT, output);
+ else
+ memcpy (output, &native, sizeof native);
+}
+
+/* Writes floating-point number X to the output file for writer
+ W. */
+static void
+write_float (struct sfm_writer *w, double x)
+{
+ uint8_t output[8];
+ convert_double_to_output_format (x, output);
+ write_bytes (w, output, sizeof output);
+}
+
+/* Writes contents of VALUE with the given WIDTH to W, padding
+ with zeros to a multiple of 8 bytes.
+ To avoid a branch, and because we don't actually need to
+ support it, WIDTH must be no bigger than 8. */
+static void
+write_value (struct sfm_writer *w, const union value *value, int width)
+{
+ assert (width <= 8);
+ if (width == 0)
+ write_float (w, value->f);
+ else
+ {
+ write_bytes (w, value_str (value, width), width);
+ write_zeros (w, 8 - width);
+ }
+}
+
+/* Writes null-terminated STRING in a field of the given WIDTH to
+ W. If STRING is longer than WIDTH, it is truncated; if WIDTH
+ is narrowed, it is padded on the right with spaces. */
+static void
+write_string (struct sfm_writer *w, const char *string, size_t width)
+{
+ size_t data_bytes = MIN (strlen (string), width);
+ size_t pad_bytes = width - data_bytes;
+ write_bytes (w, string, data_bytes);
+ while (pad_bytes-- > 0)
+ putc (' ', w->file);
+}
+
+/* Writes SIZE bytes of DATA to W's output file. */
+static void
+write_bytes (struct sfm_writer *w, const void *data, size_t size)
+{
+ fwrite (data, 1, size, w->file);
+}
+
+/* Writes N zeros to W's output file. */
+static void
+write_zeros (struct sfm_writer *w, size_t n)
+{
+ while (n-- > 0)
+ putc (0, w->file);
+}
+
+/* Writes N spaces to W's output file. */
+static void
+write_spaces (struct sfm_writer *w, size_t n)
+{
+ while (n-- > 0)
+ putc (' ', w->file);
+}