Initial release of Maemo 5 port of gnuplot
[gnuplot] / src / misc.c
1 #ifndef lint
2 static char *RCSid() { return RCSid("$Id: misc.c,v 1.81.2.4 2008/10/29 17:20:25 sfeam Exp $"); }
3 #endif
4
5 /* GNUPLOT - misc.c */
6
7 /*[
8  * Copyright 1986 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
9  *
10  * Permission to use, copy, and distribute this software and its
11  * documentation for any purpose with or without fee is hereby granted,
12  * provided that the above copyright notice appear in all copies and
13  * that both that copyright notice and this permission notice appear
14  * in supporting documentation.
15  *
16  * Permission to modify the software is granted, but not the right to
17  * distribute the complete modified source code.  Modifications are to
18  * be distributed as patches to the released version.  Permission to
19  * distribute binaries produced by compiling modified sources is granted,
20  * provided you
21  *   1. distribute the corresponding source modifications from the
22  *    released version in the form of a patch file along with the binaries,
23  *   2. add special version identification to distinguish your version
24  *    in addition to the base release version number,
25  *   3. provide your name and address as the primary contact for the
26  *    support of your modified version, and
27  *   4. retain our contact information in regard to use of the base
28  *    software.
29  * Permission to distribute the released version of the source code along
30  * with corresponding source modifications in the form of a patch file is
31  * granted with same provisions 2 through 4 for binary distributions.
32  *
33  * This software is provided "as is" without express or implied warranty
34  * to the extent permitted by applicable law.
35 ]*/
36
37 #include "misc.h"
38
39 #include "alloc.h"
40 #include "command.h"
41 #include "graphics.h"
42 #include "parse.h"              /* for const_express() */
43 #include "plot.h"
44 #include "tables.h"
45 #include "util.h"
46 #include "variable.h"
47 #include "axis.h"
48
49 #if defined(HAVE_DIRENT_H)
50 # include <sys/types.h>
51 # include <dirent.h>
52 #elif defined(_Windows)
53 # include <windows.h>
54 #endif
55
56 /* name of command file; NULL if terminal */
57 char *infile_name = NULL;
58
59 static TBOOLEAN lf_pop __PROTO((void));
60 static void lf_push __PROTO((FILE *));
61 static void arrow_use_properties __PROTO((struct arrow_style_type *arrow, int tag));
62 static char *recursivefullname __PROTO((const char *path, const char *filename, TBOOLEAN recursive));
63
64 /* A copy of the declaration from set.c */
65 /* There should only be one declaration in a header file. But I do not know
66  * where to put it */
67 /* void get_position __PROTO((struct position * pos)); */
68
69 /* State information for load_file(), to recover from errors
70  * and properly handle recursive load_file calls
71  */
72 LFS *lf_head = NULL;            /* NULL if not in load_file */
73
74 /* these two could be in load_file, except for error recovery */
75 static TBOOLEAN do_load_arg_substitution = FALSE;
76 static char *call_args[10] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
77
78 /*
79  * iso_alloc() allocates a iso_curve structure that can hold 'num'
80  * points.
81  */
82 struct iso_curve *
83 iso_alloc(int num)
84 {
85     struct iso_curve *ip;
86     ip = (struct iso_curve *) gp_alloc(sizeof(struct iso_curve), "iso curve");
87     ip->p_max = (num >= 0 ? num : 0);
88     if (num > 0) {
89         ip->points = (struct coordinate GPHUGE *)
90             gp_alloc(num * sizeof(struct coordinate), "iso curve points");
91     } else
92         ip->points = (struct coordinate GPHUGE *) NULL;
93     ip->next = NULL;
94     return (ip);
95 }
96
97 /*
98  * iso_extend() reallocates a iso_curve structure to hold "num"
99  * points. This will either expand or shrink the storage.
100  */
101 void
102 iso_extend(struct iso_curve *ip, int num)
103 {
104     if (num == ip->p_max)
105         return;
106
107 #if defined(DOS16) || defined(WIN16)
108     /* Make sure we do not allocate more than 64k points in msdos since
109        * indexing is done with 16-bit int
110        * Leave some bytes for malloc maintainance.
111      */
112     if (num > 32700)
113         int_error(NO_CARET, "Array index must be less than 32k in msdos");
114 #endif /* 16bit (Win)Doze */
115
116     if (num > 0) {
117         if (ip->points == NULL) {
118             ip->points = (struct coordinate GPHUGE *)
119                 gp_alloc(num * sizeof(struct coordinate), "iso curve points");
120         } else {
121             ip->points = (struct coordinate GPHUGE *)
122                 gp_realloc(ip->points, num * sizeof(struct coordinate), "expanding curve points");
123         }
124         ip->p_max = num;
125     } else {
126         if (ip->points != (struct coordinate GPHUGE *) NULL)
127             free(ip->points);
128         ip->points = (struct coordinate GPHUGE *) NULL;
129         ip->p_max = 0;
130     }
131 }
132
133 /*
134  * iso_free() releases any memory which was previously malloc()'d to hold
135  *   iso curve points.
136  */
137 void
138 iso_free(struct iso_curve *ip)
139 {
140     if (ip) {
141         if (ip->points)
142             free((char *) ip->points);
143         free((char *) ip);
144     }
145 }
146
147 void
148 load_file(FILE *fp, char *name, TBOOLEAN can_do_args)
149 {
150     int len;
151
152     int start, left;
153     int more;
154     int stop = FALSE;
155
156     lf_push(fp);                /* save state for errors and recursion */
157     do_load_arg_substitution = can_do_args;
158
159     if (fp == (FILE *) NULL) {
160         os_error(c_token, "Cannot open %s file '%s'",
161                  can_do_args ? "call" : "load", name);
162     } else if (fp == stdin) {
163         /* DBT 10-6-98  go interactive if "-" named as load file */
164         interactive = TRUE;
165         while (!com_line());
166     } else {
167         /* go into non-interactive mode during load */
168         /* will be undone below, or in load_file_error */
169         int argc = 0; /* number arguments passed by "call" */
170         interactive = FALSE;
171         inline_num = 0;
172         infile_name = name;
173
174         if (can_do_args) {
175             while (c_token < num_tokens && argc <= 9) {
176                 if (isstring(c_token))
177                     m_quote_capture(&call_args[argc++], c_token, c_token);
178                 else
179                     m_capture(&call_args[argc++], c_token, c_token);
180                 c_token++;
181             }
182             /* Gnuplot "call" command can have up to 10 arguments named "$0"
183                to "$9". After reading the 10th argument (i.e., "$9") the
184                variable 'argc' equals 10.
185                Also, "call" must be the last command on the command line.
186             */
187             if (c_token < num_tokens)
188                 int_error(++c_token, "too many arguments for 'call <file>'");
189         }
190         while (!stop) {         /* read all commands in file */
191             /* read one command */
192             left = gp_input_line_len;
193             start = 0;
194             more = TRUE;
195
196             while (more) {
197                 if (fgets(&(gp_input_line[start]), left, fp) == (char *) NULL) {
198                     stop = TRUE;        /* EOF in file */
199                     gp_input_line[start] = '\0';
200                     more = FALSE;
201                 } else {
202                     inline_num++;
203                     len = strlen(gp_input_line) - 1;
204                     if (gp_input_line[len] == '\n') {   /* remove any newline */
205                         gp_input_line[len] = '\0';
206                         /* Look, len was 1-1 = 0 before, take care here! */
207                         if (len > 0)
208                             --len;
209                         if (gp_input_line[len] == '\r') {       /* remove any carriage return */
210                             gp_input_line[len] = NUL;
211                             if (len > 0)
212                                 --len;
213                         }
214                     } else if (len + 2 >= left) {
215                         extend_input_line();
216                         left = gp_input_line_len - len - 1;
217                         start = len + 1;
218                         continue;       /* don't check for '\' */
219                     }
220                     if (gp_input_line[len] == '\\') {
221                         /* line continuation */
222                         start = len;
223                         left = gp_input_line_len - start;
224                     } else
225                         more = FALSE;
226                 }
227             }
228
229             if (strlen(gp_input_line) > 0) {
230                 if (can_do_args) {
231                     int il = 0;
232                     char *rl;
233                     char *raw_line = gp_strdup(gp_input_line);
234
235                     rl = raw_line;
236                     *gp_input_line = '\0';
237                     while (*rl) {
238                         int aix;
239                         if (*rl == '$'
240                             && ((aix = *(++rl)) != 0)   /* HBB 980308: quiet BCC warning */
241                             && ((aix >= '0' && aix <= '9') || aix == '#')) {
242                             if (aix == '#') {
243                                 /* replace $# by number of passed arguments */
244                                 len = argc < 10 ? 1 : 2; /* argc can be 0 .. 10 */
245                                 while (gp_input_line_len - il < len + 1) {
246                                     extend_input_line();
247                                 }
248                                 sprintf(gp_input_line + il, "%i", argc);
249                                 il += len;
250                             } else
251                             if (call_args[aix -= '0']) {
252                                 /* replace $n for n=0..9 by the passed argument */
253                                 len = strlen(call_args[aix]);
254                                 while (gp_input_line_len - il < len + 1) {
255                                     extend_input_line();
256                                 }
257                                 strcpy(gp_input_line + il, call_args[aix]);
258                                 il += len;
259                             }
260                         } else {
261                             /* substitute for $<n> here */
262                             if (il + 1 > gp_input_line_len) {
263                                 extend_input_line();
264                             }
265                             gp_input_line[il++] = *rl;
266                         }
267                         rl++;
268                     }
269                     if (il + 1 > gp_input_line_len) {
270                         extend_input_line();
271                     }
272                     gp_input_line[il] = '\0';
273                     free(raw_line);
274                 }
275                 screen_ok = FALSE;      /* make sure command line is
276                                            echoed on error */
277                 if (do_line())
278                     stop = TRUE;
279             }
280         }
281     }
282
283     /* pop state */
284     (void) lf_pop();            /* also closes file fp */
285 }
286
287 /* pop from load_file state stack
288    FALSE if stack was empty
289    called by load_file and load_file_error */
290 static TBOOLEAN
291 lf_pop()
292 {
293     LFS *lf;
294
295     if (lf_head == NULL)
296         return (FALSE);
297     else {
298         int argindex;
299         lf = lf_head;
300         if (lf->fp != (FILE *) NULL && lf->fp != stdin) {
301             /* DBT 10-6-98  do not close stdin in the case
302              * that "-" is named as a load file
303              */
304             (void) fclose(lf->fp);
305         }
306         for (argindex = 0; argindex < 10; argindex++) {
307             if (call_args[argindex]) {
308                 free(call_args[argindex]);
309             }
310             call_args[argindex] = lf->call_args[argindex];
311         }
312         do_load_arg_substitution = lf->do_load_arg_substitution;
313         interactive = lf->interactive;
314         inline_num = lf->inline_num;
315         infile_name = lf->name;
316         lf_head = lf->prev;
317         free((char *) lf);
318         return (TRUE);
319     }
320 }
321
322 /* push onto load_file state stack
323    essentially, we save information needed to undo the load_file changes
324    called by load_file */
325 static void
326 lf_push(FILE *fp)
327 {
328     LFS *lf;
329     int argindex;
330
331     lf = (LFS *) gp_alloc(sizeof(LFS), (char *) NULL);
332     if (lf == (LFS *) NULL) {
333         if (fp != (FILE *) NULL)
334             (void) fclose(fp);  /* it won't be otherwise */
335         int_error(c_token, "not enough memory to load file");
336     }
337     lf->fp = fp;                /* save this file pointer */
338     lf->name = infile_name;     /* save current name */
339     lf->interactive = interactive;      /* save current state */
340     lf->inline_num = inline_num;        /* save current line number */
341     lf->do_load_arg_substitution = do_load_arg_substitution;
342     for (argindex = 0; argindex < 10; argindex++) {
343         lf->call_args[argindex] = call_args[argindex];
344         call_args[argindex] = NULL;     /* initially no args */
345     }
346     lf->prev = lf_head;         /* link to stack */
347     lf_head = lf;
348 }
349
350 /* used for reread  vsnyder@math.jpl.nasa.gov */
351 FILE *
352 lf_top()
353 {
354     if (lf_head == (LFS *) NULL)
355         return ((FILE *) NULL);
356     return (lf_head->fp);
357 }
358
359 /* called from main */
360 void
361 load_file_error()
362 {
363     /* clean up from error in load_file */
364     /* pop off everything on stack */
365     while (lf_pop());
366 }
367
368 /* find max len of keys and count keys with len > 0 */
369
370 /* FIXME HBB 2000508: by design, this one belongs into 'graphics', and the
371  * next to into 'graph3d'. Actually, the existence of a module like this
372  * 'misc' is almost always a sign of bad design, IMHO */
373 /* may return NULL */
374 FILE *
375 loadpath_fopen(const char *filename, const char *mode)
376 {
377     FILE *fp;
378
379 #if defined(PIPES)
380     if (*filename == '<') {
381         if ((fp = popen(filename + 1, "r")) == (FILE *) NULL)
382             return (FILE *) 0;
383     } else
384 #endif /* PIPES */
385     if ((fp = fopen(filename, mode)) == (FILE *) NULL) {
386         /* try 'loadpath' variable */
387         char *fullname = NULL, *path;
388
389         while ((path = get_loadpath()) != NULL) {
390             /* length of path, dir separator, filename, \0 */
391             fullname = gp_realloc(fullname, strlen(path) + 1 + strlen(filename) + 1, "loadpath_fopen");
392             strcpy(fullname, path);
393             PATH_CONCAT(fullname, filename);
394             if ((fp = fopen(fullname, mode)) != NULL) {
395                 free(fullname);
396                 fullname = NULL;
397                 /* reset loadpath internals!
398                  * maybe this can be replaced by calling get_loadpath with
399                  * a NULL argument and some loadpath_handler internal logic */
400                 while (get_loadpath());
401                 break;
402             }
403         }
404
405         if (fullname)
406             free(fullname);
407
408     }
409
410     return fp;
411 }
412
413
414 /* Harald Harders <h.harders@tu-bs.de> */
415 /* Thanks to John Bollinger <jab@bollingerbands.com> who has tested the
416    windows part */
417 static char *
418 recursivefullname(const char *path, const char *filename, TBOOLEAN recursive)
419 {
420     char *fullname = NULL;
421     FILE *fp;
422
423     /* length of path, dir separator, filename, \0 */
424     fullname = gp_alloc(strlen(path) + 1 + strlen(filename) + 1,
425                         "recursivefullname");
426     strcpy(fullname, path);
427     PATH_CONCAT(fullname, filename);
428
429     if ((fp = fopen(fullname, "r")) != NULL) {
430         fclose(fp);
431         return fullname;
432     } else {
433         free(fullname);
434         fullname = NULL;
435     }
436
437     if (recursive) {
438 #ifdef HAVE_DIRENT_H
439         DIR *dir;
440         struct dirent *direntry;
441         struct stat buf;
442
443         dir = opendir(path);
444         if (dir) {
445             while ((direntry = readdir(dir)) != NULL) {
446                 char *fulldir = gp_alloc(strlen(path) + 1 + strlen(direntry->d_name) + 1,
447                                          "fontpath_fullname");
448                 strcpy(fulldir, path);
449 #  if defined(VMS)
450                 if (fulldir[strlen(fulldir) - 1] == ']')
451                     fulldir[strlen(fulldir) - 1] = '\0';
452                 strcpy(&(fulldir[strlen(fulldir)]), ".");
453                 strcpy(&(fulldir[strlen(fulldir)]), direntry->d_name);
454                 strcpy(&(fulldir[strlen(fulldir)]), "]");
455 #  else
456                 PATH_CONCAT(fulldir, direntry->d_name);
457 #  endif
458                 stat(fulldir, &buf);
459                 if ((S_ISDIR(buf.st_mode)) &&
460                     (strcmp(direntry->d_name, ".") != 0) &&
461                     (strcmp(direntry->d_name, "..") != 0)) {
462                     fullname = recursivefullname(fulldir, filename, TRUE);
463                     if (fullname != NULL)
464                         break;
465                 }
466                 free(fulldir);
467             }
468             closedir(dir);
469         }
470 #elif defined(_Windows)
471         HANDLE filehandle;
472         WIN32_FIND_DATA finddata;
473         char *pathwildcard = gp_alloc(strlen(path) + 2, "fontpath_fullname");
474
475         strcpy(pathwildcard, path);
476         PATH_CONCAT(pathwildcard, "*");
477
478         filehandle = FindFirstFile(pathwildcard, &finddata);
479         free(pathwildcard);
480         if (filehandle != INVALID_HANDLE_VALUE)
481             do {
482                 if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
483                     (strcmp(finddata.cFileName, ".") != 0) &&
484                     (strcmp(finddata.cFileName, "..") != 0)) {
485                     char *fulldir = gp_alloc(strlen(path) + 1 + strlen(finddata.cFileName) + 1,
486                                              "fontpath_fullname");
487                     strcpy(fulldir, path);
488                     PATH_CONCAT(fulldir, finddata.cFileName);
489
490                     fullname = recursivefullname(fulldir, filename, TRUE);
491                     free(fulldir);
492                     if (fullname != NULL)
493                         break;
494                 }
495             } while (FindNextFile(filehandle, &finddata) != 0);
496         FindClose(filehandle);
497
498 #else
499         int_warn(NO_CARET, "Recursive directory search not supported\n\t('%s!')", path);
500 #endif
501     }
502     return fullname;
503 }
504
505
506 /* may return NULL */
507 char *
508 fontpath_fullname(const char *filename)
509 {
510     FILE *fp;
511     char *fullname = NULL;
512
513 #if defined(PIPES)
514     if (*filename == '<') {
515         os_error(NO_CARET, "fontpath_fullname: No Pipe allowed");
516     } else
517 #endif /* PIPES */
518     if ((fp = fopen(filename, "r")) == (FILE *) NULL) {
519         /* try 'fontpath' variable */
520         char *tmppath, *path = NULL;
521
522         while ((tmppath = get_fontpath()) != NULL) {
523             TBOOLEAN subdirs = FALSE;
524             path = gp_strdup(tmppath);
525             if (path[strlen(path) - 1] == '!') {
526                 path[strlen(path) - 1] = '\0';
527                 subdirs = TRUE;
528             }                   /* if */
529             fullname = recursivefullname(path, filename, subdirs);
530             if (fullname != NULL) {
531                 while (get_fontpath());
532                 free(path);
533                 break;
534             }
535             free(path);
536         }
537
538     } else
539         fullname = gp_strdup(filename);
540
541     return fullname;
542 }
543
544
545 /* Push current terminal.
546  * Called 1. in main(), just after init_terminal(),
547  *        2. from load_rcfile(),
548  *        3. anytime by user command "set term push".
549  */
550 static char *push_term_name = NULL;
551 static char *push_term_opts = NULL;
552
553 void
554 push_terminal(int is_interactive)
555 {
556     if (term) {
557         free(push_term_name);
558         free(push_term_opts);
559         push_term_name = gp_strdup(term->name);
560         push_term_opts = gp_strdup(term_options);
561         if (is_interactive)
562             fprintf(stderr, "   pushed terminal %s %s\n", push_term_name, push_term_opts);
563     } else {
564         if (is_interactive)
565             fputs("\tcurrent terminal type is unknown\n", stderr);
566     }
567 }
568
569 /* Pop the terminal.
570  * Called anytime by user command "set term pop".
571  */
572 void
573 pop_terminal()
574 {
575     if (push_term_name != NULL) {
576         char *s;
577         int i = strlen(push_term_name) + 11;
578         if (push_term_opts) {
579             /* do_string() does not like backslashes -- thus remove them */
580             for (s=push_term_opts; *s; s++)
581                 if (*s=='\\' || *s=='\n') *s=' ';
582             i += strlen(push_term_opts);
583         }
584         s = gp_alloc(i, "pop");
585         i = interactive;
586         interactive = 0;
587         sprintf(s,"set term %s %s", push_term_name, (push_term_opts ? push_term_opts : ""));
588         do_string(s);
589         free(s);
590         interactive = i;
591         if (interactive)
592             fprintf(stderr,"   restored terminal is %s %s\n", term->name, ((*term_options) ? term_options : ""));
593     } else
594         fprintf(stderr,"No terminal has been pushed yet\n");
595 }
596
597
598 /* Parse a plot style. Used by 'set style {data|function}' and by
599  * (s)plot.  */
600 enum PLOT_STYLE
601 get_style()
602 {
603     /* defined in plot.h */
604     enum PLOT_STYLE ps;
605
606     c_token++;
607
608     ps = lookup_table(&plotstyle_tbl[0], c_token);
609
610     c_token++;
611
612     if (ps == -1) {
613         int_error(c_token, "\
614 expecting 'lines', 'points', 'linespoints', 'dots', 'impulses',\n\
615 \t'yerrorbars', 'xerrorbars', 'xyerrorbars', 'steps', 'fsteps',\n\
616 \t'histeps', 'filledcurves', 'boxes', 'boxerrorbars', 'boxxyerrorbars',\n\
617 \t'vectors', 'financebars', 'candlesticks', 'errorlines', 'xerrorlines',\n\
618 \t'yerrorlines', 'xyerrorlines', 'pm3d', 'labels', 'histograms'"
619 #ifdef WITH_IMAGE
620 ",\n\t 'image', 'rgbimage'"
621 #endif
622 );
623         ps = LINES;
624     }
625
626     return ps;
627 }
628
629 /* Parse options for style filledcurves and fill fco accordingly.
630  * If no option given, then set fco->opt_given to 0.
631  */
632 void
633 get_filledcurves_style_options(filledcurves_opts *fco)
634 {
635     int p;
636     struct value a;
637     p = lookup_table(&filledcurves_opts_tbl[0], c_token);
638
639     if (p == FILLEDCURVES_ABOVE) {
640         fco->oneside = 1;
641         p = lookup_table(&filledcurves_opts_tbl[0], ++c_token);
642     } else if (p == FILLEDCURVES_BELOW) {
643         fco->oneside = -1;
644         p = lookup_table(&filledcurves_opts_tbl[0], ++c_token);
645     } else
646         fco->oneside = 0;
647
648     if (p == -1) {
649         fco->opt_given = 0;
650         return;                 /* no option given */
651     } else
652         fco->opt_given = 1;
653
654     c_token++;
655
656     fco->closeto = p;
657     if (!equals(c_token, "="))
658         return;
659     /* parameter required for filledcurves x1=... and friends */
660     if (p != FILLEDCURVES_ATXY)
661         fco->closeto += 4;
662     c_token++;
663     fco->at = real(const_express(&a));
664     if (p != FILLEDCURVES_ATXY)
665         return;
666     /* two values required for FILLEDCURVES_ATXY */
667     if (!equals(c_token, ","))
668         int_error(c_token, "syntax is xy=<x>,<y>");
669     c_token++;
670     fco->aty = real(const_express(&a));
671     return;
672 }
673
674 /* Print filledcurves style options to a file (used by 'show' and 'save'
675  * commands).
676  */
677 void
678 filledcurves_options_tofile(filledcurves_opts *fco, FILE *fp)
679 {
680     if (!fco->opt_given)
681         return;
682     if (fco->oneside)
683         fputs(fco->oneside > 0 ? "above " : "below ", fp);
684     if (fco->closeto == FILLEDCURVES_CLOSED) {
685         fputs("closed", fp);
686         return;
687     }
688     if (fco->closeto <= FILLEDCURVES_Y2) {
689         fputs(filledcurves_opts_tbl[fco->closeto].key, fp);
690         return;
691     }
692     if (fco->closeto <= FILLEDCURVES_ATY2) {
693         fprintf(fp, "%s=%g", filledcurves_opts_tbl[fco->closeto - 4].key, fco->at);
694         return;
695     }
696     if (fco->closeto == FILLEDCURVES_ATXY) {
697         fprintf(fp, "xy=%g,%g", fco->at, fco->aty);
698         return;
699     }
700 }
701
702 /* line/point parsing...
703  *
704  * allow_ls controls whether we are allowed to accept linestyle in
705  * the current context [ie not when doing a  set linestyle command]
706  * allow_point is whether we accept a point command
707  * We assume compiler will optimise away if(0) or if(1)
708  */
709
710 void
711 lp_use_properties(struct lp_style_type *lp, int tag, int pointflag)
712 {
713     /*  This function looks for a linestyle defined by 'tag' and copies
714      *  its data into the structure 'lp'.
715      *
716      *  If 'pointflag' equals ZERO, the properties belong to a linestyle
717      *  used with arrows.  In this case no point properties will be
718      *  passed to the terminal (cf. function 'term_apply_lp_properties' below).
719      */
720
721     struct linestyle_def *this;
722
723     this = first_linestyle;
724     while (this != NULL) {
725         if (this->tag == tag) {
726             *lp = this->lp_properties;
727             /* FIXME - It would be nicer if this were always true already */
728             if (!lp->use_palette) {
729                 lp->pm3d_color.type = TC_LT;
730                 lp->pm3d_color.lt = lp->l_type;
731             }
732             lp->pointflag = pointflag;
733             return;
734         } else {
735             this = this->next;
736         }
737     }
738
739     /* tag not found: */
740     /* Mar 2006 - This used to be a fatal error; now we fall back to line type */
741     lp->l_type = tag - 1;
742     lp->pm3d_color.type = TC_LT;
743     lp->pm3d_color.lt = lp->l_type;
744     lp->p_type = tag - 1;
745 }
746
747
748 /* allow any order of options - pm 24.11.2001 */
749 /* EAM Oct 2005 - Require that default values have been placed in lp by the caller */
750 void
751 lp_parse(struct lp_style_type *lp, TBOOLEAN allow_ls, TBOOLEAN allow_point)
752 {
753     struct value t;
754
755     if (allow_ls &&
756         (almost_equals(c_token, "lines$tyle") || equals(c_token, "ls"))) {
757         c_token++;
758         lp_use_properties(lp, (int) real(const_express(&t)), allow_point);
759     } else {
760         /* avoid duplicating options */
761         int set_lt = 0, set_pal = 0, set_lw = 0, set_pt = 0, set_ps = 0;
762
763         lp->pointflag = allow_point;
764
765         while (!END_OF_COMMAND) {
766             if (almost_equals(c_token, "linet$ype") || equals(c_token, "lt")) {
767                 if (set_lt++)
768                     break;
769                 c_token++;
770                 if (almost_equals(c_token, "rgb$color")) {
771                     if (set_pal++)
772                         break;
773                     c_token--;
774                     parse_colorspec(&lp->pm3d_color, TC_RGB);
775                     lp->use_palette = 1;
776                 } else
777                 /* both syntaxes allowed: 'with lt pal' as well as 'with pal' */
778                 if (almost_equals(c_token, "pal$ette")) {
779                     if (set_pal++)
780                         break;
781                     c_token--;
782                     parse_colorspec(&lp->pm3d_color, TC_Z);
783                     lp->use_palette = 1;
784 #ifdef KEYWORD_BGND
785                 } else if (equals(c_token,"bgnd")) {
786                     lp->l_type = LT_BACKGROUND;
787                     c_token++;
788 #endif
789                 } else {
790                     int lt = real(const_express(&t));
791                     lp->l_type = lt - 1;
792                     /* user may prefer explicit line styles */
793                     if (prefer_line_styles && allow_ls)
794                         lp_use_properties(lp, lt, TRUE);
795                 }
796             } /* linetype, lt */
797
798             /* both syntaxes allowed: 'with lt pal' as well as 'with pal' */
799             if (almost_equals(c_token, "pal$ette")) {
800                 if (set_pal++)
801                     break;
802                 c_token--;
803                 parse_colorspec(&lp->pm3d_color, TC_Z);
804                 lp->use_palette = 1;
805                 continue;
806             }
807
808             if (equals(c_token,"lc") || almost_equals(c_token,"linec$olor")) {
809                 lp->use_palette = 1;
810                 if (set_pal++)
811                     break;
812                 c_token++;
813                 if (almost_equals(c_token, "rgb$color")) {
814                     c_token--;
815                     parse_colorspec(&lp->pm3d_color, TC_RGB);
816                 } else if (almost_equals(c_token, "pal$ette")) {
817                     c_token--;
818                     parse_colorspec(&lp->pm3d_color, TC_Z);
819 #ifdef KEYWORD_BGND
820                 } else if (equals(c_token,"bgnd")) {
821                     lp->pm3d_color.type = TC_LT;
822                     lp->pm3d_color.lt = LT_BACKGROUND;
823                     c_token++;
824 #endif
825                 } else if (almost_equals(c_token, "var$iable")) {
826                     c_token++;
827                     lp->l_type = LT_COLORFROMCOLUMN;
828                     lp->pm3d_color.type = TC_LINESTYLE;
829                 } else {
830                     lp->pm3d_color.type = TC_LT;
831                     lp->pm3d_color.lt = (int) real(const_express(&t)) - 1;
832                 }
833                 continue;
834             }
835
836             if (almost_equals(c_token, "linew$idth") || equals(c_token, "lw")) {
837                 if (set_lw++)
838                     break;
839                 c_token++;
840                 lp->l_width = real(const_express(&t));
841                 if (lp->l_width < 0)
842                     lp->l_width = 0;
843                 continue;
844             }
845
846             if (equals(c_token,"bgnd")) {
847                 lp->l_type = LT_BACKGROUND;
848                 lp->use_palette = 0;
849                 c_token++;
850                 continue;
851             }
852
853             /* HBB 20010622: restructured to allow for more descriptive
854              * error message, here. Would otherwise only print out
855              * 'undefined variable: pointtype' --- rather unhelpful. */
856             if (almost_equals(c_token, "pointt$ype") || equals(c_token, "pt")) {
857                 if (allow_point) {
858                     if (set_pt++)
859                         break;
860                     c_token++;
861                     lp->p_type = (int) real(const_express(&t)) - 1;
862                 } else {
863                     int_warn(c_token, "No pointtype specifier allowed, here");
864                     c_token += 2;
865                 }
866                 continue;
867             }
868
869             if (almost_equals(c_token, "points$ize") || equals(c_token, "ps")) {
870                 if (allow_point) {
871                     if (set_ps++)
872                         break;
873                     c_token++;
874                     if (almost_equals(c_token, "var$iable")) {
875                         lp->p_size = PTSZ_VARIABLE;
876                         c_token++;
877                     } else if (almost_equals(c_token, "def$ault")) {
878                         lp->p_size = PTSZ_DEFAULT;
879                         c_token++;
880                     } else {
881                         lp->p_size = real(const_express(&t));
882                         if (lp->p_size < 0)
883                             lp->p_size = 0;
884                     }
885                 } else {
886                     int_warn(c_token, "No pointsize specifier allowed, here");
887                     c_token += 2;
888                 }
889                 continue;
890             }
891
892             /* unknown option catched -> quit the while(1) loop */
893             break;
894         }
895
896         if (set_lt > 1 || set_pal > 1 || set_lw > 1 || set_pt > 1 || set_ps > 1)
897             int_error(c_token, "duplicated arguments in style specification");
898
899 #if defined(__FILE__) && defined(__LINE__) && defined(DEBUG_LP)
900         fprintf(stderr,
901                 "lp_properties at %s:%d : lt: %d, lw: %.3f, pt: %d, ps: %.3f\n",
902                 __FILE__, __LINE__, lp->l_type, lp->l_width, lp->p_type,
903                 lp->p_size);
904 #endif
905     }
906 }
907
908 /* <fillstyle> = {empty | solid {<density>} | pattern {<n>}} {noborder | border {<lt>}} */
909 void
910 parse_fillstyle(struct fill_style_type *fs, int def_style, int def_density, int def_pattern, int def_bordertype)
911 {
912     struct value a;
913     TBOOLEAN set_fill = FALSE;
914     TBOOLEAN set_param = FALSE;
915
916     /* Set defaults */
917     fs->fillstyle = def_style;
918     fs->filldensity = def_density;
919     fs->fillpattern = def_pattern;
920     fs->border_linetype = def_bordertype;
921
922     if (END_OF_COMMAND)
923         return;
924     if (!equals(c_token, "fs") && !almost_equals(c_token, "fill$style"))
925         return;
926     c_token++;
927
928     while (!END_OF_COMMAND) {
929         if (almost_equals(c_token, "e$mpty")) {
930             fs->fillstyle = FS_EMPTY;
931             c_token++;
932         } else if (almost_equals(c_token, "s$olid")) {
933             fs->fillstyle = FS_SOLID;
934             set_fill = TRUE;
935             c_token++;
936         } else if (almost_equals(c_token, "p$attern")) {
937             fs->fillstyle = FS_PATTERN;
938             set_fill = TRUE;
939             c_token++;
940         }
941
942         if (END_OF_COMMAND)
943             continue;
944         else if (almost_equals(c_token, "bo$rder")) {
945             fs->border_linetype = LT_UNDEFINED;
946             c_token++;
947             /* FIXME EAM - isanumber really means `is a positive number` */
948             if (isanumber(c_token) ||
949                 (equals(c_token, "-") && isanumber(c_token + 1))) {
950                 fs->border_linetype = (int) real(const_express(&a)) - 1;
951             }
952             continue;
953         } else if (almost_equals(c_token, "nobo$rder")) {
954             fs->border_linetype = LT_NODRAW;
955             c_token++;
956             continue;
957         }
958         /* We hit something unexpected */
959         else if (!set_fill || !isanumber(c_token) || set_param)
960             break;
961
962         if (fs->fillstyle == FS_SOLID) {
963             /* user sets 0...1, but is stored as an integer 0..100 */
964             fs->filldensity = 100.0 * real(const_express(&a)) + 0.5;
965             if (fs->filldensity < 0)
966                 fs->filldensity = 0;
967             if (fs->filldensity > 100)
968                 fs->filldensity = 100;
969             set_param = TRUE;
970         } else if (fs->fillstyle == FS_PATTERN) {
971             fs->fillpattern = real(const_express(&a));
972             if (fs->fillpattern < 0)
973                 fs->fillpattern = 0;
974             set_param = TRUE;
975         }
976     }
977 }
978
979 /*
980  * Parse the sub-options of text color specification
981  *   { def$ault | lt <linetype> | pal$ette { cb <val> | frac$tion <val> | z }
982  * The ordering of alternatives shown in the line above is kept in the symbol definitions
983  * TC_DEFAULT TC_LT TC_RGB TC_CB TC_FRAC TC_Z  (0 1 2 3 4 5)
984  * and the "options" parameter to parse_colorspec limits legal input to the
985  * corresponding point in the series. So TC_LT allows only default or linetype
986  * coloring, while TC_Z allows all coloring options up to and including pal z
987  */
988 void
989 parse_colorspec(struct t_colorspec *tc, int options)
990 {
991     struct value a;
992
993     c_token++;
994     if (END_OF_COMMAND)
995         int_error(c_token, "expected colorspec");
996     if (almost_equals(c_token,"def$ault")) {
997         c_token++;
998         tc->type = TC_DEFAULT;
999 #ifdef KEYWORD_BGND
1000     } else if (equals(c_token,"bgnd")) {
1001         c_token++;
1002         tc->type = TC_LT;
1003         tc->lt = LT_BACKGROUND;
1004 #endif
1005     } else if (equals(c_token,"lt")) {
1006         c_token++;
1007         if (END_OF_COMMAND)
1008             int_error(c_token, "expected linetype");
1009         tc->type = TC_LT;
1010         tc->lt = (int)real(const_express(&a))-1;
1011         if (tc->lt < LT_BACKGROUND) {
1012             tc->type = TC_DEFAULT;
1013             int_warn(c_token,"illegal linetype");
1014         }
1015     } else if (options <= TC_LT) {
1016         tc->type = TC_DEFAULT;
1017         int_error(c_token, "only tc lt <n> possible here");
1018     } else if (equals(c_token,"ls") || almost_equals(c_token,"lines$tyle")) {
1019         c_token++;
1020         tc->type = TC_LINESTYLE;
1021         tc->lt = (int)real(const_express(&a));
1022     } else if (almost_equals(c_token,"rgb$color")) {
1023         char *color;
1024         int rgbtriple;
1025         c_token++;
1026         tc->type = TC_RGB;
1027         if (almost_equals(c_token, "var$iable")) {
1028             /* Flag to indicate "variable", not currently checked anywhere */
1029             tc->value = -1.0;
1030             c_token++;
1031             return;
1032         } else
1033             tc->value = 0.0;
1034         if (!(color = try_to_get_string()))
1035             int_error(c_token, "expected a color name or a string of form \"#RRGGBB\"");
1036         if ((rgbtriple = lookup_table_nth(pm3d_color_names_tbl, color)) >= 0)
1037             rgbtriple = pm3d_color_names_tbl[rgbtriple].value;
1038         else
1039             sscanf(color,"#%x",&rgbtriple);
1040         free(color);
1041         if (rgbtriple < 0)
1042             int_error(c_token, "expected a known color name or a string of form \"#RRGGBB\"");
1043         tc->type = TC_RGB;
1044         tc->lt = rgbtriple;
1045     } else if (almost_equals(c_token,"pal$ette")) {
1046         c_token++;
1047         if (equals(c_token,"z")) {
1048             /* The actual z value is not yet known, fill it in later */
1049             if (options >= TC_Z) {
1050                 tc->type = TC_Z;
1051             } else {
1052                 tc->type = TC_DEFAULT;
1053                 int_error(c_token, "palette z not possible here");
1054             }
1055             c_token++;
1056         } else if (equals(c_token,"cb")) {
1057             tc->type = TC_CB;
1058             c_token++;
1059             if (END_OF_COMMAND)
1060                 int_error(c_token, "expected cb value");
1061             tc->value = real(const_express(&a));
1062         } else if (almost_equals(c_token,"frac$tion")) {
1063             tc->type = TC_FRAC;
1064             c_token++;
1065             if (END_OF_COMMAND)
1066                 int_error(c_token, "expected palette fraction");
1067             tc->value = real(const_express(&a));
1068             if (tc->value < 0. || tc->value > 1.0)
1069                 int_error(c_token, "palette fraction out of range");
1070         } else {
1071             /* END_OF_COMMAND or palette <blank> */
1072             if (options >= TC_Z)
1073                 tc->type = TC_Z;
1074         }
1075     } else {
1076         int_error(c_token, "colorspec option not recognized");
1077     }
1078 }
1079
1080 /* arrow parsing...
1081  *
1082  * allow_as controls whether we are allowed to accept arrowstyle in
1083  * the current context [ie not when doing a  set style arrow command]
1084  */
1085
1086 static void
1087 arrow_use_properties(struct arrow_style_type *arrow, int tag)
1088 {
1089     /*  This function looks for an arrowstyle defined by 'tag' and
1090      *  copies its data into the structure 'ap'. */
1091     struct arrowstyle_def *this;
1092
1093     this = first_arrowstyle;
1094     while (this != NULL) {
1095         if (this->tag == tag) {
1096             *arrow = this->arrow_properties;
1097             return;
1098         } else {
1099             this = this->next;
1100         }
1101     }
1102
1103     /* tag not found: */
1104     int_error(NO_CARET,"arrowstyle not found", NO_CARET);
1105 }
1106
1107 void
1108 arrow_parse(
1109     struct arrow_style_type *arrow,
1110     TBOOLEAN allow_as)
1111 {
1112     struct value t;
1113
1114     if (allow_as && (almost_equals(c_token, "arrows$tyle") ||
1115                      equals(c_token, "as"))) {
1116         c_token++;
1117         arrow_use_properties(arrow, (int) real(const_express(&t)));
1118     } else {
1119         /* avoid duplicating options */
1120         int set_layer=0, set_line=0, set_head=0;
1121         int set_headsize=0, set_headfilled=0;
1122
1123         while (!END_OF_COMMAND) {
1124             if (equals(c_token, "nohead")) {
1125                 if (set_head++)
1126                     break;
1127                 c_token++;
1128                 arrow->head = NOHEAD;
1129                 continue;
1130             }
1131             if (equals(c_token, "head")) {
1132                 if (set_head++)
1133                     break;
1134                 c_token++;
1135                 arrow->head = END_HEAD;
1136                 continue;
1137             }
1138             if (equals(c_token, "backhead")) {
1139                 if (set_head++)
1140                     break;
1141                 c_token++;
1142                 arrow->head = BACKHEAD;
1143                 continue;
1144             }
1145             if (equals(c_token, "heads")) {
1146                 if (set_head++)
1147                     break;
1148                 c_token++;
1149                 arrow->head = BACKHEAD | END_HEAD;
1150                 continue;
1151             }
1152
1153             if (almost_equals(c_token, "fill$ed")) {
1154                 if (set_headfilled++)
1155                     break;
1156                 c_token++;
1157                 arrow->head_filled = 2;
1158                 continue;
1159             }
1160             if (almost_equals(c_token, "empty")) {
1161                 if (set_headfilled++)
1162                     break;
1163                 c_token++;
1164                 arrow->head_filled = 1;
1165                 continue;
1166             }
1167             if (almost_equals(c_token, "nofill$ed")) {
1168                 if (set_headfilled++)
1169                     break;
1170                 c_token++;
1171                 arrow->head_filled = 0;
1172                 continue;
1173             }
1174
1175             if (equals(c_token, "size")) {
1176                 struct position hsize;
1177                 if (set_headsize++)
1178                     break;
1179                 hsize.scalex = hsize.scaley = hsize.scalez = first_axes;
1180                 /* only scalex used; scaley is angle of the head in [deg] */
1181                 c_token++;
1182                 if (END_OF_COMMAND)
1183                     int_error(c_token, "head size expected");
1184                 get_position(&hsize);
1185                 arrow->head_length = hsize.x;
1186                 arrow->head_lengthunit = hsize.scalex;
1187                 arrow->head_angle = hsize.y;
1188                 arrow->head_backangle = hsize.z;
1189                 /* invalid backangle --> default of 90.0° */
1190                 if (arrow->head_backangle <= arrow->head_angle)
1191                     arrow->head_backangle = 90.0;
1192                 continue;
1193             }
1194
1195             if (equals(c_token, "back")) {
1196                 if (set_layer++)
1197                     break;
1198                 c_token++;
1199                 arrow->layer = 0;
1200                 continue;
1201             }
1202             if (equals(c_token, "front")) {
1203                 if (set_layer++)
1204                     break;
1205                 c_token++;
1206                 arrow->layer = 1;
1207                 continue;
1208             }
1209
1210             /* pick up a line spec - allow ls, but no point. */
1211             {
1212                 int stored_token = c_token;
1213                 lp_parse(&arrow->lp_properties, TRUE, FALSE);
1214                 if (stored_token == c_token || set_line++)
1215                     break;
1216                 continue;
1217             }
1218
1219             /* unknown option caught -> quit the while(1) loop */
1220             break;
1221         }
1222
1223         if (set_layer>1 || set_line>1 || set_head>1 || set_headsize>1 || set_headfilled>1)
1224             int_error(c_token, "duplicated arguments in style specification");
1225
1226 #if defined(__FILE__) && defined(__LINE__) && defined(DEBUG_LP)
1227         arrow->layer = 0;
1228         arrow->lp_properties = tmp_lp_style;
1229         arrow->head = 1;
1230         arrow->head_length = 0.0;
1231         arrow->head_lengthunit = first_axes;
1232         arrow->head_angle = 15.0;
1233         arrow->head_backangle = 90.0;
1234         arrow->head_filled = 0;
1235         fprintf(stderr,
1236                 "arrow_properties at %s:%d : layer: %d, lt: %d, lw: %.3f, head: %d, headlength/unit: %.3f/%d, headangles: %.3f/%.3f, headfilled %d\n",
1237                 __FILE__, __LINE__, arrow->layer, arrow->lp_properties.l_type,
1238                 arrow->lp_properties.l_width, arrow->head, arrow->head_length,
1239                 arrow->head_lengthunit, arrow->head_angle,
1240                 arrow->head_backangle, arrow->head_filled);
1241 #endif
1242     }
1243 }
1244