pivot-table-test
[pspp] / tests / output / pivot-table-test.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <string.h>
25
26 #include "data/file-handle-def.h"
27 #include "language/lexer/lexer.h"
28 #include "language/lexer/format-parser.h"
29 #include "libpspp/assertion.h"
30 #include "libpspp/compiler.h"
31 #include "libpspp/i18n.h"
32 #include "libpspp/string-map.h"
33 #include "output/driver.h"
34 #include "output/message-item.h"
35 #include "output/options.h"
36 #include "output/pivot-table.h"
37 #include "output/table-item.h"
38
39 #include "gl/error.h"
40 #include "gl/progname.h"
41 #include "gl/xalloc.h"
42 #include "gl/xvasprintf.h"
43
44 /* --emphasis: Enable emphasis in ASCII driver? */
45 static bool emphasis;
46
47 /* --box: ASCII driver box option. */
48 static char *box;
49
50 /* -o, --output: Base name for output files. */
51 static const char *output_base = "render";
52
53 static const char *parse_options (int argc, char **argv);
54 static void usage (void) NO_RETURN;
55 static struct pivot_table *read_table (struct lexer *);
56 static void output_msg (const struct msg *, void *);
57
58 int
59 main (int argc, char **argv)
60 {
61   const char *input_file_name;
62
63   set_program_name (argv[0]);
64   i18n_init ();
65   output_engine_push ();
66   input_file_name = parse_options (argc, argv);
67
68   settings_init ();
69
70   struct lex_reader *reader = lex_reader_for_file (input_file_name, NULL,
71                                                    LEX_SYNTAX_AUTO,
72                                                    LEX_ERROR_CONTINUE);
73   if (!reader)
74     exit (1);
75
76   struct lexer *lexer = lex_create ();
77   msg_set_handler (output_msg, lexer);
78   lex_include (lexer, reader);
79   lex_get (lexer);
80
81   for (;;)
82     {
83       while (lex_match (lexer, T_ENDCMD))
84         continue;
85       if (lex_match (lexer, T_STOP))
86         break;
87
88       struct pivot_table *pt = read_table (lexer);
89       pivot_table_dump (pt, 0);
90       pivot_table_submit (pt);
91     }
92
93   lex_destroy (lexer);
94   output_engine_pop ();
95   fh_done ();
96
97   return 0;
98 }
99
100 static void PRINTF_FORMAT (2, 3)
101 register_driver (struct string_map *options,
102                  const char *output_file, ...)
103 {
104   va_list args;
105   va_start (args, output_file);
106   string_map_insert_nocopy (options, xstrdup ("output-file"),
107                             xvasprintf (output_file, args));
108   va_end (args);
109
110   struct output_driver *driver = output_driver_create (options);
111   if (driver == NULL)
112     exit (EXIT_FAILURE);
113   output_driver_register (driver);
114 }
115
116 static void
117 configure_drivers (int width, int length, int min_break)
118 {
119   /* Render to stdout. */
120   struct string_map options = STRING_MAP_INITIALIZER (options);
121   string_map_insert (&options, "format", "txt");
122   string_map_insert_nocopy (&options, xstrdup ("width"),
123                             xasprintf ("%d", width));
124   if (min_break >= 0)
125     string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
126                               xasprintf ("%d", min_break));
127   string_map_insert (&options, "emphasis", emphasis ? "true" : "false");
128   if (box != NULL)
129     string_map_insert (&options, "box", box);
130   register_driver (&options, "-");
131
132
133 #ifdef HAVE_CAIRO
134   /* Render to <base>.pdf. */
135   string_map_insert (&options, "top-margin", "0");
136   string_map_insert (&options, "bottom-margin", "0");
137   string_map_insert (&options, "left-margin", "0");
138   string_map_insert (&options, "right-margin", "0");
139   string_map_insert_nocopy (&options, xstrdup ("paper-size"),
140                             xasprintf ("%dx%dpt", width * 5, length * 8));
141   if (min_break >= 0)
142     {
143       string_map_insert_nocopy (&options, xstrdup ("min-hbreak"),
144                                 xasprintf ("%d", min_break * 5));
145       string_map_insert_nocopy (&options, xstrdup ("min-vbreak"),
146                                 xasprintf ("%d", min_break * 8));
147     }
148   register_driver (&options, "%s.pdf", output_base);
149 #endif
150
151   register_driver (&options, "%s.txt", output_base);
152   register_driver (&options, "%s.csv", output_base);
153   register_driver (&options, "%s.odt", output_base);
154   register_driver (&options, "%s.spv", output_base);
155
156   string_map_destroy (&options);
157 }
158
159 static const char *
160 parse_options (int argc, char **argv)
161 {
162   int width = 79;
163   int length = 66;
164   int min_break = -1;
165
166   for (;;)
167     {
168       enum {
169         OPT_WIDTH = UCHAR_MAX + 1,
170         OPT_LENGTH,
171         OPT_MIN_BREAK,
172         OPT_EMPHASIS,
173         OPT_BOX,
174         OPT_TABLE_LOOK,
175         OPT_HELP
176       };
177       static const struct option options[] =
178         {
179           {"width", required_argument, NULL, OPT_WIDTH},
180           {"length", required_argument, NULL, OPT_LENGTH},
181           {"min-break", required_argument, NULL, OPT_MIN_BREAK},
182           {"emphasis", no_argument, NULL, OPT_EMPHASIS},
183           {"box", required_argument, NULL, OPT_BOX},
184           {"output", required_argument, NULL, 'o'},
185           {"table-look", required_argument, NULL, OPT_TABLE_LOOK},
186           {"help", no_argument, NULL, OPT_HELP},
187           {NULL, 0, NULL, 0},
188         };
189
190       int c = getopt_long (argc, argv, "o:", options, NULL);
191       if (c == -1)
192         break;
193
194       switch (c)
195         {
196         case OPT_WIDTH:
197           width = atoi (optarg);
198           break;
199
200         case OPT_LENGTH:
201           length = atoi (optarg);
202           break;
203
204         case OPT_MIN_BREAK:
205           min_break = atoi (optarg);
206           break;
207
208         case OPT_EMPHASIS:
209           emphasis = true;
210           break;
211
212         case OPT_BOX:
213           box = optarg;
214           break;
215
216         case 'o':
217           output_base = optarg;
218           break;
219
220         case OPT_TABLE_LOOK:
221           {
222             struct pivot_table_look *look;
223             char *err = pivot_table_look_read (optarg, &look);
224             if (err)
225               error (1, 0, "%s", err);
226             pivot_table_look_set_default (look);
227             pivot_table_look_unref (look);
228           }
229           break;
230
231         case OPT_HELP:
232           usage ();
233
234         case 0:
235           break;
236
237         case '?':
238           exit(EXIT_FAILURE);
239           break;
240
241         default:
242           NOT_REACHED ();
243         }
244
245     }
246
247   configure_drivers (width, length, min_break);
248
249   if (optind + 1 != argc)
250     error (1, 0, "exactly one non-option argument required; "
251            "use --help for help");
252   return argv[optind];
253 }
254
255 static void
256 usage (void)
257 {
258   printf ("%s, to test rendering of PSPP tables\n"
259           "usage: %s [OPTIONS] INPUT\n"
260           "\nOptions:\n"
261           "  --width=WIDTH   set page width in characters\n"
262           "  --length=LINE   set page length in lines\n",
263           program_name, program_name);
264   exit (EXIT_SUCCESS);
265 }
266
267 static bool
268 parse_settings_value_show (struct lexer *lexer, const char *name,
269                            enum settings_value_show *show)
270 {
271   if (lex_match_id (lexer, name))
272     {
273       if (!lex_force_match (lexer, T_EQUALS))
274         exit (1);
275
276       if (lex_match_id (lexer, "DEFAULT"))
277         *show = SETTINGS_VALUE_SHOW_DEFAULT;
278       else if (lex_match_id (lexer, "VALUE"))
279         *show = SETTINGS_VALUE_SHOW_VALUE;
280       else if (lex_match_id (lexer, "LABEL"))
281         *show = SETTINGS_VALUE_SHOW_LABEL;
282       else if (lex_match_id (lexer, "BOTH"))
283         *show = SETTINGS_VALUE_SHOW_BOTH;
284       else
285         {
286           lex_error_expecting (lexer, "DEFAULT", "VALUE", "LABEL", "BOTH");
287           exit (1);
288         }
289
290       return true;
291     }
292   else
293     return false;
294 }
295
296 static bool
297 parse_string_setting (struct lexer *lexer, const char *name, char **stringp)
298 {
299   if (lex_match_id (lexer, name))
300     {
301       lex_match (lexer, T_EQUALS);
302       if (!lex_force_string (lexer))
303         exit (1);
304
305       free (*stringp);
306       *stringp = xstrdup (lex_tokcstr (lexer));
307
308       lex_get (lexer);
309       return true;
310     }
311   else
312     return false;
313 }
314
315 static void
316 read_value_option (struct lexer *lexer, struct pivot_value *value)
317 {
318   enum settings_value_show *show
319     = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.show
320        : value->type == PIVOT_VALUE_STRING ? &value->string.show
321        : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.show
322        : NULL);
323   if (show && parse_settings_value_show (lexer, "SHOW", show))
324     return;
325
326   char **var_name
327     = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.var_name
328        : value->type == PIVOT_VALUE_STRING ? &value->string.var_name
329        : NULL);
330   if (var_name && parse_string_setting (lexer, "VAR", var_name))
331     return;
332
333   char **label
334     = (value->type == PIVOT_VALUE_NUMERIC ? &value->numeric.value_label
335        : value->type == PIVOT_VALUE_STRING ? &value->string.value_label
336        : value->type == PIVOT_VALUE_VARIABLE ? &value->variable.var_label
337        : NULL);
338   if (label && parse_string_setting (lexer, "LABEL", label))
339     return;
340
341   if (value->type == PIVOT_VALUE_STRING && lex_match_id (lexer, "HEX"))
342     {
343       value->string.hex = true;
344       return;
345     }
346
347   if (value->type == PIVOT_VALUE_NUMERIC)
348     {
349       msg_disable ();
350       struct fmt_spec fmt;
351       bool ok = parse_format_specifier (lexer, &fmt);
352       msg_enable ();
353
354       if (ok)
355         {
356           if (!fmt_check_output (&fmt)
357               || !fmt_check_type_compat (&fmt, VAL_NUMERIC))
358             exit (1);
359
360           value->numeric.format = fmt;
361           return;
362         }
363     }
364
365   if (parse_string_setting (lexer, "SUPERSCRIPT", &value->superscript))
366     return;
367
368   if (lex_match_id (lexer, "SUBSCRIPTS"))
369     {
370       lex_match (lexer, T_EQUALS);
371       size_t allocated_subscripts = value->n_subscripts;
372       while (lex_token (lexer) == T_STRING)
373         {
374           if (value->n_subscripts >= allocated_subscripts)
375             value->subscripts = x2nrealloc (value->subscripts,
376                                             &allocated_subscripts,
377                                             sizeof *value->subscripts);
378
379           value->subscripts[value->n_subscripts++] = xstrdup (
380             lex_tokcstr (lexer));
381           lex_get (lexer);
382         }
383       return;
384     }
385
386   lex_error (lexer, "Expecting valid value option");
387   exit (1);
388 }
389
390 static struct pivot_value *
391 read_value (struct lexer *lexer)
392 {
393   struct pivot_value *value;
394   if (lex_is_number (lexer))
395     {
396       value = pivot_value_new_number (lex_number (lexer));
397       lex_get (lexer);
398     }
399   else if (lex_is_string (lexer))
400     {
401       value = xmalloc (sizeof *value);
402       *value = (struct pivot_value) {
403         .type = PIVOT_VALUE_STRING,
404         .string = { .s = xstrdup (lex_tokcstr (lexer)) },
405       };
406       lex_get (lexer);
407     }
408   else if (lex_token (lexer) == T_ID)
409     {
410       value = xmalloc (sizeof *value);
411       *value = (struct pivot_value) {
412         .type = PIVOT_VALUE_VARIABLE,
413         .variable = { .var_name = xstrdup (lex_tokcstr (lexer)) },
414       };
415       lex_get (lexer);
416     }
417   else
418     {
419       msg (SE, "Expecting pivot_value");
420       exit (1);
421     }
422
423   while (lex_match (lexer, T_LBRACK))
424     {
425       read_value_option (lexer, value);
426
427       if (!lex_force_match (lexer, T_RBRACK))
428         exit (1);
429     }
430
431   return value;
432 }
433
434 static void
435 read_group (struct lexer *lexer, struct pivot_table *pt,
436             struct pivot_category *group)
437 {
438   if (lex_match (lexer, T_ASTERISK))
439     group->show_label = true;
440
441   if (!lex_force_match (lexer, T_LPAREN))
442     exit (1);
443
444   if (lex_match (lexer, T_RPAREN))
445     return;
446
447   do
448     {
449       struct pivot_value *name = read_value (lexer);
450       if (lex_token (lexer) == T_ASTERISK
451           || lex_token (lexer) == T_LPAREN)
452         read_group (lexer, pt, pivot_category_create_group__ (group, name));
453       else
454         {
455           char *rc;
456           if (lex_token (lexer) == T_ID
457               && is_pivot_result_class (lex_tokcstr (lexer)))
458             {
459               rc = xstrdup (lex_tokcstr (lexer));
460               lex_get (lexer);
461             }
462           else
463             rc = NULL;
464
465           pivot_category_create_leaf_rc (group, name, rc);
466
467           free (rc);
468         }
469     }
470   while (lex_match (lexer, T_COMMA));
471   if (!lex_force_match (lexer, T_RPAREN))
472     exit (1);
473 }
474
475 static void
476 read_dimension (struct lexer *lexer, struct pivot_table *pt,
477                 enum pivot_axis_type a)
478 {
479   lex_match (lexer, T_EQUALS);
480
481   struct pivot_value *name = read_value (lexer);
482   struct pivot_dimension *dim = pivot_dimension_create__ (pt, a, name);
483   read_group (lexer, pt, dim->root);
484 }
485
486 static bool
487 parse_bool_setting (struct lexer *lexer, const char *name,
488                     const char *true_kw, const char *false_kw,
489                     bool *out)
490 {
491   if (lex_match_id (lexer, name))
492     {
493       if (!lex_force_match (lexer, T_EQUALS))
494         exit (1);
495
496       if (lex_match_id (lexer, true_kw))
497         *out = true;
498       else if (lex_match_id (lexer, false_kw))
499         *out = false;
500       else
501         {
502           lex_error_expecting (lexer, true_kw, false_kw);
503           exit (1);
504         }
505
506       return true;
507     }
508   else
509     return false;
510 }
511
512 static void
513 read_look (struct lexer *lexer, struct pivot_table *pt)
514 {
515   lex_match (lexer, T_EQUALS);
516
517   if (lex_is_string (lexer))
518     {
519       struct pivot_table_look *look;
520       char *error = pivot_table_look_read (lex_tokcstr (lexer), &look);
521       if (error)
522         {
523           msg (SE, "%s", error);
524           exit (1);
525         }
526       lex_get (lexer);
527
528       pivot_table_set_look (pt, look);
529       pivot_table_look_unref (look);
530     }
531
532   struct pivot_table_look *look = pivot_table_look_unshare (
533     pivot_table_look_ref (pt->look));
534   for (;;)
535     {
536       if (!parse_bool_setting (lexer, "EMPTY", "HIDE", "SHOW",
537                                &look->omit_empty)
538           && !parse_bool_setting (lexer, "ROWLABELS", "CORNER", "NESTED",
539                                   &look->row_labels_in_corner)
540           && !parse_bool_setting (lexer, "MARKERS", "NUMERIC", "ALPHA",
541                                   &look->show_numeric_markers)
542           && !parse_bool_setting (lexer, "LEVEL", "SUPER", "SUB",
543                                   &look->footnote_marker_superscripts)
544           && !parse_bool_setting (lexer, "LAYERS", "CURRENT", "ALL",
545                                   &look->print_all_layers)
546           && !parse_bool_setting (lexer, "PAGINATELAYERS", "YES", "NO",
547                                   &look->paginate_layers)
548           && !parse_bool_setting (lexer, "HSHRINK", "YES", "NO",
549                                   &look->shrink_to_fit[TABLE_HORZ])
550           && !parse_bool_setting (lexer, "VSHRINK", "YES", "NO",
551                                   &look->shrink_to_fit[TABLE_VERT])
552           && !parse_bool_setting (lexer, "TOPCONTINUATION", "YES", "NO",
553                                   &look->top_continuation)
554           && !parse_bool_setting (lexer, "BOTTOMCONTINUATION", "YES", "NO",
555                                   &look->bottom_continuation)
556           && !parse_string_setting (lexer, "CONTINUATION",
557                                     &look->continuation))
558         break;
559     }
560   pivot_table_set_look (pt, look);
561   pivot_table_look_unref (look);
562 }
563
564 static enum table_stroke
565 read_stroke (struct lexer *lexer)
566 {
567   for (int stroke = 0; stroke < TABLE_N_STROKES; stroke++)
568     if (lex_match_id (lexer, table_stroke_to_string (stroke)))
569       return stroke;
570
571   lex_error (lexer, "expecting stroke");
572   exit (1);
573 }
574
575 static struct cell_color
576 read_color (struct lexer *lexer)
577 {
578   struct cell_color color;
579   if (!parse_color__ (lex_tokcstr (lexer), &color))
580     {
581       msg (SE, "%s: unknown color", lex_tokcstr (lexer));
582       exit (1);
583     }
584   lex_get (lexer);
585   return color;
586 }
587
588 static bool
589 parse_value_setting (struct lexer *lexer, const char *name,
590                      struct pivot_value **valuep)
591 {
592   if (lex_match_id (lexer, name))
593     {
594       lex_match (lexer, T_EQUALS);
595
596       pivot_value_destroy (*valuep);
597       *valuep = read_value (lexer);
598
599       return true;
600     }
601   else
602     return false;
603 }
604
605 static void
606 read_border (struct lexer *lexer, struct pivot_table *pt)
607 {
608   static const char *const pivot_border_ids[PIVOT_N_BORDERS] = {
609     [PIVOT_BORDER_TITLE] = "title",
610     [PIVOT_BORDER_OUTER_LEFT] = "outer-left",
611     [PIVOT_BORDER_OUTER_TOP] = "outer-top",
612     [PIVOT_BORDER_OUTER_RIGHT] = "outer-right",
613     [PIVOT_BORDER_OUTER_BOTTOM] = "outer-bottom",
614     [PIVOT_BORDER_INNER_LEFT] = "inner-left",
615     [PIVOT_BORDER_INNER_TOP] = "inner-top",
616     [PIVOT_BORDER_INNER_RIGHT] = "inner-right",
617     [PIVOT_BORDER_INNER_BOTTOM] = "inner-bottom",
618     [PIVOT_BORDER_DATA_LEFT] = "data-left",
619     [PIVOT_BORDER_DATA_TOP] = "data-top",
620     [PIVOT_BORDER_DIM_ROW_HORZ] = "dim-row-horz",
621     [PIVOT_BORDER_DIM_ROW_VERT] = "dim-row-vert",
622     [PIVOT_BORDER_DIM_COL_HORZ] = "dim-col-horz",
623     [PIVOT_BORDER_DIM_COL_VERT] = "dim-col-vert",
624     [PIVOT_BORDER_CAT_ROW_HORZ] = "cat-row-horz",
625     [PIVOT_BORDER_CAT_ROW_VERT] = "cat-row-vert",
626     [PIVOT_BORDER_CAT_COL_HORZ] = "cat-col-horz",
627     [PIVOT_BORDER_CAT_COL_VERT] = "cat-col-vert",
628   };
629
630   lex_match (lexer, T_EQUALS);
631
632   struct pivot_table_look *look = pivot_table_look_unshare (
633     pivot_table_look_ref (pt->look));
634   while (lex_token (lexer) == T_STRING)
635     {
636       char *s = xstrdup (lex_tokcstr (lexer));
637       lex_get (lexer);
638       if (!lex_force_match (lexer, T_LPAREN))
639         exit (1);
640
641       struct table_border_style style = TABLE_BORDER_STYLE_INITIALIZER;
642       style.stroke = read_stroke (lexer);
643       if (lex_is_string (lexer))
644         style.color = read_color (lexer);
645       if (!lex_force_match (lexer, T_RPAREN))
646         exit (1);
647
648       int n = 0;
649       for (int b = 0; b < PIVOT_N_BORDERS; b++)
650         {
651           if (!strncmp (s, pivot_border_ids[b], strlen (s)))
652             {
653               look->borders[b] = style;
654               n++;
655             }
656         }
657       if (!n)
658         {
659           msg (SE, "%s: no matching borders", s);
660           exit (1);
661         }
662       free (s);
663     }
664   pivot_table_set_look (pt, look);
665   pivot_table_look_unref (look);
666 }
667
668 static struct pivot_table *
669 read_table (struct lexer *lexer)
670 {
671   struct pivot_table *pt = pivot_table_create__ (NULL, NULL);
672   while (lex_match (lexer, T_SLASH))
673     {
674       assert (!pivot_table_is_shared (pt));
675
676       if (lex_match_id (lexer, "ROW"))
677         read_dimension (lexer, pt, PIVOT_AXIS_ROW);
678       else if (lex_match_id (lexer, "COLUMN"))
679         read_dimension (lexer, pt, PIVOT_AXIS_COLUMN);
680       else if (lex_match_id (lexer, "LAYER"))
681         read_dimension (lexer, pt, PIVOT_AXIS_LAYER);
682       else if (lex_match_id (lexer, "LOOK"))
683         read_look (lexer, pt);
684       else if (lex_match_id (lexer, "ROTATE"))
685         {
686           lex_match (lexer, T_EQUALS);
687           while (lex_token (lexer) == T_ID)
688             if (!parse_bool_setting (lexer, "INNERCOLUMNS", "YES", "NO",
689                                      &pt->rotate_inner_column_labels)
690                 && !parse_bool_setting (lexer, "OUTERROWS", "YES", "NO",
691                                         &pt->rotate_outer_row_labels))
692               break;
693         }
694       else if (lex_match_id (lexer, "DISPLAY"))
695         {
696           lex_match (lexer, T_EQUALS);
697           while (lex_token (lexer) == T_ID)
698             {
699               if (parse_bool_setting (lexer, "GRID", "YES", "NO",
700                                       &pt->show_grid_lines)
701                   || parse_bool_setting (lexer, "CAPTION", "YES", "NO",
702                                          &pt->show_caption)
703                   || parse_bool_setting (lexer, "TITLE", "YES", "NO",
704                                          &pt->show_title))
705                 continue;
706
707               if (parse_settings_value_show (lexer, "VALUES", &pt->show_values)
708                   || parse_settings_value_show (lexer, "VARIABLES",
709                                                 &pt->show_variables))
710                 continue;
711
712               break;
713             }
714         }
715       else if (parse_value_setting (lexer, "TITLE", &pt->title)
716                || parse_value_setting (lexer, "SUBTYPE", &pt->subtype)
717                || parse_value_setting (lexer, "CORNER", &pt->corner_text)
718                || parse_value_setting (lexer, "CAPTION", &pt->caption)
719                || parse_string_setting (lexer, "NOTES", &pt->notes))
720         {
721           /* Nothing. */
722         }
723       else if (lex_match_id (lexer, "BORDER"))
724         read_border (lexer, pt);
725       else
726         {
727           msg (SE, "Expecting keyword");
728           exit (1);
729         }
730     }
731
732   size_t *dindexes = xcalloc (pt->n_dimensions, sizeof *dindexes);
733   for (size_t i = 0; ; i++)
734     {
735       pivot_table_put (pt, dindexes, pt->n_dimensions,
736                        pivot_value_new_integer (i));
737
738       for (size_t j = 0; j < pt->n_dimensions; j++)
739         {
740           if (++dindexes[j] < pt->dimensions[j]->n_leaves)
741             goto next;
742           dindexes[j] = 0;
743         }
744         break;
745     next:;
746     }
747   free (dindexes);
748
749   if (!lex_force_match (lexer, T_ENDCMD))
750     exit (1);
751   return pt;
752 }
753
754 static void
755 output_msg (const struct msg *m_, void *lexer_)
756 {
757   struct lexer *lexer = lexer_;
758   struct msg m = *m_;
759
760   if (m.file_name == NULL)
761     {
762       m.file_name = CONST_CAST (char *, lex_get_file_name (lexer));
763       m.first_line = lex_get_first_line_number (lexer, 0);
764       m.last_line = lex_get_last_line_number (lexer, 0);
765     }
766
767   m.command_name = output_get_command_name ();
768
769   message_item_submit (message_item_create (&m));
770
771   free (m.command_name);
772 }