Initial release of Maemo 5 port of gnuplot
[gnuplot] / src / misc.c
diff --git a/src/misc.c b/src/misc.c
new file mode 100644 (file)
index 0000000..be94f8b
--- /dev/null
@@ -0,0 +1,1244 @@
+#ifndef lint
+static char *RCSid() { return RCSid("$Id: misc.c,v 1.81.2.4 2008/10/29 17:20:25 sfeam Exp $"); }
+#endif
+
+/* GNUPLOT - misc.c */
+
+/*[
+ * Copyright 1986 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
+ *
+ * Permission to use, copy, and distribute this software and its
+ * documentation for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.
+ *
+ * Permission to modify the software is granted, but not the right to
+ * distribute the complete modified source code.  Modifications are to
+ * be distributed as patches to the released version.  Permission to
+ * distribute binaries produced by compiling modified sources is granted,
+ * provided you
+ *   1. distribute the corresponding source modifications from the
+ *    released version in the form of a patch file along with the binaries,
+ *   2. add special version identification to distinguish your version
+ *    in addition to the base release version number,
+ *   3. provide your name and address as the primary contact for the
+ *    support of your modified version, and
+ *   4. retain our contact information in regard to use of the base
+ *    software.
+ * Permission to distribute the released version of the source code along
+ * with corresponding source modifications in the form of a patch file is
+ * granted with same provisions 2 through 4 for binary distributions.
+ *
+ * This software is provided "as is" without express or implied warranty
+ * to the extent permitted by applicable law.
+]*/
+
+#include "misc.h"
+
+#include "alloc.h"
+#include "command.h"
+#include "graphics.h"
+#include "parse.h"             /* for const_express() */
+#include "plot.h"
+#include "tables.h"
+#include "util.h"
+#include "variable.h"
+#include "axis.h"
+
+#if defined(HAVE_DIRENT_H)
+# include <sys/types.h>
+# include <dirent.h>
+#elif defined(_Windows)
+# include <windows.h>
+#endif
+
+/* name of command file; NULL if terminal */
+char *infile_name = NULL;
+
+static TBOOLEAN lf_pop __PROTO((void));
+static void lf_push __PROTO((FILE *));
+static void arrow_use_properties __PROTO((struct arrow_style_type *arrow, int tag));
+static char *recursivefullname __PROTO((const char *path, const char *filename, TBOOLEAN recursive));
+
+/* A copy of the declaration from set.c */
+/* There should only be one declaration in a header file. But I do not know
+ * where to put it */
+/* void get_position __PROTO((struct position * pos)); */
+
+/* State information for load_file(), to recover from errors
+ * and properly handle recursive load_file calls
+ */
+LFS *lf_head = NULL;           /* NULL if not in load_file */
+
+/* these two could be in load_file, except for error recovery */
+static TBOOLEAN do_load_arg_substitution = FALSE;
+static char *call_args[10] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+
+/*
+ * iso_alloc() allocates a iso_curve structure that can hold 'num'
+ * points.
+ */
+struct iso_curve *
+iso_alloc(int num)
+{
+    struct iso_curve *ip;
+    ip = (struct iso_curve *) gp_alloc(sizeof(struct iso_curve), "iso curve");
+    ip->p_max = (num >= 0 ? num : 0);
+    if (num > 0) {
+       ip->points = (struct coordinate GPHUGE *)
+           gp_alloc(num * sizeof(struct coordinate), "iso curve points");
+    } else
+       ip->points = (struct coordinate GPHUGE *) NULL;
+    ip->next = NULL;
+    return (ip);
+}
+
+/*
+ * iso_extend() reallocates a iso_curve structure to hold "num"
+ * points. This will either expand or shrink the storage.
+ */
+void
+iso_extend(struct iso_curve *ip, int num)
+{
+    if (num == ip->p_max)
+       return;
+
+#if defined(DOS16) || defined(WIN16)
+    /* Make sure we do not allocate more than 64k points in msdos since
+       * indexing is done with 16-bit int
+       * Leave some bytes for malloc maintainance.
+     */
+    if (num > 32700)
+       int_error(NO_CARET, "Array index must be less than 32k in msdos");
+#endif /* 16bit (Win)Doze */
+
+    if (num > 0) {
+       if (ip->points == NULL) {
+           ip->points = (struct coordinate GPHUGE *)
+               gp_alloc(num * sizeof(struct coordinate), "iso curve points");
+       } else {
+           ip->points = (struct coordinate GPHUGE *)
+               gp_realloc(ip->points, num * sizeof(struct coordinate), "expanding curve points");
+       }
+       ip->p_max = num;
+    } else {
+       if (ip->points != (struct coordinate GPHUGE *) NULL)
+           free(ip->points);
+       ip->points = (struct coordinate GPHUGE *) NULL;
+       ip->p_max = 0;
+    }
+}
+
+/*
+ * iso_free() releases any memory which was previously malloc()'d to hold
+ *   iso curve points.
+ */
+void
+iso_free(struct iso_curve *ip)
+{
+    if (ip) {
+       if (ip->points)
+           free((char *) ip->points);
+       free((char *) ip);
+    }
+}
+
+void
+load_file(FILE *fp, char *name, TBOOLEAN can_do_args)
+{
+    int len;
+
+    int start, left;
+    int more;
+    int stop = FALSE;
+
+    lf_push(fp);               /* save state for errors and recursion */
+    do_load_arg_substitution = can_do_args;
+
+    if (fp == (FILE *) NULL) {
+       os_error(c_token, "Cannot open %s file '%s'",
+                can_do_args ? "call" : "load", name);
+    } else if (fp == stdin) {
+       /* DBT 10-6-98  go interactive if "-" named as load file */
+       interactive = TRUE;
+       while (!com_line());
+    } else {
+       /* go into non-interactive mode during load */
+       /* will be undone below, or in load_file_error */
+       int argc = 0; /* number arguments passed by "call" */
+       interactive = FALSE;
+       inline_num = 0;
+       infile_name = name;
+
+       if (can_do_args) {
+           while (c_token < num_tokens && argc <= 9) {
+               if (isstring(c_token))
+                   m_quote_capture(&call_args[argc++], c_token, c_token);
+               else
+                   m_capture(&call_args[argc++], c_token, c_token);
+               c_token++;
+           }
+           /* Gnuplot "call" command can have up to 10 arguments named "$0"
+              to "$9". After reading the 10th argument (i.e., "$9") the
+              variable 'argc' equals 10.
+              Also, "call" must be the last command on the command line.
+           */
+           if (c_token < num_tokens)
+               int_error(++c_token, "too many arguments for 'call <file>'");
+       }
+       while (!stop) {         /* read all commands in file */
+           /* read one command */
+           left = gp_input_line_len;
+           start = 0;
+           more = TRUE;
+
+           while (more) {
+               if (fgets(&(gp_input_line[start]), left, fp) == (char *) NULL) {
+                   stop = TRUE;        /* EOF in file */
+                   gp_input_line[start] = '\0';
+                   more = FALSE;
+               } else {
+                   inline_num++;
+                   len = strlen(gp_input_line) - 1;
+                   if (gp_input_line[len] == '\n') {   /* remove any newline */
+                       gp_input_line[len] = '\0';
+                       /* Look, len was 1-1 = 0 before, take care here! */
+                       if (len > 0)
+                           --len;
+                       if (gp_input_line[len] == '\r') {       /* remove any carriage return */
+                           gp_input_line[len] = NUL;
+                           if (len > 0)
+                               --len;
+                       }
+                   } else if (len + 2 >= left) {
+                       extend_input_line();
+                       left = gp_input_line_len - len - 1;
+                       start = len + 1;
+                       continue;       /* don't check for '\' */
+                   }
+                   if (gp_input_line[len] == '\\') {
+                       /* line continuation */
+                       start = len;
+                       left = gp_input_line_len - start;
+                   } else
+                       more = FALSE;
+               }
+           }
+
+           if (strlen(gp_input_line) > 0) {
+               if (can_do_args) {
+                   int il = 0;
+                   char *rl;
+                   char *raw_line = gp_strdup(gp_input_line);
+
+                   rl = raw_line;
+                   *gp_input_line = '\0';
+                   while (*rl) {
+                       int aix;
+                       if (*rl == '$'
+                           && ((aix = *(++rl)) != 0)   /* HBB 980308: quiet BCC warning */
+                           && ((aix >= '0' && aix <= '9') || aix == '#')) {
+                           if (aix == '#') {
+                               /* replace $# by number of passed arguments */
+                               len = argc < 10 ? 1 : 2; /* argc can be 0 .. 10 */
+                               while (gp_input_line_len - il < len + 1) {
+                                   extend_input_line();
+                               }
+                               sprintf(gp_input_line + il, "%i", argc);
+                               il += len;
+                           } else
+                           if (call_args[aix -= '0']) {
+                               /* replace $n for n=0..9 by the passed argument */
+                               len = strlen(call_args[aix]);
+                               while (gp_input_line_len - il < len + 1) {
+                                   extend_input_line();
+                               }
+                               strcpy(gp_input_line + il, call_args[aix]);
+                               il += len;
+                           }
+                       } else {
+                           /* substitute for $<n> here */
+                           if (il + 1 > gp_input_line_len) {
+                               extend_input_line();
+                           }
+                           gp_input_line[il++] = *rl;
+                       }
+                       rl++;
+                   }
+                   if (il + 1 > gp_input_line_len) {
+                       extend_input_line();
+                   }
+                   gp_input_line[il] = '\0';
+                   free(raw_line);
+               }
+               screen_ok = FALSE;      /* make sure command line is
+                                          echoed on error */
+               if (do_line())
+                   stop = TRUE;
+           }
+       }
+    }
+
+    /* pop state */
+    (void) lf_pop();           /* also closes file fp */
+}
+
+/* pop from load_file state stack
+   FALSE if stack was empty
+   called by load_file and load_file_error */
+static TBOOLEAN
+lf_pop()
+{
+    LFS *lf;
+
+    if (lf_head == NULL)
+       return (FALSE);
+    else {
+       int argindex;
+       lf = lf_head;
+       if (lf->fp != (FILE *) NULL && lf->fp != stdin) {
+           /* DBT 10-6-98  do not close stdin in the case
+            * that "-" is named as a load file
+            */
+           (void) fclose(lf->fp);
+       }
+       for (argindex = 0; argindex < 10; argindex++) {
+           if (call_args[argindex]) {
+               free(call_args[argindex]);
+           }
+           call_args[argindex] = lf->call_args[argindex];
+       }
+       do_load_arg_substitution = lf->do_load_arg_substitution;
+       interactive = lf->interactive;
+       inline_num = lf->inline_num;
+       infile_name = lf->name;
+       lf_head = lf->prev;
+       free((char *) lf);
+       return (TRUE);
+    }
+}
+
+/* push onto load_file state stack
+   essentially, we save information needed to undo the load_file changes
+   called by load_file */
+static void
+lf_push(FILE *fp)
+{
+    LFS *lf;
+    int argindex;
+
+    lf = (LFS *) gp_alloc(sizeof(LFS), (char *) NULL);
+    if (lf == (LFS *) NULL) {
+       if (fp != (FILE *) NULL)
+           (void) fclose(fp);  /* it won't be otherwise */
+       int_error(c_token, "not enough memory to load file");
+    }
+    lf->fp = fp;               /* save this file pointer */
+    lf->name = infile_name;    /* save current name */
+    lf->interactive = interactive;     /* save current state */
+    lf->inline_num = inline_num;       /* save current line number */
+    lf->do_load_arg_substitution = do_load_arg_substitution;
+    for (argindex = 0; argindex < 10; argindex++) {
+       lf->call_args[argindex] = call_args[argindex];
+       call_args[argindex] = NULL;     /* initially no args */
+    }
+    lf->prev = lf_head;                /* link to stack */
+    lf_head = lf;
+}
+
+/* used for reread  vsnyder@math.jpl.nasa.gov */
+FILE *
+lf_top()
+{
+    if (lf_head == (LFS *) NULL)
+       return ((FILE *) NULL);
+    return (lf_head->fp);
+}
+
+/* called from main */
+void
+load_file_error()
+{
+    /* clean up from error in load_file */
+    /* pop off everything on stack */
+    while (lf_pop());
+}
+
+/* find max len of keys and count keys with len > 0 */
+
+/* FIXME HBB 2000508: by design, this one belongs into 'graphics', and the
+ * next to into 'graph3d'. Actually, the existence of a module like this
+ * 'misc' is almost always a sign of bad design, IMHO */
+/* may return NULL */
+FILE *
+loadpath_fopen(const char *filename, const char *mode)
+{
+    FILE *fp;
+
+#if defined(PIPES)
+    if (*filename == '<') {
+       if ((fp = popen(filename + 1, "r")) == (FILE *) NULL)
+           return (FILE *) 0;
+    } else
+#endif /* PIPES */
+    if ((fp = fopen(filename, mode)) == (FILE *) NULL) {
+       /* try 'loadpath' variable */
+       char *fullname = NULL, *path;
+
+       while ((path = get_loadpath()) != NULL) {
+           /* length of path, dir separator, filename, \0 */
+           fullname = gp_realloc(fullname, strlen(path) + 1 + strlen(filename) + 1, "loadpath_fopen");
+           strcpy(fullname, path);
+           PATH_CONCAT(fullname, filename);
+           if ((fp = fopen(fullname, mode)) != NULL) {
+               free(fullname);
+               fullname = NULL;
+               /* reset loadpath internals!
+                * maybe this can be replaced by calling get_loadpath with
+                * a NULL argument and some loadpath_handler internal logic */
+               while (get_loadpath());
+               break;
+           }
+       }
+
+       if (fullname)
+           free(fullname);
+
+    }
+
+    return fp;
+}
+
+
+/* Harald Harders <h.harders@tu-bs.de> */
+/* Thanks to John Bollinger <jab@bollingerbands.com> who has tested the
+   windows part */
+static char *
+recursivefullname(const char *path, const char *filename, TBOOLEAN recursive)
+{
+    char *fullname = NULL;
+    FILE *fp;
+
+    /* length of path, dir separator, filename, \0 */
+    fullname = gp_alloc(strlen(path) + 1 + strlen(filename) + 1,
+                       "recursivefullname");
+    strcpy(fullname, path);
+    PATH_CONCAT(fullname, filename);
+
+    if ((fp = fopen(fullname, "r")) != NULL) {
+       fclose(fp);
+       return fullname;
+    } else {
+       free(fullname);
+       fullname = NULL;
+    }
+
+    if (recursive) {
+#ifdef HAVE_DIRENT_H
+       DIR *dir;
+       struct dirent *direntry;
+       struct stat buf;
+
+       dir = opendir(path);
+       if (dir) {
+           while ((direntry = readdir(dir)) != NULL) {
+               char *fulldir = gp_alloc(strlen(path) + 1 + strlen(direntry->d_name) + 1,
+                                        "fontpath_fullname");
+               strcpy(fulldir, path);
+#  if defined(VMS)
+               if (fulldir[strlen(fulldir) - 1] == ']')
+                   fulldir[strlen(fulldir) - 1] = '\0';
+               strcpy(&(fulldir[strlen(fulldir)]), ".");
+               strcpy(&(fulldir[strlen(fulldir)]), direntry->d_name);
+               strcpy(&(fulldir[strlen(fulldir)]), "]");
+#  else
+               PATH_CONCAT(fulldir, direntry->d_name);
+#  endif
+               stat(fulldir, &buf);
+               if ((S_ISDIR(buf.st_mode)) &&
+                   (strcmp(direntry->d_name, ".") != 0) &&
+                   (strcmp(direntry->d_name, "..") != 0)) {
+                   fullname = recursivefullname(fulldir, filename, TRUE);
+                   if (fullname != NULL)
+                       break;
+               }
+               free(fulldir);
+           }
+           closedir(dir);
+       }
+#elif defined(_Windows)
+       HANDLE filehandle;
+       WIN32_FIND_DATA finddata;
+       char *pathwildcard = gp_alloc(strlen(path) + 2, "fontpath_fullname");
+
+       strcpy(pathwildcard, path);
+       PATH_CONCAT(pathwildcard, "*");
+
+       filehandle = FindFirstFile(pathwildcard, &finddata);
+       free(pathwildcard);
+       if (filehandle != INVALID_HANDLE_VALUE)
+           do {
+               if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+                   (strcmp(finddata.cFileName, ".") != 0) &&
+                   (strcmp(finddata.cFileName, "..") != 0)) {
+                   char *fulldir = gp_alloc(strlen(path) + 1 + strlen(finddata.cFileName) + 1,
+                                            "fontpath_fullname");
+                   strcpy(fulldir, path);
+                   PATH_CONCAT(fulldir, finddata.cFileName);
+
+                   fullname = recursivefullname(fulldir, filename, TRUE);
+                   free(fulldir);
+                   if (fullname != NULL)
+                       break;
+               }
+           } while (FindNextFile(filehandle, &finddata) != 0);
+       FindClose(filehandle);
+
+#else
+       int_warn(NO_CARET, "Recursive directory search not supported\n\t('%s!')", path);
+#endif
+    }
+    return fullname;
+}
+
+
+/* may return NULL */
+char *
+fontpath_fullname(const char *filename)
+{
+    FILE *fp;
+    char *fullname = NULL;
+
+#if defined(PIPES)
+    if (*filename == '<') {
+       os_error(NO_CARET, "fontpath_fullname: No Pipe allowed");
+    } else
+#endif /* PIPES */
+    if ((fp = fopen(filename, "r")) == (FILE *) NULL) {
+       /* try 'fontpath' variable */
+       char *tmppath, *path = NULL;
+
+       while ((tmppath = get_fontpath()) != NULL) {
+           TBOOLEAN subdirs = FALSE;
+           path = gp_strdup(tmppath);
+           if (path[strlen(path) - 1] == '!') {
+               path[strlen(path) - 1] = '\0';
+               subdirs = TRUE;
+           }                   /* if */
+           fullname = recursivefullname(path, filename, subdirs);
+           if (fullname != NULL) {
+               while (get_fontpath());
+               free(path);
+               break;
+           }
+           free(path);
+       }
+
+    } else
+       fullname = gp_strdup(filename);
+
+    return fullname;
+}
+
+
+/* Push current terminal.
+ * Called 1. in main(), just after init_terminal(),
+ *        2. from load_rcfile(),
+ *        3. anytime by user command "set term push".
+ */
+static char *push_term_name = NULL;
+static char *push_term_opts = NULL;
+
+void
+push_terminal(int is_interactive)
+{
+    if (term) {
+       free(push_term_name);
+       free(push_term_opts);
+       push_term_name = gp_strdup(term->name);
+       push_term_opts = gp_strdup(term_options);
+       if (is_interactive)
+           fprintf(stderr, "   pushed terminal %s %s\n", push_term_name, push_term_opts);
+    } else {
+       if (is_interactive)
+           fputs("\tcurrent terminal type is unknown\n", stderr);
+    }
+}
+
+/* Pop the terminal.
+ * Called anytime by user command "set term pop".
+ */
+void
+pop_terminal()
+{
+    if (push_term_name != NULL) {
+       char *s;
+       int i = strlen(push_term_name) + 11;
+       if (push_term_opts) {
+           /* do_string() does not like backslashes -- thus remove them */
+           for (s=push_term_opts; *s; s++)
+               if (*s=='\\' || *s=='\n') *s=' ';
+           i += strlen(push_term_opts);
+       }
+       s = gp_alloc(i, "pop");
+       i = interactive;
+       interactive = 0;
+       sprintf(s,"set term %s %s", push_term_name, (push_term_opts ? push_term_opts : ""));
+       do_string(s);
+       free(s);
+       interactive = i;
+       if (interactive)
+           fprintf(stderr,"   restored terminal is %s %s\n", term->name, ((*term_options) ? term_options : ""));
+    } else
+       fprintf(stderr,"No terminal has been pushed yet\n");
+}
+
+
+/* Parse a plot style. Used by 'set style {data|function}' and by
+ * (s)plot.  */
+enum PLOT_STYLE
+get_style()
+{
+    /* defined in plot.h */
+    enum PLOT_STYLE ps;
+
+    c_token++;
+
+    ps = lookup_table(&plotstyle_tbl[0], c_token);
+
+    c_token++;
+
+    if (ps == -1) {
+       int_error(c_token, "\
+expecting 'lines', 'points', 'linespoints', 'dots', 'impulses',\n\
+\t'yerrorbars', 'xerrorbars', 'xyerrorbars', 'steps', 'fsteps',\n\
+\t'histeps', 'filledcurves', 'boxes', 'boxerrorbars', 'boxxyerrorbars',\n\
+\t'vectors', 'financebars', 'candlesticks', 'errorlines', 'xerrorlines',\n\
+\t'yerrorlines', 'xyerrorlines', 'pm3d', 'labels', 'histograms'"
+#ifdef WITH_IMAGE
+",\n\t 'image', 'rgbimage'"
+#endif
+);
+       ps = LINES;
+    }
+
+    return ps;
+}
+
+/* Parse options for style filledcurves and fill fco accordingly.
+ * If no option given, then set fco->opt_given to 0.
+ */
+void
+get_filledcurves_style_options(filledcurves_opts *fco)
+{
+    int p;
+    struct value a;
+    p = lookup_table(&filledcurves_opts_tbl[0], c_token);
+
+    if (p == FILLEDCURVES_ABOVE) {
+       fco->oneside = 1;
+       p = lookup_table(&filledcurves_opts_tbl[0], ++c_token);
+    } else if (p == FILLEDCURVES_BELOW) {
+       fco->oneside = -1;
+       p = lookup_table(&filledcurves_opts_tbl[0], ++c_token);
+    } else
+       fco->oneside = 0;
+
+    if (p == -1) {
+       fco->opt_given = 0;
+       return;                 /* no option given */
+    } else
+       fco->opt_given = 1;
+
+    c_token++;
+
+    fco->closeto = p;
+    if (!equals(c_token, "="))
+       return;
+    /* parameter required for filledcurves x1=... and friends */
+    if (p != FILLEDCURVES_ATXY)
+       fco->closeto += 4;
+    c_token++;
+    fco->at = real(const_express(&a));
+    if (p != FILLEDCURVES_ATXY)
+       return;
+    /* two values required for FILLEDCURVES_ATXY */
+    if (!equals(c_token, ","))
+       int_error(c_token, "syntax is xy=<x>,<y>");
+    c_token++;
+    fco->aty = real(const_express(&a));
+    return;
+}
+
+/* Print filledcurves style options to a file (used by 'show' and 'save'
+ * commands).
+ */
+void
+filledcurves_options_tofile(filledcurves_opts *fco, FILE *fp)
+{
+    if (!fco->opt_given)
+       return;
+    if (fco->oneside)
+       fputs(fco->oneside > 0 ? "above " : "below ", fp);
+    if (fco->closeto == FILLEDCURVES_CLOSED) {
+       fputs("closed", fp);
+       return;
+    }
+    if (fco->closeto <= FILLEDCURVES_Y2) {
+       fputs(filledcurves_opts_tbl[fco->closeto].key, fp);
+       return;
+    }
+    if (fco->closeto <= FILLEDCURVES_ATY2) {
+       fprintf(fp, "%s=%g", filledcurves_opts_tbl[fco->closeto - 4].key, fco->at);
+       return;
+    }
+    if (fco->closeto == FILLEDCURVES_ATXY) {
+       fprintf(fp, "xy=%g,%g", fco->at, fco->aty);
+       return;
+    }
+}
+
+/* line/point parsing...
+ *
+ * allow_ls controls whether we are allowed to accept linestyle in
+ * the current context [ie not when doing a  set linestyle command]
+ * allow_point is whether we accept a point command
+ * We assume compiler will optimise away if(0) or if(1)
+ */
+
+void
+lp_use_properties(struct lp_style_type *lp, int tag, int pointflag)
+{
+    /*  This function looks for a linestyle defined by 'tag' and copies
+     *  its data into the structure 'lp'.
+     *
+     *  If 'pointflag' equals ZERO, the properties belong to a linestyle
+     *  used with arrows.  In this case no point properties will be
+     *  passed to the terminal (cf. function 'term_apply_lp_properties' below).
+     */
+
+    struct linestyle_def *this;
+
+    this = first_linestyle;
+    while (this != NULL) {
+       if (this->tag == tag) {
+           *lp = this->lp_properties;
+           /* FIXME - It would be nicer if this were always true already */
+           if (!lp->use_palette) {
+               lp->pm3d_color.type = TC_LT;
+               lp->pm3d_color.lt = lp->l_type;
+           }
+           lp->pointflag = pointflag;
+           return;
+       } else {
+           this = this->next;
+       }
+    }
+
+    /* tag not found: */
+    /* Mar 2006 - This used to be a fatal error; now we fall back to line type */
+    lp->l_type = tag - 1;
+    lp->pm3d_color.type = TC_LT;
+    lp->pm3d_color.lt = lp->l_type;
+    lp->p_type = tag - 1;
+}
+
+
+/* allow any order of options - pm 24.11.2001 */
+/* EAM Oct 2005 - Require that default values have been placed in lp by the caller */
+void
+lp_parse(struct lp_style_type *lp, TBOOLEAN allow_ls, TBOOLEAN allow_point)
+{
+    struct value t;
+
+    if (allow_ls &&
+       (almost_equals(c_token, "lines$tyle") || equals(c_token, "ls"))) {
+       c_token++;
+       lp_use_properties(lp, (int) real(const_express(&t)), allow_point);
+    } else {
+       /* avoid duplicating options */
+       int set_lt = 0, set_pal = 0, set_lw = 0, set_pt = 0, set_ps = 0;
+
+       lp->pointflag = allow_point;
+
+       while (!END_OF_COMMAND) {
+           if (almost_equals(c_token, "linet$ype") || equals(c_token, "lt")) {
+               if (set_lt++)
+                   break;
+               c_token++;
+               if (almost_equals(c_token, "rgb$color")) {
+                   if (set_pal++)
+                       break;
+                   c_token--;
+                   parse_colorspec(&lp->pm3d_color, TC_RGB);
+                   lp->use_palette = 1;
+               } else
+               /* both syntaxes allowed: 'with lt pal' as well as 'with pal' */
+               if (almost_equals(c_token, "pal$ette")) {
+                   if (set_pal++)
+                       break;
+                   c_token--;
+                   parse_colorspec(&lp->pm3d_color, TC_Z);
+                   lp->use_palette = 1;
+#ifdef KEYWORD_BGND
+               } else if (equals(c_token,"bgnd")) {
+                   lp->l_type = LT_BACKGROUND;
+                   c_token++;
+#endif
+               } else {
+                   int lt = real(const_express(&t));
+                   lp->l_type = lt - 1;
+                   /* user may prefer explicit line styles */
+                   if (prefer_line_styles && allow_ls)
+                       lp_use_properties(lp, lt, TRUE);
+               }
+           } /* linetype, lt */
+
+           /* both syntaxes allowed: 'with lt pal' as well as 'with pal' */
+           if (almost_equals(c_token, "pal$ette")) {
+               if (set_pal++)
+                   break;
+               c_token--;
+               parse_colorspec(&lp->pm3d_color, TC_Z);
+               lp->use_palette = 1;
+               continue;
+           }
+
+           if (equals(c_token,"lc") || almost_equals(c_token,"linec$olor")) {
+               lp->use_palette = 1;
+               if (set_pal++)
+                   break;
+               c_token++;
+               if (almost_equals(c_token, "rgb$color")) {
+                   c_token--;
+                   parse_colorspec(&lp->pm3d_color, TC_RGB);
+               } else if (almost_equals(c_token, "pal$ette")) {
+                   c_token--;
+                   parse_colorspec(&lp->pm3d_color, TC_Z);
+#ifdef KEYWORD_BGND
+               } else if (equals(c_token,"bgnd")) {
+                   lp->pm3d_color.type = TC_LT;
+                   lp->pm3d_color.lt = LT_BACKGROUND;
+                   c_token++;
+#endif
+               } else if (almost_equals(c_token, "var$iable")) {
+                   c_token++;
+                   lp->l_type = LT_COLORFROMCOLUMN;
+                   lp->pm3d_color.type = TC_LINESTYLE;
+               } else {
+                   lp->pm3d_color.type = TC_LT;
+                   lp->pm3d_color.lt = (int) real(const_express(&t)) - 1;
+               }
+               continue;
+           }
+
+           if (almost_equals(c_token, "linew$idth") || equals(c_token, "lw")) {
+               if (set_lw++)
+                   break;
+               c_token++;
+               lp->l_width = real(const_express(&t));
+               if (lp->l_width < 0)
+                   lp->l_width = 0;
+               continue;
+           }
+
+           if (equals(c_token,"bgnd")) {
+               lp->l_type = LT_BACKGROUND;
+               lp->use_palette = 0;
+               c_token++;
+               continue;
+           }
+
+           /* HBB 20010622: restructured to allow for more descriptive
+            * error message, here. Would otherwise only print out
+            * 'undefined variable: pointtype' --- rather unhelpful. */
+           if (almost_equals(c_token, "pointt$ype") || equals(c_token, "pt")) {
+               if (allow_point) {
+                   if (set_pt++)
+                       break;
+                   c_token++;
+                   lp->p_type = (int) real(const_express(&t)) - 1;
+               } else {
+                   int_warn(c_token, "No pointtype specifier allowed, here");
+                   c_token += 2;
+               }
+               continue;
+           }
+
+           if (almost_equals(c_token, "points$ize") || equals(c_token, "ps")) {
+               if (allow_point) {
+                   if (set_ps++)
+                       break;
+                   c_token++;
+                   if (almost_equals(c_token, "var$iable")) {
+                       lp->p_size = PTSZ_VARIABLE;
+                       c_token++;
+                   } else if (almost_equals(c_token, "def$ault")) {
+                       lp->p_size = PTSZ_DEFAULT;
+                       c_token++;
+                   } else {
+                       lp->p_size = real(const_express(&t));
+                       if (lp->p_size < 0)
+                           lp->p_size = 0;
+                   }
+               } else {
+                   int_warn(c_token, "No pointsize specifier allowed, here");
+                   c_token += 2;
+               }
+               continue;
+           }
+
+           /* unknown option catched -> quit the while(1) loop */
+           break;
+       }
+
+       if (set_lt > 1 || set_pal > 1 || set_lw > 1 || set_pt > 1 || set_ps > 1)
+           int_error(c_token, "duplicated arguments in style specification");
+
+#if defined(__FILE__) && defined(__LINE__) && defined(DEBUG_LP)
+       fprintf(stderr,
+               "lp_properties at %s:%d : lt: %d, lw: %.3f, pt: %d, ps: %.3f\n",
+               __FILE__, __LINE__, lp->l_type, lp->l_width, lp->p_type,
+               lp->p_size);
+#endif
+    }
+}
+
+/* <fillstyle> = {empty | solid {<density>} | pattern {<n>}} {noborder | border {<lt>}} */
+void
+parse_fillstyle(struct fill_style_type *fs, int def_style, int def_density, int def_pattern, int def_bordertype)
+{
+    struct value a;
+    TBOOLEAN set_fill = FALSE;
+    TBOOLEAN set_param = FALSE;
+
+    /* Set defaults */
+    fs->fillstyle = def_style;
+    fs->filldensity = def_density;
+    fs->fillpattern = def_pattern;
+    fs->border_linetype = def_bordertype;
+
+    if (END_OF_COMMAND)
+       return;
+    if (!equals(c_token, "fs") && !almost_equals(c_token, "fill$style"))
+       return;
+    c_token++;
+
+    while (!END_OF_COMMAND) {
+       if (almost_equals(c_token, "e$mpty")) {
+           fs->fillstyle = FS_EMPTY;
+           c_token++;
+       } else if (almost_equals(c_token, "s$olid")) {
+           fs->fillstyle = FS_SOLID;
+           set_fill = TRUE;
+           c_token++;
+       } else if (almost_equals(c_token, "p$attern")) {
+           fs->fillstyle = FS_PATTERN;
+           set_fill = TRUE;
+           c_token++;
+       }
+
+       if (END_OF_COMMAND)
+           continue;
+       else if (almost_equals(c_token, "bo$rder")) {
+           fs->border_linetype = LT_UNDEFINED;
+           c_token++;
+           /* FIXME EAM - isanumber really means `is a positive number` */
+           if (isanumber(c_token) ||
+               (equals(c_token, "-") && isanumber(c_token + 1))) {
+               fs->border_linetype = (int) real(const_express(&a)) - 1;
+           }
+           continue;
+       } else if (almost_equals(c_token, "nobo$rder")) {
+           fs->border_linetype = LT_NODRAW;
+           c_token++;
+           continue;
+       }
+       /* We hit something unexpected */
+       else if (!set_fill || !isanumber(c_token) || set_param)
+           break;
+
+       if (fs->fillstyle == FS_SOLID) {
+           /* user sets 0...1, but is stored as an integer 0..100 */
+           fs->filldensity = 100.0 * real(const_express(&a)) + 0.5;
+           if (fs->filldensity < 0)
+               fs->filldensity = 0;
+           if (fs->filldensity > 100)
+               fs->filldensity = 100;
+           set_param = TRUE;
+       } else if (fs->fillstyle == FS_PATTERN) {
+           fs->fillpattern = real(const_express(&a));
+           if (fs->fillpattern < 0)
+               fs->fillpattern = 0;
+           set_param = TRUE;
+       }
+    }
+}
+
+/*
+ * Parse the sub-options of text color specification
+ *   { def$ault | lt <linetype> | pal$ette { cb <val> | frac$tion <val> | z }
+ * The ordering of alternatives shown in the line above is kept in the symbol definitions
+ * TC_DEFAULT TC_LT TC_RGB TC_CB TC_FRAC TC_Z  (0 1 2 3 4 5)
+ * and the "options" parameter to parse_colorspec limits legal input to the
+ * corresponding point in the series. So TC_LT allows only default or linetype
+ * coloring, while TC_Z allows all coloring options up to and including pal z
+ */
+void
+parse_colorspec(struct t_colorspec *tc, int options)
+{
+    struct value a;
+
+    c_token++;
+    if (END_OF_COMMAND)
+       int_error(c_token, "expected colorspec");
+    if (almost_equals(c_token,"def$ault")) {
+       c_token++;
+       tc->type = TC_DEFAULT;
+#ifdef KEYWORD_BGND
+    } else if (equals(c_token,"bgnd")) {
+       c_token++;
+       tc->type = TC_LT;
+       tc->lt = LT_BACKGROUND;
+#endif
+    } else if (equals(c_token,"lt")) {
+       c_token++;
+       if (END_OF_COMMAND)
+           int_error(c_token, "expected linetype");
+       tc->type = TC_LT;
+       tc->lt = (int)real(const_express(&a))-1;
+       if (tc->lt < LT_BACKGROUND) {
+           tc->type = TC_DEFAULT;
+           int_warn(c_token,"illegal linetype");
+       }
+    } else if (options <= TC_LT) {
+       tc->type = TC_DEFAULT;
+       int_error(c_token, "only tc lt <n> possible here");
+    } else if (equals(c_token,"ls") || almost_equals(c_token,"lines$tyle")) {
+       c_token++;
+       tc->type = TC_LINESTYLE;
+       tc->lt = (int)real(const_express(&a));
+    } else if (almost_equals(c_token,"rgb$color")) {
+       char *color;
+       int rgbtriple;
+       c_token++;
+       tc->type = TC_RGB;
+       if (almost_equals(c_token, "var$iable")) {
+           /* Flag to indicate "variable", not currently checked anywhere */
+           tc->value = -1.0;
+           c_token++;
+           return;
+       } else
+           tc->value = 0.0;
+       if (!(color = try_to_get_string()))
+           int_error(c_token, "expected a color name or a string of form \"#RRGGBB\"");
+       if ((rgbtriple = lookup_table_nth(pm3d_color_names_tbl, color)) >= 0)
+           rgbtriple = pm3d_color_names_tbl[rgbtriple].value;
+       else
+           sscanf(color,"#%x",&rgbtriple);
+       free(color);
+       if (rgbtriple < 0)
+           int_error(c_token, "expected a known color name or a string of form \"#RRGGBB\"");
+       tc->type = TC_RGB;
+       tc->lt = rgbtriple;
+    } else if (almost_equals(c_token,"pal$ette")) {
+       c_token++;
+       if (equals(c_token,"z")) {
+           /* The actual z value is not yet known, fill it in later */
+           if (options >= TC_Z) {
+               tc->type = TC_Z;
+           } else {
+               tc->type = TC_DEFAULT;
+               int_error(c_token, "palette z not possible here");
+           }
+           c_token++;
+       } else if (equals(c_token,"cb")) {
+           tc->type = TC_CB;
+           c_token++;
+           if (END_OF_COMMAND)
+               int_error(c_token, "expected cb value");
+           tc->value = real(const_express(&a));
+       } else if (almost_equals(c_token,"frac$tion")) {
+           tc->type = TC_FRAC;
+           c_token++;
+           if (END_OF_COMMAND)
+               int_error(c_token, "expected palette fraction");
+           tc->value = real(const_express(&a));
+           if (tc->value < 0. || tc->value > 1.0)
+               int_error(c_token, "palette fraction out of range");
+       } else {
+           /* END_OF_COMMAND or palette <blank> */
+           if (options >= TC_Z)
+               tc->type = TC_Z;
+       }
+    } else {
+       int_error(c_token, "colorspec option not recognized");
+    }
+}
+
+/* arrow parsing...
+ *
+ * allow_as controls whether we are allowed to accept arrowstyle in
+ * the current context [ie not when doing a  set style arrow command]
+ */
+
+static void
+arrow_use_properties(struct arrow_style_type *arrow, int tag)
+{
+    /*  This function looks for an arrowstyle defined by 'tag' and
+     *  copies its data into the structure 'ap'. */
+    struct arrowstyle_def *this;
+
+    this = first_arrowstyle;
+    while (this != NULL) {
+       if (this->tag == tag) {
+           *arrow = this->arrow_properties;
+           return;
+       } else {
+           this = this->next;
+       }
+    }
+
+    /* tag not found: */
+    int_error(NO_CARET,"arrowstyle not found", NO_CARET);
+}
+
+void
+arrow_parse(
+    struct arrow_style_type *arrow,
+    TBOOLEAN allow_as)
+{
+    struct value t;
+
+    if (allow_as && (almost_equals(c_token, "arrows$tyle") ||
+                    equals(c_token, "as"))) {
+       c_token++;
+       arrow_use_properties(arrow, (int) real(const_express(&t)));
+    } else {
+       /* avoid duplicating options */
+       int set_layer=0, set_line=0, set_head=0;
+       int set_headsize=0, set_headfilled=0;
+
+       while (!END_OF_COMMAND) {
+           if (equals(c_token, "nohead")) {
+               if (set_head++)
+                   break;
+               c_token++;
+               arrow->head = NOHEAD;
+               continue;
+           }
+           if (equals(c_token, "head")) {
+               if (set_head++)
+                   break;
+               c_token++;
+               arrow->head = END_HEAD;
+               continue;
+           }
+           if (equals(c_token, "backhead")) {
+               if (set_head++)
+                   break;
+               c_token++;
+               arrow->head = BACKHEAD;
+               continue;
+           }
+           if (equals(c_token, "heads")) {
+               if (set_head++)
+                   break;
+               c_token++;
+               arrow->head = BACKHEAD | END_HEAD;
+               continue;
+           }
+
+           if (almost_equals(c_token, "fill$ed")) {
+               if (set_headfilled++)
+                   break;
+               c_token++;
+               arrow->head_filled = 2;
+               continue;
+           }
+           if (almost_equals(c_token, "empty")) {
+               if (set_headfilled++)
+                   break;
+               c_token++;
+               arrow->head_filled = 1;
+               continue;
+           }
+           if (almost_equals(c_token, "nofill$ed")) {
+               if (set_headfilled++)
+                   break;
+               c_token++;
+               arrow->head_filled = 0;
+               continue;
+           }
+
+           if (equals(c_token, "size")) {
+               struct position hsize;
+               if (set_headsize++)
+                   break;
+               hsize.scalex = hsize.scaley = hsize.scalez = first_axes;
+               /* only scalex used; scaley is angle of the head in [deg] */
+               c_token++;
+               if (END_OF_COMMAND)
+                   int_error(c_token, "head size expected");
+               get_position(&hsize);
+               arrow->head_length = hsize.x;
+               arrow->head_lengthunit = hsize.scalex;
+               arrow->head_angle = hsize.y;
+               arrow->head_backangle = hsize.z;
+               /* invalid backangle --> default of 90.0° */
+               if (arrow->head_backangle <= arrow->head_angle)
+                   arrow->head_backangle = 90.0;
+               continue;
+           }
+
+           if (equals(c_token, "back")) {
+               if (set_layer++)
+                   break;
+               c_token++;
+               arrow->layer = 0;
+               continue;
+           }
+           if (equals(c_token, "front")) {
+               if (set_layer++)
+                   break;
+               c_token++;
+               arrow->layer = 1;
+               continue;
+           }
+
+           /* pick up a line spec - allow ls, but no point. */
+           {
+               int stored_token = c_token;
+               lp_parse(&arrow->lp_properties, TRUE, FALSE);
+               if (stored_token == c_token || set_line++)
+                   break;
+               continue;
+           }
+
+           /* unknown option caught -> quit the while(1) loop */
+           break;
+       }
+
+       if (set_layer>1 || set_line>1 || set_head>1 || set_headsize>1 || set_headfilled>1)
+           int_error(c_token, "duplicated arguments in style specification");
+
+#if defined(__FILE__) && defined(__LINE__) && defined(DEBUG_LP)
+       arrow->layer = 0;
+       arrow->lp_properties = tmp_lp_style;
+       arrow->head = 1;
+       arrow->head_length = 0.0;
+       arrow->head_lengthunit = first_axes;
+       arrow->head_angle = 15.0;
+       arrow->head_backangle = 90.0;
+       arrow->head_filled = 0;
+       fprintf(stderr,
+               "arrow_properties at %s:%d : layer: %d, lt: %d, lw: %.3f, head: %d, headlength/unit: %.3f/%d, headangles: %.3f/%.3f, headfilled %d\n",
+               __FILE__, __LINE__, arrow->layer, arrow->lp_properties.l_type,
+               arrow->lp_properties.l_width, arrow->head, arrow->head_length,
+               arrow->head_lengthunit, arrow->head_angle,
+               arrow->head_backangle, arrow->head_filled);
+#endif
+    }
+}
+