Fix assertion for proper Huffman merge pattern: 0 == 1 modulo 1.
[pspp] / src / devind.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 /* Device-independent output format.  Eventually I intend for all
21    PSPP output to work this way, but adding it as an available
22    format is a first step.
23
24    Each line in the output is a command.  The first character on
25    the line is the command name, and the rest of the line is the
26    command arguments.  The commands are described below as Perl
27    regular expressions:
28
29    #.*          comment
30
31    s            starts a new table
32    S[rc]\d+     table size in rows or columns (optional)
33    H[lrtb]\d+   number of left/right/top/bottom header rows/columns
34    B(\d+)-(\d+)/(\d+)
35                 allow column breaks every \3 rows from \1 to \2 exclusive
36    T.*          table title
37    C.*          table caption (not yet supported)
38    t(\d+)(-\d+)?,(\d+)(-\d+)?[wn][hb][lcr][tmb]:.*
39                 text for cells in rows (\1-\2) inclusive and
40                 columns (\3-\4) inclusive,
41                 wrappable/nonwrappable, header/body,
42                 left/center/right justified, top/middle/bottom
43                 justified
44    l[hv][sdtn](\d+),(\d+)-(\d+)
45                 horiz/vert line in single/double/thick/none
46                 style, running across columns/rows \2 to \3
47                 inclusive at offset \1 from top/left side of
48                 table
49    b[sdtno]{4}(\d+)-(\d+),(\d+)-(\d+)
50                 box across columns \1 to \2 inclusive and rows \3
51                 to \4 inclusive with
52                 single/double/thick/none/omit style for horiz &
53                 vert frame and horiz & vert interior lines
54    f(\d+),(\d+):.*
55                 add footnote for cell \1, \2
56    e            end table
57
58    v(\d(.\d+)+) insert \1 lines of blank space
59
60    p:.*         plain text
61    m[ewmlu]:(.*),(\d+),((\d+)(-\d+)?)?:(.*)
62                 error/warning/message/listing/user class message
63                 for file \1, line \2, columns \4 to \5, actual
64                 message \6
65
66    q            end of file
67
68    Text tokens are free-form, except that they are terminated by
69    commas and new-lines.  The following escapes are allowed:
70
71    \\n          line break
72    \\c          comma
73    \\s          non-breaking space
74    \\[0-7]{3}   octal escape
75    \\B          toggle subscript
76    \\P          toggle superscript
77    \\e          toggle emphasis
78    \\E          toggle strong emphasis
79    \\v          toggle variable name font
80    \\F          toggle file name font
81    \\p          toggle fixed-pitch text font (default: proportional)
82    \\n\((\d+)?(\.\d+)?(-?\d+(\.\d+)?+(e-?\d+))?\)
83                 number \3 (sysmis if not provided) in \1.\2 format
84    \\f\(([A-Z]*(\d+)?(\.\d+)?)(-?\d+(\.\d+)?+(e-?\d+))?\)
85                 number \1 in \4 format
86
87 */
88
89 #include <config.h>
90 #include "devind.h"
91 #include "error.h"
92 #include <errno.h>
93 #include <stdlib.h>
94 #include <ctype.h>
95 #include <time.h>
96
97 #if HAVE_UNISTD_H
98 #include <unistd.h>
99 #endif
100
101 #include "alloc.h"
102 #include "error.h"
103 #include "filename.h"
104 #include "getline.h"
105 #include "output.h"
106 #include "som.h"
107 #include "tab.h"
108 #include "version.h"
109
110 /* Device-independent output driver extension record. */
111 struct devind_driver_ext
112   {
113     /* Internal state. */
114     struct file_ext file;       /* Output file. */
115     int sequence_no;            /* Sequence number. */
116   };
117
118 static int
119 devind_open_global (struct outp_class *this UNUSED)
120 {
121   return 1;
122 }
123
124 static int
125 devind_close_global (struct outp_class *this UNUSED)
126 {
127   return 1;
128 }
129
130 static int
131 devind_preopen_driver (struct outp_driver *this)
132 {
133   struct devind_driver_ext *x;
134
135   assert (this->driver_open == 0);
136   msg (VM (1), _("DEVIND driver initializing as `%s'..."), this->name);
137
138   this->ext = x = xmalloc (sizeof *x);
139   this->res = 0;
140   this->horiz = this->vert = 0;
141   this->width = this->length = 0;
142
143   this->cp_x = this->cp_y = 0;
144
145   x->file.filename = NULL;
146   x->file.mode = "w";
147   x->file.file = NULL;
148   x->file.sequence_no = &x->sequence_no;
149   x->file.param = this;
150   x->file.postopen = NULL;
151   x->file.preclose = NULL;
152
153   x->sequence_no = 0;
154
155   return 1;
156 }
157
158 static int
159 devind_postopen_driver (struct outp_driver *this)
160 {
161   struct devind_driver_ext *x = this->ext;
162
163   assert (this->driver_open == 0);
164   if (NULL == x->file.filename)
165     x->file.filename = xstrdup ("pspp.devind");
166         
167   msg (VM (2), _("%s: Initialization complete."), this->name);
168   this->driver_open = 1;
169
170   return 1;
171 }
172
173 static int
174 devind_close_driver (struct outp_driver *this)
175 {
176   struct devind_driver_ext *x = this->ext;
177
178   assert (this->driver_open);
179   msg (VM (2), _("%s: Beginning closing..."), this->name);
180   fputs ("q\n", x->file.file);
181   fn_close_ext (&x->file);
182   free (x->file.filename);
183   free (x);
184   msg (VM (3), _("%s: Finished closing."), this->name);
185   this->driver_open = 0;
186   
187   return 1;
188 }
189
190 /* Generic option types. */
191 enum
192 {
193   boolean_arg = -10,
194   string_arg,
195   nonneg_int_arg
196 };
197
198 /* All the options that the DEVIND driver supports. */
199 static struct outp_option option_tab[] =
200 {
201   /* *INDENT-OFF* */
202   {"output-file",               1,              0},
203   {"", 0, 0},
204   /* *INDENT-ON* */
205 };
206 static struct outp_option_info option_info;
207
208 static void
209 devind_option (struct outp_driver *this, const char *key, const struct string *val)
210 {
211   struct devind_driver_ext *x = this->ext;
212   int cat, subcat;
213
214   cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
215   switch (cat)
216     {
217     case 0:
218       msg (SE, _("Unknown configuration parameter `%s' for DEVIND device "
219            "driver."), key);
220       break;
221     case 1:
222       free (x->file.filename);
223       x->file.filename = xstrdup (ds_c_str (val));
224       break;
225     default:
226       assert (0);
227     }
228 }
229
230 static int
231 devind_open_page (struct outp_driver *this)
232 {
233   struct devind_driver_ext *x = this->ext;
234
235   assert (this->driver_open && this->page_open == 0);
236   x->sequence_no++;
237   if (!fn_open_ext (&x->file))
238     {
239       if (errno)
240         msg (ME, _("DEVIND output driver: %s: %s"), x->file.filename,
241              strerror (errno));
242       return 0;
243     }
244
245   if (!ferror (x->file.file))
246     this->page_open = 1;
247   return !ferror (x->file.file);
248 }
249
250 static int
251 devind_close_page (struct outp_driver *this)
252 {
253   struct devind_driver_ext *x = this->ext;
254
255   assert (this->driver_open && this->page_open);
256   this->page_open = 0;
257   return !ferror (x->file.file);
258 }
259
260 static void output_tab_table (struct outp_driver *, struct tab_table *);
261
262 static void
263 devind_submit (struct outp_driver *this, struct som_table *s)
264 {
265   extern struct som_table_class tab_table_class;
266   struct devind_driver_ext *x = this->ext;
267   
268   assert (this->driver_open && this->page_open);
269   if (x->sequence_no == 0 && !devind_open_page (this))
270     {
271       msg (ME, _("Cannot open first page on DEVIND device %s."), this->name);
272       return;
273     }
274
275   if (s->class == &tab_table_class)
276     output_tab_table (this, s->ext);
277   else
278     assert (0);
279 }
280
281 /* Write string S of length LEN to file F, escaping characters as
282    necessary for DEVIND. */
283 static void
284 escape_string (FILE *f, char *s, int len)
285 {
286   char *ep = &s[len];
287   char *bp, *cp;
288
289   putc (':', f);
290
291   for (bp = cp = s; bp < ep; bp = cp)
292     {
293       while (cp < ep && *cp != ',' && *cp != '\n' && *cp)
294         cp++;
295       if (cp > bp)
296         fwrite (bp, 1, cp - bp, f);
297       if (cp < ep)
298         switch (*cp++)
299           {
300           case ',':
301             fputs ("\\c", f);
302             break;
303           case '\n':
304             fputs ("\\n", f);
305             break;
306           case 0:
307             break;
308           default:
309             assert (0);
310           }
311     }
312 }
313   
314 /* Write table T to THIS output driver. */
315 static void
316 output_tab_table (struct outp_driver *this, struct tab_table *t)
317 {
318   struct devind_driver_ext *x = this->ext;
319   
320   if (t->nr == 1 && t->nc == 1)
321     {
322       fputs ("p:", x->file.file);
323       escape_string (x->file.file, ls_c_str (t->cc), ls_length (t->cc));
324       putc ('\n', x->file.file);
325       
326       return;
327     }
328
329   /* Start table. */
330   fprintf (x->file.file, "s\n");
331
332   /* Table size. */
333   fprintf (x->file.file, "Sr%d\n", t->nr);
334   fprintf (x->file.file, "Sc%d\n", t->nc);
335
336   /* Table headers. */
337   if (t->l != 0)
338     fprintf (x->file.file, "Hl%d\n", t->l);
339   if (t->r != 0)
340     fprintf (x->file.file, "Hr%d\n", t->r);
341   if (t->t != 0)
342     fprintf (x->file.file, "Ht%d\n", t->t);
343   if (t->b != 0)
344     fprintf (x->file.file, "Hb%d\n", t->b);
345
346   /* Title. */
347   if (!ls_empty_p (&t->title))
348     {
349       putc ('T', x->file.file);
350       escape_string (x->file.file, ls_c_str (&t->title),
351                      ls_length (&t->title));
352       putc ('\n', x->file.file);
353     }
354
355   /* Column breaks. */
356   if (t->col_style == TAB_COL_DOWN) 
357     fprintf (x->file.file, "B%d-%d/%d\n", t->t, t->nr - t->b, t->col_group);
358
359   /* Table text. */
360   {
361     int r;
362     unsigned char *ct = t->ct;
363
364     for (r = 0; r < t->nr; r++)
365       {
366         int c;
367         
368         for (c = 0; c < t->nc; c++, ct++)
369           {
370             struct len_string *cc;
371             struct tab_joined_cell *j;
372
373             if (*ct == TAB_EMPTY)
374               continue;
375             
376             cc = t->cc + c + r * t->nc;
377             if (*ct & TAB_JOIN) 
378               {
379                 j = (struct tab_joined_cell *) ls_c_str (cc);
380                 cc = &j->contents;
381                 if (c != j->x1 || r != j->y1)
382                   continue;
383               }
384             else
385               j = NULL;
386
387             putc ('t', x->file.file);
388             if (j == NULL) 
389               fprintf (x->file.file, "%d,%d", r, c);
390             else
391               fprintf (x->file.file, "%d-%d,%d-%d",
392                        j->y1, j->y2, j->x1, j->x2);
393             putc ((*ct & TAT_NOWRAP) ? 'n' : 'w', x->file.file);
394             putc ((*ct & TAT_TITLE) ? 'h' : 'b', x->file.file);
395             if ((*ct & TAB_ALIGN_MASK) == TAB_RIGHT)
396               putc ('r', x->file.file);
397             else if ((*ct & TAB_ALIGN_MASK) == TAB_LEFT)
398               putc ('l', x->file.file);
399             else
400               putc ('c', x->file.file);
401             putc ('t', x->file.file);
402             escape_string (x->file.file, ls_c_str (cc), ls_length (cc));
403             putc ('\n', x->file.file);
404           }
405       }
406   }
407
408   /* Horizontal lines. */
409   {
410     int r, c;
411
412     for (r = 0; r <= t->nr; r++)
413       for (c = 0; c < t->nc; c++) 
414         {
415           int rule = t->rh[c + r * t->nc];
416           if (rule != 0)
417             fprintf (x->file.file, "lh%c%d,%d-%d\n", "nsdt"[rule], r, c, c);
418         }
419   }
420
421   /* Vertical lines. */
422   {
423     int r, c;
424
425     for (r = 0; r < t->nr; r++)
426       for (c = 0; c <= t->nc; c++) 
427         {
428           int rule = t->rv[c + r * (t->nc + 1)];
429           if (rule != 0)
430             fprintf (x->file.file, "lv%c%d,%d-%d\n", "nsdt"[rule], c, r, r);
431         }
432   }
433
434   /* End of table. */
435   fputs ("e\n", x->file.file);
436 }
437
438 /* DEVIND driver class. */
439 struct outp_class devind_class =
440 {
441   "devind",
442   0xb1e7,
443   1,
444
445   devind_open_global,
446   devind_close_global,
447   NULL,
448
449   devind_preopen_driver,
450   devind_option,
451   devind_postopen_driver,
452   devind_close_driver,
453
454   devind_open_page,
455   devind_close_page,
456
457   devind_submit,
458
459   NULL,
460   NULL,
461   NULL,
462
463   NULL,
464   NULL,
465   NULL,
466   NULL,
467
468   NULL,
469   NULL,
470   NULL,
471   NULL,
472   NULL,
473   NULL,
474   NULL,
475   NULL,
476   NULL,
477 };