From 8f3ad4a4c952033757a9bd8bcc41dcc8fb9247fe Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 6 Nov 2021 19:38:19 -0700 Subject: [PATCH] READ and WRITE are tested. --- doc/matrices.texi | 44 ++++---- src/language/lexer/lexer.c | 34 +------ src/language/stats/matrix.c | 180 ++++++++++++++++++++++++--------- src/libpspp/i18n.c | 51 ++++++++++ src/libpspp/i18n.h | 3 + src/libpspp/str.c | 17 ++++ src/libpspp/str.h | 2 + tests/language/stats/matrix.at | 158 ++++++++++++++++++++++++++++- 8 files changed, 393 insertions(+), 96 deletions(-) diff --git a/doc/matrices.texi b/doc/matrices.texi index 5c03eb79c3..4ecf0360e7 100644 --- a/doc/matrices.texi +++ b/doc/matrices.texi @@ -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 diff --git a/src/language/lexer/lexer.c b/src/language/lexer/lexer.c index 382afa0abd..f678b2349f 100644 --- a/src/language/lexer/lexer.c +++ b/src/language/lexer/lexer.c @@ -28,7 +28,6 @@ #include #include #include -#include #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 diff --git a/src/language/stats/matrix.c b/src/language/stats/matrix.c index 9d053374de..088870d358 100644 --- a/src/language/stats/matrix.c +++ b/src/language/stats/matrix.c @@ -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: diff --git a/src/libpspp/i18n.c b/src/libpspp/i18n.c index ac195cc517..3faadcbb87 100644 --- a/src/libpspp/i18n.c +++ b/src/libpspp/i18n.c @@ -29,11 +29,13 @@ #include #include #include +#include #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 diff --git a/src/libpspp/i18n.h b/src/libpspp/i18n.h index ee8be2fd63..d41ef1ef2c 100644 --- a/src/libpspp/i18n.h +++ b/src/libpspp/i18n.h @@ -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); diff --git a/src/libpspp/str.c b/src/libpspp/str.c index 86e7fd9199..b40cb4b4ab 100644 --- a/src/libpspp/str.c +++ b/src/libpspp/str.c @@ -26,6 +26,7 @@ #include #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; +} /* Initializes ST as an empty string. */ void diff --git a/src/libpspp/str.h b/src/libpspp/str.h index 8cde577914..aaf83d71a4 100644 --- a/src/libpspp/str.h +++ b/src/libpspp/str.h @@ -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); /* Variable length strings. */ diff --git a/tests/language/stats/matrix.at b/tests/language/stats/matrix.at index 87d9701a84..395c578795 100644 --- a/tests/language/stats/matrix.at +++ b/tests/language/stats/matrix.at @@ -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 -- 2.30.2