READ and WRITE are tested.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 7 Nov 2021 02:38:19 +0000 (19:38 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 7 Nov 2021 02:38:19 +0000 (19:38 -0700)
doc/matrices.texi
src/language/lexer/lexer.c
src/language/stats/matrix.c
src/libpspp/i18n.c
src/libpspp/i18n.h
src/libpspp/str.c
src/libpspp/str.h
tests/language/stats/matrix.at

index 5c03eb79c3748841d898cfb43aa298c434fe2595..4ecf0360e7b5456f9fb896b0a6ea35f75d7c15db 100644 (file)
@@ -2151,33 +2151,39 @@ addition to @code{FORMAT}, the optional @code{BY} specification on
 
 @itemize @bullet
 @item
-Without @code{BY} and @code{FORMAT}, the numbers in the text file are
-in @code{F} format separated by spaces or commas.  For @code{WRITE},
-@pspp{} uses as many digits of precision needed to represent the
-numbers in the matrix
+With neither @code{BY} nor @code{FORMAT}, the numbers in the text file
+are in @code{F} format separated by spaces or commas.  For
+@code{WRITE}, @pspp{} uses as many digits of precision as needed to
+accurately represent the numbers in the matrix.
 
 @item
-With @code{BY @i{width}}, the input area is divided into fixed-width
-fields with the given @i{width}.  The input area must be a multiple of
+@code{BY @i{width}} divides the input area into fixed-width fields
+with the given @i{width}.  The input area must be a multiple of
 @i{width} columns wide.  Numbers are read or written as
 @code{F@i{width}.0} format.
 
 @item
-With @code{FORMAT=@i{count}F}, the input area is divided into
-@i{count} equal-width fields per line.  The input area must be a
-multiple of @i{count} columns wide.  Another format type may be
-substituted for @code{F}.
+@code{FORMAT=@i{count}F} divides the input area into @i{count}
+equal-width fields per line.  The input area must be a multiple of
+@i{count} columns wide.  Another format type may be substituted for
+@code{F}.
 
 @item
-@code{FORMAT=F@i{w}.@i{d}} divides the input area into fixed-width
+@code{FORMAT=F@i{w}}[@code{.@i{d}}] divides the input area into fixed-width
 fields with width @i{w}.  The input area must be a multiple of @i{w}
 columns wide.  Another format type may be substituted for @code{F}.
+The @code{READ} command disregards @i{d}.
 
 @item
-If @code{BY} and @code{FORMAT} are both used, then they must agree on
-the field width.
+@code{FORMAT=F} specifies format @code{F} without indicating a field
+width.  Another format type may be substituted for @code{F}.  The
+@code{WRITE} command accepts this form, but it has no effect unless
+@code{BY} is also used to specify a field width.
 @end itemize
 
+If @code{BY} and @code{FORMAT} both specify or imply a field width,
+then they must indicate the same field width.
+
 @node Matrix READ Command
 @subsubsection The @code{READ} Command
 
@@ -2205,7 +2211,8 @@ Later @code{READ} commands (in syntax order) use the previous
 referenced file if @code{FILE} is omitted.
 
 The @code{FIELD} and @code{FORMAT} subcommands specify how input lines
-are interpreted.  @xref{Matrix READ and WRITE Commands}, for details.
+are interpreted.  @code{FIELD} is required, but @code{FORMAT} is
+optional.  @xref{Matrix READ and WRITE Commands}, for details.
 
 The @code{SIZE} subcommand is required for reading into an entire
 variable.  Its restricted expression argument should evaluate to a
@@ -2313,9 +2320,9 @@ END LOOP.
       [@t{/HOLD}]@t{.}
 @end display
 
-The @code{WRITE} command evaluates @i{expression} and writes it to a
-text file in a specified format.  Write the expression to evaluate
-just after the command name.
+The @code{WRITE} command evaluates @i{expression} and writes its value
+to a text file in a specified format.  Write the expression to
+evaluate just after the command name.
 
 The @code{OUTFILE} subcommand is required in the first @code{WRITE}
 command that appears within @code{MATRIX}.  It specifies the text file
@@ -2325,7 +2332,8 @@ Later @code{WRITE} commands (in syntax order) use the previous
 referenced file if @code{FILE} is omitted.
 
 The @code{FIELD} and @code{FORMAT} subcommands specify how output
-lines are formed.  @xref{Matrix READ and WRITE Commands}, for details.
+lines are formed.  @code{FIELD} is required, but @code{FORMAT} is
+optional.  @xref{Matrix READ and WRITE Commands}, for details.
 
 By default, or with @code{MODE=RECTANGULAR}, the command writes an
 entry for every row and column.  With @code{MODE=TRIANGULAR}, the
index 382afa0abda0fa8e9d0a71e7cb534dc11a501780..f678b2349fd8a1342980268963f2f1ffed0ab7d3 100644 (file)
@@ -28,7 +28,6 @@
 #include <unictype.h>
 #include <unistd.h>
 #include <unistr.h>
-#include <uniwidth.h>
 
 #include "language/command.h"
 #include "language/lexer/macro.h"
@@ -1206,39 +1205,12 @@ lex_token_get_last_line_number (const struct lex_source *src,
     }
 }
 
-static int
-count_columns (const char *s_, size_t length)
-{
-  const uint8_t *s = CHAR_CAST (const uint8_t *, s_);
-  int columns;
-  size_t ofs;
-  int mblen;
-
-  columns = 0;
-  for (ofs = 0; ofs < length; ofs += mblen)
-    {
-      ucs4_t uc;
-
-      mblen = u8_mbtouc (&uc, s + ofs, length - ofs);
-      if (uc != '\t')
-        {
-          int width = uc_width (uc, "UTF-8");
-          if (width > 0)
-            columns += width;
-        }
-      else
-        columns = ROUND_UP (columns + 1, 8);
-    }
-
-  return columns + 1;
-}
-
 static int
 lex_token_get_first_column (const struct lex_source *src,
                             const struct lex_token *token)
 {
-  return count_columns (&src->buffer[token->line_pos - src->tail],
-                        token->token_pos - token->line_pos);
+  return utf8_count_columns (&src->buffer[token->line_pos - src->tail],
+                             token->token_pos - token->line_pos) + 1;
 }
 
 static int
@@ -1252,7 +1224,7 @@ lex_token_get_last_column (const struct lex_source *src,
   newline = memrchr (start, '\n', end - start);
   if (newline != NULL)
     start = newline + 1;
-  return count_columns (start, end - start);
+  return utf8_count_columns (start, end - start) + 1;
 }
 
 static struct msg_location
index 9d053374de22f98a808fd59c617a48b707aca2d9..088870d35882e5f6238d734d2ce975b678a6b592 100644 (file)
@@ -59,6 +59,7 @@
 #include "output/pivot-table.h"
 
 #include "gl/c-ctype.h"
+#include "gl/ftoastr.h"
 #include "gl/minmax.h"
 #include "gl/xsize.h"
 
@@ -3405,7 +3406,7 @@ struct matrix_cmd
             int c1, c2;
             enum fmt_type format;
             int w;
-            int d;
+            //int d;
             bool symmetric;
             bool reread;
           }
@@ -3416,11 +3417,15 @@ struct matrix_cmd
             struct write_file *wf;
             struct matrix_expr *expression;
             int c1, c2;
-            enum fmt_type format;
-            int w;
-            int d;
+
+            /* If this is nonnull, WRITE uses this format.
+
+               If this is NULL, WRITE uses free-field format with as many
+               digits of precision as needed. */
+            struct fmt_spec *format;
+
             bool triangular;
-            bool hold;          /* XXX */
+            bool hold;
           }
         write;
 
@@ -4676,14 +4681,15 @@ matrix_parse_read (struct matrix_state *s)
                 }
               lex_get (s->lexer);
             }
-          else if (!fmt_from_name (p, &read->format))
+          else if (fmt_from_name (p, &read->format))
+            lex_get (s->lexer);
+          else
             {
               struct fmt_spec format;
               if (!parse_format_specifier (s->lexer, &format))
                 goto error;
               read->format = format.type;
               read->w = format.w;
-              read->d = format.d;
             }
         }
       else
@@ -4771,28 +4777,78 @@ error:
   return NULL;
 }
 
+static void
+parse_error (const struct dfm_reader *reader, enum fmt_type format,
+             struct substring data, size_t y, size_t x,
+             int first_column, int last_column, char *error)
+{
+  int line_number = dfm_get_line_number (reader);
+  struct msg_location *location = xmalloc (sizeof *location);
+  *location = (struct msg_location) {
+    .file_name = xstrdup (dfm_get_file_name (reader)),
+    .first_line = line_number,
+    .last_line = line_number + 1,
+    .first_column = first_column,
+    .last_column = last_column,
+  };
+  struct msg *m = xmalloc (sizeof *m);
+  *m = (struct msg) {
+    .category = MSG_C_DATA,
+    .severity = MSG_S_WARNING,
+    .location = location,
+    .text = xasprintf (_("Error reading \"%.*s\" as format %s "
+                         "for matrix row %zu, column %zu: %s"),
+                       (int) data.length, data.string, fmt_name (format),
+                       y + 1, x + 1, error),
+  };
+  msg_emit (m);
+
+  free (error);
+}
+
 static void
 matrix_read_set_field (struct read_command *read, struct dfm_reader *reader,
-                       gsl_matrix *m, struct substring p, size_t y, size_t x)
+                       gsl_matrix *m, struct substring p, size_t y, size_t x,
+                       const char *line_start)
 {
   const char *input_encoding = dfm_reader_get_encoding (reader);
-  union value v;
-  char *error = data_in (p, input_encoding, read->format,
-                         settings_get_fmt_settings (), &v, 0, NULL);
-  /* XXX report error if value is missing */
+  char *error;
+  double f;
+  if (fmt_is_numeric (read->format))
+    {
+      union value v;
+      error = data_in (p, input_encoding, read->format,
+                       settings_get_fmt_settings (), &v, 0, NULL);
+      if (!error && v.f == SYSMIS)
+        error = xstrdup (_("Matrix data may not contain missing value."));
+      f = v.f;
+    }
+    else
+      {
+        uint8_t s[sizeof (double)];
+        union value v = { .s = s };
+        error = data_in (p, input_encoding, read->format,
+                         settings_get_fmt_settings (), &v, sizeof s, "UTF-8");
+        memcpy (&f, s, sizeof f);
+      }
+
   if (error)
-    msg (SW, _("GET parse error (%.*s): %s"), (int) p.length, p.string, error);
+    {
+      int c1 = utf8_count_columns (line_start, p.string - line_start) + 1;
+      int c2 = c1 + ss_utf8_count_columns (p) - 1;
+      parse_error (reader, read->format, p, y, x, c1, c2, error);
+    }
   else
     {
-      gsl_matrix_set (m, y, x, v.f);
+      gsl_matrix_set (m, y, x, f);
       if (read->symmetric && x != y)
-        gsl_matrix_set (m, x, y, v.f);
+        gsl_matrix_set (m, x, y, f);
     }
 }
 
 static bool
 matrix_read_line (struct read_command *read, struct dfm_reader *reader,
-                  struct substring *line)
+                  struct substring *line, const char **startp)
 {
   if (dfm_eof (reader))
     {
@@ -4800,8 +4856,10 @@ matrix_read_line (struct read_command *read, struct dfm_reader *reader,
       return false;
     }
   dfm_expand_tabs (reader);
-  *line = ss_substr (dfm_get_record (reader),
-                     read->c1 - 1, read->c2 - read->c1);
+  struct substring record = dfm_get_record (reader);
+  /* XXX need to recode record into UTF-8 */
+  *startp = record.string;
+  *line = ss_utf8_columns (record, read->c1 - 1, read->c2 - read->c1);
   return true;
 }
 
@@ -4814,6 +4872,7 @@ matrix_read (struct read_command *read, struct dfm_reader *reader,
       size_t nx = read->symmetric ? y + 1 : m->size2;
 
       struct substring line = ss_empty ();
+      const char *line_start = line.string;
       for (size_t x = 0; x < nx; x++)
         {
           struct substring p;
@@ -4824,7 +4883,7 @@ matrix_read (struct read_command *read, struct dfm_reader *reader,
                   ss_ltrim (&line, ss_cstr (" ,"));
                   if (!ss_is_empty (line))
                     break;
-                  if (!matrix_read_line (read, reader, &line))
+                  if (!matrix_read_line (read, reader, &line, &line_start))
                     return;
                   dfm_forward_record (reader);
                 }
@@ -4833,7 +4892,7 @@ matrix_read (struct read_command *read, struct dfm_reader *reader,
             }
           else
             {
-              if (!matrix_read_line (read, reader, &line))
+              if (!matrix_read_line (read, reader, &line, &line_start))
                 return;
               size_t fields_per_line = (read->c2 - read->c1) / read->w;
               int f = x % fields_per_line;
@@ -4843,7 +4902,7 @@ matrix_read (struct read_command *read, struct dfm_reader *reader,
               p = ss_substr (line, read->w * f, read->w);
             }
 
-          matrix_read_set_field (read, reader, m, p, y, x);
+          matrix_read_set_field (read, reader, m, p, y, x, line_start);
         }
 
       if (read->w)
@@ -4983,7 +5042,6 @@ matrix_parse_write (struct matrix_state *s)
   struct matrix_cmd *cmd = xmalloc (sizeof *cmd);
   *cmd = (struct matrix_cmd) {
     .type = MCMD_WRITE,
-    .write = { .format = FMT_F },
   };
 
   struct file_handle *fh = NULL;
@@ -4996,7 +5054,8 @@ matrix_parse_write (struct matrix_state *s)
   int by = 0;
   int repetitions = 0;
   int record_width = 0;
-  bool seen_format = false;
+  enum fmt_type format = FMT_F;
+  bool has_format = false;
   while (lex_match (s->lexer, T_SLASH))
     {
       if (lex_match_id (s->lexer, "OUTFILE"))
@@ -5069,12 +5128,11 @@ matrix_parse_write (struct matrix_state *s)
         write->hold = true;
       else if (lex_match_id (s->lexer, "FORMAT"))
         {
-          if (seen_format)
+          if (has_format || write->format)
             {
               lex_sbc_only_once ("FORMAT");
               goto error;
             }
-          seen_format = true;
 
           lex_match (s->lexer, T_EQUALS);
 
@@ -5086,21 +5144,25 @@ matrix_parse_write (struct matrix_state *s)
             {
               repetitions = atoi (p);
               p += strspn (p, "0123456789");
-              if (!fmt_from_name (p, &write->format))
+              if (!fmt_from_name (p, &format))
                 {
                   lex_error (s->lexer, _("Unknown format %s."), p);
                   goto error;
                 }
+              has_format = true;
               lex_get (s->lexer);
             }
-          else if (!fmt_from_name (p, &write->format))
+          else if (fmt_from_name (p, &format))
             {
-              struct fmt_spec format;
-              if (!parse_format_specifier (s->lexer, &format))
+              has_format = true;
+              lex_get (s->lexer);
+            }
+          else
+            {
+              struct fmt_spec spec;
+              if (!parse_format_specifier (s->lexer, &spec))
                 goto error;
-              write->format = format.type;
-              write->w = format.w;
-              write->d = format.d;
+              write->format = xmemdup (&spec, sizeof spec);
             }
         }
       else
@@ -5157,7 +5219,7 @@ matrix_parse_write (struct matrix_state *s)
       goto error;
     }
   int w = (repetitions ? record_width / repetitions
-           : write->w ? write->w
+           : write->format ? write->format->w
            : by);
   if (by && w != by)
     {
@@ -5171,7 +5233,24 @@ matrix_parse_write (struct matrix_state *s)
              w, by);
       goto error;
     }
-  write->w = w;
+  if (w && !write->format)
+    {
+      write->format = xmalloc (sizeof *write->format);
+      *write->format = (struct fmt_spec) { .type = format, .w = w };
+
+      if (!fmt_check_output (write->format))
+        goto error;
+    };
+
+  if (write->format && fmt_var_width (write->format) > sizeof (double))
+    {
+      char s[FMT_STRING_LEN_MAX + 1];
+      fmt_to_string (write->format, s);
+      msg (SE, _("Format %s is too wide for %zu-byte matrix eleemnts."),
+           s, sizeof (double));
+      goto error;
+    }
+
   return cmd;
 
 error:
@@ -5204,11 +5283,6 @@ matrix_cmd_execute_write (struct write_command *write)
     }
 
   const struct fmt_settings *settings = settings_get_fmt_settings ();
-  struct fmt_spec format = {
-    .type = write->format,
-    .w = write->w ? write->w : 40,
-    .d = write->d
-  };
   struct u8_line *line = write->wf->held;
   for (size_t y = 0; y < m->size1; y++)
     {
@@ -5221,11 +5295,24 @@ matrix_cmd_execute_write (struct write_command *write)
       int x0 = write->c1;
       for (size_t x = 0; x < nx; x++)
         {
-          /* XXX string values */
-          union value v = { .f = gsl_matrix_get (m, y, x) };
-          char *s = (write->w
-                     ? data_out (&v, NULL, &format, settings)
-                     : data_out_stretchy (&v, NULL, &format, settings, NULL));
+          char *s;
+          double f = gsl_matrix_get (m, y, x);
+          if (write->format)
+            {
+              union value v;
+              if (fmt_is_numeric (write->format->type))
+                v.f = f;
+              else
+                v.s = (uint8_t *) &f;
+              s = data_out (&v, NULL, write->format, settings);
+            }
+          else
+            {
+              s = xmalloc (DBL_BUFSIZE_BOUND);
+              if (c_dtoastr (s, DBL_BUFSIZE_BOUND, FTOASTR_UPPER_E, 0, f)
+                  >= DBL_BUFSIZE_BOUND)
+                abort ();
+            }
           size_t len = strlen (s);
           int width = u8_width (CHAR_CAST (const uint8_t *, s), len, UTF8);
           if (width + x0 > write->c2)
@@ -5238,7 +5325,7 @@ matrix_cmd_execute_write (struct write_command *write)
           u8_line_put (line, x0, x0 + width, s, len);
           free (s);
 
-          x0 += write->w ? write->w : width + 1;
+          x0 += write->format ? write->format->w : width + 1;
         }
 
       if (y + 1 >= m->size1 && write->hold)
@@ -5249,10 +5336,10 @@ matrix_cmd_execute_write (struct write_command *write)
   if (!write->hold)
     {
       u8_line_destroy (line);
+      free (line);
       line = NULL;
     }
   write->wf->held = line;
-  dfm_close_writer (writer);
 
   gsl_matrix_free (m);
 }
@@ -6555,6 +6642,7 @@ matrix_cmd_destroy (struct matrix_cmd *cmd)
 
     case MCMD_WRITE:
       matrix_expr_destroy (cmd->write.expression);
+      free (cmd->write.format);
       break;
 
     case MCMD_GET:
index ac195cc517529da246ed333cbd640b6be5ff084d..3faadcbb87c770395f5ce0c86fdef509df50c612 100644 (file)
 #include <string.h>
 #include <unicase.h>
 #include <unigbrk.h>
+#include <uniwidth.h>
 
 #include "libpspp/assertion.h"
 #include "libpspp/compiler.h"
 #include "libpspp/hmapx.h"
 #include "libpspp/hash-functions.h"
+#include "libpspp/misc.h"
 #include "libpspp/pool.h"
 #include "libpspp/str.h"
 #include "libpspp/version.h"
@@ -501,6 +503,55 @@ utf8_encoding_concat_len (const char *head, const char *tail,
   return prefix_len + tail_len;
 }
 
+/* Returns the number of display columns that would be occupied by the LENGTH
+   bytes of UTF-8 starting at S. */
+size_t
+utf8_count_columns (const char *s_, size_t length)
+{
+  const uint8_t *s = CHAR_CAST (const uint8_t *, s_);
+
+  size_t columns = 0;
+  for (int ofs = 0; ofs < length; )
+    {
+      ucs4_t uc;
+      ofs += u8_mbtouc (&uc, s + ofs, length - ofs);
+      if (uc != '\t')
+        {
+          int width = uc_width (uc, "UTF-8");
+          if (width > 0)
+            columns += width;
+        }
+      else
+        columns = ROUND_UP (columns + 1, 8);
+    }
+  return columns;
+}
+
+/* Returns the byte offset in LENGTH-byte UTF-8 string S that is N_COLUMNS
+   display columns into the string. */
+size_t
+utf8_columns_to_bytes (const char *s_, size_t length, size_t n_columns)
+{
+  const uint8_t *s = CHAR_CAST (const uint8_t *, s_);
+
+  size_t columns = 0;
+  int ofs;
+  for (ofs = 0; ofs < length && columns < n_columns; )
+    {
+      ucs4_t uc;
+      ofs += u8_mbtouc (&uc, s + ofs, length - ofs);
+      if (uc != '\t')
+        {
+          int width = uc_width (uc, "UTF-8");
+          if (width > 0)
+            columns += width;
+        }
+      else
+        columns = ROUND_UP (columns + 1, 8);
+    }
+  return ofs;
+}
+
 /* Returns an allocated, null-terminated string, owned by the caller,
    containing as many characters[*] from the beginning of S that would fit
    within MAX_LEN bytes if the returned string were to be re-encoded in
index ee8be2fd631509af50579569549dbb055e5a0ff8..d41ef1ef2c80046f15d84404db542a038e072c5a 100644 (file)
@@ -59,6 +59,9 @@ char *utf8_encoding_concat (const char *head, const char *tail,
 size_t utf8_encoding_concat_len (const char *head, const char *tail,
                                  const char *encoding, size_t max_len);
 
+size_t utf8_count_columns (const char *, size_t);
+size_t utf8_columns_to_bytes (const char *, size_t, size_t n_columns);
+
 char *utf8_to_filename (const char *filename);
 char *filename_to_utf8 (const char *filename);
 
index 86e7fd9199a5b9e22fba9fa2ac2eec4e9e7ff1c7..b40cb4b4ab8f07ac662965e70ae88adebfea90d0 100644 (file)
@@ -26,6 +26,7 @@
 #include <unistr.h>
 
 #include "libpspp/cast.h"
+#include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/pool.h"
 
@@ -928,6 +929,22 @@ ss_at_mblen (struct substring s, size_t ofs)
   else
     return 0;
 }
+
+size_t
+ss_utf8_count_columns (struct substring s)
+{
+  return utf8_count_columns (s.string, s.length);
+}
+
+/* Returns a substring of S starting at 0-based display column START and
+   running for N display columns. */
+struct substring
+ss_utf8_columns (struct substring s, size_t start, size_t n)
+{
+  ss_advance (&s, utf8_columns_to_bytes (s.string, s.length, start));
+  s.length = utf8_columns_to_bytes (s.string, s.length, n);
+  return s;
+}
 \f
 /* Initializes ST as an empty string. */
 void
index 8cde5779143773e5dd3aeb02d914a01a4740ae16..aaf83d71a448fcb9e429a165d92fa617ffbfd8fc 100644 (file)
@@ -150,6 +150,8 @@ int ss_first_mblen (struct substring);
 ucs4_t ss_get_mb (struct substring *);
 ucs4_t ss_at_mb (struct substring, size_t ofs);
 int ss_at_mblen (struct substring, size_t ofs);
+size_t ss_utf8_count_columns (struct substring);
+struct substring ss_utf8_columns (struct substring, size_t start, size_t n);
 \f
 /* Variable length strings. */
 
index 87d9701a84a6aef19cbd8e53495f90caa1e8f65e..395c57879544ea893aa5c2bb2a1bfc2095fb6f60 100644 (file)
@@ -2535,6 +2535,14 @@ AT_DATA([matrix.txt], [dnl
 5    6
     78        89
 10   11
+$1 $2 3
+4 $5 6
+$1   $2   $3   4
+   $5$6      $78
+1% 2% 3% 4
+  56%  7%8
+abcdefghijkl
+ABCDEFGHIJKL
 ])
 AT_DATA([matrix2.txt], [dnl
 2, 3, 5, 7
@@ -2564,6 +2572,14 @@ PRINT x.
 
 READ x/SIZE={2,6}/FIELD=1 TO 20 BY 5.
 PRINT x.
+READ x/SIZE={2,3}/FIELD=1 TO 20/FORMAT=DOLLAR.
+PRINT x.
+READ x/SIZE={2,4}/FIELD=1 TO 20/FORMAT=DOLLAR5.1.
+PRINT x.
+READ x/SIZE={2,4}/FIELD=1 TO 12/FORMAT='4PCT'.
+PRINT x.
+READ x/SIZE={2,4}/FIELD=1 TO 12/FORMAT='4A'.
+PRINT x/FORMAT=A3.
 
 COMPUTE y={}.
 LOOP IF NOT EOF('matrix2.txt').
@@ -2608,6 +2624,22 @@ x
    1   2   3   4   5   6
    7   8   8   9  10  11
 
+x
+  1  2  3
+  4  5  6
+
+x
+  1  2  3  4
+  5  6  7  8
+
+x
+  1  2  3  4
+  5  6  7  8
+
+x
+ abc def ghi jkl
+ ABC DEF GHI JKL
+
 y
    2   3   5   7
   11  13  17  19
@@ -2654,9 +2686,13 @@ READ x/FIELD=1 TO 10/SIZE={-1}/FILE='matrix.txt'.
 COMPUTE x={1,2,3}.
 READ x(:,:)/FIELD=1 TO 10/SIZE={2,2}/FILE='matrix.txt'.
 READ x/FIELD=1 TO 10/SIZE={1,3}/FILE='matrix.txt'/MODE=SYMMETRIC.
+READ x/FIELD=1 TO 10/SIZE=2/FILE='matrix.txt'.
 END MATRIX.
 ])
-AT_DATA([matrix.txt], [])
+AT_DATA([matrix.txt], [dnl
+xyzzy
+.
+])
 AT_CHECK([pspp matrix.sps], [1], [dnl
 matrix.sps:2.6: error: READ: Syntax error at `!': expecting identifier.
 
@@ -2726,6 +2762,126 @@ from submatrix dimensions 1×3.
 
 matrix.sps:29: error: MATRIX: Cannot read non-square 1×3 matrix using READ with
 MODE=SYMMETRIC.
+
+matrix.txt:1.1-1.4: warning: Error reading "xyzzy" as format F for matrix row
+1, column 1: Field contents are not numeric.
+
+matrix.txt:2.1: warning: Error reading "." as format F for matrix row 2, column
+1: Matrix data may not contain missing value.
 ])
 AT_CLEANUP
 
+AT_SETUP([MATRIX - WRITE])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+WRITE {1.5, 2; 3, 4.12345}/OUTFILE='matrix.txt'/FIELD=1 TO 80.
+WRITE {1.5, 2; 3, 4.12345}/OUTFILE='matrix.txt'/FIELD=1 TO 5.
+WRITE {1, 2; 3, 4}/OUTFILE='matrix.txt'/FIELD=1 TO 80 BY 5.
+WRITE {1, 2; 3, 4}/OUTFILE='matrix.txt'/FIELD=1 TO 80/FORMAT=F8.2.
+WRITE {1, 2; 3, 4}/OUTFILE='matrix.txt'/FIELD=1 TO 80/FORMAT=E.
+WRITE {1, 2; 3, 4}/OUTFILE='matrix.txt'/FIELD=1 TO 10 BY 10/FORMAT=E.
+WRITE "abcdefhi"/OUTFILE='matrix.txt'/FIELD=1 TO 80/FORMAT=A8.
+WRITE "abcdefhi"/OUTFILE='matrix.txt'/FIELD=1 TO 80/FORMAT=A4.
+WRITE "abcdefhi"/OUTFILE='matrix.txt'/FIELD=1 TO 80/FORMAT=AHEX12.
+END MATRIX.
+])
+AT_CHECK([pspp matrix.sps])
+AT_CHECK([cat matrix.txt], [0], [dnl
+ 1.5 2
+ 3 4.12345
+ 1.5 2
+ 3
+ 4.12345
+     1    2
+     3    4
+     1.00    2.00
+     3.00    4.00
+ 1 2
+ 3 4
+    1.E+000
+    2.E+000
+    3.E+000
+    4.E+000
+ abcdefhi
+ abcd
+ 616263646566
+])
+AT_CLEANUP
+
+AT_SETUP([MATRIX - WRITE - negative])
+AT_DATA([matrix.sps], [dnl
+MATRIX.
+WRITE !.
+WRITE 1/OUTFILE=!.
+WRITE 1/ENCODING=!.
+WRITE 1/FIELD=!.
+WRITE 1/FIELD=1 !.
+WRITE 1/FIELD=1 TO 0.
+WRITE 1/FIELD=1 TO 10 BY 20.
+WRITE 1/FIELD=1 TO 10 BY 6.
+WRITE 1/MODE=TRAPEZOIDAL.
+WRITE 1/FORMAT=F5/FORMAT=F5.
+WRITE 1/FORMAT='5ASDF'.
+WRITE 1/FORMAT=ASDF5.
+WRITE 1/!.
+WRITE 1.
+WRITE 1/FIELD=1 TO 10.
+WRITE 1/FIELD=1 TO 10/OUTFILE='matrix.txt'/FORMAT='15F'.
+WRITE 1/FIELD=1 TO 10 BY 5/OUTFILE='matrix.txt'/FORMAT='5F'.
+WRITE 1/FIELD=1 TO 10 BY 5/OUTFILE='matrix.txt'/FORMAT=E.
+WRITE 1/FIELD=1 TO 10/OUTFILE='matrix.txt'/FORMAT=A9.
+WRITE {1,2}/FIELD=1 TO 10/OUTFILE='matrix.txt'/MODE=TRIANGULAR.
+END MATRIX.
+])
+AT_CHECK([pspp matrix.sps], [1], [dnl
+matrix.sps:2.7: error: WRITE: Syntax error at `!'.
+
+matrix.sps:3.17: error: WRITE: Syntax error at `!': expecting a file name or
+handle name.
+
+matrix.sps:4.18: error: WRITE: Syntax error at `!': expecting string.
+
+matrix.sps:5.15: error: WRITE: Syntax error at `!': Expected positive integer
+for FIELD.
+
+matrix.sps:6.17: error: WRITE: Syntax error at `!': expecting `TO'.
+
+matrix.sps:7.20: error: WRITE: Syntax error at `0': Expected positive integer
+for TO.
+
+matrix.sps:8.26-8.27: error: WRITE: Syntax error at `20': Expected integer
+between 1 and 10 for BY.
+
+matrix.sps:9: error: WRITE: BY 6 does not evenly divide record width 10.
+
+matrix.sps:10.14-10.24: error: WRITE: Syntax error at `TRAPEZOIDAL': expecting
+RECTANGULAR or TRIANGULAR.
+
+matrix.sps:11: error: WRITE: Subcommand FORMAT may only be specified once.
+
+matrix.sps:12.16-12.22: error: WRITE: Syntax error at `'5ASDF'': Unknown format
+ASDF.
+
+matrix.sps:13: error: WRITE: Unknown format type `ASDF'.
+
+matrix.sps:14.9: error: WRITE: Syntax error at `!': expecting OUTFILE, FIELD,
+MODE, HOLD, or FORMAT.
+
+matrix.sps:15: error: WRITE: Required subcommand FIELD was not specified.
+
+matrix.sps:16: error: WRITE: Required subcommand OUTFILE was not specified.
+
+matrix.sps:17: error: WRITE: 15 repetitions cannot fit in record width 10.
+
+matrix.sps:18: error: WRITE: FORMAT specifies 5 repetitions with record width
+10, which implies field width 2, but BY specifies field width 5.
+
+matrix.sps:19: error: WRITE: Output format E5.0 specifies width 5, but E
+requires a width between 6 and 40.
+
+matrix.sps:20: error: WRITE: Format A9 is too wide for 8-byte matrix eleemnts.
+
+matrix.sps:21: error: MATRIX: WRITE with MODE=TRIANGULAR requires a square
+matrix but the matrix to be written has dimensions 1×2.
+])
+AT_CLEANUP
\ No newline at end of file